Cómo manejar las solicitudes repetidas con elegancia (solicitudes simultáneas)

Utilice un número de solicitud único para eliminar duplicados

Lo que podría pensar es que siempre que la solicitud tenga un número de solicitud único, puede pedir prestado Redis para realizar esta deduplicación, siempre que el número de solicitud único exista en redis y demuestre que se ha procesado, entonces se considera un duplicado.

El código es aproximadamente el siguiente:

  String KEY = "REQ12343456788"; // Solicita un número único
    long expireTime = 1000; // Caduca en 1000 milisegundos, las solicitudes repetidas dentro de 1000 milisegundos se considerarán repetidas
    long expireAt = System.currentTimeMillis () + expireTime;
    String val = "expireAt @" + expireAt;

    // Si la clave redis aún existe, considere la solicitud como un duplicado
    Boolean firstSet = stringRedisTemplate.execute ((RedisCallback <Boolean>) conexión -> connection.set (KEY.getBytes (), val.getBytes (), Expiration.milliseconds (expireTime), RedisStringCommands.SetOption.SET_IF_ABSENT));

    isConsiderDup booleano final;
    if (firstSet! = null && firstSet) {// primera visita
        isConsiderDup = falso;
    } else {// El valor de redis ya existe y se considera un duplicado
        isConsiderDup = true;
    }

Deduplicación de parámetros comerciales

La solución anterior puede resolver el escenario con un número de solicitud único. Por ejemplo, antes de cada solicitud de escritura, el servidor devuelve un número único al cliente, y el cliente realiza una solicitud con este número de solicitud, y el servidor puede completar la desinterceptación.

Sin embargo, en muchos escenarios, la solicitud no incluye un número tan exclusivo. Entonces, ¿podemos usar los parámetros solicitados como un identificador de solicitud?

Considere primero un escenario simple: asumiendo que el parámetro de solicitud tiene solo un campo, reqParam, podemos usar el siguiente identificador para determinar si la solicitud se repite. ID de usuario: nombre de la interfaz: parámetro de solicitud

String KEY = "dedup: U =" + userId + "M =" + método + "P =" + reqParam;

Luego, cuando el mismo usuario accede a la misma interfaz y viene con el mismo reqParam, podemos localizarlo como un duplicado.

Pero el problema es que nuestra interfaz no suele ser tan simple. Con la corriente principal actual, nuestro parámetro suele ser un JSON. Entonces, para este tipo de escena, ¿cómo lo hacemos?

Calcular el resumen de los parámetros de la solicitud como identificador de parámetro

Supongamos que ordenamos los parámetros de solicitud (JSON) en orden ascendente por CLAVE y luego los combinamos en una cadena como el valor CLAVE. Pero esto puede ser muy largo, por lo que podemos considerar solicitar un resumen MD5 de esta cadena como parámetro y usar este resumen para reemplazar la posición de reqParam.

String KEY = "dedup: U =" + userId + "M =" + método + "P =" + reqParamMD5;

De esta forma, se marca el identificador único de la solicitud.

Nota: MD5 puede repetirse teóricamente, pero la deduplicación suele ser deduplicación en un período de tiempo corto (por ejemplo, un segundo). La misma interfaz de usuario puede deletrear diferentes parámetros en un período corto de tiempo, lo que da como resultado el mismo MD5. posible.

Continúe optimizando, considere eliminar algunos factores de tiempo

El problema anterior es en realidad una muy buena solución, pero cuando se pone en uso, se pueden encontrar algunos problemas: algunos usuarios solicitan que los usuarios hagan clic repetidamente en poco tiempo (por ejemplo, se envían tres solicitudes en 1000 milisegundos), pero se omiten El juicio de deduplicación anterior (valor CLAVE diferente).

La razón es que hay un campo de tiempo en el campo de estos parámetros de solicitud. Este campo marca la hora de la solicitud del usuario y el servidor puede usarlo para descartar algunas solicitudes antiguas (por ejemplo, hace 5 segundos). Como en el siguiente ejemplo, los otros parámetros de la solicitud son los mismos, excepto que el tiempo de la solicitud difiere en un segundo:

  // Las dos solicitudes son iguales, pero el tiempo de solicitud es de un segundo
    Cadena req = "{\ n" +
            "\" requestTime \ ": \" 20190101120001 \ ", \ n" +
            "\" requestValue \ ": \" 1000 \ ", \ n" +
            "\" requestKey \ ": \" key \ "\ n" +
            "}";

    Cadena req2 = "{\ n" +
            "\" requestTime \ ": \" 20190101120002 \ ", \ n" +
            "\" requestValue \ ": \" 1000 \ ", \ n" +
            "\" requestKey \ ": \" key \ "\ n" +
            "}";

Para este tipo de solicitud, es posible que también necesitemos bloquear solicitudes repetidas posteriores. Por lo tanto, es necesario eliminar dichos campos de tiempo antes de buscar un resumen de parámetros comerciales. Un campo similar puede ser el campo de latitud y longitud del GPS (puede haber diferencias muy pequeñas entre las solicitudes repetidas).

Solicitar clase de herramienta de deduplicación, implementación de Java

public class ReqDedupHelper {

