k8s源码编译调试

最近赋闲在家等考科三,闲来无事想着本地编译下k8s玩玩(毕竟云原生时代最重要的一个平台)。

  • 环境:m1使用multipass创建的一个ubuntu虚拟机
  • 代码版本:v1.24.0

需要的工具包

1
2
3
4
5
6
7
8
sudo apt install build-essential
sudo apt install -y gcc g++ gawk autoconf automake python3-cmarkgfm
sudo apt install -y acl libacl1-dev
sudo apt install -y attr libattr1-dev
sudo apt install -y libxxhash-dev
sudo apt install -y libzstd-dev
sudo apt install -y liblz4-dev
sudo apt install -y libssl-dev

第三方包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#jq
sudo apt-get install jq

#pyyaml
sudo apt install python3-pip
pip install pyyaml

#rsync
cd ~/Downloads
wget https://github.com/WayneD/rsync/archive/refs/tags/v3.2.4.tar.gz
tar -xf v3.2.4.tar.gz
cd rsync-3.2.4

./configure
make
sudo cp ./rsync /usr/local/bin/
sudo cp ./rsync-ssl /usr/local/bin/

etcd

1
2
3
4
5
6
7
8
9
ETCD_VER=v3.5.4  
curl -L https://storage.googleapis.com/etcd/${ETCD_VER}/etcd-${ETCD_VER}-linux-arm64.tar.gz -o /tmp/etcd-${ETCD_VER}-linux-arm64.tar.gz
mkdir ~/etcd
tar xzvf /tmp/etcd-${ETCD_VER}-linux-arm64.tar.gz -C ~/etcd --strip-components=1
rm -f /tmp/etcd-${ETCD_VER}-linux-arm64.tar.gz

sudo vim ~/.bashrc
添加 export PATH="/home/ubuntu/etcd:${PATH}"
source ~/.bashrc

安装docker及golang(golang 1.18)

代码下载

1
2
3
### 用的码云,速度快亿点
git clone https://gitee.com/mirrors/kubernetes.git
git checkout -b kube1.24 v1.24.0

启动单机版

1
2
#进入源码目录编译并启动k8s
sudo ./hack/local-up-cluster.sh

调试

1
2
export KUBECONFIG=/var/run/kubernetes/admin.kubeconfig
cluster/kubectl.sh

看到下面这个说明编译成功

新开控制台使用k8s.使用k8s命令前记得执行这个命令: export KUBECONFIG=/var/run/kubernetes/admin.kubeconfig (或是把这个加入环境变量)

部署nginx

外网访问自己刚启动的nginx,命令:cluster/kubectl.sh port-forward nginx 8080:80 –address 0.0.0.0

成功的!可以愉快的玩耍自己编译出来的k8s

springcloud容器化部署

springcloudAlibaba项目容器化部署(springcloudAlibaba项目的集成测试例子,推荐下这个项目。可以体验seata分布式事务 同rocketmq的集成等)。从新梳理各个 DevOps 工具的串联集成,从而落地一个企业级的持续交付流水线

  • 自动化部署工具:jenkins
  • 企业通用制品库: Artifactory 可以管理软件包 docker镜像 helm包,新时代DevOps 流水线比较实用的一个工具
  • 代码管理工具:gitee,本地有gitlab但太占资源了

jenkins流水线,一键部署

Artifactory管理docker镜像和helm(结合k8s,很方便回滚及团队内复用)

springcloudAlibaba项目

剥离自阿里巴巴官方的springcloud项目:spring-cloud-alibaba,用的分支是:2021.x(支持jdk8比较稳定),只保留了集成测试的项目。调整其目录结构符合企业级项目需要,修正了其docker文件错误并添加打包docker镜像及上传到Artifactory私服的脚本(不懂是否侵犯开源协议故未开源此项目)

k8s部署




总结:devpos结合k8s确实是大规模社会化生产的利器

本地安装k8s集群

参考文档: 安装 Kubernetes 集群

本文记录自己安装过程遇到的坑及处理办法

环境:

  • 两台centos7虚拟机
  • k8s版本v1.22

