跳到主要内容

JavaScript 事件循环机制

· 阅读需 4 分钟

在 JavaScript 中,任务的执行顺序是基于事件循环(Event Loop)机制。理解这一机制对于编写高效、非阻塞的 JavaScript 代码至关重要。JavaScript 的执行模型可以分为以下几部分:

  1. 调用栈(Call Stack)

  2. 任务队列(Task Queue)

  3. 微任务队列(Microtask Queue)

  4. 事件循环(Event Loop)

调用栈(Call Stack)

调用栈是 JavaScript 引擎的一部分,用于跟踪函数调用的执行顺序。每当一个函数被调用时,它会被推入调用栈;函数执行完毕后,会从调用栈中弹出。调用栈是同步执行的,即一个函数必须执行完毕后,下一个函数才能执行。

任务队列(Task Queue)

任务队列用于存放异步任务的回调,如 setTimeoutsetInterval。当异步任务完成后,对应的回调函数会被放入任务队列中等待执行。

微任务队列(Microtask Queue)

微任务队列用于存放微任务,如 Promise 的回调函数和 MutationObserver 的回调。微任务具有更高的优先级,会在当前调用栈执行完毕后立即执行,优先于任务队列中的任务。

事件循环(Event Loop)

事件循环不断地检查调用栈是否为空,如果为空,它会检查微任务队列是否有任务需要执行。如果微任务队列为空,事件循环会从任务队列中取出第一个任务并执行。

执行顺序示例

通过以下代码示例,可以更清楚地了解 JavaScript 的任务执行顺序:

console.log("Start");

setTimeout(() => {
console.log("Timeout callback");
}, 0);

Promise.resolve().then(() => {
console.log("Promise callback");
});

console.log("End");

执行顺序如下:

  1. console.log("Start") 被推入调用栈并执行,输出 Start

  2. setTimeout 被推入调用栈并执行,注册一个回调函数,并设置 0 毫秒延迟。回调函数被放入任务队列。

  3. Promise.resolve().then 被推入调用栈并执行,注册一个微任务回调函数,并放入微任务队列。

  4. console.log("End") 被推入调用栈并执行,输出 End

  5. 调用栈清空后,事件循环检查微任务队列,发现有一个微任务。将微任务回调 console.log("Promise callback") 推入调用栈并执行,输出 Promise callback

  6. 微任务队列清空后,事件循环检查任务队列,发现有一个任务。将 setTimeout 的回调函数 console.log("Timeout callback") 推入调用栈并执行,输出 Timeout callback

最终输出顺序为:

Start
End
Promise callback
Timeout callback

总结

JavaScript 的任务执行顺序是基于事件循环、调用栈、微任务队列和任务队列的协同工作。理解这些机制有助于编写高效、非阻塞的代码,并避免常见的异步编程问题。

2023年 StackOverflow 开发者调查报告说了啥

· 阅读需 5 分钟

偶然想起,大名鼎鼎的 stackoverflow 报告一直没见过原版,关于它的信息都是二手的。虽然在这个时间点(2024 年过半)有些尴尬,但是还是有读一读的热情。报告很长,我就节选一些我感兴趣的部分做个摘录,并结合自己的印象来谈谈感受。

Developer profile

这部分是开发者画像。绝大部分调查者来自欧美+印度。其中 5 年以上经验的人占了绝大多数。角色这块,依旧是全栈、后端、前端占了前三甲。老外的全栈真的普遍,这点和国内真是显著不同。

Technology

编程语言

职业程序员,HTML/CSS、Javascript、SQL 是绝对统治级别的前三,因为有了这三样,绝大部分的网站都能开发。全栈几乎可以和这三个划等号。4-6 位也比较有意思,分别是 python、typescript、shell。Python 能处在这个位置,除了它自身的简单易用,也是它作为很多人的入门语言形成的惯性,以及它在数据分析、机器学习领域的不可替代性造成的。Typescript 的使用率能和 Python 并驾齐驱,倒是我没有想到的。我想这是因为 JavaScript 语言的流行性极大带动了 ts 的发展。最后的 shell,毫无疑问它的场景在于服务器运维,也是全栈开发者工作中必须面对的场景。未来我打算学的 Golang, 2023 年的排名在 12,落后于 C。

