Codis作者黄东旭细说分布式Redis架构设计和踩过的那些坑们

本次分享的内容主要包括五个大部分:

  • Redis、RedisCluster和Codis;
  • 我们更爱一致性;
  • Codis在生产环境中的使用的经验和坑们;
  • 对于分布式数据库和分布式架构的一些看法;
  • Q & A环节。

Codis是一个分布式Redis解决方案,与官方的纯P2P的模式不同,Codis采用的是Proxy-based的方案。今天我们介绍一下Codis及下一个大版本RebornDB的设计,同时会介绍一些Codis在实际应用场景中的tips。最后抛砖引玉,会介绍一下我对分布式存储的一些观点和看法,望各位首席们雅正。

一、 Redis,RedisCluster和Codis


Redis:想必大家的架构中,Redis已经是一个必不可少的部件,丰富的数据结构和超高的性能以及简单的协议,让Redis能够很好的作为数据库的上游缓存层。但是我们会比较担心Redis的单点问题,单点Redis容量大小总受限于内存,在业务对性能要求比较高的情况下,理想情况下我们希望所有的数据都能在内存里面,不要打到数据库上,所以很自然的就会寻求其他方案。 比如,SSD将内存换成了磁盘,以换取更大的容量。更自然的想法是将Redis变成一个可以水平扩展的分布式缓存服务,在Codis之前,业界只有Twemproxy,但是Twemproxy本身是一个静态的分布式Redis方案,进行扩容/缩容时候对运维要求非常高,而且很难做到平滑的扩缩容。Codis的目标其实就是尽量兼容Twemproxy的基础上,加上数据迁移的功能以实现扩容和缩容,最终替换Twemproxy。从豌豆荚最后上线的结果来看,最后完全替换了Twem,大概2T左右的内存集群。

Redis Cluster :与Codis同期发布正式版的官方cluster,我认为有优点也有缺点,作为架构师,我并不会在生产环境中使用,原因有两个:

  • cluster的数据存储模块和分布式的逻辑模块是耦合在一起的,这个带来的好处是部署异常简单,all-in-the-box,没有像Codis那么多概念,组件和依赖。但是带来的缺点是,你很难对业务进行无痛的升级。比如哪天Redis cluster的分布式逻辑出现了比较严重的bug,你该如何升级?除了滚动重启整个集群,没什么好办法。这个比较伤运维。
  • 对协议进行了较大的修改,对客户端不太友好,目前很多客户端已经成为事实标准,而且很多程序已经写好了,让业务方去更换Redisclient,是不太现实的,而且目前很难说有哪个Rediscluster客户端经过了大规模生产环境的验证,从HunanTV开源的Rediscluster proxy上可以看得出这个影响还是蛮大的,否则就会支持使用cluster的client了。

Codis:和Redis cluster不同的是,Codis采用一层无状态的proxy层,将分布式逻辑写在proxy上,底层的存储引擎还是Redis本身(尽管基于Redis2.8.13上做了一些小patch),数据的分布状态存储于zookeeper(etcd)中,底层的数据存储变成了可插拔的部件。这个事情的好处其实不用多说,就是各个部件是可以动态水平扩展的,尤其无状态的proxy对于动态的负载均衡,还是意义很大的,而且还可以做一些有意思的事情,比如发现一些slot的数据比较冷,可以专门用一个支持持久化存储的server group来负责这部分slot,以节省内存,当这部分数据变热起来时,可以再动态的迁移到内存的server group上,一切对业务透明。比较有意思的是,在Twitter内部弃用Twmeproxy后,t家自己开发了一个新的分布式Redis解决方案,仍然走的是proxy-based路线。不过没有开源出来。可插拔存储引擎这个事情也是Codis的下一代产品RebornDB在做的一件事情。btw,RebornDB和它的持久化引擎都是完全开源的,见https://github.com/reborndb/reborn和https://github.com/reborndb/qdb。当然这样的设计的坏处是,经过了proxy,多了一次网络交互,看上去性能下降了一些,但是记住,我们的proxy是可以动态扩展的,整个服务的QPS并不由单个proxy的性能决定(所以生产环境中我建议使用LVS/HA Proxy或者Jodis),每个proxy其实都是一样的。

二、我们更爱一致性


很多朋友问我,为什么不支持读写分离,其实这个事情的原因很简单,因为我们当时的业务场景不能容忍数据不一致,由于Redis本身的replication模型是主从异步复制,在master上写成功后,在slave上是否能读到这个数据是没有保证的,而让业务方处理一致性的问题还是蛮麻烦的。而且Redis单点的性能还是蛮高的,不像mysql之类的真正的数据库,没有必要为了提升一点点读QPS而让业务方困惑。这和数据库的角色不太一样。所以,你可能看出来了,其实Codis的HA,并不能保证数据完全不丢失,因为是异步复制,所以master挂掉后,如果有没有同步到slave上的数据,此时将slave提升成master后,刚刚写入的还没来得及同步的数据就会丢失。不过在RebornDB中我们会尝试对持久化存储引擎(qdb)可能会支持同步复制(syncreplication),让一些对数据一致性和安全性有更强要求的服务可以使用。

说到一致性,这也是Codis支持的MGET/MSET无法保证原本单点时的原子语义的原因。 因为MSET所参与的key可能分不在不同的机器上,如果需要保证原来的语义,也就是要么一起成功,要么一起失败,这样就是一个分布式事务的问题,对于Redis来说,并没有WAL或者回滚这么一说,所以即使是一个最简单的二阶段提交的策略都很难实现,而且即使实现了,性能也没有保证。所以在Codis中使用MSET/MGET其实和你本地开个多线程SET/GET效果一样,只不过是由服务端打包返回罢了,我们加上这个命令的支持只是为了更好的支持以前用Twemproxy的业务。

在实际场景中,很多朋友使用了lua脚本以扩展Redis的功能,其实Codis这边是支持的,但记住,Codis在涉及这种场景的时候,仅仅是转发而已,它并不保证你的脚本操作的数据是否在正确的节点上。比如,你的脚本里涉及操作多个key,Codis能做的就是将这个脚本分配到参数列表中的第一个key的机器上执行。所以这种场景下,你需要自己保证你的脚本所用到的key分布在同一个机器上,这里可以采用hashtag的方式。

比如你有一个脚本是操作某个用户的多个信息,如uid1age,uid1sex,uid1name形如此类的key,如果你不用hashtag的话,这些key可能会分散在不同的机器上,如果使用了hashtag(用花括号扩住计算hash的区域):{uid1}age,{uid1}sex,{uid1}name,这样就保证这些key分布在同一个机器上。这个是twemproxy引入的一个语法,我们这边也支持了。

在开源Codis后,我们收到了很多社区的反馈,大多数的意见是集中在Zookeeper的依赖,Redis的修改,还有为啥需要Proxy上面,我们也在思考,这几个东西是不是必须的。当然这几个部件带来的好处毋庸置疑,上面也阐述过了,但是有没有办法能做得更漂亮。于是,我们在下一阶段会再往前走一步,实现以下几个设计:

  • 使用proxy内置的Raft来代替外部的Zookeeper,zk对于我们来说,其实只是一个强一致性存储而已,我们其实可以使用Raft来做到同样的事情。将raft嵌入proxy,来同步路由信息。达到减少依赖的效果。
  • 抽象存储引擎层,由proxy或者第三方的agent来负责启动和管理存储引擎的生命周期。具体来说,就是现在codis还需要手动的去部署底层的Redis或者qdb,自己配置主从关系什么的,但是未来我们会把这个事情交给一个自动化的agent或者甚至在proxy内部集成存储引擎。这样的好处是我们可以最大程度上的减小Proxy转发的损耗(比如proxy会在本地启动Redis instance)和人工误操作,提升了整个系统的自动化程度。
  • 还有replication based migration。众所周知,现在Codis的数据迁移方式是通过修改底层Redis,加入单key的原子迁移命令实现的。这样的好处是实现简单、迁移过程对业务无感知。但是坏处也是很明显,首先就是速度比较慢,而且对Redis有侵入性,还有维护slot信息给Redis带来额外的内存开销。大概对于小key-value为主业务和原生Redis是1:1.5的比例,所以还是比较费内存的。

在RebornDB中我们会尝试提供基于复制的迁移方式,也就是开始迁移时,记录某slot的操作,然后在后台开始同步到slave,当slave同步完后,开始将记录的操作回放,回放差不多后,将master的写入停止,追平后修改路由表,将需要迁移的slot切换成新的master,主从(半)同步复制,这个之前提到过。

三、Codis在生产环境中的使用的经验和坑们


来说一些 tips,作为开发工程师,一线的操作经验肯定没有运维的同学多,大家一会可以一起再深度讨论。

关于多产品线部署:很多朋友问我们如果有多个项目时,codis如何部署比较好,我们当时在豌豆荚的时候,一个产品线会部署一整套codis,但是zk共用一个,不同的codis集群拥有不同的product name来区分,codis本身的设计没有命名空间那么一说,一个codis只能对应一个product name。不同product name的codis集群在同一个zk上不会相互干扰。

关于zk:由于Codis是一个强依赖的zk的项目,而且在proxy和zk的连接发生抖动造成sessionexpired的时候,proxy是不能对外提供服务的,所以尽量保证proxy和zk部署在同一个机房。生产环境中zk一定要是>=3台的奇数台机器,建议5台物理机。

