一、介绍
springCloud第一代与第二代:
名称 | 第一代 | 第二代 |
---|---|---|
网关 | Spring Cloud Zuul | Spring Cloud Gateway |
服务注册 | Eureka、Consul、ZK | Spring Cloud Alibaba Nacos |
配置中心 | Spring Cloud Config | Spring Cloud Alibaba Nacos |
负载均衡 | Ribbon | Spring Cloud loadbalancer |
熔断器 | Hystrix | Spring Cloud Alibaba Sentinel |
- 第一代使用Netflix系列开源组件
- 第二代实际上是自己研发和国内优秀微服务框架整合
- Spring Cloud Gateway 网关
- Spring Cloud Loadbalancer 客户端负载均衡
- Spring Cloud r4j 服务保护
- Spring Cloud Alibaba Nacos 服务注册
- Spring Cloud Alibaba Nacos 分布式配置中心
- Spring Cloud Alibaba Sentinel 服务保护
- Spring Cloud Alibaba Seata 分布式事务解决框架
- Alibaba Cloud OSS 阿里云存储
- Alibaba Cloud SchedulerX 分布式任务调度平台
- Alibaba Cloud SMS 分布式短信系统
Alibaba解决方案:
- 这么多小服务,如何管理他们? 【Nacos】
- 这么多小服务,他们之间如何通讯?【restful、rpc、dubbo、feign】
- 这么多小服务,客户端怎么访问?【Gateway】
- 这么多小服务,一旦出现问题,应该如何自处理?【容错Sentinel】
- 这么多小服务,一旦出现问题,应该如何排错?【链路追踪Skywalking】
二、Nacos服务注册与发现
Nacos是SpringCloudAlibaba中分布式服务注册中心、分布式配置中心;相当于之前的SpringCloudEruika+Config。
2.1 传统RPC远程调用的问题
- RPC远程调用框架:HttpClient、gRpc、dubbo、rest、openFeign
问题:服务与服务之间URL地址怎么管理?
- 例如A服务要调用B服务,传统手段在A服务端写死,一旦B服务除了问题,要改A服务的代码才能切换。
在微服务架构通讯,服务之间依赖关系非常大。如果通过传统手段管理服务URL,一旦地址发生变化,还需要手动修改。因此需要使用URL治理技术,可以实现对我们整个实现动态服务注册与发现,本地负载均衡,容错等。
2.2 注册中心
-
注册中心就是管理服务的URL地址信息,能够实现动态感知。
-
常用的注册中心:Dubbo依赖ZK、Eureka、Consul、Nacos
值得注意的是,Nacos支持CP和AP的切换。首先在分布式系统中,需要保证其P分区容错性;在此基础上,如果使用主从架构,就是保证其一致性,但是这种保证了CP的系统,就无法保障其可用性A,当一半以上的节点不可用时整个架构不可用。另一个方向就是不使用主从架构,而是不同服务在不同的nacos中进行注册,这种情况保证了其可用性AP,但是就无法保障一致性C。Nacos默认保证了AP,可以通过Nacos提供的restful接口进行CP的切换。
CAP:
- 一致性(Consistency)
- 可用性(Availability)
- 分区容错性(Partition tolerance)
- 整个微服务注册中心的实现原理:
- 生产者:单个服务启动后,会将自己的IP、端口、服务名称等基本信息发送给注册中心。注册中心存放
Map<String, List<String>>
中。 - 消费者:其他服务会先去注册中心找服务名称,获取服务信息列表;采用负载均衡器选择一个地址进行RPC远程调用。
整体流程示意图
- NacosClient会默认每5s一次发送心跳
- NacosServer对于超过15s没有收到心跳的服务,会将其健康值置位false,超过30s没有心跳就会将其剔除
2.3 Nacos安装
链接:传送门
这里下载的是源码,因此需要本地环境搭建。
- 进入到nacos源码目录,使用如下mvn命令,开始本地编译:
mvn -Prelease-nacos -Dmaven.test.skip=true clean install -U
- 开启单机模式,添加虚拟机参数:
-Dnacos.standalone=true
- 运行后访问
http://localhost:8848/nacos/#/login
- 登录查看,账号密码都是
Nacos
- 如果不使用源码,就在github中下载对应release版本,因为我这里使用的mac,我就下载对应.zip文件,不同Spring Cloud Alibaba版本,对应nacos版本不同,版本对照表
- 下载好后,首先确保环境变量没有问题(Mac配置java环境变量),然后进入到nacos文件夹下的
/bin
目录,执行bin sh startup.sh -m standalone
,然后直接访问http://localhost:8848/nacos/#/login
即可访问
2.4 Nacos管理界面
- 服务管理
- 查看服务详细数据
对某个服务点击“详情”
- 查看订阅者列表
三、客户端搭建
我们需要构造一个如下的环境:
3.1 Nacos服务注册与发现的依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>0.2.2.RELEASE</version>
</dependency>
3.2 生产者服务
- POM文件添加
3.1
所述依赖 - 编写
yaml
配置
server:
port: 8001
spring:
application:
name: order-service # 这里是服务的名字,多个服务时可以一致
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848 # 填写服务注册中心的地址
- 启动类
@SpringBootApplication
@EnableDiscoveryClient
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}
- controller
@RestController
public class OrderController {
@Value("${server.port}")
private String port;
@GetMapping("/port")
public String getPort() {
return port;
}
}
为了能够说明Nacos自带Ribbon负载均衡的效果,我们需要多个生产者,因此进行如下操作
- 多个生产者
最开始还像傻子一样复制之前的生产者然后改配置,这样很容易出现问题,而且改的地方很多。其实IDEA有很便捷的方式,我们先启动生产者服务,然后在上面右键点击
Copy Configuration
:
然后在弹出界面中进行配置修改:
启动在这里,点击:
3.3 服务消费者
- 添加
POM
依赖 - 修改
yaml
文件
server:
port: 8003
baseUrl: http://order-service # 这里是需要远程调用的服务名称,就是3.2.2中服务的名字
spring:
application:
name: consumer-8003 # 自己服务的名字
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848 # 注册中心
- 启动类
@SpringBootApplication
@EnableDiscoveryClient
public class NacosComsumer8003Application {
public static void main(String[] args) {
SpringApplication.run(NacosComsumer8003Application.class, args);
}
/**
* 这里要注意,因为消费者要通过远程调用访问生产者的对应端口,这里需要配置restTemplate
* 因为Nacos远程调用是自带负载均衡,这里一定要写LoadBalance,否则会报错说找不到对应服务
*/
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
- controller
@RestController
public class ComsumerController {
/* 自动注入restTemplate */
@Autowired
private RestTemplate restTemplate;
/* 这里就是在配置文件中配置的生产者服务跟地址,即 “http://order-service” */
@Value("${server.baseUrl}")
private String baseUrl;
@GetMapping("/port")
public String getServer() {
return restTemplate.getForObject(baseUrl + "/port", String.class);
}
}
3.4 启动服务
启动服务前先保证Nacos注册中心是启动的
- 启动两个生产者
单独访问生产者可以正常返回其端口
- 启动消费者
三个服务我们都启动后,查看Nacos:
- 我们访问消费者,测试负载均衡
Nacos自带了
Ribbon
负载均衡器,默认是轮训的方式,我们也可以从依赖看出来这一点
3.5 Nacos配置项
在上面小结中,我们在配置文件里对Nacos的地址进行了配置,它的常用配置项如下:
配置项 | Key | 默认值 | 说明 |
---|---|---|---|
服务端地址 | spring.cloud.nacos.discovery.server-addr | 无 | NacosServer启动监听的ip地址和端口 |
服务名 | spring.cloud.nacos.discovery.service | ${spring.application.name} | 给当前服务命名 |
服务分组 | spring.cloud.nacos.discovery.group | DEFAULT_GROUP | 设置读物所处的分组 |
权重 | spring.cloud.nacos.discovery.weight | 1 | 取值范围1 到100 ,数值越大权重越大,可以结合带权重的负载均衡策略 |
网卡名 | spring.cloud.nacos.discovery.nerwork-interface | 无 | 当IP未配置时,注册的IP为此网卡对应IP地址,如果此项也未配置,则默认取缔一块网卡的地址 |
注册的IP地址 | spring.cloud.nacos.discovery.ip | 无 | 优先级最高 |
注册的端口 | spring.cloud.nacos.discovery.port | -1 | 默认情况下不会配置,自动探测 |
命名空间 | spring.cloud.nacos.discovery.namespace | 无 | 常用场景之一是不同环境的注册区分隔离,dev、master |
AccessKey | spring.cloud.nacos.discovery.access-key | 无 | 当要上阿里云时云账号名 |
SecretKey | spring.cloud.nacos.discovery.secret-key | 无 | 当要上阿里云时云密码 |
MetaData | spring.cloud.nacos.discovery.metadata | 无 | 使用Map格式配置,用户可以根据自己的需要自定义一些和服务相关的元数据信息。例如版本号 |
日志文件 | spring.cloud.nacos.discovery.log-name | 无 | |
集群 | spring.cloud.nacos.discovery.cluster-name | DEFAULT | 配置Nacos集群名称 |
接入点 | spring.cloud.nacos.discovery.enpoint | UTF-8 | 地域的某个服务入口域名,通过此域名可以动态拿到服务器地址 |
是否集成Ribbon | ribbon.nacos.enabled | true | 一般设置为true 即可 |
是否开启NacosWatch | spring.cloud.nacos.discovery.watch.enabled | true | |
是否作为临时实例 | spring.cloud.nacos.discovery.ephemeral | true | 为true 表示是临时实例,存在内存中,如果没有心跳超过一定时间就会被删除;对应的是持久化实例,实例会被写入数据库,即使没有心跳也是会存在的,标记为不健康 |
在雪崩保护时,如果健康实例的比例小于保护阈值时,不健康实例也是可以被访问。例如有两个生产者,每个服务上限是10w,因此可以承载20w访问量,当服务雪崩时,如果20w访问一起冲到一台服务器去,就会导致整个服务瘫痪。因此此时让不健康实例也可以访问,虽然这样做会有10w的用户访问不成功,但是另外的10w可以正常访问,不至于整个服务访问不了。
四、Nacos源码分析
思考:Nacos服务端有成百上千的服务同时写(注册),还有成百上千的服务同时读(获取注册表),那么Nacos服务器是如何解决读写高可用的问题?
- CopyOnWrite写时复制:写注册表时,将注册表复制一份进行写副本。因此不用加锁。
4.1 启动Nacos
通过注解
@SpringBootApplication
可以看出这是一个SpringBoot项目,实际上Nacos服务注册中心就是一个web服务器,对外提供了很多的HTTPAPI,应用服务通过API对外接口来实现将自己注册和发送心跳等功能。
4.2 客户端如何进行注册
刚才提到,Nacos本身就是web应用,提供了很多API,然后客户端通过远程服务调用来进行各个功能实现。我们可以通过API文档找到服务注册的API: 官方链接
我们先从客户端代码看起,我们可以通过全局搜索/instance
,就能够找到在UtilAndComs.java
文件中定义了服务注册的对应路径,然后我们再次全局产找看哪里使用了NACOS_URL_INSTANCE
public static String NACOS_URL_INSTANCE = NACOS_URL_BASE + "/instance";
最终可以在NamingProcy.java
文件中的registerService
方法中看到使用了这个路径,我们在此处打上断点,然后启动客户端,看是否能够断住:
还有一种方法,可以找到这个断点,就是在对应包下的spring.factories中找到自动装配的类
查看
NacosDiscoveryAutoConfiguration
类中,可以看到它向容器中添加了一个Bean,其中最重要的就是nacosAutoServiceRegistration
点进NacosAutoServiceRegistration
类中,该类继承自AbstractAutoServiceRegistration
:
进入AbstractAutoServiceRegistration
,里面有一个@EventListener
,会调用this.start
方法:
而this.start
方法继续调用register()
方法
而register()
方法最终会调用到NacosServiceRegistry
的同名方法:
而这里最终会调用咱们断点的位置。
以上只是提供一种找到这个断点的方式。不用过度深究
我们打好断点之后,启动客户端,可以看到代码走到了这里,说明我们找的没有错,这里可以看到服务注册的相关信息:
接着跟进reqAPI
方法,可以看到底层是使用了HttpClient
来进行远程服务调用:
4.3 服务端如何处理注册请求
我们在Nacos源码中可以看到有一个naming
的模块,里面有各种controller。可以看到如图的Controller正是对应我们的注册请求:
...未完