高并发核心技术 - 幂等性与分布式锁

高并发核心技术之 - 幂等性

1. 什么是幂等性

幂等性就是指:一个幂等操作任其执行多次所产生的影响均与一次执行的影响相同。
用数学的概念表达是这样的: f(f(x)) = f(x).
就像 nx1 = n 一样, x1 就是一个幂等操作。无论是乘以多少次结果都一样。

2. 常见的幂等性问题

幂等性问题经常会是由网络问题引起的,还有重复操作引起的。

场景一:比如点赞功能,一个用户只能对同一片文章点赞一次,重复点赞提示已经点过赞了。

示例代码:

    public void like(Article article,User user) {
    //检查是否点过赞
    if (checkIsLike(article,user)) {
    //点过赞了
    throw new ApiException(CodeEnums.SYSTEM_ERR);
}
else {
    //保存点赞
    saveLike(article,user);
}
}
</pre>

看上去好像没有什么问题,保存点赞之前已经检查过是否点赞了,理论上同一个人不会对同一篇文章重复点赞。但实际不是这样的。因为网络请求不是排队进来的,而是一窝蜂涌进来的。

某些时候,用户网络不好,可能很短的时间内点击了多次,由于网络传输问题,这些请求可能会同时来到我们的服务器。

  • 第一个请求 checkIsLike() 返回 false , 正在执行 saveLike() 操作,还没来的及提交事务
  • 第二个请求过来了 ,checkIsLike() 返回 也是 false , 并去 执行了 saveLike() 操作

这样子,就造成了一个用户同时对一篇文章进行了多次点赞操作。

这就是典型的幂等性问题, 操作了一次和操作了两次结果不一样,因为你多点了一次赞,按照幂等性原则 不管你点击了多少次结果都一样,只点了一次赞。

很多场景都是这样造成的,比如用户重复下单,重复评论,重复提交表单等。

那怎么解决呢?
假设网络的请求是排队进来的就不会出现这个问题了。

于是我们可以改成这样:

public synchronized void like(Article article,User user) {
    //检查是否点过赞
if (checkIsLike(article,user)) {
    //点过赞了
throw new ApiException(CodeEnums.SYSTEM_ERR);
}
else {
    //保存点赞
saveLike(article,user);
}
}
</pre>

synchronized 同步锁 这样我们的请求就会乖乖的排队进来了。

PS :这样做是效率比较低的做法,不建议这么做,只是举例子,synchronized 也不适合分布式集群场景。

场景二 : 第三方回调

我们系统经常需要和第三方系统打交道,比如微信充值,支付宝充值什么的,微信和支付宝常常会以回调你的接口通知你支付结果。为了保证你能收到回调,往往可能会回调多次。

有时候我们也为了保证数据的准确性会有个定时器去查询支付结果未知的流水,并执行响应的处理。
如果定时器的轮训和回调刚好是在同时进行,这可能又出BUG了,又进行了两次重复操作。

那么问题来了
假设我是一个充值操作, 回调回来的时候 ,会做业务处理,成功了给用户账户加钱。这是后就要保证幂等性了, 假设微信同一笔交易给你回调了两次,如果你给用户充值了两次,这显然不合理(我是老板肯定扣你工资),所以要保证 不管微信回调你多少次 ,同一笔交易你只能给用户充一次钱。这就幂等性。

解决幂等性问题方案

  • synchronized
    适合单机应用,不追求性能 ,不追求并发。
  • 分布式锁
    但是往往我们的应用是分布式的集群,并且很讲究性能,并发,所以我们需要用到 分布式锁 来解决这个问题。

Redis 分布式锁:

