搜索引擎优化怎么样(就是优化的核心背景舆情系统过低的新闻搜索)
优采云 发布时间: 2021-12-10 04:15搜索引擎优化怎么样(就是优化的核心背景舆情系统过低的新闻搜索)
随着互联网的飞速发展,互联网上的数据量也在不断增加。各种类型的文章、图片、视频充斥着各种类型的网站和应用。如果用户想访问这些海量的信息中寻找和获取自己喜欢的内容,就需要使用搜索功能。面对如此海量复杂的数据,传统的数据库搜索无法实现快速响应和模糊搜索。这种情况一般采用全文检索技术。Elasticsearch(以下简称ES)和Solr是目前最常用的全文搜索引擎。.
对于搜索引擎来说,查询的响应速度是最重要的,也是用户最直观的感受。本次针对新闻搜索场景的ES优化,实现了单机QPS从8/s提升到108/s,提升120%,通过优化节省了30%的服务器成本。
接下来,享受:
该项目是一个企业舆情监测系统,通过监控公司的各种公开信息和新闻,帮助用户快速了解企业动态,掌握企业舆情动向。而用户操作最多的核心功能之一就是搜索,包括新闻搜索、事件搜索和公司搜索。其中,新闻搜索由于更新频率快、时效性强,数据量最大,也是最容易被用户感知为“慢”、“卡”、“不动”的模块。
因此,本次优化的核心背景是舆情系统的新闻搜索速度慢,QPS过低。详情请参考下图:
该测试是在一个独立的案例中进行的。使用的测试工具是k6。测试方法是通过发起10个线程模拟10个用户的并发请求,观察10分钟内处理请求的次数。
经测试,新闻搜索单服务器QPS8.9/s。由于是单机测试,正式上线后会采用集群,所以正式环境下的QPS肯定会比这个数字大很多。并且由于搜索服务调用频繁,QPS小于10,客户会感觉到明显的滞后,所以这个问题急需优化。另外,公司的大量业务都依赖于ES,所以研究如何改进新闻搜索也可以启发优化其他类似的业务,例如实体链接、知识图谱,包括基础设施部门的日志采集ELK,以及很快。
1.段合并
即对ES内部的新闻索引进行强制分段,这是一种非常常用的加快ES查询速度的手段。
(1)原则
一个索引的所有数据分布在一个集群中的不同节点上,一个节点由若干个分片组成。一个shard就是一个Lucene实例,一个基本的搜索单元,一个查询请求发送到ES,最终会命中所有Lucene实例进行搜索(一个ES索引有一个或多个分片,分片对应实际存储的Lucene索引数据),并汇总。对于 Lucene 实例,索引文件由大量段组成。对于每个查询,Lucene 实例都会打开所有的段进行搜索,打开段需要维护响应的文件句柄和上下文的数量。环境等,所以底层segment的数量会影响搜索效率,大量的小segment会严重拖慢搜索速度。
这里留个洞来补,这就是段合并的原理。为什么每次flush后触发的segment合并仍然有大量的子segment?合并段对在线索引有什么影响?稍后我将描述其中使用的算法。
综上所述,对冷索引执行强制合并有以下好处:
(2)操作
POST //_forcemerge?max_num_segments=
(3)效果
通过段合并,段数从269个增加到10个,QPS值提高了约3倍。
2.使用高亮替换全文
因为pprof的CPU时间在优化的时候被误认为是实际运行时间,而且字符串操作占用了大部分CPU时间,所以我们先尝试减少字符串操作的次数。此前,为了精细调度功能,在召回阶段直接召回整个新闻内容。这种方法是准确的,但它总共会带来两个问题。一是IO造成的传输速度慢,粗糙阶段总共召回100篇,平均一篇文章约5KB,总共约500KB,但如果只召回高的部分,可以减少到10KB以下;另外,文章的长度较长,计算特征时的处理时间也会相应提升。
用highlight替换全文不会对搜索结果产生任何影响,因为新闻搜索对新闻的时间特征特别敏感,所以其他内容特征对最终的评分并不起决定性作用。
最后,QPS的实际提升效果比较一般,服务器QPS大致是32->35,算是小幅提升了。
3.优化细排
通过下面的PPROF图,我们可以发现strings.Split和strings.ToLower在NewSentence函数中占有很大的比例:
我们来分析一下这一步中的代码实际执行的是什么:
func NewSentence(indexCategory string, field string, text string, k1 float64, b float64, weight float64, id string) *SentenceType {
textBlank := strings.ToLower(strings.Replace(text, "
", "
", -1))
textBlankVec := strings.Split(textBlank, " ")
NonBlankVec := make([]string, 0, len(textBlankVec))
for _, term := range textBlankVec {
if len(term) > 0 {
NonBlankVec = append(NonBlankVec, term)
}
}
length := len(NonBlankVec)
// 涉及公司敏感代码,以下省略
}
该函数的目的是根据ES调用的粗排序内容,生成为细排序准备的数据。
该函数先在换行符后加一个空格方便分句,然后所有字符都小写,最后根据空格对单词进行分词。
那么我们可以从两个方面进行优化:
一、为什么ToLower中有map操作?这一步你在做什么?标准库中的ToLower方法是不是做了一些不相关的操作?
其次,为什么不考虑将上述操作结合起来,只遍历所有字符一次?
对于第一个问题,我们可以参考golang中ToLower的官方实现:
<p>// ToLower returns s with all Unicode letters mapped to their lower case.
func ToLower(s string) string {
isASCII, hasUpper := true, false
for i := 0; i < len(s); i++ {
c := s[i]
if c >= utf8.RuneSelf {
isASCII = false
break
}
hasUpper = hasUpper || ('A'