 
简介
什么是Redis
有哪些公司在使用Redis
GitHub, Twitter, Stackoverflow, Alibaba, 百度,微博,美团,搜狐
Redis的八大特性
- 速度快
- 支持持久化
- 存在多种数据结构
- 支持多种语言
- 功能丰富
- 简单(代码量小,使用方便)
- 主从复制
- 高可用,分布式
速度快
官方给的指标是10w OPS(每秒十万次读写)
这么快的原因:
- 数据存放在内存中 
- 使用C语言编写(大约五万行代码),C语言离操作系统较近 
- 线程模型为单线程 
持久化
Redis所有数据保存在内存中,对数据的更新将异步的保存到磁盘上。
多种数据结构
五大数据结构:
- Strings
- Hash Tables
- Linked Lists
- Sets
- Sorted Sets
其他新结构:
- BitMaps: 位图(本质是字符串,可以实现类似布隆过滤器的功能)
- HyperLogLog:超小内存唯一值计数(本质是字符串)
- GEO:地理信息定位(本质是有序集合,Redis-3.2后提供)
支持多种客户端语言
Java,PHP,python,Ruby,Lua,nodejs
功能丰富
- 发布订阅模式
- Lua脚本
- 事务
- pipeline(提高客户端并发效率)
典型应用场景
- 缓存系统
- 计数器(微博的转发数,评论数以及视频网站的播放数等)
- 消息队列系统
- 排行榜
- 社交网络(粉丝数,关注数,共同关注数)
- 实时系统(垃圾邮件处理系统,布隆过滤器)
安装Redis
Redis安装
Linux系统下安装Redis
| 1 | 1. 下载 | 
可执行文件说明
| 可执行文件 | 说明 | 
|---|---|
| redis-server | Redis服务器 | 
| redis-cli | Redis命令行客户端 | 
| redis-benchmark | Redis性能测试工具 | 
| redis-check-aof | AOF文件修复工具 | 
| redis-check-dump | RDB文件修复工具 | 
| redis-sentinel | Sentinel服务器(2.8以后) | 
三种启动方法
最简启动
直接使用redis-server
动态参数启动
| 1 | redis-server --port 6380 # 默认端口是6379 | 
配置文件启动
| 1 | redis-server configPath | 
验证方法
| 1 | ps -ef|grep redis | 
比较
- 生产环境推荐配置启动
- 单机多实例配置文件可以用端口区分开
简单的客户端连接
连接
| 1 | redis-cli -h 10.10.79.150 -p 6384 | 
返回值
- 状态回复(如ping返回PONG)
- 错误回复(如(error) WRONGTYPE Operation against)
- 整数回复(如incr hello回复(integer) 1)
- 字符串回复(如get hello回复"world")
- 多行字符串回复(如mget hello foo返回"world" "bar")
Redis常用配置
| 属性 | 说明 | 
|---|---|
| daemonize | 是否是守护进程(no默认|yes推荐) | 
| port | Redis对外端口号,默认为6379 7379对应手机按键上MERZ对应的号码,MERZ取自意大利歌女Alessia Merz的名字 | 
| logfile | Redis系统日志 | 
| dir | Redis工作目录 | 
Redis API
通用命令
- keys - eg: - keys *遍历所有的key- 怎么用: -  热备从节点 -  scan 
- dbsize - 计算key的总数 
- exists key - 检查key是否存在,存在返回1,否则返回0 
- del key [key …] - 删除指定key-value,成功返回1,否则返回0 
- expire key seconds - ttl key查看key剩余的过期时间- persist key去掉key的过期时间
- type key - 返回key的类型(string,hash,list,set,zset,none) 
| 命令 | 时间复杂度 | 
|---|---|
| keys | O(n) | 
| dbsize | O(1) | 
| del | O(1) | 
| exists | O(1) | 
| expire | O(1) | 
| type | O(1) | 
数据结构和内部编码
内部编码

字符串键值结构
| key | value | 
|---|---|
| hello | world | 
| counter | 1 | 
| bits | |1|0|1|1|1|0|1| | 
| { “prodduct”:{ “id”: “3242” “name”: “test423” } } | 
Value up to 512MB,up to 100k is recommended
场景
- 缓存
- 计数器
- 分布式锁
命令
| 1 | 1. 获取key对应的value O(1) | 
| 1 | 1. key自增1,如果key不存在,自增后get(key)=1 O(1) | 
| 1 | 1. 不管key是否存在,都设置 O(1) | 
| 1 | 1. 批量获取key,原子操作 O(n) | 
n次get = n次网络时间 + n次命令时间
1次mget = 1次网络时间 + n次命令时间
| 1 | 1. set key newvalue并返回旧的value O(1) | 
| 1 | 1. 增加key对应的值3.5 O(1) | 
复杂度总结
| 命令 | 含义 | 复杂度 | 
|---|---|---|
| set key value | 是指key-value | O(1) | 
| get key | 获取key-value | O(1) | 
| del key | 删除key-value | O(1) | 
| setnx setxx | 根据key是否存在设置key-value | O(1) | 
| Incr decr | 计数 | O(1) | 
| mget mset | 批量操作key-value | O(n) | 
实战
- 记录网站每个用户个人主页的访问量? - 1 - incr userid:pageview 
- 缓存视频的基本信息(数据源在MySQL中)伪代码 - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12- public VideoInfo get(long id){ 
 String redisKey = redisPrefix + id;
 VideoInfo videoInfo = redis.get(redisKey);
 if(videoInfo == null){
 videoInfo = mysql.get(id);
 if(videoInfo != null){
 //序列化
 redis.set(redisKey, serialize(videoInfo));
 }
 }
 return videoInfo;
 }
- 分布式计数器 - incr id (原子操作) 
Hash键值结构
| key | field | value | 
|---|---|---|
| user:1:info | name age Date | Ronaldo 32 239 | 
特点
- 是一个 - value为- Map的- Map
- field不能相同,- value可以相同
API
所有以哈希为结构的命令都是以H开头的。
| 1 | 1. 获取hash key对应的field的value O(1) | 
| 1 | 1. 判断hash key是否有field O(1) | 
| 1 | 1. 批量获取hash key的一批field对应的值 O(n) | 
| 1 | 1. 返回hash key对应所有的field和value O(n) | 
| 1 | 1. 设置hash key对应field的value(若field已存在,则失败) O(1) | 
复杂度总结
| 命令 | 复杂度 | 
|---|---|
| hget hset hdel | O(1) | 
| hexists | O(1) | 
| hincrby | O(1) | 
| hgetall hvals hkeys | O(n) | 
| hmget hmset | O(n) | 
列表
特点
- 有序(根据插入顺序得到遍历顺序)
- 可以重复
- 左右两边都可以插入和弹出
API
列表的API都以L开头
增
| 1 | 1. 从列表右端插入值 O(1) | 
删
| 1 | 1. 从列表左侧弹出一个item O(1) | 
| 1 | 3. 根据count值,从列表中删除所有value相等的项 O(n) | 
| 1 | 4. 按照索引范围修剪列表 O(n) | 
查
| 1 | 1. 获取列表指定索引范围所有item O(n) | 
| 1 | 2. 获取列表指定索引的item O(n) | 
| 1 | 3. 获取列表长度 O(1) | 
改
| 1 | 1. 设置列表指定索引值为newValue O(n) | 
其他
| 1 | 1. lpop阻塞版本,timeout是阻塞超时时间,timeout=0为永远不阻塞 O(1) | 
TIPS
- LPUSH + LPOP = Stack
- LPUSH + RPOP = Queue
- LPUSH + LTRIM = Capped Collection
- LPUSH + BRPOP = Message Queue
实战
- TimeLine
实战
- 记录网站每个用户个人主页的访问量 - 1 - hincrby user:1:info pageview count 
- 缓存视频的基本信息(数据源在mysql中)伪代码 - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12- public VideoInfo get(long id){ 
 String redisKey = redisPrefix + id;
 Map<String, String> hashMap = redis.hgetAll(redisKey);
 VideoInfo videoInfo = transferMapToVideo(hashMap);
 if(videoInfo == null){
 videoInfo = mysql.get(id);
 if(videoInfo != null){
 redis.hmset(redisKey, transferVideoToMap(videoInfo));
 }
 }
 return videoInfo;
 }
