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会报溢出错误,上面的代码已经处理.

  • 相关阅读:
    背水一战 Windows 10 (61)
    背水一战 Windows 10 (60)
    背水一战 Windows 10 (59)
    背水一战 Windows 10 (58)
    背水一战 Windows 10 (57)
    背水一战 Windows 10 (56)
    背水一战 Windows 10 (55)
    背水一战 Windows 10 (54)
    背水一战 Windows 10 (53)
    背水一战 Windows 10 (52)
  • 原文地址:https://www.cnblogs.com/GreenLeaves/p/10165352.html
Copyright © 2011-2022 走看看