分布式解决方案------session一致性问题

前言

分布式session 问题造成的原因是因为集群造成的。
常说的解决方案有6种:

  • ip_hash: nginx 分发服务的时候,绑定ip,但这样就没有负载均衡效果了
  • tomcat 内置的session 同步,这个有延时,没有人用,很垃圾
  • 使用cookie 代替session,也没有人用,太不安全
  • 使用 数据库同步session ,效率太差了,也不用
  • spring -session 框架同步
  • 使用token 代替 session
    其中后两种是推荐用的,下面就简单说下

session 原理

先脑补下,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 负载均衡方位,发现每次返回的数据不一样。

spring - session框架

这个框架就是重写了 HttpSession 方法,从而把 session 信息放到redis 里边,这样每台服务器就不存在自己单独的session了,实现起来很简单。
添加maven依赖

<!--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>

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.

token 方式

这是最推荐的,很灵活
我们抛弃了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<String> value = (Set<String>) 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放到 请求头里边的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值