上节课简单说了一下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. 腾讯新闻构建高性能的 react 同构直出方案

    在腾讯新闻抢金达人活动 node 同构直出渲染方案的总结文章中我们整体了解了下同构直出渲染方案在我们项目中的使用.正如我在上篇文章结尾所说的: 应用型技术的难点不是在克服技术问题,而是在于能够不断的结 ...

  2. 测试面试题集-测试用例设计:登录、购物车、QQ收藏表情、转账、充值、提现

    以下内容首发于微信公众号[ITester软件测试小栈]: 测试面试题集-2.测试用例设计 大家好 我是coco小锦鲤 上周五给大家分享了测试基础理论题 这个周五给大家分享测试用例设计题 测试用例的考察 ...

  3. python学习之【第十三篇】:Python中的生成器

    1.为什么要有生成器? 在Python中,通过列表生成式,我们可以直接创建一个列表.但是,受到内存限制,列表容量肯定是有限的.而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅 ...

  4. Css3动画-@keyframes与animation

    一.@keyframe 定义和用法 @keyframes是用来创建帧动画的,我们通过这个属性可以用纯css来实现一些动画效果. 一般格式是: @keyframes 动画名称{ 0%{ 动画开始时的样式 ...

  5. 开源 ERP 系统 GoodERP

    如果你有一个苹果,我也有一个苹果,彼此交换后,你我还是一人一个苹果,但是如果你有一个想法,我有一个想法,彼此交换后,你我就都有两个想法,三个人呢?一百个人呢? 使用openobject框架 重写全部功 ...

  6. 前端与算法 leetcode 48. 旋转图像

    目录 # 前端与算法 leetcode 48. 旋转图像 题目描述 概要 提示 解析 解法一:转置加翻转 解法二:在单次循环中旋转 4 个矩形 算法 传入测试用例的运行结果 执行结果 GitHub仓库 ...

  7. JSON《===》JavaBean的相互转换

    1.JSON的作用和好处  在JavaWeb项目中前后端直接的交互,接口之间的对接等等,基本离不开JSON. JSON: 全称JavaScript Object Notation(JavaScript ...

  8. 架构设计:"4+1"视图

    概念 "4+1"视图,是指从5个不同视角来描述软件体系结构. "4+1"分别指: 逻辑视图 过程视图 物理视图 开发视图 场景/用例 视图 逻辑架构的描述可以围 ...

  9. PHP程序员-常用工具

    三连问 经常有社区的同学问: “我的PHP程序有没有阻塞,我的PHP程序有没有开启协程(对自己写好的代码表示不自信),我的PHP程序有没有问题”.然后贴出了自己的程序,然后进入了愉快的灌水环节,随着时 ...

  10. error: (-215:Assertion failed) size.width>0 && size.height>0 in function 'cv::imshow'

    用Python打开图像始终提示错误 error: OpenCV(4.1.1) C:\projects\opencv-python\opencv\modules\highgui\src\window.c ...