本篇继续学习如何整合非关系型数据库(NoSQL),主要是MongoDB的整合,顺便学习如何进行Session的共享。

MongoDB简介

MongoDB是一种面向文档的数据库管理系统,它是一个介于关系型数据库和非关系型数据库之间的产品,MongoDB功能丰富,它支持一种类似JSON的BSON数据格式,既可以存储简单的数据格式,也可以存储复杂的数据类型。MongoDB最大的特点是它支持的查询语言非常强大,且支持对数据建立索引。总的来说,MongoDB是一款应用相当广泛的NoSQL数据库。

MongoDB安装

目前MongoDB最新版为4.4.1,但是为避免一些问题,此处依然使用4.0.0版本,安装环境选择CentOS7,安装的步骤如下:
第一步,下载MongoDB。进入到home目录下执行如下命令下载MongoDB:

1
wget https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-4.0.0.tgz

下载完成后,将下载的MongoDB解压,并将解压后的文件夹重命名为mongodb,可执行如下命令:

1
2
tar -zxvf mongodb-linux-x86_64-4.0.0.tgz
mv mongodb-linux-x86_64-4.0.0 mongodb

第二步,配置MongoDB。进入到第一步中的mongodb目录下,创建两个文件夹db和logs,分别用来保存数据和日志,使用的命令如下:

1
2
3
cd monngodb
mkdir db
mkdir logs

接着进入到bin目录(mongodb目录自带的目录)下,新建一个新的MongoDB配置文件mongo.conf,里面的代码为:

1
2
3
4
dbpath=/home/mongodb/db
logpath=/home/mongodb/logs/mongodb.log
port=27017
fork=true

简单解释一下上述的配置信息:dbpath表示数据存储目录;logpath表示日志文件位置;port表示启动端口;fork=true表示以守护程序的方式启动MongoDB,也就是运行MongoDB在后台运行。
第三步,MongoDB的启动和关闭。配置完成后,还是在bin目录下,运行如下命令来启动MongoDB

1
./mongod -f mongo.conf --bind_ip_all

上述命令中的-f表示指定配置文件的位置,--bind_ip_all则表示允许所有的远程地址连接该MongoDB实例。MongoDB启动成功后,在bin目录下再执行mongo命令,即可进入到MongoDB的控制台,然后输入db.version(),如果能看到MongoDB的版本号,则表示MongoDB就已经安装成功:

默认情况下,启动后连接的是MongoDB中的test库,而关闭MongoDB的命令需要在admin库中执行,因此关闭MongoDB首先需要切换到admin库,然后执行db.shutdownServer();命令,完整的操作步骤为:

1
2
3
use admin;
db.shutdownServer();
exit

服务关闭后,执行exit退出MongoDB控制台,此时如果再执行./mongo命令就会执行失败,如下图所示:

第四步,MongoDB安全管理。默认情况下,启动的MongoDB是没有登录密码的,这在生产环境中是非常不安全的,但是不同于MySQL、Oracle等关系型数据库,MongoDB中每一个库都有独立的密码,在哪一个库中创建用户就需要在哪一个库中验证密码。要配置密码,首先需要创建一个用户,如在admin库中创建一个用户,代码如下(需要进入到MongoDB控制台下)

1
2
use admin;
db.createUser({user:"envy",pwd:"123",roles:[{role:"readWrite",db:"test"}]})

新创建的用户名为envy,密码为123,roles表示该用户具有的角色,这里的配置表示该用户对test库具有读和写两项权限:

用户创建成功后,关闭当前实例,然后重新启动,启动命令如下:

1
./mongod -f mongo.conf --auth --bind_ip_all

启动成功后,再次进入控制台,然后切换到admin库中验证登录(默认连接上的库是test库),验证成功后就可以对test库执行读写操作了,代码如下:

1
2
3
./mongo
use admin;
db.auth("envy","123");

如果db.auth("envy","123");命令执行结果为1,则表示认证成功,可以对test库执行读写操作了:

MongoDB整合SpringBoot

借助于SpringData MongoDB,SpringBoot 为MongoDB也提供了开箱即用的自动化配置方案,具体的配置步骤如下:

第一步,创建SpringBoot Web项目并添加依赖。使用spring Initializr构建工具构建一个SpringBoot的Web应用,名称为mongodbspringboot,然后在pom.xml文件中添加如下依赖:

1
2
3
4
5
6
7
8
9
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--mongodb依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>

第二步,配置MongoDB。application.properties文件中配置MongoDB的连接信息,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
# 验证信息登录库
spring.data.mongodb.authentication-database=admin
# 要连接的库,认证信息不一定要在连接的库中创建,因此这两个分开配置
spring.data.mongodb.database=test
# mongodb的主机IP
spring.data.mongodb.host=192.168.2.132
# mongodb的端口
spring.data.mongodb.port=27017
# mongodb的用户名
spring.data.mongodb.username=envy
# mongodb的密码
spring.data.mongodb.password=123

第三步,创建实体类。新建pojo包,并在其中新建Book类,里面的代码为:

1
2
3
4
5
6
public class Book {
private Integer id;
private String name;
private String author;
//getter、setter和toString方法
}

第四步,创建BookDao。新建一个dao包,并在其中新建一个BookDao接口,需要继承MongoRepository接口。其实BookDao的定义类似于Spring Data JPA中的Repository的定义,BookDao接口文件里中的代码为:

1
2
3
4
public interface BookDao extends MongoRepository<Book,Integer> {
List<Book> findByAuthorContains(String author);
Book findByNameEquals(String name);
}

MongoRepository接口中已经预定义了针对实体类的查询、添加、删除等操作,因此可以仿照Spring Data JPA的例子和方法命令规则来定义查询方法。

第五步,创建BookController。新建一个controller包,并在其中新建一个BookController类,处于简单考虑这里就不创建Service层,相应的代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@RestController
public class BookController {
@Autowired
private BookDao bookDao;
@GetMapping("/test")
public void test(){
List<Book> lists = new ArrayList<>();
Book book1 =new Book();
book1.setId(1);
book1.setName("西游记");
book1.setAuthor("吴承恩");
lists.add(book1);
Book book2 =new Book();
book2.setId(2);
book2.setName("彷徨");
book2.setAuthor("鲁迅");
lists.add(book2);
//插入集合中的数据
bookDao.insert(lists);
//查询作者名字中包含“鲁迅”的所有书
List<Book> books1 = bookDao.findByAuthorContains("鲁迅");
System.out.println(books1);
//查询书名为“西游记”的图书信息
Book books2 = bookDao.findByNameEquals("西游记");
System.out.println(books2);
}
}

第六步,测试。运行项目,在浏览器地址栏中输入http://localhost:8080/test,可以看到控制台打印日志信息:

1
2
[Book{id=2, name='彷徨', author='鲁迅'}]
Book{id=1, name='西游记', author='吴承恩'}

此时可以登录到MongoDB服务器,认证身份后,在test库中即可查询到刚刚插入的数据,如下图所示:

第七步,使用MongoTemplate。在dao 接口文件中除了继承MongoRepository接口外,Spring Data MongoDB还提供了MongoTemplate用来更加方便地操作MongoDB。在SpringBoot中,若添加了MongoDB相关的依赖,而开发者并没有提供MongoTemplate时,此时SpringBoot默认会有一个MongoTemplate注解到Spring容器中,相关配置源码可以在MongoDataAutoConfiguration类中。因此用户可以直接使用MongoTemplate,只需要在controller中直接注入MongoTemplate就可以使用了。可以将第五步的BookController文件修改为如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@RestController
public class BookController {
@Autowired
private MongoTemplate mongoTemplate;
@GetMapping("/test")
public void test(){
List<Book> lists = new ArrayList<>();
Book book1 =new Book();
book1.setId(3);
book1.setName("三国演义");
book1.setAuthor("罗贯中");
lists.add(book1);
Book book2 =new Book();
book2.setId(4);
book2.setName("且介亭文集");
book2.setAuthor("鲁迅");
lists.add(book2);
//插入集合中的数据
mongoTemplate.insertAll(lists);
//查询集合中所有的数据
List<Book> books1 = mongoTemplate.findAll(Book.class);
System.out.println(books1);
//根据书的id来进行查询
Book books2 = mongoTemplate.findById(3,Book.class);
System.out.println(books2);
}
}

运行项目,在浏览器地址栏中输入http://localhost:8080/test,可以看到控制台打印日志信息:

1
2
[Book{id=1, name='西游记', author='吴承恩'}, Book{id=2, name='彷徨', author='鲁迅'}, Book{id=3, name='三国演义', author='罗贯中'}, Book{id=4, name='且介亭文集', author='鲁迅'}]
Book{id=3, name='三国演义', author='罗贯中'}

当然也可以去MongoDB服务器上查看数据是否已经保存成功:

那么这样关于SpringBoot整合MongoDB的内容就学习完了。

Session共享

正常情况下,HTTPSession是通过Servlet容器创建并进行管理的,创建成功之后都是保存在内存中。如果开发者需要对项目进行横向扩展搭建集群,那么可以利用一些硬件或者软件工具来作负载均衡,此时来自同一用户的HTTP请求就有可能被分发到不同的实例上去,如何保证各个实例之间Session的同步就成为一个必须解决的问题。SpringBoot提供了自动化的Session共享配置,它结合Redis可以非常方便地解决这个问题。使用Redis解决Session共享问题的原理非常简单,就是把原本存储在不同服务器上的Session拿出来放在一个独立的服务器上,如下图所示:

当一个请求到达Nginx服务器后,首先进行请求分发,假设请求被图中的real server1处理了,real server1在处理请求时,无论是存储Session还是读取Session,都去操作Session服务器而不是操作自身内存中的Session,其他real server在处理请求时也是如此,这样就可以实现Session共享了。

Session共享配置

SpringBoot中的Session共享配置非常容易,按照下面的步骤进行即可:

第一步,新建项目并添加Redis和Session依赖。使用spring Initializr构建工具构建一个SpringBoot的Web应用,名称为sessionspringboot,然后在pom.xml文件中添加如下依赖:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--添加data-redis依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<exclusions>
<exclusion>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--添加Jedis依赖-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<!--session依赖-->
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>

除了Redis依赖外,这里还需要提供spring-session-data-redis依赖,SpringSession可以做到透明化地替换掉应用的Session容器。
第二步,配置数据库连接信息。项目创建成功后,在application.properties配置文章中进行Redis的基本连接信息配置,相应的代码为:

1
2
3
4
5
6
7
8
9
10
spring.redis.database=0
spring.redis.host=47.100.175.12
spring.redis.port=6379
spring.redis.password=123@456
spring.redis.jedis.pool.max-active=8
spring.redis.jedis.pool.max-idle=8
spring.redis.jedis.pool.max-wait=-1ms
spring.redis.jedis.pool.min-idle=0

server.port=8080

第三步,创建controller并进行测试。新建controller包,并在其中新建HelloController类,里面的代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@RestController
public class HelloController {
@Value("${server.port}")
private String port;

@PostMapping("/save")
public String saveName(String name, HttpSession session){
session.setAttribute("name",name);
return port;
}

@GetMapping("/get")
public String getName(HttpSession session){
return port+ ":"+session.getAttribute("name").toString();
}
}

这里提供了两个方法,一个save接口用来向Session中存储数据,还有一个get接口用于从Session中获取数据,这里注入了项目启动的端口号server.port,主要是为了区分到底是哪个服务器提供的服务。另外,虽然还是HttpSession,但实际上HttpSession容器已经被透明替换,真正的Session此时储存在Redis服务器上。

项目创建完成以后,将项目打包成jar包上传到CentOS上,然后依次执行如下两条命令来启动项目:

1
2
nohup java -jar sessionspringboot-0.0.1-SNAPSHOT.jar --server.port=8080 &
nohup java -jar sessionspringboot-0.0.1-SNAPSHOT.jar --server.port=8081 &

nohup表示不挂断程序运行,即当终端窗口关闭后,程序依然在后台运行,最后的&表示让程序在后台运行。--server.port表示设置启动端口,一个为8080,一个为8081。启动成功后,接下来就可以配置负载均衡器了。

Nginx负载均衡

本例子使用Nginx做负载均衡,因此首先需要在CentOS上安装Nginx,安装过程如下:

第一步,下载源码并解压。使用如下命令下砸nginx源码并解压:

1
2
wget https://nginx.org/download/nginx-1.14.2.tar.gz
tar -zxvf nginx-1.14.2.tar.gz

然后进入解压目录中执行编译安装,使用的命令如下:

1
2
3
4
cd nginx-1.14.2
./configure
make
make install

安装成功后,找到Nginx的安装目录,执行sbin目录下的nginx文件启动nginx,使用的命令如下:

1
/usr/local/nginx/sbin/nginx

Nginx启动成功后,默认端口是80,可以在物理机直接进行访问(服务器需要在安全组中开放80端口访问权限):

接下来进入到Nginx安装目录使用vi /usr/local/nginx/conf/nginx.conf命令来修改其配置文件nginx.conf,需要编辑以下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
......
......
upstream envyzhan.com {
server 47.100.175.12:8080 weight=1;
server 47.100.175.12:8081 weight=1;
}
server {
listen 80;
server_name localhost;
location / {
proxy_pass http://envyzhan.com;
proxy_redirect default;
}

注意这里仅仅只列出了修改的配置,在修改的配置中首先配置上游服务器,即两个real server,两个real server的权重都是1,意味2这请求将平均分配到两个real server上,然后在server中配置拦截规则,将拦截到的请求转发到定义好的real server上。

配置完成后,启动Nginx,重启命令如下:

1
/usr/local/nginx/sbin/nginx -s reload

请求转发

当real server和Nginx都启动后,调用/save接口来储存数据,打开postman测试工具,以post方式提交并输入以下信息http://47.100.175.12:80/save?name=余思,可以发现后台输出8080。此处调用的端口是80,也就是调用的是Nginx服务器,请求会被Nginx转发到real server上进行处理,返回值为8080,说明真正处理请求的real server是8080那台服务器。

接下来调用get接口获取数据,打开postman测试工具,以get方式提交并输入以下信息http://47.100.175.12:80/get,可以发现后台输出8081:余思。调用的端口依然是80,也就是调用的是Nginx服务器,但是返回值是8081,说明是8081那台real server提供的服务,如果这里不是8081,再访问一次即可。

经过如上步骤就完成了利用Redis实现Session共享的功能,基本上不需要额外的配置,开箱即用。

整合NoSQL数据库小结

在第九篇中介绍了SpringBoot如何整合Redis,而在第十篇中介绍了如何整合MongoDB,以及结合Redis实现了Session共享。对应NoSQL数据库,此处仅仅介绍了较为常见的两种:Redis和MongoDB。MongoDB在一些场景中甚至可以完全替代关系型数据库,而Redis更多的使用场景则是作为缓存服务器,这一点在后续将会学习到,开发者可以根据自己实际情况选择合适的NoSQL。