【SpringCloud】13. 消息总线

330 阅读6分钟

BUS组件

1. 说明

Spring Cloud Bus使用轻量级消息代理将分布式系统的节点连接起来。然后,可以使用它来广播状态更改(例如配置更改)或其他管理指令。AMQPRabbitMQ)和Kafka broker实现包含在项目中。或者,在类路径上找到的任何Spring Cloud Stream绑定器都可以作为传输使用。

2. 定义

Bus之所以被称为Spring Cloud中消息总线,主要用来在微服务系统中实现远端配置更新时通过广播形式通知所有客户端刷新配置信息,避免手动重启服务的工作。

配置半自动刷新

1. 搭建RabbitMQ服务

  1. 下载Erlang:www.erlang.org/downloads

  2. 下载RabbitMQ:github.com/rabbitmq/ra…

  3. 准备一个CentOS7的虚拟机。

  4. 搭建FTP将两个安装包上传至虚拟机。

  5. 安装Erlang

    rpm -ivh erlang-21.3.8.6-1.el7.x86_64-2.rpm
    
  6. 安装RabbitMQ

    yum install -y rabbitmq-server-3.7.18-1.el7.noarch.rpm
    
  7. RabbitMQ安装完成后将/usr/share/doc/rabbitmq-server-3.7.18/rabbitmq.config.example配置文件复制到/etc/rabbitmq/目录下,并命名为rabbitmq.config

    cp /usr/share/doc/rabbitmq-server-3.7.18/rabbitmq.config.example /etc/rabbitmq/rabbitmq.config
    
  8. 修改rabbitmq.config配置

    vim /etc/rabbitmq/rabbitmq.config
    

  9. 执行插件管理服务

    rabbitmq-plugins enable rabbitmq_management
    
  10. 启动RabbitMQ

    # 启动RabbitMQ
    systemctl start rabbitmq-server
    # 重启RabbitMQ
    systemctl restart rabbitmq-server
    # 停止RabbitMQ
    systemctl stop rabbitmq-server
    
  11. 查看RabbitMQ状态

    systemctl status rabbitmq-server
    
  12. 访问RabbitMQ

    http://虚拟机ip:15672/
    
  13. 登录RabbitMQ,账号密码都是guest

注意:

  1. RabbitMQ的运行需要Erlang的环境,RabbitMQ和Erlang的版本必须严格适配。

  2. 安装RabbitMQ时需要联网,因为RabbitMQ的安装过程中,yum需要联网去下载其他的依赖。

  3. 在修改rabbitmq.config如果找不到修改位置可以输入":61"会直接定位到61行进行修改,修改内容是去掉前面两个%和最后一个逗号

  4. 如果http://虚拟机ip:15672/访问不到RabbitMQ页面,那么可以开放虚拟机端口,或者关闭虚拟机防火墙。

    开放虚拟机端口(推荐):

    # 开放虚拟机端口
    sudo firewall-cmd --permanent --add-port=15672/tcp
    sudo firewall-cmd --permanent --add-port=5672/tcp
    # 关闭虚拟机端口
    sudo firewall-cmd --permanent --remove-port=15672/tcp
    sudo firewall-cmd --permanent --remove-port=5672/tcp
    # 开放和关闭命令输入后,都需要重启防火墙
    firewall-cmd --reload
    

    关闭防火墙:

    systemctl disable firewalld
    systemctl stop firewalld
    # 查看防火墙状态
    systemctl status firewalld
    

2. 实现配置半自动刷新

本案例是在手动刷新的基础上实现自动刷新,所以手动刷新的配置保持不变。

  1. Config配置中心引入依赖

    <dependency>
       <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-bus-amqp</artifactId>
    </dependency>
    
  2. Config配置中心配置RabbitMQ

    spring:
      application:
        name: config
      cloud:
        # consul配置
        consul:
          ...
        # config配置
        config:
          ...
      # rabbitmq配置(新加)
      rabbitmq:
        # rabbitmq所在服务器的ip
        host: 172.20.131.194
        # rabbitmq所在服务器的port(tcp)
        port: 5672
        # rabbitmq用户名和密码
        username: guest
        password: guest
    
  3. Config客户端引入依赖

    <dependency>
       <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-bus-amqp</artifactId>
    </dependency>
    
  4. 去远程仓库中找到Config客户端对应的公共配置文件添加

     spring:
       ...
       rabbitmq:
          host: 172.20.131.194
          port: 5672
          username: guest
          password: guest
    
  5. Config客户端bootstrap.yml添加配置

    spring:
      cloud:
        config:
          ...
          # 让消息总线连接消息服务器出现问题的时候,微服务也能正常运行
          fail-fast: true
    
  6. Config配置中心开启所有web端点(其中包括配置文件远程刷新的端点)

    management:
      endpoints:
        web:
          exposure:
            include: "*"
    
  7. 向配置服务中心发一个POST请求,让服务中心发送消息给RabbitMQ,RabbitMQ通知所有服务器刷新配置文件。

    curl -X POST http://服务中心ip:服务中心port/actuator/bus-refresh
    

