ELK学习总结

基础概念

1.ElasticSearch是什么,做什么的

ElasticSearch(简称ES)是一个基于Lucene的搜索服务器,是一个 【开源】 的,【高扩展】 的 分布式搜索引擎,可以用于搜索!是排名第一的搜索引擎类应用,可以近乎实时的存储,检索数据。

2.ElasticSearch和Lucene的关系

Lucene是最先进、功能最强大的搜索库,但如果直接基于lucene开发,api非常复杂,实现一些简单的功能,也需要在深入理解原理(各种索引结构)的基础上写大量的java代码,ElasticSearch基于Lucene进行了封装,隐藏复杂性,提供了简单易用的restful api接口,提供了对多种语言的支持,开箱即用。
关于ElasticSearch的一个传说,有一个程序员失业了,陪着自己老婆去英国伦敦学习厨师课程。程序员在失业期间想给老婆写一个菜谱搜索引擎,觉得Lucene实在太复杂了,就开发了一个封装了Lucene的开源项目,compass。后来程序员找到了工作,是做分布式的高性能项目的,觉得compass不够,就写了ElasticSearch,让Lucene变成分布式的系统。

3.使用场景

1、检索。ES本身作为一个搜索引擎,用来处理检索的任务再合适不过。你可以在线上项目中直接将内容写入ES以提供检索服务,也可以把以往的数据导入ES以处理特定的需求。
2、统计。ES的统计也是基于检索功能的,聚合功能使得统计结果处理起来非常方便。如果你只需要统计而不用检索,可能有其他工具更适合你,比如Spark SQL。
3、其他的诸如文档存错查询,日志存储和索引,监控基础信息、应用程序性能和使用情况,地理数据存储和分析。
总之就是可以对静态数据进行存储搜索,对动态数据(时间序列数据)进行产品分析、报告、异常检测 ……

4.为什么不用mysql做全文搜索

mysql做全文检索的话,查询速度会非常的慢,比如 %xxx% 所以在大数据量的情况下,用ES可以以前所未有的速度进行检索。

5.为什么Elasticsearch不适合做数据存储?

ES同时还是一个文档数据库,但并不能完全代替数据库,因为ES的核心是检索,没有用户验证和权限控制,无法多对多,MYSQL支持事务和访问权限控制,ES不支持事务和访问权限控制,虽然ElasticSearch 比 MySQL 更适合复杂条件查询,但是有好就有弊,ES为了查询做很多的准备工作,插入速度就会慢于 MySQL,而且数据存入ES后并不是立马就能检索到(可以配置,但会非常影响性能),所以在存储时使用mysql,而在搜索,统计时使用ES时一个非常棒的选择。

6.关于ES和Solr的比较,同类产品,如何选择

Elasticsearch 的竞争对手只有一个,Apache Solr,有着和 Elasticsearch 相似的特性,但 Solr 的发展势头远不及 Elasticsearch。Solr学习成本相对较高,但功能更丰富,生态更好,如果你已经在使用solr了,请继续使用它,因为迁移到Elasticsearch并不会带来具体的优势。如果你刚开始使用全文索引,推荐ES,Elasticsearch由于其易用性而在较新的开发人员中更受欢迎。

7.PHP开发者如何使用ElasticSearch?其他语言如何使用

和Redis的使用一样,php也提供了对应的扩展包,Elasticsearch-PHP是PHP连接Elasticsearch库的扩展,是用PHP语言开发的,类似于PHP通过Predis操作redis库的功能。可以通过composer或者github下载,集成到现有的php项目中,实现对ES的使用。

8.ElasticSearch 是不是可以替换mysql了呢

完全替代肯定不行,脱离业务场景谈技术选型都是瞎扯。es和mysql两者都能解决一部分问题,能力也有交集。而各自都有独特能力的一面,不能空谈取代。难易程度来说,Mysql学习成本要比ES低的多,ES的技术栈主要也是ELK(ELK技术,elasticsearch+logstash+kibana)。所以其具体用途可以有以下几个:用户行为日志(点击,浏览,收藏,评论)等的分析;电商网站检索商品;日志数据分析(logstash采集日志,ES进行复杂的数据分析,kibana展示)