image.png

关闭swap

1
vim /etc/fstab

注释掉这一行 /dev/mapper/centos-swap swap swap defaults 0 0
image.png

然后重启机器

修改cgroup driver

1
2
3
4
5
6
7
8
vim  /etc/docker/daemon.json

内容:

{
"exec-opts": ["native.cgroupdriver=systemd"],
"registry-mirrors": ["https://ud6340vz.mirror.aliyuncs.com"]
}

重启docker

1
2
systemctl daemon-reload
systemctl restart docker

关闭窗口忘记加入master节点命令

1
#获取token
kubeadm token list
#获取sha256值
openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | openssl rsa -pubin -outform der 2>/dev/null | openssl dgst -sha256 -hex | sed 's/^.* //'

拼接出加入集群命令

1
kubeadm join master的ip:6443 --token token值 --discovery-token-ca-cert-hash sha256:sha256值

springboot项目启动后外网无法访问

添加参数 –address

1
2
##将本机8080端口转发至Pod的8080端口
kubectl port-forward springboot-app-f787c6646-rm9hr 8080:8080 --address 0.0.0.0

image.png

索引和Btree简介

索引和Btree简介

当我们考虑数据库的性能时,首先想到的是索引。 在这里,我们将研究数据库索引如何在数据库上工作。
Btree 是一种数据结构,将数据按排序顺序存储在其节点中。 我们可以将样本 Btree 表示如下:

image.png

Btree 存储数据,使得每个节点都包含按升序排列的键。 这些键中的每一个都具有对另外两个子节点的两个引用。 左侧子节点键小于当前键,右侧子节点键大于当前键。 如果单个节点有n个键,那么它最多可以有n+1个子节点。

为什么在数据库中使用索引

认为您需要将数字列表存储在文件中并在该列表中搜索给定的数字。 最简单的解决方案是将数据存储在数组中,并在新值出现时附加值。 但是,如果您需要检查给定值是否存在于数组中,则需要一一搜索所有数组元素并检查给定值是否存在。 如果你足够幸运,你可以在第一个元素中找到给定的值。 在最坏的情况下,该值可能是数组中的最后一个元素。 我们可以用渐近符号将这种最坏情况表示为 O(n)。 这意味着,如果您的数组大小最多为n,则您需要执行n+1次搜索才能在数组中找到给定值。

你怎么能减少这个搜索时间? 最简单的解决方案是对数组进行排序并使用二分查找来查找值。 每当您将值插入数组时,它都应该保持顺序。 从数组中间选择一个值开始搜索。 然后将所选值与搜索值进行比较。 如果选择的值大于搜索值,则忽略数组的左侧并搜索右侧的值,反之亦然。

image.png

在这里,我们尝试从已经排序好的数组 3,6,8,11,15,18 和 18 中搜索键 15。 如果您进行正常搜索,则从第五个位置的元素开始搜索将花费五个单位的时间。 但是,在二分搜索中,它只需要三个搜索。
如果我们将此二分查找应用于数组中的所有元素,那么它将如下所示。

image.png

看着眼熟? 它是一棵二叉树。 这是 Btree 的最简单形式。 对于二叉树,我们可以使用指针而不是将数据保存在排序数组中。 从数学上我们可以证明二叉树的最坏情况搜索时间是 O(log(n))。 二叉树的概念可以扩展为更广义的形式,称为 Btree。 Btree 不是为单个节点使用单个条目,而是为单个节点使用条目数组,并为每个条目引用子节点。 下面对每种预先描述的方法进行一些时间复杂度比较。

1
2
3
4
5
6
7
┌────────────────┬───────────┬────────────┬───────────┐
│ Type │ Insertion │ Deletion │ Lookup │
├────────────────┼───────────┼────────────┼───────────┤
│ Unsorted Array │ O(1)O(n)O(n)
│ Sorted Array │ O(n)O(n)O(log(n)) │
│ Btree │ O(log(n)) │ O(log(n))) │ O(log(n)) │
└────────────────┴───────────┴────────────┴───────────┘

B+tree 是另一种用于存储数据的数据结构,看起来与 Btree 几乎相同。 B+tree 的唯一区别是它在叶子节点上存储数据。 这意味着所有非叶节点值再次在叶节点中重复。 下面是一个示例 B+树。

image.png
这里 13、30、9、11、16、38 个非叶值再次在叶节点中重复。 你能在叶子节点看到这棵树的特长吗?
是的,叶节点包括所有值,所有记录都按排序顺序排列。 B+tree 的特点是,您可以进行与 Btree 相同的搜索,此外,如果我们如下放置一个指向每个叶节点的指针,您可以遍历叶节点中的所有值。

image.png

数据库中如何使用索引?

当 Btree 涉及到数据库索引时,这个数据结构变得有点复杂,不仅有一个键,而且还有一个与键关联的值。 该值是对实际数据记录的引用。 此键和值一起称为有效负载。
假设以下三列表需要存储在数据库中。

Name Marks Age
Jone 5 28
Alex 32 45
Tom 37 23
Ron 87 13
Mark 20 48
Bob 89 32

首先,数据库为每个给定记录创建一个唯一的随机索引(或主键),并将相关行转换为字节流。 然后它将每个键和记录字节流存储在 B+树上。 这里,随机索引用作索引的键。 密钥和记录字节流统称为有效载荷。 得到的 B+tree 可以表示如下。

image.png
在这里可以看到,所有记录都存储在 B+tree 的叶子节点中,索引作为创建 B+tree 的 key。 没有记录存储在非叶节点上。 每个叶节点都引用了树中的下一条记录。 数据库可以通过使用索引或顺序搜索来执行二分查找,方法是仅通过叶节点来搜索每个元素。
如果没有使用索引,那么数据库读取这些记录中的每一个以找到给定的记录。 启用索引后,数据库会为表中的每一列创建三个 Btree,如下所示。 这里的键是用于索引的 Btree 键。 索引是对实际数据记录的引用。

image.png

使用索引时首先,数据库在对应的 Btree 中搜索给定的键,并在 O(log(n)) 时间内获得索引。 然后它在 O(log(n)) 时间内使用已经找到的索引在 B+tree 中执行另一次搜索并获取记录。

数据库页实现

Btree 和 B+tree 中的每个节点都存储在 Pages 中。 页面大小固定。 页面具有从一开始的唯一编号。 通过使用页码,一个页面可以是对另一个页面的引用。 在页面的开头,页面元详细信息,例如最右边的子页号、第一个空闲单元格偏移量和存储的第一个单元格偏移量。 数据库中可以有两种类型的页面。

  • 用于索引的页面:这些页面仅存储索引和对另一个页面的引用。
  • 存储记录的页面:这些页面存储实际数据,页面应该是叶子页面。

总结

数据库应该有一种有效的方式来存储、读取和修改数据。 Btree 提供了一种高效的方式来插入和读取数据。 在实际的Database实现中,Database同时使用Btree和B+tree来存储数据。 Btree 用于索引,B+tree 用于存储实际记录。 B+tree 除了二叉搜索之外还提供顺序搜索功能,这使数据库可以更好地控制在数据库中搜索非索引值。

翻译自:https://github.com/madushadhanushka/simple-sqlite/tree/master/blog

polardb源码分析

编译并启动galaxyengine(polardb存储层)

我是搭建了一个虚拟机-centos7.6,以下搭建步骤摘录自官方文档

cmake . \ -DFORCE_INSOURCE_BUILD=ON \ -DCMAKE_BUILD_TYPE="Debug" \ -DSYSCONFDIR="/u01/mysql" \ -DCMAKE_INSTALL_PREFIX="/u01/mysql" \ -DMYSQL_DATADIR="/u01/mysql/data" \ -DWITH_BOOST="./extra/boost/boost_1_70_0.tar.gz"

1
2
make -j8
make install
  • 启动galaxyengine
1
2
3
mkdir -p /u01/my3306/{data,log,run,tmp,mysql}
/u01/mysql/bin/mysqld --defaults-file=my.cnf --initialize-insecure
/u01/mysql/bin/mysqld --defaults-file=my.cnf

注意要以非root用户执行启动

这样就启动了galaxyengine(可以认为就是启动了一个mysql, 计算层galaxysql要与一个mysql进行交互)

导入galaxysql(polardb计算层)到idea

导入polardx-rpc模块

polardb把galaxysql的rpc模块拆分出galaxyglue项目https://github.com/ApsaraDB/galaxyglue.git,所以要把galaxysql导入本地idea需要做以下操作:

1
2
3
4
5
6
7
8
# 移动rpc代码到galaxysql目录下的polardbx-rpc
mv galaxyglue galaxysql/polardbx-rpc

# 进入代码目录
cd galaxysql/

# 编译打包
mvn install -D maven.test.skip=true -D env=release

导入idea

配置信息-server.properties

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# PolarDB-X 服务端口
serverPort=8527
# PolarDB-X RPC 端口
rpcPort=9090
# MetaDB地址
metaDbAddr=192.168.1.8:4886
# MetaDB私有协议端口
metaDbXprotoPort=32886
# MetaDB用户
metaDbUser=my_polarx
metaDbName=polardbx_meta_db_polardbx
# PolarDB-X实例名
instanceId=polardbx-polardbx
metaDbPasswd=123456
initializeGms=false
dnList=192.168.1.8:4886
polarxPasswd=123456
rootPasswd=Cliu123#
galaxyXProtocol=1
dnPasswdKey=asdf1234ghjk5678
storageDbXprotoPort=32886
polarxRootUser=polardbx_root
polarxRootPasswd=123456
allowManagerLogin=1

先设置initializeGms=true运行程序,初始化完成后把initializeGms=false然后再起服务。

浅说零拷贝

传统IO问题

传统的IO将一个文件通过socket写出

1
2
3
4
5
6
7
8
File f = new File("data.txt");
RandomAccessFile file = new RandomAccessFile(f, "r");

byte[] buf = new byte[(int) f.length()];
file.read(buf);

Socket socket = ...;
socket.getOutputStream().write(buf);

内部工作流程

  1. java 本身并不具备IO读写能力,因此read方法调用后,要从java程序的用户态切换至内核态,去调用操作系统(Kernel)的读能力,将数据读入内核换中去。这期间用户线程阻塞,操作系统使用DMA(Direct memory Access)来实现文件读,其间也不会使用cpu(DMA也可以理解为硬件单元,用来解放cpu完成文件IO)
  2. 从内核态切换回用户态,将数据从内核缓冲区读入用户缓冲区(即byte[] buf),这期间cpu会参与拷贝,无法利用DMA
  3. 调用write方法,这时将数据从用户缓冲区(byte[] buf)写入socket缓冲区,cpu会参与拷贝
  4. 接下来要向网卡写数据,这项能力java又不具备,因此又得从用户态切换至内核态,调用操作系统的写能力,使用DMA将socket缓冲区的数据写入网卡,不会使用cpu

java的IO实际不是物理设备级别的读写,而是缓存的复制,底层的真正读写是操作系统来完成的

  • 用户态与内核态的切换发生了3次,这个操作比较重量级
  • 数据拷贝了4次

NIO优化

通过DirectByteBuf

  • ByteBuffer.allocae(10) -返回HeapByteBuffer,表示java内存
  • ByteBuffer.allocaeDirect(10) -返回DirectByteBuffer,使用的是操作系统内存

java可以使用DirectByteBuf将堆外内存映射到jvm内存中来直接访问使用

  • 这块内存不受jvm垃圾回收的影响,因此内存地址固定,有助于IO读写
  • java中的DirectByteBuf对象仅维护了此内存的虚引用,内存回收分成两步
    • DirectByteBuf对象被垃圾回收,将虚引用加入引用队列
    • 通过专门线程访问引用队列,根据虚引用释放堆外内存
  • 减少了一次数据拷贝,用户态与内核态的切换次数没有减少

