Bootstrap

微服务专题14-Spring Cloud Bus

前言

前面的章节我们讲了Spring Cloud Stream (上)

本节,继续微服务专题的内容分享,共计16小节,分别是:

本节内容重点为:

  • Spring 事件/监听器机制
  • Spring Cloud Bus 实现

知识回顾和发散

Java 事件/监听者模式

事件
  • 所有事件类型扩展 java.util.Event
  • 每个事件对象都有事件源
事件监听器
  • 事件监听器接口扩展 java.util.EventListener

  • 方法参数类型

    • java.util.Event 对象(子类)
    • 事件源
      • JPA EntityListener
        • @Entity
        • EntityManager persist(Object)
  • 监听方法访问限定符 public

  • 监听方法是没有返回值(void

    • 例外:Spring @EventListener
  • 监听方法不会 throws Throwable

多事件监听器接口

限制:Java 8 之前,接口没有 default method,当出现多个监听方法时,需要 Adapter 抽象类提供空实现。

  • Adapter
    • 接口空实现
    • A 和 B 两种没有层次关系,A 适配为 B

注意:事件/监听者器,尤其在扩展时,注意事件(语法)时态。

举例说明:GUI 桌面程序
public class GUIEvent {

    public static void main(String[] args) throws Exception {

        final JFrame frame = new JFrame("简单 GUI 程序 - Java 事件/监听机制");

        frame.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent event) {
                System.out.printf("[%s] 事件 : %s\n", Thread.currentThread().getName(), event);
            }
        });

        frame.setBounds(300, 300, 400, 300);

        frame.setVisible(true);

        frame.addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent e) {
                frame.dispose();
            }

            @Override
            public void windowClosed(WindowEvent event) {
                System.exit(0);
            }
        });

    }
}

Spring 事件

Spring 事件

Spring 事件基类 ApplicationEvent

  • 相对于 java.util.EventObject 增加事件发生时间戳
    • timestamp
内建事件

ContextRefreshedEvent

  • ConfigurableApplicationContext#refresh()

ContextClosedEvent

  • ConfigurableApplicationContext#close()

ContextStartedEvent

  • ConfigurableApplicationContext#start()

ContextStoppedEvent

  • ConfigurableApplicationContext#stop()

RequestHandledEvent

  • FrameworkServlet#publishRequestHandledEvent

注意:事件/监听者器,注意事件(语法)时态。

public class SpringEvent {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();

        context.register(Object.class);

        // 增加 监听
        context.addApplicationListener(e -> {
            System.err.println("监听 : " + e.getClass().getSimpleName());
        });

        context.refresh();
        context.start();
        context.stop();
        context.close();

    }
}

打印如下:
在这里插入图片描述

Spring 事件监听器

所有 Spring 事件监听器实现 ApplicationListener

public class SpringAnnotationDrivenEvent {

    public static void main(String[] args) {

        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        // SpringAnnotationDrivenEvent 注册为 Spring Bean
        context.register(SpringAnnotationDrivenEvent.class);

        context.refresh(); // 启动上下文
        // 确保上下文启动完毕后,再发送事件
        context.publishEvent(new MyApplicationEvent("Hello,World"));

        context.close(); // 关闭上下文

    }

    private static class MyApplicationEvent extends ApplicationEvent {

        /**
         * Create a new ApplicationEvent.
         *
         * @param source the object on which the event initially occurred (never {@code null})
         */
        public MyApplicationEvent(Object source) {
            super(source);
        }
    }

    @EventListener
    public void onMessage(Object eventSource) {
        System.err.println("监听到 MyApplicationEvent 事件源 : " + eventSource);
    }
}

运行结果如下:
在这里插入图片描述

Spring Boot 事件

@RestController
public class SpringEventController implements ApplicationEventPublisherAware {

    private ApplicationEventPublisher publisher;

    @GetMapping("/send/event")
    public String sendEvent(@RequestParam String message) {
        publisher.publishEvent(message);
        return "Sent";
    }

    @EventListener
    public void onMessage(PayloadApplicationEvent event) {
        System.out.println("接受事件:" + event.getPayload());
    }

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

启动 spring-cloud-client-application 应用后,访问http://localhost:8888/send/event?message=hello
打印结果:
在这里插入图片描述

Spring Cloud 事件

Event Bus(事件总线)

主要内容

Spring cloud bus通过轻量消息代理连接各个分布的节点。这会用在广播状态的变化(例如配置变化)或者其他的消息指令。Spring bus的一个核心思想是通过分布式的启动器对spring boot应用进行扩展,也可以用来建立一个多个应用之间的通信频道。目前唯一实现的方式是用AMQP消息代理作为通道,同样特性的设置(有些取决于通道的设置)在更多通道的文档中。

总体架构

在这里插入图片描述

Git 配置更新 Bus 架构

在这里插入图片描述

Spring Cloud Bus 事件

RemoteApplicationEvent

客户端

前面我们提到的单机版事件监听,这里我们通过远程的形式实现监听器,将事件数据发送 HTTP 请求到目标机器。

public class HttpRemoteAppEventListener implements SmartApplicationListener {

    private RestTemplate restTemplate = new RestTemplate();

    // 得到 DiscoveryClient Bean
    private DiscoveryClient discoveryClient;

    public String currentAppName;

    public void onApplicationEvent(RemoteAppEvent event) {
        Object source = event.getSource();
        String appName = event.getAppName();
        List<ServiceInstance> serviceInstances = discoveryClient.getInstances(appName);

        for (ServiceInstance s : serviceInstances) {

            String rootURL = s.isSecure() ?
                    "https://" + s.getHost() + ":" + s.getPort() :
                    "http://" + s.getHost() + ":" + s.getPort();

            String url = rootURL + "/receive/remote/event/";

            Map<String, Object> data = new HashMap<>();
            data.put("sender", currentAppName);
            data.put("value", source);
            data.put("type", RemoteAppEvent.class.getName());
            // 发送HTTP 请求到
            String responseContent = restTemplate.postForObject(url, data, String.class);

        }
    }