关于HA:这里的HA分成两部分,一个是proxy层的HA,还有底层Redis的HA。先说proxy层的HA。之前提到过proxy本身是无状态的,所以proxy本身的HA是比较好做的,因为连接到任何一个活着的proxy上都是一样的,在生产环境中,我们使用的是jodis,这个是我们开发的一个jedis连接池,很简单,就是监听zk上面的存活proxy列表,挨个返回jedis对象,达到负载均衡和HA的效果。也有朋友在生产环境中使用LVS和HA Proxy来做负载均衡,这也是可以的。 Redis本身的HA,这里的Redis指的是codis底层的各个server group的master,在一开始的时候codis本来就没有将这部分的HA设计进去,因为Redis在挂掉后,如果直接将slave提升上来的话,可能会造成数据不一致的情况,因为有新的修改可能在master中还没有同步到slave上,这种情况下需要管理员手动的操作修复数据。后来我们发现这个需求确实比较多的朋友反映,于是我们开发了一个简单的ha工具:codis-ha,用于监控各个server group的master的存活情况,如果某个master挂掉了,会直接提升该group的一个slave成为新的master。 项目的地址是:https://github.com/ngaut/codis-ha。

关于dashboard:dashboard在codis中是一个很重要的角色,所有的集群信息变更操作都是通过dashboard发起的(这个设计有点像docker),dashboard对外暴露了一系列RESTfulAPI接口,不管是web管理工具,还是命令行工具都是通过访问这些httpapi来进行操作的,所以请保证dashboard和其他各个组件的网络连通性。比如,经常发现有用户的dashboard中集群的ops为0,就是因为dashboard无法连接到proxy的机器的缘故。

关于go环境:在生产环境中尽量使用go1.3.x的版本,go的1.4的性能很差,更像是一个中间版本,还没有达到production ready的状态就发布了。很多朋友对go的gc颇有微词,这里我们不讨论哲学问题,选择go是多方面因素权衡后的结果,而且codis是一个中间件类型的产品,并不会有太多小对象常驻内存,所以对于gc来说基本毫无压力,所以不用考虑gc的问题。

关于队列的设计:其实简单来说,就是「不要把鸡蛋放在一个篮子」的道理,尽量不要把数据都往一个key里放,因为codis是一个分布式的集群,如果你永远只操作一个key,就相当于退化成单个Redis实例了。很多朋友将Redis用来做队列,但是Codis并没有提供BLPOP/BLPUSH的接口,这没问题,可以将列表在逻辑上拆成多个LIST的key,在业务端通过定时轮询来实现(除非你的队列需要严格的时序要求),这样就可以让不同的Redis来分担这个同一个列表的访问压力。而且单key过大可能会造成迁移时的阻塞,由于Redis是一个单线程的程序,所以迁移的时候会阻塞正常的访问。

关于主从和bgsave:codis本身并不负责维护Redis的主从关系,在codis里面的master和slave只是概念上的:proxy会将请求打到「master」上,master挂了codis-ha会将某一个「slave」提升成master。而真正的主从复制,需要在启动底层的Redis时手动的配置。在生产环境中,我建议master的机器不要开bgsave,也不要轻易的执行save命令,数据的备份尽量放在slave上操作。

关于跨机房/多活:想都别想。。。codis没有多副本的概念,而且codis多用于缓存的业务场景,业务的压力是直接打到缓存上的,在这层做跨机房架构的话,性能和一致性是很难得到保证的

关于proxy的部署:其实可以将proxy部署在client很近的地方,比如同一个物理机上,这样有利于减少延迟,但是需要注意的是,目前jodis并不会根据proxy的位置来选择位置最佳的实例,需要修改。

四、对于分布式数据库和分布式架构的一些看法(one more Thing)


Codis相关的内容告一段落。接下来我想聊聊我对于分布式数据库和分布式架构的一些看法。 架构师们是如此贪心,有单点就一定要变成分布式,同时还希望尽可能的透明:P。就MySQL来看,从最早的单点到主从读写分离,再到后来阿里的类似Cobar和TDDL,分布式和可扩展性是达到了,但是牺牲了事务支持,于是有了后来的OceanBase。Redis从单点到Twemproxy,再到Codis,再到Reborn。到最后的存储早已和最初的面目全非,但协议和接口永存,比如SQL和Redis Protocol。

NoSQL来了一茬又一茬,从HBase到Cassandra到MongoDB,解决的是数据的扩展性问题,通过裁剪业务的存储和查询的模型来在CAP上平衡。但是几乎还是都丢掉了跨行事务(插一句,小米上在HBase上加入了跨行事务,不错的工作)。

我认为,抛开底层存储的细节,对于业务来说,KV,SQL查询(关系型数据库支持)和事务,可以说是构成业务系统的存储原语。为什么memcached/Redis+mysql的组合如此的受欢迎,正是因为这个组合,几个原语都能用上,对于业务来说,可以很方便的实现各种业务的存储需求,能轻易的写出「正确」的程序。但是,现在的问题是数据大到一定程度上时,从单机向分布式进化的过程中,最难搞定的就是事务,SQL支持什么的还可以通过各种mysqlproxy搞定,KV就不用说了,天生对分布式友好。

于是这样,我们就默认进入了一个没有(跨行)事务支持的世界里,很多业务场景我们只能牺牲业务的正确性来在实现的复杂度上平衡。比如一个很简单的需求:微博关注数的变化,最直白,最正常的写法应该是,将被关注者的被关注数的修改和关注者的关注数修改放到同一个事务里,一起提交,要么一起成功,要么一起失败。但是现在为了考虑性能,为了考虑实现复杂度,一般来说的做法可能是队列辅助异步的修改,或者通过cache先暂存等等方式绕开事务。

但是在一些需要强事务支持的场景就没有那么好绕过去了(目前我们只讨论开源的架构方案),比如支付/积分变更业务,常见的搞法是关键路径根据用户特征sharding到单点MySQL,或者MySQLXA,但是性能下降得太厉害。

后来Google在他们的广告业务中遇到这个问题,既需要高性能,又需要分布式事务,还必须保证一致性:),Google在此之前是通过一个大规模的MySQL集群通过sharding苦苦支撑,这个架构的可运维/扩展性实在太差。这要是在一般公司,估计也就忍了,但是Google可不是一般公司,用原子钟搞定Spanner,然后再Spanner上构建了SQL查询层F1。我在第一次看到这个系统的时候,感觉简直惊艳,应该是第一个可以真正称为NewSQL的公开设计的系统。所以,BigTable(KV)+F1(SQL)+Spanner(高性能分布式事务支持),同时Spanner还有一个非常重要的特性是跨数据中心的复制和一致性保证(通过Paxos实现),多数据中心,刚好补全了整个Google的基础设施的数据库栈,使得Google对于几乎任何类型的业务系统开发都非常方便。我想,这就是未来的方向吧,一个可扩展的KV数据库(作为缓存和简单对象存储),一个高性能支持分布式事务和SQL查询接口的分布式关系型数据库,提供表支持。

五、Q & A


Q1:我没看过Codis,您说Codis没有多副本概念,请问是什么意思?

A1:Codis是一个分布式Redis解决方案,是通过presharding把数据在概念上分成1024个slot,然后通过proxy将不同的key的请求转发到不同的机器上,数据的副本还是通过Redis本身保证

Q2:Codis的信息在一个zk里面存储着,zk在Codis中还有别的作用吗?主从切换为何不用sentinel

A2:Codis的特点是动态的扩容缩容,对业务透明;zk除了存储路由信息,同时还作为一个事件同步的媒介服务,比如变更master或者数据迁移这样的事情,需要所有的proxy通过监听特定zk事件来实现 可以说zk被我们当做了一个可靠的rpc的信道来使用。因为只有集群变更的admin时候会往zk上发事件,proxy监听到以后,回复在zk上,admin收到各个proxy的回复后才继续。本身集群变更的事情不会经常发生,所以数据量不大。Redis的主从切换是通过codis-ha在zk上遍历各个server group的master判断存活情况,来决定是否发起提升新master的命令。

Q3:数据分片,是用的一致性hash吗?请具体介绍下,谢谢。

A3:不是,是通过presharding,hash算法是crc32(key)%1024

Q4:怎么进行权限管理?

A4:Codis中没有鉴权相关的命令,在reborndb中加入了auth指令。

Q5:怎么禁止普通用户链接Redis破坏数据?

A5:同上,目前Codis没有auth,接下来的版本会加入。

Q6:Redis跨机房有什么方案?

A6:目前没有好的办法,我们的Codis定位是同一个机房内部的缓存服务,跨机房复制对于Redis这样的服务来说,一是延迟较大,二是一致性难以保证,对于性能要求比较高的缓存服务,我觉得跨机房不是好的选择。

Q7:集群的主从怎么做(比如集群S是集群M的从,S和M的节点数可能不一样,S和M可能不在一个机房)?

A7:Codis只是一个proxy-based的中间件,并不负责数据副本相关的工作。也就是数据只有一份,在Redis内部。

Q8:根据你介绍了这么多,我可以下一个结论,你们没有多租户的概念,也没有做到高可用。可以这么说吧?你们更多的是把Redis当做一个cache来设计。

A8:对,其实我们内部多租户是通过多Codis集群解决的,Codis更多的是为了替换twemproxy的一个项目。高可用是通过第三方工具实现。Redis是cache,Codis主要解决的是Redis单点、水平扩展的问题。把codis的介绍贴一下: Auto rebalance Extremely simple to use Support both Redis or rocksdb transparently. GUI dashboard & admin tools Supports most of Redis commands. Fully compatible with twemproxy(https://github.com/twitter/twemproxy). Native Redis clients are supported Safe and transparent data migration, Easily add or remove nodes on-demand.解决的问题是这些。业务不停的情况下,怎么动态的扩展缓存层,这个是codis关注的。

Q9:对于Redis冷备的数据库的迁移,您有啥经验没有?对于Redis热数据,可以通过migrate命令实现两个Redis进程间的数据转移,当然如果对端有密码,migrate就玩完了(这个我已经给Redis官方提交了patch)。

A9:冷数据我们现在是实现了完整的Redissync协议,同时实现了一个基于rocksdb的磁盘存储引擎,备机的冷数据,全部是存在磁盘上的,直接作为一个从挂在master上的。实际使用时,3个group,keys数量一致,但其中一个的ops是另外两个的两倍,有可能是什么原因造成的?key的数量一致并不代表实际请求是均匀分布的,不如你可能某几个key特别热,它一定是会落在实际存储这个key的机器上的。刚才说的rocksdb的存储引擎:https://github.com/reborndb/qdb,其实启动后就是个Redis-server,支持了PSYNC协议,所以可以直接当成Redis从来用。是一个节省从库内存的好方法。

Q10:Redis实例内存占比超过50%,此时执行bgsave,开了虚拟内存支持的会阻塞,不开虚拟内存支持的会直接返回err,对吗?

A10:不一定,这个要看写数据(开启bgsave后修改的数据)的频繁程度,在Redis内部执行bgsave,其实是通过操作系统COW机制来实现复制,如果你这段时间的把几乎所有的数据都修改了,这样操作系统只能全部完整的复制出来,这样就爆了。

Q11:刚读完,赞一个。可否介绍下codis的autorebalance实现。

A11:算法比较简单,https://github.com/wandoulabs/codis/blob/master/cmd/cconfig/rebalancer.go#L104。代码比较清楚,code talks:)。其实就是根据各个实例的内存比例,分配slot好的。

Q12:主要想了解对降低数据迁移对线上服务的影响,有没有什么经验介绍?

A12:其实现在codis数据迁移的方式已经很温和了,是一个个key的原子迁移,如果怕抖动甚至可以加上每个key的延迟时间。这个好处就是对业务基本没感知,但是缺点就是慢。

来自:http://mp.weixin.qq.com/s?__biz=MzAwMDU1MTE1OQ==&mid=208733458&idx=1&sn=691bfde670fb2dd649685723f7358fea

这可能是最全的 Redis 集群方案介绍了

由于Redis出众的性能,其在众多的移动互联网企业中得到广泛的应用。Redis在3.0版本前只支持单实例模式,虽然现在的服务器内存可以到100GB、200GB的规模,但是单实例模式限制了Redis没法满足业务的需求(例如新浪微博就曾经用Redis存储了超过1TB的数据)。Redis的开发者Antirez早在博客上就提出在Redis 3.0版本中加入集群的功能,但3.0版本等到2015年才发布正式版。各大企业在3.0版本还没发布前为了解决Redis的存储瓶颈,纷纷推出了各自的Redis集群方案。这些方案的核心思想是把数据分片(sharding)存储在多个Redis实例中,每一片就是一个Redis实例。

下面介绍Redis的集群方案。

1.客户端分片

客户端分片是把分片的逻辑放在Redis客户端实现,通过Redis客户端预先定义好的路由规则,把对Key的访问转发到不同的Redis实例中,最后把返回结果汇集。这种方案的模式如图1所示。

图1 客户端分片的模式

客户端分片的好处是所有的逻辑都是可控的,不依赖于第三方分布式中间件。开发人员清楚怎么实现分片、路由的规则,不用担心踩坑。

客户端分片方案有下面这些缺点。

  • 这是一种静态的分片方案,需要增加或者减少Redis实例的数量,需要手工调整分片的程序。
  • 可运维性差,集群的数据出了任何问题都需要运维人员和开发人员一起合作,减缓了解决问题的速度,增加了跨部门沟通的成本。
  • 在不同的客户端程序中,维护相同的分片逻辑成本巨大。例如,系统中有两套业务系统共用一套Redis集群,一套业务系统用Java实现,另一套业务系统用PHP实现。为了保证分片逻辑的一致性,在Java客户端中实现的分片逻辑也需要在PHP客户端实现一次。相同的逻辑在不同的系统中分别实现,这种设计本来就非常糟糕,而且需要耗费巨大的开发成本保证两套业务系统分片逻辑的一致性。

2.Twemproxy

Twemproxy是由Twitter开源的Redis代理,其基本原理是:Redis客户端把请求发送到Twemproxy,Twemproxy根据路由规则发送到正确的Redis实例,最后Twemproxy把结果汇集返回给客户端。

Twemproxy通过引入一个代理层,将多个Redis实例进行统一管理,使Redis客户端只需要在Twemproxy上进行操作,而不需要关心后面有多少个Redis实例,从而实现了Redis集群。

Twemproxy集群架构如图2所示。

图2Twemproxy集群架构

Twemproxy的优点如下。

  • 客户端像连接Redis实例一样连接Twemproxy,不需要改任何的代码逻辑。
  • 支持无效Redis实例的自动删除。
  • Twemproxy与Redis实例保持连接,减少了客户端与Redis实例的连接数。

Twemproxy有如下不足。

  • 由于Redis客户端的每个请求都经过Twemproxy代理才能到达Redis服务器,这个过程中会产生性能损失。
  • 没有友好的监控管理后台界面,不利于运维监控。
  • 最大的问题是Twemproxy无法平滑地增加Redis实例。对于运维人员来说,当因为业务需要增加Redis实例时工作量非常大。

Twemproxy作为最被广泛使用、最久经考验、稳定性最高的Redis代理,在业界被广泛使用。

3.Codis

Twemproxy不能平滑增加Redis实例的问题带来了很大的不便,于是豌豆荚自主研发了Codis,一个支持平滑增加Redis实例的Redis代理软件,其基于Go和C语言开发,并于2014年11月在GitHub上开源。

Codis包含下面4个部分。

  • Codis Proxy:Redis客户端连接到Redis实例的代理,实现了Redis的协议,Redis客户端连接到Codis Proxy进行各种操作。Codis Proxy是无状态的,可以用Keepalived等负载均衡软件部署多个Codis Proxy实现高可用。
  • CodisRedis:Codis项目维护的Redis分支,添加了slot和原子的数据迁移命令。Codis上层的 Codis Proxy和Codisconfig只有与这个版本的Redis通信才能正常运行。
  • Codisconfig:Codis管理工具。可以执行添加删除CodisRedis节点、添加删除Codis Proxy、数据迁移等操作。另外,Codisconfig自带了HTTP server,里面集成了一个管理界面,方便运维人员观察Codis集群的状态和进行相关的操作,极大提高了运维的方便性,弥补了Twemproxy的缺点。
  • ZooKeeper:分布式的、开源的应用程序协调服务,是Hadoop和Hbase的重要组件,其为分布式应用提供一致性服务,提供的功能包括:配置维护、名字服务、分布式同步、组服务等。Codis依赖于ZooKeeper存储数据路由表的信息和Codis Proxy节点的元信息。另外,Codisconfig发起的命令都会通过ZooKeeper同步到CodisProxy的节点。

Codis的架构如图3所示。

图3Codis的架构图

在图3的Codis的架构图中,Codis引入了Redis Server Group,其通过指定一个主CodisRedis和一个或多个从CodisRedis,实现了Redis集群的高可用。当一个主CodisRedis挂掉时,Codis不会自动把一个从CodisRedis提升为主CodisRedis,这涉及数据的一致性问题(Redis本身的数据同步是采用主从异步复制,当数据在主CodisRedis写入成功时,从CodisRedis是否已读入这个数据是没法保证的),需要管理员在管理界面上手动把从CodisRedis提升为主CodisRedis。

如果觉得麻烦,豌豆荚也提供了一个工具Codis-ha,这个工具会在检测到主CodisRedis挂掉的时候将其下线并提升一个从CodisRedis为主CodisRedis。

Codis中采用预分片的形式,启动的时候就创建了1024个slot,1个slot相当于1个箱子,每个箱子有固定的编号,范围是1~1024。slot这个箱子用作存放Key,至于Key存放到哪个箱子,可以通过算法“crc32(key)%1024”获得一个数字,这个数字的范围一定是1~1024之间,Key就放到这个数字对应的slot。例如,如果某个Key通过算法“crc32(key)%1024”得到的数字是5,就放到编码为5的slot(箱子)。1个slot只能放1个Redis Server Group,不能把1个slot放到多个Redis Server Group中。1个Redis Server Group最少可以存放1个slot,最大可以存放1024个slot。因此,Codis中最多可以指定1024个Redis Server Group。

Codis最大的优势在于支持平滑增加(减少)Redis Server Group(Redis实例),能安全、透明地迁移数据,这也是Codis 有别于Twemproxy等静态分布式 Redis 解决方案的地方。Codis增加了Redis Server Group后,就牵涉到slot的迁移问题。例如,系统有两个Redis Server Group,Redis Server Group和slot的对应关系如下。

Redis Server Group slot
1 1~500
2 501~1024

当增加了一个Redis Server Group,slot就要重新分配了。Codis分配slot有两种方法。

第一种:通过Codis管理工具Codisconfig手动重新分配,指定每个Redis Server Group所对应的slot的范围,例如可以指定Redis Server Group和slot的新的对应关系如下。

Redis Server Group slot
1 1~500
2 501~700
3 701~1024

第二种:通过Codis管理工具Codisconfig的rebalance功能,会自动根据每个Redis Server Group的内存对slot进行迁移,以实现数据的均衡。

4.Redis 3.0集群

Redis 3.0集群采用了P2P的模式,完全去中心化。Redis把所有的Key分成了16384个slot,每个Redis实例负责其中一部分slot。集群中的所有信息(节点、端口、slot等),都通过节点之间定期的数据交换而更新。

Redis客户端在任意一个Redis实例发出请求,如果所需数据不在该实例中,通过重定向命令引导客户端访问所需的实例。

Redis 3.0集群的工作流程如图4所示。

图4Redis 3.0集群的工作流程图

如图4所示Redis集群内的机器定期交换数据,工作流程如下。

(1)      Redis客户端在Redis2实例上访问某个数据。

(2)      在Redis2内发现这个数据是在Redis3这个实例中,给Redis客户端发送一个重定向的命令。

(3)      Redis客户端收到重定向命令后,访问Redis3实例获取所需的数据。

Redis 3.0的集群方案有以下两个问题。

  • 一个Redis实例具备了“数据存储”和“路由重定向”,完全去中心化的设计。这带来的好处是部署非常简单,直接部署Redis就行,不像Codis有那么多的组件和依赖。但带来的问题是很难对业务进行无痛的升级,如果哪天Redis集群出了什么严重的Bug,就只能回滚整个Redis集群。
  • 对协议进行了较大的修改,对应的Redis客户端也需要升级。升级Redis客户端后谁能确保没有Bug?而且对于线上已经大规模运行的业务,升级代码中的Redis客户端也是一个很麻烦的事情。

综合上面所述的两个问题,Redis 3.0集群在业界并没有被大规模使用。

5.云服务器上的集群服务

国内的云服务器提供商阿里云、UCloud等均推出了基于Redis的云存储服务。

这个服务的特性如下。

(1)动态扩容

用户可以通过控制面板升级所需的Redis存储空间,扩容的过程中服务部不需要中断或停止,整个扩容过程对用户透明、无感知,这点是非常实用的,在前面介绍的方案中,解决Redis平滑扩容是个很烦琐的任务,现在按几下鼠标就能搞定,大大减少了运维的负担。

(2)数据多备

数据保存在一主一备两台机器中,其中一台机器宕机了,数据还在另外一台机器上有备份。

(3)自动容灾

主机宕机后系统能自动检测并切换到备机上,实现服务的高可用。

(4)实惠

很多情况下为了使Redis的性能更高,需要购买一台专门的服务器用于Redis的存储服务,但这样子CPU、内存等资源就浪费了,购买Redis云存储服务就很好地解决了这个问题。

有了Redis云存储服务,能使App后台开发人员从烦琐运维中解放出来。App后台要搭建一个高可用、高性能的Redis服务,需要投入相当的运维成本和精力。如果使用云存储服务,就没必要投入这些成本和精力,可以让App后台开发人员更专注于业务。

redis持久化和常见故障

redis持久化和常见故障 – SRE-网站可靠性工程师之路 – SegmentFault

redis 主从复制

Redis主从复制的原理

当建立主从关系时,slave配置slaveof <master_host> <master_port> 。slave服务器会向主服务器发送一个sync命令。master接受并fork一个进程来执行BGSAVE命令。该命令生成一个RDB文件并且全量发送给slave服务器,slave服务器接收并载入RDB文件,同时,主服务器将缓冲区的命令以增量的方式发送给从服务器,最终使从服务器的数据状态和主服务器保持一致。

RDB的工作原理

当redis生成dump.rdb文件时,工作过程如下

  • redis主进程fork一个子进程
  • fork出来的子进程将内存的数据集dump到临时的RDB中
  • 当子进程对临时的RDB文件写入完毕,redis用新的RDB文件代替旧的RDB文件

AOF的工作原理

AOF :append only file。每当Redis执行一个改变数据集的命令时,这个命令都会被追加到AOF文件的末尾。当redis重新启动时,程序可以通过AOF文件恢复数据

持久化文件监控

Redis 监控最直接的方法当然就是使用系统提供的 info 命令来做了,只需要执行下面一条命令,就能获得 Redis 系统的状态报告。

redis-cli info

RDB文件状态监控

其中跟RDB文件状态监控相关的参数

  • rdb_changes_since_last_save 表明上次RDB保存以后改变的key次数
  • rdb_bgsave_in_progress 表示当前是否在进行bgsave操作。是为1
  • rdb_last_save_time 上次保存RDB文件的时间戳
  • rdb_last_bgsave_time_sec 上次保存的耗时
  • rdb_last_bgsave_status 上次保存的状态
  • rdb_current_bgsave_time_sec 目前保存RDB文件已花费的时间

AOF文件状态监控

其中跟AOF文件状态监控相关的参数

  • aof_enabled AOF文件是否启用
  • aof_rewrite_in_progress 表示当前是否在进行写入AOF文件操作
  • aof_rewrite_scheduled
  • aof_last_rewrite_time_sec 上次写入的时间戳
  • aof_current_rewrite_time_sec:-1
  • aof_last_bgrewrite_status:ok 上次写入状态
  • aof_last_write_status:ok 上次写入状态

查看rdb文件生成耗时

在我们优化master之前,可以看看目前我们的其中一个生产环境的的redis的持久化状态

# Persistence
loading:0
rdb_changes_since_last_save:116200
rdb_bgsave_in_progress:1
rdb_last_save_time:1448944451
rdb_last_bgsave_status:ok
rdb_last_bgsave_time_sec:85
rdb_current_bgsave_time_sec:33
aof_enabled:0
aof_rewrite_in_progress:0
aof_rewrite_scheduled:0
aof_last_rewrite_time_sec:-1
aof_current_rewrite_time_sec:-1
aof_last_bgrewrite_status:ok
aof_last_write_status:ok

通过redis-cli的info命令,可以看到 「rdb_last_bgsave_time_sec」参数的值,
这个值表示上次bgsave命令执行的时间。在磁盘IO定量的情况下,redis占用的内存越大,
这个值也就越大。通常「rdb_last_bgsave_time_sec」这个时间取决于两个因素:

  • REDIS占用的内存大小
  • 磁盘的写速度。

rdb_last_bgsave_time_sec:85 这个标识表示我们上次保存dump RDB文件的时间。这个耗时受限于上面提到的两个因素。

当redis处于 rdb_bgsave_in_progress状态时,通过vmstat命令查看性能,得到wa值偏高,也就是说CPU在等待
IO的请求完成,我们线上的一个应用redis占用的内存是5G左右,也就是redis会生成大约5G左右的dump.rdb文件

vmstat命令

  r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 0  4      0 223912 2242680 5722008    0    0   200 48648 3640 5443  1  1 63 35  0
 0  3      0 222796 2242680 5722052    0    0    16 48272 2417 5019  1  1 63 35  0
 0  3      0 222300 2242680 5722092    0    0    40 24612 3042 3568  1  1 63 35  0
 0  3      0 220068 2242680 5722124    0    0    64 40328 4304 4737  2  1 63 34  0
 0  3      0 218952 2242680 5722216    0    0   100 48648 4966 5786  1  2 63 35  0
 0  3      0 215356 2242680 5722256    0    0     0 66168 3546 4382  2  1 62 35  0

通过上面的输出,看到BGSAVE 对于IO的性能影响比较大

那么该如何解决由RDB文件带来的性能上不足的问题,又能保证数据持久化的目的

通常的设计思路就是利用「Replication」机制来解决:即master不开启RDB日志和AOF日志,来保证master的读写性能。而slave则开启rdb和aof来进行持久化,保证数据的持久性,

建立主从复制步骤和灾难恢复

我在测试机器上,开启两个实例,端口分别为6379和6380

master: 172.16.76.232 6379
slave:  172.16.76.232 6380

修改配置

修改master的redis.conf

关闭RDB

# save 900 1
# save 300 10
# save 60 10000

关闭AOF

appendonly no

分别启动master和slave的redis

service redis start

修改slave配置,指向master服务器

redis-cli > slaveof 172.16.76.232 6379

查看slave的复制状态

redis-cli > info replication

脚本模拟填充数据


#!/bin/bash

ID=1
while(($ID<50001))
do
 redis-cli set "my:$ID" "aaa_okthisis_Omb5EVIwBgPHgbRj64raygpeRLKaNhyB9sLF_$ID"
 redis-cli set "your:$ID" "your_okthisis_Omb5EVIwBgPHgbRj64raygpeRLKaNhyB9sLF_$ID"
 redis-cli set "her:$ID" "her_okthisis_Omb5EVIwBgPHgbRj64raygpeRLKaNhyB9sLF_$ID"
 redis-cli set "his:$ID" "his_okthisis_Omb5EVIwBgPHgbRj64raygpeRLKaNhyB9sLF_$ID"

 ID=$(($ID+1))
done

kill掉master实例模拟灾难

master redis > killall -9 redis-server
SLAVE redis > SLAVEOF NO ONE

取消Slave的同步,避免主库在未完成数据恢复前就重启,进而直接覆盖掉从库上的数据,导致所有的数据丢失。

将slave上的RDB和AOF复制到master数据文件夹中

cp /data/redis_data_slave/dump.rdb /data/redis_data/
cp /data/redis_data_slave/Append.AOF /data/redis_data/

启动master的实例

master redis > dbsize

查看数据是否恢复

重新开启slave复制

slave redis > slaveof 172.16.76.232 6379

故障案例报告

redis丢失数据案例

背景介绍:

我们的一台redis服务器,硬件配置为4核,4G内存。redis持久话方案是RDB。前面几个月redis使用的

内存在1G左右。在一次重启之后,redis只恢复了部分数据,这时查看redis.log文件。看见了如下的错误

[23635] 25 Jul 08:30:54.059 * 10000 changes in 60 seconds. Saving...
[23635] 25 Jul 08:30:54.059 # Can't save in background: fork: Cannot allocate memory

这时,想起了redis启动时的警告

WARNING overcommit_memory is set to 0!
Background save may fail under low memory condition.
To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and
then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect.

翻译

警告:过量使用内存设置为0!在低内存环境下,后台保存可能失败。为了修正这个问题,
请在/etc/sysctl.conf 添加一项 'vm.overcommit_memory = 1' ,
然后重启(或者运行命令'sysctl vm.overcommit_memory=1' )使其生效。

vm.overcommit_memory不同的值说明

  • 0 表示检查是否有足够的内存可用,如果是,允许分配;如果内存不够,拒绝该请求,并返回一个错误给应用程序。
  • 1 允许分配超出物理内存加上交换内存的请求
  • 2 内核总是返回true

redis的数据回写机制分为两种

  • 同步回写即SAVE命令。redis主进程直接写数据到磁盘。当数据量大时,这个命令将阻塞,响应时间长
  • 异步回写即BGSAVE命令。redis 主进程fork一个子进程,复制主进程的内存并通过子进程回写数据到磁盘。

由于RDB文件写的时候fork一个子进程。相当于复制了一个内存镜像。当时系统的内存是4G,而redis占用了
近3G的内存,因此肯定会报内存无法分配。如果 「vm.overcommit_memory」设置为0,在可用内存不足的情况
下,就无法分配新的内存。如果 「vm.overcommit_memory」设置为1。 那么redis将使用交换内存。

解决办法:

  • 方法一: 修改内核参数 vi /etc/sysctl。设置 vm.overcommit_memory = 1 然后执行
    ``` sysctl -p ```
  • 方法二: 使用交换内存并不是一个完美的方案。最好的办法是扩大物理内存。

复制有可能碰到的问题

使用slaveof命令后,长时间看不到数据同步。以为复制功能失效,或配置错了。其实,不用担心,有两种方法可以确定是否正在建立复制。

在创建Redis复制时,一开始可能会发现Slave长时间不开始同步数据,可能数据量太大,导致了Master正在dump数据慢,此时如果你在Master上执行「top -p $(pgrep -d, redis-server)」命令,就能看到dump的过程

方式一: 通过「top」命令

[root@img1_u ~]# top -p $(pgrep -d, redis-server)
top - 14:06:24 up 54 days,  6:13,  1 user,  load average: 1.18, 1.32, 1.20
Tasks:   2 total,   1 running,   1 sleeping,   0 stopped,   0 zombie
Cpu(s): 15.2%us,  1.7%sy,  0.6%ni, 81.9%id,  0.2%wa,  0.0%hi,  0.4%si,  0.0%st
Mem:  24542176k total, 22771848k used,  1770328k free,  2245720k buffers
Swap:   524280k total,        0k used,   524280k free,  4369452k cached

  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND
21619 root      20   0 5654m 5.4g  388 R 99.9 23.0   0:23.70 redis-server
 1663 root      20   0 5654m 5.4g 1068 S 15.3 23.0   5042:31 redis-server

redis-server是单进程的,现在通过「top」命令查看已经有2个进程,因为之前提到的,redis在建立复制的时,会在

主服务器上执行 BGSAVE 命令。fork一个子进程,dump出RDB文件。 master dump 完毕,然后再将快照文件传给slave。

方式二:通过「rdb_bgsave_in_progress」标识

进入master的redis-cli

redis-cli > info persistence
...
loading:0
rdb_changes_since_last_save:0
rdb_bgsave_in_progress:1
rdb_last_save_time:1448992510
rdb_last_bgsave_status:ok
rdb_last_bgsave_time_sec:4
rdb_current_bgsave_time_sec:1
...

如果「rdb_bgsave_in_progress」为1,那么master正在进行bgsave命令。同时「rdb_current_bgsave_time_sec」
显示bgsave命令已经执行的时间。由于在master服务器上默认不开启RDB和AOF日志,如果「rdb_bgsave_in_progress」为1,那么就可以肯定由于复制原因发送一个「bgsave」指令 dump 出 RDB 文件。

redis 内存达到上限

有运营的同事反应,系统在登录的情况下,操作时会无缘无故跳到登录页面。 由于我们的系统做了分布式的
session,默认把session放到redis里,按照以往的故障经验。可能是redis使用了最大内存上限
导致了无法设置key。 登录 redis 服务器查看 redis.conf 文件设置了最大8G内存「maxmemory 8G」
然后通过「redis-cli info memory 」 查询到目前的内存使用情况 「used_memory_human:7.71G」
接着通过redis-cli工具设置值 。报错 「OOM command not allowed when used memory 」。再次
验证了redis服务器已经达到了最大内存

解决方法:

  1. 关闭redis 服务器 redis-cli shutdown
  2. 修改配置文件的最大内存 「maxmemory」
  3. 启动redis服务器 redis-server redis.conf

Redis之KEY操作命令 【学习笔记】

本文记录redis key操作的大部分命令和自己测试的例子,希望能帮助到别人

 

开启服务

$sudo ./src/redis-server &

键操作

*定义并赋值变量苹果的数量
$ redis-cli set dangcheng::apple::count 152
OK

*获取刚才的苹果数量
$ redis-cli get dangcheng::apple::count
“152”

*当苹果数量增加1 incr关键字实际上是increment的简写
$ redis-cli incr dangcheng::apple::count
(integer) 153

*当苹果来了一盒,而这盒苹果共7个则我们需要加上指定数量关键字是incrby
$ redis-cli incrby dangcheng::apple::count 7
(integer) 160

*如果set一个php数组 我们可以使用php函数serialize将对象序列化后写入
$age=array(“Bill”=>“35”,“Steve”=>“37”,“Peter”=>“43”);
$age_str = seriallze($age);//将这个结果写入redias中
$age = unseriallze($age_str);//get获取后我们可以使用unseriallze反序列化把其转换为PHP对象

EXISTS key 是否存在

存在返回 1
$ redis-cli exists dangcheng::apple::count
(integer) 1
不存在返回 0
$ redis-cli exists dangcheng::apple::color
(integer) 0

DUMP序列化值 不包含任何生命周期信息

不存在返回nil
$ redis-cli dump dangcheng::apple::color
(nil)
返回序列化结果
$redis-cli dump dangcheng::apple::count
“\x00\xc1\xa0\x00\x06\x00\xb0t6\xdaT\x99#\xa2”

expire 和 ttl 设置生命期和查看剩余时间

TTL命令是查询生存时间剩余,不设置生存时间返回-1代表永久
$ redis-cli ttl dangcheng::apple::count
(integer) -1
用expire设置生存时间剩余366秒
$ redis-cli expire dangcheng::apple::count 366
(integer) 1
写这段的过程中消耗了127秒
$ redis-cli ttl dangcheng::apple::count
(integer) 239
PS1:此外还有PEXPIRE命令与expire功能是一样的,只是设置是以毫秒为单位
*PS2:此外还有PTTL命令与TTL功能是一样的,只是显示是以毫秒为单位

expireat

此命令不同于expire去设定剩余,而是直接设置当前key什么时候死亡 而参数是linux时间戳表示
PS:pexpireat 命令与expireat命令等效,区别是单位精确到毫秒数的unix时间戳

keys 查找所有符合给定模式 pattern 的 key

KEYS * 匹配数据库中所有 key 。
KEYS h?llo 匹配 hello , hallo 和 hxllo 等。
KEYS hllo 匹配 hllo 和 heeeeello 等。
KEYS h[ae]llo 匹配 hello 和 hallo ,但不匹配 hillo 。
特殊符号用 \ 隔开
$ redis-cli keys dangcheng::*
1) “dangcheng::apple::color”
2) “dangcheng::apple::count”

