一、雪崩效应

1、灾难性雪崩效应

  1. 服务提供者不可用:硬件故障,程序BUG,缓存击穿,并发请求量过大。
  2. 重试加大流量:用户重试,代码重试逻辑。
  3. 服务调用者:同步请求阻塞造成的资源耗尽。

    2、如何防止灾难性雪崩效应

    降级

    超时降级、资源不足(线程或信号量)降级,降级后可以配合降级接口返回托底数据。实现一个fallback方法,当请求后端服务出现异常的时候,可以使用fallback方法返回的值。
    保证:服务出现问题整个项目还可以继续运行。

    熔断

    当失败率(如网络故障/超时造成的失败率高)达到阈值自动触发降级,熔断器触发的失败会进行快速恢复。
    熔断就是具有特定条件的降级,当出现熔断时在设定的时间内容就不再请求application service了。所以在代码上熔断和降级都是一个注解。
    保证:服务出现问题整个项目还可以继续运行。

    请求缓存

    提供了请求缓存。服务A调用服务B,如果在A中添加请求缓存,第一次请求后走缓存了,就不再访问服务B,即使出现大量的请求,也不会对B产生高负载。
    请求缓存可以使用Spring Cache实现
    保证:减少对Application Service的调用。

    请求合并

    提供请求合并。当服务A调用服务B时,设定在5ms内所有请求合并到一起,对于服务B的负载就会大大的减少了,解决了对于服务B负载激增的问题。
    保证:减少对Application Service的调用。

    隔离

    隔离分为线程池隔离和信号量隔离。通过判断线程池或者信号量是否已满,超出容量的请求直接降级,从而达到限流(限流的本质是限制设备而不是限制请求)的作用。

    二、Hystrix

    springCloud中通过spring cloud Netfilx Hystrix(断路器)实现。Hystrix即Netflix的一款容错框架,保证在高并发下即使出现问题也可以保证程序能够继续运行的一系列的方案。
    作用:容错和限流
    Hystrix依赖: ```xml org.springframework.cloud spring-cloud-starter-netflix-hystrix
  1. <a name="xRdsa"></a>
  2. ## 三、雪崩效应解决方式
  3. <a name="aeGtJ"></a>
  4. ### 1、降级
  5. 1、启动器添加注解(Eureka客户端,启动断路器)<br />@EnableEurekaClient<br />@EnableCircuitBreaker<br />2、ApplicationConfig中配置RestTemplate
  6. ```java
  7. @Configuration
  8. public class ApplicationConfig {
  9. @Bean
  10. /*负载均衡器注解*/
  11. @LoadBalanced
  12. public RestTemplate restTemplate(){
  13. return new RestTemplate();
  14. }
  15. }

3、serviceimpl
创建RestTemplate的Bean对象

@Service
public class DemoServiceImpl implements DemoService {

    @Autowired
    private RestTemplate restTemplate;

    /*-----------降级-----------*/
    @Override
    @HystrixCommand(fallbackMethod = "myFallback")
    public String demo01() {
        String result = restTemplate.getForObject("http://applicationservice/demo01", String.class);
        System.out.println("进入了方法---远程调用demo01");
        return result;
    }
    /*定义降级方法,返回托底数据*/
    public String myFallback(){
        System.out.println("托底方法");
        return "服务器忙,稍后再试";
    }
}

4.controller

@RestController
public class DomoController {
    @Autowired
    private DemoService demoService;
    /*-----------降级-----------*/
    @RequestMapping("/demo01")
    public String demo01(){
        return demoService.demo01();
    }
}

2、熔断

熔断的属性:
熔断的实现是在调用远程服务的方法上增加@HystrixCommand注解。当注解配置满足则开启或关闭熔断器。
@HystrixProperty的name属性取值可以使用HystrixPropertiesManager常量,也可以直接使用字符串进行操作。
注解属性描述:
CIRCUIT_BREAKER_ENABLED
是否开启熔断策略。默认值为true。
CIRCUIT_BREAKER_REQUEST_VOLUME_THRESHOLD
单位时间内(默认10s内),请求超时数超出则触发熔断策略。默认值为20次请求数。通俗说明:单位时间内容要判断多少次请求。
EXECUTION_ISOLATION_THREAD_TIMEOUT_IN_MILLISECONDS
设置单位时间,判断circuitBreaker.requestVolumeThreshold的时间单位,默认10秒。单位毫秒。
CIRCUIT_BREAKER_SLEEP_WINDOW_IN_MILLISECONDS
当熔断策略开启后,延迟多久尝试再次请求远程服务。默认为5秒。单位毫秒。这5秒直接执行fallback方法,不在请求远程application service。
CIRCUIT_BREAKER_ERROR_THRESHOLD_PERCENTAGE
单位时间内,出现错误的请求百分比达到限制,则触发熔断策略。默认为50%。
CIRCUIT_BREAKER_FORCE_OPEN
是否强制开启熔断策略。即所有请求都返回fallback托底数据。默认为false。
CIRCUIT_BREAKER_FORCE_CLOSED
是否强制关闭熔断策略。即所有请求一定调用远程服务。默认为false。

