一个表有用户和时间两个列,现有3个需求:根据用户查;根据日期查;根据日期和用户查;问怎么建立索引?

典型回答 这是一个比较简单的索引创建的问题,其实考察的点无非就是一个联合索引和最左前缀匹配,这里不涉及到区分度的问题、也不涉及到索引失效的问题。所以还是比较简单的。 需求要求支持三种查询,其中两个是单列查询,一个是多列查询。那么只需要建立2个索引就够了。 1、建立用户id、日期的联合索引。来满足按照用户查询、和按照用户+日期查询,这样都可以走到这个索引上。 CREATE INDEX idx_user_date ON table_name (user, date); 2、建立时间索引。来满足按照时间查询。 CREATE INDEX idx_date ON table_name (date); idx_user_date 联合索引中把用户放在最左边,就可以实现按照用户查询也能走到这个索引,同时按照用户+时间的查询也能走到这个索引。这样就可以减少重复的索引创建,用一个索引就能满足两个查询了。 另外,如果这张表中,用户的区分度不高,比如一共就没有多少用户,但是每个用户的单量很大,比如B类业务,那么可以考虑改一下索引: 1、建立日期、用户id的联合索引。来满足按照时间查询、和按照用户+日期查询,这样都可以走到这个索引上。 CREATE INDEX idx_date_user ON table_name (date,user); 2、建立用户索引。来满足按照用户查询。 CREATE INDEX idx_user ON table_name (user);

March 22, 2026 · 1 min · santu

进入电梯里断网后又恢复刚开始为什么网络慢?

典型回答 在电梯里断网后,网络恢复时速度通常会比较慢,这是由多种因素引起的,以下是一些主要原因: TCP的拥塞控制 在网络断开并重新恢复连接后,TCP协议的拥塞控制机制会起作用。在网络断开的过程中,TCP的超时重传机制会导致其认为网络发生了拥塞,因此TCP发送窗口(Congestion Window,cwnd)会被缩小。 当连接恢复时,TCP并不会立刻恢复到断开前的传输速度,而是通过慢启动机制从一个较低的发送速率开始,逐步增加发送窗口。因此,刚开始时速度会比较慢,直到TCP的拥塞窗口逐渐扩大,传输速率才会恢复。 ✅介绍下TCP是如何实现拥塞控制的? 网络重新协商 如果用的是无线网络,在中断后恢复时,通常需要重新进行信号质量的检测和连接的重新协商。这个过程可能包括以下几个步骤: 重新连接到基站/路由器:网络断开后,设备需要重新寻找并连接到最合适的基站或无线接入点(AP)。 分配资源:网络还需要重新分配通信资源,包括信道和带宽的分配。 IP地址重新分配:如果在网络恢复后,IP地址发生变化,设备可能需要重新获得一个IP地址,这可能涉及DHCP或其他协议的重新交互。 这些过程都需要一些时间,在刚恢复连接时可能会导致数据传输较慢。

March 22, 2026 · 1 min · santu

全国的酒店价格(千万级)需要在某个瞬间比如7点发生变动,怎样高性能准点去进行变更

典型回答 问题中提到的其实是一个千万级数据的秒级变更的设计方案。其实这个方案很难,因为任何一种分布式任务、分布式更新的方案,都很难做到秒级更新千万级数据,那么,如果要保证7点时间一到,价格立即更新,可以考虑以下几个方案。 前提、预计算 想要实现高性能的准点价格更新,基本上不太可能在7点这一刻进行价格的计算,所以价格一定是提前就计算好了的,这样不管用什么样的方案,在7点需要更新价格的时候,都可以避免实时计算,可以大大的降低更新过程的耗时。 所以,价格的预计算,是这个问题的解决方案的前提。 方案一、多版本价格 有一个方案是我们在用的,我之前做过一个金融的定价中心,我们的价格就有经常的变动,我们的做法是不会针对价格配置做更新的,只会新增一条新的价格。比如以下是我的价格表的关键几个字段: 字段名 解释 price 价格 strat_time 生效开始时间 end_time 生效结束时间 code 价格定义的唯一 code condition 价格生效条件 product_id 价格所属的产品 比如当前有一条定价,是A 酒店的 X 房型,199每晚,从24年1月1日开始有效。那么定价配置如下: 字段名 解释 id 1000 price 199 strat_time 2024-01-01 00:00:00 end_time null code A_X_after_20240101 那么,如果从4.1日的晚7点开始调价,299生效,则配置内容如下: 字段名 解释 id 1000 price 199 strat_time 2024-01-01 00:00:00 end_time 2024-04-01 19:00:00 code A_X_after_20240101 字段名 解释 id 1001 price 299 strat_time 2024-04-01 19:00:00 end_time null code A_X_after_20240401 那么就相当于把之前的定价的结束时间设置为2024-04-01 19:00:00,新插入一条新的定价,生效时间为2024-04-01 19:00:00。这样用户在查询价格的时候,只需要我们计算下当前时间,去数据库查询的时候再 where 中加上start_time <= now() and end_time > now()即可。 ...

