在前面学习了SpringBoot如何整合关系型数据库,接下来开始学习如何整合非关系型数据库(NoSQL),本篇主要介绍Redis的整合,具体包括Redis介绍、Redis集群搭建,SpringBoot整合单机版Redis和SpringBoot整合Redis集群等。

非关系型数据库

非关系型数据库(NoSQL)和关系型数据库两者存在许多的显著不同点,其中最重要的是NoSQL不使用SQL作为查询语言。其数据存储可以不需要固定的表格模式,一般都有水平可扩展的特征。NoSQL主要有如下几种不同的分类:

  • Key/Value键值存储。这种数据存储通常都是无数据结构的,一般被当作字符串或者二进制数据,但是数据加载速度快,典型的使用场景就是处理高并发或者用于日志系统等,这一类的数据库有Redis、Tokyo Cabinet等。
  • 列存储数据库。列存储数据库功能相对局限,但是查找速度快,容易进行分布式扩展,一般用于分布式文件系统中,这一类的数据库有HBase、Cassandra等。
  • 文档型数据库。和Key/Value键值存储类似,文档型数据库也没有严格的数据格式,这既是缺点也是优点,因为不需要预先创建表结构,数据格式更加灵活,一般可用在Web应用中,这一类的数据库有MongoDB、CouchDB等。
  • 图形数据库。图形数据库专注于构建关系图谱,例如社交网络,推荐系统等,这一类的数据库有Neo4J、MongoDB等。

NoSQL种类繁多,SpringBoot对大多数NoSQL都提供了配置支持,本篇主要介绍两个常见的:Redis和MongoDB。

Redis相关介绍

Redis简介

Redis是一个使用C编写的基于内存的NoSQL数据库,它是目前最流行的键值对存储数据库。Redis由一个Key、Value映射的字典构成,与其他NoSQL不同的是,Redis中的Value类型不局限于字符串,还支持列表、集合、有序集合、散列等。Redis不仅可以当做缓存使用,也可以配置数据持久化后当做NoSQL数据库使用,目前支持两种持久化方式:快照持久化和AOF持久化。另一方面,Redis也可以搭建集群或者主从复制结构,在高并发环境下具有高可用性。

Redis安装

笔者在学习时,Redis最新版本为6.0.1,但是考虑到一些版本的依赖,依旧采用较为流行的Redis4.0版本,安装环境选择CentOS7。其中安装步骤如下:

第一步,下载Redis。启动虚拟机,进入home目录,并在终端输入以下命令下载Redis:

1
wget http://download.redis.io/releases/redis-4.0.10.tar.gz

若提示未找到wget命令,则先执行如下命令安装wget,再下载Redis:

1
yum -y install wget

第二步,安装Redis。首先解压下载的文件,然后进入解压目录中进行编译,请按照顺序执行如下4条命令:

1
2
3
4
tar -zxvf redis-4.0.10.tar.gz
cd redis-4.0.10
make MALLOC=libc
make install

若在执行make MALLOC=libc命令时提示gcc:未找到命令,则先执行如下命令安装gcc,等安装成功后再进行编译安装:

1
yum -y install gcc

请注意Redis新版本的安装方式和之前有所不同,可以点击 这里进行查看。也可以参看这篇文章:Linux下Redis的安装和部署

第三步,配置Redis。Redis安装成功后,接下来进行配置,打开Redis解压目录下的redis.conf文件,主要修改如下几个地方(请注意这是修改后的配置情况):

1
2
3
4
daemonize yes
#bind 127.0.0.1
requirepass 123@456
protected-mode no

简单解释一下上述配置的含义:daemonize设置为yes,表示运行Redis在后台启动;第二行表示允许连接该Redis实例的地址,默认情况下只允许本地连接,因此需要将默认配置注释掉,这样外网就可以连接Redis了。requirepass表示设置了登录该Redis实例所需的密码。由于有了第三行配置的密码登录,因此protected-mode表示的保护模式就可以关闭了。

第四步,配置CentOS防火墙。为了远程能连接上Redis,还需要关闭该虚拟机的防护墙,执行命令如下:

1
2
systemctl stop firewalld.service
systemctl disable firewalld.service

