报个喜,百度一面过了~

教育   2024-12-24 08:30   湖南  

欢迎点击下方👇关注我,记得星标哟~

文末会有重磅福利赠送

今天的面经来自咱们训练营的一位超级机灵的小姐姐。她加入我们还不到一个月的时间,凭借着自己的努力加上我们的助力,先是顺利通过了百度的第一轮面试,紧接着又告诉我,阿里巴巴那边的第一轮面试也过了,真是好事成双啊!

她以前底子不是特别好,虽然能约到面试,但是经常在一面就被刷下来,这个真的是很多人的共性问题

加入我们的训练营后,有了系统的指导和持续的监督,再加上她本人也积极上进,各个方面都提升得很快。现在面试的情况,她自己是这么和我说的:”能和面试官聊起来,而且聊的挺开心“。

但是接下来还得继续加油,争取拿到更多的offer,到时候选择权就在她手里了。

今天,我想跟大家分享的就是她在百度面试的经历,面试的职位是在百度旗下的一款汽车资讯平台——有驾。如果你对这个职位或者面试经验感兴趣,就请继续往下看看哈。

unsetunset面试题目unsetunset

跟她项目内容相关的问题没有具体答案

  1. 自我介绍
  2. 说一下你的项目?
  3. 说一下你们的微服务架构?
  4. 介绍一下项目内容中台?
  5. 项目中的权限系统的存储是如何设计的?

