redis实战笔记(6)-第6章 使用 Redis构建应用程序组件

本章主要内容
 
1.构建两个前缀匹配自 动补全程序
2.经过构建分布式锁来提升性能
3.经过开发计数信号量来控制并发
4.构建两个不一样用途的任务队列
5.经过消息拉取系统来实现延迟消息传递
6.学习如何进行文件分发
 
本章首先会构建
两个自 动补全函数, 它们能够分别用于在较短或较长的联系人列表中快速找到指定的用户。
接着本章会花一些时间仔细地介绍如何实现两种不一样类型的锁, 这些锁能够用来减小数据冲突、 提高
性能、 防止数据出错并减小没必要要的工做。
以后, 本章将会使用刚刚介绍过的锁来构建一个能够在指定时间执行任务的延迟任务队列, 并在这个延迟任务队列的基础上构建两个不一样的消息系统, 以此来提供点对消息服务以及广播消息服务。 最后, 本章将重用以前在第5章中开发的IP所属地查询程序, 并将它应用在由Redis存储和分发的数百万日 志条目上面。
 
 

6.1 自动补全
 
6.1.1 自 动补全最近联系人
 
由于Redis的 列表会以有序的方式来存储元素, 而且和Redis提供的其余结构相比, 列表占用的内存是最少的, 因此咱们选择使用列表来存储用户的联系人信息
 
构建最近联系人自 动补全列表一般须要对Redis执行3个操做。
第一个操做
就是添加或者更新一个联系人, 让他成为最新的被联系用户, 这个操做包含如下3个步骤。
( 1) 若是指定的联系人已经存在于最近联系人列表里面, 那么从列
表里面移除他。
( 2) 将指定的联系人添加到最近联系人列表的最前面。
( 3) 若是在添加操做完成以后, 最近联系人列表包含的联系人数量
超过了 100个, 那么对列表进行修剪, 只保留位于列表前面的100个联系人。以上描述的3个操做能够经过依次执行LREM命令、 LPUSH命令和LTRIM命令来实现, 而且为了确保操做不会带有任何竞争条件, 咱们会像在第3章中介绍的那样, 使用由MULTI命令和EXEC命令构成的事务包裹起LREM、 LPUSH和LTRIM这3个命令
 
第二个操做
就是在用户不想再看见某个联系人的时候, 将指定的联系人从联系人列表里面移除掉,这个操做能够经过如下这个LREM调用来完成:
 
最后一个操做
就是获取自 动补全列表并查找匹配的用户。 由于实际的自 动补全处理是在Python里面完成的, 因此操做须要首先获取整个列表结构, 而后再在Python里面处理它, 正如代码清单6-2所示

 

由于咱们已经预先考虑到了 “ 从列表里面移除一个元素所需的时间与列表长度成正比”这个问题, 并明确地限制最近联系人列表最多只能存储100个联系人, 因此本节给出的自 动补全实现能够运行得很是好, 而且速度也足够快,
但它并不适合用来处理很是大的列表。 若是你须要一个可以存储更多元素的最常使用列表( most-recently-used list) 或者最少使用列表( least-recently-used list) , 那么能够考虑使用 带有时间戳的有序集合来代替本节介绍的最近联系人列表。
 
 
6.1.2 通信录自动补全
对于比较短的列表来讲, 这种作法还算可行, 但对于很是长的列表来讲, 仅仅为了找到几个元素而获取成千上万个元素, 是一种很是浪费资源的作法。 所以, 为了对包含很是多元素的列表进行自 动补全, 咱们必须直接在Redis内部完成查找匹配元素的工做。
 
为了在客户端进行自 动补全的时候, 尽可能 减小服务器须要传输给客户端的数据量, 咱们将使用有序集合来直接在Redis内部完成自 动补全的前缀计算工做。
 
在大多数状况下, 咱们使用有序集合是为了 快速地判断某个元素是否存在于有序集合里面、 查看某个成员在有序集合中的位置或索引 , 以及从有序集合的某个地方快速地按范围取出多个元素。
 
而这一次, 咱们将把有序集合里面的 全部分值都设置为0——这种作法使得咱们可使用有序集合的另外一个特性: 当全部成员的分值都相同时, 有序集合将根据成员的名字来进行排序; 而当全部成员的分值都是0的时候, 成员将按照字符串的二进制顺序进行排序
 
为了执行自 动补全操做, 程序会以小写字母的方式插入联系人的名字, 而且为了方便起见, 程序规定用户的名字只能包含英文字母, 这样的话就不须要考虑如何处理数字或者符号了。
 
(1)
那么咱们该如何实现这个自 动补全功能呢? 首先, 若是咱们将用户的名字看做是相似abc, abca, abcd, …, abd这样的有序字符串序列, 那么查找带有abc前缀的单词实际上就是查找介于abbz. . . 以后和abd以前的字符串。 若是咱们知道第一个排在abbz以前的元素的排名以及第一个排在abd以后的元素的排名, 那么就能够用一个ZRANGE调用来取得全部介于abbz. . . 和abd之间的元素, 而问题就在于咱们并不知道那两个元素的具体排名。 为了解决这个问题, 咱们须要向有序集合分别插入两个元素, 一个元素排在abbz. . . 的后面, 而另外一个元素则排在abd的前面, 接着根据这两个元素的排名来调用 ZRANGE命令, 最后移除被插入的两个元素。
 
由于在ASCII编码里面, 排在z后面的第一个字符就是左花括号 {, 因此咱们只要将{拼接到abc前缀的末尾, 就能够得出元素abc{, 这个元素既位于abd以前, 又位于全部带有abc前缀的合法名字以后。 一样的, 只要将{追加到abb的末尾, 就能够得出元素abb{, 这个元素位于全部带有abc前缀的合法名字以前, 能够在按范围查找全部带有abc前缀的名字时, 将其用做起始元素。 另外一方面, 由于在ASCII编码里面,第一个排在a前面的字符就是反引 号`, 因此若是咱们要查找的是带有aba前缀的名字, 而不是带有abc前缀的名字, 那么可使用ab` 做为范围查找的起始元素, 并将aba{用做范围查找的结束元素。综上所述, 经过将给定前缀的最后一个字符替换为第一个排在该字符前面的字符, 能够获得前缀的前驱( predecessor) , 而经过给前缀的末尾拼接上左花括号, 能够获得前缀的后继( successor) 。 为了防止多个前缀搜索同时进行时出现任何问题, 程序还会给前缀拼接一个左花括号, 以便在有须要的时候, 根据这个左花括号来过滤掉被插入有序集合里面的起始元素和结束元素。
 
(2)
字符集与国际化 对于只使用a~z字符的语言来讲, 这个在ASCII编码里面查找前一个字符和后一个字符的方法能够运做得很是好, 但若是你要处理的字符并不只仅限于a~z范围, 那么你还须要解决其余几个问题。
 
首先, 你须要想办法把全部字符都转换为字节, 常见的作法是使用 UTF-八、 UTF-16或者UTF-32字符编码( UTF-16和UTF-32有大端版本和小端版本可用, 但只有大端版本能够在咱们所处的状况下运做) 。
其次, 你须要找出自 己想要支持的字符范围, 并确保你的字符编码在你所选范围的前面和后面都至少留有一个字符。
最后, 你须要使用位于范围前面和后面的字符来分别代替前面例子中的反引 号` 和左花括号{。
 
好在咱们的算法只关心编码而不是字符在底层的排列顺序, 因此不管你使用的是UTF-8, 仍是大端或者小端的UTF-1六、 UTF-32, 你均可以 使用空字节( null) 来代替反引 号, 并使用你的编码和语言支持的最大值来代替左花括号。 (某些语言的绑定数量是比较有限的, 它们在UTF-16上面最大只能支持Unicode码点U+ffff, 在UTF-32上面最大只能支持Unicode码点U+2ffff。 )
 
在确认了须要查找的范围以后, 程序会将起始元素和结束元素插入有序集合里面, 而后查看两个被插入元素的排名, 并从它们之间取出一些元素, 最后再从有序集合里面移除这两个元素(为了不滋扰用户,程序最多只会取出10个元素) 。 为了防止自 动补全程序在多个公会成员同时向同一个公会成员发送消息的时候, 将多个相同的起始元素和结束元素重复地添加到有序集合里面, 或者错误地从有序集合里面移除了由其余自 动补全程序添加的起始元素和结束元素, 自 动补全程序会将一个随机生成的128位全局惟一标识符( UUID) 添加到起始元素和结束元素的后面。 另外自 动补全程序还会在插入起始元素和结束元素以后, 经过使用WATCH、 MULTI和EXEC来确保有序集合不会在进行范围查找和范围取值期间发生变化。
 
经过向有序集合添加元素来建立查找范围, 并在取得范围内的元素以后移除以前添加的元素, 这是一种很是有用的技术。 虽然本章只将这种技术用在了实现自 动补全上面, 可是这种技术一样能够应用在任何已排序索引 ( sorted index) 上面。 第7章中将会介绍一种可以改善这类操做的技术, 这种技术可以应用于几种不一样类型的范围查询, 而且不须要过添加元素来建立范围。 之因此把这个改善后的方法留到以后才介绍, 是由于它只可以应用于某些类型的数据, 而本章介绍的方法则能够对任意类型的数据进行范围查询。
 
咱们须要谨慎地处理其余正在执行的自 动补全操做, 这也是程序里面用到了 WATCH命令的缘由。 可是随着负载的增长, 程序进行重试的次数可能会愈来愈多, 致使资源被白白浪费。 接下来的一节将介绍如何经过使用锁来减小对WATCH命令的使用, 甚至使用锁来代替WATCH命令, 从而达到避免重试、 提高性能并在某些状况下简化代码的效果。
 

6.2 分布式锁
Redis使用WATCH命令来代替对数据进行加锁, 由于WATCH只会在数据被其余客户端抢先修改了的状况下通知执行了这个命令的客户端, 而不会阻止其余客户端对数据进行修改, 因此这个命令被称为 乐观锁
 
分布式锁也有相似的“首先获取锁, 而后执行操做, 最后释放锁”动做, 但这种锁既不是给同一个进程中的多个线程使用, 也不是给同一台机器上的多个进程使用, 而是由不一样机器上的不一样Redis客户端进行获取和释放的。
 
虽然Redis提供的 SETNX命令确实具备基本的加锁功能, 但它的功能并不完整, 而且也不具有分布式锁常见的一些高级特性, 因此咱们仍是须要自 己动手来构建分布式锁
 
这一节将会说明“为何使用WATCH命令来监视被频繁访问的键可能会引 起性能问题”, 还会展现构建一个锁的详细步骤, 并最终在某些状况下使用锁去代替WATCH命令。
 
 
6.2.1 锁的重要性
 
如今来回顾一下商品的购物过程。 当玩家在市场上购买商品的时候, 程序首先须要使用 WATCH去监视市场以及买家的我的信息散列, 在得知买家现有的钱数以及商品的售价以后, 程序会验证买家是否有足够的钱来购买指定的商品: 若是买家有足够的钱, 那么程序会将买家支付的钱转移给卖家, 接着将商品添加到买家的包裹里面, 并从市场里面移除已被售出的商品; 相反地, 若是买家没有足够的钱来购买商品, 那么程序就会取消事务。 在执行购买操做的过程当中, 若是有其余玩家对市场进行了改动, 或者由于记录买家我的信息的散列出现了变化而引 发了WATCH错误, 那么程序将从新执行购买操做。
 
为了展现锁对于性能扩展的必要性, 咱们会模拟市场在3种不一样负载状况下的性能表现, 这3种状况分别是1个玩家出售商品, 另1个玩家购买商品; 5个玩家出售商品, 另1个玩家购买商品; 以及5个玩家出售商品, 另外5个玩家购买商品。 表6-1展现了模拟的结果。

根据表6-1的模拟结果显示, 随着负载不断增长, 系统完成一次交易所需的重试次数从最初的3次上升到了 250次, 与此同时, 完成一次交易所需的等待时间也从最初的少于10 ms上升到了 500 ms。 这个模拟示例完美地展现了为何WATCH、 MULTI和EXEC组成的事务并不具备可扩性, 缘由在于程序在尝试完成一个事务的时候, 可能会由于事务执行失败而反复地进行重试。 保证数据的正确性是一件很是重要的事情, 但用WATCH命令的作法并不完美。 为了解决这个问题, 并以可扩展的方式来处理市场交易, 咱们将使用锁来保证市场在任一时刻只能上架或者销售一件商品。
 
6.2.2 简易锁
本书接下来将向读者介绍第1版的锁实现, 这个锁很是简单, 而且在一些状况下可能会没法正常运做。
 
下面列出了一些致使锁出现不正确行为的缘由, 以及锁在不正确运行时的症状。
 
1.持有锁的 进程由于操做时间过长而致使锁被自 动释放, 但进程自己并不知晓这一点, 甚至还可能会错误地释放掉了其余进程持有的
锁。
2.一个持有锁并打算执行长时间操做的进程已经崩溃, 但其余想要获取锁的进程不知道哪一个进程持有着锁, 也没法检测出持有锁的进程已经崩溃, 只能白白地浪费时间等待锁被释放。
3.在一个进程持有的锁过时以后, 其余多个进程同时尝试去获取锁,
而且都得到了锁。
 
上面提到的第一种状况和第三种状况同时出现, 致使有多个进程得到了锁, 而每一个进程都觉得自 己是惟一一个得到锁的进程。
 
由于Redis在最新的硬件上能够每秒执行 100 000个操做, 而在高端的硬件上甚至能够每秒执行将近225 000个操做, 因此尽管上面提到的题出现的概率只有万分之一, 但这些问题在高负载的状况下仍是有可能会出现②, 所以, 让锁正确地运做起来仍然是一件至关重要的事情。
 
 
6.2.3 使用 Redis构建锁
本节接下来要介绍的是锁实现的第1个版本, 这个版本的锁要作的事就是 正确地实现基本的加锁功能, 而以后的一节将会介绍如何处理过时的锁以及由于持有者崩溃而没法释放的锁。
 
(1)
程序首先要作的就是获取锁。 SETNX命令天生就适合用来实现锁的获取功能, 这个命令只会在键不存在的状况下为键设置值, 而锁要作的就是将一个随机生成的128位UUID设置为键的值, 并使用这个值来防止锁被其余进程取得。若是程序在尝试获取锁的时候失败, 那么它将不断地进行重试, 直到成功地取得锁或者超过给定的时限为止, 正如代码清单6-8所示。

acquire_lock()函数的行为和前面描述的同样: 它会使用SETNX命令, 尝试在表明锁的键不存在的状况下, 为键设置一个值, 以此来获取锁; 在获取锁失败的时候, 函数会在给定的时限内进行重试, 直到成功获取锁或者超过给定的时限为止(默认的重试时限为10秒) 。
 
(2)
在实现了锁以后, 咱们就可使用锁来代替针对市场的WATCH操做了。 代码清单6-9展现了使用锁从新实现的商品购买操做: 程序首先对市场进行加锁, 接着检查商品的价格, 并在确保买家有足够的钱来购买商品以后, 对钱和商品进行相应的转移。 当操做执行完毕以后, 程序就会释放锁。

 

(3)
由于在程序持有锁期间, 其余客户端可能会擅自 对锁进行修改, 因此锁的释放操做须要和加锁操做同样当心谨慎地进行。 代码清单6-10中的release_lock()函数展现了锁释放操做的实现代码: 函数首先使用WATCH命令监视表明锁的键, 接着检查键目 前的值是否和加锁时设置的值相同, 并在确认值没有变化以后删除该键(这个检查还能够防止程序错误地释放同一个锁屡次) 。
在使用锁代替WATCH从新实现商品购买操做以后, 咱们能够再次进行以前的商品买卖模拟操做: 表6-2中的单数行展现了WATCH实现的模拟结果, 而表中的复数行则展现了在与前一行条件相同的状况下, 锁实现的模拟结果。

与以前的WATCH实现相比, 锁实现的上架商品数量虽然有所减小,可是在买入商品时 却不须要进行重试, 而且上架商品数量和买入商品数量之间的比率, 也跟卖家数量和买家数量之间的比率接近。 目 前来讲,不一样上架和买入进程之间的竞争限制了商品买卖操做性能的进一步提高, 而接下来介绍的细粒度锁将解决这个问题。
 
6.2.4 细粒度锁
在前面介绍锁实现以及加锁操做的时候, 咱们考虑的是如何实现与WATCH命令粒度相同的锁——这种锁能够把整个市场都锁住。 由于咱们是自 己动手来构建锁实现, 而且咱们关心的不是整个市场, 而是市场里面的某件商品是否存在, 因此咱们实际上能够将加锁的粒度变得更细一些。 经过只锁住被买卖的商品而不是整个市场, 能够减小锁竞争出现的概率并提高程序的性能。
表6-3展现了使用只对单个商品进行加锁的锁实现以后, 进行与表6-2所示相同的模拟时的结果。

表6-3中的模拟结果显示, 在使用细粒度锁的状况下, 不管有多少个上架进程和买入进程在运行, 程序总能在60秒内完成220 000~230 000次的上架和买入操做, 而且不会引 发任何重试操做。
 
在高负载状况下, 使用锁能够减小重试次数、 下降延迟时间、 提高性能并将加锁的粒度调整至合适的大小。
 
6.2.5 带有超时限制特性的锁
为了给锁加上超时限制特性, 程序将在取得锁以后, 调用 EXPIRE命令来为锁设置过时时间, 使得Redis能够自 动删除超时的锁。 为了确保锁在客户端已经崩溃(客户端在执行介于SETNX和EXPIRE之间的时候崩溃是最糟糕的) 的状况下仍然可以自 动被释放, 客户端会在尝试获取锁失败以后, 检查锁的超时时间, 并为未设置超时时间的锁设置超时时间。 所以锁总会带有超时时间, 并最终由于超时而自 动被释放, 使得其余客户端能够继续尝试获取已被释放的锁。
 
须要注意的一点是, 由于多个客户端在同一时间内设置的超时时间基本上都是相同的, 因此 即便有多个客户端同时为同一个锁设置超时时间, 锁的超时时间也不会产生太大变化。

 

新的acquire_lock_with_timeout()函数给锁增长了超时限制特性, 这一特性确保了锁总会在有须要的时候被释放, 而不会被某个客户
端一直把持着。 更棒的是, 这个新的加锁函数能够和以前写好的锁释放函数一块儿使用, 咱们不须要另外再写新的锁释放函数。

在其余数据库里面, 加锁一般是一个自 动执行的基本操做。 而Redis的WATCH、 MULTI和EXEC, 就像以前所说的那样, 只是一个乐观锁——这种锁只会在数据被其余客户端抢先修改了的状况下, 通知加锁的客户端, 让它撤销对数据的修改, 而不会真正地把数据锁住。 经过在客户端上面实现一个真正的锁, 程序能够为用户带来更好的性能、 更熟悉的编程概念、 更简单易用的API, 等等。 可是与此同时, 也请记住Redis并不会主动使用这个自 制的锁, 咱们必须自 己使用这个锁来代替WATCH, 或者同时使用锁和WATCH协同进行工做, 从而保证数据的正确与一致。
 

6.3 计数信号量
计数信号量是一种锁, 它可让用户限制一项资源最多可以同时被多少个进程访问, 一般用于限定可以同时使用的资源数量。 你能够把咱们在前一节建立的锁看做是只能被一个进程访问的信号量。
 
 

6.4 任务队列
 
在处理Web客户端发送的命令请求时, 某些操做的执行时间可能会比咱们预期的更长一些。 经过将待执行任务的相关信息放入队列里面,并在以后对队列进行处理, 用户能够推迟执行那些须要一段时间才能完成的操做, 这种将工做交给任务处理器来执行的作法被称为任务队列( task queue) 。 如今有不少专门的任务队列软件(如ActiveMQ、RabbitMQ、 Gearman、 Amazon SQS, 等等) , 另外在缺乏专门的任务队列可用的状况下, 也有一些临时性的方法能够建立任务队列。 比方说使用按期做业来扫描一个数据表, 查找那些在给定时间/日 期以前或者以后被修改过/被检查过的用户帐号, 并根据扫描的结果执行某些操做, 这也是在建立任务队列。
 
这一节接下来将介绍两种不一样类型的任务队列, 第一种队列会根据务被插入队列的顺序来尽快地执行任务, 而第二种队列则具备安排任务在将来某个特定时间执行的能力。
 
6.4.1 先进先出队列
咱们要编写的队列将以“先到先服务”( first-come, first-served) 的方式发送邮件, 而且不管发送是否成功, 程序都会把发 送结果记录到日 志里面。 本书在第3章和第5章中曾经介绍过, Redis的列表结构容许用户经过RPUSH和LPUSH以及RPOP和LPOP, 从列表的两端推入和弹出元素。此次的邮件队列将使用RPUSH命令来将待发送的邮件推入列表的右端,而且由于工做进程除了发送邮件以外不须要执行其余工做, 因此它将使用阻塞版本的弹出命令BLPOP从队列中弹出待发送的邮件, 而命令的最大阻塞时限为30秒(从右边推入元素并从左边弹出元素的作法, 符合咱们从左向右进行阅读的习惯) 。
 
 
 
 
 
6.4.2 延迟任务
有几种不一样的方法能够为队列中的任务添加延迟性质, 如下是其中3种最直截了当的方法。
 
1.在任务信息中包含任务的执行时间, 若是工做进程发现任务的执行时间还没有来临, 那么它将在短暂等待以后, 把任务从新推入队列里面。
2.工做进程使用一个本地的等待列表来记录全部须要在将来执行的任务, 并在每次进行while循环的时候, 检查等待列表并执行那些已
经到期的任务。
3.把全部须要在将来执行的任务都添加到有序集合里面, 并将任务的执行时间设置为分值, 另外再使用一个进程来查找有序集合里面是否存在能够当即被执行的任务, 若是有的话, 就从有序集合里面移除那个任务, 并将它添加到适当的任务队列里面。
 
由于不管是进行短暂的等待, 仍是将任务从新推入队列里面, 都会浪费工做进程的时间, 因此咱们不会采用第一种方法。 此外, 由于工做进程可能会由于崩溃而丢失本地记录的全部待执行任务, 因此咱们也不会采用第二种方法。 最后, 由于使用有序集合的第三种方法最简单和直接, 因此咱们将采起这一方法, 并使用6.2节中介绍的锁来保证任务从有序集合移动到任务队列时的安全性。
 
 
 

6.5 消息拉取
两个或多个客户端在互相发送和接收消息的时候, 一般会使用如下两种方法来传递消息。 第一种方法被称为消息推送( push
messaging) , 也就是由发送者来确保全部接收者已经成功接收到了消息。
 
Redis内置了用于进行消息推送的PUBLISH命令和SUBSCRIBE命令,本书在第3章中已经介绍过这两个命令的用法和缺陷③。
第二种方法被称为消息拉取( pull messaging) , 这种方法要求接收者自 己去获取存储在某种邮箱( mailbox) 里面的消息。
 
尽管消息推送很是有用, 可是当客户端由于某些缘由而没办法一直保持在线的时候, 采用这一消息传递方法的程序就会出现各类各样的问题。 为了解决这个问题, 咱们将编写两个不一样的消息拉取方法, 并使用它们来代替PUBLISH命令和SUBSCRIBE命令。
 
6.5.1 单接收者消息的发送与订阅替代品
 
 
 
 
6.5.2 多接收者消息的发送与订阅替代品
 
 
 

6.6 使用 Redis进行文件分发
在构建分布式软件和分布式系统的时候, 咱们经常须要在多台机器上复制、 分发或者处理数据文件, 而现有的工具能够以几种不一样的方式来完成这些工做:
(1)若是服务器须要持续地分发文件, 那么常见的作法是使用NFS或者Samba来载入一个路径( path) 或者驱动器;
(2)对于内容会逐渐发生变化的文件来讲, 常见的作法是使用一款名为Rsync的软件来尽可能减小两个系统之间须要传输的数据量;
(3)在须要将多个文件副本分发到多台机器上面的时候, 可使用BitTorrent协议来将文件部分地( partial) 分发到多台机器上面, 而后经过让各台机器互相分享自 己所拥有的数据来下降服务器的负载。
 
遗憾的是, 以上提到的全部方法都有显著的安装成本以及相对的价值。
(1)虽然NFS和Samba都很好用, 可是因为这两种技术都对操做系统进行了整合, 因此它们在网络链接不完美的时候都会出现明显的问题(有时候甚至在网络链接无恙的状况下, 也是如此) 。
(2) Rsync旨在解决网络不稳定带来的问题, 让单个文件或者多个文件能够部分地进行传送和续传( resume) , 但Rsync在开始传输文件以前必须先下载整个文件, 而且负责获取文件的软件也必须与Rsync进行对接, 这一点是否可行也是一个须要考虑的地方。
(3)尽管BitTorrent是一个了不得的技术, 但它也只适用于服务器在发送文件方面遇到了限制或者网络未被充分使用的状况
下, 而且这种技术也须要软件与BitTorrent客户端进行对接, 而咱们须要获取文件的系统上可能并无合适的BitTorrent客户端可用。
 
除了上面提到的问题以外, 上述3种方法还须要设置并维护帐号、 权限以及服务器。 由于咱们已经有了一个安装完毕、 正在运行而且随时可用的Redis, 因此咱们仍是使用Redis来进行文件分发比较好,
这也能够避免使用其余软件时碰到的一些问题: Redis的客户端会妥善地处理链接故障, 经过客户端也能够直接获取数据, 而且针对数据的处理操做能够当即执行而没必要等待整个文件出现。
 
6.6.1 根据地理位置聚合用户数据
 
 
6.6.2 发送日 志文件
 
6.6.3 接收日 志文件
 
6.6.4 处理日 志文件
 
 
 
 

6.7 小结
在这一章, 咱们学习了 6个主要的主题, 但若是仔细地观察这些主题的话, 就会发现咱们实际上解决了 9个问题。 本章尽量地采用前面章节介绍过的想法和工具来构建更有用的工具, 以此来强调“解决某个问题时用到的技术, 一样能够用来解决其余问题”这个道理。
 
本章试图向读者传达的第一个概念是: 尽管WATCH是一个内置、 方便且有用的命令, 可是使用6.2节中介绍的分布式锁可让针对Redis的并发编程变得简单得多。 经过锁住粒度更细的部件而不是整个数据库键, 能够大大减小冲突出现的概率, 而锁住各个相关的操做也有助于下降操做的复杂度。 在这一章中, 咱们就看到了如何使用锁去简化4.6节介绍过的商品买卖市场以及6.4.2节介绍过的延迟任务队列, 并对它们的性能进行提高。
 
本章试图向读者传达的第二个概念, 读者应该铭记于心, 并将其付诸于实践的就是: 只要多花点心思, 就可使用Redis构建出可重用的组件。 好比在这一章中, 咱们就看到了如何在计数器信号量、 延迟队列和具备多个接收者的消息传递系统中重用分布式锁, 以及如何在使用Redis进行文件分发的时候, 重用具备多个接收者的消息传递系统。
 
在接下来的一章中, 咱们将使用Redis来构建更高级的工具, 并编写文档索引 、 基于分值进行索引 和排序的搜索引 擎等可以支援整个应用序的代码, 还会实现一个广告追踪系统和一个职位搜索系统。 本书在后章节中也会继续使用这些组件, 所以请读者留心观察, 并记住使用Redis来构建可重用的组件并非一件难事。
 
 
② 本书做者对几个带有超时限制特性的Redis锁实现进行了测试,发现即便只使用5个客户端来获取和释放同一个锁, 也有至少一半的锁实如今10秒内就出现了多个客户端都得到了锁的问题。
③ 简单来讲, PUBLISH和SUBSCRIBE的缺陷在于客户端必须一 直在线才能接收到消息, 断线可能会致使客户端丢失信息; 除此以外, 旧版Redis可能会由于订阅者处理消息的速度不够快而变得不稳定甚至崩溃, 又或者被管理员杀死。
④ MapReduce(又称Map/Reduce) 是Google推广的一种分布式计算方式, 它能够高效而且简单地解决某些问题。
相关文章
相关标签/搜索