JavaEE 企业级分布式高级架构师(十三)微服务框架 SpringCloud (H 版)(2)

本文深入解析SpringCloud中的OpenFeign与Ribbon,介绍OpenFeign作为声明式REST客户端的工作原理,以及如何与Ribbon结合实现负载均衡。涵盖了OpenFeign的使用、配置、超时与压缩设置,Ribbon的负载均衡策略及自定义实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

OpenFeign与Ribbon

概述

OpenFeign简介

在这里插入图片描述

  • 原文】Declarative REST Client: Feign creates a dynamic implementation of an interface decorated(装饰) with JAX-RS or Spring MVC annotations.
  • 翻译】声明式 REST 客户端:Feign 通过使用 JAX-RS 或 SpringMVC 注解的装饰方式,生成接口的动态实现。
  • 综合说明:Feign,假装、伪装。OpenFeign 可以使消费者将提供者提供的服务名伪装为接口进行消费,消费者只需使用“Feign 接口 + 注解”的方式即可直接调用 Service 接口方法,而无需再使用 RestTemplate 了。

OpenFeign与Feign

  • Spring Cloud D版及之前的版本使用的是 Feign,而该项目现已更新为了 OpenFeign,所以后续使用的依赖也发生了变化。
<!-- openfeign 依赖 -->
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

Ribbon与OpenFeign

  • 说到 OpenFeign,不得不提的就是 Ribbon。 Ribbon 是 Netflix 公司的一个开源的负载均衡项目,是一个客户端负载均衡器,运行在消费者端。
  • OpenFeign 中使用 Ribbon 进行负载均衡,所以 OpenFeign 直接内置了 Ribbon。即在导入OpenFeign 依赖后,无需再专门导入 Ribbon 依赖了。

声明式Rest客户端OpenFeign

创建消费者工程 03-consumer-feign-8080

  • 复制 02-consumer-8080 工程,并重命名为 03-consumer-feign-8080,添加 openfeign 依赖即可。
  • 定义 Feign Service 接口,并修改 Controller 层接口实现:
@FeignClient("msc-provider-depart") // 指定Service绑定的微服务名称
@RequestMapping("/provider/depart")
public interface DepartService {
    @PostMapping("/save")
    boolean saveOne(Depart depart);
    @DeleteMapping("/del/{id}")
    boolean deleteById(@PathVariable("id") int id);
    @PutMapping("/update")
    boolean updateOne(Depart depart);
    @GetMapping("/get/{id}")
    Depart getDepartById(@PathVariable("id") int id);
    @GetMapping("/list")
    List<Depart> listDeparts();
}
  • 关于 Feign 的说明:
    • Feign 接口名一般是与业务接口名相同的,但不是必须的;
    • Feign 接口中的方法名一般也与业务接口方法名相同,但也不是必须的;
    • Feign 接口中的方法返回值类型、方法参数要求与业务接口中的相同;
    • 接口上与方法上的 Mapping 的参数 URI 要与提供者处理器相应方法上的 Mapping 的 URI 相同。
@RestController
@RequestMapping("/consumer/depart")
public class DepartController {
    /**
     * 这里的DepartService是FeignClient,并不是真正的业务接口
     */
    @Autowired
    private DepartService departService;

    @PostMapping("/save")
    public boolean saveHandle(@RequestBody Depart depart) {
        return departService.saveOne(depart);
    }

    @DeleteMapping("/del/{id}")
    public boolean deleteHandle(@PathVariable("id") int id) {
        return departService.deleteById(id);
    }

    @PutMapping("/update")
    public boolean updateHandle(@RequestBody Depart depart) {
        return departService.updateOne(depart);
    }

    @GetMapping("/get/{id}")
    public Depart getHandle(@PathVariable("id") int id) {
        return departService.getDepartById(id);
    }

    @GetMapping("/list")
    public List<Depart> listHandle() {
        return departService.listDeparts();
    }
}
  • 启动类开启Feign客户端:

