如果要实现如Singleton、Lazy Initialization模式,那么你需要了解sync.Once,它可以用于保证如:只加载一次配置文件、只初始化一次数据库连接等,此外它还可以帮助实现更好的Plugin封装

当前go版本:1.24

快速上手

以下代码展示了如何使用sync.Once实现singleton模式

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

import (
"fmt"
"sync"
"time"
)

type Singleton struct {
data string
}

var instance *Singleton
var once sync.Once

func GetInstance() *Singleton {
once.Do(func() {
fmt.Println("Creating Singleton instance")
instance = &Singleton{data: "I'm the only one!"}
})
return instance
}

func main() {
for i := 0; i < 5; i++ {
go func() {
fmt.Printf("%p\n", GetInstance())
}()
}

// Wait for goroutines to finish
time.Sleep(3 * time.Second)
}
阅读全文 »

map/哈希表,是golang常用的数据结构之一,也充当set数据结构的存在,相对slice要复杂很多。从1.24开始,swiss table替代noswiss成为默认实现,swiss与noswiss区别在于,swiss使用开放地址法,noswiss使用拉链法

当前go版本:1.24

swiss map的开关放在文件src/internal/buildcfg/exp.go的函数ParseGOEXPERIMENT

数据结构

todo:文章图片待补充

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// table-groups是二维数组,算上slot的话是三维
//
// map_header -> table1 -> group1(8个slot)
// -> group2
// ...
// -> group127
//
// -> table2
// -> ...
// -> tableN
//
// 当hint<=8时,map_header直接指向一个group,全量扫描操作
// 当hint>8 and hint<=1024*7/8时,一个table,2~128个group
// 当hint>1024*7/8时,多个table,多个group
//
// hash高B位 - 用于定位table
// h1-hash高57位 - 用于定位group
// h2-hash低7位 - 用于匹配hash,类似tophash

核心数据结构包括Map、table、groupsReference、groupReference,具体如下

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
// src/internal/runtime/maps/map.go
type Map struct {
used uint64 // 已用slot,当数据量<=8时用来替换table的used使用
seed uintptr // 哈希函数种子
dirPtr unsafe.Pointer // table数组指针/group指针
dirLen int // table数组大小
globalDepth uint8 // log2(dirLen)(相当于旧版的B)
globalShift uint8 // 64-globalDepth,高B位用做table的索引
writing uint8 // 是否正在写入,乐观锁
clearSeq uint64 // 执行过多少次clear,扩容时,获取数据判断用
}

// src/internal/runtime/maps/table.go
type table struct {
used uint16 // 已用slot,最多能写入1024*7/8=896个slot
capacity uint16 // slot容量 <=1024(由hint算出,2的乘方向上取整)
growthLeft uint16 // 可用slot,与used相反,初始值最大为896
localDepth uint8 // >globalDepth?分裂/遍历时使用
index int // 上层directory数组中的index(-1-过期,作用类似localDepth)
groups groupsReference // group数组,8个slot为一组,最多1024/8=128组
}

// src/internal/runtime/maps/group.go
type groupsReference struct {
data unsafe.Pointer // group数组,8个slot为一组,具体结构看下方
lengthMask uint64 // 长度固定为2^N,因此mask=2^N-1
}

type groupReference struct {
// 结构如下
//
// type group struct {
// ctrls ctrlGroup // 8个8bit的ctrl,共三种状态
// slots [abi.SwissMapGroupSlots]slot // 8个slot(key/value对)
// }
//
// 三种状态如下:
// empty: 1 0 0 0 0 0 0 0
// deleted: 1 1 1 1 1 1 1 0
// full: 0 h h h h h h h // h represents the H2 hash bits
//
// type slot struct {
// key typ.Key // 键
// elem typ.Elem // 值
// }
data unsafe.Pointer // ctrls数组+slots数组
// 内存布局如下(C语言开发者真的很喜欢这种内存布局啊)
// | ctrls | slots |
// |ctrl7|...|ctrl0|slot0|...|slot7|
}
阅读全文 »

