我有一个用Tornado异步框架编写的python应用程序。当HTTP请求进入时,会调用此方法:
@classmethod
def my_method(cls, my_arg1):
# Do some Database Transaction #1
x = get_val_from_db_table1(id=1, 'x')
y = get_val_from_db_table2(id=7, 'y')
x += x + (2 * y)
# Do some Database Transaction #2
set_val_in_db_table1(id=1, 'x', x)
return True
三个数据库操作是相互关联的。这是一个并发应用程序,因此多个此类HTTP调用可以同时发生并命中同一个数据库。
出于数据完整性的目的,重要的是这个方法中的三个数据库操作都被调用,而没有其他进程读取或写入其间的那些数据库行。
如何确保此方法具有数据库原子性?龙卷风有装饰师吗?
答案 0 :(得分:2)
由于您希望一个接一个地运行这三个数据库操作,因此函数my_method
必须非异步。
但这也意味着my_method
将阻止服务器。你绝对不希望如此。我能想到的一种方法是在另一个线程中运行此函数。这不会阻止服务器,并将在操作运行时继续接受新请求。因为,它将是非同步的,db原子性是有保证的。
以下是帮助您入门的相关代码:
import concurrent.futures
executor = concurrent.futures.ThreadPoolExecutor(max_workers=1)
# Don't set `max_workers` more than 1, because then multiple
# threads will be able to perform db operations
class MyHandler(...):
@gen.coroutine
def get(self):
yield executor.submit(MyHandler.my_method, my_arg1)
# above, `yield` is used to wait for
# db operations to finish
# if you don't want to wait and return
# a response immediately remove the
# `yield` keyword
self.write('Done')
@classmethod
def my_method(cls, my_arg1):
# do db stuff ...
return True
答案 1 :(得分:2)
您尚未说明如何访问数据库。如果,您可能在get_val_from_db_table1
和朋友(例如pymysql)和my_method
中有同步数据库访问权限阻止(不会将控制权返回到IO循环)那么您阻止您的服务器(这会影响服务器的性能和响应能力)但有效地序列化您的客户端,并且每次只能执行my_method
。因此,就数据一致性而言,您不需要做任何事情,但通常它是一个糟糕的设计。您可以在短期内使用@ xyres的解决方案解决这两个问题(以牺牲线程安全问题为代价,因为大多数Tornado&#39的功能isn't thread-safe)。
如果您在get_val_from_db_table1
和朋友(例如tornado-mysql)中拥有异步数据库访问权限,那么您可以使用tornado.locks.Lock
。这是一个例子:
from tornado import web, gen, locks, ioloop
_lock = locks.Lock()
def synchronised(coro):
async def wrapper(*args, **kwargs):
async with _lock:
return await coro(*args, **kwargs)
return wrapper
class MainHandler(web.RequestHandler):
async def get(self):
result = await self.my_method('foo')
self.write(result)
@classmethod
@synchronised
async def my_method(cls, arg):
# db access
await gen.sleep(0.5)
return 'data set for {}'.format(arg)
if __name__ == '__main__':
app = web.Application([('/', MainHandler)])
app.listen(8080)
ioloop.IOLoop.current().start()
请注意,上面说的是关于正常的单进程Tornado应用程序。如果您使用tornado.process.fork_processes
,那么您只能使用multiprocessing.Lock
。