1、启动器添加注解(Eureka客户端,启动断路器)
@EnableEurekaClient
@EnableCircuitBreaker
2、ApplicationConfig中配置RestTemplate
创建RestTemplate的Bean对象

@Configuration
public class ApplicationConfig {

    @Bean
    /*负载均衡器注解*/
    @LoadBalanced
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }

}

3、serviceimpl

@Service
public class DemoServiceImpl implements DemoService {

    @Autowired
    private RestTemplate restTemplate;

    /*-----------熔断-----------*/
    @Override
    @HystrixCommand(fallbackMethod = "myFallback",commandProperties={
            //统计的时间周期  10s 默认也是10s  单位是毫秒
            @HystrixProperty(name = HystrixPropertiesManager.EXECUTION_ISOLATION_THREAD_TIMEOUT_IN_MILLISECONDS,value = "10000"),
            //单位时间发送请求数量 这个数量必须满足 无论是成功请求还是失败的请求
            @HystrixProperty(name = HystrixPropertiesManager.CIRCUIT_BREAKER_REQUEST_VOLUME_THRESHOLD,value = "3"),
            //请求失败错误占比 默认是百分之50  如果超过这个占比 下次周期请求直接执行托底数据
            @HystrixProperty(name = HystrixPropertiesManager.CIRCUIT_BREAKER_ERROR_THRESHOLD_PERCENTAGE,value = "50"),
            //执行降级操作后 多长时间不在进行ApplicationService服务的调用
            @HystrixProperty(name = HystrixPropertiesManager.CIRCUIT_BREAKER_SLEEP_WINDOW_IN_MILLISECONDS,value = "10000")
    })
    public String demo01() {
        String result = restTemplate.getForObject("http://applicationservice/demo01", String.class);
        System.out.println("进入了方法---远程调用demo01");
        return result;
    }
    /*定义降级方法,返回托底数据*/
    public String myFallback(){
        System.out.println("托底方法");
        return "服务器忙,稍后再试";
    }
}

4.controller

@RestController
public class DomoController {
    @Autowired
    private DemoService demoService;
    /*-----------熔断-----------*/
    @RequestMapping("/demo01")
    public String demo01(){
        return demoService.demo01();
    }
}

测试方法:关闭eureka-applicationserver项目,访问DemoFallback控制器,刷新5次后会发现页面加载快了,这时就开启熔断了。此时打开eureka-applicationserver,发现依然返回托底数据。到达10秒后再次访问才能正常访问eureka-applicationserver中内容。

3、请求缓存

