分布式webSocket session无法共享问题

分布式webSocket session无法共享问题

问题:
最近碰到一个麻烦的事情,就是在使用webSocket推送消息时候,发现session存放在各个节点,各节点之间无法获取session。而且经过研究发现:WebSocket与http协议一样都是基于TCP的,所以他们都是可靠的协议,调用的WebSocket的send函数在实现中最终都是通过TCP的系统接口进行传输的。WebSocket和Http协议一样都属于应用层的协议,WebSocket在建立握手连接时,数据是通过http协议传输的,但是在建立连接之后,真正的数据传输阶段是不需要参与的
分析
也就是说目前问题:1、wehsocketSession无法序列化,即无法存放在redis里面达到共享
2、就算能在别的节点创建session对象,也无法推送消息,因为webSocket是基于tcp的协议,http,https在传输数据上是不会参与的,也就是说与哪个节点建立的连接必须那个节点推送消息
解决思路
1 使用订阅发布服务工具,各个节点即为订阅方也为发布方,一个节点发布,其余节点收到消息,检测自己是否存在改session,存在通过收到的消息推送数据。
2记录session关键信息(节点ip,端口,用户数据)到缓存里面(推荐redis),通过查找session关键信息,定位到哪个节点,通过http请求改节点,达到推送效果(涉及http请求,可能响应回慢)

ps:本来想使用方法一,通过redis订阅发布服务实现消息推送,但是项目节点很多,如果一个节点发布,其余都要查找,担心增加服务器压力,所以本人选择方法二,但是方法一我会在最下面做简单介绍
websocket服务部署:
redsi 存放关键信息,ip,端口

@ServerEndpoint("/signWebsocket/{mobile}")
@Component
public class SignWebSocket {

    private static final Logger LOG = LoggerFactory.getLogger(SignWebSocket.class);

    private static SignWebSocket instance = new SignWebSocket();

    //保存客户端发起的唯一标识与长连接
    private static ConcurrentHashMap<String, Session> sessionMap = new ConcurrentHashMap<String, Session>();

    public static final String KEY_SOCKET_SESSION = "socket_session_";

    private static int KEY_SOCKET_TIME = 3000;

    private RedisCache redisCache;

    public SignWebSocket() {
        ApplicationContext act = ApplicationContextRegister.getApplicationContext();
        this.redisCache = act.getBean(RedisCache.class);
    }

    public static ConcurrentHashMap<String, Session> getSessionMap() {
        return sessionMap;
    }

    public static SignWebSocket getInstance() {
        return instance;
    }

    /**
     * 连接建立成功调用的方法
     *
     * @param session 可选的参数。session为与某个客户端的连接会话,需要通过它来给客户端发送数据
     */
    @OnOpen
    public void onOpen(@PathParam("mobile") String mobile, Session session) {

        String path= AllConstant.getInstance().getLocalAddr()+":"+AllConstant.getInstance().getLocalPort();
        try {
            if (sessionMap.containsKey(mobile)) {
                Session session_old = sessionMap.get(mobile);
                session_old.close();
                sessionMap.remove(mobile);
            }
        } catch (IOException e) {
            LOG.error(e.getMessage(),e);
        }
        sessionMap.put(mobile, session);
        this.redisCache.set(KEY_SOCKET_SESSION + mobile, path, 3000);
    }

    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose(@PathParam("mobile") String mobile, Session session) {
        try {
            if (sessionMap.containsKey(mobile)) {
        //        System.out.println("调用onClose:"+mobile);
                session = sessionMap.get(mobile);
                session.close();
                sessionMap.remove(mobile);
            }
        } catch (Exception ex) {
            LOG.error(ex.getMessage(), ex);
        }
    }

    /**
     * 收到客户端消息后调用的方法
     *
     * @param message 客户端发送过来的消息
     * @param session 可选的参数
     */
    @OnMessage
    public void onMessage(@PathParam("mobile") String mobile, String message, Session session) {
        try {
        } catch (Exception e) {
            LOG.error(e.getMessage(),e);
        }
    }

    /**
     * 发生错误时调用
     *
     * @param session
     * @param error
     */
    @OnError
    public void onError(@PathParam("mobile") String mobile, Session session, Throwable error) {
        try {
            if (sessionMap.containsKey(mobile)) {
                sessionMap.remove(mobile);
            }
            session.close();
        } catch (Exception ex) {
            LOG.error(ex.getMessage(), ex);
        }
    }

    /**
     * mobile ,向对应的客户端推送消息
     */
    public void pushMessage(String mobile, int coin, int flow) {
        try {
            Session session;
            System.out.println("sessionMap ========={}"+sessionMap);
            if (sessionMap.containsKey(mobile)) {
                JSONObject user = new JSONObject();
                user.put("coin", coin);
                user.put("flow", flow);
                session = sessionMap.get(mobile);
                if (null != session) {
                    session.getBasicRemote().sendText(user.toString());
                }
            }

        } catch (Exception ex) {
            LOG.error(ex.getMessage(), ex);
        }
    }
}

http监听请求,推送消息

    @RequestMapping( value = {"/socket"},method = {RequestMethod.GET})
    @ResponseBody
    public JSONObject socket(String phone,Integer coin,Integer flow , HttpServletRequest request){

        JSONObject obj =new JSONObject();
        logger.error("开始推送");
        System.out.println("socket phone:"+phone+" coin:"+coin+" flow:"+flow);
        try{
            SignWebSocket.getInstance().pushMessage(phone,coin,flow);
        }catch (Exception e){
            e.printStackTrace();
        }
        obj.put("phone",phone);
        obj.put("coin",coin);
        obj.put("flow",flow);
        return obj;
    }

方法二:
使用redis订阅发布服务,每个节点都是发布方,其他节点都是订阅方,当接收到发布的消息时,检测webSocket sessionMap里面session,推送消息。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值