Redis保证数据一致性-延时双删代码实现

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);
			}
		});
	}

}

好了,文章就分享到这里了~

  • 5
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值