本文从实际工做中方案选型出发,以Redis的特性为着眼点,逐层剥开Redis技术内幕,并对工做中容易出现的使用误区进行了总结。redis
对于有状态的服务而言,数据库每每会成为系统的瓶颈所在。在用户活跃的高峰期,或者因为PUSH、活动等引起的请求突增,都会给后端的数据库形成巨大的压力。数据库
由存储系统的特性咱们知道,从内存读一个数据,比从通常的磁盘读要快10000倍左右,基于这样的缘由,数据库自己也会有必定的内存cache。可是当热数据集比较大的时候,本地cache会频繁淘汰,此时会触发大量磁盘IO,性能急剧降低,每每也会伴随有大量的慢日志。另外,有些数据是须要经过复杂的查询或计算后获得且又不会频繁变化的。后端
虽然说数据库能够经过读写分离来扩展读的能力,但存在增长slave实例的成本、主从延迟致使数据不一致等问题。子曾经曰过,“计算机科学领域的任何问题能够经过增长一个中间层来解决”,因而咱们考虑在系统中再增长一个cache层,这里暂不讨论cache的设计实现。数组
须要cache的热点数据缓存
基于以上数据的特色,咱们最终选择了redis。服务器
Redis is an open source (BSD licensed), in-memory data structure store, used as a database, cache and message broker. It supports data structures such as strings, hashes, lists, sets, sorted sets with range queries, bitmaps, hyperloglogs, geospatial indexes with radius queries and streams. Redis has built-in replication, Lua scripting, LRU eviction, transactions and different levels of on-disk persistence, and provides high availability via Redis Sentinel and automatic partitioning with Redis Cluster.网络
以上引自官网的原话,归纳起来有这样一些特性:数据结构
虽然是单进程单线程模型,可是读写性能很是优异,单机可支持10wQPS,缘由主要有如下几点:多线程
固然这种单线程事件机制也是有缺陷的,因为全部的事件都是串行执行,一旦某个事件比较重就会阻塞其它事件,从而致使整个系统的吞吐率降低。好比某个客户端执行了一个比较重的lua函数、或者使用了诸如keys*、zrange(0,-1)、hgetall等全集合扫描的操做,又或者删除的过时键是个big key,又或者使用了较多内存的redis实例进行bgsave时,都会致使服务器必定程度的阻塞,通常伴随会有相应的慢日志。因此咱们在实际使用redis的过程当中,必需要给每一次的操做分配合理的时间片。架构
对于内存型数据库,好比redis和memcache,若是数据状态不落盘,一旦服务器进程退出,那么这些数据状态也就会所有消失不见。数据状态的重建须要从后端数据库回源,这会给后端数据库形成很是大的压力,最坏的状况可能会把数据库压垮,致使服务不可用。
为了解决这个问题,Redis提供了RDB和AOF两种持久化方式。前者会生成一分内存快照--RDB文件,该文件是通过压缩的二进制格式,记录的是键值对数据;后者则是以Redis的命令请求协议格式来保存,记录的是命令操做;
因为RDB SAVE和AOF重写会阻塞主线程,因此都支持BG模式执行,至于持久化的具体实现这里就不展开讨论了。
比较巧妙的是,Redis并无使用固定的数据结构来存储各类类型的数据,而是建立了一套对象系统,对于同一个对象,能够对应一个或多个不一样的底层数据结构(或者叫作编码方式),某些特定的编码方式在时空间的效率上有所优化,经过执行"Object Encoding"能够查询当前编码方式。
Redis的高可用,主要经过主从复制机制以及Sentinel集群来实现。
当从服务器有2个或者多个时,Redis的主从架构能够有两种形式。一种是,全部的从服务器直接挂在主服务器上,这种模式的优势是,全部从服务器复制的延迟相对较低,而缺点在于加大了主服务器的复制压力;另外一种形式,是采用级联的方式,S1从M复制,S2从S1复制,以此类推,这种模式的优势是,将主服务器的复制压力分摊到多个服务器上,而缺点在于越处于级联下游的从实例,复制延迟就越大。
从主从复制模式能够看出,Redis的数据只能保证最终一致,不能保证强一致性。
读扩展,基于主从架构,能够很好的平行扩展读的能力。写扩展,主要受限于主服务器的硬件资源的限制,一是单个实例内存容量受限,二是一个实例只使用到CPU一个核。下面讨论基于多套主从架构Redis实例的集群实现,目前主要有如下几种方案:
Redis的key是string类型,最大能够是512MB,那么实际中是否是也能够这样用呢?答案是否认的,redis将key保存在一个全局的hashtable,若是key过大,一是占用过多的内存,二是计算hash和字符串比较都会更耗时;通常建议key的大小不超过2kB。
或者说是big value,这会致使删除key的操做比较耗时,会阻塞主线程。好比有些同窗喜欢用集合类的对象,动辄上百万的元素。对于这类超大集合,通常有两种优化方案,一是采起分片的方式,将每一个集合分片控制在较小的范围内,好比小于1000个元素;二是起一个异步任务,对集合中的元素分批进行老化。
好比在业务代码使用了keys*,hgetall,zrange(0, -1)等返回集合中全部元素,这些都属于阻塞操做,通常考虑用scan,hscan等迭代操做代替。
内存过大有什么问题呢?上文中在讲到持久化的时候其实有说到,不管是生成RDB文件,仍是AOF重写,都是要对整个实例的内存数据进行扫描,很是消耗CPU和磁盘资源;当使用Backgroud方式建立子进程时也会涉及到内存空间的拷贝,即使使用了COW机制,也会占用至关的内存开销。另外,在主从复制的第一阶段,save、传输和加载RDB文件的开销,也会随着RDB文件的变大而变大。当单个实例达到瓶颈时,更好的解决方案应该是采用集群方案。
redis删除过时键采用了惰性删除和按期删除相结合的策略,惰性删除则是在每次GET/SET操做时去删,按期删除,则是在时间事件中,从整个key空间随机取样,直到过时键比率小于25%,若是同时有大量key过时的话,很可能致使主线程阻塞。通常能够经过作散列来优化处理。