一次简单的基于Redis的每日使用次数限制需求开发

新年快乐

『大伙们开工了不 ?』
过年刚回来,前两天需求还没出,摸鱼正开心呢,需求来了!


需求描述

需求是给系统内的用户发薪,需要用户添加/修改银行卡,对用户的银行卡的信息进行校验,这个校验的接口是从阿里云上找的供应商,不太能白嫖在这里插入图片描述
,每次请求这个接口收费1毛,但是预算有限,所以开发过程中产品强烈邀请增加一个校验:需要对每个用户添加/修改银行卡的操作进行限制,规则是每日每人三次只能校验三次银行卡


概要设计

需求本身也不难理解,相信xdm看到这个需求都能想到很多的解决方案,例如

  • 在数据库中存储用户Id及校验次数,定时任务根据每日时间定时删除。(比较繁琐,且不优雅,不推荐)
  • 使用redis缓存,以userId生成不同的key,value初始化为0,每次添加/修改银行卡后加1,超过3次进行校验提示,根据业务需要对key设置过期时间(本次需求我使用的就是这个)

需求虽然简单,技术方案也比较明确,但是我好久没用redis了,这个项目也比较老,redis相关的配置也没有,开发过程中出现了不少的低级问题,以此文记录下,并鞭策下自己。


开发中的问题
RedisTemplate相关API不熟悉

之前也用过RedisTemplate,虽然印象有点模糊,但我蜜汁自信这么简单的需求难得倒我,就没有再去看看redis相关的命令,一把梭开始(以后再也不这样了,好好反省)。在这里插入图片描述

首先每次调用请求进来,要做的是根据userId生成key,初始value的值为1,放进redis缓存里
为了方便本地测试,过期时间先不设置成1天,这里我是设置成50秒。

 boolean existsFlag = this.redisTemplate.opsForValue().setIfAbsent("addBank_"+qry.getUserId()+"_count",1,50, TimeUnit.SECONDS);
 Integer bindedDailyCounts = (Integer) redisTemplate.opsForValue().get("addBank_"+qry.getUserId()+"_count");

接着进行相关的银行卡校验之后,对这次userId对应的value进行+1的操作。
第一个问题就是下面我写的这个错误代码,使用了RedisTemplate的getAndSet()方法

this.redisTemplate.opsForValue().getAndSet("addBank_"+qry.getUserId()+"_count",bindedDailyCounts + 1);

当时的我还没意识到问题所在,一把梭完代码后,打开postman就是一顿请求,同一个userId,请求三次后,如愿以偿地看到了限制,这不就是产品同事要的效果嘛!

{
   "code": 1010,
   "data": null,
   "message": "当日添加银行卡次数超过3次",
   "taskId": "a0893c76ddda41f2bd9e27cee37c773f",
   "time": "2022-02-09 18:05:08"
}

but!50秒之后我就使用同一个userId继续请求,按照设想,这个时候是没有限制的,结果居然还是被限制了。

{
    "code": 1010,
    "data": null,
    "message": "当日添加银行卡次数超过3次",
    "taskId": "8020e8ba0be144bfb0319888b2788ce5",
    "time": "2022-02-09 18:12:27"
}

第一时间,我这过期时间设置的没错呀,setIfAbsent()方法,点击进去看看源码

/**
	 * Set {@code key} to hold the string {@code value} and expiration {@code timeout} if {@code key} is absent.
	 *
	 * @param key must not be {@literal null}.
	 * @param value must not be {@literal null}.
	 * @param timeout the key expiration timeout.
	 * @param unit must not be {@literal null}.
	 * @return {@literal null} when used in pipeline / transaction.
	 * @since 2.1
	 * @see <a href="https://redis.io/commands/set">Redis Documentation: SET</a>
	 */
	@Nullable
	Boolean setIfAbsent(K key, V value, long timeout, TimeUnit unit);

看完之后,我的评价是我代码没问题啊,这个时候我还没意识到getAndSet()方法的问题,决定先打印这个key的过期时间看看。

System.out.println(this.redisTemplate.getExpire("addBank_"+qry.getUserId()+"_count") + " ---过期时间");

输出结果 -1,-1是什么意思,第一反应打开redis官网看看文档(https://redis.io/commands/ttl
在这里插入图片描述
我key的过期时间呢,一行一行的代码看,嗯?getAndSet()
难道是它的问题,看了下源码,复习了下redis的命令,果然是它,由于自己对redis的不熟悉,非常不严谨的用了这个api,它会先拿到当前key,并且重新设置这个key,但是这个api没法设置过期时间,我当时根本没想到(再次鞭尸),当时心里主观认为,这个api就是拿出当前的key,然后再放回去,没想到这个key会被覆盖,导致拿不到之前设置的过期时间了。

/*
	 * (non-Javadoc)
	 * @see org.springframework.data.redis.core.ValueOperations#getAndSet(java.lang.Object, java.lang.Object)
	 */
	@Override
	public V getAndSet(K key, V newValue) {

		byte[] rawValue = rawValue(newValue);
		return execute(new ValueDeserializingRedisCallback(key) {

			@Override
			protected byte[] inRedis(byte[] rawKey, RedisConnection connection) {
				return connection.getSet(rawKey, rawValue);
			}
		}, true);
	}

最后赶紧去复习下RedisTemplate和redis相关的命令,正确的增加使用次数的操作如下,在业务流程和银行卡校验走完后,使用increment()

this.redisTemplate.opsForValue().increment("addBank_"+qry.getUserId()+"_count",bindedDailyCounts + 1);

删除这个key后,再次重复下之前的请求操作,发现50秒之后,可以对相同的userId再次操作了。


排查问题思路不对

接下来的问题和技术本身倒是没太大关系,问题也不大,主要是记录下自己排查问题的时候的思路,mark下自己思维有问题的点。

这个需求本地折腾完之后,自测了下接口,问题不大,使用IDEA一键发布到了测试机的docker上,然后开始和前端联调这个需求。
问题现象:添加银行卡的接口严重超时,前端调不通。我立马试了下本地发现没有问题,然后postman调用测试机发现确实超时调不通。
排查:首先我先看了一遍本地代码,发现没啥问题,重新发布了测试机,还是不行,折腾了很久才发现问题,docker内解析阿里云服务校验地址的域名居然和服务器解析的不一样。
解决办法:配置DNS强行映射到正确的IP。

排查过程中我的不足

  • 丢到测试服务器上没有自测
  • 定位问题时,日志没看全就以为知道问题是啥了,当时日志打印的很清楚,结果自己在网上一顿乱搜
  • 开发过程中太过粗心(很严重)
总结

这次需求很简单,但实际开发过程中暴露了自身的很多问题,也不仅仅是这一个需求的问题,谨以此文鞭策自己,不断地温故而知新,不能连续两次掉进同一条河。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值