zoukankan      html  css  js  c++  java
  • 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会报溢出错误,上面的代码已经处理.

  • 相关阅读:
    sharedWorker 实现多页面通信
    cookie跨域那些事儿
    event loop整理
    tsConfig/baseUrl -- 一键告别相对路径import
    vscode配置golang开发环境手把手描述篇
    Vue学习笔记二
    Vue学习笔记
    echarts迁移图动态加载
    病虫害可视化监测平台(一)
    昆虫识别开发进展APP(四)
  • 原文地址:https://www.cnblogs.com/GreenLeaves/p/10165352.html
Copyright © 2011-2022 走看看