溫馨提示×

溫馨提示×

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

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

如何解決mongodb深分頁的問題

發(fā)布時間:2021-07-09 17:52:11 來源:億速云 閱讀:1182 作者:chen 欄目:大數(shù)據(jù)

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

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

  1.     新用戶另存為我的模板會創(chuàng)建一個 “個人模板” 的場景,場景id存儲在用戶的數(shù)據(jù)表中;

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

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

    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深分頁的問題        

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

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

  2.     優(yōu)化mongodb分頁,優(yōu)化mongodb分頁,優(yōu)化如下

    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());
    }


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

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

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

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

     優(yōu)化后的查詢?nèi)缦聢D:

        如何解決mongodb深分頁的問題

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

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

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

         接下來就開始去按照這個優(yōu)化方案去執(zhí)行:

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

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

如何解決mongodb深分頁的問題

        流程定義好了,為了不影響業(yè)務的正常執(zhí)行,一般遷移數(shù)據(jù)這樣子的工作都是從數(shù)據(jù)庫的從庫獲取數(shù)據(jù), 接下來就開發(fā)遷移程序,首先建立兩個項目,data-provider,data-consumer,data-provider 查詢用戶,把另存為模板的場景ID發(fā)送到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));
        }
        // 每一次的執(zhí)行跨度
        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")){
                // 得到下一次循環(huán)的最后一個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的值的原因是防止程序執(zhí)行中斷,自己設置 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){
                //查詢是否已經(jīng)同步過
                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表的頁面數(shù)據(jù)
                scenePageDoc.removeScenePageById(pageId);
            }
        }catch (Exception e){
            logger.error("執(zhí)行同步程序出現(xiàn)異常,error param is ={}", message);
            scenePageDoc.saveSyncError(pageId);
            e.printStackTrace();
        }
    }
}

mq優(yōu)化

@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個數(shù)
        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 &

但是執(zhí)行發(fā)現(xiàn),consumer的利用率并不高,如下圖:

如何解決mongodb深分頁的問題

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

1、消費者太少;

2、消費端的ack太慢;

3、消費者太多。

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

優(yōu)化后的查詢速度如下圖:

如何解決mongodb深分頁的問題

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

向AI問一下細節(jié)

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

AI