[2023] Implementierung der Schnittstellen-Idempotenz-Lösung während der Projektentwicklung und Codebeispiele

Schnittstellen-Idempotenz ist häufig in unserem Projektentwicklungsprozess involviert, am häufigsten in Systemen wie Zahlungs- und Einkaufszentren; natürlich wird sie auch oft vom Interviewer während des Interviewprozesses gefragt.

1. Was ist Schnittstellen-Idempotenz?

Was ist Idempotenz (Baidu-Enzyklopädie)
Idempotenz (Idempotent, Idempotenz) ist ein mathematisches und informatisches Konzept, das häufig in der abstrakten Algebra vorkommt.
Das Merkmal einer idempotenten Operation in der Programmierung besteht darin, dass die Auswirkung mehrerer Ausführungen dieselbe ist wie die Auswirkung einer einzelnen Ausführung. Eine idempotente Funktion oder idempotente Methode ist eine Funktion, die wiederholt mit denselben Parametern ausgeführt werden kann und das gleiche Ergebnis erzielt. Diese Funktionen wirken sich nicht auf den Systemstatus aus und Sie müssen sich keine Sorgen über Änderungen am System machen, die durch wiederholte Ausführung verursacht werden. Beispielsweise ist die Funktion „setTrue()“ eine idempotente Funktion. Egal wie oft sie ausgeführt wird, das Ergebnis ist das gleiche. Komplexere Operationen sind durch die Verwendung einer eindeutigen Transaktionsnummer (Seriennummer) garantiert idempotent.

Idempotenz : Das mehrmalige Aufrufen einer Methode oder Schnittstelle ändert den Geschäftsstatus nicht und kann sicherstellen, dass die Ergebnisse wiederholter Aufrufe mit den Ergebnissen eines einzelnen Aufrufs übereinstimmen.

2. Idempotente Szenarien treten auf

1. Wiederholte Einreichung im Front-End

Für die Benutzerregistrierung, die Benutzererstellung von Produkten und andere Vorgänge übermittelt das Front-End einige Daten an den Backend-Dienst, und das Backend muss auf der Grundlage der vom Benutzer übermittelten Daten Datensätze in der Datenbank erstellen. Wenn der Benutzer versehentlich mehrmals klickt und das Backend mehrere Übermittlungen erhält, werden in der Datenbank wiederholt mehrere Datensätze erstellt. Dies ist der Fehler, der durch die fehlende Idempotenz der Schnittstelle verursacht wird.

2. Zeitüberschreitung der Schnittstelle und erneuter Versuch

Bei Schnittstellen, die an Dritte aufgerufen werden, kann der Aufruf aus Netzwerkgründen fehlschlagen. In diesem Fall wird dem Schnittstellenaufruf während des Entwurfs normalerweise ein Fehlerwiederholungsmechanismus hinzugefügt. Wenn sich der erste Aufruf bereits in der Mitte der Ausführung befindet, tritt eine Netzwerkausnahme auf. Zu diesem Zeitpunkt tritt bei einem erneuten Aufruf eine Aufrufausnahme auf, da fehlerhafte Daten vorhanden sind.

3. Wiederholter Konsum von Nachrichten

Wenn Sie Nachrichten-Middleware verwenden, um die Nachrichtenwarteschlange zu verarbeiten und manuell zu bestätigen, um zu bestätigen, dass die Nachricht normal verarbeitet wird. Wenn der Verbraucher plötzlich die Verbindung trennt, werden halb ausgeführte Nachrichten wieder in die Warteschlange gestellt.

Wenn die Nachricht von anderen Verbrauchern erneut konsumiert wird und keine Idempotenz vorliegt, führt dies zu abnormalen Ergebnissen, wenn die Nachricht erneut konsumiert wird, z. B. wiederholte Datenbankdaten, Datenbankdatenkonflikte, Ressourcenduplizierung usw.

3. Lösung

1. Implementierung des Token-Mechanismus

Die Idempotenz der Schnittstelle wird durch den Token-Mechanismus erreicht, der eine relativ vielseitige Implementierungsmethode darstellt.
Das schematische Diagramm sieht wie folgt aus:
Fügen Sie hier eine Bildbeschreibung ein

Spezifische Prozessschritte:

  1. Der Client sendet zunächst eine Anfrage zum Abrufen des Tokens, und der Server generiert eine global eindeutige ID als Token, speichert sie in Redis und gibt diese ID an den Client zurück.
  2. Der Client muss dieses Token beim zweiten Aufruf der Geschäftsanfrage mit sich führen.
  3. Der Server überprüft das Token. Wenn die Überprüfung erfolgreich ist, wird der Dienst ausgeführt und das Token in Redis gelöscht.
  4. Wenn die Überprüfung fehlschlägt, bedeutet dies, dass in Redis kein entsprechendes Token vorhanden ist. Dies bedeutet, dass der Vorgang wiederholt und das angegebene Ergebnis direkt an den Client zurückgegeben werden muss.

