程序猿修仙之路--数据结构之设计高性能访客记录系统


菜菜呀,最近我有个想法呀!


(心想:又尼玛有折磨人的想法了。) X总,您说~



我想给咱们的用户做个个人空间,目前先有访客记录就可以,最近访问的人显示在最上边,由于用户量有十几亿,可能对性能要求比较高,三天后上线,你做一下吧!


(心想:一万头羊驼飘过!!)  但是X总,个人空间访问量比较大,需要设计,测试等环节,三天不够呀!~


这个关系到公司的生死存亡,你加加班就行了```


(心想:一亿头羊驼!!) 好吧,X总,我尽最大努力! 苦笑中。。。。~


需求要点


每个用户都有自己的个人空间,当有其他用户来访问的时候,需要添加访客记录,并且更新为最新的访客,这里设计到一个坑,如果存在这个用户的访问记录需要更新用户的最后访问时间。那这个需求在技术维度来说,有什么特点吗?

先想10秒钟,在接着往下看!!!

有什么设计要点呢?


用户的访客记录一定要缓存,要不然怎么抗住大并发呢?

由于最新的访客记录变化非常快,要有一种能快速添加新数据,删除老数据的数据结构。

缓存的篇章今日暂且不说,说一下以上的第二点,也就引出了今日数据结构主角:链表


链表

链表百科:链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表属于线性结构

链表分类


1. 单链表:链表中的元素的指向只能指向链表中的下一个元素或者为空,元素之间不能相互指向。也就是一种线性链表。



public class Node<T>

{

//当前节点的数据元素

public T Data { get; set; }

//当前节点的下一个元素

public Node<T> NextNode { get; set; }

}


2. 双向链表:每个链表元素既有指向下一个元素的指针,又有指向前一个元素的指针,其中每个结点都有两种指针。


public class Node<T>

{

//当前节点的前一个节点

public Node<T> PreNode { get; set; }

//当前节点的数据元素

public T Data { get; set; }

//当前节点的下一个元素

public Node<T> NextNode { get; set; }

}


3. 循环链表:指的是在单向链表和双向链表的基础上,将两种链表的最后一个结点指向第一个结点从而实现循环。



特性


1. 元素的数量可以随时扩充。由于链表在物理的存储单元上是非连续的,这就早就了它天生的优势,我的节点可以在任意符合要求的地方分配内存。

2. 添加元素:

单链表:

当在一个位置N之后插入新元素的时候,单链表首先把当前位置N的元素的Next指针指向新的元素,然后新的元素的Next指针指向N+1位置的元素。当然如果是在首位置插入新元素,只需要把新元素的Next指针指向链表的首元素即可,同理,如果要在单链表尾部插入新元素,只需要把单链表的尾部元素的Next指针指向新元素。至于循环单链表,无所谓首元素和尾元素之分。

双向链表:

在位置N之后添加新元素和单链表原理类似,原理也是修改元素的指针指向。但是这里有一个不同,双向链表要修改前后元素(N位置和N+1位置)和新元素三个Node的指针,所以略微麻烦一点。

3. 删除元素:

单链表:

当要删除位置N的元素的时候,只需要把N-1位置元素的Next指针指向N+1即可。

双向链表:

当要删除位置N的元素的时候,需要修改N-1位置元素的Next指针指向N+1元素,同时还要修改N+1位置元素的Pre指针指向N-1元素。


4. 查找元素:

由于链表的元素在内存中并非连续,所以不能像数组那样拥有O(1)的查找时间复杂度,只能是通过首元素去遍历链表,所以时间复杂度为O(n)


程序设计


给你10秒回到X总的需求中来。通过对链表的介绍,我们该选择哪种链表呢?这里我先说一下我的思路,如有错误请指正:


1. 当一个访客进入个人空间的首页时,大多数情况下,访客记录只需要缓存前100条或者200条即可,也就是说这个场景是存在热点数据的,80%(甚至更高)的请求命中在最近100条访客数据上,很少人会去查看很久以前的记录。所以基于占用内存空间上的考虑,我决定缓存最近的100条访客数据。

2. 假设我用链表缓存了前100条数据,其中在非首位置有一条访客A的记录,此时A又访问的这个用户空间,我需要把A的记录移到首位置,这个过程经历了删除A数据,在首位置添加A数据。假如A开始的位置是N,我在删除N位置数据的时候,需要查找N-1的位置元素修改其指针指向,如果是单链表由于当前位置N的元素中没有N-1位置元素的信息,所有需要重新遍历链表。如果是双向链表呢,位置N的元素中保存了位置N-1的元素,所以没有必要在重新遍历链表了,这也是双向链表对比单链表的优势,虽然内存占用上多了一个指针的内存大小,但是在实际的应用场景中更为常用。所以我选择双向链表。删除操作和添加操作时间复杂度都是O(1).

3.     对同一个空间的访问,必然存在锁和多线程的问题。所以我在选择框架的时候优先选择了基于Actor模型的框架。避免了在同一个用户空间上加锁的操作。

4. 由于基于Actor模型的框架,所以我没有采用类似Redis这样的进程外缓存,而是采用了进程内缓存,毕竟网络传输的速度再快也比内存操作要慢的多。应用层的Actor服务天然支持分布式。如果对actor 不太了解的同学可以度娘一下。


优化


1. 阅读到这里你是否感觉哪里有问题呢?是的,就是链表元素的查找,由于只能是遍历,所有链表查找元素的时间复杂度为O(n),那有没有办法优化呢?那就是我们以后要讲的另外一种数据结构了。

2. 空间的访客记录是以时间为维度的倒序排列,所以业务以及DB时间列的设计类型推荐为UTC时间戳long类型,毕竟long类型在多数语言中比datetime类型占用内存要小很多。

3. 无论是否使用缓存,用户的访问记录都是需要DB来持久化的,当有大量的请求的时候,我们可以利用某种机制来批量持久化到DB,而不是一个请求就访问数据库一次。

4. 当对空间的访客记录实时性要求不是很高的时候,我们可以每10秒或者5秒更新缓存,也就是批量更新缓存,这比单条加锁更新缓存效果更好。


X总的个人空间需求并没有结束,菜菜仍然在持续优化中,欢迎大佬指正

菜菜出品
一个不止于技术的公众号

原文地址:https://www.cnblogs.com/zhanlang/p/10225312.html

时间: 2024-08-28 18:21:17

程序猿修仙之路--数据结构之设计高性能访客记录系统的相关文章

程序员修仙之路- CXO让我做一个计算器!!

菜菜呀,个税最近改革了,我得重新计算你的工资呀,我需要个计算器,你开发一个吧 CEO,CTO,CFO于一身的CXO X总,咱不会买一个吗? 菜菜 那不得花钱吗,一块钱也是钱呀··这个计算器支持加减乘除运算就行,很简单 CEO,CTO,CFO于一身的CXO (尼玛)那能不能给我涨点工资呀? 菜菜 公司现在很困难,你这个计算器关系到公司的存亡,你要注意呀!! CEO,CTO,CFO于一身的CXO (关于撇开话题佩服的五体投地)好吧X总,我尽快做 菜菜 给你一天时间,我这里着急要用 CEO,CTO,C

程序员修仙之路--优雅快速的统计千万级别uv

菜菜,咱们网站现在有多少PV和UV了? Y总,咱们没有统计pv和uv的系统,预估大约有一千万uv吧 写一个统计uv和pv的系统吧 网上有现成的,直接接入一个不行吗? 别人的不太放心,毕竟自己写的,自己拥有主动权.给你两天时间,系统性能不要太差呀 好吧~~~ 定义PV是page view的缩写,即页面浏览量,通常是衡量一个网络新闻频道或网站甚至一条网络新闻的主要指标.网页浏览数是评价网站流量最常用的指标之一,简称为PV UV是unique visitor的简写,是指通过互联网访问.浏览这个网页的自

菜鸟程序猿的成长之路(一)——菜鸟程序猿起步

看到标题之后,突然想起一句文的话:白驹过隙,时光荏苒.每当看到这么有文採的话,总认为有点酸,再酸我也用*^◎^*. 9月开学季,大家陆续返校,让我想起了三年前自己带着新鲜与憧憬步入大学,大学对我来说一切都那么的美好和新鲜,时刻盼望着大学的生活.那些美好的场景仿佛就在昨天,转眼间,今年都大四了,"大四"简简单单的两个字让我清醒了很多,另一年的时间,我该面临毕业,就业压力也随之而至.青春,总是经不起蹉跎,留在大学的最后一年,也许一晃就过,如今的我们,也唯有奋力拼搏. 进入提高班也满两年的时

程序员修神之路--做好分库分表其实很难之一(继续送书)

菜哥,领导让我开发新系统了 这么说领导对你还是挺信任的呀~ 必须的,为了设计好这个新系统,数据库设计我花了好多心思呢 做一个系统我觉得不应该从数据库入手,应该从设计业务模型开始,先不说这个,说说你的数据库设计的优势 为了高性能我首先设计了分库 分表策略,为以后打下基础 那你的数据量将来会很大吗?分库分表其实涉及到很多难题,你了解过吗? 我觉得分库分表很容易呀 是吗? 是否需要分 说到数据库分库分表,不能一味的追求,我们要明白为什么要进行分库分表才是最终目的.现在网上一些人鼓吹分库分表如何应对了多

高效程序猿的狂暴之路

不觉间已经工作六年,回忆第一天实习的场景历历在目.恍若昨日.六年已足以令很多人转管理.转产品.转測试.转行,也一定有人还在坚守着编码,仅仅因热爱.遇到过形形色色的经理.架构师.运维.性能工程师等等,还是认为写代码的才是最厉害的!在这里分享一些这些年来修习到的个人心得.或许并不适用于其它人,但对于我却都是最珍贵的"宝藏". 也谨以此文纪念在代码堆里度过的青春~ 看到这些点点滴滴的收获,就会感到全部逝去的光阴和挥洒的汗水都是值得的. 1.启动:得心应手的工具 1.1 全能IDE 关于IDE

oo修仙之路

写在前面: 之前听说过oo这门课的威力,计院全体修仙现场的图也被转了不知多少遍,然而自己不亲身经历就不知这门课的难度所在.每次debug时耳边总会想起三国杀里面周瑜的话"挣扎吧,在血和暗的深渊里:痛苦吧,在仇与恨的深渊中!"oo对我来说大抵就是这样,痛苦却无法避免,下面就来回顾一下这一个月以来的oo生涯. 第一次作业: 第一次作业我美滋滋地以为老师会讲Java,像c语言和数据结构那门课一样,第一次作业并不会太难.然而我太天真了,第一次作业就给了我致命一击,看着如同天书一般的指导书,生平

一个程序猿的梦想之路

梦想的第一步 2006年,因为怀揣着对计算机浓烈的兴趣,于是高考第一志愿志愿填写了计算机专业,很快第一批次录取结束,大学入学通知书寄来了,是的,很激动,很多年过去了,再回忆那时的心情,心里还是有点微波荡漾,这应该算是我实现梦想的第一步. 来到了乌鲁木齐市,开始了学习计算机专业的生涯.那个时候班里的女生不多,不过我们这一届的女生算是多了,我也是其中一名,大学时光说短暂也漫长,我们意气风发,期待着在这里学到更多,只为了毕业后能找到一份工作,但是并不是无忧无虑的,希望时间过得快一点,赶紧毕业,赶紧工作

Java架构师“修仙”之路:推荐几本适用于所有Java程序员阅读书籍,希望你有一天成为架构狮

1.<深入理解Java虚拟机:JVM高级特性与最佳实践>本书适合所有Java程序员.系统调优师和系统架构师阅读. 共分为五大部分,围绕内存管理.执行子系统.程序编译与优化.高效并发等核心主题对JVM进行了全面而深入的分析,深刻揭示了JVM的工作原理. 第一部分从宏观的角度介绍了整个Java技术体系.Java和JVM的发展历程.模块化,以及JDK的编译,这对理解本书后面内容有重要帮助. 第二部分讲解了JVM的自动内存管理,包括虚拟机内存区域的划分原理以及各种内存溢出异常产生的原因:常见的垃圾收集

程序猿的健康之路

当我第一次听到加班的时候,其实我是是拒绝的,我对领导说我拒绝:领导说可以加工资,就这样我加了一个月的班之后,我的工资就DUANG的一下,上去了. 之后我每个月都在加班,我也告诉我身边的朋友加班,白天不用怎么干活,晚上可以加班,周末可以加班,假期可以加班,之后工资就duang duang duang 的上去了: 就这样我的加了几年的班之后,我的工资在duang duang duang的向上长,可是前一阵子去医院检查一下发现,我的身体也duang 的一下出毛病了:我的脂肪是高的,我的血压是高的,我的