集群
一致哈希
构建一个存储数据集群的关键是有一个有效的数据存储和复制机制。我希望通过一个行之有效的方法来说明建造一个数据集群,在这个过程中你可以随意添加或移除一个 Redis 节点,同时保证你的数据仍然存在,而不会消失。这个方法称为一致哈希
应用
- String:缓存、限流、计数器、分布式锁、分布式 Session
- Hash:存储用户信息、用户主页访问量、组合查询
- List:微博关注人时间轴列表、简单队列
- Set:赞、踩、标签、好友关系
- Zset:排行榜
IO 多路复用
I/O 多路复用技术,是为了解决进程或线程阻塞到某个 I/O 系统调用而出现的技术,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪,就是这个文件描述符进行读写操作之前),能够通知程序进行相应的读写操作。
在 Redis 中一个字符串最大的容量为 512MB
对于 Hash 结构存储,由于 Hash 结构会在单个 Hash 元素在不足一定数量时进行压缩存储,所以可以大量节约内存。这一点在 String 结构里是不存在的。
数据一致性
真正意义上来讲数据库的数据和缓存的数据是不可能一致的,数据分为最终一致和强一致两类。如果业务中对数据的要求必须强一致那么就不能使用缓存。缓存能做的只能保证数据的最终一致性。
我们能做的只能是尽可能地保证数据的一致性。不管是先删库再删缓存还是先删缓存再删库,都可能出现数据不一致的情况,因为读和写操作是并发的,
Redis 的过期和内存淘汰
Redis 存储数据时我们可以设置他的过期时间。
Redis 过期删除采用的是定期删除,默认是每 100ms 检测一次,遇到过期的 Key 则进行删除,这里的检测并不是顺序检测,而是随机检测。
那这样会不会有漏网之鱼?显然 Redis 也考虑到了这一点,当我们去读/写一个已经过期的 Key 时,会触发 Redis 的惰性删除策略,直接回干掉过期的 Key。
内存淘汰是指用户存储的一部分 Key 是可以被 Redis 自动的删除,从而会出现从缓存中查不到数据的情况。加入我们的服务器内存为 2G、但是随着业务的发展缓存的数据已经超过 2G 了。
但是这并不影响我们程序的运行,因为操作系统的可见内存并不受物理内存的限制。物理内存不够用没关系,计算机会从硬盘中划出一片空间来作为虚拟内存。这就是 Redis 设计两种应用场景的初衷:缓存、持久存储。
缓存击穿
解决方案:
1、后台设置定时任务,主动地去更新缓存数据。这种方案容易理解,但是当 Key 比较分散的时候,操作起来还是比较复杂的。
2、分级缓存。比如设置两层缓存保护层,1 级缓存失效时间短,2 级缓存失效时间长。有请求过来优先从 1 级缓存中去查找,如果在 1 级缓存中没有找到相应数据,则对该线程进行加锁,这个线程再从数据库中取到数据,更新至 1 级和 2 级缓存。其他线程则直接从 2 级线程中获取。
3、提供一个拦截机制,内部维护一系列合法的 Key 值。当请求的 Key 不合法时,直接返回。
缓存雪崩
如何避免雪崩:
1、给缓存加上一定区间内的随机生效时间,不同的 Key 设置不同的失效时间,避免同一时间集体失效。
2、和缓存击穿解决方案类似,做二级缓存,原始缓存失效时从拷贝缓存中读取数据。
3、利用加锁或者队列方式避免过多请求同时对服务器进行读写操作。
性能
Redis 的性能极高,读的速度是 110000 次/s,写的速度是 81000 次/s,支持事务,支持备份,丰富的数据类型。
任何事情都是两面性,Redis 也是有缺点的:
1、由于是内存数据库,所以单台机器存储的数据量是有限的,需要开发者提前预估,需要及时删除不需要的数据。
2、当修改 Redis 的数据之后需要将持久化到硬盘的数据重新加入到内容中,时间比较久,这个时候 Redis 是无法正常运行的。
运维
生产环境禁用命令
keys
1 | 禁止使用Keys正则匹配 |
1、redis 是单线程的,其所有操作都是原子的,不会因并发产生数据异常;
2、使用高耗时的 Redis 命令是很危险的,会占用唯一的一个线程的大量处理时间,导致所有的请求都被拖慢。(例如时间复杂度为 O(N)的 KEYS 命令,严格禁止在生产环境中使用);
- 运维人员进行 keys *操作,该操作比较耗时,又因为 redis 是单线程的,所以 redis 被锁住;
- 此时 QPS 比较高,又来了几万个对 redis 的读写请求,因为 redis 被锁住,所以全部 Hang 在那;
- 因为太多线程 Hang 在那,CPU 严重飙升,造成 redis 所在的服务器宕机;
- 所有的线程在 redis 那取不到数据,一瞬间全去数据库取数据,数据库就宕机了;
flushdb flushall config
1 | flushdb 清空当前数据库的所有的key |
禁用命令
1 | rename-command FLUSHALL "" |
对于 FLUSHALL 命令,需要设置配置文件中 appendonly no,否则服务器是无法启动。
时间复杂度高于 O(N)的命令: hgetall、lrange、smembers、zrange、sinter 等,它们并非不能使用,但这些命令的时间复杂度都为 O(N),使用这些命令需要明确 N 的值,否则也会出现缓存宕机。
Redis2.8 版本以后有了一个新命令 scan,可以用来分批次扫描 redis 记录,这样肯定会导致整个查询消耗的总时间变大,但不会影响 redis 服务卡顿,影响服务使用。
分层架构设计,有一条准则:站点层、服务层要做到无数据无状态,这样才能任意的加节点水平扩展,数据和状态尽量存储到后端的数据存储服务,例如数据库服务或者缓存服务。
Redis or Memcached
value 是哈希,列表,集合,有序集合这类复杂的数据结构时,会选择 redis,因为 mc 无法满足这些需求,最典型的场景,用户订单列表,用户消息,帖子评论列表等。
Redis 支持持久化
千万不要把 redis 当作数据库用:
(1)redis 的定期快照不能保证数据不丢失
(2)redis 的 AOF 会降低效率,并且不能支持太大的数据量
缓存场景,开启固化功能,有什么利弊?
如果只是缓存场景,数据存放在数据库,缓存在 redis,此时如果开启固化功能:
优点是,redis 挂了再重启,内存里能够快速恢复热数据,不会瞬时将压力压到数据库上,没有一个 cache 预热的过程。
缺点是,在 redis 挂了的过程中,如果数据库中有数据的修改,可能导致 redis 重启后,数据库与 redis 的数据不一致。
redis 天然支持集群功能,可以实现主动复制,读写分离。
redis 官方也提供了 sentinel 集群管理工具,能够实现主从服务监控,故障自动转移,这一切,对于客户端都是透明的,无需程序改动,也无需人工介入。
memcache 的 value 存储,最大为 1M,如果存储的 value 很大,只能使用 redis
什么时候倾向于 memcache?
纯 KV,数据量非常大,并发量非常大的业务,使用 memcache 或许更适合。
内存分配
memcache 使用预分配内存池的方式管理内存,能够省去内存分配时间。Redis 则是临时申请空间,可能导致碎片。mc 会更快一些。
虚拟内存使用
memcache 把所有的数据存储在物理内存里。redis 有自己的 VM 机制,理论上能够存储比物理内存更多的数据,当数据超量时,会引发 swap,把冷数据刷到磁盘上。数据量大时,mc 会更快一些。
网络模型
memcache 使用非阻塞 IO 复用模型,redis 也是使用非阻塞 IO 复用模型。但由于 redis 还提供一些非 KV 存储之外的排序,聚合功能,在执行这些功能时,复杂的 CPU 计算,会阻塞整个 IO 调度。
由于 redis 提供的功能较多,mc 会更快一些。
线程模型
memcache 使用多线程,主线程监听,worker 子线程接受请求,执行读写,这个过程中,可能存在锁冲突。redis 使用单线程,虽无锁冲突,但难以利用多核的特性提升整体吞吐量。
从这一点上,mc 会快一些。
画外音:理论上,mc 只支持 kv,而 redis 支持了这么多功能,mc 性能应该高非常多非常多,但实际并非如此,真的可能和代码质量有关。
水平扩展的支持
不管是 mc 和 redis,服务端集群没有天然支持水平扩展,需要在客户端进行分片,这其实对调用方并不友好。如果能服务端集群能够支持水平扩展,会更完美一些。