March 22, 2026 · 1 min · santu

服务器有多个节点,线上出现用户进入缓慢,监控服务器cpu和缓存没有什么压力,可以从哪些方面排查?

典型回答 这个问题,有几个关键信息需要提取出来: 多个节点、缓慢、CPU和缓存无压力。 首先,要确定的是这个问题是个例,还是普遍情况,如果只是个例,那么有可能是用户的网络、浏览器等问题,我们之前就出现过类似的情况,用户自己开了梯子,然后你懂得。。。还有的是用户自己加了一些特殊的绑定(这个通常出现在开发或者测试自己的机器上) 排除了个人问题之后。接着分析。 首先我们看问题是什么,问题是用户访问慢,如何排查。用户访问慢那就是RT长(Response Time),那么用户的一次请求的响应时间中,包含了网络连接的时长、网路传输的时长以及服务处理的时长。 在CPU和缓存都没有压力的情况下,请求的RT还是长,那么首先考虑的方向就是网络上面的耗时,有以下几种可能: 带宽限制或网络拥塞:这种情况可以检查下服务器的出入带宽是否达到了上限,或者是否存在网络拥塞情况。 网络延迟或丢包:如果网络出现了延迟,或者丢包的情况,也会导致访问速度变慢。使用网络工具(如 ping 或 traceroute)检查服务器与用户之间的延迟,看看是否存在网络中断或者数据包丢失。 CDN 问题:如果使用了 CDN,也有可能是 CDN 的某个节点有问题,导致用户无法正常加载资源。 接下来,问题中提到有多个节点(你记住,面试官再给你出问题的时候,题眼中的很多关键字,都是很重要的)。 多个节点,这是面试官主动提的,那么说明他肯定有些想问的东西埋在了里面,这里其实想问的就包含了负载均衡。因为多个节点,就需要涉及到如何在多个节点之间选择一个节点来处理请求了。 所以我们也需要检查负载均衡配置是否正确,有没有将请求分配到不健康的节点上。 接下来,就是应用本身的一些问题(其实,实际我们应该首先考虑应用问题,都排除了之后在考虑负载均衡和网络问题)。 首先就是有可能有一次时间比较长的GC。这个大家都比较容易理解。 应用的CPU虽然不高,但是如果应用频繁读写磁盘数据,有大量的I/O操作,也可能会导致响应慢,所以我们需要检查磁盘的 I/O 是否存在大量读写以及瓶颈。 比如说有大量的日志写入,这是个很常见的一个问题,有些应用的瓶颈都卡在了日志写入上。 ✅项目中,如果日志打印成为瓶颈,该如何优化? 其次,除了文件的I/O外,还有一些网络层面的,比如你这里需要调外部接口,而外部接口响应很慢,一直等他返回,这时候也会导致请求的RT变长,但是CPU和缓存没有啥异常。 最后,还有一个值得考虑的,那就是数据库的性能,如果存在一些慢SQL,数据库连接数不够,以及锁阻塞等问题,也会导致请求很慢,这时候就要结合数据库的监控来排查这个问题。 扩展知识 排查路径 如果这个问题,是我遇到了,我会这么排查(我司监控比较完善,所以排查问题还是比较容易的) 1、查看监控,是否存在误报。看异常是否还在持续。 2、查看当前是否正在发布,是否和发布有关,进一步确认是否代码问题,或者未预热导致。 ✅应用启动后前几分钟,Load、RT、CPU等飙高,如何定位,可能的原因是什么? 3、通过机房、分组等对比看下异常是全局的还是部分的。进一步定位到具体的影响范围。 4、同时查看当前时刻的QPS是否有明显增加,是否和流量上涨有关,如果是流量突增,先扩容。 ✅什么是QPS,什么是RT? 5、查看当前时刻是否有频繁的GC,尤其是FullGC,以及GC的时长。 ✅4C8G的机器,各项系统指标,什么范围算是正常? 6、同时找到具体的RT变长的服务(我们针对每一个RPC接口都有详细的RT的监控)以及下游依的服务。 7、看下游服务是否有大量的超时或者失败的情况,如果是,直接call人。 8、查看底层依赖,比如Redis、MySQL等的是否有慢查询的异常情况,如果有,进一步看原因。 9、如果以上都没有,查看这台虚拟机所在的宿主机的情况,是否有大量的网络重传等问题。 ✅什么是TCP重传率,有什么用?如何查看?

