官方文档: https://nacos.io/zh-cn/docs/what-is-nacos.html
Nacos 致力于帮助您发现、配置和管理微服务。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据及流量管理。
Nacos的关键特性包括:
NamingService: 命名服务,注册中心核心接口
ConfigService:配置服务,配置中心核心接口
OpenAPI文档:https://nacos.io/zh-cn/docs/open-api.html
nacos版本: v1.1.4 升级到v1.4.1
你可以通过源码和发行包两种方式来获取 Nacos。
下载源码编译:
源码下载地址:https://github.com/alibaba/nacos/ | 可以用迅雷下载
cd nacos/
mvn -Prelease-nacos clean install -U
下载安装包:
下载地址:https://github.com/alibaba/Nacos/releases
当前推荐的稳定版本是2.0.3。
这次学习,我们暂时使用v1.4.1版本…
下载好nacos-server-1.4.1.tar.gz之后,我们将其上传到linux服务器上。
官方文档: https://nacos.io/zh-cn/docs/deployment.html
解压:
tar -axvf nacos-server-1.4.1.tar.gz
sh ./bin/startup.sh -m standalone
ps -ef | grep nacos
在nacos的启动脚本startup.sh中可以看到,默认的启动方式为集群方式。
我们可以修改默认启动方式:
访问nocas的管理端:http://192.168.131.172:8848/nacos ,默认的用户名密码是 nocas/nocas 。
成功启动!成功访问!很棒!!!
官网文档: https://nacos.io/zh-cn/docs/cluster-mode-quick-start.html
集群部署架构图:
1、单机搭建伪集群,复制nacos安装包,修改为nacos8849,nacos8850,nacos8851
2、创建mysql数据库,sql文件位置:conf\nacos-mysql.sql
注意,nacos1.4.1版本搭配mysql5.x版本的时候,启动总是报错说数据库连接错误!后来换成mysql8.x之后成功解决这个错误!!!
注意: 使用内置数据源时,无需进行任何配置。使用外置数据源,生产使用建议至少主备模式,或者采用高可用数据库。
我们此时是要使用外部数据源,所以要配置。我们创建一个名为nacos的数据库,并执行一下sql脚本!
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/******************************************/
/* 数据库全名 = nacos_config */
/* 表名称 = config_info */
/******************************************/
CREATE TABLE `config_info` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
`data_id` varchar(255) NOT NULL COMMENT 'data_id',
`group_id` varchar(255) DEFAULT NULL,
`content` longtext NOT NULL COMMENT 'content',
`md5` varchar(32) DEFAULT NULL COMMENT 'md5',
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
`src_user` text COMMENT 'source user',
`src_ip` varchar(50) DEFAULT NULL COMMENT 'source ip',
`app_name` varchar(128) DEFAULT NULL,
`tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段',
`c_desc` varchar(256) DEFAULT NULL,
`c_use` varchar(64) DEFAULT NULL,
`effect` varchar(64) DEFAULT NULL,
`type` varchar(64) DEFAULT NULL,
`c_schema` text,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_configinfo_datagrouptenant` (`data_id`,`group_id`,`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info';
/******************************************/
/* 数据库全名 = nacos_config */
/* 表名称 = config_info_aggr */
/******************************************/
CREATE TABLE `config_info_aggr` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
`data_id` varchar(255) NOT NULL COMMENT 'data_id',
`group_id` varchar(255) NOT NULL COMMENT 'group_id',
`datum_id` varchar(255) NOT NULL COMMENT 'datum_id',
`content` longtext NOT NULL COMMENT '内容',
`gmt_modified` datetime NOT NULL COMMENT '修改时间',
`app_name` varchar(128) DEFAULT NULL,
`tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_configinfoaggr_datagrouptenantdatum` (`data_id`,`group_id`,`tenant_id`,`datum_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='增加租户字段';
/******************************************/
/* 数据库全名 = nacos_config */
/* 表名称 = config_info_beta */
/******************************************/
CREATE TABLE `config_info_beta` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
`data_id` varchar(255) NOT NULL COMMENT 'data_id',
`group_id` varchar(128) NOT NULL COMMENT 'group_id',
`app_name` varchar(128) DEFAULT NULL COMMENT 'app_name',
`content` longtext NOT NULL COMMENT 'content',
`beta_ips` varchar(1024) DEFAULT NULL COMMENT 'betaIps',
`md5` varchar(32) DEFAULT NULL COMMENT 'md5',
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
`src_user` text COMMENT 'source user',
`src_ip` varchar(50) DEFAULT NULL COMMENT 'source ip',
`tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_configinfobeta_datagrouptenant` (`data_id`,`group_id`,`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info_beta';
/******************************************/
/* 数据库全名 = nacos_config */
/* 表名称 = config_info_tag */
/******************************************/
CREATE TABLE `config_info_tag` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
`data_id` varchar(255) NOT NULL COMMENT 'data_id',
`group_id` varchar(128) NOT NULL COMMENT 'group_id',
`tenant_id` varchar(128) DEFAULT '' COMMENT 'tenant_id',
`tag_id` varchar(128) NOT NULL COMMENT 'tag_id',
`app_name` varchar(128) DEFAULT NULL COMMENT 'app_name',
`content` longtext NOT NULL COMMENT 'content',
`md5` varchar(32) DEFAULT NULL COMMENT 'md5',
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
`src_user` text COMMENT 'source user',
`src_ip` varchar(50) DEFAULT NULL COMMENT 'source ip',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_configinfotag_datagrouptenanttag` (`data_id`,`group_id`,`tenant_id`,`tag_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info_tag';
/******************************************/
/* 数据库全名 = nacos_config */
/* 表名称 = config_tags_relation */
/******************************************/
CREATE TABLE `config_tags_relation` (
`id` bigint(20) NOT NULL COMMENT 'id',
`tag_name` varchar(128) NOT NULL COMMENT 'tag_name',
`tag_type` varchar(64) DEFAULT NULL COMMENT 'tag_type',
`data_id` varchar(255) NOT NULL COMMENT 'data_id',
`group_id` varchar(128) NOT NULL COMMENT 'group_id',
`tenant_id` varchar(128) DEFAULT '' COMMENT 'tenant_id',
`nid` bigint(20) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`nid`),
UNIQUE KEY `uk_configtagrelation_configidtag` (`id`,`tag_name`,`tag_type`),
KEY `idx_tenant_id` (`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_tag_relation';
/******************************************/
/* 数据库全名 = nacos_config */
/* 表名称 = group_capacity */
/******************************************/
CREATE TABLE `group_capacity` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`group_id` varchar(128) NOT NULL DEFAULT '' COMMENT 'Group ID,空字符表示整个集群',
`quota` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '配额,0表示使用默认值',
`usage` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '使用量',
`max_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个配置大小上限,单位为字节,0表示使用默认值',
`max_aggr_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '聚合子配置最大个数,,0表示使用默认值',
`max_aggr_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个聚合数据的子配置大小上限,单位为字节,0表示使用默认值',
`max_history_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '最大变更历史数量',
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_group_id` (`group_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='集群、各Group容量信息表';
/******************************************/
/* 数据库全名 = nacos_config */
/* 表名称 = his_config_info */
/******************************************/
CREATE TABLE `his_config_info` (
`id` bigint(64) unsigned NOT NULL,
`nid` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`data_id` varchar(255) NOT NULL,
`group_id` varchar(128) NOT NULL,
`app_name` varchar(128) DEFAULT NULL COMMENT 'app_name',
`content` longtext NOT NULL,
`md5` varchar(32) DEFAULT NULL,
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`src_user` text,
`src_ip` varchar(50) DEFAULT NULL,
`op_type` char(10) DEFAULT NULL,
`tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段',
PRIMARY KEY (`nid`),
KEY `idx_gmt_create` (`gmt_create`),
KEY `idx_gmt_modified` (`gmt_modified`),
KEY `idx_did` (`data_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='多租户改造';
/******************************************/
/* 数据库全名 = nacos_config */
/* 表名称 = tenant_capacity */
/******************************************/
CREATE TABLE `tenant_capacity` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`tenant_id` varchar(128) NOT NULL DEFAULT '' COMMENT 'Tenant ID',
`quota` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '配额,0表示使用默认值',
`usage` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '使用量',
`max_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个配置大小上限,单位为字节,0表示使用默认值',
`max_aggr_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '聚合子配置最大个数',
`max_aggr_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个聚合数据的子配置大小上限,单位为字节,0表示使用默认值',
`max_history_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '最大变更历史数量',
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_tenant_id` (`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='租户容量信息表';
CREATE TABLE `tenant_info` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
`kp` varchar(128) NOT NULL COMMENT 'kp',
`tenant_id` varchar(128) default '' COMMENT 'tenant_id',
`tenant_name` varchar(128) default '' COMMENT 'tenant_name',
`tenant_desc` varchar(256) DEFAULT NULL COMMENT 'tenant_desc',
`create_source` varchar(32) DEFAULT NULL COMMENT 'create_source',
`gmt_create` bigint(20) NOT NULL COMMENT '创建时间',
`gmt_modified` bigint(20) NOT NULL COMMENT '修改时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_tenant_info_kptenantid` (`kp`,`tenant_id`),
KEY `idx_tenant_id` (`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='tenant_info';
CREATE TABLE `users` (
`username` varchar(50) NOT NULL PRIMARY KEY,
`password` varchar(500) NOT NULL,
`enabled` boolean NOT NULL
);
CREATE TABLE `roles` (
`username` varchar(50) NOT NULL,
`role` varchar(50) NOT NULL,
UNIQUE INDEX `idx_user_role` (`username` ASC, `role` ASC) USING BTREE
);
CREATE TABLE `permissions` (
`role` varchar(50) NOT NULL,
`resource` varchar(255) NOT NULL,
`action` varchar(8) NOT NULL,
UNIQUE INDEX `uk_role_permission` (`role`,`resource`,`action`) USING BTREE
);
INSERT INTO users (username, password, enabled) VALUES ('nacos', '$2a$10$EuWPZHzz32dJN7jexM34MOeYirDdFAZm2kuWj7VEOJhhZkDrxfvUu', TRUE);
INSERT INTO roles (username, role) VALUES ('nacos', 'ROLE_ADMIN');
在数据库创建一个名为nacos数据库并执行以上sql脚本得到结果如下:
3、以nacos8849为例,进入nacos8849目录:
3.1)修改conf\application.properties的配置,使用外置数据源
#修改端口号
server.port=8849
#使用外置mysql数据源
spring.datasource.platform=mysql
### Count of DB:
db.num=1
### Connect URL of DB:
db.url.0=jdbc:mysql://192.168.131.172:3306/nacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC
db.user.0=root
db.password.0=123456
注意填写正确的数据库连接用户名和密码。我的密码是123456。
3.2)将conf\cluster.conf.example文件copy一份改为cluster.conf,并添加节点配置。请每行配置成ip:port。(请配置3个或3个以上节点)
cp ./cluster.conf.example ./cluster.conf
vim cluster.conf
# 192.168.131.172:8849 注意,本服务的IP:端口不用自己配置,启动的时候刽自动生成,只需要配置其他集群节点的信息!
192.168.131.172:8850
192.168.131.172:8851
nacos8850,nacos8851 按同样的方式配置(端口分别改为8850,8851)。
4、修改启动脚本(bin\startup.sh)的jvm参数(nacos8850,nacos8851 按同样的方式配置JVM参数)
注意,我们此时是集群模式,应该修改else分支下的JVM参数!!!
5、别启动nacos8849,nacos8850,nacos8851
使用内置数据源
sh startup.sh -p embedded
使用外置数据源
sh startup.sh
以nacos8849为例,进入nacos8849目录,启动nacos
bin/startup.sh
然后分别启动nacos8850,nacos8851服务。
然后我们再来访问http://192.168.131.172:8849/nacos,登录后查看集群信息:
可以看到,集群已经成功启动!!!
注意,nacos默认启动方式就是集群模式!
修改/conf/nginx.conf配置文件,添加反向代理配置:
upstream nacoscluster {
server 192.168.131.172:8849;
server 192.168.131.172:8850;
server 192.168.131.172:8851;
}
server {
listen 8847;
server_name 192.168.131.172;
location /nacos/ {
proxy_pass http://nacoscluster/nacos/;
}
}
访问: http://192.168.131.172:8847/nacos
即,此时我们客户端要使用nacos集群服务的时候,只需要配置这个nginx代理的地值http://192.168.131.172:8847地址即可,并且nginx端已经帮我们处理好了负载均衡,默认为轮训算法,可以配置权重,一致hash等其他算法。
server:
port: 8045
spring:
application:
name: mall-order
# 配置nacos注册中心地址
cloud:
nacos:
discovery:
server-addr: 192.168.131.172:8847
1、启动nginx(记得nginx中配置好nacos反向代理)
# 默认编译的nginx路径为这个,也可以使用whereid nginx 查看地址
/usr/local/nginx/sbin/nginx
2、分别启动nacos8849,nacos8850,nacos8851服务
# 分别在集群服务的bin目录下启动
sh startup.sh
3、使用nginx中配置的nacos地址在浏览器中访问测试集群是否成功:
http://192.168.131.172:8847/nacos
官方监控文档地址:https://nacos.io/zh-cn/docs/monitor-guide.html
Nacos 0.8.0版本完善了监控系统,支持通过暴露metrics数据接入第三方监控系统监控Nacos运行状态,目前支持prometheus、elastic search和influxdb,下面结合prometheus和grafana如何监控Nacos,官网grafana监控页面。与elastic search和influxdb结合可自己查找相关资料。
下面我们开始配置。(为了简单测试,这里我们暂时使用nacos单机模式实战吧… 妥协了)
management.endpoints.web.exposure.include=*
注:上面的这个配置原来是注释掉的,现在去掉注释,放开。(集群中每个都要修改)
访问{ip}:8848/nacos/actuator/prometheus,看是否能访问到metrics数据。
下载你想安装的prometheus版本,地址为: https://prometheus.io/download/
我们使用linux Centos7,所以下载linux版本:
上传后解压prometheus压缩包:
tar -zxvf prometheus-2.31.1.linux-amd64.tar.gz
修改配置文件prometheus.yml采集Nacos metrics数据:
添加如下配置:
- job_name: "nacos"
metrics_path: '/nacos/actuator/prometheus'
static_configs:
# 这里配置nacos服务地址,可以看到这里可以配置一个集合,即集群信息可以配置在这里
# - targets: ['192.168.131.172:8849','192.168.131.172:8850','192.168.131.172:8851']
# 如果是单机模式,集合中只配置一个元素即可
- targets: ['192.168.131.172:8848']
注意yml中空格不要多写,格式一定要严格对齐!否则启动可能会报错说配置文件出错!
启动prometheus服务:
./prometheus --config.file="prometheus.yml"
通过访问http://{ip}:9090/graph可以看到prometheus的采集数据,在搜索栏搜索nacos_monitor可以搜索到Nacos数据说明采集数据成功。
查看nacos(集群)状态:
配制采集nacos metrics数据:
如下图,进入Graph这个tab后,输入nacos,选中nacos_monitor :
点"Execute",有数据显示,就说明采集到数据成功:
可以看到,此时数据已经采集成功。但是这样可能显示还不是很直观,接下来我们再集成grafana来展示数据。
和prometheus在同一台机器上安装grafana,使用 yum 安装grafana。
下载grafana
本地下载地址:https://dl.grafana.com/oss/release/grafana-7.3.4.linux-amd64.tar.gz
linux推荐使用yum安装:
sudo yum install https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-5.2.4-1.x86_64.rpm
如果下载太慢,使用国内镜像:https://www.newbe.pro/Mirrors/Mirrors-Grafana/
注意要下载第五个,这个大小为51.8M的,其他的无法使用…
下载后解压:
ar -zxvf grafana-5.2.4.linux-amd64.tar.gz
启动服务:
# 直接启动并在直接输入日志
./bin/grafana-server
nohup ./bin/grafana-server >> nuhup.log 2>&1 &
访问grafana: http://{ip}:3000:
可以到此页面说明启动成功!默认用户名和密码都是admin。登录之后可以会要求修改密码,可以选择skip跳过:
然后会进入主页面:
配置prometheus数据源:
点"Add data source":
Name:prometheus
类型选择Prometheus:
URL填写prometheus的地址(注意:http要写上,最后地址后的’/'也最好加上,即完整地址为:http://192.168.131.172:9090/),http method选择get方法,其他不用填写,然后点"Save & Test":
接下来我们需要引入nacos监控展示模板:
github模板下载链接:https://github.com/nacos-group/nacos-template/blob/master/nacos-grafana.json
如果网速慢,可以使用gitee,gitee模板下载连接:https://gitee.com/jakhyd/nacos-template
点“Upload JSON file”上传模板文件,然后点"Load"
然后再点击"import"按钮:
然后就可以看到监控数据了:
Nacos监控分为三个模块:
配置grafana告警:
当Nacos运行出现问题时,需要grafana告警通知相关负责人。grafana支持多种告警方式,常用的有邮件,钉钉和webhook方式。
具体配置连接:https://nacos.io/zh-cn/docs/monitor-guide.html
服务注册:Nacos Client会通过发送REST请求的方式向Nacos Server注册自己的服务,提供自身的元数据,比如ip地址、端口等信息。Nacos Server接收到注册请求后,就会把这些元数据信息存储在一个双层的内存Map中。
服务心跳:在服务注册后,Nacos Client会维护一个定时心跳来持续通知Nacos Server,说明服务一直处于可用状态,防止被剔除。默认5s发送一次心跳。
服务同步:Nacos Server集群之间会互相同步服务实例,用来保证服务信息的一致性。 leader raft
服务发现:服务消费者(Nacos Client)在调用服务提供者的服务时,会发送一个REST请求给Nacos Server,获取上面注册的服务清单,并且缓存在Nacos Client本地,同时会在Nacos Client本地开启一个定时任务定时拉取服务端最新的注册表信息更新到本地缓存。
服务健康检查:Nacos Server会开启一个定时任务用来检查注册服务实例的健康情况,对于超过15s没有收到客户端心跳的实例会将它的healthy属性置为false(客户端服务发现时不会发现),如果某个实例超过30秒没有收到心跳,直接剔除该实例(被剔除的实例如果恢复发送心跳则会重新注册) 。
nacos可以以namespace来隔离服务,我们可以为不同的环境创建不同的命名空间。注意,不同命名空间中的服务是无法相互调用的。
再往下划分,还有group::serviceName、ClusterName等划分。
#配置nacos注册中心地址
cloud:
nacos:
discovery:
server-addr: 192.168.131.172:8848
namespace: 39e1e969-15f9-46d2-832d-fa052da55377
group: mall-user
cluster-name: Shanghai
总结:
NameSpace可以用来区分不同的环境,如dev、qa、prod等。所以说一套nacos可以支持多个环境的服务注册。
Group用来区分不同的微服务组,如交易微服务、仓储微服务组等。不同的微服务可以属于一个微服务组。
Service对应一个具体的服务,如订单服务、支付服务。而一个具体的服务中还可以进行区分:Cluster。Cluster可以用来描述一个服务的异地多机房部署。比如一个订单服务,可能在北京有部署,也可能在上海有部署。
围绕配置,主要有两个关联的实体,一个是配置变更历史,一个是服务标签(用于打标分类,方便索引),由 ID 关联。
1、引入依赖
父Pom中支持spring cloud&spring cloud alibaba, 引入依赖:
<?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>
<modules>
<module>mall-order</module>
<module>mall-common</module>
</modules>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>org.jihu.mall</groupId>
<artifactId>spring-cloud-alibaba-mall</artifactId>
<version>1.0-SNAPSHOT</version>
<name>spring-cloud-alibaba-mall</name>
<packaging>pom</packaging>
<description>Demo project for Spring Cloud Alibaba</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Hoxton.SR8</spring-cloud.version>
<spring-cloud-alibaba.version>2.2.5.RELEASE</spring-cloud-alibaba.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<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>
</dependencies>
</dependencyManagement>
</project>
当前项目orderpom中引入依赖:
<!-- nacos服务注册与发现 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
完整pom:
<?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>vip-spring-cloud-alibaba</artifactId>
<groupId>com.tuling.mall</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>mall-user-consumer-demo</artifactId>
<dependencies>
<dependency>
<groupId>com.tuling.mall</groupId>
<artifactId>mall-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!-- nacos服务注册与发现 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies>
</project>
2、application.yml中配置
server:
port: 8046
spring:
application:
name: mall-order
# 配置nacos注册中心地址
cloud:
nacos:
discovery:
server-addr: 192.168.131.172:8848
# 配置是否为持久化实例
#ephemeral: false
更多配置连接:https://github.com/alibaba/spring-cloud-alibaba/wiki/Nacos-discovery
3、启动springboot应用,nacos管理端界面查看是否成功注册
// 由于common模块中引入了数据库相关的,所以这里暂时排除相关自动化配置
// @EnableDiscoveryClient 该注解可以不写,springCloudAlibaba已经完成了配置
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class, DruidDataSourceAutoConfigure.class})
public class MallOrderApplication {
public static void main(String[] args) {
SpringApplication.run(MallOrderApplication.class, args);
}
}
启动成功之后,登录nacos去查看服务列表:
http://192.168.131.172:8848/nacos
4、测试是否可用
使用RestTemplate进行服务调用,可以使用微服务名称 (spring.application.name)。
我们之前使用RestTemplate调用的时候必须写ip:port或者hostname:port,此时我们已经使用nacos完成了服务mall-order的注册,思考是否可以直接使用服务名称来调用。
// 之前url;
String url = "http://localhost:8040/order/findOrderByUserId/"+id;
// 现在url:
String url = "http://mall-order/order/findOrderByUserId/"+id;
我们使用另一个服务mall-user中的如下controller方法来测试,该方法中使用RestTemplate调用mall-order服务,但是此时url地址中写的是服务名称,而并不是具体的ip:port。
@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private RestTemplate restTemplate;
@RequestMapping(value = "/findOrderByUserId/{id}")
public Result findOrderByUserId(@PathVariable("id") Integer id) {
log.info("根据userId {} 查询订单信息", id);
String url = "http://mall-order/order/findOrderByUserId/"+id;
Result result = restTemplate.getForObject(url, Result.class);
return result;
}
}
打开浏览器,我们来访问mall-user服务中的:http://localhost:8040/user/findOrderByUserId/1,以此来触发restTemplate去调用mall-order中的方法。
但是结果却是报错了:
-11-08 11:31:07.218 ERROR 14448 --- [nio-8040-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.web.client.ResourceAccessException: I/O error on GET request for "http://mall-order/order/findOrderByUserId/1": mall-order; nested exception is java.net.UnknownHostException: mall-order] with root cause
java.net.UnknownHostException: mall-order
什么意思呢?不知道的主机名mall-order。
分析,依旧是说我们此时调用的是普通的restTemplate服务,虽然我们将服务已经注册到nacos中了,但是restTemplate并不认识这个服务名称,自然就无法通过服务名称解析具体的ip:port了。
所以说,此时要有一个组件,能够去动态的从nacos中读取服务列表,并且选择其中一个获得其ip:port,并将mall-order服务名转化为ip:port真实地址之后再使用restTemplate去调用服务。
其实Spring中的httpClient中提供有一个扩展点拦截器:ClientHttpRequestInterceptor。而其中负载均衡器组件Ribbon就实现了这个拦截器:
public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {
private LoadBalancerClient loadBalancer;
private LoadBalancerRequestFactory requestFactory;
public LoadBalancerInterceptor(LoadBalancerClient loadBalancer, LoadBalancerRequestFactory requestFactory) {
this.loadBalancer = loadBalancer;
this.requestFactory = requestFactory;
}
public LoadBalancerInterceptor(LoadBalancerClient loadBalancer) {
this(loadBalancer, new LoadBalancerRequestFactory(loadBalancer));
}
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body, final ClientHttpRequestExecution execution) throws IOException {
URI originalUri = request.getURI();
// 获取url中的服务名称,比如mall-order
String serviceName = originalUri.getHost();
Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);
// 调用execute,我们跟进去看看这个方法是如何解析服务名的
return (ClientHttpResponse)this.loadBalancer.execute(serviceName, this.requestFactory.createRequest(request, body, execution));
}
}
我们进入到这个RibbonLoadBalancerClient是实现类中对应的这个方法中:
// serviceId就是serverName
public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {
return this.execute(serviceId, (LoadBalancerRequest)request, (Object)null);
}
// 继续跟进去
public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint) throws IOException {
// 根据服务名称从Map contexts = new ConcurrentHashMap();
// 中获取一个服务的instance
ILoadBalancer loadBalancer = this.getLoadBalancer(serviceId);
// 获取到具体的server
Server server = this.getServer(loadBalancer, hint);
if (server == null) {
throw new IllegalStateException("No instances available for " + serviceId);
} else {
RibbonLoadBalancerClient.RibbonServer ribbonServer = new RibbonLoadBalancerClient.RibbonServer(serviceId, server, this.isSecure(server, serviceId), this.serverIntrospector(serviceId).getMetadata(server));
return this.execute(serviceId, (ServiceInstance)ribbonServer, (LoadBalancerRequest)request);
}
}
// --------
protected Server getServer(ILoadBalancer loadBalancer, Object hint) {
// 根据不同的算法选择一个server
return loadBalancer == null ? null : loadBalancer.chooseServer(hint != null ? hint : "default");
}
Ps:Ribbon的源码我们后面会仔细研究,现在只是大概了解一下。
总结来说,就是此时我们其实可以使用Ribbon负载均衡器来实现服务选择,它默认实现了如上的功能,即拉取服务列表并通过算法选择其中一个服务来进行调用。
mall-order(localhost:8040, localhost:8041, localhost:8042) — 选择一个并替换mall-order名称—> localhost:8040 -------> 最终服务调用
所以说我们此时要给我们的RestTemplate配置这个负载均衡器,让其可以自动实现这个功能。
那么该如何配置呢?其实RestTemplate中是可以设置拦截器的:
@Configuration
public class RestTemplateConfig {
@Autowired
LoadBalancerClient loadBalancerClientl;
@Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
// 构造方法需要一个List参数, 此时LoadBalancerClient在spring容器中是已经存在的
restTemplate.setInterceptors(Collections.singletonList(new LoadBalancerInterceptor(loadBalancerClientl)));
return restTemplate;
}
}
配置好重启项目之后,我们再来测试:http://localhost:8040/user/findOrderByUserId/1
可以看到,此时已经restTemplate已经可以成功通过服务名mall-order来访问了。
优化:
在我们实际的使用中,其实可以不用手动的给restTemplate设置这个拦截器,可以直接使用Ribbon提供的注解 @LoadBalence 来完成拦截器的设置!
@Configuration
public class RestTemplateConfig {
@Autowired
LoadBalancerClient loadBalancerClientl;
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
这样也可以实现如上功能。