Implement an efficient local cache

I don't know if you have these doubts as you often learn.
Why do interviewers always ask about the implementation principle of hashmap?
Is it useful to read the spring source code?
The interviewer always asks me what the thread pool does? Asked so deeply!

Or these doubts: I
have read the principle of hashmap implementation a hundred or eighty times, and the spring source code has been used two or three times. The
thread pool can be handwritten every day, but my company is small and I don't use these technologies in the project.

Today, I will introduce a local caching tool to give you a deeper understanding of these technologies!
Implement an efficient local cache

The cache usage scenarios are as follows:

For example, I’m in the air ticket business, and the project often uses airport, airline, city, country, model and other business information. These data are not updated frequently in 99% of the cases. If you request the interface every time , It would be a waste of user request time.
At this time, the necessity of caching is reflected.

So the question is, how to choose between distributed cache and local cache?

1. Cache performance PK
redis: In the domestic and international air ticket query scenario, a city may query more than 200 routes for a single journey. There are also scenarios such as cabins and transfers in the routes, plus the PK filtering logic for the airlines. There may be 20 flights to redis, 200 routes 20 = 4000 queries; if the qps of the current interface is 1000, 4000 1000 = 1000000 (100w / s) It is such a simple scenario that requires 100w per second /s redis, no matter how high the performance of redis iops is, it can't support it. After fetching data from redis, deserialization is required. Deserialization is a CPU-consuming operation. Such a large amount of data will increase the load on the CPU. redis seems a bit worrying.

本地缓存:将机场,城市这些信息直接放进 JVM 中,由 JVM 统一管理,本地缓存几乎不用考虑性能,也不要序列化。看起来还不错。

2. Cache update PK
redis: redis is a distributed cache, so the corresponding cache timing update needs are handed over to the distributed timing task framework to do it, we use the qschedule of Qunar, open source can use Dangdang elastic -job
local cache: local cache can be implemented using Quartz and spring schedule.

By the way, the following points should be considered when selecting a distributed timing task framework:

1. It must be executed at the specified time (the delay must be less or no)
2. The log is traceable
3. The cluster deployment cannot be repeated
4. The cluster can be elastically expanded
5. Blocking, exception and other processing strategies
6. Whether it is integrated with spring
7. Whether the community is strong, whether the documents are rich and updated in time,
8. Highly available
9. Whether the timed tasks can be reused and executed separately

So in general, the use of cache is mainly for performance, and local cache seems to perform better.

What are the main points of local cache implementation?

Implement an efficient local cache

1. Timing asynchronous execution: both Quartz and spring schedule can achieve asynchronous and timing

2. Initialization: After the project server is started, the cached data has to be loaded. It seems that all timing tasks cannot be done at this point, so the task of timing the cached data can only be handed over to the delayed thread pool ScheduledThreadPoolExecutor. Once completed, the distributed cache will not be used based on initialization.

3. Retry mechanism: When the data source service is abnormal or timed out, the cached data will fail to update, but the abnormality of the interface is a rare case after all. At this time, you need to request the interface again. We can configure the number of retries in the comment in.

4. Update: After the interface request is successful, the new data will replace the old data.
Use con Implement an efficient local cachecurrentHashMap to achieve the same key directly overwrite.

The realization idea of ​​local cache:

Use annotations as the method level, and the configuration requirements can be expressed by the annotation attributes:

2. Define the springbean object to implement the ApplicationListener interface, listen to the springContextRefreshedEvent event container and initialize the cache component after all the business beans are initialized.

@Component("beanDefineConfig")
public class BeanDefineConfig implements ApplicationListener<ContextRefreshedEvent> {
  @Autowired
  private OnlineCacheManager cacheManager;

  @Override
  public void onApplicationEvent(ContextRefreshedEvent event) {
    cacheManager.initCache();
  }
}

3. Initialize the delayed thread pool and get all the objects that implement the CacheBean interface to get the cache configuration

