redis5.0新特性

Stream数据类型

本质是抽象日志,基于时间序列的数据。物联网,各种传感器产生的时间序列数据,定位未来。

redis5.0之stream尝鲜

Timers & Cluster API

RDB现在存储LFU和LRU信息

集群管理器从Ruby(redis-trib.rb)移植到C

搭建集群

集群创建不用先安装ruby。配置3主3从的redis集群:端口范围是5001~5006

redis5001.conf

port 5001
pidfile /usr/local/var/run/redis5001.pid
logfile /tmp/logs/redis/redis-server5001.log
cluster-config-file redis5001.conf

其他配置文件类似。使用6个配置文件启动redis。可以使用redis-cli --cluster help命令查看帮助信息:

# 创建集群,主从比例为1:1
➜  redis-cluster redis-cli --cluster create 127.0.0.1:5001 127.0.0.1:5002 127.0.0.1:5003 127.0.0.1:5004 127.0.0.1:5005 127.0.0.1:5006 --cluster-replicas 1
>>> Performing hash slots allocation on 6 nodes...
Master[0] -> Slots 0 - 5460
Master[1] -> Slots 5461 - 10922
Master[2] -> Slots 10923 - 16383
Adding replica 127.0.0.1:5004 to 127.0.0.1:5001
Adding replica 127.0.0.1:5005 to 127.0.0.1:5002
Adding replica 127.0.0.1:5006 to 127.0.0.1:5003
>>> Trying to optimize slaves allocation for anti-affinity
[WARNING] Some slaves are in the same host as their master
M: a118aa98313f9d7bb3799ad942f3a69489e55e89 127.0.0.1:5001
   slots:[0-5460] (5461 slots) master
M: 6592afc22ce09055b713a049c594445ecdf9246c 127.0.0.1:5002
   slots:[5461-10922] (5462 slots) master
M: 965e863a346c5c0558623313bbc4b9124a7707ee 127.0.0.1:5003
   slots:[10923-16383] (5461 slots) master
S: 2c06122ac54a7cf34027d1248a6b89df4e74ff0d 127.0.0.1:5004
   replicates a118aa98313f9d7bb3799ad942f3a69489e55e89
S: 51c8959f1b428fe9e21b634800bf1da80b2d97e7 127.0.0.1:5005
   replicates 6592afc22ce09055b713a049c594445ecdf9246c
S: f0ba58939c6ae6de4e178e7f7070c78ed19d376d 127.0.0.1:5006
   replicates 965e863a346c5c0558623313bbc4b9124a7707ee
Can I set the above configuration? (type 'yes' to accept): yes
>>> Nodes configuration updated
>>> Assign a different config epoch to each node
>>> Sending CLUSTER MEET messages to join the cluster
Waiting for the cluster to join
....
>>> Performing Cluster Check (using node 127.0.0.1:5001)
M: a118aa98313f9d7bb3799ad942f3a69489e55e89 127.0.0.1:5001
   slots:[0-5460] (5461 slots) master
   1 additional replica(s)
S: 51c8959f1b428fe9e21b634800bf1da80b2d97e7 127.0.0.1:5005
   slots: (0 slots) slave
   replicates 6592afc22ce09055b713a049c594445ecdf9246c
M: 965e863a346c5c0558623313bbc4b9124a7707ee 127.0.0.1:5003
   slots:[10923-16383] (5461 slots) master
   1 additional replica(s)
S: f0ba58939c6ae6de4e178e7f7070c78ed19d376d 127.0.0.1:5006
   slots: (0 slots) slave
   replicates 965e863a346c5c0558623313bbc4b9124a7707ee
S: 2c06122ac54a7cf34027d1248a6b89df4e74ff0d 127.0.0.1:5004
   slots: (0 slots) slave
   replicates a118aa98313f9d7bb3799ad942f3a69489e55e89
M: 6592afc22ce09055b713a049c594445ecdf9246c 127.0.0.1:5002
   slots:[5461-10922] (5462 slots) master
   1 additional replica(s)
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.

从上面的信息可以看出16384个slots几乎平均分配给了3个master节点,3个slave节点是没有slots的,分别和对应的主节点的数据是一致的。

