分布式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;
    }

}

 

posted @ 2019-08-12 13:53  Chris,Cai  阅读(432)  评论(0编辑  收藏  举报