AI面传统Java问题

张开发
2026/5/17 20:04:50 15 分钟阅读
AI面传统Java问题
RedisSDS相对C字符串优势Dict触发hash扩容的两个条件String三种底层编码是什么什么区别触发List底层结构由ziplist转向双向链表的两个阈值条件Redis 3.2 版本前后List 类型的底层编码分别是什么ZSet 的 dict 和 zskiplist 如何配合ZSet 的 dict 和 zskiplist 如何避免成员内存重复占用Set的三种底层编码是什么什么区别redis采用的过期删除策略什么是BIGKEY问题怎么解决Redis删除中的熔断机制是什么Redis如何通过写时复制实现异步持久化机制哨兵模式下主节点挂掉哨兵如何选取新的主节点选取的策略是什么什么是脑裂在集群模式下和哨兵模式在如何预防Mysql什么是mysql事务的一致性原子性隔离性持久性。分别是使用什么实现的可重复读如何解决幻读有没有彻底解决四种事务隔离级别从低到高排序脏读、不可重复读、幻读分别被哪些隔离级别解决MVCC 实现依赖哪三大核心组件ReadView 是什么包含哪四个核心字段RR 与 RC 级别在 ReadView 生成时机上的核心区别快照读和当前读的区别是什么InnoDB 无索引的查询会加什么锁UPDATE 索引字段的底层加锁逻辑是什么为何会被间隙锁阻塞简述 InnoDB 插入记录成功与失败时的加锁流程。什么是隐式锁隐式锁何时转为显式锁意向锁IS/IX的作用是什么加行锁 / 间隙锁前为何要先加表意向锁Buffer Pool 里的数据页和索引页的区别是什么简述 MySQL 执行一条增删改并 commit 的完整流程简单 LRU 算法用于 Buffer Pool 管理会出现哪两个严重问题请分别说明。InnoDB优化LRU分哪两个区域什么作用InnoDB 优化 LRU 时为何要为 old 区域添加访问停留时间限制bufferpool管理用到的三个链表索引失效场景有哪些出现了慢sql如何排查优化JVMJVM内存结构Java四种引用区别Java内存泄露的常见情况JVM创建对象的过程JVM类加载器有哪几种JVM类加载到卸载过程双亲委派模型是什么有什么作用可达性分析算法中的GC Roots包含哪些对象对象真正被回收前会经历哪两次标记finalize()方法的作用是什么CMS 收集器的完整执行流程分为哪四个阶段哪些阶段会 STWCMS 的核心缺陷有哪些G1 的执行流程包含哪几个核心阶段Mixed GC的核心逻辑与优势是什么CMS用于堆的哪个区域G1 为什么采用标记 - 复制算法而不是标记 - 清除Java基础为什么需要包装类自动装箱拆箱原理Integer的缓存机制equals和hashcode为什么要配套重写StringBuffer和StringBuilder的区别Stream流有什么用Optional有什么用Volatile什么作用什么是代理模式、适配器模式、责任链模式、策略模式Java集合CocurrentHashMap如何实现并发安全hashmap的put过程介绍一下包括出现冲突以及扩容和变化桶结构Hashtable和hashmap区别Hashmap每次扩容多少为什么HashMap为什么要变化桶结构java并发编程讲一下对CAS的理解以及存在什么问题ABA问题怎么解决threadlocal原理为什么threadlocalmaps的key为弱引用讲一下threadlocalmaps的内存泄漏原理怎么解决cocurrenthashmap如何保证线程安全java创建线程的方法java如何停止一个线程的执行java线程的六种状态sleep()和wait()方法的区别包括设计用途如何唤醒使用前提以及是否释放锁Blocked状态和Waiting状态什么区别ReentrantLock的高级功能什么是读写锁synchronized工作原理讲一下synchronized的锁升级CountDownLatch应用场景死锁四个条件SpringResource和Autowired什么区别什么是构造器注入字段注入setter注入为什么Spring更推荐构造器注入什么是循环依赖怎么解决为什么使用三级缓存而不是二级缓存事务什么时候失效Bean的生命周期SpringMVC的流程SpringBoot自动配置类原理原生的mybatis查询怎么写计算机网络一句话总结linux网络协议栈HTTP和TCP的keepalive什么区别http3,4,5开头的状态码什么含义为什么说HTTP1.1是无状态的HTTP和HTTPS哪些区别HTTPS如何通过数字证书防止服务器冒充HTTPS如何防止信息泄露被窃听HTTPS如何建立连接的什么是RPCRPC和HTTP的主要区别Websocket如何建立连接为什么要用websocket和http相比优势TCP三个特点TCP握手为什么是三次不是两次和四次为什么每次TCP连接初始序列号都不一样为什么TCP挥手需要四次什么是TCP快速重传什么是TCP流量控制什么是TCP拥塞控制如何理解TCP是面向字节流的协议UDP是面向什么的IP都有分片机制为什么还要有TCP的MSS为什么要区分网络号和主机号为什么要划分网络号和子网号面试select * from user inner join address on user.id address.user_id where user.sex ‘male’ and user.age 18 and address.location‘henan’ and address.type2。要优化这个sql如何建立索引1.如果驱动表是address那么查询过程是先查到locationhenan and type2的user_id再拿user_id去user表中查记录并返回iduser_id and sexmale and age18的记录。因此首先有索引(location, type, user_id)注意user_id在最后。由于是select *因此之后在user表的主键索引查询sex和age相关记录。2.如果驱动表是user那查询过程是首先查user.sex male and user.age 18的对应user记录包括id之后在address查对应userid对应的location和type如果有联合索引(userid,location,type)则查联合索引找到符合location和type条件的索引节点之后过滤掉不符合的userid对应记录剩余的user记录就是结果。所以我们可以建立索引user表(sex, age)和address表(userid, location, type)或者user表直接用聚簇索引只建立address表(userid, location, type)threadlocalmaps为什么设计key对threadlocal是弱引用value对存数据是强引用1.key如果对threadlocal是强引用那么如果threadlocal是局部变量或者用户手动置为nullthreadlocalmaps也不会释放该keythreadlocal对象仍然存在堆中。如果是弱引用就会被GC回收2.value如果对存数据是弱引用假设我们存了一个new User()而该对象只被value引用那么GC会直接回收导致刚存就丢失了因此要强引用。threadlocal在生产中可能出现的问题1.由于key是弱引用因此置threadlocal为null导致对象在堆中被回收key变为了nullvalue还在发生了内存泄露2.在线程池环境中如果上一个线程处理用户A请求没清理threadlocal该线程服务下一个用户B由于是同一个线程B会拿到A的数据3.假设userinfo的threadlocal不是final而被人修改了那就会导致同时所有线程的threadlocalmaps的key指向的该userinfo threadlocal全都是这一个堆对象不被引用而被GC回收所有线程的用户信息都同时丢失rocketmq事务执行流程1.后端调用template的sendMessageInTransaction方法并给出topic和消息负载2.后端rocketmqtemplate发送一个半消息和全消息负载一样给broker来表示我要发送消息了3.1.broker如果没接收到半消息那么后端的executeLocalTransaction中的业务逻辑根本不会执行也不会发送全消息3.2.broker接收到半消息并返回给后端该半消息Message msg后端执行executeLocalTransaction方法4.1.方法逻辑执行成功则return commit信号后端发送commit信号给brokerbroker把半消息转成全消息4.2.方法逻辑执行失败则return rollback信号后端发送rollback信号给brokerbroker销毁半消息4.3.方法逻辑执行成功return commit信号但是commit信号没有到达broker后端本地认为自己发送成功但是broker迟迟接收不到任何信号4.4.broker收不到信号于是定时去查询后端后端checkLocalTransaction方法接收去查询redis或数据库并重新返回commit和rollback信号。rocketmq事务是否会有“本地事务执行失败回滚而消息发送了”的情况OverridepublicRocketMQLocalTransactionStateexecuteLocalTransaction(Messagemsg,Objectarg){try{// 1. 调用 Service。// 如果 commit() 在此处失败这一行代码会直接抛出异常orderService.doBusiness(msg);// 2. 如果上面抛了异常这一行【永远不会执行】returnRocketMQLocalTransactionState.COMMIT;}catch(Exceptione){// 3. 异常被这里抓住了log.error(本地事务执行异常包括commit失败,e);// 4. 返回 ROLLBACK告诉 RocketMQ 删掉半消息returnRocketMQLocalTransactionState.ROLLBACK;}}executeLocalTransaction上不能加Transactional防止事务嵌套保证当doBusiness执行完事务就会提交doBusiness要加Transactional。在这种情况下只要doBusiness事务提交失败就会发生异常被捕获发送ROLLBACK消息。因此规范写法下不会出现问题中的情况。用一个表如user存储所有租户的user信息会导致表过于庞大如何解决1.数据库水平分片。设计多个数据库以及路由算法比如tenantid%2利用ShardingSphere等中间件可以对业务完全无感代码仍然写select * from user中间件自动帮你路由2.数据库原生分区表。CREATE TABLE USER ( id BIGINT, ...) PARTITION BY HASH(tenant_id) PARTITIONS 100;可以将USER表预分成100个物理分区相当于100个分表同样业务完全无感mysql底层自动帮助你进行分区路由分库分表的算法有哪些直接取模简单但是假设库或表的数量变化原本的取模逻辑就变化就需要移动所有数据范围算法映射表算法专门设置一个key-ShardId的映射表。优点是非常灵活缺点是每次都要多查询一个表一致性哈希算法为什么不先更新mysql再更新redis而是采用删除redis的策略先更新mysql再更新redis有一种意外情况事务A更新mysql为V1过了1s事务B更新mysqlV2事务B更新redisV2事务A更新redisV1。此时数据库为V1但是redis为V2先更新mysql再删除redis有没有特殊情况下的风险事务A更新mysql事务A删除redis事务B读取到mysql旧值事务B把旧值放到redis事务Acommit。这时mysql是新值但是redis是旧值。不过这种情况较少因为删除redis到提交一般都挺快的。这种情况的解决方案延迟双删先删除redis再更新mysql提交事务等待几百毫秒再删除redis。为什么要等几百毫秒是因为期间可能有事务读到旧值并放入redis我们要保证删除redis在其之后。为什么要先删除redis因为等了几百毫秒才第二次删期间防止redis有脏数据。令牌桶限流算法能不能用于限制访问请求量比如10s只允许10000个请求访问。如何实现1.采用redislua脚本2.为了防止每个请求都会访问redis造成频繁读写。用服务器批量取令牌并存在本地内存请求在服务器内存取令牌3.服务器定期或在令牌即将耗尽时异步取令牌放入本地内存4.redis在每次服务器取令牌时根据与前一次取令牌时间差进行补充令牌5.如果redis没有令牌了则后端直接忽视请求该算法虽然有一定的限流作用。但是我们知道每天各个时间段请求访问量是动态的。比如晚7点高峰这会大量请求进入而其他时间请求量较少。这种情况下该如何设计限流算法首先需要明确一点在流量高峰期应该放宽限流策略还是加紧限流策略答案是看系统负载如果CPU内存还有余力则应缓慢增加令牌数如果硬件告急则应该强制降低令牌数。基于此我们有下面几个基本的限流算法设计1.基于时间段动态限流2.监控CPU使用率内存占用以及平均响应时间等。假如CPU70%则说明策略偏保守缓慢增加rateCPU80则强制降低rate3.核心业务和非核心业务采用不同的令牌桶且核心业务分配更多令牌分布式环境下三级缓存中如果mysql更新如何保证caffeine中的本地缓存被清除由于是分布式环境简单使用CacheEvict只会清除本机的缓存因此不适用解决方案使用MQ的广播模式。当某服务器执行更新操作时发送广播消息到MQ的某topic此外每个服务器都有消费者监控该topic监控到则调用caffeine.invalidate(key)nginx集群环境下mysql更新要通知nginx删除缓存这种情况下有一个基础的实现方案nginx暴露清理缓存接口后端或MQ消费者去调用接口清除nginx缓存。我们的这种方案有什么问题如何解决问题nginx集群该请求所有nginx的接口么如果nginx集群扩容了呢解决方案最基础的解决方案是在redis中除了要存具体的数据外额外存一个键值对用来注明该键值对的版本号。nginx在每次请求时都去请求一次redis查询版本号比查全量数据要快与自己本地缓存的版本号对比。如果不对则请求放到后端这个解决方案有几个问题每次请求都要查一次redis造成大量负担此时nginx缓存就失去初衷了进阶解决方案在redis中除了要存具体的数据外额外存一个键值对用来注明该键值对的版本号。nginx每隔一秒去请求一次redis并放到lua_shared_dict共享内存中。查询版本号比查全量数据要快与自己本地缓存的版本号对比。如果不对则请求放到后端该方案不用每次请求都访问redis保证了1s访问一次redis以及缓存的实时更新。同时1s的延迟在零售场景下完全可以接受但是有一个严重问题假设100个nginx服务器每个服务器16个 worker同时请求redis版本号造成缓存击穿最终解决方案1在redis中除了要存具体的数据外额外存一个键值对用来注明该键值对的版本号。nginx每隔一段时间去请求一次redis并放到lua_shared_dict共享内存中并使用共享锁锁定nginx内部worker对redis的请求确保每个nginx服务器只有一个worker在请求redis。查询版本号比查全量数据要快与自己本地缓存的版本号对比。如果不对则请求放到后端原本的100*16个请求被缩小到了100个最终解决方案2采用rocketMQ广播消息100个nginx服务器作为独立消费者监听并主动更新本地shared_dict当前项目实现了乐观锁扣减库存防止超卖redis扣减库存并异步插入mysql订单记录。不过这里没有涉及到用户支付操作请给出如果要加上用户支付基本的业务流程该是什么样的用户点击下单redis判断库存是否充足并扣减库存扣减成功则发送MQ延迟消息并发送MQ订单入mysql库消息用户点击支付并付款异步入mysql库付款记录并更新订单状态UNPAID-PAID。如果用户没有点击支付则延迟消息到期查询付款记录发现查不到于是认定用户未支付更新订单状态为CANCELED清除已下单用户id并往redis中补充库存这个业务流程有个很大的漏洞假设延迟消息过期时间15min在刚好15min这个节点用户支付的回调执行要更新订单状态并生成支付记录延迟消息过期回调也要执行更新UNPAID为CANCELED并添加redis中库存。这该怎么办我的方案是使用乐观锁UPDATE ORDER SET STATUS CANCELLED WHERE ID ORDERID AND STATUS UNPAID这样可以保证只有一个能执行成功之后判断如果CANCEL执行成功了就redis.incr。这里如果支付回调支付失败则需要后续处理比如退钱逻辑删除支付记录等上述业务流程还有一个漏洞延迟队列到期时更新订单状态以及更新redis信息如何保证mysql和redis操作的一致性我认为还可以用canalMQ的方案。canal监控订单状态由UNPAID变为CANCELED于是发送MQ消息消费者异步更新redis信息。对于清除redis下单用户id和补充redis中库存这样的非幂等操作没有主键保证幂等性假如MQ消费者消费成功但是返回给MQ的信息丢失可能会重复消费。如何解决我认为可以使用lua把判断用户id是否在下单用户set中清除set中该用户id补充redis库存三个步骤放在一起。如果用户id不在set中则不补充库存。如何防止同时 qps 过高导致瞬间全部访问某个 key热点key比如明星发布博客 的请求到达 redis 造成 redis 炸了不是缓存击穿。如何解决。首先理解问题含义不是缓存击穿缓存击穿指的是击穿了数据库这里是击穿了redis其次超多请求同时到达redis此时caffeine还没有被填入数据还没有一个请求执行完解决思路是锁双重检查caffeine只存储热点key其实思路和缓存击穿类似只是caffeine不能存所有数据因为JVM内存很宝贵因此需要判断热点key并放到caffeine中非热点key没必要放到caffeine中。nginxcaffeineredis分别应该存什么nginx和caffeine应该存公有的相对稳定的热点数据。至于订单信息等高频变动的私有数据不能存nginx缓存里。商品信息就可以存nginxcaffeine中nginx一般存的是具体商品的json字符串可以直接返回给前端caffeine和redis也存caffeine由于直接运行在JVM可以直接存对象redis存序列化后数据或者HASHcaffeine减少对redis的压力。你的布隆过滤器在初始化时就指定了比特位大小那万一业务量激增布隆过滤器比特位太少了呢使用redisbloom本来就实现了自动扩容逻辑。底层实现是分层布隆过滤器布隆过滤器不支持删除操作假如一个商品此时过期大量请求通过还是会有缓存穿透的风险。如何解决不单单使用布隆过滤器加上缓存空值使用布谷鸟过滤器替代布隆过滤器布谷鸟过滤器支持删除操作什么是本地事务表为了保证事务执行与消息发送的一致性在早期由于MQ没有事务消息因此需要我们自己实现本地事务。具体的实现方式是在该事务中的最后往本地事务表t_local_transaction中插入一个事务记录包括要发送的消息内容主题状态待发送已发送发送失败重试次数等。由于和原事务在一个事务中因此原事务和事务表插入要么一起成功要么一起失败。事务提交成功后发送一个消息到MQ中发送成功则把事务表状态改为已发送起一个定时任务定时把待发送任务消息发送防止事务提交成功但是消息发送失败本地事务表高度定制但是要自己写重试逻辑次数等。并且定时扫描消耗大量资源MQ事务消息是被动回查对数据库压力小的多。并且本地事务表每次执行事务都要多访问一次mysql来存储事务信息降低吞吐量。假如某次EXPLAIN某sql语句的rows明显偏大且走了索引排除了索引失效的问题。你认为还能是什么问题索引区分度过低比如sexLIMITSELECT * FROM user WHERE age 18 ORDER BY id LIMIT 100000, 10;先找到age18的记录后返回其中第100000到100010的记录。那么就需要扫描1-100010记录才能结束。什么是索引下推ICP假设你有一张用户表建立了联合索引(name, age)。执行 SQLSELECT * FROM user WHERE name LIKE 张% AND age 20;没有 ICP 时5.6 之前存储引擎通过索引找到所有名字叫“张xx”的人的 ID假设有 1000 个。回表 1000 次把这 1000 人的完整信息从磁盘读出来。Server 层最后过滤出age 20的那 10 个人。代价990 次无效的回表随机 I/O。开启 ICP 时5.6 之后存储引擎找到名字叫“张xx”的索引记录。原地判断由于索引里刚好也存了age字段引擎直接看这行索引里的age是不是 20。精准回表只有age真的等于 20 的那 10 个人才会执行回表。代价仅 10 次回表。性能提升了 100 倍。

更多文章