Galxe - 二面 - 杭州 面经 已oc

(一个小时二十分钟)

1. 介绍一下最近三个月做的事情。

2. 对于 rocksdb 了解吗?(参与的开源项目中用到了)

3. 讲讲在这个项目中的用法。(对实现不太了解,从设计相关的角度讲了一下)

4. 假设你有一个集群,有一批大量的 kv(key 很大(无规律),value很小)需要写入,你想想这里对读/写分别怎么优化?(大概从集群结构,然后梳理到单节点,之后讨论读写优化,讲异步写的时候提到了 WAL)

5. 项目中 WAL 怎么用的?(拉了一个具体的实现场景出来讲,ZSet SkipList优化实现相关,讲了流程)

6. 集群分片这里怎么实现?(上一轮面试和另一个面试官聊过,大概讲了一下)

7. 我看你这里有做集群的扩缩容,讲讲怎么做的。(本节点存一份连接信息,新增Voter,然后广播出去)

8. 我看你这里有一些内存泄漏的排查经验,可以讲讲平时怎么排查吗?(还是拉了一件具体做过的事情讲了整个排查到确认问题的过程)

9. 我看你这里Golang比较熟悉,做个题吧。(并发安全的LRUCache)

遇到两次了,贴个代码

package main

import (
	"fmt"
	"sync"
	"sync/atomic"
	"unsafe"
)

type Node struct {
	key  string
	val  string
	next *Node
	prev *Node
}

type LRUCache struct {
	capacity int
	size atomic.Int64
	cache sync.Map // key -> *Node
	head  *Node
	tail  *Node
}

func NewLRUCache(capacity int) *LRUCache {
	lru := &LRUCache{
		capacity: capacity,
		head:     &Node{},
		tail:     &Node{},
	}

	lru.head.next = lru.tail
	lru.tail.prev = lru.head

	return lru
}

func (c *LRUCache) Get(key string) (string, bool) {
	if node, ok := c.cache.Load(key); ok {
		c.moveToHead(node.(*Node))
		return node.(*Node).val, true
	}

	return "", false
}

func (c *LRUCache) Put(key string, value string) {
	if node, ok := c.cache.Load(key); ok {
		node.(*Node).val = value
		c.moveToHead(node.(*Node))
		return
	}

	newNode := &Node{key: key, val: value}
	c.cache.Store(key, newNode)
	c.addToHead(newNode)
	c.size.Add(1)

	if c.size.Load() > int64(c.capacity) {
		removed := c.removeTail()
		c.cache.Delete(removed.key)
		c.size.Add(-1)
	}
}

func (c *LRUCache) addToHead(n *Node) {
	atomic.CompareAndSwapPointer((*unsafe.Pointer)(unsafe.Pointer(&n.prev)), unsafe.Pointer(n.prev), unsafe.Pointer(c.head))
	atomic.CompareAndSwapPointer((*unsafe.Pointer)(unsafe.Pointer(&n.next)), unsafe.Pointer(n.next), unsafe.Pointer(c.head.next))
	atomic.CompareAndSwapPointer((*unsafe.Pointer)(unsafe.Pointer(&c.head.next.prev)), unsafe.Pointer(c.head.next.prev), unsafe.Pointer(n))
	atomic.CompareAndSwapPointer((*unsafe.Pointer)(unsafe.Pointer(&c.head.next)), unsafe.Pointer(c.head.next), unsafe.Pointer(n))
}

func (c *LRUCache) moveToHead(n *Node) {
	c.removeNode(n)
	c.addToHead(n)
}

func (c *LRUCache) removeNode(n *Node) {
	atomic.CompareAndSwapPointer((*unsafe.Pointer)(unsafe.Pointer(&n.prev.next)), unsafe.Pointer(n.prev.next), unsafe.Pointer(n.next))
	atomic.CompareAndSwapPointer((*unsafe.Pointer)(unsafe.Pointer(&n.next.prev)), unsafe.Pointer(n.next.prev), unsafe.Pointer(n.prev))
}

func (c *LRUCache) removeTail() *Node {
	tail := c.tail.prev
	c.removeNode(tail)
	return tail
}

func main() {
	// 创建一个容量为 2 的 LRUCache
	c := NewLRUCache(2)

	// 放入 k1, k2
	c.Put("k1", "v1")
	c.Put("k2", "v2")

	// 再次获取 k1
	if val, ok := c.Get("k1"); ok {
		fmt.Println("Get k1:", val) // 期望输出 "v1"
	} else {
		fmt.Println("k1 不存在")
	}

	// 插入 k3,此时容量超出,应当淘汰最久未使用的元素 (k2 或 k1)
	c.Put("k3", "v3")

	// 检查 k2 是否被淘汰
	if val, ok := c.Get("k2"); ok {
		fmt.Println("Get k2:", val)
	} else {
		fmt.Println("k2 不存在,已经被淘汰")
	}

	// 继续查看 k3
	if val, ok := c.Get("k3"); ok {
		fmt.Println("Get k3:", val) // 期望输出 "v3"
	} else {
		fmt.Println("k3 不存在")
	}
}

10. 继续做题。(假设在区块链中每次交易是一个最小事务,我现在需要保证并发进行的结果和串行的结果是一致的,怎么实现)

结构定义:

    type Transaction struct {
    	ID string
    	WriteSet map[string]string // 准备写入的 key->newValue
    }

测试数据:

txs := []Transaction{
    		{
    			ID: "Tx1",
    			WriteSet: map[string]string{
    				"A": "100",
    				"B": "200",
    			},
    		},
    		{
    			ID: "Tx2",
    			WriteSet: map[string]string{
    				"C": "300",
    				"B": "200",
    			},
    		},
    		{
    			ID: "Tx3",
    			WriteSet: map[string]string{
    				"A": "200",
    			},
    		},
    	}

大致意思就是现在串行的结果是 A = 200, B = 200, C = 300

需要修改为并发,并且并发执行的结果要和串行的一致。

思路:注意到 ID 的性质,可以根据 ID 确认哪个值更新,考虑类似多版本的思想,将 ID 作为结果的版本,并发执行时保存所有的版本,最终结果即每个key最新版本的值。

维护版本使用大根堆即可。如下:

 	type TxPair struct {
    	ID    string
    	Value string
    }
    
    type TxPairHeap []TxPair
    
    func (h TxPairHeap) Len() int { return len(h) }
    
    func (h TxPairHeap) Less(i, j int) bool { return h[i].ID > h[j].ID }
    
    func (h TxPairHeap) Swap(i, j int) {
    	h[i], h[j] = h[j], h[i]
    }
    
    func (h *TxPairHeap) Push(x interface{}) {
    	*h = append(*h, x.(TxPair))
    }
    
    func (h *TxPairHeap) Pop() interface{} {
    	old := *h
    	n := len(old)
    	x := old[n-1]
    	*h = old[0 : n-1]
    	return x
    }


剩下的 goroutine + WaitGroup,不断Push到堆就行

	func main() {
    	txs := []Transaction{
    		{
    			ID: "Tx1",
    			WriteSet: map[string]string{
    				"A": "100",
    				"B": "200",
    			},
    		},
    		{
    			ID: "Tx2",
    			WriteSet: map[string]string{
    				"C": "300",
    				"B": "200",
    			},
    		},
    		{
    			ID: "Tx3",
    			WriteSet: map[string]string{
    				"A": "200",
    			},
    		},
    	}
    
    	res := make(map[string]*TxPairHeap)
    
    	var wg sync.WaitGroup
    	mu := &sync.Mutex{}
    
    	for _, tx := range txs {
    		wg.Add(1)
    		go func(t Transaction) {
    			defer wg.Done()
    
    			mu.Lock()
    			defer mu.Unlock()
    
    			txID := t.ID
    			for k, v := range t.WriteSet {
    				var pairHeap *TxPairHeap
    				if _, ok := res[k]; ok {
    					pairHeap = res[k]
    				} else {
    					pairHeap = &TxPairHeap{}
    					res[k] = pairHeap
    					heap.Init(pairHeap)
    				}
    
    				pairHeap.Push(TxPair{ID: txID, Value: v})
    			}
    		}(tx)
    	}
    
    	wg.Wait()
    
    	for k, hp := range res {
    		fmt.Println("Key = ", k, "Value = ", hp.Pop().(TxPair).Value)
    	}
    }

11. 这里如果我每个交易都需要一个前提条件的完成后才能进行,怎么办,讲讲思路。(引入拓扑,维护拓扑性质的前提下维护堆性质)

---

结束。

有些问题忘了(

---

2.24 update: oc

#面经#
全部评论
记录一下:和群友聊天的时候才发现 lru 并发有点问题(,还是得加大锁,优化的话考虑分桶
1 回复 分享
发布于 02-22 23:36 宁夏
太强了,佬
点赞 回复 分享
发布于 03-14 14:54 湖南
都是本地idea写的吗
点赞 回复 分享
发布于 02-25 10:32 香港

相关推荐

前情提要:https://www.nowcoder.com/share/jump/1744867053616--太长不看,直接先上整理的面经# 4399 - java只有一面 ● 你为什么选择投递Java后端开发岗位?  ● Java和Go语言的优缺点是什么?  ● 你了解Go语言的协程实现吗?  ● 在Go语言中,编写协程时需要关注哪些问题?  ● Go语言中,有哪些方案可以保证并发安全?  ● Go语言中常见的原子操作有哪些?  ● Go中的sync.WaitGroup和sync.Once有什么区别?  ● 如果第三方接口返回的数据类型不确定,你会如何设计数据结构?  ● 如何处理Go语言中接口的空类型?  ● 如果你请求第三方接口时出现超时,你会如何处理?  ● 在Go语言中,如何使用Context实现请求超时?  ● Go语言中常用的ORM框架有哪些?  ● MySQL中常见的锁类型有哪些?  ● MySQL中的间隙锁是如何产生的?  ● Redis中常见的数据存储结构有哪些?  ● 如果有多个服务器需要加锁处理接口请求,你会怎么做?  ● 如何实现分布式锁?在Redis中,分布式锁会用到哪些命令?  ● 如果分布式锁没有正常释放,你会如何进行容灾处理?  ● 如果加了分布式锁后,业务长时间被阻塞,如何减少服务不可用的时间?  ● 如何监控接口响应时间并优化服务的可用性?  ● 如果你需要将代码部署到阿里云的Linux服务器上,你会如何做?  ● 如何在Windows开发环境下打包Go语言代码,并使其在Linux环境中运行?  ● 你觉得自己做的哪些项目比较有亮点?  ● 在设计单点登录系统时,遇到的核心难点是什么?  ● 为什么你选择找实习,除了零花钱,还有哪些原因?  ● 如果公司需要你学习新的编程语言,你是否有信心快速上手?  ● 你的学习规划是什么样的?  # 讯飞 - java - 消费者 只有一面  ● Golang语言的优势和劣势是什么?  ● 你之前在抖音服务端开发的项目中,团队的规模有多大?  ● 作为服务端后端负责人,你在项目中具体负责哪些工作?  ● 你是如何管理项目的节奏和设计文档的?  ● 在团队协作中,你是如何分配任务和沟通进度的?  ● 在项目中遇到过团队成员之间的认知偏差,如何处理?  ● 你在项目中遇到过哪些技术上的挑战或难题?  ● Golang语言中,内存泄漏的常见原因是什么?  ● 如何排查Golang中的内存泄漏问题?  ● Go语言的协程与传统线程有什么区别?  ● Go语言是如何实现协程之间的通信的?  ● MySQL的索引结构是什么?  ● B+树是什么样的结构,它有哪些特性?  ● 聚簇索引和非聚簇索引有什么区别?  ● 如果一个表没有主键,它还会有聚簇索引吗?  ● 如果我们在多个字段上建立联合索引,字段顺序是a、b、c,查询条件为b=... and a=...,会使用该索引吗?  ● 为什么MySQL使用MVCC来实现不同的事务隔离级别?  ● 你在项目中使用过Redis吗?  ● 使用Redis作为缓存时,如何保证缓存和底层数据的一致性?  ● 当某些数据访问频繁时,删除缓存可能会带来压力,如何优化?  ● 在高并发的场景下,如何优化旁路缓存策略?  ● 如果遇到DB和缓存不一致的情况,如何解决?  ● Redis的高性能是如何设计出来的?  ● Redis为何采用单线程模型,它的性能优势是什么?  ● 在高并发场景下使用分布式锁时,如何避免加锁带来的性能问题?# 知乎 - 监控组● 前缀树是什么?它的应用场景是什么?  ● LRU缓存是怎么实现的?  ● 你能解释一下虚拟内存吗?它解决了什么问题?  ● 如果宿主机的CPU打爆了,你如何判断哪个进程占用了最多的CPU资源?  ● 软链接和硬链接有什么区别?  ● 什么是上下文切换?一般在什么情况下会发生上下文切换?  ● 如果创建了10万线程来处理任务,会有什么问题?除了内存泄漏和性能问题,还有哪些方面会受到影响?  ● 你怎么分析慢SQL查询?  ● IP协议和ARP协议的作用分别是什么?  ● 如果带宽不是瓶颈,如何快速传输大文件?  ● Singleflight的机制是什么?  ● TCP的流量控制和拥塞控制有何不同?  ● 如何调整TCP的滑动窗口大小,以确保最大的吞吐量?  ● 常见的限流算法有哪些?漏桶算法和令牌桶算法有什么区别?  ● 雪花算法是什么?为什么你在项目中使用了它?  --面试上的反思的话,最开始也好像也没太多好说的:问题后面还是改掉了大部分只是最开始面试的话,根本就不怎么会面试虽然我也是前暑期,大二下就开始的了但是我根本没那么强的学习进化能力这是比较让人绝望的事情经历回顾的话,我的秋招是从十月末开始的那个时候已经准备从实习离职了,没转正然后其实当时,根本就没多少中大厂能过我的简历想了下后面还是详细开另一个帖子专门说我的秋招详细经历和心路,如果有人感兴趣的话这里就先打住这里只说跟面试强相关一点的事情了清楚记得第一个过我简历还是4399所以虽然面试体验不好?好像也没太不好,算一般吧。只是公司比较一般面4399的时候,更多是基础知识没答上来。一些场景分析欠佳现在想来的话,其实这些东西都能背不是只停留在对基础知识的理解,而是确实去针对针对问题的回答演练所以当时得出的一个很重要的结论是,模拟面试和刷面经很重要。想起来了,面试官迟到+只面了30min,只能说态度还行这个是实习中面的,偷感拉满当时装作去对接安卓头头离开的工位讯飞忘了是什么阶段了当时在校还找不到面试的地方在图书馆阳台面的,环境比较差整体好像回答得还行,但是也是一面就挂了算是第一个还挺想去的厂梦碎了当时应该是问得算简单,但是几个关键技术问题答得不是很完美就挂了不过说起来讯飞挺看测评的,面试的时候还问我有认真做没知乎也清楚记得,是离职前一天面的很有意思,当时馒头还说我们这边挺方便的,到处都是能面的会议室然后整体知乎算是第一次给我打上一点自信的面试体验还不错,面试官虽然没开麦,但是会充分引导你然后这场突出一个酣畅淋漓其实问题不止上面那些,是ai提取的,如果想要详细的可以私我就是问的问题都挺有难度,但是我也能答出来一些虽然最后还是不合要求给挂了
青春猪头少年不能没有offer:佬真的很优秀,加油!哥们也还在找
点赞 评论 收藏
分享
评论
8
12
分享

创作者周榜

更多
牛客网
牛客企业服务