文章目录
1、Spring Cloud Alibaba介绍
1.1、概述
下面是Spring Cloud 官方对Spring Cloud Alibaba的介绍:
Spring Cloud Alibaba旨在为微服务开发提供一站式解决方案。该项目包括开发分布式应用程序和服务所需的组件,以便开发人员可以使用Spring Cloud编程模型轻松开发分布式应用程序。使用Spring Cloud Alibaba,您只需要添加一些注释和配置,就可以为您的应用程序使用Alibaba的分布式解决方案,并使用Alibaba中间件构建自己的分布式系统。
- Spring Cloud Alibaba是阿里的微服务解决方案,是阿里巴巴结合自身微服务实践,开源的微服务全家桶,在Spring Cloud项目中孵化成为Spring Cloud的子项目
- 第一代的Spring Cloud标准中很多组件已经停更,如:Eureak、Zuul等,所以Spring Cloud Alibaba已经慢慢成为Spring Cloud第二代的标准实现,许多组件在业界逐渐开始使用,已经有很多成功案例
1.2、功能
Spring Cloud Alibaba是阿里巴巴结合自身的微服务实践开源的微服务全家桶,我个人觉得其组件比Spring Cloud 中的组件更加好用和强大。并且对Spring Cloud组件做了很好的兼容。比如在Spirng Cloud Alibaba中依然可以使用Feign作为服务调用方式,使用Eureak做服务注册发现等等。Spring Cloud Alibaba主要的功能如下:
1、流控制和服务降级
支持WebServlet,WebFlux,OpenFeign,RestTemplate,Dubbo访问限制和降级流的功 能。它可以在运行时通过控制台实时修改限制和降级流的规则,并且还支持监视限制和 降级度量标准。
2、服务注册和发现
可以注册服务,并且客户可以使用Spring托管的bean(自动集成功能区)发现实例。
3、分布式配置
支持分布式系统中的外部配置,配置更改时自动刷新。
4、RPC服务
扩展Spring Cloud客户端RestTemplate和OpenFeign以支持调用Dubbo RPC服务。
5、分布式事务
支持高性能且易于使用的分布式事务解决方案。
6、阿里云对象存储
大规模,安全,低成本,高度可靠的云存储服务。支持随时随地在任何应用程序中存储 和访问任何类型的数据。
7、阿里云SchedulerX
准确,高度可靠,高可用性的计划作业调度服务,响应时间在几秒钟内。
8、阿里云短信
阿里云短信服务覆盖全球,提供便捷,高效,智能的通信功能,帮助企业快速联系客户
9、事件驱动
支持构建与共享消息系统连接的高度可扩展的事件驱动微服务。
下面给出一个Spring Cloud Alibaba的架构图:
- Gateway: 服务网关
- Nacos:服务注册与发现
- Nacos:配置统一管理
- Sentinel:服务限流
- Sentinel:服务熔断降级
- Seata分布式事务
下面给大家准备了一张图,通俗易懂一点:
下面是SpringCloud 和 Spring Cloud Alibaba的功能对比:
功能 | Spring Cloud Netflix | Spring Cloud Alibaba |
---|---|---|
网关 | Spring Cloud Netflix Zuul | Spring Cloud Gateway |
服务注册与发现 | Spring Cloud Netflix Eureka | Spring Cloud Alibaba Nacos |
配置中心 | Spring Cloud Config | Spring Cloud Alibaba Nacos |
服务限流 | Spring Cloud Netflix Hystrix | Spring Cloud Alibaba Sentinel |
服务熔断 | Spring Cloud Netflix Hystrix | Spring Cloud Alibaba Sentinel |
分布式事务 | TX-LCN | Spring Cloud Alibaba Seata |
服务调用 | Ribbon/Feign | Ribbon/OpenFeign/Dubbo |
1.3、版本
Spring Cloud Alibaba是Spring Cloud的子项目 ,Spring Cloud 基于Spring Boot,所以我们在选择版本的时候需要考虑三个框架的版本兼容性,目前官方给出的版本如下:
注意:每个Spring Cloud Alibaba版本及其自身所适配的各组件对应版本如下图所示:(经过验证,自行搭配各组件版本不保证可用)
2、Nacos服务注册与发现
2.1、为什么需要服务注册与发现?
微服务的其中一个特点是服务之间需要进行网络通信,服务之间发起调用时,需要知道目标服务的通信地址(IP端口信息),那么当微服务数量成百上千个非常多的时候,程序员该如何管理众多的服务通信地址呢?对于随时新增加的微服务和下线的微服务,又该如何去动态添加和删除这些微服务的通信地址呢?
所以手工管理服务的通信地址是一件不现实的事情,我们需要借助一个强大的工具帮我们实现这一功能,这个工具就是注册中心。服务注册与发现组件有Nacos、Eureka、Zookeeper、Consul等
2.2、什么是服务注册与发现?
服务注册
当微服务(客户端)在启动的时候会向服务端(注册中心)提交自己的服务信息(服务名,IP,端口等),在服务端会形成一个微服务的通信地址列表,这叫服务注册
服务发现
通常情况下,微服务(客户端)会定时从服务端(注册中心)拉取微服务通信地址列表缓存到本地,当一个微服务(消费者)在向另一个微服务(提供者)发起调用的时候会根据目标服务的服务名找到其通信地址,然后向目标服务发起网络请求,这叫服务发现
服务续约
微服务(客户端)会采用“心跳机制”向服务端(注册中心)发请求进行服务续约,其实就是定时向注册中心发请求报告自己的健康状况,告诉注册中心自己还活着,不要把自己从服务地址清单中剔除掉,那么当微服务(客户端)宕机未向注册中心发起续约请求,或者续约请求超时,注册中心机会从服务地址清单中剔除该续约失败的服务
2.3、Netflix Eureka 与 Alibaba Nacos
Netflix Eureka作为Spring Cloud 第一代技术标准做服务发现的组件,如今已经停止维护,这里我们重点讨论一下 Alibaba nacos。
Nacos和Eureka有着相同的能力,甚至更为强大,作为 Dubbo 生态系统中重要的注册中心实现。官方对它有如下定义:
Nacos致力于帮助您发现,配置和管理微服务。
它提供了一组简单有用的功能,使您能够实现动态服务发现,服务配置,服务元数据和流量管理。
Nacos使构建,交付和管理微服务平台变得更容易,更快捷。它是通过微服务或云原生方法支持以服务为中心的现代应用程序体系结构的基础架构。
这里我们看到Nacos不仅是服务发现组件,同时也是一个配置管理组件,也就是说它不仅可以用来取代Eureak作为注册中心, 也可以用来取代Spring Cloud Config 做配置统一管理,我们慢慢讲,别急
2.4、Nacos介绍
Nacos官网:https://nacos.io/zh-cn/docs/what-is-nacos.html
下面是来自官网的介绍:
Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service的首字母简称,一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台
从上面我们可以看出Nacos至少有三个核心功能
- 动态服务发现
- 配置管理
- 服务管理
2.5、实战编码
1、Nacos服务端下载安装
下载
官方提供了Nacos的服务端供我们下载使用,我们启动Nacos后将我们的微服务注册进入Nacos即可。
注意:这里和Eureka有个很大的区别是:Eureka注册中心服务端是需要新建一个单独的微服务,而Nacos不需要新建微服务,就是启动一个软件就可以了,非常简单方便
官网下载地址:https://github.com/alibaba/nacos/releases
由于大家不是都可以顺利访问github的(大家都懂的),所以我这里已经为大家下载好了,而且下载了各个版本,都放到网盘中了,自己根据需要下载即可:
链接:https://pan.baidu.com/s/1SD6CTuZNY6v-PWr1eauWUQ
提取码:3301
本文档编写时,Nacos最新版本是2.1.1 (Aug 8th, 2022),本文档是基于这个版本的,当然,官方推荐我们生产环境使用稳定版:2.0.3
官网首页:
下载页面:
下载下来后,我们此处用windows版即可,解压目录如下:
目录介绍:
- bin:启动和停止脚本
- conf:Nacos的配置文件
- target:Nacos的jar包,启动脚本就是运行这里面的jar包,停止脚本是直接kill进程
运行
将软件解压后,进入bin目录,然后双击startup.cmd即可(Linux系统的话,运行startup.sh文件即可)
默认是以集群模式启动,可能会报如下错误:
那么我们需要通过添加参数以单击模式启动,进入bin目录后,cmd运行下面命令:
startup.cmd -m standalone
就会是这样:
启动成功如下图所示,就不会有错误信息了:
启动成功后,打开浏览器,输入:http://127.0.0.1:8848/nacos/index.html
就会出现如下页面:
默认的登录账号和密码都是nacos,登录成功之后页面如下:
到目前为止,nacos的服务端就已经启动成功了,接下来需要搭建客户端,将微服务注册到Nacos中
这个页面就可以认为是Nacos的管理控制台,包含如下信息:
- 配置管理
- 服务管理
- 权限控制
- 命名空间
- 集群管理
上面这些功能下面我们都会详细介绍到,先别急,我们先搭建一个项目架子出来,方便我们做一些测试
下面开始搭架子
2、项目架构搭建
我们需要创建很多的微服务做相关测试,并且要实现服务之间的通信,所以我这里使用多模块方式来做演示,最终搭建工程结构如下:
SpringCloudAlibabaDemo
pom.xml
springcloudalibaba-user-server-1010 //用户服务
springcloudalibaba-order-server-1020 //订单服务
我这里的模块名称之所以用端口,是可以一目了然的看到该模块是微服务,还是一个单纯的模块,微服务是有启动类可以启动的,而单纯的模块是不能启动的
在IDEA中新建完毕之后就是这样的:
由于第一层【springcloudalibaba-parent】不写代码,所以可以将src目录删除掉
父工程【SpringCloudAlibabaDemo】中的pom.xml文件,引入相关依赖:Spring boot版本为2.1.3.RELEASE,Spring Cloud 版本为Greenwich.SR1,Alibaba版本为2.1.1.RELEASE ,父工程的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">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.wujiangbo</groupId>
<artifactId>SpringCloudAlibabaDemo</artifactId>
<version>1.0-SNAPSHOT</version>
<!-- 不需要手动添加下面packaging,新建子模块时,会自动添加的 -->
<packaging>pom</packaging>
<!-- 子模块(这些内容不需要我们手动写,新建子模块时会自动添加进来) -->
<modules>
<module>springcloudalibaba-user-server-1010</module>
<module>springcloudalibaba-order-server-1020</module>
</modules>
<!--公共的一些配置-->
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<!-- 管理SpringBoot的依赖 -->
<parent>
<groupId> org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.13.RELEASE</version>
</parent>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.1.1.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.SR2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<!--引入一些子模块都用得到的相关工具包-->
<dependencies>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--常用工具类 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
<!-- 阿里JSON解析器 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.50</version>
</dependency>
<!--糊涂工具-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.16</version>
</dependency>
</dependencies>
</project>
3、微服务改造
上面我们只是搭了一个架子而已,用户服务和订单服务还没有写任何代码,接下来我们就开始写代码
1、用户服务改造
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>SpringCloudAlibabaDemo</artifactId>
<groupId>cn.wujiangbo</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>springcloudalibaba-user-server-1010</artifactId>
<dependencies>
<!--服务注册与发现-->
<dependency>
<groupId>com.alibaba.cloud </groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--web基础依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>
启动类:
创建配置类加上@EnableDiscoveryClient注解开启服务发现功能,代码如下
package cn.wujiangbo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
/**
* <p>启动类</p>
*
* @author 波波老师(微信 : javabobo0513)
*/
@SpringBootApplication
@EnableDiscoveryClient //开启服务发现功能
public class UserApp1010 {
public static void main(String[] args){
SpringApplication.run(UserApp1010.class, args);
}
}
yml配置文件:
配置文件主要配置端口,服务名,已经nacos注册中心地址
server:
port: 1010
spring:
application:
name: user-server
cloud:
nacos:
discovery:
# Nacos 注册中心地址
server-addr: 127.0.0.1:8848
2、订单服务改造
订单服务改造步骤和上面的用户服务改造步骤是一样的,就不做过多的赘述了
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>SpringCloudAlibabaDemo</artifactId>
<groupId>cn.wujiangbo</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>springcloudalibaba-order-server-1020</artifactId>
<dependencies>
<!--服务注册与发现-->
<dependency>
<groupId>com.alibaba.cloud </groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--加入web依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>
启动类:
package cn.wujiangbo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
/**
* <p>启动类</p>
*
* @author 波波老师(微信 : javabobo0513)
*/
@SpringBootApplication
@EnableDiscoveryClient //开启服务发现功能
public class OrderApp1020 {
public static void main(String[] args){
SpringApplication.run(OrderApp1020.class, args);
}
}
yml配置文件:
server:
port: 1020
spring:
application:
name: order-server
cloud:
nacos:
discovery:
# Nacos 注册中心地址
server-addr: 127.0.0.1:8848
4、测试
启动用户服务和订单服务,然后进入Nacos管理页面,浏览器访问:http://127.0.0.1:8848/nacos/index.html,如下:
从上图可以看出,用户服务和订单服务已经成功的注册到Nacos注册中心了
2.6、Ribbon实现服务通信
1、什么是Ribbon?
- Ribbon是Netflix发布的云中间层服务开源项目,主要功能是提供客户端负载均衡算法。Ribbon客户端组件提供一系列完善的配置项,如,连接超时,重试等。
- 简单来说,Ribbon是一个客户端负载均衡器,Ribbon可以按照负载均衡算法(如简单轮询,随机连接等)向多个服务发起调用,我们也很容易使用Ribbon实现自定义的负载均衡算法
2、实战编码
上面测试中,我们仅仅是启动了两个服务,并成功注册到注册中心中了,但是两个服务之间还没有发生调用关系,现在我们就要开始做服务之间调用的测试了,整个流程如下:
根据上图,我们需要再新建一个【springcloudalibaba-user-common】公共模块,因为两个服务都需要用到User对象,不能两个服务都存在一个相同的类,太冗余了,不优雅,所以需要抽取出来成为一个单独的模块,哪个微服务需要用到这个User类,就导入这个模块即可
新建user-common模块
新建【springcloudalibaba-user-common】模块,里面新建一个User实体对象就可以了,代码如下:
package cn.wujiangbo.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* <p>用户对象</p>
*
* @author 波波老师(微信 : javabobo0513)
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Long id;//编号
private String name;//姓名
private String desc;//描述
}
服务提供者
修改用户服务,在pom.xml中引入user-common模块,如下:
<?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>SpringCloudAlibabaDemo</artifactId>
<groupId>cn.wujiangbo</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>springcloudalibaba-user-server-1010</artifactId>
<dependencies>
<!--服务注册与发现-->
<dependency>
<groupId>com.alibaba.cloud </groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--加入web依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--user-common模块-->
<dependency>
<groupId>cn.wujiangbo</groupId>
<artifactId>springcloudalibaba-user-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
新建UserController接口,这个是向消费者暴露的访问接口,如下:
package cn.wujiangbo.controller;
import cn.wujiangbo.dto.User;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* <p>用户服务相关api接口</p>
*
* @author 波波老师(微信 : javabobo0513)
*/
@RestController
@RequestMapping("/user")
public class UserController {
@Value("${server.port}")
private String port;
@GetMapping("/getUserById/{id}")
public User getUserById(@PathVariable Long id){
return new User(id,"王天霸", "我是王天霸,你好吗?port=" + port);
}
}
消费者服务
订单服务的pom.xml中也需要引入user-common模块,因为订单服务也需要用到User类,如下:
<?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>SpringCloudAlibabaDemo</artifactId>
<groupId>cn.wujiangbo</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>springcloudalibaba-order-server-1020</artifactId>
<dependencies>
<!--服务注册与发现-->
<dependency>
<groupId>com.alibaba.cloud </groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--加入web依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--user-common模块-->
<dependency>
<groupId>cn.wujiangbo</groupId>
<artifactId>springcloudalibaba-user-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
然后订单服务新增OrderController接口,如下:
package cn.wujiangbo.cn.wujiangbo;
import cn.wujiangbo.dto.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
/**
* <p>订单服务相关api接口</p>
*
* @author 波波老师(微信 : javabobo0513)
*/
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/getUserById/{id}")
public User getUserById(@PathVariable Long id){
//拼接请求路径
String url = "http://user-server/user/getUserById/" + id;
//利用 RestTemplate 发请求给用户服务,将用户服务的返回结果返回前端
return restTemplate.getForObject(url, User.class);
}
}
需要在启动类中定义RestTemplate对象,使用@LoadBalanced开启负载均衡,Nacos默认整合了Ribbon,启动类修改如下:
package cn.wujiangbo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
/**
* <p>启动类</p>
*
* @author 波波老师(微信 : javabobo0513)
*/
@SpringBootApplication
@EnableDiscoveryClient //开启服务发现功能
public class OrderApp1020 {
@Bean
@LoadBalanced //开启负载均衡,默认是轮询策略
public RestTemplate restTemplate(){
return new RestTemplate();
}
public static void main(String[] args){
SpringApplication.run(OrderApp1020.class, args);
}
}
添加了 @LoadBalanced 注解之后,Ribbon会给RestTemplate请求添加一个拦截器,在拦截器中获取注册中心的服务列表,并使用Ribbon内置的负载均衡算法从服务列表里选中一个服务,通过获取到的服务信息 (ip,port)替换 ServiceId 实现负载请求
3、测试
启动用户服务和订单服务,然后浏览器访问订单服务的接口,地址:http://localhost:1020/order/getUserById/13,返回结果如下:
可以看到可以正常显示数据,我们要知道,这个数据是订单服务调用用户服务获取到的,实际上是用户服务生成出来的这条数据
4、负载均衡测试
现在我们想要做负载均衡测试的话,必须先把用户服务做一个集群,本地模拟也非常简单,步骤如下:
然后勾选允许并行运行的选项就可以了:
然后第一遍直接运行启动类就可以了,那就以1010端口启动了,然后将启动端口改为1011,再运行启动类就可以了,最终效果如下:
很明显,我本地成功将用户服务启动了两份,运行的代码是一样的,但是端口不一样,一份代码部署多份运行,就是集群嘛,到此为止,用户服务的集群就做好了
那么接下来我们就启动订单服务,浏览器访问订单服务的接口:http://localhost:1020/order/getUserById/13
然后不断刷新页面,观察端口的值,会轮询显示1010和1011,说明Ribbon的默认负载均衡策略就是轮询
那么我想指定其他负载均衡策略,该如何操作呢?
5、指定负载均衡策略
我们此时需要编写一个配置类,用来指定负载均衡策略,代码如下:
package cn.wujiangbo.config;
import com.alibaba.cloud.nacos.ribbon.NacosRule;
import com.netflix.loadbalancer.IRule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* <p>指定负载均衡策略</p>
*
* @author 波波老师(微信 : javabobo0513)
*/
@Configuration
public class RibbonConfig {
/**
* 指定负载均衡策略
*/
@Bean
public IRule iRule(){
/**
* NacosRule是Alibaba Nacos自己实现的一个负载均衡策略,可以在nacos平台中根据自定义权重进行访问
*/
return new NacosRule();
}
}
上面配置类的位置有两种情况:
- 和启动类同包或者是启动类子包,能够被启动类扫描到,此时,相当于是全局配置,多所有服务都生效
- 如果刚好相反,上面配置类所在包启动类扫描不到的话,那就相当于是局部配置,我们可以针对某些服务生效,配置如下:
package cn.wujiangbo;
import cn.wujiangbo.config.RibbonConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.cloud.netflix.ribbon.RibbonClients;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
/**
* <p>启动类</p>
*
* @author 波波老师(微信 : javabobo0513)
*/
@SpringBootApplication
@EnableDiscoveryClient //开启服务发现功能
//如果RibbonConfig是局部配置的话,那这里就可以这样设置,指定调用不同的服务,用不同的负载均衡策略
@RibbonClients(value = {
@RibbonClient(name = "user-server", configuration = RibbonConfig.class),
})
public class OrderApp1020 {
@Bean
@LoadBalanced //开启负载均衡,默认是轮询策略
public RestTemplate restTemplate(){
return new RestTemplate();
}
public static void main(String[] args){
SpringApplication.run(OrderApp1020.class, args);
}
}
添加@RibbonClients标签即可,上面我只是写了一个Demo而已,具体根据实际情况而定
接下来我们测试一下,重启订单服务,我们先看下Nacos管理台:
因为用户服务做了集群,所以这里实例数和健康实例数都是2,点击后面的【详情】按钮,可以看到:
这里我们设置一下权重,设置过后如下:
我给1010这个实例的权重设置为10,这样它的权重是1011实例的10倍,那么就应该要承受10倍的请求流量
然后我们再访问:http://localhost:1020/order/getUserById/13
测试重点是你要一直刷新页面,观察端口号,是不是1010出现的频次明显高于1011,因为1010服务应该接收的请求量是1011服务接收请求量的10倍,如果是的话,那就是测试成功了
6、Ribbon负载均衡策略汇总
Ribbon支持7种负载均衡策略:
- 轮询策略(默认)
- 权重策略
- 随机策略
- 最小连接数策略
- 重试策略
- 可用性敏感策略
- 区域敏感策略
1、轮询策略
轮询策略:RoundRobinRule,按照一定的顺序依次调用服务实例,yml中这样配置:
#设置Ribbon的负载均衡策略
user-server: #Nacos中目标服务的ID
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule #设置负载均衡
2、权重策略
权重策略:WeightedResponseTimeRule,根据每个服务提供者的响应时间分配一个权重,响应时间越长,权重越小,被选中的可能性也就越低。 它的实现原理是,刚开始使用轮询策略并开启一个计时器,每一段时间收集一次所有服务提供者的平均响应时间,然后再给每个服务提供者附上一个权重,权重越高被选中的概率也越大,yml中这样配置:
#设置Ribbon的负载均衡策略
user-server: #Nacos中目标服务的ID
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.WeightedResponseTimeRule #设置负载均衡
3、随机策略
随机策略:RandomRule,从服务提供者的列表中随机选择一个服务实例,yml中这样配置:
#设置Ribbon的负载均衡策略
user-server: #Nacos中目标服务的ID
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #设置负载均衡
4、最小连接数策略
最小连接数策略:BestAvailableRule,也叫最小并发数策略,它是遍历服务提供者列表,选取连接数最小的⼀个服务实例。如果有相同的最小连接数,那么会调用轮询策略进行选取,yml中这样配置:
#设置Ribbon的负载均衡策略
user-server: #Nacos中目标服务的ID
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.BestAvailableRule #设置负载均衡
5、重试策略
重试策略:RetryRule,按照轮询策略来获取服务,如果获取的服务实例为 null 或已经失效,则在指定的时间之内不断地进行重试来获取服务,如果超过指定时间依然没获取到服务实例则返回 null,yml中这样配置:
#设置Ribbon的负载均衡策略
ribbon:
ConnectTimeout: 2000 #请求连接的超时时间(单位:毫秒)
ReadTimeout: 5000 #请求处理的超时时间(单位:毫秒)
user-server: #Nacos中目标服务的ID
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #设置负载均衡
6、可用性敏感策略
可用敏感性策略:AvailabilityFilteringRule,先过滤掉非健康的服务实例,然后再选择连接数较小的服务实例,yml中这样配置:
#设置Ribbon的负载均衡策略
user-server: #Nacos中目标服务的ID
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.AvailabilityFilteringRule #设置负载均衡
7、区域敏感策略
区域敏感策略:ZoneAvoidanceRule,根据服务所在区域(zone)的性能和服务的可用性来选择服务实例,在没有区域的环境下,该策略和轮询策略类似,yml中这样配置:
#设置Ribbon的负载均衡策略
user-server: #Nacos中目标服务的ID
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.ZoneAvoidanceRule #设置负载均衡
8、总结
Ribbon 为客户端负载均衡器,相比于服务端负载均衡器的统一负载均衡策略来说,它提供了更多的灵活性。Ribbon 内置了 7 种负载均衡策略:轮询策略、权重策略、随机策略、最小连接数策略、重试策略、可用性敏感策略、区域性敏感策略,并且用户可以通过继承 AbstractLoadBalancerRule 来实现自定义负载均衡策略
7、自定义负载均衡策略
我们可以自己定义一个策略,比如下面,我自己定义了一个策略,每个服务每次分3个请求,依次轮询:
package cn.wujiangbo.config;
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractLoadBalancerRule;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
/**
* <p>自定义负载均衡策略</p>
*
* @author 波波老师(微信 : javabobo0513)
*/
public class MyRibbonConfig extends AbstractLoadBalancerRule {
/**
* 默认等于0,如果小于等于thresholdValue,指向下一个服务
*/
private int total = 0;
/**
* 默认=0,如果total==thresholdValue,currentIndex++
*/
private int currentIndex = 0;
/**
* 阈值,分给每个服务的请求数量
*/
private int thresholdValue = 3;
//选择服务的方法
public Server choose(ILoadBalancer lb, Object key) {
if (lb == null) {
return null;
} else {
Server server = null;
while (server == null) {
if (Thread.interrupted()) {
return null;
}
//获得所有的服务
List<Server> upList = lb.getReachableServers();
//获得活着的服务
List<Server> allList = lb.getAllServers();
int serverCount = allList.size();
if (serverCount == 0) {
return null;
}
if (total < thresholdValue - 1) {
server = upList.get(currentIndex);
total++;
} else {
total = 0;
currentIndex++;
if (currentIndex >= upList.size()) {
currentIndex = 0;
}
server = upList.get(currentIndex);
}
if (server == null) {
Thread.yield();
} else {
if (server.isAlive()) {
return server;
}
server = null;
Thread.yield();
}
}
return server;
}
}
protected int chooseRandomInt(int serverCount) {
return ThreadLocalRandom.current().nextInt(serverCount);
}
@Override
public Server choose(Object key) {
return choose(getLoadBalancer(), key);
}
@Override
public void initWithNiwsConfig(IClientConfig clientConfig) {
}
}
这样写了还不会生效,RibbonConfig类还需要做如下修改:
package cn.wujiangbo.config;
import com.netflix.loadbalancer.IRule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* <p>指定负载均衡策略</p>
*
* @author 波波老师(微信 : javabobo0513)
*/
@Configuration
public class RibbonConfig {
/**
* 指定负载均衡策略
*/
@Bean
public IRule iRule(){
/**
* 指定自定义的负载均衡策略
*/
return new MyRibbonConfig();
}
}
这样再重新测试,页面访问:http://localhost:1020/order/getUserById/13,然后一直刷新,你会发现每个端口都会出现3次
2.7、Feign实现服务通信
1、为什么要使用Feign?
在上面我们使用Ribbon是可以做到微服务之间的通信调用的,但是我们在代码中是拼接的URL目标服务的路径,有下面两个问题:
- 代码这样写死属于硬编码,很不优雅
- 调用目标方法传参需要字符串拼接,效率较低,参数较多时,很麻烦,很不优雅
如果使用Feign的话,就没有这些烦恼了,它基于Ribbon进行了封装,把一些URL拼接和参数处理细节屏蔽起来,我们只需要简单编写Feign的客户端接口,就可以像调用本地方法去调用远程微服务方法
2、什么是Feign?
- Feign是一个声明式的http客户端,使用Feign可以实现声明式REST调用,它的目的就是让Web Service调用更加简单
- Feign整合了Ribbon和SpringMvc注解,这让Feign的客户端接口看起来就像一个Controller
- Feign提供了HTTP请求的模板,通过编写简单的接口和插入注解,就可以定义好HTTP请求的参数、格式、地址等信息
- 而Feign则会完全代理HTTP请求,我们只需要像调用方法一样调用它就可以完成服务请求及相关处理
- 同时Feign整合了Hystrix,可以很容易的实现服务熔断和降级
3、实战编码
现在我们还是实现和上面一样的调用流程和功能,只是现在我们改用Feign去调用user-server的接口了,流程图如下:
新建支付服务
订单服务中使用的是Ribbon,为了不混淆,我们单独再新建一个支付服务做Feign的测试,新建微服务:springcloudalibaba-pay-server-1030
然后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>SpringCloudAlibabaDemo</artifactId>
<groupId>cn.wujiangbo</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>springcloudalibaba-pay-server-1030</artifactId>
<dependencies>
<!--服务注册与发现-->
<dependency>
<groupId>com.alibaba.cloud </groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--加入web依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--Feign依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--user-common模块-->
<dependency>
<groupId>cn.wujiangbo</groupId>
<artifactId>springcloudalibaba-user-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
多了一个spring-cloud-starter-openfeign的依赖
yml配置文件:
server:
port: 1030
spring:
application:
name: pay-server
cloud:
nacos:
discovery:
# Nacos 注册中心地址
server-addr: 127.0.0.1:8848
启动类
通过 @EnableFeignClients 注解开启Feign,括号后面填写Feign接口的包路径即可
package cn.wujiangbo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
/**
* <p>启动类</p>
*
* @author 波波老师(微信 : javabobo0513)
*/
@SpringBootApplication
@EnableDiscoveryClient //开启服务发现功能
@EnableFeignClients("cn.wujiangbo.feign.client")
public class PayApp1030 {
public static void main(String[] args){
SpringApplication.run(PayApp1030.class, args);
}
}
编写Feign接口
该接口作为Feign的客户端接口,用来调用用户服务(user-server)
package cn.wujiangbo.feign.client;
import cn.wujiangbo.dto.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
/**
* <p>Feign的客户端接口</p>
*
* @author 波波老师(微信 : javabobo0513)
*/
@FeignClient("user-server")
public interface UserFeignClient {
@GetMapping("/user/getUserById/{id}")
User getUserById(@PathVariable Long id);
}
编写Controller
通过 UserFeignClient 客户端来调用用户服务,获取User并返回。
package cn.wujiangbo.controller;
import cn.wujiangbo.dto.User;
import cn.wujiangbo.feign.client.UserFeignClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* <p>支付服务相关api</p>
*
* @author 波波老师(微信 : javabobo0513)
*/
@RestController
@RequestMapping("/pay")
public class PayController {
@Autowired
private UserFeignClient userFeignClient;
@GetMapping("/getUserById/{id}")
public User getUserById(@PathVariable Long id){
//通过 UserFeignClient Feign的客户端接来调用用户服务,获取User并返回,此时这样的写法才够优雅
return userFeignClient.getUserById(id);
}
}
4、测试
启动用户服务和支付服务,然后浏览器访问支付服务的接口,地址:http://localhost:1030/pay/getUserById/13,返回结果如下:
可以看到可以正常显示数据,我们要知道,这个数据是支付服务调用用户服务获取到的,实际上是用户服务生成出来的这条数据
5、编写兜底类
为什么要写兜底类呢?我们在上面环境下,停掉用户服务,页面就会是这样的:
这样就很不友好,我们希望目标服务不可用的时候,返回一个兜底数据,那该怎么做呢?
就需要编写兜底类了,如下:
package cn.wujiangbo.feign.fallback;
import cn.wujiangbo.dto.User;
import cn.wujiangbo.feign.client.UserFeignClient;
import feign.hystrix.FallbackFactory;
import org.springframework.stereotype.Component;
/**
* <p>兜底类</p>
*
* @author 波波老师(微信 : javabobo0513)
*/
@Component
public class UserFeignClientFallback implements FallbackFactory<UserFeignClient> {
@Override
public UserFeignClient create(Throwable throwable) {
return new UserFeignClient(){
@Override
public User getUserById(Long userId) {
//打印异常信息,方便从控制台查看触发兜底的原因
throwable.printStackTrace();
return new User(userId, "null", "User服务不可用,返回兜底数据");
}
};
}
}
然后在Feign客户端接口类中指定该兜底类即可,只需要在@FeignClient注解中添加fallbackFactory属性即可,如下:
package cn.wujiangbo.feign.client;
import cn.wujiangbo.dto.User;
import cn.wujiangbo.feign.fallback.UserFeignClientFallback;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
/**
* <p>Feign的客户端接口</p>
*
* @author 波波老师(微信 : javabobo0513)
*/
@FeignClient(value = "user-server", fallbackFactory = UserFeignClientFallback.class)
public interface UserFeignClient {
@GetMapping("/user/getUserById/{id}")
User getUserById(@PathVariable Long id);
}
还需要在yml中添加下面内容,feign中默认是包含hystrix的,但默认是关闭状态,所以需要这里手动开启一下
feign:
hystrix:
enabled: true #开启熔断功能
这样做的含义是:当目标服务user-server出问题不可访问时,就返回我们兜底类中定义好的数据,如果目标服务可以正常提供服务的话,那兜底类就等于没写,不会执行到
上面代码改变做完后,重启下,然后再刷新页面,如下:
现在返回的数据就符合预期了,这是我们自己定义的兜底数据,走了熔断
2.8、Nacos功能
1、临时实例与持久实例
临时实例
1、默认情况,服务实例仅会注册在Nacos内存,不会持久化到Nacos磁盘,其健康检测机制为Client模式,即Client主动向Server上报其健康状态(类似于推模式);
2、默认心跳间隔为5秒,在15秒内Server未收到Client心跳,则会将其标记为“不健康”状态;在30秒内若收到了Client心跳,则重新恢复“健康”状态,否则该实例将从Server端内存清除。即对于不健康的实例,Server会自动清除;
持久实例
1、服务实例不仅会注册到Nacos内存,同时也会被持久化到Nacos磁盘,其健康检测机制为Server模式,即Server会主动去检测Client的健康状态(类似于拉模式);
2、默认每20秒检测一次,健康检测失败后服务实例会被标记为“不健康”状态,但不会被清除,因为其是持久化在磁盘的,其对不健康持久实例的清除,需要专门进行;
适用场景
临时实例:适合于存在突发流量暴增可能的互联网项目,可以实现弹性扩容,正常生产中的环境就是这样;
持久实例:保护阈值,比如说服务A有100个实例,那么当有98个不可用时:
如果是临时实例,则只会返回两个服务,那么大并发量请求这两个服务肯定会造成雪崩的,造成整个服务不可用;
如果是持久实例,实例会全部返回,虽有98个不可用,消费者可能会请求失败,但不至于剩下的两个健康实例也崩溃,有效的保证系统的可用性;
配置如下:
server:
port: 1010
spring:
application:
name: user-server
cloud:
nacos:
discovery:
# Nacos 注册中心地址
server-addr: 127.0.0.1:8848
ephemeral: false #默认是true(true:临时实例;false:持久实例;)
2、保护阈值
概念:可以设置为0-1之间的浮点数,它其实是⼀个⽐例值(当前服务健康实例数/当前服务总实例数)
场景:
⼀般流程下, nacos是服务注册中⼼,服务消费者要从nacos获取某⼀个服务的可⽤实例信息,对于服务实例有健康/不健康状态之分, nacos在返回给消费者实例信息的时候,会返回健康实例。这个时候在⼀些⾼并发、⼤流量场景下会存在⼀定的问题,问题如下:
如果服务A有100个实例, 90个实例都不健康了,只有10个实例是健康的,如果nacos只返回这两个健康实例的信息的话,那么后续消费者的请求将全部被分配到这两个实例,流量洪峰到来时,10个健康的实例也会扛不住,导致整个服务A就扛不住了,上游的微服务也会导致崩溃,产⽣雪崩效应
保护阈值的意义在于
- 当【服务A健康实例数/总实例数】 < 【保护阈值】 的时候,说明健康实例真的不多了,这个时候保护阈值会被触发
- nacos将会把该服务所有的实例信息(健康的+不健康的)全部提供给消费者,消费者可能访问到不健康的实例,请求失败,但这样也⽐造成雪崩要好,牺牲了⼀些请求,保证了整个系统的可用性
这个机制有个名称叫做【全死全活】-SpringCloud Tencent
3、权重
一个服务可能会存在多个实例,可能对应的机器配置有所不同,所以我们可以给不同的实例设置不同的权重,比如:
我给1010这个实例设置的权重是2,这样它的权重是1011实例的两倍,那么就应该要承受2倍的请求流量
不过指的注意的是,我们在消费一个服务时,通常是通过Ribbon来进行负载均衡的,所以默认情况下Nacos配置的权重是不起作用的,因为Ribbon使用的是自己的负载均衡策略,如果我们想用到Nacos的权重设置的话,可以这样做:
@Bean
public IRule ribbonRule(){
return new NacosRule();
}
这样配置后,就会用到Nacos的权重配置了
4、cluster(就近访问)
一个服务下会有多个实例,在Nacos中,可以将这些实例指定到不同的集群中,比如通过:
server:
port: 1010
spring:
application:
name: user-server
cloud:
nacos:
discovery:
ephemeral: false #默认是true(true:临时实例;false:持久实例;)
server-addr: 127.0.0.1:8848 # Nacos 注册中心地址
cluster-name: beijing #指定集群名字
上面配置先用cluster-name=beijing启动一次,然后端口改成1011,再启动一次
然后cluster-name=shanghai,端口改成1012启动一次,就会出现如下图所示的局面了:
很明显,beijing集群中有1010和1011两个实例,而shanghai实例中只有1012一个实例
此时消费端也可以配置cluster-name=shanghai,那么此时消费端就只会调用shanghai集群中的那一个实例了。
5、Nacos集群配置
上面我们都是使用的单击模式部署的Nacos,为了保证Nacos的高可用,也就是保证配置中心和注册中心的高可用,通常我们都需要以集群的方式来部署Nacos,也就是在不同的机器上多部署几份Nacos
首先,我们需要找到nacos的安装目录的conf目录,你会发现里面有个cluster.conf.example文件,拷贝复制一份,名字改成:cluster.conf,然后内容改成下面这样即可:
192.168.80.1:8848
192.168.80.1:8858
192.168.80.1:8868
参数说明:
- 我这里是在本机做集群,所以我都是配置的本机局域网IP,公司真实场景中,有多台服务器的话,这里的IP肯定是不一样的,端口在本机的话,肯定是不一样的
- 多个机器配置的话,那么每个Nacos中的这个配置文件,都得这样配置,表示每个Nacos节点都得知道自己所在集群的节点情况
那么我这里是本地做集群,就需要将nacos文件复制多份,然后分别启动,如下:
每个文件夹内容都是一样的,唯一不一样的是端口,每个文件中的conf下的application.properties文件中的端口是不一样的
nacos1文件夹中的conf文件夹中的application.propertie配置的端口是:8848
nacos2文件夹中的conf文件夹中的application.propertie配置的端口是:8858
nacos3文件夹中的conf文件夹中的application.propertie配置的端口是:8868
然后开始运行
分别进入到三个文件夹中的bin目录,然后cmd命令运行下面指令:
startup.cmd -p embedded
embedded:表示使用内置数据源,我们可以使用MySQL数据源的,下面再说
三个都运行完毕之后,需要等待一小会,等他们互相发现注册之后就会提示成功了,如下图所示:
然后我们可以通过下面地址进行访问:
http://127.0.0.1:8848/nacos/index.html
http://127.0.0.1:8858/nacos/index.html
http://127.0.0.1:8868/nacos/index.html
访问上面三个地址,应该都是下面内容:
集群配置完毕后,我们微服务的连接参数需要修改一下:
server:
port: 1010
spring:
application:
name: user-server
cloud:
nacos:
discovery:
ephemeral: false #默认是true(true:临时实例;false:持久实例;)
server-addr: 127.0.0.1:8848,127.0.0.1:8858,127.0.0.1:8868 # Nacos 注册中心地址
cluster-name: shanghai #指定集群名字
这样的话,就算其中某个Nacos节点挂了的话,对于应用服务而言,也能从其他节点获取信息
不过,在应用上指定多个IP地址,有一个缺点就是如果IP发生改变的话,那就得修改yml配置文件了,所以我们此时可以在Nacos集群之上再搭一个Nginx
先下载一个Nginx,然后修改conf/nginx配置文件,添加upstream
upstream nacos-cluster{
server 192.168.80.1:8848;
server 192.168.80.1:8858;
server 192.168.80.1:8868;
}
添加location:
location /nacos {
proxy_pass http://nacos-cluster;
}
启动nginx后,访问http://localhost/nocas就可以访问到nacos的管理台了,并且在服务中只需要这样配置了:
server:
port: 1010
spring:
application:
name: user-server
cloud:
nacos:
discovery:
ephemeral: false #默认是true(true:临时实例;false:持久实例;)
#server-addr: 127.0.0.1:8848,127.0.0.1:8858,127.0.0.1:8868 # Nacos 注册中心地址
server-addr: localhost:80/nacos # Nacos 注册中心地址
cluster-name: shanghai #指定集群名字
这样就可以进行服务注册和发现了
3、Nacos分布式配置中心
3.1、为什么需要配置中心?
-
在微服务项目中服务众多,每个微服务都有自己的配置文件,他们分散在各种的服务中,这样的模式在开发阶段貌似没有什么问题,但是一旦项目上线,修改配置将是一个麻烦的事情。
-
除了配置文件比较分散不好管理以外,我们每次修改了配置都要重新打包项目然后走一遍服务的部署流程,也就是说在服务配置变更的这段时间服务将不可被访问。
针对这两种问题,我们需要对配置文件进行统一管理
3.2、Nacos配置中心原理
- 在Spring Cloud 第一代技术标准里面,Spring Cloud Config作为统一配置管理方案,在Spring Cloud Alibaba技术体系中 Nacos 同时充当了 服务注册与发现 和 **统一配置中心 **的角色。
- 和Nacos服务注册发现一样,Nacos的配置管理也分为:服务端和客户端两个角色,服务端是独立的服务,有自己的进程,而客户端需要集成到微服务中
他们工作流程如下:
这里我们使用同一个Nacos Server即可,它既充当注册中心也充当配置中心
配置中心原理:
- 客户端通过长轮询pull拉取配置
- 如果有配置变更,服务端会实时返回变更的配置内容
3.3、实战编码
1、Nacos管理配置文件
我们现在把User服务的配置文件交给Nacos管理,打开Nacos监控面板 - 【配置管理】- 【配置列表】 -点击右上角 “+” 图标添加配置 如下:
在弹窗中填写好DataID,选择YAML格式,其他的默认,配置内容中编写简单的配置内容作为测试即可:
这里定义了一个名字为application-user-dev.yaml的配置,使用的是YAML格式
- Data ID : 非常重要,可以看做是配置的文件的名字,在程序中拉取配置文件的时候需要指定Data ID
- Group : 分组,默认是 DEFAULT_GROUP , 可以针对不同的项目指定不同的配置组。
2、客户端接入配置中心
用户服务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>SpringCloudAlibabaDemo</artifactId>
<groupId>cn.wujiangbo</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>springcloudalibaba-user-server-1010</artifactId>
<dependencies>
<!--服务注册与发现-->
<dependency>
<groupId>com.alibaba.cloud </groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--加入web依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--user-common模块-->
<dependency>
<groupId>cn.wujiangbo</groupId>
<artifactId>springcloudalibaba-user-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!--配置中心依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
</dependencies>
</project>
用户微服务的工程resources目录下,就不需要application.yml文件了,新建bootstrap.yml文件,内容如下:
server:
port: 1010
spring:
application:
name: user-server
cloud:
nacos:
#注册中心相关配置
discovery:
server-addr: 127.0.0.1:8848 #注册中心地址
#配置中心相关配置
config:
server-addr: 127.0.0.1:8848 #配置中心地址
file-extension: yaml #配置文件格式
prefix: application-user #配置前缀
group: DEFAULT_GROUP #默认分组
profiles:
active: dev #指定环境
提示:客户端是如何从Nacos中找到配置文件的呢?
- spring.cloud.nacos.config.server-addr :配置了Nacos的地址
- spring.cloud.nacos.config.file-extension:指定了配置文件的格式为YAML,默认是properties,
- spring.cloud.nacos.config.prefix:配置前缀如果不配置前缀默认会把服务名即spring.application.name的值作为前缀
- spring.cloud.nacos.config.group :分组名,默认是DEFAULT_GROUP对应了Nacos配置中的Group
- spring.profiles.active:配置了环境为dev .该配置可以实现多配置多环境管理
根据如上配置,那么客户端会将:前缀+环境+后缀 拼接成的文件名“application-user-dev.yaml” 去Nacos上查找是否有对应Data ID的配置文件。
注意细节
- 云端配置文件的后缀应该是 yaml而不是yml
- 客户端配置需要指定:spring.profiles.active=dev 环境名
- 客户端配置 :前缀 + 环境名 + 后缀应该和云端配置文件的DataId一致
改造UserController,改造后代码如下:
package cn.wujiangbo.controller;
import cn.wujiangbo.dto.User;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* <p>用户服务相关api接口</p>
*
* @author 波波老师(微信 : javabobo0513)
*/
@RestController
@RequestMapping("/user")
@RefreshScope //作用:动态加载配置文件
public class UserController {
//获取配置文件中的值
@Value("${server.port}")
private String port;
//获取配置文件中的值
@Value("${temp.value}")
private String value;
@GetMapping("/getUserById/{id}")
public User getUserById(@PathVariable Long id){
return new User(id,"王天霸,value=" + value, "我是王天霸,你好吗?port=" + port);
}
}
3.4、测试
启动Nacos,启动用户服务即可,浏览器访问接口:http://localhost:1010/user/getUserById/13
浏览器结果:
然后此时到Nacos管理页面,修改配置文件:
然后弹框点击【确认发布】即可
用户服务项目不需要重启,浏览器再访问:http://localhost:1010/user/getUserById/13,结果如下:
从结果可以看出,我们在没有重启项目的情况下,获取到了修改后的配置内容,那就很nice了啊
实际生产环境中,我们肯定会有修改配置文件内容的场景,那就不需要重启也不需要重新打包发布了,多么优雅啊
3.5、持久化
使用MySQL数据源对Nacos配置中心的配置文件进行持久化,这样Nacos即使停掉了再启动,这些配置文件还会存在,因为都已经保存到MySQL数据库中了,下面我们来实操一下:
首先在你的数据库中,新建数据库nacos_config,然后执行下面脚本:(建库脚本在nacos服务端软件中已经存在了)
脚本执行完毕后,表新建完毕了:
注意:只有配置中心的信息才会存入MySQL中,注册中心的服务信息是不会持久化到MySQL数据库中的
接下来开始修改配置文件:
修改如下:(33行开始)
#*************** 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://127.0.0.1:3306/nacos_config?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC
db.user.0=root
db.password.0=123456
这样配置完毕之后,在Nacos中添加配置文件后,就会持久化到数据库的表中了,如下:
这样即使Nacos重启的话,这些配置文件依然存在,nice
4、总结
- 本文主要介绍了Nacos作为注册中心和配置中心是如何使用的
- 大家在真实项目中知道如何配置即可