Redis深入

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 GUI管理客户端

GUI客户端非常多,个人推荐使用RedisDesktopManager

.Net操作Redis

用StackExchange.Redis ,而不是ServiceStack.Redis,因为StackExchange.Redis依赖组件少,而且操作更接近原生的redis操作,ServiceStack封装的太厉害,而且有过收费的“前科”。

在vs中Nuget包管理器下的程序包管理器控制台中输入:或者直接去Nuget中安装StackExchange.Redis

Install-Package StackExchange.Redis

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("获得锁失败");
            }
        }
    }
}


猜你喜欢

转载自blog.csdn.net/Fanbin168/article/details/80941441