Redis的使用

这篇文章我们主要是想介绍 Redis 的使用,现在开发中 Redis 是使用的非常频繁的,一般都是作为缓存来使用,进而减小用户请求对数据库的压力,这样就能使系统运行更加稳定,其实 Redis 数据库中保存的就是比较固定的数据,比如用户的信息,当有请求想要访问某个用户的信息时,就可以直接从 Redis 缓存中获取得到了,而不用每次都去查询数据库了,这样数据库的压力就减轻了,同时当用户信息有修改时,我们也同时修改 Redis 缓存中的信息,这样的话就相当于对系统中数据的存储进行了优化。

1.介绍

Redis 也是一种数据库,不过是一种 NoSQL 数据库,也就是 Not Only SQL 的意思,一般的数据库大多都是文件系统的,而 Redis 数据库则不仅有文件系统,同时也有内存,可以将数据保存在内存中,同时也可以持久化到硬盘上面,Redis 数据库中的数据都是以 key-value 这种键值对的形式进行保存的,Redis 这样的缓存数据库有下面 3 种特点:

1.支持数据的持久化。数据不仅可以保存在内存中,同时也可以保存在磁盘中,并且可以进行交互。
2.不仅支持保存简单的字符串string格式的数据,还支持list、set、zset、hash等多种格式的数据。
3.支持数据的备份。也就是主从模式的备份。

同时,Redis 相对于其它的缓存数据库来说,还具有以下优势:

1.性能好。读写速度都非常快。
2.丰富的数据类型。也就是我们上面所提到的5种数据类型。
3.原子性操作。Redis中所有的操作都是原子性的,而且还支持几个操作合并后的原子性执行。
4.丰富的特性。Redis还支持publish/subscribe,通知以及key过期等高级特性。

2.安装

现在我们就来看如何安装 Redis 数据库,安装之前当然是需要先下载了,下载的话我们就可以直接到官网进行下载了,也就是 https://redis.io/ 这个网站了,进入网站在导航栏会有一个 Download 按钮,点击该按钮就可以进入到下载页面了,进入下载页面之后我们就能看到不同版本的 Redis 供我们下载了,我们这时候就可以选择稳定版 (Stable) 进行下载了,就是点击 Stable 那一栏下面的 Download 按钮了,当然这样下载的话,肯定就是下载到 Windows 系统中了,而我们是需要在 Linux 系统中进行安装的,因此还需要将该下载的文件上传到 Linux 系统中,不过也可以直接在 Linux 系统中进行下载,那就是直接登录 Linux 系统,然后进入到期望进行安装操作的目录中,直接使用 wget 命令进行下载,比如:

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

这样的话,同样也是可以进行下载操作的,不过需要保证是有网络环境的,这样操作之后,我们的安装包就下载好了,这里我们使用 Redis 的版本为 3.2.8 的,还需要说明的一点就是,Redis 数据库原生是只支持 Linux 系统的,不支持 Windows 系统的,所以 Windows 版本的 Redis 数据库是微软自己建立的分支,当然是根据 Redis 官方的源码进行编译、发布和维护的,这样的话,Windows 版本的 Redis 版本就总是低于 Linux 版本的,但是使用起来是没有什么区别的。

下面我们就来看如何进行安装操作,之前我们已经得到了 Redis 数据库的安装包,也就是 redis-3.2.8.tar.gz 的安装包,我们在 Linux 系统中安装软件时一般都会将其安装到 /usr/local 目录下,因此我们可以先将 Redis 数据库的安装包上传到该目录,然后我们就可以进行安装操作了,第一步便是解压安装包了,我们可以使用 tar -zxvf redis-3.2.8.tar.gz 命令进行解压缩包操作,执行完解压操作之后,就可以在同一级目录中看到有一个 redis-3.2.8 目录,当然就是我们压缩包解压出来的了,那接下来就是第二步的操作了,因为 redis-3.2.8 目录中的都是 Redis 数据库的源码,所以我们需要对该源码进行编译和安装操作,第二步我们便是进行编译操作,首先需要进入到 redis-3.2.8 目录,然后执行 make 命令就行了,这个命令便是进行编译源码操作的,这里需要注意的一点是,这里由于系统环境的原因可能会报错,如:

/bin/sh: cc: command not found

这个报错是因为当前 Linux 系统中缺少 gcc 环境的原因,所以我们需要先安装 gcc 环境,安装方式为:

yum install gcc

这样我们安装了 gcc 环境之后,再执行 make 命令进行源码编译就是可以的了,执行了编译操作之后,下面就是最后一步安装操作了,即将我们的 Redis 安装到指定的目录当中,当然还是在 redis-3.2.8 目录当中,执行如下命令:

make PREFIX=/usr/local/redis install

这个命令便是将 Redis 安装到 /usr/local/redis 这个目录当中,但是因为现在 /usr/local 目录是没有 redis 目录的,所以需要先去新建这个目录,然后再执行上面的安装操作,操作完成之后就安装完成了,这便是第三步安装操作,这样的话,Redis 数据库的安装便完成了。

下面我们就可以到 Redis 数据库的安装目录 /usr/local/redis 目录进行查看了,会发现其中有一个 bin 目录,进入该 bin 目录,会发现有几个文件,我们一个一个进行说明。

redis-server:Redis服务器
redis-cli:Redis命令行客户端
redis-benchmark:Redis性能检测工具
redis-check-aof:AOF文件修复工具
redis-check-rdb:RDB文件修复工具

安装好 Redis 服务器之后,我们可以启动服务然后测试是否可以正常使用的,启动的方式十分简单,可以直接到 redis 目录,然后再到 bin 目录,直接执行 ./redis-server 命令,这时候我们就可以看到日志提示 Redis 数据库正常启动了,这种方式在测试时可以使用,但是在实际使用中一般不会这样,在实际使用中我们通常会使用配置文件的方式启动服务,配置文件就是 redis-3.2.8 目录中的 redis.conf 文件,我们可以先进入到 redis-3.2.8 目录,然后使用下面的命令将配置文件拷贝到 Redis 的安装目录中。

cp redis.conf /usr/local/redis

复制好文件之后,我们就可以回到 redis 目录中启动 Redis 数据库了,执行命令为:

./bin/redis-server ./redis.conf

通过控制台打印的日志我们就能看到启动好了,也就是 Redis 数据库的服务端启动好了,然后下面我们还需要启动 Redis 数据库的命令行客户端,这样就便于我们进行测试,启动命令行客户端也十分简单,只需要进入到 bin 目录,然后执行 ./redis-cli 命令就好了,当然执行这种命令的话,就是默认连接本地的 Redis 数据库了,那如果想要连接指定 IP 和端口的服务器呢,就可以使用类似下面的命令了。

./redis-cli -h 127.0.0.1 -p 6379

上面的命令就是指定连接本地主机端口号为 6379Redis 数据库了,连接成功后,我们便可以使用 Redis 数据库了,我们可以输入一个 ping 命令进行测试,这时候会返回一个 PONG,这就表示 Redis 数据库安装是正常的了。

需要说明的一点是,当 Redis 数据库启动时默认是只能在本机进行访问的,也就是别的主机是访问不了的,这是在 Redis 的配置文件 redis.conf 中进行设置的,配置为 bind 127.0.0.1,也就是为 Redis 数据库绑定的 IP 地址为 127.0.0.1,然后我们看这个配置上面的说明的话,就可以看到,之所以默认设置为只能本机访问,是出于安全考虑。

3.Redis数据类型与常见操作

Redis 数据库提供了丰富的数据类型供我们使用,一共有 5 种数据类型,分别为:

string:普通字符串
list:链表
set:集合
zset:有序集合(sorted set)
hash:哈希

下面我们就分别针对这五种数据类型分别进行说明,看看它们各自都有什么方法供我们使用。

3.1 string类型数据

字符串类型数据是 Redis 数据库中最基本的一种数据类型,在 Redis 数据库中是使用二进制进行安全存储的,因此我们便可以存储任何格式的数据,比如 json 格式的数据,甚至是 jpeg 这样的图片格式数据,同时,在 Redis 数据库中的字符串类型的 value 最大可以存储 512M 大小的数据,那对于 Redis 的基本数据类型字符串,也就是 string 来说,有哪些常用的命令是可以供我们使用的呢,下面我们一一进行说明。

set key value

这个 set 命令便是用来设置键值对的,即设置 key 所对应的值为 value,如果这个 key 已经存在了,则会覆盖这个 key 所对应的原来的 value 值。比如可以这样使用:

set name kobe

上面这个命令便是设置 name 这个键所对应的值为 kobe,那如果想要获取到某个键对应的值呢?那就可以使用与上面这个 set 命令相对应的 get 命令了,使用 get 命令我们便可以获取 key 所对应的 value 值了。

get key

这个 get 命令便是用来获取键所对应的值的,如果 keyRedis 数据库中不存在的话,则会返回 nil,表示该 key 不存在,并且该 key 所对应的 value 也不存在。

mset key value [key value...]

这个 mset 命令是用来设置多个键值对的,和 set 命令相同的,当对应的 key 已经存在时,这个命令同样也会覆盖 key 所对应的 value 值,所以这个命令完全可以看成是 set 命令的多次迭代操作。具体的使用则可以如下:

mset age 18 sex boy

这里便是设置了两组键值对,一组为年龄,另一组则为性别,keyage 所对应的 value18,而 keysex 所对应的 value 则为 boy,这样就完成了设置多组键值对的操作。有设置多个键值对的命令,当然也有对应的根据多个键获取对应值的操作了,也就是 mget 命令了。

mget key [key...]

这个 mget 命令就是根据多个键获取得到对应的值了,当有的 key 不存在时,则会返回 nil,因此 mget 可以完全看作是 get 命令的多次迭代操作,例如我们可以这样使用:mget sex age,这样的话我们就能获取得到 sexage 这两个键所对应的 value 了。

setnx key value

这个 setnx 命令同样也是用来设置某个键所对应的值的,但是它比较特别的地方在于,当这个 key 不存在时,setnx 命令的效果和 set 命令是一样的,都是设置这个 key 所对应的 value,并且此时的返回值为 1,但是当命令中设置的 key 已经在 Redis 中存在时,那么这时候不会做任何操作,这时的返回值为 0。具体的使用可以如下:

setnx age 19
setnx birth 2019-01-18

第一个命令是来设置 age 这个 key 对应的 value 值为 19 的,但是之前我们已经设置过 age 对应的 value18,因此这里使用 setnx 命令不会修改 age 所对应的 value 值,而且此时的返回值也为 0;再看第二个命令,是设置 birth 这个 key 的,因为我们之前是没有设置过这个 key 的,因此此时是可以设置成功的,返回值也应该为 1

msetnx key value [key value...]

这个 msetnx 命令是原子性的完成参数中所有 key/value 的设置操作,相当于是循环迭代的 setnx 命令,有一点需要注意的就是,如果命令中的某个 key 已经存在了,那么该命令所有的操作都会回滚,也就是全部的操作都会失效,返回值为 0,具体的使用可以如下:

msetnx age 10 like basketball

在上面这个命令中,由于我们之前已经设置过 keyage 的键值对,因此在这里再次设置时是不会生效的,而且由于该命令具有原子性的特点,因此命令中所有的键值对设置都不会生效。

append key value

这个 append 命令是用来追加某个 key 所对应的 value 值的,当命令中的 key 已经存在时,那这个命令就会将命令中的 value 追加到命令中 key 所对应 value 的后面,如果命令中的 keyRedis 数据库还不存在的话,那么就相当于是一次设置操作,具体的使用可以如下:

append name ' is a boy'

因为在之前的命令中,我们已经设置 name 这个 key 所对应的 valuekobe 了,然后在这里再使用 append 追加命令,那么现在 name 这个 key 所对应的 value 就应该是 kobe is a boy 了,当然我们是可以使用 get name 这个命令来进行验证的。

decr key

这个 decr 命令是将 key 所对应的 value 值递减 1,当然这时候就必须要求这个 key 所对应的 value 必须是能转换成整形值的数据了,否则就会报错了,同时,如果命令中的 key 不存在的话,那么就会默认这个 key 所对应的 value0,然后再进行递减 1 的操作,返回的就是 -1 了,其实这里的 key 如果不存在的话,就相当于是先设置该 key 所对应的 value 值为 0 了,然后再进行的递减操作。具体使用可以如下:

decr age

由于之前我们已经将 age 这个 key 所对应的 value 设置为 18 了,然后这里 decr 命令的结果就会返回 17 了。

incr key

这个 incr 命令的效果便是和上面 decr 命令的效果是相对的,也就是将 key 所对应的 value 递增 1,同样也要求 key 所对应的 value 值是能转换成整形值的数据,当命令中的 keyRedis 中不存在时,会先设置该 key 所对应的 value0,然后再进行递增 1 的操作,这时候就会返回 1 了,具体的使用如下:

incr age

因为之前的操作,Redis 数据库中 age 对应的 value 应该是 17 了,而这里使用 incr 命令的话就会返回 18 了。

decrby key decrement

这个 decrby 命令的作用和 decr 命令的作用是一样的,也是将命令中 key 所对应的 value 值进行减操作,只不过 decr 命令每次都是确定的减 1,而 decrby 命令则可以根据命令中的参数减少指定的数据,不过有一点是相同的,那就是当命令中的 key 不存在时,Redis 会先指定该 key 对应的 value 值为 0,然后再进行减操作。具体的使用可以如下:

decrby count 4

这里因为之前在 Redis 中是不存在 count 这个 key 的,所以返回的应该是 -4

incrby key increment

这个命令则是和上面的 decrby 命令相对应的,incrby 命令主要是用来对某个 key 所对应的 value 值进行增加操作,同样可以指定所增加值的大小,并且当 key 不存在时,也会先将 key 所对应的 value 置为 0,具体使用可以如下:

incrby count 5

由于之前 count 所对应的值已经设为 -4 了,在这里进行加 5 的操作,那最后返回的结果就应该是 1 了。

getset key value

这个 getset 命令同样也是用来设置键值对的,作用的效果其实和 set 命令是一样的,不过有一点区别的就是,getset 命令不仅会设置键值对,而且还会返回这个 key 所对应的原来 value 值,具体使用可以如下:

getset age 16

因为之前 age 这个 key 所对应的 value18,所以执行上面这个命令时,就会将 age 这个 key 所对应的 value 设置为 16,同时也会返回原来的对应值 18

strlen key

这个 strlen 命令则是用来返回 key 所对应 value 值的长度,我们可以直接使用:

strlen name

由于之前 name 这个 key 所对应的 value 值为 kobe is a boy,因此这里的返回值应该是 13

setex key seconds value

这个 setex 命令主要是用来设置键值对,同时设置键的生命周期的,也就是这个 keyRedis 中存在的时长,之前我们使用 set 命令设置的键值对,其生命周期都是永久的,在实际开发中我们一般都会指定 key 的生命周期,这样就不会使 Redis 中的 key 过多。具体的使用可以如下:

setex age 10 6

上面这个命令的作用就是设置 age 这个 key 所对应的 value 值为 6,并且该 key 的存活时间为 10 秒钟,我们这里如果想要查看某个 key 的生命周期的话,是可以使用 ttl 命令的,比如 ttl age 命令,便是查看 age 这个 key 的生命周期了,我们之前使用 set 命令设置的键值对,其生命周期为永远,这时候使用 ttl 命令返回的就是 -1 了。

setrange key offset value

这个 setrange 命令主要是用来对 key 所对应的 value 值进行局部替换操作,替换的位置就是 offset 了,替换的值也就是命令中的 value 值了,当命令中的 offset 值大于 key 所对应 value 的长度时,那就会在原 value 值和 offset 之间插入 \u0000 值,在我们之前的操作中,name 这个 key 所对应的 value 值应该是 kobe is a boy,我们现在如果使用下面这个命令的话:

setrange name 10 girl

那这时候,name 这个 key 所对应的 value 值就应该是 kobe is a girl 了。同时,如果命令中的 key 是不存在的话,那就会直接为该 key 进行设置操作了,并且如果该命令中的 offset 大于 0 的话,同样的也会在 offset 位置之前插入 \u0000 字符的。

getrange key start end

这个 getrange 命令则是用来获取 key 所对应 value 中的某部分值的,命令中的 startend 则是表示获取数据的区间,这里的区间是闭区间,也就是说获取 value 值上面的第 start 个元素到 end 个元素时,startend 位置上面的元素都会包含在内,比如我们可以如下进行使用:

getrange name 0 1

那上面这个命令就会返回 ko 了,当然,如果想要获取得到 key 所对应的 value 完整值的话,其实是可以使用这个命令的,getrange name 0 -10 表示的是从第 1 位开始,-1 则是表示到最后一位。

getbit key offset

