摘要:本文使用redis来实现乐观锁,并以秒杀系统为实例来讲解整个过程。
本文源码请在这里下载:https://github.com/appleappleapple/DistributeLearning
乐观锁
大多数是基于数据版本(version)的记录机制实现的。即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个”version”字段来实现读取出数据时,将此版本号一同读出,之后更新时,对此版本号加1。此时,将提交数据的版本号与数据库表对应记录的当前版本号进行比对,如果提交的数据版本号大于数据库当前版本号,则予以更新,否则认为是过期数据。redis中可以使用watch命令会监视给定的key,当exec时候如果监视的key从调用watch后发生过变化,则整个事务会失败。也可以调用watch多次监视多个key。这样就可以对指定的key加乐观锁了。注意watch的key是对整个连接有效的,事务也一样。如果连接断开,监视和事务都会被自动清除。当然了exec,discard,unwatch命令都会清除连接中的所有监视。
Redis事务
Redis中的事务(transaction)是一组命令的集合。事务同命令一样都是Redis最小的执行单位,一个事务中的命令要么都执行,要么都不执行。Redis事务的实现需要用到 MULTI 和 EXEC 两个命令,事务开始的时候先向Redis服务器发送 MULTI 命令,然后依次发送需要在本次事务中处理的命令,最后再发送 EXEC 命令表示事务命令结束。Redis的事务是下面4个命令来实现
1.multi,开启Redis的事务,置客户端为事务态。
2.exec,提交事务,执行从multi到此命令前的命令队列,置客户端为非事务态。
3.discard,取消事务,置客户端为非事务态。
4.watch,监视键值对,作用时如果事务提交exec时发现监视的监视对发生变化,事务将被取消。
下面笔者简单实现一个用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
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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146 1package com.github.distribute.lock.redis;
2
3import java.util.List;
4import java.util.Set;
5import java.util.concurrent.ExecutorService;
6import java.util.concurrent.Executors;
7
8import redis.clients.jedis.Jedis;
9import redis.clients.jedis.Transaction;
10
11/**
12 * redis乐观锁实例
13 * @author linbingwen
14 *
15 */
16public class OptimisticLockTest {
17
18 public static void main(String[] args) throws InterruptedException {
19 long starTime=System.currentTimeMillis();
20
21 initPrduct();
22 initClient();
23 printResult();
24
25 long endTime=System.currentTimeMillis();
26 long Time=endTime-starTime;
27 System.out.println("程序运行时间: "+Time+"ms");
28
29 }
30
31 /**
32 * 输出结果
33 */
34 public static void printResult() {
35 Jedis jedis = RedisUtil.getInstance().getJedis();
36 Set<String> set = jedis.smembers("clientList");
37
38 int i = 1;
39 for (String value : set) {
40 System.out.println("第" + i++ + "个抢到商品,"+value + " ");
41 }
42
43 RedisUtil.returnResource(jedis);
44 }
45
46 /*
47 * 初始化顾客开始抢商品
48 */
49 public static void initClient() {
50 ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
51 int clientNum = 10000;// 模拟客户数目
52 for (int i = 0; i < clientNum; i++) {
53 cachedThreadPool.execute(new ClientThread(i));
54 }
55 cachedThreadPool.shutdown();
56
57 while(true){
58 if(cachedThreadPool.isTerminated()){
59 System.out.println("所有的线程都结束了!");
60 break;
61 }
62 try {
63 Thread.sleep(1000);
64 } catch (InterruptedException e) {
65 e.printStackTrace();
66 }
67 }
68 }
69
70 /**
71 * 初始化商品个数
72 */
73 public static void initPrduct() {
74 int prdNum = 100;// 商品个数
75 String key = "prdNum";
76 String clientList = "clientList";// 抢购到商品的顾客列表
77 Jedis jedis = RedisUtil.getInstance().getJedis();
78
79 if (jedis.exists(key)) {
80 jedis.del(key);
81 }
82
83 if (jedis.exists(clientList)) {
84 jedis.del(clientList);
85 }
86
87 jedis.set(key, String.valueOf(prdNum));// 初始化
88 RedisUtil.returnResource(jedis);
89 }
90
91}
92
93/**
94 * 顾客线程
95 *
96 * @author linbingwen
97 *
98 */
99class ClientThread implements Runnable {
100 Jedis jedis = null;
101 String key = "prdNum";// 商品主键
102 String clientList = "clientList";//// 抢购到商品的顾客列表主键
103 String clientName;
104
105 public ClientThread(int num) {
106 clientName = "编号=" + num;
107 }
108
109 public void run() {
110 try {
111 Thread.sleep((int)(Math.random()*5000));// 随机睡眠一下
112 } catch (InterruptedException e1) {
113 }
114 while (true) {
115 System.out.println("顾客:" + clientName + "开始抢商品");
116 jedis = RedisUtil.getInstance().getJedis();
117 try {
118 jedis.watch(key);
119 int prdNum = Integer.parseInt(jedis.get(key));// 当前商品个数
120 if (prdNum > 0) {
121 Transaction transaction = jedis.multi();
122 transaction.set(key, String.valueOf(prdNum - 1));
123 List<Object> result = transaction.exec();
124 if (result == null || result.isEmpty()) {
125 System.out.println("悲剧了,顾客:" + clientName + "没有抢到商品");// 可能是watch-key被外部修改,或者是数据操作被驳回
126 } else {
127 jedis.sadd(clientList, clientName);// 抢到商品记录一下
128 System.out.println("好高兴,顾客:" + clientName + "抢到商品");
129 break;
130 }
131 } else {
132 System.out.println("悲剧了,库存为0,顾客:" + clientName + "没有抢到商品");
133 break;
134 }
135 } catch (Exception e) {
136 e.printStackTrace();
137 } finally {
138 jedis.unwatch();
139 RedisUtil.returnResource(jedis);
140 }
141
142 }
143 }
144
145}
146
和上文的使用悲观锁相比,乐观锁的实现更加的简单,并发性能也会更好。
本文源码请在这里下载:https://github.com/appleappleapple/DistributeLearning