文章目录

  • 1.环境说明
  • 2.maven依赖
  • 3.代码实现
    • 3.1.Redis
      • 3.1.1.配置文件
      • 3.1.2.redisTemplate配置
    • 3.2.Curator
      • 3.1.1.配置文件
      • 3.1.2.Curator配置
    • 3.2.测试

1.环境说明

序号 名称 版本 说明
1 springboot 2.2.1.RELEASE
2 curator 2.8.0 zookeeper的封装框架
3 redis 5.0.9 下载路径: https://github.com/tporadowski/redis/releases/download/v5.0.9/Redis-x64-5.0.9.zip
4 zookeeper 3.4.14 下载路径: https://mirrors.tuna.tsinghua.edu.cn/apache/zookeeper/zookeeper-3.4.14/zookeeper-3.4.14.tar.gz

备注: redis和zookeeper的安装启动这里不做说明

2.maven依赖

<!--curator-->
<dependency><groupId>org.apache.curator</groupId><artifactId>curator-framework</artifactId><version>2.8.0</version>
</dependency>
<dependency><groupId>org.apache.curator</groupId><artifactId>curator-recipes</artifactId><version>2.8.0</version>
</dependency><!--redis-->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--jackson-->
<dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId>
</dependency>
<dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId><version>2.6.1</version>
</dependency><!--zookeeper-->
<dependency><groupId>org.apache.zookeeper</groupId><artifactId>zookeeper</artifactId><version>3.4.10</version>
</dependency>

3.代码实现

3.1.Redis

3.1.1.配置文件
#redis的ip
spring.redis.host=127.0.0.1
#端口
spring.redis.port=6379
#密码:redis配置文件设置(默认为空)
spring.redis.password=123456
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.jedis.pool.max-active=8
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.jedis.pool.max-wait=1000
# 连接池中的最大空闲连接
spring.redis.lettuce.pool.max-idle=1
# 连接池中的最小空闲连接
spring.redis.lettuce.pool.min-idle=0
# 连接超时时间(毫秒)
spring.redis.timeout=10000
3.1.2.redisTemplate配置
package com.tt.tt.redis.config;import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;/*** @author 小枫* @version 1.0.0* @date 2020/6/18 20:18* @desciption MyRedisConfig*/
@Configuration
public class MyRedisConfig {@Beanpublic RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory factory){RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();redisTemplate.setConnectionFactory(factory);ObjectMapper objectMapper=new ObjectMapper();objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);Jackson2JsonRedisSerializer jackson2JsonRedisSerializer=new Jackson2JsonRedisSerializer(Object.class);jackson2JsonRedisSerializer.setObjectMapper(objectMapper);//1.设置key的序列化方式redisTemplate.setKeySerializer(new StringRedisSerializer());redisTemplate.setHashKeySerializer(new StringRedisSerializer());//2.设置value的序列化方式redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);return redisTemplate;}
}

3.2.Curator

3.1.1.配置文件
## zookeeper: curator
#重试次数
curator.retryCount=5
#重试间隔时间(ms)
curator.elapsedTimeMs=5000
# zookeeper 地址
curator.connectString=127.0.0.1:2181
# session超时时间
curator.sessionTimeoutMs=60000
# 连接超时时间
curator.connectionTimeoutMs=5000
3.1.2.Curator配置
package com.tt.tt.zookeeper.service;import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.cache.PathChildrenCache;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.ZooDefs;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import java.util.concurrent.CountDownLatch;/*** @author 小枫* @version 1.0.0* @date 2020/6/18 20:05* @desciption DistributedLockByCuratorServiceImpl*/
@Service
public class DistributedLockByCuratorServiceImpl implements InitializingBean {private final static String ROOT_PATH_LOCK = "rootLock";private CountDownLatch countDownLatch = new CountDownLatch(1);@Autowiredprivate CuratorFramework curatorFramework;@Overridepublic void afterPropertiesSet() throws Exception {curatorFramework= curatorFramework.usingNamespace("distributedLock");String path = "/" + ROOT_PATH_LOCK;if (null==curatorFramework.checkExists().forPath(path)){curatorFramework.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT).withACL(ZooDefs.Ids.OPEN_ACL_UNSAFE).forPath(path);addWatcher(ROOT_PATH_LOCK);}}/*** 创建 watcher 事件*/private void addWatcher(String path) throws Exception {String keyPath;if (path.equals(ROOT_PATH_LOCK)) {keyPath = "/" + path;} else {keyPath = "/" + ROOT_PATH_LOCK + "/" + path;}final PathChildrenCache cache = new PathChildrenCache(curatorFramework, keyPath, false);cache.start(PathChildrenCache.StartMode.POST_INITIALIZED_EVENT);cache.getListenable().addListener((client, event) -> {if (event.getType().equals(PathChildrenCacheEvent.Type.CHILD_REMOVED)) {String oldPath = event.getData().getPath();if (oldPath.contains(path)) {//释放计数器,让当前的请求获取锁countDownLatch.countDown();}}});}/*** 获取分布式锁*/public void acquireDistributedLock(String path) throws Exception {String keyPath = "/" + ROOT_PATH_LOCK + "/" + path;while (true) {try {curatorFramework.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL).withACL(ZooDefs.Ids.OPEN_ACL_UNSAFE).forPath(keyPath);break;} catch (Exception e) {try {if (countDownLatch.getCount() <= 0) {countDownLatch = new CountDownLatch(1);}countDownLatch.await();} catch (InterruptedException e1) {e1.printStackTrace();}}}}/*** 释放分布式锁*/public boolean releaseDistributedLock(String path) {try {String keyPath = "/" + ROOT_PATH_LOCK + "/" + path;if (curatorFramework.checkExists().forPath(keyPath) != null) {curatorFramework.delete().forPath(keyPath);}} catch (Exception e) {return false;}return true;}
}

3.2.测试

高并发测试工具: jmeter 方法: https://blog.csdn.net/qq_42513284/article/details/94662461

package com.tt.tt.controller;import com.tt.tt.zookeeper.service.DistributedLockByCuratorServiceImpl;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.Map;
import java.util.concurrent.TimeUnit;/*** @author 小枫* @version 1.0.0* @date 2020/6/17 20:59* @desciption DistributeLockContrller: 在redis中预设苹果手机的抢购数量(100):set productAppleNum 100*/
@RestController
@RequestMapping("/api")
public class DistributeLockContrller {@Autowiredprivate RedisTemplate redisTemplate;@Autowiredprivate DistributedLockByCuratorServiceImpl distributedLockByCuratorService;@Autowiredprivate CuratorFramework curatorFramework;@RequestMapping("/lock")public String distributeLockTest() throws Exception {String path = "/productApple";String msg = "抢购失败";InterProcessMutex mutex = null;boolean acquire = false;int productNum = 0;//为何要先查一下redis中的苹果手机: redis的读写性能非常高,// 如果查询redis中苹果手机的数量都<=0的话,直接返回失败,极大的提高了并发量//Object num =null;Object num = redisTemplate.opsForValue().get("productAppleNum");if (null != num) {productNum = Integer.parseInt(String.valueOf(num));if (productNum <= 0) {msg = "苹果手机已经被抢购完...";System.out.println(msg);throw new Exception(msg);}}try {//1.分布式锁:避免苹果手机出现超卖的严重后果mutex = new InterProcessMutex(curatorFramework, path);//获取锁acquire = mutex.acquire(5000, TimeUnit.MILLISECONDS);if (acquire) {//2.也可用数据库保存抢购苹果手机的数量//获取redis中苹果手机的数量num = redisTemplate.opsForValue().get("productAppleNum");if (null != num) {productNum = Integer.parseInt(String.valueOf(num));if (productNum > 0) {//3.抢购成功, redis中苹果手机的数量redisTemplate.getConnectionFactory().getConnection().decr(redisTemplate.getKeySerializer().serialize("productAppleNum"));msg = "苹果手机抢购成功!";System.out.println(msg);return msg;} else {msg = "苹果手机已经被抢购完...";System.out.println(msg);throw new Exception(msg);}}}} catch (Exception e) {e.printStackTrace();throw new Exception(msg);} finally {try {//释放锁if (null != mutex && acquire) {mutex.release();System.out.println("释放锁成功!");}} catch (Exception e) {e.printStackTrace();}}msg = "failed !";throw new Exception(msg);}@RequestMapping("/lock2")public String distributeLockTest2() throws Exception {String path = "productApple"; //注意不要加/String msg = "抢购失败";InterProcessMutex mutex = null;Integer productNum = 0;Object num = redisTemplate.opsForValue().get("productAppleNum");if (null != num) {productNum = Integer.parseInt(String.valueOf(num));if (productNum <= 0) {msg = "苹果手机已经被抢购完...";System.out.println(msg);throw new Exception(msg);}}try {distributedLockByCuratorService.acquireDistributedLock(path);num = redisTemplate.opsForValue().get("productAppleNum");if (null != num) {productNum = Integer.parseInt(String.valueOf(num));if (productNum > 0) {redisTemplate.getConnectionFactory().getConnection().decr(redisTemplate.getKeySerializer().serialize("productAppleNum"));msg = "苹果手机抢购成功!";System.out.println(msg);return msg;} else {msg = "苹果手机已经被抢购完...";System.out.println(msg);throw new Exception(msg);}}} catch (Exception e) {e.printStackTrace();throw new Exception(msg);} finally {try {boolean b = distributedLockByCuratorService.releaseDistributedLock(path);System.out.println("释放锁:"+b);} catch (Exception e) {e.printStackTrace();System.out.println("释放锁失败:"+false);}}msg = "failed";throw new Exception(msg);}
}

测试方式: 1000个线程,间隔1s循环10次

测试结果:

  1. 多次测试并未发现redis设置的数量变为负值(即超卖现象)
  2. 方式一比方式二的并发量高
  3. 方式一问题:偶尔出现redis设置的数量大于0的现象
  4. 方式二问题 : redis设置的数量均为0,但是偶尔出现请求超时,未得到响应

springboot整合curator实现分布式锁模拟抢购场景相关推荐

  1. springboot整合curator实现分布式锁

    理论篇: Curator是Netflix开源的一套ZooKeeper客户端框架. Netflix在使用ZooKeeper的过程中发现ZooKeeper自带的客户端太底层, 应用方在使用的时候需要自己处 ...

  2. springboot整合redis实现分布式锁思想

    思路 所有响应获取锁的线程都先尝试往redis中创建一个缓存数据,所有线程的key必须相同.使用的是redis的setnx命令.就只有一个线程能够创建成功,创建成功的线程就成功获取锁. 没有获取锁的线 ...

  3. springboot整合redisson实现分布式锁

    一.介绍Redisson Redisson是Redis官方推荐的Java版的Redis客户端(Jedis.letture也是官方推荐的java版本redis客户端程序).它提供的功能非常多,也非常强大 ...

  4. 分布式架构-ZK客户端工具Curator框架分布式锁及基本使用

    分布式架构-基于Curator分布式锁及基本使用 一.Curator Curator是Netflix公司开源的一套zookeeper客户端框架,解决了很多Zookeeper客户端非常底层的细节开发工作 ...

  5. springboot + redis(兼容单机、哨兵、集群) + 分布式锁模拟抢手机

    redis环境的搭建,可参考博主的文章 windows环境下 单机.集群.哨兵模式的redis环境_min开发的博客-CSDN博客windows环境下redis单机.集群.哨兵的部署https://b ...

  6. Redis分布式锁解决抢购问题

    转:https://segmentfault.com/a/1190000011421467 废话不多说,首先分享一个业务场景-抢购.一个典型的高并发问题,所需的最关键字段就是库存,在高并发的情况下每次 ...

  7. 干货,springboot自定义注解实现分布式锁详解

    背景 在互联网的很多场景下,会产生资源竞争,如果是单机环境,简单加个锁就能解决问题:但是在集群环境下(分布式环境),多个客户端在一个很短的时间内竞争同一服务端资源(如抢购场景),或者同一客户端重复提交 ...

  8. zookeeper操作封装——curator使用分布式锁使用

    文章目录 1. 基本操作 1.1 建立连接 1.2 建立结点 1.3 查询结点 查询数据 查询子结点 查看结点信息 1.4 修改结点 普通修改 带乐观锁的修改 1.5 删除 删除单个结点 删除带子结点 ...

  9. Apache Curator之分布式锁原理(二)

    本文主要讲解如下内容: 为什么要使用分布式锁? 分布式锁特性! 分布式锁的实现方式有哪些? Curator分布式锁原理 Curator分布式锁实现类UML及相关类的介绍 基于Redis,数据库实现分布 ...

最新文章

  1. html li 右跟下有倒影,HTML5 canvas实现的下雨夜湖面星空倒影动画特效
  2. zookeeper3.4.6配置实现自动清理日志
  3. STRUTS2单元测试
  4. jira 审批流程_博兴县行政审批服务局推暖心服务工程 企业开办实现“全程网办”_博兴新闻...
  5. Google Maps API 2.0解析(3-GEvent事件功能支持)
  6. Android系统自带样式(android:theme)
  7. linux推箱子脚本,【编程例题】标准C语言实现推箱子游戏!附解析!
  8. linux bash profile bash_profile 小结
  9. 曾遭闪电贷攻击的Origin将重新推出稳定币OUSD
  10. 水桶平分 java_关于java:桶排序算法代码问题
  11. 马云的经典语录(转载)
  12. php smarty配置,php配置smarty
  13. 徐思201771010132《面向对象程序设计(java)》第三周学习总结
  14. 【PyQt5与Requests爬虫】设计图形界面(GUI)实现小说下载器-进度条显示
  15. 上传本地文件到服务器:not a regular file
  16. 实例详解ISA防火墙策略元素
  17. 用 FFMPEG 合并 MP4 视频
  18. 对小米路由器提出严正批评,2.4G下,40MHz自动变20MHz
  19. html5 video播放按钮放在中间,在html5视频控制区跟踪点击播放按钮(Track clicks to play button in html5 video control area)...
  20. Throttling - Django REST framework

热门文章

  1. 进入openstack_OpenStack进入2015年
  2. Java项目启动成功、失败信息实时反馈提醒(邮件或者短信)
  3. 【20200705】CISA考题练习
  4. 苹果手机清灰_ROG手机2火爆?这几个缺点,买前必看
  5. CVPR2021 | VQGAN+:Taming Transformers for High-Resolution Image Synthesis
  6. uniquify (synthesis)
  7. 5G预商用,三大运营商开年一波秀 1
  8. QT给控件绑定数据:setProperty
  9. 利用Win 2K实现Internet连接共享
  10. 华为云业务部总经理陶志强:科技让教育更美好