March 22, 2026 · 1 min · santu

账户里面只有十块钱,同时发来两笔订单一共大于十块钱,怎么保证不超花?

典型回答 这是一个典型的超卖问题。 所谓"超卖"指的就是商品卖多了,一般我们在商品扣减库存的时候,都会先判断库存够不够,如果够在进行扣减,不够则直接返回下单失败。而在这个问题中,其实就是避免余额的超卖。 方案一、分布式锁 这个问题的解决方式最简单的也是最容易出错的方式,那就是先查一把余额,判断下够不够,如果够就扣减,不够就不扣减,伪代码如下: 1 2 3 4 5 6 7 8 9 10 11 function 扣减余额(账户, 扣减金额): # 1. 查询账户余额 余额 = 查询账户余额(账户) # 2. 判断余额是否足够 if 余额 >= 扣减金额: # 余额足够,进行扣减 余额 = 余额 - 扣减金额 更新账户余额(账户, 余额) else: # 余额不足,不进行扣减 但是这么做最大的问题就是一旦请求是同时来的,那么就会出现查询的时候金额都是够的,然后更新的时候就更新成负数了。 所以,我们要解决这个问题,那就是让所有的余额扣减的请求排队执行!就是不要发生并发也能解决。 那么最简单的方案,就在查询用户余额之前,针对账户先加一把锁。只有抢到锁的线程才能进行余额的查询和扣减。这样就可以避免这个问题了。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 function 扣减余额(账户, 扣减金额): # 1. 尝试给账户加锁 if 加锁(账户) == 成功: try: # 2. 查询账户余额 余额 = 查询账户余额(账户) # 3. 判断余额是否足够 if 余额 >= 扣减金额: # 余额足够,进行扣减 余额 = 余额 - 扣减金额 更新账户余额(账户, 余额) else: # 余额不足,不进行扣减 finally: # 4. 无论成功与否,解锁账户 解锁(账户) else: # 加锁失败,输出提示 方案二、数据库乐观锁 但是这样做有一个缺点,那就是加锁会降低并发度,如果并发量特别大的时候,会导致大量的线程阻塞。 ...

March 22, 2026 · 1 min · santu

有100个优惠券,有几千万流量,怎么保证服务器不跨掉,怎么保证最前面的人能抢到这个券?

