记一次线上严重并发bug

简介: 记一次线上严重并发bug

记一次线上并发bug




前言


最近在写一个视频会议系统,我这边主要的一部分代码是RTC回调,还有一部分是客户端回调。在处理回调的时候都是按照同步的方式去进行的,本质上对于我来说不能有并发问题的,但是它确实发生了,那么我们一起来看下。


展示问题代码


下面这段代码主要含义就是当客户端开启共享屏幕或者视频的时候需要客户端回调给服务端RTC状态,服务端需要根据RTC状态计算最新视频画面排序规则。


// 1+6 1:主窗口一个 6 右侧小窗口数量
    public List<Integer> getTop7(String roomId, Integer mainN, Integer rightN) {
        // 【redis:zset】获取top7排序
        List<Integer> topList = getTopN(roomId, mainN, rightN);
        log.info("Top7返回的排序列表:"+topList);
        // 【redis:list】主要针对右侧小窗口缓存 减少相对调换次数
        List<Integer> lastList = getLastScreenSortList(roomId, "7");
        log.info("Top7最后一次排序:"+lastList);
        List<Integer> tmpLastList = new ArrayList<>(lastList);
        List<Integer> tmpTopList = new ArrayList<>(topList);
        // 纯算法计算稳定性排序 不涉及中间件存储和读取
        List<Integer> newList = updateLastScreenSortList(tmpLastList, tmpTopList);
        log.info("Top7当前最新排序:"+newList);
        // 【redis:list】删除最后一次排序key
        deleteLastScreenSortList(roomId);
        if (newList.size() > 0) {
            // 【redis:list】存储最新排序为最后一次排序key
            saveLastScreenSortList(roomId, newList, "7");
        }
        log.info("Top7当前最新排序:"+newList);
        return newList;
    }


大家可以看下,并发问题主要会出现在代码块哪里???



问题就是出现在如下代码块中:


// ---并发代码开始---
    deleteLastScreenSortList(roomId);
    if (newList.size() > 0) {
        // 【redis】存储最新排序为最后一次排序key
        saveLastScreenSortList(roomId, newList, "7");
    }
    // ---并发代码结束---
    // 保存最后一次排序列表
    public void saveLastScreenSortList(String roomId, List<Integer> userIds, String topN) {
        redisTemplate.opsForList().rightPushAll(LAST_SCREEN_SORT_KEY.setParamValue(topN, roomId), userIds);
    }


为什么会出现并发问题呢?就是当出现多个RTC回调同时走到并发代码段开始的时候,就会出现一个现象,这个现象就是:删删增增也就说多个请求会同时执行删除操作,然后同时走到存储部分。而存储部分是调用redis的list结构的rightPushAll方法,因此会push多份数据,那如果有大量上万级别回调同时进行呢?那就会导致数据在短时间内急剧膨胀,导致redis内存告警,当前服务和其他服务不可用。

这是非常严重的一个并发操作不当导致的线上问题,因此需要大家特别留心观察自己的代码,尤其是共享资源部分。


解决方案


  1. 可以用并发锁解决
  2. 可以用lua 脚本
  3. 可以用redis事务


这几种方案都可以,就看自己的场景适合哪一种,我们选择第二种方式,保证删除和新增都是原子操作。当然第三种也可以,只要保证删和增命令没问题就可以。至于第一种锁的粒度需要掌控好,同时也要保证锁的正确释放。

相关文章
|
Cloud Native 持续交付 测试技术
ALPD——驱动业务创新的精益产品开发
ALPD——驱动业务创新的精益产品开发
7262 0
ALPD——驱动业务创新的精益产品开发
|
索引 流计算 消息中间件
Flink 实时写入数据到 ElasticSearch 性能调优
线上业务反应使用 Flink 消费上游 kafka topic 里的轨迹数据出现 backpressure,数据积压严重。单次 bulk 的写入量为:3000/50mb/30s,并行度为 48。针对该问题,为了避免影响线上业务申请了一个与线上集群配置相同的 ES 集群。
|
SQL 关系型数据库 MySQL
案例剖析:MySQL唯一索引并发插入导致死锁!
案例剖析:MySQL唯一索引并发插入导致死锁!
1066 0
案例剖析:MySQL唯一索引并发插入导致死锁!
|
数据采集 数据安全/隐私保护 开发者
非阻塞 I/O:异步编程提升 Python 应用速度
非阻塞 I/O:异步编程提升 Python 应用速度
|
缓存 SpringCloudAlibaba NoSQL
Spring Boot多级缓存实现方案
整合redis和caffeine实现多级缓存,解决上面单一缓存的痛点,从而做到相互补足
1417 0
|
存储 安全 算法
代码混淆和加固,保障应用程序的安全性
代码混淆是将源代码进行加密和优化,使得反编译者难以理解和还原源代码的过程。通过替换变量名、类名等信息为无意义的字符,代码混淆使得反编译后的代码难以理解和维护,从而提高了应用程序的安全性。 代码加固是对已经混淆的代码进行二次保护,防止破解者通过静态或动态分析手段获取到关键算法和逻辑。代码加固可以添加额外的安全层,包括加密、反调试、反动态调试、反内存dump等,从而增强应用程序的抗攻击能力,以IPA Guard为例,。
|
消息中间件 存储 监控
消息队列原理和选型:Kafka、RocketMQ 、RabbitMQ 和 ActiveMQ
常用的消息队列主要这 4 种,分别为 Kafka、RabbitMQ、RocketMQ 和 ActiveMQ,主要介绍前三,不BB,上思维导图!
4088 0
消息队列原理和选型:Kafka、RocketMQ 、RabbitMQ 和 ActiveMQ
|
JSON NoSQL Java
SpringDataRedis 操作 Redis,并指定数据序列化器
SpringDataRedis 操作 Redis,并指定数据序列化器
543 1
|
关系型数据库 MySQL 测试技术
MySQL 并发插入唯一键相邻数据和更新数据导致死锁
一 前言死锁其实是一个很有意思也很有挑战的技术问题,大概每个DBA和部分开发朋友都会在工作过程中遇见。关于死锁我会持续写一个系列的案例分析,希望能够对想了解死锁的朋友有所帮助。二 案例分析2.1 业务场景业务上的主要逻辑:首先执行插入数据,如果插入成功,则提交。如果插入的时候报唯一键冲突,则执行更新...
678 0
MySQL 并发插入唯一键相邻数据和更新数据导致死锁
|
Go
Go语言导出包解密:外部访问你的类型和值
Go语言导出包解密:外部访问你的类型和值
327 0

热门文章

最新文章