您好,登錄后才能下訂單哦!
這篇文章主要講解了“如何解決mongodb深分頁的問題”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“如何解決mongodb深分頁的問題”吧!
突然有一天,有用戶反饋,保存的個人模板全都不見了,當聽到這個消息的時候,第一個想法就是懷疑是用戶自己刪除了,因為有日常的開發任務,當時并沒有在意,自此每隔三五天就有用戶反饋同類的問題,這時候下意識的想,之前用戶沒反饋,現在反饋的多了起來,是不是最近上線的程序有bug,做了刪除操作,緊急的查看了一下代碼上線記錄,發現并沒有進行刪除模板操作,同時反饋的還有模板加載響應很慢,偶爾接口返回500,測試同事試了一下,加載數據是正常的,抱著質疑的態度開始深入了分析了這部分業務邏輯和程序的編寫,發現了一些端倪:
新用戶另存為我的模板會創建一個 “個人模板” 的場景,場景id存儲在用戶的數據表中;
保存的模板頁和場景頁存儲在mongodb的同一個表中,數據表的體量有十億多條數據;
查詢功能有分頁,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分頁,優化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分頁優化并不能從根本解決加載慢的問題,新的優化方案隨即產生:
場景頁和模板頁分開存儲,統計了一下,模板頁的總數一共七百多萬,其余的都是場景頁數據;
新用戶另存為個人模板不產生 “個人模板” 場景,用戶表中存儲場景ID;
在MySQL庫中建立userId和pageId的關系表;
優化后的查詢如下圖:
那么有兩個問題,這么大的體量數據,怎么遷移模板頁數據呢? 新產生的模板頁數據怎么進行存儲呢? 想了一下解決方案:
模板頁數據雙寫,新插入的存儲在不同的mongodb表中,并在MySQL中建立 UID和Tpl的關系;
開發模板遷移程序,從用戶角度出發,輪訓每一位有模板標識的用戶獲取sceneId,查詢出模板頁,隨之保存到新表,建立UID和Tpl的關系
接下來就開始去按照這個優化方案去執行:
第一步:在MySQL中建立UID和Tpl的關系表,先進行數據雙寫 ,數據關系表如下圖
2、 第二步,開發模板遷移程序,遷移的辦法有好幾種,第一種:使用ETL工具進行數據遷移、 第二種:查出歷史數據,發送到MQ中,設置一定數量的消費者使用多線程方式去消費執行,最終我覺得最優方案是第二種,如下圖:
流程定義好了,為了不影響業務的正常執行,一般遷移數據這樣子的工作都是從數據庫的從庫獲取數據, 接下來就開發遷移程序,首先建立兩個項目,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的利用率并不高,如下圖:
查了下資料,consumer utilisation 低的原因有三點
1、消費者太少;
2、消費端的ack太慢;
3、消費者太多。
因為我設置了 factory.setAcknowledgeMode(AcknowledgeMode.NONE); 那么就不存在第二種原因,那么我就調整了一下vm option參數,加大速度,很快consumer utilisation一直持續在96%以上,程序運行不到3個小時,數據都已經遷移完畢;
優化后的查詢速度如下圖:
感謝各位的閱讀,以上就是“如何解決mongodb深分頁的問題”的內容了,經過本文的學習后,相信大家對如何解決mongodb深分頁的問題這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。