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

  • 解决的场景:事物中的异步问题,当要求数据库与solr服务器的最终一致时。
  • 程序条件
  • 利用消息队列,当数据库添加成功时,将更新solr的请求发进消息队列中。
  • 考虑到拿消息的服务做了集群的问题,利用redis做幂等性:将消息的JMSMessageID作为redis的key。
  • 如果取消息的服务最终失败,就产生了需要补偿的问题。
  • 解决方案
  • 将消息的参数存入redis中的Hash列表(结构:补偿的队列名 补偿的JMSMessageID(keys)以及对应的参数),做一个补偿系统,每隔一定时间扫描redis中补偿的队列名。
  • 当redis中该hash列表不为空且长度大于0时,将整个hash列表的key序列化后放入消息队列中。
  • 服务端的JMSListener扫描到目标队列有消息入队时,首先判断幂等性的问题;然后取出该消息,反序列化后遍历keys,并且通过redis读取对象添加到对应的表中。
  • 若未补偿成功将消息写入分布式日志中。

代码:

首先是将添加solr的消息放入消息队列:
  1. public AppResult AddItem(TbItem item, TbItemDesc itemDesc) {
  2. IDUtils idUtils=new IDUtils();
  3. Long itemId=idUtils.nextId();
  4. Date date=new Date();
  5. item.setId(itemId);
  6. item.setCreated(date);
  7. item.setUpdated(date);
  8. itemDesc.setCreated(date);
  9. itemDesc.setUpdated(date);
  10. itemDesc.setItemId(itemId);
  11. int ItemCount=itemMapper.insertSelective(item);
  12. int ItemDescCount=itemDescMapper.insertSelective(itemDesc);
  13. if (ItemCount!=1||ItemDescCount!=1){
  14. throw new AppExeption(201,"添加失败");
  15. }
  16. SolrTbItem solrTbItem=new SolrTbItem();
  17. solrTbItem.setItemTitle(item.getTitle());
  18. solrTbItem.setItemCategoryName("123");
  19. solrTbItem.setItemImage(item.getImage());
  20. solrTbItem.setId(itemId);
  21. solrTbItem.setItemPrice(item.getPrice());
  22. solrTbItem.setItemSellPoint(item.getSellPoint());
  23. //添加数据库
  24. //添加到solr中,但需要通过消息队列
  25. jmsTemplate.convertAndSend("tym_002",JsonUtils.objectToJson(solrTbItem));
  26. return new AppResult(true,200,"添加成功",null);
  27. }
初始消息监听器监听(集群):
  1. @JmsListener(destination = "tym_002",containerFactory = "jmsQueryListenerFactory")
  2. public void testreceiveQueue(TextMessage textMessage, Session session) throws Exception {
  3. SolrTbItem solrTbItem=null;
  4. try {
  5. String id=textMessage.getJMSMessageID();
  6. System.out.println(id);
  7. if (redisTemplate.opsForValue().setIfAbsent(textMessage.getJMSMessageID(),"1")==false){
  8. return;
  9. }
  10. solrTbItem= JsonUtils.jsonToPojo(textMessage.getText(),SolrTbItem.class);
  11. int i=1/0;
  12. AppResult result=searchService.addTbitem(solrTbItem);
  13. if (result.isSuccess()==false){
  14. throw new AppExeption();
  15. }
  16. System.out.println("成功添加");
  17. textMessage.acknowledge();
  18. redisTemplate.delete(textMessage.getJMSMessageID());
  19. } catch (Exception e) {
  20. System.out.println("存入redis 等待补偿系统");
  21. redisTemplate.delete(textMessage.getJMSMessageID());
  22. //补偿把item存到redis中,然后利用任务系统读取key keys values 中的keys传递给Jms补偿监听器
  23. //把keys读取出来并遍历,然后调用服务添加到solr中。
  24. redisTemplate.opsForHash().put("search_bc_add",textMessage.getJMSMessageID(),solrTbItem);
  25. System.out.println("将整个消息队列放入redis的hash列表中,名为search_bc_add,等待消息系统取出补偿。。");
  26. textMessage.acknowledge();
  27. }
  28. }
