分布式session一致性问题
1,什么是session
session 是客户端与服务器通讯会话技术,比如用户登陆,会将登陆之后,将用户信息存入在session中
2,session 的原理
3,简单的session 例子
@RestController public class IndexController { @RequestMapping("/createSession") public String createSession(HttpServletRequest request, String username) { HttpSession session = request.getSession(); session.setAttribute("username", username); return "success"; } @RequestMapping("/getSession") public String getSession(HttpServletRequest request) { HttpSession session = request.getSession(); String username = (String) session.getAttribute("username"); System.out.println("username: " + username); String id = session.getId(); System.out.println("sessionid " + id); return "username: " + username + "=====" + "sessionid: " + id; } @RequestMapping("/baseDecode") private String base64Decode(String base64Value) { try { byte[] decodedCookieBytes = Base64.getDecoder().decode(base64Value); return new String(decodedCookieBytes); } catch (Exception e) { return null; } } }
服务器端创建了一个session,sessionid 是 cdd51f6c-e2ab-490b-a4f8-291f184913b3 通过 base64 加密之后(Y2RkNTFmNmMtZTJhYi00OTBiLWE0ZjgtMjkxZjE4NDkxM2Iz)传给客户端,存入到http request headers ,以便下一次请求
getSession 的时候也是将Y2RkNTFmNmMtZTJhYi00OTBiLWE0ZjgtMjkxZjE4NDkxM2Iz 在请求头中传入,然后服务器解密,找到对应的session
4,分布式session 一致性问题
在分布式集群环境中,sessionid 是存在客户端中,session 是存在服务器上,因为是分布式集群,所以会存在客户端的sessionid 和 session 不一致。
5,分布式session一致性问题的解决方案
解决方案1:nginx:ip_hash 负载均衡算法设置
详解:通过nginx负载均衡算法中的 ip_hash ,也就是ip 绑定方式,让每个客户端的和服务器进行了绑定,A 客户端访问了1号服务器,后面A 客户端发起的请求,都会分发到1号服务器了。
缺点:没有了负载均衡
# 配置上游服务器
upstream backServer{
server 127.0.0.1:8080;
server 127.0.0.1:8082;
ip_hash;
}
server {
listen 80;
server_name www.baiyue.com;
location / {
proxy_pass http://backServer;
index index.html index.htm;
}
}
解决方案2:使用spring-session框架,底层实现原理是重写httpsession,推荐
<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> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> </dependency>
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; @Value("${redis.password}") String password; @Bean public JedisConnectionFactory connectionFactory() { JedisConnectionFactory connection = new JedisConnectionFactory(); connection.setPort(Port); connection.setHostName(HostName); connection.setPassword(password); return connection; } }
import org.springframework.session.web.context.AbstractHttpSessionApplicationInitializer; //初始化Session配置 public class SessionInitializer extends AbstractHttpSessionApplicationInitializer{ public SessionInitializer() { super(SessionConfig.class); } }
application.yml
server:
port: 8080
spring:
redis:
database: 0
host: 192.168.178.110
port: 6379
password: 123
jedis:
pool:
max-active: 8
max-wait: -1
max-idle: 8
min-idle: 0
timeout: 10000
redis:
hostname: 192.168.178.110
port: 6379
password: 123
启动8080 和 8081 模拟集群,8080 创建的session,8081 也可以获取到了,实现了session 的共享,实际上是将session 存到了redis 上
解决方案3:基于Token(令牌)方式,推荐
其实就是通过就是token缓存在redis 上,因为redis 在服务器集群的时候 分布式缓存可以共享
每次都是通过token 来存,再通过token 来获取
@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(); } } TokenController @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; } }