幂等性的解决方案
什么是幂等性?
对于同一笔业务操作,不管调用多少次,结果都是一样的。
幂等性设计 比如充值功能,我们需要提供给支付宝一个充值成功后的回调接口,支付宝回调信息中携带订单号(商户系统中唯一)、交易号(支付宝中唯一)。支付宝为了保证商户系统的接口调用成功,有可能会多次调用商户的回调接口。如果不做幂等性设计,则本地可能会给用户加两次余额。
1.Lock锁
- 接收到支付宝支付成功的回调请求
- 调用java中的lock锁
- 根据订单号查询当前订单是否已经处理过。
- 如果没有被处理继续执行
- 开启本地事务
- 给用户加钱
- 设置订单状态为成功
- 提交事务,释放lock锁
但是lock锁只能在单机模式下生效,因此需要分布式锁
2.悲观锁
接收到支付宝支付成功的回调请求
开启本地事务
根据订单号查询当前订单是否已经处理过并且加悲观锁。
如果没有被处理继续执行
开启本地事务
给用户加钱
设置订单状态为成功
提交事务,释放lock锁
重点在于for upate,
当线程A执行for update,数据库会对当前记录加锁,当其他线程执行到此行代码时,会等线程A释放锁之后才获取锁,继续后续操作。
事务提交后,线程A自动释放锁。
会严重影响性能
3.乐观锁
这里用乐观锁来实现,使用status=0作为条件更新。多个线程同时执行这条sql语句时,数据库会保证update同一条记录会排队执行,最终只有一条update执行成功。其他未成功的影响行数为0,可以根据影响行数来提交或者回滚操作。
4.唯一约束
依赖数据库中的唯一约束实现。需要创建一张表来保存订单号执行记录。并把订单号设置为唯一。
- 接收到支付宝支付成功的回调请求
- 根据订单号查询执行表,可判断订单是否已处理
- 根据订单号查询当前订单是否已经处理过。
- 如果没有被处理继续执行
- 开启本地事务
- 给用户加钱
- 设置订单状态为成功
- 向订单执行表中添加一条记录。如果插入失败,则回滚本地事务,插入成功,提交事务。
- 因为订单号唯一,所以执行表中不能存在两条订单号相同的数据,最终只会一个操作成功,从而保证幂等性。不过在业务量大的场景下,执行表插入数据会成为系统平静。需要考虑分表操作解决性能问题。
- 插入操作会锁定表,所以执行表中新增记录要放在最后执行。提高系统并发性。
5.Redis实现分布式锁
在高并发场景下,需要依靠Redis实现分布式锁,但需要满足下面4个条件
互斥性。在任意时刻,只有一个客户端能持有锁。
不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。
具有容错性。只要大部分的Redis节点正常运行,客户端就可以加锁和解锁。
解铃还须系铃人。加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了。
执行过程:
- 接收到支付宝支付成功的回调请求
- 通过Redis获取锁
负载均衡算法
随机算法
public class RandomTest {
private static final List<String> servers = Arrays.asList("192.168.1.1", "192.168.1.2", "192.168.1.3", "192.168.1.4");
public static String getServer() {
Random random = new Random();
int index = random.nextInt(servers.size());
return servers.get(index);
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
String server = getServer();
System.out.println("select server: "+server);
}
}
}
当样本较小时,算法可能分布不均匀,但根据概率论,样本越大,负载会越均匀,而负载均衡算法本来就是为应对高并发场景而设计的。该算法的另一个缺点是所有机器都有相同的访问概率, 如果服务器性能不同,负载将不平衡。
轮询算法
就是四台机器的话,1到服务器1,2到服务器2,3到服务器3,4到服务器4,5到服务器1······
我们可以看到round-robin算法可以在集群中均匀的分配请求。但是,该算法具有与随机算法相同的缺点,如果服务器性能不同,负载将不平衡,因此需要加权轮询算法。
直接用%即可实现
加权轮询算法
Weighted Round-Robin加权轮询算法是在round-robin算法的基础上根据服务器的性能分配权重。服务器能支持的请求越多,权重就越高,分配的请求也就越多。对于同样的10个请求,使用加权轮询算法的请求分布会如下图所示:
一致性哈希
(207条消息) 一致性哈希算法原理详解_张维鹏的博客-CSDN博客
- 步骤一:一致性哈希算法将整个哈希值空间按照顺时针方向组织成一个虚拟的圆环,称为 Hash 环;
- 步骤二:接着将各个服务器使用 Hash 函数进行哈希,具体可以选择服务器的IP或主机名作为关键字进行哈希,从而确定每台机器在哈希环上的位置
- 步骤三:最后使用算法定位数据访问到相应服务器:将数据key使用相同的函数Hash计算出哈希值,并确定此数据在环上的位置,从此位置沿环顺时针寻找,第一台遇到的服务器就是其应该定位到的服务器
死锁问题
死锁发生的条件
- 互斥条件:指某个资源同时只能被一个进程或线程占用,如果有多个进程或线程同时需要占用该资源,则必须等待。
- 请求与保持条件:指进程或线程已经占用了至少一个资源,但又提出了新的资源请求,但该请求无法满足,因此进程或线程被阻塞,但又继续保持已经获得的资源。
- 不可剥夺条件:指已经分配给进程或线程的资源,在未经该进程或线程许可之前,不能被其他进程或线程抢占或强制回收。
- 循环等待条件:指存在多个进程或线程形成一种环路等待资源,即每个进程或线程都在等待下一个进程或线程所占用的资源
死锁是广义的,并不一定说要锁才会出现死锁,指的是2个或多个线程都在等待其他线程放弃资源自己才会继续运行,那么要是全部线程都处于这个状态,就会造成死锁问题
举例
- A在等B释放锁,B在等A释放锁,此时死锁问题
- 线程池的死锁问题:执行父任务和子任务共用同一个线程池,如果高并发情况下,线程池所有线程都用来执行父任务,那么在执行到子任务时就会由于线程池中没有线程而阻塞住
GET和POST方法有什么区别
-
GET方法是用于向指定的资源获取数据,无需在请求中附加任何请求体(request body),只需要在URL中添加参数即可。其特点是请求参数会附加在URL的后面,在浏览器中也可以看到,因此不适合用于传输敏感数据。GET方法的请求是幂等的,也就是说,多次请求同一个URL得到的响应是一样的。
- GET的参数是存放在url中的
- localhost:20000/template/getBatch?ids=2
-
POST方法是用于向指定的URL提交数据,数据在请求体中进行传输。POST方法相对于GET方法传输的数据量更大,因此可以用来传输敏感数据。POST方法的请求不是幂等的,也就是说,多次请求同一个URL得到的响应可能不一样。此外,POST方法还可以用于上传文件等复杂操作
-
POST的参数是存放在json中的
-
localhost:20000/template/search ,还携带有body
-
{
"pageSize":1,
"page":0,
"type":1
}
-