tlog数据存储

Tlog 采用了 hbase+ 云梯的存储方案,分别对应实时和离线的数据服务,它们在 tlog 中的场景描述为:

实时服务如查看近2分钟某应用的服务调用情况;检索一笔彩票订单目前的流程状态。它们的特点是数据粒度细、实时性要求高、不能重复计算或重复计算结果不一致、稳定性差

离线服务如统计昨天提供调用次数最多的前10个服务;分析前一个月里售出彩种ID=1的总额最高的代理商和最低代理商,金额分别是多少。与实时服务相反,它们的特点是数据粒度粗、时间跨度大、能重复计算、结果一致稳定

       可以看出,实时和离线可以互补长短,为接入方提供多层次个性化的数据服务。当然服务的前提是有数据,也就是如何存下来,且在大数据量下必须是高效的、稳定的。

1)写HBase

需要考虑两种类型的存储数据:一种是需要快速检索的业务数据,另一种是实时展现的统计数据。对于前者具体又可分为带唯一业务标识和不带唯一业务标识的情况,带唯一标识的如彩票检索,订单号就是唯一的标识号,只需将订单号设计为rowkey,再将该订单不同状态的日志放入同一column family(cf)的不同qualifier中就可以通过订单号快速检索该笔彩票的状态信息了;而没有唯一标识的业务类型在存储时可能导致rowkey冲突老数据被覆盖,如操作日志即使以日志类型+时间戳也不能作为唯一的rowkey设计,这时对整条日志采用碰撞率低的定长编码算法是一个很好方法,如CRC32,通过将编码放在rowkey的最后即可解决冲突的问题。如果存储的是基于时序的统计数据,对于hbase存储模型的设计就需要更加注意了,因为这类数据不仅量大(如秒级统计)而且往往附带按某种维度的聚合操作,设计不好就会给存储和查询带来性能问题。关于这类数据的存储,业界基于hbase的开源产品Opentsdb给出了解决方案,这里介绍下其schemal设计和异步hbase模型

Ø  OpenTsdbSchemal设计:

 

 

Figure 1.1 opentsdbSchemal

众所周知,与通常RDMS相比,hbase是一个schema-less、面向列存储的nosql数据库,定位一个列元数据至多会经过rowkey->cf->qualifier->version四层索引,其中cf必须在表定义时就指定无法通过后期动态扩展,而version的定位是通过timestamp解决cell数据冲突的,所以大多数实际情况下只有rowkeyqualifier两级索引可以利用。对于time-serial性质的数据,如果每个时间细粒度(假设为秒)的数据都放入rowkey,则会导致region数暴增第一级索引成为瓶颈,opentsdb采用将秒粒度的时间戳变为小时粒度后保留在rowkey中,而将具体的秒信息转化为对小时粒度时间戳的偏移量放入quliafier中,这样一来region数量大大减少,quliafier的数量也不会成为瓶颈(60*60=3600个)。更重要的是每个row中的数据更加聚合,为高效检索提供了保证,因为

a)              hbase的底层存储是HFile文件(基于Hdfsblock数据块),row中的cf可以拥有多个HFile文件,但同一HFile文件中的数据必定属于同一cf,这有利于在读取同一cf数据时尽量少跨HFile或不跨HFile,使需求密集型的数据聚合在同一row中的cf下迎合了hbase这种天然的物理存储特性,大大提高了数据的访问效能。

b)              以时间粗粒度rowkey+细粒度qualifer的方式也很容易满足用户不同粒度的检索需求,假设要查询某小时前10分钟的数据,可以通过hbase自带的qualifierFilter在服务端过滤数据,而无需将小时的数据由服务端全部返回客户端后再自行过滤

另外,opentsdb为了让rowkey包含更多的检索信息,将维度信息以kv对的形式放入rowkey中,当然kv对的数量必须是有限的,opentsdb以正则过滤的方式满足用户对源数据不同维度的检索要求。同时,opentsdb还对metrictag进行了编码、以及入库数据的压缩,这些都大大降低了数据存储量。

Ø  异步hbase模型

相比hbase自带的同步HTable APIOpenTsdb自己实现了的AsynHbase具有高效、非阻塞、线程安全等特点,在顺序读和写的响应性能上提高了一倍[4]



 

Figure 1.2 opentsdbAsynHbase

       如图1.2,异步化过程描述为:请求者(Data Sink)发送数据请求,获得异步化结果Deferred,注册回调器callbacksDeferred,完成客户端逻辑所在线程返回;服务端异步执行数据请求服务,将结果写入Deferred,之后触发并执行回调器callbacks,继续数据请求后暂停的逻辑。其中关键的两个地方在于DeferredCallbacks(异常处理时对应errbacks

a)      Deferred

JDK中的Future类似,Deferred也是一种在有限状态机中表示结果暂不可达(not yet available)的状态。不同的是,Deferred绑定了callbacks,而Future只能在后续某个时刻手动地通过get方式拿到异步调用结果,Future的问题就在于调用者并不清楚什么时候结果准备好了。

b)      Callbacks

准确地说,应该是回调链(callbakc chain),链上的前一个callback将返回结果作为参数传递给下一个callback,以此类推直到链末。下面以一个由浅入深的例子来理解下回调链。

假设我们要以RPC的方式从远程主机上获得一些感兴趣的数据,如图1.2,首先通过一个socket发送请求,获得Deferred,并将后续逻辑以callback方式注册到Deferred上。此时回调链上只有我们定义的一个callback

1st callback

Deferred:  user callback

进一步我们需要在客户端增加一层cache以提高性能。考虑缓存未命中的情况,RPC返回的结果首先需要在cache中缓存起来,然后才执行RPC后的逻辑。这时,回调链上是两个callback

     1st callback       2nd callback
                          Deferred:  add to cache  -->  user callback

更进一步,考虑到网络上回来的都是原始字节流,而cache和使用时都是数据对象,所以抽象出更底层的逻辑:验证字节流并反序列化为对象。这时,回调链上是三个callback

               1st callback          2nd callback       3rd callback
Deferred: validate & de-serialize  -->  add to cache  -->  user callback
              result: de-serialized object

最后,考虑复杂的情况,RPC的数据服务方是一个分布式集群,有master/slave之分,这意味着需要两阶段的RPC来请求数据。第一阶段,与master通信获得数据所在的slave;第二阶段,从slave上获取需要的数据,分别对应下面的AB

(A)     1nd callback    2nd callback    |    (B)      1st callback
Deferred: index lookup    user callback   |   Deferred:  resume (A)
result: lookup response   Deferred (B)    |   result: byte array

A中,1nd callback通过与master通信返回了slave地址,传递slave地址给user callback并执行第二阶段RPC。由于Auser callback返回的是一个Deferred,调用链在user callback时被中止了(not yet available),所以需要在B中的callback返回结果后恢复A中的调用链。这里体现了DeferredFuture不同的另一大特点:嵌套。通过Deferred的嵌套和调用链组合,可以灵活动态地构建异步处理管道(processing pipeline)。

在使用AsynHbase模型的时候需要明确和注意以下几点:

l  任何时候回调链上最多只能有一个线程在执行

l  回调链上的回调执行有严格的先后顺序,如果一个变量不是共享的,则无需同步

l  将初始结果放入Deferred的线程即执行回调链的线程,中间没有线程切换

l  只要callback开始执行了,Deferred就失去了对其引用

l  Deferred增加一个callback的代价是O(1)

l  相互循环的Deferred依赖可能导致无限递归,应避免

l  CallbackErrback不能接受Deferred本身为参数

 

2)写云梯

       写云梯最重要的就是IO吞吐量。经测试,采用Lzo压缩方式时单机单线程的写速率在10m/s+,单机多线程能提高写效率,当并发度为机器核数时(tlog5核虚拟机)能达到峰值,近30m/s刚好满足高峰期的数据流量(tlog目前14台服务器,每天10T+的流量,平均每台约10m/s,高峰期30m/s)。所以,tlog中不同server的不同storm任务都会成为一条向云梯传送数据的通道,对应云梯的文件句柄用“ip+taskId+timestamp”区分,timestamp是每小时滚动一次的时间戳标记。Tlog中曾经为写云梯设计了缓冲机制,本质就是一个简单的生产消费者模型,生产者从一头不断获得源日志往缓冲队列中放,当队列满就阻塞;而消费者从另一头读取日志往云梯写,发现队列空就等待。后来发现其实这样没有必要,因为写hdfs具有天然的缓存机制,那就是buffer数据首先被刷入buffer,待到满足预设的大小(事实证明buffer越大写速率越高,如128M的写效率高于64M)后整块数据再往云梯搬,自己加缓存效果不佳反而会增大线程切换和内存开销。另外,为了减小使用storm带来的数据流转开销,tlog原则上是能先入库的数据绝不流转,入云梯理应在收集器采集到数据立刻进行,后续才是Hbasetlog目前也确实这么做的。但这里需要说明一点的是tlog没有将入云梯和入hbase两块进行解耦,也就是说,如果写云梯失败了,会间接阻塞写hbase。是否值得这样做,答案是肯定的。云梯上存的是全量的原始数据,是追述问题的依据,即使入hbase失败或客户端数据滚动丢失,只要云梯上保留得有就有办法恢复,所以从数据的准确性和完整性上tlog将云梯的优先级放大了。但目前遇到一个很囧的问题就是云梯升级较频繁,每次升级花费两个小时导致tlog系统僵死两个小时,日志就在客户端积压两个小时,像eagleeeye这种大日志输出的系统两个小时早已超过文件滚动的上限200m,结果就是数据丢了。解决的方案就是落盘,但落盘需要考虑服务器磁盘空间的大小、磁盘IO对系统负载的影响、落盘的速度是否满足网络流量以及落盘后的恢复工作等等,这些因素会大大增加系统的复杂度并对稳定性带来冲击,在权衡利弊后,tlog目前还没有这么做。

 

 

【参考文献】

[1]. Nick Dimiduk, Amandeep Khurana. HBase in Action [M]. USA: Manning, 2011
[2]. http://opentsdb.net/
[3]. http://tsunanet.net/~tsuna/async/api/com/stumbleupon/async/Deferred.html
[4]. http://www.tsunanet.net/~tsuna/asynchbase/benchmark/viz.html

猜你喜欢

转载自luoshi0801.iteye.com/blog/1938835