websocket集群
由于websocket的session会话是不能通过redis去序列化,所以不能直接利用redis进行集群部署(但可以使用redis订阅与发布功能)
利用nignx+springboot websocket+rabbitmq
- 利用rabbitmq的订阅与发布
这里我用到的是fanout(广播模式),该模式把交换机里的消息发送给所有绑定该交换机的队列。由于集群部署,对于websocket的长连接 存储在不同的服务器中,我们通过将发送的所有消息集发送给不同服务器,让不同服务器里自己处理,消息集里有自己的session存储数据进行发送 - nignx
nignx去websocket反向代理到不同服务器
代码区
rabbitmq配置
#rabbitMQ配置
spring.rabbitmq.host=192.168.66.10
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
websoccket
@ServerEndpoint(value = "/websocket/{pageCode}",encoders = { ResultEncoder.class})
@Component
public class WebSocket {
private static final String loggerName=WebSocket.class.getName();
//concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。若要实现服务端与单一客户端通信的话,可以使用Map来存放,其中Key可以为用户标识
public static Map<String, Session> electricSocketMap = new ConcurrentHashMap<String, Session>();
//解决注解无法注入
private static ApplicationContext applicationContext;
// @Autowired
// private WebsocketService websocketService=applicationContext.getBean(WebsocketService.class);
public static void setApplicationContext(ApplicationContext applicationContext) {
WebSocket.applicationContext = applicationContext;
}
/**
* 连接建立成功调用的方法
*
* @param session 可选的参数。session为与某个客户端的连接会话,需要通过它来给客户端发送数据
*/
@OnOpen
public void onOpen(@PathParam("pageCode") String pageCode, Session session) {
electricSocketMap.put(pageCode,session);
System.out.println("------------连接-------");
// sendMsg(session,new MessageResult(MessageResult.SUCCESS,electricSocketMap.size()+"",""));
WebsocketService websocketService=applicationContext.getBean(WebsocketService.class);
websocketService.send(pageCode,new MessageResult(MessageResult.SUCCESS,electricSocketMap.size()+"",""));
}
/**
* 连接关闭调用的方法
*/
@OnClose
public void onClose(@PathParam("pageCode") String pageCode, Session session) {
if (electricSocketMap.containsKey(pageCode)){
electricSocketMap.remove(pageCode);
}
}
/**
* 收到客户端消息后调用的方法
*
* @param message 客户端发送过来的消息
* @param session 可选的参数
*/
@OnMessage
public void onMessage(String message, Session session) {
System.out.println("websocket received message:"+message);
String[] split = message.split("--");
if(split[1].contains("0")){
sendAll(new MessageResult(MessageResult.SUCCESS,electricSocketMap.size()+"",message));
}else {
WebsocketService websocketService=applicationContext.getBean(WebsocketService.class);
websocketService.send(split[1],new MessageResult(MessageResult.SUCCESS, electricSocketMap.size() + "", message));
// sendMsg(electricSocketMap.get(split[1]), new MessageResult(MessageResult.SUCCESS, electricSocketMap.size() + "", message));
}
}
/**
* 发生错误时调用
*
* @param session
* @param error
*/
@OnError
public void onError(Session session, Throwable error) {
error.printStackTrace();
System.out.println("发生错误");
}
/**
*
* @param session
* @param message 消息结果集
*/
public static void sendMsg(Session session, Object message) {
try {
session.getBasicRemote().sendObject(message);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 群发消息
* @param message 消息内容
*/
private void sendAll(MessageResult message) {
WebsocketService websocketService=applicationContext.getBean(WebsocketService.class);
websocketService.sendAll(electricSocketMap,message);
}
}
websocket配置类
@Component
public class WebsocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
server配置类
此类是为了动态生成队列
@Component
public class ServerConfig implements ApplicationListener<WebServerInitializedEvent> {
@Value("${server.port}")
private int serverPort;
public String getUrl() {
InetAddress address = null;
try {
address = InetAddress.getLocalHost();
} catch (UnknownHostException e) {
e.printStackTrace();
}
// return "http://" + address.getHostAddress() + ":" + this.serverPort;
return address.getHostAddress() + ":" + this.serverPort;
}
@Override
public void onApplicationEvent(WebServerInitializedEvent event) {
// this.serverPort = event.getWebServer().getPort();
}
}
交换机配置类
@Configuration
public class TestExchangeConfiguration {
private static Logger logger = LoggerFactory.getLogger(TestExchangeConfiguration.class);
@Autowired
private ServerConfig serverConfig;
@Bean
public FanoutExchange fanoutExchange() {
logger.info("【【【交换机实例创建成功】】】");
return new FanoutExchange(Constants.FANOUT_EXCHANGE_NAME);
}
@Bean
public Queue queue() {
logger.info("【【【队列实例创建成功】】】");
//动态名称
return new Queue(Constants.TEST_QUEUE1_NAME+"——"+serverConfig.getUrl());
}
@Bean
public Binding bingQueue1ToExchange() {
logger.info("【【【绑定队列到交换机成功】】】");
return BindingBuilder.bind(queue()).to(fanoutExchange());
}
}
rabbitmq提供者
@Component
public class FanoutSender {
private static Logger logger = LoggerFactory.getLogger(FanoutSender.class);
@Autowired
private RabbitTemplate rabbitTemplate;
public void sendMessage(Map<String, MessageResult> rabbitMessageList) {
logger.info("【消息发送者】发送消息到fanout交换机,");
rabbitTemplate.convertAndSend(Constants.FANOUT_EXCHANGE_NAME, "", JSONObject.fromObject(rabbitMessageList));
}
}
rabbitmq 消费者
@Component
public class FanoutReceiver {
private static Logger logger = LoggerFactory.getLogger(FanoutReceiver.class);
@RabbitHandler
@RabbitListener(queues = "#{queue.name}")//动态绑定
public void receiveMessage(JSONObject jsonObject) {
logger.info("消息接收者接收到来自【队列一】的消息,消息内容: ");
Map<String, Session> electricSocketMap = WebSocket.electricSocketMap;
Map<String,MessageResult> rabbitMessageList= (Map<String, MessageResult>) JSONObject.toBean(jsonObject, HashMap.class);
for (String key : rabbitMessageList.keySet()) {
if(electricSocketMap.get(key)!=null) {
WebSocket.sendMsg(electricSocketMap.get(key), rabbitMessageList.get(key));
}
}
}
}
静态变量
public class Constants {
/**
* 交换机名称
*/
public static final String FANOUT_EXCHANGE_NAME = "fanout.exchange.name";
/**
* 测试队列名称
*/
public static final String TEST_QUEUE1_NAME = "test.queue1.name";
}
发送消息
@Service
public class WebsocketService {
@Autowired
private FanoutSender fanoutSender;
public void send(String id, MessageResult message) {
Map<String, MessageResult> map=new HashMap<>();
map.put(id,message);
fanoutSender.sendMessage(map);
}
public void sendAll(Map<String, Session> electricSocketMap,MessageResult message) {
Map<String,MessageResult> map=new HashMap<>();
for (Map.Entry<String, Session> sessionEntry : electricSocketMap.entrySet()) {
// sessionEntry.getValue().getAsyncRemote().sendObject(message);
map.put(sessionEntry.getKey(),message);
}
fanoutSender.sendMessage(map);
}
}
启动类
@SpringBootApplication
public class SpringbootRbmqWebsocketApplication {
public static void main(String[] args) {
ConfigurableApplicationContext configurableApplicationContext = SpringApplication.run(SpringbootRbmqWebsocketApplication.class, args);
//解决WebSocket不能注入的问题
WebSocket.setApplicationContext(configurableApplicationContext);
}
}
对于websocket这一块,其实没有大的改动,只是接受消息后的发送消息改成rabbitmq。与之前的大同小异👇
SpringBoot+简单整合websocket-聊天室