Welcome to my blog.
Qemu+gdb裸机调试
假设我们有一个boot.bin裸机汇编程序 我们想用qemu进行调试 我们可以使用qemu-system-x86_64 -s -S boot.bin 参数说明: -s 可以使qemu开启1234端口,方便我们使用gdb连接调试 -S 可以让qemu暂停执行,等待我们使用gdb发送调试命令 gdb 连接: 使用gdb命令,进入gdb后使用target remote 127.0.0.1:1234连接到qemu
Welcome to my blog.
假设我们有一个boot.bin裸机汇编程序 我们想用qemu进行调试 我们可以使用qemu-system-x86_64 -s -S boot.bin 参数说明: -s 可以使qemu开启1234端口,方便我们使用gdb连接调试 -S 可以让qemu暂停执行,等待我们使用gdb发送调试命令 gdb 连接: 使用gdb命令,进入gdb后使用target remote 127.0.0.1:1234连接到qemu
errgroup是一个很简单的工具包,总共代码量加上注释和空行才100来行 作用就是方便执行的任务,比如 g := errgroup.Group{} g.Go(func1) g.Go(func2) if err := g.Wait(); err != nil { //... } 结构 type Group struct { cancel func() //context 取消函数 wg sync.WaitGroup //用来等待全部执行完成 sem chan token //用来控制并发数 errOnce sync.Once //控制err字段只赋值一次 err error //错误 } 主要函数 func (g *Group) Go(f func() error) { if g.sem != nil { g.sem <- token{} //限制并发数, 并发数由管道能容纳下的token个数决定 } g.wg.Add(1) go func() { defer g.done() if err := f(); err != nil { g.errOnce.Do(func() { //如果出错后只赋值一次err g.err = err if g.cancel != nil { //如果使用的是WithContext,这个字段会有值, 调用取消函数 g.cancel() } }) } }() } 主要的东西就这么点, 可以看到errgroup还是一个很简洁的小工具, 配合singleflight一起使用挺不错
约定 下面出现的无特殊说明都是按下面对应关系 Publishers(发布者/生产者) Consumers(消费者) Exchanges(交换机) Broker(中间件) Queues(队列) Bingdings(绑定) AMQP是什么 AMQP是Advanced Message Queuing Protocol的缩写,高级消息队列协议,是一种消息传递协议 中间件和角色 消息中间件从Publishers(发布者/生产者)接收消息,路由到Consumers(消费者) 因为AMQP是网络协议, 发布者,消费者,中间件能够在不同的机器上. AMQP模型简介 AMQP模型有下面的视角: 消息被发布到交换机(exchange,通常被比作邮局或邮箱) 交换机(exchanges)根据绑定(bindings)规则把消息复制到队列(queues) 中间件(borker)把消息投递到订阅队列或者从队列中拉取的消费者(consumers) 当一个消息发布时, 生产者可以设置一些消息属性(消息元数据meta data).一些元数据被中间件使用,剩下其他的被不透明发送到中间件给应用程序使用. 网络是不可靠的,应用也有可能处理消息失败,AMQP 0-9-1有一个消息应答(acknowledgements)的概念:当一个消息派发给消费者,消费者会通知中间件,可以自动执行或者开发者选择执行.当使用消息确认时,收到消息的通知就会从队列里删除该消息. 在某些情况,比如一个消息无法被路由,消息将会返回给生产者,丢弃,或者如果中间件实现一个扩展放入死信队列.生产者通过发布消息携带某些参数来处理这些情况. 队列(queue)、交换机(exchanges)和绑定(bingdings)统称为AMQP实体. AMQP是一个可编程的协议 AMQP是一种可编程协议,AMQP的实体和路由方案主要由应用程序自己定义,而不是由中间件管理员定义.因此,为声明队列和交换机、定义它们之间的绑定、订阅队列等操作做出了规定. 这给应用程序开发者很大的自由,但是这也要求他们意识到潜在的定义冲突,在实践中定义冲突很少,通常是配置错误。 应用定义他们需要的实体,定义必要的路由规则和不在使用时删除实体 交换机和交换机类型 交换机是向其发送消息的AMQP实体,交换机接收消息并将其路由到零个或多个队列中。使用的路由算法取决于交换机类型和绑定,AMQP协议提供四种交换机类型: 交换机类型 默认名称 Direct exchange(直接交换机) (Empty string) and amq.direct Fanout exchange(扇出交换机) amq.fanout Topic exchange(主题交换机) amq.topic Headers exchange(头交换机) amq.match (and amq.headers in RabbitMQ) 除了交换机类型之外,交换机还使用许多属性来声明,其中最重要的是: Name (名称) Durability (中间件重启后持久化) Auto-delete (最后一个队列解除绑定自动删除) Arguments (参数,可选的, 由插件和中间件特定功能使用) 交换机可以是持久的或者是临时的,持久性交换机在中间件重启后仍能存在,而暂时性交换机则不能.并非所有场景和用例都要求交换机持久化. 默认交换机 默认交换机是一个没有名称预定义在broker的直接交换机(direct exchange),它有一个特殊的特性,这使得它对于简单的应用程序非常有用:每个被创建的队列都会用和队列名称相同的路由键(routing key)自动绑定默认交换机. ...
这个算法发是今天看了一个go的代码库看到的,通过lo库看到有个Shuffle函数点进去看了一下,调用的数标准库中的shuffle算法,看了一下介绍,感觉有点意思,记录一下 Fisher-Yates算法是什么 Fisher-Yates算法 是一种生成随机排列的算法 核心原理 — To shuffle an array ‘a’ of ‘n’ elements: //对于一个规模为n的集合 for i from n-1 down to 1 do //从后向前遍历 j = random integer such that 0 <= j <= i //随机一个从0-i数字 exchange a[j] and a[i] //交换随机下标和当前下标的元素交换 用语言描述就是: 从后向前遍历 随机一个下标范围是0到当前下标,(范围包含当前下标是因为可能不交换) 交换随机下标指向的值和当前下标指向的值 go语言描述 func init(){ rand.Seed(time.Now().Unix()) } func Shuffle[T any](t []T) { for i := len(t) - 1; i > 0; i-- { j := rand.Int() % (i + 1) t[i], t[j] = t[j], t[i] } }
语法图又称铁路图,是EBNF(扩展巴克斯范式)的图形化表示 从左边界开始沿着轨道去到右边界。 沿途,你将在圆框中遇到的是字面量,在方块中遇到的是规则或者描述。 任何沿着轨道能走通的序列都是合法的。 任何不能沿着轨道走通的序列都是非法的。 /* a simple program in EBNF − Wikipedia */ program ::= 'PROGRAM' whiteSpace identifier whiteSpace 'BEGIN' whiteSpace (assignment ";")* 'END.' assignment ::= identifier ":=" ( number | identifier | string ) string ::= '"' [A-Z0-9_]+ '"' identifier ::= [A-Z] [0-9A-Z]* whiteSpace ::= [#x20]
EBNF 扩展巴科斯-瑙尔范式(EBNF, Extended Backus–Naur Form) 是表达作为描述计算机编程语言形式语言(是用精确的数学或机器可处理的公式定义的语言) 的正规方式的上下文无关文法 的元语法(metalanguage)符号表示法。它是巴科斯范式(BNF) 元语法符号表示法的一种扩展。 简单的理解就是用来描述语言词法和语法规则的语言 ISO/IEC 14977标准 基本形式 LEFT=RIGHT 意思为LEFT可由RIGHT推导而来,LEFT为非终结符,RIGHT可以为非终结符也可以为终结符; 非终结符 简单的理解就是可以继续推导的符号 终结符 不可被推导的符号 符号 符号 含义 示例 = 定义 CharA=“a”; 代表CharA由字母a推导而来 , 连接符 a,b,c 代表abc是挨着的 ; 结束符 CharA=“a”; 代表 CharA这条语句定义结束 | 或者 digit = “0” | “1” | “2” | “3” | “4” | “5” | “6” | “7” | “8” | “9”; […] 可选,出现0次或1次 number = ["-"|"+"],digit 可匹配 1 -1 +1 … {…} 重复,出现>=0次 number = ["-"|"+"],digit,{digit} 可匹配 1 -1 +1 11 … (…) 分组 number = ("-"|"+"),digit 符号必须添加,可匹配 -1 +1 … “…“或者’…' 终结符,单引号主要是一些特殊情况,比如双引号 “a"或者’a’ 由单或双引号引起来的部分是终结符,就是代表字母a,不可继续推导 (* … *) 注释 (*我是注释*) 注释不参与定义 ?…? 表示其中的内容具有特殊含义,对该含义的定义不在 EBNF 标准之内,有使用者来决定 space = ? US-ASCII character 32 ?; - 排除(将右边的内容从左边进行排除) string= allVistableChar - '”’ 表示在allVistableChar里排除"字符 示例 只允许赋值的简单编程语言可以用 EBNF ...
计算机网络分层 市面上对网络分层主要是有三种分层 七层 五层 四层 应用层 应用层 应用层 表示层 会话层 传输层 传输层 传输层 网络层 网络层 网络层 数据链路层 数据链路层 网络接口层 物理层 物理层 每层的职责 物理层 提供物理介质,电压信号等功能 数据链路层 提供P2P传输 (点对点的, 比如一个路由器到另一个路由器) 网络层 提供E2E传输 (Endpoint to Endpoint,两个端点的传输,中间可能经过若干个路由器,注意区别P2P, E2E > P2P) 传输控制层 提供进程到进程的传输(端口到端口的传输) 应用层 应用自定义个协议 每一层都是通过下层对上层提供接口的形式来提供服务 常用网络设备 交换机 交换机工作在数据链路层, 通过mac地址进行转发, 全双工网络设备, 可以隔离碰撞域, 减少链路上的信号碰撞,提高链路网络利用率 路由器 路由器工作在网际层, 通过ip进行转发, 全双工网络设备, 可以隔离广播域(广播不能通过路由器)
信号量 信号量(Semaphore),有时被称为信号灯,是在多线程环境下使用的一种设施,是可以用来保证两个或多个关键代码段不被并发调用。在进入一个关键代码段之前,线程必须获取一个信号量;一旦该关键代码段完成了,那么该线程必须释放信号量 简单的说就是通过获取资源和释放资源来进行同步的一种策略 用法 用法一共有以下几步: 创建信号量 获取信号量 释放信号量 //1.创建信号量为10 sem = semaphore.NewWeighted(10) for i := 0; i <100; i++ { go func() { ctx := context.TODO() //2.获取一个信号量, 信号量一共10个,获取最多获取10,超过的gorutine会挂起 if err := sem.Acquire(ctx, 1); err != nil { doSomething() } //3. 释放信号量,1个 sem.Release(1) }() } 代码解读 Weighted 结构(NewWeighted 返回的数据结构) type Weighted struct { size int64 //总大小,就是NewWeighted传入的个数 cur int64 //当前消耗的个数 mu sync.Mutex //互斥锁 waiters list.List //等待列表, 当信号量不足时等待的列表 } waiter 结构(等待列表保存的结构) type waiter struct { n int64 //需要的资源数 ready chan<- struct{} // 用来通知gorutine } Acquire 方法 func (s *Weighted) Acquire(ctx context.Context, n int64) error { s.mu.Lock() //判断当前资源是否足够,如果足够则累加s.cur直接返回 if s.size-s.cur >= n && s.waiters.Len() == 0 { s.cur += n s.mu.Unlock() return nil } //如果n 大于总资源就会等待超时或取消,(如果这种情况使用的是context.TODO,或者Background, Done返回nil,就会一直等待,导致gorutine泄露) if n > s.size { s.mu.Unlock() <-ctx.Done() return ctx.Err() } //这里就是判断,总资源够,但是剩余资源不够的情况, 这时就需要其他获取资源的groutine释放资源 ready := make(chan struct{}) w := waiter{n: n, ready: ready} elem := s.waiters.PushBack(w) //把当前等待的放入队列 s.mu.Unlock() //等待超时或者资源充足 select { case <-ctx.Done(): //等待超时 err := ctx.Err() s.mu.Lock() select { case <-ready: //如果超时后立刻有足够资源时也会返回 err = nil default: //超时后的逻辑 isFront := s.waiters.Front() == elem //判断当前等待的是队列头部元素(因为资源被释放后优先分配给队列头部的) s.waiters.Remove(elem) //移除超时的waiter if isFront && s.size > s.cur { //去掉头部后并且有剩余就看下,下个waiter能否满足 s.notifyWaiters() } } s.mu.Unlock() return err case <-ready: //等待资源 return nil } } notifyWaiters 方法 func (s *Weighted) notifyWaiters() { for { //取出队列头部的waiter next := s.waiters.Front() if next == nil { //没有等待的waiter就退出循环 break } w := next.Value.(waiter) //如果资源不能满足则退出循环 if s.size-s.cur < w.n { break } //如果能满足,就移除当前元素,关闭waiter.ready(Acquire 中 <-w.ready 就能取消阻塞) s.cur += w.n s.waiters.Remove(next) close(w.ready) } } Release 方法 func (s *Weighted) Release(n int64) { s.mu.Lock() //从当前使用减去n s.cur -= n if s.cur < 0 { //如果Release的资源超过,Acquire的资源就会发生panic s.mu.Unlock() panic("semaphore: released more than held") } //唤醒waiter s.notifyWaiters() s.mu.Unlock() }
安装步骤 根据官方文档使用 curl -sfL https://rancher-mirror.rancher.cn/k3s/k3s-install.sh | INSTALL_K3S_MIRROR=cn sh - 一条命令安装 安装过程的坑 建议使用Raspberry OS, 之前使用过Ubuntu 22.04 安装后发生节点有时Ready有时NotReady反复横跳, 最后用Raspberry OS安装成功 Raspberry也有点小坑,需要在/boot/cmdline.txt文件最后用空格,不要换行, 添加cgroup_memory=1 cgroup_enable=memory, 然后重启 `` 官方文档
链路追踪是什么 链路追踪是在分布式条件下将一个请求还原成一个完整调用链条,可以分析调用拓扑,延迟分析,性能分析. 链路追踪的好处 分析网络,服务耗时(通过链路追踪事件可以知道网络延迟,服务延迟) 分析网络拓扑(链路分析) 故障定位(配合日志,进行故障定位) 原理 Trace Trace代表一个调用链路,通过TraceID来标记, 一次请求调用的各个服务TraceID在全局都是唯一的 Span Span代表一个调用范围拥有(ParentID, SpanID), ParentID代表他的调用者SpanID, SpanID代表本层次调用id 通过TraceID标记一个完整调用链都调用了哪些调用过程, 通过ParendID,和SpanID还原了调用的父子关系 Annotation 通过上面三个ID只能还原调用关系, 还不能进行性能分析和定位,所以还要添加一些辅助的注解信息, 可以同定义事件比如: Client Send: 客户端调用开始 Client Receive: 客户端调用结束 Server Send: 服务端发送 Server Receive: 服务端接收 图一是一个调用关系图,展示了TraceID, SpanID, ParentID 之间的关系,和传递 其中,个方框是一个服务,箭头代表调用关系,一个完整调用链中trace是相同的, 每个服务有各自的SpanID, ParentID是调用方的SpanID, 通过这些ID我们可以知道调用的上下级关系 图二是通过Annotation附带信息进行性能分析,通过Client Send 到 Server Receive可以分析出请求服务的网络时延;通过Server Receive到Server Send可以分析出调用时延;同理Server Send到Client Receive分析出响应的网络时延, Client Send到Client Receive整个请求的时延