Redis丰富的数据存储结构及基于内存的高性能操作使得其在很多应用场景中大显身手。以下列举了一些常涉及Redis的应用场景。
针对用户登录进行频控
该场景主要是利用Redis对用户登录进行既定时间粒度的访问次数上限限制,也可以将其扩展到某种业务(或接口)的频次限制。
如何用Redis设计一套逻辑,限制1个小时内每个用户ID最多只能登录5次?
解决如上问题思路如下:
- 判断一个用户当前登录时间与向前推最近第5次登录时间做比较,如果不存在可以登录,若存在,则比较是否超过1个小时;在1小时内则拒绝登录,若大于等于1小时则可以登录。
- 如上思路借助于Redis的列表数据结构,列表的每个元素即用户登录的时间(可登录的情况)。
此外,设计上需要注意的是,为了让该风控逻辑更加灵活,存储每个用户的列表长度设置可以比阈值稍大一些(或可动态配置),比如,根据调控粒度需要,我把登录上限次数从5调到10的话,如果列表入队只入5条(若写死),还需代码层面上调整 。时间粒度也可以设计成动态配置,便于后期灵活调整频控。
Java实现代码
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import redis.clients.jedis.Jedis;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebFilter(urlPatterns = "/*")
public class FrequencyControlFilter implements Filter {
private Jedis jedis; //redis采用单例
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
if (jedis == null) {
AnnotationConfigServletWebServerApplicationContext acswac = (AnnotationConfigServletWebServerApplicationContext) WebApplicationContextUtils
.getWebApplicationContext(servletRequest.getServletContext());
jedis = (Jedis) acswac.getBean("jedis");
}
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
String uid = httpServletRequest.getHeader("uid"); // uid获取粗略地mock成从消息头获取,实际操作中,uid获取因公司而异
boolean thresholdLogInFlag = false; // 阈值登录标记
int now = (int) (System.currentTimeMillis() / 1000);
String loginTimeStampStr = jedis.lindex(uid, 4); // 获取最近第5次登录的时间点,登录上限阈值可用动态配置
if (loginTimeStampStr != null) {
thresholdLogInFlag = true;
int loginTimeStamp = Integer.parseInt(loginTimeStampStr);
if (now - loginTimeStamp < 3600) { // 登录频控的时间粒度1个小时,该值也可用动态配置,便于后续扩展
HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse;
httpServletResponse.sendError(HttpServletResponse.SC_FORBIDDEN, "Forbidden");
return;
}
}
jedis.rpush(uid, String.valueOf(now)); // 向表尾加入登录时间戳
if (thresholdLogInFlag) {
jedis.ltrim(uid, 0, 4); // 只保留上限阈值内最近登录的时间点
}
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
}
}
实现用户上线次数的统计
该场景主要是利用Redis的SETBIT命令和BITCOUNT命令来实现。
如何用Redis设计一套逻辑,统计用户上线的频次(时间粒度为1天)?
设计思路如下:
- 通过SETBIT命令来记录用户的登录设置,比如以用户ID作为key,以那天所代表的网站的上线日作为offset参数,并将这个offset上的位设置为1。
- 当要计算某个用户总共以来的上线次数时,就以该用户ID作为key,执行BITCOUNT命令,得出的结果就是该用户上线的总天数。
性能表现
如上统计例子,即使运行10年,占用的空间也只是每个用户10*365比特位(bit),即每个用户占用456字节。对于这种大小的数据来说,BITCOUNT的处理速度就像GET和INCR这种时间复杂度位O(1)的操作一样快。