分布式session解决方案

1、session的作用?

   服务器为每个用户创建一个会话,存储用户的相关信息,以便多次请求能够定位到同一个上下文。当用户在应用程序的 Web 页之间跳转时,存储在 Session 对象中的变量将不会丢失,而是在 整个用户会话中一直存在下去。当用户请求来自应用程序的 Web 页时,如果该用户还没有会话,则Web服务器将自动创建一个 Session 对象。当会话过期或被放弃后,服务器将终止该会话。

2、分布式session是什么?

   单服务器web应用中,session只会存储在该服务器中,而随着日益增长的用户量,单服务节点无法应对大量用户的     访问,我们开始考虑适用服务器集群,例如采用nginx做负载均衡反向代理,那么就会出现用户每次访问都会轮训到     不同的web应用服务器节点,从而导致获取不到Session的情况,这就是我们谈到的分布式session一致性问题。

如下案例

首先我们创建一个项目名称为distributed-session的springboot项目如下

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>cn.itchao</groupId>
	<artifactId>distributed-session</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.0.0.RELEASE</version>
	</parent>

	<dependencies>
		<!--SpringBoot WEB组件  -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<!--lombok插件 -->
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
		</dependency>
		<!--common-lang3工具包  -->
		<dependency>
			<groupId>org.apache.commons</groupId>
			<artifactId>commons-lang3</artifactId>
		</dependency>
	</dependencies>
</project>

书写如下接口

package cn.itchao.controller;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import lombok.extern.slf4j.Slf4j;

@RestController
@RequestMapping("/user")
@Slf4j
public class UserController {
	
	@Value("${server.port}")
	private String port;

	@RequestMapping("/createSession")
	public String createSession(HttpServletRequest request,String name){
		HttpSession session = request.getSession();
		session.setAttribute("name", name);
		log.info("port:"+port+",sessonId:"+session.getId());
		return "创建Session成功-对应的服务器端口号为:"+port;
	}
	
	@RequestMapping("/getSession")
	public String getSession(HttpServletRequest request){
		HttpSession session = request.getSession(false);
		if(session==null){
			return "没有获取到Session"+"-服务器端口号为:"+port;
		}
		log.info("port:"+port+",sessonId:"+session.getId());
		String name = (String) session.getAttribute("name");
		return "获取Session成功:"+name+"-对应的服务器端口号为:"+port;
	}
}

启动类:

package cn.itchao;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SessionApp {

	public static void main(String[] args) throws Exception {
		SpringApplication.run(SessionApp.class, args);
	}

}

application.yml配置如下:

server:
  port: 8080
server:
  port: 8081

分别启动两台服务器,本地ip为192.168.100.1。即现在启动了两台服务分别是

192.168.100.1:8080和192.168.100.1:8081

接下来我们配置nginx反向代理负载均衡,我的nginx服务器是在一台ip为192.168.100.131的虚拟机上安装的,本机ip为192.168.100.1,所以我在本地配置了一个域名指向这台服务器的ip。如下:

接下里我在ip为192.168.100.131的虚拟机配置了反向代理如下:

接下来我们创建第一个session:

http://www.itchao.cn/user/createSession?name=user1

发现在8081服务器上创建了session

接着我们在创建第二个session

http://www.itchao.cn/user/createSession?name=user2

发现这时候轮训到8080服务器了

接下来我们来获取这个session

http://www.itchao.cn/user/getSession

发现轮训到8081服务器上没有获取到对应的session

我们再来获取一次Session

发现在8080服务器上获取到了刚才创建session,这是为什么呢?

原因很简单由于这段代码没有设置创建session的时候为false

requset.getSession(false)代表获取session,如果没有不创建,而默认requset.getSession()等同与requset.getSession(true)代表每次获取session如果获取不到创建一个新的session,故此我们在刚才轮训到8081服务器上的时候新建了一个session,把我们设置的name=user1给覆盖掉了,所以我们无法获取到。

每次创建的session,和下一次访问的可能不在一台服务器上,所以不能获取达到session共享从而导致session一致性问题。

那我们应该如何解决这个问题呢?

1、通过数据库存储session信息,每次从数据查询。这种方式会导致数据库压力巨大。不适用

2、通过客户端Cookie写入到本地,这样很不安全,不可采取

3、通过nginx的hash一致性做ip绑定,这样失去了灵活性

4、使用tomcat内部通信机制同步数据通信,这样开销较大,不适合大型项目

5、使用spring-session框架来解决,底层采用重写httpclient保证数据共享

6、使用token令牌代替session功能,把数据存放在redis中,每次从redis中获取数据

接下来我们说说如何使用spring-session框架解决分布式session一致性问题:

首先引入一下依赖

<!-- 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>
<dependency>
	<groupId>redis.clients</groupId>
	<artifactId>jedis</artifactId>
</dependency>

编写如下代码

package config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.connection.RedisPassword;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;

public class SessionConfig {
	// 冒号后的值为没有配置文件时,自动动装载的默认值
	@Value("${redis.hostname:localhost}")
	String host;
	@Value("${redis.port:6379}")
	int port;
	@Value("${redis.password:123456}")
	String password;

	@Bean
	public JedisConnectionFactory connectionFactory() {
		RedisStandaloneConfiguration standaloneConfig = new RedisStandaloneConfiguration();
		standaloneConfig.setHostName(host);
		standaloneConfig.setPort(port);
		standaloneConfig.setPassword(RedisPassword.of(password));
		return new JedisConnectionFactory(standaloneConfig);

	}
}
package cn.itchao.session;

import org.springframework.session.web.context.AbstractHttpSessionApplicationInitializer;

import config.SessionConfig;

//初始化Session配置
public class SessionInitializer extends AbstractHttpSessionApplicationInitializer {
	public SessionInitializer() {
		super(SessionConfig.class);
	}
}

配置文件中添加如下配置:

server:
  port: 8080
spring:
  redis:
    database: 0
    host: 127.0.0.1
    port: 6379
    password: 123456
    jedis:
      pool:
        max-active: 8
        max-wait: -1
        max-idle: 8
        min-idle: 8
      timeout: 0
  session:  
    store-type: redis

需要依赖redis,请安装redis

接下来我们再次启动8080和8081服务器,测试发现

完美解决session共享问题。

利用token令牌和redis共享分布式集群方式替换Session功能。大概思路原理如下:

用户登录后,生成一个唯一令牌,令牌作为redis的key,用户信息作为key对应的value存入redis中,给设置过期时间为30分钟,然后把令牌返回给客户端,客户端保存令牌到本地Cookie中,以后用在获取用户信息的时候直接发送一个请求到后端,在请求头部传入令牌,后端通过request.getHeader("Authorization");获取到令牌,然后用令牌去redis查询对应得用户信息,如果没有查询到那么代表用户Session失效或者用户未登陆。如果查询到即返回用户信息。

  • 6
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值