数据库

数据库和我现在做的工作高度相关,感受多一些。网上对数据库的分类有很多,比如 oltp 和 olap。但是我这边的分类的话,更倾向于行数据库、列数据库和混合型。当然,redis 这种内存数据库不在这个范围内。正是因为存储方式的不同,导致不同数据库产品有不同的应用场景。比如最流行的 pgsql 和 mysql,都是行存储,因此属于 oltp。clickhouse 为列存储,故为 olap。近些年更有 tidb 这种高性能数据库,可以在两种存储方式之间切换,所以既可以当作业务数据库,也可以当作分析数据库。

其实如果紧紧围绕业务和分析两大需求,还有别的解决方案。比如前司,pgsql 读写分离,一个 master 配上三个 slave,大屏这种纯分析展示的场景,就从只读数据库去读取。不过仅仅能达到不影响主业务的程度,复杂查询对数据库的压力大,速度也慢。

上面说的其实都属于基于 SQL 查询语言的表格类型数据库。分类上与之平级的还有 nosql 数据库。其下的数据库一般和情景紧密绑定,比如 redis 在高并发、elasticsearch 在日志检索、mongodb 在松散数据存储。属于是有了这些需求,才会去用。

云平台

AWS 是遥遥领先第二的存在。AWS 几乎也成了云平台标准的制定者。比如 S3 存储,都是 AWS 最开始制定的。看来,以后想要出海工作,AWS 也是必须要接触熟悉的。

编程框架

前五名(React/Nodejs/Jquery/Angular/Express)全是基于 JavaScript,这也应证了 javascript 最流行的结果。这里面,前端框架独占其三,这说明了,前端生态圈的旺盛。

后记

这份报告比较全面,远不止上述内容。如果要阅读全文,这里给出全文链接:


2023 Devoloper Survey https://survey.stackoverflow.co/2023/#methodology


K8S Services解释

· 阅读需 2 分钟

前言

从我个人角度来看,service 是 k8s 最容易出问题的地方。网络不通是最常见的问题,想要排查网络,就要对 service 有一个深入的理解。

ClusterIP Service

clusterIP工作场景

ClusterIP 是 service 的一种类型,负责管内部的网络通信。k8s 中,每个 pod 都有独立的 ip,但是 pod 是非稳定的,很容易创建销毁,ip 地址也随之改变。clusterIP 的 service 就是通过 selector 确定请求转发到哪种 pod,还有 targetPort 属性确定 pod 的 port。targetPort 必须和容器监听的 port 相同。

Headless Service

headless 工作场景

ClusterIP 有自己的 ip,Headless 就是不分配 ip 的服务。上图中,两个 mongo-db 节点中的 pod 想要相互通信(比如主从数据同步),就要通过 headless service。

nodePort Service

NodePort工作场景

nodePort 是节点机器上的一个端口, nodePort 可以直接被 Ingress 或者外部请求访问到。它的数值范围是 30000-32767. nodePort 被创建,同时意味着有一个内部的 clusterIP service 被创建。

loadBalancer Service

loadBalancer工作场景

等于是在 nodePort service 之外又套了一个 loadBalancer,意味着创建了 loadBalancer,同时也创建了 nodePort 和 ClusterIP。

17190658904841719065889152.png

在生产环境中,出于安全考虑,应该用 Ingress 或者 loadBalancer service 处理外部请求,而不是 nodePort。

minikube的使用记录

· 阅读需 5 分钟

前言

