简单的kafka&redis学习之redis
2. Redis
2.1 什么是Redis
Redis是一种面向 “Key-Value” 数据类型的内存数据库,可以满足我们对海量数据的快速读写需求,Redis是一个 NoSQL 数据库,NoSQL的全称是not only sql,不仅仅是SQL,泛指非关系型数据库,这种类型的数据库不支持SQL语法。
首先Redis是一种内存数据库,它的数据都是放在内存里面的,然后Redis中存储的数据都是key-value类型的,其中redis中的key只能是字符串,value支持多种数据类型
常见的有string、hash、list、set、sortedset
等
- 字符串 string
- 哈希 hash,类似于java中的hashmap
- 字符串列表 list
- 字符串集合 set 不重复,无序
- 有序集合sorted set ,不重复,有序
2.2 Redis的特点
接下来看一下Redis的一些特点
- 高性能:Redis读的速度是11W次/s,写的速度是8.1W次/s
- 原子性:保证数据的准确性
- 持久存储:支持两种方式的持久化,RDB和AOF,可以把内存中的数据持久化到磁盘中
- 支持主从:master-slave架构,可以实现负载均衡、高可用
- 支持集群:从3.0版本开始支持
注意:Redis是一个 单线程的服务,作者之所以这么设计,主要是为了保证redis的快速,高效,如果涉及了多线程,就需要使用锁机制来解决并发问题,这样执行效率反而会打折扣。
2.3 Redis安装部署
下面来看一下redis的安装部署,首先下载redis,使用此链接下载,可以显示Redis目前所有的版本 http://download.redis.io/releases/,我们选择目前比较稳定的5.0.9版本。将下载好的安装包上传到bigdata04机器的/data/soft目录下
将下载好的安装包上传到bigdata04机器的/data/soft目录下
1:解压
[root@bigdata04 soft]# tar -zxvf redis-5.0.9.tar.gz
2:编译+安装
[root@bigdata04 soft]# cd redis-5.0.9
[root@bigdata04 redis-5.0.9]# make
[root@bigdata04 redis-5.0.9]# make install
只要不报错就说明编译安装成功。由于redis需要依赖于C语言环境(yum install gcc),如果你安装的centos镜像是精简版,会缺失c语言的依赖,所以需要安装C语言环境才可以编译成功。我们在这使用的centos镜像是完整版,里面是包含C语言环境的,所以就不存在这个问题了。
如果yum install gcc后执行make报 [致命错误:jemalloc/jemalloc.h:没有那个文件或目录] 错误,需要再执行 make MALLOC=libc
3:修改redis.conf配置文件
[root@bigdata04 redis-5.0.9]# vi redis.conf
daemonize yes
logfile /data/soft/redis-5.0.9/log
bind 127.0.0.1 192.168.182.103
- daemonize参数的值默认是no,表示在前台启动Redis,但是Redis是一个数据库,我们希望把它放到后台运行,所以将参数的值改为yes
- logfile 参数的值默认为空,表示redis会将日志输出到/dev/null里面,也就是不保存了,建议在这设置一个日志路径记录redis的日志,便于后期排查问题。
- bind 参数可以绑定指定ip,这样就可以通过这里指定的ip来访问redis服务了,可以在后面指定当前机器的本地回环地址(127.0.0.1)和内网地址(192.168.182.103),指定本地回环地址是为了能够在本机自己连自己比较方便。指定内网地址是为了能够让公司局域网内的其它服务器也能连到这个redis
如果你这台机器有外网地址的话不建议在这配置,因为使用外网地址的话就不安全了,容易受到网络攻击。
4:启动redis
```shell
[root@bigdata04 redis-5.0.9]# redis-server redis.conf
```
5:验证
注意:redis不是java程序,所以使用jps命令查不到,需要使用ps命令查看redis的进程
```shell
[root@bigdata04 redis-5.0.9]# ps -ef|grep redis
root 5828 1 0 16:12 ? 00:00:00 redis-server 127.0.0.1:6379
```
6:连接redis数据库
[root@bigdata04 redis-5.0.9]# redis-cli
127.0.0.1:6379>
注意:使用redis-cli默认可以连接本地的redis
其实redis-cli后面省略了-h 127.0.0.1
和 -p 6379
[root@bigdata04 redis-5.0.9]# redis-cli -h 127.0.0.1 -p 6379
此时使用内网ip也能连接,这样其实我们就可以在其它安装有redis-cli客户端的机器上连接这个redis服务了。
[root@bigdata04 redis-5.0.9]# redis-cli -h 192.168.182.103 -p 6379
192.168.182.103:6379>
7:停止redis数据库
暴力一点的方式是使用kill命令直接杀进程
不过redis提供的有停止命令
[root@bigdata04 redis-5.0.9]# redis-cli
127.0.0.1:6379> shutdown
not connected>
或者这样停止也是可以的
[root@bigdata04 redis-5.0.9]# redis-cli shutdown
2.4 Redis基础命令
下面我们来看一下Redis中的基础命令
先启动redis服务,使用redis-cli客户端连到redis数据库里面
[root@bigdata04 redis-5.0.9]# redis-server redis.conf
[root@bigdata04 redis-5.0.9]# redis-cli
127.0.0.1:6379>
-
获得符合规则的键:keys
keys 后面可以指定正则表达式
127.0.0.1:6379> keys * (empty list or set) 127.0.0.1:6379> set a 1 OK 127.0.0.1:6379> keys * 1)"a" 127.0.0.1:6379> keys a* 1) "a" 127.0.0.1:6379> keys a+ (empty list or set)
注意:在生产环境下建议禁用keys命令,因为这个命令会查询过滤redis中的所有数据,可能会造成服务阻塞,影响redis执行效率。
如果有类似的查询需求建议使用scan,scan命令用于迭代当前数据库中的key集合,它支持增量式迭代,每次执行只会返回少量元素,所以它可以用于生产环境,而不会出现像keys 命令那样可能会阻塞服务器的问题。
SCAN命令是一个基于游标的迭代器。这意味着命令每次被调用都需要使用上一次调用返回的游标作为该次调用的游标参数,以此来延续之前的迭代过程
当SCAN命令的游标参数被设置为 0 时, 服务器将开始一次新的迭代, 而当服务器向用户返回值为 0 的游标时, 表示迭代已结束。向redis中初始化一批数据
127.0.0.1:6379> set a1 1 OK 127.0.0.1:6379> set a2 1 OK 127.0.0.1:6379> set a3 1 OK 127.0.0.1:6379> set a4 1 OK 127.0.0.1:6379> set a5 1 OK 127.0.0.1:6379> set a6 1 OK 127.0.0.1:6379> set a7 1 OK 127.0.0.1:6379> set a8 1 OK 127.0.0.1:6379> set a9 1 OK 127.0.0.1:6379> set a10 1 OK
使用scan迭代数据,后面游标参数指定为0,表示从头开始迭代key
127.0.0.1:6379> scan 0 1) "3" 2) 1) "a9" 2) "a3" 3) "a1" 4) "a10" 5) "a8" 6) "a5" 7) "a4" 8) "a" 9) "a7" 10) "a6"
SCAN 命令的返回值是一个包含两个元素的数组,第一个元素是用于进行下一次迭代的新游标,而第二个元素则是一个数组, 这个数组中包含了所有被迭代出来的元素。默认情况下scan返回10条数据,所以这样执行效果也是一样的
127.0.0.1:6379> scan 0 count 10 1) "3" 2) 1) "a9" 2) "a3" 3) "a1" 4) "a10" 5) "a8" 6) "a5" 7) "a4" 8) "a" 9) "a7" 10) "a6"
scan命令此时返回的游标为3,注意,游标的值并不等于返回的数据量。如果想要继续往下面迭代数据的话,下一次执行scan的时候需要指定之前返回的游标,redis会根据这个游标继续往下面迭代
127.0.0.1:6379> scan 3 count 10 1) "0" 2) 1) "a2"
这一次使用scan命令,返回的游标为0,表示迭代已经结束,整个redis中的key都被迭代完了。redis中一共有11个key,第一次使用scan 0获取到了10个key,第二次获取到了1个key,没有问题。
注意:大家在下面练习的时候可能会发现你那边第一次返回的游标和我这边显示的不一样,那也很正常,因为你会发现你返回的这个数据集的顺序也是不一样的。
所以,如果redis中有很多key,我们可以使用scan命令来迭代,一次迭代一部分,不至于造成阻塞,如果redis中的key比较少,那么使用keys * 也是可以的。如果想要在迭代key的时候对key进行过滤,可以在scan后面指定match参数,match后面可以指定正则表达式
127.0.0.1:6379> scan 0 match a[1-5] count 10 1) "3" 2) 1) "a3" 2) "a1" 3) "a5" 4) "a4"
此时实际返回的key的数量是4个,但是游标还是3,相当于还是迭代了10条数据,只不过不满足条件的没有返回而已。
-
判断键是否存在:exists
127.0.0.1:6379> exists a (integer) 1 127.0.0.1:6379> exists b (integer) 0
-
删除键:del
127.0.0.1:6379> del a (integer) 1
注意:del也支持一次删除多个key
127.0.0.1:6379> del a1 a2 (integer) 2
-
获得键值的类型:type
返回值可能是这五种类型(string,hash,list,set,zset)
127.0.0.1:6379> set a 1 OK 127.0.0.1:6379> type a string
这个命令可以帮我们快速识别某一个key中存储的数据是什么类型的,因为针对存储了不同类型值的key,操作的命令是不一样的。
-
帮助命令:help
127.0.0.1:6379> help set SET key value [EX seconds] [PX milliseconds] [NX|XX] summary: Set the string value of a key since: 1.0.0 group: string
-
退出客户端:quit/exit
127.0.0.1:6379> quit
不过我还是习惯使用
ctrl+c
退出redis-cli客户端,最后有一点需要注意:Redis的命令不区分大小写,但是key的名称需要区分大小写!
2.5 Redis多数据库特性
Redis默认支持 16 个数据库,通过databases
参数控制的
这个参数在redis.conf配置文件中
[root@bigdata04 redis-5.0.9]# cat redis.conf | grep databases
# Set the number of databases. The default database is DB 0, you can select
# dbid is a number between 0 and 'databases'-1
databases 16
# Compress string objects using LZF when dump .rdb databases?
每个数据库对外都是以一个从0开始的递增数字命名,不支持自定义
Redis默认选择的是0号数据库,可以通过 select 命令切换
127.0.0.1:6379> select 0
OK
127.0.0.1:6379> select 1
OK
127.0.0.1:6379[1]> select 2
OK
127.0.0.1:6379[2]> select 15
OK
127.0.0.1:6379[15]> select 16
(error) ERR DB index is out of range
一般在工作中会使用2~3个数据库,可以根据业务类型来分库,不同业务的数据存到不同的库里面,还有一种用法是,一个库作为测试库,一个库作为正式库。
如果没有特殊需求,一般使用0号数据库就可以了,这个库使用起来比较方便,默认就是0号库,不需要使用select切换。具体在工作中怎么用都行,只要理解它的特性就可以了。但是有一点需要注意:多个数据库之间并不是完全隔离的,如果使用flushall命令,则会清空redis中所有数据库内的数据。
并且我们在redis中使用多个库,并不能提高redis的存储能力,因为默认这16个库共用redis的内存存储空间,如果想要提高redis的存储能力,需要给我们的服务器增加内存才可以。
127.0.0.1:6379[15]> set x 1
OK
127.0.0.1:6379[15]> flushall
OK
127.0.0.1:6379[15]> keys *
(empty list or set)
127.0.0.1:6379[15]> select 0
OK
127.0.0.1:6379> keys *
(empty list or set)
如果只想清空当前数据库中的数据,可以使用flushdb
127.0.0.1:6379> select 15
OK
127.0.0.1:6379[15]> set a 1
OK
127.0.0.1:6379[15]> keys *
1) "a"
127.0.0.1:6379[15]> flushdb
OK
2.6 Redis数据类型
下面来看一下redis中的常用数据类型,这些数据类型都是针对于redis中的value而言的,因为key都是字符串
- string
- hash
- list
- set
- sorted set
2.6.1 Redis数据类型之string
字符串类型是redis中最基本的数据类型,它能存储任何形式的内容,包含二进制数据,甚至是一张图片
一个字符串类型的值存储的最大容量是1GB,一般情况下我们存储的单条数据肯定是达不到的这个限值的,所以大家不用担心
string类型比较适合存储类型单一的数据
针对string类型主要有下面这些常见命令:
命令 | 格式 | 解释 |
---|---|---|
set | set key value | 给key设置一个Value(字符串类型的) |
get | get key | 获取key的值 |
incr | incr key | 对key的值递加+1(值必须是数字) |
decr | decr key | 对key的值递减-1(值必须是数字) |
strlen | strlen key | 获取key值的长度 |
添加数据 set
127.0.0.1:6379> set str a
OK
查询数据 get
127.0.0.1:6379> get str
"a"
一次添加多条数据
127.0.0.1:6379> mset str1 a1 str2 a2
OK
一次查询多条数据
127.0.0.1:6379> mget str1 str2
1) "a1"
2) "a2"
递增1
127.0.0.1:6379> set num 1
OK
127.0.0.1:6379> incr num
(integer) 2
127.0.0.1:6379> get num
"2"
递减1
127.0.0.1:6379> decr num
(integer) 1
127.0.0.1:6379> get num
"1"
递增指定数值(整数类型)
127.0.0.1:6379> incrby num 2
(integer) 3
127.0.0.1:6379> get num
"3"
127.0.0.1:6379> incrby num 2.1
(error) ERR value is not an integer or out of range[注意:增量数值只支持integer类型]
递减指定数值(整数类型)
127.0.0.1:6379> decrby num 2
(integer) 1
127.0.0.1:6379> get num
"1"
递增指定数值(float类型)
127.0.0.1:6379> incrbyfloat num 2.1
"3.1"
127.0.0.1:6379> get num
"3.1"
获取指定key的value长度
127.0.0.1:6379> get str
"a"
127.0.0.1:6379> strlen str
(integer) 1
127.0.0.1:6379> set str abcd
OK
127.0.0.1:6379> strlen str
(integer) 4
2.6.2 Redis数据类型之hash
hash类型的值存储了字段和字段值的映射,字段和字段值只能是字符串,不支持其他数据类型。hash类型的值至多存储2的32次方-1
个字段,一般情况下我们也达不到这个极限,hash类型比较适合存储对象,因为对象里面是有一些属性和值的,我们就可以把这些属性和值存储到这个hash类型里面
针对hash类型主要有下面这些常见命令:
命令 | 格式 | 解释 |
---|---|---|
hset | hset key field value | 向hash中添加字段和值 |
hget | hget key field | 获取hash中指定字段的值 |
hgetall | hgetall key | 获取hash中所有的字段和值 |
hexists | hexists key field | 判断hash中是否包含指定字段 |
hincrby | hincrby key field num | 对hash中指定字段的值递增 |
hdel | hdel key field | 删除hash中指定的字段 |
hkeys/hvals | hkeys/hvals key | 获取hash中所有字段或字段值 |
hlen | hlen key | 获取hash中所有字段的数量 |
添加数据 hget
127.0.0.1:6379> hset user:1 name zs
(integer) 1
查询数据 hget
127.0.0.1:6379> hget user:1 name
"zs"
向一个hash中同时添加多个k-v hmset
127.0.0.1:6379> hmset user:2 name lisi age 18
OK
查询一个hash数据中多个k的值 hmget
127.0.0.1:6379> hmget user:2 name age
1) "lisi"
2) "18"
查询一个hash数据中的所有k-v hgetall
127.0.0.1:6379> hgetall user:2
1) "name"
2) "lisi"
3) "age"
4) "18"
判断一个hash数据中是否存在指定k hexists
127.0.0.1:6379> hexists user:2 name
(integer) 1
127.0.0.1:6379> hexists user:2 city
(integer) 0
对一个hash数据中指定k的v进行递增 hincrby
127.0.0.1:6379> hincrby user:2 age 1
(integer) 19
127.0.0.1:6379> hget user:2 age
"19"
删除一个hash数据中的指定k hdel
127.0.0.1:6379> hset user:2 city beijing
(integer) 1
127.0.0.1:6379> hdel user:2 city
(integer) 1
获取一个hash数据中的所有k hkeys
127.0.0.1:6379> hkeys user:2
1) "name"
2) "age"
获取一个hash数据中的所有v hvals
127.0.0.1:6379> hvals user:2
1) "lisi"
2) "19"
获取一个hash数据中有多少个k hlen
127.0.0.1:6379> hlen user:2
(integer) 2
2.6.3 Redis数据类型之list
list是一个有序的字符串列表,列表内部是使用双向链表(linked list)实现的,list列表类型的值最多可以存储2的32次方-1
个元素,一般我们也达不到这个限值。list类型比较适合作为队列使用,使用lpush+rpop可以实现先进先出的队列
针对list类型主要有下面这些常见命令:
命令 | 格式 | 解释 |
---|---|---|
lpush | lpush key value | 从列表左侧添加元素 |
rpush | rpush key value | 从列表右侧添加元素 |
lpop | lpop key | 从列表左侧弹出元素 |
rpop | rpop key | 从列表右侧弹出元素 |
llen | llen key | 获取列表的长度 |
lrange | lrange key start stop | 获取列表指定区间的元素 |
lindex | lindex key index | 获取列表指定角标的元素 |
lset | lset key index value | 修改列表中指定角标的元素 |
添加元素(左侧添加) lpush
127.0.0.1:6379> lpush list1 a
(integer) 1
127.0.0.1:6379> lpush list1 b
(integer) 2
取出元素(左侧取元素) lpop
127.0.0.1:6379> lpop list1
"b"
127.0.0.1:6379> lpop list1
"a"
127.0.0.1:6379> lpop list1
(nil)
添加元素(右侧添加) rpush
127.0.0.1:6379> rpush list2 x
(integer) 1
127.0.0.1:6379> rpush list2 y
(integer) 2
取出元素(右侧取元素) rpop
127.0.0.1:6379> rpop list2
"y"
127.0.0.1:6379> rpop list2
"x"
列表长度 llen
127.0.0.1:6379> lpush list3 a b c d
(integer) 4
127.0.0.1:6379> llen list3
(integer) 4
获取列表中的元素 lrange
127.0.0.1:6379> lrange list3 0 -1
1) "d"
2) "c"
3) "b"
4) "a"
查询指定角标元素 lindex
127.0.0.1:6379> lindex list3 1
"c"
修改指定角标元素 lset
127.0.0.1:6379> lset list3 1 m
OK
127.0.0.1:6379> lrange list3 0 -1
1) "d"
2) "m"
3) "b"
4) "a"
2.6.4 Redis数据类型之set
set是一个集合,set集合中的元素都是不重复的,无序的。set集合类型的值最多可以存储2的32次方-1个
元素。set集合比较适合用在去重的场景下,因为它里面的元素是都不重复的
针对set类型主要有下面这些常见命令:
命令 | 格式 | 解释 |
---|---|---|
sadd | sadd key value | 向集合中添加元素 |
smembers | smembers key | 获取集合中所有元素 |
srem | srem key value | 从集合中删除指定元素 |
sismember | sismember key value | 判断集合中是否包含指定元素 |
sdiff | sdiff key1 key2 | 获取两个集合的差集 |
sinter | sinter key1 key2 | 获取两个集合的交集 |
sunion | sunion key1 key2 | 获取两个集合的并集 |
scard | scard key | 获取集合中元素的数量 |
向集合中添加元素 sadd
127.0.0.1:6379> sadd set1 a
(integer) 1
127.0.0.1:6379> sadd set1 b
(integer) 1
获取集合中所有元素 smembers
127.0.0.1:6379> smembers set1
1) "b"
2) "a"
删除集合中的元素 srem
127.0.0.1:6379> srem set1 a
(integer) 1
判断元素是否存在集合中 sismember
127.0.0.1:6379> sismember set1 b
(integer) 1
127.0.0.1:6379> sismember set1 a
(integer) 0
两个集合取差集 sdiff
127.0.0.1:6379> sadd set2 a b c
(integer) 3
127.0.0.1:6379> sadd set3 a b x
(integer) 3
127.0.0.1:6379> sdiff set2 set3
1) "c"
127.0.0.1:6379> sdiff set3 set2
1) "x"
两个集合取交集 sinter
127.0.0.1:6379> sinter set2 set3
1) "b"
2) "a"
两个集合取并集 sunion
127.0.0.1:6379> sunion set2 set3
1) "c"
2) "a"
3) "x"
4) "b"
获取集合长度(获取集合中元素的个数) scard
127.0.0.1:6379> scard set3
(integer) 3
2.6.5 Redis数据类型之sorted set
有序集合,在集合类型的基础上为集合中的每个元素都关联了一个分数,根据分数进行排序,这样就实现了有序。sorted set比较适合用在获取TopN的场景,因为它里面的数据是有序的
针对sorted set类型主要有下面这些常见命令:
命令 | 格式 | 解释 |
---|---|---|
zadd | zadd key value | 向集合中添加元素 |
zscore | zscore key value | 获取集合中指定元素的分值 |
zrange | zrange key value | 获取集合指定元素的排名(正序) |
zrevrange | 格式同上 | 获取集合指定元素的排名(倒序) |
zincrby | zincrby key num value | 给集合中指定元素增加分值 |
zcard | zcard key | 获取集合中元素的数量 |
zrem | zrem key value | 从集合中删除指定元素 |
向集合中添加元素 zadd
127.0.0.1:6379> zadd zset1 5 a
(integer) 1
127.0.0.1:6379> zadd zset1 3 b
(integer) 1
127.0.0.1:6379> zadd zset1 4 c
(integer) 1
查询集合中指定元素的分值 zscore
127.0.0.1:6379> zscore zset1 a
"5"
根据角标获取集合中的元素(按照正序) zrange
127.0.0.1:6379> zrange zset1 0 -1
1) "b"
2) "c"
3) "a"
根据角标获取集合中的元素(按照倒序) zrevrange
127.0.0.1:6379> zrevrange zset1 0 -1
1) "a"
2) "c"
3) "b"
对集合中元素的分值进行递增 zincrby
127.0.0.1:6379> zincrby zset1 3 a
"8"
127.0.0.1:6379> zscore zset1 a
"8"
获取集合中元素的个数 zcard
127.0.0.1:6379> zcard zset1
(integer) 3
删除集合中的元素 zrem
127.0.0.1:6379> zrem zset1 a
(integer) 1
127.0.0.1:6379> zrange zset1 0 -1
1) "b"
2) "c"
sorted set使用注意点:
- 1:+inf(正无穷) -inf(负无穷),在给集合中的元素设置分值的时候可以使用这两个特殊数值。
- 2:set命令:如果key持有其它类型值,set会覆盖旧值,无视类型
2.6.6 eg:存储高一班的学员信息
需求:将学员的姓名、年龄、性别、住址信息保存到Redis中,分析一下:
在这里我们可以把学生认为是一个对象,学生对象具备了多个属性信息,姓名,年龄,性别,住址信息
所以针对学生信息非常适合使用hash类型进行存储
我们可以给学生生成一个编号拼接到key里面,姓名、年龄、性别、住址信息存储到hash类型的value中
注意:这里面针对key的命名,stu是student的简写,尽量不要写太多字符,否则会额外占用内存空间的,后面的:1,表示这个学生的编号是1,后期如果我们想获取所有学员的key,就可以使用这个规则进行过滤了。
这个规则进行过滤了。
127.0.0.1:6379> hmset stu:1 name xiaoming age 18 sex 0 address beijing
OK
127.0.0.1:6379> hgetall stu:1
1) "name"
2) "xiaoming"
3) "age"
4) "18"
5) "sex"
6) "0"
7) "address"
8) "beijing"
127.0.0.1:6379> hget user:1 age
"18"
2.7 JAVA操作redis
在这我们以java代码为例,演示一下如何使用java代码操作redis,我们需要借助于第三方jar包jedis来操作,首先在idea中创建maven项目db_redis
。在pom.xml文件中添加jedis依赖
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.3.0</version>
</dependency>
2.7.1 单连接方式
接下来使用单连接的方式操作redis,代码如下:
package cn.git.redis;
import redis.clients.jedis.Jedis;
/**
* 单连接方式操作redis
* Created by lixuchun
*/
public class RedisSingle {
/**
* 注意:此代码能够正常执行的前提是
* 1:redis所在服务器的防火墙需要关闭
* 2:redis.conf中的bind参数需要指定192.168.182.103
* @param args
*/
public static void main(String[] args) {
//获取jedis连接
Jedis jedis = new Jedis("192.168.182.103",6379);
//向redis中添加数据,key=git,value=hello bigdata!
jedis.set("git","hello bigdata!");
//从redis中查询key=git的value的值
String value = jedis.get("git");
System.out.println(value);
//关闭jedis连接
jedis.close();
}
}
代码执行效果如下:hello bigdata!,此时到redis中确认一下:
127.0.0.1:6379> keys *
1) "git"
127.0.0.1:6379> get git
"hello bigdata!"
其实在这你会发现,我们前面讲的那些在redis-cli中使用的命令,和jedis中提供的函数名称是一一对应的。切换到代码中来使用也是可以直接上手的。
2.7.2 连接池方式
接下来使用连接池的方式操作redis,代码如下:
package cn.git.redis;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
/**
* 连接池的方式操作redis
* Created by lixuchun
*/
public class RedisPool {
public static void main(String[] args) {
//创建连接池配置对象
JedisPoolConfig poolConfig = new JedisPoolConfig();
//连接池中最大空闲连接数
poolConfig.setMaxIdle(10);
//连接池中创建的最大连接数
poolConfig.setMaxTotal(100);
//创建连接的超时时间
poolConfig.setMaxWaitMillis(2000);
//表示从连接池中获取连接的时候会先测试一下连接是否可用,这样可以保证取出的连接都是可用的
poolConfig.setTestOnBorrow(true);
//获取jedis连接池
JedisPool jedisPool = new JedisPool(poolConfig, "192.168.182.103", 6379);
//从jedis连接池中取出一个连接
Jedis jedis = jedisPool.getResource();
String value = jedis.get("git");
System.out.println(value);
//注意:此处的close方法有两层含义
//1:如果jedis是直接创建的单连接,此时表示直接关闭这个连接
//2:如果jedis是从连接池中获取的连接,此时会把这个连接返回给连接池
jedis.close();
//关闭jedis连接池
jedisPool.close();
}
}
执行结果:hello bigdata!
2.7.3 提取RedisUtils工具类
基于redis连接池的方式提取RedisUtils工具类
package cn.git.redis;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
/**
* 基于redis连接池提取redis工具类
* Created by lixuchun
*/
public class RedisUtils {
//私有化构造函数,禁止new
private RedisUtils(){}
private static JedisPool jedisPool = null;
//获取连接
public static synchronized Jedis getJedis(){
if(jedisPool==null){
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxIdle(10);
poolConfig.setMaxTotal(100);
poolConfig.setMaxWaitMillis(2000);
poolConfig.setTestOnBorrow(true);
jedisPool = new JedisPool(poolConfig, "192.168.182.103", 6379);
}
return jedisPool.getResource();
}
//向连接池返回连接
public static void returnResource(Jedis jedis){
jedis.close();
}
}
使用工具类代码
package cn.git.redis;
import redis.clients.jedis.Jedis;
/**
* Created by lixuchun
*/
public class TestRedisUtils {
public static void main(String[] args) {
//获取连接
Jedis jedis = RedisUtils.getJedis();
String value = jedis.get("git");
System.out.println(value);
//向连接池返回连接
RedisUtils.returnResource(jedis);
}
}
2.8 高级特性
2.8.1 expire 生存时间
Redis中可以使用expire命令设置一个键的生存时间,到时间后Redis会自动删除它
它的一个典型应用场景是:手机验证码
我们平时在登录或者注册的时候,手机会接收到一个验证码,上面会提示验证码的过期时间,过了这个时间之后这个验证码就不能用了。
expire支持以下操作
命令 | 格式 | 解释 |
---|---|---|
expire | expire key seconds | 设置key的过期时间(单位:秒) |
ttl | ttl key | 获取key的剩余有效时间 |
persist | persist key | 取消key的过期时间 |
expireat | expireat key timestamp | 设置UNIX时间戳的过期时间 |
设置key的过期时间
127.0.0.1:6379> set abc 123
OK
127.0.0.1:6379> expire abc 200
(integer) 1
获取key的剩余有效时间
127.0.0.1:6379> ttl abc
(integer) 192
取消key的过期时间
127.0.0.1:6379> persist abc
(integer) 1
此时再查看这个key的剩余有效时间,返回的值是-1,-1表示这个key是一个永久存在的key
127.0.0.1:6379> ttl abc
(integer) -1
还可以通过expireat指定key在指定时间点过期
先获取当前时间戳
[root@bigdata04 ~]# date +%s
1768618628
127.0.0.1:6379> expireat abc 1768618638
(integer) 1
过一会再查看这个key的剩余有效时间,返回的是-2,表示这个key被删除了,不存在了
127.0.0.1:6379> ttl abc
(integer) -2
127.0.0.1:6379> exists abc
(integer) 0
总结一下:
当key永久存在的时候,执行ttl返回的是-1,
当key被设置了过期时间之后,执行ttl返回的就是这个key剩余的有效时间
当key已经被删除了,不存在的时候,执行ttl返回的是-2
2.8.2 pipeline 管道
针对批量操作数据或者批量初始化数据的时候使用,效率高,Redis的pipeline功能在命令行中没有实现,在Java客户端(jedis)中是可以使用的
它的原理是这样的
不使用管道的时候,我们每执行一条命令都需要和redis服务器交互一次
使用管道之后,可以实现一次提交一批命令,这一批命令只需要和redis服务器交互一次,所以就提高了性能。
这个功能就类似于mysql中的batch批处理。
接下来看一个案例,案例:初始化10万条数据,需求:使用普通方式一条一条添加和使用管道批量初始化进行对比分析
代码如下:
package cn.git.redis;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Pipeline;
/**
* pipeline(管道)的使用
* Created by lixuchun
*/
public class PipelineOp {
public static void main(String[] args) {
// 1:不使用管道
Jedis jedis = RedisUtils.getJedis();
long start_time = System.currentTimeMillis();
for(int i=0;i<100000;i++){
jedis.set("a"+i,"a"+i);
}
long end_time = System.currentTimeMillis();
System.out.println("不使用管道,耗时:"+(end_time-start_time));
// 2:使用管道
Pipeline pipelined = jedis.pipelined();
start_time = System.currentTimeMillis();
for(int i=0;i<100000;i++){
pipelined.set("b"+i,"b"+i);
}
pipelined.sync();
end_time = System.currentTimeMillis();
System.out.println("使用管道,耗时:"+(end_time-start_time));
RedisUtils.returnResource(jedis);
}
}
结果如下:
不使用管道,耗时:40887
使用管道,耗时:180
在代码执行的过程中,我们可以使用info命令观察数据库中的数据条数
127.0.0.1:6379> info
# Keyspace
db0:keys=200000,expires=1,avg_ttl=389945
从这可以看出来,针对海量数据的初始化,管道可以显著提高初始化性能。
2.8.3 info命令
这里面参数比较多,在这我们主要关注几个重点的参数
# Redis 服务器版本
redis_version:5.0.9
# Redis服务的可执行文件路径
executable:/data/soft/redis-5.0.9/redis-server
# 启动Redis时使用的配置文件路径
config_file:/data/soft/redis-5.0.9/redis.conf
# 已连接客户端的数量
connected_clients:1
# Redis目前存储数据使用的内容
used_memory_human:15.01M
# Redis可以使用的内存总量,和服务器的内存有关
total_system_memory_human:1.78G
# db0表示0号数据库,keys:表示0号数据库的key总量,expires:表示0号数据库失效被删除的key总量
db0:keys=200001,expires=1,avg_ttl=389945
2.8.4 Redis的持久化
Redis持久化简单理解就是把内存中的数据持久化到磁盘中 可以保证Reids重启之后还能恢复之前的数据,Redis支持两种持久化,可以 单独使用 或者 组合使用
RDB 和 AOF
2.8.4.1 Redis持久化之RDB
RDB是Redis默认的持久化机制,RDB持久化是通过快照完成的,当符合一定条件时Redis会自动将内存中的所有数据执行快照操作并存储到硬盘上,默认存储在dump.rdb
文件中
[root@bigdata04 redis-5.0.9]# ll
....
-rw-r--r--. 1 root root 2955661 Jan 17 12:12 dump.rdb
......
Redis什么时候会执行快照?Redis执行快照的时机是由以下参数控制的,这些参数是在redis.conf文件中的
save 900 1
save 300 10
save 60 10000
save 900 1 表示900秒内至少一个key被更改则进行快照,这里面的三个时机哪个先满足都会执行快照操作。
RDB持久化的优缺点:
- RDB的优点:由于存储的有数据快照文件,恢复数据很方便
- RDB的缺点:会丢失最后一次快照以后更改的所有数据,因为两次快照之间是由一个时间差的,这一段时间之内修改的数据可能会丢。
2.8.4.2 Redis持久化之AOF
AOF持久化是通过日志文件的方式,默认情况下没有开启,可以通过appendonly
参数开启
[root@bigdata04 redis-5.0.9]# vi redis.conf
....
appendonly yes
....
AOF日志文件的保存位置和RDB文件相同,都是dir参数设置的,默认的文件名是appendonly.aof
注意:dir参数的值为. 表示当前目录,也就是说我们在哪个目录下启动redis,rdb快照文件和aof日志文件就产生在哪个目录下。
可以试验一下,换一个目录启动redis,发下redis中的数据是空的。关闭之后,重新在之前的目录启动redis,数据又回来了。
AOF方式只会记录用户的写命令,添加、修改、删除之类的命令,查询命令不会记录,因为查询命令不会影响数据的内容。
那redis什么时候会把用户的写命令同步到aof文件中呢?
# appendfsync always
# appendfsync everysec
# appendfsync no
三种配置解释如下:
- appendfsync everysec,默认是每秒钟执行一次同步操作。
- appendfsync always,实现每执行一次写操作就执行一次同步操作,但是这样效率会有点低。
- appendfsync no,表示不主动进行同步,由操作系统来做,30秒执行一次。
如果大家对数据的丢失确实是0容忍的话,可以使用always。不过一般情况下,redis中存储的都是一些缓存数据,就算丢了也没关系,程序还会继续往里面写新数据,不会造成多大影响。
2.9 Redis 的安全策略
2.9.1 设置数据库密码
默认情况下访问redis只要网络能通就可以直接访问,这样其实是有一些不安全的,不过我们一般会限制只能在内网访问,这样其实问题也不大。
redis针对这个问题,也支持给数据库设置密码,在redis.conf中配置
[root@bigdata04 redis-5.0.9]# vi redis.conf
....
requirepass admin
....
重启redis服务
[root@bigdata04 redis-5.0.9]# redis-cli shutdown
[root@bigdata04 redis-5.0.9]# redis-server redis.conf
重新连接redis
[root@bigdata04 redis-5.0.9]# redis-cli
127.0.0.1:6379> get git
(error) NOAUTH Authentication required.
127.0.0.1:6379> auth admin
OK
127.0.0.1:6379>get git
hello bigdata!
在代码层面,以后在使用的时候时候就需要使用auth方法先校验权限了。
package com.git.redis;
import redis.clients.jedis.Jedis;
/**
* 单连接方式操作redis
* Created by xuwei
*/
public class RedisSingle {
/**
* 注意:此代码能够正常执行的前提是
* 1:redis所在服务器的防火墙需要关闭
* 2:redis.conf中的bind参数需要指定192.168.182.103
* @param args
*/
public static void main(String[] args) {
// 获取jedis连接
Jedis jedis = new Jedis("192.168.182.103",6379);
// 使用密码
jedis.auth("admin");
// 向redis中添加数据,key=git,value=hello bigdata!
jedis.set("git","hello bigdata!");
// 从redis中查询key=git的value的值
String value = jedis.get("git");
System.out.println(value);
//关闭jedis连接
jedis.close();
}
}
注意:在实际工作中一般不会设置密码,因为我们在这设置的密码是明文的,其实意义也不大,针对别有用心的人,你这样设置是没有意义的。
所以在实际工作中我们一般只需要控制好redis服务器的访问权限就可以了,redis服务器的访问权限其实就是使用bind参数来设置的。所以再把刚才设置的密码取消掉,直接把对应的配置注释掉即可。
所以再把刚才设置的密码取消掉,直接把对应的配置注释掉即可
[root@bigdata04 redis-5.0.9]# vi redis.conf
#requirepass admin
2.9.2 bind参数的应用
在实际工作中,我们的服务器至少会有3个ip地址
- 127.0.0.1 这个是本机回环地址
- 192.168.10.14 这个是本机的内网地址
- 还有一个是外网地址
我们一般会使用bind绑定内网ip,这样其实就限制了redis服务器的访问范围,不会暴露在外网,只需要运维同学做好网络的访问限制就可以了,此时我们就可以认为redis是安全的了。
2.9.3 命令重命名
咱们前面讲过一个命令是flushall
,这个命令是很危险的,它可以把redis中的所有数据全部清空
所以在实际工作中一般需要将这个命令给禁用掉,防止误操作。
在redis.conf配置文件中进行设置
[root@bigdata04 redis-5.0.9]# vi redis.conf
....
rename-command flushall ""
....
这样修改之后,就把flushall命令给禁用掉了,重启redis服务
[root@bigdata04 redis-5.0.9]# redis-cli shutdown
[root@bigdata04 redis-5.0.9]# redis-server redis.conf
重新连接redis
[root@bigdata04 redis-5.0.9]# redis-cli
127.0.0.1:6379> flushall
(error) ERR unknown command `flushall`, with args beginning with:
127.0.0.1:6379>
此时会提示未知命令。其实我们还可以选择,在重命名的时候给这个命令起一个别名,这样后期如果想使用的时候也是可以使用的。我们现在在后面直接指定的是空字符串 就是直接禁用了,如果指定一个其它字符串,就相当于起别名了。
2.9.4 Redis实例最多存多少key
一个Redis实例最多能存放多少key?有没有限制?Redis本身是不会限制存储多少key的,但是Redis是基于内存的,它的存储极限是系统中的可用内存值,如果内存存满了,那就无法再存储key了。
2.9.5 Redis监控命令-monitor
这个monitor
命令是一把双刃剑。在实际工作中要慎用。先演示一下:
[root@bigdata04 redis-5.0.9]# redis-cli
127.0.0.1:6379> monitor
OK
执行代码RedisSingle.java
中的代码,然后会发现monitor监控到我们对redis的所有操作
[root@bigdata04 redis-5.0.9]# redis-cli
127.0.0.1:6379> monitor
OK
1768628815.007443 [0 192.168.182.1:60633] "SET" "git" "hello bigdata!"
1768628815.007797 [0 192.168.182.1:60633] "GET" "git"
monitor可以监控我们对redis的所有操作,如果在线上的服务器上打开了这个功能,这里面就会频繁打印出来我们对redis数据库的所有操作,这样会影响redis的性能,所以说要慎用。
但是在某些特殊的场景下面它是很有用的,之前在工作中我遇到过一个很奇怪的问题,redis中的一个key总是会莫名其妙的消失,我的一个程序会定时向redis中写入一个key,但是我发现这个key刚写进去,然后一会就没了,很奇怪,当时我仔细排查了我的代码,里面既没有设置失效时间,也没有使用删除功能。
所以这个key不是我的代码删的,肯定是有其它的代码会删除这个key,但是到底是哪的代码?这个时候就不好排查了,我们的业务机有几十台,根本无从下手。
这个时候我突然想到了monitor这个命令,虽然开启monitor会影响redis的性能,但是这个时候需要排查问题,使用一会也是可以接受的。
所以就打开了monitor,打开之后屏幕上就打印出来很多命令,这样根本就看不清,没办法追踪,数据太多了。所以又想到了这个办法,结合grep命令来操作,这样就可以过滤出来对指定key的所有操作了。
[root@bigdata04 redis-5.0.9]# redis-cli monitor | grep key
1768629282.484868 [0 192.168.182.1:52364] "del" "key"
通过这条数据我们可以分析出来到底是哪台机器上的程序删除了这个key。然后再排查这台机器上都有哪些程序,对应的去排查代码,这样就快多了,最终发现是有一个代码里面会定时删除这个key。这个就是monitor的典型应用。