Redis温故知新
一、定义
Redis(Remote Dictionary Server)是一个开源的、内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。Redis 支持多种类型的数据结构,如字符串(strings)、哈希表(hashes)、列表(lists)、集合(sets)、有序集合(sorted sets)与位图(bitmaps)、超日志(hyperloglogs)和地理空间(geospatial)索引半径查询等。Redis 具备丰富的特性,包括事务、发布/订阅、Lua 脚本、持久化、主从复制、哨兵(Sentinel)和集群(Cluster)等。
Redis 的主要特点和优势包括:
- 高性能:Redis 将数据存储在内存中,因此读写速度非常快,远超过传统的基于磁盘的数据库。
- 丰富的数据类型:Redis 支持多种数据类型,使得开发者可以非常灵活地处理各种数据结构和场景。
- 原子操作:Redis 的所有操作都是原子性的,这意味着多个客户端同时操作同一个数据项时,Redis 可以保证操作的串行化。
- 事务支持:Redis 提供了简单的事务支持,通过 MULTI、EXEC、DISCARD 和 WATCH 命令来实现。
- 持久化:Redis 支持两种持久化方式,RDB(Redis Database)快照和 AOF(Append Only File)日志,可以保障数据的可靠性。
- 发布/订阅:Redis 提供了发布/订阅模式,使得数据可以在多个客户端之间实时共享。
- 高可用性和分布式:通过 Redis Sentinel 和 Redis Cluster,Redis 可以实现高可用性和分布式存储,确保数据的可用性和容错性。
- 简单和易用:Redis 的命令行界面简洁且功能强大,同时提供了丰富的客户端库,支持多种编程语言,如 Python、Java、C++ 等。
缺点:
- 内存限制:由于Redis将数据存储在内存中,因此其存储容量受到物理内存大小的限制。当数据量非常大时,可能会耗尽服务器的内存资源。
- 数据持久化的开销:虽然Redis提供了持久化功能,但RDB和AOF都会带来一定的性能开销。特别是在高负载的情况下,持久化可能会成为系统的瓶颈。
- 网络依赖:Redis是一个基于网络的数据库,客户端与服务器之间的通信需要通过网络进行。因此,Redis的性能和可靠性会受到网络状况的影响。
- 复杂的数据结构操作可能较慢:虽然Redis支持多种复杂的数据结构,但在某些情况下,对这些数据结构进行复杂操作(如深度遍历、复杂查询等)可能会比较慢,因为Redis并不是为这些操作而优化的。
- 单线程模型:Redis采用单线程模型来处理客户端的请求。虽然这保证了操作的原子性和简化了数据一致性的处理,但在高并发场景下,单个CPU核心可能成为性能瓶颈。不过,由于Redis的I/O操作是异步的,并且内存访问速度非常快,因此在实际应用中,Redis的性能通常仍然很高。
基本数据结构:
String(字符串):最基本的键值对类型,支持存储二进制数据。
List(列表):一个链表,可以从两端插入和弹出元素。
Set(集合):无序集合,适合去重。
Hash(哈希):键值对的集合,类似于一个字段集合。
Sorted Set(有序集合):和 Set 类似,但可以给每个元素附加一个分数,Redis 会根据分数排序。
Bitmap:一种用于处理二进制数据的结构。
HyperLogLog:用于估算基数(例如,去重统计)。
使用场景:
缓存:Redis 常被用作缓存,来加速对数据的访问,减少数据库的压力。
消息队列:Redis 支持发布/订阅模式,也可以用作轻量级的消息队列。
分布式锁:通过 SETNX
(SET if Not eXists) 命令可以实现分布式锁。
会话存储:在 Web 应用中可以将用户的会话信息存储在 Redis 中,实现快速访问和持久化。
排行榜系统:利用 Redis 的有序集合可以快速实现排行榜功能。
基本命令:
键操作:
SET key value
:设置一个键值对。GET key
:获取某个键的值。DEL key
:删除一个键。EXPIRE key seconds
:为键设置过期时间。
列表操作:
LPUSH key value
:向列表左侧添加一个元素。RPOP key
:从列表右侧弹出一个元素。
集合操作:
SADD key value
:向集合添加元素。SMEMBERS key
:获取集合中的所有元素。
哈希操作:
HSET key field value
:为哈希表中的字段设置值。HGET key field
:获取哈希表中某个字段的值。
有序集合操作:
ZADD key score member
:添加一个带有分数的成员。ZRANGE key start stop
:按分数范围获取有序集合中的元素。
高级特性:
高可用与复制:
Redis 的高可用性依赖于主从复制(Replication)和哨兵(Sentinel)机制。
-
主从复制:Redis 支持主从架构,数据可以自动从主节点复制到从节点,保证数据的冗余和高可用。主节点进行写操作,从节点作为备份并允许读取操作。这样在主节点失效时,可以提升从节点为主节点,避免数据丢失。
-
Redis Sentinel:是 Redis 的高可用解决方案,用于监控 Redis 集群中的主从节点。Sentinel 会定期检查 Redis 主节点的状态,发现主节点宕机时会自动进行主从切换。
Sentinel 的职责:监控、通知、自动故障转移以及配置提供。
集群:
Redis Cluster 是 Redis 的分布式方案,允许将数据自动分片到多个 Redis 实例上。
- 分片机制:Redis Cluster 将键值空间分为 16384 个槽,数据根据哈希分布在多个节点上。每个节点负责一部分槽位的数据存储。当一个节点出现故障时,集群可以通过冗余机制保持数据的可用性。
- 节点故障检测和恢复:Redis Cluster 具备自动故障转移的功能,确保在部分节点失效时,数据仍然可用。
持久化机制:
Redis 的持久化保证即使在服务器重启或宕机时,数据也可以恢复。Redis 提供两种持久化方案:
- RDB(Redis Database Backup):定期将数据的快照保存到磁盘,适用于不需要实时保存数据的场景。虽然它对性能的影响较小,但可能会导致数据丢失,因为只在指定时间点生成快照。
- AOF(Append-Only File):记录每次写操作的日志,保证数据不丢失。Redis 可以通过定期对 AOF 文件进行压缩来减少磁盘空间的占用。AOF 相对较慢,但能够提供更强的数据安全性。
缓存过期策略:
-
LRU (Least Recently Used):最少使用算法,会优先删除最近最少使用的键。
-
LFU (Least Frequently Used):最少频率使用算法,会优先删除访问频率最低的键。
-
TTL (Time to Live):为键设置一个明确的过期时间,过期后自动删除。
使用 StackExchange.Redis 在 C# 中开始使用 Redis
在开始在 C# 中使用 Redis 之前,您需要安装 StackExchange.Redis 库。您可以使用 Visual Studio/Rider 中的 NuGet 包管理器或通过命令行来执行此操作。
Install-Package StackExchange.Redis
首先,让我们连接到 Redis 服务器。确保您有一个在默认端口 (6379) 上本地运行的 Redis 服务器。如果您的 Redis 服务器托管在其他地方,您可以调整连接参数。
class Program
{
static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
// 读取 Redis 连接字符串
var redisConnectionString = builder.Configuration.GetValue<string>("RedisCacheConnectionString");
// 注册 Redis 连接多路复用器
builder.Services.AddSingleton<IConnectionMultiplexer>(
ConnectionMultiplexer.Connect(string.IsNullOrEmpty(redisConnectionString) ? "localhost" : redisConnectionString));
}
}
public interface ICacheService
{
Task SetRedisByKey<T>(string key, T value, TimeSpan? expiry = null) where T: class;
Task<T?> GetByKeyFromRedis<T>(string key) where T: class;
Task RemoveByKeyFromRedis(string key);
}
public class CacheService : ICacheService
{
private readonly IConnectionMultiplexer _connectionMultiplexer;
public CacheService(IConnectionMultiplexer connectionMultiplexer)
{
_connectionMultiplexer = connectionMultiplexer;
}
public async Task SetRedisByKey<T>(string key, T value, TimeSpan? expiry = null) where T: class
{
if (value != null)
{
expiry ??= TimeSpan.FromMinutes(5);
var stringValue = value as string ?? JsonConvert.SerializeObject(value);
await _connectionMultiplexer.GetDatabase().StringSetAsync(key, stringValue, expiry).ConfigureAwait(false);
}
}
public async Task<T?> GetByKeyFromRedis<T>(string key) where T: class
{
var cachedResult = await _connectionMultiplexer.GetDatabase().StringGetAsync(key).ConfigureAwait(false);
return !cachedResult.IsNullOrEmpty
? typeof(T) == typeof(string) ? cachedResult.ToString() as T :
JsonConvert.DeserializeObject<T>(cachedResult)
: default;
}
public async Task RemoveByKeyFromRedis(string key)
{
var db = _connectionMultiplexer.GetDatabase();
await db.KeyDeleteAsync(key);
}
}
Redis 提供多种数据结构,如 String
、Hash
、List
、Set
、Sorted Set
等。下面展示如何使用 StackExchange.Redis
操作这些数据结构:
1、操作字符串
// 设置一个字符串值
await _connectionMultiplexer.GetDatabase().StringSetAsync("name", "Alice");
// 获取字符串值
var name = await _connectionMultiplexer.GetDatabase().StringGetAsync("name");
2、操作哈希 (Hash)
// 设置哈希字段
await _connectionMultiplexer.GetDatabase().HashSetAsync("user:1001", new HashEntry[]
{
new HashEntry("name", "Alice"),
new HashEntry("age", 30)
});
// 获取哈希字段的值
var name = await _connectionMultiplexer.GetDatabase().HashGetAsync("user:1001", "name");
3、操作列表 (List)
// 向列表的左侧添加元素
await _connectionMultiplexer.GetDatabase().ListLeftPushAsync("tasks", "Task 1");
// 从列表右侧弹出元素
var task = await _connectionMultiplexer.GetDatabase().ListRightPopAsync("tasks");
4、操作集合 (Set)
// 添加元素到集合
await _connectionMultiplexer.GetDatabase().SetAddAsync("skills", "C#");
await _connectionMultiplexer.GetDatabase().SetAddAsync("skills", "Redis");
// 获取集合中的所有元素
var skills = await _connectionMultiplexer.GetDatabase().SetMembersAsync("skills");
5、操作有序集合 (Sorted Set)
// 向有序集合添加元素及其分数
await _connectionMultiplexer.GetDatabase().SortedSetAddAsync("leaderboard", "Alice", 1000);
await _connectionMultiplexer.GetDatabase().SortedSetAddAsync("leaderboard", "Bob", 1500);
// 获取有序集合中前 10 名的成员
var topPlayers = await _connectionMultiplexer.GetDatabase().SortedSetRangeByRankAsync("leaderboard", 0, 9);