Original: Java Pie (wechat official account: Java Pie), welcome to share, reprint please reserve the source.

preface

Dubbo is alibaba’s open source RPC framework, which is used by many Internet companies because it supports load balancing, cluster fault tolerance, version control and other features based on interface development.

This article focuses on using timeout Settings and processing for analysis. Dubbo has three levels of timeout Settings:

  • Set the timeout for the method
  • Set the timeout on the server
  • Set the timeout at the caller

For details, see the official Dubbo documentation. What happens when the Dubbo call times out? Two things are known:

  1. The client will receive oneTimeoutExceptionabnormal
  2. The server receives a warningThe timeout response finally returned at xxx

This may seem normal, but the problem is that the server will continue to execute after the call times out. To demonstrate the timeout situation, a service is made:

@Service(version = "1.0")
@Slf4j
public class DubboDemoServiceImpl implements DubboDemoService {
    public String sayHello(String name) {
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        String result = "hello: " + name;
        log.info("Result: {}" , result);
        
        returnresult; }}Copy the code

The service is very simple and returns a string after three seconds. Then write a controller to call it:


@RestController
@RequestMapping
public class DubboDemoController {

    @Reference(url = "Dubbo: / / 127.0.0.1:22888? timeout=2000", version = "1.0")
    private DubboDemoService demoService;


    @GetMapping
    public ResponseEntity<String> sayHello(@RequestParam("name") String name){
        returnResponseEntity.ok(demoService.sayHello(name)); }}Copy the code

Connect to DubboDemoService Direct connection mode used by the service (dubbo://127.0.0.1:22888? Timeout =2000), the timeout times in the demo are specified by timeout in the URL.

Consumer timeout processing

When a timeout occurs, the client receives a TimeoutException that is dormant for 3 seconds in the sayHello implementation of the server:

public String sayHello(String name) { try { Thread.sleep(3000); } catch (InterruptedException e) { throw new RuntimeException(e); }... }Copy the code

A TimeoutException will be received if the timeout specified for connecting to the service is 2000ms:

There was an unexpected error (type=Internal Server Error, status=500). Invoke remote method timeout. method: sayHello, provider: Dubbo: / / 127.0.0.1:22888 / com. Example. The dubbo. Dubbodemo. Service. DubboDemoService? application=dubbo-demo&default.check=false&default.lazy=false&default.sticky=false& dubbo = 2.0.2 & interface = com. Example, dubbo. Dubbodemo. Service. DubboDemoService&lazy =false&methods=sayHello&pid=28662&qos.enable=false& register. IP = 192.168.0.103 & remote. Application = & revision & side = consumer&sticky = = 1.0false&timeout=2000&timestamp=1571800026289&version=1.0, cause: Waiting server-side response timeout. Start time: I: elapsed: 5 ms, server Elapsed: 2000 ms, timeout: 2000 ms, request: Request [id=4, version=2.0.2, twoway=true, event=false, broken=false, data=RpcInvocation [methodName=sayHello, parameterTypes=[class java.lang.String], arguments=[name], attachments={path=com.example.dubbo.dubbodemo.service.DubboDemoService, Interface = com. Example. Dubbo. Dubbodemo. Service. DubboDemoService, version = 1.0, the timeout = 2000}]], the channel: / 192.168.0.103:56446 - > / 192.168.0.103:22888Copy the code

Client timeout handling is relatively simple, since the occurrence of exceptions can also catch exceptions that should be rolled back or not handled, completely up to the developer to resolve.

try{
    return ResponseEntity.ok(demoService.sayHello(name));
}catch (RpcException te){
     //do something...
    log.error("consumer", te);
    return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR.value()).body("");
}
Copy the code

The focus is on resolving timeout exceptions on the server side.

Provider timeout handling

The Provider’s handling is not as simple as the client’s, because the Provider does not receive an exception and the thread does not interrupt, causing the Consumer to timeout and the Providerder to continue executing until the data is inserted successfully and the data is inconsistent.

In the demo project, the Provider method sleeps for 3000ms and the timeout parameter of the Consumer is 2000ms. The timeout occurs after 2000ms, while the Provider sayHello method does not break and prints Hello xx after 1000ms.

It is obvious that to maintain data consistency, the Provider must terminate or roll back execution after timeout. How to achieve data consistency?

Retry mechanism

Dubbo has a retry mechanism. If a call times out, a retry is initiated. The Provider must consider idempotency.

Final consistency

For final consistency using compensated transactions or asynchronous MQ, some business-neutral code is written to keep the data final consistency. For example, add a check method on the Provider side to check whether it succeeds. The specific implementation needs to be handled according to the service requirements.

 @GetMapping
public ResponseEntity<String> sayHello(@RequestParam("name") String name){
    try{
        return ResponseEntity.ok(demoService.sayHello(name));
    }catch (RpcException te){
         //do something...
        try{
            demoService.check(name);
        }catch (RpcException ignore){

        }
        log.error("consumer", te);
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR.value()).body(""); }}Copy the code

Although it is possible to add checks to verify the business state, the call execution time is not predictable, so this simple detection is not effective and is best done through MQ.

Rollback based on time

When invoking on the Consumer side, set two parameters, ctime and ttime, indicating the invocation time and timeout time respectively. Package the parameters and send them to the Provider for operation after receiving the two parameters. If the execution time exceeds ttime, the data will be rolled back. Modify our code:

 public ResponseEntity<String> sayHello(@RequestParam("name") String name){
        try{
            RpcContext context = RpcContext.getContext();
            context.setAttachment("ctime", System.currentTimeMillis() + "");
            context.setAttachment("ttime"2000 +,"");

            return ResponseEntity.ok(demoService.sayHello(name));
        }catch (RpcException te){
             //do something...
            log.error("consumer", te);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR.value()).body(""); }}Copy the code

Pass the ctime and ttime parameters to the Provider.

 public String sayHello(String name) {
        long curTime = System.currentTimeMillis();
        String ctime = RpcContext.getContext().getAttachment("ctime");
        String ttime = RpcContext.getContext().getAttachment("ttime");

        long ctimeAsLong = Long.parseLong(ctime);
        long ttimeAsLong = Long.parseLong(ttime);


        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        long spent = System.currentTimeMillis() - curTime;
        if(spent >= (ttimeAsLong - ctimeAsLong - curTime)){
            throw new RpcException("Server-side timeout.");
        }

        String result = "hello: " + name;
        log.info("Result: {}" , result);
        return result;
    }
Copy the code

Draw a picture of the timeline of execution:

This approach does not completely solve the Provider timeout problem because the response time is not calculated after execution.

conclusion

None of the methods mentioned in this paper can solve the Provider timeout problem well. In general, it is necessary to design the business code to reduce the call duration. Only by setting an accurate RPC call timeout time can this problem be solved better.