Heim  >  Artikel  >  Datenbank  >  Redis implementiert Rankings und die Funktion, dieselben Punkte nach Zeit zu sortieren

Redis implementiert Rankings und die Funktion, dieselben Punkte nach Zeit zu sortieren

WBOY
WBOYnach vorne
2022-08-22 17:51:202289Durchsuche

Empfohlenes Lernen: Redis-Video-Tutorial

In der täglichen Entwicklung stoßen wir häufig auf die Notwendigkeit, Benutzerpunkte usw. zu sortieren, z. B. die Kampfkraft in Spielen zu bewerten, und bei Teamaktivitäten ist eine Rangfolge erforderlich Der Beitragswert jedes Teams ist erforderlich, um die Anzahl der Schritte jedes Freundes zu bewerten. Zu diesem Zeitpunkt wird die geordnete Sammlung von Redis im Allgemeinen ausgewählt, um die Punktzahl des Benutzers zu speichern, aber Anders Die Szenenranking-Methode ist ebenfalls etwas anders. Das Folgende ist eine Zusammenfassung, die auf meiner täglichen Entwicklung basiert.

Anforderung: Bewerten Sie den Beitragswert jedes Teams in der Teamaktivität.

Bedenken Sie nicht, dass das Integral dasselbe ist.

Die sortierte Menge von Redis ist eine geordnete Menge vom Typ String. Satzmitglieder sind eindeutig, was bedeutet, dass im Satz keine doppelten Daten vorkommen können.

Jedem Element ist eine doppelte Typbewertung zugeordnet. Redis verwendet Scores, um die Mitglieder der Sammlung von klein nach groß zu sortieren.

Die Mitglieder eines geordneten Sets sind einzigartig, aber die Partituren können wiederholt werden.

Ignorieren Sie den Fall der gleichen Punkte und implementieren Sie die Rangliste:

// 准备数据,其中value为每个队伍的ID,score为队伍的贡献值
> zadd z1 5 a 6 b 1 c 2 d 10 e
(integer) 5

// 分页查询排行榜所有的队伍和贡献值,要使用zrevrange,而不是zrange,贡献值越大越排在前面
> zrevrange z1 0 2 withscores
1) "e"
2) "10"
3) "b"
4) "6"
5) "a"
6) "5"

// 增加某个队伍的贡献值
> zincrby z1 3 d
"5"
> zincrby z1 4 c
"5"

// 查询排行榜所有的队伍
> zrevrange z1 0 -1 withscores
 1) "e"
 2) "10"
 3) "b"
 4) "6"
 5) "d"
 6) "5"
 7) "c"
 8) "5"
 9) "a"
10) "5"

// 查询某个队伍的排名
> zrevrank z1 d
(integer) 2

Die Standardimplementierung von Redis besteht darin, Mitglieder mit derselben Punktzahl in Wörterbuchreihenfolge zu sortieren (09, AZ, a~z). Das Obige verwendet zrevrange, Es erfolgt also eine umgekehrte Reihenfolge. Daher kann die Sortierung mit derselben Punktzahl nicht nach Zeitpriorität sortiert werden.

Gleiche Punkte werden nach Zeit sortiert und die Rangfolge ist eindeutig.

Wenn in der obigen Implementierung der Beitragswert zweier Teams gleich ist, d. h. der Punktwert gleich ist, können sie nicht nach Zeit gereiht werden.

Wir müssen also einen Score = Beitragswert + Zeitstempel entwerfen. Wer einen höheren Score hat, wird zuerst bewertet.

Design 1

Verwenden Sie Ganzzahlen, um Score-Werte zu speichern. Der Score selbst in Redis ist ein Double-Typ und die maximale Ganzzahl, die genau gespeichert werden kann, ist 2^53=9007199254740992 (16 Bit). Ein auf Millisekunden genauer Zeitstempel erfordert 13 Ziffern, so dass nur 3 Ziffern für den Speicherbeitragswert übrig bleiben. Wenn die Zeit auf Sekunden genau ist, sind derzeit nur 10 Ziffern erforderlich, sodass 6 Ziffern für den Beitragswert übrig bleiben.

Gesamtdesign: Die hohen 3 Ziffern stellen den Beitragswert dar und die niedrigen 13 Ziffern stellen den Zeitstempel dar.

Wenn wir einfach die Score-Struktur zusammensetzen: Beitragswert * 10^13 + Zeitstempel, denn je größer der Score, desto näher wird er sein, und je kleiner der Zeitstempel, desto näher wird er sein Auf diese Weise sind die beiden Urteilsregeln gegensätzlich und können nicht einfach zu einer Partitur kombiniert werden. 贡献值 * 10^13 + 时间戳 拼凑,因为分数越大越靠前,而时间戳越小则越靠前,这样两部分的判断规则是相反的,无法简单把两者合成一起成为score。

但是我们可以逆向思维,可以用同一个足够大的数Integer.MAX减去时间戳,时间戳越小,则得到的差值越大,这样我们就可以把score的结构改为:贡献值 * 10^13 + (Integer.MAX-时间戳),这样就能满足我们的需求了。

设计2

由于redis的score值是double类型,可以使用整数部分存储贡献值,小数部分存储时间戳,同样时间戳的部分使用一个最大值减去它。

这样,整体设计变为:分数=贡献值 + (Integer.MAX-时间戳) * 10^-13

弊端:由于分数值是由两个变量来计算得出,所以在给队伍增加贡献值时,无法简单的使用之前的zincrby来改变score的值了,这样在并发情况下为队伍增加贡献值就会导致score值不准确。

错误情况模拟:

假设现在队伍A的贡献值为10队伍A中的队员X为队伍增加贡献值1,在程序中算出score为11.xxx队伍A中的队员Y为队伍增加贡献值1,在程序中算出score为11.yyy队伍A中的队员X调用redis的zadd命令设置队伍的贡献值为11.xxx队伍A中的队员Y调用redis的zadd命令设置队伍的贡献值为11.yyy最后算出队伍A的贡献值为11,无法保证增加贡献值这一个操作的原子性。

此时需要借助lua脚本来保证计算和设置贡献值这两个操作的原子性:

// 其中KEYS[1]为排行榜key,KEYS[2]为队伍ID
// 其中ARGV[1]为增加的贡献值,ARGV[2]为Integer.MAX-时间戳
local score = redis.call('zscore', KEYS[1], KEYS[2]) 
if not(score) then
	score=0 
end 
score=math.floor(score) + tonumber(ARGV[1]) + tonumber(ARGV[2]) 
redis.call('zadd', KEYS[1], score, KEYS[2]) return 1

由于redis中无法使用时间函数,所以(Integer.MAX-时间戳) * 10^-13

Aber wir können umgekehrt denken und den Zeitstempel von der gleichen ausreichend großen Zahl Integer.MAX subtrahieren. Je kleiner der Zeitstempel, desto größer die Differenz, die wir erhalten, sodass wir die Punktestruktur ändern können in: Beitragswert * 10 ^13 + (Integer.MAX-timestamp), dies kann unsere Anforderungen erfüllen.

Design 2

Da der Score-Wert von Redis vom doppelten Typ ist, können Sie den ganzzahligen Teil zum Speichern des Beitragswerts, den dezimalen Teil zum Speichern des Zeitstempels und einen Maximalwert verwenden, um ihn vom Teil des zu subtrahieren Gleicher Zeitstempel.

Auf diese Weise wird das Gesamtdesign zu: Score = Beitragswert + (Integer.MAX-Zeitstempel) * 10^-13

Nachteile: Da der Score-Wert durch zwei Variablen berechnet wird, also Wenn Sie dem Team einen Beitragswert hinzufügen, können Sie den Punktewert nicht einfach mit dem vorherigen Zincrby ändern. Auf diese Weise führt das Hinzufügen eines Beitragswertes zum Team unter gleichzeitigen Bedingungen zu ungenauen Punktewerten. Fehlersituationssimulation:
> zscore 排行榜key teamId
> zrevrangebyscore(排行榜key, 上一步得到的score+1, 上一步得到的score, limit, 0 , 1)
> zrevrank(排行榜key, 上一步得到的teamId)
Sie können die oben genannten Befehle weiterhin verwenden, um die Ranglisten auf Seiten abzufragen, die Ranglisten von Teams abzufragen und andere Funktionen auszuführen. Team-IDBeitragswertRankinga1001b99 2 c 992
Angenommen, der aktuelle Beitragswert von Team A beträgt 10. Spieler A fügt dem Team 1 einen Beitragswert hinzu. Berechnen Sie die Punktzahl im Programm auf 11.yyy. Der Spieler yyy hat schließlich den Beitragswert von Team A auf 11 berechnet, und die Atomizität der Erhöhung des Beitragswertes kann nicht garantiert werden. Zu diesem Zeitpunkt müssen Sie ein Lua-Skript verwenden, um die Atomizität der beiden Vorgänge zum Berechnen und Festlegen des Beitragswerts sicherzustellen: Da die Zeitfunktion in Redis nicht verwendet werden kann, (Integer.MAX- Zeitstempel) * 10^- Der 13-Teil wird vom Programm außerhalb des Skripts berechnet und übergeben.
Wer die gleichen Punkte hat, wird zeitlich sortiert und parallel gerankt Bei den sogenannten Gleichstandswertungen handelt es sich um Ranglisten mit gleicher Ranglage. Die Ergebnisse, die wir erwarten, sind wie folgt:
🎜d🎜🎜88🎜🎜4🎜🎜🎜🎜e🎜🎜87🎜🎜5🎜🎜🎜🎜

当然现实中也有排名不跳过的情况,我这里考虑的是排名跳过的情况。

redis中score的设计还是采用上面的分数=贡献值 + (Integer.MAX-时间戳) * 10^-13,只是在查询排名时需要进行计算。

比如要查上表中队伍b的排名,思路如下:

  • 首先查到队伍b的score
  • 再查到跟队伍b的score的整数部分相同(也就是贡献值一样),排在第一个的队伍的value(队伍ID)
  • 根据上一步得到的队伍ID查询此队伍的排名就是队伍b的排名

使用命令实现上面的步骤如下:

> zscore 排行榜key teamId
> zrevrangebyscore(排行榜key, 上一步得到的score+1, 上一步得到的score, limit, 0 , 1)
> zrevrank(排行榜key, 上一步得到的teamId)

为了性能考虑,可以使用下面的脚本一次查出来:

// KEYS[1]表示排行榜key
// KEYS[2]表示要查询的队伍的ID
local rank = 0 
local score = redis.call('zscore', KEYS[1], KEYS[2]) 
if not(score) then
    score=0 
else 
    score=math.floor(score) 
    local firstScore = redis.call('zrevrangebyscore', KEYS[1], score+1, score, 'limit', 0, 1) 
    rank=redis.call('zrevrank', KEYS[1], firstScore[1]) 
end 
return {score,rank}

下面附上分页查询排行榜的脚本,假如一页10条,不用下面的脚本需要查询10次上面的脚本,如果连上面的脚本都没有使用的话就要查询30次redis。

// 排行榜key
// ARGV[1]分页起始偏移
// ARGV[2]分页结束偏移
local list = redis.call('zrevrange', KEYS[1], ARGV[1], ARGV[2], 'withscores') 
local result={} 
local i = 1 
for k,v in pairs(list) do 
    if k%2 == 0 then 
        local teamId = list[k-1] 
        local score = math.floor(v) 
        local firstScore = redis.call('zrevrangebyscore', KEYS[1], score+1, score, 'limit', 0, 1) 
        local rank=redis.call('zrevrank', KEYS[1], firstScore[1]) 
        local l = {teamId=teamId, contributionValue=score, teamRank=rank+1} 
        result[i] = l i = i + 1 
    end 
end 
return cjson.encode(result)

此脚本使用了cjson库,返回的是一个json。

推荐学习:Redis视频教程

Das obige ist der detaillierte Inhalt vonRedis implementiert Rankings und die Funktion, dieselben Punkte nach Zeit zu sortieren. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:jb51.net. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen