百億級數據搜索引擎,Elasticsearch與SpringBoot整合以及怎么創建快照和恢復

  • Elasticsearch快照
  • Elasticsearch恢復
  • Spring與Elasticsearch整合

快照和恢復

Snapshot和Restore 模塊允許創建單個索引或者整個集群的快照到遠程倉庫。要試用快照和恢復,需要在Elasticsearch.yml配置文件中添加如下項:

path.repo: /home/app/es_backup

倉庫
在進行任何快照或者恢復操作之前必須有一個快照倉庫注冊在Elasticsearch里。下面的這個命令注冊了一個名為 es_backup的共享文件系統倉庫,快照將會存儲在 /home/app/es_backup 這個目錄。

$ curl -XPUT 192.168.56.105:9200/_snapshot/es_backup -d'{
   "type": "fs",
   "settings": {
      "location": "/home/hadoop/es_backup",
      "compress": true
   }
}'

# 查看這個倉庫的信息
$ curl -XGET localhost:9200/_snapshot/es_backup?pretty
$ curl -XGET localhost:9200/_snapshot/repo*,*backup?pretty
$ curl -XGET localhost:9200/_snapshot
$ curl -XGET localhost:9200/_snapshot/_all

共享文件系統的倉庫
共享文件系統倉庫 ("type": "fs") 是使用共享的文件系統去存儲快照。 在 location 參數里指定的具體存儲路徑必須和共享文件系統里的位置是一致,并且能被所有的數據節點和master節點訪問。 另外還支持如下的一些參數設置:

參數 說明
location 指定快照的存儲位置。必填。
compress 指定是否對快照文件進行壓縮, 默認為 true。
chunk_size 指定快照大文件可以被拆分為多大。這個參數指明了每塊的字節數。也可用不同的單位標識。 比如,1g,10m,5k等。默認是 null (表示不限制塊大小)。
max_restore_bytes_per_sec 每個節點恢復數據的最高速度限制,默認是 20mb/s
max_snapshot_bytes_per_sec 每個節點做快照的最高速度限制,默認是 20mb/s

只讀URL倉庫
URL倉庫("type": "url")可以用作以只讀方式訪問共享文件系統倉庫中的數據。
注冊存儲倉庫后,系統會立即在所有主節點和數據節點上對存儲倉庫進行驗證,以確保該存儲倉庫在群集中所有節點上都可以正常運行。在注冊或更新存儲倉庫時,可以使用verify參數顯式禁用存儲庫驗證:

$ curl -XPUT localhost:9200/_snapshot/s3_repository?verify=false
{
  "type": "s3",
  "settings": {
    "bucket": "my_s3_bucket",
    "region": "eu-west-1"
  }
}

其他倉庫類型
官方提供了其他存儲倉庫插件:

  • repository-s3
  • repository-hdfs
  • repository-azure
  • repository-gcs
$ curl -XPUT 192.168.56.105:9200/_snapshot/es_backup/snapshot_1?wait_for_completion=true -d'
{
   "indices": "restaurants,news,blog",
   "ignore_unavailable": true,
   "include_global_state": false
}'

快照
一個倉庫可以包含同一個集群的多個快照。快照根據集群中的唯一名字進行區分。 在倉庫 es_backup 里創建一個名為 snapshot_1 的快照可以通過下面的命令:

$ curl -XPUT localhost:9200/_snapshot/my_backup/snapshot_1?wait_for_completion=true

wait_for_completion 參數指定快照初始化(默認)后立即返回還是等待快照完成。在快照初始化期間,有關所有先前快照的信息會加載到內存中,這意味著在大型庫中,即使將wait_for_completion參數設置為false,此命令也可能需要幾秒鐘(甚至幾分鐘)才能返回。
默認情況下,集群中所有打開和啟動的索引是自動創建快照的。可以通過在快照請求里列出需要創建快照的索引。

