逐字稿实例(22K*18

简介: 李二,3年Java开发经验,熟悉SpringBoot、SpringCloud、MySQL、Redis等技术栈。主导过“触见”买药送药项目,负责AI智能助手、电子处方、优惠券等核心模块设计与实现,具备高并发场景下的系统优化能力。

自我介绍

      面试官您好,我是李二,来自重庆,目前已有三年的java开发工作经验,上一家公司的话再北京那边工作(叫四川屹哲云易购科技有限公司),在公司平时会参与需求评审,技术方案的讨论与落地,后端代码的开发,然后进行单元接口测试,然后进行前后端联调,然后还会做一些压力测试,同时在项目后期也会参与一些运维,部署,最近的做的一个项目是触键,主要涉及到的技术栈有SpringAI,Springboot,springcloud,mysql,redis,MQ,ES,xxl-job这些。

     触见做的是买药送药业务,用户可以在小程序端进行下单买药,而商家这边就会根据订单进行安排,在用户买要的时候分为处方药和非处方药,用户可以进行搜索自己需要的药品,或者会根据不同的分类来查找药品,当用户购买处方药的时候呢,用户需要先填写处方信息,然后再进行线上问诊,线上问诊通过之后呢,会开一个电子处方,而这个处方的话有就可以用于购买对应的处方药品,而用户购买非处方药品的时候,用户直接下单无需电子处方。商家这边的话可以通过上传药品的基本信息,比如药品的资质文件等,经过后台审核通过之后就可以录入,然后商家设置价格以及药品分类等信息,经过管理人员审核之后就能够上架药品。用户在下单成功之后,商家备货,然后通知人员上店取药给用户送过去,大致的业务基本就是这样,而我在里面主要负责的模块就是,AI智能问答,商品管理,商品点赞,电子处方,优惠券模块,热销排行榜这几个模块。面试官您看您哪一方面比较感兴趣聊一聊。

QPS(每秒访问量  平时10,30   高峰100,)、TPS(每秒事务数   高峰10多,快20,平时就几个)、RT(响应时间0.4s)、下载量(10w)、日活(500-1000)、最大数据量(一般说订单表,200-300万)

AI智能助手

配置:4个16G的内存,8张4090显卡,

数据库表:

chat_session(对话会话表)

chat_message(对话消息表)

这个模块呢,是因为有大量客服工作人员反馈,用户大量询问重复类似的问题,为了顺应时代潮流,同时也减轻一线工作人员压力,因此决定开发这样一个智能助手,组长让我来做这个AI智能助手,我查看了市面上的开源大模型,发现他们比较全面,但是对某一个具体方面那么就不够深入准确,同时为了保护客户的隐私,防止客户信息泄露,因此我采用Ollam的方式本地部署大模型,然后通过外挂ES向量知识库,在知识库里面导入医疗药品,医疗健康等相关知识,实现小而准,为了对接AI大模型,我们也采用MCP来实现外部文本与AI大模型对接。

同时为了实现这个大模型能够更加精准地提供服务,我们采用路由工作流智能体,把原本的单一智能体改成了多个智能体一起协同工作,当用户提出问题的时候,首先会发送给意图分析智能体,他会判断用户是想要关于药品信息查询还是健康知识咨询,一旦明确了用户的意图,就会根据不同的需求调用想用的智能体来完成任务,比如推荐饮食或药品使用方式,这样的好处是每个智能体都有明确的任务分工,并且只有在需要时才会调用特定的工具,不需要全部的智能体都配置完全,让系统更灵活。

实现能够支持历史对话,我通重写chatMemory的add,get,clean方法,过将对话记录存储在Redis中,同时也会同步到数据库,其中,采用List数据类型存储,实现支持会话续聊,与多终端共享历史,

  • 对话历史具有 顺序性(消息按时间先后发送),List 是有序结构,天然适合存储时序消息。
  • List 支持 头部 / 尾部插入lpush/rpush)和 范围查询lrange),刚好匹配 “新增消息” 和 “获取历史” 的需求。
  • 每个会话(sessionId)对应一个独立的 List,键格式可设计为 chat:memory:{sessionId},隔离性好。


电子处方

数据库表设计:

electronic_prescription(电子处方表)存储处方基本信息,关联用户和医院,标记状态

prescription_medicine(处方药品关联表)记录处方中的药品及用药规则,关联处方和药品

prescription_review(处方审核记录表)记录药师审核结果,关联处方和药师

consultation_record(问诊记录表)记录线上问诊过程,关联处方来源,存储音视频凭证

用户在选购处方药品的时候,需要先选择药品提交需求,然后再填写处方信息,然后再是进行在线问诊,其次生成电子处方的基本信息存储到数据库,同时将相关数据(药品名称,数量-是否线下就医,是否第一次服用,过敏项等)使用MQ向第三方互联网医院发起请求,最后根据返回状态(成功,失败,异常)修改电子处方的状态(开方中,完成,异常,失败)和相关信息。电子处方的有效期是3小时,过期无法使用。

医生的线上问诊聊天室我们采用的是AnyChat 这个插件包来实现的,支持语音,视频,文字,图片沟通,同时将这些数据存储到数据库

// 后端:创建问诊聊天室(基于患者发起的问诊单)
@Service
public class ConsultationChatService {
    // 初始化AnyChat服务端连接
    private AnyChatServerSDK anyChat = new AnyChatServerSDK("127.0.0.1", 8900);
    /**
     * 创建专属问诊房间
     * @param consultationId 问诊单ID(关联患者和医生)
     * @param patientId 患者ID
     * @param doctorId 医生ID
     * @return 房间号
     */
    public int createChatRoom(Long consultationId, Long patientId, Long doctorId) {
        // 1. 验证患者和医生身份(对接项目的用户系统)
        if (!userService.verifyPatient(patientId) || !userService.verifyDoctor(doctorId)) {
            throw new RuntimeException("身份验证失败");
        }
        // 2. 创建一对一房间(问诊房间唯一对应一个问诊单)
        int roomId = anyChat.RoomCreate(1, 2); // 房间类型1,最大人数2
        anyChat.RoomSetInfo(roomId, "问诊房间-" + consultationId, "");
        // 3. 患者和医生加入房间(用户ID使用项目的真实ID,便于关联)
        anyChat.UserEnterRoom(patientId.intValue(), roomId, "patient_" + patientId);
        anyChat.UserEnterRoom(doctorId.intValue(), roomId, "doctor_" + doctorId);
        return roomId;
    }
}

处方药的购买流程 选购药品-填写信息-开具电子处方-提交订单-药师审核发货

优惠券模块


优惠券流程分为发放,领券,核销三个阶段

数据库设计

优惠券表

用户优惠券关联表

使用记录表

...

发放

我们的优惠券有多种类型,比如满减、每满减、无门槛、折扣等等,都是商家自己维护自己发布的,我们有一个审核机制,避免优惠券涉及敏感信息;当商家发布之后,会去调用阿里云的内容审核接口,如果出现审核不通过久不允许发放,通过之后商家就可以发放优惠券了。

领券

首先是用户查看活动界面

因为在活动期间的用户访问量会比较大嘛 然后活动界面就会被比较高频的访问到

为了提高这个界面的查询性能 我就引入了redis作为缓存 将优惠券的活动信息存入redis中,避免直接访问数据(Redis中存储数据形式:String 类型: Key :优惠券id    value:优惠券数量)

具体实现方案就是通过xxl-job执行定时任务将多种优惠券批量预热到缓存redis中 这种方式也能预防缓存击穿这种问题

不过xxl-job设定的执行间隔是一分钟一次嘛 然后活动界面的正在进行中的活动信息就无法达到一个实时性

然后针对这个情况,我是让前端去根据活动开始时间进行一个倒计时,达到开始时间就将活动移到进行中的界面,同时让前端向后端发送一次请求,后端根据活动时间和当前时间判断得出实时的活动状态

其次是用户进行抢券在一些优惠力度比较大的活动场景, 我们这个抢券的QPS可以达到一个300,200的样子

因为在高并发情况下抢券就是对优惠券库存这个共享资源进行操作嘛,然后就会出现线程不安全的问题 。

明明我优惠券库存只有100但是可能会发出去超过一百的数量,或者用户的同一个账号,在多个设备登陆,同时抢优惠劵,导致优惠劵超出领取数量。

这些问题也就是我们常说的超领和超卖。这两个问题的实际上是并发一致性的问题。

并发一致性解决方案

java代码层面

首先在java代码层面,用synchronized或者reentrantLock对代码进行加锁,但这种方案仅支持单体架构,不支持我们的微服务架构,所以排除

所以我们想到用分布式锁,例如Redission,Zookeeper,这样就只有一个线程拿到锁去操作库存 从而保证线程安全,能解决超卖的问题,但是会出现,还有库存但同时只有一个人能买的情况,所以不符合实际的业务场景

数据库层面

然后我就想到对数据库加锁。

锁的话又分为悲观锁和乐观锁。

首先是悲观锁,在查询语句后面加上 for update它可以通过行锁,锁定优惠劵库库存,确保事务执行期间其他事务无法修改同一数据,保证操作原子性。

但是悲观锁在这种高并发的场景下容易引发锁竞争,导致性能下降。如果事务中涉及多行数据的加锁而且顺序不一致,可能因循环等待导致死锁。所以我们排除了悲观锁

之后我想到使用乐观锁,传统的乐观锁是根据where verson= 来实现加锁,但这种方式100个用户来买只会有1个用户成功,所以我对他做了优化,用where stock(库存) > 0进行加锁,这样就能保证只要库存>0,用户领取优惠券都能够成功,这种方案虽然可以解决超卖的问题,但是仍然会有大量请求到数据库,

Redis层面

既然数据库上加锁并不能完全解决高并发与并发一致性这些问题,然后我就考虑使用常用的redis。因为redis是基于内存的嘛,性能会比较好。

所以我就想到了利用redis的decrement(DECR)这个命令去实现扣减库存

当然在此之前我们需要将优惠券的库存提前预热到redis hash:小key优惠券id,value库存量

因为redis本身是单线程的嘛 然后DECR命令也是具有原子性的 不过虽然扣减这个命令是原子性的,但扣减库存的逻辑是分几个步骤的,所以必须要用到redis的批处理指令,分别是multi,pipeline,和lua脚本

