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)
  • 相关阅读:
    让UILabel的大小自适应字符串长度
    Cocos2dx中的分辨率学习
    关键帧动画-五角星动画-在层上画出五角星
    .net WINFORM 界面怎么做凹凸效果的分割线?就是横线
    游标Cursor 使用小例 (SQLServer)
    winform屏蔽Alt+F4组合键以防止用户关闭对话框
    SQL Server 2005 数据类型和.Net数据类型的对应关系
    php $GLOBALS 超全局变量的理解
    我改行了
    PHP $_FILES详解
  • 原文地址:https://www.cnblogs.com/-wenli/p/13260643.html
Copyright © 2011-2022 走看看