一个oscache的小陷阱

1.问题

很久以前遇到过一个oscache的问题,今天又有人遇到了,记录一下以便后续查询。

 

GeneralCacheAdministrator的

 public Object getFromCache(String key) throws NeedsRefreshException {
        return getCache().getFromCache(key);
    }

系列方法如果抛出了 NeedsRefreshException ,就需要catch住异常,然后put这个key的cache,或者取消update。如果不进行这两个操作中的一个,后续线程再来取这个key的数据时,就会block住!

 

一般要这样用:

  Object object;
  try {
   object = cacheAdministrator.getFromCache(cacheKey,DEFAULT_REFRESH_PERIOD);
  } catch (NeedsRefreshException e1) {
   cacheAdministrator.putInCache(cacheKey, "content");
   // or : cacheAdministrator.cancelUpdate(cacheKey);
  }

 

 

2.原因分析

GeneralCacheAdministrator的getFromCache方法中,有如下逻辑:

 synchronized (updateState) {
                    if (updateState.isAwaitingUpdate() || updateState.isCancelled()) {
                        // No one else is currently updating this entry - grab ownership
                        updateState.startUpdate();
                        
                        if (cacheEntry.isNew()) {
                            accessEventType = CacheMapAccessEventType.MISS;
                        } else {
                            accessEventType = CacheMapAccessEventType.STALE_HIT;
                        }
                    } else if (updateState.isUpdating()) {
                        // Another thread is already updating the cache. We block if this
                        // is a new entry, or blocking mode is enabled. Either putInCache()
                        // or cancelUpdate() can cause this thread to resume.
                        if (cacheEntry.isNew() || blocking) {
                          do {
                                try {
                                    updateState.wait();
                                } catch (InterruptedException e) {
                               }
                            } while (updateState.isUpdating());
                            ...

 

以上方法的意思是,如果第一次发现没有命中,就设置 accessEventType = CacheMapAccessEventType.MISS(或者STALE_HIT),在后面代码中抛出异常。

这时如果有另外一个线程调用同样的key来取,就会走入updateState.wait();的代码,等待更新完成。如果第一次没有命中的线程忽略了异常,就会一直wait了。

 

cancelUpdate的对应解锁代码如下:

if (state != null) {
                    synchronized (state) {
                        int usageCounter = state.cancelUpdate();
                        state.notify();
                        
                        checkEntryStateUpdateUsage(key, state, usageCounter);
                    }

 putInCache也类似,在内部的 completeUpdate(key)方法中,不赘述。

 

3.思考

OSCache这样的做法容错性太弱,而且javadoc中强调不够.

可能他的初衷是为了避免取到初始化一半的cache对象,但是又害怕同步块降低并发读效率的一个折中方案。

个人觉得如果要保护,使用读写锁会比较好。写阻塞读,读相互不阻塞。

 

<以上思考有问题,如果是那样,只需要在put的时候做同步,不让get就好了>

实际上它是为了达到这样的流程,在高并发访问的情况下,如果一个key他miss了,就应该由这个线程来put,其他正在取同一个key的线程block住,避免大家一起去做具体的业务,压力太大。但是它的wait应该有个超时时间,超时跑个异常也好,否则线程hang在那里,比较难排查和监控。

你可能感兴趣的:(OScache)