Python 如何在数据库中使tornado请求原子化
我有一个用Tornado异步框架编写的python应用程序。当HTTP请求传入时,将调用此方法:Python 如何在数据库中使tornado请求原子化,python,transactions,tornado,atomic,python-decorators,Python,Transactions,Tornado,Atomic,Python Decorators,我有一个用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
@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调用可以并发发生,并且访问同一个数据库
出于数据完整性的目的,重要的是此方法中的三个数据库操作都被调用,而不需要另一个进程读取或写入中间的数据库行
如何确保此方法具有数据库原子性?Tornado对此是否有一个修饰符?由于您希望依次运行这三个db操作,因此函数
my\u方法
必须是非异步的
但这也意味着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
同步数据库访问
您尚未说明如何访问数据库。如果您在get\val\u from\u DB\u table1
和friends(例如with)中有同步DB访问权限,并且my\u方法
正在阻塞(不会将控制返回IO循环),则您会阻塞服务器(这会影响服务器的性能和响应能力)但是有效地序列化您的客户机,一次只有一个客户机可以执行my\u方法
。因此,在数据一致性方面,您不需要做任何事情,但通常这是一个糟糕的设计。您可以在短期内使用@xyres的解决方案解决这两个问题(因为Tornado的大部分功能都需要考虑线程安全问题)
异步数据库访问
如果您在get_val_from_DB_table 1
和朋友(例如with)中具有异步数据库访问权限,则可以使用。下面是一个例子:
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\u进程
,则只能使用