您的位置:首页 > 运维架构 > Docker

分布式Session研究(一):Docker + spring boot +Nginx构建分布式应用

2017-01-11 16:33 1006 查看
由于自己一直痴迷于大型分布式系统的设计原理与实践。奈何条件有限,毕竟还在读书,根本无法接触到真正的分布式,真正的大数据。便只能在自己电脑上通过docker这种虚拟化技术来自己搭建”分布式系统”来玩玩,体验一下分布式Session,分布式事物等等。

这篇文章将搭建出一个”分布式”系统,并先体验分布式系统中Session管理的问题,并通过集中Session管理方案解决

构建两个SpringBoot镜像,提供Session服务。

先使用SpringBoot搭建构建一个Restful 接口,并做一些很简单的Session操作。

Maven:

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.4.0.RELEASE</version>
<relativePath></relativePath>
</parent>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>


然后写一个比较简单的Controller:

@SpringBootApplication
@RestController
public class App{

@RequestMapping("/setSession")
public String home(HttpServletRequest request) {
request.getSession().setAttribute("user","jack");
return "I am One .You session has k-v user-wang!!"+request.getSession().getAttribute("user");
}

@RequestMapping("/getSession")
public String session(HttpServletRequest request){
return "I am One .You session is :"+request.getSession().getAttribute("user");
}

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

}


现在一个SpringBoot项目就写完了。它只提供了两个Rest接口。一个设置Session值,一个获取Session值。注意一点,上面代码中返回值中有个One的标识。现在我们将其Docker化:

先通过mvn package将其打成jar包,命名为dockerOne.jar

编写Dockerfile

生成镜像并运行

Dockerfile:

FROM registry.cn-hangzhou.aliyuncs.com/alirobot/oraclejdk8-nscd #基础源
VOLUME /tmp #挂载
add dockerOne.jar app.jar #传递jar包
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]
#运行命令


然后运行 sudo docker build -t dockerone/jdk8 . 构建第一个镜像。

按上面步骤构建第二个镜像:代码中one改为two。镜像名称也改为dockertwo/jdk8。

启动上面两个镜像,通过inspect查看ip为192.168.110.2 192.168.110.3 (docker网桥为192.168.110.1)

构建Nginx并搭建负载均衡

docker pull 下载一个nginx后。我们在本机上写一个nginx的配置文件,然后通过 -v 已数据卷的形式挂载到docker镜像中。

#使用轮询法做负载均衡,权重都是1,这样请求可以随机分配。
upstream webservers{
server 192.168.110.3:8080 weight=1;
server 192.168.110.2:8080 weight=1;
}

server {
listen       80;
server_name  localhost;

#charset koi8-r;
#access_log  /var/log/nginx/log/host.access.log  main;

location =/index.html{
root /data;
}
location / {
proxy_pass  http://webservers; }
}


启动Nginx并查看ip为 192.168.110.4

访问192.168.110.4并观察浏览器携带的cookie及Session的返回值

我们知道,第一次访问网站的时候(也就是该用户还没有创建Session的时候),服务器的response会携带一个set-cookie的值随机值。用来表示该用户的Session。当该用户下次访问网站则会在request中携带一个cookie的头信息,带入该cookie。这样服务器就能在内存中找到该用户的唯一Session。

首先请求/setSession

1.请求分配到two服务器: 没有携带cookie值,服务器符合 E5DD28A3B255AEB9B95C2C5304342685

2.请求分配到one服务器: 携带cookie E5DD28A3B255AEB9B95C2C5304342685 却返回set-cookie 75965AEBC91A57B509BDE095602B9F97

3.请求到two 发送 75965AEBC91A57B509BDE095602B9F97 却返回60BC939E156DBB21676BCC02EC0C7E2A

4.请求到one 发送 60BC939E156DBB21676BCC02EC0C7E2A 却返回E090DA9B2ABA7B7D08915DF78EDFA31E

5.请求到two 发送 E090DA9B2ABA7B7D08915DF78EDFA31E 却返回96BA8FCD5305A75ECC0684F9D541EBDE

现在请求 /getSession

1.请求分配到one 携带cookie 96BA8FCD5305A75ECC0684F9D541EBDE

返回值为 I am One .You session is :null

返回set cookie值为95833D8765EB2818B220CA9AC53DA72C

2.请求分配到two 携带cookie 95833D8765EB2818B220CA9AC53DA72C

返回值为I am Two .You session is : null

返回set cookie值 6BFB1D62A6B3925A1774CC51C3B21893

现在我们应该清楚了分布式系统中Session的问题了吧,就是不清楚用户的请求会落地在那一台服务器上。而对于Session是默认仅保存在你请求落地的那台服务器上的,对于下一次的请求,如果落地在非上一个请求落地的服务器上,则没有Session信息。

分布式Session的解决方法之Session集中式管理

Session集中式管理就是将所以用户的Session统一到一台中央服务器上(也有可能这个中央服务器也搭建成分布式缓存集群)。

对于我们上述项目,现在已经有三台服务器了。一台Nginx负载均衡服务器。两台提供Rest接口的服务器。现在再加一台Session缓存服务器。让两台Rest接口服务器把Session全部交给Session缓存服务器来保持。

具体的实现我们使用Spring-Session,Session缓存保存于Redis中

首先启动一个redis的Dcoker镜像。查看ip为192.168.110.5

Maven中添加到Redis的依赖:

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.4.0.RELEASE</version>
<relativePath></relativePath>
</parent>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session</artifactId>
<version>1.2.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
<version>1.2.2.RELEASE</version>
<type>pom</type>
</dependency>
</dependencies>


SpringBoot中的配置:

@SpringBootApplication
@RestController
@EnableRedisHttpSession
public class App{

@RequestMapping("/")
public String home(HttpServletRequest request) {
request.getSession().setAttribute("user","xi");
return "I am Two .You session has k-v user-wang!!"+request.getSession().getAttribute("user");
}

@RequestMapping("/getSession")
public String session(HttpServletRequest request){
return "I am Two .You session is :"+request.getSession().getAttribute("user");
}

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

#缓存服务器的配置
@Bean
public JedisConnectionFactory connectionFactory() {
JedisConnectionFactory redisConnectionFactory = new JedisConnectionFactory();
redisConnectionFactory.setUsePool(false);
redisConnectionFactory.setHostName("192.168.110.2");
redisConnectionFactory.setPort(6379);
return redisConnectionFactory;
}

}


其实可以在properties配置文件中加入如下配置;

spring.redis.host=192.168.110.5
spring.redis.port=6379


@EnableRedisHttpSession这个注解就是最重要的东西,加了它之后,spring生产一个新的拦截器,用来实现Session共享的操作,具体实现这里暂不展开。而配置的这个JedisConnectionFactory,则是让Spring根据配置文件中的配置连到Redis。

现在继续之前的步骤,发生Session问题即可解决。

因为无论最后请求落地到哪一台服务器,根据Session共享服务器只返回一个Session给浏览器,而浏览器也永远只携带了一个cookie值

此时我们查看Redis数据库:

dev@dev:~$ redis-cli  -h 192.168.110.5
192.168.110.5:6379> keys *
1) "spring:session:sessions:96b04dd3-4045-435f-ac2b-935c2af5eb52"
3) "spring:session:expirations:1484127300000"
4) "spring:session:sessions:expires:96b04dd3-4045-435f-ac2b-935c2af5eb52"
xxxx


其中一个为Session失效时间,一个则是前面我们说的cookie值。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: