内存数据库Redis概念
Redis (Remote DIctinary Server) 是用C语言开发的一个开源的高性能键值对(key-value)数据库。Redis是一种单线程高性能的内存数据库。特征:
-
数据间没有必然的关联关系
-
内部采用单线程机制进行工作
-
高性能。官方提供测试数据,50个并发执行100000个请求,读的速度是110000次/s,写的速度是81000次/s。
-
多数据类型支持:
string
(字符串类型)、list
(列表类型)、hash
(散列类型)、set
(集合类型)、sorted_set
(有序集合类型) -
持久化支持。可以进行数据灾难恢复
Memcached 和 Redis的比较,使用 key-value 存储的话,Memcached 的内存利用率更高,而如果 Redis 采用 hash 结构来做 key-value 存储,由于其组合式的压缩,其内存利用率会高于 Memcached
Redis 安装
# 解压缩:
tar -xzf redis-6.2.6.tar.gz
cd redis-6.2.6
# 运行编译命令
make && make install
# 默认的安装路径是在 `/usr/local/bin`目录下。
/usr/local/bin
目录已经默认配置到环境变量,因此可以在任意目录下运行这些命令。
- redis-cli:是redis提供的命令行客户端
- redis-server:是redis的服务端启动脚本
- redis-sentinel:是redis的哨兵启动脚本
Redis的启动方式
- 默认启动
- 指定配置启动
- 开机自启
默认启动:
# 在任意目录输入redis-server命令即可启动Redis
redis-server
# 这种启动属于`前台启动`,会阻塞整个会话窗口,窗口关闭或者按下`CTRL + C`则Redis停止。不推荐使用。
指定配置启动:
# 让Redis以 后台 方式启动,则必须修改Redis配置文件,/usr/local/src/redis-6.2.6/redis.conf
cp redis.conf redis.conf.bck
vi redis.conf
# 修改redis.conf文件中的一些配置:
# 允许访问的地址,默认是127.0.0.1,会导致只能在本地访问。修改为0.0.0.0则可以在任意IP访问,生产环境不要设置为0.0.0.0
bind 0.0.0.0
# 守护进程,修改为yes后即可后台运行
daemonize yes
# 密码,设置后访问Redis必须输入密码
requirepass 123456
# Redis的其它常见配置:
# 监听的端口
port 6379
# 工作目录,默认是当前目录,也就是运行redis-server时的命令,日志、持久化等文件会保存在这个目录
dir .
# 数据库数量,设置为1,代表只使用1个库,默认有16个库,编号0~15
databases 1
# 设置redis能够使用的最大内存
maxmemory 512mb
# 日志文件,默认为空,不记录日志,可以指定日志文件名
logfile "redis.log"
# 启动Redis
# 进入redis安装目录
cd /usr/local/src/redis-6.2.6
# 启动
redis-server redis.conf
# 利用redis-cli来执行 shutdown 命令,即可停止 Redis 服务,
# 因为之前配置了密码,因此需要通过 -u 来指定密码
redis-cli -u 123456 shutdown
开机自启:
# 新建一个系统服务文件:
vi /etc/systemd/system/redis.service
[Unit]
Description=redis-server
After=network.target
[Service]
Type=forking
ExecStart=/usr/local/bin/redis-server /usr/local/src/redis-6.2.6/redis.conf
PrivateTmp=true
[Install]
WantedBy=multi-user.target
# 重载系统服务
systemctl daemon-reload
# 启动
systemctl start redis
# 停止
systemctl stop redis
# 重启
systemctl restart redis
# 查看状态
systemctl status redis
# 让redis开机自启
systemctl enable redis
Redis命令行客户端
Redis安装完成后就自带了命令行客户端:redis-cli
redis-cli [options] [commonds]
其中常见的options有:
-h 127.0.0.1
:指定要连接的redis节点的IP地址,默认是127.0.0.1-p 6379
:指定要连接的redis节点的端口,默认是6379-a 123321
:指定redis的访问密码
其中的commonds就是Redis的操作命令,例如:
ping
:与redis服务端做心跳测试,服务端正常会返回pong
不指定commonds
时,会进入redis-cli
的交互控制台。
Redis 默认有16个仓库,编号从0至15。通过配置文件可以设置仓库数量,但是不超过16,并且不能自定义仓库名称。通过 select 命令来选择数据库:
# 选择 0号库
select 0
Redis通用命令
通用指令是部分数据类型的,都可以使用的指令,常见的有:
- keys:查看符合模板的所有 key
- del:删除一个指定的 key
- exists:判断 key 是否存在
- expire:给一个 key 设置有效期,有效期到期时该 key 会被自动删除
- ttl:查看一个 key 的剩余有效期
通过help [command] 可以查看一个命令的具体用法,例如:
# 查看keys命令的帮助信息:
127.0.0.1:6379> help keys
KEYS pattern
summary: Find all keys matching the given pattern
since: 1.0.0
group: generic
Redis key结构
Redis 没有类似 MySQL 中的Table的概念,如何区分不同类型的key呢?
例如,需要存储用户、商品信息到redis,有一个用户id是1,有一个商品id恰好也是1,此时如果使用id作为key,那就会冲突了。
可以通过给key添加前缀加以区分,不过这个前缀不是随便加的,有一定的规范:
Redis 的 key 允许有多个单词形成层级结构,多个单词之间用':'
隔开,格式如下:
项目名:业务名:类型:id
这个格式并非固定,也可以根据需求来删除或添加词条。这样就把不同类型的数据区分开了。从而避免了key的冲突问题。
例如项目名称叫 koma,有 user 和 product 两种不同类型的数据,可以这样定义key:
-
user相关的key:koma:user:1
-
product相关的key:koma:product:1
如果 Value 是一个Java对象,例如一个User对象,则可以将对象序列化为JSON字符串后存储:
KEY | VALUE |
---|---|
koma:user:1 | {“id”:1, “name”: “Jack”, “age”: 21} |
koma:product:1 | {“id”:1, “name”: “小米11”, “price”: 4999} |
并且,在Redis的桌面客户端中,还会以相同前缀作为层级结构,让数据看起来层次分明,关系清晰。
Redis value数据类型
数据类型指的是存储的数据的类型,也就是 value 部分的类型,key 部分永远都是字符串。
String 和基础操作
String在 Redis 内部存储默认就是一个字符串(string 的数据类型是简单动态字符串,simple Dynamic string),string的内部结构实现上类似 Java 的 ArrayList。
当遇到增减类操作 incr , decr 时会转成数值型进行计算。value是字符串,根据字符串的格式不同,又可以分为3类:
- string:普通字符串
- int:整数类型,可以做自增、自减操作
- float:浮点类型,可以做自增、自减操作
底层都是字节数组形式存储,只不过是编码方式不同。字符串类型的最大空间不能超过512m。string 扩容机制:当字符串长度小于 1M 时,扩容都是加倍现有的空间;如果超过 1M,扩容时一次只会多扩 1M 的空间,需要注意的是字符串最大长度为 512M。string采用预分配冗余空间的方式来减少内存的频繁分配。
Redis 所有的操作都是原子性的,采用单线程处理所有业务,命令是一个一个执行的,因此无需考虑并发带来的数据影响。
# 信息添加
set key value
# 信息查询
get key
# 删除数据
del key
# 批量添加多个String类型的键值对
mset key1 value1 key2 value2 …
# 根据多个key获取多个String类型的value
mget key1 key2 …
# 获取数据字符个数(字符串长度)
strlen key
# 追加信息到原始信息后部(如果原始信息存在就追加,否则新建)
append key value
# 帮助命令:获取命令帮助文档,获取组中所有命令信息名称
help 命令名称
help @组名
# 设置数值数据增加指定范围的值
incr key # 让一个整型的key自增1
incrby key increment # 让一个整型的key自增并指定步长
incrbyfloat key increment # 让一个浮点类型的数字自增并指定步长
# 设置数值数据减少指定范围的值
decr key
decrby key increment
注:按数值进行操作的数据,如果原始数据不能转成数值,或超过了 redis 数值上限范围,将会报错。9223372036854775807 ( java中long型数据最大值,Long.MAX_VALUE)
String 数据时效性设置:
设置数据具有指定的声明周期
# psetex 和 setex 作用一样,都是设置过期时间,区别是:psetex 时间单位是毫秒,setex 是秒
setex key seconds value # 添加一个String类型的键值对,并且指定有效期
psetex key milliseconds value
# setex key1 10 123
# get key1 ————> "123"
# get key1 ————> (nil)
# setnx:添加一个String类型的键值对,前提是这个key不存在,否则不执行
String 类型的注意事项:
- 数据操作不成功的反馈与数据正常操作之间的差异
- 表示运行结果是否成功:(integer) 0 ——>false 失败,(integer) 1 ——> true 成功
- 表示运行结果值:(integer) 3 ——> 3 3个,(integer) 1 ——> 1 1个
- 数据未获取到 (nil):等同于null
- 数据最大存储量: 512MB
- 数值计算最大范围:(java中的long的最大值)
Hash 和基础操作
String 结构是将对象序列化为 JSON 字符串后存储,当需要修改对象某个字段时很不方便,Hash 结构可以将对象中的每个字段独立存储,可以针对单个字段做 CRUD。
# 添加/修改数据
hset key field value # 添加或者修改hash类型key的field的值
# 获取数据
hget key field # 获取一个hash类型key的field的值
hgetall key # 获取一个hash类型的key中的所有的field和value
# 删除数据
hdel key field1 [field2]
# 添加/修改多个数据
hmset key field1 value1 field2 calue2 #批量添加多个hash类型key的field的值
# 获取多个数据
hmget key field1 field2 … # 批量获取多个hash类型key的field的值
# 获取哈希表中字段的数量
hlen key
# 获取哈希表中是否存在指定的字段
hexists key field
# 获取哈希表中所有的字段名和字段值
hkeys key # 获取一个hash类型的key中的所有的field
hvals key
# 设置指定字段的数值数据增加指定范围的值
hincrby key field increment # 让一个hash类型key的字段值自增并指定步长
hincrbyfloat key field increment
# hsetnx:添加一个hash类型的key的field值,前提是这个field不存在,否则不执行
- hash 类型下的 value 只能存储字符串,不允许存储其他类型数据,不存在嵌套现象。如果数据未获取到,对应的值为 (nil)。
- 每个 hash 可以存储 232-1 个键值对。
- hash 类型十分贴近对象的数据存储形式,并且可以灵活添加删除对象属性。但 hash 设计初中不是为了存储大量对象而设计的,切记不可滥用,更不可以将 hash 作为对象列表使用。
- hgetall 操作可以获取全部属性,如果内部 field 过多,遍历整体数据效率就会很低,有可能成为数据访问瓶颈。
List 和基础操作
Redis 中的 List 类型与 Java 中的 LinkedList 类似,可以看做是一个双向链表结构。既可以支持正向检索和也可以支持反向检索。特征也与 LinkedList 类似:
- 有序
- 元素可以重复
- 插入和删除快
- 查询速度一般
Redis 应用于具有操作线后顺序的数据控制。常用来存储一个有序数据,例如:朋友圈点赞列表,评论列表等。
# 添加/修改数据
lpush key value1 [value2] … # 向列表左侧插入一个或多个元素
rpush key value1 [value2] … # 向列表右侧插入一个或多个元素
# 获取数据
lrange key start stop # 返回一段角标范围内的所有元素
lindex key index
llen key
# 删除并移除数据
lpop key # 移除并返回列表左侧的第一个元素,没有则返回nil
rpop key # 移除并返回列表右侧的第一个元素
# 规定时间内获取并移除数据 (阻塞式获取,获取值如果还没有的时候可以等,如果有值就可以获取到)
blpop key1 [key2] timeout
brpop key1 [key2] timeout
# 移除指定数据
lrem key count value
- list 中保存的数据都是string类型的,数据总容量是有限的,最多 232-1 个元素。(4294967295)
- list具有索引的概念,但是操作数据时候通常以队列的形式进行入队出队操作,或以栈的形式进入栈出栈的操作。
- 获取全部数据操作结束索引设置为-1。
- list 可以对数据进行分页操作,通过第一页的信息来自list,第2页及更多的信息通过数据库的形式加载。
list 类型应用业务场景
-
twitter、新浪微博、腾讯微博中个人用于的关注列表需要按照用户的关注顺序进行展示,粉丝列表需要将最近关注的粉丝列在前面。
-
新闻、资讯类网站如何将最新的新闻或资讯按照发生的事件顺序展示。
-
企业运营过程中,系统将产生出大量的运营数据,保障多台服务器操作日志的统一顺序输出。
- 依赖list 的数据具有顺序的特征对信息进行管理
- 使用队列模型解决多路信息汇总合并的问题
- 使用栈模型解决最新消息的问题
Set 和基础操作
Redis 的 Set 结构与 Java 中的 HashSet 类似,可以看做是一个 value 为 null 的 HashMap。因为也是一个 Hash 表,因此具备与 HashSet 类似的特征:
-
无序
-
元素不可重复
-
查找快
-
支持交集、并集、差集等功能
set类型:与 Hash 存储结构完全相同,仅存储键,不存储值( nil ) , 并且值是不允许重复的。
# 添加数据
sadd key menber1 [member2] # 向set中添加一个或多个元素
# 获取全部数据
smembers key
# 删除数据
srem key member1 [member2] # 移除set中的指定元素
# 获取集合数据总量
scard key
# 判断集合中是否包含指定数据
sismember key member
# 随机获取集合中指定数量的数据
srandmember key [count]
# 随机获取集合中的某个数据并将该数据移出集合
spop key [count]
# 求两个集合的交、并、差集
sinter key1 [key2]
sunion key1 [key2]
sdiff key1 [key2]
# 求两个集合的交、并、差集并存储到指定集合中
sinterstore destination key1 [key2]
sunionstore destination key1 [key2]
sdiffstore destination key1 [key2]
# 将指定数据从原始集合移动到目标集合中
smove source destination member
Sorted_set(zset) 和基础操作
Redis 的 SortedSet 是一个可排序的 set 集合,与 Java 中的 TreeSet 有些类似,但底层数据结构却差别很大。SortedSet 中的每一个元素都带有一个 score 属性,可以基于 score 属性对元素排序,底层的实现是一个跳表(SkipList)加 hash表。
SortedSet具备下列特性:
- 可排序
- 元素不重复
- 查询速度快
因为SortedSet的可排序特性,经常被用来实现排行榜这样的功能。
根据排序有利于数据的有效显示,需要提供一种可以根据自身特征进行排序的方式。Sorted_set类型: 在set的存储结构基础上添加可排序字段。
# 添加数据,添加一个或多个元素到sorted set ,如果已经存在则更新其score值
zadd key score1 member1 [score2 member2]
# 获取全部数据(start与stop用于限定查询范围,作用于索引,表示开始和结束索引)
zrange key start stop [WITHSCORES] # 按照score排序后,获取指定排名范围内的元素
zrevrange key start stop [WITHSCORES]
# 删除数据,删除sorted set中的一个指定元素
zrem key member [member …]
# 按条件获取数据(min与max用于限定搜索查询的条件)
zrangebyscore key min max [WITHSCORES] [LIMIT] # 按照score排序后,获取指定score范围内的元素
zrevrangebyscore key max min [WITHSCORES]
# 条件删除
zremrangebyrank key start stop
zremrangebyscore key min max
# 获取集合数据总量
zcard key # 获取sorted set中的元素个数
zcount key min max # 统计score值在给定范围内的所有元素的个数
# 集合交、并操作(计算给定的一个或多个有序集的交集,其中给定 key 的数量必须以 numkeys 参数指定,并将该交集(结果集)储存到 destination)
zinterstore destination numkeys key [key …]
zunionstore destination numkeys key [key …]
# offset与count用于限定查询范围,作用于查询结果,表示开始位置和数据总量
# 获取数据对应的索引(排名)
zrank key member # 获取sorted set 中的指定元素的排名
zrevrank key member
# score 值获取与修改
zscore key member # 获取sorted set中的指定元素的score值
zincrby key increment member # 让sorted set中的指定元素自增,步长为指定的increment值
# zdiff、zinter、zunion:求差集、交集、并集
注意:所有的排名默认都是升序,如果要降序则在命令的Z
后面添加REV
即可,例如:
-
升序获取Sorted set 中的指定元素的排名:ZRANK key member
-
降序获取Sorted set 中的指定元素的排名:ZREVRANK key memeber
下面关于Redis支持的zset数据类型,错误的说法是(D)
A. Redis 使用 ziplist (压缩列表)来实现zset类型时需要满足zset类型元素个数小于zset-max-ziplist-entries
B. Redis 使用 ziplist (压缩列表)来实现zset类型时需要满足zset类型所有member的长度小于zset-max-ziplist-value
C. zset 通过用户额外提供一个评分(score)的参数来为集合中的成员进行排序,并且是插入有序
D. zset 数据类型中成员及评分都是唯一不可重复的
zset 数据类型中成员是唯一不可重复的,但评分不是,所以是选D,A选项中 Redis 使用 ziplist (压缩列表)来实现 zset 类型时需要满足 zset 类型元素个数小于 zset-max-ziplist-entries ; B选项中 Redis 使用 ziplist (压缩列表)来实现zset类型时需要满足 zset 类型所有 member 的长度小于 zset-max-ziplist- value ;C选项中zset通过用户额外提供一个评分(score)的参数来为集合中的成员进行排序,并且是插入有序都是正确的说法。
zset 底层的存储结构包括 ziplist 或 skiplist,在同时满足有序集合保存的元素数量小于 128 个和有序集合保存的所有元素的长度小于 64 字节的时候使用 ziplist,其他时候使用 skiplist。
Redis 使用 ziplist (压缩列表)来实现需要满足 hash 类型时当哈希类型元素个数小于 hash-max-ziplist-entries 配置(默认512个但可以进行配置)
Redis value高级数据类型
BitMap 和基础操作
BitMap 通过一个 bit 位来表示某个元素对应的值或者状态,key 就是对应元素本身(setbit, getbit, bitcount)。
设置在线 key:“online:active”,当用户登录时,ID 就可以作为 offset,通过 bitcount 来统计。
bitmap 可以做布隆过滤器,确认访问值是否存在,只要在布隆过滤器里值是0,后面的服务就别访问了。
Bitmaps类型的基础操作:
获取指定 key 对应偏移量上的 bit 值
getbit key offset
设置指定 key 对应偏移量上的 bit 值,value 只能是1或0
setbit key offset value
演示:
127.0.0.1:6379> setbit bits 0 1
(integer) 0
127.0.0.1:6379> getbit bits 0
(integer) 1
127.0.0.1:6379> getbit bits 10
(integer) 0
对指定 key 按位进行交、并、非、异或操作,并将结果保存到 destKey 中
bitop op destKey key1 [key2...]
# and:交
# or:并
# not:非
# xor:异或
统计指定 key 中 1 的数量
bitcount key [start end]
示例:
127.0.0.1:6379> setbit 20880808 0 1
(integer) 0
127.0.0.1:6379> setbit 20880808 4 1
(integer) 0
127.0.0.1:6379> setbit 20880808 8 1
(integer) 0
127.0.0.1:6379> setbit 20880809 0 1
(integer) 0
127.0.0.1:6379> setbit 20880809 5 1
(integer) 0
127.0.0.1:6379> setbit 20880809 8 1
(integer) 0
127.0.0.1:6379> bitcount 20880808
(integer) 3
127.0.0.1:6379> bitcount 20880809
(integer) 3
127.0.0.1:6379> setbit 20880808 6 1
(integer) 0
127.0.0.1:6379> bitcount 20880808
(integer) 4
127.0.0.1:6379> bitop or 08-09 20880808 20880809
(integer) 2
127.0.0.1:6379> bitcount 08-09
(integer) 5
HyperLogLog 和基础操作
- 基于bitmap 计数
- 基于概率基数计数 0.87
- 这个数据结构的命令有三个:pfadd、pfcount、pfmerge
- 用途:记录网站 IP 注册数,每日访问的 IP 数,页面实时UV、在线用户人数
127.0.0.1:6379> pfadd h1 b
(integer) 1
127.0.0.1:6379> pfadd h1 a
(integer) 0
127.0.0.1:6379> pfcount h1
(integer) 2
127.0.0.1:6379> pfadd h1 c
(integer) 1
127.0.0.1:6379> pfadd h2 a
(integer) 1
127.0.0.1:6379> pfadd h3 d
(integer) 1
127.0.0.1:6379> pfmerge h3 h1 h2
OK
127.0.0.1:6379> pfcount h3
(integer) 4
Geospatial 和基础操作
- 底层数据结构 Zset GEOADD GEODIST GEOHASH GEOPOP GEOPADUIS GEORADIUSBYMEMBER
- 保存地理位置,并作位置距离计算或者根据半径计算位置
- Geo本身不是一种数据结构,它本质上还是借助于Sorted Set(ZSET)
- GEOADD key 经度 维度 名称
把某个具体的位置信息(经度,纬度,名称)添加到指定的 key 中,数据将会用一个Sorted set 存储,以便稍后能使用 GEORADIUS 和 GEORADIUSBYMEMBER 命令来根据半径来查询位置信息。
127.0.0.1:6379> geoadd cities 116.404269 39.91582 "beijing" 121.478799
31.235456 "shanghai"
(integer) 2
127.0.0.1:6379> zrange cities 0 -1
1) "shanghai"
2) "beijing"
127.0.0.1:6379> zrange cities 0 -1 WITHSCORES
1) "shanghai"
2) "4054803475356102"
3) "beijing"
4) "4069885555377153"
127.0.0.1:6379> geodist cities beijing shanghai km
"1068.5677"
127.0.0.1:6379> geopos cities beijing shanghai
1) 1) "116.40426903963088989"
2) "39.91581928642635546"
2) 1) "121.47879928350448608"
2) "31.23545629441388627"
127.0.0.1:6379> geoadd cities 120.165036 30.278973 hangzhou
(integer) 1
127.0.0.1:6379> georadius cities 120 30 500 km
1) "hangzhou"
2) "shanghai"
127.0.0.1:6379> georadiusbymember cities shanghai 200 km
1) "hangzhou"
2) "shanghai"
127.0.0.1:6379> zrange cities 0 -1
1) "hangzhou"
2) "shanghai"
3) "beijing"
127.0.0.1:6379>
# 添加坐标点
geoadd key longitude latitude member [longitude latitude member ...]
# 获取坐标点
geopos key member [member ...]
# 计算坐标点距离
geodist key member1 member2 [unit]
# 演示
127.0.0.1:6379> GEOADD geos 1 1 a
(integer) 1
127.0.0.1:6379> GEOADD geos 2 2 b
(integer) 1
127.0.0.1:6379> GEOPOS geos a
1) 1) "0.99999994039535522"
2) "0.99999945914297683"
127.0.0.1:6379> GEODIST geos a b
"157270.0561"
127.0.0.1:6379> GEODIST geos a b m
"157270.0561"
127.0.0.1:6379> GEODIST geos a b km
"157.2701"
# 根据坐标范围内的数据
georadius key longitude latitude radius m|km|ft|mi [withcoord] [withdist] [withhash] [count count]
# 根据点求范围内数据
georadiusbymember key member radius m|km|ft|mi [withcoord] [withdist] [withhash] [count count]
# 计算经纬度
geohash key member [member ...]
# 演示
127.0.0.1:6379> GEODIST geos a b
"157270.0561"
127.0.0.1:6379> GEODIST geos a b m
"157270.0561"
127.0.0.1:6379> GEODIST geos a b km
"157.2701"
127.0.0.1:6379> GEOADD geos 1 1 1,1
(integer) 1
127.0.0.1:6379> GEOADD geos 1 2 1,2
(integer) 1
127.0.0.1:6379> GEOADD geos 1 3 1,3
(integer) 1
127.0.0.1:6379> GEOADD geos 2 1 2,1
(integer) 1
127.0.0.1:6379> GEOADD geos 2 2 2,2
(integer) 1
127.0.0.1:6379> GEOADD geos 2 3 2,3
(integer) 1
127.0.0.1:6379> GEOADD geos 3 1 3,1
(integer) 1
127.0.0.1:6379> GEOADD geos 3 2 3,2
(integer) 1
127.0.0.1:6379> GEOADD geos 3 3 3,3
(integer) 1
127.0.0.1:6379> GEOADD geos 5 5 5,5
(integer) 1
127.0.0.1:6379> GEORADIUSBYMEMBER geos 2,2 180 km
1) "1,1"
2) "a"
3) "2,1"
4) "1,2"
5) "2,2"
6) "b"
7) "3,1"
8) "3,2"
9) "1,3"
10) "2,3"
11) "3,3"
127.0.0.1:6379> GEORADIUSBYMEMBER geos 2,2 120 km
1) "1,2"
2) "2,2"
3) "b"
4) "2,3"
5) "2,1"
6) "3,2"
127.0.0.1:6379> GEORADIUSBYMEMBER geos 2,2 1800 km
1) "1,1"
2) "a"
3) "2,1"
4) "1,2"
5) "2,2"
6) "b"
7) "3,1"
8) "3,2"
9) "1,3"
10) "2,3"
11) "3,3"
12) "5,5"
127.0.0.1:6379> GEORADIUS geos 1.5 1.5 90 km
1) "1,2"
2) "2,2"
3) "b"
4) "1,1"
5) "a"
6) "2,1"
127.0.0.1:6379> GEOHASH geos 2,2
1) "s037ms06g70"
Redis的Java客户端
- Jedis 和 Lettuce:这两个主要是提供了 Redis 命令对应的 API,方便我们操作 Redis,而 SpringDataRedis 又对这两种做了抽象和封装。
- Redisson:是在 Redis 基础上实现了分布式的可伸缩的 Java 数据结构,例如 Map、Queue 等,而且支持跨进程的同步机制:Lock、Semaphore等待,比较适合用来实现特殊的功能需求。
Jedis客户端
引入依赖:
<!--jedis-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.7.0</version>
</dependency>
<!--单元测试-->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.7.0</version>
<scope>test</scope>
</dependency>
建立连接:
private Jedis jedis;
@BeforeEach //用于表示应在当前类中的每个@Test方法之前执行注解方法,@BeforeEach注解的方法不得为静态方法
void setUp() {
// 1.建立连接
// jedis = new Jedis("192.168.150.101", 6379); //Jedis的直连方式
jedis = JedisConnectionFactory.getJedis(); //Jedis本身是线程不安全的,并且频繁的创建和销毁连接会有性能损耗,因此推荐使用Jedis连接池代替Jedis的直连方式。
// 2.设置密码
jedis.auth("123456");
// 3.选择库
jedis.select(0);
}
测试:
@Test
void testString() {
// 存入数据
String result = jedis.set("name", "Koma");
System.out.println("result = " + result);
// 获取数据
String name = jedis.get("name");
System.out.println("name = " + name);
}
@Test
void testHash() {
// 插入hash数据
jedis.hset("user:1", "name", "Koma");
jedis.hset("user:1", "age", "25");
// 获取
Map<String, String> map = jedis.hgetAll("user:1");
System.out.println(map);
}
释放资源:
@AfterEach
void tearDown() {
if (jedis != null) {
jedis.close();
}
}
Jedis连接池:
Jedis 本身是线程不安全的,并且频繁的创建和销毁连接会有性能损耗,因此使用Jedis连接池代替Jedis的直连方式。
import redis.clients.jedis.*;
public class JedisConnectionFactory {
private static JedisPool jedisPool;
static {
// 配置连接池
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxTotal(8);
poolConfig.setMaxIdle(8);
poolConfig.setMinIdle(0);
poolConfig.setMaxWaitMillis(1000);
// 创建连接池对象,参数:连接池配置、服务端ip、服务端端口、超时时间、密码
jedisPool = new JedisPool(poolConfig, "192.168.150.101", 6379, 1000, "123456");
}
public static Jedis getJedis(){
return jedisPool.getResource();
}
}
SpringDataRedis客户端
SpringData是Spring中数据操作的模块,包含对各种数据库的集成,其中对Redis的集成模块就叫做SpringDataRedis。
- 提供了对不同Redis客户端的整合(Lettuce 和 Jedis)
- 提供了 RedisTemplate 统一 API 来操作 Redis
- 支持 Redis 的发布订阅模型
- 支持 Redis 哨兵和 Redis 集群
- 支持基于 Lettuce 的响应式编程
- 支持基于JDK、JSON、字符串、Spring 对象的数据序列化及反序列化
- 支持基于Redis的 JDKCollection 实现
SpringDataRedis 中提供了 RedisTemplate 工具类,其中封装了各种对 Redis 的操作。并且将不同数据类型的操作 API 封装到了不同的类型中:
API | 返回值类型 | 说明 |
---|---|---|
redisTemplate .opsForValue() | ValueOperations | 操作String 类型数据 |
redisTemplate .opsForHash() | HashOperations | 操作Hash 类型数据 |
redisTemplate .opsForList() | ListOperations | 操作List 类型数据 |
redisTemplate .opsForSet() | SetOperations | 操作Set 类型数据 |
redisTemplate .opsForZSet() | ZSetOperations | 操作SortedSet 类型数据 |
redisTemplate | 通用的命令 |
引入依赖:
<dependencies>
<!--redis依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--common-pool-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!--Jackson依赖-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
配置Redis:
spring:
redis:
host: 192.168.150.101
port: 6379
password: 123456
lettuce:
pool:
max-active: 8
max-idle: 8
min-idle: 0
max-wait: 100ms
注入RedisTemplate:
// SpringBoot的自动装配
@SpringBootTest
class RedisStringTests {
@Autowired
private RedisTemplate redisTemplate;
}
测试:
@SpringBootTest
class RedisStringTests {
@Autowired
private RedisTemplate edisTemplate;
@Test
void testString() {
// 写入一条String数据
redisTemplate.opsForValue().set("name", "Koma");
// 获取string数据
Object name = stringRedisTemplate.opsForValue().get("name");
// RedisTemplate可以接收任意 Object 作为值写入 Redis;写入前会把 Object 序列化为字节形式,默认是采用 JDK 序列化,eg: \xAC\xED\x05t\x00\x06\xE6...
System.out.println("name = " + name);
}
}
注:
RedisTemplate可以接收任意 Object 作为值写入 Redis;写入前会把 Object 序列化为字节形式,默认是采用 JDK 序列化。eg: \xAC\xED\x05t\x00\x06\xE6…
自定义RedisTemplate的序列化方式
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory){
// 创建RedisTemplate对象
RedisTemplate<String, Object> template = new RedisTemplate<>();
// 设置连接工厂
template.setConnectionFactory(connectionFactory);
// 创建JSON序列化工具
GenericJackson2JsonRedisSerializer jsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
// 设置Key的序列化
template.setKeySerializer(RedisSerializer.string());
template.setHashKeySerializer(RedisSerializer.string());
// 设置Value的序列化
template.setValueSerializer(jsonRedisSerializer); // 这里采用了JSON序列化来代替默认的JDK序列化方式。
template.setHashValueSerializer(jsonRedisSerializer); // 这里采用了JSON序列化来代替默认的JDK序列化方式。
// 返回
return template;
}
}
//redis中记录为:
{
"@class":"com.xx.xx.xx.User",
"name":"koma",
"age","25"
}
整体可读性有了很大提升,并且能将 Java 对象自动的序列化为 JSON 字符串,并且查询时能自动把 JSON 反序列化为 Java 对象。不过,其中记录了序列化时对应的 class 名称,目的是为了查询时实现自动反序列化。这会带来额外的内存开销。
StringRedisTemplate 统一使用 String 序列化器
为了节省内存空间,template.setHashValueSerializer(jsonRedisSerializer)
可以不使用 JSON 序列化器来处理 value,而是统一使用 String 序列化器,要求只能存储 String 类型的 key 和 value。当需要存储 Java 对象时,手动完成对象的序列化和反序列化。
存入和读取时的序列化及反序列化都是自己实现的,SpringDataRedis 就不会将 class 信息写入 Redis 了。这种用法比较普遍,因此 SpringDataRedis 就提供了 RedisTemplate 的子类:StringRedisTemplate,它的 key 和 value 的序列化方式默认就是 String 方式RedisSerializer.string()
。
直接使用,省去了自定义 RedisTemplate 的序列化方式的步骤:
@Autowired
private StringRedisTemplate stringRedisTemplate;
// JSON序列化工具
private static final ObjectMapper mapper = new ObjectMapper();
@Test
void testSaveUser() throws JsonProcessingException {
// 创建对象
User user = new User("Koma", 25);
// 手动序列化
String json = mapper.writeValueAsString(user);
// 写入数据
stringRedisTemplate.opsForValue().set("user:1", json);
// 获取数据
String jsonUser = stringRedisTemplate.opsForValue().get("user:1");
// 手动反序列化
User user1 = mapper.readValue(jsonUser, User.class);
System.out.println("user1 = " + user1);
}
其他
Redis 支持通过 loglevel 配置项设置日志等级,共分四级,即 debug、verbose、notice、warning
Redis 以 “includes” 的方式引入其他配置文件时,如果同一个配置项在不同配置文件中都有定义,那么以最后面的配置项为准
Redis 只支持 bytes,不支持 bit 单位
Redis 在高并发情况下设置较高的 tcp-backlog 值以避免 TCP 的慢连接问题
Redis 中,有三种类型的过期策略
- 过期时间(Expire):在设置键值对的同时,可以设置一个过期时间,Redis 会自动在该键值对在指定的时间内过期,过期后会自动删除该键值对。
- 定时删除(Evict):通过配置 maxmemory 限制 Redis 的内存使用量,在内存满时 Redis 会将一些键值对从内存中删除,优先删除的是那些过期时间最短的键值对,以此来保证 Redis 的内存使用量不会超过限制。
- 惰性删除(Lazy deletion):在访问一个键值对时,Redis 会先检查该键值对是否过期,如果过期则会立即删除该键值对。惰性删除的优点是不需要额外的删除操作,节省了服务器资源,缺点是可能会有大量过期的键值对占用内存。
一般情况下,使用过期时间是最常见的过期策略,而惰性删除可以作为补充策略来保证 Redis 的内存使用量不会超过限制。当然,在特定场景下也可以使用其他的过期策略。