简介
在前面的文档中,我们介绍了RVN的概念,通过RVN可以解决的某类问题和使用技巧,以及处理RVN的逻辑的具体实现。在本文中,我们将要介绍关于如何使用RVN解决另一种在分布式系统中常出现的问题。
问题
假设我们创建了一个service来维护某种record。我们的service允许client获取record,并且基于现有record的内容对record进行修改。举例说,假设我们的record记录了client一方的某种操作的数量。Client每完成一次操作就将service一侧的count加1。当然我们有其它的方法,但是我们现在要求count的计算部分在client一侧完成。具体来说,我们的record可以定义为:
Record {int ID;int count;
}
Service提供的API 为:
void updateRecord(Record record);
但是在分布式系统中,可能有多个client同时试图更新同一个record,这样这两个client的update就会互相覆盖,从而使最终的结果错误。例如在下图,我们数据库中保存的ID “1”的数量是10。现在有两个client同时获取了这个记录,然后同时试图将数量改为11。最终两个带有11的结果将互相覆盖,从而我们错误的保存了11,而不是12。我们将如何避免这个问题?
解决方法
这个问题同样可以使用RVN来解决。具体来说,我们在record里加入RVN,代表某条记录的版本号。对于每次更新,client都要先获取当前记录以及它的版本号,然后将版本号加1写入到update record的request里。而service端需要检查request的RVN,确保该RVN大于当前保存的保本好,最后再将该记录和RVN写入到数据库。我们详细描述该过程如下:
现在client1和client2都获取了RVN为1的记录,然后都将RVN更新为2,发送request去试图更新service一侧的数据。Service一侧的逻辑如下:
在这里我们需要特别说明几点。为了保证处理的正确性,service必须保证在处理record的过程中RVN一直是合理的,否则就可能出现两个thread都认为自己的RVN是正确的,从而仍然互相覆盖。这样我们可以使用lock住record的ID,并且在处理完record之后再unlock,来保证处理的正确性。我们也可以使用DynamoDB的condition update来达到相同的目的,具体可以参见《关于在分布式环境中RVN和使用场景的介绍3》。
在这种逻辑下,后获得锁的thread将会发现它所持有的RVN已经不是合理的RVN了,所以它会拒绝处理它持有的request,并且向client汇报这一情况(比如可以throw exception)。而client可以重新从service获得最新的RVN的record,再次尝试根据最新的记录进行更新。
问题扩展
在我们讨论的解决方案里,我们期望service和client可以遵守共同的规则在一起工作,比如期望所有的client都可以基于获取的RVN每次增加1。只有在这种情况下,我们的数据才能被维护正确。假如,我们的API是公开的API,也就是说client并不总是可信的。Client可能会破坏规则给RVN增加2或者更多来试图非法获取修改数据的规则。在这种情况下,我们可以给每一个record version生成一个UUID来代替RVN。Client必须提供当前version的UUID以获取修改当前record的资格。在每次record被改变时都生成新的UUID。
参考文档
《关于在分布式环境中RVN和使用场景的介绍1》
《关于在分布式环境中RVN和使用场景的介绍2》
《关于在分布式环境中RVN和使用场景的介绍3》