あなたのインターフェースはまだインターフェース同等性を使用していませんか?この記事では、インターフェイスの同等性とは何か、およびインターフェイスの同等性を解決する方法について説明します。

質問は導きます:

  • システムには多くの操作があり、それらが何度実行されても、同じ効果を生み出すか、同じ結果を返すはずです。例1.フロントエンドは選択されたデータを繰り返し送信し、このデータに対応する1つの応答結果のみがバックグラウンドで生成されます。2。支払い要求を開始するときに、ユーザーアカウントは1回だけ差し引かれます。3。作成します。ビジネスオーダー、1つのビジネスリクエストのみ1つ作成、複数作成すると、大きな問題などが発生します。多くの重要な状況で、サポートするべき等機能必要です

解決:

  • クエリ操作:1回のクエリと複数回のクエリ。データが変更されていない場合、クエリ結果は同じです。selectは自然なべき等演算です。
  • 削除操作:削除操作もべき等です。1回削除すると、複数の削除ですべての削除データが削除されます。(返される結果は異なる場合があり、削除されたデータは存在しないことに注意してください。0を返し、複数のデータを削除し、複数の結果を返します)。
  • 新しいダーティデータを防ぐための一意のインデックス。例:Alipayのファンドアカウント、Alipayにもユーザーアカウントがあり、各ユーザーは1つのファンドアカウントしか持てません。ユーザーに複数のファンドアカウントが作成されないようにする方法は、ファンドアカウントテーブルのユーザーIDに一意のインデックスを追加することです。したがって、ユーザーは追加します。資金勘定を正常に記録します。キーポイント:新しいデータのダーティデータを防ぐための一意のインデックスまたは一意の複合インデックス(テーブルに一意のインデックスがあり、同時実行中にエラーが追加された場合は、再度クエリを実行できます。データはすでに存在している必要があり、結果を返すだけです)。
  • トークンメカニズムは、ページの繰り返し送信を防ぎます。ビジネス要件:ページ上のデータはクリックして1回だけ送信できます;理由:クリック、ネットワーク再送信、またはnginx再送信が繰り返されるため、データは繰り返し送信されます;解決策:クラスター環境はトークンとredis(redisシングルスレッド)を使用しますはい、処理はキューに入れる必要があります);単一のJVM環境:トークンとredis、またはトークンとjvmメモリを使用します。処理フロー:1。データ送信の前に、サービスからトークンを申請します。トークンはredisまたはjvmメモリに配置され、トークンは有効です。2。送信後、トークンはバックグラウンドで検証され、トークンは同時に削除され、新しいトークンが生成されて返されます。トークン機能:適用、1回限りの有効性、現在の制限。注:Redisは削除操作を使用してトークンを判別する必要があります。削除が成功すると、トークンは検証に合格したことを意味します。トークンの検証にselect + deleteを使用する場合、同時実行性の問題がある場合は使用しないことをお勧めします。
  • データを取得するときの悲観的なロックロック取得。select * from table_xxx where id = 'xxx' for update;注:idフィールドは主キーまたは一意のインデックスである必要があります。そうでない場合は、致命的な悲観的ロックであるロックテーブルです。通常、トランザクションで使用されます。データロック時間が非常に長くなる場合があります。実際の状況に応じて選択してください。
  • オプティミスティックロック-オプティミスティックロックは、データが更新されたときにのみテーブルをロックし、それ以外のときはテーブルをロックしないため、ペシミスティックロックよりも効率的です。楽観的ロックを実装する方法はたくさんあります。これは、バージョンまたはその他のステータス条件によって実現できます。
  1. 更新table_xxxset name =#name#、version = version + 1を実現します。ここで、version =#version#はバージョン番号を使用します。
  2. 条件による制限updatetable_xxx set avai_amount = avai_amount-#subAmount#where avai_amount-#subAmount#> = 0要件:quality-#subQuality#> =、このシナリオはバージョン番号なしに適しており、更新のみがデータセキュリティ検証に適しています。在庫モデルの場合、シェア控除とロールバックシェア、より高いパフォーマンス。
  • 注:楽観的ロックの更新操作では、主キーまたは一意のインデックスを使用して更新するのが最適です。これは行ロックです。そうしないと、更新中にテーブルがロックされます。上記の2つのSQLを次のように変更することをお勧めします。以下の2つ
update table_xxx set name=#name#,version=version+1 where id=#id# and version=#version#;
update table_xxx set avai_amount=avai_amount-#subAmount# where id=#id# and avai_amount-#subAmount# >= 0;
  • 分散ロック-データを挿入する例を引き続き取り上げます。分散がシステムの場合、グローバルに一意のインデックスを作成することは困難です。たとえば、一意のフィールドを特定できません。現時点では、分散ロックは3番目の方法で導入できます。 -パーティシステム(redisまたはzookeeper)、ビジネスシステムへのデータの挿入または更新、分散ロックの取得、操作の実行、およびロックの解放。これは実際には、マルチスレッドの同時ロックを複数のシステムに導入するというアイデアです。つまり、分散システムのアイデアのソリューションです。キーポイント:長いプロセスプロセスでは、同時に実行できない必要があります。プロセスを実行する前に、特定のフラグ(ユーザーID +サフィックスなど)に基づいて分散ロックを取得できます。他のプロセスを実行すると、ロックが実行されます。正常に実行できるものが存在する可能性があります。実行が完了したら、分散ロックを解放します(分散ロックはサードパーティシステムによって提供される必要があります)。

  • select + insert——べき等性をサポートし、繰り返し実行をサポートするために、同時実行性の低いバックグラウンドシステム、またはタスクJOBの場合、簡単な処理方法は、最初にいくつかの重要なデータをクエリして、実行されたかどうかを判断することです。進捗状況それだけです 注:コアの同時実行性の高いプロセスには、この方法を使用しないでください。

  • ステートマシンはべき等です-ドキュメント関連ビジネスまたはタスク関連ビジネスの設計では、ステートマシン(状態変化図)が確実に関与します。つまり、ビジネスドキュメントに状態があり、状態は異なる状態で変化します。一般に、有限状態機械がありますが、このとき、状態機械がすでに次の状態にある場合、この時点で前の状態の変化が起こり、理論的には変化しません。このように、有限状態マシンのべき等性が保証されています。注:注文やその他のドキュメントビジネスには長い状態フローがあります。ステートマシンを深く理解する必要があります。これは、ビジネスシステムの設計機能の向上に大いに役立ちます。

  • 外部インターフェースを提供するAPIのべき等性を確保する方法。たとえば、UnionPayが提供する支払いインターフェース:支払い要求を送信するには、マーチャントに接続する必要があります:ソースソース、seqシリアル番号
    source + seqは、複数の支払いを防ぐためにデータベースで一意にインデックス付けされます(同時の場合のみ1つのリクエストを処理できます)。重要:外部インターフェイスのべき等呼び出しをサポートするために、インターフェイスには渡す必要のある2つのフィールドがあります。1つはソースソースで、もう1つはソースシリアル番号シーケンスです。これら2つのフィールドは結合され、プロバイダーシステムで一意にインデックス付けされます。 、サードパーティとして呼び出す場合は、まず処理済みかどうかを確認し、対応する処理結果を返します。処理されていない場合は、対応する処理を行って結果を返します。べき等で親しみやすいようにするには、最初にトランザクションが処理されたかどうかを確認する必要があります。クエリを実行せずにビジネスシステムを直接挿入すると、エラーが報告されますが、実際には処理されています。

実用的な操作-トークンに基づいてインターフェイスの同等性を解決します

  • インターフェイスの作成2つのメソッドの追加、トークンの作成、トークンの確認
/**
 * @Author wyj PreventDuplicateSubmissions 接口冥等性  防止重复提交
 * @Date: 2020-12-12 20:41
 */
public interface IdempotentByTokenService {
    
    
    /**
     * 创建token
     * @return
     */
     R createToken();

    /**
     * 检查token
     * @return
     */
     boolean checkToken( );
}
  • インターフェイスを実装し、メソッドロジックを実装します
/**
 * @Author wyj
 * @Date: 2020-12-12 20:46
 */
@Service
public class IdempotentByTokenServiceImpl implements IdempotentByTokenService {
    
    
    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 创建token
     * @return
     */
    @Override
    public R createToken() {
    
    
        String str = UUID.randomUUID().toString().replace("-", "");
        StringBuilder token = new StringBuilder();
        token.append(Rediskey.IDEMPOTENT_PREFIX).append(str);
        redisTemplate.opsForValue().set(token.toString(), token.toString(), RedisExpire.FIVE_MINUTES, TimeUnit.SECONDS);
        return R.success(token.toString());
    }

    /**
     * 检验token
     * @return
     */
    @Override
    public boolean checkToken()  {
    
    
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        String token = request.getHeader(Rediskey.IDEMPOTENT_TOKEN_NAME);
        if (StringUtils.isEmpty(token)) {
    
    
            token = request.getParameter(Rediskey.IDEMPOTENT_TOKEN_NAME);
            if (StringUtils.isEmpty(token)) {
    
    
                throw new ApiMallPlusException("参数不合法,未传防止重复提交token");
            }
        }
        //如果redis中不包含该token,说明token已经被删除了,抛出请求重复异常
        if (!redisTemplate.hasKey(token)){
    
    
            throw new ApiMallPlusException("上次请求还在处理,请稍后在请求!");
        }
        Boolean del=redisTemplate.delete(token);
        //如果删除不成功(已经被其他请求删除),抛出请求重复异常
        if (!del){
    
    
            throw new ApiMallPlusException("上次请求还在处理,请稍后在请求!");
        }
        return  true;
    }
}
  • カスタムアノテーションを作成し、インターフェイスのべき等性を実現する必要があるメソッドにこのアノテーションを追加し、トークンの検証を実現します
/**
 * @author wyj
 * 通过自定义注解在需要实现接口幂等性的方法上添加此注解,实现token验证
 */
@Target({
    
    ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiIdempotent {
    
    
}
  • インターフェイスとその他の側面を定義し、@ ApiIdempotentのインターフェイスをマークすると、割礼チェックトークンが実行されます
/**
 * @Author wyj
 * @Date: 2020-12-13 13:02
 */
@Aspect
@Component
@Slf4j
public class IdempotentAspect {
    
    
    @Autowired
    private IdempotentByTokenService idempotentByTokenService;

    @Pointcut("@annotation(com.cgmcomm.mallplus.component.ApiIdempotent)")
    public void idempotentCut() {
    
    
    }

    @Around("idempotentCut()")
    public Object around(ProceedingJoinPoint point)  {
    
    
        return idempotentByTokenService.checkToken();
    }
}
  • テスト
/**
 * @Author wyj
 * @Date: 2020-12-13 11:13
 */
@RestController
@RequestMapping("api")
@Api(description = "获取接口冥等性token防止重复提交",value = "IdempotentController")
public class IdempotentController {
    
    

    @Autowired
    private IdempotentByTokenService idempotentByTokenService;

    /**
     * 获取接口冥等性token
     * @return
     */
    @GetMapping("/get/token")
    @ApiOperation("获取防止重复提交token")
    public Object  getToken(){
    
    
        return idempotentByTokenService.createToken();
    }


    @GetMapping("/test/Idempotence")
    @ApiIdempotent
    public boolean testIdempotence() {
    
    
        String token = "接口幂等性测试";
        return true;
    }
}
  • テスト手順:最初にgetインターフェイスの同等のトークンインターフェイスにアクセスしてトークンを取得し、取得したトークンをテストインターフェイスに渡します。テストインターフェイスに2回アクセスした後、1回目は成功し、2回目は操作を繰り返さないように求めるプロンプトであることがわかります。
  • 上記は、トークンに基づいてインターフェイスの同等性を実現するためのものです

おすすめ

転載: blog.csdn.net/weixin_45528650/article/details/111148741