第一行表示关闭防火墙,第二行表示禁止防火墙开机自启动。最后可以使用firewall-cmd --state命令检测一下防火墙是否已经关闭,当输出not running字样时表示防火墙已经成功被关闭。

第五步,Redis启动与关闭。
使用 redis-server redis.conf命令来启动Redis。Redis启动成功后,再执行如下命令进入Redis的控制台,其中-a表示Redis的登录密码选项:

1
redis-cli -a 123@456

进入控制台后执行ping命令,若能看到输出结果为PONG,则表明Redis安装成功,如下图所示:

如果你想关闭Redis实例,可以在Redis命令行终端内执行SHUTDOWN停止Redis的运行,然后使用exit退出Redis命令行终端。当然也可以直接在CentOS终端下执行如下命令直接停止Redis服务:

1
redis-cli -p 6379 -a 123@456 shutdown

其中-p表示要关闭的Redis实例的端口,-a表示Redis的登录密码。

这样单机版的Redis就安装完成并启动成功了,为什么说是单机版,那是因为这里只有一个Redis,后续会介绍Redis集群。

SpringBoot整合Redis单机版

Redis的Java客户端有很多,如Jedis、JRedis、Spring Data Redis等,SpringBoot借助于Spring Data Redis为Redis提供了开箱即用的自动化配置,开发者只需要添加相关依赖并配置Redis连接信息即可,具体的整合步骤如下:

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

1
2
3
4
5
6
7
8
9
10
<!--web配置依赖-->
<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>
</dependency>

默认情况下spring-boot-starter-data-redis中使用的Redis工具是Lettuce,考虑到有的开发者习惯使用Jedis,因此可以从spring-boot-starter-data-redis中排除Lettuce并引入Jedis,只需将上述依赖修改如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!--web配置依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--data-redis依赖中移除Lettuce,使用Jedis-->
<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>

第二步,配置Redis。application.properties文件中添加Redis的链接信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 基本连接信息配置
# 使用Redis库的编号,Redis中提供了16个database,编号从0-15
spring.redis.database=0
# Redis实例的IP地址
spring.redis.host=192.168.2.132
# Redis端口号,默认6379
spring.redis.port=6379
# Redis登录密码
spring.redis.password=123@456

# 连接池基本信息配置
# Redis连接池的最大连接数
spring.redis.jedis.pool.max-active=8
# Redis连接池的最大空闲连接数
spring.redis.jedis.pool.max-idle=8
# Redis连接池的最大阻塞等待时间,默认为-1,表示没有限制
spring.redis.jedis.pool.max-wait=-1ms
# Redis连接池的最小空闲连接数
spring.redis.jedis.pool.min-idle=0

请注意如果项目使用了Lettuce,只需将连接池的基本信息中的jedis修改为lettuce即可,而前面的基本连接信息配置不用修改。

在前面整合关系型数据库中,开发者之所以可以轻松实现零配置,是因为SpringBoot在用户未提供对应的配置类,如JdbcOperations缺失情况下会自动提供JdbcTemplate,那么SpringBoot对于Redis的零配置是否也是类似的支持呢?可以发现的确如此,在SpringBoot的自动配置类中提供了RedisAutoConfiguration类用于进行Redis的配置,这里贴出对应的源码:

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
@Configuration
@ConditionalOnClass({RedisOperations.class})
@EnableConfigurationProperties({RedisProperties.class})
@Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
public class RedisAutoConfiguration {
public RedisAutoConfiguration() {
}

@Bean
@ConditionalOnMissingBean(
name = {"redisTemplate"}
)
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
RedisTemplate<Object, Object> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}

@Bean
@ConditionalOnMissingBean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}

从源码中可以看出,application.properties文件中配置的信息将被注入到RedisProperties中,如果开发者自己没有提供RedisTemplate或者StringRedisTemplate实例,则SpringBoot默认会提供这两个实例,RedisTemplateStringRedisTemplate实例则提供了Redis的基本操作方法。

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

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

请注意必须让这个实体类实现Serializable这个序列化接口,记住实现了Serializable接口的类可以被ObjectOutputStream转换为字节流,同时也可以通过ObjectInputStream再将其解析为对象,这样便于数据的传输。