    / **
     *
     * @param parámetro de solicitud reqJSON, generalmente JSON aquí
     * @param excludeKeys Qué campos deben eliminarse en los parámetros de solicitud y luego solicitar un resumen
     * @return MD5 resumen de parámetros eliminados
     * /
    public String dedupParamMD5 (cadena final reqJSON, String ... excludeKeys) {
        String decreptParam = reqJSON;

        TreeMap paramTreeMap = JSON.parseObject (decreptParam, TreeMap.class);
        if (excludeKeys! = null) {
            List <String> dedupExcludeKeys = Arrays.asList (excludeKeys);
            if (! dedupExcludeKeys.isEmpty ()) {
                para (String dedupExcludeKey: dedupExcludeKeys) {
                    paramTreeMap.remove (dedupExcludeKey);
                }
            }
        }

        String paramTreeMapJSON = JSON.toJSONString (paramTreeMap);
        Cadena md5deDupParam = jdkMD5 (paramTreeMapJSON);
        log.debug ("md5deDupParam = {}, excludeKeys = {} {}", md5deDupParam, Arrays.deepToString (excludeKeys), paramTreeMapJSON);
        return md5deDupParam;
    }

    Private estático String jdkMD5 (String src) {
        String res = nulo;
        tratar {
            MessageDigest messageDigest = MessageDigest.getInstance ("MD5");
            byte [] mdBytes = messageDigest.digest (src.getBytes ());
            res = DatatypeConverter.printHexBinary (mdBytes);
        } captura (Excepción e) {
            log.error ("", e);
        }
        return res;
    }
}

A continuación, se muestran algunos registros de prueba:

public static void main (String [] args) {
    // Las dos solicitudes son iguales, pero el tiempo de solicitud es de un segundo
    Cadena req = "{\ n" +
            "\" requestTime \ ": \" 20190101120001 \ ", \ n" +
            "\" requestValue \ ": \" 1000 \ ", \ n" +
            "\" requestKey \ ": \" key \ "\ n" +
            "}";

    Cadena req2 = "{\ n" +
            "\" requestTime \ ": \" 20190101120002 \ ", \ n" +
            "\" requestValue \ ": \" 1000 \ ", \ n" +
            "\" requestKey \ ": \" key \ "\ n" +
            "}";

    // Compare todos los parámetros, por lo que el MD5 de los dos parámetros es diferente
    String dedupMD5 = new ReqDedupHelper (). DedupParamMD5 (req);
    String dedupMD52 = new ReqDedupHelper (). DedupParamMD5 (req2);
    System.out.println ("req1MD5 =" + dedupMD5 + ", req2MD5 =" + dedupMD52);

    // Elimina la comparación de parámetros de tiempo, MD5 es el mismo
    String dedupMD53 = new ReqDedupHelper (). DedupParamMD5 (req, "requestTime");
    String dedupMD54 = new ReqDedupHelper (). DedupParamMD5 (req2, "requestTime");
    System.out.println ("req1MD5 =" + dedupMD53 + ", req2MD5 =" + dedupMD54);

}

Salida de registro:

req1MD5 = 9E054D36439EBDD0604C5E65EB5C8267, req2MD5 = A2D20BAC78551C4CA09BEF97FE468A3F
req1MD5 = C2A36FED15128E9E878583CAAAFEFDE9, req2MD5 = C2A36FED15128E9E878583CAAAFEFDE912

Descripción del registro:

  • Al principio, los dos parámetros son diferentes debido a requestTime, por lo que al buscar el resumen del parámetro de deduplicación, puede encontrar que los dos valores son diferentes.
  • En la segunda llamada, se eliminó el requestTime y se solicitó el resumen (se pasó "requestTime" en el segundo parámetro), y se encontró que los dos resúmenes eran iguales, lo cual estaba en línea con las expectativas.

para resumir

Hasta ahora, podemos obtener una solución de deduplicación completa, de la siguiente manera:

String userId= "12345678";//用户
String method = "pay";//接口名
String dedupMD5 = new ReqDedupHelper().dedupParamMD5(req,"requestTime");//计算请求参数摘要,其中剔除里面请求时间的干扰
String KEY = "dedup:U=" + userId + "M=" + method + "P=" + dedupMD5;

long expireTime =  1000;// 1000毫秒过期,1000ms内的重复请求会认为重复
long expireAt = System.currentTimeMillis() + expireTime;
String val = "expireAt@" + expireAt;

// NOTE:直接SETNX不支持带过期时间,所以设置+过期不是原子操作,极端情况下可能设置了就不过期了,后面相同请求可能会误以为需要去重,所以这里使用底层API,保证SETNX+过期时间是原子操作
Boolean firstSet = stringRedisTemplate.execute((RedisCallback<Boolean>) connection -> connection.set(KEY.getBytes(), val.getBytes(), Expiration.milliseconds(expireTime),
        RedisStringCommands.SetOption.SET_IF_ABSENT));

final boolean isConsiderDup;
if (firstSet != null && firstSet) {
    isConsiderDup = false;
} else {
    isConsiderDup = true;
}

最后

感谢大家看到这里,文章有不足,欢迎大家指出;如果你觉得写得不错,那就给我一个赞吧。

也欢迎大家关注我的公众号:程序员麦冬,每天更新行业资讯!

Supongo que te gusta

Origin blog.51cto.com/14849432/2555604
Recomendado
Clasificación