zoukankan      html  css  js  c++  java
  • 【连载】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

     1         <dependency>
     2             <groupId>org.apache.zookeeper</groupId>
     3             <artifactId>zookeeper</artifactId>
     4             <version>3.4.13</version>
     5             <scope>compile</scope>
     6             <exclusions>
     7                 <exclusion>
     8                     <groupId>org.slf4j</groupId>
     9                     <artifactId>slf4j-log4j12</artifactId>
    10                 </exclusion>
    11             </exclusions>
    12         </dependency>
    13         <dependency>
    14             <groupId>org.apache.curator</groupId>
    15             <artifactId>curator-recipes</artifactId>
    16             <version>4.0.1</version>
    17         </dependency>

    2、curatorFramework初始化,放spring容器中

     1 /**
     2  * curatorFramework初始化
     3  *
     4  * @author LiJunJun
     5  * @date 2018/12/7
     6  */
     7 @Configuration
     8 public class CuratorBean {
     9 
    10     @Bean
    11     public CuratorFramework curatorFramework() {
    12 
    13         RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
    14         CuratorFramework client = CuratorFrameworkFactory.newClient("192.168.10.110:2381", retryPolicy);
    15         return client;
    16     }
    17 
    18     @Bean
    19     public InterProcessMutex interProcessMutex() {
    20 
    21         curatorFramework().start();
    22 
    23         return new InterProcessMutex(curatorFramework(), "/curator/lock");
    24     }
    25 }

    3、业务代码

     1     /**
     2      * interProcessMutex
     3      */
     4     @Resource
     5     private InterProcessMutex interProcessMutex;
     6 
     7     /**
     8      * 减库存(基于zookeeper分布式锁实现)
     9      *
    10      * @param trace 请求流水
    11      * @param stockManageReq(stockId、decrNum)
    12      * @return -1为失败,大于-1的正整数为减后的库存量,-2为库存不足无法减库存
    13      */
    14     @Override
    15     @ApiOperation(value = "减库存", notes = "减库存")
    16     @RequestMapping(value = "/decrByStock", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_UTF8_VALUE, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
    17     public int decrByStock(@RequestHeader(name = "Trace") String trace, @RequestBody StockManageReq stockManageReq) {
    18 
    19         long startTime = System.nanoTime();
    20 
    21         LOGGER.reqPrint(Log.CACHE_SIGN, Log.CACHE_REQUEST, trace, "decrByStock", JSON.toJSONString(stockManageReq));
    22 
    23         int res = 0;
    24         String stockId = stockManageReq.getStockId();
    25         Integer decrNum = stockManageReq.getDecrNum();
    26 
    27         // 添加分布式锁
    28         boolean lockResult = false;
    29 
    30         try {
    31             if (null != stockId && null != decrNum) {
    32 
    33                 stockId = PREFIX + stockId;
    34 
    35                 // 获取锁最多等待5s
    36                 lockResult = interProcessMutex.acquire(5, TimeUnit.SECONDS);
    37 
    38                 if (!lockResult) {
    39                     LOGGER.info("本次请求获取锁失败,lockResult=1");
    40                     return -1;
    41                 }
    42 
    43                 // redis 减库存逻辑
    44                 String vStock = redisStockPool.get(stockId);
    45 
    46                 long realV = 0L;
    47                 if (StringUtils.isNotEmpty(vStock)) {
    48                     realV = Long.parseLong(vStock);
    49                 }
    50                 //库存数  大于等于 要减的数目,则执行减库存
    51                 if (realV >= decrNum) {
    52                     Long v = redisStockPool.decrBy(stockId, decrNum);
    53                     res = v.intValue();
    54                 } else {
    55                     res = -2;
    56                 }
    57             }
    58         } catch (Exception e) {
    59             LOGGER.error(trace, "decr sku stock failure.", e);
    60             res = -1;
    61         } finally {
    62             if (lockResult) {
    63                 try {
    64                     // 释放锁
    65                     interProcessMutex.release();
    66                 } catch (Exception e) {
    67                     e.printStackTrace();
    68                 }
    69             }
    70             LOGGER.respPrint(Log.CACHE_SIGN, Log.CACHE_RESPONSE, trace, "decrByStock", System.nanoTime() - startTime, String.valueOf(res));
    71         }
    72         return res;
    73     }

    六、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中间件实现的分布式锁。

  • 相关阅读:
    樊登读书 增长黑客
    樊登读书 不妥协的谈判
    樊登读书 反脆弱 MD
    樊登读书 思考快与慢
    JS常见的API扩展形式(prototype、jquery、vue插件封装)以及怎样设计出易扩展的表单验证功能?
    [JS设计模式]:策略模式及应用-计算奖金、表单验证的实现(5)
    JS之Math.sin与Math.cos介绍及应用-实现鼠标点击后的烟花效果
    webpack系列-externals配置使用(CDN方式引入JS)
    vue+webpack工程中怎样在vue页面中引入第三方非标准的JS库或者方法
    2-class.com
  • 原文地址:https://www.cnblogs.com/ft535535/p/10148414.html
Copyright © 2011-2022 走看看