第四步,创建Controller类。新建controller包,并在其中新建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
@RestController
public class BookController {
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private StringRedisTemplate stringRedisTemplate;

@GetMapping("/test")
public void test(){
ValueOperations<String,String> ops1 = stringRedisTemplate.opsForValue();
ops1.set("name","红楼梦");
String name = ops1.get("name");
System.out.println(name);

ValueOperations ops2 = redisTemplate.opsForValue();
Book book = new Book();
book.setId(1);
book.setName("西游记");
book.setAuthor("吴承恩");
ops2.set("book",book);
Book result = (Book) ops2.get("book");
System.out.println(result);
}
}

解释一下上述代码的含义:

  • StringRedisTemplateRedisTemplate的子类,StringRedisTemplate中的key和value都是字符串,采用的序列化方案是StringRedisSerializer,而RedisTemplate则可以用来操作对象,RedisTemplate采用的序列化方案是JdkSerializationRedisSerializer。无论是StringRedisTemplate还是RedisTemplate,它们操作Redis的方法都是一致的。
  • StringRedisTemplateRedisTemplate都是通过opsForValueopsForZSet或者opsForSet等方法首先获取一个操作对象,再使用该操作对象来完成数据的读写。
  • ops.set()方法用于向Redis中存储一条记录,而ops.get()方法则是将其读取出来。

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

1
2
红楼梦
Book{id=1, name='西游记', author='吴承恩'}

还可以去Redis数据库中查找相关信息:

这样就实现了单机版Redis整合SpringBoot的目标。

Redis集群

前面介绍的都是单个Redis实例整合SpringBoot,在实际项目中,开发者为了提高Redis的扩展性,往往需要搭建Redis集群,这样就会涉及到Redis集群整合SpringBoot,接下来就来学习Redis集群的搭建和SpringBoot如何整合Redis集群。

Redis集群搭建

(1)集群原理

在Redis集群中,所有的Redis节点彼此互联,节点内部使用二进制协议优化传输速度和带宽。当一个节点挂掉后,集群中超过半数的节点检测失效时才认为该节点已失效。不同于Tomcat集群需要使用反向代理服务器,Redis集群中的任意节点都可以直接和Java客户端连接。Redis集群上的数据分配则是采用哈希槽(HASH SLOT),Redis集群中内置了16384个哈希槽,当有数据需要存储时,Redis会首先使用CRC16算法对key进行计算,将计算获得的结果对16384取余,这样每一个key都会对应一个取值在0~16383之间的哈希槽,Redis则根据这个余数将该条数据存储到对应的Redis节点上,开发者可根据每个Redis实例的性能来调整每个Redis实例上哈希槽的分布范围。

(2)集群规划

本案例在同一台服务器上使用不同的端口表示不同的Redis服务器(伪分布式集群)。其中主节点有:47.100.175.12:8001,47.100.175.12:8002,47.100.175.12:8003。从节点有:47.100.175.12:8004,47.100.175.12:8005,47.100.175.12:8006。

(3)集群配置

Redis集群管理工具redis-trib.rb依赖Ruby环境,首先需要安装Ruby环境,由于CentOS7 yum库中默认的Ruby版本较低,因此建议采用如下步骤进行安装。

首先安装RVM,RVM是一个命令行工具,可以提供一个便捷的多版本Ruby环境的管理和切换,安装命令如下:

1
2
3
gpg2 --keyserver hkp://keys.gnupg.net --recv-keys D39DC0E3
curl -L get.rvm.io | bash -s stable
source /usr/local/rvm/scripts/rvm

如果在执行过程中出现下面的错误,那是因为某些原因导致GitHub的raw.githubusercontent.com域名被屏蔽了。

1
curl: (7) Failed connect to raw.githubusercontent.com:443; Connection refused

正确的做法是首先点击 这里,然后在搜索框内填写raw.githubusercontent.com域名后开始搜索:

介绍使用vim /etc/hosts,在里面添加一条DNS解析记录:

1
199.232.68.133  raw.githubusercontent.com

然后再重新执行上述命令即可。source /usr/local/rvm/scripts/rvm命令表示使RVM生效,RVM安装成功后,可以使用下面的命令来查看RVM中有哪些Ruby:

1
rvm list known

