低代码到底是不是行业毒瘤?一线大厂怎么做的?戳此了解>>> 了解详情
写点什么

贷前系统 ElasticSearch 实践总结

2020 年 2 月 13 日

贷前系统ElasticSearch实践总结

贷前系统负责从进件到放款前所有业务流程的实现,其中涉及一些数据量较大、条件多样且复杂的综合查询,引入 ElasticSearch 主要是为了提高查询效率;并希望基于 ElasticSearch 快速实现一个简易的数据仓库,提供一些 OLAP 相关功能。有一些心得总结,向大家介绍一下。


一 索引

描述:为快速定位数据而设计的某种数据结构。


索引好比是一本书前面的目录,能加快数据库的查询速度。了解索引的构造及使用,对理解 ES 的工作模式有非常大的帮助。


常用索引介绍

  1. 位图索引

  2. 哈希索引

  3. BTREE 索引

  4. 倒排索引


1.1. 位图索引(BitMap)

位图索引适用于字段值为可枚举的有限个数值的情况


位图索引使用二进制的数字串(bitMap)标识数据是否存在,1 标识当前位置(序号)存在数据,0 则表示当前位置没有数据。


下图 1 为用户表,存储了性别和婚姻状况两个字段;


图 2 中 分别为性别和婚姻状态建立了两个位图索引。


例如:性别->男 对应索引为:101110011,表示第 1、3、4、5、8、9 个用户为男性。其他属性以此类推。



使用位图索引查询:


• 男性 并且已婚 的记录 = 101110011 & 11010010 = 100100010,即第 1、4、8 个用户为已婚男性。


• 女性 或者未婚的记录 = 010001100 | 001010100 = 011011100, 即第 2、3、5、6、7 个用户为女性或者未婚。


1.2. 哈希索引


顾名思义,是指使用某种哈希函数实现 key->value 映射的索引结构。


哈希索引适用于等值检索,通过一次哈希计算即可定位数据的位置。


下图 3 展示了哈希索引的结构,与 JAVA 中 HashMap 的实现类似,是用冲突表的方式解决哈希冲突的。



图三


1.3. BTREE 索引


BTREE 索引是关系型数据库最常用的索引结构,方便了数据的查询操作。


BTREE: 有序平衡 N 阶树, 每个节点有 N 个键值和 N+1 个指针, 指向 N+1 个子节点。


一棵 BTREE 的简单结构如下图 4 所示,为一棵 2 层的 3 叉树,有 7 条数据:



图四


以 Mysql 最常用的 InnoDB 引擎为例,描述下 BTREE 索引的应用。


Innodb 下的表都是以索引组织表形式存储的,也就是整个数据表的存储都是 B+tree 结构的,如图 5 所示。



图 5


主键索引为图 5 的左半部分(如果没有显式定义自主主键,就用不为空的唯一索引来做聚簇索引,如果也没有唯一索引,则 innodb 内部会自动生成 6 字节的隐藏主键来做聚簇索引),叶子节点存储了完整的数据行信息(以主键 + row_data 形式存储)。


二级索引也是以 B+tree 的形式进行存储,图 5 右半部分,与主键不同的是二级索引的叶子节点存储的不是行数据,而是索引键值和对应的主键值,由此可以推断出,二级索引查询多了一步查找数据主键的过程。


维护一颗有序平衡 N 叉树,比较复杂的就是当插入节点时节点位置的调整,尤其是插入的节点是随机无序的情况;而插入有序的节点,节点的调整只发生了整个树的局部,影响范围较小,效率较高。


可以参考红黑树的节点的插入算法:


https://en.wikipedia.org/wiki/Red–black_tree


因此如果 innodb 表有自增主键,则数据写入是有序写入的,效率会很高;如果 innodb 表没有自增的主键,插入随机的主键值,将导致 B+tree 的大量的变动操作,效率较低。这也是为什么会建议 innodb 表要有无业务意义的自增主键,可以大大提高数据插入效率。


注:


• Mysql Innodb 使用自增主键的插入效率高。


• 使用类似 Snowflake 的 ID 生成算法,生成的 ID 是趋势递增的,插入效率也比较高。


