【SSM分布式架构电商项目-28】RabbitMQ实现商品数据的同步

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/cckevincyh/article/details/80319902

Spring-Rabbit

Spring项目

http://spring.io/projects
这里写图片描述

这里写图片描述

简介

这里写图片描述

这里写图片描述

使用

消费者

这里写图片描述

生产者

这里写图片描述

配置文件

1、 定义连接工厂
这里写图片描述
2、 定义模板(可以指定交换机或队列)
这里写图片描述
这里写图片描述
3、 定义队列、交换机、以及完成队列和交换机的绑定
这里写图片描述
4、 定义监听
这里写图片描述
5、 定义管理,用于管理队列、交换机等:
这里写图片描述

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:rabbit="http://www.springframework.org/schema/rabbit"
    xsi:schemaLocation="http://www.springframework.org/schema/rabbit
    http://www.springframework.org/schema/rabbit/spring-rabbit-1.4.xsd
    http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-4.1.xsd">

    <!-- 定义RabbitMQ的连接工厂 -->
    <rabbit:connection-factory id="connectionFactory"
        host="127.0.0.1" port="5672" username="taotao" password="taotao"
        virtual-host="/taotao" />

    <!-- 定义Rabbit模板,指定连接工厂以及定义exchange -->
    <rabbit:template id="amqpTemplate" connection-factory="connectionFactory" exchange="fanoutExchange" />
    <!-- <rabbit:template id="amqpTemplate" connection-factory="connectionFactory"
        exchange="fanoutExchange" routing-key="foo.bar" /> -->

    <!-- MQ的管理,包括队列、交换器等,来完成自动声明 -->
    <rabbit:admin connection-factory="connectionFactory" />

    <!-- 定义队列,自动声明 -->
    <rabbit:queue name="myQueue" auto-declare="true"/>

    <!-- 定义交换器,自动声明 -->
    <!-- 自动声明:使用交换机的时候触发,如果交换机不存在就创建,存在就忽略 -->
    <rabbit:fanout-exchange name="fanoutExchange" auto-declare="true">
        <rabbit:bindings>
            <rabbit:binding queue="myQueue"/>
        </rabbit:bindings>
    </rabbit:fanout-exchange>

<!--    <rabbit:topic-exchange name="myExchange">
        <rabbit:bindings>
            <rabbit:binding queue="myQueue" pattern="foo.*" />
        </rabbit:bindings>
    </rabbit:topic-exchange> -->

    <!-- 队列监听 -->
    <rabbit:listener-container connection-factory="connectionFactory">
        <rabbit:listener ref="foo" method="listen" queue-names="myQueue" />
    </rabbit:listener-container>

    <bean id="foo" class="cn.itcast.rabbitmq.spring.Foo" />

</beans>

持久化交换机和队列

这里写图片描述

持久化:将交换机或队列的数据保存到磁盘,服务器宕机或重启之后依然存在。
非持久化:将交换机或队列的数据保存到内存,服务器宕机或重启之后将不存在。

非持久化的性能高于持久化。

如何选择持久化?非持久化? – 看需求。

非持久化设置:
这里写图片描述

实现商品数据的同步

使用什么队列模式?

使用通配符模式。

在后台系统中发送消息到交换机

导入依赖

这里写图片描述

这里写图片描述

队列和交换机的绑定关系

实现:
1、 在配置文件中将队列和交换机完成绑定
2、 可以在管理界面中完成绑定
a) 绑定关系如果发生变化,需要修改配置文件,并且服务需要重启
b) 管理更加灵活
c) 更容易对绑定关系的权限管理,流程管理

配置

这里写图片描述

这里写图片描述

这里写图片描述

