Third-party interface calls abnormal compensation mechanism to achieve record instance

background:

Our assembly (abbreviated A), belonging to the data service support node in the chain. Wherein the presence of an interface with synchronous data directly related component B (API interfaces directly exchange data calls)

problem:

We have further upstream component C (with interface), call A (US) for changing the operation data, in which case A calls B need to synchronize service interface, the problem here, C calls

A generally faster, more stable, but A calls B often out or failure, network failure, or B component design their own reasons for it, because it is not move

Program: through communication study, change this data in an acceptable time can be as long as the final agreement, so first of all, we first call the B series of logic in the service of something it out,

Made asynchronous, so C operating data can go back in time, in this asynchronous call B service interface synchronization process, the log automatically records the interface call failed when an exception occurs, and then open a

Worker thread task to poll call

design:

1, a third-party interface calls, the logic involved add, delete, change things from the management, asynchronous execution

2, after the abnormal occurrence interface calls, method calls the detailed log to record the database table

3, recording a single open polling task table to be modified retry state, sequentially retry, retry is successful or failed, the state changes are, for the several retries still fails, the interface display, notification investigation

achieve:

Asynchronous call interface is not talked about, springboot asynchronous program a lot, here is mainly about how to automatically log abnormal warehousing, and provide compensation

1, the log acquiring ideas

(1) B services call api interfaces abnormal, you need to throw specific exception, assuming called BusinessException, the abnormal inherited RuntimeException, can take exception error error, error description and other information

(2) custom log collecting annotations, acting on the method of collecting logs

(3) exception information storage, note the use of encryption to ensure the uniqueness summary

2, a single on-state processing thread to be collected for a recording retry, retry the call to be failed

coding:

1, custom annotation

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Custom annotation - call processing interface to third-party data exchange exception log collection
 * <p>
 * RetentionPolicy.RUNTIME JVM at runtime also reserve notes, annotation information can be read by reflection
 * Take effect on ElementType.METHOD method
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ExceptionCollect {

    String value() default "";

    String beanName() default "";

} 
Custom properties can be adjusted

2, defined annotation method signature

import org.aspectj.lang.annotation.Pointcut;

/**
 * Description Public pointCut
 * Definition of each method signature pointCut * / 
public  class Pointcuts { / **
     * Entry point: Notes @ExceptionCollect
     */
    @Pointcut("@annotation(com.xxx.config.aop.ExceptionCollect)")
    public void exceptionPointCut() {
    }
}

3, define specific class execution logic section

/**
 * Description section class
 * Print logs, exception log collection storage 
 * @see ExceptionCollect * /
@ slf4j
@Aspect
@Component
public class AspectService {

    @Autowired
    private ExceptionLogService exceptionLogService;/**
     * Notes @ExceptionCollect the method throws an exception will collect log information storage
     *
     * @Param Point section
     * @param e     抛出的异常
     */
    @AfterThrowing(value = "com.xxx.config.aop.PointCuts.exceptionPointCut()", throwing = "e")
    public void afterThrowing(JoinPoint point, BusinessException e) {
        MethodInfo methodInfo = new MethodInfo(point);
        ExceptionLog exceptionLog = convert(methodInfo, e);
        String beanName = methodInfo.getMethod().getAnnotation(ExceptionCollect.class).beanName();
        exceptionLog.setBeanName (beanName);
        // ensure idempotent 
        ExceptionLog oldLog = exceptionLogService.findById (exceptionLog.getId ());
         // the new record to be retry 
        IF (oldLog == null ) {
            exceptionLog.setRetryCount(1);
            exceptionLog.setStatus(ExceptionStatusEnum.RETRY.getType());
            exceptionLogService.create(exceptionLog);
        } The else {
             // been recorded, with a retry fails after 
            oldLog.setRetryCount (oldLog.getRetryCount () +. 1 );
            oldLog.setStatus(ExceptionStatusEnum.FAIL.getType());
            oldLog.setUpdateTime(CommonUtils.localDateTimeNow());
            exceptionLogService.update(oldLog);
        }
    }

    /**
     * @param methodInfo
     * @Param and
     * @return
     */
    private ExceptionLog convert(MethodInfo methodInfo, BusinessException e) {
        methodInfo.init();
        ExceptionLog exceptionLog = new ExceptionLog();
        exceptionLog.setClassName(methodInfo.getClassName());
        exceptionLog.setMethodName(methodInfo.getMethodName());
        exceptionLog.setJsonArgs(methodInfo.getJsonArgs());
        exceptionLog.setErrorCode(e.getCode());
        exceptionLog.setErrorMsg(e.getMessage());
        exceptionLog.setCreateTime(CommonUtils.localDateTimeNow());
        exceptionLog.setUpdateTime(CommonUtils.localDateTimeNow());
        //唯一键
        String id = Md5Util.getStrMD5(methodInfo.getClassName() + methodInfo.getMethodName() + methodInfo.getJsonArgs());
        exceptionLog.setId(id);
        return exceptionLog;
    }
}

4, make up the physical abnormality log

import lombok.Data;

import java.io.Serializable;
import java.time.LocalDateTime;

/**
 * Description Interface exception log entity
 */
@Data
public class ExceptionLog implements Serializable {
    private static final long serialVersionUID = 1L;

