zoukankan      html  css  js  c++  java
  • 使用Redis存储聊天数据的一种方案(使用lua解决原子性问题)

    方案设计

    使用redis列表存储两个用户之间的聊天数据,存储内容使用json字符串封装,字段包括:fromid、toid、msg、time四个字段。

    使用redis hash存储一个用户未读的消息条数。

    存在问题:原子性问题。

    Python Demo实现

    import json
    import time
    import redis
    
    pool = redis.ConnectionPool(host='xxxx',port=6379, decode_responses=True)
    conn = redis.Redis(connection_pool=pool)
    """
    function:fromid用户给toid用户发送msg消息   
    Parameters:
        fromid:int类型,发送消息的用户id
        toid:int类型,接收消息的用户id
        msg:str类型,消息内容
    return:bool类型,消息是否发送成功
    """
    def msgsend(fromid,toid,msg):
        try:
            timesocre = time.time()
            dict = {"fromid":fromid,"toid":toid,"msg" :msg,"time":timesocre}
            key = keyname(fromid, toid)
            aliveflag = checkuseralive(toid)
            if not aliveflag:
                setwaithoutnum(toid, key)
            conn.lpush(key, json.dumps(dict))
            return True
        except:
            return False
    """
    function:toid用户读取fromid用户发送过来消息   
    Parameters:
        fromid:int类型,发送消息的用户id
        toid:int类型,接收消息的用户id
    return:list,int(消息列表与toid用户未读取fromid用户发送过来的消息条数)
    """
    def msgread(fromid,toid):
        key = keyname(fromid, toid)
        msglen = conn.llen(key)
        msglist = conn.lrange(key,0,msglen)
        withoutmsgnum = returnwithoutnum(toid,key)
        return msglist,withoutmsgnum
    """
    function:检查userid用户是否在线
    Parameters:
        userid:int类型,消息的用户id
    return:bool类型,在线为True,不在线为False
    """
    def checkuseralive(userid):
        # 检查用户在线,预留
        return True
    """
    function:设置userid用户与另一个用户未读取消息的条数
    Parameters:
        userid:int类型,消息的用户id
        key:str类型
    return:bool类型,在线为True,不在线为False
    """
    def setwaithoutnum(userid,key):
        if  conn.hexists(str(userid), key):
            msgnum = conn.hget(str(userid), key)
            conn.hset(str(userid), key, int(msgnum)+1)
        else:
            conn.hset(str(userid), key, 1)
    """
    function:返回userid用户与另一个用户的未读消息条数
    Parameters:
        userid:int类型,消息的用户id
        key:str类型,
    return:
    """
    def returnwithoutnum(userid,key):
        if  conn.hexists(str(userid), key):
            msgnum = conn.hget(str(userid), key)
            conn.hset(str(userid), key,0)
            return int(msgnum)
        return 0
    
    
    """
    function:根据两个id唯一生成一个key
    Parameters:
        fromid: int
        toid:int
    return:
        str
    """
    def keyname(fromid,toid):
        key = (str(fromid)+"-"+str(toid) if (fromid > toid) else str(toid)+"-"+str(fromid))
        return key
    
    user1 = 23
    user2 = 43
    user3 = 212
    user4 = 65
    #用户1给用户2发送"你好"
    # msgsend(user1,user2,"打你")
    #用户2读取用户1发送的消息
    #第一个返回值返回全部聊天记录,第二个参数返回未读消息数量
    msglist,withoutmsgnum = msgread(user1,user2)
    print(msglist,withoutmsgnum)
    # msgsend(user2,user1,"。。。")
    # msglist,withoutmsgnum = msgread(user2,user1)
    # print(msglist,withoutmsgnum)
    
    #读取最新一条内容示例
    print(json.loads(msglist[0]))
    print(json.loads(msglist[1]))
    print(json.loads(msglist[2]))
    # print(json.loads(msglist[3]))
    # print(json.loads(msglist[5]))
    # print(json.loads(msglist[6]))
    # print(json.loads(msglist[7]))

    Redis配合lua

    上一个版本没有考虑到原子性的问题,我这里采用lua脚本了,减少网络io的同时,保证了整个执行过程的原子性。

    import json
    import time
    import redis
    pool = redis.ConnectionPool(host='xxx',port=6379, decode_responses=True)
    conn = redis.Redis(connection_pool=pool)
    """
    function:fromid用户给toid用户发送msg消息   
    Parameters:
        fromid:int类型,发送消息的用户id
        toid:int类型,接收消息的用户id
        msg:str类型,消息内容
    return:bool类型,消息是否发送成功
    """
    def msgsend(fromid,toid,msg):
        lua1 = """
            local flag = tostring(ARGV[1])
            if(flag == "False")
            then
                if(redis.call("hexists",KEYS[1],KEYS[2]) == 1)
                then
                    local msgnum = tonumber(redis.call("hget",KEYS[1],KEYS[2]))
                    redis.call("hset",KEYS[1],KEYS[2],msgnum+1)
                else
                    redis.call("hset",KEYS[1],KEYS[2],1)
                end
            end
            redis.call("lpush", KEYS[2],ARGV[2])
        """
        timesocre = time.time()
        dict = {"fromid":fromid,"toid":toid,"msg" :msg,"time":timesocre}
        key = keyname(fromid, toid)
        aliveflag = checkuseralive(toid)
        script2 = conn.register_script(lua1)
        script2(keys=[str(toid),key],args=[str(aliveflag),json.dumps(dict)])
    """
    function:toid用户读取fromid用户发送过来消息   
    Parameters:
        fromid:int类型,发送消息的用户id
        toid:int类型,接收消息的用户id
    return:list(消息总长度,未读消息数量,未读消息内容)
    """
    def msgread(fromid,toid):
        lua2 = """
            local result = {}
            local msglistlen = tonumber(redis.call("llen",KEYS[2]))
            table.insert(result, msglistlen);
            if(redis.call("hexists",KEYS[1],KEYS[2]) == 1)
            then
                local msgnum = redis.call("hget",KEYS[1],KEYS[2])
                redis.call("hset",KEYS[1],KEYS[2],0)
                table.insert(result, msgnum);
                table.insert(result,redis.call("lrange",KEYS[2],0,-msgnum))        
                return result
            end
            table.insert(result, 0);
            table.insert(result, "")
            return result
        """
        key = keyname(fromid, toid)
        script2 = conn.register_script(lua2)
        ldata = script2(keys=[str(toid), key])
        return ldata[0],ldata[1],ldata[2]
    """
    function:检查userid用户是否在线
    Parameters:
        userid:int类型,消息的用户id
    return:bool类型,在线为True,不在线为False
    """
    def checkuseralive(userid):
        # 检查用户在线,预留
        return False
    
    
    """
    function:根据两个id唯一生成一个key
    Parameters:
        fromid: int
        toid:int
    return:
        str
    """
    def keyname(fromid,toid):
        key = (str(fromid)+"-"+str(toid) if (fromid > toid) else str(toid)+"-"+str(fromid))
        return key
    
    user1 = 23
    user2 = 43
    user3 = 212
    user4 = 65
    
    #发送测试
    #用户1给用户2发送"你好"
    msgsend(user1,user2,"2324242")
    msgsend(user1,user2,"ewewe")
    msgsend(user1,user2,"ewe")
    # msgsend(user1,user2,"22")
    # msgsend(user1,user2,"233")
    # msgsend(user2,user1,"3232")
    
    #读取测试
    #用户2读取用户1发送的消息
    #第一个返回值返回全部聊天记录,第二个参数返回
    msglistlen,withoutmsgnum,msglist = msgread(user1,user2)
    print(msglistlen,withoutmsgnum,json.loads(msglist[0]))
    #用户1读取用户2发送的消息
    #第一个返回值返回全部聊天记录,第二个参数返回
    # msglist,withoutmsgnum = msgread(user2,user1)
    # print(msglist,withoutmsgnum)
  • 相关阅读:
    FZU 2150 Fire Game
    POJ 3414 Pots
    POJ 3087 Shuffle'm Up
    POJ 3126 Prime Path
    POJ 1426 Find The Multiple
    POJ 3278 Catch That Cow
    字符数组
    HDU 1238 Substing
    欧几里德和扩展欧几里德详解 以及例题CodeForces 7C
    Codeforces 591B Rebranding
  • 原文地址:https://www.cnblogs.com/-wenli/p/13260643.html
Copyright © 2011-2022 走看看