1.4. 倒排索引(反向索引)


倒排索引也叫反向索引,可以相对于正向索引进行比较理解。


正向索引反映了一篇文档与文档中关键词之间的对应关系;给定文档标识,可以获取当前文档的关键词、词频以及该词在文档中出现的位置信息,如图 6 所示,左侧是文档,右侧是索引。



图 6


反向索引则是指某关键词和该词所在的文档之间的对应关系;给定了关键词标识,可以获取关键词所在的所有文档列表,同时包含词频、位置等信息,如图 7 所示。



图 7


反向索引(倒排索引)的单词的集合和文档的集合就组成了如图 8 所示的”单词-文档矩阵“,打钩的单元格表示存在该单词和文档的映射关系。



图 8


倒排索引的存储结构可以参考图 9。其中词典是存放的内存里的,词典就是整个文档集合中解析出的所有单词的列表集合;每个单词又指向了其对应的倒排列表,倒排列表的集合组成了倒排文件,倒排文件存放在磁盘上,其中的倒排列表内记录了对应单词在文档中信息,即前面提到的词频、位置等信息。



图 9 倒排索引结构


下面以一个具体的例子来描述下,如何从一个文档集合中生成倒排索引。


如图 10,共存在 5 个文档,第一列为文档编号,第二列为文档的文本内容。



将上述文档集合进行分词解析,其中发现的 10 个单词为:[谷歌,地图,之父,跳槽,Facebook,加盟,创始人,拉斯,离开,与],以第一个单词”谷歌“为例:首先为其赋予一个唯一标识 ”单词 ID“, 值为 1,统计出文档频率为 5,即 5 个文档都有出现,除了在第 3 个文档中出现 2 次外,其余文档都出现一次,于是就有了图 11 所示的倒排索引。



1.4.1. 单词词典查询优化


对于一个规模很大的文档集合来说,可能包含几十万甚至上百万的不同单词,能否快速定位某个单词,这直接影响搜索时的响应速度,其中的优化方案就是为单词词典建立索引,有以下几种方案可供参考:


 词典 Hash 索引


Hash 索引简单直接,查询某个单词,通过计算哈希函数,如果哈希表命中则表示存在该数据,否则直接返回空就可以;适合于完全匹配,等值查询。如图 12,相同 hash 值的单词会放在一个冲突表中。



词典 BTREE 索引


类似于 Innodb 的二级索引,将单词按照一定的规则排序,生成一个 BTree 索引,数据节点为指向倒排索引的指针。



 二分查找


同样将单词按照一定的规则排序,建立一个有序单词数组,在查找时使用二分查找法;二分查找法可以映射为一个有序平衡二叉树,如图 14 这样的结构。



 FST(Finite State Transducers )实现


FST 为一种有限状态转移机,FST 有两个优点:1)空间占用小。通过对词典中单词前缀和后缀的重复利用,压缩了存储空间;2)查询速度快。O(len(str))的查询时间复杂度。


以插入“cat”、 “deep”、 “do”、 “dog” 、“dogs”这 5 个单词为例构建 FST(注:必须已排序)。



15


如图 15 最终我们得到了如上一个有向无环图。利用该结构可以很方便的进行查询,如给定一个词 “dog”,我们可以通过上述结构很方便的查询存不存在,甚至我们在构建过程中可以将单词与某一数字、单词进行关联,从而实现 key-value 的映射。


当然还有其他的优化方式,如使用 Skip List、Trie、Double Array Trie 等结构进行优化,不再一一赘述。


二 ElasticSearch 使用心得


下面结合贷前系统具体的使用案例,介绍 ES 的一些心得总结。


  • 目前使用的 ES 版本:5.6

  • 官网地址:https://www.elastic.co/products/elasticsearch

  • ES 一句话介绍:The Heart of the Elastic Stack(摘自官网)

  • ES 的一些关键信息:

  •  2010 年 2 月首次发布

  •  Elasticsearch Store, Search, and Analyze

  •  丰富的 Restful 接口


基本概念


• 索引(index)


ES 的索引,也就是 Index,和前面提到的索引并不是一个概念,这里是指所有文档的集合,可以类比为 RDB 中的一个数据库。


• 文档(document)


即写入 ES 的一条记录,一般是 JSON 形式的。


• 映射(Mapping)


文档数据结构的元数据描述,一般是 JSON schema 形式,可动态生成或提前预定义。


类型(type)


由于理解和使用上的错误,type 已不推荐使用,目前我们使用的 ES 中一个索引只建立了一个默认 type。


• 节点


一个 ES 的服务实例,称为一个服务节点。为了实现数据的安全可靠,并且提高数据的查询性能,ES 一般采用集群模式进行部署。


• 集群


多个 ES 节点相互通信,共同分担数据的存储及查询,这样就构成了一个集群。


• 分片


分片主要是为解决大量数据的存储,将数据分割为若干部分,分片一般是均匀分布在各 ES 节点上的。需要注意:分片数量无法修改。


• 副本


分片数据的一份完全的复制,一般一个分片会有一个副本,副本可以提供数据查询,集群环境下可以提高查询性能。


安装部署


• JDK 版本: JDK1.8


• 安装过程比较简单,可参考官网:下载安装包 -> 解压 -> 运行


• 安装过程遇到的坑:


ES 启动占用的系统资源比较多,需要调整诸如文件句柄数、线程数、内存等系统参数,可参考下面的文档。


http://www.cnblogs.com/sloveling/p/elasticsearch.html


实例讲解


下面以一些具体的操作介绍 ES 的使用:


• 初始化索引


初始化索引,主要是在 ES 中新建一个索引并初始化一些参数,包括索引名、文档映射(Mapping)、索引别名、分片数(默认:5)、副本数(默认:1)等,其中分片数和副本数在数据量不大的情况下直接使用默认值即可,无需配置。


下面举两个初始化索引的方式,一个使用基于 Dynamic Template(动态模板) 的 Dynamic Mapping(动态映射),一个使用显式预定义映射。


1. 动态模板 (Dynamic Template)


col 1col 2
1<p style=``"line-height: 2em;"``><span style=``"font-size: 14px;"``>curl -X PUT http:``//ip:9200/loan_idx -H 'content-type: application/json' <br> -d '{"mappings":{ "order_info":{ "dynamic_date_formats":["yyyy-MM-dd HH:mm:ss||yyyy-MM-dd],<br> "dynamic_templates":[<br> {"orderId2":{<br> "match_mapping_type":"string",<br> "match_pattern":"regex",<br> "match":"^orderId$",<br> "mapping":{<br> "type":"long"<br> }<br> }<br> },<br> {"strings_as_keywords":{<br> "match_mapping_type":"string",<br> "mapping":{<br> "type":"keyword",<br> "norms":false<br> }<br> }<br> }<br> ]<br> }<br>},<br>"aliases":{<br> "loan_alias":{}<br>}}'<br></span></p>


上面的 JSON 串就是我们用到的动态模板,其中定义了日期格式:dynamic_date_formats 字段;定义了规则 orderId2:凡是遇到 orderId 这个字段,则将其转换为 long 型;定义了规则 strings_as_keywords:凡是遇到 string 类型的字段都映射为 keyword 类型,norms 属性为 false;关于 keyword 类型和 norms 关键字,将在下面的数据类型小节介绍。


2. 预定义映射


预定义映射和上面的区别就是预先把所有已知的字段类型描述写到 mapping 里,下图截取了一部分作为示例:



16


图 16 中 JSON 结构的上半部分与动态模板相同,红框中内容内容为预先定义的属性:apply.applyInfo.appSubmissionTime, apply.applyInfo.applyId, apply.applyInfo.applyInputSource 等字段,type 表明了该字段的类型,映射定义完成后,再插入的数据必须符合字段定义,否则 ES 将返回异常。


• 常用数据类型:


常用的数据类型有 text, keyword, date, long, double, boolean, ip


实际使用中,将字符串类型定义为 keyword 而不是 text,主要原因是 text 类型的数据会被当做文本进行语法分析,做一些分词、过滤等操作,而 keyword 类型则是当做一个完整数据存储起来,省去了多余的操作,提高索引性能。


