本文介紹如何利用dubbo自定義負載實現簡單灰度(用戶緯度,部分用戶訪問一個服務,其他訪問剩餘服務)。html
其實在這以前,對dubbo瞭解的也不是不少,只是簡單的使用過,跑了幾個demo而已,可是得知接下來可能要用dubbo進行開發,仍是趕忙補了一下相關的知識,看了看官網,另外買了一本書《深刻理解Apache Dubbo實戰》,看了一大半,感受仍是很不錯的。java
由於官網介紹的很詳細了,這裏只簡單的說一下。dubbo負載均衡包含以下四種:git
能夠查看官方:dubbo.apache.org/zh-cn/docs/…web
這四個類都繼承了AbstractLoadBalance抽象類,源碼相關分析能夠查看官方:dubbo.apache.org/zh-cn/docs/…算法
springboot-dubbo使用自定義負載其實很簡單,大體分爲以下幾步:spring
gray=com.dalaoyang.balance.GrayLoadBalance
複製代碼
dubbo.provider.loadbalance=gray
複製代碼
如今模擬一個這樣的方案,好比有4個服務提供者,端口分別是9001,9002,9003,9004,將其中9002端口的服務設置爲灰度服務,當請求消費者接口testUser的userid爲1-10時,強制轉發到到灰度狀態的提供者去,其他的仍是請求到正常的服務,如圖所示。apache
接下來使用代碼簡單實現如上場景。api
首先看一下pom文件,都是一些springboot-dubbo的依賴,以下:springboot
<?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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.dalaoyang</groupId>
<artifactId>springboot_dubbo_provider</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot_dubbo_provider</name>
<description>springboot_dubbo_provider</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Aapche Dubbo -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>2.7.1</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
<version>2.7.2</version>
<exclusions>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring</artifactId>
</exclusion>
<exclusion>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
</exclusion>
<exclusion>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.8.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.curator/curator-recipes -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>2.8.0</version>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<exclusions>
<exclusion>
<artifactId>log4j</artifactId>
<groupId>log4j</groupId>
</exclusion>
<exclusion>
<artifactId>slf4j-log4j12</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
<version>3.4.10</version>
<type>pom</type>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
複製代碼
而後暴露一個接口,供服務消費者使用,以下:bash
package com.dalaoyang.api;
public interface UserService {
String testUser(Long userId, String version);
}
複製代碼
實現類,接口返回對應的端口,dubbo的端口,以下:
package com.dalaoyang.api.impl;
import com.dalaoyang.api.UserService;
import org.apache.dubbo.config.annotation.Service;
import org.springframework.beans.factory.annotation.Value;
@Service
public class UserServiceImpl implements UserService {
@Value("${server.port}")
private String port;
@Value("${dubbo.protocol.port}")
private String dubboPort;
@Override
public String testUser(Long userId, String version) {
return "調用成功,端口是:" + port +
"。版本號是:" + version +
",用戶id:" + userId +
",dubbo端口:" + dubboPort;
}
}
複製代碼
建立一個GrayLoadBalance繼承AbstractLoadBalance類,其中包含以下配置:
看完上面的簡介,在看代碼就容易了不少,大體就是取出請求的用戶id和灰度用戶id集合,判斷是不是灰度用戶,若是是,則選擇灰度服務,以下:
package com.dalaoyang.balance;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.rpc.Invocation;
import org.apache.dubbo.rpc.Invoker;
import org.apache.dubbo.rpc.cluster.loadbalance.AbstractLoadBalance;
import org.springframework.stereotype.Component;
import java.util.*;
import java.util.concurrent.ThreadLocalRandom;
@Component
public class GrayLoadBalance extends AbstractLoadBalance {
public static final String NAME = "gray";
public GrayLoadBalance() {
}
@Override
protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
List<Invoker<T>> list = new ArrayList<>();
for (Invoker invoker : invokers) {
list.add(invoker);
}
Map<String, String> map = invocation.getAttachments();
String userId = map.get("userId");
Iterator<Invoker<T>> iterator = list.iterator();
String grayUserIds = url.getParameter("grayUserids", "");
String[] arrs = grayUserIds.split(",");
while (iterator.hasNext()) {
Invoker<T> invoker = iterator.next();
String providerStatus = invoker.getUrl().getParameter("status", "prod");
if (Objects.equals(providerStatus, NAME)) {
if (Arrays.asList(arrs).contains(userId)) {
return invoker;
} else {
iterator.remove();
}
}
}
return this.randomSelect(list, url, invocation);
}
/**
* 重寫了一遍隨機負載策略
*
* @param invokers
* @param url
* @param invocation
* @param <T>
* @return
*/
private <T> Invoker<T> randomSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
int length = invokers.size();
boolean sameWeight = true;
int[] weights = new int[length];
int firstWeight = this.getWeight((Invoker) invokers.get(0), invocation);
weights[0] = firstWeight;
int totalWeight = firstWeight;
int offset;
int i;
for (offset = 1; offset < length; ++offset) {
i = this.getWeight((Invoker) invokers.get(offset), invocation);
weights[offset] = i;
totalWeight += i;
if (sameWeight && i != firstWeight) {
sameWeight = false;
}
}
if (totalWeight > 0 && !sameWeight) {
offset = ThreadLocalRandom.current().nextInt(totalWeight);
for (i = 0; i < length; ++i) {
offset -= weights[i];
if (offset < 0) {
return (Invoker) invokers.get(i);
}
}
}
return (Invoker) invokers.get(ThreadLocalRandom.current().nextInt(length));
}
}
複製代碼
接下來在src/main/resources/META-INF/dubbo下添加org.apache.dubbo.rpc.cluster.LoadBalance新增擴展點,內容以下:
gray=com.dalaoyang.balance.GrayLoadBalance
複製代碼
這裏使用了多配置文件來啓動多個服務提供者,主配置文件application.properties內容以下:
spring.profiles.active=test3
dubbo.provider.loadbalance=gray
複製代碼
application-test1.properties內容以下:
##端口號
server.port=9001
## Dubbo配置
dubbo.application.name=dubbo_provider
dubbo.registry.address=zookeeper://127.0.0.1:2181
dubbo.protocol.name=dubbo
dubbo.protocol.port=9011
dubbo.scan.base-packages=com.dalaoyang
dubbo.provider.version=2.0.0
複製代碼
application-test2.properties內容以下,這裏多配置了dubbo.provider.parameters.status=gray屬性用於區分灰度服務:
##端口號
server.port=9002
## Dubbo配置
dubbo.application.name=dubbo_provider
dubbo.registry.address=zookeeper://127.0.0.1:2181
dubbo.protocol.name=dubbo
dubbo.protocol.port=9012
dubbo.scan.base-packages=com.dalaoyang
dubbo.provider.version=2.0.0
dubbo.provider.parameters.status=gray
複製代碼
application-test3.properties內容以下:
##端口號
server.port=9003
## Dubbo配置
dubbo.application.name=dubbo_provider
dubbo.registry.address=zookeeper://127.0.0.1:2181
dubbo.protocol.name=dubbo
dubbo.protocol.port=9013
dubbo.scan.base-packages=com.dalaoyang
dubbo.provider.version=2.0.0
複製代碼
application-test4.properties內容以下:
##端口號
server.port=9004
## Dubbo配置
dubbo.application.name=dubbo_provider
dubbo.registry.address=zookeeper://127.0.0.1:2181
dubbo.protocol.name=dubbo
dubbo.protocol.port=9014
dubbo.scan.base-packages=com.dalaoyang
dubbo.provider.version=2.0.0
複製代碼
到這裏,服務提供者就建立完成了。
服務消費者就簡單不少,pom文件除dubbo對應包之外,引入服務提供者的包,以下:
<?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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.dalaoyang</groupId>
<artifactId>springboot_dubbo_consumer</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot_dubbo_consumer</name>
<description>springboot_dubbo_consumer</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Aapche Dubbo -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>2.7.1</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
<version>2.7.2</version>
<exclusions>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring</artifactId>
</exclusion>
<exclusion>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
</exclusion>
<exclusion>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.dalaoyang</groupId>
<artifactId>springboot_dubbo_provider</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.8.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.curator/curator-recipes -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>2.8.0</version>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<exclusions>
<exclusion>
<artifactId>log4j</artifactId>
<groupId>log4j</groupId>
</exclusion>
<exclusion>
<artifactId>slf4j-log4j12</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
<version>3.4.10</version>
<type>pom</type>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
複製代碼
配置文件中配置上述須要的灰度userid名單,以下:
## 端口號
server.port=8881
##Dubbo配置
dubbo.application.name=dubbo_consumer
dubbo.registry.address=zookeeper://localhost:2181
dubbo.scan.base-packages=com.dalaoyang.api
dubbo.consumer.version=2.0.0
dubbo.consumer.parameters.grayUserids=1,2,3,4,5,6,7,8,9,10
dubbo.provider.loadbalance=gray
dubbo.protocol.port=10000
複製代碼
建立一個TestController,編寫一個簡單的測試類,調用dubbo服務,內容以下:
package com.dalaoyang.controller;
import com.dalaoyang.api.UserService;
import org.apache.dubbo.config.annotation.Reference;
import org.apache.dubbo.rpc.RpcContext;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Objects;
@RestController
public class TestController {
@Reference
private UserService userService;
//灰度用戶 http://localhost:8881/testUser?userId=3333&version=2.0.0
//正經常使用戶 http://localhost:8881/testUser?userId=10&version=2.0.0
@GetMapping("/testUser")
public String testUser(Long userId, String version) {
RpcContext.getContext().setAttachment("userId", Objects.nonNull(userId) ? userId.toString() : "");
return userService.testUser(userId, version);
}
}
複製代碼
服務消費者到這裏也完成了。
若是灰度狀態的服務啓動的話,訪問http://localhost:8881/testUser?userId=10&version=2.0.0,如圖所示。
若是灰度狀態的服務沒有啓動,或者userid不在1-10之間的話會顯示以下圖所示。
[圖片]
本文相關源碼所有上傳到了碼雲上,地址是gitee.com/dalaoyang/s…