EdsCache通用缓存框架——(2.7) 批量模式

达芬奇密码2018-06-22 17:00
场景需求
我们经常有批量查询的接口,如:
 public Map getUserByIds(List ids){
     return userDao.getUsers(ids);
 }
缓存中每个UserDTO是单独存储的,每一个UserDTO对应着如user_{id}的key,
 且需要提供批量查询接口,考虑到性能,需要批量的从DB中查询出数据,而不是每个id进行一次查询操作,
 且入参List ids中,可能部分id能命中缓存,部分没有命中,
 这种场景下,缓存框架该抽象出怎么样的统一模式,提供怎么样的功能,来解决这个问题?

解决思路
  • 1.遍历入参中的ids,分别组装成缓存key列表,批量的先进行缓存查询,如果全部id都命中缓存,直接返回;(Ps:视情况而定,不是每种不同的底层缓存,对每种数据结构都提供了批量查询的接口,如分布式redis对简单类型的数据,目前就不支持批量查询)
  • 2.筛选出没有命中缓存的ids集合,替换函数的入参,进行DB批量数据加载,所以只会加载没有命中的部分数据;
  • 3.将原先没命中缓存,DB加载后的数据集合,回写缓存;
  • 4.DB数据加载完成后,与命中缓存的数据合并,形成最终的返回结果。
优点:
能解决常见的批量查询结合缓存的问题。
缺点:
对接口设计有一定限制,入参列表中只能有一个集合,作为遍历的ForeachKey,填入key模板中;返回值只能是Map的形式;
有一定的理解学习成本,尤其是进行了ids的入参替换,不了解缓存框架的新同学会不知道入参在切面中被悄悄替换了。

EdsCache使用样例
通过在@EdsCache中指定batchOp,表明使用批量模式,并在需要遍历的集合入参前加上@EdsCacheForeachKey注解。
 @EdsCache(key = constants.USER_CACHE_PREFIX,
         expire = constants.EXPIRE_SECOND, 
         batchOp = CacheBatchOpEnum.LOAD_MISS_ONLY)
 public Map getUserByIds(@EdsCacheForeachKey List ids){
     return userDao.getUsers(ids);
 }
启用批量模式时,入参列表中目前只支持设置一个 @EdsCacheForeachKey ,且必须是Collection类型。
 返回值类型只能是Map,Map的key为每项EdsCacheForeachKey,如id;Map的Value为单个数据项,如UserDTO。
其中,
 CacheBatchOpEnum.LOAD_MISS_ONLY 表明只会从DB中加载没命中缓存的数据,也就是会进行参数替换;
 CacheBatchOpEnum.LOAD_ALL 表明不进行参数替换,会从DB中加载全量ids的数据。

结合Hash结构
批量模式常常结合hash数据结构(链接)使用,如redis的mget接口能进行批量查询,如:
 @EdsCache(key = constants.PAPER_SETTING_CACHE_PREFIX, expire = constants.EXPIRE_SECOND, batchOp = CacheBatchOpEnum.LOAD_MISS_ONLY)
 @EdsCacheHash
 public Map getPaperSettingBatch(@EdsCacheKey Long paperId, @EdsCacheForeachKey List settingKeys){
     //db的批量查询
 }
使用@EdsCacheHash指定使用Hash数据结构,此时@EdsCacheForeachKey是指subKey中的遍历参数,而返回值中Map的Value,是Hash内subkey对应的单个item的数据。

结合负载均衡
批量模式与负载均衡功能可以结合使用。
 在批量查询缓存的时候,会为每一个key,添加随机结尾,然后进行缓存查询,如:
 @EdsCache(key = constants.USER_CACHE_PREFIX,
         expire = constants.EXPIRE_SECOND, 
         batchOp = CacheBatchOpEnum.LOAD_MISS_ONLY)
 @EdsCacheLoadBalance(constants.BALANCE_COUNT)
 public Map getUserByIds(@EdsCacheForeachKey List ids){
     return userDao.getUsers(ids);
 }
如果使用的是Hash数据结构,则是先为外层key添加随机结尾,确定是哪一个Hash后,对同一个Hash进行批量的mget操作,如:
 @EdsCache(key = constants.PAPER_SETTING_CACHE_PREFIX, expire = constants.EXPIRE_SECOND, batchOp = CacheBatchOpEnum.LOAD_MISS_ONLY)
 @EdsCacheHash
 @EdsCacheLoadBalance(constants.BALANCE_COUNT)
 public Map getPaperSettingBatch(@EdsCacheKey Long paperId, @EdsCacheForeachKey List settingKeys){
     //db的批量查询
 }

结合空值缓存
如果批量模式,可以跟空值缓存(链接)功能结合使用,ForeachKey比如ids中,即使DB中没有对应数据,也会写一个空值到缓存中,如:


List id包括1L、2L、3L三个id,
 其中只有id=1L的时候命中缓存,则只会去DB中加载id为2L、3L的数据,
 如果DB查询只返回了2L的数据,3L没有对应数据,则会为3L对应的key写一个空值到缓存中,下次再批量时,3L命中空值缓存,不会再穿透到DB中(空值是可以另外设置较短的超时时间的)。
 如图:
使用样例:
 直接在原有基础上,叠加空值缓存注解即可,可以在EdsCacheNull中指定较短的expire,否则默认使用非空值的expire,如:
 @EdsCache(key = constants.USER_CACHE_PREFIX,
         expire = 120, 
         batchOp = CacheBatchOpEnum.LOAD_MISS_ONLY)
 @EdsCacheNull(expire=30)
 public Map getUserByIds(@EdsCacheForeachKey List ids){
     return userDao.getUsers(ids);
 }

结合本地缓存
批量模式也可以与本地缓存结合使用,当查询缓存时,本地缓存命中,则不会穿透到分布式缓存。

本文来自网易实践者社区,经作者陈婷授权发布。  

相关阅读:EdsCache通用缓存框架——(1)总览导航