分库分表时,每个城市的人口不一样,有的密集,有的稀疏,如何实现均匀分布?

典型回答 这是一个比较典型的考察分表算法的问题,也是分库分表中一个经典的数据倾斜问题的解决。 这种问题,一般有两种方案: 1、换一个更加均匀的分表键或者算法 2、人工干预分表算法 换一个更加均匀的分表键或者算法 这是最常用的方案,当我们用城市进行分表的时候,发现不均匀的时候,那就可以考虑不要用城市字段来分表了,如果业务上没有特定要求,你就可以按照其他的字段来分表,比如按照出生的月份。 但是这个方案有个局限性,那就是如果业务有要求,比如按照城市分,那就不行了。 人工干预分表算法 所谓人工干预,并不是说我们需要每次查询、插入的是都需要人工去查。而是说我们不直接使用简单的取模算法去计算分表结果,而是自己定义一个合适的分表算法。 比如这个场景,就有几种方式来实现了。 方式一:我们设定一个阈值,当城市人口数超过某个阈值的城市我们把他定义为人口密集型城市,然后维护一个列表,一般来说都不太会需要变化。然后在分表算法中,针对人口密集型城市走单独的分表算法。而人口不密集的城市走另外一套分表算法。 假设北京、上海、广州、深圳这四个城市是人口密集型城市,那么就给他们单独创建4张分表,分别存这四个城市的数据,然后其他的城市,共用4张表。伪代码如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 if 人口密集型城市? if 北京 return "table_0000"; if 上海 return "table_0001"; if 广州 return "table_0002"; if 深圳 return "table_0003"; else int index = 城市ID % 4 + 4; return "table_" + index; 当然,如果除了北上广深之外,还有城市人口也挺多,比如杭州、南京啥的,你就可以在算法中再加一个策略,把固定的几个城市放到同一张特定的分表中。总之就是提前计算好,然后制定一个固定的分表算法。 ...

March 22, 2026 · 1 min · santu

应用启动后前几分钟,Load、RT、CPU等飙高,如何定位,可能的原因是什么?

典型回答 关于 Load飙高、RT 增大、CPU飙高等问题,我们在线上问题排查的分类中有单独介绍过如何排查。那么为什么单独又写一篇呢? 一方面是这个问题确实比较典型的,而且也很场景,另外就是这个问题有的时候比较难定位,因为可能还没等定位到,就恢复了。 那么,我们该如何定位这些问题呢,他们可能有哪些原因呢?我们先分析原因的可行。 因为这个问题的前提是应用启动后的前几分钟,那么就分析前几分钟有啥不一样就行了。 Load 和 CPU 的飙高,说明当前 CPU 比较忙,意味着有很多事在处理,以及有很多任务在排队等待被处理。 ✅负载(Load)和CPU利用率之间有什么区别? 而 Load 和 CPU 的飙高就会导致 RT 的增大,因为 CPU 没资源处理你的响应了,那么你的 RT 就变大了呗。 ✅什么是QPS,什么是RT? 所以,其实这个问题啊,本质上还是在 CPU 上面,那么启动过程中,有啥情况会导致 CPU 比较忙呢? 情况一、机器不够: 首先,有一个比较常见,但是也很容易被忽略的问题,那就是应用发布过程,很多公司都是金丝雀发布,或者叫滚动发布,意味着发布过程中对外提供服务的机器数会减少。比如100台机器,你分10批发布,那么线上同时存活的机器就只有90台,那么就意味着这90台要处理平时100台机器的请求量。 ✅灰度发布、蓝绿部署、金丝雀部署都是什么? 那么就有一种可能是因为请求量大,机器数少,导致负载变高,进而导致 RT 变长,这个问题的现象是RT变长或者CPU 飙高一般会伴随整个发布过程,一旦发布完成了,就恢复了。这种情况的话你只需要延长一个批次的发布时间,就能明显的观察到整体的有问题的时长变长了,这时候如果你试着扩容几台机器上去,问题就会有所缓解。 所以说,这个问题的解决也很简单,那就是扩容,或者发布前扩容,发布后再缩容即可。 情况二、存在初始化任务 有些应用,在启动过程中有很多东西需要初始化,比如从配置中心拉配置、从注册中心拉配置、从文件中拉配置、从数据库预加载资源做缓存预热等等。 还有一些,比如说连接池的创建初始化、线程池的创建初始化等等,定时任务的执行等等,都差不多是类似的过程。 这一类过程都可以归结为启动的预热过程,而这些预热过程需要网络交互、需要内存操作那么就会对机器资源造成一定的消耗,那么就可能会出现接口响应慢,RT 变长的情况。 这个问题也比较好排查,那就是在应用启动过程,查看你的活跃线程数,以及占用 CPU 比较高的一些线程,看看他们是不是一些后台任务,或者是预热任务。 具体可以用 jstack 或者是 arthas 工具都能看 ✅CPU飙高问题排查过程(1) 这个问题解决也好解决,那就是修改一下你的启动脚本,让你的应用在完成预热之后再把服务暴露出去,再放流量进来。这里面还涉及到一些优雅上下线的问题,这里不做展开了,文档左上角搜索搜一下就有了。 情况三、JIT 优化 这个是一个比较典型的案例,比如下面这个案例,就是我们真实生产环境遇到的一个: ✅Load飙高问题排查过程 这个问题和 JIT 优化有关在哪里呢? 两种情况,情况一是在 JIT 优化完成前, 代码需要解释执行,这个过程本来就不如编译执行快,那么就可能看上去 RT 要比平时高一些。当然这个影响比较小。 还有一个更重要的情况,那就是应用刚刚启动后,JIT 优化会开始介入,这时候会做频繁的热点代码检测以及代码编译的处理,就会耗费很多 CPU 资源,导致 Load、CPU 比较高、进而导致 RT 变高。 ...

