乐趣区

关于java:ElasticSearch还能性能调优涨见识涨见识了

ElasticSearch 性能调优

  • 作者: 博学谷狂野架构师
  • GitHub 地址:GitHub 地址(有咱们精心筹备的 130 本电子书 PDF)

概述

性能优化是个涉及面十分广的问题,不同的环境,不同的业务场景可能会存在不同的优化计划,本文只对一些相干的知识点做简略的总结,具体计划能够依据场景自行尝试。

配置文件调优

通过 elasticsearch.yml 配置文件调优

内存锁定

容许 JVM 锁住内存,禁止操作系统替换进来

因为 JVM 产生 swap 替换会导致极大升高 ES 的性能,为了避免 ES 产生内存替换,咱们能够通过锁定内存来实现,这将极大进步查问性能,但同时可能造成 OOM,须要对应做好资源监控,必要的时候进行干涉。

批改 ES 配置

批改 ES 的配置文件 elasticsearch.yml,设置 bootstrap.memory_lock 为 true

COPY# 集群名称
cluster.name: elastic
#以后该节点的名称
node.name: node-3
#是不是有资格竞选主节点
node.master: true
#是否存储数据
node.data: true
#最大集群节点数
node.max_local_storage_nodes: 3
#给以后节点自定义属性(能够省略)#node.attr.rack: r1
#数据存档地位
path.data: /usr/share/elasticsearch/data
#日志寄存地位
path.logs: /usr/share/elasticsearch/log
#是否开启时锁定内存(默认为是)#bootstrap.memory_lock: true
#设置网关地址,我是被这个坑死了,这个地址我原先填写了本人的理论物理 IP 地址,#而后启动始终报有效的 IP 地址,无奈注入 9300 端口,这里只须要填写 0.0.0.0
network.host: 0.0.0.0
#设置映射端口
http.port: 9200
#外部节点之间沟通端口
transport.tcp.port: 9300
#集群发现默认值为 127.0.0.1:9300, 如果要在其余主机上造成蕴含节点的群集, 如果搭建集群则须要填写
#es7.x 之后新增的配置,写入候选主节点的设施地址,在开启服务后能够被选为主节点,也就是说把所有的节点都写上
discovery.seed_hosts: ["node-1","node-2","node-3"]
#当你在搭建集群的时候,选出合格的节点集群,有些人说的太官网了,#其实就是,让你抉择比拟好的几个节点,在你节点启动时,在这些节点当选一个做领导者,#如果你不设置呢,elasticsearch 就会本人选举,这里咱们把三个节点都写上
cluster.initial_master_nodes: ["node-1","node-2","node-3"]
#在群集齐全重新启动后阻止初始复原,直到启动 N 个节点
#简略点说在集群启动后,至多复活多少个节点以上,那么这个服务才能够被应用,否则不能够被应用,gateway.recover_after_nodes: 2
#删除索引是是否须要显示其名称,默认为显示
#action.destructive_requires_name: true
# 容许内存锁定,进步 ES 性能
bootstrap.memory_lock: true
批改 JVM 配置

批改 jvm.options,通常设置 -Xms 和 -Xmx 的的值为“物理内存大小的一半和 32G 的较小值”

这是因为,es 内核应用 lucene,lucene 自身是独自占用内存的,并且占用的还不少,官网倡议设置 es 内存,大小为物理内存的一半,剩下的一半留给 lucene

COPY-Xms2g
-Xmx2g
敞开操作系统的 swap
长期敞开
COPYsudo swapoff -a 
永恒敞开

正文掉或删除所有 swap 相干的内容

COPYvi /etc/fstab
批改文件描述符

批改 /etc/security/limits.conf, 设置 memlock 为 unlimited

COPYelk hard memlock unlimited
elk soft memlock unlimited
批改系统配置
设置虚拟内存

批改 /etc/systemd/system.conf,设置vm.max_map_count 为一个较大的值

COPYvm.max_map_count=10240000
批改文件下限

批改/etc/systemd/system.conf,设置 DefaultLimitNOFILE,DefaultLimitNPROC,DefaultLimitMEMLOCK 为一个较大值,或者不限定

COPYDefaultLimitNOFILE=100000
DefaultLimitNPROC=100000
DefaultLimitMEMLOCK=infinity
重启 ES

服务发现优化

Elasticsearch 默认被配置为应用单播发现,以避免节点无心中退出集群