这个 getbit 命令是用来获取 key 所对应 value 值二进制位的,每个二进制位只可能是 0 或者 1,这里就需要说一下十进制数和字母如何用二进制进行表示了,比如十进制数 1,如果用 8 位二进制表示的话,那就应该是 0000 0001 了,其实所有的十进制数都可以使用二进制来表示的,那字母呢?其实也是可以的,这里就需要使用 ASCII 码表了,这张表上面就列出了字母所表示的十进制数以及二进制数,比如 a,它的十进制表示就是 97,二进制表示就是 0110 0001 了,而 A 的话,它的十进制表示就是 65,二进制表示则是 0100 0001 了,如果我们先设置一个 key 所对应的 valuea,那再获取得到它每一个二进制位上面的数据,就可以看到该 value 的二进制表示了。

> set key a
OK
> get key
"a"
> getbit key 0
0
> getbit key 1
1
> getbit key 2
1
> getbit key 3
0
> getbit key 4
0
> getbit key 5
0
> getbit key 6
0
> getbit key 7
1

上面我们得到的结果其实依次来看的话,就是 0110 0001 了,和我们上面说的 a 的二进制表示是一致的。

setbit key offset value

这个 setbit 命令是用来设置 key 所对应 value 上的二进制位的,其实理解了上面的 getbit 命令,那这个 setbit 命令也就很好理解了,因为这两个命令刚好也是相对的,setbit 命令刚好就是用来修改 value 上面某个二进制位上面的值的,如果想看到效果的话,那我们可以直接对上面的 key 所对应的 a 这个 value 进行修改,a 所对应的二进制为 0110 0001,而 A 所对应的二进制为 0100 0001,两者的二进制表示主要就是第 3 个二进制位的差别,那我们可以执行下面的命令:

setbit key 2 0

也就是将原 value 值的二进制表示第 3 位上面的数值改为 0,那这样的话,现在 key 所对应的 value 值就是 A 了。

3.2 list类型数据

下面我们再来看 list 类型的数据,其实就是数据结构中的链表了,对于链表来说,效率最高的操作就是在链表的头尾进行元素的增删了,Redis 对于 list 类型的数据也提供了很多在头尾进行数据增删的操作,下面我们具体的来看。

lpush key value [value...]

这个 lpush 命令主要是向链表中插入一个或多个元素,当命令中的 key 还不存在时,则会新建该 key,也就是一个链表了,需要注意一下的就是,往链表中插入元素时,先插入的元素是在链表尾的,后插入的元素则是在链表头部的,下面看具体的使用:

lpush lkey01 1 2 3 4 5

上面这个命令则是向 lkey01 这个链表中插入了 5 个元素,由于在 Redis 数据库中 lkey01 这个链表是不存在的,因此在这里会先创建该链表,然后再往里面插入元素。

lpushx key value [value...]

这个 lpushx 命令同样也是往链表中插入元素的,只是有一点不一样的地方,就是当命令中 key 已经存在时,那么使用 lpushx 命令插入元素是和 lpush 命令一样的,但是如果命令中的 key 不存在,那么插入元素的操作就不会执行,因此如果想要使用 lpushx 插入元素时则必须保证链表已经是存在的了,下面看具体的使用:

lpushx lkey01 10

上面这个命令就是向 lkey01 链表又插入了一个元素 10,如果是还不存在的链表的话,那就不会进行插入操作了。

lrange key start end

这个 lrange 命令主要是用来查看链表中的元素的,startend 就是元素所在的位置了,都是以 0 为开始计数的,同样的,查询区间也是闭区间,即 startend 位置上面的元素都会被查出来,而 -1 的话则是表示最后一个位置,以此类推,-2 则是表示倒数第二个位置,所以当我们想查询链表中全部的元素时,就可以使用如下的命令:

lrange lkey01 0 -1

上面便可以查询出链表中所有的元素来了。

lpop key

这个 lpop 命令主要是从链表中取出头部元素,如果链表不存在的话,就会返回 nil,因此当我们想要从链表中取得头部元素时就可以使用该命令了,具体使用可以如下:

lpop lkey01

这里便是取出 lkey01 这个链表中的头部元素了,很显然这里取出的就应该是 10 这个元素了。

llen key

这个 llen 命令则是用来获取链表的长度了,也就是链表中元素的个数,当链表还不存在时,就会返回 0,已经存在的话则会返回链表实际长度,具体使用可以如下:

llen lkey01

这里返回的值就是 5 了,因为现在链表中还有 5 个元素。

lrem key count value

这个 lrem 命令主要是用来删除链表中元素的,即删除链表中 count 个值为 value 的元素,当 count 的值大于 0 ,则是表示依次从头到尾进行删除,当 count 的值小于 0 时,则表示依次从尾到头进行删除,当 count 的值等于 0 时,则表示删除链表中所有值为 value 的元素。

lset key index value

这个 lset 命令主要是用来设置链表中某个位置上面的元素值的,我们就可以用来进行替换操作了,索引值 index 是从 0 开始的,当命令中的 index 大于链表的实际长度时,就会报索引越界错误了,如果我们想设置链表中第一个元素为 100,则可以使用如下命令:

lset lkey01 0 100

这样的话,lkey01 链表的头部元素就会被设置为 100

lindex key index

这个 lindex 命令主要是返回链表中某个位置的元素值,我们便可以根据这个命令做查询相关的操作了,命令中的 index 同样是以 0 为开始的,0 表示的是链表的头部元素所在位置,-1 则是表示链表中尾部元素所在的位置,如果我们想获取到链表中的头部元素,那就可以这样:

lindex lkey01 0

这样的话便能得到链表的头部元素了,结合上面的 lset 命令来看的话,这里获得的元素应该是 100 了。

ltrim key start end

这个 ltrim 命令主要是用来将链表中的元素截取一部分然后保存下来,startend 则是表示保留链表中元素所在位置的区间了,startend 都是以 0 为开始的,并且区间也是闭区间,即 startend 所在位置的元素都会包含在内。

linsert key before|after pivot value

这个 linsert 命令主要是用来向链表中插入元素的,即向链表中元素值为 pivot 的元素前面或者后面插入值为 value 的元素,命令中的 beforeafter 就是指定是在元素前面还是后面进行插入了。

rpush key value [value...]

前面我们介绍的往链表中插入元素和取出一个元素,都是在链表头部进行操作的,其实我们同样也是可以在链表尾部进行这样的操作的,比如这个 rpush 命令,则是向链表尾部插入元素,同样的,如果命令中的链表还不存在,则先创建该链表然后再在尾部进行插入操作。

rpushx key value

这个 rpushx 命令同样也是在链表的尾部进行插入操作,和 lpushx 类似的,这个 rpushx 只有当链表已经存在时才会进行插入操作,如果链表不存在就不会做任何操作。

rpop key

这个 rpop 命令主要是在链表尾部取出一个元素,也就是尾部元素了。

rpoplpush source destination

这个 rpoplpush 命令是作用于两个链表的,即将 source 链表的尾部元素弹出,然后插入到 destination 链表的头部,如果 source 链表不存在,则只会返回 nil,而不会做其它操作了,如果 source 链表和 destination 链表是同一个链表,那就相当于是将这个链表的尾部元素弹出然后插入到头部了。

3.3 hash类型数据

下面我们再来看 Redis 数据库中的 hash 类型的数据,Redis 数据库中的 hash 类型数据类似于 Java 中的 Map 集合类型的数据,在这里我们可以简单的将 hash 类型的数据理解为一个 key 所对应的值为一个 Map 集合,因此我们可以使用该类型的数据保存对象信息,比如 keyuser ,然后该 key 所对应的 Map 则保存该用户对象的各种属性,比如姓名,性别以及年龄,这样就能很好的描述一个对象了,下面我们具体来看 hash 类型的数据有哪些常用命令。

hset key field value

这个 hset 命令主要是用来设置一个 hash 类型的数据,当命令中的 key 不存在时,便会执行新增操作,命令中的 field 参数则是 key 所对应 Map 类型数据中的键,value 则是 key 所对应 Map 类型数据中的值,下面通过具体的命令来学习。

hset user username tom

上面这个命令则是设置了一个 hash 类型的数据,该数据的 key 则为 user,该 key 所对应的则是一个 Map 集合,现在该 Map 中有一对键值对,键为 username,值则为 tom

hget key field

hget 命令则是用来获取 key 所对应 hash 类型数据中 field 所对应的值的,显然它的作用便是和 hset 是相对的,实际使用可以如下:

hget user username

这样的话,我们就能获得 user 这个 key 所对应 hash 类型数据中 username 这个键所对应的值了,很显然,这个值就是 tom 了。

hsetnx key field value

hsetnx 这个命令也是用来设置命令中 key 所对应的 hash 类型数据的,只不过和 hset 命令有区别的是,只有当命令中的 key 或者 field 不存在的时候,命令才会起作用,否则是不会起作用的,使用可以如下:

hsetnx user age 20

由于之前 user 所对应的 hash 类型数据中是不存在 age 这个键的,因此上面的命令便是相当于对 user 这个 key 所对应的 hash 类型数据的一次设置操作,设置其中 age 这个键所对应的值为 20。当然如果使用下面这个命令的话那就没有用了:

hsetnx user username jerry

由于 user 这个 key 所对应的 hash 类型数据中之前就已经有了 username 这个键,因此再对这个键进行设置时,就不会再起作用了。

hexists key field

hexists 命令主要是用来判断 key 所对应的 hash 类型数据中是否包含 field 这个键,如果包含的话就会返回 1,不包含或者连 user 这个 key 都不存在的话就会返回 0 了。具体使用可以如下:

hexists user username

上面这个命令则是用来判断 user 这个 key 所对应的 hash 类型数据中是否包含 username 这个键,很显然是包含的,因此会返回 1

hlen key

hlen 命令是用来确定 key 所对应的 hash 类型数据中包含 field 的个数,如果 key 不存在的话,则会返回 0。具体使用可以如下:

hlen user

上面这个命令则是为了确定 user 这个 key 所对应 hash 类型数据中 field 的个数,很显然现在 user 所对应 hash 类型数据中包含的 fieldusernameage 这两个,因此上面命令的返回值为 2

hdel key field [field...]

hdel 命令是用来删除 key 所对应 hash 类型数据中对应的 field,当命令中的 field 不存在时,则会忽略该 field,具体使用可以如下:

hdel user age

上面的命令则是用来删除 user 这个 key 所对应的 hash 类型数据中的 age 这个 field,当然在我们之前的操作中,age 这个 field 是存在的,因此在这里的删除操作也是可以进行的。

hincrby key field increment

hincrby 命令是用来对 key 所对应 hash 类型数据中 field 所对应值进行增减操作的命令,当然这就必须要保证 field 对应的值必须是可以被转换为整数型值的,当命令中的 key 或者 field 不存在时,就会新建命令中的 key 或者 field,并将 field 所对应的值设为 0,然后再进行加减操作。具体使用可以如下:

hincrby user age 10

上面这个命令便是对 user 这个 key 所对应的 hash 类型数据中的 age 这个 field 对应的值进行加 10 操作,由于之前已经不存在 age 这个 field 了,因此这里 age 所对应的值先设置为 0,然后再进行加 10 操作,最后返回的就是 10 了。

hgetall key

hgetall 命令是用来获取 key 所对应 hash 类型数据中所有的 field 以及 field 所对应的值的,相当于是对 key 所对应 hash 类型数据的遍历,具体使用可以如下:

hgetall user

上面的命令便是获取 user 这个 key 所对应 hash 类型数据中所有的数据了,包括 field 以及其所对应的值。

hkeys key

hkeys 命令是用来获取 key 所对应 hash 类型数据中所有的 field,类似于 Java 中获取 Map 集合中所有的 key,因此还是很好理解的,具体使用可以如下:

hkeys user

上面命令是用来获取 user 这个 key 所对应 hash 类型数据中所有 field,根据现在的情况,返回的就应该是 usernameage 这两个 field 了。

hvals key

hvals 命令是用来获取 key 所对应 hash 类型数据中所有 field 所对应的值的,相当于是 Java 中获取 Map 集合中的所有 value 值,该命令的具体使用如下:

hvals user

上面的命令是用来获取 user 这个 key 所对应 hash 类型数据中所有 field 所对应的值的。

hmget key field [field...]

hmget 命令是用来获取 key 所对应 hash 类型数据中多个 field 所对应的值的,如果命令中 field 不存在,返回的就是 nil 了,如果命令中的 key 都不存在,那返回的就是一组 nil 了。具体使用可以如下:

hmget user username age

上面这个命令是用来获取 user 这个 key 所对应 hash 类型数据中 usernameage 这两个 field 所对应的值的。

hmset key field value [field value...]

hmset 命令是用来设置 key 所对应 hash 类型数据中多个 field 所对应的值的,这个的作用当然就是刚好和 hmget 命令是相对的了,具体的使用可以如下:

hmset user addr hubei birth 2019-01-01

上面命令的作用便是设置 user 这个 key 所对应的 hash 类型数据中 addr 这个 field 所对应的值为 hubeibirth 这个 field 所对应的值为 2019-01-01

3.4 set类型数据

下面我们来介绍 Redis 数据库中的 set 类型数据,Redis 数据库中的 set 类型数据就相当于 Java 中的 Set 集合类型数据,和 Java 中一样,Redis 数据库中的 set 类型数据中存放的也是无序的数据,可以简单地看成是一组无序的字符串集合,同时,set 类型数据还可以在两个集合间完成交并差等集合操作,所以还是十分实用的,下面我们具体来看 set 类型数据为我们提供了哪些实用的命令。

sadd key member [member...]

sadd 命令相当于是往 key 这个集合中添加元素,当命令中的元素在集合中已经存在时,我们会忽略该元素,而其它的元素仍会添加成功,具体使用可以如下:

sadd set a b c d

上面命令就是往集合 set 中添加 4 个元素了,同时 4 个元素之间是无需排列的。

scard key

scard 命令则是用来获取集合中元素的个数,当集合还不存在时会返回 0,否则就是返回实际元素个数了。具体使用可以如下:

scard set

上面这个命令便是获取 set 这个集合中元素个数了,很明显这里是有 4 个元素的,所以应该返回 4

sismember key member

sismember 命令则是用来判断某个元素是否是属于某个集合的,返回值 1 表示存在,而 0 则是表示不存在。具体使用可以如下:

sismember set a

上面命令则是判断 a 这个元素是否在 set 这个集合当中,很显然是在集合当中的,因此应该返回 1

smembers key

smembers 命令是用来查看集合中所有元素的,也就相当于是对集合的遍历了。具体使用可以如下:

smembers set

上面这个命令便是查看 set 集合中的所有元素。

spop key

spop 命令是从 key 集合中返回并删除一个元素,由于集合中元素是无序排列的,因此每次返回并删除都相当于是随机的。具体使用可以如下:

spop set

上面命令就会从 set 集合中随机返回并删除一个元素。

srandmember key

srandmember 命令是随机返回集合中的某个元素,与 spop 不一样的是,srandmember 命令只会返回元素,而不会将元素从集合中删除。具体使用可以如下:

srandmember set

上面命令就是从集合中随机返回一个元素,因为集合中元素是排列无序的,因此每次返回的元素也是随机的。

srem key member [member...]

srem 命令主要是用来删除集合中指定元素的,如果命令中的元素在集合中不存在,则会忽略该元素。具体使用可以如下:

srem set a c

上面命令则是从集合中删除 ac 这两个元素。

smove source destination member

smove 命令是用于将 source 集合中的元素移到 destination 集合当中,也就是源集合和目标集合了,假如 set 集合中的元素为 a,b,c,而 set2 集合中元素为 1,2,3,当我们想把 a 元素从 set 集合移到 set2 集合时,我们就可以使用下面这个命令:

smove set set2 a

这样的话我们就可以达到我们想要的效果了,即将 a 元素从 set 集合移动到 set2 集合了。

sdiff key [key...]

sdiff 命令是用来获取第一个集合与其后所有集合的差集的,为了便于说明,我们假定有两个集合,setset2set 中元素为 1,2,3,4,5,而 set2 中元素为 4,5,6,7,8,当我们想求两个集合的差集时,则可以使用如下命令:

sdiff set set2

通过上面的命令,我们就得到了 set 集合与 set2 集合的差集了,很明显我们这里的结果就是 1,2,3 了。

sdiffstore destination key [key...]

sdiffstore 命令同样也是用来计算集合之间的差集的,只不过和 sdiff 命令之间的差别在于,sdiffstore 命令会将集合之间的差集保存在 destination 集合当中,也就是目标集合当中,而 sdiff 命令不会,因此上面同样的两个集合求差集,也可以使用下面这个命令:

sdiffstore sdiffset set set2

上面这个命令会将两个集合之间求差集的结果保存到 sdiffset 集合当中,保存的元素也就是 1,2,3 了。

sinter key [key...]

sinter 命令则是用来求第一个集合与其后所有集合之间的交集的,这样来看的话,当命令中有一个集合为空集合或者集合不存在的话,那我们运算的结果也就是空集合了。这里我们还是求 setset2 这两个集合之间的交集,命令如下:

sinter set set2