map/哈希表,是golang常用的数据结构之一,也充当set数据结构的存在,相对slice要复杂很多。从1.24开始,swiss table替代noswiss成为默认实现,swiss与noswiss区别在于,swiss使用开放地址法,noswiss使用拉链法

当前go版本:1.23

数据结构

todo:文章图片待补充

1
2
3
4
5
6
7
8
9
10
11
12
13
// 
// hmap -> oldbuckets
// -> buckets -> bmap0(8个key/value对)
// -> bmap1 -> overflow0(bmapX) -> overflow1(bmapZ)
// -> ...
// -> bmapM -> overflow0(bmapY)
// -> bmapN
//
// -> extra.overflow -> bmapX(pre-alloc)
// -> bmapY(pre-alloc)
// -> ...
// -> bmapZ(new-alloc)
//

map的数据结构如下所示

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
// src/runtime/map.go
type hmap struct {
count int // 元素数量-key/value对
flags uint8 // 1-iter正在使用buckets字段 2-iter正在使用oldbuckets字段 4-正在写入 8-同等大小扩容
B uint8 // buckets数量=(loadFactor * 2^B)
noverflow uint16 // 统计溢出buckets的数量,当B大于15时不是精确值
hash0 uint32 // 哈希种子,计算hash用
buckets unsafe.Pointer // buckets数组
oldbuckets unsafe.Pointer // 旧buckets数组,扩容时用
nevacuate uintptr // 下一个未疏散的bucket索引
clearSeq uint64 // 执行过多少次clear
extra *mapextra // 可选字段,不是每个map都需要,同时也需要为gc考虑
}

// key/value都不是指针才会使用下面几个字段
type mapextra struct {
overflow *[]*bmap // 溢出buckets,buckets链接使用
oldoverflow *[]*bmap // 溢出buckets,oldbuckets链接使用,扩容时
nextOverflow *bmap // 下一个可用/未使用overflow的索引
}

// bucket结构,可存储8个key/value对及其他数据,编译时自动补充其余结构,真实结构如下
// A "bucket" is a "struct" {
// tophash [abi.MapBucketCount]uint8 // hash的高8位
// keys [abi.MapBucketCount]keyType // 所有key
// elems [abi.MapBucketCount]elemType // 所有value
// overflow *bucket // 下一个溢出桶指针
// }
// 详细可见MapBucketType函数(src/cmd/compile/internal/reflectdata/reflect.go)
type bmap struct {
// abi.MapBucketCount=8
// 0-默认状态 1-已删除 2-疏散到x 3-疏散到y 4-不需要疏散 5-guard xyz(>5)-正常tophash值
// 状态转移如下:
// 0/xyz -> 2/3/4 => 如果所有bucket都疏散完毕,会一次性清空释放
// 0/xyz -> 1 删除 => 如果idx+1的状态是0,则向前寻找状态为1数据并改为0
tophash [abi.MapBucketCount]uint8
}

上面数据结构中中比较关键的是

  • hmap - header部份,var变量存储的也是这部份
  • buckets - 指向一片连续内存区域,bucket数组
  • bmap - bucket的具体实现,可以存储8个key/value对,尾部overflow是溢出bucket的指针
阅读全文 »

slice/切片-动态数组,golang常用的数据结构之一,相对于数组,slice可以追加元素,在容量不足时自动扩容

当前go版本:1.24

数据结构

todo:文章图片待补充

slice数据结构如下所示

1
2
3
4
5
6
// src/runtime/slice.go
type slice struct {
array unsafe.Pointer
len int
cap int
}

其中

  • array - 指向一片连续内存区域的第一个元素
  • len - 已有元素数量
  • cap - 可容纳元素总数量

初始化

slice初始化方式有三种

  1. 使用字面量创建新切片
  2. 使用关键字make创建切片
  3. 通过下标获取数组或切片的一部份
