03百万架构师:微服务网关案例剖析

一、业内常用的系统架构图:

1.未做水平拆分的集中式架构:

在这里插入图片描述

2.做了水平拆分的架构:

在这里插入图片描述

二、网关的作用:

  • 功能一:请求鉴权

登录鉴权,session无状态化

  • 功能二:数据包完整性验证
    在这里插入图片描述
    定长的业务头:userid、cmd、session、bodylength

  • 功能三:协议转换

json转二进制HashMap(String,Object)

  • 功能四:cmd路由

根据cmd路由到不同的业务逻辑层

  • 功能五:系统级别的限流、降级、熔断

数据包完成性检查与协议转换没什么好说的,下面主要讲解请求鉴权与路由转换企业级自研实现

三、微服务网关企业级自研实现:

网关是企业自研中间件最容易落地的一个。
在这里插入图片描述

  • 高性能分为高吞吐量与低平均响应延迟,高吞吐量可以通过服务扩容实现,实现服务快速扩容,弹性缩容的前提就是服务无状态化,而加机器扩容如果使得队列无堆积也可能降低平均响应延迟(可能)。

  • 请求处理的第一步,跨域(baidu.com访问api.andawell.com,api.andawell.com让不让baidu.com访问),反作弊(黑名单userid,ip,恶意请求:如正常一秒请求一次,如果请求100次恶意用户),session鉴权,过滤器肯定不是一个,应该是过滤器责任链(或者叫拦截器责任链)

  • 根据cmd路由到下游的业务逻辑层(请求参数的处理,拿到参数cmd以后,根据路由方案路由,如路由到计划服务,计划服务返回结果以后,对结果有个包装),(问题:下游的服务增减,需要重启网关服务么?如果以jar作为组件的方式引入网关,需要重启,不优雅)

  • 风控,反作弊设计

在这里插入图片描述在这里插入图片描述

四、跨域:

跨域处理方案:传送门.
同源策略会阻止一个域的JS脚本和另外一个域的内容进行交互。所谓同源(即指在同一个域)就是两个页面具有相同的协议(protocol,如http/https),主机(host)和端口号(port)。从一个源(www.alibaba.com)默认不能访问另一个目标(如:andawell.com)的资源
在这里插入图片描述

  • Access-Control-Allow-Credentials: true
    响应头表示是否可以将对请求的响应暴露给页面。返回true则可以,其他值均不可以

  • Access-Control-Allow-Origin: | * :
    其中,origin参数的值指定了允许访问该资源的外域URI。对于不需要携带身份凭证的请求,服务器可以指定该字段的值为通配符,表示允许来自所有域的请求。如:Access-Control-Allow-Origin:https://www.alibaba.com

  • Access-Control-Allow-Methods:
    首部字段用于预检请求的响应。其指明了实际请求所允许使用的 HTTP方法。如:get\post\options

  • Access-Control-Max-Age:
    头指定了preflight请求的结果能够被缓存多久,如:Access-Control-Max-Age:
    600将预检请求的结果缓存10分钟,如果值为 -1,表示禁用缓存,则每次请求前都需要使用 OPTIONS 预检请求
    在这里插入图片描述

探测options:
在这里插入图片描述

五、风控-反作弊:

  1. 拦截恶意流量,防止对后台产生高并发压力。如,正常以后一秒访问一次,如果一秒100次则视为恶意
  2. 防止误伤,对于黑名单,定期释放。

网关层面的反作弊:

  • 业务维度:爬虫、恶意攻击;
  • 技术解决:黑名单,包括ip、deviceId、uid
  • 特点:读多写少,命中率低
    在这里插入图片描述
    策略一:离线挖掘,根据用户的行为,挖掘黑名单,写入redis,然后定时(如60秒)将redis的数据同步到内存中,
    在这里插入图片描述
    问题来了,如果黑名单数量巨大,进程内的缓存无法存下,这个时候怎么办?
    直接从redis中拿?如果请求量大,如100万都去访问redis,redis扛不住的,这个时候又该怎么办?加布隆过滤器?
    布隆过滤器是一个bit array,初始都是0,黑名单hash位置为1,为了避免hash冲突,防止误杀,可以多次hash(不同的hash算法),当新的请求过来时,进行hash,多次hash结果均为1时,则为黑名单,拦截,由黑名单hash的bit array需要定时更新。

六、session设计:

Session与登录用户数一一对应,但是允许多点登录,如手机,pc,ipad三端,三个session;
单机下存储session,无法做到高并发,高可用,因为单机,吞吐量有限,不高并发,且如果单机挂了,session丢失,不高可用,影响很大。
分布式方案:

  • Session绑定:不可靠,uid hash,session分治,取模在不同的网关,有状态化,不宜扩展。
  • Session复制(也算无状态化):
    cp影响性能,
    ap可能有问题,如果客户下一个请求来了落在的网关还没有将用户的session同步过来,会有问题,如果机器多了100台之间复制,将形成网络风暴,影响性能。
    另外,如果session量很大,如一个亿,单机存不下那么多。
  • Session共享(无状态化):
    将session,单独拿出来,放入redis中,缺点:当session数量极大,如1亿,这是如果定时异步加载到线程内存,可能线程内存存不下,如果直接读取redis,请求量大时,如100万都去访问redis,redis扛不住的。
  • Session客户端存储:推荐,如jwt

七、session生成算法:

Session是一串具有一定时效性的加密字符串,通常由服务端生成和解析。
Session包含的信息字段和各个公司的业务请相关:deviceId(设备id)、clientType(1pc,2app,3ipad)、uid、ts(ttl时间戳)。
AES(aes一般指高级加密标准)加密方案:加解密过程都在服务端。
网关高性能:本地算法+远程校验(续租)。快到期时,进行session更新进行续租。就像分布式锁一样,有续租功能

在这里插入图片描述

八、路由:

在这里插入图片描述在这里插入图片描述

方式一V1.0:

类似于服务间的调用:网关引入各service的jar包,网关启动时,进行扫描得到cmd与服务、方法路径的映射关系。对uri进行解析,拿到cmd,通过映射关系路由到正确的服务。但是,由于网关中放入了每个服务的api jar包,每当服务变动,网关需要重启,不优雅。
在这里插入图片描述在这里插入图片描述

在这里插入图片描述在这里插入图片描述

在这里插入图片描述
反射影响性能,我们可以做缓存。
在这里插入图片描述

V1.0带来的问题:

  • 启动消耗大:启动时需要扫描所有的客户端jar包。
  • 业务升级需要重启,耦合性大,降低开发效率。

方式二V2.0:

  • 针对启动消耗大:启动时需要扫描所有的客户端jar包。(不依赖服务端的client包,网关只做服务的扫描(去数据库读),方法的映射交给数据逻辑层)
  • 针对业务升级需要重启,耦合性大,降低开发效率。(只有个代理方法,所有服务一个client包,访问他将uri与参数一起传过去做处理)
    在这里插入图片描述

方式三V3.0:泛化调用

  • 通常我们想调用别人的服务时,我们需要在项目中引入对应的jar包。而泛化调用的作用是,我们无需依赖相关jar包,也能调用到该服务。之所以在服务消费者有一个没有任何具体实现的接口,是因为在设计RPC之初,设计者的最高理念就是你去面向接口编程,你在进行远程调用的时候,并没有意识到你在进行远程调用,却也能拿到接口一样,相信你也感觉到了,服务消费者在调用服务的时候,与调用一个普通的接口是一样的。
  • 泛化调用就是服务消费者端因为某种原因并没有该服务接口,这个原因有很多,比如是跨语言的,一个PHP工程师想调用某个java接口,他并不能按照你约定,去写一个个的接口,Dubbo并不是跨语言的RPC框架,但并不是不能解决这个问题,这个PHP程序员搭建了一个简单的java web项目,引入了dubbo的jar包,使用dubbo的泛化调用,然后利用web返回json,这样也能完成跨语言的调用。泛化调用的好处之一就是这个了。另外如果客户端jar包进行变动,所有客户端均需要重启,很不优雅。

以dubbo为例实现泛化调用:
实现一个通用的远程服务 Mock 框架,可通过实现 GenericService 接口处理所有服务请求

泛接口实现方式主要用于服务器端没有 API 接口及模型类元的情况,参数及返回值中的所有 POJO 均用 Map 表示,通常用于框架集成,比如:实现一个通用的远程服务 Mock 框架,可通过实现 GenericService 接口处理所有服务请求。

在 Java 代码中实现 GenericService 接口:

package com.foo;
public class MyGenericService implements GenericService {
 
    public Object $invoke(String methodName, String[] parameterTypes, Object[] args) throws GenericException {
        if ("sayHello".equals(methodName)) {
            return "Welcome " + args[0];
        }
    }
}

通过 Spring 暴露泛化实现
在 Spring 配置申明服务的实现:

<bean id="genericService" class="com.foo.MyGenericService" />
<dubbo:service interface="com.foo.BarService" ref="genericService" />

通过 API 方式暴露泛化实现

... 
// 用org.apache.dubbo.rpc.service.GenericService可以替代所有接口实现 
GenericService xxxService = new XxxGenericService(); 

// 该实例很重量,里面封装了所有与注册中心及服务提供方连接,请缓存 
ServiceConfig<GenericService> service = new ServiceConfig<GenericService>();
// 弱类型接口名 
service.setInterface("com.xxx.XxxService");  
service.setVersion("1.0.0"); 
// 指向一个通用服务实现 
service.setRef(xxxService); 
 
// 暴露及注册服务 
service.export();

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值