➜  redis-cluster redis-cli -c -p 5001
127.0.0.1:5001> set a 1
-> Redirected to slot [15495] located at 127.0.0.1:5003
OK
127.0.0.1:5003> get a
"1"
127.0.0.1:5003> set b 2
-> Redirected to slot [3300] located at 127.0.0.1:5001
OK
127.0.0.1:5001> get b
"2"
127.0.0.1:5001> keys *
1) "b"
# 当前连接到了5001端口,这个服务器上只存放了b的key
127.0.0.1:5001> CLUSTER INFO
cluster_state:ok
cluster_slots_assigned:16384
cluster_slots_ok:16384
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:6
cluster_size:3
cluster_current_epoch:6
cluster_my_epoch:1
cluster_stats_messages_ping_sent:469
cluster_stats_messages_pong_sent:463
cluster_stats_messages_sent:932
cluster_stats_messages_ping_received:458
cluster_stats_messages_pong_received:469
cluster_stats_messages_meet_received:5
cluster_stats_messages_received:932
127.0.0.1:5001> CLUSTER NODES
51c8959f1b428fe9e21b634800bf1da80b2d97e7 127.0.0.1:5005@15005 slave 6592afc22ce09055b713a049c594445ecdf9246c 0 1550143239486 5 connected
965e863a346c5c0558623313bbc4b9124a7707ee 127.0.0.1:5003@15003 master - 0 1550143238000 3 connected 10923-16383
a118aa98313f9d7bb3799ad942f3a69489e55e89 127.0.0.1:5001@15001 myself,master - 0 1550143237000 1 connected 0-5460
f0ba58939c6ae6de4e178e7f7070c78ed19d376d 127.0.0.1:5006@15006 slave 965e863a346c5c0558623313bbc4b9124a7707ee 0 1550143240496 6 connected
2c06122ac54a7cf34027d1248a6b89df4e74ff0d 127.0.0.1:5004@15004 slave a118aa98313f9d7bb3799ad942f3a69489e55e89 0 1550143240000 4 connected
6592afc22ce09055b713a049c594445ecdf9246c 127.0.0.1:5002@15002 master - 0 1550143240000 2 connected 5461-10922

集群扩容

创建2个配置文件5007和5008并启动服务器。

添加主节点5007

➜  redis-cluster redis-cli --cluster add-node 127.0.0.1:5007 127.0.0.1:5001
>>> Adding node 127.0.0.1:5007 to cluster 127.0.0.1:5001
>>> Performing Cluster Check (using node 127.0.0.1:5001)
M: a118aa98313f9d7bb3799ad942f3a69489e55e89 127.0.0.1:5001
   slots:[0-5460] (5461 slots) master
   1 additional replica(s)
S: 51c8959f1b428fe9e21b634800bf1da80b2d97e7 127.0.0.1:5005
   slots: (0 slots) slave
   replicates 6592afc22ce09055b713a049c594445ecdf9246c
M: 965e863a346c5c0558623313bbc4b9124a7707ee 127.0.0.1:5003
   slots:[10923-16383] (5461 slots) master
   1 additional replica(s)
S: f0ba58939c6ae6de4e178e7f7070c78ed19d376d 127.0.0.1:5006
   slots: (0 slots) slave
   replicates 965e863a346c5c0558623313bbc4b9124a7707ee
S: 2c06122ac54a7cf34027d1248a6b89df4e74ff0d 127.0.0.1:5004
   slots: (0 slots) slave
   replicates a118aa98313f9d7bb3799ad942f3a69489e55e89
M: 6592afc22ce09055b713a049c594445ecdf9246c 127.0.0.1:5002
   slots:[5461-10922] (5462 slots) master
   1 additional replica(s)
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
>>> Send CLUSTER MEET to node 127.0.0.1:5007 to make it join the cluster.
[OK] New node added correctly.
127.0.0.1:5001> CLUSTER NODES
51c8959f1b428fe9e21b634800bf1da80b2d97e7 127.0.0.1:5005@15005 slave 6592afc22ce09055b713a049c594445ecdf9246c 0 1550143794065 5 connected
965e863a346c5c0558623313bbc4b9124a7707ee 127.0.0.1:5003@15003 master - 0 1550143797086 3 connected 10923-16383
a118aa98313f9d7bb3799ad942f3a69489e55e89 127.0.0.1:5001@15001 myself,master - 0 1550143797000 1 connected 0-5460
f0ba58939c6ae6de4e178e7f7070c78ed19d376d 127.0.0.1:5006@15006 slave 965e863a346c5c0558623313bbc4b9124a7707ee 0 1550143795000 6 connected
32a4a4886b43f7005c265b3b5813fea2fe578541 127.0.0.1:5007@15007 master - 0 1550143795071 0 connected
2c06122ac54a7cf34027d1248a6b89df4e74ff0d 127.0.0.1:5004@15004 slave a118aa98313f9d7bb3799ad942f3a69489e55e89 0 1550143796079 4 connected
6592afc22ce09055b713a049c594445ecdf9246c 127.0.0.1:5002@15002 master - 0 1550143798092 2 connected 5461-10922

