我是靠谱客的博主 土豪彩虹,这篇文章主要介绍关于缓存细粒度化后的查询优化,现在分享给大家,希望可以做个参考。

 高性能MYSQL中有这部分的描述。此处主要记录下细粒度后结合持久型DB查询的实现问题。

场景为普通的对象查询,定义一个接口方法,目的是查询id列表对应的映射数据,key为id(唯一标识即可),value为对应值对象

复制代码
1
Map<Long, DemoDTO> list(Set<Long> idSet);

 定义好一个通用方法

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
/** * 结合MYSQL的管道查询 * * @param targetSet 目标集合 * @param dbResultMapGetter 未命中缓存时,从MYSQL获取数据映射 * @param cacheKeyPrefix 缓存前缀 * @param <K> map result key * @param <V> map result value * @return 最终查询结果映射 */ <K, V> Map<K, V> pGetCombineDb(Set<K> targetSet, Function<Set<K>, Map<K, V>> dbResultMapGetter, String cacheKeyPrefix);

 其中targetSet即为查询的id列表,dbResultMapGetter 定义为从MYSQL等持久化库中获取的未命中缓存的对象映射数据接口,cacheKeyPrefix为定义的缓存前缀,objectNull为避免缓存穿透而缓存的空对象。具体实现代码参考如下:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
@SuppressWarnings("unchecked") @Override public <K, V> Map<K, V> pGetCombineDb(Set<K> targetSet, Function<Set<K>, Map<K, V>> dbResultMapGetter, String cacheKeyPrefix) { List<String> keys = targetSet.stream().map(e -> RedisUtils.mergeKey(cacheKeyPrefix, e)) .collect(Collectors.toList()); List<V> cacheResult = (List<V>) pGet(keys); Map<K, V> resultMap = new HashMap<>(targetSet.size()); Map<K, V> dbResultMap = new HashMap<>(); boolean allHitCache = putCacheResult(new ArrayList<>(targetSet), cacheResult, resultMap, dbResultMap); if (allHitCache) { return resultMap; } // 更新真实的Mysql查询结果 dbResultMap.putAll(dbResultMapGetter.apply(dbResultMap.keySet())); resultMap.putAll(dbResultMap); // 缓存mysql查询结果 List<Tuple2<String, Object>> setCommands = new ArrayList<>(dbResultMap.size()); dbResultMap.forEach((k, v) -> setCommands.add(new Tuple2<>( // 缓存参数拼接策略,一般以:隔开 RedisUtils.mergeKey(cacheKeyPrefix, k), v))); pSet(setCommands); return resultMap; } /** * @param targetSet 查询目前列表 * @param cacheResult 管道查询缓存结果有序列表 * @param resultMap 最终查询结果 * @param dbResultMap mysql查询结果 * @param <K> map result key * @param <V> map result value * @return 是否全部命中缓存 */ private <K, V> boolean putCacheResult(List<K> targetSet, List<V> cacheResult, Map<K, V> resultMap, Map<K, V> dbResultMap) { for (int i = 0; i < targetSet.size(); i++) { if (cacheResult.get(i) == null) { // mysql查询的各个结果先视为空 dbResultMap.put(targetSet.get(i), null); } else { resultMap.put(targetSet.get(i), cacheResult.get(i)); } } return dbResultMap.keySet().size() == 0; }

 这边以redis的pipeline为例,也可用mGet方式

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public List<Object> pGet(List<String> keys) { return stringRedisTemplate.executePipelined((RedisCallback<Object>) redisConnection -> { for (String key : keys) { redisConnection.get(serializeKey(key)); } return null; }, RedisSerializer.json()); } public void pSet(List<Tuple2<String, Object>> kvs) { stringRedisTemplate.executePipelined((RedisCallback<Object>) redisConnection -> { for (Tuple2<String, Object> kv : kvs) { byte[] value = RedisSerializer.json().serialize(kv.getSecond()); if (value == null) { continue; } redisConnection.set(serializeKey(getRealKey(kv.getFirst())), value, Expiration.milliseconds(24 * 3600 * 1000L + // 避免同一时间过期较多缓存 RandomUtils.nextLong(1, 6 * 3600 * 1000L)), RedisStringCommands.SetOption.UPSERT); } return null; }, RedisSerializer.json()); }

具体到业务查询时就可以这么使用:

复制代码
1
2
3
4
5
6
7
8
@Override public Map<Long, DemoDTO> list(Set<Long> idSet) { return redisExtendService.pGetCombineDb(idSet, dbSearching -> { // 用IN查询之类的把未命中缓存的数据查询出来 List<DemoDTO> dbResult = demoDao.findAll(dbSearching); return dbResult.stream().collect(Collectors.toMap(DemoDTO::getId,demo-> demo)); }, "demo"); }

 其中,具体的mysql业务查询、缓存key前缀、缓存空对象等就可以似具体情况自行编写了。

这边提供一个思路,有更优雅的实现可以分享下哈。

最后

以上就是土豪彩虹最近收集整理的关于关于缓存细粒度化后的查询优化的全部内容,更多相关关于缓存细粒度化后内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(59)

评论列表共有 0 条评论

立即
投稿
返回
顶部