序号 | 名称 | 版本 | 说明 |
---|---|---|---|
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的安装启动这里不做说明
<dependency>
<groupId>org.apache.curatorgroupId>
<artifactId>curator-frameworkartifactId>
<version>2.8.0version>
dependency>
<dependency>
<groupId>org.apache.curatorgroupId>
<artifactId>curator-recipesartifactId>
<version>2.8.0version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
<dependency>
<groupId>com.fasterxml.jackson.coregroupId>
<artifactId>jackson-databindartifactId>
dependency>
<dependency>
<groupId>org.apache.commonsgroupId>
<artifactId>commons-pool2artifactId>
<version>2.6.1version>
dependency>
<dependency>
<groupId>org.apache.zookeepergroupId>
<artifactId>zookeeperartifactId>
<version>3.4.10version>
dependency>
#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
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 {
@Bean
public 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;
}
}
## zookeeper: curator
#重试次数
curator.retryCount=5
#重试间隔时间(ms)
curator.elapsedTimeMs=5000
# zookeeper 地址
curator.connectString=127.0.0.1:2181
# session超时时间
curator.sessionTimeoutMs=60000
# 连接超时时间
curator.connectionTimeoutMs=5000
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);
@Autowired
private CuratorFramework curatorFramework;
@Override
public 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;
}
}
高并发测试工具: 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 {
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private DistributedLockByCuratorServiceImpl distributedLockByCuratorService;
@Autowired
private 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次
测试结果: