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. Oh-My-Zsh 提示符只显示当前路径,不需要修改主题文件

    我真是服了.就这么一个简单的小问题我在网上找了一个多小时,一大堆 CSDN 文章都是抄 同一篇博客 的教程,所有的博客都要我去把 ~/.oh-my-zsh/themes/*.zsh-theme 文件里 ...

  2. 我写CSS的常用套路(附demo的效果实现与源码)

    大赞: https://mp.weixin.qq.com/s/dYCWYeM629DwiSqmaaAs1w

  3. ClickHouse介绍(二)MergeTree引擎

    MergeTree引擎 ClickHouse中有多种表引擎,包括MergeTree.外部存储.内存.文件.接口等,6大类,20多种表引擎.其中最强大的当属MergeTree(及其同一家族中)引擎.我们 ...

  4. 使用Python爬取公众号的合集内容

    使用Python爬取公众号的合集 前言 ...最近老是更新关于博客的文章,很久没更新其他的了,然后写一下如何爬取微信公众号里面的图片吧! 先看看微信公众号的样子吧: 我爬取的是公众号的合集内容 讲解 ...

  5. 详解Web应用安全系列(6)安全配置错误

    Web攻击中的安全配置错误漏洞是一个重要的安全问题,它涉及到对应用程序.框架.应用程序服务器.Web服务器.数据库服务器等组件的安全配置不当.这类漏洞往往由于配置过程中的疏忽或错误,使得攻击者能够未经 ...

  6. 2个qubit的量子门

    量子计算机就是基于单qubit门和双qubit门的,再多的量子操作都是基于这两种门.双qubit门比单qubit门难理解得多,不过也重要得多.它可以用来创建纠缠,没有纠缠,量子机就不可能有量子霸权. ...

  7. vscode插件记录

    前言 vscode因插件而强大. 记录一下好用的插件,以备后续参考. 插件汇总 内容1-14来源于<正点原子 I.MX6U驱动开发指南>4.5节, C/C++,这个肯定是必须的; C/C+ ...

  8. 我跟你说@RefreshScope跟Spring事件监听一起用有坑!

    本文记录一下我在 Spring 自带的事件监听类添加 @RefreshScope 注解时遇到的坑,原本这两个东西单独使用是各自安好,但当大家将它们组合在一起时,会发现我们的事件监听代码被重复执行.希望 ...

  9. 推荐王牌远程桌面软件Getscreen,所有的远程桌面软件中使用最简单的一个

    今天要推荐的远程桌面软件就是这款叫Getscreen的,推荐理由挺简单: 简单易用:只需要两步就能轻松连上远程桌面 第一步:在需要被远程连接的机器上下载它的Agent程序并启动,点击Send获得一个链 ...

  10. Python 潮流周刊第 2 季完结了,分享几项总结

    我订阅了很多的周刊/Newsletter,但是发现它们都有一个共同的毛病:就是缺乏对往期内容的整理,它们很少会对内容数据作统计分析,更没有将内容整理成合集的习惯. 在自己开始连载周刊后,我就想别开生面 ...