Hystrix为了降低访问的频率,支持将一个请求与返回结果做缓存处理。如果再次请求的URL没有变化,那么Hystrix不会请求服务,而是直接从缓存中将结果返回。这样可以大大降低访问服务的压力。
Hystrix自带缓存缺点:

  • 本地缓存,在集群情况下缓存不能同步。
  • 不支持第三方缓存容器,如Reids,memcached不支持。可以用springcache实现请求缓存。

    方式一:使用原生Redis缓存

    导入Hystrix和Redis依赖

       <!--断路器-->
       <dependency>
           <groupId>org.springframework.cloud</groupId>
           <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
       </dependency>
       <!--redis-->
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-data-redis</artifactId>
       </dependency>
    

    配置Redis的IP和端口号(端口号默认6379)

    spring:
    redis:
    host: 192.168.80.128
    

    sreviceipml

    @Service
    public class DemoServiceImpl implements DemoService {
    
    @Autowired
    private RestTemplate restTemplate;
    @Autowired
    private RedisTemplate<String,String> redisTemplate;
    
    /*请求缓存*/
    /*使用原生的Redis缓存进行操作*/
    @Override
    public String findAll() {
       String key="sxt:add";
       Boolean b = redisTemplate.hasKey(key);
       if (b){
           System.out.println("redis缓存");
           return redisTemplate.opsForValue().get(key);
       }
       System.out.println("数据库查询");
       String list = restTemplate.getForObject("http://applicationservice/demo01", String.class);
       redisTemplate.opsForValue().set(key,list);
       return list;
    }
    }
    

    controller

    @RestController
    public class DomoController {
    @Autowired
    private DemoService demoService;
    
    @RequestMapping("/findRedis")
    public String findRedis(){
       return demoService.findAll();
    }
    }
    

    方式二:使用SpringCaching缓存

    1.添加Redis依赖,同上
    2.修改配置文件中Redis相关配置,同上
    3.启动类添加注解

    /*开启eureka客户端*/
    @EnableEurekaClient
    /*开启断路器(用于熔断和降级)*/
    @EnableCircuitBreaker
    /*开启Caching缓存*/
    @EnableCaching
    

    使用cacheable进行缓存查询

    cacheable(cacheNames = “sxt:his”,key = “‘findAll’”)
    cacheNames:Redis保存数据key的前缀
    key:Redis保存数据key的后缀 —->key=sxt:his::findAll 中间的::是Spring Cache帮助我们增加
    底层保存数据时默认采用的是JDK序列化器
    执行流程:调用findAll方法,springcache会根据我们定义的key去Redis中进行查询,如果可以查询到直接返回Redis数据,如果查询不到,再进入到方法进行远程调用,调用结束后把返回结果根据我们自定义的key保存到Redis中。
    定义key的操作进行参数的传递,注意key中String名称必须使用单引号引出,以你为如果我们我们没有引出这个时候就会被当作表达式进行处理SpringEl。示例:@Cacheable(cacheNames = “sxt:his”,key = “‘findOne_’+#id”)

    使用CachePut进行缓存添加

    @CachePut(cacheNames = “sxt:his”,key = “‘addOne_’+#id”)
    添加、修改操作,在进行添加修改之前会在Redis中保存对应的信息把方法返回值保存到Redis中,如果key不存在直接新建,如果key存在直接修改(覆盖)

    cacheable和cacheput区别

    cacheput不管在Redis中是否查询到都会执行对应的方法—-用于增删改
    cacheable在Redis中查询到指定的key就会直接返回数据—-用于查询

4.serviceimp
查询方法添加cacheable注解
增改方法添加cacheput注解
删除方法添加cacheevict注解

@Service
public class DemoServiceImpl implements DemoService {

    @Autowired
    private RestTemplate restTemplate;

    /*使用SpringCache进行操作*/

    @Override
    @Cacheable(cacheNames = "sxt:his",key = "'findAll'")
    public String findAll() {
        System.out.println("进入了springcache方法,调用了远程");
        String forObject = restTemplate.getForObject("http://applicationservice/demo01", String.class);
        return forObject;
    }

    @Override
    @Cacheable(cacheNames = "sxt:his",key = "'findOne_'+#id")
    public String findOne(int id){
        System.out.println("进入了springcache条件查询的方法,调用了远程");
        String forObject = restTemplate.getForObject("http://applicationservice/demo01", String.class);
        return forObject;
    }

    @Override
    @CachePut(cacheNames = "sxt:his",key = "'addOne_'+#id")
    public String addOne(int id){
        System.out.println("进入到springcache添加的方法,调用远程");
        String forObject = restTemplate.getForObject("http://applicationservice/demo01", String.class);
        return "添加成功"+id;
    }

    @Override
    @CacheEvict(cacheNames = "sxt:his",key = "'remove_'+#id")
    public String remvoeOne(int id) {
        System.out.println("进入到springcache删除的方法,调用了远程");
        String forObject = restTemplate.getForObject("http://applicationservice/demo01", String.class);
        return "删除成功"+id;
    }

}

3.controller

@RestController
public class DomoController {
    @Autowired
    private DemoService demoService;

    @RequestMapping("/findAll")
    public String findAll(){
        return demoService.findAll();
    }

    @RequestMapping("/findOne")
    public String findOne(int id){
        return demoService.findOne(id);
    }

    @RequestMapping("/addOne")
    public String addOne(int id){
        return demoService.addOne(id);
    }

    @RequestMapping("/removeOne")
    public String removeOne(int id){
        return demoService.remvoeOne(id);
    }
}

4、请求合并

application-serve

    @RequestMapping("/selectMore")
    public List<String> selectMore(@RequestBody List<Integer> id){
        List<String> list = new ArrayList<>();
        for (Integer s : id) {
            list.add("sxt:"+s);
        }
        return list;
    }

application-client
serviceimpl

@Service
public class DemoServiceImpl implements DemoService {

    @Autowired
    private RestTemplate restTemplate;
    /*
    * batchMethod请求合并执行方法
    * scope设置请求合并的作用域默认是Request,如果使用默认值会报错
    * timerDelayInMilliseconds:指定多长时间进行请求合并,默认为10毫秒
    * maxRequestsInBatch:最大请求合并数量
    * */
    @Override
    @HystrixCollapser(batchMethod = "BathchMethod",
    scope = com.netflix.hystrix.HystrixCollapser.Scope.GLOBAL,
    collapserProperties = {
            @HystrixProperty(name = "timerDelayInMilliseconds",value = "30"),
            @HystrixProperty(name = "maxRequestsInBatch",value = "100"),
    })
    public Future<String> findOne2(Integer id) {
        System.out.println("配置请求合并后该方法中内容不会执行");
        return null;
    }

    //请求合并后执行方法
    //注意:请求合并是把我们请求过来的多个service进行了合并执行该方法
    @HystrixCommand
    public List<String> BathchMethod(List<Integer> id) {
        System.out.println("参数为:"+id);
        List<String> list = restTemplate.postForObject("http://APPLICATIONSERVICE/selectMore", id, List.class);
        System.out.println("返回结果为:"+list);
        return list;
    }
}

controller

@RestController
public class DomoController {
    @Autowired
    private DemoService demoService;
    @RequestMapping("/match")
    public String match() throws ExecutionException, InterruptedException {
        Future<String> one = demoService.findOne2(1);
        Future<String> two = demoService.findOne2(2);
        Thread.sleep(100);
        Future<String> three = demoService.findOne2(3);
        System.out.println(one.get());
        System.out.println(two.get());
        return "ok";
    }
}

输出结果
image.png

5、隔离

线程池隔离

线程池隔离的优缺点:
优点:

  • 任何一个服务都会被隔离在自己的线程池内,即使自己的线程池资源填满也不会影响其他服务。
  • 当依赖的服务重新恢复时,可通过清理线程池,瞬间恢复服务的调用。但是如果是tomcat线程池被填满,再恢复就会很麻烦。
  • 每个都是独立线程池,一定程度上解决了高并发的问题。
  • 由于线程池中线程个数是有限制的,所以也就解决了限流的问题。

缺点:

  • 增加了CPU开销。因为不仅仅有tomcat的线程池,还需要有Hystrix线程池。
  • 每个操作都是独立的线程,就有排队、调度和上下文切换等问题。

serviceimpl

@Service
public class DemoServiceImpl implements DemoService {

    @Autowired
    private RestTemplate restTemplate;
    @Autowired
    private RedisTemplate<String,String> redisTemplate;
    /*线程池隔离*/
    /*
    * groupKey:组名称 定义时候没有要求
    * commandKey:command的键 这个定义也是可以任意定义 一般叫方法名称
    * threadPoolKey线程池的名称 如hystrix-demo02-2
    * */
    @Override
    @HystrixCommand(groupKey = "sxt",commandKey = "demo02",threadPoolKey = "demo02",threadPoolProperties = {
            @HystrixProperty(name="coreSize",value="2"),//定义线程池核心数量
            @HystrixProperty(name="maxQueueSize",value="5"),//定义等待队列大小
            @HystrixProperty(name="keepAliveTimeMinutes",value="2"),//超过了等待队列最大值 超过两小时就会拒绝
            @HystrixProperty(name="queueSizeRejectionThreshold",value="2"),//线程存活时间2分钟 单位:分钟
    })
    public String demo02() {
        System.out.println("demo02:"+Thread.currentThread().getName());
        return "demo02";
    }
}

controller

@RestController
public class DomoController {
    @Autowired
    private DemoService demoService;
        @RequestMapping("/demo02")
    public String demo02(){
        System.out.println("demo02的controller:"+Thread.currentThread().getName());
        return demoService.demo02();
    }
}

信号量隔离

serviceimpl

@Service
public class DemoServiceImpl implements DemoService {

    @Autowired
    private RestTemplate restTemplate;
    /*信号量隔离*/
    @Override
    @HystrixCommand(commandProperties={
            //当前隔离方式 默认是线程池隔离  我们需要指定SEMAPHORE信号量
            @HystrixProperty(name =HystrixPropertiesManager.EXECUTION_ISOLATION_STRATEGY,value ="SEMAPHORE" ),
            //指定目前允许并发请求个数
            @HystrixProperty(name =HystrixPropertiesManager.EXECUTION_ISOLATION_SEMAPHORE_MAX_CONCURRENT_REQUESTS,value ="2" ),
            //如果超过了并发请求可以执行托底数据
    },fallbackMethod = "myFallback")
    public String semaphore() {
        System.out.println("访问service中的信号量隔离");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "ok";
    }

        /*定义降级方法,返回托底数据*/
    public String myFallback(){
        System.out.println("托底方法");
        return "服务器忙,稍后再试";
    }
}

controller

@RestController
public class DomoController {
    @Autowired
    private DemoService demoService;
    @RequestMapping("/semaphore")
    public String semaphore(){
        return demoService.semaphore();
    }
}