客户端负载均衡器

 

文章目录

 

客户端负载均衡器

在SpringCloud中Ribbon负载均衡客户端,会从Eureka注册中心服务器端上获取服务注册信息列表,缓存到本地。然后在本地实现轮询负载均衡策略。

[外链图片转存失败(img-6GzCfmbm-1568879451316)(images/1568860466422.png)]
SpringCloud→客户端负载均衡器(三)_线程池

Ribbon与Nginx区别

服务器端负载均衡Nginx

Nginx是客户端所有请求统一交给Nginx,由Nginx进行实现负载均衡请求转发,属于服务器端负载均衡。
即请求由Nginx服务器端进行转发。

客户端负载均衡Ribbon

Ribbon是从eureka注册中心服务器端上获取服务注册信息列表,缓存到本地(JVM中),然后在本地实现轮询负载均衡策略。
即在客户端实现负载均衡。

应用场景的区别:

Nginx适合于服务器端实现负载均衡 比如Tomcat
Ribbon适合于在微服务中RPC远程调用实现本地服务负载均衡,比如Dubbo、SpringCloud中都是采用本地负载均衡。

使用discoveryClient负载本地负载均衡

package com.itmayiedu;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import java.util.List;

/**
 * 纯手写Ribbon本地负载均衡效果
 * 需要把@LoadBalanced 注解注释掉 因为SpringCloud本身内部已实现了Ribbon本地负载均衡
 */
@RestController
public class ExtRibbonController {


    // 可以获取注册中心上的服务列表
    @Autowired
    private DiscoveryClient discoveryClient;
    @Autowired
    private RestTemplate restTemplate;

    // 接口的请求总数
    // 案例只为模拟操作 未考虑线程安全问题   现实中可使用 原子计数器 因为线程安全 采用无所机制 使用CAS 效率非常高
    private int reqCount = 1;

    @RequestMapping("/ribbonMember")
    public String ribbonMember(){

        // 1. 获取对应服务器远程调用地址
        String instancesUrl = getInstances() + "/getMember";
        System.out.println("instancesUrl:" + instancesUrl);
        // 2. 再使用rest方式发送请求
        String result = restTemplate.getForObject(instancesUrl, String.class);
        return result;
    }

    public String getInstances(){
        List<ServiceInstance> instances = discoveryClient.getInstances("app-itmayiedu-member");
        if (instances==null||instances.size()<=0){
            return null;
        }
        // 获取服务器集群个数
        int instanceSize = instances.size();
        int serviceIndex = reqCount % instanceSize;
        reqCount++;
        return instances.get(serviceIndex).getUri().toString();
    }
}

服务保护机制SpringCloud Hystrix

微服务高可用技术

大型复杂的分布式系统中,高可用相关的技术架构非常重要。
高可用架构非常重要的一个环节,就是如何将分布式系统中的各个服务打造成高可用的服务,从而足以应对分布式系统环境中的各种各样的问题,,避免整个分布式系统被某个服务的故障给拖垮。

比如:

  • 服务间的调用超时
  • 服务间的调用失败

要解决这些棘手的分布式系统可用性问题,就涉及到了高可用分布式系统中的很多重要的技术,包括:

  • 资源隔离
  • 限流与过载保护
  • 熔断
  • 优雅降级
  • 容错
  • 超时控制
  • 监控运维

服务雪崩效应、服务降级、熔断、隔离、限流概念

服务雪崩效应

服务雪崩效应产生与服务堆积在同一个线程池中,因为所有的请求都是同一个线程池进行处理,这时候如果在高并发情况下,所有的请求全部访问同一个接口,这时候可能会导致其他服务没有线程进行接受请求,造成用户一直等待,最终请求超时。这就是服务雪崩效应效应。

[外链图片转存失败(img-hKV6511F-1568879451317)(images/1568861659251.png)]
SpringCloud→客户端负载均衡器(三)_spring_02

服务降级

在高并发情况下,防止用户一直等待,使用服务降级方式(直接返回一个友好的提示给客户端,调用fallBack方法) 目的是为了用户体验

如果调用其他接口的时候(默认是1s时间) 如果在1s中没有及时响应的话,默认情况下业务逻辑是可以执行的,但是直接执行服务降级方法

服务熔断