multi指令支持原子性,但是不支持复杂的业务逻辑判断,所以排除

pipeline不支持原子性,所以在不适合我们这个高并发的场景

因此我们最后选择了lua脚本

Lua脚本

于是我就考虑使用lua脚本,使用lua脚本只需要6个步骤

流程1:先判断库存(比如库存为stock判断stock是否大于0,不大于直接return)

流程2:再通过用户ID和优惠劵ID查找用户已领券数量num,

流程3:然后判断num是否为空。1.为空说明是第一次抢增加记录(将数量存储在reids中,结构为hash,大key为优惠券id,小key为用户id,值value为已领券数量num,设为1)。2.如果不为空说明已经抢过券,然后进行判断是否num大于预设值比如3大于就直接返回(超领问题)。如果没超过则重新给记录赋值,将num的值设为num+1。

流程4:接着对库存进行预扣减,库存=库存-1。

流程5:利用redis生成自增值version给后面同步到数据库做准备(乐观锁)

流程6:最后将用户id,优惠券id,版本号version存入reids中。结构为hash,大key为用户id,小key为优惠券id,值为version。(执行完lua脚本会从redis中拿取数据放入消息队列中)

所以lua脚本不仅能解决超卖的问题,也能解决超领的问题

消息队列

执行完lua脚本就可以将库存扣减的消息发到消息队列中,然后直接给用户返回结果。从请求到后端就已经整个流程就已经结束。但是后面还需要异步将消息队列中的扣减库存同步到数据库中。下面是消息队列的选型:

所以为了保证消息的可靠性,首先排除了kafka,然后在RabbitMq和RocketMq中,RabbitMq的吞吐已经足够我们项目使用,且为了更快的响应我们最终选择了RabbitMq。利用它来削峰填谷保证高并发下对消息的异步缓冲。然后监听消息队列将消息同步到数据库中。

为了保证数据的幂等性,利用mysql乐观锁机制,加一个version版本号,保证同步时并发一致性问题。version由redis生成,为什么不从数据库拿:1.对数据库有压力。2.大量消息版本号相同,每次只有一个消息成功,大量失败由redis生成自增版本号,避免这个问题。

核销

最后是核销阶段 就是订单服务远程调用优惠券服务进行核销 如果下单核销成功会给优惠券核销表添加记录如果取消订单会退回优惠券 然后下单和核销优惠券,退单和退回优惠券都涉及到两个服务 因此这里有一个分布式事务问题 我们项目是通过seata的at模式解决的

at的核心是通过一个undo_log文件,在tc的管理下 一阶段提交事务 二阶段根据事务的情况决定是否回滚

分布式锁的选型

Setnx

可以在高并发场景下可以提供一定的并发控制和一致性保障,但是发生死锁,锁失效,锁超时,锁误删等问题,哪怕能够通过设置(ex)过期时间解决死锁问题。其潜在问题仍然过大。

Redlock

它是一个由多个主节点构成,重量级锁,而且Redlock的可用性很高并且具有多节点结构容错性很高即使部分节点故障,剩余节点仍可保证锁的安全性。

潜在问题

  1. 节点一致性要求: 无法保证同一个锁加在同一个节点上,而不是其他节点
  2. 时钟漂移影响: 客户端与节点间时间不同步可能导致锁提前释放(如客户端A误判锁已过期,而节点仍持有锁)。
  3. 网络分区风险: 断网时可能出现“脑裂”,导致多个客户端同时操作共享资源(如客户端A与多数节点失联,客户端B误判锁已失效)

Redisson

支持公平锁,重铸锁,并且有看门狗机制,用于解决锁自动续期的问题,可以动态续期锁的过期时间,但是我发现redisson框架锁了之后变成串行,哪怕库存有一百个也只能卖一个。

三种消息中间件的选型

kafka

主要用于大数据的处理,百万级别的吞吐,但是消息可靠性不高,容易消息重复消费,或丢失。

RabbitMq

万级到十万级吞吐,低延迟处理消息相对较快,实时同步扫盘同步,消息可靠性高,并发处理性高。

RocketMq

十万级以上吞吐,消息可靠性较高,但效率相对RabbitMq毫秒级处理速度相对较低。

在 Redis 集群环境中使用 Lua 脚本时的问题

执行一次Lua脚本会涉及到多个key,在redis集群下执行lua脚本要求多个key必须最终落到同一个节点,否则调用Lua脚本会报错。

所以我们可以使用大括号‘{}’然后通过保证大括号中的内容相同为优惠券id,再根据大括号中的内容求哈希,因为内容相同所以求得的哈希数据相同所以就落在了同一个Redis节点

互动中心

数据库表设计:

点赞总数表

用户点击商品详情页“点赞”按钮 → 系统校验用户是否已点赞(是→取消点赞;否→新增点赞) → 更新商品点赞数(+1或-1) → 记录用户点赞行为 → 实时展示更新后的点赞数

用set来存储用户的点击行为,key和value是商品id和用户id,而同步到数据库用Zset来存储,其中KEY是商品类型,member是商品id,而score是总的点赞总数,通过xxl-job定时更新到数据库,这样实现多次写操作合并。其中set我们不更新到数据库,在内存中存3个月,然后删除。

         因为用户点赞看到是返回的点赞总数.所以会有以下业务流程 用户在进行点赞(有可能是对视频点赞,有可能是对评论点赞,类型bizType不一样)的时候,可能会出现频 繁点赞取消的情况,系统就会频繁查询数据库判断该用户有没有对该业务点过赞,如果没有就会将其保存 在点赞记录表,并统计用户对点赞业务的总数量写入点赞数量表,然后查询点赞数量表该点赞业务的数量 的返回给前端,也就是说用户在一秒钟点赞一次会对数据库进行两次读两次写这种频繁的操作,在高并发 的情况下,对数据库的负载太大了性能会有问题. 所以有以下优化  

        针对该问题我想到了使用redis缓存来解决,最开始把点赞记录跟点赞总数缓存到Redis中,在点赞记录 加一个点赞记录缓存, 点赞数量加一个点赞数量缓存,用户进行点赞跟统计点赞数量先不保存到数据库,而是先保存到redis缓存 中,用户频繁点赞前端会进行一个防抖处理,而后端的话我选择使用redis的set数据结构,也可以使用hash 结构来实现(可以自己思考一下怎么用hash).使用set流程如下,判断key是bizid,value是userid,因为set本 身可以去重,所以不会造成一个userid对同一个bizid进行多次点赞,前端每次进行点赞都需要对点赞记录 缓存中的业务id进行统计并返回, set中有一个指令SISMEMBER可以对userid进行统计,不过该命令每次 只能处理一个业务,前端请求是有大量的业务id,所以引入了pipeline管道方式,他可以一次性进行多个业 务id的统计,并返回. 然后把业务id数量统计写入点赞数量缓存,这里选择使用zset结构来实现,大key是bizType,member是 bizId,value是likedTimes(也就是对set集合中的userid进行统计).最后通过xxljob定时任务写入数据表  

数据库设计





常见问题  

看你项目中介绍,你负责点赞功能的设计和开发,那你能 不能讲讲你们的点赞系统是如何设计的?  

答:首先在设计之初我们分析了一下点赞业务可能需要的一些要求。 例如,在我们项目中需要用到点赞的业务不止一个,因此点赞系统必须具备通用性,独立性,不能跟具 体业务耦合。 再比如,点赞业务可能会有较高的并发,我们要考虑到高并发写库的压力问题。 所以呢,我们在设计的时候,就将点赞功能抽离出来作为独立服务。当然这个服务中除了点赞功能以 外,还有与之关联的评价功能,不过这部分我就没有参与了。在数据层面也会用业务类型对不同点赞数 据做隔离。 从具体实现上来说,为了减少数据库压力,我们会利用Redis来保存点赞记录、点赞数量信息。然后利 用定时任务定期的将点赞数量同步给业务方,持久化到数据库中。 注意事项:回答时要先说自己的思考过程,再说具体设计,彰显你的逻辑清晰。设计的时候先不说细 节,只说大概,停顿一下,吸引面试官去追问细节。如果面试官不追问,停顿一下后,自己接着说下面 的  

那你们Redis中具体使用了哪种数据结构呢?  

:我们使用了两种数据结构,set和zset 首先保存点赞记录,使用了set结构,key是业务类型+业务id,值是点赞过的用户id。当用户点赞时就 SADD用户id进去,当用户取消点赞时就SREM删除用户id。当判断是否点赞时使用SISMEMBER即可。 当要统计点赞数量时,只需要SCARD就行,而Redis的SET结构会在头信息中保存元素数量,因此 SCARD直接读取该值,时间复杂度为O(1),性能非常好。 不过这里存在一个问题,就是页面需要判断当前用户有没有对某些业务点赞。这个时候会传来多个业务 id的集合,而SISMEMBER只能一次判断一个业务的点赞状态,要判断多个业务的点赞状态,就必须多 次调用SISMEMBER命令,与Redis多次交互,这显然是不合适的。(此处略停顿,等待面试官追问,面 试官可能会问“那你们怎么解决的”。如果没追问,自己接着说),所以呢我们就采用了Pipeline管道方 式,这样就可以一次请求实现多个业务点赞状态的判断了  

那你ZSET干什么用的?  

严格来说ZSET并不是用来实现点赞业务的,因为点赞只靠SET就能实现了。但是这里有一个问题, 我们要定期将业务方的点赞总数通过MQ同步给业务方,并持久化到数据库。但是如果只有SET,我没 办法知道哪些业务的点赞数发生了变化,需要同步到业务方。 因此,我们又添加了一个ZSET结构,用来记录点赞数变化的业务及对应的点赞总数。可以理解为一个待 持久化的点赞任务队列。 每当业务被点赞,除了要缓存点赞记录,还要把业务id及点赞总数写入ZSET。这样定时任务开启时,只 需要从ZSET中获取并移除数据,然后发送MQ给业务方,并持久化到数据库即可。  

那为什么一定 要用ZSET结构,把更新过的业务扔到一个List中不行吗?  

:扔到List结构中虽然也能实现,但是存在一些问题: 首先,假设定时任务每隔2分钟执行一次,一个业务如果在2分钟内多次被点赞,那就会多次向List中添 加同一个业务及对应的点赞总数,数据库也要持久化多次。这显然是多余的,因为只有最后一次才是有 效的。而使用ZSET则因为member的唯一性,多次添加会覆盖旧的点赞数量,最终也只会持久化一次。 (面试官可能说:“那就改为SET结构,SET中只放业务id,业务方收到MQ通知后再次查询不就行了。” 如果没问就自己往下说) 当然要解决这个问题,也可以用SET结构代替List,然后当业务被点赞时,只存业务id到SET并通知业务 方。业务方接收到MQ通知后,根据id再次查询点赞总数从而避免多次更新的问题。但是这种做法会导 致多次网络通信,增加系统网络负担。而ZSET则可以同时保存业务id及最新点赞数量,避免多次网络查 询。 不过,并不是说ZSET方案就是完全没问题的,毕竟ZSET底层是哈希结构+跳表,对内存会有额外的占 用。但是考虑到我们的定时任务每次会查询并删除ZSET数据,ZSET中的数据量始终会维持在一个较低 级别,内存占用也是可以接受的  


商品录入流程

  • 药店提供药品资质文件(药品经营许可证、批准文号等)
  • 管理员审核资质文件
  • 录入商品基本信息
  • 系统自动生成商品编码
  • 设定库存阈值和预警机制
  • 商品状态设置为 "待上架"

商品上架流程

  • 质量管理员审核商品信息
  • 确认药品分类和适应症标签
  • 设置销售价格和会员价格
  • 上传商品图片和说明书
  • 商品状态变更为 "在售"
  • 同步至前端展示

库存管理流程

  • 门店实时更新库存(支持扫码入库)
  • 线上订单生成后自动锁定对应门店库存
  • 订单取消时释放库存并通知周边门店
  • 库存低于预警值时自动推送补货提醒
  • 每日闭店前系统自动盘点并生成差异报表

商品调度流程

  • 用户下单时,系统根据 LBS 定位匹配 3 公里内门店
  • 若无货自动扩大搜索范围至 5 公里
  • 支持跨店调拨(紧急情况下)
  • 热销商品自动推荐门店备货量
  • 临期商品优先在就近门店促销

效期管理流程

  • 药品入库时强制记录效期信息
  • 距效期 6 个月自动标记为临期商品
  • 距效期 3 个月自动下架并启动退货流程
  • 系统定期生成近效期商品报表
  • 支持按效期先进先出拣货提示

热销排行榜模块

统计上一周的药品销售数量排行,使用Redis中Zset数据类型来进行存储,

k = 分类ID,v= 药品信息,sorted = 销量

若两个商品销量相同,那么如何处理?

在销量上面加上时间戳,这样可以解决,比如,sorted=销量+(1-时间戳/10的13次方),这样就在销量的后面加上了了时间戳,同时排行的时候按照销量先达到这个数量的商品排行在前面

人事题

你开发的时候,需求一直变更,你怎么搞

(1)需求管理不到位:不要口头说,一定要留痕:企业可能会有需求管理平台,比如禅道

需求没办法实现,你怎么跟【产品经理】沟通

你这边有什么想问的

(1)面试有几轮

(2)什么时候出结果

(3)我的技术,跟贵司的匹配吗

(4)我进去了,做什么业务

(5)我进去了,可能面临的挑战

你最近学了什么新技术

(1)微服务相关的任意一个

(2)SpringAI那一套:MCP、Tool Calling

你们公司的开发流程是什么样的?

(1)需求评审:产品经理

(2)技术方案评审:你写方案,组长、架构师一起参与评审,入职要打的第一场硬仗

(3)前后端代码开发【都在自己本地:一个需求一个feature】:后端写三层架构,单元测试(Junit)

(4)前后端联调【开发环境:dev】:有bug就改bug,这时候只涉及前后端开发人员

(5)系统测试【测试环境:test】:测试人员用Jmeter做压力测试、性能测试,涉及人员:测试、开发(改bug、优化)

(6)发布上线【正式/生产环境:master】:运维人员发布,涉及人员:运维、测试、开发

你们团队成员组成?你们团队有几个人?你们项目组有几个人呢

面试官,我们开发小组是4个人,后端3个前端1个,还有一个公共的比如测试、运维、产品经理、项目经理

热点八股文

Java基础

说一下你对红黑树的理解?HashMap为什么不用二叉平衡树

红黑树(Red-Black Tree)是一种自平衡二叉搜索树,它通过一套严格的规则维持树的 “近似平衡”,从而保证查找、插入、删除操作的时间复杂度稳定在 O(log n)(n 为节点数)。其核心设计是通过 “颜色标记” 和 “旋转操作” 避免树结构退化为链表,同时平衡维护成本(相比其他平衡树更轻量)。

每个节点要么是红色,要么是黑色。

根节点必须是黑色。

所有叶子节点(NIL 节点,即空节点)都是黑色。

如果一个节点是红色,它的两个子节点必须是黑色(即不允许两个红节点相邻)。

从任意节点到其所有叶子节点的路径中,包含的黑色节点数量相同(称为 “黑高” 相等)。

维护成本:红黑树更轻量

AVL 树为了维持 “左右子树高度差≤1” 的严格平衡,插入 / 删除时可能需要多次旋转(例如连续插入有序数据时,旋转次数远多于红黑树);而红黑树通过 “颜色 + 宽松平衡”,旋转次数更少(平均插入旋转 1 次,删除 2 次)。
HashMap 的场景中,哈希冲突导致的树化节点频繁插入、删除(尤其是扩容时的 rehash),红黑树的低维护成本更适合。

性能需求:近似平衡足够用

HashMap 的核心是 “哈希查找”,树化只是为了优化哈希冲突后的链表性能。红黑树的 “黑高相等” 特性已能保证树高在 O (log n) 范围内,足以将查询时间从链表的 O (n) 优化到 O (log n);而 AVL 树的 “严格平衡” 虽然能让树高更矮,但带来的性能提升(微乎其微)远不及维护成本的增加。

空间开销:红黑树更简洁

AVL 树需要存储 “节点高度” 信息(用于判断平衡),而红黑树只需存储一个 “颜色标记”(1bit 即可),空间开销更小。对于 HashMap 这种可能存储大量节点的结构,更节省内存。


ArrayList和LinkedList

HashMap

数据结构

   - 1.7:数组+链表

   - 1.8:数组+链表/红黑树

     - 数组长度大于64,链表长度大于8

扩容机制:2N,0.75(泊松分布)

   - 负载因子:0.75

     - 泊松分布

   - 扩容:2N

源码

  - put

   - get

     - key-->hashcode-->int-->key得到value

线程安全

   - 不安全

   - 要安全

     - ConcurrentHashMap

       - 1.7:分段锁:16个Segement

         - 可以保证同时有16个并发,并且安全

       - 1.8

         - CAS+Syciz思想

     - SynUtils.HashMap

     - HashTable

JUC多线程

创建线程有几种方式

这个的话呢,根据我了解一共有4种方式,1、继承Thread类 重写Run方法  2、实现Runnable接口 3、 实现Callable并使用 Future 4、通过线程池创建

  • 继承 Thread:简单直观,但受限于单继承
  • 实现 Runnable:更灵活,可多实现,适合多个线程共享资源
  • 实现 Callable:可返回结果,可处理异常,适合有返回值的任务
  • 线程池:适合大量线程的场景,提高性能和资源管理效率

线程池的七个参数

这七个参数分别是

  核心线程数

  最大线程数

  临时线程存活时间

  临时线程存活时间单位

  任务队列

  线程工厂

  拒绝策略

  • JDK 默认提供 4 种策略:
  • AbortPolicy:直接抛出RejectedExecutionException(默认)
  • CallerRunsPolicy:由提交任务的线程自己执行
  • DiscardPolicy:直接丢弃新任务
  • DiscardOldestPolicy:丢弃队列中最旧的任务,然后尝试提交新任务

JUC并发包用过哪些

用到的有concurentHashMap,Future,CompletableFuture

CountDownLatch

ConpyOnWriteArrayList

轻量级:CAS(compare and swap)

重量级锁:synchronzed、分布式锁

synchronzed和ReetrantLock都是可重入锁,synchronzed不总是重量级锁,不绝对是,ReetrantLock通常不是重量级锁,例如synchronzed只有一个线程加锁,没有其他的线程竞争就不是,而分布式锁就绝对是重量级锁。

synchronzed只能保证单台服务器加锁有效,多台无效:

Threadlocal

一般用在记录用户登录的信息

volatile

JMM内存模型(工作内存,主内存)volatile修饰的变量在工作内存中修改的时候,会第一时间刷新到主内存当中,让其他所有线程可见

SSM框架

SpringBoot自动装配的原理

自动装配原理呢是在Soring Boot项目中的启动类上有一个注解@SpringBootApplication,这里面封装了三个注解@SpringBootConfiguration 是表明当前是一个配置类@ComponentScan会扫描当前包路径下的所有包的Bean然后将其加入到IOC容器中,@EnableAutoConfiguration是实现自动化配置的核心注解,该注解通过@Import注解导入对应的配置选择器,内部就是读取这个项目以及这个项目引用的jar包下单classpath路径下的META/INFO下面的pring.factories文件中的配置类的全类名,在通过指定条件来决定要不要将其导入spring容器中,但在2.7之后就使用imports文件。

Spring IOC和DI

IOC是控制反转,是一种思想,就是将类的创建对象权和控制权从程序本身交给外部容器,例如在Spring框架中就是用IOC容器来创建存储对象,而DI就是依赖注入,为类动态注入属性依赖。DI的实现方式有三种,一是构造器注入,setter注入,还有字段注入。

AOP

AOP是spring的核心思想之一,将不是大量重复但并非核心业务的行为和逻辑抽取出来成为一个公共可重用的模块,这个模块就叫切面,AOP在项目中使用不是很多,通常使用在权限认证、性能监控、自定义注解、日志等。AOP的底层是使用动态代理技术来实现,而动态代理包括cglib和jdk提供的动态代理两种方式。

Spring三级缓存

spring中三级缓存时是为了解决Spring中循环依赖的问题,比如下面这块代码示例A和B相互依赖

