上节课简单说了一下mq是怎么保证数据一致性的。下面直接上代码了。

所需环境:1、zookeepor注册中心   2、kafka的服务端和工具客户端(工具客户端也可以不要只是为了更方便的查看消息而已)  3、springcloud的消息生产者  4、springcloud的消息消费者。

1、zk的安装和启动。百度有很多,kafka是依赖于zk的,所以zk必须要有。

2、kafka的服务端安装和启动。安装选择2进制的,不要选源码安装【我就遇到过坑,切记】,启动命令:进入kafka的安装目录后按住Shift键然后鼠标右键选择在此处打开命令窗口然后输入.\bin\windows\kafka-server-start.bat .\config\server.properties  (说明:kafka的服务端下载完成后默认的配置文件中的zk是本地的localhost:2181,自己的端口默认是9092,都是可以根据实际情况进行修改的)

3、springcloud 集成 kafka的消息生产者:

pom.xml:

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-kafka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-binder-kafka</artifactId>
</dependency>

application.yml: 这里我写的比较简单,输出的通道是在java代码中项目启动的时候去加载的,不在配置文件中,若topic主题不多建议放在配置文件中,因为我的topic比较多,采用的是动态生成的..

  cloud:
stream:
kafka:
binder:
brokers: localhost:9092 # kafka服务地址和端口
zk-nodes: localhost:2181 # ZK的集群配置地址和端口

项目启动加载topic:

@Component
@EnableBinding
public class KafkaTopicConfig { private static final Logger logger = LoggerFactory.getLogger(KafkaTopicConfig.class); @Autowired
private BinderAwareChannelResolver resolver; @PostConstruct
public void initKafkaTopic() {
logger.info("初始化topic begin..");
// 这里我写死了topic,其实可以动态的去表中读取,然后循环去调用下面的方法就好了
String topicName = "order";
// 这行代码是动态去生成topic的,先检查kafka中有没有传入的topic,有就直接返回topic,没有则新建
resolver.resolveDestination(topicName);
}
}

控制层到发送消息的代码:

   @Autowired
private IntegrateService integrateService; /**
* 下单操作,将个人账户充值100元,下单和充值分别属于不同库不同项目
* @param order
* @return
*/
@RequestMapping("/createOrder")
R createOrder(@RequestBody Order order) {
order.setCreateTime(new Date());
order.setOrderNo(System.currentTimeMillis() +
MathUtil.getFiveRandom());
integrateService.createOrder(order);
return R.ok();
} @Component
public class IntegrateService {
/**
* log
*/
private static final Logger logger = LoggerFactory.getLogger(IntegrateService.class); @Autowired
private OrderService orderService;
@Autowired
private SendMessage sendMessage; @Transactional(rollbackFor = Exception.class)
public void createOrder(Order order) {
try{
// 本地下单操作
orderService.createOrder(order);
Msg msg = Msg.getMsg("下单操作",1L, order);
// 下单后将发送消息到kafka通知消费者进行账户加100
sendMessage.sendOrderMessage(msg, "order");
}catch (Exception e) {
logger.error("下单失败..", e);
}
}
}

4、springcloud集成kafka的消息消费者:

pom.xml和上面的一致。

application.yml:

cloud:
stream:
kafka:
binder:
brokers: localhost:9092 # kafka服务地址和端口
zk-nodes: localhost:2181 # ZK的集群配置地址和端口
bindings:
inboundOrgChanges: #默认为input
destination: order #此处order是输出者定义的
content-type: application/json
group: licensingGroup #消费者组保证消息只被一组服务实例处理一次

定义接收接口:

/**
* @Title: CustomChannels
* @Description: 定义输入通道和yml中的配置一致,
* @author: sunxuesong@hztianque.com
* @date: Created in 21:43 2019/8/11
* @Modifired by:
*/
public interface CustomChannels { /**
* 接收订单消息通道
* @return
*/
@Input("inboundOrgChanges")
SubscribableChannel receiveOrderMsg();
}

消息监听进行消费账户加100:

@EnableBinding(CustomChannels.class)
public class ConsumerHandler { private static final Logger logger = LoggerFactory.getLogger(ConsumerHandler.class); @Autowired
private AmountService amountService; @StreamListener("inboundOrgChanges")
public void receiveOrderMsg(String msg) {
logger.info("接收消息msg:{}",msg);
if (StringUtils.isEmpty(msg)) {
return ;
}
JSONObject jsonObject = JSONObject.parseObject(msg);
jsonObject = jsonObject.getJSONObject("data");
Long userId = Long.parseLong(jsonObject.getString("userId"));
Double amount = Double.parseDouble(jsonObject.getString("amount"));
// 先查询当前账户然后和下单的金额相加
Account account1 = amountService.getAmountByUserId(userId);
BigDecimal b1 = new BigDecimal(Double.toString(account1.getAmount()));
BigDecimal b2 = new BigDecimal(Double.toString(amount));
Account account = new Account();
account.setAmount(b1.add(b2).doubleValue());
account.setUserId(userId);
amountService.updateAmountByUserId(account);
// return出去,不然会出现重复消费,后面有机会的话做全局id+日志进行控制幂等性
return;
}
}

下单之后在kafka的客户端中可以看到topic中的消息:

消费端一旦监听到topic中有消息就会立马进行消费。

虽然最终能保证消费者和生产者的消息最终一致性,但是难免会有一点点的延迟。这种方式不怎么好,分布式事物的控制还有其他方式:比如LCN解决。

LCN是进行分段提交的:两段提交协议或者三段提交协议,集成之后只需要在方法上加一个@TxTransactional主键就可以了。并且两边的数据是同时进行commit的,没有延迟。推荐使用LCN。

下次有空了周末在家集成一下然后发布出去..

mq解决分布式事物问题【代码】的更多相关文章

  1. mq解决分布式事物问题

    今天只看看原理,下一节看项目怎么集成mq进行解决分布式事物. 1.什么情况下会使用到分布式事物? 举例说明:现有一个支付系统,因为项目使用的是微服务框架,有订单模块和支付模块两个模块.生产者进行订单的 ...

  2. RabbitMq解决分布式事物

    一.RabbitMQ解决分布式事务思路: 案例: 经典案例,以目前流行点外卖的案例,用户下单后,调用订单服务,让后订单服务调用派单系统通知送外卖人员送单,这时候订单系统与派单系统采用MQ异步通讯. 二 ...

  3. seata代码控制回滚和临时挂起分布式事物

    seata代码控制回滚和临时挂起分布式事物 一.说明 二.功能实现 1.手动回滚分布式事物 2.临时挂起分布式事物 三.完整代码 四 参考链接 一.说明 此处只是简单的记录一下,使用了 Seata后, ...

  4. Atomikos和GTS-Fescar和TCC-Transaction和TX-LCN分布式事物的比较

    什么是分布式事物 分布式系统中保证不同节点之间的数据一致性的事物,叫做分布式事物. 为什么要用分布式事物 微服务,SOA等服务架构模式,一个是service产生多个节点,另一个是resource产生多 ...

  5. 搞懂分布式技术19:使用RocketMQ事务消息解决分布式事务

    搞懂分布式技术19:使用RocketMQ事务消息解决分布式事务 初步认识RocketMQ的核心模块 rocketmq模块 rocketmq-broker:接受生产者发来的消息并存储(通过调用rocke ...

  6. 2018-01-08 学习随笔 SpirngBoot整合Mybatis进行主从数据库的动态切换,以及一些数据库层面和分布式事物的解决方案

    先大概介绍一下主从数据库是什么?其实就是两个或N个数据库,一个或几个主负责写(当然也可以读),另一个或几个从只负责读.从数据库要记录主数据库的具体url以及BigLOG(二进制日志文件)的参数.原理就 ...

  7. RabbitMQ解决分布式事务

    案例:经典案例,以目前流行点外卖的案例,用户下单后,调用订单服务,让后订单服务调用派单系统通知送外卖人员送单,这时候订单系统与派单系统采用MQ异步通讯. RabbitMQ解决分布式事务原理: 采用最终 ...

  8. Springboot与ActiveMQ、Solr、Redis中分布式事物的初步探索

    Springboot与ActiveMQ.Solr.Redis中分布式事物的初步探索 解决的场景:事物中的异步问题,当要求数据库与solr服务器的最终一致时. 程序条件: 利用消息队列,当数据库添加成功 ...

  9. 【分布式事务】使用atomikos+jta解决分布式事务问题

    一.前言 分布式事务,这个问题困惑了小编很久,在3个月之前,就间断性的研究分布式事务.从MQ方面,数据库事务方面,jta方面.近期终于成功了,使用JTA解决了分布式事务问题.先写一下心得,后面的二级提 ...

随机推荐

  1. js 将base64转为图片

    var imgurl = response.data; $(".codeimg").attr('src','data:image/png;base64,'+imgurl); var ...

  2. day2 上午 游戏 对应关系--->判断素数---->多重背包 神题

    #include<iostream> using namespace std; int n; ; ]; long long p[maxn]; long long dp[maxn][maxn ...

  3. C表达式中的汇编指令

    C 表达式中的汇编指令 asm 为 gcc 中的关键字,asm 表达式为在 C代码中嵌套汇编指令,该表达式只是单纯的替换出汇编代码,并不对汇编代码的含义进行解析. asm 表达式有两种形式,第二种 a ...

  4. 基于cookie的用户登录状态管理

    cookie是什么 先来花5分钟看完这篇文章:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Cookies 看完上文,相信大家对cookie已经有 ...

  5. python——int()、hex()、oct()、bin()、float()数值类型转换函数

    摘要:在python中,数值类型转换函数常用的有浮点型float().取整int().八进制oct().二进制bin().十六进制hex()这五个函数. 单词float的意思就是浮动的意思: int是 ...

  6. Proxy动态代理-增强方法

    增强对象的功能 设计模式:一些通用的解决固定问题的方式 装饰器模式 代理模式 概念: 在代理模式(Proxy Pattern)中,一个类代表另一个类的功能.这种类型的设计模式属于结构型模式. 在代理模 ...

  7. jenkins手把手教你从入门到放弃01-jenkins简介(详解)

    一.简介 jenkins是一个可扩展的持续集成引擎.持续集成,也就是通常所说的CI(Continues Integration),可以说是现代软件技术开发的基础.持续集成是一种软件开发实践, 即团队开 ...

  8. Vue_声明周期

    Vue生命周期 在vue2.0的时候,声明钩子发生了改变,具体有八个 <!-- HTML部分 --> <div id="app"> <div>{ ...

  9. head first 设计模式第一章笔记

    设计模式是告诉我们如何组织类和对象以解决某种问题. 学习设计模式,也就是学习其他开发人员的经验与智慧,解决遇到的相同的问题. 使用模式的最好方式是:把模式装进脑子,然后在设计的时候,寻找何处可以使用它 ...

  10. 天啦!竟然从来没有人讲过 SpringBoot 支持配置如此平滑的迁移

    SpringBoot 是原生支持配置迁移的,但是官方文档没有看到这方面描述,在源码中才看到此模块,spring-boot-properties-migrator,幸亏我没有跳过.看到这篇文章的各位,可 ...