9.ElasticSearch 中的重要术语

  • NRT Near RealTime(NRT) 近实时
    elasticsearch是一个近似实时的搜索平台,近实时有两种意思,一种是从索引文档到可搜索也就是从写入数据到可以被搜索到有一个小延迟(通常为1秒),还有一种就是基于ElasticSearch 进行搜索和分析可以达到秒级,实时又分为准实时和近实时,准实时是毫秒级,近实时是秒级,
  • 集群 Cluster
    集群就是一个或多个节点存储数据,其中一个节点为主节点,这个主节点是可以通过选举产生的,并提供跨节点的联合索引和搜索的功能。集群有一个唯一性标示的名字,默认是elasticsearch,集群名字很重要,每个节点是基于集群名字加入到其集群中的。因此,确保在不同环境中使用不同的集群名字。一个集群可以只有一个节点。强烈建议在配置elasticsearch时,配置成集群模式。
  • 节点 Node
    节点就是一台单一的服务器,是集群的一部分,存储数据并参与集群的索引和搜索功能。像集群一样,节点也是通过名字来标识,默认是在节点启动时随机分配的字符名。当然啦,你可以自己定义。该名字也蛮重要的,在集群中用于识别服务器对应的节点。
    节点可以通过指定集群名字来加入到集群中。默认情况下,每个节点被设置成加入到elasticsearch集群。如果启动了多个节点,假设能自动发现对方,他们将会自动组建一个名为elasticsearch的集群。
  • 索引 Index
    索引是有几分相似属性的一系列文档的集合。如nginx日志索引、syslog索引等等。索引是由名字标识,名字必须全部小写。这个名字用来进行索引、搜索、更新和删除文档的操作。
    索引相对于关系型数据库的库。
  • 类型 Type
    在一个索引中,可以定义一个或多个类型。类型是一个逻辑类别还是分区完全取决于你。通常情况下,一个类型被定于成具有一组共同字段的文档。如ttlsa运维生成时间所有的数据存入在一个单一的名为logstash-ttlsa的索引中,同时,定义了用户数据类型,帖子数据类型和评论类型。
    类型相对于关系型数据库的表。
    *文档 Document
    文档是信息的基本单元,可以被索引的。文档是以JSON格式表现的。
    在类型中,可以根据需求存储多个文档。
    虽然一个文档在物理上位于一个索引,实际上一个文档必须在一个索引内被索引和分配一个类型。
    文档相对于关系型数据库的列。
  • 分片和副本 shards & replica
    在实际情况下,索引存储的数据可能超过单个节点的硬件限制。如一个十亿文档需1TB空间可能不适合存储在单个节点的磁盘上,或者从单个节点搜索请求太慢了。为了解决这个问题,elasticsearch提供将索引分成多个分片的功能。当在创建索引时,可以定义想要分片的数量。每一个分片就是一个全功能的独立的索引,可以位于集群中任何节点上。
    分片的两个最主要原因:
    a、水平分割扩展,增大存储量
    b、分布式并行跨分片操作,提高性能和吞吐量
    分布式分片的机制和搜索请求的文档如何汇总完全是有elasticsearch控制的,这些对用户而言是透明的。
    网络问题等等其它问题可以在任何时候不期而至,为了健壮性,强烈建议要有一个故障切换机制,无论何种故障以防止分片或者节点不可用。
    为此,elasticsearch让我们将索引分片复制一份或多份,称之为分片副本或副本。
    副本也有两个最主要原因:
    高可用性,以应对分片或者节点故障。出于这个原因,分片副本要在不同的节点上。
    提供性能,增大吞吐量,搜索可以并行在所有副本上执行。
    总之,每一个索引可以被分成多个分片。索引也可以有0个或多个副本。复制后,每个索引都有主分片(母分片)和复制分片(复制于母分片)。分片和副本数量可以在每个索引被创建时定义。索引创建后,可以在任何时候动态的更改副本数量,但是,不能改变分片数。
    默认情况下,elasticsearch为每个索引分片5个主分片和1个副本,这就意味着集群至少需要2个节点。索引将会有5个主分片和5个副本(1个完整副本),每个索引总共有10个分片。
    每个elasticsearch分片是一个Lucene索引。一个单个Lucene索引有最大的文档数LUCENE-5843, 文档数限制为2147483519(MAX_VALUE – 128)。 可通过_cat/shards来监控分片大小

ElasticSearch安装

1.ES版本的选择

在使用ES前,我们首先要选择一个合适的版本,选择最新的版本永远是一个不会错的选择,目前ES官网最新版本是8.0.0-alpha2,发布于September 17, 2021(2021/9/17),属于内部测试版,距今只有一个月时间,由于网络上关于其使用说明较少且可能没有相对应的php扩展包,不太利于学习,故我们可以选择7.x版本进行学习。7.15.1是最新的Release版本,发布于2021/10/15,距今只有几天时间,所有选择7.15.1进行学习是最佳的。

2.ES的下载安装

我本地调试学习一般使用的是Windows系统,生产环境使用的是Linux,所以这里两种操作系统下ES的下载安装都会用到。
官网下载链接:https://www.elastic.co/cn/downloads/elasticsearch#ga-release (属于中国节点,下载速度很快,3分钟左右就可以下载完成)
Windows下:
选择windows版本进行下载,解压elasticsearch-7.15.1-windows-x86_64.zip,双击运行bin目录下的elasticsearch.bat(杀毒软件可能会进行拦截,允许即可)
Linux下
mkdir /usr/local/software/es
cd /usr/local/software/es
curl -L -O https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-7.15.1-linux-x86_64.tar.gz
tar -xvf elasticsearch-7.15.1-darwin-x86_64.tar.gz
./elasticsearch-7.15.1-darwin-x86_64/bin/elasticsearch
检测安装:
命令行提示信息中如果出现 publish_address {127.0.0.1:9200}, bound_addresses {127.0.0.1:9200}, {[::1]:9200}则表示安装成功,或直接访问 http://localhost:9200 查看es具体信息