注意:

  1. RabbitMQ的Web端口为15672,TCP端口为5672。
  2. 在RabbitMQ的Web中,可以自己创建用户。本文就用它自带的guest账户。

3. 指定配置的半自动刷新

默认情况下使用

curl -X POST http://服务中心ip:服务中心port/actuator/bus-refresh

这种方式刷新配置是广播形式,配置中心会通知RabbitMQ,让RabbitMQ广播给所有远程仓库的配置文件进行刷新。但有时我们修改的仅仅是某个服务的配置,这个时候对于其他服务的通知是多余的,因此就需要指定服务进行通知。

  • 指定某个服务集群中所有节点进行配置文件的自动刷新

    curl -X POST http://服务中心ip:服务中心port/actuator/bus-refresh/服务名
    
  • 指定某个服务集群中的某个节点进行配置文件的自动刷新

    curl -X POST http://服务中心ip:服务中心port/actuator/bus-refresh/服务名:节点port
    

配置全自动刷新

在半自动刷新中,我们在远程仓库修改了配置文件之后,还是需要手动给配置中心发送一个POST请求,让其通知RabbitMQ进行修改。在使用Github提供的webhock功能实现配置全自动刷新后,配置文件修改完就会自动刷新到相应服务,而不需要我们手动发送请求。

webhock的原理就类似于监听器,当我们修改提交了配置文件之后,github自动帮助我们给配置中心发送POST请求。

1. 配置webhock

  1. 进入远程仓库

  2. 添加webhock

  3. 填写webhock配置

2. NATAPP实现内网穿透

因为NATAPP中一个注册用户可免费拥有2条不同协议的隧道,所以本文使用NATAPP来实现内网穿透。

  1. 登录NATAPP官网,注册,登录。

  2. 购买免费隧道

  3. 获取authtoken

  4. 下载客户端

  5. 在NATAPP执行文件的目录下打开cmd。

  6. 启动NATAPP,在cmd中输入:natapp -authtoken=56f71db69ea4be2a

  7. 获取内网穿透之后的公网地址

  8. 将webhock配置中需要的payload URL填上公网地址

  9. 在Config配置中心创建config包。

  10. 创建Filter过滤webhock发送POST的请求中的其他多余信息

    import org.springframework.stereotype.Component;
    
    import javax.servlet.*;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletRequestWrapper;
    import javax.servlet.http.HttpServletResponse;
    import java.io.BufferedReader;
    import java.io.ByteArrayInputStream;
    import java.io.IOException;
    
    @Component
    public class UrlFilter implements Filter {
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
     
        }
     
        @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
            HttpServletRequest httpServletRequest = (HttpServletRequest)request;
            HttpServletResponse httpServletResponse = (HttpServletResponse)response;
     
            String url = new String(httpServletRequest.getRequestURI());
     
            //只过滤/actuator/bus-refresh请求
            if (!url.endsWith("/bus-refresh")) {
                chain.doFilter(request, response);
                return;
            }
     
            //获取原始的body
            String body = readAsChars(httpServletRequest);
     
            System.out.println("original body:   "+ body);
     
            //使用HttpServletRequest包装原始请求达到修改post请求中body内容的目的
            CustometRequestWrapper requestWrapper = new CustometRequestWrapper(httpServletRequest);
     
            chain.doFilter(requestWrapper, response);
        }
     
        @Override
        public void destroy() {
     
        }
     
        private class CustometRequestWrapper extends HttpServletRequestWrapper {
            public CustometRequestWrapper(HttpServletRequest request) {
                super(request);
            }
     
            @Override
            public ServletInputStream getInputStream() throws IOException {
                byte[] bytes = new byte[0];
                ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
     
                return new ServletInputStream() {
                    @Override
                    public boolean isFinished() {
                        return byteArrayInputStream.read() == -1 ? true:false;
                    }
     
                    @Override
                    public boolean isReady() {
                        return false;
                    }
     
                    @Override
                    public void setReadListener(ReadListener readListener) {
     
                    }
     
                    @Override
                    public int read() throws IOException {
                        return byteArrayInputStream.read();
                    }
                };
            }
        }
     
        public static String readAsChars(HttpServletRequest request) {
     
            BufferedReader br = null;
            StringBuilder sb = new StringBuilder("");
            try {
                br = request.getReader();
                String str;
                while ((str = br.readLine()) != null) {
                    sb.append(str);
                }
                br.close();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (null != br) {
                    try {
                        br.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
            return sb.toString();
        }
    }
    

注意:

  1. 配置webhock中的payload URL必须是公网IP,要不然就把配置中心部署到云服务器上,要不然就做内网穿透。

  2. 如果不创建Filter,那么github发送Post请求给配置中心时会报400错误。这是因为Spring Boot无法正常反序列化造成,我们需要写一个过滤器将Body直接返回为空,就可以达到过滤body的效果。