数据库中排序的一些想法

数据库中很多时候要有排序字段作为排序依据,有时候还会对排序进行调整,常见的我们会加一个xx_sort字段,从1开始的一个排序,那样如果我们把最后一个元素排到第一时会导致每个记录中排序都要进行+1操作,需要操作很多记录;那有没有一种方法可以只修改被调整的那一条记录或者减少更改记录的次数. 今天想到一个方法,在给排序字段赋值的时候,不是按照+1的方式递增,而是以一个间隔递增比如 id order 1 1 2 1001 3 2001 4 3001 在每次调整顺序时,比如把4调整到1和2之间, 那么就在(1,1001)区间范围内随机找到一个值(或者取中间值),赋给4这种情况下只需要调整一次就可以了. 那么如果上面的结果变成这样了, 3想要移动到1和4之间怎么办 id order 1 1 4 2 2 1001 3 2001 和上面一样现在(1,2)区间随机(或者取中间值),发现不能找到一个值,那么就需要一个策略,把后面的一部分数据进行分散. 分散策略比如:按顺序找到后面一条间距大于多少的(比如1000)数据,然后把这几条数据按总间距平均分配一下,或者更简单粗暴的,如果遇到无法插入的值,就把后面的数据重新安排一下,都按初始间隔重新赋值 只要合理的设置间隔,就能达到在大多数情况下都能只修改一条记录 注意: 设置间隔可能需要考虑最多能容纳多少元素,还有字段类型所能容纳的最大值

2023-06-02 10:18:35    |    1 分钟    |    31 字    |    Fengbin

service中的几种port的区别

port : service暴露的端口,可以在集群内进行访问 nodePort : 可以在集群外访问service的端口 targetPort : pod内的container暴露的端口 一张图来理解

2023-05-22 15:00:31    |    1 分钟    |    10 字    |    Fengbin

k8s 几种探针作用和用法

在 Kubernetes 中,探针(probes)是一种机制,用于检查容器是否处于正常状态以及容器是否可以接收流量。 Kubernetes 提供了三种类型的探针:存活探针(Liveness Probe)、就绪探针(Readiness Probe)和启动探针(Startup Probe)。 以下是这三种探针的作用和用法: 存活探针(Liveness Probe) 作用:检查容器中的进程是否仍在运行。如果进程崩溃或停止响应,Kubernetes 将重启容器。 用法:建议在容器的生命周期中始终使用存活探针,以确保容器的可靠性。例如,可以配置一个存活探针来检查容器中的进程是否响应 HTTP 请求或 ping 命令。 示例 YAML 配置: livenessProbe: httpGet: path: /healthz port: 8080 initialDelaySeconds: 5 periodSeconds: 10 该配置文件指示 Kubernetes 发送一个 HTTP GET 请求到容器的 /healthz 路径,并在容器启动后的 5 秒后开始检查容器的运行状况。检查频率为每隔 10 秒发送一次。 就绪探针(Readiness Probe) 作用:检查容器是否准备好接收流量。如果容器尚未准备好接收流量,则 Kubernetes 将不会将流量发送到容器。 用法:建议在容器需要一些额外的时间来启动的情况下使用就绪探针。例如,可以配置一个就绪探针来检查容器是否存在必需的文件或数据库连接。 示例 YAML 配置: readinessProbe: tcpSocket: port: 8080 initialDelaySeconds: 5 periodSeconds: 10 该配置文件指示 Kubernetes 检查容器是否可以在 8080 端口接收 TCP 连接,并在容器启动后的 5 秒后开始检查容器的运行状况。检查频率为每隔 10 秒发送一次。 启动探针(Startup Probe) 作用:检查容器内部应用程序是否已经启动,成功后会运行存活和就绪探针。与存活探针和就绪探针不同,它们在容器启动时运行,并在容器启动完成后禁用。 用法:建议在容器启动时需要启动较长时间的应用程序时使用启动探针。例如,可以配置一个启动探针来检查容器中的某个进程或 Web 服务器是否已经启动。 ...

2023-05-12 11:03:18    |    1 分钟    |    107 字    |    Fengbin

在WSL中配置gitlab/github SSH KEY

背景 在公司里普遍使用搭建的gitlab,很多情况下我们使用终端提交,这时候就会频繁的数据账户和密码,为了不输入密码我们会配置SSH KEY来免登录. 步骤 使用ssh-keygen 来生成公钥和私钥 使用ssh-keygen -t ed25519 或者 ssh-keygen -t rsa -b 2048 没什么特别的一路回车 这时候会在你~/.ssh/目录下生成密钥对 以rsa密钥为例,把~/.ssh/id_rsa.pub的内容配置到gitlab 测试使用ssh git@gitlab.xxx.com(你公司的域名)看下是否正常打印你的配置名称,如果不正常使用ssh -Tvvv git@gitlab.xxx.com打印调试信息,看是哪个地方出了问题 已经可以正常提交代码 可能出现的问题 ssh连接没问题,但是git会提示输入用户密码 这种情况看下自己仓库是否使用的是http连接, 如果是替换ssh链接就可以解决git remote set-url origin git@xxx.com:username/blog.git 使用go来拉取包时如果使用http地址拉取怎么办 可以使用Git提供的凭证存储器来配置HTTP协议的自动登录,Git提供了三种凭证存储器,分别是cache、store和osxkeychain。不同的存储器有各自的优缺点,例如cache存储器将凭证信息保存在内存中,store存储器将凭证信息保存在本地的plain-text文件中,osxkeychain存储器则将凭证信息保存到Mac的Keychain应用程序中. 可以使用git config --global credential.helper store来配置,然后随便拉取一个该域名的项目,输入账号密码就会存储到本地,后面就不会再要求输入用户密码

2023-05-08 14:24:08    |    1 分钟    |    38 字    |    Fengbin

go 编译指令build

go:build 是 Go 1.17 版本中引入的一个构建标记,用于根据不同的条件控制代码的编译。go:build 格式如//go:build <标记表达式>, 1.17之前使用+build的方式,这里我们介绍更强大的的go:build. 表达式可以包含由 ||、&& 和 ! 运算符和括号组合的选项,含义与 Go 语言中相同. 构建约束可以出现在任何类型的源文件中(不仅仅是 Go 文件),但是它们必须出现在文件的顶部附近,仅由空行和其他行注释分隔。这些规则意味着在 Go 文件中,构建约束必须出现在包声明之前。 例如,下面的构建约束将约束一个文件在满足“linux”和“386”约束条件或在满足“darwin”条件且未满足“cgo”条件时进行构建://go:build (linux && 386) || (darwin && !cgo) 自定义tag //go:build atest 当我们使用go build -tags atest的情况下会编译此文件,否则不会编译 控制go版本 //go:build go1.17 指定当前文件>=go1.17才编译 //go:build go1.17 && go1.20 版本>= go1.17 并且<= go1.20

2023-03-07 10:50:47    |    1 分钟    |    47 字    |    Fengbin

Intel hex文件格式

Intel HEX格式文件是一种常见的用于表示程序存储器内容的文本格式,常用于将程序存储器内容导出或者导入到不同的开发工具中。它的文件扩展名通常是 .hex。 该文件格式由一系列行组成,每一行以冒号(:)开头,以回车换行符(\r\n)结尾,每一行包含以下几个部分: 起始符号:一个字符,表示该行的起始,必须为“:”。 数据长度:一个字节,表示该行所包含的数据长度,以字节为单位,范围从 0 到 255。 起始地址:两个字节,表示该行数据在存储器中的起始地址,以字节为单位,高位在前,低位在后。 记录类型:一个字节,表示该行数据的类型。常见的记录类型包括数据记录(00)、结束记录(01)等。 数据:一个或多个字节,表示该行的数据内容。数据的长度由数据长度字段指定。 校验和:一个字节,表示该行数据的校验和,为数据长度、起始地址、记录类型和数据的按位异或和的补码。 例如 :10010000214601360121470136007EFE09D2190140 //起始符号是“:”。 //数据长度是“10”。 //起始地址是“0100”。 //记录类型是“00”,表示这是一个数据记录。 //数据部分包含了16个字节的数据:“214601360121470136007EFE09D21901”。 //校验和是“40”。 //这个数据记录包含了16个字节的数据,它们对应的起始地址是0x0100~0x010F。每两个字节表示一个数据,因此这个记录实际包含了8个数据,分别是“21 46 01 36 01 21 47 01 36 00 7E FE 09 D2 19 01”。 :00000001FF //起始符号是“:”。 //数据长度是“00”。 //起始地址是“0000”。 //记录类型是“01”,表示这是一个结束记录。 //数据部分为空。 //校验和是“FF”。 //这个记录表示文件结束。当解析Intel HEX格式文件时,如果读到了一个记录类型为“01”的记录,就表示已经读到了文件的结尾,应该停止解析。 记录类型 在Intel HEX格式文件中,一共定义了5种记录类型。它们分别是: 数据记录(Data Record),记录类型为“00”。 结束记录(End Of File Record),记录类型为“01”。 扩展段地址记录(Extended Segment Address Record),记录类型为“02”。 扩展线性地址记录(Extended Linear Address Record),记录类型为“04”。 起始段地址记录(Start Segment Address Record),记录类型为“03”。 其中,最常用的是数据记录和结束记录。其他三种记录类型则用于更高级的应用,如程序跳转、内存分段等。 举例: 数据记录(Data Record)::10010000214601360121470136007EFE09D2190140 这是一条16字节的数据记录,其记录类型为“00”。数据部分为“214601360121470136007EFE09D21901”。 ...

2023-02-27 16:45:45    |    1 分钟    |    141 字    |    Fengbin

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