记一次线上严重并发bug

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
简介: 记一次线上严重并发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事务


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

相关文章
|
测试技术 5G iOS开发
【游戏专项测试】弱网测试-测试用例
【游戏专项测试】弱网测试-测试用例
|
11月前
|
数据采集 数据安全/隐私保护 开发者
非阻塞 I/O:异步编程提升 Python 应用速度
非阻塞 I/O:异步编程提升 Python 应用速度
|
关系型数据库 MySQL
MySQL自增ID用完会怎样?
MySQL自增ID用完会怎样?
|
存储 缓存 NoSQL
Redis常见面试题(二):redis分布式锁、redisson、主从一致性、Redlock红锁;Redis集群、主从复制,哨兵模式,分片集群;Redis为什么这么快,I/O多路复用模型
redis分布式锁、redisson、可重入、主从一致性、WatchDog、Redlock红锁、zookeeper;Redis集群、主从复制,全量同步、增量同步;哨兵,分片集群,Redis为什么这么快,I/O多路复用模型——用户空间和内核空间、阻塞IO、非阻塞IO、IO多路复用,Redis网络模型
Redis常见面试题(二):redis分布式锁、redisson、主从一致性、Redlock红锁;Redis集群、主从复制,哨兵模式,分片集群;Redis为什么这么快,I/O多路复用模型
|
12月前
IDEA创建项目失败提示 Failed to create directory 或 “项目初始化失败”
文章解释了IDEA创建项目失败提示“Failed to create directory”或“项目初始化失败”的原因通常是由于IDEA对目标文件夹没有操作权限,并建议更换一个有权限的文件夹来创建项目。
1368 2
IDEA创建项目失败提示 Failed to create directory 或 “项目初始化失败”
|
设计模式 人工智能 算法
策略模式的应用场景有哪些?
【7月更文挑战第2天】策略模式的应用场景有哪些?
631 1
|
Java
Java 中 sleep 和 wait 之间的区别?
【8月更文挑战第21天】
1349 0
|
编译器 C语言 C++
如何解决VS中scanf使用时报错或无法使用的问题
如何解决VS中scanf使用时报错或无法使用的问题
412 0
|
Java 数据安全/隐私保护 Sentinel
微服务学习 | Spring Cloud 中使用 Sentinel 实现服务限流
微服务学习 | Spring Cloud 中使用 Sentinel 实现服务限流
|
分布式计算 算法 搜索推荐
老程序员分享:Mahout介绍和简单应用
老程序员分享:Mahout介绍和简单应用