8868交易平台怎么卖东西,8868交易平台怎么注册

  

  迪迪,上车!   

  

  在这次旅行中,你将获得以下知识:   

  

  Nacos在微服务架构中的作用:Linux下Nacos的安装和使用搭建真实项目环境,实现服务注册和发现,实现真实项目环境下Nacos的配置管理,将Nacos集群的配置和集群数据持久化到MySQL,使用Nginx负载均衡访问Nacos集群,分析Nacos在微服务架构中的作用。服务发现注册中心是微服务架构不可或缺的一部分,充当服务注册发现.   

  

  为什么使用注册中心?   

  

  假设有这样一个场景,我们需要在乘车前确定自己的座位在哪里。不然可能是别人的位子,别人做了就把我踢出去,很尴尬。通常情况下,售票员会给我发一张有座位号的票,然后我会去正确的座位,这样我就可以很快找到我的座位,而不会有被踢出去的风险。   

  

  在这里,售票员实际上是提供注册和发现.的一个中间联络员,座位信息已经向列车员登记了。乘客来乘车时,只要找售票员就能很快找到座位。   

  

     

  

  在微服务架构系统中,每个微服务组件都是相互独立的,但最终会组合成一个整体,作为软件系统服务于最终客户。在整个大系统中,每个服务都需要互相通信,互相调用方法。   

  

  服务消费者是微架构中发起通信调用方法的一方,提供远程方法调用的服务称为服务提供者.   

  

  为了提高系统性能,此时一般会提供多个服务器作为服务提供者.服务消费者服务提供者的过程类似于乘客上车找座位的过程。   

  

     

  

  因此,将注册中心引入微服务架构,让服务消费者能够快速找到自己需要的服务提供者。注册中心实现服务提供和服务消费的快速匹配功能。   

  

  Nacos提供了一套简单易用的特性,可以快速实现动态服务发现。   

  

  除了服务发现,在有很多服务的微服务架构系统中,配置的集中管理也很重要,因为服务很多。每次修改一个配置,可能需要跟进多个服务同步修改,然后重启这些项目,会比较麻烦。配置中心用于完成配置的统一管理,一次修改实时生效。   

  

  可以和我之前关于阿波罗配置中心的文章一起吃:分布式配置中心的阿波罗实战。   

  

  Nacos不仅可以是微服务的注册表,还可以支持配置中心。   

  

  使用Nacos的Nacos安装是Spring Cloud Alibaba,的组件之一,它支持服务的注册和发现、分布式系统的外部配置和配置的自动刷新。   

  

  是时候支持Nacos's的环境了。   

  

  本文中的Nacos安装环境:   

  

  CentOS 7.6 2C 4GJDK 1.8版本1.8选择下载当前官方推荐版本:2.0.3   

  

  解压并开始上传下载的nacos-server-2.0.3.tar.gz到服务器(服务器ip地址:192.168.242.129,记住这个IP地址,后面会和MySQL、nginx所在的服务器不一样),解压:   

  

  #解压tar-zxvf nacos-server-2.0.3.tar.gz # Start CD nacos/Binsh startup . sh-m standalone。在启动命令中,standalone代表独立模式,而不是集群模式。   

  

  要验证nacos是否成功启动,首先可以查看Nacos服务在服务器端是否成功启动:   

  

  # jps 9763 nacos-server . jar 13720 jps # PS-ef | grep nacos copy code   

  

  Nacos服务的默认端口是8848,浏览器会打开以下URL进行验证:   

  

  33、33、35/nacos 538638363637   

  

  提示:把ip地址改成你实际环境的IP地址,注意防火墙和端口开放。   

  

  

默认用户名密码:nacos/nacos

  

登录后可以看到有服务管理、配置管理等:

  

  

这样,一个Nacos服务就配置好了。

  

项目实战演示本项目 SpringCloudAlibabaTest 源代码仓库: github.com/xblzer/Spri…

  

因为Nacos既可以作为服务发现注册中心,也可以是配置中心,所以我这里也分两部分进行操作。

  

demo结构用 Maven父子工程 ,Maven父工程导入 Spring BootSpring CloudSpring Cloud Alibaba 基础依赖,各个子工程作为module依赖父工程。

  

创建Maven父工程IDE:IntelliJ IDEA创建一个普通的Maven工程,并删除IDE自动生成的文件夹和文件,只保留 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"> <modelVersion>4.0.0</modelVersion> <groupId>com.xblzer</groupId> <artifactId>SpringCloudAlibabaTest</artifactId> <version>1.0-SNAPSHOT</version> <packaging>pom</packaging> <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> </properties> <!-- 作版本仲裁 --> <dependencyManagement> <dependencies> <!-- Spring Boot --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>2.6.3</version> <type>pom</type> <scope>import</scope> </dependency> <!-- Spring Cloud --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>2021.0.1</version> <type>pom</type> <scope>import</scope> </dependency> <!-- Spring Cloud Alibaba --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>2021.0.1.0</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement></project>复制代码父工程创建OK。

  

本文所选版本是:

  

Spring Boot:2.6.3Spring Cloud:2021.0.1Spring Cloud Alibaba:2021.0.1.0Tip:本文使用的是Spring Cloud Alibaba 2021.0.1.0,该版本对应的Spring Cloud版本为2021.0.1。从 2021.0.1.0 开始,Spring Cloud Alibaba 版本将会对应 Spring Cloud 版本, 前三位为 Spring Cloud 版本,最后一位为扩展版本。

  

Spring Cloud 2021.0.1新版本使用 Spring Cloud Loadbalancer 做负载均衡,没有默认集成 Ribbon 了,在进行服务消费者开发的项目中需要引入 Loadbalancer 依赖,这一点需要注意一下。

  

Part Ⅰ:服务注册与发现服务提供者项目和创建普通Spring Boot项目一样,创建完成后,删除无用的文件,保留src和pom.xml。

  

因为是子工程,在pom中添加其父工程依赖:

  

<parent> <groupId>com.xblzer</groupId> <artifactId>SpringCloudAlibabaTest</artifactId> <version>1.0-SNAPSHOT</version> <relativePath/></parent>复制代码依赖中引入 spring-cloud-starter-alibaba-nacos-discovery :

  

<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId></dependency>复制代码然后在父工程的pom中添加子模块:

  

<modules> <module>cloud-nacos-provider</module></modules>复制代码Nacos服务提供者配置文件 application.yml :

  

server: port: 8080spring: application: name: cloud-nacos-provider cloud: nacos: discovery: server-addr: 192.168.242.129:8848management: endpoint: web: exposure: include: "*"复制代码主启动类上加 @EnableDiscoveryClient 注解。

  

然后启动 cloud-nacos-provider 项目,看Nacos后台是否注册上该服务了:

  

  

现在编写一个对外提供的接口 /test-port ,访问该接口时,返回项目的端口。写一个 Controller 就行了:

  

package com.xblzer.cloudnacosprovider.controller;import org.springframework.beans.factory.annotation.Value;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RestController;/** * @author 行百里者 * @date 2022-06-30 18:29 */@RestControllerpublic class ProviderController { @Value("${server.port}") private String serverPort; @GetMapping("/test-port") public String getServerPort() { return "Nacos Provider port:" + serverPort; }}复制代码既然是对外提供服务,一般我们会多准备几个服务提供者的服务器,已提高系统效率和备份,这里再启动一个 8081 端口的Provider服务。

  

  

启动两个Provider服务后,可以看到Nacos后台服务列表注册成功:

  

服务消费者项目创建子module的过程和前面一样,主要是配置文件和pom有些区别。

  

配置文件 application.yml :

  

server: port: 9080spring: application: name: cloud-nacos-consumer cloud: nacos: discovery: server-addr: 192.168.242.129:8848# 消费者要访问的服务提供者-这些服务提供者已注册到nacosservice-url: nacos-provider-service: http://cloud-nacos-provider复制代码前文提到过,既然是服务消费者,肯定需要去调用服务提供者提供的接口,服务提供者是多台服务器的,那么我应该去调用哪台服务(这里假设不同的端口服务部署在不同的服务器上)的接口呢?

  

使用 Spring Cloud Loadbalancer 就可以做负载均衡了,需要引入 Loadbalancer 依赖:

  

<!-- 2021.0.1版本移除了netflix ribbon 使用LoadBalancer 必须引入LoadBalancer的依赖 --><dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-loadbalancer</artifactId></dependency>复制代码引入依赖后,我们只需要在注入 RestTemplate 的时候加上 @LoadBalanced 注解即可。

  

RestTemplate是 Spring 提供的用于访问 Rest 服务的客户端,它提供了多种边界访问远程 Http 服务的方法,能够大大提高客户端的编写效率。

  

@Bean@LoadBalancedpublic RestTemplate restTemplate() { return new RestTemplate();}复制代码编写调用接口在接口中调用服务提供者的接口 /test-port :

  

@RestControllerpublic class ConsumerController { @Resource private RestTemplate restTemplate; @Value("${service-url.nacos-provider-service}") private String serviceUrl; @GetMapping("/comsume") public String consume() { return restTemplate.getForObject(serviceUrl + "/test-port", String.class); }}复制代码启动并验证是否注册到Nacos中:

  

  

访问 http://localhost:9080/consume :

  

  

多次调用该接口,返回的信息在 8080 与 8081 之间切换,可见实现了负载均衡。

  

Part Ⅱ:配置中心Nacos不仅仅可以作为注册中心来使用,同时它支持作为配置中心,我们来看一下怎么用。

  

同样创建module,引入nacos config依赖:

  

<!-- 引入Nacos config --><dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId></dependency>复制代码将该子模块添加到父工程:

  

<modules> <module>cloud-nacos-provider</module> <module>cloud-nacos-consumer</module> <module>cloud-nacos-config</module></modules>复制代码关于配置文件,需要注意的是, spring-cloud-starter-alibaba-nacos-config 模块移除了 spring-cloud-starter-bootstrap 依赖,如果想以旧版的方式使用,需要手动加上该依赖。

  

旧版使用方式:

  

有两个配置文件,一个 application.yml ,一个 bootstrap.yml ,在项目初始化时,要保证先从配置中心进行配置拉取,拉取配置之后,才能保证项目的正常启动。

  

bootstrap.yml文件内容:

  

# nacos配置server: port: 7071spring: application: name: nacos-config-client cloud: nacos: discovery: # Nacos服务注册中心地址 server-addr: 192.168.242.129:8848 config: # Nacos作为配置中心地址 server-addr: 192.168.242.129:8848 # 指定yaml格式的配置 file-extension: yaml 复制代码这里bootstrap.yml配置的内容起到两个作用:

  

让7071这个配置服务注册到Nacos中去Nacos中读取指定后缀为yaml的配置文件现在推荐使用 spring.config.import 方式引入配置,以上述 bootstrap.yml 的配置为例, spring.config.import 引入方式如下:

  

配置文件 application.yml :

  

server: port: 7071spring: application: name: cloud-nacos-config cloud: nacos: config: group: DEFAULT_GROUP server-addr: 192.168.242.129:8848 config: import: - nacos:test.yml复制代码Tip:配置文件的写法一定要注意, spring.config.import 下面的配置 nacos:test.yml 中间一定不要留空格,否则启动不成功。

  

在Nacos,需要在DEFAULT_GROUP下创建一个 test.yml 文件,这个文件名一定要和 spring.config.import 配置下的 nacos:test.yml 的yml文件名一致。

  

  

项目启动成功,访问 http://localhost:7071/info :

  

  

在Nacos配置管理里面,动态修改 config.info 的值为 I am a config info, v2 再次访问接口,将返回新值:

  

  

PS:关于旧版本Spring Boot中Nacos配置中心的配置规则首先必须配置 spring.application.name ,是因为它是构成 Nacos 配置管理 dataId 字段的一部分。

  

在 Nacos Spring Cloud 中, dataId 的完整格式如下:

  

${prefix}-${spring.profiles.active}.${file-extension}复制代码prefix 默认为 spring.application.name 的值,也可以通过配置项 spring.cloud.nacos.config.prefix 来配置。spring.profiles.active 即为当前环境对应的 profile。 注意:当 spring.profiles.active 为空时,对应的连接符 - 也将不存在,dataId 的拼接格式变成 ${prefix}.${file-extension}file-exetension 为配置内容的数据格式,可以通过配置项 spring.cloud.nacos.config.file-extension 来配置。目前只支持 properties 和 yaml 类型。Nacos集群生产环境中,Nacos的配置一般是集群模式部署,来满足高可用。

  

现在我们来想一个问题,前面我们配置的 test.yml 在一台机器的nacos上,也就是这个配置在这台服务器(192.168.242.129)的nacos内部的数据库里存储着,一旦我们改成集群部署,这些数据怎么保证一致性呢?

  

看一下Nacos集群架构图先:

  

  

前面我们操作的都是Nacos单节点,Nacos默认使用嵌入式数据库实现数据的存储,所以,如果启动多个默认配置下的Nacos节点, 数据储存存在一致性问题

  

为了解决这个问题,Nacos采用了集中存储方式来支持集群化部署,目前仅支持MySQL的存储。

  

下面我就根据这个集群架构来部署一套Nacos集群。该集群模式下,需要有nginx对Nacos做负载均衡,MySQL做存储。

  

Nacos的数据持久化Nacos默认的内部存储数据的数据库是内置的derby数据库,我们搭建集群环境的话,为了保证数据的一致性,将不再继续使用默认的derby,通过修改配置,将数据持久化到MySQL数据库。

  

  

Nacos默认Derby数据库切换到外部MySQL数据库方法前面安装的Nacos所在的服务器IP地址为 192.168.242.129 ,MySQL所在服务器在 192.168.242.112

  

第一步:将Nacos安装目录 conf 下的 nacos-mysql.sql 文件上传到MySQL所在的服务器 192.168.242.112 (以下简称112)中;

  

# 上传sql脚本文件到MySQL所在的112服务scp nacos-mysql.sql root@192.168.242.112:/usr/local/sql-scripts/复制代码

  

第二步:在MySQL服务器上,创建 nacos 数据库,导入 nacos-mysql.sql 脚本;

  

mysql> create database nacos;Query OK, 1 row affected (0.03 sec)mysql> use nacos;mysql> source /usr/local/sql-scripts/nacos-mysql.sql;mysql> show tables;+----------------------+| Tables_in_nacos |+----------------------+| config_info || config_info_aggr || config_info_beta || config_info_tag || config_tags_relation || group_capacity || his_config_info || permissions || roles || tenant_capacity || tenant_info || users |+----------------------+12 rows in set (0.00 sec)复制代码第三步:修改 conf/application.properties 文件,将其中MySQL配置的部分修改为如下内容:

  

#*************** Config Module Related Configurations ***************#### If use MySQL as datasource:spring.datasource.platform=mysql### Count of DB:db.num=1### Connect URL of DB:db.url.0=jdbc:mysql://192.168.242.112:3306/nacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTCdb.user.0=zhangsandb.password.0=Fawai@kuangtu6复制代码通过以上操作,此时仍以单机模式重启nacos,

  

cd /usr/local/nacos/binsh shutdown.shsh startup.sh -m standalone复制代码查看启动日志,启动日志输出文件: /usr/local/nacos/logs/start.out

  

  

此时,访问nacos后台,发现之前我们的配置文件 test.yml 消失了,这是因为我们切换了默认的derby存储,换成了外部存储MySQL。

  

  

重新创建一个 test.yml :

  

  

这条记录在配置的MySQL数据库中可以查到:

  

mysql> select * from config_info\G*************************** 1. row *************************** id: 1 data_id: test.yml group_id: DEFAULT_GROUP content: config: info: this is new version! md5: 57609a39c0477b74a7e5315c2acd062b gmt_create: 2022-07-07 07:22:41gmt_modified: 2022-07-07 07:22:41 src_user: NULL src_ip: 192.168.242.1 app_name: tenant_id: c_desc: NULL c_use: NULL effect: NULL type: yaml c_schema: NULL1 row in set (0.00 sec)复制代码这样就实现了Nacos数据持久化到外部存储MySQL中。

  

集群搭建-主机管理Nacos集群中各个环节( SLB、Nacos、MySQL )所需要的主机信息分配如下:

  

序号

  

IP地址(简称)

  

部署服务

  

1

  

192.168.242.112(112)

  

Nginx

  

2

  

192.168.242.112(112)

  

MySQL

  

3

  

192.168.242.129(129)

  

Nacos

  

4

  

192.168.242.130(130)

  

Nacos

  

5

  

192.168.242.131(131)

  

Nacos

  

为了方便,Nginx和MySQL就不做高可用了,Nginx和MySQL部署在 192.168.242.112 上,另外三台主机部署Nacos。

  

也可以在一台服务器上部署三个Nacos服务,通过端口来区分。

  

注意:如果你是在一台机器上用三个端口的服务来搭建nacos集群,在修改端口的时候一定要有一定的偏移量(比如三个nacos分别设置成8848/8868/8888),不要设置成8848/8849/8850这样, 因为Nacos2.0增加了9848,9849端口来进行GRPC通信,这两个端口是通过8848+1000以及8848+1001这种偏移量方式计算出来的,如果我们将集群中的第二个端口设置成8849,那么8849+1000就和第一个的8848+1001端口重合了!

  

