Redis是基于单线程模型实现的,也就是Redis是使用一个线程来处理全部的客户端请求的,尽管Redis使用了非阻塞式IO,而且对各类命令都作了优化(大部分命令操做时间复杂度都是O(1)),但因为Redis是单线程执行的特色,所以它对性能的要求更加苛刻。java
键值对的长度是和性能成反比的,好比咱们来作一组写入数据的性能测试,执行结果以下。
从以上数据能够看出,在key不变的状况下,value值越大操做效率越慢,由于Redis对于同一种数据类型会使用不一样的内部编码进行存储,好比字符串的内部编码就有三种,int(整数编码)、raw(优化内存分配的字符串编码)、embstr(动态字符串编码),这是由于Redis的做者是想经过不一样的编码实现效率和空间的平衡,然而数据量越大使用的内部编码就越复杂,而越是复杂的内部编码存储的性能就越低。web
这还只是写入时的速度,当键值对内容较大时,还会带来另外几个问题:redis
所以在保存完整语义的同时,咱们要尽可能的缩短键值对的存储长度,必要时要对数据进行序列化和压缩再存储,以Java为例,序列化咱们可使用protostuff
或kryo
,压缩咱们可使用snappy
。数据库
lazy free特性是Redis 4.0新增的一个很是实用的功能,它能够理解为惰性删除或延迟删除。意思是在删除的时候提供异步延时释放键值的功能,把键值释放操做放在BIO(Background I/O)单独的子线程处理中,以减小删除对Redis主线程的阻塞,能够有效地避免删除big key时带来的性能和可用性问题。缓存
lazy free对应了4中场景,默认都是关闭的。安全
lazyfree-lazy-eviction no lazyfree-lazy-expire no lazyfree-lazy-server-del no slave-laze-flush no
它们表明的含义以下:bash
lazyfree-lazy-eviction:
表示当Redis运行内存超过maxmemory时,是否开启lazy free机制删除;lazyfree-lazy-expire
:表示设置了过时时间的键值,当过时以后是否开启lazy free机制;lazyfree-lazy-server-del
:有些指令在处理已存在键时,会带一个隐式的del键的操做,好比rename命令,当目标键已存在,Redis会先删除目标键,若是这些目标键是一个big key,就会形成阻塞删除的问题,此配置表示在这种场景中是否开始lazy free机制删除;slave-lazy-flush
:针对slave(从节点)进行全量数据同步,salve在加载master的RDB文件前,会运行flushall来清理本身的数据,它表示此时是否开启lazy free机制删除。建议开启其中的lazyfree-lazy-eviction、lazyfree-lazy-expire、lazyfree-lazy-server-del等配置,这样就能够有效的提升主线程的执行效率。服务器
咱们应该根据实际的业务状况,对键值设置合理的过时时间,这样Redis会帮你自动清理过时的键值对,以节约对内存的占用,以免键值过多的堆积,频繁的触发内存淘汰策略。网络
Redis绝大多数读写命令的时间复杂度都在O(1)到O(N)之间,在官方文档对每一个命令都有时间复杂度说明,其中O(1)表示能够安全使用的,而O(N)就应该小心了,N表示不肯定,数据越大查询的速度可能会越慢。由于Redis只用一个线程来作数据查询,若是这些指令耗时长,就会阻塞Redis,形成大量耗时。架构
要避免O(N)命令对Redis形成影响,能够从如下几个方面入手改造:
咱们可使用slowlog功能找出最耗时的Redis命令进行相关的优化,以提高Redis的运行速度,慢查询有两个重要的配置项:
slowlog-log-slower-than
:用于设置慢查询的评定时间,也就是说超过此配置项的命令,将会被当成慢操做记录在慢查询日志中,它执行单位是微秒;slowlog-max-len
:用来配置慢查询日志的最大记录数 。咱们能够根据实际的业务状况进行相应的配置,其中慢查询是按照插入的顺序倒序存入慢查询日志中,咱们可使用slowlog get n
来获取相关的慢查询日志,再找到这些慢查询对应的业务进行相关的优化。
Pipeline(管道技术)
是客户端提供的一种批处理技术,用于一次处理多个Redis命令,从而提升整个交互的性能。
咱们使用Java代码来测试一下Pipeline和普通操做的性能对比,Pipeline的测试代码以下:
publicclass PipelineExample { public static void main(String[] args) { Jedis jedis = new Jedis("127.0.0.1", 6379); // 记录执行开始时间 long beginTime = System.currentTimeMillis(); // 获取 Pipeline 对象 Pipeline pipe = jedis.pipelined(); // 设置多个 Redis 命令 for (int i = 0; i < 100; i++) { pipe.set("key" + i, "val" + i); pipe.del("key"+i); } // 执行命令 pipe.sync(); // 记录执行结束时间 long endTime = System.currentTimeMillis(); System.out.println("执行耗时:" + (endTime - beginTime) + "毫秒"); } }
以上程序直接结果为:
执行耗时:297毫秒
普通的操做代码以下:
publicclass PipelineExample { public static void main(String[] args) { Jedis jedis = new Jedis("127.0.0.1", 6379); // 记录执行开始时间 long beginTime = System.currentTimeMillis(); for (int i = 0; i < 100; i++) { jedis.set("key" + i, "val" + i); jedis.del("key"+i); } // 记录执行结束时间 long endTime = System.currentTimeMillis(); System.out.println("执行耗时:" + (endTime - beginTime) + "毫秒"); } }
以上程序的执行结果为:
执行耗时:17276毫秒
从以上的结果能够看出,管道的执行时间是297毫秒,而普通命令执行时间是17276毫秒,管道技术要比普通的执行大约快了58倍。
Redis过时键值删除使用的是贪心策略,它每秒会进行10次过时扫描,此配置可在redis.conf进行配置,默认是hz 10
,Redis会随机抽取20个值,删除这20个键中过时的键,若是过时key的比例超过25%,重复执行此流程,以下图所示:
若是在大型系统中有大量缓存在同一时间同时过时,那么会致使Redis循环屡次持续扫描过时字典,直到过时字典中过时键值被删除的比较稀疏为止,而在整个执行过程当中会致使Redis的读写出现明显的卡顿,卡顿的另外一种缘由是内存管理器须要频繁回收内存页,所以也会消耗必定的CPU。
为了不这种卡顿现象的产生,咱们须要预防大量的缓存在同一时刻一块儿过时,最简单的解决方案就是在过时时间的基础上添加一个指定范围的随机数。
在客户端的使用上咱们除了要尽可能使用Pipeline的技术外,还须要注意尽可能使用Redis链接池,而不是频繁建立销毁Redis链接,这样就能够减小网络传输次数和减小了非必要调用指令。
在64位操做系统中Redis的内存大小是没有限制的,也就是配置项maxmemory <bytes>
是被注释掉的,这样就会致使在物理内存不足时,使用swap空间既交换空间,而当操做系统将Redis所用的内存分页移至swap空间时,将会阻塞Redis进程,致使Redis出现延迟,从而影响Redis的总体性能。所以咱们须要限制Redis的内存大小为一个固定的值,当Redis的运行到达此值时会触发内存淘汰策略,内存淘汰策略在Redis 4.0以后有8种:
noeviction
:不淘汰任何数据,当内存不足时,新增操做会报错,Redis默认内存淘汰策略;allkeys-lru
:淘汰整个键值中最久未使用的键值;allkeys-random
:随机淘汰任意键值;volatile-lru
:淘汰全部设置了过时时间的键值中最久未使用的键值;volatile-random
:随机淘汰设置了过时时间的任意键值;volatile-ttl
:优先淘汰更早过时的键值。在Redis 4.0版本中有新增了2种淘汰策略:
volatile-lfu
:淘汰全部设置了过时时间的键值中,最少使用的键值;allkeys-lfu
:淘汰整个键值中最少使用的键值。其中allkeys-xxx表示从全部的键值中淘汰数据,而volatile-xxx表示从设置了过时时间的键值中淘汰数据。
咱们能够根据实际的业务状况进行设置,默认的淘汰策略不淘汰任何数据,在新增时会报错。
在虚拟机中运行Redis服务器,由于和物理机共享一个物理网口,而且一台物理机可能有多个虚拟机在运行,所以在内存占用上和网络延迟方面都会有很糟糕的表现,咱们能够经过 ./redis-cli --intrinsic-latency 100
命令查看延迟时间,若是对Redis的性能有较高要求的话,应尽量在物理机上直接部署Redis服务器。
Redis的持久化策略是将内存数据复制到硬盘上,这样才能够进行容灾恢复或者数据迁移,但维护此持久化的功能,须要很大的性能开销。
在Redis 4.0以后,Redis有3种持久化的方式:
RDB(Redis Database,快照方式)
将某一个时刻的内存数据,以二进制的方式写入磁盘;AOF(Append Only File,文件追加方式)
,记录全部的操做命令,并以文本的形式追加到文件中;混合持久化方式
,Redis 4.0以后新增的方式,混合持久化是结合了RDB和AOF的优势,在写入的时候,先把当前的数据以RDB的形式写入文件,再将后续的操做命令以AOF的格式存入文件,这样既能保证Redis重启时的速度,又能减低数据丢失的风险。RDB和AOF持久化各有利弊,RDB可能会致使必定时间内的数据丢失,而AOF因为文件较大则会影响Redis的启动速度,为了能同时拥有RDB和AOF的优势,Redis 4.0以后新增了混合持久化的方式,所以咱们在必需要进行持久化操做时,应该选择混合持久化的方式。
查询是否开启混合持久化可使用config get aof-use-rdb-preamble
命令,执行结果以下图所示:
其中yes表示已经开启混合持久化,no表示关闭,Redis 5.0默认值为yes。若是是其余版本首先须要检查下是否已经开启了混合持久化,若是关闭的状况下,能够经过如下两种方式开启:
(1)经过命令行开启
使用命令config set aof-use-rdb-preamble yes
,执行结果以下:
命令设置配置的缺点是重启Redis服务以后,设置的配置就会失效。
(2)经过修改redis配置文件开启
在Redis的根路径下找到redis.conf文件,把配置文件中的aof-use-rdb-preamble no
改成aof-use-rdb-preamble yes
,以下图所示:
配置完成以后,须要重启Redis服务器才能生效,但修改配置文件的方式,在每次重启Redis服务器以后,配置信息不会丢失。
须要注意的是,在非必须进行持久化的业务中,能够关闭持久化,能够有效提高Redis的运行速度,不会出现间歇性卡顿的现象。
Linux kernel在2.6.38内核增长了Transparent Huge Pages(THP)特性,支持大内存页2MB分配,默认开启。
当开启了THP时,fork的速度会变慢,fork以后每一个内存页从原来4KB变为2MB,会大幅增长重写期间父进程内存消耗。同时每次写命令引发的复制内存页单位放大了512倍,会拖慢写操做的执行时间,致使大量写操做慢查询。例如简单的incr命令也会出如今慢查询中,所以Redis建议将此特性进行禁用,禁用方法以下:
echo nerver > /sys/kernel/mm/transparent_hugepage/enabled
为了使机器重启后THP配置依然生效,能够在/ect/rc.local中追加 echo nerver > /sys/kernel/mm/transparent_hugepage/enabled
。
Redis分布式架构有三个重要的手段:
使用主从同步功能咱们能够把写入放到主库上执行,把读功能转移到从库上,所以就能够在单位时间内处理更多的请求,从而提高Redis总体的运行速度。
而哨兵模式是对于主从功能的升级,但当主节点崩溃后,无需人工干预就能自动恢复Redis的正常使用。
Redis Cluster是Redis 3.0以后正式推出的,Redis集群是经过将数据库分散存储到多个节点上来平衡各个节点的负载压力。
Redis Cluster采用虚拟哈希槽分区,全部的键根据哈希函数映射到0~16383整数槽内,计算公式:slot = CRC16(key)&16383
,每个节点负责维护一部分槽以及槽所映射的键值数据。这样Redis就能够把读写压力从一台服务器,分散给多台服务器了,所以性能会有很大的提高。
在这三个功能中,咱们只须要使用一个就好了,毫无疑问Redis Cluster应该是首选的实现方案,他能够把读写压力自动的分担给更多的服务器,而且拥有自动容灾的能力。