我是靠谱客的博主 玩命银耳汤,这篇文章主要介绍Java单体项目和分布式项目中的锁,现在分享给大家,希望可以做个参考。

单体应用解决超卖问题

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
@Service @Slf4j public class OrderService { @Resource private OrderMapper orderMapper; @Resource private OrderItemMapper orderItemMapper; @Resource private ProductMapper productMapper; //购买商品id private int purchaseProductId = 100100; //购买商品数量 private int purchaseProductNum = 1; @Autowired private PlatformTransactionManager platformTransactionManager; @Autowired private TransactionDefinition transactionDefinition; private Lock lock = new ReentrantLock();// 可重入锁 public Integer createOrder() throws Exception{ Product product = null; //上锁 lock.lock(); try { //手动开启事务 TransactionStatus transaction1 = platformTransactionManager.getTransaction(transactionDefinition); product = productMapper.selectByPrimaryKey(purchaseProductId); if (product==null){ //抛异常,回滚事务 platformTransactionManager.rollback(transaction1); throw new Exception("购买商品:"+purchaseProductId+"不存在"); } //商品当前库存 Integer currentCount = product.getCount(); System.out.println(Thread.currentThread().getName()+"库存数:"+currentCount); //校验库存 if (purchaseProductNum > currentCount){ //抛异常,回滚事务 platformTransactionManager.rollback(transaction1); throw new Exception("商品"+purchaseProductId+"仅剩"+currentCount+"件,无法购买"); } productMapper.updateProductCount(purchaseProductNum,"xxx",new Date(),product.getId()); //正常执行,提交事务 platformTransactionManager.commit(transaction1); }finally { // 释放锁 lock.unlock(); } // 其他创建订单业务逻辑 } }

我们再finally里面释放锁,方法还未结束,事务还未提交,高并发下,恰好在锁释放后,事务提交前,读到了未提交的脏数据,导致超卖!
解决上述问题,需要使用aop锁
1、定义注解:

复制代码
1
2
3
4
5
6
7
8
9
@Target({ElementType.PARAMETER,ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface ServiceLock { String description() default ""; }

2、定义切面

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
@Component @Scope @Aspect @Order(1) // 表示在事务之前执行 public class LockAspect { // 定义锁对象 private static Lock lock = new ReentrantLock(true); @Pointcut("@annotation(com.sugo.seckill.aop.lock.ServiceLock)") public void lockAspect(){ } // 增强方法 @Around("lockAspect()") public Object around(ProceedingJoinPoint joinPoint){ Object obj = null; // 加锁 lock.lock(); // 执行业务 try { obj = joinPoint.proceed(); } catch (Throwable throwable) { throwable.printStackTrace(); } finally { // 释放锁 lock.unlock(); } return obj; } }

3、使用注解

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
@Transactional @ServiceLock @Override public HttpResult startKilledByLocked(Long killId, String userId) { try { // 从数据库查询商品数据 TbSeckillGoods seckillGoods = seckillGoodsMapper.selectByPrimaryKey(killId); //判断 if(seckillGoods == null){ return HttpResult.error(HttpStatus.SEC_GOODS_NOT_EXSISTS,"商品不存在"); } if(seckillGoods.getStatus() != 1){ return HttpResult.error(HttpStatus.SEC_NOT_UP,"商品未审核"); } if(seckillGoods.getStockCount() <= 0){ return HttpResult.error(HttpStatus.SEC_GOODS_END,"商品已售罄"); } if(seckillGoods.getStartTimeDate().getTime() > new Date().getTime()){ return HttpResult.error(HttpStatus.SEC_ACTIVE_NOT_START,"活动未开始"); } if(seckillGoods.getEndTimeDate().getTime() <= new Date().getTime()){ return HttpResult.error(HttpStatus.SEC_ACTIVE_END,"活动结束"); } //库存扣减 seckillGoods.setStockCount(seckillGoods.getStockCount() - 1); //更新库存 seckillGoodsMapper.updateByPrimaryKeySelective(seckillGoods); //下单 TbSeckillOrder order = new TbSeckillOrder(); order.setSeckillId(killId); order.setUserId(userId); order.setCreateTime(new Date()); order.setStatus("0"); order.setMoney(seckillGoods.getCostPrice()); seckillOrderMapper.insertSelective(order); return HttpResult.ok("秒杀成功"); } catch (Exception e) { e.printStackTrace(); } return null; }

分布式锁

基于Redis实现分布式锁

依赖

复制代码
1
2
3
4
5
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>

定义RedisLock

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
@Slf4j public class RedisLock implements AutoCloseable { private RedisTemplate redisTemplate; private String key; private String value; //单位:秒 private int expireTime; public RedisLock(RedisTemplate redisTemplate,String key,int expireTime){ this.redisTemplate = redisTemplate; this.key = key; this.expireTime=expireTime; this.value = UUID.randomUUID().toString(); } /** * 获取分布式锁 * @return */ public boolean getLock(){ RedisCallback<Boolean> redisCallback = connection -> { //设置NX RedisStringCommands.SetOption setOption = RedisStringCommands.SetOption.ifAbsent(); //设置过期时间 Expiration expiration = Expiration.seconds(expireTime); //序列化key byte[] redisKey = redisTemplate.getKeySerializer().serialize(key); //序列化value byte[] redisValue = redisTemplate.getValueSerializer().serialize(value); //执行setnx操作 Boolean result = connection.set(redisKey, redisValue, expiration, setOption); return result; }; //获取分布式锁 Boolean lock = (Boolean)redisTemplate.execute(redisCallback); return lock; } /** * 释放分布式锁 * @return */ public boolean unLock() { String script = "if redis.call("get",KEYS[1]) == ARGV[1] thenn" + " return redis.call("del",KEYS[1])n" + "elsen" + " return 0n" + "end"; RedisScript<Boolean> redisScript = RedisScript.of(script,Boolean.class); List<String> keys = Arrays.asList(key); Boolean result = (Boolean)redisTemplate.execute(redisScript, keys, value); log.info("释放锁的结果:"+result); return result; } /** * 实现了AutoCloseable,实现自动释放锁,避免忘记释放锁 * @throws Exception */ @Override public void close() throws Exception { unLock(); } }

使用RedisLock

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@RestController @Slf4j public class RedisLockController { @Autowired private RedisTemplate redisTemplate; @RequestMapping("redisLock") public String redisLock(){ log.info("我进入了方法!"); // redisKey是业务key,可以自定义 try (RedisLock redisLock = new RedisLock(redisTemplate,"redisKey",30)){ if (redisLock.getLock()) { log.info("我进入了锁!!"); // 这里可以写具体并发业务 Thread.sleep(15000); } } catch (Exception e) { e.printStackTrace(); } log.info("方法执行完成"); return "方法执行完成"; } }

使用RedisLock就可以用@Scheduled实现分布式定时任务(启动类上要加@EnableScheduling)

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Service @Slf4j public class SchedulerService { @Autowired private RedisTemplate redisTemplate; @Scheduled(cron = "0/5 * * * * ?") public void sendSms(){ // autoSms 是业务key, 可以自定义 try(RedisLock redisLock = new RedisLock(redisTemplate,"autoSms",30)) { if (redisLock.getLock()){ log.info("向138xxxxxxxx发送短信!"); } } catch (Exception e) { e.printStackTrace(); } } }

上面同样有锁失效的问题,所以同样有Redis aop锁,具体实现如下:

复制代码
1
2
3
4
5
6
7
8
9
@Target({ElementType.PARAMETER,ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface ServiceRedisLock { String description() default ""; }
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
@Component @Scope @Aspect @Order(1) // 表示在事务之前执行 public class LockRedisAspect { @Pointcut("@annotation(com.sugo.seckill.aop.redis.ServiceRedisLock)") public void lockAspect(){ } // 增强方法 @Around("lockAspect()") public Object around(ProceedingJoinPoint joinPoint){ Object obj = null; // 加锁 boolean res = RedissLockUtil.tryLock(Constants.DISTRIBUTED_REDIS_LOCK_KEY, TimeUnit.SECONDS, 3, 10); // 执行业务 try { if(res){ obj = joinPoint.proceed(); } } catch (Throwable throwable) { throwable.printStackTrace(); } finally { // 释放锁 if(res){ RedissLockUtil.unlock(Constants.DISTRIBUTED_REDIS_LOCK_KEY); } } return obj; } }
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
public class RedissLockUtil { private static RedissonClient redissonClient; public void setRedissonClient(RedissonClient locker) { redissonClient = locker; } /** * 加锁 * @param lockKey * @return */ public static RLock lock(String lockKey) { RLock lock = redissonClient.getLock(lockKey); lock.lock(); return lock; } /** * 释放锁 * @param lockKey */ public static void unlock(String lockKey) { RLock lock = redissonClient.getLock(lockKey); lock.unlock(); } /** * 释放锁 * @param lock */ public static void unlock(RLock lock) { lock.unlock(); } /** * 带超时的锁 * @param lockKey * @param timeout 超时时间 单位:秒 */ public static RLock lock(String lockKey, int timeout) { RLock lock = redissonClient.getLock(lockKey); lock.lock(timeout, TimeUnit.SECONDS); return lock; } /** * 带超时的锁 * @param lockKey * @param unit 时间单位 * @param timeout 超时时间 */ public static RLock lock(String lockKey, TimeUnit unit ,int timeout) { RLock lock = redissonClient.getLock(lockKey); lock.lock(timeout, unit); return lock; } /** * 尝试获取锁 * @param lockKey * @param waitTime 最多等待时间 * @param leaseTime 上锁后自动释放锁时间 * @return */ public static boolean tryLock(String lockKey, int waitTime, int leaseTime) { RLock lock = redissonClient.getLock(lockKey); try { return lock.tryLock(waitTime, leaseTime, TimeUnit.SECONDS); } catch (InterruptedException e) { return false; } } /** * 尝试获取锁 * @param lockKey * @param unit 时间单位 * @param waitTime 最多等待时间 * @param leaseTime 上锁后自动释放锁时间 * @return */ public static boolean tryLock(String lockKey, TimeUnit unit, int waitTime, int leaseTime) { RLock lock = redissonClient.getLock(lockKey); try { return lock.tryLock(waitTime, leaseTime, unit); } catch (InterruptedException e) { return false; } } /** * 初始红包数量 * @param key * @param count */ public void initCount(String key,int count) { RMapCache<String, Integer> mapCache = redissonClient.getMapCache("skill"); mapCache.putIfAbsent(key,count,3,TimeUnit.DAYS); } /** * 递增 * @param key * @param delta 要增加几(大于0) * @return */ public int incr(String key, int delta) { RMapCache<String, Integer> mapCache = redissonClient.getMapCache("skill"); if (delta < 0) { throw new RuntimeException("递增因子必须大于0"); } return mapCache.addAndGet(key, 1);//加1并获取计算后的值 } /** * 递减 * @param key 键 * @param delta 要减少几(小于0) * @return */ public int decr(String key, int delta) { RMapCache<String, Integer> mapCache = redissonClient.getMapCache("skill"); if (delta < 0) { throw new RuntimeException("递减因子必须大于0"); } return mapCache.addAndGet(key, -delta);//加1并获取计算后的值 } }

基于Zookeeper实现分布式锁

依赖

复制代码
1
2
3
4
5
6
<dependency> <groupId>org.apache.zookeeper</groupId> <artifactId>zookeeper</artifactId> <version>3.4.14</version> </dependency>

定义ZkLock

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
import lombok.extern.slf4j.Slf4j; import org.apache.zookeeper.*; import org.apache.zookeeper.data.Stat; import java.io.IOException; import java.util.Collections; import java.util.List; @Slf4j public class ZkLock implements AutoCloseable, Watcher { private ZooKeeper zooKeeper; private String znode; public ZkLock() throws IOException { this.zooKeeper = new ZooKeeper("localhost:2181", 10000,this); } public boolean getLock(String businessCode) { try { //创建业务 根节点 Stat stat = zooKeeper.exists("/" + businessCode, false); if (stat==null){ zooKeeper.create("/" + businessCode,businessCode.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); } //创建瞬时有序节点 /order/order_00000001 znode = zooKeeper.create("/" + businessCode + "/" + businessCode + "_", businessCode.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL); //获取业务节点下 所有的子节点 List<String> childrenNodes = zooKeeper.getChildren("/" + businessCode, false); //子节点排序 Collections.sort(childrenNodes); //获取序号最小的(第一个)子节点 String firstNode = childrenNodes.get(0); //如果创建的节点是第一个子节点,则获得锁 if (znode.endsWith(firstNode)){ return true; } //不是第一个子节点,则监听前一个节点 String lastNode = firstNode; for (String node:childrenNodes){ if (znode.endsWith(node)){ zooKeeper.exists("/"+businessCode+"/"+lastNode,true); break; }else { lastNode = node; } } synchronized (this){ wait(); } return true; } catch (Exception e) { e.printStackTrace(); } return false; } @Override public void close() throws Exception { zooKeeper.delete(znode,-1); zooKeeper.close(); log.info("我已经释放了锁!"); } @Override public void process(WatchedEvent event) { if (event.getType() == Event.EventType.NodeDeleted){ synchronized (this){ notify(); } } } }

使用ZkLock

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@RestController @Slf4j public class ZookeeperController { @Autowired private CuratorFramework client; @RequestMapping("zkLock") public String zookeeperLock(){ log.info("我进入了方法!"); try (ZkLock zkLock = new ZkLock()) { if (zkLock.getLock("order")){ log.info("我获得了锁"); Thread.sleep(10000); } } catch (IOException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } log.info("方法执行完成!"); return "方法执行完成!"; } }

基于curator实现分布式锁

依赖

复制代码
1
2
3
4
5
6
<dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-recipes</artifactId> <version>4.2.0</version> </dependency>

启动类注入CuratorFramework

复制代码
1
2
3
4
5
6
7
@Bean(initMethod="start",destroyMethod = "close") public CuratorFramework getCuratorFramework() { RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3); CuratorFramework client = CuratorFrameworkFactory.newClient("localhost:2181", retryPolicy); return client; }

使用

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@RestController @Slf4j public class ZookeeperController { @Autowired private CuratorFramework client; @RequestMapping("curatorLock") public String curatorLock(){ log.info("我进入了方法!"); // /order表示业务路径 InterProcessMutex lock = new InterProcessMutex(client, "/order"); try{ if (lock.acquire(30, TimeUnit.SECONDS)){ log.info("我获得了锁!!"); Thread.sleep(10000); } } catch (IOException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); }finally { try { log.info("我释放了锁!!"); lock.release(); } catch (Exception e) { e.printStackTrace(); } } log.info("方法执行完成!"); return "方法执行完成!"; } }

基于Redisson实现分布式锁(SpringBoot项目推荐用法)

依赖

复制代码
1
2
3
4
5
6
<dependency> <groupId>org.redisson</groupId> <artifactId>redisson-spring-boot-starter</artifactId> <version>3.17.2</version> </dependency>

配置redis
在这里插入图片描述
使用

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@RestController @Slf4j public class RedissonLockController { @Autowired private RedissonClient redisson; @RequestMapping("redissonLock") public String redissonLock() { RLock rLock = redisson.getLock("order"); log.info("我进入了方法!!"); try { rLock.lock(30, TimeUnit.SECONDS); log.info("我获得了锁!!!"); Thread.sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); }finally { log.info("我释放了锁!!"); rLock.unlock(); } log.info("方法执行完成!!"); return "方法执行完成!!"; } }

基于Redisson实现分布式锁(通过xml配置)

依赖

复制代码
1
2
3
4
5
6
<dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>3.11.2</version> </dependency>

定义redisson.xml
在这里插入图片描述

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:redisson="http://redisson.org/schema/redisson" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://redisson.org/schema/redisson http://redisson.org/schema/redisson/redisson.xsd "> <redisson:client> <!--单机模式--> <redisson:single-server address="redis://127.0.0.1:6379"/> </redisson:client> </beans>

启动类上添加@ImportResource(“classpath*:redisson.xml”)

复制代码
1
2
3
4
5
6
7
8
@SpringBootApplication @ImportResource("classpath*:redisson.xml") public class RedissonLockApplication { public static void main(String[] args) { SpringApplication.run(RedissonLockApplication.class, args); } }

使用

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@RestController @Slf4j public class RedissonLockController { @Autowired private RedissonClient redisson; @RequestMapping("redissonLock") public String redissonLock() { RLock rLock = redisson.getLock("order"); log.info("我进入了方法!!"); try { rLock.lock(30, TimeUnit.SECONDS); log.info("我获得了锁!!!"); Thread.sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); }finally { log.info("我释放了锁!!"); rLock.unlock(); } log.info("方法执行完成!!"); return "方法执行完成!!"; } }

多种分布式锁,我如何选择

推荐使用Redisson和Cutator实现分布式锁, 成熟,有大量的实践!

最后

以上就是玩命银耳汤最近收集整理的关于Java单体项目和分布式项目中的锁的全部内容,更多相关Java单体项目和分布式项目中内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(70)

评论列表共有 0 条评论

立即
投稿
返回
顶部