一、微服务网关层的整体架构思考
先回顾下网关层的功能:
1.请求鉴权
发布商品,登录鉴权。
2. 数据完整性检查
数据定长Header,变长body
3. 协议转换
JSON-> HashMap(string,obj)
如果value不支持json嵌套的话,就string就可以。hashmap(string,string)
4. 路由转发
根据CMD转发到不同业务逻辑层
5. 服务治理
限流,降级,熔断等。
其中最核心的是1和4.
1涉及到session存到哪,
4涉及到如何把众多的HTTP请求路由到逻辑层的RPC接口。新上一个业务(逻辑层),网关不需要的重启的情况下如何发现它。
二、自研网关的各个关键点
2.1 自研网关需求剖析
打造一个高性能的分布式网关(SaaS),实现HTTP请求转发到RPC服务、接口请求鉴权、反作弊(风控、antispam)等相关功能。
要实现如下功能:
- 高性能分布式模块(这里模块指的Process)
- 鉴权功能
- 路由能力
- 简单实用的反作弊
落实到技术层面,解决方案如下:
- 无状态设计
- 过滤器责任链设计(拦截器)
- 反作弊设计(拦截器)
可以理解为责任链的一部分 - 路由方案设计
架构参考
整体架构基于Spring boot框架。
网关本质上是一个websever。
注意上面的Filter责任链模式。 跨域问题
比如,访问www.baidu.com,调用的js中有www.iqiyi.com。那么到了iqiyi的网关层,它发现主域名是来自于其他网站,就会考虑让不让它访问iqiyi,这就是一个跨域问题。网关属于高并发模块
所以要逻辑简单,业务逻辑剥离到logic层缓存设计、异步线程设计
反作弊模块,希望有一个黑名单缓存,比如map或set。(因为和外部交互影响性能)。黑名单应该是由数据分析得到的,比如放到redis里。那么我如何在进程内缓存呢?
这时就要一个异步加载机制,比如每隔5s从redis里读出来覆盖set。(最终一致性),即进程内和进程外通讯。进程外缓存,配置平台
进程外缓存就指的redis集群
配置平台是路由逻辑用的,后面详解。
上面的图不对,需要换一下
2.2 跨域问题
从一个源(www.baidu.com)加载的文档或脚本(js),不能访问另一个源(www.iqiyi.com)的资源。
Access-Control-Allow-Origin
栗子:
比如baidu的网页想嵌入iqiyi的一个url资源,那么它会先去iqiyi的网关层探测我能不能访问你的资源,如果iqiyi允许的话,会在网关层建立一个豁免清单(允许哪些第三方的域名访问)。跨cookie Access-control-allow-credentials
cokie是种在url里的,比如种在api1.baidu.com里的,那么api2.baidu.com就不行了。如果设置为允许跨cokie,那么cokie在任何一个二级域名里都可以(*.baidu.com).允许哪些方法进行跨域访问
2.3 Session设计
2.3.1 session绑定
本质就是将uid hash到固定的节点。
问题是高可用怎么保证呢? session一般是没必要持久化的。
思路是网关冗余,模仿数据库主从复制。
但是,网关间的数据复制如何设计呢?(要考虑vip、主从复制等,简单问题复制化了)。所以session绑定在工业界很少用。
所以一般用session绑定
2.3.2 session 复制
而且是最终一致性的,存在session重写。这种方案也是不合理的。
2.3.3 session共享
这里的redis是redis cluster。
使用缓存服务Redis,统一存储Session。
优点
- 网关层无状态
- 缓存服务本身高可用
缺点
- 多一次服务调用IO
思考如果减少IO调用,且网关是无状态化的。很自然的想到session存在客户端。
2.3.4 Session 客户端缓存
可以存在app的sqlite里
优点:
- 简单高性能
- 网关层无状态
缺点: - 依赖客户端Cookie(session)存储
每次带cookie,消耗一些app端的流量。
这个用的比较多,因为响应延迟低,且高可用。
2.3.5 session 生成算法
session=AES(uid + TTL + checksum)
用户第一次登录时,网关生成session,然后返回给客户端,之后每次登录时带着这个session。
本地算法,AES解密看TTL是否过期,checksum是否正确。
远程校验,是因为session在很多公司是在业务逻辑层生成的(因为更多的是业务逻辑的判断),而不在网关层生成。TTL过期后要到业务逻辑层去校验,业务逻辑层返回给GW一个新的session,GW返回给APP。这个可能很少概率。
2.3.6 session 拦截器
拦截器中会生成logid,logid是app请求的唯一标识。
以上主要两部分功能,1 是session校验,2是生成logid。
2.4 网关层反作弊需求
- 针对恶意流量,从网关层面进行拦截,防止对后端服务造成高并发压力
-
为了防止误伤,对于黑名单数据定期释放能力
image.png
分析:
初期阶段
数据量小的时候,可以内存中local cache。高可用设计
进程内缓存解决不了高可用黑名单
持久化怎么做
上面是数据量比较小的时候,那么数据量大的时候怎么做,几十G的话进程内缓存不了。
风控挖掘的时候可以存在redis里,但是为了这种小概率的事读redis会导致耗时增加。
所以可以用布隆过滤器。
思考
- 数据量大怎么做(布隆过滤器)
如果每次读redis,为了这小概率事件开销太大
但是布隆过滤器具体怎么做?后面详解 - 实时性怎么破?
如果5s拉一次可能不够实时,如果实时性要求高,可以改成redis有更新然后push的方式。
2.5 自研网关各个击破:之路由
RPC客户端比如Dubbo,用来和网关后面的业务逻辑层的RPC services通信。
架构设计原则
初期阶段约定大于配置:
url什么规范,接口的约定。服务端简洁化设计
- 功能够简单
- 使用用够方便
负载均衡和服务发现
- RPC框架实现
熔断设计
Hystrix
协议约定
- 网关和APP间的数据协议是JSON
- 网关层调用业务逻辑层入参统一为Map
- 业务逻辑层返回统一的Result对象{code,data,msg}
code:0, data:xxx, msg: success
网关层拿到后再转化成json,然后返回给APP。
2.5.1 路由设计
上图其中Sevrice比如分别对应用户、商品、交易的logic。
其中RPC框架比如是Dubbo的RPC客户端,调用Dubbo的RPC服务端(即后端的logic)。
uri->Service(比如User logic),Method对应User logic中的get user方法。
这块主要做两件事:
- 建立url和服务间的映射关系(服务启动初始化,内存里建立映射)
- RPC通过反射实现远程调用。(反射生成对应的对象供调用)
25.2 具体实现:
上面讲的其实网关1.0的路由实现,有如下几个问题:
启动耗时大
比如有几百个业务逻辑,都去网关建立路由映射关系,那么服务启动初始化的时候就会非常耗时。业务升级需要重启,耦合严重,降低开发效率。
比如新加个服务,需要重新到网关层注册(建立路由映射),那么网关就需要重启进程。
那么2.0就需要解决这两个问题。
2.6 网关2.0
- 路由到通用接口。接口再去路由到具体的服务。
这样就不需要引入service,不需要重启。基于接口编程。
proxy每增加一个服务的时候,需要上报CLASS和Method给存储层(Mysql),网关层每隔5s扫描存储层加载到内存里。这样网关层根据CMD对应的Class和Method拿到Service IP,然后调用固定的接口。
思考 网关3.0的演进
- 进一步解耦如何做?
一定需要mysql作为存储层吗? 可否用注册中心或配置中心来做? - 泛化调用的逻辑?