需求说明
防止Redis
服务不可用导致服务不可用,保证业务正常流程,在Redis
服务崩掉后,走数据库进行加锁解锁操作,需要尽量不修改原有Redis
代码
原有redis示例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13
| @Slf4j @Service public class OldRedisService{
@Autowired private RedisTemplate redisTemplate;
public Boolean set(String key, long expireTime){ log.info("进入redis方法内部"); return redisTemplate.opsForValue().setIfAbsent(key,1,expireTime, TimeUnit.SECONDS); }
}
|
JDK动态代理实现
增加A接口
1 2 3
| public interface RedisServiceI { Boolean set(String key,long expireTime); }
|
原有redis类实现A
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| @Slf4j @Service @Primary public class OldRedisService implements RedisServiceI{
@Autowired private RedisTemplate redisTemplate;
@Override public Boolean set(String key, long expireTime){ log.info("进入redis方法内部"); return redisTemplate.opsForValue().setIfAbsent(key,1,expireTime, TimeUnit.SECONDS); }
}
|
增加MySQL加锁类实现A接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| @Slf4j @Service public class DatabaseService implements RedisServiceI {
@Autowired private TCacheInfoMapper tCacheInfoMapper;
@Override public Boolean set(String key, long expireTime) { log.info("进入database内部"); TCacheInfo tCacheInfo = new TCacheInfo(); tCacheInfo.setId(1515155L); tCacheInfo.setCreateTime(LocalDateTime.now()); tCacheInfo.setUpdateTime(LocalDateTime.now()); tCacheInfo.setCacheKey(key); tCacheInfo.setCacheValue(key); tCacheInfo.setExpireTime(LocalDateTime.now()); tCacheInfo.setVersion(1); return tCacheInfoMapper.insert(tCacheInfo) > 0; } }
|
对原有类进行代理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| @Slf4j @Component public class GlobalConfiguration implements BeanPostProcessor {
@Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { if (bean instanceof RedisServiceI && "oldRedisService".equals(beanName)) { bean = getProxyBean(bean); } return bean; }
private Object getProxyBean(Object bean) { return Proxy .newProxyInstance( this.getClass().getClassLoader(), bean.getClass().getInterfaces(), new DynamicProxyHandler(bean) ); }
}
|
调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| @Slf4j public class DynamicProxyHandler implements InvocationHandler {
Object redisBean; public DynamicProxyHandler(Object redisBean) { this.redisBean = redisBean; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { log.info("代理开始执行:"+method.getName()); Object obj = null; try { obj = method.invoke(redisBean, args); }catch (InvocationTargetException e){ if(e.getTargetException() instanceof BussinessException){ throw new BussinessException(ErrorCode.CA000001,"自定义的异常,需要抛出让全局异常处理"); } RedisServiceI bean = (RedisServiceI) SpringContextUtil.getContext().getBean("databaseService"); return method.invoke(bean, args); } log.info("代理结束执行"); return obj; } }
|
使用方式
1 2
| @Autowired private RedisServiceI redisServiceI;
|
因为上面OldRedisService
类使用了@Primary
注解,所以会优先被注入到接口中
优化点
- 上面的方式有个缺点,这样修改后,原来的
OldRedisService
类就不能被注入了,因为已经被代理了。可以优化一下,不代理原有的OldRedisService
类,而是新写一个类,也实现RedisServiceI
,新类所有方法全部调用OldRedisService
,且使用@Primary
保证被优先注入接口,最后代理这个新写的类即可。
- 其实还有一个锁的数据一致性问题,在Redis加锁后,Redis崩了,是无法同步到数据库中的,这个处理比较麻烦,如果对业务要求比较高需要注意一下