使用 ES + 云开发实战优化网站搜索javascript
你们好,我是鱼皮,今天搞一场技术实战,需求分析 => 技术选型 => 设计实现,从 0 到 1,带你们优化网站搜索的灵活性。前端
本文大纲:java
我开发的 编程导航网站 已经上线 6 个月了,可是从上线之初,网站一直存在一个很严重的问题,就是搜索功能并很差用。node
此前,为了追求快速上线,搜索功能就简单地使用了数据库模糊查询(包含)来实现,开发是方便了,但这种方式很不灵活。git
举个例子,网站上有个资源叫 “Java 设计模式”,而用户搜索 “Java设计模式” 就啥都搜不出来,缘由是资源名中包含了空格,而用户搜索时输入的关键词并不包含空格。程序员
空格只是一种特例,相似的状况还有不少,好比网站上有个资源叫 “Java 并发编程实战”,但用户搜索 “Java 实战” 时,明明前者包含 “Java” 和 “实战” 这两个词,但倒是什么都搜不出来的。github
要知道,搜索功能对于一个信息聚合类站点是相当重要的,直接影响用户的体验。在你的网站上搜不到资源,谁还会用?shell
因此我也收到了一些小伙伴的礼貌建议,好比这位秃头 Tom:数据库
以前没有优化搜索,主要是两个缘由:穷 + 怕麻烦。但随着网站用户量的增大,是时候填坑了!编程
想要提升网站搜索灵活性,可使用 全文搜索 技术,在前端和后端均可以实现。
有时,咱们要检索的数据是有限的,且全部数据都是 存储在客户端 的。
好比我的博客网站,咱们一般会把每篇文章做为一个文件存放在某目录下,而不是存在后台数据库中,这种状况下,不须要再从服务器上去请求动态数据,那么能够直接在前端搜索数据。
有一些现成的搜索库,好比 Lunr.js
(GitHub 7k+ star),先添加要检索的内容:
var idx = lunr(function () {
this.field('title')
this.field('body')
// 内容
this.add({
"title": "yupi",
"body": "wx搜程序员鱼皮,阅读个人原创文章",
"id": "1"
})
})
复制代码
而后搜索就能够了:
idx.search("鱼皮")
复制代码
纯前端全文搜索的好处是无需后端、简单方便,能够节省服务器的压力;无需连网,也没有额外的网络开销,检索更快速。
区别于前端,后端全文搜索在服务器上完成,从远程数据库中搜索符合要求的数据,再直接返回给前端。
目前主流的后端全文搜索技术是 Elasticsearch,一个分布式、RESTful 风格的搜索和数据分析引擎。
它的功能强大且灵活,可是须要本身搭建、定义数据、管理词典、上传和维护数据等,可操做性很强,须要一些水平,新手和大佬设计出的 ES 搜索系统那是天差地别。
因此,对于不熟悉 Elasticsearch 的同窗,也能够直接使用现成的全文检索服务。好比 Algolia,直接经过它提供的 API 上传须要检索的数据,再用它提供的 API 检索就好了。它提供了必定的免费空间,对于小型网站和学习使用彻底足够了。
那么个人编程导航网站选择哪一种实现方式呢?
首先,该网站的资源数是不固定的、无规律动态更新的,所以不适合前端全文检索。
其次,考虑到往后网站的数据量会比较大,并且可能要根据用户的搜索动态地去优化检索系统(好比自定义编程词典),所以考虑使用 Elasticsearch 技术 自行搭建搜索引擎,而不用现成的全文检索服务,这样从此本身想怎么定制系统均可以。此外,不用向其余平台发送网站数据,能保证数据的安全。
肯定使用 Elasticsearch 后,要先搭建环境。
能够本身购买服务器,再按照官方文档一步步手动安装。对于有必定规模的我的网站来讲,虽然搭建过程不难,但后期的维护成本倒是巨大的,好比性能分析、监控、告警、安全等等,都须要本身来配置。尤为是后期网站数据量更大了,还要考虑搭建集群、水平扩容等等。
所以,我选择直接使用云服务商提供的 Elasticsearch 服务,这里选择腾讯云,自动为你搭建了现成的 ES 集群服务,还提供了可视化架构管理、集群监控、日志、高级插件、智能巡检等功能。
虽然 ES 服务的价格贵,但节省下大量时间成本,对我来讲是值得的。
还有个很方便的定制化搜索服务 Elastic App Search,你们感兴趣能够试试。
咱们的目标是优化网站资源的搜索功能,但接下来要作的不是直接编写具体的业务逻辑,而是先开发一个 公共的 ES 服务 。
其实对 ES 的操做比较简单,能够先简单地把它理解为一个数据库,那么公共的 ES 服务应具备基本的增删改查功能,供其余函数调用。
因为编程导航的后端使用的是腾讯云开发技术,用 Node.js 来编写服务,因此选用官方推荐的 @elastic/elasticsearch
库来操做 ES。
没用过云开发也没事,能够先把它理解为一个后端,欢迎阅读我以前的文章:了解云开发 。
代码很简单,先是创建和 ES 的链接,此处为了保证数据安全,使用内网地址:
const client = new Client({
// 内网地址
node: 'http://10.0.61.1:9200',
// 用户名和密码
auth: {
username: esConfig.username,
password: esConfig.password,
},
});
复制代码
而后是编写增删改查。这里作一步 抽象,经过 switch
等分支语句,根据请求参数来区分操做、要操做的数据等,这样就不用把每一个操做都独立写成一个接口了。
// 接受请求参数
const { op, index, id, params } = event;
// 根据操做执行增删改查
switch (op) {
case 'add':
return doAdd(index, id, params);
case 'delete':
return doDelete(index, id);
case 'search':
return doSearch(index, params);
case 'update':
return doUpdate(index, id, params);
}
复制代码
在云开发中,假如某个函数过久没被调用,就会释放资源。下次请求时,会进行冷启动,从新建立资源,致使接口返回较慢。所以,把多个操做封装到同一个函数中,也能够减小冷启动的概率。
具体的增删改查代码就不赘述了,对着 ES Node 的官方文档看一遍就好了,后面会把代码开源到编程导航仓库中(github.com/liyupi/code…
编写好代码后,能够用云开发自带的 tcb
命令行工具在本地执行该函数。
记得先把 ES 的链接地址改为公网,而后输入一行命令就好了。好比咱们要向 ES 插入一条数据,传入要执行的函数名、请求参数、代码路径:
tcb fn run
--name <functionName>
--params "{\"op\": \"add\"}"
--path <functionPath>
复制代码
执行成功后,就能在 ES 中看到新插入的数据了(经过 Kibana 面板或 curl 查看):
本地测试好公共服务代码后,把 ES 链接地址改为内网 IP,而后发布到云端。
接下来试着编写一个其余的函数来访问公共 ES 服务,好比插入资源到 ES,经过 callFunction
请求:
// 添加资源到 ES
function addData() {
// 请求公共服务
app.callFunction({
name: 'esService',
data: {
op: 'add',
index: 'resource',
id,
params: data,
}
});
}
复制代码
可是,数据并无被成功插入,而是返回了接口超时,Why?
经过日志得知是 ES 链接不上,会不会是由于发布上线的 ES 公共服务所在的机器和 ES 不在同一个内网呢?
因此须要在云开发控制台更改 ES 公共服务的私有网络配置,选择和购买 ES 时一样的子网就好了:
修改以后,再次远程请求 ES 公共服务,数据就插入成功了~
开发好 ES 公共服务后,就能够编写具体的业务逻辑了。
首先要在 ES 中创建一个索引(相似数据库的表),来约定数据的类型、分词等信息,而不是容许随意插入数据。
好比为了更灵活搜索,资源名应该指定为 "text" 类型,以开启分词,并指定 ik
中文分词器:
"name": {
"type": "text",
"analyzer": "ik_max_word",
"search_analyzer": "ik_smart",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}
复制代码
而点赞数应设置为 "long" 类型,只容许传入数字:
"likeNum": {
"type": "long"
}
复制代码
最好还要为索引指定一个别名,便于后续修改字段时重建索引:
"aliases" : { "resource": {}}
复制代码
编写好创建索引的 json 配置后,经过 curl 或 Kibana 去调用 ES 新建索引接口就好了。
以前,编程导航网站的资源数据都是存在数据库中的,用户从数据库中查询。而如今要改成从 ES 中查询,ES 空空如也可不行,得想办法把数据库中的资源数据同步到 ES 中。
这里有几种同步策略。
之前,用户推荐的资源只会插入到数据库,双写是指在资源插入数据库的时候,同时插入到 ES 就行了。
听上去挺简单的,但这种方式存在一些问题:
那有没有对现有代码 侵入更小 的方法呢?
若是对数据实时性的要求不高,能够选择定时同步,每隔一段时间将最新插入或修改的数据从数据库复制到 ES 上。
实现方式有不少种,好比用 Logstash
数据传输管道,或者本身编写定时任务程序,这样就彻底不用改现有的代码。
若是对数据实时性要求很高,刚刚插入数据库的数据就要能马上就能被搜索到,那么就要实时同步。除了双写外,还能够监听数据库的 binlog,在数据库发生任何变动时,咱们都能感知到。
阿里有个开源项目叫 Canal
,可以实时监听 MySQL 数据库,并推送通知给下游,感兴趣的朋友能够看看。
因为编程资源的搜索对实时性要求不高,因此定时同步就 ok。
云开发默认提供了定时函数功能,我就直接写一个云函数,每 1 分钟执行一次,每次读取数据库中近 5 分钟内发生了变动的数据,以防止上次执行失败的状况。此外,还要配置超时时间,防止函数执行时间过长致使的执行失败。
在云开发 - 云函数控制台就能可视化配置了,须要为定时任务指定一个 crontab 表达式:
开启定时同步后,不要忘了再编写并执行一个 首次 同步函数,用于将历史的全量数据同步到 ES。
如今 ES 上已经有数据了,只剩最后一步,就是怎么把数据搜出来呢?
首先咱们要学习 ES 的搜索 DSL(语法),包括如何取列、搜索、过滤、分页、排序等,对新手来说,仍是有点麻烦的,尤为是查询条件中布尔表达式的组合,稍微不注意就查不出数据。因此建议你们先在 Kibana 提供的调试工具中编写查询语法:
查出预期的数据后,再编写后端的搜索函数,接受的请求参数最好和原接口保持一致,减小改动。
能够根据前端传来的请求动态拼接查询语法,好比要按照资源名搜索:
// 传了资源名if (name) { // 拼接查询语句 query.bool.should = [ { match: { name } } ];}
复制代码
由此,整个网站的搜索优化完毕。
再去试一下效果,如今哪怕我输入一些多 “鱼” 的词,也能搜到了!
ES 是怎么实现灵活搜索的呢?欢迎阅读 这篇文章 。
新 ES 搜索接口的发布并不意味着老的数据库查询接口淘汰,能够同时保留。按名称搜索资源时用新接口,更灵活;而根据审核状态、搜索某用户发布过的资源时,能够用老接口,从数据库查。从而分摊负载,职责分离,让对的技术作对的事情!
以上就是本期分享,有帮助的话点个赞吧 ❤️
我是鱼皮,最后再送你们一些 帮助我拿到大厂 offer 的学习资料:
欢迎阅读 我从 0 自学进入腾讯的编程学习、求职、考证、写书经历,再也不迷茫!