【连载】redis库存操作,分布式锁的四种实现方式[一]--基于zookeeper实现分布式锁
一、背景
在电商系统中,库存的概念一定是有的,例如配一些商品的库存,做商品秒杀活动等,而由于库存操作频繁且要求原子性操作,所以绝大多数电商系统都用Redis来实现库存的加减,最近公司项目做架构升级,以微服务的形式做分布式部署,对库存的操作也单独封装为一个微服务,这样在高并发情况下,加减库存时,就会出现超卖等问题,这时候就需要对库存操作做分布式锁处理。最近对分布式锁的实现以及性能做了对比分析,今天记录下来,与君共勉。
二、分布式锁介绍
分布式锁主要用于在分布式环境中保护跨进程、跨主机、跨网络的共享资源实现互斥访问,以达到保证数据的一致性。
分布式锁要具有以下几个特性:
1、可以保证在分布式部署的应用集群中,同一个方法在同一时间只能被一台机器上的一个线程执行。
2、这把锁要是一把可重入锁(避免死锁)
3、这把锁最好是一把阻塞锁(根据业务需求考虑要不要这条)
4、有高可用的获取锁和释放锁功能
5、获取锁和释放锁的性能要好
三、分布式锁的几种实现方式
1.基于zookeeper实现分布式锁
2.采用中间件redisson提供分布式锁
3.采用redis的watch做分布式锁
4.采用redis的lua脚本编程方式实现分布式锁
四、基于zookeeper实现分布式锁的原理
1、zk的底层数据结构是树形结构,由一个一个的数据节点组成;
2、zk的节点分为永久节点和临时节点,客户端可以创建临时节点,当客户端会话终止或超时后,zk会自动删除临时节点,该特性可以避免死锁;
3、当节点的状态发生变化时,zk的watch机制会通知监听相应事件的客户端,该特性可以可以用来实现阻塞等待加锁;
4、客户端可以在某个节点下创建子节点,Zookeeper会根据子节点数量自动生成整数序号,类似于数据库的自增主键;
基于zk以上特性,可以实现分布式锁,思路为:
创建一个永久节点作为锁节点,试图加锁的客户端在锁节点下创建临时顺序节点。Zookeeper会保证子节点的有序性。若锁节点下id最小的节点是为当前客户端创建的节点,说明当前客户端成功加锁。否则加锁失败,订阅上一个顺序节点。当上一个节点被删除时,当前节点为最小,说明加锁成功。操作完成后,删除锁节点释放锁。
该方案的特征是优先排队等待的客户端会先获得锁,这种锁称为公平锁。而锁释放后,所有客户端重新竞争锁的方案称为非公平锁。
五、代码实现
1、引入相关zookeeper和curator相关jar
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.13</version>
<scope>compile</scope>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>4.0.1</version>
</dependency>
2、curatorFramework初始化,放spring容器中
/**
* curatorFramework初始化
*
* @author LiJunJun
* @date 2018/12/7
*/
@Configuration
public class CuratorBean { @Bean
public CuratorFramework curatorFramework() { RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
CuratorFramework client = CuratorFrameworkFactory.newClient("192.168.10.110:2381", retryPolicy);
return client;
} @Bean
public InterProcessMutex interProcessMutex() { curatorFramework().start(); return new InterProcessMutex(curatorFramework(), "/curator/lock");
}
}
3、业务代码
/**
* interProcessMutex
*/
@Resource
private InterProcessMutex interProcessMutex; /**
* 减库存(基于zookeeper分布式锁实现)
*
* @param trace 请求流水
* @param stockManageReq(stockId、decrNum)
* @return -1为失败,大于-1的正整数为减后的库存量,-2为库存不足无法减库存
*/
@Override
@ApiOperation(value = "减库存", notes = "减库存")
@RequestMapping(value = "/decrByStock", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_UTF8_VALUE, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public int decrByStock(@RequestHeader(name = "Trace") String trace, @RequestBody StockManageReq stockManageReq) { long startTime = System.nanoTime(); LOGGER.reqPrint(Log.CACHE_SIGN, Log.CACHE_REQUEST, trace, "decrByStock", JSON.toJSONString(stockManageReq)); int res = 0;
String stockId = stockManageReq.getStockId();
Integer decrNum = stockManageReq.getDecrNum(); // 添加分布式锁
boolean lockResult = false; try {
if (null != stockId && null != decrNum) { stockId = PREFIX + stockId; // 获取锁最多等待5s
lockResult = interProcessMutex.acquire(5, TimeUnit.SECONDS); if (!lockResult) {
LOGGER.info("本次请求获取锁失败,lockResult=1");
return -1;
} // redis 减库存逻辑
String vStock = redisStockPool.get(stockId); long realV = 0L;
if (StringUtils.isNotEmpty(vStock)) {
realV = Long.parseLong(vStock);
}
//库存数 大于等于 要减的数目,则执行减库存
if (realV >= decrNum) {
Long v = redisStockPool.decrBy(stockId, decrNum);
res = v.intValue();
} else {
res = -2;
}
}
} catch (Exception e) {
LOGGER.error(trace, "decr sku stock failure.", e);
res = -1;
} finally {
if (lockResult) {
try {
// 释放锁
interProcessMutex.release();
} catch (Exception e) {
e.printStackTrace();
}
}
LOGGER.respPrint(Log.CACHE_SIGN, Log.CACHE_RESPONSE, trace, "decrByStock", System.nanoTime() - startTime, String.valueOf(res));
}
return res;
}
六、ab压测结果分析
发现性能低的简直无法忍受,5000个请求,100并发量,tps仅有19.76,还有922个请求失败
统计日志中打印的获取锁失败的请求个数,发现等待5s后仍未获取到锁数目就是就是ab压测中失败的922个
压测过程中,我们可以看下zk的/curator/lock节点下的临时节点变化情况,我们连接zk客户端
./zkCli.sh -server 192.168.10.110:2381
查看目录节点
ls /curator/lock,发现/curator/lock创建了很多临时节点,并且随着请求的执行,临时节点也在不停的变化
七、总结
zookeeper确实可以实现分布式锁,但由于需要频繁的新增和删除节点,性能比较差,不推荐使用。
下一篇我们分享基于redisson中间件实现的分布式锁。
【连载】redis库存操作,分布式锁的四种实现方式[一]--基于zookeeper实现分布式锁的更多相关文章
- 【连载】redis库存操作,分布式锁的四种实现方式[二]--基于Redisson实现分布式锁
一.redisson介绍 redisson实现了分布式和可扩展的java数据结构,支持的数据结构有:List, Set, Map, Queue, SortedSet, ConcureentMap, L ...
- 【连载】redis库存操作,分布式锁的四种实现方式[三]--基于Redis watch机制实现分布式锁
一.redis的事务介绍 1. Redis保证一个事务中的所有命令要么都执行,要么都不执行.如果在发送EXEC命令前客户端断线了,则Redis会清空事务队列,事务中的所有命令都不会执行.而一旦客户端发 ...
- 【转载】Redis的Java客户端Jedis的八种调用方式(事务、管道、分布式…)介绍
转载地址:http://blog.csdn.net/truong/article/details/46711045 关键字:Redis的Java客户端Jedis的八种调用方式(事务.管道.分布式…)介 ...
- 分布式锁的两种实现方式(基于redis和基于zookeeper)
先来说说什么是分布式锁,简单来说,分布式锁就是在分布式并发场景中,能够实现多节点的代码同步的一种机制.从实现角度来看,主要有两种方式:基于redis的方式和基于zookeeper的方式,下面分别简单介 ...
- 【连载】redis库存操作,分布式锁的四种实现方式[四]--基于Redis lua脚本机制实现分布式锁
一.redis lua介绍 Redis 提供了非常丰富的指令集,但是用户依然不满足,希望可以自定义扩充若干指令来完成一些特定领域的问题.Redis 为这样的用户场景提供了 lua 脚本支持,用户可以向 ...
- Redis的Java客户端Jedis的八种调用方式(事务、管道、分布式)介绍
jedis是一个著名的key-value存储系统,而作为其官方推荐的java版客户端jedis也非常强大和稳定,支持事务.管道及有jedis自身实现的分布式. 在这里对jedis关于事务.管道和分布式 ...
- 基于ZooKeeper的分布式Session实现(转)
1. 认识ZooKeeper ZooKeeper—— “动物园管理员”.动物园里当然有好多的动物,游客可以根据动物园提供的向导图到不同的场馆观赏各种类型的动物,而不是像走在原始丛林里,心惊胆颤的被 ...
- 基于ZooKeeper的分布式Session实现
1. 认识ZooKeeper ZooKeeper—— “动物园管理员”.动物园里当然有好多的动物,游客可以根据动物园提供的向导图到不同的场馆观赏各种类型的动物,而不是像走在原始丛林里,心惊胆颤的被 ...
- 多线程深入:乐观锁与悲观锁以及乐观锁的一种实现方式-CAS(转)
原文:https://www.cnblogs.com/qjjazry/p/6581568.html 首先介绍一些乐观锁与悲观锁: 悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每 ...
随机推荐
- git版本控制-详细操作
- git,软件帮助使用者进行版本的管理 阶段一git 命令: git init 初始化 git config --global user.email "you@example.com&qu ...
- HTTP 与TCP/IP 、Socket区别(一)
网络由下往上分为: 物理层-- 数据链路层-- 网络层-- IP协议 传输层-- TCP协议 会话层-- 表示层和应用层-- HTTP协议 1.TCP/IP连接 手机能够使用联网功能是因为手机底层实现 ...
- DOM对象与JQuery对象
在初学JAVA时,经常会迷惑,分不清DOM/JQuery对象,以及可以调用的方法. 1.什么是DOM/JQuery对象 DOM对象,即是我们用传统的方法(javascript)获得的对象: eg:va ...
- C Primer Plus学习笔记(十三)- 结构和其他数据形式
建立结构声明 结构声明(structure declaration)描述了一个结构的组织布局 struct book { char title[MAXTITL]; char author[MAXAUT ...
- python's twenty-third day for me 面向对象进阶
普通方法:对象和类绑定的过程. class A: def func1(self):pass def func2(self):pass def func3(self):pass def func4(se ...
- 用纯js实现一个文本编辑器
效果图 代码 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UT ...
- List<T>集合使用总结
- python--numpy学习(一)
NumPy 部分功能如下: ndarray,一个具有矢量运算符和复杂广播能力的快速节省空间的多维数组 用于对数组数据进行快速运算的标准数学函数 用于读写磁盘数据的工具以及用于操作内存映射文件的工具 线 ...
- openGL 纹理05
纹理(Texture) 为了能够把纹理映射(Map)到三角形上,我们需要指定三角形的每个顶点各自对应纹理的哪个部分. 这样每个顶点就会关联着一个纹理坐标(Texture Coordinate) 用来标 ...
- Spring整合Junit4进行单元测试
一. 添加依赖包(maven) <dependency> <groupId>junit</groupId> <artifactId>junit</ ...