Kratos 项目结构理解
前言Kratos 是一个 Go 语言实现的微服务框架,近期正在用 Kratos 做实训,也是我最早接触微服务实践的项目。
初期读项目结构深感无力,花了一段时间了解 DDD 设计,才慢慢搞明白项目和业务结构 orz。
关于 DDD 的知识,可以(存疑)参考我上一篇文章:DDD 架构
项目结构若直接使用 kratos cli 创建项目,将得到基于官方 kratos-layout 的项目结构:
.├── Dockerfile├── LICENSE├── Makefile├── README.md├── api // 下面维护了微服务使用的proto文件以及根据它们所生成的go文件│ └── helloworld│ └── v1│ ├── error_reason.pb.go│ ├── error_reason.proto│ ├── error_reason.swagger.json│ ├── greeter.pb.go│ ├── greeter.proto│ ...
DDD 架构
近期在学习领域驱动设计(Domain-Driven Design,简称 DDD),其与传统的 MVC 设计模式存在较大差异,在此简单记录我的认识。
概述传统的 MVCMVC(Model-View-Controller)将 Web 应用划分为三层:模型层(Model)、视图层(View)和控制器层(Controller)
模型层 Model:负责处理数据和业务逻辑,以及对象关系映射,是系统核心,包含以下内容:
数据实体类:描述业务对象,如用户
业务逻辑:通过服务(Service)实现业务逻辑,如用户注册
数据访问对象(DAO):负责与数据库交互,执行 CRUD
模型层的职责是封装业务逻辑和数据操作,确保数据的一致性
视图层 View:负责用户交互与数据展示,是用户与应用程序进行交互的窗口,在 Web 应用中包括 HTML、JSP 等页面,专注于数据展示
控制器层 Controller:负责 Model 与 View 之间的数据流动,属于桥梁角色,不处理数据和展示逻辑,而是专注于流程控制
DDDDDD 是一个以业务为核心,用代码精确表达业务概念和规则,并作为团队通用沟通基础的抽象系统。 ...
CS144 Lab6-7
Lab-6IP Router这个部分会实现一个简单的路由器,不考虑复杂的路由协议、以及路由算法,只需要对给定的数据包,确认发送接口以及下一跳的 IP 地址。简单的 match-action 行为。
参考实验文档,很容易做出来。
实现只需要实现 add_route 和 route_one_datagram 两个方法即可。
Router 类及辅助类声明如下,其他已实现的辅助类可以在同文件 libsponge/router.hh 下找到:
class RouteEntry { public: const uint32_t _route_prefix; const uint8_t _prefix_length; const std::optional<Address> _next_hop; const size_t _interface_num; const uint32_t _prefix_mask; RouteEntry(uint32_t a, uint8_t b, std::optional<Address> c ...
CS144 Lab5
Lab-5Network interface这部分内容是实现网络接口 network interface (也可以称为适配器)
TCP 报文有三种方式可被发送到远程终端:
TCP-in-UDP-in-IP:用户提供 TCP 包,之后可以使用 Linux 提供的接口,让内核来负责构造 UDP 报头、IP报头以及以太网报头,并将构造出的数据包发送至下一个层。
TCP-in-IP:将 TCP 数据包直接放进 IP 包作为其 payload,被称为 TCP/IP。但用户层如果想直接操作构造 IP 报文,需要使用到 Linux 提供的 TUN 虚拟网络设备来作为中转。当用户将 IP 报文发送给 TUN 设备后,剩余的以太网报头构造、发送以太网帧等等的操作均会由内核自动进行,无需用户干预。
TCP-in-IP-in-Ethernet:每次用户向TUN设备写入IP数据报时,Linux 内核都必须构造一个适当的链路层(以太网)帧,并将 IP 数据报作为其 payload。因此 Linux 必须找出下一跳的以太网目的地址,给出下一跳的 IP 地址。如果 Linux 无法得知该映射关系,则将会 ...
CS144 Lab4
Lab-4TCPConnectionTCPConnection 将 TCPSender 和 TCPReceiver 组合形成 TCP 的终端,同时维护数据收发。
对于接收数据:
收到 RST 标志位时,设置错误状态并永久关闭 TCP 连接
告知自己的 TCPSender 对端的 ackno 和 window_size
收到 TCP 报文时需要告知对端,自己的 ackno 和 window_size
对于发送数据:
将自己 TCPReceiver 的 ackno 和 window_size 填充到待发送的 TCP segment
设置待发送报文的 ack
通过自己的 tick 函数,告知 TCPSender 时间的流逝
连续重传次数超过最大重传次数 MAX_RETX_ATTEMPTS,需要发送 RST 包
在一定条件下关闭 TCP 连接,主要是暴力退出、先发出 FIN 一方的等待(linger)行为
假设本地先关闭连接(输出流结束),对方输出流未结束(还可以发送数据),此时本地会在对方(更晚)关闭连接后进入linger状态,等待后关闭。本地进入linger状态的确切时机是在本地发 ...
CS144 Lab3
Lab-3TCPSenderTCPSender 负责从 ByteStream 读取数据,以 TCP 报文的形式发送,处理 TCPReceiver 传入的 ackno 和 window_size,以及处理重传。
状态图参考实验文档。
重传判断TCP 使用超时重传机制,需要追踪每个已发送报文(已被发送但还未被接收)的发送时间,如果某些已发送报文太久没有被接收方确认(即接收方接收到对应的 ackno),则该数据包必须重传。
需要注意的是,接收方返回的 ackno 不一定对应着发送方返回的 seqno(且与 seqno 不存在数学关系),因为发送的数据可能会因为内存问题,被接收方截断。
TCPSender 的 tick 函数会被不定时调用,其参数声明了距离上一次调用过去的时间,是 TCPSender 唯一能调用的超时时间相关函数。因为直接调用系统提供的 clock 或者 time 将会导致测试不可用。
TCPSender 在构造时会被给予一个重传超时时间 RTO 的初始值。RTO 是在重新发送未完成确认的 TCP segment 之前需要等待的毫秒数。RTO值将会随着时间的流逝(或者更应该说是 ...
CS144 Lab2
Lab-2TCPReceiver需要实现 TCPReceiver,接收传入的 TCP segment 并将其转换成可读的数据流
TCPReceiver 将读入的 segment 载荷交给 StreamReassembler,还需要告诉发送者确认号 ackno 和接收窗口长度 window_sizeackno 是第一个未组装的字节索引,是接收者需要的第一个字节的索引。window_size 是第一个未组装的字节索引和第一个不可接受的字节索引之间的距离。
TCP 接收方可以通过接收窗口进行流量控制,限制发送方发送数据的速度。
状态图参考实验文档。
WrappingInt32TCP 报文中用来描述当前数据首字节的索引(序列号 seqno)是32位类型的,最大表示值为 4GB,存在溢出风险
出于安全性考虑,以及避免与之前的 TCP 报文混淆,TCP 希望让每一个 seqno 难以预测,降低重复的可能性。因此 TCP 使用一个 32 位随机数作为初始序列号 ISN
流中的每个数据字节占用一个序列号,SYN 和 FIN 控制标志也会分别分配一个序列号,SYN 使用的就是 ISN
字节索引类型多样, ...
CS144 Lab1
Lab-1StreamReassembler这次是实现 StreamReassembler,一个流重组器,字节流接收方用于正确重组接收到的字节流的子串。会适应到 Lab-0 中实现的 ByteStream
实验文档有非常好的图文描述,这里不贴了。
因为网络环境的约束,TCP 发送方会将数据分割为多个小段的数据,分次发送,TCP 接收方则必须通过流重组器,将接收到的这些可能被重排、重传的数据包重新组装成新的连续字节流。
实现流的每个字节都有自己唯一的索引,从零开始向上计数。当重组器知道了流的下一个字节,它应该将其写入 ByteStream
传入的子串可能存在重叠部分,且 ByteStream 容量有限,StreamReassembler 自身也需要维护自身容量CS144 要求 ByteStream 持有长度和未重组的 unassembled_strings 长度之和不超过 StreamReassembler 构造函数传入的容量大小。使用位图 std::deque<bool> bitmap 记录缓冲区使用状态。
如果感觉容量和片段关系的比较逻辑不好理解,可以参考实验文档的图。
...
CS144 Lab0
CS144 学习前言打算写一系列博客记录一下自己学习 CS144 的心得,说起来又好久没有更新博客了呢(笑)这次在 CS144 遇到挺多之前并不熟悉的实践,也算是有所收获,以及认识到了 morden C++ 看起来也可以很舒服(其实根本没有用到太多特性)
这门课的 GitHub Repository 会随着每一年的进度被直接清空,所以使用了 PKUFlyingPig 的 fork:https://github.com/PKUFlyingPig/CS144-Computer-Network该课程的课程视频翻译可以参考B站这个视频
环境我使用 VMware + Ubuntu22.04,工具链之前用的就基本齐了,克隆到本地后,根据 Cmake 补上就行
实践基本跟着这个仓库下的 lab_handouts/ 做就行,会告诉你做每个 lab 需要注意哪些地方
Lab-0Networking by handFetch a Web page 使用 telnet 手动发送 HTTP 请求,他们的 cs144 课程网站用不了,随便找个可请求的域名就行。Send yourself an email 则使用 ...
Rust tokio 异步使用记录
Rust tokio 异步使用记录最近学 Rust tokio 时因为一时疏忽,踩了个坑,同时想起几个月没有更新博客,故作此篇,以便提醒未来的我。
基本知识:async/await无栈协程Rust 异步使用 async/await 模型,基于无栈协程(Stackless Coroutine)实现,可以达到轻量级、零内存分配和高效协作式并发。作为比较,Golang 使用有栈协程实现 goroutine。
什么是无栈协程呢?无栈协程的特点是无独立调用栈,每个协程的上下文通过编译时生成的状态机保存,不使用独立的栈内存。
async 函数会被 Rust 编译器转换为一个实现了 Future trait 的结构体,内部使用 enum 记录所有可能的执行状态(如 Start、AfterAwait1、AfterAwait2、Completed 等)。
无栈协程的状态大小固定,无需动态分配栈空间,因此可嵌入其他数据结构(如 struct)中;同时状态机的内存布局在编译期确定,可以轻松通过 Rust 编译器检查。
异步流程简要解释Rust 提供多个组件共同实现异步过程,可以分为异步操 ...
