面灵AI→

字节跳动业务中台后端一面面经

轮次
一面
时间
2026-10
来源
牛客网

《面试题目》

  1. Redis GEO 的底层是什么?GEO 是怎么实现经纬度计算的?
  2. 为什么要用 GEO,为什么不直接从数据库里查?
  3. 第二段实习的分布式锁:为什么要用分布式锁?它解决了什么问题,有什么意义?
  4. 进程、线程、协程的区别是什么?
  5. 手撕:单词接龙。

《参考解析》

Redis GEO 底层是 GeoHash 加有序集合,本质是把二维问题压成一维范围查询。 做法是把经度和纬度分别做区间二分,交替取位编码成一个整数(Redis 用 52 位,经纬度各 26 位,落在 zset 的 score 里),再配合 GEOADD / GEOSEARCH 使用。这样「附近的人」就变成了对 zset 的 score 做范围查询:先按目标点的 geohash 前缀圈出它所在的格子以及周围 8 个相邻格子,用 ZRANGEBYSCORE 捞出候选集,最后对候选点逐个算真实距离过滤。复杂度是 O(log N + M),M 是候选数,跟总数据量基本无关。要补两点工程细节:格子边长由精度决定,精度选得粗会捞回大量无关点,选得细就要检查更多相邻格,实际要按查询半径反推精度;跨经线(±180 度)和两极附近的格子邻居计算要特殊处理,GeoHash 的边界问题不处理会漏结果。

为什么不用数据库直接查,答案在索引的维度上。 传统 B+ 树索引是一维有序的,WHERE lat BETWEEN a AND b AND lng BETWEEN c AND d 只能先用一个维度的索引把范围缩下来,另一个维度只能在回表后过滤,半径一大候选集就爆炸;而且半径变化时索引选择性完全不可控。数据库也不是不能做,MySQL 有 SPATIAL 索引(R 树)、PostgreSQL 有 PostGIS,但空间索引的写入维护成本高、分库分表后基本失效,还要额外做距离计算。Redis 的优势是数据全在内存、zset 本身就是有序结构、范围扫描极快,等于用「一次范围查询 + 少量距离计算」换掉了复杂空间索引。选型边界也要说清:只是「附近 N 公里、按距离排序」这类需求 Redis GEO 足够;涉及多边形判断、路径规划、复杂地理聚合时就该上 Elasticsearch 的 geo 查询或者专业地理引擎。

分布式锁的价值在于把「进程内互斥」升级成「跨机器互斥」。 单机时代用 synchronized 或 ReentrantLock 就够了,但服务多实例部署后,同一份共享资源(库存、账户余额、定时任务、对账批次)会被不同机器上的线程同时改,JVM 锁完全管不到别的进程,于是需要一把所有实例都认的锁。Redis 实现的三要素:加锁用 SET key value NX PX 一条命令完成「不存在才设置 + 带过期时间」,必须是原子的,过期时间用来防持锁进程崩溃导致的死锁;value 放唯一标识(UUID 加线程 id),释放时用 Lua 先比对 value 再删除,否则可能删掉别人刚拿到的锁;业务执行时间不确定时要用看门狗自动续期(Redisson 的 watchdog 就是干这个)。再往上还有 Redlock 和 ZooKeeper / etcd 这类强一致方案,选型看对可用性和一致性的取舍。最后一定要提醒:分布式锁不能替代幂等设计——网络分区、GC 停顿、锁过期都可能让两个进程同时进入临界区,真正的兜底是数据库唯一约束或业务状态机。

进程、线程、协程的差别,一句话是「隔离性越强越贵,并发度越高越依赖运行时」。 进程是资源分配单位,有独立地址空间,隔离性最好、一个崩了不影响别人,但创建和切换要动页表和 TLB,开销最大,进程间通信还得走共享内存或管道。线程是 CPU 调度单位,共享进程地址空间,切换只保存寄存器和栈,比进程轻,但同步要靠锁、一个线程野指针能弄死整个进程。协程是用户态调度,由语言运行时或框架在同一线程上切换,不陷入内核,栈按需增长,切换开销在纳秒级,所以能开出几十万并发——Go 的 goroutine、Kotlin 的 coroutine、Java 的虚拟线程都属于这一类。代价也必须讲:协程需要非阻塞 IO 配合,一个阻塞式系统调用或者一段 CPU 密集计算会把承载它的整个线程卡住,所以要靠调度器把阻塞点让出去,或者把 CPU 密集任务丢回线程池。