{
  "name" : "PS2019WRLCRMZY",
  "cluster_name" : "elasticsearch",
  "cluster_uuid" : "LcZNPNroRDOdQ8f6hkw12Q",
  "version" : {
    "number" : "7.15.1",
    "build_flavor" : "default",
    "build_type" : "zip",
    "build_hash" : "83c34f456ae29d60e94d886e455e6a3409bba9ed",
    "build_date" : "2021-10-07T21:56:19.031608185Z",
    "build_snapshot" : false,
    "lucene_version" : "8.9.0",
    "minimum_wire_compatibility_version" : "6.8.0",
    "minimum_index_compatibility_version" : "6.0.0-beta1"
  },
  "tagline" : "You Know, for Search"
}

3.ES配置文件

解决CORS
找到config文件夹下的elasticsearch.yml,在文件的末尾添加如下内容:
http.cors.enabled: true
http.cors.allow-origin: “*”

ES可视化插件elasticsearch-head安装

  • elasticsearch-head是用于监控 Elasticsearch 状态的客户端插件,包括数据可视化、执行增删改查操作等

  • 安装node.js
    下载地址:https://nodejs.org/en/download/ 根据自己系统下载相应的msi,双击安装。

  • 安装head插件
    https://github.com/mobz/elasticsearch-head 下载zip文件
    解压至es目录

  • 修改配置 解决跨域

  • elasticsearch-head-master\Gruntfile.js 97行左右新增 hostname: ‘*’,

        connect: {
            server: {
                options: {
                    hostname: '*',
                    port: 9100,
                    base: '.',
                    keepalive: true
                }
            }
        }
        _node_handler: function(data) {
            if(data) {
                this.prefs.set("app-base_uri", this.cluster.base_uri) || "http://localhost:9200";
                if(data.version && data.version.number)
                    this.cluster.setVersion(data.version.number);
            }
        },
  • elasticsearch-head-master\package.json “license”: “Apache2” => “license”: “Apache-2.0”,

  • 运行

  • es/es-head目录下运行

    • npm install
    • npm run start(以后每次),如果还不成功,再执行一次npm install
  • 浏览器中运行http://localhost:9100/

    image.png

ES集群健康值

正常情况下,Elasticsearch 集群健康状态分为三种:
green 最健康得状态,说明所有的分片包括备份都可用; 这种情况Elasticsearch集群所有的主分片和副本分片都已分配, Elasticsearch集群是 100% 可用的。

yellow 基本的分片可用,但是备份不可用(或者是没有备份); 这种情况Elasticsearch集群所有的主分片已经分片了,但至少还有一个副本是缺失的。不会有数据丢失,所以搜索结果依然是完整的。不过,你的高可用性在某种程度上被弱化。如果 更多的 分片消失,你就会丢数据了。把 yellow 想象成一个需要及时调查的警告。

red 部分的分片可用,表明分片有一部分损坏。此时执行查询部分数据仍然可以查到,遇到这种情况,还是赶快解决比较好; 这种情况Elasticsearch集群至少一个主分片(以及它的全部副本)都在缺失中。这意味着你在缺少数据:搜索只能返回部分数据,而分配到这个分片上的写入请求会返回一个异常。

到这里我们的Elasticsearch的安装配置和简单使用就完成,我们可以将index设为数据库,type设为表名,来对数据表进行索引管理。

在laravel中使用Elasticsearch

Elasticsearch-PHP基本API使用

1.下载php框架和es扩展包

在扩展包的选择上,我们尽量选一些下载排名靠前的扩展,方便日后查阅文档还有学习。在packagist中文网可以看到以下几个扩展包的下载量都非常大,我们选择排名第一的进行使用

elasticsearch/elasticsearch

laravel/scout

ruflin/elastica

composer create-project laravel/laravel elasticsearch-php
composer install
composer require elasticsearch/elasticsearch

访问 http://localhost/elasticsearch-php/public/ 查看项目是否安装成功

2. 在项目中配置es

config/database.php

    'elasticsearch' => [
        // Elasticsearch 支持多台服务器负载均衡,因此这里是一个数组
        'hosts' => explode(',', env('ES_HOSTS')),
    ],

.env文件

#es配置# 这里是你的 ElasticSearch 服务器 IP 及端口号#多个hosts用 , 隔开
# 如果要启用ssl,加https前缀即可
#ES_HOSTS=127.0.0.1:9200,127.0.0.2:9200,127.0.0.3:9200,127.0.0.4:9200,127.0.0.5:9200 
ES_HOSTS=127.0.0.1:9200

注意:ES默认使用的是9200端口,在服务器上一定要开放对应端口才能正常服务

初始化elasticsearch对象

//注入到容器中 App/Providers/AppServiceProvider.php
<?php

namespace App\Providers;

use Elasticsearch\ClientBuilder as ElasticBuilder;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        $this->app->singleton('es', function () {
            $bulider = ElasticBuilder::create()->setHosts(config('database.elasticsearch.hosts'));
            if (app()->environment() == 'local') {
                //配置日志,Elasticsearch 的请求和返回数据将打印到日志文件中,方便我们调试
                $bulider->setLogger(app('log'));
            }

            return $bulider->build();
        });
    }

    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
    }
}

测试

php artisan tinker 

