spring cloud組件之ribbon+自定義IRule+fegin

相關閱讀:
SpringBoot2.X快速構建和配置
微服務入門 spring cloud環境搭建
spring cloud組件之 eureka搭建、集羣、心跳

經過前面幾篇的學習我們已經學會了spring cloud的服務註冊,服務管理,但是我們如何從Eureka 服務配置中心獲取相應的服務,今天我們就學習下相關知識

1.ribbon是什麼?

Spring Cloud Ribbon是基於Netflix Ribbon實現的一套客戶端負載均衡的工具。
簡單的說,Ribbon是Netflix發佈的開源項目,主要功能是提供客戶端的軟件負載均衡算法,將Netflix的中間層服務連接在一起。Ribbon客戶端組件提供一系列完善的配置項如連接超時,重試等。簡單的說,就是在配置文件中列出Load Balancer(簡稱LB)後面所有的機器,Ribbon會自動的幫助你基於某種規則(如簡單輪詢,隨機連接等)去連接這些機器。我們也很容易使用Ribbon實現自定義的負載均衡算法。
客戶端負載均衡?? 服務端負載均衡??
我們用一張圖來描述一下這兩者的區別

在這裏插入圖片描述

這篇文章裏面不會去解釋nginx,如果不知道是什麼的話,可以看看下面幾個關於nginx的文檔
Windows下Nginx的啓動、停止、重啓等命令
Linux下nginx的安裝以及環境配置
nginx+tomcat 實現負載均衡部署

接着回來看看這張圖
服務端的負載均衡是一個url先經過一個代理服務器(這裏是nginx),然後通過這個代理服務器通過算法(輪詢,隨機,權重等等…)反向代理你的服務,l來完成負載均衡
而客戶端的負載均衡則是一個請求在客戶端的時候已經聲明瞭要調用哪個服務,然後通過具體的負載均衡算法來完成負載均衡

2.如何使用:

首先,我們還是要引入依賴,但是,eureka已經把ribbon集成到他的依賴裏面去了,所以這裏不需要再引用ribbon的依賴,如圖:
在這裏插入圖片描述

要使用ribbon,只需要一個註解:

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

在RestTemplate上面加入@LoadBalanced註解,這樣子就已經有了負載均衡, 怎麼來證明?
我們這裏現在啓動了eureka集羣(3個eureka) 和Power集羣(2個power) 和一個服務調用者(User)
在這裏插入圖片描述

但是我們的User僅僅只需要調用服務,不需要註冊服務信息,所以需要改一下配置文件:
配置什麼意思就不做過多解釋了,上面講eureka的時候有講到過

server:
  port: 8080
eureka:
  client:
    serviceUrl:
        defaultZone: http://localhost:3000/eureka,http://localhost:3001/eureka,http://localhost:3002/eureka  #eureka服務端提供的註冊地址 參考服務端配置的這個路徑
  instance:
    instance-id: user-1 #此實例註冊到eureka服務端的唯一的實例ID
    prefer-ip-address: true #是否顯示IP地址
    leaseRenewalIntervalInSeconds: 1 #eureka客戶需要多長時間發送心跳給eureka服務器,表明它仍然活着,默認爲30(與下面配置的單位都是秒)
    leaseExpirationDurationInSeconds: 3 #Eureka服務器在接收到實例的最後一次發出的心跳後,需要等待多久纔可以將此實例刪除,默認爲90秒

spring:
  application:
    name: client-user #此實例註冊到eureka服務端的name

然後啓動起來的頁面是這樣子的

在這裏插入圖片描述

我們能看見 微服務名:SERVER-POWER 下面有2個微服務(power-0,power-1),現在我們來通過微服務名調用這個服務
這是我們的user項目的調用代碼 :
controller代碼修改

@RestController
public class UserController {

    @Autowired
    private RestTemplate restTemplate;
    private static final String POWER_URL="http://SERVER-POWER";

    @RequestMapping("/getUser.do")
    public R getUser(){
        return R.success().set("user","zhangsan");
    }

    @RequestMapping("/getPower.do")
    public R getPower(){
        return R.success("操作成功",restTemplate.getForObject(POWER_URL+"/getPower.do",Object.class));
    }

//    @RequestMapping("/getPower.do")
//    public R getPower(){
//        return R.success("操作成功",restTemplate.getForObject("http://localhost:8081/getPower.do",Object.class));
//    }

}

由原來的ip訪問修改成Servername訪問的方式,這就是爲什麼同一個服務的servername是一樣的原因

我們來看看效果:

在這裏插入圖片描述
在這裏插入圖片描述
刷新會看到返回的data裏面的值變化power和power1,默認負載均衡的策略是輪詢

這裏可能有點抽象,需要你們自己去寫才能體會到,但是我們已經完成了負載均衡, 他默認的負載均衡是輪詢策略,也就是一人一次,下一節我們來講一下他還有哪些策略。

3.核心組件:IRule

IRule是什麼? 它是Ribbon對於負載均衡策略實現的接口, 怎麼理解這句話? 說白了就是你實現這個接口,就能自定義負載均衡策略, 自定義我們待會兒來講, 我們先來看看他有哪些默認的實現

在這裏插入圖片描述

這裏是ribbon負載均衡默認的實現, 由於是筆記的關係,這裏不好測試,只能你們自己去測試一下了, 具體怎麼使用呢?
看代碼:

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

在Spring 的配置類裏面把對應的實現作爲一個Bean返回出去就行了。

4.自定義負載均衡策略

我們剛剛講過,只要實現了IRule就可以完成自定義負載均衡,至於具體怎麼來,我們先看看他默認的實現

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.netflix.loadbalancer;

import com.netflix.client.config.IClientConfig;
import edu.umd.cs.findbugs.annotations.SuppressWarnings;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;

public class RandomRule extends AbstractLoadBalancerRule {
    public RandomRule() {
    }

    @SuppressWarnings({"RCN_REDUNDANT_NULLCHECK_OF_NULL_VALUE"})
    public Server choose(ILoadBalancer lb, Object key) {
        if (lb == null) {
            return null;
        } else {
            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 = this.chooseRandomInt(serverCount);
                server = (Server)upList.get(index);
                if (server == null) {
                    Thread.yield();
                } else {
                    if (server.isAlive()) {
                        return server;
                    }

                    server = null;
                    Thread.yield();
                }
            }

            return server;
        }
    }

    protected int chooseRandomInt(int serverCount) {
        return ThreadLocalRandom.current().nextInt(serverCount);
    }

    public Server choose(Object key) {
        return this.choose(this.getLoadBalancer(), key);
    }

    public void initWithNiwsConfig(IClientConfig clientConfig) {
    }
}

我們來看看這個類AbstractLoadBalancerRule

public abstract class AbstractLoadBalancerRule implements IRule, IClientConfigAware {

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

這裏我們能發現,還是我們上面所說過的 實現了IRule就能夠自定義負載均衡即使是他默認的策略也實現了IRule
我們可以直接把代碼copy過來改動一點:

package com.hrp.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.Random;
import java.util.concurrent.ThreadLocalRandom;

/**
 * MySpringCloud
 *
 * @Title: com.hrp.MyRule
 * @Date: 2020/5/23 18:52
 * @Author: wfg
 * @Description:原來是純隨機策略 我們現在改爲。
 * 如果一個下標已經被隨機到了2次了,第三次還是同樣的下標的話,那就再隨機一次
 * @Version:
 */
public class MyRule extends AbstractLoadBalancerRule {

    private Random rand;
    private int lastIndex = -1;
    private int nowIndex = -1;
    private int skipIndex = -1;
    public MyRule() {
        rand = new Random();
    }