监听服务(监听任务):
  1. @Scheduled(cron = "*/10 * * * * ?")
  2. public void searchItemBcTask(){
  3. //先判断redis里面有没有该队列
  4. //取出该队列且根据长度和是否为空判断是否要发送补偿消息进补偿队列
  5. Set<String> keys=redisTemplate.opsForHash().keys("search_bc_add");
  6. if (keys!=null&&keys.size()>0){
  7. System.out.println("发现有补偿消息");
  8. jmsTemplate.convertAndSend("cs1901_bc_search_add", JsonUtils.objectToJson(keys));
  9. }else {
  10. System.out.println("无---------");
  11. }
  12. }
补偿监听(集群):
  1. //该监听器监听补偿 即是第一个监听器未添加进solr成功,被任务系统查出,查找的消息队列
  2. @JmsListener(destination = "cs1901_bc_search_add",containerFactory = "jmsQueryListenerFactory")
  3. public void BcSearchAdd(TextMessage textMessage, Session session) throws Exception {
  4. //首先考虑幂等性 问题,无则存入redis 有则return
  5. //监听cs1901_bc_search_add 队列,若有则处理
  6. // 处理方法,遍历keys,查询redis中存的tbitem 并调用searchService的方法插入solr索引库
  7. //成功与否都删除redis中的键,全部键插入完成删除整个hash列表
  8. //未成功打印到分布式日志中
  9. try {
  10. if (redisTemplate.hasKey(textMessage.getJMSMessageID())==true){
  11. System.out.println("该数据被别的监听器锁住");
  12. return;
  13. }
  14. System.out.println("将补偿消息锁住");
  15. redisTemplate.opsForValue().set(textMessage.getJMSMessageID(),"");
  16. Set keys=JsonUtils.jsonToPojo(textMessage.getText(), Set.class);
  17. for (Object key : keys) {
  18. SolrTbItem solrTbItem= (SolrTbItem) redisTemplate.opsForHash().get("search_bc_add",key);
  19. System.out.println(solrTbItem.getId());
  20. AppResult result=searchService.addTbitem(solrTbItem);
  21. if (result.isSuccess()==false){
  22. throw new AppExeption();
  23. }
  24. redisTemplate.opsForHash().delete("search_bc_add",key);
  25. System.out.println("redis删除小键");
  26. }
  27. redisTemplate.delete("search_bc_add");
  28. System.out.println("redis删除hash");
  29. redisTemplate.delete(textMessage.getJMSMessageID());
  30. System.out.println("redis删除该消息,解除锁");
  31. textMessage.acknowledge();
  32. }catch (Exception e){
  33. //此处打印
  34. textMessage.acknowledge();
  35. }
  36. }

第一次的监听服务的补偿暂时未做重试,由于考虑到定义次数的线程不安全性。

