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

在工作中总能碰见有人的接口使用循环查询数据库的情况,针对这种情况我总结了一些针对这种情况的初步优化思路. 限制和优点 限制 此种方法只针对循环查询这种类型的优化 优点 不用完全了解业务逻辑,即可完成有效的优化 方法 概述 先明确循环的位置, 以及循环内要查的数据 采用包一层的思路(计算机界没有什么是包一层不能解决的, 如果不能就再包一层😊), 把查询提前到循环外, 通过提供相同的查询逻辑(在内存里的查询),替换循环中的查询(数据库的查询),必须保证新的查询和老查询功能一致. 听起来好像没什么特别的,我们来看下具体实施过程 伪代码 假设有一个这种函数 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

语法图

语法图又称铁路图,是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]

2022-07-07 18:00:27    |    1 分钟    |    48 字    |    Fengbin

EBNF

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 ...

2022-07-05 15:31:12    |    3 分钟    |    430 字    |    Fengbin