网站基于ElasticSearch搜索的优化笔记 PHP

基本情况就是,媒体、试题、分类,媒体可能有多个试题,一个试题可能有多个分类,分类为三级分类加上一个综合属性。通过试题名称、分类等搜索查询媒体。

现在的问题为,搜索结果不精确,部分搜索无结果,ES的数据结构不满足搜索需求。解决方案就是,重构ES数据结构,采用父子关系的方式,建立media和question两个type。

全程使用https://github.com/mobz/elasticsearch-head,这个进行ES的管理和查看,很方便。

从ES的说明可以看出,ES是面向文档,其实所有的数据都是一张张卡片,例如下面这个:

几个重要的概念:mapping,index,type可以直接参考上图:
_index,可以看做是数据库吧,上图命名为links,在对搜索进行操作的时候需要指定,就像指定数据库一样。

_type,约等于表,例如这里的media,上图的_type列大家还可以看到有question数值。其实就等于我们的media表和question表

mapping,映射、绘制...的地图,顾名思义。其实就是表结构和表关系。例如上面点开的卡片内,_source内有id,language等,其实就是mapping。mapping还包括关系的定义,例如这里的media是parent父级,question的结构创建的时候就需要指定_parent为media。

了解了以上几个概念,我们就可以进行结构创建了。就像数据库一样,我们需要一个media表,放媒体信息,媒体ID作为唯一的ID。然后question表,放question的信息(这里还包括试题的分类),我们把同一个试题分配为不同分类也算作不同试题。这里这样的结构,也是为了根据多级分类搜索的时候方便而设置的,下面说搜索的时候会挑明。

这是初始化创建index和mapping的代码:

    $elasticaClient = new \Elastica\Client(array(‘host‘=>‘localhost‘,‘port‘=>9200));
    // Load index
    $elasticaIndex = $elasticaClient->getIndex(‘links‘);
    // Create the index new
    // 创建index的参数自行参见官网,就不一一解释了
    $elasticaIndex->create(
        array(
            ‘number_of_shards‘ => 4,
            ‘number_of_replicas‘ => 1,
            ‘analysis‘ => array(
                ‘analyzer‘ => array(
                    ‘indexAnalyzer‘ => array(
                        ‘type‘ => ‘custom‘,
                        ‘tokenizer‘ => ‘standard‘,
                        ‘filter‘ => array(‘lowercase‘, ‘mySnowball‘)
                    ),
                    ‘searchAnalyzer‘ => array(
                        ‘type‘ => ‘custom‘,
                        ‘tokenizer‘ => ‘standard‘,
                        ‘filter‘ => array(‘standard‘, ‘lowercase‘, ‘mySnowball‘)
                    )
                ),
                ‘filter‘ => array(
                    ‘mySnowball‘ => array(
                        ‘type‘ => ‘snowball‘,
                        ‘language‘ => ‘German‘
                    )
                )
            )
        ),
        true
    );

    //创建media的mapping,作为父级
    $mediaType = $elasticaIndex->getType(‘media‘);

    // Define mapping
    $mapping = new \Elastica\Type\Mapping();
    $mapping->setType($mediaType);
    $mapping->setParam(‘index_analyzer‘, ‘indexAnalyzer‘);
    $mapping->setParam(‘search_analyzer‘, ‘searchAnalyzer‘);

    // Define boost field
    $mapping->setParam(‘_boost‘, array(‘name‘ => ‘_boost‘, ‘null_value‘ => 1.0));

    // Set mapping
    // 定义media的字段和属性
    $mapping->setProperties(array(
        ‘id‘      => array(‘type‘ => ‘string‘, ‘include_in_all‘ => FALSE),
        ‘media_name‘     => array(‘type‘ => ‘string‘, ‘include_in_all‘ => TRUE),
        ‘tstamp‘  => array(‘type‘ => ‘date‘, ‘include_in_all‘ => FALSE),
        ‘language‘ => array(‘type‘ => ‘integer‘, ‘include_in_all‘ => FALSE),
        ‘_boost‘  => array(‘type‘ => ‘float‘, ‘include_in_all‘ => FALSE)
    ));

    // Send mapping to type
    // 保存media的mapping
    $mapping->send();

    //创建question的mapping,父级为media
    $questionType = $elasticaIndex->getType(‘question‘);

    // Define mapping
    $mapping = new \Elastica\Type\Mapping();
    $mapping->setType($questionType);
    $mapping->setParam(‘index_analyzer‘, ‘indexAnalyzer‘);
    $mapping->setParam(‘search_analyzer‘, ‘searchAnalyzer‘);

    // Define boost field
    $mapping->setParam(‘_boost‘, array(‘name‘ => ‘_boost‘, ‘null_value‘ => 1.0));

    // Set mapping
    // question的字段和属性
    $mapping->setProperties(array(
        ‘id‘      => array(‘type‘ => ‘string‘, ‘include_in_all‘ => FALSE),
        ‘level_one‘      => array(‘type‘ => ‘integer‘, ‘include_in_all‘ => FALSE),
        ‘level_two‘      => array(‘type‘ => ‘integer‘, ‘include_in_all‘ => FALSE),
        ‘level_thr‘      => array(‘type‘ => ‘integer‘, ‘include_in_all‘ => FALSE),
        ‘top_level‘      => array(‘type‘ => ‘string‘, ‘include_in_all‘ => FALSE),
        ‘cat_id‘      => array(‘type‘ => ‘integer‘, ‘include_in_all‘ => FALSE),
        ‘quest_hash‘      => array(‘type‘ => ‘string‘, ‘include_in_all‘ => TRUE),
        ‘content‘     => array(‘type‘ => ‘string‘, ‘include_in_all‘ => TRUE),
        ‘view_num‘      => array(‘type‘ => ‘integer‘, ‘include_in_all‘ => FALSE),
        ‘like_num‘      => array(‘type‘ => ‘integer‘, ‘include_in_all‘ => FALSE),
        ‘_boost‘  => array(‘type‘ => ‘float‘, ‘include_in_all‘ => FALSE)
    ));
    $mapping->setParent("media");//指定question的父类

    // Send mapping to type
    // 保存question的mapping
    $mapping->send();

