- 自动重试:Execute 内按 CanRetry 内联重试(与手动 RetryStep 共享计数); RetryStep 补 select_for_update 行锁 - 步骤数据流:前置步骤 StepResult 合并进后续步骤载荷(不落库,向后兼容) - 空闲超时:IsTimedOut 改以 UpdateTime 起算,Execute 每步推进刷新计时 - TransactionResult:Execute/Compensate 终态写入步骤汇总快照 - 幂等键:_call_service 携带 Idempotency-Key(事务码:步骤号:重试次数), local_tx 侧按 key 内存去重(TTL 300s) - 修复 RetryCompensate 成功后残留 compensate_error 导致事务误判 FAILED - 迁移 0001 FK 去除 db_column(列名对齐模型 attname,消除 makemigrations 漂移) - 新增 gvsdsdk_check_schema 命令:模型与数据库 schema 一致性校验 - 新增 VERSION_MATRIX.md 版本连接状态表;版本 1.0.0 -> 1.1.0
171 lines
5.9 KiB
Python
171 lines
5.9 KiB
Python
"""本地事务管理器
|
|
|
|
每个工作服务运行一个 LocalTransactionManager 实例,
|
|
负责注册本服务的步骤处理器(action + compensate),
|
|
并在收到主服务器请求时执行本地逻辑。
|
|
|
|
用法(在服务启动时注册处理器):
|
|
from gvsdsdk.msync.local_tx import local_tx
|
|
|
|
local_tx.register(
|
|
'create_user',
|
|
action=create_user_action,
|
|
compensate=create_user_compensate,
|
|
)
|
|
"""
|
|
|
|
import logging
|
|
import time
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class StepHandler:
|
|
"""步骤处理器——封装 action 与 compensate 函数"""
|
|
|
|
def __init__(self, name, action, compensate=None):
|
|
self.Name = name
|
|
self.Action = action
|
|
self.Compensate = compensate
|
|
|
|
def __repr__(self):
|
|
return f'<StepHandler:{self.Name}>'
|
|
|
|
|
|
class LocalTransactionManager:
|
|
"""本地事务管理器——每个工作服务一个实例"""
|
|
|
|
#: 幂等缓存 TTL(秒):主服务器崩溃窗口内重复发送同一步骤时去重
|
|
IDEMPOTENCY_TTL_SECONDS = 300
|
|
|
|
def __init__(self):
|
|
self._handlers = {}
|
|
self._idempotency_cache = {}
|
|
|
|
def register(self, name, action, compensate=None):
|
|
"""注册步骤处理器"""
|
|
if name in self._handlers:
|
|
logger.warning(f'步骤处理器已存在,覆盖: {name}')
|
|
self._handlers[name] = StepHandler(
|
|
name=name,
|
|
action=action,
|
|
compensate=compensate,
|
|
)
|
|
logger.info(f'步骤处理器注册: {name}')
|
|
|
|
def unregister(self, name):
|
|
"""注销步骤处理器"""
|
|
if name in self._handlers:
|
|
del self._handlers[name]
|
|
logger.info(f'步骤处理器注销: {name}')
|
|
|
|
def HasHandler(self, name):
|
|
"""检查是否注册了指定步骤处理器"""
|
|
return name in self._handlers
|
|
|
|
def ListHandlers(self):
|
|
"""列出所有已注册的步骤处理器名称"""
|
|
return list(self._handlers.keys())
|
|
|
|
# ─── 幂等去重(可选能力,收到 Idempotency-Key 时启用)────────────
|
|
|
|
def _IdempotencyGet(self, key):
|
|
if not key:
|
|
return None
|
|
entry = self._idempotency_cache.get(key)
|
|
if not entry:
|
|
return None
|
|
if time.time() - entry['ts'] > self.IDEMPOTENCY_TTL_SECONDS:
|
|
self._idempotency_cache.pop(key, None)
|
|
return None
|
|
return entry['result']
|
|
|
|
def _IdempotencySet(self, key, result):
|
|
if not key:
|
|
return
|
|
# 惰性清理:条目数超限时剔除过期项,防止缓存无限增长
|
|
if len(self._idempotency_cache) >= 1000:
|
|
now = time.time()
|
|
expired = [
|
|
k for k, v in self._idempotency_cache.items()
|
|
if now - v['ts'] > self.IDEMPOTENCY_TTL_SECONDS
|
|
]
|
|
for k in expired:
|
|
self._idempotency_cache.pop(k, None)
|
|
self._idempotency_cache[key] = {'ts': time.time(), 'result': result}
|
|
|
|
def ExecuteStep(self, name, payload=None, idempotency_key=None):
|
|
"""执行步骤——调用本地 action 函数
|
|
|
|
Args:
|
|
name: 步骤处理器名称
|
|
payload: 载荷
|
|
idempotency_key: 幂等键(主服务器 Idempotency-Key 头),
|
|
相同键在 TTL 内重复调用直接返回首次结果
|
|
"""
|
|
cached = self._IdempotencyGet(idempotency_key)
|
|
if cached is not None:
|
|
logger.info(f'步骤执行幂等命中: {name} key={idempotency_key}')
|
|
return cached
|
|
|
|
handler = self._handlers.get(name)
|
|
if handler is None:
|
|
msg = f'未注册的步骤处理器: {name}'
|
|
logger.error(msg)
|
|
result = {'success': False, 'result': None, 'error': msg}
|
|
self._IdempotencySet(idempotency_key, result)
|
|
return result
|
|
|
|
try:
|
|
result = handler.Action(payload or {})
|
|
logger.info(f'步骤执行成功: {name}')
|
|
result = {'success': True, 'result': result, 'error': None}
|
|
except Exception as e:
|
|
logger.error('步骤执行失败: %s: %s', name, e, exc_info=True)
|
|
result = {'success': False, 'result': None, 'error': str(e)}
|
|
|
|
self._IdempotencySet(idempotency_key, result)
|
|
return result
|
|
|
|
def CompensateStep(self, name, payload=None, action_result=None, idempotency_key=None):
|
|
"""补偿步骤——调用本地 compensate 函数
|
|
|
|
Args:
|
|
name: 步骤处理器名称
|
|
payload: 载荷
|
|
action_result: 原始执行结果(幂等回滚依据)
|
|
idempotency_key: 幂等键(主服务器 Idempotency-Key 头)
|
|
"""
|
|
cached = self._IdempotencyGet(idempotency_key)
|
|
if cached is not None:
|
|
logger.info(f'步骤补偿幂等命中: {name} key={idempotency_key}')
|
|
return cached
|
|
|
|
handler = self._handlers.get(name)
|
|
if handler is None:
|
|
msg = f'未注册的步骤处理器: {name}'
|
|
logger.error(msg)
|
|
result = {'success': False, 'result': None, 'error': msg}
|
|
self._IdempotencySet(idempotency_key, result)
|
|
return result
|
|
|
|
if handler.Compensate is None:
|
|
logger.warning(f'步骤无补偿函数,跳过: {name}')
|
|
result = {'success': True, 'result': None, 'error': None}
|
|
self._IdempotencySet(idempotency_key, result)
|
|
return result
|
|
|
|
try:
|
|
result = handler.Compensate(payload or {}, action_result or {})
|
|
logger.info(f'步骤补偿成功: {name}')
|
|
result = {'success': True, 'result': result, 'error': None}
|
|
except Exception as e:
|
|
logger.error('步骤补偿失败: %s: %s', name, e, exc_info=True)
|
|
result = {'success': False, 'result': None, 'error': str(e)}
|
|
|
|
self._IdempotencySet(idempotency_key, result)
|
|
return result
|
|
|
|
|
|
local_tx = LocalTransactionManager()
|