SpringCloud与Docker微服务架构实战笔记

时间:2023-03-08 15:40:50
SpringCloud与Docker微服务架构实战笔记

SpringCloud与Docker微服务架构实战笔记一  微服务架构概述

1. 单体应用架构存在的问题

结合:https://www.cnblogs.com/jialanshun/p/10637454.html一起看,在该篇博客中搜索“单块架构的优缺点

(1)复杂性高

以笔者经手的一个百万行级别的单体应用为例,整个项目包含的模块非常多、模块的边界模糊、依赖关系不

清晰、代码质量参差不齐、混乱地堆砌在一起整个项目非常复杂。每次修改代码都心惊胆战,甚至添加一个简单

的功能,或者修改一个Bug都会带来隐含的缺陷。

(2)技术债务

随着时间推移、需求变更和人员更迭,会逐渐形成应用程序的技术债务,并且越积越多。“不坏不修

(Notbroken,don'tfix)'',这在软件开发中非常常见,在单体应用中这种思想更甚。已使用的系统设计或代码难以

被修改,因为应用程序中的其他模块可能会以意料之外的方式使用它。

(3)部署频率低

随着代码的增多,构建和部署的时间也会增加。而在单体应用中,每次功能的变更或缺陷的修复都会导致需

要重新部署整个应用。全量部署的方式耗时长、影响范围大、风险高,这使得单体应用项目上线部署的频率较低。

而部署频率低又导致两次发布之间会有大量的功能变更和缺陷修复,出错概率比较高。

(3)可靠性差

某个应用Bug,例如死循环、OOM等,可能会导致整个应用的崩溃。

(4)新人培养周期长

2. 什么是微服务架构

什么是微服务的架构和微服务架构的一些思想去博客https://www.cnblogs.com/jialanshun/p/10637454.html中看

二  Spring Cloud概述

1. Spring Cloud的版本号

如下图所示,Spring Cloud是以英文单词加SRX(X代表数字)来命名版本号的,其中英文单词 Brixton、Angel、

Camden表示的是伦敦地铁站的名称,按照字母顺序发行。SR表示“Service Release”,一般表示BUG修复,在SR版

本发行前,会先发行一个Release版本,例如“Camden Release”。

这样定义版本的原因是因为SpringCloud是一个综合项目,它包含很多的子项目。由于子项目也维护着自己的版

本号,springcloud采用了这种版本命名方式,从而避免与子项目的版本混淆。

SpringCloud与Docker微服务架构实战笔记

3. Spring Cloud的前世今生

https://my.oschina.net/polly/blog/1790057

2. Spring Cloud和Spring Boot的兼容性

Spring Cloud

Spring Boot

Finchley

兼容Spring Boot 2.0.x,不兼容Spring Boot 1.5.x

Dalston和Edgware

兼容Spring Boot 1.5.x,不兼容Spring Boot 2.0.x

Camden

兼容Spring Boot 1.4.x,也兼容Spring Boot 1.5.x

Brixton

兼容Spring Boot 1.3.x,也兼容Spring Boot 1.4.x

Angel

兼容Spring Boot 1.2.x

可以去https://spring.io/projects/spring-cloud查看版本兼容性,这本书比较老,上面表格可能不是最新的

三  Eureka

SpringCloud支持多种注册中心,如Eureka、Consul、Zookeeper,但是本文只讲Eureka

1.  Eureka简介

(1)Eureka的基本原理

关于Eureka的原理参看:

https://blog.csdn.net/forezp/article/details/73017664

SpringCloud与Docker微服务架构实战笔记SpringCloud与Docker微服务架构实战笔记

SpringCloud与Docker微服务架构实战笔记

由图可知,Eureka包含两个组件,Eureka Server和Eureka Client

Eureka Server:提供服务注册服务,各个节点启动后会在Eureka Server进行注册。Eureka Server之间通过复

制的方式完成数据的同步。在应用启动后,将会向Eureka Server发送心跳,默认周期为30秒,

如果Eureka Server在多个心跳周期内没有接收到某个节点的心跳,将会重服务器注册表中把

这个服务节点移除(开启自我保护除外)。

Eureka Client:是一个Java客户端,用于简化与Eureka Server的交互,该客户端具备一个内置的使用轮询负

载均算法的负载均衡器。Eureka提供客户端缓存机制,即使所有的Eureka Server都挂掉,客

户端依然可以利用缓存中的信息消费其它服务中的Api。

客户端缓存的实现原理:

Eureka Client缓存机制很简单,设置了一个每30秒执行一次的定时任务,定时去服务端获

取注册信息。获取之后,存入本地内存。

综上,Eureka通过心跳检测、健康检查、客户端缓存等机制确保了系统的高可用性、灵活性和可伸缩性。

(2)Eureka包含的核心功能

(a)Register:服务注册

当Eureka客户端向Eureka Server注册时,它提供自身的元数据,比如IP地址、端口,运行状况指示符

URL,主页等。

(b)Renew:服务续约

Eureka客户会每隔30秒发送一次心跳来续约。 通过续约来告知Eureka Server该Eureka客户仍然存在,没

有出现问题。 正常情况下,如果Eureka Server在90秒没有收到Eureka客户的续约,它会将实例从其注册

表中删除。 建议不要更改续约间隔。

(c)Fetch Registries:获取注册列表信息

Eureka客户端从服务器获取注册表信息,并将其缓存在本地。客户端会使用该信息查找其他服务,从而进

行远程调用。该注册列表信息定期(每30秒钟)更新一次。每次返回注册列表信息可能与Eureka客户端的

缓存信息不同, Eureka客户端自动处理。如果由于某种原因导致注册列表信息不能及时匹配,Eureka客户

端则会重新获取整个注册表信息。 Eureka服务器缓存注册列表信息,整个注册表以及每个应用程序的信息

进行了压缩,压缩内容和没有压缩的内容完全相同。Eureka客户端和Eureka 服务器可以使用JSON / XML

格式进行通讯。在默认的情况下Eureka客户端使用压缩JSON格式来获取注册列表的信息。

(d)Cancel:服务下线

Eureka客户端在程序关闭时向Eureka服务器发送取消请求。 发送请求后,该客户端实例信息将从服务器的

实例注册表中删除。该下线请求不会自动完成,它需要调用以下内容:

DiscoveryManager.getInstance().shutdownComponent();

2. 学习Eureka需要掌握的知识点

(1)Eureka集群配置

Eureka服务器端application.yml配置(以两台为例):

server:
port: 8761
spring:
application:
name: eureka-servier
#Spring提供了profiles的功能,可以配置多套配置,用于区分不同的环境(开发、测试、生产)在运行时
#指定使用那套,这样代码只要一套,运行时加入不同参数就可以了。比如在UnitTest中,加入:
#@ActiveProfiles("dev"),即可使用dev的配置。也可以在运行jar的时候
#加入:-Dspring.profiles.active=release。
profiles: slave1
eureka:
client:
serviceUrl:
#服务注册中心的配置内容,指定服务注册中心的位置
defaultZone: http://localhost:8762/eureka
---
server:
port: 8762
spring:
application:
name: eureka-servier
profiles: slave2
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka

Eureka客户端配置:

spring:
application:
name: consumer-demo
server:
port: 9000
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka,http://localhost:8762/eureka

集群成功示例图:

SpringCloud与Docker微服务架构实战笔记SpringCloud与Docker微服务架构实战笔记​​

(2)健康检查:

如果Eureka Server在一定时间内(默认90秒)没有接收到某个微服务实例的心跳,Eureka Server将会移除该

实例。

健康检查配置(下面代码是一个eureka客户端而不是eureka server):

spring:
application:
name: consume-demo-hertbeat
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka
instance:
#eureka客户端发送给eureka服务器心跳的频率
lease-renewal-interval-in-seconds: 5
#表示eureka server至上一次收到client的心跳之后,等待下一次心跳的超时时间,在这个时间内若没收到下一次心跳,则将移除该instance。
lease-expiration-duration-in-seconds: 10

同时应该在eureka server中关闭自我保护,示例代码如下(下例是一个eureka server而不是eureka client):

spring:
application:
name: eurker-servier-heartbeat
server:
port: 8761
eureka:
client:
register-with-eureka: false
fetch-registry: false
server:
#自我保护配置(true 开启,false 关闭)
enable-self-preservation: false
#(清理服务列表的时间间隔)

上面代码中为了测试方便还增加了清理服务列表的时间,即使满足“10秒没收到请求但不满足到了清理服务列

表的时间,一样不会剔除该服务”。

(3)自我保护:

首先阐明存在的一个问题:

默认情况下,如果Eureka Server在一定时间内(默认90秒)没有接收到某个微服务实例的心跳,

Eureka Server将会移除该实例。但是当网络分区故障发生时,微服务与Eureka Server之间无法正常通

信,而微服务本身是正常运行的,此时不应该移除这个微服务,Eureka通过自我保护机制来解决该问题

的。

自我保护机制

当Eureka Server节点在短时间内丢失过多客户端时(15分钟内超过85%的客户端节点都没有正常的心

跳),那么这个节点就会进入自我保护模式。一旦进入该模式,Eureka Server就会保护服务注册表中的

信息,不再删除服务注册表中的数据(也就是不会注销任何微服务)。当网络故障恢复后,该

Eureka Server节点会自动退出自我保护模式。

自我保护配置:

spring:
application:
name: eurker-servier-heartbeat
server:
port: 8761
eureka:
client:
register-with-eureka: false
fetch-registry: false
server:
#自我保护配置(true 开启,false 关闭)
enable-self-preservation: false
#(清理服务列表的时间间隔)
eviction-interval-timer-in-ms: 10000

(4)健康监控

默认情况下注册到eureka server的服务是通过心跳来告知自己是UP还是DOWN,并不是通过

spring-boot-actuator模块的/health端点来实现的,这样其实不是很合理。因为默认的心跳实现方式可以有效

的检查eureka客户端进程是否正常运作,但是无法保证客户端应用能够正常提供服务(大多数微服务应用都

会有一些其他的外部资源依赖,比如数据库,REDIS缓存等,如果我们的应用与这些外部资源无法连通的时

候,实际上已经不能提供正常的对外服务了,但因为客户端心跳依然在运行,所以它还是会被服务消费者调

用)。

健康状态有UP和DOWN两种,如果Eureka中的是UP,则该服务可以正常调用;如果Eureka中的健康状

态是DOWN则该服务不可调用。

问题场景:如果一个服务并没有死掉,但是其本身是有问题的,例如访问数据库的服务无法连接

到数据库,这个时候需要使用健康监控。

注意:健康监控监控的是客户端,所以健康指示器和健康处理器的代码只能写在需要健康的客户端。

(a)引入jar包

       <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<version>1.5.4.RELEASE</version>
       </dependency>

(b)编写健康指示器

package app;

import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.actuate.health.Status;
import org.springframework.stereotype.Component; @Component
public class MyHealthIndicator implements HealthIndicator {
public Health health() {
if(ProviderMonitorController.canVisitDB)
{
return new Health.Builder(Status.UP).build();
}else
{
return new Health.Builder(Status.DOWN).build();
}
}
}

(c)模拟数据库无法访问

package app;

import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController; @RestController
public class ProviderMonitorController {
public static boolean canVisitDB=true;
@RequestMapping(value = "/setDB/{can}",method = RequestMethod.GET)
public void setDB(@PathVariable boolean can)
{
canVisitDB=can;
}
}

(d)测试

(i)查看健康状态

在浏览器里输入http://localhost:8080/health,返回状态为UP

(ii)执行模拟数据库无法访问并查看健康状态

浏览器中输入:http://localhost:8080/setDB/false 设置数据库无法访问

浏览器中输入:http://localhost:8080/health 查看健康状态为 DOWN

查看Eureka中的健康状态仍然为UP,这时需要使用健康检查处理器来改变Eureka中的状态(只有改

变了Eureka中的状态,那么有问题的服务才不能被其它被访问)。

(e)健康处理器代码(默认情况下这个处理器30秒执行一次):

package app;

import com.netflix.appinfo.HealthCheckHandler;
import com.netflix.appinfo.InstanceInfo;
import jdk.net.SocketFlow;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.boot.actuate.health.Status;
@Component
public class MyHealthCheckHandle implements HealthCheckHandler {
@Autowired
private MyHealthIndicator myHealthIndicator;
public InstanceInfo.InstanceStatus getStatus(InstanceInfo.InstanceStatus instanceStatus) {
Status status= myHealthIndicator.health().getStatus();
if(status.equals(Status.UP))
{
return InstanceInfo.InstanceStatus.UP;
}else
{
return InstanceInfo.InstanceStatus.DOWN;
}
}
}

application.yml(主要关注健康处理器的执行频率)

spring:
application:
name: my-health-provider
endpoints:
sensitive: false
eureka:
client:
#健康处理器执行频率。默认30秒执行一次,这里改成10秒执行一次
instanceInfoReplicationIntervalSeconds: 10
serviceUrl:
defaultZone: http://localhost:8761/eureka/

(f)测试健康处理器

浏览器中执行:http://localhost:8080/setDB/false,可以看到health端口和Eureka 中的状态全部变成

DOWN

(5)Eureka元数据

(6)REST端点

(7)多网卡环境下的IP选择

上面代码中为了测试方便还增加了清理服务列表的时间,即使满足“10秒没收到请求但不满足到了清理服务列

表的时间,一样不会剔除该服务”。

二  Ribbon

1.客户端负载均衡框架,支持可插拔式的负载均衡规则

2.支持多中协议,如HTTP、UDP

3.Ribbon支持的负载均衡Rule

(1)RoundRobinRule:轮询

(2)AvailabilityFilteringRule:会过滤掉两类服务器。(1)短路状态(连续3次超时)(2)并发数过高的服务

器。

(3)WeightedResponseTimeRule:为每个服务器设置一个权重值,服务器的响应时间越长权重值越小。这里

是根据权重值随机选择服务器。

(4)ZoneAvoidanceRule:复合判断server所在区域的性能和server的可用性选择server,使用区域对服务器进

行选择。

(5)BestAvailableRule:忽略短路的服务器并选择并发数最小的一个服务器

(6)RandomRule:随机数

(7)RetryRule:含有重试机制的选择逻辑。

4. Ribbon可配置的类

NFLoadBalancerPingClassName:用于配置查看服务器是否存活。配置IPing的实现类。

NFLoadBalancerRuleClassName:指定负载均衡器的实现类。当然,可以设置自己实现的负载均衡器。配置

IRule的实现类。IRule代表的是负载均衡算法。

NFLoadBalancerClassName:配置ILoadBalancer的实现类

NIWSServerListClassName:是服务器列表的处理类,用来维护服务器列表的。Ribbon已经实现了动态服务器

列表。配置ServerList的实现类。

NIWSServerListFilterClassName:是服务器的拦截类。配置ServerListFilter的实现类。

三  feign

Feign是Netflix开发的声明式、模板化的HTTP客户端,Feign可帮助我们更加便捷、优雅地调用HTTPAPI。

在springCloud中,使用Feign非常简单一创建一个接口,并在接口上添加一些注解,代码就完成了。Feign支持多

种注解,例如Feign自带的注解或者JAX-RS注解等。

SpringCloud对Feign进行了增强,使Feign支持了SpringMVC注解,并整合了Ribbon和Eureka,从而让

Feign的使用更加方便。

feign是可以配置的,默认配置类是FeignClientsConfiguration,该类定义了feign的编码器、解码器、使用契约

(SpringCloud的默认契约使用的是SpringMVCContract,因此它可以使用SpringMVC的注解)等。SpringCloud允

许通过注解@FeignCIient的configuration属性自定义Feign的配置,自定义配置的优先级要高于

FeignClientsConfiguration。

feign中已经使用了Ribbon的Api,默认负载均衡配置是轮询,可以通过设置Ribbon的负载均衡策略来定义

          Feign客户端的负载均衡策略

1. SpriingCloud整合feign

(1)添加feign的依赖

SpringCloud与Docker微服务架构实战笔记

(2)创建一个Feign接口,并添加@FeignClient注解

SpringCloud与Docker微服务架构实战笔记

@FeignC1ient注解中的microservice-provider-user是一个任意的客户端名称,用于创建Ribbon负载均衡

器。在本例中,由于使用了Eureka,所以Ribbon会把microservice-provider-user解析成EurekaServer服

务注册表中的服务。当然,如果不想使用Eureka,可使用service.ribbon.listofservers属性配置服务器列

表(详见5·5节)。

还可使用url属性指定请求的URL(URL可以是完整的URL或者主机名),例如:

SpringCloud与Docker微服务架构实战笔记

(4)调用feign接口的Controller代码如下:

SpringCloud与Docker微服务架构实战笔记

(5)启动类添加@EnableFeignClients使feign生效

SpringCloud与Docker微服务架构实战笔记

这样就可以通过feign去调用微服务的microservice-provider-user了

2. 自定义feign的配置

SpringCloud允许通过注解@FeignCIient的configuration属性自定义Feign的配置,自定义配置的优先级比

FeignClientsConfiguration要高。

(1)自定义契约

SpringCloud中,默认使用的契约是SpriingMVCContract,因此,我们在使用feign时,使用的是

SprinigMVC注解。

本例示范自定义使用feign的自己的注解。

(a)创建feign的配置类

SpringCloud与Docker微服务架构实战笔记

扩展,为了看懂该例子,了解下@Bean

代码:

                     @Configuration
public class AppConfig {
@Bean
public TransferService transferService() {
return new TransferServiceImpl();
} }

等同于:

<beans>
<bean id="transferService" class="com.acme.TransferServiceImpl"/>
</beans>

(b)Feign接口修改为如下,使用@FeignC1ient的configuration属性指定配置类,同时,将findById

上的SpringMVC注解修改为Feign自带的注解。

SpringCloud与Docker微服务架构实战笔记

(2)为接口添加HTTP Baseic

配置类写法:

SpringCloud与Docker微服务架构实战笔记

其它略。

(3)类似地,还可自定义Feign的编码器、解码器、日志打印,甚至为Feign添加拦截器。具体demo网上找,

书上没写。

Feign的常用配置有:

解码器(Decoder):bean名称为feignDecoder,ResponseEntityDecoder类。

编码器(Encoder):bean名称为feignEecoder,SpringEncoder类。

日志(Logger): bean名称为feignLogger,Slf4jLogger类。

注解翻译器(Contract): bean名称为feignContract,SpringMvcContract类。

Feign实例的创建者(Feign.Builder):bean名称为feignBuilder,HystrixFeign.Builder类。Hystrix框架

将在后面章节中讲述。

Feign客户端(Client):bean名称为feignClient,LoadBalancerFeignClient类。

Logger.Level:接口日志的记录级别,相当于调用了Fiegn.Builder的logLevel方法,请见5.2.9章节(视频)。

Retryer:重试处理器,相当于调用了Fiegn.Builder的retryer方法。

ErrorDecoder:异常解码器,相当于调用了Fiegn.Builder的errorDecoder方法。

Request.Options:设置请求的配置项,相当于调用了Fiegn.Builder的options方法。

Collection<RequestInterceptor>:设置请求拦截器,相当于调用了Fiegn.Builder的requestInterceptors

方法。下面是配置多个拦截器的样例,并不全,只是为了便于理解,

只是补充需要百度。

2. feign对压缩的支持

压缩配置:

feign.compression.request.enabled:

设置为true时,表示开启“请求”压缩。

feign.compression.response.enabled:

设置为true时,表示开启响应压缩。

feign.compression.request.mime-types:

数据类型列表,默认值为text/xml,application/xml,application/json。

feign.compression.request.min-request-size:

设置请求内容的最小阀值,默认值为2048。若大于2048则开启压缩。

3. feign构造多参数请求

看书,挺重要但是不带要摘录了

四  Hystrix

1.雪崩效应

服务雪崩效应是一种因,服务提供者的不可用导致服务调用者的不可用,并将不可用逐渐放大的过程。

要想防止雪崩效应,必须有一个强大的容错机制。该容错机制需实现以下两点:

(1)为网络设置超时时间

必须为网络请求设置超时。正常情况下,一个远程调用一般在几十毫秒内就能得到响应了。如果

依赖的服务不可用或者网络有问题,那么响应时间就会变得很长(几十秒)通常情况下,一次远程调

用对应着一个线程/进程。如果响应太慢,这个线程/进程就得不到释放。而线程/进程又对应着系统资

源,如果得不到释放的线程/进程越积越多,资源就会逐渐被耗尽,最终导致服务的不可用。因此必须

为每个网络请求设置超时,让资源尽快释放。

(2)使用断路器

如果对某个微服务的请求有大量超时(常常说明该微服务不可用),再去让新的请求访问该服务

已经没有任何意义,只会无谓消耗资源。例如,设置了超时时间为1秒,如果短时间内有大量的请求无

法在1秒内得到响应,就没有必要再去请求依赖的服务了。

断路器可以实现快速失败,如果它在一段时间内检测到许多类似的错误(例如超时),就会在之

后的一段时间内,强迫对该服务的调用快速失败,即不再请求所依赖的服务。这样,应用程序就无须

再浪费CPU时间去等待长时间的超时。

断路器也可自动诊断依赖的服务是否已经恢复正常。如果发现依赖的服务已经恢复正常,那么就

会恢复请求该服务。使用这种方式,就可以实现微服务的“自我修复"一当依赖的服务不正常时打开断路

器时快速失败,从而防止雪崩效应;当发现依赖的服务恢复正常时,又会恢复请求。

断路器的执行逻辑:

--  正常情况下,断路器关闭,可正常请求依赖的服务。

--  当一段时间内,请求失败率达到一定阈值(例如错误率达到50%,或100次/分钟等),断路器

就会打开。此时,不会再去请求依赖的服务。

-- 断路器打开一段时间后,会自动进入“半开"状态。此时,断路器可允许一个请求访问依赖的服务。

如果该请求能够调用成功,则关闭断路器;否则继续保持打开状态。

2. 使用Hystrix实现容错

翻以前博客和看书

3. Hystrix的监控

(1)使用/hystrix.stream端点进行监控(有用)

看书

(2)使用Hystrix Dashboard可视化监控(有用)

看书,书上的例子并没有把Hystrix Dashboard注册到Eureka,生产环境可以把Hystrix Dashboard注册

到Eureka上。

(3)使用Turbine进行监控(重点)

由于/hystrix.stream只能看单个服务,如需要查看其它服务就需要在Hystrix Dashboard上切换要

监控的地址,这样做很不方便,于是我们使用Turbine,它可将所有./hystrix.stream聚合到一个组合的

./turbine.stream中,让集群监控更加方便。

SpringCloud与Docker微服务架构实战笔记

具体使用示例看书,太繁琐,不好摘录