migrate 将当前redis实例中的对象迁移到另一个host下的对应端口的实例【数据的迁移】

在本机8888端口创建另一个redis服务
$ redis-server –port 8888 &
[1] 13086
原有实例中存在
$ redis-cli
127.0.0.1:6379> keys dangcheng::*
1) “dangcheng::apple::color”
2) “dangcheng::apple::count”

8888端口下的实例中找不到数据
$ redis-cli -p 8888
127.0.0.1:8888> keys dangcheng

(empty list or set)
将源实例中的数据count传入8888端口的实例
127.0.0.1:6379> migrate 127.0.0.1 8888 dangcheng::apple::count 0 -1
OK
源实例中的count不见了,证明传输源会被删掉
127.0.0.1:6379> keys dangcheng
1) “dangcheng::apple::color”
在8888端口下的实例中查到了
127.0.0.1:8888> get dangcheng::apple::count
“25”

move 将数据从一个db空间中迁移到另一个db空间中

选择index0的dbspace
127.0.0.1:6379> select 0
OK
127.0.0.1:6379> keys *
1) “dangcheng::apple::color”
2) “test::first”
移动到index1的dbspace
127.0.0.1:6379> move test::first 1
(integer) 1
127.0.0.1:6379> keys *
1) “dangcheng::apple::color”
127.0.0.1:6379> select 1
OK
127.0.0.1:6379[1]> keys *
1) “test::first”
我们看到了的确在dbsopace1中查询到了
127.0.0.1:6379[1]> get test::first
“556779”
PS:要注意的是如果target db 有相同的key 则移动不会成功

OBJECT 对象操作

*返回给定 key 引用所储存的值的次数。此命令主要用于除错
127.0.0.1:6379[1]> object refcount test::first
(integer) 1

  • 返回给定 key 锁储存的值所使用的内部表示(representation)。
    127.0.0.1:6379[1]> object encoding test::first
    “int”
    127.0.0.1:6379[1]> set str ‘hello redis!’
    OK
    127.0.0.1:6379[1]> object encoding str
    “raw”
    返回给定 key 自储存以来的空闲时间(idle, 没有被读取也没有被写入),以秒为单位
    127.0.0.1:6379[1]> object idletime str
    (integer) 236
    对象可以以多种方式编码:
    字符串可以被编码为 raw (一般字符串)或 int (为了节约内存,Redis 会将字符串表示的 64 位有符号整数编码为整数来进行储存)。
    列表可以被编码为 ziplist 或 linkedlist 。 ziplist 是为节约大小较小的列表空间而作的特殊表示。
    集合可以被编码为 intset 或者 hashtable 。 intset 是只储存数字的小集合的特殊表示。
    哈希表可以编码为 zipmap 或者 hashtable 。 zipmap 是小哈希表的特殊表示。
    *有序集合可以被编码为 ziplist 或者 skiplist 格式。 ziplist 用于表示小的有序集合,而 skiplist 则用于表示任何大小的有序集合。

    persist 将一个key的超时时间变为永久

    使用expire 给对象设置生存时间
    127.0.0.1:6379[1]> expire test::first 3600
    (integer) 1
    查询剩余时间
    127.0.0.1:6379[1]> ttl test::first
    (integer) 3597
    127.0.0.1:6379[1]> persist test::first
    (integer) 1
    *的确变为了持久化key
    127.0.0.1:6379[1]> ttl test::first
    (integer) -1

