第十九章 说过的话就一定要做到-redo日志

一、事先说明

本章会频繁提到之前说的InnoDB记录行格式、页面格式、索引原理、表空间组成等基础知识

二、redo日志是什么

InnoDB以页为单位进行管理存储空间的,

一、准备工作

MySQL中负责对表进行数据的读取写入工作的部分是存储引擎

二、InnoDB页简介

InnoDB是一个将表中数据存储到磁盘的存储引擎。InnoDB是将数据划分为若干页,以页作为磁盘和内存之间交互的基本单位

页的默认大小为16KB

三、InnoDB行格式

四种行格式

1、指定行格式的语法

创建表或者修改表的语句中去实现

2、COMPACT行格式

(1)记录额外信息

  • 变长字段的长度列表
  • NULL值列表
  • 记录头信息

(2)记录真实数据

InnoDB表主键生成策略:优先使用用户自定义的主键;如果用户未定义主键,选取一个不允许为NULL的UNIQUE键作为主键;若都没有,则会为表添加一个row_id隐藏列作为主键

(3)CHAR(M)列的存储格式

3、REDUNDANT行格式

(1)字段长度偏移列表

(2)记录头信息

(3)记录头信息中的1byte_offs_flag的值是怎么选择的

(4)REDUNDANT行格式中的NULL值的处理

(5)CHAR(M)列的存储格式

4、溢出列

(1)溢出列

(2)产生溢出列的临界点

5、DYNAMIC行格式和COMPRESSED行格式

四、总结

页是InnoDB中磁盘与内存交互的基本单位,也是InnoDB管理存储空间的基本单位,默认大小为16KB

接下来探讨的话题将是规模方面(Scale)的,有请Docker Swarm登场

概括来说,Swarm有两个核心组件:

  • 安全集群
  • 编排引擎

一、Docker Swarm-简介

Docker Swarm包含两个方面:一个企业级的Docker安全集群、一个微服务应用编排引擎

Docker的核心思想就是将应用整合到容器中,并且能在容器中运行

Go语言基础-包(Packet)

Go语言源码复用建立在包的基础之上

Go语言的包与文件夹一一对应,所有与包有关的操作必须依赖于工作目录(GOPATH)

一、工作目录(GOPATH)

GOPATH是GO语言使用的一个环境变量,使用绝对路径提供项目的工作目录。

1、使用命令行查看GOPATH

1
go env

执行上面的指令可以得到以下输出(部分)

1
2
3
4
5
6
7
8
GO111MODULE="on"
GOARCH="arm64"
GOBIN="/opt/homebrew/Cellar/go/1.18.1/bin"
GOCACHE="/Users/xietingyu/Library/Caches/go-build"
GOENV="/Users/xietingyu/Library/Application Support/go/env"
......
GOPATH="/Users/xietingyu/go"
......
  • GOARCH:目标处理器架构
  • GOBIN:编译器和链接器安装位置

就不一一解释了,由上命令行输出可见,GOPATH路径为:/Users/xietingyu/go

2、使用GOPATH的工程结构

代码保存在$GOPATH/src

工程经过编译后的二进制文件在$GOPATH/bin

生成的中间缓存文件在$GOPATH/pkg

3、设置和使用GOPATH

(1)设置当前目录为GOPATH
1
export GOPATH=`pwd`
(2)建立GOPATH的源码路径
1
mkdir -p src/hello
  • -p:表示递归创建·文件夹
(3)添加main.go文件

先进入src/hello文件夹

1
cd src/hello

使用vim创建文件

1
vim hello.go

写入以下代码

1
2
3
4
5
6
7
package main

import "fmt"

func main() {
fmt.Println("hello")
}
(4)编译运行
1
go install hello

bin目录下会有名为hello的可执行文件

1
./hello

这节课程能学到的啥?

一、接入问题引入

1、经典问题引入

输入域名到网页加载出来,经历了哪些过程?

  • 域名解析
  • TCP建立连接
  • SSL握手
  • ……

DNS -> TCP -> TLS -> HTTP请求

2、字节接入架构

《》

二、企业接入升级打怪之路

1、使用域名系统

(1)Host管理

  • 流量和负载
  • 名称冲突
  • 时效性

(2)使用域名系统

《》

解决Host管理的问题

关于域名空间:

顶级域gTLD:gov、edu、com、mil、org等

域名报文格式:

《》

(3)域名的购买

各大云厂商可以购买

(4)如何开放外部用户访问

《》

2、自建DNS服务器

(1)问题背景

  • 效率低下
  • 容易被攻击
  • 容易出问题
  • 扩大公司自己的影响力

(2)DNS查询过程

深入理解DNS解析过程

(3)DNS记录类型

(4)权威DNS系统架构

Q:企业需要那种DNS?

A:权威DNS

常见的开源DNS:bind、nsd、knot、coredns

  • DNS Query
  • DNS response
  • DNS Update
  • DNS Notify
  • DNS XFR

3、接入HTTPS协议

(1)问题背景

  • 出现白页
  • 返回403
  • 页面弹出广告

HTTP明文传输,所以使用HTTPS

(2)对称加密和非对称加密

  • 对称加密:一份密钥

《》

  • 非对称加密:公钥和私钥

《》

(3)SSL通信过程

  • client random
  • server random
  • premaster secret
  • 加密算法协商

得到密钥:session key

(4)证书链

client收到证书会进行验证:

  • 颁发机构是否可信
  • 与实际访问域名是否一致
  • 检查数字签名是否一致
  • 检查证书的有效期
  • 检查证书的撤回状态

证书链验证签名

证书摘要信息 - 数字签名 - 上级CA公钥

(5)使用HTTPS

《》

4、接入全站加速

(1)问题背景

  • 源站容量低
  • 报文经过网络设备越多,丢包、劫持、mtu
  • 自主选路网络链路长

(2)解决方案

  • 源站容量

    后端机器扩容、静态加速缓存

  • 网络传输

    动态加速DCDN

全站加速:静态加速 + 动态加速

(3)静态加速CDN

缓存静态文件

  • 缓解运营商之间互联的瓶颈造成的影响
  • 减轻了各省出口带宽的压力
  • 优化网络热点内容分布

(4)动态加速DCDN

针对 POST 等非静态请求等不能在用户边缘缓存的业务,基于智能选路技术,从众多回源线路中择优选择一条线路进行传输

(5)DCDN原理

CDN的原理

5、Nginx

(1)反向代理

《》

(2)内部架构

《》

(3)事件驱动模型

异步非阻塞模型、减少OS进程切换

(4)简单调优

《》

一、博客之旅

1、gin

gin的github地址

gin是Go语言编写的HTTP Web框架

gin的特点:小巧、精美、易用

2、初始化项目

以下三个命令初始化了一个项目:

  • mkdir blog-service
  • cd blog-service
  • go mod init github.com/go-programming-tour-book/blog-service

3、安装gin

安装相关模块

go get -u github.com/gin-gonic/gin@1.6.3

并发是指在同一时间内可以执行多个任务

Go语言通过编译器运行时(runtime),从语言上支持了并发的特性,通过goroutine特性完成并发。goroutine由Go语言的运行时调度完成,而线程是通过操作系统调度完成。

一、轻量级线程(goroutine)-根据需要随时创建的“线程”

Go程序从main包的main()函数开始,就会为mian()函数创建一个默认goroutine

1、使用普通函数创建goroutine

使用go关键字,将running()函数并发执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func running() {
var times int
// 构建一个无限循环
for {
times++
fmt.Println("tick", times)
// 延迟一秒
time.Sleep(time.Second)
}
}

func main() {
// 并发执行程序
go running()
// 接收命令行输入,不做任何事
var input string
fmt.Scanln(&input)
}

2、使用匿名函数创建goroutine

在main中创建一个匿名函数并为匿名函数启动goroutine

1
2
3
4
5
6
7
8
9
10
func main() {
go func() {
var times int
for {
times++;
fmt.Println("tick", times)
time.Sleep(time.Second)
}
}()
}

所有goroutine函数都会在main()函数结束以后一同结束

3、调整并发的运行性能(GOMAXPROCS)

一般情况下可以使用runtime.numCPU()查询cpu数量,并使用runtime.GOMAXPROCS()函数进行设置

4、并发和并行

  • 并发(concurrency):将任务在不同的时间点交给处理器进行处理
  • 并行(parallelism):将任务分配给每个单独的处理器独立完成

5、Go中的协程(goroutine)和普通协程(coroutine)

  • goroutine可能发生并行操作,coroutine始终顺序执行
  • goroutine见使用channel通信,coroutine使用yieId和resume操作

二、通道(channel)-在多个goroutine间的通信管道

Go语言提倡使用通信的方式代替共享内存,就是所谓的通道(channel)

1、通道的特性

任何时候,只能有一个goroutine进行发送和获取数据

2、通道的使用:并发打印数字

创建一个无缓冲通道,使用无缓冲通道时,装入方将被阻塞,直到数据在另一个goroutine被取出

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
func printer(c chan int) {
// 开始无限循环等待数据
for {
// 从channel中获取一个数据
data := <-c
// 将0视为结束
if data == 0 {
break
}
// 打印数据
fmt.Println(data)
}
// 通知main函数已经循环结束
c <- 0
}

func main() {
// 创建一个channel
c := make(chan int)
// 并发执行printer,传入channel
go printer(c)
for i := 1; i <= 10; i++ {
// 通过channel将数据传给printer
c <- i
}
// 通知并发的printer结束循环等待
c <- 0
// 等待printer结束
<-c
}

3、单向通道-通道中的单行道

Go通道可以在声明时约束其操作方向,如只发送或接收

1
2
3
4
5
6
7
ch := make(chan int)
// 声明一个只能发送的通道类型,并赋值未ch
var chSendOnly chan<- int = ch
// 声明一个只能接受的通道类型
var chRecvOnly <-chan int = ch
chSendOnly <- 0
<- chRecvOnly

4、带缓冲的通道

带缓冲的通道无需等待接收方接收即可完成发送过程,并且不会阻塞,只有存储空间满的时候才会发生阻塞

1
2
3
4
5
6
7
8
9
10
// 创建一个3个元素缓冲区大小的整形通道
ch := make(chan int, 3)
// 查看当前通道的大小
fmt.Println(len(ch))
// 发送三个整形元素到通道
ch <- 1
ch <- 2
ch <- 3
// 查看当前通道的大小
fmt.Println(len(ch))

阻塞条件:

  • 带缓冲通道满继续发送数据
  • 带缓冲通道空继续接收数据

Go语言对通道限制长度的原因

一方生产数据,一方消费数据,当提供数据一方速度大于消费数据一方速度时候,若不限制长度,会撑爆内存

5、模拟远程过程调用(RPC)

服务器开发会使用RPC(远程调用)简化通信过程,使得远程通信如同本地函数调用一样

(1)客户端请求和接收封装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 模拟RPC客户端请求和接收
func RPCClient(ch chan string, req string) (string, error) {
// 向服务器发送请求
ch <- req
// 等待服务器返回
select {
// 接收到数据
case ack := <-ch:
return ack, nil
// 超时
case <-time.After(time.Second):
return "", errors.New("time Out")
}
}
  • 模拟socket向服务器发送一个字符串,服务器接收后结束阻塞执行下一行
  • 使用select进行多路复用
  • time.After(time.Second)通过这个返回的通道在指定时间后返回当前时间

两个case会同时执行,看哪个通道先返回数据

(2)服务器接收和返回数据

1
2
3
4
5
6
7
8
9
10
11
// 模拟RPC服务端接收客户端请求和相应
func RPCServer(ch chan string) {
for {
// 接收客户端请求
data := <-ch
// 打印接受的数据
fmt.Println("server received : ", data)
// 向客户端反馈已收到
ch <-"roger"
}
}

使用无限循环处理客户端请求

在main函数中分别调用两个函数,输出如下:

(3)模拟超时

在RPCServer中加入一段代码即可:time.Sleep(time.Second * 2),通过睡眠阻塞代码

四、同步-保证并发环境下数据访问的正确性

在某些轻量级场合,原子访问(atomic包)、互斥锁(sync.Mutex)以及等待组(sync.WaitGroup)能最大程度满足需求

1、竞态检测-检测代码并发环境下可能出现的问题

我们执行以下代码,就会发现错误

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var (
seq int64
)

func GenID() int64 {
// 尝试原子增加序号
atomic.AddInt64(&seq, 1)
return seq
}

func main() {
for i := 0; i < 10; i++ {
go GenID()
}
fmt.Println(GenID())
}

输出结果每次都不一样,原因是return seq有竞态问题,将代码改成如下所示就没问题了:return atomic.AddInt64(&seq, 1)

2、互斥锁(sync.Mutex)

使用锁非常简单

1
2
3
4
5
6
// 声明锁
var lock sync.Mutex
// 锁定
lock.Lock()
// 取消锁定
lock.Unlock()

加锁解锁过程保证原子性

3、读写互斥锁(sync.RWMutex)

在读多写少的环境中,可以使用读写互斥锁

基本使用类似

1
2
3
var lockr sync.RWMutex
lockr.RLock()
lockr.RLocker()

4、等待组(sync.WaitGroup)

保证并发环境中完成指定的任务数,等待组内部有一个计数器,计数器的值可以通过调用方法进行加减

直接上代码看演示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
func main() {
// 声明一个等待组
var wg sync.WaitGroup
// 准备一系列网站
var urls = []string{
"https://www.github.com",
"https://www.qiniu.com/",
"https://www.baidu.com/",
}
for _, url := range urls {
wg.Add(1)
go func(url string) {
defer wg.Done()
_, err := http.Get(url)
fmt.Println(url, err)
// 传递url
}(url)
}
wg.Wait()
fmt.Println("over")
}

所有的任务(网站响应)完成后,wait就会停止阻塞状态向下执行