基于分布式环境下统计在线用户数问题的研究
分布式系统介绍
分布式系统就是让终端用户把一组工作在一起的计算机当做一台单独的计算机来使用。这些机器共享状态,并发操作,并且单台机器出现问题不会影响到整个系统的正常工作。1
分布式系统分类
先讲一下,对分布式系统分类,按照不同人的理解划分是不一样的,比如按照功能、地域、结构来划分,都是不一样的;我只按照我个人理解的分布式(不如说是我目前水平所知道的)来划分,分为主-从式和去中心化式两中分布式系统。2
主-从结构
典型的主从式结构如下图所示:
带有明显的层级关系,以Master
为中心,Slave
作为从属,主从结构又称为中心化结构;类似与中央集权控制的关系,各个地方政府都受中央政权管控(比喻)。
哪些产品属于这种设计?
Hadoop,它是一个典型的主从结构,通常情况下Master显得尤为重要,Master一旦宕掉,就无法对外提供服务了,因此正在的分布式部署往往还需要对Master做一个副本,甚至多个副本。
去中心化
与主从结构相对,去中心化的设计就是,让你感觉不到谁是主谁是从,往往任何一个节点都能代表这一整个系统,它带有明显的“环”状特点,其结构如下3:
画一个环,环上的各个节点互相都能通信,当突然某个节点或某些节点“坏”掉了,并不影响整个“环”对外提供服务。因为“环”上的任何一个节点,都能够独立提供系统的功能。
这样看来,似乎去中心化设计的分布式系统似乎更有吸引力哈。
采用上面提到的去中心化设计思想的产品,我所知道的产品有:FastDFS,一个去中心化的文件存储系统,很早前,国人设计的,挺不错的,公司现在还用它存文件,图片等。
再说一下,上面去中心化是我个人理解的,请勿跟其他网站、博客、文献、论文等等所提到的去中心化对号入座,我所定义的都不十分准确完整,我这里讲的主题将是如何处理一个分布式系统中的登录用户数问题。
另外,我下面要讨论的问题也是基于这个“去中心化”思想下的分布式解决方案。
当前面临的问题
先做一个大前提假设,将问题限定一个范围:有一个A、B、C三台主机组成的一个去中心化的分布式系统,另外有个nginx负载均衡这三台主机,提供对外服务;
终端用户登录系统,会记录一个token信息到服务器中,按照去中心化的设计,这个token信息应该“同时”存在于ABC三台服务器中,这时,要统计在线用户数简直太简单了,没有讨论的必要,在任意一台服务上,计算一下token的数量就知道了当前在线的用户数了!然而,我们面临的问题是如何将token,“同时”地准确的放到三种主机中?这才是问题的最关键所在。
在分布式系统中,有个很著名的定理 -- CAP定理
CAP定理
CAP定理指出分布式的数据存储不能同时满足一致性
(Consistency
),可用性
(Availability
)和分区容忍性
(Partition Tolerance
)。可以选择三者中的两个,但是不能是一致性
和可用性
4。
因此,我们没法很好的同时将一个用户的token一致性的都放到ABC中,因此应当设计一种方法尽可能的一致,并且还要简单有效,太复杂的我能力有效无法设计出来。
一种设计思想
再假设,小a登录了我们的系统,产生了一条token信息,他产生的token位于服务器A上,紧接着小a再登录系统后要有其他操作,需要使用这个token,然而根据负载均衡策略,往往并不能保证小a的下一次操作还是位于服务器A,可能在B行也可能在C上,假设,这次操作服务由B提供服务,然而,B上并没有这条token信息,就无法对小a提供服务了,那么CAP
中的可用性A
就没了,这时,就需要考虑如何保证可用性,有一个非常简单的方法,“定时同步”。
定时同步方法
为了保证系统可用性,必须有一个机制让ABC三者之间内容达到"一致",可以约定比如每5分钟同步一次,将ABC三者中当前时刻缓存的token信息取并集,只要将并集取到了,那么B中当然也会有A中的token信息了,此时小a向B请求,B中有小a的token信息,当然会对其提供服务了。
现在又有另外一个问题了,如何求并集问题?
要是取严格意义上数学的并也是不可能的,因为你没法确定在某一个时刻,是否有人正在登录系统,而你计算的这个时刻的下一时刻又有人登录了,此时你求的并集并不完善。ABC三者的“同步”终有一定的时间差。
如果你期望可用更小的时间间隔去同步,比如可以5秒中同步一次,更甚1秒一次,这也是行不通的,更短的时间间隔往往是牺牲了系统的性能代价,这样引入分布式系统的意义就不大了。
求并集,可以这样解决,以A为例,首先获取B和C的token缓存信息,在将A当前的token信息进行合并,合并后放到tokenAll缓存中,注意tokenAll跟A中的token缓存不是同一个(暂时想到的这种设计);同理,对于B,就是先获取A和C的token缓存,再加上自己的token缓存,合并放入tokenAll;C也一样;
上面提到tokenAll的设计,说一下为什么这样,因为最“原生”的,存在于ABC中各个的token缓存,一定能够保证是自己服务器上产生的token信息,而tokenAll的缓存只是某个时刻下ABC的所有缓存token数据,再下一次缓存同步的时候,这种设计可以减少工作量,另外获取token的时候效率也更高,毕竟可以先从原生的token缓存中取,取不到再去大的tokenAll中获取。
如何计算在线用户数
根据上面讨论,有一种非常简单的在线用户数统计方式,那就是,计算tokenAll的数据,在任意的一个节点上统计出tokenAll的数量,就是在线用户数5。
但是有一个非常大的缺点,就是,他的误差,跟同步时间间隔有非常大的关系,说白了,这个在线用户数量是非常不准确的,体现不出实时性,5分钟同步一次,所得到的用户数在这5分钟内的变数很大。那有没有一种近乎实时的统计在线用户数量的设计内?
另一种思路,前面有tokenAll缓存设计,并且还保留了ABC中“原生”的token缓存,我们只需要拿原生的token就可以近乎实时的获取在线用户数了,因为原生的token缓存,一定是在它自己服务器上产生的,那么如果由A计算在线用户数,只需要将A的原生token数加上B和C的原生token数即可。同理,对应在B和C上计算在线用户数;这个计算一定是在这个时刻的实时在线用户数量。
最后,去中心化的分布式设计应该非常适合小规模的分布式,太多节点要保证某一个节点跟所有其他节点”联系“本身这个事情可能就复杂的要命了,我记得如何求一个平面区域中心点问题中有个计算随机点之间关系的时候,当有1万个点的时候,耗时几乎就不可原谅了,(求两个点之间的距离)跟这里的问题类似,更不用说要维护一个节点跟其他(n-1)个节点的通信和管理了。
真的很难想象像苹果、Folding@Home、雅虎、LinkedIn、冰封王座4都是如何做到维护成千上万个节点的规模,他们确实实现了,了不起!