zoukankan      html  css  js  c++  java
  • Redis .NET操作

    Redis是一个支持数据结构更多的键值对数据库。它的值不仅可以是字符串等基本数据类型,也可以是类对象,更可以是Set、List、计数器等高级的数据结构。
      Memcached也可以保存类似于Set、List这样的结构,但是如果说要向List中增加元素,Memcached则需要把List全部元素取出来,然后再把元素增加进去,然后再保存回去,不仅效率低,而且有并发访问问题。Redis内置的Set、List等可以直接支持增加、删除元素的操作,效率很高,操作是原子的。

    Memcached数据存在内存中,memcached重启后数据就消失;
    Redis会把数据持久化到硬盘中,Redis重启后数据还存在。

    1 安装

    redis for windows >=2.8的版本支持直接安装为windows服务(Redis-x64-3.2.100.msi才可以,zip不行)
    https://github.com/MicrosoftArchive/redis
    如果下载msi自动装完服务,如果下载zip需要按照下面的方法安装为服务:
    http://www.runoob.com/redis/redis-install.html

    2 redis与Memcached 区别

    2.1 redis优缺点

    2.1.1 redis的优点:

    1. 支持string、list、set、geo等复杂的数据结构。
    2. 高命中的数据运行时是在内存中,数据最终还是可以保存到磁盘中,这样服务器重启之后数据还在。
    3. 服务器是单线程的,来自所有客户端的所有命令都是串行执行的,因此不用担心并发修改(串行操作当然还是有并发问题)的问题,编程模型简单;
    4. 支持消息订阅/通知机制,可以用作消息队列;
    5. Key、Value最大长度允许512M;

    2.1.2 redis的缺点:

    1. Redis是单线程的,因此单个Redis实例只能使用一个CPU核,不能充分发挥服务器的性能。可以在一台服务器上运行多个Redis实例,不同实例监听不同端口,再互相组成集群。
    2. 做缓存性能不如Memcached;

    2.2 Memcached的优缺点

    2.2.1 Memcached的优点:

    1. 多线程,可以充分利用CPU多核的性能;
    2. 做缓存性能最高;

    2.2.2 Memcached的缺点:

    1. 只能保存键值对数据,键值对只能是字符串,如果有对象数据只能自己序列化成json
      字符串;
    2. 数据保存在内存中,重启后会丢失;
    3. Key最大长度255个字符,Value最长1M。

    2.3 总结

    Memcached只能当缓存服务器用,也是最合适的;Redis不仅可以做缓存服务器(性能没有Memcached好),还可以存储业务数据。

    3 redis命令行管理客户端

    3.1 直接启动redis安装目录下的redis-cli即可。

    执行set myKey abc,就是设置键值对myKey=abc
    执行get myKey就是查找名字是myKey的值;
    keys *是查找所有的key
    key *n*是查找所有名字中含有n的key

    3.2 数据没有隔离性

    和Redis一样,Redis也是不同系统放到Redis中的数据都是不隔离的,因此设定Key的时候也要选择好Key。

    3.3 尽量选用默认的数据库

    Redis服务器默认建了16个数据库,Redis的想法是让大家把不同系统的数据放到不同的数据库中。但是建议大家不要这样用,因为Redis是单线程的,不同业务都放到同一个Redis实例的话效率就不高,建议放到不同的实例中。因此尽量只用默认的db0数据库。

    命令行下可以用select 0、select 1这样的指令切换数据库,最高为15。试试在不同数据库下新建、查询数据。

    了解的常用的几个命令就可以。所有对数据的操作都可以通过命令行进行,后面讲的.net操作Redis的驱动其实就是对这些命令的封装。

    4 GUI管理客户端

    RedisDesktopManager (0.9.3以后需要订阅)
    https://github.com/uglide/RedisDesktopManager/releases

    5 .NET连接redis

    推荐组件:StackExchange.Redis
    https://stackexchange.github.io/StackExchange.Redis/

    其他作品:
    NewLife.Redis基础教程
    https://www.cnblogs.com/nnhy/p/icache.html

    using (ConnectionMultiplexer redis = ConnectionMultiplexer.Connect("localhost:6379")) 
    { 
     
    IDatabase db = redis.GetDatabase();
    //默认是访问db0数据库,可以通过方法参数指定数字访问不同的数据库 
     
    db.StringSet("Name", "abc"); 
    } 
    

    支持设置过期时间:

    db.StringSet("name", "rupeng.com", TimeSpan.FromSeconds(10)) 
    

    获取数据:

    string s = db.StringGet("Name")
    //如果查不到则返回null 
    

    Redis里所有方法几乎都支持异步,比如StringGetAsync()StringSetAsync(),尽量用异步方法。

    注意看到访问的参数、返回值是RedisKeyRedisValue类型,进行了运算符重载,可以和string
    byte[]之间进行隐式转换。

    6 命令

    6.1 键(Key)

    因为Redis里所有数据类型都是用KeyValue保存,因此Key操作针对所有数据类型,

    • KeyDelete(RedisKey key):根据Key删除;
    • KeyExists(RedisKey key)判断Key是否存在,尽量不要用,因为会有并发问题;
    • KeyExpire(RedisKey key, TimeSpan? expiry)KeyExpire(RedisKey key, DateTime? expiry) 设置过期时间;

    6.2 字符串(String)

    可以用 StringGetStringSet 来读写键值对,是基础操作
    StringAppend(RedisKey key, RedisValue value):向Key的Value中附加内容,不存在则新建;

    6.2.1 场景:计数器

    可以用作计数器:db.StringIncrement("count", 2.5); 给 count 这个计数器增加一个值,如果不存在
    则从0开始加;db.StringDecrement("count",1)计数器减值;获取还是用StringGet()获取字符串类型的
    值。比如可以用这个来计算新闻点击量、点赞量,效率非常高。

    public class NewsController : Controller
        {
            private  string NEWSPREFIX = "WX_NEWS_";
            // GET: News
            public async Task<ActionResult> Index(int id)
            {
                
                using (ConnectionMultiplexer redis = ConnectionMultiplexer.Connect("localhost:6379"))
                {
    
                    IDatabase db = redis.GetDatabase();
                    //默认是访问db0数据库,可以通过方法参数指定数字访问不同的数据库 
                    string clickCount = NEWSPREFIX + Request.UserHostAddress + "_ClickCount_" + id;
                   
    			   // Task<long> StringIncrementAsync:
                    // 返回值:The value of key after the increment. (递增后的值)
    			   long increment = await db.StringIncrementAsync(clickCount);
    			   
                  //RedisValue count = await db.StringGetAsync(clickCount);
                    //ViewBag.count = count;
    				
                    ViewBag.count = increment;
                }
    
                return View();
            }
        }
    

    index.cshtml

    <h2>点击量:@ViewBag.count</h2>
    

    6.2 列表(List)

    • Redis列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)
    • 一个列表最多可以包含 232 - 1 个元素 (4294967295, 每个列表超过40亿个元素)。

    6.2.1 常用方法

    1.从左侧压栈:ListLeftPush(RedisKey key, RedisValue value)
    2.从左侧弹出:RedisValue ListLeftPop(RedisKey key)
    3.从右侧压栈:ListRightPush(RedisKey key, RedisValue value )
    4.从右侧弹出:RedisValue ListRightPop(RedisKey key)
    5.获取Key为key的List中第index个元素的值:RedisValue ListGetByIndex(RedisKey key, long index) ;
    6.获取Key为key的List中元素个数:long ListLength(RedisKey key) ;

    尽量不要用ListGetByIndex、ListLength因为会有并发问题。
    如果是读取而不pop,则使用 ListRange:RedisValue[] ListRange(RedisKey key, long start = 0, long stop = -1)。不传 startend 表示获取所有数据。指定之后则获取某个范围。

    6.2.2 应用场景

      可以把Redis的list当成消息队列使用,比如向注册用户发送欢迎邮件的工作,可以在注册的流程中把要发送邮件的邮箱放到list中,另一个程序从list中pop获取邮件来发送。 生产者、消费者模式。把生产过程和消费过程隔离。

    6.3 集合(Set)

    • Redis 的 Set 是 String 类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据。
    • Redis 中集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。
    • 集合中最大的成员数为 232- 1 (4294967295, 每个集合可存储40多亿个成员)。

    List与Set区别:

    6.3.1 常用方法

    SetAdd(RedisKey key, RedisValue value) 向set中增加元素
    bool SetContains(RedisKey key, RedisValue value) 判断set中是否存在某个元素;
    long SetLength(RedisKey key) 获得set中元素的个数;
    SetRemove(RedisKey key, RedisValue value)从set中删除元素;
    RedisValue[] SetMembers(RedisKey key)获取a集合中的元素;

    如果使用set保存封禁用id等,就不用做重复性判断了。

    6.4 有序集合(sorted set)

    • Redis 有序集合。
      与Set不同的是,每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。

    • 有序集合的成员是唯一的,但分数(score)却可以重复。

    • 集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。 集合中最大的成员数为 232- 1 (4294967295, 每个集合可存储40多亿个成员)。

    6.4.1 常用方法

    SortedSetAdd(RedisKey key, RedisValue member, double score) 
    

    在key这个sortedset中增加member,并且给这个member打分,如果member已经存在,则覆盖之前的打分;

    double SortedSetIncrement(RedisKey key, RedisValue member, double value)
    

    给key中member这一项增加value分;

    double SortedSetDecrement(RedisKey key, RedisValue member, double value)
    

    给key中member这一项减value分;

    SortedSetEntry[] SortedSetRangeByRankWithScores(RedisKey key, long start = 0, long stop = -1, Order order = Order.Ascending)
    

    根据排序返回 sortedset中的元素以及元素的打分,start、stop用来分页查询、order用来指定排序规则。

    RedisValue[] SortedSetRangeByRank(RedisKey key, long start = 0, long stop = -1, Order order = Order.Ascending) 
    

    根据打分排序返回值,可以根据序号查询其中一部分;

    RedisValue[] SortedSetRangeByScore(RedisKey key, double start = double.NegativeInfinity, double stop = double.PositiveInfinity, Exclude exclude = Exclude.None, Order order = Order.Ascending, long skip = 0, long take = -1) 
    

    根据打分排序返回值,可以只返回start- stop 这个范围的打分;

    6.4.2 sortedset应用场景

    1. 用户每搜一次一个关键词,就给这个关键词加一分;展示热搜的时候就把前N个获取出来就行了;
    2. 高积分用户排行榜;
    3. 热门商品;
    4. 给宝宝投票;

    6.5 哈希(Hash)

    • Redis hash 是一个string类型的field和value的映射表,hash特别适合用于存储对象。
    • Redis 中每个 hash 可以存储 232 - 1 键值对(40多亿)。

    6.5.1 应用场景

    存储文章数据
    文章对象序列化后使用一个字符串类型键存储,可是这种方法无法提供对单个字段的原子读写操作,从而产生竞态条件。如两个客户端同事修改不同属性存储,后者覆盖前者。

    使用多个字符串类型键存储一个对象,好处是只要修改一处属性,十分方便。

    使用一个散列类型键存储一个对象更适合。散列更适合这个场景。

    6.6 GEO 地理位置

    • Geo是Redis 3.2版本后新增的数据类型,用来保存兴趣点(POI,point of interest)的坐标信息。
    • 可以实现计算两POI之间的距离、获取一个点周边指定距离的POI。

    6.6.1 常用方法

    1.下面添加兴趣点数据,”1”、”2”是点的主键,点的名称、地址、电话等存到其他表中。

    db.GeoAdd("ShopsGeo", new GeoEntry(116.34039, 39.94218,"1")); 
    db.GeoAdd("ShopsGeo", new GeoEntry(116.340934, 39.942221, "2")); 
    db.GeoAdd("ShopsGeo", new GeoEntry(116.341082, 39.941025, "3")); 
    db.GeoAdd("ShopsGeo", new GeoEntry(116.340848, 39.937758, "4")); 
    db.GeoAdd("ShopsGeo", new GeoEntry(116.342982, 39.937325, "5")); 
    db.GeoAdd("ShopsGeo", new GeoEntry(116.340866, 39.936827, "6")); 
    

    2.删除一个点

    GeoRemove(RedisKey key, RedisValue member)
    

    3.根据点的主键获取坐标:

    GeoPosition? pos = db.GeoPosition("ShopsGeo", "1") 
    

    4.查询两个POI之间的距离:

    double? dist = db.GeoDistance("ShopsGeo", "1", "5", GeoUnit.Meters);//最后一个参数为距离单位 
    

    5.获取一个POI周边的POI:

    GeoRadiusResult[] results = db.GeoRadius("ShopsGeo", "2", 200, GeoUnit.Meters);//获取”2”这个周边200米范围内的POI 
    foreach(GeoRadiusResult result in results) 
    { 
    		Console.WriteLine("Id="+result.Member+",位置"+result.Position+",距离"+result.Distance); 
    } 
    

    6.获取一个坐标(这个坐标不一定是POI)周边的POI:

    GeoRadiusResult[] results = db.GeoRadius("ShopsGeo", 116.34092, 39.94223, 200, GeoUnit.Meters);// 获取(116.34092, 39.94223)这个周边200米范围内的POI 
    foreach(GeoRadiusResult result in results) 
    { 
    Console.WriteLine("Id="+result.Member+",位置"+result.Position+",距离"+result.Distance); 
    } 
    

    Geo Hash原理:http://www.cnblogs.com/LBSer/p/3310455.html

    7 批量操作

    如果一次性操作很多,会很慢,那么可以使用批量操作,两种方式:

    1. 几乎所有的操作都支持数组类型,这样就可以一次性操作多条数据:比如
    GeoAdd(RedisKey key, GeoEntry[] values)
    SortedSetAdd(RedisKey key, SortedSetEntry[] values) 
    
    1. 如果一次性的操作不是简单的同类型操作,那么就要使用批量模式:
    IBatch batch = db.CreateBatch(); 
    db.GeoAdd("ShopsGeo1", new GeoEntry(116.34039, 39.94218, "1")); 
    db.StringSet("abc", "123"); 
    batch.Execute(); 
    

    会把当前连接的CreateBatch()、Execute() 之间的操作一次性提交给服务器。

    8 分布式锁

    多线程中的lock等的作用范围是当前的程序范围内的,如果想跨多台服务器的锁(尽量避免这样搞),就要使用分布式锁

    using (ConnectionMultiplexer redis = ConnectionMultiplexer.Connect("localhost:6379"))
                {
                    IDatabase db = redis.GetDatabase();
    
                    RedisValue token = Environment.MachineName;
                    //实际项目秒杀此处可换成商品ID 
                    //第三个参数为锁超时时间,锁占用最多10秒钟,超过10秒钟如果还没有LockRelease,则也自动释放锁,避免了死锁
                    if (db.LockTake("mylock", token, TimeSpan.FromSeconds(10)))
                    {
                        try
                        {
                            Console.WriteLine("操作开始~");
                            Thread.Sleep(30000);
                            Console.WriteLine("操作完成~");
                        }
                        finally
                        {
                            db.LockRelease("mylock", token);
                        }
                    }
                    else
                    {
                        Console.WriteLine("获得锁失败");
                    }
                    Console.ReadKey();
                }
    

    9 抢红包案例

    • 把这个红包数组以List的形式存到Redis中;
    • 用户抢红包就是从List中Pop取红包。

    发出一个固定金额的红包,由若干个人来抢,需要满足哪些规则?

    1.所有人抢到金额之和等于红包金额,不能超过,也不能少于。
    2.每个人至少抢到一分钱。
    3.要保证所有人抢到金额的几率相等。

    9.1 二倍均值法

    参考:程序员小灰——漫画:如何实现抢红包算法?
    剩余红包金额为M,剩余人数为N,那么有如下公式:

    每次抢到的金额 = 随机区间 (0, M / N X 2)

    这个公式,保证了每次随机金额的平均值是相等的,不会因为抢红包的先后顺序而造成不公平。
    举个栗子:

    假设有10个人,红包总额100元。100/10X2 = 20, 所以第一个人的随机范围是(0,20 ),平均可以抢到10元。
    假设第一个人随机到10元,那么剩余金额是100-10 = 90 元。90/9X2 = 20, 所以第二个人的随机范围同样是(0,20 ),平均可以抢到10元。
    假设第二个人随机到10元,那么剩余金额是90-10 = 80 元。80/8X2 = 20, 所以第三个人的随机范围同样是(0,20 ),平均可以抢到10元。
    以此类推,每一次随机范围的均值是相等的。

            static void Main(string[] args)
            {
                    //例子:50元分配10个人
                    List<int> amountList = divideRedPackage(5000, 10);
                    foreach (double amount in amountList)
                    {
                        double item = (amount / 100);
                        Console.WriteLine($"抢到金额:{item}");
                    }
            }
    
            //发红包算法,金额参数以分为单位
            public static List<int> divideRedPackage(int totalAmount, int totalPeopleNum)
            {
                List<int> amountList = new List<int>();
                int restAmount = totalAmount;
                int restPeopleNum = totalPeopleNum;
                Random random = new Random();
                for (int i = 0; i < totalPeopleNum - 1; i++)
                {
                    //随机范围:[1,剩余人均金额的两倍),左闭右开
                    int amount = random.Next(restAmount / restPeopleNum * 2 - 1) + 1;
                    restAmount -= amount;
                    restPeopleNum--;
                    amountList.Add(amount);
                }
                amountList.Add(restAmount);
                return amountList;
            }
    
  • 相关阅读:
    NABCD
    返回一个整数数组中最大子数组的和。
    四则运算
    返回一个二维整数数组中最大子数组的和
    返回一个整数数组中最大子数组的和
    解决方案
    测绘软件
    ImageMagick还是GraphicsMagick?
    opencv-2.4.11编译备忘
    Graphicmagick编译
  • 原文地址:https://www.cnblogs.com/tangge/p/9737212.html
Copyright © 2011-2022 走看看