单线程网络IO、KV读写
Redis的网络IO和KeyValue读写是由一个线程来完成的。
而Redis的持久化、异步删除、集群数据同步是额外的线程执行。
也由于Redis是单线程的,所以要特别小心耗时的操作,这些操作会阻塞后续指令。
简单来说就是处理事务一套、前台接待一套。不会因为前面办事导致人均等待时间太久。
Redis使用IO多路复用(epoll),将连接信息、事件放到队列中,使其能够处理并发的客户端连接。
socket: { s0 s1 s2 s3 "..." }
IO多路复用: { s3 -> s2 -> s1 -> s0 }
事件处理器: { 连接处理器 命令请求处理器 命令回复处理器 }
socket -> IO多路复用 -> 文件事件分派器 -> 事件处理器
|
详解GET key
Redis相当于HashMap,也由于Hash是无序的,因此scan
这样的流式查询,在查改场景中,可能会漏扫中途插入到前面下标的元素。
Redis持久化
RDB Snapshot
默认情况下,Redis将内存数据快照保存为dump.rdb
,可以使用
save <time_duration> <row_insertion>
|
指示Redis多少秒内插入多少条数据后持久化到数据库
也可以直接用save
和bgsave
命令写入数据库
bgsave 异步持久化
bgsave使用写时复制COW。bgsave从主线程fork出来,当主线程修改数据时,bgsave线程会将写入数据拷贝一份,然后写入rdb
Append-Only File
快照不能做到完全持久,假如服务宕机,可能会丢失几条写入。
这时候我们直接做个命令日志AOF,将执行的修改指令写入appendonly.aof
中
appendonly yes appendfilename "appendonly.aof"
|
aof有三种模式appendfsync
:
- always:立刻写入磁盘
- everysec:每秒写一次
- no:交给OS调度
但是,由于aof是记录命令,需要执行时间,对于持久化大量数据比较耗时间。
对于连续操作(如自增)aof会优化为1条命令,可以用bgrewriteaof
命令手动重写
# 最小重构大小 auto-aof-rewrite-min-size 64mb # 增长了100%,即128mb就重构 auto-aof-rewrite-percentage 100
|
Redis4 混合持久化
由于Redis重启时优先使用aof恢复数据,rdb利用率不高。因此出现了混合持久化
# 必须同时开启aof aof-use-rdb-preamle yes # 可以直接把快照关掉,因为混合持久化都写在aof里面
|
开启后,当aof重写时,会直接写入rdb,将rdb快照和aof增量存储在一起。
于是Redis重启可以先读rdb,再执行增量aof恢复数据,提高效率。
Redis主从
# redis-<your_port>.conf pidfile /var/run/redis_<your_port>.pid logfile "<your_port>.log" # 数据存放目录 dir /usr/local/redis/data/<your_port>
### 主从复制 replicaof <main_redis_ip> <port> # 从节点,只读 replica-read-only yes
### 启动 # 启动从节点 redis-server redis-<your_port>.conf # 连接到从节点 redis-cli -p <minor_redis_port>
|
主从原理
master: { rdb data repl buffer } slave
slave -> master: 1. psync全量复制同步数据(通过socket长连接) master.rdb data -> master.rdb data: 2.1 收到psync命令,执行bgsave生成最新rdb快照 master.repl buffer -> master.repl buffer: 2.2 主节点将增量写语句更新到buffer master.rdb data -> slave: 3. 发送rdb数据 slave -> slave: 4. 清空旧数据,加载主节点rdb master.repl buffer -> slave: 5. 发送缓冲区写命令 slave -> slave: 6. 执行主节点buffer写命令 master -> slave: 7. 主节点通过socket长连接,持续发送写命令给从节点,保持数据一致
|
断点续传
master: { repl backlog buffer } slave
slave -> master: 1. 连接断开 master.repl backlog buffer -> master.repl backlog buffer: 2. 主节点增量写命令写入buffer slave -> master: 3. 恢复socket长连接 slave -> master: 4. psync(offset)带偏移量 master -> slave: 5. 若offset在buffer中,断点以后的数据发送给从节点;否则,全量发送 master -> slave: 6. 持续发送buffer写命令,保持数据一致
|
如果存在很多从节点,那么主节点传输压力会比较大。可以采用树型架构,让从节点再给它的子节点传输数据。
哨兵高可用
sentinel_cluster: { sentinel1 <-> sentinel2 <-> sentinel3 <-> sentinel1 }
client -> master <-> sentinel_cluster master -> slave1 master -> slave2 client -> sentinel_cluster sentinel_cluster <-> slave1 sentinel_cluster <-> slave2
|
哨兵会动态监听redis主节点,如果主节点挂了,哨兵会选择一个新redis示例作为主节点(通知给client端)
开启哨兵
# sentinel.conf
port 26379 pidfile <your_file> logfile <your_file> dir "<your_dir>"
# quorm是指多少个sentinel同时认为主节点挂了,才让master失效,一般设置为一半以上 sentinel monitor mymaster <redis_ip> <redis_port> <quorm>
|
启动哨兵./redis-sentinel sentinel.conf
Redis Cluster
当哨兵集群选举新节点的时候,服务会宕机几秒钟。因此我们需要Cluster
client1 -> RedisCluster client2 -> RedisCluster RedisCluster: Hash slot: CRC16(key) % 16384 RedisCluster -> Redis集群 Redis集群: { master1 -> slave1-1 master1 -> slave1-2 master2 -> slave2-1 master2 -> slave2-2 master3 -> slave3-1 master3 -> slave3-2 }
|
在Cluster中,每个master数据是不重叠的,数据会被分片储存。通过Hash算法来决定存储数据到哪一个master节点。
使用Cluster,可以避免Redis服务完全宕机。
2的幂次取模小技巧:
Xmod2n=X & (2n−1)
Redis集群搭建
redis-cluster/ |-- 8000 | `-- redis.conf |-- 8010 `-- 8020
|
- Redis配置
# ...其他配置
daemonize yes port 8000 dir /path/to/redis-cluster/8000/ # 启用集群 cluster-enabled yes cluster-config-file nodes-8000.conf cluster-node-timeout 5000 # 密码 requirepass <your_password> masterauth <your_auth_password>
|
- 启动所有master和slave节点
redis-server /path/to/redis-cluster/80*/redis.conf ps aux | grep redis
|
- 开启集群
# replicas表示节点的副本,配置为1,则1主1从 redis-cli -a <your_auth_password> --cluster create --cluster-replicas 1 \ localhost:8000 localhost:8001 localhost:8002 ...
|
注意,第二次启动集群后,就不需要这一步了。节点会自动读取nodes-8000.conf
文件,恢复上次集群状态。
- 进入redis节点验证配置
cluster info cluster nodes
|
Redission原理
Thread1: { Redission } Thread2: { Redission }
Thread1.Redission -> Try Lock Try Lock -> 守护线程: 加锁成功 守护线程 -> Redis(Master): lock,每隔10s检查线程是否仍持有锁。如果持有,则延长锁失效时间
Thread2.Redission -> Try Lock Try Lock -> Thread2.Redission: 加锁失败,使用while自旋尝试加锁
|
Redission利用了Redis Lua脚本保证原子操作。