单线程架构
Redis在同一时刻只会执行一条命令
单线程为什么这么快
- 纯内存 
- 非阻塞IO 
- 避免线程切换和竞态消耗 
- 拒绝长(慢)命令 -  keys, flushall, flushdb, slow lua script, mutil/exec, operate big value(collection) 
- 其实不是单线程 - fysnc file descriptor - close file descriptor 
Redis客户端
Java客户端:Jedis
获取Jedis
- 添加Maven依赖
| 1 | <!-- https://mvnrepository.com/artifact/redis.clients/jedis --> | 
- Jedis直连(TCP连接) - 1 
 2
 3- Jedis jedis = new Jedis("127.0.0.1", 6379); 
 jedis.set("hello","world");
 String value = jedis.get("hello");- Jedis(String host, int port, int connectionTimeout, int soTimeout)- host : Redis节点所在机器的IP
- port : Redis节点的端口
- connectionTimeout : 客户端连接超时时间(内部使用socket技术)
- soTimeout : 客户端读写超时时间
 
Jedis基本使用
Jedis连接池使用
简单使用
| 1 | // 初始化Jedis连接池,通常来讲JedisPool是单例的。 | 
| 优点 | 缺点 | |
|---|---|---|
| 直连 | - 简单方便 - 适用于少量长期连接的场景 | - 存在每次新建/关闭TCP开销 - 资源无法控制,存在连接泄露的可能 - Jedis对象线程不安全 | 
| 连接池 | - Jedis预先生成,降低开销使用 - 连接池的形式保护和控制资源的使用 | 相对于直连,使用相对麻烦,尤其在资源的管理上需要很多参数来保证,一旦规划不合理也会出现问题。 | 
python客户端:redis-py
源码安装
| 1 | wget https://github.com/andymccurdy/redis-py/archive/3.0.0.zip | 
简单使用
| 1 | import redis | 
Go客户端
| 1 | c,err := redis.Dial("tcp", "127.0.0.1:6379") | 
瑞士军刀Redis
慢查询
生命周期

- 慢查询发生在第三阶段
- 客户端超时不一定是慢查询导致的,慢查询只是导致客户端超时的一个可能因素
两个配置
slowlog-max-len
- 先进先出队列
- 固定长度
- 保存在内存内
slowlog-log-slower-than
- 慢查询阈值(单位:微妙,1毫秒等于1000微秒)
- slowlog-log-slower-than=0,记录所有命令
- slowlog-log-slower-than<0,不记录任何命令
配置方法
- 默认值- config get slowlog-max-len = 128
- config get slowlog-log-slower-than = 10000
 
- 修改配置文件后重启(适用于未启动时)
- 动态配置- config set slowlog-max-len 1000
- config set slowlog-log-slower-than 1000
 
三个命令
- slowlog get [n] :获取慢查询队列
- slowlog len :获取慢查询队列长度
- slowlog reset :清空慢查询队列
运维经验
- slowlog-max-len不要是指过大,默认10ms,通常是指1ms
- slowlog-log-slower-than不要设置太小,通常设置1000左右
- 理解命令生命周期
- 定期持久化慢查询
pipeline
什么是流水线
流水线就是一次网络连接里传输一批命令,节省网络传输时间
| 命令 | N个命令操作 | 1次pipeline | 
|---|---|---|
| 时间 | n次网络 + n次命令 | 1次网络 + n次命令 | 
| 数据量 | 1条命令 | n条命令 | 
注意:
- Redis的命令时间是微秒级别。
- pipeline每次条数要控制(网络)。
客户端实现 pipeline-Jedis
| 1 | // before pipeline | 
| 1 | // after pipeline | 
与原生M操作做对比
M操作是原子操作,pipeline是非原子操作。
使用建议
- 注意每次pipeline携带数据量
- pipeline每次只能作用在一个Redis节点上
- M操作与pipeline区别
发布订阅
角色
- 发布者(publisher)
- 订阅者(subscriber)
- 频道(channel)
模型
Redis server中有各个频道。
发布者向频道中发布消息
订阅者收到其所订阅频道的消息,只能收到订阅时刻之后的消息,之前的收不到。

需要注意的是,redis没有消息堆积的能力。
API
publish
| 1 | publish channel message | 
eg:
| 1 | publish sohu:tv "hello world" | 
subscribe
| 1 | subscribe [channel] # 一个或多个 | 
unsubscribe
| 1 | unsubcribe [channle] # 一个或多个 | 
其他
| 1 | 1. 订阅模式 | 
发布订阅与消息队列
发布订阅是所有订阅者可以收到所有的消息
消息队列是订阅者要进行消息抢夺,一条消息只有一个订阅者能够抢到。
Bitmap
位图
| b | i | g | 
|---|---|---|
| 01100010 | 01101001 | 01100111 | 
相关命令
| 1 | 1. 给位图指定索引设置值 | 
独立用户统计
- 使用set和Bitmap
- 总共1亿用户,每日5千万独立访问。
| 数据类型 | 每个userid只用空间 | 需要存储的用户量 | 全部内存量 | 
|---|---|---|---|
| set | 32位 (假设userid用的是整型, 实际很多网站用的是长整型) | 50,000,000 | 32位*50,000,000=100MB | 
| Bitmap | 1位 | 100,000,000 | 12.5MB | 
| 一天 | 一个月 | 一年 | |
|---|---|---|---|
| set | 200M | 6G | 72G | 
| Bitmap | 12.5M | 375M | 4.5G | 
如果只有10万对立用户:
| 数据类型 | 每个userid占用空间 | 需要存储的用户量 | 全部内存量             |
| ——– | —————— | —————- | ———————- |
| set      | 32位               | 1,000,000        | 32位1,000,000=4MB     |
| Bitmap   | 1位                | 100,000,000      | 1位100,000,000=12.5MB |
使用经验
- type=string,最大512MB
- 注意setbit时的偏移量,可能有较大耗时
- 位图不是绝对好。
HyperLogLog
是否是新的数据结构
- 基于 HyperLogLog 算法:极小空间完成独立数量统计。
- 本质还是字符串
三个命令
- 向 hyperloglog 添加元素 - 1 - pfadd key element [element ...] 
- 计算 hyperloglog 的独立总数 - 1 - pfcount key [key ...] 
- 合并多个 hyperloglog - 1 - pfmerge destkey sourcekey [sourcekey ...] 
内存消耗
使用经验
- 存在错误率(0.81%)
- 无法取出单条数据
GEO
GEO是什么
GEO(地理信息定位):存储经纬度,计算两地距离,范围计算等。
5个城市经纬度
| 城市 | 经度 | 纬度 | 
|---|---|---|
| 北京 | 116.28 | 39.55 | 
| 天津 | 117.12 | 39.08 | 
| 石家庄 | 114.29 | 38.02 | 
| 唐山 | 118.01 | 39.38 | 
| 保定 | 115.29 | 38.51 | 
相关命令
| 1 | 1. 添加地理位置信息 | 
相关说明
- since 3.2+
- type geoKey = zset
- 没有删除 API:zrem key member
持久化
持久化的作用
(1)什么是持久化
redis 所有数据保存在内存中,对数据的更新将异步的保存到磁盘上。
(2)持久化的实现方式
- 快照- MySQL Dump
- Redis RDB
 
- 写日志- MySQL Binlog
- Hbase Hlog
- Redis AOF
 
RDB
(1)什么是RDB
 redis 通过命令创建 RDB 文件(二进制)保存在硬盘中。当 redis 重启时通过命令将 RDB 文件载入到内存。实质是一个复制媒介。
(2)触发机制-主要三种方式
- save(同步) - 新文件将会替换老文件
- 复杂度位 O(n)
 
- bgsave(异步) - 利用 Linux fork() 异步执行 
- 文件策略及复杂度与 save 相同 - 命令 - save - bgsave - IO类型 - 同步 - 异步 - 阻塞 - 是 - 是(阻塞发生在fork) - 复杂度 - O(n) - O(n) - 优点 - 不会消耗额外内存 - 不阻塞客户端命令 - 缺点 - 阻塞客户端命令 - 需要fork,消耗内存 
 
- 自动 - redis 提供了 save 配置 - 配置 - seconds - changes - save - 900 - 1 - save - 300 - 10 - save - 60 - 10000 - 满足任意一个条件,redis 会自动创建(bgsave)RDB 文件 - 默认配置: - 1 
 2
 3
 4
 5
 6
 7
 8- save 900 1 
 save 300 10
 save 60 10000
 dbfiename dump.rdb
 dir ./
 stop-writes-on-bgsave-error yes
 rdbcompression yes
 rdbcheksum yes- 最佳配置: - 1 
 2
 3
 4
 5- dbfilename dump-${port}.rdb 
 dir /bigdiskpath
 stop-writes-on-bgsave-error yes
 rdbcompression yes
 rdbcheksum yes