熔断机制目的为了保护服务,在高并发的情况下,如果请求达到一定极限(可以自己设置阔值)如果流量超出了设置阈值,然后直接拒绝访问,保护当前服务。使用服务降级方式返回一个友好提示,服务熔断和服务降级通常一起使用

服务隔离

因为默认情况下,只有一个线程池会维护所有的服务接口,如果大量的请求访问同一个接口,达到tomcat 线程池默认极限,可能会导致其他服务无法访问。
解决服务雪崩效应: 使用服务隔离机制(线程池方式和信号量),

使用线程池方式实现隔离的原理:
相当于每个接口(服务)都有自己独立的线程池,因为每个线程池互不影响,这样的话就可以解决服务雪崩效应。
SpringCloud→客户端负载均衡器(三)_负载均衡_03

  • 线程池隔离:
    每个服务接口,都有自己独立的线程池,每个线程池互不影响。缺点是 CPU占用率非常高
  • 信号量隔离:
    使用一个原子计数器(或信号量)来记录当前有多少个线程在运行,当请求进来时先判断计数器的数值,若超过设置的最大线程个数则拒绝该请求,若不超过则通行,这时候计数器+1,请求返 回成功后计数器-1。

服务限流

服务限流就是对接口访问进行限制,常用服务限流算法令牌桶、漏桶。计数器也可以进行粗暴限流实现。

Hystrix简单介绍

Hystrix是国外知名的视频网站Netflix所开源的非常流行的高可用架构框架。Hystrix能够完美的解决分布式系统架构中打造高可用服务面临的一系列技术难题。

Hystrix “豪猪”,具有自我保护的能力。hystrix 通过如下机制来解决雪崩效应问题。

在微服务架构中,我们把每个业务都拆成了单个服务模块,然后当有业务需求时,服务间可互相调用,但是,由于网络原因或者其他一些因素,有可能出现服务不可用的情况,当某个服务出现问题时,其他服务如果继续调用这个服务,就有可能出现线程阻塞,但如果同时有大量的请求,就会造成线程资源被用完,这样就可能会导致服务瘫痪,由于服务间会相互调用,很容易造成蝴蝶效应导致整个系统宕掉。因此,就有人提出来断路器来解决这一问题。

资源隔离:包括线程池隔离和信号量隔离,限制调用分布式服务的资源使用,某一个调用的服务出现问题不会影响其他服务调用。

降级机制:超时降级、资源不足时(线程或信号量)降级,降级后可以配合降级接口返回托底数据。

熔断:当失败率达到阀值自动触发降级(如因网络故障/超时造成的失败率高),熔断器触发的快速失败会进行快速恢复。

缓存:提供了请求缓存、请求合并实现。

Hystrix服务保护框架,在微服务中Hystrix能够为我们解决哪些问题?

  • 断路器
  • 服务降级
  • 服务熔断
  • 服务隔离机制
  • 服务雪崩效应 连环雪崩效应

Hystrix环境搭建

maven依赖

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

yml配置文件中加入开启Hystrix断路器配置

此处加在了订单模块中,目的是防止访问订单服务发生雪崩效应时 不影响会员服务的正常访问

### 开启Hystrix断路器
feign:
  hystrix:
    enabled: true

#### hystrix禁止服务超时时间
hystrix:
  command:
    default:
      execution:
        timeout:
          enabled: false

启动类添加注解@EnableHystrix

package com.itmayiedu.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;

// @EnableHystrix 注解的作用 开启Hystrix
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
@EnableHystrix
public class AppOrder {
    public static void main(String[] args) {
        SpringApplication.run(AppOrder.class, args);
    }
}

服务降级处理

// fallbackMethod 方法的作用: 服务降级执行
    // @HystrixCommand 作用为当服务发生雪崩时 开启服务降级 默认开启了线程池隔离方式 服务降级 服务熔断机制
    // Hystrix 有两种方式配置保护服务 通过注解和接口形式
    // 解决服务雪崩效应的
    @HystrixCommand(fallbackMethod = "orderToMemberUserInfoHystrixFallback")
    @RequestMapping("/orderToMemberUserInfoHystrix")
    public ResponseBase orderToMemberUserInfoHystrix() {
        System.out.println("orderToMemberUserInfoHystrix-线程池名称:" + Thread.currentThread().getName());
        return memberServiceFeign.getUserInfo();
    }

    public ResponseBase orderToMemberUserInfoHystrixFallback(){
        return setResultSuccess("返回一个友好的提示: 服务降级,服务器忙,请稍后重试!");
    }

以类的方式实现服务降级 创建Fallback类 以统一管理服务降级提示

  1. 编写测试接口
@RequestMapping("/orderToUserInfo")
public ResponseBase orderToUserInfo() {
    return memberServiceFeigin.getUserInfo();
}
  1. 创建服务降级的类 统一管理提示
@Component
public class MemberServiceFallback extends BaseApiService implements MemberServiceFeigin {
	public ResponseBase getUserInfo() {
		// 服务降级处理
		return setResultError("系统错误,请稍后重试,以类的方式实现服务降级!");
	}
}
  1. 在feign客户端添加fallback类注解 制定fallback类
package com.itmayiedu.api.feign;

import com.itmayiedu.api.fallback.MemberServiceFallback;
import com.itmayiedu.api.service.IMemberService;
import org.springframework.cloud.openfeign.FeignClient;

@FeignClient(value = "app-itmayiedu-member", fallback = MemberServiceFallback.class)
public interface MemberServiceFeign extends IMemberService {

}

声明式服务调用SpringCloud Feign

SpringCloud中支持两种客户端调用工具

  • Rest RestTemplate(基本上不用的)
  • Feign客户端工具 (以后在实际开发中用的最多)

Feign介绍

Feign客户端是一个web声明式http远程调用工具,提供了接口注解方式进行调用。易读性比较强

环境搭建

Maven依赖信息

<!-- SpringBoot整合fegnin客户端 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

feign客户端接口

package com.itmayiedu.api.feign;

import com.itmayiedu.api.service.IMemberService;
import org.springframework.cloud.openfeign.FeignClient;

// name 指定服务名称
@FeignClient("app-itmayiedu-member")
public interface MemberServiceFeign  {

	// Feign 书写形式: 以SpringMVC接口形式书写
	@RequestMapping("/getMember")
	public String getMember();
}

创建Contrller

@RestController
public class FeignController {
	@Autowired
	private MemberServiceFeign  memberServiceFeign;
	
    @RequestMapping("/feignMember") 
    public String feignMember(){
        // 使用Feign客户端调用
        String result = memberServiceFeign.getMember();
        return result;
    }

}

项目启动加上@EnableFeignClients

package com.itmayiedu.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;

// @EnableFeignClients 可以开启Feign权限
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
public class AppOrder {
    public static void main(String[] args) {
        SpringApplication.run(AppOrder.class, args);
    }
}

feign继承特性

在使用声明式Feign客户端工具的时候,因为书写的方式代码可能会产生重复,可以使用feign客户端继承方式减少代码。

如上面的代码可将MemberServiceFeign.java改为如下方式

package com.itmayiedu.api.feign;

import com.itmayiedu.api.service.IMemberService;
import org.springframework.cloud.openfeign.FeignClient;

@FeignClient("app-itmayiedu-member")
public interface MemberServiceFeign extends IMemberService {

}

package com.itmayiedu.api.service;

import com.itmayiedu.api.entity.UserEntity;
import com.itmayiedu.base.ResponseBase;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

public interface IMemberService {

    @RequestMapping("/getMember")
    public UserEntity getMember(@RequestParam("name") String name);

}

项目目录结构:

[外链图片转存失败(img-0KFntc4W-1568879451317)(images/1568879076405.png)]
SpringCloud→客户端负载均衡器(三)_nginx_04
SpringCloud→客户端负载均衡器(三)_负载均衡_05

  • parent作为总父类
    • member-service-impl 为会员服务实现 写的接口的相关实现
    • order-service-impl 为订单服务实现 写的接口的相关实现
    • api-service
      • api-member-service 为会员服务接口 相当于提供的SDK 只写接口 不写实现
      • api-order-service 为订单服务接口 相当于提供的SDK 只写接口 不写实现

Ribbon配置

SpringCloud Feign客户端Http调用工具,默认已经整合了Ribbon负载均衡客户端。

配置Feign客户端超时时间

### SpringCloud默认支持ribbon
###设置feign客户端超时时间
ribbon:
  ###指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间。
  ReadTimeout: 5000
  ###指的是建立连接后从服务器读取到可用资源所用的时间。
  ConnectTimeout: 5000