Hystrix:断路器: RestTemplate & Openfeign=>5种防止服务雪崩方法

文章目录

  • 服务雪崩
  • Hystrix
  • RestTemplate
    • 降级
    • 熔断
    • 缓存
    • 请求合并
    • 隔离
      • 线程池隔离
      • 信号量隔离
      • 线程池隔离和信号量隔离的选择
  • Openfeign的容灾处理
    • 1.RestTemplate用那一套
    • 2.用自带的Hystrix

服务雪崩

当其中某一个服务突然遇到大量请求时。整个链条上所有服务负载骤增。
原因
服务提供者(Application Service)不可用
重试加大流量
服务调用者(Application Client)不可用

解决方法:
1.降级: 实现一个fallback方法,当请求后端服务出现异常的时候,使用fallback方法返回值. 即使某服务出现了问题,整个项目还可以继续运行。
2.熔断: 当失败率达到阀值自动触发降级,熔断器触发的快速失败会进行快速恢复,具有特定条件的降级
3.请求缓存: 服务A调用服务B,在A中添加请求缓存,第一次请求后走缓存了,就不在访问服务B了,即使出现大量请求时,也不会对B产生高负载。
4. 请求合并: 当服务A调用服务B时,设定在5毫秒内所有请求合并到一起,对于服务B的负载就会大大减少
5. 隔离: 隔离分为线程池隔离和信号量隔离。通过判断线程池或信号量是否已满,超出容量的请求直接降级,从而达到限流的作用。

Hystrix

Hystrix就是保证在高并发下即使出现问题也可以保证程序继续运行的一系列方案,作用包含两点:容错和限流。
在Spring cloud中处理服务雪崩效应,都是在服务调用方(Application Client)实现,需要依赖hystrix组件。

RestTemplate

降级

降级就是,执行访问远程的方法,但application server 异常,无法调用远程方法后,走降级方法
大概就是: 远程-> 异常 - > 降级方法…循环
创建Eureka Server和application client ,application server
在application client(调用方)
1.导入依赖


    <dependency>
        <groupId>org.springframework.cloudgroupId>
        <artifactId>spring-cloud-starter-netflix-hystrixartifactId>
    dependency>

2.在启动类上加上@EnableHystrix注解

@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
@EnableHystrix
public class SpringcloudEClient2Application {
    public static void main(String[] args) {
        SpringApplication.run(SpringcloudEClient2Application.class, args);
    }
}

3.启动application client,不启动application server,但是用application client远程调用application server 的方法
注意: 在那个方法远程调用,就在那个方法上加@HystrixCommand(fallbackMethod = “降级方法名”)注解
一般在Controller层调用Service,在ServiceImpl中远程调用,所以一般加载ServiceImpl类中的方法上.在该类中写降级方法

@RestController
public class DemoController {
    @Autowired
    private OpenfeignService openfeignService;
    @Autowired
    private RestTemplate restTemplate;
    @Autowired
    private ApplicationClientService applicationClientService;

    @RequestMapping("/fs")
    @HystrixCommand(fallbackMethod = "downgrade")
    public String show(){
        String show = openfeignService.show();//使用openfeign远程调用
        System.out.println(show);
        return show;
    }

    @RequestMapping("/index")
    @HystrixCommand(fallbackMethod = "downgrade")//降级方法名
    public String show1(){
    	//使用restTemplate远程调用,注意穿件配置类,加上@Bean和@LoadBlance注解
        String forObject = restTemplate.getForObject("http://E-SERVER/save", String.class);
        return forObject;
    }
	//降级方法: 注意降级方法的参数要和远程调用异常方法的参数完全一致
    public String downgrade(){
        return "hello error";
    }

4.具体看配置文件,到浏览器访问地址
比如: 访问127.0.0.1:9090/fs,127.0.0.1:9090/index 浏览器均打印hello error

熔断

降级的升级版,有条件判断的降级,在熔断状态下直接走降级方法,不走请求方法
(不满足熔断条件)远程-> 异常-> 降级方法-> …->(满足熔断条件) 降级方法-> 降级方法-> 降级方法…->(不满足熔断)远程-> 异常->降级方法…

在降级的环境配置下加上