4, OnlineCache object annotation configuration assigned to
the polymerization of all the cache configuration to cacheMap

public class OnlineCacheManager {

  private static final ILog LOGGER = LogManager.getLogger(OnlineCacheManager.class);
  private Map<String, OnlineCache> cacheMap = new ConcurrentHashMap<>();
  private ApplicationContext applicationContext;
  private ScheduledThreadPoolExecutor executor;
  private Object lock = new Object();
  private List<Runnable> executeRunnableList = new ArrayList<>();
  private boolean isInit = true;

  public synchronized void initCache() {
    try {
      if (executor == null) {
        executor = (ScheduledThreadPoolExecutor) Executors.newScheduledThreadPool(5);
      }
      cacheMap.clear();
      executeRunnableList.clear();
      Map<String, CacheBean> map = applicationContext.getBeansOfType(CacheBean.class);
      Collection<CacheBean> beans = map.values();
      for (CacheBean bean : beans) {
        List<Method> methods = OnlineCollectionUtil.findAll(
            Arrays.asList(bean.getClass().getMethods()),
            x -> x.getAnnotation(CacheAnnotation.class) != null);
        for (Method method : methods) {
          OnlineCache cache = new OnlineCache();
          CacheAnnotation cacheAnnotation = method.getAnnotation(CacheAnnotation.class);
          Parameter[] parameters = method.getParameters();
          cache.setContainParam(parameters != null && parameters.length > 0);
          cache.setAutoRefresh(cacheAnnotation.autoFlash());
          cache.setCacheBean(bean);
          cache.setCacheName(cacheAnnotation.name());
          cache.setTimeOut(getTimeOut(cacheAnnotation.timeOut(), cacheAnnotation.timeType()));
          cache.setData(new ConcurrentHashMap<>());
          cache.setParams(new ConcurrentHashMap<>());
          cache.setDescription(cacheAnnotation.description());
          cache.setHandler(convertHandler(method, bean));
          cache.setDependentReference(cacheAnnotation.dependentReference() != null
              && cacheAnnotation.dependentReference().length > 0 ? cacheAnnotation
              .dependentReference() : null);
          cache.setEssential(cacheAnnotation.essential());
          cache.setRetryTimes(cacheAnnotation.retryTimes());
          cacheMap.put(cacheAnnotation.name(), cache);
        }
      }
      // 为了解决缓存之间的依赖问题 不做深究
      List<String> keyList = sortKey();
      for (String key : keyList) {
        OnlineCache cache = cacheMap.get(key);
        executeSaveCache(cache);
        if (cache.isAutoRefresh()) {
          Runnable runnable = () -> executeSaveCache(cache);
          executor.scheduleAtFixedRate(runnable, cache.getTimeOut(),
              cache.getTimeOut(), TimeUnit.MILLISECONDS);
          executeRunnableList.add(runnable);
        }
      }
    } catch (Throwable e) {
      LOGGER.error(e.getMessage(), e);
    }
  }
  }

5. Start requesting the interface and put the result into the cache. The
key is the annotated name and the value is all corresponding result sets Map<String,cacheEntity>

@Component
public class AirportCacheHelper implements CacheBean {
    private static final ILog LOGGER = LogManager.getLogger(AirportCacheHelper.class);

    @Autowired
    OnlineCacheManager onlineCacheManager;
    /**
     * 获取所有的机场信息
     *
     * @return the all airports
     */
    public Map<String, AirportEntity> getAllAirports() {
        return onlineCacheManager.getCache(LocalCacheConstant.CODE_TO_AIRPORT_MAP);
    }

