第五节:SpringCloud Ribbon与Feign

Ribbon

1:什么是Ribbon

①:什么是客户端的负载均衡

进程内的LB(Load Balancer),他是一个类库集成到消费端,通过消费端进行获取提供者的地址
生活中:类似与你去火车站排队进站(有三条通道),只要是正常人,都会排队到人少的队伍中去.
程序中: 我们消费端 能获取到服务提供者地址列表,然后根据某种策略去获取一个地址进行调用.
第五节:SpringCloud Ribbon与Feign

②:什么是服务端的负载均衡

1.2) 什么是服务端的负载均衡?

生活中:类似与你去火车站排队进站的时候,有一个火车站的引导员告诉你说三号通道人少,你去三号通道排队。
程序中:就是你消费者调用的是ng的地址,由ng来选择 请求的转发(轮询 权重,ip_hash等)
第五节:SpringCloud Ribbon与Feign

2:如何快速整合ribbon

第一步:搭建二个eureka的集群

参考第四节Eureka

第二步:把ribbon集成到消费端

①:加入依赖

<!-- eureka 客户端的依赖(其实他集成了ribbon的依赖)-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    <!-- 集成ribbon的依赖 -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
    </dependency>

②:写application.yml的配置文件

server:
  port: 9004
  servlet:
    context-path: /

spring:
  application:
    #注册到eureka服务端的微服务名称
    name: eureka-ribbon-client
  datasource:
    username: root
    password: 123456
    url: jdbc:mysql://127.0.0.1:3306/yinz
    driver-class-name: com.mysql.cj.jdbc.Driver
    
eureka:
  client:
  service-url:
    #注册到eureka服务端的地址
    defaultZone: http://localhost:9000/eureka/

    defaultZone=http://www:
      eureka9000:
        com:9000/eureka/,http://www:
          eureka9001:
            com:9001/eureka/​:instance:
  #点击具体的微服务,右下角是否显示ip
  prefer-ip-address: true
  #显示微服务的名称
  instance-id: eureka-ribbon-client-9004


③:修改调用配置

package com.ribbon.client.conf;

import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class MyConfig {

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

package com.ribbon.client.controller;

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

@RestController
public class RibbonController {

    @Autowired
    private RestTemplate restTemplate;

    @RequestMapping("/ribbon")
    public String ribbon() {
        //通过微服务实例名称进行调用
        return restTemplate.getForObject("http://EUREKA-CLIENT2/eurekaClient2Test", String.class);
    }
}

第三步:搭建服务提供者

即其他客服端提供的服务,这里使用yinz-eureka-client的服务
测试地址:http://localhost:9004/ribbon

3:如何使用ribbon进行负载均衡测试

拷贝yinz-ribbon-client客服端两份,修改端口,定义相同的服务,打印不同的日志测试调用

4:测试Ribbon的负载均衡策略(默认的是轮询)#

关闭某台服务,测试宕机

5:Ribbon核心知识点

Ribbon所包含的负载均衡策略分类

**RandomRule:**随机选择一个Server
**RetryRule:**对选定的负载均衡策略机上重试机制,在一个配置时间段内当选择Server不成功,则一直尝试使用subRule的方式选择一个可用的server
**RoundRobinRule:**轮询选择, 轮询index,选择index对应位置的Server
**AvailabilityFilteringRule:**过滤掉一直连接失败的被标记为circuit tripped的后端Server,并过滤掉那些高并发的后端Server或者使用一个AvailabilityPredicate来包含过滤server的逻辑,其实就就是检查status里记录的各个Server的运行状态
**BestAvailableRule:**选择一个最小的并发请求的Server,逐个考察Server,如果Server被tripped了,则跳过。
**WeightedResponseTimeRule:**根据响应时间加权,响应时间越长,权重越小,被选中的可能性越低;
**ZoneAvoidanceRule:**复合判断Server所在区域的性能和Server的可用性选择Server

如何修改默认负载均衡的策略

写一个配置类来修改ribbon的负载均衡的配置

@Configuration
public class MyConfig {

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }


    /**
     * 随机算法的负载均衡
     * @return
     */
    @Bean
    public IRule MyRule() {
        //return new RandomRule();
        return new RetryRule();
    }
}