class A{
    B b;
}
class B{
    A a;
}

那么这样在创建A的时候需要b的对象,而创建B的时候又需要a,因此就无法完成创建对象,

三级缓存就是

一级缓存:singletonObjects 单例池,存储已经初始化完全的bean

二级缓存:earlySingletonObjects 缓存早期的Bean(实例了但未初始化的Bean)或者ObjectFactory对象创建的代理对象

三级缓存:singletonFactories缓存的是ObjectFactory对象,用于创建某个对象(比如代理对象)

而三级缓存通过earlySingletonObjects  来解决创建A,A和B相互依赖的问题(A,B都没有在一级缓存中),当实例化A后需要B对象来初始化A完成创建对象,而在实例化A的时候会将半成品A放在二级缓存中,在找B进行创建时,也会将实例后的半成品B放在二级缓存中,但此时B会从二级缓存获得A来进行创建,而B创建成功后会放在一级缓存中,再将B注入给A,A也存储到一级缓存中,此时完成创建。

但这样解决不了当A被AOP增强的情况,这个时候就要用到三级缓存,当实例化A后,原始对象A会生成一个ObjectFactory对象放在三级缓存中,此时需要注入B,然后创建B,当实例化B之后也会创建ObjectFactory对象放在三级缓存中然后需要注入A,但这个时候B会从二级缓存中获取ObjectFactory创建的对象,也就是将A的代理对象注入给B,此时B创建成功,然后再将B注入给A放在一级缓存中,一级缓存中存放的就是A代理对象和B。

Spring 框架中bean的作用域(Scope)?

主要支持五种,singleton ,prototype ,request ,session ,global-session这五种,

1、singleton是单例模式,只能在容器中创建一个对象,它的生命周期是随着容器的创建和销毁而进行创建销毁,是spring中默认的Bean的作用域。

2、prototype ,它能够在容器中创建多个对象,但每个对象的生命周期是当需要从容器中去一个对象时才会创建,而销毁则是由程序员销毁或gc销毁。

3、request 作用域在web环境中,他是每前端有一个请求就会创建一个对象,这个对象生命周期是随着前端请求开始创建,响应结束销毁。

4、session 作用域在web环境中,每有一个会话就会创建一个对象,生命周期是随着会话的开始和结束而创建和销毁。

5、global-session 作用域在web环境中,每有一个全局的会话会创建一个对象

事务传播行为

Spring中事务传播行为的话共有七种

①required:当前如果已经有了事务那么九加入,没有事务则创建新事务

②supports: 当前如果有事务则加入,没有事务就以非事务的方式执行

③mandatory:当前有事务就加入,没有事务就抛出异常

④requires_new:当前有事务会将当前事务挂起创建新事务,没有就直接创建新事务

⑤not_supported:当前有事务就将当前事务挂起,以非事务状态执行,没有事务直接执行

⑥never:以非事务方式执行,如果当前存在事务,则抛出异常。

⑦nested:如果当前存在事务就嵌套事务,如果没有事务就创建新事务

Spring MVC执行流程

spring mvc 执行流程目前的话是分两种执行流程,一种是以前的老的执行流程方式,第二种是现在流行的前后端分离开发的这种执行流程,接下里我分别讲述一下这两种执行流程,第一种老方式的执行流程是

①首先用户从浏览器发起请求到前端控制器DispatcherServlet ,

②然后DispatcherServlet会调用处理器映射器HandlerMapping查找handler(处理器)

③HandlerMapping找到具体的处理器,生成处理器对象以及处理器拦截器(有的话)将其封装后传给DispatcherServlet

④DispatcherServlet会调用处理器适配器HandlerAdaptor

⑤HandlerAdaptor会找到对应的处理器(Handler/Controller)

⑥Controller执行完会将对应的结果ModelViewAnd返回给HandlerAdaptor

⑦HandlerAdaptor会将ModelAndView返回给DispatcherServlet

⑧DispatcherServlet会将ModelAndView传给视图解析器ViewResolver

⑨ViewResolver将ModelAndView进行解析成View(视图)返回给DispatcherServlet

⑩DispatcherServlet会根据得到的View进行视图渲染然后返回给前端

而前后端分离开发的执行流程则是这样

①用户同样从浏览器发送请求到DispatcherServlet

②DispatcherServlet会调用HandlerMapping

③HandlerMapping找到具体的处理器,生成处理器对象以及处理器拦截器(有的话)将其封装后传给DispatcherServlet

④DispatcherServlet会调用HandlerAdaptor

⑤HandlerAdaptor经过适配找到对应的处理器(Handler/Controller)

⑥方法上添加了@ResponseBody

⑦通过HttpMessageConverter将返回结果转化为JSON格式并响应

Spring事务失效的常见场景

Spring事务失效有很多情况,其中最常见的有三种,一是自调用,一个加了@Transactional的的方法只有被代理对象调用时才会生效,被被代理对象调用就不会生效,比如如下代码:

public class A{
    void a(){
        this.b();
    }
   @Transcationl
    viod b(){
        
    }
}

二是当@Transactional加在private方法上面不会生效,因为spring事务底层用的是cglib来实现的,而cglib是靠子类继承父类才实现,因此当加在父类的private方法上面,子类不会继承就无法生效,而cglib和 jdk 动态代理不同,jdk的动态代理是通过实同一个接口来实现的,

三是当方法中捕获类异常而并未抛出去,导致spring框架不知道有异常,此时也会造成事务失效。

Spring Security如何做鉴权,授权

Spring Security 的认证流程核心是通过 过滤器链(Filter Chain) 拦截请求,结合多个核心组件完成身份验证