1
2
3
4
5
6
// len=3 cap=3
var v1 = []int{1, 2, 3}
// len=10 cap=10
var v2 = make([]int, 10) // var v2 = make([]int, 0, 10)
// len=4 cap=9
var v3 = v2[1:5]
阅读全文 »

2026.03.04更新: rootless太耗资源了,没必要,不喜欢。nerdctl要么要rootless,要么sudo,很烦,先放弃了。

Ubuntu 24.04为例,安装nerdctl以及containerd

前置准备

ubuntu

  • 安装uidmap
1
sudo apt update && sudo apt install -y uidmap
  • AppArmor配置

由于懒人安装方式跟单独安装方式bin目录不一致,配置内容会有些许差异,放在下面章节各自介绍

依赖包

阅读全文 »

简单介绍下macOS当前docker运行环境的最新选择:colima,一个开源产品,使用体验与vagrant类似,感觉非常不错

colima

根据colima官方仓库指导安装colima

1
brew install colima

colima默认的runtime是docker,因为k8s已经剥离了docker,只保留containerd,所以为了跟k8s保持一致,另外也不想再多安装一个软件,就选择了只用containerd

但如果你使用了docker compose,那么最好还是根据教程安装docker以及docker-compose

1
2
3
4
5
6
7
8
9
10
11
12
13
# 拉取镜像并创建vm
colima start --cpu 1 --memory 2 --disk 50 --runtime containerd
# 这里还可以开启kubernetes支持
# colima start --cpu 4 --memory 8 --disk 100 --runtime containerd --kubernetes

# 查看vm列表
colima list

# 连接vm
colima ssh

# 安装nerdctl用于支持containerd
colima nerdctl install
阅读全文 »

MySQL 9以及PostgreSQL 17为例,从Web开发者的角度出发,列举两个数据库在使用方面的差异,仅介绍与CRUD依赖的操作,存储过程/主从复制暂不介绍

账号管理

管理员

MySQL创建管理员账户操作如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
-- 1. 创建mydb_admin管理员账户
-- 格式:`CREATE USER 'username'@'host' IDENTIFIED WITH authentication_plugin BY 'password';`
CREATE USER 'mydb_admin'@'%' IDENTIFIED WITH caching_sha2_password BY 'your_password';

-- 2. 授权数据库/表访问权限
-- 格式:`GRANT PRIVILEGE ON database.table TO 'username'@'host';`
GRANT ALL PRIVILEGES ON mydb.* TO 'mydb_admin'@'%';
-- 如果不清楚有哪些权限,执行
-- SHOW PRIVILEGES;

-- 3. 立即生效
FLUSH PRIVILEGES;

-- 4. 查看账户列表核对
SELECT * FROM mysql.user;
-- SELECT * FROM mysql.user WHERE User='mydb_admin' AND Host='%';

-- 5.查看账户授权
SHOW GRANTS FOR 'mydb_admin'@'%';

PostgreSQL创建管理员账户操作如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
-- 1. 创建mydb_admin管理员账户
CREATE ROLE mydb_admin LOGIN PASSWORD 'your_password';

-- 2. 授权数据库/表访问权限(需要多条语句)
GRANT ALL PRIVILEGES ON DATABASE mydb TO mydb_admin;
-- 如果不想让账号访问其他db
-- REVOKE CONNECT ON DATABASE otherdb1 FROM PUBLIC;

-- 所有跟public相关的授权都需要先切换到指定数据库
\c mydb
-- 授权public
GRANT ALL PRIVILEGES ON SCHEMA public TO mydb_admin;
-- 后续所有变更自动授权(由super admin执行)
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO mydb_admin;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO mydb_admin;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON FUNCTIONS TO mydb_admin;

-- 3. 删除角色
-- DROP OWNED BY mydb_admin;
-- DROP ROLE mydb_admin;
阅读全文 »

记录下如何移除swift文件的头部注释

注意,zsh要在空格处添加反斜杠 \,否则无法生成正确的文件路径

清空模板注释内容(可选)

以下操作可以先备份文件

修改IDETemplateMacros.plist文件,移除注释内容

1
vi ~/Library/Developer/Xcode/UserData/IDETemplateMacros.plist

找到FILEHEADER,将string的内容清空

Swift File

进入Xcode目录

1
cd ~/Library/Developer/Xcode/

添加模板No Comment Swift File.xctemplate,该模板会展示在Xcode的文件目录列表

1
mkdir -p Templates/File Templates/MultiPlatform/Source/No Comment Swift File.xctemplate

进入模板目录

1
cd Templates/File Templates/MultiPlatform/Source/No Comment Swift File.xctemplate

复制原swift file模板数据

1
cp /Applications/Xcode.app/Contents/Developer/Library/Xcode/Templates/File Templates/MultiPlatform/Source/Swift File.xctemplate/* ./

修改___FILEBASENAME___.swift,将第一行头部注释部分移除

阅读全文 »

默认情况下,k8s创建的服务是无法对公网提供访问的,如果要从公网访问k8s服务,一般有三种方式,NodePort、LoadBalancer、Ingress,下面简单介绍NodePort、LoadBalancer,着重讲解Ingress

本文使用的deployment同官方教程nginx-deployment,图片是从ingress-nginx官方文档拷贝过来借用,如侵必删

NodePort

nodeport

如上图所示,客户端访问集群内任意机器端口30100,k8s自动将请求转发到对应的服务

  1. 创建NodePort类型的service(不指定nodeport的情况下,默认从30000-32767中挑选一个端口使用)
1
kubectl expose deployment nginx-deployment --type="NodePort" --port 80
  1. 查看service kubectl get svc,输出如下
1
2
3
NAME               TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 12d
nginx-deployment NodePort 10.110.255.135 <none> 80:31402/TCP 4s
  1. 验证

外部客户端先访问集群内任意节点端口31402,然后节点会转发到与端口关联的k8s内部服务nginx-deployment。

在物理机上访问服务curl http://vm1:31402,对比在虚拟机如vm1上执行curl http://10.110.255.135,两者的输出是一致的

  1. 删除service
1
kubectl delete svc nginx-deployment

注意:考虑到线上一般是直接通过域名访问,如果不想在域名后还要添加端口访问服务,如www.noname.io:31402,那么还需要使用nginx/haproxy建立一个反向代理的网关进行管理

阅读全文 »

在Kubernetes官网的学习教程:wordpress中,我尝试了手动创建pv并绑定pvc,但是每次申请pvc都要手动添加pv并绑定是一件很麻烦且很容易出错的事情,官方也早就意识到这个问题,提出了动态存储分配,只需要通过storageclass即可实现

本文使用了nfs作为集群存储用于实践,线上建议考虑使用ceph等分布式文件存储系统,nfs搭建教程参考:How to Install NFS Server and Client on Ubuntu 22.04

安装nfs provisioner

  1. 确认nfs服务器在集群节点中可以正常访问
1
2
3
4
# 安装客户端
sudo apt install nfs-common
# 执行命令挂载,确认可正常使用
sudo mount 192.168.0.105:/mnt/nfs_share /mnt/nfs_clientshare
  1. 新建一个目录如nfs-example,创建kustomization.yaml文件

鉴于gfw的影响,建议下载github.com/kubernetes-sigs/nfs-subdir-external-provisioner仓库,并将deploy文件夹提取出来,里面主要是账户创建、角色绑定、命名空间、deployment等配置

1
2
3
4
5
# kustomization.yaml
namespace: nfs-provisioner
resources:
# - github.com/kubernetes-sigs/nfs-subdir-external-provisioner//deploy
- ./deploy
  1. 创建命名空间nfs-provisioner
1
2
3
4
5
# namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
name: nfs-provisioner
阅读全文 »
0%