我正在使用Pyramid和Cornice为Web服务开发REST API;服务器端的数据使用SQLAlchemy和MySQL处理。 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实现锁定和同步的最佳方法,以便安全有效地处理上述示例中的并发调用(即无需不必要的序列化)。
解决方案(?)
{id}
标记/标记为locked
是不合理的,因为SQLAlchemy会缓存此类修改,而flush()
在此上下文中似乎不够原子?< / LI>
答案 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。您可以使用它来提出某种形式的同步。