一、什么是分布式session问题
a、什么是session
session是一种会话技术,我们知道http是无状态协议的,就是这次连接传输数据后,下次连接服务器是不知道这次的请求是谁的,因此我们要做一个标记,让服务器知道每次请求是哪个(客户端)浏览器发出的,就是请求的时候服务器会创建一个session把session的值保存在服务器,把sessionID返回给浏览器,请求的时候把sessionID放在请求头中,这样服务器解析之后就能发现是哪个浏览器发来的请求
b、session存放在什么地方,会引起什么问题
session是存在服务器的,只是把sessionID返回给浏览器。这样我们把浏览器关掉,session也不会实现,但是只是丢失了sessionID,这样也是访问不到的。
c、session的工作原理
session是由服务器创建的,存放在服务器中,把sessionID返回给浏览器,请求的时候,每次请求把sessionID就到请求头中,服务器解析以后就知道是哪个浏览器
e、分布式session是什么
我们知道session是保存在服务器的,这样当我们的项目做了负载均衡以后,如果在session中存了数据,那么就有可能有有些项目取不到session中的数据,这就是分布式session问题
二、服务做了集群一般是怎么样做的
如果是一般的springboot项目,那么只要改下端口号启动几个,然后用nginx统一做反向代理。
springcloud微服务项目,那么这个时候,可以使用ribbon本地负载均衡。
nginx负载均衡和ribbon做负载均衡的区别:
nginx做负载均衡是服务器端的负载均衡,统一访问一个地址,根据负载均衡算法访问决定访问那一个服务器。
ribbon负载均衡,这是本地负载均衡(也可以说是客户端负载均衡),把提供服务的看客户端地址都缓存记录下来,根据本地的算法实现负载均衡。
三、分布式session解决办法
使用springsession的原理:把项目中的session统一存在redis中,所有的参与集群的项目都在redis中取,这样就不不会出现明明在session中设了值但取不到值的情况。
maven依赖
<groupId>com.junlaninfo</groupId> <artifactId>solveSession</artifactId> <version>1.0-SNAPSHOT</version> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.0.RELEASE</version> <relativePath /> <!-- lookup parent from repository --> </parent> <properties> <weixin-java-mp.version>2.8.0</weixin-java-mp.version> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <maven.compiler.encoding>UTF-8</maven.compiler.encoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.locales>zh_CN</project.build.locales> </properties> <dependencies> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <!-- <exclusions> <exclusion> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> </exclusion> </exclusions> --> </dependency> <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson --> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.47</version> </dependency> <!-- Testing Dependencies --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <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>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> </dependency> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <maimClass>com.meiteedu.WxMpApplication</maimClass> </configuration> <executions> <execution> <goals> <goal>repackage</goal> </goals> </execution> </executions> </plugin> </plugins> </build>properties 文件
server.port=8080 redis.hostname=127.0.0.1 redis.port=6379 redis.password=redis配置
package com.junlaninfo.config; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession; //这个类用配置redis服务器的连接 //maxInactiveIntervalInSeconds为SpringSession的过期时间(单位:秒) @EnableRedisHttpSession(maxInactiveIntervalInSeconds = 1800) public class SessionConfig { // 冒号后的值为没有配置文件时,制动装载的默认值 @Value("${redis.hostname:localhost}") String HostName; @Value("${redis.port:6379}") int Port; @Bean public JedisConnectionFactory connectionFactory() { JedisConnectionFactory connection = new JedisConnectionFactory(); connection.setPort(Port); connection.setHostName(HostName); return connection; } } package com.junlaninfo.config; import org.springframework.session.web.context.AbstractHttpSessionApplicationInitializer; //初始化Session配置 public class SessionInitializer extends AbstractHttpSessionApplicationInitializer { public SessionInitializer() { super(SessionConfig.class); } }项目启动
package com.junlaninfo; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @SpringBootApplication @RestController public class TestSessionController { // 创建session 会话 @RequestMapping("/createSession") public String createSession(HttpServletRequest request, String nameValue) { HttpSession session = request.getSession(false); String id = session.getId(); System.out.println("存入Session sessionid:信息" + session.getId() + ",nameValue:" + nameValue); session.setAttribute("name", nameValue); return "success"; } // 获取session 会话 @RequestMapping("/getSession") public Object getSession(HttpServletRequest request) { HttpSession session = request.getSession(false); String id = session.getId(); System.out.println("获取Session sessionid:信息" + session.getId()); Object value = session.getAttribute("name"); return value; } public static void main(String[] args) { SpringApplication.run(TestSessionController.class, args); } }
只要加入springsession的依赖,配置好redis,那么就可以解决分布式session一致性的问题,不用在原来的项目中做任何改变
使用nginx做负载均衡配置
启动项目,分别用8080和8081启动,这样就可以看到解决分布式session问题的效果
创建session的注意点:
HttpSession session = request.getSession(false);这里可以传入一个布尔类型的变量,ture的话表示没有这个sessionID对应的session那么就新创建一个。
如果是false,那么没有这个sessionID对应的session,那么就直接返回失败,不会新创建session
这里我们需要提一个问题,项目中使用到了session,那么即使使用springsession解决分布式session一致性的问题,只要你换了一个浏览器操作,那么同样因为sessionID丢失的问题,也同样获取不到值,为了彻底解决分布式session的问题,那么我们可以用token代替session
@Service
public class TokenService {
@Autowired
private RedisService redisService;
// 新增 返回token
public String put(Object object) {
String token = getToken();
redisService.setString(token, object);
return token;
}
// 获取信息
public String get(String token) {
String reuslt = redisService.getString(token);
return reuslt;
}
public String getToken() {
return UUID.randomUUID().toString();
}
}
@RestController
public class TokenController {
@Autowired
private TokenService tokenService;
@Value("${server.port}")
private String serverPort;
@RequestMapping("/put")
public String put(String nameValue) {
String token = tokenService.put(nameValue);
return token + "-" + serverPort;
}
@RequestMapping("/get")
public String get(String token) {
String value = tokenService.get(token);
return value + "-" + serverPort;
}
}
代码:https://github.com/xuexionghui/solveSession.git