经过上面的命令,我们便能得到 setset2 集合之间的交集了,很明显这里我们得到的结果应该是 4,5

sinterstore destination key [key...]

sinterstore 命令也是用来计算第一个集合与其后所有集合之间的交集的,与 sinter 命令稍微有点不同的是,sinterstore 命令会将交集计算的结果保存在 destination 集合中,也就是目标集合中,而 sinter 命令则是将交集计算的结果直接返回。如果要计算 setset2 这两个集合之间的交集的话,那我们就还可以使用下面的命令:

sinterstore sinterset set set2

上面这个命令则是将两个集合的交集结果保存到 sinterset 集合当中了,很显然其中就是 45 这两个元素了。

sunion key [key...]

sunion 命令则是用来获取第一个集合与其后所有集合之间的并集,为了便于演示,我们还是使用 setset2 这两个集合,以求它们的并集:

sunion set set2

上面这个命令便是求 set 集合和 set2 集合之间的并集了,很明显可以看出结果就是 1,2,3,4,5,6,7,8

sunionstore destination key [key...]

sunionstore 命令同样也是用来求两个集合之间的并集的,同样的,与 sunion 命令不同的地方就还是在于,sunionstore 命令会将集合之间并集运算的结果保存到 destination 集合中,也就是目标集合当中,而 sunion 命令则是直接返回结果,具体使用如下:

sunionstore sunionset set set2

上面命令则是将 set 集合和 set2 集合并集的结果保存在 destination 集合之中了,很明显其中集合元素也是 1,2,3,4,5,6,7,8

3.5 sortedSet类型数据

下面我们再来介绍 Redis 数据库中的 sortedSet 类型数据,其实就是相当于 Java 中的 Set 集合,不过这里的 sortedSet 是有序集合,那它其中元素是如何保持有序状态的呢?其实就是因为集合中每个元素都会有一个分数,然后每个元素都根据分数的大小进行排列,这样就能保证集合中元素是有序的了,同时,集合中元素是不允许重复的,但是元素所带有的分数是允许重复的,由于 sortedSet 中元素都是有序排列的,因此即使是在集合中部进行增删操作,效率也是很高的,下面我们再具体来看 Redis 中提供了哪些实用的方法供我们使用。

zadd key score member [score member...]

zadd 命令的作用就是往集合中添加元素了,只不过在添加元素时也会指定元素所对应的分数,这样集合中元素就能根据分数大小进行排序了,如果命令中的元素在集合中已经存在了,那这时就会根据命令中元素对应的分数对集合中元素对应的分数进行更新,同时调整集合中元素的排序。下面看具体的使用:

zadd zset 1 a 2 b 3 c 4 d 5 e

这样我们就创建了一个新的有序集合 zset,并往其中添加了 5 个元素,并指定了 5 个元素所对应的分数是多少。

zincrby key increment member

zincrby 命令的作用主要是调整集合中元素所对应的分数,当命令中的元素在集合中并不存在时,我们就会新增该元素到集合之中,并指定该元素所对应的分数为命令中的分数参数,具体使用可以如下:

zincrby zset 2 d

上面的命令就是将集合中的 d 元素所对应的分数增加了 2,从而也就改变了元素之间的排序序列。

zcard key

zcard 命令则是用来获取集合中的元素个数的,具体使用可以如下:

zcard zset

上面这个命令就是用来获取 zset 集合中元素个数了,很明显这里会返回 5

zcount key min max

