概要
1)Redis的常用指令及业务应用场景
2)使用Java客户端操作Redis
简介:redis是一个开源的使用C语言编写、支持网络、可基于内存也可以持久化的日志型Key-Value数据库,并提供多种语言的API。
本质:客户端-服务端应用程序
特点:使用简单、性能强大、应用场景丰富
1.1 通用指令
指令 | 说明 |
---|---|
del key | key存在时,将key删除 |
dump key | 序列化给定key,并返回被序列化的值 |
exists key | 检查给定key是否存在 |
expire key seconds | 为给定key设置过期时间,以秒计 |
ttl key | 以秒为单位,返回给定key的剩余生存时间(TTL-time to live) |
type key | 返回key所存储的值得类型 |
dbsize | 返回对应DB的大小 |
help @对应的数据类型 | 查看对应的数据类型的所有指令 |
help cmd | 查看对应的指令的用法 |
示例
连接到服务端:
[docker@docker-node1 redis-all]$ ./redis-cli -h 127.0.0.1 -p 6379
127.0.0.1:6379>
简单指令演示:
127.0.0.1:6379> select 1
OK
127.0.0.1:6379[1]> set name vander
OK
127.0.0.1:6379[1]> exists name
(integer) 1
127.0.0.1:6379[1]> ttl name
(integer) -1
127.0.0.1:6379[1]> type name
string
127.0.0.1:6379[1]> set name vander ex 10
OK
127.0.0.1:6379[1]> ttl name
(integer) 5
127.0.0.1:6379[1]> get name
"vander"
127.0.0.1:6379[1]> ttl name
(integer) -2
127.0.0.1:6379[1]> get name
(nil)
127.0.0.1:6379[1]> set name1 jason
OK
127.0.0.1:6379[1]> keys *
1) "name1"
127.0.0.1:6379[1]>
1.2 五种基本数据结构
Redis的数据结构的命令可以从官方文档查找:https://redis.io/commands
1.2.1 数据结构-String
简介:string数据结构是简单的key-value类型,value不仅是string,还可以是数字
应用场景:微博数、粉丝数等常规计数、分布式锁(Redis执行Lua脚本保证原子性)、Session共享、全局序列号
字符串常用操作
指令 | 说明 |
---|---|
set key value | 设置指定key的值 |
get key | 获取指定key的值 |
mset key value [ |
批量存储多个键值对 |
mget key [key ...] | 获取所有给定key的值 |
原子加减
指令 | 说明 |
---|---|
incr key | 将key中存储的数字值增1 |
decr key | 将key中存储的数字值减1 |
incrby key increment | 将key存储的数字值加上increment |
decrby key decrement | 将key存储的数字值减去decrement |
设置Key-Value的有效时间:
127.0.0.1:6379[1]> set name vander ex 10
OK
127.0.0.1:6379[1]> get name
"vander"
127.0.0.1:6379[1]> ttl name
(integer) 5
127.0.0.1:6379[1]> get name
"vander"
127.0.0.1:6379[1]> ttl name
(integer) -2
127.0.0.1:6379[1]> get name
(nil)
设置对Value进行递增或者递减
127.0.0.1:6379[1]> set counter 10
OK
127.0.0.1:6379[1]> incr counter
(integer) 11
127.0.0.1:6379[1]> decr counter
(integer) 10
127.0.0.1:6379[1]>
应用场景
## 单值缓存或者是使用对象序列化器存储
127.0.0.1:6379> set name jason
OK
127.0.0.1:6379> get name
"jason"
127.0.0.1:6379>
## 分布式锁的应用
# 假设是第一个线程执行这个指令
127.0.0.1:6379> setnx product:iPhone true
(integer) 1
# 另一个线程执行此指令就失败了
127.0.0.1:6379> setnx product:iPhone true
(integer) 0
# 第一个线程获取设置成功后表示获取到了锁,等用完之后把锁释放掉
127.0.0.1:6379> del product:iPhone
(integer) 1
## 计数器场景
# 例如记录文章被读取的次数
127.0.0.1:6379> incr article:read_count:001
(integer) 1
127.0.0.1:6379> incr article:read_count:001
(integer) 2
127.0.0.1:6379> incr article:read_count:001
(integer) 3
127.0.0.1:6379> get article:read_count:001
"3"
## Web集群的Session共享
Spring Session + Redis 实现Session共享
## 分布式系统全局序列号
#为了保证性能每个客户端批量获取一批序列号,用完之后再到Redis里重新取一批
incrby orderid 1000
1.2.2 数据结构-List
简介:即链表
可实现的数据结构:
Stack(栈) = LPUSH+LPOP = FILO
Queue(队列)= LPUSH+RPOP = FIFO
Blocking Queue(阻塞队列)= LPUSH + BRPOP
使用场景:微博的关注列表,粉丝列表
指令 | 说明 |
---|---|
lpush key value1 value2 ... | 将一个或多个值插入到列表头部 |
rpush key value1 value2 ... | 在列表中添加一个或多个值 |
lpop key | 移除并获取列表的第一个元素,返回值为移除的元素 |
rpop key | 移除并获取列表的最后一个元素,返回值为移除的元素 |
lrange | 获取所有给定key的值 |
实现栈(后进先出)的功能:
127.0.0.1:6379[1]> rpush teachers Miss.Xu Miss.Yu
(integer) 2
127.0.0.1:6379[1]> rpop teachers
"Miss.Yu"
127.0.0.1:6379[1]> rpop teachers
"Miss.Xu"
127.0.0.1:6379[1]> rpush teachers Miss.Xu
(integer) 1
127.0.0.1:6379[1]> rpush teachers Miss.Yu
(integer) 2
127.0.0.1:6379[1]> rpop teachers
"Miss.Yu"
127.0.0.1:6379[1]> rpop teachers
"Miss.Xu"
127.0.0.1:6379[1]>
实现队列(先进先出)功能:
127.0.0.1:6379[1]> lpush students stu1 stu2
(integer) 2
127.0.0.1:6379[1]> rpop students
"stu1"
127.0.0.1:6379[1]> rpop students
"stu2"
127.0.0.1:6379[1]>
场景模拟
指令 | 说明 |
---|---|
lpush key value1 value2 ... | 将一个或多个值插入到列表头部 |
rpush key value1 value2 ... | 在列表中添加一个或多个值 |
lpop key | 移除并获取列表的第一个元素,返回值为移除的元素 |
rpop key | 移除并获取列表的最后一个元素,返回值为移除的元素 |
lrange | 获取所有给定key的值 |
blpop key [key ...] timeout | 从key列表头部弹出一个元素,若列表没有元素,阻塞等待timeout秒,如果timeout=0,则会一直阻塞等待 |
brpop key [key ...] timeout | 从key列表尾部弹出一个元素,若列表没有元素,阻塞等待timeout秒,如果timeout=0,则会一直阻塞等待 |
##微博或微信公众号消息,如Vander关注了"财经旗舰"、“大众点评”两个公众号
#001为用户Vander的UserID
#模拟财经旗舰发送了一个消息,消息编号为1001
127.0.0.1:6379> lpush msg:001 msg_1001
(integer) 1
#模拟大众点评发送了一个消息,消息编号为1005
127.0.0.1:6379> lpush msg:001 msg_1005
(integer) 2
#查看待阅列表
127.0.0.1:6379> lrange msg:001 0 2
1) "msg_1005"
2) "msg_1001"
1.2.3 数据结构-Set
简介:即集合,存储一堆不重复值的组合
使用场景:抽奖、微信微博点赞、收藏、标签
Set常用操作
指令 | 说明 |
---|---|
sadd key member [member ...] | 将一个或多个值插入到列表头部 |
srem key member [member ...] | 从集合key中删除元素 |
spop key [count] | 移除并获取集合中随机获取count个元素,返回值为移除的元素 |
srandmember key [count] | 获取集合中随机获取count个元素,不会移除元素 |
sismembers key member | 判断member元素是否存在集合key中 |
smembers key | 返回集合中的所有成员 |
sunion | 返回给定集合的并集 |
往集合中添加删除元素:
127.0.0.1:6379[1]> sadd students a_stu b_stu
(integer) 2
127.0.0.1:6379[1]> spop students
"b_stu"
127.0.0.1:6379[1]> sadd students c_stu
(integer) 1
127.0.0.1:6379[1]> spop students
"c_stu"
127.0.0.1:6379[1]> sadd students 0_stus
(integer) 1
127.0.0.1:6379[1]> spop students
"a_stu"
127.0.0.1:6379[1]> sadd students a_stu b_stu
(integer) 2
127.0.0.1:6379[1]> smembers students
1) "b_stu"
2) "0_stus"
3) "a_stu"
127.0.0.1:6379[1]> spop students 3
1) "b_stu"
2) "0_stus"
3) "a_stu"
将三个集合取并集:
127.0.0.1:6379[1]> sadd stuGroup1 stu_a stu_b stu_c
(integer) 3
127.0.0.1:6379[1]> sadd stuGroup2 stu_k stu_a stu_b
(integer) 3
127.0.0.1:6379[1]> sadd stuGroup3 stu_x stu_c stu_b
(integer) 3
127.0.0.1:6379[1]> sunion stuGroup1 stuGroup2 stuGroup3
1) "stu_x"
2) "stu_c"
3) "stu_k"
4) "stu_a"
5) "stu_b"
127.0.0.1:6379[1]>
场景模拟
##模拟抽奖
# 添加抽奖用户
127.0.0.1:6379> sadd draw_user Vander Jason Panda Susan
(integer) 4
# 查看所有参与抽奖用户
127.0.0.1:6379> smembers draw_user
1) "Susan"
2) "Jason"
3) "Panda"
4) "Vander"
# 抽取一个用户放回的(不放回的用spop draw_user 1)
127.0.0.1:6379> srandmember draw_user 1
1) "Susan"
127.0.0.1:6379> srandmember draw_user 1
1) "Panda"
127.0.0.1:6379> srandmember draw_user 1
1) "Jason"
##模拟点赞取消点赞、收藏、标签
# 点赞,001-消息ID、1001-用户ID,说明1001用户对001消息点赞
127.0.0.1:6379> sadd like:001 1001
(integer) 1
# 1001用户对002消息点赞
127.0.0.1:6379> sadd like:002 1001
(integer) 1
# 1002用户对001消息点赞
127.0.0.1:6379> sadd like:001 1002
(integer) 1
# 检查1002用户是否对001消息点赞
127.0.0.1:6379> sismember like:001 1002
(integer) 1
# 获取对001消息点赞的用户列表
127.0.0.1:6379> smembers like:001
1) "1001"
2) "1002"
# 获取对001消息点赞的用户数
127.0.0.1:6379> scard like:001
(integer) 2
# 1001用户取消对001消息点赞
127.0.0.1:6379> srem like:001 1001
(integer) 1
Set集合操作
指令 | 说明 |
---|---|
sinter key [key ...] | 交集运算 |
sinterstore dest key [key...] | 交集运算并把结果存到新的集合dest中 |
sunion key [key ...] | 并集运算 |
sunionstore dest key [key ...] | 并集运算并把结果存到新的集合dest中 |
sdiff key [key ...] | 差集运算 |
sdiffstore dest key [key ...] | 差集运算并把结果存到新的集合dest中 |
集合运算应用场景:共同关注、共同喜好、电商商品筛选
假设上图中的集合Set1是A同学的关注列表,Set2是B同学的关注列表,Set3是C同学的关注列表
A同学和B同学的共用关注:Set1∩Set2 -> sinter set1 set2
A同学关注的人关注了他:即A同学关注了B同学,A同学关注了C同学,B、C同学都关注了d
SISMEMBER Set2 d(即A同学关注的B同学关注了d)
SISMEMBER Set3 d(即A同学关注的C同学关注了d)
A可能认识的人:SDIFF Set2 Set1 (即从B同学的关注列表里列出A同学没有的)
场景模拟
# 添加华为p40手机
127.0.0.1:6379> sadd brand:huawei p40
(integer) 1
# 添加小米11Pro手机
127.0.0.1:6379> sadd brand:xiaomi "xiaomi 11 pro"
(integer) 1
# 添加iPhone 12 Pro手机
127.0.0.1:6379> sadd brand:apple "iPhone 12 Pro"
(integer) 1
# 添加安卓系统的手机
127.0.0.1:6379> sadd os:android p40 "xiaomi 11 pro"
(integer) 2
# 添加内存8G的手机
127.0.0.1:6379> sadd ram:8G p40 "xiaomi 11 pro" "iPhone 12 Pro"
(integer) 3
# 获取安卓系统且内存为8G的手机
127.0.0.1:6379> sinter os:android ram:8G
1) "p40"
2) "xiaomi 11 pro"
1.2.4 数据结构-Sorted Set(ZSet)
简介:即有序集合,存储一堆不重复值的组合,与Set类似,区别在于Set不是自动有序,Sorted Set则可以通过用户额外提供一个优先级(score)来为成员排序,并且是插入有序(会自动进行排序)
使用场景:排行榜、按用户投票和进行时间排序
ZSet常用操作
指令 | 说明 |
---|---|
zadd key |
往有序集合key中加入带分值的元素 |
zrem key member [member ...] | 从有序集合key中删除元素 |
zscore key member | 返回有序集合key中元素member的分值 |
zincrby key incr member | 为有序集合key中元素member的分支加上incr |
zcard key | 返回有序集合key中的元素个数 |
zrange key start stop [withscores] | 正序获取有序集合Key,从start下标到stop下标的元素 |
zrevrange key start stop [withscores] | 倒序获取有序集合Key,从start下标到stop下标的元素 |
往有序集中添加元素:
127.0.0.1:6379[1]> del stuGroup1
(integer) 1
127.0.0.1:6379[1]> zadd stuGroup1 0 "stu_a" 1 "stu_b" 2 "stu_c"
(integer) 3
127.0.0.1:6379[1]> del stuGroup1
(integer) 1
127.0.0.1:6379[1]> zadd stuGroup1 0 stu_a 1 stu_b 2 stu_c
(integer) 3
127.0.0.1:6379[1]> zrange stuGroup1 0 -1 withscores
1) "stu_a"
2) "0"
3) "stu_b"
4) "1"
5) "stu_c"
6) "2"
移除并显示有序集的元素:
127.0.0.1:6379[1]> zrem stuGroup1 stu_b
(integer) 1
127.0.0.1:6379[1]> zrange stuGroup1 0 -1 withscores
1) "stu_a"
2) "0"
3) "stu_c"
4) "2"
127.0.0.1:6379[1]> zcard stuGroup1
(integer) 2
场景模拟
##使用ZSet实现排行榜,假设当前百度热榜是赵丽颖结婚、郑爽弃养、乔妹离婚
# 使用此方式登录,否则显示乱码
./redis-cli --raw -h 127.0.0.1 -p 6379
# 模拟点击新闻
127.0.0.1:6379> zincrby HotNews:20210201 1 郑爽弃养
"1"
127.0.0.1:6379> zincrby HotNews:20210201 1 郑爽弃养
"2"
127.0.0.1:6379> zincrby HotNews:20210201 1 郑爽弃养
"3"
127.0.0.1:6379> zincrby HotNews:20210201 1 郑爽弃养
"4"
127.0.0.1:6379> zincrby HotNews:20210201 1 赵丽颖结婚
"1"
127.0.0.1:6379> zincrby HotNews:20210201 1 赵丽颖结婚
"2"
127.0.0.1:6379> zincrby HotNews:20210201 1 乔妹离婚
"1"
127.0.0.1:6379> zincrby HotNews:20210201 1 小米11发布
"1"
127.0.0.1:6379> zincrby HotNews:20210201 1 苹果发布会
"1"
# 展示当日点击量排行前3的新闻,并将点击量输出
127.0.0.1:6379> zrevrange HotNews:20210201 0 2 withscores
郑爽弃养
4
赵丽颖结婚
2
苹果发布会
1
#新增其它两天的点击量
127.0.0.1:6379> zincrby HotNews:20210202 1 小米11发布
1
127.0.0.1:6379> zincrby HotNews:20210202 1 乔妹离婚
1
127.0.0.1:6379> zincrby HotNews:20210203 1 郑爽弃养
1
127.0.0.1:6379> zincrby HotNews:20210203 1 小米11发布
1
# 3天内的榜单计算
127.0.0.1:6379> zunionstore HotNews:20210201-20210203 3 HotNews:20210201 HotNews:20210202 HotNews:20210203
5
127.0.0.1:6379> zrange HotNews:20210201-20210203 0 -1 withscores
苹果发布会
1
乔妹离婚
2
赵丽颖结婚
2
小米11发布
3
郑爽弃养
5
# 展示3天排行前3名点击量的事件
127.0.0.1:6379> zrevrange HotNews:20210201-20210203 0 2
郑爽弃养
小米11发布
赵丽颖结婚
ZSet集合操作
指令 | 说明 |
---|---|
zunionstore dest numkeys key [key ...] | 并集运算,并存储结果到dest |
zinterstore dest numkeys key [key ...] | 交集运算,并存储结果到dest |
1.2.5 数据结构-Hash
简介:Hash是一个string类型的field和value的映射表
使用场景:存储部分变更数据,如用户信息
指令 | 说明 |
---|---|
hset key field value | 将哈希表中key的字段field的值设置为value |
hget key field | 获取存储在哈希表中指定field的值 |
hmset key field value [field value ...] | 批量获取哈希表key中对应多个field的键值 |
hmget key field [field ...] | 批量获取哈希表key中多个field键值 |
hsetnx key field value | 存储一个不存在与hash表key的键值对 |
hdel key field [field ...] | 删除hash表key中的field简直 |
hlen key | 返回hash表key中field的数量 |
hget all | 获取在哈希表中指定key的所有字段和值 |
hincrby key field increment | 为hash表key中filed键的值上增量increment |
往Hash表中插入元素:
127.0.0.1:6379[1]> hset Miss.Xu age 25
(integer) 1
127.0.0.1:6379[1]> hset Miss.Xu height 170
(integer) 1
127.0.0.1:6379[1]> hget Miss.Xu age
"25"
127.0.0.1:6379[1]> hgetall Miss.Xu
1) "age"
2) "25"
3) "height"
4) "170"
127.0.0.1:6379[1]>
场景模拟
##对象缓存
127.0.0.1:6379> hmset user:2010130110 name Vander age 18
OK
127.0.0.1:6379> hmget user:2010130110 name age
1) "Vander"
2) "18"
##电商购物车,key-用户ID field-商品ID value-商品数量
# 添加商品
127.0.0.1:6379> hset cart:2010130110 1001 1
(integer) 1
127.0.0.1:6379> hset cart:2010130110 1002 1
(integer) 1
127.0.0.1:6379> hset cart:2010130110 1003 2
(integer) 1
127.0.0.1:6379> hincrby cart:2010130110 1001 3
(integer) 4
# 获取商品种类的个数
127.0.0.1:6379> hlen cart:2010130110
(integer) 3
# 删除商品
127.0.0.1:6379> hdel cart:2010130110 1002
(integer) 1
# 获取购物车的所有商品
127.0.0.1:6379> hgetall cart:2010130110
1) "1001"
2) "4"
3) "1003"
4) "2"
Hash VS string
优势:
1)同类数据归类整合存储,方便数据管理
2)相比string操作消耗内存与CPU更小
3)相比string存储更省空间
劣势:
1)过期功能不能使用在field上,只能用在key上
2)Redis集群架构下不适合大规模使用
1.3 Java代码实操
下面我们通过Spring演示以上的数据,细节请查看Spring官方文档:
https://docs.spring.io/spring-data/redis/docs/2.1.8.RELEASE/reference/html/
官方文档中有这么一句话:
Spring Redis requires Redis 2.6 or above and Spring Data Redis integrates with Lettuce and Jedis, two popular open-source Java libraries for Redis(说明Spring的Redis客户端有Lettuce和Jedis两种)
1.3.1 Example :使用Jedis连接单实例的Redis服务端
@Profile(“jedis”)—@ActiveProfiles(“jedis”)成对使用,ActiveProfiles会选择对应的Profile进行注入
测试类:JedisTests.java——使用Jedis操作set、hash、zset、list(模拟队列)
package szu.vander.test.standalone;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import redis.clients.jedis.Jedis;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
@ActiveProfiles("single") // 设置profile
@Slf4j
public class JedisTests {
@Autowired
private Jedis jedis;
// ------------------------ jedis 工具直连演示
// jedis和redis命令名称匹配度最高,最为简洁,学习难度最低
// 列表~ 集合数据存储~ java.util.List,java.util.Stack
// 生产者消费者(简单MQ)
@Test
public void list() {
// 插入数据1 --- 2 --- 3
jedis.rpush("queue_1", "1");
jedis.rpush("queue_1", "2", "3");
List strings = jedis.lrange("queue_1", 0, -1);
for (String string : strings) {
log.info(String.format("往队列queue_1中写入:%s", string));
}
// 消费者线程简例
while (true) {
String item = jedis.lpop("queue_1");
if (item == null) break;
log.info(String.format("从队列queue_1中取出:%s", item));
}
jedis.close();
}
// 类似:在redis里面存储一个hashmap
// 推荐的方式,无特殊需求是,一般的缓存都用这个
@Test
public void hashTest() {
String key = "2010130110";
jedis.hset(key, "name", "Vander");
jedis.hset(key, "age", "18");
jedis.hget(key, "name");
log.info(String.format("获取Key=%s的所有相关属性:%s", key, jedis.hgetAll(key).toString()));
jedis.close();
}
// 用set实现(交集 并集)
// 交集示例: 共同关注的好友
// 并集示例:
@Test
public void setTest() {
// 取出两个人共同关注的好友
// 每个人维护一个set
jedis.sadd("userA", "userC", "userD", "userE");
jedis.sadd("userB", "userC", "userE", "userF");
// 取出共同关注
Set intersection = jedis.sinter("userA", "userB");
log.info(String.format("获取userA{%s}和userB{%s}的交集:%s", jedis.smembers("userA"), jedis.smembers("userB"), intersection));
// 取出共同人群
Set unionSet = jedis.sunion("userA", "userB");
log.info(String.format("获取userA{%s}和userB{%s}的并集:%s", jedis.smembers("userA"), jedis.smembers("userB"), unionSet));
jedis.close();
}
// 游戏排行榜
@Test
public void zsetTest() {
String ranksKeyName = "exam_rank";
jedis.zadd(ranksKeyName, 100.0, "stu1");
jedis.zadd(ranksKeyName, 82.0, "stu2");
jedis.zadd(ranksKeyName, 90, "stu3");
jedis.zadd(ranksKeyName, 96, "stu4");
jedis.zadd(ranksKeyName, 89, "stu5");
jedis.zadd(ranksKeyName, 29, "stu6");
Set stringSet = jedis.zrevrange(ranksKeyName, 0, 2);
System.out.println("返回前三名:");
for (String s : stringSet) {
System.out.println(s);
}
long zcount = jedis.zcount(ranksKeyName, 85, 100);
System.out.println("超过85分的数量 " + zcount);
jedis.close();
}
}
JedisConfig
package szu.jason.redis.standalone.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import redis.clients.jedis.Jedis;
import szu.jason.redis.standalone.config.common.RedisStandaloneProperties;
/**
* @author : Vander
* @date : 2021/3/7
* @description : 注入连接单节点的Jedis客户端
*/
@Profile("jedis")
@Slf4j
@Configuration
public class JedisConfig {
@Autowired
private RedisStandaloneProperties properties;
@Bean
public Jedis jedis() {
log.info("注入Jedis!");
Jedis jedis = new Jedis(properties.getHostname(), properties.getPort());
jedis.select(properties.getDb());
return jedis;
}
}
User
package szu.jason.redis.standalone.model;
import com.sun.xml.internal.ws.developer.Serialization;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* @author : Vander
* @date : 2021/3/7
* @description : 此对象需要实现序列化接口,否则JDK序列化器无法将其序列化
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {
private String userId;
private String username;
}
AppConfig
package szu.jason.redis.standalone.config.common;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import szu.jason.redis.standalone.model.User;
import java.util.HashMap;
import java.util.Map;
/**
* @author : Vander
* @date : 2021/3/7
* @description :
*/
@ComponentScan("szu.jason.redis.standalone")
@Configuration
public class AppConfig {
@Bean
public Map userRepository() {
Map userRepository = new HashMap<>();
userRepository.put("2010130110", new User("2010130110", "Jason"));
userRepository.put("2013130128", new User("2013130128", "Panda"));
return userRepository;
}
}
PropertiesConfig
package szu.jason.redis.standalone.config.common;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
/**
* @author : Vander
* @date : 2021/3/7
* @description :
*/
@Configuration
@EnableConfigurationProperties(RedisStandaloneProperties.class)
public class PropertiesConfig {
}
RedisStandaloneProperties
package szu.jason.redis.standalone.config.common;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* @author : Vander
* @date : 2021/3/7
* @description : 根据前缀注入属性值
*/
@Getter
@Setter
@ConfigurationProperties(prefix = "standalone.redis")
public class RedisStandaloneProperties {
private String hostname;
private int port;
private int db;
}
application-redis.properties——配置redis服务
standalone.redis.hostname=192.168.118.8
standalone.redis.port=6379
standalone.redis.db=1
Pom
cache-redis-demo
szu.jason
0.0.1-SNAPSHOT
4.0.0
redis-standalone
redis.clients
jedis
3.1.0
io.lettuce
lettuce-core
5.2.2.RELEASE
org.springframework
spring-test
5.2.4.RELEASE
ch.qos.logback
logback-classic
1.2.3
junit
junit
4.12
org.springframework.data
spring-data-redis
2.2.5.RELEASE
org.springframework.boot
spring-boot-configuration-processor
2.2.5.RELEASE
org.springframework.boot
spring-boot-starter
2.2.5.RELEASE
org.springframework.boot
spring-boot-starter-test
2.2.5.RELEASE
1.3.2 Example :使用Lettuce连接单实例的Redis服务端
测试类:使用Lettuce客户端操作Hash类型的数据
package test.redis.standalone;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import szu.jason.redis.standalone.config.common.AppConfig;
import szu.jason.redis.standalone.model.User;
import szu.jason.redis.standalone.service.LettuceExampleService;
@ActiveProfiles("lettuce")
@Slf4j
@RunWith(SpringJUnit4ClassRunner.class)
@PropertySource("classpath:application-redis.properties")
@ImportAutoConfiguration(classes= AppConfig.class)
public class LettuceTests {
@Autowired
private LettuceExampleService exampleService;
@Test
public void testGet() throws Exception {
User user = exampleService.findUser("2010130110");
System.out.println(user);
}
}
LettuceConfig
package szu.jason.redis.standalone.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import szu.jason.redis.standalone.config.common.RedisStandaloneProperties;
@Profile("lettuce")
@Slf4j
@EnableCaching
@Configuration
public class LettuceConfig {
@Autowired
private RedisStandaloneProperties properties;
@Bean
public LettuceConnectionFactory redisConnectionFactory() {
log.info(String.format("注入LettuceConnectionFactory,当前Host:%s:%s database:%s!",
properties.getHostname(), properties.getPort(), properties.getDb()));
RedisStandaloneConfiguration redisStandaloneConfiguration =
new RedisStandaloneConfiguration(properties.getHostname(), properties.getPort());
redisStandaloneConfiguration.setDatabase(properties.getDb());
return new LettuceConnectionFactory(redisStandaloneConfiguration);
}
@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
log.info("注入RedisTemplate!");
RedisTemplate redisTemplate = new RedisTemplate();
redisTemplate.setConnectionFactory(redisConnectionFactory);
// 可以配置对象的转换规则,比如使用json格式对object进行存储。
// Object --> 序列化 --> 二进制流 --> redis-server存储
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());
return redisTemplate;
}
// 配置Spring Cache注解功能
@Bean
public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
log.info("注入CacheManager!");
RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory);
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();
RedisCacheManager cacheManager = new RedisCacheManager(redisCacheWriter, redisCacheConfiguration);
return cacheManager;
}
}
LettuceExampleService
package szu.jason.redis.standalone.service;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Profile;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import szu.jason.redis.standalone.model.User;
import java.util.Map;
@Profile("lettuce")
@Slf4j
@Service
public class LettuceExampleService {
// 参数可以是任何对象,默认由JDK序列化
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private Map userRepository;
/**
* 对象缓存功能
*/
public User findUser(String userId) throws Exception {
User user;
// 1、 判定缓存中是否存在
user = (User) redisTemplate.opsForValue().get(userId);
if (user != null) {
log.info("从缓存中读取到值:" + user);
return user;
}
// TODO 2、不存在则读取数据库或者其他地方的值
user = userRepository.get(userId);
log.info("从数据库中读取到值:" + user);
// 3、 同步存储value到缓存。
redisTemplate.opsForValue().set(userId, user);
return user;
}
}
AppConfig、PropertiesConfig、RedisStandaloneProperties、User、application-redis.properties、pom均与上个example一致
1.3.3 Example :使用Spring Cache实现自动移除缓存、添加缓存、更新缓存
测试类:模拟查找、更新、删除数据库记录的操作,Spring Cache会自动更新缓存
package test.redis.standalone;
import lombok.extern.slf4j.Slf4j;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.MethodSorters;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import szu.jason.redis.standalone.config.common.AppConfig;
import szu.jason.redis.standalone.model.User;
import szu.jason.redis.standalone.service.SpringCacheService;
@ActiveProfiles("lettuce")
@Slf4j
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@RunWith(SpringJUnit4ClassRunner.class)
@PropertySource("classpath:application-redis.properties")
@ImportAutoConfiguration(classes= AppConfig.class)
public class SpringCacheTests {
private final static String USER_ID = "2010130110";
@Autowired
private SpringCacheService springCacheService;
@Test
public void test0FindUserById() {
User user = springCacheService.findUserById(USER_ID);
System.out.println("\n");
log.info("查找到userId为{}的用户:{}", USER_ID, user.toString());
System.out.println("\n");
}
@Test
public void test1UpdateUser() {
springCacheService.updateUser(new User(USER_ID, "Panda"));
User user = springCacheService.findUserById(USER_ID);
System.out.println("\n");
log.info("userId为{}的用户:{}", USER_ID, user.toString());
System.out.println("\n");
}
@Test
public void test2DeleteUserById() {
User user = springCacheService.deleteUserById(USER_ID);
System.out.println("\n");
log.info("删除userId为{}的用户:{}", USER_ID, user.toString());
System.out.println("\n");
}
}
SpringCacheService
package szu.jason.redis.standalone.service;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;
import szu.jason.redis.standalone.model.User;
import java.util.Map;
@Profile("lettuce")
@Slf4j
@Service
public class SpringCacheService {
@Autowired
private Map userRepository;
/**
* value的作用:写入Redis时会自动添加缓存前缀
* key:支持SpEL表达式
* @param userId
* @return
*/
@Cacheable(cacheManager = "cacheManager", value = "user", key = "#userId")
public User findUserById(String userId) {
// 读取数据库
User user = userRepository.get(userId);
System.out.println("\n");
log.info("从数据库中读取到数据:{}", user);
System.out.println("\n");
return user;
}
@CacheEvict(cacheManager = "cacheManager", value = "user", key = "#userId")
public User deleteUserById(String userId) {
User user = userRepository.remove(userId);
System.out.println("\n");
log.info(String.format("用户从数据库删除成功,请检查缓存ID:%s是否已经清除!", user.getUserId()));
System.out.println("\n");
return user;
}
// 如果数据库更新成功,更新redis缓存
@CachePut(cacheManager = "cacheManager", value = "user", key = "#user.userId", condition = "#result ne null")
public User updateUser(User user) {
// 更新数据库
userRepository.put(user.getUserId(), user);
System.out.println("\n");
log.info("数据库进行了更新,检查缓存是否一致");
System.out.println("\n");
return user; // 返回最新内容,代表更新成功
}
}
AppConfig、PropertiesConfig、RedisStandaloneProperties、User、application-redis.properties、pom均与上个example一致
运行结果:
test0FindUserById:观察到对应的User被加载到了缓存
test1UpdateUser:观察到对应的User的名称被改为了Panda
test2DeleteUserById:观察到对应的Key已经被删除了
附录:建议安装教程
下载Redis,解压后编译
$ wget http://download.redis.io/releases/redis-5.0.4.tar.gz
$ tar xzf redis-5.0.4.tar.gz
$ cd redis-5.0.4
$ make
运行Redis服务端
$ src/redis-server
You can interact with Redis using the built-in client:
运行Redis客户端
$ src/redis-cli
redis> set foo bar
OK
redis> get foo
"bar"
···