整合NoSQL---Redis
在前面学习了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 | tar -zxvf redis-4.0.10.tar.gz |
若在执行make MALLOC=libc
命令时提示gcc:未找到命令
,则先执行如下命令安装gcc,等安装成功后再进行编译安装:
1 | yum -y install gcc |
请注意Redis新版本的安装方式和之前有所不同,可以点击 这里进行查看。也可以参看这篇文章:Linux下Redis的安装和部署
第三步,配置Redis。Redis安装成功后,接下来进行配置,打开Redis解压目录下的redis.conf
文件,主要修改如下几个地方(请注意这是修改后的配置情况):
1 | daemonize yes |
简单解释一下上述配置的含义:daemonize设置为yes,表示运行Redis在后台启动;第二行表示允许连接该Redis实例的地址,默认情况下只允许本地连接,因此需要将默认配置注释掉,这样外网就可以连接Redis了。requirepass表示设置了登录该Redis实例所需的密码。由于有了第三行配置的密码登录,因此protected-mode表示的保护模式就可以关闭了。
第四步,配置CentOS防火墙。为了远程能连接上Redis,还需要关闭该虚拟机的防护墙,执行命令如下:
1 | systemctl stop 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 | <!--web配置依赖--> |
默认情况下spring-boot-starter-data-redis
中使用的Redis工具是Lettuce,考虑到有的开发者习惯使用Jedis,因此可以从spring-boot-starter-data-redis
中排除Lettuce并引入Jedis,只需将上述依赖修改如下:
1 | <!--web配置依赖--> |
第二步,配置Redis。在application.properties
文件中添加Redis的链接信息:
1 | # 基本连接信息配置 |
请注意如果项目使用了Lettuce,只需将连接池的基本信息中的jedis修改为lettuce即可,而前面的基本连接信息配置不用修改。
在前面整合关系型数据库中,开发者之所以可以轻松实现零配置,是因为SpringBoot在用户未提供对应的配置类,如JdbcOperations缺失情况下会自动提供JdbcTemplate
,那么SpringBoot对于Redis的零配置是否也是类似的支持呢?可以发现的确如此,在SpringBoot的自动配置类中提供了RedisAutoConfiguration
类用于进行Redis的配置,这里贴出对应的源码:
1 | @Configuration |
从源码中可以看出,application.properties
文件中配置的信息将被注入到RedisProperties
中,如果开发者自己没有提供RedisTemplate
或者StringRedisTemplate
实例,则SpringBoot默认会提供这两个实例,RedisTemplate
和StringRedisTemplate
实例则提供了Redis的基本操作方法。
第三步,创建实体类。新建pojo包,并在其中新建Book类,里面的代码为:
1 | public class Book implements Serializable { |
请注意必须让这个实体类实现Serializable
这个序列化接口,记住实现了Serializable
接口的类可以被ObjectOutputStream
转换为字节流,同时也可以通过ObjectInputStream
再将其解析为对象,这样便于数据的传输。
第四步,创建Controller类。新建controller包,并在其中新建BookController类,里面的代码为:
1 | @RestController |
解释一下上述代码的含义:
StringRedisTemplate
是RedisTemplate
的子类,StringRedisTemplate
中的key和value都是字符串,采用的序列化方案是StringRedisSerializer
,而RedisTemplate
则可以用来操作对象,RedisTemplate
采用的序列化方案是JdkSerializationRedisSerializer
。无论是StringRedisTemplate
还是RedisTemplate
,它们操作Redis的方法都是一致的。StringRedisTemplate
和RedisTemplate
都是通过opsForValue
、opsForZSet
或者opsForSet
等方法首先获取一个操作对象,再使用该操作对象来完成数据的读写。ops.set()
方法用于向Redis中存储一条记录,而ops.get()
方法则是将其读取出来。
第五步,测试。运行项目,在浏览器地址栏中输入http://localhost:8080/test
,可以看到控制台打印日志信息:
1 | 红楼梦 |
还可以去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 | gpg2 --keyserver hkp://keys.gnupg.net --recv-keys D39DC0E3 |
如果在执行过程中出现下面的错误,那是因为某些原因导致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 | tar -zxvf redis-4.0.10.tar.gz |
安装成功后,将redis-4.0.10/src
目录下的redis-trib.rb
文件复制到redisCluster
目录下,使用的命令为:(请注意执行此命令时,当前终端处于redisCluster
目录下)
1 | cp -r ./redis-4.0.10/src/redis-trib.rb ./ |
确认一下是否真的复制成功:
1 | [root@iZthsx5glu6ohyZ redisCluster]# ls |
接着在redisCluster
目录下创建6个文件夹,分别命令为8001、8002、8003、8004、8005、8006,然后将redis-4.0.10
目录下的redis.conf
文件分别往这个6个目录中复制一份,然后对每个目录中的redis.conf
文件进行修改。这里以8001目录中的redis.conf
文件为例进行说明,主要修改如下配置:
1 | port 8001 |
可以发现这里的配置在前面单机版安装配置的基础上增加了几条。其中端口修改为8001,cluster-enabled yes
表示开启集群,cluster-config-file
表示集群节点的配置文件,由于每个节点都开启了密码认证,因此又增加了masterauth
配置,使得从机可以登录到主机上。按照这里的配置,对8002-8006目录中的redis.conf
文件依次进行修改,注意修改时每个文件的port和cluster-config-file
都是不一样的。全部修改完成后,进入redis-4.0.10
目录下,分别启动6个Redis实例,相应的命令为:
1 | redis-server ../8001/redis.conf |
可以使用ps -ef|grep redis
命令检查一下是否都成功启动了:
1 | [root@iZthsx5glu6ohyZ redis-4.0.10]# ps -ef|grep 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 | <!--web配置依赖--> |
特别注意jedis
和spring-data-redis
的版本问题,这个很容易出错,建议按照上述版本进行操作。
第二步,配置集群信息。由于集群节点有多个,可以保存在一个集合中,因此这里的配置文件使用YAML格式较为方便,删除resources目录下的application.properties
文件,创建application.yml
配置文件,里面的代码为:
1 | spring: |
由于本案例是一个伪集群,因此ports代表实际的nodes节点,且这里的Redis实例的host都是一样的,因此这里配置了一个host,而port配置成了一个集合,这些port将被注入到一个集合中。poolConfig则是基本的连接池信息配置,和单机版的基本上没什么大的区别。
第三步,配置Redis。新建config包,并在包中创建RedisConfig
类,用于返回RedisTemplate
和StringRedisTemplate
对象,里面的代码为:
1 | @Configuration |
解释一下上述代码的含义:
- 首先使用
@Configuration
注解表示这是一个配置类,接着使用@ConfigurationProperties("spring.redis.cluster")
注解来声明配置文件前缀,配置文件中定义的ports数组,host及连接池配置信息都将被注入port,host、poolConfig这三个属性中。 - 接着定义一个
redisClusterConfiguration
方法,该方法用于返回一个RedisClusterConfiguration
对象,为什么需要定义这个方法呢,那是因为在前面介绍RedisAutoConfiguration
类的时候提到过自己定义需要提供StringRedisTemplate
和RedisTemplate
这两个对象,而这两个对象中均需要传入redisConnectionFactory
对象,而这个redisConnectionFactory
对象的构造方法红需要传入RedisClusterConfiguration
对象,最重要的是这个RedisClusterConfiguration
对象实例化需要port,host、poolConfig这三个属性,正好就将配置文件中的信息都给注入进去:
所以通过上面的分析就知道为什么需要定义这个方法,而不是直接复制即可。RedisClusterConfiguration
对象用于设置Redis登录密码及Redis节点信息。
- 那自然而然
jedisConnectionFactory
方法是根据前面的RedisClusterConfiguration
对象以及连接池配置信息来创建Jedis连接工厂,即JedisConnectionFactory
对象。 - 接着根据
JedisConnectionFactory
对象来创建RedisTemplate
和StringRedisTemplate
,请注意需要在对应的template中配置key和value的序列化方式,前面也说过字符串的序列化采用StringRedisSerializer
,而对象的序列化采用的是JdkSerializationRedisSerializer
,这一点需要明白。有了RedisTemplate
和StringRedisTemplate
对象,接下来的使用就和单实例的用法完全一致了。
第四步,创建实体类。新建pojo包,并在其中新建Book类,里面的代码为:
1 | public class Book implements Serializable { |
请注意必须让这个实体类实现Serializable
这个序列化接口,记住实现了Serializable
接口的类可以被ObjectOutputStream
转换为字节流,同时也可以通过ObjectInputStream
再将其解析为对象,这样便于数据的传输。
第五步,创建Controller类。新建controller包,并在其中新建BookController类,里面的代码为:
1 | @RestController |
解释一下上述代码的含义:
StringRedisTemplate
是RedisTemplate
的子类,StringRedisTemplate
中的key和value都是字符串,采用的序列化方案是StringRedisSerializer
,而RedisTemplate
则可以用来操作对象,RedisTemplate
采用的序列化方案是JdkSerializationRedisSerializer
。无论是StringRedisTemplate
还是RedisTemplate
,它们操作Redis的方法都是一致的。StringRedisTemplate
和RedisTemplate
都是通过opsForValue
、opsForZSet
或者opsForSet
等方法首先获取一个操作对象,再使用该操作对象来完成数据的读写。ops.set()
方法用于向Redis中存储一条记录,而ops.get()
方法则是将其读取出来。
第六步,测试。运行项目,在浏览器地址栏中输入http://localhost:8080/test
,可以看到控制台打印日志信息:
1 | 红楼梦 |
开发者此时可以登录任意一个Redis实例,查询数据,请注意查询时只需要登录任意一个Redis实例即可,RedisCluster
会负责将查询请求Redirect到相应的实例上去。
这样就实现了Redis集群整合SpringBoot的目标。当然不仅仅可以使用jedis
和spring-data-redis
组合形式,其实还可以直接使用spring-boot-starter-data-redis
依赖来完成Redis操作。