#序言


自从开始接触 Node 起,就离不开查阅大大小小的书籍、课程等等,大部分书籍和网络教程其实多多少少都是讲到一些框架或者生态里的各类工具库的使用(生态是 Node 引以为‘豪’的一点),偏向于速成,当然,我认为 Node 无论是在学习成本还是开发效率上都是数一数二的。

这些途径的最终目的都是好的,也是一个帮助初学者快速入门的办法: 学会一门语言(Javascript、Typescript…),通过框架的使用(Express、Koa…),快速上手搭建一个能够正常运行的Web应用,整个过程并不是很难,也容易让人产生满足感(至少不会像 Java 那一套得折腾老半天)。

阅读全文 »

背景

公司部分后端的 Node.Js 服务在2018年年初接入了SOA基础平台(服务拆分、基础服务下沉),由于对接得非常匆忙,因此上游服务的代码大部分仅仅考虑了如何接入封装好的 SDK,并未及时考虑在具体场景下接入是否会存在较明显的风险甚至影响服务的稳定。

作为底层中台服务,一个非常重要的目的是将不同业务线对同一个模型的「写」操作权限收拢至一个独立的服务,提供相关的功能:

  • DBAccess, DB统一接入层,业务服务不直连db,控制连接数
  • 对同一个模「型\数据表」进行不同应用的写入、更新
  • 权限分应用验证
  • 以及敏感数据、字段 脱敏

在实际的业务场景中,Teambition 有一个常用的功能就是「批量归档任务」,随着使用 Teambition 的任务功能时间越来越长,一个列表下的任务可能有成千上万条,这时候我们可以将这些已经过去很久的任务归档掉。

插曲

需要注意的一个点是,在归档的过程中,不仅仅是对 DBCRUD 操作,伴随更新的过程中我们可能需要针对每一条数据(任务)触发一系列行为,这些行为可能有:

  • 发送 websocket
  • 为其他几张表写入相关业务数据
  • 发送 webhook
  • 发送通知、推送
  • 进入数据分析系统

这个过程中每一个任务的处理都有各自需要执行的业务逻辑。

在「归档」这个场景,我们之前是采用「游标」的方式来对某一集合的数据进行更新及连带更新,Teambition 的任务数据模型是一个树状结构,抽象出来就是一颗树,在业务上有个基本逻辑是: 当父节点被删除(归档),子孙节点也需要执行相同操作

在具体的业务场景中,我们在处理一颗树的根节点的时候,需要把它的子孙节点一并处理掉。有一个需要特别解释的点,在query的场景,如果我们对一个树的遍历是递归遍历的话,那么我们通常只需要把根节点处理掉即可,子孙节点其实也是跟着父节点(根节点)的行为属性保持一致的,根\父节点不满足筛选条件即跳出递归。

如果仅仅考虑这个场景的话,已经满足了我们普通的业务,在处理一个任务(节点)的时候,节点本身做了标记,那么下次查询的时候忽略有相关标记的节点即可。

但是,我们需要考虑的不仅仅是满足业务需求,也需要保证通过每一种模型实现背后的代价: 性能、通用性、拓展性、耦合度等等;

比如,我们对任务的应用场景是「读多写少」,那么如果我们在数据库中查询任务,还需要采用递归的方式来查询那么毫无疑问效率是非常低的,因此在设计此类数据模型的时候,针对这类的业务场景,我们参考的是 MongoDB 推荐的数据结构 – Model Tree Structures with an Array of Ancestors

1
2
3
4
5
6
db.categories.insert( { _id: "MongoDB", ancestors: [ "Books", "Programming", "Databases" ] } )
db.categories.insert( { _id: "dbm", ancestors: [ "Books", "Programming", "Databases" ] } )
db.categories.insert( { _id: "Databases", ancestors: [ "Books", "Programming" ] } )
db.categories.insert( { _id: "Languages", ancestors: [ "Books", "Programming" ] } )
db.categories.insert( { _id: "Programming", ancestors: [ "Books" ] } )
db.categories.insert( { _id: "Books", ancestors: [ ]} )

对于每一个节点,我们都用额外的字段记录了他的祖先节点,因此当我们需要查询某个节点极其子节点的时候,只需要将它本身及 ancestors 字段中包含父节点的数据找出来即可,极大程度上满足了我们对「读」这一场景的性能要求。