在这里插入图片描述

超时与压缩

  • 在 application.yml 中添加如下配置
feign:
  client:
    config:
      default:
        # 指定Feign连接提供者的超时时限,决定于网络状况
        connectTimeout: 5000
        # 指定Feign从请求到获取提供者响应的超时时限,决定于提供者的处理时间
        readTimeout: 5000
  compression:
    request:
      # 开启对请求的压缩
      enabled: true
      # 指定对哪些MIME类型的文件进行压缩
      mime-types: ['text/xml', 'application/xml', 'application/json']
      min-request-size: 2048
    response:
      # 开启对客户端响应的压缩
      enabled: true

Ribbon负载均衡

系统结构

在这里插入图片描述

创建提供者 03-provider-8090

  • 复制 02-provider-8090 工程,并重命名为 03-provider-8090,利用该工程在本地启动三个提供者,端口号分别是 8090、8091、8092,IDEA允许并行运行。

在这里插入图片描述- 修改Service实现类:

@Service
public class DepartServiceImpl implements DepartService{
    @Autowired
    private DepartRepository repository;
    // 读取配置文件中的属性值
    @Value("${server.port}")
    private int port;
    // 略...
    @Override
    public Depart getDepartById(int id) {
        // repository.getOne()中指定的id若不存在,则会抛出异常,因此这里要先判断是否存在
        if (repository.existsById(id)) {
            Depart depart = repository.getOne(id);
            depart.setName(rename(depart.getName()));
            return depart;
        }
        return new Depart().setName(port + " No this depart");
    }

    @Override
    public List<Depart> listDeparts() {
        List<Depart> departs = repository.findAll();
        departs.forEach(v -> v.setName(rename(v.getName())));
        return departs;
    }

    private String rename(String name) {
        return name + " : " + port;
    }
}
  • 启动三个提供者:

在这里插入图片描述

  • 访问地址:http://localhost:8080/consumer/depart/get/1,测试负载均衡效果,可以看到采用的是轮询方式。

负载均衡策略

内置负载均衡策略

  • RoundRobinRule:轮询策略。Ribbon 默认采用的策略。若经过一轮轮询没有找到可用的 provider,其最多轮询 10 轮。若最终还没有找到,则返回 null。
  • RandomRule:随机策略,从所有可用的 provider 中随机选择一个。
  • RetryRule:重试策略。先按照 RoundRobinRule 策略获取 provider,若获取失败,则在指定的时限内重试。默认的时限为 500 毫秒。
  • BestAvailableRule:最可用策略。选择并发量最小的 provider,即连接的消费者数量最少的 provider。
  • AvailabilityFilteringRule:可用过滤算法。该算法规则是,先采用轮询方式选择一个 Server,然后判断其是否处于熔断状态,是否已经超过连接极限。若没有,则直接选择。否则再重新按照相同的方式进行再选择。最多重试 10 次。若 10 次后仍没有找到,则重新将所有 Server 进行判断,挑选出所有未熔断,未超过连接极限的 Server,然后再采用轮询方式选择一个。若还没有符合条件的,则返回 null。
  • ZoneAvoidanceRule:zone 回避策略。根据 provider 所在 zone 及 provider 的可用性,对 provider 进行选择。
  • WeightedResponseTimeRule:“权重响应时间”策略。根据每个 provider 的平均响应时间计算其权重,响应时间越快权重越大,被选中的机率就越高。在刚启动时采用轮询策略。后面就会根据权重进行选择了。

更换内置策略 03-consumer-lb-8080

  • Ribbon 默认采用的是 RoundRobinRule,即轮询策略。但通过修改消费者工程的配置文件,或修改消费者的启动类或 JavaConfig 类可以实现更换负载均衡策略的目的。
  • 复制 03-consumer-feign-8080 工程,并重命名为 03-consumer-lb-8080。
  • 方式一 修改 application.yml 配置文件:在其中添加如下内容
# 修改负载均衡策略
msc-provider-depart: # 要负载均衡的提供者微服务名称
  ribbon:
    # 指定要使用的负载均衡策略
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
  • 方式二 修改 JavaConfig 类:在 JavaConfig 类中添加负载负载 Bean 方法,这种方式的优先级比配置文件中的要高
@Configuration
public class ConsumerConfig {
    // 指定Ribbon使用随即算法策略
    @Bean
    public IRule loadBalanceRule() {
        return new RandomRule();
    }
}

自定义负载均衡策略

  • Ribbon 支持自定义负载均衡策略,自定义实现的负载均衡算法类需要实现 IRule 接口。
  • 自定义 CustomRule 类:从所有可用的 provider 中排除掉指定端口号的 provider,剩余 provider 进行随机选择。
public class CustomRule implements IRule {
    private ILoadBalancer lb;
    // 要排除的提供者的服务端口号
    private List<Integer> excludePorts;

    public CustomRule() {
    }
    public CustomRule(List<Integer> excludePorts) {
        this.excludePorts = excludePorts;
    }

    @Override
    public void setLoadBalancer(ILoadBalancer lb) {
        this.lb = lb;
    }

    @Override
    public ILoadBalancer getLoadBalancer() {
        return lb;
    }

    @Override
    public Server choose(Object key) {
        // 获取所有可用的提供者主机
        List<Server> servers = lb.getReachableServers();
        // 获取所有排除了指定端口号的提供者
        List<Server> availableServers = this.getAvailableServers(servers);
        // 从剩余的提供者中随机获取可用的提供者
        return this.getAvailableRandomServers(availableServers);
    }

    // 获取所有排除了指定端口号的提供者
    private List<Server> getAvailableServers(List<Server> servers) {
        // 若没有要排除的主机,则返回所有
        if(excludePorts == null || excludePorts.size() == 0) {
            return servers;
        }
        return servers.stream()
                .filter(server -> excludePorts.stream().noneMatch(port -> server.getPort() == port))
                .collect(Collectors.toList());
    }
    // 从剩余的提供者中随机获取可用的提供者
    private Server getAvailableRandomServers(List<Server> availableServers) {
        // 获取一个[0,availableServers.size())的随机数
        int index = new Random().nextInt(availableServers.size());
        return availableServers.get(index);
    }
}
  • 修改 JavaConfig 类:将原来的负载均衡 Bean 方法注释掉,添加新的负载均衡策略方法。
@Configuration
public class ConsumerConfig {
    // ...
//    // 指定Ribbon使用随即算法策略
//    @Bean
//    public IRule loadBalanceRule(){
//        return new RandomRule();
//    }
    // 指定Ribbon使用自定义负载均衡策略
    @Bean
    public IRule loadBalanceRule(){
        List<Integer> ports = new ArrayList<>();
        ports.add(8083);
        return new CustomRule(ports);
    }
}

OpenFeign源码解析

重要的类与接口解析

@EnableFeignClients
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
	String[] value() default {};
	String[] basePackages() default {};
	Class<?>[] basePackageClasses() default {};
	Class<?>[] defaultConfiguration() default {};
	Class<?>[] clients() default {};
}
  • @EnableXxx 中一般都会包含@Import 注解,该注解用于导入配置类。有三种:
    • 直接导入配置类:一般为 Configuration 结尾的类
    • 根据条件选择配置类:一般为 ConfigurationSelector 结尾的类
    • 动态注册配置类:一般为 Registrar 结尾
  • 进入 FeignClientsRegistrar

在这里插入图片描述

  • 我们找到 registerBeanDefinitions 方法:打断点查看

在这里插入图片描述

  • 再进入到 registerFeignClients 方法中,找到 registerFeignClient 方法,断点查看

在这里插入图片描述

  • 执行解析:访问 http://localhost:8080/consumer/depart/get/1,在对应的控制器方法中打断点查看
    在这里插入图片描述
  • 由以上源码分析可以知道,Feign的底层仍然是用的 RestTemplate。
@FeignClient
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface FeignClient {
	@AliasFor("name")
	String value() default "";
	@Deprecated
	String serviceId() default "";
	String contextId() default "";
	@AliasFor("value")
	String name() default "";
	String qualifier() default "";
	String url() default "";
	boolean decode404() default false;
	Class<?>[] configuration() default {};
	Class<?> fallback() default void.class;
	Class<?> fallbackFactory() default void.class;
	String path() default "";
	boolean primary() default true;
}

Ribbon源码解析

跟踪RibbonLoadBalancerClient类

  • 分析入口:@LoadBalanced——开启消费者客户端的负载均衡功能
    在这里插入图片描述
  • 再来看一下 LoadBalancerClient 接口的实现类 RibbonLoadBalancerClient:
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
  • 其中 rule:
    在这里插入图片描述

解析IRule接口

  • 查看一下 IRule 接口:
    在这里插入图片描述
  • 该接口的实现类:
    在这里插入图片描述

Ribbon内置负载均衡算法

RoundRobinRule
  • 轮询策略:Ribbon 默认采用的策略。
public class RoundRobinRule extends AbstractLoadBalancerRule {
	// 原子整形,用于获取下一个Server的循环计数器
    private AtomicInteger nextServerCyclicCounter;
    private static final boolean AVAILABLE_ONLY_SERVERS = true;
    private static final boolean ALL_SERVERS = false;

    private static Logger log = LoggerFactory.getLogger(RoundRobinRule.class);

    public RoundRobinRule() {
        nextServerCyclicCounter = new AtomicInteger(0);
    }

    public RoundRobinRule(ILoadBalancer lb) {
        this();
        setLoadBalancer(lb);
    }
	public Server choose(ILoadBalancer lb, Object key) {
	    if (lb == null) {
	        log.warn("no load balancer");
	        return null;
	    }
	
	    Server server = null;
	    int count = 0;
	    while (server == null && count++ < 10) { // 默认尝试获取10次
	        List<Server> reachableServers = lb.getReachableServers();
	        List<Server> allServers = lb.getAllServers();
	        int upCount = reachableServers.size();
	        int serverCount = allServers.size();
	
	        if ((upCount == 0) || (serverCount == 0)) {
	            log.warn("No up servers available from load balancer: " + lb);
	            return null;
	        }
	        // 轮询获取
	        int nextServerIndex = incrementAndGetModulo(serverCount);
	        // 这里是从allServers获取,所以有可能获取到不可用的server
	        server = allServers.get(nextServerIndex);
	
	        if (server == null) {
	            /* Transient. */  
	            Thread.yield();		// 线程让步,把CPU的使用权让出去
	            continue;
	        }
	        if (server.isAlive() && (server.isReadyToServe())) {
	            return (server);
	        }
	        // Next.
	        server = null;
	    }
	
	    if (count >= 10) {
	        log.warn("No available alive servers after 10 tries from load balancer: " + lb);
	    }
	    return server;
	}	
	private int incrementAndGetModulo(int modulo) {
        for (;;) {
            int current = nextServerCyclicCounter.get();
            int next = (current + 1) % modulo;
            if (nextServerCyclicCounter.compareAndSet(current, next))
                return next;
        }
    }
    @Override
    public Server choose(Object key) {
        return choose(getLoadBalancer(), key);
    }
    // 省略 initWithNiwsConfig() ...
}
RandomRule
  • 随即策略:从所有可用的 provider 中随机选择一个。
public class RandomRule extends AbstractLoadBalancerRule {
    public Server choose(ILoadBalancer lb, Object key) {
        if (lb == null) {
            return null;
        }
        Server server = null;

        while (server == null) {
            if (Thread.interrupted()) {
                return null;
            }
            List<Server> upList = lb.getReachableServers();
            List<Server> allList = lb.getAllServers();

            int serverCount = allList.size();
            if (serverCount == 0) {
                return null;
            }
			// 随机选择
            int index = chooseRandomInt(serverCount);
			// 这里是一个Bug,很可能就会数组越界
            server = upList.get(index);

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

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

            // Shouldn't actually happen.. but must be transient or a bug.
            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);
	}
	// 省略 initWithNiwsConfig() ...
}
RetryRule
  • 先按照 RoundRobinRule 策略获取 provider,若获取失败,则在指定的时限内重试。默认的时限为 500 毫秒。
public class RetryRule extends AbstractLoadBalancerRule {
	IRule subRule = new RoundRobinRule();	// 子策略:轮询
	long maxRetryMillis = 500;		// 最大重试时间,500ms写死,不过可以修改

	public RetryRule() {
	}

	public RetryRule(IRule subRule) {
		this.subRule = (subRule != null) ? subRule : new RoundRobinRule();
	}

	public RetryRule(IRule subRule, long maxRetryMillis) {
		this.subRule = (subRule != null) ? subRule : new RoundRobinRule();
		this.maxRetryMillis = (maxRetryMillis > 0) ? maxRetryMillis : 500;
	}

	public void setRule(IRule subRule) {
		this.subRule = (subRule != null) ? subRule : new RoundRobinRule();
	}

	public IRule getRule() {
		return subRule;
	}

	public void setMaxRetryMillis(long maxRetryMillis) {
		if (maxRetryMillis > 0) {
			this.maxRetryMillis = maxRetryMillis;
		} else {
			this.maxRetryMillis = 500;
		}
	}
	public long getMaxRetryMillis() {
		return maxRetryMillis;
	}
	@Override
	public void setLoadBalancer(ILoadBalancer lb) {		
		super.setLoadBalancer(lb);
		subRule.setLoadBalancer(lb);
	}
	public Server choose(ILoadBalancer lb, Object key) {
		long requestTime = System.currentTimeMillis();
		long deadline = requestTime + maxRetryMillis;

		Server answer = null;
		answer = subRule.choose(key);

		if (((answer == null) || (!answer.isAlive())) && (System.currentTimeMillis() < deadline)) {
			InterruptTask task = new InterruptTask(deadline - System.currentTimeMillis());
			while (!Thread.interrupted()) {
				answer = subRule.choose(key);
				if (((answer == null) || (!answer.isAlive()))
						&& (System.currentTimeMillis() < deadline)) {
					/* pause and retry hoping it's transient */
					Thread.yield();
				} else {
					break;
				}
			}
			task.cancel();
		}

		if ((answer == null) || (!answer.isAlive())) {
			return null;
		} else {
			return answer;
		}
	}
	@Override
	public Server choose(Object key) {
		return choose(getLoadBalancer(), key);
	}
	// 省略 initWithNiwsConfig() ...
}
BestAvailableRule
  • 选择并发量最小的 provider,即连接的消费者数量最少的 provider。
public class BestAvailableRule extends ClientConfigEnabledRoundRobinRule {
    private LoadBalancerStats loadBalancerStats;
    @Override
    public Server choose(Object key) {
        if (loadBalancerStats == null) {
            return super.choose(key);
        }
        List<Server> serverList = getLoadBalancer().getAllServers();
        int minimalConcurrentConnections = Integer.MAX_VALUE;
        long currentTime = System.currentTimeMillis();
        Server chosen = null;
        for (Server server: serverList) {
        	// 获取服务器状态
            ServerStats serverStats = loadBalancerStats.getSingleServerStat(server);
            // 判断是否处于熔断状态
            if (!serverStats.isCircuitBreakerTripped(currentTime)) {
                int concurrentConnections = serverStats.getActiveRequestsCount(currentTime);
                // 冒泡获取请求连接数量最少的那个服务器
                if (concurrentConnections < minimalConcurrentConnections) {
                    minimalConcurrentConnections = concurrentConnections;
                    chosen = server;
                }
            }
        }
        if (chosen == null) {
            return super.choose(key);
        } else {
            return chosen;
        }
    }

