OSCache 缓存重建在 Race Condition 下的 NRE 问题

一,现象:

高并发情况下,使用 OSCache 作为本地缓存中间件的前端服务,日志文件中会出现大量如下错误信息:

异常堆栈:

java.lang.IllegalStateException:   Cannot complete cache update   -   current state (2) is not UPDATE_IN_PROGRESS

at com.opensymphony.oscache.base.EntryUpdateState.completeUpdate(EntryUpdateState.java:105)

at com.opensymphony.oscache.base.Cache. completeUpdate (Cache.java:762)

at com.opensymphony.oscache.base.Cache. putInCache (Cache.java:619)

at com.opensymphony.oscache.base.Cache.putInCache(Cache.java:580)

at com.opensymphony.oscache.general.GeneralCacheAdministrator.putInCache(GeneralCacheAdministrator.java:249)

at com.opensymphony.oscache.general.GeneralCacheAdministrator.putInCache(GeneralCacheAdministrator.java:259)

二,NRE 背景:

无论你使用哪一种本地缓存中间件,如果你缓存数据片段时设置了过期时间,都需要考虑缓存失效后的缓存重建(repopulate the cache )场景。

进一步必须考虑 Race Condition (同进程下多线程,或不同进程)下如何重建。

也就是说,某个线程在重建缓存过程中,其他线程发现缓存不存在或已过期,该如何处置?

从2005 年OSCache 的版本到现在,它一直这么声称:

* @throws   NeedsRefreshException   Thrown when the object either

* doesn't exist, or exists but is stale.   When this exception occurs,

the CacheEntry corresponding to the supplied key will be locked

     *   and other threads requesting this entry will potentially be blocked

     *   until the caller repopulates the cache . If the caller choses not

* to repopulate the cache, they <em>must</em> instead call

* {@link #cancelUpdate(String)}.

即,

当 key 不存在,或者存在但数据过期(stale )时,

调用 getFromCache 函数时会抛出 NRE 异常;

当异常发生时,

对应于这个 key 的 CacheEntry 将被锁住,

而请求这个 entry 的其他线程将可能(注意,仅仅是可能) 被阻塞,直到调用者重建缓存。

如果调用者没有选择重建缓存,必须   调用 cancelUpdate 函数来“Cancels any pending update for this cache entry ”。

三,缓存重建的 OSCache 官方推荐做法:

读取缓存遇到 NRE 异常时,OSCache 官方推荐的做法是:

1 String myKey = "myKey";

2 String myValue;

3 int myRefreshPeriod = 1000;

4 try {

5      // Get from the cache

6      myValue = (String) admin. getFromCache (myKey, myRefreshPeriod);

7 } catch ( NeedsRefreshException nre) {

8      try {

9          // Get the value (probably from the database)

10          myValue = "This is the content retrieved.";

11          // Store in the cache

12          admin. putInCache (myKey, myValue);

13      } catch (Exception ex) {

14          // We have the current content if we want fail-over.

15          myValue = (String) nre.getCacheContent();

16          // It is essential that cancelUpdate is called if the

17          // cached content is not rebuilt

18          admin. cancelUpdate (myKey);

19      }

20 }

即,本线程先试图重建缓存,如果再次发生异常,则本线程(不管三七二十一)直接调用 cancelUpdate 函数。

现在的一些 OSCache Manager 工具类, get 方法也就实现为一旦捕获 NRE 异常就直接 canelUpdate :

1 public OSCache get(String key, int myRefreshPeriod){

2       try {

3           return (OSCache) this .admin. getFromCache (key,myRefreshPeriod);

4       } catch ( NeedsRefreshException ex){

5           this .admin. cancelUpdate (key);

6           return null ;

7       }

8   }

四,OSCache 在 Race Condition 下缓存重建的特殊场景

简单地说,就是:

线程1 正在重建缓存;

线程2 读取缓存时得到 NRE 异常,主动 cancel update ;

线程1 重建缓存完毕,却发现状态被改为了 UPDATE_CANCELLED ,与期望不符,于是抛出异常 java.lang.IllegalStateException  。

具体过程如下:

(0 )缓存过期;

(1 )线程 T1 获得 update lock ,并开始调用 putInCache 函数  ;

(2 ) 线程 T2 在 T1 没有结束 update 之前,也开始 getCacheEntry   了;

(3 )T2 调用的 getCacheEntry 函数捕获  NeedsRefreshException  异常;

(4 )T2 调用 cancelUpdate 函数来“取消所有试图更新本 cache entry 的操作”, 于是 EntryUpdateState 变为 UPDATE_CANCELLED ,它是一个正整数 2

(5 )T1 其实已经重建了缓存

(6 )T1 随后调用 completeUpdate (EntryUpdateState.java,93 行) 来通知那些等着本次更新操作的线程; 但 completeUpdate 函数却发现当前 EntryUpdateState  居然不等于 UPDATE_IN_PROGRESS (对应0 ),而是 UPDATE_CANCELLED (对应2 ),于是抛出异常 。即下面代码抛出的 IllegalStateException 异常,文字通常为:“ Cannot complete cache update - current state ( 2 ) is not UPDATE_IN_PROGRESS ”,其中的 2 就是指  UPDATE_CANCELLED :

1.     /**

2.      * Updates the state to <code>UPDATE_COMPLETE</code>. This should <em>only</em>

3.      * be called by the thread that managed to get the update lock.

4.      * @return the counter value after the operation completed

5.      */

6.     public   int  completeUpdate() {

7.          if  (state != UPDATE_IN_PROGRESS) {

8.              throw   new  IllegalStateException( "Cannot complete cache update - current state ("  + state +  ") is not UPDATE_IN_PROGRESS" );

9.         }

10.

11.      state = UPDATE_COMPLETE;

12.       return  decrementUsageCounter();

13.  }

总之,按目前 OSCacheManager 的做法,在高并发环境下,一旦一个 OSCache 缓存失效,而缓存的数据片段很大,那么很有可能让其他线程在 getFromCache 时有机会捕获 NRE 异常,最终导致做缓存重建的线程抛出 IllegalStateException 异常,虽然此时缓存已经重建完毕。

OSCache 缓存重建在 Race Condition 下的 NRE 问题相关推荐

  1. java race condition_java 多线程下race condition问题

    这个问题的讨论来自内部的一个关于"多线程环境下使用Hashmap的安全问题"的讨论,HashMap多线程的问题之前已经提过一次,见之前的blog.本篇文章主要讨论多线程下race ...

  2. Django下防御Race Condition漏洞

    今天下午在v2ex上看到一个帖子,讲述自己因为忘记加分布式锁导致了公司的损失: 我曾在<从Pwnhub诞生聊Django安全编码>一文中描述过关于商城逻辑所涉及的安全问题,其中就包含并发漏 ...

  3. java race condition_java多线程(一)Race Condition现象及产生的原因

    什么是Race Condition 首先,什么是Race Condition呢,Race Condition中文翻译是竞争条件,是指多个进程或者线程并发访问和操作同一数据且执行结果与访问发生的特定顺序 ...

  4. docker 不使用缓存重建镜像

    使用Dockerfile构建镜像可以利用它的缓存功能:只有在命令已更改的情况下,才会重建已构建的步骤.下面是重新构建之前涉及到的to-do app的示例: $ docker build . Sendi ...

  5. python 很高兴问题_Python 3.7曾有一个很老的GIL竞态条件(race condition),我是这么解决的...

    Python部落(python.freelycode.com)组织翻译,禁止转载,欢迎转发. 作者:Victor Stinner 作为Python最关键的组成部分之一:GIL(全局解释器锁),我花了4 ...

  6. 架构系列---利用zookeeper 分布式锁解决缓存重建冲突实战

    上一篇 分布式缓存重建并发冲突问题以及zookeeper分布式锁解决方案, 主要讲解了分布式缓存重建冲突原因及利用zookeeper分布式锁解决缓存重建冲突问题,本篇接着上篇,实现上篇思路,带你利用z ...

  7. Race Condition漏洞

    Race Condition Race Condition(竞争条件)是一种情形,在该情形下系统或者程序的输出受其他不可控事件的顺序或事件的影响.软件中的Race Condition通常出现在两个并发 ...

  8. java race condition_Java中的Race condition和Critical section(译)

    Java中的Race condition和Critical section(译) race condition,即竞态,是一种可能发生于critical section中的特殊状态,critical ...

  9. Race condition

    在很多门课上都接触到race condition, 其中也举了很多方法解决这个问题.于是想来总结一下这些方法. Race condition 它旨在描述一个系统或者进程的输出依赖于不受控制的事件出现顺 ...

最新文章

  1. 2018-2019 网络对抗技术 20165231 Exp5 MSF基础应用
  2. ARM汇编加载/存储指令
  3. 初识Python导图笔记
  4. 28. Avoid returning handles to object internals
  5. 线性排序算法-堆排序 (2)
  6. 深度学习:神经风格转移
  7. Nginx初学者指南
  8. web渗透--rpcbind利用
  9. DAP -Link 仿真下载 STM32 教程
  10. 微信小程序富文本编辑器
  11. win10开机出现任务栏卡死无反应,桌面点击正常,重启任务管理器无效
  12. js调用原生android应用的方法
  13. 新媒体运营的基本方法——四步法让你成为新媒体业内大佬
  14. c# 游戏设计:人物走动动画
  15. 序设计思维与实践 CSP-M4
  16. 苹果icloud解锁_如何绘制苹果iCloud图标– Photoshop教程
  17. 初识Vulkan渲染管线
  18. word导出html乱码怎么办,Word文档文件变成乱码如何完全修复?-修复百科
  19. 中国各朝代统治时间列表
  20. 编程规范和范例(转)-续

热门文章

  1. 20190425 看数据统计和数据挖掘让我写了一篇杂文
  2. abstract类中不可以有private的成员_UML类图还不懂?来看看这版乡村爱情类图,一把学会!
  3. 永恒之蓝--漏洞修补
  4. 百度UE-第二篇(ue的上传配置)
  5. ChatGPT 余额查询接口
  6. Leetcode 98:验证二叉搜索树
  7. memcpy函数的介绍以及实现
  8. Caffe 在Windows 10下的安装
  9. 清除WORD格式HTML垃圾样式
  10. [SDCard_FatFs笔记][一]STM32F7使用SDMMC外设移植FatFs遇到f_mount挂载成功,而f_open函数未运行的解决方法