*randomkey 随机抽取一个key
127.0.0.1:6379> randomkey
“dangcheng::apple::color2”
127.0.0.1:6379> randomkey
“dangcheng::apple::color”
127.0.0.1:6379> randomkey
“dangcheng::apple::color1”

mset[multi set] 批量设置

127.0.0.1:8888> mset a ‘aaa’ b ‘bbb’ c ‘cccc’
OK
查询所有key
127.0.0.1:8888> keys *
1) “dangcheng::apple::count”
2) “o”
3) “a”
4) “b”
5) “c”
清空dbspace
127.0.0.1:8888> flushdb
OK
*查看所有,很显然没有了
127.0.0.1:8888> keys *
(empty list or set)

rename 修改key名字

127.0.0.1:6379> set a ‘aaa’
OK
127.0.0.1:6379> rename a b
OK
127.0.0.1:6379> get a
(nil)
127.0.0.1:6379> get b
“aaa”

renamenx 目标key一旦已存在不会被覆盖 只有空的key才能成为target

127.0.0.1:6379> renamenx b c
(integer) 0
127.0.0.1:6379> get c
“66”
127.0.0.1:6379> get b
“bb”
127.0.0.1:6379> renamenx b d
(integer) 1
127.0.0.1:6379> get d
“bb”
127.0.0.1:6379> getb
(error) ERR unknown command ‘getb’
127.0.0.1:6379> get b
(nil)

dump&restore 序列化和反序列化

127.0.0.1:6379> dump c
“\x00\xc0B\x06\x00\xac\x15Y\x1aO127.0.0.1:6379> get c
“66”
序列化c中的值
127.0.0.1:6379> dump c
“\x00\xc0B\x06\x00\xac\x15Y\x1aO
通过反序列化一个值创建新的键 newc
127.0.0.1:6379> restore newc 0 “\x00\xc0B\x06\x00\xac\x15Y\x1aOOK
127.0.0.1:6379> get newc
“66”

sort [asc|desc] [alpha][limit]给一个列表进行排序 默认为asc方式,使用alpha方式可以给字符串进行排序,此外还可以使用limit进行范围限制

*lpush 是用于创建一个列表的命令
127.0.0.1:6379> lpush sort_list 15 2 45 20 16 3 7 9
(integer) 8
127.0.0.1:6379> sort sort_list
1) “2”
2) “3”
3) “7”
4) “9”
5) “15”
6) “16”
7) “20”
8) “45”
127.0.0.1:6379> sort sort_list asc
1) “2”
2) “3”
3) “7”
4) “9”
5) “15”
6) “16”
7) “20”
8) “45”
127.0.0.1:6379> sort sort_list desc
1) “45”
2) “20”
3) “16”
4) “15”
5) “9”
6) “7”
7) “3”
8) “2”

type 查看key的数据类型

*@return none (key不存在) |string (字符串)|list (列表)|set (集合)|zset (有序集)|hash (哈希表)
127.0.0.1:6379> type sort_list
list
127.0.0.1:6379> type c
string

Redis系列-存储篇set主要操作函数小结

最近,总是以“太忙“为借口,很久没有blog了,凡事贵在恒,希望我能够坚持不懈,毕竟在blog的时候,也能提升自己。废话不说了,直奔主题”set“

redis set 是string类型对象的无序集合,set不管存储多少对象,对存储对象的add,remove和test操作的时间复杂度是O(1)。set最多能包含 232 – 1 个member。

1、增加

语法:sadd key member[member…]

解释:对特定key的set增加一个或多个值,返回是增加元素的个数。注意:对同一个member多次add,set中只会保留一份。

  1. [root@xsf001 ~]# redis-cli
  2. redis 127.0.0.1:6379> sadd stu zhangsan lisi wangwu #新增
  3. (integer) 3
  4. redis 127.0.0.1:6379> smembers stu    #得到set的所有member
  5. 1) “wangwu”
  6. 2) “lisi”
  7. 3) “zhangsan”
  8. redis 127.0.0.1:6379> sadd stu zhangsan #增加存在的member
  9. (integer) 0
  10. redis 127.0.0.1:6379> smembers stu
  11. 1) “wangwu”
  12. 2) “lisi”
  13. 3) “zhangsan”
  14. redis 127.0.0.1:6379> sadd tech wangwu liming joe
  15. (integer) 3
  16. redis 127.0.0.1:6379> sadd tech jim
  17. (integer) 1
  18. redis 127.0.0.1:6379> smembers tech
  19. 1) “jim”
  20. 2) “liming”
  21. 3) “wangwu”
  22. 4) “joe”

2、查询

a)smembers

语法:smembers key

解释:获取set中的所有member

  1. redis 127.0.0.1:6379> smembers stu
  2. 1) “wangwu”
  3. 2) “lisi”
  4. 3) “zhangsan”
  5. redis 127.0.0.1:6379> smembers tech
  6. 1) “jim”
  7. 2) “liming”
  8. 3) “wangwu”
  9. 4) “joe”

b)sismember

语法:sismember key member

解释:判断值是否是set的member。如果值是set的member返回1,否则,返回0

  1. redis 127.0.0.1:6379> sismember tech jim #jim 是set的member
  2. (integer) 1
  3. redis 127.0.0.1:6379> sismember tech jim001 #jim001 不是set的member
  4. (integer) 0

c)scard

语法:scard key

解释:返回set的member个数,如果set不存在,返回0

  1. redis 127.0.0.1:6379> scard tech  # tech 存在
  2. (integer) 4
  3. redis 127.0.0.1:6379> scard stud #stud 不存在
  4. (integer) 0
  5. redis 127.0.0.1:6379> scard stu
  6. (integer) 4

d)srandmember

语法:srandmember key

解释:从set中返回一个随机member

  1. redis 127.0.0.1:6379> srandmember stu
  2. “zhangsan”
  3. redis 127.0.0.1:6379> srandmember stu
  4. “zhangsan”
  5. redis 127.0.0.1:6379> srandmember stu
  6. “wangwu”
  7. redis 127.0.0.1:6379> srandmember stu
  8. “zhangsan01”

3、删除

a)spop

语法:spop key

解释:移除并返回一个随机member

  1. redis 127.0.0.1:6379> smembers stu #pop前
  2. 1) “zhangsan01”
  3. 2) “wangwu”
  4. 3) “lisi”
  5. 4) “zhangsan”
  6. redis 127.0.0.1:6379> spop stu  #移除一个随机member
  7. “lisi”
  8. redis 127.0.0.1:6379> smembers stu #pop后
  9. 1) “zhangsan01″<span style=”white-space:pre”>   </span>
  10. 2) “wangwu”
  11. 3) “zhangsan”

b)srem

语法:srem key member [member …]

解释:移除一个或多个member

  1. redis 127.0.0.1:6379> smembers tech
  2. 1) “jim”
  3. 2) “liming”
  4. 3) “wangwu”
  5. 4) “joe”
  6. redis 127.0.0.1:6379> srem tech jim   #移除jim
  7. (integer) 1
  8. redis 127.0.0.1:6379> smembers tech
  9. 1) “liming”
  10. 2) “wangwu”
  11. 3) “joe”
  12. redis 127.0.0.1:6379> srem tech liming joe #移除多个member
  13. (integer) 2
  14. redis 127.0.0.1:6379> smembers tech
  15. 1) “wangwu”

c)smove

语法:smove source destination member

解释:将source中的member移动到destination

  1. redis 127.0.0.1:6379> smembers tech   #smove前
  2. 1) “wangwu”
  3. redis 127.0.0.1:6379> smembers stu
  4. 1) “zhangsan01”
  5. 2) “wangwu”
  6. 3) “zhangsan”
  7. redis 127.0.0.1:6379> smove stu tech zhangsan  #将zhangsan 从stu移动到tech
  8. (integer) 1
  9. redis 127.0.0.1:6379> smembers stu #smove后
  10. 1) “zhangsan01”
  11. 2) “wangwu”
  12. redis 127.0.0.1:6379> smembers tech
  13. 1) “wangwu”
  14. 2) “zhangsan”

4、其他

a)并集

语法:sunion key[key…]

解释:多个set的并集

  1. redis 127.0.0.1:6379> smembers stu
  2. 1) “zhangsan01”
  3. 2) “wangwu”
  4. redis 127.0.0.1:6379> sunion stu
  5. 1) “zhangsan01”
  6. 2) “wangwu”
  7. redis 127.0.0.1:6379> smembers tech
  8. 1) “wangwu”
  9. 2) “zhangsan”
  10. redis 127.0.0.1:6379> sunion stu tech
  11. 1) “zhangsan01”
  12. 2) “wangwu”
  13. 3) “zhangsan”

b)把并集结果存储到set

语法:sunionstore destination key [key …]

解释:求多个set并集,并把结果存储到destination

  1. redis 127.0.0.1:6379> sunionstore same stu tech #把stu tech并集结果存储在union
  2. (integer) 3
  3. redis 127.0.0.1:6379> smembers union
  4. 1) “zhangsan01”
  5. 2) “wangwu”
  6. 3) “zhangsan”

c)交集

语法:sinter key[key…]

解释:多个set的交集

  1. redis 127.0.0.1:6379> smembers stu
  2. 1) “zhangsan01”
  3. 2) “wangwu”
  4. redis 127.0.0.1:6379> smembers tech
  5. 1) “wangwu”
  6. 2) “zhangsan”
  7. redis 127.0.0.1:6379> sinter stu tech
  8. 1) “wangwu”

d)把交集结果存储到指定set

语法:sinterstore destination key [key …]

解释:把多个set的交集结果存储到destination

  1. redis 127.0.0.1:6379> sinterstore inter stu tech
  2. (integer) 1
  3. redis 127.0.0.1:6379> smembers inter
  4. 1) “wangwu”

e) set中在其他set中不存在member

语法:sdiff key[key …]

  1. redis 127.0.0.1:6379>
  2. redis 127.0.0.1:6379> smembers stu
  3. 1) “zhangsan01”
  4. 2) “wangwu”
  5. redis 127.0.0.1:6379> smembers tech
  6. 1) “wangwu”
  7. 2) “zhangsan”
  8. redis 127.0.0.1:6379> sdiff stu tech
  9. 1) “zhangsan01”
  10. redis 127.0.0.1:6379> sdiff tech stu
  11. 1) “zhangsan”

f)把set中在其他set中不存在的member存储到新的set

语法:sdiffstore key[key…]

  1. redis 127.0.0.1:6379> sdiffstore diff stu tech
  2. (integer) 1
  3. redis 127.0.0.1:6379> smembers diff
  4. 1) “zhangsan01”

主要参考:

http://redis.io/commands#set

http://redis.io/topics/data-types

redis info命令详解

以一种易于解释(parse)且易于阅读的格式,返回关于 Redis 服务器的各种信息和统计数值。

通过给定可选的参数 section ,可以让命令只返回某一部分的信息:

  • server : 一般 Redis 服务器信息,包含以下域:

    • redis_version : Redis 服务器版本
    • redis_git_sha1 : Git SHA1
    • redis_git_dirty : Git dirty flag
    • os : Redis 服务器的宿主操作系统
    • arch_bits : 架构(32 或 64 位)
    • multiplexing_api : Redis 所使用的事件处理机制
    • gcc_version : 编译 Redis 时所使用的 GCC 版本
    • process_id : 服务器进程的 PID
    • run_id : Redis 服务器的随机标识符(用于 Sentinel 和集群)
    • tcp_port : TCP/IP 监听端口
    • uptime_in_seconds : 自 Redis 服务器启动以来,经过的秒数
    • uptime_in_days : 自 Redis 服务器启动以来,经过的天数
    • lru_clock : 以分钟为单位进行自增的时钟,用于 LRU 管理
  • clients : 已连接客户端信息,包含以下域:

    • connected_clients : 已连接客户端的数量(不包括通过从属服务器连接的客户端)
    • client_longest_output_list : 当前连接的客户端当中,最长的输出列表
    • client_longest_input_buf : 当前连接的客户端当中,最大输入缓存
    • blocked_clients : 正在等待阻塞命令(BLPOP、BRPOP、BRPOPLPUSH)的客户端的数量
  • memory : 内存信息,包含以下域:

    • used_memory : 由 Redis 分配器分配的内存总量,以字节(byte)为单位
    • used_memory_human : 以人类可读的格式返回 Redis 分配的内存总量
    • used_memory_rss : 从操作系统的角度,返回 Redis 已分配的内存总量(俗称常驻集大小)。这个值和 topps 等命令的输出一致。
    • used_memory_peak : Redis 的内存消耗峰值(以字节为单位)
    • used_memory_peak_human : 以人类可读的格式返回 Redis 的内存消耗峰值
    • used_memory_lua : Lua 引擎所使用的内存大小(以字节为单位)
    • mem_fragmentation_ratio : used_memory_rssused_memory 之间的比率
    • mem_allocator : 在编译时指定的, Redis 所使用的内存分配器。可以是 libc 、 jemalloc 或者 tcmalloc 。
    在理想情况下, used_memory_rss 的值应该只比 used_memory 稍微高一点儿。
    rss > used ,且两者的值相差较大时,表示存在(内部或外部的)内存碎片。
    内存碎片的比率可以通过 mem_fragmentation_ratio 的值看出。
    used > rss 时,表示 Redis 的部分内存被操作系统换出到交换空间了,在这种情况下,操作可能会产生明显的延迟。

    Because Redis does not have control over how its allocations are mapped to memory pages, high used_memory_rss is often the result of a spike in memory usage.

    当 Redis 释放内存时,分配器可能会,也可能不会,将内存返还给操作系统。
    如果 Redis 释放了内存,却没有将内存返还给操作系统,那么 used_memory 的值可能和操作系统显示的 Redis 内存占用并不一致。
    查看 used_memory_peak 的值可以验证这种情况是否发生。
  • persistence : RDBAOF 的相关信息

  • stats : 一般统计信息

  • replication : 主/从复制信息

  • cpu : CPU 计算量统计信息

  • commandstats : Redis 命令统计信息

  • cluster : Redis 集群信息

  • keyspace : 数据库相关的统计信息

除上面给出的这些值以外,参数还可以是下面这两个:

  • all : 返回所有信息
  • default : 返回默认选择的信息

当不带参数直接调用 INFO 命令时,使用 default 作为默认参数。

不同版本的 Redis 可能对返回的一些域进行了增加或删减。

因此,一个健壮的客户端程序在对 INFO 命令的输出进行分析时,应该能够跳过不认识的域,并且妥善地处理丢失不见的域。

可用版本:
>= 1.0.0
时间复杂度:
O(1)
返回值:
具体请参见下面的测试代码。
redis> INFO
# Server
redis_version:2.5.9
redis_git_sha1:473f3090
redis_git_dirty:0
os:Linux 3.3.7-1-ARCH i686
arch_bits:32
multiplexing_api:epoll
gcc_version:4.7.0
process_id:8104
run_id:bc9e20c6f0aac67d0d396ab950940ae4d1479ad1
tcp_port:6379
uptime_in_seconds:7
uptime_in_days:0
lru_clock:1680564

# Clients
connected_clients:1
client_longest_output_list:0
client_biggest_input_buf:0
blocked_clients:0

# Memory
used_memory:439304
used_memory_human:429.01K
used_memory_rss:13897728
used_memory_peak:401776
used_memory_peak_human:392.36K
used_memory_lua:20480
mem_fragmentation_ratio:31.64
mem_allocator:jemalloc-3.0.0

# Persistence
loading:0
rdb_changes_since_last_save:0
rdb_bgsave_in_progress:0
rdb_last_save_time:1338011402
rdb_last_bgsave_status:ok
rdb_last_bgsave_time_sec:-1
rdb_current_bgsave_time_sec:-1
aof_enabled:0
aof_rewrite_in_progress:0
aof_rewrite_scheduled:0
aof_last_rewrite_time_sec:-1
aof_current_rewrite_time_sec:-1

# Stats
total_connections_received:1
total_commands_processed:0
instantaneous_ops_per_sec:0
rejected_connections:0
expired_keys:0
evicted_keys:0
keyspace_hits:0
keyspace_misses:0
pubsub_channels:0
pubsub_patterns:0
latest_fork_usec:0

# Replication
role:master
connected_slaves:0

# CPU
used_cpu_sys:0.03
used_cpu_user:0.01
used_cpu_sys_children:0.00
used_cpu_user_children:0.00

高效运维最佳实践(03):Redis集群技术及Codis实践

专栏介绍

“高效运维最佳实践”是InfoQ在2015年推出的精品专栏,由触控科技运维总监萧田国撰写,InfoQ总编辑崔康策划。

前言

诚如开篇文章所言,高效运维包括管理的专业化和技术的专业化。前两篇我们主要在说些管理相关的内容,本篇说一下技术专业化。希望读者朋友们能适应这个转换,谢谢。

互联网早在几年前就已进入Web 2.0时代,对后台支撑能力的要求,提高了几十倍甚至几百倍。在这个演化过程中,缓存系统扮演了举足轻重的角色。

运维进化到今天,已经不是重复造轮子的时代。所以,我们在架构优化和自动化运维中,可以尽可能地选用优秀的开源产品,而不是自己完全从头再来(各种技术geek除外)。

本文主要讨论Redis集群相关技术及新发展,关于Redis运维等内容,以后另开主题讨论。

本文重点推荐Codis——豌豆荚开源的Redis分布式中间件(该项目于4个月前在GitHub开源,目前star已超过2100)。其和Twemproxy相比,有诸多激动人心的新特性,并支持从Twemproxy无缝迁移至Codis。

本文主要目录如下,对Redis比较了解的朋友,可跳过前两部分,直接欣赏Codis相关内容。

1. Redis常见集群技术
1.1 客户端分片
1.2 代理分片
1.3 Redis Cluster
2. Twemproxy及不足之处
3. Codis实践
3.1 体系架构
3.2 性能对比测试
3.3 使用技巧、注意事项

好吧我们正式开始。

1. Redis常见集群技术

长期以来,Redis本身仅支持单实例,内存一般最多10~20GB。这无法支撑大型线上业务系统的需求。而且也造成资源的利用率过低——毕竟现在服务器内存动辄100~200GB。