上面虽然是PHP代码,但是最终生成的也是一个url请求。

下面说搜索,搜索的话ES是通过query、filter等来处理的,query里面有很多不同的方式,参见:http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-queries.html,filter也是,参见http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-filters.html

这里搜索是这样的,根据media的media_name做query_string搜索,然后对media进行has_child的filter搜索,has_child搜索内使用boolAnd的filter来筛选。

下面是搜索的代码:

$query = new \Elastica\Query();
if (!empty($input[‘key‘])) {
    //针对media的media_name字段设置QueryString查询
    $elasticaQueryString  = new \Elastica\Query\QueryString();
    $elasticaQueryString->setFields(array("media.media_name"));
    $elasticaQueryString->setQuery($input[‘key‘]);
    //
    $query->setQuery($elasticaQueryString);
}else {
    $query->setQuery(new MatchAll()); //命中全部纪录
}
$language_bool = false;
$elasticaFilterAnd = new \Elastica\Filter\BoolAnd();
//language也是针对media,设置BoolAnd查询
if (isset($input[‘language‘]) && !empty($input[‘language‘])) {
    $filterl1 = new \Elastica\Filter\Term();
    $filterl1->setTerm(‘language‘, intval($input[‘language‘]));
    $elasticaFilterAnd->addFilter($filterl1);
    $language_bool = true;
}
//
//对子集进行筛选查询,使用has_child
$subFilterAnd = new \Elastica\Filter\BoolAnd();
$bool = false;
// 一级分类条件
if (isset($input[‘level_one‘]) && !empty($input[‘level_one‘])) {
    $filterl1 = new \Elastica\Filter\Term();
    $filterl1->setTerm(‘level_one‘, intval($input[‘level_one‘]));
    $subFilterAnd->addFilter($filterl1);
    $bool = true;
}
// 二级分类条件
if (isset($input[‘level_two‘]) && !empty($input[‘level_two‘])) {
    $filterl1 = new \Elastica\Filter\Term();
    $filterl1->setTerm(‘level_two‘, intval($input[‘level_two‘]));
    $subFilterAnd->addFilter($filterl1);
    $bool = true;
}
// 三级分类条件
if (isset($input[‘level_thr‘]) && !empty($input[‘level_thr‘])) {
    $filterl1 = new \Elastica\Filter\Term();
    $filterl1->setTerm(‘level_thr‘, intval($input[‘level_thr‘]));
    $subFilterAnd->addFilter($filterl1);
    $bool = true;
}
// 直接指定分类ID查询
if (isset($input[‘cat_id‘]) && !empty($input[‘cat_id‘])) {
    $filterl1 = new \Elastica\Filter\Term();
    $filterl1->setTerm(‘cat_id‘, intval($input[‘cat_id‘]));
    $subFilterAnd->addFilter($filterl1);
    $bool = true;
}
// 分类属性查询
if (isset($input[‘top_level‘]) && !empty($input[‘top_level‘])) {
    $filterl1 = new \Elastica\Filter\Term();
    $filterl1->setTerm(‘top_level‘, $input[‘top_level‘]);
    $subFilterAnd->addFilter($filterl1);
    $bool = true;
}
if($bool){
    // 声明一个查询,用于放入子查询
    $subQuery = new \Elastica\Query();
    // 使用filteredquery,融合query和filter
    $filteredQuery = new \Elastica\Query\Filtered(new MatchAll(),$subFilterAnd);
    // 添加filterquery到子查询
    $subQuery->setQuery($filteredQuery);
    // 声明hasChildFilter,声明的时候就指定子查询的内容,指定查询的子表(也就是TYPE)为question
    $filterHasChild = new \Elastica\Filter\HasChild($subQuery,"question");
    // 将拥有子类查询增加到父级查询的filter中
    $elasticaFilterAnd->addFilter($filterHasChild);
}
if($bool || $language_bool){
    // 将filter增加到父查询汇中
    $query->setFilter($elasticaFilterAnd);
}
//
//        
$query->setFrom($start);    // Where to start?
$query->setLimit($limit);   // How many?
//
//Search on the index.
$elasticaResultSet = $elasticaIndex->search($query);

