高并发文章浏览量计数系统设计

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
简介: 最近因为个人网站的文章浏览量计数在Chrome浏览器下有BUG,所以打算重新实现这个功能。

最近因为个人网站的文章浏览量计数在Chrome浏览器下有BUG,所以打算重新实现这个功能。


原本的实现很简单,每次点击文章详情页的时候,前端会发送一个GET请求articles/id获取一篇文章详情。这个时候,会把这篇文章的浏览量+1,再存进数据库里。


这个实现原本可以实现这个功能,但是后来我才发现,我犯了一个很致命的错误:在GET请求的业务逻辑里进行了数据的写操作!

原则来讲,GET请求应该具有幂等性,即短时间内同时两个一模一样的GET请求,返回的结果也应该是一样的。而我原本的实现就破坏了GET请求的幂等性。


恰好,在Chrome浏览器里,我的文章详情页会发送两次GET请求。这疑似Chrome浏览器和nuxt服务端渲染之间的一个BUG,目前还没有定位到具体原因。


但无论如何,后端应该是可以避免这样的BUG,即使某用户短时间内请求两次或者多次,也应该只增加一次浏览量计数。


由于最近在学习高并发方面的知识,所以这里也考虑一下,如果一个高并发的文章浏览量计数系统,应该如何设计?

先来理一下需求。


需求


  1. 用户可以是匿名的,不需要登录
  2. 每当一个用户点击了一个文章的详情页面,这个文章的浏览量应该+1
  3. 用户应该能立即看到自己点击文章后浏览量+1的反馈
  4. 浏览量这个数据存在Mysql和ElasticSearch里面,要最终一致(不要求强一致)
  5. 作者可能在后台编辑文章,然后保存文章。如果在这期间有浏览量的增加,保存文章的时候不应该覆盖掉这段时间的浏览量增量。
  6. 应该在服务端对用户的请求去重,防止用户不断刷新或者使用爬虫不断请求某个API(建议通过IP)
  7. 要过滤掉百度和谷歌的爬虫请求(根据User-Agent头判断,可以先不做)
  8. 要高性能地实现“查看浏览最多文章列表”的功能。
  9. 尽可能优化性能,满足多个用户的高并发需求。


设计思路


如果要满足高并发,那首先考虑用异步和缓存。所以考虑使用多线程加Redis的解决方案。

请求流程:

  1. 用户点击某篇文章详情页
  2. 前端发送一个PUT请求/articles/{id:\\d+}/view
  3. 后端使用线程池执行一个异步任务,立即返回给前端200响应。
  4. 前端得到200响应后,立即把当前文章的浏览量+1,满足需求3。

后端主要逻辑:

后端的主要思路是暂时把增加的浏览量(假设某篇文章为n)放进Redis里,然后每隔一段时间刷新到Mysql数据库和ElasticSearch存储里,让这篇文章的浏览量在现有的基础上加n,然后把Redis这篇文章的浏览量清零。

  1. 后端首先判断redis里时候有没有当前ip对这篇文章的浏览记录,这个key为:isViewd:articleId:ip。如果有,就说明之前浏览过,就什么也不做,直接返回。如果没有,就加上这个key。时间可以设置为1小时过期,防止占用过多内存。这里使用Redis的string类型。
  2. 如果第5步的结果是没有,那就在Redis里给这篇文章的浏览量+1。Redis的这个支持原子操作,所以不用担心并发问题。key为viewCount:articleId,value为缓存的浏览量。完成后当前线程任务就结束了。这里使用Redis的string类型。这些key应该没有过期时间。
  3. 弄一个定时任务,比如每5分钟,去Redis里拿缓存的浏览量,拿到后就更新到数据库和ElasticSearch里,并把Redis的数据清零。为了防止并发带来的问题,这里应该是拿到m,就在Redis里减去m,而不是直接设置为0。
  4. 为了节约内存,应该删除不必要的key,按照业务逻辑来看,如果一篇文章长时间没有人浏览,可能这篇文章比较“旧”了,我们可以考虑删除它在Redis里面的key。所以我们可以在第6步,每次在Redis里进行浏览量+1操作时,记录下一个时间戳。所以Redis可以使用hash类型,一个字段存最后操作时间,一个字段存浏览量。而在第7步里,我们可以顺便删除掉最后操作时间小于十天前的key。
  5. 保存更新文章的时候,应该只更新其它字段,而不更新浏览量这个字段。或者执行一遍第7步的逻辑。由于Redis加减操作的原子性,这里不用担心并发问题。如果当前线程把一篇文章的浏览量在Redis里减了m,那定时任务线程应该得到的是减了m之后的结果,所以数据会是一致的。
  6. 关于需求8,在并发量不算特别大的时候,我们还是去取数据库里面的数据,根据数据库里面的浏览量来排序,只是可以在应用里面给它加一个缓存,缓存时间应该与第7步定时任务一致,这里设置为5分钟。