    /**
     * 缓存初始化
     */
    @CacheAnnotation(name = LocalCacheConstant.CODE_TO_AIRPORT_MAP, timeOut = 120, essential = true, retryTimes = 3)
    public Map<String, AirportEntity> initCodeToAirportMap() {
        Map<String, AirportEntity> map = null;
        // 从数据源接口获取需要缓存的数据  故不展示纯业务代码
        List<AirportEntityWs> airports = getAirportsFromSoa();
        if (!CollectionUtils.isEmpty(airports)) {
            map = new HashMap<>(airports.size());
            for (AirportEntityWs soaEntity : airports) {
                if (map.containsKey(soaEntity.getCode())) {
                    continue;
                }
                AirportEntity cacheEntity = new AirportEntity();
                cacheEntity.setCode(soaEntity.getCode());
                cacheEntity.setAddress(soaEntity.getAddress());
                cacheEntity.setAirportPy(soaEntity.getAirportPY());
                cacheEntity.setCityId(soaEntity.getCityID());
                cacheEntity.setCityCode(soaEntity.getCityCode());
                cacheEntity.setDistance(soaEntity.getDistance());
                cacheEntity.setName(soaEntity.getName());
                cacheEntity.setNameEn(soaEntity.getName_En());
                cacheEntity.setShortName(soaEntity.getShortName());
                cacheEntity.setTelphone(soaEntity.getTelphone());
                cacheEntity.setSuperShortName(soaEntity.getSuperShortName());
                cacheEntity.setShortNameEn(soaEntity.getShortName_En());
                cacheEntity.setLocType(soaEntity.getLocType());
                cacheEntity.setLatitude(soaEntity.getLatitude());
                cacheEntity.setLongitude(soaEntity.getLongitude());

                map.put(cacheEntity.getCode(), cacheEntity);
            }
        }
        return map;
    }

    // 在 OnlineCacheManager 中获取缓存
    public <T> T getCache(String cacheName, Object... params) {
    T t = null;
    try {
      if (cacheMap.containsKey(cacheName)) {
        OnlineCache cache = cacheMap.get(cacheName);
        long nowTime = System.currentTimeMillis();
        if (!cache.isContainParam()) {
          if (cache.isAutoRefresh()) {
            t = (T) cache.getData().get(cacheName);
          } else {
            t = getStaticCache(cacheName, cache, nowTime);
          }
        } else {
          if (params != null && params.length > 0) {
            StringBuilder cacheKey = new StringBuilder(cacheName);
            for (Object o : params) {
              cacheKey.append(o.hashCode());
            }
            cache.getParams().put(cacheKey.toString(), params);
            t = getStaticCache(cacheKey.toString(), cache, nowTime);
          }
        }
      }
    } catch (Throwable e) {
      LOGGER.error(e);

    }
    return t;
  }

6. Call through API CacheManageHelper.getXXXEntity(Stringcode)


/**
     * 根据机场三字码获取机场信息
     *
     * @param code the code
     * @return the airport entity
     */
    public static AirportEntity getAirportEntity(String code) {
        AirportEntity entity = null;
        if (StringUtils.isEmpty(code)) {
            return entity;
        }
        Map<String, AirportEntity> dataMap = airportCacheHelper.getAllAirports();
        if (null != dataMap && dataMap.containsKey(code)) {
            entity = dataMap.get(code);
        }
        return entity;
    }

to sum up:

This local cache is not difficult to implement. The key is to find business pain points and flexibly use the technology learned to implement it.

After reading the source code of springIOC or springMVC, spring will have a deep understanding of the use of reflection. If you know that reflection will be needed later, you can think of whether I can encapsulate a handler. After
reading the source code of hashmap, you will know that it is based on cached data. Size to initialize the hashmap to avoid excessively large hashmap expansion and occupy CPU resources. Only after seeing the thread pool did you know that the timing task cannot do this when the cache is initialized. It just so happens that Spring's schedule is also a time-limited thread pool, which is the result of long-term technology accumulation.

After reading this cache component, do you have a deeper understanding and application of these technologies?
My favorite friends come to pay attentionImplement an efficient local cache

Guess you like

Origin blog.51cto.com/15075523/2606417