1.基本概念
-
实现方案
-
用 Redis 保存 5000 万个键值对,每个键值对大约是 512B
-
键值对所占的内存空间大约是 25GB(5000 万 *512B\
-
选择一台 32GB 内存的云主机来部署 Redis\
-
采用 RDB 对数据做持久化,以确保 Redis 实例故障后,还能从 RDB 恢复数据\
-
-
问题:上述栗子Redis 的响应有时会非常慢
-
排查:使用 INFO 命令查看 Redis 的 latest_fork_usec 指标值(表示最近一次 fork 的耗时),快到秒级别了
-
分析:在使用 RDB 进行持久化时,Redis 会 fork 子进程来完成 ,fork 操作的用时和 Redis 的数据量是正相关的(25GB) ,而 fork 在执行时会阻塞主线程
-
解决:将数据分片,利用横向扩展降低单机fork压力
//info # Server 基础信息 redis_version:3.2.0 redis_git_sha1:00000000 redis_git_dirty:0 redis_build_id:8c57f577cb1c6bc6 redis_mode:standalone os:Linux 3.10.0-514.16.1.el7.x86_64 x86_64 arch_bits:64 multiplexing_api:epoll gcc_version:4.8.5 process_id:455 run_id:69669d1db7966e2dab3457b81d97cf83962dca69 //redis实例id tcp_port:6379 uptime_in_seconds:4309158 uptime_in_days:49 hz:10 lru_clock:8444669 executable:/home/xiaoju/redis/./bin/redis-server config_file:/home/xiaoju/redis/redis.conf # Clients 客户端信息 connected_clients:220 client_longest_output_list:0 client_biggest_input_buf:0 blocked_clients:0 # Memory 内存信息 used_memory:7217712 used_memory_human:6.88M //内存大小 used_memory_rss:13815808 used_memory_rss_human:13.18M used_memory_peak:15046936 used_memory_peak_human:14.35M total_system_memory:270227243008 total_system_memory_human:251.67G used_memory_lua:37888 used_memory_lua_human:37.00K maxmemory:0 maxmemory_human:0B maxmemory_policy:noeviction mem_fragmentation_ratio:1.91 mem_allocator:jemalloc-4.0.3 # Persistence 持久化信息 loading:0 rdb_changes_since_last_save:0 rdb_bgsave_in_progress:0 rdb_last_save_time:1635834385 rdb_last_bgsave_status:ok rdb_last_bgsave_time_sec:0 rdb_current_bgsave_time_sec:-1 aof_enabled:0 aof_rewrite_in_progress:0 aof_rewrite_scheduled:0 aof_last_rewrite_time_sec:-1 aof_current_rewrite_time_sec:-1 aof_last_bgrewrite_status:ok aof_last_write_status:ok # Stats 状态信息 total_connections_received:451 total_commands_processed:10095 instantaneous_ops_per_sec:0 total_net_input_bytes:828525 total_net_output_bytes:248488 instantaneous_input_kbps:0.00 instantaneous_output_kbps:0.00 rejected_connections:0 sync_full:0 sync_partial_ok:0 sync_partial_err:0 expired_keys:2396 evicted_keys:0 keyspace_hits:2395 keyspace_misses:138 pubsub_channels:0 pubsub_patterns:0 latest_fork_usec:527 //fork执行时间 migrate_cached_sockets:0 # Replication 主从节点信息 role:master connected_slaves:0 master_repl_offset:0 repl_backlog_active:0 repl_backlog_size:1048576 repl_backlog_first_byte_offset:0 repl_backlog_histlen:0 # CPU cpu信息 used_cpu_sys:1239.72 used_cpu_user:1185.27 used_cpu_sys_children:20.26 used_cpu_user_children:73.27 # Cluster 集群信息 cluster_enabled:0 # Keyspace db0:keys=3733,expires=0,avg_ttl=0
2.如何保存更多数据?
-
如何保存更多数据
-
纵向扩展
- 大内存云主机
- 优点:实施起来简单直接
- 缺点:随着数据量增加,fork时间增加+纵向扩展会受到硬件和成本的限制+扩展不是呈现线性增长
-
横向扩展
- 切片集群,也叫分片集群
- 启动多个Redis实例组成一个集群,按照一定的规则把收到数据划分成多份,每份用一个实例保存
- 优点:仍可以保存多份数据+fork时间缩短+可扩展性更好
- 缺点:引入复杂度(分布式管理等等)
-
分布式需要解决的两大问题
- 数据切片后,在多个实例之间如何分布?
- 客户端怎么确定想要访问的数据在哪个实例上?\
\
3.数据切片和实例的对应分布关系
-
切片集群是一种保存大量数据的通用机制,这个机制可以有不同的实现方案\
-
Redis 3.0 之前,官方并没有针对切片集群提供具体的方案(codis等替代方案)\
-
从 3.0 开始,官方提供了一个名为 Redis Cluster 的方案,用于实现切片集群\
-
-
实现
-
Redis Cluster 方案采用哈希槽(Hash Slot,接下来我会直接称之为 Slot),来处理数据和实例之间的映射关系\
-
在 Redis Cluster 方案中,一个切片集群共有 16384 个哈希槽,这些哈希槽类似于数据分区,每个键值对都会根据它的 key,被映射到一个哈希槽中\
-
一个redis实例管理一部分slot
-
-
查找过程
-
根据键值对的 key,按照CRC16 算法计算一个 16 bit 的值\
-
用这个 16bit 值对 16384 取模,得到 0~16383 范围内的模数,每个模数代表一个相应编号的哈希槽\
-
补充:集群的创建过程
-
自动版
-
部署 Redis Cluster 方案时,可以使用 cluster create 命令创建集群
-
Redis 会自动把这些槽平均分布在集群实例上\
-
每个实例上的槽个数为 16384/N 个\
-
-
手动版
-
可以使用 cluster meet 命令手动建立实例间的连接,形成集群,再使用 cluster addslots 命令,指定每个实例上的哈希槽个数\
-
使用 cluster addslots 命令手动分配哈希槽\
-
在手动分配哈希槽时,需要把 16384 个槽都分配完,否则 Redis 集群无法正常工作
-
-
-
\
redis-cli -h 172.16.19.3 –p 6379 cluster addslots 0,1 redis-cli -h 172.16.19.4 –p 6379 cluster addslots 2,3 redis-cli -h 172.16.19.5 –p 6379 cluster addslots 4
4.客户端如何定位数据?
\
-
要进一步定位到实例,还需要知道哈希槽分布在哪个实例上\
-
客户端和集群实例建立连接后,实例就会把哈希槽的分配信息发给客户端\
-
但是每个实例只知道自己被分配了哪些哈希槽,是不知道其他实例拥有的哈希槽信息的
-
Redis 实例会把自己的哈希槽信息发给和它相连接的其它实例\
-
客户端收到哈希槽信息后,会把哈希槽信息缓存在本地。当客户端请求键值对时,会先计算键所对应的哈希槽,然后就可以给相应的实例发送请求了。\
-
-
但是hash槽可能发生变化
-
在集群中,实例有新增或删除,Redis 需要重新分配哈希槽\
-
为了负载均衡,Redis 需要把哈希槽在所有实例上重新分布一遍\
-
-
由于hash槽位置的变化导致与客户端的缓存不一致
-
迁移完成的情况
-
Redis Cluster 方案提供了一种重定向机制\
-
当客户端把一个键值对的操作请求发给一个实例时,如果这个实例上并没有这个键值对映射的哈希槽\
-
这个实例就会给客户端返回下面的 MOVED 命令响应结果,这个结果中就包含了新实例的访问地址\
-
客户端还会更新缓存
-
-
迁移中的情况
-
在这种迁移部分完成的情况下,客户端就会收到一条 ASK 报错信息
-
客户端请求的键值对所在的哈希槽 13320,在 172.16.19.5 这个实例上,但是这个哈希槽正在迁移
-
客户端需要先给 172.16.19.5 这个实例发送一个 ASKING 命令
-
让这个实例允许执行客户端接下来发送的命令。然后,客户端再向这个实例发送 GET 命令,以读取数据\
-
不会更改本地缓存,只会让请求新的实例槽
-
-
GET hello:key (error) MOVED 13320 172.16.19.5:6379
\
5.总结
-
切片集群在保存大量数据方面的优势,以及基于哈希槽的数据分布机制和客户端定位键值对的方法
- 大部分情况由客户端缓存完成(槽->实例)
-
虽然增加内存这种纵向扩展的方法简单直接,但是会造成数据库的内存过大,导致性能变慢\
-
Redis 切片集群提供了横向扩展的模式,也就是使用多个实例,并给每个实例配置一定数量的哈希槽,数据可以通过键的哈希值映射到哈希槽,再通过哈希槽分散保存到不同的实例上\
-
集群的实例增减,或者是为了实现负载均衡而进行的数据重新分布,会导致哈希槽和实例的映射关系发生变化,客户端发送请求时,会收到命令执行报错信息。了解了 MOVED 和 ASK 命令,你就不会为这类报错而头疼了\
-
redis3.0之前的方案
- 基于客户端的ShardedJedis
- 基于代理的Codis、Twemproxy
-
可以根据这些方案的特点,选择合适的方案实现切片集群,以应对业务需求了\
\