1. 简介
这里的Gateway指的是Spring Cloud Gateway。Gateway的基本作用是路由,将请求交给相应的服务进行处理,这里我们对gateway提出了更多的要求:认证、鉴权、熔断限流。
-
请求到来之后,一般都需要判断该用户是否登录,如果没有登录则禁止访问,也就是认证(Authentication)功能;
-
请求到来之后,判断该用户是否有权访问、操作资源,如果没有,则禁止访问,也就是鉴权(Authorization)功能;
-
当流量过大后,能够进行熔断、限流措施,保护系统,spring Cloud Gateway通过整合Sentinel,提供了该功能;
在之前单体的Auth中,采用了Interceptor进行拦截完成了这2大功能,在微服务中,虽然也可以使用,但interceptor维护起来相对没有在统一的服务(gateway)中简单一些。
Spring Cloud Gateway采用WebFlux框架,本身是一个SpringBoot应用,它几个核心的概念:
-
Route
网关配置的基本组成模块,一个 Route 由路由 ID,转发 URI(服务的路径),一组 Predicate 以一组 Filter 构成。如果Predicate为真,则路由匹配,则转发到该URI。
-
Predicate
表示路由的匹配条件,可以用来匹配请求的各种属性,如请求路径、方法、header 等。一个 Route 可以包含多个子 Predicates,多个子 Predicates 最终会合并成一个。
-
Filter
Filter与SpringBoot的Filter类似,包括了处理请求和响应的逻辑,可以分为 pre 和 post 两个阶段。多个 Filter 在 pre 阶段会按优先级高到低顺序执行,post 阶段则是反向执行。Gateway包括2类Filter:全局Filter与路由Filter。
过程如下:
这个过程与SpirngMVC的过程有些相似。
2. 基本用法
2.1 依赖
这里的配置相对多一些:
<!-- 网关 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- nacos 服务发现 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- nacos 配置中心-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!-- sentinel -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!-- sentinel gateway 扩展-->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-spring-cloud-gateway-adapter</artifactId>
</dependency>
<!-- sentinel的nacos数据源-->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
2.2 配置
如果是静态配置gateway,则仅配置application.yml
spring:
cloud:
gateway:
discovery:
locator:
enabled: true
lower-case-service-id: true
routes:
- id: order-server
uri: lb://order-server
predicates:
- Path=/order/**
filters:
- StripPrefix=1
这里使用的是动态配置,需要在bootstrap.yml中增加
nacos:
config:
gateway-route:
data-id: gateway-route
group: DEFAULT_GROUP
gateway-flow-rule:
data-id: gateway-flow-rule
group: DEFAULT_GROUP
主要是routes部分,通过id、uri、predicates、filters配置了一条Route。
这里使用了Path断言,Path包含/order时,路由到lb://order-server上,而这里的order-server,是需要配置中心的,故而,这里gateway也需要注册进nacos。
后边的filters是将是将路由上的/path删除掉的意思,如访问gateway时是http://localhost:7878/order/test
,而gateway转发后就是http://localhost:7878/test
。
以上是静态的路由方式,Nacos有配置中心的功能,能否通过Nacos来对路由进行配置,这样不同重启gateway就能完成对路由的修改,这也就是下边的动态路由。
详细的predicate参考官网
3. 动态路由
在前面的Nacos中,我们对Nacos动态配置做了简单的描述,这里以此来做个示例。过程如下:
- 通过nacos创建动态路由配置项(json格式)
- gateway服务中,监听该配置,并注册handler
- 当Nacos中配置发生改变时,回调用该handler,将动态路由注册进gateway
参考:动态路由配置
3.1 Nacos配置项
创建gateway-route配置项,格式选择JSON:
[
{
"id": "auth-server",
"uri": "lb://auth-server",
"predicates": [
{
"name": "Path",
"args": {
"pattern": "/auth/**"
}
}
],
"filters": [
{
"name": "StripPrefix",
"args":{
"parts": 1
}
}
]
},
{
"id": "order-server",
"uri": "lb://order-server",
"predicates": [
{
"name": "Path",
"args": {
"pattern": "/order/**"
}
}
],
"filters": [
{
"name": "StripPrefix",
"args":{
"parts": 1
}
}
]
}
]
可以看出,yml的配置与json的配置在predicates(断言)与filters方面是有差异的。
注意:这里为对比依旧使用的"Path"断言,后边Auth中,修改为了“Host”断言。
3.2 监听动态配置
这里为了抽象了DynamicConfigManager
来进行监听,抽象出DynamicConfigHandler
统一各监听的处理。
@Slf4j
@Component
public class DynamicConfigManager {
private ConfigService configService;
@Value("${spring.cloud.nacos.discovery.server-addr}")
private String serverAddr;
@Value("${spring.cloud.nacos.discovery.namespace}")
private String namespace;
@Autowired
private List<DynamicConfigHandler> dynamicConfigHandlerList;
@PostConstruct
public void init(){
createConfigService();
registerListener();
}
private boolean createConfigService() {
try{
Properties properties = new Properties();
properties.setProperty("serverAddr", serverAddr);
properties.setProperty("namespace", namespace);
configService = NacosFactory.createConfigService(properties);
} catch (Exception e) {
log.error("初始化动态配置失败",e);
return false;
}
return true;
}
private boolean registerListener(){
if(configService == null){
return false;
}
for (DynamicConfigHandler dynamicConfigHandler : dynamicConfigHandlerList) {
NacosConfigKey configKey = dynamicConfigHandler.getConfigKey();
try {
String firstConfigInfo = configService.getConfigAndSignListener(configKey.getDataId(), configKey.getGroup(), 5000, new Listener() {
@Override
public Executor getExecutor() {
return null;
}
@Override
public void receiveConfigInfo(String configInfo) {
log.info("接收dataId:{} group:{}的配置:\r\n{}", configKey.getDataId(), configKey.getGroup(), configInfo);
dynamicConfigHandler.handle(configInfo);
}
});
dynamicConfigHandler.handle(firstConfigInfo);
} catch (NacosException e) {
e.printStackTrace();
}
}
return true;
}
}
3.3 动态配置Handler
public interface DynamicConfigHandler {
/**
* 获取配置在配置中心的key:dataId、group
* @return
*/
NacosConfigKey getConfigKey();
/**
* 处理推送的配置
* @param configInfo 配置中心推送的配置,json
* @return
*/
boolean handle(String configInfo);
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class NacosConfigKey {
private String dataId;
private String group;
}
动态路由的配置如下:
@Slf4j
@Service
public class DynamicRouteConfigHandler implements ApplicationEventPublisherAware, DynamicConfigHandler {
@Value("${nacos.config.gateway-route.data-id}")
private String dataId;
@Value("${nacos.config.gateway-route.group}")
private String group;
@Autowired
private RouteDefinitionWriter routeDefinitionWriter;
@Autowired
private RouteDefinitionLocator routeDefinitionLocator;
/**
* 发布事件
*/
@Autowired
private ApplicationEventPublisher publisher;
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.publisher = applicationEventPublisher;
}
@Override
public NacosConfigKey getConfigKey() {
return new NacosConfigKey(dataId, group);
}
@Override
public boolean handle(String configInfo) {
List<RouteDefinition> definitionList = null;
try {
definitionList = JSONUtil.toList(configInfo, RouteDefinition.class);
}catch (JSONException e){
e.printStackTrace();
return false;
}
return updateRouteList(definitionList);
}
/**
* 更新路由列表
* 先删除已有的,再新增新的
* @param definitions
* @return
*/
private Boolean updateRouteList(List<RouteDefinition> definitions) {
List<RouteDefinition> routeDefinitionsExits = routeDefinitionLocator.getRouteDefinitions().buffer().blockFirst();
deleteRouteList(routeDefinitionsExits);
return addRouteList(definitions);
}
/**
* 增加路由列表
* @param definitionList
* @return
*/
private Boolean addRouteList(List<RouteDefinition> definitionList){
if(definitionList==null || definitionList.isEmpty()){
return false;
}
for(RouteDefinition definition : definitionList){
routeDefinitionWriter.save(Mono.just(definition)).subscribe();
}
this.publisher.publishEvent(new RefreshRoutesEvent(this));
return true;
}
/**
* 删除路由列表
* @param definitionList
* @return
*/
private Boolean deleteRouteList(List<RouteDefinition> definitionList){
if(definitionList==null || definitionList.isEmpty()){
return false;
}
for (RouteDefinition definition : definitionList) {
this.routeDefinitionWriter.delete(Mono.just(definition.getId()));
}
return true;
}
}
4. 熔断限流:Sentinel
熔断限流是依靠Sentinel提供的功能,下边先来看看Sentinel,然后再把它整合进gateway
4.1 Sentinel简介
参考:官网、工作流程
-
概念
资源,可以是任何东西,服务,服务里的方法,甚至是一段代码。使用 Sentinel 来进行资源保护,主要分为几个步骤:
- 定义资源
- 定义规则
- 检验规则是否生效
先把可能需要保护的资源定义好(埋点),之后再配置规则。也可以理解为,只要有了资源,我们就可以在任何时候灵活地定义各种流量控制规则。在编码的时候,只需要考虑这个代码是否需要保护,如果需要保护,就将之定义为一个资源。
在 Sentinel 里面,所有的资源都对应一个资源名称(resourceName
),每次资源调用都会创建一个 Entry
对象。Entry 可以通过对主流框架的适配自动创建,也可以通过注解的方式或调用 SphU
API 显式创建。Entry 创建的时候,同时也会创建一系列功能插槽(slot chain),这些插槽有不同的职责,例如:
NodeSelectorSlot
负责收集资源的路径,并将这些资源的调用路径,以树状结构存储起来,用于根据调用路径来限流降级;
ClusterBuilderSlot
则用于存储资源的统计信息以及调用者信息,例如该资源的 RT, QPS, thread count 等等,这些信息将用作为多维度限流,降级的依据;
StatisticSlot
则用于记录、统计不同纬度的 runtime 指标监控信息;
FlowSlot
则用于根据预设的限流规则以及前面 slot 统计的状态,来进行流量控制;
AuthoritySlot
则根据配置的黑白名单和调用来源信息,来做黑白名单控制;
DegradeSlot
则通过统计信息以及预设的规则,来做熔断降级;
SystemSlot
则通过系统的状态,例如 load1 等,来控制总的入口流量;
总体框架如下:
4.2 Sentinel基本使用
参考官网 、示例,基本用法需要先定义资源、在定义规则,最后确定一下是否生效即可。
-
定义资源
资源有多种定义方式,这里只写注解方式:
@Service
public class QueryService {
private static final String KEY="query";
@SentinelResource(value = KEY,blockHandler ="blockHandlerMethod",
fallback = "fallbackMethod")
public String query(String name) {
if(name.equals("3")){
throw new RuntimeException("3 error");
}
return "begin query method, name= " + name;
}
public String blockHandlerMethod(String name, BlockException e){
e.printStackTrace();
return "blockHandlerMethod for Query : " + name;
}
public String fallbackMethod(String name, Throwable e){
e.printStackTrace();
return "fallbackMethod for Query : " + name;
}
}
通过 @SentinelResource
注解定义资源并配置 blockHandler
和 fallback
函数来进行限流之后的处理。blockHandler
函数会在原方法被限流/降级/系统保护的时候调用,而 fallback
函数会针对所有类型的异常。
-
定义规则
在Sentinel
中,可以定制5种规则:
- 流量控制规则
FlowRule
- 熔断降级规则
DegradeRule
- 访问控制规则
AuthorityRule
- 系统保护规则
SystemRule
- 热点规则
ParamFlowRule
@Component
public class SentinelConfig {
private static final String KEY="query";
@PostConstruct
private void init(){
initDegradeRule();
initFlowQpsRule();
initSystemRule();
initAuthorityRule();
initParamFlowRule();
}
//熔断降级规则
private void initDegradeRule(){
List<DegradeRule> rules=new ArrayList<>();
DegradeRule rule=new DegradeRule();
rule.setResource(KEY);
// 80s内调用接口出现 异常 ,次数超过5的时候, 进行熔断
rule.setCount(5);
rule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT);
rule.setTimeWindow(80);
rules.add(rule);
DegradeRuleManager.loadRules(rules);
}
//流量控制规则
private void initFlowQpsRule() {
List<FlowRule> rules = new ArrayList<>();
FlowRule rule = new FlowRule(KEY);
rule.setCount(20);
rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
rule.setLimitApp("default");
rules.add(rule);
FlowRuleManager.loadRules(rules);
}
//系统保护规则
private void initSystemRule() {
List<SystemRule> rules = new ArrayList<>();
SystemRule rule = new SystemRule();
rule.setHighestSystemLoad(10);
rules.add(rule);
SystemRuleManager.loadRules(rules);
}
//黑白名单控制
private void initAuthorityRule(){
List<AuthorityRule> rules=new ArrayList<>();
AuthorityRule rule = new AuthorityRule();
rule.setResource(KEY);
rule.setStrategy(RuleConstant.AUTHORITY_BLACK);
rule.setLimitApp("nacos-consumer");
rules.add(rule);
AuthorityRuleManager.loadRules(rules);
}
//热点参数规则
private void initParamFlowRule(){
ParamFlowRule rule = new ParamFlowRule(KEY)
.setParamIdx(0)
.setCount(20);
ParamFlowItem item = new ParamFlowItem().setObject(String.valueOf("4"))
.setClassType(String.class.getName())
.setCount(2);
rule.setParamFlowItemList(Collections.singletonList(item));
ParamFlowRuleManager.loadRules(Collections.singletonList(rule));
}
}
4.3 网关动态限流
参考:Sentinel 整合 Gateway + Nacos
参考:网关限流
从依赖上看,网关限流,需要sentinel-spring-cloud-gateway-adapte
,动态限流需要sentinel-datasource-nacos
。
将gateway限流的规则配置到nacos中,在nacos中修改配置后,限流规则自动改变。
Sentinel 可以简单的分为 Sentinel 核心库和 Dashboard。核心库不依赖 Dashboard,但是结合 Dashboard 可以取得最好的效果。
Dashbord以界面的方式来配置规则,这些规则配置并没有持久化,生产环境中需要配合配置中心来使用:
一般情况下如此,但我们的Nacos本身就有Dashboard,这里也就不需要Sentinel Dashborad进行配置了,我们直接从Nacos的Dashborad进行配置
-
nacos中动态配置
创建gateway-flow-rule配置项:
[
{
"resource": "order-server",
"resourceMode": 0,
"count": 100,
"intervalSec": 5
},
{
"resource": "order-api",
"resourceMode": 1,
"pattern": "/order/oms/order/create",
"count": 1,
"intervalSec": 3
}
]
对gateway的限流有2种,一种是对网关的route进行限流(resourceMode=0),另外一种是对自定义的route进行限流(resourceMode=1),如下:
resource
:资源名称,可以是网关中的 route 名称或者用户自定义的 API 分组名称。
resourceMode
:规则是针对 API Gateway 的 route(RESOURCE_MODE_ROUTE_ID
)还是用户在 Sentinel 中定义的 API 分组(RESOURCE_MODE_CUSTOM_API_NAME
),默认是 route。
- pattern: 自定义API所对应的path
count
:限流阈值
intervalSec
:统计时间窗口,单位是秒,默认是 1 秒。
-
gateway的bootstrap.yml配置
nacos:
config:
gateway-route:
data-id: gateway-route
group: DEFAULT_GROUP
gateway-flow-rule:
data-id: gateway-flow-rule
group: DEFAULT_GROUP
-
动态配置Handler
这一部分与动态路由的Handler处理一致:
@Component
public class DynamicFlowRuleConfigHandler implements DynamicConfigHandler {
@Value("${nacos.config.gateway-flow-rule.data-id}")
private String dataId;
@Value("${nacos.config.gateway-flow-rule.group}")
private String group;
@Override
public NacosConfigKey getConfigKey() {
return new NacosConfigKey(dataId, group);
}
@Override
public boolean handle(String configInfo) {
List<GatewayFlowRule> gatewayFlowRules = null;
try {
gatewayFlowRules = JSONUtil.toList(configInfo, GatewayFlowRule.class);
} catch (JSONException e){
e.printStackTrace();
return false;
}
if(gatewayFlowRules.isEmpty()){
addDefaultGatewaySentinelRule();
} else {
addGatewaySentinelRuleList(gatewayFlowRules);
}
return true;
}
public void addGatewaySentinelRuleList(List<GatewayFlowRule> gatewayFlowRules) {
Set<com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule> rules = new HashSet<>();
Set<ApiDefinition> definitions = new HashSet<>();
for (GatewayFlowRule sentinelConfig : gatewayFlowRules) {
com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule flowRule = new com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule(sentinelConfig.getResource())
.setResourceMode(sentinelConfig.getResourceMode())
.setCount(sentinelConfig.getCount()) // 限流阈值
.setIntervalSec(sentinelConfig.getIntervalSec());
rules.add(flowRule);
if (sentinelConfig.getResourceMode() == 1) { //自定义API模式
ApiDefinition apiDefinition = new ApiDefinition(sentinelConfig.getResource())
.setPredicateItems(new HashSet<ApiPredicateItem>() {{
ApiPathPredicateItem apiPathPredicateItem = new ApiPathPredicateItem().setPattern(sentinelConfig.getPattern());
if(sentinelConfig.getPattern().contains("**")){
apiPathPredicateItem.setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX);
}
add(apiPathPredicateItem);
}});
definitions.add(apiDefinition);
}
}
GatewayApiDefinitionManager.loadApiDefinitions(definitions);
GatewayRuleManager.loadRules(rules);
}
//配置删除,初始化一个默认规则
public void addDefaultGatewaySentinelRule() {
Set<com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule> rules = new HashSet<>();
rules.add(new com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule("no-rule")
.setResourceMode(0)
.setCount(10000000) // 限流阈值
.setIntervalSec(1)); // 统计时间窗口,单位是秒,默认是 1 秒
GatewayRuleManager.loadRules(rules);
}
}
5. 认证、鉴权
认证与鉴权需要配合鉴权中心来完成,鉴权中心放到后边章节来整理
5.1 认证
认证过程较为简单,验证request的cookie中是否携带token即可。这里采用Gateway GlobalFilter的方式,直接从redis缓存中判断是否有该token,并将userId取出,添加到request的header中,这样在后续服务中直接使用该userId即可。
具体实现如下:
@Slf4j
@Component
public class AuthenticationFilter implements GlobalFilter, Ordered {
@Autowired
private GatewayConfig gatewayConfig;
@Autowired
private RedisTemplate redisTemplate;
@Override
public int getOrder() {
return 3;
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
// 判断路由是不需要认证
String domainPath = request.getURI().getAuthority()+request.getURI().getPath();
if(gatewayConfig.getIgnoreList().contains(domainPath)){
return chain.filter(exchange);
}
// 获取token
HttpCookie cookie = exchange.getRequest().getCookies().getFirst("token");
String token = cookie==null ? "" : cookie.getValue();
// 从缓存缓存中获取token
Long userId = StrUtil.isBlank(token) ? null : TokenUtil.getToken(redisTemplate, token);
if(userId == null){
// 重定向到login
String uri = request.getURI().getAuthority();
Map<String, String> clientMap = gatewayConfig.getClientMap();
String clientId = clientMap.get(uri);
if(clientId == null){
MyResponseBody body = MyResponseBody.fail(MyResponseCode.BAD_REQUEST, "域名未注册");
return respond(response, body);
}
String loginURL = gatewayConfig.getLoginURL() + "?clientId="+clientId;
return redirect(response, loginURL);
} else {
// 设置header,携带userId
try{
ServerHttpRequest newHttpRequest = RequestUtil.createHttpRequest(request,
RequestUtil.createHttpHeadersConsumer(USER_HEADER, userId.toString()));
return chain.filter(exchange.mutate().request(newHttpRequest).build());
} catch (Exception e){
e.printStackTrace();
}
}
return chain.filter(exchange);
}
private Mono<Void> respond(ServerHttpResponse response, MyResponseBody body) {
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
response.setStatusCode(HttpStatus.OK);
JSONObject jsonObject = JSONUtil.parseObj(body);
byte[] bits = jsonObject.toString().getBytes();
DataBuffer buffer = response.bufferFactory().wrap(bits);
return response.writeWith(Mono.just(buffer));
}
private Mono<Void> redirect(ServerHttpResponse response, String location){
response.setStatusCode(HttpStatus.SEE_OTHER);
response.getHeaders().set("Location", location);
return response.setComplete();
}
}
5.2 鉴权
鉴权的逻辑是,判断用户(user)在该组织(org)下,是否有访问该应用(app)的资源(path)的权限(permission)。
共有5个元素,先通过f(orgId, app, path)求出访问这个path所需要的permissin,然后通过f(user, org, app)求出用户所拥有的permission,这两个permission进行对比即可。
前者需要建立path与permission的关联,后者要建立user-role-permission的关联。后者的关联是常见的,前者的却有不同实现方式。
- 通过注解将关联直接写到业务服务的controller上,这样需要在业务服务中进行判断;
- 将path与permission的关系存储到数据库中,这样可以在gateway中进行读取;
第1中方式运维上相对简单,但管理上不太严格,与之前单体的方案相同,再者对于不同产品线,鉴权的逻辑可能有所不同,这种灵活一点;
第2中方案运维上相对复杂,写完代码需要将路由入库,管理更规范一些;
考虑到目前的编码习惯,这里选择的第1种方式。
实现上将这个放到了common中,并增加了app.authorization配置项,某些不需要鉴权的服务,可以配置为false。
@Component
@ConditionalOnProperty(prefix = "app", name = "authorization", havingValue = "true")
public class AuthorizationInterceptor implements HandlerInterceptor {
@Autowired
UserPermissionService userPermissionService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
if(handler instanceof HandlerMethod){
// 路由是否有权限要求
HandlerMethod h = (HandlerMethod)handler;
OrgPermission authPermission = h.getMethodAnnotation(OrgPermission.class);
if(authPermission == null){
return true;
}
String[] needPermissionList = authPermission.value();
// 从header中获取useId
Long userId = UserUtil.getUserId();
if(userId == 0){
throw new AuthenticatedException();
}
// 从path中获取orgId
Map<String, String> pathVarsMap = (Map<String, String>) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
if(pathVarsMap == null){
return true;
}
String param = pathVarsMap.get("orgId");
if(param == null){
return true;
}
Long orgId = Long.valueOf(param);
if(orgId == 0L){
return true;
}
// 从auth获取user在该org中的具有的permission
List<String> permissionList = userPermissionService.getUserPermissionList(orgId, userId);
Set<String> hasPermList = new HashSet<>(permissionList);
// 判断用户是否具有路由所要求的权限
for (String perm: needPermissionList) {
if(hasPermList.contains(perm)){
return true;
}
}
throw new ForbiddenException();
}
return true;
}
}
6. 结语
这样gateway就整理结束了,包含着动态路由、熔断限流、认证鉴权等3大部分的功能。
这一段可替代的方案是nginx,nginx本身就有反向代理(动态路由)、负载均衡的功能,认证、熔断限流都可可以通过模块来增加。
与nginx相比,gateway更适合的nacos体系,它可以注入到nacos中,通过服务名来访问;nginx更适合K8S的场景,知道service即可,不必关注Pod信息。