Map并发读写会有问题吗
因为map为引用类型,所以即使函数传值调用,参数副本依然指向映射m, 所以多个goroutine并发写同一个映射m, 写过多线程程序的同学都知道,对于共享变量,资源,并发读写会产生竞争的, 故共享资源遭到破坏
怎么安全并发读写Map(sync.Map,锁)
1. 加锁
(1)通用锁 sync.Mutex
(2)读写锁 sync.RwMutex
2、利用channel串行化处理
go的锁使用怎么写,信号量有什么了解
1. sync / mutex (互斥锁)
互斥锁的作用就不介绍了。golang互斥锁的代码在 sync/mutex.go中。
- func (m *Mutex) Lock() --加锁
简单来说,如果当前goroutine可以加锁,那么调用原子操作使得mutex中的flag设置成已占用达到互斥;如果当前goroutine发现锁已被占用,那么会有条件的循环尝试获取锁,这里是不用信号量去对goroutine进行sleep和wake操作的(尽可能避免开销),如果循环尝试失败,则最后调用原子操作争抢一次,获取不到则还是得调用runtime_Semacquire去判断阻塞goroutine还是继续争用锁。
func (m *Mutex) Unlock() --释放锁
2 sync / RWMutex (读写锁)
RWMutex是一个读写锁,该锁可以加多个读锁或者一个写锁,其经常用于读次数远远多于写次数的场景(如果读写相当,用Mutex没啥区别)。但加读锁时,只能继续加读锁;当有写锁时,无法加载其他任何锁。也就是说,只有读-读之间是共享的,其它为互斥的。
信号量
信号量的使用主要是用来保护共享资源,使得资源在一个时刻只有一个进程(线程)所拥有。 信号量的值为正的时候,说明它空闲。 所测试的线程可以锁定而使用它。 若为0,说明它被占用,测试的线程要进入睡眠队列中,等待被唤醒。
channel
通道(chan)在以下情况下会发生阻塞:
- 发送阻塞:当向一个没有缓冲区或者缓冲区已满的通道发送数据时,发送操作会被阻塞,直到有其他协程接收该数据或者通道的缓冲区有足够的空间来存储数据。
- 接收阻塞:当从一个没有缓冲区或者缓冲区为空的通道接收数据时,接收操作会被阻塞,直到有其他协程发送数据到该通道或者通道中有数据可供接收。
如果 chan 缓冲区满了是阻塞还是丢弃还是panic
在 Go 语言中,当通道的缓冲区已满时,发送操作有不同的行为选择:
- 阻塞:默认情况下,当通道的缓冲区已满时,发送操作会被阻塞,直到有其他协程从通道中接收数据,腾出空间来存储新的数据。这种阻塞行为是通道的默认行为。
- 丢弃:在某些情况下,我们可能希望在通道缓冲区已满时,放弃新的发送数据而不是阻塞。可以使用通道的选择语句(select)结合默认发送操作和一个额外的默认分支来实现这个行为。
- Panic:在通道缓冲区已满时,发送操作也可以触发 panic。这种行为可以通过设置通道的容量为 0 来实现。
chan 什么时候会 panic
- 如果chan不为nil,chan也没有关闭,向已经关闭的通道中发送数据,会导致panic。
- 使用未初始化或重复关闭的通道,也会引起panic
已经关闭的的 chan 进行读写,会怎么样?
- 读已经关闭的
chan
能一直读到东西,但是读到的内容根据通道内关闭前
是否有元素而不同。- 如果
chan
关闭前,buffer
内有元素还未读 , 会正确读到chan
内的值,且返回的第二个 bool 值(是否读成功)为true
。 - 如果
chan
关闭前,buffer
内有元素已经被读完,chan
内无值,接下来所有接收的值都会非阻塞直接成功,返回channel
元素的零值,但是第二个bool
值一直为false
。
- 如果
- 写已经关闭的
chan
会panic
链表成环怎么判断
可以是循环单链表,即首位相连;也可以是部分成环,即尾部和其他节点相连。 判断是否成环: 使用快慢指针遍历链表: 慢指针: 从头节点开始,一次跳一个节点。 快指针: 从头节点开始,一次跳两个节点。 如果是成环的,这两个指针一定会相遇。
怎么找第k大个数,时间复杂段是多少,通过树结构可以处理吗
方法1:
对数组A进行排序,然后遍历一遍就可以找到第K大的数字。该方法的时间复杂度为O(N*logN)
方法2:
利用简单选择排序法的思想,每次通过比较选出最大的数字来,比较上K次就能找出第K大的数字来。该方法的时间复杂度为O(N*K),最坏情况下为O(N^2)。
方法3:
这种方法是本文谈论的重点,可以利用快排的思想,首先快排每次执行都能确定一个元素的最终的位置,如果这个位置是n-k(其中n是数组A的长度)的话,那么就相当于找到了第K大的元素。记住快排每次确定一个元素的位置后,左边都是比<=它,右边>=它,很关键的前提。设经过一次快速排序后确定的元素位置是m的话,如果m > n - k大的话,那么第K大的数字一定在A[0]~A[m - 1]之间;如果m < n - k的话,那么第K大的数字一定在A[m+1]~A[n - 1]之间。整个过程可以通过递归实现,
对于二叉排序树
中序遍历第k个节点就是要找的
slice的底层实现,扩容机制
在 Go 语言中,有一个很常用的数据结构,那就是切片(Slice)。
切片是一个拥有相同类型元素的可变长度的序列,它是基于数组类型做的一层封装。它非常灵活,支持自动扩容。
切片是一种引用类型,它有三个属性:指针,长度和容量。
扩容时机
当切片的长度超过其容量时,切片会自动扩容。这通常发生在使用 append
函数向切片中添加元素时。
扩容时,Go 运行时会分配一个新的底层数组,并将原始切片中的元素复制到新数组中。然后,原始切片将指向新数组,并更新其长度和容量。
需要注意的是,由于扩容会分配新数组并复制元素,因此可能会影响性能。如果你知道要添加多少元素,可以使用 make
函数预先分配足够大的切片来避免频繁扩容。
切片扩容通常是在进行切片的 append
操作时触发的。在进行 append
操作时,如果切片容量不足以容纳新的元素,就需要对切片进行扩容,此时就会调用 growslice
函数进行扩容。
切片扩容分两个阶段,分为 go1.18 之前和之后:
一、go1.18 之前:
- 如果期望容量大于当前容量的两倍就会使用期望容量;
- 如果当前切片的长度小于 1024 就会将容量翻倍;
- 如果当前切片的长度大于 1024 就会每次增加 25% 的容量,直到新容量大于期望容量;
二、go1.18 之后:
- 如果期望容量大于当前容量的两倍就会使用期望容量;
- 如果当前切片的长度小于阈值(默认 256)就会将容量翻倍;
- 如果当前切片的长度大于等于阈值(默认 256),就会每次增加 25% 的容量,基准是
newcap + 3*threshold
,直到新容量大于期望容量;
HTTP和HTTPS的区别
1、HTTPS 协议需要到 CA (Certificate Authority,证书颁发机构)申请证书,一般免费证书较少,因而需要一定费用。(以前的网易官网是http,而网易邮箱是 https 。)
2、HTTP 是超文本传输协议,信息是明文传输,HTTPS 则是具有安全性的 SSL 加密传输协议。
3、HTTP 和 HTTPS 使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。
4、HTTP 的连接很简单,是无状态的。HTTPS 协议是由 SSL+HTTP 协议构建的可进行加密传输、身份认证的网络协议,比 HTTP 协议安全。(无状态的意思是其数据包的发送、传输和接收都是相互独立的。无连接的意思是指通信双方都不长久的维持对方的任何信息。)
TLS/SSL在哪一层协议
只有在应用层是基于HTTP的报文也就是明文的。 在HTTPS通信中,加密操作是在传输层(Transport Layer)和应用层(Application Layer)之间通过TLS/SSL协议实现的。 在TLS/SSL协议中,HTTP报文会被加密并封装到TLS/SSL记录协议(Record Protocol)中,然后再交给传输层进行分段处理和传输。
应用层有什么协议
ICMP
ICMP是 Internet Control Message Protocol 的缩写,即互联网控制消息协议。它是互联网协议族的核心协议之一。它用于 TCP/IP 网络中发送控制消息,提供可能发生在通信环境中的各种问题反馈,通过这些信息,使网络管理者可以对所发生的问题作出诊断,然后采取适当的措施解决问题。
HTTPS怎么实现安全加密
HTTPS是一种安全协议,用于在互联网上传输数据时提供加密和身份验证。它使用SSL/TLS协议来实现安全加密,以下是一些实现HTTPS安全加密的步骤:
- 获取SSL/TLS证书:要实现HTTPS,您需要获得由可信证书颁发机构(CA)颁发的SSL/TLS证书。这些证书用于验证服务器的身份,并向客户端提供加密密钥。
- 安装SSL/TLS证书:一旦您获得了SSL/TLS证书,您需要将其安装在您的服务器上。这通常涉及将证书与您的服务器软件集成,例如Apache或Nginx web服务器。
- 配置服务器:您需要配置您的服务器以支持HTTPS。这涉及指定加密协议(如TLSv1.2)和加密密钥的长度(如2048位)。此外,您需要确保服务器上的所有URL都以“https”开头,以便在所有请求中使用HTTPS。
- 强制使用HTTPS:为了确保所有客户端(包括浏览器和其他应用程序)都使用HTTPS与您的服务器通信,您应该配置您的服务器以强制使用HTTPS。这可以通过重定向所有HTTP请求到HTTPS URL,或通过禁用HTTP协议来实现。
- 监控和维护:为了保持HTTPS的安全性和可用性,您应该定期监控您的服务器和网络,确保没有中间人攻击或其他安全漏洞。此外,您应该遵循最佳实践,例如及时更新证书和密钥,以确保它们不受撤销 或其他攻击的影响。
总的来说,实现HTTPS安全加密需要获得和安装SSL/TLS证书,配置服务器以支持HTTPS,强制使用HTTPS,以及监控和维护您的服务器和网络。
对称加密和非对称加密
对称加密: 加密和解密的秘钥使用的是同一个.
非对称加密: 与对称加密算法不同,非对称加密算法需要两个密钥:公开密钥(publickey)和私有密钥(privatekey)。
对称加密算法: 密钥较短,破译困难,除了数据加密标准(DES),另一个对称密钥加密系统是国际数据加密算法(IDEA),它比DES的加密性好,且对计算机性能要求也没有那么高.
优点:
算法公开、计算量小、加密速度快、加密效率高
缺点:
在数据传送前,发送方和接收方必须商定好秘钥,然后 使双方都能保存好秘钥。其次如果一方的秘钥被泄露,那么加密信息也就不安全了。另外,每对用户每次使用对称加密算法时,都需要使用其他人不知道的唯一秘钥,这会使得收、发双方所拥有的钥匙数量巨大,密钥管理成为双方的负担。
常见的对称加密算法有: DES、3DES、Blowfish、IDEA、RC4、RC5、RC6 和 AES
非对称加密算法: 公开密钥与私有密钥是一对,如果用公开密钥对数据进行加密,只有用对应的私有密钥才能解密;如果用私有密钥对数据进行加密,那么只有用对应的公开密钥才能解密。因为加密和解密使用的是两个不同的密钥,所以这种算法叫作非对称加密算法。
非对称加密算法实现机密信息交换的基本过程是:甲方生成一对密钥并将其中的一把作为公用密钥向其它方公开;得到该公用密钥的乙方使用该密钥对机密信息进行加密后再发送给甲方;甲方再用自己保存的另一把专用密钥对加密后的信息进行解密。甲方只能用其专用密钥解密由其公用密钥加密后的任何信息。
优点:
安全
缺点:
速度较慢
常见的非对称加密算法有: RSA、ECC(移动设备用)、Diffie-Hellman、El Gamal、DSA(数字签名用)
Hash算法(摘要算法)
Hash算法特别的地方在于它是一种单向算法,用户可以通过hash算法对目标信息生成一段特定长度的唯一hash值,却不能通过这个hash值重新获得目标信息。因此Hash算法常用在不可还原的密码存储、信息完整性校验等。
常见的摘要算法有: MD2、MD4、MD5、HAVAL、SHA
TCP粘包以及怎么处理
TCP粘包就是指发送方发送的若干包数据到达接收方时粘成了一包,从接收缓冲区来看,后一包数据的头紧接着前一包数据的尾,出现粘包的原因是多方面的,可能是来自发送方,也可能是来自接收方。
主要也可从发送方和接收方两方面来分析
发送方:因为主要是Nagle算法合并导致,故可通过不使用Nagle算法来解决,发送方用TCP_NODELAY选项来关闭。
接收方:这里接收方解决指的是应用层方面,即格式化数据包和附带数据包长度发送,格式化数据包是指为数据包加上开始符和结束符,那么应用程序读取时就能区分出每个数据包的开始和结束。附带数据包长度发送是指在数据包头部定义出数据包的长度,那么程序在读取时,会按照长度读取对应字节数据,保证读取的是单个包,且数据完整,这样就能保证数据包是单个且完整。
UDP会有粘包问题吗?
UDP不存在粘包问题,因为UDP连接的接收方每次只接收一条独立的数据包,而TCP协议可以将多个数据包一起发送并接收(前提是接收方缓冲区剩余大小要能接收这多个数据),即接收方不保证一次只接收一条信息,所以TCP才存在粘包问题。
OAuth协议
OAuth 2.0是一个业界标准的授权协议(authorization protocol),这里的授权是以委派代理(delegation)的方式。可以这样理解,OAuth 2.0提供一种协议交互框架,让某个应用能够以安全地方式获取到用户的委派书,这个委派书在OAuth 2.0中就是访问令牌(access token),随后应用便可以使用该委派书,代表用户来访问用户的相关资源。
在OAuth 2.0的协议交互中,有四个角色的定义,
- 资源所有者(Resource Owner):顾名思义,资源的所有者,很多时候其就是我们普通的自然人(但不限于自然人,如某些应用程序也会创建资源),拥有资源的所有权。
- 资源服务器(Resource Server):保存着受保护的用户资源。
- 应用程序(Client):准备访问用户资源的应用程序,其可能是一个web应用,或是一个后端web服务应用,或是一个移动端应用,也或是一个桌面可执行程序。
- 授权服务器(Authorization Server):授权服务器,在获取用户的同意授权后,颁发访问令牌给应用程序,以便其获取用户资源。
进程的通信方式
管道pipe:管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
命名管道FIFO:有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
消息队列MessageQueue:消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
共享存储SharedMemory:共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。
信号量Semaphore:信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
套接字Socket:套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同及其间的进程通信。
信号 ( sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
进程能无限开吗?有什么限制
在Go语言中,goroutine的创建成本很低,调度效率高,Go语言在设计时就是按以数万个goroutine为规范进行设计的,数十万个并不意外,但是goroutine在内存占用方面确实具有有限的成本,你不能创造无限数量的它们,
控制goroutine的方法
1、Context
包
Go 语言中的每一个请求的都是通过一个单独的 goroutine
进行处理的,HTTP/RPC
请求的处理器往往都会启动新的Goroutine
访问数据库和 RPC
服务,我们可能会创建多个goroutine
来处理一次请求,而 Context
的主要作用就是在不同的 goroutine
之间同步请求特定的数据、取消信号以及处理请求的截止日期。
2、channel
我们知道channel
是用于goroutine
的数据通信,在Go
中通过goroutine+channel
的方式,可以简单、高效地解决并发问题。上面我们介绍了使用context
来达到对goroutine
的控制,实际上context
的内部实现也是使用的channel
,所以有时候为了实现方便,我们可以直接通过channel+select
或者channel+close
的方式来控制goroutine
的退出,我们分别来一写一个例子:
有点卡或者慢了,怎么排查服务的情况
1、如果所有的模块都卡:
极有可能是网络出问题,cpu被拉满了。
2、如果是单一个模块的变得卡,其他模块都正常:
变卡的问题可能性点:文件句柄,IO流,SOCKET流,代码中sql不规范,数据库连接资源,数据库连接问题导致锁无法释放,代码中sleep过长,线程池使用不规范等等。
go有什么性能分析工具
pprof 这个工具是 Go 提供监控工具,可以实时采集程序运行过程中的性能数据
pprof监控信息展示——火焰图
火焰图(Flame Graph)是 Bredan Gregg 创建的一种性能分析图表,因为它的样子近似火焰而得名。golang性能监控结果可以转换成火焰图来进行直观展示。火焰图 svg 文件可以通过浏览器打开,它展示调用图的最大优点是火焰图动态的——可以通过点击每个方块来分析它上层概况/下层详细的内容。
火焰图的调用顺序从下到上,每个方块代表一个函数,它上面一层表示这个函数会调用哪些函数,方块的大小代表了占用资源值的多少(例如,CPU使用时间的长短,内存使用的大小等)。火焰图的配色并没有特殊的意义,默认的红、黄配色是为了更像火焰而已。
生成火焰图,有两种方式:go-torch(golang version < 1.10)和golang原生的pprof(golang version < 1.10+的pprof集成了火焰图功能)。
mysql的事务特性
一、事务的四大特性(ACID)
如果一个数据库声称支持事务的操作,那么该数据库必须要具备以下四个特性:
1、原子性(Atomicity)
原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚,因此事务的操作如果成功就必须要完全应用到数据库,如果操作失败则不能对数据库有任何影响。
2、一致性(Consistency)
一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。
3、隔离性(Isolation)
隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。
4、持久性(Durability)
持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。
事务的四种隔离级别 SQL标准定义了4类隔离级别
包括了一些具体规则,用来限定事务内外的哪些改变是可见的,哪些是不可见的。低级别的隔离级一般支持更高的并发处理,并拥有更低的系统开销。
Read Uncommitted(读未提交): 在该隔离级别,所有事务都可以看到其他未提交事务的执行结果。本隔离级别很少用于实际应用,因为它的性能也不比其他级别好多少。
Read Committed(读已提交): 这是大多数数据库系统的默认隔离级别(但不是MySQL默认的)。它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变。这种隔离级别也支持所谓的不可重复读(Nonrepeatable Read),因为同一事务的其他实例在该实例处理其间可能会有新的commit,所以同一select可能返回不同结果。
Repeatable Read(可重复读): 这是MySQL的默认事务隔离级别,它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。不过理论上,这会导致另一个棘手的问题:幻读 (Phantom Read)。简单的说,幻读指当用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会发现有新的“幻影” 行。InnoDB和Falcon存储引擎通过多版本并发控制(MVCC,Multiversion Concurrency Control)机制解决了该问题。
Serializable(串行化): 这是最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。在这个级别,可能导致大量的超时现象和锁竞争。
MySQL三大日志:binlog、redo log和undo log
二进制日志(binlog)和事务日志(包括redo log和undo log)
由binlog和redo log的区别可知:binlog日志只用于归档,只依靠binlog是没有crash-safe能力的。但只有redo log也不行,因为redo log是InnoDB特有的,且日志上的记录落盘后会被覆盖掉。因此需要binlog和redo log二者同时记录,才能保证当数据库发生宕机重启时,数据不会丢失。
索引的底层数据结构是怎么样的
索引是一种数据结构(平衡树非二叉),即B树,B+树,通过不断的缩小想要获得数据的范围来筛选出最终想要的结果,同时把随机的事件变成顺序的事件。
redis分布式锁原理
分布式锁,是控制分布式系统之间同步访问共享资源的一种方式。在分布式系统中,常常需要协调他们的动作。如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问这些资源的时候,往往需要互斥来防止彼此干扰来保证一致性,在这种情况下,便需要使用到分布式锁。
使用setnx、getset、expire、del这4个redis命令实现
数据库和缓存的数据一致性怎么保证
方案一:由缓存的调用者在更新数据库的时候同时更新缓存
方案二:将缓存和数据库整合为一个服务,由该服务来维护一致性。 对外提供接口,调用者调用该服务提供的接口,无需关心缓存一致性问题
方案三:调用者只操作缓存,由其他线程异步将缓存数据持久化到数据库,保证最终一致性。
redis数据结构
String
:字符串类型List
:列表类型Set
:无序集合类型ZSet
:有序集合类型Hash
:哈希表类型
1. zset 底层数据结构
zset类型的底层数据结构的实现?
zset是Redis提供的一个非常特别的数据结构,常用作排行榜等功能,以用户id为value,关注时间或者分数作为score进行排序。与其他数据结构相似,zset也有两种不同的实现,分别是zipList和skipList。
数据结构
跳表(skiplist)是一个查询/插入/删除 复杂度o(lgn)的数据结构。在查询上跟平衡树的复杂度一致,因此是替代平衡树的方案。在redis的zset,leveldb都有应用。
跳表skipList在Redis中的运用场景只有一个,那就是作为有序列表zset的底层实现。
跳表如何构建 当插入一个数据时,随机获得这个节点的高度,没错,就是随机!每涨一层的概率为p,这个认为设置,一般为0.25或者0.5,这样层数越高的节点就越少(这种结构跟平衡树有点像)。
redis过期的key在内部是怎么被清理的
惰性清理
当key被访问时,清理已经过期的key
定时清理
Redis配置项hz定义了serverCron任务的执行周期,默认每次清理时间为25ms,每次清理会依次遍历所有DB,从db随机取出20个key,如果过期就删除,如果其中有5个key过期,那么就继续对这个db进行清理,否则开始清理下一个db。
内存不够时清理
当执行写入命令时,如果发现内存不够,那么就会按照配置的淘汰策略清理内存,淘汰策略一般有6种,Redis4.0版本后又增加了2种,主要由分为三类
第一类 不处理,等报错(默认的配置)
第二类 从所有结果集中的key中挑选,进行淘汰
第三类 从设置了过期时间的key中挑选,进行淘汰
这种就是从设置了expires过期时间的结果集中选出一部分key淘汰,
LRU算法
LRU算法的设计原则是如果一个数据近期没有被访问到,那么之后一段时间都不会被访问到。所以当元素个数达到限制的值时,优先移除距离上次使用时间最久的元素。
LFU算法
LFU算法的设计原则时,如果一个数据在最近一段时间被访问的时次数越多,那么之后被访问的概率会越大,基本实现是每个数据都有一个引用计数,每次数据被访问后,引用计数加1,需要淘汰数据时,淘汰引用计数最小的数据。在Redis的实现中,每次key被访问后,引用计数是加一个介于0到1之间的数p,并且访问越频繁p值越大,而且在一定的时间间隔内,
如果key没有被访问,引用计数会减少。