你知道的越多,你不知道的越多java
点赞再看,养成习惯git
GitHub github.com/JavaFamily上已经收录有一线大厂面试点脑图、我的联系方式和技术交流群,欢迎Star和指教github
这是帅丙真实事件,你们都知道不少公司都是有故障等级这么一说的,这就是敖丙在公司背的P0级故障,敖丙差点所以被解雇,事情通过十分惊心动魄,个人心脏病都差点复发。web
事故等级主要针对生产环境,划分依据相似于bug等级。面试
P0属于最高级别事故,好比崩溃,页面没法访问,主流程不通,主功能未实现,或者在影响面上影响很大(即便bug自己不严重)。正则表达式
P1事故属于高级别事故,通常属于主功能上的分支,支线流程,核心次功能等,后面还有P2,P3等,主要根据企业实际状况划分。算法
敖丙以前也负责公司的商品搜索业务,由于业务体量增速太快了,商品表中的商品数据也很快跃入千万级别,查询的RT(response time 响应时间)也愈来愈高了,并且产品说须要根据更多维度去查询商品。数据库
由于以前咱们都是根据商品的名称去查询的,可是电商其实都会根据不少个维度去查询商品。apache
就好比你们去淘宝的查询的时候就会发现,你搜商品名称、颜色、标签等等多个维度均可以找到这个商品,就好比下图的搜索,我只是搜了【帅丙】你会发现,名字里面也没有连续的帅丙两个字,有帅和丙的出来了数组
你们知道的传统的关系型数据库都是用什么 name like %帅丙% 这样的方式查询的,并且查询出来的结果确定只能是name里面带帅丙的对吧。
那你还想搜别的字段好比什么尺寸、关键词、价格等等,都能搜到帅丙,这至关因而多个维度的了,传统的关系型数据库作不到呀。
作技术选型的时候,帅丙第一时间想到了搜索引擎。
当时市面是比较流行的有:Apache Lucene、Elasticsearch、Solr
搜索引擎我后面会讲ELK(Elasticsearch、Logstash、Kibana)和Canal,我呀真的是太宠大家了,这样会不会把大家惯坏了。
帅丙我呀,噼里啪啦一顿操做,最后得出结论:
相对来说,若是考虑静态搜索,Sorl相对更合适。
若是考虑实时,涉及到分布式,Elasticsearch相对合适。
那咱们商品仍是要实时的呀,你后台改了价格啥的,是否是都要实时同步出去,否则不是炸了嘛。
看到这,我想可爱的你和帅丙心中都有了答案:Elasticsearch这是个神同样的引擎。
我这里就作一个简单的介绍就行了,细节的点咱们后面去他的章节讲,啥都写了,敖丙哪里有这么多素材写文章?
ElasticSearch是一个基于Lucene的搜索服务器。
它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口。
Elasticsearch是用Java语言开发的,并做为Apache许可条款下的开放源码发布,是一种流行的企业级搜索引擎。
ElasticSearch用于云计算中,可以达到实时搜索,稳定,可靠,快速,安装使用方便。官方客户端在Java、.NET(C#)、PHP、Python、Apache Groovy、Ruby和许多其余语言中都是可用的。
根据DB-Engines的排名显示,Elasticsearch是最受欢迎的企业搜索引擎,其次是Apache Solr,也是基于Lucene。
看过敖丙以前文章的朋友都知道,咱们作技术选型以前,要作什么呀,设计!
咱们要去了解这玩意的好处、坏处、常见的坑、出了问题的应急预案等等,还有他的数据同步机制啊,持久化机制啥的,就是高可用嘛。
一样的我不大篇幅介绍了,之后都会写的嘛,我就给你们看看我当时作的设计吧。
这个只是最初的demo,详细的终稿我就不给你们看了,由于有不少公司内部的逻辑。
不过你们仍是能够看到敖丙真的考虑了不少,仍是那句话,不打没把握的仗!
设计作好敖丙就卡卡卡的用起来了。
说实话,真香,这玩意真的好用,学习成本也很低,查询语句分分钟掌握了,官网文档把功能介绍得清晰无比。
https://www.elastic.co/cn/
用着用着重头戏来了,大家都知道敖丙我是作电商活动的,都是什么很高的流量打进来这样,仍是如往常同样上线了一个活动。
这是一个月飞风高的夜晚,丝丝凉风迎面吹来,敖丙清闲的坐在椅子上,手里拿着破旧的茶杯,喝着外婆炒的苦荆茶,享受着这惬意的时光。
忽然,说时迟那时快,运维打来了紧急电话ES集群CPU打到了99%要挂了,个人心蓦然一痛,内心还在庆幸仍是集群没崩。
而后他接着说了一句,很差集群挂了!
敖丙卒,本篇完….
开玩笑的哈,不过当时敖丙真的要死的心真的都要有了,就在崩掉的1分钟内,就有用户反馈搜索未响应,我第一时间想到的就是重启,因而我一个健步冲出去,开启电脑,进机器,输入了重启命令。
好了,是的好了,还好有惊无险,不过只过了10秒,集群又99%了,呐呢?
我又只能重启了,此次没挂,过了好久好久,直到活动结束,仍是没挂。
可是此次影响到线上,3分钟的搜索未响应,我想我估计明天是要去财务领工资,提早回家过年了。
还好Leader说没事,先找到问题,把他修复掉。
大家都知道敖丙天才来的,我第一时间想到的就是看日志,我登上去看es没报错,再看自己的服务,除了超时的错误啥都没有,卧槽,是的当时我脑壳嗡嗡响。
不过我继续想为啥是个人搜索挂了,会不会是有人搜了什么奇怪的东西?
我打开了个人搜索日志!!!
卧槽这不是吧,哪一个坑爹玩意搜这么长的一串中文,差很少250个字吧。
可是我一想,搜这么长也不该该打挂服务啊,会不会是我写了bug!
我脸颊流下一滴汗水💦,我看了看周围,发现没人注意到个人紧张,我故做镇定的把它擦掉。
我仔细一想,别人查询虽然长,就算查数据库也没事啊,为啥es就报错了?会不会?
Es有Bug!没错确定是Es的锅。
那为啥会这样呢,我直接跟老大这样解释也好像不行啊,仍是要被开除的吧!
因而我去看看看代码,我在关键词使用了通配符,我当时是为了匹配更多内容才这么作的,相似数据库的like,Es的通配符就是: * 帅丙 * 这样在关键词先后加“*”号去查询。
后面我发现就是通配符的锅,那柯南丙就说一下为啥会这样的问题出现。
许多有RDBMS/SQL背景的开发者,在初次踏入ElasticSearch世界的时候,很容易就想到使用通配符(Wildcard Query)来实现模糊查询(好比用户输入补全),由于这是和SQL里like操做最类似的查询方式,用起来感受很是温馨。
然而帅丙的故障就揭示了,滥用Wildcard query可能带来灾难性的后果。
我当时首先复现了问题
POST test_index/type1/?refresh=true
{
"foo": "bar"
}
2.使用wildcard query执行一个首尾带有通配符*的长字符串查询
POST /test_index/_search
{
"query": {
"wildcard": {
"foo": {
"value": "轻轻的我走了,正如我轻轻的来;我轻轻的招手,道别西天的云彩。那河畔的金柳,是夕阳中的新娘;波光里的艳影,在个人心头荡漾。软泥上的青荇,油油的在水底招摇;在康河的柔波里,我甘心作一条水草!那榆荫下的一潭,不是清泉,是天上虹;揉碎在浮藻间,沉淀着彩虹似的梦。寻梦?撑一支长篙,向青草更青处漫溯;满载一船星辉,在星辉斑斓里放歌。但我不能放歌,悄悄是别离的笙箫;夏虫也为我沉默,沉默是今晚的康桥!悄悄的我走了,正如我悄悄的来;我挥一挥衣袖,不带走一片云彩。"
}
}
}
}
{
"took": 3445,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"failed": 0
},
"hits": {
"total": 0,
"max_score": null,
"hits":
}
}
即便no hits,耗时倒是惊人的3.4秒 (测试机是macbook pro, i7 CPU),而且执行过程当中,CPU有一个很高的尖峰。
线上的查询比我这个范例要复杂得多,会同时查几个字段,实际测试下来,一个查询可能会执行十几秒钟。
再有比较多长字符串查询的时候,集群可能就DOS了。
为何对只有一条数据的索引作这个查询开销这么高? 直觉上应该是瞬间返回结果才对!
回答这个问题前,能够再作个测试,若是继续加大查询字符串的长度,到了必定长度后,ES直接抛异常了,服务ES里异常给出的cause以下:
Caused by: org.apache.lucene.util.automaton.TooComplexToDeterminizeException: Determinizing automaton with 22082 states and 34182 transitions would result in more than 10000 states. at org.apache.lucene.util.automaton.Operations.determinize(Operations.java:741) ~[lucene-core-6.4.1.jar:6.4.1
解释:该异常来自org.apache.lucene.util.automaton这个包,异常缘由的字面含义是说“自动机过于复杂而没法肯定状态: 因为状态和转换太多,肯定一个自动机须要生成的状态超过10000个上限"
柯南丙网上查找了大量资料后,终于搞清楚了问题的前因后果。
为了加速通配符和正则表达式的匹配速度,Lucene4.0开始会将输入的字符串模式构建成一个DFA (Deterministic Finite Automaton),带有通配符的pattern构造出来的DFA可能会很复杂,开销很大。
好比a*bc构造出来的DFA就像下面这个图同样:
看了一下Lucene的里相关的代码,构建过程大体以下:
public static Automaton toAutomaton(Term wildcardquery) {
List<Automaton> automata = new ArrayList<>();
String wildcardText = wildcardquery.text();
for (int i = 0; i < wildcardText.length();) {
final int c = wildcardText.codePointAt(i);
int length = Character.charCount(c);
switch(c) {
case WILDCARD_STRING:
automata.add(Automata.makeAnyString());
break;
case WILDCARD_CHAR:
automata.add(Automata.makeAnyChar());
break;
case WILDCARD_ESCAPE:
// add the next codepoint instead, if it exists
if (i + length < wildcardText.length()) {
final int nextChar = wildcardText.codePointAt(i + length);
length += Character.charCount(nextChar);
automata.add(Automata.makeChar(nextChar));
break;
} // else fallthru, lenient parsing with a trailing \
default:
automata.add(Automata.makeChar(c));
}
i += length;
}
return Operations.concatenate(automata);
}
复制代码
此时生成的状态机是不肯定状态机,也就是Non-deterministic Finite Automaton(NFA)。
org.apache.lucene.util.automaton.Operations类里的determinize方法则会将NFA转换为DFA
/**
\* Determinizes the given automaton.
\* <p>
\* Worst case complexity: exponential in number of states.
\* @param maxDeterminizedStates Maximum number of states created when
\* determinizing. Higher numbers allow this operation to consume more
\* memory but allow more complex automatons. Use
\* DEFAULT_MAX_DETERMINIZED_STATES as a decent default if you don't know
\* how many to allow.
\* @throws TooComplexToDeterminizeException if determinizing a creates an
\* automaton with more than maxDeterminizedStates
*/
复制代码
代码注释里说这个过程的时间复杂度最差状况下是状态数量的指数级别!
为防止产生的状态过多,消耗过多的内存和CPU,类里面对最大状态数量作了限制
/**
* Default maximum number of states that {@link Operations#determinize} should create.
*/
public static final int DEFAULT_MAX_DETERMINIZED_STATES = 10000;
复制代码
在有首尾通配符,而且字符串很长的状况下,这个determinize过程会产生大量的state,甚至会超过上限。
至于NFA和DFA的区别是什么? 如何相互转换?
网上有不少数学层面的资料和论文,限于帅丙算法方面有限的知识,无精力去深刻探究。
可是一个粗浅的理解是: NFA在输入一个条件的状况下,能够从一个状态转移到多种状态,而DFA只会有一个肯定的状态能够转移,所以DFA在字符串匹配时速度更快。
DFA虽然搜索的时候快,可是构造方面的时间复杂度可能比较高,特别是带有首部通配符+长字符串的时候。
回想Elasticsearch官方文档里对于Wildcard query有特别说明,要避免使用通配符开头的term。
" Note that this query can be slow, as it needs to iterate over many terms. In order to prevent extremely slow wildcard queries, a wildcard term should not start with one of the wildcards * or ?."
结合对上面Wildcard query底层实现的探究,也就不难理解这句话的含义了!
小结: Wildcard query应杜绝使用通配符打头,实在不得已要这么作,就必定须要限制用户输入的字符串长度。
最好换一种实现方式,经过在index time作文章,选用合适的分词器,好比nGram tokenizer预处理数据,而后使用更廉价的term query来实现同等的模糊搜索功能。
对于部分输入即提示的应用场景,能够考虑优先使用completion suggester, phrase/term suggeter一类性能更好,模糊程度略差的方式查询,待suggester没有匹配结果的时候,再fall back到更模糊但性能较差的wildcard, regex, fuzzy一类的查询。
补充: 有同窗问regex, fuzzy query是否有一样的问题,答案是有,缘由在于他们底层和wildcard同样,都是经过将pattern构形成DFA来加速字符串匹配速度的。
回忆:为啥以前挂了一次重启恢复了,立刻又挂了?用户搜了两次。。。
其实解决这种问题很简单,既然知道关键词长了会有问题,我就作限制嘛,你们能够去看看搜索引擎某度、某宝啥的,是否是都作了长度限制?
我复制了很长的一段汉字进去百度就是这个结果咯,某宝过长都返回默认页面了。
若是你的产品必定要给用户一点东西,简单,找出一些热词分析出来就行了,或者给点热搜商品兜底。
我怎么作的呢?判断字符串长度大于50我就直接返回空数组了,这样对用户体验好点,你返回个参数错误或者默认错误别人还觉得你有Bug呢对吧。
其实敖丙我啥事故等级都没背哈哈,这个算是事故,可是敖丙我这么可爱,领导也心疼我啊,确定不会怪个人拉,主要是我设计都考虑了不少方案和场景了,没想到有这个坑。(yy:敖丙你个渣男,又是标题党,人家还觉得你没工做了要养你呢!)
你们也能够经过此次事故体会到,技术选型的时候,方案的重要性了吧,就算你考虑不全,可是不至于真正的问题来了手足无措啊,并非全部的事故均可以像此次这样重启就搞定了,不要存有侥幸心理,心存敬畏。
恰饭环节:没需求的小伙伴跳过就行了,我帮阿里推广服务89/年,229/3年,买来送本身,送女友立刻过年再合适不过了,买了搭建个项目给面试官看也香,还能够熟悉技术栈,我明天会出一个服务器搭建我的项目的教程(老用户用家人的购买,我用我妈的😂)。
扫码或者点击购买
敖丙啊,又有牌面了,获得阿里消息中间件团队小伙伴的承认,而且发现竟然是我学姐-风云(花名)!!!
她是个好学的小姐姐,你们多多像优秀的仔学习,学姐不是作技术的,可是都在不断学习,说实话个人眼角又湿了。
我准备把个人公众号JavaFamily 这个名字改了,这个名字仍是差点意思,可是又不能叫敖丙了,被注册商标了,我就问了下群里的人才,目前有两个我比较喜欢的
由于这个可能会陪伴我好久,甚至直到死去,但愿你们都给点建议哈哈。
别问我为啥要跟敖丙这个名字相关,再问自杀!
我花名就叫这个,因此😂
好了各位,以上就是这篇文章的所有内容了,能看到这里的人呀,都是人才。
我后面会每周都更新几篇一线互联网大厂面试和经常使用技术栈相关的文章,很是感谢人才们能看到这里,若是这个文章写得还不错,以为「敖丙」我有点东西的话 求点赞👍 求关注❤️ 求分享👥 对暖男我来讲真的 很是有用!!!
创做不易,各位的支持和承认,就是我创做的最大动力,咱们下篇文章见!
敖丙 | 文 【原创】
若是本篇博客有任何错误,请批评指教,不胜感激 !
文章每周持续更新,能够微信搜索「 三太子敖丙 」第一时间阅读和催更(公众号比博客早一到两篇哟),本文 GitHub github.com/JavaFamily 已经收录,有一线大厂面试点思惟导图,也整理了不少个人文档,欢迎Star和完善,你们面试能够参照考点复习,但愿咱们一块儿有点东西。