上一篇文章有说到,springCloud的常用组件,有以下几个:
今天主要是介绍feiqn负载均衡,hystrix熔断器,zuul服务网关
一:客户端负载均衡 OpenFeign
1.什么是Fegin
1.1 为什么要使用Fegin
上一篇文章中,我们使用Ribbon作为客户端负载均衡完成了订单服务和用户服务的通信 ,当我们通过RestTemplate调用其它服务时,所需要的参数须在请求的URL中进行拼接,如果参数少的话或许我们还可以忍受,一旦有多个参数的话,这是拼接字符串就会效率低下。而Feign的服务调用方式对于程序员来说更为友好,它基于Ribbon进行了封装,把一些负责的url和参数处理细节屏蔽起来,我们只需要简单编写Fiegn的客户端接口就可以像调用本地service去调用远程微服务。
1.2 什么是Fegin
Feign是一个声明式的http客户端,使用Feign可以实现声明式REST调用,它的目的就是让Web Service调用更加简单。Feign整合了Ribbon和SpringMvc注解,这让Feign的客户端接口看起来就像一个Controller。Feign提供了HTTP请求的模板,通过简单的注解和插入接口,就可以定义好HTTP请求的参数,格式,地址等信息。而Feign则会完全代理HTTP请求,我们只需要像调用方法一样调用它就可以完成服务请求及相关处理。同时Feign整合了Hystrix,可以很容易的实现服务熔断和降级。
2.Fegin的编码实战
官方参考文档:OpenFeign,我们这里想要实现的效果和之前学习Ribbon案例的效果一样:
我们在上一篇文章的项目的基础上,增加一个订单的模块来演Fegin,那在这里支付服务和订单服务充当的角色一样,都是服务消费者,通过调用用户服务来获取数据。
2.1 创建工程模块
创建工程 springcloud-netflix-service-pay,用来集成Feign,效果如下:
2.2 导入依赖
导入Feign,Eureka Client和web的依赖包,同时依赖user-common模块 ,具体的 pom.xml如下
<dependencies>
<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>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--导入公共模块user-->
<dependency>
<groupId>com.lqf</groupId>
<artifactId>springcloud-netflix-pojo-user</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
2.3 配置yml文件
#注册到EurekaServer
eureka:
client:
serviceUrl:
defaultZone: http://localhost:10010/eureka/
instance:
prefer-ip-address: true #使用ip地址进行注册
instance-id: server-pay:10040 #实例ID
spring:
application:
name: server-pay #服务的名字
server:
port: 10040
feign:
hystrix:
enabled: true #开启Hystrix
2.4 配置启动类
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients // 开启Feign支持 扫描接口 就是扫描使用@FeignClient注解的接口
public class PayApplication {
public static void main(String[] args) {
SpringApplication.run(PayApplication.class);
}
}
2.5 编写Fegin的客户端接口
Feign的客户端接口是用来调用微服务的,我这里就编写了一个用来调用用户服务的客户端接口,如下:
@FeignClient("server-user") // 调用服务的服务名
public interface UserFeignClient {
@GetMapping("/user/{id}")
User getById(@PathVariable("id") Long id);
}
@FeignClient("server-user") : server-user是用户服务的服务名字,Feign根据服务名能够在注册中心找到目标服务的通信地址
你会发现接口中的方法跟用户服务(springcloud-netflix-service-user)中的方法一样,其实Feign就是通过客户端接口里面的方法,来决定目标服务的资源路径url,参数以及返回值,这里我们可以直接把要调用的目标服务的controller方法拷贝过来,然后去掉方法体即可。
注意:接口和目标服务的服务名要一致 , url路径要一致 , 参数要一致 , 返回值类型要一致。
建议 :服务名直接去目标服务配置中拷贝 , 方法直接从目标服务controller中拷贝
2.6 编写controller,使用Fegin接口
@RestController
public class PayController {
@Autowired
private UserFeignClient userFeignClient; // 注入UserFeignClient接口
@GetMapping("/pay/{id}")
public User getById(@PathVariable("id")Long id){
return userFeignClient.getById(id); // 刚刚这个接口的方法返回的也是user,就直接return
}
}
这里调用UserFeignClient.getById方法,看起来是像在调用本地方法,其实该接口已经被Feign代理,我们发起调用时其实在像Feign接口配置的目标服务发起调用。
2.7 测试
通过浏览器访问server-pay的controller:http://localhost:10040/pay/1,多次请求发现依然默认使用了轮询策略.
2.8 Fegin工作原理
要使用Feign,我们除了导入依赖之外,需要主配置类通过@EnableFeignClients(value="")
注解开启Feign,也可以通过value属性指定了Feign的扫描包。同时我们需要为Feign编写客户端接口,接口上需要注解@FeignClient
标签。 当程序启动,注解了@FeignClient的接口将会被扫描到然后交给Spring管理
。
当请求发起,会使用jdk的动态代理方式代理接口,生成相应的RequestTemplate
,Feign会为每个方法生成一个RequestTemplate同时封装好http信息
最终RequestTemplate生成request请求,交给Http客户端,
然后Http客户端会交给LoadBalancerClient(负载均衡客户端),使用Ribbon的负载均衡发起调用。
3.Fegin开启日志调试
3.1 配置Fegin日志打印内容
有的时候我们需要看到Feign的调用过程中的参数及相应,我们可以对Feign的日志进行配置,Feign支持如下几种日志模式来决定日志记录内容多少:
-
NONE,不记录(DEFAULT)。
-
BASIC,仅记录请求方法和URL以及响应状态代码和执行时间。
-
HEADERS,记录基本信息以及请求和响应标头。
-
FULL,记录请求和响应的标题,正文和元数据。
@Configuration // 配置类注解
public class FeignConfiguration {
@Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL; // 打印Feign的所有日志
}
}
3.2 配置日志打印机别
配置UserFeignClient的日志打印级别,上面的配置打印Feign的那些内容,下面这个是配置日志框架打印日志的级别,不修改可能打印不出来日志,DEBUG打印日志调试信息。
logging:
level:
com.lqf: debug
二:Hystrix熔断器
1.理解Hystrix
1.1 雪崩效应
在电影里面经常出现的场景,在冰山雪地不要大声呼喊,因为声音的震动会导致雪球的滑落,然后引起连锁反应导致整个雪山的崩塌这就是生活中的雪崩。在微服务里面也是一样,服务的调用非常复杂的 ,一个请求往往需要很多的微服务共同完成,可能会形成很长的服务调用链,在整个服务调用链中,某一个服务发生故障会导致调用它的服务跟着异常,然后导致整个调用链调用的异常,甚至导致整个微服务瘫痪 , --- 这就是雪崩效应。如下图:
1.2 Hystrix的介绍
Hystrix是国外知名的视频网站Netflix所开源的非常流行的高可用架构框架。Hystrix能够完美的解决分布式系统架构中打造高可用服务面临的一系列技术难题,如雪崩。
Hystrix是处理依赖隔离的框架,将出现故障的服务通过熔断、降级等手段隔离开来
,这样不影响整个系统的主业务(比如你得了传染病是不是要把你关起来隔离呢),同时也是可以帮我们做服务的治理和监控
。
Hystrix的英文是豪猪,中文翻译为 熔断器,其思想来源于我们家里的保险开关,当家里出现短路,保险开关及时切掉电路,保证家里人员的安全,其目的就是起保护作用。
Hystrix其设计原则如下:
-
防止单个服务异常导致整个微服务故障。
-
快速失败,如果服务出现故障,服务的请求快速失败,线程不会等待。
-
服务降级,请求故障可以返回设定好的二手方案数据(兜底数据)。
-
熔断机制,防止故障的扩散,导致整个服务瘫痪。
-
服务监控,提供了Hystrix Bashboard仪表盘,实时监控熔断器状态
1.3 Hystrix的功能
1.资源隔离
资源隔离包括线程池隔离和信号量隔离
,作用是限制调用分布式服务的资源使用
,某一个调用的服务出现问题不会影响其他服务调用 ,这里可以简单的理解为资源隔离就是限制请求的数量
。
线程池隔离:使用一个线程池来存储当前请求,线程池对请求作处理,设置任务返回处理超时时间,堆积的请求先入线程池队列。这种方式要为每个依赖服务申请线程池,有一定的资源消耗,好处是可以应对突发流量(流量洪峰来临时,处理不完可将数据存储到线程池队里慢慢处理)
信号量隔离:使用一个原子计数器(或信号量)记录当前有多少个线程在运行,请求来先判断计数器的数值,若超过设置的最大线程个数则丢弃该类型的新请求,若不超过则执行计数操作请求来计数器+1,请求返回计数器-1。这种方式是严格的控制线程且立即返回模式,无法应对突发流量(流量洪峰来临时,处理的线程超过数量,其他的请求会直接返回,不继续去请求依赖的服务)
2.服务熔断
熔断机制是对服务链路的保护机制,如果链路上的某个服务不可访问,调用超时,发生异常等,服务会触发降级返回托底数据,然后熔断服务的调用(失败率达到某个阀值服务标记为短路状态),当检查到该节点能正常使用时服务会快速恢复。
简单理解就是当服务不可访问了或者服务器报错了或者服务调用超过一定时间没返回结果,就立马触发熔断机制配合降级返回预先准备的兜底数据返回,不至于长时间的等待服务的相应造成大量的请求阻塞,也不至于返回一些错误信息给客户端,而是返回一些兜底数据。
3.降级机制
超时降级、资源不足时(线程或信号量)降级,降级后可以配合降级接口返回托底数据。
简单理解就是服务降级就是当服务因为网络故障,服务器故障,读取超时等原因造成服务不可达的情况下返回一些预先准备好的数据给客户端。
在生活中服务降级到处可见,如在系统故障我们会返回友好的提示“服务暂时不可用”,或者如淘宝双11期间退款服务,留言服务暂时不可用,这个就是为了保证正常的购物流程相关服务没问题,然后人为的关闭一些不重要的服务,配合降级返回一些托底数据返回给用户(比如返回友好的提示信息“暂时不能退款”)。
4.缓存
提供了请求缓存、请求合并实现 , 在高并发的场景之下,Hystrix请求缓存可以方便地开启和使用请求缓存来优化系统,达到减轻高并发时请求线程的消耗、降低请求响应时间的效果。
1.4 Hystrix的工作机制
正常情况下,断路器处于关闭状态(Closed),如果调用持续出错或者超时达到设定阈值,电路被打开进入熔断状态(Open),这时请求这个服务会触发快速失败(立马返回兜底数据不要让线程死等),后续一段时间内的所有调用都会被拒绝(Fail Fast),一段时间以后(withCircuitBreakerSleepWindowInMilliseconds=5s),保护器会尝试进入半熔断状态(Half-Open),允许少量请求进来尝试,如果调用仍然失败,则回到熔断状态,如果调用成功,则回到电路闭合状态;
2.OpenFegin使用Hystrix
官方文档:Spring Cloud
支付服务 springcloud-netflix-service-pay之前集成了Feign,修改该工程集成Hystrix。我们除了要给Feign开启Hystrix以外还需要为Feign接口编写托底类。
2.1 开启Hystrix
在yml配置feign.hystrix.enabled=true
开启Hystrix
2.2 Fegin接口熔断——fallback方式
服务通过Feign接口调用异常或超时需要触发降级,返回托底数据。这里有两种方式,分别是通过@FeignClient(fallback=..) ,以及@FeignClient(fallbackFactory=..) 来指定托底类,区别在于通过fallback的方式编写的托底是没办法打印出异常日志的 ,而fallbackFactory方式是可以打印出异常日志, 我们先来看第一种写法:
@FeignClient(value = "server-user",fallback = UserFeignClientFallback.class)
public interface UserFeignClient {
//订单服务来调用这个方法
@GetMapping(value = "/user/{id}" )
User getById(@PathVariable("id")Long id);
}
提示: fallback = UserFeignClientFallback.class : 该类是当前接口的实现类 ,也是托底数据所在的处理类
托底实现类:
//让Spring扫描到该托底类
@Component
public class UserFeignClientFallback implements UserFeignClient {
//日志打印器
private Logger log = LoggerFactory.getLogger(UserFeignClientFallback.class);
@Override
public User getById(Long id) {
log.info("用户服务不可用");
//托底数据
return new User(-1l,"无此用户","用户服务不可用");
}
}
提示:注意,这里托底类需要交给Spirng管理,类上需要打 @Component 注解 , 拖地类需要实现 Feign接口,复写接口中的方法作为托底方法返回拖地数据。当Fiegn调用失败就会以拖地方法返回的结果返回给用户
测试:
依次启动 springcloud-netflix-eureka , springcloud-netflix-service-user, springcloud-netflix-service-pay , 访问:http://localhost:10040/pay/1 观察浏览器是否正常拿到结果,然后关闭 springcloud-netflix-service-user工程观察浏览器是否返回托底数据。
这种方式及时触发托底了也看不到异常日志。
2.3 Fegin接口熔断——fallbackFactory方式
使用fallbackFactory属性,使用工厂方式指定托底
@FeignClient(value = "server-user",fallbackFactory = UserFeignClientFallbackFacotry.class) // 调用服务的服务名
public interface UserFeignClient {
@GetMapping("/user/{id}")
User getById(@PathVariable("id") Long id);
}
编写托底类:
工程方式的托底类需要去实现 FallbackFactory接口 ,并指定泛型为“”Feign客户端接口(UserFeignClient )。FallbackFactory的create方法返回了Feign客户端接口的实例,该方法的throwable是参数是Feign调用失败的异常信息,如下:
@Component
public class UserFeignClientFallbackFacotry implements FallbackFactory<UserFeignClient> {
@Override
public UserFeignClient create(Throwable throwable) {
return new UserFeignClient() {
@Override
public User getById(Long id) {
throwable.printStackTrace();//打印错误日志
return new User(-1L,"","系统错误");
}
};
}
}
启动测试:
测试方式同上 ,只是这种方式触发托底是可以在控制台看到异常信息,方便我们调试。
三:服务网关zuul
1.理解zuul
Zuul 是netflix开源的一个API Gateway 服务器, 本质上是一个web servlet(filter)应用。Zuul 在云平台上提供动态路由(请求分发),监控,弹性,安全等边缘服务的框架。Zuul 相当于是设备和 Netflix 流应用的 Web 网站后端所有请求的前门,也要注册入Eureka,用一张图来理解zuul在架构中的的角色:
需要注意的是,zuul本身是一个独立的服务,默认集成了Ribbon,zuul通过Ribbon将客户端的请求分发到下游的微服务,所以zuul需要通过Eureka做服务发行,同时zuul也集成了Hystrix。
根据上图理解 ,我们需要建立独立的工程去搭建Zuul服务,同时需要把Zuul注册到EurekaServer,因为当请求过来时,zuul需要通过EurekaServer获取下游的微服务通信地址,使用Ribbon发起调用。
2.搭建zuul
还是和之前的服务一样,搭建一个zuul模块springcloud-netflix-service-zuul
2.1 导入依赖
因为Zuul需要通过Eureak做服务发现,所以导入了eureka-client基础依赖,和Zuul自身的基础依赖:netflix-zuul
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
</dependencies>
2.2 配置启动类并开启zuul
@SpringBootApplication
@EnableZuulProxy //开启zuul服务功能。
public class ZuulApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulApplication.class);
}
}
2.3 配置zuul的yml文件
#注册到EurekaServer
eureka:
client:
serviceUrl: #注册中心的注册地址
defaultZone: http://localhost:10010/eureka/
instance:
prefer-ip-address: true #使用ip地址进行注册
instance-id: server-zuul #实例ID
spring:
application:
name: server-zuul #这个是服务名
server:
port: 10050
zuul:
ignored-services: "*" #禁用掉使用浏览器通过服务名的方式访问服务
routes:
server-order: "/order/**" #指定server-order这个服务使用 /order路径来访问 - 别名
server-user: "/user/**" #指定server-user这个服务使用 /user路径来访问 - 别名
server-pay: "/pay/**" #指定server-pay这个服务使用 /pay路径来访问 - 别名
2.4 测试
浏览器访问:http://localhost:10050/pay/pay/1
那今天的内容就分享到这里了哦,加油加油