This article shows you how to use dubbo custom loads to achieve simple grayscale (user latitude, where some users access one service and the rest access the rest).

In fact, BEFORE this, I didn’t know much about Dubbo, just used it briefly and ran several demos. However, knowing that dubbo might be used for development in the future, I hastened to supplement relevant knowledge. I read the official website and bought a book entitled In-depth Understanding of Apache Dubbo In Practice. It feels pretty good.

1. Dubbo load balancing

Because the official website is very detailed, here is just a brief introduction. Dubbo load balancers include the following four types:

  • RandomLoadBalance: Default load policy, random load.
  • ConsistentHashLoadBalance: consistency Hash load.
  • Leastactive VeloadBalance: minimum active load.
  • RoundRobinLoadBalance: Polls load.

You can check the official: dubbo.apache.org/zh-cn/docs/…

The AbstractLoadBalance abstract class is derived from the AbstractLoadBalance abstract class.

2. Springboot-dubbo implements a custom load method

Using springboot-Dubbo custom loads is simple and can be divided into the following steps:

  • 1. Create a custom load class that inherits AbstractLoadBalance and overrides the doSelect method where algorithm rules are defined.
  • 2. Add the dubbo load extension points, in SRC/main/resources directory to create META INFO/dubbo directory, the directory to create org. Apache. Dubbo. RPC. Cluster. LoadBalance files, configuration inside the corresponding class load algorithm, As follows:
gray=com.dalaoyang.balance.GrayLoadBalance
Copy the code
  • 3. Used in the configuration file as follows:
dubbo.provider.loadbalance=gray
Copy the code

3. Simulated grayscale scheme and concrete implementation

3.1 Grayscale Scenario

For example, there are four service providers with port 9001,9002,9003,9004. Set the service of port 9002 to grayscale service. When the userid of the request consumer interface testUser is 1-10, the service will be forcibly forwarded to the provider in the grayscale state. The rest are still requested to the normal service, as shown in the figure.

3.2 Code Implementation

Then use code to simply implement the above scenario.

3.2.1 Service Provider

Take a look at the POM files, which are some springboot-Dubbo dependencies, as follows:

<? 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 > < the parent > < groupId > org. Springframework. Boot < / groupId > < artifactId > spring - the 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 - the 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> <version>2.7.2</version> <exclusions> <groupId>org.spring Framework </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> < the 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>
Copy the code

An interface is then exposed for use by the service consumer as follows:

package com.dalaoyang.api;

public interface UserService {

    String testUser(Long userId, String version);
}
Copy the code

Implementation class, the interface returns the corresponding port, dubbo port, as follows:

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 "Call successful, port is:" + port +
                ". The version number is: + version +
                ", user ID: + userId +
                ", dubbo port:"+ dubboPort; }}Copy the code

Create a GrayLoadBalance class that inherits AbstractLoadBalance with the following configuration:

  • The dubbo implicit parameter used by the currently requested userId (optionally).
  • Grayscale user lists are configured in the consumer configuration
  • A status attribute is configured in the service provider configuration to distinguish between a PROD service and a Gray service.
  • If there is no match, the random load policy is used for distribution.

After reading the above introduction, it is much easier to look at the code. Roughly, it is to extract the requested user ID and gray user ID set, determine whether it is gray user, if so, select gray service, as follows:

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(); }}}returnthis.randomSelect(list, url, invocation); } /** * Rewrote the random load policy ** @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)); }}Copy the code

Next in the SRC/main/resources/meta-inf/dubbo add org. Apache. Dubbo. RPC. Cluster. LoadBalance new extension points, content is as follows:

gray=com.dalaoyang.balance.GrayLoadBalance
Copy the code

Multiple profiles are used to start multiple service providers. The main profile application.properties reads as follows:

spring.profiles.active=test3
dubbo.provider.loadbalance=gray
Copy the code

Application-test1.properties:

# # the port number
server.port=9001

# # Dubbo configurationDubbo. Application. Name = dubbo_provider dubbo, registry. Address = zookeeper: / / 127.0.0.1:2181 dubbo. Protocol. The name = dubbo Dubbo. Protocol. The port = 9011 dubbo. Scan. The base - packages = com. Dalaoyang dubbo. The provider. The version = 2.0.0Copy the code

Application – test2. Properties content as follows, here many configuration dubbo. The provider. The parameters. The status = gray property is used to distinguish between gray services:

# # the port number
server.port=9002

# # Dubbo configurationDubbo. Application. Name = dubbo_provider dubbo, registry. Address = zookeeper: / / 127.0.0.1:2181 dubbo. Protocol. The name = dubbo Dubbo. Protocol. The port = 9012 dubbo. Scan. The base - packages = com. Dalaoyang dubbo. The provider. The version = 2.0.0 dubbo.provider.parameters.status=grayCopy the code

Application-test3. properties contents are as follows:

# # the port number
server.port=9003

# # Dubbo configurationDubbo. Application. Name = dubbo_provider dubbo, registry. Address = zookeeper: / / 127.0.0.1:2181 dubbo. Protocol. The name = dubbo Dubbo. Protocol. The port = 9013 dubbo. Scan. The base - packages = com. Dalaoyang dubbo. The provider. The version = 2.0.0Copy the code

Application-test4.properties contents are as follows:

# # the port number
server.port=9004

# # Dubbo configurationDubbo. Application. Name = dubbo_provider dubbo, registry. Address = zookeeper: / / 127.0.0.1:2181 dubbo. Protocol. The name = dubbo Dubbo. Protocol. The port = 9014 dubbo. Scan. The base - packages = com. Dalaoyang dubbo. The provider. The version = 2.0.0Copy the code

At this point, the service provider is created.

3.2.2 Service consumers

The service consumer is much simpler. The POM file introduces the service provider’s package in addition to the dubbo package, as follows:

<? 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 > < the parent > < groupId > org. Springframework. Boot < / groupId > < artifactId > spring - the 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 - the 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> <version>2.7.2</version> <exclusions> <groupId>org.spring Framework </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 - the SNAPSHOT < / version > < / dependency > < the dependency > < the 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> < the 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>

Copy the code

The grayscale userID list required above is configured in the configuration file as follows:

# # the port number
server.port=8881

# # Dubbo configurationdubbo.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. The parameters. The grayUserids = 1,2,3,4,5,6,7,8,9,10 dubbo. Provider. Loadbalance = gray dubbo, protocol, port = 10000Copy the code

Create a TestController, write a simple test class, and call the Dubbo service as follows:

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; // Grayscale user http://localhost:8881/testUser? UserId =3333&version=2.0.0 // Normal user 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() : "");
        returnuserService.testUser(userId, version); }}Copy the code

The service consumer is done here.

4. Test

4.1 Starting a Project

  • 1. Start the zookeeper
  • 2. Start the service provider. You can use IDEA to start multiple services, or package them with different configuration files.
  • 3. After the service provider is started, start the service consumer.

4.2 Page Request

If gray level of the state of the service started, visit http://localhost:8881/testUser? UserId =10&version=2.0.0, as shown in the figure.

If the grayscale service is not started, or the userID is not between 1 and 10, the following figure will be displayed.

[images]

5. The source code

This article related source code all uploaded to the code cloud, the address is gitee.com/dalaoyang/s…