做项目使用websocket网上的方案很多,但是如果是产品或者平台,采用的是微服务架构,而每个微服务都可能有异步消息处理,想采用websocket,对于前端处理就会产生一个问题,那就是前端vue是SPA应用,它与后台建立一个websocket连接,如果每个微服务都建立一个连接,前端代码岂不是非常复杂。
从下图可以看到前端应用只需要跟消息微服务建立websocket连接即可,后台业务逻辑处理,调用消息服务提供的dubbo接口,再通过消息服务将响应结果推送给前端应用,整个流程就完整了。
接下只需要考虑消息微服务的集群了。
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方案,但我的工程并不需要做什么
路由增加配置,这里看到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
目录下
WebSocket协议插件
jmeter 04jmeter做websocket协议的接口测试,这个有些复杂
jmeter下载配置压测
执行任务前,设置动态参数
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连接立马关闭
3 jprofiler性能监控
3.1 单例
从jprofiler中可以看到MemoryWebsocketManager
、ImBusiController
、MemoryWebSocketManagerConfig
均是单例
执行jcmd 19060 GC.run
,多例的实例从内存中回收掉了,但单例还存在。
线程安全问题:
- 静态常量
常量是只读的,线程安全 - 局部变量
方法的参数变量和方法内变量不是共享资源,局部变量是线程安全的。局部变量存于栈内存,方法执行完毕后,释放掉内存。
JVM的栈内存 - 成员变量
- 类变量
类中通过static修饰的静态变量,类变量是所有实例共一个,是线程不安全的,可以通过final修饰,在一定程度上保障了线程安全。final 静态变量和线程安全,比如在配置加载的时候给类变量做一次赋值,同时防止其他人在其他地方误操作改变了它的值,那么定义为final static变量即可。 - 实例变量
多例情况下对象与对象之间的实例变量修改互不影响,是线程安全的,@ServerEndpoint
中websocket
即是多例;但是单例模式下,因实例变量共享一个实例变量,故而存在线程安全的问题。spring默认都是单例模式,线程安全如何保障的呢?
如何看待Spring下单例模式与线程安全的矛盾,spring单例模式下肯定有线程安全的问题,只要涉及成员变量都存在这个问题。对我们接触的Controller层,如果只有读,那么也不会有线程安全的问题。
高并发的情况下,多线程由tomcat或者netty等容器管理,多线程可以访问同一个实例对象,进入到方法区,因为方法的局部变量又是线程安全的,故而不用担心此问题,只有没有设置共有的成员属性即可。
3.2 多例
从上图可以看到WebSocketEndpoint
、WebSocketConn
这些是多例,websocket是线程安全,采用的是多例模式。
- 类变量