新添加的5007节点作为了master节点,但是还没有slots,暂时还不能存储数据,需要进行分片的操作将其他master节点的slots分一些给5007节点均衡一下存储。

➜  redis-cluster redis-cli --cluster reshard 127.0.0.1:5007
>>> Performing Cluster Check (using node 127.0.0.1:5007)
M: 32a4a4886b43f7005c265b3b5813fea2fe578541 127.0.0.1:5007
   slots: (0 slots) master
M: a118aa98313f9d7bb3799ad942f3a69489e55e89 127.0.0.1:5001
   slots:[0-5460] (5461 slots) master
   1 additional replica(s)
M: 6592afc22ce09055b713a049c594445ecdf9246c 127.0.0.1:5002
   slots:[5461-10922] (5462 slots) master
   1 additional replica(s)
S: 2c06122ac54a7cf34027d1248a6b89df4e74ff0d 127.0.0.1:5004
   slots: (0 slots) slave
   replicates a118aa98313f9d7bb3799ad942f3a69489e55e89
M: 965e863a346c5c0558623313bbc4b9124a7707ee 127.0.0.1:5003
   slots:[10923-16383] (5461 slots) master
   1 additional replica(s)
S: 51c8959f1b428fe9e21b634800bf1da80b2d97e7 127.0.0.1:5005
   slots: (0 slots) slave
   replicates 6592afc22ce09055b713a049c594445ecdf9246c
S: f0ba58939c6ae6de4e178e7f7070c78ed19d376d 127.0.0.1:5006
   slots: (0 slots) slave
   replicates 965e863a346c5c0558623313bbc4b9124a7707ee
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
How many slots do you want to move (from 1 to 16384)? 500
What is the receiving node ID? 32a4a4886b43f7005c265b3b5813fea2fe578541 # 输入5007的id
Please enter all the source node IDs.
  Type 'all' to use all the nodes as source nodes for the hash slots.
  Type 'done' once you entered all the source nodes IDs.
Source node #1: all

Ready to move 500 slots.
  Source nodes:
    M: a118aa98313f9d7bb3799ad942f3a69489e55e89 127.0.0.1:5001
       slots:[0-5460] (5461 slots) master
       1 additional replica(s)
    M: 6592afc22ce09055b713a049c594445ecdf9246c 127.0.0.1:5002
       slots:[5461-10922] (5462 slots) master
       1 additional replica(s)
    M: 965e863a346c5c0558623313bbc4b9124a7707ee 127.0.0.1:5003
       slots:[10923-16383] (5461 slots) master
       1 additional replica(s)
  Destination node:
    M: 32a4a4886b43f7005c265b3b5813fea2fe578541 127.0.0.1:5007
       slots: (0 slots) master
  Resharding plan:
    Moving slot 5461 from 6592afc22ce09055b713a049c594445ecdf9246c
    Moving slot 11088 from 965e863a346c5c0558623313bbc4b9124a7707ee
Do you want to proceed with the proposed reshard plan (yes/no)? yes

添加从节点5008

redis-cluster redis-cli --cluster add-node 127.0.0.1:5008 127.0.0.1:5001

上面的操作将5008添加为了master节点(可以使用客户端连上使用CLUSTER NODES查看),需要设置其变为从节点:

➜  redis-cluster redis-cli -c -p 5008
127.0.0.1:5008> CLUSTER NODES
965e863a346c5c0558623313bbc4b9124a7707ee 127.0.0.1:5003@15003 master - 0 1550144560000 3 connected 11089-16383
32a4a4886b43f7005c265b3b5813fea2fe578541 127.0.0.1:5007@15007 master - 0 1550144562933 7 connected 0-165 5461-5627 10923-11088
f0ba58939c6ae6de4e178e7f7070c78ed19d376d 127.0.0.1:5006@15006 slave 965e863a346c5c0558623313bbc4b9124a7707ee 0 1550144560914 3 connected
a118aa98313f9d7bb3799ad942f3a69489e55e89 127.0.0.1:5001@15001 master - 0 1550144561920 1 connected 166-5460
aafdcb6b443c9449ca0131562e1ee516f2f6a6e4 127.0.0.1:5008@15008 myself,master - 0 1550144559000 0 connected
51c8959f1b428fe9e21b634800bf1da80b2d97e7 127.0.0.1:5005@15005 slave 6592afc22ce09055b713a049c594445ecdf9246c 0 1550144561000 2 connected
6592afc22ce09055b713a049c594445ecdf9246c 127.0.0.1:5002@15002 master - 0 1550144560000 2 connected 5628-10922
2c06122ac54a7cf34027d1248a6b89df4e74ff0d 127.0.0.1:5004@15004 slave a118aa98313f9d7bb3799ad942f3a69489e55e89 0 1550144559903 1 connected
127.0.0.1:5008> CLUSTER REPLICATE 32a4a4886b43f7005c265b3b5813fea2fe578541
OK
127.0.0.1:5008> CLUSTER NODES
965e863a346c5c0558623313bbc4b9124a7707ee 127.0.0.1:5003@15003 master - 0 1550144580000 3 connected 11089-16383
32a4a4886b43f7005c265b3b5813fea2fe578541 127.0.0.1:5007@15007 master - 0 1550144579000 7 connected 0-165 5461-5627 10923-11088
f0ba58939c6ae6de4e178e7f7070c78ed19d376d 127.0.0.1:5006@15006 slave 965e863a346c5c0558623313bbc4b9124a7707ee 0 1550144579000 3 connected
a118aa98313f9d7bb3799ad942f3a69489e55e89 127.0.0.1:5001@15001 master - 0 1550144582129 1 connected 166-5460
aafdcb6b443c9449ca0131562e1ee516f2f6a6e4 127.0.0.1:5008@15008 myself,slave 32a4a4886b43f7005c265b3b5813fea2fe578541 0 1550144578000 0 connected
51c8959f1b428fe9e21b634800bf1da80b2d97e7 127.0.0.1:5005@15005 slave 6592afc22ce09055b713a049c594445ecdf9246c 0 1550144581114 2 connected
6592afc22ce09055b713a049c594445ecdf9246c 127.0.0.1:5002@15002 master - 0 1550144580104 2 connected 5628-10922
2c06122ac54a7cf34027d1248a6b89df4e74ff0d 127.0.0.1:5004@15004 slave a118aa98313f9d7bb3799ad942f3a69489e55e89 0 1550144581000 1 connected
127.0.0.1:5008>

缩容

删除从节点

redis-cli --cluster del-node 127.0.0.1:5008 aafdcb6b443c9449ca0131562e1ee516f2f6a6e4

删除主节点

删除主节点之前要先进行分片,将slots中的数据分配给集群中的其他master。

➜  redis-cluster redis-cli --cluster reshard 127.0.0.1:5007
>>> Performing Cluster Check (using node 127.0.0.1:5007)
M: 32a4a4886b43f7005c265b3b5813fea2fe578541 127.0.0.1:5007
   slots:[0-165],[5461-5627],[10923-11088] (499 slots) master
M: a118aa98313f9d7bb3799ad942f3a69489e55e89 127.0.0.1:5001
   slots:[166-5460] (5295 slots) master
   1 additional replica(s)
M: 6592afc22ce09055b713a049c594445ecdf9246c 127.0.0.1:5002
   slots:[5628-10922] (5295 slots) master
   1 additional replica(s)
S: 2c06122ac54a7cf34027d1248a6b89df4e74ff0d 127.0.0.1:5004
   slots: (0 slots) slave
   replicates a118aa98313f9d7bb3799ad942f3a69489e55e89
M: 965e863a346c5c0558623313bbc4b9124a7707ee 127.0.0.1:5003
   slots:[11089-16383] (5295 slots) master
   1 additional replica(s)
S: 51c8959f1b428fe9e21b634800bf1da80b2d97e7 127.0.0.1:5005
   slots: (0 slots) slave
   replicates 6592afc22ce09055b713a049c594445ecdf9246c
S: f0ba58939c6ae6de4e178e7f7070c78ed19d376d 127.0.0.1:5006
   slots: (0 slots) slave
   replicates 965e863a346c5c0558623313bbc4b9124a7707ee
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
How many slots do you want to move (from 1 to 16384)? 500 # 因为扩容的时候分配了500个
What is the receiving node ID? a118aa98313f9d7bb3799ad942f3a69489e55e89 # 随机挑选一个master节点,此处为node1
Please enter all the source node IDs.
  Type 'all' to use all the nodes as source nodes for the hash slots.
  Type 'done' once you entered all the source nodes IDs.
