SpringCloud-3-Ribbon

SpringCloud ---- Ribbon

1. Ribbon概述

  • Ribbon是基於Netflix Ribbon 實現的一套客戶端負載均衡的工具java

  • 負載均衡LB (Load Balance),簡單的來講就是將用戶的請求平攤的分配到多個服務上, 從而達到系統的HA(高可用)mysql

  • 負載均衡的分類web

    1. 集中式負載均衡,客戶端和服務端使用獨立的負載均衡措施(硬件的F5,軟件的Nginx)

    1149971-20190617115318385-271620277

    1. 進程內負載均衡,將負載均衡邏輯集成在客戶端組件中,客戶端從註冊中心獲取可用服務,而後再從服務地址中選擇合適的服務端發起請求(Ribbon

1149971-20190617115552199-963109768

  • Ribbon要從註冊中心獲取可用的服務, 同時是集成在客戶端, 所以, 咱們要在consumer中進行操做, 依賴要包括ribbon和eureka

2. 使用Ribbon實現負載均衡

因爲Ribbon是在客戶端進行負載均衡的操做, 所以咱們只須要操做consumer服務就好了算法

1. 導入依賴

<!--Ribbon-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-ribbon</artifactId>
    <version>1.4.6.RELEASE</version>
</dependency>
<!--Eureka-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-eureka</artifactId>
    <version>1.4.6.RELEASE</version>
</dependency>

注意spring

  • Ribbon要去Eureka中發現可用的服務, 所以除了引入Ribbon依賴之外, 也要引入Eureka的依賴

2. 配置Config

package com.wang.springcloud.config;

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 ConfigBean {

    //配置負載均衡實現RestTemplate
    @LoadBalanced   //Ribbon
    //註冊RestTemplate
    @Bean
    public RestTemplate getRestTemplate() {
        return new RestTemplate();
    }

}

注意sql

  • Ribbon利用RestTemplate的URL實現負載均衡, 所以要在註冊RestTemplate時開啓Ribbon對RestTemplate的負載均衡的支持, 這裏加一個 @LoadBalanced 註解就能夠了

3. 改寫controller

package com.wang.springcloud.controller;

import com.wang.springcloud.pojo.Dept;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import java.util.List;

@ApiModel("Consumer Controller")
@RestController
public class DeptConsumerController {
    //消費者, 不該該有service層!(爲了實現解耦)
    //RestFul模板 ==> RestTemplate, 裏面有不少方法供咱們直接調用, 須要註冊到Spring中
    //遠程調用provider的url

    //在ConfigBean中註冊了, 這裏能夠直接自動裝配(AutoWired是按照類型自動裝配)
    //RestTemplate的方法 ==> url, 請求的實體(request), 響應的類型(response)Class<T>
    //RestTemplate實質上就是提供多種便捷訪問遠程http服務的方法, 是一個簡單的RestFul服務模板
    @Autowired
    private RestTemplate restTemplate;

    //經過Ribbon, 咱們這裏的地址應該是一個變量, 不要寫死, 經過服務名來訪問(provider的spring.applicatoion.name的大寫), 不要忘了寫http://
    private static final String REST_URL_PREFIX = "http://SPRINGCLOUD-PROVIDER-DEPT";
    @ApiOperation("經過部門編號查詢一個部門")
    @RequestMapping(value = "/consumer/dept/get/{id}", method = RequestMethod.GET)
    public Dept get(@PathVariable("id") @ApiParam("部門編號") Long id) {
        //因爲咱們在provider中的list是get方法, 這裏調用getForObject方法
        return restTemplate.getForObject(REST_URL_PREFIX + "/dept/get/" + id, Dept.class);
    }

    @ApiOperation("經過部門名稱添加一個部門")
    @RequestMapping(value = "/consumer/dept/add", method = RequestMethod.POST)
    public boolean add(@ApiParam("部門的名稱") Dept dept){
        return restTemplate.postForObject(REST_URL_PREFIX + "/dept/add", dept, Boolean.class);
    }

    @ApiOperation("查詢所有的部門")
    @RequestMapping(value = "consumer/dept/list", method = RequestMethod.GET)
    public List<Dept> list() {
        return restTemplate.getForObject(REST_URL_PREFIX + "/dept/list", List.class);
    }
}

注意數據庫

  • 這裏的核心代碼是app

    private static final String REST_URL_PREFIX = "http://SPRINGCLOUD-PROVIDER-DEPT";

    經過服務名來訪問provider負載均衡

  • 因爲是經過服務名來訪問provider, 所以作provider的被發現的服務要有相同的 spring.applicatoion.name運維

  • 這裏本質上仍是url, 不要忘了寫 http://

4. 配置application

server:
  port: 8080

#Eureka配置
eureka:
  client:
    register-with-eureka: false #咱們這裏是消費者, 是客戶端, 所以不須要想Eureka中註冊本身
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/

注意

  • 這裏因爲用到了Eureka進行服務的註冊與發現, 要配置Eureka
  • 咱們這裏是消費者, 是客戶端, 所以不須要想Eureka中註冊本身, 要配置 register-with-eureka: false

5. 配置主啓動類

package com.wang.springcloud;

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

//Ribbon 和 Eureka 整合之後, 客戶端能夠直接調用, 不用關心IP地址和端口號
@SpringBootApplication
@EnableEurekaClient
public class DeptConsumer_8080 {
    public static void main(String[] args) {
        SpringApplication.run(DeptConsumer_8080.class, args);
    }
}

注意

  • 添加 @EnableEurekaClient 註解, 代表他是一個Eureka的客戶端, 能夠使用Eureka進行服務的發現

6. 添加服務的提供者

這裏添加了三個服務的提供者, 端口分別爲8001, 8002, 8003

image-20201012095135025

注意

  • 三個服務的提供者內容基本相同, 由於要作負載均衡

  • 注意要修改application的配置, 主要是端口號

  • 因爲Ribbon是經過服務的名稱進行發現並負載均衡的, 注意這裏的Spring的名字要相同, 即

    #Spring的配置
    spring:
      application:
        name: springcloud-provider-dept   #三個服務名稱一致是前提!
  • 這裏以第三個數據庫爲例, 給出建庫的sql代碼

    create database /*!32312 IF NOT EXISTS*/`db03` /*!40100 DEFAULT CHARACTER SET utf8mb4 */;
    use `db03`;
    /*Table structure for table `dept` */
    
    drop table if exists `dept`;
    
    create table `dept` (
      `deptno` bigint(20) not null auto_increment,
      `dname` varchar(60) default null,
      `db_source` varchar(60) default null,
      primary key (`deptno`)
    ) engine=innodb auto_increment=1 default charset=utf8mb4 comment='部門表';
    
    /*Data for the table `dept` */
    
    insert into dept (dname, db_source) values ('開發部', database());
    insert into dept (dname, db_source) values ('人事部', database());
    insert into dept (dname, db_source) values ('財務部', database());
    insert into dept (dname, db_source) values ('市場部', database());
    insert into dept (dname, db_source) values ('運維部', database());

3. 自定義負載均衡算法

1. Ribbon的一些自定義策略

  • Ribbon的策略實現的接口時IRule
  • 一些策略
    • AvailabilityFilteringRule 會先過濾掉跳閘或者訪問故障的服務
    • RoundRobinRule 輪詢, 默認的
    • RandomRule 隨機
    • RetryRule 會先按照輪詢獲取服務, 若是服務獲取失敗, 則會在指定的時間內進行重試

2. 使用隨機策略

@Bean
public IRule myRule() {
    return new RandomRule();
}

注意

  • Ribbon默認的策略爲輪詢算法
  • 想要更改成Ribbon內置的其餘算法, 只須要將其內置的策略註冊進Bean就行了
  • 類型爲IRule, 返回值爲想要用的內置的策略

3. 自定義策略

1. 創建自定義策略類

image-20201012102309090

注意

  • 自定義策略選擇不要與主啓動類同級, 不然會被主啓動類掃描到, 這樣全部的服務都使用了一樣的策略

2. 配置主啓動類

package com.wang.springcloud;

import com.wang.myRule.WangRule;
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;

//Ribbon 和 Eureka 整合之後, 客戶端能夠直接調用, 不用關心IP地址和端口號
@SpringBootApplication
@EnableEurekaClient
//在微服務啓動的時候, 就能夠去加載咱們自定義的Ribbon類
@RibbonClient(name = "SPRINGCLOUD-PROVIDER-DEPT", configuration = WangRule.class)
public class DeptConsumer_8080 {
    public static void main(String[] args) {
        SpringApplication.run(DeptConsumer_8080.class, args);
    }
}

注意

  • 這裏添加了 @RibbonClient(name = "SPRINGCLOUD-PROVIDER-DEPT", configuration = WangRule.class) 註解, 指定了服務的名稱. 同時, 因爲咱們自定義的策略與SpringBoot的主啓動類不在一級, 不會被掃描到, 這裏須要指定策略的類

3. 編寫本身的策略

package com.wang.myRule;

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

import java.util.List;
import java.util.concurrent.ThreadLocalRandom;

public class WangRandomRule extends AbstractLoadBalancerRule {

    /**
     * 每一個服務訪問5次, 換下一個服務
     *
     * total=0, 默認=0, 若是=5, 咱們指向下一個服務節點
     * index=0, 默認0, 若是total=5, index+1
     */
//    @edu.umd.cs.findbugs.annotations.SuppressWarnings(value = "RCN_REDUNDANT_NULLCHECK_OF_NULL_VALUE")

    private int total = 0;      //總共被調用的次數

    private int currentIndex = 0;   //當前是誰在提供服務

    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);
//            //從活着的服務中隨機獲取一個
//            server = upList.get(index);

            //-============================================

            if (total < 5) {
                server = upList.get(currentIndex);
                total++;
            } else {
                total = 0;
                currentIndex++;
                //index從0開始, list的大小要-1, 這裏用 >=
                if (currentIndex >= upList.size()) {
                    currentIndex = 0;
                }
                //從活着的服務中, 獲取指定的服務來進行操做
                server = upList.get(currentIndex);
            }


            //-============================================

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

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

            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);
    }

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

    }
}

4. 註冊自定義策略

package com.wang.myRule;

import com.netflix.loadbalancer.IRule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class WangRule {

    @Bean
    public IRule myRule() {
        return new WangRandomRule();
    }

}
相關文章
相關標籤/搜索
本站公眾號
   歡迎關注本站公眾號,獲取更多信息