zoukankan      html  css  js  c++  java
  • Redis之品鉴之旅(一)

    Redis之品鉴之旅(一)

    好知识就如好酒,需要我们坐下来,静静的慢慢的去品鉴。Redis作为主流nosql数据库,在提升性能的方面是不可或缺的。下面就拿好小板凳,我们慢慢的来一一品鉴。

    1)redis号称是大数据高并发的利器,那么到底什么是redis?

    redis是nosql(not only sql,不仅仅是sql语句),它提供了各种操作的api,不像我们的关系型数据库使用sql语句来操作数据库。

    redis全称Remote Dictionary Server(远程字典服务),首先是一个字典,其次是内存数据库(读写速度:CPU>内存>硬盘)。

    redis方便扩展,关系型数据库在进行数据扩展时需要 一些列的操作(比如使用了ORM,需要再实体类中增加字段,然后修改dto,修改映射,最后做数据迁移),相对而言redis就会简化很多,只要是数据都可以直接存进去,不需要提前去规范字段。

    2)redis8种数据结构:string,hash,list,set,zset,bitmaps,hyperlogloss,streams。前5种是最常用的,后面3中比较少用。

    3)redis分布式存储,多台机器实现一个内存共享

    4)redis是单线程,但不是redis服务只有一个线程。

    单线程的原子性可以理解为不需要锁,不需要上下文切换。单线程就是一个任务一个人做,多线程可以理解成一个任务多个人做,肯定有一个指导这些人去合理做的人。如果需要多线程实现原子性,就会涉及到各种锁、上下文切换 。典型的单线程服务就是nginx,但是在大数据高并发下nginx为什么还是那么的快呢?这就是使用了IO多路复用,redis同样使用,原理一模一样。

    后面如果有时间会单独对IO多路复用进行理解。

    5)使用的redis客户端的库:ServiceStack.Redis。这个库对客户端的请求操作是有限制的,每小时6000次,但是可以通过修改源码的形式进行限制,不建议反编译dll。

    下面我们看一下各种数据结果的API

    1)string类型,先上代码

    //IP地址,端口,密码,db序号(默认0)
    using (RedisClient client = new RedisClient("127.0.0.1", 6379, "12345", 10))
    {
        //删除当前数据库中的所有Key
        client.FlushDb();
        //删除所有数据库中的key,慎用 
        //client.FlushAll();
        //统计网站访问数量、当前在线人数、微博数、粉丝数等,全局递增ID等
        #region 设置key的value
        client.FlushDb();
        client.Set<string>("name", "测试数据123");
        Console.WriteLine("错误输出");
        Console.WriteLine(client.GetValue("name"));
        Console.WriteLine("正确输出");
        Console.WriteLine(client.Get<string>("name"));
        Console.WriteLine(JsonConvert.DeserializeObject<string>(client.GetValue("name")));
    
        //client.Set<int>("keyInt", 123);
        //Console.WriteLine("输出1");
        //Console.WriteLine(client.GetValue("keyInt"));
        //Console.WriteLine("输出2");
        //Console.WriteLine(client.Get<int>("keyInt"));
        //Console.WriteLine(JsonConvert.DeserializeObject<int>(client.GetValue("keyInt")));
    
        #endregion
    
        #region 设置多个key的value
        //批量的写入redis key
        client.FlushDb();
        client.SetAll(new Dictionary<string, string> { { "id", "001" }, { "name", "world" } });
        //批量读取内存中多个key的结果 如果我们获取的key不存在,程序会返回一个空的字符串,这redis不会报错
        //来判断当前用户是否是老用户
        var getall = client.GetAll<string>(new string[] { "id", "name", "number" });
        foreach (var item in getall)
        {
            Console.WriteLine(item);
        }
        #endregion
    
        #region  设置key的value并设置过期时间
        client.FlushDb();
        client.Set<string>("name", "测试数据123", TimeSpan.FromSeconds(1));
        Task.Delay(1 * 1000).Wait();
        Console.WriteLine(client.Get<string>("name"));
        #endregion
    
        #region 设置key的value并设置过期时间
    
        client.FlushDb();
        client.Set<string>("name", "测试数据123", DateTime.Now.AddSeconds(1));
        Console.WriteLine("刚写进去的结果");
        Console.WriteLine(client.Get<string>("name"));
        Task.Delay(1 * 1000).Wait();
        Console.WriteLine("1秒钟之后的结果");
        Console.WriteLine(client.Get<string>("name"));
    
        client.Set<string>("class", "优秀班级", TimeSpan.FromSeconds(10));
        Task.Delay(1 * 1000).Wait();
        Console.WriteLine(client.Get<string>("class"));
        #endregion
    
        #region 在原有key的value值之后追加value
        client.FlushDb();
        client.AppendToValue("name3", "I");
        client.AppendToValue("name3", " ");
        client.AppendToValue("name3", "LOVE YOU");
        Console.WriteLine(client.Get<string>("name3"));
        #endregion
    
        #region 获取旧值赋上新值
        client.FlushDb();
        client.Set("name", "测试数据123");
        //获取当前key的之前的值,然后把新的结果替换进入
        var value = client.GetAndSetValue("name", "world");
        Console.WriteLine("原先的值" + value);
        Console.WriteLine("新值" + client.GetValue("name"));
        #endregion
    
        #region 自增1,返回自增后的值
        //给key为sid的键自增1 ,返回了自增之后的结果,如果键不存在,则默认将初始值赋0,自增之后就是1
        client.FlushDb();
        Console.WriteLine(client.Incr("sid"));
        Console.WriteLine(client.Incr("sid"));
        Console.WriteLine(client.Incr("sid"));
        Console.WriteLine("华丽丽的结束");
    
        Console.WriteLine(client.GetValue("sid"));
        //每次通过传递的count累计,count就是累加的值
        client.FlushDb();
        client.IncrBy("sid", 2);
        Console.WriteLine(client.Get<string>("sid"));
        client.IncrBy("sid", 100);
        Console.WriteLine("最后的结果***" + client.GetValue("sid"));
        #endregion
    
        #region 自减1,返回自减后的值
        client.FlushDb();
        Console.WriteLine(client.Decr("sid"));
        Console.WriteLine(client.Decr("sid"));
        Console.WriteLine(client.Decr("sid"));
        Console.WriteLine("最后的结果" + client.GetValue("sid"));
        //通过传入的count去做减肥 之前的结果-count
        client.DecrBy("sid", 2);
        Console.WriteLine("最终的结果" + client.GetValue("sid"));
        #endregion
    
        #region add 和set 的区别?
        client.FlushDb();
        //当使用add 方法去操作redis的时候,如果key存在的话,则不会再次进行操作 返回false 如果操作成功返回true
        Console.WriteLine(client.Add("name1", "world"));
        //用add的时候帮你去判断如果有则不进行操作,如果没有则写,它只能写新值,不能修改
        Console.WriteLine(client.Add("name1", "你很好world"));
        Console.WriteLine(client.Get<string>("name1"));
    
    
        //使用set去操作 redis的时候,如果key不存在则写入当前值,并且返回true,通过存在,则对之前的值进行了一个替换 返回操作的结果
        Console.WriteLine(client.Set("name2", "world"));
        Console.WriteLine(client.Set("name2", "你很好world"));
        Console.WriteLine(client.Get<string>("name2"));
        #endregion
    
    }
    

    注意:尽量不是要使用GetValue,这个api的返回值是带有双引号的内容,那是因为redis默认将数据进行了序列化。这里推荐使用Get泛型的方式进行获取,这里特指的是string类型的数据。而对Int类型的数据,这里返回的结果是一致的。

    自减的情况我们可以使用在减库存的场景。set和add的区别:add只做新增的工作,set既能够新增又能够改变。

    抢购前景模拟代码:

    public static void Show(string id, int minute)
    {
    	#region 自减1,返回自减后的值
    	//开启10个线程去抢购
    	Console.WriteLine($"在{minute}分0秒正式开启秒杀!");
    	var flag = true;
    	while (flag)
    	{
    		if (DateTime.Now.Minute == minute)
    		{
    			flag = false;
    			for (int i = 0; i < 10; i++)
    			{
    				string name = $"客户端{id}号:{i}";
    				Task.Run(() =>
    				{
    					using (RedisClient client = new RedisClient("127.0.0.1", 6379, "12345", 10))
    					{
    						//本来是二步走
    						//1 订单+1
    						//2 库存-1 //同一个时间片。只会执行一条指令
    						var num = client.Decr("Stock");
    						if (num < 0)
    						{
    							Console.WriteLine(name + "抢购失败");
    						}
    						else//>=0
    						{
    							Console.WriteLine(name + "**********抢购成功***************");
    							client.Incr("Order");
    						}
    					}
    				});
    				Thread.Sleep(10);
    			}
    		}
    		Thread.Sleep(10);
    	}
    
    	Console.ReadLine();
    	#endregion
    }
    
    
    //////命令行参数启动
    ////dotnet RedisSeckill.dll --id=1 minute=03
    var builder = new ConfigurationBuilder().AddCommandLine(args);
    var configuration = builder.Build();
    string id = configuration["id"];
    int minute = int.Parse(configuration["minute"]);
    Console.WriteLine("开始" + id);
    
    using (RedisClient client = new RedisClient("127.0.0.1", 6379, "12345", 10))
    {
        //首先给数据库预支了秒杀商品的数量,订单数初始为0
        client.Set<int>("Stock", 10);
        client.Set<int>("Order", 0);
    }
    Seckill.Show(id, minute);
    
    //多个用户抢购结束之后,订单数肯定是10,stock变成了-30,一共抢购了40次,10次成功,30次失败。
    

    在redis内部进行string类型数据存储时,内部有两种数据类型,分别是短数据类型、长数据类型。短数据类型存储时长度是受限制的,长数据类型内部会在string实际长度基础上进行额外空间的开辟,进行空间的预留,这就浪费了数据存储空间。这就是string类型的缺陷。

  • 相关阅读:
    [转帖]活用Quartus II内置模板,快速输入HDL代码、TimeQuset束缚及tcl语句等
    [笔记] FPGA的发展
    [转帖]状态机的编码
    [笔记]Altera中DDR3设计
    [笔记]Test Plan的编写 及 程序开头注释
    [HDOJ2457]DNA repair
    [HDOJ2355]The Sidewinder Sleeps Tonite
    [HDOJ2825]Wireless Password
    [HDOJ2222]Keywords Search
    [HDOJ2454]Degree Sequence of Graph G
  • 原文地址:https://www.cnblogs.com/vigorous/p/13538492.html
Copyright © 2011-2022 走看看