V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
shuangdeyu
V2EX  ›  程序员

redis 并发下写入数据丢失

  •  
  •   shuangdeyu · 253 天前 · 2247 次点击
    这是一个创建于 253 天前的主题,其中的信息可能已经有所发展或是发生改变。

    不懂就问,使用 redis 的 list 类型做消息队列,通过这个队列将要写到 mysql 的数据延后批量写入

    遇到的问题是,使用 jmeter 测并发的时候发现,10000 线程的时候,写到 redis 的数据会丢失,而且丢失数量是不规则的; 5000 线程以内则一切正常,主要想知道这是什么原因?单机如何去优化?替代方案比如 MQ 暂时不考虑。

    使用语言是 Go,代码如下,其实也很简单,只是往 redis 队列中推入数据,用 Redis Manager 观察:

    func Testt(c *gin.Context) {
        // 这是测试数据,数据长度小得到的也是一样的结果
        //x := `{"Dateline":"2020-08-31 13:34:59","Error":200002,"Id":"ee0da19f-eb05-4728-8e23-107336043ede","Ip":"192.168.2.150","Method":"POST","Resp":"null"}`
    
        // 这是封装的 LPush 函数
        dbhelper.Lpush("testttt", "_api", "1")
    
        c.JSON(200, gin.H{
            "e":   0,
            "msg": "success",
        })
    }
    

    10000 线程的测试结果,只存进去 1542 个数据:

    5000 线程的测试结果,5000 个数据全部写进队列了:

    第 1 条附言  ·  252 天前

    感谢大家的回答,问题找到了,测试代码没有严谨对待,没有抛出错误信息,饶了弯路 =_= ,一直在钻redis配置的牛角尖,加上后抛出的错误是"connection pool timeout",然后修改代码,在建立连接客户端的时候在"redis.Options"中配置连接池大小等属性后问题解决,因为默认的"PoolSize"只有10,一个进程内10000的并发量自然是不够用了。

    配置连接数上也发现了问题,修改"redis.conf"连接数后发现数量怎么都不会增加,因为之前有解决mysql连接数问题的经验,很快就找到还有一个隐藏的"limit.conf"中一个"LimitNOFILE"参数限制了最大连接数,修改后连接数修改就能生效了。

    12 条回复    2020-09-01 10:01:05 +08:00
    useben
        1
    useben   253 天前
    查看下 redis 连接是否有错误, 调大连接数
    saturn7
        2
    saturn7   253 天前
    看 demo 代码象只是复用单实例连接到 Redis Server,典型的写 PHP 思维。解决要用连接池 + 并发锁。
    micean
        3
    micean   253 天前
    lpush 的结果不观察一下吗?
    JJstyle
        4
    JJstyle   253 天前
    @saturn7 楼主需要能并发插入数据,你给加一个并发锁,是要解决什么问题?
    fanpei0121
        5
    fanpei0121   253 天前
    经测试 并发 10 万条 redis 插入,没有漏数据,测试代码不要太在意细节。


    func Test(c *gin.Context) {
    // 这是测试数据,数据长度小得到的也是一样的结果
    x := `{"Dateline":"2020-08-31 13:34:59","Error":200002,"Id":"ee0da19f-eb05-4728-8e23-107336043ede","Ip":"192.168.2.150","Method":"POST","Resp":"null"}`

    // 这是封装的 LPush 函数
    rdb := redis.NewClient(&redis.Options{
    Addr: "localhost:6379",
    Password: "",
    DB: 0,
    })
    dataChan := make(chan string, 1000)

    go func() {
    for i := 1; i <= 100000; i++ {
    dataChan <- x
    }
    }()

    for j := 1; j < 20; j++ {
    go func() {
    for {
    data := <-dataChan
    err := rdb.RPush("test111", data).Err()
    logs.Error(err)
    }
    }()
    }

    c.JSON(200, gin.H{
    "e": 0,
    "msg": "success",
    })
    }
    huntcool001
        6
    huntcool001   253 天前
    没看明白. 你是怎么知道 lpush 结果正常的.. redis 返回值是什么, 异常有 catch 住打印没?


    另外,消息队列最好用 Redis Stream 来做.
    Citrus
        7
    Citrus   253 天前 via iPhone
    你写请求发出去就不管了是咋知道这成功了呢?
    90928yao
        8
    90928yao   253 天前
    打个 redis 返回啊。肯定很多报错 拿不到链接
    th00000
        9
    th00000   253 天前
    猜测一下: Redis 的写入是单线程的, 假设你的 dbhelper 是有缓存的, 每次去写的时候会等待前面写入之后再去写
    但是你并发比较高, 导致前面的这个 dbhelper 还没来得及写入, 就被拎出来又存了数据, 继续排队, 导致只有最终数据写进去了
    ZehaiZhang
        10
    ZehaiZhang   253 天前
    之前 trycatch 发现了一些因为数据格式问题导致的 push 失败
    PiersSoCool
        11
    PiersSoCool   253 天前
    大哥你这用异步跑,又没有同步措施,协程没跑完,主进程就退出了,肯定有问题啊
    securityCoding
        12
    securityCoding   252 天前
    @PiersSoCool 加个 waitGroup 或者 chan
    关于   ·   帮助文档   ·   FAQ   ·   API   ·   我们的愿景   ·   广告投放   ·   感谢   ·   实用小工具   ·   1212 人在线   最高记录 5497   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 21ms · UTC 23:35 · PVG 07:35 · LAX 16:35 · JFK 19:35
    ♥ Do have faith in what you're doing.