部署运行k8s

重要

  1. k8sdocker的版本必须兼容,否则会安装失败
    本次使用的k8s-1.19.16docker-19.03.9

  2. 至少使用两台服务器,你也可以使用虚拟机
    注:本文档使用的服务器都是 linux/centos7

  3. 如果机器上有其他版本的docker或者k8s,请全部卸载干净

  4. 如有任何操作没有达到预期的效果,你进行网络搜索

安装docker19.03.9

两台服务器都需要安装docker

1
2
3
yum install docker-ce-19.03.9-3.el7 -y
systemctl enable docker
systemctl start docker
1
2
# 打开文件 
vim /etc/docker/daemon.json

输入如下内容

1
2
3
4
{
"registry-mirrors": ["https://b9pmyelo.mirror.aliyuncs.com"],
"exec-opts":["native.cgroupdriver=systemd"]
}
1
2
# 重启docker
systemctl restart docker

安装k8s1.19.16

两台服务器都需要安装k8s

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
# 跟着步骤依次复制粘贴运行
# 两台服务器都要执行
cat <<EOF > /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=http://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64
enabled=1
gpgcheck=1
repo_gpgcheck=1
gpgkey=https://packages.cloud.google.com/yum/doc/yum-key.gpg https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg
EOF

setenforce 0

yum install -y kubelet-1.19.16 kubeadm-1.19.16 kubectl-1.19.16 --disableexcludes=kubernetes
systemctl enable kubelet && systemctl start kubelet

cat <<EOF > /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
net.ipv4.ip_forward = 1
EOF

sysctl --system

sudo systemctl stop firewalld
sudo systemctl disable firewalld
sudo swapoff -a

systemctl daemon-reload
systemctl restart kubelet

systemctl enable docker.service
systemctl enable kubelet.service

echo "export KUBECONFIG=/etc/kubernetes/admin.conf" >> /etc/profile
source /etc/profile

master服务器初始化kubeadm

1
2
3
4
5
6
7
8
kubeadm init \
--image-repository registry.cn-hangzhou.aliyuncs.com/google_containers \
--kubernetes-version v1.19.16 \
--ignore-preflight-errors=Swap \
# pod网段
--pod-network-cidr 200.100.0.0/16 \
# 当前master服务器的ip
--apiserver-advertise-address 192.168.1.100

经过一段时间后,初始化完毕,终端显示如下内容,请务必保存好

1
2
3
# 务必保存好,在worker节点运行
kubeadm join 192.168.1.100:6443 --token 02fbxd.3w8re5u8avpbnw5z \
--discovery-token-ca-cert-hash sha256:44b8880409e4e09c395a69599049320c90425700c60f3f219c779d7e8182a5bd

继续查看服务启动情况,确保启动完毕

1
2
3
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

检查pod

1
kubectl get pod --all-namespaces

直到全部running

master服务器安装网络插件

1
kubectl apply -f "https://cloud.weave.works/k8s/net?k8s-version=$(kubectl version | base64 | tr -d '\n')"

检查pod

1
kubectl get pod --all-namespaces

直到全部running

添加worker节点

请确认worker节点已经安装好docker和k8s了

1
2
kubeadm join 192.168.1.100:6443 --token 02fbxd.3w8re5u8avpbnw5z \
--discovery-token-ca-cert-hash sha256:44b8880409e4e09c395a69599049320c90425700c60f3f219c779d7e8182a5bd

添加成功后

回到master节点

1
2
#查看node
kubectl get nodes

请确保全部Ready,如果没有,请网络搜索

总结

至此,k8s安装部署完成,k8s之路才刚刚开始

我们将使用kubectl命令做很多事情,请接下来熟悉kubectl的使用方法

最终成功后,在master节点,可以看到如下打印示例

1
2
3
4
5
[root@k8s-master ~]$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
k8s-master Ready master 25m v1.19.16
k8s-node1 Ready <none> 11m v1.19.16
[root@k8s-master ~]$

全排列

输入一个长度为n的字符串,输出该字符串中字符的全排列

解法一

