场景题大全

电商平台中订单未支付过期如何实现自动关单?

定时任务

适用场景:订单取消操作复杂,需要分布式支持。

优点:

  • 成熟的调度框架支持复杂任务调度。
  • 灵活性高,支持分布式扩展。

缺点:

  • 对实时性支持有限。
  • 框架本身较复杂。
延迟消息队列

在订单创建时,发送一个消息到延迟队列(延迟消息),延迟队列的时间为支付超时时间。当消息被消费时,如果订单仍未支付,则关闭订单。

适用场景:适用高并发系统,实时性要求高。

优点:

  • 消息队列支持分布式,高并发下表现优秀。
  • 数据可靠性高,不容易丢消息。

缺点:

  • 引入消息队列增加了系统复杂性。
  • 需要处理队列堆积的问题。
RedisKey过期监听

在创建订单时,写入一个定时过期的key,当key过期时,监听到redis的keyevent 事件通知,进行后续处理。当前这种方式不能作为唯一,还需在重启服务时做一次扫库,避免因发版本等异常情况而导致未监听到key过期。

适用场景:对超时事件实时性要求高,并且希望依赖 Redis 本身的特性实现简单的任务调度。

优点:

  • 实现简单,直接利用 Redis 的过期机制。
  • 实时性高,过期事件触发后立即响应。

缺点:

  • 依赖 Redis 的事件通知功能,需要开启 notify-keyspace-events 配置。
  • 如果 Redis 中大量使用过期 Key,可能导致性能问题。

如何在100亿数据中找到中位数

如果内存足够,直接在内存中进行快排即可。

如果内存不够,可以用桶排序。这个需要先知道数据的范围,例如数据是1~100之间。可以创建十个桶,1到10、11到20等等

在这里插入图片描述

1)创建多个小文件桶,设定每个桶的取值范围,然后把海量数据元素根据数值分配到对应的桶中,并记录桶中元素的个数

2)根据桶中元素的个数,计算出中位数所在的桶(比如 100 亿个数据,第 1 个桶到第 18 个桶一共有 49 亿个数据,第 19 个桶有 2 亿数据,那么中位数一定在第 19 个桶中),然后针对该桶进行排序,就可以求出海量数据中位数的值(如果内存还是不够,可以继续对这个桶进行拆分;或者直接用 BitMap 来排序)

参考:
https://www.cnblogs.com/myf008/p/18323329

如果系统QPS突然提升10倍该怎么处理?

预防措施
  • 性能监控:首先,建立完善的性能监控体系,实时监控系统额度QPS、CPU、内存、磁盘IO等指标,及时发现和预警性能问题。
  • 负载均衡:使用负载均衡中间件(如Nginx)将流量分布到多个节点上,防止某个节点成为瓶颈。
  • 部署备用节点:流量突增假如伴随着事件,在事件发布之前做好备用节点,使用zk或者redis的心跳过期监听来启动备用节点,以防节点挂了一个之后导致其他节点成为瓶颈,也有可能会跟着挂掉
  • 自动扩容:可以监控核心参数,例如CPU使用率,高于30%自动扩容等。
突发流量处理
  • 系统扩容:在突发流量到来时,可通过水平扩展快速提升系统容量:自动扩展:使用容器编排工具(如k8s)配置自动扩展策略,根据流量自动增加或减少服务实例数量。
  • 缓存策略:合理使用缓存,能大幅度提升系统性能,减少数据库压力:本地缓存:可以使用guava或caffeine等本地缓存库缓存热点数据。分布式缓存:使用Redis缓存高频访问的数据。
  • 限流与降级:限流:使用限流算法(如令牌桶、漏桶)限制每秒请求数,保护系统免受流量冲击。降级:对非核心功能进行降级处理,确保核心功能的稳定性

原文链接:https://blog.youkuaiyun.com/qq_35551875/article/details/140842405

实现一个分布式锁需要注意什么

使用什么介质存储

可以使用内存存储,对缓存来说非常快。

过期策略
  • 定时删除,定时扫描哪些过期了。
  • 惰性删除,下次访问时发现过期就删除。
  • 到时间点删除,即定义一个监控线程,到点删除。
自动延期策略

