Redis原理之_交头接耳_通信协议
前言
Github:https://github.com/HealerJean
1、引入
为了确保连续多个操作的原子性,一个成熟的数据库通常都会有事务支持,
Redis
也不 例外。Redis
的事务使用非常简单**不同于关系数据库,我们无须理解那么多复杂的事务模 型,就可以直接使用。不过也正是因为这种简单性,它的事务模型很不严格,这要求我们不 能像使用关系数据库的事务一样来使用
Redis
**
Redis
事务不保证原子性,且没有回滚。存在事务中任意命令执行失败,其余的命令仍会被执行。
2、Redis
事务的基本使用
每个事务的操作都有
begin
、commit
和rollback
,Redis
在形式上看起来也差不多,分别是multi
/exec
/discard
multi
和exec
这两个命令,一个代表事物开始,一个代表事物结束 。discard
命了可以用来放弃事务
2.1、命令正常
**redis是不支持回滚的特性,只要语法没有问题,中间即使有的命令报错,其他的命令也一定会执行**
举例:社交网站上用户A关注了用户B,那么需要再用户A的关注表中添加用户B,用户B的表中添加粉丝A 这是两个操作,只能同时失败或者成功
客户端1
127.0.0.1:6379> multi
OK
127.0.0.1:6379> sadd user:A:flow user:B
QUEUED
127.0.0.1:6379> sadd user:B:fans user:A
QUEUED
客户端2
这个时候,如果其他客户端执行查找命令,则会提示在队列中,不能查找成功
127.0.0.1:6379> sismember user:A:flow user:B
QUEUED
127.0.0.1:6379> smembers user:A:flow
QUEUED
127.0.0.1:6379>
客户端1
只有执行exec 上面两条命令才能执行成功
127.0.0.1:6379> exec
1) (integer) 1
2) (integer) 1
127.0.0.1:6379>
2.1、命令错误
1.1.1、语法命令错误不会提交事务
事物中间命令出现语法错误,事物
exec
不能正常执行
127.0.0.1:6379> smembers user:A:flow user:B # 语法错误。此处应该使用 sismember
(error) ERR wrong number of arguments for 'smembers' command
127.0.0.1:6379> smembers user:A:flow
QUEUED
127.0.0.1:6379> exec
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> MULTI
1.1.2、运行时错误会提交事务
没有语法错误,当命令格式正确,而因为操作数据结构引起的错误,则该命令执行出现错误,而其之前和之后的命令都会被正常执行。
127.0.0.1:6379> set num1 1
127.0.0.1:6379> set key healerjean
127.0.0.1:6379> set num2 2
QUEUED
127.0.0.1:6379> multi
OK
127.0.0.1:6379> incr num1
QUEUED
127.0.0.1:6379> incr key
QUEUED
127.0.0.1:6379> incr num2
QUEUED
127.0.0.1:6379> exec
1) (integer) 2
2) (error) ERR value is not an integer or out of range
3) (integer) 3
3、事务包含多条命令执行优化
问题1: Redis
事务在发送每个指令到事务缓存队列时都要经过一次网络读写,当一个事务内部的指令较多时,需要的网络 IO
时间也会线性增长。怎么能优化一下呢?
答案:通常 Redis
的客户端在执行 事务时都会结合 pipeline
一起使用,这样可以将多次 IO
操作压缩为单次 IO
操作。比如我 们在使用 Python
的 Redis
客户端时执行事务时是要强制使用 pipeline
的。
pipe = redis.pipeline(transaction=true)
pipe.multi()
pipe.incr("books")
pipe.incr("books")
values = pipe.execute()
4、Watch
考虑到一个业务场景,
Redis
存储了我们的账户余额数据,它是一个整数。现在有两个 并发的客户端要对账户余额进行修改操作,这个修改不是一个简单的incrby
指令,而是要对余额乘以一个倍数。。我们需要先取出余额然后在 内存里乘以倍数,再将结果写回Redis
。
问题1:会出现并发问题,因为有多个客户端会并发进行操作。
答案:我们可以通过 Redis
的分布 式锁来避免冲突 ,这是一个很好的解决方案。分布式锁是一种悲观锁,那可以用乐观锁来解决吗?
问题2:如果使用乐观锁来解决上面这个问题呢?
答案:Redis
提供了这种 watch
的机制,它就是一种乐观锁。有了 watch
我们又多了一种可以 用来解决并发修改的方法。
Redis
提供了watch
命令来解决这个问题 ,作为WATCH
命令的参数的键会受到Redis
的监控,Redis
能够检测到它们的变化。在执行EXEC
命令之前,如果Redis
检测到至少有一个键被修改了,那么整个事务便会中止运行,然后EXEC
命令会返回一个nil
值,提醒用户事务运行失败。
# 客户端1
> watch books OK
> incr books (integer) 1 # 被修改了
# 客户端2
> multi
OK
> incr books
QUEUED
> exec # 事务执行失败 (nil)
(nil)
5、为什么 Redis
的事务不能支持回滚
首先需要明确的是:没有人能解决程序员自己的错误
在
exec
执行后所产生的错误中,即使事务中有某个/某些命令在执行时产生了错误,事务中的其他命令仍然会继续执行。Redis
在事务失败时不进行回滚,而是继续执行余下的命令。
Redis
的这种设计原则是:失败的命令不是Redis
所致,而是由编程错误造成的,这样错误应该在开发的过程中被发现,生产环境中不应出现的错误。而Redis
能够保证正确的命令一定会被执行。