1.简介
集群/分布式/微服务
集群:同一个业务,部署在多个服务器上(不同的服务器运行同样的代码,干同一件事)。集群系统中的单个计算机通常称为节点,通常通过局域网连接,但也有其它的可能连接方式。
分布式:一个业务分拆多个子业务,每个子业务为一个节点,部署在不同的服务器上(不同的服务器,运行不同的代码,为了同一个目的)
微服务:是系统架构上的一种设计风格, 它的主旨是将一个原本独立的系统 拆分成多个小型服务,这些小型服务都在各自独立的进程中运行,服务之间通过基于HTTP 的RESTful API进行通信协作。 被拆分成的每一个小型服务都围绕着系统中的某一项或一 些耦合度较高的业务功能进行构建, 并且每个服务都维护着自身的数据存储、 业务开发、 自动化测试案例以及独立部署机制。 由千有了轻量级的通信协作基础, 所以这些微服务可 以使用不同的语言来编写。
CAP理论
-
C:数据一致性(consistency)
-
- 所有节点拥有数据的最新版本,强一致性, 或是时序一致性, 或是滞后的最终一致性
-
A:可用性(availability)
-
- 数据具备高可用性,尽量保证服务不会失去响应
-
P:分区容错性(partition-tolerance)
-
-
节点间通信失败时保证系统不受影响,对容错的要求提高会降低对可用性或一致性的期望, 要么停止系统用于错误恢复,要么继续服务但是降低一致性。
-
举个可能不太恰当的例子(现实可能不会这么拆分,但意思到位就好了):
拆分出多个模块以后,就会出现各种各样的问题,而SpringCloud提供了一整套的解决方案!
-
注:这些模块是独立成一个子系统的(不同主机)。
2.SpringCloud的基础功能
-
服务治理: Spring Cloud Eureka
-
客户端负载均衡: Spring Cloud Ribbon
-
服务容错保护: Spring Cloud Hystrix
-
声明式服务调用: Spring Cloud Feign
-
API网关服务:Spring Cloud Gateway
-
分布式配置中心: Spring Cloud Config
3.Eureka
Eureka专门用于给其他服务注册的称为Eureka Server(服务注册中心),其余注册到Eureka Server的服务项目中都有一个Eureka Client组件,这个组件专门负责将这个服务信息注册到Eureka Server中。
- Eureka Client:负责将这个服务的信息注册到 Eureka Server 中。
- Eureka Server:注册中心,里面有一个注册表,保存了各个服务所在的机器和端口号。
Eureka Client分为服务提供者和服务消费者。
- 但很可能,某服务既是服务提供者又是服务消费者。
- 也有可能只是消费者
eureka:
client:
register-with-eureka: false # 当前微服务不注册到eureka中(消费端)
service-url:
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
问题:
现在有A、B、C、D四个服务,它们之间会互相调用(而且IP地址很可能会发生变化),一旦某个服务的IP地址变了,那服务中的代码要跟着变,手动维护这些静态配置(IP)非常麻烦!
Eureka是这样解决上面所说的情况的:
-
创建一个E服务,将A、B、C、D四个服务的信息都注册到E服务上,E服务维护这些已经注册进来的信息
-
代码中通过服务名找到对应的IP地址(IP地址会变,但服务名一般不会变)
Eureka的治理机制:
-
服务提供者
-
- 服务注册:启动的时候会通过发送POST请求带上当前实例信息到类 ApplicationResource 的 addInstance 方法进行服务注册。注册到Eureka Server上,服务注册信息保存在一个嵌套的 map 中。
- 服务续约:在注册完服务之后,服务提供者会维护一个心跳(默认周期为30秒)用来持续告诉Eureka Server: "我还活着 ” 、
- 服务下线:当服务实例进行正常的关闭操作时,它会触发一个服务下线的DELETE 请求给Eureka Server, 告诉服务注册中心:“我要下线了 ”。
-
服务消费者
-
- 获取服务:当我们启动服务消费者的时候,它会发送一个REST请求给服务注册中心,来获取上面注册的服务清单
- 服务调用:服务消费者在获取服务清单后,通过服务名可以获得具体提供服务的实例名和该实例的元数据信息。在进行服务调用的时候,优先访问同处一个Zone中的服务提供方。
-
Eureka Server(服务注册中心):
-
- 失效剔除:默认每隔一段时间(默认为60秒) 将当前清单中超时(默认为90秒)没有续约的服务剔除出去。
- 自我保护:。EurekaServer 在运行期间,会统计心跳失败的比例在15分钟之内是否低于85%(通常由于网络不稳定导致)。 Eureka Server会将当前的实例注册信息保护起来, 让这些实例不会过期,尽可能保护这些注册信息。
创建Eureka Sever
- 添加maven
<!-- SpringCloud Eureka 注册中心依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
</dependency>
- 配置文件
eureka:
instance:
hostname: peer1 # 应用实例主机名
client:
registerWithEureka: false #是否将自己注册到Eureka server,默认为true。该服务为注册中心,所以不用注册
fetchRegistry: false #是否从Eureka server中获取注册信息。该服务为注册中心,不用获取
serviceUrl:
#设置Eureka服务器所在的地址,询服务和注册服务都需要依赖这个地址,可以不写
#如果写另一个IP地址,相当于开启多个注册服务,成为集群,如果为多个以逗号隔开。
#默认为localhost
defaultZone: http://127.0.0.1:8761/eureka/
- 启动类加注解@EnableEurekaServer,启动Eureka Server
@SpringBootApplication
@EnableEurekaServer
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class, args);
}
}
- 项目启动后,访问
创建Eureka Client
- 添加maven
<!-- SpringCloud Eureka client依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
- 配置文件
eureka:
client:
service-url:
defaultZone: http://ip:8761/eureka #设置Eureka服务所在地址
instance:
prefer‐ip‐address: true #可以跨域访问
ip-address: #设置当前服务的公网ip,默认注册时内网ip
- 启动类加注解@EnableEurekaClient或者@EnableDiscoveryClient,启动Eureka Client
@SpringBootApplication
@EnableEurekaClient
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class, args);
}
}
-
启动后自动注册到Eureka服务中
可以在界面查看
4.Ribbon负载均衡
Ribbon是一个基于HTTP和TCP的客户端负载均衡工具,基于Netflix Ribbon实现。
不需要独立部署,它几乎存在于每一个Spring Cloud构建的微服务和基础设施中。
微服务间的调用,API网关的请求转发等内容,实际上都是通过Ribbon来实现的。
简单的说,就是在配置文件中列出Load Balancer(简称LB)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器。
服务端和客户端负载均衡区别
服务端的负载均衡是一个url先经过一个代理服务器(这里是nginx),然后通过这个代理服务器通过算法(轮询,随机,权重等等..)反向代理你的服务,来完成负载均衡.
而客户端的负载均衡则是一个请求在客户端的时候已经声明了要调用哪个服务,然后通过具体的负载均衡算法来完成负载均衡
引入Ribbon
- 添加maven
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
</dependency>
- 要使用ribbon,只需要一个注解: @LoadBalanced,在RestTemplate上面加入@LoadBalanced注解,这样子就已经有了负载均衡
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
RestTemplate restTemplate = new RestTemplate();
return restTemplate;
}
- 在使用Feign后就不需要@LoadBalanced了,Feign集成了Ribben,一个@FeignClient注解就包含了。
5.hystrix熔断
问题
在微服务框架中多层服务之间会相互调用,如果其中有一层服务故障了,可能会导致一层服务或多层服务故障,从而导致整个系统故障。这种想象为雪崩效应。
解决
服务降级,服务熔断,服务隔离。
服务降级
在高并发情况下,防止客户端一直等待,使用服务器降级方式(返回一个友好提示,不会去处理请求,调用fallback本地方法)。目的:提高用户体验
秒杀 ——当前请求过多,请稍后重试。
服务熔断
服务熔断目的就是为了保护服务,在高并发的情况下,如果请求达到了一定的极限,如果服务超出了规定的上线后,会自动开启保护服务功能,使用服务降级方式返回一个友好的提示。服务熔断机制和服务降级是配套使用的。
hystrix这里管理熔断器,在运行过程中会向每个commandKey对应的熔断器报告成功、失败、超时和拒绝的状态,熔断器维护并统计这些数据,并根据这些统计信息来决策熔断开关是否打开。
熔断器有三种状态
- closed:关闭状态,服务调用方每次请求都到服务提供方
- open:打开状态,提供方的异常率或者请求并发量超过设置的阈值,就开启熔断机制,之后所有的请求都不会请求到提供方,而是直接使用本地的服务降级方法
- half-open:半打开状态,服务调用方所有请求依然会请求到提供方
单个请求超时5s,+重试>10s,超15s则熔断
服务隔离
线程池和信号量隔离
**线程池:**例如线程池大小为10,如果某个时刻10个线程均被使用,那么新的请求将不会进入等待队列,而是直接返回失败,起到限流的作用。
**信号量:**用于共享资源的场景,比如:计算机连接两台打印机,初始信号量为2,被进程获取后-1,当信号量为0后,需要获取的线程进入资源等待状态。Hystrix的处理有些不同,其不等待,直接返回失败。
引入Hystrix
-
添加maven
<!--hystrix 熔断器--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency> -
配置文件
##开启Hystrix断路器 feign: hystrix: enabled: true ##设置hystrix超时时间,默认是1秒,不修改的话,很快就会进入回调方法 hystrix: command: serverMethod: execution: isolation: thread: timeoutInMilliseconds: 3000 -
启动类
feign集成了,不需要再加@EnableHystrix
-
回调fallback类
①细粒度的需要实现feign接口,实现所有接口方法。
//接口类上加入的注解中添加属性fallback,指定回调类 @FeignClient(name = "CLIENT-87",fallback = FeignClientFallback.class) public interface UserFeign { @RequestMapping("/getUser") public String getUser(); }创建回调
@Component class FeignClientFallback implements UserFeign { @Override public String getUser() { System.out.println("熔断,默认回调函数"); return "{\"username\":\"admin\",\"age\":\"-1\"}"; } }②回调工厂类,统一回调:此类实现FallbackFactory类,并实现方法T create(Throwable arg0);其中arg0.getMessage();就是服务回退时的异常信息。
@Component public class UserFeignFallbackFactory implements FallbackFactory<UserFeign>{ @Override public WebFeignService create(Throwable arg0) { return new UserFeign(){ @Override public Object getUser() { return arg0.getMessage(); } }; } }
6.Feign
它是一个http请求调用的轻量级框架,可以以Java接口注解的方式调用Http请求,而不用像Java中通过封装HTTP请求报文的方式直接调用。
基于 Netflix Feign 实现,整合了 Spring Cloud Ribbon 与 Spring Cloud Hystrix, 除了整合这两者的强大功能之外,它还提 供了声明式的服务调用(不再通过RestTemplate)。
在Feign 底层,通过基于面向接口的动态代理方式生成实现类,将请求调用委托到动态代理实现类。
引入Feign
-
添加maven
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> <version>2.0.2.RELEASE</version> </dependency> <dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-core</artifactId> <version>9.7.0</version> </dependency> <dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-slf4j</artifactId> <version>9.7.0</version> </dependency> -
在main主入口类添加FeignClients注解启用Feign客户端
@SpringBootApplication //激活Feign @EnableFeignClients public class TestApplication { public static void main(String[] args) { SpringApplication.run(TestApplication.class, args); } } -
声明需要调用的微服务
/** * 声明需要调用的微服务 * @FeignClient * name : 服务提供者在Eureka注册的名称 */ @FeignClient(name="service-product") public interface ProductFeignClient { /** * 配置需要调用的微服务接口 * value:服务提供方的全映射路径 */ @RequestMapping(value="/product/{id}") public Product findById(@PathVariable("id") Long id); }
7.Config统一配置中心
微服务不使用config的问题:
-
配置文件分散在各个项目中,不方便维护
-
配置内容安全与权限,实际开发中,开发人员是不知道线上环境的配置的
-
更新配置后,项目需要重启
Spring Cloud Config为分布式系统中的外部配置提供服务器和客户端支持。方便部署与运维。分客户端、服务端。
-
服务端:分布式配置中心,独立的微服务应用,用来连接配置仓库(git)并为客户端提供获取配置信息、加密/解密等访问接口。
-
客户端:微服务架构中各个微服务应用和基础设施,通过指定配置中心管理应用资源与业务相关的配置内容,启动时从配置中心获取和加载配置信息。
服务端配置
-
maven
<!-- Config 服务端 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-server</artifactId> </dependency> <!--Eureka client依赖,将该服务注册到Eureka Server中 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> -
配置文件
spring: application: name: #应用名 cloud: config: server: git: uri: https://gitee.com/zhangzd666/spring-cloud-config #git仓库位置 search-paths: #如果有子文件夹,搜索配置文件的相对路径 username: #git 账号 password: #密码 eureka: client: service-url: defaultZone: http://ip:8761/eureka #设置连接注册中心地址 -
启动类开启配置:@EnableConfigServer
//注册发现客户端 @EnableEurekaClient @SpringBootApplication @EnableConfigServer public class TestApplication { public static void main(String[] args) { SpringApplication.run(TestApplication.class, args); } } -
浏览器访问
将配置文件application.yml放在git上,供微服务获取
客户端配置
客户端向服务端获取相关信息。
-
maven
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-config</artifactId> </dependency> -
配置文件 将application.yml重命名为bootstrap.yml。不能加注释。
注:之所以要用bootstrap.yml,是因为启动SpringBoot项目时,会优先读取bootstrap.yml里的配置,然后才会读取application.yml。如果不通过bootstrap.yml里的配置,先从配置中心拉下相应的配置文件,就会报错
server: port: 7071 spring: application: name: config-client #应用名 cloud: config: name: client #配置客户端应用关联的名称 label: master #关联label discovery: enabled: true #激活 Config Server 服务发现,默认为false,不开启的话,无法从注册中心获取到相关配置信息 service-id: config-server # Config Server 服务器应用名称 eureka: client: service-url: defaultZone: http://182.92.109.238:8761/eureka #设置连接注册中心地址 -
将该项目注册到Eureka server中
-
编写测试类,获取git中配置文件的数据
@RestController public class TestController { //获取外部化配置 key 为hello的键值 @Value("${hello}") private String hello; @GetMapping("/hello") public String configHelloWorld() { return this.hello; } } -
访问