$ curl -XPUT localhost:9200/_snapshot/es_backup/snapshot_1 -d'
{
  "indices": "restaurnts,news,blog",
  "ignore_unavailable": true,
  "include_global_state": false
}'
  • indices 參數指定快照包含的索引,這個參數支持同時配置多個索引
  • ignore_unavailable 這個選項設置為 true 的時候在創建快照的過程中會忽略不存在的索引。默認情況下, 如果沒有設置 ignore_unavailable 在索引不存在的情況下快照請求將會失敗。
  • include_global_state 為 false 能夠防止集群的全局狀態被作為快照的一部分存儲起來。默認情況下,如果快照中的1個或多個索引不是全部主分片都可用會導致整個創建快照的過程失敗。 通過設置 partial 為 true 可以改變這個行為。
    索引創建快照的過程是增量的。在給索引創建快照的過程中,Elasticsearch會分析存儲在倉庫中的索引文件并且只會復制那些自從上次快照 之后新建或有所更新的文件。這使得多個快照以一種緊湊的方式存儲在同一個倉庫里。
    創建快照的過程是以非阻塞方式執行的。一個索引在創建快照的同時能夠被檢索和查詢。盡管如此,快照保存的是在開始進行創建快照的那個時間點的索引的視圖。
    如果指定了倉庫名字和快照id,這個命令就會返回這個快照的詳細信息,甚至這個快照是不是正在運行。
$ curl -XGET "localhost:9200/_snapshot/es_backup/snapshot_1/_status"
裝填 說明
SUCCESS 快照已經完成,所有的分片已經成功存儲
FAILED 快照出現錯誤,無法完成存儲
PARTIAL 全局的集群狀態已存儲,但至少有一個分片沒有存儲成功。在這種情況下,失敗部分會包含未正確處理分片的詳細信息。
INCOMPATIBLE 快照是是用舊版本創建的,與當前集群的版本不兼容。

可以通過如下的命令將倉庫里的某個快照刪除:

$ curl -XDELETE 192.168.56.105:9200/_snapshot/es_backup/snap_shot3

從庫中刪除快照后,Elasticsearch會刪除與快照相關聯且未被其他任何快照使用的所有文件。如果在創建快照時執行了刪除操作,則快照過程將中止,并且將清理快照過程中創建的所有文件。因此,刪除快照操作可用于取消誤啟動后長時間運行的快照操作。
恢復
快照可以使用如下的操作來恢復:

$ curl -XPOST localhost:9200/_snapshot/my_backup/snapshot_1/_restore

默認情況下,快照中的所有索引以及集群狀態都會被恢復。在恢復請求中可以通過 indices 來指定需要被恢復的索引,同樣可以使用 include_global_state 選項來防止恢復集群的狀態。indices 支持配置多個索引。rename_pattern 和 rename_replacement 選項可以在恢復的時候使用正則表達式來重命名index。

curl -XPOST 192.168.56.105:9200/_snapshot/es_backup/snapshot_5/_restore -d'{
   "indices": "index_1, index_2",
   "ignore_unavailable": true,
   "include_global_state": false,
   "rename_pattern": "index_(.+)",
   "rename_replacement": "restored_indexd_$1"
}'

在還原過程中,可以覆蓋大多數索引設置。例如,以下命令將在還原索引index_1而不創建任何副本:

curl -XPOST 192.168.56.105:9200/_snapshot/es_backup/snapshot_5/_restore -d'{
   "indices": "index_1, index_2",
   "index_settings": {
      "index.number_of_replicas": 0
   }
}'

Spring和Elasticsearch整合

Transport Client

TransportClient利用Transport模塊遠程連接一個Elasticsearch集群。它并不加入到集群中,只是簡單的獲得一個或者多個初始化的Transport地址,并以輪詢的方式與這些地址進行通信。

Java High Level REST Client

Java High Level REST Client在Java Low Leve REST Client之上工作。它的主要目的是公開API的特定方法,這些方法接受請求對象作為參數并返回響應對象,因此請求編組和響應解編組由客戶端本身處理。
每個API可以同步或異步進行調用。同步方法會返回一個響應對象,而名稱以async后綴結尾的異步方法則需要一個監聽器,一旦接收到響應或錯誤,監聽器就會獲得通知(基于低級客戶端管理的線程池之上)。
Java High Level REST Client依賴Elasticsearch核心項目。它接受與TransportClient相同的請求參數,并返回相同的響應對象。

<!-- 所需的最低Java版本是1.8 -->
<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-high-level-client</artifactId>
    <version>5.6.16</version>
