spring websocket集群

做项目使用websocket网上的方案很多,但是如果是产品或者平台,采用的是微服务架构,而每个微服务都可能有异步消息处理,想采用websocket,对于前端处理就会产生一个问题,那就是前端vue是SPA应用,它与后台建立一个websocket连接,如果每个微服务都建立一个连接,前端代码岂不是非常复杂。
从下图可以看到前端应用只需要跟消息微服务建立websocket连接即可,后台业务逻辑处理,调用消息服务提供的dubbo接口,再通过消息服务将响应结果推送给前端应用,整个流程就完整了。
1
接下只需要考虑消息微服务的集群了。

1 spring websocket
代码参考websocket-springboot-starter
websocket-demo

@Slf4j
@Component
@ServerEndpoint("/busi/{source}/{identifier}")
public class WebSocketEndpoint extends AbstractWebSocketEndpoint  {

    private static WebSocketManager webSocketManager;

    @Autowired
    public void setWebSocketManager(WebSocketManager webSocketManager) {
        WebSocketEndpoint.webSocketManager = webSocketManager;
    }

    @OnOpen
    public void onOpen(Session session,@PathParam("source") String source, @PathParam(value="identifier")String identifier ){
        log.info("用户连接成功,连接来源为{},连接用户为:{}",source, identifier);
        connect(session, source, identifier);
    }

    @OnMessage
    public void onMessage(String message,Session session,  @PathParam(value="identifier")String identifier ){
        log.info("接受到的消息是{}",message);
        recieveMessage(message, session,identifier );
    }

    @OnClose
    public void onClose(Session session, @PathParam(value="identifier")String identifier ){
        log.info("用户断开连接");
        disconnect(session, identifier);
    }

    @OnError
    public void onError(Session session,Throwable t, @PathParam("identifier") String identifier){
        log.info("发生异常:, identifier = " + identifier);
        log.error(t.getMessage() , t);
        disconnect(session, identifier);
    }

    @Override
    public WebSocketManager getWebSocketManager() {
        return WebSocketEndpoint.webSocketManager ;
    }

}

这里重点关注的是applicationContext的赋值

@Configuration
public class MemoryWebSocketManagerConfig implements ApplicationContextAware {

    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    @Bean(WebSocketManager.WEBSOCKET_MANAGER_NAME)
    public WebSocketManager webSocketManager() {
        return new MemoryWebsocketManager(this.applicationContext);
    }


}
@Slf4j
@RestController
@RequestMapping("imBusi")
public class ImBusiController {

    @Autowired
    private WebSocketManager webSocketManager;

    @GetMapping("call/{id}")
    public String call(@PathVariable("id") String id){
        WebSocketConn webSocketConn = webSocketManager.get(id);
        WebsocketUtil.sendText(webSocketConn.getSession(), "hi," +id);
        return "ok";
    }
}

@Data
public class WebSocketConn {

    /**
     * 连接集合
     */
    private Session session;

    /**
     * 连接终端的来源
     */
    private WebSocketSource source;

    /**
     * 会话的唯一标识
     */
    private String id;
}

2 gateway websocket
spring gateway自身就带了WebsocketRoutingFilter,网上有sockjs方案,但我的工程并不需要做什么
1
路由增加配置,这里看到uri只需要调整下增加ws协议前缀就可以了,gateway本身不用做什么

{
    "id": "im-server",
    "predicates": [{
        "args": {
            "pattern": "/api/im/**"
        },
        "name": "Path"
    }],
	"filters": [{
		"name": "StripPrefix",
		"args": {
            "_genkey_0": "2"
        }
	}],
    "uri": "lb:ws://im-server"
}

3 jmeter验证
看到别人写的jmx脚本,自己不亲自动手写,感觉还是差很多,连jmeter的菜单都熟练。掌握jmeter并不是为了做测试,而是会让你更有全局思维。
jmeter之websocket压测
这个方案比较简单JMeterWebSocketSamplers-1.2.8.jar放到/lib/ext目录下
1
WebSocket协议插件
jmeter 04jmeter做websocket协议的接口测试,这个有些复杂
jmeter下载配置压测
1

执行任务前,设置动态参数

int size_full_thread = ${size_full_thread};
String phonenum_prefix = "${phonenum_prefix}"; 

String thead = String.valueOf(${__threadNum});
int size_thead = thead.length();

String pading_thead = "";
for(i = 0; i< size_full_thread - size_thead; ++i) {
	pading_thead = "0" + pading_thead;
	}
thead = pading_thead + thead;


String username = phonenum_prefix + thead;
vars.put("username", username);
log.info("###############################username#############################:" + username);

设置一定的延迟,防止ws连接立马关闭
2

3 jprofiler性能监控
3.1 单例
从jprofiler中可以看到MemoryWebsocketManagerImBusiController MemoryWebSocketManagerConfig 均是单例
1
执行jcmd 19060 GC.run,多例的实例从内存中回收掉了,但单例还存在。
2
线程安全问题:

  • 静态常量
    常量是只读的,线程安全
  • 局部变量
    方法的参数变量和方法内变量不是共享资源,局部变量是线程安全的。局部变量存于栈内存,方法执行完毕后,释放掉内存。
    JVM的栈内存
  • 成员变量
    • 类变量
      类中通过static修饰的静态变量,类变量是所有实例共一个,是线程不安全的,可以通过final修饰,在一定程度上保障了线程安全。final 静态变量和线程安全,比如在配置加载的时候给类变量做一次赋值,同时防止其他人在其他地方误操作改变了它的值,那么定义为final static变量即可。
    • 实例变量
      多例情况下对象与对象之间的实例变量修改互不影响,是线程安全的,@ServerEndpointwebsocket即是多例;但是单例模式下,因实例变量共享一个实例变量,故而存在线程安全的问题。spring默认都是单例模式,线程安全如何保障的呢?
      如何看待Spring下单例模式与线程安全的矛盾,spring单例模式下肯定有线程安全的问题,只要涉及成员变量都存在这个问题。对我们接触的Controller层,如果只有读,那么也不会有线程安全的问题。
      高并发的情况下,多线程由tomcat或者netty等容器管理,多线程可以访问同一个实例对象,进入到方法区,因为方法的局部变量又是线程安全的,故而不用担心此问题,只有没有设置共有的成员属性即可。
      3.2 多例
      从上图可以看到WebSocketEndpoint WebSocketConn这些是多例,websocket是线程安全,采用的是多例模式。
  • 3
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

warrah

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

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

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

打赏作者

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

抵扣说明:

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

余额充值