Springboot与ActiveMQ、Solr、Redis中分布式事物的初步探索的更多相关文章

  1. Redis 中的原子操作(3)-使用Redis实现分布式锁

    Redis 中的分布式锁如何使用 分布式锁的使用场景 使用 Redis 来实现分布式锁 使用 set key value px milliseconds nx 实现 SETNX+Lua 实现 使用 R ...

  2. 使用redis 中的事务处理实现商品秒杀

    redis中的事务处理: redis中的事物事物处理是指能够批量的执行一组命令(当事务开始执行时,事务中的命令能够按照按照规定好的顺序执行而不会被插队或打断): 与mysql事务的区别在于:mysql ...

  3. SpringBoot进阶教程(二十七)整合Redis之分布式锁

    在之前的一篇文章(<Java分布式锁,搞懂分布式锁实现看这篇文章就对了>),已经介绍过几种java分布式锁,今天来个Redis分布式锁的demo.redis 现在已经成为系统缓存的必备组件 ...

  4. springboot(三):Spring boot中Redis的使用

    spring boot对常用的数据库支持外,对nosql 数据库也进行了封装自动化. redis介绍 Redis是目前业界使用最广泛的内存数据存储.相比memcached,Redis支持更丰富的数据结 ...

  5. 在AspNetCore 中 使用Redis实现分布式缓存

    AspNetCore 使用Redis实现分布式缓存 上一篇讲到了,Core的内置缓存:IMemoryCache,以及缓存的基础概念.本篇会进行一些概念上的补充. 本篇我们记录的内容是怎么在Core中使 ...

  6. 在AspNetCore 中 使用Redis实现分布式缓存 (转载)

    文章概念描述 分布式缓存描述:分布式缓存重点是在分布式上,相信大家接触过的分布式有很多中,像分布式开发,分布式部署,分布式锁.事物.系统 等有很多.使我们对分布式本身就有一个很明确的认识,分布式就是有 ...

  7. SpringBoot(三) :Spring boot 中 Redis 的使用

    前言: 这一篇讲的是Spring Boot中Redis的运用,之前没有在项目中用过Redis,所以没有太大的感觉,以后可能需要回头再来仔细看看. 原文出处: 纯洁的微笑 SpringBoot对常用的数 ...

  8. 【转载】在AspNetCore 中 使用Redis实现分布式缓存

    原文地址:https://www.cnblogs.com/szlblog/p/9045209.html AspNetCore 使用Redis实现分布式缓存 上一篇讲到了,Core的内置缓存:IMemo ...

  9. SpringBoot电商项目实战 — Redis实现分布式锁

    最近有小伙伴发消息说,在Springboot系列文第二篇,zookeeper是不是漏掉了?关于这个问题,其实我在写第二篇的时候已经考虑过,但基于本次系列文章是实战练习,在项目里你能看到Zookeepe ...

随机推荐

  1. Spark 基础操作

    1. Spark 基础 2. Spark Core 3. Spark SQL 4. Spark Streaming 5. Spark 内核机制 6. Spark 性能调优 1. Spark 基础 1. ...

  2. java war包 远程debug出现的问题解决,学会查看日志

    开启远程debug之后,8005 关闭tomcat 又启动不了了.. netstat -lnp 未发现8005接口 eclipse 内远程链接到服务器,debug 下发现服务器线程启动也存在问题.很多 ...

  3. Linux 安装部署 Redis

    一.Redis介绍 Redis是当前比较热门的NOSQL系统之一,它是一个key-value存储系统.和Memcache类似,但很大程度补偿了Memcache的不足,它支持存储的value类型相对更多 ...

  4. 约会II

    #include <stdio.h> int main() { int a,b; while(scanf("%d %d",&a,&b)!=EOF& ...

  5. Scala 面向对象编程之类

    定义一个简单的类 // 定义类,包含field以及方法 class HelloWorld { private var name = "leo" def sayHello() { p ...

  6. Thread interrupted() 线程的中断

    问题: 1.线程的中断方式. 2.为什么中断阻塞中的线程,会抛出异常. 代码示例: package com.hdwl.netty; public class ThreadInterrupted { p ...

  7. zookeeper启动占用8080端口,跟HDFS默认使用的8080端口冲突

    zookeeper最近的版本中有个内嵌的管理控制台是通过jetty启动,也会占用8080 端口. 通过查看zookeeper的官方文档,发现有3种解决途径: (1).删除jetty. (2)修改端口. ...

  8. 怎样获取iframe节点的window对象

    需要使用iframeElement.contentWindow;  var frame = document.getElementById('theFrame'); var frameWindow = ...

  9. Spring全框架讲解

    Day 01: https://blog.csdn.net/sinat_29211659/article/details/81335229

  10. 在Windows平台搭建C语言开发环境

    一.在Windows平台搭建DEV C++集成开发环境     官网 https://sourceforge.net/projects/orwelldevcpp/ 中下载Dev C++运行即可 环境准 ...