Because the official account has changed the push rules, please click “Looking” and add “star” to get exciting technology sharing as soon as possible
Click to follow the #InternetArchitect official account and get the full set of architect information here
0,2T architect learning materials dry content
Previous article: Sharing of useful learning materials for 2T architects
Hi everyone, I am an Internet Architect!
1. Business scenario
In the case of multi-thread concurrency, suppose there are two database modification requests. In order to ensure the data consistency between the database and redis, the implementation of the modification request requires modifying the database and then cascadingly modifying the data in Redis.
-
Request 1: A modifies database data and B modifies Redis data
-
Request 2: C modifies database data and D modifies Redis data
In a concurrent situation, there will be a situation like A -> C -> D -> B.
It is important to understand that when threads execute multiple sets of atomic operations concurrently, the execution order may overlap.
1. Problems at this time
The data modified by A in the database is eventually saved in Redis, and C also modified the database data after A.
At this time, there is an inconsistency between the data in Redis and the database data. In the subsequent query process, Redis will be checked first for a long time, resulting in a serious problem that the queried data is not the real data in the database.
2. Solution
When using Redis, you need to maintain the consistency of Redis and database data. One of the most popular solutions is the delayed double delete strategy.
Note: You must know that frequently modified data tables are not suitable for using Redis, because the result of the double delete strategy is to delete the data saved in Redis, and subsequent queries will query the database. Therefore, Redis uses a data cache that reads far more than changes.
Implementation steps of delayed double deletion plan
-
Delete cache
-
Update database
-
Delay 500 milliseconds (set the delay execution time according to the specific business)
-
Delete cache
3. Why is there a delay of 500 milliseconds?
This is so that we can complete the database update operation before deleting Redis for the second time. Let’s pretend that if there is no third step, there is a high probability that after the two deletion Redis operations are completed, the data in the database has not been updated. If there is a request to access the data at this time, the problem we mentioned at the beginning will appear. That question.
4. Why do you need to delete the cache twice?
If we do not have a second deletion operation and there is a request to access data at this time, it may be the Redis data that has not been modified before. After the deletion operation is executed, Redis will be empty. When a request comes in, the database will be accessed. At that time, the data in the database is already updated data, ensuring data consistency.
2. Code Practice
1. Introduce Redis and SpringBoot AOP dependencies
<!-- redis usage --> <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>
2. Write custom aop annotations and aspects
ClearAndReloadCache delayed double deletion annotation
/** *Delayed double deletion **/ @Retention(RetentionPolicy.RUNTIME) @Documented @Target(ElementType.METHOD) public @interface ClearAndReloadCache { String name() default ""; }
ClearAndReloadCacheAspect delayed double deletion of aspects
@Aspect @Component public class ClearAndReloadCacheAspect { @Autowired private StringRedisTemplate stringRedisTemplate; /** * entry point *Entry point, entry point implemented based on annotations. Those with this annotation are all entry points for Aop aspects. * */ @Pointcut("@annotation(com.pdh.cache.ClearAndReloadCache)") public void pointCut(){ } /** * Surround notifications * Surround notifications are very powerful and can determine whether and when the target method is executed, whether the method parameters need to be replaced during execution, and whether the return value needs to be replaced after execution. * The first parameter of the surround notification must be of type org.aspectj.lang.ProceedingJoinPoint * @param proceedingJoinPoint */ @Around("pointCut()") public Object aroundAdvice(ProceedingJoinPoint proceedingJoinPoint){ System.out.println("---------- Surround notification -----------"); System.out.println("Target method name of surrounding notification: " + proceedingJoinPoint.getSignature().getName()); Signature signature1 = proceedingJoinPoint.getSignature(); MethodSignature methodSignature = (MethodSignature)signature1; Method targetMethod = methodSignature.getMethod();//Method object ClearAndReloadCache annotation = targetMethod.getAnnotation(ClearAndReloadCache.class);//Reflection to get the method object of the custom annotation String name = annotation.name();//Get the parameter of the method object of the custom annotation, which is name Set<String> keys = stringRedisTemplate.keys("*" + name + "*");//Fuzzy definition key stringRedisTemplate.delete(keys);//Fuzzy delete redis key value //Execute the business of modifying the database with double deletion annotations, that is, the method business in the controller Object proceed = null; try { proceed = proceedingJoinPoint.proceed(); } catch (Throwable throwable) { throwable.printStackTrace(); } //Open a thread and delay for 1 second (here is an example of 1 second, you can change it to your own business) // Delay deletion in the thread and return the result of the business code at the same time so that it does not affect the execution of the business code new Thread(() -> { try { Thread.sleep(1000); Set<String> keys1 = stringRedisTemplate.keys("*" + name + "*");//Fuzzy delete stringRedisTemplate.delete(keys1); System.out.println("----------After 1 second, the delayed deletion in the thread will be completed -----------"); } catch (InterruptedException e) { e.printStackTrace(); } }).start(); return proceed;//Return the value of the business code } }
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 # 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 script
Used to produce test data
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, 'Zhang San'); INSERT INTO `user_db` VALUES (2, '李思'); INSERT INTO `user_db` VALUES (3, '王二'); INSERT INTO `user_db` VALUES (4, 'Mazi'); INSERT INTO `user_db` VALUES (5, '王三'); INSERT INTO `user_db` VALUES (6, '李三');
5. UserController
/** * User control layer */ @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 layer */ @Service public class UserService { @Resource private UserMapper userMapper; public Result get(Integer id){ LambdaQueryWrapper<User> 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,"Failed to operate database"); } public Result delete(Integer id) { LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(User::getId, id); int line = userMapper.delete(wrapper); if (line > 0) return Result.success(line); return Result.fail(888, "Failed to operate the database"); } public Result update(User user){ int i = userMapper.updateById(user); if(i > 0) return Result.success(i); return Result.fail(888,"Failed to operate database"); } }
3. Testing and verification
1. ID=10, add a new piece of data
2. When querying the database for the first time, Redis will save the query results
3. The first visit ID is 10
4. The first access database ID is 10, and store the result in Redis
5. Update the user name corresponding to ID 10 (verify database and cache inconsistency scheme)
Database and cache inconsistency verification scheme:
Make a breakpoint and simulate that after thread A performs the first deletion, before A completes updating the database, another thread B accesses ID=10 and reads the old data.
6. Use the second deletion and set the delay time according to the business scenario. After the cache is deleted successfully twice, the Redis result will be empty. What is read is the real data of the database, and there will be no inconsistency between the read cache and the database.
4. Code project and address
The core code is shown in the red box
Code: https://gitee.com/jike11231/redisDemo
Original text: blog.csdn.net/jike11231/article/details/126329789
1.2TSharing of useful learning materials for architects
2.10000 + TB resources, Alibaba cloud disk, awesome! !
3. Basically covers a summary of all core knowledge points of Spring
· END ·
Finally, follow the public account Internet Architect and reply in the background: 2T, you can get the Java series interview questions and answers I compiled, which is very complete.
If this article is helpful or inspiring to you, please scan the QR code above to pay attention. Your support is my biggest motivation to keep writing.
Please like, retweet and watch three times in one click