写点什么

小鹅通面经详解,冲!

作者:王中阳Go
  • 2024-10-22
    北京
  • 本文字数:3911 字

    阅读完需:约 13 分钟

小鹅通面经详解,冲!

不知道大家有没有用过小鹅通,我以前就用过,今天分享一下训练营的朋友在小鹅通的面经,希望对你有帮助。


目前只是进行了一面,还在等通知,但是他本人和我说感觉面的不太理想,遇到的很多问题明明都会但是当时却又回答不上来,可以说很多小伙伴都有这样的问题,这多半是心态和表达能力的问题,面试可不只是考察你对知识点的掌握程度,你还要知道怎么去表达才行。


这次面试主要就考察了 go 并发、计网和项目,内容都给你们整理好了:

go

go 的 map 是并发安全的吗?为什么?

答案肯定是:map 不是并发安全的


因为它没有内置的锁机制来保护多个 goroutine 同时对其进行读写操作。


当多个 goroutine 同时对同一个 map 进行读写操作时,就会出现数据竞争和不一致的结果。


当两个或者多个 goroutine 同时尝试更新同一个键值对时,最终的结果可能取决于哪个 goroutine 先完成了更新操作。这种不确定性可能会导致程序出现错误或崩溃。


Go 语言团队没有将 map 设计成并发安全的,是因为这样会增加程序的开销并降低性能。


如果 map 内置了锁机制,那么每次访问 map 时都需要进行加锁和解锁操作,这会增加程序的运行时间并降低性能。


而且并不是所有的程序都需要在并发场景下使用 map,因此将锁机制内置到 map 中会对那些不需要并发安全的程序造成不必要的开销。

如何保证 map 的并发安全?除了加 mutex 外呢?还有其他办法吗?

最常用的就是加锁,Mutex、RWMutex 都可以,当然除此之外还有其他的:


  • 分片加锁


通过对整个 map 加锁来实现需求很简单,但相对来说,锁会大大降低程序的性能,那如何优化呢?其中一个优化思路就是降低锁的粒度,不对整个 map 进行加锁。


这种方法是分片加锁,将这个 map 分成 n 块,每个块之间的读写操作都互不干扰,从而降低冲突的可能性。


package main
import ( "fmt" "sync")
const N = 16
type SafeMap struct { maps [N]map[string]string locks [N]sync.RWMutex}
func NewSafeMap() *SafeMap { sm := new(SafeMap) for i := 0; i < N; i++ { sm.maps[i] = make(map[string]string) } return sm}
func (sm *SafeMap) ReadMap(key string) string { index := hash(key) % N sm.locks[index].RLock() value := sm.maps[index][key] sm.locks[index].RUnlock() return value}
func (sm *SafeMap) WriteMap(key string, value string) { index := hash(key) % N sm.locks[index].Lock() sm.maps[index][key] = value sm.locks[index].Unlock()}
func hash(s string) int { h := 0 for i := 0; i < len(s); i++ { h = 31*h + int(s[i]) } return h}
func main() { safeMap := NewSafeMap()
var wg sync.WaitGroup
// 启动多个goroutine进行写操作 for i := 0; i < 10; i++ { wg.Add(1) go func(i int) { defer wg.Done() safeMap.WriteMap(fmt.Sprintf("name%d", i), fmt.Sprintf("John%d", i)) }(i) }
wg.Wait()
// 启动多个goroutine进行读操作 for i := 0; i < 10; i++ { wg.Add(1) go func(i int) { defer wg.Done() fmt.Println(safeMap.ReadMap(fmt.Sprintf("name%d", i))) }(i) }
wg.Wait()}
复制代码


在这个示例中,我们定义了一个 SafeMap 结构体,它包含一个长度为 N 的 map 数组和一个长度为 N 的锁数组。


定义了两个方法:ReadMap 和 WriteMap。在这两个方法中,我们都使用了一个 hash 函数来计算 key 应该存储在哪个 map 中。然后再对这个 map 进行读写操作。


在 main 函数中,我们启动了多个 goroutine 来进行读写操作,这些操作都是安全的。


  • sync.Map


在内置的 sync 包中(Go 1.9+)也有一个线程安全的 map,通过将读写分离的方式实现了某些特定场景下的性能提升。感兴趣的小伙伴可以去使用一下,或者查看一下它的源码实现。

并发安全的 map 在写的时候会有大量锁竞争,导致读操作读不到数据,这个问题怎么解决?

可以考虑以下几种方案:


  1. 使用更细粒度的锁:如果你的应用场景允许,可以考虑自己实现一个具有更细粒度锁定机制的并发安全 map。例如,你可以创建一个基于哈希槽(hash slots)的 map,每个槽都有自己的锁,这样只有访问相同槽的操作才会发生锁竞争。

  2. 乐观锁/版本控制:对于某些特定情况,可以考虑使用乐观锁或者版本号的方式来管理数据更新,这样可以在一定程度上减少锁的使用。

  3. 使用带缓存的 map:对于一些读多写少的情况,可以采用带缓存的数据结构。比如先从一个无锁只读的副本中读数据,只有当发现数据陈旧时再加锁更新主数据结构,并刷新只读副本。

  4. 升级到 sync.Map 以外的库:有一些第三方库提供了更高性能的并发安全 map 实现,如 go-map 或者 concurrent-map 等,它们可能采用了更先进的算法来降低锁竞争。

  5. 利用 Go 的通道:在某些情况下,可以通过 channel 来传递需要更新的数据,而不是直接修改共享的 map。这种方式可以将状态变更委托给单独的 goroutine 来处理,从而避免锁的竞争。

  6. 数据分区:将数据分成几个部分,每个部分由独立的 sync.Map 管理,这样即使在一个部分上有锁竞争,也不会影响到其他部分。

计算机网络

在浏览器上输入 baidu.com 这个操作,背后都发生来什么?

一个很常见的问题,一定要掌握。


1、客户端浏览器通过 DNS 解析到 www.baidu.com 的 IP 地址 202.108.22.5,通过这个 IP 地址找到客户端到服务器的路径。客户端浏览器发起一个 HTTP 会话到 202.108.22.5,然后通过 TCP 进行封装数据包,输入到网络层。


2、在客户端的传输层,把 HTTP 会话请求分成报文段,添加源和目的端口,如服务器使用 80 端口监听客户端的请求,客户端由系统随机选择一个端口如 5000,与服务器进行交换,服务器把相应的请求返回给客户端的 5000 端口。然后使用 IP 层的 IP 地址查找目的端。


3、客户端的网络层不用关心应用层或者传输层的东西,主要做的是通过查找路由表确定如何到达服务器,期间可能经过多个路由器,这些都是由路由器来完成的工作,我不作过多的描述,无非就是通过查找路由表决定通过那个路径到达服务器。


4、客户端的链路层,包通过链路层发送到路由器,通过邻居协议查找给定 IP 地址的 MAC 地址,然后发送 ARP 请求查找目的地址,如果得到回应后就可以使用 ARP 的请求应答交换的 IP 数据包现在就可以传输了,然后发送 IP 数据包到达服务器的地址。


事件顺序:


(1) 浏览器获取输入的域名 www.baidu.com


(2) 浏览器向 DNS 请求解析 www.baidu.com 的 IP 地址


(3) 域名系统 DNS 解析出百度服务器的 IP 地址


(4) 浏览器与该服务器建立 TCP 连接(默认端口号 80)


(5) 浏览器发出 HTTP 请求,请求百度首页


(6) 服务器通过 HTTP 响应把首页文件发送给浏览器


(7) TCP 连接释放


(8) 浏览器将首页文件进行解析,并将 Web 页显示给用户。


涉及到的协议:


(1) 应用层:HTTP(www 访问协议),DNS(域名解析服务)


DNS 解析域名为目的 IP,通过 IP 找到服务器路径,客户端向服务器发起 HTTP 会话,然后通过运输层 TCP 协议封装数据包,在 TCP 协议基础上进行传输


(2) 传输层:TCP(为 HTTP 提供可靠的数据传输),UDP(DNS 使用 UDP 传输)


HTTP 会话会被分成报文段,添加源、目的端口;TCP 协议进行主要工作


(3)网络层:IP(IP 数据数据包传输和路由选择),ICMP(提供网络传输过程中的差错检测),ARP(将本机的默认网关 IP 地址映射成物理 MAC 地址)

http 和 https 的区别?

  • HTTP 以明文形式传输数据,这意味着所有信息在传输过程中都是未加密的,因此安全性较差。相比之下,HTTPS(基于 SSL/TLS 的 HTTP)在整个数据传输过程中使用加密技术,提供了更好的安全性。

  • 采用 HTTPS 协议通常需要从 CA(证书颁发机构)获取数字证书。虽然市面上有一些免费证书的选择(如 Let's Encrypt 提供的服务),但许多证书仍需支付一定费用。知名的证书颁发机构包括 Symantec、Comodo、GoDaddy 和 GlobalSign 等。

  • 就页面加载速度而言,HTTP 通常比 HTTPS 更快。这是因为 HTTP 连接仅需通过 TCP 的三次握手来建立,即客户端与服务器之间交换三个数据包即可完成连接建立过程。而 HTTPS 不仅要完成上述的 TCP 握手步骤,还需要额外进行 SSL/TLS 握手,这一过程涉及多达九个数据包的交互,总计需要十二个数据包才能建立起安全连接。

  • HTTP 与 HTTPS 使用不同的端口进行通信:HTTP 默认使用 80 端口,而 HTTPS 则使用 443 端口。由于 HTTPS 实际上是在 SSL/TLS 协议之上构建的 HTTP 协议,因此它相较于纯 HTTP 而言会消耗更多的服务器资源,尤其是在处理加密和解密操作时。

https 由于需要加密解密,这些操作会带来额外的开销,导致响应速度变慢,该如何优化?

可以采取以下措施:


  1. 使用支持 AES-NI 的 CPU:选择支持 AES 指令集的处理器,可以显著加速加解密过程。

  2. 启用 HTTP/2:HTTP/2 支持多路复用和头部压缩,能够减少网络延迟并提高页面加载速度。

  3. 实施 OCSP Stapling:通过在服务器端缓存证书状态信息,减少客户端与 CA 的验证通信,从而加快握手过程。

  4. 使用 HSTS:强制浏览器仅通过 HTTPS 访问网站,避免不必要的重定向。

  5. 配置 TLS 会话复用:允许客户端和服务器之间复用之前的 TLS 会话,减少每次请求时的握手次数。

项目

项目中最有挑战的难点有哪些?

出现频率非常高的问题,建议根据自己的项目提前做好对应的回答话语。

统一查询表达式如何解决不同数据源语法的差异化问题?

借鉴文章:


https://blog.csdn.net/qq_35887546/article/details/104241303


https://www.51cto.com/article/754626.html

欢迎关注 ❤

我们搞了一个免费的面试真题共享群,互通有无,一起刷题进步。


没准能让你能刷到自己意向公司的最新面试题呢。


感兴趣的朋友们可以加我微信:wangzhongyang1993,备注:infoq 面试群。

用户头像

王中阳Go

关注

靠敲代码在北京买房的程序员 2022-10-09 加入

【微信】wangzhongyang1993【公众号】程序员升职加薪之旅【成就】InfoQ专家博主👍掘金签约作者👍B站&掘金&CSDN&思否等全平台账号:王中阳Go

评论

发布
暂无评论
小鹅通面经详解,冲!_Go_王中阳Go_InfoQ写作社区