Redis学习系列二之.Net开发环境搭建及基础数据结构String字符串
一、简介
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会报溢出错误,上面的代码已经处理.