    /**
     * Class names, method names, into a digest value is generated according to the reference
     */
    private String id;
    /**
     Type * (defined treatment, artificial timing compensation or compensation)
     */
    private Integer type;
    /**
     * Processing status
     */
    private Integer status;
    /**
     * number of retries
     */
    private Integer retryCount;
    /**
     * error code
     */
    private String errorCode;
    /**
     * wrong description
     */
    private String errorMsg;
    /**
     * Full class name
     */
    private String className;
    /**
     * Bean name
     */
    private String beanName;
    /**
     * Method name
     */
    private String methodName;
    /**
     * Into the reference method
     */
    private String jsonArgs;
    /**
     * Remarks
     */
    private String remark;
    /**
     * Create a Time
     */
    private LocalDateTime createTime;
    /**
     * Change the time
     */
    private LocalDateTime updateTime;

}

5, auxiliary class

package com.hikvision.idatafusion.dglist.config.aop;

import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import lombok.Data;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.reflect.MethodSignature;

import java.lang.reflect.Method;

/**
 * Description interface method information
 * Aop acquire parameter information of a method * /

@Data
public class MethodInfo {

    /**
     * Entry point for information
     */
    private JoinPoint joinPoint;
    /**
     * The method of signature
     */
    private MethodSignature signature;
    /**
     * Methods Information
     */
    private Method method;
    /**
     * Class Information
     */
    private Class<?> targetClass;
    /**
     * Parameter information
     */
    private Object[] args;
    /**
     * String parameter information
     */
    private String jsonArgs;
    /**
     * Class name
     */
    private String className;
    /**
     * Method name
     */
    private String methodName;

    public MethodInfo(JoinPoint joinPoint) {
        this.joinPoint = joinPoint;
    }

    public void init() {
        this.signature = (MethodSignature) joinPoint.getSignature();
        this.method = signature.getMethod();
        this.methodName = method.getName();
        this.targetClass = method.getDeclaringClass();
        this.className = targetClass.getName();
        this.args = joinPoint.getArgs();
        this.jsonArgs = JSONObject.toJSONString(args, SerializerFeature.WriteMapNullValue, SerializerFeature.WriteNullStringAsEmpty);
    }

}

Next, we add comments @ExceptionCollect (beanName = "xxxService") on the specific method of calling external API interface

6, api interface calls methods

    @Override
    @ExceptionCollect(beanName = "xxxService")
    @Retryable(value = {BusinessException.class}, maxAttempts = 5, backoff = @Backoff(delay = 5000, multiplier = 2))
    public void test(TestBean person) {
        URL String = getUrl (); // full API request path
        String result;
        try {
            result = HttpUtils.post(url, param, header).body();
        } catch (HttpException e) {
            log.error("post API test failed!", e);
            throw new BusinessException(ErrorCodeEnum.API_INTERFACE_EXCEPTION.getCode());
        } 
// simplified parsing logic result of the request as
the Result JSON.parseObject Result = (Result, new new typereference <the Result> () { }); if (ErrorCodeEnum.SUCCESS.getCode().equals(result.getCode())) { log.info("XFACE add person success!"); } else { log.info("call API:test exception,code={},msg={}", result.getCode(), result.getMsg()); throw new BusinessException(ErrorCodeEnum.ADD_PERSON_EXCEPTION.getCode()); } }

 Here, a method of labeling the test @ExceptionCollect () will exceptionPointCut () method signature entry point is cut

If an exception is thrown performed by AspectService class, the marked @AfterThrowing afterThrowing () method to do exception handling logic

Here we make an exception for the implementation of the four states: -100 (failure) - 1 (canceled), 0 (to be retried), 100 (success)

It is to be recorded first storage retries (0), after several retries still fails to -100 to 100 successful

@Retryable, changing the definition method throws an exception if, automatic retry, five times the maximum retry, the next retry time interval is performed on a multiple press (2) increases, 5s, 10s, 20s ....... weight test

7, ExceptionWorker thread polling compensation call

/**
 * Description interface calls abnormal work thread compensation
 * T_exception_log service startup timing of the scan table record status = 0
 * 5-minute intervals * /

@Component
@ slf4j
public class ExceptionWorkerTask {

    @Autowired
    private ExceptionLogService exceptionLogService;
    @Autowired
    private xxxService xxxService;

    /**
     * Task only retry the status = 0
     * Every 5 minutes, every retry each record 1
     */
    @Scheduled(initialDelay = 10000, fixedDelay = 300000)
    public void retry() {
        List<ExceptionLog> list = exceptionLogService.getRetryList();
        for (ExceptionLog e : list) {
            String methodName = e.getMethodName();
            String jsonArgs = e.getJsonArgs();
            JSONObject argsJo = JSON.parseObject(jsonArgs);
            xxxService.test (argsJo); // If the call succeeds, the update status is 
            e.setRetryCount (e.getRetryCount () + 1 );
            e.setStatus(ExceptionStatusEnum.CONFIRM.getType());
            e.setUpdateTime(CommonUtils.localDateTimeNow());
            exceptionLogService.update(e);
        }
    }
}

Design flaws:

1, are not common, strong coupling business

2, ExceptionLog definition to be improved further

3, retry mechanism can also be designed to be more complex, there is a preliminary design manually retry scenarios

 

Guess you like

Origin www.cnblogs.com/yb38156/p/12013309.html