上节课简单说了一下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. 中文企业云操作系统 CecOS

    CecOS介绍 CecOS(原中文企业云操作系统.第一个版本基于oVirt 3.0,后续在此基础上不断升级迭代拓展至今,已形成基于基础底层和应用功能拓展集成在内的10款产品和四大平台),旨在通过先进的 ...

  2. Docker 学习 | 基础命令

    基本概念定义 基本组成 客户端/守护进程 C/S架构 本地/服务器 镜像 容器基石 只读文件系统 联合加载(union mount) 容器 通过镜像启动 执行 写时复制 仓库 公有 docker hu ...

  3. win7/win10系列的office安装与激活

    Windows系列电脑安装office傻瓜式教程 一.    下载与安装 下载 (1).所需工具:迅雷 下载链接:http://xl9.xunlei.com/ 显示界面如下,点击“立即下载”即可,然后 ...

  4. LocalDate类

    LocalDate类与Date类不同.Date类是距离一个固定时间点的毫秒数(UTC 1970.1.1 00:00:00) Date类表示时间点,LocalDate类用来表示日历表示法. import ...

  5. [git]关于github的一些用法笔记(入门)

    本视频来自于观看尚硅谷B站教学:https://www.bilibili.com/video/av10475153?from=search&seid=9735863941344749813 而 ...

  6. [Error]使用了未经检查或不安全的操作...

    编译错误注: MethodReflect.java使用了未经检查或不安全的操作.注: 有关详细信息, 请使用 -Xlint:unchecked 重新编译. 解决:在类前面加入下面一句解决 @Suppr ...

  7. hdu 1263 水果 (嵌套 map)

    水果Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)Total Submissio ...

  8. nyoj 217-a letter and a number (char)

    217-a letter and a number 内存限制:64MB 时间限制:3000ms 特判: No 通过数:4 提交数:5 难度:1 题目描述: we define f(A) = 1, f( ...

  9. HTML基础学习心得分享

    开始学些Html的时候主要进行一些简单的静态网页的处理: 1.HTML 标题 HTML 标题(Heading)是通过 h1-h6 加中括号<>等标签进行定义的. 2.HTML 段落 HTM ...

  10. [转发]CSR8670的DFU功能

    本文源自:https://blog.csdn.net/wzz4420381/article/details/52371409 作者:RyomaWang 申明:为了保持原作者内容,这里不进行任何修改,后 ...