dubbo service grouping, current limiting measures and service fusing degradation

Keywords: Dubbo Apache Database Spring

Order module issues

1. Split the order module horizontally and vertically.

There will be more and more data in the order table in the e-commerce platform. In order to better business expansion, the database table needs to be split.

Horizontal split is divided into clothing order table, home appliance order table and other order tables according to different order types.
Split vertically by year, for example, one table in 2018 and one table in 2020.

After the database table is split, when the data needs to be looked up from multiple tables, it needs the feature service grouping and grouping aggregation provided by dubbo.

dubbo service group

An interface implements the query between different database tables, which can be distinguished by group in dubbo.

Server

Add the group attribute in the server, and customize the group name.

<dubbo:service group="feedback" interface="com.xxx.IndexService" />
<dubbo:service group="member" interface="com.xxx.IndexService" />
client

The group group provided by the server that the client needs to call is declared through the group attribute in the reference.

If the client only needs to call the feedback group, it only needs to declare the feedback group,

<dubbo:reference id="feedbackIndexService" group="feedback" interface="com.xxx.IndexService" />
<dubbo:reference id="memberIndexService" group="member" interface="com.xxx.IndexService" />

You can also call any implementation that can be grouped through group = "*".

<dubbo:reference id="barService" interface="com.foo.BarService" group="*" />
dubbo group aggregation

It's just grouping services, and dubbo also provides aggregation of the results of service calls. For example, the menu service is the same as the interface, but there are many implementations, which are differentiated by group. Now the consumer needs to call the result once again from each group, and the result of merging is returned, so that the aggregated menu item can be realized.

Client configuration

If you need to aggregate all groups, just set the group to * and add the merger property to true.

<dubbo:reference interface="com.xxx.MenuService" group="*" merger="true" />

Merge the specified group. If you only need to merge the results of aaa and bbb, the example is as follows:

<dubbo:reference interface="com.xxx.MenuService" group="aaa,bbb" merger="true" />

Specify the method merge result, other unspecified methods will only call one group.

<dubbo:reference interface="com.xxx.MenuService" group="*">
    <dubbo:method name="getMenuItems" merger="true" />
</dubbo:reference>

One method does not merge results, others merge results.

<dubbo:reference interface="com.xxx.MenuService" group="*" merger="true">
    <dubbo:method name="getMenuItems" merger="false" />
</dubbo:reference>

Specify the merge policy, which is automatically matched according to the return value type by default. If there are two mergers of the same type, you need to specify the name of the merger.

<dubbo:reference interface="com.xxx.MenuService" group="*">
    <dubbo:method name="getMenuItems" merger="mymerge" />
</dubbo:reference>

If the return value types of the two services are not the same, you need to specify the type of a combiner by the merger="mymerge".

The combiners provided by dubbo are:

org.apache.dubbo.rpc.cluster.merger.ArrayMerger
org.apache.dubbo.rpc.cluster.merger.ListMerger
org.apache.dubbo.rpc.cluster.merger.SetMerger
org.apache.dubbo.rpc.cluster.merger.MapMerger

2. How to ensure the launch of multiple versions of blue and green

In the formal environment, when the new version goes online, it will be released in the formal business together with the old version. In the formal environment, the stability of the new version will be tested in a small range for a period of time.
The isolation of services can be controlled by grouping, or by multiple versions in dubbo.

dubbo multi version

When an interface is implemented and an incompatible upgrade occurs, the version number can be used for transition, and services with different version numbers will not be referenced.

You can perform version migration as follows:

  • 0. In the low pressure period, upgrade half of the providers to the new version first.
  • 1. Then upgrade all consumers to the new version.
  • 2. Then upgrade the remaining half of the providers to the new version.

Old version service provider configuration:

<dubbo:service interface="com.foo.BarService" version="1.0.0" />

New version service provider configuration:

<dubbo:service interface="com.foo.BarService" version="2.0.0" />

Old version service consumer configuration:

<dubbo:reference id="barService" interface="com.foo.BarService" version="1.0.0" />

New version service consumer configuration:

<dubbo:reference id="barService" interface="com.foo.BarService" version="2.0.0" />

