背景

Hello, everyone,long time no see.

事情发生在9月8号晚淘宝促销活动,短链接应用突然数据库连接飙升,监控中发现有SQL在疯狂地更新,其中有一条就是更新短链接的点击数。查看了该接口功能其实非常简单:判断ip是否合法,然后短链接的点击数+1,更新到数据库表。

问题分析

接口功能虽然简单,但如果是在统计几个淘宝超级卖家的会员点击数的时候,我们如果稍不注意就容易将系统给搞垮。从上可以得出以下问题:

  • 1、短链接是直接更新到数据库,并发量过高时会增加数据库的压力,进而影响其他业务。

  • 2、接口仅仅做了ip校验,没有任何高并发和防刷限制,容易被外部攻击。

解决方案

缓存点击数异步入库

由于需求是需要实时更新点击数据,所以不能缓存太久。

1、使用mq就可以实现对流量消峰,达到异步处理的效果,但是项目中mq主要是rabbitmq,在大量堆积的情况下效果又不太好。(如果你的是rocketmq,那么当然首选是它了)

2、使用redis其实也可以实现类似的效果。

  • 2.1、只需要将点击的链接id+ip使用rPush到一个redis的list集合中。
  • 2.2、开启线程定时1min执行一次,获取当前redis的list的llen总长度。
  • 2.3、每次取出最大不超过1w条点击数据进行统计,并批量更新点击数。
  • 2.4、统计完毕后,使用redis管道循环将刚处理完毕的1w条数据弹出lpop即可。
  • 2.5、循环3、4步至到取到llen条点击数。

此处要确保第二步和第三步是在同一个事务中,否则容易出现计算重复的情况。

一条点击数据=短链接id+ip,大约25个字节,其实1个G的redis内存就可以存下4千万人点一下接口的量,具体要预估数据量加内存或者做取舍。(老板给了5个G,不够就丢弃的策略。)

或许有人会说,后面能多线程处理就好了。其实每次处理1w条,如果1min内有1个亿的点击量,其实只需要执行1w更新操作即可,整个流程只有入库耗时占大部分,1min其实1w次循环还是可以实现的,没必要开多线程带来更多并发问题(如并发更新同一行容易锁表)。

核心代码:

1、外部接收点击请求:

 @Overridepublic String visitLink(String shortUrl) {if (StringUtils.isEmpty(shortUrl)) {return null;}//此处可以将最近1天生成的短链接加入到缓存,提高响应速度。//将点击数缓存,使用异步线程批量更新。String resultStr = redisUtil.get(RedisKey.LINK_LIST_LAST + shortUrl);if (!StringUtils.isEmpty(resultStr)) {redisUtil.lRightPush(RedisKey.LINK_CLICK_COUNT, shortUrl);return resultStr;}switch (shortUrl.length()) {case 4://极短链接MinShortUrl originUrl = minShortUrlMapper.getOriginUrl(shortUrl);if (originUrl != null) {minShortUrlMapper.updateShortUrl(originUrl);}resultStr = originUrl.getUrl();break;case 6://普通短链接ShortUrl oUrl = shortUrlMapper.getOriginUrl(shortUrl);if (oUrl != null) {shortUrlMapper.updateShortUrl(oUrl);}resultStr = oUrl.getUrl();break;default:break;}if (!StringUtils.isEmpty(resultStr)) {redisUtil.setEx(RedisKey.LINK_LIST_LAST + shortUrl, resultStr, 1, TimeUnit.DAYS);}return resultStr;}

2、定时任务处理点击数入库:

/*** 统计短链接定时任务*/
@Component
public class ShortUrlSchedule {@Autowiredprivate RedisUtil redisUtil;@AutowiredShortUrlService shortUrlService;//每10分钟执行一次@Scheduled(cron = "0 0/10 * * * ? ")@Transactional(rollbackFor = Exception.class)public void calculateClickCount() {Long size = redisUtil.size(RedisKey.LINK_CLICK_COUNT);if (size != null && size > 0) {//统计短链接点击数Map<String, Integer> urlMap = new HashMap<>();Long batchSize = 10000L;do {Long pageSize = size > batchSize ? batchSize : size;List<String> tmpList = redisUtil.lRange(RedisKey.LINK_CLICK_COUNT, 0, pageSize);if (CollectionUtils.isEmpty(tmpList)) {return;}for (String shortUrl : tmpList) {//处理短链接被点击数Integer count = urlMap.get(shortUrl);if (count == null || count == 0) {count = 0;}urlMap.put(shortUrl, ++count);}//批量更新int i = shortUrlService.batchUpdateClickCount(urlMap);//弹出redisUtil.getRedisTemplate().executePipelined(new RedisCallback<String>() {@Overridepublic String doInRedis(RedisConnection redisConnection) throws DataAccessException {RedisConnection pl = redisConnection;for (int i = 0; i <= tmpList.size(); i++) {pl.lPop(RedisKey.LINK_CLICK_COUNT.getBytes());}return null;}});size = size - tmpList.size();} while (size > 0);}}
}

接口IP防刷

问题:想让某个接口某个人在某段时间内只能请求N次。

原理:在你请求的时候,服务器通过redis 记录下你请求的次数,如果次数超过限制就不给访问。 在redis 保存的key 是有时效性的,过期就会删除。

核心详细代码如下:

/*** 请求拦截*/
@Slf4j
@Component
public class RequestLimitIntercept extends HandlerInterceptorAdapter {@Autowiredprivate RedisTemplate<String,Object> redisTemplate;@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {/*** isAssignableFrom() 判定此 Class 对象所表示的类或接口与指定的 Class 参数所表示的类或接口是否相同,或是否是其超类或超接口* isAssignableFrom()方法是判断是否为某个类的父类* instanceof关键字是判断是否某个类的子类*/if(handler.getClass().isAssignableFrom(HandlerMethod.class)){//HandlerMethod 封装方法定义相关的信息,如类,方法,参数等HandlerMethod handlerMethod = (HandlerMethod) handler;Method method = handlerMethod.getMethod();// 获取方法中是否包含注解RequestLimit methodAnnotation = method.getAnnotation(RequestLimit.class);//获取 类中是否包含注解,也就是controller 是否有注解RequestLimit classAnnotation = method.getDeclaringClass().getAnnotation(RequestLimit.class);// 如果 方法上有注解就优先选择方法上的参数,否则类上的参数RequestLimit requestLimit = methodAnnotation != null?methodAnnotation:classAnnotation;if(requestLimit != null){if(isLimit(request,requestLimit)){resonseOut(response,Result.error(ApiResultEnum.REQUST_LIMIT));return false;}}}return super.preHandle(request, response, handler);}//判断请求是否受限public boolean isLimit(HttpServletRequest request,RequestLimit requestLimit){// 受限的redis 缓存key ,因为这里用浏览器做测试,我就用sessionid 来做唯一key,如果是app ,可以使用 用户ID 之类的唯一标识。String limitKey = request.getServletPath()+request.getSession().getId();// 从缓存中获取,当前这个请求访问了几次Integer redisCount = (Integer) redisTemplate.opsForValue().get(limitKey);if(redisCount == null){//初始 次数redisTemplate.opsForValue().set(limitKey,1,requestLimit.second(), TimeUnit.SECONDS);}else{if(redisCount.intValue() >= requestLimit.maxCount()){return true;}// 次数自增redisTemplate.opsForValue().increment(limitKey);}return false;}/*** 回写给客户端* @param response* @param result* @throws IOException*/private void resonseOut(HttpServletResponse response, Result result) throws IOException {response.setCharacterEncoding("UTF-8");response.setContentType("application/json; charset=utf-8");PrintWriter out = null ;String json = JSONObject.toJSON(result).toString();out = response.getWriter();out.append(json);}
}

详情可以参考文章:
xbmchina.cn/AAAAAD

下期分享如何设计一个小型的短链接小模块设计。就像上面的参考链接这样子。
如果喜欢这篇文章的麻烦点赞一下下哈。

用Redis实现短链接点击统计相关推荐

  1. WP博客ajax,WordPress文章点击统计ajax版,兼容wp super cache缓存代码及插件

    自从陌小雨博客历经文章阅读数数次清零后,陌小雨对这个文章浏览数也不是很在意了,这也就是所谓的破罐子破摔吧,但自从陌小雨前段时间再启用 wp super cache 纯缓存代码后,陌小雨觉得还是有必要把 ...

  2. 分享三个网页访问(点击)统计脚本,展示访问来源地图分布

    分享三个网页访问/点击统计脚本,展示访问来源地图分布 1. Flag Counter 2. Revolvermaps 3. 蝴蝶计数器 总结 首先简单展示一下三个脚本的效果; 左边的是FlagCoun ...

  3. H5静态页面跳转微信小程序;从外部浏览器,点击H5链接跳转打开微信小程序;以及在微信内直接点击H5链接打开微信小程序;

    参考链接 需求:从外部浏览器,点击H5链接跳转打开微信小程序:以及在微信内直接点击H5链接打开微信小程序: 步骤1: 小程序开发需要使用云开发创建项目,使用云开发生成的项目会自带云函数文件夹: 步骤2 ...

  4. SpringBoot-心跳机制+redis实现网站实时在线人数统计

    在社交网站中,通常需要实时统计某个网站的在线人数,通过该指标来实时帮助运营人员更好的维护网站业务: 先说一下目前在市面上主流的做法再加上我自己查阅的资料总结: 创建一个session监听器,在用户登录 ...

  5. 【转自聊聊架构公众号】 Redis大key图形化统计及展示

    原标题:不管你的Redis集群规模有多大,都是时候思考下如何提升资源利用率了 内容来自"聊聊架构"公众号. 董明鑫,雪球 SRE 工程师,主要负责保障雪球稳定性.提升资源利用率及提 ...

  6. redis实现用户签到,统计活跃用户,用户在线状态,用户留存率

    开发的过程中,可能会遇到用户签到.统计当天的活跃用户.以及每个用户的在线状态,用户留存率的开发需求,可能会用传统的方法,根据相应的需求设计数据库表等,但这样耗费的存储空间大,以及性能方面也不会太好,下 ...

  7. redis 实现搜索热词统计

    核心需求 一个项目中,遇到了搜索热词统计的需求,我使用了 Redis 的五大数据类型之一 Sorted Set 实现.目前有两项数据需要统计:"当日搜索热词 top10"和&quo ...

  8. Flash制作网页广告的并添加点击统计事件

    平时我们看到的一些网上的一些小广告,点击之后就进去了,这个广告条很多度都是Flash完成的,今天我们就整理一下制作的过程. (1)首先打开我们的Flash软件. 新建一个AS的文件,这里已2.0为例. ...

  9. exchange怎么改签名_如何在Microsoft Exchange上分析电子邮件签名和免责声明中的链接点击

    exchange怎么改签名 If you're an Exchange admin, the benefits of tracking link clicks in emails may not be ...

最新文章

  1. python简单代码hello-python教程——【1 hello, python】
  2. Android Studio相关资料链接
  3. java高并发(十一)同步容器
  4. Lua日期与时间操作
  5. arm 架构_ARM发布A78增强版大核架构:性能怪兽
  6. 十六个 HTML,CSS,jQuery,WordPress等快速启动项目样板
  7. (转)Geoserver基础配图研究
  8. 一加8T真机渲染图泄露:后置四摄+双闪光灯
  9. python改变列的数据类型_PySpark SQL: 改变列的数据类型
  10. 测量两台机器的的网络延迟和时间差
  11. MATLAB gui 对表格增添(删除)数据
  12. opus在arm的嵌入式平台上的移植和开发
  13. k8s(Kubernetes) 上部署 Redis 集群(3主3从)
  14. ListView优化问题
  15. 遥感影像数据解译基本步骤
  16. 装系统弹出计算机丢失,重装系统时提示缺少硬盘驱动怎么解决
  17. 雷曼 疯狂兔子 java_《雷曼:疯狂兔子》详细流程攻略+个人心得分享
  18. 2022年最新广播电视广告报价(共23份)
  19. ssl证书过期怎么解决?
  20. 怎样才能在网上快速赚到钱?

热门文章

  1. 广东省计算机一级网络题分值,计算机一级各题分值 [Office操作题自动评分的分析与实现]...
  2. CSS字体属性与文本属性详解
  3. 带指针的【七彩时钟】(C语言)
  4. 解决程序员加班难题:项目研发管理项目5大关键
  5. [RK3288][Android6.0] WiFi之开机自动连接过程
  6. Android定位基础
  7. C语言基于链表的学生管理系统,超详细
  8. HP RISC平台9i升级到HP Itanium平台上10g
  9. 标准十进制ASCII码表
  10. mac中的Windows虚拟机字体太小解决办法