第7篇:spring cloud 之bus应用

作者: 小肥爬爬 | 来源:发表于2021-03-09 20:11 被阅读0次

事件机制的概念

事件机制是编程的一种模式. 为什么运用消息机制呢? 就是为了程序解耦. 考虑一个最典型的场景. 用户付款购买了某个产品, 那么需要做的事情有:

  1. 将金额发送到金融中心(例如某付宝,某信支付,银联支付)
  2. 更新用户的支付信息表. (例如会员扣款, 加积分)
  3. 更新用户右上角的提示(用户能够获得一个通知)
  4. ... (其他可能多达10几项操作)

那么最差的代码写法当然是:


// 1 发送到金融中心
uploadPayInfoToBankCenter();

// 2 更新用户支付信息
updateUserCreditInfo();

// 3 发送通知
sendNotificationToUser();

// 4 其他操作
doOtherAction();

这里代码的问题主要耦合和异步. 耦合就不用说了, 在代码里引用了其他服务模块, 当其他模块修改了这里就可能要改. 另外要注意同步异步的问题. 发送到金融中心通常需要等待3-5秒反馈, 程序不能干巴巴等待, 都是通过异步去操作. 这样的话, 很可能会出现事务不一致的情况: 银行那边数据更新失败, 本地更新却成功了.

总而言之, 从业务角度考虑这一二三四步没有问题, 代码上是不能这么做的.

合理的做法应该是:

  1. 设计一个事件, 例如叫做 UserPaySuccessEvent .
  2. 所有支付相关的服务都关注这个消息, 并编写自己的相应代码.
  3. 当用户支付这个操作完成之后, 发出一个 UserPaySuccessEvent 事件
  4. 其他关注这个消息的服务代码就自动运行了.

spring cloud bus

同样地, 整个spring cloud 都是围绕着事件来进行设计的, 可以通过事件来驱动绝大多数的行为. 这些行为包括系统已有行为和用户自定义行为.

系统已有行为指什么呢? 例如重启某个实例. 用户自定义行为就是用户自己定义的事件.

自定义事件的实现

工程位于: https://gitee.com/xiaofeipapa/spring-cloud-demo
目录是: bus-demo

这个工程想实现这样的效果: orderService 发出消息, 监听该消息的 userService / notificationService 能够对该消息进行处理.

增加bus的maven 依赖

在父工程的pom里, 加入:

<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bus-amqp</artifactId>
        </dependency>

这表示会使用bus机制, 同时用mq 来进行处理.

启动rabbitmq

sudo docker run -d -p 5672:5672 -p 15672:15672 --name rabbitmq rabbitmq:management

新增了common工程

common工程封装所有微服务需要依赖的类, 例如工具类, 常量类, 事件类. 在这个工程我们加入了一个新的事件类:


image.png
package org.xiaofeipapa.feimall.common.event;

import org.springframework.cloud.bus.event.RemoteApplicationEvent;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationEventPublisherAware;

import java.util.Objects;

/**
 * 作者: 小肥爬爬
 * 简书: https://www.jianshu.com/u/db796a501972
 * gitee: https://gitee.com/xiaofeipapa
 * 邮箱: imyunshi@163.com
 * 您可以自由转载此博客文章, 恳请保留原链接, 谢谢!
 **/
public class UserPayEvent extends RemoteApplicationEvent {

    String userName;
    Boolean succ;           // 是否成功

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public Boolean getSucc() {
        return succ;
    }

    public void setSucc(Boolean succ) {
        this.succ = succ;
    }

    // 重新定义构造方法
    public UserPayEvent(Object source, String originService, String destinationService, String userName, Boolean succ) {
        super(source, originService, destinationService);
        this.userName = userName;
        this.succ = succ;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        if (!super.equals(o)) return false;
        UserPayEvent that = (UserPayEvent) o;
        return Objects.equals(userName, that.userName) &&
                Objects.equals(succ, that.succ);
    }

    @Override
    public int hashCode() {
        return Objects.hash(super.hashCode(), userName, succ);
    }

    @Override
    public String toString() {
        return "UserPayEvent{" +
                "userName='" + userName + '\'' +
                ", succ=" + succ +
                '}';
    }
}

新增 notificationApi 工程

如下:


image.png

增加工程间的依赖

修改 notificationApi, orderApi, userApi 三个子工程, 让它们都依赖 common 工程

修改 orderApi 工程

修改启动类