回到本文遇到的问题中来,大部分业务所遇到的性能问题通常在于数据库,在上游应用能够独立操作数据库的时候,只需要按照一定的方式处理数据调优,便可以在一定程度上保证服务执行的效率以及稳定。
然而,在对接底层服务的过程中,由于不能自由的操作数据库(自由的),因此我们遭遇了 OOM 黑洞: 生产环境 k8s 集群里的某个或者某几个实例(pod)会因为触发类似的操作行为导致 OOMkill 掉。

具体的业务场景是: 将从基础服务获取到的所有的数据一并放到内存中进行计算,计算过程中由于方法的不当,申请了大量的内存来进行 tmp 数据的存储,在数据量很大的情况下,处于新生代的变量频繁被使用,老生代的变量由于一直被引用导致无法被及时GC,再加之处理的数据量过多导致 CPU 跑满,GC 执行缓慢,最终导致 OOM,如图:

目的

Nodejs作为单进程应用,一旦被 kill 势必会将整个 API 请求的链路打断,同时也会影响正在执行的异步操作,导致了脏数据的产生及影响了系统的稳定性,令人遗憾的是,在 OOM 的过程前后,我们只能靠监控系统及时发现应用 Crash,再根据相关的时间日志去推测导致 OOM 的原因,我们并被有把应用被”杀死“之前的案发现场给 dump 下来,因此,摆在面前的解决思路有两条:

  1. 从代码的角度来讲,改变处理策略
  • 数据分片并行处理,减少内存的使用,优化算法;
  • 将相关的行为放入消息队列,由多个应用从消息队列中获取并消费,减轻单个实例的负载;
  1. 维护的角度来看
  • 服务出现此类异常一定要能够快速寻找案发源头;
  • 找到源头并快速进行分析,查明原因,尽力保证避免类似的错误再次发生;

代码的处理上,在进行一定的优化之后已经解决了这类问题,在这里我只简单的讲一下第二点我的实践思路,并未应用到生产环境中去。

方案

Node.Js 官方有一个启动命令叫做 --abort-on-uncaught-exception, 应用异常崩溃或者终止的时候会生成用于调试的 core.pid 文件,我们可以理解为应用异常退出前的快照。

这是最关键的一个环节,有了这个文件之后,我们只需要对这个文件进行分析就好,只要案卷在手,找到分析方式只是时间问题(之前遇到的问题就是连案卷也没有)

有了案卷,如何对案卷进行分析也是至关重要的,以下是一个简单的Demo。

1. 注入能够导致「爆栈」的代码

在实际的工程项目中,我为某个 API 注入了如下代码:

1
2
3
4
5
6
7
function func () {
let arr = []
for (let i = 0; i < 1e10; i++) {
arr.push(i)
}
}
func()

在浏览器或者 Node 环境中执行以上代码,通常会导致「爆栈」,也就是堆内存溢出(JavaScript heap out of memory)

2. 启动我们的实例,访问 API

毫无疑问,应用 Crash 并抛出如下错误:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<--- Last few GCs --->

[36270:0x102802400] 14867 ms: Mark-sweep 696.0 (746.8) -> 692.9 (746.2) MB, 132.5 / 0.0 ms allocation failure GC in old space requested
[36270:0x102802400] 15010 ms: Mark-sweep 692.9 (746.2) -> 692.5 (706.2) MB, 142.3 / 0.0 ms last resort GC in old space requested
[36270:0x102802400] 15172 ms: Mark-sweep 692.5 (706.2) -> 692.5 (706.2) MB, 162.1 / 0.0 ms last resort GC in old space requested


<--- JS stacktrace --->

==== JS stack trace =========================================