Source node #1: 32a4a4886b43f7005c265b3b5813fea2fe578541 # 5007的id
Source node #2: done

Ready to move 500 slots.
  Source nodes:
    M: 32a4a4886b43f7005c265b3b5813fea2fe578541 127.0.0.1:5007
       slots:[0-165],[5461-5627],[10923-11088] (499 slots) master
  Destination node:
    M: a118aa98313f9d7bb3799ad942f3a69489e55e89 127.0.0.1:5001
       slots:[166-5460] (5295 slots) master
       1 additional replica(s)
  Resharding plan:
    Moving slot 0 from 32a4a4886b43f7005c265b3b5813fea2fe578541
    Moving slot 1 from 32a4a4886b43f7005c265b3b5813fea2fe578541
    Moving slot 11088 from 32a4a4886b43f7005c265b3b5813fea2fe578541
Do you want to proceed with the proposed reshard plan (yes/no)? yes
➜  redis-cluster redis-cli -c -p 5001
127.0.0.1:5001> CLUSTER NODES
51c8959f1b428fe9e21b634800bf1da80b2d97e7 127.0.0.1:5005@15005 slave 6592afc22ce09055b713a049c594445ecdf9246c 0 1550145060000 5 connected
965e863a346c5c0558623313bbc4b9124a7707ee 127.0.0.1:5003@15003 master - 0 1550145058000 3 connected 11089-16383
a118aa98313f9d7bb3799ad942f3a69489e55e89 127.0.0.1:5001@15001 myself,master - 0 1550145061000 8 connected 0-5627 10923-11088
f0ba58939c6ae6de4e178e7f7070c78ed19d376d 127.0.0.1:5006@15006 slave 965e863a346c5c0558623313bbc4b9124a7707ee 0 1550145061000 6 connected
32a4a4886b43f7005c265b3b5813fea2fe578541 127.0.0.1:5007@15007 master - 0 1550145061643 7 connected
2c06122ac54a7cf34027d1248a6b89df4e74ff0d 127.0.0.1:5004@15004 slave a118aa98313f9d7bb3799ad942f3a69489e55e89 0 1550145062000 8 connected
6592afc22ce09055b713a049c594445ecdf9246c 127.0.0.1:5002@15002 master - 0 1550145062649 2 connected 5628-10922

5007的slots已经分配给了别人!最后从集群中移除5007

redis-cli --cluster del-node 127.0.0.1:5007 32a4a4886b43f7005c265b3b5813fea2fe578541

新的sorted set命令zpopmin/max和阻塞变种

取出集合中分值最大和最小的元素。

主动碎片整理V2 & 更好的内存统计报告

当删除一个key的时候,redis并不会立即回收内存空间。如果反复进行增删键值,内存中将产生大量碎片,这就会影响到之后申请大块连续的内存,所以有必要对内存碎片进行整理。

# 快速产生测试数据
127.0.0.1:6379> DEBUG populate  300000 abc 1000
127.0.0.1:6379> info memory
# Memory
used_memory:326834704
used_memory_human:311.69M
used_memory_rss:317788160
used_memory_rss_human:303.07M
used_memory_peak:331743840
used_memory_peak_human:316.38M
used_memory_peak_perc:98.52%
used_memory_overhead:17232110
used_memory_startup:988032
used_memory_dataset:309602594
used_memory_dataset_perc:95.01%
allocator_allocated:326801360
allocator_active:317750272
allocator_resident:317750272
total_system_memory:8589934592
total_system_memory_human:8.00G
used_memory_lua:37888
used_memory_lua_human:37.00K
used_memory_scripts:0
used_memory_scripts_human:0B
number_of_cached_scripts:0
maxmemory:0
maxmemory_human:0B
maxmemory_policy:noeviction
allocator_frag_ratio:0.97
allocator_frag_bytes:18446744073700500528
allocator_rss_ratio:1.00
allocator_rss_bytes:0
rss_overhead_ratio:1.00
rss_overhead_bytes:37888
mem_fragmentation_ratio:0.97
mem_fragmentation_bytes:-9013200
mem_not_counted_for_evict:0
mem_replication_backlog:0
mem_clients_slaves:0
mem_clients_normal:49694
mem_aof_buffer:0
mem_allocator:libc
active_defrag_running:0
lazyfree_pending_objects:0

