精华内容
下载资源
问答
  • websocket分布式共享session解决方法

    千次阅读 2018-10-29 14:47:59
    项目开发是基于cloud分布式开发模式,需要实时向客户端推送消费的kafka内容,由于websocketsession不可序列化,故不能够存储到redis等缓存当中。为解决其分布式多服务器共享session,想到以下解决方案。 1,...

    问题描述:

    项目开发是基于cloud分布式开发模式,需要实时向客户端推送消费的kafka内容,由于websocket的session不可序列化,故不能够存储到redis等缓存当中。为解决其分布式多服务器共享session,想到以下解决方案。

    1,socket连接时,将其sessionkey和当前服务器ip进行绑定,放入redis缓存。

    2,消费kafka消息时,判断session是否存在该服务器。

    3,如果不存在,从redis缓存中,根据sessionkey查询该服务器ip。用HTTP请求发送该sessionkey和kafka消息至该服务器。

    4,写一个调用session方法,接收别的服务器发送过来的sessionkey请求,得到该session,并将接收到的kafka信息发送至该session客户端。

     

    另一种比较好的解决方法是,改写服务器端为netty连接,既能够共享channel,又能实现nio多连接。

    展开全文
  • 但是分布式存在websocket session共享问题,于是考虑radis存储session,但是遇到websocket session不支持序列化,无法存储。 一番搜索后有了以下几个方案 1. 使用spring session自定义session. 2. 既然

    单websocket服务器在面对并发量很大时压力会很大,而且session储存在Map中,内存压力也会很大。于是考虑分布式。

    但是分布式存在websocket session共享问题,于是考虑radis存储session,但是遇到websocket session不支持序列化,无法存储。

    一番搜索后有了以下几个方案

    1. 使用spring session自定义session.

    2.  既然无法序列化session,那还是存储在Map中,各服务器通过发布订阅变相实现共享websocket session.

    暂时方案这些,还没实现,先记录下.

    展开全文
  • 本文主要是针对分布式场景下的使用websocket的一个解决方案。我们以下面的图来说明下业务使用场景。  针对如图的情况,很多人第一时间想到的是websocketsession共享,这是大多数的第一反应。很遗憾的是,...

    本文主要是针对分布式场景下的使用websocket的一个解决方案。我们以下面的图来说明下业务使用场景。

      针对如图的情况,很多人第一时间想到的是websocket的session共享,这是大多数的第一反应。很遗憾的是,websocketsession是不支持序列化操作,所以也就不可能存在redis中。那么我们有什么其他的方式解决呢。

      我们知道在单节点中我们只需要吧websocketsession存储在Map中就OK,每次发送通知都从map中根据clientID获取对应的websocket的session进行消息通知。那么我们是不是可以这样,不管是哪个服务节点要发送消息,我都告诉其他的服务,当前要发送到的客户端和发送的内容。然后各个服务节点判断自己是否存储了对应的clientID,然后将消息推送给出去呢。

     具体的实现逻辑,redis配置:

    @Configuration
    public class RedisConfig {
    	  // 注入 RedisConnectionFactory
        @Autowired
        private RedisConnectionFactory redisConnectionFactory;
    
        @Bean
        public RedisTemplate<String, Object> functionDomainRedisTemplate() {
            RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
            initDomainRedisTemplate(redisTemplate, redisConnectionFactory);
            return redisTemplate;
        }
    
        /**
         * 设置数据存入 redis 的序列化方式
         * @param redisTemplate
         * @param factory
         */
        private void initDomainRedisTemplate(RedisTemplate<String, Object> redisTemplate, RedisConnectionFactory factory) {
            redisTemplate.setKeySerializer(new StringRedisSerializer());
            redisTemplate.setHashKeySerializer(new StringRedisSerializer());
            redisTemplate.setHashValueSerializer(new JdkSerializationRedisSerializer());
            redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());
            redisTemplate.setConnectionFactory(factory);
        }
        
         /**
         * 实例化 HashOperations 对象,可以使用 Hash 类型操作
         * @param redisTemplate
         * @return
         */
        @Bean
        public HashOperations<String, String, Object> hashOperations(RedisTemplate<String, Object> redisTemplate) {
            return redisTemplate.opsForHash();
        }
    
        /**
         * 实例化 ValueOperations 对象,可以使用 String 操作
         * @param redisTemplate
         * @return
         */
        @Bean
        public ValueOperations<String, Object> valueOperations(RedisTemplate<String, Object> redisTemplate) {
            return redisTemplate.opsForValue();
        }
    
        /**
         * 实例化 ListOperations 对象,可以使用 List 操作
         * @param redisTemplate
         * @return
         */
        @Bean
        public ListOperations<String, Object> listOperations(RedisTemplate<String, Object> redisTemplate) {
            return redisTemplate.opsForList();
        }
    
        /**
         * 实例化 SetOperations 对象,可以使用 Set 操作
         * @param redisTemplate
         * @return
         */
        @Bean
        public SetOperations<String, Object> setOperations(RedisTemplate<String, Object> redisTemplate) {
            return redisTemplate.opsForSet();
        }
        
        /**
         * 实例化 ZSetOperations 对象,可以使用 ZSet 操作
         * @param redisTemplate
         * @return
         */
        @Bean
        public ZSetOperations<String, Object> zSetOperations(RedisTemplate<String, Object> redisTemplate) {
            return redisTemplate.opsForZSet();
        }
    }

    redis发布订阅配置:

    @Configuration
    public class RedisPublishConfig {
    	@Autowired
    	private StaticProperties staticProperties;
    	/**
    	 * redis消息监听器容器 可以添加多个监听不同话题的redis监听器,只需要把消息监听器和相应的消息订阅处理器绑定,该消息监听器
    	 * 通过反射技术调用消息订阅处理器的相关方法进行一些业务处理
    	 * 
    	 * @param connectionFactory
    	 * @param listenerAdapter
    	 * @return
    	 */
    	@Bean // 相当于xml中的bean
    	RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory,
    			MessageListenerAdapter listenerAdapter) {
    		RedisMessageListenerContainer container = new RedisMessageListenerContainer();
    		container.setConnectionFactory(connectionFactory);
    		// 订阅了一个叫chat 的通道
    		container.addMessageListener(listenerAdapter, new PatternTopic(staticProperties.getWS_CHANNEL()));
    		// 这个container 可以添加多个 messageListener
    		return container;
    	}
    
    	/**
    	 * 消息监听器适配器,绑定消息处理器,利用反射技术调用消息处理器的业务方法
    	 * 
    	 * @param receiver
    	 * @return
    	 */
    	@Bean
    	MessageListenerAdapter listenerAdapter(RedisMsg receiver) {
    		// 这个地方 是给messageListenerAdapter 传入一个消息接受的处理器,利用反射的方法调用“receiveMessage”
    		// 也有好几个重载方法,这边默认调用处理器的方法 叫handleMessage 可以自己到源码里面看
    		return new MessageListenerAdapter(receiver, "receiveMessage");
    	}
    
    }

     定义接受信息的接口

    @Component
    public interface RedisMsg {
    	/**
    	 * 接受信息
    	 * @param message
    	 */
    	public void receiveMessage(String message);
    }
    

    websocket配置

    @Configuration
    @EnableWebSocket
    public class WebSocketConfig implements WebSocketConfigurer{
     
        @Override
        public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
            
            //handler是webSocket的核心,配置入口
            registry.addHandler(new CTIHandler(), "/webscoket/{ID}").setAllowedOrigins("*").addInterceptors(new WebSocketInterceptor());
            
        }
    
    }

     

    interceptor配置

    public class WebSocketInterceptor extends HttpSessionHandshakeInterceptor {
    	/**
    	 * 配置日志
    	 */
    	private final static Logger logger = LoggerFactory.getLogger(WebSocketInterceptor.class);
    
    	@Override
    	public boolean beforeHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse seHttpResponse,
    			WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
    //		HttpServletRequest request = ((ServletServerHttpRequest) serverHttpRequest).getServletRequest();
    		String userName = serverHttpRequest.getURI().toString().split("ID=")[1];
    		attributes.put("userName", userName);
    		logger.info("握手之前");
    		//从request里面获取对象,存放attributes
    		return super.beforeHandshake(serverHttpRequest, seHttpResponse, wsHandler, attributes);
    	}
    
    	@Override
    	public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
    			Exception ex) {
    		logger.info("握手之后");
    		super.afterHandshake(request, response, wsHandler, ex);
    	}
    }
    

    配置websocket的handler,并配置为redis的接受消息的实现类:

    @Service
    public class CTIHandler implements WebSocketHandler ,RedisMsg{
    	/**
    	 * 配置日志
    	 */
    	private final static Logger logger = LoggerFactory.getLogger(CTIHandler.class);
    	/**
    	 * concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
    	 */
        private static Map<String,WebSocketSession> socketMap = new HashMap<String, WebSocketSession>();
        //新增socket
        @Override
        public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        	logger.info("成功建立连接");
        	//获取用户信息
        	String userName = (String) session.getAttributes().get("userName");
        	logger.info("获取当前"+socketMap.get(userName));
        	if(socketMap.get(userName)==null) {
        		socketMap.put(userName,session);
        		sendMessageToUser(userName, new TextMessage("链接建立成功"));
        		//并且通过redis发布和订阅广播给其他的的机器,或者通过消息队列
        	}
        	logger.info("链接成功");
        }
    
        //接收socket信息
        @Override
        public void handleMessage(WebSocketSession webSocketSession, WebSocketMessage<?> webSocketMessage) throws Exception {
        	logger.info("收到信息"+webSocketMessage.toString());
        	String userName = (String) webSocketSession.getAttributes().get("userName");
        	webSocketSession.sendMessage(new TextMessage("aaa"));
        	sendMessageToUser(userName, new TextMessage("我收到你的信息了"));
        }
    
        /**
         * 发送信息给指定用户
         * @param clientId
         * @param message
         * @return
         */
        public boolean sendMessageToUser(String clientId, TextMessage message) {
        	WebSocketSession session = socketMap.get(clientId);
        	if(session==null) {
        		return false;
        	}
        	logger.info("进入发送消息");
        	if (!session.isOpen()) {
        		return false;
            }
        	try {
        		logger.info("正在发送消息");
    			session.sendMessage(message);
    		} catch (IOException e) {
    			e.printStackTrace();
    		}
            return true;
        }
    
    
        @Override
        public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
            if (session.isOpen()) {
                session.close();
            }
            logger.info("连接出错");
        }
    
        @Override
        public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        	//获取用户信息
        	String userName = (String) session.getAttributes().get("userName");
        	if(socketMap.get(userName)!=null) {
        		socketMap.remove(userName);
        		//并且通过redis发布和订阅广播给其他的的机器,或者通过消息队列
        	}
            logger.info("连接已关闭:" + status);
        }
    
        @Override
        public boolean supportsPartialMessages() {
            return false;
        }
        /**
         * 接受订阅信息
         */
    	@Override
    	public void receiveMessage(String message) {
    		// TODO Auto-generated method stub
    		JSONObject sendMsg = JSONObject.fromObject(message.substring(message.indexOf("{")));
    		String clientId = sendMsg.getString("userName");
    		TextMessage receiveMessage = new TextMessage(sendMsg.getString("message"));
    		boolean flag = sendMessageToUser(clientId, receiveMessage);
    		if(flag) {
    			logger.info("我发送消息成功了!");
    		}
    	}
    
    
    }

    配置文件配置

    spring:
      application:
        name: crm-cti
      #redis配置
      redis:
        host: 47.95.250.218
        password: zhudaxian;.,68NB
        port: 6379
        database: 0

    POM文件配置 

     <!-- websocket支持 -->
    	 <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
         </dependency>
         <!-- redis的支持 -->
         <dependency>
              <groupId>org.springframework.boot</groupId>
              <artifactId>spring-boot-starter-data-redis</artifactId>
          </dependency>

    已上就是大体的环境配置,业务接口发送消息

    	@Override
    	public String tjJTPush(String cno) {
    		// TODO Auto-generated method stub
    		JSONObject result = new JSONObject();
    		if(null==Pub_Tools.getString_TrimZeroLenAsNull(cno)) {
    			result.put("result", "error");
    		}else {
    			try {
    				Map<String, Object> userData = crmCallBindDao.findSalesMessage(cno);
    				//单节点实现方式,如果是单节点建议使用该方式,如果是分布式部署废弃该方式
    			    Boolean flag = ctiHanler.sendMessageToUser(userData.get("userName").toString(), new TextMessage("hangup"));
    			    if(!flag) {//发送失败广播出去,让其他节点发送
    					//广播消息到各个订阅者
    					JSONObject message = new JSONObject();
    					message.put("userName", userData.get("userName"));
    					message.put("message", "connect");
    					redisTemplate.convertAndSend(staticProperties.getWS_CHANNEL(),message.toString());
    			    }
    			} catch (Exception e) {
    				e.printStackTrace();
    				logger.error("推送给客户端失败");
    			}
    			result.put("result", "success");
    		}
    		return result.toString();
    	}

    大家如果有什么不明白的可以留言

    展开全文
  • 分布式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,推送消息。

    展开全文
  • 增加websocket的handler的实现 @ServerEndpoint(value = "/webscoket/call/{ID}") @Component public class CTIHandler extends ParentHandler implements CallBackService{ /** * 配置日志 */ private ...
  • 流程是首先前端跟后端应用新建一个连接,并携带当前登录的用户ID,此时WebSocket会创建一个WebsocketSession来唯一绑定该连接,我们会在后端用Map建立用户ID与Session的映射关系: Map<String userI...
  • 问题起因最近做项目时遇到了需要多用户之间通信的问题,涉及到了WebSocket握手请求,以及集群中WebSocket Session共享的问题。期间我经过了几天的研究,总结出了几个实现分布式WebSocket集群的办法,从zuul到spring ...
  • 1. http session支持分布式; 2. session 同时支持 cookie 和 header 传递; 3. websocket 连接 共享 http session。 对于第一个问题,很简单: implementation 'org.springframework.boot:spring-boot-starter...
  • 1.对面向接口设计的个人理解 1.1 接口的核心作用: 1.2 抽象类的核心作用: 1.3 如何进行抽象设计 ...将要解决的是一类问题,而非一个问题 对这一类问题,至少掌握两种及以上...import javax.websocket.Session; /*
  • 分布式WebSocket架构

    千次阅读 2018-10-22 21:03:14
    ClientServiceRedis订阅Topic发起WebSocket建立WebSocket连接,把用户相对应的Session存至内存。下单发送对应Topic的消息下单成功推送订阅此Top的消息根据消息体内容查找对应Session向其他用户推送下单成功提醒...
  • 最近做项目时遇到了需要多用户之间通信的问题,涉及到了WebSocket握手请求,以及集群中WebSocket Session共享的问题。 期间我经过了几天的研究,总结出了几个实现分布式WebSocket集群的办法,从zuul到spring cloud ...
  • 微信点餐的源代码,包含学习视频,内容有spring boot jpa websocket 分布式session Redis等等
  • 最近做项目时遇到了需要多用户之间通信的问题,涉及到了WebSocket握手请求,以及集群中WebSocket Session共享的问题。 期间我经过了几天的研究,总结出了几个实现分布式WebSocket集群的办法,从zuul到spring cloud g...
  • 故在分布式系统中,可以通过把websocketsession存储在服务器本地map,然后把消息发布到redis指定的频道上,每个服务器节点都订阅该频道,这样的话,消息一发布,每个节点都能接受到该消息,然后再从map中获取...
  • 这是我本来的打算,把socket session 进行序列化分布式存储! 呵呵 然而现实很残酷,这b东西不支持序列化! 解决办法: 转载于:https://www.cnblogs.com/java-synchronized/p/7495420.html...
  • 分布式WebSocket实现(通过RabbitMQ) 1、实现思路 1、WebSocket接收用户或者接口传过来的数据时,统一发送到RabbitMQ 2、每个服务器监听RabbitMQ数据并获取数据,通过判断数据中persons是否为空来判断是单发还是...
  • 微服务分布式多应用场景中,由于ws的session无法序列化到redis,所以集群中,无法将所有WebSocket Session都缓存到redis进行session共享。Session 都存储在各应用当中 解决办法-基于Redis 的发布订阅 redis 订阅配置...
  • 由于websocket是长连接,session保持在一个server中,所以在不同server在使用websocket推送消息时就需要获取对应的session进行推送,在分布式系统中就无法获取到所有session,这里就需要使用 ...
  • websocket核心是将握手成功的session信息存入到静态变量中,但是分布式中有很多个服务器,tomcat,使用nginx转发到不同的tomcat中,就不能保证此tomcat存储着握手成功的session信息,因此可以使用redis订阅功能,当...
  • 单点服务中使用websocket:我们每个client客户端都是与同一个service服务器进行连接,连接的session通过map存储在内存中,发送消息时,只需要在map中查找到所有需要发送的id对应的session进行发送即可。 分布式服务...
  • 实现了多种应用功能:微服务架构、基于zbus或motan的RPC框架、用户与权限管理、基于Redis的分布式session技术、基于zbus的消息框架、E-Mail后台发送技术、后台接口展示技术、分布式节点动态管理、自定义反向代理。...
  • Spring boot 集群使用websocket长连接通讯引言基础使用创建一个基础的spring boot引入websocket需要的依赖编写websocket服务代码编写websocket客户端代码测试通过集群使用解决分布式session共享集群连接数限制环境...
  • 本来想用websocket做一个消息推送 可是分布式环境下不支持session共享因为服务器不同 所以采用 rabbitMQ+webSocket实现分布式消息推送 生产者将消息 发送给 rabbitMQ 的 virtual-host:/(顶极路由) 再由它路由到...
  • 提供了 AOP、RPC、分布式缓存、限流、降级、熔断、统一配置中心、Opentracing 数据追踪、metrics 数据监控、分布式 session、代码生成器、shiro 和 jwt 安全控制、swagger api 自动生成等功能。Jboot v1.4.0 主要...
  • 在http下增加如下配置,确保nginx能处理正常的http请求,由于一般情况下开发人员在开发过程中会将websocket的会话状态session通过集合进行管理存储于内存中,则在多节点分布式的情况下,可使用IP_HASH负载策略进行...
  • 其中,可以学习到的技术干货非常多,包括:系统架构设计、Spring Boot2.X、缓存Redis、多线程并发编程、消息中间件RabbitMQ、全文搜索引擎Elastic Search、前后端消息实时通知WebSocket分布式任务调度中间件...
  • 1. Spring Session 简介

    万次阅读 2019-08-27 16:15:26
    Spring session 是用来解决分布式环境下的session共享问题的. 不仅支持HttpSession, 也支持WebSessionWebSocket. 不过笔者基本上没使用过WebSessionWebSocket, 所以笔者只介绍HttpSession 的用法. 1. Spring ...

空空如也

空空如也

1 2 3 4
收藏数 76
精华内容 30
关键字:

websocket分布式session