1. 当有序集合遇上C#
在游戏排行榜、实时竞价系统、延时任务队列等场景中,我们常常需要处理带有权重的有序数据。Redis的SortedSet(有序集合)正是为此而生,它通过score
数值进行自动排序的特性,就像给每个元素贴上了价格标签的货架。今天我们将用C#的StackExchange.Redis客户端,深入探索这个数据结构的妙用。
2. 环境搭建与基础准备
首先通过NuGet安装最新版StackExchange.Redis(当前最新为2.7.58):
Install-Package StackExchange.Redis -Version 2.7.58
建立Redis连接的推荐方式:
using StackExchange.Redis;
// 创建复用连接(重要!不要频繁创建新连接)
ConnectionMultiplexer redis = ConnectionMultiplexer.Connect("localhost:6379");
IDatabase db = redis.GetDatabase();
// 测试连接是否正常
if (db.Ping().TotalMilliseconds < 1000)
{
Console.WriteLine("成功连接Redis!");
}
3. 核心操作全解析
3.1 基础数据写入
我们以游戏积分榜为例准备测试数据:
// 清空旧数据(测试环境使用)
db.KeyDelete("player_scores");
// 批量添加玩家得分(参数依次:键名,分数,成员)
var players = new[]
{
new SortedSetEntry("player_001", 1500),
new SortedSetEntry("player_002", 3200),
new SortedSetEntry("player_003", 2750)
};
db.SortedSetAdd("player_scores", players);
// 单个添加
db.SortedSetAdd("player_scores", "player_004", 4100);
3.2 范围查询三剑客
案例1:获取前3名玩家
// 参数说明:键名,起始排名(0表示第一个),结束排名,排序方式
RedisValue[] top3 = db.SortedSetRangeByRank(
"player_scores",
start: 0,
stop: 2,
order: Order.Descending);
Console.WriteLine($"冠军:{top3[0]}"); // 输出:player_004
案例2:查询2000-3000分的玩家
var midPlayers = db.SortedSetRangeByScore(
"player_scores",
min: 2000,
max: 3000,
order: Order.Descending);
// 输出:player_003(2750分)、player_001(1500分不满足)
案例3:带分页的查询
// 每页5条,获取第2页数据(跳过前5条)
var page2 = db.SortedSetRangeByScore(
"player_scores",
skip: 5,
take: 5,
order: Order.Descending);
3.3 高阶排序技巧
带分数返回的查询
var withScores = db.SortedSetRangeByRankWithScores(
"player_scores",
start: 0,
stop: -1,
order: Order.Descending);
foreach (var item in withScores)
{
Console.WriteLine($"{item.Element}: {item.Score}分");
}
组合条件查询
// 查询分数大于等于3000的玩家数量
long count = db.SortedSetCount("player_scores", min: 3000, max: double.PositiveInfinity);
Console.WriteLine($"高分玩家数量:{count}"); // 输出:2
4. 典型应用场景
4.1 实时排行榜系统
每小时更新电商商品热度值:
// 每次点击增加10点热度
db.SortedSetIncrement("product_hot", "product_123", 10);
// 获取Top10热门商品
var hotProducts = db.SortedSetRangeByRank("product_hot", 0, 9, Order.Descending);
4.2 延时任务队列
处理30分钟后到期的订单:
// 将订单ID和到期时间戳存入有序集合
double expireTime = DateTimeOffset.Now.AddMinutes(30).ToUnixTimeSeconds();
db.SortedSetAdd("delay_orders", "order_888", expireTime);
// 定时任务查询到期订单
var currentTime = DateTimeOffset.Now.ToUnixTimeSeconds();
var readyOrders = db.SortedSetRangeByScore("delay_orders", 0, currentTime);
5. 技术选型分析
优势亮点:
- 内存级性能:10万级数据量查询可在毫秒级完成
- 自动排序:无需额外代码维护排序逻辑
- 灵活查询:支持分数范围、排名区间、分页等多种方式
- 原子操作:ZADD、ZINCRBY等命令保证并发安全
潜在局限:
- 内存消耗:超大集合需要合理设置过期时间
- 分页性能:深度分页(如第1000页)效率较低
- 精度问题:分数使用double类型存储,需注意精度控制
6. 避坑指南
6.1 键命名规范
建议采用业务:类型:ID
的格式:
// 推荐写法
const string KEY = "game:rank:season2";
6.2 连接复用策略
创建连接池的正确方式:
// 使用Lazy创建线程安全连接
private static readonly Lazy<ConnectionMultiplexer> LazyConnection =
new Lazy<ConnectionMultiplexer>(() =>
ConnectionMultiplexer.Connect("localhost:6379"));
public static ConnectionMultiplexer Connection => LazyConnection.Value;
6.3 大数据量优化
当处理百万级数据时:
// 使用SCAN迭代代替一次性获取
var allElements = new List<RedisValue>();
long cursor = 0;
do
{
var result = db.SortedSetScan("big_key", cursor: cursor);
cursor = result.Cursor;
allElements.AddRange(result.Items.Select(x => x.Element));
} while (cursor != 0);
7. 总结与展望
通过本文的实战演练,我们掌握了使用StackExchange.Redis操作有序集合的核心技巧。从基础的增删改查到复杂的分页、范围查询,Redis的有序集合展现了强大的排序能力。在后续开发中,可以结合管道(Pipeline)技术提升批量操作效率,或是尝试使用RediSearch实现更复杂的查询需求。记住,技术选型时要根据具体场景选择最合适的工具,就像在超市选购商品——有序集合是那个贴满价签的智能货架,而我们要做聪明的采购者。