(3)触发机制-不容忽视方式
1. 全量复制
2. debug reload
3. shutdown (4)总结
1. RDB是 Redis 内存到硬盘的快照,用于持久化。
2. save 通常会阻塞 Redis
3. bgsave 不会阻塞 Redis,但是会 fork 新进程。
4. save 自动配置满足任意一个条件就会被执行。AOF
RDB 现存问题
- 耗时,耗性能 - O(n): 耗时 
- fork(): 消耗内存,copy-on-write 策略 
- Disk I/O: IO 性能 
 
- 不可控,丢失数据 
什么是 AOF
每执行一条命令,就将该命令写入到 AOF 日志文件中。
AOF 三种策略
(1)always
 每条写命令都会出发刷新,将缓冲区的命令日志刷新到 AOF 文件中。
(2)everysec
 每秒(默认值)刷新一次
(3)no
 由操作系统决定什么时候刷新。
| 命令 | always | everysec | no | 
|---|---|---|---|
| 优点 | 不丢失数据 | 每秒一次 fsync 丢失一秒数据 | 不用管 | 
| 缺点 | IO 开销较大,一般的 sata 盘只有几百 TPS | 丢失1秒数据 | 不可控 | 
AOF 重写
| 原生 AOF | AOF 重写 | 
|---|---|
| set hello world set hello java set hello hehe incr counter incr counter rpush mylist a rpush mylist b rpush mylist c 过期数据 | set hello hehe set counter 2 rpush mylist a b c | 
AOF 重写的作用:
- 减少磁盘占用量
- 加速恢复速度
AOF 重写的两种实现方式
- berewriteaof 
- AOF 重写配置 - 配置名 - 含义 - autoaof-rewrite-min-size - AOF 文件重写需要的尺寸 - auto-aof-rewrite-percentage - AOF 文件增长率 - 统计名 - 含义 - aof_current_size - AOF 当前尺寸(单位:字节) - aof_base_size - AOF 上次启动和重写的尺寸(单位:字节) - 自动触发时机: - aof_current_size > auto-aof-rewrite-min-size
- aof_current_size - aof_base_size / aof_base_size > auto-aof-rewrite-percentage
 - AOF 重写流程 - 常用配置: - 1 
 2
 3
 4
 5
 6
 7- appendonly yes 
 appendfilename "appendonly-${port}.aof"
 appendfsync everysec
 dir /bigdiskpath
 no-appendfsync-on-rewrite yes
 auto-aof-rewrite-percentage 100
 auto-aof-rewrite-min-size 64mb
RDB 和 AOF 的抉择
RDB 和 AOF 比较
| 命令 | RDB | AOF | 
|---|---|---|
| 启动优先级 | 低 | 高 | 
| 体积 | 小 | 大 | 
| 恢复速度 | 快 | 慢 | 
| 数据安全性 | 丢数据 | 根据策略决定 | 
| 轻重 | 重 | 轻 | 
RDB 最佳策略
- “关”
- 集中管理
- 主从,从开
AOF 最佳策略
- ”开“:缓存和存储
- AOF 重写集中管理
- everysec
最佳策略
- 小分片
- 缓存或者存储
- 监控(硬盘、内存、负载、网络)
- 足够的内存
开发运维常见问题
fork 操作
- 同步操作
- 与内存量息息相关:内存越大,耗时越长(与机器类型有关)
- info: latest_fork_usec 查询上一次 fork 的时间
如何改善fork
- 优先使用物理机或者高效支持 fork 操作的虚拟化技术
- 控制 Redis 实例最大可用内存:maxmemory
- 合理配置 Linux 内存分配策略:vm.overcommit_memory = 1
- 降低 fork 频率:例如放宽 AOF 重写自动触发时机,不必要的全量复制
进程外开销
子进程开销和优化
- CPU:- 开销:RDB 和 AOF 文件生成,属于 CPU 密集型
- 优化:不做 CPU 绑定,不和 CPU 密集型部署
 
- 内存:- 开销:fork 内存开销,copy-on-write。
- 优化:echo never > /sys/kernel/mm/transparent_hugepage/enabled
 
- 硬盘- 开销:AOF 和 RDB 文件写入,可以结合 iostat,iotop 分析
- 优化:- 不要和高硬盘负责服务部署在一起:存储服务、消息队列等
- no-appendfsync-on-rewrite = yes
- 根据写入量决定磁盘类型:例如 ssd
- 单机多实例持久化文件目录可以考虑分盘
 
 
AOF追加阻塞

AOF 阻塞定位
 Reids 日志:
 Asynchronous AOF fsync is taking too long (disk is busy?).
 Writing the AOF buffer without waiting for fsync to complete,this may slow down Redis.
 info Persistence:
 …
 aof_delayed_fsync: 100
 …
单机多实例部署
Redis 复制的原理与优化
什么是主从复制
(1)单机有什么问题?
 机器故障
 容量瓶颈
 QPS 瓶颈
(2)作用
 数据副本
 扩展读性能
复制的配置
两种实现方式
- slaveof 命令 
- 配置 - 1 
 2- slaveof ip port 
 slave-read-only yes
全量复制和部分复制
查看 run_id
| 1 | redis-cli -p 6379 info server | grep run | 
查看偏移量
| 1 | redis-cli -p 6379 info replication | 
全量复制开销:
- bgsave 时间
- RDB文件网络传输时间
- 从节点清空数据时间
- 从节点加载 RDB 的时间
- 可能的 AOF 重写时间
故障处理
开发运维常见问题
- 读写分离 - 将读流量分摊到从节点 - 可能遇到的问题: - 复制数据延迟
- 读到过期数据
- 从节点故障
 
- 主从配置不一致 
- 规避全量复制 - 第一次不可避免
- 系欸但运行 ID  不匹配- 主节点重启
- 故障转移
 
- 复制积压缓冲区不足- 网络中断,部分复制无法满足
- 增大复制缓冲区配置 rel_backlog_size,网络“增强”。
 
 
- 规避复制风暴 - 单主节点复制风暴- 问题:主节点重启,多从节点复制
- 解决:更换复制拓扑
 
- 单机器复制风暴- 机器宕机后,大量全量复制
- 主节点分散多机器
 
 
- 单主节点复制风暴
Redis Sentinel
主从复制高可用存在的问题
- 手动故障转移
- 写能力和存储能力受限
架构说明
客户端不直接访问 redis,而是通过 sentinel 来访问。由 sentinel 来负责主从节点的管理。
安装配置
sentinel 的默认端口为 26379
- 配置开启主从节点
- 配置开启 sentinel 监控主节点(sentinel 是特殊的 redis)
客户端连接
请求相应流程
jedis
| 1 | JedisSentinelPool sentinelPool = new JedisSentinelPool(masterName, sentinelSet, poolConfig, timeout); | 
redis-py
实现原理
常见开发运维问题
Redis Cluster
呼唤集群
- 超高并发量需求
- 超大数据量需求
数据分布
顺序分布
哈希分布
- 节点取余- 使用多倍扩容降低迁移率
 
- 一致性哈希- 顺时针取余
- 只影响邻近节点
- 扩容后会存在负载不均衡的情况
 
- 虚拟槽- 每个槽映射一个数据子集
- 良好的哈希函数:例如 CRC16
- 服务端管理节点、槽、数据:例如 Redis Cluster
 
| 分布方式 | 特点 | 典型产品 | 
|---|---|---|
| 哈希分布 | 数据分散度高 键值分布业务无关 无法顺序访问 支持批量操作 | 一致性哈希 Memcache Redis Cluster | 
| 顺序分布 | 数据分散度易倾斜 键值业务相关 可顺序访问 不支持批量操作 | BigTable HBase | 
搭建集群
集群伸缩
客户端路由
集群原理
开发运维常见问题
Redis Cluster 架构
- 节点
- meet
- 指派槽
- 复制
Redis Cluster 特性
- 复制
- 高可用
- 分片
两种安装方式
原生命令安装
- 配置开启节点 - 1 
 2
 3
 4
 5
 6
 7- port ${port} 
 daemonize yes
 dir "/opt/redis/data"
 dbfilename "dump-${port}.rdb"
 logfile "${port}.log"
 cluster-enabled yes
 cluster-config-file nodes-${port}.conf
- meet - 1 - cluster meet ip port 
- 指派槽 - 1 - cluster addslots slot [slot ...] 
- 分配主从关系 - 1 - cluster replicate node-id