字节青训整合笔记

Zephyr Lv3

啥都听不懂的青训营 摸鱼笔记

Go语言基本特质

协程

协程:用户态,轻量级线程,栈MB级别

线程:内核态,线程中可以运行多个协程,栈KB级别

协程主要通信方式

通过通信共享内存,类似生产者消费者模型

协程通信

Channel 通道

make(chan 元素类型, [缓冲大小])

无缓冲通道又称为同步通道

适当的缓冲大小可以弥补生产者消费者执行速度不均衡的问题

同步

​ 可使用lock,WaitGroup实现同步。

Go的依赖管理

1. GoPath

结构

  • bin: 项目编译的二进制文件

  • pkg: 项目编译的中间产物,加速编译

  • src: 项目源码

    项目代码直接依赖于src下的源码

    缺陷:无法处理项目代码依赖不同版本的情况

2. GoVendor

项目目录下新增vendor文件,所有依赖包副本形式放在$ProjectRoot/vendor

依赖寻址:有先到vendor下,再到GoPath下

问题:

  • 无法控制依赖版本
  • 更新项目又可能出现依赖冲突,导致编译出错。
  • 本质:仍旧依赖的是源码,无法通过版本分辨

3. Go Module

通过go.mod文件进行版本管理

依赖管理三要素

 1. 配置文件,描述依赖   	go.mod
 1. 中心仓库管理依赖库       Proxy
 1. 本地工具                         go get/mod

对于所需要的不同版本的依赖,go会采取最低兼容版本算法。

依赖分发-Proxy

在源站与目标之间加一层代理,代理缓存源站中的依赖

单元测试

1. 规则

所有测试文件以_test.go结尾

函数命名:func Testxx(*testing.T)

初始化逻辑放到TestMain中

2. mock

模仿真实对象行为的模拟对象

mock模拟的不是测试对象而是测试对象的依赖

Why?

  • (1)提高 A 的测试覆盖率。A 依赖 B,本质上依赖的是 B 的返回结果,也就是说 B 的返回结果会影响 A 的行为。通过 mock B 我们可以构造各种正常和异常的来自 B 的返回结果,从而更充分测试 A 的行为。
  • (2)避免 B 的因素从而对 A 产生影响。依赖真实的 B 去测试 A 可能有很多问题:B 的开发没有完成时无法测试 A;B 有阻塞性bug 时无法测试 A;B 的依赖 C 有阻塞性 bug 时无法测试 A;
  • (3)提高 A 的测试效率。B 的真实行为可能很慢,而 B 的模拟行为是非常快的,因此可以加快 A 的测试执行速度。

高性能Go语言发行版优化与落地实践

性能优化

业务层优化

  • 针对特定场景,具体问题,具体分析
  • 容易获得较大性能收益

语言运行时优化

  • 解决更通用的性能问题
  • 考虑更多场景
  • tradeoffs

内存管理

自动内存管理

由程序语言的运行时系统回收动态内存

  • 避免手动内存管理,专注于业务逻辑
  • 保证内存使用的正确性和安全性

三个任务

  • 为新对象分配空间
  • 找到存活对象
  • 回收死亡对象的内存空间

相关概念

  • Mutator:业务线程,分配新对象,修改对象指向关系
  • Collector:GC线程,找到存活对象,回收死亡对象的内存空间
  • Serial GC: 只有一个Collector,每次进行GC操作时需要暂停程序
  • Parallel GC: 支持多个Collector同时回收GC算法
  • Concurrent GC:业务代码与垃圾回收同时执行(Collector必须感知对象指向关系的改变)

追踪垃圾回收

对象被回收的条件:指针指向关系不可达的对象

标记根对象:静态变量,全局变量,常量,线程栈等(所有在程序运行时永远存活的对象)

标记:找到可达对象

清理:所有不可达对象(根据对象的生命周期,使用不同的标记和清理策略)

  • 将存活对象复制到另外的内存空间
  • 将死亡对象的内存标记为“可分配”
  • 移动并整理存活对象

分代GC

Intution:很多对象在分配出来后很快就不再使用了

每个对象的年龄:经历过GC的次数

不同年龄的对象处于heap的不同区域

  1. 年轻代

    常规的对象分配

    由于存活对象很少,可以直接复制到其他空间

    GC吞吐率高

  2. 老年代

    对象趋向于一直活着,反复复制开销大

    可以采用标记对象的方法。

引用计数

每个对象都有一个与之关联的引用数目

对象存活的条件:当且仅当引用数大于0

优点:

  1. 内存管理的操作被平摊到程序执行过程中
  2. 内存管理不需要了解runtime的实现细节

缺点:

  1. 维护引用计数的开销较大:通过原子操作保证对引用技术操作的原子性和可见性
  2. 无法回收环形数据结构 (所有对象相互之间会引用,虽然该数据结构已经不可达,但因为其中每个对象的引用数不会降为0,所以无法被回收)
  3. 内存开销:每个对象都引入的额外内存空间存储引用数目
  4. 回收内存依然可能引发暂停

Balanced GC

GAB对于Go内存管理来说是个对象

本质:将多个小对象的分配合并成一次对象的分配

利用三个指针标记内存块的起点和终点,以及下一块可用内存的地址

问题:GAB的对象分配方式会导致内存被延迟释放。例如,GAB中仅存活了1个小对象,此时GAB无法释放,但又浪费了大量内存。

解决:当GAB总大小超过一定阈值时,将GAB中存活的对象复制到另外分配的GAB中(用copying GC 的算法管理小对象)

编译优化

编译器的结构

函数内联

将被调用函数的函数体的副本替换到调用位置,同时重写代码以反映参数的绑定

优点:

  1. 消除函数调用开销,例如传递参数,保存寄存器等
  2. 将过程间分析转化为过程内分析,帮助其他优化

缺点:

  1. 函数体变大,(icache)不友好
  2. 编译生成的Go镜像变大

逃逸分析

分析代码中指针的动态作用域,指针在何处可以被访问

思路:

从对象分配处出发,沿着控制流,观察对象的数据流

若发现指针p在当前作用域s:

  1. 作为参数传递给其他函数

  2. 传递给全局变量

  3. 传递给其他的goroutine

  4. 传递给已逃逸的指针指向的对象

    即,若该对象可以被外部函数调用,则称其逃逸出s

优化:增加函数的内联,扩大函数边界,使得原本逃逸的对象只能作用在当前作用域。而未逃逸的对象可以在栈上分配,并且无论是分配还是回收都很快,同时也减少了在heap上的分配,减小GC负担。

网络

网络接入 —— 路由

路由不是对称的,根据当前的网络情况,响应包的路径也可能跟请求包不同

路由也不会修改IP地址,在数据包传输过程中,源IP与目的IP均不会发生变化,只会修改MAC地址,同时在发出数据包时不仅要知道目的MAC,还要知道发出的端口。

ARP请求不会跨网段发送,若目的与源不在同一网段,则会先寻找下一跳的MAC地址,利用ARP逐级跳转。

免费ARP

发送一个包含新IP的ARP数据包,提示所有服务器刷新ARP表

  • 当网段中新增了一台机器时,可以通过这种方式让其余机器刷新ARP表,而避免在使用时临时刷新。
  • 当服务器有新增IP时,可以通过免费ARP检测IP冲突。

PS:Mac地址不能替代IP地址 -> IP地址的存在实际上就是为了解决二层协议(包括Mac)的兼容问题。

SSL/TLS非对称加密

将数据进行加密之后再传输,并且使用的加密算法也进行加密,接收方通过与发送方协商获知所使用的加密算法。

网络提速

对于静态资源,使用CDN缓存

对于动态API,优化网络路径

网络容灾

故障发生->故障感知->自动切换->服务恢复

  1. 外网容灾

内部专线出现故障后,切换为外网连接

  1. 降级容灾

网址会被映射到两个机房,其中一个机房出现问题时,首先进行故障感知,然后判断机房B能否承受住那部分流量,最后才进行切换。

  1. 缓存

当程序故障后,就用缓存先响应用户请求

DNS

DNS记录类型

  1. A/AAAA:IP指向记录,前者指向IPv4, 后者指向IPv6
  2. CNAME:别名记录,配置值为别名或主机名,客户端根据别名继续解析以提取IP地址
  3. TXT:文本记录,购买证书时需要
  4. MX:邮件交换记录,用于指向邮件交换服务器
  5. NS:解析服务器记录,用于指定哪台服务器对于该域名解析
  6. SOA:起始授权机构记录

HTTP接入协议

加密

对称加密:使用相同的秘钥来加密传输内容,一端加密后,对端收到数据会用相同的秘钥来解密

非对称加密:如果用公钥对数据进行加密,只有用对应的私钥才能解密;如果用私钥对数据进行加密,那么只有用对应的公钥才能解密。

高质量编程与性能调优

编程原则

  1. 简单性

    消除“多余的复杂性”,以简单清晰的逻辑编写代码

  2. 可读性

  3. 生产力

编码规范

注释:

  1. 注释应该解释代码作用, 适合注释公共符号

  2. 注释应该解释代码如何做的,适合注释实现过程

  3. 注释应该解释代码实现的原因,适合解释代码的外部因素,提供额外的上下文。

  4. 注释应该解释代码什么情况会出错,适合解释代码的限制条件。

  5. 公共符号始终需要注释

    PS:代码是最好的注释,注释应提供代码未表达出的上下文信息。

变量命名:

  1. 简洁
  2. 缩略词全部大写,当其位于变量开头其不需要导出时,全部小写
  3. 变量与实际使用位置距离越远,需要携带的上下文信息越多

函数命名:

  1. 函数名不携带包名的上下文信息
  2. 简短
  3. 当名为foo的包某个函数返回类型Foo时,可以省略类型信息。

包名:

  1. 只由小写字母组成。不包含大写字母和下划线等字符
  2. 简短并包含一定上下文信息
  3. 不要与标准库同名

控制流程:

  1. 线性原理,避免嵌套
  2. 正常代码最小缩进

性能优化

Go提供benchmark工具进行基准性能测试

go test -bench=. -benchmen

性能优化建议

预分配

尽量在make阶段确定切片/对象大小

slice进行扩容时会在已有切片基础上创建切片,不会创建新的底层数组。

场景

  1. 原切片较大,代码在原切片基础上新建小切片
  2. 原底层数组在内存中有引用,得不到释放

此时可以使用copy代替re-slice

空结构体

使用空结构体节省内存

空结构体struct{}实例不占据任何的内存空间

使用场景:

  • 节省资源
  • 空结构体本身具备很强的语义,可以作为占位符使用

atomic

锁是通过操作系统来实现,属于系统调用

atomic操作通过硬件实现,效率更高

sync.Mutex应该用于保护一段逻辑,而不仅仅是用于保护一个变量

架构

架构的定义

  • 是有关软件整体结构与组件的抽象描述
  • 用于指导软件系统各个方面的设计

架构的演进

单机

把所有功能都实现在一个进程里,并部署在一台机器上

优点:简单

缺点:能提供的服务有限,运维需要停服。

单体、垂直应用 | 垂直切分

单体架构:分布式部署

垂直应用架构:按应用垂直切分的单体

优点:水平扩容,运维不需要停服

缺点:职责太多,开发效率不高。爆炸半径大(个体出现问题,容易导致整条链停摆)

单体架构

SOA

service-oriented architecture

  1. 将应用的不同功能单元抽象为服务
  2. 定义服务之间的通信标准(核心要点)

SOA

微服务

SOA的去中心化演进方向

微服务

问题:

  • 数据一致性
  • 高可用,不同服务之间如何高效交互
  • 治理,如何容灾
  • 解耦 vs 运维 收益是否大于提高的运维成本
  • 标题: 字节青训整合笔记
  • 作者: Zephyr
  • 创建于 : 2022-06-30 03:19:53
  • 更新于 : 2023-01-26 12:31:58
  • 链接: https://faustpromaxpx.github.io/2022/06/30/coroutine/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论