</dependency>
ElasticsearchRestClientConfig.java
import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ElasticsearchRestClientConfig {
    @Value("${elasticsearch.host}")
    private String host;

    @Value("${elasticsearch.http.port}")
    private int port;

    @Bean
    public RestHighLevelClient restHighLevelClient() {
        String[] hosts = host.split(",");
        HttpHost[] httpHosts = new HttpHost[hosts.length];
        for (int i = 0; i < hosts.length; i++) {
            String addr = hosts[i];
            httpHosts[i] = new HttpHost(addr, port);
        }

        return new RestHighLevelClient(RestClient.builder(httpHosts).build());
    }
}
Java Low Level REST Client

低級客戶端包括如下功能:

  • 跨所有可用節點進行負載平衡
  • 如果節點發生故障會根據特定響應代碼進行故障轉移
  • 失敗的連接懲罰(是否對失敗的節點進行重試取決于連續失敗的次數;-
    失敗嘗試次數越多,客戶端再次嘗試該節點之前等待的時間越長)
  • 持久連接
  • 跟蹤并記錄請求和響應
  • 可選的群集節點自動發現
<!-- 所需的最低Java版本是1.7 -->
<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-client</artifactId>
    <version>5.6.16</version>
</dependency>
Spring-Data-Elasticsearch

Spring-data-elasticsearch是Spring提供的操作ElasticSearch的數據層,封裝了大量的基礎操作,通過它可以很方便的操作ElasticSearch的數據。
下表顯示了Spring Data、Elasticsearch、Spring Data Elasticsearch,以及Spring Boot版本之間的對應關系:

Spring Data Elasticsearch Elasticsearch Spring Boot
3.2.x 6.8.4 2.2.x
3.1.x 6.2.2 2.1.x
3.0.x 5.5.0 2.0.x
2.1.x 2.4.0 1.5.x
ElasticsearchRepository

Spring-Data-Elasticsearch支持的關鍵字列表如下所示。

關鍵字 示例 Spring Boot
And findByNameAndPrice {"bool" : {"must" : [ {"field" : {"name" : "?"}}, {"field" : {"price" : "?"}} ]}}
Or findByNameOrPrice {"bool" : {"should" : [ {"field" : {"name" : "?"}}, {"field" : {"price" : "?"}} ]}}
Is findByName {"bool" : {"must" : {"field" : {"name" : "?"}}}}
Not findByNameNot {"bool" : {"must_not" : {"field" : {"name" : "?"}}}}
Between findByPriceBetween {"bool" : {"must" : {"range" : {"price" : {"from" : ?,"to" : ?,"include_lower" : true,"include_upper" : true}}}}}
LessThanEqual findByPriceLessThan {"bool" : {"must" : {"range" : {"price" : {"from" : null,"to" : ?,"include_lower" : true,"include_upper" : true}}}}}
GreaterThanEqual findByPriceGreaterThan {"bool" : {"must" : {"range" : {"price" : {"from" : ?,"to" : null,"include_lower" : true,"include_upper" : true}}}}}
Before findByPriceBefore {"bool" : {"must" : {"range" : {"price" : {"from" : null,"to" : ?,"include_lower" : true,"include_upper" : true}}}}}
After findByPriceAfter {"bool" : {"must" : {"range" : {"price" : {"from" : ?,"to" : null,"include_lower" : true,"include_upper" : true}}}}}
Like findByNameLike {"bool" : {"must" : {"field" : {"name" : {"query" : "?*","analyze_wildcard" : true}}}}}
StartingWith findByNameStartingWith {"bool" : {"must" : {"field" : {"name" : {"query" : "?*","analyze_wildcard" : true}}}}}
EndingWith findByNameEndingWith {"bool" : {"must" : {"field" : {"name" : {"query" : "*?","analyze_wildcard" : true}}}}}
Contains/Containing findByNameContaining {"bool" : {"must" : {"field" : {"name" : {"query" : "?","analyze_wildcard" : true}}}}}
In findByNameIn(Collectionnames) {"bool" : {"must" : {"bool" : {"should" : [ {"field" : {"name" : "?"}}, {"field" : {"name" : "?"}} ]}}}}
NotIn findByNameNotIn(Collectionnames) {"bool" : {"must_not" : {"bool" : {"should" : {"field" : {"name" : "?"}}}}}}
Near findByStoreNear Not Supported Yet !
True findByAvailableTrue {"bool" : {"must" : {"field" : {"available" : true}}}}
False findByAvailableFalse {"bool" : {"must" : {"field" : {"available" : false}}}}
OrderBy findByAvailableTrueOrderByNameDesc {"sort" : [{ "name" : {"order" : "desc"} }],"bool" :
{"must" : {"field" : {"available" : true}}}}

