Redis是一个支持数据结构更多的键值对数据库。它的值不只能够是字符串等基本数据类型,也能够是类对象,更能够是Set、List、计数器等高级的数据结构。
Memcached也能够保存相似于Set、List这样的结构,可是若是说要向List中增长元素,Memcached则须要把List所有元素取出来,而后再把元素增长进去,而后再保存回去,不只效率低,并且有并发访问问题。Redis内置的Set、List等能够直接支持增长、删除元素的操做,效率很高,操做是原子的。html
Memcached数据存在内存中,memcached重启后数据就消失;
Redis会把数据持久化到硬盘中,Redis重启后数据还存在。git
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程序员
1) 支持string、list、set、geo等复杂的数据结构。
2) 高命中的数据运行时是在内存中,数据最终仍是能够保存到磁盘中,这样服务器重启以后数据还在。
3) 服务器是单线程
的,来自全部客户端的全部命令都是串行执行的,所以不用担忧并发修改(串行操做固然仍是有并发问题)的问题,编程模型简单;
4) 支持消息订阅/通知机制,能够用做消息队列;
5) Key、Value最大长度容许512M;github
1) Redis是单线程
的,所以单个Redis实例只能使用一个CPU核,不能充分发挥服务器的性能。能够在一台服务器上运行多个Redis实例,不一样实例监听不一样端口,再互相组成集群。
2) 作缓存性能不如Memcached;redis
1) 多线程,能够充分利用CPU多核的性能;
2) 作缓存性能最高;算法
1) 只能保存键值对数据,键值对只能是字符串,若是有对象数据只能本身序列化成json
字符串;
2) 数据保存在内存中,重启后会丢失;
3) Key最大长度255个字符,Value最长1M。数据库
Memcached只能当缓存服务器用,也是最合适的;Redis不只能够作缓存服务器(性能没有Memcached好),还能够存储业务数据。编程
执行
set myKey abc
,就是设置键值对myKey=abc
执行get myKey
就是查找名字是myKey的值;
keys *
是查找全部的key
key *n*
是查找全部名字中含有n的keyjson
和Redis同样,Redis也是不一样系统放到Redis中的数据都是不隔离的,所以设定Key的时候也要选择好Key。windows
Redis服务器默认建了16个数据库,Redis的想法是让你们把不一样系统的数据放到不一样的数据库中。可是建议你们不要这样用,由于Redis是单线程的,不一样业务都放到同一个Redis实例的话效率就不高,建议放到不一样的实例中。所以尽可能只用默认的db0数据库。
命令行下能够用select 0、select 1这样的指令切换数据库,最高为15。试试在不一样数据库下新建、查询数据。
了解的经常使用的几个命令就能够。全部对数据的操做均可以经过命令行进行,后面讲的.net操做Redis的驱动其实就是对这些命令的封装。
RedisDesktopManager (0.9.3之后须要订阅)
https://github.com/uglide/RedisDesktopManager/releases
推荐组件: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()
,尽可能用异步方法。
注意看到访问的参数、返回值是RedisKey
、RedisValue
类型,进行了运算符重载,能够和string
、
byte[]
之间进行隐式转换。
由于Redis里全部数据类型都是用KeyValue保存,所以Key操做针对全部数据类型,
KeyDelete(RedisKey key)
:根据Key删除;KeyExists(RedisKey key)
判断Key是否存在,尽可能不要用,由于会有并发问题;KeyExpire(RedisKey key, TimeSpan? expiry)
、KeyExpire(RedisKey key, DateTime? expiry)
设置过时时间;能够用 StringGet
、StringSet
来读写键值对,是基础操做
StringAppend(RedisKey key, RedisValue value)
:向Key的Value中附加内容,不存在则新建;
能够用做计数器: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>
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)
。不传start
、end
表示获取全部数据。指定以后则获取某个范围。
能够把Redis的list当成消息队列使用,好比向注册用户发送欢迎邮件的工做,能够在注册的流程中把要发送邮件的邮箱放到list中,另外一个程序从list中pop获取邮件来发送。 生产者、消费者模式。把生产过程和消费过程隔离。
List与Set区别:
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等,就不用作重复性判断了。
Redis 有序集合。
与Set不一样的是,每一个元素都会关联一个double类型的分数。redis正是经过分数来为集合中的成员进行从小到大的排序。
有序集合的成员是惟一的,但分数(score)却能够重复。
集合是经过哈希表实现的,因此添加,删除,查找的复杂度都是O(1)。 集合中最大的成员数为 232- 1 (4294967295, 每一个集合可存储40多亿个成员)。
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 这个范围的打分;
存储文章数据
文章对象序列化后使用一个字符串类型键存储,但是这种方法没法提供对单个字段的原子读写操做,从而产生竞态条件。如两个客户端同事修改不一样属性存储,后者覆盖前者。
使用多个字符串类型键存储一个对象,好处是只要修改一处属性,十分方便。
使用一个散列类型键存储一个对象更适合。散列更适合这个场景。
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
若是一次性操做不少,会很慢,那么可使用批量操做,两种方式:
GeoAdd(RedisKey key, GeoEntry[] values) SortedSetAdd(RedisKey key, SortedSetEntry[] values)
IBatch batch = db.CreateBatch(); db.GeoAdd("ShopsGeo1", new GeoEntry(116.34039, 39.94218, "1")); db.StringSet("abc", "123"); batch.Execute();
会把当前链接的CreateBatch()、Execute() 之间的操做一次性提交给服务器。
多线程中的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(); }
发出一个固定金额的红包,由若干我的来抢,须要知足哪些规则?
1.全部人抢到金额之和等于红包金额,不能超过,也不能少于。
2.每一个人至少抢到一分钱。
3.要保证全部人抢到金额的概率相等。
参考:程序员小灰——漫画:如何实现抢红包算法?
剩余红包金额为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; }