所以我们在设置端口号的时候注意要避开,不要占用端口。

  

我这里为了模拟实际场景,我整了三台部署Nacos的虚拟机,由于在三台机器上,我可以均以默认的8848端口部署。

  

集群配置在130/131这两台虚拟机中,将 conf/application.properties 中MySQL的部分修改成一致的,然后分别修改三台机器上的 nacos/cluster.conf 文件。

  

先拷贝一份 cluster.conf :

  

cp cluster.conf.example cluster.conf复制代码然后修改 cluster.conf 内容为:

  

# ip:port192.168.242.129:8848192.168.242.130:8848192.168.242.131:8848复制代码三台Nacos服务均如此修改。

  

这样,一个Nacos集群就支棱起来了,启动nacos集群也相当的简单,直接执行 bin/starup.sh 就可以了, nacos默认的启动方式就是集群方式启动。

  

  

这时,访问 http://192.168.242.129:8848/nacos、http://192.168.242.130:8848/nacos、http://192.168.242.131:8848/nacos 均能看到nacos后台,集群节点:

  

  

Nginx做Nacos集群的SLB(负载均衡)访问Nacos集群,需要对外提供一个统一的ip地址,使用nginx做集群的负载均衡。

  

Nginx安装这里选择 tengine (阿里版的nginx),安装步骤:

  

1.上传 tengine-2.3.3.tar.gz 文件到 /usr/local/warehouse

  

2.cd /usr/local/warehouse

  

3.tar -zxvf tengine-2.3.3.tar.gz

  

4.cd tengine-2.3.3/

  

5../configure --with-stream --prefix=/usr/local/nginx

  

6.make && make install

  

安装过程可能出现的错及解决办法# 错误为:./configure: error: the HTTP rewrite module requires the PCRE library.# 安装pcre-devel解决问题yum -y install pcre-devel#还有可能出现:./configure: error: the HTTP cache module requires md5 functions from OpenSSL library# 解决办法:yum -y install openssl openssl-devel复制代码配置nacos集群代理这里,只需要对nginx做如下配置即可:

  

# 编辑nginx.conf文件vi /usr/local/nginx/conf/nginx.conf复制代码nacos代理配置:

  

stream { upstream nacos { server 192.168.242.129:8848; server 192.168.242.130:8848; server 192.168.242.131:8848; } server { listen 81; proxy_pass nacos; }}复制代码启动nginx:

  

/usr/local/nginx/sbin/nginx复制代码现在,直接访问 http://192.168.242.112:81/nacos 地址就可以访问Nacos集群了:

  

  

代码中单机Nacos切换成Nacos集群模式在前文例子 SpringCloudAlibabaTest 项目中,用到的Nacos均是单机模式下的Nacos,要切换到集群模式,只需要将IP地址换成Nginx代理的ip地址 192.168.242.112:81 即可。

  

比如,将 cloud-nacos-config 项目配置修改如下,并启动项目:

  

spring: application: name: cloud-nacos-config cloud: nacos: config: group: DEFAULT_GROUP server-addr: 192.168.242.112:81 config: import: - nacos:test.yml复制代码访问接口:

  

  

能够得到集群中配置的值!

  

Nacos客户端服务注册源码分析到这里,我们已经对Nacos的服务注册发现、配置管理等功能进行了实际操作,也体验到了它的强大。

  

我们可以跟着源码总结一下其中的一些核心点,最后能够跟着源码来做出核心流程图,当我们对核心功能的实现了解其源码后,就可能会借鉴到实际工作项目中,提升我们的编程技能和编程思想。

  

那么这些Nacos有哪些核心功能呢?他们又是怎么实现的?

  

前面搭建了真实的微服务项目环境,体验了Nacos作为服务注册、服务发现以及配置中心的功能,这些功能里面包含了一下核心知识点:

  

服务注册: Nacos Client 会通过发送REST请求的方式向 Nacos Server 注册自己的服务,提供自身的元数据,比如ip地址、端口等信息。Nacos Server接收到注册请求后,就会把这些元数据信息存储在一个 双层的内存Map 中。服务发现: 服务消费者(Nacos Client)在调用服务提供者的服务时,会发送一个REST请求给Nacos Server,获取上面注册的服务清单,并且缓存在Nacos Client本地,同时会在Nacos Client本地开启一个定时任务定时拉取服务端最新的注册表信息更新到本地缓存 。服务心跳: 在服务注册后, Nacos Client 会维护一个 定时心跳 来持续通知 Nacos Server ,说明服务一直处于可用状态,防止被剔除。 默认5s发送一次心跳。服务健康检查: Nacos Server 会开启一个 定时任务 用来检查注册服务实例的健康情况,对于 超过15s没有收到客户端心跳的实例会将它的healthy属性置为false (客户端服务发现时不会发现),如果某个 实例超过30秒没有收到心跳,直接剔除该实例 ( 被剔除的实例如果恢复发送心跳则会重新注册 )。服务同步: Nacos Server集群 之间会互相同步服务实例,用来保证服务信息的 一致性 。Nacos源码环境搭建因为前面我们的Nacos版本选择的是 2.0.3 ,所以下载源码的时候去下载对应版本的源码:

  

  

如果直接拉取 github.com/alibaba/nac… ,下载的源码是最新版2.1.0。

  

下载下来导入到Idea中,项目结构为:

  

  

启动后台管理 nacos-console 模块的启动类 Nacos.java ,如果直接启动报如下错误:

  

  

原因是 Nacos 2.0 版本使用的是protocol buffer compiler编译,这里我们下载下来后使用Maven compile ,重新编译一下就行了。

  

  

