再送要求を防ぐために、分散ロックベースのソリューション

I.はじめに

        再送要求について、その要求を繰り返す非常に短い時間内に同じ内容の多くを受け、当社のサーバーを指します。そして、このような反復要求は冪等(各リクエストの結果は、このようなクエリと、同じ)であれば、実際には、私たちのために影響はありませんが、非冪等(すべての要求のような重要なデータに影響するかどうかということ)など、関係を構築し、関係を削除し、それがシステムエラーながら、ダーティ・データに至るまで生成されます。

        したがって、現在のユニバーサル分散サービスの場合には、回避し、異常なことで、私たちにもたらし重複要求データを解決する方法を深刻な問題となっています。複製要求を回避しながら、最善の方法は、共通の前端と後端を行うことです。

        1.直接行う非冪等の操作ボタンのフロントエンドまたはクライアントは、再送要求を許可しません。

        ロック要求、ロック解除の後端を受信する2。

        このブログは、再送要求の決済上の共通液に分散ロックの概念に基づいて、バックエンドについて、主です。

第二に、テキスト

        なぜそれを解決するために、分散ロックを使うのか?現在広く分散サーバアーキテクチャであり、以下に示すように、我々は、要求は、後端にゲートウェイ層遠位によって転送ので、単一のサーバ上の制限は、それが分散サービスに完了できない場合にのみ、これを行います再三の要求に高周波応答。

              

画像

基本的な考え方

        アイデアは、インターフェイスに接続された分散ロック・再送要求を防ぐために行うことを基本的に次のステップです。

  1. 要求を受信すると、メソッド名+ MD5パラメータに従って取得方法の値をとり、一意のパラメータを特定します。
  2. 分散ロックは、識別子の後に配置され、および有効期限を設定します。
  3. 要求した後、リリースには、ロックを配布しました。

        現在の要求を完了するために反復要求は禁止されています。あなたがユニバーサルソリューションをしたい場合は、それが機能上の小さな一歩を作る必要があるでしょう、私はJavaの上ですので、春のフレームワークは、より精通しているので、彼らは一例であることを、これを取りました。

ばね部、Redisのベースを達成

        おそらくいくつかの身近な春の学生は、すでに次のようにmd5key +反射+のRedisのは、達成+カット+注釈の春AOPの特性を使用してください、私がしたい、と汎用を行うどのように知っています:

  1. パラメータを無視し、有効期限が含まれるように、分散ロックにコメントを定義します。
  2. カットの定義は、時間がセクションに期限が切れ、分散ロック取得メソッド名、パラメータを使用する必要があり、分散ロック・ノートのためのポイントをカットオフ、およびメソッド名とパラメータは、MD5は固有の識別を取る無視しないでください。
  3. 分散ロック・redsisは、一意の識別子に基づいて設定します。
  4. メソッドのロックを解除した後。

        コードは以下の通りであります:

コメント

        (すなわち財産の分散コンピューティングMD5ロック識別に参加していない)コメントRepeatOperationLock、パラメータロックの有効期限を定義し、プロパティを無視します。

@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Component
public @interface RepeatOperationLock {
    /**
     * 锁时长,默认500ms
     * @return
     */
    long timeOut() default 500;

    /**
     * 忽略上锁参数位置,从0开始
     * @return
     */
    int[] ignoreIndex();
}
复制代码

セクション

        注意事項+プロパティのMD5値を計算し、注釈の属性(有効期限は、プロパティを無視する)を取得、メソッド名を取得するために行うにはいくつかのことをカットし、カットオフポイントのためのものである、方法は、外部の分散ロックを呼び出します。

@Aspect
@Slf4j
@Component
public class LockAspect {

    @Autowired
    RepeatLockService repeatLockService;

    @Pointcut("@annotation(com.ls.javabase.aspect.annotation.RepeatOperationLock)")
    public void serviceAspect() {
    }

    @Before("serviceAspect()")
    public void setLock(JoinPoint point) {
        log.info("防止方法重复调用接口锁,上锁,point:{}", point);
        Method method = ((MethodSignature) point.getSignature()).getMethod();
        RepeatOperationLock repeatOperationLock = method.getAnnotation(RepeatOperationLock.class);
        if (Objects.isNull(repeatOperationLock)) {
            log.warn("---repeatOperationLock is null---");
            return;
        }
        long timeOut = repeatOperationLock.timeOut();
        int [] ignoreIndex = repeatOperationLock.ignoreIndex();
        log.info("lockTime——{}", timeOut);
        if (Objects.isNull(timeOut)) {
            log.warn("---timeOut is null");
            return;
        }
        String methodName = method.getName();
        Object[] args = point.getArgs();


        repeatLockService.setRepeatLock(methodName, args, timeOut);
    }

    @After("serviceAspect()")
    public void removeLock(JoinPoint point) {
        log.info("防止方法重复调用接口锁,解锁,point:{}",point);
        Method method = ((MethodSignature) point.getSignature()).getMethod();
        RepeatOperationLock repeatOperationLock = method.getAnnotation(RepeatOperationLock.class);
        if (Objects.isNull(repeatOperationLock)) {
            log.warn("---repeatOperationLock is null---");
            return;
        }
        long timeOut = repeatOperationLock.timeOut();
        log.info("lockTime——{}", timeOut);
        if (Objects.isNull(timeOut)) {
            log.warn("---timeOut is null");
            return;
        }
        String methodName = method.getName();
        Object[] args = point.getArgs();
        repeatLockService.removeRepeatLock(methodName, args);
    }

    /**
     *
     * @param args
     * @param ignoreIndex
     * @return
     */
    private Object [] getEffectiveArgs(Object[] args,int [] ignoreIndex) {
        for (int i:ignoreIndex){
            args[i] = null;
        }
        for (Object obj:args){
            if (obj==null){

            }
        }
        return args;
    }
}
复制代码

MD5方法

public class Md5Encode {

    /**
     * constructors
     */
    private Md5Encode() {

    }

    /**
     * @param s 需要hash的字符串
     * @return hash之后的字符串
     */
    public static final String md5(final String s) {
        char[] hexDigits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
        try {
            byte[] btInput = s.getBytes(Charset.defaultCharset());
            // 获得MD5摘要算法的 MessageDigest 对象
            MessageDigest mdInst = MessageDigest.getInstance("MD5");
            // 使用指定的字节更新摘要
            mdInst.update(btInput);
            // 获得密文
            byte[] md = mdInst.digest();
            // 把密文转换成十六进制的字符串形式
            int j = md.length;
            char[] str = new char[j * 2];
            int k = 0;
            for (int i = 0; i < j; i++) {
                byte byte0 = md[i];
                str[k++] = hexDigits[byte0 >>> 4 & 0xf];
                str[k++] = hexDigits[byte0 & 0xf];
            }
            return new String(str);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}
复制代码

分散ロック

        あなたがロックとロックの有効期限を設定するときにここでのRedisを使用して、分散ロックが、少し問題がアトミックされていない、ですよ、フォローアップが完全分散ロック・プログラムを達成するための改善を行います、ブログに書いています。

@Slf4j
@Service
public class RepeatLockService {

    @Autowired
    RepeatRedisUtil repeatRedisUtil;

    public void setRepeatLock(String methodName, Object[] args, Long expireTime) {
        for (Object obj : args) {
            log.info("方法名:{},对象:{},对象hashcode:{}", methodName, obj, obj.hashCode());
        }
        Boolean lock = repeatRedisUtil.setRepeatLock(methodName, args, expireTime);
        if (!lock) {
            log.info("已有相同请求");
        }
    }

    public void removeRepeatLock(String methodName, Object[] args) {
        repeatRedisUtil.removeRepeatLock(methodName, args);
    }
}

@Component
public class RepeatRedisUtil {
    @Autowired
    RedisTemplate redisTemplate;

    private static final String repeatLockPrefix = "repeat_lock_";

    /**
     * 设置重复请求锁,这一块的分布式锁的加与释放有问题,后续会专门出个文章总结redis分布式锁
     * @param methodName
     * @param args
     * @param expireTime 过期时间ms
     * @return
     */
    public boolean setRepeatLock(String methodName, Object[] args,long expireTime) {
        String key = getRepeatLockKey(methodName, args);
        ValueOperations val = redisTemplate.opsForValue();
        Long flag = val.increment(key, 1);
        if (flag != 1) {
            return false;
        }
        redisTemplate.expire(key, expireTime, TimeUnit.MILLISECONDS);
        return true;
    }

    /**
     * 删除重复请求锁
     * @param methodName
     * @param args
     */
    public void removeRepeatLock(String methodName, Object[] args){
        String key = getRepeatLockKey(methodName, args);
        redisTemplate.delete(key);
    }

    /**
     * 获取重复请求锁Key
     *
     * @param methodName
     * @param args
     * @return
     */
    public String getRepeatLockKey(String methodName, Object[] args) {
        String repeatLockKey = repeatLockPrefix + methodName;
        for (Object obj : args) {
            repeatLockKey = repeatLockKey+"_"+ obj.hashCode();
        }
        return repeatLockKey;
    }
}
复制代码

テストサービス・インターフェース

        つまり、200000msの有効期限を代表して、第二引数を省略するノートに使用される方法です。

@Slf4j
@Service
public class TestLockService {

    @RepeatOperationLock(timeOut = 200000, ignoreIndex = 1)
    public void testLock(UserDto userDto,int i){
        log.info("service中属性:{},{}",userDto,i);
        log.info("service中hashcode,userDto:{},i:{}",userDto.hashCode(),i);
    }
}
复制代码

エピローグ

        このような、スプリング・ベースの汎用分散ロック・ソリューションが完成し共有することが、確かに、分散ロック上海の実装におけるいくつかの欠陥は、それがロックを解除するかどうか何の裁判官は、そのようなロックがアトミックでない場合など、いくつかの問題が存在する残っていませんなどの誤解が、フォローアップの概要分散ロックに専念して改善され、すぐ上にも、我々はより多くの交流を行うことを願って、分散ロック・再送要求に基づくソリューションのアイデアを提案しました。

おすすめ

転載: juejin.im/post/5d3d1038e51d4577596487d9