Ribbon&Feign

Ribbon & Feign

第一节 Ribbon

1. Ribbon 介绍

Ribbon是Netflix发布的负载均衡器,它有助于控制HTTP和TCP的客户端的行为。为Ribbon配置服务提供者地址后,Ribbon就可基于某种负载均衡算法,自动地帮助服务消费者去请求。

2. Ribbon 作用

  • 在Spring Cloud中,当Ribbon与Eureka配合使用时,Ribbon可自动从Eureka Server获取服务提供者地址列表,并基于负载均衡算法,请求其中一个服务提供者实例。
  • 当集群里的1台或者多台服务器down的时候,剩余的没有down的服务器可以保证服务的继续使用。
  • 使用了更多的机器保证了机器的良性使用,不会由于某一高峰时刻导致系统cpu急剧上升。

3. Ribbon 与 Eureka 搭配结构图

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

4. Ribbon 应用

4.1 在所有的qf-user服务中添加服务接口
package com.qf.cloud.controller;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/user")
public class UserController {

    @Value("${server.port}")
    private int port;

    @GetMapping
    public String getUserInfo(){
        return "This user comes from " + port;
    }

}
4.2 在qf-goods中添加依赖
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
4.3 在qf-goods中编写控制器GoodsController
package com.qf.cloud.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/goods")
public class GoodsController {

    @GetMapping
    public String buyGoods(){
        //购买商品的时候需要获取用户信息


        return "";
    }
}
4.4 启动类GoodsServer中添加如下代码
@Bean
public RestTemplate restTemplate(){//服务之间用来通信使用的就是这个对象
    return new RestTemplate();
}
4.5 完善GoodsController
package com.qf.cloud.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
@RequestMapping("/goods")
public class GoodsController {

    @Autowired
    private RestTemplate restTemplate;

    @GetMapping
    public String buyGoods(){
        //购买商品的时候需要获取用户信息
        String userInfo = restTemplate.getForObject("http://localhost:2001/user", String.class);
        return userInfo + ", The method name is buyGoods";
    }
}
4.6 启动服务,在浏览器中访问
4.7 负载均衡

如果qf-user这样的服务被部署了多份,此时,应该如何来访问呢?

  • RestTemplate开启负载均衡功能

    @Bean
    @LoadBalanced //开启负载均衡
    public RestTemplate restTemplate(){//服务之间用来通信使用的就是这个对象
        return new RestTemplate();
    }
    
  • 使用服务名去获取信息

    @GetMapping
    public String buyGoods(){
        //购买商品的时候需要获取用户信息
        String userInfo = restTemplate.getForObject("http://USER-SERVICE/user", String.class);
        return userInfo + " buy goods";
    }
    
4.8 再次测试

第二节 Ribbon 核心组件 IRule

1. 类图

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

2. 使用说明

  • RoundRobinRule–轮询,默认规则
  • RandomRule–随机
  • AvailabilityFilteringRule --会先过滤掉由于多次访问故障处于断路器跳闸状态的服务,还有并发的连接数量超过阈值的服务,然后对于剩余的服务列表按照轮询的策略进行访问
  • WeightedResponseTimeRule–根据平均响应时间计算所有服务的权重,响应时间越快服务权重越大被选中的概率越大。刚启动时如果统计信息不足,则使用轮询的策略,等统计信息足够会切换到自身规则
  • RetryRule-- 先按照轮询的策略获取服务,如果获取服务失败则在指定的时间内会进行重试,获取可用的服务
  • BestAvailableRule --会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量小的服务
  • ZoneAvoidanceRule – 复合判断Server所在区域的性能和Server的可用行选择服务器。

3. 启动类中更改负载均衡算法

@Bean
public IRule rule(){
    return new RandomRule(); //负载均衡规则:随机
}

启动服务,在浏览器中访问,然后刷新,观察浏览器中内容变化

4 自定义负载均衡算法

4.1 自定义负载均衡规则IpHashRule
package com.qf.cloud.rule;

import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractLoadBalancerRule;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server;
import org.apache.commons.lang.StringUtils;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.Random;

public class IpHashRule extends AbstractLoadBalancerRule {
    @Override
    public void initWithNiwsConfig(IClientConfig iClientConfig) {
    }

    @Override
    public Server choose(Object o) {
        ILoadBalancer loadBalancer = this.getLoadBalancer();
        if(loadBalancer == null) return  null;
        List<Server> reachableServers = loadBalancer.getReachableServers();
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes sra = (ServletRequestAttributes) requestAttributes;
        HttpServletRequest request = sra.getRequest();
        String ip = getIpAddress(request);
        if(ip == null){
            Random r = new Random();
            int index = r.nextInt(reachableServers.size());
            return reachableServers.get(index);
        } else {
            int hash = ip.hashCode();
            //hash值可能很大 因此需要取模
            //比如 hash值为9876  server数量 5  9876 % 5
            int index = hash % reachableServers.size();
            return reachableServers.get(index);
        }
    }

    /**
     * 获取Ip地址
     * @return
     */
    private String getIpAddress(HttpServletRequest request) {
        //请求属性  从请求上下文持有者中获取一个请求属性,因为我们使用的是web请求,因此这个请求属性就是
        //一个Servlet请求属性
        String Xip = request.getHeader("X-Real-IP");
        String XFor = request.getHeader("X-Forwarded-For");
        if(StringUtils.isNotEmpty(XFor) && !"unKnown".equalsIgnoreCase(XFor)){
            //多次反向代理后会有多个ip值,第一个ip才是真实ip
            int index = XFor.indexOf(",");
            if(index != -1){
                return XFor.substring(0,index);
            }else{
                return XFor;
            }
        }
        XFor = Xip;
        if(StringUtils.isNotEmpty(XFor) && !"unKnown".equalsIgnoreCase(XFor)){
            return XFor;
        }
        if (StringUtils.isBlank(XFor) || "unknown".equalsIgnoreCase(XFor)) {
            XFor = request.getHeader("Proxy-Client-IP");
        }
        if (StringUtils.isBlank(XFor) || "unknown".equalsIgnoreCase(XFor)) {
            XFor = request.getHeader("WL-Proxy-Client-IP");
        }
        if (StringUtils.isBlank(XFor) || "unknown".equalsIgnoreCase(XFor)) {
            XFor = request.getHeader("HTTP_CLIENT_IP");
        }
        if (StringUtils.isBlank(XFor) || "unknown".equalsIgnoreCase(XFor)) {
            XFor = request.getHeader("HTTP_X_FORWARDED_FOR");
        }
        if (StringUtils.isBlank(XFor) || "unknown".equalsIgnoreCase(XFor)) {
            XFor = request.getRemoteAddr();
        }
        return XFor;
    }
}
4.2 配置自定义负载均衡规则
package com.qf.cloud.config;

import com.netflix.loadbalancer.IRule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.qf.cloud.rule.IpHashRule;

@Configuration
public class RibbonConfig {

    @Bean
    public IRule rule(){
        return new IpHashRule();
    }
}


package com.qf.cloud;

import com.qf.cloud.config.RibbonConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

@SpringBootApplication
@EnableEurekaClient
//Ribbon客户端配置 name表示该客户端需要消费的服务名 configuration表示该客户端的配置
@RibbonClient(name = "user-service", configuration = RibbonConfig.class)
public class GoodsServer {

    public static void main(String[] args) {
        SpringApplication.run(GoodsServer.class, args);
    }


    @Bean
    @LoadBalanced //开启负载均衡
    public RestTemplate restTemplate(){//服务之间用来通信使用的就是这个对象
        return new RestTemplate();
    }

//    @Bean
//    public IRule rule(){
//        return new RandomRule(); //负载均衡规则:随机
//    }
}

启动服务,在浏览器中访问,然后刷新,观察浏览器中内容变化

第三节 Feign

1. Feign 介绍

Feign是一个声明式的伪Http客户端,它使得写Http客户端变得更简单。使用Feign,只需要创建一个接口并注解。它具有可插拔的注解特性,可使用Feign 注解。Feign默认集成了Ribbon,并和Eureka结合,默认实现了负载均衡的效果。

2. Feign 的作用