使用递归,但是这样会出现重复的,需要做去重处理

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
const permutation = (str) => {
const result = [];

// 用来交换数组的两个位置上的值
const swap = (arr, start, end) => {
const tmp = arr[start];
arr[start] = arr[end];
arr[end] = tmp;
return arr;
};

// 开始执行递归逻辑
// arr是当前传入的字符串数组
// begin是当前开始处理的位置,开始为0
const run = (arr, begin) => {
if (begin === arr.length) {
// 当begin累加到当前最大值时,将其输出
res.push(arr.join(''));
} else {
for (let i = begin; i < arr.length; i++) {
swap(arr, i, begin);
run(arr, begin + 1);
swap(arr, i, begin);
}
}
};

run(str.split(''), 0);
return Array.from(new Set(res));
};
console.log(permutation('123'))
// 输出
// [ '123', '132', '213', '231', '321', '312' ]

说明:
使用123举列

  1. 第一个swap说明,将每一位的值和第一位交换位置
    第一层循环会得到这些结果
    1xx2xx3xx

  2. 1xx:后,开始执行递归函数run
    这个时候继续走逻辑,begin变为1,递归后循环结果: 123,接着继续递归,发现begin === arr.length,这个时候输出123,本次递归结束
    回到for循环,这个时候swap,132,继续递归,有发现begin === arr.length,这个时候输出132
    至此1xx后的for循环和递归结束

  3. 后续的2xx3xx,同理上述1xx的描述

解法二

基于上述的解法,如果传入的字符串是abb,则会出现重复的排列值,需要做去重,上述方法,使用了new Set方法对数组做去重,那能否有方法不需要去重就能实现了,同时也不增加额外的空间开销,同时算法的时间复杂度也最小

不断地计算当前字符串的字典序中下一个更大的排列,直到不存在更大的排列为止即可

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
const permutation = function (s) {
const res = [];

const arr = Array.from(s).sort();

res.push(arr.join(''));

const swap = (arr, i, j) => {
const temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
};

const reverse = (arr, start) => {
let left = start,
right = arr.length - 1;
while (left < right) {
swap(arr, left, right);
left++;
right--;
}
};

const nextPermutation = (arr) => {
let i = arr.length - 2;
while (i >= 0 && arr[i] >= arr[i + 1]) {
i--;
}
if (i < 0) {
return false;
}
let j = arr.length - 1;
while (j >= 0 && arr[i] >= arr[j]) {
j--;
}
swap(arr, i, j);
reverse(arr, i + 1);
return true;
};

while (nextPermutation(arr)) {
res.push(arr.join(''));
}

return res;
};
console.log(permutation('123'))
// 输出
// [ '123', '132', '213', '231', '312', '321' ]

解法三

使用深度优先遍历(dfs)

当然该解法,没有去重,还需要加个去重处理

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
const permutation = (nums) => {
const res = [];
const len = nums.length;
if (len === 0) {
return res;
}

const depth = 0;
const path = [];
const used = {};

dfs(nums, len, depth, path, used, res);

return res;
};

const dfs = (nums, len, depth, path, used, res) => {
if (len === depth) {
res.push([...path]);
return;
}

for (let i = 0; i < len; i++) {
if (used[nums[i]]) {
continue;
}
used[nums[i]] = true;
path.push(nums[i]);
dfs(nums, len, depth + 1, path, used, res);
path.pop();
used[nums[i]] = false;
}
};

console.log(permutation('123'))

总结

可以看到输出结果和解法一都不一样

解法一:[ '123', '132', '213', '231', '321', '312' ]
解法二:[ '123', '132', '213', '231', '312', '321' ]

第二种解法是基于字典序的递归查找,这样查找出来的结果,不仅没有重复,而且按字典序进行排序,不需要再进行排序

第一种解法基于遍历的递归交换查找,必然没有任何顺序,但是同样也能很快的找出所有的结果,但势必会有重复的

耗时测试

1
2
3
4
5
6
7
8
const a = '0123456789';
console.time();
console.log(方案二(a).length);
console.timeEnd();

console.time();
console.log(方案一(a).length);
console.timeEnd();

执行输出

1
2
3
4
3628800
default: 1231.317ms
3628800
default: 2406.821ms

由此可见,方案二比方案一,快了1倍

新爬楼梯(一步登天)(列出具体爬法)

原题爬楼梯,请直接参考leetcode-爬楼梯

建议先阅读下leetcode原题,也可以直接往下看该题的解题思路

我在其基础上做了一个抽象,即使用任意步数爬任意高的楼梯,爬到最后一层有多少种爬法

推导出转移方程

