准备加班中ing.....数据库
需求要点
每一个用户都有本身的我的空间,当有其余用户来访问的时候,须要添加访客记录,而且更新为最新的访客,这里设计到一个坑,若是存在这个用户的访问记录须要更新用户的最后访问时间。那这个需求在技术维度来讲,有什么特色吗? 先想10秒钟,在接着往下看!!!数组
有什么设计要点呢?缓存
用户的访客记录必定要缓存,要否则怎么抗住大并发呢?
因为最新的访客记录变化很是快,要有一种能快速添加新数据,删除老数据的数据结构。
缓存的篇章今日暂且不说,说一下以上的第二点,也就引出了今日数据结构主角:链表bash
链表百科:链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是经过链表中的指针连接次序实现的。链表属于线性结构。网络
链表分类
单链表:链表中的元素的指向只能指向链表中的下一个元素或者为空,元素之间不能相互指向。也就是一种线性链表。
public class Node<T>
{
//当前节点的数据元素
public T Data { get; set ; }
//当前节点的下一个元素
public Node<T> NextNode { get; set ; }
}
复制代码
双向链表:每一个链表元素既有指向下一个元素的指针,又有指向前一个元素的指针,其中每一个结点都有两种指针。
public class Node<T>
{
//当前节点的前一个节点
public Node<T> PreNode { get; set ; }
//当前节点的数据元素
public T Data { get; set ; }
//当前节点的下一个元素
public Node<T> NextNode { get; set ; }
}
复制代码
循环链表:指的是在单向链表和双向链表的基础上,将两种链表的最后一个结点指向第一个结点从而实现循环。
特性
元素的数量能够随时扩充。因为链表在物理的存储单元上是非连续的,这就早就了它天生的优点,个人节点能够在任意符合要求的地方分配内存。
添加元素: 单链表:当在一个位置N以后插入新元素的时候,单链表首先把当前位置N的元素的Next指针指向新的元素,而后新的元素的Next指针指向N+1位置的元素。固然若是是在首位置插入新元素,只须要把新元素的Next指针指向链表的首元素便可,同理,若是要在单链表尾部插入新元素,只须要把单链表的尾部元素的Next指针指向新元素。至于循环单链表,无所谓首元素和尾元素之分。
双向链表: 在位置N以后添加新元素和单链表原理相似,原理也是修改元素的指针指向。可是这里有一个不一样,双向链表要修改先后元素(N位置和N+1位置)和新元素三个Node的指针,因此略微麻烦一点。
删除元素: 单链表:当要删除位置N的元素的时候,只须要把N-1位置元素的Next指针指向N+1便可。
双向链表:当要删除位置N的元素的时候,须要修改N-1位置元素的Next指针指向N+1元素,同时还要修改N+1位置元素的Pre指针指向N-1元素。
查找元素: 因为链表的元素在内存中并不是连续,因此不能像数组那样拥有O(1)的查找时间复杂度,只能是经过首元素去遍历链表,因此时间复杂度为O(n)
程序设计
给你10秒回到X总的需求中来。经过对链表的介绍,咱们该选择哪一种链表呢?这里我先说一下个人思路,若有错误请指正:数据结构
当一个访客进入我的空间的首页时,大多数状况下,访客记录只须要缓存前100条或者200条便可,也就是说这个场景是存在热点数据的,80%(甚至更高)的请求命中在最近100条访客数据上,不多人会去查看好久之前的记录。因此基于占用内存空间上的考虑,我决定缓存最近的100条访客数据。
假设我用链表缓存了前100条数据,其中在非首位置有一条访客A的记录,此时A又访问的这个用户空间,我须要把A的记录移到首位置,这个过程经历了删除A数据,在首位置添加A数据。假如A开始的位置是N,我在删除N位置数据的时候,须要查找N-1的位置元素修改其指针指向,若是是单链表因为当前位置N的元素中没有N-1位置元素的信息,全部须要从新遍历链表。若是是双向链表呢,位置N的元素中保存了位置N-1的元素,因此没有必要在从新遍历链表了,这也是双向链表对比单链表的优点,虽然内存占用上多了一个指针的内存大小,可是在实际的应用场景中更为经常使用。因此我选择双向链表。删除操做和添加操做时间复杂度都是O(1).
对同一个空间的访问,必然存在锁和多线程的问题。因此我在选择框架的时候优先选择了基于Actor模型的框架。避免了在同一个用户空间上加锁的操做。
因为基于Actor模型的框架,因此我没有采用相似Redis这样的进程外缓存,而是采用了进程内缓存,毕竟网络传输的速度再快也比内存操做要慢的多。应用层的Actor服务自然支持分布式。若是对actor 不太了解的同窗能够度娘一下。
优化
阅读到这里你是否感受哪里有问题呢?是的,就是链表元素的查找,因为只能是遍历,全部链表查找元素的时间复杂度为O(n),那有没有办法优化呢?那就是咱们之后要讲的另一种数据结构了。
空间的访客记录是以时间为维度的倒序排列,因此业务以及DB时间列的设计类型推荐为UTC时间戳long类型,毕竟long类型在多数语言中比datetime类型占用内存要小不少。
不管是否使用缓存,用户的访问记录都是须要DB来持久化的,当有大量的请求的时候,咱们能够利用某种机制来批量持久化到DB,而不是一个请求就访问数据库一次。
当对空间的访客记录实时性要求不是很高的时候,咱们能够每10秒或者5秒更新缓存,也就是批量更新缓存,这比单条加锁更新缓存效果更好。
X总的我的空间需求并无结束,菜菜仍然在持续优化中,欢迎大佬指正!多线程
添加关注,查看更精美版本,收获更多精彩 并发