前言在经历了,缓存、限流、布隆穿透等等一系列加强功能,十万博客基本算是成型,网站上线以后也加入了百度统计来见证十万 的整个过程,我来为大家科普一下关于springboot发表文章功能?下面希望有你要的答案,我们一起来看看吧!

springboot发表文章功能(从SpringBoot构建十万博文聊聊高并发文章浏览量设计)

springboot发表文章功能

前言

在经历了,缓存、限流、布隆穿透等等一系列加强功能,十万博客基本算是成型,网站上线以后也加入了百度统计来见证十万 的整个过程。

但是百度统计并不能对每篇博文进行详细的浏览量统计,如果做一些热点博文排行、48小时排行之类统计,还需要引入浏览量统计功能。

设计

通常情况下,我们只需要每次请求浏览量 1,但是这样真的好吗?或者更直白的讲,真实浏览数准确吗?

UPDATE blog SET views = views 1 WHERE id=?

参考了多个社区博客的设计,因为并不十分清楚其后端实现过程,只能从前端得出以下结论。

基于以上社区的数据,直接 Pass 掉前两位,总结了以下几种方案,都是基于缓存标识实现。

所以说,怎么算都不准确,浏览数本身就是一个不需要太精确的功能,不要想太多,直接使用 IP 文章ID 维度即可。

方案

方案一

得到 GET 请求,在限流之后,缓存之前,判断缓存中是否存在 IP 文章ID是否存在 Key。

如果存在,说明之前浏览过,就什么也不做。如果没有,就加上这个 Key,根据业务设置缓存失效时间,然后更新数据库浏览量 1,下面是代码实现:

//获取 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}); }

方案二

这样基本能保证真实的博文浏览量,你以为就这么结束了吗?我们做的可是一个高并发的博客,直接落库,显得不是逼格太 Low 了!

为了进一步提升性能力,来做下一步优化,判断不存在之后,先不急于更新数据库,先在 Redis 里给这篇文章的浏览量 1,Key 为 viewCount:articleId,value 为缓存的浏览量。然后设置一个定时任务,定时更新 Redis 缓存数据到数据库。

这样,是不是逼格一下子提升了好几个档次!!!下面来介绍一款更有逼格的第三方计数工具。

方案三

一款高并发计数神器 Redis HyperLogLog,她是用来做基数统计的算法,优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定的、并且是很小的。

在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。

什么是基数?比如数据集 {1, 3, 5, 7, 5, 7, 8}, 那么这个数据集的基数集为 {1, 3, 5 ,7, 8}, 基数(不重复元素)为5。

为了校验准确性,博主特意测试了一下,分别测试了,20000 和 100000 的数据量,基本上用了 12KB。

在测试之前 info 查询一下:

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("计数落库结束"); }

小结

撸完计数功能,作为一个个人博客基本上差不多了已经,前后端框架、连接池、限流、缓存、计数、动静分离,HTTPS安全认证、百度收录等等,后面会追加后台管理,模板、插件等等一系列功能,有兴趣的小伙伴可以一起参与进来啊啊啊啊啊啊......、

来源:https://www.cnblogs.com/smallSevens/p/11374237.html

,