缓存数据库--Redis
使用缓存保护MySQL
MySQL能承担的并发读写的量是有上限的,当系统的在线用户超过几万到几十万这个量级的时候, 单台MySQL就很难应付了。
Redis简介
与其他数据库和软件的比较
与memcache比较
底层实现机制
1.内存分配
memcache使用预分配内存池的方式管理内存,能够省去内存分配时间。
redis则是临时申请空间,可能导致碎片。
2.虚拟内存使用
memcache把所有的数据存储在物理内存里。
redis有自己的VM机制,理论上能够存储比物理内存更多的数据,当数据超量时,会引发swap,把冷数据刷到磁盘上。
3.网络模型
memcache使用非阻塞IO复用模型;
redis也是使用非阻塞IO复用模型,redis还提供一些非KV存储之外的排序,聚合功能, 在执行这些功能时,复杂的CPU计算,会阻塞整个IO调度;
4.线程模型
memcache使用多线程,主线程监听,worker子线程接受请求,执行读写,这个过程中,可能存在锁冲突。
redis使用单线程,虽无锁冲突,但难以利用多核的特性提升整体吞吐量。
什么时候倾向于选择redis?
1.复杂数据结构
value是哈希,列表,集合,有序集合这类复杂的数据结构时,会选择redis,因为mc无法满足这些需求。 最典型的场景,用户订单列表,用户消息,帖子评论列表等。
memcache只能存储普通的字符串键
2.持久化
mc无法满足持久化的需求,只得选择redis。
只读场景,或者允许一些不一致的业务场景,可以尝试开启redis的固化功能。
3.天然高可用
redis天然支持集群功能,可以实现主动复制,读写分离。
但是,这里要提醒的是,大部分业务场景,缓存真的需要高可用么?
(1)缓存场景,很多时候,是允许cache miss
(2)缓存挂了,很多时候可以通过DB读取数据
4.存储的内容比较大
memcache的value存储,最大为1M,如果存储的value很大,只能使用redis。
什么时候倾向于memcache?
纯KV,数据量非常大,并发量非常大的业务,使用memcache或许更适合。
Redis为什么快
Redis 是一个使用内存保存数据的高性能 KV 数据库,它的高性能主要来自于:
- 简单的数据结构;
- 使用内存存储数据;
- epoll
缺点:
Redis 仍然是一个不可靠的存储,它在设计上天然就不保证数据的可靠性,所以一般我们都使用 Redis 做缓存, 很少使用它作为唯一的数据存储。
Redis数据结构
String
数据类型比较简单,对应到数据结构里,就是字符串
常用命令:set/get/decr/incr/mget等;
使用场景:String是最常用的一种数据类型,普通的key/value存储都可以归为此类;
实现方式:默认是一个字符串,被redisObject所引用,遇到incr/decr等操作时会转成数值型进行计算;
List
常用命令:lpush/rpush/lpop/rpop/lrange等;
应用场景:如微博关注列表、粉丝列表等可以用list结构来实现;
1.压缩列表-ziplist
当列表中存储的数据量比较小的时候,列表就可以采用压缩列表的方式实现。
具体需要同时满足下面两个条件:
-
列表中保存的单个数据(有可能是字符串类型的)小于 64 字节
-
列表中数据个数少于 512 个
2.双向循环链表
以下是C语言代码
typedef struct listnode {
struct listNode *prev;
struct listNode *next;
void *value;
} listNode;
typedef struct list {
listNode *head;
listNode *tail;
unsigned long len;
// ....省略其他定义
} list;
Set
常用命令:sadd/spop/smembers/sunion等;
应用场景:Redis set对外提供的功能与list类似是一个列表的功能,特殊之处在于set是可以自动排重的, 当你需要存储一个列表数据,又不希望出现重复数据时,set是一个很好的选择, 并且set提供了判断某个成员是否在一个set集合内的重要接口,这个也是list所不能提供的;
set 的内部实现是一个 value永远为null的HashMap,实际就是通过计算hash的方式来快速排重的, 这也是set能提供判断一个成员是否在集合内的原因;
1.有序数组
需要同时满足下面两个条件:
-
存储的数据都是整数
-
存储的数据元素个数不超过 512 个
2.散列表
Hash
常用命令:hget/hset/hgetall等;
应用场景:如果需要存储一个用户信息对象数据,其中包括用户ID、name、age、birth, 通过用户ID我们希望获取该用户的name、age或者birth;
内部存储的Value为一个HashMap,并提供了直接存取这个Map成员的接口。Key是用户ID, value是一个Map。这个Map的key是成员的属性名,value是属性值。这样对数据的修改和存取都可以直接通过其内部Map的Key(Redis里称内部Map的key为field), 也就是通过 key(用户ID) + field(属性标签) 就可以操作对应属性数据;
1.压缩列表-ziplist
只有当存储的数据量比较小的情况下,Redis 才使用压缩列表来实现字典类型
具体需要同时满足下面两个条件:
-
字典中保存的键和值的大小都要小于 64 字节
-
字典中键值对的个数要小于 512 个
2.散列表
MurmurHash2这种运行速度快、随机性好的哈希算法作为哈希函数
哈希冲突问题:使用链表法
动态扩容、缩容
渐进式扩容缩容策略,将数据的搬移分批进行
ZSET
常用命令:zadd/zrange/zrem/zcard等;
应用场景:Redis sorted set的使用场景与set类似,区别是set不是自动有序的, 而sorted set可以通过用户额外提供一个优先级(score)的参数来为成员排序, 并且是插入有序的,即自动排序。 当你需要一个有序的并且不重复的集合列表,那么可以选择sorted set数据结构;
sorted set的内部使用HashMap和跳跃表(SkipList)来保证数据的存储和有序, HashMap里放的是成员到score的映射,而跳跃表里存放的是所有的成员, 排序依据是HashMap里存的score,使用跳跃表的结构可以获得比较高的查找效率,并且在实现上比较简单;
1.压缩列表
使用压缩列表来实现有序集合的前提:
-
所有数据的大小都要小于 64 字节
-
元素个数要小于 128 个
跳表
各方面性能都比较优秀的动态数据结构,可以支持快速的插入、删除、查找操作,写起来也不复杂,甚至可以替代红黑树(Red-black tree)
链表加多级索引
在跳表中查询某个数据的时候,如果每一层都要遍历 m 个结点,那在跳表中查询一个数据的时间复杂度就是 O(m*logn)。
插入、删除操作的时间复杂度也是 O(logn)
跳表通过随机函数来维护“平衡性”。
跳表与红黑树
插入一个数据;
删除一个数据;
查找一个数据;
按照区间查找数据(比如查找值在[100, 356]之间的数据);
迭代输出有序序列。
按照区间来查找数据这个操作,红黑树的效率没有跳表高
跳表也不能完全替代红黑树。因为红黑树比跳表的出现要早一些,很多编程语言中的 Map 类型都是通过红黑树来实现的。我们做业务开发的时候,直接拿来用就可以了,不用费劲自己去实现一个红黑树,但是跳表并没有一个现成的实现,所以在开发中,如果你想使用跳表,必须要自己实现。
应用场景
1.Session Cacheh会话缓存
2.全页缓存
3.计数器
4.排行榜
5.队列
6.发布/订阅
持久化策略
快照(RDB)
RDB持久化指的是在满足一定的触发条件时(在一个的时间间隔内执行修改命令达到一定的数量, 或者手动执行SAVE和BGSAVE命令),对这个时间点的数据库所有键值对信息生成一个压缩文件dump.rdb, 然后将旧的删除,进行替换。
默认开启
fork一个进程来进行持久化
持久化的文件默认保存在dump.rdb, 可以修改redis.conf文件的dir配置路径
bgsave使用子进程持久化
save使用主进程持久化
shutdown时会自动触发
主从复制的情况下rdb关不掉
bgsave的原理主要是fork和cow,fork即redis通过创建子进程来进行bgsave操作; cow即Copy On Write,子进程创建后,父子进程共享数据段,父进程继续提供读写服务, 写脏的页面数据会逐渐和子进程分离开来;
AOF 日志
AOF持久化主要是Redis在修改相关的命令后,将命令添加到aof_buf缓存区的末尾,然后在每次事件循环结束时, 根据appendfsync的配置(always是总是写入,everysec是每秒写入,no是根据操作系统来决定何时写入), 判断是否需要将aof_buf写入AOF文件。
丢失的数据少,每1s执行一次保存,丢失数据不超过2s
每秒可以写10W+数据
可在配置中修改,appendonly 开关,使用everysec,不要用always
appendonly.aof
aof重写:
auto-aof-rewrite-min-size 64M
auto-aof-rewrite-percentage 100
bgrewriteaof
redis4.0
混合持久化机制
如何做持久化
bgsave做镜像全量持久化,AOF做增量持久化;
在Redis实例重启时,使用bgsave持久化文件重新构建内存, 再使用AOF重放近期的操作指令来实现完整恢复重启之前的状态;
过期策略
惰性清除(被动删除)
在访问key时,如果发现key已经过期,那么会将key删除。
定时清理(主动清理)
由于惰性删除策略无法保证冷数据被及时删掉,所以Redis会定期主动淘汰一批已过期的key
Redis会把有过期时间的key放在一个单独的字典里,默认每100ms检查,是否有过期的key,有过期的key则删除
Redis配置项hz定义了serverCron任务的执行周期,默认每次清理时间为25ms,每次清理会依次遍历所有DB, 从db随机取出20个key,如果过期就删除,如果其中有5个key过期,那么就继续对这个db进行清理,否则开始清理下一个db。
内存不够时,淘汰策略
当执行写入命令时,如果发现内存不够,那么就会按照配置的淘汰策略清理内存
淘汰策略主要由以下几种:
eviction: 不删除,达到内存限制时,执行写入命令时直接返回错误信息。
allkeys-lru: 在所有key中,优先删除最少使用的key(适合请求符合幂定律分布,也就是一些键访问频繁,一部分键访问较少)
allkeys-random: 在所有key中,随机删除一部分key(适合请求分布比较平均)
volatile-lru: 在设置了expire的key中,优先删除最少使用的key
volatile-random: 在设置了expire的key中,随机删除一部分key。
volatile-ttl: 在设置了expire的key中,优先删除剩余时间短的key。
高可用
1.cluster
2.主从
3.哨兵,小规模,中小型公司
演进:
单机———-> 主从复制 ———> 哨兵模式 —-> cluster —->
读写分离、容灾备份 解决单机故障,自动转移
- 单机版
内存容量有限
处理能力有限
无法高可用
- 主从复制
特点:
master/slave 角色
master/slave 数据相同
降低 master 读压力在转交从库
问题:
无法保证高可用
没有解决master写的压力
- 哨兵
特点:
保证高可用
监控各个节点
自动故障迁移
缺点:
主从模式,切换需要时间丢数据
没有解决master写的压力
- 集群(proxy型)
Twemproxy是一个Twitter开源的一个redis和memcache快速/轻量级代理服务器,Twemproxy是一个快速的单线程代理程序, 支持Memcached ASCII协议和redis协议
特点:
多种hash算法: MD5、CRC16、CRC32、CRC32a、hsieh、murmur、Jenkins
支持失败节点自动删除
后端Sharding 分片逻辑对业务透明,业务方的读写方式和操作单个Redis一致
缺点:
增加了新的proxy,需要维护其高可用
failover 逻辑需要自己实现,其本身不能支持故障的自动转移可扩展性差,进行扩缩容都需要手动干预
- 集群(redis cluster)
从redis 3.0之后版本支持redis-cluster集群,Redis-Cluster采用无中心结构,每个节点保存数据和整个集群状态, 每个节点都和其他所有节点连接。
特点:
无中心结构(不存在哪个节点影响性能瓶颈),少了proxy层
数据按照slot存储分布在多个节点,节点间数据共享,可动态调整数据分布
可扩展性,可线性扩展到1000个节点,节点可动态添加和删除
高可用性,部分节点不可用时,集群仍可用,通过添加Slave做备份数据副本
实现故障自动failover,节点之间通过gossip协议交换状态信息,用投票机制完成Slave到Master的角色提升
缺点:
资源隔离性较差,容易出现相互影响的情况
数据通过异步复制,不保证数据的强一致性
Redis Sentinal着眼于高可用,在master宕机时会自动将slave提升为master,继续提供服务;
Redis Cluster着眼于扩展性,在单个redis内存不足时,使用Cluster进行分片存储;
同步机制
Redis可以使用主从同步、从从同步; 第一次同步时,主节点bgsave,同时将后续修改操作记录到内存buffer, 待完成后将rdb文件全量同步到复制节点;
redis分布式锁
先拿setnx来争抢锁,抢到之后,再用expire给锁加一个过期时间防止锁忘记了释放;
如果在setnx之后执行expire之前进程意外crash?
可以同时把setnx和expire合成一条指令来用
redis异步队列
一般使用list结构作为队列,rpush生产消息,lpop消费消息; 当lpop没有消息的时候,要适当sleep一会再重试;
使用pub/sub主题订阅模式实现1:N消息队列;但在消费者下线的情况下,生产的消息会丢失;
Redis事务
Redis 通过 MULTI 、 DISCARD 、 EXEC 和 WATCH 四个命令来实现事务功能;
事务提供了一种“将多个命令打包, 然后一次性、按顺序地执行”的机制, 并且事务在执行的期间不会主动中断:服务器在执行完事务中的所有命令之后,才会继续处理其他客户端的其他命令;
MULTI 命令的执行标记着事务的开始;当客户端进入事务状态之后, 服务器在收到来自客户端的命令时, 不会立即执行命令, 而是将这些命令全部放进一个事务队列里, 然后返回 QUEUED , 表示命令已入队;
当事务队列里的所有命令被执行完之后, EXEC 命令会将回复队列作为自己的执行结果返回给客户端, 客户端从事务状态返回到非事务状态, 至此, 事务执行完毕;
DISCARD 命令用于取消一个事务, 它清空客户端的整个事务队列, 然后将客户端从事务状态调整回非事务状态, 最后返回字符串 OK 给客户端, 说明事务已被取消;
WATCH 命令用于在事务开始之前监视任意数量的键: 当调用 EXEC 命令执行事务时, 如果任意一个被监视的键已经被其他客户端修改了, 那么整个事务不再执行, 直接返回失败;
常见问题
缓存穿透
查询不存在数据的现象我们称为缓存穿透
查询缓存—->没有—->数据库查询—->回写缓存
解决办法
1.为这些key对应的值设置为null 丢到缓存里面去。后面再出现查询这个key 的请求的时候,直接返回null 。设置过期时间。
2.在缓存之前在加一层 BloomFilter ,在查询的时候先去 BloomFilter 去查询 key 是否存在,如果不存在就直接返回,存在再走查缓存 -> 查 DB。 针对key异常多、请求重复率比较低的数据(攻击),我们就没有必要进行缓存,使用第二种方案直接过滤掉。 而对于空数据的key有限的,重复率比较高的,我们则可以采用第一种方式进行缓存。
缓存击穿
在平常高并发的系统中,大量的请求同时查询一个key时, 此时这个key正好失效了,就会导致大量的请求都打到数据库上面去。
这种现象我们称为缓存击穿。
影响
会造成某一时刻数据库请求量过大,压力剧增。
解决办法
多个线程同时去查询数据库的这条数据,那么我们可以在第一个查询数据的请求上使用一个 互斥锁 来锁住它。 其他的线程走到这一步拿不到锁就等着,等第一个线程查询到了数据,然后做缓存。 后面的线程进来发现已经有缓存了,就直接走缓存。
缓存雪崩
缓存雪崩的情况是说,当某一时刻发生大规模的缓存失效的情况, 比如你的缓存服务宕机了,会有大量的请求进来直接打到DB上面。
结果就是DB撑不住,挂掉
解决办法
1.事前:使用集群缓存,保证缓存服务的高可用
2.事中:ehcache本地缓存 + Hystrix限流&降级,避免MySQL被打死
3.事后:开启Redis持久化机制,尽快恢复缓存集群
缓存与数据库不一致问题
多数情况下,使用缓存的策略是:
读请求: 先读缓存,缓存没有命中就读数据库,然后同步到缓存
写请求: 1.先删除缓存再更新数据库–可能读到缓存脏数据; 2.先更新数据库再删除缓存–看着没什么问题,除非更新数据库之后,宕机,缓存没来得及更新(概率很小),一直是脏数据
加入缓存后,导致的不一致影响时间会很长,并且最终也不会达到一致;
如果是Redis集群,或者主从模式,由于Redis复制存在一定时延,也有可能导致数据不一致;
解决思路
1.延时双删
写库前后都进行缓存删除操作;
2.异步淘汰
通过读取binlog的方式,异步淘汰缓存;
业务代码侵入性低,缩小缓存与数据库不一致的时间
常见性能问题
1.master最好不要写内存快照,或者AOF持久化
会阻塞主线程的工作,当快照比较大时对性能的影响很大,会间断性暂停服务;
AOF持久化对性能影响最小,但随着AOF文件不断增大,会影响master重启的恢复速度;
master最好不要做任何持久化工作,如果数据比较关键,可以某个slave开启AOF备份数据,每秒同步一次;
2.master和slave最好在同一局域网内,主从复制性能会更好;
3.使用keys命令可以扫出指定模式的key列表,但会导致线程阻塞一段时间, 线上服务会停顿,直到指令执行完毕,服务才能恢复; 可以使用scan命令,scan指令可以无阻塞的取出指定模式的key列表,但是会有一定的重复率; 可以在客户端做一次去重;
4.如果大量的key过期时间设置的过于集中,在过期的那个时间点,Redis可能会出现短暂的卡顿现象; 一般需要在时间上加一个随机值,使得时间分散一些;
5.如何删除一个大key 非字符串的bigkey,不要使用 del 删除,使用 hscan、sscan、zscan 方式渐进式删除; Redis 4.0 推出了一个重要命令 UNLINK,用来拯救 del 删大key的困境:在所有命名空间中把 key 删掉,立即返回,不阻塞, 后台线程执行真正的释放空间的操作;
Redis常见命令
1.连接操作相关的命令
quit:关闭连接(connection)
auth:简单密码认证
2.对value操作的命令
exists(key):确认一个key是否存在
del(key):删除一个key
type(key):返回值的类型
keys(pattern):返回满足给定pattern的所有key
randomkey:随机返回key空间的一个key
rename(oldname, newname):将key由oldname重命名为newname,若newname存在则删除newname表示的key
dbsize:返回当前数据库中key的数目
expire:设定一个key的活动时间(s)
ttl:获得一个key的活动时间
select(index):按索引查询
move(key, dbindex):将当前数据库中的key转移到有dbindex索引的数据库
flushdb:删除当前选择数据库中的所有key
flushall:删除所有数据库中的所有key
3.对String操作的命令
set(key, value):给数据库中名称为key的string赋予值value
get(key):返回数据库中名称为key的string的value
getset(key, value):给名称为key的string赋予上一次的value
mget(key1, key2,…, key N):返回库中多个string(它们的名称为key1,key2…)的value
setnx(key, value):如果不存在名称为key的string,则向库中添加string,名称为key,值为value
setex(key, time, value):向库中添加string(名称为key,值为value)同时,设定过期时间time
mset(key1, value1, key2, value2,…key N, value N):同时给多个string赋值,名称为key i的string赋值value i
msetnx(key1, value1, key2, value2,…key N, value N):如果所有名称为key i的string都不存在,则向库中添加string,名称key i赋值为value i
incr(key):名称为key的string增1操作
incrby(key, integer):名称为key的string增加integer
decr(key):名称为key的string减1操作
decrby(key, integer):名称为key的string减少integer
append(key, value):名称为key的string的值附加value
substr(key, start, end):返回名称为key的string的value的子串
4.对List操作的命令
压缩列表(ziplist)/双向循环链表
rpush(key, value):在名称为key的list尾添加一个值为value的元素
lpush(key, value):在名称为key的list头添加一个值为value的 元素
llen(key):返回名称为key的list的长度
lrange(key, start, end):返回名称为key的list中start至end之间的元素(下标从0开始,下同)
ltrim(key, start, end):截取名称为key的list,保留start至end之间的元素
lindex(key, index):返回名称为key的list中index位置的元素
lset(key, index, value):给名称为key的list中index位置的元素赋值为value
lrem(key, count, value):删除count个名称为key的list中值为value的元素。count为0,删除所有值为value的元素,count>0从头至尾删除count个值为value的元素,count<0从尾到头删除|count|个值为value的元素。 lpop(key):返回并删除名称为key的list中的首元素 rpop(key):返回并删除名称为key的list中的尾元素 blpop(key1, key2,… key N, timeout):lpop命令的block版本。即当timeout为0时,若遇到名称为key i的list不存在或该list为空,则命令结束。如果timeout>0,则遇到上述情况时,等待timeout秒,如果问题没有解决,则对keyi+1开始的list执行pop操作。
brpop(key1, key2,… key N, timeout):rpop的block版本。参考上一命令。
rpoplpush(srckey, dstkey):返回并删除名称为srckey的list的尾元素,并将该元素添加到名称为dstkey的list的头部
对Hash操作的命令
压缩列表/散列表
hset(key, field, value):向名称为key的hash中添加元素field<—>value
hget(key, field):返回名称为key的hash中field对应的value
hmget(key, field1, …,field N):返回名称为key的hash中field i对应的value
hmset(key, field1, value1,…,field N, value N):向名称为key的hash中添加元素field i<—>value i
hincrby(key, field, integer):将名称为key的hash中field的value增加integer
hexists(key, field):名称为key的hash中是否存在键为field的域
hdel(key, field):删除名称为key的hash中键为field的域
hlen(key):返回名称为key的hash中元素个数
hkeys(key):返回名称为key的hash中所有键
hvals(key):返回名称为key的hash中所有键对应的value
hgetall(key):返回名称为key的hash中所有的键(field)及其对应的value
对Set操作的命令
有序数组/散列表
sadd(key, member):向名称为key的set中添加元素member
srem(key, member) :删除名称为key的set中的元素member
spop(key) :随机返回并删除名称为key的set中一个元素
smove(srckey, dstkey, member) :将member元素从名称为srckey的集合移到名称为dstkey的集合
scard(key) :返回名称为key的set的基数
sismember(key, member) :测试member是否是名称为key的set的元素
sinter(key1, key2,…key N) :求交集
sinterstore(dstkey, key1, key2,…key N) :求交集并将交集保存到dstkey的集合
sunion(key1, key2,…key N) :求并集
sunionstore(dstkey, key1, key2,…key N) :求并集并将并集保存到dstkey的集合
sdiff(key1, key2,…key N) :求差集
sdiffstore(dstkey, key1, key2,…key N) :求差集并将差集保存到dstkey的集合
smembers(key) :返回名称为key的set的所有元素
srandmember(key) :随机返回名称为key的set的一个元素
对zset(sorted set)操作的命令
跳表/压缩列表
zadd(key, score, member):向名称为key的zset中添加元素member,score用于排序。如果该元素已经存在,则根据score更新该元素的顺序。
zrem(key, member) :删除名称为key的zset中的元素member
zincrby(key, increment, member) :如果在名称为key的zset中已经存在元素member,则该元素的score增加increment;否则向集合中添加该元素,其score的值为increment
zrank(key, member) :返回名称为key的zset(元素已按score从小到大排序)中member元素的rank(即index,从0开始),若没有member元素,返回“nil”
zrevrank(key, member) :返回名称为key的zset(元素已按score从大到小排序)中member元素的rank(即index,从0开始),若没有member元素,返回“nil”
zrange(key, start, end):返回名称为key的zset(元素已按score从小到大排序)中的index从start到end的所有元素
zrevrange(key, start, end):返回名称为key的zset(元素已按score从大到小排序)中的index从start到end的所有元素
zrangebyscore(key, min, max):返回名称为key的zset中score >= min且score <= max的所有元素 zcard(key):返回名称为key的zset的基数 zscore(key, element):返回名称为key的zset中元素element的score zremrangebyrank(key, min, max):删除名称为key的zset中rank >= min且rank <= max的所有元素 zremrangebyscore(key, min, max) :删除名称为key的zset中score >= min且score <= max的所有元素
zunionstore / zinterstore(dstkeyN, key1,…,keyN, WEIGHTS w1,…wN, AGGREGATE SUM|MIN|MAX):对N个zset求并集和交集,并将最后的集合保存在dstkeyN中。对于集合中每一个元素的score,在进行AGGREGATE运算前,都要乘以对于的WEIGHT参数。如果没有提供WEIGHT,默认为1。默认的AGGREGATE是SUM,即结果集合中元素的score是所有集合对应元素进行SUM运算的值,而MIN和MAX是指,结果集合中元素的score是所有集合对应元素中最小值和最大值。
持久化
save:将数据同步保存到磁盘
bgsave:将数据异步保存到磁盘
lastsave:返回上次成功将数据保存到磁盘的Unix时戳
shundown:将数据同步保存到磁盘,然后关闭服务
远程服务控制
info:提供服务器的信息和统计
monitor:实时转储收到的请求
slaveof:改变复制策略设置
config:在运行时配置Redis服务器
管理命令
启动Redis
redis-server [–port 6379]
连接Redis
./redis-cli [-h 127.0.0.1 -p 6379]
停止Redis
redis-cli shutdown
kill redis-pid
测试连通性
ping
refs
https://zhuanlan.zhihu.com/p/130923806