大数据处理:百分点实时计算架构和算法
a)实时计算架构
工欲善其事,必先利其器。一个稳定可靠且高效的底层架构是实时计算的必要基础。图1给出了百分点数据大平台的总体框架,如图所示,大数据平台包含数据存储和数据处理两个层次。
数据处理层由四个部分组成。其中Web应用云包含了所有直接面对用户的Web服务,每个Web应用都会产生Web日志以及其他实时数据,这些数据一方面会及时交由实时计算框架进行处理,另一方面也会定期同步至离线计算框架;实时计算框架会处理接收到的实时数据,并将处理结果输出到数据查询框架或者离线计算框架;离线计算框架则定期对数据进行处理,并将处理结果输出至数据查询框架;数据查询框架提供了一系列应用接口供程序调取需要的各项数据,同时提供了一些Web工具帮助业务人员对海量数据进行统计、汇总和分析。
百分点公司的主要服务都是运行在这套架构上的,它拥有良好的稳定性和扩展性,一般来说只需要增加水平扩展结点即可提高数据处理能力,这为百分点业务的稳定发展奠定了技术基础。
b)实时计算算法
c)简单方案
看到这个问题时,大部分读者会很快想到如图3所示的算法方案。图中红色、蓝色和绿色的方块分别表示不同的单品。在这个方案中,我们为每个单品保存一份浏览信息,它包含两个数据结构:
e)累计浏览量(简称累计量),一个整数,代表截止到最后一次访问时的浏览量。
如图所示,假设蓝色单品对应的数据是[(t1,a1),(t2,a2),…,(tn,an)]和A。这表示t1时刻的该单品浏览量是a1,t2时刻是a2,tn是最后一次记录到浏览该单品的时刻,浏览量是an。截止到tn,该单品的总浏览量是A。
当单品浏览源源不断进入到消息队列时,处理进程(或线程)P1,P2…会实时读取到这些信息,并修改对应单品的数据信息。例如,P1读取到t时刻对蓝色单品的浏览记录时,会进行下面的操作:
f)得到当前时刻ct;
g)对数据库中蓝色单品数据加锁,加锁成功后读取出数据,假设历史是[(t1,a1),(t2,a2),…,(tn,an)],累计量是A;
h)累计量递增,即从A修改为A+1
j)将新的历史和累计量输出至数据库,释放锁。
不难验证这个方案是可以正确得出每个单品24小时内的浏览量的,并且只要在资源(计算、存储和网络)充足的情况下,数据库中单品的浏览量是实时更新的。这个方案也是分布式实时计算中最简单最常见的一种模式。
k)避免锁
要想提高实时处理效率,避免锁是非常重要的。一种常见的做法是将并行操作串行化,就像MapReduce中的Reduce阶段一样,将key相同的数据交由同一个reducer处理。基于这个原理,我们可以将方案改造为如图4所示,我们新增一个数据分发处理过程,它的作用是保证同一个单品的所有数据都会发送给同一个处理程序。例如将蓝色单品交由P1处理,红色交由P2处理,绿色交由P3处理。这样P1在处理过程中不需要对数据库加锁,因为不存在资源竞争。这样可以极大的提高计算效率,于是整个计算过程变为:
l)得到当前时刻ct;
m)读取数据库中蓝色单品信息,假设历史是[(t1,a1),(t2,a2),…,(tn,an)],累计量是A;
n)累计递增,即从A修改为A+1
p)将新的历史和累计量输出至数据库。
步骤b)和e)省去了锁操作,整个系统的并发性和吞吐量会得到大大提高。当然,没有免费的午餐,这种方案的缺点在于存在单点隐患,例如一旦P1由于某些原因挂掉了,那么蓝色单品的数据将得不到及时处理,计数结果将无法保证实时。这种计算过程对系统监控和故障转移会有很高的要求。
q)数据分层
方案二已经可以大大提高计算效率,但这还不够,我们可以看到在计算步骤b)和e)中总是要把历史和累计量同时从数据库中读出或写入,实际上这是没有必要的,因为只有累计量才是外部必须使用的数据,而历史只是算法的中间数据。这样,我们可以区别对待历史和累计量,我们将历史和累计量都缓存在计算进程中,定期更新历史至数据库,而累计量则实时更新。新的方案如图5所示,计算过程变为:
r)得到当前时刻ct;
s)如果本地没有蓝色单品的信息,则从数据库中读取蓝色单品信息;否则直接使用本地缓存的信息。假设历史是[(t1,a1),(t2,a2),…,(tn,an)],累计量是A;
t)累计量递增,即从A修改为A+1
这种方案可以大大降低数据库压力、数据IO和序列化反序列化次数,从而提高整个系统的处理效率。数据分层实际上是计算机中一种常用的路数,例如硬件中的高速缓存/内存/磁盘,系统IO中的缓冲区/磁盘文件,数据库的内存索引、系统DNS缓存等等。我们使用的开源搜索引擎Solr就使用了同样的思路达到近实时索引。Solr包含磁盘全量索引和实时增加的内存增量索引,并引入了“soft提交”的方式更新新索引。新数据到达后,Solr会使用“soft”提交的方式更新内存增量索引,在检索的时候通过同时请求全量索引和增量索引并合并的方式获得到最新的数据。之后会在服务器空闲的时候,Solr会把内存增量索引合并到磁盘全量索引中保证数据完整。
w)模糊化
x)得到当前时刻精确到小时的部分ct;
y)如果本地没有蓝色单品的信息,则从数据库中读取蓝色单品信息;否则直接使用本地缓存的信息。假设历史是[(t1,a1),(t2,a2),…,(tn,an)],累计量是A;
z)累计量递增,即从A修改为A+1
aa)如果ct=tn,则更新历史为[(t1,a1),(t2,a2),…,(tn,an+1)],否则更新为[(t1,a1),(t2,a2),…,(tn,an),(ct,1)];最后删除小时数小于ct-24的列表元素,删除的同时从累计量中减去对应时刻的浏览量;
ab)将新的浏览量输出至数据库;如果满足一定的条件,则将历史输出至数据库。
在这种方案下,数据库中存储的并不是过去24小时内的浏览量,而是过去23小时多一点内的。例如在1月2日12:15时数据库中的浏览量实际上是1月1日13:00到1月2日12:15的浏览量!
这种降低数据精度的方法我们可以称之为模糊化,它是用资源换效率的一种方法。在对数据精确性不是特别敏感的领域,这种方法可以大大降低系统资源使用量、提高系统的处理效率。利用模糊化的实时算法快速得到近似结果,而后用离线算法慢慢修正结果的精确度,是百分点在大数据处理中经常使用的招数。
ac)局部精化
这种方案会增加系统的设计和开发难度,而且必须有灵活的配置才能满足多变的业务需求。
[a0+(a1-a0)×(60-15)/60]+a1+…+a24
其中a0代表1月1日12:00到13:00之间的浏览量,依次类推,a24代表1月2日12:00到12:15之间的浏览量。公式中的a0+(a1-a0)×(60-15)/60估计了1月1日12:15-13:00之间的浏览量,这样就得出了从1月1日12:15到1月2日12:15之间24小时内的浏览量。
当然,模型也不是万能的,模型本身的建立和更新也是有代价的,如果建模方法不恰当或者模型更新不及时,很有可能得出的结果会很差。