未命名
未命名
剖析cellnet包网络库,设计并实现Socket聊天功能
cellnet的设计理念是:高性能、简单、方便、开箱即用
一、了解cellnet网络库的特性、流程与架构
1、cellnet网络库的特性
2、cellnet网络库的流程以及架构
二、管理TCP Scket连接
1、理解Socket的事件类型
2、管理事件回调
3、连接器(Connector)
4、会话管理(SessionManager)
5、接收器(Acceptor)
三、组织接收和发送数据流程的Socket会话(Session)
1、在会话开始时启动goroutine和派发事件
2、会话中的接收数据循环
3、会话中的发送数据循环
四、排队处理事件的事件队列(EventQueue)
1、实现事件队列
2、使用不同的事件队列模式处理数据
五、消息编码(codec)-让cellnet支持消息的多种编码格式
六、消息元信息(MessageMeta)-消息ID、消息名称和消息类型的关联关系
1、理解消息元信息
2、注册消息元信息
3、示例:使用消息元信息
4、实现消息的编码(EncodeMessage())和解码(DecodeMessage())函数
七、接收和发送封包(Packet)
1、接收可变长度封包
2、了解封包数据读取器(PacketReader)
3、了解封包数据写入器(PacketWriter)
4、读取自定义封包及数据
5、写入自定义封包及数据
6、响应消息处理事件
八、使用cellnet实现聊天功能
1、定义聊天协议
2、实现客户端功能
3、实现服务器功能
4、运行聊天服务器和客户端
未命名
未命名
第一次课程前半部分讲了Go基本语法,后半部分讲了三个小项目
零、Go基础
1、基本语法
变量
声明、初始化、匿名变量
数据类型
整形、浮点型、布尔、字符型、字符串、切片、
指针
变量的生命周期
常量
变量别名
2、容器
数组
声明、初始化、遍历
切片(slice)
make()、append()
映射(map)
列表(list)
3、流程控制
- 条件判断:if
- 循环:for
- 键值循环:for range
- 分支选择:switch
- 代码跳转:goto
一、猜字谜游戏
1、代码版本一
1 | package main |
每次运行输出的数字都是同样的
随机数种子
2、代码版本二
需要设置代码随机数种子,设置为当前的时间戳
1 | rand.Seed(time.Now().UnixNano()) |
3、代码版本三
实现用户的输入输出
1 | reader := bufio.NewReader(os.Stdin) |
4、代码版本四
使用循环实现游戏的循环
变量、循环、函数、文件流、错误处理等
二、命令行版本词典
调用第三方API
使用Go发送网络请求,解析JSON,并使用相应的代码生成工具提高开发效率
1、抓包
抓取第三方翻译平台的API
按f12打开开发人员工具,找到正确的请求
2、代码生成
右键请求
复制结果如下:
1 | curl 'https://api.interpreter.caiyunai.com/v1/dict' \ |
然后我们就去这个平台生成代码:Go代码生成
1 | package main |
3、生成requst body
1 | type DictRequest struct { |
将请求结构体序列化为JSON:
1 | request := DictRequest{TransType: "en2zh", Source: "good"} |
4、response body的解析
由于结构复杂,为了防止出错,我们使用第三方代码生成工具
去这个平台进行Go结构体的生成:Go结构体
1 | type AutoGenerated struct { |
接下来就反序列化response body了:
1 | var dictResponse DictResponse |
获取JSON中指定的信息
1 | fmt.Println("UK:", dictResponse.Dictionary.Prons.En, "US:", dictResponse.Dictionary.Prons.EnUs) |
5、获取命令行参数
1 | if len(os.Args) != 2 { |
三、SOCKET5代理服务器
1、socks5原理
2、向一个连接读写数据
读数据:
1 | reader := bufio.NewReader(conn) |
写数据:
1 | _, err = conn.Write([]byte{b}) |
4、连接的各个流程
基于字节流,根据协议规则,依次读取字节,将字节拼接成不同的具体数据
5、实现双向的数据转发
任何一个方向的数据copy失败,我们就返回这个函数,并关闭连接清理数据
1 | ctx, cancel := context.WithCancel(context.Background()) |
未命名
一、语言进阶
并发与并行
并发:多线程在单核CPU上运行
并行:多线程程序在多核程序上运行
1、Goroutine
协程:用户态,轻量级线程,栈MB级别
线程:内核态,线程跑多个协程,栈KB级别
创建协程代码:
1 | func hello(i int) { |
2、CSP(Communicating Sequential Processes)
提倡通过通信共享内存而不是通过共享内存实现通信
3、Channel
通过make(chan 元素类型,, [缓冲大小])
- 有缓冲通道
- 无缓冲通道
代码示例:
A协程发送0~9数字;B协程计算输入数字的平方;主协程输出最后的平方数
1 | func CalSquare() { |
4、并发安全Lock
5、WaitGroup
计数器,开启协程+1;执行结束-1;主协程阻塞直到计数器为0
6、小结
- Goroutine
- Channel
- Sync
二、依赖管理
1、Go依赖管理演进
GOPATH -> Go Vender -> Go Module
(1)GOPATH
- 项目代码直接依赖src下的代码
- go get下载最新的代码到src目录下
无法实现package的多版本控制
(2)Go Vender
- 增加vender文件,存放所有的依赖包副本
- 为每一个项目引入一份依赖副本
无法控制依赖版本
(3)Go Module
- 通过
go.mod
文件管理依赖包版本 - 通过
go get/go mod
指令工具管理依赖包
定义版本规则和管理项目依赖关系
2、依赖管理
- 配置文件:go.mod
- 中心仓库管理依赖库:Proxy
- 本地工具:go get/mod
(1)依赖配置-go.mod
也就是编写go.mod
1 | // 依赖管理基本单元 |
(2)依赖配置-version
- 语义化版本
- 基于commit伪版本
(3)依赖配置-indirect
直接依赖和间接依赖
(4)依赖配置-incompatible
主版本2+模块会在模块路径增加/vN后缀
对于没有go.mod文件并且主版本2+的依赖,会加上该关键字
(5)工具
go get
go mod (init / download / tidy)
3、小结
- Go依赖管理演进
- Go Module依赖管理方案
三、测试
回归测试 -> 集成测试 -> 单元测试(测试成本逐渐降低)
1、单元测试
(1)规则
- 所有测试文件以
_test.go
结尾 - func TestXxx(*testing.T)
- 初始化逻辑放到TestMain中
(2)单元测试-assert
待测试函数:
1 | func HelloTom() string { |
测试代码:
1 | func TestHelloTom(t *testing.T) { |
2、单元测试-依赖
外部依赖 => 稳定&幂等
3、单元测试-Mock
monkey:https://github.com/bouk/monkey
- 为一个函数打桩
- 为一个方法打桩
不依赖于本地文件
4、基准测试
- 优化代码
- 内置的测试框架提供了基准测试能力
5、小结
- 单元测试
- Mock测试
- 基准测试
四、项目实战
1、分层结构
数据层
数据Model,外部数据的增删改查
逻辑层
业务Entity,处理核心业务逻辑的输出
视图层
视图View,处理和外部交互逻辑
2、组件工具
Gin高性能go web框架
Go mod
go mod init
未命名
x高性能Go语言发行版优化与落地实践
本节课主要内容
优化
内存管理优化
编译器优化
背景
自动内存管理和Go内存管理
编译器优化
性能优化是什么?
提升软件系统处理能力,减少不必要消耗
为什么做性能优化?
用户体验、资源的高效利用
怎么做优化?
- 业务层优化
- 语言运行时优化
总结:
- 性能优化的基本问题
- 性能优化的两个层面
- 性能优化的可维护性
一、自动内存管理
1、自动内存管理
动态内存
程序运行时内存的动态分配:
malloc()
自动内存管理
避免手动内存管理
保证内存的使用的正确性和安全性:double-free-problem 和 use-after-free problem
相关概念
Mutator
业务线程,分配新的对象,修改对象的指向关系
Collector
GC线程,找到存活对象,回收死亡对象
Serial GC
只有一个collector
Parallel GC
支持多个collector
Concurrent GC
业务线程和GC线程同时执行,必须感知对象指向关系的改变
评价GC 算法
安全性(Safety):不能回收存活的对象 基本要求
吞吐率(Throughput):花在GC上的时间
暂停时间(Pause time):stop the world(STW)业务是否感知
内存开销 (Space overhead)GC 元数据开销
追踪垃圾回收(Tracing garbage collection)
引用计数(Reference counting)
2、追踪垃圾回收
回收条件:指针指向关系不可达
标记根对象:静态变量、全局变量、常量、线程栈
标记:找到可达对象
- 求指针指向关系的传递闭包:从根对象出发,找到所有可达对象
清理:所有不可达对象
- Copying GC
- Mark-Sweep GC
- Mark-Compact GC
根据对象的生命周期选择不同的标记清理策略
3、分代GC(Generational GC)
分代假说 (Generational hypothesis): most objects die young
Intuition:很多对象在分配出来后很快就不再使用了
每个对象都有年龄:经历过 GC 的次数
目的:针对年轻和老年的对象,制定不同的 GC 策路,降低整体内存管理的开销,不同年龄的对象处于 heap的不同区域
年轻代 (Young generation)
常规的对象分配
由于存活对象很少,可以采用 copying collection
GC 吞吐率很高
老年代 (Old generation)
对象趋向于一直活着,反复复制开销较大
可以采用 mark-sweep collection
4、引用计数
每个对象都有一个与之关联的引用数目
对象存活的条件:当且仅当引用数大于 0
优点
内存管理的操作被平雄到程序执行过程中
内存管理不需要了解runtime 的实现细节:C++ 智能指针 (smart pointer)
缺点
维护引用计数的开销较大:通过原子操作保证对引用计数操作的原子性和可见性
无法回收环形数据结构—weak reference
内存开销:每个对象都引1入的额外内存空间存储引用数目
回收内存时依然可能引发暂停
5、总结
- 背景和意义
- 概念和评价方法
- 追踪垃圾回收
- 引用回收
- 分代GC
二、Go内存管理
1、Go内存分配
三、编译器和静态分析
四、Go编译器优化
未命名
3、网络接入-ARP协议
- 逻辑网段相同才能发送ARP
- ARP请求广播,ARP答应单播
- 免费ARP?ARP代理?
4、网络接入协议
- 唯一标识,互联网通用
- Mac地址不能代替IP地址吗
5、网络接入-NAT
- 家里路由器是怎么上网的?
- 多个内网客户端访问同一个目标地址
6、网络传输-数据包
7、网络传输-先请求DNS协议
递归迭代
8、网络传输-DNS的传输协议
9、网络传输-TCP
TCP保活机制
通过MSS防止分片
三次握手确认MSS,TCP的OPTION字段
Sequence Number
Acknowledge Number
Timewaite
10、网络传输协议-HTTP/HTTP 1.1
- 为什么不同TCP通信
- 为什么网络上那么多HTTP通信
- HTTP只是多了一层规矩
做了哪些优化
- 长连接
11、网络传输-SSL/TLS握手
第三方机构CA
二、
1、网络提速-HTTP2.0
3、网络提速-QUIC/HTTP3.0
- TCP or UDP
- Kernel or Userspace
- 0 RTT
- 弱网优势
未命名
一、TCP分片和UDP分片
1、为什么?
数据报太大,拆分成小数据包
2、怎么做?
(1)TCP分片是在传输层
如果在IP层分片,如果当一个IP包丢失,那么整个IP报文的所有分片都得重传
TCP是由超时重传机制,重传丢失的TCP报文
(2)UDP是在IP层
UDP传输不可靠
二、第二次握手丢失情况
重传5次
客户端角度
认为第一次握手SYN报文丢失或者第二次握手ACK-SYN报文,会重传SYN请求连接报文
服务器角度
没有收到第三次ACK报文,认为第二次握手ACK-SYN报文或者第三次ACK丢失,会重传第二次握手ACK-SYN报文
三、进程的上下文切换
实际上linux内核中,进程上下文包括进程的虚拟地址空间和硬件上下文
1、是什么?
分配CPU资源的一个过程,一个运行的进程切换到另外一个进程去运行
2、为什么?
为了使得计算机能够并发执行多个进程
3、怎么做?
先保存当前进程的PCB状态,再加载下一个进程的PCB状态
四、线程池
线程池参数、线程池拒绝策略和等待队列类型之间的关系
拒绝策略
- 直接抛弃
- 抛弃最老的
- 丢弃抛异常的
- 当前线程直接执行
五、MySQL日志
binlog、redo log、undo log
binlog(归档):记录对数据库进行修改的更新操作,用于数据库的同步和恢复(比如主从数据库之间使用bin log进行同步),只在事务结束时写入
redo log(重做):记录InnoDB引擎的事务,保证事物的持久性,执行过程中会不断写入
undo log(回滚):undo log会记录数据的版本,可以实现事务的回滚,回滚到某个版本的数据,实现MVCC
六、主从复制
1、是什么?
MySQL 主从复制是指数据可以从一个MySQL数据库服务器主节点复制到一个或多个从节点。MySQL 默认采用异步复制方式,这样从节点不用一直访问主服务器来更新自己的数据,数据的更新可以在远程连接上进行,从节点可以复制主数据库中的所有数据库或者特定的数据库,或者特定的表
2、为什么?
- 读写分离
- 数据的实时备份
- 高可用
3、怎么做?
增量发送
主节点接收到来自从节点的I/O请求后,通过负责复制的I/O进程(log dump 线程)根据请求信息读取指定日志指定位置之后的日志信息,返回给从节点。返回信息中除了日志所包含的信息之外,还包括本次返回的信息的bin-log file 的以及bin-log position(bin-log中的下一个指定更新位置)
七、死锁问题
1、什么是死锁?
死锁是指两个或以上进程在执行过程中,由于资源竞争造成的一种阻塞现象,若无外力作用,将一直阻塞下去。我们称这种现象为系统产生了死锁或者系统处于死锁状态
2、产生死锁的原因有哪些?
- 系统资源不足
- 程序执行顺序不当
- 资源分配不当
3、产生死锁的必要条件
互斥条件
一个资源只能被一个进程使用
请求与保持条件
请求资源阻塞时,对已获得的资源并不释放
不剥夺条件
进程已获得的资源,除非自己释放,否则不能被剥夺
循环等待条件
进程之间形成一种头尾相接的资源等待关系
4、如何解决死锁?
在系统设计、进程调度等方面注意不要让四个必要条件成立,确定合理的资源分配算法、防止进程处于等待状态下占用资源
八、TCP协议如何保证可靠传输
https://segmentfault.com/a/1190000022944999
通过下面几种机制共同保证可靠传输
1、数据块划分
应用数据被划分为TCP认为合适发送的数据块
2、数据排序
TCP会对每个数据进行编号,接收端对收到的数据进行排序,将有序的数据传给应用层
3、校验和
发送端和接收端分别计算数据的数据的校验和,如果数据有差错就丢弃
4、数据包去重
丢弃重复接收的数据
5、超时重传
当TCP发出一个段后,会启动一个定时器,等待接收方会送确认报文,如果不能及时收到一个确认就重传
6、流量控制
连接双方都有固定大小的缓冲区,接收端只允许发送端发送接收段缓冲区大小的数据,当接收方来不及处理发送方发送的确认数据时候,会提示发送方降低发送速率。TCP使用滑动窗口协议实现流量控制
7、拥塞避免
- 慢开始
- 拥塞避免
- 快重传和快恢复
8、ARQ协议
发送完一个分组就停止等待确认报文,收到确认报文后在发送下一个分组数据
九、Redis为什么这么快?
https://juejin.cn/post/6978280894704386079
基于内存实现省去了磁盘IO的消耗
底层实现了高效的数据结构
合理的数据编码
合理的线程模型
单线程(避免了上下文切换和竞争锁的切换):是指Redis网络IO和键值对读写是由一个线程来完成的,其他诸如持久化、异步删除、集群数据同步由额外的线程执行
I/O多路复用
I/O:网络IO
多路:多个网络连接
复用:复用同于一个线程
是一种同步的IO模型,实现一个线程可以监视多个文件句柄
虚拟内存机制
未命名
一、操作系统
1、线程切换比进程切换开销要小
(1)线程进程切换的流程
进程切换分为两步:
- 切换页表以使用新的地址空间,所有已经缓存的内存地址一瞬间都作废了
- 切换内核栈和硬件上下文
对于linux来说,线程和进程的最大区别就在于地址空间,因为每个进程都有自己的虚拟地址空间,而线程是共享所在进程的虚拟地址空间的,因此同一个进程中的线程进行线程切换时不涉及虚拟地址空间的转换
(2)虚拟地址空间切换会比较耗时
进程都有自己的虚拟地址空间,把虚拟地址转换为物理地址需要查找页表,页表查找是一个很慢的过程,因此通常使用Cache来缓存常用的地址映射
由于每个进程都有自己的虚拟地址空间,都有自己的页表,那么当进程切换后页表也要进行切换,页表切换后TLB就失效了,Cache失效导致命中率降低,那么虚拟地址转换为物理地址就会变慢,表现出来的就是程序运行会变慢,而线程切换则不会导致TLB失效,因为线程无需切换地址空间
二、计算机网络
1、HTPP状态码
状态码 | 类别 |
---|---|
1XX | 信息性状态码 |
2XX | 成功状态码 |
3XX | 重定向状态码 |
4XX | 客户端错误状态码 |
5XX | 服务端错误状态码 |
1XX
- 100 Continue:表示正常,客户端可以继续发送请求
- 101 Switching Protocols:切换协议,服务器根据客户端的请求切换协议。
2XX
- 200 OK:请求成功
- 201 Created:已创建,表示成功请求并创建了新的资源
- 202 Accepted:已接受,已接受请求,但未处理完成。
- 204 No Content:无内容,服务器成功处理,但未返回内容。
- 205 Reset Content:重置内容,服务器处理成功,客户端应重置文档视图。
- 206 Partial Content:表示客户端进行了范围请求,响应报文应包含Content-Range指定范围的实体内容
3XX
- 301 Moved Permanently:永久性重定向
- 302 Found:临时重定向
- 303 See Other:和301功能类似,但要求客户端采用get方法获取资源
- 304 Not Modified:所请求的资源未修改,服务器返回此状态码时,不会返回任何资源。
- 305 Use Proxy:所请求的资源必须通过代理访问
- 307 Temporary Redirect: 临时重定向,与302类似,要求使用get请求重定向。
4XX
- 400 Bad Request:客户端请求的语法错误,服务器无法理解。
- 401 Unauthorized:表示发送的请求需要有认证信息。
- 403 Forbidden:服务器理解用户的请求,但是拒绝执行该请求
- 404 Not Found:服务器无法根据客户端的请求找到资源。
- 405 Method Not Allowed:客户端请求中的方法被禁止
- 406 Not Acceptable:服务器无法根据客户端请求的内容特性完成请求
- 408 Request Time-out:服务器等待客户端发送的请求时间过长,超时
5XX
- 500 Internal Server Error:服务器内部错误,无法完成请求
- 501 Not Implemented:服务器不支持请求的功能,无法完成请求
- 502 Bad Gateway:指错误网关,无效网关;在互联网中表示一种网络错误;表现在WEB浏览器中给出的页面反馈;它通常并不意味着上游服务器已关闭(无响应网关网关/98992)/代理) ,而是上游服务器和网关/代理使用不一致的协议交换数据
2、HTTP请求头
https://segmentfault.com/a/1190000018234763
(1)General
(不属于headers,只用于收集请求url和响应的status等信息)
- Request URL
- Request Method
- Status Code
- Remote Address
(2)Request Headers
- Accept
- Accept-Encoding
- Connection
- Cookie
- Host
- Origin
- User-Agent
(3)Response Headers
- cache-control
- connection
- data
3、第二次挥手和第三次挥手能否合并
可以合并
TCP四次挥手里,第二次和第三次挥手之间,是有可能有数据传输的。第三次挥手的目的是为了告诉主动方,”被动方没有数据要发了”。
在第一次挥手之后,如果被动方没有数据要发给主动方。第二和第三次挥手是有可能合并传输的。这样就出现了三次挥手。
4、Get和Post请求区别
- HTTP报文层面GET请求将信息放到url中,POST请求将信息放到请求体中
- 数据库层面,GET请求是幂等性和安全性的,POST不是
- 缓存层面,GET 请求能够被缓存,POST不可以
5、滑动窗口
滑动窗口的组成
四大组成部分
发送过程
当已经发送的窗口收到确认时,窗口会向右移,之前在窗口右边的分组就会移动到窗口内部
一、数据库
1、三大范式
https://segmentfault.com/a/1190000013695030
(1)第一范式
属性的原子性,不可再分
(2)第二范式
消除所有非主属性对主属性的部分依赖
(3)第三范式
消除非主属性对主属性的传递依赖