リソース使用非同期マルチコンディションプールの動的管理

背景

デジタル証明書と、そのような秘密鍵2048、平均単消費近い300msの特に高い強度などのアプリケーションCSRデータ、公開鍵と秘密鍵のペアの生成に必要な秘密鍵と乱数と大きな変動を発生させるの経時変化;

KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
generator.initialize(2048, new SecureRandom());

long startTime = System.currentTimeMillis();
for(int i = 0; i < 100; i++) {
     generator.generateKeyPair();
}
System.out.println("cost: " + (System.currentTimeMillis() - startTime));
复制代码

事前に生成された秘密鍵プールに格納されなければならない秘密鍵への迅速な対応を確保するためには、秘密鍵プールから直接アプリケーションでのデジタル証明書を読むことができます。

ビジネス、安定した業務量の小さなスケールに起因する初期のシステムでは、導入コスト、鍵の生成と達成するために、同じサービスを右、生産者と消費者の単純なモデルで実行されている証明書の申請を節約するために:

  • 秘密鍵を生成するために連続スレッドプール、コアスレッドプールスレッド=スレッド=(Runtime.getRuntime()availableProcessors() - 1)の最大数。
  • LinkedBlockingQueueは、生成した秘密鍵のペアを使用して保存し、キュー制限2000を設定します。

単純な分散キー・プール

問題

ある日、顧客は、デジタル証明書の申請プロセスを作成するために、顧客とユーザーとして知られているオンラインバンキングの活動のためのインターネットは多くの時間がかかり必要なので、事前に一括で選択する顧客は、ユーザーを作成し、オンライン活動。結果は予測可能でした。

  1. 秘密鍵プールが使い果たされたローカルサービスの証明書の要求、キャッシュ内訳;
  2. ビジネス、同期トリガが秘密鍵のペアを生成し、ユーザーが残業の多くを作成し、よりゆっくりと秘密鍵のプールを生成し、CPUリソースの競争を激化。
  3. サービスが制限IPホワイトリストCAセンターがあり、展開を迅速に行うことができない、ユニファイドネットワークの出口ではありません。
  4. 展開が完了した場合でも、秘密鍵は、秘密鍵生成同期サービスは、一般的な効果残っているため、プールが空です。

最後に、唯一のキラーを頼っ:システムが制限されたと言う顧客と顧客のトラフィックとの通信を制限する......

最適化

独自のビジネスモデルを、次の一般的な問題:

  • かかわらず、ユーザーのビジネスの、バックグラウンドの計算集約型のビジネスに属する秘密鍵を生成しますが、ビジネスのCPUリソースの競合があり、不安定なパフォーマンスの証明書申請のビジネスにつながることができますすること。
  • 秘密鍵ペアの生成能力とカップリングの運用能力、個々の能力の急速な拡大を達成することはできません。
  • 各キューのデータが迅速かつ容易になり、プール内のキャッシュキー内のオブジェクトの数を監視することはできません、すぐに統一された統計値にすることはできません。
  • サービスを再起動した後、迅速なサービスの再起動を許可していないシステムを引き起こし、消えるために秘密鍵を生成しました。

これらの問題に対応して、次のアイデアを最適化します:

  • Redisのは、失われたリスタートプールの問題を解決するために、ユーザーサービスおよびバックオフィスツール、およびキーを切り離すことによって、集中キャッシュバッファ・プールとして導入しました。
  • 秘密鍵のペア、10Wの上限の秘密鍵Redisのキャッシュメモリを使用して、リスト構造。
  • リストRedisの中の要素の数は、総数の35%未満を監視することが、ひいてはサービスクラスタ動的自動拡張(ライトトス)を実装することができるアラーム;
  • 複数のキューreetrantLockと生産者の特性の生産管理を使用した条件制御。
  • プールRedisの合計数がいっぱいになったとき、鍵生成サービスのスレッドプールは秘密鍵のペアを生成し、スレッドが起こされるのを待って、状態を待つ入ります。
  • 固定スレッドプールのスレッドの半分よりも75%少ない5Sのプール春のスケジュール固定速ポーリング間隔内のオブジェクトの数に基づいてサービスを使用して、鍵生成は、秘密鍵生成ウェイク、ウェイクアップすべてのスレッドプールのスレッドが50%未満に秘密鍵を生成します。

システム構造をデカップリングした後、

実装を簡素化

スレッドの数を考慮すると、キャッシュ内のオブジェクトの数よりはるかに小さい、制御10Wは近似のみで、少ない数は、現在のビジネスシナリオでのRedis自体に大きな影響が発生することはありませんよりも、さらにいくつかの

秘密鍵ペアの生成作業員

/**
 * 秘钥对生成执行线程池
 */
public final class KeyPairExecutors {

    // Redis Client 桩类
    private final RedisCacheDAO cacheDAO;
    // 锁
    private final ReentrantLock lock;
    // 与线程数量相当的condition
    private final List<Condition> workerConditions;

    public KeyPairExecutors(ReentrantLock lock, List<Condition> workerConditions) {
        this.cacheDAO = new RedisCacheDAO();
        this.workerConditions = workerConditions;
        this.lock  = lock;
    }

    /**
     * 工作线程构造方法
     */
    public void start() {

        int coreNum = workerConditions.size();
        ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(coreNum, coreNum, 120, TimeUnit.SECONDS,
                new SynchronousQueue<>(),
                new KeyPairGeneratorThreadFactory("keypair_gen_", workerConditions.size()),
                new ThreadPoolExecutor.DiscardOldestPolicy());

        Stream.iterate(0, n -> n + 1).limit(workerConditions.size())
                .forEach( i -> poolExecutor.submit(new KeyPairRunable(cacheDAO, lock, workerConditions.get(i))));
    }

    class KeyPairRunable implements Runnable {

        private final RedisCacheDAO cacheDAO;
        private final ReentrantLock lock;
        private final Condition condition;

        public KeyPairRunable(RedisCacheDAO cacheDAO, ReentrantLock lock, Condition condition) {
            this.cacheDAO = cacheDAO;
            this.lock = lock;
            this.condition = condition;
        }

        @Override
        public void run() {

            while(true) {

                String keyBytes = genKeyPair();

                try {
                    int currentSize = cacheDAO.listLpush(keyBytes);
                    // 写入记录后实时返回当前List元素数
                    if(currentSize >= RedisCacheDAO.MAX_CACHE_SIZE) {
                            System.out.println("cache is full. " + Thread.currentThread().getName() + " ready to park.");

                            lock.lock();
                            condition.await();

                            System.out.println("cache is consuming. " + Thread.currentThread().getName() + " unparked.");
                    }
                } catch (InterruptedException e) {
                    System.out.println(Thread.currentThread().getName() + " is interuupted.");
                } finally {

                    if(lock.isLocked()) {
                        lock.unlock();
                    }
                }
            }
        }

        private String genKeyPair() {

            // TODO 秘钥对桩
            return "";
        }
    }

    class KeyPairGeneratorThreadFactory implements ThreadFactory {

        private final String threadGroupName;
        private final AtomicInteger idSeq;

        public KeyPairGeneratorThreadFactory(String threadGroupName, int maxSeq) {
            this.threadGroupName = threadGroupName;
            this.idSeq = new AtomicInteger(maxSeq);
        }

        @Override
        public Thread newThread(Runnable r) {

            int threadId = idSeq.getAndDecrement();
            if(threadId < 0) {
                throw new UnsupportedOperationException("thread number cannot be out of range");
            }

            return new Thread(r, threadGroupName + "_" + threadId);
        }
    }
}
复制代码

秘密鍵ペアの生成モニター

/**
 * 秘钥对生成定时调度
 */
public enum  KeyPairsMonitor {

    INSTANCE;

    private final ReentrantLock reentrantLock;
    private final List<Condition> conditionList;
    private final RedisCacheDAO redisCacheDAO;
    private final int coreSize;

    KeyPairsMonitor() {

        this.redisCacheDAO = new RedisCacheDAO();
        this.reentrantLock = new ReentrantLock();

        coreSize = Runtime.getRuntime().availableProcessors();
        this.conditionList = new ArrayList<>(coreSize);
        for( int i=0; i< coreSize; i++ ) {
            conditionList.add(reentrantLock.newCondition());
        }
    }

    /**
     * 启动密钥生成任务,开启调度
     */
    public void monitor() {

        KeyPairExecutors executors = new KeyPairExecutors(reentrantLock, conditionList);
        executors.start();

        buildMonitorSchedule();
    }

    /**
     * 构造定时任务
     */
    private void buildMonitorSchedule() {

        ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();
        service.scheduleAtFixedRate(new Runnable() {

            @Override
            public void run() {
                int currentSize = redisCacheDAO.listLlen();
                System.out.println("current cache size is: " + currentSize);

                int executNum = 0;
                if(currentSize <= RedisCacheDAO.HALF_MAX_CACHE_SIZE) {
                    System.out.println("current cache level is under 50% to ." + currentSize);
                    executNum = coreSize;
                } else if(currentSize <= RedisCacheDAO.PERCENT_75_MAX_CACHE_SIZE) {
                    System.out.println("current cache level is under 75% to ." + currentSize);
                    executNum = coreSize >> 1;
                }

                for(int i=0; i < executNum; i++) {
                    try {
                        reentrantLock.lock();
                        conditionList.get(i).signal();
                    } catch (IllegalMonitorStateException e) {
                        // do nothing, condition no await
                    } catch (Exception e) {
                        System.out.println(e.getMessage());
                    } finally {
                        if(reentrantLock.isLocked()) {
                            reentrantLock.unlock();
                        }
                    }

                }
            }

        }, 0, 5, TimeUnit.SECONDS);
    }
复制代码

運転操作を重ねリストのRedis:

public class RedisCacheDAO {

    public static final String DEFAULT_KEYPAIE_CACHE_KEY = "keypaie_redis_list_rsa_byte";
    public static final int MAX_CACHE_SIZE = 1 << 4;
    public static final int HALF_MAX_CACHE_SIZE = MAX_CACHE_SIZE >> 1;
    public static final int PERCENT_75_MAX_CACHE_SIZE = MAX_CACHE_SIZE - (MAX_CACHE_SIZE >> 2);

    private String key;
    private static final AtomicInteger count = new AtomicInteger(1);

    public RedisCacheDAO() {
        this.key = DEFAULT_KEYPAIE_CACHE_KEY;
    }

    public RedisCacheDAO(String key) {
        this.key = key;
    }

    public int listLpush(String value) {

        System.out.println(Thread.currentThread().getName() + " push value");
        return count.addAndGet(1);
    }

    public int listLlen() {
        return count.get();
    }

    public void listPop(int newValue) {
        count.getAndSet(newValue);
    }
}
复制代码

主な方法:

public static void main(String[] args) throws InterruptedException {

        KeyPairsMonitor monitor  = KeyPairsMonitor.INSTANCE;
        monitor.monitor();

        while (true) {
            RedisCacheDAO dao = new RedisCacheDAO();
            Thread.sleep(10);

            dao.listPop(new Random().nextInt(RedisCacheDAO.MAX_CACHE_SIZE));
        }
    }
复制代码

考えます

上記の修正プログラムによると、少なくともすぐに高コアの構成ECS、急速な拡大を購入することができ、トラフィックの急増に直面します。ただし、ディスパッチャは、スケジューリングスレッドが予期せず終了した場合は、ワーカースレッドはサーバが動作しないことを、各サーバに独立して実行を分散させました。

現在考えられて:

1つのアイデア:分散タスクスケジューリングは、保守作業員のスケジュールを設定するために、統一派遣センターを採用しますが、システムの複雑さは、現在の実装よりも大きくなります。

アイデア2:、自治のタイムアウト機構条件を使用することがあり、タイムアウト条件を設定し、タイムアウト後に自動的に上記に起因する問題のリスクを軽減するために、キャッシュallkeysランダム位相アウト戦略で、書かれて生成されました。

おすすめ

転載: juejin.im/post/5d6cff0cf265da03e83b8747