Redis原理(一)

发布订阅模式

列表的局限

  通过队列的rpush和blpop可以实现消息队列(队尾进队头出),没有任何元素可以弹出的时候,连接会被阻塞。
  但是基于list实现的消息队列,不支持一对多的消息分发,相当于只有一个消费者。
  如果要实现一对多的消息分发,怎么办?
  可以通过消息发布者(发布者)向指定的频道发布消息,订阅者(接收者)则通过订阅该频道来接收消息。
  注意:Redis的发布/订阅模式是一种异步消息传递模式,因此订阅者需要在消息发布之前就已经订阅了频道,否则将无法接收到该消息

发布订阅模式

  除了通过list实现消息队列之外,Redis还提供了发布订阅的功能。

订阅频道

  消息的生产者和消费者是不同的客户端,连接到同一个Redis的服务。通过什么对象把生产者和消费者关联起来呢?
  在RabbitMQ里面叫Queue,在Kafka里面叫Topic。 Redis的模型里面这个叫channel (频道)。

订阅者可以订阅一个或者多个channeL消息的发布者可以给指定的channel发布消息。只要有消息到达了 channel,所有订阅了这个channel的订阅者都会收到这条消息。

订阅者订阅频道:可以一次订阅多个,比如这个客户端订阅了 3个频道,频道不用实现创建。

subscribe channel-1 channel-2 channel-3

发布者可以向指定频道发布消息(并不支持一次向多个频道发送消息):

publish channel-1 2673

取消订阅(不能在订阅状态下使用):

unsubscribe channel-1

按规则(Pattern)订阅频道

支持?和占位符。?代表一个字符,代表0个或者多个字符。
例如,现在有三个新闻频道,运动新闻(news-sport)、音乐新闻(news-music)、天气新闻(news-weather) 。
三个消费者,消费端1,关注运动信息:

psubscribe *sport

消费端2,关注所有新闻:

psubscribe news*

消费端3,关注天气新闻:

psubscribe news-weather

生产者,向3个频道发布3条信息,对应的订阅者能收到消息:

publish news-sport kobe
publish news-music jaychou
publish news-weather sunny

一般来说,考虑到性能和持久化的因素,不建议使用Redis的发布订阅功能来实现MQ。Redis的一些内部机制用到了发布订阅功能。

Redis事务

为什么要用事务呢

Redis的单个命令是原子性的(比如get set mget mset) , 要么成功要么失败,不存在并发干扰的问题。

如果涉及到多个命令的时候,需要把多个命令作为一个不可分割的处理序列,就必须要依赖Redis的功能特性来实现了。

Redis提供了事务的功能,可以把一组命令一起执行。Redis的事务有3个特点:

1、按进入队列的倾序执行。
2、不会受到其他客户端的请求的影响。
3、事务不能嵌套,多个multi命令效果一样

事务的用法

Redis的事务涉及到四个命令:multi (开启事务),exec (执行事务),discard(取消事务),watch(监视)。
案例场景:xiami和xiaoming各有1000元,xiami向xiaoming转账100元。

set xiami 1000 
set xiaoming 1000 
multi 
decrby xiami 100 
incrby xiaoming 100 
exec
get xiami
get xiaoming

通过multi的命令开启事务。multi执行后,客户端可以继续向服务器发送任意多条命令,这些命令不会立即被执行,而是被放到一个队列中。当exec命令被调用时,所有队列中的命令才会被执行。
如果没有执行exec,所有的命令都不会被执行。
QA:如果中途不想执行事务了,怎么办?
可以调用discard可以清空事务队列,放弃执行。

multi
decrby xiami 100 
discard
get xiami

watch命令

为了防止事务过程中某个key的值被其他客户端请求修改,带来非预期的结果,在 Redis中还提供了一个watch命令。
也就是多个客户端更新变量的时候,会跟原值做比较,只有它没有被其他线程修改 的情况下,才更新成新的值。它可以为Redis事务提供CAS乐观锁行为(Compare and Swap) 。

我们可以用watch监视一个或者多个key,如果开启事务之后,至少有一个被监视 key键在exec执行之前被修改了,那么整个事务都会被取消(key提前过期除外)。可以用unwatch取消。

client1 client2
set balance
watch balance
multi
incrby balance 100
decrby balance 100
exec【返回nil】
get balance

以上是事务相关的命令的基本使用方法。如果在执行时候的时候发生了异常,而不是我们主动取消,会发生什么情况呢?

事务可能遇到的问题

事务执行遇到的问题分成两种,一种是在执行exec之前发生错误,一种是在执行 exec之后发生错误。

在执行exec之前发生错误

比如:入队的命令存在语法错误,包括参数数量,参数名等等(编译器错误)。

multi
set snail 2673
set hehe yes
hset bobo 666
exec

比如这里出现了参数个数错误,事务会被拒绝执行,也就是队列中所有的命令都不会得到执行。

在执行exec之后发生错误

比如对String使用了 Hash的命令,参数个数正确,但数据类型错误,这是一种运行时错误。

flushall
multi
set k1 1
hset k1 a b
exec
1)OK
2)(error) WRONGTYPE Operation against a key holding the wrong kind of value 
get k1

最后我们发现set k1 1的命令是成功的,也就是在这种发生了运行时异常的情况下,只有错误的命令没有被执行,但是其他命令没有受到影响。

这个显然不符合我们对原子性的定义。也就是我们没办法用Redis的这种事务机制来实现原子性,保证数据的一致。

那么为什么不回滚呢?

官方的解释是这样的:

  • Redis命令只会因为错误的语法而失败,也就是说,从实用性的角度来说,失败的命令是由代码错误造成的,而这些错误应该在开发的过程中被发现,而不应该出现在生产环境中(这个是程序员的锅)。
  • 因为不需要对回滚进行支持,所以Redis的内部可以保持简单且快速。需要知道的是:回滚不能解决代码的问题(程序员的锅必须程序员来背)。

Redis从2.6版本开始引入了 Lua脚本,也就是说Redis可以用Lua来执行Redis 命令。

posted @ 2021-01-21 00:10  snail灬  阅读(55)  评论(0编辑  收藏  举报