March 22, 2026 · 1 min · santu

项目中,如果日志打印成为瓶颈,该如何优化?

典型回答 这个问题有点刁钻,很少有人问,但是这确确实实是个比较大的问题,尤其是很多大厂的大促的时候,经常会出现因为日志打印而导致接口 RT 变长的问题。那么有哪些方案可以解决呢? 1、最容易能想到的,就是控制日志的输出量,把一些没用的日志干脆就不要输出了,你输出的日志应该是能帮助你做监控、告警,以及问题排查的,如果没有用,就干脆不要输出了。 2、日志级别,这个也挺重要的,线上一般来说只需要输出 WARN 和 ERROR 就够了,至于INFO 很多系统都不太关心,所以这个视情况而定,可以考虑通过提高日志级别来减少日志输出的量,但是这个需要注意,别为了减少日志量导致你日志缺失,影响问题排查,那就得不偿失了。 3、日志打印前,做级别检测,这个不展开说,下面这篇写的很清楚了。 ✅为什么logger.warn()之前要使用logger.isWarnEnabled()? 4、异步日志。建议大家都采用异步日志而不是同步日志,log4j、logback 等框架都是支持的。这样可以减少应用打印日志时候的阻塞。同时根据日志是否可都是可以配置neverBlock,neverBlock=true 时,会在队列满了之后丢弃日志,避免阻塞。根据你的实际情况选择使用。 5、日志文件的滚动,这个也需要注意,通过配置rollingPolicy,来限制一个日志文件的大小,并且让他在"满了"之后可以创建出新的日志文件的。 6、日志文件拆分,可以不要把日志都打在同一个日志文件中,做一些拆分,业务日志、异常日志等可以配置不同的 logger,让他们打印到不同的文件中,以防止单个文件操作的互相阻塞。 7、日志的降级,在我司,很多业务,在大促的时候是可以开启日志降级的。有两种方案 ,一种是直接配置日志打印的比例,比如配置只输出10%的日志用于采样和监控。还有一种是是直接推送ERROR\WARN 等级别来运行时控制日志输出的级别,当然可以细粒度到不同的 logger 上,进行日志的降级。 以上,就是我能想到的一些关于日志优化的方案了。

March 22, 2026 · 1 min · santu

分布式系统,用户登录信息保存在服务器A上,服务器B如何获取到共享Session