组播发现应该永远不被应用在生产环境了,否则你失去的后果就是一个节点意外的退出到了你的生产环境,仅仅是因为他们收到了一个谬误的组播信号,ES 是一个 P2P 类型的分布式系统,应用 gossip 协定,集群的任意申请都能够发送到集群的任一节点,而后 es 外部会找到须要转发的节点,并且与之进行通信,在 es1.x 的版本,es 默认是开启组播,启动 es 之后,能够疾速将局域网内集群名称,默认端口的雷同实例退出到一个大的集群,后续再 es2.x 之后,都调整成了单播,防止平安问题和网络风暴;

单播discovery.zen.ping.unicast.hosts,倡议写入集群内所有的节点及端口,如果新实例退出集群,新实例只须要写入以后集群的实例,即可主动退出到以后集群,之后再解决原实例的配置即可,新实例退出集群,不须要重启原有实例;

节点 zen 相干配置:discovery.zen.ping_timeout:判断 master 选举过程中,发现其余 node 存活的超时设置,次要影响选举的耗时,参数仅在退出或者选举 master 主节点的时候才起作用discovery.zen.join_timeout:节点确定退出到集群中,向主节点发送退出申请的超时工夫,默认为 3 sdiscovery.zen.minimum_master_nodes:参加 master 选举的最小节点数,当集群可能被选为 master 的节点数量小于最小数量时,集群将无奈失常选举。

故障检测(fault detection)

故障检测状况

以下两种状况下回进行故障检测

COPY* 第一种是由 master 向集群的所有其余节点发动 ping,验证节点是否处于活动状态
* 第二种是:集群每个节点向 master 发动 ping,判断 master 是否存活,是否须要发动选举
配置形式

故障检测须要配置以下设置应用

  • discovery.zen.fd.ping_interval:节点被 ping 的频率,默认为 1s。
  • discovery.zen.fd.ping_timeout 期待 ping 响应的工夫,默认为 30s,运行的集群中,master 检测所有节点,以及节点检测 master 是否失常。
  • discovery.zen.fd.ping_retries ping 失败 / 超时多少导致节点被视为失败,默认为 3。

队列数量优化

不倡议自觉加大 es 的队列数量,要依据理论状况来进行调整

如果是偶发的因为数据突增,导致队列阻塞,加大队列 size 能够应用内存来缓存数据,如果是持续性的数据阻塞在队列,加大队列 size 除了加大内存占用,并不能无效进步数据写入速率,反而可能加大 es 宕机时候,在内存中可能失落的上数据量。

查看线程池状况

通过以下能够查看线程池的状况,哪些状况下,加大队列 size 呢?

COPYGET /_cat/thread_pool

察看 api 中返回的 queue 和 rejected,如果的确存在队列回绝或者是继续的 queue,能够酌情调整队列 size。

内存应用

配置熔断限额

设置 indices 的内存熔断相干参数,依据理论状况进行调整,避免写入或查问压力过高导致 OOM

  • indices.breaker.total.limit: 50%,集群级别的断路器,默认为 jvm 堆的 70%
  • indices.breaker.request.limit: 10%,单个 request 的断路器限度,默认为 jvm 堆的 60%
  • indices.breaker.fielddata.limit: 10%,fielddata breaker 限度,默认为 jvm 堆的 60%。
配置缓存

依据理论状况调整查问占用 cache,防止查问 cache 占用过多的 jvm 内存,参数为动态的,须要在每个数据节点配置

  • indices.queries.cache.size: 5%,管制过滤器缓存的内存大小,默认为 10%,承受百分比值,5% 或者准确值,例如 512mb。

创立分片优化

如果集群规模较大,能够阻止新建 shard 时扫描集群内全副 shard 的元数据,晋升 shard 调配速度

  • cluster.routing.allocation.disk.include_relocations: false,默认为 true

零碎层面调优

jdk 版本

选用以后版本 ES 举荐应用的 ES,或者应用 ES 自带的 JDK

jdk 内存配置

首先,-Xms 和 -Xmx 设置为雷同的值,防止在运行过程中再进行内存调配,同时,如果零碎内存小于 64G,倡议设置略小于机器内存的一半,残余留给零碎应用,同时,jvm heap 倡议不要超过 32G(不同 jdk 版本具体的值会略有不同),否则 jvm 会因为内存指针压缩导致内存节约

敞开替换分区

