分布式缓存Redis之数据类型

来源:互联网 发布:怎么修改软件版权 编辑:程序博客网 时间:2024/06/05 07:48

写在前面

  本学习教程所有示例代码见GitHub:https://github.com/selfconzrr/Redis_Learning

  Redis 数据类型官方文档:http://www.redis.net.cn/tutorial/3505.html

  Redis支持五种数据类型:string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合)

使用场景及简介

String:

  string是最简单的类型,你可以理解成与Memcached是一模一样的类型,一个key对应一个value,其上支持的操作与Memcached的操作类似。但它的功能更丰富。

  string类型是二进制安全的。意思是redis的string可以包含任何数据,比如jpg图片或者序列化的对象。从内部实现来看其实string可以看作byte数组,最大上限是1G字节,下面是string类型的定义:

struct sdshdr {  long len;  long free;  char buf[];  };

  其中:

  len是buf数组的长度。

  free是数组中剩余可用字节数,由此可以理解为什么string类型是二进制安全的了,因为它本质上就是个byte数组,当然可以包含任何数据了。

  buf是个char数组用于存贮实际的字符串内容,其实char和c#中的byte是等价的,都是一个字节。

  另外string类型可以被部分命令按int处理。比如incr等命令,如果只用string类型,redis就可以被看作加上持久化特性的memcached。当然redis对string类型的操作比memcached还是多很多的。

  学习的时候,就有个疑问了:什么是二进制安全?为什么string是二进制安全的?

  见博文:http://blog.csdn.net/u011489043/article/details/78738374

LIST:

  Redis的list类型其实就是一个每个子元素都是string类型的双向链表。链表的最大长度是(2的32次方)。我们可以通过push,pop操作从链表的头部或者尾部添加删除元素。这使得list既可以用作栈,也可以用作队列。

  list的pop操作还有阻塞版本的,当我们[l/r]pop一个list对象时,如果list是空,或者不存在,会立即返回nil。但是阻塞版本的b[l/r]pop则可以阻塞,当然可以加超时时间,超时后也会返回nil。

  为什么要阻塞版本的pop呢,主要是为了避免轮询。举个简单的例子如果我们用list来实现一个工作队列,执行任务的thread可以调用阻塞版本的pop去获取任务。这样就可以避免轮询去检查是否有任务存在,当任务来时候工作线程可以立即返回,也可以避免轮询带来的延迟。
  
  应用: 可以作为消息队列,LPUSH链表头作为生产者插入消息,RPOP作为消费者取得消息。

Hash:

  Redis hash是一个string类型的field和value的映射表。它的添加、删除操作都是O(1)(平均)。hash特别适合用于存储对象。相较于将对象的每个字段存成单个string类型。将一个对象存储在hash类型中会占用更少的内存,并且可以更方便的存取整个对象。

  省内存的原因是新建一个hash对象时,开始是用zipmap(又称为small hash)来存储的。这个zipmap其实并不是hash table,但是zipmap相比正常的hash实现可以节省不少hash本身需要的一些元数据存储开销。尽管zipmap的添加,删除,查找都是O(n),但是由于一般对象的field数量都不太多。所以使用zipmap也是很快的,也就是说添加删除平均还是O(1)。如果field或者value的大小超出一定限制后,Redis会在内部自动将zipmap替换成正常的hash实现。这个限制可以在配置文件中指定。

hash-max-zipmap-entries 64 #配置字段最多64个。hash-max-zipmap-value 512 #配置value最大为512字节。

  应用: AIOP中用户标签信息使用HASHMAP存储,存储结构为user:phone tag1 val1 tag2 val2 ,AIOP传递客户手机号,即可通过HGET user:phone tag1 tag2 取出标签值。

Set:

  Redis的set是string类型的无序集合。set元素最大可以包含(2的32次方)个元素。

  set的是通过hash table实现的,所以添加、删除和查找的复杂度都是O(1)。hash table会随着添加或者删除自动的调整大小。需要注意的是调整hash table大小时候需要同步(获取写锁)会阻塞其他读写操作,可能不久后就会改用跳表(skip list)来实现,跳表已经在sorted set中使用了。关于set集合类型除了基本的添加删除操作,其他有用的操作还包含集合的取并集(union),交集(intersection),差集(difference)。通过这些操作可以很容易的实现sns中的好友推荐和blog的tag功能

  应用: COC中将符合标签的用户进行聚类,存放到Set中,比如set1 存放学生标签用户、set2存放低消费用户、set3存放非合约用户,那么取出可能购买小米的低消非合约学生用户群:

SINTERSTORE  aset set1 set2 // 将set1与set2的交集赋给asetSDIFF aset set3   //取aset与set3的差集

Sorted Set:

  和set一样sorted set也是string类型元素的集合,不同的是每个元素都会关联一个double类型的score。sorted set的实现是skip list和hash table的混合体

  当元素被添加到集合中时,一个元素到score的映射被添加到hash table中,所以给定一个元素获取score的开销是O(1),另一个score到元素的映射被添加到skip list,并按照score排序,所以就可以有序的获取集合中的元素。添加,删除操作开销都是O(log(N))和skip list的开销一致。

  redis的skip list实现用的是双向链表,这样就可以逆序从尾部取元素。sorted set最经常的使用方式应该是作为索引来使用。我们可以把要排序的字段作为score存储,对象的id当元素存储。
  
  应用:可以用于一个大型在线游戏的积分排行榜。每当玩家的分数发生变化时,可以执行ZADD命令更新玩家的分数,此后再通过ZRANGE命令获取积分TOP TEN的用户信息。当然我们也可以利用ZRANK命令通过username来获取玩家的排行信息。最后我们将组合使用ZRANGE和ZRANK命令快速的获取和某个玩家积分相近的其他用户的信息。

在Ubuntu下的终端操作:

  启动redis服务器后,进入redis客户端,然后大家可以尝试如下命令的操作,一方面熟悉Linux操作,一方面熟悉redis支持的数据类型。

1、增加一条字符串记录key1# 增加一条记录key1redis 127.0.0.1:6379> set key1 "hello"OK# 打印记录redis 127.0.0.1:6379> get key1"hello"2、增加一条数字记录key2# 增加一条数字记录key2set key2 1OK# 让数字自增redis 127.0.0.1:6379> INCR key2(integer) 2redis 127.0.0.1:6379> INCR key2(integer) 3# 打印记录redis 127.0.0.1:6379> get key2"3"3、增加一条列表记录key3# 增加一个列表记录key3redis 127.0.0.1:6379> LPUSH key3 a(integer) 1# 从左边插入列表redis 127.0.0.1:6379> LPUSH key3 b(integer) 2# 从右边插入列表redis 127.0.0.1:6379> RPUSH key3 c(integer) 3# 打印列表记录,按从左到右的顺序redis 127.0.0.1:6379> LRANGE key3 0 31) "b"2) "a"3) "c"4、增加一条哈希表记录key4# 增加一个哈希记表录key4redis 127.0.0.1:6379> HSET key4 name "John Smith"(integer) 1# 在哈希表中插入,email的Key和Value的值redis 127.0.0.1:6379> HSET key4 email "abc@gmail.com"(integer) 1# 打印哈希表中,name为key的值redis 127.0.0.1:6379> HGET key4 name"John Smith"# 打印整个哈希表redis 127.0.0.1:6379> HGETALL key41) "name"2) "John Smith"3) "email"4) "abc@gmail.com"5、增加一条哈希表记录key5# 增加一条哈希表记录key5,一次插入多个Key和value的值redis 127.0.0.1:6379> HMSET key5 username antirez password P1pp0 age 3OK# 打印哈希表中,username和age为key的值redis 127.0.0.1:6379> HMGET key5 username age1) "antirez"2) "3"# 打印完整的哈希表记录redis 127.0.0.1:6379> HGETALL key51) "username"2) "antirez"3) "password"4) "P1pp0"5) "age"6) "3"# 查看所有的key列表redis 127.0.0.1:6379> keys *1) "key2"2) "key3"3) "key4"4) "key5"5) "key1"# 删除key1,key5redis 127.0.0.1:6379> del key1(integer) 1redis 127.0.0.1:6379> del key5(integer) 1# 查看所有的key列表redis 127.0.0.1:6379> keys *1) "key2"2) "key3"3) "key4"

在windows下的Eclipse操作

  新建java工程,需要引入客户端jedis的jar包,两种方式

  添加maven依赖

<dependency>    <groupId>redis.clients</groupId>    <artifactId>jedis</artifactId>    <version>2.9.0</version></dependency>

  直接下载,(我的百度网盘)

  Jedis2.1.0,链接:https://pan.baidu.com/s/1o7K0Zai 密码:wabo
  Jedis2.9.0,链接:https://pan.baidu.com/s/1i5tNhx7 密码:u9si

  然后,执行下面的简单测试代码。看能否成功连接Ubuntu下的服务器,并实现key-value的操作。

  比如在Ubuntu终端下,输入命令:get key6;如果在终端界面输出:jedis test6,说明远程连接、操作数据库成功。

        Jedis jedis = new Jedis("192.168.65.130", 6379);// 此ip为Ubuntu的ip地址        jedis.auth("redis");// redis-cli的访问密码        System.out.println("Connection to server sucessfully");        // 查看服务是否运行        System.out.println("Server is running: " + jedis.ping());        // 进行数据库操作测试        jedis.set("key6", "redis test");        jedis.set("key7", "张瑞瑞");//中文在Ubuntu下没正常显示        String string = jedis.get("key7");        String string1 = jedis.get("key6");        System.out.println(string + " " + string1);

  比如对set类型的详细操作,完整代码见开篇留的GitHub源码地址(几乎囊括了五种数据类型的所有操作)。主要是熟悉一些常用命令的功能及用法。

public static void SetOperate(Jedis jedis, ShardedJedis shardedJedis) {        System.out.println("================set=================");        System.out.println("清空库中所有数据:" + jedis.flushDB());        System.out.println("=============增=============");        System.out.println("向sets集合中加入元素element001:"                + jedis.sadd("sets", "element001"));        System.out.println("向sets集合中加入元素element002:"                + jedis.sadd("sets", "element002"));        System.out.println("向sets集合中加入元素element003:"                + jedis.sadd("sets", "element003"));        System.out.println("向sets集合中加入元素element004:"                + jedis.sadd("sets", "element004"));        System.out.println("向sets集合中加入元素element001:"                + jedis.sadd("sets", "element001"));        System.out.println("查看sets集合中的所有元素:" + jedis.smembers("sets"));        System.out.println();        System.out.println("=============删=============");        System.out.println("集合sets中删除元素element003:"                + jedis.srem("sets", "element003"));        // smembers(key) :返回名称为key的set的所有元素        System.out.println("查看sets集合中的所有元素:" + jedis.smembers("sets"));        /*         * System.out.println("sets集合中任意位置的元素出栈:"+jedis.spop("sets")); --无实际意义         * System.out.println("查看sets集合中的所有元素:"+jedis.smembers("sets"));         */        System.out.println();        System.out.println("=============查=============");        // sismember(key, member) :member是否是名称为key的set的元素        System.out.println("判断element001是否在集合sets中:"                + jedis.sismember("sets", "element001"));        // scard(key) :返回名称为key的set的基数        System.out.println("基数: " + jedis.scard("sets"));        System.out.println("循环查询获取sets中的每个元素:");        Set<String> set = jedis.smembers("sets");        Iterator<String> it = set.iterator();        while (it.hasNext()) {            Object obj = it.next();            System.out.println(obj);        }        System.out.println();        System.out.println("=============集合运算=============");        System.out.println("sets1中添加元素element001:"                + jedis.sadd("sets1", "element001"));        System.out.println("sets1中添加元素element002:"                + jedis.sadd("sets1", "element002"));        System.out.println("sets1中添加元素element003:"                + jedis.sadd("sets1", "element003"));        System.out.println("sets2中添加元素element002:"                + jedis.sadd("sets2", "element002"));        System.out.println("sets2中添加元素element003:"                + jedis.sadd("sets2", "element003"));        System.out.println("sets2中添加元素element004:"                + jedis.sadd("sets2", "element004"));        System.out.println("查看sets1集合中的所有元素:" + jedis.smembers("sets1"));        System.out.println("查看sets2集合中的所有元素:" + jedis.smembers("sets2"));        System.out.println("sets1和sets2交集:" + jedis.sinter("sets1", "sets2"));        System.out.println("sets1和sets2并集:" + jedis.sunion("sets1", "sets2"));        // 记A,B是两个集合,则所有属于A且不属于B的元素构成的集合,为“差集”        System.out.println("sets1和sets2差集:" + jedis.sdiff("sets1", "sets2"));    }

—–乐于分享,共同进步
—–Any comments greatly appreciated
—–诚心欢迎各位交流讨论!QQ:1138517609

原创粉丝点击