Redis的优点
1> 支持string、list、set、geo等复杂的数据结构。
2> 高命中的数据运行时是在内存中,数据最终还是可以保存到磁盘中,这样服务器重启之后数据还在。
3> 服务器是单线程的,来自所有客户端的所有命令都是串行执行的,
因此不用担心并发修改(串行操作当然还是有并发问题)的问题,编程模型简单;
4> 支持消息订阅/通知机制,可以用作消息队列;
5> Key、Value最大长度允许512M
Redis的缺点
1> Redis是单线程的,因此单个Redis实例只能使用一个CPU核,不能充分发挥服务器的性能。
可以在一台服务器上运行多个Redis实例,不同实例监听不同端口,再互相组成集群。
2> 做缓存性能不如Memcached
特点:
1>和Memcached一样,Redis也是不同系统放到Redis中的数据都是不隔离的,因此设定Key的时候也要选择好Key。
2>Redis服务器默认建了16个数据库,Redis的想法是让大家把不同系统的数据放到不同的数据库中。但是建议大家不要这样用,因为Redis是单线程的,不同业务都放到同一个Redis实例的话效率就不高,建议放到不同的实例中。因此尽量只用默认的db0数据库。
命令行下可以用select 0、select 1这样的指令切换数据库,最高为15。试试在不同数据库下新建、查询数据。
Redis具体配置属性
配置选项 | 默认 | 含义 |
---|---|---|
AbortOnConnectFail | true (false 在Azure上) | 如果为true,Connect 则在没有服务器可用时将不会创建连接 |
AllowAdmin | false | 启用一系列被认为具有风险的命令 |
ChannelPrefix | null | 所有发布/订阅操作的可选通道前缀 |
ConnectRetry | 3 | 初始期间重复尝试连接的次数 Connect |
ConnectTimeout | 5000 | 连接操作超时(毫秒) |
ConfigurationChannel | __Booksleeve_MasterChanged | 用于传达配置更改的广播频道名称 |
ConfigCheckSeconds | 60 | 检查配置的时间(秒)。如果支持,它可以充当交互式套接字的保持活动状态。 |
DefaultDatabase | null | 默认数据库索引,从0 到databases - 1 |
KeepAlive | -1 | 发送消息以帮助套接字保持活动的时间(秒)(默认为60秒) |
ClientName | null | 标识Redis中的连接 |
Password | null | Redis服务器密码 |
Proxy | Proxy.None | 使用中的代理类型(如果有);例如“ twemproxy” |
ResolveDns | false | 指定DNS解析应该是明确且渴望的,而不是隐式的 |
ResponseTimeout | SyncTimeout | 决定套接字是否不健康的时间(毫秒) |
Ssl | false | 指定应使用SSL加密 |
SslHost | null | 在服务器的证书上强制使用特定的SSL主机身份 |
SslProtocols | null | 使用加密连接时支持Ssl / Tls版本。使用“ |” 提供多个值。 |
SyncTimeout | 5000 | 允许同步操作的时间(毫秒) |
TieBreaker | __Booksleeve_TieBreak | 在模棱两可的主方案中用于选择服务器的密钥 |
DefaultVersion | (3.0 在Azure中,否则2.0 ) | Redis版本级别(在服务器不可用时有用) |
WriteBuffer | 4096 | 输出缓冲区的大小 |
Redis GUI管理客户端
GUI客户端非常多,个人推荐使用RedisDesktopManager
.Net操作Redis
用StackExchange.Redis ,而不是ServiceStack.Redis,因为StackExchange.Redis依赖组件少,而且操作更接近原生的redis操作,ServiceStack封装的太厉害,而且有过收费的“前科”。
在vs中Nuget包管理器下的程序包管理器控制台中输入:或者直接去Nuget中安装StackExchange.Redis
<span style="color:#000000">Install-Package StackExchange.Redis</span>
String类型的操作
class Program
{
static void Main(string[] args)
{
using (ConnectionMultiplexer redis = ConnectionMultiplexer.Connect("192.168.31.126:6379"))
{
//默认是访问db0数据库,可以通过方法参数指定数字访问不同的数据库,例如:redis.GetDatabase(2);
IDatabase db = redis.GetDatabase();
//string类型的数据写入与读取
//1:数据写入
//参数1:键
//参数2:值
//参数3:过期时间(可选参数)
//参数4:操作方式 (可选参数)
// When.Alway表示:不管数据库中是否存在Name这个键,都会写入新的数据
// When.Exists表示:只有数据库中存在Name这个键,才会写入新的数据
// When.NotExists表示:只有数据库中不存在Name这个键,才会写入新的数据
//参数5:我也不知道意思
bool aa = db.StringSet("Name", "lily", TimeSpan.FromDays(3), When.Always, CommandFlags.None);
//2:附加操作(向Key的Value中附加内容,不存在则新建)
db.StringAppend("Name", "中国");//获取数据库中key为Name这条数据,并在这条数据的值的后面附加一个“中国”字符串。
//3:数据读取
//这里也可以写成string b=db.StringGet("food");因为RedisValue默认做了数据转换
RedisValue b = db.StringGet("Name"); //读取key为Name的这条数据 //此时Key为Name的值为 lily中国
//4:计数器(它是原子操作,不存在并发的问题)【应用场景:网站点击量,网络投票等】
db.StringIncrement("count", 1);//给key为count这个计数器增加一个值,如果不存在则从0开始加
db.StringIncrement("count", 1);
db.StringIncrement("count", 1);
db.StringDecrement("count", 1);//给key为count的这个计数器减一个值
RedisValue vs = db.StringGet("count"); //这里vs的值为2 (因为1+1+1+1-1=2)
}
}
}
List类型的操作
我们都知道List是有顺序的,例如:如下
List<int> list = new List<int>();
list.Add(1); //它是第0个元素
list.Add(2); //它是第一个元素
foreach (var item in list)
{
Console.WriteLine(item); //它会按顺序输出:1,2
}
Redis中的List比较特别,它集合了Querue和Stack的优点,它可以做到先进先出,也可以做到先进后出的功能。即:它可以两边存,取
Redis中用List保存字符串集合。 比如可以把聊天记录保存到List中;商品的物流信息记录。也可以当成双向队列或者双向栈用,list长度是无限。
ListLeftPush(RedisKey key, RedisValue value)从左侧压栈;
RedisValue ListLeftPop(RedisKey key)从左侧弹出;
ListRightPush(RedisKey key, RedisValue value ) 从右侧压栈;
RedisValue ListRightPop(RedisKey key) 从右侧弹出;
RedisValue ListGetByIndex(RedisKey key, long index)获取Key为key的List中第index个元素的值;
long ListLength(RedisKey key) 获取Key为key的List中元素个数;尽量不要用ListGetByIndex、ListLength因为会有并发问题;。
如果是读取而不Pop,则使用ListRange
RedisValue[] ListRange(RedisKey key, long start = 0, long stop = -1) 不传start、end表示获取所有数据。指定之后则获取某个范围。
可以把Redis的list当成消息队列使用,比如向注册用户发送欢迎邮件的工作,可以在注册的流程中把要发送邮件的邮箱放到list中,另一个程序从list中pop获取邮件来发送。
生产者、消费者模式。把生产过程和消费过程隔离。
class Program
{
static void Main(string[] args)
{
using (ConnectionMultiplexer redis = ConnectionMultiplexer.Connect("192.168.31.126:6379"))
{
//默认是访问db0数据库,可以通过方法参数指定数字访问不同的数据库,例如:redis.GetDatabase(2);
IDatabase db = redis.GetDatabase();
//从左边入栈
db.ListLeftPush("list", 1);
db.ListLeftPush("list", 2);
var a = db.ListLength("list");
for (int i = 0; i < a; i++)
{
Console.WriteLine(db.ListLeftPop("list"));//从左边出栈:输出1,2
}
//从右边入栈
db.ListRightPush("list", 3);
db.ListRightPush("list", 4);
var b = db.ListLength("list");
for (int i = 0; i < a; i++)
{
Console.WriteLine(db.ListRightPop("list")); //从右边出栈:输出4,3
}
//读取数据
db.ListRightPush("list", "abc");
db.ListRightPush("list", false);
long length = db.ListLength("list");
RedisValue[] list = db.ListRange("list", 0, length); //从list的第0个开始获取,总共获取list的总长度那么多数据
foreach (RedisValue item in list)
{
Console.WriteLine(item); //输出:abc,0 注:false在Redis中以0保存
}
RedisValue indexValue = db.ListGetByIndex("list",0); //获取key为list的数据集合中的第0条数据。尽量不要用ListGetByIndex、ListLength因为会有并发问题;
Console.WriteLine(indexValue); //输出:abc
}
}
}
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)获取集合中的元素;
class Program
{
static void Main(string[] args)
{
using (ConnectionMultiplexer redis = ConnectionMultiplexer.Connect("192.168.31.126:6379"))
{
//默认是访问db0数据库,可以通过方法参数指定数字访问不同的数据库,例如:redis.GetDatabase(2);
IDatabase db = redis.GetDatabase();
//如大家所知,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)获取集合中的元素;
//特别注意:数据库中有一个key的数据存储的是Hash,List等类型的,但是你使用jedis执行数据操作的时候却使用了非Hash,List的操作方法。此时就会报 WRONGTYPE Operation against a key holding the wrong kind of value这个错误!
db.SetAdd("list5", 1);
db.SetAdd("list5", 2);
db.SetAdd("list5", 3);
db.SetAdd("list5", "abc");
RedisValue[] list = db.SetMembers("list5");
//获取集合中所有的数据
foreach (RedisValue item in list)
{
Console.WriteLine(item);//输出1,2,3,abc
}
//查询key为list这个集合中,是否有值为3的数据
bool myboll = db.SetContains("list5", 3);//输出:返回true
}
}
}
sortedset类型操作
如果对于数据遍历顺序有要求,可以使用sortedset,他会按照打分来进行遍历。
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用来指定排序规则。
class Program
{
static void Main(string[] args)
{
using (ConnectionMultiplexer redis = ConnectionMultiplexer.Connect("192.168.10.21:6379,password=myfanbin123456"))
{
//默认是访问db0数据库,可以通过方法参数指定数字访问不同的数据库,例如:redis.GetDatabase(2);
IDatabase db = redis.GetDatabase();
SortedSetAdd方法:新值覆盖旧值
//db.SortedSetAdd("myList", "张三", 1);
//db.SortedSetAdd("myList", "张三", 2); //成员只能保留1个,如果有2个张三的成员,那么新值会覆盖旧的值
//db.SortedSetAdd("myList", "李四", 1);
//db.SortedSetAdd("myList", "王五", 1);
long mylistCount = db.SortedSetLength("myList"); //所以这里总共获取到的长度为 3 而不是4
参数1:键(必选参数,如果只有有键,则默认取全部)
参数2:从集合的第几个元素开始取(可选参数)
参数3:总共获取几个(可选参数)
参数4:按照升序,或者降序排序(可选参数)
SortedSetEntry[] mylist = db.SortedSetRangeByRankWithScores("myList", 0, mylistCount, Order.Ascending);
foreach (SortedSetEntry item in mylist)
{
Console.WriteLine(item);//输出:李四:1 王五:1 张三:2
}
//SortedSetIncrement方法:新值累加旧值
//应用场景,给宝宝投票,每个人都可以给同一个宝宝投票,可以计算分数最高的宝宝
db.SortedSetIncrement("vote", "张三", 1);
db.SortedSetIncrement("vote", "张三", 1);
db.SortedSetIncrement("vote", "张三", 1); //成员只能保留1个,如果有3个张三的成员,那么新的值会累加旧的值
db.SortedSetIncrement("vote", "李四", 1);
db.SortedSetIncrement("vote", "李四", 1);
db.SortedSetIncrement("vote", "王五", 1);
db.SortedSetIncrement("vote", "钱七", 1);
db.SortedSetIncrement("vote", "王八", 1);
long votelistCount = db.SortedSetLength("vote");
//SortedSetRangeByRankWithScores方法是获取键和值
SortedSetEntry[] voteList = db.SortedSetRangeByRankWithScores("vote", 0, votelistCount, Order.Ascending);
foreach (SortedSetEntry item in voteList)
{
Console.WriteLine(item);//输出:王五:1 李四:2 张三:3
}
//SortedSetRangeByRank方法是获取数据的键
//参数1:键(必选参数,如果只有有键,则默认取全部)
//参数2:从集合的第几个元素开始取(可选参数)
//参数3:总共获取几个(可选参数)
//参数4:按照升序,或者降序排序(可选参数)
RedisValue[] voteKeyList = db.SortedSetRangeByRank("vote");
foreach (RedisValue item in voteKeyList)
{
//Console.WriteLine(item);//输出:王五 李四 张三
}
//SortedSetRangeByScore方法获取的也是键,但是参数更多点而已。可以设定分数的区间
//参数1:键(必选参数,如果只有有键,则默认取全部)
//参数2:从多少分开始取(可选参数),这里表示从0分开始取
//参数3:指定一个最大的分数(可选参数),这里表示最大分数是44 即:取0-44分之间的数据
//参数4:临界值区间:【临界值就是第二个参数与第三个参数组成的区间的首尾;例如:3-44 ,那么临界值最小分数就是3,最大分数就是44】
//Exclude.None:就是临界值的最小分数和最最大分数都包括(默认就行)
//Exclude.Start:排除最临界值的最小分数
//Exclude.Stop:排除临界值的最大分数
//Exclude.Both:排除临界值最下分数,和临界值的最大分数,取中间的分数
//参数5:按照升序,或者降序排序(可选参数)
//参数6:从第几个元素开始获取数据
//参数7:总共获取几条数据
RedisValue[] voteValueList = db.SortedSetRangeByScore("vote", 3, 44, Exclude.Both, Order.Descending, 0, 10);
foreach (RedisValue item in voteValueList)
{
Console.WriteLine(item);
}
//来说说Exclude这个枚举:Exclude是针对第二个参数和第三个参数组成的区间的临界值来说的
//例如:第二个参数是50,第三个参数是95 那么它们两个组成数据区间就是50-95 最小临界值就是50,最大临界值就是95
//例如:我通过SortedSetIncrement方法在Redis数据库中存了N多条数据
//其中 张三:100,李四:95,王五:80 赵六:70,钱七:65,孙八:60
//我通过RedisValue[] voteValueList = db.SortedSetRangeByScore("vote", 50, 95, Exclude.Both, Order.Descending);
//首先:取50-95之间的分数,这样把张三(100分)给排除了
//那么就剩下:李四:95,王五:80 赵六:70,钱七:65,孙八:60
//而第二个参数与第三个参数设定的值是50-95 所以临界值就是50和95
//而Exclude.Both是指排除临界值的首尾,正好李四(95分)符合最大的临界值95 ,所以李四被排除
//而最小临界值是50,而我们的数据中孙八的分数是60,不是最小临界值,不能排除
//所以最后剩下的数据就是 王五 赵六 钱七 孙八
}
}
}
Geo类型操作
Geo是Redis 3.2版本后新增的数据类型,用来保存兴趣点(POI,point of interest)的坐标信息。可以实现计算两POI之间的距离、获取一个点周边指定距离的POI。
下面添加兴趣点数据,”1”、”2”是点的主键,点的名称、地址、电话等存到其他表中。例如:如下
class Program
{
static void Main(string[] args)
{
using (ConnectionMultiplexer redis = ConnectionMultiplexer.Connect("192.168.31.126:6379"))
{
//默认是访问db0数据库,可以通过方法参数指定数字访问不同的数据库,例如:redis.GetDatabase(2);
IDatabase db = redis.GetDatabase();
//GeoAdd方法:
//参数1:经度
//参数2:纬度
//参数3:兴趣点(一般情况下它只能只是一条数据的Id编号,例如:数据库中保存了一条详细的店铺地址,电话等信息,这条数据肯定有一个Id编号。比如说,我查询商铺A,到商铺B的距离。那么我们去数据库中找到商铺A的Id,找到商铺B的Id,然后在调用db.GeoDistance方法,将这个两个id,传到方法中,就可以计算他们两点的距离了)
db.GeoAdd("MyAddressGeo", new GeoEntry(114.042737, 22.611297, "1"));//这里的字符串1其实就表示上面的表中的Id
db.GeoAdd("MyAddressGeo", new GeoEntry(114.049164, 22.607273, "2"));
db.GeoAdd("MyAddressGeo", new GeoEntry(114.042669, 22.610609, "3"));
db.GeoAdd("MyAddressGeo", new GeoEntry(114.051307, 22.605646, "4"));
db.GeoAdd("MyAddressGeo", new GeoEntry(114.054976, 22.600187, "5"));
db.GeoAdd("MyAddressGeo", new GeoEntry(114.036902, 22.616602, "6"));
db.GeoAdd("MyAddressGeo", new GeoEntry(114.054495, 22.568803, "7"));
db.GeoAdd("MyAddressGeo", new GeoEntry(114.041407, 22.61133, "8"));
db.GeoAdd("MyAddressGeo", new GeoEntry(114.043276, 22.611393, "9"));
db.GeoAdd("MyAddressGeo", new GeoEntry(114.046056, 22.611364, "10"));
//查询两点之间的距离
//参数1:键
//参数2:兴趣点1
//参数3:兴趣点2
//参数4:距离的单位
//GeoUnit.Meters:米
//GeoUnit.Kilometers:千米
//GeoUnit.Miles:英里
//GeoUnit.Feet:步数
double? dist = db.GeoDistance("MyAddressGeo", "1", "5", GeoUnit.Meters);
//根据兴趣点(Id主键)获取坐标
//参数1:键
//参数2:兴趣点(数据的ID主键)
GeoPosition? pos = db.GeoPosition("ShopsGeo", "1");
//获取一个坐标(这个坐标不一定是POI)周边的POI:
//获取(116.34092, 39.94223)这个周边200米范围内的POI
GeoRadiusResult[] results = db.GeoRadius("ShopsGeo", 116.34092, 39.94223, 200, GeoUnit.Meters);
foreach (GeoRadiusResult result in results)
{
Console.WriteLine("Id=" + result.Member + ",位置" + result.Position + ",距离" + result.Distance);
}
}
}
}
Geo Hash原理:http://www.cnblogs.com/LBSer/p/3310455.html
Redis的批量操作
如果一次性操作很多,会很慢,那么可以使用批量操作,两种方式:
1> 几乎所有的操作都支持数组类型,这样就可以一次性操作多条数据:比如
GeoAdd(RedisKey key, GeoEntry[] values) //注意:第二个参数是一个数组
SortedSetAdd(RedisKey key, SortedSetEntry[] values) //注意:第二个参数是一个数组
2> 如果一次性的操作不是简单的同类型操作,例如:有的是List数据类型的操作,有的是Geo数据类型的操作,有的是String类型的操作,那么就要使用批量模式:
IBatch batch = db.CreateBatch();
db.GeoAdd("ShopsGeo1", new GeoEntry(116.34039, 39.94218, "1"));
db.StringSet("abc", "123");
batch.Execute();
会把当前连接的CreateBatch()、Execute()之间的操作一次性提交给服务器。
Redis 分布式锁(了解即可,一般用不到)
class Program
{
static void Main(string[] args)
{
using (ConnectionMultiplexer redis = ConnectionMultiplexer.Connect("192.168.31.126:6379"))
{
//默认是访问db0数据库,可以通过方法参数指定数字访问不同的数据库,例如:redis.GetDatabase(2);
IDatabase db = redis.GetDatabase();
RedisValue token = Environment.MachineName;//获取一个token
//实际项目秒杀此处可换成商品ID
//mylock是锁的名字,大家只要连接上这个Redis服务器的人都需要受这个mylock锁的控制
if (db.LockTake("mylock", token, TimeSpan.FromSeconds(10)))//第三个参数为锁超时时间,锁占用最多10秒钟,超过10秒钟如果还没有LockRelease,则也自动释放锁,避免了死锁
{
try
{
Console.WriteLine("操作中");
Thread.Sleep(3000);
Console.WriteLine("操作完成");
}
finally
{
db.LockRelease("mylock", token);//释放锁
}
}
else
{
Console.WriteLine("获得锁失败");
}
}
}
}