    @Override
    public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
        return RemoteAppEvent.class.isAssignableFrom(eventType)
                || ContextRefreshedEvent.class.isAssignableFrom(eventType);
    }

    @Override
    public boolean supportsSourceType(@Nullable Class<?> sourceType) {
        return true;
    }

    @Override
    public void onApplicationEvent(ApplicationEvent event) {

        if (event instanceof RemoteAppEvent) {
            onApplicationEvent((RemoteAppEvent) event);
        } else if (event instanceof ContextRefreshedEvent) {
            onContextRefreshedEvent((ContextRefreshedEvent) event);
        }
    }

    private void onContextRefreshedEvent(ContextRefreshedEvent event) {
        ApplicationContext applicationContext = event.getApplicationContext();
        this.discoveryClient = applicationContext.getBean(DiscoveryClient.class);
        this.currentAppName = applicationContext.getEnvironment().getProperty("spring.application.name");
    }

    @Override
    public int getOrder() {
        return 0;
    }
}

远程事件:

public class RemoteAppEvent extends ApplicationEvent {

    /**
     * 事件传输类型 HTTP、RPC、MQ
     */
    private String type;

    /**
     * 应用名称
     */
    private final String appName;

    /**
     * 是否广播到集群
     */
    private final boolean isCluster;

    /***
     * @param source POJO 事件源,JSON 格式
     * @param appName
     * @param isCluster
     */
    public RemoteAppEvent(Object source, String appName, boolean isCluster) {
        super(source);
        this.appName = appName;
        this.isCluster = isCluster;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public String getAppName() {
        return appName;
    }

}

编写远程应用事件控制器:

@RestController
public class RemoteAppEventSenderController implements
        ApplicationEventPublisherAware {

    @Value("${spring.application.name}")
    public String currentAppName;

    private ApplicationEventPublisher publisher;

    @Autowired
    private DiscoveryClient discoveryClient;

    @GetMapping("/send/remote/event")
    public String sendEvent(@RequestParam String message) {
        publisher.publishEvent(message);
        return "Sent";
    }

    @PostMapping("/send/remote/event/{appName}")
    public String sendAppCluster(@PathVariable String appName, @RequestBody Object data){
        RemoteAppEvent remoteAppEvent = new RemoteAppEvent(data, appName, true);
        // 发送事件当前上下文
        publisher.publishEvent(remoteAppEvent);
        return "Ok";
    }

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

}

启动类注入Listener:

    public static void main(String[] args) {
        new SpringApplicationBuilder(SpringCloudClientApplication.class)
                .web(WebApplicationType.SERVLET)
                .listeners(new HttpRemoteAppEventListener())
                .run(args);
    }
服务端

编写远程事件接收器 控制器:

/**
 * 远程事件接收器 控制器
 */
@RestController
public class RemoteAppEventReceiverController implements
        ApplicationEventPublisherAware {

    private Logger logger = LoggerFactory.getLogger(getClass());

    private ApplicationEventPublisher publisher;

    @PostMapping("/receive/remote/event/")
    public String receive(@RequestBody Map<String, Object> data) { //  REST 请求不需要具体类型

        // 事件的发送者
        String sender = (String) data.get("sender");
        // 事件的数据内容
        Object value = data.get("value");
        // 事件类型
        String type = (String) data.get("type");

        logger.info("接受到事件");
        // 接受到对象内容,同样也要发送事件到本地,做处理
        publisher.publishEvent(new SenderRemoteAppEvent(sender, value));
        return "received";
    }

    private static class SenderRemoteAppEvent extends ApplicationEvent {

        private final String sender;

        private SenderRemoteAppEvent(String sender, Object value) {
            super(value);
            this.sender = sender;
        }

        public String getSender() {
            return sender;
        }
    }

    @EventListener
    public void onEvent(SenderRemoteAppEvent event) {
        logger.info("接受到事件源:" + event.getClass().getSimpleName() + " , 来自应用 : " + event.getSender());
    }

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

整体架构:
在这里插入图片描述

接下来我们调用客户端远程接口,发送post方式的接口:

curl -X POST http://localhost:8888/send/remote/event/spring-cloud-server-application -d ‘{“name”:“zhangsan”}’ -H “content-type:application/json;charset=UTF-8”

在这里插入图片描述
当然,你也可以用postman工具请求,最终控制台打印效果:

在这里插入图片描述

我们发现接收到事件以后,在发送事件到本地的过程中,控制台为同一个线程处理,即同步执行。

现在我们通过多线程的方式,去处理发送事件的逻辑:

  • 启动类引入@EnableAsync注解

在这里插入图片描述

  • 在处理多线程的入口加入@Async标示为开启多线程
 @EventListener
    @Async
    public void onEvent(SenderRemoteAppEvent event) {
        logger.info("接受到事件源:" + event.getClass().getSimpleName() + " , 来自应用 : " + event.getSender());
    }

最终控制台打印效果::
在这里插入图片描述
我们发现[nio-9090-exec-3] 、[ask-scheduler-1] 两个线程去分别处理业务逻辑,达到了异步的效果。

后记

通过 Spring Cloud Bus 的实现事件编程模型(数据源转换)的逻辑如下:
在这里插入图片描述
通过 Spring 事件/监听者编程模型,屏蔽了事件源传输的细节,可能是 MQ、RPC 或 HTTP 等。

本节代码地址:Spring Cloud Bus

更多架构知识,欢迎关注本套Java系列文章Java架构师成长之路

;