上节课简单说了一下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. AndroidOS体系结构

    首先上图一张 对照着图,我们再来看Android 系统的体系结构就爽多了.我们从底层向上进行分析. 一.Linux 内核层 Linux Kernel 基于linux2.6.其核心系统服务如安全性.内存 ...

  2. 使用Typescript重构axios(七)——实现基础功能:处理响应header

    0. 系列文章 1.使用Typescript重构axios(一)--写在最前面 2.使用Typescript重构axios(二)--项目起手,跑通流程 3.使用Typescript重构axios(三) ...

  3. Vue 2.x 3.x 配置项目开发环境跟线上环境

    先找到package.json  (这是nuxt版的vue 可能会跟一般vue不一样  当然总体上差不多的) "scripts": { "dev": " ...

  4. day5-字典专区

    # 字典# dict 1.fromkeys()根据序列,创建字典,并指定统一的值 v = dict.fromkeys(["],123) print(v) {': 123} 2.get()根据 ...

  5. 一、netcore跨平台之 Linux上部署netcore和webapi

    这几天闲着的时候在linux上部署了一下netcore webapi,下面就纪要一下这个过程. 中间遇到不少的坑,心里都是泪啊. 话不多说,开始干活. ------------------------ ...

  6. 利用GitHub Pages + jekyll快速搭建个人博客

    前言 想搭建自己博客很久了(虽然搭了也不见得能产出多频繁). 最初萌生想写自己博客的想法,想象中,是自己一行一行码出来的成品,对众多快速构建+模板式搭建不屑一顾,也是那段时间给闲的,从前后端选型.数据 ...

  7. centos7 openssh 7.9.1 升级

    由于项目构建时间比较长,近期安全检查发现openssh有漏洞.所以要升级openssh到7.9p1版本.由于ssh用于远程连接,所以要谨慎操作. 1. 依赖安装 OpenSSL版本:目前OpenSSH ...

  8. pat 1124 Raffle for Weibo Followers(20 分)

    1124 Raffle for Weibo Followers(20 分) John got a full mark on PAT. He was so happy that he decided t ...

  9. Netty创建服务器与客户端

    Netty 创建Server服务端 Netty创建全部都是实现自AbstractBootstrap.客户端的是Bootstrap,服务端的则是ServerBootstrap. 创建一个 HelloSe ...

  10. 【故障公告】数据库服务器 CPU 近 100% 引发的故障(源于 .NET Core 3.0 的一个 bug)

    非常抱歉,这次故障给您带来麻烦了,请您谅解. 今天早上 10:54 左右,我们所使用的数据库服务(阿里云 RDS 实例 SQL Server 2016 标准版)CPU 突然飙升至 90% 以上,应用日 ...