面试真题(真实面试题)

72 阅读8分钟

什么是Spring AOP?AOP的应用场景?

Spring AOP:面向切面编程,是一种编程范式

  • 允许将横切关注点(如事务管理、日志记录、安全性等)与业务逻辑分开
  • Spring AOP是Spring框架对AOP的实现,通过代理模式来实现AOP功能
    • JDK动态代理:JDK原生的实现方式,需要被代理的目标类必须实现接口
    • CGlib动态代理:通过继承被代理的目标类来实现代理
      • cglib通过动态生成一个需要被代理的子类(即被代理的类作为父类),该子类重写被代理类的所以不是 final 修饰的方法,并在子类中采用方法拦截的技术拦截父类所有方法的调用,进而织入横切逻辑
  • 当使用Spring AOP时,会在目标对象周围创建一个代理对象,这个代理对象可以拦截对目标方法的调用,并在方法调用前后插入额外的行为

AOP的应用场景

  • 日志记录:通过AOP,可以在不修改业务逻辑代码的情况下,在方法执行前后添加日志记录操作
  • 性能监控:可以使用 AOP 来监控方法的执行时间。在方法执行前后记录时间戳,计算方法的执行时长
  • 事务管理:AOP可以将事务管理的代码抽取出来,形成一个切面,然后在数据访问的方法调用前后进行事务的控制,使得事务管理代码和业务逻辑代码分离,提高代码的可维护性和可读性
  • 安全检查:通过AOP,可以在方法执行前,插入权限检查的逻辑。如果用户没有足够的权限,则阻止方法的执行,从而保护系统的安全
  • 缓存管理:在业务逻辑中,有些数据是经常被访问但变化不频繁的。可以利用 AOP 在方法执行后,将结果缓存起来,下次访问相同数据时,先检查缓存,如果有就直接从缓存中获取,减少了对数据库等后端存储的访问,提高了系统的性能,同时,在数据更新后,可以通过切面来更新缓存或者清除缓存。

在Redis中,Big Key 是什么?怎么解决?

在Redis中,Big Key是指那些占用大量内存或包含大量元素的键值对

判断标准

  • String类型:通常超过 1MB,极端情况下 10MB 以上即为大Key
  • 集合类型(List、Hash、Set、ZSet):元素超过5000个或上百万级,或者这些元素占用的内存较大,可视为大Key
  • 其它场景:存储长文本、大文件元数据或实时统计结果等,若占用内存过多,也属于大Key

解决方案

  • 拆分大Key:
    • 按业务逻辑拆分:比如将用户相关的大 Hash 按照用户 ID 拆分成多个小 Hash;对于按时间变化的数据,如订单信息,可按时间范围拆分
    • 使用分片键设计:通过引入分片键,将大 Key 的数据分散存储到多个 Redis 实例或数据库中。例如,对于一个存储用户帖子信息的大 Hash,可以按照用户 ID 进行分片,将不同用户的数据存储在不同的实例中,从而降低单个实例中 Key 的大小
  • 压缩数据:
    • 采用压缩算法:在存储数据时,可以先对数据进行压缩,减少数据的存储体积
    • 使用二进制序列化协议:采用Protocol Buffers 等二进制序列化协议,它比普通的文本序列化方式更加紧凑,能有效减少数据的大小,同时也有较高的解析效率
  • 清理过期数据:通过 hscan 命令配合 hdel 命令对失效数据进行清理,避免大量过期数据堆积导致 Key 过大
  • 转存大Key:对于一些不经常访问的大 Key,如 String 类型的大文件或 BLOB,可以将其转存至其他专门的存储系统,如对象存储 OSS 等,并在 Redis 实例中删除此类数据,以减轻 Redis 的存储压力
  • 限制数据量:在业务设计阶段,就对单个 Key 的数据量进行限制,如通过 Redis 的 maxmemory 参数来限制内存使用,或者在应用程序中设置合理的阈值,当数据量接近阈值时,触发警报或自动进行数据清理、转存等操作
  • 异步删除:可通过 UNLINK 命令安全地删除大 Key,该命令通过异步方式清理 Key,避免阻塞主线程;而对于 Redis 开源版 4.0 之前的版本,可先通过 SCAN 命令读取部分数据,然后进行删除,防止一次性删除大量 Key 导致主线程阻塞
  • 使用分页读取:对于 List、Hash、Set、ZSet 等数据结构的大 Key,可以使用分页读取的方式,避免一次性加载全部数据。例如,对于 List 可使用 LRANGE 命令的起始和结束参数来获取指定范围内的元素;对于 Hash 可使用hscan命令分页读取字段和值

请设计一个分布式自动生成唯一ID的方案?

雪花算法

  • 原理:
    • 使用 64 位的数字作为全局唯一 ID,其中 1 位是符号位,永远是 0
    • 41位是时间戳,记录当前时间戳减去一个固定的开始时间戳
    • 10 位是工作机器 ID,包括 5 位数据中心标识和 5 位机器标识
    • 12 位是序列号,每毫秒内可生成 4096 个不同的序列号
  • 优点:
    • 生成的 ID 整体上按时间趋势递增,便于后续插入索引树时性能较好
    • 整个分布式系统内不会产生 ID 碰撞;本地生成,不依赖数据库等第三方组件,效率高
  • 缺点:强依赖于时间,若发生时钟回拨,可能会引起 ID 重复、乱序等问题
  • 优化方案:
    • 将 ID 生成交给少量服务器,并关闭时钟同步,直接报错,交给上层业务处理
    • 若回拨时间较短,可在耗时要求内等待回拨时长后再生成
    • 若回拨时间长,可匀出少量位作为回拨位,一旦时钟回拨,将回拨位加 1 来得到不一样的 ID

Redis自增序列法

  • 原理:利用 Redis 的原子操作 INCR 和 INCRBY 来实现。
    • 可以每天在 Redis 中生成一个 Key,使用 INCR 进行累加,从而生成全局唯一的递增数值,常用于生成每天从 0 开始的流水号,如订单号可由日期加当日自增长号组成
  • 优点:
    • 不依赖于数据库,灵活方便,且性能优于数据库;
    • 数字 ID 天然排序,对分页或需要排序的结果很方便
  • 缺点:
    • 需要引入 Redis 组件,增加了系统复杂度
    • 需要编码和配置的工作量较大
    • 若 Redis 故障,ID 生成会受影响,需配置持久化机制以防数据丢失

数据库自增主键法

  • 原理:
    • 基于数据库的 auto_increment 自增 ID 功能,每次插入记录时自动生成递增的 ID
    • 对于分布式系统,可通过批量获取 ID 号段,减少对数据库的访问频率
  • 优点:实现简单,ID 单调自增,数值类型查询速度快
  • 缺点:
    • 在高并发场景下,单点数据库易成为性能瓶颈,且存在单点故障风险;
    • 多个数据库实例可能生成重复 ID
  • 改进方案:
    • 采用数据库集群模式,通过多个数据库实例设置不同的起始值和步长来生成全局唯一的 ID
    • 使用号段模式,在内存中缓存一定量的 ID 号段,使用完再取下一段,减少数据库访问

基于UUID的方案

  • 原理:
    • UUID 是一种软件构建的标准和规范,用于在分布式系统中生成全局唯一的标识符。
    • 常见的如 UUID 基于时间和空间的全局唯一,UUID4 基于随机数的全局唯一
  • 优点:使用方便,不需要中心化的协调,就能相对轻易地产生唯一标识;足够唯一,基本可以无视冲突的可能性
  • 缺点:
    • UUID 是 16 字节,存储空间比一般自增主键大很多
    • 由于其长度和版本信息的存在,索引空间消耗大,查询效率低
    • 其无序性可能导致磁盘的随机 IO,影响性能

号段模式

  • 原理:将一批连续的数字 ID 作为一个号段,例如 [1001, 2000],客户端从号段中依次分配 ID,直到用完,再申请新的号段
  • 优点:减少了对数据库的直接访问,提升了生成性能
  • 缺点:需要管理号段的分配和更新,以避免号段之间的冲突