专业网站建设服务包括哪些,张伟专业团队,怎么做网站开发,长沙 服务文章目录 概述示例 缓存双写一致性缓存按照操作来分#xff0c;细分2种读写缓存#xff1a;同步直写策略读写缓存#xff1a;异步缓写策略双检加锁策略 数据库和缓存一致性更新策略先更新数据库#xff0c;再更新缓存先更新缓存#xff0c;再更新数据库先删除缓存#xf… 文章目录 概述示例 缓存双写一致性缓存按照操作来分细分2种读写缓存同步直写策略读写缓存异步缓写策略双检加锁策略  数据库和缓存一致性更新策略先更新数据库再更新缓存先更新缓存再更新数据库先删除缓存再更新数据库解决方案延时双删策略 先更新数据库再删除缓存解决方案总结 问题示例 概述 
示例 缓存双写一致性 如果redis中有数据 需要和数据库中的值相同如果redis中无数据 数据库中的值要是最新值且准备回写redis 缓存按照操作来分细分2种 
只读缓存读写缓存 
读写缓存同步直写策略 
写数据库后也同步写redis缓存缓存和数据库中的数据一致;对于读写缓存来说要想保证缓存和数据库中的数据一致就要采用同步直写策略 
读写缓存异步缓写策略 
正常业务运行中mysql数据变动了但是可以在业务上容许出现一定时间后才作用于redis比如仓库、物流系统异常情况出现不得不将失败的动作重新修补有可能需要借助kafka或者RabbitMQ等消息中间件实现重试重写 
双检加锁策略 多个线程同时去查询数据库的这条数据那么我们可以在第一个查询数据的请求上使用一个 互斥锁来锁住它。 其他的线程走到这一步拿不到锁就等着等第一个线程查询到了数据然后做缓存。 后面的线程进来发现已经有缓存了就直接走缓存。 import com.atguigu.redis.entities.User;
import com.atguigu.redis.mapper.UserMapper;
import io.swagger.models.auth.In;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.PathVariable;import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;Service
Slf4j
public class UserService {public static final String CACHE_KEY_USER  user:;Resourceprivate UserMapper userMapper;Resourceprivate RedisTemplate redisTemplate;/*** 业务逻辑没有写错对于小厂中厂(QPS《1000)可以使用但是大厂不行* param id* return*/public User findUserById(Integer id){User user  null;String key  CACHE_KEY_USERid;//1 先从redis里面查询如果有直接返回结果如果没有再去查询mysqluser  (User) redisTemplate.opsForValue().get(key);if(user  null){//2 redis里面无继续查询mysqluser  userMapper.selectByPrimaryKey(id);if(user  null){//3.1 redismysql 都无数据//你具体细化防止多次穿透我们业务规定记录下导致穿透的这个key回写redisreturn user;}else{//3.2 mysql有需要将数据写回redis保证下一次的缓存命中率redisTemplate.opsForValue().set(key,user);}}return user;}/*** 加强补充避免突然key失效了打爆mysql做一下预防尽量不出现击穿的情况。* param id* return*/public User findUserById2(Integer id){User user  null;String key  CACHE_KEY_USERid;//1 先从redis里面查询如果有直接返回结果如果没有再去查询mysql// 第1次查询redis加锁前user  (User) redisTemplate.opsForValue().get(key);if(user  null) {//2 大厂用对于高QPS的优化进来就先加锁保证一个请求操作让外面的redis等待一下避免击穿mysqlsynchronized (UserService.class){//第2次查询redis加锁后user  (User) redisTemplate.opsForValue().get(key);//3 二次查redis还是null可以去查mysql了(mysql默认有数据)if (user  null) {//4 查询mysql拿数据(mysql默认有数据)user  userMapper.selectByPrimaryKey(id);if (user  null) {return null;}else{redisTemplate.opsForValue().setIfAbsent(key,user,7L,TimeUnit.DAYS);}}}}return user;}}数据库和缓存一致性更新策略 给缓存设置过期时间定期清理缓存并回写是保证最终一致性的解决方案。 我们可以对存入缓存的数据设置过期时间所有的写操作以数据库为准对缓存操作只是尽最大努力即可。也就是说如果数据库写成功缓存更新失败那么只要到达过期时间则后面的读请求自然会从数据库中读取新值然后回填缓存达到一致性切记要以mysql的数据库写入库为准。 先更新数据库再更新缓存 
异常问题 1 先更新mysql的某商品的库存当前商品的库存是100更新为99个。 2 先更新mysql修改为99成功然后更新redis。 3 此时假设异常出现更新redis失败了这导致mysql里面的库存是99而redis里面的还是100 。 4 上述发生会让数据库里面和缓存redis里面数据不一致读到redis脏数据 
异常问题 
【先更新数据库再更新缓存】A、B两个线程发起调用【正常逻辑】1 A update mysql 1002 A update redis 1003 B update mysql 804 B update redis 80
【异常逻辑】多线程环境下A、B两个线程有快有慢有前有后有并行1 A update mysql 1003 B update mysql 804 B update redis 802 A update redis 100最终结果mysql和redis数据不一致o(╥﹏╥)omysql80,redis100先更新缓存再更新数据库 
mysql一般作为底单数据库保证最后解释 
【先更新缓存再更新数据库】A、B两个线程发起调用【正常逻辑】1 A update redis 1002 A update mysql 1003 B update redis 804 B update mysql 80
【异常逻辑】多线程环境下A、B两个线程有快有慢有并行A update redis  100B update redis  80B update mysql 80A update mysql 100
----mysql100,redis80先删除缓存再更新数据库 
异常问题 
1请求A进行写操作删除redis缓存后工作正在进行中更新mysql......A还么有彻底更新完mysql还没commit2请求B开工查询查询redis发现缓存不存在(被A从redis中删除了)3请求B继续去数据库查询得到了mysql中的旧值(A还没有更新完)4请求B将旧值写回redis缓存5请求A将新值写入mysql数据库 上述情况就会导致不一致的情形出现。 时间线程A线程B出现的问题t1请求A进行写操作删除缓存成功后工作正在mysql进行中…t21 缓存中读取不到立刻读mysql由于A还没有对mysql更新完读到的是旧值 2 还把从mysql读取的旧值写回了redis1 A还没有更新完mysql导致B读到了旧值 2 线程B遵守回写机制把旧值写回redis导致其它请求读取的还是旧值A白干了。t3A更新完mysql数据库的值redis是被B写回的旧值mysql是被A更新的新值。出现了数据不一致问题。 
解决方案延时双删策略 问题示例 线程A sleep的时间就需要大于线程B读取数据再写入缓存的时间。 这个时间怎么确定呢 第一种方法 在业务程序运行的时候统计下线程读数据和写缓存的操作时间自行评估自己的项目的读数据业务逻辑的耗时 以此为基础来进行估算。然后写数据的休眠时间则在读数据业务逻辑的耗时基础上加百毫秒即可。 这么做的目的就是确保读请求结束写请求可以删除读请求造成的缓存脏数据。第二种方法 新启动一个后台监控程序比如后面要讲解的WatchDog监控程序会加时 这种方法吞吐降低怎么办 使用异步 使用WatchDog 先更新数据库再删除缓存 
时间线程A线程B出现的问题t1更新数据库中的值…t2缓存中立刻命中此时B读取的是缓存旧值。A还没有来得及删除缓存的值导致B缓存命中读到旧值。t3更新缓存的数据 
假如缓存删除失败或者来不及导致请求再次访问redis时缓存命中读取到的是缓存旧值。 微软云案例https://docs.microsoft.com/en-us/azure/architecture/patterns/cache-aside 阿里巴巴canal 
解决方案 可以把要删除的缓存值或者是要更新的数据库值暂存到消息队列中例如使用Kafka/RabbitMQ等。当程序没有能够成功地删除缓存值或者是更新数据库值时可以从消息队列中重新读取这些值然后再次进行删除或更新。如果能够成功地删除或更新我们就要把这些值从消息队列中去除以免重复操作此时我们也可以保证数据库和缓存的数据一致了否则还需要再次进行重试如果重试超过的一定次数后还是没有成功我们就需要向业务层发送报错信息了通知运维人员。 
最终解决方案最终一致性 案例 
流量充值先下发短信实际充值可能滞后5分钟可以接受电商发货短信下发但是物流明天见 
总结 
优先使用先更新数据库再删除缓存的方案(先更库→后删存)。理由如下 
先删除缓存值再更新数据库有可能导致请求因缓存缺失而访问数据库给数据库带来压力导致打满mysql。如果业务应用中读取数据库和写缓存的时间不好估算那么延迟双删中的等待时间就不好设置。 如果业务层要求必须读取一致性的数据那么我们就需要在更新数据库时先在Redis缓存客户端暂停并发读请求等数据库更新完、缓存值删除后再读取数据从而保证数据一致性这是理论可以达到的效果但 实际不推荐因为真实生产环境中分布式下很难做到实时一致性一般都是最终一致性 如果使用先更新数据库再删除缓存的方案 
策略高并发多线程条件下问题现象解决方案先删除redis缓存再更新mysql无缓存删除成功但数据库更新失败Java程序从数据库中读到旧值再次更新数据库重试有缓存删除成功但数据库更新中…有并发读请求并发请求从数据库读到旧值并回写到redis导致后续都是从redis读取到旧值延迟双删先更新mysql再删除redis缓存无数据库更新成功但缓存删除失败Java程序从redis中读到旧值再次删除缓存重试有数据库更新成功但缓存删除中…有并发读请求并发请求从缓存读到旧值等待redis删除完成这段时间有数据不一致短暂存在。 
问题示例 
你只要用缓存就可能会涉及到redis缓存与数据库双存储双写你只要是双写就一定会有数据一致性的问题那么你如何解决一致性问题?双写一致性你先动缓存redis还是数据库mysql哪一个?why?延时双删你做过吗?会有哪些问题?有这么一种憶况微服务查询redis无mysql有为保证数据双写一致性回写redis你需要注意什么?双减加锁策略了解过吗如何尽量避免缓存击穿redis和mysql双写100%会出纰漏做不到强一致性你如何保证最终一致性?