随着互联网业务对性能需求日益强烈,作为Key/Value存储的Redis具有数据类型丰富和性能表现优异的特点。如果能够熟练地驾驭它,不管是把它用做缓存还是存储,对很多大型应用都很多帮助。新浪作为世界上最大的Redis使用者,体会到了Redis为高并发在线业务带来的好处,但同时也遇到了很多挑战,新浪为推动Redis这种NoSQL产品在中国互联网产品技术架构中的使用做出了卓越的贡献。作为国内第一本推进Redis普及的书,此书比较详细地介绍了Redis入门必备的基础知识,同时拥有了一些实践性方面的章节。如果你对Redis感兴趣,推荐你阅读此书,此书会为你开启Redis的大门。
---- 杨海朝,新浪首席数据架构师
拿到这本书,看到豆瓣上8.3分的评价,Redis这个如此广泛运用的开源技术还是很吸引我的,所以读完吧,然后写个读书笔记,引言是新浪工程师对此书的评价,我看完这本书之后,果然收获颇丰,记录如下。
作学习交流之用,非盈利性质
第一章 Redis入门
Redis始于一个意大利创业公司Merzia,创始人Salvatore Sanfilippo以及另外一名主要代码贡献者Pieter Noordhuis目前在VMware,全职开发Redis。Redis代码托管在Github上。
Redis在性能上是单线程模型,而Memcached支持多线程,所以在多核服务器上后者的性能更高一些,然而Redis的性能已经足够优异,在绝大部分场合下其性能都不会成为瓶颈。所以在使用时更应该关心的是二者功能上的区别,如果需要用到高级的数据类型或是持久化等功能,Redis将会是Memcached很好的替代品。
第二章 Redis准备
首先找到linux机器,安装一个redis,依赖命令wget,gcc
wget http://download.redis.io/redis-stable.tar.gz tar xzf redis-stable.tar.gz cd redis-stable make MALLOC=libc make install
redis-server (--port 6397) & //启动redis服务,可以指定端口,后台运行
redis-cli SHUTDOWN //停止Redis,Redis收到SHUTDOWN命令后,会先断开所有客户端连接,然后根据配置执行持久化,最后完成退出,“kill Redis进程PID”也可以正常结束Redis
redis-cli -h 127.0.0.1 -p 6379 客户端可以指定连接服务端的ip和端口
redis-cli PING
如果接收到PONG说明一切正常,客户端请求收到服务端的响应。
Redis默认支持16个数据库,不同的应用应该使用不同的Redis实例存储数据。由于Redis非常轻量级,一个空Redis实例占用的内存只有1MB左右,所以不用担心多个Redis实例会占用很多内存。
第三章 Redis入门
一些基本命令就不介绍了
HSETNX 原子地实现了HEXISTS和HSET两个命令以避免竞态条件
LRANGE numbers 0 -1可以获取列表中的所有元素
列表类型可以存储一个有序的字符串列表,常用的操作是向列表两端添加元素,或者获得列表的某一个片段。列表类型内部使用双向链表实现,列表类型适合用来记录日志,可以保证加入新日志的速度不会受到已有日志数量的影响,但是不适合从中间获取数据,当列表元素非常多时访问中间元素效率并不高,适合场景是新鲜事这些只关心最新的内容。
有序集合类型是使用散列表和跳跃表实现的,增删改查都是Log(N),跳表的设计非常精彩,多个level的索引思想,用空间换时间。
场景1.记录日志时希望只保留最近的100条日志
LPUSH logs $newLog
LTRIM logs 0 99
场景2.网站监控系统
将元素从一个列表转到另一个列表 RPOPLPUSH source destination
当source和destination相同时,RPOPLPUSH命令会不断地将队尾的元素移到队首,借助这个特性我们可以实现一个网站监控系统:使用一个队列存储需要监控的网址,然后监控程序不断地使用RPOPLPUSH命令循环取出一个网址来测试其可用性。这里使用RPOPLPUSH命令的好处在于在程序执行过程中仍然可以不断地向网址列表中加入新网址,而且整个系统容易扩展,允许多个客户端同时处理队列。
场景3.用集合实现倒排索引
tag:Redis:posts->3
tag:MySQL:posts->2,3
tag:Java:posts->1,2,3
场景4.分页实现(按时间,按点击量)
ZRANGEBYSCORE key in min max [WITHSCORES] [LIMIT offset count]
可以用时间+offset作为score,然后就可以根据这个score进行分页展示了
第四章 Redis进阶
事务
MULTI //事务开始
....
EXEC //执行事务
Redis的事务没有关系数据库事务提供的回滚功能,不过由于Redis不支持回滚,也使得Redis在事务上可以保持简洁和快速,另外如果能够很好的规划数据库,保证键名规范的使用,是不会出现如命令与数据类型不匹配这样的运行错误的。
因为事务中每个命令的执行结果都是最后一起返回的,所以无法将前一条命令的结果作为下一条命令的参数,这时候问题出现了:如何原子的实现增1的功能?
watch命令!WATCH命令可以监控一个或多个键,一旦其中一个键被修改或删除,之后的事务就不会执行。监控一直持续到EXEC命令,通过事务实现incr函数,伪代码如下:
def incr($key) WATCH $key $value = GET $key if not $value $value = 0 $value = $value+1 //如果在watch之后有其他线程修改了value,则事务失败 MULTI SET $key, $value result = EXEC //失败的话,需要重新执行 return result[0]
场景1.实现访问频率限制
$isKeyExists = EXISTS rate.limiting:$IP if $isKeyExists is 1 $times = INCR rate.limiting:$IP if $time > 100 print 访问频率超过了限制,请稍后再试 exit else MULTI INCR rate.limiting:$IP EXPIRE $keyName, 60 //这个用事务控制,否则这行如果没有执行,那用户最多只能访问100次博客了 EXEC //这段程序有一个临界问题,如果一个人在前一分钟的最后访问了99次,新的分钟开始访问了99次,虽然密集访问了198次,但是代码判断不出来,推荐的做法是,缩小时间窗口,比如把一分钟压缩成10秒钟。还有一种方式是记录每次访问的时间,然后用列表去存储最近的访问时间,这个方法会占用较多的存储空间,实际使用时还需要开发者自己去权衡。
场景2.实现缓存
如果命中用redis的值,否则重新计算出新值,两小时失效,伪代码如下
$rank = GET cache:rank if not $rank $rank = 计算排名... MULTI SET cache:rank, $rank EXPIRE cache:rank, 7200 EXEC
场景3.优先级队列
当确认邮件和发送通知邮件两种任务同时存在时,应该优先执行前者。伪代码如下:
BRPOP key [key ...] timeout 阻塞弹出
loop $task = BRPOP queue:confirmation.email, queue:notification.email, 0 execute($task[1])
场景4.发布/订阅 模式
SUBSCRIBE channel1 PUBLISH channel1 hi
第五章 Redis实践
列举了PHP、Ruby、Python、Node.js与Redis的一些应用场景,包括用户注册登录功能,自动提示功能(搜索补足提示功能),在线好友功能,IP地址查询功能,有兴趣的朋友可以再细看。
第六章 Redis脚本
Redis在2.6推出了脚本功能,允许开发者使用Lua语言编写脚本传到Redis中执行。使用脚本的好处如下:
- 1.减少网络开销:本来5次网络请求的操作,可以用一个请求完成,原先5次请求的逻辑放在redis服务器上完成。
- 2.原子操作:Redis会将整个脚本作为一个整体执行,中间不会被其他命令插入。
- 3.复用:客户端发送的脚本会永久存储在Redis中,意味着其他客户端可以复用这一脚本而不需要使用代码完成同样的逻辑。
可以看例子:Lua脚本配合Redis的实践
第七章 Redis管理
Redis支持两种方式持久化,一种是RDB方式,一种是AOF方式。根据应用场景和对数据可靠性的要求,可以单独使用其中一种或将二者结合使用。
RDB方式
RDB方式的持久化是通过快照完成的,快照的过程如下
1.Redis使用fork函数复制一份当前进程的副本
2.父进程继续接手并处理客户端发来的命令,而子进程开始将内存中的数据写入硬盘的临时文件。
3.当子进程写入完所有数据后会用该临时文件替换旧的RDB文件,至此一次快照操作完成。
在执行fork的时候操作系统(类UNIX操作系统)会使用写时复制(copy-on-write)策略,即fork函数发生的一刻父子进程共享同一内存数据,当父进程要更改其中某片数据时(如执行一个写命令),操作系统会将该片数据复制一份以保证子进程的数据不受影响,所以新的RDB文件存储的是执行fork一刻的内存数据。
Redis还支持手动发送SAVE或BGSAVE命令让Redis执行快照,前者是父进程进行快照,会阻塞其他请求,后者会通过fork子进程执行快照操作。
AOF方式
开启AOF持久化后每执行一条会更改Redis中的数据的命令,Redis会将命令写入硬盘中的AOF文件。按照一定的策略会重写AOF文件,因为对于变量的两次set,只需要保存后面的set即可,这样可以减小AOF文件的大小。
操作系统缓存的机制,数据并没有真正写入到硬盘,而是进入了系统的硬盘缓存,一般设置appendsync everysec每秒执行一次同步操作。flush缓存到硬盘。
集群,读写分离
在常见的场景中,读的频率大于写,当单机Redis无法应付大量的杜请求时(尤其是较耗资源的请求,比如SORT命令等)可以通过复制功能简历多个从数据库,主数据库只进行写操作,而从数据库负责写操作。
另一个相对耗时的操作是持久化,为了提高性能,可以通过复制功能建立一个(或若干个)从数据库,并在从数据库中启用持久化,同时在主数据库中禁用持久化。当从数据库崩溃时重启后主数据库会自动将数据同步过来,所以无需担心数据丢失。而当主数据库崩溃时,需要在从数据库中使用SLAVEOF NO ONE命令将从数据库提升成主数据库继续服务,并在原来的主数据库启动后使用SLAVEOF命令将其设置成新的主数据库的从数据库,即可将数据同步回来。perfect!
安全
Redis的安全设计是在”Redis运行在可信环境“这个前提下做出的,在生产环境运行时不能允许外界直接连接到Redis服务器上,虽然Redis支持密码,但是由于Redis性能极高,并且输入错误密码后Redis并不会进行主动延迟(考虑到Redis的单线程模型),所以攻击者可以通过穷举法破解Redis的密码(1秒内能够尝试十几万个密码)。
管理工具
一般用redis-cli命令行工具就可以,也有一些网页管理工具,比如phpRedisAdmin,但phpRedisAdmin在获取键列表时使用的是KEYS *命令,当键非常多的时候性能并不高,所以对生产环境下拥有大数据量的数据库来说不适宜使用phpRedisAdmin管理。
附上书封面