Redis笔记
Redis作为一款高效的键值型内存数据库,值得学习。本文梳理学习过程中的笔记。
Redis笔记
钱文品. Redis深度历险:核心原理与应用实践[M]. 北京: 电子工业出版社, 2019.
1 概述
1.1 了解
Redis主要可以用作:
- 缓存:记录点赞数、缓存热帖、用户行为、榜单……;
- 分布式锁
1.2 安装
1 | # ubuntu |
1.3 运行
1 | # redis command-line interface |
2 数据结构
Redis提供5种基础数据结构,分别为:字符串string、列表list、字典hash、集合set、有序集合zset。
2.1 字符串string
Redis所有数据结构都以唯一的key字符串作为名称,以此获得相应的value数据。
Redis string内部数据结构类似Java的ArrayList,预分配冗余空间以免频繁分配内存。当字符串小于1MB时,扩容方法为加倍当前容量;当超过1MB时,每次扩充1MB空间。字符串最大长度为512MB。
2.2 列表list
Redis list类似Java中的LinkedList链表(实际上不完全是),双向链表,插入和删除时间复杂度O(1),查询时间复杂度O(N)。
当list删除最后一个元素时,该数据结构被自动删除,内存回收。
双向链表可以被用来实现队列、栈。
2.2.1 底层实现——ziplist与quicklist
Redis list底层实现是quicklist数据结构。
当list元素较少时,采用ziplist(压缩列表)。ziplist用连续内存将所有的元素连续存储。
当list元素较多时,采用quicklist(快速链表)。quicklist是将链表与ziplist结合的产物,每一个ziplist包含多个元素,却仅需两个前后指针,因此,quicklist避免了为每个元素配备prev/next双指针的空间消耗。quicklist既满足了快速的插入和删除,又避免了产生较大的空间冗余。
2.3 字典hash
Redis hash类似Java中的HashMap,无序字典,存储键值对。
hash采用数组+链表的数据结构,但hash的值只能是字符串。
当hash删除最后一个元素时,该数据结构被自动删除,内存回收。
2.3.1 渐进式rehash
Java的HashMap每次rehash需要一次性全部rehash,而Redis的hash在rehash时,为了避免阻塞服务,采用渐进式rehash。
渐进式rehash在rehash时,保留新旧两个hash结构。旧的hashtable仍可用作查询,同时将旧的hashtable持续rehash到新的hashtable上。等rehash全部完成后,才以新的hashtable取代旧的hashtable。
2.4 集合set
Redis set相当于Java中的HashSet,内部的键值对时无序的、唯一的。
set的底层实现相当于是hash,只不过hash的value村的都是NULL。
当set删除最后一个元素时,该数据结构被自动删除,内存回收。
2.5 有序集合zset
Redis zset类似Java的SortedSet和HashMap的结合体。
zset一方面是set,value元素是唯一的,另一方面其有序性是依靠为value赋予score作为排序权重实现的。
当zset删除最后一个元素时,该数据结构被自动删除,内存回收。
2.5.1 跳跃列表skiplist
zset内部的排序功能采用skiplist实现。
skiplist中,高层(level)链表跨度大,连接比较大的跨度范围。越往底层跨度越小,表示比较小的跨度范围。通过skiplist,可以从大范围缩小到小范围,快速定位插入与查询的位置。
2.6 小结
2.6.1 通用性质
list, set, hash, zset这四种容器数据结构具有两个通用性质:
- create if not exists:操作时,如容器不存在,则新建;
- drop if not elements:操作时,如容器为空,则删除。
2.6.2 过期时间
所有数据结构都可设置过期时间,过期则删除。
字符串设置过期时间后,如果字符串被修改,则过期时间失效。
3 应用
3.1 分布式锁
setnx (set if not exists)指令做锁标记,del删除锁标记。
1 | > setnx lock:resource_a true |
- 事务需要判单自己能够设置对争用资源的分布式锁,才能修改资源。
- expire设置5秒过期时间,防止死锁。
3.2 延时队列
list可以作为异步消息队列。
rpush/lpush操作入队列,lpop/rpop操作出队列。
blpop/brpop可以阻塞式(blocking)地读取数据。
3.3 位图
get/set处理整个位图的内容。
getbit/setbit处理各个位。
bitcount统计范围内1的位数。
bitpos查询第一个0或1的位置。
bitfield,包含get/set/incrby子指令,可以读取、设置和自增指定范围的位。bitfield可以混合多个子指令执行。
3.4 HyperLogLog
统计PV量无需去重,incrby自增就可以。统计UV则需要去重,不是简单的自增,去重常用的set集合在数据量很大时会消耗巨大的内存空间。
HyperLogLog可以实现去重计数问题。
pfadd添加元素(增加对该元素的计数);
pfcount统计元素的计数。
pfmerge用于合并多个pf计数元素为同一个元素,合并pf计数值。
pf指的是HyperLogLog的发明人Philippe Flajolet教授。
HyperLogLog数据结构在计数较小时采用 稀疏矩阵存储,在计数超过阈值时,转变为稠密矩阵。
HyperLogLog占据12KB存储空间,在数据量很大时,比set小了太多。
HyperLogLog的原理是调整低位连续零位的最大长度K,若K越大,概率越低,则说明计数N越大,由此通过有限的连续零位K来估算计数N,K与N存在线性相关性。占用12KB则是因为Redis的HyperLogLog实现采用\(2^{14}=16384\)个桶,每个桶maxbit为6 bit,因此\(2^{14} \times 6bit \div 8bit/byte = 12KB\)。
3.5 布隆过滤器(Bloom Filter)
RedisBloom: Probabilistic Data Structures for Redis
The RedisBloom module provides four data structures: a scalable Bloom filter, a cuckoo filter, a count-min sketch, and a top-k. These data structures trade perfect accuracy for extreme memory efficiency, so they're especially useful for big data and streaming applications.
bf.add添加元素;
bf.exists检查元素是否存在。
bf.madd添加多个元素;
bf.mexists检查多个元素是否存在。(返回分别表示每个元素存在性的0/1)
bf.reverse在添加元素之前预设布隆过滤器的key, error_rate和initial_size。
布隆过滤器:
- 添加时计算元素k个哈希,将对应的k个bit置为1;
- 检查存在时计算元素k个哈希,检查对应的k个bits是否都为1,如果是,则元素存在,否则不存在。
对hash函数数量k,布隆过滤器bit数量m,预计元素数量n,错误率f,有公式: \[ k = \ln2 \times (m/n) = 0.7 \times (m / n) \\ f = 2^{-k} = 0.6185^{m/n} \]
- 此时错误率最低。
3.6 简单限流
以zset的score范围来划定滑动窗口。score存储timestamp,这样就可以计算得出时间窗口内的元素数量,判断访问计数是否超限。
zset不适合数量很大的限流,例如:60秒内限流100万次,100万个元素的zset会占用过大的空间。
3.7 漏斗限流
A Redis module that provides rate limiting in Redis as a single command. Implements the fairly sophisticated generic cell rate algorithm (GCRA) which provides a rolling time window and doesn't depend on a background drip process.
漏斗(funnel)容量有限,不满时可以装入液体,漏斗满时无法装入液体,需要等漏斗内的液体慢慢流走一部分,才能继续装入。
1 | CL.THROTTLE user123 15 30 60 1 |
3.8 GeoHash
通过GeoHash功能,可以快速找出指定经纬度周围的元素。
GeoHash将二维平面处理成网格,然后不断地行、列二分,对二维坐标进行编码,映射为一维整数。
Redis中,GeoHash将经纬度编码为52位整数,存入zset中,score是经纬度编码整数(zset的浮点数score可以无损存储52位整数),value是元素值。在zset中,借助skiplist来找出元素附近范围的其他元素是很容易的事情。使用坐标时,将编码整数解码还原为坐标即可。
geoadd添加经纬度坐标;
geodist计算元素之间的距离;
geopos读取元素的坐标;
geohash读取元素的经纬度编码字符串(base32编码的坐标值)。
georadiusbymember查询指定元素附近的其他元素。
注意:集群中,单个key下存储的坐标数量不宜过多(超过1MB),避免集群迁移出现卡顿。或者干脆采用独立实例,不做集群。
3.9 scan
keys列出符合pattern的key,采用遍历算法,时间复杂度O(N)。
scan从指定cursor开始,匹配pattern,扫描count个槽位。相较于keys,scan可以避免每次遍历整个redis内存槽。
Redis本身就相当于是一个很大的HashMap。scan的遍历顺序采用高位进位加法,以此避免字典扩容和缩容时重复或遗漏遍历槽位。
zscan遍历zset元素;
sscan遍历set元素;
hscan遍历hash元素。
3.10 避免bigkey
在业务开发中,避免大key的产生。
大key数据不论是在集群迁移时,还是在容器需要扩容时,哪怕是在回收时(因较大内存空间的分配和回收),都容易造成卡顿。
可以采用--bigkeys
选项来检索大key。
1 | redis-cli --bigkeys |
4 原理
4.1 I/O模型
Redis是单线程程序。
Redis通过非阻塞I/O多路复用技术来提高单线程I/O处理效率。
对于每一个客户端socket连接,Redis为其关联:
- 一个指令队列,用于从客户端socket连接中读取指令。指令队列中的指令遵循FCFS;
- 一个响应队列,用于向客户端socket连接中写入指令。如果响应队列为空,说明暂无响应数据,则将该响应队列移出多路复用的write_fds以节省select代价。
对于定时任务,Redis采用最小堆进行管理:
- 最临近的任务放在堆顶;
- 取堆顶任务的距离时间作为select操作的timeout,这样在这段时间内就可以放心地select,不必担心错过定时任务。
4.2 通信协议
RESP (Redis Serialization Protocol)是Redis采用地通信协议,这是一种文本协议,实现简单,解析性能好。
RESP把数据分为5种最小单元类型,制定规则:
- 单行字符串,以
+
开头; - 多行字符串,以
$<len>
开头; - 整数,以
:
开头; - 错误消息,以
-
开头; - 数组,以
*<len>
开头。
4.3 持久化
4.3.1 快照
Redis采用fork机制创建子进程来导出快照。
内存空间采用COW机制,因此,父进程照常处理事务,修改的数据会记录在新的空间中,而子进程看到的仍然是fork时的内存数据,不用担心导出时数据又被更新的情况。
4.3.2 AOF日志
AOF日志记录Redis实例创建以来所有的修改性指令序列。
Redis收到客户端修改指令后,进行检查和处理,如果指令执行成功,则立即将该指令文本存储到AOF日志中。
AOF重写:长时间修改会积累大量的AOF日志,Redis可以开辟一个子进程遍历生成新的AOF指令日志,替代旧的AOF日志,起到日志瘦身的效果。(对同一个key频繁修改,会产生大量AOF日志,但实际上存一项就可以了。)
fsync:Redis定期调用fsync确保AOF日志实实在在写入磁盘,避免突然断电造成内存缓冲数据丢失。
混合持久化:快照 + AOF日志(增量)。提高重启效率,避免重做全部的AOF日志操作。
4.4 管道
Redis客户端重排指令。将读指令连续归在一起,写指令连续归在一起。这样客户端只需要向操作系统网络写缓冲区写一次,读缓冲区读一次即可,服务器端同理。节省了网络读写的次数。
4.5 事务
Redis可以实现begin, commit和rollback的事务功能。
4.6 PubSub
PubSub, Publisher Subscriber.
消息多播,一个Publisher可以向多个Subscriber提供消息。
Subscriber需要先订阅若干个channel,随后,Publisher向channel中发布数据,Redis会将数据提供给订阅该channel的所有Subscriber。
但是,如果subscriber掉线了,过后再上线,就不会再收到掉线时错过的消息了。Redis宕机时,就相当于时没有任何subscriber的情况,会造成所有的消息都被直接丢失的情况。
Redis在5.0版本开始提供新的Stream数据结构,实现了持久化的消息队列。
4.7 节省空间
4.7.1 32bit vs 64bit
32bit编译的Redis比64bit编译的版本节省一半的指针内存消耗。如使用内存不超过4GB,采用32bit即可。
4.7.2 基于ziplist的小对象压缩存储
相较于传统的链表,每个entry作为一个节点,都需要配备prev/next两个指针,ziplist则将多个entry以数组的形式存为一个节点,减少所需的指针空间。
每个ziplits节点存储:
- zlbytes,4字节,ziplist占用的字节数;
- zltail,4字节,最后一个entry的偏移地址;
- zllen,2字节,ziplist的entry数量;
- entry数组,存储若干entry;
- zlend,1字节,幻数255标记结尾。
4.7.3 基于intset的紧凑整数数组
inset数据结构包含:
- encoding,表示value的位宽;
- length,表示元素的个数;
- value数组,存储若干value。
若整数用uint16表示即可,intset就用uint16;需要升级到uint32或uint64时再动态升级。
4.7.4 内存回收机制
删除key时,内存不会立即全部回收释放交给操作系统,而是会预留部分内存给未来的使用需求。
4.7.5 内存分配算法
Redis有多种内存分配算法:
- jemalloc,facebook;
- tcmalloc,google。
Redis默认使用jemalloc,该库性能稍好。
通过info memory
可以查到当前使用的内存分配库。
5 集群
多个Redis节点组成Redis集群。
5.1 Redis集群与CAP定理
5.1.1 CAP定理
CAP定理指的是分布式系统的一致性(Consistency)、可用性(Availability)和分区容忍性(Partition tolerance)不能三者兼得,最多只能满足两项。
当网络异常时,分布式节点之间无法连接,形成网络分区现象,如果要容忍分区情况,此时有两种选择:
- 保证可用性:即允许对每个节点的读和写,这样一来,节点之间就会因为无法立即同步而出现数据不一致的问题,放弃了强一致性,即AP;
- 放弃可用性:只允许对每个节点的读,禁止写,这样一来,能保证节点之间的数据一致性,但用户无法更新数据,损失了可用性,即CP[;
也就是说,网络分区发生时,一致性和可用性无法两全。
5.1.2 最终一致性
Redis的主从节点之间异步同步,不能保证严格的强一致性,因此Redis的选择是放弃一致性,转而满足可用性和分区容忍性。
Redis提供的是最终一致性(Eventually consistent),网络断开时,主从节点之间会出现不一致,但网络恢复后,会多策略地尽快同步,最终主从节点保持一致。
5.2 集群同步技术
5.2.1 主从同步与从从同步
主从同步(master-slave sync):主节点与从节点之间同步,主节点把数据复制(replicate)到从节点。
从从同步(slave-slave sync):从节点把数据复制到另一个从节点。
通过引入从从同步,可以降低主节点的同步负担。
5.2.2 增量同步
Redis同步指令流。
Redis主节点把写指令记录在本地的指令缓存(buffer)中,异步地将缓存中地指令同步到从节点,即增量同步。
指令缓存采用的是定长环形数组,因此,如果数组写满了,就会重新从头写入,也就覆盖掉了原有内容。如果网络分区发生时,有节点上产生大量写指令,为了避免指令缓存被覆盖导致写入记录丢失,不能只依赖指令缓存来保存未同步的指令。
5.2.3 快照同步
快照同步:执行bgsave操作,把内存中的数据全部快照存储到硬盘文件中。
增加从节点:增加新的从节点时,通过快照同步为从节点全量加载数据,随后再做增量同步。
快照同步死循环问题:当快照同步太慢,或者指令缓存太小时,就会出现快照同步还没结束,指令缓存就写满的情况。这样一来,指令就不得不直接写入,那快照就过期了,又得重新做一遍快照,而重新做快照可能又太慢,指令缓存又写满了……。为避免死循环,需要设置一个合适的指令缓存大小。
5.2.4 无盘复制
快照同步需要写入磁盘,有不小的文件IO代价。而且Redis执行AOF时需要做fsync,如果此时快照同步,就不得不延后fsync,这样AOF就延后了,指令执行就延后了。
为此,Redis 2.8.18开始支持无盘复制,主节点可以通过socket通信直接把快照发给从节点,避免磁盘上的文件IO代价。
5.2.5 wait指令实现同步复制
Redis的复制本身时异步执行的,因此不具备强一致性。
通过wait指令,可以实现Redis的同步复制,保证系统的(在没有网络分区情况下的)强一致性。
wait可以有限等待,也可以无限等待N个从节点同步完成,再执行后续指令。
如果无限等待时,Redis出现网络分区,那么同步无法完成,就会一直阻塞,导致Redis失去可用性。
5.3 Sentinel:自动主从切换技术
Redis Sentinel集群通常包含3~5个Sentinel节点,保证Sentinel的可用性。
Sentinel集群持续监控主节点和从节点的状态,一旦出现问题,就自动提升一个可用的从节点为主节点,取代故障的不可用的主节点。
Sentinel的具体工作流程:
- Client首先向Sentinel请求主节点的地址;
- Sentinel将最新的主节点地址返回给Client;
- Client访问主节点。
5.4 Codis:中心化集群方案
Codis是Redis集群方案之一,在Codis基础之上,开发出了TiDB。
单个Redis节点如果存储太多数据,会使得快照文件rdb特别大,导致同步起来很耗时,而且全量恢复也变得很慢。
Codis通过把数据分散到众多Redis节点上,来避免每个节点的数据量过大。
Codis对key做哈希,映射到1024个槽位(slots),以此求模,取得数据应该映射到的节点序号。分配完成后,Codis节点会存储槽位与Redis节点的映射关系。
Codis的扩容:可以通过增加Redis节点来扩容集群的容量。
Codis通过mget指令可以从分散的节点上取数据并汇总给用户。
5.5 Cluster:去中心化集群方案
Redis Cluster是去中心化的集群方案,每个节点都是对等的。
Redis Cluster把数据分为16384个槽位(\(2^{14}\)),每个节点负责一部分的槽位。客户端根据key来确定槽位,进而确定目标节点。如果客户端向错误的节点发送请求,该节点会计算key对应的槽位,向客户端发送重定向的响应,告知客户端目标节点。
节点迁移:迁移的最小单位是槽位,流程是从源节点获取内容,然后存到目标节点,最后从源节点中删除内容。
容错:Redis Cluster可以为每个主节点设置若干从节点,自动实现故障时从节点提升为主节点。
可能下线与确定下线:集群节点采用Gossip协议来广播自己的状态。一个节点发现某个节点失联,则进入可能下线(PFail, Possibly Fail)状态。集群中大多数节点都收到该节点失联的消息,则标记该节点为确定下线(Fail)状态。
6 扩展
6.1 Stream:Redis5.0的消息队列
Redis Stream是Redis 5.0中退出的一款新的支持多播的可持久化消息队列,极大地借鉴了Kafka的设计。
Redis Stream通过消息链表将所有加入的消息串起来,每个消息包含唯一ID(timestampInMillis-sequence)和消息内容(形如hash结构的键值对)。
消费组:每个Stream可以挂载多个消费组(Consumer Group),不同消费组互相独立,互不影响,每个消费组都有一个游标last_delivered_id在Stream数组上向前移动,表示当前已经消费到哪条消息了。
消费者:每个消费组中可以包含多个消费者(Consumer),消费者之间为竞争关系,任意一个消费者读取消息都会使消费组的游标last_delivered_id向前移动。
PEL:每个消费者有一个的PEL(Pending Entries List),PEL是一个状态列表pending_ids,记录已经被客户端读取,但尚未收到ACK的消息ID。通过PEL可以确保客户端至少消费了消息1次,而不会在网络传输中途丢失了消息。 客户端重连时,可以根据PEL重新获取一遍接收失败的消息。
分区:Redis没有原生支持分区,分区Stream可以通过在客户端设计哈希策略来实现。Kafka原生支持Partition也是通过客户端的HashStrategy来决定将不同的消息加入不同的分区的。
xgroup create:创建消费组,创建时需要指定从哪个消息ID开始消费。
xadd:加入消息;
xdel:删除消息,只设置标志位,不实际删除消息;
xrange:获取消息列表,自动过滤标记为删除的消息;
xlen:获取消息长度;
del:删除整个消息列表的所有消息。
6.2 info:状态诊断
info指令可查询:
- server:服务器信息;
- clients:客户端信息;
- memory:运行内存统计数据;
- persistence:持久化信息;
- stats:通用统计数据;
- replication:主从复制;
- cpu:cpu使用情况;
- cluster:集群信息;
- keyspace:键值对统计信息。
查询方式如:
Redis内:
1 | > info memory |
Redis外:
1 | redis-cli info memory |
6.3 Redlock分布式锁
Sentinel集群中,主节点挂掉后,从节点取而代之,但主节点的分布式锁没有同步到从节点,新升任主节点的从节点中没有这个分布式锁,就会造成不安全性。
对多个对等的Redis实例,Redlock基于“大多数机制”,加锁时,向过半的节点发送set指令,过半的节点加锁成功,则本次加锁成功;解锁时,向所有节点发送del指令。因为Redlock需要向多个节点进行读写,考虑出错重试、时钟漂移等问题,相对单实例Redis的性能会下降一点。
6.4 过期策略
设置了expire时间的key放在一个独立的字典里。
Redis的过期策略既有定期扫描,也有惰性策略。
定期扫描,Redis默认每秒10次过期扫描,扫描算法为:
- 从过期字典中随机选出20个key;
- 删除这20个key中已经过期的key;
- 如果过期key的比例超过1/4,则重复步骤1.。
为避免循环过度造成线程卡死,默认设置扫描时间上限为25ms。这个25ms的依据是,1秒10次,每次25ms,总共最多占用250ms,即1/4的CPU时间。Redis实际上限制的是CPU时间,避免过期扫描耗费超过1/4的CPU时间。
如果大量key同时过期,Redis就会循环扫描字典,删除key,直到过期字典中的过期key比例变低。当过期的key数量很多的时候,扫描时间是完全可能撞到25ms的上限的。再加上内存回收的代价,就会产生比较多的CPU消耗。如果此时新来的请求设置的超时时间很短,例如10ms,就会导致刚设置数据,就开始扫描,等25ms扫描完,才来得及处理客户端的读取操作时,key早就过期了。客户端就发现自己刚设置的值,立即去修改就会超时过期,实际上是因为Redis的过期策略在间隔中消耗了时间。
为了避免以上问题,一方面,考虑到过期策略扫描耗时,过期时间不宜设置的过短;另一方面,避免大量key同时过期,哪怕对统一的过期时间加上一个随机量也好。
惰性策略:访问key时对key的过期时间进行检查,如果过期了就删除。
从节点不会主动执行过期策略,主节点删除节点并同步del给从节点,从节点收到后写入AOF,跟着主节点照做就是。不过因为同步是异步的,所以主从节点之间强一致性无法保证。
6.5 内存淘汰算法
Redis不允许发生swap,因为会造成性能急剧下降。
当Redis实际内存超过maxmemory时,有几种maxmemory-policy:
- noeviction:可读不可写;
- volatile-lru:淘汰过期集合中最少使用的(LRU);
- volatile-ttl:淘汰过期集合中剩余寿命TTL最小的key;
- volatile-random:淘汰过期集合中随机key;
- allkeys-lru:全体key中淘汰LRU;
- allkeys-random:全体key中淘汰随机的key。
6.6 懒惰删除
del直接删除,通常非常快,但对象非常大时,删除操作会造成单线程卡顿。
Redis 4.0引入的unlink可以解决卡顿问题,unlink卸下待删除对象,然后交给后台线程去异步地回收内存。
Redis
4.0为flushdb和flushall都引入了异步化,加上async选项即可,如:flushall async
。
异步删除借助异步队列实现,MainThread通过submitTask将待删除对象放入ConcurrentQueue,懒惰删除线程LazyFreeThread从中fetchTask并执行异步删除。
Redis的AOF Sync需要将AOF日志同步到磁盘,需要调用sync函数,因为sync比较耗时,因此采用异步线程去调用,该异步线程也有自己的任务队列,存放AOF Sync任务。
Redis在del和flush以外,也会在key过期、LRU淘汰、rename指令执行时回收内存。节点接受全量同步rdb文件后也会清空内存以载入数据。这些删除场景涉及额外的选项:
- slave-lazy-flush:从节点接受rdb文件后的flush操作;
- lazyfree-lazy-eviction:内存达到maxmemory时进行淘汰;
- lazyfree-lazy-expire-key:过期删除;
- lazyfree-lazy-server-del rename:指令删除destKey。
6.7 Jedis
Jedis是Java的Redis开源客户端。
因为Jedis对象不是线程安全的,所以使用Jedis是从Jedis连接池JedisPool中取出一个Jedis对象归该线程独占,用完了再还给连接池。
Jedis默认没有重试机制,网络抖动造成连接断开,再发送指令就会报错。需要手动捕获JedisConnectionException进行重连处理。
6.8 Redis安全
6.8.1 指令安全
rename-command既可以将已有命令更名,也可以更名为空字符串,从而屏蔽该命令被调用。
6.8.2 端口安全
bind指令规定监听的IP地址。
requirepass设置密码访问限制,从节点masterauth设置于主节点同步连接密码。
6.8.3 脚本安全
避免UGC的Lua脚本。
避免以root权限启动Redis。
6.8.4 SSL代理
使用SSH保护Redis连接。
使用官方推荐的spiped工具对SSH通道进行二次加密。spiped是一款加密代理软件。