典型回答 场景题场景题,其实拆解一下就是八股文,这个问题归根结底,问你的就是如何实现分布式Session。 ✅怎么实现分布式Session? 在上面这篇文章中我们介绍了很多分布式 Session的方案,其中最常用的就是用 Redis 来保存了。 其实就是把用户的登录信息保存在 Redis 中,然后 A 服务器和 B 服务器都从这个 Redis 中读取 Session。主要的流程如下: 除了用 Redis 以外,还可以用其他的第三方存储,比如MySQL,mongodb,等等的都可以的。 除了用 Redis 以外,还有一些其他方案,在上面的文章中都提到了,就不展开说了,如客户端存储、粘性 Session、Session 复制等等

March 22, 2026 · 1 min · santu

实现一个登录拉黑功能,实现拉黑用户和把已经登陆用户踢下线。

典型回答 这个问题,主要分两部分,一部分是拉黑,一部分是踢下线。分开讨论。 拉黑 用户的拉黑,可以看是哪种拉黑方式,如果是单个用户的拉黑,可以提供一个后台接口,在管理端,通过输入用户的手机号,或者用户 ID 进行拉黑,如果是批量的,那么就需要提供一个页面,支持批量上传用户列表。 其次就是拉黑功能的实现,有3种方式,第一种是加黑名单,第二种是更新用户的状态,第三种是在用户表上打标。 第一种是不修改用户表的方案,即我们单独有一张黑名单表,这里记录了用户的黑名单的列表,这么做的好处是可以和用户表解耦,互相不干预,而且还有个好处,就是可以做很多其他的事情,比如说拉黑开始时间、拉黑结束时间等等的各种控制。 第二种和第三种方案本质上是一样的,就是在用户表上有个字段表示这个用户的被拉黑了。 **如果用户量比较大,或者是拉黑的用户比较多,建议用第一种方案,而且这个名单还可以前置给到其他的业务一起用,比如风控。**如果用户量不大的话,没必要搞这个黑名单表,用户表加一个状态或者字段就行了。 然后为了提升性能,针对用户的黑名单,还可以做缓存,将黑名单用户缓存在 Redis 或者本地缓存中,可以快速的针对黑用户进行拦截,而且黑名单非常适合使用布隆过滤器!如果量很大,可以进一步的用布隆过滤器来做缓存。 ✅什么是布隆过滤器,实现原理是什么? 踢人下线 除了拉黑外,还有一个功能就是踢人下线,这个功能其实也简单,只需要我们把用户登录后的 Session 给他清空就行了。这样用户在下次访问我们的系统的时候,因为查不到 Session,就是被强制下线了。 至于实现方式,要看 Session 存在哪,比如 Redis 的话,那么就找到这个用户的 ID,然后把他对应的 Session 给他清空即可。 如果用的是那种单点登录的框架,很多都是支持这些功能的,可能你只需要调一个方法,就可以直接一键踢人下线了。如:https://sa-token.cc/doc.html#/use/kick

March 22, 2026 · 1 min · santu

调用第三方接口支付时,第三方接口显示支付成功,但是在调用方显示支付失败,问题可能出在哪里

