文章内容输出来源:拉勾教育Java高薪训练营
为什么要实现Session共享
Http是无状态的,为了保持用户的信息,就需要通过Cookie或者Session来存储会话信息。如用户登录成功后,在Session中存储用户信息,此用户后续的操作就不用再重复登录。
但当在集群环境中,同一个站点部署在多台服务器上,就需要实现Session共享。不然用户登录时,请求指向A服务器实例,在Session中存储了会话信息。但下一次请求可能指向了B服务器实例,B服务器拿不到用户之前登录的信息,又要再登录一次。
实现方案
1. Nginx的IP_Hash策略
-
方案说明
同一IP的请求都路由到同一台服务器上,即会话粘滞 -
优点
- 配置简单
- 无代码侵入
-
缺点
- 服务器重启会导致Session丢失
- 存在单点故障风险
推荐使用
2. Tomcat的Session复制
-
方案说明
多个Tomcat之间修改配置文件达到Session复制的目的。这是早期处理的一种方案 -
优点
- 服务器重启不会导致Session丢失
- 无代码侵入
- 方便服务器的水平扩展
-
缺点
- 性能低。存储的数据越多,性能越低下
- 消耗内存
- 复制存在延迟性
不推荐使用的方案
3. Session集中存储
-
方案说明
Session的本质是缓存,将它交给专业的缓存中间件(如Redis来处理) -
优点
- 服务器重启不会导致Session丢失
- 扩展能力强
- 适合大集群环境的使用
-
缺点
- 代码侵入性,需要在代码中实现与Redis的交互
推荐使用。Spring Session使得基于Redis的Session共享应⽤起来⾮常之简单
SpringSession使用示例
开发一个简单的SpringBoot项目,实现了登录以及简历增、删、改、查的功能。页面直接使用JSP进行实现。
在本机上配置Nginx以及Tomcat模仿集群环境。配置两个Tomcat实例,将上述的项目打包为war包,部署在两个Tomcat实例中。
数据库脚本
CREATE DATABASE db_test DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
CREATE TABLE `tb_resume`(
`id` int(10) NOT NULL AUTO_INCREMENT COMMENT '用户ID',
`name` varchar(200) NOT NULL COMMENT '名称',
`address` varchar(200) COMMENT '地址',
`phone` varchar(200) COMMENT '手机',
PRIMARY KEY(`id`)
)ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
项目实现步骤
- 创建SpringBoot项目
cluster_demo
- 实现了简历表
t_resume
的数据访问,并实现了简历的增、删、改、查功能(具体详见项目代码) - 实现简单的登录功能
- 登录后端请求,实现登录页面跳转以及登录请求,登录请求中直接判断用户名和密码为admin即登录成功
@Controller
public class LoginController {
@GetMapping("/login")
public String loginPage(HttpServletRequest request) {
Object loginUser = request.getSession().getAttribute("loginUser");
if(null != loginUser) {
return "redirect:resumes/list";
}
return "login";
}
@PostMapping("/login")
@ResponseBody
public Res login(HttpServletRequest request, @RequestBody LoginVO loginVO) {
if(null == loginVO.getName() || loginVO.getName().trim().length() == 0) {
return Res.fail("用户名不能为空");
}
if(null == loginVO.getPassword() || loginVO.getPassword().trim().length() == 0) {
return Res.fail("密码不能为空");
}
if(!loginVO.getName().equals("admin") || !loginVO.getPassword().equals("admin")) {
return Res.fail("用户名或密码错误");
}
//登录成功后,在session中存储用户信息
request.getSession().setAttribute("loginUser", "admin");
return Res.ok("登录成功");
}
}
- 登录拦截器的配置,判断非登录页面的请求,判断用户是否登录,没有登录则跳转到登录页面
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
Object loginUser = request.getSession().getAttribute("loginUser");
if(null != loginUser) {
return true;
}
response.sendRedirect("/login");
return false;
}
}
@Configuration
public class LoginAppConfigurer extends WebMvcConfigurerAdapter{
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/**").excludePathPatterns("/login/**","/error");
super.addInterceptors(registry);
}
}
- 登录表单的页面以及登录请求的交互实现
<html>
<body>
<h2>用户登录</h2>
<div>
<div>
<b>用户名:</b><input id="name" type="text" placeholder="请输入用户名"/>
</div>
<div>
<b>密码:</b><input id="password" type="password" placeholder="请输入用户密码"/>
</div>
<div>
<input id="btnLogin" type="button" value="登录"/>
</div>
</div>
</body>
<script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
<script type="text/javascript">
$('#btnLogin').on('click', function() {
var name = $('#name').val().trim();
var password = $('#password').val().trim();
if(name.length == 0) {
alert('请输入用户名');
return;
}
if(password.length == 0) {
alert('请输入密码');
return;
}
var params = {
'name': name,
'password': password
};
$.ajax({
type: 'post',
url: 'login',
data: JSON.stringify(params),
contentType: 'application/json;charset=utf-8',
dataType: 'json',
success: function(result) {
if(result.success) {
window.location.href = 'resumes/list';
}else {
alert(result.data);
}
}
});
});
</script>
</html>
- 开始实现Session共享功能。引入Redis以及SpringSession的依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
- 项目配置文件
application.properties
中配置下redis的配置信息
spring.redis.database=0
spring.redis.host=127.0.0.1
spring.redis.port=6379
- 在
ClusterApplication
中添加上EnableRedisHttpSession
注解,实现Session共享的配置
@SpringBootApplication
@EnableCaching
@EnableRedisHttpSession
public class ClusterApplication extends SpringBootServletInitializer {}
- 在
login.jsp
中增加输出端口号以及SessionID
<div>
<div>
<p>服务器端口:${pageContext.request.localPort}</p>
<p>当前SessionID:${pageContext.session.id}</p>
</div>
</div>
- 使用
mvn clean package
打包项目为demo.war
Tomcat配置步骤
-
创建两个Tomcat实例,分别配置为8080和8081端口
image.png
-
将上述打包的
demo.war
分别放到两个tomcat的webapps目录下 -
修改每个Tomcat实例的
server.xml
配置
- 在
Server
和Connector
修改访问端口为8080或8081(默认为8080端口的话则不用再修改)
<Server port="8006" shutdown="SHUTDOWN">
<Connector port="8081"/>
</Server>
- 在
Host
标签上增加Context
子标签,指定相应项目的路径以及访问路径。这里配置如下:
<Host name="localhost" appBase="webapps"
deployOnStartup="false" unpackWARs="true" autoDeploy="true">
<Context docBase="demo.war" path="/" />
</Host>
- 启动tomcat,确认项目访问成功
sh bin/startup.sh
Nginx配置步骤
在Nginx上配置负载均衡策略
- 修改
nginx.conf
配置文件,配置反向代理。此处是监听80端口,域名是www.yyh.com。配置内容如下:
http{
upstream cluster-server {
server 127.0.0.1:8080;
server 127.0.0.1:8081;
}
server {
listen 80;
server_name www.yyh.com;
access_log /logs/nginx/cluster.access.log;
location / {
proxy_pass http://cluster-server;
}
}
}
- 修改电脑的hosts文件,配置上
www.yyh.com
与本机的映射关系
127.0.0.1 www.yyh.com
- Nginx执行热加载
nginx -s reload
演示效果
在浏览器上访问http://www.yyh.com
查看实现效果
- Nginx转发请求给了
tomcat-8080
在这里插入图片描述
- Nginx转发请求给了
tomcat-8081
在这里插入图片描述
从上面可以看到服务器端口在8080和8081顺序变化着,但是当前SessionID的值没有变化,则实现了Session的共享
- 输入
admin/admin
进行登录,登录成功后跳转到简历列表页面
在这里插入图片描述
网友评论