典型回答 这是一个典型的秒杀问题。几千万个并发抢100个库存,如何保证不超卖、不少卖,如何保证服务不被打垮、如何保证前面的人能抢到,这都是秒杀系统需要考虑的重点问题。 超卖:一共100件,卖出去101件 少卖:一共100件,只卖出去99件,另外的那一件无法购买了 关于优惠券的库存的超卖和少卖的问题,可以看一下其他的文档,内容都是一样的方案。 ✅库存扣减如何避免超卖和少卖? ✅账户里面只有十块钱,同时发来两笔订单一共大于十块钱,怎么保证不超花? 这里重点说一下如何保证前面的人能抢到。 这个其实就是我们多次提到过的,要用Redis来做扣减,而不是直接用加分布式锁和限流来实现秒杀的主要原因。 因为如果使用限流,那么没办法保证被限制住的流量是先来的还是后来的,因为有可能非常极限的他们差个零点几毫秒,但是限流其实是按照秒级做统计和限制的,所以一秒内的请求一起过来的时候,谁能被放过,谁能被阻塞,就靠运气了。 分布式锁也是一样的,需要靠抢锁来获取库存扣减的资格,但是并不能保证先来的一定能抢到锁,因为有可能当前有人在持有这个锁,而后来的线程可能来的时候刚好没锁,就很快就拿到了。 而如果借助Redis的lua脚本或者事务进行库存扣减,那么Redis可以保证命令执行的先后顺序,按照发送顺序进行执行,先来的请求就能先进行库存扣减了。扣减成功则说明有库存,那么就可以给他发放优惠券了。(操作数据库) 当库存扣减为0之后,后续的请求再进来,会在Redis中构建失败,因为库存已经没有了,无法扣减了,这样就可以把后续的所有流量都抗在数据库之前了。 如果做的再好一点,在redis中维护一个优惠券是否还有库存的表示,先判断是否还有,然后再扣减,或者这个标识也可以维护到本地缓存,可以进一步提升性能,减少应用被打垮的概率。(这个本地缓存的方案在我的项目课中有用到。感兴趣的可以了解下、) 🧣🧣🧣项目实战课介绍&老用户福利

March 22, 2026 · 1 min · santu

秒杀场景下,怎么加库存?

典型回答 这是一个非常典型的秒杀相关的问题,秒杀有关的问题,在我的八股文中有多篇都提到过: ✅让你设计一个秒杀系统,你会考虑哪些问题? ✅有100个优惠券,有几千万流量,怎么保证服务器不跨掉,怎么保证最前面的人能抢到这个券? ✅库存扣减如何避免超卖和少卖? 但是之前讲的主要都是基于库存的扣减的,那么,如果在秒杀期间,想要增加库存该怎么办呢?又需要考虑哪些问题呢? 这个问题看上去好像挺难的。但是如果你想清楚,其实他和库存的扣减是一回事。因为在库存扣减的方案中,我们不管使用数据库也好,Redis也好,都是需要串行进行扣减的,并且库存也是在扣减过程中进行计算的,要么是LUA脚本中计算,要么是SQL语句中计算。 那么,既然高并发的扣减我们都能解决了,那库存的增加还不是一样的道理。我么只需要把扣减的方案一模一样的用到增加库存上就行了。 比如最复杂的Redis+数据库的方案: 1、通过lua脚本进行库存的增加,借助Redis的单线程执行的特性,加上Lua脚本执行的原子性,实现库存的原子性增加。 1 2 3 4 5 6 7 8 9 local key = KEYS[1] -- 商品的键名 local amount = tonumber(ARGV[1]) -- 增加的数量 -- 获取商品当前的库存量 local stock = tonumber(redis.call('get', key)) -- 增加库存并返回新的库存量 redis.call('incrby', key, amount) return redis.call('get', key) 2、发送MQ通知数据库进行库存增加(加库存场景这一步可以省略,但是要做好表单防重复提交) 3、消费MQ,进行数据库的库存的增加,在执行时通过SQL进行库存的增加,即: 1 2 3 update inventory set quantity = quantity + #{count} where sku_id='123' 如上,Redis的库存增加可以和库存扣减进行排队,靠单线程+Lua脚本原子性。数据库的库存增加也可以可库存扣钱进行排队,靠的是update自带的行级锁。 所以,这个问题就解决了,至于MQ丢失的问题,Redis挂了的问题,和扣减库存其实是一样的。 但是,上面的方案有一个关键前提,那就是需要让增加库存和扣减库存一样,知道我这次要加多少。但是实际上,很多时候库存的修改,并不一定是知道要【加多少】的,只知道我要【加到多少】。所以,这个就需要业务上做取舍了,要么就是在库存设置的后台处,提供【加多少】的增加库存的方式,要么就是以实际增加那一刻为准,算出要加多少,但是这么做可能会在高并发扣减下不准,但是影响其实也不大。