敞开替换分区,避免内存产生替换导致性能降落(局部状况下,宁死勿慢)swapoff -a

文件句柄

Lucene 应用了 大量的 文件,同时,Elasticsearch 在节点和 HTTP 客户端之间进行通信也应用了大量的套接字,所有这所有都须要足够的文件描述符,默认状况下,linux 默认运行单个过程关上 1024 个文件句柄,这显然是不够的,故须要加大文件句柄数 ulimit -n 65536

mmap

Elasticsearch 对各种文件混合应用了 NioFs(注:非阻塞文件系统)和 MMapFs(注:内存映射文件系统)。

请确保你配置的最大映射数量,以便有足够的虚拟内存可用于 mmapped 文件。这能够临时设置:sysctl -w vm.max_map_count=262144 或者你能够在 /etc/sysctl.conf 通过批改 vm.max_map_count 永恒设置它。

磁盘

如果你正在应用 SSDs,确保你的零碎 I/O 调度程序是配置正确的

当你向硬盘写数据,I/O 调度程序决定何时把数据理论发送到硬盘,大多数默认 linux 发行版下的调度程序都叫做 cfq(齐全偏心队列),但它是为旋转介质优化的:机械硬盘的固有个性意味着它写入数据到基于物理布局的硬盘会更高效。

这对 SSD 来说是低效的,只管这里没有波及到机械硬盘,然而,deadline 或者 noop 应该被应用,deadline 调度程序基于写入等待时间进行优化,noop 只是一个简略的 FIFO 队列。

COPYecho noop > /sys/block/sd/queue/scheduler

磁盘挂载

COPYmount -o noatime,data=writeback,barrier=0,nobh /dev/sd* /esdata*

其中,noatime,禁止记录拜访工夫戳;data=writeback,不记录 journal;barrier=0,因为敞开了 journal,所以同步敞开 barrier;nobh,敞开 buffer_head,避免内核影响数据 IO

磁盘其余注意事项

应用 RAID 0,条带化 RAID 会进步磁盘 I /O,代价显然就是当一块硬盘故障时整个就故障了,不要应用镜像或者奇偶校验 RAID 因为正本曾经提供了这个性能。

另外,应用多块硬盘,并容许 Elasticsearch 通过多个 path.data 目录配置把数据条带化调配到它们下面,不要应用近程挂载的存储,比方 NFS 或者 SMB/CIFS。这个引入的提早对性能来说齐全是南辕北辙的。

应用形式调优

当 elasticsearch 自身的配置没有显著的问题之后,发现 es 应用还是十分慢,这个时候,就须要咱们去定位 es 自身的问题了,首先祭出定位问题的第一个命令:

Index(写)调优

正本数置 0

如果是集群首次灌入数据, 能够将正本数设置为 0,写入结束再调整回去,这样正本分片只须要拷贝,节俭了索引过程

COPYPUT /my_temp_index/_settings
{"number_of_replicas": 0}
主动生成 doc ID

通过 Elasticsearch 写入流程能够看出,如果写入 doc 时如果内部指定了 id,则 Elasticsearch 会先尝试读取原来 doc 的版本号,以判断是否须要更新,这会波及一次读取磁盘的操作,通过主动生成 doc ID 能够防止这个环节

正当设置 mappings

将不须要建设索引的字段 index 属性设置为 not_analyzed 或 no。

  • 对字段不分词,或者不索引,能够缩小很多运算操作,升高 CPU 占用,尤其是 binary 类型,默认状况下占用 CPU 十分高,而这种类型进行分词通常没有什么意义。
  • 缩小字段内容长度,如果原始数据的大段内容毋庸全副建设 索引,则能够尽量减少不必要的内容。
  • 应用不同的分析器(analyzer),不同的分析器在索引过程中 运算复杂度也有较大的差别。
调整_source 字段
_source` 字段用于存储 doc 原始数据,对于局部不须要存储的字段,能够通过 includes excludes 过滤,或者将 `_source` 禁用,个别用于索引和数据拆散,这样能够升高 I/O 的压力,不过理论场景中大多不会禁用 `_source
对 analyzed 的字段禁用 norms

Norms 用于在搜寻时计算 doc 的评分,如果不须要评分,则能够将其禁用

COPYtitle": {"type":"string","norms": {"enabled": false}
调整索引的刷新距离

该参数缺省是 1s,强制 ES 每秒创立一个新 segment,从而保障新写入的数据近实时的可见、可被搜寻到,比方该参数被调整为 30s,升高了刷新的次数,把刷新操作耗费的系统资源释放出来给 index 操作应用

COPYPUT /my_index/_settings
{
 "index" : {"refresh_interval": "30s"}
}

这种计划以就义可见性的形式,进步了 index 操作的性能。

批处理

批处理把多个 index 操作申请合并到一个 batch 中去解决,和 mysql 的 jdbc 的 bacth 有类似之处

比方每批 1000 个 documents 是一个性能比拟好的 size,每批中多少 document 条数适合,受很多因素影响而不同,如单个 document 的大小等,ES 官网倡议通过在单个 node、单个 shard 做性能基准测试来确定这个参数的最优值

Document 的路由解决

当对一批中的 documents 进行 index 操作时,该批 index 操作所需的线程的个数由要写入的目标 shard 的个数决定

有 2 批 documents 写入 ES, 每批都须要写入 4 个 shard,所以总共须要 8 个线程,如果能缩小 shard 的个数,那么消耗的线程个数也会缩小,例如下图,两批中每批的 shard 个数都只有 2 个,总共线程耗费个数 4 个,缩小一半。

默认的 routing 就是 id,也能够在发送申请的时候,手动指定一个 routing value,比如说 put/index/doc/id?routing=user_id

值得注意的是线程数尽管升高了,然而单批的解决耗时可能减少了。和进步刷新距离办法相似,这有可能会缩短数据不见的工夫

Search(读)调优

在存储的 Document 条数超过 10 亿条后,咱们如何进行搜寻调优

数据分组

很多人拿 ES 用来存储日志,日志的索引治理形式个别基于日期的,基于天、周、月、年建索引,如下图,基于天建索引

当搜寻单天的数据,只须要查问一个索引的 shards 就能够,当须要查问多天的数据时,须要查问多个索引的 shards,这种计划其实和数据库的分表、分库、分区查问计划相比,思路相似,小数据范畴查问而不是海底捞针。

开始的计划是建一个 index,当数据量增大的时候,就扩容减少 index 的 shard 的个数,当 shards 增大时,要搜寻的 shards 个数也随之显著回升,基于数据分组的思路,能够基于 client 进行数据分组,每一个 client 只需依赖本人的 index 的数据 shards 进行搜寻,而不是所有的数据 shards,大大提高了搜寻的性能,如下图:

应用 Filter 代替 Query

在搜寻时候应用 Query,须要为 Document 的相关度打分,应用 Filter,没有打分环节解决,做的事件更少,而且 filter 实践上更快一些。

如果搜寻不须要打分,能够间接应用 filter 查问,如果局部搜寻须要打分,倡议应用’bool’查问,这种形式能够把打分的查问和不打分的查问组合在一起应用,如

COPYGET /_search
{
"query": {
"bool": {
"must": {
"term": {"user": "kimchy"}
},
"filter": {
"term": {"tag": "tech"}
}
}
}
}
ID 字段定义为 keyword

个别状况,如果 ID 字段不会被用作 Range 类型搜寻字段,都能够定义成 keyword 类型,这是因为 keyword 会被优化,以便进行 terms 查问,Integers 等数字类的 mapping 类型,会被优化来进行 range 类型搜寻,将 integers 改成 keyword 类型之后,搜寻性能大概能晋升 30%

hot_threads

能够应用以下命令,抓取 30s 区间内的节点上占用资源的热线程,并通过排查占用资源最多的 TOP 线程来判断对应的资源耗费是否失常

COPYGET /_nodes/hot_threads&interval=30s

个别状况下,bulk,search 类的线程占用资源都可能是业务造成的,然而如果是 merge 线程占用了大量的资源,就应该思考是不是创立 index 或者刷磁盘距离太小,批量写入 size 太小造成的。

pending_tasks

有一些工作只能由主节点去解决,比方创立一个新的索引或者在集群中挪动分片,因为一个集群中只能有一个主节点,所以只有这一 master 节点能够解决集群级别的元数据变动

在 99.9999% 的工夫里,这不会有什么问题,元数据变动的队列基本上放弃为零,在一些常见的集群里,元数据变动的次数比主节点能解决的还快,这会导致期待中的操作会累积成队列,这个时候能够通过 pending_tasks api 剖析以后什么操作阻塞了 es 的队列,比方,集群异样时,会有大量的 shard 在 recovery,如果集群在大量创立新字段,会呈现大量的 put_mappings 的操作,所以失常状况下,须要禁用动静 mapping。

COPYGET /_cluster/pending_tasks

字段存储

以后 es 次要有 doc_values,fielddata,storefield 三种类型,大部分状况下,并不需要三种类型都存储,可依据理论场景进行调整:

以后用得最多的就是 doc_values,列存储,对于不须要进行分词的字段,都能够开启 doc_values 来进行存储(且只保留 keyword 字段),节约内存,当然,开启 doc_values 会对查问性能有肯定的影响,然而,这个性能损耗是比拟小的,而且是值得的;

fielddata 构建和治理 100% 在内存中,常驻于 JVM 内存堆,所以可用于疾速查问,然而这也意味着它实质上是不可扩大的,有很多边缘状况下要提防,如果对于字段没有剖析需要,能够敞开 fielddata;

storefield 次要用于 _source 字段,默认状况下,数据在写入 es 的时候,es 会将 doc 数据存储为 _source 字段,查问时能够通过 _source 字段疾速获取 doc 的原始构造,如果没有 update,reindex 等需要,能够将_source 字段 disable;

_all,ES 在 6.x 以前的版本,默认将写入的字段拼接成一个大的字符串,并对该字段进行分词,用于反对整个 doc 的全文检索,在晓得 doc 字段名称的状况下,倡议敞开掉该字段,节约存储空间,也防止不带字段 key 的全文检索;

norms:搜寻时进行评分,日志场景个别不须要评分,倡议敞开;

事务日志

Elasticsearch 2.0 之后为了保障不丢数据,每次 index、bulk、delete、update 实现的时候,肯定会触发同步刷新 translog 到磁盘上,才给申请返回 200 OK

异步刷新

采纳异步刷新,这个扭转在进步数据安全性的同时当然也升高了一点性能,如果你不在意这点可能性,还是心愿性能优先,能够在 index template 里设置如下参数

COPY{"index.translog.durability": "async"}
其余参数
index.translog.sync_interval

对于一些大容量的偶然失落几秒数据问题也并不重大的集群,应用异步的 fsync 还是比拟无益的,比方,写入的数据被缓存到内存中,再每 5 秒执行一次 fsync,默认为 5s,小于的值 100ms 是不容许的。

index.translog.flush_threshold_size

translog 存储尚未平安保留在 Lucene 中的所有操作,尽管这些操作可用于读取,但如果要敞开并且必须复原,则须要从新编制索引,此设置管制这些操作的最大总大小,以避免复原工夫过长,达到设置的最大 size 后,将产生刷新,生成新的 Lucene 提交点,默认为 512mb。

refresh_interval

执行刷新操作的频率,这会使索引的最近更改对搜寻可见,默认为 1s,能够设置 - 1 为禁用刷新,对于写入速率要求较高的场景,能够适当的加大对应的时长,减小磁盘 io 和 segment 的生成;

禁止动静 mapping

动静 mapping 的毛病
  1. 造成集群元数据始终变更,导致 不稳固;
  2. 可能造成数据类型与理论类型不统一;
  3. 对于一些异样字段或者是扫描类的字段,也会频繁的批改 mapping,导致业务不可控。
映射配置

动静 mapping 配置的可选值及含意如下

  • true:反对动静扩大,新增数据有新的字段属性时,主动增加对于的 mapping,数据写入胜利
  • false:不反对动静扩大,新增数据有新的字段属性时,间接疏忽,数据写入胜利
  • strict:不反对动静扩大,新增数据有新的字段时,报错,数据写入失败

批量写入

批量申请显然会大大晋升写入速率,且这个速率是能够量化的,官网倡议每次批量的数据物理字节数 5 -15MB 是一个比拟不错的终点,留神这里说的是物理字节数大小。

文档计数对批量大小来说不是一个好指标,比如说,如果你每次批量索引 1000 个文档,记住上面的事实:1000 个 1 KB 大小的文档加起来是 1 MB 大,1000 个 100 KB 大小的文档加起来是 100 MB 大。

这可是完完全全不一样的批量大小了,批量申请须要在协调节点上加载进内存,所以批量申请的物理大小比文档计数重要得多,从 5–15 MB 开始测试批量申请大小,迟缓减少这个数字,直到你看不到性能晋升为止。

