硬核——golang之channel、select

在日常开发中,数据结构channel和select语句被高频使用,本文基于Go1.18.1版本的源码,探讨channel的底层数据结构和select访问Channel在编译期和运行时的底层原理 探讨底层原理是一个很奇妙却枯燥乏味的过程,希望读者您能保持足够的耐心,我们开始吧🤗 channel 的底层数据结构 1 ch := make(chan it, 5) 我们通过make关键字创建了一个缓冲区为5存储数据类型为int的channel ch存储在栈上的一个指针,而指向的是堆上的hchan结构体 首先一个channel需要能支持多个goroutine并发访问,这需要一把🔒(lock mutex) 对于有缓冲区的channel而言,需要知道缓冲区的位置(buf unsafe.Pointer)以及缓冲区内有多少个元素(qcount unit),每个元素多大(datasiz uint),所以缓冲区实际上就是一个数组 因为golang运行时中内存复制、垃圾回收等机制依赖数据的类型信息,所以还需要一个指针(elemtype *_type)指向数据的类型元数据 为了支持定时器的功能添加了timer *timer channel支持交替的读写,需要分别记录读和写下标的位置(sendx uint recvx uint),当读和写不能立即完成的时候,需要能够让当前的goroutine在channel上等待,当条件满足时,需要可以立即唤醒等待中的goroutine,所有需要两个等待队列来针对读和写操作(sendq waitq recvq waitq) channel支持被关闭(closed uint32) 综上所述,channel的底层数据结构就长这个样子 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 type hchan struct { qcount uint // total data in the queue dataqsiz uint // size of the circular queue buf unsafe.Pointer // points to an array of dataqsiz elements elemsize uint16 closed uint32 timer *timer // timer feeding this chan elemtype *_type // element type sendx uint // send index recvx uint // receive index recvq waitq // list of recv waiters sendq waitq // list of send waiters // lock protects all fields in hchan, as well as several // fields in sudogs blocked on this channel. // // Do not change another G's status while holding this lock // (in particular, do not ready a G), as this can deadlock // with stack shrinking. lock mutex } 当我们创建一个有缓冲区的channel的时候,recvx和sendx都为0,不断往channel中发送数据的时候,因为没有goroutine在等待接收或发送数据,所以send会不断向后移动,最后移动回起点(recvx = sendx),那么这个时候则表明channel的缓冲区满了,其实channel的缓冲区是一个环形队列,也称之为环形缓冲区 ...

March 20, 2025 · 9 min · 1856 words · Whitea

硬核——你真的搞定Golang接口了么

编译阶段有变量、类型、方法等,那在运行阶段反射、接口、类型断言这些语言特性或机制是怎么动态的获取数据类型信息呢?今天我们就来聊聊这些问题吧😀 类型系统 首先我们要知道在Go中,这些属于内置类型: 1 2 3 4 5 6 7 8 9 10 bool int(32 or 64), int8, int16, int32, int64 uint(32 or 64), uint8(byte), uint16, uint32, uint64 float32, float64 string complex64, complex128 array -- 固定长度的数组 slice -- 序列数组 map -- 映射 chan -- 管道 当然还有自定义类型,比如: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 // custom type based on int type T int // that is different from the one below // type T = int // that just a alias for T, it's type is still int // struct type T struct { name string } // interface type T interface { Name() string } Go不允许为内置类型添加方法,同时接口类型是无效的方法接收者。 ...

March 16, 2025 · 4 min · 809 words · Whitea

OTel可观测性

可观测性 什么是可观测性? 马克·安德森(Marc Andreessen)说过这样一句话:“软件正在吞噬世界 ”。这句话发表于2011年,但是十多年后的今天,我想它更好的演绎应该是“云原生正在吞噬世界,万物皆可上云”。面对云原生这个新赛道,BAT、美团、字节跳动、快手等一线大厂都在加速推进业务的容器化、云原生化。 也正是由于各大厂商对云原生的奔赴,传统的技术架构面临着巨大的冲击,我们的监控对象也由传统的单体结构,变成了分布式的多个微服务。监控,被架到了一个不得不革自己命的位置。在这样的背景之下,可观测性(Observability)脱颖而出。 CNCF 早在定义云原生的概念时就提到了可观测性 ,它声称可观测性是云原生时代的必备能力。而随着可观测性的概念明晰化,相关产品纷纷涌现,“可观测性”越来越成为云原生一个绕不开的话题。 在计算机系统和软件领域,可观测性的含义:它可以从系统和应用对外输出的信息(包括你可能已经知道的指标、日志和链路),来帮助我们了解应用程序的内部系统状态和运行情况。 可观测性能够架起开发人员和运维人员构建合作的桥梁,运维人员使用可观测性来发现问题,给故障现场提供足够的数据让开发人员进行分析,而开发人员可以使用可观测性来指导运维人员定位问题,并使用工具来质疑和验证假设; 从单机电脑时代的Windows任务管理器,Linux一堆Top、PS等命令帮助我们知道操作系统的运行转台;再到局域网时代的C/S架构、互联网时代的B/S架构的网络监控工具;再到如今移动互联网时代,出现了大量的可观测性技术。ELK方案、基于时序数据库的监控软件(如Prometheus、Telegraf + InfluxDB 等,APM 则出现了 ZipKin、Jaeger、Pinpoint、Skywalking 这些软件)然而,如果我们要完整地“观测”一个互联网系统,还是需要将各种形态的开源监控产品组合起来使用。 OpenTelemetry 这个组织的出现标志着业界意识的产生,也就是,我们需要将系统的可观测性变成一种统一的标准和规范。OpenTelemetry 致力于推动更多的应用和服务遵循这一规范,同时也会提供相应的可观测性能力。这也是我们今天这节课的重点 Metrics 指标 是对一段时间内基础设施或应用程序的数值数据的汇总。由于指标最大的特点是聚合性,它生成的数值反映了预定义时间段内系统状态的汇总报告,在此期间处于活动状态的所有请求的行为都会汇总为一个数值,因此缺乏细颗粒度。同时这些指标很可能都是彼此不相关的,没有关联性。 Log 日志 是一种带时间戳的文本记录,可以是结构化,也可以是非结构化的。 结构化日志 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 31 32 33 34 { "timestamp": "2024-08-04T12:34:56.789Z", "level": "INFO", "service": "user-authentication", "environment": "production", "message": "User login successful", "context": { "userId": "12345", "username": "johndoe", "ipAddress": "192.168.1.1", "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36" }, "transactionId": "abcd-efgh-ijkl-mnop", "duration": 200, "request": { "method": "POST", "url": "/api/v1/login", "headers": { "Content-Type": "application/json", "Accept": "application/json" }, "body": { "username": "johndoe", "password": "******" } }, "response": { "statusCode": 200, "body": { "success": true, "token": "jwt-token-here" } } } 非结构化日志 ...

March 8, 2025 · 2 min · 389 words · Whitea

SAST授课-从HTTP到RPC & 服务发现与注册

我想先说说 TCP 大家如果在开发时候遇到希望不同进程可以通讯,常用到的就是 Socket 编程,这是一种面向传输层的网络编程方式,无非就是 TCP 或者 UDP。 大部分人无脑选择 TCP 就好了 这是一种基于字节流的传输方式,就这样一个裸的TCP就可以解决我们收发数据的全部问题了么? 裸TCP的问题 八股文常背,TCP 是有三个特点,面向连接、可靠、基于字节流 我们今天重点关注的是基于字节流这个特点 字节流可以理解成一个双向通道里面流淌的数据,也就是我们常说的二进制数据,说白了也就是一串01串,纯裸 TCP 在收发这一堆 01 串的时候是没有任何边界的, 它根本不知道从哪到哪才是一条完整的消息。 上面这张图就是粘包问题 因此很显然,纯裸的 TCP 是不适合直接拿过来用的,需要制定一些规则用于区分消息的边界 比如我们可以把消息封装一下,分为 Header 和 Body, Header 用于存放一些元数据,比如这个消息有多长, Body 用于存放真正的消息 这样封装方式,只要收发信息的上下游约定好,这就是所谓的协议 因此基于 TCP 就衍生出很多的协议,比如 HTTP 和 RPC HTTP 和 RPC HTTP协议:超文本传输协议 (HTTP) 是一种用于传输超媒体文档(如 HTML)的应用层协议。它专为 Web 浏览器和 Web 服务器之间的通信而设计,但也可用于其他目的,例如机器对机器通信、对 API 的编程访问等 RPC:RPC协议是一种通过网络调用远程服务的方法,使得不同系统间可以像调用本地方法一样进行通信 这两个都是应用层协议,了解微服务的同学应该知道,常常在服务内部一般都会使用 RPC 协议进行服务间通讯,而不是使用 HTTP。要弄懂这个需要从这两个协议的设计原理出发了。 HTTP 协议 HTTP的请求报文由四部分组成:请求行(request line)、请求头部(header)、空行和请求数据(request data) 不过 HTTP 协议不是我们今天的重点 更多内容可以看 MDN文档 ...

March 5, 2025 · 3 min · 560 words · Whitea

SAST授课-入门Redis&了解云原生

入门Redis & 了解云原生 第一部分:Redis快速入门 初步认识 Redis 是一个开源(BSD 许可)的,内存中的键值对(K-V)存储系统,它可以用作 数据库、缓存和消息中间件。 它支持多种类型的数据结构,如 字符串(strings),散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 与范围查询 bitmaps, hyperloglogs 和 地理空间(geospatial) 索引半径查询。 Redis 内置了复制(replication),LUA脚本(Lua scripting),LRU驱动事件(LRU eviction),事务(transactions)和不同级别的磁盘持久化(persistence), 并通过Redis哨兵(Sentinel)和自动分区(Cluster)提供高可用性(high availability) 你得知道: Redis是Nosql型的非关系型数据库(Nosql:Not Only sql) Redis是基于内存存储的,速度快,当然也支持数据的持久化 Redis是在处理客户端请求的时候是单线程的,在处理客户端连接时候使用IO多路复用技术 Redis其实是支持事务的,但是一次性、顺序性、排他性的执行队列中的命令,是不支持回滚的,缺乏传统ACID事务的隔离性和持久性 安装 Windows: 官方推荐使用WSL2官网指南,当然也可以在Github上下载Zip,解压缩到本地,然后在安装目录执行redis-server.exe redis.windows.conf即可临时启动 Linux & Mac: 使用包管理器安装即可 但待会我们会讲容器技术,这会让Redis的安装使用更加方便😀 常用命令 使用select切换数据库:select [number] (redis共16个数据库) 查看数据库大小:DBSIZE 查看库内容:keys * 判断某个值存在:exists key 存储数据:set key value云 数据限时:expire key number\setex key number value限时多少秒后数据失效,ttl key查询还有多久截止 查看key类型:type key 删除数据库:flushall or flushdb DbName 批量插入、获取:mset k1 v1 k2 v2...\mget k1 k2 k3... 五大基本数据类型 不用记,用的时候查就行🤣 ...

February 27, 2025 · 3 min · 620 words · Whitea

初探微服务架构

先聊聊单体架构吧 在一个项目的最初阶段,只有几个人参与的时候,想着业务能够快速迭代,往往采用单体应用的架构。求快,因此会把不同功能模块耦合在一起,编译打包部署也都在一起。 但是当业务规不断扩大,团队规模不断扩大,问题就慢慢暴露出来了。代码的高耦合,很难进行高效的横向扩展、每次部署上线都要重新部署整个应用、CI/CD和测试也逐渐变得更加耗时且容易因为一个很小的问题就失败… 于是团队为了解决这些问题,做了很细致的技术调研,最后选定了服务化的解决方案,对原有的单体应用架构进行改造,把功能相对独立的模块拆分出去,部署为微服务,分别交给专门的更小的团队来维护。后来我们又引入了Docker容器化,以及Service Mesh等技术,为了更好地适应业务的高速发展。 这就是应用架构经历了经历了单体应用 - 微服务架构 - 容器化应用 - DevOps的发展历程 微服务化拆分 因此当团队出现上述类似的问题时,往往就该考虑将单体架构应用进行拆分。 拆分往往分为横向拆分和纵向拆分 纵向:是从业务维度进行拆分。标准是按照业务的关联程度来决定,关联比较密切的业务适合拆分为一个微服务,而功能相对比较独立的业务适合单独拆分为一个微服务 横向:是从公共且独立功能维度拆分。标准是按照是否有公共的被多个其他服务调用,且依赖的资源独立不与其他业务耦合 但同时从单体架构向微服务的迁移会暴露出更多的问题 服务如何定义 服务如何发布和订阅 服务如何监控 服务如何治理 故障如何定位 微服务架构 总结一下,微服务架构下,服务调用主要依赖下面几个基本组件: 服务描述 注册中心 服务框架 服务监控与追踪 服务治理 服务描述 服务如何对外描述就是当我们对外提供一个服务,服务的服务名叫什么?调用这个服务需要提供哪些信息?调用这个服务返回的结果是什么格式的?该如何解析? 常见的一般是RESTful API,IDL文件等等。前者往往是基于HTTP,可以通过Wiki或者Swagger来管理。后者往往基于RPC,通过IDL文件管理 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 service ProductCatalogService { rpc AddProduct(AddProductReq) returns (AddProductResp) {} rpc UpdateProduct(UpdateProductReq) returns (UpdateProductResp) {} rpc DeleteProduct(DeleteProductReq) returns (DeleteProductResp) {} rpc OnlineProduct(OnlineProductReq) returns (OnlineProductResp) {} rpc OfflineProduct(OfflineProductReq) returns (OfflineProductResp) {} rpc ListProducts(ListProductsReq) returns (ListProductsResp) {} rpc GetProduct(GetProductReq) returns (GetProductResp) {} rpc BatchGetProducts(BatchGetProductsReq) returns (BatchGetProductsResp) {} rpc SearchProducts(SearchProductsReq) returns (SearchProductsResp) {} rpc GetCategories(GetCategoriesReq) returns (GetCategoriesResp) {} rpc GetCategory(GetCategoryReq) returns (GetCategoryResp) {} rpc DecrStock(DecrStockReq) returns (DecrStockResp) {} rpc IncrStock(IncrStockReq) returns (IncrStockResp) {} } 注册中心 提供了一个服务,如何让外部想调用你的服务的人知道。这个时候就需要一个类似注册中心的角色,服务提供者将自己提供的服务以及地址登记到注册中心,服务消费者则从注册中心查询所需要调用的服务的地址,然后发起请求 ...

February 10, 2025 · 1 min · 140 words · Whitea

SAST授课-SpringBoot入门

前言 Spring Boot是一个流行的Java框架,用于简化Spring应用程序的创建和部署。它通过提供一个集成的开发环境,使得开发人员能够快速构建高效的应用程序。同时本节课的目的主要在于编码的实践,概念很多,一开始记不住没关系,但一定要跟着写一写! 为什么要面向接口编程 面向接口编程(Programming to an Interface)是一种设计理念,在Spring Boot等框架中广泛应用。以下是Spring Boot中面向接口编程的几个重要原因: 降低耦合度:面向接口编程可以减少模块之间的耦合。通过依赖接口而非具体实现,系统的不同部分可以独立开发和修改,降低了修改某一部分时对其他部分的影响。 增强可测试性:使用接口可以方便地进行单元测试。因为可以轻松地创建接口的模拟实现(Mock),在测试中可以用这些模拟对象替代真实对象,从而隔离测试环境,确保测试的独立性。 提高灵活性,扩展性和可维护性:面向接口编程允许在不改变客户端代码的情况下,轻松替换或扩展实现。只需提供新的实现类,而不必修改使用该接口的代码,使得系统更具灵活性。同时当系统的实现细节被封装在接口后,未来的维护和改进工作将更加简单。修改实现不影响接口的定义,减少了维护成本。 清晰的设计架构:接口提供了清晰的契约,使得开发人员能够明确各个模块的功能和责任。这种设计方式也使得团队协作变得更加简单,便于不同开发人员理解系统的整体架构。 那跟new一个对象有什么区别呢? 直接使用具体实现: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class EmailService { public void sendEmail(String message) { // 发送邮件逻辑 } } public class UserService { private EmailService emailService = new EmailService(); public void registerUser(String email) { // 注册用户逻辑 emailService.sendEmail("欢迎注册: " + email); } } 使用接口: ...

November 21, 2024 · 6 min · 1090 words · Whitea

SAST授课-啥是后端,如何学呢 🤔

碎碎念 首先欢迎大家成功通过笔试面试亦或者是免试进入到SASTWeb研发组,祝愿大家在这里能够学习到自己感兴趣的知识,结识更多志同道合的朋友。但是要记住,加入SAST,意味着更大的舞台,更多的资源,但并不代表你很厉害。 沉下心去学,学习的路真的还挺长的。😵 什么是后端 后端是指一个软件系统的服务器端,也称作服务器端。 它是指在一个软件系统中,负责处理数据存储、业务逻辑处理和与前端交互的一部分。 后端通常包括数据库、服务器、应用程序和其他相关组件。 比如: b站这个页面就是前端展示出来的,但是着其中的每一个词条,视频,数据来源都是来源于后端数据库,然后经过后端程序一些算法逻辑传递给前端进行展示。 GitHub(全球最大同性交友平台)的登录界面,这个界面就是前端展示的,而当我们输入Username和Password并点击Sign in之后,数据会传递给后端服务器处理,只有当后端服务器校验通过后,才会给前端返回正确的信息,前端才会跳转主页。 后端开发用什么语言 TIOBE Index - TIOBE 在国内,Java仍是后端开发的主流语言,凭借其成熟的生态和广泛的社区支持,成为众多企业的首选。在招聘市场上,Java技能依然是后端开发岗位的主要要求。然而,随着云原生技术的发展,Go和Rust等新兴语言也逐渐崛起,特别是在字节跳动、腾讯等大厂,GoLang已经被广泛应用于后端开发。同时,Python、C#和Node.js等语言在特定场景下也发挥着重要作用,丰富了后端开发的语言选择。 主流后端开发语言:JAVA、C、C++、GO、PYTHON对比 Top 7 Programming Languages for Backend Web Development - GeeksforGeeks 我们目前的授课计划前期依旧是以Java为主。 学习后端然后干嘛 这里有两种不同的情况:一种是你对后端开发感兴趣,想初步了解后端开发的实际工作内容;另一种是你计划在大学期间持续深入学习后端开发,最终达到企业招聘的标准,未来寻找后端开发相关的岗位。 强烈建议大家尽早规划好自己的大学四年和职业发展方向,无论是考研、保研、本科就业、研究生就业,还是考公、从事科研。目前大部分人最终都会走向就业市场,因此在大学四年内,专注于一个垂直领域并持续深入学习,会让你在校招中更具竞争力。(不要以为找工作还很遥远,现实是如果你仅仅按部就班跟随学校课程,毕业后找到理想工作的难度会非常大。很多人选择考研,其实只是为了暂时逃避就业的压力) 此外,建议大家了解一下当前后端开发工程师的行业薪资水平,以便更好地衡量自己的目标和努力方向。 南京Java工资待遇_收入水平-BOSS直聘 (zhipin.com) 根据许多学长的经验,对于前后端开发方向的同学来说,考研的意义并不如就业那么明显(考研三年所积累的知识和三年实际工作经验相比,差距较大)。很多学长在大三时会主动寻找相关岗位的实习机会,这段实习经历往往会让他们在大四的秋招中对那些没有实习经历的同学形成降维打击,因为企业更倾向于招聘有实习经验的学生。 但无论你是打算简单了解后端开发的工作流程,还是计划深入学习并以此为职业发展方向,跟着我们的课程学习都是非常合适的选择!通过这些课程,你可以逐步积累专业知识,为未来的实习或就业打下坚实基础。 你可以不来上课,可以自学,但是你的进度不可以比我们的课程进度慢,否则你就慢了( 学习路线 这里粘贴一份网上的学习路线,还比较全,里面也给到了很多学习资料的推荐。 对于纯小白而言(比如我)刚开始是不适合看文档进行学习的,推荐大家跟着一门视频课程进行学习,但在后期要逐渐拓宽自己接受知识的途径,学会看文档高效学习。 codefather/学习路线/Java学习路线(github.com) The Roadmap to Becoming a Backend Java Developer | by Hussein Shamas | Medium 计算机教育中缺失的一课 · the missing semester of your cs education (missing-semester-cn.github.io) ...

October 15, 2024 · 2 min · 227 words · Whitea

KMP算法-来自leetcode-28的思考

本文的思考来自于leetcode-28.找出字符串中第一个匹配项的下标leetcode链接 什么是KMP算法 在计算机科学中,克努斯-莫里斯-普拉特字符串查找算法(英语:Knuth–Morris–Pratt algorithm,简称为KMP算法)可在一个字符串S内查找一个词W的出现位置。一个词在不匹配时本身就包含足够的信息来确定下一个匹配可能的开始位置,此算法利用这一特性以避免重新检查先前配对的字符。(引用维基百科) ...

June 20, 2024 · 3 min · 574 words · Whitea