新手 JAVA Redis常用应用指南
常用指令
首先大略介绍下 redis 常用 指令
- set key value //设置指定 key 的值
- get key // 获取指定key的值
- exists key //检查key是否存在
- expire key timestamp //设置key的过期时间 时间单位 毫秒
- INCR key //key 值为数字自增 没有key时默认0
- hset key hkey value // 设置指定key上对应hashKey的值
- hget key hkey //获取指定key上对应hashKey的值
- lphsh key value //设置指定key列表从左边插入值
- lpop key //移除key列表左往右第一个值并返回 下图 第一个值为c 返回c
- rpush key value //与lpush相反的情况
- rpop key //移除key列表右往左第一个值 并返回 例如上图第一个值为a 返回a
… 指令还有很多 目前先介绍这些 想了解更多 可以去官网:https://redis.io/commands/ 或者去菜鸟教程上查询了解详细信息 这里就不再进行复述
常用场景
-
登录鉴权
这个场景应该是开发最常见的,redis 可以利用 set userId token 用于存放用户登录的token,使其能在各处服务能公用相同token进行鉴权,利用 expire userId 3600000 设置token的失效时间, 确保用户登录的时效。 -
单号生成器
订单号在单服务下可能无所谓写个工具可以直接使用,但是在微服务下确保单号唯一需要将序号共享,而redis内能做到序号不重复自增数的指令是 INCR key, 所以我们可以用他去做共享唯一值。缺点这样容易出现断码,但是按照一般逻辑 不影响。除非是要强一致连码。以下是我使用的单号生成器工具方法
/**
* 获取订单号
*
* @return
*/
private String getOrderNo() {
//获取日期
String date = DateFormatUtils.format(new Date(), "YYYYMMdd");
// 自增属性并设置过期时间
Long incr = redisUtils.incr("order_" + date, EXPIRE);
//单号拼接
return date + "" + String.format("%0" + 5 + "d", incr);
}
- 订单公用队列
假设有个业务系统,需要将订单创建时有序依次发送到业务系统内做备份,每次只能发送一个订单到业务系统内。
这个时候我们首先想到的是利用队列的形式依次去发送请求。但是大部分队列都是对应单服务情况下使用,当然这里也可以用MQ框架进行共享,当然只能算我比较懒没有去打算部署MQ,所以我就想了下redis下如何进行使用。 假设我有3个服务 base,order1,order2, order1 和 order2 都在生产订单,我们可以在生产订单完成结尾 用 redis 指令 rphsh order orderId1 ,order 为key 值 不变 orderId1 为实际订单id值 或者 直接传order对象, 然后base 环境可以单起一个线程专门用死循环做订单数据处理 用 redis 指令 lpop order 检查最先进入的一个值进行业务处理,处理完自动去查询下个数据。如果没有数据,可以对循环进行定时休眠操作,隔一段时间查询一次,确保后来的数据能被捕获且处理。
例如以下监听代码。
- 待支付超时处理
其实延时队列很多MQ想要了解的可以去百度百度,我这里是利用了redis的失效监听机制,
在java内,redis的key删除是可以被检测到的,其他的应该也可以。那么我们可以在订单创建时就可以将订单 加入到缓存内 并设置失效时间
用到指令 set orderId order ; expire orderId 1800000; 这样我们就可以设置完订单了,然后java 内 我们可以这样配置
下面是java配置以及监听代码
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport
{
@Autowired
private RedisConnectionFactory redisConnectionFactory;
@Bean
RedisMessageListenerContainer container() {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(redisConnectionFactory);
return container;
}
@Bean
public RedisKeyExpirationListener keyExpiredListener() {
return new RedisKeyExpirationListener(this.container());
}
}
其中的 keyExpiredListener 用作设置过期监听类
RedisKeyExpirationListener 代码 大致配置框架 可根据实际业务进行调整
public class RedisKeyExpirationListener extends KeyExpirationEventMessageListener {
private static final Logger log = LoggerFactory.getLogger(RedisKeyExpirationListener.class);
//限流使用 防止大批量订单失效导致系统业务崩溃
private static AtomicInteger count=new AtomicInteger(0);
@Autowired
private RedisUtils redisCache;
@Autowired
private OmsOrderBaseExtService orderBaseExtService;
//存放重试次数
private static Map<String,Integer> retryMap=new HashMap<>();
public RedisKeyExpirationListener(RedisMessageListenerContainer listenerContainer) {
super(listenerContainer);
}
@Override
public void onMessage(Message message, byte[] pattern) {
String expiredKey = message.toString();
log.info("expiredKey========="+expiredKey);
String[] s = expiredKey.split(RedisUtils.COLON);
if (s.length==3 && RedisUtils.EXPIRE.equals(s[0])){
if (count.get()>3) {
//同时有过多订单进入时 最多只有两个待处理 其余的 重新进入队列等待
redisCache.hset(s[1], s[2], cacheMapValue, 20L, TimeUnit.SECONDS);
}else{
count.incrementAndGet();
//多类型参数处理
String cacheMapValue = redisCache.hget(s[1], s[2]);
if (StringUtils.isNotBlank(cacheMapValue)) {
redisCache.hdel(s[1], s[2]);
}
try {
switch (s[1]){
case RedisUtils.CHECK_ORDER:
//TODO 订单失效业务处理
break;
default:
log.error("不存在");
}
retryMap.remove(expiredKey);
}catch (Exception e){
if (retryMap.get(expiredKey)!=null&&retryMap.get(expiredKey)>5){
log.error(cacheMapValue+"重试失败次数过多:",e);
retryMap.remove(expiredKey);
return;
}
log.error(cacheMapValue+"二十秒后重试:",e);
//二十秒后重试
redisCache.hset(s[1],s[2],cacheMapValue,20L, TimeUnit.SECONDS);
retryMap.put(expiredKey,retryMap.get(expiredKey)==null?0:retryMap.get(expiredKey)+1);
}
count.decrementAndGet();
}
}
}
}
- 地图计算应用
redis内在3.2版本新增了一个地理位置信息存储功能 RedisGEO,这个功能可以让我们很方便的实现点与点之间的支线距离计算以及根据点查找附件点位的功能,常见的有比如共享充电宝地址 查找已用户为中心最近的共享充电宝位置。这些都可以利用redis的指令去实现,具体的可以搜索 redis geo 了解详细指令。
注意:使用坐标点 一定要做好 坐标同源 比如 高德的坐标点 和 百度的坐标点 直接计算是存在误差的 将高德的坐标点放到百度地图上 也会出现位置偏移 。 这是因为不同地图使用的 坐标系不同 ,所以使用时一定需要注意
分享到此结束,如果还有其他比较常用的场景,可以写评论区一起学习。