通过SpringCloudGateway转发WebSocket实现消息推送⼀、总体⽅案流程
先上总体流程图吧,(我没有⽤visio来画图,⽽是⽤了⽹上的⼀个⼯具Process On,所以图看上去不是那么正规。不⽤visio主要还是因为visio⽤的太菜了,有机会还是要多练习⼀下使⽤visio画图)。
主要起了四个⼯程,
1. ⼀个是模拟⽤户服务的⼯程user-service(模拟两个服务就通过两个不同的端⼝来模拟);
2. ⼀个是Spring Cloud Gateway⽹关⼯程;
3. ⼀个是dispatch,⽤来做消息的分发调度;
4. 还有⼀个⼯程就是模拟消息的⽣产者;
5. 服务发现使⽤的是Consul。
接着对照上⾯的图说⼀下整体流程和思路。⽤户通过⽹关登录到服务器上,服务器A和服务器B模拟的是⼀个微服务的两台服务器,⽹关可能会让⽤户连到ServerA上,也可能会连到ServerB上。并且在⽤户登录的同时会连接WebSocket,WebSocket是消息推送能否实现的关键,为什么要使⽤WebSocket下⾯再说。⽤户登录并连接到服务器上以后,将⽤户的登录名和服务器信息写⼊Redis中
(Key:userName,Value:serverIpAdress+port)。为什么要记录服务器信息呢?因为每台服务器都会订阅⼀个以⾃⼰ip地址加端⼝号命名的topic,如下图所⽰:
当消息的⽣产者有⼀条消息要推送时,会先发给Dispatch(这⾥的流程跟上图有些不⼀样,按理说应该要先存⼊⼀个队列中,然后让Dispatch⾃⼰主动去取然后再推送给指定的消息队列。为什么要这样做呢?按照架构师他的话来说,就是要在Dispatch这⾥做持久化等等⼀系列操作,真正的⼯程是很复杂的,消息推送不可能像我上⾯这个图⼀样简单)。但是因为⼀些原因我并没有这样做,⽽是直接让⽣产者将消息推给了Dispatch。Dispatch收到消息后,会在Redis中去查⽤户所连接的服务器,然后将消息推送到该服务器监听的消息队列中,服务器最后通过WebSocket将消息推给⽤户。
2、具体实现
2.1、Spring Boot集成WebSocket
2.1.1、WebSocket简介
⾸先抛出⼏个问题:
Q1:什么是WebSocket?
A1:WebSocket是Html5提供的⼀种能在单个TCP连接上进⾏全双⼯通讯的协议,是HTTP协议中长连接的升级版。全双⼯通讯就是你能主动给我发消息,我也能主动给你发消息。
Q2:为什么要使⽤WebSocket?
A2:使⽤WebSocket最⼤的好处就是客户端能主动向服务端发送消息,服务端也能主动向客户端发送消息,能最⼤程度的保证信息交换的实时性。Web应⽤的信息交互过程通常是客户端通过浏览器发出⼀个请求,服务器端接收和审核完请求后进⾏处理并返回结果给客户端,然后客户端浏览器将信息呈现出来,这种机制对于信息变化不是特别频繁的应⽤尚能应付,但是对于那些实时要求⽐较⾼的应⽤来说,⽐如说在线游戏、股票查询,在线证券、设备监控、新闻在线播报、RSS订阅推送等等,当客户端浏览器准备呈现这些信息的时候,这些信息在服务器端可能已经过时了。所以保持客户端和服务器端的信息同步是实时Web 应⽤的关键要素。
在WebSocket出现之前,客户端是怎么和服务端进⾏信息交换的呢?最常⽤的⽅式就是轮询,⽽轮询⼜分为短轮询和长轮询。
短轮询就相当于客户端每隔⼀定时间就主动发送⼀个request给服务端请求数据交换,服务端不管有没有新的数据都要对客户端的请求进⾏响应。短轮询⼀般都是客户端使⽤ajax来实现的,⽤户⼀般是不会感觉到客户端发⽣的变化的,但是这种短轮询的⽅式对服务端来说会有不⼩的压⼒。因为每次客户端和服务端之间信息的交换都对应着⼀次Request-Response的过程,每发⽣⼀次这个过程,双⽅都要交换⼤量的请求头和响应头,这增加了每次传输的数据量;并且轮询的时间间隔⾮常难控制,时间短了,容易造成服务器CPU资源的浪费,⽽时间长了⼜难以保证信息的实时性。
长轮询则相当于客户端给服务端打⼀个电话,如果服务端有数据更新,那么它就接起这个电话(响应),将数据返回给客户端;如果服务端没有数据更新,那么这个电话就⼀直挂在那(不响应),直到有数据更新才接起电话。长轮询的缺点很明显,服务器的并发连接数是有限的,如果服务端⼀直没有更新数据⽽⼀直保持这个连接,那么这个空闲的连接就是⼀种资源浪费。
WebSocket相对于轮询的优势很明显,WebSocket只需要建⽴⼀次连接,客户端和服务端只需要交换⼀次请求头和响应头就可以⽆数次交换信息。
2.1.2、相关代码
Spring Boot集成WebSocket的maven依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
WebSocket配置⽂件WebSocketAutoConfig:
/**
* websocket配置
*/
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketAutoConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/mq")        //开启/bullet端点
.setAllowedOrigins("*")        //允许跨域访问
.withSockJS();                  //使⽤sockJS
}
}
前端页⾯,前端通过SockJS来进⾏WebSocket连接(其中⽐较关键的代码我都写了注释,就不再⼀⼀解释了):
<!DOCTYPE html>
<!DOCTYPE html>
<html xmlns:text-align="java.sun/JSP/Page">
<head>
<meta charset="UTF-8" />
<title>h3</title>
<noscript>
<h2 >貌似你的浏览器不⽀持websocket</h2>
</noscript>
<script src="/user-service/static/sockjs.js"></script>
<script src="/user-service/static/stomp.js"></script>
<script src="/user-service/static/jquery.js"></script>
<script src="/user-service/static/bootstrap-3.3.7-dist/js/bootstrap.min.js"></script>
<script src="/user-service/static/bootstrap-3.3.7-dist/css/bootstrap.min.css"></script>    <script type="text/javascript">
function loginin() {
$.ajax({
type:"get",
data:"username=" + ElementById("username1").value,
url:"login"
})
connect();
}
var stompClient = null;
//gateway⽹关的地址
var host="localhost:4445/user-service";
function setConnected(connected) {
$('#response').html();
}
function connect() {
/
/地址+端点路径,构建websocket链接地址
var socket = new SockJS(host+'/mq');
var username = ElementById("username1").value;
stompClient = Stomp.over(socket);
setConnected(true);
console.log('Connected:' + frame);
//监听⼀个具有⾃⼰信息的对列(/toAll/id)
//把⽤户名作为⾃⼰监听的消息队列标识
stompClient.subscribe('/toAll/' + username, function(response) {
showResponse(response.body);
});
});
}
function disconnect() {
if (stompClient != null) {
stompClient.disconnect();
}
setConnected(false);
console.log("Disconnected");
}
function send() {
var name = $('#name').val();
var message = $('#messgae').val();
//发送消息的路径
stompClient.send("/chat", {}, JSON.stringify({username:name,message:message}));    }
function showResponse(message) {
var response = $('#response');
response.html(message);
}
</script>
</head>
<body onload="disconnect()">
<div class="input-group" id="login" >
<span class="input-group-addon" id="basic-addon1">⽤户名</span>
<input type="text" class="form-control" placeholder="请输⼊⽤户名" aria-describedby="basic-addon1" id="username1"
name="username" >
</div>
<div   class="btn-group" role="group" aria-label="...">
<button type="button" class="btn btn-default" id="loginbutton" onclick="loginin()">登录</button>
<button type="button" class="btn btn-default" id="disconnect" onclick="disconnect();">断开连接</button>
</div>
<div >
<h1 id="response"></h1>
</div>
</body>
</html>
使⽤WebSocket给单个⽤户发送信息:
/**
* ⽤户在前端订阅的消息队列名称为"/toAll/username"
* 通过teleWebSocketManager发送信息给指定⽤户
为什么使用bootstrap?*/
@ResponseBody
@RequestMapping(value="/sendToOne", produces = {"application/json; charset=utf-8"},method= RequestMethod.GET)
public String sendToOne(String username, String message){
teleWebSocketManager.send("/toAll/" + username, message);
return "成功";
}
2.2、Spring Boot集成Spring Cloud Gateway
2.2.1、Spring Cloud Gateway简介
Spring Cloud Gateway是Spring Cloud下的⼀个全新项⽬,它创建了⼀个在Spring控制下的API⽹关,它的⽬的是提供⼀个简单、⾼效的api路由⽅式。
Spring Cloud Gateway的特点:
使⽤gateway时,Spring版本应该不低于5.0,Spring boot版本不低于2.0;
能够在任何请求属性中匹配路由;
能够为每个路由单独制定断⾔和过滤器;
能够很容易的配置断⾔和过滤器;
⽀持路径重写;
以上是从Spring官⽹翻译过来的,英语⽔平有限,⼤家可以⾃⼰去官⽹看⼀下。
2.2.2、Spring Cloud Gateway配置相关代码
⾸先是Spring Cloud Gateway的相关依赖:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Finchley.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
<version>2.0.1.RELEASE</version>
<exclusions>
<exclusion>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-gateway-core</artifactId>
</exclusion>
</exclusions>
</dependency>
Spring Cloud Gateway配置路由有两种⽅式,⼀种是在代码中进⾏配置,还有⼀种是在配置⽂件中进⾏配置。在代码中进⾏配置扩展性较差,⼀般都不推荐;使⽤配置⽂件进⾏配置是⽐较好的⼀种⽅式,路由配置⽂件:
server:
port: 4445
spring:
application:
name: gateway-service
profiles:
active: dev
cloud:
consul:
host: localhost
port: 8500
# 服务发现配置
discovery:
serviceName: gateway-service
#路由配置,user-service是我的服务名
gateway:
routes:
#表⽰websocket的转发
- id: user-service-websocket
uri: lb:ws://user-service
predicates:
- Path=/user-service/mq/**
- id: user-service
uri: lb://user-service
predicates:
- Path=/user-service/**
discovery:
locator:
enabled: true
lower-case-service-id: true

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。