服务保护利器
微服务高可用技术
大型复杂的分布式系统中,高可用相关的技术架构非常重要。
高可用架构非常重要的一个环节,就是如何将分布式系统中的各个服务打造成高可用的服务,从而足以应对分布式系统环境中的各种各样的问题,,避免整个分布式系统被某个服务的故障给拖垮。
比如:
服务间的调用超时
服务间的调用失败
要解决这些棘手的分布式系统可用性问题,就涉及到了高可用分布式系统中的很多重要的技术,包括:
资源隔离
限流与过载保护
熔断
优雅降级
容错
超时控制
监控运维
服务降级、熔断、限流概念
服务学崩效应
服务雪崩效应产生与服务堆积在同一个线程池中,因为所有的请求都是同一个线程池进行处理,这时候如果在高并发情况下,所有的请求全部访问同一个接口,
这时候可能会导致其他服务没有线程进行接受请求,这就是服务雪崩效应效应。
服务降级
在高并发情况下,防止用户一直等待,使用服务降级方式(直接返回一个友好的提示给客户端,调用fallBack方法)
服务熔断
熔断机制目的为了保护服务,在高并发的情况下,如果请求达到一定极限(可以自己设置阔值)如果流量超出了设置阈值,让后直接拒绝访问,保护当前服务。使用服务降级方式返回一个友好提示,服务熔断和服务降级一起使用
服务隔离
因为默认情况下,只有一个线程池会维护所有的服务接口,如果大量的请求访问同一个接口,达到tomcat 线程池默认极限,可能会导致其他服务无法访问。
解决服务雪崩效应:使用服务隔离机制(线程池方式和信号量),使用线程池方式实現隔离的原理: 相当于每个接口(服务)都有自己独立的线程池,因为每个线程池互不影响,这样的话就可以解决服务雪崩效应。
线程池隔离:
每个服务接口,都有自己独立的线程池,每个线程池互不影响。
信号量隔离:
使用一个原子计数器(或信号量)来记录当前有多少个线程在运行,当请求进来时先判断计数器的数值,若超过设置的最大线程个数则拒绝该请求,若不超过则通行,这时候计数器+1,请求返 回成功后计数器-1。
服务限流
服务限流就是对接口访问进行限制,常用服务限流算法令牌桶、漏桶。计数器也可以进行粗暴限流实现。
Hystrix简单介绍
Hystrix是国外知名的视频网站Netflix所开源的非常流行的高可用架构框架。Hystrix能够完美的解决分布式系统架构中打造高可用服务面临的一系列技术难题。
Hystrix “豪猪”,具有自我保护的能力。hystrix 通过如下机制来解决雪崩效应问题。
在微服务架构中,我们把每个业务都拆成了单个服务模块,然后当有业务需求时,服务间可互相调用,但是,由于网络原因或者其他一些因素,有可能出现服务不可用的情况,当某个服务出现问题时,其他服务如果继续调用这个服务,就有可能出现线程阻塞,但如果同时有大量的请求,就会造成线程资源被用完,这样就可能会导致服务瘫痪,由于服务间会相互调用,很容易造成蝴蝶效应导致整个系统宕掉。因此,就有人提出来断路器来解决这一问题。
资源隔离:包括线程池隔离和信号量隔离,限制调用分布式服务的资源使用,某一个调用的服务出现问题不会影响其他服务调用。
降级机制:超时降级、资源不足时(线程或信号量)降级,降级后可以配合降级接口返回托底数据。
融断:当失败率达到阀值自动触发降级(如因网络故障/超时造成的失败率高),熔断器触发的快速失败会进行快速恢复。
缓存:提供了请求缓存、请求合并实现。
断路器 服务降级 服务熔断 服务隔离机制 服务雪崩效应
连环雪崩:
订单服务调用会员服务 达到极限
另外客户端也没法访问会员服务了
首先引入Hystrix相关依赖
<!-- hystrix断路器 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency>
然后去开启Hystrix断路器(调用者配置)
Hystrix 有两种方式配置保护服务 1、注解 2、接口
@HystrixCommand 默认开启线程池隔离 开启服务降级 开启服务熔断
@EnableHystrix 需要加到启动类
熔断机制: tomcat 默认极限 可以尝试下 在这里我就不做实验了
注意有时间设置哦:
超时也会走降级!
禁止超时时间设置:
#### hystrix禁止服务超时时间
hystrix:
command:
default:
execution:
timeout:
enabled: false
如果调用其他接口超时时候(默认1s),如果1s没有及时响应, 默认情况下业务逻辑是可以执行的,但是直接执行服务降级方法。
禁止后:
不禁止:
如果调用其他接口超时的时候(默认1s),如果在1s内没有及时响应的话,默认情况下业务逻辑是可以执行的,但是直接执行服务降级方法。浏览器拿到的是降级后的结果,但是后台的业务逻辑也是执行了的。
Eureka server : 略
Member:
service
pom:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>com.toov5</groupId> <artifactId>toov5-api-service</artifactId> <version>0.0.1-SNAPSHOT</version> </parent> <artifactId>toov5-api-member-service</artifactId> <dependencies> <dependency> <groupId>com.toov5</groupId> <artifactId>toov5.common</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency> </dependencies> </project>
实体类
package com.toov5.api.entity; import lombok.Data; @Data public class UserEntity { private String name; private Integer age; }
接口
package com.toov5.api.service; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import com.toov5.api.entity.UserEntity; import com.toov5.base.ResponseBase; @RestController public interface IMemberService { @RequestMapping("/getMember") //接口加@RequestMapping 被其他项目调用时候 feign客户端可以继承 public UserEntity getMember(@RequestParam("name") String name); @RequestMapping("/getUserInfo") public ResponseBase getUserInfo(); }
实现:
package com.toov5.api.service.impl; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import com.toov5.api.entity.UserEntity; import com.toov5.api.service.IMemberService; import com.toov5.base.BaseApiService; import com.toov5.base.ResponseBase; //注意加在实现类上面!!! 接口不能加 接口不能被实例化 @RestController public class MemberServiceImpl extends BaseApiService implements IMemberService { @RequestMapping("/getMember") public UserEntity getMember(@RequestParam("name") String name) { UserEntity userEntity = new UserEntity(); userEntity.setName(name); userEntity.setAge(10); return userEntity; } @RequestMapping("/getUserInfo") public ResponseBase getUserInfo() { try { Thread.sleep(1500); } catch (Exception e) { } return setResultSuccess("订单服务接口调用会员服务接口成功...."); } }
启动类
package com.toov5.api.service.impl; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; import org.springframework.cloud.openfeign.EnableFeignClients; @SpringBootApplication (scanBasePackages={"com.toov5.*"}) @EnableEurekaClient @EnableFeignClients public class AppMember { public static void main(String[] args) { SpringApplication.run(AppMember.class, args); } }
yml
###服务启动端口号 server: port: 8000 ###服务名称(服务注册到eureka名称) spring: application: name: app-toov5-member ###服务注册到eureka地址 eureka: client: service-url: defaultZone: http://localhost:8100/eureka ###因为该应用为注册中心,不会注册自己 register-with-eureka: true ###是否需要从eureka上获取注册信息 fetch-registry: true
pom:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>com.toov5</groupId> <artifactId>parent</artifactId> <version>0.0.1-SNAPSHOT</version> </parent> <artifactId>toov5-api-member-service-impl</artifactId> <dependencies> <dependency> <groupId>com.toov5</groupId> <artifactId>toov5-api-member-service</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency> <dependency> <groupId>com.toov5</groupId> <artifactId>toov5-api-order-service</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency> </dependencies> </project>
pom
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>com.toov5</groupId> <artifactId>toov5-api-service</artifactId> <version>0.0.1-SNAPSHOT</version> </parent> <artifactId>toov5-api-order-service</artifactId> <dependencies> <dependency> <groupId>com.toov5</groupId> <artifactId>toov5.common</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency> </dependencies> </project>
package com.toov5.api.service; import org.springframework.web.bind.annotation.RequestMapping; import com.toov5.base.ResponseBase; public interface IOrderService { //订单服务带哦用会员服务接口信息fegin @RequestMapping("/orderToMember") public String orderToMember(String name); //订单服务接口调用会员服务接口 @RequestMapping("/orderToMemberUserInfo") public ResponseBase orderToMemberUserInfo(); //订单服务接口 @RequestMapping("/orderInfo") public ResponseBase orderInfo(); }
feign
package com.toov5.api.feign; import org.springframework.cloud.openfeign.FeignClient; import com.toov5.api.service.IMemberService; //避免了冗余代码 直接过来就ok了 @FeignClient("app-toov5-member") public interface MemberServiceFeign extends IMemberService { //实体类是存放接口项目还是存放在实现项目 实体类存放在接口项目里面 //实体类和定义接口信息存放在接口项目 //代码实现放在接口实现类里面 }
package com.toov5.api.service.impl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; import com.toov5.api.entity.UserEntity; import com.toov5.api.feign.MemberServiceFeign; import com.toov5.api.service.IOrderService; import com.toov5.base.BaseApiService; import com.toov5.base.ResponseBase; @RestController public class OrderServiceImpl extends BaseApiService implements IOrderService { @Autowired private MemberServiceFeign memberServiceFeign; @RequestMapping("/orderToMmeber") public String orderToMember(String name) { UserEntity user = memberServiceFeign.getMember(name); return user==null ? "没有找到用户先关信息" : user.toString(); } //需要解决服务雪崩效应 @RequestMapping("/orderToMemberUserInfo") public ResponseBase orderToMemberUserInfo() { return memberServiceFeign.getUserInfo(); } //解决服务雪崩效应 @HystrixCommand(fallbackMethod="orderToMemberUserInfoHystrixFallback") @RequestMapping("/orderToMemberUserInfoHystrix") public ResponseBase orderToMemberUserInfoHystrix() { System.out.println("orderToMemberUserInfoHystrix"+"线程池名称"+Thread.currentThread().getName()); return memberServiceFeign.getUserInfo(); } public ResponseBase orderToMemberUserInfoHystrixFallback() { return setResultSuccess("系统繁忙哦!请稍后再试~"); } //订单服务接口 @RequestMapping("/orderInfo") public ResponseBase orderInfo() { System.out.println("orderInfo"+"线程池名字"+Thread.currentThread().getName()); return setResultSuccess(); } }
启动
package com.toov5.api; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; import org.springframework.cloud.netflix.hystrix.EnableHystrix; import org.springframework.cloud.openfeign.EnableFeignClients; @SpringBootApplication(scanBasePackages={"com.toov5.*"}) @EnableEurekaClient @EnableFeignClients @EnableHystrix public class AppOrder { public static void main(String[] args) { SpringApplication.run(AppOrder.class, args); } }
访问:http://localhost:8001/orderToMemberUserInfoHystrix
看到线程池名称
然后访问: http://localhost:8001/orderInfo
两个线程池! 服务隔离已经起作用
实际上后台的业务逻辑都已经实现了,只是前端返回这个结果而已