启动的时候还需要加个参数,以单机模式启动:

  

-Dnacos.standalone=true复制代码如果不加这个参数,默认以集群方式启动,这种方式启动需要修改 application.properties 中关于数据库MySQL部分的配置(保证集群数据一致性),否则启动会报错 Unable to start embedded Tomcat

  

看源码,只需要单机模式启动就行了。在Idea中添加启动参数如下:

  

  

配置好之后就可以运行测试,和启动普通的Spring Boot聚合项目一样,启动之后直接访问: http://localhost:8848/nacos, 这个时候就能看到我们以前看到的对应客户端页面了,Nacos源码启动完成。

  

  

Nacos客户端服务注册源码分析从源码级别看Nacos是如何注册实例的Nacos源码模块中有一个 nacos-client ,直接看其中测试类 NamingTest

  

@Ignorepublic class NamingTest { @Test public void testServiceList() throws Exception { // 连接nacos server信息 Properties properties = new Properties(); properties.put(PropertyKeyConst.SERVER_ADDR, "127.0.0.1:8848"); properties.put(PropertyKeyConst.USERNAME, "nacos"); properties.put(PropertyKeyConst.PASSWORD, "nacos"); //实例信息封装,包括基础信息和元数据信息 Instance instance = new Instance(); instance.setIp("1.1.1.1"); instance.setPort(800); instance.setWeight(2); Map<String, String> map = new HashMap<String, String>(); map.put("netType", "external"); map.put("version", "2.0"); instance.setMetadata(map); //通过NacosFactory获取NamingService NamingService namingService = NacosFactory.createNamingService(properties); //通过namingService注册实例 namingService.registerInstance("nacos.test.1", instance); }}复制代码这就是 客户端注册 的一个测试类,它模仿了一个真实的服务注册进Nacos的过程,包括 Nacos Server连接属性封装实例的创建实例属性的赋值注册实例 ,所以一段测试代码包含了服务注册的核心代码。

  

设置Nacos Server连接属性Nacos Server连接信息,存储在Properties当中:

  

Properties properties = new Properties();properties.put(PropertyKeyConst.SERVER_ADDR, "127.0.0.1:8848");properties.put(PropertyKeyConst.USERNAME, "nacos");properties.put(PropertyKeyConst.PASSWORD, "nacos");复制代码这些信息包括:

  

SERVER_ADDR :Nacos服务器地址,属性的PropertyKeyConst key为serverAddrUSERNAME :连接Nacos服务的用户名,PropertyKeyConst key为username,默认值为nacosPASSWORD :连接Nacos服务的密码,PropertyKeyConst key为passwod,默认值为nacos服务实例封装注册实例信息用 Instance 对象承载,注册的实例信息又分两部分: 实例基础信息元数据

  

  

基础信息字段说明:

  

instanceId :实例的唯一ID;ip :实例IP,提供给消费者进行通信的地址;port : 端口,提供给消费者访问的端口;weight :权重,当前实例的权重,浮点类型(默认1.0D);healthy :健康状况,默认true;enabled :实例是否准备好接收请求,默认true;ephemeral :实例是否为瞬时的,默认为true;clusterName :实例所属的集群名称;serviceName :实例的服务信息。元数据:

  

Map<String, String> map = new HashMap<String, String>();map.put("netType", "external");map.put("version", "2.0");instance.setMetadata(map);复制代码元数据 Metadata 封装在HashMap中,这里只设置了 netType 和 version 两个数据,未设置的元数据通过Instance设置的默认值可以get到。

  

Instance获取元数据-心跳时间、心跳超时时间、实例IP被剔除的时间、实例ID生成器的方法:

  

/** * 获取实例心跳间隙,默认为5s,也就是默认5秒进行一次心跳 * @return 实例心跳间隙 */ public long getInstanceHeartBeatInterval() { return getMetaDataByKeyWithDefault(PreservedMetadataKeys.HEART_BEAT_INTERVAL, Constants.DEFAULT_HEART_BEAT_INTERVAL); } /** * 获取心跳超时时间,默认为15s,也就是默认15秒收不到心跳,实例将会标记为不健康 * @return 实例心跳超时时间 */ public long getInstanceHeartBeatTimeOut() { return getMetaDataByKeyWithDefault(PreservedMetadataKeys.HEART_BEAT_TIMEOUT, Constants.DEFAULT_HEART_BEAT_TIMEOUT); } /** * 获取实例IP被删除的时间,默认为30s,也就是30秒收不到心跳,实例将会被移除 * @return 实例IP被删除的时间间隔 */ public long getIpDeleteTimeout() { return getMetaDataByKeyWithDefault(PreservedMetadataKeys.IP_DELETE_TIMEOUT, Constants.DEFAULT_IP_DELETE_TIMEOUT); } /** * 实例ID生成器,默认为simple * @return 实例ID生成器 */ public String getInstanceIdGenerator() { return getMetaDataByKeyWithDefault(PreservedMetadataKeys.INSTANCE_ID_GENERATOR, Constants.DEFAULT_INSTANCE_ID_GENERATOR); }复制代码

  

Nacos提供的元数据key:

  

public class PreservedMetadataKeys { //心跳超时的key public static final String HEART_BEAT_TIMEOUT = "preserved.heart.beat.timeout"; //实例IP被删除的key public static final String IP_DELETE_TIMEOUT = "preserved.ip.delete.timeout"; //心跳间隙的key public static final String HEART_BEAT_INTERVAL = "preserved.heart.beat.interval"; //实例ID生成器key public static final String INSTANCE_ID_GENERATOR = "preserved.instance.id.generator";}复制代码元数据key对应的默认值:

  

package com.alibaba.nacos.api.common;import java.util.concurrent.TimeUnit;/** * Constants. * * @author Nacos */public class Constants { //...略 //心跳超时,默认15s public static final long DEFAULT_HEART_BEAT_TIMEOUT = TimeUnit.SECONDS.toMillis(15); //ip剔除时间,默认30s未收到心跳则剔除实例 public static final long DEFAULT_IP_DELETE_TIMEOUT = TimeUnit.SECONDS.toMillis(30); //心跳间隔。默认5s public static final long DEFAULT_HEART_BEAT_INTERVAL = TimeUnit.SECONDS.toMillis(5); //实例ID生成器,默认为simple public static final String DEFAULT_INSTANCE_ID_GENERATOR = "simple"; //...略}复制代码这些都是Nacos默认提供的值,也就是当前实例注册时会告诉Nacos Server说:我的心跳间隙、心跳超时等对应的值是多少,你按照这个值来判断我这个实例是否健康。

  

此时,注册实例的时候,该封装什么参数,我们心里应该有点数了。

  

通过NamingService接口进行实例注册NamingService接口是Nacos命名服务对外提供的一个统一接口,其提供的方法丰富:

  

  

主要包括如下方法:

  

void registerInstance(...): 注册服务实例void deregisterInstance(...): 注销服务实例List getAllInstances(...): 获取服务实例列表List selectInstances(...): 查询健康服务实例List selectInstances(....List clusters....): 查询集群中健康的服务实例Instance selectOneHealthyInstance(...): 使用负载均衡策略选择一个健康的服务实例void subscribe(...): 服务订阅void unsubscribe(...): 取消服务订阅List getSubscribeServices(): 获取所有订阅的服务String getServerStatus(): 获取Nacos服务的状态void shutDown(): 关闭服务这些方法均提供了重载方法,应用于不同场景和不同类型实例或服务的筛选。

  

回到服务注册测试类中的第3步,通过NamingService接口注册实例:

  

//通过NacosFactory获取NamingServiceNamingService namingService = NacosFactory.createNamingService(properties);//通过namingService注册实例namingService.registerInstance("nacos.test.1", instance);复制代码再来看一下 NacosFactory 创建namingService的具体实现方法:

  

/** * 创建NamingService实例 * @param properties 连接nacos server的属性 */public static NamingService createNamingService(Properties properties) throws NacosException { try { //通过反射机制来实例化NamingService Class<?> driverImplClass = Class.forName("com.alibaba.nacos.client.naming.NacosNamingService"); Constructor constructor = driverImplClass.getConstructor(Properties.class); return (NamingService) constructor.newInstance(properties); } catch (Throwable e) { throw new NacosException(NacosException.CLIENT_INVALID_PARAM, e); }}复制代码通过反射机制来实例化一个NamingService,具体的实现类是 com.alibaba.nacos.client.naming.NacosNamingService

  

NacosNamingService实现注册服务实例注册代码中:

  

namingService.registerInstance("nacos.test.1", instance);复制代码前面已经分析到,通过反射调用的是 NacosNamingServiceregisterInstance 方法,传递了两个参数:服务名和实例对象。具体方法在 NacosNamingService 类中如下:

  

//服务注册,传递参数服务名称和实例对象@Overridepublic void registerInstance(String serviceName, Instance instance) throws NacosException { registerInstance(serviceName, Constants.DEFAULT_GROUP, instance);}复制代码该方法完成了对实例对象的分组,即将对象分配到默认分组中 DEFAULT_GROUP

  

紧接着调用的方法 registerInstance(serviceName, Constants.DEFAULT_GROUP, instance) :

  

//注册服务//参数:服务名称,实例分组(默认DEFAULT_GROUP),实例对象@Overridepublic void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException { //检查实例是否合法:通过服务心跳,如果不合法直接抛出异常 NamingUtils.checkInstanceIsLegal(instance); //通过NamingClientProxy代理来执行服务注册 clientProxy.registerService(serviceName, groupName, instance);}复制代码这个 registerInstance 方法干了两件事:

  

1: checkInstanceIsLegal(instance) 检查传入的实例是否合法,通过检查心跳时间设置的对不对来判断,其源码如下

  

//类NamingUtils工具类下public static void checkInstanceIsLegal(Instance instance) throws NacosException { //心跳超时时间必须小于心跳间隔时间 //IP剔除的检查时间必须小于心跳间隔时间 if (instance.getInstanceHeartBeatTimeOut() < instance.getInstanceHeartBeatInterval() || instance.getIpDeleteTimeout() < instance.getInstanceHeartBeatInterval()) { throw new NacosException(NacosException.INVALID_PARAM, "Instance 'heart beat interval' must less than 'heart beat timeout' and 'ip delete timeout'."); }}复制代码2:通过 NamingClientProxy 代理来执行服务注册。

  

进入 clientProxy.registerService(serviceName, groupName, instance) 方法,发现有多个实现类(如下图),那么这里对应的是哪个实现类呢?

  

  

我们继续阅读NacosNamingService源码,找到 clientProxy 属性,通过构造方法可以知道 NamingClientProxy 这个代理接口的具体实现类是 NamingClientProxyDelegate

  

  

NamingClientProxyDelegate中实现实例注册的方法从上面分析得知,实例注册的方法最终由 NamingClientProxyDelegate 中的 registerService(String serviceName, String groupName, Instance instance) 来实现,其方法为:

  

/** * 注册服务 * @param serviceName 服务名称 * @param groupName 服务所在组 * @param instance 注册的实例 */@Overridepublic void registerService(String serviceName, String groupName, Instance instance) throws NacosException { //这一句话干了两件事: //1.getExecuteClientProxy(instance) 判断当前实例是否为瞬时对象,如果是瞬时对象,则返回grpcClientProxy(NamingGrpcClientProxy),否则返回httpClientProxy(NamingHttpClientProxy) //2.registerService(serviceName, groupName, instance) 根据第1步返回的代理类型,执行相应的注册请求 getExecuteClientProxy(instance).registerService(serviceName, groupName, instance);}//...//返回代理类型private NamingClientProxy getExecuteClientProxy(Instance instance) { //如果是瞬时对象,返回grpc协议的代理,否则返回http协议的代理 return instance.isEphemeral() ? grpcClientProxy : httpClientProxy;}复制代码该方法的实现只有一句话: getExecuteClientProxy(instance).registerService(serviceName, groupName, instance); 这句话执行了2个动作:

  

1. getExecuteClientProxy(instance):判断传入的实例对象是否为瞬时对象,如果是瞬时对象,则返回 grpcClientProxy(NamingGrpcClientProxy) grpc协议的请求代理,否则返回 httpClientProxy(NamingHttpClientProxy) http协议的请求代理;

  

2. registerService(serviceName, groupName, instance):根据返回的clientProxy类型执行相应的注册实例请求。

  

**瞬时对象 ** 就是对象在实例化后还没有放到持久化储存中,还在内存中的对象。而这里要注册的实例默认就是瞬时对象,因此在 Nacos(2.0版本) 中默认就是采用gRPC(Google开发的高性能RPC框架)协议与Nacos服务进行交互。下面我们就看 NamingGrpcClientProxy 中注册服务的实现方法。

  

NamingGrpcClientProxy中服务注册的实现方法在该类中,实现服务注册的方法源码:

  

/** * 服务注册 * @param serviceName 服务名称 * @param groupName 服务所在组 * @param instance 注册的实例对象 */@Overridepublic void registerService(String serviceName, String groupName, Instance instance) throws NacosException { NAMING_LOGGER.info(" {} registering service {} with instance {}", namespaceId, serviceName, instance); //缓存当前实例,用于将来恢复 redoService.cacheInstanceForRedo(serviceName, groupName, instance); //基于gRPC进行服务的调用 doRegisterService(serviceName, groupName, instance);}复制代码该方法一是要将当前实例缓存起来用于恢复,二是执行基于gRPC协议的请求注册。

  

缓存当前实例的具体实现:

  

public void cacheInstanceForRedo(String serviceName, String groupName, Instance instance) { //将Instance实例缓存到ConcurrentMap中 //缓存实例的key值,格式为 groupName@@serviceName String key = NamingUtils.getGroupedName(serviceName, groupName); //缓存实例的value值,就是封装的instance实例 InstanceRedoData redoData = InstanceRedoData.build(serviceName, groupName, instance); synchronized (registeredInstances) { //registeredInstances是一个 ConcurrentMap<String, InstanceRedoData>,key是NamingUtils.getGroupedName生成的key,value是封装的实例信息 registeredInstances.put(key, redoData); }}复制代码

  

基于gRPC协议的请求注册具体实现:

  

//NamingGrpcClientProxy.javapublic void doRegisterService(String serviceName, String groupName, Instance instance) throws NacosException { InstanceRequest request = new InstanceRequest(namespaceId, serviceName, groupName, NamingRemoteConstants.REGISTER_INSTANCE, instance); requestToServer(request, Response.class); redoService.instanceRegistered(serviceName, groupName);}复制代码//NamingGrpcRedoService.javapublic void instanceRegistered(String serviceName, String groupName) { String key = NamingUtils.getGroupedName(serviceName, groupName); synchronized (registeredInstances) { InstanceRedoData redoData = registeredInstances.get(key); if (null != redoData) { redoData.setRegistered(true); } }}复制代码综上分析,Nacos的服务注册流程:

  

  

实际微服务项目中是如何进行服务注册的?以前文创建的 cloud_nacos_provider 项目为例,引入了 spring-cloud-starter-alibaba-nacos-discovery 这个包,先来看一下这个jar的结构:

  

  

Spring Boot通过读取 META-INF/spring.factories 里面的监听器类来做相应的动作,看一下客户端的这个 spring.factories 文件的内容:

  

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.alibaba.cloud.nacos.discovery.NacosDiscoveryAutoConfiguration,\ com.alibaba.cloud.nacos.endpoint.NacosDiscoveryEndpointAutoConfiguration,\ com.alibaba.cloud.nacos.registry.NacosServiceRegistryAutoConfiguration,\ com.alibaba.cloud.nacos.discovery.NacosDiscoveryClientConfiguration,\ com.alibaba.cloud.nacos.discovery.reactive.NacosReactiveDiscoveryClientConfiguration,\ com.alibaba.cloud.nacos.discovery.configclient.NacosConfigServerAutoConfiguration,\ com.alibaba.cloud.nacos.loadbalancer.LoadBalancerNacosAutoConfiguration,\ com.alibaba.cloud.nacos.NacosServiceAutoConfigurationorg.springframework.cloud.bootstrap.BootstrapConfiguration=\ com.alibaba.cloud.nacos.discovery.configclient.NacosDiscoveryClientConfigServiceBootstrapConfigurationorg.springframework.context.ApplicationListener=\ com.alibaba.cloud.nacos.discovery.logging.NacosLoggingListener复制代码很显然,Spring Boot自动装配首先找到 EnableAutoConfiguration 对应的类来进行加载,这里我们要看服务时怎么注册的,自然就能想到注册服务对应的是 com.alibaba.cloud.nacos.registry.NacosServiceRegistryAutoConfiguration 这个类。

  

该类自动注册服务的方法:

  

@Bean@ConditionalOnBean({AutoServiceRegistrationProperties.class})public NacosAutoServiceRegistration nacosAutoServiceRegistration(NacosServiceRegistry registry, AutoServiceRegistrationProperties autoServiceRegistrationProperties, NacosRegistration registration) { //实例化一个NacosAutoServiceRegistration return new NacosAutoServiceRegistration(registry, autoServiceRegistrationProperties, registration);}复制代码这里实例化了一个 NacosAutoServiceRegistration 类,它就是实例注册的核心:

  

protected void register() { if (!this.registration.getNacosDiscoveryProperties().isRegisterEnabled()) { log.debug("Registration disabled."); } else { if (this.registration.getPort() < 0) { this.registration.setPort(this.getPort().get()); }//调用父类的register super.register(); }}复制代码那么NacosAutoServiceRegistration的父类是哪个呢?来看一下它的关系图:

  

  

也就是说, NacosAutoServiceRegistration 继承了 AbstractAutoServiceRegistrationAbstractAutoServiceRegistration 实现了监听接口 ApplicationListener ,一般情况下,根据经验,该类型的监听类,都会实现 onApplicationEvent 这种方法,我们来看源码验证一下:

  

public abstract class AbstractAutoServiceRegistration<R extends Registration> implements AutoServiceRegistration, ApplicationContextAware, ApplicationListener<WebServerInitializedEvent> { //...略 //实现监听类的方法 public void onApplicationEvent(WebServerInitializedEvent event) { this.bind(event); }//具体实现 public void bind(WebServerInitializedEvent event) { ApplicationContext context = event.getApplicationContext(); if (!(context instanceof ConfigurableWebServerApplicationContext) || !"management".equals(((ConfigurableWebServerApplicationContext)context).getServerNamespace())) { this.port.compareAndSet(0, event.getWebServer().getPort()); //启动 this.start(); } } public void start() { if (!this.isEnabled()) { if (logger.isDebugEnabled()) { logger.debug("Discovery Lifecycle disabled. Not starting"); } } else { if (!this.running.get()) { this.context.publishEvent(new InstancePreRegisteredEvent(this, this.getRegistration())); //调用注册的方法 this.register(); if (this.shouldRegisterManagement()) { this.registerManagement(); } this.context.publishEvent(new InstanceRegisteredEvent(this, this.getConfiguration())); this.running.compareAndSet(false, true); } } } //...略}复制代码也就是说,项目启动的时候就会触发该类,然后 bind() 调用 start() 然后调用 register() 方法。在 register() 方法处打个断点,debug一下:

  

  

可以看到,配置文件中的相关属性被放到实例信息中了。没有配置的,nacos会给默认值,比如分组的默认值就是 DEFAULT_GROUP 等。

  

那么Nacos客户端将什么信息传递给服务器,我们就明了了,比如nacos server的ip地址、用户名,密码等,还有实例信息比如实例的ip、端口、权重等,实例信息还包括元数据信息(metaData)。

  

接着往下看,调用的register方法:

  

protected void register() { //调用NacosServiceRegistry的register方法 this.serviceRegistry.register(this.getRegistration());}复制代码在 NacosServiceRegistry 中:

  

public void register(Registration registration) { if (StringUtils.isEmpty(registration.getServiceId())) { log.warn("No service to register for nacos client..."); } else { //实例化NamingService NamingService namingService = this.namingService(); //服务id、组信息 String serviceId = registration.getServiceId(); String group = this.nacosDiscoveryProperties.getGroup(); //实例信息封装 Instance instance = this.getNacosInstanceFromRegistration(registration); try { //注册实例 namingService.registerInstance(serviceId, group, instance); log.info("nacos registry, {} {} {}:{} register finished", new Object<>{group, serviceId, instance.getIp(), instance.getPort()}); } catch (Exception var7) { if (this.nacosDiscoveryProperties.isFailFast()) { log.error("nacos registry, {} register failed...{},", new Object<>{serviceId, registration.toString(), var7}); ReflectionUtils.rethrowRuntimeException(var7); } else { log.warn("Failfast is false. {} register failed...{},", new Object<>{serviceId, registration.toString(), var7}); } } }}复制代码注册实例调用的是NamingService的实现类 NacosNamingServiceregisterInstance 方法:

  

public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException { //检查服务实例设置的心跳时间是否合法 NamingUtils.checkInstanceIsLegal(instance); String groupedServiceName = NamingUtils.getGroupedName(serviceName, groupName); if (instance.isEphemeral()) { BeatInfo beatInfo = this.beatReactor.buildBeatInfo(groupedServiceName, instance); this.beatReactor.addBeatInfo(groupedServiceName, beatInfo); }//服务注册 this.serverProxy.registerService(groupedServiceName, groupName, instance);}复制代码这里就和前面直接从源码看服务的注册过程连接上了,先检查实例的心跳时间,然后调用gPRC协议的代理进行服务注册:

  

  

最终调用发送请求 /nacos/v1/ns/instance 实现注册。

  

  

Nacos服务注册流程总结

  

注册步骤小结:

  

读取Spring Boot装载配置文件 spring.factories ,找到启动类 NacosAutoServiceRegistration ;NacosAutoServiceRegistration继承 AbstractAutoServiceRegistration ,它实现 ApplicationListener 接口;实现ApplicationListener接口的 onApplicationEvent 方法,该方法调用 bind() ,然后调用 start() 方法;start()方法中调用register(),该方法调用 NacosServiceRegistry 的register方法;NacosServiceRegistry的register方法内部调用 NacosNamingServiceregisterInstance 方法;根据实例的瞬时状态选择不同的proxy执行注册,默认是 gRPC 协议的 NamingGrpcClientProxy 执行注册;完成实例注册(POST请求 /nacos/v1/ns/instance )。最后本文主要内容是针对Spring Cloud Alibaba组件之注册中心Nacos的介绍,从安装使用到项目实战,最后分析了一波客户端注册服务的源码。

  

后续将继续分享Spring Cloud Alibaba的其他功能组件的操作,如 SentinelSeata 等,或许还会继续分享一些核心功能的源码,请关注我吧,方便上车不迷路。

  

本次导航结束。

相关文章