什么是ribbon?
springcloud ribbon是Netflix发布的开源项目,主要的功能是提供客户端软件的负载均衡算法和服务的调用。是实现客户端负载均衡的一套工具。我们可以很容易的通过ribbon实现自定义的负载均衡算法。客户端组件提供了一系列的组件。
是现在的主流的客户端工具之一。
负载均衡?
简单的来说就是将用户的请求平摊的分配到多个服务上,从而达到系统的高可用。
常见的负载均衡的软件有Nginx等
ribbon与nginx负载均衡的区别?
nginx是服务器负载均衡,客户端会把请求都交给nginx然后由nginx来进行转发,即负载均衡是由服务端实现的。
ribbon本地负载均衡,在调用服务接口的时候,会在配置中心上获取注册信息服务列表之后缓存到jvm本地,从而在本地实现RPC远程服务调用技术。
ribbon能用来做什么?
简单的一句话,负载均衡+RestTemplate调用
总结:ribbon其实是一个软负载均衡的客户端组件。他可以和eureka结合只是其中的一个实例。
整体项目架构图,Rest项目的搭建和之前使用的springcloud eureka服务注册与发现的项目相同。8001,8002是两个服务提供者,80是客户端,也是服务的消费者。7001,7002两个微服务搭建看Eureka集群环境。
在之前写案例的时候并没有引入ribbon的pom依赖,但同样可以使用ribbon
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
原因是在引入了eureka的客户端依赖时里面自动包含了ribbon的相关信息
getforobject和getforentity的区别?
getfor object返回的对象信息为响应体中数据转换的对象。基本上可以理解为json.
getforentity返回的信息包含响应中的一些重要的信息。比如响应头,响应状态码,响应体等。
@RequestMapping("/consumer/payment/get/{id}")
@ResponseBody
public CommonResult<Payment> getPayment(@PathVariable("id")Long id){
return restTemplate.getForObject(PAYMENT_URL+"/payment/get/"+id,CommonResult.class);
}
Irule:根据特定的算法从服务列表中选取一个要访问的服务。
IRule接口的具体实现类
修改cloud-consumer-order80的同时注意配置的细节。新建一个package com.dzu.myrule。然后在包下面新建MySelfRule规则类。最后在主启动类上添加注解@RibbonClient后进行测试。
package com.dzu.myrule;
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author chenruxu
* @date 1.5
*/
@Configuration
public class MySelfRule {
@Bean
public IRule myRule(){
return new RandomRule(); //定义方式为随机
}
}
在主启动类上加入注解之后就可以进行使用了
package com.dzu.springcloud;
import com.dzu.myrule.MySelfRule;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
/**
* @author chenruxu
* @date 2022 01/03
*/
@SpringBootApplication
@EnableEurekaClient
@RibbonClient(name = "CLOUD-PAYMENT-SERVICE",configuration = MySelfRule.class)
public class OrderMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderMain80.class,args);
}
}
之后启动五个微服务,在浏览器端不断的刷新测试,观察负载均衡算法改变之后程序的变化
采用轮询方式的负载均衡算法:rest接口第几次请求数%服务器集群总数量=实际调用服务器的下标。
每次服务器启动之后rest接口的计数从一开始。
对于轮询算法可以类比于操作系统中的时间片轮转算法,两者之间有相似之处
roundRobbionRule源码分析
package com.netflix.loadbalancer;
public interface IRule {
Server choose(Object var1);
void setLoadBalancer(ILoadBalancer var1);
ILoadBalancer getLoadBalancer();
}
RoundRobinRule extends AbstractLoadBalancerRule
/*
*
* Copyright 2013 Netflix, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.netflix.loadbalancer;
import com.netflix.client.config.IClientConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
/**
* The most well known and basic load balancing strategy, i.e. Round Robin Rule.
*
* @author stonse
* @author Nikos Michalakis
*
*/
public class RoundRobinRule extends AbstractLoadBalancerRule {
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) {
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);
server = allServers.get(nextServerIndex);
if (server == null) {
/* Transient. */
Thread.yield();
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;
}
/**
* Inspired by the implementation of {@link AtomicInteger#incrementAndGet()}.
*
* @param modulo The modulo to bound the value of the counter.
* @return The next value.
*/
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);
}
@Override
public void initWithNiwsConfig(IClientConfig clientConfig) {
}
}
什么是fegin?
fegin是一个可以与Eureka和ribbon组合使用的一支持负载均衡。
是一个声明式的web客户端,集成了ribbon。只需要创建一个接口,并在接口上添加一个微服务注解,就可以使用。
openfegin的使用步骤
新建一个微服务模块cloud-consumer-feign-order80。并引入需要用到的pom依赖信息
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<scope>runtimescope>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>com.atguigu.springcloudgroupId>
<artifactId>cloud-api-commonsartifactId>
<version>${project.version}version>
dependency>
dependencies>
OpenFegin中整合了ribbon中相关的组件
创建主启动类并使用注解@EnableFeginClients激活相对应的组件。
package com.dzu.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
/**
* openFegin测试使用的微服务
* @author chenruxu
* @date 1.7
*/
@SpringBootApplication
@EnableFeignClients
public class OrderFeignMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderFeignMain80.class,args);
}
}
编写配置文件完成Eureka的配置信息
server:
port: 80
eureka:
client:
#表示是否将自己注册进eurekaServer 默认为true
register-with-eureka: false
service-url:
#defaultZone: http://localhost:7001/eureka/
defaultZone: http://localhost:7001/eureka/,http://localhost:7002/eureka/
业务类的编写是重点的环节 编写一个业务逻辑接口+@FeginClient配置调用provider服务
创建一个微服务调用接口
找到CLOUD-PAYMENT-SERVICE下面的微服务进行调用
package com.dzu.springcloud.service;
import com.dzu.springcloud.entities.CommonResult;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
/**
*微服务调用接口
* @author chenruxu
*/
@Component
@FeignClient(value = "CLOUD-PAYMENT-SERVICE")
public interface PaymentFeignService {
@RequestMapping(value = "/payment/get/{id}")
public CommonResult getPaymentbyid(@PathVariable("id")Long id);
}
创建controller层的控制器类调用刚刚创建的微服务接口的实现类
package com.dzu.springcloud.controller;
import com.dzu.springcloud.entities.CommonResult;
import com.dzu.springcloud.entities.Payment;
import com.dzu.springcloud.service.PaymentFeignService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
@Slf4j
public class OrderFeignController {
@Resource
private PaymentFeignService paymentFeignService;
/**
*通过该控制器调用Feign中的微服务接口
* @param id
* @return
*/
@RequestMapping(value = "/consumer/payment/get/{id}")
public CommonResult<Payment> getPaymentbyid(@PathVariable("id")Long id){
return paymentFeignService.getPaymentbyid(id);
}
}
启动需要的微服务进行测试。openFegin中集成了ribbon自带了负载均衡的配置项。
从测试中可以看出采用的是轮询方式的负载均衡算法。
为了方便进行测试服务提供方8001,故意的写一个服务暂停的程序。
先在8001微服务的端口上增加添加一个超时的方法
@RequestMapping("/payment/feign/timeout")
public String paymentFeignTimeout(){
//暂停几秒钟线程
try{
TimeUnit.SECONDS.sleep(3);
}catch (InterruptedException e){
e.printStackTrace();
}
return serverPort;
}
服务消费方80添加超时方法PaymentFeignService
@GetMapping("/payment/feign/timeout")
public String paymentFeignTimeout();
服务消费方80的controller添加超时方法
package com.dzu.springcloud.controller;
import com.dzu.springcloud.entities.CommonResult;
import com.dzu.springcloud.entities.Payment;
import com.dzu.springcloud.service.PaymentFeignService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
@Slf4j
public class OrderFeignController {
@Resource
private PaymentFeignService paymentFeignService;
/**
*通过该控制器调用Feign中的微服务接口
* @param id
* @return
*/
@RequestMapping(value = "/consumer/payment/get/{id}")
public CommonResult<Payment> getPaymentbyid(@PathVariable("id")Long id){
return paymentFeignService.getPaymentbyid(id);
}
@RequestMapping("/consumer/payment/feign/timeout")
public String paymentFeignTimeout(){
//OpenFeign-ribbon 客户端一般默认等待1秒钟
return paymentFeignService.paymentFeignTimeout();
}
}
完成之后开启各个微服务进行测试,先直接的访问8001微服务进程中超时的方法。测试通过之后。使用客户端访问观察openFegin的超时报错页面。
http://localhost:8001/payment/feign/timeout
在通过客户端访问的时候,由于默认的时间为1s所以会报超时的错误。
默认Feign客户端只等待秒钟, 但是服务端处理需要超过1秒钟,导致Feign客户端不想等待了,直接返回报错。为了避免这样的情况,有时候我们需要设置Feign客户端的超时控制。
Yaml文件里需要开启OpenFeign客户端超时控制
#没置feign客户端超时时间(openFeign默认支持ribbon)
ribbon:
#指的是建立连接所用的时间,适用于网络状况iE常的情况下两端连接所用的时间
Readtimeout: 5000
#指的是建立连接后从服务器读取到可用资源所用的时间
ConnectTimeout: 5000