配合 keyword 使用的还有一个关键词 norm,置为 false 表示当前字段不参与评分;所谓评分是指根据单词的 TF/IDF 或其他一些规则,对查询出的结果赋予一个分值,供展示搜索结果时进行排序, 而一般的业务场景并不需要这样的排序操作(都有明确的排序字段),从而进一步优化查询效率。


• 索引名无法修改


初始化一个索引,都要在 URL 中明确指定一个索引名,一旦指定则无法修改,所以一般建立索引都要指定一个默认的别名(alias):


col 1col 2
1<p style=``"line-height: 2em;"``><span style=``"font-size: 14px;"``>``"aliases"``:{ ``"loan_alias"``:{ }<br> }<br></span></p>


别名和索引名是多对多的关系,也就是一个索引可以有多个别名,一个别名也可以映射多个索引;在一对一这种模式下,所有用到索引名的地方都可以用别名进行替换;别名的好处就是可以随时的变动,非常灵活。


• Mapping 中已存在的字段无法更新


如果一个字段已经初始化完毕(动态映射通过插入数据,预定义通过设置字段类型),那就确定了该字段的类型,插入不兼容的数据则会报错,比如定义了一个 long 类型字段,如果写入一个非数字类型的数据,ES 则会返回数据类型错误的提示。


这种情况下可能就需要重建索引,上面讲到的别名就派上了用场;一般分 3 步完成:1)新建一个索引将格式错误的字段指定为正确格式,2)使用 ES 的 Reindex API 将数据从旧索引迁移到新索引,3)使用 Aliases API 将旧索引的别名添加到新索引上,删除旧索引和别名的关联。


上述步骤适合于离线迁移,如果要实现不停机实时迁移步骤会稍微复杂些。


• API


基本的操作就是增删改查,可以参考 ES 的官方文档:


https://www.elastic.co/guide/en/elasticsearch/reference/current/docs.html


一些比较复杂的操作需要用到 ES Script,一般使用类 Groovy 的 painless script,这种脚本支持一些常用的 JAVA API(ES 安装使用的是 JDK8,所以支持一些 JDK8 的 API),还支持 Joda time 等。


举个比较复杂的更新的例子,说明 painless script 如何使用:


需求描述:


appSubmissionTime 表示进件时间,lenssonStartDate 表示开课时间,expectLoanDate 表示放款时间。要求 2018 年 9 月 10 日的进件,如果进件时间 与 开课时间的日期差小于 2 天,则将放款时间设置为进件时间。


Painless Script 如下:


col 1col 2
1<p style=``"line-height: 2em;"``><span style=``"font-size: 14px;"``>POST loan_idx/_update_by_query<br> { ``"script"``:{ ``"source"``:``"long getDayDiff(def dateStr1, def dateStr2){ <br> LocalDateTime date1= toLocalDate(dateStr1); LocalDateTime date2= toLocalDate(dateStr2); ChronoUnit.DAYS.between(date1, date2);<br> }<br> LocalDateTime toLocalDate(def dateStr)<br> { <br> DateTimeFormatter formatter = DateTimeFormatter.ofPattern(\"yyyy-MM-dd HH:mm:ss\"); LocalDateTime.parse(dateStr, formatter);<br> }<br> if(getDayDiff(ctx._source.appSubmissionTime, ctx._source.lenssonStartDate) < 2)<br> { <br> ctx._source.expectLoanDate=ctx._source.appSubmissionTime<br> }"``, ``"lang"``:``"painless"``<br> }<br> , ``"query"``:<br> { ``"bool"``:{ ``"filter"``:[<br> { ``"bool"``:{ ``"must"``:[<br> { ``"range"``:{ <br> ``"appSubmissionTime"``:<br> {<br> ``"from"``:``"2018-09-10 00:00:00"``, ``"to"``:``"2018-09-10 23:59:59"``, ``"include_lower"``:``true``, ``"include_upper"``:``true``<br> }<br> }<br> }<br> ]<br> }<br> }<br> ]<br> }<br> }<br>}<br></span></p>