旨在使编写Java Http客户端变得更容易。在使用Ribbon+RestTemplate时,利用RestTemplate对http请求的封装处理,形成了一套模板化的调用方法。但是在实际开发中,由于对服务器依赖的调用可能不止一处,往往一个接口会被多处调用,所以通常都会针对每个微服务自行封装一些客户端类来包装这些依赖服务的调用。所以,Feign在此基础上做了进一步封装,由它来帮助我们定义和实现依赖服务接口的定义。在Feign的实现下,我们只需要创建一个接口并使用注解的方式来配置它,即可完成对服务提供方的接口绑定,简化了使用Spring Cloud Ribbon时,自动封装服务调用客户端的开发量。

3. Feign 应用

3.1 创建工程 qf-goods02
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>qf-cloud</artifactId>
        <groupId>com.qf</groupId>
        <version>1.0</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>qf-goods02</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

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

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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>
</project>
3.2 编写启动类
package com.qf.cloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.openfeign.EnableFeignClients;

@SpringBootApplication
@EnableEurekaClient
//启用Feign客户端
//Feign客户端的感知是通过@SpringBootApplication注解来扫描的,如果扫描不到,那么需要使用@EnableFeignClients
//注解的basePackages属性来指定Feign端所处位置
@EnableFeignClients
public class GoodsServer02 {

    public static void main(String[] args) {
        SpringApplication.run(GoodsServer02.class, args);
    }
}
3.3 编写FeignClient接口
package com.qf.cloud.client;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;

//@FeignClient表示这是一个Feign客户端
//name属性表示该客户端访问的服务名称
//path表示访问的URL地址的前缀,相当于控制器上的RequestMapping中的value属性值
@FeignClient(name = "user-service", path = "/user")
public interface UserClient {

    @GetMapping
    String getUserInfo();
}
3.4 编写控制器类
package com.qf.cloud.controller;

import com.qf.cloud.client.UserClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/goods")
public class GoodsController {

    @Autowired
    private UserClient userClient;

    @GetMapping
    public String buyGoods(){
        //购买商品的时候需要获取用户信息
        String userInfo = userClient.getUserInfo();
        return userInfo + ", The method name is buyGoods";
    }
}
3.5 yml配置
server:
  port: 13001 #为了方便管理,服务提供者的端口设定在2000-3000
spring:
  application:
    name: goods-service #微服务的名称
eureka:
  instance:
    hostname: localhost
    instance-id: goods-service13001 # eureka中显示的微服务的实例名称
  client:
    service-url:
      #查询和注册服务的地址
      defaultZone: http://localhost:11000/eureka

启动服务,在浏览器中访问

3.6 FeignClient实现原理

在qf-goods工程中进行模拟

3.6.1 定义FeignClient注解
package com.qf.cloud.client;

import java.lang.annotation.*;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FeignClient {

    String name() default ""; //Feign客户端需要调用的服务的名称

    String path() default ""; //URL地址匹配前缀
}
3.6.2 定义UserClient接口
package com.qf.cloud.client;

import org.springframework.web.bind.annotation.GetMapping;

@FeignClient(name = "user-service", path="/user")
public interface UserClient {

    @GetMapping
    String getUserInfo();
}
3.6.3 编写客户端代理对象工具类
package com.qf.cloud.client;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.client.RestTemplate;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

@Component
public class FeignClientService {

    @Autowired
    private RestTemplate restTemplate;