Code

1. Abhängigkeiten hinzufügen

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.4</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

2. Konfigurationsklasse hinzufügen

  • Rücksendekörper anfordern
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Response {
    
    
        private int status;
        private String msg;
        private Object data;
}
  • Rückgabe Code
public enum ResponseCode {
    
    
    // 通用模块 1xxxx
    ILLEGAL_ARGUMENT(10000, "参数不合法"),
    REPETITIVE_OPERATION(10001, "请勿重复操作"),
    ;
    ResponseCode(Integer code, String msg) {
    
    
        this.code = code;
        this.msg = msg;
    }
    private Integer code;
    private String msg;
    public Integer getCode() {
    
    
        return code;
    }
    public void setCode(Integer code) {
    
    
        this.code = code;
    }
    public String getMsg() {
    
    
        return msg;
    }
    public void setMsg(String msg) {
    
    
        this.msg = msg;
    }
}

  1. Konfigurieren Sie die globale Ausnahmebehandlung
  • Benutzerdefinierte Ausnahme
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ServiceException extends RuntimeException{
    
    
    private String code;
    private String msg;
}
  • Globales Abfangen von Ausnahmen
@ControllerAdvice
public class MyControllerAdvice {
    
    
    @ResponseBody
    @ExceptionHandler(ServiceException.class)
    public Response serviceExceptionHandler(ServiceException exception){
    
    
        Response response=new Response(Integer.valueOf(exception.getCode()),exception.getMsg(),null);
        return response;
    }
}
  1. Bearbeiten und erstellen Sie die Schnittstelle und Implementierung zum Erhalten des Tokens und zum Überprüfen des Tokens
  • Schnittstelle
@Service
public interface TokenService {
    
    
    public Response createToken();
    public Response checkToken(HttpServletRequest request);
}
  • Implementierungsklassenspezifischer Code
@Service
public class TokenServiceImpl implements TokenService {
    
    
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    @Override
    public Response createToken() {
    
    
//        生成uuid当作token,也可用雪花等其他算法
        String token = UUID.randomUUID().toString().replaceAll("-", "");
//        将生成的token存入redis中
        redisTemplate.opsForValue().set(token,token);
//        返回正确的结果信息
        Response response = new Response(0, token.toString(), null);
        return response;
    }

    @Override
    public Response checkToken(HttpServletRequest request) {
    
    
        String token = request.getHeader("token");
        if (StringUtils.isBlank(token)){
    
    
            //如果请求头token为空就从参数中获取
            token=request.getParameter("token");
            //如果都为空抛出参数异常的错误
            if (StringUtils.isBlank(token)){
    
    
                throw new ServiceException(ResponseCode.ILLEGAL_ARGUMENT.getCode().toString(),ResponseCode.ILLEGAL_ARGUMENT.getMsg());
            }
        }
        //如果redis中不包含该token,说明token已经被删除了,抛出请求重复异常
        if (!redisTemplate.hasKey(token)){
    
    
            throw new ServiceException(ResponseCode.REPETITIVE_OPERATION.getCode().toString(),ResponseCode.REPETITIVE_OPERATION.getMsg());
        }
        //删除token
        Boolean del=redisTemplate.delete(token);
        //如果删除不成功(已经被其他请求删除),抛出请求重复异常
        if (!del){
    
    
            throw new ServiceException(ResponseCode.REPETITIVE_OPERATION.getCode().toString(),ResponseCode.REPETITIVE_OPERATION.getMsg());
        }
        return new Response(0,"校验成功",null);
    }
}

5. Konfigurieren Sie benutzerdefinierte Anmerkungen.
Sie müssen diese Anmerkung nur der Schnittstelle hinzufügen, die Schnittstellen-Idempotenz implementieren muss, um eine Token-Überprüfung zu erreichen. Sie müssen das Token zuerst anfordern und dann überprüfen.

  • benutzerdefinierte Anmerkung
@Target({
    
    ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiIdempotent {
    
    
}
  • Schnittstellen-Interceptor, der ApiIdempotentdie Überprüfung durch Abfangen der annotierten Schnittstelle auf der Schnittstelle implementiert
public class ApiIdempotentInterceptor implements HandlerInterceptor {
    
    
    @Autowired
    private TokenService tokenService;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    
    
        if (!(handler instanceof HandlerMethod)) {
    
    
            return true;
        }
        HandlerMethod handlerMethod= (HandlerMethod) handler;
        Method method=handlerMethod.getMethod();
//        拦截有 ApiIdempotent 注解的接口
        ApiIdempotent methodAnnotation=method.getAnnotation(ApiIdempotent.class);
        if (methodAnnotation != null){
    
    
            // 校验通过放行,校验不通过全局异常捕获后输出返回结果
            tokenService.checkToken(request);
        }
        return true;
    }
}
  • Den Bohnenbehälter aufspritzen
@Configuration
public class WebConfig implements WebMvcConfigurer {
    
    
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
    
    
        registry.addInterceptor(apiIdempotentInterceptor());
    }

    @Bean
    public ApiIdempotentInterceptor apiIdempotentInterceptor() {
    
    
        return new ApiIdempotentInterceptor();
    }
}

  1. Überprüfung der Controller-Schnittstelle
