springboot+websocket(集群方案)

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-聊天室

你可能感兴趣的:(springboot+websocket(集群方案))