	意思是在5秒内统计4个请求,错误百分比达50%,开启熔断持续3,熔断状态下直接走降级方法,不执行请求方法
    @RequestMapping("/fs")
    @HystrixCommand(fallbackMethod = "downgrade",commandProperties = {
            @HystrixProperty(name= HystrixPropertiesManager.EXECUTION_ISOLATION_THREAD_TIMEOUT_IN_MILLISECONDS, 
            value = "5000"),//统计周期,默认10秒
            @HystrixProperty(name = HystrixPropertiesManager.CIRCUIT_BREAKER_REQUEST_VOLUME_THRESHOLD, 
            value = "4"), // 指定周期内统计多少个请求 默认20
            @HystrixProperty(name = HystrixPropertiesManager.CIRCUIT_BREAKER_ERROR_THRESHOLD_PERCENTAGE, 
            value = "50"), // 统计周期内,错误百分比达到多少,开启熔断, 默认50
            @HystrixProperty(name = HystrixPropertiesManager.CIRCUIT_BREAKER_SLEEP_WINDOW_IN_MILLISECONDS, 
            value = "3000"), // 开启熔断后,多少毫秒不访问远程服务,默认5000毫秒

	下面三个一般不配
    @HystrixProperty(name = HystrixPropertiesManager
                     .CIRCUIT_BREAKER_ENABLED,
                     value = "true"), // 是否开启熔断,默认true
    @HystrixProperty(name = HystrixPropertiesManager
                     .CIRCUIT_BREAKER_FORCE_OPEN,
                     value = "false"), // 是否强制开启熔断器, 默认false,一直熔断
    @HystrixProperty(name = HystrixPropertiesManager
                     .CIRCUIT_BREAKER_FORCE_CLOSED,
                     value = "false") // 是否强制关闭熔断器, 默认false,不熔断
    })
    public String show(){
        System.out.println("访问远程");
        String show = openfeignService.show();
        System.out.println(show);
        return show;
    }

    public String downgrade(){
        System.out.println("error");
        return "hello error";
    }

缓存

注意: 环境是建立在降级环境的基础上,但是启动了application server

使用Spring Cache技术实现,访问Redis,做缓存处理。
一.
1.导入spring-context.jar,直接或间接。
2. 要访问的缓存服务器客户端依赖。如:访问Redis需要Spring Data Redis依赖

二.使用Spring Cache,查询逻辑的流程是:
访问缓存,有,直接返回,没有,执行方法,方法返回结果自动保存到缓存服务器中。

使用:
1.在启动类上加上@EnableCaching注解
2.在需要缓存返回结果的的方法上加上@Cacheable(key = “‘client’”,cacheNames = “com:bjsxt”)注解
cacheNames - 缓存中key的前缀,key - 缓存中key的后缀
缓存到redis中的key是key::cacheNames ,value是方法返回值

1.redis的yml配置
spring:
  redis:
    host: 127.0.0.1
    port: 6379

2.启动类配置
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
@EnableHystrix
@EnableCaching
public class SpringcloudEClient2Application {
    public static void main(String[] args) {
        SpringApplication.run(SpringcloudEClient2Application.class, args);
    }
}

3.要缓存方法配置
    @RequestMapping("/index")
    @HystrixCommand(fallbackMethod = "downgrade")
    @Cacheable(cacheNames = "fs",key = "'list2'")
    public String show1(){
        System.out.println("远程调用");
        String forObject = restTemplate.getForObject("http://E-SERVER/save", String.class);
        return forObject;
    }
    
4.访问http://localhost:9090/index第一次访问控制台打印"远程调用",以后就不打印直接返回了

5.可选:更改默认的序列化器,简单说就是让保存在redis中的内容能看懂,不乱码,不配置也行,虽然redis中乱码,但是取出来和以前是一样的
  @Bean
    public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
        //缓存配置对象
        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();

        redisCacheConfiguration = redisCacheConfiguration.entryTtl(Duration.ofMinutes(30L)) //设置缓存的默认超时时间:30分钟
                .disableCachingNullValues()             //如果是空值,不缓存
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))         //设置key序列化器
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));  //设置value序列化器

        return RedisCacheManager
                .builder(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory))
                .cacheDefaults(redisCacheConfiguration).build();
    }

3.带有参数的方法缓存

@Service
@CacheConfig(cacheNames = "com.bjsxt")//可以在类上加统一前缀
public class DemoServiceImpl implements DemoService {
@Cacheable(key = "'selectById' + #id")//也可以在方法上加前缀(cacheNames),优先级比类上的高
    public String selectById(Long id) {
        System.out.println("执行了selectById:"+id);
        return "selectById"+id;
    }
}

4.修改数据:@CachePut(cacheNames = “”,key = " ’ ’ ")不管Redis中是否有对应的数据都会执行方法,修改或新增更适合做缓存内容修改