k8s 一直以概念繁杂著称。在容器编排这个领域,它是统治级的存在,可以说它就代表了这个领域内的几乎所有知识。因为我对这个领域并不熟悉,所以很难记住这些概念。只能一次次地翻看基础教程。

k8s 的背景是容器化部署盛行,加上微服务要求,一个系统的容器数量成百上千,这样,必须有一个专门的编排工具,达到高可用、可伸缩等目的。

在之前写的k8s 入门中,介绍了两个在线平台运行 k8s 命令。其实,本地也可以运行 minikube 这个精简版。下面记录一下它的使用过程。

安装

minikube架构

简而言之,一个节点中包含了控制进程和工作进程,因此一台笔记本也能模拟 k8s 集群。


minikube 文档 https://minikube.sigs.k8s.io/docs/


minikube 的默认启动命令是minikube start,命令行启动界面:

minikube启动界面

可以看出,我本机上的 minikube 版本号为 1.32.0,且使用到了预装的 docker 和 kubectl,k8s 的版本号为 1.28.3. 启动后,docker desktop 显示多了一个 minikube 容器,是为 node。

kubectl


kubectl cheat sheet https://spacelift.io/blog/kubernetes-cheat-sheet


kubectl 是一个命令行工具,需要单独安装。通过它,操控 k8s 的各级资源。

yaml 文件编写

yaml 文件的四个必要部分:apiversion, kind, metadata, spec

  • apiVersion: 指定资源的 API 版本,例如 v1。
  • kind: 指定资源的类型,例如 Pod、Service、Deployment 等。
  • metadata: 提供资源的元数据,例如名称、命名空间、标签等。
  • spec: 指定资源的具体配置,内容根据资源类型的不同而有所不同。

一份示例 yaml 文件:

apiVersion: v1
kind: Pod
metadata:
name: mypod
labels:
app: myapp
spec:
containers:
- name: mycontainer
image: nginx:latest
ports:
- containerPort: 80
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: mydeployment
labels:
app: myapp
spec:
replicas: 3
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: mycontainer
image: nginx:latest
ports:
- containerPort: 80

一般 yaml 文件不要求从头写,而是从模板复制:


kubernetes 官方文档 https://kubernetes.io/zh-cn/docs/concepts


运行 yaml 文件的命令:kubectl apply -f test.yaml

spec 下的 replicas 字段指 pod 数量,不论 kind 是 deployment 或者 replicaset。

另外,yaml 文件还有一个实时更新的部分:status。它记录了当前状态和 yaml 文件的目标状态之间的差异。

labels 作用:template 下的 labels 是给一种 pod 打上相同的标签;selector 下的 matchLabels 则是告诉 Deployment,replicas 是使用该标签下的 pod。

service 下的 port/targetport/nodePort

可以把 service 想象为一台完整的机器。既有接受外部请求的端口(port),又有内部的容器(pod)监听的端口(targetPort)。

port:这是 Service 本身暴露给集群内其他 Pod 或外部用户的端口号。当其他应用程序或服务想要与 Service 通信时,它们将使用该端口。例如,如果你有一个 Web 应用程序,你可能会将端口设置为 80(HTTP)或 443(HTTPS)。

targetPort:这是 Service 用来连接后端 Pod 的端口号。当 Service 接收到来自外部的请求时,它将流量路由到目标端口上。这个端口对应于 Service 所指向的后端 Pod 中运行的应用程序的端口。例如,如果你的后端应用程序在容器中监听 3000 端口,你会将 targetPort 设置为 3000。

最后,nodePort 只存在于 external service 中,表示暴露给 k8s 外的 client 的端口。

Nana 教程中 mongo-express 例子最后的坑

在例子的最后,命令行执行minikube service mongo-express-service后,有如下日志:

命令行输出

下面那行才是本机浏览器可访问的地址。只是还需要账号密码。nana 的视频并不需要输入账号密码。在 stackoverflow 上找到答案,这里的账号密码是 mongo-express 要求提供的,默认是 admin:pass

一文弄懂字符编码,面对乱码不再抓瞎

· 阅读需 4 分钟

我们都知道,计算机起源自美国。那时候的电脑普遍支持 8 bits 运算。因此,128 位以下的数字用作表达英文字母,即 ASCII 码。

后来,计算机卖到了世界各国,混沌之初,一切无序。各国按照各自的语言,在 128 位以上的广阔天地里自由飞翔。

这样就导致了一个问题,美国人发来的英文邮件在西班牙的电脑上有乱码,根本看不懂!

光是想想,每个国家都有自己的小册子,上面记录了 128 位以上的字母(字符)表示,这要求每个计算机都要存储这些小册子,太疯狂了!

历史车轮滚滚向前,Unicode 出场了。它带着将世界上每一个字符都赋予编码的使命来了。

Unicode 的思路是:每一个字母都分配一个抽象的编号,比如'Hello' - U+0048 U+0065 U+006C U+006C U+006F.

好的,现在赋予编号完成了,可是计算机是基于二进制呀,Unicode 如何存储在计算机中呢?

先做一个简单的心算。上面 Hello 的编码,去掉 U+: 00 48 00 65 00 6C 00 6C 00 6F. 每个数字的范围 0-F,二进制需要四位,也就是半个 byte,所以 00 是一个 byte,一个字母 H 是两个 byte!

这时候,美国人跳出来表达了不满:我们的 ASCII 码用的好好的,八位就可以表达所有的字母,现在要我们用 Unicode,每个字母都要用 16 位,那文件大小生生比原来多了两倍!

于是,UTF-8 为了解决上述痛点,依旧将 128 以下的编码用于英文字母,且只用一个字节,只有 128 以上的,才用两个字节存储。这样,对于英语用户之间的文件传递,没有任何影响。

Unicode 只是一个抽象的操作,将任何字符转化为一个 U+字符串。任何计算机上实际显示一个字符串需要一个编码方案的支持。可以想象的 hello 字符串的 unicode 为...,然后某个编码方案 iso-xxx 中找不到这些码,于是显示?或者 �。

最后,作者特别指出一句话:

It does not make sense to have a string without knowing what encoding it uses.

Joel 的文章地址:


The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!) https://www.joelonsoftware.com/2003/10/08/the-absolute-minimum-every-software-developer-absolutely-positively-must-know-about-unicode-and-character-sets-no-excuses/


VSCode 使用效率大提升!从常用快捷键谈起

· 阅读需 6 分钟

快捷键

记录一些明显提升工作效率的快捷键,用表格形式,并注明场景。

命令用途场景
ctrl+p快速定位,加冒号后还可以定位行号,加@后定位变量,@后再加冒号还可以按分类查看变量多标签页切换或者大文件中定位行
ctrl+shift+p命令画板记不清快捷键的时候,调出来尝试用自然语言调用
ctrl+tab切换标签页多标签页
alt+click多光标选中批量添加字符
Ctrl+Shift+L选中相同的字符批量选中
CTRL+D选中相同的字符(one by one)批量选中
Shift+Alt+drag按列选中-
alt+鼠标滚轮加速上下滚动大文件查看
shift+alt+up/down向上下复制类似内容批量创建(替代 ctrlcv)
alt+up/down向上下移动代替剪切粘贴
ctrl+shift+[/]折叠、展开代码大文件折叠(根据光标位置,自动选择层级)
ctrl+K+Q快速定位到上次编辑位置大文件编辑查看
ctrl+shift+K删除行当一行较长,避免了拉鼠标和敲退格
ctrl+shift+enter插入行当一行较长,不用移动到行末按回车
home(笔记本 Fn+home)、end定位到行首、行尾很长的一行
ctrl+home/end定位到文件的开头、结尾很长的一个文件
Shift + End选中光标到行尾的文本选中很长的一行

参考资料:

vscode 官方 tips

多项目管理利器:工作区

随着工作接受的项目越来越多,如何更轻松地管理多个项目成为一个问题。目前我有三个项目,每次重启,需要分别启动三个不同的窗口,如果加上最近搞的 side project,可能同时打开 6 个窗口!所以,我打算寻找一个解决方案。

我之前用过一个叫 Project Manager 的插件,但是这次装回来体验了一下依旧乏善可陈。工作区(Workspace)是 vscode 自带的功能,它指的是你在一个窗口中打开的一个或多个文件夹及其相关配置的集合。工作区允许你将相关的项目文件夹、配置和设置组织在一起,方便进行项目开发。

工作区的创建过程

  1. 打开一个空白的新窗口。

  2. 左上角,File -> Add Folder To Workspace, 把项目文件夹添加进来,有几个项目就重复几次这一步操作。

  3. 待项目全部添加完毕后,File -> Save Workspace As,选一个方便的地方存,确认后会生成一个名为xxx.code-workspace的文件,内容示例:

{
"folders": [
{
"path": "frontend"
},
{
"path": "backend"
}
],
"settings": {
"workbench.colorCustomizations": {
"editor.lineHighlightBackground": "#1073cf2d",
"editor.lineHighlightBorder": "#9fced11f",
"activityBar.background": "#080A9B",
"titleBar.activeBackground": "#0B0EDA",
"titleBar.activeForeground": "#FCFCFF"
}
}
}

后续点击这个文件,即可打开工作空间。

工作区的使用

一个工作空间会同时列出多个项目,可以同时管理。我的使用感受是,搜索文件似乎反倒有些不便了,比如搜 index.js,会出现非常多的结果,因为三个文件夹的搜索结果都汇聚起来了。此外,explorer 和 source control 卡,都把所有项目并列显示。

还有一个非常常用的部分,就是 Terminal。不同项目有不同路径的 Terminal,还好 vscode 会帮我们标注出来。不过,更好的做法是,用 split 将不同 terminal 表示为一组,这样既可以并列观察,也可以对 terminal 分组。

remote explorer

相较于 xshell+xftp 来连接远程服务器和文件操作,vscode 的 remote explorer 显然更操作友好。它同样支持密钥和密码登录两种模式,密钥登录虽然稍微麻烦,但是安全性更高,应该首先考虑。增添连接的路径为:SSH 栏的设置图标,打开.ssh/config文件,添加如下内容:

Host <别名>
HostName <服务器地址>
User <用户名>
Port <端口号>
IdentityFile <私钥路径>

如果是密码登录,就不用IdentityFile了。这里假设已经从服务器申请到了私钥。windows 还需要对私钥的权限进行设置。

在 Windows 上,修改文件权限可以通过以下步骤:

  1. 右键点击 jason.pem 文件,选择 "属性"

  2. 切换到 "安全" 选项卡,然后点击 "高级" 按钮。

  3. 在弹出的窗口中,点击 "禁用继承"

  4. 选择 "从此对象中删除所有继承的权限"

  5. 点击 "添加" 按钮,然后点击 "选择主体"

  6. 输入 你的 Windows 用户名 ,点击 确定

  7. 在权限窗口中,勾选 "完全控制" ,点击 确定

  8. 确保只有当前用户拥有权限,点击 应用确定

HTTPS原理详解,从出发点到实现方式

· 阅读需 4 分钟

这一篇文章,主要记录我在学习网络安全相关知识的一些体会。

https

了解 https 之前,首先学习一下哈希算法。弄明白哈希算法的输入输出:输入一串不定长的数据,输出的字符串是定长的(比如 256bit)。

关于 https,首先在脑海中呈现的,应该是这样一个模型:

17109479490611710947948667.png

可以看出,https 就是在 http 的基础上,套了一层 SSL/TLS,而 SSL/TLS 的功能其实本质上是如何协商出安全的对称加密密钥以利用此密钥进行后续通讯的过程

我们来解释一下上面这句话的意思。因为 http 是明文传输,所以应该想办法对传输数据进行加密,使得黑客拿到的数据都是一堆无意义的字符串。加密需要密钥,密钥就像一把箱子的钥匙,数据就被所在箱子里。看起来,明文传输的问题解决了。

然而,密钥也是通过互联网进行传输的,意味着黑客也可以获得密钥,这意味着数据还是裸奔。所以,有必要对密钥也进行加密!听起来是不是在套娃了呢?别急,非对称加密终结了这个死结。所谓的非对称加密,就是让客户端(浏览器)保存公钥(任何人可获得),服务器保存私钥(绝对不能让其他人知道)。客户端带着用公钥加密的对称加密密钥(这里别被绕晕了哈),传给服务器,只有服务器的私钥才能解密,获得对称加密密钥,到这里,我们是不是可以理解出上面加粗句子中的协商的意思呢。

然而,问题到这里还没有结束。客户端是如何获得真实的公钥呢?肯定是不能由服务器传来的啦!这时候就要引入一个可信的第三方(CA)的概念了!服务器会去向这个权威第三方申请一个证书,证书的签发如下图所示。证书内带了站点的公钥CA 的私钥会加密这个证书,然后等到客户端获得证书时,再用CA 的公钥来解密证书,获得站点的公钥。而且,CA 的公钥是操作系统内置的,代表绝对可信。这样就能实现安全性的闭环了。

17110787820041711078781120.png

最后结合一点工作上的实际经验加深体会。常常听到别人说,某个项目网站的 https 证书过期了,要花钱续期(也不便宜了,一年几千)。这个证书就是上面的服务端证书,需要向 CA 申请!所以说,不能随便信任证书。

参考链接:


20 张图让你彻底弄懂 HTTPS 原理.md https://github.com/allentofight/easy-cs/blob/main/%E7%BD%91%E7%BB%9C/20%E5%BC%A0%E5%9B%BE%E8%AE%A9%E4%BD%A0%E5%BD%BB%E5%BA%95%E5%BC%84%E6%87%82HTTPS%E5%8E%9F%E7%90%86.md


nginx 入门

· 阅读需 4 分钟

出现报错,nginx 的错误日志位置:

tail -f /var/log/nginx/error.log

静态网页

server{

listen 8000;
server_name localhost;

location / {
root /home/AdminLTE-3.2.0;
index index.html index2.html index3.html;
}

}

location 的运行方式有点绕。首先在浏览器输入网址,最重要的三部分:ip(域名)、端口号、路由。ip 的话,显然不需要写在配置里,因为是固定的。上面的 server_name 字段只是作为一个标识符,起区别作用。端口号对应的就是 listen。路由这部分由 location 确定。location 后面跟的就是路由,那么 nginx 服务器把 root 拼接上述的路由,得到的结果作为文件路径去硬盘里寻找文件(如 index.html)。

反向代理

反向代理本身可以视为一种基本能力,是后面的负载均衡、缓存等高级功能的前提。

server {

listen 8001;

server_name ruoyi.localhost;

location / {
proxy_pass http://localhost:8088;
}

}

将 8001 端口收到的请求,转发到 8088 端口。

proxy_pass 配置说明:

location /some/path/ {
proxy_pass http://localhost:8080;
}

如果 proxy-pass 的地址只配置到端口,不包含/或其他路径,那么 location 将被追加到转发地址中。 如上所示,访问 http://localhost/some/path/page.html 将被代理到 http://localhost:8080/some/path/page.html

location /some/path/ {
proxy_pass http://localhost:8080/zh-cn/;
}

如果 proxy-pass 的地址包括/或其他路径,那么/some/path 将会被替换,如上所示,访问 http://localhost/some/path/page.html 将被代理到 http://localhost:8080/zh-cn/page.html。

动静分离

动静分离是指图片、css、js 等文件的请求直接由 nginx 返回响应,而不进行转发,减少宝贵的动态服务器资源占用。

