Redis高并发分布式锁详解

为什么需要分布式锁1.为了解决Java共享内存模型带来的线程安全问题 , 我们可以通过加锁来保证资源访问的单一 , 如JVM内置锁synchronized , 类级别的锁ReentrantLock 。
2.但是随着业务的发展 , 单机服务毕竟存在着限制 , 故会往多台组合形成集群架构 , 面对集群架构 , 我们同样存在则资源共享问题 , 而每台服务器有着自己的JVM , 这时候我们对于锁的实现不得不考虑分布式的实现 。
分布式锁应该具备哪些条件1.在分布式系统环境下 , 一个方法在同一时间只能被一个机器的一个线程执行
2.高可用的获取锁与释放锁
3.高性能的获取锁与释放锁
4.具备可重入特性(可理解为重新进入 , 由多于一个任务并发使用 , 而不必担心数据错误)
5.具备锁失效机制 , 即自动解锁 , 防止死锁
6.具备非阻塞锁特性 , 即没有获取到锁将直接返回获取锁失败
秒杀抢购场景模拟(模拟并发问题:其实就是指每一步如果存在间隔时间 , 那么当某一线程间隔时间拉长 , 会对其余线程造成什么影响)0.如果要在本机测试的话
1)配置Nginx实现负载均衡
http {upstream testfuzai {server 127.0.0.1:8080 weight=1;server 127.0.0.1:8090 weight=1;}server {listen 80;server_name localhost;location / {//proxy_pass:设置后端代理服务器的地址 。这个地址(address)可以是一个域名或ip地址和端口 , 或者一个 unix-domain socket路径 。proxy_pass http://testfuzai;proxy_set_header Host $proxy_host;}}}2)启动redis设置好参数与数量
3)启动项目并分别配置不同端口(要与Nginx里面的一致)
4)进行压测 , 通过jmeter的Thread Group里面编辑好HTTP Request , 设置参数 线程数 Number of Threads 【设置为200】  , 请求的重复次数 Loop count 【设置为5】  , Ramp-up period(seconds)线程启动开始运行的时间间隔(单位是秒)【设置为1】 。则 , 一秒内会有1000个请求打过去 。
1.不加锁进行库存扣减的情况:
代码示例
@RequestMapping("/deduct_stock")public String deductStock() {//从redis取出库存int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));if (stock > 0) {int realStock = stock - 1;//往redis写入库存stringRedisTemplate.opsForValue().set("stock", realStock + "");System.out.println("扣减成功 , 剩余库存:" + realStock);} else {System.out.println("扣减失败 , 库存不足");}return "end";}发现说明
1)通过打印输出 , 我们会发现两台机器上会出现重复的值(即出现了超卖现象) 。甚至会出现另一台服务器的数据覆盖本服务器的数据 。
2)原因在于读取数据和写入数据存在时间差 , 如两个服务器Q1和Q1 , Q1有请求 , 获取库存【假设300】 , 在库存判断大小之后进行扣减库存如果慢了【假设需要3秒】 , 那么Q2有5次请求 , 获取到库存 , 扣减完后设置 , 依次5次 , 则库存为【295】 。但是此时Q1完成自身请求又会把库存设置为【299】 。故不合理 。所以应该改为使用stringRedisTemplate.boundValueOps("stock").increment(-1); 改为采用redis内部扣除 , 减少了超卖的个数 。但是就算改了也只是避免了覆盖问题 , 仍然没有解决超卖问题 。如果有6台服务器 , 库存剩下1个的时候六个请求同时进入到扣减库存这一步 , 那么就会出现超卖5个的现象(这也是超卖个数最多的现象) 。

经验总结扩展阅读