Skip to content

客诉项目面试准备_第二版

项目内容问题

  1. 你们这个项目多少人在做?人员分布是怎样的?做了多长时间?

答:整个服务有 100 人左右,我们组负责的平台 5 个后端 3 个前端,迭代了有 5 年了,我过去主要负责服务的改造,还有一些日常需求开发问题定位和优化

  1. 你们项目一共有几个端? 每个端都是哪些人在用? 作用是什么?

答:有哪些模块?pc,app。司机货主,客服,作用是给司机和货主提供发货和接货的需求。

催收,申请单,客诉单,申诉单

  1. 你们项目一共有几个微服务? 每个微服务负责的任务是什么?

答:交易(处理退钱和收钱),运单(物流的跟踪),货运(货源之类的),用户中心(对用户的分析),风控(风险用户判断),客诉,工单(申请单审批已经生成一些模板),履约平台(给用户网贷,推广一些产品)

  1. 你们项目一共有几个数据库? 你负责的数据库是哪个? 核心表是哪几张? 表中有哪些字段?

答 1 个数据库(100 多张表),1 主 2 从,都负责,核心表有 3 张,客诉单表,催收单表,申请单表

有几个分支,大家都怎么一起开发的

  • dev 测试 qa 模拟生产 master 生产
  • 组员5个人,先同时在master克隆一个分支自己命名,然后在自己分支上开发需求,开发完成后集成到dev分支进行提测,测试测dev分支,如果测试没问题
  • 将自己分支集成到qa分支进行发布预生产,测试再进行功能测试,如果没问题集成到master分支进行发布生产,生产环境灰度发布,先切百分之5流量,再切百分之20,再切百分之50再切100%,如果没异常完成发布,有异常回滚。

讲述一下你项目觉得亮点的地方

当时随着业务的迭代,当时的客诉服务进行了服务拆分。

当时的一个做法是先将涉及业务量不是很大的催收单服务进行了拆分,确保没问题后,进行逐步拆分。

这个过程中需要注意业务边界,同时尽量遵从单一职责,当时是一边画流程图,一边进行逐步拆分,同时保证测试没问题后,保留新老两套服务,同时在配置中心进行动态配置,进行切换服务。

另外,在进行拆分客诉服务的时候,对于当时的部门代码进行了部分优化,将客诉创建这个流程:主要包括:客诉单创建、运单冻结、申请单模板生成,进行一定的抽象封装,使用模板设计模式,将这个步骤抽离了出来,具体的方法由子类去实现,同时针对不同的客诉类型下的申请单模板,采用策略的方式进行不同的实现。

讲下当时你们遇到的 JVM 调优问题

当时的现象是有时候会收到报警邮件,显示有些接口调用时间比较长。

当时首先是抽查了部分接口,发现大部门是调用下游的接口会比较慢,没有太在意。持续观察后,发现在固定的时间 10 点左右,会出现 tp99 耗时升高的情况。而这个时候,是我们服务的一个业务高峰,当时是通过排查发现内存使用率也会增大,然后再释放,其他各项指标正常,于是怀疑是GC导致的。当时查看了一下容器的 Full GC 情况,发现的确有些频率,于是定位到这个大概就是 Full GC 问题。

当时先看了一下选择的垃圾收集器,是默认的 JDK8 的 Parallel、Parallel Old。根据监控看到的现象,是观察到 老年代的内存占用频繁到达 90%,排查大概是老年代空间不足,导致频繁 Full GC 的现象。

当时是一方面监控观察堆的内存变化,发现是 Survivor区域大小一直在变化,而且基本一致保持在50%以上;当时是查阅资料是发现 JDK8 默认是会自动打开UseAdaptiveSizePolicy(动态调整);导致的一个现象的 Survivor区域内存区域自动调整,导致Survivor 区空间不足以容纳所有存活对象,导致这些对象不得不晋升到老年代。

因此当时首先的行为的关闭了这个动态调整策略,同时另一方面将堆的内存进行了调整,同时换了一个低延迟的收集器。低延迟的组合,选择 ParNew 与 CMS 的组台。

线程池参数设置

  • 设置线程池核心参数,其实我们会考虑一下CPU利用率的使用
  • 一般会有两种现象,如果执行非阻塞计算型操作的时候,单个核心CPU的利用率是比较高的,这个时候,如果设置核心线程数大于核数,会发现频繁的不必要的线程切换,会造成负载过高,执行的反而会更慢了
  • 如果是执行IO阻塞类的线程,CPU 处于空闲状态的时候,系统调用执行其他线程的时候,反而可以提高CPU利用率,这个还是需要看具体的一个IO频率和等待时间
  • 一般来说,我们设置CPU核心可以参考看一下我们的CPU核数,以及CPU利用率,再看一下等待时间和运算时间的比例,大概关系是 核心线程数 = CPU核数 * 目标CPU利用率 * (1 + 等待时间/运算时间)
  • 但实际上设置是没有标准的所谓公式的,很多时候,如果对于性能要求不高,我们设置核心线程数为核数就可以了,追求性能的话就需要不断结合实际情况进行压测看实际情况。