有两种动静分离的方法,第一种是把静态文件手动放到一个目录里,以后的所有静态文件请求都指向这个目录。另一种是比较熟悉的,首次请求走动态服务器,并且把文件存在特定的目录,这样也是被缓存。第二种方式避免了手动和跨服务器操作,故推荐。

proxy_cache_path /var/cache/nginx/data keys_zone=mycache:10m;

server {

listen 8001;
server_name ruoyi.localhost;

location / {
#设置buffer
proxy_buffers 16 4k;
proxy_buffer_size 2k;
proxy_pass http://localhost:8088;

}


location ~ \.(js|css|png|jpg|gif|ico) {
#设置cache
proxy_cache mycache;
proxy_cache_valid 200 302 10m;
proxy_cache_valid 404 1m;
proxy_cache_valid any 5m;

proxy_pass http://localhost:8088;
}

location = /html/ie.html {

proxy_cache mycache;
proxy_cache_valid 200 302 10m;
proxy_cache_valid 404 1m;
proxy_cache_valid any 5m;

proxy_pass http://localhost:8088;
}

location ^~ /fonts/ {

proxy_cache mycache;
proxy_cache_valid 200 302 10m;
proxy_cache_valid 404 1m;
proxy_cache_valid any 5m;

proxy_pass http://localhost:8088;
}

}

proxy_cache_path 规定了缓存文件存放位置。keys_zone 作为标识供 location 引用。10m 表示缓存文件大小不超过 10m。location 中的 proxy_cache_valid 规定了缓存失效时间。

负载均衡

upstream ruoyi-apps {
#不写,采用轮循机制
ip_hash; #可确保来自同一客户端的请求将始终定向到同一服务器
hash $request_uri consistent;
server localhost:8080;
server localhost:8088;

}

server {

listen 8003;
server_name ruoyi.loadbalance;

location / {
proxy_pass http://ruoyi-apps;
}

}

k8s 入门

· 阅读需 6 分钟

前言

我没想到有一天我会去学 k8s,但这是新公司要求的,现在我觉得我即将入职的岗位可能是 devops(development+operation, 开发+运维)。

首先,k8s 是 kubernetes 的简称,因为 k 和 s 之间有八个字母。我之前一直以为 8 来自 b,是国人的专属简称,哈哈。还有,orchestration, 编排,字面意义就是对容器的管理。

安装的话,我在笔记本的 windows 上和云主机的 centos7 上都安装失败了。笔记本上是想着按照虚拟机的方案模拟集群,但是 vmware 的 player 非常难用,换成基于 ubuntu 的轻量虚拟机 multipass 也失败了。最后发现,还是在线环境适合这种大型软件的学习。一共两个可选项:


killerconda https://killercoda.com/playgrounds/scenario/kubernetes



play-with-k8s https://labs.play-with-k8s.com/


play-with-k8s 的网页命令行不能执行复制粘贴,killercoda 不仅可以粘贴命令,而且是已经设置好集群了(包含控制平面和一个工作节点)。所以,推荐 killercoda。

基础概念

node(节点),一个集群至少包含一个控制平面和一个节点。一个节点通常映射到一个物理服务器或一个虚拟机。节点上的组件包括 kubelet、 容器运行时以及 kube-proxy。

k8s 能操作的最小单元是 pod,大多数应用场景下,一个 Pod 只包含一个容器。

Deplyment 下的 replica 属性规定了 pod 的集合内容器。有了 deployment,意味着应用容器挂掉可以马上转到新的一模一样的容器中,而没有任何 downtime.

statefulset 类似于 deployment,但是针对于有状态的应用(如数据库)。deployment 只记录了无状态应用的 blueprint,而 statefulset 还保存了有状态应用的状态信息。(注:如果数据库只有单节点而非集群,也可以用 deployment 管理,只需要配置好 volumn 即可)

