websocket 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 "%r" %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 #复数加上-
websocket session共享的更多相关文章
- [Spring] spring-session + JedisPool 实现 session 共享
1.至少导入四个jar包: jedis spring-session spring-data-redis commons-pool2 2.bean配置 <?xml version="1 ...
- Nginx+Tomcat搭建集群,Spring Session+Redis实现Session共享
小伙伴们好久不见!最近略忙,博客写的有点少,嗯,要加把劲.OK,今天给大家带来一个JavaWeb中常用的架构搭建,即Nginx+Tomcat搭建服务集群,然后通过Spring Session+Redi ...
- Nginx反向代理,负载均衡,redis session共享,keepalived高可用
相关知识自行搜索,直接上干货... 使用的资源: nginx主服务器一台,nginx备服务器一台,使用keepalived进行宕机切换. tomcat服务器两台,由nginx进行反向代理和负载均衡,此 ...
- 分布式中使用Redis实现Session共享(二)
上一篇介绍了一些redis的安装及使用步骤,本篇开始将介绍redis的实际应用场景,先从最常见的session开始,刚好也重新学习一遍session的实现原理.在阅读之前假设你已经会使用nginx+i ...
- windows 环境下nginx + tomcat群 + redis 实现session共享
nginx作为负载均衡根据定义将不同的用户请求分发到不同的服务器,同时也解决了因单点部署服务器故障导致的整个应用不能访问的问题 在加入nginx之后,如果多个服务器中的一个或多个(不是全部)发生故障, ...
- shiro实现session共享
session共享:在多应用系统中,如果使用了负载均衡,用户的请求会被分发到不同的应用中,A应用中的session数据在B应用中是获取不到的,就会带来共享的问题. 假设:用户第一次访问,连接的A服务器 ...
- Redis安装及实现session共享
一.Redis介绍 1.redis是key-value的存储系统,属于非关系型数据库 2.特点:支持数据持久化,可以让数据在内存中保存到磁盘里(memcached:数据存在内存里,如果服务重启,数据会 ...
- Tomcat7基于Redis的Session共享实战二
目前,为了使web能适应大规模的访问,需要实现应用的集群部署.集群最有效的方案就是负载均衡,而实现负载均衡用户每一个请求都有可能被分配到不固定的服务器上,这样我们首先要解决session的统一来保证无 ...
- Nginx+Tomcat+Redis实现负载均衡、资源分离、session共享
Nginx+Tomcat+Redis实现负载均衡.资源分离.session共享 CentOS安装Nginx http://centoscn.com/CentosServer/www/2013/0910 ...
随机推荐
- STM32按键输入
下面3个接上拉电阻 WK_UP接上拉电阻 因为用到了PA,PC,PH所以要使能3个模块 STATIC静态变量只会初始化一次 每次调用flag++,不会再初始化为0:起记忆作用. 最关键的是头 件不要忘 ...
- 单元测试系列之九:Sonar 常用代码规则整理(一)
更多原创测试技术文章同步更新到微信公众号 :三国测,敬请扫码关注个人的微信号,感谢! 摘要:公司部署了一套sonar,经过一段时间运行,发现有一些问题出现频率很高,因此有必要将这些问题进行整理总结和分 ...
- list,tuple,dict,set的增删改查
数据结构 list tuple dict set 增 append insert d['key']=value add 删 pop pop(0) d.pop('name') pop re ...
- eclipse 下修改Dynamic Web Modulle 的问题
上图右侧圈中位置 有提示对应jdk版本. 若在eclipse修改Dynamic Web Modulle 为3.0失败,可以去项目工作空间文件中的.seting文件下修改: 上图对应的 <ins ...
- VBoxManage安装
扩展包的版本需要与VirtualBox的版本一致,通过帮助可以查看VirtualBox的版本信息,然后在http://download.virtualbox.org/virtualbox/寻找对应的版 ...
- 使用JSR-303进行校验
package com.ieou.comac.module.web.dto.employee; import lombok.Data; import javax.validation.constrai ...
- Redis(四)-持久化
1.Redis将所有数据存储在内存中,从内存同步到磁盘上,就做持久化过程. 2.持久化有两种方式:rdb(Redis Database)和aof(Append of file) # rdb持久化方法: ...
- Go语言学习之15 商品秒杀开发与接入层实现
outline 1. 秒杀抢购接入层实现2. 秒杀逻辑层实现 秒杀接入层核心功能 秒杀逻辑层核心功能 SecKill接口 /seckill?product=20&source=android& ...
- 《R语言入门与实践》第六章:R 的环境系统
前言 这一章在对象的基础之上,讲解了对象所处的环境,进一步讲了环境对对象的作用,以及如何使用环境.结构如下: 环境的定义和操作 环境的规则 制作闭包 环境 R 环境的定义 在 R 中,每一个数据对象都 ...
- VBA正则笔记 理解肯定环视
之前没有理解好,还以为是学习笔记有谬误. 'VBA正则笔记 肯定环视 Public Sub RegExHandle() Dim Regex As Object Dim Mh As Object, On ...