文章内容输出来源:拉勾教育Java高薪训练营
Http是无状态的,为了保持用户的信息,就需要通过Cookie或者Session来存储会话信息。如用户登录成功后,在Session中存储用户信息,此用户后续的操作就不用再重复登录。
但当在集群环境中,同一个站点部署在多台服务器上,就需要实现Session共享。不然用户登录时,请求指向A服务器实例,在Session中存储了会话信息。但下一次请求可能指向了B服务器实例,B服务器拿不到用户之前登录的信息,又要再登录一次。
方案说明
同一IP的请求都路由到同一台服务器上,即会话粘滞
优点
缺点
推荐使用
方案说明
多个Tomcat之间修改配置文件达到Session复制的目的。这是早期处理的一种方案
优点
缺点
不推荐使用的方案
方案说明
Session的本质是缓存,将它交给专业的缓存中间件(如Redis来处理)
优点
缺点
推荐使用。Spring Session使得基于Redis的Session共享应⽤起来⾮常之简单
开发一个简单的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;
cluster_demo
t_resume
的数据访问,并实现了简历的增、删、改、查功能(具体详见项目代码)@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>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
<dependency>
<groupId>org.springframework.sessiongroupId>
<artifactId>spring-session-data-redisartifactId>
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
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>
sh bin/startup.sh
在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;
}
}
}
www.yyh.com
与本机的映射关系127.0.0.1 www.yyh.com
nginx -s reload
在浏览器上访问http://www.yyh.com
查看实现效果
从上面可以看到服务器端口在8080和8081顺序变化着,但是当前SessionID的值没有变化,则实现了Session的共享