技术栈 | 传送门 |
JAVA 基础 | 手撸架构,Java基础面试100问_vincent-CSDN博客 |
JAVA 集合 | 手撸架构,JAVA集合面试60问_vincent-CSDN博客 |
JVM 虚拟机 | 手撸架构,JVM面试30问_vincent-CSDN博客 |
并发编程 | 手撸架构,并发编程面试123问_vincent-CSDN博客 |
Spring | 手撸架构,Spring面试63问_vincent-CSDN博客 |
Spring cloud | 手撸架构,Spring cloud面试45问_vincent-CSDN博客 |
SpringBoot | 手撸面试,Spring Boot面试41问_vincent-CSDN博客 |
Netty 与 RPC | 手撸架构,Netty 与 RPC面试48问_vincent-CSDN博客 |
Doubo | 手撸架构,Dubbo面试49问_vincent-CSDN博客 |
Redis | 手撸架构,Redis面试41问_vincent-CSDN博客 |
Zookeeper | 手撸架构,Zookeeper面试27问_vincent-CSDN博客 |
Mysql | 手撸架构,Mysql 面试126问_vincent-CSDN博客 |
MyBatis | 手撸架构,MyBatis面试42问_vincent-CSDN博客 |
MongoDB | 手撸架构,MongDB 面试50问_vincent-CSDN博客 |
Elasticsearch | 手撸架构,Elasticsearch 面试25问_vincent-CSDN博客 |
RabbitMQ | 手撸架构,RabbitMQ 面试49问_vincent-CSDN博客 |
Kafka | 手撸架构,Kafka 面试42问_vincent-CSDN博客 |
Docker | 手撸架构,Docker 面试25问_vincent-CSDN博客 |
Nginx | 手撸架构,Nginx 面试40问_vincent-CSDN博客 |
算法 | 常用排序算法总结(1)-- 比较排序_vincent-CSDN博客_比较排序 常用排序算法总结(2)-- 非比较排序算法_vincent-CSDN博客_非比较排序的算法有 |
分布式事务 | 分布式事务解决方案(总览)_vincent-CSDN博客 |
HTTP | 太厉害了,终于有人能把TCP/IP 协议讲的明明白白了_vincent-CSDN博客_tcp和ip |
微服务并没有一个官方的定义,想要直接描述微服务比较困难,我们可以通过对比传统WEB应用,来理解什么是微服务。
传统的WEB应用核心分为业务逻辑、适配器以及API或通过UI访问的WEB界面。业务逻辑定义业务流程、业务规则以及领域实体。适配器包括数据库访问组件、消息组件以及访问接口等。一个打车软件的架构图如下:
尽管也是遵循模块化开发,但最终它们会打包并部署为单体式应用。例如Java应用程序会被打包成WAR,部署在Tomcat或者Jetty上。
这种单体应用比较适合于小项目,优点是:
开发简单直接,集中式管理
基本不会重复开发
功能都在本地,没有分布式的管理开销和调用开销
当然它的缺点也十分明显,特别对于互联网公司来说:
开发效率低:所有的开发在一个项目改代码,递交代码相互等待,代码冲突不断
代码维护难:代码功能耦合在一起,新人不知道何从下手
部署不灵活:构建时间长,任何小修改必须重新构建整个项目,这个过程往往很长
稳定性不高:一个微不足道的小问题,可以导致整个应用挂掉
扩展性不够:无法满足高并发情况下的业务需求
所以,现在主流的设计一般会采用微服务架构。其思路不是开发一个巨大的单体式应用,而是将应用分解为小的、互相连接的微服务。一个微服务完成某个特定功能,比如乘客管理和下单管理等。每个微服务都有自己的业务逻辑和适配器。一些微服务还会提供API接口给其他微服务和应用客户端使用。
比如,前面描述的系统可被分解为:
每个业务逻辑都被分解为一个微服务,微服务之间通过REST API通信。一些微服务也会向终端用户或客户端开发API接口。但通常情况下,这些客户端并不能直接访问后台微服务,而是通过API Gateway来传递请求。API Gateway一般负责服务路由、负载均衡、缓存、访问控制和鉴权等任务。
微服务与微服务架构
优势:
劣势:
Spring Cloud来源于Spring,质量、稳定性、持续性都可以得到保证
Spirng Cloud天然支持Spring Boot,更加便于业务落地。
Spring Cloud发展非常的快,从16年开始接触的时候相关组件版本为1.x,到现在将要发布2.x系列
Spring Cloud是Java领域最适合做微服务的框架。
相比于其它框架,Spring Cloud对微服务周边环境的支持力度最大。
对于中小企业来讲,使用门槛较低。
Spring cloud 的核心组件:
Spring Cloud Zookeeper:基于Apache Zookeeper的服务治理组件
首先,他们都是分布式管理框架。
Dubbo 是二进制传输,占用带宽会少一点。SpringCloud是http 传输,带宽会多一点,同时使用http协议一般会使用JSON报文,消耗会更大。
Dubbo 开发难度较大,所依赖的 jar 包有很多问题大型工程无法解决。SpringCloud 对第三方的继承可以一键式生成,天然集成。 SpringCloud 接口协议约定比较松散,需要强有力的行政措施来限制接口无序升级。
最大的区别: Spring Cloud抛弃了Dubbo 的RPC通信,采用的是基于HTTP的REST方式。 严格来说,这两种方式各有优劣。虽然在一定程度上来说,后者牺牲了服务调用的性能,但也避免了上面提到的原生RPC带来的问题。而且REST相比RPC更为灵活,服务提供方和调用方的依赖只依靠一纸契约,不存在代码级别的强依赖,这在强调快速演化的微服务环境下,显得更为合适。
Eureka : 就是服务注册中心(可以是一个集群),对外暴露自己地址;
提供者 : 启动后向Eureka注册自己信息(地址,提供什么服务)
消费者 : 向Eureka 订阅服务,Eureka会将对应服务的服务列表发送给消费者,并且定期更新
心跳(续约): 提供者定期通过http方式向Eureka刷新自己的状态
服务注册
服务提供者在启动时,会向EurekaServer发起一次请求,将自己注册到Eureka注册中心中去
服务续约
在注册服务完成以后,服务提供者会维持一个心跳(每30s定时向EurekaServer 分发起请求)告诉EurekaServer "我还活着"
失效剔除
有时候,我们的服务提供方并不一定是正常下线,可能是内存溢出,网络故障等原因导致服务无法正常工作.EurekaServer会将这些失效的服务剔除服务列表.因此它会开启一个定时任务.每隔60秒会对失效的服务进行一次剔除
自我保护
当服务未按时进行心跳续约时,在生产环境下,因为网络原因,此时就把服务从服务列表中剔除并不妥当发,因为服务也有可能未宕机.Eureka就会把当前实例的注册信息保护起来,不允剔除.这种方式在生产环境下很有效,保证了大多数服务依然可用
Eureka Server: 提供服务注册服务,各个节点启动后会在这里进行注册,这样Eureka Server中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观的看到.
Eureka Client: 是一个java客户端,用于简化Eureka Server的交互,客户端同时也具备一个内置的、使用轮询(round-robin)负载复法的负载均衡器。在应用启动后,将会向Eureka Server发送心跳(默认周期为30秒),如果Eureka Server在多个心跳周期内没有接收到某 Eureka Server将会从服务注册表中把这个服务节点移除(默认90)
默认情况下,如果 EurekaServer在一定时间内没有接收到某个微服务实例的心, EurekaServer将会注销该实例(默认90秒)但是当网络分区故障发生时,微服务与 EurekaServer之间无法正常通信,以上行为可能变得非常危险了——因为微服务本身其实是健康的,此时本不应该注销这个微服务。 Eureka通过“自我保护模式来解决这个问题—当 EurekaServer节点在短时间内丢失过多客户端时(可能发生了网络分区故障),那么这个节点就会进入自我保护模式。一旦进入该模式, Eureka Serverl就会保护服务注册表中的信息,不再删除服务注册表中的数据(也就是不会注销任何微服努)。当网络故障恢复后,该 Eureka Server节点会自动退出自我保护模式。
在自我保护模式中, Eureka Server会保护服务注册表中的信息,不再注销任何服务实例。当它收到的心跳数重新恢复到阈值以上时,该 Eureka Server节点就会自动退出自我保护模式。它的设计哲学就是宁可保留错误的服务注册信息,也不盲目注销任何可能健康的服务实例。一句话讲解:好死如赖活着
综上,自我保护模式是一种应对网络异常的安全保护措施。它的架构哲学是宁可同时保留所有微服务(健康的微服务和不健康的微服务都会保留),也不盲目注销任何健康的微服务。使用自我保护模式,可以让 Eureka集群更加的健壮在 Spring Cloud中,
可以使用 eureka, server enable-sef- preservation= false禁用自我保护模式
不推荐禁用建议要更改注销实例的时间: eureka instance. lease-expiration-duration-in-seconds
分布式的CAP理论告诉我们“任何一个分布式系统都无法同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance),最多只能同时满足两项。”所以,很多系统在设计之初就要对这三者做出取舍。
Eureka 遵守:可用性(Availability)和分区容错性(Partition tolerance)
1. Eureka取CAP中的AP,注重可用性,Zookepper取CAP理论中的CP强调高的一致性:
2. ZooKeeper有Leader和Follower角色,Eureka各个节点平等
3. ZooKeeper采用过半数存活原则,Eureka采用自我保护机制解决分区问题
4. Eureka本质上是一个工程,而ZooKeeper只是一个进程
原因:由于心跳机制,默认30s,只有当实例、服务器、客户端的本地缓存数据一致时才能被其他服务发现(3次心跳),可以修改心跳时间间隔(eureka.instance.leaseRenewal-IntervalInSeconds)
关闭自我保护、修改清理间隔时间(修改配置文件)
DiscoveryManager.getInstance().shutdownComponent();
注:如果集群中有一个eureka server宕机。不会有类似选举的过程,客户端请求会自动切换到新的eureka server节点,维持强一致性和高可用性,对网络分区等问题采用自我保护机制,保留信息不过期。
造成灾难性雪崩效应的原因,可以简单归结为下述三种:
雪崩效应最终的结果就是:服务链条中的某一个服务不可用,导致一系列的服务不可用,最终造成服务逻辑崩溃。这种问题造成的后果,往往是无法预料的。
服务熔断:Hystrix 会记录各个服务的请求信息,通过 成功、失败、拒绝、超时 等统计信息判断是否打开断路器,将某个服务的请求进行熔断。一段时间后切换到半开路状态,如果后面的请求正常则关闭断路器,否则继续打开断路器。
服务降级:服务降级是请求失败时的后备方法,故障时执行降级逻辑。
资源隔离:Hystrix 通过线程池实现资源的隔离,确保对某一服务的调用在出现故障时不会对其他服务造成影响。
限流:避免突发量请求,导致服务宕机。
运维监控:实现了服务监控、报警和运维控制。Hystrix Dashboard和Turbine可以配合Hystrix完成这些功能。
当请求超时、资源不足等情况发生时进行服务降级处理,不调用真实服务逻辑,而是使用快速失败(fallback)方式直接返回一个托底数据,保证服务链条的完整,避免服务雪崩。
/**
* 服务降级处理。
* 当前方法远程调用service服务的时候,如果service服务出现了任何错误(超时,异常等)
* 不会将异常抛到客户端,而是使用本地的一个fallback(错误返回)方法来返回一个托底数据。
* 避免客户端看到错误页面。
* 使用注解来描述当前方法的服务降级逻辑。
* @HystrixCommand - 开启Hystrix命令的注解。代表当前方法如果出现服务调用问题,使用Hystrix逻辑来处理。
* 重要属性 - fallbackMethod
* 错误返回方法名。如果当前方法调用服务,远程服务出现问题的时候,调用本地的哪个方法得到托底数据。
* Hystrix会调用fallbackMethod指定的方法,获取结果,并返回给客户端。
* @return
*/
@HystrixCommand(fallbackMethod="downgradeFallback")
public List
请求合并是指,在一定时间内,收集一定量的同类型请求,合并请求需求后,一次性访问服务提供者,得到批量结果。这种方式可以减少服务消费者和服务提供者之间的通讯次数,提升应用执行效率。
未使用请求合并:
使用请求合并:
什么情况下使用请求合并:
在微服务架构中,我们将一个项目拆分成很多个独立的模块,这些独立的模块通过远程调用来互相配合工作,但是,在高并发情况下,通信次数的增加会导致总的通信时间增加,同时,线程池的资源也是有限的,高并发环境会导致有大量的线程处于等待状态,进而导致响应延迟,为了解决这些问题,我们需要来了解Hystrix的请求合并。
通常来说,服务链条超出4个,不推荐使用请求合并。因为请求合并有等待时间。
请求合并的缺点:
设置请求合并之后,本来一个请求可能5ms就搞定了,但是现在必须再等10ms看看还有没有其他的请求一起的,这样一个请求的耗时就从5ms增加到15ms了,不过,如果我们要发起的命令本身就是一个高延迟的命令,那么这个时候就可以使用请求合并了,因为这个时候时间窗的时间消耗就显得微不足道了,另外高并发也是请求合并的一个非常重要的场景。
@HystrixCollapser注解介绍:此注解描述的方法,返回值类型必须是java.util.concurrent.Future类型的。代表方法为异步方法。
@HystrixCollapser注解的属性:
batchMethod - 请求合并方法名。
scope - 请求合并方式。可选值有REQUEST和GLOBAL。REQUEST代表在一个request请求生命周期内的多次远程服务调用请求需要合并处理,此为默认值。GLOBAL代表所有request线程内的多次远程服务调用请求需要合并处理。
timerDelayInMilliseconds - 多少时间间隔内的请求进行合并处理,默认值为10ms。建议设置时间间隔短一些,如果单位时间并发量不大,并没有请求合并的必要。
maxRequestsInBatch - 设置合并请求的最大极值,也就是timerDelayInMilliseconds时间内,最多合并多少个请求。默认值是Integer.MAX_VALUE。
@Service
public class HystrixService {
@Autowired
private LoadBalancerClient loadBalancerClient;
/**
* 需要合并请求的方法。
* 这种方法的返回结果一定是Future类型的。
* 这种方法的处理逻辑都是异步的。
* 是application client在一定时间内收集客户端请求,或收集一定量的客户端请求,一次性发给application service。
* application service返回的结果,application client会进行二次处理,封装为future对象并返回
* future对象需要通过get方法获取最终的结果。 get方法是由控制器调用的。所以控制器调用service的过程是一个异步处理的过程。
* 合并请求的方法需要使用@HystrixCollapser注解描述。
* batchMethod - 合并请求后,使用的方法是什么。如果当前方法有参数,合并请求后的方法参数是当前方法参数的集合,如 int id >> int[] ids。
* scope - 合并请求的请求作用域。可选值有global和request。
* global代表所有的请求线程都可以等待可合并。 常用,所有浏览器或者请求源(Postman、curl等)调用的请求
* request代表一个请求线程中的多次远程服务调用可合并
* collapserProperties - 细致配置。就是配置合并请求的特性。如等待多久,如可合并请求的数量。
* 属性的类型是@HystrixProperty类型数组,可配置的属性值可以直接通过字符串或常量类定义。
* timerDelayInMilliseconds - 等待时长
* maxRequestsInBatch - 可合并的请求最大数量。
*
* 方法处理逻辑不需要实现,直接返回null即可。
* 合并请求一定是可合并的。也就是同类型请求。同URL的请求。
* @param id
* @return
*/
@HystrixCollapser(batchMethod = "mergeRequest",
scope = com.netflix.hystrix.HystrixCollapser.Scope.GLOBAL,
collapserProperties = {
// 请求时间间隔在20ms之内的请求会被合并为一个请求,默认为10ms
@HystrixProperty(name = "timerDelayInMilliseconds", value = "20"),
// 设置触发批处理执行之前,在批处理中允许的最大请求数。
@HystrixProperty(name = "maxRequestsInBatch", value = "200"),
})
public Future> testMergeRequest(Long id){
return null;
}
/**
* 批量处理方法。就是合并请求后真实调用远程服务的方法。
* 必须使用@HystrixCommand注解描述,代表当前方法是一个Hystrix管理的服务容错方法。
* 是用于处理请求合并的方法。
* @param ids
* @return
*/
@HystrixCommand
public List> mergeRequest(List ids){
ServiceInstance si =
this.loadBalancerClient.choose("eureka-application-service");
StringBuilder sb = new StringBuilder();
sb.append("http://").append(si.getHost())
.append(":").append(si.getPort()).append("/testMerge?");
for(int i = 0; i < ids.size(); i++){
Long id = ids.get(i);
if(i != 0){
sb.append("&");
}
sb.append("ids=").append(id);
}
System.out.println("request application service URL : " + sb.toString());
RestTemplate rt = new RestTemplate();
ParameterizedTypeReference>> type =
new ParameterizedTypeReference>>() {
};
ResponseEntity>> response =
rt.exchange(sb.toString(), HttpMethod.GET, null, type);
List> result = response.getBody();
return result;
}
}
当一定时间内,异常请求比例(请求超时、网络故障、服务异常等)达到阀值时,启动熔断器,熔断器一旦启动,则会停止调用具体服务逻辑,通过fallback快速返回托底数据,保证服务链的完整。
熔断有自动恢复机制,如:当熔断器启动后,每隔5秒,尝试将新的请求发送给服务提供者,如果服务可正常执行并返回结果,则关闭熔断器,服务恢复。如果仍旧调用失败,则继续返回托底数据,熔断器持续开启状态。
/**
* 熔断机制
* 相当于一个强化的服务降级。 服务降级是只要远程服务出错,立刻返回fallback结果。
* 熔断是收集一定时间内的错误比例,如果达到一定的错误率。则启动熔断,返回fallback结果。
* 间隔一定时间会将请求再次发送给application service进行重试。如果重试成功,熔断关闭。
* 如果重试失败,熔断持续开启,并返回fallback数据。
* @HystrixCommand 描述方法。
* fallbackMethod - fallback方法名
* commandProperties - 具体的熔断标准。类型是HystrixProperty数组。
* 可以通过字符串或常亮类配置。
* CIRCUIT_BREAKER_REQUEST_VOLUME_THRESHOLD - 错误数量。在10毫秒内,出现多少次远程服务调用错误,则开启熔断。
* 默认20个。 10毫秒内有20个错误请求则开启熔断。
* CIRCUIT_BREAKER_ERROR_THRESHOLD_PERCENTAGE - 错误比例。在10毫秒内,远程服务调用错误比例达标则开启熔断。
* CIRCUIT_BREAKER_SLEEP_WINDOW_IN_MILLISECONDS - 熔断开启后,间隔多少毫秒重试远程服务调用。默认5000毫秒。
* @return
*/
@HystrixCommand(fallbackMethod = "breakerFallback",
commandProperties = {
// 默认20个;10ms内请求数大于20个时就启动熔断器,当请求符合熔断条件时将触发getFallback()。
@HystrixProperty(name=HystrixPropertiesManager.CIRCUIT_BREAKER_REQUEST_VOLUME_THRESHOLD,
value="10"),
// 请求错误率大于50%时就熔断,然后for循环发起请求,当请求符合熔断条件时将触发getFallback()。
@HystrixProperty(name=HystrixPropertiesManager.CIRCUIT_BREAKER_ERROR_THRESHOLD_PERCENTAGE,
value="50"),
// 默认5秒;熔断多少秒后去尝试请求
@HystrixProperty(name=HystrixPropertiesManager.CIRCUIT_BREAKER_SLEEP_WINDOW_IN_MILLISECONDS,
value="5000")}
)
注解属性描述:
CIRCUIT_BREAKER_ENABLED
"circuitBreaker.enabled";
# 是否开启熔断策略。默认值为true。
CIRCUIT_BREAKER_REQUEST_VOLUME_THRESHOLD
"circuitBreaker.requestVolumeThreshold";
# 10ms内,请求并发数超出则触发熔断策略。默认值为20。
CIRCUIT_BREAKER_SLEEP_WINDOW_IN_MILLISECONDS
"circuitBreaker.sleepWindowInMilliseconds";
# 当熔断策略开启后,延迟多久尝试再次请求远程服务。默认为5秒。
CIRCUIT_BREAKER_ERROR_THRESHOLD_PERCENTAGE
"circuitBreaker.errorThresholdPercentage";
# 10ms内,出现错误的请求百分比达到限制,则触发熔断策略。默认为50%。
CIRCUIT_BREAKER_FORCE_OPEN
"circuitBreaker.forceOpen";
# 是否强制开启熔断策略。即所有请求都返回fallback托底数据。默认为false。
CIRCUIT_BREAKER_FORCE_CLOSED
"circuitBreaker.forceClosed";
# 是否强制关闭熔断策略。即所有请求一定调用远程服务。默认为false。
所谓隔离,就是当服务发生问题时,使用技术手段隔离请求,保证服务调用链的完整。隔离分为线程池隔离和信号量隔离两种实现方式。
所谓线程池隔离,就是将并发请求量大的部分服务使用独立的线程池处理,避免因个别服务并发过高导致整体应用宕机。
线程池隔离优点:
线程池隔离缺点:
线程池隔离机制,会导致服务硬件计算开销加大(CPU计算、调度等),每个命令的执行都涉及到排队、调度、上下文切换等,这些命令都是在一个单独的线程上运行的。
线程池隔离的实现方式同样是使用@HystrixCommand注解。相关注解配置属性如下:
/**
* 如果使用了@HystrixCommand注解,则Hystrix自动创建独立的线程池。
* groupKey和threadPoolKey默认值是当前服务方法所在类型的simpleName
*
* 所有的fallback方法,都执行在一个HystrixTimer线程池上。
* 这个线程池是Hystrix提供的一个,专门处理fallback逻辑的线程池。
*
* 线程池隔离实现
* 线程池隔离,就是为某一些服务,独立划分线程池。让这些服务逻辑在独立的线程池中运行。
* 不使用tomcat提供的默认线程池。
* 线程池隔离也有熔断能力。如果线程池不能处理更多的请求的时候,会触发熔断,返回fallback数据。
* groupKey - 分组名称,就是为服务划分分组。如果不配置,默认使用threadPoolKey作为组名。
* commandKey - 命令名称,默认值就是当前业务方法的方法名。
* threadPoolKey - 线程池命名,真实线程池命名的一部分。Hystrix在创建线程池并命名的时候,会提供完整命名。默认使用gourpKey命名
* 如果多个方法使用的threadPoolKey是同名的,则使用同一个线程池。
* threadPoolProperties - 为Hystrix创建的线程池做配置。可以使用字符串或HystrixPropertiesManager中的常量指定。
* 常用线程池配置:
* coreSize - 核心线程数。最大并发数。1000*(99%平均响应时间 + 适当的延迟时间)
* maxQueueSize - 阻塞队列长度。如果是-1这是同步队列。如果是正数这是LinkedBlockingQueue。如果线程池最大并发数不足,
* 提供多少的阻塞等待。
* keepAliveTimeMinutes - 心跳时间,超时时长。单位是分钟。
* queueSizeRejectionThreshold - 拒绝临界值,当最大并发不足的时候,超过多少个阻塞请求,后续请求拒绝。
*/
@HystrixCommand(groupKey="test-thread-quarantine",
commandKey = "testThreadQuarantine",
threadPoolKey="test-thread-quarantine",
threadPoolProperties = {
@HystrixProperty(name="coreSize", value="30"),
@HystrixProperty(name="maxQueueSize", value="100"),
@HystrixProperty(name="keepAliveTimeMinutes", value="2"),
@HystrixProperty(name="queueSizeRejectionThreshold", value="15")
},
fallbackMethod = "threadQuarantineFallback")
关于线程池:
所谓信号量隔离,就是设置一个并发处理的最大极值。当并发请求数超过极值时,通过fallback返回托底数据,保证服务完整性。
信号量隔离同样通过@HystrixCommand注解配置,常用注解属性有:
/**
* 信号量隔离实现
* 不会使用Hystrix管理的线程池处理请求。使用容器(Tomcat)的线程处理请求逻辑。
* 不涉及线程切换,资源调度,上下文的转换等,相对效率高。
* 信号量隔离也会启动熔断机制。如果请求并发数超标,则触发熔断,返回fallback数据。
* commandProperties - 命令配置,HystrixPropertiesManager中的常量或字符串来配置。
* execution.isolation.strategy - 隔离的种类,可选值只有THREAD(线程池隔离)和SEMAPHORE(信号量隔离)。
* 默认是THREAD线程池隔离。
* 设置信号量隔离后,线程池相关配置失效。
* execution.isolation.semaphore.maxConcurrentRequests - 信号量最大并发数。默认值是10。常见配置500~1000。
* 如果并发请求超过配置,其他请求进入fallback逻辑。
*/
@HystrixCommand(fallbackMethod="semaphoreQuarantineFallback",
commandProperties={
@HystrixProperty(
name=HystrixPropertiesManager.EXECUTION_ISOLATION_STRATEGY,
value="SEMAPHORE"), // 信号量隔离
@HystrixProperty(
name=HystrixPropertiesManager.EXECUTION_ISOLATION_SEMAPHORE_MAX_CONCURRENT_REQUESTS,
value="100") // 信号量最大并发数
})
@FeignClient("microservice-provider-user")
public interface UserFeignClient {
@RequestMapping(value = "/simple/{id}", method = RequestMethod.GET)
public User findById(@PathVariable("id") Long id);
...
}
@PathVariable("id")
中的”id”,不能省略,必须指定
FeignClient("microservice-provider-user")
public interface UserFeignClient {
@RequestMapping(value = "/simple/{id}", method = RequestMethod.GET)
public User findById(@PathVariable("id") Long id);
...
}
直接使用复杂对象:该请求不会成功,只要参数是复杂对象,即使指定了是GET方法,feign依然会以POST方法进行发送请求。
@FeignClient("microservice-provider-user")
public interface UserFeignClient {
@RequestMapping(value = "/query-by", method = RequestMethod.GET)
public User queryBy(User user);
...
}
正确的写法
// 写法一
@FeignClient("microservice-provider-user")
public interface UserFeignClient {
@RequestMapping(value = "/query-by", method = RequestMethod.GET)
public User queryBy(@RequestParam("id")Long id, @RequestParam("username")String username);
}
//写法二
@FeignClient(name = "microservice-provider-user")
public interface UserFeignClient {
@RequestMapping(value = "/query-by", method = RequestMethod.GET)
public List queryBy(@RequestParam Map param);
}
我们知道Feign本身就是支持Hystrix的,可以直接使用@FeignClient(value = "microservice-provider-user", fallback = XXX.class)
来指定fallback的类,这个fallback类集成@FeignClient所标注的接口即可。
但是假设我们需要使用Hystrix Stream进行监控,默认情况下,访问http://IP:PORT/hystrix.stream 是个404。如何为Feign增加Hystrix Stream支持呢?
第一步:添加依赖,示例:、
org.springframework.cloud
spring-cloud-starter-hystrix
第二步:在启动类上添加@EnableCircuitBreaker 注解,示例:
@SpringBootApplication
@EnableFeignClients
@EnableDiscoveryClient
@EnableCircuitBreaker
public class MovieFeignHystrixApplication {
public static void main(String[] args) {
SpringApplication.run(MovieFeignHystrixApplication.class, args);
}
}
Feign的@Configuration 注解的类不能与@ComponentScan 的包重叠。如果包重叠,将会导致所有的Feign Client都会使用该配置。
造成该问题的原因
Hystrix默认的超时时间是1秒,如果超过这个时间尚未响应,将会进入fallback代码。而首次请求往往会比较慢(因为Spring的懒加载机制,要实例化一些类),这个响应时间可能就大于1秒了。知道原因后,我们来总结一下解决放你。
方法1:
该配置是让Hystrix的超时时间改为5秒
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 5000
方法2:
该配置,用于禁用Hystrix的超时时间
hystrix.command.default.execution.timeout.enabled: false
方法3:
禁用feign的hystrix。该做法除非一些特殊场景,不推荐使用
feign.hystrix.enabled: false
1. serviceId属性已经失效,尽量使用name属性。例如:
@FeignClient(serviceId = "microservice-provider-user")
这么写是不推荐的,应写为:
@FeignClient(name = "microservice-provider-user")
2. 在使用url属性时,在老版本的Spring Cloud中,不需要提供name属性,但是在新版本(例如Brixton、Camden)@FeignClient必须提供name属性,并且name、url属性支持占位符。例如:
@FeignClient(name = "${feign.name}", url = "${feign.url}")
Spring Cloud Feign添加自定义Header_chuihou6312的博客-CSDN博客
方案一:方法上的@RequestMapping注解添加headers信息
@FeignClient(name = "server",url = "127.0.0.1:8080")
public interface FeignTest {
@RequestMapping(value = "/test",headers = {"app=test-app","token=${test-app.token}"})
String test();
}
方案二:接口上的@RequestMapping注解添加headers信息
@FeignClient(name = "server",url = "127.0.0.1:8080")
@RequestMapping(value = "/",headers = {"app=test-app","token=${test-app.token}"})
public interface FeignTest {
@RequestMapping(value = "/test")
String test();
}
方案三:使用@Headers注解添加headers信息
@FeignClient(name = "server",url = "127.0.0.1:8080")
@Headers({"app: test-app","token: ${test-app.token}"})
public interface FeignTest {
@RequestMapping(value = "/test")
String test();
}
方案四:自定义RequestInterceptor添加headers信息
@Component
public class FeignRequestInterceptor implements RequestInterceptor {
@Value("${test-app.token}")
private String token;
@Override
public void apply(RequestTemplate requestTemplate) {
requestTemplate.header("app","test-app");//静态
requestTemplate.header("token",token);//读配置
}
}=
方案五:自定义RequestInterceptor实现添加动态数据到header
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = Objects.requireNonNull(attributes).getRequest();
String signedMsg = getSignedMsg(reqJson); // 计算签名字符串
Map reqMap = new HashMap<>();
reqMap.put("content-type", "application/json");//常量字段
reqMap.put("accessKey", accessKey);//常量字段
reqMap.put("signedMsg", signedMsg);//动态计算/获取字段
request.setAttribute("customizedRequestHeader", reqMap);
@Component
public class FeignRequestInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate requestTemplate) {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
// 设置自定义header
// 设置request中的attribute到header以便转发到Feign调用的服务
Enumeration reqAttrbuteNames = request.getAttributeNames();
if (reqAttrbuteNames != null) {
while (reqAttrbuteNames.hasMoreElements()) {
String attrName = reqAttrbuteNames.nextElement();
if (!"customizedRequestHeader".equalsIgnoreCase(attrName)) {
continue;
}
Map requestHeaderMap = (Map)request.getAttribute(attrName);
for (Map.Entry entry : requestHeaderMap.entrySet()) {
requestTemplate.header(entry.getKey(), entry.getValue());
}
break;
}
}
}
}
通过zuul访问服务的,URL地址默认格式为:http://zuulHostIp:port/要访问的服务名称/服务中的URL
服务名称:properties配置文件中的spring.application.name。
服务的URL:就是对应的服务对外提供的URL路径监听。
Zuul中提供了过滤器定义,可以用来过滤代理请求,提供额外功能逻辑。如:权限验证,日志记录等。
Zuul提供的过滤器是一个父类。父类是ZuulFilter。通过父类中定义的抽象方法filterType,来决定当前的Filter种类是什么。有前置过滤、路由后过滤、后置过滤、异常过滤。
继承父类ZuulFilter。在父类中提供了4个抽象方法,分别是:filterType, filterOrder, shouldFilter, run。其功能分别是:
filterType:方法返回字符串数据,代表当前过滤器的类型。可选值有-pre, route, post, error。
filterOrder:返回int数据,用于为同filterType的多个过滤器定制执行顺序,返回值越小,执行顺序越优先。
shouldFilter:返回boolean数据,代表当前filter是否生效。
run:具体的过滤执行逻辑。如pre类型的过滤器,可以通过对请求的验证来决定是否将请求路由到服务上;如post类型的过滤器,可以对服务响应结果做加工处理(如为每个响应增加footer数据)。
在Edgware版本之前,Zuul提供了接口ZuulFallbackProvider用于实现fallback处理。从Edgware版本开始,Zuul提供了ZuulFallbackProvider的子接口FallbackProvider来提供fallback处理。
Zuul的fallback容错处理逻辑,只针对timeout异常处理,当请求被Zuul路由后,只要服务有返回(包括异常),都不会触发Zuul的fallback容错逻辑。
@Component
public class TestFallBbackProvider implements FallbackProvider {
/**
* return - 返回fallback处理哪一个服务。返回的是服务的名称
* 推荐 - 为指定的服务定义特性化的fallback逻辑。
* 推荐 - 提供一个处理所有服务的fallback逻辑。
* 好处 - 服务某个服务发生超时,那么指定的fallback逻辑执行。如果有新服务上线,未提供fallback逻辑,有一个通用的。
*/
@Override
public String getRoute() {
return "eureka-application-service";
}
/**
* fallback逻辑。在早期版本中使用。
* Edgware版本之后,ZuulFallbackProvider接口过期,提供了新的子接口FallbackProvider
* 子接口中提供了方法ClientHttpResponse fallbackResponse(Throwable cause)。
* 优先调用子接口新定义的fallback处理逻辑。
*/
@Override
public ClientHttpResponse fallbackResponse() {
System.out.println("ClientHttpResponse fallbackResponse()");
List> result = new ArrayList<>();
Map data = new HashMap<>();
data.put("message", "服务正忙,请稍后重试");
result.add(data);
ObjectMapper mapper = new ObjectMapper();
String msg = "";
try {
msg = mapper.writeValueAsString(result);
} catch (JsonProcessingException e) {
msg = "";
}
return this.executeFallback(HttpStatus.OK, msg,
"application", "json", "utf-8");
}
/**
* fallback逻辑。优先调用。可以根据异常类型动态决定处理方式。
*/
@Override
public ClientHttpResponse fallbackResponse(Throwable cause) {
System.out.println("ClientHttpResponse fallbackResponse(Throwable cause)");
if(cause instanceof NullPointerException){
List> result = new ArrayList<>();
Map data = new HashMap<>();
data.put("message", "网关超时,请稍后重试");
result.add(data);
ObjectMapper mapper = new ObjectMapper();
String msg = "";
try {
msg = mapper.writeValueAsString(result);
} catch (JsonProcessingException e) {
msg = "";
}
return this.executeFallback(HttpStatus.GATEWAY_TIMEOUT,
msg, "application", "json", "utf-8");
}else{
return this.fallbackResponse();
}
}
/**
* 具体处理过程。
* @param status 容错处理后的返回状态,如200正常GET请求结果,201正常POST请求结果,404资源找不到错误等。
* 使用spring提供的枚举类型对象实现。HttpStatus
* @param contentMsg 自定义的响应内容。就是反馈给客户端的数据。
* @param mediaType 响应类型,是响应的主类型, 如: application、text、media。
* @param subMediaType 响应类型,是响应的子类型, 如: json、stream、html、plain、jpeg、png等。
* @param charsetName 响应结果的字符集。这里只传递字符集名称,如: utf-8、gbk、big5等。
* @return ClientHttpResponse 就是响应的具体内容。
* 相当于一个HttpServletResponse。
*/
private final ClientHttpResponse executeFallback(final HttpStatus status,
String contentMsg, String mediaType, String subMediaType, String charsetName) {
return new ClientHttpResponse() {
/**
* 设置响应的头信息
*/
@Override
public HttpHeaders getHeaders() {
HttpHeaders header = new HttpHeaders();
MediaType mt = new MediaType(mediaType, subMediaType, Charset.forName(charsetName));
header.setContentType(mt);
return header;
}
/**
* 设置响应体
* zuul会将本方法返回的输入流数据读取,并通过HttpServletResponse的输出流输出到客户端。
*/
@Override
public InputStream getBody() throws IOException {
String content = contentMsg;
return new ByteArrayInputStream(content.getBytes());
}
/**
* ClientHttpResponse的fallback的状态码 返回String
*/
@Override
public String getStatusText() throws IOException {
return this.getStatusCode().getReasonPhrase();
}
/**
* ClientHttpResponse的fallback的状态码 返回HttpStatus
*/
@Override
public HttpStatus getStatusCode() throws IOException {
return status;
}
/**
* ClientHttpResponse的fallback的状态码 返回int
*/
@Override
public int getRawStatusCode() throws IOException {
return this.getStatusCode().value();
}
/**
* 回收资源方法
* 用于回收当前fallback逻辑开启的资源对象的。
* 不要关闭getBody方法返回的那个输入流对象。
*/
@Override
public void close() {
}
};
}
}
Zuul网关组件也提供了限流保护。当请求并发达到阀值,自动触发限流保护,返回错误结果。只要提供error错误处理机制即可。
Zuul的限流保护需要额外依赖spring-cloud-zuul-ratelimit组件
# 开启限流保护
zuul.ratelimit.enabled=true
# 60s内请求超过3次,服务端就抛出异常,60s后可以恢复正常请求
zuul.ratelimit.default-policy.limit=3
zuul.ratelimit.default-policy.refresh-interval=60
# 针对IP进行限流,不影响其他IP
zuul.ratelimit.default-policy.type=origin
网关的两层超时调优
从上图中可以看出。整体请求逻辑还是比较复杂的,在没有zuul网关的情况下,app client请求app service的时候,也有请求超时的可能。那么当增加了zuul网关的时候,请求超时的可能就更明显了。
当请求通过zuul网关路由到服务,并等待服务返回响应,这个过程中zuul也有超时控制。zuul的底层使用的是Hystrix+ribbon来实现请求路由。结构如下:
使用Zuul的spring cloud微服务结构图:
zuul中的Hystrix内部使用线程池隔离机制提供请求路由实现,其默认的超时时长为1000毫秒。ribbon底层默认超时时长为5000毫秒。如果Hystrix超时,直接返回超时异常。如果ribbon超时,同时Hystrix未超时,ribbon会自动进行服务集群轮询重试,直到Hystrix超时为止。如果Hystrix超时时长小于ribbon超时时长,ribbon不会进行服务集群轮询重试。
那么在zuul中可配置的超时时长就有两个位置:Hystrix和ribbon。具体配置如下:
# 开启zuul网关重试
zuul.retryable=true
# hystrix超时时间设置
# 线程池隔离,默认超时时间1000ms
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=8000
# ribbon超时时间设置:建议设置比Hystrix小
# 请求连接的超时时间: 默认5000ms
ribbon.ConnectTimeout=5000
# 请求处理的超时时间: 默认5000ms
ribbon.ReadTimeout=5000
# 重试次数:MaxAutoRetries表示访问服务集群下原节点(同路径访问);MaxAutoRetriesNextServer表示访问服务集群下其余节点(换台服务器)
ribbon.MaxAutoRetries=1
ribbon.MaxAutoRetriesNextServer=1
# 开启重试
ribbon.OkToRetryOnAllOperations=true
Spring-cloud中的zuul网关重试机制是使用spring-retry实现的。工程必须依赖下述资源:
org.springframework.retry
spring-retry