阅读视图

发现新文章,点击刷新页面。

Memcached 和 Redis 有什么区别,做缓存 WordPress 用哪个好?

前面介绍了使用 Memcached 内存缓存来实现 WordPress 站点秒开,但是很多人问到了 Redis,就是做缓存 Redis 和 Memcached 有什么区别,用哪个更好?今天就给大家做一个简单介绍。

Memcached 和 Redis 有什么区别

首先从模型上看,Memcached 是一个分布式内存缓存系统,专注于简单的键-值对存储,设计简单易用,它不支持复杂数据类型,而 Redis 是一个内存数据结构存储,支持更多的数据结构(如列表、集合、排序集合、哈希等),不仅仅是简单的键-值对。

所以 Memcached 是为简单的读写操作优化的,适合需要快速缓存大量数据的场景,而 Redis 提供了丰富的数据操作功能,对复杂数据操作优化良好,同时也提供了高性能的读写速度,并且 Redis 支持数据持久化,可以将内存数据保存到磁盘,Memcached 没有持久性功能,所有数据都存储在内存中,服务器重启时数据就会丢失,作为缓存这个问题不大。

此外,它们还有一点比较大的区别,就是 Memcached 支持多线程,所以支持高并发访问,而 Redis 一般使用单线程模型,虽然通过 I/O 多路复用技术提供高吞吐量,这样就比较在秒杀等场景下使用,不需要考虑并发的问题,因为单线程。

简单做个功能特性对比表格:

特性MemcachedRedis
数据结构仅支持键值对(String)支持 String、List、Hash、Set 等
持久化不支持支持 RDB 快照和 AOF 日志
线程模型多线程(高并发读优)单线程(避免锁竞争,顺序执行)
内存管理预分配固定内存,LRU 淘汰支持内存淘汰策略,可配置虚拟内存
集群模式依赖客户端分片(如 Twemproxy)原生 Cluster 模式支持
适用场景简单键值缓存(如会话、HTML片段)缓存、消息队列、实时统计等复杂场景

Redis 的作者的建议

Redis 的作者 Salvatore Sanfilippo 曾经对这两种基于内存的数据存储系统进行过比较:

  1. Redis 支持服务器端的数据操作:Redis 相比 Memcached 来说,拥有更多的数据结构和并支持更丰富的数据操作,通常在 Memcached 里,你需要将数据拿到客户端来进行类似的修改再 set 回去。这大大增加了网络 IO 的次数和数据体积。在 Redis 中,这些复杂的操作通常和一般的 GET/SET 一样高效。所以,如果需要缓存能够支持更复杂的结构和操作,那么 Redis 会是不错的选择。
  2. 内存使用效率对比:使用简单的 key-value 存储的话,Memcached 的内存利用率更高,而如果 Redis 采用 hash 结构来做 key-value 存储,由于其组合式的压缩,其内存利用率会高于 Memcached。
  3. 性能对比:由于 Redis 只使用单核,而 Memcached 可以使用多核,所以平均每一个核上 Redis 在存储小数据时比 Memcached 性能更高。而在 100k 以上的数据中,Memcached 性能要高于 Redis,虽然 Redis 最近也在存储大数据的性能上进行优化,但是比起 Memcached,还是稍有逊色。

总结一下:Memcached 是解决简单缓存问题的可靠选择,而 Redis 通过提供更丰富的功能和各种各样的特性而优于 Memcached,这些特性对于解决复杂的场景更有优势。

WordPress 使用哪个做缓存好?

如果单纯作为内存缓存来使用,两者其实没有什么区别,那么为什么 WPJAM Basic 为什么使用 Memcached 呢?

这里有一份 4 核 CPU / 8G 内存的服务器上的测试数据:

操作Memcached QPSRedis QPS
GET 请求120,000+100,000+
SET 请求90,000+80,000+

可以看出在 WordPress 这样简单的键-值缓存,并且需要处理大量简单数据,Memcached 是一个更好的选择。

此外我们一开始就选择了 Memcached 做 WordPress 的内存缓存的存储介质,并且在使用 Memcached 做内存缓存过程中,针对出现的各种的问题,我也做了很多的优化,比如我们在后台「WPJAM」菜单下的「系统信息」中展示「Memcached」的各种信息。

Memcached 命中率

当然如果你已经使用了 Redis 做内存缓存或者更熟悉 Redis,WordPress 官方插件库也有很多第三方提供了 Redis 的 object-cache.php,和使用 Memcached 一样,只需要将对应的 object-cache.php 上传到 wp-content 目录即可。

此外也有小伙伴问,WPJAM Basic 会不会集成 Redis,让大家自己选,这个可以明确的说,目前作为简单缓存功能,Memcached 已经足够好用,不会再花时间集成 Redis。

最后还有个问题,Memcached 和 Redis 可以一起用吗?如果作为缓存,WordPress 只能选择一个,因为你上传谁的 object-cache.php,就是用谁得了。

但是不等于使用 Memcached 作为缓存,就不能使用 Redis 了,比如我们之前就利用 Redis 的一些特性做过一些开发,比如因为 Redis 单线程天然串行排队的特性,我们在花生小店秒杀的功能中就是使用 Redis 进行处理的,还有一些简单的消息队列的功能处理,也是基于 Redis 进行开发的。

简单总结

所以最后总结说一下,如果简单的 key-value 的内存缓存,建议使用 Memcached,WordPress 绝大部分站点也是使用 Memcached 进行缓存的,此外 WPJAM Basic 也对此优化得比较完善。

如果要进行复杂的程序开发,比如上面说的电商的秒杀功能 ,消息队列系统等,那么 Redis 才是发挥其长处的地方。

谈谈分布式锁

不要使用分布式锁

就像 Martin Fowler 说的那样,“分布式调用的第一原则就是不要分布式”,谈分布式锁也要先说,不要使用分布式锁。原因很简单,分布式系统是软件系统中复杂的一种形式,而分布式锁是分布式系统中复杂的一种形式,没有必要的复杂性就不要引入。

有的逻辑是没有副作用的(纯函数代码),那就可以无锁执行;有的数据经过合理的 sharding 之后,可以使用单线程(单节点)执行,那就单线程执行。

比如一种常见的模式就是使用 queue(比如 Kafka),任务全部放到队列中,然后根据 sharding 的逻辑,不同的 consumer 来处理不同的任务,互相之间不会干扰冲突。

还有一个例子是 Kotlin Coroutine,通过指定一个单线程的 dispatcher,也可以保证它执行的操作之间互相不会有多线程的冲突问题。

有了这样的原则以后,再来谈谈几种分布式锁。

数据库锁

分布式系统中,我觉得我们最常见的锁就是使用一个中心数据库来做的。

一种是悲观锁,就是 “select xxx … for update” 这样的,相应的数据行会被锁上,直到 commit/rollback 操作发生。如果被别人锁了,当前线程没得到锁的话就会等着。

还有一种是乐观锁,就是使用版本号,“update … where … version=A” 这样的。如果 update 成功,表示获取锁成功,并且操作也成功;否则就是 update 失败,需要重新获取状态再来操作一遍。

大多数情况下,后者要更高效一些,因为阻塞的时间通常更短,不过在锁竞争比较激烈的情况下,反而效率会反过来。另外一个,悲观锁代码写起来会容易一些,因为 select 语句执行和 commit/rollback 是两步操作,因此二者之间可以放置任意逻辑;而乐观锁则是需要把数据的写操作和 version 的比较放在一条语句里面。

这两种都很常见,基本上我接触过的一半以上的项目都用过两者。这个数据库不一定非得是关系数据库,但是强一致性必须是保证的。

S3

使用 S3 来创建文件,让创建成功的节点得到锁,文件里面也可以放自定义的内容。我们去年的项目用到这个机制。这种方式是建立在 S3 2020 年 12 月 1 日,上线的 strong consistency 的 feature

大致上,有这样两种思路:

  1. 使用 S3 versioning,就是说,在 versioning 打开的情况下,文件的写入不会有 “覆盖” 的情况发生,所有内容都会保留。在创建文件的时候,response 种会有一个 x-amz-version-id header。节点写入文件后,再 list 一下所有的 version,默认这些 version 会根据创建的时间顺序递减排列,后创建的在前,因此比较其中最早的那个 version 和自己创建文件后得到的 version,如果两者相等,说明自己得到了锁。
  2. 使用 S3 Object Lock,这个可以控制让第一次写成功,后面的操作全部失败,所以第一次写入成功的节点得到锁。

使用这种方式,对于那些本来就需要使用 S3 文件系统来共享任意信息的情况很方便,但是需要自己处理超时的问题,还有 retention 策略(该不该/什么时候删掉文件)。

Redlock

Redlock 就是 Redis 的锁机制。Martin Kleppmann(就是那个写《Design Data-Intensive Applications》的作者)几年前写过一篇文章,来吐槽 Redlock 在几种情况下是有问题的:

  1. Clock jump:Redlock 依赖于物理时钟,而物理时钟有可能会跳(jump),并且这种状况是无法预测的。Clock jump 就是说,始终会不断进行同步,而同步回来的时间,是有可能不等于当前时间的,那么系统就会设置当前时间到这个新同步回来的时间。在这种情况下,依赖于物理时间的锁逻辑(比如超时的判断等等)就是不正确的。
  2. Process pause:得到锁的节点,它的运行是有可能被阻塞的。比如 GC,下面这个图说的就是这个情况——client 1 一开始得到锁了,执行过程中有一个超长时间的 pause,这个 pause 导致锁超时并被强制释放,client 2 就得到锁了,之后 client 1 GC 结束,缓过来后恢复执行,它却并没有意识到,它的锁已经被剥夺了,于是 client 1 和 client 2 都得到了锁,对于数据的修改就会发生冲突。
  3. Network delay:其实原理和上面差不多,网络延迟+锁超时被强制剥夺和重分配的逻辑,在特定情况下就是不正确的。

问题可以理解,可是仔细想想这个问题的本质是什么?它的本质其实就是消息延迟+重排序的问题,或者更本质地说,就是分布式系统不同节点保持 consistency 的问题,因为 lock service 和 client 就是不同的节点,lock service 认为之前的锁过期了,并重分配锁给了 client 2,并且 client 2 也是这样认为的,可是 client 1 却不是,它在 GC 之后认为它还持有者锁呢。

如果我们把数据的写操作和锁管理的操作彻底分开,这个问题就很难解决,因为两个节点不可能 “一直” 在通信,在不通信的时间段内,就可能会发生这种理解不一致的情况。但是如果我们把写操作和锁管理以某种方式联系上,那么这个问题还是可以被解决的。简单说,就是物理时钟不可靠,逻辑时钟可以解决这个问题

之后 Martin Kleppmann 提出了解决方案,他的解决方案也就是按照这个思路进行的。他的方法很简单,就是在获取锁的时候,得到一个永远递增的 token(可以被称作 “fencing token”),在执行写操作的时候,必须带上这个 token。如果 storage 看到了比当前 token 更小的 token,那么那个写操作就要被丢弃掉。

Chubby

Chubby 是 Google 的分布式锁系统,论文在这里可以找到,还有这个胶片,对于进一步理解论文很有帮助。从时间上看,它是比较早的。

Chubby 被设计成用于粗粒度的(coarse-grained)锁需求,而非细粒度(fine-grained,比如几秒钟以内的)的锁需求。对于这样一个系统,文中开始就提到 consistency 和 availablity 重要性要大过 performance,后面再次提到首要目标包括 reliability,能够应对较多数量的 clients,和易于理解的语义,而吞吐量和存储容量被列在了第二位。

Chubby 暴露一个文件系统接口,每一个文件或者文件夹都可以视作一个读写锁,文件系统和 Unix 的设计思路一致,包括命名、权限等等的设计都是基于它。这是一个很有意思的设计。

对于一致性的达成,它使用 Paxos,客户端寻找 master 和写数据都使用 quorum 的机制,保证写的时候大部分节点成功,而读的时候则是至少成功读取大部分节点(R+W>N,这个思路最早我记得是 Dynamo 的论文里面有写);如果 lock 有了变化,它还提供有通知机制,因为 poll 的成本太高。

内部实现上面,每一个 Chubby 的 cell 都是由作为 replica 的 5 个服务节点组成,它们使用 Paxos 来选举 master 和达成一致,读写都只在 master 上进行(这个看起来还是挺奢侈的,一个干活,四个看戏)。如果 master 挂掉了,在 master lease 过了以后,会重新选举。Client 根据 DNS 的解析,会访问到该 cell 里面的某一个节点,它不一定是 master,但是它会告知谁是 master。

分布式锁里面比较难处理的问题不是失败,而是无响应或者响应慢的超时问题。Chubby 采用一种租约的机制,在租约期内,不会轻易变动当前的 master 节点决定。在响应超时的时期,客户端的策略就是 “不轻举妄动”,耐心等待一段时间等服务端恢复,再不行才宣告失败:

这个图的大致意思是,第一次租约 C1 续订没有问题;第二次租约续订 C2 了之后,原来的 master 挂了,心跳请求无响应,这种情况客户端不清楚服务端的状况,就比较难处理,于是它只能暂时先阻塞所有的操作,等到 C2 过期了之后,有一个 grace period;接着再 grace period 之内,新的 master 被选举出来了,心跳就恢复了,之后租约续订的 C3 顺利进行。

这显然是一个异常情形,但是一旦这种情况发生,系统是被 block 住的,会有非常大的延迟问题。思考一下,这种情况其实就是从原来的 master 到新的 master 转换的选举和交接期间,锁服务是 “暂停” 的。再进一步,这个事情的本质,其实就是在分布式系统中,CAP 原理告诉我们,为了保证 Consistency 和 Partition Tolerance,这里的情形下牺牲掉了 Availability;同时,为了保证 consistency,很难去兼顾 performance(latency 和 throughput)。

此外,有一个有点反直觉的设计是,Chubby 客户端是设计有缓存的。通常来讲,我们设计一个锁机制,第一印象就是使用缓存会带来复杂性,因为缓存会带来一致性的难题。不过它的解决办法是,使用租约。在租约期内,服务端的锁数据不可以被修改,如果要修改,那么就要同步阻塞操作通知所有的客户端,以让缓存全部失效(如果联系不上客户端那就要等过期了)。很多分布式系统都是采用 poll 的方案——一堆 client 去 poll 一个核心服务(资源),但是 Chubby 彻底反过来了,其中一个原因也是低 throughput 的考虑,毕竟只有一个 master 在干活。

对于前面提到的 Martin Kleppmann 谈到的那个问题,Chubby 给了两个解决方法:

  1. 一个是锁延迟,就是说,如果一切正常,那么持有锁的客户端在释放掉锁之后,另外的客户端可以立即获取锁。但是如果出现超时等等异常,这个锁必须被空置一段时间才可以被分配。这个方法可以降低这个问题出现的概率,但是不能彻底规避问题。
  2. 第二个就是使用序列号了,对于写入操作来说,如果请求携带的序列号要小于前一次写入的序列号,那就丢弃请求,返回失败。

回过头思考 Chubby 的实现机制,我觉得有这样几个启发:

  1. 不要相信任何 “人”(节点),必须询问到多数人(quorum),多数人的结论才可以认为是正确的。这个方式发生在很多操作上,比如寻找 master,比如选举 master,比如写数据。
  2. 超时是很难处理的,它采用了租约的机制保证节点丢失时间的上限,使用 grace period 来容忍 master 选举发生的时延,使用序列号来保证正确性。

文章未经特殊标明皆为本人原创,未经许可不得用于任何商业用途,转载请保持完整性并注明来源链接 《四火的唠叨》

Redis 性能分析与优化

Redis 简介

Redis 是一个 key-value 内存存储系统,并且支持丰富的数据结构,包括:

  1. string
  2. list
  3. hash table
  4. set(集合)
  5. zset(有序集合)

其体量很小,但却支持丰富的数据结构和相应的操作方式,是一个非常好的工具。

然而像很多工具一样,Redis 也有一些坑,我也是被坑过一段时间的……

当然,这里就不对 Redis 的数据结构及操作做过多说明了,可能的话单独再写一篇文章,不过考虑网上类似的文章实在不少,估计是不会去写了。

从遇到的问题说起

目前我负责的一个项目使用着 Redis ,在 10 月中下旬的时候发现往 Redis 写数据的定时任务频繁 timeout ,同时从同一个 Redis 读数据的线上项目的 log 也显示经常一些读操作耗时增长了十倍甚至更多。

发现问题的时候,首先怀疑是不是自己代码里的逻辑写得有问题,花了两天时间把相关的代码翻来覆去看了好几遍,虽然是有一些地方有待优化,但这些地方的问题是不足以引发前面说的问题的。而在我为代码中每个 Redis 操作都加上了超时检测后,发现甚至连 get 这样的操作都会发生超时(所取的 key 的 value 并不大)后,就已经大致明白应该和我的代码没有关系。

找 SA 要来 Redis server 所在服务器上的帐号后开始跑脚本往 Redis 写数据,同时监控 Redis server 的 log ,最后发现了这么一条 log:

[25206] 04 Nov 17:34:41.090 * Asynchronous AOF fsync is taking too long (disk is busy?). Writing the AOF buffer without waiting     for fsync to complete, this may slow down Redis.

this may slow down Redis !

原来,这个 Redis server 的持久化方式是 RDB+AOF,而上述警告信息是因为 AOF 操作造成的。有了这个明确的警告信息后,再去网上搜索就找到了不少类似的案例:

  1. how redis took us offline and what we did about it
  2. logs of timeout in someone second #248

各种迹象都说明我遇到的问题是和这条警告信息密切相关的,于是回过头重新启动脚本,并且监控 Redis server 日志中包含 "Asynchronous AOF fsync is taking too long" 的内容。经过一个下午的观察,确定超时错误与这条日志是同时出现的。

redisdebugger.png

最后的解决办法是在 Redis master 上关闭了 AOF。

用于分析 Redis 性能的一些命令(参数)

都是在解决上面提到的问题的过程中,所了解到的 tricks。

redis-cli 参数

  • –bigkeys

    redis-cli -h <host> -p <port> -n <db> --bigkeys
    

    这条命令会从指定的 Redis DB 中持续采样,实时输出当时得到的 value 占用空间最大的 key 值,并在最后给出各种数据结构的 biggest key 的总结报告:

    user $ redis-cli -h myhost -p 12345 --bigkeys
    
    # Scanning the entire keyspace to find biggest keys as well as
    # average sizes per key type.  You can use -i 0.1 to sleep 0.1 sec
    # per 100 SCAN commands (not usually needed).
    
    [00.00%] Biggest hash   found so far 'idx:同类项' with 1 fields
    [00.00%] Biggest hash   found so far 'idx:格点' with 3 fields
    [00.00%] Biggest hash   found so far 'idx:任意' with 14 fields
    [02.29%] Biggest hash   found so far 'idx:设' with 16 fields
    [02.29%] Biggest hash   found so far 'idx:4' with 69 fields
    [04.45%] Biggest set    found so far 'indexed_word_set' with 1482 members
    [05.93%] Biggest hash   found so far 'idx:c' with 159 fields
    [11.79%] Biggest hash   found so far 'idx:的' with 196 fields
    
    -------- summary -------
    
    Sampled 1484 keys in the keyspace!
    Total key length in bytes is 13488 (avg len 9.09)
    
    Biggest    set found 'indexed_word_set' has 1482 members
    Biggest   hash found 'idx:的' has 196 fields
    
    0 strings with 0 bytes (00.00% of keys, avg size 0.00)
    0 lists with 0 items (00.00% of keys, avg size 0.00)
    2 sets with 1710 members (00.13% of keys, avg size 855.00)
    1482 hashs with 6731 fields (99.87% of keys, avg size 4.54)
    0 zsets with 0 members (00.00% of keys, avg size 0.00)
    

    这条命令给出的 "big key" ,是值得去分析一下的。

  • –latency, –latency-history

    这两条命令用来测试 Redis 服务端的响应延迟,–latency 的输出是这样的

    min: 7, max: 1230, avg: 66.22 (89 samples)
    

    –latency-history 的输出是这样的:

    min: 7, max: 3332, avg: 157.41 (91 samples) -- 15.23 seconds range
    min: 6, max: 774, avg: 75.69 (176 samples) -- 15.10 seconds range
    min: 7, max: 1631, avg: 93.49 (147 samples) -- 15.23 seconds range
    min: 7, max: 714, avg: 67.90 (194 samples) -- 15.14 seconds range
    min: 7, max: 809, avg: 95.09 (143 samples) -- 15.04 seconds range
    

    两者的区别在于前者显示的是持续的采样结果,而后者则只输出一段时间内的采样结果。

    用这两个参数可以判断客户端与服务端之间的网络通信造成的延迟对整体延迟的影响,方法是在 Redis Server 所在的服务器上使用 –latency/–latency-history 得到大致的延迟结果,然后在客户端所在机器上同样得到延迟情况进行对比。

redis-cli 子命令

  • slowlog 命令

    默认的 Redis server 配置中有这么两条:

    slowlog-log-slower-than 10000
    slowlog-max-len 128
    

    这两条配置会产生这样的效果: 如果一条命令的响应时间超过了 10000us (即 10ms) ,那么将会作为 "slow command" 被记录,并且将只保留最新的 128 条 记录。

    需要说明的是,这里所说的 响应时间 不包括客户端与服务端之间的通信开销,仅仅指命令在 Redis server 中的执行时间消耗。

    使用 slowlog 这个子命令可以获取当前的这些记录:

    127.0.0.1:6379> slowlog get 3
    1) 1) (integer) 26
       2) (integer) 1450253133
       3) (integer) 43097
       4) 1) "flushdb"
    2) 1) (integer) 25
       2) (integer) 1450252804
       3) (integer) 33115
       4) 1) "flushdb"
    3) 1) (integer) 24
       2) (integer) 1450248951
       3) (integer) 3328650
       4) 1) "flushdb"
    

    在不指定记录数量的情况下(slowlog get),默认返回 10 条记录,每条记录包含四个字段,以下面这个为例来说明

    1) (integer) 26
    2) (integer) 1450253133
    3) (integer) 43097
    4) 1) "flushdb"
    

    第一个字段表示该条记录在所有慢日志中的序号,最新的记录被展示在最前面;

    第二个字段是这条记录被记录时的系统时间,可以用 date 命令来将其转换为友好的格式:

    linusp $ date -d @1450253133
    2015年 12月 16日 星期三 16:05:33 CST
    

    第三个字段表示这条命令的响应时间,单位为 us (微秒);

    第四个字段为对应的 Redis 操作。

    因此上面这个例子的含义就是: 在 2015 年 12 月 16 日 16 时 5 分 33 秒执行的 flushdb 操作耗时 43ms ,因为超过了设定的值被记录到慢日志中。

  • info

    顾名思义,这个子命令用来获取 Redis server 的一些信息,这些信息按照内容被分成了很多部分,可以用额外的参数来单独获取,如下:

    参数名 说明
    server 获取 server 信息,包括 version, OS, port 等信息
    clients 获取 clients 信息,如客户端连接数等
    memory 获取 server 的内存信息,包括当前内存消耗、内存使用峰值
    persistence 获取 server 的持久化配置信息
    stats 获取 server 的一些基本统计信息,如处理过的连接数量等
    replication 获取 server 的主从配置信息
    cpu 获取 server 的 CPU 使用信息
    keyspace 获取 server 中各个 DB 的 key 的数量
    cluster 获取集群节点信息,仅在开启集群后可见
    commandstas 获取每种命令的统计信息,非常有用

    挑几个个人觉得有用的部分再详细说明一下.

    • info memory

      输出如下:

      # Memory
      used_memory:17348250392
      used_memory_human:16.16G
      used_memory_rss:14339567616
      used_memory_peak:17348362312
      used_memory_peak_human:16.16G
      used_memory_lua:33792
      mem_fragmentation_ratio:0.83
      mem_allocator:jemalloc-3.0.0
      

      了解 Redis server 的内存占用是很有必要的

    • info keyspace

      输出如下:

      db0:keys=52021100,expires=0,avg_ttl=0
      db1:keys=520062,expires=0,avg_ttl=0
      db2:keys=559810,expires=0,avg_ttl=0
      db4:keys=513887,expires=0,avg_ttl=0
      

      由于 Redis 要为每个 key 维护一些入口信息,因此 key 会比 value 中的对象额外消耗一些内存,当 key 的数量太多的时候会多消耗不少内存。一些优化 Redis 内存占用的方法基本都是基于减少 key 的数量以及减少 key 的平均长度这两个思路。

      key 的数量太多时可以使用 hash 结构将 key 进行分隔成两部分来减少数量,当然这个要看是否能满足具体的业务、功能需求了。

    • info commandstats

      其输出如下:

      cmdstat_get:calls=774811168,usec=2567380170,usec_per_call=3.31
      cmdstat_set:calls=4616783,usec=17308580,usec_per_call=3.75
      cmdstat_append:calls=290375669,usec=2051415709,usec_per_call=7.06
      cmdstat_exists:calls=41374218,usec=72561479,usec_per_call=1.75
      cmdstat_sadd:calls=60640,usec=540252,usec_per_call=8.91
      cmdstat_sismember:calls=3,usec=16,usec_per_call=5.33
      cmdstat_smembers:calls=1863495,usec=253706917,usec_per_call=136.15
      cmdstat_incrby:calls=114499073,usec=157843782,usec_per_call=1.38
      cmdstat_keys:calls=1,usec=79743,usec_per_call=79743.00
      cmdstat_multi:calls=4618103,usec=6087466,usec_per_call=1.32
      cmdstat_exec:calls=4618103,usec=2597435761,usec_per_call=562.45
      cmdstat_replconf:calls=335,usec=512,usec_per_call=1.53
      cmdstat_flushdb:calls=5,usec=842393,usec_per_call=168478.59
      cmdstat_info:calls=825297,usec=39131868,usec_per_call=47.42
      cmdstat_slowlog:calls=23,usec=22646,usec_per_call=984.61
      ......
      

      输出中包含处理过的每一种命令的调用次数、消耗的总 CPU 时间(单位 ms)以及平均 CPU 耗时,这对了解自己的程序所使用的 Redis 操作情况非常有用。

Redis 使用的一点点经验

  1. 管理好自己的 key,比如用一个 set 来存放所有同类型的 key,这样可以避免使用 keys 来获取该类型所有 key,由于 Redis 是单线程的,在数据量大的情况下将会阻塞 Redis 中其他的操作
  2. 如果不能避免使用 keys 操作,那么使用 scan 来替代,将遍历 Redis DB 中所有 key 的操作放到客户端来做,这样就不会导致其他操作被阻塞了
  3. 在数据量大的情况下,key 或 value 的微小的压缩也能带来不小的内存使用提高
  4. hash 结构是个好东西,但是它只支持一级 hash,所以如果需要使用多级 hash,那就进行序列化吧
  5. 注意客户端和服务端之间的网络通信情况(–latency/–latency-history)
  6. Redis 的 python 接口返回的 key 的类型和 value 中的最小结构的类型都是 string,记得转换成自己需要的类型

在 WordPress 的 Docker 镜像上加装 Redis 拓展,以支持 Redis 缓存

从 LAMP 到 Docker based PaaS 工具 当中,我提到我现在使用的是 Docker Based PaaS 产品来托管站点。本站目前其实就是跑在 Docker 上的。

使用默认的 WordPress 镜像时,我发现一个问题:没有支持 Redis 拓展!我使用 Redis 来缓存 Query,提升访问的性能。如果缺失了 Redis 拓展,就会减少一部分缓存的能力。于是开始研究如何在官方的 WordPress 镜像上加入 Redis 拓展。

根据 WordPress 镜像的官方说明,我们可以 docker-php-ext-* 命令来配置镜像,安装必要的拓展,来满足我们日常使用的需求,并给出了官方的参考。

不过,我在验证 Redis 拓展时,使用 docker-php-ext-* 命令没有配置成功,好在可以使用 pecl 来安装。于是,我便将 Dockerfile 修改成如下内容,来完成对于 Redis 拓展的安装。

FROM wordpress:latest
RUN pecl install -o -f redis && rm -rf /tmp/pear && docker-php-ext-enable redis

修改好 Dockerfile ,然后重新启动,一切都好了~

d2b5ca33bd970f64a6301fa75ae2eb22 4
❌