【Distributed】分布式Session一致性问题

一、概述

1.1 什么是Session

  Session 是客户端与服务器通讯会话技术, 比如浏览器登陆、记录整个浏览会话信息 

1.2 Session实现原理

  客户对向服务器端发送请求后,Session 创建在服务器端,返回Sessionid给客户端浏览器保存在本地,当下次发送请求的时候,在请求头中传递sessionId获取对应的从服务器上获取对应的Sesison

1.3 Session常见问题

Session 保证在那里?

  答案:存放在服务器上
  

关闭浏览器Session会失效吗

  答案:不会消失
  

服务器集群之后,Session产生的问题

  
  如果服务器产生了集群后,因为session是存放在服务器上,客户端会使用同一个Sessionid在多个不同的服务器上获取对应的Session,从而会导致Session不一致问题。
  

1.4 Nginx

Nginx配置负载均衡

Nginx负载均衡提供上游服务器(真实业务逻辑访问的服务器),负载均衡、故障转移、失败重试、容错、健康检查等。
当上游服务器(真实业务逻辑访问的服务器)发生故障时,可以转移到其他上游服务器(真实业务逻辑访问的服务器)。

Upstream Server配置

upstream 主要配置如下:
IP地址和端口号:配置上游服务器的IP地址和端口
    ###定义上游服务器(需要被nginx真实代理访问的服务器) 默认是轮训机制
    upstream  backServer{
        server 127.0.0.1:8080;
        server 127.0.0.1:8081;
    }
    
server {
    listen       80;
    server_name  www.itmayiedu.com;
    location / {
        ### 指定上游服务器负载均衡服务器
        proxy_pass http://backServer;
        index  index.html index.htm;
    }
}

负载均衡算法

  • 1、轮询(默认),每个请求按时间顺序逐一分配到不同的后端服务,如果后端某台服务器死机,自动剔除故障系统,使用户访问不受影响。
  • 2、weight(轮询权值),weight的值越大分配到的访问概率越高,主要用于后端每台服务器性能不均衡的情况下。或者仅仅为在主从的情况下设置不同的权值,达到合理有效的地利用主机资源。
    • 3、ip_hash,每个请求按访问IP的哈希结果分配,使来自同一个IP的访客固定访问一台后端服务器,并且可以有效解决动态网页存在的session共享问题。俗称IP绑定。
    • 4、fair(第三方),比 weight、ip_hash更加智能的负载均衡算法,fair算法可以根据页面大小和加载时间长短智能地进行负载均衡,也就是根据后端服务器的响应时间 来分配请求,响应时间短的优先分配。Nginx本身不支持fair,如果需要这种调度算法,则必须安装upstream_fair模块。
    • 5、url_hash(第三方),按访问的URL的哈希结果来分配请求,使每个URL定向到一台后端服务器,可以进一步提高后端缓存服务器的效率。Nginx本身不支持url_hash,如果需要这种调度算法,则必须安装Nginx的hash软件包。

二、Session 相关代码演示

2.1 Controller

 @SpringBootApplication
    @RestController
    public class TestSessionController {

    // 创建session 会话
    @RequestMapping("/createSession")
    public String createSession(HttpServletRequest request, String nameValue) {
        HttpSession session = request.getSession();
        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();
        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);
    }
}

2.2 TestSessionController

@SpringBootApplication
@RestController
public class TestSessionController {
@Value("${server.port}")
private String serverPort;

@RequestMapping("/")
public String index() {
    return serverPort;
}

// 创建session 会话
@RequestMapping("/createSession")
public String createSession(HttpServletRequest request, String nameValue) {
    HttpSession session = request.getSession();
    System.out.println(
            "存入Session  sessionid:信息" + session.getId() + ",nameValue:" + nameValue + ",serverPort:" + serverPort);
    session.setAttribute("name", nameValue);
    return "success-" + serverPort;
}

// 获取session 会话
@RequestMapping("/getSession")
public Object getSession(HttpServletRequest request) {
    HttpSession session = request.getSession(false);
    if (session == null) {
        return serverPort + "-" + "没有找到对应的session值";
    }
    System.out.println("获取Session sessionid:信息" + session.getId() + "serverPort:" + serverPort);
    Object value = session.getAttribute("name");
    return serverPort + "-" + value;
}

public static void main(String[] args) {
    SpringApplication.run(TestSessionController.class, args);
}
}

三、分布式Session一致性解决方案

3.1 nginx或者haproxy实现IP绑定

  • 用Nginx 做的负载均衡可以添加ip_hash这个配置,用haproxy做的负载均衡可以用 balance source这个配置。从而使同一个ip的请求发到同一台服务器。

3.2 利用数据库同步session

3.3 使用Session集群存放Redis

  使用spring-session框架,底层实现原理是重写httpsession

引入maven依赖

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

YML配置信息

server:
    port: 8080
    redis:
     hostname: 192.168.212.151
     port: 6379
     password: 123456  
    
启动redis /usr/local/redis/bin/redis-server /usr/local/redis/etc/redis.conf 

创建SessionConfig

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;
    }
}

初始化Session

//初始化Session配置
public class SessionInitializer extends AbstractHttpSessionApplicationInitializer{
    public SessionInitializer() {
        super(SessionConfig.class);
    }
}

3.4 最靠谱的分布式Session解决方案

基于令牌(Token)方式实现Session解决方案,因为Session本身就是分布式共享连接。

Service

@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;
    }
}

  

上一篇:CF457B. Distributed Join


下一篇:ECS与EDAS什么意思?