上述used_memory_human:311.69M,而used_memory_rss_human:303.07M,rss比used还小,这种情况比较糟糕,redis服务器的内存可能被OS换到了交换空间中了,相反如果used小很多的话可能有大量内存碎片;还可以查看mem_fragmentation_ratio,这个参数大于1是比较理想的情况,如果这个只为几十或者几百也表示有大量内存碎片。

127.0.0.1:6379> MEMORY STATS
 1) "peak.allocated" # 最大内存
 2) (integer) 331743840 
 3) "total.allocated" # 当前已使用的内存
 4) (integer) 326834688
 5) "startup.allocated" # 服务启动初始化之后的内存
 6) (integer) 988032
 7) "replication.backlog" # 主从复制中backlog占用
 8) (integer) 0
 9) "clients.slaves"
10) (integer) 0
11) "clients.normal"
12) (integer) 49694
13) "aof.buffer"
14) (integer) 0
15) "lua.caches"
16) (integer) 0
17) "db.0"
18) 1) "overhead.hashtable.main"
    2) (integer) 16194384
    3) "overhead.hashtable.expires"
    4) (integer) 0
19) "overhead.total"
20) (integer) 17232110
21) "keys.count"
22) (integer) 300002
23) "keys.bytes-per-key"
24) (integer) 1086
25) "dataset.bytes"
26) (integer) 309602578
27) "dataset.percentage"
28) "95.014816284179688"
29) "peak.percentage"
30) "98.520195007324219"
31) "allocator.allocated"
32) (integer) 326801360
33) "allocator.active"
34) (integer) 312306688
35) "allocator.resident"
36) (integer) 312306688
37) "allocator-fragmentation.ratio"
38) "0.95564687252044678"
39) "allocator-fragmentation.bytes"
40) (integer) -14494672
41) "allocator-rss.ratio"
42) "1"
43) "allocator-rss.bytes"
44) (integer) 0
45) "rss-overhead.ratio"
46) "1.0001213550567627"
47) "rss-overhead.bytes"
48) (integer) 37888
49) "fragmentation"
50) "0.95576280355453491"
51) "fragmentation.bytes"
52) (integer) -14456784
# 查看单个键占用的内存
127.0.0.1:6379> MEMORY USAGE abc:2
(integer) 1053

许多带有子命令的命令现在都有一个HELP子命令

127.0.0.1:6379> XINFO help
1) XINFO <subcommand> arg arg ... arg. Subcommands are:
2) CONSUMERS <key> <groupname>  -- Show consumer groups of group <groupname>.
3) GROUPS <key>                 -- Show the stream consumer groups.
4) STREAM <key>                 -- Show information about the stream.
5) HELP                         -- Print this help.
127.0.0.1:6379> XINFO CONSUMERS codehole cg1
1) 1) "name"
   2) "c1"
   3) "pending"
   4) (integer) 2
   5) "idle"
   6) (integer) 675253
127.0.0.1:6379> XINFO STREAM codehole
 1) "length"
 2) (integer) 3
 3) "radix-tree-keys"
 4) (integer) 1
 5) "radix-tree-nodes"
 6) (integer) 2
 7) "groups"
 8) (integer) 2
 9) "last-generated-id"
10) "1550066609501-0"
11) "first-entry"
12) 1) "1550066141188-0"
    2) 1) "name"
       2) "youming"
       3) "age"
       4) "60"
13) "last-entry"
14) 1) "1550066609501-0"
    2) 1) "name"
       2) "xiaorui"
       3) "age"
       4) "1"
127.0.0.1:6379>

pubsub,xgroup也有子命令。

分布式锁

分布式系统中经常会碰到2个问题:互斥性和幂等性。互斥性问题的解决方案是分布式锁;幂等性问题,分布式环境中,有些接口是天然保证幂等性的,如查询操作。其他情况下,所有涉及对数据的修改、状态的变更就都有必要防止重复性操作的发生。通过间接的实现接口的幂等性来防止重复操作所带来的影响,成为了一种有效的解决方案。这个间接方案也可以使用分布式锁来实现。

分布式锁的条件

  • 需要有存储锁的空间,并且锁的空间是可以访问到的。
  • 锁需要被唯一标识。
  • 锁要有至少两种状态。

redis中实现分布式锁的解决方案: SETNX lockid,如果锁不存在则set并获得锁,否则跳过此次操作或者等待下一次操作,SETNX是一个原子操作,可以保证一个节点只会拿到一个锁。

首先看看不加锁的版本:

const key = 'money';

const Redis = require('ioredis');
const redis = new Redis();

