redis 缓存什么数据(Redis只会用缓存20种妙用让同事直呼惊艳)
redis 缓存什么数据(Redis只会用缓存20种妙用让同事直呼惊艳)寄快递、网购的时候,查询物流信息,都会给我们展示xxx时候,快递到达什么地方了,这就是一个典型的时间线列表;# 购物车添加单个商品 127.0.0.1:6379> HSET sc:u1 c001 1 (integer) 1 # 购物车添加多个商品 127.0.0.1:6379> HMSET sc:u1 c002 1 coo3 2 OK # 添加商品购买数量 127.0.0.1:6379> HINCRBY sc:u1 c002 1 (integer) 2 # 减少商品的购买数量 127.0.0.1:6379> HINCRBY sc:u1 c003 -1 (integer) 1 # 获取单个的购买数量 127.0.0.1:6379> HGET sc:u1 c002 "2" # 获取购物车的商品数量 127.0.0.1:6379> HLEN s
假如现在iphone 100 、华为mate 5000 已发布,在各大商城上线;下面就通过 Redis 的 set 来实现上述的商品筛选功能;
功能所需命令
- SADD key member [member …]:添加一个或多个元素
- SINTER key [key …]:返回给定所有集合的交集
# 将iphone100 添加到品牌为苹果的集合
127.0.0.1:6379> sadd brand:apple iphone100
(integer) 1
# 将meta5000 添加到品牌为苹果的集合
127.0.0.1:6379> sadd brand:huawei meta5000
(integer) 1
# 将 meta5000 iphone100 添加到支持5T内存的集合
127.0.0.1:6379> sadd ram:5t iphone100 meta5000
(integer) 2
# 将 meta5000 添加到支持10T内存的集合
127.0.0.1:6379> sadd ram:10t meta5000
(integer) 1
# 将 iphone100 添加到操作系统是iOS的集合
127.0.0.1:6379> sadd os:ios iphone100
(integer) 1
# 将 meta5000 添加到操作系统是Android的集合
127.0.0.1:6379> sadd os:android meta5000
(integer) 1
# 将 iphone100 meta5000 添加到屏幕为6.0-6.29的集合中
127.0.0.1:6379> sadd screensize:6.0-6.29 iphone100 meta5000
(integer) 2
# 筛选内存5T、屏幕在6.0-6.29的机型
127.0.0.1:6379> sinter ram:5t screensize:6.0-6.29
1) "meta5000"
2) "iphone100"
# 筛选内存10T、屏幕在6.0-6.29的机型
127.0.0.1:6379> sinter ram:10t screensize:6.0-6.29
1) "meta5000"
# 筛选内存5T、系统为iOS的机型
127.0.0.1:6379> sinter ram:5t screensize:6.0-6.29 os:ios
1) "iphone100"
# 筛选内存5T、屏幕在6.0-6.29、品牌是华为的机型
127.0.0.1:6379> sinter ram:5t screensize:6.0-6.29 brand:huawei
1) "meta5000"
18购物车
商品缓存
电商项目中,商品消息,都会做缓存处理,特别是热门商品,访问用户比较多,由于商品的结果比较复杂,店铺信息,产品信息,标题、描述、详情图,封面图;为了方便管理和操作,一般都会采用 Hash 的方式来存储(key为商品ID,field用来保存各项参数,value保存对于的值)
购物车
当商品信息做了缓存,购物车需要做的,就是通过Hash记录商品ID,以及需要购买的数量(其中key为用户信息,field为商品ID,value用来记录购买的数量) ;
功能所需命令
- HSET key field value : 将哈希表 key 中的字段 field 的值设为 value ;
- HMSET key field1 value1 [field2 value2 ] :同时将多个 field-value (域-值)对设置到哈希表 key 中。
- HGET key field:获取存储在哈希表中指定字段的值。
- HGETALL key :获取在哈希表中指定 key 的所有字段和值
- HINCRBY key field increment :为哈希表 key 中的指定字段的整数值加上增量 increment 。
- HLEN key:获取哈希表中字段的数量
# 购物车添加单个商品
127.0.0.1:6379> HSET sc:u1 c001 1
(integer) 1
# 购物车添加多个商品
127.0.0.1:6379> HMSET sc:u1 c002 1 coo3 2
OK
# 添加商品购买数量
127.0.0.1:6379> HINCRBY sc:u1 c002 1
(integer) 2
# 减少商品的购买数量
127.0.0.1:6379> HINCRBY sc:u1 c003 -1
(integer) 1
# 获取单个的购买数量
127.0.0.1:6379> HGET sc:u1 c002
"2"
# 获取购物车的商品数量
127.0.0.1:6379> HLEN sc:u1
(integer) 3
# 购物车详情
127.0.0.1:6379> HGETALL sc:u1
1) "c001"
2) "1"
3) "c002"
4) "2"
5) "coo3"
6) "2"
19定时取消订单(key过期监听)
电商类的业务,一般都会有订单30分钟不支付,自动取消的功能,此时就需要用到定时任务框架,Quartz、xxl-job、elastic-job 是比较常用的 Java 定时任务;我们也可以通过 Redis 的定时过期、以及过期key的监听,来实现订单的取消功能;
- Redis key 过期提醒配置修改 redis 相关事件配置。找到 redis 配置文件 redis.conf,查看 notify-keyspace-events 配置项,如果没有,添加 notify-keyspace-events Ex,如果有值,则追加 Ex,相关参数说明如下:
- K:keyspace 事件,事件以 keyspace@ 为前缀进行发布
- E:keyevent 事件,事件以 keyevent@ 为前缀进行发布
- g:一般性的,非特定类型的命令,比如del,expire,rename等
- $:字符串特定命令
- l:列表特定命令
- s:集合特定命令
- h:哈希特定命令
- z:有序集合特定命令
- x:过期事件,当某个键过期并删除时会产生该事件
- e:驱逐事件,当某个键因 maxmemore 策略而被删除时,产生该事件
- A:g$lshzxe的别名,因此”AKE”意味着所有事件
- 添加RedisKeyExpirationListener的监听import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
@Configuration
public class RedisListenerConfig {
@Bean
RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
return container;
}
}
KeyExpirationEventMessageListener 接口监听所有 db 的过期事件 keyevent@*:expired"package com.ehang.redis.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.listener.KeyExpirationEventMessageListener;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.stereotype.Component;
/**
* 监听所有db的过期事件__keyevent@*__:expired"
*
* @author 一行Java
* @title: RedisKeyExpirationListener
* @projectName ehang-spring-boot
* @description: TODO
* @date 2022/8/5 16:36
*/
@Component
@Slf4j
public class RedisKeyExpirationListener extends KeyExpirationEventMessageListener {
public RedisKeyExpirationListener(RedisMessageListenerContainer listenerContainer) {
super(listenerContainer);
}
/**
* 针对 redis 数据失效事件,进行数据处理
*
* @param message
* @param pattern
*/
@Override
public void onMessage(Message message byte[] pattern) {
// 获取到失效的 key,进行取消订单业务处理
// 由于这里是监听了所有的key,如果只处理特定数据的话,需要做特殊处理
String expiredKey = message.toString();
log.info("过期的Key:{}" expiredKey);
}
} - 测试为了快速验证效果,这里 将过期时间调整为2秒;注意,由于过期之后,Redis中的Key已经不存在了,因此,一定要将订单号作为key,不能作为值保存,否则监听到过期Key之后,将拿不到过期的订单号;
- 不推荐使用基于这一套机制,确实能够实现订单的超时取消,但是还是不太建议使用,这里仅作为一个思路;原因主要有以下几个:
- redis 的过期删除策略是采用定时离线扫描,或者访问时懒性检测删除,并没有办法保证时效性,有可能key已经到期了,但Redis并没有扫描到,导致通知的延迟;
- 消息发送即忘(fire and forget),并不会保证消息的可达性,如果此时服务不在线或者异常,通知就再也收不到了;
寄快递、网购的时候,查询物流信息,都会给我们展示xxx时候,快递到达什么地方了,这就是一个典型的时间线列表;
数据库的做法,就是每次变更就插入一条带时间的信息记录,然后根据时间和ID(ID是必须的,如果出现两个相同的时间,单纯时间排序,会造成顺序不对),来排序生成时间线;
我们也可以通过 Redis 的 List 来实现时间线功能,由于 List 采用的是双向链表,因此升序,降序的时间线都能正常满足;
- RPUSH key value1 [value2]:在列表中添加一个或多个值,(升序时间线)
- LPUSH key value1 [value2]:将一个或多个值插入到列表头部(降序时间线)
- LRANGE key start stop:获取列表指定范围内的元素
Redis-cli 客户端测试
- 升序127.0.0.1:6379> RPUSH time:line:asc 20220805170000
(integer) 1
127.0.0.1:6379> RPUSH time:line:asc 20220805170001
(integer) 2
127.0.0.1:6379> RPUSH time:line:asc 20220805170002
(integer) 3
127.0.0.1:6379> LRANGE time:line:asc 0 -1
1) "20220805170000"
2) "20220805170001"
3) "20220805170002" - 降序127.0.0.1:6379> LPUSH time:line:desc 20220805170000
(integer) 1
127.0.0.1:6379> LPUSH time:line:desc 20220805170001
(integer) 2
127.0.0.1:6379> LPUSH time:line:desc 20220805170002
(integer) 3
127.0.0.1:6379> LRANGE time:line:desc 0 -1
1) "20220805170002"
2) "20220805170001"
3) "20220805170000"
好了,关于Redis 的妙用,就介绍到这里;有了这些个场景的运用,下次再有面试官问你,Redis除了缓存还做过什么,相信聊上个1把小时,应该不成问题了。