上面看上去很长的PHP代码,其实最后发出的时候也只是一个发送json数据的请求,对照下面这个json数据和上面的代码,大家就很容易明白了:

{
    "query": {
        "query_string": {
            "query": "like",
            "fields": [
                "media.media_name"
            ]
        }
    },
    "filter": {
        "and": [
            {
                "term": {
                    "language": 1
                }
            },
            {
                "has_child": {
                    "query": {
                        "filtered": {
                            "query": {
                                "match_all": {}
                            },
                            "filter": {
                                "and": [
                                    {
                                        "term": {
                                            "top_level": "111"
                                        }
                                    }
                                ]
                            }
                        }
                    },
                    "type": "question"
                }
            }
        ]
    },
    "from": 0,
    "size": 20
}

总结:ES很强大,不仅仅是在导入性能还是搜索性能,或者是搜索结果,或者是结构的调整上来说。作为刚接触不久的也能很快的进行数据结构重构并重写搜索,还是算比较好的。唯一的缺点就是,中文的文档太少,需要不断的使用谷歌来查看文档、去官网看文档说明、看PHP的API。

在处理过程中,感谢之前的哥们留下的代码,至少不是摸瞎。感谢同事搞的谷歌搜索,一般人我不告诉他,大家可以去试试 另客网,首页的搜索框里面,选择谷歌。

部分借鉴来自于这里:http://www.spacevatican.org/2012/6/3/fun-with-elasticsearch-s-children-and-nested-documents/

个人笔记,写来大家分享分享,肯定有不足错误的,欢迎大家指出,谢谢。

网站基于ElasticSearch搜索的优化笔记 PHP

时间: 2024-08-22 02:09:27

网站基于ElasticSearch搜索的优化笔记 PHP的相关文章

千万级记录的Discuz论坛导致MySQL CPU 100%的优化笔记

