最近在读hbase源码关于rpc的一些实现细节,想正好趁此机会和大家分享一下我理解到的hbase关于Nonce机制的实现。
Nonce机制的来源
Nonce这个词由来已久,且在各个领域都会有相对应的名词解释。对于HBase来说,由于网络环境的复杂性,在客户端调用rpc像服务器发送请求时,随时都有丢失该请求的风险。对于客户端发出的一个请求来说,客户端只会收到三种结果:服务端响应成功、服务端响应失败、请求超时。在一个并不可靠的网络连接下,请求超时是经常会发生的,对此,HBase中有相应的Retry机制保证一个请求可以顺利被发送到服务端,然而对于Append、Increment这样的非幂等操作(在HBase中被称为Delta操作)来说,一个被服务端重复接收的请求带来的后果无疑是灾难性的。于是Nonce机制便应运而生。
Nonce机制的实现
下面进入正题,关于Nonce机制如何在HBase中实现,我将从提出问题-解决问题的角度简单分析HBase中对于“delta”操作的实现过程。
第一步 现在假设我是一个客户端,那么对于我来说,最重要的事就是要去区分我发送的多个请求,所以我要为我发送的每一个请求分配一个id并且保证这些请求的id都是唯一的,对于这个要求,我们可以简单的使用jdk中的Random类来实现随机生成不重复的long型数字作为请求的id。
第二步 现在我们已经实现了用一个id标示一个客户端的不同请求,但是对于一个server来说,他会收到来自超级多的客户端发送的请求,那么就会不可避免的遇到不同客户端发送的请求中会有id重复的问题。所以解决问题的方式也非常简单,就是为每一个客户端也生成一个id去标识,这样就可以通过一个客户端id和一个请求id来全局唯一的标识一个服务端收到的请求了,(HBase的服务端中用来标识请求的类被称为NonceKey,其中核心的两个成员变量便是客户端id和请求id即nonce值)在HBase3的版本中,这个唯一的客户端id的生成方式是使用client id的哈希值加一个随机数
第三步 有了前面两步,我们现在已经知道一个server是如何区分请求的问题了,接下来我们关注服务端如何对请求进行nonce处理的问题。服务端有一个核心的管理器叫ServerNonceManager,他掌握着一个请求是否被执行的“生杀大权”,在这个类的内部有一个核心容器ConcurrentHashMap<NonceKey, OperationContext>,其中NonceKey就是我们在上两步中说的用于标识一个请求的key,而OperationContext代表着这个请求进行的操作的具体信息,其中有这个操作的执行状态(三种执行状态DONT_PROCEED、PROCEED和WAIT),这三种执行状态中,DONT_PROCEED代表已经执行成功不需要再次执行了,PROCEED表示已经被执行但是执行失败了,WAIT表示这个请求正在等待其他请求执行结束后再执行。
所以对于ServerNonceManager来说,他要做的事情就是判断一个请求是否可以被执行。对于这个问题,他首先根据NonceKey去map中找是否之前收到过相同NonceKey的请求,如果没有,则代表这个请求可以被执行(因为之前没有被重复提交的请求),如果map中有相同NonceKey的请求,则说明这个请求由于网络问题被重复发送了,这时候manager就要通过map中已有的这个请求的状态来判断了,
1. 如果这个已有的请求状态为wait,则代表请求还在等待执行,于是接着等待,而直接丢弃新来的请求
2. 如果这个已有请求的状态为DONT_PROCEED,则代表请求已经执行完毕,不执行新请求
3. 如果这个已有请求的状态为PROCEED,则代表请求已经执行完毕并且执行失败了,则将会执行新请求。
另外,为了防止这个map无限扩张,ServerNonceManager将会对其进行定期清理(默认半个小时)
第四步 我们通过前三步已经了解到了一个ServerNonceManager是如何构建起来的以及如何解决问题的。于是问题就简单了,当服务端收到一个delta请求时,首先会根据ServerNonceManager反馈的结果,若不能执行便直接丢弃,允许执行才会进行接下来的处理逻辑。
以上便是我对于HBase中实现Nonce机制的一些思考。