解释一下为什么分布式redis锁+事物没有超卖的原因

This commit is contained in:
小柒2012
2018-06-02 15:32:02 +08:00
parent 0ae5ae1a09
commit 53e0d673da
5 changed files with 103 additions and 56 deletions

View File

@@ -1,7 +1,8 @@
package com.itstyle.seckill.common.enums;
public enum SeckillStatEnum {
MUCH(2,"哎呦喂,人也太多了,请稍后!"),
SUCCESS(1,"秒杀成功"),
END(0,"秒杀结束"),
REPEAT_KILL(-1,"重复秒杀"),

View File

@@ -1,11 +1,12 @@
package com.itstyle.seckill.distributedlock.zookeeper;
import java.util.concurrent.TimeUnit;
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.springframework.beans.factory.annotation.Value;
/**
* zookeeper 分布式锁
@@ -13,8 +14,7 @@ import org.springframework.beans.factory.annotation.Value;
*/
public class ZkLockUtil{
@Value("${zookeeper.address}")
private static String address;
private static String address = "192.168.1.180:2181";
public static CuratorFramework client;
@@ -23,21 +23,37 @@ public class ZkLockUtil{
client = CuratorFrameworkFactory.newClient(address, retryPolicy);
client.start();
}
/**
* 私有的默认构造子,保证外界无法直接实例化
*/
private ZkLockUtil(){};
/**
* 类级的内部类,也就是静态的成员式内部类,该内部类的实例与外部类的实例
* 没有绑定关系,而且只有被调用到才会装载,从而实现了延迟加载
* 针对一件商品实现多件商品同时秒杀建议实现一个map
*/
private static class SingletonHolder{
/**
* 静态初始化器由JVM来保证线程安全
*/
private static InterProcessMutex mutex = new InterProcessMutex(client, "/curator/lock");
}
public static InterProcessMutex getMutex(){
return SingletonHolder.mutex;
}
//获得了锁
public static void acquire(String lockKey){
public static boolean acquire(long time, TimeUnit unit){
try {
InterProcessMutex mutex = new InterProcessMutex(client, "/curator/lock/"+lockKey);
mutex.acquire();
return getMutex().acquire(time,unit);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
//释放锁
public static void release(String lockKey){
public static void release(){
try {
InterProcessMutex mutex = new InterProcessMutex(client, "/curator/lock/"+lockKey);
mutex.release();
getMutex().release();
} catch (Exception e) {
e.printStackTrace();
}

View File

@@ -26,22 +26,40 @@ public class SeckillDistributedServiceImpl implements ISeckillDistributedService
public Result startSeckilRedisLock(long seckillId,long userId) {
boolean res=false;
try {
//尝试获取锁最多等待3秒上锁以后20秒自动解锁实际项目中推荐这种以防出现死锁、这里根据预估秒杀人数设定自动释放锁时间
/**
* 尝试获取锁最多等待3秒上锁以后20秒自动解锁实际项目中推荐这种以防出现死锁、这里根据预估秒杀人数设定自动释放锁时间.
* 看过博客的朋友可能会知道(Lcok锁与事物冲突的问题)https://blog.52itstyle.com/archives/2952/
* 分布式锁的使用和Lock锁的实现方式是一样的但是测试了多次分布式锁就是没有问题当时就留了个坑
* 闲来咨询了《静儿1986》推荐下博客https://www.cnblogs.com/xiexj/p/9119017.html
* 先说明下之前的配置情况Mysql在本地而Redis是在外网。
* 回复是这样的:
* 这是因为分布式锁的开销是很大的。要和锁的服务器进行通信它虽然是先发起了锁释放命令涉及网络IO延时肯定会远远大于方法结束后的事务提交。
* ==========================================================================================
* 分布式锁内部都是Runtime.exe命令调用外部肯定是异步的。分布式锁的释放只是发了一个锁释放命令就算完活了。真正其作用的是下次获取锁的时候要确保上次是释放了的。
* 就是说获取锁的时候耗时比较长,那时候事务肯定提交了就是说获取锁的时候耗时比较长,那时候事务肯定提交了。
* ==========================================================================================
* 周末测试了一下把redis配置在了本地果然出现了超卖的情况或者还是使用外网并发数增加在10000+也是会有问题的,之前自己没有细测,我的锅。
* 所以这钟实现也是错误的事物和锁会有冲突建议AOP实现。
*/
res = RedissLockUtil.tryLock(seckillId+"", TimeUnit.SECONDS, 3, 20);
String nativeSql = "SELECT number FROM seckill WHERE seckill_id=?";
Object object = dynamicQuery.nativeQueryObject(nativeSql, new Object[]{seckillId});
Long number = ((Number) object).longValue();
if(number>0){
SuccessKilled killed = new SuccessKilled();
killed.setSeckillId(seckillId);
killed.setUserId(userId);
killed.setState((short)0);
killed.setCreateTime(new Timestamp(new Date().getTime()));
dynamicQuery.save(killed);
nativeSql = "UPDATE seckill SET number=number-1 WHERE seckill_id=? AND number>0";
dynamicQuery.nativeExecuteUpdate(nativeSql, new Object[]{seckillId});
if(res){
String nativeSql = "SELECT number FROM seckill WHERE seckill_id=?";
Object object = dynamicQuery.nativeQueryObject(nativeSql, new Object[]{seckillId});
Long number = ((Number) object).longValue();
if(number>0){
SuccessKilled killed = new SuccessKilled();
killed.setSeckillId(seckillId);
killed.setUserId(userId);
killed.setState((short)0);
killed.setCreateTime(new Timestamp(new Date().getTime()));
dynamicQuery.save(killed);
nativeSql = "UPDATE seckill SET number=number-1 WHERE seckill_id=? AND number>0";
dynamicQuery.nativeExecuteUpdate(nativeSql, new Object[]{seckillId});
}else{
return Result.error(SeckillStatEnum.END);
}
}else{
return Result.error(SeckillStatEnum.END);
return Result.error(SeckillStatEnum.MUCH);
}
} catch (Exception e) {
e.printStackTrace();
@@ -55,27 +73,35 @@ public class SeckillDistributedServiceImpl implements ISeckillDistributedService
@Override
@Transactional
public Result startSeckilZksLock(long seckillId, long userId) {
boolean res=false;
try {
ZkLockUtil.acquire(seckillId+"");
String nativeSql = "SELECT number FROM seckill WHERE seckill_id=?";
Object object = dynamicQuery.nativeQueryObject(nativeSql, new Object[]{seckillId});
Long number = ((Number) object).longValue();
if(number>0){
SuccessKilled killed = new SuccessKilled();
killed.setSeckillId(seckillId);
killed.setUserId(userId);
killed.setState((short)0);
killed.setCreateTime(new Timestamp(new Date().getTime()));
dynamicQuery.save(killed);
nativeSql = "UPDATE seckill SET number=number-1 WHERE seckill_id=? AND number>0";
dynamicQuery.nativeExecuteUpdate(nativeSql, new Object[]{seckillId});
//基于redis分布式锁 基本就是上面这个解释 但是 使用zk分布式锁 使用本地zk服务 并发到10000+还是没有问题,谁的锅?
res = ZkLockUtil.acquire(3,TimeUnit.SECONDS);
if(res){
String nativeSql = "SELECT number FROM seckill WHERE seckill_id=?";
Object object = dynamicQuery.nativeQueryObject(nativeSql, new Object[]{seckillId});
Long number = ((Number) object).longValue();
if(number>0){
SuccessKilled killed = new SuccessKilled();
killed.setSeckillId(seckillId);
killed.setUserId(userId);
killed.setState((short)0);
killed.setCreateTime(new Timestamp(new Date().getTime()));
dynamicQuery.save(killed);
nativeSql = "UPDATE seckill SET number=number-1 WHERE seckill_id=? AND number>0";
dynamicQuery.nativeExecuteUpdate(nativeSql, new Object[]{seckillId});
}else{
return Result.error(SeckillStatEnum.END);
}
}else{
return Result.error(SeckillStatEnum.END);
return Result.error(SeckillStatEnum.MUCH);
}
} catch (Exception e) {
e.printStackTrace();
} finally{
ZkLockUtil.release(seckillId+"");
if(res){//释放锁
ZkLockUtil.release();
}
}
return Result.ok(SeckillStatEnum.SUCCESS);
}
@@ -87,20 +113,24 @@ public class SeckillDistributedServiceImpl implements ISeckillDistributedService
try {
//尝试获取锁最多等待3秒上锁以后10秒自动解锁实际项目中推荐这种以防出现死锁
res = RedissLockUtil.tryLock(seckillId+"", TimeUnit.SECONDS, 3, 10);
String nativeSql = "SELECT number FROM seckill WHERE seckill_id=?";
Object object = dynamicQuery.nativeQueryObject(nativeSql, new Object[]{seckillId});
Long count = ((Number) object).longValue();
if(count>=number){
SuccessKilled killed = new SuccessKilled();
killed.setSeckillId(seckillId);
killed.setUserId(userId);
killed.setState((short)0);
killed.setCreateTime(new Timestamp(new Date().getTime()));
dynamicQuery.save(killed);
nativeSql = "UPDATE seckill SET number=number-? WHERE seckill_id=? AND number>0";
dynamicQuery.nativeExecuteUpdate(nativeSql, new Object[]{number,seckillId});
if(res){
String nativeSql = "SELECT number FROM seckill WHERE seckill_id=?";
Object object = dynamicQuery.nativeQueryObject(nativeSql, new Object[]{seckillId});
Long count = ((Number) object).longValue();
if(count>=number){
SuccessKilled killed = new SuccessKilled();
killed.setSeckillId(seckillId);
killed.setUserId(userId);
killed.setState((short)0);
killed.setCreateTime(new Timestamp(new Date().getTime()));
dynamicQuery.save(killed);
nativeSql = "UPDATE seckill SET number=number-? WHERE seckill_id=? AND number>0";
dynamicQuery.nativeExecuteUpdate(nativeSql, new Object[]{number,seckillId});
}else{
return Result.error(SeckillStatEnum.END);
}
}else{
return Result.error(SeckillStatEnum.END);
return Result.error(SeckillStatEnum.MUCH);
}
} catch (Exception e) {
e.printStackTrace();

View File

@@ -72,7 +72,7 @@ public class SeckillDistributedController {
seckillService.deleteSeckill(seckillId);
final long killId = seckillId;
LOGGER.info("开始秒杀二");
for(int i=0;i<1000;i++){
for(int i=0;i<10000;i++){
final long userId = i;
Runnable task = new Runnable() {
@Override

View File

@@ -46,7 +46,7 @@ spring.redis.host=192.168.1.180
# \u670d\u52a1\u5668\u8fde\u63a5\u7aef\u53e3
spring.redis.port=6379
# \u670d\u52a1\u5668\u8fde\u63a5\u5bc6\u7801\uff08\u9ed8\u8ba4\u4e3a\u7a7a\uff09\u5982\u679c\u6709\u53d8\u66f4\u4e3a\u81ea\u5df1\u7684
spring.redis.password=6347097
spring.redis.password=123456
# \u8fde\u63a5\u6c60\u6700\u5927\u8fde\u63a5\u6570\uff08\u4f7f\u7528\u8d1f\u503c\u8868\u793a\u6ca1\u6709\u9650\u5236\uff09
spring.redis.pool.max-active=8
# \u8fde\u63a5\u6c60\u6700\u5927\u963b\u585e\u7b49\u5f85\u65f6\u95f4\uff08\u4f7f\u7528\u8d1f\u503c\u8868\u793a\u6ca1\u6709\u9650\u5236\uff09
@@ -63,7 +63,7 @@ spring.session.store-type=redis
# redisson lock
redisson.address=redis://192.168.1.180:6379
redisson.password=6347097
redisson.password=123456
#kafka\u76f8\u5173\u914d\u7f6e \u53c2\u8003\uff1ahttps://blog.52itstyle.com/archives/2868/
spring.kafka.bootstrap-servers=192.168.1.180:9092