一、存放数据
安装:Install-Package StackExchange.Redis
建立连接
cs
ConfigurationOptions configurationOptions = new ConfigurationOptions
{
EndPoints = { { "127.0.0.1", 6379 } }, // Redis服务器地址和端口
};
ConnectionMultiplexer connection = ConnectionMultiplexer.Connect(configurationOptions);
IDatabase RedisDb = connection.GetDatabase();
1、字符串
存放
cs
RedisDb.StringSet("Mystring", "你好");
RedisDb.StringSetAsync("MystringAsync", "你好");
获取
cs
Console.WriteLine(RedisDb.StringGet("Mystring"));
Console.WriteLine(await RedisDb.StringGetAsync("MystringAsync"));
删除
cs
RedisDb.KeyDelete("Mystring");
RedisDb.KeyDeleteAsync("MystringAsync");
2、哈希
存放
cs
RedisDb.HashSet("MyHash",new HashEntry[]
{
new HashEntry("a","小明"),
new HashEntry("b","小红"),
new HashEntry("c","小军"),
});
RedisDb.HashSet("MyHash", "d", "小蓝");
获取
cs
Console.WriteLine(RedisDb.HashGet("MyHash", "b")); //获取单个字段
foreach(var item in RedisDb.HashGetAll("MyHash"))//获取表中所有字段
{
Console.WriteLine(item.Name+"===="+ item.Value);
}
删除
cs
RedisDb.HashDelete("MyHash", "a");//删除单个字段
RedisDb.KeyDelete("MyHash");//删除整个哈希表
3、列表
存放
cs
RedisDb.ListLeftPush("Mylist", "小绿");//列表头部添加
RedisDb.ListRightPush("Mylist", "小蓝");//列表尾部添加
获取
cs
RedisValue[] listitem = RedisDb.ListRange("Mylist",0,-1);//范围获取列表的值,(0到-1为所有值)(0到2为获取下标为0到2的值)
foreach(var item in listitem)
{
Console.WriteLine(item);
}
删除
cs
Console.WriteLine(RedisDb.ListLeftPop("Mylist"));//从列表头部取出值并删除
Console.WriteLine(RedisDb.ListRightPop("Mylist"));//从列表尾部取出值并删除
RedisDb.ListRemove("Mylist","小绿");//删除指定元素
RedisDb.KeyDelete("Mylist");//删除整个列表
4、集合
是一个无序且不包含重复元素的字符串集合,相同值只会存储一个。
存放
cs
bool isboot = RedisDb.SetAdd("Myset", "小绿");//会返回一个bool类型值,true为存放成功,false为存放失败,同时也表示该集合中存在该值
获取
cs
RedisValue[] setMembers = RedisDb.SetMembers("Myset");//获取集合中所有值
foreach(RedisValue member in setMembers)
{
Console.WriteLine(member);
}
Console.WriteLine(RedisDb.SetContains("Myset", "小绿"));//集合中是否有该值,true为有,反之
删除
cs
bool isRemoved = RedisDb.SetRemove("Myset", "小绿");//true删除成功,false删除失败,集合中无该值
二、原子性
原子性是指一系列操作在执行过程中不会被其他操作打断或干扰,即这些操作要么全部成功执行,要么全部不执行,这确保了数据的一致性和正确性。
cs
// 创建事务
var tran = RedisDb.CreateTransaction();
// 添加一个条件:表"Mylist" 必须存在,否则事务不执行
tran.AddCondition(Condition.KeyExists("Mylist"));
// 添加要执行的命令
tran.ListLeftPushAsync("Mylist", "你好");
tran.ListRightPushAsync("Mylist", "不好");
// 执行事务
bool committed = tran.Execute();//返回false条件不满足,事件不执行,上面操作不执行
三、数据持久化
Redis 提供了几种不同的持久化策略,其中最常见的是 RDB(Redis DataBase)快照和 AOF(Append Only File)日志。
两种方式的配置皆在Redis 服务器安装目录下的redis.conf文件当中配置,列如我这里的是redis.windows.conf文件,需要注意寻找以.conf结尾的文件。
1、RDB快照
RDB快照是在指定的时间间隔内,Redis 会创建一个数据快照并将其写入磁盘。
默认情况下,RDB 快照可能是启用的,可以通过查找save指令来确认和修改策略,save指令的格式为save <seconds> <changes>,表示在指定的秒数内如果有指定数量的键被修改,则创建一个快照,我们在.conf文件中查找save <seconds> <changes>,在下面进行配置。如:
bash
save 900 1 # 如果在 900 秒(15 分钟)内至少有 1 个键被修改,则保存快照
save 300 10 # 如果在 300 秒(5 分钟)内至少有 10 个键被修改,则保存快照
save 60 10000 # 如果在 60 秒内至少有 10000 个键被修改,则保存快照
配置完成之后需要保存然后重启Redis服务器,注意:频繁的快照可能会对 Redis 服务器的性能产生影响,特别是在高负载的情况下。
2、AOF 日志
AOF 日志是Redis 将每一个写命令追加到一个日志文件中。当 Redis 重启时,它会重新执行这些命令来恢复数据。
我们找到appendonly配置项,将其设置为yes,这即为启用AOF 日志。
bash
appendonly yes
配置 AOF 文件目录:
bash
appendfilename "文件名" # AOF 日志文件名
dir /.../.../.../ # AOF 日志文件存储目录
配置 AOF 同步策略 :
always 表示每个写命令都立即同步到磁盘,这提供了最高的数据安全性但可能会降低性能。
everysec 表示每秒同步一次。
no 表示不同步,可能会丢失数据但提供了更高的性能。如:
bash
appendfsync everysec
注意:AOF 日志可能会对磁盘 I/O 产生较高的负载。
四、分布式锁
分布式锁用于在分布式系统中控制对共享资源的访问,该方法的实现主要是利用Redis在对一个字符串类型的数据进行保存时,如果该字符串数据的键存在,那么会导致操作失败,利用这个特性就可以制作一把锁,这里的锁就是键值对,这个键存在就说明正在被使用,需要等待一段时间。
创建一个分布式锁的类RedisDistributedLock继承IDisposable并且实现父类的Dispose方法。
cs
public class RedisDistributedLock : IDisposable
{
private readonly IDatabase _db;
private readonly string _lockKey;
private readonly RedisValue _requestId;
private bool _isDisposed;
public RedisDistributedLock(IDatabase db, string lockKey, RedisValue redisValue, TimeSpan lockTimeout, TimeSpan lockWaitTimeout)
{
_db = db;
_lockKey = lockKey;
_requestId = redisValue;
// 尝试获取锁,如果Redis数据库中存在会返回false
var acquired = _db.StringSet(_lockKey, _requestId.ToString(), lockTimeout, When.NotExists, CommandFlags.DemandMaster);
if (!acquired)
{
// 如果锁已经被其他客户端持有,则等待一段时间再尝试获取
var waitStart = DateTime.UtcNow;
while ((DateTime.UtcNow - waitStart) < lockWaitTimeout)
{
Thread.Sleep(100); // 等待一段时间(例如100毫秒)
acquired = _db.StringSet(_lockKey, _requestId, lockTimeout, When.NotExists, CommandFlags.DemandMaster);
if (acquired) break;
}
}
if (!acquired)
{
throw new TimeoutException("Could not acquire the lock within the specified timeout.");
}
}
//这个接口主要用于释放非托管资源或执行任何必要的清理操作
public void Dispose()
{
if (!_isDisposed)
{
_isDisposed = true;
// 释放锁:只有锁的持有者才能释放锁
if (_db.StringGet(_lockKey) == _requestId)
{
_db.KeyDelete(_lockKey);
}
}
}
}
需要注意的是,我们创建利用完锁之后, 需要对该锁移除,这样才能保障下一个任务会执行,否则,一直不删除这个锁,会导致当前任务完成,后续任务一直处于等待状态,这里我们利用了Dispose方法对锁(也就是键值对)的删除。
使用分布式锁
cs
// 当离开 using 块时,锁会自动释放
using (var lockInstance = new RedisDistributedLock(RedisDb, "MyString","你好", TimeSpan.FromSeconds(20), TimeSpan.FromSeconds(5)))
{
// 在这里执行需要同步的代码
}
下面两个参数是我们所使用过的两个参数,这两个参数都是一个枚举。
1、when参数
用于确定何时应用过期时间。
When.Always
:无论键是否已存在,都应用过期时间。如果键不存在,操作会失败。
When.Exists
:仅当键已存在时应用过期时间。如果键不存在,操作不会执行,并且不会有任何错误。
When.NotExists
:仅当键不存在时尝试设置键并应用过期时间。如果键已存在,操作不会执行,并且不会有任何错误。
2、CommandFlags参数
为Redis命令提供额外的选项和指示。
None
:默认值,表示没有额外的标志被设置。
FireAndForget
:此标志用于指示命令应在后台执行,并且调用者不需要等待结果。当使用此标志时,被调用的方法会立即返回,而命令的执行则会在后台异步进行。
PreferMaster
:如果主服务器可用,此标志指示命令应在主服务器上执行。但请注意,这个标志也允许读操作在从服务器上执行。
DemandMaster
:此标志强制命令只在主服务器上执行。
PreferSlave
:如果从服务器可用,此标志指示命令应优先在从服务器上执行。
五、发布/订阅
发布/订阅模式的三个主要角色:
发送者(Publisher):负责发布消息到频道。
订阅者(Subscriber):订阅频道并接收从该频道发布的消息。
频道(Channel):发送者和订阅者之间传递消息的桥梁。
发布订阅的基本流程:订阅者通过Subscribe订阅一个或多个频道,发布者通过Publish向频道中发布消息,Redis接收到发布的消息存储在内部缓冲区中,一旦有订阅者订阅了相应的频道,Redis会将该频道缓冲区中的消息发送给订阅者,如果订阅者在消息发布后才进行订阅,那么它将不会收到之前发布的消息。
注意:Redis服务器重启或者断连等,消息会丢失。
1、发布
cs
ConfigurationOptions configurationOptions = new ConfigurationOptions
{
EndPoints = { { "127.0.0.1", 6379 } }, // Redis服务器地址和端口
};
ConnectionMultiplexer connection = ConnectionMultiplexer.Connect(configurationOptions);
IDatabase RedisDb = connection.GetDatabase();
// 发布消息到频道
RedisDb.Publish("mychannel", "你好");
connection.Close();
connection.Dispose();
2、订阅
这是一个新的项目,两个为不同项目(可以跨进程发送消息)。
cs
ConfigurationOptions configurationOptions = new ConfigurationOptions
{
EndPoints = { { "127.0.0.1", 6379 } }, // Redis服务器地址和端口
};
ConnectionMultiplexer connection = ConnectionMultiplexer.Connect(configurationOptions);
ISubscriber subscriber = connection.GetSubscriber();
// 订阅一个频道
subscriber.Subscribe("mychannel", (channel, message) =>
{
Console.WriteLine(channel+"===="+message);
});
// 等待用户输入,以保持程序运行并接收消息
Console.ReadKey();
// 取消订阅并关闭连接
subscriber.UnsubscribeAll();
connection.Close();
connection.Dispose();