选择最新的稳定版本2.7.0进行安装,使用的命令为:

1
rvm install 2.7.0

可以使用ruby -v命令来检查Ruby是否安装成功。最后使用如下命令来安装Redis依赖:

1
gem install redis

接下来在home目录下创建redisCluster文件夹,然后进入到该文件夹中,使用如下命令下载redis安装包:

1
wget http://download.redis.io/releases/redis-4.0.10.tar.gz

接着依次执行如下命令安装redis:

1
2
3
4
tar -zxvf redis-4.0.10.tar.gz
cd redis-4.0.10
make MALLOC=libc
make install

安装成功后,将redis-4.0.10/src目录下的redis-trib.rb文件复制到redisCluster目录下,使用的命令为:(请注意执行此命令时,当前终端处于redisCluster目录下)

1
cp -r ./redis-4.0.10/src/redis-trib.rb ./

确认一下是否真的复制成功:

1
2
[root@iZthsx5glu6ohyZ redisCluster]# ls
redis-4.0.10 redis-4.0.10.tar.gz redis-trib.rb

接着在redisCluster目录下创建6个文件夹,分别命令为8001、8002、8003、8004、8005、8006,然后将redis-4.0.10目录下的redis.conf文件分别往这个6个目录中复制一份,然后对每个目录中的redis.conf文件进行修改。这里以8001目录中的redis.conf文件为例进行说明,主要修改如下配置:

1
2
3
4
5
6
7
8
port 8001
# bind 127.0.0.1
cluster-enabled yes
cluster-config-file nodes-8001.conf
protected-mode no
daemonize yes
requirepass 123@456
masterauth 123@456

可以发现这里的配置在前面单机版安装配置的基础上增加了几条。其中端口修改为8001,cluster-enabled yes表示开启集群,cluster-config-file表示集群节点的配置文件,由于每个节点都开启了密码认证,因此又增加了masterauth配置,使得从机可以登录到主机上。按照这里的配置,对8002-8006目录中的redis.conf文件依次进行修改,注意修改时每个文件的port和cluster-config-file都是不一样的。全部修改完成后,进入redis-4.0.10目录下,分别启动6个Redis实例,相应的命令为:

1
2
3
4
5
6
redis-server ../8001/redis.conf
redis-server ../8002/redis.conf
redis-server ../8003/redis.conf
redis-server ../8004/redis.conf
redis-server ../8005/redis.conf
redis-server ../8006/redis.conf

可以使用ps -ef|grep redis命令检查一下是否都成功启动了:

1
2
3
4
5
6
7
8
[root@iZthsx5glu6ohyZ redis-4.0.10]# ps -ef|grep redis
root 19007 1 0 14:26 ? 00:00:00 redis-server *:8001 [cluster]
root 19022 1 0 14:26 ? 00:00:00 redis-server *:8002 [cluster]
root 19027 1 0 14:27 ? 00:00:00 redis-server *:8003 [cluster]
root 19032 1 0 14:27 ? 00:00:00 redis-server *:8004 [cluster]
root 19037 1 0 14:27 ? 00:00:00 redis-server *:8005 [cluster]
root 19042 1 0 14:27 ? 00:00:00 redis-server *:8006 [cluster]
root 19047 3722 0 14:27 pts/2 00:00:00 grep --color=auto redis

在确认6个Redis实例都启动成功后,回到redisCluster目录下,首先对redis-trib.rb文件进行修改,由于配置了密码登录,而该命令在执行时默认没有密码,因此将等不上各个Redis实例,此时用vi编辑器打开redis-trib.rb文件,搜索Redis.new找到如下一行:

1
@r = Redis.new(:host => @info[:host], :port => @info[:port], :timeout => 60)

位置可能就在这里附近:

修改这一行,在末尾添加密码参数:

1
@r = Redis.new(:host => @info[:host], :port => @info[:port], :timeout => 60, :password => "123@456")

这里设置的”123@456”就是各个Redis实例的登录密码。这些配置都完成后,接下来就可以创建Redis集群了。

(4)创建集群

接下来使用如下命令来创建Redis集群(请确保当前终端处于redisCluster目录下):

1
./redis-trib.rb create --replicas 1 47.100.175.12:8001 47.100.175.12:8002 47.100.175.12:8003 47.100.175.12:8004 47.100.175.12:8005 47.100.175.12:8006

特别说明,如果你正在阿里云或者腾讯云服务器创建该集群,那么你不仅需要注释掉bind 127.0.0.1,且上面必须使用127.0.0.1来代替服务器的公网或者内网IP,否则无法创建集群。此时应使用的命令为:

1
./redis-trib.rb create --replicas 1 127.0.0.1:8001 127.0.0.1:8002 127.0.0.1:8003 127.0.0.1:8004 127.0.0.1:8005 127.0.0.1:8006

其中,replicas表示每个主节点的slave数量。在集群的创建过程中会分配主机和从机,每个集群在创建过程中都将分配到一个唯一的id并分配到一段slot。

当集群创建成功后,进入redis-4.0.10目录中,登录任意一个Redis实例,使用的命令为:

1
redis-cli -p 8006 -a 123@456 -c

简单解释一些上述代码中各参数的含义:-p表示要登录的集群的端口,-a表示要登录的集群的密码,-c表示以集群的方式进行登录。登录成功后,通过cluster info命令可以查询集群状态信息:

还可以通过cluster nodes命令查询集群节点信息。在集群节点信息中,可以看到每一个节点的id,该节点是slave还是master,如果是slave,那么它的master的id是什么;如果是master,那么每一个master的slot范围是多少,这些信息都会被显示出来:

(5)添加主节点

当集群创建成功后,随着业务的增长,有可能需要添加主节点,添加主节点需要先构建主节点实例,将redisCluster目录下的8001目录再复制一份,名为8007,然后根据第三步中集群配置来修改8007目录下的redis.conf文件,修改完成后,在redis-4.0.10目录下运行如下命令来启动该节点:

1
redis-server ../8007/redis.conf

启动成功后,进入redisCluster目录下,执行如下命令将该节点添加到集群中:

1
./redis-trib.rb add-node 47.100.175.12:8007 47.100.175.12:8001

如果是阿里云或者腾讯云服务器创建该集群,则使用的命令为:./redis-trib.rb add-node 127.0.0.1:8007 127.0.0.1:8001

中间的参数是要添加的Redis实例地址,最后的参数是集群中的实例。添加成功后,使用如下命令登录任意一个Redis实例,查看集群节点信息,就可以看到该实例已经被添加进集群了:(确保此时终端处于redis-4.0.10目录中)

1
redis-cli -p 8001 -a 123@456 -c

可以看到新实例的确被添加到集群中,但是由于slot已经被之前的三个实例分配完了,因此新添加的实例没有slot,也就意味着新添加的实例没有存储数据的机会,此时需要从另外三个实例中拿出一部分的slot来分配给新实例,具体的操作如下:
首先,在redisCluster目录下执行如下命令对slot重新分配:

1
./redis-trib.rb reshard 47.100.175.12:8001

如果是阿里云或者腾讯云服务器创建该集群,则使用的命令为:./redis-trib.rb reshard 127.0.0.1:8001
上述一行代码中的第二个参数表示连接集群中的任意一个实例。在执行命令的过程中有三个核心配置需要手动配置:

第一个配置是要拿出多少个slot分配给新实例,此处拿出了1000个。第二个配置是把拿出来的1000个slot分配给谁,输入接收这1000个slot的Redis实例的id,这个id在节点添加成功后就能看到,也可以进入集群控制台后使用cluster nodes命令进行查看。第三个配置是这1000个slot由哪个实例出,如从端口为8001的实例中拿出1000个slot分配给端口8007的实例,那么这里就输入8001的id后按回车键,再输入done按回车键即可;如果你想将这1000个slot均摊到原有的所有实例中,那么这里输入all按回车即可。

slot分配成功后,再查看节点信息,就可以看到新实例也有slot了,如下图所示:

(6)添加从节点

第五步中介绍的是添加主节点,接下来介绍添加从节点,添加从节点相对来说要容易一些。同样添加从节点也需要先构建从节点实例,将redisCluster目录下的8001目录再复制一份,名为8008,然后根据第三步中集群配置来修改8008目录下的redis.conf文件,修改完成后,在redis-4.0.10目录下运行如下命令来启动该节点:

1
redis-server ../8008/redis.conf

启动成功后,进入redisCluster目录下,执行如下命令将该节点添加到集群中:

1
./redis-trib.rb add-node --slave --master-id 3097ef4b2f84f3d29476094e76bee6564020ab56 47.100.175.12:8008 47.100.175.12:8001

如果是阿里云或者腾讯云服务器创建该集群,则使用的命令为:./redis-trib.rb add-node --slave --master-id 3097ef4b2f84f3d29476094e76bee6564020ab56 127.0.0.1:8008 127.0.0.1:8001

添加从节点需要指定该从节点的masterid,--master-id后面的参数即表示该从节点master的id,此处以节点7的id为其masterid(节点7也是master节点),而后127.0.0.1:8008表示从节点的地址,127.0.0.1:8001则表示集群中任意一个实例的地址。当从节点添加成后,登录集群中的任意一个Redis实例,通过cluster nodes命令就可以查看到从节点的信息:

(7)删除节点

如果删除的是一个从节点,直接运行下面的命令即可删除:

1
./redis-trib.rb del-node 47.100.175.12:8001 be68432a3a33aa4bc5329e43c5bbae328d593b69

如果是阿里云或者腾讯云服务器创建该集群,则使用的命令为:./redis-trib.rb del-node 127.0.0.1:8001 be68432a3a33aa4bc5329e43c5bbae328d593b69
中间的实例地址表示集群中任意一个实例,最后的参数表示要删除的节点的id。但若删除的节点是主节点,它会占有slot,会导致删除失败,此时可以按照前面的步骤,先将要删除主节点的slot全部都分配出去,然后运行如上的命令就可以成功的删除一个占有slot的主节点了。

SpringBoot整合Redis集群

不同于单机版的Redis整合SpringBoot,Redis集群整合SpringBoot需要开发者手动配置,配置步骤如下:

第一步,创建SpringBoot Web项目并添加依赖。使用spring Initializr构建工具构建一个SpringBoot的Web应用,名称为redisclusterspringboot,然后在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
26
27
<!--web配置依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--添加jedis依赖-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.1</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>2.1.3.RELEASE</version>
</dependency>
<dependency>
<!--添加数据库连接池依赖-->
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!--添加spring-boot-configuration-processor依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>

特别注意jedisspring-data-redis的版本问题,这个很容易出错,建议按照上述版本进行操作。
第二步,配置集群信息。由于集群节点有多个,可以保存在一个集合中,因此这里的配置文件使用YAML格式较为方便,删除resources目录下的application.properties文件,创建application.yml配置文件,里面的代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
spring:
redis:
cluster:
ports:
- 8001
- 8002
- 8003
- 8004
- 8005
- 8006
- 8007
- 8008
host: 47.100.175.12
poolConfig:
max-total: 8
max-idle: 8
max-wait-millis: -1
min-idle: 0

由于本案例是一个伪集群,因此ports代表实际的nodes节点,且这里的Redis实例的host都是一样的,因此这里配置了一个host,而port配置成了一个集合,这些port将被注入到一个集合中。poolConfig则是基本的连接池信息配置,和单机版的基本上没什么大的区别。

第三步,配置Redis。新建config包,并在包中创建RedisConfig类,用于返回RedisTemplateStringRedisTemplate对象,里面的代码为:

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
@Configuration
@ConfigurationProperties("spring.redis.cluster")
public class RedisConfig {
List<Integer> ports;
String host;
JedisPoolConfig jedisPoolConfig;
//getter和setter方法

@Bean
RedisClusterConfiguration redisClusterConfiguration(){
RedisClusterConfiguration configuration = new RedisClusterConfiguration();
List<RedisNode> nodes = new ArrayList<>();
for(Integer port:ports){
nodes.add(new RedisNode(host,port));
}
configuration.setPassword(RedisPassword.of("123@456"));
configuration.setClusterNodes(nodes);
return configuration;
}

@Bean
JedisConnectionFactory jedisConnectionFactory(){
JedisConnectionFactory factory = new JedisConnectionFactory(redisClusterConfiguration(),jedisPoolConfig);
return factory;
}

@Bean
RedisTemplate redisTemplate(){
RedisTemplate redisTemplate = new RedisTemplate();
redisTemplate.setConnectionFactory(jedisConnectionFactory());
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());
return redisTemplate;
}