>>>app('es')->info()

使用

//使用了laravel的容器注入技术,在使用时可以直接app('es')->apis ,不需要再次实例化
app('es')->info();
app('es') ->index([
                'index' => 'name',
                'type' => 'title',
                'id' => 1,
                'body' => ['testField' => 'xxx'],
            ]);

//单独使用的话需要实例化

use Elasticsearch\ClientBuilder;
$client = ClientBuilder::create()->build();
$client ->apis;

3.基本使用

索引一个文档

为了索引一个文档,我们要指定4部分信息:index,type,id 和一个 body。构建一个键值对的关联数组就可以完成上面的内容。body 的键值对格式与文档的数据保持一致性。(译者注:如 [“testField” ⇒ “abc”] 在文档中则为 {“testField” : “abc”}):

    public function index()
    {
        $params = [
            'index' => 'my_index',
            'type' => 'my_type',
            'id' => 'my_id',
            'body' => ['testField' => 'abc'],
        ];

        $response = app('es')->index($params);

        return $response;
    }
  • 响应
    响应的是一个关联数组,return 后通过调试工具看到的是json格式的字符串
{
    "_index": "my_index",
    "_type": "my_type",
    "_id": "my_id",
    "_version": 1,
    "result": "created",
    "_shards": {
        "total": 2,
        "successful": 1,
        "failed": 0
    },
    "_seq_no": 0,
    "_primary_term": 1
}

获取一个文档

public function fetch()
    {
        $params = [
            'index' => 'my_index',
            'type' => 'my_type',
            'id' => 'my_id',
        ];

        $response = app('es')->get($params);

        return $response;
    }
  • 响应
{
    "_index": "my_index",
    "_type": "my_type",
    "_id": "my_id",
    "_version": 2,
    "_seq_no": 1,
    "_primary_term": 1,
    "found": true,
    "_source": {
        "testField": "abc"
    }
}

搜索文档

    public function search_es()
    {
        $params = [
            'index' => 'my_index',
            'type' => 'my_type',
            'body' => [
                'query' => [
                    'match' => [
                        'testField' => 'abc',
                    ],
                ],
            ],
        ];

        $response = app('es')->search($params);

        return $response;
    }
  • 响应结果
{
    "took": 2,
    "timed_out": false,
    "_shards": {
        "total": 1,
        "successful": 1,
        "skipped": 0,
        "failed": 0
    },
    "hits": {
        "total": {
            "value": 1,
            "relation": "eq"
        },
        "max_score": 0.2876821,
        "hits": [
            {
                "_index": "my_index",
                "_type": "my_type",
                "_id": "my_id",
                "_score": 0.2876821,
                "_source": {
                    "testField": "abc"
                }
            }
        ]
    }
}

删除一个文档

    public function delete_es_doc()
    {
        $params = [
            'index' => 'my_index',
            'type' => 'my_type',
            'id' => 'my_id',
        ];

        $response = app('es')->delete($params);

        return $response;
    }
  • 响应
{
    "_index": "my_index",
    "_type": "my_type",
    "_id": "my_id",
    "_version": 4,
    "result": "deleted",
    "_shards": {
        "total": 2,
        "successful": 1,
        "failed": 0
    },
    "_seq_no": 3,
    "_primary_term": 1
}

删除一个索引

    public function delete_es()
    {
        $deleteParams = [
            'index' => 'my_index',
        ];
        $response = app('es')->indices()->delete($deleteParams);

        return $response;
    }
  • 响应结果
{
    "acknowledged": true
}

创建一个索引

//创建一个索引
    public function index_es_cre()
    {
        $params = [
            'index' => 'my_index',
            'body' => [
                'settings' => [
                    'number_of_shards' => 12,
                    'number_of_replicas' => 0,
                ],
            ],
        ];

        $response = app('es')->indices()->create($params);

        return $response;
    }
  • 响应结果
{
    "acknowledged": true,
    "shards_acknowledged": true,
    "index": "my_index"
}

响应结果字段说明

_index:文档所在的索引名
_type:文档所在的类型名
_id:文档唯一id
_score:相关性算分
_uid:组合id,有_type和_id组成(从6.x开始_type不再起作用,同_id一样)
_source:文档的原始json数据,可以从这里获取每个字段的内容
_all:整合所有字段内容到该字段,默认下禁用

明日计划

深入学习Elasticsearch-PHP API的使用,使其可以满足跟数据库相关的业务需求

总结

ElasticSearch学习总结

MySQL 中的数据库(DataBase),等价于 ES 中的索引(Index)。

MySQL 中一个数据库下面有 N 张表(Table),等价于1个索引 Index 下面有 N 多类型(Type)。

MySQL 中一个数据库表(Table)下的数据由多行(Row)多列(column,属性)组成,等价于1个 Type 由多个文档(Document)和多 Field 组成。

MySQL 中定义表结构、设定字段类型等价于 ES 中的 Mapping。举例说明,在一个关系型数据库里面,Schema 定义了表、每个表的字段,还有表和字段之间的关系。与之对应的,在 ES 中,Mapping 定义索引下的 Type 的字段处理规则,即索引如何建立、索引类型、是否保存原始索引 JSON 文档、是否压缩原始 JSON 文档、是否需要分词处理、如何进行分词处理等。

MySQL 中的增 insert、删 delete、改 update、查 search 操作等价于 ES 中的增 PUT/POST、删 Delete、改 _update、查 GET。其中的修改指定条件的更新 update 等价于 ES 中的 update_by_query,指定条件的删除等价于 ES 中的 delete_by_query。

创建一个索引相当于创建一个库,索引一个文档相当于给库的某个表中新增一条记录,获取/搜索一个文档相当于获取某一个库的某个表中的一条记录,删除一个文档相当于删除这条记录,删除一个索引相当于删除了这个库

在模拟项目中实操使用Elasticsearch-PHP的API

项目:用户管理系统

现在有一个项目是做一个用户管理系统,要求可以对用户的信息实现增删改查,用户的信息包括
<姓名(name),性别(sex),年龄(age),身份证号(idcard)>

元数据数据字典

_index:info_manager
_type : user
_id:自动生成

插入用户

$data = [
            'name' => $request->name,
            'sex' => $request->sex,
            'age' => $request->age,
            'idcard' => $request->idcard,
            'created_at' => date('Y-m-d H:i:s', time()),
            'updated_at' => date('Y-m-d H:i:s', time()),
        ];

        $params = [
            'index' => $this->index,
            'type' => $this->type,
            'body' => $data,
        ];

        $exists = app('es')->search([
            'index' => $this->index,
            'type' => $this->type,
            'body' => [
                'query' => [
                    'match' => [
                        'idcard' => $request->idcard,
                    ],
                ],
            ],
        ]);
        if ($exists['hits']['hits']) {
            return '已经存在该用户';
        }

        $response = app('es')->index($params);

        if ($response['result'] == 'created') {
            return '创建成功';
        }

        return '创建失败';
}

查找姓名为“张三”的用户

    public function get(Request $request)
    {
        $exists = app('es')->search([
            'index' => $this->index,
            'type' => $this->type,
            'body' => [
                'query' => [
                    'match' => [
                        'name' => $request->name,
                        //'idcard' => $request->idcard,
                    ],
                ],
            ],
        ]);
        if ($exists['hits']['hits']) {
            return $exists['hits']['hits'];
        }

        return  '不存在该用户';
    }
  • 响应
[
    {
        "_index": "info_manager",
        "_type": "user",
        "_id": "SBQ5qHwBQ6qKqx4bjNy_",
        "_score": 4.60517,
        "_source": {
            "name": "张三",
            "sex": "男",
            "age": "28",
            "idcard": "61062164545212444121221",
            "created_at": "2021-10-22 13:38:36",
            "updated_at": "2021-10-22 13:38:36"
        }
    }
]

修改id为xx的用户年龄

    public function update(Request $request)
    {
        $exists = app('es')->exists([
            'index' => $this->index,
            'type' => $this->type,
            'id' => $request->id,
        ]);

        if ($exists) {
            $response = app('es')->update([
                'index' => $this->index,
                'type' => $this->type,
                'id' => $request->id,
                'body' => [
                    'doc' => [
                        'age' => $request->age,
                    ],
                ],
            ]);

            return  $response;
        }

        return '资源不存在';
    }

Elasticsearch-PHP分页

es实现分页查询,在ES中有三种方式可以实现分页:from+size、scroll、search_after

1.from+size 分页

public function list(Request $request)
    {
        $params = [
            'size' => $request->limit,
            'from' => ($request->page - 1) * $request->limit,
            'index' => $this->index,
            'type' => $this->type,
        ];

        $response = app('es')->search($params);

        return $response['hits']['hits'];
    }
  • 在使用过程中,有一些典型的使用场景,比如分页、遍历等。在使用关系型数据库中,我们被告知要注意甚至被明确禁止使用深度分页,同理,在 Elasticsearch 中,也应该尽量避免使用深度分页。es为了性能,限制了我们分页的深度,es目前支持的最大的 max_result_window = 10000;from+size二者之和不能超过1w,也就是说我们不能分页到1w条数据以上。
  • from+size分页原理很简单,比如需要查询10条数据,es则需要执行from+size条数据然后根据偏移量截断前N条处理后返回。随着偏移量的增大这个时间会呈几何式增长。

如何解决from+size 分页带来的性能问题

  • 1.在业务逻辑上禁止深度分页,比如不允许查询100页以后的数据
  • 2.更换分页方式,采用游标 scroll的方式

2.游标 scroll 分页

  • Scroll往往是应用于后台批处理任务中,不能用于实时搜索,因为这个scroll相当于维护了一份当前索引段的快照信息,这个快照信息是你执行这个scroll查询时的快照。在这个查询后的任何新索引进来的数据,都不会在这个快照中查询到。但是它相对于from和size,不是查询所有数据然后剔除不要的部分,而是记录一个读取的位置,保证下一次快速继续读取。查询时会自动返回一个_scroll_id,通过这个id可以继续查询