而后开始减少你的批量写入的并发度(多线程等等方法),用 iostat、top 和 ps 等工具监控你的节点,察看资源什么时候达到瓶颈。如果你开始收到 EsRejectedExecutionException,你的集群没方法再持续了:至多有一种资源到瓶颈了,或者缩小并发数,或者提供更多的受限资源(比方从机械磁盘换成 SSD),或者增加更多节点。

索引和 shard

es 的索引,shard 都会有对应的元数据,

因为 es 的元数据都是保留在 master 节点,且元数据的更新是要 hold 住集群向所有节点同步的,当 es 的新建字段或者新建索引的时候,都会要获取集群元数据,并对元数据进行变更及同步,此时会影响集群的响应,所以须要关注集群的 index 和 shard 数量,

应用倡议

倡议如下

  1. 应用 shrink 和 rollover api,绝对生成适合的数据 shard 数;
  2. 依据数据量级及对应的性能需求,抉择创立 index 的名称,形如:按月生成索引:test-YYYYMM,按天生成索引:test-YYYYMMDD;
  3. 管制单个 shard 的 size,失常状况下,日志场景,倡议单个 shard 不大于 50GB,线上业务场景,倡议单个 shard 不超过 20GB;

段合并

段合并的计算量宏大,而且还要吃掉大量磁盘 I/O

合并在后盾定期操作,因为他们可能要很长时间能力实现,尤其是比拟大的段,这个通常来说都没问题,因为大规模段合并的概率是很小的。

如果发现 merge 占用了大量的资源,能够设置:index.merge.scheduler.max_thread_count: 1 特地是机械磁盘在并发 I/O 反对方面比拟差,所以咱们须要升高每个索引并发拜访磁盘的线程数,这个设置容许 max_thread_count + 2 个线程同时进行磁盘操作,也就是设置为 1 容许三个线程,对于 SSD,你能够疏忽这个设置,默认是 Math.min(3, Runtime.getRuntime().availableProcessors() / 2),对 SSD 来说运行的很好。

业务低峰期通过 force_merge 强制合并 segment,升高 segment 的数量,减小内存耗费;敞开冷索引,业务须要的时候再进行开启,如果始终不应用的索引,能够定期删除,或者备份到 hadoop 集群;

主动生成_id

当写入端应用特定的 id 将数据写入 es 时,es 会去查看对应的 index 下是否存在雷同的 id,这个操作会随着文档数量的减少而耗费越来越大,所以如果业务上没有强需要,倡议应用 es 主动生成的 id,放慢写入速率。

routing

对于数据量较大的业务查问场景,es 侧个别会创立多个 shard,并将 shard 调配到集群中的多个实例来摊派压力,失常状况下,一个查问会遍历查问所有的 shard,而后将查问到的后果进行 merge 之后,再返回给查问端。

此时,写入的时候设置 routing,能够防止每次查问都遍历全量 shard,而是查问的时候也指定对应的 routingkey,这种状况下,es 会只去查问对应的 shard,能够大幅度降低合并数据和调度全量 shard 的开销。

应用 alias

生产提供服务的索引,切记应用别名提供服务,而不是间接裸露索引名称,防止后续因为业务变更或者索引数据须要 reindex 等状况造成业务中断。

防止宽表

在索引中定义太多字段是一种可能导致映射爆炸的状况,这可能导致内存不足谬误和难以复原的状况,这个问题可能比预期更常见,index.mapping.total_fields.limit,默认值是 1000

防止稠密索引

因为索引稠密之后,对应的相邻文档 id 的 delta 值会很大,lucene 基于文档 id 做 delta 编码压缩导致压缩率升高,从而导致索引文件增大,同时,es 的 keyword,数组类型采纳 doc_values 构造,每个文档都会占用肯定的空间,即便字段是空值,所以稠密索引会造成磁盘 size 增大,导致查问和写入效率升高。

最初说一句(求关注,别白嫖我)

如果这篇文章对您有所帮忙,或者有所启发的话,求一键三连:点赞、转发、在看。您的反对是我保持写作最大的能源。

  • 作者: 博学谷狂野架构师
  • GitHub 地址:GitHub 地址(有咱们精心筹备的 130 本电子书 PDF)

本文由 传智教育博学谷狂野架构师 教研团队公布。

如果本文对您有帮忙,欢送 关注 点赞 ;如果您有任何倡议也可 留言评论 私信,您的反对是我保持创作的能源。

转载请注明出处!

退出移动版