Redis 是一个开源的,基于内存的数据存储,用作db,cache和message broker(PUB/SUB message system)。
# Redis是单线程的
”单线程-多路复用IO模型”来实现高性能的内存数据服务的,这种机制避免了使用锁。
CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器内存的大小或者网络带宽。既然单线程容易实现,而且CPU不会成为瓶颈,那就顺理成章地采用单线程的方案了。
# Redis快的原因
1、完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1);
2、数据结构简单,对数据操作也简单,Redis中的数据结构是专门进行设计的;
3、采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;
4、使用多路I/O复用模型,非阻塞IO;
5、使用底层模型不同,它们之间底层实现方式以及与客户端之间通信的应用协议不一样,Redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求;
# Redis 支持LUA scripts。
EVAL/EVALSHA 执行脚本
EVALSHA 命令根据给定的 sha1 校验码,执行缓存在服务器中的脚本。将脚本缓存到服务器的操作可以通过 SCRIPT LOAD 命令进行。
Redis可以缓存脚本,将params以附加参数形式传递,有利于redis缓存script模板。
# Redis插入大量数据,使用pipe mode,将各命令转码后导入
cat data.txt | redis-cli --pipe
# Redis事务
即使事务中的某些任务失败,其他命令仍可能继续执行。
不支持回滚。
WATCH 命令可以为 Redis 事务提供 check-and-set (CAS)行为。
CAS实现了乐观锁
被 WATCH 的键会被监视,并会发觉这些键是否被改动过了。 如果有至少一个被监视的键在 EXEC 执行之前被修改了, 那么整个事务都会被取消, EXEC 返回nil-reply来表示事务已经失败。
# Expire
过期 设置key的过期时间,超过时间后,将会自动删除该key对应的value。
#可以通过Pub/Sub获取keyspace发生的event的通知
# redis作为LRU cache
LRU 最近最少使用, redis实现近似LRU
配置redis.conf
#redis内存大小
maxmemory 100mb
#回收策略 尝试回收最少使用的键
maxmemory-policy allkeys-lru
#采样数量,控制LRU精度
maxmemory-samples 5
# redis可以用来作辅助索引(secondary indexing)
# redis分区
散列分区: 一致性哈希
分区缺点:
涉及多个key的操作通常不被支持,因为多个key可能存储在不同redis实例上。
同时操作多个key,不能使用事务。
分区的粒度是key,所以不能存储一个数据量极大的key。
备份等数据处理复杂
scaling操作复杂
分区的实现:
(1)redis-cluster:是query routing和client side partitioning的一种混合实现。
(客户端随机地请求任意一个redis实例,然后由Redis查询路由(query routing),将正确的Redis节点返回client。client再请求正确的redis 实例)
(2)客户端实现一致哈希算法,确定分区。
使用redis实现分布式锁
单redis实例实现分布式锁的正确方法
获取锁使用命令:
SET resource_name my_random_value NX PX 30000
这个命令仅在不存在key的时候才能被执行成功(NX选项),并且这个key有一个30秒的自动失效时间(PX属性)。这个key的值是“my_random_value”(一个随机值),这个值在所有的客户端必须是唯一的,所有同一key的获取者(竞争者)这个值都不能一样。 value的值必须是随机数主要是为了更安全的释放锁,释放锁的时候使用脚本告诉Redis:只有key存在并且存储的值和我指定的值一样才能告诉我删除成功。可以通过以下Lua脚本实现: if redis.call("get",KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1]) else return 0 end
使用这种方式释放锁可以避免删除别的客户端获取成功的锁。(lua脚本相当于事务,redis单线程顺序执行)
举个例子:客户端A取得资源锁,但是紧接着被一个其他操作阻塞了,当客户端A运行完毕其他操作后要释放锁时,原来的锁早已超时并且被Redis自动释放,并且在这期间资源锁又被客户端B再次获取到。如果仅使用DEL命令将key删除,那么这种情况就会把客户端B的锁给删除掉。使用Lua脚本就不会存在这种情况,因为脚本仅会删除value等于客户端A的value的key(value相当于客户端的一个签名,乐观锁,CAS)。
这个随机字符串应该怎么设置?我认为它应该是从/dev/urandom产生的一个20字节随机数,但是我想你可以找到比这种方法代价更小的方法,只要这个数在你的任务中是唯一的就行。例如一种安全可行的方法是使用/dev/urandom作为RC4的种子和源产生一个伪随机流;一种更简单的方法是把以毫秒为单位的unix时间和客户端ID拼接起来,理论上不是完全安全,但是在多数情况下可以满足需求. key的失效时间,被称作“锁定有效期”。它不仅是key自动失效时间,而且还是一个客户端持有锁多长时间后可以被另外一个客户端重新获得。
截至到目前,我们已经有较好的方法获取锁和释放锁。基于Redis单实例,假设这个单实例总是可用,这种方法已经足够安全。
分布式redis集群锁:redlock算法
# 常用数据类型:
strings: binary safe
lists: 一系列有序的元素 (linked list)
阻塞操作:BRPOP,LPOP
hashs:一个string类型的field和value的映射表。hash类型特别适合用于存储对象
sets:一系列无序的不重复元素
sorted set:一系列无序的不重复元素,每个元素关联一个float型的score,用于排序
HyperLogLogs: 接受多个元素作为输入,并给出输入元素的基数估算值
计算基数所需的空间总是固定的、并且是很小的。
stream
# 优化:
使用pipelining,减少RTT
读写节点分离
内存优化:
为较小的聚合数据类型(hashes,lists,sets,sorted sets)使用特殊编码。通过redis.conf配置
使用32bit的redis(redis内存将被限制在4G以下),对于每一个key,使用的内存更少
使用位和字节级别的操作(用最小数据类型表示数据,比如性别中的男女映射为0,1进行存储)
尽可能使用散列表(hashes)存储对象类型数据,而不是对该对象的每一个field进行key-field存储。
内存分配(基于可能会用的最大内存来指定redis的最大内存)
# Redis持久化
RDB 指定的时间间隔能对你的数据进行快照存储.
AOF(Append of file)录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据.
RDB优点:数据量小,恢复方便。 缺点:如果在两次备份时间间隔中意外停止,可能丢失部分数据。
AOF优点:更健壮,可以设置记录间隔,不易丢失数据。缺点:文件数据量,恢复慢。
# 安全
配置密码
内网访问
命令过滤
#复制
redis 异步非阻塞主从复制
复制过程:
mster持有一个replication ID(标记一个给定的数据集)和offset(复制偏移量)
slave可以根据其自身记录的replication ID和offset进行增量更新或全量更新
redis保存两个replication ID(master ID 和secondary ID【旧的master ID】),用于在slave升级为master后,其他slave可以根据secondary ID进行增量更新而不是全量更新
全量更新细节:
master 开启一个后台保存进程,以便于生产一个 RDB 文件。同时它开始缓冲所有从客户端接收到的新的写入命令。当后台保存完成时, master 将数据集文件传输给 slave, slave将之保存在磁盘上,然后加载文件到内存。
复制中的key过期问题
master让key过期,生成del 命令并传输到所有slave
# 高可用性(HA)
Redis 通过 Redis哨兵(Sentinel)和集群(Cluster)提供高可用性(High Availability)
Redis Sentinel
Sentinel提供对redis master/slave的监控,提醒,自动故障迁移功能,配置提供者(供新加入的redis instance发现master)
Sentinel定时检查(PING)master/slave的状态,用多数投票的方式标记节点是否可用,是否进行故障转移,选举新的master节点。
最少配置3各sentinel节点以保证安全性,避免brain split。
master 选举策略: raft leader election
Redis cluster
数据分布式存储在不同节点
整个集群的部分节点失败或者不可达的情况下能够继续处理命令。
分片:
Redis cluster使用hash slot(哈希槽)的概念,cluster有16384个槽,通过hash分布在不同的redis server上。
使用主从复制模型
Redis不能保证强一致性