// Cache 每次都走方法体,无论redis中是否存在指定key.
// 恒执行redis的新增操作
@CachePut(key = "'update'")
@Override
public String update() {
    System.out.println("执行修改");
    return "update2";
}

5.删除拼接前后缀的键:.@CacheEvict(cacheNames = “”,key = " ’ ’ ")

@CacheEvict(cacheNames = "",key = "''")
public int delete() {
    System.out.println("执行delete");
    return 1;
}

请求合并

将多个请求合并为一个请求,解决高并发下请求太多的延迟问题.
合并什么样的请求:
如:
1.去后台查询id为1,2,3的对应的信息,那么就要执行3次service.selectById(id);
调用的sql是select 字段… from table where id = #{id},执行完后一次返回

2.如果每一次查询响应时间很慢,那么我们可以将这三次查询合并为一次查询,直接调用service.selectByIdList(ids)
执行sql是 select 字段… from table where id in (ids…);远程执行完后返回client,client拆分返回客户端
这样三次查询合并为一次查询,效率就会快很多

在测降级的环境下,启动application server

1.往service中增加接口
Future<String> getUserById(Integer id);//通过Future拿到返回值

2.serviceImpl中
    @Override
    该注解的含义是在500ms内最多合并2个请求,还有也合并不了
    @HystrixCollapser(batchMethod = "getUsersByIds",//批处理方法名
            scope = com.netflix.hystrix.HystrixCollapser.Scope.GLOBAL,
            collapserProperties = {
                    @HystrixProperty(name =
                            HystrixPropertiesManager.MAX_REQUESTS_IN_BATCH,
                            value = "2"), // 最多合并多少个请求
                    @HystrixProperty(name =
                            HystrixPropertiesManager.TIMER_DELAY_IN_MILLISECONDS,
                            value = "50") // 最多等待多少毫秒
            }
    )
    这个方法不会执行,但是用户调用的是这个方法,实际执行的批处理方法
    public Future<String> getUserById(Integer id) {
	    System.out.println("这个方法不会执行");
        return null;
    }

批处理方法:
定义要求:
     *  1. 访问修饰符是public
     *  2. 返回值的类型是远程服务的返回类型。
     *  3. 方法命名,随意。不重复即可。
     *  4. 参数表,是List集合类型,泛型是要合并的方法参数类型。名称和要合并的方法参数名一致
     *  5. 抛出异常,不能抛出范围超过要合并的方法的异常类型。
@HystrixCommand
    public List<String> getUsersByIds(List<Integer> id){
		List<String> result = restTemplate.postForObect("http://APPSERVER/findMore",id,List.class);
		System.out.println("result = " + resul);
        return result ;
    }

3.controller中
@RequestMapping("/getUserById")
public void getUserById(){
	Future<String> f1 = service.getUserById(1)
	Future<String> f2 = service.getUserById(2)
	Thread.sleep(2000)
	
	Future<String> f3 = service.getUserById(3)

	System.out.println(f1);
	System.out.println(f2);
	System.out.println(f3);
}

4.访问http://localhost:8888/getUserById
控制台打印
[1,2]
[3]
1
2
3

隔离

线程池隔离

简单来说: 以前一个请求发到控制层到业务层是一个线程,现在变成了在远程调用的方法里,控制层一个线程,业务层一个线程.变成了两个线程.
在简单说就是: 控制层是一个线程,控制层调用业务层里面加了@HystrixCommand注解的方法是另一个单独的线程执行.变成了两个线程

1.controller
    @RequestMapping("/h1")
    public String show4() {
        System.out.println(Thread.currentThread().getName());
        String s = applicationClientService.client2(1);
        return s;
    }
2.serviceImpl
    @HystrixCommand
    public String client2(Integer id) {
        System.out.println(Thread.currentThread().getName());
        String forObject = restTemplate.getForObject("http://E-SERVER/save", String.class);
        return forObject;
    }
3.控制台打印
http-nio-9090-exec-1
hystrix-ApplicationClientServiceImpl-1
是两个线程


