go 编译指令Linkname

go:linkename是go的编译指令,可以在一个包中使用另一个包中的非导出函数或变量 具体使用方法举例 在pkg1包中有个未导出的函数add package pkg1 func add(a, b int) int { return a + b } 在pkg2中想要使用pkg1.add,正常来说add开头是小写是不能被另一个包使用的,这时候我们想要在另一个包中使用就有两种方法: 在pkg1中定义一个可以导出的函数,这种就是在写个Add函数包裹一下,这种就不举例了 在pkg2中使用linkname编译指令修改函数或变量的名称和可见性(不太建议) 这里我们举例第二种 package pkg2 import ( _ "goasm/pkg1" //需要引入pkg1, 让pkg1参与代码编译,否则编译器找不到, 这句也可以写在别的文件中,只要让pkg1参与编译就行, relocation target goasm/pkg1.add not defined _ "unsafe" //编译器要求导入unsafe包,否则不能使用linkname指令 ) //下面就是linkname编译指令使用方法, 在函数声明上添加注释, 其中pkg1_add是函数名称, goasm/pkg1.add 是要连接的函数, 这句指令的效果就是,调用pkg1_add就是在调用goasm/pkg1包中的add函数 //go:linkname pkg1_add goasm/pkg1.add func pkg1_add(a, b int) int func Add2(a, b int) int { return pkg1_add(a, b) } 总结: go:linkname的使用是依赖于编译器的实现,因此使用时需要慎重考虑其可维护性和可移植性。 总之了解就好,不是迫不得已不要真的使用

2023-02-24 14:35:25    |    1 分钟    |    65 字    |    Fengbin

Swap开启和关闭

树莓派 /etc/dphys-swapfile 是 Raspberry Pi 上的一个文件,用于控制swap文件的设置和使用。该文件用于设置swap文件的大小、位置以及启用和禁用swap。 在 Raspberry Pi 上,如果想要使用swap文件,可以通过以下步骤配置和使用swap文件: 安装 dphys-swapfile 包,使用命令: sudo apt-get update sudo apt-get install dphys-swapfile 打开 /etc/dphys-swapfile 文件,配置swap文件的大小和位置。例如,将以下行: CONF_SWAPSIZE=100 #修改为0可关闭swap CONF_SWAPFILE=/var/swap 启用 sudo dphys-swapfile setup sudo dphys-swapfile swapon 禁用 sudo dphys-swapfile swapoff 其他linux 在Linux中,可以通过以下步骤关闭swap: 查看当前的swap情况,使用命令: swapon -s 如果没有任何输出,则说明当前系统没有启用swap。 如果系统启用了swap,需要先关闭swap,使用命令: swapoff -a 执行此命令会关闭所有swap分区。 临时禁用swap,使用命令: echo 0 > /proc/sys/vm/swappiness 执行此命令可以临时禁用swap,即使系统启用了swap,也不会自动将数据交换到swap分区。 永久禁用swap,可以修改/etc/fstab文件,注释掉swap分区的行,或者直接删除swap分区的行。例如,将以下行: #/dev/sda2 swap swap defaults 0 0

2023-02-23 15:37:31    |    1 分钟    |    60 字    |    Fengbin

针对循环查询的逻辑优化思路

在工作中总能碰见有人的接口使用循环查询数据库的情况,针对这种情况我总结了一些针对这种情况的初步优化思路. 限制和优点 限制 此种方法只针对循环查询这种类型的优化 优点 不用完全了解业务逻辑,即可完成有效的优化 方法 概述 先明确循环的位置, 以及循环内要查的数据 采用包一层的思路(计算机界没有什么是包一层不能解决的, 如果不能就再包一层😊), 把查询提前到循环外, 通过提供相同的查询逻辑(在内存里的查询),替换循环中的查询(数据库的查询),必须保证新的查询和老查询功能一致. 听起来好像没什么特别的,我们来看下具体实施过程 伪代码 假设有一个这种函数 func GetPersonList(xx) { ps := models.GetPersonByXX() for _, v := range ps { //xxxx很多逻辑,很复杂 orders := models.GetOrderByPerson(v.ID) //这里循环查数据库,我们需要看懂这块的查询条件和返回值 //xxx很多逻辑,很复杂 } } 我们可以这样优化 //1. 在models层实现一个批量查询接口 models.GetOrderByPersons(id ...int) //2. 包一层实现一个数据缓存结构 type OrderDataCache struct { order map[int/*person id */] []Order //这里的结构根据具体查询定义, 上述例子是通过person id查 } //3. 初始化数据缓存结构函数, 根据你具体的业务实现 func NewOrderDataCache(personID ....int) OrderDataCache { //调用批量查询 orders := models.GetOrderByPersons(id ...int) for _, v := range orders { //处理成[personid] []Order 结构 } return OrderDataCache{ //[personid] []Order } } //4. 用来替换循环查询的方法, 和单次查询的查询条件和返回值保持一致 func (o OrderDataCache) GetOrderByPerson( persionID int) []Order { //这里实现一个和orders := models.GetOrderByPerson(v.ID) 一样的查询逻辑 return o.order[persionID] } func GetPersonList(xx) { ps := models.GetPersonByXX() orderCache := NewOrderDataCache(ps中提取id) //6.批量查询 for _, v := range ps { //xxxx很多逻辑,很复杂 orders := orderCache.GetOrderByPerson(v.ID) //7. 查询替换成我们新的方法 //xxx很多逻辑,很复杂 } }

2023-02-02 17:06:48    |    1 分钟    |    132 字    |    Fengbin

Proto Plugin

1.插件命名规则 ​ proto插件名称需要使用protoc-gen-xxx ​ 当使用protoc –xxx_out时就会调用proto-gen-xxx插件 2.protobuf解析一般流程 方法1: 先通过标准输入生成CodeGeneratorRequest 通过CodeGeneratorRequest初始化插件plugin 通过插件plugin获取文件File 遍历文件,处理内容,生成文件 像标准输出写入响应plugin.Response 示例代码: s := flag.String("a", "", "a") flag.Parse() //生成Request input, _ := ioutil.ReadAll(os.Stdin) var req pluginpb.CodeGeneratorRequest proto.Unmarshal(input, &req) //设置参数,生成plugin opts := protogen.Options{ ParamFunc: flag.CommandLine.Set, } plugin, err := opts.New(&req) if err != nil { panic(err) } fmt.Fprintf(os.Stderr, "a=%s\n", *s) // protoc 将一组文件结构传递给程序处理,包含proto中import中的文件 for _, file := range plugin.Files { if !file.Generate { //显示传入的文件为true continue } fmt.Fprintf(os.Stderr, "path:%s\n", file.GoImportPath) genF := plugin.NewGeneratedFile(fmt.Sprintf("%s_error.pb.go", file.GeneratedFilenamePrefix), file.GoImportPath) //用来处理生成文件的对象 GenFile(genF, file, *s) } // 生成response resp := plugin.Response() out, err := proto.Marshal(resp) if err != nil { panic(err) } // 相应输出到stdout, 它将被 protoc 接收 fmt.Fprintf(os.Stdout, string(out)) 方法2: var s = flag.String("aaa", "", "aaa") var s1 = flag.String("bbb", "", "aaa") flag.Parse() protogen.Options{ ParamFunc: flag.CommandLine.Set, //设置命令行参数 --xxx_out=aaa=10,bbb=20:. }.Run(func(gen *protogen.Plugin) error { //Run内部封装了方法1的步骤 for _, f := range gen.Files { if !f.Generate { //判断是否需要生成代码, 影响这里是否是true的原因是protoc是否指定这个文件 continue } //遍历各种message/service...,同方法1 } }) 示例代码: ...

2022-10-19 11:39:19    |    3 分钟    |    518 字    |    Fengbin

树莓派安装k8s

本地系统为树莓派官方64位系统 Raspberry Pi OS Lite 64(Debian GNU/Linux 11) 在master和node都需要执行的步骤 本机cgroup配置 在执行kubeadm init 时出现 missing required cgroup: memory时,可以在/boot/cmdline.txt(有的系统可能在/boot/firmware/cmdline.txt)中追加 cgroup_enable=memory cgroup_memory=1 cgroup_enable=memory: 启用Cgroup子系统中的内存控制 cgroup_memory=1: 将内存控制子系统的版本设置为1 在某些特定的发行版中,可能会出于兼容性或其他原因而禁用了Cgroup子系统中的内存控制功能,所以需要手动开启 关闭swap 网上很多教程通过编辑/etc/fstab编辑swap, 但是在树莓派系统中,并不使用fstab配置,正确的做法是 编辑/etc/dphys-swapfile 找到配置项CONF_SWAPSIZE (通过名称我们可以知道该配置项为swap大小)该值配置为0 使配置生效 sudo /etc/init.d/dphys-swapfile restart 或者 sudo reboot 重启 通过free -h命令查看swap大小 系统模块加载 # 必要的模块加载 # overlay 文件系统 # br_netfilter 网桥网络包过滤 cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf overlay br_netfilter EOF # 这个命令会将 "overlay" 和 "br_netfilter" 内核模块的名称添加到 /etc/modules-load.d/k8s.conf 文件中,以便在系统启动时自动加载这些模块 sudo modprobe overlay sudo modprobe br_netfilter # sudo modprobe overlay:加载 overlay 的内核模块,该模块提供了用于 overlayfs 的文件系统类型。在使用容器化技术如 Docker 时,通常需要使用 overlayfs 进行容器镜像的存储和管理。因此,在启用容器化环境时,需要确保该模块已加载。 # sudo modprobe br_netfilter:加载 br_netfilter 的内核模块,该模块提供了用于 Linux 桥接网络的过滤和 NAT 功能。在使用 Kubernetes 集群时,通常需要将容器内部的网络流量转发到主机上的网络设备,以实现容器与外部网络的通信。因此,在启用 Kubernetes 集群时,需要确保该模块已加载。 # 开启转发和流量可观测(开机启动) # sysctl params required by setup, params persist across reboots # bridge-nf-call-iptables 让ip表可以看到桥接流量 # bridge-nf-call-ip6tables 让ip6表可以看到桥接流量 cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf net.bridge.bridge-nf-call-iptables = 1 net.bridge.bridge-nf-call-ip6tables = 1 net.ipv4.ip_forward = 1 EOF # 这个命令的作用是将其写入 /etc/sysctl.d/k8s.conf 文件中。在系统启动时,这些参数就会被自动设置。 # net.bridge.bridge-nf-call-iptables: 设置为 1,表示将 Linux 桥接网络中的 IP 数据包转发给 iptables 进行过滤。在使用 Kubernetes 时,需要启用此功能以实现网络策略等功能。 # net.bridge.bridge-nf-call-ip6tables: 设置为 1,表示将 Linux 桥接网络中的 IPv6 数据包转发给 ip6tables 进行过滤。 # net.ipv4.ip_forward: 设置为 1,表示启用 IPv4 数据包的转发功能。在使用 Kubernetes 时,需要启用此功能以实现跨节点的容器网络互通。 # Apply sysctl params without reboot(不用开机立刻生效) sudo sysctl --system CRI安装 根据官方文档配置环境doc ...

2022-10-05 20:49:13    |    3 分钟    |    525 字    |    Fengbin

树莓派系统关闭swap

网上很多教程通过编辑/etc/fstab编辑swap, 但是在树莓派系统中,并不使用fstab配置,正确的做法是 编辑/etc/dphys-swapfile 找到配置项CONF_SWAPSIZE (通过名称我们可以知道该配置项为swap大小)该值配置为0 使配置生效 sudo /etc/init.d/dphys-swapfile restart 或者 sudo reboot 重启 通过free -h命令查看swap大小

2022-10-02 21:10:36    |    1 分钟    |    15 字    |    Fengbin

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

2022-09-23 16:31:24    |    1 分钟    |    16 字    |    Fengbin

errgroup

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一起使用挺不错

2022-09-06 17:22:20    |    1 分钟    |    95 字    |    Fengbin

AMQP 0-9-1协议

约定 下面出现的无特殊说明都是按下面对应关系 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)自动绑定默认交换机. ...

2022-08-02 11:06:49    |    1 分钟    |    98 字    |    Fengbin

Fisher-Yates Shuffle

这个算法发是今天看了一个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] } }

2022-07-27 11:41:33    |    1 分钟    |    80 字    |    Fengbin