Redis分布式锁—-乐观锁的实现,以秒杀系统为例

释放双眼,带上耳机,听听看~!

  摘要:本文使用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

Redis分布式锁----乐观锁的实现,以秒杀系统为例

和上文的使用悲观锁相比,乐观锁的实现更加的简单,并发性能也会更好。

本文源码请在这里下载:https://github.com/appleappleapple/DistributeLearning

给TA打赏
共{{data.count}}人
人已打赏
安全网络

CDN安全市场到2022年价值76.3亿美元

2018-2-1 18:02:50

安全经验

grpc(5):使用grpc+consul 开发服务调用

2021-11-28 16:36:11

个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索