授权是在认证成功后,判断用户是否有权限访问某个资源的过程。Spring Security 支持多种授权方式,核心是 “基于规则判断用户权限是否匹配资源要求”。

  • 他就是在用户请求之后,经过SpringMVC的DispatcherServerlet,找到对应的HandlerMapping之后的Handler(controller)之后,我们会在方法上追加一个注解@PreAuthorize("@ss.hasPermi('elder:bed:list')" ,此时Security会自动进入自己的过滤器
  • 首先判断访问的路径是不是在白名单印象,在里面就直接方向
  • 不在里面就根据用户的id,去底层RBAC模型查找这个用户对应的角色、菜单,返回一个菜单列表,有则放行并生成JWT信息存储在session里,没有则返回HTTP 401权限不足

Redis

Redis 穿透、击穿、雪崩

缓存穿透是指请求一个不存在的数据,缓存层和数据库层都没有这个数据,这种请求会穿透缓存直接到数据库进行查询。它通常发生在一些恶意用户可能故意发起不存在的请求,试图让系统陷入这种情况,以耗尽数据库连接资源或者造成性能问题。比如:在快速入门程序中,查询一个缓存中不存在的数据将会执行方法查询数据库,数据库也不存在此数据,查询完数据库也没有缓存数据,缓存没有起到作用。

解决方案:1、对请求增加校验机制 2、缓存空值或特殊值 3、使用布隆过滤器

缓存击穿发生在访问热点数据,大量请求访问同一个热点数据,当热点数据失效后同时去请求数据库,瞬间耗尽数据库资源,导致数据库无法使用。

比如某手机新品发布,当缓存失效时有大量并发到来导致同时去访问数据库。

解决方案:1单体架构且单实例部署下(单进程内)可以使用同步锁控制查询数据库的代码,只允许有一个线程去查询数据库,查询得到数据库存入缓存。分布式架构下(多个进程之间)可以使用分布式锁进行控制。

2、热点数据不过期

3、缓存预热 分为提前预热、定时预热。提前预热就是提前写入缓存。定时预热是使用定时程序去更新缓存。

4、热点数据查询降级处理 对热点数据查询定义单独的接口,当缓存中不存在时走降级方法避免查询数据库。

缓存雪崩是缓存中大量key失效后当高并发到来时导致大量请求到数据库,瞬间耗尽数据库资源,导致数据库无法使用。比如对某信息设置缓存过期时间为30分钟,在大量请求同时查询该类信息时,此时就会有大量的同类信息存在相同的过期时间,一旦失效将同时失效,造成雪崩问题。

解决方案1、使用锁进行控制

思路同缓存击穿。

2、对同一类型信息的key设置不同的过期时间

通常对一类信息的key设置的过期时间是相同的,这里可以在原有固定时间的基础上加上一个随机时间使它们的过期时间都不相同。

具体实现:在framework工程中定义缓存管理器指定过期时间加上随机数。

3、缓存定时预热

不用等到请求到来再去查询数据库存入缓存,可以提前将数据存入缓存。使用缓存预热机制通常有专门的后台程序去将数据库的数据同步到缓存。

Redis常见数据类型、高级数据类型

Redis持久化策略

我的理解的话Redis的持久化有两种方式,一是RDB,二是AOF。RDB的话呢就是将Redis存储的文件存到磁盘当中,

       RDB的话是Redis运行save命令嘛,在这个过程当中,Redis这个主进程会使用虚拟地址通过页表映射到实际物理地址然后对数据进行操作,然而运行Redis运行save命令会阻塞其他的命令,因此就有那个bgsave命令,而这个命令就是通过复制一个主进程信息形成一个子进程嘛,这样主进程就不必阻塞可以干其他的,但此时如果主进程要对数据进行写操作,就会再复制一份数据然后主进程就会写在这个复制的数据中,当然后面主进程读也会读取这个复制的数据,

       AOF的话简单来讲就是将redis运行的命令都写再AOF这个文件当中嘛,他底层就是有三种写命令的方式嘛,一是一直写,只要redis运行了就会写进aof中,二是每隔一秒就写到aof中,三是由系统来决定而redis默认是每隔一秒写进aof中,也就是统称刷盘策略,

    这两种的话各有千秋,aof的话当redis宕机的时候丢失的数据较少,比较安全,但是数据恢复的时候比较慢,而rdb的话数据恢复比较快,因为他底层是二进制文件,而当redis在rdb模式下若两次快照中宕机了,那么这样就会丢失这两次时间内的数据,可能丢失的数据比较多。

Redis数据过期策略

在Redis中呢提供了两种数据过期删除策略,一种是惰性删除,在设置该key的过期时间后,我们不必管他,当需要这个key的时候,我们会检查是否过期,如果过期那么就删除它,反之就返回这个key,而第二种是定期删除,就是说每隔一段时间,我们就对一些key进行检查,删除其中过期的key,而在定期清理中,还有两种模式,一是slow模式,它的执行频率默认是10hz,每次不超过25ms,可以通过修改配置文件redis.conf的hz选项来调整这个次数,二是FAST模式,它的执行频率不固定,每次时间循环都会尝试执行,但两次间隔不低于2ms,每次耗时不超过1ms.

我们现如今在项目中的话基本都是惰性删除和定期删除两种策略都一起进行配合使用。

Redis数据淘汰策略

数据淘汰策略总共有八种其中包含默认的noevction不淘汰,但是当内存满了就会直接报错  ,LRU适用于有热点数据的业务,也是使用最常用的淘汰策略,LFU最不常用的淘汰策略,适用于就固定时间突然有大量访问比如月初查话费,random随机淘汰,而LRU和LFU还有进阶的就是加上TTL的算法。

Redis分布式锁如何实现

分布式锁有 redisson  zoomkeepr(借助于文件夹的唯一性)

嗯,Redis分布式锁是依靠redis中提供的一个命令setnx,因为Redis是单线程,因此在使用这个命令之后,那么就只能有一个线程进行设置Key值,在没有完成这个操作时,其他的线程无法进行操作,但这样会有这个加锁时间不好控制的问题,因此就可以用redisson,redission是在setnx的基础上引入了看门狗机制来实时监听这个加锁的线程,每隔一段时间就会检查这个线程是否还持有,锁有的话,这个看门狗就会续期,增加加锁的持有时间默认是每10s(加锁时间/3)加30s,同时这个锁还是可重入锁,底层用Hash实现。

Redis和Mysql如何保证数据⼀致

强一致性:比如我们在做优惠券这个业务的时候,因为时效性要求比较高,因此采用读写锁来保证强一致性。那您看要不要给您讲一讲读写锁(我们采用的是redisson实现的读写锁,在读的时候添加共享锁,可以保证读读不互斥,读写互斥。当我们更新数据的时候,添加排他锁,它是读写,读读都互斥,这样就能保证在写数据的同时是不会让其他线程读数据的,避免了脏数据。这里面需要注意的是读方法和写方法上需要使用同一把锁才行。其实排他锁底层使用也是setnx,保证了同时只能有一个线程操作锁住的方法)

最终一致性:当我们在做哪些允许有一定延时的业务时,我们采用异步的方案同步数据,主要有两种方式嘛,

①使用MQ中间中间件,更新数据之后,通知缓存删除

②利用canal中间件,不需要修改业务代码,伪装为mysql的一个从节点,canal通过读取binlog数据更新缓存

我们当时就采用的第二种,因为第二种对业务没有影响。

Redis主从集群,哨兵集群和分片集群的区别

  1. 主从集群(Master-Slave)
    节点组成:1 个主节点(Master) + N 个从节点(Slave,N≥1)。
    角色分工:
    主节点:负责写操作(增删改)和数据同步源;
    从节点:通过复制机制同步主节点数据,负责读操作(查询),作为主节点的备份。
    特点:无额外管理节点,结构简单。
  2. 哨兵集群(Sentinel)
    节点组成:主从集群(1 主 N 从) + M 个哨兵节点(Sentinel,M≥3 且为奇数)。
    角色分工:
    主从节点:功能同主从集群(主写从读);
    哨兵节点:不存储数据,仅负责监控主从节点健康状态、判断主节点故障、自动触发故障转移(从从节点中选举新主节点)。
    特点:通过哨兵节点实现自动化管理,解决主从集群的手动切换问题。
  3. 分片集群(Redis Cluster)
    节点组成:N 个主节点(每个主节点可带 1 个以上从节点) + 哈希槽(共 16384 个,分配给主节点)。
    角色分工:
    主节点:每个主节点负责一部分哈希槽(如 3 个主节点分别负责 0-5460、5461-10922、10923-16383),存储对应槽位的数据,处理写操作;
    从节点:同步主节点数据,作为备份,主节点故障时可晋升为主节点;
    哈希槽:用于数据分片的 “索引”,键通过哈希计算映射到某个槽位,进而确定存储在哪个主节点。
    特点:数据分布式存储,支持水平扩展(新增主节点即可分配更多槽位)。

XXl-job怎么避免任务重复执行

分片广播

Docker

Docker命令

docker打包镜像: docker build -t dockerdemo:1.0 .(最后面有空格加点)

排查docker日志:docker logs -f 容器名称 -f表示一直打印

排查linux日志:cat info.log | grep '报错信息关键字'

排查日志万能工具:Arthas、Skywalking

查看镜像:docker images

拉取/推送镜像:docker pull/push

删除镜像:docker rmi -f

容器启停:docker run/start/stop/restart

查看所有运行中的容器:docker ps

查看所有容器:docker ps -a(包括没有运行的)

删除容器:docker rm -f '容器名称'

数据卷:docker volume inspect/create

linux、docker部署常见命令:

Docker 知识点:

dockerfile组成: from 、env、copy、run 、expose、entrypoint【java-jar ,/app/app.jar】{

from :指定基础镜像

env:设置环境变量

copy:拷贝本地文件到镜像指定目录

run 执行linux的shell命令,一般是安装过程的命令

expose:指定容器运行时的端口

entrypoint:启动命令,运行例如java-jar的jar包

}

docker compose:docker部署简单的java项目一般需要三个容器才能部署如:前端 nginx、后端 java项目、数据库MySql,而compose则是可以通过允许用户使用一个docker-compose.yml定义一组容器,例如下面

version: "3.8"
services:
  mysql:
    image: mysql:8
    container_name: mysql
    ports:
      - "3307:3306"
    environment:
      TZ: Asia/Shanghai
      MYSQL_ROOT_PASSWORD: 123
    volumes:
      - "/usr/local/app/mysql/conf:/etc/mysql/conf.d"
      - "/usr/local/app/mysql/data:/var/lib/mysql"
      - "/usr/local/app/mysql/init:/docker-entrypoint-initdb.d"
    networks:
      - itheima
  tlias:
    build: 
      context: .
      dockerfile: Dockerfile
    container_name: tlias-server
    ports:
      - "8080:8080"
    networks:
      - itheima
    depends_on:
      - mysql
  nginx:
    image: nginx:1.20.2
    container_name: nginx
    ports:
      - "80:80"
    volumes:
      - "/usr/local/app/nginx/conf/nginx.conf:/etc/nginx/nginx.conf"
      - "/usr/local/app/nginx/html:/usr/share/nginx/html"
    depends_on:
      - tlias
    networks:
      - itheima
networks:
  itheima:
    name: itheima

通过一个yml文件就实现定义三个容器

MySQL

MYSQL支持的存储引擎有哪些, 有什么区别

MYSQL存储引擎有很多如:InnerDB,MyISAM,MEMORY,Archive, 常用的就二种 : MyISAMInnoDB(在版本5.5后支持) , 这两种存储引擎的区别 ;

  • MyISAM支持256TB的数据存储 , InnerDB只支持64TB的数据存储
  • MyISAM 不支持事务 , InnerDB支持事务
  • MyISAM 不支持外键 , InnerDB支持外键
  • MyISAM崩溃后需要手动恢复,InnerDB会自动恢复

锁机制 (Locking):

  • InnoDB: 默认采用行级锁。当修改某一行时,只锁定那一行,提高了高并发写入和更新场景下的性能
  • MyISAM: 采用表级锁。当对表进行任何写操作(INSERT, UPDATE, DELETE)时,会锁定整个表。。
  • 优先选择 InnoDB (绝大多数场景)
  • 考虑 MyISAM 的场景 (非常有限):
  • 只读或读密集型应用: 例如数据仓库、报表数据库,其中几乎没有写操作,并且需要非常快的 COUNT(*) 或简单 SELECT

了解过Mysql的索引嘛

索引是帮助MySQL高效获取数据的一种数据结构,其索引主要是分为单列索引,组合索引,空间索引,

其中,单列索引分为 普通索引(索引加在表的任意字段上面)、唯一索引(需要表的字段其值保持唯一,适用于手机号或邮箱唯一校验),主键索引(索引加在主键上面,没有主键的表会加在row_id上面,这是MYSQL自动生成的主键),全文索引(字段的类型需要为text/varchar/char,使用于文章内容搜索,商品描述),组合索引是在表的多个字段上面加索引,需要遵循最左前缀原则,空间索引用的比较少主要用在地理数据上面

Mybatis动态SQL的执行原理是什么?有哪些常用标签?

MyBatis 中有9种标签 if choose foreach set where when bind trim otherwise ,执行原理首先是Mybatis会先加载xml文件,然后生成一棵sqlNode树,第二步是传入参数,比如pojo或map等传入,第三步是采用深度优先算法的方式遍历这棵sqlNode树,第四步是通过遍历结果拼接SQL语句,第五步是将占位符属性替换。

mybatisplus和mybatis有什么区别

mybatis和mp都是属于持久层,但mybatis是框架,而mp则是在mybatis的基础上增强,增加了许多的模板,在mybatis的基础上简化单表的增删改查,但mp不会改变mybatis的代码,总的来说,MyBatis 给了你最大的灵活性但需要写更多基础代码;MyBatis-Plus 在保留 MyBatis 灵活性的前提下,通过丰富的内置功能,让你写更少的代码、更快地完成开发,尤其擅长简化单表操作。

MyBatis

Mybatis中一级缓存,二级缓存

一级缓存:基于PerpetualCache的HashMap本地缓存,其作用域位Session,当Session进行flush或close,该Session中的所有Cache就将清空,默认打开一级缓存

二级缓存:基于namespace和mapper的作用域起作用的,不是依赖于SQL session ,默认也是采用PerpetualCache,HashMap存储,需要单独开启,一个是核心配置,一个是mapper映射文件

当某一个作用域(一级缓存session/二级缓存namespaces)的进行了新增,修改,删除操作后,默认该作用域下的所有select中的缓存将被clear


聚簇索引 、非聚簇索引

聚簇索引是数据和索引放在一块,B+树的叶子节点保存了这一行的所有数据,有且只有一个。(一般是主键,如果没有主键,那么就会选取第一个唯一索引,如果主键和唯一索引都没有,InnoDB会自动生成一个row_id来作为隐藏的聚簇索引)

非聚簇索引是数据和索引分开存储,其中B+树的叶子节点中存储的是对应的主键,可以有多个。

回表查询

通过非聚簇索引找到对应的主键值,再到聚簇索引中查找整行数据,这个过程就是回表。

覆盖索引

覆盖索引是指select查询语句使用了索引,在返回的列,必须在索引中全部能够找到,如果我们使用id查询,它会直接走聚集索引查询,一次索引扫描,直接返回数据,性能高。如果按照二级索引查询数据的时候,返回的列中没有创建索引,有可能会触发回表查询,尽量避免使用select*,尽量在返回的列中都包含添加索引的字段

可以用于解决超大分页的问题,超大分页一般都是在数据量比较大时,我们使用了limit分页查询,并且需要对数据进行排序,这个时候效率就很低,我们可以采用覆盖索引和子查询来解决,先分页查询数据的id字段,确定了id之后,再用子查询来过滤,只查询这个id列表中的数据就可以了因为查询id的时候,走的覆盖索引,所以效率可以提升很多

mysql的性能优化

数据库的性能优化的话有很多个方面吧,比如从建表的时候,使用的索引呀,SQL语句的编写,还有主从复制,读写分离,分库分表,您看需要给您进一步讲一讲吗,在表层面,建表的时候参考阿里巴巴嵩山版开发手册,定义字段的时候需要结合字段的内容来选择合适的类型,如果是数值的话,像tinyint、int、bigint这些类型,要根据实际情况选择。如果是字符串类型,也是结合存储的内容来选择char和varchar或者text类型,在SQL语句方面就不要直接使用select*,还有就是要注意SQL语句避免造成索引失效的写法;如果是聚合查询,尽量用union all代替union,union会多一次过滤,效率比较低;如果是表关联的话,尽量使用inner join,不要使用left join right join,如果必须使用一定要小表驱动大表(表层面,SQL层面,索引层面)

Mvcc

中mvcc的意思是多版本并发控制。指维护一个数据的多个版本,使得读写操作没有冲突,它的底层实现主要是分为了三个部分,第一个是隐藏字段,第二个是undolog日志,第三个是readView读视图隐藏字段是指:在mysql中给每个表都设置了隐藏字段,有一个是trx_id(事务id),记录每一次操作的事务id,是自增的;另一个字段是roll_pointer(回滚指针),指向上一个版本的事务版本记录地址undolog主要的作用是记录回滚日志,存储老版本数据,在内部会形成一个版本链,在多个事务并行操作某一行记录,记录不同事务修改数据的版本,通过roll_pointer指针形成一个链表readView解决的是一个事务查询选择版本的问题,在内部定义了一些匹配规则和当前的一些事务id判断该访问那个版本的数据,不同的隔离级别快照读是不一样的,最终的访问的结果不一样。如果是rc隔离级别,每一次执行快照读时生成ReadView,如果是r隔离级别仅在事务中第一次执行快照读时生成ReadView,后续使用

如何定位慢查询

定位慢查询的话我了解的主要有两种嘛,一是可以借助这种开源工具来进行定位,比如有Arthas(阿尔萨斯)、SkyWalking之类的,如果没有这种开源工具的情况下也可以借助MySql自带的慢日志来进行定位,在系统配置文件中开启慢日志功能,并设置SQL执行超过多长时间就记录到日志当中,比如设置将超过2秒的都记录在日志当中。

一个SQL语句执行很慢, 如何分析

如果一条SQL语句执行很慢的话,那我们通常会使用mysql的那个explain关键字来进行分析,比如可以通过查看key和key_len检查索引的命中率,也可以通过查看type字段查看是否存在全索引扫描或全盘扫描,,最后呢可以通过extra建议来判断是否出现了回表的情况从而进行修复。

微服务

微服务用到哪些组件

我们呢微服务常用组件有  nacos用于注册中心和配置中心   网关这方面我们采用的是gateway  关于远程调用呢是openFeign 在链路追踪方面采用第三方的skywaking 服务熔断 Hystrix  负载均衡Ribbon

微服务的好处?一定比单体好吗?

面试官这个不一定,技术还是为业务服务的,简单的业务单体肯定更好,他没有分布式事务、服务雪崩等一系列服务治理的问题,并且部署、维护成本低。微服务更适合一些表模型复杂、业务链路长的场景,这样虽然会带来一些服务治理问题,但是也降低了服务之间的耦合,有利于后续的服务拓展

微服务你用到了哪些中间件

我的项目用到了Nacos,当做注册和配置中心;远程服务调用OpenFeign;网关GateWay

Nacos注册中心的心跳机制

面试官,简单来说呢Nacos的心跳机制是服务的提供者每隔一段时间就会向nacos注册中心进行注册服务信息嘛,然后这个服务消费着也会定时拉取服务,服务消费者就可以知道目前有哪些服务,而当服务提供者有一个突然宕机挂了,那么他不会周期性地向注册中心注册服务信息,然后服务中心就能够发现这个服务已经宕机了,就会立即通知给服务消费者嘛,这样服务消费者就能及时知道哪个服务挂了嘛。

Nacos支持服务端主动检测提供者状态:临时实例采用心跳模式,非临时实例采用主动检测模式临时实例心跳不正常会被剔除,非临时实例则不会被剔除

Nacos与eureka的共同点

  1. 都支持服务注册和服务拉取
  2. 都支持服务提供者心跳方式做健康检测

Nacos与Eureka的区别

  1. Nacos支持服务端主动检测提供者状态:临时实例采用心跳模式,非临时实例采用主动检测模式
  2. 临时实例心跳不正常会被剔除,非临时实例则不会被剔除
  3. Nacos支持服务列表变更的消息推送模式,服务列表更新更及时
  4. Nacos集群默认采用AP方式,当集群中存在非临时实例时,采用CP模式;Eureka采用AP方式


Nacos和Eureka的区别

  • Eureka
    专注于服务注册与发现,仅提供基础的服务注册、心跳检测、实例健康状态管理等核心功能,是 Spring Cloud Netflix 生态的一部分,设计上遵循 "AP 原则"(可用性优先,牺牲部分一致性)。
  • Nacos
    定位为动态服务发现、配置管理和服务管理平台,除了服务注册发现外,还内置了配置中心功能(无需额外集成 Config 组件),支持动态配置更新、多环境配置管理等,功能更全面,是 Spring Cloud Alibaba 生态的核心组件。

讲一下OpenFeign的原理和优点

常见的负载均衡算法

有轮询、加权轮询、权重、响应时间、随机、区域可用等吧,我们项目中一般都是轮询,它的意思就是当有多台机器的时候,就一个一个轮着来。

微服务保护

我们主要通过三层机制实现微服务保护:

  1. 熔断:用 Sentinel,当服务调用失败率超阈值(如 50%),自动熔断避免依赖故障扩散,熔断后走降级逻辑,恢复期内半开状态试探恢复。(状态分为闭合、打开、半开)
  2. 降级:预设降级策略,如资源不足或压力过大时返回缓存数据或默认值,优先保障核心接口(如支付),非核心接口(如推荐)降级。
  3. 限流:基于 Sentinel 的 QPS / 线程数限流,针对接口设置阈值(如 1000QPS),确保请求数不会超过系统的处理能力,超出部分排队或返回限流提示,结合 Nginx 做入口限流。

整体通过配置中心动态调整规则,监控面板实时观测指标。

AT模式的原理

AT 模式是 Seata 的分布式事务模式,基于两阶段提交:

      两阶段原理:

  • 第一阶段:
  1. 本地事务执行:每个分支事务(如订单服务、库存服务的本地 SQL 操作)在 RM 的控制下执行本地事务,修改业务数据,并生成两种日志:
  • undo log:记录数据修改前的镜像(before image)和修改后的镜像(after image),用于后续回滚时恢复数据。
  • redo log:记录数据修改的具体内容,用于提交时确认修改。
  1. 资源锁定:RM 对当前修改的行数据加本地行锁,防止其他事务在分布式事务未完成前修改该数据(避免脏写)。
  2. 分支事务注册:RM 将本地事务执行结果(成功 / 失败)上报给 TC,同时注册分支事务到全局事务中。
  3. 暂停本地事务:本地事务执行完成后不立即提交,处于 “待提交” 状态,等待 TC 的全局指令。
  • 第二阶段:协调者根据各分支事务状态,决定全局提交(删除日志)或回滚(执行 undo 日志)。
  • 解决脏读方案:
    通过全局锁实现。本地事务提交前需获取全局锁,未获取则放弃提交并回滚;持有全局锁期间,其他事务需等待,避免并发修改导致的脏读。

如果解决MQ消息堆积问题?

消息堆积就是生产者的生产速度大于消费者的消费速度,我们常用的办法的话有增加消费者,这种的话适用于问题比较紧急,还有比如优化消费者的效率,让消费者的消费速度加快,也可以让这个消息队列存储更多的消息,比如使用惰性队列存下更多的消息。

如何保证MQ幂等性?或 如何防止消息重复消费

MQ的幂等性问题的话,我们在项目中采用的是生成唯一的标识ID,再加上业务去重。

MQ消息确认机制

MQ的消息确认机制的话主要有三个部分,分别是生产者确认机制,MQ持久化,消费者确认机制,生产者确认机制的话就是靠publish-confirm,publish-return这两个函数来确保消息一定到了MQ,MQ持久化的话就是借助于springAMQP声明的交换机、队列、消息默认都是持久化,或者在浏览器界面化创建的时候,勾选Durable参数,也可以默认持久化,消费者确认机制的话就是当指定队列失败进行重试,重试次数达到上限之后,可以将消息投递给死信队列,然后人工处理。您看要不要进一步讲一讲。

confirm有两个返回结果

  nack:找不到交换机

  ack:找到交换机,不管是否匹配到队列都能接收到消息

return只有一个返回结果

  ack:找到了交换机,但是找不到队列

当publish-confirm返回nack,那么代表出现找不到交换机异常,然后publish-return返回ack,那么就代表找到交换机,却匹配不到消息队列,如果publish-confirm返回ack,然后publish-return不返回,那么就代表正常。

MQTT怎么避免消息丢失

MQTT通过QoS来避免消息丢失,根据业务可靠性需求选择合适等级:

  • QoS 0(最多一次):消息只发送一次,不确认,可能丢失(适合非关键数据,如日志)。
  • QoS 1(至少一次):消息会被确认,确保到达,但可能重复(适合重要数据,如指令)。
  • QoS 2(刚好一次):通过四次握手确保消息唯一且不丢失(适合金融交易等严格场景)。

倒排索引为什么快?

  倒排索引是相对于mysql而言的,传统的关系型数据库mysql是先得到数据再内存判断是否满足条件,当模糊匹配的时候,没有索引的前提下需要去【叶子节点】遍历,时间复杂度是o(N),相当于扫全表;而ES是根据用户输入的关键词,借助于IK、PY分词器分好之后,匹配词条对应的文档ID,然后根据ID去找到具体的文档数据,所以是先过滤再查询具体数据,时间复杂度比较低,当数据量很大的时候,ES的优势就更明显了

网关有什么作用

服务分发,鉴权,限流

ES向量知识库与数据库之间怎么保证一致性

采用cannal,其中,过去的数据我们直接同步到向量知识库,而后面再新增的数据通过cannal来解决,因为Cannal能够监控数据库的新增,修改,删除然后通过RabbitMQ这种消息队列同步到向量知识库.

AI篇

Tool Calling的执行流程

首先用户发送请求到后端,然后大模型判断这次请求是否需要使用某个toolcalling,如果使用某个tool,那么就会调用,然后将结果返回给大模型,大模型拿着tool的调用结果组织返回数据给前端。

SSE

SSE 是一种轻量级、低成本的实时单向通信方案,其设计核心是 “复用 HTTP 协议、简化实时推送逻辑”—— 无需复杂的双向连接,通过原生 API 和自动重连降低开发成本,适合仅需服务器向客户端主动推送数据的场景。关闭 SSE 连接的核心是:客户端通过 EventSource.close() 主动终止,或服务器返回 204 状态码触发客户端自动关闭。

Stable Diffusion  


设计模式

工厂模式(Factory)

核心思想:用工厂类代替new关键字创建对象,将对象创建逻辑与使用逻辑分离。
分类与实现


  • 简单工厂:一个工厂类处理所有对象创建(适合类型较少的场景);
  • 工厂方法:每个产品对应一个工厂子类(符合开闭原则,扩展方便);
  • 抽象工厂:创建一系列相关联的产品(如 “操作系统 + 浏览器” 的组合)。
// 产品接口
public interface Button {
    void render();
}
// 具体产品(Windows按钮)
public class WindowsButton implements Button {
    @Override
    public void render() {
        System.out.println("渲染Windows风格按钮");
    }
}
// 具体产品(Mac按钮)
public class MacButton implements Button {
    @Override
    public void render() {
        System.out.println("渲染Mac风格按钮");
    }
}
// 工厂接口
public interface ButtonFactory {
    Button createButton();
}
// 具体工厂(Windows按钮工厂)
public class WindowsButtonFactory implements ButtonFactory {
    @Override
    public Button createButton() {
        return new WindowsButton();
    }
}
// 使用方(无需关心具体实现)
public class Application {
    private Button button;
    // 传入工厂,灵活切换产品类型
    public Application(ButtonFactory factory) {
        this.button = factory.createButton();
    }
    public void renderUI() {
        button.render();
    }
}

优点:降低耦合(使用方无需知道产品细节),便于扩展(新增产品只需加工厂);
缺点:类数量增多(每类产品对应一个工厂);
典型应用

  • Java 的Calendar.getInstance()(根据时区创建不同日历实例);
  • 日志框架(LoggerFactory创建不同类型的日志器);
  • 数据库驱动(DriverManager根据 URL 创建 MySQL/Oracle 连接)。

策略模式(Strategy)

核心思想:定义一族算法,封装每个算法并使它们可互换,让算法独立于使用它的客户端。
实现关键

  • 策略接口:统一算法方法;
  • 具体策略:实现接口的不同算法;
  • 上下文:持有策略引用,调用算法(不关心具体实现)。

优点:算法可动态切换,便于扩展(新增算法只需加策略类);
缺点:客户端需知道所有策略(才能选择合适的);
典型应用

  • Java 的Comparator接口(不同排序策略);
  • 电商折扣系统(会员折扣、节日折扣等策略);
  • 路由算法(最短路径、最快路径等策略)。

观察者模式(Observer)

核心思想:定义 “主题 - 观察者” 一对多关系,当主题状态变化时,自动通知所有观察者更新。
实现关键


  • 主题(Subject)维护观察者列表,提供注册、删除、通知方法;
  • 观察者(Observer)实现更新接口,接收主题通知。
// 观察者接口
public interface Observer {
    void update(String message); // 接收主题通知
}
// 具体观察者(用户)
public class User implements Observer {
    private String name;
    public User(String name) { this.name = name; }
    
    @Override
    public void update(String message) {
        System.out.println(name + "收到消息:" + message);
    }
}
// 主题接口
public interface Subject {
    void registerObserver(Observer observer); // 注册观察者
    void removeObserver(Observer observer);  // 移除观察者
    void notifyObservers();                  // 通知所有观察者
}
// 具体主题(公众号)
public class WechatAccount implements Subject {
    private List<Observer> observers = new ArrayList<>();
    private String message;
    
    public void setMessage(String message) {
        this.message = message;
        notifyObservers(); // 消息更新后自动通知
    }
    
    @Override
    public void registerObserver(Observer observer) {
        observers.add(observer);
    }
    
    @Override
    public void removeObserver(Observer observer) {
        observers.remove(observer);
    }
    
    @Override
    public void notifyObservers() {
        for (Observer observer : observers) {
            observer.update(message);
        }
    }
}
// 使用示例
public class Demo {
    public static void main(String[] args) {
        WechatAccount account = new WechatAccount();
        Observer user1 = new User("张三");
        Observer user2 = new User("李四");
        
        account.registerObserver(user1);
        account.registerObserver(user2);
        account.setMessage("新文章发布啦!"); // 所有用户收到通知
    }
}

优点:解耦主题与观察者(双方无需知道对方细节),支持动态增减观察者;
缺点:通知顺序不确定,可能导致性能问题(大量观察者时);
典型应用


  • Java 的java.util.ObserverObservable(经典实现);
  • 事件监听机制(如 Swing 的ActionListener);
  • 消息队列(生产者 - 消费者模型,主题 = 队列,观察者 = 消费者)。

业务

AI智能评估模块

面试官,最近项目也做过一个AI智能评估的功能,还是比较有意思的。因为我们做的是宠物医疗,为了节省人力成本,我们也顺应当下AI的潮流,所以开发了这样一个功能。

我们会将宠物的体检报告,里面有宠物的尿检、生化、血检这些信息作为一个PDF文档上传给到AI大模型平台,完成风险的评估,得到他的风险信息、健康得分、是否适合做绝育、手术这样的风险建议,从而帮助医生更好的决策,在这里面我也做了几个小的优化点

第一个就是:对于PDF这种非结构化数据,我们是统一采用的OSS存储,OSS本身的高可用可以避免我们文件的丢失

第二个就是:传统开源AI大模型,没办法在某个专业领域做到小而美,我们小公司也不想去招聘一个大模型训练师,因此我这边研究了一下目前开源的架构,最后采用了RAG的模式,自建了一个本地知识库,里面存储了宠物医疗的专业信息,从而提高检索、判断的正确率,使用的是ES充当向量知识库来解决的

第三个就是:检验报告出来的当天,我们发现用户会多次频繁查询,而这个报告是需要多张表信息汇总的,为了提高查询效率,我们这里使用了Redis缓存一下,使用的是String结构key就是宠物的唯一信息,value就是整个体检报告数据,过期时间我们设置的一天。为了解决缓存雪崩,我们也在过期时间上加了随机值

这个就是我觉得还比较不错的一个点,面试官你看你对哪块感兴趣我们都可以继续聊聊

你们的大模型API,是调用开源的还是私有化部署?

  • 回答:面试官,我们这个项目使用的是私有化部署,私有化部署可以保证信息的安全,同时我们借助RAG外挂知识库的方式,可以保证自己业务的准确性,知识库我们使用的是ES向量知识库,你看关于私有化部署的方式、外挂知识库的细节要不要给你讲一讲

过去做过第三方接口API的对接吗?做过SDK对接吗对接的时候关注什么?

Stable Diffusion

Stable Diffusion(稳定扩散)模型实现文生图主要有5步

文本输入与编码(Input Encoder)

1 用户输入描述文本(比如 “一个沙滩,一艘船靠在沙滩边” ),模型先提取关键词(sandy beach、ship、mountain 等),转化为统一的文本标记 。

2 借助 CLIP Text 模型,把文本标记编码成 “文本特征向量”(77×768 维度),让计算机能理解文本语义 。

图像生成(Image Generator):在 “潜在空间” 反向扩散

3 先初始化一个 “随机噪声张量”(4×64×64 维度,可理解为模糊的基础素材 )

4 通过 Unet 网络 + 调度器(Scheduler),结合文本特征向量,逐步 “去噪”(Unet Step 1 到 Step N )。每一步根据文本语义,优化噪声张量,让它越来越接近 “符合文本描述的潜在特征” 。

图像解码(Image Decoder):转回像素空间

5  经过反向扩散得到的 “潜在特征”(4×64×64 维度),会被 VAE Decoder(变分自动编码器解码器) 处理,把潜在空间的特征映射回 “像素空间”,生成最终的可视化图像(比如 512×512 分辨率的沙滩、船场景 )。

场景题

你们团队怎么做代码质量管控的

  • 我们自己会做代码质量检测,比如Idea里的阿里巴巴规范插件、SonarLint、PMD
  • 也会有内部的CodeReview(代码评审会议),大家交叉验证,保障代码的健壮性和拓展性
  • 同时我们也会借助于Junit和Mockito做单元测试,保障测试覆盖率在90%以上

你们公司怎么部署代码的?

  • 不要直接说:java -jar order.jar,用shell脚本

你们怎么排查错误

用过鉴权框架吗?实现的原理大概说一下?

  • 面试官,常见的一些鉴权框架比如Shiro、Security、还有自定义RBAC模型都用过一些,相对来说前两者还是比较复杂的,我给您说一下Security的原理吧/说一下RBAC底层/领域模型(就是数据库表)吧
  • Security:他就是在用户请求之后,经过SpringMVC的DispatcherServerlet,找到对应的HandlerMapping之后的Handler(controller)之后,我们会在方法上追加一个注解@PreAuthorize("@ss.hasPermi('elder:bed:list')" ,此时Security会自动进入自己的过滤器
  • 首先判断访问的路径是不是在白名单印象,在里面就直接方向
  • 不在里面就根据用户的id,去底层RBAC模型查找这个用户对应的角色、菜单,返回一个菜单列表,有则放行并生成JWT信息存储在session里,没有则返回HTTP 401权限不足
  • RBAC:参照下图说出自己的理解即可
  • 面试官,RBAC模型就是经典的用户、角色、资源三张主表,同时增加用户-角色、角色-资源两张中间表,用来记录哪个用户有哪些角色,每个角色有哪些资源;当然在此基础之上,也可以增加部门的,也都是一样的原理,我就不重复赘述

你们怎么发布项目的?

我们的话通过jenkens进行发布,实现CI/CD,但是之前的话哦我们也是有用过linux和docker进行发布过,像linux的话我们是先将jar包上传到服务器指定目录,然后停止旧服务,然后替换旧文件,修改配置,然后启动jar包,docker的话就是首先通过编写Dockerfile构建镜像,然后进行docker push推送仓库,再停止旧容器,启动新容器

发布失败你们遇到了吗?怎么处理的?


关联的下游服务版本回退,怎么处理的?

你们遇到CPU飙高怎么解决的

问题主要有缓存失效 / 击穿,慢SQL,MQ的消息积压

线上故障怎么排查

线上故障遇到过哪些

空指针、sql语句、OOM

线上故障怎么处理

Skywaking如何进行链路追踪

SkyWalking 遵循分布式追踪的通用模型,通过以下核心概念标识和串联调用链路:

分布式追踪核心:Trace Span TraceID SpanID

  • Trace:表示一个完整的分布式事务链路,由多个相关联的 Span 组成。例如,用户发起一个下单请求,从前端到后端服务、数据库、缓存等所有参与的调用过程,共同构成一个 Trace
  • Span:表示链路中的一个具体操作(如一次服务调用、一次数据库查询等),是追踪的基本单位。每个 Span 包含以下关键信息:
  • TraceID:全局唯一标识,用于关联同一个 Trace 中的所有 Span(即整个链路)。
  • SpanID:当前 Span 的唯一标识,用于区分链路中的不同操作。
  • ParentSpanID:父 Span 的 ID,用于建立 Span 之间的层级关系(如调用方与被调用方的关系)。
  • 操作名称(如服务接口名、数据库方法名)、时间戳(开始 / 结束时间)、耗时、标签(如服务地址、错误信息)等。

通过 TraceIDParentSpanID,SkyWalking 可以将分布式系统中分散的 Span 串联起来,还原出完整的调用链路。

让你来设计高并发系统,你会怎么设计

主要是并发读和并发写嘛,在读方面的话我们可以采用缓存的方式,比如前端采用CDN和freamaker,后端采用redis来缓存,然后在写方面用MQ或者xxl-job来实现异步,削封填谷

面试真题

Docker相关面试真题

1:docker,打一个镜像包,从打包到上传过程,dockerfile了解过吗,排查docker日志。
2:有没有了解过docker在多台机部署情况下的网络问题,怎么处理?

回答:【线下版本】面试官,过去我只操作过单台机器的,借助于docker create network命令

【线上版本】啊面试官,这个问题我遇到过,大概是这样,用一个命令叫overlay(语速慢点懂得都懂)

3:docker熟悉吗?dockerfile呢

回答:从这几方面回答常见命令、dockerfile、dockercompose,项目部署(docker-compose up -d)

4:用docker还是k8s(kubernetes)

,K8S一般用在CI/CD场景

5:有独立部署过liux项目吗?
6:使用Docker部署项目是怎么进行部署的?前端有部署过吗?
7:基于docker进行搭建的吗?有没有写过docker compose文件
8:docker compose的理解


Redis相关面试真题

1.Redis数据类型和特性

string、list、set、zset、hash


多线程相关面试真题

用过多线程吗?创建线程有几种方式

注意要解释每一种的细节

(1)繼承Thread(死瑞得)

(2)实现Runnable

(3)实现Callable,有返回值

(4)线程池

线程池核心参数,工作原理

核心线程数:5

等待队列:10

最大线程数:20

Case1:第1个线程过来,会怎么样:核心

Case2:第6个过来会怎么样:等待

Case3:第16个来怎么样:临时线程

Case4:第30个:拒绝策略(直接抛异常,交给主线程执行或任意你能记住的常见两个)

怎么定义核心线程数:IO密集型、CPU密集型

MySql相关面试真题

mysql的索引你知道吗

(1)索引类型:主键、唯一、组合;又叫聚簇索引和非聚簇索引(二级索引、辅助索引);

(2)索引数据结构:Hash、B+Tree

索引失效的场景

(1)like的时候%写在前面

(2)in条件过多

(3)违背最左匹配原则

(4)数据过少,mysql的执行引擎会不用索引

有很多种不止这些

SSM框架面试真题

AOP的实现方式

(1)JDK:有接口

(2)CGLIB:普通类

.Srping的Bean生命周期

集合面试真题

HashMap 1.8数据结构

数组 + 链表/红黑树

hashmap的了解

 - **数据结构**

   - 1.7:数组+链表

   - 1.8:数组+链表/红黑树

     - 数组长度大于64,链表长度大于8

 - **扩容机制**

   - 负载因子:0.75

     - 泊松分布

   - 扩容:2N

 - **源码流程**

   - put

   - get

     - key-->hashcode-->int-->key得到value

 - **线程安全**

   - 不安全

   - 要安全

     - ConcurrentHashMap

       - 1.7:分段锁:16个Segement

         - 可以保证同时有16个并发,并且安全

       - 1.8

         - CAS思想

     - SynUtils.HashMap

     - HashTable

算法

选择排序

快排

public class QuickSort {
    public static void quickSort(int[] a, int low, int high) {
      int i,j,temp,t;
      if (low > high){
          return;
      }
      i = low;
      j = high;
      //temp就是基准位
      temp = a[low];
      while (i < j){
          //先看右边,依次往左递减
          while (temp <= a[j] && i < j){
              j--;
          }
          //再看左边,依次往右递增
          while (temp >= a[i] && i < j){
              i++;
          }
          //如果满足条件则交换
          if (i < j){
              t = a[i];
              a[i] = a[j];
              a[j] = t;
          }
      }
      //最后将基准为与i和j相等位置的数字交换
        a[low] = a[i];
        a[i] = temp;
        quickSort(a, low, i-1);//继续递归左边序列
        quickSort(a, j+1, high);
    }
    public static void main(String[] args) {
        int[] a = {1, 5, 4, 3, 2, 6, 7, 9, 8};
        quickSort(a, 0, a.length - 1);
        for (int i = 0; i < a.length; i++) {
            System.out.print(a[i] + " ");
        }
    }
}

单例模式

//单例模式:饿汉式
public class Singleton1 {
    //私有构造
    private Singleton1(){}
    //定义类变量,记录此类对象
    private static Singleton1 instance = new Singleton1();
    //定义类方法,返回此类唯一对象
    public static Singleton1 getInstance(){
        return instance;
    }
}
//单例模式:懒汉
public class Singleton2 {
    //私有构造
    private Singleton2(){}
    //定义类变量,记录此类对象
    private static Singleton2 instance;
    //定义类方法,返回此类唯一对象
   public static Singleton2 getInstance(){
       if(instance == null){
           instance = new Singleton2();
       }
       return instance;
   }
    
}
//双检锁
public class Singleton {
    // 使用volatile关键字确保instance在多线程环境下的可见性
    private static volatile Singleton instance;
    
    // 私有构造方法,防止外部实例化
    private Singleton() {
        // 可以添加初始化代码
    }
    
    // 双检锁获取单例实例
    public static Singleton getInstance() {
        // 第一次检查:如果实例不存在才进入同步块,提高效率
        if (instance == null) {
            // 同步块,保证多线程环境下的线程安全
            synchronized (Singleton.class) {
                // 第二次检查:防止多个线程同时通过第一次检查后重复创建实例
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}
相关文章
|
13天前
|
数据采集 人工智能 安全
|
8天前
|
编解码 人工智能 自然语言处理
⚽阿里云百炼通义万相 2.6 视频生成玩法手册
通义万相Wan 2.6是全球首个支持角色扮演的AI视频生成模型,可基于参考视频形象与音色生成多角色合拍、多镜头叙事的15秒长视频,实现声画同步、智能分镜,适用于影视创作、营销展示等场景。
652 4
|
8天前
|
机器学习/深度学习 人工智能 前端开发
构建AI智能体:七十、小树成林,聚沙成塔:随机森林与大模型的协同进化
随机森林是一种基于决策树的集成学习算法,通过构建多棵决策树并结合它们的预测结果来提高准确性和稳定性。其核心思想包括两个随机性:Bootstrap采样(每棵树使用不同的训练子集)和特征随机选择(每棵树分裂时只考虑部分特征)。这种方法能有效处理大规模高维数据,避免过拟合,并评估特征重要性。随机森林的超参数如树的数量、最大深度等可通过网格搜索优化。该算法兼具强大预测能力和工程化优势,是机器学习中的常用基础模型。
350 164
|
7天前
|
机器学习/深度学习 自然语言处理 机器人
阿里云百炼大模型赋能|打造企业级电话智能体与智能呼叫中心完整方案
畅信达基于阿里云百炼大模型推出MVB2000V5智能呼叫中心方案,融合LLM与MRCP+WebSocket技术,实现语音识别率超95%、低延迟交互。通过电话智能体与座席助手协同,自动化处理80%咨询,降本增效显著,适配金融、电商、医疗等多行业场景。
359 155