    @Override
    public void setLoadBalancer(ILoadBalancer lb) {
        super.setLoadBalancer(lb);
        if (lb instanceof AbstractLoadBalancer) {
            loadBalancerStats = ((AbstractLoadBalancer) lb).getLoadBalancerStats();            
        }
    }
}
AvailabilityFilteringRule
  • 可用过滤策略:该算法规则是过滤掉处于断路器跳闸状态的 provider,或已经超过连接极限的 provider,对剩余 provider 采用轮询策略。
public class AvailabilityFilteringRule extends PredicateBasedRule {    

    private AbstractServerPredicate predicate;
    
    public AvailabilityFilteringRule() {
    	super();
    	predicate = CompositePredicate.withPredicate(new AvailabilityPredicate(this, null))
                .addFallbackPredicate(AbstractServerPredicate.alwaysTrue())
                .build();
    }
    
    
    @Override
    public void initWithNiwsConfig(IClientConfig clientConfig) {
    	predicate = CompositePredicate.withPredicate(new AvailabilityPredicate(this, clientConfig))
    	            .addFallbackPredicate(AbstractServerPredicate.alwaysTrue())
    	            .build();
    }

    @Monitor(name="AvailableServersCount", type = DataSourceType.GAUGE)
    public int getAvailableServersCount() {
    	ILoadBalancer lb = getLoadBalancer();
    	List<Server> servers = lb.getAllServers();
    	if (servers == null) {
    		return 0;
    	}
    	return Collections2.filter(servers, predicate.getServerOnlyPredicate()).size();
    }
    @Override
    public Server choose(Object key) {
        int count = 0;
        Server server = roundRobinRule.choose(key);
        while (count++ <= 10) {
            if (predicate.apply(new PredicateKey(server))) {
                return server;
            }
            server = roundRobinRule.choose(key);
        }
        return super.choose(key);
    }
    @Override
    public AbstractServerPredicate getPredicate() {
        return predicate;
    }
}
	↓↓↓↓↓
public abstract class PredicateBasedRule extends ClientConfigEnabledRoundRobinRule {
    public abstract AbstractServerPredicate getPredicate();
    @Override
    public Server choose(Object key) {
        ILoadBalancer lb = getLoadBalancer();
        // 轮询选择
        Optional<Server> server = getPredicate().chooseRoundRobinAfterFiltering(lb.getAllServers(), key);
        if (server.isPresent()) {
            return server.get();
        } else {
            return null;
        }       
    }
}
public class AvailabilityPredicate extends  AbstractServerPredicate {     
    // 省略...
    
    @Override
    public boolean apply(@Nullable PredicateKey input) {
        LoadBalancerStats stats = getLBStats();
        if (stats == null) {
            return true;
        }
        return !shouldSkipServer(stats.getSingleServerStat(input.getServer()));
    }
    
    
    private boolean shouldSkipServer(ServerStats stats) {        
        if ((CIRCUIT_BREAKER_FILTERING.get() && stats.isCircuitBreakerTripped()) 
                || stats.getActiveRequestsCount() >= activeConnectionsLimit.get()) {
            return true;
        }
        return false;
    }

}
ZoneAvoidanceRule
  • 复合判断 provider 所在区域的性能及 provider 的可用性选择服务器。
WeightedResponseTimeRule
  • “权重响应时间”策略。根据每个 provider 的平均响应时间计算其权重,响应时间越快权重越大,被选中的机率就越高。在刚启动时采用轮询策略。后面就会根据权重进行选择了。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

讲文明的喜羊羊拒绝pua

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值