class Lock {
    constructor(redis, lockKey, lockValue, lockTTL) {
        this.redis = redis;
        this.lockKey = lockKey;
        this.lockValue = lockValue;
        this.lockTTL = lockTTL;
    }

    async lock() {
        const res = await this.redis.set(this.lockKey, this.lockValue, 'NX', 'EX', this.lockTTL);
        return res === 'OK';
    }

    async unlock() {
        // 使用redis lua脚本的方式避免窗口时间
        const res = await this.redis.get(this.lockKey);
        if (res !== this.lockValue) return false;
        const delCount = await this.redis.del(this.lockKey);
        return delCount > 0;
    }
}

const lock = new Lock(redis,'money_lock','money_lock_value',100);

async function doTask(seq){
    const money = await redis.get(key);
    const value = money - 10;
    await redis.set(key, value);
    console.log(seq, money, value);
}

async function task1(seq) {

    const locked = await lock.lock();

    if (locked) {
        await doTask(seq);
        await lock.unlock();
    } else {
        task1(seq);
    }
}

async function task2(seq) {
    await doTask(seq);
}

for (let i = 0; i < 10; i++) {
    // process.nextTick(task2.bind(this, i)); // 不加锁版本
    process.nextTick(task1.bind(this, i)); // 加锁版本
}

nodejs中由于没有多线程的概念,用异步模拟了多线程(其实用child_process模拟多进程也可以)

# 加锁版本
0 '100' 90
1 '90' 80
2 '80' 70
3 '70' 60
4 '60' 50
5 '50' 40
6 '40' 30
9 '30' 20
8 '20' 10
7 '10' 0
# 不加锁版本
0 '100' 90
1 '100' 90
2 '100' 90
3 '100' 90
4 '100' 90
5 '100' 90
6 '100' 90
7 '100' 90
8 '100' 90
9 '100' 90

为什么要用redis实现分布式锁:上述的doTask不是一个原子操作,多个线程(进程)get到了同一个值,然后将其减少设置到redis,上述锁的释放也不是一个原子操作,存在一个时间窗口,可以使用lua脚本重写unlock

async unlock(key: string, value: string): Promise<boolean> {
   const luaScript = `
   if redis.call('get',KEYS[1]) == ARGV[1] then
      return redis.call('del',KEYS[1])
   else
      return 0
   end`;
   const delCount = await this.redis.eval(luaScript, 1, key, value);
   return delCount === 1;
}

缓存中常见的几个问题

并发竞争

这个也是线上非常常见的一个问题,就是多客户端同时并发写一个 key,可能本来应该先到的数据后到了,导致数据版本错了;或者是多客户端同时获取一个 key,修改值之后再写回去,只要顺序错了,数据就错了。redis 自己就有天然解决这个问题的 CAS 类的乐观锁方案。

某个时刻,多个系统实例都去更新某个 key。可以基于 zookeeper 实现分布式锁。每个系统通过 zookeeper 获取分布式锁,确保同一时间,只能有一个系统实例在操作某个 key,别人都不允许读和写。你要写入缓存的数据,都是从 mysql 里查出来的,都得写入 mysql 中,写入 mysql 中的时候必须保存一个时间戳,从 mysql 查出来的时候,时间戳也查出来。每次要写之前,先判断一下当前这个 value 的时间戳是否比缓存里的 value 的时间戳要新。如果是的话,那么可以写,否则,就不能用旧的数据覆盖新的数据。

redis的过期策略是怎样的

redis 过期策略是:定期删除+惰性删除。

所谓定期删除,指的是 redis 默认是每隔 100ms 就随机抽取一些设置了过期时间的 key,检查其是否过期,如果过期就删除。

假设 redis 里放了 10w 个 key,都设置了过期时间,你每隔几百毫秒,就检查 10w 个 key,那 redis 基本上就死了,cpu 负载会很高的,消耗在你的检查过期 key 上了。注意,这里可不是每隔 100ms 就遍历所有的设置过期时间的 key,那样就是一场性能上的灾难。实际上 redis 是每隔 100ms 随机抽取一些 key 来检查和删除的。

但是问题是,定期删除可能会导致很多过期 key 到了时间并没有被删除掉,那咋整呢?所以就是惰性删除了。这就是说,在你获取某个 key 的时候,redis 会检查一下 ,这个 key 如果设置了过期时间那么是否过期了?如果过期了此时就会删除,不会给你返回任何东西。

获取 key 的时候,如果此时 key 已经过期,就删除,不会返回任何东西。