为解决单机承载能力不足的问题,各大互联网企业纷纷出手,“自助式”地实现了集群机制。在这些非官方集群解决方案中,物理上把数据“分片”(sharding)存储在多个Redis实例,一般情况下,每一“片”是一个Redis实例。

包括官方近期推出的Redis Cluster,Redis集群有三种实现机制,分别介绍如下,希望对大家选型有所帮助。

1.1 客户端分片

这种方案将分片工作放在业务程序端,程序代码根据预先设置的路由规则,直接对多个Redis实例进行分布式访问。这样的好处是,不依赖于第三方分布式中间件,实现方法和代码都自己掌控,可随时调整,不用担心踩到坑。

这实际上是一种静态分片技术。Redis实例的增减,都得手工调整分片程序。基于此分片机制的开源产品,现在仍不多见。

这种分片机制的性能比代理式更好(少了一个中间分发环节)。但缺点是升级麻烦,对研发人员的个人依赖性强——需要有较强的程序开发能力做后盾。如果主力程序员离职,可能新的负责人,会选择重写一遍。

所以,这种方式下,可运维性较差。出现故障,定位和解决都得研发和运维配合着解决,故障时间变长。

这种方案,难以进行标准化运维,不太适合中小公司(除非有足够的DevOPS)。

1.2 代理分片

这种方案,将分片工作交给专门的代理程序来做。代理程序接收到来自业务程序的数据请求,根据路由规则,将这些请求分发给正确的Redis实例并返回给业务程序。

这种机制下,一般会选用第三方代理程序(而不是自己研发),因为后端有多个Redis实例,所以这类程序又称为分布式中间件。

这样的好处是,业务程序不用关心后端Redis实例,运维起来也方便。虽然会因此带来些性能损耗,但对于Redis这种内存读写型应用,相对而言是能容忍的。

这是我们推荐的集群实现方案。像基于该机制的开源产品Twemproxy,便是其中代表之一,应用非常广泛。

1.3 Redis Cluster

在这种机制下,没有中心节点(和代理模式的重要不同之处)。所以,一切开心和不开心的事情,都将基于此而展开。

Redis Cluster将所有Key映射到16384个Slot中,集群中每个Redis实例负责一部分,业务程序通过集成的Redis Cluster客户端进行操作。客户端可以向任一实例发出请求,如果所需数据不在该实例中,则该实例引导客户端自动去对应实例读写数据。

Redis Cluster的成员管理(节点名称、IP、端口、状态、角色)等,都通过节点之间两两通讯,定期交换并更新。

由此可见,这是一种非常“重”的方案。已经不是Redis单实例的“简单、可依赖”了。可能这也是延期多年之后,才近期发布的原因之一。

这令人想起一段历史。因为Memcache不支持持久化,所以有人写了一个Membase,后来改名叫Couchbase,说是支持Auto Rebalance,好几年了,至今都没多少家公司在使用。

这是个令人忧心忡忡的方案。为解决仲裁等集群管理的问题,Oracle RAC还会使用存储设备的一块空间。而Redis Cluster,是一种完全的去中心化……

本方案目前不推荐使用,从了解的情况来看,线上业务的实际应用也并不多见。

2. Twemproxy及不足之处

Twemproxy是一种代理分片机制,由Twitter开源。Twemproxy作为代理,可接受来自多个程序的访问,按照路由规则,转发给后台的各个Redis服务器,再原路返回。

这个方案顺理成章地解决了单个Redis实例承载能力的问题。当然,Twemproxy本身也是单点,需要用Keepalived做高可用方案。

我想很多人都应该感谢Twemproxy,这么些年来,应用范围最广、稳定性最高、最久经考验的分布式中间件,应该就是它了。只是,他还有诸多不方便之处。

Twemproxy最大的痛点在于,无法平滑地扩容/缩容。

这样导致运维同学非常痛苦:业务量突增,需增加Redis服务器;业务量萎缩,需要减少Redis服务器。但对Twemproxy而言,基本上都很难操作(那是一种锥心的、纠结的痛……)。

或者说,Twemproxy更加像服务器端静态sharding。有时为了规避业务量突增导致的扩容需求,甚至被迫新开一个基于Twemproxy的Redis集群。

Twemproxy另一个痛点是,运维不友好,甚至没有控制面板。

Codis刚好击中Twemproxy的这两大痛点,并且提供诸多其他令人激赏的特性。

3. Codis实践

Codis由豌豆荚于2014年11月开源,基于Go和C开发,是近期涌现的、国人开发的优秀开源软件之一。现已广泛用于豌豆荚的各种Redis业务场景(已得到豌豆荚@刘奇同学的确认,呵呵)。

从3个月的各种压力测试来看,稳定性符合高效运维的要求。性能更是改善很多,最初比Twemproxy慢20%;现在比Twemproxy快近100%(条件:多实例,一般Value长度)。

3.1 体系架构

Codis引入了Group的概念,每个Group包括1个Redis Master及至少1个Redis Slave,这是和Twemproxy的区别之一。这样做的好处是,如果当前Master有问题,则运维人员可通过Dashboard“自助式”切换到Slave,而不需要小心翼翼地修改程序配置文件。

为支持数据热迁移(Auto Rebalance),出品方修改了Redis Server源码,并称之为Codis Server。

Codis采用预先分片(Pre-Sharding)机制,事先规定好了,分成1024个slots(也就是说,最多能支持后端1024个Codis Server),这些路由信息保存在ZooKeeper中。

ZooKeeper还维护Codis Server Group信息,并提供分布式锁等服务。

3.2 性能对比测试

Codis目前仍被精益求精地改进中。其性能,从最初的比Twemproxy慢20%(虽然这对于内存型应用而言,并不明显),到现在远远超过Twemproxy性能(一定条件下)。

我们进行了长达3个月的测试。测试基于redis-benchmark,分别针对Codis和Twemproxy,测试Value长度从16B~10MB时的性能和稳定性,并进行多轮测试。

一共有4台物理服务器参与测试,其中一台分别部署codis和twemproxy,另外三台分别部署codis server和redis server,以形成两个集群。

从测试结果来看,就Set操作而言,在Value长度<888B时,Codis性能优越优于Twemproxy(这在一般业务的Value长度范围之内)。

就Get操作而言,Codis性能一直优于Twemproxy。

3.3 使用技巧、注意事项

Codis还有很多好玩的东东,从实际使用来看,有些地方也值得注意。

1)无缝迁移Twemproxy

出品方贴心地准备了Codis-port工具。通过它,可以实时地同步 Twemproxy 底下的 Redis 数据到你的 Codis 集群。同步完成后,只需修改一下程序配置文件,将 Twemproxy 的地址改成 Codis 的地址即可。是的,只需要做这么多。

2)支持Java程序的HA

Codis提供一个Java客户端,并称之为Jodis(名字很酷,是吧?)。这样,如果单个Codis Proxy宕掉,Jodis自动发现,并自动规避之,使得业务不受影响(真的很酷!)。

3)支持Pipeline

Pipeline使得客户端可以发出一批请求,并一次性获得这批请求的返回结果。这提升了Codis的想象空间。

从实际测试来看,在Value长度小于888B字节时,Set性能迅猛提升;

Get性能亦复如是。

4)Codis不负责主从同步

也就是说, Codis仅负责维护当前Redis Server列表,由运维人员自己去保证主从数据的一致性。

这是我最赞赏的地方之一。这样的好处是,没把Codis搞得那么重。也是我们敢于放手在线上环境中上线的原因之一。

5)对Codis的后续期待?

好吧,粗浅地说两个。希望Codis不要变得太重。另外,加pipeline参数后,Value长度如果较大,性能反而比Twemproxy要低一些,希望能有改善(我们多轮压测结果都如此)。

因篇幅有限,源码分析不在此展开。另外Codis源码、体系结构及FAQ,参见如下链接:https://github.com/wandoulabs/codis

PS:线上文档的可读性,也是相当值得称赞的地方。一句话:很走心,赞!

最后,Redis初学者请参考这个链接:http://www.gamecbg.com/bc/db/redis/13852.html,文字浅显易懂,而且比较全面。

本文得到Codis开发团队刘奇和黄东旭同学的大力协助,并得到Tim Yang老师等朋友们在内容把控方面的指导。本文共同作者为赵文华同学,他主要负责Codis及Twemproxy的对比测试。在此一并谢过。

关于作者

萧田国,男,硕士毕业于北京科技大学,ACMUG核心成员,目前为触控科技运维负责人。拥有十多年运维及团队管理经验。先后就职于联想集团(Oracle数据库主管)、搜狐畅游(数据库主管)、智明星通及世纪互联等。从1999年开始,折腾各种数据库如Oracle/MySQL/MS SQL Server/NoSQL等,兼任数据库培训讲师若干年。

曾经的云计算行业从业者,现在喜欢琢磨云计算及评测、云端数据库,及新技术在运维中的应用。主张管理学科和运维体系的融合、人性化运维管理,打造高效、专业运维团队。

近来有时参加一些大小技术会议,做做演讲嘉宾或主持人(有空找我来玩呀:)我的个人微信号:xiaotianguo。如需更多了解,请百度“萧田国”。

另外,我也有微信公众号叻,微信搜索“开心南瓜by萧田国”或扫描如下二维码,和我进行微信互动。公众号里将有我原创的各类技术和非技术文章,及我所喜欢的文章/帖子。一起来吧~


感谢崔康对本文的策划,丁晓昀对本文的审校。

给InfoQ中文站投稿或者参与内容翻译工作,请邮件至editors@cn.infoq.com。也欢迎大家通过新浪微博(@InfoQ@丁晓昀),微信(微信号:InfoQChina)关注我们,并与我们的编辑和其他读者朋友交流。