一、缓存
1.1 介绍
缓存就是把常用的数据放到访问速度快的地方。这里的访问速度快是相对的:CPU寄存器 > 内存 > 磁盘 > 网络。对于网络来说磁盘就是缓存,但对于磁盘来说内存就是缓存。
Redis 一般用于 MySQL 缓存,但是内存的容量毕竟小于磁盘,如果都放到内存中那不就满了吗?其实在实际的业务中是符合二八定律的就是 20% 的热点数据可以满足 80% 的场景。注意:缓存一般解决的都是读操作,对于写操作只能去 MySQL 中写。
1.2 更新策略
它可以细分为:定期生成、实时生成。
定期生成:
这个比较简单就是每个一段时间统计出现频率高的数据,然后把数据更新到 Redis 中即可。
实时生成:
当 Redis 命中数据则直接返回,没有命中则从 MySQL 中查找然后添加到 Redis 中然后返回。但是如果一直这样用不了多久 Redis 就会把内存打满,所以就引入了内存淘汰机制具体如下:
- FIFO:淘汰最先来的数据
- LRU:淘汰最长时间没有使用的数据
- LFU:淘汰使用次数最少的数据
- Random:随机淘汰
在 Redis 中是可以配置,Redis 中默认的淘汰策略是当内存不足以容纳新写入数据时,新写入操作会报错。
1.3 缓存预热
当刚启动 Redis 时,由于 Redis 中并没有数据,所以如果有请求来会访问到 MySQL。如果数据量大,会导致 MySQL 扛不住,严重甚至直接宕机。
解决方法也简单,就是可以在离线的状态下先把一批热点数据导入到 Redis 中。当然热点数据也不一定要非常精准,只要能阻止大部分请求访问 MySQL 即可,随着时间的推移热点数据会调整。
1.4 缓存穿透
当数据既不在 Redis 也不在 MySQL 时,就会缓存穿透。一次倒也什么大问题,如果有大量的类似这种数据请求时,会增加 MySQL 压力,严重甚至直接宕机。
出现这种情况一般是:1. 业务上面没有对 key 做出检查;2. 不小心误删了数据;3. 黑客攻击。解决方法:1. 在查询前对 key 做出检查;2. 可以在 Redis 中插入该 key 把 value 设为非法值;3.使用布隆过滤器来判断 key 是否存在。
1.5 缓存雪崩
短时间内,大量的 key 过期导致访问 MySQL 的请求变多,严重甚至直接宕机。
出现这种情况一般是:1. Redis 挂了;2. 短时间大量 key 过期。解决方法:1. 部署高可用的 Redis 集群, 并且完善监控报警体系。
1.6 缓存击穿
它是缓存雪崩的特殊情况,它是热点数据过期导致访问 MySQL 的请求变多,严重甚至直接宕机。
解决方法:1. 基于统计的方式发现热点 key,并设置永不过期。2. 限制同一时间访问数据库的并发数。
二、分布式锁
2.1 介绍
锁相信大家都不陌生,不过之前都是在一台主机上。其实分布式锁很好理解,就是在一台主机创建锁,然后其他主机要修改数据库数据时,从那台主机申请到锁然后修改。大家可能会想事务不是也能做到吗?事务确实可以但是如果数据量太大 MySQL 扛不住,并且存储数据也不一定使用的一定是数据库。
大概过程就是一台主机先在另一个主机上创建一个 key(这个 key 就是锁,创建成功就代表加锁成功,失败则代表加锁失败。用 setnx 命令实现),使用完后把 key 删除即可。这个流程虽然听着很简单,但要考虑的东西很多。
2.2 引入过期时间
如果一个主机加锁成功,假如这个主机断电了或者挂了,那么锁就不能被释放,这就会导致其他机器无法申请到锁。为了解决这种情况就要在申请锁的时候加入过期时间,时间一到则自动被释放。
注意引入过期时间最好使用:set ex nx,不要用 setnx、expire。虽然说第二种也是可以实现但是可能会出现一台机器刚 setnx 成功之后,突然挂了或者 setnx 成功 expire 失败。
2.3 引入检验 id
当 A 主机申请锁成功了,但是 B 主机把 A 主机申请的锁给删了。这种情况一般是误操作。为了解决这种情况可以把申请 key 的 value 设置为主机 id,当要删除时,先判断 id 是否相同相同则在删除。
2.4 引入 lua
因为上面引入 id 后需要先 get 获取 value 值然后 del,这是两个命令在 Redis 中虽然命令是原子的单命令之间并不是。这就可能导致当 A 线程 get 之后准备del时锁过期了,然后 B 线程申请成功了锁,之后 A 线程把执行 del 将 B 线程刚申请的锁给删了。
这时相信大家一定会想到 Redis 事务,让 get、del 操作一起执行。这当然可以但是 Redis 的事务并不保证一定执行成功,所以更好的方法是引入 lua 脚本,在 Redis 执行 lua 脚本的过程是原子的所以一般是让它来代替事务。这个 lua 脚本是在 Redis 中,客户端可以直接调用它。
2.5 引入看门狗
有一种情况是申请锁的时间不够用,如果依然选择到时间释放就会出现问题。所以就在那台机器的后台起一个线程,这个线程被称为看门狗线程,它用于向 Redis 发送延长多少时间。就算机器挂了也没事儿,因为没有人给它续约,时间到了也就释放了。
2.6 引入 Redlock 算法
还有一种情况是 Redis 挂了。大概情况是 A 主机刚加好锁,然后 Redis 挂了,大家可能会想不是有哨兵吗?它挂了还有其他 Redis 存在。确实是但是因为数据同步是需要时间的,所以就有一种情况是没来得及同步就挂了,这样 A 主机以为加上锁了但是其实没加上。
为了解决这个方法 Redis 作者提出了 Redlock 算法,本质上就是冗余。大概是加锁不是只给一个 Redis 发请求,而是给多个 Redis 都发请求(注意这里的 Redis 都是主节点,就是给多个主节点同时发请求)。加锁是设置时间如果超出时间则认为加锁失败,去尝试下一个节点。只要加锁成功的数量大于一半则认为加锁成功,这样即使某个主节点挂了,也不影响。
2.7 总结
上面设计了这么多本质上都是对节点突然挂了的措施,因为是分布式我们需认为每个节点都不可靠。对于之前学习的加锁没这么复杂,是因为是在不行就进程挂了,锁直接释放了。