1.Redis是什么
Redis 是完全开源免费的,遵守 BSD 协议,是一个高性能的 key-value 数据库。
Redis 与其他 key - value 缓存产品有以下三个特点:
- Redis 支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。
- Redis 不仅仅支持简单的 key-value 类型的数据,同时还提供 list,set,zset,hash 等数据结构的存储。
- Redis 支持数据的备份,即 master-slave 模式的数据备份。
2.Redis缓存
使用 redis 或 memcached 之类的称为分布式缓存,在多实例的情况下,各实例共用一份缓存数据,缓存具有一致性。缺点是需要保持 redis 或 memcached服务的高可用,整个程序架构上较为复杂。
3.Redis与memcache对比
- 性能上
Redis:单线程模式处理请求,只使用单核,平均每一个核上Redis在存储小数据时比Memcached性能更高。
Memcached:使用多线程异步 IO 的方式,可以使用多核,而在100k以上的数据中,Memcached性能要高于Redis。 - 内存使用率
MemCached:使用简单的key-value存储,Memcached的内存利用率更高。
Redis:如果采用hash结构来做key-value存储,由于其组合式的压缩,其内存利用率会高于Memcached。 - 内存空间和数据量大小
MemCached:可以修改最大内存,采用LRU算法。Memcached单个key-value大小有限,一个value最大只支持1MB。
Redis:增加了VM的特性,突破了物理内存的限制。Redis单个key-value大小最大支持512MB 。 - 数据类型
MemCached:数据结构单一,仅用来缓存数据。
Redis:支持更加丰富的数据类型,Redis不仅仅支持简单的k/v类型的数据,同时还提供list,set,zset,hash等数据结构的存储。
可以在服务器端直接对数据进行丰富的操作,这样可以减少网络IO次数和数据体积。 - 可靠性
Memcached:只是个内存缓存,对可靠性要求低。MemCached不支持数据持久化,断电或重启后数据消失,但稳定性是有保证的。
Redis:对可靠性要求高,支持数据持久化和数据恢复,允许单点故障,同时也会影响部分性能。支持数据的备份,即master-slave模式的数据备份。Redis支持数据的持久化,可以将内存中的数据保存到磁盘中,重启的时候可以再次加载进行使用。
为什么选择Redis
使用 MC 中的存在许多限制,这些限制在现在的互联网场景下很致命,成为大家选择Redis、MongoDB的重要原因:
key 不能超过 250 个字节;
value 不能超过 1M 字节;
key 的最大失效时间是 30 天;
只支持 K-V 结构,不提供持久化和主从同步功能。
4.Redis特点
与 MC 不同的是,Redis 采用单线程模式处理请求。这样做的原因有 2 个:一个是因为采用了非阻塞的异步事件处理机制;另一个是缓存数据都是内存操作 IO 时间不会太长,单线程可以避免线程上下文切换产生的代价。
Redis 支持持久化,所以 Redis 不仅仅可以用作缓存,也可以用作 NoSQL 数据库。
相比 MC,Redis 还有一个非常大的优势,就是除了 K-V 之外,还支持多种数据格式,例如 list、set、sorted set、hash 等。
Redis 提供主从同步机制,以及 Cluster 集群部署能力,能够提供高可用服务。
5.Redis缓存类型
缓存是高并发场景下提高热点数据访问性能的一个有效手段,在开发项目时会经常使用到。
缓存的类型分为:本地缓存、分布式缓存和多级缓存。
- 本地缓存
本地缓存就是在进程的内存中进行缓存,比如我们的 JVM 堆中,可以用 LRUMap 来实现,也可以使用 Ehcache 这样的工具来实现。
本地缓存是内存访问,没有远程交互开销,性能最好,但是受限于单机容量,一般缓存较小且无法扩展。 - 分布式缓存
分布式缓存一般都具有良好的水平扩展能力,对较大数据量的场景也能应付自如。缺点就是需要进行远程请求,性能不如本地缓存。 - 多级缓存
为了平衡这种情况,实际业务中一般采用多级缓存,本地缓存只保存访问频率最高的部分热点数据,其他的热点数据放在分布式缓存中。
在目前的一线大厂中,这也是最常用的缓存方案,单考单一的缓存方案往往难以撑住很多高并发的场景。
6.Redis淘汰策略
不管是本地缓存还是分布式缓存,为了保证较高性能,都是使用内存来保存数据,由于成本和内存限制,当存储的数据超过缓存容量时,需要对缓存的数据进行剔除。
一般的剔除策略有 FIFO 淘汰最早数据、LRU 剔除最近最少使用、和 LFU 剔除最近使用频率最低的数据几种策略。
- noeviction
返回错误当内存限制达到并且客户端尝试执行会让更多内存被使用的命令(大部分的写入指令,但DEL和几个例外) - allkeys-lru
尝试回收最少使用的键(LRU),使得新添加的数据有空间存放。 - volatile-lru
尝试回收最少使用的键(LRU),但仅限于在过期集合的键,使得新添加的数据有空间存放。 - allkeys-random
回收随机的键使得新添加的数据有空间存放。 - volatile-random
回收随机的键使得新添加的数据有空间存放,但仅限于在过期集合的键。 - volatile-ttl
回收在过期集合的键,并且优先回收存活时间(TTL)较短的键,使得新添加的数据有空间存放。
如果没有键满足回收的前提条件的话,策略volatile-lru, volatile-random以及volatile-ttl就和noeviction 差不多了。
7.Redis缓存问题
缓存穿透
- 产生原因
可能是外部的恶意攻击,例如,对用户信息进行了缓存,但恶意攻击者使用不存在的用户id频繁请求接口,导致查询缓存不命中,然后穿透 DB 查询依然不命中。这时会有大量请求穿透缓存访问到 DB。 - 解决方法
1.对不存在的用户,在缓存中保存一个空对象进行标记,防止相同 ID 再次访问 DB。不过有时这个方法并不能很好解决问题,可能导致缓存中存储大量无用数据。
2.使用 BloomFilter 过滤器,BloomFilter 的特点是存在性检测,如果 BloomFilter 中不存在,那么数据一定不存在;如果 BloomFilter 中存在,实际数据也有可能会不存在。非常适合解决这类的问题。
缓存击穿
- 产生原因
就是某个热点数据失效时,大量针对这个数据的请求会穿透到数据源。 - 解决方法
1.可以使用互斥锁更新,保证同一个进程中针对同一个数据不会并发请求到 DB,减小 DB 压力。
2.使用随机退避方式,失效时随机 sleep 一个很短的时间,再次查询,如果失败再执行更新。
3.针对多个热点 key 同时失效的问题,可以在缓存时使用固定时间加上一个小的随机数,避免大量热点 key 同一时刻失效。
缓存雪崩
-
产生的原因
缓存挂掉,这时所有的请求都会穿透到 DB。
-
解决方法
1.使用快速失败的熔断策略,减少 DB 瞬间压力;
2.使用主从模式和集群模式来尽量保证缓存服务的高可用。
实际场景中,这两种方法会结合使用