Untitled
Go技能
slice底层数据结构
1 | type slice struct { |
底层使用runtime.makeslice计算slice所需内存的大小,然后调用mallocgc进行内存分配。
slice深拷贝和浅拷贝
深拷贝:拷贝数据本身,创建一个新对象,分配新的内存空间。
1、copy(slice2,slice1),2、append赋值发生扩容时。
浅拷贝:拷贝数据地址,新对象于旧对象的内存地址相同,修改值时对互相都会有影响。
slice2 := slice1
slice的扩容机制
1、预估容量:新申请的容量大于旧容量的2倍,那么扩容后的大小就为新申请的容量;旧容量小于1024,那么新容量就为旧容量的2倍;如果旧容量大于1024,那么新容量就为旧容量的1.25呗。
2、计算申请内存,内存=预估容量 * 元素类型大小,go向自身的内存管理管理模块只有0、8、16、32、48……32768等60个内存规格大小,当申请内存时,会匹配足够大,最接近的内存规格。最后容量=分配内存大小 / 元素类型大小。
1 | func main() { |
首先计算预估容量=5,申请内存=5*8=40,40最接近48,所以分配的内存大小为48,最后的容量=48/8=6。
slice不是线程安全的
slice的底层结构并没有使用加锁等方法,不支持并发读写,所以是不安全的,slice在并发执行过程中不会报错,但是数据丢失。
map底层数据结构
是一个指向hmap结构体的指针,占用8个字节
1 | type hmap struct { |
1 | // 静态的bmap. |
map遍历为什么是无序的
1、map在遍历时,会随机选择一个bucket,再从其中随机遍历。
2、map在扩容后,会发生key的搬迁,原来落在一个bucket中的key可能会落到其它bucket中。
map不是线程安全的
map的底层数据结构没有锁,同时对map进行并发读写时,程序会panic,go的官方,认为map更适配经典使用场景,而不是为了并发,导致大部分程序付出加锁的性能代价。可以使用 map+mutex,或者sync.map实现并发安全。
map是如何查找的
获取map的value时,可以由一个值接收,也可以由两个值接收,第二个值为布尔类型,如果时false则value为该类型的零值。然后在查找时,会根据key的不同类型调用不同的函数,来优化效率。
0、判断hmap是否为空,以及count是否为0
1、写保护监测,如果flag值为1,则导致程序panic。
2、计算hash值,根据hash值得低B位找bucket。
3、如果bucket正在扩容,就在旧的oldbuckets中找,否则直接在bucket中找。
4、根据hash值得高8位匹配tophash,返回key对应的指针,没找到则返回空指针。
map解决hash冲突
1、链地址法,将新bucket放到溢出桶,通过链表连接起来。
2、开放寻址法,按照一定的顺序,将新元素放到bucket空闲的单元中。
map为什么负载因子是6.5
每个bucket所装元素个数为负载因子,
负载因子越大,填入的元素越多,空间利用率越高,但是发生哈希冲突的几率就越大,
负载因子越小,填入的元素越少,空间就会浪费,还会提高扩容操作的次数,
经过官方的认真测试,取了一个相对适中的值6.5。
map是如何扩容的
map扩容需要将原有的key/value重新搬迁到新的内存空间,如果map太大,就会产生较大延长,所以map采用了一种渐进式的方式,每次只会搬迁2个bucket,nevacuate则记录它搬迁的一个进度。
1、双倍扩容:map元素的个数>6.5 * 桶个数,新建一个bucket数组,新的bucket大小是原来的2倍,然后旧的bucket数据搬迁到新的bucket。
2、等量扩容:溢出桶>=桶的总数,或者溢出桶大于桶的2^15,bucket数量位置不变,重新做一遍类似双倍扩容的搬迁动作,把松散的键值对重新排列一次。
当map插入,修改和删除时(如果正在迁移,就把当前桶进行迁移然后进行修改和迁移),当满足以上两个条件就会发生扩容,hashGrow函数,判断条件,oldbucket指向老的bucket,newbucket申请新的内存空间,bucket指向newbucket,oldbucket向bucket搬迁,搬迁完后就值为nil。
map与sync.map
sync.map支持并发读写,采用了空间换时间的机制,适合读多写少的的场景,在非并发的场景下map会比sync.map有更好的性能;sync.map没有提供获取map数量的方法,只有用m.Range来遍历计数;sync.map没有初始化过程,无法确定key的数据类型,所以在用map,slice不可比较类型做key时,运行时才会报错。
1 | type Map struct { |
1 | type readOnly struct { |
1 | type entry struct { |

store
1、从readOnly查找key,如果有直接修改entry所指向的值,这个修改对于dirty与read时同步的;
2、如果没有则加锁,并再次重复以上操作,双检查机制,如果read中有该key,则修改,如果此时p是擦除状态,则在dirty中添加。
3、如果没有然后再dirty中查找,有则更新没有者添加,此时read中没有此key将readOnly的amended(额们的) 置为true
load
1、直接从read中找,如果找到了直接调用entry的load方法,取出其中的值
2、如果没找到,且amended为false 说明dirty为空,返回空和false,
3、如果amended为true,说明key在dirty中,双检查机制,read中没有则去dirty中找,
4、misses+=1,if misses>len(dirty),read.m=dirty,misses=0
delete
基本上和上面一样,当read和dity都有则将p置为nil,只有dirty有则直接删除
https://zhuanlan.zhihu.com/p/344834329
channel的底层实现
1 | type hchan struct { |
发送数据
1、如果chan的读等待队列存在接收者goroutine,将数据直接发给第一个等待的goroutine,并唤醒接收的goroutine。
2、如果不存在接收者,循环数组未满,则将数据发送到循环数组的队尾
3、如果不存在接收者,循环数组已满,则会阻塞,将当前goroutine加入等待队列,并挂起等待唤醒。
接收数据
1、如果chan的写等待队列存在发送goroutine,无缓冲,或者循环数组已满,直接第一个发送者的goroutine和循环数组队首拷贝给接收变量,并唤醒发送的goroutine。
2、如果不存在发送者goroutine,循环数组非空,则将循环数组队首的元素拷贝给变量。
3、如果不存在发送者goroutine,循环数组为空,则将当前goroutine,加入读等待队列,并挂起等待唤醒。
channel死锁场景
1、一个goroutine中(main),非缓存channel写,读,非缓存channel只读不写,只写不读
3、一个goroutine中(main),缓冲channel写超过缓存数量。
4、一个goroutine中(main),读取空的未关闭的channel。
channel panic场景
1、关闭一个已经关闭的chan
2、向关闭的chan发送数据
向关闭的chan接收数据不会发生panic,如果chan中有数据就拷贝给变量,没有则赋给变量该数据类型的零值
channel应用场景
1、goroutine之间的通信
2、解耦消费者与生产者
3、控制并发goroutine的数量
4、超时处理
5、实现互斥锁
mutex的底层实现
1 | type Mutex struct { |

加锁

解锁

mutex的正常模式和饥饿模式
正常模式:默认情况下就是正常模式,当goroutine想要去获取被抢占的锁时,首先会自旋去尝试获取这个锁,当自旋超过一定次数(4)后,这个goroutine就会被加入等待队列,阻塞等待唤醒,所有goroutine都会按照 IFIO的顺序等待,被唤醒的goroutine不会直接拥有锁,而是和新goroutine进行竞争,而刚被唤醒的goroutine有很大的几率在锁竞争中失败。
饥饿模式:刚被唤醒的goroutine长时间(1ms)获取不到锁,mutex就会切换到饥饿模式下,直接获取锁,并将自旋状态下的所有goroutine放到等待队列中去。当goroutine的执行时间小于1ms或者等待队列为空就回归正常模式。
https://www.cnblogs.com/mayanan/p/15842916.html
https://blog.csdn.net/baolingye/article/details/111357407
rwmutex的底层实现
读写锁同一时间内只允许一个写者,但是允许多个读者同时读对象。
1 | type RWMutex struct { |
GMP模型
go起初用的是GM模型,但是当M从全局队列中获取G的时候,都要获取锁或导致激烈的锁竞争。
G:代表go的协程goroutine,储存了goroutine的栈空间、状态以及任务函数等,G的数量理论上只受内存影响。
M:go对操作系统线程的封装,M的数量默认是10000个,每个M都有一个G0,负责调度是协程的切换。
P:虚拟处理器,P有256大小的数组形成的本地队列,只有将P与M绑定,M上才可以允许G,P的数量决定了系统内最大可并行的G的数量,P的数量默认为CPU核心数,可以通过环境变量来设置。
全局队列:当所有P的本地队列满了,G就会放到全局队列中,全局队列是一个链表,没有长度限制。
1、由go关键字创建G,并优先保存在P的本地队列,如果P的本地队列满了,就保存到全局队列。
2、P唤醒M,M从P获取G,由G0切换到G,执行G的函数。当M无可运行的G时,会全局队列获取G,当全局队列为空时,会到别的P里偷取一半的G,这就是偷取机制(work stealing)
3、当M执行G的过程中发生系统调用阻塞,会阻塞G和M,此时P会和当前M解绑,去寻找新的M执行P全局队列剩余的G,当系统调用结束后,这个G会优先获取之前的P,如果获取不到就会被放到全局队列,M变为休眠状态,这就是握手机制(hand off),同步阻塞。
4、如果时发生网络IO等操作阻塞,会阻塞G,不会阻塞M,G会被go的网络轮询器接收,M会寻找P中其它可执行的G继续执行。异步阻塞。
5、M执行完G后清理现场,重新进度调度循环。
https://www.cnblogs.com/sunsky303/p/11058728.html
内存分配机制
mspan:内存管理的基本单元,68中大小的spanClass内存规格
mcache:每个goroutine绑定的P都要一个mcache字段
mcentral:中心缓存,管理全局的mspan供所有的线程使用。
mheap:动态分配内存
内存逃逸
变量从栈上逃逸到堆上的现象就称为内存逃逸
1、返回值为指针类型,变量超过函数的生命周期
2、动态类型,当函数参数为interface时。
3、当栈空间不足
4、变量大小不确定,例如编译期间无法确定slice的值
5、闭包引用对象
内存泄漏
当内存被无效占用无法被释放的情况
1、由一个很大的数据切只割出一小段slice使用。
2、互斥锁未释放,导致其它goroutine一直阻塞。
3、死锁,导致goroutine一直阻塞。
内存对齐
为了能让cpu可以更快的存取到各个字段,Go编译器,会帮你struct的结构体做数据对齐,就是指内存地址所存储数据大小的整数倍,让cpu可以一次将该数据从内存中读取出来,空间换时间。
1 | //结构体的顺序不同导致结构体的大小不同 |
GC
根对象:全局对象、执行栈(goroutine申请或引用在堆内存的变量)
v1.3-标记清除法
1.开启STW,停止程序的运行
2.从根节点出发,标记所有可达对象
3、停止STW,回收所有未被标记的对象
整个gc期间需要STW,将整个程序暂停,造成性能的损耗。
v1.5三色标记法
1.创建三个集合:白色、灰色、黑色
2.将所有对象放入到白色集合进行遍历,将遍历到的对象放到灰色集合中去
3.遍历灰色集合,将灰色对象标记为黑色,将灰色集合所引用的白色集合变为灰色,重复此操作直到灰色为空
4.将白色的对象当作垃圾回收掉。
插入屏障技术
A对象引用B对象的时候,B对象被标记为灰色
满足强三色不变式:不存在黑色对象引用白色对象的指针
在栈空间会有stw进行三色标记
删除屏障技术
当A对象删除B对象的引用时,B对象会被标记成灰色
满足弱三色不变式:将删除的对象标记会灰色
精度低,垃圾会被留到下一轮
v1.8混合写屏障
1.开启stw,扫描栈,将所有可达对象置黑
2.gc期间,任何栈上新创建的对象,都置为黑色
3.被删除的对象置灰
4.新挂载的对象置灰
GC的触发时机
1、系统触发:使用系统监控,当两分钟没有产生任何gc时,强制触发gc,或者当分配的堆大小达到阈值时
2、手动触发:使用runtime.GC手动触发
https://www.luozhiyun.com/archives/475
interface底层实现
1 | //空接口 |
new与make的区别
make用于初始化切片,map,chan这三个引用类型,为其分配内存空间,返回该类型的值
new用于所有的基本数据类型,返回该类型的指针,用的不多。
context
tag
协程同步
锁
网络协议
tcp连接,连接的含义
连接的意思就是客户端、服务端双方有创建资源(创建连接的数据结构,称为tcp控制块)为对方服务,双方交换控制信息,然后通过四元组作为连接的唯一标识。
七层模型

wan,lan口
wan口接入外网,lan口接入内网
划分子网
HTTP常见状态码
200:传统的成功。
204:只有头部没有body。
206:分块下载或者断点续传,数据未发送完。
301:永久重定向,之前的资源不存在了。
302:暂时重定向,服务器的压力将地址重定向到其它地方。
304:重定向缓存,代表使用缓存。
400:笼统的客户端错误,参数传递错误等。
401:未登录,代表无资格访问此服务器的这个资源。
403:登录了,但是没有权限。
404:资源不存在。
500:笼统的服务器错误,例如代码逻辑的错误。
501:客户端的请求服务器不支持。
502:此服务器做网关或者代理服务器访问时出错,但是服务器还是正常。
503:服务繁忙。
504:网关超时。
GET与POST区别
1、GET常用于获取资源,POST常用于携带负载传输资源到后端去处理资源。
2、GET所携带URL参数有限制,而POST携带的body参数无限制。
3、GET是幂等的只读操作对服务器是安全的,浏览器会缓存GET资源,POST不是幂等的,会修改服务器上的资源,不安全的。
4、以上都是从语法上来分析的。
HTTP缓存技术
1、强制缓存:浏览器给数据head设置一个过期时间、只要未过期就使用这个缓存。
2、协商缓存:浏览器第一次请求资源时,会在相应头加上唯一标识ETag,浏览器再次请求时,如果缓存过期,就向服务器发送请求,判断ETag相不相等,不相等的话就返回新数据,如果相等就返回服务器上的缓存数据,此时的状态码为304。
HTTP报文格式
请求报文
1、请求行:请求方法+url+协议版本号
2、头部:
1 | POST /login |
3、包体:body
响应报文
1、状态行:协议版本号+状态码+状态描述
2、响应头部:
1 | 200 OK |
3、响应数据:
HTTPS与HTTP区别
1、HTTP是超文本传输协议,是明文传输不安全,HTTPS在TCP层加入了SSL/TSL安全协议能够加密传输,并且还需要SSL/TSL的握手
2、HTTP的端口是80,HTTPS的端口是443。
3、HTTPS需要申请CA证书,来保证服务器的身份可信。
HTTPS的混合加密
1、非对称加密:客户端获得证书,证书中含有公钥,服务端有专属的私钥。客户端首先验证,证明服务器是可信任的。然后通过客户端随机数1、服务端随机数和客户端随机数2生产密钥,客户端随机数2通过公钥加密传输给服务端,服务端用密钥解密,解析出客户端随机数2然后与客户端随机数1、服务端随机数生产密钥。非对称加密速度慢,主要目的是为了客户端和服务端有相同的密钥。
2、对称加密,经过非对称加密后,双方传输的明文就可以通过此密钥加密成密文进行数据传输了。
HTTP/1.0、HTTP/1.1、HTTP/2.0、HTTP/3.0
1、HTTP/1.0每次请求都要建立三次握手,有队头阻塞的问题。
2、HTTP/1.1虽然每次请求不用都建立三次握手,但是在TCP通道间,也会有队头阻塞的问题。
3、HTTP/2.0会头部压缩,多个请求头的头部一样的协议会消除重复的部分;头信息和数据体都是二进制,统称为帧;数据流,每个请求都称为一个数据流有唯一编号,有数据的优先级,并实现了多路复用,在TCP管道不会被阻塞;双向。但是也存在队头阻塞的问题,如果前一个字节数据没有达到,后面的数据只会放在内核缓冲区,只要那个缺失的字节拿到了,才能在应用层拿到数据。
4、HTTP/3.0使用了UDP协议,QUIC协议无队头阻塞,QUIC三次握手包含了SSL握手,可以更快的建立连接,不使用四元组使用唯一ID,降低连接迁移的成本
TCP与的HTTP区别
1、HTTP是应答式的,建立在TCP通道之间的,只能由客户端向服务端发送请求。而TCP是长连接,是全双工。
2、HTTP是应用层的协议。TCP是传输层的协议
3、HTTP是无状态的,上次请求和下一次请求无关,TCP是有状态的,两次报文之间是有顺序关系的
TCP首部有哪些字段
https://www.csdn.net/tags/MtTaMg4sMjA3NTcwLWJsb2cO0O0O.html
TCP 可靠传输机制
1、确认:TCP传输的过程中,每次接收方收到数据后,都会对传输方进行确认应答,也就是ACK报文。
2、超时重传机制:当主机A向主机B发送报文,而主机B没有在规定时间内收到主机A发送的报文,那么就认为ACK包丢失,主机A就会重发报文。
3、排序机制:TCP的包头有序列号,每当当接收方收到数据,就会回复ACK包,其中有确认序列号,告诉发送方接收到了那些数据,下次的数据从哪里发。
4、滑动窗口:TCP连接的每一方都有固定大小的缓存空间,TCP的接收端只允许另一端发送接收端缓冲区能接纳的数据。防止发送较快主机引起接收较慢主机的缓冲区溢出现象。
TCP拥塞控制
1、满开始:一开始发送方先设置窗口cwnd=1,每经过一个传输轮次cwnd=2^n,呈指数增长。
2、拥塞避免:当cwnd大于慢开始的是门限值时,就会改为拥塞避免算法,cwnd+=1,呈线性增长。
当出现网络阻塞的时候,门限值=cwnd/2,cwnd=1,重新开始慢开始算法。
3、快重传:当个别报文段在网络中丢失,但网络未发生拥塞时,接收方需接收到数据时立即回复,当发送方收到3个重复确认就会发生快重传机制。
4、快恢复:当发生快重传机制后,就会发生快恢复,将门限值和cwnd=cwnd/2,并开始拥塞避免算法。
https://blog.csdn.net/qq_46312987/article/details/124061775
TCP与UDP的区别
1、TCP是面向连接的可靠服务、UDP提供无连接的不可靠服务。
2、TCP只支持一对一,UDP支持一对多、多对多通信。
3、TCP适用于文件传输等,UDP适用于视频、直播等,
TCP三次握手
1、客户端向正在监听状态的服务端发送SYN报文,序列号seq=x,说我想和你建立连接,然后客户端处于同步已发送SYN_SENT状态,不可携带数据。
2、服务端收到客户端的SYN报文之后,向客户端发送SYN-ACK报文,序列号seq=y,应答号ack=x+1,说我同意和你建立连接并申请和你建立连接,然后服务端处于同步已接受收RCVD状态,可以携带数据。
3、客户端收到服务端的SYN-ACK报文之后,向服务端发送ACK报文,序列号seq=x+1,应答号ack=y+1,说我同意和你建立连接。然后处于链接已建立established状态。可以携带数据,
4、服务端收到客户端的ACK报文之后,也处于连接已建立establish状态,双方就可建立连接了。
原因一:避免历史连接,首先客户端发送seq=x的SYN报文,但是网络阻塞此报文未到达服务端,而且客户端宕机,然后客户端又发送一个seq=y的SYN报文,但是seq=x的报文先到达,服务端收到后就返回客户端ack=x+1的SYN-ACK报文,但是客户端理应收到ack=y+1的报文,然后客户端就会认为此次连接为历史连接,就会发送RST报文终止连接。
原因二:避免资源浪费,客户端发送的SYN由于网络拥堵,客户端没有收到服务端的SYN-ACK报文,重发SYN报文,由于没有第三次握手,服务端只能每次的收到客户端发送的SYN都建立连接,就会建立重复的连接,造成资源的浪费。
原因三:确认双方的收发数据的能力,1服务端知道了客户端具有发数据的能力;2客户端知道了服务端具有收发数据的能力;3服务端知道了客户端具有收数据的能力。
原因四:同步序列号
TCP四次挥手
1、客户端向服务端发送FIN包,seq=u,说我要关闭连接,客户端停止发送数据,进入终止等待1FIN_WAIT1状态
2、服务端向客户端发送ACK包,seq=v,ack=u+1,说我同意你关闭连接。客户端进入终止等待1FIN_WAIT2状态,服务端进入关闭等待CLOSE_WAIT状态
3、服务端向客户端发送FIN包,seq=w,ack=u+1,说我也要关闭连接。服务端停止发送数据,进入最后确认LAST_ACK状态
4、客户端向服务端发送ACK包,seq=u+1,ack=w+1,说我同意你关闭连接,进入时间等待TIME_WAIT状态,并等待2MSL后进入CLOSED状态。
5、服务端向客户端发送ACK包后进入CLOSED状态
原因:服务端收到客户端得FIN报文后,只会先回复一个ACK报文,然后将自己服务器得所有报文发送完之后才会发送FIN包停止发送。
保活计时器
keep alive每次收到数据就会重新设置并启动保活定时器(2h),到时时,每隔75 秒发送探测报文段,10次无反应后就自动关闭。
SYN攻击
攻击者在短时间内伪造许多不同SYN报文,服务端每收到一个SYC包,就会向服务器发送一个SYC-ACK包,但是无法得到未知IP地址得会ACK回应,就会占满半连接队列,造成服务器的资源浪费,无法再建立TCP连接,甚至宕机。
2MSL等待的意义
MSL 是 Maximum Segment Lifetime,报文最大生产时间。
目的一:保证客户端最后的ACK能够到达服务端,使服务端完成正常的关闭。当ACK报文丢失在网络中后,此时服务端会重发FIN报文,客户端接正会在2MSL内收到FIN报文,然后再次刷新等待时间。
目的二:防止已失效的连接请求报文段出现在此次连接中,在2MSL后,本此连接的所有报文段就会消失了。
TCP协议的缺陷,优化
缺陷一:TCP的升级很难,TCP是在内核中实现的,应用只能使用不能修改,升级内核是一件很麻烦的事。
缺陷二:队头阻塞,如果前一个字节数据没有达到,后面的数据只会放在内核缓冲区,只要那个缺失的字节拿到了,才能在应用层拿到数据。
缺陷三:网络迁移需要建立新的TCP连接。
优化:三次握手、四次挥手、数据传输三个方面。
ftp的工作模式
1、仅支持TCP,且除了20,21端口其它端口大于1024
2、主动式:客户端向服务端的21端口发送连接请求,服务端收到请求后建立一条命令链路;当有数据传输时,服务端从20端口向客户端的空闲端口发送请求,建立数据传输的数据链路。
3、被动式:客户端向服务端的21端口发送连接请求,服务端收到请求后建立一条命令链路;当有数据传输时,客户端向服务端的空闲端口发送请求,建立数据传输的数据链路。
ARP、DHCP、DNS、ICMP
ARP:获取下一站的mac地址,ip地址是不变的,通过路由表获取下一站的ip,然后广播获得下一站的mac地址然后进行数据传输。
DHCP:UDP广播一步一步自动获取IP
DNS:域名解析,浏览器缓存->本机缓存->根域名->权威域名一步步去询问IP
ICPM:ping命令
键入网址到网页显示,期间发生了什么
首先浏览器解析URL,解析出协议、域名、资源路径和端口号等信息生成HTTP请求消息,如果浏览器有缓存直接用缓存,域名需要DNS解析出IP,然后就可以把HTTP的传输工作交给操作系统的协议栈了,按照协议栈,将HTTP的请求信息放到TCP报文的数据段中,TCP的三次握手还需要IP的帮助,将TCP的包封装在IP数据包中,然后再封装成MAC帧通过网卡将数字信号转为电信号发送出去。服务端收到后一层层扒皮得到HTTP请求,然后将响应也一层一层封装起来返回给客户端。客户端收到后就可以渲染数据了。
网络编程
WebSocket简介,与HTTP的区别
1、WebSocket是HTTP的升级,需要借助HTTP的请求,TCP三次握手,在借助HTTP一次握手。请求方式必须是GET
2、WebSocket是长连接,60s内无数据传输则关闭,WebSocket是双向传输。
3、WebSocket 请求行的url是 ws// Connection: Upgrade Upgrade ->socket,响应码为101
4、响应码为101
WebSocket与Socket的区别
WebSocket是应用层的协议,Socket严格来说不是协议,是对于TCP的函数封装,是一个抽象层
Socket建立网络的流程
1、服务端调用bind,绑定IP和端口。
2、服务调用listen,监听端口。
3、服务端调用accept,等待客户端连接。
4、客户端调用connect,向服务端的地址端口发送连接请求
5、在内核态,系统进行三次握手之后,通过服务端的accept函数就知道连接已经建立成功
6、然后调用read、write来读写数据。
linux
文件权限
r–4;w–2;x–1;读写执行
目录:可以查看此目录文件,可以修改此目录文件、可以进入此目录
文件所有者;所有者同组其它成员;其它组用户
0–标准输入;1–标注输出;2–标准错误输出
文件第一个字符:d目录,-普通文件,l软连接,b块设备,c字符设备
僵尸孤儿进程
1、僵尸进程是当子进程比父进程先结束,而父进程又没有回收子进程,释放子进程占用的资源,此时子进程将成为一个僵尸进程。
2、孤儿进程,是指父进程先结束,但没有回收子进程的资源,子进程仍在允许,此时子进程将成为一个孤儿进程,最终由init回收。
进程、线程、协程
1. 进程
进程就是程序在操作系统中的一次执行过程,是由系统进行资源分配和调度的基本单位,进程是一个动态概念,是程序在执行过程中分配和管理资源的基本单位,每一个进程都有一个自己的地址空间。一个进程至少有5种基本状态:初始态、执行态、等待状态、就绪状态、终止状态。
通俗讲: 进程就是一个正在执行的程序。
2. 线程
线程是进程的一个执行实例,是程序执行的最小单元,它是比进程更小的能独立运行的基本单位。
通俗讲:一个进程可以创建多个线程,同一个进程中的多个线程可以并发执行,一个程序要运行的话至少有一个进程。
****协程:协程是抽象的线程,由用户态完成调度,栈空间一般为2KB,协程切换不需要用户态到内核态的转换,切换的开销更小。只有PC(指令)、SP(数据)、DX三个寄存器
防火墙
进程的几个状态
1、running:R正在运行,或者在运行队列准备运行。
2、interruptible sleeping:S可中断睡眠状态,该程序阻塞,如socket可以直接中断。
3、uninterruptible sleep:D不可中断睡眠状态,该程序等待其它程序的结果。
4、stopped:T停止状态
5、zombies:Z中止状态,也是僵尸状态。
6、dead:X死亡状态,几乎捕捉不到。
https://blog.csdn.net/qq_49613557/article/details/120294908
常用命令
https://wuxm.online/2021/08/31/linux%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4/
死锁产生的4个必要条件
1、互斥:在一段时间内某资源只由一个进程占用。
2、持有并等待:某个进程已经有了一个或者多个资源,然是还要请求其它资源,但是要要等待
3、不可抢占:某个进程获取的资源不能被其它进程抢占,只能在使用完后,由该进程自己释放。
4、环路等待:进程和请求资源之间形成环路。
同步、异步、阻塞、非阻塞、
1、同步:当用户线程发起 I/O 请求,需要等待 接收方 I/O 操作完成后才能做后面的工作。
2、异步:当用户线程发起 I/O 请求,不需要等待 接收方 I/O 操作完成后就能做后面的工作。
3、阻塞IO:用户级线程一直等待IO数据,占用线程,让出CPU。
4、非阻塞IO:用户级线程不必一直等待IO数据的到来,然是需要一直询问,fd的复制,不断的从用户态到内核态的切换,浪费CPU。
https://www.cnblogs.com/loveer/p/11479249.html
io多路复用
一个线程来负责多个io请求
select:将所有文件描述符的集合从用户空间拷贝到内核空间,底层是数组1024个,内核空间遍历fd,有数据就通知用户态,并将fd拷贝的用户态,用户态也不知道是哪个fd有数据,只知道fd就绪的数量,所以需要遍历fd,一次遍历没有就绪的话就会阻塞用户线程直到有数据来
poll:与select相似,底层是链表
epoll:epoll在内核维护了一个等待队列、就绪链表和红黑树,三个函数epoll_create、创建epoll,epoll_ctl、epoll_ctl将文件描述符插入红黑树中,并注册回调函数,当有数据准备就绪时,将fd添加到就绪链表中,epoll_wait监听就绪链表,如果有就绪fd,就将其拷贝到用户空间, ,适用连接数量多的场景,重,可移植性不强,目前只支持Linux。两种模式,LT(Level Trigger)水平模式,当epoll_wait将fd返回给应用程序时,程序不处理或者处理不完,下次还会返回,知道程序处理。ET(Edge Trigger),只会通知一次。
软连接硬链接
软连接:符号链接包含到原文件的路径,有自己的文件属性和权限,可以对不存在的文件或目录创建软连接。ln -s file link
硬链接:是对原文件起了一个别名,属性相同,本质是同一个文件,只能对文件使用。
进程如何通信
匿名管道:shell命令中的 | 竖线就是匿名管道,匿名管道是只能用于存在父子关系的进程间通信。
命名管道:在文件系统中创建一个类型为p的设备文件,毫无关系的进程就可以通过这个设备文件进行通信。
消息队列:消息队列实际上是保存在内核的消息链表。
共享内存:它之间分配一个共享空间,每个进程都可以直接访问
信号量:信号量是一个计数器,表示的是资源的个数,
信号:异步的通信机制,(cltr+c)或(kill)
socket
jobs,fg,bg,nohup
1 | ctrl+z 将当前进程挂起到后台,停止运行,发送sigstop信号,使用fg会唤醒最近挂起的进程 |
jobs可以查看当前终端下的任务列表
fg可以将挂起的任务唤起到前台运行
bg可以将挂起的任务唤起到后台运行
但是在终端关闭后jobs中的任务会全部关闭🐳
nohup启动的任务不会关闭,**&** 符号将任务放在后台,>log 2>&1 &
进程间的同步机制
临界区:通过对多线程的串行化来访问公共资源或一段代码,保证在某一时刻只有一个线程能访问数据的简便方法,只能同步本进程内的线程。
互斥量:只有拥有互斥对象的线程才具有访问资源的权限,可以跨进程。
信号量:它允许多个线程在同一时刻访问统一资源
事件:事件对象通过通知操作的方式来保持线程的同步
进程间的调度算法
1.先来先服务调度算法:先进入就绪队列的进程,先被运行,当一个大进程先被允许运行,就会使后面的小进程等待很长事件,增加了进程的平均周转时间。
2.优先级调度算法:高优先级进程优先运行,属于抢占式
3.时间片轮转调度算法:先来先服务,第一个进程运行一个时间片,被抢夺,放到队尾,再运行下一个。
4.短进程优先调度算法:当多个进程准备好了的时候,运行最短时间进程先被运行,如果时间相同则先到的先运行
5.最短剩余时间优先调度算法:与正在运行的,以及被抢占了的进程,进行比较,如果运行时间少的话,就会抢占。
https://blog.csdn.net/xiaowanziddd/article/details/124350296
https://www.cnblogs.com/nixwl/p/16473029.html
MySQL
索引
索引相当于目录,为了方便查找书中的内容,通过对内容建立索引形成目录,索引是一个文件,要占据物理空间,MySQL的InnoDB搜索引擎索引的实现就是B+数,索引可以大大加快数据的检索速度,但是在对表中的数据进行增删改的时候,索引要动态维护,效率会下降,索引也会占用物理空间。
主键索引:数据列不允许重复,不允许为空,一个表只能有一个主键,最好是自增的。
唯一索引:数据列不允许重复,允许为空值,一个表允许多个列创建唯一索引。
普通索引:基本的索引类型,没有唯一限制,允许为空值。
全文索引:目前搜索引擎使用的一种关键技术。
聚簇索引和非聚簇索引
聚簇索引:聚簇索引就是主键索引,叶子节点存放中所有一条数据的所有数据。
非聚簇索引:非聚簇索引是二级索引,叶子节点存放的是主键值,然后再通过主键索引中的B+数查询到对应的叶子节点,需要查两个b+shu,这个过程叫回表,如果再二级索引的B+树中能查询到结构的过程就叫做索引覆盖
索引失效
1、使用左模糊查询或者左右迷糊匹配的时候,like %xx like %xx%。
2、对索引使用函数或者表达式。
3、对索引隐式转换。
4、where语句or前面时索引列,后面不是索引列。
覆盖索引
覆盖索引是select的数据列能从索引中获得,不必读取数据行,查询列要被所建的索引覆盖。
为什么使用B+数索引
1、哈希索引能够提供O(1)的单行数据操作性能,但是对于范围查询与排序需要进行全表扫描,桶过小会有哈希冲突,过大会浪费。
2、红黑树深度过大,会导致大量的随机磁盘IO。
3、B树在非叶子节点中存储数据,在查询范围数据时,会带来大量的随机IO,而B+树的叶子节点使用指针连接,形成链表,能够有效减少IO
二叉搜索树(BST)、val树、红黑树、B树、B+树、B*树
https://blog.csdn.net/qq_44197482/article/details/123574771
事务ACID
1、原子性:一个事务中的所有操作,要么全部完成,要么全部不完成,如果事务在执行过程中发送错误,会被回滚到事务开始前的状态,undo log。
2、一致性:事务操作前后,数据满足完整性约束,数据库保持一致性状态。
3、隔离性:数据库允许多个事务同时对其数据进行读写和修改的能力,事务与事务之间不相互影响。
4、持久性:事务结束后,对于数据库的修改是永久的,即便系统故障也不会丢失,redo log。
脏读>不可重复读>幻读。
这些都是事务并行引发的问题脏读:如果一个事务读到另一个未提交事务修改过的数据,当未提交事务发生回滚时就发生了脏读现象。
不可重复读:当一个事务内多次读取同一个数据,出现前后两次读的数据不一样的情况。
幻读:当一个事务内多次查询符合某个查询条件的记录数量,出现前后两次读的数据不一样的情况。
四种隔离级别
1、读未提交:一个事务还没提交时,他做的变更就能被其它事务看到。ru
2、读提交:一个事务提交之后,它做的变更才能被其它事务看到。rc
3、可重复读:一个事务执行过程中看到的数据,一直跟这个事务启动时看到的数据是一致的。rr,默认的隔离机制
4、串行化:对记录加上读写锁,在多个事务对条记录进行读写操作时,如果发生读写冲突,后访问的事务必须等前一个事务执行完成,才能继续执行。s
undo log、redo log、binlog
undo log:回滚日志,是Innodb储存引擎层生成的日志,实现了事务中的原子性,主要用于事务回滚和MVCC。插入一条记录的时候,记录主键,回滚时把这个主键值对应的记录删掉。删除和更新一条记录时,就把这条记录的内容记下来,回滚时在插入和更新。
redo log:重做日志,是Innodb储存引擎层生成的日志,实现了事务中的持久性,主要用于断点等故障恢复。Buffer Pool 更新一条新纪录的时候,首先要从磁盘读取该记录,然后在内存中修改这条记录,然后再缓存起来,然后将该页设置为脏页,后续由后台线程选择一个合适的时机将脏页写入到磁盘,这个时候就需要redo log物理日志,记录某个数据页做了什么修改
binlog:归档日志,主要用于数据备份和主从复制,也是记录了所有数据库表结构变更和表数据修改的日志但是他是server层生成的日志,所有储存引擎都可以用。
MVCC
通过Read View形成一个版本链来控制并发事务访问同一个记录的行为多版本并发控制
三大范式
1NF:数据库的每一列都是不可分割的原子数据项。
2NF:必须有主键,非主属性必须依赖主键。
3NF:所有非主属性不依赖于其它非主属性
https://blog.csdn.net/weixin_43183219/article/details/123639846
count计数

1和可以统计null值,1没有主键时更快,只有一个字段时更快,字段不会统计null值
一条update语句是怎样执行的
1、server层通过TCP收到一条sql语句。
2、然后进行再解析器词法分析,根据输入的字符串标识出关键字,构建SQL语法树。
3、语法法分析,判断SQL语法是否满足MySQL的语法。
4、预处理器验证表或者字段是否存在。
5、优化器将SQL语句的最优执行方案确定下来。
6、执行器执行并与存储引擎进行交互,调用储存引擎的接口。
7、如果修改记录的数据页再缓存池,就之间给执行器,如果不在就从磁盘读到缓存池再返回给执行器。
8、执行器比较更新前和更新后的记录,一样的化就不进行后续流程,不一样的化就发两次记录都给InnoDB引擎层。
9、InnoDB将更新前的值记录到undo log,然后再把这个更新操作记录到redo log
10、现在再修改缓存池中的值,并设置为脏页,后续由后台线程选择一个合适的时机写入磁盘
11、最后记录语句对应的binlog。

MySQL有哪些锁
表锁:将整个表锁起来
元数据锁:读写锁
行级锁:需要时获取,事务提交后释放。
练习SQL语句
redis
常见数据类型和应用场景
1、string字符串:SDS简单字符串。缓存对象、常规计数,访问次数库存数量。
2、列表list:双向链表,压缩列表。消息队列
3、hash对象:压缩列表,哈希表,listpack。缓存对象,购物车。
4、无序集合:哈希表,整数集合,共同关注,抽奖。
5、有序集合:压缩列表、跳表。排行榜。
6、BitMap:签到,判断用户登录状态。
7、HyperLogLog:百万级以上网页访问计数。
8、GEO:地图。
9、Stream:消息队列。
过期策略
1、惰性删除策略:不主动删除过期键,每次从数据库访问key时,都监测key是否过期,如果过期则删除该key
2、定期删除策略:每隔一段时间,检查数据库中一定量的key,并删除过期的key。
内存淘汰策略
当Redis的运行内存已经超过Redis设置的最大内存后,则会使用内存淘汰策略删除符合条件的key
1、volatile-random:随机淘汰设置了过期时间的任意键值。
2、volatile-ttl:优先淘汰更早过期的键值。
3、volatile-lru:淘汰最久未使用的键值。
4、volatile-lfu:在3的基础上淘汰最少使用的键值。
持久化AOF、RDB
1、AOF:Append Only File,将操作日志写到AOF日志中,,重启Redis就会读取AOF日志。Always总是、Everysec每秒、No交给系统,三种方式将日志写入硬盘中去,缺点文件体积大,恢复时间长。
2、RDB,Redis DB,RDB快照的内容是二进制数据,恢复数据的效率回避AOF高些,但是无法保证最近一次快照后的数据。save同一个线程会阻塞主线程,bgsave子进程避免主线程的阻塞。
3、混合持久化,先示RDB,后是AOF。
缓存雪崩、击穿、穿透
1、缓存雪崩:当大量数据同时过期或者Redis故障宕机的时候,如果由大量的用户请求,会直接访问数据库,导致数据压力骤增,造成数据库宕机,从而造成一系列的系统崩溃。
解决方案:均匀设置过期时间、互斥锁、双key;服务熔断:暂停业务应用对缓存服务的访问,之间返回错误;限流:只将少部分的请求发生到数据库进行处理。
2、击穿:某个热点数据过期,此时大量的请求访问了该热点,数据库很快就会被高并发的请求击垮,缓存雪崩的一个子集。
3、穿透:数据既不再缓存中,也不在数据库中,导致请求缓存时,缓存缺失,数据库也缺失无法构建缓存数据,当由大量这样的请求时,数据库压力骤增。业务误操作,黑客攻击。
解决方案:非法请求的限制、缓存空值。使用布隆过滤器快速判断数据是否存在。
一图解析:

主从复制哨兵模式
从服务器缓解主服务器的读压力,同步主服务器的数据
哨兵机制,实现主从节点故障转移,监测主节点是否存货,如果主节点挂了,他会选举一个从节点切换为主节点,并把新主节点的相关信息通知给从节点和客户端。
监控:哨兵集群向主节点发送ping命令,投票决定主节点是否挂机,哨兵至少有三个并且为奇数。
选主:哨兵集群投票选择leader发送命令,选择优先级最高的、复制进度最靠前的、ID号小的作为主节点,然后,将从节点指向新的主节点。
通知:通知给客户端。
缓存一致性问题
1、无论是先更新数据库,再更新缓存,还是先更新缓存再更新数据库,当两个请求并发更新同一条数据的时候,都可能会出现缓存和数据库中的数据不一致的现象。
2、先删除缓存,再更新数据库,再读写并发的时候,还是会出现缓存和数据库的数据不一致的问题
3、先更新数据库,再删除缓存,可以借助消息队列,将要把删除缓存的数据加入到消息队列,删除失败的话可以从消息队列中重新读取数据再次删除。
Redis为什么这么快
1、基于内存,内存的读写速度非常快
2、redis读写数据是单线程的,省去了很多上下文的切换时间,但其实还有后台进程如bgsave在进行数据的持久化工作
3、采用epoll作为I/O多路复用技术的实现,可以让单个线程高效的处理多个连接请求,redis自身的处理模型将epoll中的连接,读写,关闭都转换为了事件,不在IO上浪费太多的时间。
Docker
https://wuxm.online/2022/04/01/docker/
gRPC
gRPC的启动流程
服务端
1、net.Listen绑定监听端口。
2、grpc.NewServer()创建服务
3、服务注册,创建服务结构体,实现proto文件中的方法,并将服务于结构体一同注册到grpc中
4、启动服务。
客户端
1、获取连接 grpc.Dial
2、获取客户端,调用pb文件
3、调用groc中的定义服务
grpcui调试工具
连接grpc
1 | grpcui -plaintext 127.0.0.1:52452 |
grpc如何序列化proto文件
1、安装protoc编译器,但是它不支持go,还需要安装proto-gen-go,protoc-gen-go-grpc两个二进制文件
2、protoc --go_out=plugins=grpc:.(生成.pb.go的路径)--proto_path=.(proto文件的路径,后面跟proto文件名)这个是一种情况只生成一个.pb.go文件,而另一个现在官方是有用了一个protoc-gen-go-grpc插件又生成了grpc.pb.go文件。
proto生成.pb.go中都有什么
1、go_package="./pb"生成.pb.go的包名package为pb。
2、message->struct,double->float64,bytes->[]byte,map<string, string>->map[string]string,repeated string->[]string
1、版本验证,proto文件更新了go文件就更新。
消息体生成结构体,有String()等6个方法
service生成的接口,接口中就是pb定义方法
客户端结构体,NewHelloClient函数获取客户端
RegisterHelloServer注册函数,需要自己定义接口实现结构体,
如果是流式的话定义了 接口中有不同的 send和revc接口
最下面的话就是服务的一个描述,注册服务时需要的。
gRPC拦截器
一元拦截器和流式拦截器
自定义一个函数,返回值为grpc.UnaryServerInterceptor类型的函数,其中调用metadata.FromIncomingContext(ctx)从context中获取元数据,里面可以存token,info.FullMethod包含函数路径/Hello/Simple,然后将自定义函数放到创建的服务中去,以此可以实现权限拦截。
流式拦截器,分为三阶段、预处理、调用RPC方法、后处理。
预处理阶段的拦截只是在流式请求第一次发起拦截,后面的流式请求不会再进入处理阶段。它将 contex 放到了 ServerStream接口中,里面做了更多的操作。
如果想要再发消息前后都拦截的话,需要定义一个结构体实现 SendMsg 和 RecvMsg 方法注册到handler中去。
客户端拦截器
可以直接在客户端拦截,放到 Dial获取连接时 顺便把token放进元数据中metadata.AppendToOutgoingContext(ctx, "authorization", token)
第三方的拦截器 go-grpc-middleware https://github.com/grpc-ecosystem/go-grpc-middleware
传输报文中metadata通常存放哪些内容
1 | stream.SendHeader()//服务端向客户端发生元数据 |
1、自定义的key-value,可以存放token
2、content-type:[application/grpc],消息的类型。
3、user-agent:[grpc-go/1.25.1],客户端信息。
4、authority,对方的IP和端口。
多路复用指的是什么
1、HTTP2的请求TCP一旦建立,后续请求将以stream的方式发送
2、每个stream的基本单位是frame(二进制帧),每种frame又分为头部帧和内容帧等等。
3、每个stream都有唯一标识,无需按顺序发送。
resolver,balancer
resolver模块提供了服务注册的功能,(瑞死奥沃)
balancer模块提供了负载均衡的功能,(白len瑟)
https://www.jianshu.com/p/c0f81d4c6384
https://wuxm.online/2021/11/09/grpc/
Git
https://wuxm.online/2021/12/01/202112%E6%8A%80%E6%9C%AF%E6%8A%A5%E5%91%8A/
微服务
nginx
nginx负载均衡算法
通过upstream指定一个负载均衡器。
1、轮询,一个服务地址访问一次。
2、权重,权重大的访问次数多 a a b a a b。
4、ip_hash,每个ip固定访问一个服务器地址。
5、fair,服务器响应快的优先访问。
nginx日志
1、access.log,访问日志,ip,日期,方法,路径,协议版本,浏览器信息。
2、error.log,错误日志。
nginx优点
1、操作简单,配置简单,成本低。
2、由c语言编写,轻量级,内存消耗低,10个nginx占150M内存
3、非阻塞、高并发连接:处理2-3万个并发连接树。
4、内置的健康检查功能,如果一个服务器宕机,再次发送的请求就提交到其它节点上。
nginx应用场景
1、代理静态资源。
2、负载均衡,作为负载均衡服务器。
3、搭建api接口网关
master与worker进程
1、master:nginx启动后只会产生一个master进程,主要是管理worker进程,监控worker进程的运行状态,接收外界发来的信号。
2、worker:由master fork而来,一个请求,完全由worker进程来处理,而且只在一个worker进程中处理。master解析配置文件监听socket,worker子进程在注册listenfd读事件前会抢accept_mutex锁,然后调用accept接收连接,当一个连接请求过来时,读取请求,解析请求,处理请求,返回数据。解决惊群效应
nginx多进程
nginx基于异步非阻塞的事件驱动模型和多进程机制实现高性能
1、进程之间不共享资源,不需要加锁,减少使用锁对性能造成的影响。
2、独立进程机制,相互不影响,当一个进程发送异常退出时,其它进程正常工作,master进程会很快启动新的worker进程。
nginx解决跨域
使用nginx的转发请求,把跨域的接口写成调本地接口,然后把这些请求转发到真正的请求地址。
nginx处理http请求的过程
1、解析请求头
2、url匹配location
3、判断限速
4、验证请求,验证用户权限
5、生成响应
6、过滤响应,压缩响应,图片处理等
7、log日志
nginx实现高并发
nginx的异步非阻塞工作方式,使用单线程多进程的模式,不会为每个请求分配cup和内存资源,节省了大量的资源,同时也减少了大量的cpu的上下文切换。
限制访问
1、限制浏览器和爬虫,在server中 使用 $http_user_agent字段。
2、限制IP访问,deny限制访问,allow允许访问。
3、限制访问频率,limit_conn 限制并发数,limit_req限制访问速度
