Angenommen, es gibt zwei Datenbankänderungsanforderungen, um die Datenkonsistenz zwischen der Datenbank und Redis sicherzustellen:
Die Implementierung der Änderungsanforderung erfordert eine Kaskadierung Änderungen nach der Änderung der Datenbank in Redis.
Anfrage 1: A ändert die Datenbankdaten B ändert die Redis-Daten
Anfrage 2: C ändert die Datenbankdaten D ändert die Redis-Daten
In einer gleichzeitigen Situation gibt es die Situation A —> –> , und C hat auch die Datenbank nach A-Daten geändert.
Hinweis: Sie müssen wissen, dass häufig geänderte Datentabellen nicht für die Verwendung von Redis geeignet sind, da das Ergebnis der doppelten Löschstrategie darin besteht, die in Redis gespeicherten Daten zu löschen und nachfolgende Abfragen die Datenbank abfragen. Daher verwendet Redis einen Datencache, der weit mehr liest als ändert.
Ausführungsschritte des verzögerten Doppellöschplans1> Cache löschen
3> Verzögerung 500 Millisekunden (stellen Sie die verzögerte Ausführungszeit entsprechend dem jeweiligen Unternehmen ein)
3 500 Millisekunden?Wenn wir keinen zweiten Löschvorgang durchführen und zu diesem Zeitpunkt eine Anforderung zum Zugriff auf Daten vorliegt, sind die Redis-Daten möglicherweise nicht geändert. Nach der Ausführung des Löschvorgangs ist Redis leer Wenn eine Anfrage eingeht, wird auf die Datenbank zugegriffen. Zu diesem Zeitpunkt handelt es sich bei den Daten in der Datenbank bereits um aktualisierte Daten, wodurch die Datenkonsistenz sichergestellt wird. 2. Code-Praxis1. Einführung von Redis- und SpringBoot-AOP-Abhängigkeiten
Wir müssen den Datenbankaktualisierungsvorgang vor der zweiten Redis-Löschung abschließen. Nehmen wir an, wenn kein dritter Schritt erfolgt, besteht eine hohe Wahrscheinlichkeit, dass die Daten in der Datenbank nach Abschluss der beiden Redis-Löschvorgänge nicht aktualisiert wurden. Wenn zu diesem Zeitpunkt eine Anforderung zum Zugriff auf die Daten vorliegt, liegt das Problem vor Die eingangs erwähnte Frage wird auftauchen.
4. Warum muss der Cache zweimal gelöscht werden?
<!-- redis使用 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!-- aop --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
/** *延时双删 **/ @Retention(RetentionPolicy.RUNTIME) @Documented @Target(ElementType.METHOD) public @interface ClearAndReloadCache { String name() default ""; }. Schreiben Sie benutzerdefinierte AOP-Anmerkungen und -Aspekte
3. Anwendung. yml
@Aspect @Component public class ClearAndReloadCacheAspect { @Autowired private StringRedisTemplate stringRedisTemplate; /** * 切入点 *切入点,基于注解实现的切入点 加上该注解的都是Aop切面的切入点 * */ @Pointcut("@annotation(com.pdh.cache.ClearAndReloadCache)") public void pointCut(){ } /** * 环绕通知 * 环绕通知非常强大,可以决定目标方法是否执行,什么时候执行,执行时是否需要替换方法参数,执行完毕是否需要替换返回值。 * 环绕通知第一个参数必须是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 Set<String> 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); Set<String> keys1 = stringRedisTemplate.keys("*" + name + "*");//模糊删除 stringRedisTemplate.delete(keys1); System.out.println("-----------1秒钟后,在线程中延迟删除完毕 -----------"); } catch (InterruptedException e) { e.printStackTrace(); } }).start(); return proceed;//返回业务代码的值 } }
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 # 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
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, '李三');
6, UserService
/** * 用户控制层 */ @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); } }
3. Testverifizierung
2 In Redis speichern
5, aktualisieren Sie den Benutzernamen, der der ID 10 entspricht (Datenbank- und Cache-Inkonsistenzschema überprüfen). Ein Thread zum Ausführen des ersten Nach einem Löschvorgang, bevor A die Aktualisierung der Datenbank abschließt, greift ein anderer Thread B auf ID=10 zu und liest die alten Daten.Verwenden Sie den zweiten Löschvorgang und stellen Sie die entsprechende Verzögerungszeit entsprechend dem Geschäftsszenario ein. Nachdem der Cache zweimal erfolgreich gelöscht wurde, ist das Ausgabeergebnis von Redis leer. Was gelesen wird, sind die tatsächlichen Daten der Datenbank, und es besteht keine Inkonsistenz zwischen dem Lesecache und der Datenbank.
4. Code Engineering
Der Kerncode wird im roten Feld angezeigt
Das obige ist der detaillierte Inhalt vonWie SpringBoot AOP Redis die verzögerte Doppellöschfunktion implementiert. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!