J2Cache Level 2 Cache'No Auto Update'

Posted by LazyJones on Sun, 23 Jun 2019 00:19:59 +0200

Popularization of secondary caching for small group partners Description of two-level caching Afterwards, the small partners in the group developed the announcement function by using the secondary cache.

The functions are as follows:

<cache name="notice"
       maxElementsInMemory="100000"
       eternal="false"
       overflowToDisk="false"
       timeToIdleSeconds="300"
       timeToLiveSeconds="3600"
       memoryStoreEvictionPolicy="LFU">
</cache>
@Override
@Cacheable(value = "notice")
public List<TbNotice> getNoticeInfo(TbNoticeSo notice) {
   return remindMapper.getNoticeInfo(notice);
}

The problem arises and it is found that even after 3600s, the cache will not be automatically cleared.

For debugging convenience, modify ehcache configuration


<cache name="notice"
       maxElementsInMemory="100000"
       eternal="false"
       overflowToDisk="false"
       timeToIdleSeconds="2"
       timeToLiveSeconds="2"
       memoryStoreEvictionPolicy="LFU">
</cache>

After 2s, it will expire, and a strange phenomenon is found. When debugging, the cache will be cleared after 2S is found by calling the cache interface. However, the actual situation is always unable to update the cache automatically, and the development partners are puzzled.

So what's the problem?

First of all, the meaning of caching has been cleared up in the above description, the purpose is simply to save the calculated data for easy access next time.

Then there is bound to be a key in the storage area for mapping.

So what is the default value of key?

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Cacheable {
 
   /**
    * Name of the caches in which the update takes place.
    * <p>May be used to determine the target cache (or caches), matching the
    * qualifier value (or the bean name(s)) of (a) specific bean definition.
    */
   String[] value();
 
   /**
    * Spring Expression Language (SpEL) attribute for computing the key dynamically.
    * <p>Default is "", meaning all method parameters are considered as a key.
    */
   String key() default "";
 
   /**
    * Spring Expression Language (SpEL) attribute used for conditioning the method caching.
    * <p>Default is "", meaning the method is always cached.
    */
   String condition() default "";
 
   /**
    * Spring Expression Language (SpEL) attribute used to veto method caching.
    * <p>Unlike {@link #condition()}, this expression is evaluated after the method
    * has been called and can therefore refer to the {@code result}. Default is "",
    * meaning that caching is never vetoed.
    * @since 3.2
    */
   String unless() default "";
}

Obviously, when the key is not filled in, all the parameters are transmitted as keys.

The code is as follows

   /**
    * Computes the key for the given caching operation.
    * @return generated key (null if none can be generated)
    */
   protected Object generateKey() {
      if (StringUtils.hasText(this.operation.getKey())) {
         EvaluationContext evaluationContext = createEvaluationContext(ExpressionEvaluator.NO_RESULT);
         return evaluator.key(this.operation.getKey(), this.method, evaluationContext);
      }
      return keyGenerator.generate(this.target, this.method, this.args);
   }
 
   private EvaluationContext createEvaluationContext(Object result) {
      return evaluator.createEvaluationContext(this.caches, this.method, this.args, this.target, this.targetClass, result);
   }
 
   protected Collection<Cache> getCaches() {
      return this.caches;
   }
}

The key expression supports the spel expression, so when the user does not fill in the key, the default key Generator is executed to generate it.

public class DefaultKeyGenerator implements KeyGenerator {
 
   public static final int NO_PARAM_KEY = 0;
   public static final int NULL_PARAM_KEY = 53;
 
   public Object generate(Object target, Method method, Object... params) {
      if (params.length == 1) {
         return (params[0] == null ? NULL_PARAM_KEY : params[0]);
      }
      if (params.length == 0) {
         return NO_PARAM_KEY;
      }
      int hashCode = 17;
      for (Object object : params) {
         hashCode = 31 * hashCode + (object == null ? NULL_PARAM_KEY : object.hashCode());
      }
      return Integer.valueOf(hashCode);
   }
 
}

At this point, it is clear that the value at this point is only related to the parameter (then the hashCode of the parameter will affect the generated key)

pojo still needs to copy hashcode method and equals method

It is conceivable that even the same parameters of equals can not get the same hashcode value without copying the hashcode method. In real sense, only objects with the same memory address can be considered equal.

Memory leaks are likely to occur once someone does not use any key and does not copy the pojo of hashcode as a parameter.

Why does memory leak occur? Have we set an expiration date?

Let's consider implementing expired functionality as developers. Consider the following options

  1. protected byte[] getKeyName(Object key) {
        if (key instanceof Number)
            return ("I:" + key).getBytes();
        else if (key instanceof String || key instanceof StringBuilder || key instanceof StringBuffer)
            return ("S:" + key).getBytes();
        return ("O:" + key).getBytes();
    }
    Set a thread globally to listen for all key s that need to expire and delete them once they expire
  2. Setting the key while there is an expiration time, then starting a delayed thread at the same time automatically clears the corresponding time
  3. Do you need to expire when check ing without automatic expiration?

    For redis, the first scheme is adopted. The second scheme, which automatically deletes expired keys from the whole world, is almost impossible to implement. It may lead to a sharp increase in the number of threads, followed by the possibility of canceling expired keys and so on.
    ehcache uses the third option. The specific call stack is as follows

    When the user calls get, he checks isExpired. If it expires, he calls cacheExpiredListener and removes Key at the same time.
    So let's see why the parameters of the cache annotation method should be cached.

    /**
     * Created by PPbear on 2017/6/12.
     */
    public class TbNoticeSo extends So {
     
        private static final long serialVersionUID = -3432391375970229933L;
       
    }

    The entity does not override the hashCode method equals method toString method, so look at the parent class

    public class So implements Serializable, Pagable, Sortable {
     
        private static final long serialVersionUID = 436513842210828356L;
       
        
     
        @Override
        public String toString() {
            return "So{" +
                    "currentPage=" + currentPage +
                    ", pageSize=" + pageSize +
                    ", pkId='" + pkId + '\'' +
                    ", idOwnOrg='" + idOwnOrg + '\'' +
                    ", sorts=" + sorts +
                    '}';
        }
     
       
    }

    The parent class also does not override hashCode and equals methods. Extended reading Always override hashCode when overriding equals   

  4. Then each time a page requests to construct parameters, the ehcache will keep caching different key s until the oom or the upper limit of the cache is reached. As mentioned above, if this is the case, why did you develop and modify the database to get the previous value? Not every query? Then explain the read order

  5. We read the cache order as follows - > L1 - > L2 - > DB Level 1 cache, ehcache Level 2 cache, redis here, to store objects in redis, which involves serialization. What is the key that we store r object in redis? Look at the results first.

  6. public void put(Object key, Object value) throws CacheException {
        if (key == null)
            return;
        if (value == null)
            evict(key);
        else {
            try {
                redisCacheProxy.hset(region2, getKeyName(key), SerializationUtils.serialize(value));
            } catch (Exception e) {
                throw new CacheException(e);
            }
        }
    }
    

     

protected byte[] getKeyName(Object key) {
    if (key instanceof Number)
        return ("I:" + key).getBytes();
    else if (key instanceof String || key instanceof StringBuilder || key instanceof StringBuffer)
        return ("S:" + key).getBytes();
    return ("O:" + key).getBytes();
}

Obviously, if toString is used directly here, the corresponding parameter toString has indeed been overwritten in the parent class, so as long as toString is the same in the object here, the secondary cache can be obtained.

Therefore, the value of the secondary cache will be available here. Therefore, the database will not be queried. So once the database values have been queried, they will not be automatically updated.

When developing reusable debugging functions to invoke caches, since the instance objects have not changed, it is natural to detect expiration notifications when calling keys, as described in Scheme 3, so that the secondary cache also remove s the corresponding key.

Naturally, there can be a "debug-time cache will automatically expire" mysterious event.

 

There are many solutions.

My plan is as follows

Modify the corresponding cache annotations as follows


@Override
@Cacheable(value = "notice", key = "'notice'")
public List<TbNotice> getNoticeInfo(TbNoticeSo notice) {
   return remindMapper.getNoticeInfo(notice);
}

Everything is back to normal.

Topics: Ehcache Redis Spring Attribute