赞
踩
微服务架构是一种架构风格,它提倡将单一的应用划分为一组小的服务,每个服务都围绕着具体的业务进行构建,服务之间互相协调、互相配合,为用户提供最终价值。每个微服务都运行在其独立的进程中,服务与服务之间采用轻量级的通信机制互相协作。
将项目拆分,并部署在不同的服务器上
SpringCloud 是分布式微服务架构的一站式解决方案,是多种微服务架构落地技术的几何体,即微服务全家桶
SpringCloud 的版本号是使用 伦敦地铁站 的方式,即ABCDE.... 的方式,每升级一个版本号,就升级一阶符号
https://spring.io/projects/spring-cloud#overview
选型提示:https://start.spring.io/actuator/info
json 工具: https://tool.lu/
SpringCloud 封装了 Eureka 实现服务治理
在传统的 rpc 远程调用框架中,管理每个服务与服务之间的依赖关系比较复杂,故而需要使用到服务治理,即统一管理服务与服务之间的依赖调用关系,可以实现服务调用、负载均衡、容错等,完成服务的发现与注册。
Eureka 采用了 CS 的设计架构,Eureka Server 作为服务注册功能的服务器,是服务注册中心。而系统中其他的微服务,使用 Eureka 客户端连接到 Eureka Server 并维持心跳连接,这样就实现了系统中各个微服务运行状态的监控。
● 服务注册
Eureka Server 作为一个服务注册中心,当每一个 Eureka 客户端启动后,会将当前自己服务器的信息,如服务地址、通讯地址等以别名的方式(如 cloud-user-order)注册到注册中心上。
● 服务发现
服务消费者会以别名方式将自己注册到注册中心上,并去注册中心上获取到实际要调用的服务通讯地址,再通过 rpc 方式远程调用到对应的服务
● 流程图
● Eureka Server
Eureka Server 主要提供服务注册功能,在各个微服务节点启动会,会在 Eureka Server 中注册,Eureka Server 的服务注册表中将存储所有可用服务节点的信息
● Eureka Client
Eureka Client 是一个 Java 客户端,主要用于与 Eureka Server 交互。同时客户端内置了一个负载均衡器,负责支持负载均衡。同时,在应用启动后,Eureka Client 会给 Eureka Server 发送心跳(默认30s),若 Eureka Server 在多个心跳周期内没有收到某个节点的心跳,则 Eureka Server 会将其中从服务注册表中移除(默认90s)
Eureka 高可用是通过搭建 Eureka 集群,实现负载均衡、故障容错,来达到高可用的。
Eureka 集群中的节点,互相注册,互相监控,实现集群
增加 maven 配置
- <!-- eureka 服务端 -->
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
- </dependency>
增加配置文件 application.yml
- server:
- port: 7001
- eureka:
- instance:
- hostname: eureka7001
- client:
- # false 即不像注册中心注册自己
- register-with-eureka: false
- # false 表示自己就是注册中心,不用去检索服务
- fetch-registry: false
- service-url:
- # 设置与 eureka server 交互的地址,查询服务和注册服务都需要依赖这个地址
- # 若是单机版,则配置自己的地址
- # 若是集群版,则配置其他 eureka 服务的访问地址
- defaultZone: http://eureka7002.com:7002/eureka/
- server:
- port: 7002
- eureka:
- instance:
- hostname: eureka7002
- client:
- # false 即不向注册中心注册自己
- register-with-eureka: false
- # false 表示自己就是注册中心,不用去检索服务
- fetch-registry: false
- service-url:
- # 设置与 eureka server 交互的地址,查询服务和注册服务都需要依赖这个地址
- # 若是单机版,则配置自己的地址
- # 若是集群版,则配置其他 eureka 服务的访问地址
- defaultZone: http://eureka7001.com:7001/eureka/
修改启动类,使用注解 @EnableEurekaServer
- @SpringBootApplication
- @EnableEurekaServer
- public class Eureka02Applicaiton {
- public static void main(String[] args) {
- SpringApplication.run(Eureka02Applicaiton.class, args);
- }
- }
访问查看(配置了两个,7001 和 7002):
添加 maven 配置
- <!-- eureka 客户端 -->
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
- </dependency>
修改配置文件 application.yml
- eureka:
- client:
- #表示是否将自己注册进EurekaServer默认为true。
- register-with-eureka: true
- #是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
- fetchRegistry: true
- service-url:
- defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
- instance:
- instance-id: consumer9001
修改启动类,添加注解 @EnableEurekaClient
- @SpringBootApplication
- @EnableEurekaClient
- public class OrderApplication {
- public static void main(String[] args) {
- SpringApplication.run(OrderApplication.class, args);
- }
- }
进入 Eureka Sever 查看
● 服务提供者设置集群
服务提供者要集群,只需要指定相同的服务名即可
- spring:
- application:
- name: cloud-payment-service
● 服务消费者,使用负载均衡
当要调用的对应服务也是集群,Eureka Client 本身支持了使用负载均衡方式请求
将原本的调用请求改为调用服务,服务名需要与 Eureka 上显示的相同:
public static final String PAYMENT_URL = "http://CLOUD-PAYMENT-SERVICE";
使用 @LoadBalanced 注解
- @Configuration
- public class ApplicationContextConfig
- {
- @Bean
- @LoadBalanced
- public RestTemplate getRestTemplate()
- {
- return new RestTemplate();
- }
- }
服务发现即消费者能够通过 Eureka Server,获取到对应的服务信息
- package com.tom.controller;
-
- import com.tom.entity.CommonResult;
- import com.tom.entity.Payment;
- import com.tom.service.PaymentService;
- import lombok.extern.slf4j.Slf4j;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.beans.factory.annotation.Value;
- import org.springframework.cloud.client.ServiceInstance;
- import org.springframework.cloud.client.discovery.DiscoveryClient;
- import org.springframework.web.bind.annotation.*;
-
- import javax.annotation.Resource;
- import java.util.ArrayList;
- import java.util.List;
-
- @RestController
- @RequestMapping("/pay")
- @Slf4j
- public class PaymentController {
-
- @Autowired
- private PaymentService paymentService;
-
- @Value("${server.port}")
- private String serverPort;
-
- @Resource
- private DiscoveryClient discoveryClient;
-
- @GetMapping("/discovery")
- public Object discovery() {
- List<String> services = discoveryClient.getServices();
- for(String service : services) {
- log.info(" service : "+ service);
- }
- List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
- for (ServiceInstance instance : instances) {
- log.info(instance.getServiceId() + "\t" + instance.getHost() + "\t" + instance.getPort() + "\t" + instance.getUri());
- }
- return this.discoveryClient;
- }
- }
- @SpringBootApplication
- @EnableEurekaClient
- @EnableDiscoveryClient
- public class PayApplication {
- public static void main(String[] args) {
- SpringApplication.run(PayApplication.class, args);
- }
- }
保护模式即某时刻某一个微服务不可用了,Eureka 不会立刻清理,依旧会保存该微服务的信息
一旦进入保护模式,Eureka Server 会尝试保护服务注册表中的信息,不再删除服务注册表中的数据,即不会注销任何服务
防止 EurekaClient 是正常运行,但与 EurekaServer 网络不通,EurekaServer 不会立刻将 EurekaClient 剔除。
通过关闭保护模式,Eureka Server 会在一定时间内没有收到服务心跳,就将这个服务从服务注册表中删除
Eureka Server 配置:
- eureka:
- ......
- server:
- # 关闭自我保护机制,保证不可用服务被及时剔除
- enable-self-preservation: false
- # 设置等待时间,超过等待时间没有收到心跳,则会清除
- eviction-interval-timer-in-ms: 2000
注意!!!
若是关闭了保护模式,且修改了等待时间,需要对应修改客户端发送心跳包的时间,否则可能存在客户端被误删的情况。生产环境下不建议关闭保护模式!
Eureka Client 配置:
- eureka:
- ......
- instance:
- instance-id: payment18001
- prefer-ip-address: true # 显示ip地址
- # Eureka 客户端想服务器发送心跳的间隔时间,单位为秒(默认是30秒)
- lease-renewal-interval-in-seconds: 1
- # Eureka 服务器在收到最后一次心跳后等待的时间上限,单位为秒(默认是90秒),超时将剔除服务
- lease-expiration-duration-in-seconds: 2
由于 Eureka 已经停更,故可以改为 Zookeeper 作为注册中心
增加引入的 pom 依赖:
- <!-- zookeeper 服务 -->
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
- </dependency>
修改启动类,使用 @EnableDiscoveryClient
- @SpringBootApplication
- @EnableDiscoveryClient
- public class CloudPaymentZoo {
-
- public static void main(String[] args) {
- SpringApplication.run(CloudPaymentZoo.class, args);
- }
- }
注册成功后,进入 zkCli.sh
./opt/zookeeper/bin/zkCli.sh
获取节点
ls /
可以看到 services 节点,查看这个节点
ls /services
可以看到服务已经注册到 zookeeper 上了,查看 cloud-payment-zookeeper 节点上存储了什么
http://CLOUD-PAYMENT-SERVICE + restapi
Consul 是一个服务网格(即各个微服务间的 TCP/IP,负责服务之间的网络调用、限流、熔断和监控)解决方案,作为注册中心被使用。
● 服务发现(Service Discovery)
Consul 允许通过 dns 或 http 接口的方式来注册服务和发现服务,一些外部的服务能够通过 Consul 很容易找到注册了的服务
● 健康检查(Health Checking)
Consul 的 Client 可以提供任意数量的健康检查,可以与特定的服务或是本地节点相关联并查看状态,操作员可以通过这些来监视集群的健康状况,服务发现组件可以使用这些信息将不健康的主机路由出去
● KV存储
提供了易用的键值存储,结合其他工具可以实现动态配置、功能标记、Leader 选举等
● 安全服务通信
Consul 可以为服务生成和分发 TLS 证书,以建立相互的 TSL 连接。意图是用于定义允许那些服务通信,服务分割可以很容易地进行管理,目的是可以实时更改,而不是使用复杂的网络拓扑和静态防火墙规则
● 多数据中心
Consul 支持开箱即用的多数据中心
下载安装包:https://www.springcloud.cc/spring-cloud-consul.html
- # 解压安装包
- unzip consul_1.8.3_linux_amd64.zip
-
- # 查看命令
- ./consul
-
- # 查看版本
- ./consul --version
运行
- @ 运行,并允许任意远端地址访问
- ./consul agent -dev --client 0.0.0.0
访问控制台页面
http://xxxxx:8500
引入 pom 依赖
- <!-- consul 服务 -->
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
- </dependency>
修改配置文件
- server:
- port: 18003
- spring:
- application:
- name: cloud-payment-consul
- cloud:
- consul:
- host: www.xxxx.com
- port: 8500
- discovery:
- server-name: ${spring.application.name}
修改启动类
- @SpringBootApplication
- @EnableDiscoveryClient
- public class CloudPaymentConsul {
-
- public static void main(String[] args) {
- SpringApplication.run(CloudPaymentConsul.class, args);
- }
- }
组件名 | 语言 | CAP | 服务健康检查 | 对外暴露接口 | Spring Cloud 集成 |
Eureka | Java | AP | 可配置支持 | HTTP | 已集成 |
Zookeeper | Java | CP | 支持 | 客户端 | 已集成 |
Consul | Go | CP | 支持 | HTTP/DNS | 已集成 |
一个分布式系统不可能同时满足 CAP,但其中 P 必须满足,故只有 CP 或 AP。其中 AP 的有 Eureka,而 CP 的是 Zookeeper 和 Consul
C: Consistency 强一致性 必须让数据必须一致,放弃 C 的情况如点赞数等
A: Availability 系统可用性
P: Partition tolerance 分区容错性
CP: 强调必须保证一致性,若服务出现故障,就会拒接请求,但这时候就违背了系统可用性
AP:强调系统的高可用性,即允许数据不是强一致的,而是通过其他方式保证数据的最终一致性
Ribbon 是基于 Netflix Ribbon 实现的一套客户端负载均衡和服务调用的工具。Ribbon 客户端组件提供一系列完善的配置项,如连接超时、重试等。简单地说,就是在配合文件中列出参与负载均衡的所有机器,Ribbon 会自动依据负载均衡规则(如简单轮询、随机连接等)去连接这些机器,也支持自定义负载均衡算法。
先选择 EurekaServer,优先选择在同一个区域内负载较少的 server
根据用户指定的负载均衡策略,在从 server 渠道的服务注册列表中选择一个地址进行请求
Nginx 是服务器负载均衡,客户端所有请求都会交给 Nginx,然后由 Nginx 实现转发请求
Ribbon 是本地负载均衡,在调用微服务接口的时候,会在注册中心上获取微服务列表之后缓存到 本地 jvm 中,从而实现本地 RPC 远程服务调用技术
即 Nginx 这一类处理的请求是十分笼统的,而 Ribbon 这是可以精细到某个具体的服务
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
- </dependency>
在 spring-cloud-starter-netflix-eureka-client 中自带了 spring-cloud-starter-ribbon 的引用
也可以自己单独配置 ribbon
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
- </dependency>
根据特定算法中从服务列表中选取一个要访问的服务
类名 | 策略 | 描述 |
RoundRobinRule | 轮询 | 逐一选择地址列表中的路径进行请求 |
RandomRule | 随机 | 从地址列表中随机选择一个路径进行请求 |
RetryRule | 轮询+重试 | 先采用轮询策略获取服务,若请求失败则会在指定时间内重试 |
WeightedResponseTimeRule | 权重 | 对轮询策略的扩展,响应速度越快的实例选择权重越大,越容易被选择到 |
BestAvailableRule | 最可用 | 先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务 |
AvailabilityFilteringRule | 选择并发量最小 | 先过滤掉故障实例,再选择并发量较小的实例 |
ZoneAvoidanceRule | 默认规则 | 复合判断 server 所在区域的性能和 server 的可用性选择服务器 |
定义使用的负载均衡算法的配置类,不能放置在 @ComponentScan 能够扫到的包下,否则会被所有 Ribbon 客户端共享
新建一个包,放在启动类所属的包外面,如 MyRule.java
- @Configuration
- public class MyRule {
-
- @Bean
- public IRule mRule() {
- return new RandomRule(); // 定义为随机策略
- }
- }
修改主启动类:
- @SpringBootApplication
- @EnableEurekaClient
- // name: 指定哪个服务,configuration: 用哪种策略
- @RibbonClient(name = "CLOUD-PAYMENT-SERVICE",configuration = MyRule.class)
- public class OrderApplication {
- public static void main(String[] args) {
- SpringApplication.run(OrderApplication.class, args);
- }
- }
结构目录,创建了 LoadBalancer 是自己写的负载均衡类,MyLB 则是他的实现类
LoadBalancer.java:
- public interface LoadBalancer {
- ServiceInstance instances(List<ServiceInstance> serviceInstances);
- }
MyLB.java:
- @Component
- public class MyLB implements LoadBalancer {
-
- private AtomicInteger atomicInteger = new AtomicInteger(0);
-
- /**
- * 得到再增加
- * do..while 中,使用了自旋锁的方式,即 AtomicInteger,实现设置取值和设置值
- * 设置值在 compareAndSet 中实现,使用 cas 算法
- * @return
- */
- public final int getAndIncrement() {
- int current;
- int next;
- // 当设置值失败时,不断重新获取,并尝试设置值
- do {
- current = this.atomicInteger.get();
- next = current > 2147483647 ? 0 : current+1;
- }while (!this.atomicInteger.compareAndSet(current, next));
- System.out.println("**** next = " + next);
- return next;
- }
-
- /**
- * 选出干活的那个服务地址
- * @param serviceInstances
- * @return
- */
- @Override
- public ServiceInstance instances(List<ServiceInstance> serviceInstances) {
- // 获取余数
- int index = getAndIncrement() % serviceInstances.size();
- return serviceInstances.get(index);
- }
- }
使用:
- @RestController
- public class OrderController {
- @Resource
- private RestTemplate restTemplate;
-
- @Resource
- private LoadBalancer loadBalancer;
-
- @Resource
- private DiscoveryClient discoveryClient;
-
- @GetMapping("/consumer/payment/lb")
- public String getPaymentLB() {
- List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
- // 若是无效服务,则返回
- if (instances == null || instances.size() <= 0) {
- return null;
- }
- // 若服务有效,则选择出实际要调用的服务实例
- ServiceInstance serviceInstance = loadBalancer.instances(instances);
- URI uri = serviceInstance.getUri();
- return restTemplate.getForObject(uri + "/pay/lb", String.class);
- }
- }
OpenFeign 是一个声明式的 webservice 客户端,通过定义一个服务接口,然后在上面添加注解实现服务之间的调用。可以与 Eureka 和 Ribbon(OpenFeign 已内置) 组合使用以支持负载均衡。
Feign | OpenFeign | |
描述 | Feign 是 SpringCloud 组件中的一个轻量级的 RESTful 的 HTTP 服务客户端,内置了 Ribbon。 | Open Feign 是 SpringCloud 在 Feign 的基础上又支持了 SpringMVC 的注解,如 @RequestMapping 等,通过 OpenFeign 的 @FeignClient 解析 @RequestMapping 注解下的接口,并使用动态代理的方式产生实现类,在实现类中做负载均衡并调用其他服务 |
使用方式 | 使用 Feign 的注解定义接口,调用这个接口就可以调用服务注册中心的服务 | 与 Feign 相同 |
maven 依赖 | | |
OpenFeign 是用在消费方的,相比较 RestTemplate + Ribbon 的组合,OpenFeign 的接口对接口的方式无疑更适合程序员使用和理解。
当 Feign 调用的服务是集群的情况下,默认使用轮询策略
先添加 maven 依赖
- <!-- eureka 客户端 -->
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
- </dependency>
- <!-- openfeign -->
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-openfeign</artifactId>
- </dependency>
修改启动类,加上 @EnableFeignClients
- @SpringBootApplication
- @EnableFeignClients
- public class OrderFeignMain {
- public static void main(String[] args) {
- SpringApplication.run(OrderFeignMain.class, args);
- }
- }
根据需求,创建对应的 FeignService,使用 @FeignClient 指定要调用 Eureka 上注册的哪个微服务
- @Component
- @FeignClient("CLOUD-PAYMENT-SERVICE")
- public interface PaymentFeignService {
-
- @RequestMapping("/pay/find/{id}")
- public CommonResult<Payment> findById(@PathVariable("id") Long id);
- }
配置文件 application.yml
- server:
- port: 9004
- spring:
- application:
- name: cloud-consumer-order-feign
- eureka:
- client:
- register-with-eureka: false
- service-url:
- # 设置与 eureka server 交互的地址,查询服务和注册服务都需要依赖这个地址
- defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka,http://eureka7003.com:7003/eureka
由于 OpenFeign 默认等待一秒钟,若有长业务,则需要在客户端中进行设置
由于 OpenFeign 整合了 Ribbon,故在主配置文件中对 Ribbon 进行设置超时时间
- server:
- port: 9004
- spring:
- application:
- name: cloud-consumer-order-feign
- eureka:
- client:
- register-with-eureka: false
- service-url:
- # 设置与 eureka server 交互的地址,查询服务和注册服务都需要依赖这个地址
- defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka,http://eureka7003.com:7003/eureka
- ribbon:
- # 建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间
- ConnectTimeout: 5000
- # 建建连接后,从服务器读取到可用资源所用的时间
- ReadTimeout: 5000
级别 | 描述 |
NONE | 默认,不显示任何日志 |
BASIC | 仅记录请求方法、URL、响应状态码及执行时间 |
HEADERS | 除了 BASIC 中定义的信息,还有请求和响应的头信息 |
FULL | 在 HEADERS 的基础上,还增加了请求和响应的正文及元数据 |
修改主启动类,增加日志配置
- logging:
- level:
- # 定义 feign 日志以书面级别监控哪个接口
- com.tom.feign.PaymentFeignService: debug
编写配置类
- @Configuration
- public class FeignConfig {
- @Bean
- Logger.Level feignLoggerLevel() {
- return Logger.Level.FULL;
- }
- }
Hystrix 是一个用于处理分布式系统的延迟和容错的开源库,它能够保证在一个依赖调用出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式的弹性
主要作用是 服务降级、服务熔断以及接近实时的监控
断路器类似于一种开关装置,当某个服务单元发生故障后,通过断路器的故障监控,向调用方返回一个符合预期的、可处理的备选响应(FallBack),而不是长时间地等待或是抛出调用方无法处理的异常。通过这样的方式能够保证服务调用者的线程不会被长时间、不必要地占用,从而避免系统发生更多级联故障,甚至服务雪崩的发生。
在复杂的分布式体系结构中,应用程序间可能有数十个依赖,每个依赖在某些时候都有可能会失败
多个微服务之间调用的时候,如服务A调用服务B和服务C,服务B和服务C又调用了其他服务,这就称为“扇出”。
若扇出的链路上某个微服务的调用响应时间过长或不可用,则对服务A的调用就会占用越来越多的系统资源,从而引起系统崩溃。
可能造成的影响有:
故需要对故障和延迟进行隔离和管理,使得单个依赖关系的失败,不会对系统有巨大的影响
服务降级即当服务出现故障时,至少有一个保底的处理方案,如给客户端返回友好提示等
发送服务降级的情况:
当访问量达到极限值时,直接拒绝访问,然后使用服务降级的方式进行后续处理
限制通过的请求,一定时间内仅允许指定数量的请求通过,待处理的请求可能等待或是拒绝
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。