可以定义一个 TimerTask 的定时异步任务,它继承了Runnable,run里的任务是刷新过期时间。
定义一个 Timer,指定什么时候去执行 TimerTask 这个任务,例如每隔1/5个过期时间执行一次,就可以实现自动延期。

可重入策略

针对每个加锁的地方,需要记录它的id,并且每加一次锁,在redis中对他进行++,每次解锁对他进行–,以此来记录它的重入次数,实现可重入。

记录它的id可以用这样的方式:代码里会有 RedisLock lock = getLock() 的这样的操作,即:针对每个key需要加一个Lock对象,这个对象在生成时会生成自己的id,当做rediskey里的value,生成id的规则:应用id + 本地机器的ip +进程id + 线程id

为了优化它的速度,可以在 threadLocal 里记录它的可重入次数,同线程内的可重入性用 threadLocal 保证就可以了。

当然这两种方法都无法避免这样的场景:在 主线程thread 里对 key1 加锁,但是 主线程thread 又启动一个子线程 thread2,又在thread2 里对 key1 进行加锁,就会进入死锁。所以不能在子线程里对同一个key加锁。

分布式锁加锁失败后的等待处理策略
  • 使用信号量Semaphore,分布式环境下需要使用 DistSemaphore,信号量Semaphore可以实现阻塞和被唤醒。
  • 使用轮询,不停轮询,这样实现简单,但是对资源消耗比较高。
  • 使用消息通知,释放锁时唤醒其他机器。

什么是信号量?

信号量(Semaphore)是一种计数器,用于控制对共享资源的访问。它维护了一组许可(permits),线程在访问资源之前必须先获取许可,访问完成后释放许可。如果许可数为 0,线程会被阻塞,直到有其他线程释放许可。

信号量的核心操作包括:

  • acquire():获取一个许可,如果许可数为 0,则线程阻塞。
  • tryAcquire(long timeout, TimeUnit unit):获取一个许可,如果许可数为 0,则线程阻塞。如果超时则返回false。
  • release():释放一个许可,唤醒等待的线程。

在分布式环境中,多个节点(JVM 进程)之间需要共享信号量状态,因此需要使用分布式信号量(DistSemaphore)。分布式信号量通常基于分布式协调服务(如 ZooKeeper、Redis、etcd)实现。

举一个分布式信号量的实现的例子:

import org.redisson.Redisson;
import org.redisson.api.RSemaphore;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;

public class DistSemaphoreExample {
    public static void main(String[] args) throws InterruptedException {
        // 1. 配置 Redisson 客户端
        Config config = new Config();
        config.useSingleServer().setAddress("redis://127.0.0.1:6379");

        // 2. 创建 Redisson 客户端
        RedissonClient redisson = Redisson.create(config);

        // 3. 获取分布式信号量
        RSemaphore semaphore = redisson.getSemaphore("mySemaphore");
        semaphore.trySetPermits(1); // 设置信号量的许可数为 3

        // 4. 申请资源
        semaphore.acquire(); // 阻塞直到获取许可
        System.out.println("Resource acquired!");

        // 5. 释放资源
        semaphore.release();
        System.out.println("Resource released!");

        // 6. 关闭 Redisson 客户端
        redisson.shutdown();
    }
}

我们可以使用信号量来完成重试,如果获取失败,尝试获取信号量,获取不到信号量,线程会阻塞住,等待持有锁的线程释放信号量,释放信号量时就会唤醒之前获取锁失败的线程。

集群策略,主从同步策略

保证集群的高可用性,主机器坏了可以立马让从机器接替。

数据分片

通过一致性哈希算法,分配缓存键到不用的节点,实现集群的分片和扩张

并发处理策略
  • 单线程处理,可以避免并发问题,并且避免频繁的上下文切换,但是不能利用多核机器的资源
  • 多程程处理,可以使用版本号机制,可以充分利用多核,缺点是实现复杂,切自旋效率低

接口慢如何排查

排查方向:

  1. 数据库:是不是有慢SQL,数据库抖动等等
  2. 外部接口:下游接口响应慢
  3. 代码问题:代码里有循环、递归等等
  4. JVM:是不是频繁GC,导致GC线程暂停了业务线程
  5. 缓存问题:本地缓存失效,访问外部资源慢
  6. 网络问题:使用ping、traceroute或telnet等工具检查网络连通性和延迟。
  7. 机器问题:CPU、内存、磁盘I/O、网络等是否正常。
  8. 线程池:是不是线程池设置不对,比如队列太长,用不到最大线程数

