文章目录
前言
随着当前时代的发展,互联网也迎来了巨大的挑战。为了应对用户高并发、大流量的访问,应用中添加缓存就非常有必要了。缓存有很多,笔者这里将以Redis为例,介绍下这款缓存利器的基础使用。
本系列文章,笔者准备对互联网缓存利器Redis的使用,做一下简单的总结,内容大概如下:
博文内容 | 资源链接 |
---|---|
Linux环境下搭建Redis基础运行环境 | https://blog.csdn.net/smilehappiness/article/details/107298145 |
互联网缓存利器-Redis的使用详解(基础篇) | https://blog.csdn.net/smilehappiness/article/details/107592368 |
Redis基础命令使用Api详解 | https://blog.csdn.net/smilehappiness/article/details/107593218 |
Redis编程客户端Jedis、Lettuce和Redisson的基础使用 | https://blog.csdn.net/smilehappiness/article/details/107301988 |
互联网缓存利器-Redis的使用详解(进阶篇) | https://blog.csdn.net/smilehappiness/article/details/107592336 |
如何基于Redis实现分布式锁 | https://blog.csdn.net/smilehappiness/article/details/107592896 |
基于Redis的主从复制、哨兵模式以及集群的使用,史上最详细的教程来啦~ | https://blog.csdn.net/smilehappiness/article/details/107433525 |
Redis相关的面试题总结 | https://blog.csdn.net/smilehappiness/article/details/107592686 |
1 Redis的定义
Redis是一个key-value存储系统
。和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set --有序集合)和hash(哈希类型)。这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。在此基础上,redis支持各种不同方式的排序。与memcached一样,为了保证效率,数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。
Redis 是一个高性能的key-value数据库
。 redis的出现,很大程度补偿了memcached这类key/value存储的不足,在部 分场合可以对关系数据库起到很好的补充作用。它提供了Java,C/C++,C#,PHP,JavaScript,Perl,Object-C,Python,Ruby,Erlang等客户端,使用很方便。
Redis支持主从同步
。数据可以从主服务器向任意数量的从服务器上同步,从服务器可以是关联其他从服务器的主服务器。这使得Redis可执行单层树复制。存盘可以有意无意的对数据进行写操作。由于完全实现了发布/订阅机制,使得从数据库在任何地方同步树时,可订阅一个频道并接收主服务器完整的消息发布记录。同步对读取操作的可扩展性和数据冗余很有帮助。
redis的官网地址,非常好记,是redis.io。(域名后缀io属于国家域名,是british Indian Ocean territory,即英属印度洋领地),Vmware在资助着redis项目的开发和维护。(百度百科)
2 Redis的基础历程
官网是这样描述Redis的:
Redis是一个开源的,基于内存的数据结构存储服务器,被用作数据库、缓存、消息代理
。它支持的数据结构类型有strings, hashes, lists, sets, sorted sets,bitmaps, hyperloglogs, geospatial, Stream。Redis有内置的复制,lua脚本,LRU(Least Recently Used最近最少使用)淘汰,事务,磁盘持久化,并提供高可用的Redis Sentinel哨兵
和自动分区的Redis Cluster集群
。
Redis历程:
- Redis于2008年开始开发,2009年开发完成并对外发布,国内如新浪微博、知乎是使用Redis最早的公司
- VMware公司从2010年开始赞助Redis的开发,Redis作者
Salvatore Sanfilippo
也于2010年3月加入VMware,全职开发Redis - Redis使用C语言开发的,代码托管在GitHub上:https://github.com/antirez/redis
关于redis历程这个,可以参考:https://www.jianshu.com/p/c9e15960c169
3 Linux环境下搭建Redis基础运行环境
现在的互联网开发,服务一般部署在Linux上,如果没有在Linux搭建过Redis的运行环境的小伙伴,可以参考我的另一篇博客入门:Linux环境下搭建Redis基础运行环境
4 Redis基础数据类型的介绍
Redis is an open source (BSD licensed), in-memory data structure store, used as a database, cache and message broker. It supports data structures such as strings
, hashes
, lists
, sets
, sorted sets
with range queries, bitmaps
, hyperloglogs
, geospatial
indexes with radius queries and streams
. Redis has built-in replication, Lua scripting, LRU eviction, transactions and different levels of on-disk persistence, and provides high availability via Redis Sentinel and automatic partitioning with Redis Cluster.
通过官方网站可以看到,Redis数据结构有以上九种,但是常用的数据类型也就五种: 字符串(strings
)、列表(lists
)、集合(sets
)、有序集合(sorted sets
)、哈希表-哈希(hashes
)
注意:
Redis支持多个数据库,并且每个数据库的数据是隔离的不能共享,并且基于单机才有,如果是集群就没有数据库的概念
。
Redis是一个字典结构的存储服务器,而实际上一个Redis实例提供了多个用来存储数据的字典,客户端可以指定将数据存储在哪个字典中。这与我们熟知的在一个关系数据库实例中可以创建多个数据库类似,所以可以将其中的每个字典都理解成一个独立的数据库。
每个数据库对外都是一个从0开始的递增数字命名,Redis默认支持16个数据库
(可以通过配置文件支持更多,无上限),可以通过配置databases来修改这一数字。客户端与Redis建立连接后会自动选择0号数据库,不过可以随时使用SELECT命令更换数据库
。
4.1 strings类型
4.1.1 String字符串类型介绍
字符串类型strings
是redis中最基本的数据类型,也是使用最多的一种数据类型。它能存储任何形式的字符串,包括二进制数据
,byte字节
等,也就是说,string类型能存储任何数据,你可以用它存储序列化后的用户对象,json化的对象,甚至图片,视频等,一个key下的string类型允许存储的最大数据容量是512M。
strings类型可以用来存储任何类型的数据,比如可以使用SET
,GET
命令设置和获取数据。
4.1.2 String类型使用场景
在讲实际应用之前,要声明的是Redis是基于单线程IO多路复用的架构实现的NoSql
,意味着它的操作都是串行化
的,所以在命令操作上不会出现线程安全问题,基于这个特性可以有很多应用。
strings字符串类型使用场景:
-
分布式锁
利用Redis的串行化特性,可以轻松的实现分布式锁。
其中用到的命令有:setnx key value
、expire key time
、del key
命令说明: 其中第一个setnx是指在key不存在时能赋值成功,expire用来来设置key的过期时间,来防止程序异常而没有及时del到key值的情况,会导致死锁问题。 -
分布式session
这里仅仅是利用Redis的数据库功能,把分布式应用的session抽取到Redis中,普通的get、set,命令即可完成分布式环境下,session数据的一致性。 -
分布式限流
思路:借助于Redis的INCR操作来实现Limit限流
将INCR key中储存的数字值增一,如果key不存在,那么key的值会先被初始化为 0 ,然后再执行INCR操作。
如果值包含错误的类型,或字符串类型的值不能表示为数字,那么返回一个错误,本操作的值限制在 64 位(bit)有符号数字表示之内。
当API被调用时,在调用API前进行INCR key,key可以是ip地址相关,用户相关,业务参数相关,或是全局的一个key。如果返回值为1,则表示刚开始调用,赋予key过期时间,然后判断返回值是否大于设定的Limit限流数量,如果大于抛异常或者阻塞重试。
-
商品秒杀
把需要销售的商品提前放入Redis,通过redis的incr
和decr
命令安全的增加和减少库存 -
strings类型可以用来做计数器
使用INCR,DECR,INCRBY,DECRBY这些命令即可实现 -
短信验证
可用于验证码验证,expire key time
,判断exists key是否存在,在短信验证时,当redis中,指定时间内存在数据,且验证码一致,就可以通过短息验证。
4.2 lists类型
4.2.1 List列表类型介绍
列表类型可以存储一个字符串列表,按照数据加入的顺序进行排序,使用LPUSH
命令,RPUSH
命令,可以非常方便地在往列表左右两端添加元素(左边称为头部
,右边称为尾部
)。
一个list列表最大能存放2的32次方-1个元素(4294967295,40多亿个元素)
,列表类型内部使用双向链表实现
,所以向列表两端添加元素的时间复杂度为O(1),获取越接近两端的元素速度就越快
。这意味着即使是一个有几千万个元素的列表,获取头部或尾部的10条记录也是很快的,但是如果尝试访问一个非常大的列表的中间元素,则速度会很慢,因为这是一个时间复杂度o(n)操作。
4.2.2 lists列表结构
首先,List的主要存取操作有lpush、lpop、rpush、rpop,有点像是双向队列。List的实现是灵活多样的,它分别有ziplist(压缩链表)
、LinkedList(双向链表)
两种实现方式。
-
ziplist
如下图所示,它是基于连续内存实现(类似数组)。当然,它的每一个entry的大小可能不是一致的,这就需要特殊的控制手段去解决,所以才叫压缩表
。那么数组有的特性它都会有,比如在lpush、lpop的时候就会有数据的搬移,时间复杂度是O(n),所以,一般在数据元素较少时使用ziplist结构实现。
-
LinkedList
双向链表这个,与我们日常所学的双向链表相差无异,同样也保留则头尾指针、数据长度等数据,这里就不再详细说明,需要了解的去读一读Java的LinkedList源码
也不错。
4.2.3 lists列表类型使用场景
lists列表类型使用场景:
-
消息队列
可以作为一个消息传递的顺序队列,从左边放入元素,从右边取出元素,使用到的命令:LPUSH
,RPOP
,brpop
其中brpop key time
为阻塞式弹出,当队列中为空时会阻塞当前操作,该操作需要添加超时参数,单位为秒。 -
有限集合
使用ltrim key start end
操作可以获取一个固定位置的数据,可以快速实现一个有限的集合 -
类似时间轴的应用,每个时间点发生的重大事件存入list形成时间轴
使用到的命令:LPUSH
,LRANGE
-
记住最近的操作,往list列表头部放入元素,取头部的几个元素即为最近记录
使用到的命令:LPUSH
,LTRIM
4.3 sets类型
4.3.1 Set集合类型介绍
sets集合类型是一个无顺序的字符串集合,集合中每个元素都是不同的,也就是不允许有重复数据,多次添加同一个元素,集合中只会有一个该元素。
一个set集合最大能存放2的32次方-1个元素(4294967295,40多亿个元素)
,集合类型sets和列表类型lists的最大的区别是无序性/有序性和唯一性(不可重复)/可重复
。set类型在redis内部是使用的值为空的散列表(hash table)
,所以添加或删除元素操作的时间复杂度都是O(1)。
Set是一个不允许重复的,无顺序的数据集合。值得注意的是,这里说的无顺序其实还是有一点歧义的,那么到底是怎么回事呢?
实现结构:
-
IntSet。
这里的IntSet是一种在满足特定情况下所使用的数据结构。这种情况就是当全部value都为整型时
,redis会使用IntSet这种结构。在这个情况下它是有序的。这是为什么呢?先从结构开始说起,为了平衡空间的性能的消耗,Redis在数据都为整型的时候使用了一种基于动态数组的结构体,同时在存放元素时保正元素的大小顺序,这样就可以使用二分查找
以时间复杂度O(logn)来完成增删改查的操作。这样就节省了很多空间。至于增和删操作,同样会涉及到数组的数据搬移操作。下面为它的结构体代码:
typedef struct intset { // 编码方式 uint32_t enconding; // 集合包含的元素数量 uint32_t length; // 保存元素的数组 int8_t contents[]; } intset;
-
HashTable
当存在非整型数据的时候,Redis会自动把IntSet转换为HashTable的结构存放数据,但HashTable不能转换为IntSet。这里的HashTable与上面Hash结构提到的HashTable没有太大的差别。唯一的差别就在于Set存放在HashTable中只有Key值,没有value值,所以在HashTable的Entry中,Enrty的value永远为null
。
4.3.2 sets集合类型与列表类型lists的区别
集合类型sets和列表类型lists简单区别:
- lists列表类型,可以存储有序可重复的key,而sets集合类型只能存储无序不可重复的key
- lists列表类型,查询时效率会高一些,因为是一个链表结构,而sets集合类型增删元素时效率会高一些,因为内部采用的是值为空的散列表(hash table)。
- 集合最大的优势在于可以进行交集、并集、差集操作
4.3.3 sets集合类型的使用场景
-
可用于较多随机访问的场景中
如:随机用户抽奖,把名单放入set集合中,利用set无序不会重复的特性,通过srandmember key
随机返回一个set中的数据即可。
使用的命令:
spop
,srandmember
-
跟踪不同的事情,比如:每天的ip访问量、身份证等
-
用户标签
当用户在使用某个产品的时候,后台可能会记录该用户对某个东西的喜好,从而在该标签中记录该用户。同时,可以利用sinter key1 key2
、sunion
、sdiff
返回标签中的交集、并集、差集。这样就可以轻松的得出用户的共同喜好、所有喜好、非共同喜好等数据。
4.4 sorted sets类型
4.4.1 有序集合zset类型介绍
有序集合zset类型与集合sets相似,也是一个无重复元素的集合,不同的是zset类型的每个元素,会关联一个double类型的分数,这个分数用于对集合元素进行排序,所以说有序集合sorted sets(zset)
是一个有序不可重复的数据
。
SortSet是一个实现了数据有序且唯一的键值对集合
。其中,Entry的键为string类型,值为整型或浮点型,表示权值score。其中sorted set的顺序就是通过score的值来确定的
。zset中的元素是唯一的,但是每个元素的分数是可以重复的。
4.4.2 sorted sets实现结构
SortSet的实现结构同样有两种,一种是ZipList
结构实现,适用于较少数据的情况。另一种是SkipList+HashTable
的形式,使用与数据较多的情况,其中SkipList是在保证有序的情况下优化范围查找
的时间复杂度,而HashTable
则是优化增删改查
的时间复杂度。
-
ZipList
SortSet的ZipList和Hash中的数据结构类似,同样也是存放键值对,但是它维护了基于Score的有序性(默认从小到大
)。 -
SkipList+HashTable
首先来说明主要的SkipList(跳表)
,跳表是一种基于有序链表
,通过建立多层索引
,以空间换时间
的方式实现平均查找效率为O(logn)复杂度
的一种数据结构。下面给出一个跳表的基本形式图:
可以看到在根据数据的权值Score进行查找的时候,从最顶层的索引开始查找。当找到数据在某个范围后,在往下一层的索引查找,然后就这样一路缩小查找的范围。看上去是不是有点像二分查找
?至于跳表的具体介绍和实现,可以参考这篇文章:为什么Redis要用跳表实现有序集合?上面可以看到,基于跳表的实现的有序集合可以完成增删改查实现O(logn)的时间复杂度,那么有没有更加快的方式来实现O(1)的时间复杂度呢?通常说起O(1)的时间复杂度都会想起HashTable这个数据结构。Redis就利用HashTable+SkipList的组合数据结构,
HashTable来实现增删改查的时间复杂度为O(1)的同时SkipList保证数据的有序性
,可以方便的获取一个范围的数据。至于HashTable的实现与前面谈到的一致,下面用一张图来说明两个数据结构结合是什么样子的。
上图中,每一个节点可以看成一个跳表的节点
同时也是HashTable中的一个节点
。在看跳表的时候,我们需要忽略hnext指针,每个节点通过双向链表来保证有序性。
在看HashTable的时候,可以忽略prev指针和next指针。看上去就是一个用拉链法解决冲突的HashTable,而hnext就是指向下一节点的指针。这样,当我们需要增删改查的时候,利用HashTable的特性实现时间复杂度为1的操作,当我们需要基于权值Score进行范围查找的时候可以通过SkipList进行时间复杂度为O(logn)的查找
。
4.4.3 sorted sets使用场景
-
获取某个权值范围的用户
例如在应用中获取积分为80到100的用户,可以使用ZRANGEBYSCORE key 80 100 WITHSCORES
来输出score在80到100间的用户。 -
实时的数据排行榜
比如使用zrange key start end
,根据积分、热度、评论、销售等可以衡量的权值Score进行排行,其中score排序为从小到大,用ZREVRANGE实现从大到小排序
。每次数据的更新会更新分数,最后用分数进行排行,即可实现排行榜。
4.5 hashes类型
4.5.1 hash类型介绍
首先,Hash的特性我们可以想象为Java集合中的HashMap,一个hash中可以有多个field:value(键值对)。关于hash的实现同样有两种情况。一种是基于ZipList,一种是基于HashTable实现。
Redis hash是一个键值(key=>value)对集合,Redis hash是一个string类型的field和value的映射表
,hash特别适合用于存储对象。每个 hash 可以存储 2的32次方-1个键值对(40多亿)。
4.5.2 hash类型应用场景
-
存放对象Object
存储一些结构化的数据,比如用户的昵称、年龄、性别、积分等,存储一个用户信息对象数据。你可能会发现,从宏观上来说,这种一个key , field1 : value1 ,field2 : value2 一个键值,对应多个字段field的格式非常适合用于描写一个对象。所以,
hash一般会用于描述一个对象
,但其实我们在实际中也有可能会用一个Json格式的字符串来描述一个对象。那么这两种方法都可行的情况下,会有什么优缺点呢?利用hash描述一个对象: 可以做到序列化开销小,可以单独修改某一个字段而不用读出全部数据,但是使用比较复杂。
而使用json描述对象: 使用简单但需要耗费额外的序列化开销。需要使用什么形式,具体情况需要具体分析。 -
结合Json描述对象的集合
例如,在商城应用中,可以利用Hash的key来描述一个用户的id,而field用于描述用户的购物车列表中的一个物品的详细信息。
5 Redis基础api使用总结
5.1 设置和取消过期时间
在Redis中提供了Expire
命令设置一个键的过期时间,到期以后Redis会自动删除它,实际开发中也经常使用。
EXPIRE命令的使用:
-
先设置好数据,然后再设置该key的过期时间
set key value
EXPIRE key seconds
EXPIRE 返回值为1表示设置成功,0表示设置失败或者key不存在 -
在设置数据的同时,可以直接设置过期时间
SETEX key seconds value
其中seconds参数表示键的过期时间,单位为秒
PEXPIRE命令的使用:
PEXPIRE key 1000
PEXPIRE命令的单位是毫秒,即PEXPIRE key 1000
与EXPIRE key 1
相等
EXPIRE命令的seconds命令必须是整数,所以最小单位是1秒,如果想要更精确的设置key的过期时间可以使用PEXPIRE命令,当然实际过程中用秒的单位就够用了
PERSIST命令的使用:
如果想取消key的过期时间(使该key恢复成永久的),可以使用PERSIST
命令,如果该命令执行成功,清除了过期时间,则返回1 ,否则返回0(key不存在或者本身就是永久的)
5.2 获取Redis的所有的key
- 查看redis中当前数据库0号库所有的key
keys *
5.3 TTL命令和PTTL命令的使用
-
TTL命令
如果想知道一个key还有多久时间过期,可以使用TTL命令:
TTL key
如果key没有设置过期时间,通过TTL命令会返回-1, -1表示永不过期,当键不存在时,TTL命令会返回-2 。 -
PTTL命令
以毫秒单位获取键的剩余有效时间,即过期时间还有多少
Api的命令非常多,限于篇幅,这部分内容我写到了另一篇博文中了,需要的童鞋们,可以参考我的另一篇博客:Redis基础命令使用Api详解
6 Redis基础客户端的使用
Redis的客户端有好多种,常用的也就这几种:命令行客户端
、Jedis客户端
、Lettuce客户端
以及Redisson客户端
。限于篇幅,这部分内容我写到了另一篇博文中了,需要的童鞋们,可以参考我的另一篇博客:Redis编程客户端Jedis、Lettuce和Redisson的基础使用
好啦,本篇就暂时介绍到这里了,这些内容掌握了以后,就可以进行Redis的基础应用开发了,希望多老铁们有所帮助!
参考资料链接 :
https://www.cnblogs.com/hill1126/p/11523329.html
https://www.imooc.com/article/70762
写博客是为了记住自己容易忘记的东西,另外也是对自己工作的总结,希望尽自己的努力,做到更好,大家一起努力进步!
如果有什么问题,欢迎大家评论,一起探讨,代码如有问题,欢迎各位大神指正!
给自己的梦想添加一双翅膀,让它可以在天空中自由自在的飞翔!