opentsdb源码分析---Deferred

来源:互联网 发布:自考计算机本科 知乎 编辑:程序博客网 时间:2024/05/17 15:21

在看opentsdb的源码过程中,有许多的Deferred ,理解它的概念对了解源码中的回调很重要,网上的资料很少,就翻译了一个api

理解Deferred 的概念
Deferred 代表结果还没有确定,一个异步的操作(I/O,RPC或者是其它的)已经开始并且延迟在将来处理这个结果(成功或者失败)。Future和 Deferred的不同之处在于Deferred有一个回调链,然而Future需要在某一个点手工处理。
当你开始一个异常操作,在操作结束后通常要返回调用。如果操作成功,你希望你的callback会使用这个结果来执行操作来开始异常操作,如果操作失败,你希望获取到错误处理码。
Deferred比单一的回调处理的更多,你可以添加任意数量的回调,这能够让你以一个简单的方式来处理复杂的处理管道。
理解回调链


让我们讲一个经典的例子。你正在写一个客户端库来供其它人使用你的简单远程存储服务,当用户调用get方法时,会从远程服务中获取到一些数据并把数据返回给用户,但是你想用异步的方式来做这个功能。


迟早rpc会完成,socket会变成可读。让我们来假设一切都像预期一样,当soket可读时,我们就可以从socket中读取到结果。然后你把他交给Deferred。Deferred 然后会处理这个结果并且调用callback。期望是调用你的客户端库后,当调用get方法时,你会添加callback到Deferred 上。这样的话,当Deferred 结果可用时,就会把结果作为一个参数给回调。


到目前为止,我们解释的仅仅是一个与callback相关的Future,但是Deferred 能做的比这个多得多。让我们假设现在有人想要在你的客户端库的顶层建立一个缓存层,为了避免get操作获取相同的值一遍又一遍的通过网络来查找,用户想要使用缓存来调用get而不是直接调用客户端库。


让我们假设缓存库已经有一个结果来缓存get调用,它会生产一个Deferred,并且立刻处理缓存结果,并且把Deferred返回给用户。用户会添加一个callback,当Deferred结果真的可用时会立刻被调用。所以全部的get操作在一个线程上会瞬间执行,没有上下文切换,没有阻塞,一切看上去很快完成。


现在假设缓存库没有缓存值需要使用之前提到的原始客户端库来执行get方法。这样给远程服务器发送rpc请求并且客户端库会给缓存库返回Deferred。这是真正令人兴奋的地方,缓存库在返回给用户之前给Deferred添加callback。callback会处理远程服务器返回的结果,添加到缓存并且返回。实际上,用户添加自己的callback处理结果。所以现在Deferred有两个相关的callback


1st callback       2nd callback


   Deferred:  add to cache  -->  user callback




当rpc结束时,原来的客户端库会从线上序列化结果并且把结果交给Deferred,第一个callback会被调用,处理结果进行缓存,不管第一个callback是否缓存都会调用第二个callback。




现在非常重要的是,第一个回调可能会返回另一个任意值,而这将被传递给二次回调。这可能听起来很奇怪,但它实际上是延迟的关键。


为了说明原因,让我们把事情更复杂些。让我们假设远程服务提供的get请求是一个相当简单和低层次存储服务,所以它的工作只是byte数组,它并不关心内容是什么。所以原始的客户端库只是从网络序列化字节并且管理到Deferred的byte数组。


现在你在编写一个高层的库来使用存储系列来存储用户自定义对象,所以当你从服务端获取到byte数组时,你需要把它们转换为对象。高层库的用户并不关心你使用远程存储系统的类型,它们只关心能够异常操作来执行get操作获取对象,你的高层库是建立在原始底层库的基础上。


当高层库的用户来调用get时,你就会调用低层库的get,调用rpc来返回高层库的Deferred,高层库然后添加第一个callback来做接下来的序列化数组转换为对象。然后高层库的用户添加他们自己的callback来对对象对一些操作
1st callback                    2nd callback


   Deferred:  de-serialize to an object  -->  user callback




