【分布式WebSocket - 4】SpringBoot集成STOMP协议,RabbitMQ为消息代理

一、为什么引入RabbitMQ为消息代理?

   spring基于内存为代理时,扩容不太方便,一旦服务宕机,内存的消息将全部丢失。因此需要有方便扩容的解决方案。

方案1

image-20210611170958446

  1. 通过负载策略,用户1、2连接推送服务A。用户3、4连接服务器B。redis存储每个用户和服务器的对应关系,当然这里也不一定是redis,zookeeper也可以。
  2. 业务系统在发送消息时,去redis查看用户在哪个服务器上,然后通过RPC调用对应服务器的接口进行发现消息,这个逻辑需要自行实现。

方案2

image-20210611171319445

  1. 通过负载策略,用户1、2连接推送服务A。用户3、4连接服务器B。他们分别自己订阅自己想要了解的主题。
  2. 业务服务器想要推送消息时,不管是群里还是资料,可以连接任意服务器,只要推送给STOMP代理即可。STOMP代理会自行推送消息。
  3. 这样如果推送服务压力大时,可以任意扩容。

因此,在一些需要快速开发的项目中,后端使用了springboot,而且使用了STOMP协议,那么这个方案是一个不错的选择。

二、为什么使用RabbitMQ?

        支持STOMP协议的中间件还有activemq,但是rabbitmq相对来说更加强大,使用的人更多。

三、架构图

message flow broker relay

        这个架构图和内存的broker类似,区别在于,左下角有一个专门存储消息的容器。这个容器就是RabbitMQ。当然,由这个架构图看来,在原有的功能上配置RabbitMQ只需要修改部分代码即可。

四、安装RabbitMQ

        网上安装RabbitMQ的资料很多,这里我就不在陈述。如果安装Windows版本的RabbitMQ可以参考。我在测试时,用的是相对新一点的版本。

安装成功之后启用插件,注:这是两个命令,一个一个执行。

rabbitmq-plugins enable rabbitmq_stomp
rabbitmq-plugins enable rabbitmq_web_stomp

正常的RabbitMQ启用stomp协议之后,看下图

image-20210611155243787

五、代码配置RabbitMQ

5.1、服务器端

pom文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>websocket-demo</artifactId>
    <version>1.0-SNAPSHOT</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.10.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.12</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>




        <dependency>
            <groupId>org.webjars</groupId>
            <artifactId>webjars-locator-core</artifactId>
        </dependency>
        <dependency>
            <groupId>org.webjars</groupId>
            <artifactId>sockjs-client</artifactId>
            <version>1.0.2</version>
        </dependency>
        <dependency>
            <groupId>org.webjars</groupId>
            <artifactId>stomp-websocket</artifactId>
            <version>2.3.3</version>
        </dependency>
        <dependency>
            <groupId>org.webjars</groupId>
            <artifactId>bootstrap</artifactId>
            <version>3.3.7</version>
        </dependency>
        <dependency>
            <groupId>org.webjars</groupId>
            <artifactId>jquery</artifactId>
            <version>3.1.0</version>
        </dependency>


        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.62</version>
        </dependency>
        <dependency>
            <groupId>io.projectreactor</groupId>
            <artifactId>reactor-core</artifactId>
            <version>3.2.9.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>4.1.42.Final</version>
        </dependency>
        <dependency>
            <groupId>io.projectreactor.netty</groupId>
            <artifactId>reactor-netty</artifactId>
            <version>0.8.11.RELEASE</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

配置类WebSocketConfig.java

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer
{
   // 启用一个简单的基于内存的消息代理
   @Override
   public void configureMessageBroker(MessageBrokerRegistry config) {
      //通过/topic 开头的主题可以进行订阅
      config.enableSimpleBroker("/topic");
      //send命令时需要带上/app前缀
      config.setApplicationDestinationPrefixes("/app");
      //配置RabbitMQ代理
          // 配置支持的topic
      config.enableStompBrokerRelay("/topic/","/queue/","exchange")
            .setRelayHost("localhost")   //地址
            .setRelayPort(61613)     //端口
            .setClientLogin("guest")  // 账号密码
            .setClientPasscode("guest")
            .setVirtualHost("/");
 
   }
   @Override
   public void registerStompEndpoints(StompEndpointRegistry registry) {
      //连接前缀
      registry.addEndpoint("/gs-guide-websocket")
            .setAllowedOrigins("*")  // 跨域处理
            .withSockJS();  //支持socketJs
   }
}

控制器类

@Slf4j
@RestController
public class TestController
{
   @Autowired
   private SimpMessagingTemplate simpMessagingTemplate;
   @GetMapping ("/queue")
   public void queue(HelloMessage message) throws Exception {
      simpMessagingTemplate.convertAndSend ("/queue/user"+message.getUserId (),
            new Greeting("Hello, " + HtmlUtils.htmlEscape(message.getName()) + "!"));
   }
   @GetMapping ("/topic")
   public void topic(HelloMessage message) throws Exception {
      simpMessagingTemplate.convertAndSend ("/topic/user"+message.getUserId (),
            new Greeting("Hello, " + HtmlUtils.htmlEscape(message.getName()) + "!"));
   }
}

5.2、H5端

var stompClient = null;
var userId = null;
function setConnected(connected) {
    $("#connect").prop("disabled", connected);
    $("#disconnect").prop("disabled", !connected);
    if (connected) {
        $("#conversation").show();
    }
    else {
        $("#conversation").hide();
    }
    $("#greetings").html("");
}

function connect() {
    var socket = new SockJS('/gs-guide-websocket');
    stompClient = Stomp.over(socket);
    stompClient.connect({}, function (frame) {
        setConnected(true);
        console.log('Connected: ' + frame);
           stompClient.subscribe('/queue/user'+userId, function (greeting) {
                  showGreeting(JSON.parse(greeting.body).content);
           });
         stompClient.subscribe('/topic/user'+userId, function (greeting) {
                     showGreeting(JSON.parse(greeting.body).content);
              });
    });
}

function disconnect() {
    if (stompClient !== null) {
        stompClient.disconnect();
    }
    setConnected(false);
    console.log("Disconnected");
}
function showGreeting(message) {
    $("#greetings").append("<tr><td>" + message + "</td></tr>");
}
function GetQueryString(name) {
   var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)", "i");
   var r = window.location.search.substr(1).match(reg); //获取url中"?"符后的字符串并正则匹配
   var context = "";
   if (r != null)
      context = r[2];
   reg = null;
   r = null;
   return context == null || context == "" || context == "undefined" ? "" : context;
}
$(function () {
    userId =  GetQueryString("userId");
    $("form").on('submit', function (e) {
        e.preventDefault();
    });
    $( "#connect" ).click(function() { connect(); });
    $( "#disconnect" ).click(function() { disconnect(); });
    

});

html

<!DOCTYPE html>
<html>
<head>
    <title>Hello WebSocket</title>
    <link href="/webjars/bootstrap/css/bootstrap.min.css" rel="stylesheet">
    <link href="/main.css" rel="stylesheet">
    <script type="text/javascript" src="https://cdn.bootcss.com/jquery/3.2.1/jquery.min.js"></script>

    <script src="/webjars/sockjs-client/sockjs.min.js"></script>
    <script src="/webjars/stomp-websocket/stomp.min.js"></script>
    <script src="/app.js"></script>
</head>
<body>
<noscript><h2 style="color: #ff0000">Seems your browser doesn't support Javascript! Websocket relies on Javascript being
    enabled. Please enable
    Javascript and reload this page!</h2></noscript>
<div id="main-content" class="container">
    <div class="row">
        <div class="col-md-6">
            <form class="form-inline">
                <div class="form-group">
                    <label for="connect">WebSocket connection:</label>
                    <button id="connect" class="btn btn-default" type="submit">Connect</button>
                    <button id="disconnect" class="btn btn-default" type="submit" disabled="disabled">Disconnect
                    </button>
                </div>
            </form>
        </div>
        <div class="col-md-6">
<!--             -->
        </div>
    </div>
    <div class="row">
        <div class="col-md-12">
            <table id="conversation" class="table table-striped">
                <thead>
                <tr>
                    <th>Greetings</th>
                </tr>
                </thead>
                <tbody id="greetings">
                </tbody>
            </table>
        </div>
    </div>
</div>
</body>
</html>

浏览器进入http://localhost:8080/?userId=1,点击connect进行连接。然后调用controller的queue方法,向用户1发送信息,http://localhost:8080/queue?userId=1&name=ni'hao发送信息。

image-20210611164749101

RabbitMQ中队列信息如下,如此,RabbitMQ代理配置完成。

image-20210611164919830

  • 13
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 16
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

叁滴水

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值