    @SuppressWarnings({"RCN_REDUNDANT_NULLCHECK_OF_NULL_VALUE"})
    public Server choose(ILoadBalancer lb, Object key) {
        if (lb == null) {
            return null;
        } else {
            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 = this.chooseRandomInt(serverCount);


                System.out.println("當前下標爲:"+index);
                if (skipIndex>=0&&index == skipIndex) {
                    System.out.println("跳過");
                    index = rand.nextInt(serverCount);
                    System.out.println("跳過後的下標:"+index);
                }
                skipIndex=-1;

                nowIndex = index;
                if (nowIndex == lastIndex) {
                    System.out.println("下一次需要跳過的下標"+nowIndex);
                    skipIndex = nowIndex;
                }
                lastIndex = nowIndex;


                server = (Server)upList.get(index);
                if (server == null) {
                    Thread.yield();
                } else {
                    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 this.choose(this.getLoadBalancer(), key);
    }

    @Override
    public void initWithNiwsConfig(IClientConfig clientConfig) {
    }
}

這裏我們就把自己寫的Rule給new出來交給spring 就好了

@Bean
    public IRule iRule(){
        //return  new RandomRule();
        return new MyRule();
    }

具體測試的話就不測試了, 那個效果放在筆記上不太明顯,可以自己把代碼copy過去測試一下

4.1 不通的服務使用不同的負載均衡策略

我們有很多服務,比如現在userclient要調用powerserver和orderserver但是這2個的負載均衡策略我想使用不同的策略,畢竟每個服務的速度,服務環境等等不同

The CustomConfiguration class must be a @Configuration class, but take care that it is not in a @ComponentScan for the main application context. Otherwise, it is shared by all the @RibbonClients. If you use @ComponentScan (or @SpringBootApplication), you need to take steps to avoid it being included (for instance, you can put it in a separate, non-overlapping package or specify the packages to scan explicitly in the @ComponentScan).

這是官網的一句話,大概的意思是: 這個自定義的配置類必須有@Configuration 註解,但是不能在主程序的上下文中,也就是說@ComponentScan不能掃碼到,否則所有@RibbonClients將共享,如果你使用了@ComponentScan or @SpringBootApplication ,則需要採取步驟來避免包含它(例如,可以將它放在一個單獨的、不重疊的包中,或者在@ComponentScan中指定要顯式掃描的包)

第一步: 在com包外面創建配置類
在這裏插入圖片描述

@Configuration
public class CustomOrderIRule {

    @Bean
    public IRule iRule(){
        return new MyRule();
    }
}

@Configuration
public class CustomPowerIRule {

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

第二步: 客戶端啓動添加註解

在這裏插入圖片描述

@RibbonClients({
        @RibbonClient(name="SERVER-ORDER",configuration = CustomOrderIRule.class),
        @RibbonClient(name="SERVER-POWER",configuration = CustomPowerIRule.class)
})

在這裏插入圖片描述

controller裏面添加這2個方法,瀏覽器訪問getPower.do和getOrder.do 自己測試是使用不同的策略

5.feign負載均衡

private static final String POWER_URL=“http://SERVER-POWER”;
private static final String ORDER_URL=“http://SERVER-ORDER”;
從上面的代碼中我們也可以看出來,雖然RestTemplate已經封裝了http請求,但是我們每次調用接口前我們還需要先寫url,並且我們的服務會在不同的地方多次調用,感覺還是不夠方便,feign就是爲了讓我們的RestTemplat更簡便…

5.1 feign是什麼

Feign是一個聲明式WebService客戶端。使用Feign能讓編寫Web Service客戶端更加簡單, 它的使用方法是定義一個接口,然後在上面添加註解,同時也支持JAX-RS標準的註解。Feign也支持可拔插式的編碼器和解碼器。Spring Cloud對Feign進行了封裝,使其支持了Spring MVC標準註解和HttpMessageConverters。Feign可以與Eureka和Ribbon組合使用以支持負載均衡。

5.2 feign 能幹什麼

Feign旨在使編寫Java Http客戶端變得更容易。 前面在使用Ribbon+RestTemplate時,利用RestTemplate對http請求的封裝處理,形成了一套模版化的調用方法。但是在實際開發中,由於對服務依賴的調用可能不止一處,往往一個接口會被多處調用,所以通常都會針對每個微服務自行封裝一些客戶端類來包裝這些依賴服務的調用。所以,Feign在此基礎上做了進一步封裝,由他來幫助我們定義和實現依賴服務接口的定義。在Feign的實現下,我們只需創建一個接口並使用註解的方式來配置它(以前是Dao接口上面標註Mapper註解,現在是一個微服務接口上面標註一個Feign註解即可),即可完成對服務提供方的接口綁定,簡化了使用Spring cloud Ribbon時,自動封裝服務調用客戶端的開發量。

5.3 如何使用?

第一步:在客戶端(User)引入依賴:

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

第二步: 在啓動類上面加上註解:@EnableFeignClients
第三步: 然後編寫一個service接口加上@FeignClient()註解 參數就是你的微服務名字

package com.hrp.service;

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

/**
 * MySpringCloud
 *
 * @Title: com.hrp.service
 * @Date: 2020/5/24 6:31
 * @Author: wfg
 * @Description:
 * @Version:
 */
@FeignClient(name = "SERVER-POWER")
public interface PowerServiceClinet {

    @RequestMapping("/getPower.do")
    public Object getPower();
}

下面是調用代碼:

在這裏插入圖片描述
這裏拿了RestTemplate做對比 可以看看2者區別
Feign集成了Ribbon
利用Ribbon維護了服務列表信息,並且融合了Ribbon的負載均衡配置,也就是說之前自定義的負載均衡也有效,這裏需要你們自己跑一遍理解一下。而與Ribbon不同的是,通過feign只需要定義服務綁定接口且以聲明式的方法,優雅而簡單的實現了服務調用

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章