mysql轉ElasticSearch的分析 及JAVA API 初探


前言

最近工作中在進行一些技術優化,為了減少對數據庫的壓力,對於只讀操作,在程序與db之間加了一層-ElasticSearch。具體實現是db與es通過bin-log進行同步,保證數據一致性,代碼調用es查詢數據,與mysql解耦。
優勢:

  • 減少與mysql的耦合,查詢不依賴於mysql特性。因為當前ElasticSearch的勢頭如同json一樣,輕量、簡潔。
  • ElasticSearch擴展性強,可以使用廉價機器平行擴展性能。
  • ElasticSearch對所有字段進行了索引,不用在原mysql表中大量添加索引,減少了數據復雜度。

API的個人理解

由於es的java api文檔不多,因此參照es官方文檔的概念,自己總結了一些api的用法,個人看法,不保證正確。
ElasticSearch官方文檔
Spring-data-es官方文檔

  1. term 和 terms 是包含操作,而不是相等操作,假如真的需要完全匹配這種行為,最好是通過添加另一個字段來實現。
  2. 在 bool 條件中過濾器的順序對性能有很大的影響。更詳細的過濾條件應該被放置在其他過濾器之前,以便在更早的排除更多的文檔。
  3. 由於es有打分功能,所以api是有配合條件的。withFilter->filter->term,terms,range等一系列不打分的聚合條件。withQuery->must->matchPhrase
  4. 查詢語句不僅要查找相匹配的文檔,還需要計算每個文檔的相關性,所以一般來說查詢語句要比 過濾語句更耗時,並且查詢結果也不可緩存。《官方文檔.p.133》(因此在進行mysql遷移時,優先使用filter)

遷移案例分析

實體

@Document(indexName = "dbName", type = "tableName", shards = 6) public class UserInfo{ /** * 主鍵 */ @JsonProperty("id") private Long id; /** * 用戶編號 */ @JsonProperty("user_id") private String userId; /** * 分數 */ @JsonProperty("score") private String score; /** * 創建時間 */ @JsonProperty("order_time") @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") @Field(type = FieldType.Date, index = FieldIndex.not_analyzed, format = DateFormat.custom, pattern = "yyyy-MM-dd HH:mm:ss") private Date createTime;

 

  • queryOne
select * from user_info where id = #{id}

 

public UserInfo getById(String id){ CriteriaQuery query = new CriteraQuery(Criteria.where("id").is(id)); UserInfo userInfo = elasticsearchTemplate.queryForObject(query, UserInfo.class); }
  • queryForList(小數據量)
select * from user_info where user_id in #{userIdList}

 

public List<UserInfo> getByUserIdList(List<String> userIdList){ SearchQuery searchQuery = new NativeSearchQueryBuilder(). withIndices(EsQueryConstant.obtainIndicesName("dbName","tableName")). withFilter(QueryBuilders.termsQuery("user_id",userIdList)). return elasticsearchTemplate.queryForList(searchQuery,UserInfo.class); }
  • queryForList(大數據量)
select * from user_info where crete_time > #{createTime}

 

public List<UserInfo> getByUserIdList(Date createTime){ BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery(). filter(QueryBuilders.rangeQuery("create_time").gt(new DateTime(createTime).toString("yyyy-MM-dd HH:mm:ss"))); SearchQuery searchQuery = new NativeSearchQueryBuilder(). withIndices(EsQueryConstant.obtainIndicesName("dbName","tableName")). withFilter(boolQueryBuilder ).build(); String scrollId = elasticsearchTemplate.scan(searchQuery, TimeValue.timeValueMinutes(20).getMillis(), false); List<UserInfo> result= Lists.newArrayList(); while (true) { Page<UserInfo> userInfoPage = elasticsearchTemplate.scroll(scrollId, TimeValue.timeValueMinutes(20).getMillis(), UserInfo.class); List<UserInfo> userInfoContent= orderEsPage.getContent(); if (CollectionUtils.isEmpty(userInfoContent)) { break; } result.addAll(userInfoContent); } return result; }

由於es采用的是分布式存儲,所以在數據量大到一定程度的情況下,分頁已經變得不可行。比如要拿1000-1010的數據,假設es有6個分片,則每個分片都要拿到1010條數據,總體排序以后取到1000-1010的數據。這樣的計算顯然是不可能的。所以如果數據量夠大,應當使用游標的方式查詢數據。雖然指定了頁大小,但是這只針對於每一片,實際得到的數據不超過片數*頁大小。一直循環,直到所有分片都沒有滿足條件的數據為止。

  • queryForPage
select * from user_info where score != #{score} limit #{pageIndex},#{pageSize}

public Page<UserInfo> getByUserIdList(String score,int pageIndex,int pageSize){ BoolQueryBuilder query= QueryBuilders.boolQuery(). mustNot(QueryBuilders.termQuery("score", score); SearchQuery searchQuery = new NativeSearchQueryBuilder(). withIndices(EsQueryConstant.obtainIndicesName("dbName","tableName")). withPageable(new PageRequest(pageIndex, pageSize)). withFilter(query).build(); return elasticsearchTemplate.queryForPage(searchQuery,UserInfo.class); }

后記

本文簡單的介紹了mysql轉ElasticSearch時的一些場景的案例,API並不難,只是相關資料少,很多功能只能探索前進,以后用到了更深入的功能會繼續更新。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM