飞天班第27节:SpringCloud(中)

2020/04/28

1、Ribbon负载均衡

Spring Cloud Ribbon是一套客户端的负载均衡工具。

LB:负载均衡

将用户的请求均摊的分配到多个服务器上,从而达到HA(高可用)

分类:

  • 集中式LB

    服务消费者和服务提供者之间,单独设置一个设施

    比如Nginx

  • 进程式LB

    将LB集成到消费者,消费者从服务注册中心获得有哪些可用的地址,从中选取一个

    Ribbon就是属于进程式LB

Ribbon配置

1、导入依赖

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>

2、向程序的spring ioc容器注入一个bean: restTemplate

@Configuration
public class ConfigBean
{
    @Bean
    @LoadBalanced // 支持负载均衡的功能
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }    
}

通过restTemplate基于服务名调用微服务

RestController
public class DeptConsumerController {
    @Autowired
    private RestTemplate restTemplate;

    // 这里现在是死的地址
    // private static final String REST_URL_PREFIX = "http://localhost:8001";
    // 一旦集成了Ribbon,这里可以直接编写服务名即可 springcloud-provider-dept
    // 可以基于微服务的名称来访问微服务
    // springcloud-provider-dept  8001   8002   8003 (集群)
    private static final String REST_URL_PREFIX = "http://SPRINGCLOUD-PROVIDER-DEPT";

    @RequestMapping("/consumer/dept/discovery")
    public Object discovery(){
        return restTemplate.getForObject(REST_URL_PREFIX+"/dept/discovery",Object.class);
    }

    @RequestMapping("/consumer/dept/add")
    public boolean add(Dept dept){
        return restTemplate.postForObject(REST_URL_PREFIX+"/dept/add",dept,Boolean.class);
    }

    @RequestMapping("/consumer/dept/get/{id}")
    public Dept get(@PathVariable("id") Long id) {
        System.out.println("===>"+id);
        return restTemplate.getForObject(REST_URL_PREFIX+"/dept/get/"+id,Dept.class);
    }

    @RequestMapping("/consumer/dept/list")
    public List<Dept> list() {
        return restTemplate.getForObject(REST_URL_PREFIX+"/dept/list",List.class);
    }

IRule 负载均衡算法

ribbon的负载均衡算法都是实现了IRule接口

Ribbon中的7中负载均衡算法:

(1)RoundRobinRule:轮询;

(2)RandomRule:随机;

(3)AvailabilityFilteringRule:会先过滤掉由于多次访问故障而处于断路器状态的服务,还有并发的连接数量超过阈值的服务,然后对剩余的服务列表按照轮询策略进行访问;

(4)WeightedResponseTimeRule:根据平均响应时间计算所有服务的权重,响应时间越快的服务权重越大被选中的概率越大。刚启动时如果统计信息不足,则使用RoundRobinRule(轮询)策略,等统计信息足够,会切换到WeightedResponseTimeRule;

(5)RetryRule:先按照RoundRobinRule(轮询)策略获取服务,如果获取服务失败则在指定时间内进行重试,获取可用的服务;

(6)BestAvailableRule:会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务;

(7)ZoneAvoidanceRule:复合判断Server所在区域的性能和Server的可用性选择服务器;

在ConfigBean 修改LB策略

@Configuration
public class ConfigBean {

    // 支持负载均衡的功能
    @LoadBalanced // SpringCloud Ribbon 是基于客户端实现的一套负载均衡的工具
    @Bean
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }

    // 修改LB策略
    @Bean
    public IRule myRule(){
        return new RetryRule();
    }
}

自定义负载均衡算法

注意点:一定不要在主启动类同级或子级下写算法,不能被Spring扫描!

1、配置类MySelfRule

@Configuration
public class MySelfRule {
    // 修改LB策略
    @Bean
    public IRule myRule(){
        return new CodingRandomRule();
    }
}

2、自定义负载均衡规则CodingRandomRule

public class CodingRandomRule extends AbstractLoadBalancerRule {

    // 8001    8002     8003 , 每个服务调用5次,轮询升级版
    // 逻辑
    // 1、total = 0,当total数量为5,就切换到下一个服务,重置为0
    // 2、index = 0. 对外提供的服务选择 ,当index>3, 重置为0

    private int total = 0;
    private int currentIndex = 0;

    public Server choose(ILoadBalancer lb, Object key) {
        System.out.println("codingRandom rule choice");

        if (lb == null) {
            return null;
        }
        // 最终选择的服务
        Server server = null;

        while (server == null) {
            if (Thread.interrupted()) {
                return null;
            }
            // 获取所有上线的服务  8001   8002   8003
            List<Server> upList = lb.getReachableServers();
            // 获取所有所有的服务  8001   8002   8003  ......
            List<Server> allList = lb.getAllServers();
            // 获取服务数量
            int serverCount = allList.size();
            if (serverCount == 0) {
                return null;
            }

            // 随机选择一个 (算法的具体位置)
            if (total < 5){ // 如果当前total小于5
                server = upList.get(currentIndex);
                total++;
            } else { // 如果当前total大于5
                total = 1;
                currentIndex++;
                // >=
                if (currentIndex >= upList.size()){
                    currentIndex = 0;
                }
                server = upList.get(currentIndex);
            }
            // ======= END =======

            if (server == null) {
                Thread.yield();
                continue;
            }

            if (server.isAlive()) {
                return (server);
            }

            server = null;
            Thread.yield();
        }

        return server;
    }

    protected int chooseRandomInt(int serverCount) {
        return ThreadLocalRandom.current().nextInt(serverCount);
    }

	@Override
	public Server choose(Object key) {
		return choose(getLoadBalancer(), key);
	}

	@Override
	public void initWithNiwsConfig(IClientConfig clientConfig) {
		// TODO Auto-generated method stub
	}
}

3、主启动类,加上注解@RibbonClient

@SpringBootApplication
@EnableEurekaClient // 客户端
@EnableFeignClients
// 配置Ribbon客户端, 多个  *
@RibbonClient(name = "provider-dept",configuration = MySelfRule.class)
public class ConsumerDeptApplication {
	public static void main(String[] args) {
		SpringApplication.run(ConsumerDeptApplication.class,args);
	}
}

2、Feign调用

Feign 声明式的web service客户端,调用微服务更加简单,类似controller 调用sevice

feign集成类ribbon,也支持负载均衡,调用ribbon的负载均衡算法。

Feign 其实不是做负载均衡的,负载均衡的是ribbon的功能,feign只是集成了ribbon

使用

1、导入依赖

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

2、定义一个feign接口,通过@ FeignClient(“服务名”),来指定调用哪个服务

@FeignClient(value = "provider-dept",fallback = DeptClientServiceFallbackFactory.class)
public interface DeptClientService {

	@RequestMapping(value = "/dept/get/{id}",method = RequestMethod.GET)
	R getByid(@PathVariable("id") String id);
}

3、Web层conroller调用接口

@RestController
public class DeptConsumerController {

	//编译器报错,无视。 因为这个Bean是在程序启动的时候注入的,编译器感知不到,所以报错。
	@Autowired
	DeptClientService deptClientService;
  
	@RequestMapping("/consumer/dept/get/{id}")
  public Dept get(@PathVariable("id") Long id) {
    return deptClientService.getById(id);
  }
}

4、启动类加上注解@EnableFeignClients开启Feign的功能:

@SpringBootApplication
@EnableEurekaClient // 客户端
@EnableFeignClients
// 配置Ribbon客户端, 多个  *
@RibbonClient(name = "provider-dept",configuration = MySelfRule.class)
public class ConsumerDeptApplication {
	public static void main(String[] args) {
		SpringApplication.run(ConsumerDeptApplication.class,args);
	}
}

3、Hystrix断路器

服务雪崩

分布式系统面临的问题:

一个复杂系统中,经常会有数十个依赖相互调用,有时候依赖可能会调用失败

服务雪崩

多个微服务之间的调用,A调用B,B调用C,C又调用….,这就是所谓的扇出

假设扇出的链路上,某一个微服务调用响应时间过长,或者直接不可用,对于微服务A,这个时候就会占用越来越多的资源,从而可能引起系统崩溃,这就是所谓的“雪崩效应”。

对于高流量的应用来说,单例的后端依赖可能会导致所有服务上的资源在几秒内就饱和。比失败更糟糕的是,导致服务之间的延迟增加,队列,线程资源都紧张,可能导致更多的问题出现。

Hystrix的出现

Hystrix (豪猪,断路器)是一个用于处理分布式系统的延迟和容错的库。在分布式系统中,许多的依赖,不可避免会调用失败,比如超时,异常,Hystrix就可以保证一个依赖出现了问题的时候,不会导致整体的服务失败,避免级联故障,从而提高分布式系统的弹性。

它的作用:

服务熔断(提供方,服务端)

熔断就是一种应对服务雪崩的链路保护机制,在服务提供方设置的。

测试

1、导入依赖

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>

2、在controller对应的类上,处理问题,编写解决方案

@RestController
public class DeptController {

    @Autowired
    private DeptService deptService;

    // 面对这种情况,就可以使用Hystrix,
    // 使用 @HystrixCommand 然后配置 fallbackMethod
    // 一旦调用服务失败,抛出了错误信息后,就自动会调用 fallbackMethod的方法!
    @HystrixCommand(fallbackMethod = "hystrixFallbackGet")
    @GetMapping("/dept/get/{id}")
    public Dept get(@PathVariable("id") Long id){
        Dept dept = deptService.queryById(id);
        if (dept==null){
            throw new RuntimeException("该id"+id + "没有查询到对应的信息");
        }
        return dept;
    }

    // 出现问题的解决方案!备用
    public Dept hystrixFallbackGet(@PathVariable("id") Long id){
        return new Dept().setDeptno(id)
                .setDname("该id"+id+"没有对应的信息null=>hystrix_get_fallbackMethod")
                .setDb_source("no this data in mysql");
    }
}

3、启动类增加注解@EnableHystrix或者@EnableCircuitBreaker

@SpringBootApplication
@EnableEurekaClient // 本服务启动之后,就会自动注册到 Eureka 中
@EnableDiscoveryClient // 开启服务发现
@EnableCircuitBreaker // 对Hystrix熔断机制的支持
//@EnableHystrix // 源码已包含@EnableCircuitBreaker注解
public class ProviderDeptApplication {
	public static void main(String[] args) {
		SpringApplication.run(ProviderDeptApplication.class,args);
	}
}

服务降级(消费方,客户端)

理解:在某种情况下,调节服务,保证最高性能

上图为服务应用,在做秒杀活动60%的请求都是秒杀,只有10%的请求是退款的,为保证秒杀的请求的资源,可以把退款调用的微服务关闭,通过服务降级快速结束请求,把资源让处理,活动过后,再把退款的微服务应用开启。

Feign集成了hystrix,不用导入hystrix依赖

这里使用FallbackFactory编写降级方法,也可以使用Fallback,很简单的,参考我另外一篇文章

在feign使用断路器

1、编写降级Factory类

// 客户端调用的失败的处理!
// 1、客户端编写 FallbackFactory 类,实现 FallbackFactory接口,参数就是要处理的业务方法
// 2、编写对应的逻辑(处理,接口调用这个 FallbackFactory)
// 3、丢到spring中
@Component
public class DeptClientServiceFallbackFactory implements FallbackFactory<DeptClientService> {

    // 处理错误逻辑, 统一的处理方式
    public DeptClientService create(Throwable throwable) {
        return new DeptClientService() {

            public Dept queryById(Long id) {
                return new Dept().setDeptno(id)
                        .setDname("请明天退款!")
                        .setDb_source("no this data in mysql");
            }

            public List<Dept> queryAll() {
                return null;
            }
        };
    }
}

2、DeptClientServiceFallbackFactory配置feign接口中

@FeignClient(value = "SPRINGCLOUD-PROVIDER-DEPT",fallbackFactory = DeptClientServiceFallbackFactory.class)
public interface DeptClientService {

    @GetMapping("/dept/get/{id}")
    public Dept queryById(@PathVariable("id") Long id); // 根据id查询

    @GetMapping("/dept/list")
    public List<Dept> queryAll(); // 查询所有

}

3、开启hystrix支持

feign:
  hystrix:
    enabled: true

小结

服务熔断:服务端设定,一般是因为一个服务故障,或者异常引起的,类似保险丝,触发了异常,就直接熔断,不会等待服务超时。

服务降级:客户端设定,从整体的负荷考虑,将某个服务挂掉啦,不能再被调用了,前面举的秒杀活动例子,这个时候客户端可以准备一个自己的本地fallback进行回调,返回缺省值。虽然整体架构上,服务水平下降了,但是比直接挂掉强。

4、服务监控-可视化面板

单个服务监控

/springcloud/2019/11/05/springcloud-hystrix-dashboard.html

多个服务监控

/springcloud/2019/11/06/springcloud-hystrix-turbine.html

Post Directory