典型回答 这是一个典型的系统间交互数据不一致的问题,这个问题没问怎么解决,问的是可能的原因有哪些,那就考察你对这个链路的理解。一般来说,出现这种情况,可能的原因有以下几个: 当服务端调用第三方支付接口时,第三方接口显示支付成功,而在应用程序(app)端显示支付失败,这种情况可能由多个原因造成。以下是一些常见的问题及其解决方法: 网络问题 其实,还有一个比较重要的原因,也是发生最多的原因,那就是网络问题,比如网络抖动。当我们调用第三方支付的时候,因为发生了网络延迟,导致我们调用超时了,我们以为失败了,就把状态改成支付失败了。 但是,网络超时了,并不代表外部支付就没接到请求,有可能他已经接到了,那么他就会正常处理了,他处理完了,就成功了。 这时候就是我们失败了,他成功了。 所以,遇到这种网络问题的时候,需要有重试机制,或者是依赖外部的回调结果进行更新处理 异步处理 对于很多第三方支付接口中,他们的处理可能是异步的,交互的方式是他接受到请求后,然后异步处理,处理完之后再通过回调、或者 webhook 的方式通知到调用方。 那么,就可能出现一种情况,外部系统他们异步处理成功了,然后还没来及回调,或者回调正在处理中,这时候在调用方可能就会有状态延迟。 但是一般这种延迟的时候,都会显示支付中,而不是支付失败,但是有些系统做的不够好的, 也可能会显示成支付失败。 回调处理失败 接着前面的情况讲,当异步回调处理过程中,因为系统原因导致失败了,那么就可能会出现不一致的情况,或者是极端情况,回调的时候,正好应用在重启, 或者挂了,消息没处理,也会出现这种情况。 第三方支付接口问题 还有一种是可能,虽然发生的概率不高,那就是第三方支付接口可能在处理支付结果时存在问题,导致返回的信息与实际支付状态不一致。 因为只要是系统,他就有可能会出现问题。

March 22, 2026 · 1 min · santu

a,b 的联合索引,select b where a = xx,无法走索引覆盖什么原因?

典型回答 如果有一个 a,b 两个字段的联合索引,然后用 a 查询 b,正常来讲是可以走到索引覆盖的,那么如果没走到,可能的原因有以下几个: 1、索引中,a 并不在最左边,有可能是 b,a 这样的顺序,那么按照 a 查询,就会因为未遵循最左前缀匹配而无法直接用到索引查询。 2、如果 a 在最左边,但是因为用到了函数、或者是类型不一致,比如 a 是 varchar 类型,但是查询的字段是 int 类型,那么就会导致索引失效,这时候也无法进行索引覆盖了。 3、有长事务在操作同一条记录可能会无法走索引覆盖。在下面这篇文章中,我们介绍过,二级索引页有一个 PAGE_MAX_TRX_ID,如果他太新,或者如果二级索引中的记录被删除标记, InnoDB可能需要使用聚集索引来查找记录。详细的内容见: ✅二级索引在索引覆盖时如何使用MVCC?

March 22, 2026 · 1 min · santu

读取一千个文件,一个线程读取和开十个线程读取,哪种方式效率高?

典型回答 这个问题,是一个典型的多线程和单线程的选择的问题。还涉及到一些操作系统资源(I/O 密集、CPU 密集)相关的知识。 先来说结论:从磁盘上进行文件读取,是一种典型的 I/O密集型操作,这种操作的大部分耗时都在 I/O 上,那么可以利用多线程来同时执行任务,从而提高整体效率。 ✅什么是 IO 密集,什么是 CPU 密集? 单线程读取文件的优点是实现简单,没有线程之间的竞争和同步的问题。还有就是单线程不涉及到多个线程的创建和开销,也不需要进行上下文的切换,所以这部分的开销比较少。 但是单线程的缺点也比较明显,那就是只有一个线程处理所有文件读取任务,因此所有操作都是串行的。而文件读取操作又是I/O密集型操作,可能会导致较长的等待时间。 多线程的优点是可以同时进行多个文件的读取操作,特别是在I/O密集型任务中,多个线程可以更好地利用系统的CPU资源。 但是多线程也不是毫无缺点,主要有几个,其实就是单线程的那几个优点,一个是多个线程的创建、调度、销毁都是额外的开销,并且需要考虑线程安全问题,以及频繁的上下文切换会带来额外的开销。 但是,即使这样,多个线程的执行也会更快一些,一方面多线程的开销销毁这部分开销很小,可以忽略不计,线程安全的问题可以考虑通过分片的方式,让多个线程天然就处理不同的文件,互相不冲突即可。

March 22, 2026 · 1 min · santu

不用大于号小于号怎么判断两个正整数大小?

