mirror of
https://gitee.com/52itstyle/spring-boot-seckill.git
synced 2025-12-30 10:22:26 +00:00
解释一下为什么分布式redis锁+事物没有超卖的原因
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
package com.itstyle.seckill.common.enums;
|
||||
|
||||
public enum SeckillStatEnum {
|
||||
|
||||
|
||||
MUCH(2,"哎呦喂,人也太多了,请稍后!"),
|
||||
SUCCESS(1,"秒杀成功"),
|
||||
END(0,"秒杀结束"),
|
||||
REPEAT_KILL(-1,"重复秒杀"),
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user