@Bean
StringRedisTemplate stringRedisTemplate(){
StringRedisTemplate stringRedisTemplate = new StringRedisTemplate(jedisConnectionFactory());
stringRedisTemplate.setKeySerializer(new StringRedisSerializer());
stringRedisTemplate.setValueSerializer(new StringRedisSerializer());
return stringRedisTemplate;
}
}

解释一下上述代码的含义:

  • 首先使用@Configuration注解表示这是一个配置类,接着使用@ConfigurationProperties("spring.redis.cluster")注解来声明配置文件前缀,配置文件中定义的ports数组,host及连接池配置信息都将被注入port,host、poolConfig这三个属性中。
  • 接着定义一个redisClusterConfiguration方法,该方法用于返回一个RedisClusterConfiguration对象,为什么需要定义这个方法呢,那是因为在前面介绍RedisAutoConfiguration类的时候提到过自己定义需要提供StringRedisTemplateRedisTemplate这两个对象,而这两个对象中均需要传入redisConnectionFactory对象,而这个redisConnectionFactory对象的构造方法红需要传入RedisClusterConfiguration对象,最重要的是这个RedisClusterConfiguration对象实例化需要port,host、poolConfig这三个属性,正好就将配置文件中的信息都给注入进去:

所以通过上面的分析就知道为什么需要定义这个方法,而不是直接复制即可。RedisClusterConfiguration对象用于设置Redis登录密码及Redis节点信息。

  • 那自然而然jedisConnectionFactory方法是根据前面的RedisClusterConfiguration对象以及连接池配置信息来创建Jedis连接工厂,即JedisConnectionFactory对象。
  • 接着根据JedisConnectionFactory对象来创建RedisTemplateStringRedisTemplate,请注意需要在对应的template中配置key和value的序列化方式,前面也说过字符串的序列化采用StringRedisSerializer,而对象的序列化采用的是JdkSerializationRedisSerializer,这一点需要明白。有了RedisTemplateStringRedisTemplate对象,接下来的使用就和单实例的用法完全一致了。

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

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

请注意必须让这个实体类实现Serializable这个序列化接口,记住实现了Serializable接口的类可以被ObjectOutputStream转换为字节流,同时也可以通过ObjectInputStream再将其解析为对象,这样便于数据的传输。

第五步,创建Controller类。新建controller包,并在其中新建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
@RestController
public class BookController {
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private StringRedisTemplate stringRedisTemplate;

@GetMapping("/test")
public void test(){
ValueOperations<String,String> ops1 = stringRedisTemplate.opsForValue();
ops1.set("name","红楼梦");
String name = ops1.get("name");
System.out.println(name);

ValueOperations ops2 = redisTemplate.opsForValue();
Book book = new Book();
book.setId(1);
book.setName("西游记");
book.setAuthor("吴承恩");
ops2.set("book",book);
Book result = (Book) ops2.get("book");
System.out.println(result);
}
}

解释一下上述代码的含义:

  • StringRedisTemplateRedisTemplate的子类,StringRedisTemplate中的key和value都是字符串,采用的序列化方案是StringRedisSerializer,而RedisTemplate则可以用来操作对象,RedisTemplate采用的序列化方案是JdkSerializationRedisSerializer。无论是StringRedisTemplate还是RedisTemplate,它们操作Redis的方法都是一致的。
  • StringRedisTemplateRedisTemplate都是通过opsForValueopsForZSet或者opsForSet等方法首先获取一个操作对象,再使用该操作对象来完成数据的读写。
  • ops.set()方法用于向Redis中存储一条记录,而ops.get()方法则是将其读取出来。

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

1
2
红楼梦
Book{id=1, name='西游记', author='吴承恩'}

开发者此时可以登录任意一个Redis实例,查询数据,请注意查询时只需要登录任意一个Redis实例即可,RedisCluster会负责将查询请求Redirect到相应的实例上去。

这样就实现了Redis集群整合SpringBoot的目标。当然不仅仅可以使用jedisspring-data-redis组合形式,其实还可以直接使用spring-boot-starter-data-redis依赖来完成Redis操作。