首页 文章

如何在可伸缩,高度并发和容错的系统中逐个处理来自同一客户端的多个API调用

提问于
浏览
1

我们有Web服务API来支持在数千万台设备上运行的客户端 . 通常客户每天呼叫服务器一次 . 这是每秒约116个客户 . 对于每个客户端(每个客户端都有唯一的ID),它可以同时进行多个API调用 . 但是,Server只能从同一客户端逐个处理这些API调用 . 因为,那些API调用将在后端Mongodb数据库中更新该客户端的同一文档 . 例如:需要更新此客户端文档中的上次查看时间和其他嵌入文档 .

我的一个解决方案是将synchronized块放在表示该客户端唯一ID的“实习生”对象上 . 这将允许来自同一客户端的仅一个请求获得锁并同时进行处理 . 此外,其他客户的请求也可以同时处理 . 但是,这个解决方案需要打开负载 balancer 器的“粘性” . 这意味着负载均衡器将在预设的时间间隔(例如15分钟)内将来自相同IP地址的所有请求路由到特定服务器 . 我不确定这是否会对整个系统设计的稳健性产生任何影响 . 我能想到的一件事是,一些客户可能会提出更多请求并使负载不均衡(创建热点) .

解决方案#1:

Interner<Key> myIdInterner = Interners.newWeakInterner();

public ResponseType1 processApi1(String clientUniqueId, RequestType1 request) {
    synchronized(myIdInterner.intern(new Key(clientUniqueId))) {
        // code to process request
    }
}

public ResponseType2 processApi2(String clientUniqueId, RequestType2 request) {
    synchronized(myIdInterner.intern(new Key(clientUniqueId))) {
        // code to process request
    }
}

您可以详细了解此解决方案的其他问题:Should I use Java String Pool for synchronization based on unique customer id?

我想的第二个解决方案是以某种方式锁定该客户端的文档(Mongodb)(我还没有找到一个很好的例子) . 然后,我不需要触摸负载 balancer 器设置 . 但是,我对这种方法感到担忧,因为我认为与解决方案#1相比,性能(到Mongodb服务器的往返和忙碌的等待?)会更糟糕 .

解决方案#2:

public ResponseType1 processApi1(String clientUniqueId, RequestType1 request) {
    try {
        obtainDocumentLock(new Key(clientUniqueId));
        // code to process request
    } finally {
        releaseDocumentLock(new Key(clientUniqueId));       
    }   
}

public ResponseType2 processApi2(String clientUniqueId, RequestType2 request) {
    try {
        obtainDocumentLock(new Key(clientUniqueId));
        // code to process request
    } finally {
        releaseDocumentLock(new Key(clientUniqueId));       
    }   
}

我相信这是一个可扩展的高并发系统中非常常见的问题 . 你是如何解决这个问题的?还有其他选择吗?我想要实现的是能够一次处理来自同一客户端的那些请求的一个请求 . 请注意,仅控制对数据库的读/写访问权限不起作用 . 解决方案需要控制整个请求的独占处理 .

例如,有两个请求:请求#1和请求#2 . 请求#1读取客户端的文档,更新子文档#5的一个字段,并保存整个文档 . 请求#2读取相同的文档,更新子文档#8的一个字段,并保存整个文档 . 此时,我们将得到一个OptimisticLockingFailureException,因为我们使用spring-data-mongodb中的@Version注释来检测版本冲突 . 因此,必须随时处理来自同一客户端的一个请求 .

附:选择解决方案#1(锁定打开负载 balancer 器粘性的单个进程/实例)或解决方案#2(分布式锁定)以获得可扩展且高并发系统设计的任何建议 . 目标是支持数以千万计的客户端,每秒同时有数百个客户端访问系统 .

3 回答

  • 0

    在您的解决方案中,您正在根据客户ID进行锁定拆分,因此两个客户可以同时处理服务 . 只有问题是粘性sesion.One解决方案可以使用分布式锁定,因此您可以调度任何服务器的任何请求和服务器获取锁定过程只有一个考虑是涉及远程调用 . 我们使用hazelcast / Ignite它非常适合平均节点数 . Hazelcast

  • 0

    为什么不在Mongodb中创建一个处理队列,您提交客户端请求文档,然后另一个使用它们的服务器进程,生成一个结果文档,客户端等待...将数据与clientId同步,并避免在API提交步骤 . 客户端提交活动的第二部分(完成后)只是轮询Mongodb以查找其API / ClientID和一些作业标记的消费记录 . 这样,您可以扩展API提交,并将API消费活动单独扩展到不同的服务器上 .

  • 0

    一个显而易见的方法就是在您的最终实现完整的乐观锁定算法 .

    也就是说,当有并发修改时,有时会得到 OptimisticLockingFailureException ,但是's fine: just re-read the document and start the modification that failed over again. You' ll会获得与使用锁定相同的效果 . 基本上,您正在利用已经内置到MongoDB的并发控制 . 这也有获得几个优势如果事务不冲突(例如,一个是读取,或者它们写入不同的文档),则从同一客户端进行事务处理,这可能会增加系统的并发性 . 另一方面,您必须实现重试逻辑 .

    如果您确实想要基于每个客户端(或每个文档或其他任何内容)进行锁定,并且您的服务器是单个进程(由您建议的方法暗示),您只需要一个可以处理任意 String 键的锁定管理器,其中有several reasonable solutions,其中包括 Interner .

相关问题