If you do not need to distinguish versions, you can configure [1] as follows:

<dubbo:reference id="barService" interface="com.foo.BarService" version="*" />

3. Service restriction

In the production environment, for example, the server can only handle 10000 connection requests. If there are more than 10000 connection requests, it will cause pressure on the server and make the system unavailable. In order to ensure the high availability of services, we need to restrict the flow of requests.

  • Current limiting measures are a highly available means of the system.
  • Use dubbo concurrency and connection control for current limiting. (not recommended in practice)
  • The leakage bucket method and token bucket algorithm are used to limit the flow.
Leaky bucket algorithm

All requests are saved in a queue bucket, and then the requests are taken out of the bucket at a fixed frequency.

Token Bucket

Here are two pictures from the Internet to describe the token bucket algorithm. The token is added to the bucket at a fixed frequency. If the bucket is full, the token is discarded.
When a request comes in, the token can be obtained from the bucket for access. If the token is not available, access is denied.



Difference between leaky bucket algorithm and token bucket algorithm:

If there is no request within 10 seconds, suddenly 10000 requests come in. The leaky bucket algorithm still requests at a fixed frequency. If there are 10000 tokens in the token algorithm bucket, all the 10000 requests will succeed. If there is any further request, it will be rejected. Token bucket algorithm has a good bearing capacity for peak traffic.

Example: simply implement a token bucket algorithm.

public class TokenBucket {
    //Barrel capacity
    private int bucketNums = 100;
    //Inflow velocity
    private int rate = 1;
    // Current number of tokens
    private int nowTokens;
    // time
    private long timestamp;

    private long getNowTime() {
        return System.currentTimeMillis();
    }

    private int min(int tokens) {
        if (bucketNums > tokens) {
            return tokens;
        } else {
            return bucketNums;
        }
    }
    public boolean getToke() {
        // Record the time to get the token
        long nowTime = getNowTime();
        //        Add token [judge how many tokens there should be]
        nowTokens = nowTokens + (int)((nowTime - timestamp) * rate);
        // The number of tokens added is smaller than the capacity of the bucket
        nowTokens = min(nowTokens);
        // Modify the time to get the token
        timestamp = nowTime;
        // Determine if the token is sufficient.
        if (nowTokens >= 1) {
            nowTokens -= 1;
            return true;
        } else {
            return false;
        }
    }

    public static void main(String[] args) {
        TokenBucket tokenBucker = new TokenBucket();
    }
}

Use in business: when the request comes in, get the token, get it successfully, and return true.

    private static TokenBucket bucket = new TokenBucket();
    /**
     *  Order tickets
     * @return
     */
    @RequestMapping(value = "buyTickets", method = RequestMethod.POST)
    public ResponseVO buyTickets(Integer fieldId, String soldSeats, String seatsName) {
        try {
            if (bucket.getToke()) {
                // Verify that the tickets sold are true.
                boolean trueSeats = orderServiceApi.isTrueSeats(fieldId + "", soldSeats);
                // Are there any of the seats that have been sold.
                boolean notSoldSeats = orderServiceApi.isNotSoldSeats(fieldId + "", soldSeats);
                // If one of the above two contents is not true, the order information will not be created.
                if (trueSeats && notSoldSeats) {
                    // Create order information
                    String userId = CurrentUser.getUserId();
                    if (StringUtils.isBlank(userId)) {
                        return ResponseVO.serviceFail("User not logged in!");
                    }
                    OrderVO orderVO = orderServiceApi.saveOrderInfo(fieldId, soldSeats, seatsName, userId);
                    if (orderVO == null) {
                        log.error("Abnormal ticket purchase business");
                        return ResponseVO.serviceFail("Abnormal ticket purchase business");
                    } else {
                        return ResponseVO.success(orderVO);
                    }
                } else {
                    log.error("Abnormal ticket purchase business");
                    return ResponseVO.serviceFail("There is a problem with the seat number in the order");
                }
            } else {
              return ResponseVO.serviceFail("Too many people have bought tickets. Please try again later!");
            }

        } catch (Exception e) {
           log.error("Abnormal ticket purchase business", e);
           return ResponseVO.serviceFail("Abnormal ticket purchase business");
        }

    }

4. Hystrix fuse degradation

In a multi service environment, for example, the order service will call multiple services, such as commodity service, user service and point service. If the commodity service hangs up or the service call times out, the order service will fail to place the order due to long-time requests, resulting in poor user experience. At this time, we can use the method of fuse degradation, instead of directly returning an exception to the user, we can return a bad network or other problems. Give the user a compromise and minimal impact scheme to return.

Example of hystrix application

1. Add dependency

  <!-- hystrix rely on -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
            <version>2.0.0.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
            <version>2.0.0.RELEASE</version>
        </dependency>

2. Start class add annotation

@EnableHystrixDashboard
@EnableCircuitBreaker
@EnableHystrix
@SpringBootApplication(scanBasePackages = {"com.stylefeng.guns"})
@EnableDubboConfiguration
@EnableAsync
@EnableHystrixDashboard
@EnableCircuitBreaker
@EnableHystrix
public class GatewayApplication {

    public static void main(String[] args) {
        SpringApplication.run(GatewayApplication.class, args);
    }
}

3. On the methods that need to be fused, add annotation configuration and add degraded callback methods.

    @HystrixCommand(fallbackMethod = "error", commandProperties = {
            @HystrixProperty(name = "execution.isolation.strategy", value = "THREAD"),
            @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "4000"),
            @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"),
            @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "50")
    },
            threadPoolProperties = { // hystrix middle route pool configuration
                    @HystrixProperty(name = "coreSize", value = "1"),
                    @HystrixProperty(name = "maxQueueSize", value = "10"),
                    @HystrixProperty(name = "keepAliveTimeMinutes", value = "1000"),
                    @HystrixProperty(name = "queueSizeRejectionThreshold", value = "8"),
                    @HystrixProperty(name = "metrics.rollingStats.numBuckets", value = "12"),
                    @HystrixProperty(name = "metrics.rollingStats.timeInMilliseconds", value = "1500")
            })
    @RequestMapping(value = "buyTickets", method = RequestMethod.POST)
    public ResponseVO buyTickets(Integer fieldId, String soldSeats, String seatsName) {
        try {


            if (bucket.getToke()) {
                // Verify that the tickets sold are true.
                boolean trueSeats = orderServiceApi.isTrueSeats(fieldId + "", soldSeats);
                // Are there any of the seats that have been sold.
                boolean notSoldSeats = orderServiceApi.isNotSoldSeats(fieldId + "", soldSeats);
                // If one of the above two contents is not true, the order information will not be created.
                if (trueSeats && notSoldSeats) {
                    // Create order information
                    String userId = CurrentUser.getUserId();
                    if (StringUtils.isBlank(userId)) {
                        return ResponseVO.serviceFail("User not logged in!");
                    }
                    OrderVO orderVO = orderServiceApi.saveOrderInfo(fieldId, soldSeats, seatsName, userId);
                    if (orderVO == null) {
                        log.error("Abnormal ticket purchase business");
                        return ResponseVO.serviceFail("Abnormal ticket purchase business");
                    } else {
                        return ResponseVO.success(orderVO);
                    }
                } else {
                    log.error("Abnormal ticket purchase business");
                    return ResponseVO.serviceFail("There is a problem with the seat number in the order");
                }
            } else {
                return ResponseVO.serviceFail("Too many people have bought tickets. Please try again later!");
            }

        } catch (Exception e) {
            log.error("Abnormal ticket purchase business", e);
            return ResponseVO.serviceFail("Abnormal ticket purchase business");
        }
    }

The custom fuse method is consistent with the value of the fallbackMethod property and the defined method name.

    /**
     *  Note on which method, return value and method parameter must be consistent.
     * @param fieldId
     * @param soldSeats
     * @param seatsName
     * @return
     */
    public ResponseVO error(Integer fieldId, String soldSeats, String seatsName) {
        return ResponseVO.serviceFail("Sorry, there are too many next people,Please try again later!");
    }

Posted by alpha_juno on Sat, 20 Jun 2020 01:55:04 -0700