js的Symbol
symbol 是一种基本数据类型(primitive data type)。Symbol()
函数会返回 symbol 类型的值,该类型具有静态属性和静态方法。它的静态属性会暴露几个内建的成员对象;它的静态方法会暴露全局的 symbol 注册,且类似于内建对象类,但作为构造函数来说它并不完整,因为它不支持语法:”new Symbol()
“。
每个从 Symbol()
返回的 symbol 值都是唯一的。一个 symbol 值能作为对象属性的标识符;这是该数据类型仅有的目的。更进一步的解析见—— glossary entry for Symbol。
1 | Symbol([description]) |
直接使用Symbol()
创建新的 symbol 类型,并用一个可选的字符串作为其描述。
description
:可选可选的,字符串类型。对 symbol 的描述,可用于调试但不是访问 symbol 本身。
1 | var sym1 = Symbol(); |
上面的代码创建了三个新的 symbol 类型。注意,Symbol("foo")
不会强制将字符串“foo”转换成 symbol 类型。它每次都会创建一个新的 symbol 类型:
1 | Symbol("foo") === Symbol("foo"); // false |
Symbol 值作为属性名时,该属性是公有属性不是私有属性,可以在类的外部访问。但是不会出现在 for...in
、 for...of
的循环中,也不会被 Object.keys()
、 Object.getOwnPropertyNames()
返回。如果要读取到一个对象的 Symbol
属性,可以通过 Object.getOwnPropertySymbols()
和 Reflect.ownKeys()
取到。
js中的同步和异步
1 | function a() { |
a()
和b()
是同步执行,a()
中fetch
请求是异步操作。
for遍历中的go func
循环变量只是普通变量,每次循环只是重新赋值。
以下两种写法是等价的:
1 | // 写法 1 |
循环变量 i 和 v 每次循环都会被重新赋值,并非生成新的变量。
这样就产生了一个问题:如果循环体中有并发操作(最常见的是for中起协程),并且并发中用到了 i 和 v,那么 i 和 v 很可能已经不是此次循环的值了,而是下次甚至是最后一次循环的值。
如果要保证协程中用到的循环变量一定就是此次循环的值,就需要 绑定循环变量,两种方式:
- 协程传参
- 在协程开始之前,新定义变量,将循环变量赋值给新变量,然后协程中使用新变量
ffmpeg
1 | ffmpeg \ |
要注意的是,所有的参数仅仅对仅接下来的文件有效,下一个文件得把参数再写一遍。
所有没有使用 -i
指定的文件都被认为是输出文件。 ffmpeg 可以接受多个输入文件并输出到您指定的位置。
你也可以将输入输出都指定为同一个文件名,不过这个时候要在输出文件前使用用 -y
标记。
你不应该将输入和输出混淆,先指定输入,再指定输出文件。
yarn基本使用
yarn v1
npm (v5) | yarn(1.x) | 说明 |
---|---|---|
npm install |
yarn add |
初始化项目 |
(N/A) | yarn add --flat |
|
(N/A) | yarn add --har |
|
npm install --no-package-lock |
yarn add --no-lockfile |
|
(N/A) | yarn add --pure-lockfile |
|
npm install [package] --save |
yarn add [package] |
|
npm install [package] --save-dev |
yarn add [package] --dev |
|
(N/A) | yarn add [package] --peer |
|
npm install [package] --save-optional |
yarn add [package] --optional |
|
npm install [package] --save-exact |
yarn add [package] --exact |
|
(N/A) | yarn add [package] --tilde |
|
npm install [package] --global |
yarn global add [package] |
|
npm update --global |
yarn global upgrade |
|
npm rebuild |
yarn add --force |
|
npm uninstall [package] |
yarn remove [package] |
|
npm cache clean |
yarn cache clean [package] |
|
rm -rf node_modules && npm install |
yarn upgrade |
|
npm version major |
yarn version --major |
|
npm version minor |
yarn version --minor |
|
npm version patch |
yarn version --patch |
yarn v2
https://next.yarnpkg.com/getting-started/install
全局安装最新版本的yarn v2
1
npm install -g yarn@berry
初始化react项目
1
yarn dlx create-react-app my-app --template typescript --use-pnp
1
初始化项目:
1 | yarn init -2 |
一键更新package.json中所有模块为最新版本
https://www.jianshu.com/p/ce9a46ae3a03
1 | yarn global add npm-check-updates |
go并发的GPM模型
马士兵老师对于GPM的讲解,可以说是很通俗易懂了:https://www.bilibili.com/video/BV1pA411i7Ro?p=2&t=805.3
go.mod
go.mod 示例:
1 | module api.local |
- module:用于定义当前项目的模块路径。
- go:用于设置预期的 Go 版本。
- require:用于设置一个特定的模块版本。
- exclude:用于从使用中排除一个特定的模块版本。
- replace:用于将一个模块版本替换为另外一个模块版本。
环境变量
GO111MODULE
Go modules 的开关,auto/on/off,推荐 on
1.17 中将完全放弃对 GOPATH 模式的支持,忽略 GO111MODULE 变量设置。
GOPROXY
设置 go 模块代理,用于使 Go 在后续拉取模块版本时能够脱离传统的 VCS 方式从镜像站点快速拉取。它拥有一个默认:https://proxy.golang.org,direct
,但很可惜proxy.golang.org
在中国无法访问,故而建议使用goproxy.cn
作为替代:https://goproxy.cn,direct
“direct” 为特殊指示符,用于指示 Go 回源到模块版本的源地址去抓取(比如 GitHub 等),当值列表中上一个 Go module proxy 返回 404 或 410 错误时,Go 自动尝试列表中的下一个,遇见 “direct” 时回源,遇见 EOF 时终止并抛出类似 “invalid version: unknown revision…” 的错误。
GOSUMDB
Go checksum database,用于使 Go 在拉取模块版本时(无论是从源站拉取还是通过 Go module proxy 拉取)保证拉取到的模块版本数据未经篡改,也可以是“off”即禁止 Go 在后续操作中校验模块版本
默认:sum.golang.org
,但是在国内无法访问,故而更加建议将 GOPROXY 设置为goproxy.cn
,因为goproxy.cn
支持代理sum.golang.org
GONOPROXY / GONOSUMDB / GOPRIVATE
这三个环境变量都是用在当前项目依赖了私有模块,也就是依赖了由 GOPROXY 指定的 Go module proxy 或由 GOSUMDB 指定 Go checksum database 无法访问到的模块时的场景。
其中 GOPRIVATE 较为特殊,它的值将作为 GONOPROXY 和 GONOSUMDB 的默认值,所以建议的最佳姿势是只是用 GOPRIVATE。
示例:
1 | GOPRIVATE=*.corp.example.com |
表示所有模块路径以corp.example.com
的下一级域名(如:team1.corp.example.com
)为前缀的模块版本都将不经过 Go module proxy 和 Go checksum database,需要注意的是不包括corp.example.com
本身。
Global Caching
这个主要是针对 Go modules 的全局缓存数据说明,如下:
- 同一个模块版本的数据只缓存一份,所有其他模块共享使用。
- 目前所有模块版本数据均缓存在
$GOPATH/pkg/mod
和$GOPATH/pkg/sum
下,未来或将移至$GOCACHE/mod
和$GOCACHE/sum
下 - 可以使用
go clean -modcache
清理所有已缓存的模块版本数据
indirect 表示什么意思
https://my.oschina.net/renhc/blog/3162751
在执行命令 go mod tidy
时,Go module 会自动整理 go.mod 文件
,如果有必要会在部分依赖包的后面增加 // indirect
注释。一般而言,被添加注释的包肯定是间接依赖的包,而没有添加 // indirect
注释的包则是直接依赖的包,即明确的出现在某个 import
语句中。
然而,这里需要着重强调的是:并不是所有的间接依赖都会出现在 go.mod
文件中。
间接依赖出现在 go.mod
文件的情况,可能符合下面所列场景的一种或多种:
- 直接依赖未启用 Go module
- 直接依赖 go.mod 文件中缺失部分依赖
直接依赖未启用 Go module
如下图所示,Module A 依赖 B,但是 B 还未切换成 Module,也即没有 go.mod
文件,此时,当使用 go mod tidy
命令更新 A 的 go.mod
文件时,B 的两个依赖 B1 和 B2 将会被添加到 A 的 go.mod
文件中(前提是 A 之前没有依赖 B1 和 B2),并且 B1 和 B2 还会被添加 // indirect
的注释。
此时 Module A 的 go.mod
文件中 require 部分将会变成:
1 | require ( |
依赖 B 及 B 的依赖 B1 和 B2 都会出现在 go.mod
文件中。
直接依赖 go.mod 文件不完整
如上面所述,如果依赖 B 没有 go.mod
文件,则 Module A 将会把 B 的所有依赖记录到 A 的 go.mod
文件中。即便 B 拥有 go.mod
,如果 go.mod
文件不完整的话,Module A 依然会记录部分 B 的依赖到 go.mod
文件中。
如下图所示,Module B 虽然提供了 go.mod
文件中,但 go.mod
文件中只添加了依赖 B1,那么此时 A 在引用 B 时,则会在 A 的 go.mod
文件中添加 B2 作为间接依赖,B1 则不会出现在 A 的 go.mod
文件中。
此时 Module A 的 go.mod
文件中 require 部分将会变成:
1 | require ( |
由于 B1 已经包含进 B 的 go.mod
文件中,A 的 go.mod
文件则不必再记录,只会记录缺失的 B2。
总结
为什么要记录间接依赖
在上面的例子中,如果某个依赖 B 没有 go.mod
文件,在 A 的 go.mod
文件中已经记录了依赖 B 及其版本号,为什么还要增加间接依赖呢?
我们知道 Go module 需要精确地记录软件的依赖情况,虽然此处记录了依赖 B 的版本号,但 B 的依赖情况没有记录下来,所以如果 B 的 go.mod
文件缺失了(或没有)这个信息,则需要在 A 的 go.mod
文件中记录下来。此时间接依赖的版本号将会跟据 Go module 的版本选择机制确定一个最优版本。
如何处理间接依赖
综上所述间接依赖出现在 go.mod
中,可以一定程度上说明依赖有瑕疵,要么是其不支持 Go module,要么是其 go.mod
文件不完整。
由于 Go 语言从 v1.11 版本才推出 module 的特性,众多开源软件迁移到 go module 还需要一段时间,在过渡期必然会出现间接依赖,但随着时间的推进,在 go.mod
中出现 // indirect
的机率会越来越低。
出现间接依赖可能意味着你在使用过时的软件,如果有精力的话还是推荐尽快消除间接依赖。可以通过使用依赖的新版本或者替换依赖的方式消除间接依赖。
如何查找间接依赖来源
Go module 提供了 go mod why
命令来解释为什么会依赖某个软件包,若要查看 go.mod
中某个间接依赖是被哪个依赖引入的,可以使用命令 go mod why -m <pkg>
来查看。
另外,命令 go mod why -m all
则可以分析所有依赖的依赖链。
如何锁定版本号
使用 replace
go module
go.mod 示例:
1 | module api.local |
- module:用于定义当前项目的模块路径。
- go:用于设置预期的 Go 版本。
- require:用于设置一个特定的模块版本。
- exclude:用于从使用中排除一个特定的模块版本。
- replace:用于将一个模块版本替换为另外一个模块版本。
indirect 表示什么意思
https://my.oschina.net/renhc/blog/3162751
在执行命令 go mod tidy
时,Go module 会自动整理 go.mod 文件
,如果有必要会在部分依赖包的后面增加 // indirect
注释。一般而言,被添加注释的包肯定是间接依赖的包,而没有添加 // indirect
注释的包则是直接依赖的包,即明确的出现在某个 import
语句中。
然而,这里需要着重强调的是:并不是所有的间接依赖都会出现在 go.mod
文件中。
间接依赖出现在 go.mod
文件的情况,可能符合下面所列场景的一种或多种:
- 直接依赖未启用 Go module
- 直接依赖 go.mod 文件中缺失部分依赖
直接依赖未启用 Go module
如下图所示,Module A 依赖 B,但是 B 还未切换成 Module,也即没有 go.mod
文件,此时,当使用 go mod tidy
命令更新 A 的 go.mod
文件时,B 的两个依赖 B1 和 B2 将会被添加到 A 的 go.mod
文件中(前提是 A 之前没有依赖 B1 和 B2),并且 B1 和 B2 还会被添加 // indirect
的注释。
此时 Module A 的 go.mod
文件中 require 部分将会变成:
1 | require ( |
依赖 B 及 B 的依赖 B1 和 B2 都会出现在 go.mod
文件中。
直接依赖 go.mod 文件不完整
如上面所述,如果依赖 B 没有 go.mod
文件,则 Module A 将会把 B 的所有依赖记录到 A 的 go.mod
文件中。即便 B 拥有 go.mod
,如果 go.mod
文件不完整的话,Module A 依然会记录部分 B 的依赖到 go.mod
文件中。
如下图所示,Module B 虽然提供了 go.mod
文件中,但 go.mod
文件中只添加了依赖 B1,那么此时 A 在引用 B 时,则会在 A 的 go.mod
文件中添加 B2 作为间接依赖,B1 则不会出现在 A 的 go.mod
文件中。
此时 Module A 的 go.mod
文件中 require 部分将会变成:
1 | require ( |
由于 B1 已经包含进 B 的 go.mod
文件中,A 的 go.mod
文件则不必再记录,只会记录缺失的 B2。
总结
为什么要记录间接依赖
在上面的例子中,如果某个依赖 B 没有 go.mod
文件,在 A 的 go.mod
文件中已经记录了依赖 B 及其版本号,为什么还要增加间接依赖呢?
我们知道 Go module 需要精确地记录软件的依赖情况,虽然此处记录了依赖 B 的版本号,但 B 的依赖情况没有记录下来,所以如果 B 的 go.mod
文件缺失了(或没有)这个信息,则需要在 A 的 go.mod
文件中记录下来。此时间接依赖的版本号将会跟据 Go module 的版本选择机制确定一个最优版本。
如何处理间接依赖
综上所述间接依赖出现在 go.mod
中,可以一定程度上说明依赖有瑕疵,要么是其不支持 Go module,要么是其 go.mod
文件不完整。
由于 Go 语言从 v1.11 版本才推出 module 的特性,众多开源软件迁移到 go module 还需要一段时间,在过渡期必然会出现间接依赖,但随着时间的推进,在 go.mod
中出现 // indirect
的机率会越来越低。
出现间接依赖可能意味着你在使用过时的软件,如果有精力的话还是推荐尽快消除间接依赖。可以通过使用依赖的新版本或者替换依赖的方式消除间接依赖。
如何查找间接依赖来源
Go module 提供了 go mod why
命令来解释为什么会依赖某个软件包,若要查看 go.mod
中某个间接依赖是被哪个依赖引入的,可以使用命令 go mod why -m <pkg>
来查看。
另外,命令 go mod why -m all
则可以分析所有依赖的依赖链。
如何锁定版本号
systemd管理进程自动重启
可以使用 systemd 管理 go 进程
systemd 实践: 依据情况自动重启服务,参考:https://mp.weixin.qq.com/s/W6_Z1zPn3MeDgfGDP263vg
systemd 服务异常自动重启很好用,但有的时候希望某些服务只在特定情况下进行重启,其他时候不要自动重启(比如 OOM,需要人工介入)。 本文抛砖引玉,旨在能够让读者对 systemd 的重启机制有一定了解。
最简单的自动重启范例
1 | [Unit] |
重点参数详解
- Restart=always: 只要不是通过 systemctl stop 来停止服务,任何情况下都必须要重启服务,默认值为 no
- RestartSec=5: 重启间隔,比如某次异常后,等待 5(s)再进行启动,默认值 0.1(s)
- StartLimitInterval: 无限次重启,默认是 10 秒内如果重启超过 5 次则不再重启,设置为 0 表示不限次数重启
案例需求
需求:有个业务,当程序因受到 OOM 而退出的时候,不希望自动重启(此时需要人工介入排查),其他情况下可以自动重启
分析:OOM 就是通过 kill -9 来杀进程,因此只要找到方法,告诉 systemd 当该服务遇到 kill -9 时候不自动重启即可
RestartPreventExitStatus 参数
查询 man systemd.service 发现,systemd 的[Service]段落里支持一个参数,叫做 RestartPreventExitStatus
该参数从字面上看,意思是当符合某些退出状态时不要进行重启。
该参数的值支持 exit code 和信号名 2 种,可写多个,以空格分隔,例如:
1 | RestartPreventExitStatus=143 137 SIGTERM SIGKILL |
表示,当退出情况只要符合以下 4 种情况中任意一种时候,则不再进行重启
- exit code 为 143
- exit code 为 137
- 信号为 TERM
- 信号为 KILL
但具体如何使用,请继续往下看
测试方法
/usr/lib/systemd/system/mytest.service
1 | [Unit] |
/root/mem.c(不断消耗内存直至发生 OOM)
1 | #include <stdio.h> |
编译及执行
1 | gcc -o /root/mem /root/mem.c |
测试结果
1 | [root@fzxiaomange ~]# systemctl status mytest |
重点看上面第 6 行 MainPID:10555(code=killed,signal=KILL)
,这行表示主进程的状态,常见有 2 种情况
- code=exited, status=143:表示 systemd 认为主进程自行退出的,exit code 为 143
- code=killed, signal=KILL:表示 systemd 认为主进程是被 kill 的,接收到的信号是 SIGKILL
等待 5 秒后,并没有自动重启,符合预期
此时将 RestartPreventExitStatus=SIGKILL 改为 RestartPreventExitStatus=SIGTERM
执行 systemctl restart mytest,再进行一次观察,等待 5 秒后,服务自动重启,符合预期
注意事项
RestartPreventExitStatus 与 Restart 的关系
配置 RestartPreventExitStatus=后,并没有完全忽略 Restart=,而是指当退出情况与 RestartPreventExitStatus=匹配的时候,才忽略 Restart=,若没有匹配,根据 Restart=该怎么样还怎么样(具体详见后面的详细测试数据)
kill 子进程会是什么情况
若 systemd 启动的不是一个简单进程,而是会派生子进程的情况(比如执行 shell 脚本,shell 脚本里启动多个程序),那么当另外开一个窗口通过 kill-信号
测试时,会是什么情况呢,先贴出测试方法
ExecStart=/root/mem 改为 ExecStart=/root/mytest.sh
/root/mytest.sh 内容为
1 | #!/bin/bash |
测试结果
- 若 kill 主进程 PID(kill 不带参数),则主进程状态为
code=killed,signal=TERM
- 若 kill -9 主进程 PID,则主进程状态为
code=killed,signal=KILL
- 若 kill 最后一个子进程 PID(kill 不带参数),则 systemd 不认为是接收到信号,而是根据最后一个进程的 exit code 进行处理,此时主进程状态为
code=exited,status=143
- 若 kill -9 最后一个子进程 PID,此时主进程状态为
code=exited,status=137
详细测试数据
上面有提到 RestartPreventExitStatus 和 Restart 的关系,但没有数据说明
另外,kill 和 kill -9 的区别,也需要有一份数据说明
因此做了一个详细对比,这里附上详细数据