Redis看这一篇就够了(一)

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
云原生数据库 PolarDB MySQL 版,通用型 2核4GB 50GB
简介: Redis看这一篇就够了

大数据时代NoSQL开始大行其道,其中常用于缓存的Redis可谓风头正盛,是大小公司技术架构中必不可少的一种中间件,也是职场技术同仁们必知必会的一种技术。本场Chat将从各个方面对Redis进行全面的讲解并分析常见问题。本场Chat将涉及如下内容

  1. Redis的基本概念及背景知识
  2. 五种常用数据对象及应用场景
  3. 数据对象的底层实现方式
  4. Jedis的使用
  5. Redis持久化策略
  6. Redis的事务机制
  7. 分布式锁的实现及改进策略
  8. Redis的删除策略
  9. Cluster集群模式
  10. 缓存穿透、缓存雪崩、缓存击穿

适合人群:不了解Redis的新手,对Redis的实现机制感兴趣的技术人员

本文的全部内容来自我个人在Redis学习过程中整理的博客,是该博客专栏的精华部分。在书写过程中过滤了流程性的上下文,例如部署环境、配置文件等,而致力于像读者讲述其中的核心部分,如果读者有意对过程性内容深入探究,可以移步MaoLinTian的Blog,在这篇索引目录里找到答案分布式技术相关专栏索引,需要注意的是,本文的内容学习来源来自于书籍《Redis的设计与实现》及《黑马程序员-Redis视频教程》,特作相关说明。

Redis的基本概念及背景

首先我们要知道什么是NoSQL,什么又是Redis,为什么需要用Redis,Redis有哪些使用场景?

什么是非关系型数据库

NoSQL = Not Only SQL,也就是非关系型数据库,既然有了SQL,为什么还需要NoSQL?这和时代背景有很大的关系,我们所处的时代可以划分为Web1.0和Web2.0时代:

  • Web1.0,是基于浏览器,用户通过浏览器获取内容信息。
  • Web2.0,是基于1.0,增加了用户与系统的交互,使用者既是网络内容的获取者,也是网络数据的制造者,例如:论坛、博客、微博等相关社交类型的平台。

我们当前身处Web2.0时代,面对很多问题:

  • High performance - 高并发读写,在Web2.0时代,需要依据用户个性化需要高并发读写,关系型数据库读还可以,写就很难做到了。例如论坛这样的站点, 网站的用户并发性非常高,往往达到每秒上万次读写请求,对于传统关系型数据库来说,硬盘I/O是一个很大的瓶颈
  • Huge Storage - 海量数据的高效率存储和访问,海量数据高效率存储和访问, 网站每天产生的数据量是巨大的,对于关系型数据库来说,在一张包含海量数据的表中查询,效率是非常低的,类似FaceBook这样的社交网站、社区。
  • High Scalability &High Availability - 高可拓展性和高可用性, 在基于Web的结构当中,数据库是最难进行横向扩展的,当一个应用系统的用户量和访问量与日俱增的时候,数据库却没有办法像Web server和App Server那样简单的通过添加更多的硬件和服务节点来扩展性能和负载能力。对于很多需要提供24小时不间断服务的网站来说,对数据库系统进行升级和扩展是非常痛苦的事情,往往需要停机维护和数据迁移。

这些问题主要是由于关系型数据库要求的:事务一致性、读写实时性、和复杂SQL的查询,这些都是导致关系型数据库性能差的原因,而这些场景和严格的要求在很多场景下不必要了,例如社交网络。NoSQL因为它的易扩展、大数据量高性能、灵活的数据模型和高可用在社区时代可以发挥很大的作用。

NoSQL的分类

NoSQL 依据存储的内容也分很多种,让我们从这些类别里来定位Redis吧!依据使用场景来划分类别,按照上面我们提到的三种优点,看看哪种能发挥极致:

  • 面向高性能并发读写的key-value数据库:key-value数据库的主要特点是具有极高的并发读写性能,Redis,Tokyo Cabinet,Flare就是这类的代表
  • 面向海量数据访问的面向文档数据库:这类数据库的特点是,可以在海量的数据中快速的查询数据,典型代表为MongoDB以及CouchDB
  • 面向可扩展性的分布式数据库:这类数据库想解决的问题就是传统数据库存在可扩展性上的缺陷,这类数据库可以适应数据量的增加以及数据结构的变化

也正是因为Redis极高的并发读写能力,所以被常用作缓存。

Redis的应用场景

Redis有着广泛的应用场景。典型应用是:内容缓存,主要用于处理大量数据的高访问负载,优点就是快速查询

  • 缓存,缓存现在几乎是所有中大型网站都在用的必杀技,合理的利用缓存不仅能够提升网站访问速度,还能大大降低数据库的压力。Redis提供了键过期功能,也提供了灵活的键淘汰策略,所以,现在Redis用在缓存的场合非常多。
  • 排行榜,很多网站都有排行榜应用的,如京东的月度销量榜单、商品按时间的上新排行榜等。Redis提供的有序集合数据类构能实现各种复杂的排行榜应用。
  • 计数器,什么是计数器,如电商网站商品的浏览量、视频网站视频的播放数等。为了保证数据实时效,每次浏览都需+1,并发量高时如果每次都请求数据库操作无疑是种挑战和压力。Redis提供的incr命令来实现计数器功能,内存操作,性能非常好,非常适用于这些计数场景。
  • 分布式会话,集群模式下,在应用不多的情况下一般使用容器自带的session复制功能就能满足,当应用增多相对复杂的系统中,一般都会搭建以Redis等内存数据库为中心的session服务,session不再由容器管理,而是由session服务及内存数据库管理。
  • 分布式锁,很多互联网公司中都使用了分布式技术,分布式技术带来的技术挑战是对同一个资源的并发访问,如全局ID、减库存、秒杀等场景,并发量不大的场景可以使用数据库的悲观锁、乐观锁来实现,但在并发量高的场合中,利用数据库锁来控制资源的并发访问是不太理想的,大大影响了数据库的性能。
  • 社交网络,点赞、踩、关注/被关注、共同好友等是社交网站的基本功能,社交网站的访问量通常来说比较大,而且传统的关系数据库类型不适合存储这种类型的数据,Redis提供的哈希、集合等数据结构能很方便的的实现这些功能。
  • 最新列表,Redis列表结构,LPUSH可以在列表头部插入一个内容ID作为关键字,LTRIM可用来限制列表的数量,这样列表永远为N个ID,无需查询最新的列表,直接根据ID去到对应的内容页即可。
  • 消息系统,消息队列是大型网站必用中间件,如ActiveMQ、RabbitMQ、Kafka等流行的消息队列中间件,主要用于业务解耦、流量削峰及异步处理实时性低的业务。Redis提供了发布/订阅及阻塞队列功能,能实现一个简单的消息队列系统。

这些场景都是当下大型电商网站和社交网站需要的。所以Reids火也不奇怪,其它的NoSQL数据库的应用场景都比较窄,类似文档存储和图片存储等,都在特定应用场景下使用

五种常用数据对象及应用场景

Redis是高性能键值对数据库,支持的键值数据类型:字符串类型 、哈希类型、列表类型 、集合类型、有序集合类型 , 这些类型的操作方式和结构需要详细了解。

字符串类型

字符串的操作命令有很多,常用的操作命令有如下几种,涉及到:设置及获取值,获取并修改值,自增值,自减值,追加字符串等操作:

  • set key value:设置指定 key 的值。
  • get key:获取指定 key 的值。
  • getset key value:将给定 key 的值设为 value ,并返回 key 的旧值(old value)。
  • incr key:将 key 中储存的数字值增一。
  • incr key increment:将 key 所储存的值加上给定的增量值(increment)
  • decr key :将 key 中储存的数字值减一。
  • decr key decrement:key 所储存的值减去给定的减量值(decrement) 。
  • append key value:如果 key 已经存在并且是一个字符串, APPEND 命令将指定的 value 追加到该 key 原来值(value)的末尾。
  • del key:删除该key。

需要注意的是,操作状态返回的0和1要和返回结果做区分,并且set操作其实是有则更新,无则新增。常用场景如下:

  • 场景一:利用数值操作特性Incr指令为分布式数据库主键自增,例如数据库做分库分表后仍然希望所有数据能保持主键单调递增。
  • 场景二:利用key的生命周期做投票系统,在投票的场景中,我们经常会有一天可投几次票这样的限制,那么这个就需要一个过期时间,例如每天最多可以投5张票,可以把用户id作为key,当value大于5时不允许继续投,24小时后key销毁,重新设置。
  • 场景三:利用数值操作特性Incr指令刷新热点数据,例如分别设置微博大V的粉丝数、点赞数为key,然后刷新数量。

以上就是字符串类型的介绍。String的场景利用了String的Incr指令、过期Key的特性

哈希类型

Hash是一个String类型的Field和Value的映射表,Hash特别适合用于存储对象。Redis 中每个 hash 可以存储 2^32 - 1 个键值对(40多亿),常用操作如下

  • hset key field value:将哈希表 key 中的字段 field 的值设为 value 。
  • hget key field:获取存储在哈希表中指定字段的值。
  • hmset key field1 value1 field2 value2 ...:同时将多个 field-value (域-值)对设置到哈希表 key 中。时间复杂度为O(n)
  • hmget key field1 field2...:获取所有给定字段的值。时间复杂度为O(n)
  • hgetall key:获取在哈希表中指定 key 的所有字段和值 。时间复杂度为O(n)
  • hdel key field1 field2... :删除一个或多个哈希表字段。返回值为0则表示删除的属性不存在
  • del key:删除该key,也就是删除该哈希。

还有一些自增及判断的命令:

  • hincrby key field increment:为哈希表 key 中的指定字段的整数值加上增量 increment 。
  • hlen key:获取哈希表中字段的数量
  • hvals key:同时将多个 field-value (域-值)对设置到哈希表 key 中。时间复杂度为O(n)
  • hexists key field:查看哈希表 key 中,指定的字段是否存在。时间复杂度为O(n)

由于Hash的这些特性,常有如下的应用场景:

  • 场景一:利用hash的对象存储特性设置用户的购物车,一个人的购物车可以看做一个对象,而商品可以当做field,数量可以当做value,然后对购物车进行各种操作。
  • 场景二 :利用hash作为商品秒杀计数对象完成商品秒杀系统,一个商品秒杀系统可以看作一个对象,而秒杀的商品可以当作field,数量可以当作value,设置value为该商品的余量,然后使用hincrby来进行秒杀业务,降低数量。

和String类型相比,Hash更适合数据的呈现,而不适合数据的更新,具体为什么,可以在下一小节其底层结构上一窥究竟。Hash的场景利用了Hash的结构特性存储对象信息

列表类型

List的核心特点是顺序性,其底层主要实现为一个双向链表,为什么说主要,因为本节提到的各种数据对象底层都有不止一种实现方式。简单常用的一些相关相关命令:

  • lpush key value1 [value2]:将一个或多个值插入到列表头部,从左侧添加,最后添加的在最左边
  • lrange key start stop:获取列表指定范围内的元素, 假如共有6个元素,[0,-2]表示从列表头到倒数第二个,[0,-1]和[0,5]效果一样。
  • rpush key value1 [value2]:将一个或多个值插入到列表尾部,从右侧添加,最后添加的在最右边
  • lpop key:移出并获取列表的第一个元素,弹出列表头
  • rpop key:移除列表的最后一个元素,返回值为移除的元素。
  • llen key:获取列表长度。
  • lpushx key value:将一个值插入到已存在的列表头部,仅队列存在时有效。当队列不存在时,不进行任何操作。
  • rpushx key value:将一个值插入到已存在的列表尾部,仅队列存在时有效。当队列不存在时,不进行任何操作。

当然还有些相对复杂的命令:

  • lrem key count value:移除count个为value的元素,如果count大于0,则从左向右数,如果count小于0,从右向左数,如果count等于0,删除全部为value的元素。
  • lset key index value:通过索引设置列表元素的值,列表头的索引是0.
  • linsert key before|after pivot value:在列表的元素前或者后插入元素

列表有如下的一些应用场景:

  • 场景一:利用blpop特性实现任务队列,轮询从任务队列里取数据【可以同时从多个队列获取】,如果取到数据就返回,如果没有数据就等待设置时间持续获取,直到数据过期
  • 场景二:利用list顺序特性实现朋友圈点赞,因为点赞等信息都是有顺序性的,而且修改的效率高,适合使用list来操作,点赞用rpush,取消点赞用lrem
  • 场景三:利用list顺序特性进行分布式日志顺序性展示,使用list顺序性实现多路数据汇总展示,利用其栈的特性实现最新的消息最先展示,即组合使用rpush和rpop

在List的场景下,主要利用了List的顺序性、队列和栈的双重特性

集合类型

Set 是String 类型的无序集合集合成员是唯一的,这就意味着集合中不能出现重复的数据。集合中最大的成员数为 2^32 - 1 (40多亿个成员),常用操作如下:

  • sadd key member1 [member2]:向集合添加一个或多个成员
  • srem key member1 [member2]:移除集合中一个或多个成员
  • smembers key:返回集合中的所有成员
  • sismember key member:判断 member 元素是否是集合 key 的成员
  • scard key:获取集合的成员数
  • srandmember key [count]:返回集合中一个或多个随机数
  • spop key:移除并返回集合中的一个随机元素

set集合之间的操作通过如下命令实现:

  • sdiff key1 [key2]:返回给定所有集合的差集,两个集合的第一个不同数字。
  • sdiffstore destination key1 [key2]:返回给定所有集合的差集并存储在 destination 中
  • sinter key1 [key2]:返回给定所有集合的交集
  • sinterstore destination key1 [key2]:返回给定所有集合的交集并存储在 destination 中
  • sunion key1 [key2]:返回所有给定集合的并集
  • sunionstore destination key1 [key2]:返回所有给定集合的并集存储在 destination 集合中
  • smove source destination member:将 member 元素从 source 集合移动到 destination 集合

set的主要特性是不重复性,我们看基于这样的特性,常用的有哪些场景呢?

  • 场景一:利用set特性随机获取不重复数据实现简单推荐系统,系统汇集好读者的所有爱好标签后,使用srandmember 指令实现随机推送三个用户喜欢的标签数据
  • 场景二:利用set交并差实现推荐系统池,例如获取中老年群体用户中共同的爱好,可以用sinter来实现,获取我在中国地质大学中的一度人脉、二度人脉等可以使用sunion来实现,获取我喜欢但是我妈不喜欢的电视节目,可以用sdiff来实现。
  • 场景三:利用set不重复特征获取所有业务系统权限,我们想要获取所有用户的所有权限,取出其中补充五的业务系统权限该怎么做?我们可以设置用户为一个set集合,他的权限为value,把所有权限放到set
  • 场景四:利用set不重复特征获取UV和IP数据,UV即网站被不同用户访问的次数,相同用户切换IP地址,UV不变,使用set存储用户cookie信息,统计UV量。IP即网站被不同IP的访问次数,相同IP访问,不同用户访问,IP不变。使用set存储IP信息,统计IP量。
  • 场景五:利用set不重复特征实现黑白名单,可以在黑名单中添加IP或设备或用户,通过不重复特性,设置唯一的黑名单

Set的场景主要利用了set的不重复特性、随机取值特性和并交差集的特性

有序集合类型

Zset和集合一样也是string类型元素的集合,且不允许重复的成员。不同的是每个元素都会关联一个double类型的分数。Redis正是通过分数来为集合中的成员进行从小到大的排序。有序集合的成员是唯一的,但分数(score)却可以重复,常用操作如下:

  • zadd key score1 member1 [score2 member2]:向有序集合添加一个或多个成员,或者更新已存在成员的分数
  • zscore key member:返回有序集中,成员的分数值
  • zcard key:获取有序集合的成员数
  • zrem key member [member ...]:移除有序集合中的一个或多个成员
  • zrange key start stop [withscores]:通过索引区间返回有序集合成指定区间内的成员,如果需要同时返回scores ,带上后边那段,默认分数从低到高
  • zrevrange key start stop [withscores]:返回索引区间集中指定区间内的成员,分数从高到底
  • zrangebyscore key min max [withscores] [limit]:通过分数区间返回有序集合指定区间内的成员,默认分数从低到高
  • zremrangebyrank key start stop:移除有序集合中给定的排名区间的所有成员
  • zremrangebyscore key min max:移除有序集合中给定的分数区间的所有成员
  • zincrby key increment member:有序集合中对指定成员的分数加上增量 increment
  • zcount key min max:计算在有序集合中指定区间分数的成员数

Zset最经典的应用场景就是进行排行榜设置了。当然除此之外还有些带权重的操作都类似:

  • 场景一:利用set不重复排序特征实现计数器组合排序排行榜功能,为所有参与排名的资源进行排序
  • 场景二:利用set不重复排序特征实现基于时效性任务提醒,队列中全部为vip,按照会员时间长短排序,短时间到期后提醒下一个快到期的任务。
  • 场景三:利用set不重复排序特征实现带权重任务队列,仅是任务队列可以通过队列,但是如果队列中的任务有优先级,则需要使用带权重的

ZSet的场景主要利用了set的不重复特性和分数排序特性

数据对象的底层实现方式

上一小节我们提到的五种数据类型其实就是Redis的数据对象,我们先来看看数据对象的类型:Redis的key都是string类型的,以上各类型说的其实都是value的类型,以下是对象的几个优点:

  • 通过这五种不同类型的对象,Redis可以在执行命令之前,根据对象的类型来判断一个对象是否可以执行给定的命令适配场景
  • 使用对象的另一个好处是,我们可以针对不同的使用场景,为对象设置多种不同的数据结构实现,从而优化对象在不同场景下的使用效率,提升效率
  • Redis的对象系统还实现了基于引用计数技术的内存回收机制,当程序不再使用某个对象的时候,这个对象所占用的内存就会被自动释放,内存回收
  • Redis还通过引用计数技术实现了对象共享机制,这一机制可以在适当的条件下,通过让多个数据库键共享同一个对象来节约内存,节约内存
  • Redis的对象带有访问时间记录信息,该信息可以用于计算数据库键的空转时长,在服务器启用了maxmemory功能的情况下,空转时长较大的那些键可能会优先被服务器删除内存回收

每次当我们在Redis的数据库中新创建一个键值对时,我们至少会创建两个对象,一个对象用作键值对的键(键对象),另一个对象用作键值对的值(值对象)。Redis中的每个对象都由一个redisObject结构表示,该结构中和保存数据有关的三个属性分别是type属性、encoding属性和ptr属性

redisObject结构:
    typedef struct redisObject{
    //类型
    unsigned type:4;
    //编码
    unsigned encoding:4;
    //指向底层实现数据结构的指针
    void *ptr;
    ….. 
}
  • 对象的type属性记录了对象的类型,REDIS_STRING、REDIS_HASH、REDIS_LIST、REDIS_SET、REDIS_ZSET,对于Redis数据库保存的键值对来说,键总是一个字符串对象,而值则可以是字符串对象、列表对象、哈希对象、集合对象或者有序集合对象的其中一种
  • 对象的encoding属性记录了对象所使用的编码,也即是说这个对象使用了什么数据结构作为对象的底层实现,接下来会详细介绍下使用的数据编码
  • 对象的ptr指针指向对象的底层实现数据结构,而这些数据结构由对象的encoding属性决定

也就是一个对象包含自身的数据结构属性,实际使用的编码类型以及数据对象对实际数据编码的指针

数据对象和数据结构

在Redis中会涉及很多数据结构,比如SDS,双向链表、字典、压缩列表、整数集合、跳跃表等。数据结构有如下几种:

结构常量 结构对应的底层数据结构
REDIS_ENCODING_INT long类型的整数
REDIS_ENCODING_EMBSTR embstr编码的简单动态字符串SDS
REDIS_ENCODING_RAW 简单动态字符串SDS
REDIS_ENCODING_HT 字典
REDIS_ENCODING_LINKEDLIST 双向链表
REDIS_ENCODING_ZIPLIST 压缩列表
REDIS_ENCODING_INTSET 整数集合
REDIS_ENCODING_SKIPLIST 跳跃表

每种类型的对象都至少使用了两种不同的编码,在内容长短发生变化的时候数据对象会自动切换适合的数据编码,且切换后不可逆

数据对象 数据编码 备注
String int long类型的整数
embstr sds实现 <=32 字节
raw sds实现 > 32字节
List ziplist 压缩列表实现
linkedlist 双端链表实现
Set intset 整数集合实现
hashtable 字典实现
Hash ziplist 压缩列表实现
hashtable 字典实现
Zset ziplist 压缩列表实现
skiplist 跳跃表+字典实现
相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
5月前
|
存储 缓存 NoSQL
Redis的神奇之处:为什么它如此快速?【redis第三部分】
Redis的神奇之处:为什么它如此快速?【redis第三部分】
53 0
|
存储 消息中间件 NoSQL
redis入门到精通系列(一):入门redis看这一篇就够了
如果你是计算机专业学生 ,那么一定使用过关系型数据库mysql。在请求量小的情况下,使用mysql不会有任何问题,但是一旦同时有成千上万个请求同时来访问系统时,就会出现卡顿甚至系统崩溃的情况。最典型的例子就是早期的12306购票网站,一旦到了购票高峰期,12306肯定崩溃。造成这个原因的罪魁祸首就是关系型数据库。
4397 0
redis入门到精通系列(一):入门redis看这一篇就够了
|
5月前
|
存储 NoSQL Linux
【Redis入门】 —— 关于Redis的一点儿知识
【Redis入门】 —— 关于Redis的一点儿知识
|
存储 缓存 NoSQL
前端了解这些 Redis 操作就够了
前端了解这些 Redis 操作就够了
1717 0
|
存储 缓存 监控
Redis看这一篇就够了(四)
Redis看这一篇就够了(四)
55 0
Redis看这一篇就够了(四)
|
存储 NoSQL 安全
Redis看这一篇就够了(二)
Redis看这一篇就够了(二)
97 0
Redis看这一篇就够了(二)
|
存储 缓存 监控
Redis看这一篇就够了(六)
Redis看这一篇就够了(六)
103 0
|
存储 消息中间件 缓存
Redis看这一篇就够了(五)
Redis看这一篇就够了(五)
149 0
|
缓存 NoSQL API
Redis看这一篇就够了(三)
Redis看这一篇就够了(三)
58 0
|
存储 缓存 监控
一文搞懂redis(下)
一文搞懂redis(下)