头图

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

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

这次面试
主要就考察了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,备注:sf面试群。


王中阳Go
799 声望295 粉丝