@RestController
@RequestMapping("/token")
public class TokenController {
    
    
    @Autowired
    private TokenService tokenService;
	//获取token
    @GetMapping("getToken")
    public Response getToken(){
    
    
        return tokenService.createToken();
    }
	//核验
    @PostMapping("checkToken")
    public Response checkToken(HttpServletRequest request){
    
    
        return tokenService.checkToken(request);
    }
}
  • Konkrete Umsetzungsanwendungen
@Controller
public class BasicController {
    
    

    // http://127.0.0.1:8080/hello?name=lisi
    @RequestMapping("/hello")
    @ResponseBody
    @ApiIdempotent
    public String hello(@RequestParam(name = "name", defaultValue = "unknown user") String name) {
    
    
        return "Hello " + name;
    }

    // http://127.0.0.1:8080/user
    @RequestMapping("/user")
    @ResponseBody
    @ApiIdempotent
    public User user() {
    
    
        User user = new User();
        user.setName("theonefx");
        user.setAge(666);
        return user;
    }

Ergebnisüberprüfung

Erstellen Sie zunächst ein Token über die Token-Schnittstelle. Zu diesem Zeitpunkt ist das Token auch in Redis vorhanden.
Fügen Sie hier eine Bildbeschreibung ein

  • Testen Sie die Schnittstelle und fügen Sie das erhaltene Token in den Anforderungsheader ein.
    Fügen Sie hier eine Bildbeschreibung ein

2. Implementierung basierend auf MySQL

Diese Implementierung nutzt die einzigartige Indexfunktion von MySQL.
Das schematische Diagramm sieht wie folgt aus:
Fügen Sie hier eine Bildbeschreibung ein

Spezifische Prozessschritte :

  1. Erstellen Sie eine Deduplizierungstabelle, in der ein eindeutiger Index für ein bestimmtes Feld erstellt werden muss
  2. Wenn der Client den Server anfordert, fügt der Server einige Informationen aus dieser Anfrage in diese Deduplizierungstabelle ein.
  3. Da ein Feld in der Tabelle über einen eindeutigen Index verfügt, beweist die erfolgreiche Einfügung, dass dieses Mal keine angeforderten Informationen in der Tabelle vorhanden sind, und die nachfolgende Geschäftslogik wird ausgeführt.
  4. Wenn das Einfügen fehlschlägt, bedeutet dies, dass die aktuelle Anforderung ausgeführt wurde und direkt zurückgegeben wird.

3. Implementierung basierend auf Redis

这种实现方式是基于 setNx 命令实现的,分布式锁一般也就是采用这种方式。

setNx-Schlüsselwert: Setzt den Wert des Schlüssels genau dann auf „Wert“, wenn der Schlüssel nicht vorhanden ist. Wenn der angegebene Schlüssel bereits vorhanden ist, ergreift SETNX keine Aktion.

Dieser Befehl gibt 1 zurück, wenn die Einstellung erfolgreich ist, und 0, wenn die Einstellung fehlschlägt.

Das schematische Diagramm ist wie folgt
Fügen Sie hier eine Bildbeschreibung ein

Spezifische Prozessschritte:

  1. Der Client fordert zunächst den Server an und erhält ein eindeutiges Feld, das das angeforderte Unternehmen darstellt.
  2. Speichern Sie dieses Feld in Redis in Form von SETNX und legen Sie das entsprechende Zeitlimit entsprechend dem Unternehmen fest
  3. Wenn die Einstellung erfolgreich ist, wird bewiesen, dass es sich um die erste Anfrage handelt, und die nachfolgende Geschäftslogik wird ausgeführt.
  4. Wenn die Einstellung fehlschlägt, bedeutet dies, dass die aktuelle Anforderung ausgeführt wurde und direkt zurückgegeben wird.

Zusammenfassen

Diese verschiedenen Möglichkeiten, Idempotenz zu erreichen, sind tatsächlich ähnlich. Zu den ähnlichen Methoden gehören die Verwendung von Zustandsmaschinen, pessimistischen Sperren und optimistischen Sperren. Tatsächlich werden sie alle mithilfe einer Sperre implementiert, was relativ einfach ist.

Kurz gesagt, wenn Sie eine Schnittstelle entwerfen, ist die Idempotenz die Hauptüberlegung, insbesondere wenn Sie für die Gestaltung von Schnittstellen verantwortlich sind, bei denen es um Geld geht, wie z. B. Überweisungen und Zahlungen.

Supongo que te gusta

Origin blog.csdn.net/weixin_52315708/article/details/131766475
Recomendado
Clasificación