一、简介
Redis有5种基本数据结构,分别是string、list(列表)、hash(字典)、set(集合)、zset(有序集合),这是必须掌握的5种基本数据结构.注意Redis作为一个键值对缓存系统,其所有的数据结构,都以唯一的key(字符串)作为名称,然后通过key来获取对应的数据.
二、.Net开发环境搭建
这个版本,暂时不考虑并发问题,后续的文章会说!
第一步:安装StackExchange.Redis包,我用的是2.0.519版本的.
第二步:编写代码,采用扩展方法的链式编程模式+async/await的编程模型
AppConfiguration.cs 全局配置类
???/// <summary> ???/// 全局配置类 ???/// </summary> ???public class AppConfiguration ???{ ???????/// <summary> ???????/// 单例实现,static关键字默认加锁 ???????/// </summary> ???????static AppConfiguration() ???????{ ???????????Current = new AppConfiguration(); ???????} ???????public static readonly AppConfiguration Current; ???????/// <summary> ???????/// 配置完Redis之后,所有需要的Redis基础服务对象,都在这里面 ???????/// </summary> ???????public RedisConfigurations RedisConfigurations { get; set; } ???}
FluentConfiguration.cs 链式配置核心类
???/// <summary> ???/// 链式编程模式,扩展方法实现 ???/// </summary> ???public static class FluentConfiguration ???{ ???????/// <summary> ???????/// 配置Redis ???????/// </summary> ???????/// <typeparam name="T"></typeparam> ???????/// <param name="configuration"></param> ???????/// <returns></returns> ???????public static AppConfiguration ConfigureRedis<T>(this AppConfiguration configuration) where T: IRedisConfig, new() ???????{ ???????????if (configuration == null) ???????????????throw new ArgumentNullException("configuration"); ???????????var config = new T(); ???????????var redisConfigurations=config.ConfigRedis(); ???????????configuration.RedisConfigurations = redisConfigurations; ???????????return configuration; ???????} ???}
RedisConfigurations.cs Redis全局配置共享类
???/// <summary> ???/// Redis配置完毕后,返回需要使用的相关对象 ???/// </summary> ???public class RedisConfigurations ???{ ???????public IConnectionMultiplexer ConnectionMultiplexer { get; set; } ???}
RedisConfig.cs Redis配置类
???/// <summary> ???/// Redis配置类 ???/// </summary> ???public class RedisConfig : IRedisConfig ???{ ???????/// <summary> ???????/// 比较耗费资源,所以写入缓存,全局共享 ???????/// 封装了Redis基础服务对象的详细信息 ???????/// </summary> ???????public static ConnectionMultiplexer ConnectionMultiplexer { get; } ???????/// <summary> ???????/// 可能存在线程安全的配置,或者只需要初始化一次的配置,放这里 ???????/// </summary> ???????static RedisConfig() ???????{ ???????????//暂时读配置文件,后期可以用Core的配置文件系统读json文件 ???????????var redisServerAdress = ConfigurationManager.AppSettings["RedisServerAdress"]; ???????????if (string.IsNullOrEmpty(redisServerAdress)) ???????????????throw new ApplicationException("配置文件中未找到RedisServer的有效配置"); ???????????ConnectionMultiplexer = ConnectionMultiplexer.Connect(redisServerAdress); ???????} ???????/// <summary> ???????/// 配置Redis ???????/// </summary ???????public RedisConfigurations ConfigRedis() ???????{ ???????????var config = new RedisConfigurations(); ???????????config.ConnectionMultiplexer = ConnectionMultiplexer; ???????????return config; ???????} ???}
相关约束接口如下:
???/// <summary> ???/// Redis配置约束 ???/// </summary> ???public interface IRedisConfig ???{ ???????/// <summary> ???????/// 配置Redis ???????/// </summary> ???????RedisConfigurations ConfigRedis(); ???} ???/// <summary> ???/// Redis客户端实例约束接口 ???/// </summary> ???public interface IRedisInstance ???{ ???}
RedisClient.cs Redis客户端调用类
???/// <summary> ???/// 基于async和await的异步操作的Redis客户端,有效利用CPU资源 ???/// </summary> ???public class RedisClient: IRedisInstance ???{ ???????private static RedisConfigurations RedisConfigurations { get; } ???????static RedisClient() ???????{ ???????????RedisConfigurations=AppConfiguration.Current.RedisConfigurations; ???????} ???????/// <summary> ???????/// 异步,写入键值对 ???????/// </summary> ???????/// <param name="key"></param> ???????/// <param name="value"></param> ???????/// <returns></returns> ???????public static async Task<bool> StringSetAsync(string key,string value) ???????{ ???????????var db=GetDatabase(); ???????????return await db.StringSetAsync(key,value); ???????} ???????/// <summary> ???????/// 根据传入键,异步获取对应的值 ???????/// </summary> ???????/// <param name="key"></param> ???????/// <returns></returns> ???????public static async Task<string> StringGetAsync(string key) ???????{ ???????????var db = GetDatabase(); ???????????return await db.StringGetAsync(key); ???????} ???????/// <summary> ???????/// 异步判断是否存在某个键 ???????/// </summary> ???????/// <param name="key"></param> ???????/// <returns></returns> ???????public static async Task<bool> KeyExistsAsync(string key) ???????{ ???????????var db = GetDatabase(); ???????????return await db.KeyExistsAsync(key); ???????} ???????/// <summary> ???????/// 异步删除某个键 ???????/// </summary> ???????/// <param name="key"></param> ???????/// <returns></returns> ???????public static async Task<bool> KeyDeleteAsync(string key) ???????{ ???????????var db = GetDatabase(); ???????????return await db.KeyDeleteAsync(key); ???????} ???????/// <summary> ???????/// Redis DataBase工厂方法 ???????/// </summary> ???????/// <returns></returns> ???????private static IDatabase GetDatabase() ???????{ ???????????return RedisConfigurations.ConnectionMultiplexer.GetDatabase(); ???????} ???}
暂时只扩展了一些方法,或许会持续扩展.
Program.cs 控制台入口类
???class Program ???{ ???????static Program() ???????{ ???????????//链式配置Redis ???????????AppConfiguration.Current.ConfigureRedis<RedisConfig>(); ???????} ???????static void Main(string[] args) ???????{ ???????????StringSetGetAsync(); ???????????Console.ReadKey(); ???????} ???????static async void StringSetGetAsync() ???????{ ???????????if (await RedisClient.StringSetAsync("name", "xiaochao")) ???????????{ ???????????????Console.WriteLine("Redis中键为name的值为:{0}", await RedisClient.StringGetAsync("name")); ???????????} ???????????else { ???????????????Console.WriteLine("写入异常"); ???????????} ???????} ???}
ok,到这里.Net下使用StackExchange.Redis包操作Redis的环境构建完毕.
运行代码:
控制台环境:
Redis桌面管理工具
Linux下Redis-cli
后续的文章都会围绕上面三个操作方式展开.
三、string(字符串)
1、简单键值对操作
字符串string是Redis中最简单的数据类型,内部原理和C#的string类型一样,是一个字符数组.常见的用法是缓存一些用户数据,将用户数据序列化程Json,然后以用户Id作为键值,然后将用户数据存入Redis中.获取的时候,只需要通过用户Id去获取,然后将Json反序列化成对应的实体.
注:Redis的string类型是动态字符串,而且支持修改,这和C#中的string不一样,内部结构类似于C#中的List,有一个初始大小,如果存入string的长度大小大于string的初始大小,那么每次都会扩展1倍的大小.但是字符串最大长度只能为512MB.
代码实战:
(1)、Linux终端
(2)、C#控制台
修改控制台方法如下:
???????static void Main(string[] args) ???????{ ???????????StringSetGetAsync(); ???????????Console.ReadKey(); ???????} ???????static async void StringSetGetAsync() ???????{ ???????????var key = "name"; ???????????if (await RedisClient.StringSetAsync(key, "xiaochao")) ???????????{ ???????????????Console.WriteLine("Redis中键为name的值为:{0}", await RedisClient.StringGetAsync(key)); ???????????????if (await RedisClient.KeyExistsAsync(key)) ???????????????{ ???????????????????Console.WriteLine("Redis中,存在key为name的键值对"); ???????????????} ???????????????if (await RedisClient.KeyDeleteAsync(key)) ???????????????{ ???????????????????Console.WriteLine($"删除键:{key}成功"); ???????????????????if (await RedisClient.KeyExistsAsync(key)) ???????????????????????Console.WriteLine($"{key}存在,删除失败"); ???????????????????else ???????????????????????Console.WriteLine($"{key}不存在了,被删除了"); ???????????????} ???????????} ???????????else { ???????????????Console.WriteLine("写入异常"); ???????????} ???????}
桌面管理工具:
2、批量键值对操作
C#控制台:首先引入Newtonsoft.Json包
修改RedisClient.cs如下,给它扩展两个方法
???????/// <summary> ???????/// 异步批量插入键值对 ???????/// </summary> ???????/// <param name="keyValuePair"></param> ???????/// <returns></returns> ???????public static async Task<bool> StringSetAsync(KeyValuePair<RedisKey, RedisValue>[] keyValuePair) ???????{ ???????????var db = GetDatabase(); ???????????return await db.StringSetAsync(keyValuePair); ???????} ???????/// <summary> ???????/// 异步批量获取值 ???????/// </summary> ???????/// <param name="keyValuePair"></param> ???????/// <returns></returns> ???????public static async Task<RedisValue[]> StringGetAsync(RedisKey[] keys) ???????{ ???????????var db = GetDatabase(); ???????????return await db.StringGetAsync(keys); ???????}
Program.cs如下:
???class Program ???{ ???????static Program() ???????{ ???????????//链式配置Redis ???????????AppConfiguration.Current.ConfigureRedis<RedisConfig>(); ???????} ???????static void Main(string[] args) ???????{ ???????????StringSetGetAsync(); ???????????Console.ReadKey(); ???????} ???????static async void StringSetGetAsync() ???????{ ???????????var userInfos = UserInfo.UserInfos; ???????????var keyValues = new KeyValuePair<RedisKey, RedisValue>[userInfos.Count]; ???????????var keys =new RedisKey[userInfos.Count]; ???????????for (var i = 0; i < userInfos.Count; i++) ???????????{ ???????????????var currUserInfo = userInfos[i]; ???????????????var key = currUserInfo.Id.ToString(); ???????????????var value = JsonConvert.SerializeObject(currUserInfo); ???????????????keyValues[i] = new KeyValuePair<RedisKey, RedisValue>(key, value); ???????????????keys[i] = key; ???????????} ???????????if (await RedisClient.StringSetAsync(keyValues)) ???????????{ ???????????????try ???????????????{ ???????????????????var values = await RedisClient.StringGetAsync(keys); ???????????????????for (var i = 0; i < values.Length; i++) ???????????????????{ ???????????????????????Console.WriteLine(values[i]); ???????????????????} ???????????????} ???????????????//捕获辅助线程产生的异常 ???????????????catch (AggregateException ex) ???????????????{ ???????????????????ex.Handle(x => ???????????????????{ ???????????????????????//记录日志 ???????????????????????Console.WriteLine("异常处理完毕,批量获取值失败!"); ???????????????????????return true; ???????????????????}); ???????????????} ???????????} ???????????else ???????????{ ???????????????//记录日志 ???????????????Console.WriteLine("写入异常"); ???????????} ???????} ???????class UserInfo ???????{ ???????????public Guid Id { get; set; } ???????????public string Name { get; set; } ???????????public int Age { get; set; } ???????????internal static List<UserInfo> UserInfos = new List<UserInfo>() ???????????{ ???????????????new UserInfo() ???????????????{ ???????????????????Id=Guid.NewGuid(), ???????????????????Name="小超", ???????????????????Age=23 ???????????????}, ????????????????new UserInfo() ???????????????{ ???????????????????Id=Guid.NewGuid(), ???????????????????Name="大超", ???????????????????Age=23 ???????????????}, ???????????}; ???????} ???}
(2)、管理工具
(3)、Linux终端
3、过期时间
Redis可以给Key设置过期时间,到达设置的时间,对应的键值对会被删除,内存会被回收,这个功能常用来控制缓存的失效时间.这里这个自动删除的机制很复杂,这里不想说太多,只介绍基本用法,后续的文章会介绍.
C#控制台,修改RedisClient.cs中的StringSetAsync方法如下:
???????/// <summary> ???????/// 异步,写入键值对,可指定过期时间 ???????/// </summary> ???????/// <param name="key"></param> ???????/// <param name="value"></param> ???????/// <param name="expireTime">过期时间</param> ???????/// <returns></returns> ???????public static async Task<bool> StringSetAsync(string key,string value, TimeSpan? expireTime=null) ???????{ ???????????var db=GetDatabase(); ???????????return await db.StringSetAsync(key,value, expireTime); ???????}
Program.cs代码如下:
???class Program ???{ ???????static Program() ???????{ ???????????//链式配置Redis ???????????AppConfiguration.Current.ConfigureRedis<RedisConfig>(); ???????} ???????static void Main(string[] args) ???????{ ???????????StringSetGetAsync(); ???????????Console.ReadKey(); ???????} ???????static async void StringSetGetAsync() ???????{ ????????????????????if (await RedisClient.StringSetAsync("name","xiaochao",TimeSpan.FromSeconds(2))) ???????????{ ???????????????Console.WriteLine("Redis中存在键为name的键值对,值为:{0}",await RedisClient.StringGetAsync("name")); ???????????????await Task.Delay(2000);//模拟休息两秒 ???????????????Console.WriteLine("休息两秒后,Redis的键为name的键值对:{0}", string.IsNullOrEmpty(await RedisClient.StringGetAsync("name")) ? "不存在" : "存在"); ???????????} ???????????else ???????????{ ???????????????//记录日志 ???????????????Console.WriteLine("写入异常"); ???????????} ???????} ???}
这边其它两个终端就不演示了,自行观察.
4、计数器
Redis提供了自增命令,前提操作的数据必须是整数,而且自增是有范围的.默认对应long的最大值,一般达不到这个值.
C#控制台:
修改RedisClient.cs下的StringSetAsync方法如下:
???????/// <summary> ???????/// 异步,写入键值对,可指定过期时间 ???????/// </summary> ???????/// <param name="key"></param> ???????/// <param name="value"></param> ???????/// <param name="expireTime">过期时间</param> ???????/// <returns></returns> ???????public static async Task<bool> StringSetAsync(string key,RedisValue value, TimeSpan? expireTime=null) ???????{ ???????????var db=GetDatabase(); ???????????return await db.StringSetAsync(key, value, expireTime); ???????}
Program.cs代码如下:
???class Program ???{ ???????static Program() ???????{ ???????????//链式配置Redis ???????????AppConfiguration.Current.ConfigureRedis<RedisConfig>(); ???????} ???????static void Main(string[] args) ???????{ ???????????StringSetGetAsync(); ???????????Console.ReadKey(); ???????} ???????static async void StringSetGetAsync() ???????{ ????????????????????if (await RedisClient.StringSetAsync("站点首页",0)) ???????????{ ??????????????????????????????????//模拟用户访问 ???????????????????Parallel.For(0, 250000, async (i, ParallelLoopState) => ????????????????????{ ????????????????????????try ????????????????????????{ ????????????????????????????await RedisClient.StringIncrementAsync("站点首页"); ????????????????????????} ????????????????????????catch (RedisServerException ex) ????????????????????????{ ????????????????????????????//记录日志 ????????????????????????????Console.WriteLine(ex.Message); ????????????????????????????ParallelLoopState.Stop(); ????????????????????????????return; ????????????????????????} ????????????????????}); ???????????????????//输出站点的UV ???????????????????Console.WriteLine(await RedisClient.StringGetAsync("站点首页")); ???????????} ???????????else ???????????{ ???????????????//记录日志 ???????????????Console.WriteLine("写入异常"); ???????????} ???????} ???}
注:这里存在两个问题,如果你把Parallel的上限值设置的过大,那么短时间内,可能Redis无法处理这么多的并发量,而报超时错误,这个时候,解决方案是使用集群的方式,解决这个问题,但是这里就不演示了.第二个问题是,如果把Set的初始值设为Long.MaxValue,那么Redis会报溢出错误,上面的代码已经处理.
Redis学习系列二之.Net开发环境搭建及基础数据结构String字符串
原文地址:https://www.cnblogs.com/GreenLeaves/p/10165352.html