Pyramid REST API:如何安全地处理并发数据访问?

时间:2015-11-26 09:24:56

标签: synchronization pyramid cornice

我正在使用PyramidCornice为Web服务开发REST API;服务器端的数据使用SQLAlchemyMySQL处理。 Web服务器使用nginx uwsgi,并且配置为运行多个Python进程:

[uwsgi]
socket = localhost:6542
plugins = python34
...
processes = 2 # spawn the specified number of workers/processes
threads = 2 # run each worker in prethreaded mode with the specified number of threads

问题

假设服务器端有一个表customers。使用API​​可以读取客户数据,修改或删除客户数据。除此之外,还有其他API函数可以读取客户数据。

我可以同时发出多个API调用,然后竞争相同的客户资源:

# Write/modify the customer {id} data
curl --request POST ... https://some.host/api/customer/{id}
# Delete customer {id} and all of its associated data
curl --request DELETE https://some.host/api/customer/{id}
# Perform some function which reads customer {id}
curl --request GET ... https://some.host/api/do-work

本质上这是一个Readers-Writers Problem,但由于涉及多个进程,因此使用locks/mutexes/semaphores的传统线程同步将不起作用。

问题

我想了解为这种基于金字塔的Web API实现锁定和同步的最佳方法,以便安全有效地处理上述示例中的并发调用(即无需不必要的序列化)。

解决方案(?)

2 个答案:

答案 0 :(得分:12)

我假设您正在处理一个MySQL数据库,并且您的锁不需要涵盖其他资源(Redis,第三方API等)。我还假设您的客户端函数本身不需要处理事务数据(通过多个API调用维护会话),您只是想阻止并发API访问来弄乱您的数据库。

有两种锁定,悲观锁定和乐观锁定。

悲观锁定是大多数人通常通过锁定知道的 - 您事先在代码中以编程方式创建和获取锁。这就是分布式锁管理器。

乐观锁定是您可以轻松使用SQL数据库的方法。如果两个事务从同一资源竞争,则数据库有效地执行其中一个事务,并且应用程序框架(在本例中为Pyramid + pyramid_tm)可以在放弃之前重试该事务N次。

从开发的角度来看,乐观锁定是更理想的解决方案,因为它不会给应用程序开发人员带来任何认知负担,无法记住正确锁定资源或创建内部锁定机制。相反,开发人员依赖框架和数据库来重试和管理并发情况。但是,乐观锁定并不是Web开发人员所熟知的,因为由于编程语言缺乏灵活性,在广泛的PHP环境中进行乐观锁定很困难。

pyramid_tm实现乐观锁定解决方案,我建议您使用它或其他一些乐观锁定解决方案,除非您知道一个您不想要的非常具体的原因。

  • pyramid_tm将事务生命周期与HTTP请求联系起来,从Web开发人员的角度来看非常自然

  • pyramid_tm可以将其他事件与成功的交易联系起来,例如仅当事务提交

  • 时,pyramid_mailer才会向用户发送电子邮件
  • pyramid_tm经过充分测试,基于ZODB transaction交易管理器,自2000年初开始投入生产使用

  • 确保您的SQLAlchemy session设置为SERIALIZABLE SQL isolation级别 - 您从最高一致性模型开始。如果你知道API调用容忍它,你可以降低性能要求 - 例如调用统计数据只读分析。

  • 乐观锁定通常在“正常”大量读取中表现更好 - 很少写入工作负载,冲突很少发生(两次API调用一次更新同一用户)。只有在发生冲突时才会发生交易重试惩罚。

  • 如果在N次重试后交易最终失败,例如在异常高负载情况下,这应该在API消费者方面解决,告知服务器端数据已经更改,用户必须再次验证或重新填充表单

进一步阅读

答案 1 :(得分:3)

通常,您首先要确定哪种consistency model是可以接受的。您的一致性要求越弱,此问题在服务器端就越容易。

例如:

是否有可能摆脱乐观并发?即假设您有锁,执行您的操作,但检测何时出现并发情况,以便您可以正常恢复?如果您不希望发生大量碰撞,这可能是一个不错的选择。 Sqlalchemy应该能够检测到它正在更新已经修改过的行。

如果这是不可接受的,你可以在redis中使用distributed locking。您可以使用它来提出某种形式的同步。