python分布式事务方案(二)基于消息最终一致性

上一章采用的是tcc方案,但是在进行批量操作时,比如说几百台主机一起分配策略时,会执行很长时间,这时体验比较差。
由于zabbix隐藏域后台,而这个慢主要是集中在调用zabbix接口,这里我们就基于消息最终一致性来进行优化
消息一致性方案是通过消息中间件保证上、下游应用数据操作的一致性。基本思路是将本地操作和发送消息放在一个事务中,保证本地操作和消息发送要么两者都成功或者都失败。下游应用向消息系统订阅该消息,收到消息后执行相应操作。

本地消息表是一种简化版的方案,将数据库中的表来作为消息中间件。
本地消息表这种实现方式应该是业界使用最多的,其核心思想是将分布式事务拆分成本地事务进行处理,这种思路是来源于ebay。我们可以从下面的流程图中看出其中的一些细节:

基本思路就是:

消息生产方,需要额外建一个消息表,并记录消息发送状态。消息表和业务数据要在一个事务里提交,也就是说他们要在一个数据库里面。然后消息会经过MQ发送到消息的消费方。如果消息发送失败,会进行重试发送。

消息消费方,需要处理这个消息,并完成自己的业务逻辑。此时如果本地事务处理成功,表明已经处理成功了,如果处理失败,那么就会重试执行。如果是业务上面的失败,可以给生产方发送一个业务补偿消息,通知生产方进行回滚等操作。

生产方和消费方定时扫描本地消息表,把还没处理完成的消息或者失败的消息再发送一遍。如果有靠谱的自动对账补账逻辑,这种方案还是非常实用的。

这种方案遵循BASE理论,采用的是最终一致性,笔者认为是这几种方案里面比较适合实际业务场景的,即不会出现像2PC那样复杂的实现(当调用链很长的时候,2PC的可用性是非常低的),也不会像TCC那样可能出现确认或者回滚不了的情况。

  • 优点: 一种非常经典的实现,避免了分布式事务,实现了最终一致性。在 .NET中 有现成的解决方案。

  • 缺点: 消息表会耦合到业务系统中,如果没有封装好的解决方案,会有很多杂活需要处理。

下面是实现步骤:

1、先创建本地消息表

MESSAGE_STATUS={
'active':'active',
'fail':'fail',
'success':'success'
} class Message(models.Model):
topic = models.CharField(max_length=50, blank=True)
event_module = models.CharField(max_length=50, blank=True,null=True)
event_fun= models.CharField(max_length=30, blank=True,null=True)
params=models.TextField(null=True)
remark=models.CharField(max_length=300, blank=True,null=True)
status = models.CharField(max_length=20, blank=True)
exec_count=models.SmallIntegerField(null=True)
error_msg = models.TextField(null=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True) index_together = ('status','exec_count') #联合索引 def __unicode__(self):
return '%s' % self.remark def __str__(self):
return '%s' % self.remark

2、定义生产者api
这里提前定义了mysql和rabbix两种消息存储方式

from models import Message
from serializers import MessageSerializer def event_add(message):
MysqlQueue().add(message) class MessageQueue():
def __init__(self):
pass def add(self,message):
pass class MysqlQueue(MessageQueue): def add(self,message):
message["status"]="active"
message["exec_count"]=0
serializer=MessageSerializer(data=message)
serializer.is_valid(raise_exception=True)
serializer.save() class RabbitQueue(MessageQueue): def add(self,message):
pass

3、定义消费者api,这里使用定时任务框架celery

import json
from models import Message
from celery.utils.log import get_task_logger logger = get_task_logger(__name__) class MessageConsumer:
def receive(self, topic=None):
queryset = Message.objects.filter(exec_count__lt=5).exclude(status='success')
if topic:
queryset = queryset.filter(topic=topic)
messages = queryset.order_by('id').all()
for message in messages:
try:
m = __import__(message.event_module, fromlist=True)
if hasattr(m, message.event_fun):
target_func = getattr(m, message.event_fun)
logger.info(message.params)
target_func(json.loads(message.params))
message.status='success'
message.exec_count=message.exec_count + 1
message.save()
else:
logger.error("can not find function " + message.event_fun)
message.status='fail'
message.exec_count=message.exec_count + 1
message.error_msg="can not find function" + message.event_fun
message.save()
except Exception ,e:
logger.error("exec message fail,id:" + str(message.id)+",cause by "+e.message)
logger.exception(e)
message.status='fail'
message.exec_count=message.exec_count + 1
message.error_msg=e.message
message.save()

4、定义定时任务,这里如果已经有一个定时任务在跑,则直接跳过

exec_flag=False
@shared_task(ignore_result=True)
def reveive_event_message():
global exec_flag
if exec_flag:
logger.warning("exists a tast exec reveive_event_message")
return
exec_flag=True
logger.info("reveive_event_message start")
MessageConsumer().receive()
logger.info("reveive_event_message end")
exec_flag=False

5、下面定义业务调用

def add_message(event_fun,params,remark):
event_message = dict()
event_message["topic"] = "topci"
event_message["event_module"] = "callback path"
event_message["event_fun"] =event_fun
event_message["params"] = json.dumps(params)
event_message["remark"] =remark
logger.debug(event_message)
event_add(event_message) def create(self, request, *args, **kwargs):
'''
policy add
'''
assets = request.data["data"]
try:
with transaction.atomic():
#save policy
for ;;
#发送消息
add_message("async_update_zabbix_trigger",params,"update trigger ")
except rest_framework_serializers.ValidationError, e:
logger.exception(e)
raise

6、定义回调方法,这里由于是使用python可以直接传方法名,就可以进行回调
比如说创建定时器

def async_create_zabbix_trigger(params):
client = ZabbixClientProxy()
host_id = get_zabbix_host_by_uuid(uuid)
zabbix_items = get_zabbix_items(host_id)
if zabbix_items is None or len(zabbix_items) == 0:
return
condition = alert_models.ConditionItem.objects.get(id=params["condition_id"])
condition.alert_duration = params["alert_duration"]
condition.item_threshold = params["item_threshold"]
triggers = create_zabbix_trigger(client, asset, zabbix_items, condition, uuid)
serializer = policy_serializers.ConditionTriggerSerializer(data=triggers, many=True)
serializer.is_valid(raise_exception=True)
serializer.save()

这里可以配置一个最大重试次数,如果超过就不会进行重试,这时就会发送邮件通知管理员进行手工重试,来达到最终一致性

python分布式事务方案(二)基于消息最终一致性 荐的更多相关文章

  1. 如何选择分布式事务形态(TCC,SAGA,2PC,补偿,基于消息最终一致性等等)

    各种形态的分布式事务 分布式事务有多种主流形态,包括: 基于消息实现的分布式事务 基于补偿实现的分布式事务(gts/fescar自动补偿的形式) 基于TCC实现的分布式事务 基于SAGA实现的分布式事 ...

  2. 如何选择分布式事务形态(TCC,SAGA,2PC,基于消息最终一致性等等)

    各种形态的分布式事务 分布式事务有多种主流形态,包括: 基于消息实现的分布式事务 基于补偿实现的分布式事务 基于TCC实现的分布式事务 基于SAGA实现的分布式事务 基于2PC实现的分布式事务 这些形 ...

  3. 可靠消息最终一致性【本地消息表、RocketMQ 事务消息方案】

    更多内容,前往IT-BLOG 一.可靠消息最终一致性事务概述 可靠消息最终一致性方案是指当事务发起方执行完成本地事务后并发出一条消息,事务参与方(消息消费者)一定能够接收消息并处理事务成功,此方案强调 ...

  4. .Net Core with 微服务 - 分布式事务 - 可靠消息最终一致性

    前面我们讲了分布式事务的2PC.3PC , TCC 的原理.这些事务其实都在尽力的模拟数据库的事务,我们可以简单的认为他们是一个同步行的事务.特别是 2PC,3PC 他们完全利用数据库的事务能力,在一 ...

  5. 对比7种分布式事务方案,还是偏爱阿里开源的Seata,真香!(原理+实战)

    前言 这是<Spring Cloud 进阶>专栏的第六篇文章,往期文章如下: 五十五张图告诉你微服务的灵魂摆渡者Nacos究竟有多强? openFeign夺命连环9问,这谁受得了? 阿里面 ...

  6. ebay分布式事务方案中文版

    http://cailin.iteye.com/blog/2268428 不使用分布式事务实现目的  -- ibm https://www.ibm.com/developerworks/cn/clou ...

  7. Dubbo学习系列之十五(Seata分布式事务方案TCC模式)

    上篇的续集. 工具: Idea201902/JDK11/Gradle5.6.2/Mysql8.0.11/Lombok0.27/Postman7.5.0/SpringBoot2.1.9/Nacos1.1 ...

  8. 分布式事务(二)Java事务API(JTA)规范

    一.引子 既然出现了分布式场景(DTP模型), 大java也及时制定出一套规范来给各大应用服务器.数据库/mq等厂商使用,以方便管理互通--->JTA闪亮登场.JTA(Java Transact ...

  9. Dubbo学习系列之十四(Seata分布式事务方案AT模式)

    一直说写有关最新技术的文章,但前面似乎都有点偏了,只能说算主流技术,今天这个主题,我觉得应该名副其实.分布式微服务的深水区并不是单个微服务的设计,而是服务间的数据一致性问题!解决了这个问题,才算是把分 ...

  10. [转帖]深度剖析一站式分布式事务方案 Seata-Server

    深度剖析一站式分布式事务方案 Seata-Server https://www.jianshu.com/p/940e2cfab67e 金融级分布式架构关注 22019.04.10 16:59:14字数 ...

随机推荐

  1. 关于 Jupyter Nbconvert 自定义 LaTeX 模板,中文兼容与格式设置,从 Notebook 构建 LaTeX PDF 文档

    目录 为什么会有这篇随笔的内容? 简述一下我遇到的问题 Nbconvert 转换 .ipynb 文件的基本方法 Jupyter Nbconvert 构建中文 \(\LaTeX\) 文档的痛点 Jupy ...

  2. typora中LaTeX公式常用指令

    # typora中LaTeX公式常用指令 以下指令只能保证在typora中完美显示,但是在其他编辑器中可能会部分不支持 \cal F.X.Y = KaTeX parse error: Expected ...

  3. 使用 Spring 实现控制反转和依赖注入

    使用 Spring 实现控制反转和依赖注入 概述 在本文中,我们将介绍IoC(控制反转)和DI(依赖注入)的概念,以及如何在Spring框架中实现它们. 什么是控制反转? 控制反转是软件工程中的一个原 ...

  4. Bike Sharing Analysis(二)- 假设检验方法

    假设检验 假设检验是推论统计学(inferential statistics)的一个分支,也就是对一个较小的.有代表性的数据组(例如样本集合)进行分析与评估,并依此推断出一个大型的数据组(例如人口)的 ...

  5. .NET项目中使用HtmlSanitizer防止XSS攻击

    .NET项目中使用HtmlSanitizer防止XSS攻击 前言 最近博客也是上线了留言板功能,但是没有做审核(太懒了),然后在留言的时候可以输入<script>alert('xss')& ...

  6. PaddleNLP UIE -- 药品说明书信息抽取(名称、规格、用法、用量)

    目录 创建项目 环境配置 上传代码 训练定制 代码结构 数据标注 准备语料库 数据标注 导出数据 数据转换 doccano Label Studio 模型微调 模型评估 定制模型--预测 效果 Pad ...

  7. 树莓派4B-控制霍尔编码器

    霍尔编码器-直流减速电机 介绍 直流减速电机,即齿轮减速电机,是在普通直流电机的基础上,加上配套齿轮减速箱.齿轮减速箱的作用是,提供较低的转速,较大的力矩.同时,齿轮箱不同的减速比可以提供不同的转速和 ...

  8. git将本地代码提交到远程仓库

    来源:https://blog.csdn.net/gaoying_blogs/article/details/53337112 将本地代码上传到远程仓库的时候,打开命令行窗口,进入到本地代码的文件夹. ...

  9. [oeasy]python0052_ raw格式字符串_单引号_双引号_反引号_ 退格键

    转义字符 回忆上次内容 最近玩的是\n.\r 之外的转义序列 \a是 ␇ (bell) \t是 水平制表符 \v是 换行不回车 通过 16 进制数值转义 \xhh 把(hh)16 进制对应的 asci ...

  10. MFC 关于按键状态获取

    alt键会阻断消息? moousemovealt键无法判断,按下一次 并松开一次状态改变一次#define KeyState GetAsyncKeyState BOOL bCtrlDown = (Ke ...