4.可以加一些属性
@HystrixCommand(
		groupKey = "MyFirstThread",//l唯一的名称代表是线程池的组
		commandKey = "thread",//唯一的名称,一殷代表的就是方法名称
		threadPoolKey = "pool-name",//隔离的线程池命名中缀默认hystrix-类名-nhystrix-sxt-n
		threadPoolProperties = {
		@HystrixProperty(name = HystrixPropertiesManager.CORE_SIZE,value = "3"), // 线程池容量
		@HystrixProperty(name = HystrixPropertiesManager.KEEP_ALIVE_TIME_MINUTES,value = "5"),//线程空闲时,最大存活时间(分钟)
		// 线程池占满时,最多由多少个请求阻塞等待,等待队列长度,默认-1(无限)
		@HystrixProperty(name = HystrixPropertiesManager.MAX_QUEUE_SIZE,value = "5"), 
		//设置阻塞队列有多到个请求时,拒绝请求,MAX_QUEUE_SIZE = -1 时 (无限) 该属性不可用,且该属性小于MAX_QUEUE_SIZE 
		@HystrixProperty(name = HystrixPropertiesManager.QUEUE_SIZE_REJECTION_THRESHOLD,value = "5") // 当阻塞队列MAX_QUEUE_SIZE占满时,可以由多少个
							 // 请求同时阻塞等待后续处理。
		}
)
public String thread() {
	......
}

信号量隔离

限制访问同一个服务的最大线程数
超过最大线程数直接返回托底方法

1.serviceImpl
@Override
@HystrixCommand(fallbackMethod = "downgrade",
		commandProperties = {
		// 默认线程池隔离,改成信号量隔离 THREAD(默认) | SEMAPHORE
		@HystrixProperty(name = HystrixPropertiesManager.EXECUTION_ISOLATION_STRATEGY,value = "SEMAPHORE"), 
		// 最大信号量
		@HystrixProperty(name = HystrixPropertiesManager.EXECUTION_ISOLATION_SEMAPHORE_MAX_CONCURRENT_REQUESTS,value = "2")
		}
)
public String semaphore() {
	System.out.println("远程地址是:" + url + " , 线程名称是:" + Thread.currentThread().getName());
	//远程调用方法
	String forObject = restTemplate.getForObject("http://E-SERVER/save", String.class);
	Thread.sleep(800);	
	return result;
}
托底方法
public String downgrade() {
	return "hello error";
}

线程池隔离和信号量隔离的选择

线程池隔离: 请求并发量大,响应耗时较长
信号量隔离: 请求并发量大,响应耗时较短

Openfeign的容灾处理

1.RestTemplate用那一套

注意一下jar包导入,就行排除掉spring-cloud-starter-openfeign中自带的那个hystrix配置,导入spring-cloud-starter-netflix-hystrix
导入之后RestTemplate怎么使用hystrix,Openfeign就怎么使用

        <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-starter-openfeignartifactId>
            <exclusions>
                <exclusion>
                    <groupId>com.netflix.feigngroupId>
                    <artifactId>hystrix-coreartifactId>
                exclusion>
            exclusions>
        dependency>
        <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-starter-netflix-hystrixartifactId>
        dependency>

2.用自带的Hystrix

一.降级

1.启动类加@EnableFeignClients即可

@SpringBootApplication
@EnableFeignClients
public class OpfClientApplication {
    public static void main(String[] args) {
        SpringApplication.run(OpfClientApplication.class, args);
    }
}

2.yml配置文件:

server:
  port: 6363

eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:8888/eureka/
spring:
  application:
    name: my-openfeign

feign:
  hystrix:
    enabled: true#开启降级配置,默认false

3.远程调用接口: FeignService
一个远程方法对应一个托底类的托底方法

@FeignClient(name = "e-server",fallback = FeignServiceImpl.class(托底类))
public interface FeignService {
    @RequestMapping("/save")
    public String show();
}

4.托底类: FeignServiceImpl 继承 FeignService
一个远程方法对应一个托底类的托底方法

一定要加@Component注解

@Component//注意
public class FeignServiceImpl implements FeignService{
    @Override
    public String show() {
        return "hello error2";
    }
}

5.创建controller

@RestController
public class DemoController {
    @Qualifier("com.example.opf_client.openFeign.FeignService")
    @Autowired
    private FeignService feignService;

    @RequestMapping("/abc")
    public String show(){
        System.out.println("hello");
        return feignService.show();
    }
}

6.feignService.show()没有异常正常调用,有异常返回对应托底方法返回值

二.熔断
yml全局配置即可

hystrix: # hystrix 容灾配置
  command: # hystrix 命令配置,就是具体的容灾方案
    default: # 默认环境,相当于全局范围,后续的配置,参考HystrixPropertiesManager中的常量配置
      circuitBreaker: # 熔断配置, 常用。 其他配置不常用。
        enabled: true
        requestVolumeThreshold: 2
        sleepWindowInMilliseconds: 2000
        errorThresholdPercentage: 50

你可能感兴趣的:(分布式,hystrix,spring,cloud,java)