如果并发量特别大,可以考虑不把浏览量存在数据库里,而仅存在Redis里,这样可以得到近乎实时的浏览量存储,而且需求8排序也是实时的(使用zset),但这样可能会耗费大量的内存资源。


后记


虽然最后权衡了并发量和复杂性,我的个人网站的文章浏览逻辑并没有完全按照上述设计思路来做,但上述思路是我对一个高并发文章浏览量计数系统设计的思考,以后如果有机会可以写一个开源的版本。

可能实现起来会更复杂,根据并发量的不同,代码也会有一些差别,以上思路仅供参考。

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
缓存 负载均衡 算法
“软件系统三高问题”高并发、高性能、高可用系统设计经验
​ 总的来说解决三高问题核心就是 “分字诀” 业务分层、系统分级、服务分布、数据库分库/表、动静分离、同步拆分成异步、单线程分解成多线程、原数据缓存分离、分流等等。。。。 直观的表述就是:从前端用的CDN、动静分离,到后台服务拆分成微服务、分布式、负载均衡、缓存、池化、多线程、IO、分库表、搜索引擎等等。都是强调一个“分”字。
2846 0
“软件系统三高问题”高并发、高性能、高可用系统设计经验
|
4月前
|
监控 应用服务中间件 nginx
高并发架构设计三大利器:缓存、限流和降级问题之Nginx的并发连接数计数的问题如何解决
高并发架构设计三大利器:缓存、限流和降级问题之Nginx的并发连接数计数的问题如何解决
|
6月前
|
消息中间件 缓存 监控
直呼内行!阿里大佬离职带出内网专属“高并发系统设计”学习笔记
我们知道,高并发代表着大流量,高并发系统设计的魅力就在于我们能够凭借自己的聪明才智设计巧妙的方案,从而抵抗巨大流量的冲击,带给用户更好的使用体验。这些方案好似能操纵流量,让流量更加平稳得被系统中的服务和组件处理。
|
存储 缓存 应用服务中间件
|
消息中间件 缓存 数据库
好家伙!阿里最新版高并发系统设计涵盖了“三高”所有骚操作
为啥都爱面高并发? 首先为啥面试官喜欢问高并发、性能调优相关的问题,我想有两点原因: 第一,本身互联网区别于传统软件行业的特点之一就是海量请求。传统软件公司每秒用户几个、几十个的请求很常见,但是互联网公司哪怕一个二线的 App,后端接口请求一天几个亿也很正常。业务特点导致对候选人在海量请求相关的技术上考察的会比较多。 第二、高并发性能调优等方面的问题相当于高考试卷里的难题部分。CRUD 谁都会,xx 培训机构培训上三个月,出来都能写。但是对于高性能、高并发这没几把刷子真会玩不起来的。通过这个来区分候选人水平的高低(招人肯定选水平高的)。
98 1
|
算法 Java 应用服务中间件
高并发系统设计之限流
当我们谈论Web应用或者服务,一个重要的话题就不能避免:限流。这是一种保护系统和维持服务稳定性的重要手段。
97 0
高并发系统设计之限流
|
消息中间件 缓存 负载均衡
秒杀系统设计:高并发下的架构考虑
随着互联网的快速发展,电商平台上的秒杀活动越来越受欢迎。然而,高并发的情况下,如何保证秒杀系统的稳定性和可扩展性成为一个非常具有挑战性的问题。在本文中,我们将讨论如何设计一个高效、可靠的秒杀系统。
221 1
|
消息中间件 缓存 分布式计算
真牛!阿里最新发布这份《亿级高并发系统设计手册》涵盖所有操作
前言 我们知道,高并发代表着大流量,高并发系统设计的魅力就在于我们能够凭借自己的聪明才智设计巧妙的方案,从而抵抗巨大流量的冲击,带给用户更好的使用体验。这些方案好似能操纵流量,让流量更加平稳得被系统中的服务和组件处理。 那我们改如何应对大流量的三种方式? 第一种方法:Scale-out。 第二种方法:使用缓存提升性能 第三种方法:异步处理 面试京东,阿里这些大厂遇到这些问题改怎么办? 秒杀时如何处理每秒上万次的下单请求? 如何保证消息仅仅被消费一次? 如何降低消息队列系统中消息的延迟?
|
负载均衡 网络协议 Dubbo
高并发系统设计之负载均衡
通过负载均衡,我们能提高系统的可用性,提升响应速度,同时也能防止任何单一的资源过度使用。
329 0
|
消息中间件 SQL 缓存
高并发系统设计之思考
高并发系统设计之思考
145 0