现在基本所有的网站,不管是新闻博客,还是电商,短视频都会有一个浏览量或者热度的属性,以显示文章或者商品的受欢迎程度。那么这个热度或者说浏览量该怎么设计才比较合理呢?没点击浏览一次就Update数据库的浏览量属性+1?本文就来探讨一下这个问题。
UPDATE blog SET views = views+1 WHERE id=?参考了多个社区博客的设计,因为并不十分清楚其后端实现过程,只能从前端得出以下结论。
CSDN博客:无论是用户登录模式还是用户状态,每次刷新页面浏览数都不变,但是隔天访问,浏览数会+1,没细测。
//堆代码 duidaima.com //获取 Key String key = IPUtils.getIpAddr()+":blog:"+id; //判断是否存在 boolean flag = redisUtil.hasKey(key); if(!flag){ //设置缓存标识并更新数据库 redisUtil.set(key,"true",36000); String nativeSql = "UPDATE blog SET views = views+1 WHERE id=?"; dynamicQuery.nativeExecuteUpdate(nativeSql,new Object[]{id}); }方案二
used_memory_human:910.14K测试之后,可以说基本差不多:
used_memory_human:922.27K下面我们通过代码来实现,引入 redis starter:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>这里,我们只需要两个API即可:
/** * 计数 * @param key * @param value */ public void add(String key, Object... value) { redisTemplate.opsForHyperLogLog().add(key,valu); } /** * 获取总数 * @param key */ public Long size(String key) { return redisTemplate.opsForHyperLogLog().size(key); }然后写个AOP:
@Around("ServiceAspect()") public Object around(ProceedingJoinPoint joinPoint) { Object[] object = joinPoint.getArgs(); Object blogId = object[0]; Object obj = null; try { String value = IPUtils.getIpAddr(); String key = "viewCount:" + blogId; // key 为 文章ID,Value 为请求IP地址 redisUtil.add(key,value); obj = joinPoint.proceed(); } catch (Throwable e) { e.printStackTrace(); } return obj; }博文请求:
/** * 博文 */ @RequestMapping("{id}.shtml") public String page(@PathVariable("id") Long id, ModelMap model) { try{ Blog blog = blogService.getById(id); String key = "viewCount:"+id; Long views = redisUtil.size(key); //直接从缓存中获取并与之前的数量相加 blog.setViews(views+blog.getViews()); model.addAttribute("blog",blog); } catch (Throwable e) { return "error/404"; } return "article"; }业务代码:
/** * 执行顺序 * 1)限流 * 2)布隆 * 3)计数 * 4) 缓存 * @param id * @return */ @Override @ServiceLimit(limitType= ServiceLimit.LimitType.IP) @BloomLimit @HyperLogLimit @Cacheable(cacheNames ="blog") public Blog getById(Long id) { String nativeSql = "SELECT * FROM blog WHERE id=?"; return dynamicQuery.nativeQuerySingleResult(Blog.class,nativeSql,new Object[]{id}); }最后,写个定时任务,夜间入库:
@Scheduled(cron = "0 30 23 * * ?") public void createHyperLog() { logger.info("计数落库开始"); String nativeSql = "SELECT id FROM blog"; List<Object> list = dynamicQuery.query(nativeSql,new Object[]{}); list.forEach(blogId ->{ String key = "viewCount:"+blogId; Long views = redisUtil.size(key); if(views>0){ String updateSql = "UPDATE blog SET views=views+? WHERE id=?"; dynamicQuery.nativeExecuteUpdate(updateSql,new Object[]{views,blogId}); redisUtil.del(key); } }); logger.info("计数落库结束"); }