进一步优化(底层采用了linux2.1后的提供的sendFile方法),java中对应着两个channel调用transferTo/TransferFrom方法拷贝数据

1.java调用transferTo方法后,要从java程序的用户态切换至内核态,使用DMA
将数据读入内核缓冲区,不会使用cpu

2.数据从内核缓冲区传输到socket缓冲区,cpu会参与拷贝

3.最后使用DMA将socket缓冲区的数据写入网卡,不会使用cpu

进一步优化( Linux2.4)

1.java调用transferTo方法后,要从java程序的用户态切换至内核态,使用DMA将数据读入内核缓冲区,不会使用cpu

2.只会将一些offset和length信息拷入socket缓冲区,几乎无消耗

3.使用DMA将内核缓冲区的数据写入网卡,不会使用cpu

整个过程仅只发生了一次用户态与内核态的切换,数据拷贝了2次。所谓的【零拷贝】,并不是真正的无拷贝,而是在不会拷贝到重复数据到jvm内存中,零拷贝的优点有

  • 更少的用户态与内核态的切换
  • 不利用cpu计算,减少cpu缓存伪共享
  • 零拷贝适合小文件传输

java线程运行机制初探

创建线程demo

1
2
3
4
5
6
7
8
9
10
11
public static void main(String[] args) throws InterruptedException {

Thread thread = new Thread(() -> {

System.out.println("搵食艰难");

});

thread.start();

}

启动线程:start方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public synchronized void start() {
...
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */

}
}
}

随后调用start0()方法,这是一个jvm本地方法,最终是通过操作系统来创建线程,关键代码在 os_bsd.cpp文件(笔者用的mac调试jvm源码,Windows是os_windows.cpp)

avatar

ThreadState state; 就是定义了线程的状态(面试偶尔会问线程流转状态o(╯□╰)o,要记记),是通过c的枚举标识的:

1
2
3
4
5
6
7
8
9
10
11
enum ThreadState {
ALLOCATED, // Memory has been allocated but not initialized
INITIALIZED, // The thread has been initialized but yet started
RUNNABLE, // Has been started and is runnable, but not necessarily running
MONITOR_WAIT, // Waiting on a contended monitor lock
CONDVAR_WAIT, // Waiting on a condition variable
OBJECT_WAIT, // Waiting on an Object.wait() call
BREAKPOINTED, // Suspended at breakpoint
SLEEPING, // Thread.sleep()
ZOMBIE // All done, but not reclaimed yet
};

有没有觉得有点眼熟~ 最关键的步骤是通过操作系统函数创建系统线程:

1
int ret = pthread_create(&tid, &attr, (void* (*)(void*)) java_start, thread);

pthread_create参数含义:

  • 第一个参数:新创建的线程ID指向的内存单元
  • 第二个参数:线程属性
  • 第三个参数:新创建的线程从start_rtn函数的地址开始运行
  • 第四个参数: 若上述函数需要参数,将参数放入结构中并将地址作为arg传入

mysql源码编译使用clion调试

国庆假期闲来无事,编译一个mysql玩玩。参考:
Mac Clion MySQL 8.0 源码调试环境搭建

因为所接触的系统大多没升级到mysql8.0,所以使用的mysql源码版本是 mysql-5.7.23.下载链接:

1
https://cdn.mysql.com//Downloads/MySQL-5.7/mysql-boost-5.7.23.tar.gz

第一步(进入mysql源码目录)

1
2
3
4
5
6
cmake -DCMAKE_INSTALL_PREFIX=/Volumes/longmao/openProject/sc/mysql_data/mysql-5.7.23-rc 
-DMYSQL_DATADIR=/Volumes/longmao/openProject/sc/mysql_data/mysql-5.7.23-rc/data
-DSYSCONFDIR=/Volumes/longmao/openProject/sc/mysql_data/mysql-5.7.23-rc
-DMYSQL_UNIX_ADDR=/Volumes/longmao/openProject/sc/mysql_data/mysql-5.7.23-rc/data/mysql.sock
-DWITH_DEBUG=1 -DDOWNLOAD_BOOST=1
-DWITH_BOOST=/Volumes/longmao/openProject/sc/boost_1_59_0

第二步

1
make -j 4

第三步

1
make install -j 4

第四步-初始化数据库

1
2
3
cd /Volumes/longmao/openProject/sc/mysql_data/mysql-5.7.23-rc/bin

./mysqld --basedir=/Volumes/longmao/openProject/sc/mysql_data/mysql-5.7.23-rc --datadir=/Volumes/longmao/openProject/sc/mysql_data/mysql-5.7.23-rc/data --initialize-insecure --user=longmao

clion启动mysql服务

连接mysql

1
mysql -h127.0.0.1 -P3306 -uroot

密码为空

即可在clion中开启断点调试,了解mysql运行机理

从0到1实现一个支付系统

最近离职在家,闲来无事,研究如何实现一个支付系统

服务划分

  • dubbo服务对应端口
工程 端口 描述
pay-service-account 20801 账户服务
pay-service-bank 20802 银行管理服务
pay-service-banklink 20892 银行后置服务
pay-service-boss 20804 运营服务
pay-service-fee 20807 商户计费服务
pay-service-limit 20809 交易限制服务
pay-service-notify 20822 通知服务
pay-service-payrule 20811 支付规则服务
pay-service-remit 20813 打款服务
pay-service-report 20815 报表服务
pay-service-settlement 20816 结算服务
pay-service-trade 20817 交易服务
pay-service-user 20818 用户服务
  • web服务对应端口
工程 端口 描述
pay-web-boss 8083 运营管理系统
pay-web-gateway 8084 支付网关
pay-web-notify-receive 8086 通知消息接收
pay-web-portal 8085 门户系统
pay-web-trade 8087 交易系统

技术架构

管理

  • maven依赖和项目管理
  • git版本控制
  • Jenkins持续构建

后端

  • IoC容器 Spring
  • web框架 SpringMvc
  • orm框架 Mybatis
  • rpc框架 Dubbo
  • 任务调度框架 quartz
  • 缓存 redis
  • 数据源 druid
  • 日志 slf4j+log4j2
  • Json jackson
  • kaptcha 验证码
  • jsp 模板视图

前端

  • jquery js框架
  • easyui 界面框架
  • zTree 树框架

分布式事务:使用tcc-transaction框架

运营管理后台

gitlab源码管理

jenkins服务自动化部署

项目结构

包括dubbo服务启动脚本

待完善点(或者说不懂做)

  • 风控系统:只要老板不想把底裤都赔掉,那就必须上风控。可对互联网公司来说,风控是一个谜一般的话题,无论是对风控专家还是IT工程师而言。机器学习,深度学习,规则推理,随机森林….这些只想说还不知道怎么玩~~
  • 对账系统:每一笔交易,都要做到各参与者的记录能够吻合,没有偏差。对账系统的工作,是发现有差异的记录,即轧帐;然后通过人工或者自动的方式,解决这些差异,即平帐。

龙猫云消息推送系统

系统架构

项目结构

  • mpush:开源的实时消息推送系统,基于该项目改造了其中消息推送流程,使用pulsar订阅推送的消息,作为一个broker
  • push-admin:使用spring-boot搭建的消息推送管理后台
  • alloc:是针对client提供的一个轻量级的负载均衡服务,每次客户端在链接broker之前都要调用下该服务
  • mpush-android:android客户端

使用的开源项目

  • netty
  • mpush
  • pulsar:存储与计算分离的新一代消息中间件
  • herdb:HerdDB 一个JVM-embeddable的分布式数据库,内嵌在broker里使用

功能演示

  • 消息推送管理后台

  • Android客户端

未来计划

  • 完成应用管理功能:用户可以创建多个应用,给应用分配appKey
  • 数据统计:接入新设备统计、消息推送记录、消息到达率统计、消息点击率统计
  • 新建龙猫云推送管理平台:应用计费统计等(计划是一个推送云平台产品,功能待定~~)
  • 推送sdk:建设统一sdk,拿分配到的appKey接入龙猫云推送平台