package org.xiaofeipapa.feimall.orderApi;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.cloud.bus.jackson.RemoteApplicationEventScan;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@EnableDiscoveryClient
@EnableAutoConfiguration(exclude={DataSourceAutoConfiguration.class})
@RemoteApplicationEventScan(basePackages = {"org.xiaofeipapa.feimall.common.event.*"})
public class OrderApiApplication {
    public static void main( String[] args )
    {
        SpringApplication.run(OrderApiApplication.class, args);
    }
}

因为我们将事件放到了一个独立的工程, 所以要启动扫描, 要不然工程会找不到代码.

新增service, 并加入触发事件的代码

package org.xiaofeipapa.feimall.orderApi.service;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Component;
import org.xiaofeipapa.feimall.common.event.UserPayEvent;

/**
 * 作者: 小肥爬爬
 * 简书: https://www.jianshu.com/u/db796a501972
 * gitee: https://gitee.com/xiaofeipapa
 * 邮箱: imyunshi@163.com
 * 您可以自由转载此博客文章, 恳请保留原链接, 谢谢!
 **/
@Component
public class OrderService implements ApplicationContextAware, ApplicationEventPublisherAware {

    ApplicationContext applicationContext;
    ApplicationEventPublisher applicationEventPublisher;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.applicationEventPublisher = applicationEventPublisher;
    }

    // 模拟用户下单的方法
    public void createOrder(String userName){
        System.out.println("=========== 用户已经下单了. " + userName);

        // 触发事件
        UserPayEvent payEvent = new UserPayEvent(
                userName + "_" + System.currentTimeMillis(),
                applicationContext.getId(), "*:**", userName, true);

        applicationEventPublisher.publishEvent(payEvent);
    }

}

增加controller 接口


    @Resource
    OrderService orderService;


    @GetMapping(value="/buy")
    private String buy(@RequestParam String userName){
        orderService.createOrder(userName);
        return "success";
    }

顺手把工程启动吧.

修改 notificationApi 工程

启动类和orderApi 类似, 需要加入扫描路径

package org.xiaofeipapa.feimall.noticationApi;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.cloud.bus.jackson.RemoteApplicationEventScan;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@EnableDiscoveryClient
@EnableAutoConfiguration(exclude={DataSourceAutoConfiguration.class})
@RemoteApplicationEventScan(basePackages = {"org.xiaofeipapa.feimall.common.event.*"})
public class NotificationApiApplication {
    public static void main( String[] args )
    {
        SpringApplication.run(NotificationApiApplication.class, args);
    }
}

增加service 处理事件方法

package org.xiaofeipapa.feimall.noticationApi.service;

import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import org.xiaofeipapa.feimall.common.event.UserPayEvent;

/**
 * 作者: 小肥爬爬
 * 简书: https://www.jianshu.com/u/db796a501972
 * gitee: https://gitee.com/xiaofeipapa
 * 邮箱: imyunshi@163.com
 * 您可以自由转载此博客文章, 恳请保留原链接, 谢谢!
 **/
@Component
public class NotificationService {

    // 在订单成功之后, 发出通知
    @EventListener(classes = {UserPayEvent.class})
    private String sendMessageAfterPay(UserPayEvent payEvent) throws Exception{

        // 简单打个log, 表示处理
        System.out.println("发送通知成功! 用户: " + payEvent.getUserName());
        return "发送通知成功";
    }
}

顺便启动工程.

同样修改 userApi工程

启动类修改类似, 不贴了. userService 修改如下:

package org.xiaofeipapa.feimall.userApi.service;

import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.xiaofeipapa.feimall.common.event.UserPayEvent;

/**
 * 作者: 小肥爬爬
 * 简书: https://www.jianshu.com/u/db796a501972
 * gitee: https://gitee.com/xiaofeipapa
 * 邮箱: imyunshi@163.com
 * 您可以自由转载此博客文章, 恳请保留原链接, 谢谢!
 **/
@Component
public class UserService {

    // 在订单成功之后, 更新用户信息
    @EventListener(classes = {UserPayEvent.class})
    private String updateInfoAfterPay(UserPayEvent payEvent) throws Exception{

        // 简单打个log, 表示处理
        System.out.println("更新用户消息, 用户: " + payEvent.getUserName());
        return "更新用户成功";
    }

}

启动工程.

测试

现在启动 gateway 工程, 然后浏览器访问: localhost:10000/api/order/buy?userName=fei , 同时观察控制台, 可以看到当用户下单之后, 其他订阅事件的方法也随之更新.

相关文章

网友评论

    本文标题:第7篇:spring cloud 之bus应用

    本文链接:https://www.haomeiwen.com/subject/zqnvqltx.html