可以用其他方法来判断两个正整数的大小,而不使用大于号(>)和小于号(<)。以下是几种方法: 使用减法: 这个是最容易想到的办法了,通过减法的结果是否大于0,可以判断两个数的大小。 如果 (a - b > 0),则 (a > b)。 如果 (b - a > 0),则 (b > a)。 如果 (a - b = 0),则 (a = b)。 但是这个方案显然不能让面试官满意,那么还有一种用加法的方案, 使用加法: 因为说了 (a) 和 (b) 是两个正整数,你可以比较它们的和和其中一个数的两倍: 如果 (a + b > 2a),则 (b > a)。 如果 (a + b > 2b),则 (a > b)。 如果 (a + b = 2a),则 (b = a)。 这个办法是想一想还是可以想到的,类似的还可以用取模、除法等其他数学运算来实现。 使用位运算 还有一种办法,那就是利用差值的符号位来判断两个数的大小关系。基本思路是计算两个数的差值,然后通过分析差值的符号位来确定两个数的大小。 计算差值 diff = a - b。 通过差值的符号位来判断: 如果 diff 为正,则 a > b。 如果 diff 为负,则 b > a。 如果 diff 为零,则 a == b。 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 public class CompareUsingBitwise { public static String compare(int a, int b) { int diff = a - b; // 获取符号位 int sign = (diff >> 31) & 1; // 处理符号位 if (sign == 0) { if (diff == 0) { return "a == b"; } else { return "a > b"; } } else { return "b > a"; } } public static void main(String[] args) { System.out.println(compare(5, 3)); // 输出: "a > b" System.out.println(compare(3, 5)); // 输出: "b > a" System.out.println(compare(5, 5)); // 输出: "a == b" } } diff >> 31 右移 31 位,将差值的符号位移到最低位(最右边),这样可以得到符号位。 & 1 用来提取符号位。符号位为 0 表示 diff 为非负(即 a >= b),为 1 表示 diff 为负(即 a < b)。 如果 diff 为 0,说明 a 和 b 相等。 如果面试官还是不满意,那么也可以回答以下方法(并不常见,纯属是硬要回答。。。): ...

March 22, 2026 · 2 min · santu

把商品加入购物车时断网了,该怎么在重新联网时同步?

典型回答 这也是一个比较典型的场景题,在用户尝试将商品加入购物车时,若设备断网,需要一个机制来确保在重新联网后,购物车中的商品能够正确地同步到服务器。 想要实现这个功能,需要有以下几方面的机制: 客户端: 本地缓存: 当断网时,将商品加入购物车的请求缓存在本地。 网络状态检测: 定期检测网络状态,以便在恢复网络连接时触发同步操作。 重试机制: 需要有一个重试机制以确保请求能够成功发送。 服务端: 幂等性: 确保服务器处理请求时是幂等的,即相同的请求多次发送不会导致副作用。 本地缓存 要实现客户端的本地缓存,可以使用本地数据库(如 SQLite)或文件存储来缓存购物车操作请求。 SQLite是一个进程内的库,实现了自给自足的、无服务器的、零配置的、事务性的SQL 数据库引擎。 同时,需要设计一个数据结构来存储操作请求,包括商品ID、数量、操作类型(加入购物车、删除商品等)和时间戳等信息。 网络状态检测 使用系统提供的网络状态监听 API(例如,Android 的 ConnectivityManager)来检测网络连接的状态变化。当网络状态变为可用时,触发同步机制,将本地缓存的请求发送到服务器。 重试机制 这里可以考虑实现一个"指数退避"的重试策略,即每次重试的间隔时间逐渐增加,避免频繁请求导致的服务器压力。 指数退避算法是适用于网络应用的标准错误处理策略,使用这种策略时,客户端会定期重试失败的请求,并不断增加各次请求之间的延迟时间。客户端应对发送到 Memorystore for Redis 且返回 HTTP 5xx 和 429 响应代码错误的所有请求使用指数退避算法。 幂等控制 因为网络连接恢复以后会重试,所以这里就可能存在重复处理的问题。所以幂等性一定要考虑。 想要确保每个请求的处理是幂等的,通常通过使用唯一的请求 ID 来实现。例如,生成唯一的请求 ID 并在服务器端存储,避免重复处理相同请求。 ✅如何解决接口幂等的问题?

March 22, 2026 · 1 min · santu

留言给博主