解释:整个文本分两部分,下半部分 query 关键字表示一个按范围时间查询(2018 年 9 月 10 号),上半部分 script 表示对匹配到的记录进行的操作,是一段类 Groovy 代码(有 Java 基础很容易读懂),格式化后如下, 其中定义了两个方法 getDayDiff()和 toLocalDate(),if 语句里包含了具体的操作:


col 1col 2
1<p style=``"line-height: 2em;"``><span style=``"font-size: 14px;"``>long getDayDiff(def dateStr1, def dateStr2){<br> LocalDateTime date1= toLocalDate(dateStr1);<br> LocalDateTime date2= toLocalDate(dateStr2);<br> ChronoUnit.DAYS.between(date1, date2);<br>}<br>LocalDateTime toLocalDate(def dateStr){<br> DateTimeFormatter formatter = DateTimeFormatter.ofPattern(``"yyyy-MM-dd HH:mm:ss"``);<br> LocalDateTime.parse(dateStr, formatter);<br>}``if``(getDayDiff(ctx._source.appSubmissionTime, ctx._source.lenssonStartDate) < ``2``){<br> ctx._source.expectLoanDate=ctx._source.appSubmissionTime<br>}<br></span></p>


然后提交该 POST 请求,完成数据修改。


• 查询数据


这里重点推荐一个 ES 的插件 ES-SQL:


https://github.com/NLPchina/elasticsearch-sql/wiki/Basic-Queries-And-Conditions


这个插件提供了比较丰富的 SQL 查询语法,让我们可以使用熟悉的 SQL 语句进行数据查询。其中,有几个需要注意的点:


• ES-SQL 使用 Http GET 方式发送情况,所以 SQL 的长度是受限制的(4kb),可以通过以下参数进行修改:


http.max_initial_line_length: “8k”


• 计算总和、平均值这些数字操作,如果字段被设置为非数值类型,直接使用 ESQL 会报错,可改用 painless 脚本。


• 使用 Select as 语法查询出的结果和一般的查询结果,数据的位置结构是不同的,需要单独处理。


• NRT(Near Real Time):准实时


向 ES 中插入一条记录,然后再查询出来,一般都能查出最新的记录,ES 给人的感觉就是一个实时的搜索引擎,这也是我们所期望的,然而实际情况却并非总是如此,这跟 ES 的写入机制有关,做个简单介绍:


– Lucene 索引段 -> ES 索引


写入 ES 的数据,首先是写入到 Lucene 索引段中的,然后才写入 ES 的索引中,在写入 ES 索引前查到的都是旧数据。


– commit:原子写操作


索引段中的数据会以原子写的方式写入到 ES 索引中,所以提交到 ES 的一条记录,能够保证完全写入成功,而不用担心只写入了一部分,而另一部分写入失败。


– refresh:刷新操作,可以保证最新的提交被搜索到


索引段提交后还有最后一个步骤:refresh,这步完成后才能保证新索引的数据能被搜索到。


出于性能考虑,Lucene 推迟了耗时的刷新,因此它不会在每次新增一个文档的时候刷新,默认每秒刷新一次。这种刷新已经非常频繁了,然而有很多应用却需要更快的刷新频率。如果碰到这种状况,要么使用其他技术,要么审视需求是否合理。


不过,ES 给我们提供了方便的实时查询接口,使用该接口查询出的数据总是最新的,调用方式描述如下:


GET http://IP:PORT/index_name/type_name/id


上述接口使用了 HTTP GET 方法,基于数据主键(id)进行查询,这种查询方式会同时查找 ES 索引和 Lucene 索引段中的数据,并进行合并,所以最终结果总是最新的。但有个副作用:每次执行完这个操作,ES 就会强制执行 refresh 操作,导致一次 IO,如果使用频繁,对 ES 性能也会有影响。


• 数组处理


数组的处理比较特殊,拿出来单独讲一下。


– 表示方式就是普通的 JSON 数组格式,如:


• [1, 2, 3]、 [“a”, “b”]、 [ { “first” : “John”, “last” : “Smith” },{“first” : “Alice”, “last” : “White”} ]


