一、背景

在电商系统中,库存的概念一定是有的,例如配一些商品的库存,做商品秒杀活动等,而由于库存操作频繁且要求原子性操作,所以绝大多数电商系统都用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实现分布式锁的更多相关文章

  1. 【连载】redis库存操作,分布式锁的四种实现方式[二]--基于Redisson实现分布式锁

    一.redisson介绍 redisson实现了分布式和可扩展的java数据结构,支持的数据结构有:List, Set, Map, Queue, SortedSet, ConcureentMap, L ...

  2. 【连载】redis库存操作,分布式锁的四种实现方式[三]--基于Redis watch机制实现分布式锁

    一.redis的事务介绍 1. Redis保证一个事务中的所有命令要么都执行,要么都不执行.如果在发送EXEC命令前客户端断线了,则Redis会清空事务队列,事务中的所有命令都不会执行.而一旦客户端发 ...

  3. 【转载】Redis的Java客户端Jedis的八种调用方式(事务、管道、分布式…)介绍

    转载地址:http://blog.csdn.net/truong/article/details/46711045 关键字:Redis的Java客户端Jedis的八种调用方式(事务.管道.分布式…)介 ...

  4. 分布式锁的两种实现方式(基于redis和基于zookeeper)

    先来说说什么是分布式锁,简单来说,分布式锁就是在分布式并发场景中,能够实现多节点的代码同步的一种机制.从实现角度来看,主要有两种方式:基于redis的方式和基于zookeeper的方式,下面分别简单介 ...

  5. 【连载】redis库存操作,分布式锁的四种实现方式[四]--基于Redis lua脚本机制实现分布式锁

    一.redis lua介绍 Redis 提供了非常丰富的指令集,但是用户依然不满足,希望可以自定义扩充若干指令来完成一些特定领域的问题.Redis 为这样的用户场景提供了 lua 脚本支持,用户可以向 ...

  6. Redis的Java客户端Jedis的八种调用方式(事务、管道、分布式)介绍

    jedis是一个著名的key-value存储系统,而作为其官方推荐的java版客户端jedis也非常强大和稳定,支持事务.管道及有jedis自身实现的分布式. 在这里对jedis关于事务.管道和分布式 ...

  7. 基于ZooKeeper的分布式Session实现(转)

    1.   认识ZooKeeper ZooKeeper—— “动物园管理员”.动物园里当然有好多的动物,游客可以根据动物园提供的向导图到不同的场馆观赏各种类型的动物,而不是像走在原始丛林里,心惊胆颤的被 ...

  8. 基于ZooKeeper的分布式Session实现

    1.   认识ZooKeeper ZooKeeper—— “动物园管理员”.动物园里当然有好多的动物,游客可以根据动物园提供的向导图到不同的场馆观赏各种类型的动物,而不是像走在原始丛林里,心惊胆颤的被 ...

  9. 多线程深入:乐观锁与悲观锁以及乐观锁的一种实现方式-CAS(转)

    原文:https://www.cnblogs.com/qjjazry/p/6581568.html 首先介绍一些乐观锁与悲观锁: 悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每 ...

随机推荐

  1. 1141 PAT Ranking of Institutions

    题意:给出考生id(分为乙级.甲级和顶级),取得的分数和所属学校.计算各个学校的所有考生的带权总成绩,以及各个学校的考生人数.最后对学校进行排名. 思路:本题的研究对象是学校,而不是考生!因此,建立学 ...

  2. Linux操作系统下的多线程编程详细解析----条件变量

    条件变量通过允许线程阻塞和等待另一个线程发送信号的方法,弥补了互斥锁(Mutex)的不足. 1.初始化条件变量pthread_cond_init #include <pthread.h> ...

  3. SSH开发中的注解使用

    在SSH中使用注解可以减少配置XML文件,毕竟随着项目规模的扩大,配置bean将把Spring的配置文件(applicationContext.xml)变得很混乱 在Spring的配置文件中开启注解扫 ...

  4. oracle导入数据和编码问题

    配置contrl文件 load data characterset utf8 append into table role_res_gold fields terminated by ';' TRAI ...

  5. centos 系统使用verdaccio搭建npm私库

    .安装nodejs yum install -y nodejs 2.安装verdaccio npm install -g verdaccio --unsafe-perm 3.配置 a.修改配置文件 c ...

  6. 优化深度神经网络(三)Batch Normalization

    Coursera吴恩达<优化深度神经网络>课程笔记(3)-- 超参数调试.Batch正则化和编程框架 1. Tuning Process 深度神经网络需要调试的超参数(Hyperparam ...

  7. Python可执行对象——exec、eval、compile

    Python提供的调用可执行对象的内建函数进行说明,涉及exec.eval.compile三个函数.exec语句用来执行存储在代码对象.字符串.文件中的Python语句,eval语句用来计算存储在代码 ...

  8. VB.NET使用TagLib#读取MP3中的ID3v2标签

    Taglib#是一个为.NET开发的元数据读取类库,为一个开源项目,可以在他们的官网上获取windows版本的源码包或者编译好的类库:http://download.banshee.fm/taglib ...

  9. leetcode599

    public class Solution { public string[] FindRestaurant(string[] list1, string[] list2) { var dic = n ...

  10. jQuery.prop() 与attr()

    1.attr()是jQuery 1.0版本就有的函数,prop()是jQuery 1.6版本新增的函数.毫无疑问,在1.6之前,你只能使用attr()函数:1.6及以后版本,你可以根据实际需要选择对应 ...