10,000 個の同時プログラムとコードを実行する Java スタンドアロン スパイク

まず、通常のロックとトランザクションのスパイクのパフォーマンスを見てみましょう。

例証します:

1. ここで seckill ビジネスを実行するには 100 ミリ秒かかります

2. コンピューターには 16g メモリ、4 コア、8 スレッド、CPU i7 第 7 世代、およびデータベース接続プール最大 = 20 が装備されています。 


    @RequestMapping("/purchase2")
    public ResultJson purchase2( Long productId){
        int userId = new Random().nextInt(10000);
        UserInfo.setUserId(userId);
        RLock lock = redissonClient.getLock("LOCK");
        lock.lock();
        OrderVO purchase = null;
        try {
            purchase = tOrderService.purchase2(productId);
            if(purchase.isSuccess()){
                return new ResultJson<>(200, "成功下单", purchase);
            }else{
                return new ResultJson<>(500, purchase.getMsg(), purchase);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        lock.unlock();
        return new ResultJson<>(500, "下单失败", null);
    }

100 製品に対して 8000 の同時 seckill

1. 通常のロックとトランザクション 100 商品 8000 同時実行、100 アイテムを保存するのに 13 秒かかり、スループットは1 秒あたり 9 回ここで、jmeter は 2000 スレッドしか実行せず、遅すぎて待つことができないため停止します。

 

1000 個の商品、8000 個の同時フラッシュキル

1. 通常のロックとトランザクション 1000 項目、8000 同時実行、1000 項目を保存するには199 秒かかります。ここでは、jmeter は 2000 スレッドのみを実行し、遅すぎて待ちたくないために停止しました。

以下は、AOP 処理を使用して Seckill アノテーションと ProductIdMark アノテーションをカスタマイズする、私の最適化されたソリューションです。

    @RequestMapping("/purchase")
    @Seckill(tableName = "products", inventoryColumn = "prod_num" ,productIdColumn="id" )
    public ResultJson purchase(@ProductIdMark Long productId){
        OrderVO purchase = null;
        try {
            purchase = tOrderService.purchase(productId);
            if(purchase.isSuccess()){
                return new ResultJson<>(200, "成功下单", purchase);
            }else{
                return new ResultJson<>(500, purchase.getMsg(), purchase);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return new ResultJson<>(500, "下单失败", null);
}

100 製品に対して 8000 の同時 seckill

1. 100 項目の同時ロックとトランザクションが 8,000 件ある場合、100 項目を保存するには4 秒かかり、スループットは1 秒あたり1,034 回です。

 

1000 製品に対して 8000 の同時 seckill

1. 通常のロックとトランザクション 1000 項目 8000 同時実行、1000 項目の保存には 7 秒かかり、スループットは1 秒あたり615 回

 

分析する

最適化前 100 商品: 保管に 13 秒、スループットに 9/秒

100 個のアイテムを最適化した後: 倉庫保管に 4 秒かかり、スループットは 1034/秒

1000 アイテムの最適化前: ストレージ 199 秒、スループット 9/秒

1,000 個の商品が最適化された後: 保管に 7 秒かかり、スループットは 615/秒です

まだ大きな差があることがわかります

実装アイデア: すべてのリクエストは aop によってインターセプトされ、aop はユーザーを redis に保存します。1000 個の製品の購入率は 70% であるため、redis には 1500 個のユーザー ID を保存するだけで済み、冗長な製品は売り切るために直接返されます。次に 1500 の番です ユーザーは 1000 製品をめぐって競争します。ウォームアップ中に、1000 製品を 20 の部分に分割します。つまり、1 つの部分につき 50 製品です。lockName ロックとして使用するために、20 製品を倉庫に保管します。同期ローテーション トレーニングは次の結果を取得します。データベース内のlockNameを取得し、対応するlockNameを取得します 記録された在庫を差し引くことができます 一般的なプロセスは次のとおりです

aop の主な実装は次のとおりです。

@Aspect
@Configuration
public class SeckillAspect {

    @Autowired
    private RedissonClient redissonClient;
    @Autowired
    private ProductsSubsectionService productsSubsectionService;

    @Pointcut("@annotation(seckill)")
    public void pointCut(Seckill seckill) {
    }

    private final String infoKey= "info:productId:";
    private final String indexKey= "index:productId:";
    private final Long time=60*10L;
    @Around("pointCut(seckill)")
    public Object around(ProceedingJoinPoint joinPoint,Seckill seckill) throws Throwable {
        String className = joinPoint.getTarget().getClass().getName();
        String methodName = joinPoint.getSignature().getName();
        String appName = className +":"+ methodName;
        Long productId = getProductId(joinPoint);
        //模拟用户
        int userId = new Random().nextInt(100000);
        UserInfo.setUserId(userId);
        //初始化商品数据
        init(appName,productId);

        if(stopRun(appName,productId)){
            return new ResultJson<>(500, "商品售空", null);
        }

        //防止同一个用户使用外挂疯狂点击
        RMap<Integer, Integer> map = redissonClient.getMap(appName+":userClicks");
        RLock lockClicks = redissonClient.getLock("LOCK:"+appName+":5");
        try {
            lockClicks.lock();
            map.put(userId,map.get(userId)+1);
        }finally {
            lockClicks.unlock();
        }

        if(map.get(userId) > 1){
            return new ResultJson<>(500, "请勿重复提交", null);
        }

        RLock lock = redissonClient.getLock("LOCK:"+appName+":2");
        lock.lock();
        if(isUpdatePrimaryTable(appName,productId)){
            updatePrimaryTable(seckill,productId);
        }
        String lockName = getLockName(appName, productId);
        RLock lock4 = redissonClient.getLock(lockName);
        try {
            lock4.lock();
            lock.unlock();
            ProductsSubsection productsSubsection = productsSubsectionService.queryByLockMark(lockName);
            if(productsSubsection.getNumber()==0){
                return new ResultJson<>(500, "商品售空", null);
            }
            Object proceed = joinPoint.proceed();
            productsSubsection.setNumber(productsSubsection.getNumber()-1);
            productsSubsectionService.update(productsSubsection);
            return proceed;
        }finally {
            lock4.unlock();
        }
    }

    private Long getProductId(ProceedingJoinPoint joinPoint){
        Object[] args = joinPoint.getArgs();
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        //一维数组是注解位置,二维数组是注解个数
        Annotation[][] parameterAnnotations = method.getParameterAnnotations();
        Long productId=null;
        boolean noProductId = true;
        for (int i = 0; i < parameterAnnotations.length; i++) {
            Annotation[] annotation = parameterAnnotations[i];
            for (Annotation var : annotation) {
                if (var.annotationType().equals(ProductIdMark.class)) {
                    productId= (Long) args[i];
                    noProductId = false;
                    break;
                }
            }
        }
        if(noProductId){
            throw new RuntimeException("形参未指定注解:ProductId");
        }
        return productId;
    }

    private boolean init(String appName, Long productId){

        if(!redissonClient.getMap(appName).isEmpty()){
            //已初始化
            return false;
        }
        RLock lock = redissonClient.getLock("LOCK:"+appName+":2");
        try {
            lock.lock();
            if(redissonClient.getMap(appName).isEmpty()){
                List<ProductsSubsection> list = productsSubsectionService.queryByProdId(productId);
                int inventory =0 ;
                LinkedHashSet<String> locks = new LinkedHashSet<>();
                for (ProductsSubsection subsection : list) {
                    inventory+=subsection.getNumber();
                    locks.add(subsection.getLockMark());
                }
                CommodityInfo info = new CommodityInfo();
                info.setLockNames(locks);
                info.setInventory(inventory);
                RMap<String, Object> map = redissonClient.getMap(appName);
                map.expire(time, TimeUnit.SECONDS);
                String key= infoKey+ productId;
                String key2= indexKey+ productId;
                map.put(key,info);
                map.put(key2,0);
            }
        }finally {
            lock.unlock();
        }
        return true;
    }

    //统计用户点击数
    private Integer userClicks(String appName,int size){
        RMap<Integer, Integer> user = redissonClient.getMap(appName+":userClicks");
        user.expire(time, TimeUnit.SECONDS);

        RLock lock = redissonClient.getLock("LOCK:"+appName+":3");
        int userLength = user.size();
        if(userLength >= size){
            return user.size();
        }
        try {
            lock.lock();
            if(user.size() < size){
                //初始化用户点击次数
                int userId = UserInfo.getUserId();
                Integer clicks = user.get(userId);
                if( clicks == null){
                    user.put(userId,0);
                }else{
                    user.put(userId,clicks+1);
                }
            }
        } finally {
            lock.unlock();
        }
        return user.size();
    }

    private boolean stopRun(String appName,Long productId){
        RMap<String, Object> map = redissonClient.getMap(appName);
        map.expire(time, TimeUnit.SECONDS);
        CommodityInfo info = (CommodityInfo) map.get( infoKey+ productId);
        double size = info.getProbability() *  info.getInventory();
        RMap<Integer, Integer> user = redissonClient.getMap(appName+":userClicks");
        user.expire(time, TimeUnit.SECONDS);

        if(userClicks(appName, (int) size) >= size && !user.containsKey(UserInfo.getUserId())){
            return true;
        }
        return false;
    }

    private String getLockName(String appName,Long productId){
        String key= infoKey+ productId;
        String key2= indexKey+ productId;
        RMap<Object, Object> map = redissonClient.getMap(appName);
        map.expire(time, TimeUnit.SECONDS);
        CommodityInfo  info = (CommodityInfo) map.get(key);
        Integer index = (Integer) map.get(key2);
        List<String> lockNamesList = new ArrayList<>(info.getLockNames());
        map.put(key2,index+1);
        return lockNamesList.get(index % lockNamesList.size());
    }



    private boolean isUpdatePrimaryTable(String appName,Long productId){
        String key= infoKey+ productId;
        String key2= indexKey+ productId;
        RMap<Object, Object> map = redissonClient.getMap(appName);
        map.expire(time, TimeUnit.SECONDS);
        CommodityInfo  info = (CommodityInfo) map.get(key);
        Integer index = (Integer) map.get(key2);
        return index==info.getInventory();
    }

    //更新主表
    private void updatePrimaryTable(Seckill seckill,Long productId){
        // 获取注解的值
        String tableName = seckill.tableName();
        String inventoryColumn = seckill.inventoryColumn();
        String productIdColumn = seckill.productIdColumn();
        System.out.println("开始更新"+tableName+"主表数据");
        productsSubsectionService.updatePrimaryTable(tableName,inventoryColumn,productIdColumn,productId);
    }
}

プロジェクトアドレス:

すべての seckill ビジネスに共通して、製品テーブルに在庫がある限り、製品 ID は問題ありません

使用規則:

1. セグメントテーブルを作成する

CREATE TABLE `products_subsection` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `prod_id` bigint(20) DEFAULT NULL,
  `number` int(11) DEFAULT NULL,
  `lock_mark` varchar(20) DEFAULT NULL,
  `create_date` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=21 DEFAULT CHARSET=utf8mb4

2.コントローラーメソッドで Seckill アノテーションと @ProductIdMark を使用する

//商品表名,库存字段名,商品id名
@Seckill(tableName = "products", inventoryColumn = "prod_num" ,productIdColumn="id" )
//用于表示商品id
@ProductIdMark

訪問後の効果

アーキテクチャ図

 

おすすめ

転載: blog.csdn.net/qq_42058998/article/details/130162722