微服务拆分太细了有什么问题?

  1. 通信成本增加

  • 每个微服务之间通过网络进行通信,频繁的网络请求会增加延迟。
  • 序列化和反序列化的开销也会增加,尤其是在使用JSON等文本格式时。
  • 部署和维护复杂度增加

    • 更多的服务意味着更多的部署单元,需要更多的服务器资源和运维工作。
    • 需要更多的监控和日志管理,以确保每个服务的健康状态。
  • 服务间依赖关系复杂

    • 服务之间的依赖关系变得更加复杂,可能导致“服务雪崩”现象,即一个小服务的故障可能引发整个系统的瘫痪。
    • 需要更多的协调和管理来确保各个服务之间的兼容性和一致性。
  • 团队协作难度增加

    • 团队成员需要了解更多的服务和接口,增加了学习和沟通的成本。
    • 协调多个团队的开发进度和发布计划变得更加困难。

    项目中是怎么使用gRPC的?gRPC和HTTP+JSON比较有什么差异?

    gRPC的使用:

    • 服务定义:使用Protocol Buffers(protobuf)定义服务接口和消息格式。
    • 客户端和服务端代码生成:通过protoc编译器生成客户端和服务端的代码。
    • 高效通信:使用高效的二进制协议,减少传输数据的大小和解析时间。
    • 流式传输:支持客户端流、服务器流和双向流,适用于需要大量数据交换的场景。

    gRPC与HTTP+JSON的差异:

    • 协议:gRPC使用HTTP/2协议,支持双向流式传输;HTTP+JSON使用HTTP/1.1协议,主要支持请求-响应模式。
    • 数据格式:gRPC使用二进制格式,传输效率更高;HTTP+JSON使用文本格式,易于阅读和调试。
    • 代码生成:gRPC可以自动生成客户端和服务端代码,减少开发工作量;HTTP+JSON需要手动编写请求和响应处理代码。
    • 性能:gRPC在大数据传输和高并发场景下性能更好;HTTP+JSON在简单应用场景下更灵活。

    项目中的权限系统存储是如何设计的?

    1. 用户表(Users)

    • 存储用户基本信息,如用户名、密码、邮箱等。
    • 示例字段:user_idusernamepasswordemailcreated_at
  • 角色表(Roles)

    • 存储角色信息,如角色名称和描述。
    • 示例字段:role_idrole_namedescription
  • 权限表(Permissions)

    • 存储具体的权限信息,如查看、编辑、删除等。
    • 示例字段:permission_idpermission_namedescription
  • 用户角色关联表(User_Roles)

    • 存储用户和角色的关联关系。
    • 示例字段:user_idrole_id
  • 角色权限关联表(Role_Permissions)

    • 存储角色和权限的关联关系。
    • 示例字段:role_idpermission_id

    权限系统的缓存是缓存的什么内容,大小多少,什么数据格式?

    1. 缓存内容

    • 用户ID
    • 用户的角色ID列表
    • 用户的权限列表
  • 缓存大小

    • 具体大小取决于用户数量和权限数量,但通常会设置合理的过期时间(如30分钟),以避免占用过多内存。
  • 数据格式

    • 键值对形式,键通常为用户ID,值为包含角色ID和权限列表的JSON字符串。
    • 示例:
      {
        "user_id""123",
        "roles": ["admin""user"],
        "permissions": ["read""write""delete"]
      }

    缓存有大Key如何解决?

    1. 分片

    • 将大Key的数据拆分成多个小Key,每个小Key存储一部分数据。
    • 例如,可以按角色ID分片,每个角色ID对应一个Key。
  • 使用更高效的数据结构

    • 使用Redis的哈希表(Hash)来存储复杂的数据结构。
    • 示例:
      HSET user:123 roles admin,user
      HSET user:123 permissions read,write,delete
  • 限制单个Key的大小

    • 在应用层限制单个Key的大小,超过一定阈值时进行分片存储。

    使用批量提交进行优化,有没有丢失数据的可能?如何解决?

    1. 丢失数据的可能性

    • 网络中断:在批量提交过程中,如果网络中断,部分数据可能没有成功提交。
    • 数据库错误:数据库在处理批量提交时可能出现错误,导致部分数据丢失。
  • 解决方案

    • 事务管理:将批量提交放在一个事务中,确保所有操作要么全部成功,要么全部失败。
    • 检查点机制:在每次批量提交前记录一个检查点,如果发生异常,可以从最后一个成功的检查点重新开始。
    • 幂等性设计:设计幂等性的批量提交接口,即使多次提交同一批数据也不会产生副作用。
    • 重试机制:在网络中断或其他临时错误时,自动重试批量提交操作。

    如果评论表要分表怎么设计?

    1. 分表策略

    • 示例:按年份和月份分表,如comments_2023_10comments_2023_11
    • 水平分表:按时间戳或评论所属的文章ID进行分表。
    • 垂直分表:将不同类型的字段分开存储,如将评论内容和元数据分开存储。
  • 分表后的管理

    • 路由机制:使用中间件或代理层自动路由请求到正确的表。
    • 统一接口:对外提供统一的接口,内部根据规则路由到不同的表。
    • 数据迁移:在分表后,需要将现有数据迁移到新的表结构中。

    分完表后,上线新表怎么做?

    1. 准备阶段

    • 创建新的表结构,并确保所有的业务逻辑已经适配新的表结构。
    • 进行充分的测试,确保新表结构的正确性和性能。
  • 灰度发布

    • 选择一小部分用户或流量,逐步将请求路由到新表。
    • 监控新表的表现,确保没有异常情况。
  • 全面切换

    • 一旦确认新表稳定可靠,可以逐步增加路由到新表的流量比例。
    • 最终完全切换到新表,关闭旧表的写入功能。
  • 回滚机制

    • 准备好回滚方案,如果新表出现问题,可以迅速切换回旧表。

    新表上线后应该读哪个表?

    1. 读取策略

    • 双写双读:在新表和旧表都写入数据,读取时优先从新表读取,如果新表中没有数据再从旧表读取。
    • 渐进切换:一开始主要从旧表读取数据,随着新表数据的积累,逐渐增加从新表读取的比例。
    • 最终切换:当新表的数据足够完整且稳定后,完全切换到新表读取。
  • 监控和日志

    • 监控新表的读取性能和数据完整性。
    • 记录读取日志,分析数据的一致性和完整性。

    聊一聊数据库的索引?

    1. 索引的作用

    • 加速查询:通过索引可以快速定位到目标数据,减少全表扫描的时间。
    • 唯一性约束:索引可以用于确保某个字段的值是唯一的。
  • 索引类型

    • B-Tree索引:最常见的索引类型,适用于范围查询和精确查询。
    • 哈希索引:适用于精确查询,但不支持范围查询。
    • 全文索引:用于全文搜索,支持复杂的文本匹配。
    • 位图索引:适用于布尔值或枚举值的查询。
  • 索引的代价

    • 存储开销:索引需要额外的存储空间。
    • 写入性能:插入、更新和删除数据时需要维护索引,可能会影响写入性能。
  • 索引优化

    • 选择合适的字段:为经常用于查询条件的字段创建索引。
    • 避免过度索引:不必要的索引会增加存储和维护成本。
    • 复合索引:为多个字段创建复合索引,提高查询效率。

    索引的叶子节点存的什么?MySQL一页多大?

    1. 叶子节点的内容

    • 聚簇索引:叶子节点存储完整的行数据。
    • 非聚簇索引:叶子节点存储指向聚簇索引的指针(通常是主键值)。
  • MySQL一页的大小

    • 默认情况下,MySQL的InnoDB存储引擎一页的大小是16KB。
    • 可以通过配置文件中的innodb_page_size参数调整页大小,但通常不建议修改。

    不使用自增ID作为主键,使用随机ID作为主键乱序插入有什么问题?

    1. 页分裂

    • 随机ID插入会导致页分裂,即当插入的数据不能放入现有的数据页时,需要将数据页拆分成两个页面。
    • 页分裂会增加磁盘I/O操作,降低插入性能。
  • 磁盘碎片

    • 随机插入会导致数据在磁盘上分布不均匀,形成磁盘碎片。
    • 磁盘碎片会降低读取性能,因为连续读取变得困难。
  • 索引树的高度

    • 随机ID插入会导致B-Tree索引树的高度增加,影响查询性能。
    • 插入顺序ID时,索引树的高度相对较低,查询性能更好。
  • 内存使用

    • 随机ID插入会增加缓冲池的碎片,影响内存使用效率。

    了解页分裂吗?

    1. 页分裂的原因

    • 当向已满的数据页中插入新记录时,数据库引擎会将该页分成两个页面,以便腾出空间来存放新记录。
    • 常见于随机插入和更新操作。
  • 页分裂的影响

    • 性能下降:页分裂会增加磁盘I/O操作,降低插入和更新性能。
    • 磁盘碎片:数据在磁盘上分布不均匀,影响读取性能。
    • 索引碎片:索引树的结构变得不规则,影响查询性能。
  • 避免页分裂的方法

    • 预分配空间:在创建表时预分配足够的空间,减少页分裂的发生。
    • 顺序插入:尽量使用自增ID或其他有序的主键,减少随机插入。
    • 定期优化:定期对表和索引进行优化,如使用OPTIMIZE TABLE命令。

    聚簇索引和覆盖索引的问题

    1. 聚簇索引

    • 定义:聚簇索引是表数据的物理存储方式,决定了数据在磁盘上的排列顺序。
    • 特点:每个表只能有一个聚簇索引,通常是主键索引。
    • 优点:查询速度快,因为数据按索引顺序存储。
    • 缺点:插入和更新性能较差,因为需要维护数据的物理顺序。
  • 覆盖索引

    • 定义:覆盖索引是指一个非聚簇索引包含了查询所需要的所有列,这样查询可以直接通过索引完成,而不需要再回表查找其他信息。
    • 特点:可以显著提高查询性能,减少磁盘I/O操作。
    • 创建方法:在创建索引时,确保索引包含查询所需的全部列。

    聊一聊事务?

    1. 事务的定义

    • 事务是一组数据库操作,这些操作要么全部成功执行,要么全部不执行,以保证数据的一致性和完整性。
  • ACID特性

    • 原子性(Atomicity):事务中的所有操作要么全部完成,要么全部不完成。
    • 一致性(Consistency):事务执行前后,数据库必须处于一致状态。
    • 隔离性(Isolation):事务之间相互隔离,一个事务的执行不会受到其他事务的干扰。
    • 持久性(Durability):事务一旦提交,其结果是永久的,即使系统发生故障也不会丢失。
  • 事务的用途

    • 数据一致性:确保多个操作作为一个整体执行,避免部分操作成功而部分操作失败的情况。
    • 并发控制:通过事务隔离级别,控制并发事务之间的相互影响,避免数据冲突。

    事务如何实现的?

    1. 日志文件

    • 预写日志(WAL):在事务开始时,数据库会先记录所有即将发生的变更到日志文件中,然后再真正地修改数据。
    • 日志文件的作用:用于恢复未完成的事务状态,确保事务的持久性。
  • 事务日志

    • Redo日志:记录事务对数据的修改操作,用于事务提交后的数据恢复。
    • Undo日志:记录事务前的数据状态,用于事务回滚和多版本并发控制(MVCC)。
  • 事务管理

    • 开始事务:标记事务的开始,开始记录日志。
    • 提交事务:将所有日志记录写入磁盘,确保事务的持久性。
    • 回滚事务:撤销事务中的所有操作,恢复到事务开始前的状态。

    Go的map是线程安全的吗?

    1. 标准map

    • Go语言中的标准map不是线程安全的。
    • 如果多个goroutine同时读写同一个map,可能会导致程序崩溃。
  • 线程安全的map

    • 使用互斥锁(Mutex):在并发访问map时,使用sync.Mutexsync.RWMutex来同步访问。
      var mu sync.Mutex
      var m = make(map[string]int)

      func add(key string, value int) {
          mu.Lock()
          defer mu.Unlock()
          m[key] = value
      }

      func get(key string) int {
          mu.Lock()
          defer mu.Unlock()
          return m[key]
      }
    • **使用sync.Map**:sync.Map是Go标准库提供的线程安全版本的map
      var m sync.Map

      func add(key string, value int) {
          m.Store(key, value)
      }

      func get(key string) int {
          val, _ := m.Load(key)
          return val.(int)
      }

    Go的slice扩容怎么做?

    1. 自动扩容

    • 当一个slice的空间不足时,Go运行时会自动为其分配更大的内存空间,并复制原有数据到新的位置。
    • 扩容时,通常会将容量扩大到原来的两倍左右。
  • 扩容机制

    • 初始容量slice的初始容量通常是固定的,如16或32。
    • 扩容公式:当slice的长度达到容量上限时,新的容量通常是当前容量的两倍。
      newCap := oldCap + oldCap/2
      if newCap > maxSliceCap(len) {
          newCap = maxSliceCap(len)
      }
  • 性能优化

    • 预分配容量:在创建slice时预分配足够的容量,减少扩容次数。
      s := make([]int01000// 初始长度为0,容量为1000
    • 手动扩容:在特定场景下,可以手动进行扩容操作,避免频繁的自动扩容。
      if cap(s) < neededCapacity {
          newS := make([]intlen(s), neededCapacity)
          copy(newS, s)
          s = newS
      }

    编程题:合并区间

    题目描述: 给定一个区间的集合,合并所有重叠的区间。

    解题思路

    1. 排序:首先按照区间的起始位置进行排序。
    2. 遍历:遍历排序后的区间,对于每个新区间,如果它的起始位置小于或等于当前合并区间的结束位置,则说明这两个区间是重叠的,应该合并;否则,开始一个新的合并区间。

    示例代码

    package main

    import (
        "fmt"
        "sort"
    )

    // Interval 表示一个区间
    type Interval struct {
        Start int
        End   int
    }

    // mergeIntervals 合并区间
    func mergeIntervals(intervals []Interval) []Interval {
        iflen(intervals) == 0 {
            return intervals
        }

        // 按照区间的起始位置排序
        sort.Slice(intervals, func(i, j int) bool {
            return intervals[i].Start < intervals[j].Start
        })

        var result []Interval
        current := intervals[0]

        for _, interval := range intervals[1:] {
            if interval.Start <= current.End {
                // 区间重叠,合并区间
                current.End = max(current.End, interval.End)
            } else {
                // 区间不重叠,将当前合并区间加入结果集
                result = append(result, current)
                current = interval
            }
        }

        // 添加最后一个合并区间
        result = append(result, current)
        return result
    }

    // max 返回两个整数中的较大值
    func max(a, b int) int {
        if a > b {
            return a
        }
        return b
    }

    func main() {
        intervals := []Interval{
            {13},
            {26},
            {810},
            {1518},
        }

        merged := mergeIntervals(intervals)
        fmt.Println("Merged Intervals:", merged)
    }

    输出

    Merged Intervals: [{1 6} {8 10} {15 18}]

    unsetunset早日上岸!unsetunset

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

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

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

    点击下方文章,看看他们是怎么找到好工作的!

    Go就业陪跑训练营,辅导到就业为止!

    Java就业陪跑训练营,辅导到就业为止!

    我们又出成绩啦!大厂Offer集锦!遥遥领先!

    王中阳
    公司技术总监,创办就业陪跑服务,辅导学员拿到600多个offer。专注程序员的就业辅导、简历优化、模拟面试等。
     最新文章