日常开发中,经常会遇到商品浏览量,文章阅读量,帖子点赞量等等,一般的实现就是每操作一次,数据库记录+1。
这种操作理论上是可以实现的,但是在高用户的情况下,频繁更新数据库,导致数据库压力过大导致的数据库挂掉却是得不偿失。
在网上看到有使用redis的自增value去实现,实现方案大致是服务启动时,通过@PostConstruct读取数据库中商品的游览量,存到缓存中,后续涉及到的游览量的读取,修改都在redis中操作。
但是如果商品的数据量过大,会占用redis过多内存,冷热数据不分离也会导致冷门商品占用redis内存资源。
最终的实现方案是在商详的时候存入到redis,这样能避免全部商品数据写入redis,占用redis内存。最后通过定时任务回写到数据库,删除redis缓存,释放内存。
实例代码如下
public ProductVo queryClientProductDetail(Long id) throws CommonException {
// 获取商品详情
ProductVo productVo = queryProductDetail(id);
// 从缓存取游览量
String KEY = RedisConstants.PRODUCT_VISIT_COUNT_PRE + "storeId:" + productVo.getStoreId() + "productId:" + productVo.getId();
Number visitCount = redisCache.getCacheObject(KEY);
if (Objects.nonNull(visitCount)) {
productVo.setVisitCount(visitCount.longValue());
} else {
redisCache.setCacheObject(KEY, productVo.getVisitCount());
}
// 游览量自增
redisCache.incr(KEY, 1);
return productVo;
}
定时任务回写
@Slf4j
@Component("productvisitCountAutoSyncTask")
public class ProductvisitCountAutoSyncTask {
@Autowired
private RedisCache redisCache;
@Autowired
private ProductServiceImpl productService;
public void runProductvisitCountAutoSyncTask() {
Collection<String> keys = redisCache.keys(RedisConstants.PRODUCT_VISIT_COUNT_PRE + "*");
if (CollectionUtils.isEmpty(keys)) {
return;
}
Map<String, Number> visitCountMap = keys.stream().collect(Collectors.toMap(key -> key, key -> redisCache.getCacheObject(key)));
if (MapUtils.isEmpty(visitCountMap)) {
return;
}
List<Product> updateList = Lists.newArrayList();
visitCountMap.forEach((key, value) ->
updateList.add(new Product().setId(Long.valueOf(key.substring(key.lastIndexOf(":")+1))).setVisitCount(value.longValue()))
);
productService.updateBatchById(updateList);
redisCache.deleteObject(keys);
}
}