如何在数据库中使龙卷风请求原子化
How to make a tornado request atomic in the Database
我有一个用 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 调用可以同时发生并访问同一个数据库。
出于数据完整性的目的,重要的是调用此方法中的三个数据库操作时没有其他进程读取或写入中间的那些数据库行。
如何确保此方法具有数据库原子性? Tornado 有装饰器吗?
因为你想 运行 这三个数据库操作一个接一个地进行,函数 my_method
必须 非异步.
但这也意味着 my_method
将阻止服务器。你绝对不想要那个。我能想到的一种方法是 运行 在另一个线程中使用此函数。这不会阻止服务器,并且会在操作 运行ning 期间继续接受新请求。而且,由于它将是非异步的,因此可以保证数据库的原子性。
以下是帮助您入门的相关代码:
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_from_db_table1
和朋友(例如 pymysql) and my_method
is blocking (doesn't return control to IO loop) then you block your server (which has implications on performance and responsiveness of your server) but effectively serialise your clients and only one can execute my_method
at a time. So in terms of data consistency you don't need to do anything, but generally it's a bad design. You can solve both with @xyres's solution in short term (at cost of keeping in mind thread-safely concerns because most of Tornado's functionality isn't thread-safe)中有同步数据库访问。
异步数据库访问
如果您在 get_val_from_db_table1
和朋友中有异步数据库访问(例如 tornado-mysql) then you can use 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
。
我有一个用 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 调用可以同时发生并访问同一个数据库。
出于数据完整性的目的,重要的是调用此方法中的三个数据库操作时没有其他进程读取或写入中间的那些数据库行。
如何确保此方法具有数据库原子性? Tornado 有装饰器吗?
因为你想 运行 这三个数据库操作一个接一个地进行,函数 my_method
必须 非异步.
但这也意味着 my_method
将阻止服务器。你绝对不想要那个。我能想到的一种方法是 运行 在另一个线程中使用此函数。这不会阻止服务器,并且会在操作 运行ning 期间继续接受新请求。而且,由于它将是非异步的,因此可以保证数据库的原子性。
以下是帮助您入门的相关代码:
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_from_db_table1
和朋友(例如 pymysql) and my_method
is blocking (doesn't return control to IO loop) then you block your server (which has implications on performance and responsiveness of your server) but effectively serialise your clients and only one can execute my_method
at a time. So in terms of data consistency you don't need to do anything, but generally it's a bad design. You can solve both with @xyres's solution in short term (at cost of keeping in mind thread-safely concerns because most of Tornado's functionality isn't thread-safe)中有同步数据库访问。
异步数据库访问
如果您在 get_val_from_db_table1
和朋友中有异步数据库访问(例如 tornado-mysql) then you can use 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
。