这里写图片描述

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:rabbit="http://www.springframework.org/schema/rabbit"
    xsi:schemaLocation="http://www.springframework.org/schema/rabbit
    http://www.springframework.org/schema/rabbit/spring-rabbit-1.4.xsd
    http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-4.1.xsd">

    <!-- 定义RabbitMQ的连接工厂 -->
    <rabbit:connection-factory id="connectionFactory"
        host="${rabbitmq.host}" port="${rabbitmq.port}" username="${rabbitmq.username}"
        password="${rabbitmq.password}" virtual-host="${rabbitmq.vhost}" />

    <!-- 管理 -->
    <rabbit:admin connection-factory="connectionFactory" />

    <!-- 定义交换机 -->
    <rabbit:topic-exchange name="TAOTAO-ITEM-EXCHANGE"
        auto-declare="true" durable="true" />

    <!-- 定义模板 -->
    <rabbit:template id="rabbitTemplate"
        connection-factory="connectionFactory" exchange="TAOTAO-ITEM-EXCHANGE" />

</beans>
消息内容

方案:
1、 将Item对象做json序列化发送
a) 数据大
b) 有些数据其他人是可能用不到的
2、 发送商品的id、操作类型

实现

这里写图片描述
这里写图片描述

package com.taotao.manage.service;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.abel533.entity.Example;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.taotao.common.bean.EasyUIResult;
import com.taotao.common.service.ApiService;
import com.taotao.manage.mapper.ItemMapper;
import com.taotao.manage.pojo.Item;
import com.taotao.manage.pojo.ItemDesc;
import com.taotao.manage.pojo.ItemParamItem;

@Service
public class ItemService extends BaseService<Item> {

    @Autowired
    private ItemMapper itemMapper;

    @Autowired
    private ItemDescService itemDescService;

    @Autowired
    private ItemParamItemService itemParamItemService;

    @Autowired
    private ApiService apiService;

   // @Value("${TAOTAO_WEB_URL}")
   // private String TAOTAO_WEB_URL;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    private static final ObjectMapper MAPPER = new ObjectMapper();

    public Boolean saveItem(Item item, String desc, String itemParams) {
        // 初始值
        item.setStatus(1);
        item.setId(null); // 出于安全考虑,强制设置id为null,通过数据库自增长得到
        Integer count1 = super.save(item);

        // 保存商品描述数据
        ItemDesc itemDesc = new ItemDesc();
        itemDesc.setItemId(item.getId());
        itemDesc.setItemDesc(desc);
        Integer count2 = this.itemDescService.save(itemDesc);

        // 保存规格参数数据
        ItemParamItem itemParamItem = new ItemParamItem();
        itemParamItem.setItemId(item.getId());
        itemParamItem.setParamData(itemParams);
        Integer count3 = this.itemParamItemService.save(itemParamItem);

        //发送消息到MQ的交换机,通知其他系统
        sendMsg(item.getId(), "insert");

        return count1.intValue() == 1 && count2.intValue() == 1 && count3.intValue() == 1;

    }

    public EasyUIResult queryItemList(Integer page, Integer rows) {

        // 设置分页参数
        PageHelper.startPage(page, rows);

        Example example = new Example(Item.class);
        // 安装创建时间排序
        example.setOrderByClause("created DESC");
        List<Item> items = this.itemMapper.selectByExample(example);

        PageInfo<Item> pageInfo = new PageInfo<Item>(items);

        return new EasyUIResult(pageInfo.getTotal(), pageInfo.getList());
    }

    public Boolean updateItem(Item item, String desc, String itemParams) {
        item.setStatus(null);// 强制设置状态不能被修改
        Integer count1 = super.updateSelective(item);

        ItemDesc itemDesc = new ItemDesc();
        itemDesc.setItemId(item.getId());
        itemDesc.setItemDesc(desc);
        Integer count2 = this.itemDescService.updateSelective(itemDesc);

        // 更新规格参数数据
        Integer count3 = this.itemParamItemService.updateItemParamItem(item.getId(), itemParams);

//        try {
//            // 通知其他系统该商品已经更新
//            String url = TAOTAO_WEB_URL + "/item/cache/" + item.getId() + ".html";
//            this.apiService.doPost(url);
//        } catch (Exception e) {
//            e.printStackTrace();
//        }

        //发送消息到MQ的交换机,通知其他系统
        sendMsg(item.getId(), "update");

        return count1.intValue() == 1 && count2.intValue() == 1 && count3.intValue() == 1;
    }