排查方式:

  • 日志:查看日志排查
  • 机器监控:查看机器性能
  • 告警:查看告警

峰值100万QPS的缓存系统如何设计

多级缓存设计
  • 本地缓存:拉取Redis缓存,这里需要注意有个缓存一致性的问题。
  • Redis缓存:由一个单独的写入系统,将数据库的数据写入Redis缓存(可以是更新时写入,也可以是定时轮询),读取由Redis缓存提供,降低数据库压力。
  • 业务上的性能优化:根据大部分请求的规则,将多个缓存聚合在一起返回等等。

分布式缓存数据量太大怎么处理

  • 数据压缩:设计一套压缩算法,将业务数据进行压缩,例如boolean类型转为一个byte,字符转如果是枚举,转为枚举等等。
  • Redis数据压缩:Redis本身也支持数据压缩。
  • 数据分片:通过一致性哈希算法,对数据进行分片。增加更多的缓存节点,通过水平扩展来分担数据存储压力。
  • 优化缓存的过期和清理策略:使用合适的缓存淘汰策略(如LRU、LFU)来自动移除不常用的数据。设置合理的过期时间来确保过期的数据及时被清除。

交易系统中如何防止资损的问题

  • 幂等性设计,确保资金相关的操作(如支付、退款、转账)是幂等的,即同一请求多次执行的结果与一次执行的结果相同。例如记录退款通知的主键,根据主键做幂等。
  • 事务管理,当涉及到多张表的操作时,需要考虑使用事务操作,保证原子性。如果是单机,使用本地事务就可以;如果是多个应用,就需要考虑分布式事务。
  • 定时对账:定期与银行、第三方支付平台对账,确保系统数据与外部数据一致。
  • 实时对比:通过监控或者是JOB,隔10分钟扫描一次数据,对比自己数据库和外部交易系统的查询接口,保证资金的一致性。
  • 审计系统:记录每一条资金变化的交易流水,保证可以追溯,并且通过审计系统(一套单独的校验逻辑,例如根据账户配置计算),保证每条交易流水的正确性。
  • 考虑并发问题,加分布式锁,防止并发问题。
  • 单元测试,编写各个交易场景的单元测试,防止计算有问题。
  • 监控告警,增加监控告警,防止没有长时间没有支付回调、支付失败、多次退款、退成负数、应实收不一致等等。
  • 业务逻辑保证不重复退款,例如保证取消交易只会有一次,这种业务逻辑上的校验,保证不会重复退。

高并发下如何保证库存扣减

分为两种,一种是直接对放在MySQL的库存操作,一种是将库存放在Redis缓存中,异步操作完成库存扣减。

有下面这几种方案:

  1. 加分布式的锁,例如Redis分布式锁,再对库存进行扣减。
  2. 直接数据库操作,乐观锁的方式,例如:update table set count = 1 where count>0;这样的方式,用where语句判断
  3. 直接数据库操作,加一个悲观锁,例如数据库本身的写锁, select * for update。
  4. 高并发的场景下,可以将库存放在Redis缓存中,对redis操作,后续对数据库的操作可以异步进行,这里需要考虑到缓存一致性、缓存的击穿、Redis操作的原子性等等。

缓存的一致性的话,我们会有一系列的缓存一致性的操作:

  1. 写操作时,先使缓存无效,更新数据库,再使缓存无效;读操作时,如果有缓存,直接读,无缓存时,读数据库,并更新缓存。
  2. 写操作时,先更新数据库,再更新缓存;读操作,直接读取。

缓存的击穿的话,需要采用降级和限流的策略:

  • 降级:如果缓存获取不到值,直接返回空,抛出异常,不要访问数据库。
  • 限流:对数据库的访问进行限流,使用令牌桶的策略等等。
  • 加锁:当获取不到缓存,需要查数据库并更新缓存时,需要加分布式的锁,再做操作,防止并发访问。

Redis操作的原子性:

  • 单个操作就用Redis的操作命令。
  • Lua脚本,多个操作可以通过Lua脚本来实现。Lua脚本中的多个命令会作为一个整体执行,不会被其他命令打断。
  • Redis事务,需要注意Redis事务并不是严格意义上的原子性,如果在EXEC执行前发生错误(如语法错误),整个事务会回滚;但如果是在EXEC执行期间发生错误(如类型错误),只有出错的命令会失败,其他命令仍会执行。
  • 加分布式锁之后再做操作。

