Ensuring Cache‑Database Consistency: Delayed Double Delete vs Binlog‑Driven Async Updates
This article compares the delayed double‑delete method with an asynchronous binlog‑subscription approach using Canal and RabbitMQ, explaining their workflows, pitfalls, and code implementations for maintaining cache and database consistency under high concurrency.
Introduction
Cache and database consistency is a classic challenge. The article compares two solutions: the delayed double‑delete mechanism and an asynchronous update using Alibaba Canal to pull binlog events and push them through a message queue.
Delayed Double Delete Strategy
Process: client A deletes the cache, writes to the DB; client B reads, finds no cache, queries the DB (may get stale replica data), caches it, then after master‑slave sync client A deletes the cache again. The pause can cause inconsistency.
Key point: if the second cache deletion fails, stale data remains. Retry methods include recording failures in a table for scheduled retries (high DB load) or using an asynchronous MQ to retry.
Demo code:
@RestController
@RequestMapping
public class RedisController {
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private SysUserMapper sysUserMapper;
@GetMapping
public void duobleCancle() throws InterruptedException {
redisTemplate.delete("1");
SysUser sysUser = sysUserMapper.selectUserById(Long.valueOf(1));
SysUser updateSysUser = new SysUser();
updateSysUser.setUserName("Lxlxxx");
updateSysUser.setEmail("@163.com");
UpdateWrapper<SysUser> updateWrapper = new UpdateWrapper();
updateWrapper.eq("userId",1);
sysUserMapper.update(updateSysUser,updateWrapper);
Thread.sleep(3000);
redisTemplate.opsForValue().append(sysUser.getUserId(), JSON.toJSONString(sysUser));
redisTemplate.delete("1");
}
}The demo shows many pitfalls that can still lead to dirty reads.
Asynchronous Cache Update via Binlog Subscription
Overall flow: install Canal, RabbitMQ, and MySQL; configure Canal to use RabbitMQ; enable binlog on MySQL; consume messages to update or delete Redis entries.
Canal Configuration
canal.ip = 1
canal.serverMode = rabbitmq
canal.mq.servers = 127.0.0.1
canal.mq.vhost=canal
canal.mq.exchange=exchange.trade
canal.mq.username=guest
canal.mq.password=guest
---------------------------------------------------------------------------------
canal.instance.dbUsername=root
canal.instance.dbPassword=123456
canal.instance.mysql.slaveId=1234
canal.instance.master.address=127.0.0.1:3306
canal.instance.defaultDatabaseName=test
canal.mq.topic=exampleMySQL my.cnf settings:
log-bin=mysql-bin
binlog-format=ROW
server_id=1Dependencies (Spring Boot, Redis, AMQP, FastJSON, MyBatis‑Plus):
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis.plus.version}</version>
</dependency>application.yml snippet:
spring:
rabbitmq:
virtual-host: canal
host: 127.0.0.1
publisher-confirms: true
datasource:
url: jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=UTC
username: root
password: root
driver-class-name: com.mysql.jdbc.Driver
redis:
host: 127.0.0.1RabbitMQ configuration class:
@Configuration
public class RabbitMqConfig {
@Bean
public Queue TestDirectQueue() {
return new Queue("exchange.canal.queue", true);
}
@Bean
public DirectExchange TestDirectExchange() {
return new DirectExchange("exchange.canal");
}
@Bean
public Binding bindingDirect() {
return BindingBuilder.bind(TestDirectQueue()).to(TestDirectExchange()).with("example");
}
}Listener that processes Canal messages and updates Redis:
@Component
@Slf4j
public class RabbitMqListener {
@Autowired
private StringRedisTemplate redisTemplate;
@RabbitListener(queues = "exchange.canal.queue")
public void process(Message message) {
log.info("canal queue消费的消息" + message.getBody());
Map map = JSON.parseObject(message.getBody(), Map.class);
JSONArray array = null;
String sqlType = (String) map.get("type");
if (StringUtils.endsWithIgnoreCase("SELECT", sqlType)) {
array = JSONArray.parseArray((String) map.get("data"));
}
if (array == null) {
return;
}
JSONObject jsonObject = array.getJSONObject(0);
if (StringUtils.endsWithIgnoreCase("UPDATE", sqlType) ||
StringUtils.endsWithIgnoreCase("INSERT", sqlType)) {
redisTemplate.boundValueOps(jsonObject.get("code").toString()).set(jsonObject.toString());
} else if (StringUtils.endsWithIgnoreCase("DELETE", sqlType)) {
redisTemplate.delete(jsonObject.get("code").toString());
}
if (StringUtils.endsWithIgnoreCase("SELECT", sqlType)) {
redisTemplate.boundValueOps(jsonObject.get("code").toString()).set(jsonObject.toString());
} else {
redisTemplate.delete(jsonObject.get("code").toString());
}
}
}Conclusion
In high‑concurrency scenarios, cache‑database consistency cannot be guaranteed instantly; using asynchronous messaging to achieve eventual consistency provides a reliable way to control cache updates and deletions.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
MaGe Linux Operations
Founded in 2009, MaGe Education is a top Chinese high‑end IT training brand. Its graduates earn 12K+ RMB salaries, and the school has trained tens of thousands of students. It offers high‑pay courses in Linux cloud operations, Python full‑stack, automation, data analysis, AI, and Go high‑concurrency architecture. Thanks to quality courses and a solid reputation, it has talent partnerships with numerous internet firms.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