Security context: 0x27be5dd25879 <JSObject>
1: func [/Users/jiangwei/Desktop/Teambition/Code/core/lib/*****.js:~29] [pc=0xffc20deeda](this=0x27be5ed04f01 <JSGlobal Object>)
2: getTasksByTQL [/Users/jiangwei/Desktop/Teambition/Code/core/lib/*****.js:35] [bytecode=0x27be9a61ba51 offset=369](this=0x27be5ed04f01 <JSGlobal Object>,req=0x27beb7082311 <the_hole>)
3: /* anonymous */(th...

FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory
1: node::Abort() [/Users/jiangwei/.nvm/versions/node/v8.12.0/bin/node]
2: node::FatalException(v8::Isolate*, v8::Local<v8::Value>, v8::Local<v8::Message>) [/Users/jiangwei/.nvm/versions/node/v8.12.0/bin/node]
3: v8::internal::V8::FatalProcessOutOfMemory(char const*, bool) [/Users/jiangwei/.nvm/versions/node/v8.12.0/bin/node]
4: v8::internal::Factory::NewUninitializedFixedArray(int) [/Users/jiangwei/.nvm/versions/node/v8.12.0/bin/node]
5: v8::internal::(anonymous namespace)::ElementsAccessorBase<v8::internal::(anonymous namespace)::FastPackedSmiElementsAccessor, v8::internal::(anonymous namespace)::ElementsKindTraits<(v8::internal::ElementsKind)0> >::GrowCapacity(v8::internal::Handle<v8::internal::JSObject>, unsigned int) [/Users/jiangwei/.nvm/versions/node/v8.12.0/bin/node]
6: v8::internal::Runtime_GrowArrayElements(int, v8::internal::Object**, v8::internal::Isolate*) [/Users/jiangwei/.nvm/versions/node/v8.12.0/bin/node]
7: 0xffc1c042fd

其中有几个关键信息:

  • 错误的类型: JavaScript heap out of memory
  • 错误的链路、发生的位置: 具体到 xx 文件,xx 行
  • 错误的原因是神马: 申请不了额外的空间存放老生代的变量
  • 当前应用运行的环境、版本号等等

这个时候我们能够快速的定位到问题并分析、处理,但是如果在真实地生产环境中,应用要是崩溃退出了,我们是不能等的,需要瞬间立马重启并提供服务,因此这个场景在实际的生产环境中是很难直接捕获到。

3. 仔细思考一下,通常我们遇到的问题极大可能性别人也遇到过并提供了解决思路

出现了前面的问题,难道就没救了吗?日志记录不了,无法稳定复现,无法 DEBUG,到底应该怎么办?

其实在日常开发的过程中,当我们遇上了疑难杂症、自己不清楚的问题而怀疑工具、语言甚至人生的时候,我们应该换个角度先想想这个问题出现的原因以及场景: 错误的原因是因为 OOM,那么当我们的业务、应用大到一定的程度时候,大大小小都应该遇到或发生过 OOM 或者类似导致应用 Crash 的问题,编程这么严谨的事情,容不得半点马虎和侥幸。

因此 Google 一下或者请教一下有经验的朋友,问题通常是有解的。

这一节一开始我就已经提到了,官方已经为我们提供了专门的命令 --abort-on-uncaught-exception,当程序出现的时候把类似的错误 dump 为文件存储下来以供我们分析;这里我就安利一下 llnode:一个用于分析 Nodelldb 插件。

4. 分析的过程

Node 会为我们生成一个 core.pid 的文件,因为是基于 V8,V8 又是用 C++ 写的,因此免不了得像调试 C++ 代码一样使用 gdblldb 等工具进行分析,当然如果使用这些工具的话我们应该是看见不了我们实际的JS代码相关的问题,因此基于 lldb 的插件 llnode 能够在标准的 C/C++ 调试工具中检查 JavaScript 堆栈帧、对象、源代码等,这样作为大部分 js 的同学应该都能理解起来没问题了。

安装的过程我就不说了,README 讲得还是很详细的,着重描述一下过程。

我的本机系统是 MacOS,因此生成的文件是保存在 /cores 目录中的,步骤 2 中我运行的实例生成的文件叫做 core.36270 也就是 core.进程号 的命名。

运行:

1
2
// -c 是 --core 的缩写,后面跟 fileName
llnode node -c core.36270

我们会进入调试界面:

1
2
3
4
5
➜  /cores llnode node -c core.36270
(lldb) target create "node" --core "core.36270"
Core file '/cores/core.36270' (x86_64) was loaded.
(lldb) plugin load '/Users/jiangwei/.nvm/versions/node/v8.12.0/lib/node_modules/llnode/llnode.dylib'
(lldb) settings set prompt '(llnode) '

执行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
(llnode) v8
Node.js helpers

Syntax: v8

The following subcommands are supported:

bt -- Show a backtrace with node.js JavaScript functions and their args. An optional
argument is accepted; if that argument is a number, it specifies the number of
frames to display. Otherwise all frames will be dumped.
Syntax: v8 bt [number]
findjsinstances -- List every object with the specified type name.
Flags:
* -v, --verbose - display detailed `v8 inspect` output for each
object.
* -n <num> --output-limit <num> - limit the number of entries displayed to
`num` (use 0 to show all). To get next page repeat command or press
[ENTER].
Accepts the same options as `v8 inspect`
findjsobjects -- List all object types and instance counts grouped by type name and sorted by
instance count. Use -d or --detailed to get an output grouped by type name,
properties, and array length, as well as more information regarding each type.
findrefs -- Finds all the object properties which meet the search criteria.
The default is to list all the object properties that reference the specified
value.
Flags:
* -v, --value expr - all properties that refer to the specified JavaScript
object (default)
* -n, --name name - all properties with the specified name
* -s, --string string - all properties that refer to the specified JavaScript
string value
getactivehandles -- Print all pending handles in the queue. Equivalent to running
process._getActiveHandles() on the living process.
getactiverequests -- Print all pending requests in the queue. Equivalent to running
process._getActiveRequests() on the living process.
inspect -- Print detailed description and contents of the JavaScript value.
Possible flags (all optional):
* -F, --full-string - print whole string without adding ellipsis
* -m, --print-map - print object's map address
* -s, --print-source - print source code for function objects
* -l num, --length num - print maximum of `num` elements from
string/array
Syntax: v8 inspect [flags] expr
nodeinfo -- Print information about Node.js
print -- Print short description of the JavaScript value.
Syntax: v8 print expr
settings -- Interpreter settings
source -- Source code information

For more help on any particular subcommand, type 'help <command> <subcommand>'.

我们能够看到一大堆执行选项以及说明,在这个场景下,我们需要的是 bt: Show a backtrace with node.js JavaScript functions and their args

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
(llnode) v8 bt
* thread #1: tid = 0x0000, 0x00007fff69957b86 libsystem_kernel.dylib`__pthread_kill + 10, stop reason = signal SIGSTOP
* frame #0: 0x00007fff69957b86 libsystem_kernel.dylib`__pthread_kill + 10
frame #1: 0x00007fff69a0dc50 libsystem_pthread.dylib`pthread_kill + 285
frame #2: 0x00007fff698c11c9 libsystem_c.dylib`abort + 127
frame #3: 0x00000001000285eb node`node::Abort() + 34
frame #4: 0x00000001000287ba node`node::OnFatalError(char const*, char const*) + 74
frame #5: 0x000000010015bec3 node`v8::internal::V8::FatalProcessOutOfMemory(char const*, bool) + 707
frame #6: 0x00000001004a915c node`v8::internal::Factory::NewUninitializedFixedArray(int) + 284
frame #7: 0x0000000100466819 node`v8::internal::(anonymous namespace)::ElementsAccessorBase<v8::internal::(anonymous namespace)::FastPackedSmiElementsAccessor, v8::internal::(anonymous namespace)::ElementsKindTraits<(v8::internal::ElementsKind)0> >::GrowCapacity(v8::internal::Handle<v8::internal::JSObject>, unsigned int) + 185
frame #8: 0x0000000100721f1d node`v8::internal::Runtime_GrowArrayElements(int, v8::internal::Object**, v8::internal::Isolate*) + 365
frame #9: 0x000000ffc1c042fd <exit>
frame #10: 0x000000ffc20deeda func(this=0x27be5ed04f01:<Global proxy>) at /Users/jiangwei/Desktop/Teambition/Code/core/**.js:29:17 fn=0x000027be27b39f49
frame #11: 0x000000ffc1cbd1d6 getTasksByTQL(this=0x27be5ed04f01:<Global proxy>, 0x27beb7082311:<hole>) at /Users/jiangwei/Desktop/Teambition/Code/core/**.js:14:23 fn=0x000027be8ede32b1
frame #12: 0x000000ffc1cb8056 (anonymous)(this=0x27be5ed04f01:<Global proxy>, 0x27be27b4c029:<Object: Object>) at (no script) fn=0x000027be27b4c3b1
frame #13: 0x000000ffc1c89cfc <builtin>
frame #14: 0x000000ffc1c04239 <internal>
frame #15: 0x000000ffc1c04101 <entry>
frame #16: 0x000000010049dc03 node`v8::internal::(anonymous namespace)::Invoke(v8::internal::Isolate*, bool, v8::internal::Handle<v8::internal::Object>, v8::internal::Handle<v8::internal::Object>, int, v8::internal::Handle<v8::internal::Object>*, v8::internal::Handle<v8::internal::Object>, v8::internal::Execution::MessageHandling) + 675
frame #17: 0x000000010049de1e node`v8::internal::Execution::TryCall(v8::internal::Isolate*, v8::internal::Handle<v8::internal::Object>, v8::internal::Handle<v8::internal::Object>, int, v8::internal::Handle<v8::internal::Object>*, v8::internal::Execution::MessageHandling, v8::internal::MaybeHandle<v8::internal::Object>*) + 222
frame #18: 0x00000001005cf4fb node`v8::internal::Isolate::PromiseReactionJob(v8::internal::Handle<v8::internal::PromiseReactionJobInfo>, v8::internal::MaybeHandle<v8::internal::Object>*, v8::internal::MaybeHandle<v8::internal::Object>*) + 651
frame #19: 0x00000001005d0099 node`v8::internal::Isolate::RunMicrotasksInternal() + 1353
frame #20: 0x00000001005ceeaa node`v8::internal::Isolate::RunMicrotasks() + 42
frame #21: 0x000000ffc1d94b87 <exit>
frame #22: 0x000000ffc21e80fb _tickCallback(this=0x27be5ed028d1:<Object: process>) at (external).js:152:25 fn=0x000027be5ed05411
frame #23: 0x000000ffc1c04239 <internal>
frame #24: 0x000000ffc1c04101 <entry>
frame #25: 0x000000010049dc03 node`v8::internal::(anonymous namespace)::Invoke(v8::internal::Isolate*, bool, v8::internal::Handle<v8::internal::Object>, v8::internal::Handle<v8::internal::Object>, int, v8::internal::Handle<v8::internal::Object>*, v8::internal::Handle<v8::internal::Object>, v8::internal::Execution::MessageHandling) + 675
frame #26: 0x000000010049d8ce node`v8::internal::Execution::Call(v8::internal::Isolate*, v8::internal::Handle<v8::internal::Object>, v8::internal::Handle<v8::internal::Object>, int, v8::internal::Handle<v8::internal::Object>*) + 158
frame #27: 0x0000000100178fbd node`v8::Function::Call(v8::Local<v8::Context>, v8::Local<v8::Value>, int, v8::Local<v8::Value>*) + 381
frame #28: 0x0000000100027adc node`node::InternalCallbackScope::Close() + 524
frame #29: 0x0000000100027c36 node`node::InternalMakeCallback(node::Environment*, v8::Local<v8::Object>, v8::Local<v8::Function>, int, v8::Local<v8::Value>*, node::async_context) + 120
frame #30: 0x0000000100027de1 node`node::MakeCallback(v8::Isolate*, v8::Local<v8::Object>, v8::Local<v8::Function>, int, v8::Local<v8::Value>*, node::async_context) + 108
frame #31: 0x000000010001be8c node`node::Environment::CheckImmediate(uv_check_s*) + 104
frame #32: 0x00000001008e842f node`uv__run_check + 167
frame #33: 0x00000001008e34ab node`uv_run + 329
frame #34: 0x000000010003037f node`node::Start(v8::Isolate*, node::IsolateData*, int, char const* const*, int, char const* const*) + 805
frame #35: 0x000000010002f8d8 node`node::Start(uv_loop_s*, int, char const* const*, int, char const* const*) + 461
frame #36: 0x000000010002f036 node`node::Start(int, char**) + 522
frame #37: 0x0000000100001534 node`start + 52

可以很清楚的看见,整个应用的生命周期完完全全展现在我们面前,倒叙来看,底层 node 的启动、uv_loop …直至应用奔溃之前的生命周期被完整的记录下来,报错的关键信息和我们debug时候看见的一样,精确到特定的文件、行数及函数名称。

还记得我之前提到的解决的方法以及目的吗,现在我们达到了目的: 准确定位并分析问题。

总结

至此,我们应该已经掌握了使用 --abort-on-uncaught-exception 命令帮助我们生成 core.pid 文件,并使用 llnode 加以分析的方法,另辟蹊径解决了我们无法从代码层面直接捕获的 OOM 问题。

文尾呼应一下我方案中提到的一句话

找到了方案,却未应用至生产环境中去。

这个方法乍一看很好、很不错啊,为什么不直接上生产环境,问题就出现在 dump 文件这个过程,如果你仔细的运行完了上述流程,你可能会发现,dump 文件还是需要花”一定“的时间,其实这个时间是和服务器具体的运行情况,比如系统、负载压力等因素相关的,如果花的时间足够长,那么我们的应用服务中断的时间也会顺延,如果集群中突发一定规模的问题势必会对应用的重启造成时序上的影响,同时 dump 文件对 CPU 造成的负载有多大暂时也没有数据上的报告,还需要运维同事协助分析并找到满足的条件,因此在没有足够的调研和准备的情况下暂时还不可以发到生产环境中尝试。

ps.分享的过程也是一个抛砖引玉的过程,本事不嫌多,如果你有更好或者落地的方案也请指教🤝。

一周过去了,从大学到工作一直有个习惯就是把每周琐碎、临时记下来的东西总结回顾一下,这两个月真的好忙好忙断了好久,好多不清楚的东西还是不清楚,重新踏上我的旅程吧~

JS

一、JS 如何实现数组或对象的深拷贝

无意间在 SF 上看见一个同学问 JS 如何实现数组或对象的深拷贝,往下划了几个答案都挺常规,用 lodash 哇,JSON.parse()&JSON.stringify()[].concat()递归 等方式,不过有一条对数组的实现让我耳目一新(是我知识太浅薄了~): 用 ES6 的结构语法实现深拷贝。

1
2
3
4
5
const a = [1,2,3,4]
const [...b] = a
a[1] = 1

a === b?

简单粗暴哈~,举一反三,脑袋里面在回想一下 引用传递, 值传递 的那些坑-。-

二、JS 数组实现浅谈

看见一个淘汰算法 hashlru, 也是同事使用我看见的,和同事聊了一下,也看了一下实现原理,对 JS 的对象、数组实现有了一些新的认识,或者说是我以前了解得太少了,没事不晚赶紧记下~

1
2
3
4
5
6
7
8
9
10
11
12
13
14
源码注释:
// The JSArray describes JavaScript Arrays
// Such an array can be in one of two modes:
// - fast, backing storage is a FixedArray and length <= elements.length();
// Please note: push and pop can be used to grow and shrink the array.
// - slow, backing storage is a HashTable with numbers as keys.
class JSArray: public JSObject {
public:
// [length]: The length property.
DECL_ACCESSORS(length, Object)

// Number of element slots to pre-allocate for an empty array.
static const int kPreallocatedArrayElements = 4;
};

JS 的数组实现有两种模式,一种是 “快” 模式,一种是 “慢” 模式,怎么理解呢,又去查了一下文章,其实“慢”模式比较好理解,就是通过哈希表的方式来进行存储查找,但是“快”模式中提到的 FixedArray 让我不太好从字面上来进行理解,只清楚和数组的长度可能有些联系,于是又去查了一下 FixedArray 的相关实现和解释:

其实翻译过来应该叫做 固定数组大小 的数组,也就是在编译的时候就已经知道具体大小的数组,其实就是声明一个数组的时候指明需要使用的长度,其实这么想想我也就明白了,原理还是一样的,其实我们都知道数组在内存里是连续的,如果我们预先指定了数组的长度,那么在查找或者更新某个元素的时候时间复杂度就是 O(1),按索引进行排列,通过索引就可以找到了,如果我们预先不清楚数组的长度,那么当我们对数组进行插入或者删除操作的时候,超过了容量阈值,可能会进行“搬移操作”,以便内存地址能够连续并且足够“大”,但这样的话整个操作就会变慢。

所以当我们进行如下操作的时候,数组其实是一个“快”模式:

1
2
3
const arr = new Array(3)
arr[0] = 1
// push、pos 也会在“快”模式的基础上进行长度的增加或减少

进行如下操作的时候,就是“慢”模式:

1
2
3
4
5
const arr2 = new Array(2)

arr2[500] = 1
// 这个时候如果我们还是按索引顺序排列的话,将会是 0, 1, 2, ... 500 = 1 浪费了不少的空间并且还需要扩容,是没有必要的,
// 因此如果我们将只记住 0, 1, 500 的话,就能有效地节省开销了

Node.JS

探讨一下 Node 的 GC 方面的知识吧,也是看了 (Node 案发现场揭秘 —— 如何利用 GC 日志不修改代码调优应用性能)[https://zhuanlan.zhihu.com/p/47425089] 这篇文章有所体会,还有张大神的视频分享: https://www.youtube.com/watch?v=DSBLAG2IvsY,背景不说了,上结论:

  • 我们可以通过设置一系列 flag 来实时获取到不同级别的 V8 GC 日志信息:

    • --trace_gc: 一行日志简要描述每次 GC 时的时间、类型、堆大小变化和产生原因
    • --trace_gc_verbose: 结合 –trace_gc 一起开启的话会展示每次 GC 后每个 V8 堆空间的详细状况
    • --trace_gc_nvp: 每一次 GC 的一些详细键值对信息,包含 GC 类型,暂停时间,内存变化等信息
  • 老生代内存空间应该设置为 --max-old-space-size=768

  • GC 优化的点集中在 scavenge 回收阶段,即新生代的内存回收。这个阶段触发回收的条件是:semi space allocation failed, --max-semi-space-size=64,这样只要没有内存泄露,Node.js的服务是可以正常运行。

K8s

网易云分享的 k8s service 一些基本背景知识,对 k8s 的负载均衡分发策略等等做了一些简单的介绍,多多了解也还是不错的~

很少总结 Node.Js 相关的调试以及优化技巧,正好最近在做性能调优并取得了不错的效果,借此机会总结和分享一下

Teambition 是一款实时协同的跨平台应用,相关的业务接口也是处于读多写少的场景,因此主要的性能瓶颈也是出现在接口平均响应速度慢、不稳定等方面,此次我将结合 Teambition 实际业务从以下几个维度来回顾总结优化的整个过程:

  • 我们的接口是如何变慢的? 慢在哪里?
  • 接口性能和并发操作的连带关系?
  • 通过 Chrome:instpact 进行代码 Debug 及性能分析?
  • Node.Js 事件循环模型及单线程为基础带来的优势与不足,如何避免及解决?
阅读全文 »

阅读本篇文章以前,你需要具备基本的服务器运维知识,包括不限于: 基本 Shell 命令,服务器选择及配置,Nginx 基本操作…

序言

都已经 8102 年了,为什么要使用 HTTPS 我就不记录了,相关资料网上一搜一大堆,总之,用 HTTPS 准没错对吧?

为什么要写一篇文章专门讨论启用 HTTPS 这个问题呢,原因有几点:

  • Chrome 69 以后,默认 HTTPS 的网站才不会出现安全风险提示,否则在地址栏永远会有一个感叹号,心里难受
  • 了解一下准没错吧?
  • 了解了,实践一下更没错吧?
阅读全文 »

title

用了很久的 zsh,一直感叹它的强大与便捷,很早就打算记录一篇安装和使用 zsh 的心路历程,工作忙一直在往后拖(借口-。-)

知乎传送门: 为什么说 zshshell 中的「极品」?

# zsh 是什么?

Z Shell(Zsh) 是一种Unix shell,它可以用作为交互式的登录shell,也是一种强大的shell脚本命令解释器。Zsh可以认为是一种Bourne shell的扩展,带有数量庞大的改进,包括一些bash、ksh、tcsh的功能。

简而言之,就是 shell 脚本语言的一种扩展与加强。

# zsh 有哪些功能?

From Wiki

  • 开箱即用、可编程的命令行补全功能可以帮助用户输入各种参数以及选项。
  • 在用户启动的所有shell中共享命令历史。
  • 通过扩展的文件通配符,可以不利用外部命令达到find命令一般展开文件名。
  • 改进的变量与数组处理。
  • 在缓冲区中编辑多行命令。
  • 多种兼容模式,例如使用/bin/sh运行时可以伪装成Bourne shell。
  • 可以定制呈现形式的提示符;包括在屏幕右端显示信息,并在键入长命令时自动隐藏。
  • 可加载的模块,提供其他各种支持:完整的TCP与Unix域套接字控制,FTP客户端与扩充过的数学函数。
  • 完全可定制化。
阅读全文 »

插曲

今天下午同事突然抛出一个问题,shell 中我想 grep 这么一段话:

1
scenarioFieldConfig's _id

有哪几种方式可以实现呢?

阅读全文 »

背景

IPFS (InterPlanetary File System) 中文名是 「星际文件系统」,由 Juan Benet 在2014年5月份发起,Protocol Labs 实验室维护和发展。

IPFS本质上是一种内容可寻址、版本化、点对点超媒体的分布式存储、传输协议,目标是补充甚至取代过去20年里使用的超文本媒体传输协议(HTTP),希望构建更快、更安全、更自由的互联网时代。

阅读全文 »