这是一门 Redis 从零基础开始逐步成为 Redis 专家的进阶教程,主要包括认识 Redis、基础 API 的理解和使用、使用 Redis 客户端、Redis 高级功能、Redis 持久化和开发运维常用问题探讨、Redis 复制的原理和优化策略、Redis 分布式解决方案、集群和故障转移等核心内容,系统而全面地剖析 Redis 的应用。
经过此达人课的学习,你将获得 Redis 至少三年的开发经验。如果你是程序员,在大数据时代下,我们需要有一个大数据的概念,一个好的程序员必须要理解大和小的概念,使用 Redis 的项目均具有庞大的数据量和访问量,这就要求我们不仅需要有良好的代码意识,还需要在未来大数据时代中对项目有更好的扩展能力。
陈宠,现任某汽车IT公司资深 Java 高级工程师、IT写作者。精通多门编程语言,主攻 Java 后台开发,具有 8 年 Java 开发经验和数据库开发经验,特别有着三年的 Redis 研发经验,对大型高并发缓存架构有深刻的认识。做过大型电商系统,大型企业售后系统等亿级流量的项目,经历了多个从小型项目发展到大型项目的全过程,同时也致力于在 IT 领域帮助更多初学的程序员,给他们带来更多的启发和认识。
如果你从来没使用过 Redis 数据库,那你肯定会问,为什么我们要学 Redis数据库,我只使用 MySQL 或 Oracle 就够了。其实 Redis 虽叫数据库,可又不是传统意义上的关系型数据库,Redis 是一个高性能的 Key-Value 数据库。
首先我们先来讲一下 Redis 的历史。Redis 其实是作者 Salvatore Sanfilippo 为了解决实际问题而创造出来的。当时作者 Salvatore 有这么一个需求,就是多个网站不断向服务器发送页面,而服务器需要为每个网站保存一定数量的最新页面记录,同时通过网页将数据实时给用户看到。但是无论 Salvatore 如何优化,都很难在关系数据库里让小虚拟机处理大负荷的负载。最终他打算自己写一个内存数据库,能对列表的两端执行常数时间复杂度的弹出和推入操作,并加上子进程的持久化操作,于是 Redis 就诞生了。
到了今天,Redis 已经进入了成熟期。数以千计的开发者都在开发和使用这个数据库,Redis 拥有非常完善的文档。我记得第一次使用 Redis,是为了在保存有数十百万用户的关系数据库里对某个条件进行查询。大家知道,要想在几百万用户中找到某条数据,是很难通过关系数据库在十几秒查询到的。于是我选择了 Redis,在不断优化后每次操作可以控制在 1 秒钟甚至更短,带给我相当大的震撼。
本教程不但教给你一些基本的使用,同时也会根据我多年总结的技巧解决日常生产环境上优化和排错的问题。特别是后期的数据库优化和集群的讲解,希望对各位进行 Redis 开发有一定帮助。
在 Redis 之前,很多互联网公司会使用 MySQL + Memcached 架构,这个架构虽然适合于海量数据存储,但随着业务的增加,会出现很多问题,例如,MySQL 数据库经常拆表,导致 Memcached 也不断扩容;同步问题;命中率低,导致直接穿透 Memcached 进入 DB 查询,DB 资源池是有限的,进而宕机。这些问题都会导致Memcached其实并不好用。
Redis 就在这种时代背景中产生,你会发现 Memcached 遇到的问题都被 Redis 给解决了。如果你用过 Memcached,你就会感受到 Redis 绝对不是简单的 Key-Value 数据,还有 List、Set、哈希等各种数据类型的存储,同时支持冷热备份和主从复制,不但解决了数据库的容错,还能轻易地将数据分布到多个 Redis 实例中。
那么 Redis 有哪些具体特性呢?大致可分为如下八大特性。
速度极快。官方给出的数据是 10 万次 ops 的读写,这主要归功于这些数据都存在于内存中。由于 Redis 是开源的,当你打开源代码,就会发现 Redis 都是用 C 语言写的,C 语言是最接近计算机语言的代码,而且只有区区 5 万行,保证了 Redis 的速度。同时一个 Redis 只是一个单线程,其真正的原因还是因为单线程在内存中是效率最高的。
持久化。Redis 的持久化可以保证将内存中的数据每隔一段时间就保存于磁盘中,重启的时候会再次加载到内存。持久化方式是 RDB 和 AOF。
支持多种数据结构。分别支持哈希、集合、BitMaps,还有位图(多用于活跃用户数等统计)、HyperLogLog(超小内存唯一值计数,由于只有 12K,是有一定误差范围的)、GEO(地理信息定位)。
支持多种编程语言。支持 Java、PHP、Python、Ruby、Lua、Node.js。
功能丰富。如发布订阅、Lua 脚本、事务、Pipeline(管道,即当指令到达一定数量后,客户端才会执行)。
简单。不依赖外部库、单线程、只有 23000 行 Code。
主从复制。主节点的数据做副本,这是做高可用的基石。
高可用和分布式。Redis-Sentinel(v2.8)支持高可用,Redis-Cluster(v3.0)支持分布式。
Redis 最大的作用是增加你原来的访问性能问题,试想如果项目已经搭建好,这个项目一般是不太可能更换的。但是 Redis 独特的存在是只需要增加一层,把常用的数据存放在 Redis 即可。你在开发环境中使用 Redis 功能,但却不需要转到 Redis。
无论是什么架构,你都可以将 Redis 融入项目中来,这可以解决很多关系数据库无法解决的问题。比如,现有数据库处理缓慢的任务,或者在原有的基础上开发新的功能,都可以使用 Redis。接下来,我们一起看看 Redis 的典型使用场景。
这是 Redis 使用最多的场景。Redis 能够替代 Memcached,让你的缓存从只能存储数据变得能够更新数据,因此你不再需要每次都重新生成数据。
毫无疑问,Redis 缓存使用的方式与 Memcached 相同。网络中总是能够看到这个技术更新换代,Redis 的原生命令,尽管简单却功能强大,把它们加以组合,能完成的功能是无法想象的。当然,你可以专门编写代码来完成所有这些操作,但 Redis 实现起来显然更为轻松。
如转发数、评论数,有了原子递增(Atomic Increment),你可以放心的加上各种计数,用 GETSET 重置,或者是让它们过期。目前新浪是号称史上最大的 Redis 集群。
比如,你想计算出最近用户在页面间停顿不超过 30 秒的页面浏览量,当计数达到比如 10 时,就可以显示提示。再比如,如果想知道什么时候封锁一个 IP 地址,INCRBY 命令让这些变得很容易,通过原子递增保持计数;GETSET 用来重置计数器;过期属性用来确认一个关键字什么时候应该删除。
虽然 Kafka 更强,但是简单的可以使用 Redis。运行稳定并且快速,支持模式匹配,能够实时订阅与取消频道。
Redis 还有阻塞队列的命令,能够让一个程序在执行时被另一个程序添加到队列。你也可以做些更有趣的事情,比如一个旋转更新的 RSS Feed 队列。
排行榜实际就是一种有序集合。对于 Redis 来说,如果你要在几百万个用户中找到排名,其他数据库查询是非常慢的,因为每过几分钟,就会有几百万个不同的数据产生变化,但是 Redis 却可以轻松解决。
排行榜(Leader Board)按照得分进行排序。ZADD 命令可以直接实现这个功能,而 ZREVRANGE 命令可以用来按照得分获取前 100 名的用户,ZRANK 可以用来获取用户排名,非常直接而且操作容易。
Redis 可以非常好地与社交网络相结合,如新浪微博、Twiter 等,比如 QQ 和用户交互的时候,用户和状态消息将会聚焦很多有用的信息,很多交互如实时聊天就是通过 Redis 来实现的。
Reddit 的排行榜,得分会随着时间变化。LPUSH 和 LTRIM 命令结合运用,把文章添加到一个列表中。一项后台任务用来获取列表,并重新计算列表的排序,ZADD 命令用来按照新的顺序填充生成列表。列表可以实现非常快速的检索,即使是负载很重的站点。
通过 Unix 时间作为关键字,用来保持列表能够按时间排序。对 current_time 和 time_to_live 进行检索,完成查找过期项目的艰巨任务。另一项后台任务使用 ZRANGE...WITHSCORES 进行查询,删除过期的条目。
使用位图来做布隆过滤器,例如实现垃圾邮件过滤系统的开发变得非常容易。
综上所述, Redis 的应用是非常广泛的,而且在实际使用中是非常有价值的。你可以让网站向 100 万用户推荐新闻、可以实时显示最新的项目列表、在游戏中实时获得排名、获得全球排名,等等。Redis 的出现,解决了传统关系数据库的短板,让开发变得更加简单和高效,大大提高了开发效率,也在用户体验上获得更加实时的体验。随着 Redis 的使用越来越广泛,将会有更多的开发者加入 Redis 的使用和开发上来。
最后我们回顾下本文所讲述的内容。
首先,介绍了 Redis 主要是用于缓存系统的,不同于一般关系数据库。
其次,我们介绍了 Redis 的八大特性。通过这八大特性,我们可以把经常变化的数据放在 Redis 数据库中,并设置过期时间,到达时间 Redis 就会自动删除;还可以缓解服务器压力,如我们日常发微博,先会保存在 Redis 数据库中,然后等数据库压力比较小的时候保存进关系数据库中。
最后,我们介绍了 Redis 用在哪些场景下。
相信通过这些介绍,你应该对 Redis 有个比较详细的认识。接下去我们将实际使用 Redis。
拓展阅读:Redis 实战场景详解
前面我们大致了解了 Redis 是什么,接下来我们就来使用 Redis。
如何使用 Redis?首先我们需要根据 Redis 提供的 7 种数据类型来了解它,分别是字符串 String 、哈希 Hash 、列表 List 、集合 Set 、有序集合 Sorted Set 、发布订阅 Pub/Sub 、事务 Transactions。
我们先来讲一下 Redis 的内部实现和运行机制。在使用 Redis 时,命令多使用就会很容易记住,这归功于 Redis 的命令简单。但是内部实现和它的单线程,你必须先了解原理,再了解 7 种数据类型,这样才能在实际开发中游刃有余。
接下来,我们简单了解下 Redis 的内部实现。Redis 内部会封装一个 redisObject 实例。由这个 redisObject 来表示所有的 key 和 value 。redisObject 所包含的字段中,最主要的是 type 和 encoding。
type 代表一个 value 对象具体是何种数据类型,包括 String 、Hash 、List 、Set 和 Sorted set 等数据类型。
encoding 则表示不同数据类型在 Redis 内部的存储方式,包括 Raw 、Int 、Ziplist 、LinkedList 、HashMap 和 Intset 存储方式。
上面说得比较抽象,为了帮助大家理解,我举个例子,比如 type 为 String 代表的是 value 存储了一个字符串,那么对应的 encoding 可以是 Int 或者 Raw 。如果是 Int 则代表实际 Redis 内部是按数值类型存储的,比如你要存储 “1234” 、“2018” 等字符串。
还有一个特别的内部字段,vm 字段。这个字段默认是关闭的,只有打开了 Redis 的虚拟内存功能,才能真正分配内存。有同学要问了,这个字段有什么用呢?因为 Redis 是 Key/Value 存储,是非常浪费内存的,这些内存成本主要是为了给 Redis 上面的 7 种数据类型提供统一管理接口。
我们再来看下为什么 Redis 中单线程快。很多程序员应该深有体会,其实其他很多语言单线程是非常慢的,但是为什么 Redis 的单线程快呢?
我觉得最大的原因是纯内存存储。正因为这个是主要原因,所以后面两个原因显得有点不太重要,即非阻塞 IO 和避免线程切换和竞态消耗。
你要清楚,首先 Redis 一次只运行一条命令。其次我们应该减少长命令,哪些是长命令呢?如 KEYS 、 FLUSHALL 、FLUSHDB 、Slow Lua Script 、MULTI/EXEC 、Operate Big Value( Collection )。最后说明一点,其实 Redis 不只是单线程,如果你去读源码,你就会发现有些指令绝不是单线程能够做的。如 Fysnc File Descriptor 、Close File Descriptor 等。
Redis 的 key 没什么好说,值得一提的就是 value 的五种数据类型。分别是字符串类型、数字、二进制、和 JSON 类型的数据。
那我们在实际生产环境哪些场景中使用它们呢?如缓存、计数器( 每次加 1 的计数 )、分布式锁等场景都能看到。
接着我将列出与该数据类型相关的命令及使用说明。
GET 、SET 和 DEL 。这是 Redis 最简单的命令,如果你要得到一个 value 的值,只需要 GET key ,就 ok 了。如果你要设置某个 key 的值,那就 SET key value ,搞定。
INCR 、DECR 、INCRBY 和 DECRBY 。
INCR key :就是 key 自增 1,不存在,自增后 get(key)=1 ;
DECR key :就是 key 自减 1,不存在,自减后返回 -1 ;
INCRBY key k :自增 k ,不存在,则返回 k ;
DECRBY key k :自减 k 。
实际使用:如果你想访问某个主页的访问量,那可以用 INCR id:pageview 。
我们实际开发中,常常会使用下面的伪代码。
public VideoInfo get(long id){ String redisKey = redisPrefix + id; VideoInfo videoInfo = redis.get(redisKey); if(videoInfo == null){ videoInfo = mysql.get(id); if(videoInfo != null){ // 序列化 redis.set(redisKey, serialize(videoInfo)); } } return videoInfo;}
SET 、SETNX 和 SET xx 。
SET key value :不管 key 是否存在,都设置;
SETNX key value :key 不存在,才设置( 相当于 add );
SET key value xx :key 存在,才设置( 相当于 update )。
实际操作,见如下代码。
exists php --> 0set php good -->OKsetnx php bad -->0set php best xx -->okexists lua --> 0set lua hehe xx -->(nil)
MGET 、MSET 。
MGET key1 key2 key3 …:批量获取 key,原子操作;
MSET key1 val2 key2 val2 key3 val3:批量设置 key-value。
实际开发的过程中,我们通常使用 MGET,因为 MGET 只有 1 次网络时间和 n 次命令时间。但是如果你使用 GET 的话,就是 n 次网络时间和 n 次命令时间。
使用 MGET 效率更高。
GETSET 、APPEND 和 STRLEN。
GETSET key newvalue:set key newvalue 并返回旧的 value,旧的 value 会被删除;
APPEND key value :将 value 追加到旧的 value 上;
STRLEN key :返回字符串的长度( 注意中文)。
INCRBYFLOAT 、GETRANGE 和 SETRANGE。
INCRBYFLOAT key 3.5:在 key 上追加对应的值 3.5;
GETRANGE key start end :获取字符串指定下标所有的值;
SETRANGE key index value :设置指定下标所有对应的值。
说到 Hash,就要说到为什么我们要使用 Hash。我们在使用字符串的数据类型的时候,如果我们存储的是个对象,比如某个图书馆的会员,里面存储着会员的姓名、年龄、身份证信息、地址、借阅书籍、借阅时间……一系列的属性。
如果我们用 String 来存储的话,那我们就需要每次都序列化这个字符串,每次只要一修改某个属性,我们就要把一串属性都覆盖一遍。这样是不是非常麻烦?
这个时候,哈希就应运而生了。Hash 相当于 value 是一个 Map ,里面的所有属性我们都可以单独新增、修改或者删除,而不需要像字符串那样全部覆盖操作。
常用的命令有 HGET 、HSET 和 HGETALL。
List 是一种简单的字符串的集合,是有顺序的。在实际生产环境中,我们时常会使用它。比如当我们需要获取某个数据的列表(例如粉丝列表)。
由于 Redis 的 List 是链表结构,我们可以非常轻松地实现消息排行等功能,还能用于消息队列等功。
常用的命令有 LPUSH 、RPUSH 、LPOP 、RPOP 和 LRANGE。
Set 和 List 最大的不同是无序,Set 是没有顺序的。集合成员是唯一的,这就意味着集合中不能出现重复的数据。
常用命令有 SADD 、SCARD 、SNENVERS 和 SPOP。
Sorted Set 和 Set 最大的不同是前者是自动排序的,而后者是无序的。如果你需要一个有序的,但是不重复的数据结构,就可以使用 Sorted Set 。
常用的命令有 ZADD 、ZRANGE 、ZREM 和 ZCARD。
即发布(Publish)与订阅(Subscribe)。在 Redis 中,你可以设定对某一个 key 值进行消息发布及消息订阅,当 key 的值进行了消息发布后,所有订阅它的客户端都会收到相应的消息,这类似于 QQ 和微信。
常用的命令有 PSUBSCRIBE 、PUBSUB 、PUBLISH 和 SUBSCRIBE。
我们一般认为 NoSQL 数据库都没有事务,恐怕要让你失望了。Redis 就支持事务,但并不是我们一般意义上的事务,如果你执行 exec 命令,途中断电或者服务器挂掉了,我们还是会发现 Redis 里一部分插入了,一部分未插入。
不过 Redis 提供了 WATCH 命令,我们可以对某个 key 来 watch 一下,然后再执行 Transactions 。如果这个被 Watch 的值进行了修改,那么这个 Transactions 会发现并拒绝执行。
redis 127.0.0.1:6381> MULTIOKredis 127.0.0.1:6381> SET book-name "JAVA Programming Mastering Series"QUEUEDredis 127.0.0.1:6381> GET book-nameQUEUEDredis 127.0.0.1:6381> SADD tag "java" "Programming" "Mastering Series"QUEUEDredis 127.0.0.1:6381> SMEMBERS tagQUEUEDredis 127.0.0.1:6381> EXEC1) OK2) "JAVA Programming Mastering Series"3) (integer) 34) 1) "java" 2) "Programming" 3) "Mastering Series"
常用命令有 MULTI 、EXEC 和 DISCARD。
Redis 作为一个数据库,很多开发者还可以单独使用它。事实上,更多时候 Redis 是在数据库和代码中间作为一个中间件使用,如果你发现目前的数据库出现瓶颈,那么就可以通过 Redis 来优化。
拓展阅读:Redis 实战场景详解
之前在学习 Redis 基础的时候,我们都是使用 Redis 的命令行。但是除了命令行,还有主流语言支持的客户端。打开 Redis 的官网,在其中找到 Clients,就能找到你熟悉的语言对应的客户端,如下图所示。
本文我们将主要讲解 redis-py(Python 客户端)、Jedis(Java 客户端),其他语言的客户端也都差不多。当你选择某个语言点进去后,你会发有笑脸和黄色五角星。五角星表示 Redis 推荐的,笑脸表示社区比较活跃。建议选择有笑脸和五角星的客户端。
首先我们必须安装 Redis,注意不是安装 redis-py。打开你的命令行,如果你使用 CentOS 或者 Mac 系统,请直接使用 pip 工具安装。如果没有 pip,请先进行安装。下面我们介绍下如何安装它,已经安装的可以直接忽略。
第一步:下载 pip。进入 https://pypi.python.org/pypi/pip,下载第二项。
第二步:解压安装。
解压下载的文件(Windows 下用解压工具,如RAR,Linux 下终端输入 tar -xf pip-9.0.1.tar.gz,即tar -xf 文件名),进入解压后的文件夹中,调出命令行窗口或者终端。
在 Windows 下输入:
python setup.py install
在 Linux 下输入:
sudo python setup.py install
安装成功后测试下,输入以下命令,输出 pip 版本号即表示安装成功。
pip -v
然后选择执行以下 pip 命令,安装 Redis。
当然你也可以使用源码安装,如下。
git clone https://github.com/andymccurdy/redis-py.git cd redis-py //进入 redis-py目录 python setup.py install //开始安装
要使用 redis-py 客户端,我们需通过代码来连接。如何连接呢?打开你的 Python 编辑器。
redis-py 提供了两个类:StrictRedis 和 Redis,用于实现 Redis 的命令,这里推荐使用 StrictRedis。StrictRedis 实现了绝大部分官方的命令,并且使用原生的语法和命令。比如,SET 命令对应着 StrictRedis.set 方法。下面是个简单的例子。
//代码连接 Redisimport redis // 导入 Redisclient = redis.StrictRedis(host='127.0.0.1',port=6379) // 通过 redis.StrictRedis 连接 Rediskey = "welcome"setResult = client.set(key, "redis-py") // SET 命令print setResult //打印这个值value = client.get(key) //在 Redis 使用 GET 命令获取 key 的值print "key = "+ key +" value="+value //打印
上面我们讲了如何连接 redis-py 客户端,下面我们使用各种数据类型来设置和获取信息,后面都有注释。
client.set("redis","helloworld") // Redis 返回 Trueclient.get("redis") // redis返回 helloworldclient.incr("counter") //自增+1
client.hset("hashvalue","a1","b1") // Hash 设置 hashvalue 的值 1client.hset("hashvalue","a2","b2")// Hash 设置 hashvalue 的值 2client.hgetall("hashvalue") // Redis 返回 {a1:b1,a2:b2}
client.rpush("listvalue","a") // List 设置是 rpushclient.rpush("listvalue","b")client.rpush("listvalue","c")client.lrange("listvalue",0,-1) // Redis 返回 ['a','b','c']
client.sadd("setvalue","a") // Set 设置是 saddclient.sadd("setvalue","b")client.sadd("setvalue","c")client.smembers("setvalue") // Redis 返回 ['a','b','c']
client.zadd("zsetvalue","1","book1") // ZSet 设置是 zaddclient.zadd("zsetvalue","2","book2")client.zadd("zsetvalue","3","book3")client.zrange("zsetvalue",0,-1,withscores=True) // withscores 表示含索引值,Redis 返回[('book3',3.0),('book2',2.0),('book1',1.0)]
怎么样,这些方法简单吗?我们再来讲一下如何使用 Java 客户端。
方式一,访问以下网址,获取 Jar 包:
http://files.cnblogs.com/liuling/jedis-2.1.0.jar.zip
相信很多刚学 Java 的朋友就是这么找包的,这里不推荐,推荐大家使用 Maven。关于如何使用 Maven,大家可以找度娘,实在搞不定也可以咨询我。
当然如果你需要使用 Redis 连接池,那你还需要一个 Jar 包(还是不推荐,建议用 Maven),点击这里,获取下载地址。
方式二,使用 Maven 管理,只有在 Maven 里增加了 Jedis 客户端的配置,才能执行下面的代码,否则报错。
redis.clients jedis 2.9.0
使用 Jedis 也是非常简单,只需要 new 一个 Jedis 对象,然后调用 SET 和 GET 就 OK 了,简单吧。
Jedis jedis = new Jedis("127.0.0.1",6379); // IP,端口,连接超时,读写超时 jedis.set("hello","world"); jedis.get("hello"); jedis.close(); // 记得要 close 掉,这是一个良好的习惯。
连接完 Jedis,我们再介绍一下常用的三种数据类型,其他的参照 redis-py,都是大同小异。
jedis.set("hello","world"); //String 是最常用的,用 set 设置jedis.get("hello"); // get 获取jedis.incr("counter"); //自增1
jedis.hset("hello","v1","f1"); //Hash 设置用 hsetjedis.hset("hello","v2","f2"); jedis.hgetAll("hello"); // Redis 返回 {v1=f1,v2=f2}
jedis.rpush("vss","1"); //List 设置用 rpushjedis.rpush("vss","2");jedis.rpush("vss","3"); jedis.lrange("vss",0,-1); //Redis 返回 [1,2,3]
当然,我们不仅要使用 Redis,还会使用 Jedis 的连接池。为什么要使用连接池呢?因为 Jedis 对象不是线程安全的,在多线程下会发生并发问题。为了避免这些问题,Jedis 提供了 JedisPool 连接池,示例代码如下。
GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig(); //JedisPool 是单例的,需要 new 一个 GenericObjectPoolConfig 对象JedisPool jedisPool = new JedisPool(poolConfig,"127.0.0.1",6379); // 建立连接池的连接Jedis jedis = null; // 类似于 new 一个 Jedis 对象try{ jedis = jedisPool.getResource(); // 从连接池获取 Jedis 对象 jedis.set("hello","world"); // 设置}catch(Exception e){ e.printStackTrace();}finally{ if(jedis!=null){ jedis.close();// 注意不是关闭,而是归还到连接池 }}
其他语言都可以参照上面的 Python 和 Java 的客户端,内容方面都差不多,而且 Jedis 主要是掌握其原理和流程,操作其实是非常简单的。如果你有什么疑问,不妨给我留言。
如果你需要了解对应的 API,请点击这里,从中选择你要用的语言对应的客户端。
拓展阅读:Redis 实战场景详解
阅读全文: http://gitbook.cn/gitchat/column/5a55d8e232c7126d8482f5d2