京东到家三周年活动已然结束,在这2年里,我们的网关系统经历过了618,1020,双11,双12,415等多个非常有意义的考试,回顾起来依旧让人觉得很刺激,每次考前我们和市场部都做了大量的效果预估、压测&扩容,但是活动当日依旧是惊心动魄,瞬时数以100倍的流量涌入(有成千上万薅羊毛党的入侵,有技术黑客的搅局,有友商的友情压力测试,有通过全站push带来的用户同一时间瞬时访问),网关作为后台服务器的第一道墙,如何站好这第一班岗呢?接下来带你一起认识一下“京东到家-网关系统”
京东到家提供给App端的HTTP接口有上千个,涉及到的系统也有上百个,大家都知道公网HTTP接口面临着诸多的挑战,我举几个例子:
上述功能如果每个系统都各自实现一套来说,时间人力成本都是不值当的,实现方式的差异性也会造成联调时出现的种种奇葩问题,基于这样的前提,京东到家孵化出了这个基础平台-网关系统应运而生,所有系统均需要做的基础工作交由网关来处理,业务系统只关心自身业务逻辑就可以了。
这是一个比较有争议的话题,不过业界有一个共识,当系统达到一定量级时有一个基础网关统一处理异常处理、数据安全、流量控制、版本控制会比较好,后端的业务系统只用关心自身业务逻辑就好了。网关其实就是业务系统的一个前置层,把一些业务系统需要处理的通用逻辑前置到网关,避免业务系统的重复开发,提高开发效率。
京东到家的物理网关独立部署,其最核心功能是请求的转发,每一个请求进入到网关后都会经历上面的处理流程。首先进行必要的参数校验,校验通过后,进行封版和参数防篡改验证,上述校验通过后,网关会通过传过来的参数去配置中心获取到后端的跳转地址,如果能取到对应的转发url,下一步网关做转发前的最后校验,包括登陆验证,封流量校验,防刷校验,全部通过后,网关对进入的请求按照请求方式做POST或GET转发。
访问示例
http://gw.o2o.jd.com/client?functionId=productsearch/searchKey&body={%22key%22:%22asdf%22,%22pageSize%22:20}&appName=paidaojia&appVersion=1.3.0&channel=AppStore&deviceId=79516EBF-4DE6-4478-9521-F06B405A6F6E&deviceModel=iPhone&deviceToken=79516EBF-4DE6-4478-9521-F06B405A6F6E&networkType=wifi&partner=AppStore&platCode=IOS&platVersion=8.4&screen=1242*2208&signKey=7e995f08f403fa84c3e0b6c7c6e13631
必传参数解释说明
body={},app请求后端系统的业务参数,网关不解析,不更改。
deviceId=,设备id,标示设备的唯一标识。
functionId=为方法ID,网关会通过配置系统配置这个functionID指向到后端set化的集群域名
signKey=7e995f08f403fa84c3e0b6c7c6e13631,是防篡改签名,网关会对其做校验,每个app对应的Md5key值是不一样的。
appName=pdj,有了appName参数,这样一套网关就可以支持多个app了
appVersion=4.2,必传,代表应用的版本号。
deviceToken=**,必传,作为发送消息的唯一标识。
platCode=ios,标示是ios还是安卓 。
channel=AppStore
deviceModel=iphone
networkType=wifi,代表网络类型。
partner=AppStore,推广渠道。
screen=尺寸。
值得一提的设计细节
1) 错误码定义
为什么特别强调一下这个问题呢,现在都是SOA架构,系统与系统间的关系愈来愈复杂,用户看到的一个页面可能由后端10个或者更多的系统支撑着,任何一个系统出现问题,都会影响到用户页面的展示,那么如何保障出现问题时,快速定位是哪个系统哪个接口的问题呢,错误码格式:系统代号+接口代号+错误情况代号
错误码示例
APP提示语(用户看到的) 真实的错误原因
网络繁忙,请稍后再试[000000] httpstatus非302,404,500,502
网络繁忙,请稍后再试[000011] 请求后端服务超5s read time out
网络繁忙,请稍后再试[000044] 请求后端返回httpstatus:404
网络繁忙,请稍后再试[000050] 请求后端返回httpstatus:500
网络繁忙,请稍后再试[000052] 请求后端返回httpstatus:502
网络繁忙,请稍后再试[000090] 加密验证未通过
网络繁忙,请稍后再试[000091] 请求方式不对
网络繁忙,请稍后再试[000092] 未配置functionId
网络繁忙,请稍后再试[000093] functionId当前版本没有配置url
2) 网关与配置中心的交互
网关functionId与后端系统url的对应关系全部存储在配置中心(也是一个应用,独立部署),为了最大限度提高配置中心的可用性和访问速度问题,配置中心的配置信息在网关机器启动时就会全量加载到网关jvm内存中,之后如果配置中心的配置新增或者修改,我们采用zookeeper的通知机制来更新jvm内存中的配置信息。
3) 日志查询
日志对于一个系统来说,重要性不容忽视,线上的突发问题定位、日常的问题排查、责任定位都需要用到,但作为日PV过亿的系统,很多系统都会因性能考虑不提供日志,其实我不是太赞成这一思路的,首先性能的问题应该是想办法解决性能,不能因为采集了日志,结果影响了性能,基于这样的大前提,我们的日志采用了Log4j2(官方号称比Log4j性能提升了10倍),配合动态开关来控制打印日志的级别,可以在一些特殊情况下控制日志的输出。
另外一个知识点就是现在我们的应用服务器通常有几个或者几十个,日志的集中化管理与关键字检索查询也显得非常麻烦,目前京东到家使用了业界比较成熟的ELK技术(ELK由ElasticSearch、Logstash和Kiabana三个开源工具组成)
4) 高可用保证
京东到家网关系统的日常现状(其中X>1,Y>1)
PV:X亿/日
峰值:Y十万/分钟
作为一个亿级PV的系统,每秒钟都会有上千次的访问,保证系统的高可用呢?比如说可能会遇到网络、硬件、软件的故障,或者程序自身的升级,如何最小化的降低对用户的感知呢
网络故障,京东到家所有公网域名都是分运营商(移动、联通、电信、香港)进行设置DNS的,任何一家运营商的网络出现问题都可以快速切换到其它运营商
硬件故障,首先我们服务器通过前置HAProxy+Nginx双层架构,网关服务器水平扩容便无后顾之忧,另外采用同机房多服务器+扩机房混合部署,防止服务器或者机房故障
程序升级,程序升级有多方面的原因,比如新功能上线或者修复bug,我们如何避免由于程序升级过程中造成的访问波动呢,这个也是SoEasy的,前面提到HAProxy,这里会维护一个VIP与后端服务器的映射关系,每次上线前可以通过VIP控制台进行摘掉一部分机器待无流量后再进行上线升级操作,待这部分正常启动后重新再挂到VIP上,并对外提供服务,依次完成剩余的一部分机器即可,当然了,由于需要修改系统参数、程序参数造成的程序重启也可以肿采取该策略。
截流
限流
网关系统,我们正在做?
持续优化,优化每个请求在网关的耗时,在网关转发请求到后端时,需要做比较多的校验,且需要和外部的会话中心和防刷系统等交互,后续优化尽量降低对外部系统的强依赖,能异步化的异步化。也可适当优化tomcat参数(IO->NIO->AIO),提高响应速度。
风控对接,单纯从技术手段有些攻击手段是无法彻底解决的,顶多是可以增加攻击的成本的复杂度,比如暴力破解、社会工程学等,我们目前逐步与风控系统数据对接,风控系统有一套非常强大的用户识别体系,集合了用户,设备,浏览,订单,优惠,支付等环节的数据,对风险用户进行星级打分,最终实现网关与风控的数据共享利用。
优化日志,方便问题查找,统一日志的输出格式,增加类似traceId的字段,通过这一字段可以看到每一个请求在网关的完整的调用日志链,方便问题定位与查找。
完善监控,增加依赖的核心系统的监控,及网关机器性能的监控,防止网络或者机器的原因导致网关可用率降低
截流限流的思路
2. 多个账号,一次性发送多个请求
很多公司的账号注册功能,在发展早期几乎是没有限制的,很容易就可以注册很多个账号。因此,也导致了出现了一些特殊的工作室,通过编写自动注册脚本,积累了一大批“僵尸账号”,数量庞大,几万甚至几十万的账号不等,专门做各种刷的行为(这就是微博中的“僵尸粉“的来源)。举个例子,例如微博中有转发抽奖的活动,如果我们使用几万个“僵尸号”去混进去转发,这样就可以大大提升我们中奖的概率。
这种账号,使用在秒杀和抢购里,也是同一个道理。例如,iPhone官网的抢购,火车票黄牛党。
应对方案:
这种场景,可以通过检测指定机器IP请求频率就可以解决,如果发现某个IP请求频率很高,可以给它弹出一个验证码或者直接禁止它的请求:
1. 弹出验证码,最核心的追求,就是分辨出真实用户。因此,大家可能经常发现,网站弹出的验证码,有些是“鬼神乱舞”的样子,有时让我们根本无法看清。他们这样做的原因,其实也是为了让验证码的图片不被轻易识别,因为强大的“自动脚本”可以通过图片识别里面的字符,然后让脚本自动填写验证码。实际上,有一些非常创新的验证码,效果会比较好,例如给你一个简单问题让你回答,或者让你完成某些简单操作(例如百度贴吧的验证码)。
2. 直接禁止IP,实际上是有些粗暴的,因为有些真实用户的网络场景恰好是同一出口IP的,可能会有“误伤“。但是这一个做法简单高效,根据实际场景使用可以获得很好的效果。
3. 多个账号,不同IP发送不同请求
所谓道高一尺,魔高一丈。有进攻,就会有防守,永不休止。这些“工作室”,发现你对单机IP请求频率有控制之后,他们也针对这种场景,想出了他们的“新进攻方案”,就是不断改变IP。
有同学会好奇,这些随机IP服务怎么来的。有一些是某些机构自己占据一批独立IP,然后做成一个随机代理IP的服务,有偿提供给这些“工作室”使用。还有一些更为黑暗一点的,就是通过木马黑掉普通用户的电脑,这个木马也不破坏用户电脑的正常运作,只做一件事情,就是转发IP包,普通用户的电脑被变成了IP代理出口。通过这种做法,黑客就拿到了大量的独立IP,然后搭建为随机IP服务,就是为了挣钱。
应对方案:
说实话,这种场景下的请求,和真实用户的行为,已经基本相同了,想做分辨很困难。再做进一步的限制很容易“误伤“真实用户,这个时候,通常只能通过设置业务门槛高来限制这种请求了,或者通过账号行为的”数据挖掘“来提前清理掉它们。
僵尸账号也还是有一些共同特征的,例如账号很可能属于同一个号码段甚至是连号的,活跃度不高,等级低,资料不全等等。根据这些特点,适当设置参与门槛,例如限制参与秒杀的账号等级。通过这些业务手段,也是可以过滤掉一些僵尸号。
心灵寄语:唯有认真对待每一次考试,方能临阵不乱