一 微服务架构概述
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采用了这种版本命名方式,从而避免与子项目的版本混淆。
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
由图可知,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
集群成功示例图:
(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的依赖
(2)创建一个Feign接口,并添加@FeignClient注解
@FeignC1ient注解中的microservice-provider-user是一个任意的客户端名称,用于创建Ribbon负载均衡
器。在本例中,由于使用了Eureka,所以Ribbon会把microservice-provider-user解析成EurekaServer服
务注册表中的服务。当然,如果不想使用Eureka,可使用service.ribbon.listofservers属性配置服务器列
表(详见5·5节)。
还可使用url属性指定请求的URL(URL可以是完整的URL或者主机名),例如:
(4)调用feign接口的Controller代码如下:
(5)启动类添加@EnableFeignClients使feign生效
这样就可以通过feign去调用微服务的microservice-provider-user了
2. 自定义feign的配置
SpringCloud允许通过注解@FeignCIient的configuration属性自定义Feign的配置,自定义配置的优先级比
FeignClientsConfiguration要高。
(1)自定义契约
SpringCloud中,默认使用的契约是SpriingMVCContract,因此,我们在使用feign时,使用的是
SprinigMVC注解。
本例示范自定义使用feign的自己的注解。
(a)创建feign的配置类
扩展,为了看懂该例子,了解下@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自带的注解。
(2)为接口添加HTTP Baseic
配置类写法:
其它略。
(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中,让集群监控更加方便。
具体使用示例看书,太繁琐,不好摘录