缓存那些事
1. 缓存
1.1 缓存特征
-
命中率
:命中率=返回正确结果数/请求缓存次数。命中率越高,表明缓存的使用率越高。 -
最大元素(或最大空间)
:缓存中可以存放的最大元素的数量,超过将会触发缓存启动清空策略。 -
清空策略
:当缓存空间被用满时,在稳定服务的同时有效提升命中率
1.2 常见的清空策略
-
FIFO(first in first out)
:先进先出策略 -
LFU(less frequently used)
:最少使用策略 -
LRU(least recently used)
:最近最少使用策略
1.3 缓存介质
-
内存
:最快,但是无持久化 -
硬盘
:持久化 -
数据库
:NoSQL数据库
1.4 缓存分类和应用场景
-
本地缓存
:本地缓存就是在进程的内存中进行缓存。优点:应用和cache是在同一个进程内部。缺点也是应为缓存跟应用程序耦合 -
分布式缓存
:分布式缓存一般都具有良好的水平扩展能力,对较大数据量的场景也能应付自如。 -
多级缓存
:本地缓存存一些频率最高的热点数据,其他的热点数据放在分布式缓存中。
1.5 本地缓存
- 编程直接实现缓存
- Ehcache
- Guava Cache
1.6 分布式缓存
- memcached缓存
- Redis缓存
2. memcached与redis优缺点
- 性能:都比较高
- 操作的便利性
- memcache数据结构单一
- redis丰富一些,数据操作方面,redis更好一些,较少的网络IO次数
- mongodb支持丰富的数据表达,索引,最类似关系型数据库,支持的查询语言非常丰富
- 内存空间的大小和数据量的大小
- redis在2.0版本后增加了自己的VM特性,突破物理内存的限制;可以对key value设置过期时间(类似memcache)
- memcache可以修改最大可用内存,采用LRU算法
- mongoDB适合大数据量的存储,依赖操作系统VM做内存管理,吃内存也比较厉害,服务不要和别的服务在一起
- 可靠性(持久化)
- redis支持(快照、AOF):依赖快照进行持久化,aof增强了可靠性的同时,对性能有所影响
- memcache不支持,通常用在做缓存,提升性能;
- MongoDB从1.8版本开始采用binlog方式支持持久化的可靠性
- 数据一致性(事务支持)
- Memcache 在并发场景下,用cas保证一致性
- redis事务支持比较弱,只能保证事务中的每个操作连续执行
- mongoDB不支持事务
- 数据分析
- mongoDB内置了数据分析的功能(mapreduce),其他不支持
- 应用场景
- redis:数据量较小的更性能操作和运算上
- memcache:用于在动态系统中减少数据库负载,提升性能;做缓存,提高性能(适合读多写少,对于数据量比较大,可以采用sharding)
- MongoDB:主要解决海量数据的访问效率问题
3. redis
3.1 reids 有哪些优缺点
优点:
- 读写性能优异
- 支持数据持久化,支持AOF和RDB两种持久化方式。
- 支持事务,Redis的所有操作都是原子性的,同时Redis还支持对几个操作合并后的原子性执行。
- 数据结构丰富,除了支持string类型的value外还支持hash、set、zset、list等数据结构。
- 支持主从复制
缺点:
- 数据库容量受到物理内存的限制,不能做海量数据的高性能读写
- Redis 不具备自动容错和恢复功能
- 不支持在线扩容
3.2 reids 数据结构
- String:字符类型
- Set name test
- Get name
- setnx name test 不存在则set进去
- Mset 存入多个值
- Msetnx
- Mget
- Getset 获取并存入新的值
- Setrange 从第几个字符替换
- Getrange
- Append 追加
- Incr 数值加一
- Incrby 数值加指定
- Decr 与incr相反
- Decrby 与decrby相反
- Strlen 返回键对应的值得字符长度
- Hash 方便存对象 键值对
- Hset
- Hsetnx
- Hget
- Hmset
- Hmget
- Hincrby
- Hexists
- Hlen:返回hash表中所有字段的数目
- Hkeys
- Hvals
- Hgetall
- Hdel
- List链表(双向链表)
- lpush 从头压入
- rpush 从尾部压入
- lpop 从链表的头部弹出一个元素
- rpop 从链表的尾部弹出一个元素
- Linsert 从某个数据之前或者之后插入数据
- Lset 通过索引来设置元素的值
- lrem 删除list表中的数据
- ltrim 对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除。
- rpoplpush 从一个链表弹出,在从头部压入到另一个链表
- lindex 返回一个list小标的索引值
llen 返回这个链表的元素的长度
- set 无序集合
- sadd 向集合中插入一条数据
- srem 删除集合中的一个元素
- smembers 查看集合中的元素
- spop 从集合随机弹出一个元素,返回键值
- sdiff 两个集合的差集 返回两个集合不一样的,根据第一个集合为标准
- sdiffstroe 将两个差集存储到另外一个集合
SortedSet 有序集合
HyperLogLog 做基数统计的算法
Redis 发布订阅
3.3 redis 持久化
持久化就是把内存的数据写到磁盘中去,防止服务宕机了内存数据丢失
- RDB:一定时间内将内存的数据以快照的形式保存到硬盘中
- AOF:将redis每次执行的命令写到单独的日志文件中
优缺点是什么?
AOF文件比RDB更新频率高,优先使用AOF还原数据。
AOF比RDB更安全也更大
RDB性能比AOF好
如果两个都配了优先加载AOF
3.4 reids 过期策略
- 定时过期:每个设置过期时间的key都需要创建一个定时器,到过期时间就会立即清除
- 惰性过期:只有当访问一个key时,才会判断该key是否已过期,过期则清除
- 定期过期:每隔一定的时间,会扫描一定数量的数据库的expires字典中一定数量的key,并清除其中已过期的key。
3.5 内存淘汰策略
Redis的内存淘汰策略的选取并不会影响过期的key的处理。内存淘汰策略用于处理内存不足时的需要申请额外空间的数据;过期策略用于处理过期的缓存数据。
3.6 集群
哨兵 + redis 主从
3.7 缓存异常
- 缓存雪崩
缓存雪崩是指缓存同一时间大面积的失效,所以,后面的请求都会落到数据库上,造成数据库短时间内承受大量请求而崩掉。
解决方案
缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
一般并发量不是特别多的时候,使用最多的解决方案是加锁排队。
给每一个缓存数据增加相应的缓存标记,记录缓存的是否失效,如果缓存标记失效,则更新数据缓存。
- 缓存穿透
缓存穿透是指缓存和数据库中都没有的数据,导致所有的请求都落到数据库上,造成数据库短时间内承受大量请求而崩掉
解决方案
接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截;
从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击
采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的 bitmap 中,一个一定不存在的数据会被这个 bitmap 拦截掉,从而避免了对底层存储系统的查询压力
- 缓存击穿
缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。和缓存雪崩不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。
解决方案
设置热点数据永远不过期。
加互斥锁,互斥锁
- 缓存预热
缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统。这样就可以避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据!
解决方案
直接写个缓存刷新页面,上线时手工操作一下;
数据量不大,可以在项目启动的时候自动进行加载;
定时刷新缓存;