1. 微服务架构
- "微服务”一词源于 Martin Fowler的名为 Microservices的博文
- 微服务是系统架构上的一种设计风格,它的主旨是将一个原本独立的系统拆分成多个小型服务,这些小型服务都在各自独立的进程中运行,服务之间一般通过 HTTP 的 RESTfuLAPI 进行通信协作。
-
被拆分成的每一个小型服务都围绕着系统中的某一项或些耦合度较高的业务功能进行构建,并且每个服务都维护着自身的数据存储、业务开发自动化测试案例以及独立部署机
制。
- 由于有了轻量级的通信协作基础,所以这些微服务可以使用不同的语言来编写。
2. 初识Spring Cloud
2.1 SpringCloud介绍
- Spring Cloud 是一系列框架的有序集合。
- Spring Cloud 并没有重复制造轮子,它只是将目前各家公司开发的比较成熟、经得起实际考验的服务框架组合起来。
- 通过 Spring Boot 风格进行再封装屏蔽掉了复杂的配置和实现原理,最终给开发者留出了一套简单易懂、易部署和易维护的分布式系统开发工具包。
- 它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、 断路器、数据监控等,都可以用Spring Boot的开发风格做到一键启动和部署。
- Spring Cloud项目官方网址:https://spring.io/projects/spring-cloud
- Spring Cloud 版本命名方式采用了伦敦地铁站的名称,同时根据字母表的顺序来对应版本时间顺序,比如:最早的Release版本:Angel,第二个Release版本:Brixton,然后是Camden、Dalston、Edgware,Finchley,Greenwich,Hoxton。
- 目前最新的是Hoxton版本。
2.2 Spring Cloud 和dubbo对比
- Spring Cloud 与 Dubbo 都是实现微服务有效的工具。
- Dubbo 只是实现了服务治理,而 Spring Cloud 子项目分别覆盖了微服务架构下的众多部件。
- Dubbo 使用 RPC 通讯协议,Spring Cloud 使用 RESTful 完成通信,Dubbo 效率略高于 Spring Cloud。
总结
- 微服务就是将项目的各个模块拆分为可独立运行、部署、测试的架构设计风格。
Spring 公司将其他公司中微服务架构常用的组件整合起来,并使用 SpringBoot 简化其开发、配置。
称为 Spring Cloud
- Spring Cloud 与 Dubbo都是实现微服务有效的工具。Dubbo 性能更好,而 Spring Cloud 功能更全面
3. Spring Cloud服务治理
3.1 Eureka
3.1.1 介绍
• Eureka 是 Netflix 公司开源的一个服务注册与发现的组件 。
• Eureka 和其他 Netflix 公司的服务组件(例如负载均衡、熔断器、网关等) 一起,被 Spring Cloud 社区整合为
Spring-Cloud-Netflix
模块。
• Eureka 包含两个组件:Eureka Server (注册中心) 和 Eureka Client (服务提供者、服务消费者)。
3.1.2 演示案例
- 环境搭建
- 创建父工程Spring-cloud-parent
- Spring-cloud-parent pom.xml
<!--spring boot 环境 --> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.0.RELEASE</version> <relativePath/> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties>
- 创建服务提供者eureka-provider
- eureka-provider pom.xml
<dependencies> <!--spring boot web--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies>
- GoodsController
package com.itheima.provider.controller; import com.itheima.provider.domain.Goods; import com.itheima.provider.service.GoodsService; 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; /** * Goods Controller 服务提供方 */ @RestController @RequestMapping("/goods") public class GoodsController { @Autowired private GoodsService goodsService; @GetMapping("/findOne/{id}") public Goods findOne(@PathVariable("id") int id){ Goods goods = goodsService.findOne(id); return goods; } }
- GoodsService
package com.itheima.provider.service; import com.itheima.provider.dao.GoodsDao; import com.itheima.provider.domain.Goods; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; /** * Goods 业务层 */ @Service public class GoodsService { @Autowired private GoodsDao goodsDao; /** * 根据id查询 * @param id * @return */ public Goods findOne(int id){ return goodsDao.findOne(id); } }
- Goods
package com.itheima.provider.domain; /** * 商品实体类 */ public class Goods { private int id; private String title;//商品标题 private double price;//商品价格 private int count;//商品库存 public Goods() { } public Goods(int id, String title, double price, int count) { this.id = id; this.title = title; this.price = price; this.count = count; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public double getPrice() { return price; } public void setPrice(double price) { this.price = price; } public int getCount() { return count; } public void setCount(int count) { this.count = count; } }
- GoodsDao
package com.itheima.provider.dao; import com.itheima.provider.domain.Goods; import org.springframework.stereotype.Repository; import javax.validation.ReportAsSingleViolation; /** * 商品Dao */ @Repository public class GoodsDao { public Goods findOne(int id){ return new Goods(1,"华为手机",3999,10000); } }
- ProviderApp
package com.itheima.provider; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * 启动类 */ @SpringBootApplication public class ProviderApp { public static void main(String[] args) { SpringApplication.run(ProviderApp.class,args); } }
- application.yml
server: port: 8000
- 创建服务消费者eureka-consumer
- OrderController
package com.itheima.consumer.controller; import com.itheima.consumer.domain.Goods; 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; /** * 服务的调用方 */ @RestController @RequestMapping("/order") public class OrderController { @GetMapping("/goods/{id}") public Goods findGoodsById(@PathVariable("id") int id){ System.out.println("findGoodsById..."+id); //远程调用Goods服务中的findOne接口 return null; } }
- Goods
package com.itheima.consumer.domain; /** * 商品实体类 */ public class Goods { private int id; private String title;//商品标题 private double price;//商品价格 private int count;//商品库存 public Goods() { } public Goods(int id, String title, double price, int count) { this.id = id; this.title = title; this.price = price; this.count = count; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public double getPrice() { return price; } public void setPrice(double price) { this.price = price; } public int getCount() { return count; } public void setCount(int count) { this.count = count; } }
- ConsumerApp
package com.itheima.consumer; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class ConsumerApp { public static void main(String[] args) { SpringApplication.run(ConsumerApp.class,args); } }
- application.yml
server: port: 9000
3.1.3 RestTemplate远程调用
- Spring提供的一种简单便捷的模板类,用于在 java 代码里访问 restful 服务。
- 其功能与 HttpClient 类似,但是 RestTemplate 实现更优雅,使用更方便。
- 修改消费方代码
- RestTemplateConfig
package com.itheima.consumer.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.client.RestTemplate; @Configuration public class RestTemplateConfig { @Bean public RestTemplate restTemplate(){ return new RestTemplate(); } }
- OrderController
package com.itheima.consumer.controller; import com.itheima.consumer.domain.Goods; 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; /** * 服务的调用方 */ @RestController @RequestMapping("/order") public class OrderController { @Autowired private RestTemplate restTemplate; @GetMapping("/goods/{id}") public Goods findGoodsById(@PathVariable("id") int id){ System.out.println("findGoodsById..."+id); /* //远程调用Goods服务中的findOne接口 使用RestTemplate 1. 定义Bean restTemplate 2. 注入Bean 3. 调用方法 */ String url = "http://localhost:8000/goods/findOne/"+id; // 3. 调用方法 Goods goods = restTemplate.getForObject(url, Goods.class); return goods; } }
3.1.4 Eureka Server搭建
- 创建 eureka-server 模块
- 引入 SpringCloud 和 euraka-server 相关依赖
<!--Spring-cloud-parent pom.xml-->
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<!--spring cloud 版本-->
<spring-cloud.version>Greenwich.RELEASE</spring-cloud.version>
</properties>
<!--引入Spring Cloud 依赖-->
<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>
</dependencies>
</dependencyManagement>
<!--eureka-server pom.xml-->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- eureka-server -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
- EurekaApp
package com.itheima.eureka;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
// 启用EurekaServer
@EnableEurekaServer
public class EurekaApp {
public static void main(String[] args) {
SpringApplication.run(EurekaApp.class,args);
}
}
- 完成 Eureka Server 相关配置
#application.yml
server:
port: 8761
# eureka 配置
# eureka 一共有4部分 配置
# 1. dashboard:eureka的web控制台配置
# 2. server:eureka的服务端配置
# 3. client:eureka的客户端配置
# 4. instance:eureka的实例配置
eureka:
instance:
hostname: localhost # 主机名
client:
service-url:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka # eureka服务端地址,将来客户端使用该地址和eureka进行通信
register-with-eureka: false # 是否将自己的路径 注册到eureka上。eureka server 不需要的,eureka provider client 需要
fetch-registry: false # 是否需要从eureka中抓取路径。eureka server 不需要的,eureka consumer client 需要
- 启动该模块
3.1.5 Eureka控制台介绍
- System status:系统状态信息
- DS Replicas:集群信息
- tance currently registered with Eureka: 实例注册信息
- General Info :通用信息
- Instance Info :实例信息
3.1.6 Eureka Client
- 引入eureka-client 相关依赖
<!--eureka-provider pom.xml-->
<dependencies>
<!--spring boot web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- eureka-client -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
- ProviderApp
package com.itheima.provider;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
/**
* 启动类
*/
@EnableEurekaClient //该注解 在新版本中可以省略
@SpringBootApplication
public class ProviderApp {
public static void main(String[] args) {
SpringApplication.run(ProviderApp.class,args);
}
}
- 完成 eureka client 相关配置
# application.yml
server:
port: 8001
eureka:
instance:
hostname: localhost # 主机名
client:
service-url:
defaultZone: http://localhost:8761/eureka # eureka服务端地址,将来客户端使用该地址和eureka进行通信
spring:
application:
name: eureka-provider # 设置当前应用的名称。将来会在eureka中Application显示。将来需要使用该名称来获取路径
- 启动 测试
- 服务消费者eureka-consumer通过修改,也可以展示在控制台
- eureka-consumer在这里仅仅是我们人为定义为消费者,作为一个服务,其实既可以作为服务提供方,同时也可以作为服务消费方
- ConsumerApp添加@EnableEurekaClient
package com.itheima.consumer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@EnableEurekaClient
@SpringBootApplication
public class ConsumerApp {
public static void main(String[] args) {
SpringApplication.run(ConsumerApp.class,args);
}
}
- application.yml
server:
port: 9000
eureka:
instance:
hostname: localhost # 主机名
client:
service-url:
defaultZone: http://localhost:8761/eureka # eureka服务端地址,将来客户端使用该地址和eureka进行通信
spring:
application:
name: eureka-consumer # 设置当前应用的名称。将来会在eureka中Application显示。将来需要使用该名称来获取路径
3.1.7 动态获取路径
- ConsumerApp添加@EnableDiscoveryClient
package com.itheima.consumer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@EnableDiscoveryClient // 激活DiscoveryClient
@EnableEurekaClient
@SpringBootApplication
public class ConsumerApp {
public static void main(String[] args) {
SpringApplication.run(ConsumerApp.class,args);
}
}
- OrderController修改代码动态获取路径
package com.itheima.consumer.controller;
import com.itheima.consumer.domain.Goods;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
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;
import java.util.List;
/**
* 服务的调用方
*/
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private RestTemplate restTemplate;
@Autowired
private DiscoveryClient discoveryClient;
@GetMapping("/goods/{id}")
public Goods findGoodsById(@PathVariable("id") int id){
System.out.println("findGoodsById..."+id);
/*
//远程调用Goods服务中的findOne接口
使用RestTemplate
1. 定义Bean restTemplate
2. 注入Bean
3. 调用方法
*/
/*
动态从Eureka Server 中获取 provider 的 ip 和端口
1. 注入 DiscoveryClient 对象.激活
2. 调用方法
*/
//演示discoveryClient 使用
List<ServiceInstance> instances = discoveryClient.getInstances("EUREKA-PROVIDER");
//判断集合是否有数据
if(instances == null || instances.size() == 0){
//集合没有数据
return null;
}
ServiceInstance instance = instances.get(0);
String host = instance.getHost();//获取ip
int port = instance.getPort();//获取端口
System.out.println(host);
System.out.println(port);
String url = "http://"+host+":"+port+"/goods/findOne/"+id;
// 3. 调用方法
Goods goods = restTemplate.getForObject(url, Goods.class);
return goods;
}
}
3.1.8 Eureka属性
- instance相关属性
- Eureka Instance的配置信息全部保存在
配置类里,实际上它是org.springframework.cloud.netflix.eureka.EurekaInstanceConfigBean
的实现类,替代了netflix的com.netflix.appinfo.EurekaInstanceConfig
的默认实现。com.netflix.appinfo.CloudInstanceConfig
- Eureka Instance的配置信息全部以
的格式配置。eureka.instance.xxx
- 配置列表
-
:应用名,首先获取spring.application.name的值,如果取值为空,则取默认unknown。appname = unknown
-
:应用组名appGroupName = null
-
:实例注册到Eureka上是,是否立刻开启通讯。有时候应用在准备好服务之前需要一些预处理。instanceEnabledOnit = false
-
:非安全的端口nonSecurePort = 80
-
:安全端口securePort = 443
-
:是否开启非安全端口通讯nonSecurePortEnabled = true
-
:是否开启安全端口通讯securePortEnabled = false
-
:实例续约间隔时间leaseRenewalIntervalInSeconds = 30
-
:实例超时时间,表示最大leaseExpirationDurationInSeconds秒后没有续约,Server就认为他不可用了,随之就会将其剔除。leaseExpirationDurationInSeconds = 90
-
:虚拟主机名,首先获取spring.application.name的值,如果取值为空,则取默认unknown。virtualHostName = unknown
-
:注册到eureka上的唯一实例ID,不能与相同appname的其他实例重复。instanceId
-
:安全虚拟主机名,首先获取spring.application.name的值,如果取值为空,则取默认unknown。secureVirtualHostName = unknown
-
:实例元数据,可以供其他实例使用。比如spring-boot-admin在监控时,获取实例的上下文和端口。metadataMap = new HashMap();
-
:实例部署的数据中心。如AWS、MyOwn。dataCenterInfo = new MyDataCenterInfo(DataCenterInfo.Name.MyOwn);
-
:实例的IP地址ipAddress=null
-
:实例状态页相对urlstatusPageUrlPath = "/actuator/info"
-
:实例状态页绝对URLstatusPageUrl = null
-
:实例主页相对URLhomePageUrlPath = "/"
-
:实例主页绝对URLhomePageUrl = null
-
:实例健康检查相对URLhealthCheckUrlUrlPath = "/actuator/health"
-
:实例健康检查绝对URLhealthCheckUrl = null
-
:实例安全的健康检查绝对URLsecureHealthCheckUrl = null
-
:配置属性的命名空间(Spring Cloud中被忽略)namespace = "eureka"
-
:主机名,不配置的时候讲根据操作系统的主机名来获取hostname = null
-
:是否优先使用IP地址作为主机名的标识preferIpAddress = false
-
- server相关属性
- Eureka Server注册中心端的配置是对注册中心的特性配置。Eureka Server的配置全部在
里,实际上它是org.springframework.cloud.netflix.eureka.server.EurekaServerConfigBean
的实现类,替代了netflix的默认实现。com.netflix.eureka.EurekaServerConfig
- Eureka Server的配置全部以eureka.server.xxx的格式进行配置。
- Eureka Server注册中心端的配置是对注册中心的特性配置。Eureka Server的配置全部在
- 配置列表
-
:是否开启自我保护enableSelfPreservation=true
-
:自我保护续约百分比阀值因子。如果实际续约数小于续约数阀值,则开启自我保护renewalPercentThreshold = 0.85
-
:续约数阀值更新频率。renewalThresholdUpdateIntervalMs = 15 * 60 * 1000
-
:Eureka Server节点更新频率。peerEurekaNodesUpdateIntervalMs = 10 * 60 * 1000
-
:是否启用复制请求压缩。enableReplicatedRequestCompression = false
-
:当从其他节点同步实例信息为空时等待的时间。waitTimeInMsWhenSyncEmpty=5 * 60 * 1000
-
:节点间连接的超时时间。peerNodeConnectTimeoutMs=200
-
:节点间读取信息的超时时间。peerNodeReadTimeoutMs=200
-
:节点间连接总数。peerNodeTotalConnections=1000
-
:单个节点间连接总数。peerNodeTotalConnectionsPerHost = 500;
-
:节点间连接空闲超时时间。peerNodeConnectionIdleTimeoutSeconds = 30;
-
:增量队列的缓存时间。retentionTimeInMSInDeltaQueue = 3 * MINUTES;
-
:清理增量队列中过期的频率。deltaRetentionTimerIntervalInMs = 30 * 1000;
-
:剔除任务频率。evictionIntervalTimerInMs = 60 * 1000;
-
:注册列表缓存超时时间(当注册列表没有变化时)responseCacheAutoExpirationInSeconds = 180;
-
:注册列表缓存更新频率。responseCacheUpdateIntervalMs = 30 * 1000;
-
:是否开启注册列表的二级缓存。useReadOnlyResponseCache = true;
-
:是否为client提供增量信息。disableDelta=false。
-
:状态同步的最大线程数。maxThreadsForStatusReplication = 1;
-
:状态同步队列的最大容量。maxElementsInStatusReplicationPool = 10000;
-
:当时间差异时是否同步。syncWhenTimestampDiffers = true;
-
:注册信息同步重试次数。registrySyncRetries = 0;
-
:注册信息同步重试期间的时间间隔。registrySyncRetryWaitMs = 30 * 1000;
-
:节点间同步事件的最大容量。maxElementsInPeerReplicationPool = 10000;
-
:节点间同步的最小线程数。minThreadsForPeerReplication = 5;
-
:节点间同步的最大线程数。maxThreadsForPeerReplication = 20;
-
:节点间同步的最大时间,单位为毫秒。maxTimeForReplication = 30000;
-
:是否启用远程区域增量。disableDeltaForRemoteRegions = false;
-
:远程区域连接超时时间。remoteRegionConnectTimeoutMs = 1000;
-
:远程区域读取超时时间。remoteRegionReadTimeoutMs = 1000;
-
:远程区域最大连接数remoteRegionTotalConnections = 1000;
-
:远程区域单机连接数remoteRegionTotalConnectionsPerHost = 500;
-
:远程区域连接空闲超时时间。remoteRegionConnectionIdleTimeoutSeconds = 30;
-
:远程区域注册信息拉取频率。remoteRegionRegistryFetchInterval = 30;
-
:远程区域注册信息线程数。remoteRegionFetchThreadPoolSize = 20;
-
3.1.9 Eureka高可用
- 搭建
- 准备两个Eureka Server
- 分别进行配置,相互注册
- Eureka Client 分别注册到这两个 Eureka Server中
- 创建eureka-server-1
- pom.xml
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- eureka-server --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency> </dependencies>
- application.yml
server: port: 8761 eureka: instance: hostname: eureka-server1 # 主机名 client: service-url: defaultZone: http://eureka-server2:8762/eureka register-with-eureka: true # 是否将自己的路径 注册到eureka上。eureka server 不需要的,eureka provider client 需要 fetch-registry: true # 是否需要从eureka中抓取路径。eureka server 不需要的,eureka consumer client 需要 spring: application: name: eureka-server-ha
- Eureka1App
package eureka; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; @SpringBootApplication // 启用EurekaServer @EnableEurekaServer public class Eureka1App { public static void main(String[] args) { SpringApplication.run(Eureka1App.class,args); } }
- 创建eureka-server-2
- pom.xml
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- eureka-server --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency> </dependencies>
- application.yml
server: port: 8762 eureka: instance: hostname: eureka-server2 # 主机名 client: service-url: defaultZone: http://eureka-server1:8761/eureka register-with-eureka: true # 是否将自己的路径 注册到eureka上。eureka server 不需要的,eureka provider client 需要 fetch-registry: true # 是否需要从eureka中抓取路径。eureka server 不需要的,eureka consumer client 需要 spring: application: name: eureka-server-ha
- Eureka2App
package eureka; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; @SpringBootApplication // 启用EurekaServer @EnableEurekaServer public class Eureka2App { public static void main(String[] args) { SpringApplication.run(Eureka2App.class,args); } }
- 修改本地host
- 客户端测试
- 修改服务提供者和服务消费者配置文件中的注册服务地址
- eureka-provider application.yml
server: port: 8001 eureka: instance: hostname: localhost # 主机名 prefer-ip-address: true # 将当前实例的ip注册到eureka server 中。默认是false 注册主机名 ip-address: 127.0.0.1 # 设置当前实例的ip instance-id: ${eureka.instance.ip-address}:${spring.application.name}:${server.port} # 设置web控制台显示的 实例id lease-renewal-interval-in-seconds: 3 # 每隔3 秒发一次心跳包 lease-expiration-duration-in-seconds: 9 # 如果9秒没有发心跳包,服务器呀,你把我干掉吧~ client: service-url: defaultZone: http://eureka-server1:8761/eureka,http://eureka-server2:8762/eureka # eureka服务端地址,将来客户端使用该地址和eureka进行通信 spring: application: name: eureka-provider # 设置当前应用的名称。将来会在eureka中Application显示。将来需要使用该名称来获取路径
- eureka-consumer application.yml
server: port: 9000 eureka: instance: hostname: localhost # 主机名 client: service-url: defaultZone: http://eureka-server1:8761/eureka,http://eureka-server2:8762/eureka # eureka服务端地址,将来客户端使用该地址和eureka进行通信 spring: application: name: eureka-consumer # 设置当前应用的名称。将来会在eureka中Application显示。将来需要使用该名称来获取路径
- 测试结果
3.2 Consul
3.2.1 Consul 概述
- Consul 是由 HashiCorp 基于 Go 语言开发的,支持多数据中心,分布式高可用的服务发布和注册服务软件。
- 用于实现分布式系统的服务发现与配置。
- 使用起来也较 为简单。具有天然可移植性(支持Linux、windows和Mac OS X);安装包仅包含一个可执行文件,方便部署 。
- 官网地址: https://www.consul.io
3.2.2 启动consul
- dev模式:不会持久化数据
- 启动成功
- 控制台
3.2.3 Consul案例演示
- 步骤
- 搭建 Provider 和 Consumer 服务。
- 使用 RestTemplate 完成远程调用。
- 将Provider服务注册到Consul中。
- Consumer 服务 通过从 Consul 中抓取 Provider 地址 完成 远程调用
- Provider pom.xml
<dependencies>
<!--consul 客户端-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
- application.yml
server:
port: 8000
spring:
cloud:
consul:
host: localhost # consul 服务端的 ip
port: 8500 # consul 服务端的端口 默认8500
discovery:
service-name: ${spring.application.name} # 当前应用注册到consul的名称
prefer-ip-address: true # 注册ip
application:
name: consul-provider # 应用名称
- consumer pom.xml
<dependencies>
<!--consul 客户端-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
- application.yml
server:
port: 9000
spring:
cloud:
consul:
host: localhost # consul 服务端的 ip
port: 8500 # consul 服务端的端口 默认8500
discovery:
service-name: ${spring.application.name} # 当前应用注册到consul的名称
prefer-ip-address: true # 注册ip
application:
name: consul-consumer # 应用名称
- OrderController
package com.itheima.consul.controller;
import com.itheima.consul.domain.Goods;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
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;
import java.util.List;
/**
* 服务的调用方
*/
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private RestTemplate restTemplate;
@Autowired
private DiscoveryClient discoveryClient;
@GetMapping("/goods/{id}")
public Goods findGoodsById(@PathVariable("id") int id){
//演示discoveryClient 使用
List<ServiceInstance> instances = discoveryClient.getInstances("consul-PROVIDER");
//判断集合是否有数据
if(instances == null || instances.size() == 0){
//集合没有数据
return null;
}
ServiceInstance instance = instances.get(0);
String host = instance.getHost();//获取ip
int port = instance.getPort();//获取端口
System.out.println(host);
System.out.println(port);
String url = "http://"+host+":"+port+"/goods/findOne/"+id;
// 3. 调用方法
Goods goods = restTemplate.getForObject(url, Goods.class);
return goods;
}
}
3.3 Nacos
3.3.1 Nacos 概述
- Nacos(Dynamic Naming and Configuration Service) 是阿里巴巴2018年7月开源的项目。
- 它专注于服务发现和配置管理领域 致力于帮助您发现、配置和管理微服务。Nacos 支持几乎所有主流类型的“服务”的发现、配置和管理。
- 一句话概括就是Nacos = Spring Cloud注册中心 + Spring Cloud配置中心。
- 官网:https://nacos.io/
- 下载地址: https://github.com/alibaba/nacos/releases
3.3.2 启动
3.3.3 控制台
- 控制台登录(账号,密码:nacos)
- 控制台页面
- Spring cloud Alibaba 组件
3.3.4 Nacos 演示案例
- nacos-provider pom.xml
<dependencies>
<!--nacos-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>0.2.2.RELEASE</version>
</dependency>
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
<version>1.1.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
- application.yml
server:
port: 8000
spring:
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848 # 配置nacos 服务端地址
application:
name: nacos-provider # 服务名称
- nacos consumer pom.xml
<dependencies>
<!--nacos-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>0.2.2.RELEASE</version>
</dependency>
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
<version>1.1.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
- application.yml
server:
port: 9000
spring:
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848 # 配置nacos 服务端地址
application:
name: nacos-consumer # 服务名称
- 控制台显示
- 详情页面
- 示例代码
4. Ribbon
- 客户端的负载均衡器
- ribbon有七种负载均衡策略可以选择
策略类 命名 描述 RandomRule 随机策略 随机选择server RoundRobinRule 轮询策略 按照顺序选择server(ribbon默认策略) RetryRule 重试策略 在一个配置时间段内,当选择server不成功,则一直尝试选择一个可用的server BestAvailableRule 最低并发策略 逐个考察server,如果server断路器打开,则忽略,再选择其中并发链接最低的server AvailabilityFilteringRule 可用过滤策略 过滤掉一直失败并被标记为circuit tripped的server,过滤掉那些高并发链接的server(active connections超过配置的阈值) ResponseTimeWeightedRule 响应时间加权重策略 根据server的响应时间分配权重,响应时间越长,权重越低,被选择到的概率也就越低,响应时间越短,权重越高,被选中的概率越高,这个策略很贴切,综合了各种因素 ZoneAvoidanceRule 区域权重策略 综合判断server所在区域的性能,和server的可用性,轮询选择server并且判断一个AWS Zone的运行性能是否可用,剔除不可用的Zone中的所有server
5. Feign
5.1 Feign概述
- Feign 是一个声明式的 REST 客户端,它用了基于接口的注解方式,很方便实现客户端配置。
- Feign 最初由 Netflix 公司提供,但不支持SpringMVC注解,后由 SpringCloud 对其封装,支持了SpringMVC注解,让使用者更易于接受
5.2 Feign演示案例
- 在消费端引入 open-feign 依赖
<!--feign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
- 编写Feign调用接口
package com.itheima.consumer.feign;
import com.itheima.consumer.config.FeignLogConfig;
import com.itheima.consumer.domain.Goods;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
/**
*
* feign声明式接口。发起远程调用的。
*
String url = "http://FEIGN-PROVIDER/goods/findOne/"+id;
Goods goods = restTemplate.getForObject(url, Goods.class);
*
* 1. 定义接口
* 2. 接口上添加注解 @FeignClient,设置value属性为 服务提供者的 应用名称
* 3. 编写调用接口,接口的声明规则 和 提供方接口保持一致。
* 4. 注入该接口对象,调用接口方法完成远程调用
*/
@FeignClient(value = "FEIGN-PROVIDER")
public interface GoodsFeignClient {
@GetMapping("/goods/findOne/{id}")
public Goods findGoodsById(@PathVariable("id") int id);
}
- OrderController
package com.itheima.consumer.controller;
import com.itheima.consumer.domain.Goods;
import com.itheima.consumer.feign.GoodsFeignClient;
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;
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private RestTemplate restTemplate;
@Autowired
private GoodsFeignClient goodsFeignClient;
@GetMapping("/goods/{id}")
public Goods findGoodsById(@PathVariable("id") int id){
/*
String url = "http://FEIGN-PROVIDER/goods/findOne/"+id;
// 3. 调用方法
Goods goods = restTemplate.getForObject(url, Goods.class);
return goods;*/
Goods goods = goodsFeignClient.findGoodsById(id);
return goods;
}
}
- goodsFeignClient报红,不影响使用
- 在启动类 添加 @EnableFeignClients 注解,开启Feign功能
package com.itheima.consumer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@EnableDiscoveryClient // 激活DiscoveryClient
@EnableEurekaClient
@SpringBootApplication
@EnableFeignClients //开启Feign的功能
public class ConsumerApp {
public static void main(String[] args) {
SpringApplication.run(ConsumerApp.class,args);
}
}
- 测试调用
5.3 Feign超时配置
- Feign 底层依赖于 Ribbon 实现负载均衡和远程调用。
- Ribbon默认1秒超时。
- 超时配置:
#feign-consumer application.yml
# 设置Ribbon的超时时间
ribbon:
ConnectTimeout: 1000 # 连接超时时间 默认1s 默认单位毫秒
ReadTimeout: 3000 # 逻辑处理的超时时间 默认1s 默认单位毫秒
5.4 Feign日志记录
- Feign 只能记录 debug 级别的日志信息。
- feign-consumer application.yml
# 设置当前的日志级别 debug,feign只支持记录debug级别的日志 logging: level: com.itheima: debug
- 定义Feign日志级别Bean
- FeignLogConfig
package com.itheima.consumer.config; import feign.Logger; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class FeignLogConfig { /* NONE,不记录 BASIC,记录基本的请求行,响应状态码数据 HEADERS,记录基本的请求行,响应状态码数据,记录响应头信息 FULL;记录完成的请求 响应数据 */ @Bean public Logger.Level level(){ return Logger.Level.FULL; } }
- 启用该Bean:
package com.itheima.consumer.feign; import com.itheima.consumer.config.FeignLogConfig; import com.itheima.consumer.domain.Goods; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; /** * * feign声明式接口。发起远程调用的。 * String url = "http://FEIGN-PROVIDER/goods/findOne/"+id; Goods goods = restTemplate.getForObject(url, Goods.class); * * 1. 定义接口 * 2. 接口上添加注解 @FeignClient,设置value属性为 服务提供者的 应用名称 * 3. 编写调用接口,接口的声明规则 和 提供方接口保持一致。 * 4. 注入该接口对象,调用接口方法完成远程调用 */ @FeignClient(value = "FEIGN-PROVIDER",configuration = FeignLogConfig.class) public interface GoodsFeignClient { @GetMapping("/goods/findOne/{id}") public Goods findGoodsById(@PathVariable("id") int id); }
6. Hystrix
6.1 Hystrix概述
- Hystix 是 Netflix 开源的一个延迟和容错库,用于隔离访问远程服务、第三方库,防止出现级联失败(雪崩)。
- 雪崩:一个服务失败,导致整条链路的服务都失败的情形
- Hystix 主要功能
- 隔离
- 线程池隔离
- 信号量隔离
- 降级
- 异常
- 超时
- 熔断
- 限流
- 隔离
6.2 Hystrix-降级
- Hystix 降级:当服务发生异常或调用超时,返回默认数据
- 服务提供方降级
- 在服务提供方,引入 hystrix 依赖
<!-- hystrix --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency>
- 定义降级方法
/** * 定义降级方法: * 1. 方法的返回值需要和原方法一样 * 2. 方法的参数需要和原方法一样 */ public Goods findOne_fallback(int id){ Goods goods = new Goods(); goods.setTitle("降级了~~~"); return goods; }
- 使用 @HystrixCommand 注解配置降级方法
/** * 降级: * 1. 出现异常 * 2. 服务调用超时 * * 默认1s超时 * * @HystrixCommand(fallbackMethod = "findOne_fallback") * fallbackMethod:指定降级后调用的方法名称 */ @GetMapping("/findOne/{id}") @HystrixCommand(fallbackMethod = "findOne_fallback",commandProperties = { //设置Hystrix的超时时间,默认1s @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value = "3000") }) public Goods findOne(@PathVariable("id") int id){ //1.造个异常 int i = 3/0; try { //2. 休眠2秒 Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } Goods goods = goodsService.findOne(id); goods.setTitle(goods.getTitle() + ":" + port);//将端口号,设置到了 商品标题上 return goods; }
- 在启动类上开启Hystrix功能:@EnableCircuitBreaker
package com.itheima.provider; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; /** * 启动类 */ @EnableEurekaClient //该注解 在新版本中可以省略 @SpringBootApplication @EnableCircuitBreaker // 开启Hystrix功能 public class ProviderApp { public static void main(String[] args) { SpringApplication.run(ProviderApp.class,args); } }
- 消费方降级
- feign 组件已经集成了 hystrix 组件。
- 定义feign 调用接口实现类,复写方法,即 降级方法
- GoodsFeignClientFallback
package com.itheima.consumer.feign; import com.itheima.consumer.domain.Goods; import org.springframework.stereotype.Component; /** * Feign 客户端的降级处理类 * 1. 定义类 实现 Feign 客户端接口 * 2. 使用@Component注解将该类的Bean加入SpringIOC容器 */ @Component public class GoodsFeignClientFallback implements GoodsFeignClient { @Override public Goods findGoodsById(int id) { Goods goods = new Goods(); goods.setTitle("又被降级了~~~"); return goods; } }
- 在 @FeignClient 注解中使用 fallback 属性设置降级处理类。
- GoodsFeignClient
package com.itheima.consumer.feign; import com.itheima.consumer.domain.Goods; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @FeignClient(value = "HYSTRIX-PROVIDER",fallback = GoodsFeignClientFallback.class) public interface GoodsFeignClient { @GetMapping("/goods/findOne/{id}") public Goods findGoodsById(@PathVariable("id") int id); }
- 配置开启 feign.hystrix.enabled = true
- application.yml
# 开启feign对hystrix的支持 feign: hystrix: enabled: true
6.3 Hystrix熔断
- 概念
- Hystrix 熔断机制,用于监控微服务调用情况,当失败的情况达到预定的阈值(5秒失败20次),会打开断路器,拒绝所有请求,直到服务恢复正常为止。
- 断路器三种状态:打开、半开、关闭、
- 熔断-代码演示
- 修改服务提供方的方法,演示熔断机制
- 熔断配置
- circuitBreaker.sleepWindowInMilliseconds:监控时间
- circuitBreaker.requestVolumeThreshold:失败次数
- circuitBreaker.errorThresholdPercentage:失败率
- GoodsController
package com.itheima.provider.controller; import com.itheima.provider.domain.Goods; import com.itheima.provider.service.GoodsService; import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty; import org.springframework.beans.factory.annotation.Autowired; 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; import java.util.Date; /** * Goods Controller 服务提供方 */ @RestController @RequestMapping("/goods") public class GoodsController { @Autowired private GoodsService goodsService; @Value("${server.port}") private int port; /** * 降级: * 1. 出现异常 * 2. 服务调用超时 * * 默认1s超时 * * @HystrixCommand(fallbackMethod = "findOne_fallback") * fallbackMethod:指定降级后调用的方法名称 */ @GetMapping("/findOne/{id}") @HystrixCommand(fallbackMethod = "findOne_fallback",commandProperties = { //设置Hystrix的超时时间,默认1s @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value = "3000"), //监控时间 默认5000 毫秒 @HystrixProperty(name="circuitBreaker.sleepWindowInMilliseconds",value = "5000"), //失败次数。默认20次 @HystrixProperty(name="circuitBreaker.requestVolumeThreshold",value = "20"), //失败率 默认50% @HystrixProperty(name="circuitBreaker.errorThresholdPercentage",value = "50") }) public Goods findOne(@PathVariable("id") int id){ //如果id == 1 ,则出现异常,id != 1 则正常访问 if(id == 1){ //1.造个异常 int i = 3/0; } /*try { //2. 休眠2秒 Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); }*/ Goods goods = goodsService.findOne(id); goods.setTitle(goods.getTitle() + ":" + port);//将端口号,设置到了 商品标题上 return goods; } /** * 定义降级方法: * 1. 方法的返回值需要和原方法一样 * 2. 方法的参数需要和原方法一样 */ public Goods findOne_fallback(int id){ Goods goods = new Goods(); goods.setTitle("降级了~~~"); return goods; } }
- 熔断监控
- Hystrix 提供了 Hystrix-dashboard 功能,用于实时监控微服务运行状态。
- 但是Hystrix-dashboard只能监控一个微服务。
- Netflix 还提供了 Turbine ,进行聚合监控。
7. Gateway
7.1 Gateway概述
- 网关旨在为微服务架构提供一种简单而有效的统一的API路由管理方式。
- 在微服务架构中,不同的微服务可以有不同的网络地址,各个微服务之间通过互相调用完成用户请求,客户端可能通过调用N个微服务的接口完成一个用户请求。
- 存在的问题
- 客户端多次请求不同的微服务,增加客户端的复杂性
- 认证复杂,每个服务都要进行认证
- http请求不同服务次数增加,性能不高
- 网关就是系统的入口,封装了应用程序的内部结构,为客户端提供统一服务,一些与业务本身功能无关的公共逻辑可以在这里实现,诸如认证、鉴权、监控、缓存、负载均衡、流量管控、路由转发等
- 在目前的网关解决方案里,有Nginx+ Lua、Netflix Zuul 、Spring Cloud Gateway等等
7.2 Gateway演示案例
-
创建api-gateway-server模块
引入依赖:starter-gateway
<dependencies>
<!--引入gateway 网关-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- eureka-client -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
- 编写启动类
package com.itheima.gateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableEurekaClient
public class ApiGatewayApp {
public static void main(String[] args) {
SpringApplication.run(ApiGatewayApp.class,args);
}
}
-
编写配置文件
application.yml
server:
port: 80
spring:
application:
name: api-gateway-server
cloud:
# 网关配置
gateway:
# 路由配置:转发规则
routes: #集合。
# id: 唯一标识。默认是一个UUID
# uri: 转发路径
# predicates: 条件,用于请求网关路径的匹配规则
- id: gateway-provider
uri: http://localhost:8001/
predicates:
- Path=/goods/**
- 启动测试
7.3 Gateway静态路由
- application.yml 中的uri是写死的,就是静态路由
server:
port: 80
spring:
application:
name: api-gateway-server
cloud:
# 网关配置
gateway:
# 路由配置:转发规则
routes: #集合。
# id: 唯一标识。默认是一个UUID
# uri: 转发路径
# predicates: 条件,用于请求网关路径的匹配规则
# filters:配置局部过滤器的
- id: gateway-provider
# 静态路由
uri: http://localhost:8001/
predicates:
- Path=/goods/**
7.4 Gateway动态路由
- 启动类添加@EnableEurekaClient(新版本不加也可以)
package com.itheima.gateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableEurekaClient
public class ApiGatewayApp {
public static void main(String[] args) {
SpringApplication.run(ApiGatewayApp.class,args);
}
}
- 引入eureka-client配置
- application.yml 中修改uri属性:uri: lb://服务名称
server:
port: 80
spring:
application:
name: api-gateway-server
cloud:
# 网关配置
gateway:
# 路由配置:转发规则
routes: #集合。
# id: 唯一标识。默认是一个UUID
# uri: 转发路径
# predicates: 条件,用于请求网关路径的匹配规则
# filters:配置局部过滤器的
- id: gateway-provider
# 静态路由
# uri: http://localhost:8001/
# 动态路由
uri: lb://GATEWAY-PROVIDER
predicates:
- Path=/goods/**
7.5 Gateway微服务名称配置
- application.yml中配置微服务名称配置
# 微服务名称配置
discovery:
locator:
enabled: true # 设置为true 请求路径前可以添加微服务名称
lower-case-service-id: true # 允许为小写
7.6 Gateway过滤器
7.6.1 概述
- Gateway 支持过滤器功能,对请求或响应进行拦截,完成一些通用操作。
- Gateway 提供两种过滤器方式:“pre”和“post”
- pre 过滤器,在转发之前执行,可以做参数校验、权限校验、流量监控、日志输出、协议转换等。
- post 过滤器,在响应之前执行,可以做响应内容、响应头的修改,日志的输出,流量监控等。
- Gateway 还提供了两种类型过滤器
- GatewayFilter:局部过滤器,针对单个路由
- GlobalFilter :全局过滤器,针对所有路由
7.6.2 局部过滤器
- GatewayFilter 局部过滤器,是针对单个路由的过滤器。
- 在Spring Cloud Gateway 组件中提供了大量内置的局部过滤器,对请求和响应做过滤操作。
- 遵循约定大于配置的思想,只需要在配置文件配置局部过滤器名称,并为其指定对应的值,就可以让其生效。
7.6.3 全局过滤器
- GlobalFilter 全局过滤器,不需要在配置文件中配置,系统初始化时加载,并作用在每个路由上。
- Spring Cloud Gateway 核心的功能也是通过内置的全局过滤器来完成。
- 自定义全局过滤器步骤:
- 定义类实现 GlobalFilter 和 Ordered接口
- 复写方法
- 完成逻辑处理
- MyFilter
package com.itheima.gateway.filter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Component
public class MyFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println("自定义全局过滤器执行了~~~");
return chain.filter(exchange);//放行
}
/**
* 过滤器排序
* @return 数值越小 越先执行
*/
@Override
public int getOrder() {
return 0;
}
}
8. config
8.1 config概述
- Spring Cloud Config 解决了在分布式场景下多环境配置文件的管理和维护。
- 好处:
- 集中管理配置文件
- 不同环境不同配置,动态化的配置更新
- 配置信息改变时,不需要重启即可更新配置信息到服务
8.2 config入门
8.2.1 gitee搭建远程仓库
- 编写仓库名称、仓库路径、公开(公开的比较方便)
- 语言和模板可以不选,一般使用单分支模型,只创建master分支
- 使用小乌龟工具,将远程仓库clone到本地
- 使用小乌龟工具将配置文件提交到远程仓库
8.2.2 config server搭建
- config server:
- 使用gitee创建远程仓库,上传配置文件
- 搭建 config server 模块
- 导入 config-server 依赖
<dependencies> <!-- config-server --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-server</artifactId> </dependency> </dependencies>
- 编写配置,设置 gitee 远程仓库地址
server: port: 9527 spring: application: name: config-server # spring cloud config cloud: config: server: # git 的 远程仓库地址 git: uri: https://gitee.com/itheima_cch/itheima-configs.git label: master # 分支配置
- 测试访问远程配置文件
8.2.3 config client搭建
- 导入 starter-config 依赖
<!--config client -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
-
配置config server 地址,读取配置文件名称等信息
创建配置文件bootstrap.yml
# 配置config-server地址
# 配置获得配置文件的名称等信息
spring:
cloud:
config:
# 配置config-server地址
uri: http://localhost:9527
# 配置获得配置文件的名称等信息
name: config # 文件名
profile: dev # profile指定, config-dev.yml
label: master # 分支
- 获取配置值
@Value("${itheima}")
private String itheima;
- 启动测试
8.2.4 config client刷新
- 在 config 客户端引入 actuator 依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
- 获取配置信息类上,添加 @RefreshScope 注解
/**
* Goods Controller 服务提供方
*/
@RestController
@RequestMapping("/goods")
@RefreshScope // 开启刷新功能
public class GoodsController {
@Autowired
private GoodsService goodsService;
@Value("${server.port}")
private int port;
@Value("${itheima}")
private String itheima;
...
}
- 添加配置management.endpoints.web.exposure.include: refresh
# 配置config-server地址
# 配置获得配置文件的名称等信息
spring:
cloud:
config:
# 配置config-server地址
uri: http://localhost:9527
# 配置获得配置文件的名称等信息
name: config # 文件名
profile: dev # profile指定, config-dev.yml
label: master # 分支
management:
endpoints:
web:
exposure:
include: refresh
- 使用curl工具发送post请求
curl -X POST http://localhost:8001/actuator/refresh
8.3 config集成Eureka
- config-server pom.xml中引入eureka-client 坐标
<!-- eureka-client -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
- 配置文件中配置eureka地址
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
- 启动类中添加注解
package com.itheima.config;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;
@SpringBootApplication
@EnableConfigServer // 启用config server功能
@EnableEurekaClient
public class ConfigServerApp {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApp.class,args);
}
}
- config-provider 工程中bootstrap.yaml中注掉写死的地址,改为从Eureka中获取
# 配置config-server地址
# 配置获得配置文件的名称等信息
spring:
cloud:
config:
# 配置config-server地址
#uri: http://localhost:9527
# 配置获得配置文件的名称等信息
name: config # 文件名
profile: dev # profile指定, config-dev.yml
label: master # 分支
#从注册中心获取config-server地址
discovery:
enabled:true
service-id:CONFIG-SERVER
9. bus
9.1 bus概述
- Spring Cloud Bus 是用轻量的消息中间件将分布式的节点连接起来,可以用于广播配置文件的更改或者服务的监控管理。关键的思想就是,消息总线可以为微服务做监控,也可以实现应用程序之间相通信。
- Spring Cloud Bus 可选的消息中间件包括 RabbitMQ 和 Kafka
9.2 bus-rabbitmq回顾
- RabbitMQ 提供了 6 种工作模式:简单模式、work queues、Publish/Subscribe 发布与订阅模式、Routing路由模式、Topics 主题模式、RPC 远程调用模式(远程调用,不太算 MQ;暂不作介绍)。
9.3 bus演示案例
- 分别在 config-server 和 config-client中引入 bus依赖:bus-amqp
<!-- bus -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
- 分别在 config-server 和 config-client中配置 RabbitMQ
- bootstrap.yml
#配置rabbitmq信息
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
virtual-host: /
- OrderController上添加@RefreshScope注解
@RestController
@RequestMapping("/order")
@RefreshScope
public class OrderController {
@Value("${itheima}")
private String itheima;
...
}
- 在config-server中设置暴露监控断点:bus-refresh
- application.yml
# 暴露bus的刷新端点
management:
endpoints:
web:
exposure:
include: 'bus-refresh'
- 启动测试
- curl结果中没有信息,说明成功了
10. stream
10.1 stream概述
- Spring Cloud Stream 是一个构建消息驱动微服务应用的框架。
- Stream 解决了开发人员无感知的使用消息中间件的问题,因为Stream对消息中间件的进一步封装,可以做到代码层面对中间件的无感知,甚至于动态的切换中间件,使得微服务开发的高度解耦,服务可以关注更多自己的业务流程。
- Spring Cloud Stream目前支持两种消息中间件RabbitMQ和Kafka
10.2 stream组件
- Spring Cloud Stream 构建的应用程序与消息中间件之间是通过绑定器 Binder相关联的。绑定器对于应用程序而言起到了隔离作用, 它使得不同消息中间件的实现细节对应用程序来说是透明的。
- binding 是我们通过配置把应用和spring cloud stream 的 binder 绑定在一起
- output:发送消息 Channel,内置 Source接口
- input:接收消息 Channel,内置 Sink接口
10.3 stream消息生产者
- 创建消息生产者模块,引入依赖 starter-stream-rabbit
<!-- stream -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>
- 编写配置,定义 binder,和 bingings
server:
port: 8000
spring:
cloud:
stream:
# 定义绑定器,绑定到哪个消息中间件上
binders:
itheima_binder: # 自定义的绑定器名称
type: rabbit # 绑定器类型
environment: # 指定mq的环境
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
virtual-host: /
bindings:
output: # channel名称
binder: itheima_binder #指定使用哪一个binder
destination: itheima_exchange # 消息目的地
- 定义消息发送业务类。添加 @EnableBinding(Source.class),注入MessageChannel output ,完成消息发送
- MessageProducer
package com.itheima.stream.producer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.messaging.Source;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Component;
@Component
@EnableBinding(Source.class)
public class MessageProducer {
@Autowired
private MessageChannel output;
public void send(){
String msessage = "hello stream~~~";
//发送消息
output.send(MessageBuilder.withPayload(msessage).build());
System.out.println("消息发送成功~~~");
}
}
- ProducerController
package com.itheima.stream.producer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ProducerController {
@Autowired
private MessageProducer producer;
@RequestMapping("/send")
public String sendMsg(){
producer.send();
return "success";
}
}
- 编写启动类,测试
package com.itheima.stream;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ProducerApp {
public static void main(String[] args) {
SpringApplication.run(ProducerApp.class,args);
}
}
10.4 stream消息消费者
- 创建消息消费者模块,引入依赖 starter-stream-rabbit
<!-- stream -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>
- 编写配置,定义 binder,和 bingings
server:
port: 9000
spring:
cloud:
stream:
# 定义绑定器,绑定到哪个消息中间件上
binders:
itheima_binder: # 自定义的绑定器名称
type: rabbit # 绑定器类型
environment: # 指定mq的环境
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
virtual-host: /
bindings:
output: # channel名称
binder: itheima_binder #指定使用哪一个binder
destination: itheima_exchange # 消息目的地
- 定义消息接收业务类。添加 @EnableBinding(Sink.class),使用@StreamListener(Sink.INPUT),完成消息接收。
package com.itheima.stream.consumer;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.annotation.StreamListener;
import org.springframework.cloud.stream.messaging.Sink;
import org.springframework.messaging.Message;
import org.springframework.stereotype.Component;
/**
* 消息接收类
*/
@EnableBinding({Sink.class})
@Component
public class MessageListener {
@StreamListener(Sink.INPUT)
public void receive(Message message){
System.out.println(message);
System.out.println(message.getPayload());
}
}
- 编写启动类,测试
package com.itheima.stream;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ConsumerApp {
public static void main(String[] args) {
SpringApplication.run(ConsumerApp.class,args);
}
}
11. Sleuth+Zipkin
11.1 概述
- Spring Cloud Sleuth 其实是一个工具,它在整个分布式系统中能跟踪一个用户请求的过程,捕获这些跟踪数据,就能构建微服务的整个调用链的视图,这是调试和监控微服务的关键工具。
- 耗时分析
- 可视化错误
- 链路优化
-
Zipkin 是 Twitter 的一个开源项目,它致力于收集服务的定时数据,以解决微服务架构中的延迟问题,包
括数据的收集、存储、查找和展现。
11.2 入门
- 安装启动zipkin。 java –jar zipkin.jar
- 启动成功日志
- 访问zipkin web界面。
http://localhost:9411/
- 在服务提供方和消费方分别引入 sleuth 和 zipkin 依赖
<!-- sleuth-zipkin -->
<!--<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
- 分别配置服务提供方和消费方。
- sleuth-provider application.yaml
server:
port: 8001
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka
spring:
application:
name: feign-provider
zipkin:
base-url: http://localhost:9411/ # 设置zipkin的服务端路径
sleuth:
sampler:
probability: 1 # 采集率 默认 0.1 百分之十。
- sleuth-consumer application.yaml
server:
port: 9000
eureka:
instance:
hostname: localhost # 主机名
client:
service-url:
defaultZone: http://localhost:8761/eureka
spring:
application:
name: feign-consumer # 设置当前应用的名称。将来会在eureka中Application显示。将来需要使用该名称来获取路径
zipkin:
base-url: http://localhost:9411/ # 设置zipkin的服务端路径
sleuth:
sampler:
probability: 1 # 采集率 默认 0.1 百分之十。
logging:
level:
com.itheima: debug
- 启动,测试 http://localhost:9411/
- 详细信息