ElasticsearchTemplate
ElasticsearchRepository繼承了ElasticsearchCrudRepository,而ElasticsearchCrudRepository又繼承自PagingAndSortingRepository。ElasticSearchTemplate更多是對ElasticsearchRepository的補充,里面提供了一些更底層的方法。

代碼部分

pom.xml

<modelVersion>4.0.0</modelVersion>
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.0.5.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>ai.yunxi</groupId>
<artifactId>vip-elasticsearch</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>vip-elasticsearch</name>
<description>Demo project for Spring Boot</description>

<properties>
    <java.version>1.8</java.version>
    <elasticsearch.version>5.6.16</elasticsearch.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>org.elasticsearch</groupId>
        <artifactId>elasticsearch</artifactId>
        <version>${elasticsearch.version}</version>
    </dependency>
    <dependency>
        <groupId>org.elasticsearch.client</groupId>
        <artifactId>transport</artifactId>
        <version>${elasticsearch.version}</version>
    </dependency>
    <dependency>
        <groupId>org.elasticsearch.client</groupId>
        <artifactId>elasticsearch-rest-high-level-client</artifactId>
        <version>${elasticsearch.version}</version>
    </dependency>

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.8</version>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
        <exclusions>
            <exclusion>
                <groupId>org.junit.vintage</groupId>
                <artifactId>junit-vintage-engine</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
</dependencies>
application.properties
# spring-data-elasticsearch 方式
spring.data.elasticsearch.cluster-name=elasticsearch
spring.data.elasticsearch.cluster-nodes=192.168.56.105:9300
spring.data.elasticsearch.repositories.enabled=true

# transport client 方式
elasticsearch.clusterName=elasticsearch
elasticsearch.host=192.168.56.105
elasticsearch.port=9200
AccountRepository.java
import ai.yunxi.es.model.Account;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.annotations.Query;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;

public interface AccountRepository extends ElasticsearchRepository<Account, Long> {

    @Query("{\"bool\" : {\"must\" : {\"field\" : {\"name\" : \"?0\"}}}}")
    Page<Account> findByName(String name, Pageable pageable);

    Page<Account> findByAge(int age, Pageable pageable);

}
AccountServiceImpl.java
import ai.yunxi.es.config.ElasticsearchRestClientConfig;
import ai.yunxi.es.model.Account;
import ai.yunxi.es.repo.AccountRepository;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.TermQueryBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.sort.FieldSortBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.ScrolledPage;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.core.query.SearchQuery;
import org.springframework.data.util.CloseableIterator;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery;

@Service
public class AccountServiceImpl implements AccountService {
    @Autowired
    private ElasticsearchRestClientConfig restClient;

    @Autowired
    private AccountRepository accountRepository;

    @Autowired
    private ElasticsearchTemplate elasticsearchTemplate;

    @Override
    public Optional<Account> findById(long id) {
        return accountRepository.findById(id);
    }

    @Override
    public Account save(Account account) {
        return accountRepository.save(account);
    }

    @Override
    public void delete(Account account) {
        accountRepository.delete(account);
    }

    @Override
    public Optional<Account> findOne(long id) {
        return accountRepository.findById(id);
    }

    @Override
    public List<Account> findAll() {
        return (List<Account>) accountRepository.findAll();
    }

    @Override
    public List<Account> findByName(String fieldName, String keyword) {
        TermQueryBuilder termQueryBuilder = new TermQueryBuilder(fieldName, keyword);
        SearchQuery searchQuery = new NativeSearchQueryBuilder()
                .withQuery(termQueryBuilder).build();
        return (List<Account>) elasticsearchTemplate.queryForList(searchQuery, Account.class);
    }

