高并发场景:秒杀商品。

秒杀一般出现在商城的促销活动中,指定了一定数量(比如:1000个)的商品(比如:手机),以极低的价格(比如:0.1元),让大量用户参与活动,但只有极少数用户能够购买成功.

示例代码

@RestController
public class IndexController { @autowired
private Redisson redisson;
@Autowired
private StringRedisTemplate stringRedisTemplate @RequestMapper("/stock/deduct_stock")
public String deductStock(){ int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock")); // jedis.get("stock") if(stock > 0){
int realStock = stock - 1;
stringRedisTemplate.opsForValue().set("stock",realStock + ""); // jedis.set(key,value) System.out.print("======商品扣减成功,剩余库存:"+ realStock); } else {
System.out.print("======商品扣减失败======");
} return "---=== end ===---";
} }

问题

假设10个线程同时扣减1000个商品,应该剩余990 个商品,但实际扣减剩余999个,发生超卖问题。

  • 解决方案

加锁。synchronized(this)

jdk 内置锁在分布式环境下并不能保证成功。

模拟高并发场景,部署测试环境。

可用jMeter 工具进行压力测试。

添加ThreadGroup,并在栏目下添加 Http Request。配置并发计划。

可选择添加Aggregate Report 报告。

完整代码:


@RequestMapper("/stock/deduct_stock")
public String deductStock(){ synchronized(this){
int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock")); if(stock > 0){
int realStock = stock - 1;
stringRedisTemplate.opsForValue().set("stock",realStock + ""); System.out.print("======商品扣减成功,剩余库存:"+ realStock); } else {
System.out.print("======商品扣减失败======");
} }
return "---=== end ===---";
}

启动多台服务器,测试日志结果可知,发生超卖问题。

添加分布式锁

redis 命令setnx key value

setnx 是“set if not exists” 的缩写。

含义是:

  • 将key 的值设为value, 当且仅当key 不存在。
  • 若给定的key 已经存在,则setnx 不做任何操作。

注:加锁完成后,要删除锁。

完整代码:

    String lockKey = "lock:product_100";
Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lock,"amumu") if (!result){
return "---=== error_code ---===";
} int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock")); if(stock > 0){
int realStock = stock - 1;
stringRedisTemplate.opsForValue().set("stock",realStock + ""); System.out.print("======商品扣减成功,剩余库存:"+ realStock); } else {
System.out.print("======商品扣减失败======");
} stringRedisTemplate.delete(lockkey);
return "---=== end ===---";

分布式锁下的原子性问题优化

发现问题
  • 在set过程中程序异常,则程序无法继续执行,造成死锁。
//获取异常并抛异常
try{ } finally{ }
  • 在set 过程中,系统宕机,或系统重启。
Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockkey,"ammumu");

//添加过期时间
stringRedisTemplate.expire(lockkey,10,TimeUnit.SECONDS);

上述代码执行过程中,程序异常情况下也会导致系统宕机,发生原子性问题,

Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockkey,"amumu",10, TimeUnit.SECONDS);

分布式锁的失效情况优化

  • 假设 10s 的过期时间。

假设多个请求,每个程序执行超过10s, 此时锁已过期。此时程序执行删除锁,导致此时锁失效。

分析问题:加锁过程,被其他线程执行删锁。

解决方案:加上锁标识,删除时判断是否是同一标识。

String clientId = UUID.randomUUID().toString();
// 加上cliendId 锁标识
Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockkey,clientId, 10, TimeUnits.SECONDS); //删除锁时,进行判断
if(clientId.equals(stringRedisTemplate.opsForValue().get(lockKey))){
stringRedisTemplate.delete(lockkey);
}
  • 假设执行删除锁前,锁快过期未过期。下一个并发线程执行加锁操作,此时上一个并发线程,执行了删除操作。锁依然会失效。

解决方案:锁续命。如redisson

参考:github.com/redisson/redisson

每隔10s 检查是否还持有锁,如果持有,则延长30s锁的时间

参考:redis lua 脚本

RLock redissonLock = redisson.getLock(lock); // 获取锁对象
redissonLock.lock(); // 加锁 相当于 setIfAbsent(lockkey,"amumu",10, TimeUnit.SECONDS); redissonLock.unlock();

完整代码:

    @autowired
private Redisson redisson;
@Autowired
private StringRedisTemplate stringRedisTemplate @RequestMapper("/stock/deduct_stock")
public String deductStock(){ String lockKey = "lock:product_101"; RLock redissonLock = redisson.getLock(lockKey);
redissonLock.lock(); // 加锁 try{
int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock")); if(stock > 0){
int realStock = stock - 1;
stringRedisTemplate.opsForValue().set("stock",realStock + ""); System.out.print("======商品扣减成功,剩余库存:"+ realStock); } else {
System.out.print("======商品扣减失败======");
}
}finally{
redissonLock.unlock();
} return "---=== end ===---";
}
锁续命业务优化
  • 问题1 :线程1 加锁,此时未同步给从结点,主结点挂了。此时从结点很可能会被选为主结点。这时一个线程3,同时请求此操作,此时线程3 操作新选为主结点redis 加锁,此时程序继续执行,执行有并发安全问题的代码。

  • 问题2 :redis 语义上,是把高并发场景下的问题串行化了,这当然不会有并发问题,但是对整个系统的性能有影响。当然redis 的性能已经是非常不错了。

  • 问题3:redissonLock.unLock() 底层默认的是非公平的抢锁机制,while 循环尝试去加锁。

redis 与 zookeeper 分布式集群方案对比

根据CAP 理论:

redis 集群更多是保证AP, 即可用性。

zookeeper 集群更多是保证CP, 即一致性,ZAB写数据机制保证,不会有锁丢失的问题。

redlock 实解决redis 集群环境锁丢失问题

想用redis 的高性能,但是又不想丢失锁,有很多方法。

比如 Redlock 实现。

java client 加锁三个以上的锁,超过半数redis 节点加锁成功才算加锁成功。

  • 问题:千万不要用从节点。要保证集群高可用,多加几个节点。
  • 问题:持久化。一般用aof 做持久化,假设设置1s 持久化一次。

    这样就设置一个key,设置2个key 时系统宕机或重启。client2 执行请求,key3 加锁成功,key2 重启后加锁成功,则加锁成功。系统继续执行的是并发安全问题的代码。如果用时时持久化设置,则性能会大大降低,还不如用zookeeper.

高并发分布式锁如何实现

分段加锁逻辑。

未完待续

by: 一只阿木木

生产级Redis 高并发分布式锁实战1:高并发分布式锁如何实现的更多相关文章

  1. java亿级流量电商详情页系统的大型高并发与高可用缓存架构实战视频教程

    亿级流量电商详情页系统的大型高并发与高可用缓存架构实战 完整高清含源码,需要课程的联系QQ:2608609000 1[免费观看]课程介绍以及高并发高可用复杂系统中的缓存架构有哪些东西2[免费观看]基于 ...

  2. 15套java架构师、集群、高可用、高可扩展、高性能、高并发、性能优化、Spring boot、Redis、ActiveMQ、Nginx、Mycat、Netty、Jvm大型分布式项目实战视频教程

    * { font-family: "Microsoft YaHei" !important } h1 { color: #FF0 } 15套java架构师.集群.高可用.高可扩展. ...

  3. java架构师负载均衡、高并发、nginx优化、tomcat集群、异步性能优化、Dubbo分布式、Redis持久化、ActiveMQ中间件、Netty互联网、spring大型分布式项目实战视频教程百度网盘

    15套Java架构师详情 * { font-family: "Microsoft YaHei" !important } h1 { background-color: #006; ...

  4. 15套java架构师、集群、高可用、高可扩 展、高性能、高并发、性能优化Redis、ActiveMQ、Nginx、Mycat、Netty、Jvm大型分布式项目实战视频教程

    * { font-family: "Microsoft YaHei" !important } h1 { color: #FF0 } 15套java架构师.集群.高可用.高可扩 展 ...

  5. java-spring基于redis单机版(redisTemplate)实现的分布式锁+redis消息队列,可用于秒杀,定时器,高并发,抢购

    此教程不涉及整合spring整合redis,可另行查阅资料教程. 代码: RedisLock package com.cashloan.analytics.utils; import org.slf4 ...

  6. 基于Redis实现分布式锁实战

    背景在很多互联网产品应用中,有些场景需要加锁处理,比如:秒杀,全局递增ID,楼层生成等等.大部分的解决方案是基于DB实现的,Redis为单进程单线程模式,采用队列模式将并发访问变成串行访问,且多客户端 ...

  7. 15套java互联网架构师、高并发、集群、负载均衡、高可用、数据库设计、缓存、性能优化、大型分布式 项目实战视频教程

    * { font-family: "Microsoft YaHei" !important } h1 { color: #FF0 } 15套java架构师.集群.高可用.高可扩 展 ...

  8. Java 架构师+高并发+性能优化+Spring boot大型分布式项目实战

    视频课程内容包含: 高级 Java 架构师包含:Spring boot.Spring cloud.Dubbo.Redis.ActiveMQ.Nginx.Mycat.Spring.MongoDB.Zer ...

  9. Redis分布式锁实战

    什么是分布式锁 在单机部署的情况下,要想保证特定业务在顺序执行,通过JDK提供的synchronized关键字.Semaphore.ReentrantLock,或者我们也可以基于AQS定制化锁.单机部 ...

  10. Redsi缓存问题(穿透,击穿,雪崩)以及解决办法(分布式锁)【高并发问题】

    Redsi常见问题 缓存在高平发和安全压力下的一些问题 缓存击穿 是某一个热点key在高并发访问的情况下,突然失效,导致大量的并发大金mysql数据库的情况 缓存穿透 是利用redis和mysql的机 ...

随机推荐

  1. Java权限认证框架比较

    认证.授权.鉴权和权限控制 定义 英文 实现方式 认证 确认声明者的身份 identification 根据声明者独特的识别信息 授权 获取用户的委派权限 authorization 颁发一个授信媒介 ...

  2. Java Objects工具类重点方法使用

    Objects工具类 jdk 1.7引进的工具类,都是静态调用的方法,jdk 1.8新增了部分方法 重点方法 equals 用于字符串和包装对象的比较,先比较内存地址,再比较值 deepEquals ...

  3. SqlParameter,参数化查询问题

    SqlParameter p = new SqlParameter("@pageIndex", (object)pageIndex); SqlParameter带有两个参数的构造函 ...

  4. 基于微信小程序+Springboot校园二手商城系统设计和实现

    \n文末获取源码联系 感兴趣的可以先收藏起来,大家在毕设选题,项目以及论文编写等相关问题都可以给我加好友咨询 一. 前言介绍: 在当今社会的高速发展过程中,产生的劳动力越来越大,提高人们的生活水平和质 ...

  5. JavaScript中的new map()和new set()使用详细(new map()和new set()的区别)

    简介:new Map(): 在JavaScript中,new Map()用于创建一个新的 Map 对象.Map 对象是一种键值对的集合,其中的键是唯一的,值可以重复.new Set(): 在JavaS ...

  6. 在Centos7中使用一键脚本安装Oracle11g

    在Centos7中使用一键脚本安装Oracle11g 1. 环境准备 1.1 系统版本:Centos7.9(2009) 1.2 Oracle版本:Oracle 11g 11.2.0.4 1.3 网络需 ...

  7. 第零讲:基础架构:一条SQL查询语句是如何执行的

    目录 第零讲:基础架构:一条SQL查询语句是如何执行的 正确的认识事物的方式方法(极为重要): sql语句内部的执行过程:(极为重要) MySQL 可以分为 Server 层和存储引擎层两部分. Se ...

  8. 【MySQL】Navicat踩坑:Illegal mix of collations (utf8mb4_0900_ai_ci,IMPLICIT) and (utf8mb4_general_ci,IMPLICIT) for operation 'instr'

    在Navicat客户端上面执行SQL报错 SQL语句: WITH RECURSIVE transfer (start_station, stop_station, stops, path) AS ( ...

  9. 【Redis】04 配置文件分析

    配置文件Redis.conf注释信息: 1.启动项: 启动Redis要求必须加上配置文件redis.conf路径作为第一参数加载 文档样例: ./redis-server /path/to/redis ...

  10. 绑定国内主机IP的域名网站必须要备案

    买了个域名: http://devilmaycry812839668.top/ 然后绑定了国内的一个云主机,刚搭了个web server,一个网页都没有(短期内页没考虑做网页): 今天看了下web s ...