Redis事务
redis中的事务是一组命令的集合,事务中的命令要么全部执行,要么都不执行,Redis 通过 MULTI 、DISCARD 、EXEC 和 WATCH 四个命令来实现事务功能,multi表示事物的开启,exec表示事物的执行,exec执行后返回事务执行的结果,discard表示放弃事务执行,清空事务队列中已有的所有命令并退出队列,watch用于监视给定的键,如果键被其他客户端修改,将不会执行事务。
127.0.0.1:6379> multi OK 127.0.0.1:6379> set key 1 QUEUED 127.0.0.1:6379> get key QUEUED 127.0.0.1:6379> exec 1) OK 2) "1" 127.0.0.1:6379> multi OK 127.0.0.1:6379> set key1 1 QUEUED 127.0.0.1:6379> discard OK 127.0.0.1:6379> get key1 (nil)
这里我在另一个客户端修改了被监视的key,导致在这个客户端事务没有执行
127.0.0.1:6379> set key 1 OK 127.0.0.1:6379> watch key OK 127.0.0.1:6379> multi OK 127.0.0.1:6379> incr key QUEUED 127.0.0.1:6379> incr key #客户端2 (integer) 2 127.0.0.1:6379> exec (nil) 127.0.0.1:6379> get key "2"
由于事务在执行时会独占服务器,所以尽量避免在事务中执行过多命令,以免服务器阻塞
Redis Pipline
redis是一个cs模式的tcp server,使用和http类似的请求响应协议。一个client可以通过一个socket连接发起多个请求命令。每个请求命令发出后client通常会阻塞并等待redis服务处理,redis处理完后请求命令后会将结果通过响应报文返回给client。如果网络延迟较大,那将会花费太多的时间,redis提供了pipline可以解决这个问题,redis可以在pipline中发送多个消息而无需等待每个消息的答复的过程。这里我使用python的redis库写了个demo来演示使用pipline的效果
from redis import Redis import time conn=Redis(host="60.205.177.100",port="6379") def usepipline(): start_time=time.time() pipline=conn.pipeline() for i in range(300): pipline.incr("key") pipline.execute() print("usepipline:",time.time()-start_time) def withoutpipline(): start_time=time.time() for i in range(300): conn.incr("key1") print("withoutpipline:",time.time()-start_time) usepipline() withoutpipline()
响应结果
usepipline: 1.2412519454956055 withoutpipline: 7.2261717319488525
可以看到使用pipline效果是很明显的
Redis发布订阅模式
Redis通过PUBLISH 、SUBSCRIBE 等命令实现了订阅与发布模式,发布者可以向多个频道发布消息,订阅者可以订阅多个频道,当然一个频道也可以有多个订阅者,发布者和订阅者的这种分离可以允许更大的可伸缩性和更动态的网络拓扑。
命令
向频道发送消息
publish channel message
例如
返回的是接收到消息的订阅者数量
127.0.0.1:6379> publish CCTV1 worldnews (integer) 0 127.0.0.1:6379> publish CCTV1 chinanews (integer) 0
订阅频道
subscribe channel [channel ...]
例如
127.0.0.1:6379> subscribe CCTV1 Reading messages... (press Ctrl-C to quit) 1) "subscribe" 2) "CCTV1" 3) (integer) 1 1) "message" 2) "CCTV1" 3) "chinanews"
退订频道
UNSUBSCRIBE [channel [channel ...]]
例如
127.0.0.1:6379> UNSUBSCRIBE CCTV1 CCTV2 1) "unsubscribe" 2) "CCTV1" 3) (integer) 0 4) "unsubscribe" 5) "CCTV2" 6) (integer) 0
订阅模式
redis支持使用glob的方式来一次订阅多个频道
PSUBSCRIBE pattern [pattern ...]
例如
127.0.0.1:6379> publish CCTV2 chinanew (integer) 1 127.0.0.1:6379> publish CCTV1 worldnews (integer) 1 127.0.0.1:6379> PSUBSCRIBE CCTV* Reading messages... (press Ctrl-C to quit) 1) "psubscribe" 2) "CCTV*" 3) (integer) 1 1) "pmessage" 2) "CCTV*" 3) "CCTV2" 4) "chinanews" 1) "pmessage" 2) "CCTV*" 3) "CCTV2" 4) "chinanew"
退订模式
PUNSUBSCRIBE [pattern [pattern ...]]
例如
127.0.0.1:6379> PUNSUBSCRIBE CCTV* 1) "punsubscribe" 2) "CCTV*" 3) (integer) 0
python demo
redis_pub.py
from redis import Redis import time conn=Redis(host="60.205.177.100",port="6379") def publish(): while True: conn.publish("CCTV3","test")
redis_sub.py
from redis import Redis import time conn=Redis(host="60.205.177.100",port="6379") def subscribe(): subscribe=conn.pubsub() subscribe.subscribe('CCTV3') message=subscribe.parse_response() print(message)
Redis复制
虽然已经有了aof和rdb做持久化了,但是为了防止单点故障,这就需要复制多个数据副本来保证数据安全
redis复制的基本特征
- Redis使用异步复制,并以每秒一次的频率向主服务器确认复制进度。
- 一个主服务器可以有多个从服务器。从服务器也可以有多个从服务器,从服务器还可以以类似级联的结构连接到其他从服务器。
- Redis复制在主服务器端无阻塞。这意味着当一个或多个副本执行初始同步或部分重新同步时,主服务器将继续处理查询。
- 复制在从服务器基本上也没有阻塞。当副本执行初始同步时,如果配置了replica-serve-stale-data参数为yes,则从服务器可以使用数据集的旧版本处理查询。或者配置replica-serve-stale-data参数为no,在复制连接断开时,向客户端发送一个错误,但是,在初始同步之后,从服务器删除旧版本数据集并载入新版本数据集的那段时间内, 连接请求会被阻塞。
- 复制功能可以单纯地用于数据冗余(data redundancy), 也可以通过让多个从服务器处理只读命令请求来提升扩展性(scalability)
- 可以使用复制来避免让主数据库执行持久化,而是复制到从服务器之后由从服务器进行持久化,但是最好还是开启主服务器的持久化,因为重新启动的主服务器将以空数据集开始,如果从服务器尝试与其同步,导致从服务器的数据也将被清空。
- 缺点也很明显,Redis主从模式仍具有单点风险,一旦主服务器挂掉将无法进行写数据操作
redis复制原理
完全同步
主服务器后台发送RDB文件给从服务器。从服务器接收rdb数据期间,主服务器会将新数据保存到复制客户端缓冲区,当从服务器接收完rdb文件后,将其保存在磁盘上,然后将其加载到内存中。加载完rdb文件后,如果开启aof,从服务器会进行重写操作。主服务器会把缓冲区的新数据发送给从服务器
部分同步
当主从连接中断后,从服务器使用psync命令向主服务器发送上次复制的偏移量,以及记录的masterID,如果上次复制的偏移量仍存在主服务器的缓冲区中,并且masterID与主服务器的masterID一致,将会从缓冲区中上次断开的位置开始增量复制,否则将会发生完全同步
psync命令
psync masterID offset
当从服务器与主服务器建立连接时,会判断是不是初次复制,如果是的话,将会发送psync ? -1进行完全同步,如果不是的话,会发送psync masterID offset尝试部分同步,如果发送的masterID与主服务器一致且offset存在于主服务器的复制缓冲区中,将进行部分同步,否则将会进行完全同步「首先我们查看主服务器的masterID和offset」
「然后将masterID和offset复制到从服务器,期间我在主服务器写了两条数据」
「可以看到从服务器已经有了主服务器的数据」