zcount 命令同样也是用来获取集合中元素数量的,只不过它获取的是分数在 minmax 之间元素的个数,需要注意的是在默认情况下这个区间是闭区间,也就是两边都是包含的,如果想表示开区间的话,则可以使用 ( 符号,如 zcount key (min (max,这样的话就是表示分数在最小值和最大值之间的元素个数是多少了,当然如果只想表示单个闭区间也是可以的,总结起来的话就是哪一边想表示开区间的话哪一边就需要加上 ( 符号。下面看具体使用:

zcount zset 2 4

上面这个命令就是得到分数大于等于 2 并小于等于 4 之间的元素个数。

zrange key start stop [withscores]

zrange 命令是用来根据索引范围获取元素的,命令中的 startstop 都是表示元素在集合之中的索引区间,这里的索引都是从 0 开始的,0 表示的是第一个元素,-1 则是表示的最后一个元素,因此总的来说就相当于是获取某个索引区间之中的元素,需要注意的一点是,命令中还有一个可选参数 withscore,表示的是返回元素时是否带上元素所对应的分数,存在该参数时就会带上元素所对应的分数,下面看具体的使用:

zrange zset 0 -1 withscore

上面的命令则是表示获取集合中所有的元素以及它们各自所对应的分数。

zrevrange key start stop [withscores]

zrevrange 命令同样也是根据索引获取集合中的元素以及元素所对应的分数的,和 zrange 命令唯一的区别就是 zrevrange 命令是将元素进行倒序排列,即根据元素所对应的分数按从大到小进行排列的,具体使用可以如下:

zrevrange zset 0 -1 withscores

上面这个命令同样也是获取 zset 集合中元素以及元素对应分数的,只不过和上面使用 zrange 命令不一样的是,这里所得到的元素排列顺序是和 zrange 命令刚好相反的。

zrangebyscore key min max [withscores] [limit offset count]

zrangebyscore 命令的作用主要是根据元素所对应的分数来获取集合中的元素,即返回分数大于等于 min 并且小于等于 max 的元素,命令中的可选参数 withscorea 则是表示是否需要返回元素所对应的分数,后面还有一个可选参数 limit,则类似于 MySQL 数据中进行分页的关键字 limit,同样的,这里的 offset 也是表示从第多少个元素开始返回,而 count 则是表示每次返回多少个元素,具体使用可以如下:

zrangebyscore zset 2 4

上面命令则是表示返回 zset 集合中分数在 24 之间的元素。

zrevrangebyscore key max min [withscores] [limit offset count]

zrevrangebyscore 命令同样也是根据分数来获取集合中的元素,不过和 zrangebyscore 命令不同的是,zrevrangebyscore 命令是根据分数从高到低来进行排列元素的,需要注意的一点是,在使用 zrevrangebyscore 命令的时候,参数中的分数的最大值是放在前面的,而分数的最小值是放在后面的,具体使用可以如下:

zrevrangebyscore zset 4 2

上面的命令则是获取分数在 24 之间的元素,并按分数从高到低进行排列返回。

zrank key member

zrank 命令是用来获取指定元素在集合中的索引值,当然,索引值是从 0 开始计数的,具体使用可以如下:

zrank zset d

上面这个命令便是获取 d 这个元素在 zset 集合中所在的索引值是多少。

zrevrank key member

zrevrank 命令也是用来获取指定元素在集合中的索引值的,只不过和 zrank 命令相反的是,zrevrank 返回的顺序和 zrank 命令返回的刚好是相反的。具体使用可以如下:

zrevrank zset d

上面这个命令也是获取 d 这个元素在 zset 集合之中的索引值的,其实就是相当于,zrank 命令获取索引值的时候是从前往后数的,而 zrevrank 命令是从后往前数的。

zscore key member

zscore 命令的作用就是获取集合中指定元素所对应的分数,当元素不存在时就直接返回 nil 了,具体使用可以如下:

zscore zset d

上面这个命令就是获取 d 这个元素在 zset 集合中所对应的分数。

zrem key member [member...]

zrem 命令是用来删除集合中指定元素的,如果命令中的元素在集合中并不存在时,则直接忽略,具体使用可以如下:

zrem zset a

上面这个命令就是直接删除掉 zset 集合中的 a 元素。

zremrangebyrank key start stop

zremrangebyrank 命令则是根据元素的索引值来删除集合中的元素,startstop 都是表示的索引值,从 0 开始计数,0 表示的是第一个元素,而 -1 则是表示的最后一个元素,命令的作用就是删除索引值在 startstop 之间的元素,具体使用可以如下:

zremrangebyrank zset 0 0

上面这个命令便是相当于删除 zset 集合中索引值为 0 的元素。

zremrangebyscore key min max

zrenrangebyscore 命令则是根据元素所对应的分数来删除元素,将分数处在 minmax 之间的元素进行删除,具体使用可以如下:

zremrangebyscore zset 5 5

上面命令则是删除 zset 集合中元素对应分数为 5 的元素。

3.6 key通用操作

介绍完了 Redis 数据库中的 5 种基本数据类型,我们再来看一些专门针对于 key 的操作命令,比如列出所有的 key 以及删除某些 key,使用这些命令我们可以完成许多我们想要的功能,因此也是十分实用的,下面具体来看。

keys pattern

keys 命令的作用是列出所有匹配给定模式的 key,其中的 pattern 参数即为正则表达式,在实际生产中,还是应该尽量避免使用该命令的,因为该命令是非常耗时的,对 Redis 数据库的性能消耗也是非常高的,下面我们来看一下具体使用:

keys *

上面这个命令的作用便是列出当前数据库中的所有 key,因为生产环境的数据量都是很大的,因此在生产环境最好不要使用该命令。

del key [key...]

del 命令则是用于删除 Redis 数据库中指定的 key,当然当命令参数中的 key 不存在时,那就会直接将 key 忽略掉了,下面看具体使用:

del name

上面的命令则是直接删除掉 name 这个 key

exists key

exists 命令主要是用来判断指定 key 在当前数据库中是否存在,如果存在则是返回 1,不存在则是返回 0,下面看具体使用:

exists name

上面这个命令便是判断 name 这个 key 是否存在于当前的数据库中。

move key db

move 命令的作用就是将指定的 key 移动到指定的数据库中,当指定的 key 在目标数据库中已经存在或者在当前数据库中不存在时,移动操作就不会发生。在 Redis 的配置文件 redis.conf 中默认配置的数据库数量是 16,而我们默认使用的数据库是 0 号数据库,当我们想切换数据库时就可以使用 select 命令,比如想切换到 10 号数据库,那我们就可以使用 select 10 这个命令,下面我们来看移动指定的 key 到别的数据库的命令。

move name 10

上面这个命令便是将 name 这个 key 移动到了 10 号数据库。

rename key newkey

rename 命令主要是为指定的 key 改名,如果当 newkey 已经存在时,那么命令实际的效果就是 key 所对应的数据会覆盖 newkey 所对应的数据,下面看具体使用:

rename username name

上面的命令就是将 username 这个 key 名称替换为 name,当 name 这个 key 在之前就已经存在时,那么就会使用 username 这个 key 所对应的数据去覆盖 name 这个 key 所对应的数据。

renamenx key newkey

renamenx 这个命令同样也是为指定的 key 改名,但是稍有不用的是,只有当 newkey 不存在时,操作才会执行成功,当返回值为 1 时表示操作成功,为 0 时则表示操作失败,下面看具体使用:

renamenx username name

上面这个命令便是将 username 这个 key 名称修改为 name,当然只有当 name 这个名称不存在时才会成功。

expire key second

expire 命令是用来设置指定 key 的有效时间的,这里的有效时间主要是秒数了,在我们之前的操作中,都是没有设置有效期的,那默认的就是永久有效,但是在实际生产中,所有的数据都是永久有效的话,就会占用相当大空间的内存,同时有些数据保留永久也没有很大意义,这样的话,为了优化,我们一般会对数据设置一个有效时间,这样就能保证缓存空间的大小不会过大,下面我们来看该命令的具体使用:

expire name 100

上面这个命令便是设置 name 这个 key 的有效时间为 100 秒,当时间过了 100 秒之后,该 key 就会自动被删除了,同时对应的数据也就被删除了。

expireat key timestamp

expireat 命令同样也是用来设置有效时间的,不过不同之处在于,expireat 是使用的绝对时间,而不是相对时间,该时间参数是 Unix timestamp 格式的,也就是从 1970-01-01 这一天开始所流经的秒数,因此可以想到使用该命令设置有效时间时是一个很大的数了,具体使用可以如下:

expireat name 100

上面这个命令的作用是设置 name 这个 key 有效时间为 1970-01-01 开始时的 100 秒,当我们再次查看该 key 时,就会发现这个 key 已经过期了,这是因为我们现在肯定远远过去 1970-01-01 这一天很长时间了。

ttl key

ttl 命令用于获取指定 key 剩下的有效生存时间,当默认情况下,key 是永久生效时,使用 ttl 命令会返回 -1,表示是永久有效的,当然对于这个 key 来说,之前指定的有效生存时间也就永久延长了,具体使用可以如下:

ttl name

上面这个命令便是查看 name 这个 key 所剩下的有效生存时间,因为本身时间也会自然流逝,因此每次我们使用该命令时,所返回的结果也是不一样的,不过肯定是慢慢减少的。

persist key

persist 命令的作用是在当 key 有生效时间时,将 key 所对应的生效时间设置为永久,并且可以持久化存储,当 key 的有效时间被设置为永久之后,我们再使用 ttl 命令查看 key 所对应的有效时间就会返回 -1 了,下面看具体使用:

persist name

上面这个命令便是将 name 这个 key 所对应的有效生存时间设置为永久。

randomkey

randomkey 命令的作用便是从当前数据库中随机返回一个 key,当然具体的使用也就是直接调用该命令了。

type key

type 命令用于返回指定 key 所对应的数据类型,因为 Redis 只有 5 种数据类型,因此返回值也就只有这 5 种,具体为 stringlistsethashzset,同时当命令中的 key 在数据库中并不存在时,那这时候就会返回 none 了。具体使用可以如下:

type name

上面这个命令便是返回 name 这个 key 所对应的数据类型了,很明显这时候返回的应该是 string

3.7 事务

下面我们再来看 Redis 数据库中的事务,和 MySQL 数据库中一样,Redis 数据库中的事务也有两个特点:

1.事务是一个单独的隔离操作。
2.事务是一个原子操作。

事务是一个单独的隔离操作,是表示事务中所有的命令都会序列化,按顺序进行执行,在事务执行命令的过程中,不会被其它客户端发送的命令请求所打断;事务是一个原子操作,表示在事务中的多个命令,要么全部执行成功,要么全部执行失败。

关于事务的命令有如下这些,我们先看一下:

multi:表示事务开始
exec:表示执行事务
discard:表示取消事务
watch:表示对指定key进行监视
unwatch:取消对指定key的监视

下面我们通过事务执行的流程,来对事务相关的命令进行详细说明,事务执行的一般流程为先开启事务,然后多个命令进入事务队列,最后我们就是执行事务或者取消事务,这边是一个完整的事务流程,针对上面说的流程,我们的一般命令的执行流程为:

执行事务:

multi
多条命令入队
exec

取消事务:

multi
多条命令入队
discard

需要说明的是,当我们取消事务时,事务中对 key 执行的操作都不会生效,下面我们再看最后的两个命令,watchunwatch 命令。

watch 命令的作用是监视一个或多个 key,在事务执行之前如果有别的命令对 key 有修改操作的话,那事务将会被打断;unwatch 命令则是用于取消 watch 命令对于 key 的监视。

4.使用Java操作Redis数据库

上面我们已经介绍了 Redis 数据库中的 5 种数据类型以及一些常用的命令,而我们在实际开发中,肯定是不能一直使用命令来操作数据库的,一般都会采用开发语言来进行操作,我们当然是使用 Java 语言了,因此我们下面就来介绍应该如何使用 Java 来操作 Redis 数据库。

4.1 使用Jedis连接Redis

对于 Java 语言使用者来说,我们一般都会使用 Jedis 来操作 Redis 数据库,Jedis 是一种十分强大并且简单的操作 Redis 数据库的工具,使用它我们就可以完成对 Redis 数据库的各种操作,平时开发中也能帮助我们快速的完成相关任务,下面先看如何使用 Jedis 连接 Redis 数据库。

首先我们需要得到 Jedisjar 包,我们这里使用的是 jedis-2.8.2.jar,有了 jar 包我们就能使用其中提供的工具类完成我们想要的功能了,所以我们就将上面这个 jar 包加入到我们的工程当中,下面我们就开始写代码连接 Redis 数据库了。

Jedis jedis = new Jedis("10.21.10.27", 6379);
jedis.set("name", "kobe");
String name = jedis.get("name");
System.out.println(name);

上面的代码便是连接 Redis,然后往里面存入一个 keyname 的键值对,我们下面再获取其对应的值,预想是可以得到我们想要的值的,执行代码,我们会发现有报错,报错为 connect timed out,也就是说未连接到 Redis 数据库,那这是什么原因呢?其实我们在之前就已经提到过,Redis 出于安全考虑,默认情况下只允许本地访问,因此当我们在 windows 中写代码访问 Linux 中安装的 Redis 数据库时是访问不了的,因此我们应该允许 Redis 数据库被外围访问,这个设置是在 Redis 的配置文件 redis.conf 当中进行设置的,也就是:

bind 127.0.0.1

我们需要做的修改就是使用 # 将这句设置注释掉,注释掉之后,Redis 数据库就能被外围所访问了,因此当我们修改好配置文件之后,就可以重启 Redis 数据库了,然后我们就可以接着执行上面的代码,看看是否可以了,执行的话发现还是不行,这次的报错信息是没有设置密码,因此我们需要设置 Redis 数据库的密码,同样也是在配置文件 redis.conf 当中,就是:

# requirepass foobared

默认情况下是被注释的,也就是没有密码的,然后我们就需要将上面注释放开,然后设置新的密码,比如下面这样:

requirepass admin

这样就是设置 Redis 数据库的密码为 admin 了,当然修改了 Redis 的密码设置之后,同样也是需要重启 Redis 数据库的,这时候我们的代码也需要做一些调整,也就是使用密码去做认证,具体的代码如下:

Jedis jedis = new Jedis("10.21.10.27", 6379);
jedis.auth("admin");
jedis.set("name", "kobe");
String name = jedis.get("name");
System.out.println(name);

这样再执行代码的话我们就可以得到预期的结果了,还有一点需要说明的是,当我们 Redis 的服务端设置了密码之后,我们再使用 Redis 命令行客户端进行连接时,可以使用如下的命令:

./redis-cli -h 127.0.0.1 -p 6379 -a admin

在上面的命令中,我们指定密码时使用的 -a 参数,这样的话当服务器端设置了密码我们也能在客户端进行连接了。

4.2 使用Jedis操作string类型数据

下面我们就具体来看使用 Jedis 操作 Redis 数据库中基本的数据类型了,其实 Jedis 中提供的方法和 Redis 中提供的命令名称一般都是一致的,因此使用起来也是非常简单与便于我们理解的,下面我们先看 string 类型数据。

由于之前已经介绍了 Redis 中所提供命令的作用,而且 Jedis 提供方法的名称也是和命令一致的,那我们就直接看代码了。

public class RedisDemo2 {

    private Jedis jedis;

    @Before
    public void createJedis() {
        jedis = new Jedis("10.21.10.27", 6379);
        jedis.auth("admin");
    }

    // set和get
    @Test
    public void test() {
        jedis.set("name", "kobe");
        String name = jedis.get("name");
        System.out.println(name);
    }

    // mset和mget
    @Test
    public void test2() {
        jedis.mset("password", "admin", "age", "20");
        List<String> values = jedis.mget("name", "password", "age");
        System.out.println(values);
    }

    // append setrange getrange
    @Test
    public void test3() {
        // jedis.append("name", " is boy");
        System.out.println(jedis.get("name"));

        jedis.setrange("name", 8, "girl");
        System.out.println(jedis.get("name"));

        System.out.println(jedis.getrange("name", 8, -1));
    }
}

看上面代码中的每一个测试方法应该都是非常好理解的,因为每个方法的作用都是和命令是一致的,这里只是简单地演示了几个方法,不过其它的方法也是类似的,因此大家可以自己探索。

4.3 使用Jedis操作list类型数据

下面我们就开始介绍使用 Jedis 操作 Redis 数据库中的 list 类型数据,其实也和 string 类型数据一样的,使用起来非常简单,因为提供的 api 方法和命令名称是一致的,下面我们就直接看代码了。

public class RedisDemo3 {

    private Jedis jedis;

    @Before
    public void createJedis() {
        jedis = new Jedis("10.21.10.27", 6379);
        jedis.auth("admin");
    }

    @After
    public void destroyJedis() throws IOException {
        if (null != jedis) {
            jedis.close();
        }
    }

    // lpush lrange
    @Test
    public void test() {
        jedis.lpush("names", "tom", "jerry", "kobe", "james");
        List<String> names = jedis.lrange("names", 0, -1);
        System.out.println(names);
    }

    // lset
    @Test
    public void test2() {
        jedis.lset("names", 0, "wade");
        List<String> names = jedis.lrange("names", 0, -1);
        System.out.println(names);
    }

    // lindex
    @Test
    public void test3() {
        String value = jedis.lindex("names", 1);
        System.out.println(value);
    }

    // linsert
    @Test
    public void test4() {
        jedis.linsert("names", LIST_POSITION.BEFORE, "kobe", "james");
        List<String> names = jedis.lrange("names", 0, -1);
        System.out.println(names);
    }

    // lrem
    @Test
    public void test5() {
        jedis.lrem("names", 1, "james");
        List<String> names = jedis.lrange("names", 0, -1);
        System.out.println(names);
    }
}

4.4 使用Jedis操作hash类型数据

下面我们开始介绍使用 Jedis 操作 Redis 数据库中的 hash 类型数据,同样的我们也是直接给出代码了。

public class RedisDemo4 {

    private Jedis jedis;

    @Before
    public void createJedis() {
        jedis = new Jedis("10.21.10.27", 6379);
        jedis.auth("admin");
    }

    @After
    public void destroyJedis() throws IOException {
        if (null != jedis) {
            jedis.close();
        }
    }

    // hset hget
    @Test
    public void test() {
        jedis.hset("user", "username", "tom");
        String username = jedis.hget("user", "username");
        System.out.println(username);
    }

    // hmset hmget
    @Test
    public void test2() {
        Map<String, String> map = new HashMap<>();
        map.put("password", "123");
        map.put("sex", "male");
        jedis.hmset("user", map);

        List<String> values = jedis.hmget("user", "username", "password", "sex");
        System.out.println(values);
    }

    // hgetall hkeys kvals
    @Test
    public void test3() {
        Map<String, String> map = jedis.hgetAll("user");
        for (String key : map.keySet()) {
            System.out.println(key + "  " + map.get(key));
        }

        Set<String> keys = jedis.hkeys("user");
        System.out.println(keys);

        List<String> vals = jedis.hvals("user");
        System.out.println(vals);
    }

    // hdel
    @Test
    public void test4() {
        jedis.hdel("user", "sex");
        Map<String, String> map = jedis.hgetAll("user");
        for (String key : map.keySet()) {
            System.out.println(key + "  " + map.get(key));
        }
    }
}

4.5 使用Jedis操作set类型数据

下面我们再来介绍使用 Jedis 操作 Redis 数据库中的 set 类型数据,同样的我们直接看代码:

public class RedisDemo5 {

    private Jedis jedis;

    @Before
    public void createJedis() {
        jedis = new Jedis("10.21.10.27", 6379);
        jedis.auth("admin");
    }

    @After
    public void destroyJedis() throws IOException {
        if (null != jedis) {
            jedis.close();
        }
    }

    // sadd smembers
    @Test
    public void test() {
        jedis.sadd("language", "java", "c++", "ruby", "python");
        Set<String> smembers = jedis.smembers("language");
        System.out.println(smembers);
    }

    // srem
    @Test
    public void test2() {
        jedis.srem("language", "java");
        Set<String> smembers = jedis.smembers("language");
        System.out.println(smembers);
    }

    // sdiff
    @Test
    public void test3() {
        jedis.sadd("language", "java", "c++", "ruby", "python");
        jedis.sadd("language2", "c", "c++", "c#", "php");
        Set<String> sdiff = jedis.sdiff("language", "language2");
        System.out.println(sdiff);
    }

    // sinter
    @Test
    public void test4() {
        jedis.sadd("language", "java", "c++", "ruby", "python");
        jedis.sadd("language2", "c", "c++", "c#", "php");
        Set<String> sinter = jedis.sinter("language", "language2");
        System.out.println(sinter);
    }

    // sunion
    @Test
    public void test5() {
        jedis.sadd("language", "java", "c++", "ruby", "python");
        jedis.sadd("language2", "c", "c++", "c#", "php");
        Set<String> sunion = jedis.sunion("language", "language2");
        System.out.println(sunion);
    }
}

4.6 使用Jedis操作sortedSet类型数据

下面我们开始介绍使用 Jedis 操作 Redis 数据库中的 sortedSet 类型数据,同样的我们直接看代码:

public class RedisDemo6 {

    private Jedis jedis;

    @Before
    public void createJedis() {
        jedis = new Jedis("10.21.10.27", 6379);
        jedis.auth("admin");
    }

    @After
    public void destroyJedis() throws IOException {
        if (null != jedis) {
            jedis.close();
        }
    }

    // zadd zrange zrangeByScore
    @Test
    public void test() {
        Map<String, Double> map = new HashMap<>();
        map.put("张三", 60.0);
        map.put("李四", 70.0);
        map.put("王五", 80.0);
        map.put("赵六", 90.0);
        map.put("孙七", 50.0);
        jedis.zadd("zset", map);
        Set<String> zset = jedis.zrange("zset", 0, -1);
        System.out.println(zset);

        Set<String> zset2 = jedis.zrangeByScore("zset", 70, 90);
        System.out.println(zset2);
    }

    // zrangeWithScores
    @Test
    public void test2() {
        Map<String, Double> map = new HashMap<>();
        map.put("张三", 60.0);
        map.put("李四", 70.0);
        map.put("王五", 80.0);
        map.put("赵六", 90.0);
        map.put("孙七", 50.0);
        jedis.zadd("zset", map);
        Set<Tuple> tuples = jedis.zrangeWithScores("zset", 0, -1);
        for (Tuple tuple : tuples) {
            System.out.println(tuple.getScore() + "  " + tuple.getElement());
        }
    }

    // zrank
    @Test
    public void test3() {
        Map<String, Double> map = new HashMap<>();
        map.put("张三", 60.0);
        map.put("李四", 70.0);
        map.put("王五", 80.0);
        map.put("赵六", 90.0);
        map.put("孙七", 50.0);
        jedis.zadd("zset", map);
        Long zrank = jedis.zrank("zset", "李四");
        System.out.println(zrank);
    }

    // zscore
    @Test
    public void test4() {
        Map<String, Double> map = new HashMap<>();
        map.put("张三", 60.0);
        map.put("李四", 70.0);
        map.put("王五", 80.0);
        map.put("赵六", 90.0);
        map.put("孙七", 50.0);
        jedis.zadd("zset", map);
        Double zscore = jedis.zscore("zset", "张三");
        System.out.println(zscore);
    }

    // zrem
    @Test
    public void test5() {
        Map<String, Double> map = new HashMap<>();
        map.put("张三", 60.0);
        map.put("李四", 70.0);
        map.put("王五", 80.0);
        map.put("赵六", 90.0);
        map.put("孙七", 50.0);
        jedis.zadd("zset", map);
        jedis.zrem("zset", "王五");
        Set<Tuple> tuples = jedis.zrangeWithScores("zset", 0, -1);
        for (Tuple tuple : tuples) {
            System.out.println(tuple.getScore() + "  " + tuple.getElement());
        }
    }
}

4.7 使用Jedis进行key的通用操作

下面我们开始介绍使用 Jedis 进行 Rediskey 的通用操作,比如获取 Redis 数据库中所有的 key,删除某些 key,对 key 设置过期时间之类的,我们先看具体的代码:

public class RedisDemo7 {

    private Jedis jedis;

    @Before
    public void createJedis() {
        jedis = new Jedis("10.21.10.27", 6379);
        jedis.auth("admin");
    }

    @After
    public void destroyJedis() throws IOException {
        if (null != jedis) {
            jedis.close();
        }
    }

    // keys pattern
    @Test
    public void test() {
        Set<String> keys = jedis.keys("*");
        System.out.println(keys);
    }

    // del
    @Test
    public void test2() {
        Long del = jedis.del("user");
        System.out.println(del);
    }

    // 关于key的时间设置
    @Test
    public void test3() throws ParseException {
        jedis.set("name", "kobe");
        String name = jedis.get("name");
        System.out.println(name);
        Long ttl = jedis.ttl("name");
        System.out.println(ttl);

        jedis.expire("name", 100);
        Long ttl2 = jedis.ttl("name");
        System.out.println(ttl2);

        jedis.persist("name");
        Long ttl3 = jedis.ttl("name");
        System.out.println(ttl3);
    }
}

关于对 key 设置过期时间,在默认情况下,keyRedis 数据库中是永久有效的,我们使用 ttl() 方法查看 key 的有效期时,返回值为 -1,当我们使用 expire() 方法为 key 设置了有效期之后,然后再使用 ttl() 方法获取有效期时,那返回的就是剩余的有效期了,最后 persist() 方法是将 key 的有效期限设置为永远,也就是使 keyRedis 数据库中永久有效。

5.Redis数据持久化

下面我们来介绍一下 Redis 的数据持久化方面知识,Redis 数据库的数据持久化方式一共有 2 种,那就是 RDBAOF 持久化方式,我们先分别介绍一下:

RDB持久化:在指定的时间间隔内将内存中的数据集快照写入到磁盘上面。
AOF持久化:以日志的形式记录服务器端进行的每一个写操作,在Redis数据库启动之时,会读取该文件构建数据库,保证启动之后数据库中的数据是完整的。

了解了 Redis 数据库的两种持久化方式之后,我们可以在 Redis 数据库中使用的持久化机制就有下面 4 种了。

1.RDB持久化;
2.AOF持久化;
3.同时应用RDB和AOF;
4.无持久化。

介绍了 Redis 数据库持久化机制之后,下面再对 RDBAOF 这两种持久化方式进行详细说明。

5.1 RDB持久化方式

Redis 数据库中默认使用的持久化方式便是 RDB 方式了,因此如果我们不修改相关设置的话,那我们就是使用的这种持久化方式,我们在 Redis 数据库的配置文件 redis.conf 中可以看到以下配置:

save 900 1
save 300 10
save 60 10000

这便是针对于 RDB 持久化方式进行的设置,前面一个数字为时间的秒数,后一个数字为操作 key 的个数,比如 save 900 1,就是表示如果在 900 秒以内有 1key 被修改的话,那么就会将内存中的数据集快照写入到硬盘中的 dump.rdb 文件,当然这个 dump.rdb 文件名称也是在 redis.conf 配置文件中进行配置的,这样的话,其它的配置也是一样的了,都是在多少秒之内有多少个 key 被修改就会将内存快照写入到磁盘当中。

5.2 AOF持久化方式

如果想要启用 AOF 方式的持久化的话,那就需要修改 redis.conf 文件当中的一个配置,也就是:

appendonly no

修改的方式也就是将其中的 no 改为 yes,这样的话便是启用 AOF 的持久化方式了。

AOF 持久化方式的策略一共有三种,在 redis.conf 配置文件中的默认配置为:

# appendfsync always
appendfsync everysec
# appendfsync no

可以看出一共是三种方式,第一种为每次有修改操作时都会追加写 AOF 文件;第二种为每一秒进行追加写 AOF 文件;第三种则是不同步。从上面的设置可以看出默认是采用的第二种方式,也就是每秒钟进行追加写 AOF 文件,这种方式通常情况也是最适中的。

关于 AOF 持久化方式,还有一点需要说明的是,因为是采用的追加写日志的方式,因此我们是可以对日志文件进行瘦身处理的,也就是只保留对 key 修改最近的一条,这样的话也是能保证数据是完整的,其实在 redis.conf 文件中也有配置,那就是:

auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

第一条设置是说当对操作记录追加写的比率达到 100% 时,就会进行自动瘦身了;第二条则是当日志文件大小达到 64mb 时,也会进行自动瘦身操作。其实除了配置之外,我们还可以使用命令来主动进行文件瘦身,那就是 bgrewriteaof 命令了。

5.3 RDB和AOF方式对比

上面已经介绍了 RDBAOF 方式的相关特性,下面我们就将它们对比来看一下彼此的优缺点。

RDB方式:
优势:
1.数据的备份和恢复非常方便,因为一个数据库只有一个持久化文件;
2.性能更高;对Redis服务进程来说,当需要做持久化时,只需要创建出子进程,然后让子进程去做这些持久化的工作,这样就可以避免服务进程进行IO操作了;
3.对比AOF来说,当持久化文件较大时,RDB方式的启动效率会更高。

劣势:
1.当系统在持久化时宕机,那还没来得及写入磁盘的数据就会丢失了;
2.由于RDB方式是通过创建子进程来协助完成数据持久化工作的,当要持久化的数据集较大时,可能会导致整个服务器停止服务几百毫秒;

AOF方式:
优势:
1.更高的数据安全性,也就是数据持久性,提供了3种同步策略,每秒同步,每次修改同步和不同步;
2.由于对持久化文件是采用追加写入的方式,因此即使在写入过程中发生服务器宕机,也不会影响已经写入的数据内容;
3.当持久化文件过大时,Redis可以进行自动瘦身;
4.AOF日志文件格式清晰,便于理解,很容易用该文件完成数据的重建。

劣势:
1.对于相同的数据来说,AOF文件要大于RDB持久化文件;
2.根据同步策略的不同,AOF运行的效率会慢于RDB方式。每秒同步策略的效率是比较高的,同步禁用策略的效率和RDB是一样的。

6.总结

其实上面的 Redis 知识更多的是一些基础类知识,也是很容易掌握的,通过自己写的过程,也能使自己的学习更加深刻,这便是我写博客的目的,掌握了最基础的知识之后,自己也能有信心去学更多精深的内容,保持乐观,耐心地积累下去吧。

坚持原创技术分享,您的支持将鼓励我继续创作!