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

redis 并发下写入数据丢失

  •  
  •   shuangdeyu · 2020-08-31 16:44:17 +08:00 · 3548 次点击
    这是一个创建于 1528 天前的主题,其中的信息可能已经有所发展或是发生改变。

    不懂就问,使用 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 条附言  ·  2020-09-01 09:44:33 +08:00

    感谢大家的回答,问题找到了,测试代码没有严谨对待,没有抛出错误信息,饶了弯路 =_= ,一直在钻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  
       2020-08-31 17:40:27 +08:00
    查看下 redis 连接是否有错误, 调大连接数
    saturn7
        2
    saturn7  
       2020-08-31 17:53:46 +08:00
    看 demo 代码象只是复用单实例连接到 Redis Server,典型的写 PHP 思维。解决要用连接池 + 并发锁。
    micean
        3
    micean  
       2020-08-31 18:02:51 +08:00
    lpush 的结果不观察一下吗?
    JJstyle
        4
    JJstyle  
       2020-08-31 18:03:41 +08:00
    @saturn7 楼主需要能并发插入数据,你给加一个并发锁,是要解决什么问题?
    fanpei0121
        5
    fanpei0121  
       2020-08-31 18:06:39 +08:00
    经测试 并发 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  
       2020-08-31 18:07:46 +08:00
    没看明白. 你是怎么知道 lpush 结果正常的.. redis 返回值是什么, 异常有 catch 住打印没?


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