分库分表的问题以及处理策略

  1. 数据分布不均,数据量增大时会有扩展性的问题;可以通过一致性哈希算法来解决。
  2. 没有办法支持跨库事务,跨库事务难以保证一致性。使用分布式事务框架(如Seata)或最终一致性方案。
  3. 跨库查询复杂且性能差。避免跨库查询,可以将跨库查询的操作转到其他类型的数据库,如hive、es、ck等等,虽然数据可能是T+1,有延迟性,但是支持大数据查询、跨库查询。
  4. 分库分表后,主键可能冲突。需要采用分布式的雪花算法提供唯一id。
  5. 数据迁移复杂且易出错。采用双写、数据同步工具(如Canal)逐步迁移。

如何设计一个秒杀系统

秒杀系统的话,需要考虑的就是并发量太大的问题。

可以有下面的一些思路:

  • 多级缓存:针对静态资源,需要做一个预加载,做成Redis缓存+本地缓存的多层缓存结构。
  • 热点数据分片:对于Redis缓存,需要考虑它的qps,太多的话,需要做数据分片,或者读取从库等,分摊它的压力。
  • 限流:针对高并发,可以做一个限流的策略,通过令牌桶或者漏桶,来限制机器的QPS,或者通过Redis做分布式限流。
  • 降级:考虑降级策略,例如手动关闭非核心功能,如评论、报销等等。
  • 异步:非核心功能做异步处理,例如推延迟Q处理退款、发送短信等等。
  • 熔断:考虑熔断机制,如果系统挂了,可以返回主页。
  • 防止库存超卖,可以参考上面的。
  • 考虑超时未支付关单,并且回滚库存。
  • 考虑防重或者幂等,防止一个客户多次、高频的请求。
  • 做好监控,例如下单成功率等等。
  • 提前做好压力测试,测试极端场景的熔断、降级、限流是否生效。

支付网关两次支付如何保证一致性

使用两阶段提交的思路:通过一个协调者(事务管理器)协调两个外部系统的操作。

  • 准备阶段:协调者询问两个外部系统是否准备好扣款。
  • 提交阶段:如果两个外部系统都准备好,协调者通知它们提交扣款;否则,回滚。

具体实现,可以使用分布式事务,使用seata的TCC模式,在try阶段进行 金额冻结,confirm阶段进行确认扣款,cancel阶段进行资源释放:例如解冻。

费用类数据如何保证数据一致性

  1. 高可靠的事件通知:操作通知可以采用QMQ的方式,保证通知不会丢失。
  2. 数据同步策略:通过监控定期比对两边数据差异。
  3. 操作的原子性:可以使用分布式事务来保证一次操作的原子性,例如一次退多个退款单。没有分布式事务的话,可以用数据库的状态字段保证,通过考虑极端场景的处理来保证幂等性、可靠性。
  4. 通过幂等、可重试等操作,保证最终一致性:可以通过补偿来保持数据一致,支持幂等和重试。

设计一个关注和粉丝的系统,要求可以查看关注列表和粉丝列表

这时候我们是需要用到MySQL来存储关联关系的数据的。

有两种思路:

  1. 建立两张表,一张关注表(uid,subscriberUid),一张粉丝表(uid,funUid)。
  2. 建立一张关联关系表 (uid,subscriberUid),就记录关联关系就好了。

假如数据量太大了怎么办?
需要做分库分表,例如用uid当键,去做分库分表。

MySQL数据库的表数据量过大,导致查询缓慢,怎么优化

  1. 加索引:如果表上索引不多,可以根据分辨度高的字段加索引,例如订单号、用户id等
  2. 分库分表:可以通过分库分表来降低单张表的数据量
  3. 增加缓存:对于频繁的查询可以增加redis缓存或者本地缓存,多级缓存的策略来优化查询速度
  4. 读写分离:写主库,读从库,读写分离降低读的压力。
  5. 过时数据迁移:定期将超过1年的数据迁移到备份库里。
  6. 大数据表查询:非实时的查询,可以用HIVE、CK等更适合报表查询的数据库查询。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值