Redis .NET操做

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

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

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;github

2.1.2 redis的缺点:

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

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的keyjson

3.2 数据没有隔离性

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

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;
        }
相关文章
相关标签/搜索