假设,爬楼梯,可以1步、2步、3步。。。。n步
楼梯总高为m,那么爬到m层,所有爬法的方程如下

1
2
3
f(m) = f(m-1) + f(m-2) + f(m-3) + ... + f(m-n)

即:需要爬到m层的爬法,由如下的层数的爬法累加得到

先进行数学归纳法

假设步骤只有1步、2步、3步

看如下数学归纳法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// f函数的参数,表示当前爬的楼层数
// 值表示:爬到该层数,所有的爬法数
f(0) = 0;
// f(1) = f(1-1) + 1
f(1) = f(0) + 1; // 这里加1的目的,表示,当前可以一步爬上去,看如下说明,即可明白

// f(2) = f(2-1) + f(2-2) + 1;
f(2) = f(1) + f(0) + 1; // 支持一次爬2步,那么也需要加上一步登天 +1

// f(3) = f(3-1) + f(3-2) + f(3-3) + 1
f(3) = f(2) + f(1) + f(0) + 1; // 同样支持一次爬3步,一步登天 +1

// f(4) = f(4-1) + f(4-2) + f(4-3)
// f(5) = f(5-1) + f(5-2) + f(5-3)

...

f(5) = f(4) + f(3) + f(2) // 没有一步登天,即计算完毕

所以,我们看到,这里存在一个一步登天的变量,需要另外加上去

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const newClimbStairs = (total, steps) => {
const f = {};
// 初始状态定位0,即没有爬楼
f[0] = 0;

for (let i = 1; i <= total; i++) {
f[i] = 0;
for (let j = 0; j < steps.length; j++) {
if (i > steps[j]) {
// 只要当前楼层大于当前步数,就进行累加
f[i] = f[i - steps[j]] + f[i];
}
// 这里是一步登天的判断
if (i === steps[j]) {
// 如果发现当前楼层支持一步登天,就再加上1
f[i] = f[i] + 1;
}
}
}
return f[total];
};

测试结果

多了一个一步登天的“爬50步”,所有步骤也仅仅多了一步

1
2
3
4
console.log(newClimbStairs(50, [1, 2]));
console.log(newClimbStairs(50, [1, 2, 50]));
// 20365011074
// 20365011075

计算爬法步骤

我们已经基于动态规划的方式,计算出爬楼梯的爬法步骤,但是并不知道具体怎么爬

那么接下来,这个算法,将列出,这些具体的爬法是什么

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
51
52
// 将数组的值进行累加
const arrAll = (a) => {
let sum = 0;
for (let i = 0; i < a.length; i++) {
sum += a[i];
}
return sum;
};

const start = (steps, target) => {
const res = [];

for (let i = 0; i < steps.length; i++) {
res.push([steps[i]]);
}

// 把每一层都走一遍
for (let i = 1; i <= target; i++) {
// 给走过的层数,添加当前这一层需要走的步
for (let j = 0; j < res.length; j++) {
// 添加步数
for (let m = 0; m < steps.length; m++) {
const value = [...res[j], steps[m]];
// 步数操作总层数,就不再进行计算了
if (arrAll(value) <= target) {
res.push(value);
}
}
}
}

const result = [];

for (let i = 0; i < res.length; i++) {
if (arrAll(res[i]) === target) {
result.push(res[i].join("-"));
}
}

return Array.from(new Set(result));
};
console.log(start([1, 2, 3], 4));
// 输出
/**
[
'1-3', '2-2',
'3-1', '1-1-2',
'1-2-1', '2-1-1',
'1-1-1-1'
]
总计7种爬法
*/

go语言之mongo-db操作

主要罗列出使用 go 语言操作 mongo 的一些细节和注意事项

基于go.mongodb.org/mongo-driver

连接 mongo-db

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
package main

import (
"context"
"fmt"

"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)

// Db mongo
var Db *mongo.Client

func main() {
// 设置客户端连接配置
clientOptions := options.Client().ApplyURI("mongodb://192.168.41.11:6300")

// 连接到MongoDB
client, err := mongo.Connect(context.TODO(), clientOptions)
if err != nil {
panic("mongodb创建失败" + err.Error())
}

// 检查连接
err = client.Ping(context.TODO(), nil)
if err != nil {
panic("mongodb连接失败" + err.Error())
}

Db = client
}

#

go实现类似Promise.all的功能

主要用来加深对于goroutinechannel的理解吧

什么是Promise.all

Promise.all 可以将多个 Promise 实例包装成一个新的 Promise 实例。
同时,成功和失败的返回值是不同的,
成功的时候返回的是一个结果数组,而失败的时候则返回最先被 reject 失败状态的值

javascript

1
2
3
4
5
6
// run1和run2函数都返回promise
Promise.all([run1(), run2()]).then((res) => {
// res的数据是个数组
const [run1Res, run2Res] = res;
console.log(run1Res, run2Res);
});

go

需要用到协程 goroutine通道 channel

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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
package main

import (
"fmt"
"sort"
"time"
)

func main() {

start := time.Now().Unix()

out1 := run1(1, 2, 0, -1, 92, 22, 33, 82, 11)
out2 := run2(1, 2, 0, -1, 92, 22, 33, 82, 11)

res1 := []int{}
res2 := []int{}

for v := range out1 {
res1 = append(res1, v)
}

for v := range out2 {
res2 = append(res2, v)
}

// 这里的耗时是4秒,而不是6秒
// 即同时执行,多协程抢占式进行任务
fmt.Println(res1, res2)
fmt.Println("耗时:", time.Now().Unix()-start, " 秒")
}

func run1(a ...int) <-chan int {
out := make(chan int)
go func() {

sort.Ints(a)

time.Sleep(time.Second * 4)

for _, v := range a {
out <- v
}

close(out)
}()
return out
}

func run2(a ...int) <-chan int {
out := make(chan int)
go func() {

sort.Ints(a)

time.Sleep(time.Second * 2)

for _, v := range a {
out <- v
}

close(out)
}()
return out
}

说明

我们可以将每个run的实现封装为promise.New的函数

上述逻辑,可以再使用promise.All的函数实现

GO 原生支持异步,用不着 promise,这完全是画蛇添足

但是这个主要是想针对多任务并行处理且同时结束的时机控制,加深对于goroutinechannel的理解吧

go语言实现递归,扁平转为树形,同时添加层级标识

原始数据结构

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
[
{
"id": "1",
"pid": "0"
},
{
"id": "2",
"pid": "1"
},
{
"id": "3",
"pid": "1"
},
{
"id": "4",
"pid": "0"
},
{
"id": "5",
"pid": "4"
},
{
"id": "6",
"pid": "4"
},
{
"id": "7",
"pid": "3"
},
{
"id": "8",
"pid": "3"
}
]

目标数据结构

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
[
{
"id": "1",
"pid": "0",
"level": 0,
"children": [
{
"id": "2",
"pid": "1",
"level": 1
},
{
"id": "3",
"pid": "1",
"level": 1,
"children": [
{
"id": "7",
"pid": "3",
"level": 2
},
{
"id": "8",
"pid": "3",
"level": 2
}
]
}
]
},
{
"id": "4",
"pid": "0",
"level": 0,
"children": [
{
"id": "5",
"pid": "4",
"level": 1
},
{
"id": "6",
"pid": "4",
"level": 1
}
]
}
]

go 实现的核心函数

指定pid根节点和level根层标识

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func handle(pid string, level int8) []d {
var tree []d
for _, v := range data {
if v.Pid == pid {
var children []d
children = append(children, handle(v.ID, level+1)...)
tree = append(tree, d{
Level: level,
ID: v.ID,
Pid: v.Pid,
Children: children,
})
}
}
return tree
}

完整代码,粘贴即可运行

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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
package main

import (
"bytes"
"encoding/json"
"log"
"os"
)

type dataSource struct {
ID string `json:"id"`
Pid string `json:"pid"`
}

type d struct {
Level int8 `json:"level"`
ID string `json:"id"`
Pid string `json:"pid"`
Children []d `json:"children"`
}

var data = []*dataSource{
{
ID: "1",
Pid: "0",
},
{
ID: "2",
Pid: "1",
},
{
ID: "3",
Pid: "1",
}, {
ID: "4",
Pid: "0",
}, {
ID: "5",
Pid: "4",
},
{
ID: "6",
Pid: "4",
},
{
ID: "7",
Pid: "3",
},
{
ID: "8",
Pid: "3",
},
}

func handle(pid string, level int8) []d {
var tree []d
for _, v := range data {
if v.Pid == pid {
var children []d
children = append(children, handle(v.ID, level+1)...)
tree = append(tree, d{
Level: level,
ID: v.ID,
Pid: v.Pid,
Children: children,
})
}
}
return tree
}

func main() {
root := handle("0", 0)
b, _ := json.Marshal(root)

var out bytes.Buffer
err := json.Indent(&out, b, "", "\t")

if err != nil {
log.Fatalln(err)
}

out.WriteTo(os.Stdout)
}

输出结果

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
51
52
53
[
{
"level": 0,
"id": "1",
"pid": "0",
"children": [
{
"level": 1,
"id": "2",
"pid": "1",
"children": null
},
{
"level": 1,
"id": "3",
"pid": "1",
"children": [
{
"level": 2,
"id": "7",
"pid": "3",
"children": null
},
{
"level": 2,
"id": "8",
"pid": "3",
"children": null
}
]
}
]
},
{
"level": 0,
"id": "4",
"pid": "0",
"children": [
{
"level": 1,
"id": "5",
"pid": "4",
"children": null
},
{
"level": 1,
"id": "6",
"pid": "4",
"children": null
}
]
}
]

monaco-editor

关于编辑器的使用,网上一搜一大堆,这里不再赘述

这里主要介绍编辑器使用时,遇到的关键问题和痛点

语法解释器

需要使用webpack插件monaco-editor-webpack-plugin
全局注册window.MonacoEnvironment对象,供monaco编辑器内核调度
根据当前编辑器的语言模式,读取对应的语法解释器worker

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin');
new MonacoWebpackPlugin({
// 指定编辑器默认加载的语言
languages: ['json', 'javascript', 'typescript', 'yaml'],
// 自定义语言解释器worker
customLanguages: [
{
label: 'yaml',
// TODO: restore once https://github.com/pengx17/monaco-yaml/pull/48 is merged
entry: 'monaco-yaml/lib/esm/monaco.contribution',
worker: {
id: 'vs/language/yaml/yamlWorker',
// TODO: restore once https://github.com/pengx17/monaco-yaml/pull/48 is merged
entry: 'monaco-yaml/lib/esm/yaml.worker.js'
}
}
]
})

语法提示(低代码编辑使用)

参考

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
// validation settings
monaco.languages.typescript.javascriptDefaults.setDiagnosticsOptions({
noSemanticValidation: true,
noSyntaxValidation: false
});

// compiler options
monaco.languages.typescript.javascriptDefaults.setCompilerOptions({
target: monaco.languages.typescript.ScriptTarget.ES2016,
allowNonTsExtensions: true,
allowJs: true
});

// extra libraries
monaco.languages.typescript.javascriptDefaults.addExtraLib(`
declare class getValue {
/**
* 这是什么
*/
antd: {
message:{
success:()=>void;
}
},
}
`,
'filename/facts.d.ts'
);

代码编辑时,需要加上这个注释

1
2
3
4
5
6
/**
* @param {getValue} event
*/
function getValue(event) {
event.antd.message
}

虚拟dom和dom diff

虚拟dom的优点

  1. 减少dom操作
  • 虚拟dom可以将多次操作合并为一次操作
  • 虚拟dom可以借助dom diff算法,可以把多余的操作省掉
  1. 跨平台
  • ReactNative、小程序

虚拟dom的缺点

  1. 需要工程化解决方案支持

  2. 对于需要操作大量dom的场景,处理起来反而耗时,需要区别对待使用

泛型典型示例

函数输入值类型和输出值类型,映射关系

1
2
3
f('1') === 1

f(1) === '1'

即函数f

  1. 接收string类型时,返回number类型
  2. 接收number类型时,返回string类型

方法一

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
function f<T extends number | string>(
p: T
): T extends number ? string : T extends string ? number : null;

function f(p) {
let result = null;
if (typeof p === "string") {
// 经过一段逻辑处理,返回了number类型
result = Number(p);
}
if (typeof p === "number") {
// 经过一段逻辑处理,返回了string类型
result = String(p);
}
return result;
}

const res1 = f("1");
if (res1 === 1) {
}

if (res1 === "1") {
}

const res2 = f(2);
if (res2 === "2") {
}
if (res2 === 2) {
}

查看类型提示示意图

方法二

1
2
3
4
// 函数重载
function f(p:string):number;
function f(p:number):string;
function f(p:any):any;