From 53e0d673daee65bf2fb6bf69da2c229229b2e3d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E6=9F=922012?= <345849402@qq.com> Date: Sat, 2 Jun 2018 15:32:02 +0800 Subject: [PATCH] =?UTF-8?q?=E8=A7=A3=E9=87=8A=E4=B8=80=E4=B8=8B=E4=B8=BA?= =?UTF-8?q?=E4=BB=80=E4=B9=88=E5=88=86=E5=B8=83=E5=BC=8Fredis=E9=94=81+?= =?UTF-8?q?=E4=BA=8B=E7=89=A9=E6=B2=A1=E6=9C=89=E8=B6=85=E5=8D=96=E7=9A=84?= =?UTF-8?q?=E5=8E=9F=E5=9B=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../seckill/common/enums/SeckillStatEnum.java | 3 +- .../distributedlock/zookeeper/ZkLockUtil.java | 36 ++++-- .../impl/SeckillDistributedServiceImpl.java | 114 +++++++++++------- .../web/SeckillDistributedController.java | 2 +- src/main/resources/application.properties | 4 +- 5 files changed, 103 insertions(+), 56 deletions(-) diff --git a/src/main/java/com/itstyle/seckill/common/enums/SeckillStatEnum.java b/src/main/java/com/itstyle/seckill/common/enums/SeckillStatEnum.java index 9325c8e..803795f 100644 --- a/src/main/java/com/itstyle/seckill/common/enums/SeckillStatEnum.java +++ b/src/main/java/com/itstyle/seckill/common/enums/SeckillStatEnum.java @@ -1,7 +1,8 @@ package com.itstyle.seckill.common.enums; public enum SeckillStatEnum { - + + MUCH(2,"哎呦喂,人也太多了,请稍后!"), SUCCESS(1,"秒杀成功"), END(0,"秒杀结束"), REPEAT_KILL(-1,"重复秒杀"), diff --git a/src/main/java/com/itstyle/seckill/distributedlock/zookeeper/ZkLockUtil.java b/src/main/java/com/itstyle/seckill/distributedlock/zookeeper/ZkLockUtil.java index 0c579c2..6ec381d 100644 --- a/src/main/java/com/itstyle/seckill/distributedlock/zookeeper/ZkLockUtil.java +++ b/src/main/java/com/itstyle/seckill/distributedlock/zookeeper/ZkLockUtil.java @@ -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(); } diff --git a/src/main/java/com/itstyle/seckill/service/impl/SeckillDistributedServiceImpl.java b/src/main/java/com/itstyle/seckill/service/impl/SeckillDistributedServiceImpl.java index 241d0ce..bd6a674 100644 --- a/src/main/java/com/itstyle/seckill/service/impl/SeckillDistributedServiceImpl.java +++ b/src/main/java/com/itstyle/seckill/service/impl/SeckillDistributedServiceImpl.java @@ -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(); diff --git a/src/main/java/com/itstyle/seckill/web/SeckillDistributedController.java b/src/main/java/com/itstyle/seckill/web/SeckillDistributedController.java index 427c3ef..580b1b2 100644 --- a/src/main/java/com/itstyle/seckill/web/SeckillDistributedController.java +++ b/src/main/java/com/itstyle/seckill/web/SeckillDistributedController.java @@ -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 diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 214f3fd..ff3a436 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -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