目录
- 1.Redis数据库简介
- 2.Redis主要特点
- 3. Redis数据库的安装以及配置
- 4. Redis的四种基本的数据结构
- 4.1 string字符串
- 4.2 list列表
- 4.3 hash哈希
- 4.4 set集合
- 4.5 sortedset有序集合
- 5. 容器型数据结构
- 6. 过期时间
1.Redis数据库简介
- Redis是一个非常快速的、开源的、使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、非关系类型的、Key-Value数据库,并提供多种语言的API。
- Redis 是一个高性能的key-value数据库。 redis的出现,很大程度补偿了memcached这类key/value存储的不足,在部分场合可以对关系数据库起到很好的补充作用。它提供了Java,C/C++,C#,PHP,JavaScript,Perl,Object-C,Python,Ruby,Erlang等客户端,使用很方便。
- Redis支持主从同步。数据可以从主服务器向任意数量的从服务器上同步,从服务器可以是关联其他从服务器的主服务器。这使得Redis可执行单层树复制。存盘可以有意无意的对数据进行写操作。由于完全实现了发布/订阅机制,使得从数据库在任何地方同步树时,可订阅一个频道并接收主服务器完整的消息发布记录。同步对读取操作的可扩展性和数据冗余很有帮助。
2.Redis主要特点
- 高级数据结构:为值提供五种可能的数据类型:字符串,列表,集合,哈希和有序集合。提供了这些数据类型独有的操作,并且具有记录良好的时间复杂度(Big O表示法)。
- 高性能:由于其内存特性,项目维护者将复杂性保持在最低限度的承诺以及基于事件的编程模型,Redis 在读写操作方面具有出色的性能。
- 没有依赖关系的轻量级:用ANSI C编写,没有外部依赖关系。适用于所有POSIX环境。Windows不受官方支持,但Microsoft提供了实验性版本。
- 高可用性: 内置支持异步,非阻塞,主/从复制,以确保数据的高可用性。目前有一种名为Redis Sentinel的高可用性解决方案目前可以使用,但仍被视为正在进行的工作。、
3. Redis数据库的安装以及配置
- Redis数据库使用的最佳环境是Linux和MAC系统,但是官方在github上也提供了相应的window64位的Redis数据库安装压缩包
- 地址如下,下载Redis数据库
- 进入GitHub主页后继续往下翻,直到看到
- window系统的选择第二个压缩包进行下载,后面两个都是Redis的底层数据库源码,深入学习可以使用。
- 解压后的文件如图
- 接下来,打开cmd命令行,进入Redis的下载目录,如图输入
如图表示安装成功,接下来来体验Redis数据库的基本功能
- 首先上述中开启Redis服务器的cmd窗口不能关闭,否则就无法访问服务端了。
- 打开另外一个cmd窗口,进入Redis数据库的下载目录,输入
redis-cli.exe -h 127.0.0.1 -p 6379
- 如图则是成功下载了Redis数据库。
以下博客内容总结来源于老钱的Redis深度历险一书
4. Redis的四种基本的数据结构
- string字符串
- list列表
- set集合
- hash哈希
- zset有序集合
4.1 string字符串
- string表示的是一个可变的字节数组,可以进行字符串初始化,获取字符串的长度,获取字符串的子串,覆盖字符串内容,追加子串等等等。
- Redis的字符串是动态字符串,是可以修改的字符串,内部结构实现上类似于Java的ArrayList,采用预分配冗余空间的方式来减少内存的频繁分配,如图中所示,内部为当前字符串实际分配的空间capacity一般要高于实际字符串长度len。当字符串长度小于1M时,扩容都是加倍现有的空间,如果超过1M,扩容时一次只会多扩1M的空间。需要注意的是字符串最大长度为512M。
设置字符串内容
获取字符串的内容
mset 批量设置字符串的内容
mget 批量获取字符串的内容
获取字符串的长度
获取子串
覆盖子串
追加子串
还可以对字符串转化为整数以及进行加减处理
incr a
等价于incrby a 1decr a
等价于decrby a 1字符串转化为整数也是有范围的不可以超过Long.Max,不可以低于Long.Min。
变量的过期和删除
- 字符串删除使用del指令主动删除,使用expire指令设置过期时间,到时间自动删除。
过十秒后
如何获取变量的寿命呢?使用ttl就可以
ttl后是6,表示还剩六秒的寿命。
del删除简单,直接del 变量名
setex name 5 codehole
等价于set+expire,定义变量同时又设置过期时间。
setnx name codehole
codehole变量如果存在则创建失败,否则创建成功。
4.2 list列表
- Redis中的列表的存储结构是双向链表,利于首尾插入删除,不利于定位操作
- 可以有负下标,链表元素的位置为0,1,2,3,,,,n-1,还可以表示为-1,-2,-3,,,,-n。-1表示倒数第一个元素对应第n-1个元素。
- 队列或者堆栈,链表可以从表头或者表尾插入或者删除元素,类似与队列和栈的概念。使用rpush或者rpop或者lpush或者lpop四条,可以将链表用作队列或者堆栈。
右进左出
左进右出
其实还是很好区分的,lpush可以看作left push就是左进入啦。
日常中列表作为异步队列使用,将需要延后处理的任务结构体序列化成字符串塞进 Redis 的列表,另一个线程从这个列表中轮询数据进行处理
列表的长度使用llen指令
随机读
- 使用lindex指令访问指定位置的元素,使用lrange指令获取链表子元素列表,提供start和end下标(包括end的)。
修改元素
- 使用lset指令在指定位置修改元素
插入元素
- 使用linsert指令在列表中插入元素,在linsert指令里增加了方向参数before/after来显示指示前置和后置插入。不过让人意想不到的是linsert指令并不是通过指定位置来插入,而是通过指定具体的值。这是因为在分布式环境下,列表的元素总是频繁变动的,意味着上一时刻计算的元素下标在下一时刻可能就不是你所期望的下标了。
删除元素
- 列表的删除操作也不是通过指定下标来确定元素的,你需要指定删除的最大个数以及元素的值。
定长列表
- 在实际应用场景中,我们有时候会遇到「定长列表」的需求。比如要以走马灯的形式实时显示中奖用户名列表,因为中奖用户实在太多,能显示的数量一般不超过100条,那么这里就会使用到定长列表。维持定长列表的指令是ltrim,需要提供两个参数start和end,表示需要保留列表的下标范围,范围之外的所有元素都将被移除。
- 维持定长列表的指令是ltrim,需要提供两个参数start和end,表示需要保留列表的下标范围,如果指定参数的end对应的真实下标小于start,其效果等价于del,因为这样的参数表示需要保留列表元素的下标范围为空。
快速列表
深入一点,Redis 底层存储的不是一个简单的 linkedlist,而是称之为
快速链表 quicklist 的一个结构。首先在列表元素较少的情况下会使用一块连续的内存存储,这个结构是 ziplist,也即是压缩列表。它将所有的元素紧挨着一起存储,分配的是一块连续的内存。当数据量比较多的时候才会改成 quicklist。因为普通的链表需要的附加指针空间太大,会比较浪费空间,而且会加重内存的碎片化。比如这个列表里存的只是 int 类型的数据,结构上还需要两个额外的指针 prev 和 next 。所以 Redis 将链表和 ziplist 结合起来组成了 quicklist。也就是将多个ziplist 使用双向指针串起来使用。这样既满足了快速的插入删除性能,又不会出现太大的空间冗余。
4.3 hash哈希
- 哈希等价于Java语言的HashMap,在实现结构上它使用二维结构,第一维是数组,第二维是链表,hash的内容key和value存放在链表中,数组里存放的是链表的头指针。通过key查找元素时,先计算key的hashcode,然后用hashcode对数组的长度进行取模定位到链表的表头,再对链表进行遍历获取到相应的value值,链表的作用就是用来将产生了「hash碰撞」的元素串起来。Java语言开发者会感到非常熟悉,因为这样的结构和HashMap是没有区别的。哈希的第一维数组的长度也是2^n。
增加元素
- 使用hset一次增加一个键值对,也可以使用hmset一次增加多个键值对
获取元素
- 可以通过hget定位具体key对应的value,可以通过hmget获取多个key对应的value,可以使用hgetall获取所有的键值对,可以使用hkeys和hvals分别获取所有的key列表和value列表,这些操作和java语言的Map接口时类似的。
删除元素
- 可以使用hdel删除指定key,hdel支持同时删除多个key
删除的是键
判断元素是否存在
- 使用hget获得key对应的value是否为空就直到对应的元素是否存在了,不过如果value的字符串长度特别大,通过这种方式来判断元素存在与否就略显浪费,这时可以使用hexists指令。
1表示为存在。
计数器
- hash结构还可以当成计数器来使用,对于内部的每一个key都可以作为独立的计数器。如果value值不是整数,调用hincrby指令会出错。
扩容
- 当hash内部的元素比较拥挤时(hash碰撞比较频繁),就需要进行扩容。扩容需要申请新的两倍大小的数组,然后将所有的键值对重新分配到新的数组下标对应的链表中(rehash)。如果hash结构很大,比如有上百万个键值对,那么一次完整rehash的过程就会耗时很长。这对于单线程的Redis里来说有点压力山大。所以Redis采用了渐进式rehash的方案。它会同时保留两个新旧hash结构,在后续的定时任务以及hash结构的读写指令中将旧结构的元素逐渐迁移到新的结构中。这样就可以避免因扩容导致的线程卡顿现象。
缩容
- Redis的hash结构不但有扩容还有缩容,从这一点出发,它要比Java的HashMap要厉害一些,Java的HashMap只有扩容。缩容的原理和扩容是一致的,只不过新的数组大小要比旧数组小一倍。
4.4 set集合
- Java程序员都知道HashSet的内部实现使用的是HashMap,只不过所有的value都指向同一个对象。Redis的set结构也是一样,它的内部也使用hash结构,所有的value都指向同一个内部值。
增加元素
- 可以增加多个元素
读取元素
- 使用smembers列出所有元素,使用scard获取集合长度,使用srandmember获取随机count个元素,如果不提供count参数,默认为1。
删除元素
- 使用srem删除一个到多个元素,使用spop删除随机一个元素
判断元素是否存在
- 使用sismember指令,只能接受单个元素
4.5 sortedset有序集合
- SortedSet(zset)是Redis提供的一个非常特别的数据结构,一方面它等价于Java的数据结构Map<String, Double>,可以给每一个元素value赋予一个权重score,另一方面它又类似于TreeSet,内部的元素会按照权重score进行排序,可以得到每个元素的名次,还可以通过score的范围来获取元素的列表。
- zset底层实现使用了两个数据结构,第一个是hash,第二个是跳跃列表,hash的作用就是关联元素value和权重score,保障元素value的唯一性,可以通过元素value找到相应的score值。跳跃列表的目的在于给元素value排序,根据score的范围获取元素列表。
增加元素
- 通过zadd指令可以增到一到多个value/score对,score放在前面
长度
删除元素
计数器实现
- 和hash一样zset也可以作为计数器使用。
获取排名和分数
- 通过zscore指令获取指定元素的权重,通过zrank指令获取指定元素的正向排名,通过zrevrank指令获取指定元素的反向排名[倒数第一名]。正向是由小到大,负向是由大到小。
根据排名范围获取元素列表
- 通过zrange指令指定排名范围参数获取对应的元素列表,携带withscores参数可以一并获取元素的权重。通过zrevrange指令按负向排名获取元素列表[倒数]。正向是由小到大,负向是由大到小。
根据score范围获取列表
- 通过zrangebyscore指令指定score范围获取对应的元素列表。通过zrevrangebyscore指令获取倒排元素列表。正向是由小到大,负向是由大到小。参数-inf表示负无穷,+inf表示正无穷。
根据范围移除元素列表,可以通过排名范围,也可以通过score范围来一次性移除多个元素
跳跃列表
- zset内部的排序功能是通过「跳跃列表」数据结构来实现的,它的结构非常特殊,也比较复杂。
因为zset要支持随机的插入和删除,所以它不好使用数组来表示。我们先看一个普通的链表结构。 - 我们需要这个链表按照score值进行排序。这意味着当有新元素需要插入时,需要定位到特定位置的插入点,这样才可以继续保证链表是有序的。通常我们会通过二分查找来找到插入点,但是二分查找的对象必须是数组,只有数组才可以支持快速位置定位,链表做不到,那该怎么办?
- 想想一个创业公司,刚开始只有几个人,团队成员之间人人平等,都是联合创始人。随着公司的成长,人数渐渐变多,团队沟通成本随之增加。这时候就会引入组长制,对团队进行划分。每个团队会有一个组长。开会的时候分团队进行,多个组长之间还会有自己的会议安排。公司规模进一步扩展,需要再增加一个层级——部门,每个部门会从组长列表中推选出一个代表来作为部长。部长们之间还会有自己的高层会议安排。
- 跳跃列表就是类似于这种层级制,最下面一层所有的元素都会串起来。然后每隔几个元素挑选出一个代表来,再将这几个代表使用另外一级指针串起来。然后在这些代表里再挑出二级代表,再串起来。最终就形成了金字塔结构。
- 想想你老家在世界地图中的位置:亚洲–>中国->安徽省->安庆市->枞阳县->汤沟镇->田间村->xxxx号,也是这样一个类似的结构。
- 「跳跃列表」之所以「跳跃」,是因为内部的元素可能「身兼数职」,比如上图中间的这个元素,同时处于L0、L1和L2层,可以快速在不同层次之间进行「跳跃」。
- 定位插入点时,先在顶层进行定位,然后下潜到下一级定位,一直下潜到最底层找到合适的位置,将新元素插进去。你也许会问那新插入的元素如何才有机会「身兼数职」呢?
- 跳跃列表采取一个随机策略来决定新元素可以兼职到第几层,首先L0层肯定是100%了,L1层只有50%的概率,L2层只有25%的概率,L3层只有12.5%的概率,一直随机到最顶层L31层。绝大多数元素都过不了几层,只有极少数元素可以深入到顶层。列表中的元素越多,能够深入的层次就越深,能进入到顶层的概率就会越大。
5. 容器型数据结构
list/set/hash/zset 这四种数据结构是容器型数据结构,它们共享下面两条通用规则:
1、create if not exists
如果容器不存在,那就创建一个,再进行操作。比如 rpush 操作刚开始是没有列表的,
2、drop if no elements
如果容器里元素没有了,那么立即删除元素,释放内存。这意味着 lpop 操作到最后一个元素,列表就消失了。
6. 过期时间
Redis 所有的数据结构都可以设置过期时间,时间到了,Redis 会自动删除相应的对象。
需要注意的是过期是以对象为单位,比如一个 hash 结构的过期是整个 hash 对象的过期,而不是其中的某个子 key,还有一个需要特别注意的地方是如果一个字符串已经设置了过期时间,然后你调用了
set 方法修改了它,它的过期时间会消失。