我正在尝试编写一个装饰器,它接受一个与mongodb交互的函数,如果发生异常,它会重试交互。 我有以下代码:
def handle_failover(f):
def wrapper(*args):
for i in range(40):
try:
yield f(*args)
break
except pymongo.errors.AutoReconnect:
loop = IOLoop.instance()
yield gen.Task(loop.add_timeout, time.time() + 0.25)
return wrapper
class CreateHandler(DatabaseHandler):
@handle_failover
def create_counter(self, collection):
object_id = yield self.db[collection].insert({'n': 0})
return object_id
@gen.coroutine
def post(self, collection):
object_id = yield self.create_counter(collection)
self.finish({'id': object_id})
但这不起作用。它给出了create_counter产生生成器的错误。我已经尝试过制作@ gen.coroutines的所有函数,但它没有帮助。
如何让handle_failover装饰工作?
编辑: 现在没有装饰器。这应该可靠地创建一个计数器并将object_id返回给用户。如果出现异常,则会显示500页。
class CreateHandler(DatabaseHandler):
@gen.coroutine
def create_counter(self, collection, data):
for i in range(FAILOVER_TRIES):
try:
yield self.db[collection].insert(data)
break
except pymongo.errors.AutoReconnect:
loop = IOLoop.instance()
yield gen.Task(loop.add_timeout, time.time() + FAILOVER_SLEEP)
except pymongo.errors.DuplicateKeyError:
break
else:
raise Exception("Can't create new counter.")
@gen.coroutine
def post(self, collection):
object_id = bson.objectid.ObjectId()
data = {
'_id': object_id,
'n': 0
}
yield self.create_counter(collection, data)
self.set_status(201)
self.set_header('Location', '/%s/%s' % (collection, str(object_id)))
self.finish({})
虽然我仍然不知道如何增加计数器幂等,因为DuplicateKeyError的技巧在这里不适用:
class CounterHandler(CounterIDHandler):
def increment(self, collection, object_id, n):
result = yield self.db[collection].update({'_id': object_id}, {'$inc': {'n': int(n)}})
return result
@gen.coroutine
def post(self, collection, counter_id, n):
object_id = self.get_object_id(counter_id)
if not n or not int(n):
n = 1
result = yield self.increment(collection, object_id, n)
self.finish({'resp': result['updatedExisting']})
答案 0 :(得分:3)
你很可能不想这样做。最好向用户显示错误,而不是重试操作。
盲目地重试任何引发AutoReconnect的插件是一个坏主意,因为你不知道MongoDB是否在丢失连接之前执行了插入。在这种情况下,您不知道是否最终会有一个或两个{'n': 0}
的记录。因此,您应确保以此方式重试的任何操作都是幂等的。有关详细信息,请参阅我的"save the monkey" article。
如果你肯定想要制作这样的包装器,你需要确保f
和wrapper
都是协同程序。此外,如果f
抛出错误40次,则必须重新提出最终错误。如果f
成功,则必须返回其返回值:
def handle_failover(f):
@gen.coroutine
def wrapper(*args):
retries = 40
i = 0
while True:
try:
ret = yield gen.coroutine(f)(*args)
raise gen.Return(ret)
except pymongo.errors.AutoReconnect:
if i < retries:
i += 1
loop = IOLoop.instance()
yield gen.Task(loop.add_timeout, time.time() + 0.25)
else:
raise
return wrapper
但只能做幂等操作!