亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

如何解決mongodb深分頁的問題

發布時間:2021-07-09 17:52:11 來源:億速云 閱讀:1196 作者:chen 欄目:大數據

這篇文章主要講解了“如何解決mongodb深分頁的問題”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“如何解決mongodb深分頁的問題”吧!

        突然有一天,有用戶反饋,保存的個人模板全都不見了,當聽到這個消息的時候,第一個想法就是懷疑是用戶自己刪除了,因為有日常的開發任務,當時并沒有在意,自此每隔三五天就有用戶反饋同類的問題,這時候下意識的想,之前用戶沒反饋,現在反饋的多了起來,是不是最近上線的程序有bug,做了刪除操作,緊急的查看了一下代碼上線記錄,發現并沒有進行刪除模板操作,同時反饋的還有模板加載響應很慢,偶爾接口返回500,測試同事試了一下,加載數據是正常的,抱著質疑的態度開始深入了分析了這部分業務邏輯和程序的編寫,發現了一些端倪:

  1.     新用戶另存為我的模板會創建一個 “個人模板” 的場景,場景id存儲在用戶的數據表中;

  2.     保存的模板頁和場景頁存儲在mongodb的同一個表中,數據表的體量有十億多條數據;

  3.     查詢功能有分頁,mongodb數據比較大時,對于深分頁性能相對較差,分頁代碼 

    query = new Query(Criteria.where("sceneId").is(sceneId)).with(new Sort(Sort.Direction.DESC, "id"))
    				.skip((page.getPageNo() - 1) * page.getPageSize()).limit(page.getPageSize());


       查詢方式如下圖:

如何解決mongodb深分頁的問題        

 那么顯而易見的模板丟失的原因就分析出來了,是用戶把 “個人模板” 場景給刪除了,從而導致場景下的模板頁隨之被刪除,分析出原因之后,想到了以下的優化方案:

  1.     場景列表查詢不顯示 “個人模板” 場景數據;

  2.     優化mongodb分頁,優化mongodb分頁,優化如下

    if (tplId == null){
       // 分頁獲取
       query = new Query(Criteria.where("sceneId").is(sceneId)).with(new Sort(Sort.Direction.DESC, "id"))
             .skip((page.getPageNo() - 1) * page.getPageSize()).limit(page.getPageSize());
    }else{
       query = new Query(Criteria.where("sceneId").is(sceneId).lte(tplId)).with(new Sort(Sort.Direction.DESC, "id")).limit(page.getPageSize());
    }


        優化方案定好之后,很快程序就在預發布部署測試并上線,果不其然,用戶在列表看不到 “個人模板” 場景之后,反饋模板丟失的問題沒了,但是 個人模板列表加載無響應的問題還在持續存在,難道是mongodb分頁優化沒起作用?其實并不是,因為表的數據體量已經達到十億級別,接口的響應最大時間設置為2秒,超過了最大響應時間就返回500,那么mongodb分頁優化并不能從根本解決加載慢的問題,新的優化方案隨即產生:

  1. 場景頁和模板頁分開存儲,統計了一下,模板頁的總數一共七百多萬,其余的都是場景頁數據;

  2. 新用戶另存為個人模板不產生 “個人模板” 場景,用戶表中存儲場景ID;

  3. MySQL庫中建立userId和pageId的關系表;

     優化后的查詢如下圖:

        如何解決mongodb深分頁的問題

        那么有兩個問題,這么大的體量數據,怎么遷移模板頁數據呢?     新產生的模板頁數據怎么進行存儲呢? 想了一下解決方案:

  1.     模板頁數據雙寫,新插入的存儲在不同的mongodb表中,并在MySQL中建立 UID和Tpl的關系;

  2.      開發模板遷移程序,從用戶角度出發,輪訓每一位有模板標識的用戶獲取sceneId,查詢出模板頁,隨之保存到新表,建立UID和Tpl的關系

         接下來就開始去按照這個優化方案去執行:

  1.  第一步:在MySQL中建立UID和Tpl的關系表,先進行數據雙寫 ,數據關系表如下圖如何解決mongodb深分頁的問題

2、 第二步,開發模板遷移程序,遷移的辦法有好幾種,第一種:使用ETL工具進行數據遷移、   第二種:查出歷史數據,發送到MQ中,設置一定數量的消費者使用多線程方式去消費執行,最終我覺得最優方案是第二種,如下圖:

如何解決mongodb深分頁的問題

        流程定義好了,為了不影響業務的正常執行,一般遷移數據這樣子的工作都是從數據庫的從庫獲取數據, 接下來就開發遷移程序,首先建立兩個項目,data-provider,data-consumer,data-provider 查詢用戶,把另存為模板的場景ID發送到mq,data-consumer接受場景ID,去查詢page,并分別保存到模板頁新表和MySQL庫的UID和Tpl的關系表中

data-provider代碼如下:

@Component
public class TaskTplSyncRunner implements ApplicationRunner {
    private Logger logger = LoggerFactory.getLogger(this.getClass());
    @Autowired
    private TaskService taskService;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        AtomicInteger total = new AtomicInteger(0);
        AtomicInteger count = new AtomicInteger(1);
        // 開始sceneId
        Long start = null;
        if (!CollectionUtils.isEmpty(args.getOptionValues("start"))) {
            start = args.getOptionValues("start").get(0) == null ? 1 :
                    Long.valueOf(args.getOptionValues("start").get(0));
        }
        // 最后sceneId
        Long end = null;
        if (!CollectionUtils.isEmpty(args.getOptionValues("end"))) {
            end = args.getOptionValues("end").get(0) == null ? 1000 :
                    Long.valueOf(args.getOptionValues("end").get(0));
        }
        // 每一次的執行跨度
        Integer pageSize = null;
        if (!CollectionUtils.isEmpty(args.getOptionValues("pageSize"))) {
            pageSize = args.getOptionValues("pageSize").get(0) == null ? 2000 :
                    Integer.valueOf(args.getOptionValues("pageSize").get(0));
        }
        logger.info("init start value is ={},end value is={}", start, end);
        while (true) {
            Map<String, Object> objectMap = taskService.sendTplMq(start, end);
            if (objectMap.containsKey("endSceneId")){
                // 得到下一次循環的最后一個id
                end = Long.valueOf(objectMap.get("endSceneId").toString());
                start = end - pageSize;
            }
            count.getAndIncrement();
            if (objectMap.containsKey("total")) {
                total.addAndGet(Integer.valueOf(objectMap.get("total") + ""));
            }
            // 是最后一個用戶直接跳出
            if (start < 1101) {
                break;
            }
       }
        logger.info("execute personage tpl sync success,total count {} ", total.intValue());
        logger.info("execute personage tpl sync task end。。。。。。。。。。");
    }
}
@Async
    public Map<String, Object> sendTplMq(Long start, Long end) {
        Map<String,Object> paramMap = new HashMap<>();
        paramMap.put("start",start);
        paramMap.put("end",end);
        List<Scene> sceneList = sceneDao.findSceneList(paramMap);

        Map<String,Object> resultMap = new HashMap<>();
        sceneList.stream().forEach(scene -> {
            amqpTemplate.convertAndSend("exchange.sync.tpl","scene.tpl.data.sync.test", JsonMapper.getInstance().toJson(scene));
        });
        resultMap.put("total",sceneList.size());
        resultMap.put("resultFlag",false);
        resultMap.put("endSceneId",start);
        return resultMap;
    }
從ApplicationArguments中獲取start,end,pageSize的值的原因是防止程序執行中斷,自己設置 VM options

data-consumer 代碼如下:

@Component
public class Receiver {

    Logger logger = LoggerFactory.getLogger(this.getClass());

    @Autowired
    private ScenePageDoc scenePageDoc;
    @RabbitListener(queues = "queue.scene.tpl.data.sync.test", containerFactory = "containerFactory")
    public void receiveTplMessage(String message) {
        Long pageId = null;
        try {
            HashMap<String, Object> hashMap = JsonMapper.getInstance().fromJson(message, HashMap.class);
            pageId = Long.valueOf(String.valueOf(hashMap.get("pageId")));
            // 查詢scene_page表中的page信息
            ScenePage scenePage = scenePageDoc.findPageById(pageId);
            if (scenePage != null){
                //查詢是否已經同步過
                ScenePageTpl scenePageTpl = scenePageDoc.findPageTplById(pageId);
                if (scenePageTpl == null){
                    scenePageDoc.savePageTpl(scenePage);
                    logger.info("execute sync success pageId value is={}",pageId);
                    // 建立UID 和 tpl 的關系
                }
                // 刪除eqs_scene_page表的頁面數據
                scenePageDoc.removeScenePageById(pageId);
            }
        }catch (Exception e){
            logger.error("執行同步程序出現異常,error param is ={}", message);
            scenePageDoc.saveSyncError(pageId);
            e.printStackTrace();
        }
    }
}

mq優化

@Configuration
public class MqConfig {

    @Bean
    public SimpleRabbitListenerContainerFactory containerFactory(SimpleRabbitListenerContainerFactoryConfigurer configurer,ConnectionFactory connectionFactory){
        SimpleRabbitListenerContainerFactory factory=new SimpleRabbitListenerContainerFactory();
        // 設置線程池
        ExecutorService service = new ThreadPoolExecutor(60,90,60, TimeUnit.SECONDS,new LinkedBlockingQueue<Runnable>(),new ThreadPoolExecutor.CallerRunsPolicy());
        factory.setTaskExecutor(service);
        //設置consumer個數
        factory.setConcurrentConsumers(60);
        // 關閉ack
        factory.setAcknowledgeMode(AcknowledgeMode.NONE);
        configurer.configure(factory,connectionFactory);
        return factory;
    }
}

下一步部署程序

nohup java -jar -Djava.security.egd=file:/dev/./urandom eqxiu-data-provider-0.0.5.jar --start=81947540 --end=81950540 --pageSize=3000 > /data/logs/tomcat/data-provider/spring.log &


nohup java -jar eqxiu-data-consumer-0.0.5.jar > /data/logs/tomcat/data-consumer/spring.log &

但是執行發現,consumer的利用率并不高,如下圖:

如何解決mongodb深分頁的問題

查了下資料,consumer utilisation 低的原因有三點

1、消費者太少;

2、消費端的ack太慢;

3、消費者太多。

因為我設置了 factory.setAcknowledgeMode(AcknowledgeMode.NONE); 那么就不存在第二種原因,那么我就調整了一下vm option參數,加大速度,很快consumer utilisation一直持續在96%以上,程序運行不到3個小時,數據都已經遷移完畢;

優化后的查詢速度如下圖:

如何解決mongodb深分頁的問題

感謝各位的閱讀,以上就是“如何解決mongodb深分頁的問題”的內容了,經過本文的學習后,相信大家對如何解決mongodb深分頁的問題這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

池州市| 德钦县| 天镇县| 田林县| 武宣县| 江门市| 普宁市| 多伦县| 马尔康县| 商河县| 龙游县| 绿春县| 凤凰县| 普兰店市| 习水县| 永济市| 凌源市| 成都市| 黄陵县| 五家渠市| 金寨县| 玉环县| 依兰县| 廉江市| 荥阳市| 高淳县| SHOW| 新河县| 新宾| 宜章县| 阿城市| 丹凤县| 漳平市| 浪卡子县| 娱乐| 若羌县| 定边县| 麦盖提县| 瑞金市| 南昌县| 亚东县|