分布式事务问题

RocketMQ 的半消息二阶段提交

发送半消息: SendResult sendResult = producer.sendMessageInTransaction(msg, null);

事务监听器

  • executeLocalTransaction:用于执行本地事务逻辑。根据本地事务的执行结果,返回 COMMIT_MESSAGEROLLBACK_MESSAGEUNKNOW
    • 最后一步一般会有 transaction_log 事务表消息记录操作
  • checkLocalTransaction:用于检查本地事务状态。当 RocketMQ 无法确认事务消息状态时,会回调此方法进行状态检查。

即只有当业务操作成功完成时,相关的消息才会被发送到消息队列供消费者处理。这对于需要强一致性的业务流程至关重要

分布式锁问题

Redisson 的可重入锁结构:Hash

  • Redis 键redisson_lock:{lockName},其中 {lockName} 是锁的名称。
  • 哈希表字段
    • UUID:threadId:值为线程持有锁的重入次数。
    • __timeout__:值为锁的过期时间戳(以毫秒为单位)

看门狗机制

  • 租期

  • 自动续期(租期的 1/3 的时候,会自动续期,直到显示释放)

  • 当使用 Redisson 的锁机制(如 RLock)锁定某个资源时,如果没有显式地设置锁的租期(lease time),Redisson 会启用“看门狗”机制。这个机制的核心是自动续租,确保在持有锁的服务实例正常运行时,锁不会因为超时而被自动释放

    • 默认租期设置:默认情况下,锁的租期设置为 30 秒。这意味着,如果在这段时间内没有操作完成,锁就会自动释放,防止死锁。
    • 自动续租:“看门狗”机制会在锁快到期时(默认情况下是租期的 1/3,即10秒后),自动发送一个命令给 Redis,将锁的租期重新延长到 30 秒。这个过程会一直持续,直到锁被显式释放。
    • 故障转移:如果持有锁的服务实例因为某些原因宕机或无法与 Redis 通信,锁将不会被续租,最终会在默认的租期结束后自动释放。这样,其他服务实例可以获取锁,继续处理任务,保证了系统的高可用性和一致性。

水平分表问题

当时采用的策略是:

哈希分表:根据分表键的哈希值将数据分散到不同的表中。哈希分表可以使数据均匀分布,适用于分表键的取值较为随机的场景。

-- 插入数据时计算哈希值决定插入哪个表,这里可以讲是客诉单ID
INSERT INTO user_{user_id % 10} (id, name, ...) VALUES (...);

表的 ID 策略

  • 美团 leaf 算法,调用的已经提供号的一个服务操作,号段模式
  • Leaf 算法一般有两种模式,一种是 号段模式,一种是 基于雪花算法的Snowflake 模式(Snowflake Mode)
  • 号段模式是基于数据库的,在数据库中会维护一个计数器表,用于记录当前分配的最大 ID 值
    • 应用实例通过数据库自增字段获取一个新的号段,并记录该号段的开始和结束位置。
    • 在本地维护当前号段的状态,直到该号段用完为止。
  • Snowflake 模式基于 Twitter 的 Snowflake 算法,生成按时间有序递增的 64 位唯一 ID,包含时间戳、机器 ID 和序列号部分。

参考: https://pdai.tech/md/arch/arch-z-id.html

在类 snowflake算法上都存在时钟回拨的问题,Leaf-snowflake在解决时钟回拨的问题上是通过校验自身系统时间与 leaf_forever/${self}节点记录时间做比较然后启动报警的措施。

redis 内存溢出问题

测试反馈客诉线上运营的资源没展示->查看代码逻辑

  • 1、查看链路追踪观测下游接口调用正常
  • 2、查看了服务日志
  • 3、发现是接口调用后查询存入缓存后的问题
  • 4、定位是历史遗留问题,因为之前的 redis 结构设计不当,没有设置过期时间,导致存放了大量的历史数据在 redis 中,导致 大 key 问题

当时的问题

  • 1、查看配置文件内存溢出策略,发现设置的是禁止写入,由于是线上,内存淘汰策略不好修改,因此是先解决大 key 问题
  • 2、当时采用的方案是:对于hash,使用 hscan 扫描法查找最开始一个月的数据,通过 使用 HDEL 命令删除符合条件的字段,观察线上问题是否解决,解决后,再批次进行删除的操作
  • 3、后续做法:更改了内存淘汰策略,更改了程序代码,添加了过期时间,添加了对应 大 key 的日常监控。

业务

客诉单 →