    /**
     * 获取Feign客户端接口的代理对象,这里的实现方式类似Mybatis中的Mapper接口的代理对象的获取
     * @param clazz
     * @param <T>
     * @return
     */
    public <T> T getFeignClient(Class<T> clazz){
        if(!clazz.isInterface()) throw new IllegalArgumentException(clazz.getName() + " is not an interface");
        FeignClient client = clazz.getAnnotation(FeignClient.class);
        if(client == null) throw new IllegalArgumentException( clazz.getName() + "is not a feign client");
        ClassLoader loader = clazz.getClassLoader();
        Class[] interfaces = { clazz };
        InvocationHandler handler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //这里只需要两个参数: 一个是url地址, 一个是返回值类型
                String path = client.path();
                boolean isRequestMethod = false; //是否是匹配请求的方法
                GetMapping gm = method.getAnnotation(GetMapping.class);
                if(gm != null){
                    isRequestMethod = true;
                    String[] value = gm.value();
                    if(value.length == 1) path += value[0];
                    else if(value.length > 1) throw new IllegalArgumentException( clazz.getName() + "." + method.getName() + "匹配了多个URL地址");
                }
                PostMapping pm = method.getAnnotation(PostMapping.class);
                if(pm != null){
                    isRequestMethod = true;
                    String[] value = gm.value();
                    if(value.length == 1) path += value[0];
                    else if(value.length > 1) throw new IllegalArgumentException( clazz.getName() + "." + method.getName() + "匹配了多个URL地址");
                }
                PutMapping mapping = method.getAnnotation(PutMapping.class);
                if(mapping != null){
                    isRequestMethod = true;
                    String[] value = gm.value();
                    if(value.length == 1) path += value[0];
                    else if(value.length > 1) throw new IllegalArgumentException( clazz.getName() + "." + method.getName() + "匹配了多个URL地址");
                }
                DeleteMapping dm = method.getAnnotation(DeleteMapping.class);
                if(dm != null){
                    isRequestMethod = true;
                    String[] value = gm.value();
                    if(value.length == 1) path += value[0];
                    else if(value.length > 1) throw new IllegalArgumentException( clazz.getName() + "." + method.getName() + "匹配了多个URL地址");
                }
                if(!isRequestMethod) throw new IllegalArgumentException(clazz.getName() + "." + method.getName() + "不是处理请求的方法");
                String url = String.format("http://%s/%s", client.name(), path);
                Class returnType = method.getReturnType();
                return restTemplate.getForObject(url, returnType, args);
            }
        };
        return (T) Proxy.newProxyInstance(loader, interfaces, handler);
    }
}
3.6.4 GoodsController改造
package com.qf.cloud.controller;

import com.qf.cloud.client.FeignClient;
import com.qf.cloud.client.FeignClientService;
import com.qf.cloud.client.UserClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
@RequestMapping("/goods")
public class GoodsController {

//    @Autowired
//    private RestTemplate restTemplate;

    @Autowired
    private FeignClientService feignClientService;

    @GetMapping
    public String buyGoods(){
        //购买商品的时候需要获取用户信息
//        String userInfo = restTemplate.getForObject("http://localhost:2001/user", String.class);
        UserClient client = feignClientService.getFeignClient(UserClient.class);
        String userInfo = client.getUserInfo();
        return userInfo + ", The method name is buyGoods";
    }
}
3.6.5 GoodsServer改造
package com.qf.cloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

@SpringBootApplication
@EnableEurekaClient
public class GoodsServer {

    public static void main(String[] args) {
        SpringApplication.run(GoodsServer.class, args);
    }

    @Bean
    @LoadBalanced //使用服务名称去调用服务时,必须要使用负载均衡
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
}
RibbonFeign是用于调用其他服务的工具,但它们的使用方式和调用方式有一些区别。首先,在启动类中使用的注解不同,Ribbon使用的是@RibbonClient注解,而Feign使用的是@EnableFeignClients注解。其次,服务的指定位置也不同,Ribbon是在@RibbonClient注解上声明,而Feign是在定义抽象方法的接口中使用@FeignClient声明。最后,调用方式也不同,Ribbon需要自己构建http请求并使用RestTemplate发送给其他服务,而Feign则是在Ribbon的基础上进行了一次改进,采用接口的方式,将需要调用的其他服务的方法定义成抽象方法即可,不需要自己构建http请求。需要注意的是,抽象方法的注解和方法签名要和提供服务的方法完全一致。\[2\] Ribbon还提供了负载均衡的功能,它是基于Netflix Ribbon实现的一套客户端负载均衡的工具。Ribbon的客户端组件提供了一系列完整的配置项,如连接超时、重试等。在配置文件中列出LoadBalancer后面所有的机器,Ribbon会根据某种规则(如简单轮询、随机连接等)去连接这些机器。同时,我们也可以使用Ribbon实现自定义的负载均衡算法。\[3\] #### 引用[.reference_title] - *1* [RibbonFeign 的区别](https://blog.csdn.net/xiaojin21cen/article/details/86704954)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down28v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [ribbonfeign](https://blog.csdn.net/weixin_43493532/article/details/119332608)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down28v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [负载均衡-Ribbon&Feign](https://blog.csdn.net/m0_56017821/article/details/127193344)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down28v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值