– 需要注意 ES 中并不存在数组类型,最终会被转换为 object,keyword 等类型。


– 普通数组对象查询的问题


普通数组对象的存储,会把数据打平后将字段单独存储,如:


col 1col 2
1<p style=``"line-height: 2em;"``><span style=``"font-size: 14px;"``>{ ``"user"``:[<br> { ``"first"``:``"John"``, ``"last"``:``"Smith"``<br> },<br> { ``"first"``:``"Alice"``, ``"last"``:``"White"``<br> }<br> ]<br>}<br></span></p>


会转化为下面的文本


col 1col 2
1<p style=``"line-height: 2em;"``><span style=``"font-size: 14px;"``>{ ``"user.first"``:[ ``"John"``, ``"Alice"``<br> ], ``"user.last"``:[ ``"Smith"``, ``"White"``<br> ]<br>}<br></span></p>


将原来文本之间的关联打破了,图 17 展示了这条数据从进入索引到查询出来的简略过程:


1.组装数据,一个 JSONArray 结构的文本。


2.写入 ES 后,默认类型置为 object。


3.查询 user.first 为 Alice 并且 user.last 为 Smith 的文档(实际并不存在同时满足这两个条件的)。


4.返回了和预期不符的结果。



17


– 嵌套(Nested)数组对象查询


嵌套数组对象可以解决上面查询不符的问题,ES 的解决方案就是为数组中的每个对象单独建立一个文档,独立于原始文档。如图 18 所示,将数据声明为 nested 后,再进行相同的查询,返回的是空,因为确实不存在 user.first 为 Alice 并且 user.last 为 Smith 的文档。



18


– 一般对数组的修改是全量的,如果需要单独修改某个字段,需要借助 painless script,参考:https://www.elastic.co/guide/en/elasticsearch/reference/5.6/docs-update.html


安全


数据安全是至关重要的环节,主要通过以下三点提供数据的访问安全控制:


• XPACK


XPACK 提供了 Security 插件,可以提供基于用户名密码的访问控制,可以提供一个月的免费试用期,过后收取一定的费用换取一个 license。


• IP 白名单


是指在 ES 服务器开启防火墙,配置只有内网中若干服务器可以直接连接本服务。


• 代理


一般不允许业务系统直连 ES 服务进行查询,需要对 ES 接口做一层包装,这个工作就需要代理去完成;并且代理服务器可以做一些安全认证工作,即使不适用 XPACK 也可以实现安全控制。


网络


• ElasticSearch 服务器默认需要开通 9200、9300 这两个端口。


下面主要介绍一个和网络相关的错误,如果大家遇到类似的错误,可以做个借鉴。


引出异常前,先介绍一个网络相关的关键词,keepalive :


Http keep-alive 和 Tcp keepalive。


HTTP1.1 中默认启用"Connection: Keep-Alive",表示这个 HTTP 连接可以复用,下次的 HTTP 请求就可以直接使用当前连接,从而提高性能,一般 HTTP 连接池实现都用到 keep-alive;


TCP 的 keepalive 的作用和 HTTP 中的不同,TPC 中主要用来实现连接保活,相关配置主要是 net.ipv4.tcp_keepalive_time 这个参数,表示如果经过多长时间(默认 2 小时)一个 TCP 连接没有交换数据,就发送一个心跳包,探测下当前链接是否有效,正常情况下会收到对方的 ack 包,表示这个连接可用。


下面介绍具体异常信息,描述如下:


两台业务服务器,用 restClient(基于 HTTPClient,实现了长连接)连接的 ES 集群(集群有三台机器),与 ES 服务器分别部署在不同的网段,有个异常会有规律的出现:


每天 9 点左右会发生异常 Connection reset by peer. 而且是连续有三个 Connection reset by peer


col 1col 2
1<p style=``"line-height: 2em;"``><span style=``"font-size: 14px;"``>Caused by: java.io.IOException: Connection reset by peer <br> at sun.nio.ch.FileDispatcherImpl.read0(Native Method) <br> at sun.nio.ch.SocketDispatcher.read(SocketDispatcher.java:``39``) <br> at sun.nio.ch.IOUtil.readIntoNativeBuffer(IOUtil.java:``223``) <br> at sun.nio.ch.IOUtil.read(IOUtil.java:``197``)<br></span></p>


