注册中心设计思路
注册中心有一个数据结构, 用来存储服务提供者信息, 服务名, ip, port, status(enable/disable), registerAt, lastRenewAt, …
每一个客户端服务启动时像注册中心发送心跳, 包含服务名, ip, port等, 假如发送间隔是10s
注册中心首次收到某服务时, 加入到列表中, 状态是enable, 每次收到心跳, 更新lastRenewAt和status
注册中心有定时任务, 每秒执行一次, 扫描30s内没有收到续期请求的服务, status改为disable
注册中心有定时任务, 每秒执行一次, 扫描60s内没有收到续期请求的服务, 直接删除该服务提供者的信息
阈值保护: 假如注册中心和服务提供者网络异常, 但是服务消费者和服务提供者网络通畅, 当某服务提供者全都不可用时, 也不能全部删除, 防止系统雪崩, 至少得保证 disable数/all数>=阈值
注册中心演变及设计思想
Nacos
Nacos 官网
什么是 Nacos
Nacos 概念
Nacos 架构
Nacos Open Api
GitHub Nacos
GitHub Spring Coud Alibaba
下载链接
Nacos 致力于帮助您发现、配置和管理微服务。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据及流量管理。
Nacos 的关键特性包括:
- 服务发现和服务健康监测 Service Discovery and Service Health Check
- 动态配置服务 Dynamic Configuration Management
- 动态 DNS 服务 Dynamic DNS Service
- 服务及其元数据管理 Service and MetaData Management
简单来说, Nacos包含两大功能, 注册中心和配置中心
Nacos 架构
NamingService: 命名服务,注册中心核心接口
ConfigService:配置服务,配置中心核心接口
Nacos注册中心架构
核心功能
- 服务注册: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秒没有收到心跳,直接剔除该实例(被剔除的实例如果恢复发送心跳则会重新注册)
服务注册表结构
service: 某一个服务
cluster: 某服务有多个不同地域的集群, 提供同城加速与容灾备份
服务领域模型
环境搭建
Nacos支持三种部署模式
- 单机模式 - 用于测试和单机试用。
- 集群模式 - 用于生产环境,确保高可用。
- 多集群模式 - 用于多数据中心场景。
单机模式部署
bin/startup.sh -m standalone
bin/shutdown.sh
Nacos 是一个 SpringBoot 应用, 依赖JRE环境, 有 application.properties 配置文件, 但JVM参数需要在 startup.sh 中修改
Nacos 启动日志
/application/jdk-1.8.0.202/bin/java -Xms512m -Xmx512m -Xmn256m -Dnacos.standalone=true -Dnacos.member.list= -Djava.ext.dirs=/application/jdk-1.8.0.202/jre/lib/ext:/application/jdk-1.8.0.202/lib/ext -Xloggc:/application/nacos-1.4.1/logs/nacos_gc.log -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=100M -Dloader.path=/application/nacos-1.4.1/plugins/health,/application/nacos-1.4.1/plugins/cmdb -Dnacos.home=/application/nacos-1.4.1 -jar /application/nacos-1.4.1/target/nacos-server.jar --spring.config.additional-location=file:/application/nacos-1.4.1/conf/ --logging.config=/application/nacos-1.4.1/conf/nacos-logback.xml --server.max-http-header-size=524288
,--.
,--.'|
,--,: : | Nacos 1.4.1
,`--.'`| ' : ,---. Running in stand alone mode, All function modules
| : : | | ' ,'\ .--.--. Port: 8848
: | \ | : ,--.--. ,---. / / | / / ' Pid: 1755
| : ' '; | / \ / \. ; ,. :| : /`./ Console: http://192.168.2.137:8848/nacos/index.html
' ' ;. ;.--. .-. | / / '' | |: :| : ;_
| | | \ | \__\/: . .. ' / ' | .; : \ \ `. https://nacos.io
' : | ; .' ," .--.; |' ; :__| : | `----. \
| | '`--' / / ,. |' | '.'|\ \ / / /`--' /
' : | ; : .' \ : : `----' '--'. /
; |.' | , .-./\ \ / `--'---'
'---' `--`---' `----'
2021-04-04 17:49:32,953 INFO Bean 'org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler@41294f8' of type [org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2021-04-04 17:49:32,957 INFO Bean 'methodSecurityMetadataSource' of type [org.springframework.security.access.method.DelegatingMethodSecurityMetadataSource] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2021-04-04 17:49:33,727 INFO Tomcat initialized with port(s): 8848 (http)
2021-04-04 17:49:34,452 INFO Root WebApplicationContext: initialization completed in 6567 ms
2021-04-04 17:49:40,427 INFO Initializing ExecutorService 'applicationTaskExecutor'
2021-04-04 17:49:40,677 INFO Adding welcome page: class path resource [static/index.html]
2021-04-04 17:49:41,335 INFO Creating filter chain: Ant [pattern='/**'], []
2021-04-04 17:49:41,424 INFO Creating filter chain: any request, [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@1827a871, org.springframework.security.web.context.SecurityContextPersistenceFilter@c1fca1e, org.springframework.security.web.header.HeaderWriterFilter@5bd1ceca, org.springframework.security.web.csrf.CsrfFilter@1a15b789, org.springframework.security.web.authentication.logout.LogoutFilter@73393584, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@344344fa, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@615f972, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@48e64352, org.springframework.security.web.session.SessionManagementFilter@499b2a5c, org.springframework.security.web.access.ExceptionTranslationFilter@51650883]
2021-04-04 17:49:41,593 INFO Initializing ExecutorService 'taskScheduler'
2021-04-04 17:49:41,638 INFO Exposing 2 endpoint(s) beneath base path '/actuator'
2021-04-04 17:49:41,969 INFO Tomcat started on port(s): 8848 (http) with context path '/nacos'
2021-04-04 17:49:41,972 INFO Nacos started successfully in stand alone mode. use embedded storage
2021-04-04 17:51:42,445 INFO Initializing Servlet 'dispatcherServlet'
2021-04-04 17:51:42,474 INFO Completed initialization in 29 ms
内置Tomcat端口为8848, 默认使用内置存储服务与配置数据, 也可以使用mysql来存储数据(数据库初始化文件:conf/nacos-mysql.sql), 需要修改 nacos 的 conf/application.properties
spring.datasource.platform=mysql
db.num=1
db.url.0=jdbc:mysql://192.168.2.115:3306/nacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC
db.user.0=root
db.password.0=myPassword
# 使用mysql的日志
2021-04-05 00:27:26,596 INFO Nacos started successfully in stand alone mode. use external storage
访问Nacos控制台, 账号密码默认都为 nacos(复制的密码可能不正确, 需要手输才行)
http://192.168.2.137:8848/nacos/index.html
集群模式配置与部署
集群部署要求必须使用外置数据源(只支持MySQL), 能让集群访问相同的数据
VIP: 虚拟浮动IP, 可通过 keepaliaved 实现
集群环境搭建
先准备3个使用MySQL数据源的单机环境配置(MySQL需要事先准备好)
默认使用内置存储服务与配置数据, 也可以使用mysql来存储数据(数据库初始化文件:conf/nacos-mysql.sql), 需要修改 nacos 的 conf/application.properties
spring.datasource.platform=mysql
db.num=1
db.url.0=jdbc:mysql://192.168.2.115:3306/nacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC
db.user.0=root
db.password.0=myPassword
在nacos的解压目录nacos/的conf目录下,有配置文件cluster.conf.example, 将其改名为cluster.conf,并将Nacos集群的IP:PORT配置好, 每行一个。(请配置3个或3个以上节点)
192.168.2.137:8848
192.168.2.138:8848
192.168.2.139:8848
修改JVM参数 (bin\startup.sh) (学习的时候没必要搞太大), 单机模式用的是 -Xms512m -Xmx512m -Xmn256m
, 其他模式用的是 -Xms2g -Xmx2g -Xmn1g
, 把其他模式调整到和单机模式一样
if [[ "${MODE}" == "standalone" ]]; then
JAVA_OPT="${JAVA_OPT} -Xms512m -Xmx512m -Xmn256m"
JAVA_OPT="${JAVA_OPT} -Dnacos.standalone=true"
else
if [[ "${EMBEDDED_STORAGE}" == "embedded" ]]; then
JAVA_OPT="${JAVA_OPT} -DembeddedStorage=true"
fi
JAVA_OPT="${JAVA_OPT} -server -Xms2g -Xmx2g -Xmn1g -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=320m"
JAVA_OPT="${JAVA_OPT} -XX:-OmitStackTraceInFastThrow -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=${BASE_DIR}/logs/java_heapdump.hprof"
JAVA_OPT="${JAVA_OPT} -XX:-UseLargePages"
fi
bin/startup.sh
sh shutdown.sh
Nginx 反向代理服务搭建(负载均衡)
这里只使用一个 Nginx 来做 Nacos 的负载均衡
在 192.168.2.137 上装 nginx
http {
# ...
upstream nacos {
server 192.168.2.137:8848;
server 192.168.2.138:8848;
server 192.168.2.139:8848;
}
server {
listen 80;
server_name localhost;
# ...
location / {
root html;
index index.html index.htm;
}
location /nacos/ {
proxy_pass http://nacos/nacos/;
}
# ...
}
}
http://192.168.2.137/nacos
普罗米修斯(prometheus) + grafana(Go开发的数据化可视工具) 监控 Nacos
nacos 启用metrics数据暴露
Nacos 0.8.0版本完善了监控系统,支持通过暴露metrics数据接入第三方监控系统监控Nacos运行状态,目前支持prometheus、elastic search和influxdb
配置application.properties文件,暴露metrics数据
management.endpoints.web.exposure.include=*
检查是否能访问到metrics数据
http://192.168.2.137/nacos/actuator/prometheus
http://192.168.2.137:8848/nacos/actuator/prometheus
http://192.168.2.138:8848/nacos/actuator/prometheus
http://192.168.2.139:8848/nacos/actuator/prometheus
搭建prometheus采集Nacos metrics数据
下载windows版本的普罗米修斯, 修改 prometheus.yml
metrics_path: '/nacos/actuator/prometheus'
static_configs:
- targets: ['192.168.2.137:8848','192.168.2.138:8848','192.168.2.139:8848']
给应用程序 prometheus.exe 创建快捷方式, 右键快捷方式, 点击属性, 修改目标, 在原值后面添加 --config.file=prometheus.yml
, 如
C:\mrathena\develop\prometheus\prometheus.exe --config.file=prometheus.yml
双击快捷方式启动普罗米修斯服务
可以看到加载了配置文件 prometheus.yml, 服务端口是9090, 在浏览器打开地址, 搜索 nacos_monitor
有结果说明采集数据成功
localhost:9090/graph
搭建grafana图形化展示metrics数据
Grafana官网
Grafana Windows 安装教程
Grafana Windows 下载页
Grafana Windows 下载链接
下载好压缩包后, 先右键属性, 勾选"解除锁定"复选框, 点击确定, 解压后, 运行 bin\grafana-server.exe 启动 Grafana 服务, 默认账号密码都是 admin, 登录后刷新页面
http://localhost:3000
配置prometheus数据源
添加数据源时选择普罗米修斯, name 为 “prometheus”, 填写url, 其他默认
导入Nacos grafana监控模版
Nacos监控分为三个模块:
- nacos monitor 展示核心监控项
- nacos detail 展示指标的变化曲线
- nacos alert为告警项
配置grafana告警
Nacos metrics含义
Nacos-Sync监控
Nacos-Sync metrics含义
Nacos 快速开始
浏览器访问
http://localhost:8083/order/query
页面打印
this is order query customer info: this is a customer
父模块 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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.mrathena.spring.cloud</groupId>
<artifactId>spring.cloud.demo</artifactId>
<version>1.0.0</version>
<name>spring.cloud.demo</name>
<packaging>pom</packaging>
<properties>
<java.version>1.8</java.version>
</properties>
<modules>
<module>service.customer</module>
<module>service.order</module>
</modules>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.3.2.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR8</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.5.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
子模块 service.customer
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>spring.cloud.demo</artifactId>
<groupId>com.mrathena.spring.cloud</groupId>
<version>1.0.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>service.customer</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies>
</project>
application.yml
spring:
application:
name: service.customer
cloud:
nacos:
discovery:
server-addr: 192.168.2.137:8848,192.168.2.138:8848,192.168.2.139:8848
server:
port: 8081
Application.java
package com.mrathena.customer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
CustomerController.java
package com.mrathena.customer;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("customer")
public class CustomerController {
@RequestMapping("query")
public Object query() {
return "this is a customer";
}
}
子模块 service.order
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>spring.cloud.demo</artifactId>
<groupId>com.mrathena.spring.cloud</groupId>
<version>1.0.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>service.order</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies>
</project>
application.yml
spring:
application:
name: service.order
cloud:
nacos:
discovery:
server-addr: 192.168.2.137:8848,192.168.2.138:8848,192.168.2.139:8848
server:
port: 8083
Application.java
package com.mrathena.order;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
RestTemplateConfig.java
package com.mrathena.order;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class RestTemplateConfig {
@Bean
@LoadBalanced
// @LoadBalanced 注解的作用
// 负载均衡器, 拉取目标服务列表
// RestTemplate 扩展点 ClientHttpRequestInterceptor
// 负载均衡器组件 Ribbon 里面有一个 LoadBalancerInterceptor 实现了 ClientHttpRequestInterceptor
// 把 服务名称 替换成 对应服务的真实地址, 同时提供负载均衡的功能
// 不使用 @LoadBalanced 注解, 也是可以达到同样效果的
// 注入一个 LoadBalancerClient 实例 (已经在 Spring 容器中了, 可以直接注入)
// new 一个 LoadBalancerInterceptor 实例, 传入 LoadBalancerClient 实例
// 给 RestTemplate 设置拦截器, restTemplate.setInterceptor(Collections.singletonList(new LoadBalancerInterceptor(loadBalancerClient)));
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
OrderController.java
package com.mrathena.order;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@RestController
@RequestMapping("order")
public class OrderController {
@Autowired
private RestTemplate restTemplate;
@RequestMapping("query")
public Object query() {
// 负载均衡器实现了 服务名 和 具体服务IP:PORT 的转换
String url = "http://service.customer/customer/query";
return "this is order query customer info: " + restTemplate.getForObject(url, String.class);
}
}