20240809面经背诵

  1. 什么是工厂模式?项目中怎么用到?

    • 工厂模式:不靠手动创建对象,而是在运行时借助工厂对象来创建对象,而我们一般在项目中运用的是抽象工厂模式,不同类型的对象可以由一个工厂来创建,只是创建的方式不同。
    • 像我们的项目中常常会用到Spring框架,而我们的项目中一个service接口可能对应着多个实现类,例如实习的项目中,定时任务同步助英台增量数据时,会用到zytService接口,除了一些通用的调用助英台的方法之外,还有着更多个性化的操作,比如助英台部门信息,助英台员工信息,助英台角色信息等等,这些都对应着不同的实现类,因此这些实现类的对象在被Spring容器创建时是通过zytService这个接口的抽象工厂来进行创建的,在依赖注入的时候使用Autowired配合Qualifier或者Resource注解来根据名称来注入
  2. redis如何防止超卖?加锁加的是什么锁?

    • redis通过分布式锁来防止超卖,在具体下单,菜品量-1的逻辑中通过使用redisson来上分布式锁来保证这个操作是原子操作,不会出现分布式环境下的并发安全问题
    • 加的分布式锁是可重入的悲观锁,redisson的分布式可重入锁使用的是redis的hash结构,key用的是锁的名称,field是对应的实例的唯一标识组合当前线程id,value则是重入次数。每次尝试获取锁时,会根据锁名和field来判断当前锁是否被当前实例的当前线程获取,如果是则代表是一次重入,将value+1即可,如果不是,则代表获取锁失败,在项目中会抛出异常,快速失败。redisson的上锁操作是通过lua脚本来实现原子性的
  3. 如果不使用redis锁,在并发环境下,单靠mysql来保证线程安全,防止超卖?

    • 在对应的表中,加一个字段版本号version,作为乐观锁来保证线程安全
    • 每次对当前行数据的修改操作都需要将版本号加一
    • 每次对当前行数据的修改操作前,需要先查询一次版本号,得到预期值
    • 修改的时候,在where子句中添加一条version=预期值的过滤条件,如果version与预期值一致则修改成功,如果不一致则修改失败
    • 并且要确保这两条语句在一个事务内,保证原子性
    • 这样就能防止A线程查出来还有存货,B也查出来还有1个存货,B去做修改了之后切回A线程还能修改
  4. CAS,版本号,怎么避免CAS重试?

    • CAS:Compare and swap,利用操作系统提供的原子操作,由C++实现,每次先去工作内存中读取共享变量,然后比较预期值与共享变量的值是否一样,如果一样,则修改,不一样则失败
    • 版本号:每次要修改数据时,比较对应的版本号是否与预期的版本号一致,一致则能修改,不一致则失败,可以解决CAS的ABA问题的限制
    • 避免CAS一直重试的话,可以通过限制循环次数或者限制循环时间来解决。
  5. kafka怎么保证数据不丢失?

    • 生产者到kafka:通过ack机制和一个回调方法来进行生产者到kafka消息发送失败的处理,可以在回调方法中进行重发,或者抛出异常来告警,或者采用重试机制,设置重试次数和重试间隔
    • kafka本身:kafka的消息不同于rabbitmq,其所有消息都是要持久化到磁盘的,这个机制确保了kafka本身消息不丢失,而同时,kafka还可以通过设置ack的值来确保集群ISR中一定数量的副本接收到同步的消息后才会向生产者确认消息发送成功来保证消息的不丢失。(kafka丢失的情况,集群中leader接收到了消息,但是还没来得及同步消息,leader挂了,并新选举一个leader,而新的leader并没有这个新的消息)
    • 到消费者,消费者消费消息失败:消费者可能会弄丢消息是这样的场景,消费者取到了消息,并提交,但是在消费的过程中挂了,那么消息就丢失了,因此可以选择手动提交来解决这个问题。
  6. Kafka中如果leader宕机了,而follower没有同步完成,造成的数据丢失怎么避免?

    • 可以通过配置ack属性,例如ack=3,即在集群中至少3个副本同步了这条消息才会向生产者端发送确认,如果出现上述情况,那么集群中肯定没有副本同步这条消息,不会向客户端发送确认,则生产者在超时后会重新发送这条消息,此时选举已经完成,由新的leader去同步消息。
  7. Kafka为什么一个分区只能由消费者组的一个消费者消费?这样设计的意义是什么?

    • 顺序消费:如果多个消费者消费同一个分区,那么会导致,例如现在有两个消息,一个是计算会员等级,一个是根据会员等级计算订单价格,那么如果多个消费者对应一个分区,那么可能一个消费者去计算了会员等级,另一个消费者立即去计算订单价格,无法保证顺序消费
    • 重复消费:为了保证kafka的消息到消费者端消息的不丢失,我们一般采用手动提交的方式,那么可能会导致这种情况,a消费者取走消息后消费,但还未提交的时候b消费者也取走消息进行消费
    • 负载均衡:通过消费者组来进行负载均衡,kafka是通过消费者组来负载均衡的,一个消费者对应一个分区组成一个消费者组,那么在添加消费者实例或者消费者实例挂掉时kafka能够通过实例来动态重新分区
    • 简化设计:如果多个消费者都能消费同一个分区,那么会带来并发问题,系统会变得更为复杂
    • 提高吞吐量:虽然一个分区只能被一个消费者消费,但是一个消费者可以消费多个分区
  8. redis有哪些数据结构?

    • string:sds
      • 存放序列化后的对象
    • list:zipList,LinkedList
      • 可作为消息队列的简单实现
    • hash:dict,zipList
      • 可用于分布式锁
    • set:IntSet,dict
      • 可用于查看共同关注,共同喜欢,通过交集实现
    • zset:skipList,zipList
      • 可用于作为排行榜
    • bitmap:
      • 位图,统计网站UV
    • hyperloglog
      • 大量数据下统计网站UV
    • geospatial
  9. 使用redis实现布隆过滤器?布隆过滤器的原理?

    • redis实现布隆过滤器通过bitmap来实现,在代码层面定义几个hash函数,计算对应的hash值,然后对bitmap容量取余得到在数组中的位置
    • 原理:对一个key利用几个哈希函数计算出几个哈希值,对容量取余得到位置,设置key时,则将这几个位置设置为1。查询时,必须得保证这几个位置都为1才是有效的,否则无效,直接拒绝,代表这个数据是肯定在数据库中不存在的数据
  10. Redis的高可用体现在哪里?

    • redis是支持集群的
      • 主从复制:通过主节点的RDB文件进行全量复制或者增量复制
      • 哨兵模式:通过哨兵监督主从节点的健康状况,如果有节点挂了则从集群中删除,如果主节点挂了则重新选举主节点
      • 分片集群:将大量数据分别存放在多个分片上,每个分片有一个主节点,多个从节点,提高了redis能存储的数据量
      • 读写分离,主节点负责写,从节点负责读
  11. Redis集群插槽为什么使用散列插槽而不是哈希?

    • 散列插槽和哈希插槽是同一个意思:将数据分布到16384个散列插槽来完成。
    • 在集群模式下,在写数据时,通常可以设置一个score来保证其对应的key的在插槽中的位置,而每个分片负责一部分插槽,数据将分发到对应的分片去。而取数据时则根据score确定其插槽位置,从指定的分片中取数据
    • 同时redis分片模式还提供增加分片和减少分片的情况下的自动数据迁移的功能。
  12. MySQL聚簇索引和非聚簇索引的区别?

    • 聚簇索引的索引存储顺序与整个表的存储顺序是一致的,并且聚簇索引的叶子结点存放的是整行数据,例如主键索引就是聚簇索引
    • 非聚簇索引的索引存储顺序与整个表的存储顺序无关,有索引本身的顺序决定,非聚簇索引叶子结点存储的是对应行数据的主键id
  13. 为什么MySQL使用B+树作为索引的数据结构?

    • 首先说下B树吧,B树是一种矮胖的树结构,每个节点存放多个索引,每个索引左右两边有两个指针,分别指向比它小和比它大的那些索引,其能在大量数据的情况下保证整棵树的高度有限,而树的高度决定了数据检索效率,因此B树能在大量数据下保证检索的效率
    • 与其他数据结构的比较:
      • 平衡二叉树:虽然其检索效率很高Ologn,但是在大量数据下,其树的高度很高,其也是有性能瓶颈的
      • 红黑树:同上
      • 跳表:同上
      • 以上数据结构除了大量数据下的性能瓶颈外,其在插入删除时保持数据结构的额外开销也比B树要大的多
      • 哈希表:查询效率O1的时间复杂度,但是大量数据下,会导致大量的哈希碰撞,检索效率也一般
    • B+树相对于B树做了改进,其非叶子节点不存放实际数据,确保了一个数据页能存放更多的索引,减少了IO次数,并且其叶子节点前后相连,组成一个双向链表,支持范围查询。
  14. Java中的线程状态?

    • new:线程对象创建了,但是没有调用start方法来启动线程
    • Runnable:调用run方法启动了线程,但不一定立即执行,等获取时间片才真正执行
    • Blocked:线程尝试获取某些资源时失败,被挂起
    • WAITING:线程调用了wait()方法
    • TIME_WAITING:线程调用了wait()方法,并且传入了超时时间,或者调用sleep()方法传入超时时间
    • TERMINATED:线程执行完毕
  15. Java线程池的创建方式?

    • 内置的线程池不推荐使用
    • 推荐直接使用ThreadPoolExecutor对象作为线程池
    • 或者使用CompletableFuture.thenCompose方法也可以作为线程池使用
  16. 线程池的参数?

    • 核心线程数
    • 最大线程数
    • 临时线程存活时间
    • 临时线程存活时间单位
    • 线程工厂
    • 阻塞队列
    • 拒绝策略
  17. 任务丢进线程池的流程?

    • 调用execute方法传入一个Runnable接口或者调用submit方法传入一个Callable接口,有核心线程空闲则调用核心线程执行,无核心线程空余则将任务存放到阻塞队列,如果队列已满,判断是否到达最大线程数,未到达则创建临时线程来执行,如果已到达则根据拒绝策略来拒绝任务
  18. Synchronized和ReentrantLock的区别?

    • 两者都是悲观锁,且都是可重入锁
    • 区别在于,synchronized是C++实现的,而ReentrantLock是java实现的
    • 相比于synchronized,ReentrantLock还提供了超时机制,中断机制,提供多条件变量实现有条件的wait和唤醒。
  19. ReentrantLock怎么实现可重入?

    • ReentrantLock底层基于AQS实现,AQS有一个变量state,在未上锁时为0,在上锁时为1,在尝试获取锁时,判断当前持有锁的线程id,如果不是,则获取锁失败,被挂起,并挂到CLH队列上,如果是,则代表是重入,则将state加一即可,释放锁时,每一次重入结束需要unlock,将state-1,只有state=0才是真正被释放
  20. AQS底层原理?

    • AQS的核心思想是,如果当前线程请求共享资源,共享资源空闲,则将当前线程设置为有效工作线程,并将共享资源设置为锁定状态
    • state字段用于控制共享资源的状态
    • 内部依赖一个CLH队列来记录尝试获取共享资源的线程,每个节点记录了线程id,前驱节点,后继节点,waitStatus,waitStatus来记录当前线程是否获取共享资源成功,注意CLH队列并没有队列实体,而是通过一个一个的node组成的虚拟队列
  21. MySQL中有哪些索引?

    • 主键索引
    • 唯一索引
    • 普通索引
    • 前缀索引
    • 联合索引
  22. 主键索引和唯一索引有什么区别?

    • 主键索引和唯一索引都是不可重复的,但是主键索引是聚簇索引,唯一索引是非聚簇索引
    • 一个表中可以有多个唯一索引,但是主键索引只能有一个
    • 主键索引不允许null值,唯一索引允许null值
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值