首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >程序员过关斩将--重复的请求并不好过滤

程序员过关斩将--重复的请求并不好过滤

作者头像
架构师修行之路
发布2021-04-01 00:23:02
6140
发布2021-04-01 00:23:02
举报
文章被收录于专栏:架构师架构师

为什么要做重复请求的过滤呢?不过滤不行吗?

过滤重复请求很难吗?加一个请求ID不就好了吗?

每个技术难点的话题,肯定是由一个产品需求引发的,俗话说:如果没有产品经理,程序员将不需要听诊器,但是会失业!!

产生背景

重复请求能够对系统造成伤害是架构中很难避免的一个设计问题,一般情况下,读请求很少会造成致命性的故障,主要是系统的写请求,很多时候一个重复写的动作,会是我们程序员加班的缘由。比如:用户使用积分兑换物品,重复的请求会造成用户积分的重复扣减,而作为线上系统,如果日志等辅助打的不好的话,排查原因其实需要很多时间。

一般的产品经理设计系统的时候并不会涉及到这类异常情况,但是一旦出现问题,产品经理就会找到程序员骂娘,多么悲哀的故事,人家付出5分精力设计的系统,我们却要花费10分的精力去编码和维护。

重复的业务请求,有的时候对系统造成的影响很大,所以程序员在设计的时候尤其要注意,产生的原因有很多:

  • 黑客进行了拦截,人为的重放了请求
  • 客户端因为某些原因,用户在很短的时间内重放了请求
  • 一些中间件(比如网关)重放了请求
  • 未知的其他情况

道理很简单,用一张图表达的会更清爽一些

image

抽象出来是不是很简单?但是落地却并非像这张图一样简单!!

从这张图上一眼就可以看到,整个过程的重点难点在于过滤器这个逻辑设计部分,这部分可以和业务代码融合在一起,有的时候也可以相分离,比如:有的网关可以内嵌脚本(比如:lua),就完全可以做到和业务无关,但是通常情况下,落地的代码却和业务息息相关。

客户端处理

客户端处理重复请求是一种可以有效过滤正常请求的手段,为什么这么说呢?当一个用户正常操作的时候,客户端完全可以利用loading的方式或者其他过滤重复手段来达到目的,比如:当用户点击一个按钮的时候,弹出loading窗口方式用户再次操作。

再比如:客户端可以设置一个类似于布隆过滤的数据结构,配合对应的过滤算法也可以达到过滤重复请求的效果。

不过,客户端的任何解决方案也只是治标不治本,毕竟,客户端在整个系统架构中,是最不可靠的终端。

请求标识

重复请求过滤的关键在于过滤器的逻辑设计,目前最常用,落地最多当属使用请求ID的方式。大体流程如下:

  1. 客户端发送请求的时候,会生成随机的请求ID,随着业务参数一起传送到服务端
  2. 服务端会根据传送上来的请求ID做是否重复的判断

服务器的判断逻辑其实有很多落地方案了,比如最常见的利用redis来存储请求ID,以下是伪代码(NetCore):

public class Para
{
    public string ReqId{get ;set ;}  

    //其他业务参数
}

public bool IsExsit(Para p)
{
    //利用redis来判断当前的key是否存在
     bool isExsit=redisMethond(p.ReqId);
     //如果存在,则说明是重复请求,如果不存在说明不是重复请求,并且添加到redis
     if(!isExsit){
         AddRedis(p.ReqId);
     }
     return isExsit;

}

一般网上的文章都到此为止了,这种方案有没有问题呢?答案:有

问题1

正常的客户端重复请求,一般情况下真的会根据我们写的代码过滤掉重复请求,为什么说一般情况呢?那是因为分布式的原因,极限情况下也会导致重复的请求到业务处理端,比如以下情况:

  1. 请求被路由到了A服务器,A服务器会去请求Redis,判断是否有相同的请求ID存在,如果是第一次请求,Redis会返回不存在
  2. 同样的时间,客户端或者黑客重放了同样的请求,这个请求被路由到了B服务器,B服务器同样会请求Redis来判断是否存在,这个时候由于A服务器还没回写Redis,所以B服务器得到的结果也是不存在该请求
  3. 这样就导致了业务端收到了两次同样的请求,会导致业务不可预期的结果

可见,一个小小重复过滤请求,可能还需要分布式锁的出场才可以

问题2

即便请求中加了唯一的请求ID,但是这个ID并没有安全保证,或者说,这个ID是可以篡改的。当黑客拦截到请求,随便改一下请求ID,在重放就搞定你了。所以,加的请求ID,还需要一个安全机制来保证安全,不然这个参数其实意义不大。

业务签名

由于单纯添加请求ID,并不能解决问题,所以我们需要一种保证请求ID的机制,目前来看,普遍的落地方案是根据业务参数生成摘要,也就是所谓的加签操作。加签操作可以有效的防止参数被篡改。如果你做过微信相关的开发,你会发现和微信服务器的交互也是基于加签操作的。而生成的签名可以作为请求ID,以下是伪代码:

    //客户端生成签名
    string sigh=MD5($"参数1=值1&参数2=值2&time=当前时间戳")

以上只是例子,虽然MD5算法有产生重复数据的可能性,但是对于当前这个业务场景来说足够了。细心的同学会发现,参数当中加了一个时间戳的参数,这个是我故意加的,这个时间戳在这个场景下会出现问题,什么问题呢?

时间戳问题

当前的请求场景是要过滤重复的请求,什么样的请求算是重复请求呢?关键是这个定义要明确,我看了很多重复过滤请求的文章,重复请求这个概念其实定义的不好,这个是和具体业务场景相关的。举个栗子:当用户一秒内重复点击某个按钮算是重复请求,那10秒内重复点击呢?用户一秒之内对同一个商品下单算重复请求,那10秒内呢?

这个定义就涉及到了上面所说的时间戳参数的问题,时间戳是否要参与生成签名,要根据具体的业务场景来定义,不过,我还是要建议,请求的参数中带上时间戳,无论它参不参与签名,至于为什么这么做,当时间长了你就知道了

写在最后

过滤重复请求这个需求,并没有像想象中那么容易,并非只要加上一个请求ID就完事了,它涉及到安全以及分布式的问题,在某些场景下(比如:秒杀)还会涉及到性能以及高可用等非功能性问题,所以那些说:只需要一个请求ID就能过滤的同学,请不要再误导别人了,技术是神圣不可侵犯的。

还是那句话:具体的业务影响到具体的代码实现,脱离业务讲架构其实就是耍流氓

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-03-31,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 架构师修行之路 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 产生背景
    • 客户端处理
      • 请求标识
        • 业务签名
        • 写在最后
        相关产品与服务
        云数据库 Redis
        腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档