基于j2cache实现的redis分布式锁


声明:本文转载自https://my.oschina.net/qixiaobo025/blog/1142654,转载目的在于传递更多信息,仅供学习交流之用。如有侵权行为,请联系我,我会及时删除。

系统现状:

由于系统目前提醒是在公共模块,对于提醒的数目实时计算。对于db的压力较大。因此采用J2cache的两级缓存。关于两级缓存的说明

但是当部分业务更新时需要将缓存删除,导致系统在某个时间点出现db负载过大。

对应业务系统出现大量慢sql以及由于超时无法完成的请求

出现了系统雪崩的场景。

两级缓存实现分析之缓存设置   ===》当大量请求提醒的业务sql过来导致系统无法及时的响应。而提醒sql又出现超时是的无法真正的缓存下来。使的sql越积越多,db负载过高。直到有成功将缓存写入或者db被击穿。

对策:

  1. 改写sql,尽量提高sql效率 
  2. 在缓存处加分布式锁,避免同时回源

第一个策略对于系统并没有得到很好的缓解。主要是由于该发生提醒缓存失效使用的用户较多,用户频繁请求数据,导致结果无法在有效的h时间内缓存,出现大量慢sql请求直至系统超时。

那么我们要做的就是在缓存上增加blockingcache的功能。

那么何为blockingCache呢?需要哪些功能呢?

  1. 对于系统同一个缓存在多个请求过来时能hold住其他请求,单个请求执行业务
  2. 防止出现死锁,在系统挂掉的情况下锁能够自动释放
  3. 可定义的超时时长
  4. 其他请求在缓存中设置过值之后自动苏醒,返回缓存结果
  5. 支持可重入
  6. 其他配置需求等等

 

由上述可知,我们在缓存get时需要处理的事情如下:

  1. 先取缓存,如果有结果直接返回
  2. 如果没有缓存,直接加锁
  3. 如果加锁成功直接返回null  ===》需要去执行业务逻辑
  4. 如果加锁不成功或者不可重入 则线程需要等待
  5. 线程每隔指定时间sleep结束(默认为300ms)最多等待60s,每次需要检查是否缓存中结果如果有结果直接返回
  6. 还需要检查是否可以加锁成功(如果加锁成功返回null)

查看原先的get的代码如下

public Object get(Object key) throws CacheException {     if (null == key)         return null;     Object obj = null;     try {         byte[] b = redisCacheProxy.hget(region2, getKeyName(key));         if (b != null)             obj = SerializationUtils.deserialize(b);     } catch (Exception e) {         log.error("Error occured when get data from redis2 cache", e);         if (e instanceof IOException || e instanceof NullPointerException)             evict(key);     }     return obj; }

改造后代码如下

public Object get(Object key) throws CacheException {     if (null == key)         return null;     Object obj = null;     try {         byte[] keyName = getKeyName(key);         byte[] b = redisCacheProxy.hget(region2, keyName);         if (b != null) {             obj = SerializationUtils.deserialize(b);         } else if (redisCacheProxy.isBlock()) {             byte[] lockKey = getLockKey(key);             boolean locked = getLock(lockKey);             if (locked || canReentrant(key)) {                 return null;             } else {                 int timeLeft = redisCacheProxy.getTimeOutMillis();                 while (timeLeft > 0) {                     Thread.sleep(redisCacheProxy.getTimeWaitMillis());                     timeLeft -= redisCacheProxy.getTimeWaitMillis();                     b = redisCacheProxy.hget(region2, keyName);                     if (b != null) {                         obj = SerializationUtils.deserialize(b);                         break;                     } else {                         //如果拿不到再尝试一次获取lock,防止出现部分情况一直没有put导致等待时间过长。后续要改造成可重入                         if (getLock(lockKey)) {                             return null;                         }                     }                     //超时是应该抛异常呢还是直接返回null? 目前返回null                 }             }           }     } catch (Exception e) {         log.error("Error occured when get data from redis2 cache", e);         if (e instanceof IOException || e instanceof NullPointerException)             evict(key);     }     return obj; }   private boolean canReentrant(Object key) {     //对于缓存来说要求不精确,使用线程id即可     try {         String value = redisCacheProxy.get(getLockKeyString(key));         if (value != null) {             long oriThreadId = Long.parseLong(value);             return oriThreadId == Thread.currentThread().getId();         }     } catch (Exception ex) {         log.error(ex.getMessage(), ex);     }     return false; }   private boolean getLock(byte[] lockKey) {     return getLock(lockKey, String.valueOf(Thread.currentThread().getId()).getBytes()); }   private boolean getLock(byte[] lockKey, byte[] keyName) {     return "OK".equals(redisCacheProxy.set(lockKey, keyName, NX, PX, redisCacheProxy.getTimeLockMillis())); }   private void releaseLock(byte[] lockKey) {     redisCacheProxy.del(lockKey); }   private String getLockKeyString(Object key) {     return String.format(lockPattern, region, key.hashCode() % redisCacheProxy.getStripes()); }   private byte[] getLockKey(Object key) {     String keyName = getLockKeyString(key);     return keyName.getBytes(); }

可以参考

 

https://git.oschina.net/ld/J2Cache/pulls/48

https://git.oschina.net/ld/J2Cache/pulls/47

http://git.oschina.net/ld/J2Cache/commit/928bde1a60b9884f5dff95257a954c61aa2bb367

本文发表于2017年07月03日 16:36
(c)注:本文转载自https://my.oschina.net/qixiaobo025/blog/1142654,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责。如有侵权行为,请联系我们,我们会及时删除.

阅读 2064 讨论 0 喜欢 0

抢先体验

扫码体验
趣味小程序
文字表情生成器

闪念胶囊

你要过得好哇,这样我才能恨你啊,你要是过得不好,我都不知道该恨你还是拥抱你啊。

直抵黄龙府,与诸君痛饮尔。

那时陪伴我的人啊,你们如今在何方。

不出意外的话,我们再也不会见了,祝你前程似锦。

这世界真好,吃野东西也要留出这条命来看看

快捷链接
网站地图
提交友链
Copyright © 2016 - 2021 Cion.
All Rights Reserved.
京ICP备2021004668号-1