March 22, 2026 · 1 min · santu

项目中需要应用发布和ddl变更,需要如何保证不出错?

典型回答 项目发布的时候,是先升级程序,还是先执行ddl的sql 一般是先执行DDL变更,比如增加索引,或者新增字段,要先把DDL变更发布了,然后再发布代码,这样的话不会有报错,如果先发布代码,会因为DDL未变更导致字段找不到的问题。 要不要做个开关,来兼容ddl的sql变更 一般来说不需要,因为大部分公司是不允许删除字段的,DDL变更也必须要向下兼容,所以不太需要这个开关。 如果是不兼容变更,那么就可以做个开关,但是这样的话代码就很复杂。。。不建议 超大表的ddl的sql执行,需要注意什么? 不要在业务高峰期执行,因为虽然MySQL 5.6中支持ONLINE DDL了,DDL过程中不会锁表了。但是Online DDL 在执行过程中会占用系统资源,如 CPU、内存和 I/O。这可能会对数据库的性能产生一定影响,尤其是在数据量较大的情况下。 ✅MySQL做索引更新的时候,会锁表吗? 在Spring Boot中,使用JPA来自动更新数据库表结构,推荐吗? 那肯定是不推荐啊! 在生产环境中,直接使用 ddl-auto=update 存在风险。复杂的表结构变更(如字段重命名、大规模数据迁移、索引变更等)可能会引发数据丢失或性能问题。 使用 ddl-auto=update 时,数据库变更的历史没有被记录下来,难以追踪变更的具体原因和执行时间。这对于维护和调试会带来困难。 有些变更(如外键关系的修改、大规模数据迁移、触发器等)无法通过 JPA 的自动更新来完成,需要手动编写 DDL 脚本。

March 22, 2026 · 1 min · santu

5 分钟内最多允许用户尝试登录 3 次,如果错误次数超过限制,需要对该用户进行锁定。如何实现?