    @Override
    public Page<Account> findByNameWithPage(String fieldName) {
        SearchQuery searchQuery = new NativeSearchQueryBuilder()
                .withQuery(matchAllQuery())
                .withFields(fieldName)
                .withPageable(PageRequest.of(20000, 10))
                .withSort(new FieldSortBuilder(fieldName).order(SortOrder.DESC))
                .build();
        return elasticsearchTemplate.queryForPage(searchQuery, Account.class);
    }

    @Override
    public List<Account> findByNameDeepth(String fieldName) {
        // 淺分頁
        SearchQuery searchQuery = new NativeSearchQueryBuilder()
                .withQuery(matchAllQuery())
                .withFields(fieldName)
                .withPageable(PageRequest.of(0, 10))
                .build();

        long scollTimeInMillis = 1000;
        Page<Account> scroll = elasticsearchTemplate.startScroll(scollTimeInMillis, searchQuery, Account.class);

        String scrollId = ((ScrolledPage) scroll).getScrollId();
        List<Account> accounts = new ArrayList<>();
        // scroll里面的內容就是類似Iterator(hasNext)
        while (scroll.hasContent()) {
            System.out.println(scroll.getContent());
            accounts.addAll(scroll.getContent());
            scrollId = ((ScrolledPage) scroll).getScrollId();
            scroll = elasticsearchTemplate.continueScroll(scrollId, scollTimeInMillis, Account.class);
        }
        elasticsearchTemplate.clearScroll(scrollId);
        return accounts;
    }

    public List<Account> findByStream() {
        SearchQuery searchQuery = new NativeSearchQueryBuilder()
                .withQuery(matchAllQuery())
                .withFields("firstname")
                .withPageable(PageRequest.of(0, 10))
                .build();

        CloseableIterator<Account> stream = elasticsearchTemplate.stream(searchQuery, Account.class);
        List<Account> accounts = new ArrayList<>();
        while (stream.hasNext()) {
            accounts.add(stream.next());
        }
        return accounts;
    }

    @Override
    public void findNameByRest() {
        SearchRequest searchRequest = new SearchRequest("bank");
        SearchSourceBuilder searchBuilder = new SearchSourceBuilder();
        searchBuilder.query(QueryBuilders.matchAllQuery());
        searchBuilder.from(0);
        searchBuilder.size(10);
        try {
            SearchResponse response = restClient.restHighLevelClient().search(searchRequest);
            SearchHit[] searchHits = response.getHits().getHits();
            for (SearchHit hit : searchHits) {
                System.out.println(hit.getSourceAsString());
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public Page<Account> findByName(String name, PageRequest pageRequest) {
        return accountRepository.findByName(name, pageRequest);
    }

    @Override
    public Page<Account> findByAge(int age, PageRequest pageRequest) {
        return accountRepository.findByAge(age, pageRequest);
    }
}
AccountController.java
import ai.yunxi.es.model.Account;
import ai.yunxi.es.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.math.BigDecimal;
import java.util.List;
import java.util.Optional;

@Controller
@RequestMapping("/account")
public class AccountController {
    @Autowired
    private AccountService accountService;

    @RequestMapping("/{id}")
    @ResponseBody
    public Account getAccountById(@PathVariable int id) {
        Optional<Account> opt = accountService.findById(id);
        Account account = opt.get();
        return account;
    }

    @RequestMapping("/{field}/{name}")
    @ResponseBody
    public List<Account> getAccountByName(@PathVariable String field, @PathVariable String name) {
        List<Account> list = accountService.findByName(field, name);
        return list;
    }

    @RequestMapping("/page/{field}")
    @ResponseBody
    public Page<Account> getByNameWithPage(@PathVariable String field) {
        Page<Account> page = accountService.findByNameWithPage(field);
        return page;
    }

    @RequestMapping("/deepth/{field}")
    @ResponseBody
    public List<Account> getByNameDeepth(@PathVariable String field) {
        List<Account> list = accountService.findByNameDeepth(field);
        return list;
    }

    @RequestMapping("/rest")
    @ResponseBody
    public void getByRestClient() {
        accountService.findNameByRest();
    }

    @RequestMapping("/save")
    @ResponseBody
    public void Save() {
        Account account = new Account(2001, 2001, "Micheal", "Jackson", 35, "M",
                new BigDecimal("999999"), "test@126.com", "CA", "Belvoir",
                "499 Laurel Avenue", "");
        accountService.save(account);
    }
}
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容