https://pic4.zhimg.com/v2-e263475646652726731e13e44b5d2479_im.jpg

TCP协议中的KeepAlive机制

HTTP中的keep-alive 在HTTP的头部中,也有一个字段 connection: keep-alive 此字段告诉服务器HTTP会复用这一条TCP连接,当前的HTTP请求完毕之后不会立即断开连接,还可以被另外的HTTP请求复用这条连接,这样就可以减少建立TCP连接的次数 ​ TCP的长连接和短连接 TCP连接并没有分为长短连接,长短连接只是在于你如何使用TCP 如果建立TCP连接短暂通讯结束之后立即释放这条连接则此连接就为短连接,比如我HTTP发一次请求就建立一次TCP连接,HTTP响应回来之后就立即断开TCP连接 短连接的缺点就是需要频繁的建立TCP连接,比较耗时,优点就是可以很快的释放TCP连接队列,以便于空出位置建立其他TCP连接 如果建立TCP连接在使用时候不立即断开,如果一直没有请求的话就将这条连接空闲即可,以便与后面如果还要请求通讯的话就不需要建立TCP连接了,直接复用那条已经建立好的TCP连接即可。比如我HTTP请求建立TCP连接,响应结束之后浏览器不会立即释放与服务器的TCP连接,如果下次还有其他请求的话则可以复用之前建立的TCP连接即可,如果一时半会儿还没有请求的话只需要将TCP连接空闲着即可,等下次有请求了就可以继续用这条连接进行传输 长连接的优点就是可以减少建立TCP连接的消耗,复用连接。缺点就是如果很长一段时间都没有数据可发的话就会占用操作系统的TCP全连接队列,如果TCP全连接队列比较紧张的话那么就无法建立其他的TCP连接了 ​ TCP的KeepAlive机制 TCP的KeepAlive机制会在指定的间隔时间发送KeepAlive探活包,如果收到了KeepAlive包的ACK则表明对方还在,如果多次发送KeepAlive包都得不到回复则可以认为对方已经挂了,就可以将这个TCP连接从队列里面删除了 KeepAlice主要有如下几个作用: 保活 解决窗口值为0的情况 在空闲的长连接里我们可以通过KeepAlive机制探测对方是否存活,如果收到ACK则表明对方还存活并且还需要复用这条连接 如果多次发送KeepAlive都收不到则说明对方已经挂了或则对方拒绝回复,这样就表明这条长连接可以断开清除了。如果没有KeepAlive机制如果对方挂了则这个连接还会一直保留在服务区占用着TCP全连接队列 还有一种情况就是在接收方的窗口值为0的情况下,那么发送方就不能继续发送数据了,那么即使接收方的窗口扩大了也没有办法告诉发送方,此时就相当于死锁了双方都不会发送数据 所以KeepAlive探测包还可以用于解决窗口值减少为0而造成死锁的情况。当接收方的窗口为0的时候,发送方可以定时的发送KeepAlive包来探测接收方的窗口是否扩大了 ​ Linux中KeepAlive相关配置 #7200 每7200s发送KeepAlive包 /proc/sys/net/ipv4/tcp_keepalive_time #75 如果75s内没有收到KeepAlive的ACK则再次发送 /proc/sys/net/ipv4/tcp_keepalive_intvl #9 如果重试9次都没有响应则把此连接删除掉 /proc/sys/net/ipv4/tcp_keepalive_probes ​ KeepAlive能携带数据吗 注意,KeepAlive包的seq也是没有意义的,seq一般和初始化的seq一致,KeepAlive是不携带数据的

Go处理error

大道至简的error go的错误处理就只有一个errors包和一个error接口,这个接口只包哈一个Error方法,该方法返回一个string,这个包的代码很少,只有两个文件: errors.go wrap.go go通过返回值来返回错误而不是通过try/catch,除非函数能保证一定能执行成功,否则每个函数都必须返回一个error 并且go的error建议只处理一次,也就是说如果你处理过了错误那么就不需要返回给上层了,如果将已经处理过的错误继续返回给上层则这个错误可能会被重复处理 func f() error{ return errors.New("error") //返回error接口 } 下面来看下errors/errors.go的源码,不过10行左右 func New(text string) error { return &errorString{text} //返回errorString指针 } //实现了error接口 type errorString struct { s string } //获取错误字符串的方法 func (e *errorString) Error() string { return e.s } ​ 自定义error 我们只需要实现error接口也就是重写Error()方法即可自定义错误 type ZeroDivisionError struct { msg string code int } func (e ZeroDivisionError) Error() string { return fmt.Sprintf("[%d]:%s", e.code, e.msg) } type NullPointerError struct { msg string } func (e NullPointerError) Error() string { return fmt.

MySql事务和锁

为什么需要有事务 为了解决数据的一致性,保障数据的正确 ​ 事务四大特性ACID 原子性 Atomicity 事务是数据库的逻辑工作单位,不可分割,事务中包含的各操作要么都做,要么都不做 一致性 Consistency 一致性是指事务将数据库从一种一致性状态变为下一种一致性状态,在事务开始之前和之后,数据库的完整性约束没有被破坏 比如转账的例子,两个账户的钱款总量在转账前后还是一致的,如果B余额增加了但是A余额没有扣除那么就会凭白无故多出一部分钱来,这就引发钱款不一致的状况了 再比如A的余额为0了,此时A还要给别人转账就不合适了,因为余额必须>=0。如果余额出现负数,这也导致了数据不一致状况 还有年龄不能为负数,红绿灯只有三种颜色,人民币最大面值为100等,这些约束都必须要符合真实世界的情况,都必须和真实世界保持一致性 隔离性 Isolation 每个事务都是隔离的,互相不影响,一共有4个隔离级别 不同事务在提交的时候,必须保证最终呈现出来的效果是串行的。比如两个顾客同时买一件衣服,衣服总量只有2件了。此时顾客A买了1件提交了,顾客B买了1件也提交了。那么最终的衣服总量必须减少为0,而不是1。如果在RR隔离级别下,顾客A和B在事务没提交之前看见的衣服总量都为2,因为他们是同时开启事务的,假设A先提交了,但是B看到的余量依旧是2件(RR隔离级别),当B再买并且提交之后,则必须还剩余0件,而不是剩余1件,必须保障数据的一致性。也就是说虽然他们看起来是并行执行的事务,但是最终的效果一定要是串行的效果。 持久性 Durability 事务一旦提交,它对数据库中的数据的改变就应该是永久性的,必须持久化到磁盘 ​ MySql事务的实现 原子性: undo log 实现 (记录了事务修改之前的数据,当事务在执行过程中如果失败了那么当前事务就处于不一致的状态,这样可以回滚到上一个一致性的状态) 持久性: redo log 实现 隔离性: 锁+MVCC 实现 一致性: 通过原子性、持久性、隔离性实现 ​ 隔离性和四大隔离级别 1、读未提交 Read Uncommitted 一个事务还没提交时,它做的变更对他事务可见 脏读 不可重复读 幻读 2、读已提交 Read Committed Oracle默认的隔离级别 一个事务只有提交时它做的变化才对其他事务可见,该级别会造成 在事务中两次读取数据不一致的情况,也就是不可重复读 不可重复读 幻读 3、可重复读 Repeatable Read MySql、Innodb默认的隔离级别 一个事务开始之后,其所看见的数据就是事务开始时候的数据,相当于给数据在事务开始时拍一个快照,在事务执行过程中看见的都是这个快照,即使是其他事务做了变更提交了对此事务也不可见 Mysql的RR隔离级别下不会出现幻读问题 4、串行化 事务必须串行化执行,一个事务只有等另外一个事务结束之后才可以开始,效率最低不支持并发,但是解决了事务隔离性的所3有问题 --锁住所有查询出来的行,因为下面这局查询了所有的行 --其他事务只能select,其他操作会阻塞,如果此时执行插入那是可以的,但是其他事务如果需要读取新插入的行则会阻塞 select * from stu --锁行,其他事务无法对这行修改但是可以读,对于其他行则同时可以读或写 select * from stu where id=1 --锁行,其他事务无法对这行读或则写 update stu set name='A' where id=1 ​

Go并发模式和channel

channel实现互斥锁 传统的sync.Mutex互斥锁 //如果不加锁那么最终结果可能不是10000 func main() { count := 0 wg := sync.WaitGroup{} mu := sync.Mutex{} for i := 0; i < 10000; i++ { wg.Add(1) go func() { mu.Lock() count++ mu.Unlock() wg.Done() }() } wg.Wait() fmt.Println(count) //10000 } channel实现互斥锁 func main() { count := 0 wg := sync.WaitGroup{} //channel的大小表示资源数量 1表示只允许一个goroutine加锁 lock := make(chan struct{}, 1) for i := 0; i < 10000; i++ { wg.Add(1) go func() { lock <- struct{}{} //加锁 count++ <-lock //解锁 wg.

Go中的slice

数组 数组值拷贝 func main(){ a1:=[...]int{1,2,3} a2:=a1 log.Println(a1,a1[0]) log.Println(a2,a2[0]) //也可以通过下标访问 a2[0] = 10 log.Println(a) //a[0]=1 } 数组指针传递 //通过指针访问数组 func main(){ a:=[...]int{1,2,3} a2:=&a log.Println(a,a[0]) log.Println(a2,a2[0]) a2[0] = 10 log.Println(a) //a[0]=10 } ​ len和cap的区别 len 代表底层数组可访问的范围,用索引访问不可越过这个界限 cap 代表底层数组的实际空间长度,不可用索引访问,如果append 元素时没有超过这个cap,则不再创建底层数组,直接在len空间后面扩展。否则开辟新的空间,同时增大cap(这里有一个增大规则),所以如果要频繁的扩容,适当设置大一些的cap能减少开销的,设置大的cap是为了防止多次扩容拷贝造成开销 func main(){ slice1 := []int{1,2,3} //len=3 cap=3 slice2 := make([]int,2) //len=2 cap=2 slice3 := make([]int,2,4) //len=2 cap=4 } func main(){ arr:=make([]int,2,10) // len=2 cap=10 arr[0]=1 arr[1]=2 //arr[2]=3 //报错 // len=6 cap=10 arr = arr[:6] //扩容,不会再申请空间 arr[2]=3 // [1,2,3,0,0,0] //会在len后面添加,如果len>cap则会进行扩容 此处不会扩容 arr = append(arr,888) // len=7 cap=10 //arr=arr[:11] //报错 超过cap的大小,此时必须用过append进行扩容 } func main(){ //下面的切片引用切片 指向同一个底层数组 cap右界限都是和父亲一样的 a1 :=[]int{1,2,3,4,5,6,7,8} // len=8 cap=8 a2 :=a1[:3] //[1,2,3] len=3 cap=8 a3 :=a1[:5] //[1,2,3,4,5] len=5 cap=8 } ​

github上的docker镜像仓库

github镜像仓库 github上也提供了docker镜像仓库服务,分为两类: docker.pkg.github.com 仓库关联的镜像仓库,必须对应一个仓库 ghcr.io 仓库无关的镜像仓库,和用户关联 tag格式分别为: docker.pkg.github.com/OWNER/REPOSITORY/IMAGE_NAME:VERSION ghcr.io/OWNER/IMAGE_NAME:VERSION 登入的密码就是github上创建的TOKEN,注意token必须有读写package的相关权限,我把token记录在了环境变量中,也可以记录在指定的文件里,将echo替换为cat即可 echo $GITHUB_DOCKER_IMAGE_TOKEN | docker login https://docker.pkg.github.com -u biningo --password-stdin echo $GITHUB_DOCKER_IMAGE_TOKEN | docker login https://ghcr.io -u biningo --password-stdin ​ 参考 Working with the Docker registry Working with the Container registry

C语法总结

函数指针和回调函数 指针函数 返回指针的函数 char * sayHello(){ char *msg = (char*)malloc(sizeof(13)); msg = "hello,world\n"; return msg; } int main(int argc, char const *argv[]) { char *msg; msg = sayHello(); printf("%s\n",msg); } 函数指针 保存函数入口地址的指针,可用于直接设置CPU的PC,直接跳转到目标函数代码指向,目标代码还可以是一段汇编程序,这样也可以实现C和汇编混合编程 void echo(char *msg) { printf("%s\n", msg); } int main(int argc, char const *argv[]) { //void:返回值 (*func):函数指针写法 (char *msg):形参 void (*func)(char *msg); //可以直接赋予一个函数的地址 或则void*地址都可 //赋予一段汇编代码地址起始处也可 func = &echo; func("hello,world"); //直接跳转到地址入口执行 } 下面将四则运算法则传入函数中进行回调 int sum(int i1,int i2){ return i1+i2; } int sub(int i1,int i2){ return i1-i2; } int mul(int i1,int i2){ return i1*i2; } int div(int i1,int i2){ return i1/i2; } void func(int i1,int i2,int (*operate)(int,int)){ printf("i1:%d i2:%d = %d\n",i1,i2,operate(i1,i2)); } int main(int argc, char const *argv[]) { func(8,4,sum); func(8,4,sub); func(8,4,mul); func(8,4,div); return 0; } 函数指针可以用于结构体,实现类似于了类方法的效果

Go工具链

go get和go install go1.6之后,go get命令的设计主要用于追加go.mod的依赖包,如下: go get github.com/go-redis/redis/v8 #如果不添加版本的话就在最新的版本 go get github.com/gin-gonic/gin@v1.7.0 #可以添加版本 当然,也可以在go.mod直接添加依赖或则删除依赖,然后使用如下命令来处理依赖 此命令会清除项目没有使用的依赖以及项目使用的依赖会进行下载 go mod tidy #此命令应该会很常用 go install命令用于安装二进制文件,此命令会下载对应的库到本地,然后自动执行build编译代码生成二进制文件转移到$GOPATH/bin路径下。此命令必须添加版本 此命令是全局安装,不会修改项目的mod文件 go install github.com/cosmtrek/air@v1.15.1 ​ go build 将代码编译为相关平台的可执行文件,只需要编译带有main的入口文件即可 go build #会寻找当前目录下main入口文件然后进行编译 会编译所有 go build -o main #指定生成可执行文件的名字 go build mymain.go #也可以编译指定的go文件 然后就会连一起依赖的代码都编译为一个二进制 ​ go env 用于管理go的环境变量相关信息,go相关环境变量也可在.bashrc等文件里面设置,优先级高 go env #打印所有go的环境变量 go env GOPROXY #打印某个环境变量的值 go env -json #json格式输出 go env -w GOPROXY=https://goproxy.cn,direct #修改某个值 这里设置了中国代理,direct表示如果代理没有则直接走go官网,可以设置多个代理网站,用逗号分割 go fmt和gofmt go fmt是对gofmt的封装,直接使用gofmt即可,格式化如果不加-w是不会改变源代码的,所以最常用的就是: gofmt -w .

Go指针

返回局部变量的指针 Go支持垃圾回收,所以当一个函数返回了局部变量的地址这是合法的。这是Go和C指针的区别之一。Go编译器会作内存逃逸分析,如果一个局部遍历的指针被返回了则会将他的内存分配到堆空间 type Stu struct { Name string Age int } func NewStudent() *Stu { stu := Stu{} stu.Name = "lyer" stu.Age = 18 return &stu //这是合法的 } func main() { stu:=NewStudent() stu.Age = 21 } ​ Go指针的限制 普通类型的指针不能作算术运算 一个指针类型的值不能被随意转化为另外一个指针类型,也就是说每个类型的指针其实也相当于一个类型 指针只能在同类型比较(==、!=) func main() { s1:=&Stu{} s1= nil s2:=&Stu{} log.Println(s1==s2) //false } 指针的值不能赋给其他类型的指针 综上所述: Go每个类型的指针都是一个独立的类型 任意一个类型的指针都可以转化为unsafe.Pointer类型,此类型相当于void* ​ unsafe.Pointer和uintptr unintptr属于一个可运算的指针类型,其长度每个平台不一样,比如64位平台则此类型必须能够表达所有的地址,所以长度为int64 注意unintptr指向的地址不会被Go感知到,也就是说其指向的地址无法保证不被GC回收 unsafe.Pointer指针相当于void*,其它所有类型的指针都可以转化为此类型,如果要进行运算的话则需要继续转化为uintptr func main(){ arr := [3]int8{1, 2, 3} p := unsafe.Pointer(&arr) p1 := (*int8)(unsafe.

MySql备份与恢复

mysqldump备份与恢复 备份所有数据库--all-databases #备份所有数据库 mysqldump -uroot -p --all-databases > ./all.sql 备份指定的数据库 #备份d1、d2 库 mysqldump -uroot -p --databases d1 d2 > ./db.sql 备份指定数据库中的表,恢复时必须use到指定的数据库下 #备份test库中的a b c 表 mysqldump -uroot -p --databases test --tables a b c > ./db.sql 恢复数据 mysql> source /home/lyer/tmp/temp/db.sql ​ MySql备份脚本 #!/bin/bash # ------------------------------------------------------------------------------- # FileName: mysql_backup.sh # Describe: Used for database backup # Revision: 1.0 # Date: 2020/08/11 # Author: wang # 设置mysql的登录用户名和密码(根据实际情况填写) mysql_user = "root" mysql_password = "yourpassword" mysql_host = "localhost" mysql_port = "3306" backup_dir = /data/mysql_backup dt=date +'%Y%m%d_%H%M' echo "Backup Begin Date:" $(date +"%Y-%m-%d %H:%M:%S") # 备份全部数据库 mysqldump -h$mysql_host -P$mysql_port -u$mysql_user -p$mysql_password -R -E --all-databases --single-transaction > $backup_dir/mysql_backup_$dt.