Jen*_*ens 17 synchronization pyramid cornice
我正在使用Pyramid和Cornice为Web服务开发REST API ; 服务器端的数据使用SQLAlchemy和MySQL处理.Web服务器是使用uwsgi的nginx,它被配置为运行多个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
Run Code Online (Sandbox Code Playgroud)
问题
假设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
Run Code Online (Sandbox Code Playgroud)
本质上这是一个读者 - 写作者问题,但由于涉及多个进程,使用锁/互斥锁/信号量的传统线程同步在这里不起作用.
题
我想了解为这种基于Pyramid的Web API实现锁定和同步的最佳方法,以便安全有效地处理上述示例中的并发调用(即没有不必要的序列化).
解决方案(?)
Mik*_*maa 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会话设置为SERIALIZABLE SQL隔离级别 - 您从最高一致性模型开始.如果你知道API调用可以容忍它,你可以降低性能要求 - 例如调用统计只读分析.
乐观锁定通常在"正常"大量读取中表现更好 - 很少写入工作负载,冲突很少发生(两次API调用一次更新同一用户).只有在发生冲突时才会发生事务重试惩罚.
如果事务在N次重试后最终失败,例如在异常高负载情况下,则应在API使用者端解决,告知服务器端数据已更改且用户必须再次验证或重新填充表单
进一步阅读
SQLAlchemy + pyramid_tm示例.注意:尽量避免全球DBSession对象,并使用request.dbsession
替代.
针对SQLAlchemy的ConflictResolver,替代,更低级别,乐观的锁定解决方案,基于使用Python函数作为可重试上下文而不是完整HTTP请求