当结果从网络返回时,byte数组从socket反序列化,第一个callback被调用并且它的参数是原始值,所以第一个callback接下来会反序列化成对象。第二个callback然后被调用它的参数是前一个回调的结果。


现在回到缓存库,与高层库无关,步骤如下
The caching library calls get on the higher-level library.
The higher-level library calls get on the lower-level library.
The lower-level library creates a Deferred, issues out the RPC call and returns its Deferred.
The higher-level library adds its own object de-serialization callback to the Deferred and returns it.
The caching library adds its own cache-updating callback to the Deferred and returns it.
The user gets the Deferred and adds their own callback to do something with the object retrieved from the data store.
              1st callback       2nd callback       3rd callback


   Deferred:  de-serialize  -->  add to cache  -->  user callback
   result: (none available)




一旦结果返回,第一个callback被调用,它反序列化对象并且返回。当前Deferred 的结果就是反序列化的对象


2nd callback       3rd callback


   Deferred:  add to cache  -->  user callback
   result: de-serialized object


因为链中还有其他的callback,Deferred 用当前的结果作为参数调用下一个


3rd callback


   Deferred:  user callback
   result: de-serialized object




最后,调用用户定义的callback


Deferred:  (no more callbacks)
   result: (whatever the user's callback returned)








用Deferred建立一个动态的处理管道


让我们把之前的例子更复杂些,让我们假设远程存储服务的get方法是一个运行在多台机器上的分布式服务,数据被分发到许多节点上。为了执行get方法,低层库首先需要知道哪些服务器是当前服务的数据。让我们假设有另外一个服务器,它是分布式服务的一部分,它维护索引并跟踪每一个数据的位置。低层库使用第一个服务器来查找位置,然后从存储节点返回。最后用户不关心数据调用了两个步骤,它只是想调用get方法并且当数据可用时被调用。


这是Deferred 最有用的地方,当用户调用get方法时,低层库会调用rpc来查找数据所作的服务器。当调用rpc时,会生成一个Deferred ,低层库添加第一个callback来处理相应然后返回给用户。


  1st callback       2nd callback


   Deferred:  index lookup  -->  user callback
   result: (none available)




Eventually, the lookup RPC completes, and the Deferred is given the lookup response. So before triggering the first callback, the Deferred will be in this state:
              1st callback       2nd callback


   Deferred:  index lookup  -->  user callback
   result: lookup response




第一个callback运行并且知道数据去哪里查找,它调用get请求到正确的存储节点。这些产生另外一个Deferred
   (A)        2nd callback    |   (B)
                              |
   Deferred:  user callback   |   Deferred:  (no more callbacks)
   result: Deferred (B)       |   result: (none available)


因为callback返回的是一个Deferred,我们还不能够调用用户的callback。因为用户不能够用Deferred来调用他们的callback,他们想要的是获取到一个byte数组。这样当前的callback中止并且停止执行callback链。当B的Deferred执行再开始执行callback链。为了实现这一功能,一个callback被添加到其它的Deferred,由Deferred来回复处理callback链。


 (A)        2nd callback    |   (B)        1st callback
                              |
   Deferred:  user callback   |   Deferred:  resume (A)
   result: Deferred (B)       |   result: (none available)


一旦A添加callback到B,它会立即被执行,没有必要等待,阻塞现成。所以整个过程很快。
现在当get方法返回时,rpc层反序列化byte数组。


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


(B)'s first and only callback is going to set the result of (A) and resume (A)'s callback chain.
   (A)        2nd callback    |   (B)        1st callback
                              |
   Deferred:  user callback   |   Deferred:  resume (A)
   result: byte array         |   result: byte array


So now (A) resumes its callback chain, and invokes the user's callback with the byte array in argument, which is what they wanted.
   (A)                        |   (B)        1st callback
                              |
   Deferred:  (no more cb)    |   Deferred:  resume (A)
   result: (return value of   |   result: byte array
            the user's cb)


Then (B) moves on to its next callback in the chain, but there are none, so (B) is done too.
   (A)                        |   (B)
                              |
   Deferred:  (no more cb)    |   Deferred:  (no more cb)
   result: (return value of   |   result: byte array
            the user's cb)




0 0