但是实际上这还是有问题的,如果定期删除漏掉了很多过期 key,然后你也没及时去查,也就没走惰性删除,此时会怎么样?如果大量过期 key 堆积在内存里,导致 redis 内存块耗尽了,咋整?

答案是:走内存淘汰机制(比如最常用的是allkeys-lru)。

如何实现redis的高可用和高并发

redis 实现高并发主要依靠主从架构,一主多从,一般来说,很多项目其实就足够了,单主用来写入数据,单机几万 QPS,多从用来查询数据,多个从实例可以提供每秒 10w 的 QPS。

如果想要在实现高并发的同时,容纳大量的数据,那么就需要 redis 集群,使用 redis 集群之后,可以提供每秒几十万的读写并发。

redis 高可用,如果是做主从架构部署,那么加上哨兵就可以了,就可以实现,任何一个实例宕机,可以进行主备切换。

redis主从架构实现高并发

单机的 redis,能够承载的 QPS 大概就在上万到几万不等。对于缓存来说,一般都是用来支撑读高并发的。因此架构做成主从(master-slave)架构,一主多从,主负责写,并且将数据复制到其它的 slave 节点,从节点负责读。所有的读请求全部走从节点。这样也可以很轻松实现水平扩容,支撑读高并发。

redis 采用异步方式复制数据到 slave 节点.注意,如果采用了主从架构,那么建议必须开启 master node 的持久化,不建议用 slave node 作为 master node 的数据热备,因为那样的话,如果你关掉 master 的持久化,可能在 master 宕机重启的时候数据是空的,然后可能一经过复制, slave node 的数据也丢了。另外,master 的各种备份方案,也需要做。万一本地的所有文件丢失了,从备份中挑选一份 rdb 去恢复 master,这样才能确保启动的时候,是有数据的,即使采用了后续讲解的高可用机制,slave node 可以自动接管 master node,但也可能 sentinel 还没检测到 master failure,master node 就自动重启了,还是可能导致上面所有的 slave node 数据被清空。

redis的主从复制支持断点续传、无磁盘化复制(master在内存中创建RDB发送给slave),对于过期key的处理是master过期了key或者LRU淘汰了一个key,那么会模拟一条del命令发送给salve。

基于哨兵集群的高可用

sentinel,中文名是哨兵。哨兵是 redis 集群机构中非常重要的一个组件,主要有以下功能:

  • 集群监控:负责监控 redis master 和 slave 进程是否正常工作。
  • 消息通知:如果某个 redis 实例有故障,那么哨兵负责发送消息作为报警通知给管理员。
  • 故障转移:如果 master node 挂掉了,会自动转移到 slave node 上。
  • 配置中心:如果故障转移发生了,通知 client 客户端新的 master 地址。

哨兵用于实现 redis 集群的高可用,本身也是分布式的,作为一个哨兵集群去运行,互相协同工作。

  • 故障转移时,判断一个 master node 是否宕机了,需要大部分的哨兵都同意才行,涉及到了分布式选举的问题。
  • 即使部分哨兵节点挂掉了,哨兵集群还是能正常工作的,因为如果一个作为高可用机制重要组成部分的故障转移系统本身是单点的,那就很坑爹了。

哨兵核心知识

  • 哨兵至少需要 3 个实例,来保证自己的健壮性。
  • 哨兵 + redis 主从的部署架构,是不保证数据零丢失的,只能保证 redis 集群的高可用性。
  • 对于哨兵 + redis 主从这种复杂的部署架构,尽量在测试环境和生产环境,都进行充足的测试和演练。

持久化的2种方式如何选择

AOF 日志文件的命令通过非常可读的方式进行记录,这个特性非常适合做灾难性的误删除的紧急恢复。比如某人不小心用 flushall 命令清空了所有数据,只要这个时候后台 rewrite 还没有发生,那么就可以立即拷贝 AOF 文件,将最后一条 flushall 命令给删了,然后再将该 AOF 文件放回去,就可以通过恢复机制,自动恢复所有数据。redis为了避免AOF文件过大的问题,会对AOF文件进行rewrite,rewrite的时候是直接基于当前内存中的数据进行指令的重新构建,而不是基于旧的指令日志进行merge,这样鲁棒性会好很多。

redis需要同时开启AOF和RDB。用AOF来保证数据不丢失,作为数据恢复的第一选择;而使用RDB来做不同程度的冷备,在AOF问你见你都丢失或者损坏不可用的时候还可以使用RDB来进行快速的数据恢复。

参考资料: