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 回复 分享
发布于 2025-02-22 23:36 宁夏
太强了,佬
点赞 回复 分享
发布于 2025-03-14 14:54 湖南
都是本地idea写的吗
点赞 回复 分享
发布于 2025-02-25 10:32 香港

相关推荐

04-13 15:31
门头沟学院 Java
某游戏厂,面了 1h。大部分时间都是问纯八股,项目一点没问,手撕也很简单,网上搜到的面经大部分是C++八股文轰炸或者项目拷打。是不是因为一开始就对我不感兴趣所以干脆不为难我了面经如下:自我介绍游戏经历主要编程语言(我说的Java 但是岗位写的是C++/GoLang)求职方向是后端,为什么选择游戏服务器开发有Linux使用经历吗(项目部署)用过的Linux命令查看文件用什么命令,查看大文件呢?租服务器会关注服务器配置吗,如何确定这个配置能够满足项目部署的需求?会分析服务器使用情况吗(CPU、内存使用率),如何定位具体的线程资源使用情况?讲讲数组和链表结构、常用操作、时间复杂度为什么数组支持随机访问(内存连续+偏移量)讲讲栈和队列结构、区别、应用讲讲RabbitMQ如何用数组实现队列讲讲哈希,平时用过哪些哈希的数据结构哈希表的key如何获得什么是哈希冲突哈希底层原理了解吗面向对象三大特性现场写一下多态的例子讲讲平时用过的设计模式手撕反转链表、反转字符串反问的时候面试官说我可以自信一点()最后给点建议吧:纯八股 + 项目一点没问,大概率不是“不感兴趣所以不为难你”,更可能是:1,面试官习惯按固定流程走,先筛基础2,或者他觉得项目跟岗位匹配度不高,问了也白问,3,面了一个小时还给建议,说明你至少过了他的及格线。别自己加戏
查看23道真题和解析
点赞 评论 收藏
分享
04-13 09:20
已编辑
电子科技大学 C++
自我介绍 实习1. 去上一家公司实习的目的?2. 为什么离职?3. 上一家公司职场氛围和交流氛围如何?4. 上一家公司实习主要的工作背景和产出?5. 介绍一下上一家公司实习的背景和原理6-12. 实习拷打13. 上一家公司有没有 AI 提效工具?有没有 AI 培训?其他员工有没有相关的使用经验?14. 你为什么在实习开发中使用 AI 工具吗?15. 总结一下上一家公司实习你的收获是什么?16. 实习期间,你遇到最困难的一个点?你是如何解决的?项目1. Raft 项目的动机是什么?算法无闲聊1. 你转专业了吗?还是自学?2. Golang 和 C++ 哪个用得比较多?3. 面试官介绍 Golang 和 C++ 在后端和鸡架开发之间的差异...4. 能实习多久?专业其他同学的规划是读研还是就业?5. 你为什么想要就业?你不用上课吗?6. 有没有想过跨考?7. 反问总结第一次约面后,面试官临时有会,面试前 5 分钟取消会议。推迟了一天,然后又迟到 10 分钟。自我介绍完就感觉像是 KPI 面了,不过没关系,感觉还是很好为人师的面试官,反问环节直接让他帮我把从 C++ 到 Golang 学习路线规划了一下,也请教了一下应该阅读哪些书籍。
发面经攒人品
点赞 评论 收藏
分享
04-10 14:00
门头沟学院 Java
4/1 hr 电话约面的时候问了是否可以转 golang, 同意后约面面试官开头介绍技术栈为 golang面试体验很好, 问答之后基本都有正面回应, 但没怎么挑我的刺, 面试官可能不熟悉 JAVA 或根本就不想要我没录音可能有遗漏Q1 自我介绍Q2 你是怎么构建这个 agent 的 (组装链 + 执行链)Q3 在执行过程中出现问题怎么解决的, 采用了什么降级措施吗 (没有采用, 直接终止)Q4 你项目上说了 RAG, 你来介绍一下 RAG 在你的项目中是怎么使用的 (作为 advisor 角色, 在思考流程时通过知识库的形式组装到 prompt 中)Q5 你项目使用了 sse, 说说 sse 是什么与 websocket 有什么区别? (sse 单向构建简单)Q6 项目中你是怎么使用 sse 时? (在 trigger 层中配置了 sse 的三个参数, 使用 emitter)Q7 你刚才提到了 trigger 层这一 DDD 领域概念, 你知道 DDD 吗? (不太熟悉, 扯了一下分层, VO, 聚合根)Q8 你这个高并发本地服务平台有什么用? (黑马点评)Q8 你第二个项目高并发平台测试过多高并发度吗? (瞎扯了几百并发度, 实际还没测试)Q9 你说实现了 session 共享怎么实现的, redis 的 key 和 value 怎么储存的 (通过 redis 实现的, 将 session id 作为 key 存储到 redis 中, key 和 value 都是 string)Q10 你说能够无感 token 刷新与权限校验是怎么实现的 (这里我忘记了, 就扯 redis 存然后将 token 返回给前端浏览器)Q11 你说返回给前端浏览器, 然后我换一个浏览器是不是 token 就失效了? (是, 因为 token 是存在浏览器中的)Q12 你提到了 cache aside, 它是什么? (redis 未命中则取数据库, 还说了一下另外两种, 说了一种另一种忘记了)Q13 你说用延迟双删实现过期时间补偿, 什么是延迟双删 (先删 redis 后 sleep 再删 redis)Q14 这个 sleep 设置时间是怎么确定的? (由于前面扯了几百并发度, 就说在这个并发度下这个时间最合适)Q15 你提到了互斥锁, 聊聊你项目里的互斥锁? (首先是 setnx 与 ex 手工首先的互斥锁, 但没有过期续费和可重入功能所以还使用了 redisson)Q16 你提到了布隆过滤器? 说说它的原理 (本质是 hash 表 + 多个 hash 函数, 对应槽位为 0 一定不存在, 全为 1 不保证一定存在)Q17 怎么提高布隆过滤器的准确度 (根据准确度的计算公式, 多增加 hash 函数来实现)Q18 你使用了 lua 脚本, 它的原子性是怎么实现的 (这个一点都不知道, 直接回答了不知道)Q19 后面你提到了 rabbitmq 消息队列, 为什么使用它, 它有哪些使用场景 (聊了 redis 自带的三种消息队列各自的缺点, 但使用场景没讲清除)Q20 你使用了 hyperloglog, 你知道它的原理吗 (不熟悉, 回答不知道后面自己补充了 geo 的原理)Q21 你知道 zset 是怎么实现的吗? (skiplist + score / ziplist)手撕:Q1 最大子数组和 (秒后讲一下原理, dp)反问:Q1 组内业务是做什么的? (QQ 浏览器 + 推荐广告)Q2 是推荐算法吗? (不是, 就是根据已经为用户选好的广告来推送)反思:面试之前都是复习第一个 agent 项目和八股去了, 导致后面的点评很多都忘记了, 后面打算改一下简历, 去掉一些没有和业务相关的技术.还要修正一下自己的回答方式, 多从 业务 -> 技术的角度来思考回复
查看25道真题和解析
点赞 评论 收藏
分享
04-07 17:56
已编辑
门头沟学院 golang
pdd 是我面试体验过的最差的公司,没有之一。面试官是一个年近中年老油条。1. 开口爆典问学历,是 985/211 吧?哦原来是啊,我没怎么听说过,不怎么有名吧。2. 有实习吗?拿到 offer 了吗?3. 我们组主要用 Java, go 在我们公司用的其实比较少,主要是在某节用得多,为什么想要来上海工作?我说随便选的,北京上海深圳随机选一个(第一个问题问完我就已经不想回答了)4. 然后就开始给我戴帽子:“哦,也就是说你对你的未来没有什么规划是吧?”我听到这实在蚌埠住了,我直接和他说:“我不认为是我自己的规划有问题,你们公司在该岗位没有写明语言限制,而且我的简历上也写明了我期望的工作是 golang 后端开发,面试安排也是你们公司安排的,是你们公司的招聘部门的规划出现了问题。”5. 然后他一看我很强硬他怂了,然后就跟我说可不可以接受转语言,如果不可以接受他可以帮我对接一个 go 开发相关的面试官。然后我钓着他,说我写过一些 Java 开发的项目,如果你们组业务对口,我也不是不接受转语言。然后他巴拉巴拉讲了一串他们组的业务(我一个字没听),然后我和他说:“那我还是坚持找 golang 开发的岗位吧。”然后就挂了,跟我说另安排招 golang 的面试官面试。总结:全程五分钟,我从寝室出来找个位置+调试设备都花了3-4个五分钟?
钱嘛数字而已:感觉像新手面试官,哪有开局就给孩子们讲业务的,谁面试谁呢?
查看5道真题和解析
点赞 评论 收藏
分享
评论
10
14
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务