public function list(Request $request)
    {
        if (!isset($request->scroll_id)) {
            $params = [
                'scroll' => '30s',  //快照存活的时间  1m ->一分钟
                'size' => $request->limit,
                'index' => $this->index,
                'type' => $this->type,
            ];
            $response = app('es')->search($params);
        } else {
            $response = app('es')->scroll([
                'scroll_id' => $request->scroll_id,  //...using our previously obtained _scroll_id
                'scroll' => '30s',           // and the same timeout window
                ]
            );
        }

        return [
            'scroll_id' => $response['_scroll_id'],
            'data' => $response['hits']['hits'],
        ];
    }

游标 scroll 带来的问题

这种分页方式虽然查询变快了,但滚动上下文代价很高,每一个 scroll_id 不仅会占用大量的资源(特别是排序的请求),而且是生成的历史快照,对于数据的变更不会反映到快照上,那么在实时情况下如果处理深度分页的问题呢?es 给出了 search_after 的方式,这是在 >= 5.0 版本才提供的功能。

3.search_after分页

searchAfter的方式通过维护一个实时游标来避免scroll的缺点,它可以用于实时请求和高并发场景。
search_after的理念是,=在不同分片上(假设有5个分片),先按照指定顺序排好,根据我们传的search_after值 ,然后仅取这个值之后的size个文档。这 5*size 个文档拿到Es内存中排序后,返回前size个文档即可。避免了浅分页导致的内存爆炸情况,经实际使用性能良好,ES空闲状态下查询耗时稳定在50ms以内,平均10~20ms。

    public function list(Request $request)
    {
        if (!isset($request->search_after)) {
            $params = [
                'size' => $request->limit,
                'index' => $this->index,
                'type' => $this->type,
                'body' => [
                    'sort' => [
                        [
                            '_id' => 'desc',
                        ],
                    ],
                ],
            ];
        } else {
            $params = [
                'size' => $request->limit,
                'index' => $this->index,
                'type' => $this->type,
                'body' => [
                    'sort' => [
                        [
                            '_id' => 'desc',
                        ],
                    ],
                    'search_after' => [$request->search_after],
                ],
            ];
        }
        $response = app('es')->search($params);

        return [
            //项目中需优化数组溢出,此处仅为简单演示
            'search_after' => $response['hits']['hits'][count($response['hits']['hits']) - 1]['sort'][0], 
            'data' => $response['hits']['hits'],
        ];
  • 注意:
    • 当我们使用search_after时,from值必须设置为0或者-1(当然你也可以不设置这个from参数)。
    • 当存在search_after参数时,不允许出现scroll参数

search_after 带来的问题

ElasticSearch之Search_After的注意事项

1.搜索时,需要指定sort,并且保证值是唯一的(可以通过加入_id或者文档body中的业务唯一值来保证);
2.再次查询时,使用上一次最后一个文档的sort值作为search_after的值来进行查询;
3.不能使用随机跳页,只能是下一页或者小范围的跳页(一次查询出小范围内各个页数,利用缓存等技术,来实现小范围分页,比较麻烦,比如从第一页调到第五页,则依次查询出2,3,4页的数据,利用每一次最后一个文档的sort值进行下一轮查询,客户端或服务端都可以进行,如果跳的比较多,则可能该方法并不适用)
它与滚动API非常相似,但与它不同,search_after参数是无状态的,它始终针对最新版本的搜索器进行解析。因此,排序顺序可能会在步行期间发生变化,具体取决于索引的更新和删除

总结

from+ size 分页,如果数据量不大或者from、size不大的情况下,效率还是蛮高的。但是在深度分页的情况下,这种使用方式效率是非常低的,并发一旦过大,还有可能直接拖垮整个ElasticSearch的集群。
scroll 分页通常不会用在客户端,因为每一个 scroll_id 都会占用大量的资源,一般是后台用于全量读取数据使用
search_after通过维护一个实时游标来避免scroll的缺点,它可以用于实时请求和高并发场景,一般用于客户端的分页查询
大体而言就是在这三种分页方式中,from + size不适合数据量很大的场景,scroll不适合实时场景,而search after在es5.x版本之后应运而生,较好的解决了这个问题。

列出所有用户

public function list(Request $request)
    {
        $params = [
            'index' => $this->index,
            'type' => $this->type,
        ];

        $response = app('es')->search();

        return $response['hits']['hits'];
    }

删除id为xx的用户

    public function del(Request $request)
    {
        $params = [
            'index' => $this->index,
            'type' => $this->type,
            'id' => $request->id,
        ];

        $response = app('es')->delete($params);

        return $response;
    }

ELK介绍

elasticsearch介绍

ElasticSearch是一个基于Lucene的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口。Elasticsearch是用Java开发的,并作为Apache许可条款下的开放源码发布,是第二流行的企业搜索引擎。设计用于云计算中,能够达到实时搜索,稳定,可靠,快速,安装使用方便。

elasticsearch几个重要术语

  • NRT
    elasticsearch是一个近似实时的搜索平台,从索引文档到可搜索有些延迟,通常为1秒。
  • 集群
    集群就是一个或多个节点存储数据,其中一个节点为主节点,这个主节点是可以通过选举产生的,并提供跨节点的联合索引和搜索的功能。集群有一个唯一性标示的名字,默认是elasticsearch,集群名字很重要,每个节点是基于集群名字加入到其集群中的。因此,确保在不同环境中使用不同的集群名字。一个集群可以只有一个节点。强烈建议在配置elasticsearch时,配置成集群模式。
  • 节点
    节点就是一台单一的服务器,是集群的一部分,存储数据并参与集群的索引和搜索功能。像集群一样,节点也是通过名字来标识,默认是在节点启动时随机分配的字符名。当然啦,你可以自己定义。该名字也蛮重要的,在集群中用于识别服务器对应的节点。
    节点可以通过指定集群名字来加入到集群中。默认情况下,每个节点被设置成加入到elasticsearch集群。如果启动了多个节点,假设能自动发现对方,他们将会自动组建一个名为elasticsearch的集群。
  • 索引
    索引是有几分相似属性的一系列文档的集合。如nginx日志索引、syslog索引等等。索引是由名字标识,名字必须全部小写。这个名字用来进行索引、搜索、更新和删除文档的操作。
    索引相对于关系型数据库的库。
  • 类型
    在一个索引中,可以定义一个或多个类型。类型是一个逻辑类别还是分区完全取决于你。通常情况下,一个类型被定于成具有一组共同字段的文档。如ttlsa运维生成时间所有的数据存入在一个单一的名为logstash-ttlsa的索引中,同时,定义了用户数据类型,帖子数据类型和评论类型。
    类型相对于关系型数据库的表。
    *文档
    文档是信息的基本单元,可以被索引的。文档是以JSON格式表现的。
    在类型中,可以根据需求存储多个文档。
    虽然一个文档在物理上位于一个索引,实际上一个文档必须在一个索引内被索引和分配一个类型。
    文档相对于关系型数据库的列。
  • 分片和副本
    在实际情况下,索引存储的数据可能超过单个节点的硬件限制。如一个十亿文档需1TB空间可能不适合存储在单个节点的磁盘上,或者从单个节点搜索请求太慢了。为了解决这个问题,elasticsearch提供将索引分成多个分片的功能。当在创建索引时,可以定义想要分片的数量。每一个分片就是一个全功能的独立的索引,可以位于集群中任何节点上。
    分片的两个最主要原因:
    a、水平分割扩展,增大存储量
    b、分布式并行跨分片操作,提高性能和吞吐量
    分布式分片的机制和搜索请求的文档如何汇总完全是有elasticsearch控制的,这些对用户而言是透明的。
    网络问题等等其它问题可以在任何时候不期而至,为了健壮性,强烈建议要有一个故障切换机制,无论何种故障以防止分片或者节点不可用。
    为此,elasticsearch让我们将索引分片复制一份或多份,称之为分片副本或副本。
    副本也有两个最主要原因:
    高可用性,以应对分片或者节点故障。出于这个原因,分片副本要在不同的节点上。
    提供性能,增大吞吐量,搜索可以并行在所有副本上执行。
    总之,每一个索引可以被分成多个分片。索引也可以有0个或多个副本。复制后,每个索引都有主分片(母分片)和复制分片(复制于母分片)。分片和副本数量可以在每个索引被创建时定义。索引创建后,可以在任何时候动态的更改副本数量,但是,不能改变分片数。
    默认情况下,elasticsearch为每个索引分片5个主分片和1个副本,这就意味着集群至少需要2个节点。索引将会有5个主分片和5个副本(1个完整副本),每个索引总共有10个分片。
    每个elasticsearch分片是一个Lucene索引。一个单个Lucene索引有最大的文档数LUCENE-5843, 文档数限制为2147483519(MAX_VALUE – 128)。 可通过_cat/shards来监控分片大小。

logstash 介绍

LogStash由JRuby语言编写,基于消息(message-based)的简单架构,并运行在Java虚拟机(JVM)上。不同于分离的代理端(agent)或主机端(server),LogStash可配置单一的代理端(agent)与其它开源软件结合,以实现不同的功能。Logstash是一个完全开源的工具,他可以对你的日志进行收集、分析,并将其存储供以后使用(如,搜索),您可以使用它。说到搜索,logstash带有一个web界面,搜索和展示所有日志。

  • logStash的四大组件

Shipper:发送事件(events)至LogStash;通常,远程代理端(agent)只需要运行这个组件即可;
Broker and Indexer:接收并索引化事件;
Search and Storage:允许对事件进行搜索和存储;
Web Interface:基于Web的展示界面
正是由于以上组件在LogStash架构中可独立部署,才提供了更好的集群扩展性。

  • LogStash主机分类

代理主机(agent host):作为事件的传递者(shipper),将各种日志数据发送至中心主机;只需运行Logstash 代理(agent)程序;
中心主机(central host):可运行包括中间转发器(Broker)、索引器(Indexer)、搜索和存储器(Search and Storage)、Web界面端(Web Interface)在内的各个组件,以实现对日志数据的接收、处理和存储。

kibana

Kibana 是一个基于Web的图形界面也是一个开源和免费的工具,Kibana可以为 Logstash 和 ElasticSearch 提供的日志分析友好的 Web 界面,可以帮助汇总、分析和搜索重要数据日志。
新增了一个FileBeat,它是一个轻量级的日志收集处理工具(Agent),Filebeat占用资源少,适合于在各个服务器上搜集日志后传输给Logstash,官方也推荐此工具
Filebeat隶属于Beats。目前Beats包含四种工具:

Packetbeat(搜集网络流量数据)
Topbeat(搜集系统、进程和文件系统级别的 CPU 和内存使用情况等数据)
Filebeat(搜集文件数据)
Winlogbeat(搜集 Windows 事件日志数据)

使用ELK必要性(解决运维痛点)

开发人员不能登录线上服务器查看详细日志
各个系统都有日志,日至数据分散难以查找
日志数据量大,查询速度慢,或者数据不够实时

总结

ELK 其实并不是一款软件,而是一整套解决方案,之间互相配合使用,完美衔接,高效的满足了很多场合的应用。
比如应用出现故障,需要通过日志排查故障信息。当应用已部署了多个环境时,这时排查的难度和耗时就是一个巨大的损耗。而ELK就可以对多个环境的日志进行收集,过滤,存储,检错,可视化。届时你只需要查看kibana上的日志信息,就可以找出故障所在。
比如针对应用在生产环境上的表现需要数据支撑,如访客数,功能调用量,出错率等等。此类数据的收集,当然可以通过使用别的产品或编写一套程序进行输出,是有其一定的效果。但能像ELK这样对应用无入侵,且功能强大的开源软件。那是少之又少了。

ELK环境的搭建

特别说明一下,我并不是为了学习而学习,而是旨在通过学习某种技术解决某些问题,所以我搭建ELK环境并不会像传统的那样按部就班,而是想要借助 ELK技术和理念 搭建一个自己项目所能用到的日志系统。
今日要完成的目标是可以在laravel项目中使用到ELK日志系统,完成日志的搜集以及分析

ELK搭建

要完成日志系统,需要有以下几个步骤
1.收集日志
2.存储日志
3.日志可视化

1.收集日志

在这一步我们需要完成日志的收集和发送
收集时可以用框架自带的日志记录工具,然后使用Filebeat 采集 Laravel 的本地日志
也可以用第三方比如monolog完成数据的采集和发送
甚至可以让 Laravel 直接将日志写入 Elasticsearch

.env环境变量配置

# 批改日志存在通道
LOG_CHANNEL=elasticsearch
# 增加 elasticsearch 日志存储配置
ELASTIC_HOST=127.0.0.1:9200    # elasticsearch 服务地址
ELASTIC_LOGS_INDEX=shopem-store-es-logs    # elasticsearch 日志存储索引名

增加日志通道

在config/logging.php 文件里的 channels 里增加如下配置

'elasticsearch' => [
            'driver' => 'monolog',
            'level' => 'debug',
            'handler' => \Monolog\Handler\ElasticsearchHandler::class,
            'formatter' => \Monolog\Formatter\ElasticsearchFormatter::class,
            'formatter_with' => [
                'index' => env('ELASTIC_LOGS_INDEX'),
                'type' => '_doc',
            ],
            'handler_with' => [
                'client' => \Elasticsearch\ClientBuilder::create()->setHosts([env('ELASTIC_HOST')])->build(),
            ],
        ],

遇到的问题

按照如上配置,会抛出异常
RuntimeException: Error sending messages to Elasticsearch in file D:\phpstudy_pro\WWW\elasticsearch-php\vendor\monolog\monolog\src\Monolog\Handler\ElasticsearchHandler.php on line 156

具体异常为
mapper_parsing_exception: failed to parse field [context] of type [text] in document with id ‘ytJ8v….
应该是插入某些值到es中时类型不匹配,暂时不懂如何处理
所以就先屏蔽了ElasticsearchHandler.php 中的异常抛出

在ES-HEAD中查看

因为还没有安装kibaba,所以先在es-head中查看,可以看到已经存储进去一些日志了

image.png

2.安装kibaba

Refused to execute inline script because it violates the following Content Security Policy directive: 
"script-src 'unsafe-eval' 'self'". Either the 'unsafe-inline' keyword, a hash 
('sha256-P5polb1UreUSOe5V/Pv7tc+yeZuJXiOi/3fqhGsU7BE='), or a nonce ('nonce-...') is required to enable 
inline execution.

切换到谷歌浏览器后可以正常访问

image.png

  • 查看kibaba状态 http://localhost:5601/status

    image.png

  • 接下来要做的就是把自己的日志数据或者业务数据与kibaba建立联系,就可以图形化,近实时监控,可视化显示了

总结

这几天先是学习了ES的基本语法,尝试使用了ESapi的CURD,安装了ES-HEAD查看ES存储的内容,然后在项目中通过monolog收集了日志直接写入到ES,安装了kibaba,对ELK的技术架构有了一个简单的了解和认识,但还远远到不到商业使用,本期的简单学习就告一段落了,后期会在项目中尝试使用ES,并一步一步完善其架构

本作品采用《CC 协议》,转载必须注明作者和本文链接
本帖由系统于 10个月前 自动加精
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 3

到位,感觉好久不用都忘了

10个月前 评论
DogLoML

666

10个月前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!