    private void sendMsg(Long itemId, String type){
        try {
            //发送消息到MQ的交换机,通知其他系统
            Map<String, Object> msg = new HashMap<String, Object>();
            msg.put("itemId", itemId);
            msg.put("type", type);
            msg.put("date", System.currentTimeMillis());
            this.rabbitTemplate.convertAndSend("item." + type, MAPPER.writeValueAsString(msg));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

前台系统接收消息

导入依赖

这里写图片描述
这里写图片描述

配置

这里写图片描述

这里写图片描述

这里写图片描述

具体处理逻辑

这里写图片描述

这里写图片描述

package com.taotao.web.mq.handler;

import org.springframework.beans.factory.annotation.Autowired;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.taotao.common.service.RedisService;
import com.taotao.web.service.ItemService;

public class ItemMQHandler {

    @Autowired
    private RedisService redisService;

    private static final ObjectMapper MAPPER = new ObjectMapper();

    /**
     * 删除缓存中的商品数据,完成数据同步
     * 
     * @param msg
     */
    public void execute(String msg) {
        try {
            JsonNode jsonNode = MAPPER.readTree(msg);
            Long itemId = jsonNode.get("itemId").asLong();
            String key = ItemService.REDIS_KEY + itemId;
            this.redisService.del(key);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

在界面管理工具中完成绑定关系

这里写图片描述

搜索系统中接收消息

导入依赖

这里写图片描述

配置

这里写图片描述

这里写图片描述
加入其它需要的依赖和service:
这里写图片描述

处理业务逻辑

这里写图片描述

package com.taotao.search.mq.handler;

import org.apache.commons.lang3.StringUtils;
import org.apache.solr.client.solrj.impl.HttpSolrServer;
import org.springframework.beans.factory.annotation.Autowired;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.taotao.search.bean.Item;
import com.taotao.search.service.ItemService;

public class ItemMQHandler {

    private static final ObjectMapper MAPPER = new ObjectMapper();

    @Autowired
    private HttpSolrServer httpSolrServer;

    @Autowired
    private ItemService itemService;

    /**
     * 处理消息,新增、修改、删除的消息,将商品数据同步到solr中
     * 消息中并没有包含商品的基本数据,需要通过id到后台系统提供的接口中获取
     * 
     * @param msg
     */
    public void execute(String msg) {
        try {
            JsonNode jsonNode = MAPPER.readTree(msg);
            Long itemId = jsonNode.get("itemId").asLong();
            String type = jsonNode.get("type").asText();
            if (StringUtils.equals(type, "insert") || StringUtils.equals(type, "update")) {
                Item item = this.itemService.queryById(itemId);
                this.httpSolrServer.addBean(item);
                this.httpSolrServer.commit();
            } else if (StringUtils.equals(type, "delete")) {
                this.httpSolrServer.deleteById(String.valueOf(itemId));
                this.httpSolrServer.commit();
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

ItemService:

package com.taotao.search.service;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.taotao.common.service.ApiService;
import com.taotao.search.bean.Item;

@Service
public class ItemService {

    @Autowired
    private ApiService apiService;

    @Value("${TAOTAO_MANAGE_URL}")
    private String TAOTAO_MANAGE_URL;

    private static final ObjectMapper MAPPER = new ObjectMapper();

    public Item queryById(Long itemId) {
        try {
            String url = TAOTAO_MANAGE_URL + "/rest/api/item/" + itemId;
            String jsonData = this.apiService.doGet(url);
            if (StringUtils.isNotEmpty(jsonData)) {
                return MAPPER.readValue(jsonData, Item.class);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

}

在管理工具中绑定队列和交换机

这里写图片描述

测试

测试结果:商品数据已经完成和索引数据的同步。

总结

使用MQ实现商品数据的同步优势:
1、 降低系统间耦合度
2、 便于管理数据的同步

阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页