为了解决这个问题,我们尝试了多种方案,查官方文档、比对代码、抓包。。。经过若干天的努力,最终发现这个异常是和上面提到 keepalive 关键词相关(多亏运维组的同事帮忙)。


实际线上环境,业务服务器和 ES 集群之间有一道防火墙,而防火墙策略定义空闲连接超时时间为例如为 1 小时,与上面提到的 linux 服务器默认的例如为 2 小时不一致。由于我们当前系统晚上访问量较少,导致某些连接超过 2 小时没有使用,在其中 1 小时后防火墙自动就终止了当前连接,到了 2 小时后服务器尝试发送心跳保活连接,直接被防火墙拦截,若干次尝试后服务端发送 RST 中断了链接,而此时的客户端并不知情;当第二天早上使用这个失效的链接请求时,服务端直接返回 RST,客户端报错 Connection reset by peer,尝试了集群中的三台服务器都返回同样错误,所以连续报了 3 个相同的异常。解决方案也比较简单,修改服务端 keepalive 超时配置,小于防火墙的 1 小时即可。


本文转载自宜信技术学院网站。


原文链接:http://college.creditease.cn/detail/177


2020 年 2 月 13 日 21:47204

评论

发布
暂无评论
发现更多内容

进入最佳汇报状态的反思

JiangX

28天写作

第十周学习总结

Binary

工具词典: MIT

lidaobing

MIT 28天写作 Slidepad Roam Research

与前端训练营的日子 --Week14

SamGo

学习笔记

【Animate.css】CSS动画库

学习委员

CSS css3 html/css 28天写作

极客大学产品经理训练营 解决方案的设计与积累 第6课总结 John 易筋 ARTS 打卡 Week 37

John(易筋)

ARTS 打卡计划 极客大学产品经理训练营 解决方案的设计与积累

人员培养,不是捷径的捷径(上)

一笑

管理 人才培养 28天写作

五种C语言非数值计算的常用经典排序算法

华为云开发者社区

算法 记录 C语言 排序 非数值计算

第10周作业&总结

胡益

PMI 项目管理认证体系

Ian哥

28天写作

java import 导入包时,我们需要注意什么呢?

看山

Java import

机器学习·笔记之:Gradient Descent For Linear Regression

Nydia

为什么太过努力有时候也会造成问题

熊斌

学习方法 个人成长 28天写作

Elasticsearch Document 增删改内部原理

escray

七日更 28天写作 死磕Elasticsearch 60天通过Elastic认证考试 2月春节不断更

作业二

KYoKO

第5周课后练习-技术选型一

潘涛

架构师训练营 4 期

算法训练营总结

Geek_ac4080

壁纸欣赏

小马哥

七日更

【初级】个人分享Vue前端开发教程笔记

魔王哪吒

程序员 面试 Vue 前端 2月春节不断更

终于用我的那个二手显卡搭建好了TF2的环境

IT蜗壳-Tango

七日更

写一个玄幻的序章——梦想种植「幻想短篇 24/28」

道伟

28天写作

管理笔记[3]:各得其所,各尽其才

俊毅

第5周课后总结-技术选型一

潘涛

架构师训练营 4 期

如何理解平行宇宙

陈东泽 EuryChen

科普 物理 平行宇宙 平行世界

python subprocess-更优雅的创建子进程

jeffery

Python

产品经理训练营-第三周学习总结

月亮 😝

一致性hash算法

MR.X

腾讯会议增长背后的技术实践

李忠良

28天写作

创业失败启示录|舌尖辨茶

青城

28天写作 创业失败启示录

创业公司如何做技术品牌? | 视频号28天(25)

赵新龙

28天写作

马克吐温关于拖延症的几个段子

Justin

心理学 工作效率 拖延症 28天写作

2021 ThoughtWorks 技术雷达峰会

2021 ThoughtWorks 技术雷达峰会

贷前系统ElasticSearch实践总结-InfoQ