/**
* setNx
*
*  @param key
*  @param value
*  @return
*/
public Boolean setNx(String key,Object value) {
    return redisTemplate.opsForValue().setIfAbsent(key,value);
}
/**
*  @param key 锁
*  @param waitTime 等待时间  毫秒
*  @param expireTime 超时时间  毫秒
*  @return
*/
public Boolean lock(String key,Long waitTime,Long expireTime) {
    String vlaue =  UUIDUtil.mongoObjectId();
    Boolean flag = setNx(key,vlaue);
    //尝试获取锁  成功返回
if (flag) {
    redisTemplate.expire(key,expireTime,TimeUnit.MILLISECONDS);
    return flag;
}
else {
    //失败
//现在时间
long newTime =  System.currentTimeMillis();
    //等待过期时间
long loseTime = newTime + waitTime;
    //不断尝试获取锁成功返回
while (System.currentTimeMillis()  < loseTime) {
    Boolean testFlag = setNx(key,vlaue);
    if (testFlag) {
    redisTemplate.expire(key,expireTime,TimeUnit.MILLISECONDS);
    return testFlag;
}
//休眠100毫秒
try {
    Thread.sleep(100);
}
catch (InterruptedException e) {
    e.printStackTrace();
}
}}return false;}/**
*  @param key
*  @return
*/
public Boolean lock(String key) {
    return lock(key,1000L,60  *  1000L);
}
/**
*  @param key
*/
public void unLock(String key) {
    remove(key);
}
</pre>

利用Redis 分布式锁 我们的代码可以改成这样:

public void like(Article article,User user) {
    String key =  "key:like"  + article.getId()  +  ":"  + user.getUserId();
    //  等待锁的时间  0  ,  过期时间  一分钟防止死锁
boolean flag = redisService.lock(key,0,60  *  1000L);
    if(!flag) {
    //获取锁失败  说明前面的请求已经获取了锁
throw new ApiException(CodeEnums.SYSTEM_ERR);
}
//检查是否点过赞
if (checkIsLike(article,user)) {
    //点过赞了
throw new ApiException(CodeEnums.SYSTEM_ERR);
}
else {
    //保存点赞
saveLike(article,user);
}
//删除锁
redisService.unLock(key);
}
</pre>

key 的设计也很讲究:
数据不冲突的两个业务场景,key不能冲突,不同人的key也不一样,不同的文章Key也不一样。
根据场景业务设定。

一个原则: 尽可能的缩小key的范围。?这样才能增强我们的并发。

首先我们先获取锁,获取锁成功 执行完操作,保存数据 ,删除锁。获取不到锁返回失败。设置过期时间是为了防止‘死锁’,比如机器获取到了 锁,没有设置过期时间,但是他死机了,没有删除释放锁。

  • 版本号控制
    CAS 算法: CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。这个比较繁杂,有兴趣的同学可以去看看。

原文地址:https://blog.51cto.com/14230003/2422709

时间: 2024-10-12 12:34:03

高并发核心技术 - 幂等性与分布式锁的相关文章

Java生鲜电商平台-高并发核心技术订单与库存实战

Java生鲜电商平台-高并发核心技术订单与库存实战 一. 问题 一件商品只有100个库存,现在有1000或者更多的用户来购买,每个用户计划同时购买1个到几个不等商品. 如何保证库存在高并发的场景下是安全的? (1)不多发 (2)不少发 二. 下单的步骤 (1)下单 (2)下单同时预占库存 (3)支付 (4)支付成功真正减扣库存 (5)取消订单 (6)回退预占库存 三. 什么时候进行预占库存? (1)方案一:加入购物车的时候去预占库存 (2)方案二:下单的时候去预占库存 (3)方案三:支付的时候去

高并发核心技术 - 订单与库存

问题:一件商品只有100个库存,现在有1000或者更多的用户来购买,每个用户计划同时购买1个到几个不等商品.如何保证库存在高并发的场景下是安全的.1.不多发2.不少发 下单涉及的一些步骤1.下单2.下单同时预占库存3.支付4.支付成功真正减扣库存5.取消订单6.回退预占库存 什么时候进行预占库存方案一:加入购物车的时候去预占库存.方案二:下单的时候去预占库存.方案三:支付的时候去预占库存.分析:方案一:加入购物车并不代表用户一定会购买,如果这个时候开始预占库存,会导致想购买的无法加入购物车.而不

【原码】java 高并发、高性能、分布式 java后台框架

获取[下载地址]   QQ: 313596790A 代码生成器(开发利器);     增删改查的处理类,service层,mybatis的xml,SQL( mysql   和oracle)脚本,   jsp页面 都生成   就不用写搬砖的代码了,生成的放到项目里,可以直接运行B 阿里巴巴数据库连接池druid;  数据库连接池  阿里巴巴的 druid.Druid在监控.可扩展性.稳定性和性能方面都有明显的优势C 安全权限框架shiro ;  Shiro 是一个用 Java 语言实现的框架,通过

java 高并发、高性能、分布式 java后台框架 springmvc整合mybatis框架源码

获取[下载地址]   QQ: 313596790   [免费支持更新]三大数据库 mysql  oracle  sqlsever   更专业.更强悍.适合不同用户群体[新录针对本系统的视频教程,手把手教开发一个模块,快速掌握本系统]A 集成代码生成器(开发利器);                                         技术:313596790   增删改查的处理类,service层,mybatis的xml,SQL( mysql   和oracle)脚本,   jsp页面

高并发、高性能、分布式 java后台框架

获取[下载地址]   QQ: 313596790   [免费支持更新]A 代码生成器(开发利器);全部是源码     增删改查的处理类,service层,mybatis的xml,SQL( mysql   和oracle)脚本,   jsp页面 都生成   就不用写搬砖的代码了,生成的放到项目里,可以直接运行B 阿里巴巴数据库连接池druid;  数据库连接池  阿里巴巴的 druid.Druid在监控.可扩展性.稳定性和性能方面都有明显的优势C 安全权限框架shiro ;  Shiro 是一个用

微服务、分布式、高并发都不懂,你拿什么去跳槽?

微服务架构 BAT互联网架构这些年的演进分析 国内外常见分布式系统架构状况介绍 微服务架构指南:领域驱动设计DDD模型 SpringCloud1-2实战篇 Config分布式配置中心 Eureka注册与发现机制 Ribbon客户端负载均衡 Hystrix服务熔断组件 Feign声明式服务调用 Zuul网关服务 项目实战:SpringCloud微服务架构 4.1 高并发分布式技术专题 - 分布式开发技术 4.1.1 RPC 4.1.2 分布式系统指挥官Zookeeper 4.1.3 Dubbo框架

怎么理解分布式、高并发、多线程?(含面试题和答案解析)

看到分布式.高并发.多线程这三个词的时候,很多人是不是都认为分布式=高并发=多线程?当面试官问到高并发系统可以采用哪些手段来解决,或者被问到分布式系统如何解决一致性的问题,是不是一脸懵逼?确实,在一开始接触的时候,不少人都会分布式.高并发.多线程将三者混淆,误以为所谓的分布式高并发的系统就是能同时供海量用户访问,而采用多线程手段不就是可以提供系统的并发能力吗?实际上,他们三个总是相伴而生,但侧重点又有不同. 接下来我就看看分布式.高并发.多线程这三者之间到底有什么区别? 什么是分布式? 分布式更

Java高并发高性能分布式框架从无到有微服务架构设计

微服务架构模式(Microservice Architect Pattern).近两年在服务的疯狂增长与云计算技术的进步,让微服务架构受到重点关注 微服务架构是一种架构模式,它提倡将单一应用程序划分成一组小的服务,服务之间互相协调.互相配合,为用户提供最终价值.每个服务运行在其独立的进程中,服务与服务间采用轻量级的通信机制互相沟通(通常是基于HTTP的RESTful API).每个服务都围绕着具体业务进行构建,并且能够被独立地部署到生产环境.类生产环境等.另外,应尽量避免统一的.集中式的服务管理

分布式=高并发=多线程

当提起这三个词的时候,是不是很多人都认为分布式=高并发=多线程? 当面试官问到高并发系统可以采用哪些手段来解决,或者被问到分布式系统如何解决一致性的问题,是不是一脸懵逼? 确实,在一开始接触的时候,不少人都会将三者混淆,误以为所谓的分布式高并发的系统就是能同时供海量用户访问,而采用多线程手段不就是可以提供系统的并发能力吗?实际上,他们三个总是相伴而生,但侧重点又有不同. 什么是分布式? 分布式更多的一个概念,是为了解决单个物理服务器容量和性能瓶颈问题而采用的优化手段.该领域需要解决的问题极多,在