Redis场景应用
缓存基础
Redis由于性能高效,通常可以做数据库存储的缓存,比如给MySQL当缓存就是常见的用法。具体而言,就是将MySQL中的热点数据存储在Redis中,通常业务都满足二八原则,即80%的流量集中在20%的数据上,所以缓存是可以有效提高系统的吞吐量。
缓存的四种模式
缓存一般有四种模式:
- Cache-Aside Pattern:旁路缓存
- 可能出现数据不一致问题
- Read Through Pattern:读穿透缓存
- 对业务透明,代码简洁
- 缓存命中时性能较低,多一次服务间调用过程
- Write Through Pattern:写穿透缓存
- Write Behind Pattern:异步缓存写入模式
- 数据不安全,有丢失风险
Cache-Aside Pattern
旁路缓存就是指业务方将缓存当做是数据库的旁路,业务直接和缓存打交道。
旁路缓存读流程:业务方先查看缓存中是否有目标数据,如果没有,则去数据库中查找指定数据。业务方在获取到数据的同时,将数据保存在缓存中,以备后续的查询。
旁路缓存写流程:业务方首先向数据库写入数据,在写入成功之后将缓存中对于的数据删除。这是为了避免由于更新缓存的时序差异导致的数据不一致问题。
Read Through
读穿透缓存模式提供一个数据服务,业务方不再直接和缓存打交道,而是将查询请求发送给数据服务,让数据服务执行查询缓存、数据库的操作。一图蔽之:
Write Through
写穿透缓存模式通常和读穿透模式一同使用。写穿透模式要求数据服务在接受到业务方的写请求时,先将数据写入数据库,接着写入缓存。
由于写穿透模式要求写完数据库后马上写缓存,对缓存的及时性要求高,在高并发的环境下就可能会出现数据不一致问题。另一方面,还需要合理设置缓存中数据的过期时间,因为每一次写入操作都会增加缓存中的数据,不合理的数据过期时间可能会导致缓存中的数据急剧膨胀,占用大量内存资源。
Write Behind
Write Behind和Write Through在执行写操作时都会对数据库和缓存操作。区别在于,Write Behind先写缓存,再异步将数据写入数据库。
Write Behind缓存策略采用异步写数据的方式,可以选择在某个特定的时间点,例如数据库负载较低时,进行写入操作,也可以选择积累一定量的写命令后一次性将数据写入数据库中。
异步写入操作极大降低了写请求的延迟以及数据库的负载,但代价是数据安全性不够高。倘若当部分存储在内存中的数据未写入数据库时,存储服务发生了崩溃,那么数据就丢失了。
分布式锁
分布式锁就是应用在分布式场景下的锁,保证各台机器上的各个进程并发安全的操作某一资源。一个合理的分布式锁应具备如下特点:
- 互斥性:针对某一资源,同一时刻只能有一个竞争者持有锁,其他竞争者则阻塞
- 抗死锁性(活性):当一个竞争者持有锁的期间,即使发生了异常导致程序退出,无法正常释放锁,也能有兜底策略将锁释放,避免发生死锁
- 对称性:对同一资源上锁和解锁必须是同一个竞争者
- 可靠性:具备一定的容灾能力
实现
从分布式锁的特点可以推测出,使用Redis实现分布式锁大致流程应该是:
- 对某一个资源尝试加锁
- 加锁并且标记
owner
- 处理业务
- 检查锁的
owner
- 删除锁
为什么要标记
owner
?
如果不标记锁的owner
,可能会出现竞争者A将竞争者B的锁释放,🌰:假设A率先拿到分布式锁,接着进行业务流程。倘若此时A由于网络延迟或CPU占用率高等因素导致在分布式锁的过期时,业务仍没有完成。那么B以为分布式锁没有被其他竞争者占用,顺理成章地拿到了分布式锁,执行业务逻辑。不幸的是,在B执行业务过程中,A终于完成了业务,然后执行delete
删除分布式锁的动作。但此时A删除的并非是自己,而是B的分布式锁。那么该分布式锁就失去了保证资源并发安全的意义。
如何保证检查-删除的原子性?
4、5步骤是典型的check-do something场景,需要保证原子性,否则会出现并发问题。
要想实现Redis某些操作组合的原子性,Lua脚本是不二法门。Redis+Lua,可以说是专门为解决原子问题而生的。有了Lua的特性,Redis才真正在分布式锁、秒杀等场景有了用武之地,最终Redis实现分布式锁的流程如下: