精华内容
下载资源
问答
  • 本文聊天室基于 websocket 进行实现,同时也为解决websocket session集群部署服务时的无法共享导致的收发消息问题。 问题引入     当我们使用 websocket 实现聊天时,后端服务会将所有的 ...

    WebSocket 集群 session 共享方案

    !! 本文聊天室基于 websocket 进行实现,同时也为解决websocket session在集群部署服务时的无法共享导致的收发消息问题。

    问题引入

        当我们使用 websocket 实现聊天时,后端服务会将所有的 websocket session缓存起来,之后根据收到的消息,遍历或者找到某个session进行消息的发送。 但是在我们进行后端集群部署时,这里假设存在2台后端服务器A和B,用户u1连接A的websocket同时创建session,这时A将session进行缓存,u2进行同样的操作,连接B服务器。
        此时,u1群发消息,消息到达A服务,读取session缓存,遍历session发送消息,但由于u2的session不在A服务上,所以u2无法受到消息。 问题形成。

    思路分析

        针对上述问题,可以将session进行分布式存储或者将消息进行广播。

    1. session分布式存储
          可以采用redis进行分布式的session存储,但是发现 websocket session 并未实现 Serializable 接口,无法进行序列化。此方案不可行。

    2. 消息广播
      将消息发送至mq组件,由mq通知各个服务节点消息内容,满足群发消息。

    代码实现

    这里针对第二种消息广播的方案实现较为简单,不再做详细叙述。

    展开全文
  • springboot+websocket集群session共享

    千次阅读 2018-07-06 15:23:26
     # 下面处理websocket  # Origin不能少,否则会爆403  proxy_http_version 1.1;  proxy_set_header Upgrade $http_upgrade;    proxy_set_header Connection "upgrade";  proxy_set_header Origin ...

    1.安装redis(省略)

    外网访问权限更改redis.conf 文件

    bind 127.0.0.1
    
    protected-mode yes

    更改为

    # bind 127.0.0.1
    
    protected-mode no

    启动redis命令:    ./redis-server ../redis.conf

    2.安装nginx(省略)

    更改nginx.conf 文件

        upstream springboots{
                ip_hash;	#同一个ip负载到同一个服务器	
    	    server 192.168.1.204:8001;
    	    server 192.168.1.204:8002;
        }
        server {
            listen       80;
            server_name  localhost;
    
            location / {
                proxy_pass http://springboots;             # 下面处理websocket            # Origin不能少,否则会爆403
    	    proxy_http_version 1.1;
                proxy_set_header Upgrade $http_upgrade;
        	    proxy_set_header Connection "upgrade";
    	    proxy_set_header Origin "";
            }
    

    3.stringboot 代码

    Maven配置

            <!-- springboot - Redis -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-redis</artifactId>
            </dependency>
            <!--spring session 与redis应用基本环境配置,需要开启redis后才可以使用,不然启动Spring boot会报错 -->
            <dependency>
                <groupId>org.springframework.session</groupId>
                <artifactId>spring-session-data-redis</artifactId>
            </dependency>

    Application.properties

    application.properties 
    特别注意此处需要spring.session.store-type

    ########################  Redis ###################################
    spring.redis.database=0
    spring.redis.host=127.0.0.1
    spring.redis.port=6379
    spring.redis.password=
    # 连接池最大连接数
    spring.redis.pool.max-active=8
    spring.redis.pool.max-wait=-1
    # 连接池中的最大空闲连接
    spring.redis.pool.max-idle=8
    # 连接池中的最小空闲连接
    spring.redis.pool.min-idle=0
    # 连接超时时间(毫秒)
    spring.redis.timeout=0
    
    spring.session.store-type=redis

    Config

    /**
     * @description 分布式Session,使用spring.session.store-type=redis自动配置
     */
    @EnableRedisHttpSession(maxInactiveIntervalInSeconds= 1800)
    public class SessionConfig {
    
    }
    
    • 1
    • 2








    展开全文
  • 主要为大家详细介绍了Springboot实现多服务器session共享,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
  • Websocket Session ...所以我的解决方案采用订阅式队列去触发Websocket 反推的方式来实现看上去是Session共享 的,思路如下图: Websocket Session共享 思路图解释: 模块一:这里没有什么好说明的,因为每一...

    Websocket Session 共享解决(方案一)

    既然Websocket Session 不能直接序列化然后存储,而且如果用户请求的时候,开始与集群中的A 节点创建链接,就算把这个  Session  拿到B 节点去再给用户Push 消息,应该也是不通(没测试)的。

    所以我的解决方案采用订阅式队列去触发  Websocket  反推的方式来实现看上去是  Session共享  的,思路如下图:

    Websocket Session  共享 思路图解释:

    模块一:这里没有什么好说明的,因为每一个用户只创建以一个  Session  ,当用户再次链接到第二个节点的时候,第一个节点的  Session  就会关闭和销毁。而每个节点采用静态(static )的成员变量去存储(Map)当前节点的所有Websocket Session 。

    模块二:因为每个节点存储的是每个节点的所有Websocket Session ,那么只需要判断下当前节点是否存在当前用户的Websocket Session ,存在则反推消息。

    模块三:当有消息需要通过  Websocket  推送给对应的用户的时候,而又不知道这个用户在哪个节点,那么通过任务调度模块,把消息以订阅MQ 的方式放到  MQ  中,这样每个节点都可以收到消息,从而参照 “模块二”判断当前用户是否存在当前节点。然后推送。

     

    Websocket Session 共享解决(方案二)

    其实我们之所以要做共享,其实就是因为在集群环境中,在HA/Nginx等方案中,做轮询请求来减轻节点压力,这样导致用户可能分配在多个节点,那么如果我们采用IP等的Hash值规则,或者其他可以参考的规则。解决一个用户只会分配到一个节点上,那么就不存在Session共享了。这样的中间件好多。如我们的“伞神”提出的 采用阿里的 Tengine 的session sticky 组件” 就可以解决。其他的也很多。

    展开全文
  • websocket session共享

    万次阅读 2018-09-30 00:20:04
    用户a通过服务器进入房间room,用户b也通过房间进入room,用户之间是通过session来通话的,所以session直接存储在集合中就可以了。 因为session存储在一台服务器的集合中,所以每次发送消息的时候,直接发给房间内的...

    单机运行

    用户a通过服务器进入房间room,用户b也通过房间进入room,用户之间是通过session来通话的,所以session直接存储在集合中就可以了。

    因为session存储在一台服务器的集合中,所以每次发送消息的时候,直接发给房间内的所有人的session就可以了。

    多机运行

    假如有两台服务器,用户a通过服务器A进入了房间room,用户b通过服务器B也进入了房间room,由于session是用集合存储在各自的服务器中的,所以这种情况下,用户a发的消息只能通过服务器A,但是服务器A中是没有用户b的session的,所以当用户a发送消息的时候,用户b就收不到用户a发送的消息了。

    实现共享session

    利用redis的订阅发布实现

    当用户a通过服务器A进入房间room,然后将用户存入redis的房间room中,用户b通过服务器B进入房间room,也将用户b存入redis的房间room中,并且都订阅房间room.将各自的session还是存储在各自的服务器中,

    然后发消息的时候,发布消息到redis的room中,然后监听redis中的room房间,当有消息发送的时候,判断redis的房间中是否存在本地用户,如果存在则反推给当前服务器对应的用户。

    在本机用两个tomcat配置不同的端口号模拟启动,访问测试成功,效果如下:

     

    RedisConfig  这里没有配置JedisConnectionFactory,因为在application.yml中配置集群(windows集群配置参考这篇博客https://www.cnblogs.com/tommy-huang/p/6240083.html)时会自动注入连接工厂。

    配置监听容器RedisMessageListenerContainer,用于监听redis发布的消息。

    package com.test;/*
    
    */
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.data.redis.connection.RedisConnectionFactory;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.data.redis.listener.RedisMessageListenerContainer;
    import org.springframework.data.redis.serializer.*;
    import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
    import redis.clients.jedis.HostAndPort;
    import redis.clients.jedis.JedisCluster;
    
    import java.util.HashSet;
    
    @Configuration
    public class RedisConfig {
    
        @Bean
        public RedisTemplate<?,?> getRedisTemplate(@Autowired RedisConnectionFactory redisConnectionFactory){
          RedisTemplate redisTemplate=  new RedisTemplate<>();
          redisTemplate.setConnectionFactory(redisConnectionFactory);
          redisTemplate.setKeySerializer(new StringRedisSerializer());
          redisTemplate.setValueSerializer(new StringRedisSerializer());
            return redisTemplate;
        }
    
        @Bean
        public RedisMessageListenerContainer initRedisContainer(@Autowired RedisConnectionFactory redisConnectionFactory){
    
            RedisMessageListenerContainer container=new RedisMessageListenerContainer();
            container.setConnectionFactory(redisConnectionFactory);
            return container;
        }
    }
    

    SocketRest

    实现MessageListener接口,重写onMessage方法,接收redis消息并发送。
    方法container.addMessageListener(socketRest,topic); socketRest为监听者,topic为监听的房间号。

    每当第一个用户进入房间后,就为该房间增加一个监听者,用来监听该房间的消息。

     

    package com.test;
    import java.util.*;
    import java.util.concurrent.ConcurrentHashMap;
    import java.util.concurrent.ConcurrentMap;
    import javax.websocket.*;
    import javax.websocket.server.PathParam;
    import javax.websocket.server.ServerEndpoint;
    import com.alibaba.fastjson.JSON;
    import com.alibaba.fastjson.JSONObject;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.ApplicationContext;
    import org.springframework.data.redis.connection.Message;
    import org.springframework.data.redis.connection.MessageListener;
    import org.springframework.data.redis.listener.ChannelTopic;
    import org.springframework.data.redis.listener.RedisMessageListenerContainer;
    import org.springframework.data.redis.listener.Topic;
    import org.springframework.stereotype.Component;
    
    
    
    @ServerEndpoint("/websocket/{roomId}/{username}")
    @Component
    public class SocketRest implements MessageListener{
    
        //接收redis中的消息
        @Override
        public void onMessage(Message msgs, byte[] pattern) {
            try {
                //消息内容
                byte[] body=msgs.getBody();
                System.out.println("body: "+body);
                //订阅房间
                String topic=new String(pattern);
                //获取存在的房间中的用户
            String result= new String(body,"utf-8");
                System.out.println("msg: "+result);
                JSONObject js= JSON.parseObject(result);
                String username=js.getString("username");
                String msg=js.getString("msg");
                Set<Object> SessionKeys= UserSessions.keySet();
                HashMap message=new HashMap();
                message.put("msg",msg);
                message.put("username",username);
               broadcast(topic,message,username);
            }catch (Exception e){
                log.info("onMessage exception");
            }
        }
    
    }

    application.yml

    server:
      port: 8088
    
    logging:
      config: classpath:log4j2.yml
    spring:
      profiles:
           active: test
    ---
    spring:
      profiles:  test
    
      datasource:
            url: jdbc:mysql://localhost:3306/test
            driver-class-name: com.mysql.jdbc.Driver
            data-password: 1234
            data-username: root
      redis:
        cluster:
          nodes: 127.0.0.1:7001,127.0.0.1:7002,127.0.0.1:7003
        database: 0
        host: 127.0.0.1:7001
        port: 6379
        
      session:
      store-type: redis
    
    
    
    
    
    
    WebsocketConfig  打包成war用外置tomcat部署时需要注释,不注释会和tomcat自带的websocket注入bean时冲突。
    @Configuration
    public class WebsocketConfig {
    
      @Bean
        public ServerEndpointExporter config(){
          return new ServerEndpointExporter();
      }
    
    }

    启动时写入房间

    @Component
    public class StartupRunner implements CommandLineRunner {
    
    
        @Autowired
        private RedisUtil redisUtil;
    
        @Override
        public void run(String... strings) throws Exception{
            if(!redisUtil.sGet("room111").isEmpty()){
                System.out.println("清空数据");
                redisUtil.del("room111");
            }
            redisUtil.sSet("rooms","room111","room222");
           Set<Object> set=redisUtil.sGet("rooms");
           for (Object o:set){
               System.out.println("------>"+o.toString());
           }
    
        }
    }

    ApplicationContextRegister   解决websocket中不能注入bean.

    package com.test;/*
    
    */
    
    import org.springframework.beans.BeansException;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.ApplicationContextAware;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.stereotype.Component;
    
    @Component
    public class ApplicationContextRegister implements ApplicationContextAware {
    
        private static ApplicationContext applicationContext;
    
        @Override
        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
            this.applicationContext=applicationContext;
        }
    
        public static ApplicationContext getApplicationContext() {
            return applicationContext;
        }
    }
    

    RedisUtil  发布方法

    增加订阅发布方法, channel订阅房间号,message发送给该房间的消息。

    public  void convertAndSend(String channel ,Object message){
        redisTemplate.convertAndSend(channel,message);
    }

    启动类Application

    package com.test;
    
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.boot.builder.SpringApplicationBuilder;
    import org.springframework.boot.web.support.SpringBootServletInitializer;
    
    @EnableAutoConfiguration
    @SpringBootApplication
    public class Application extends SpringBootServletInitializer {
    
    	@Override
    	protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
    		return builder.sources(Application.class);
    	}
    
    	public static void main(String[] args) {
    		 SpringApplication.run(Application.class, args);
    	}
    
    }
    

    pom.xml

    用tomcat部署时,如果不是默认的8080,则需要添加端口号,在server.xml中配置,增加端口以及设置编码。

    <Connector port="8088" protocol="HTTP/1.1"
                   connectionTimeout="20000"
                   redirectPort="8443" URIEncoding="UTF-8" />

    设置项目启动路径,docBase即项目名称

     <Host name="localhost"  appBase="webapps"
                unpackWARs="true" autoDeploy="true">
         <Context path="" docBase="whatsup" debug="0" reloadable="true" />
            <!-- SingleSignOn valve, share authentication between web applications
                 Documentation at: /docs/config/valve.html -->
            <!--
            <Valve className="org.apache.catalina.authenticator.SingleSignOn" />
            -->

            <!-- Access log processes all example.
                 Documentation at: /docs/config/valve.html
                 Note: The pattern used is equivalent to using pattern="common" -->
            <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
                   prefix="localhost_access_log" suffix=".txt"
                   pattern="%h %l %u %t &quot;%r&quot; %s %b" />

          </Host>

    log4j.yml

    Appenders:
        Console:  #输出到控制台
          name: CONSOLE #Appender命名
          target: SYSTEM_OUT
          PatternLayout:
            pattern: "%d{yyyy-MM-dd HH:mm:ss,SSS}:%4p %t (%F:%L) - %m%n"
        RollingFile: # 输出到文件,超过256MB归档
          - name: ROLLING_FILE
            ignoreExceptions: false
            fileName: springboot.log
            filePattern: "/springboot/logs/$${date:yyyy-MM}/springboot -%d{yyyy-MM-dd}-%i.log.gz"
            PatternLayout:
              pattern: "%d{yyyy-MM-dd HH:mm:ss,SSS}:%4p %t (%F:%L) - %m%n"
            Policies:
              SizeBasedTriggeringPolicy:
                size: "256 MB"
            DefaultRolloverStrategy:
              max: 1000
    Loggers:
        Root:
          level: info
          AppenderRef:
            - ref: CONSOLE
        Logger: #单独设置某些包的输出级别
          - name: app.com.kenho.mapper #复数加上-
            additivity: false #去除重复的log
            level: trace
            AppenderRef:
              - ref: CONSOLE #复数加上-
              - ref: ROLLING_FILE #复数加上-

     

     

    展开全文
  • 看了网上很多springwebsocket的帖子,关于用户的session都是存在map或List里 比如 private static Map, WebSocketSession> users = new ConcurrentHashMap,WebSocketSession>(); 但是在做集群的时候,用户肯定不会...
  • websocket分布式共享session解决方法

    千次阅读 2018-10-29 14:47:59
    项目开发是基于cloud分布式开发模式,需要实时向客户端推送消费的kafka内容,由于websocketsession不可序列化,故不能够存储到redis等缓存当中。为解决其分布式多服务器共享session,想到以下解决方案。 1,...
  • WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。 它是为了解决客户端发起多个http请求到服务器资源浏览器必须要经过长时间的轮训问题而生的,...
  • 流程是首先前端跟后端应用新建一个连接,并携带当前登录的用户ID,此时WebSocket会创建一个WebsocketSession来唯一绑定该连接,我们会在后端用Map建立用户ID与Session的映射关系: Map 后续有新消息到达时,就...
  • 应用集群情况下Spring+Spring-session+Redis实现Session共享
  • 分布式WebSocket集群解决方案

    千次阅读 2018-12-08 15:14:50
    最近做项目时遇到了需要多用户之间通信的问题,涉及到了WebSocket握手请求,以及集群中WebSocket Session共享的问题。 期间我经过了几天的研究,总结出了几个实现分布式WebSocket集群的办法,从zuul到spring cloud ...
  • Nginx-tomcat 负载均衡配置 以及redis-session共享 websocket集群, Nginx是一个高性能的HTTP和反向代理web服务器,返现代理指代表外部网络用户向内部服务器发出请求,即接收来自Internet上用户的连接请求,并将...
  • springboot+websocket+RabbitMQ(集群

    千次阅读 2020-05-25 14:04:52
    websocket集群 由于websocket的session会话是不能通过redis去序列化,所有不能直接利用redis进行集群部署(但可以使用redis订阅与发布功能) 方案 利用nignx+springboot websocket+rabbitmq 利用rabbitmq的订阅与...
  • 最近做项目时遇到了需要多用户之间通信的问题,涉及到了WebSocket握手请求,以及集群中WebSocket Session共享的问题。 期间我经过了几天的研究,总结出了几个实现分布式WebSocket集群的办法,从zuul到spring cloud ...
  • 由于websocket session不能序列化,所以不能存储在redis中。故在分布式系统中,可以通过把websocketsession存储在服务器本地map,然后把消息发布到redis指定的频道上,每个服务器节点都订阅该频道,这样的话,...
  • WebSocket 集群解决方案

    2021-10-19 00:27:49
    点击关注公众号,利用碎片时间学习问题起因最近做项目时遇到了需要多用户之间通信的问题,涉及到了WebSocket握手请求,以及集群WebSocket Session共享的问题。期间我经过了...
  • // 集群订阅方式 (默认) mqProperties.put(PropertyKeyConst.MessageModel, PropertyValueConst.CLUSTERING); Consumer consumer = ONSFactory.createConsumer(mqProperties); return consumer; } } 增加...
  • WebSocketsession是不... 既然无法序列化session,那还是存储在Map中,各服务器通过发布订阅变相实现共享websocket session. 第二种实现,参考:https://gitee.com/xxssyyyyssxx/websocket-springboot-starter ...
  • websocket集群搭建方案

    千次阅读 2019-01-14 17:03:46
    1.前言 我们都知道http协议,http属于短链接,属于一个request对应一个response,但是而且请求后就必须有响应... 假设有个业务想要连接不断,并且更加自由,那么就得用websocket协议。本编文章讲述搭建websocket集群...
  • 微服务分布式多应用场景中,由于ws的session无法序列化到redis,所以集群中,无法将所有WebSocket Session都缓存到redis进行session共享。Session 都存储在各应用当中 解决办法-基于Redis 的发布订阅 redis 订阅配置...
  • 关于websocket做一次全面的总结。...实现WebSocket集群的2种方式 用redis的订阅/推送功能实现的。(推荐) 用redis存客户端连接对应的服务器的IP+端口,再用http去调用对应服务器的接口。 用redi.
  • 其中只有一台服务器具备ssl认证域名,一台redis+mysql服务器,两台应用服务器(集群) 应用发布限制条件:由于场景需要,应用场所需要ssl认证的域名才能发布。因此ssl认证的域名服务器用来当api网关,负责https...
  • 解决方案:使用redis消息发布订阅解决多个tomcat应用服务器下,连接不共享问题;具体如下 @Configuration public class WebSocketConfig {  //TODEO如果用外置tomcat,要注释掉以下代码,否则启动项目会报错,...
  • Nginx+Tomcat+Redis搭建高性能负载均衡集群Session共享
  • 大规模 WebSocket 集群项目 AnyIM 实战

    千次阅读 2018-08-04 21:07:37
    WebSocket 应用场景非常广泛,例如社交聊天、弹幕、多玩家游戏、协同编辑、股票基金实时报价、体育实况更新、视频会议/聊天、实时定位、在线教育、智能家居等,这些场景都与我们的生活息息相关。 ANY-IM 2.0 是基于...
  • 深入浅出Websocket(二)分布式Websocket集群 深入浅出Websocket(三)分频道的Websocket(分析socket.io源码以及ws-wrapper) 正文 这个是我在造的玩具的一个简单架构图。将实时通信部分给抽离出来作...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,529
精华内容 611
关键字:

websocket集群session共享