咱们接上回
上一节我们基于Sentinel实现了微服务体系下的限流和熔断,使得整个微服务架构的安全性和稳定性上升了一个台阶
篇尾我们引出了一个问题,众多的微服务节点,我们如何部署才能满足客户端简洁高效的访问需求?
—— 今天我们就来引入服务网关的概念
什么是服务网关?
服务网关是微服务体系下唯一的流量入口,对内实现内部架构统合,所有外来请求都要经由网关路由到对应的微服务节点,进而实现完整的业务逻辑
由于是每个外部请求的必经之路,因此除了路由之外,服务网关还可以胜任几乎所有的横切面功能,比如我们上一节提到的限流、熔断,以及统一的认证服务、日志监控等
使用服务网关的优势:
1. 客户端简化 —— 加入服务网关之后,客户端只需要知道服务网关的访问地址即可,而不再需要了解每个微服务的访问端口
2. 降低耦合度 —— 其他微服务节点有变动,我们只需要灵活调整服务网关的路由即可,不必每次都去修改客户端
3. 提升可维护性 —— 由服务网关统一实现路由、灰度发布、负载均衡、限流熔断等机制,开发人员更专注于业务实现
引入服务网关带来的弊端:
微服务架构讲求去中心化,而网关的引入使之变成了唯一的单点,其高可用性和可维护性变成了必须要解决的课题
如下是服务网关的基本功能以及常见的几种服务网关的对比:(图示来自:CSDN 张维鹏,感谢)
我们本节重点关注 SpringCloudGateway 的用法,其他感兴趣大家可以自行了解
新建nacos-sentinel-gateway模块
我们首先创建一个新模块并引入如下依赖项:
很多依赖项我们已经很熟悉了,这里重新说明一下他们的用途:
Nacos Service Discovery —— 基于Nacos实现服务发现
Nacos Configuration —— 基于Nacos实现配置中心
Spring Cloud Alibaba Sentinel —— Sentinel限流熔断组件,我们上一节的主要内容
Cloud Bootstrap —— bootstrap.yml 加载机制,实现Nacos云配置的关键
Gateway —— 即 SpringCloudGateway,我们本节关注的重点
Spring Cloud Alibaba Sentinel Gateway —— Sentinel组件对于SpringCloudGateway的适配机制,本节后半部分我们要用到
值得注意的是:SpringCloudGateway内部直接包含了spring-boot-starter-web依赖,如果你的工程中同时包含二者的引用,会报冲突错误
解决的办法是添加spring-cloud-starter-gateway的同时排除掉spring-boot-starter-web
<!-- 引入gateway网关 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </exclusion> </exclusions> </dependency>
由于本节中不牵扯spring-boot-starter-web的使用,所以不存在这个问题,如下是本节的设计到的各个依赖项在pom中的声明:
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency>
项目工程目录如下:
本节相较于之前第一个不同点来了,配置文件不再是.properties,而是变成了.yml,后者比前者的表达能力更强,且比xml更简洁
.yml是yaml的一个变种(基本完全一样),非常适合作为配置文件,它不但可以表示变量,还可以声明数组及字典
这里给大家提供一个可以在线将.properties转换为.yml的工具,非常的方便,感谢BeJSON站长
====================================
在线properties转yaml、yml工具 - BeJSON.com
====================================
如下是转换完毕后的bootstrap.yml
spring: cloud: nacos: config: username:nacos password:nacos contextPath:/nacos server-addr:127.0.0.1:8848
内容依然仅是nacos的声明,为便于测试效果,本节使用了本地的application.yml,因此bootstrap.yml中并未包含spring.application.name的声明
而如果我们打算将application.yml托管给nacos,则必须在bootstrap.yml中声明spring.application.name
然后我们来看application.yml的内容,这将是本节的重点
spring: application: name: nacos-sentinel-gateway cloud: gateway: enabled: true routes: - id: dubbo-nacos-consumer uri: http://127.0.0.1:8080 order: 1 predicates: - Path=/consumer/** filters: - StripPrefix=1 server: port: 8081
到这里,网关服务就可以直接跑起来了
啥?还没写代码呢!是的,一句代码不用写,来一个配置文件,你的服务网关就已经搭建起来了,非常方便对不?
我们分析下gateway的相关配置,不难看出其中的端倪:
1. route(路由)是gateway的基本配置单元,说白了就是搭建网关从设定路由规则开始
2. 每个route都包含四部分:
id —— 路由标识,自行命名,不重复即可
uri —— 请求转发的真实目标地址
order —— 优先级,数字越小代表优先级越高
predicates —— 断言(数组),用于判定转发规则是否成立
filters —— 过滤器(数组),请求url ---> 过滤器处理 --->目标url,定义了url的变换规则
我们以此来解读一下上述的网关路由到底定义了一个怎样的转发规则
id —— 和我们之前构建的dubbo-nacos-consumer同名,那下述转发规则自然与其有关
uri —— dubbo-nacos-consumer监听8080端口,因此我们打算将请求转发给dubbo-nacos-consumer服务
order —— 1代表高优先级
predicates —— Path=/consumer/**,代表请求路径中需要包含/consumer/的前缀,也就说我们会将路径中包含/consumer/的请求转发给dubbo-nacos-consumer服务
filters —— StripPrefix=1,是我们以后经常会遇到的一种过滤器,含义为过滤一级前缀,这里的前缀就是上边提到的/consumer前缀
这是什么意思?
我们网关监听的端口是8081,
假定我们的请求为 http://127.0.0.1:8081/consumer/person/select ,
因为满足包含前缀/consumer/的设定,请求被转发到 http://127.0.0.1:8080/consumer/person/select ,
而过滤器会过滤掉/consumer前缀,于是请求路径变为 http://127.0.0.1:8080/person/select
而这正是dubbo-nacos-consumer中的PersonController.select方法的有效映射
我们依次启动先前创建的 dubbo-nacos-provider 、dubbo-nacos-consumer 以及我们刚创建好的 nacos-sentinel-gateway
打开post执行请求验证下结果
结果证实了我们的猜想,这样一来我们就简单实现了服务网关的路由功能
虽然功能是实现了,但是你有没有发现,我们是直接指定了转发的目标地址,这种模式配置的路由规则存在几个非常大的弊端:
1. 网关必须知道所有微服务的地址,并且逐一配置
2. 微服务地址有任何变动,网关必须做同步更改
3. 无法实现负载均衡
那么有没有其他配置方式可以避开这些不足?当然是有的!
基于Nacos服务发现实现负载均衡
我们将app.yml的内容做如下改动:
spring: application: name: nacos-sentinel-gateway cloud: nacos: discovery: server-addr: 127.0.0.1:8848 gateway: enabled: true routes: - id: dubbo-nacos-consumer uri: lb://dubbo-nacos-consumer predicates: - Path=/consumer/** filters: - StripPrefix=1 server: port: 8081
相比于前一个版本,我们有两个地方的改动
首先,我们加入了基于Nacos的服务发现配置:
cloud: nacos: discovery: server-addr: 127.0.0.1:8848
其次,我们原有固定的转发地址改为了另一种形式:uri: lb://dubbo-nacos-consumer
这种模式遵循的固定格式为:lb://service-name
lb —— load balancing的缩写,即负载均衡
service-name —— Nacos中注册的服务名称,注意这里是名称,而并非ip
lb://dubbo-nacos-consumer 代表如果断言成立,则请求将均衡的分发至各个dubbo-nacos-consumer的服务实例
我们先在post中执行验证下结果:
结果跟前一个版本是一样的,印证了这种配置模式是有效的
但是由于我们的dubbo-nacos-cosumer服务只有一个实例,所以看不出来负载均衡的效果
幸运的是,在idea之下我们可以非常方便的配置另一个启动实例
为了便于对比,我们先把consumer的application.properties配置文件迁移回本地,并删除nacos的云端配置
而后我们创建另一个新的配置文件application-anotherr.properties,内容如下:
# 应用名称 spring.application.name=dubbo-nacos-consumer # dubbo 协议 dubbo.protocol.id=dubbo dubbo.protocol.name=dubbo # dubbo 协议端口( -1 表示自增端口,从 20880 开始) dubbo.protocol.port=-1 # Dubbo 消费端订阅服务端的应用名,多个服务提供者用逗号分隔 dubbo.cloud.subscribed-services=dubbo-nacos-provider # dubbo 服务扫描基准包 dubbo.scan.base-packages=com.example.dubbonacosconsumer # Nacos帮助文档: https://nacos.io/zh-cn/docs/concepts.html # Nacos认证信息 spring.cloud.nacos.discovery.username=nacos spring.cloud.nacos.discovery.password=nacos # Nacos 服务发现与注册配置,其中子属性 server-addr 指定 Nacos 服务器主机和端口 spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848 # 注册到 nacos 的指定 namespace,默认为 public spring.cloud.nacos.discovery.namespace=public # 应用服务 WEB 访问端口 server.port=8098 # sentinel看板配置 spring.cloud.sentinel.transport.dashboard = 127.0.0.1:9999 # 开启对sentinel看板的饥饿式加载。sentinel默认是懒加载机制,只有访问过一次的资源才会被监控,通过关闭懒加载,在项目启动时就连接sentinel控制台 spring.cloud.sentinel.eager = true
内容与application.properties基本相同,区别仅在于
# 应用服务 WEB 访问端口
server.port=8098
我们知道,同一台机器,同一个监听端口智能使用一次,因此如果想借用idea直接在开发环境下起另一个服务实例,则需要开启另一个不同的端口
然后我们创建一个新的启动配置
最关键的一句配置:
--spring.profiles.active=another
代表我们指定程序的启动配置文件为:application-anotherr.properties
为了与原始的consumer做区分,我们先启动consumer,然后改一下consumer的PersonController内容,再启动another-consumer
@GetMapping("/select") @SentinelResource(value = "person/select", blockHandler = "selectBlock") public SelectRetVo select() { SelectRetVo vo = new SelectRetVo(); vo.setPersons(service.select()); vo.setError("ok-another"); // another的区别 return vo; }
此刻我们打开Nacos的服务中心,会看到dubbo-nacos-consumer存在两个运行中的实例
然后我们再次通过Post访问网关验证结果
返回结果中的 ok 和 ok-another 会交替出现
看到效果了吗?原始consumer和another-consumer都是动态启动的,而基于Nacos服务发现机制,
gateway在无感知的情况下,完全不做任何改动,就实现了多个consumer实例的动态负载均衡,是不是非常便捷?
那除了 lb://service-name 这种方式,还有没有更简单的办法?当然是有的!
基于Naocs的全自动路由配置
我们再次改动gateway的app.yml配置文件内容
spring: application: name: nacos-sentinel-gateway cloud: nacos: discovery: server-addr: 127.0.0.1:8848 gateway: enabled: true discovery: locator: enabled: true lower-case-service-id: true server: port: 8081
这一次我们没有特别指定下游节点的ip,也没有配置相关的负载均衡,那这样写实现了什么效果?
我先给大家看Post的测试结果:
我们来和前边的负载均衡示例做下对比:
前次的访问地址:http://localhost:8081/consumer/person/select
本地的访问地址:http://localhost:8081/dubbo-nacos-consumer/person/select
看到区别了吗?这样写相当于 lb://默认spring.application.name
仅是这样吗?其实还不止!仔细看,本次配置中我们并没有指定特定的service-name开负载均衡!
也就是说这种模式之下,所有注册到Nacos服务中心的外部可访问Web节点实例均启动负载均衡设定!
不信你可以仿照刚刚的consumer,自己再起一组支持web访问的实例,随便写几个controller测试方法,结果一试便知,gateway同样满足无感知访问
本节中我们对SpringCloudGateway分别进行了 从特定ip配置,到指定服务的负载均衡,再到全自动动态负载均衡 的一个统一的介绍,但SpringCloudGateway的使用其实还远不止这些
对 断言 和 过滤器 更加细化的定制还可以帮我们过滤掉各种场景下的外部非法访问,从而对整个内部集群的稳定性起到一个更加的有效的防护作用
但是我们要思考一个问题 —— 微服务体系下的不稳定因素仅来自于集群外部吗?答案显然不是!我们面临的业务需求是丰富多样的,集群内部同样可能出现性能消耗的热点
那么当问题发生在微服务体系内部时,我们如何实现问题及热点的精准定位呢?请看下回 —— 基于Spring Cloud Sleuth的链路追踪,敬请期待~