典型回答 题外话:这个问题,我在14年找实习的时候,被易保支付这家公司问过,当时面试通过了,但是我没去。没想到10年了,我最近看面经的时候,这家公司还在问这个问题。当年我回答的还行,但是现在让我回答肯定能回答的更好了。 这个问题,其实主要实现2个关键功能即可。 1、限制用户5分钟内最多尝试登录3次。 2、对用户进行锁定。 第一个功能,其实是一个典型的滑动窗口问题。 ✅什么是滑动窗口限流? ✅如何基于Redis实现滑动窗口限流? 我们只需要构造一个滑动窗口,窗口大小限制5分钟,然后限流次数设置为3次即可实现这个功能了。而滑动窗口我们可以借助Redis来实现。 而第二个功能,我们只需要把被锁定的用户保存在Redis中即可,这样还能根据业务要求,设置一个合理的超时时间。 主要的实现代码如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 import redis.clients.jedis.Jedis; public class SlidingWindowRateLimiter { private Jedis jedis; private String key; private int limit = 3; //限制请求次数最大3次 private int lockTime; // 锁定用户的时间,单位:秒 public SlidingWindowRateLimiter(Jedis jedis, String key, int limit, int lockTime) { this.jedis = jedis; this.key = key; this.limit = limit; this.lockTime = lockTime; // 锁定时间 } public boolean allowRequest() { // 当前时间戳,单位:毫秒 long currentTime = System.currentTimeMillis(); // 锁定键的名称(锁定的用户) String lockKey = "lock:" + key; // 检查用户是否已被锁定 if (jedis.exists(lockKey)) { return false; // 用户已被锁定,返回 false } // 使用Lua脚本来确保原子性操作 String luaScript = "local window_start = ARGV[1] - 300000\n" + // 计算5分钟的起始时间 "redis.call('ZREMRANGEBYSCORE', KEYS[1], '-inf', window_start)\n" + // 清理过期的请求 "local current_requests = redis.call('ZCARD', KEYS[1])\n" + // 获取当前请求次数 "if current_requests < tonumber(ARGV[2]) then\n" + // 如果请求次数小于限制 " redis.call('ZADD', KEYS[1], ARGV[1], ARGV[1])\n" + // 添加当前请求时间 " return 1\n" + // 允许请求 "else\n" + " redis.call('SET', 'lock:'..KEYS[1], 1, 'EX', tonumber(ARGV[3]))\n" + // 锁定用户 " return 0\n" + // 拒绝请求 "end"; // 调用 Lua 脚本进行原子操作 Object result = jedis.eval(luaScript, 1, key, String.valueOf(currentTime), String.valueOf(limit), String.valueOf(lockTime)); // 返回操作结果 return (Long) result == 1; } } 滑动窗口:滑动窗口的逻辑依然和原来一样,使用 Redis 有序集合(ZADD)记录每次登录尝试的时间戳,过期的记录会被自动清理。 ...

March 22, 2026 · 2 min · santu

两个不相关的网站A和B,如何实现A登录B也能自动登录

典型回答 这其实是一个非常简单的单点登录的问题,如果你能把这个想清楚,这个问题也就很好回答了。 单点登录(Single Sign-On,简称 SSO)是一种用户认证机制,允许用户在一次登录后,访问多个系统或应用程序而无需再次登录。 一般来说有以下几种方案: ✅Cookie,Session,Token的区别是什么? 上面这篇文章中,我们介绍过Cookie、Session和Token,其实,这三个东西都能帮助我们实现SSO, 1、基于Cookie(不适合本问题,但是先提一下) 单点登录的实现有很多种方式,首先是**如果是同一个域名下 **(如 a.example.com 和 b.example.com) **,可以通过共享Cookie的方式来实现SSO。**优点就是实现起来简单,浏览器天然就支持,而缺点就是限制必须不能跨域。 但是因为本题要求是不相关的两个网站,所以这个方案并不适用。 2、基于Token 使用标准化的令牌(如 JWT)来携带用户认证信息。用户登录后,服务端颁发一个令牌(Token)给到前端,前端在访问其他的网站的时候,把这个Token放到HTTP Header 中带过去,然后就可以基于这个Tokne来验证用户身份了。 这个方案可以解决跨域的问题,只要支持HTTP的协议即可。但是缺点是Token可能会被泄漏。存在一定的安全隐患。 3、基于共享Session(面试的时候重点讲这个) 关于分布式Session(共享Session),我们有多篇文章讲到过,这里就不在重复介绍了 ✅怎么实现分布式Session? ✅分布式系统,用户登录信息保存在服务器A上,服务器B如何获取到共享Session 这个方案的好处就是因为Session是服务端存储的,所以不存在泄漏的风险,但是他需要依赖一个公共的第三方存储,如Redis、数据库等。 但是一般来说Session都还是需要一个其他的信息来做关联的,比如Token或者Cookie,所以也并不是说就绝对安全的。只不过可以做二次校验。 4、通过CAS 所谓CAS,这个和我们并发编程中的Compare And Swap的那个不是一回事儿,他是Central Auth Service,也就是认证中心服务。 就是说,多个需要实现单点登录的系统,都接入这个统一的认证中心,所有的登录、 认证都通过这个认证中心来实现。 1、用户访问子系统时重定向到 CAS 服务器登录。 2、CAS 服务器验证用户身份并返回一个票据(Ticket)。 3、子系统使用票据向 CAS 验证,获取用户信息。 这个方案一般适合于企业内部做集成,可以让业务系统不再关心登录、授权,而是只关注业务逻辑,只需要做一次接入就行了。比如说你们公司的很多内部平台之间,就可能是用了同一个登录服务。 优点就是接入成本低,缺点就是这个认证中心本身的开发成本还是挺高的。 5、通过OAuth2/ OpenID Connect 基于 OAuth 2.0 协议,允许用户通过授权服务器进行身份验证,子系统通过访问令牌或用户信息完成验证。 ✅什么是OAuth2?有什么用? 这个方案和CAS类似,只不过CAS是把OAuth2做到自己的内部了。简化了接入成本。

March 22, 2026 · 1 min · santu

留言给博主