下一篇:使用Nacos作为分布式配置中心
以前那个Spring Cloud Alibaba系列写的简直太差劲了,项目的依赖乱七八糟,很多无用的依赖也没有去掉。
正好最近参加一个比赛,要写一个比较小型的微服务项目,正好借此机会重新学习Spring Cloud Alibaba,并且在这里记录一下自己遇到的坑。
可能你要问了:为啥这么想不开,要一个人开发一套微服务系统呢?
原因是:项目定位(我们队设计的项目是在有第一群用户时峰值QPS就会达到2000+而且随着用户的增加日均流量和QPS会增长得非常快,而且未来将会不断扩展、不断接入新功能的大型Web应用。架构的横向扩展能力和伸缩性必须非常高)。这就决定了微服务架构是最适合这个项目的选择,而且核心业务不是很复杂,仔细想了想服务提供者+服务消费者大概也就10~20个,不是很多,在众多微服务架构的系统中也算是小型的吧,而且时间比较宽裕且不要求100%完成,一个人开发没有特别大的问题。
其实还是全队暂时只有我可以胜任开发与整合这套微服务系统一些基础组件(如缓存,SSO,中间件整合等)的工作,所以……
因为是比赛用的项目,所以暂时不放出所有代码,等比赛结束后再把代码上传到GitHub供大家学习和参考。
技术栈(随时更新)
- 架构:微服务架构,前后端分离
- 服务风格:对内RPC,对外RESTful
- Java版本:1.8(本来用的是11,结果
SkyWalking探针
在Java11下居然不能正常工作,其他框架也出现了或多或少的问题,放弃) - 微服务框架:Spring Cloud
- Web框架:Spring Boot
- RPC框架:Dubbo
- ORM框架:SpringDataMongoDB
- 数据库:MongoDB
- 缓存数据库:Redis
- 消息队列:Kafka
- 容器化引擎:Docker
- 服务注册与发现中心/服务配置中心:Nacos
- 服务监控:Spring Boot Admin
- 断路器:Sentinel
- 服务链路追踪:Skywalking
- 微服务网关:Spring Cloud Gateway
- 认证鉴权机制:Spring Cloud Security + OAuth2.0 + JWT
- 日志采集与分析:ELK
搭建Demo项目
为了避免一上来就踩坑,影响到项目进度,我决定先建立一个Demo项目,把要选择的框架都试一遍,再以这个Demo项目为基准开发其他基础框架和业务逻辑。其实现参考了上次写的那一个——传入一个Message,返回一个包含端口的字符串。
先看一下效果吧:
父级依赖管理项目
还是使用一个父级项目来管理所有微服务的依赖:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.5.RELEASE</version>
</parent>
<groupId>com.timeline</groupId>
<artifactId>timeline-web</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
<!-- 项目依赖版本管理 -->
<!-- Spring Cloud 采用最新的H(Hoxton)版 -->
<spring.cloud.version>Hoxton.RELEASE</spring.cloud.version>
<!-- Spring Cloud Alibaba 采用最新的2.2.0版本 -->
<spring.cloud.alibaba.version>2.2.0.RELEASE</spring.cloud.alibaba.version>
<!-- Spring Boot Admin 采用最新的2.2.2版本,以适配项目使用的 Spring Boot 2.2.5版本 -->
<spring.boot.admin.version>2.2.2</spring.boot.admin.version>
</properties>
<modules>
<module>timeline-api</module>
<module>timeline-provider</module>
<module>timeline-consumer</module>
<module>timeline-gateway</module>
<module>timeline-admin-server</module>
</modules>
<dependencyManagement>
<dependencies>
<!-- Spring Cloud -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring.cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- Spring Cloud Alibaba -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring.cloud.alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- 公共API模块 -->
<dependency>
<groupId>com.timeline</groupId>
<artifactId>timeline-api</artifactId>
<version>${project.version}</version>
</dependency>
<!-- Spring Boot Admin -->
<!-- Server -->
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-server</artifactId>
<version>${spring.boot.admin.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>
相比之前那个,删除了所有可以被代替或者被包含的依赖。综合算下来,其实只有Spring Cloud
、Spring Cloud Alibaba
的父级依赖和Spring Boot Admin
。
其实Spring Boot Admin的依赖可以单独放在相应的模块中,但是为了统一管理依赖版本方便,就放在父级pom.xml里了。
服务提供者
pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>com.timeline</groupId>
<artifactId>timeline-web</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>timeline-provider</artifactId>
<dependencies>
<!-- 公共API模块 -->
<dependency>
<groupId>com.timeline</groupId>
<artifactId>timeline-api</artifactId>
</dependency>
<!-- Dubbo -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-dubbo</artifactId>
</dependency>
<!-- Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- Nacos Discovery -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Nacos Config -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!-- Alibaba Spring Context Support -->
<dependency>
<groupId>com.alibaba.spring</groupId>
<artifactId>spring-context-support</artifactId>
</dependency>
</dependencies>
</project>
解释一下这里为什么没有spring-boot-admin-starter-client
:
除了使用Spring Boot Admin Client
能把微服务注册到Spring Boot Admin Server
之外,Spring Boot Admin
在和Spring Cloud
结合使用时,Spring Boot Admin Server
可以使用每个微服务的服务注册信息来注册和管理服务,就不需要再使用Client了,再使用Client就会把一个实例识别成两个了。服务启动后可以在控制台看到,服务向Spring Boot Admin
注册使用的是向Nacos
注册的元数据:
Spring Boot Admin Server的实现很简单,就是一个注解的事,网上也有很多教程,就不放出来了。
(搞不懂为什么要这么实现,跟Nacos和Consul一样做成独立部署、对应用无侵入的不好嘛?)
bootstrap.yml:
server:
port: 10222
spring:
application:
name: timeline-provider
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
# 注册到TIMELINE_DEMO分组
group: TIMELINE_DEMO
config:
server-addr: 127.0.0.1:8848
file-extension: yml
main:
allow-bean-definition-overriding: true
# 服务健康检查
management:
endpoints:
web:
exposure:
include: "*"
dubbo:
registry:
address: spring-cloud://localhost:8848
scan:
base-packages: com.timeline.provider.service
# 使用dubbo协议,端口从20880开始自增以防重复
protocol:
name: dubbo
port: -1
cloud:
subscribed-services: "*"
timeline:
value: 2
包含了Nacos
的一些信息和Dubbo
的信息。其实有一部分配置可以迁移到Nacos Config
中,这样方便统一管理,比如Dubbo的部分配置。咱们之后再实现这个。
业务代码与上一个差不多,想必用过Dubbo
的各位大佬也很容易看出来是怎么实现的,就不赘述了。
服务消费者
pom.xml与服务提供者一模一样,就是artifactId
不同。
bootstrap.yml:
server:
port: 10200
spring:
application:
name: timeline-consumer
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
# 注册到TIMELINE_DEMO分组
group: TIMELINE_DEMO
config:
server-addr: 127.0.0.1:8848
file-extension: yml
main:
allow-bean-definition-overriding: true
# 服务健康检查
management:
endpoints:
web:
exposure:
include: "*"
dubbo:
registry:
# 注册到 Spring Cloud 注册中心(Nacos)
address: spring-cloud://localhost:8848
scan:
base-packages: com.timeline.consumer.service
# 使用dubbo协议,端口号为-1表示从20880开始自增端口,以防重复
protocol:
name: dubbo
port: -1
cloud:
# 订阅 timeline-provider 服务提供者
subscribed-services: timeline-provider
主要的不同点就是subscribed-services
,声明了订阅timeline-provider
。
微服务网关
据说这个东西在真正的微服务系统中不常用,好像是因为像网关这样流量大的中心节点很容易全部挂掉而导致单点故障。但是这次为了系统的完善和前端调用方便,我们还是把它实现一下吧,反正又不会真的上线啦。
pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>timeline-web</artifactId>
<groupId>com.timeline</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>timeline-gateway</artifactId>
<dependencies>
<!-- Spring Cloud Gateway -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- Nacos Discovery -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies>
</project>
bootstrap.yml:
server:
port: 10100
spring:
application:
name: timeline-gateway
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
# 注册到TIMELINE_DEMO分组
group: TIMELINE_DEMO
gateway:
discovery:
locator:
enabled: true
lower-case-service-id: true
routes:
- id: timeline-consumer
# lb表示开启负载均衡,timeline-consumer是应用名称
uri: lb://timeline-consumer
predicates:
- Path=/timeline/**
management:
endpoints:
web:
exposure:
include: "*"
主要是配置了路由信息,将所有/timeline/
开头的请求都转发到timeline-consumer
里去。
同样,有很多配置是可以移动到Nacos Config
中的。
使用docker-compose搭建SkyWalking环境
“链路追踪”是微服务中比较重要的一个组成部分,但是在用过了Cat
和Zipkin
之后,我发现还是SkyWalking
最好:
- SkyWalking使用javaagent埋点,对应用无任何侵入。
- Zipkin虽然能与Spring Cloud无缝集成,但是在Dubbo当前版本
2.7.4.1
上不能正常使用,按照能找到的资料里的方法进行配置,会导致服务消费者无法启动(可能是我使用的方法不对?)。 - Cat虽然表现比Zipkin好一些,但是与Spring Cloud配合不佳、参考资料太少。
- SkyWalking生成的报表比较丰富,对服务状态、调用状态的展示比较清楚。
- 据说跟Cat一样是国人开发的,666666!
docker-compose启动单机SkyWalking
单独搭建SkyWalking
比较繁琐,所以还是使用docker
来搭建。
SkyWalking
官方已经给出了docker-compose
文件供我们方便地搭建环境,我们使用官方的文件就可以了。GitHub地址:https://github.com/apache/skywalking-docker
具体步骤也很简单:
git clone git@github.com:apache/skywalking-docker.git # 需要配置SSH
cd skywalking-docker/6/6.6/compose-es7
docker-compose -f docker-compose.yml up -d
启动完毕后就可以在localhost:8080
看到SkyWalking
控制台了。
在IDEA中部署SkyWalking应用探针
打开右上角的Edit Configurations...
,在每个服务的VM options
里都加上这么一段:
-javaagent:path to/skywalking-agent.jar
-Dskywalking.agent.service_name=你的应用名称
-Dskywalking.collector.backend_service=SkyWalking OAP的地址,我这里是localhost:11800
例如:
然后启动应用,可以发现控制台输出了SkyWalking Agent
相关的内容:
使用SkyWalking监测服务调用
回到浏览器中刷新几次,就可以在SkyWalking中看到服务调用的信息了(可能因为我电脑比较渣,导致它反应比较慢,如果没反应可以等一会):
可以发现,除了我们直接进行的HTTP调用,Dubbo
的RPC调用也可以正常被检测到。不过为啥Spring Cloud Gateway
没被写进来呢(我调用的时候是通过了Gateway的)?
SkyWalking还有一个很有意思的功能:拓扑图。因为SkyWalking把健康检测也算成用户对服务的调用了,所以User
和timeline-provider
之间也有线,但实际上是没有直接调用的。这里服务太少了,这个图看起来没啥意思,不知为何Spring Cloud Gateway没有检测到(虽然其实它是不是被检测好像并没有太大的意义,因为Gateway一旦挂了,那无论请求什么都是500了):
踩坑记录
坑1:项目启动时抛出"Connection Refused"异常
描述:在启动项目时,莫名其妙地抛出了Conncetion Refused
的异常,但是功能一切正常。
解决:这是Dubbo
一些莫名其妙的默认行为的问题。通过断点调试后,发现如果不配置dubbo.registry.address: spring-cloud://localhost
,在读取配置的时候就会自动加上这一条,并且端口指明为9090
(咱也不知道为啥要这么干,在我印象中除了Eureka默认是需要自己指定以外,好像没有什么注册中心默认端口是9090),然后在启动时会自动连接localhost:9090
。因为我们的注册中心是跑在8848
端口上的,9090没有任何东西,所以自然会Connection Refused
。解决方法就是配置dubbo.registry.address: spring-cloud://localhost:8848
,如下图。这样配置好就可以了。
并且,由于这样配置的含义是“把Dubbo的注册信息挂载到Spring Cloud的注册中心(在这里就是Nacos)”,所以Nacos服务列表里显示的信息就不包含Dubbo的元数据了,更加精简。