千万级记录的Discuz论坛导致MySQL CPU 100%的优化笔记 2007年3月,我写过一篇文章<解决一个 MySQL 服务器进程 CPU 占用 100%的技术笔记>( http://www.xiaohui.com/weekly/20070307.htm ),谈到自己在解决一个拥有 60 万条记录的 MySQL 数据库访问时,导致 MySQL CPU 占用 100% 的经过.在解决问题完成优化(optimize)之后,我发现 Discuz 论坛也存在这个问题,当时稍微提了一下: 发现此主

Elasticsearch集群知识笔记

Elasticsearch集群知识笔记 Elasticsearch内部提供了一个rest接口用于查看集群内部的健康状况: curl -XGET http://localhost:9200/_cluster/health response结果: { "cluster_name": "format-es", "status": "green", ... } 这里的status有3种状态,分别是green(所有主分片和复制分片都可用

SQL优化笔记—CPU优化

补充:常规服务器动态管理对象包括,下面有些资料可能会应用到 dm_db_*:数据库和数据库对象dm_exec_*:执行用户代码和关联的连接dm_os_*:内存.锁定和时间安排dm_tran_*:事务和隔离dm_io_*:网络和磁盘的输入/输出 优化性能的常用方法是检索速度最慢的查询构成您 SQL Server 实例上的正常. 每日工作负载的一部分,然后调整它们,一个接一个的"Top 10"列表. 跟踪会话. 请求 和 SQL Server 基础架构中的最耗费大量资源,查询和执行时间最长

大型php网站性能和并发访问优化方案

网站性能优化对于大型网站来说非常重要,一个网站的访问打开速度影响着用户体验度,网站访问速度慢会造成高跳出率,小网站很好解决,那对于大型网站由于栏目多,图片和图像都比较庞大,那该怎么进行整体性能优化呢?本文为你提供一份大型php网站性能和并发访问优化方案. 一.大型网站性能提高策略: 大型网站,比如门户网站,在面对大量用户访问.高并发请求方面,基本的解决方案集中在这样几个环节:使用高性能的服务器.高性能的数据库.高效率的编程语言.还有高性能的Web容器.这几个解决思路在一定程度上意味着更大的投入.

销售型网站还要不要做SEO优化

SEO的操作难度越来越大,优化的成本也越来越高,加上新媒体营销被热捧,站在公司领导角度,具体网站要不要做seo优化,是需要认真认真权衡下.那么本文家世比seo夜无影拿销售型网站来说说做seo优化的抉择. 首先我们试想下,一个销售型的网站,用户可能会通过哪些方式找到我们的? 1.百度搜索品牌关键词进入: 2.搜索除品牌关键词外的其他关键词进入: 3.通过百度竞价方式进入: 4.网站网址在QQ群.微信群里推广: 5.通过自媒体文章.微博等社会化媒体文章引导进入: 6.用户推荐.口碑传播. 以上者6种

Clustering by fast search and find of desity peaks(基于快速搜索与寻找密度峰值的聚类)

基于快速搜索与寻找密度峰值的聚类(Alex Rodriguez and Alessandro Laio) 摘要:聚类分析目的是基于元素之间的相似度对其进行分类,应用范围从天文学到生物信息学.文献计量学到模式识别.我们提出一种方法,思想基于簇中心具有比其邻居更大密度的特点以及与更大密度点之间有一个相对较大的距离(1.簇中心点有相对高的密度 2.簇中心点之间距离一般较大,即不同类别之间一般距离较远),这种思想形成了簇数目直观出现的聚类机制的基础,自动发现和排除异常点,同时在识别簇时,不用关心其形状和

百度移动搜索地域优化服务说明及心得!

今天广州SEO跟大家分享的文章题目是:<运用页面二次点击,提升网站用户体验度>,简单的说就是用户看了一个页面之后,还在当前浏览页面看了用户同样需要看得页面,这样就完成了用户二次需求,用户二次点击了. 在网站关键词排名比较稳定后,这时我们进入了SEO后期维护工作.SEO的后期工作,其实就是根据数据对网站不断进行微调,从而提升用户的体验.那么这些数据我们从个哪里来?这时我们需要用到百度统计里的页面点击图和链接点击图.注意,现在不能用第三方数据了哈.. 页面点击图是百度统计里面的一项统计工具,大多数

根据网站所做的SEO优化整理的一份文档

今日给合作公司讲解本公司网站SEO优化整理的一份简单文档 架构 ########################################## 1.尽量避免Javascript和flash导航. 虽然JS和FLASH能把网站做的绚丽漂亮,但目前搜索引擎还是无法顺利的抓取其中的内容,所以我们要避免. 2.目录层次不能太深. 网站目录尽量保持在三层以内,尽可能接近根网址,比如“www.xxx.com/产品目录/产品名称”明显比“www.xxx.com/产品目录/年份/月份/产品名称”要好. 3

java性能优化笔记(三)java程序优化

程序代码优化要点: 字符串优化:分析String源码,了解String常用方法,使用StringBuffer.StringBuilder. List.Map.Set优化:分析常用ArrayList.LinkedList.HashMap.TreeMap.LinkedHashMap.Set接口.集合常用方法优化. 使用NIO:Buffered.Channel操作和原理,使用零拷贝. 引用优化:强引用.弱引用.软引用.虚引用.WeekHashMap. 优化技巧:常用代码优化技巧.这里不一一罗列,请参考