上节课简单说了一下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实现列表从下往上循环滚动

    html: <div class="liscorll"> <ul> <li>内容</li> </ul> </div ...

  2. 大数据之路day04_2--经典bug(equals与==比较不同,break的跳出不同)

    一.equals与==比较不同 在实现某个人去5个商场去购物,控制台输入是否购物(Y/N)的时候,在比较出了问题,发现无论输入什么都是false,后来查阅资料发现,字符串的比较,==和equals不一 ...

  3. NOIP模拟14-16

    最近事情有些多,先咕了! 鸽了,时间太久远了,写了话坑太大,太费时间了!

  4. [干货]AspNetCore熟练应用CancellationToken,CTO会对你刮目相看

    背景 已经有很多文章记录了 web程序中采用异步编程的优势和.Net异步编程的用法, 异步编程虽然不能解决查询数据库的瓶颈, 但是利用线程切换,能最大限度的弹性利用工作线程, 提高了web服务的响应能 ...

  5. MySQL InnoDB MVCC

    MySQL 原理篇 MySQL 索引机制 MySQL 体系结构及存储引擎 MySQL 语句执行过程详解 MySQL 执行计划详解 MySQL InnoDB 缓冲池 MySQL InnoDB 事务 My ...

  6. PowerMock学习(三)之Mock局部变量

    编写powermock用例步骤: 类上面先写这两个注解@RunWith(PowerMockRunner.class).@PrepareForTest(StudentService.class) 先模拟 ...

  7. 忘记Linux登录密码的破解方法

    注意:1.破解方式只限于7.0以后的Linux系统. 2.要注意自己linux系统中有没有开启selinux,如果开启则在后面要建一个名为:autorelabel的隐藏文件.     1.启动Linu ...

  8. thinkphp6.0 多应用模块下提示控制器不存在

    thinkphp6.0 多应用模块下提示控制器不存在 在项目根目录下使用Composer composer require topthink/think-multi-app 参考链接

  9. 设置 DNS 服务器转发试验

    一.主节点的配置 1.yum install bind -y 安装 DNS 服务 2.vim /etc/named.conf 编辑 DNS 的配置文件 3. vim /etc/named.rfc191 ...

  10. GitHub远程库的搭建以及使用

    GitHub远程库的搭建 一).配置SSH 步骤: 1).注册GitHub账号 2).本地git仓库与远程的GitHub仓库的传输要通过SSH进行加密 3).创建SSH key ​ 1.检查在用户主目 ...