zoukankan      html  css  js  c++  java
  • ElasticSearch NEST笔记

    ElasticSearch NEST笔记

    1. 什么是ElasticSearch?

    ElasticSearch is a powerful open source search and analytics engine that makes data easy to explore.

     

    可以简单理解成索引加检索的工具,当然它功能多于此。
    ElasticSearch分为服务端与客户端,服务端提供REST API,客户端使用REST API。

     

    2.怎么安装Elastic?

    1. 安装JDK(下载地址
    2. 安装ElasticSearch(下载地址
      1. 解压,运行inelasticsearch.bat.
      2. 浏览器输入http://localhost:9200/可以看到如图:

      3. 安装成功。
    3. 安装ElasticSearch – header plugin https://github.com/mobz/elasticsearch-head

      文档中有详细说明

      完成后,在如下图的地方找到一个html

    4. 安装完成~

    3. 如何使用NEST客户端(文档:http://nest.azurewebsites.net/nest/quick-start.html

    1. 连接
      1. var node = new Uri("http://localhost:9200/");
      2.  
      3. var settings = new ConnectionSettings(
      4.     node,
      5.     defaultIndex: " geopoint-tests "
      6. );
      7.  
      8. var client = new ElasticClient(settings);
    2. 添加索引
      1. client.CreateIndex("geopoint-tests");

      上面这句代码是可以不用写的,因为在调用下面的index方法的时候,如果没有指定使用哪个index,ElasticSearch会直接使用我们在setting中的defaultIndex,如果没有,则会自动创建。

      1. client.Index(obj);

      但是如何你需要使用Mapping来调整索引结构,就会需要CreateIndex这个方法。具体的会在下面的Mapping中提到

    3. 添加数据
      1. client.Index(obj)
    4. 搜索

      正常来说,搜索的需求一般是我们传入一个keyword(和需要搜索的field name),返回符合条件的列表,那么搜索就分为全文搜索和单属性搜索。顾名思义,全文搜索就是用keyword去匹配所有的属性,单属性搜索就是只匹配指定的属性。

    • 全文搜索:
        1.  keyword = String.Format("*{0}*", keyword);
          1. //默认的Operator是Or,当keyword是类似于"One Two"之类的中间有空格的时候,会被当成两个关键词搜索,然后搜索结果进行or运算
          2. //所以我们需要根据需求来调整Operator
        2. var searchResults = client.Search<T>(s => s
        3.     .Index(index)
        4.     .Query(q => q.QueryString(qs => qs.Query(keyword).DefaultOperator(Operator.And)))
        5. );
        6.  
        7. return searchResults.Documents;

    另外由于ES是分词搜索,所以当我们要用"One"来搜索完整的单词"JustOne"的时候,就必须在"One"外面添加**,类似于SQL里面的%keyword%,但是这样的做法会导致在用完整的单词来搜索的时候搜索不到结果,所以我们需要使用下面的方式(如果有更好的方法请不吝赐教):

        1. wholeKeyword = keyword;
        2. keyword = String.Format("*{0}*", keyword);
        3. QueryContainer query = new QueryStringQuery() { Query = keyword, DefaultOperator = Operator.And };
        4. if(!String.IsNullOrEmpty(wholeKeyword)){
        5.    QueryContainer wholeWordQuery = new QueryStringQuery() { Query = wholeKeyword };
        6.    query = query || wholeWordQuery;
        7. }
        8. var searchResults = client.Search<Person>(s => s
        9.     .Index("zhixiao-application")
        10.     .Query(query)
        11. );
    • 指定属性搜索

      指定属性的搜索有两种:

    1. 使用term Query
         QueryContainer query2 = new TermQuery { Field = item.Key, Value = item.Value.ToLower() };

    Term是一个被索引的精确值,也就是说Foo, foo, FOO是不相等的,因此

    在使用term query的时候要注意,term query在搜索的Field已经被索引的时候,是不支持大写的。下面为elasticSearch - header测试

    所有数据:

    大写搜索:

    小写搜索:

    NEST的使用:

        1. var searchResults = client.Search<Person>(s => s
        2.     .Index("zhixiao-application")
        3.     .Query(q => q.Term(t => t.OnField(f => f.Lastname == "keyword")))
        4. );

          或者(效果一样):

        5. QueryContainer termQuery = new TermQuery { Field = "lastname", Value = "keyword" };
        6. var searchResults = client.Search<Person>(s => s
        7.     .Index("zhixiao-application")
        8.     .Query(termQuery)
        9. );

    PS:term query的Field是必须的,如果Field为空,会产生下面的错误           

    2.使用 Query String query

    QueryString query一般用于全文搜索,但是也可以用于单个属性的搜索(设置DefaultField属性),queryString query可以不区分大小写。QueryString还有一个好处就是我们可以搜索一个term中的一部分,例如lastname为"t Boterhuis 1",那么我们可以用"terhuis"搜索到这个数据(虽然需要在外面包上**),在term query里面就做不到,因为ES把每一个属性的值都分析成一个个单独的term,提高了搜索的效率。

    下面为elasticSearch - header测试:

    完整term搜索(大写):

    完整term搜索(小写):

    部分搜索(大写,不带**):

    部分搜索(大写,带**):

    部分搜索(小写,带**):

    多词语搜索:当我们想搜索类似于:"t Boterhuis 2"这样的多个单词构成的keyword(用空格分开),term query是无法查询的,term query顾名思义就是单词查询。不能支持多单词查询

    QueryString query:

    大家可以看到,第三条也被搜索进来了,这是因为ES把"t Boterhuis 2"解析成了三个词汇"t"" Boterhuis""2"。然后分开搜索,把结果集合并,所以ID为4的记录也被搜索出来了。那么我们如何让合起来搜索呢?

        1. string keyword = "t Boterhuis 2";
        2. QueryContainer wholeWordQuery = new QueryStringQuery() { Query = keyword, DefaultOperator = Operator.And };
        3. var searchResults = client.Search<Person>(s => s
        4.     .Index("zhixiao-application")
        5.     .Query(wholeWordQuery)
        6. );

    QueryString query有一个DefaultOperator的属性,我们可以将其设置为And,这样搜索的时候,ES会将几个term的search结果做and运算。

    但是有一个比较大的问题是如果我的keyword是"Aberdeen Boterhuis",

    这样也可以搜索出结果来:

    我们搜索出了ID为7的数据,因为这条数据firstname里面有Aberdeen ,last name里面有Boterhuis。

    目前还没有找到比较好的方法来解决这个问题,如果各位有好的方法请不吝赐教。

    5.排序

    搜索需要我们重新构建索引,这样才能发现错误并且解决他们。

    首先我们把原先的索引先删除了

      1. var response = client.DeleteIndex(new DeleteIndexRequest(new IndexNameMarker() { Name = "zhixiao-application", Type = typeof(Person) };

    然后重新创建索引

      1. var indexResult = client.CreateIndex("zhixiao- application");
      2. var response1 = client.Map<Person>(m => m.MapFromAttributes());
      3. IEnumerable<Person> persons = new List<Person>
      4. {
      5.     new Person()
      6.     {
      7.         Id = "4",
      8.         Firstname = "Boterhuis-040",
      9.         Lastname = "Gusto-040",
      10.         Chains = new string[]{ "a","b","c" },
      11.     },
      12.     new Person()
      13.     {
      14.         Id = "5",
      15.         Firstname = "sales@historichousehotels.com",
      16.         Lastname = "t Boterhuis 1",
      17.         Chains = new string[]{ "a","b","c" },
      18.     },
      19.     new Person()
      20.     {
      21.         Id = "6",
      22.         Firstname = "Aberdeen #110",
      23.         Lastname = "sales@historichousehotels.com",
      24.         Chains = new string[]{ "a","b","c" },
      25.     },
      26.     new Person()
      27.     {
      28.         Id = "7",
      29.         Firstname = "Aberdeen #110",
      30.         Lastname = "t Boterhuis 2",
      31.         Chains = new string[]{ "a","b","c" },
      32.     },
      33. };
      34. foreach (var person in persons)
      35. {
      36.     client.Index(person);
      37. }

    Person的类:

      1. public class Person
      2.     {
      3.         public string Id { get; set; }
      4.         public string Firstname { get; set; }
      5.         public string Lastname { get; set; }
      6.         public string[] Chains { get; set; }
      7.     }

    好了,让我们来测试下排序~

      1).ID排序

      正常!

    var searchResults = client.Search<Person>(s => s

        .Index("zhixiao-application")

        .Sort(sort => sort.OnField(f => f.Id).Order(SortOrder.Ascending))

    );

    2.字符串排序

    咦,奇怪,数据哪里去了呢?刚才ID的排序正常,为啥这里就不正常了呢?

    我们先断点看下:

    可以看到,ConnnectionStatus的status code是200,说明连接成功,但是我们又没有查询出数据来,接下来就需要我们的header工具了

    在最后一个tab中,header工具允许我们将ConnectionStatus.Request中的json用于查询,以此来验证对错。我们可以看到,查询的时候报了一个错误

    Can't sort on string types with more than one value per doc, or more than one token per field

    因为我们的数据是

    这样的,在解析的时候,firstname会被analyse成多个term,所以在排序的时候,就会出错。

    那么我们要怎么办呢?

    http://blog.wiercinski.net/2011/uncategorized/elasticsearch-sorting-on-string-types-with-more-than-one-value-per-doc-or-more-than-one-token-per-field/

    在这里我找到了一些答案,我们需要将一个我们需要排序的字段mapping成两个不同的字段,一个经过分析(e.g. firstname),一个没有经过分析(e.g. firtname.sort ).

    在NEST中,有一个简便的方法,就是在你需要排序的属性上面加一个Attribute

    [ElasticProperty(AddSortField = true)]

       所以我们先将原先的索引删除,然后重新添加索引。

       client.Map<Person>(m => m.MapFromAttributes());

       这句代码很重要(在调用它之前需要先CreateIndex),它根据ElasticProperty对对象进行了mapping,使得firstname变成了一个multi fields,这点我们可以从      Request的Json中红色部分看出

    {
        "person": {
            "properties": {
                "id": {
                    "type": "string"
                },
                "firstname": {
                    "type": "multi_field",
                    "fields": {
                        "firstname": {
                            "type": "string"
                        },
                        "sort": {
                            "index": "not_analyzed",
                            "type": "string"
                        }
                    }
                },
                "lastname": {
                    "type": "string"
                },
                "chains": {
                    "type": "string"
                }
            }
        }
    }

    另外在header中可以发现,属性中多了一个person.firstname.sort

    所以我们现在可以使用这个属性来进行排序了

    代码如下:

    var searchResults = client.Search<Person>(s => s

    .Index("zhixiao-application")

    .Sort(sort => sort.OnField(f => f.Firstname.Suffix("sort")).Ascending())

    );

    PS:注意上面红色部分的代码

    排序结果如下:

    成功!

    3.距离排序

    Elastic Search 自带了距离的排序和距离的筛选。所以我们只需要将索引建好,就可以使用。

    好了,下面我们就一步步来进行:

           (1)创建model
     
    public class Location
    {
        public string Name { get; set; }
    
        [ElasticProperty(Type = FieldType.GeoPoint)]
        public Coordinate Coordinate { get; set; }
    }
    
    public class Coordinate
    {
        public double Lat { get; set; }
    
        public double Lon { get; set; }
    }

         PS:[ElasticProperty(Type = FieldType.GeoPoint)]这个attribute是为了下面mapping的时候,ES会将其识别为GeoPoint

            (2)创建索引并且mapping
    client.CreateIndex("geopoint-tests", s => s
        .AddMapping<Location>(f => f
        .MapFromAttributes()
        .Properties(p => p
            .GeoPoint(g => g.Name(n => n.Coordinate).IndexLatLon())
         )
       )
    );

    下面是创建索引并且mapping的request json: 

    {
        "settings": {
            "index": { }
        },
        "mappings": {
            "location": {
                "properties": {
                    "name": {
                        "type": "string"
                    },
                    "coordinate": {
                        "type": "geo_point",
                        "lat_lon": true
                    }
                }
            }
        }
    }
          (3)创建数据:为了方便大家看出来正确的排序,我将latitude设置成一样的,这样可以一眼就看出来排序的对错
    client.IndexMany(new[]
    {
    
        createLocation("1", 52.310551, 5.07039),
    
        createLocation("2", 52.310551, 10.761176),
    
        createLocation("3", 52.310551, 8.07039),
    
        createLocation("4", 52.310551, 6.07039),
    
    });
    
    private static Location createLocation(string name, double latitude, double longitude)
    
    {
    
        return new Location
    
        {
    
            Name = name,
    
            Coordinate = new Coordinate { Lat = latitude, Lon = longitude }
    
        };
    
    }

      (4)排序的使用:

      1. var results = client.Search<Location>(s => s
      2.     .SortGeoDistance(sort => sort.OnField("coordinate").PinTo(52.310551, 4.404954).Ascending()));

      结果:

      

      成功!

       4.距离Filter(其实这个应该分类在query中,不过这个需要在上面排序的代码上面做修改,所以就放到这里了)

      代码如下:

      1. var results = client.Search<Location>(s => s
      2.    .Filter(f => f.GeoDistance("coordinate", fd => fd.Distance(100, GeoUnit.Kilometers).Location(52.310551, 4.404954)))
      3.    .SortGeoDistance(sort => sort.OnField("coordinate").PinTo(52.310551, 4.404954).Ascending()));

      结果如下:

      

    6.更新数据

    由于比较简单,我就不解释啥了,直接上代码

                Person newperson = new Person()
                {
                    Id = "7",
                    Firstname = "Aberdeen #110",
                    Lastname = "Update",
                    Chains = new string[] { "a", "b", "c" },
                };
    
                UpdateRequest<Person> updateRequest = new UpdateRequest<Person>(7)
                {
                    Doc = newperson
                };
    
                IUpdateResponse updateResponse = updateClient.Update<Person, Person>(updateRequest);
                if (updateResponse.ConnectionStatus.HttpStatusCode != 200)
                {
                    updateClient.Index(newperson);
                }
    
                client.ClearCache();
    
                var searchResults = updateClient.Search<Person>(s => s
                    .Index("zhixiao-application")
                    .Query(q => q.Term(t => t.OnField(f => f.Id).Value(7)))
                );
    
    
                Console.WriteLine(searchResults.Documents.Count());
    
                foreach (var person in searchResults.Documents)
                {
                    Console.WriteLine(person.Id + "," + person.Lastname);
                }

    PS:需要注意的是,如果你需要清除一个属性的值,传入null会导致ES认为你不需要更新这个属性,所以清除一个属性的值我们需要传入一个""。

    7.删除数据
    //delete one by id

    updateClient.Delete<Person>(d => d.Id(7));

    //delete one by object

     updateClient.Delete<Person>(new Person() { });

    //delete the Indices

     updateClient.DeleteIndex(new DeleteIndexRequest(new IndexNameMarker() { Name = "zhixiao-application", Type = typeof(Person) }));

  • 相关阅读:
    PHP实现无限极分类
    html2canvas生成并下载图片
    一次线上问题引发的过程回顾和思考,以更换两台服务器结束
    Intellij IDEA启动项目报Command line is too long. Shorten command line for XXXApplication or also for
    mq 消费消息 与发送消息传参问题
    idea 创建不了 java 文件
    Java switch 中如何使用枚举?
    Collections排序
    在idea 设置 git 的用户名
    mongodb添加字段和创建自增主键
  • 原文地址:https://www.cnblogs.com/Angle-Louis/p/4218678.html
Copyright © 2011-2022 走看看