2024年最新悟了悟了,WebSocket 集群解决方案原来是这样的(1),2024年最新面试题及参考答案

❤️ 谢谢支持

喜欢的话别忘了 关注、点赞哦~。

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

前端校招面试题精编解析大全

org.springframework.boot

spring-boot-starter-websocket

第二步:添加配置类

@Configuration

public class WebSocketConfig implements WebSocketConfigurer {

@Override

public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {

registry.addHandler(myHandler(), “/”)

.setAllowedOrigins(“*”);

}

@Bean

public WebSocketHandler myHandler() {

return new MessageHandler();

}

}

第三步:实现消息监听类

@Component

@SuppressWarnings(“unchecked”)

public class MessageHandler extends TextWebSocketHandler {

private List clients = new ArrayList<>();

@Override

public void afterConnectionEstablished(WebSocketSession session) {

clients.add(session);

System.out.println(“uri :” + session.getUri());

System.out.println("连接建立: " + session.getId());

System.out.println("current seesion: " + clients.size());

}

@Override

public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {

clients.remove(session);

System.out.println("断开连接: " + session.getId());

}

@Override

protected void handleTextMessage(WebSocketSession session, TextMessage message) {

String payload = message.getPayload();

Map<String, String> map = JSONObject.parseObject(payload, HashMap.class);

System.out.println(“接受到的数据” + map);

clients.forEach(s -> {

try {

System.out.println("发送消息给: " + session.getId());

s.sendMessage(new TextMessage(“服务器返回收到的信息,” + payload));

} catch (Exception e) {

e.printStackTrace();

}

});

}

}

从这个demo中,使用spring websocket实现ws服务的便利性大家可想而知了。为了能更好地向spring cloud大家族看齐,我最终采用了spring websocket实现ws服务。

因此我的应用服务架构是这样子的:一个应用既负责restful服务,也负责ws服务。没有将ws服务模块拆分是因为拆分出去要使用feign来进行服务调用。第一本人比较懒惰,第二拆分与不拆分相差在多了一层服务间的io调用,所以就没有这么做了。

从zuul技术转型到spring cloud gateway

==============================

要实现websocket集群,我们必不可免地得从zuul转型到spring cloud gateway。原因如下:

zuul1.0版本不支持websocket转发,zuul 2.0开始支持websocket,zuul2.0几个月前开源了,但是2.0版本没有被spring boot集成,而且文档不健全。因此转型是必须的,同时转型也很容易实现。

在gateway中,为了实现ssl认证和动态路由负载均衡,yml文件中以下的某些配置是必须的,在这里提前避免大家采坑

server:

port: 443

ssl:

enabled: true

key-store: classpath:xxx.jks

key-store-password: xxxx

key-store-type: JKS

key-alias: alias

spring:

application:

name: api-gateway

cloud:

gateway:

httpclient:

ssl:

handshake-timeout-millis: 10000

close-notify-flush-timeout-millis: 3000

close-notify-read-timeout-millis: 0

useInsecureTrustManager: true

discovery:

locator:

enabled: true

lower-case-service-id: true

routes:

  • id: dc

uri: lb://dc

predicates:

  • Path=/dc/**

  • id: wecheck

uri: lb://wecheck

predicates:

  • Path=/wecheck/**

如果要愉快地玩https卸载,我们还需要配置一个filter,否则请求网关时会出现错误not an SSL/TLS record

@Component

public class HttpsToHttpFilter implements GlobalFilter, Ordered {

private static final int HTTPS_TO_HTTP_FILTER_ORDER = 10099;

@Override

public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {

URI originalUri = exchange.getRequest().getURI();

ServerHttpRequest request = exchange.getRequest();

ServerHttpRequest.Builder mutate = request.mutate();

String forwardedUri = request.getURI().toString();

if (forwardedUri != null && forwardedUri.startsWith(“https”)) {

try {

URI mutatedUri = new URI(“http”,

originalUri.getUserInfo(),

originalUri.getHost(),

originalUri.getPort(),

originalUri.getPath(),

originalUri.getQuery(),

originalUri.getFragment());

mutate.uri(mutatedUri);

} catch (Exception e) {

throw new IllegalStateException(e.getMessage(), e);

}

}

ServerHttpRequest build = mutate.build();

ServerWebExchange webExchange = exchange.mutate().request(build).build();

return chain.filter(webExchange);

}

@Override

public int getOrder() {

return HTTPS_TO_HTTP_FILTER_ORDER;

}

}

这样子我们就可以使用gateway来卸载https请求了,到目前为止,我们的基本框架已经搭建完毕,网关既可以转发https请求,也可以转发wss请求。接下来就是用户多对多之间session互通的通讯解决方案了。接下来,我将根据方案的优雅性,从最不优雅的方案开始讲起。

session广播

=========

这是最简单的websocket集群通讯解决方案。场景如下:

教师A想要群发消息给他的学生们

  • 教师的消息请求发给网关,内容包含{我是教师A,我想把xxx消息发送我的学生们}

  • 网关接收到消息,获取集群所有ip地址,逐个调用教师的请求

  • 集群中的每台服务器获取请求,根据教师A的信息查找本地有没有与学生关联的session,有则调用sendMessage方法,没有则忽略请求

WebSocket 集群解决方案

session广播实现很简单,但是有一个致命缺陷:计算力浪费现象,当服务器没有消息接收者session的时候,相当于浪费了一次循环遍历的计算力,该方案在并发需求不高的情况下可以优先考虑,实现很容易。

spring cloud中获取服务集群中每台服务器信息的方法如下

@Resource

private EurekaClient eurekaClient;

Application app = eurekaClient.getApplication(“service-name”);

//instanceInfo包括了一台服务器ip,port等消息

InstanceInfo instanceInfo = app.getInstances().get(0);

System.out.println("ip address: " + instanceInfo.getIPAddr());

服务器需要维护关系映射表,将用户的id与session做映射,session建立时在映射表中添加映射关系,session断开后要删除映射表内关联关系

一致性哈希算法实现(本文的要点)

================

这种方法是本人认为最优雅的实现方案,理解这种方案需要一定的时间,如果你耐心看下去,相信你一定会有所收获。再强调一次,不了解一致性哈希算法的同学请先看这里,现先假设哈希环是顺时针查找的。

首先,想要将一致性哈希算法的思想应用到我们的websocket集群,我们需要解决以下新问题:

  • 集群节点DOWN,会影响到哈希环映射到状态是DOWN的节点。

  • 集群节点UP,会影响到旧key映射不到对应的节点。

  • 哈希环读写共享。

在集群中,总会出现服务UP/DOWN的问题。

针对节点DOWN的问题分析如下:

一个服务器DOWN的时候,其拥有的websocket session会自动关闭连接,并且前端会收到通知。此时会影响到哈希环的映射错误。我们只需要当监听到服务器DOWN的时候,删除哈希环上面对应的实际结点和虚结点,避免让网关转发到状态是DOWN的服务器上。

实现方法:在eureka治理中心监听集群服务DOWN事件,并及时更新哈希环。

针对节点UP的问题分析如下:

现假设集群中有服务 CacheB上线了,该服务器的ip地址刚好被映射到key1和 cacheA之间。那么key1对应的用户每次要发消息时都跑去 CacheB发送消息,结果明显是发送不了消息,因为 CacheB没有key1对应的session。

WebSocket 集群解决方案

此时我们有两种解决方案。

方案A简单,动作大:

eureka监听到节点UP事件之后,根据现有集群信息,更新哈希环。并且断开所有session连接,让客户端重新连接,此时客户端会连接到更新后的哈希环节点,以此避免消息无法送达的情况。

方案B复杂,动作小:

我们先看看没有虚拟节点的情况,假设 CacheC和 CacheA之间上线了服务器 CacheB。所有映射在 CacheC到 CacheB的用户发消息时都会去 CacheB里面找session发消息。也就是说 CacheB一但上线,便会影响到 CacheC到 CacheB之间的用户发送消息。所以我们只需要将 CacheA断开 CacheC到 CacheB的用户所对应的session,让客户端重连。

WebSocket 集群解决方案
总结:

  • 函数式编程其实是一种编程思想,它追求更细的粒度,将应用拆分成一组组极小的单元函数,组合调用操作数据流;

  • 它提倡着 纯函数 / 函数复合 / 数据不可变, 谨慎对待函数内的 状态共享 / 依赖外部 / 副作用;

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

Tips:

其实我们很难也不需要在面试过程中去完美地阐述出整套思想,这里也只是浅尝辄止,一些个人理解而已。博主也是初级小菜鸟,停留在表面而已,只求对大家能有所帮助,轻喷🤣;

我个人觉得: 这些编程范式之间,其实并不矛盾,各有各的 优劣势

理解和学习它们的理念与优势,合理地 设计融合,将优秀的软件编程思想用于提升我们应用;

所有设计思想,最终的目标一定是使我们的应用更加 解耦颗粒化、易拓展、易测试、高复用,开发更为高效和安全

种编程思想,它追求更细的粒度,将应用拆分成一组组极小的单元函数,组合调用操作数据流;

  • 它提倡着 纯函数 / 函数复合 / 数据不可变, 谨慎对待函数内的 状态共享 / 依赖外部 / 副作用;

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

Tips:

其实我们很难也不需要在面试过程中去完美地阐述出整套思想,这里也只是浅尝辄止,一些个人理解而已。博主也是初级小菜鸟,停留在表面而已,只求对大家能有所帮助,轻喷🤣;

我个人觉得: 这些编程范式之间,其实并不矛盾,各有各的 优劣势

理解和学习它们的理念与优势,合理地 设计融合,将优秀的软件编程思想用于提升我们应用;

所有设计思想,最终的目标一定是使我们的应用更加 解耦颗粒化、易拓展、易测试、高复用,开发更为高效和安全

  • 30
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值