Service 是将运行在一个或一组 Pod (一组内的 pod 应该具有相同的功能)上的网络应用程序公开为网络服务的方法。具体而言,Service 提供了三个功能:标签选择器(Label Selector)、稳定的网络标识、负载均衡。其中,稳定的网络标识是核心。Service 为其选择的一组 Pods 提供一个稳定的 IP 地址和 DNS 名称。无论 Pod 的 IP 地址如何变化,客户端都可以通过 Service 的 IP 地址或 DNS 名称来访问这些 Pods。

Ingress 是指在访问域名而非 ip 地址时,需要先经过 Ingress(DNS?)

configMap: 明文配置;secret: 不方便给外界看的配置。

k8s 架构

k8s集群架构

每个工作节点内部必须有三个进程:

  • kubelet 负责 pod 的创建
  • kube proxy 负责 pod 请求的转发
  • 容器运行时

控制节点也可以不止一个。控制平面管理集群中的所有操作。主要组件包括:

  • API Server:集群的入口,接收和处理所有的 REST 请求。
  • etcd:一个高可用的键值存储,用于存储集群的所有数据。
  • Controller Manager:运行控制器,确保集群的期望状态和实际状态一致。
  • Scheduler:负责将新创建的 Pod 分配到适当的工作节点上。

作为一个如此复杂的工具,我们应该知道什么东西是需要人来配置,什么东西是工具自己来调节操控的。Deployment 以下的,都是 k8s 自主调节。

从头开始

说了这么多,有必要把一个应用是如何由原来的直接部署或者 docker 部署迁移到 k8s 上的完整流程叙述一遍。首先,把手头上拥有的机器资源做个清点,划分出控制节点和工作节点。然后,在每台机器上都装上 Kubeadm, Kubelet 和 Kubectl。在控制节点上执行kubeadm init初始化集群;在工作节点上执行kubeadm join加入集群。验证集群是否成立。最后就是编写 yaml 文件,在任意一个节点上执行kubectl apply -f执行 yaml 配置(无论哪个节点,kubectl 都会到控制节点的 api-server 中)。

linux上kafka安装和入门

· 阅读需 2 分钟

安装

之前在 windows 上安装 kafka 算是吃尽苦头。现在用 linux 机器再试一次。像 kafka 这种依赖 zookeeper 的服务,最简单的方法还得是 docker。具体的教程就参考这篇文字:


使用 Docker 部署 Kafka 单机版 https://ken.io/note/kafka-standalone-docker-install


kafkajs

公司项目的 kafka 是用 kafkajs 这个库来驱动的。但是,文档给出的例子没法直接运行,所以让 chatgpt 给出了一段能用的代码:

const { Kafka } = require("kafkajs");

// 创建 Kafka 客户端实例
const kafka = new Kafka({
clientId: "my-kafka-app",
brokers: ["localhost:9092"], // 你的 Kafka 服务器地址
});

// 创建生产者实例
const producer = kafka.producer();

// 创建消费者实例
const consumer = kafka.consumer({ groupId: "test-group" });

// 发送消息函数
const sendMessage = async () => {
await producer.connect();
await producer.send({
topic: "test-topic",
messages: [{ value: "Hello KafkaJS!" }],
});
await producer.disconnect();
};

// 接收消息函数
const receiveMessage = async () => {
await consumer.connect();
await consumer.subscribe({ topic: "test-topic", fromBeginning: true });

await consumer.run({
eachMessage: async ({ topic, partition, message }) => {
console.log({
topic,
partition,
offset: message.offset,
value: message.value.toString(),
});
},
});
};

// 发送和接收消息
sendMessage()
.then(() => receiveMessage())
.catch(console.error);

17085683262981708568325402.png

在输出消息之前,控制台还打印出了几条日志。目前主要关注最后两条。消费者启动并加入到消费者组。为了确定 kafka 的主题中是否真的有这个消息,运行:

kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic test-topic --from-beginning