分布式session 问题造成的原因是因为集群造成的。
常说的解决方案有6种:
先脑补下,session 的用法,session 位于服务器端,我们把会话信息放到 里边,通过session.setAtribute()方法使用,这个session 就是session 对象,每个请求对应一个session,服务器有一个session 集合,然后通过sessionId 区别每个session 对象,每次request 的时候,服务器会将sessionId返给客户端,当下次请求的时候,客户端会将sessionId 放到请求头里边,然后根据id 向服务器验证session 是否存在。一台服务器还好,如果是集群,这样就不行了,因为每台服务器的session 集合不一样,这就是 session 一致性问题,比如反复要求输入验证码,反复要求登录的,就是session 不一致导致的。
小例子说下:
我开了两个服务,8080 和8090 端口,然后查看请求的时候创建session,返回sessionId,如果sessionId相同,就是共享的,否则,就是Sesion不一致
@ResponseBody
@RequestMapping(value = "/session",method = RequestMethod.GET)
public String getSessionId(HttpServletRequest request){
HttpSession session = request.getSession();
System.out.println(" sessionid is"+session.getId());
return "8080 端口:"+session.getId();
}
@ResponseBody
@RequestMapping(value = "/session",method = RequestMethod.GET)
public String getSessionId(HttpServletRequest request){
HttpSession session = request.getSession();
System.out.println(" sessionid is"+session.getId());
return "8090端口:"+session.getId();
}
然后nginx 配置好,通过nginx 负载均衡方位,发现每次返回的数据不一样。
这个框架就是重写了 HttpSession 方法,从而把 session 信息放到redis 里边,这样每台服务器就不存在自己单独的session了,实现起来很简单。
添加maven依赖
org.springframework.session
spring-session-data-redis
org.apache.commons
commons-pool2
redis.clients
jedis
redis 配置类:
//这个类用配置redis服务器的连接
//maxInactiveIntervalInSeconds为SpringSession的过期时间(单位:秒)
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 1800)
public class RedisSessionConfig {
// 冒号后的值为没有配置文件时,制动装载的默认值
@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;
}
}
核心类,重写session方法
public class SessionInitializer extends AbstractHttpSessionApplicationInitializer {
public SessionInitializer() {
super(RedisSessionConfig.class);
}
}
这样就好,每次返回的sessionId一样,是一个共享session.
这是最推荐的,很灵活
我们抛弃了session,感觉和spring-session 的原理是一样的,都是放到redis,只不过这里放的是自己定义的。
配置文件添加
## redis相关
# Redis数据库索引(默认为0)
spring.redis.database=1
# Redis服务器地址
spring.redis.host=127.0.0.1
# Redis服务器连接端口
spring.redis.port=6379
# Redis服务器连接密码(默认为空)
spring.redis.password=
redis服务类:
@Service
public class RedisService {
@Autowired
private StringRedisTemplate stringRedisTemplate;
// public void set(String key, Object object, Long time) {
// stringRedisTemplate.opsForValue();
// // 存放String 类型
// if (object instanceof String) {
// setString(key, object);
// }
// // 存放 set类型
// if (object instanceof Set) {
// setSet(key, object);
// }
// // 设置有效期 以秒为单位
// stringRedisTemplate.expire(key, time, TimeUnit.SECONDS);
// }
// redis 在服务器集群的时候 分布式缓存可以共享
public void setString(String key, Object object) {
// 开启事务权限
// stringRedisTemplate.setEnableTransactionSupport(true);
try {
// 开启事务 begin
// stringRedisTemplate.multi();
String value = (String) object;
stringRedisTemplate.opsForValue().set(key, value);
System.out.println("存入完毕,马上开始提交redis事务");
// 提交事务
// stringRedisTemplate.exec();
} catch (Exception e) {
// 需要回滚事务
// stringRedisTemplate.discard();
}
}
public void setSet(String key, Object object) {
Set value = (Set) object;
for (String oj : value) {
stringRedisTemplate.opsForSet().add(key, oj);
}
}
public String getString(String key) {
return stringRedisTemplate.opsForValue().get(key);
}
token 类
@Service
public class TokenService {
@Autowired
private RedisService redisService;
// 新增 返回token
public String put(String 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();
}
}
controller
@RestController
@RequestMapping("/token")
public class TokenController {
@Autowired
private TokenService tokenService;
@Value("${server.port}")
private String serverPort;
@RequestMapping("/put")
public String put(String nameValue) {
System.out.println("进入put方法"+nameValue);
String token = tokenService.put(nameValue);
return token + "-" + serverPort;
}
@RequestMapping("/get")
public String get(String token) {
System.out.println("进入get方法");
String value = tokenService.get(token);
return value + "-" + serverPort;
}
}
我就手动先获取token 了,然后将token作为参数去请求,实际生产中是吧token放到 请求头里边的。