leaderboard, 在 ruby 中,Redis支持的排行榜

分享于 

27分钟阅读

GitHub

  繁體 雙語
Ruby Gem: Leaderboards backed by Redis in Ruby
  • 源代码名称:leaderboard
  • 源代码网址:http://www.github.com/agoragames/leaderboard
  • leaderboard源代码文档
  • leaderboard源代码下载
  • Git URL:
    git://www.github.com/agoragames/leaderboard.git
    Git Clone代码到本地:
    git clone http://www.github.com/agoragames/leaderboard
    Subversion代码到本地:
    $ svn co --depth empty http://www.github.com/agoragames/leaderboard
    Checked out revision 1.
    $ cd repo
    $ svn up trunk
    
    排行榜

    Redis 支持的排行榜在 ruby 中。

    建立在 http://www.agoragames.com/blog/2011/01/01/creating-high-score-tables-leaderboards-using-redis/的想法。

    Build Status

    安装

    gem install leaderboard

    或者你的Gemfile

    gem 'leaderboard'

    确保你的redis服务器运行 ! Redis配置超出了本文的范围,但是请查看 Redis文档文档。

    兼容性

    已经在 2.2.1下构建并测试了 gem。

    注意:gem 在 ruby的早期版本中应该能正常工作,例如 1.8.7或者 1.9.3。 我们现在利用的ruby 2.x 没有具体的方面。

    用法

    创建排行榜

    确保需要排行榜库:

    require'leaderboard'

    创建新的排行榜或者附加到名为'排行榜'的现有排行榜:

     highscore_lb =Leaderboard.new('highscores')
     => #<Leaderboard:0x0000010307b530 @leaderboard_name="highscores", @page_size=25, @redis_connection=#<Redis client v2.2.2 connected to redis://localhost:6379/0 (Redis v2.2.5)>>

    定义排行榜选项

    Leaderboard::DEFAULT_OPTIONS 如下所示:

    DEFAULT_OPTIONS= {
     :page_size => DEFAULT_PAGE_SIZE,
     :reverse => false,
     :member_key => :member,
     :rank_key => :rank,
     :score_key => :score,
     :member_data_key => :member_data,
     :member_data_namespace => 'member_data',
     :global_member_data => false}

    DEFAULT_PAGE_SIZE 为 25.

    如果你想从lowest-to-highest得分排序的话,你可以使用选项 :reverse => true。 你也可以在你创建一个新的排行榜实例后,在排行榜上设置 reverse 选项。 上面的各种 ..._key 选项控制从 leaders 或者 around_me 等调用的排序数据哈希返回的数据。 finally,global_member_data 选项允许你控制可选的成员数据是按 leaderboard ( false ) 还是全局( true )。

    如果需要传递Redis选项,可以在初始值设定项中执行这里操作:

     redis_options = {:host => 'localhost', :port => 6379, :db => 1}
     => {:host=>"localhost", :port=>6379, :db=>1}
     highscore_lb =Leaderboard.new('highscores', Leaderboard::DEFAULT_OPTIONS, redis_options)
     => #<Leaderboard:0x00000103095200 @leaderboard_name="highscores", @page_size=25, @redis_connection=#<Redis client v2.2.2 connected to redis://localhost:6379/1 (Redis v2.2.5)>>

    你可以在 redis_options 散列中使用 :redis_connection 传递到Redis的现有连接:

     redis =Redis.new => #<Redis client v2.2.2 connected to redis://127.0.0.1:6379/0 (Redis v2.2.5)> redis_options = {:redis_connection => redis}
     => {:redis_connection=>#<Redis client v2.2.2 connected to redis://127.0.0.1:6379/0 (Redis v2.2.5)>} highscore_lb =Leaderboard.new('highscores', Leaderboard::DEFAULT_OPTIONS, redis_options)
     => #<Leaderboard:0x000001028791e8 @leaderboard_name="highscores", @page_size=25, @redis_connection=#<Redis client v2.2.2 connected to redis://127.0.0.1:6379/0 (Redis v2.2.5)>>

    要对多个排行榜使用相同的连接,请在例示更多排行榜之前重置选项散列:

    redis =Redis.new => #<Redis client v2.2.2 connected to redis://127.0.0.1:6379/0 (Redis v2.2.5)>redis_options = {:redis_connection => redis}
     => {:redis_connection=>#<Redis client v2.2.2 connected to redis://127.0.0.1:6379/0 (Redis v2.2.5)>}highscore_lb =Leaderboard.new('highscores', Leaderboard::DEFAULT_OPTIONS, redis_options)
    redis_options = {:redis_connection => redis}
    other_highscore_lb =Leaderboard.new('other_highscores', Leaderboard::DEFAULT_OPTIONS, redis_options)

    你可以将页面大小设置为默认页面大小( 25 ) 以外的其他内容:

     highscore_lb.page_size =5 => 5 highscore_lb
     => #<Leaderboard:0x000001028791e8 @leaderboard_name="highscores", @page_size=5, @redis_connection=#<Redis client v2.2.2 connected to redis://127.0.0.1:6379/0 (Redis v2.2.5)>>

    排行榜中的排名成员

    使用 rank_member 将成员添加到你的排行榜中:

    1.upto(10) do |index|
     highscore_lb.rank_member("member_#{index}", index)
     end => 1

    你可以使用相同的成员呼叫 rank_member,并且将自动更新排行榜。

    获取有关你的排行榜的信息:

     highscore_lb.total_members
     => 10 highscore_lb.total_pages
     => 1

    rank_member 调用还将接受一个可选参数 member_data,它可以用于存储有关排行列表中给定成员的它的他信息。 这可以能非常有用,在你存储成员for的情况下,你希望能够存储一个成员 NAME 显示。 你可以使用JSON对成员数据的散列进行编码。 例如:

    require'json'highscore_lb.rank_member('84849292', 1, {'username' => 'member_name'}.to_json)

    你可以使用 member_data_forupdate_member_dataremove_member_data 调用来检索。更新和删除可选的成员数据。 例如:

    JSON.parse(highscore_lb.member_data_for('84849292'))
     => {"username"=>"member_name"}
    highscore_lb.update_member_data('84849292', {'last_updated' => Time.now, 'username' => 'updated_member_name'}.to_json)
     => "OK"JSON.parse(highscore_lb.member_data_for('84849292'))
     => {"username"=>"updated_member_name", "last_updated"=>"2012-06-09 09:11:06 -0400"}
    highscore_lb.remove_member_data('84849292')

    如果你 delete的排名,所有的成员数据也被删除。

    可选的成员数据注释

    如果使用可选成员数据,则使用 remove_members_in_score_range 或者 remove_members_outside_rank 方法将在成员数据哈希中留下数据。 这是因为内部Redis方法 zremrangebyscore 只返回删除的项的数目。 它不返回已经删除的成员。

    获取有关排行榜中特定成员的一些信息:

     highscore_lb.score_for('member_4')
     => 4.0 highscore_lb.rank_for('member_4')
     => 7 highscore_lb.rank_for('member_10')
     => 1

    从排行榜中检索成员

    在排行榜中获得第 1页:

     highscore_lb.leaders(1)
     => [{:member=>"member_10", :rank=>1, :score=>10.0}, {:member=>"member_9", :rank=>2, :score=>9.0}, {:member=>"member_8", :rank=>3, :score=>8.0}, {:member=>"member_7", :rank=>4, :score=>7.0}, {:member=>"member_6", :rank=>5, :score=>6.0}, {:member=>"member_5", :rank=>6, :score=>5.0}, {:member=>"member_4", :rank=>7, :score=>4.0}, {:member=>"member_3", :rank=>8, :score=>3.0}, {:member=>"member_2", :rank=>9, :score=>2.0}, {:member=>"member_1", :rank=>10, :score=>1.0}]

    你可以将各种选项传递给调用 leadersall_leadersaround_memembers_from_score_rangemembers_from_rank_rangeranked_in_list。 有效选项包括:

    • :with_member_data - true 或者 false ( 默认值) 返回可选的成员数据。
    • :page_size - 更改该调用的页面大小的整数值。
    • :members_only - true 或者 false ( 默认值) 只返回成员,而不返回他们的分数和等级。
    • :sort_by - :sort_by的有效值为 :none ( 默认值),:score:rank
    • :include_missing - true ( 默认值) 或者 false 返回未排序的成员。

    你还可以使用 membersmembers_in 方法作为 leadersleaders_in 方法的别名。

    还有一些方便的方法可以从给定的排名中检索所有领导者。 他们是 all_leadersall_leaders_from。 你还可以使用别名 all_members 或者 all_members_from。 将所有这些方法都谨慎使用,因为将返回排行榜中的所有信息。

    在你的排行榜中添加更多成员:

    50.upto(95) do |index|
     highscore_lb.rank_member("member_#{index}", index)
     end => 50 highscore_lb.total_pages
     => 3

    获取给定成员的"我周围"leaderboard页面,该页面将成员拉到给定成员之上和下面:

     highscore_lb.around_me('member_53')
     => [{:member=>"member_65", :rank=>31, :score=>65.0}, {:member=>"member_64", :rank=>32, :score=>64.0}, {:member=>"member_63", :rank=>33, :score=>63.0}, {:member=>"member_62", :rank=>34, :score=>62.0}, {:member=>"member_61", :rank=>35, :score=>61.0}, {:member=>"member_60", :rank=>36, :score=>60.0}, {:member=>"member_59", :rank=>37, :score=>59.0}, {:member=>"member_58", :rank=>38, :score=>58.0}, {:member=>"member_57", :rank=>39, :score=>57.0}, {:member=>"member_56", :rank=>40, :score=>56.0}, {:member=>"member_55", :rank=>41, :score=>55.0}, {:member=>"member_54", :rank=>42, :score=>54.0}, {:member=>"member_53", :rank=>43, :score=>53.0}, {:member=>"member_52", :rank=>44, :score=>52.0}, {:member=>"member_51", :rank=>45, :score=>51.0}, {:member=>"member_50", :rank=>46, :score=>50.0}, {:member=>"member_10", :rank=>47, :score=>10.0}, {:member=>"member_9", :rank=>48, :score=>9.0}, {:member=>"member_8", :rank=>49, :score=>8.0}, {:member=>"member_7", :rank=>50, :score=>7.0}, {:member=>"member_6", :rank=>51, :score=>6.0}, {:member=>"member_5", :rank=>52, :score=>5.0}, {:member=>"member_4", :rank=>53, :score=>4.0}, {:member=>"member_3", :rank=>54, :score=>3.0}, {:member=>"member_2", :rank=>55, :score=>2.0}]

    获取任意成员列表的秩和分数( 比如。 来自排行榜的朋友:

     highscore_lb.ranked_in_list(['member_1', 'member_62', 'member_67'])
     => [{:member=>"member_1", :rank=>56, :score=>1.0}, {:member=>"member_62", :rank=>34, :score=>62.0}, {:member=>"member_67", :rank=>29, :score=>67.0}]

    在给定得分范围内从排行榜中检索成员:

    members = highscore_lb.members_from_score_range(4, 19)
     => [{:member=>"member_10", :rank=>47, :score=>10.0}, {:member=>"member_9", :rank=>48, :score=>9.0}, {:member=>"member_8", :rank=>49, :score=>8.0}, {:member=>"member_7", :rank=>50, :score=>7.0}, {:member=>"member_6", :rank=>51, :score=>6.0}, {:member=>"member_5", :rank=>52, :score=>5.0}, {:member=>"member_4", :rank=>53, :score=>4.0}]

    在给定位置从排行榜中检索单个成员:

    members = highscore_lb.member_at(4)
     => {:member=>"member_92", :rank=>4, :score=>92.0}

    在给定的排名范围内从排行榜中检索一系列成员:

    members = highscore_lb.members_from_rank_range(1, 5)
     => [{:member=>"member_95", :rank=>1, :score=>95.0}, {:member=>"member_94", :rank=>2, :score=>94.0}, {:member=>"member_93", :rank=>3, :score=>93.0}, {:member=>"member_92", :rank=>4, :score=>92.0}, {:member=>"member_91", :rank=>5, :score=>91.0}]

    选项 :sort_by 用于从给定的排名列表中检索任意成员列表,你希望在返回时排序数据。 下面的示例演示它的用法:

    friends = highscore_lb.ranked_in_list(['member_6', 'member_1', 'member_10'], :sort_by => :rank)
     => [{:member=>"member_10", :rank=>47, :score=>10.0}, {:member=>"member_6", :rank=>51, :score=>6.0}, {:member=>"member_1", :rank=>56, :score=>1.0}]
    friends = highscore_lb.ranked_in_list(['member_6', 'member_1', 'member_10'], :sort_by => :score)
     => [{:member=>"member_1", :rank=>56, :score=>1.0}, {:member=>"member_6", :rank=>51, :score=>6.0}, {:member=>"member_10", :rank=>47, :score=>10.0}]

    在排行榜中有条件地排名成员

    你可以将一个lambda传递给 rank_member_if 方法,以条件地排序排行榜中的成员。 lambda被传递以下 5个参数:

    • member: 成员名称。
    • current_score: 排行榜中成员的当前得分。 如果成员当前不能在排行榜中排名,则可能为 nil
    • score: 成员得分。
    • member_data: 可选的成员数据。
    • leaderboard_options: 排行榜选项 比如: 反向= 反向选项的> 值
    highscore_check =lambdado |member, current_score, score, member_data, leaderboard_options|
     returntrueif current_score.nil?
     returntrueif score > current_score
     falseendhighscore_lb.rank_member_if(highscore_check, 'david', 1337)
    highscore_lb.score_for('david')
     => 1337.0highscore_lb.rank_member_if(highscore_check, 'david', 1336)
    highscore_lb.score_for('david')
     => 1337.0highscore_lb.rank_member_if(highscore_check, 'david', 1338)
    highscore_lb.score_for('david')
     => 1338.0

    注意:使用lambda而不是 proc,否则将得到一个 LocalJumpError 作为中的返回语句。

    一次将多个成员排序

    为成员及其关联的分数插入多个数据项:

    作为 splat:

    highscore_lb.rank_members('member_1', 1, 'member_5', 5, 'member_10', 10)

    或者作为 array:

    highscore_lb.rank_members(['member_1', 1, 'member_5', 5, 'member_10', 10])

    使用这里方法进行大量插入数据,但是要注意,因为单个事务可以获得很大的数据量。

    在多个排行榜中加入成员

    highscore_lb.rank_member_across(['highscores', 'more_highscores'], 'david', 50000, { :member_name => "david" })

    备选的排行榜类型

    排行榜库提供了 3种风格的排名。 这只是在排行榜中得分相同的成员的问题。

    默认值:Leaderboard 类使用默认的Redis排序集排序,因此具有相同分数的不同成员按顺序排序。 对于。集合中的。文档,"使用的字典排序是二进制的,它将字符串比较为字节 array。"

    Tie rank: LeaderboardTieRankingLeaderboard 子类允许你定义一个排行榜,其中具有相同得分的成员的排名相同。 例如得分相关的排行榜中的成员将具有以下等级:

    
    | member | score | rank |
    
    
    -----------------------------
    
    
    | member_1 | 50 | 1 |
    
    
    | member_2 | 50 | 1 |
    
    
    | member_3 | 30 | 2 |
    
    
    | member_4 | 30 | 2 |
    
    
    | member_5 | 10 | 3 |
    
    
    
    

    TieRankingLeaderboard 接受一个附加选项 :ties_namespace ( 默认值: 当初始化这里类的新实例时,。 请注意,在当前实现中,TieRankingLeaderboard 类使用额外的排序集对分数进行排序。

    比赛排名:LeaderboardCompetitionRankingLeaderboard 子类允许你定义一个排名,同等分的成员会有相同的排名。 例如得分相关的排行榜中的成员将具有以下等级:

    
    | member | score | rank |
    
    
    -----------------------------
    
    
    | member_1 | 50 | 1 |
    
    
    | member_2 | 50 | 1 |
    
    
    | member_3 | 30 | 3 |
    
    
    | member_4 | 30 | 3 |
    
    
    | member_5 | 10 | 5 |
    
    
    
    

    其他有用的方法

    
     delete_leaderboard: Delete the current leaderboard
    
    
     member_data_for(member): Retrieve the optional member data for a given member in the leaderboard
    
    
     members_data_for(members): Retrieve the optional member data for a given list of members in the leaderboard
    
    
     update_member_data(member, member_data): Update the optional member data for a given member in the leaderboard
    
    
     remove_member_data(member): Remove the optional member data for a given member in the leaderboard
    
    
     remove_member(member): Remove a member from the leaderboard
    
    
     total_members: Total # of members in the leaderboard
    
    
     total_pages: Total # of pages in the leaderboard given the leaderboard's page_size
    
    
     total_members_in_score_range(min_score, max_score): Count the number of members within a score range in the leaderboard
    
    
     total_scores: Sum of scores for all members in leaderboard
    
    
     change_score_for(member, delta): Change the score for a member by some amount delta (delta could be positive or negative)
    
    
     rank_for(member): Retrieve the rank for a given member in the leaderboard
    
    
     score_for(member): Retrieve the score for a given member in the leaderboard
    
    
     check_member?(member): Check to see whether member is in the leaderboard
    
    
     score_and_rank_for(member): Retrieve the score and rank for a member in a single call
    
    
     remove_members_in_score_range(min_score, max_score): Remove members from the leaderboard within a score range
    
    
     remove_members_outside_rank(rank): Remove members from the leaderboard outside a given rank
    
    
     percentile_for(member): Calculate the percentile for a given member
    
    
     score_for_percentile(percentile): Calculate the score for a given percentile value in the leaderboard
    
    
     page_for(member, page_size): Determine the page where a member falls in the leaderboard
    
    
     expire_leaderboard(seconds): Expire the leaderboard in a set number of seconds.
    
    
     expire_leaderboard_at(timestamp): Expire the leaderboard at a specific UNIX timestamp.
    
    
     rank_members(members_and_scores): Rank an array of members in the leaderboard where you can call via (member_name, score) or pass in an array of [member_name, score]
    
    
     merge_leaderboards(destination, keys, options = {:aggregate => :min}): Merge leaderboards given by keys with this leaderboard into destination
    
    
     intersect_leaderboards(destination, keys, options = {:aggregate => :min}): Intersect leaderboards given by keys with this leaderboard into destination
    
    
     top(number, options): Retrieve members from the leaderboard within a range from 1 to the number given.
    
    
    
    

    查看联机文档了解每个方法的详细信息。

    性能度量

    10 million连续评分:

     highscore_lb =Leaderboard.new('highscores')
     => #<Leaderboard:0x0000010205fc50 @leaderboard_name="highscores", @page_size=25, @redis_connection=#<Redis client v2.2.2 connected to redis://localhost:6379/0 (Redis v2.2.5)>> insert_time =Benchmark.measure do1.upto(10000000) do |index|
     highscore_lb.rank_member("member_#{index}", index)
     endend => 323.070000148.560000471.630000 (942.068307)

    从排行榜请求任意页面的平均时间:

     requests_to_make =50000 => 50000 lb_request_time =0 => 01.upto(requests_to_make) do lb_request_time +=Benchmark.measure do highscore_lb.leaders(rand(highscore_lb.total_pages))
     end.total
     end => 1p lb_request_time / requests_to_make
     0.001513999999999998 => 0.001513999999999998

    10 million随机得分:

     insert_time =Benchmark.measure do1.upto(10000000) do |index|
     highscore_lb.rank_member("member_#{index}", rand(50000000))
     endend => 338.480000155.200000493.680000 (2188.702475)

    从排行榜请求任意页面的平均时间:

    1.upto(requests_to_make) do lb_request_time +=Benchmark.measure do highscore_lb.leaders(rand(highscore_lb.total_pages))
     end.total
     end => 1p lb_request_time / requests_to_make
     0.0014615999999999531 => 0.0014615999999999531

    大容量插入性能

    对单个成员排序:

    insert_time =Benchmark.measure do1.upto(1000000) do |index|
     highscore_lb.rank_member("member_#{index}", index)
     endend => 29.34000015.05000044.390000 ( 81.673507)

    一次对多个成员进行排序:

    member_data = []
     => []1.upto(1000000) do |index|
     member_data <<"member_#{index}" member_data << indexend => 1insert_time =Benchmark.measure do highscore_lb.rank_members(member_data)end => 22.3900006.38000028.770000 ( 31.144027)

    端口

    以下端口已经被排行榜 gem

    正式支持:

    Unofficially支持的( 他们需要一些 feature ):

    致力于排行榜

    • 检查最新的主机,确保功能没有实现,或者 Bug 尚未被修复。
    • 查看问题跟踪程序以确保某人已经没有请求它并/或者贡献它
    • fork 项目
    • 启动特征/修正分支
    • 提交并推送直到你对自己的贡献满意
    • 一定要为它添加测试。 这很重要,所以我不会在以后的版本中无意中。
    • 请不要与 Rakefile,版本或者历史混淆。 如果你想有自己的版本或者是必要的,那么很好,但是请隔离它自己的提交。

    版权

    版权所有( c ) 2011 -2017 David Czarnecki。 有关详细信息,请参阅 LICENSE.txt。


    RED  Redis  lead  Leaderboard  leader  
    相关文章