实际项目中使用redis比较多,本文来了解redis概念相关的内容,不聊架构。说一个前提,Redis的高效性和灵活性正是得益于对于同一个对象类型采取不同的底层结构,并在必要的时候对二者进行转换;以及各种底层结构对内存的合理利用。所以redis的五种结构通常都有两种实现方式。
redis是一个开源的、底层使用C语言编写的、支持网络交互的、可基于内存也可持久化的高性能Key-Value数据库,并且支持多语言客户端、高可用的,似乎高大上的词给给了它,它也是一个数据库,不过是内存数据库,主要解决读写慢的问题,在内存中读取比磁盘快太多了,每秒读写次数达到千万级别,采用单线程处理网络请求。
我们都知道redis有5个数据类型,但是key只能是字符串,为什么呢?我个人思考key只是表名一个标示,没必要把数据类型弄的这么复杂,所以不需要list、set等数据类型,哪为什么不用int等数字,首先字符串可以表示为数字,另一个方面,数字太受约束,字符串就要灵活很多。所以我们一般用字符串作为key。
对外对数据类型一共有5种:字符串、list、set、zset、hash。这些都是value的数据类型,是redis对外的数据类型,其实底层都是二进制的字节数组(byte[]),有人尝试把对象转换为二进制然后存到redis中,这里就不研究了,地址:如何使用节点在redis中存储二进制对象?,redis还有自己的数据结构,这些数据结构支撑来对外的数据类型,这个之后会讲解。
String在redis内部存储默认就是一个字符串,被redisObject所引用,当遇到incr,decr等操作时会转成数值型进行计算,此时redisObject的encoding字段为int。普通的字符串有两种,embstr和raw。embstr应该是Redis 3.0新增的数据结构,在2.8中是没有的。如果字符串对象的长度小于39字节,就用embstr对象。否则用传统的raw对象。
redis 127.0.0.1:6379> set baidu http://www.baidu
OK
redis 127.0.0.1:6379> append baidu .com
(integer) 20
redis 127.0.0.1:6379> get baidu
"http://www.baidu.com"
// 数字处理
redis 127.0.0.1:6379> set visitors 0
OK
redis 127.0.0.1:6379> incr visitors
(integer) 1
redis 127.0.0.1:6379> incr visitors
(integer) 2
redis 127.0.0.1:6379> get visitors
"2"
redis 127.0.0.1:6379> incrby visitors 100
(integer) 102
redis 127.0.0.1:6379> get visitors
"102"
redis 127.0.0.1:6379> type baidu
string
redis 127.0.0.1:6379> type visitors
string
redis 127.0.0.1:6379> ttl baidu
(integer) -1
redis 127.0.0.1:6379> rename baidu baidu-site
OK
redis 127.0.0.1:6379> get baidu
(nil)
redis 127.0.0.1:6379> get baidu-site
"http://www.baidu.com"
疑问:如果incr 是一个不可以转换为数字的字符串会出现什么?
如果指定的key不存在,那么在执行incr操作之前,会先将它的值设定为0。
如果指定的key中存储的值不是字符串类型(fix:)或者存储的字符串类型不能表示为一个整数,
那么执行这个命令时服务器会返回一个错误(eq:(error) ERR value is not an integer or out of range)。
由于INCR等指令本身就具有原子操作的特性,所以我们完全可以利用redis的INCR、INCRBY、DECR、DECRBY等指令来实现原子计数的效果,假如,在某种场景下有3个客户端同时读取了mynum的值(值为2),然后对其同时进行了加1的操作,那么,最后mynum的值一定是5。不少网站都利用redis的这个特性来实现业务上的统计计数需求。
翻译成中文叫做“列表”,redis中的lists在底层实现上并不是数组,而是链表,所以链表的优点、缺点它都有,push方法: lpush 从左边添加数据, rpush从右边添加数据。pop:lpop 左边取数据, rpop右边取数据。
//新建一个list叫做mylist,并在列表头部插入元素"1"
127.0.0.1:6379> lpush mylist "1" // //返回当前mylist中的元素个数
(integer) 1
127.0.0.1:6379> rpush mylist "2" //在mylist右侧插入元素"2"
(integer) 2
127.0.0.1:6379> lpush mylist "0" //在mylist左侧插入元素"0"
(integer) 3
127.0.0.1:6379> lrange mylist 0 1 //列出mylist中从编号0到编号1的元素
1) "0"
2) "1"
127.0.0.1:6379> lrange mylist 0 -1 //列出mylist中从编号0到倒数第一个元素
1) "0"
2) "1"
3) "2"
lists的应用相当广泛,随便举几个应用场景:
set 是一种无序的集合,集合中的元素没有先后顺序,可以理解为一堆值不重复的列表。set 的内部实现是一个 value永远为null的哈希表,实际就是通过计算hash的方式来快速排重的,这也是set能提供判断一个成员是否在集合内的原因。
其实我比较奇怪为什么会有哈希表?
127.0.0.1:6379> sadd myset "one" //向集合myset中加入一个新元素"one"
(integer) 1
127.0.0.1:6379> sadd myset "two"
(integer) 1
127.0.0.1:6379> smembers myset //列出集合myset中的所有元素
1) "one"
2) "two"
127.0.0.1:6379> sismember myset "one" //判断元素1是否在集合myset中,返回1表示存在
(integer) 1
127.0.0.1:6379> sismember myset "three" //判断元素3是否在集合myset中,返回0表示不存在
(integer) 0
127.0.0.1:6379> sadd yourset "1" //新建一个新的集合yourset
(integer) 1
127.0.0.1:6379> sadd yourset "2"
(integer) 1
127.0.0.1:6379> smembers yourset
1) "1"
2) "2"
127.0.0.1:6379> sunion myset yourset //对两个集合求并集
1) "1"
2) "one"
3) "2"
4) "two"
业务场景主要利用set的特点:判断是否存在包含、两个集合求并集、交集等。比如两个人的共同好友数,非共同好友数。
有序集合中的每个元素都关联一个序号(score),这便是排序的依据。很多时候,我们都将redis中的有序集合叫做zsets,这是因为在redis中,有序集合相关的操作指令都是以z开头的,比如zrange、zadd、zrevrange、zrangebyscore等等。Redis sorted set的内部使用HashMap和跳跃表(SkipList)来保证数据的存储和有序,HashMap里放的是成员到score的映射,而跳跃表里存放的是所有的成员,排序依据是HashMap里存的score,使用跳跃表的结构可以获得比较高的查找效率,并且在实现上比较简单。有序集合的编码可能两种,一种是ziplist,另一种是skiplist与dict的结合。
Redis有序集合添加、删除和测试的时间复杂度均为O(1)(固定时间,无论里面包含的元素集合的数量)。列表的最大长度为2^32- 1元素(4294967295,超过40亿每个元素的集合)。
redis 127.0.0.1:6379> zadd dbs 100 redis
(integer) 1
redis 127.0.0.1:6379> zadd dbs 98 memcached
(integer) 1
redis 127.0.0.1:6379> zadd dbs 99 mongodb
(integer) 1
redis 127.0.0.1:6379> zadd dbs 99 leveldb
(integer) 1
redis 127.0.0.1:6379> zcard dbs // ZCARD key 得到的有序集合成员的数量
(integer) 4
redis 127.0.0.1:6379> zcount dbs 10 99 // ZCOUNT key min max 计算一个有序集合成员与给定值范围内的分数
(integer) 3
redis 127.0.0.1:6379> zrank dbs leveldb // ZRANK key member 确定成员的索引中有序集合
(integer) 1
redis 127.0.0.1:6379> zrank dbs other
(nil)
redis 127.0.0.1:6379> zrangebyscore dbs 98 100 // ZREMRANGEBYSCORE key min max 在给定的分数之内删除所有成员的有序集合
1) "memcached"
2) "leveldb"
3) "mongodb"
4) "redis"
应用场景:
哈希是从redis-2.0.0版本之后才有的数据结构。Redis Hash对应Value内部实际就是一个HashMap,实际这里会有2种不同实现,这个Hash的成员比较少时Redis为了节省内存会采用类似一维数组的方式来紧凑存储,而不会采用真正的HashMap结构,对应的value redisObject的encoding为zipmap,当成员数量增大时会自动转成真正的HashMap,此时encoding为ht。hashes存的是字符串和字符串值之间的映射,比如一个用户要存储其全名、姓氏、年龄等等,就很适合使用哈希。
127.0.0.1:6379> hset person name jack
(integer) 1
127.0.0.1:6379> hset person age 20
(integer) 1
127.0.0.1:6379> hset person sex famale
(integer) 1
127.0.0.1:6379> hgetall person // HGETALL key 获取对象的所有属性域和值
1) "name"
2) "jack"
3) "age"
4) "20"
5) "sex"
6) "famale"
127.0.0.1:6379> hkeys person // HVALS key 获取对象的所有属性值
1) "name"
2) "age"
3) "sex"
127.0.0.1:6379> hvals person / / HVALS key 获取对象的所有属性值
1) "jack"
2) "20"
3) "famale"
hash 可以存储一个对象。应用场景:hash 类型十分适合存储对象类数据,理由如下:
redis提供了两种持久化的方式,分别是RDB(Redis DataBase)和AOF(Append Only File)。
我比较奇怪的是为什么还要持久化?很多时候
Redis中key的数据类型有哪些?(不是value)
如何使用节点在redis中存储二进制对象?
超详细Redis入门教程
深入了解Redis底层数据结构
深入浅出Redis-redis底层数据结构(上)