原文链接:https://www.cnblogs.com/bluesummer/p/7874788.html
Redis事物
Redis命令实现事务
Redis的事物包含在multi和exec(执行)或者discard(回滚)命令中
和sql事务不同的是,Redis调用Exec只是将所有的命令变成一个单元一起执行,期间不会插入其他的命令。
这种方式不保证事务的一致性,即使中间有一条命令出错了,其他命令仍然可以正常执行,并且无法回滚
下面的例子演示了一个基本的事务操作
1
2
3
4
5
6
7
8
9
10
11
|
127.0.0.1:6379> multi
OK
127.0.0.1:6379>
set
name mike
QUEUED
127.0.0.1:6379>
set
age 20
QUEUED
127.0.0.1:6379> exec
1) OK
2) OK
127.0.0.1:6379>
get
name
"mike"
|
可以看到,直到调用Exec命令时,才开始执行之前的所有命令,同时会返回两个结果,discard 命令类似,就不贴代码了。
下面模拟一个会报错的命令来看一下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
127.0.0.1:6379>
get
age
"20"
127.0.0.1:6379>
get
name
"mike"
127.0.0.1:6379> multi
OK
127.0.0.1:6379> incr name
QUEUED
127.0.0.1:6379> incr age
QUEUED
127.0.0.1:6379> exec
1) (error) ERR value
is
not an integer or
out
of range
2) (integer) 21
127.0.0.1:6379>
get
age
"21"
127.0.0.1:6379>
|
我们同时将用户name和age 进行自增1操作,然而name不是数字类型,结果执行失败,但是age的自增操作仍然成功了。这无疑是个很令人不舒服的弊端,所以在写相关代码时要注意
乐观锁
前面说到通过multi命令只是保证一个事物中的所有命令可以在一起执行,显然只是实现这一点的话对于大部分的业务都是无法满足的。
所以Redis提供了Watch命令来监控一个key以达到乐观锁的效果。关于乐观锁的原理有不了解的小伙伴可以抽十分钟去科普一下
下面展示一个乐观锁实例:
这里模拟了两个客户端同时操作一个相同的键
左边为client1,我们用watch监控了name和age两个键,然后分别设置name和age的值。在exec命令之前,通过另一个客户端client2设置了name的值。
client1执行exec命令时,Redis检测到name的值已经被其他客户端改过了,因此在事物中的所有命令都会回滚。
watch命令是对整个连接有效的,用完之后可以用discard、unwatch、exec命令清除监视
StackExchange.Redis中的事物控制
在StackExchange.Redis是无法用watch multi命令来执行的,因为在并发环境下,会产生多个watch multi命令,全混在一起就乱套了。
但是StackExchange.Redis提供了一套非常简单易懂的创建事物的方式 ,下面为示例代码
public void TestTran() { IDatabase db = StackExchangeRedisHelper.GetDatabase(); string name = db.StringGet("name"); string age = db.StringGet("age"); Console.WriteLine("NAME:" + name); Console.WriteLine("Age:" + age); var tran = db.CreateTransaction(); tran.AddCondition(Condition.StringEqual("name", name)); Console.WriteLine("tran begin"); tran.StringSetAsync("name", "leap"); tran.StringSetAsync("age", 12); Thread.Sleep(4000); bool result = tran.Execute(); Console.WriteLine("执行结果:" + result); Console.WriteLine("Age:" + db.StringGet("age")); Console.WriteLine("Name:" + db.StringGet("name")); }
这里通过CreateTransaction函数(multi)来创建一个事物,调用其Execute函数(exec)提交事物,其中的 "Condition.StringEqual("name", name)" 就相当于Redis命令中的watch name。
其中睡眠四秒是我需要在事物提交之前打开另一个客户端来修改name的值.最终的执行结果如下
1
2
3
4
5
6
|
NAME:leo
Age:20
tran begin
执行结果:False
Age:20
Name:mike
|
在程序睡眠期间我用另一个客户端将name改成了mike,所以事物最终执行失败
通过查询Redis的慢日志。其调用的命令也是watch multi exec。(慢日志没有记录Exec命令,实际上是执行了的)。
我们可以通过设置redis.windows-service.conf文件中的slowlog-log-slower-than的值为0让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
|
127.0.0.1:6379> slowlog
get
100
1) 1) (integer) 293
2) (integer) 1511257634
3) (integer) 1
4) 1)
"GET"
2)
"name"
2) 1) (integer) 292
2) (integer) 1511257634
3) (integer) 0
4) 1)
"GET"
2)
"age"
3) 1) (integer) 291
2) (integer) 1511257634
3) (integer) 3
4) 1)
"SELECT"
2)
"0"
4) 1) (integer) 290
2) (integer) 1511257634
3) (integer) 1
4) 1)
"SET"
2)
"age"
3)
"12"
5) 1) (integer) 289
2) (integer) 1511257634
3) (integer) 1
4) 1)
"SET"
2)
"name"
3)
"leap"
6) 1) (integer) 288
2) (integer) 1511257634
3) (integer) 1
4) 1)
"MULTI"
7) 1) (integer) 287
2) (integer) 1511257634
3) (integer) 3
4) 1)
"GET"
2)
"name"
8) 1) (integer) 286
2) (integer) 1511257634
3) (integer) 11
4) 1)
"WATCH"
2)
"name"
9) 1) (integer) 285
2) (integer) 1511257634
3) (integer) 4
4) 1)
"GET"
2)
"age"
10) 1) (integer) 284
2) (integer) 1511257634
3) (integer) 6
4) 1)
"GET"
2)
"name"
|
这里可能大家会有个疑惑,既然tran是直接调用的watch multi等命令,为什么不会有并发的顺序问题?
这是因为Tran开启后,所做的watch,stringset等操作,都会再调用Exec函数时把相应的命令封装成一个请求发送给Redis一起执行。这样每个事务之间都是独立的,就不会有问题了。
Batch批量操作
StackExchange.Redis中对于连续多次的缓存等请求,我们会多次调用相关的函数来执行Redis命令。然而这种方式有个弊端就是每一次的请求都需要等待返回结果
如果在网络状况不好的情况下,可能会造成不好的用户体验。
对于这种问题可以用StackExchange.Redis提供的CreateBatch()解决
public void TestPipeLine() { IDatabase db = StackExchangeRedisHelper.GetDatabase(); var batch = db.CreateBatch(); Task t1 = batch.StringSetAsync("name", "bob"); Task t2 = batch.StringSetAsync("age", 100); batch.Execute(); Task.WaitAll(t1, t2); Console.WriteLine("Age:" + db.StringGet("age")); Console.WriteLine("Name:" + db.StringGet("name")); }
batch会把所需要执行的命令打包成一条请求发到Redis,然后一起等待返回结果。这样批量操作的速度就大大提升啦!
原文链接:https://www.cnblogs.com/bluesummer/p/7874788.html
Redis事物
Redis命令实现事务
Redis的事物包含在multi和exec(执行)或者discard(回滚)命令中
和sql事务不同的是,Redis调用Exec只是将所有的命令变成一个单元一起执行,期间不会插入其他的命令。
这种方式不保证事务的一致性,即使中间有一条命令出错了,其他命令仍然可以正常执行,并且无法回滚
下面的例子演示了一个基本的事务操作
1
2
3
4
5
6
7
8
9
10
11
|
127.0.0.1:6379> multi
OK
127.0.0.1:6379>
set
name mike
QUEUED
127.0.0.1:6379>
set
age 20
QUEUED
127.0.0.1:6379> exec
1) OK
2) OK
127.0.0.1:6379>
get
name
"mike"
|
可以看到,直到调用Exec命令时,才开始执行之前的所有命令,同时会返回两个结果,discard 命令类似,就不贴代码了。
下面模拟一个会报错的命令来看一下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
127.0.0.1:6379>
get
age
"20"
127.0.0.1:6379>
get
name
"mike"
127.0.0.1:6379> multi
OK
127.0.0.1:6379> incr name
QUEUED
127.0.0.1:6379> incr age
QUEUED
127.0.0.1:6379> exec
1) (error) ERR value
is
not an integer or
out
of range
2) (integer) 21
127.0.0.1:6379>
get
age
"21"
127.0.0.1:6379>
|
我们同时将用户name和age 进行自增1操作,然而name不是数字类型,结果执行失败,但是age的自增操作仍然成功了。这无疑是个很令人不舒服的弊端,所以在写相关代码时要注意
乐观锁
前面说到通过multi命令只是保证一个事物中的所有命令可以在一起执行,显然只是实现这一点的话对于大部分的业务都是无法满足的。
所以Redis提供了Watch命令来监控一个key以达到乐观锁的效果。关于乐观锁的原理有不了解的小伙伴可以抽十分钟去科普一下
下面展示一个乐观锁实例:
这里模拟了两个客户端同时操作一个相同的键
左边为client1,我们用watch监控了name和age两个键,然后分别设置name和age的值。在exec命令之前,通过另一个客户端client2设置了name的值。
client1执行exec命令时,Redis检测到name的值已经被其他客户端改过了,因此在事物中的所有命令都会回滚。
watch命令是对整个连接有效的,用完之后可以用discard、unwatch、exec命令清除监视
StackExchange.Redis中的事物控制
在StackExchange.Redis是无法用watch multi命令来执行的,因为在并发环境下,会产生多个watch multi命令,全混在一起就乱套了。
但是StackExchange.Redis提供了一套非常简单易懂的创建事物的方式 ,下面为示例代码
public void TestTran() { IDatabase db = StackExchangeRedisHelper.GetDatabase(); string name = db.StringGet("name"); string age = db.StringGet("age"); Console.WriteLine("NAME:" + name); Console.WriteLine("Age:" + age); var tran = db.CreateTransaction(); tran.AddCondition(Condition.StringEqual("name", name)); Console.WriteLine("tran begin"); tran.StringSetAsync("name", "leap"); tran.StringSetAsync("age", 12); Thread.Sleep(4000); bool result = tran.Execute(); Console.WriteLine("执行结果:" + result); Console.WriteLine("Age:" + db.StringGet("age")); Console.WriteLine("Name:" + db.StringGet("name")); }
这里通过CreateTransaction函数(multi)来创建一个事物,调用其Execute函数(exec)提交事物,其中的 "Condition.StringEqual("name", name)" 就相当于Redis命令中的watch name。
其中睡眠四秒是我需要在事物提交之前打开另一个客户端来修改name的值.最终的执行结果如下
1
2
3
4
5
6
|
NAME:leo
Age:20
tran begin
执行结果:False
Age:20
Name:mike
|
在程序睡眠期间我用另一个客户端将name改成了mike,所以事物最终执行失败
通过查询Redis的慢日志。其调用的命令也是watch multi exec。(慢日志没有记录Exec命令,实际上是执行了的)。
我们可以通过设置redis.windows-service.conf文件中的slowlog-log-slower-than的值为0让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
|
127.0.0.1:6379> slowlog
get
100
1) 1) (integer) 293
2) (integer) 1511257634
3) (integer) 1
4) 1)
"GET"
2)
"name"
2) 1) (integer) 292
2) (integer) 1511257634
3) (integer) 0
4) 1)
"GET"
2)
"age"
3) 1) (integer) 291
2) (integer) 1511257634
3) (integer) 3
4) 1)
"SELECT"
2)
"0"
4) 1) (integer) 290
2) (integer) 1511257634
3) (integer) 1
4) 1)
"SET"
2)
"age"
3)
"12"
5) 1) (integer) 289
2) (integer) 1511257634
3) (integer) 1
4) 1)
"SET"
2)
"name"
3)
"leap"
6) 1) (integer) 288
2) (integer) 1511257634
3) (integer) 1
4) 1)
"MULTI"
7) 1) (integer) 287
2) (integer) 1511257634
3) (integer) 3
4) 1)
"GET"
2)
"name"
8) 1) (integer) 286
2) (integer) 1511257634
3) (integer) 11
4) 1)
"WATCH"
2)
"name"
9) 1) (integer) 285
2) (integer) 1511257634
3) (integer) 4
4) 1)
"GET"
2)
"age"
10) 1) (integer) 284
2) (integer) 1511257634
3) (integer) 6
4) 1)
"GET"
2)
"name"
|
这里可能大家会有个疑惑,既然tran是直接调用的watch multi等命令,为什么不会有并发的顺序问题?
这是因为Tran开启后,所做的watch,stringset等操作,都会再调用Exec函数时把相应的命令封装成一个请求发送给Redis一起执行。这样每个事务之间都是独立的,就不会有问题了。
Batch批量操作
StackExchange.Redis中对于连续多次的缓存等请求,我们会多次调用相关的函数来执行Redis命令。然而这种方式有个弊端就是每一次的请求都需要等待返回结果
如果在网络状况不好的情况下,可能会造成不好的用户体验。
对于这种问题可以用StackExchange.Redis提供的CreateBatch()解决
public void TestPipeLine() { IDatabase db = StackExchangeRedisHelper.GetDatabase(); var batch = db.CreateBatch(); Task t1 = batch.StringSetAsync("name", "bob"); Task t2 = batch.StringSetAsync("age", 100); batch.Execute(); Task.WaitAll(t1, t2); Console.WriteLine("Age:" + db.StringGet("age")); Console.WriteLine("Name:" + db.StringGet("name")); }
batch会把所需要执行的命令打包成一条请求发到Redis,然后一起等待返回结果。这样批量操作的速度就大大提升啦!