建设网站需求文档,免费软件下载官方网站,家乡网站建设策划书,wordpress建站ftp一、业务场景 
在多线程并发情况下#xff0c;假设有两个数据库修改请求#xff0c;为保证数据库与redis的数据一致性#xff0c;修改请求的实现中需要修改数据库后#xff0c;级联修改Redis中的数据。 请求一#xff1a;A修改数据库数据 B修改Redis数据  请求二#xff…一、业务场景 
在多线程并发情况下假设有两个数据库修改请求为保证数据库与redis的数据一致性修改请求的实现中需要修改数据库后级联修改Redis中的数据。 请求一A修改数据库数据 B修改Redis数据  请求二C修改数据库数据 D修改Redis数据  
并发情况下就会存在A — C — D — B的情况 ❝ 一定要理解线程并发执行多组原子操作执行顺序是可能存在交叉现象的 ❞ 1、此时存在的问题 
A修改数据库的数据最终保存到了Redis中C在A之后也修改了数据库数据。 
此时出现了Redis中数据和数据库数据不一致的情况在后面的查询过程中就会长时间去先查Redis 从而出现查询到的数据并不是数据库中的真实数据的严重问题。 
2、解决方案 
在使用Redis时需要保持Redis和数据库数据的一致性最流行的解决方案之一就是延时双删策略。 
注意要知道经常修改的数据表不适合使用Redis因为双删策略执行的结果是把Redis中保存的那条数据删除了以后的查询就都会去查询数据库。所以Redis使用的是读远远大于改的数据缓存。 
延时双删方案执行步骤 删除缓存  更新数据库  延时500毫秒 (根据具体业务设置延时执行的时间)  删除缓存  
3、为何要延时500毫秒 
这是为了我们在第二次删除Redis之前能完成数据库的更新操作。假象一下如果没有第三步操作时有很大概率在两次删除Redis操作执行完毕之后数据库的数据还没有更新此时若有请求访问数据便会出现我们一开始提到的那个问题。 
4、为何要两次删除缓存 
如果我们没有第二次删除操作此时有请求访问数据有可能是访问的之前未做修改的Redis数据删除操作执行后Redis为空有请求进来时便会去访问数据库此时数据库中的数据已是更新后的数据保证了数据的一致性。 
二、代码实践 
1、引入Redis和SpringBoot AOP依赖 
dependency  groupIdorg.springframework.boot/groupId  artifactIdspring-boot-starter-data-redis/artifactId  
/dependency  dependency  groupIdorg.springframework.boot/groupId  artifactIdspring-boot-starter-aop/artifactId  
/dependency  2、编写自定义aop注解和切面 
ClearAndReloadCache延时双删注解 
*/**  *延时双删  **/*  
Retention(RetentionPolicy.RUNTIME)  
Documented  
Target(ElementType.METHOD)  
publicinterface ClearAndReloadCache {  
String name()default ;  
}  ClearAndReloadCacheAspect延时双删切面 
Aspect  
Component  
publicclassClearAndReloadCacheAspect{  Autowired  
private StringRedisTemplate stringRedisTemplate;  */**  
* 切入点  
*切入点,基于注解实现的切入点  加上该注解的都是Aop切面的切入点  
*  
*/*  Pointcut(annotation(com.pdh.cache.ClearAndReloadCache))  
publicvoidpointCut(){  }  
*/**  
* 环绕通知  
* 环绕通知非常强大可以决定目标方法是否执行什么时候执行执行时是否需要替换方法参数执行完毕是否需要替换返回值。  
* 环绕通知第一个参数必须是org.aspectj.lang.ProceedingJoinPoint类型  
* param proceedingJoinPoint  
*/*  
Around(pointCut())  
public Object aroundAdvice(ProceedingJoinPoint proceedingJoinPoint){  System.out.println(----------- 环绕通知 -----------);  System.out.println(环绕通知的目标方法名  proceedingJoinPoint.getSignature().getName());  Signature signature1  proceedingJoinPoint.getSignature();  MethodSignature methodSignature  (MethodSignature)signature1;  Method targetMethod  methodSignature.getMethod();*//方法对象*  ClearAndReloadCache annotation  targetMethod.getAnnotation(ClearAndReloadCache.class);*//反射得到自定义注解的方法对象*  String name  annotation.name();*//获取自定义注解的方法对象的参数即name*  SetString keys  stringRedisTemplate.keys(*  name  *);*//模糊定义key*  stringRedisTemplate.delete(keys);*//模糊删除redis的key值*  *//执行加入双删注解的改动数据库的业务 即controller中的方法业务*  Object proceed  null;  
try {  proceed  proceedingJoinPoint.proceed();  } catch (Throwable throwable) {  throwable.printStackTrace();  }  *//开一个线程 延迟1秒此处是1秒举例可以改成自己的业务*  
*// 在线程中延迟删除  同时将业务代码的结果返回 这样不影响业务代码的执行*  
new Thread(() - {  
try {  Thread.sleep(1000);  SetString keys1  stringRedisTemplate.keys(*  name  *);*//模糊删除*  stringRedisTemplate.delete(keys1);  System.out.println(-----------1秒钟后在线程中延迟删除完毕 -----------);  } catch (InterruptedException e) {  e.printStackTrace();  }  }).start();  return proceed;*//返回业务代码的值*  }  
}  3、application.yml server:  port: 8082  spring:  # redis setting  redis:  host: localhost  port: 6379  # cache setting  cache:  redis:  time-to-live: 60000 # 60s  datasource:  driver-class-name: com.mysql.cj.jdbc.Driver  
url: jdbc:mysql://localhost:3306/test  
username: root  
password: 1234   基于 SpringCloudAlibaba  Gateway  Nacos  RocketMQ  Vue  Element 实现的后台管理系统  用户小程序支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能  * 项目地址https://github.com/YunaiV/yudao-cloud  * 视频教程https://doc.iocoder.cn/video/  # mp setting  
mybatis-plus:  mapper-locations: classpath*:com/pdh/mapper/*.xml  global-config:  db-config:  table-prefix:  configuration:  # log of sql  log-impl: org.apache.ibatis.logging.stdout.StdOutImpl  # hump  map-underscore-to-camel-case: true  4、user_db.sql脚本 
用于生产测试数据 
DROP TABLE IF EXISTS user_db;  
CREATE TABLE user_db  (  id int(4) NOT NULL AUTO_INCREMENT,  username varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,  PRIMARY KEY (id) USING BTREE  
) ENGINE  InnoDB AUTO_INCREMENT  8 CHARACTER SET  utf8 COLLATE  utf8_general_ci ROW_FORMAT  Dynamic;  -- ----------------------------  
-- Records of user_db  
-- ----------------------------  
INSERT INTO user_db VALUES (1, 张三);  
INSERT INTO user_db VALUES (2, 李四);  
INSERT INTO user_db VALUES (3, 王二);  
INSERT INTO user_db VALUES (4, 麻子);  
INSERT INTO user_db VALUES (5, 王三);  
INSERT INTO user_db VALUES (6, 李三);  5、UserController 
/**  * 用户控制层  */*  
RequestMapping(/user)  
RestController  
public class UserController {  Autowired  private UserService userService;  GetMapping(/get/{id})  Cache(name  get method)  *//Cacheable(cacheNames  {get})*  public Result get(PathVariable(id) Integer id){  return userService.get(id);  }  PostMapping(/updateData)  ClearAndReloadCache(name  get method)  public Result updateData(RequestBody User user){  return userService.update(user);  }  PostMapping(/insert)  public Result insert(RequestBody User user){  return userService.insert(user);  }  DeleteMapping(/delete/{id})  public Result delete(PathVariable(id) Integer id){  return userService.delete(id);  }  
}  6、UserService 
/**  * service层  */*  
Service  
public class UserService {  Resource  private UserMapper userMapper;  public Result get(Integer id){  LambdaQueryWrapperUser wrapper  new LambdaQueryWrapper();  wrapper.eq(User::getId,id);  User user  userMapper.selectOne(wrapper);  return Result.success(user);  }  public Result insert(User user){  int line  userMapper.insert(user);  if(line  0)  return Result.success(line);  return Result.fail(888,操作数据库失败);  }  public Result delete(Integer id) {  LambdaQueryWrapperUser wrapper  new LambdaQueryWrapper();  wrapper.eq(User::getId, id);  int line  userMapper.delete(wrapper);  if (line  0)  return Result.success(line);  return Result.fail(888, 操作数据库失败);  }  public Result update(User user){  int i  userMapper.updateById(user);  if(i  0)  return Result.success(i);  return Result.fail(888,操作数据库失败);  }  
}  三、测试验证 
1、ID10新增一条数据 2、第一次查询数据库Redis会保存查询结果 3、第一次访问ID为10 4、第一次访问数据库ID为10将结果存入Redis 5、更新ID为10对应的用户名验证数据库和缓存不一致方案 数据库和缓存不一致验证方案 
打个断点模拟A线程执行第一次删除后在A更新数据库完成之前另外一个线程B访问ID10读取的还是旧数据。 6、采用第二次删除根据业务场景设置延时时间两次删除缓存成功后Redis结果为空。读取的都是数据库真实数据不会出现读缓存和数据库不一致情况。 四、代码工程及地址 
核心代码红色方框所示 ❝ https://gitee.com/jike11231/redisDemo.git ❞ https://mp.weixin.qq.com/s/VBr3E086U58PyQkNdFfNzg