如何来自定义负载均衡策略

引用规则,使用@RibbonClient指定负载均衡策略

@SpringBootApplication
@EnableDiscoveryClient
@RibbonClient(name = "EUREKA-CLIENT2",configuration = MyRule.class)
public class RibbonClientApplication {

自定义的负载均衡策略不能写在@SpringbootApplication注解的@CompentScan扫描得到的地方

自定义负载均衡策略
原生的RandomRule策略解析

public class RandomRule extends AbstractLoadBalancerRule {
    Random rand;

    public RandomRule() {
        rand = new Random();
    }


    @edu.umd.cs.findbugs.annotations.SuppressWarnings(value = "RCN_REDUNDANT_NULLCHECK_OF_NULL_VALUE")
    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) {
                /*
                 * No servers. End regardless of pass, because subsequent passes
                 * only get more restrictive.
                 */
                return null;
            }
 
			//随机获取一个服务下标
            int index = rand.nextInt(serverCount);
			//获取一个服务列表
            server = upList.get(index);

            if (server == null) {
                /*
                 * The only time this should happen is if the server list were
                 * somehow trimmed. This is a transient condition. Retry after
                 * yielding.
                 */
                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;

    }

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

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

自定义Rule

package com.config;

import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractLoadBalancerRule;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

/**
 * 自定义的随机策略
 * Created by smlz on 2019/4/8.
 */
public class MyRule extends AbstractLoadBalancerRule {

    Random rand;

    public MyRule () {
        rand = new Random();
    }

    private int currentIndex = 0;

    private List<Server> currentChooseList = new ArrayList<Server>();

    /**
     * Randomly choose from all living servers
     */
    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 = rand.nextInt(serverCount);

            //当前轮询的次数小于等于5
            if(currentIndex<5) {
                //保存当前选择的服务列表ip
                if(currentChooseList.isEmpty()) {
                    currentChooseList.add(upList.get(index));
                }
                //当前的++
                currentIndex++;
                //返回保存的
                return currentChooseList.get(0);
            }else {
                currentChooseList.clear();
                currentChooseList.add(0,upList.get(index));
                currentIndex=0;
            }


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

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

            server = null;
            Thread.yield();
        }

        return server;

    }

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

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

    }
}

Feign

1:什么是Feign

Feign是Netflix开发的声明式、模板化的HTTP客户端,其灵感来自Retrofit、JAXRS-2.0以及WebSocket。Feign可帮助我们更加便捷、优雅地调用HTTP API。
在Spring Cloud中,使用Feign非常简单——只需创建接口,并在接口上添加注解即可。
Feign支持多种注解,例如Feign自带的注解或者JAX-RS注解等。Spring Cloud对Feign进行了增强,使其支持Spring MVC注解,另外还整合了Ribbon和Eureka,从而使得Feign的使用更加方便

eg: 以前我们调用dao的时候 ,我们是不是一个接口加 注解然后在service中就可以进行调用了
@Mapper
public interface OrderDao {
List queryOrdersByUserId(Integer userId);
}

2:Feign VS Ribbon

Ribbon+RestTemplate进行微服务调用缺点:
ResponseEntity responseEntity = restTemplate.getForEntity(“http://order-service/order/queryOrdersByUserId/”+userId,List.class);

①:我们不难发现,我们构建上诉的URL 是比较简单的,假如我们业务系统十分复杂,类似如下节点
https://www.baidu.com/s?wd=asf&rsv_spt=1&rsv_iqid=0xa25bbeba000047fd&issp=1&f=8&rsv_bp=0&rsv_idx=2&ie=utf-8&tn=baiduhome_pg&rsv_enter=1&rsv_sug3=3&rsv_sug1=2&rsv_sug7=100&rsv_sug2=0&inputT=328&rsv_sug4=328

那么我们构建这个请求的URL是不是很复杂,若我们请求的参数有可能变动,那么是否这个URL是不是很复杂

②:如果系统业务非常复杂,而你是一个新人,当你看到这行代码,恐怕很难一眼看出其用途是什么!此时,你很可能需要寻求老同事的帮助(往往是这行代码的作者,哈哈哈,可万一离职了呢?),或者查阅该目标地址对应的文档(文档常常还和代码不匹配),才能清晰了解这行代码背后的含义!否则,你只能陷入蛋疼的境地!

3:工程搭建

1、加入依赖

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

2、定义服务

// 正接口上的name指定微服务的名称,  path是指定服务消费者的调用前缀
@FeignClient(name = "ms-provider-user",path = "/user")
public interface OrderApi {

    @RequestMapping("/queryUserById/{userId}")
    public String queryUserById(@PathVariable("userId") Integer userId);
}

将yinz-feign-client 打成jar

3、在消费端依赖jar

<dependency>
	<groupId>com.tuling</groupId>
	<artifactId>yinz-feign-client</artifactId>
	<version>0.0.1-SNAPSHOT</version>
</dependency>

4、在消费端主启动类商添加

@EnableFeignClients(basePackages = "com.yinz")

3、如何自定义Feign

1:自定义日志级别

日志级别:
NONE:【性能最佳,适用于生产】:不记录任何日志(默认值)

public class MyConfig{
    @Bean
    public Logger.Level feignLoggerLevel() {
        return Logger.Level.NONE;
    }
}

HEADERS:记录BASIC级别的基础上,记录请求和响应的header

public class MyConfig {
    @Bean
    public Logger.Level feignLoggerLevel() {
        return Logger.Level.HEADERS;
    }
}

FULL【比较适用于开发及测试环境定位问题】:记录请求和响应的header、body和元数据

public class MyConfig {
   @Bean
    public Logger.Level feignLoggerLevel() {
        return Logger.Level.FULL;
    }
}

代码中如何自定义

public class MyConfig {
    @Bean
    public Logger.Level feignLoggerLevel() {
        return Logger.Level.BASIC;
    }
}

@FeignClient(name = "ms-provider-user",configuration=MyConfig.class,path = "/user")
public interface OrderApi {

    @RequestMapping("/queryUserById/{userId}")
    public String queryUserById(@PathVariable("userId") Integer userId);
}

yml中如何配置:

feign.client.config.ms-provider-order-feign-custom01.loggerLevel=full

如何支持feign的注解来替换springmvc的注解

代码配置

public class MyConfig {
    // 修改协议契约
    @Bean
    public Contract feignContract() {
        return new Contract.Default();
    }
}

@FeignClient(name = "ms-provider-user",configuration=MyConfig.class,path = "/user")
public interface OrderApi {
    @RequestMapping("/queryUserById/{userId}")
    public String queryUserById(@PathVariable("userId") Integer userId);
}

配置文件配置

feign.client.config.ms-provider-order-feign-custom01.contract=feign.Contract.Default

创建拦截器设置公用参数实现:RequestInterceptor

编写拦截器:

public class MyInterceptor implements RequestInterceptor {
    @Override
    public void apply(RequestTemplate template) {
        System.out.println("自定义拦截器");
        template.header("token","123456");
    }
}

加入拦截器配置
代码配置

public class MyCfg {
    @Bean
    public RequestInterceptor myInterceptor () {
        return new MyInterceptor();
    }
}

Yml配置

feign.client.config.ms-provider-order-feign-custom01.requestInterceptors[0]=com.yinz.client.MyInterceptor

服务提供端获取公共参数
第五节:SpringCloud Ribbon与Feign

上一篇:Ribbon简介


下一篇:微服务服务调用-Ribbon的简单使用