Redis和数据库的数据一致性在某些场景下非常重要,如何最大程度保证Reids和数据库之间的数据一致呢?
想必大家第一时间会想到延时双删策略:
在修改数据库之前删除缓存,然后数据库中的数据修改完成后,再过一段时间再删一次缓存。
这篇文章就如何实现延时双删提供了案例代码,代码可能不是最优的,但是确实能够达到双删的效果。
具体代码实现:
RedisUtils.java
RedisUtils是一个自定义的接口,表示redis的工具类
package cn.edu.sgu.www.mhxysy.redis;
import java.util.concurrent.TimeUnit;
/**
* redis工具类
* @author heyunlin
* @version 1.0
*/
public interface RedisUtils {
String get(String key);
void set(String key, String value);
void set(String key, String value, long timeout, TimeUnit timeUnit);
void delete(String key);
Long incrBy(String key);
Boolean hasKey(String key);
void expire(String key, long timeout, TimeUnit timeUnit);
}
StringRedisUtils.java
StringRedisUtils是基于StringRedisTemplate实现的操作Redis客户端的工具类,封装了一些常用的方法。
package cn.edu.sgu.www.mhxysy.redis;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
/**
* 封装了StringRedisTemplate的redis工具类
* @author heyunlin
* @version 1.0
*/
@Component
public class StringRedisUtils implements RedisUtils {
private final StringRedisTemplate stringRedisTemplate;
@Autowired
public StringRedisUtils(StringRedisTemplate stringRedisTemplate) {
this.stringRedisTemplate = stringRedisTemplate;
}
/**
* get key
* @param key 键
* @return String
*/
@Override
public String get(String key) {
return getValueOperations().get(key);
}
/**
* set key value
* @param key 键
* @param value 值
*/
@Override
public void set(String key, String value) {
getValueOperations().set(key, value);
}
/**
* set key value
* expire key seconds
* @param key 键
* @param value 值
* @param timeout 过期的时间
* @param timeUnit 时间单位
*/
public void set(String key, String value, long timeout, TimeUnit timeUnit) {
getValueOperations().set(key, value, timeout, timeUnit);
}
/**
* del key
* @param key 键
*/
@Override
public void delete(String key) {
stringRedisTemplate.delete(key);
}
/**
* key的值自增:incrby key
* @param key 键
* @return 自增后的值
*/
@Override
public Long incrBy(String key) {
return getValueOperations().increment(key);
}
@Override
public Boolean hasKey(String key) {
return stringRedisTemplate.hasKey(key);
}
/**
* expire key seconds
* @param key 键
* @param timeout 过期的时间
* @param timeUnit 时间单位
*/
@Override
public void expire(String key, long timeout, TimeUnit timeUnit) {
stringRedisTemplate.expire(key, timeout, timeUnit);
}
/**
* 获取ValueOperations对象
* @return ValueOperations<String, String>
*/
private ValueOperations<String, String> getValueOperations() {
return stringRedisTemplate.opsForValue();
}
}
RedisRepository.java
RoleAccountRepository是一个操作RoleAccount数据的redis仓库类,一个数据库实体类对应了一个XxxRepository,这些仓库类实现同一个接口RedisRepository。
通过一个Consumer接口类型的参数接收需要在两次删除缓存的代码之间执行的业务代码。
package cn.edu.sgu.www.mhxysy.redis.repository;
import java.util.function.Consumer;
/**
* redis仓库的顶级接口:为了满足开闭原则设计了此接口
* @author heyunlin
* @version 1.0
*/
public interface RedisRepository {
void put(String key, Object value);
Object get(String key);
void putList(Object value);
Object getList();
void delete();
void delete(String key);
/**
* 延时双删
* @param key 数据的ID
* @param consumer Consumer<String>
*/
default void delayDoubleDelete(String key, Consumer<String> consumer) { }
}
RoleAccountRepository.java
package cn.edu.sgu.www.mhxysy.redis.repository.impl;
import cn.edu.sgu.www.mhxysy.consts.RedisKeyPrefixes;
import cn.edu.sgu.www.mhxysy.entity.role.RoleAccount;
import cn.edu.sgu.www.mhxysy.redis.RedisUtils;
import cn.edu.sgu.www.mhxysy.redis.repository.RedisRepository;
import cn.edu.sgu.www.mhxysy.util.TimerUtils;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.TimerTask;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
/**
* @author heyunlin
* @version 1.0
*/
@Slf4j
@Component
public class RoleAccountRepository implements RedisRepository {
private final RedisUtils redisUtils;
public RoleAccountRepository(RedisUtils redisUtils) {
this.redisUtils = redisUtils;
}
@Override
public void put(String id, Object value) {
String key = RedisKeyPrefixes.PREFIX_ROLE_ACCOUNT + id;
redisUtils.set(key, JSON.toJSONString(value));
redisUtils.expire(key, 7, TimeUnit.DAYS);
}
@Override
public RoleAccount get(String id) {
String key = RedisKeyPrefixes.PREFIX_ROLE_ACCOUNT + id;
String value = redisUtils.get(key);
if (value != null) {
log.debug("命中缓存{}", key);
}
return JSON.parseObject(value, RoleAccount.class);
}
@Override
public void putList(Object value) {
String key = RedisKeyPrefixes.PREFIX_ROLE_ACCOUNTS;
redisUtils.set(key, JSON.toJSONString(value), 7, TimeUnit.DAYS);
}
@Override
public List<RoleAccount> getList() {
String key = RedisKeyPrefixes.PREFIX_ROLE_ACCOUNTS;
String value = redisUtils.get(key);
if (value != null) {
log.debug("命中缓存{}", key);
return JSON.parseArray(value, RoleAccount.class);
}
return null;
}
@Override
public void delete() {
String key = RedisKeyPrefixes.PREFIX_ROLE_ACCOUNTS;
redisUtils.delete(key);
}
@Override
public void delete(String key) {
redisUtils.delete(RedisKeyPrefixes.PREFIX_ROLE_ACCOUNT + key);
}
@Override
public void delayDoubleDelete(String key, Consumer<String> consumer) {
delete();
delete(key);
consumer.accept(key);
// 延时双删
TimerUtils.schedule(new TimerTask() {
@Override
public void run() {
delete();
delete(key);
}
}, 500);
}
}
TimerUtils.java
TimerUtils是基于Timer实现的简单定时器工具类
package cn.edu.sgu.www.mhxysy.util;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
/**
* @author heyunlin
* @version 1.0
*/
public class TimerUtils {
// 创建定时器
private static final Timer timer = new Timer();
/**
* 延迟500毫秒执行一次定时任务
* @param task 任务
*/
public static void schedule(TimerTask task) {
schedule(task, 500);
}
/**
* 延迟执行一次定时任务
* @param task 任务
* @param delay 延迟时间,单位:毫秒(ms)
*/
public static void schedule(TimerTask task, long delay) {
timer.schedule(task, delay);
}
public static void main(String[] args) {
// 创建定时器任务
TimerTask task = new TimerTask() {
@Override
public void run() {
System.out.println("Hello world!");
}
};
timer.schedule(task, 1000); // 1秒后执行一次
timer.schedule(task, 2000, 2000); // 两秒后每两秒执行一次
timer.scheduleAtFixedRate(task, 3000, 3000); // 3秒后每3秒执行一次
timer.scheduleAtFixedRate(task, new Date(), 4000); // 每4秒执行一次
}
}
使用案例代码:
RoleAccountService.java
/**
* @author heyunlin
* @version 1.0
*/
public interface RoleAccountService {
/**
* 通过ID删除角色
* @param roleId 角色ID
*/
@Transactional(rollbackFor = Exception.class)
void deleteById(String roleId);
}
RoleAccountServiceImpl
/**
* @author heyunlin
* @version 1.0
*/
@Service
public class RoleAccountServiceImpl implements RoleAccountService {
private final RoleAccountMapper roleAccountMapper;
private final RoleJiadianMapper roleJiadianMapper;
private final SchoolSkillMapper schoolSkillMapper;
private final JiadianSchemaMapper jiadianSchemaMapper;
private final RoleAttributeMapper roleAttributeMapper;
private final RoleAccountRepository roleAccountRepository;
private final SchoolSkillCategoryMapper schoolSkillCategoryMapper;
private final GangService gangService;
private final ServerService serverService;
private final SchoolService schoolService;
private final AccountService accountService;
private final RoleModelingService roleModelingService;
@Autowired
public RoleAccountServiceImpl(
RoleAccountMapper roleAccountMapper,
RoleJiadianMapper roleJiadianMapper,
SchoolSkillMapper schoolSkillMapper,
JiadianSchemaMapper jiadianSchemaMapper,
RoleAttributeMapper roleAttributeMapper,
RoleAccountRepository roleAccountRepository,
SchoolSkillCategoryMapper schoolSkillCategoryMapper,
GangService gangService,
ServerService serverService,
SchoolService schoolService,
AccountService accountService,
RoleModelingService roleModelingService) {
this.roleAccountMapper = roleAccountMapper;
this.roleJiadianMapper = roleJiadianMapper;
this.schoolSkillMapper = schoolSkillMapper;
this.jiadianSchemaMapper = jiadianSchemaMapper;
this.roleAttributeMapper = roleAttributeMapper;
this.roleAccountRepository = roleAccountRepository;
this.schoolSkillCategoryMapper = schoolSkillCategoryMapper;
this.gangService = gangService;
this.serverService = serverService;
this.schoolService = schoolService;
this.accountService = accountService;
this.roleModelingService = roleModelingService;
}
@Override
public void deleteById(String roleId) {
// 删除缓存
roleAccountRepository.delayDoubleDelete(roleId, new Consumer<String>() {
@Override
public void accept(String s) {
Map<String, Object> columnMap = new HashMap<>(1);
columnMap.put("role_id", s);
// 删除角色属性
roleAttributeMapper.deleteByRoleId(s);
// 删除角色加点
roleJiadianMapper.deleteByMap(columnMap);
// 删除门派技能
schoolSkillMapper.deleteByMap(columnMap);
// 删除角色
roleAccountMapper.deleteById(s);
}
});
}
}
好了,文章就分享到这里了~