使用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)

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM