昨天被公司领导批评了。我在一家单身红娘互联网公司工作,双十一那天,我发起了一个下单送女朋友的活动。谁能想到,凌晨12点过后,用户激增,技术故障,用户无法下单。当时,老板怒了!查找后发现redis报Couldnotgetaresourcefromthepool。获取不到连接资源,集群中单个Redis的连接数高。于是各种修改最大连接数和等待连接数,虽然报错频率有所缓解,但是还是继续报错。后来经过离线测试,发现Redis中存储的字符数据非常大,平均1s返回数据。?能否分享一下Redis的使用规范?我要做一个只有速度快的真正的男人!为什么Redis这么快?这篇文章我们知道Redis在高性能和节省内存方面下了很大功夫。所以,只有规范的使用Redis,才能达到高性能和节省内存,否则Redis是受不了我们的。Redis的使用规范围绕着以下纬度展开:键值对使用规范命令使用规范数据存储规范运维规范键值对使用规范有两点需要注意:只有好的键命名才能提供可读和可维护的键,方便定位问题和查找资料。Values应该避免bigkeys,选择高效的序列化和压缩,使用对象共享池,选择高效合适的数据类型(参考《Redis 实战篇:巧用数据类型实现亿级数据统计》)。按键命名规范标准的按键命名,遇到问题可以轻松定位。Redis属于没有Scheme的NoSQL数据库。因此,需要依赖规范来建立其Scheme语义。比如我们根据不同的场景创建不同的数据库。敲黑板,以“业务模块名称”为前缀(如数据库方案),以“冒号”隔开,再加上“具体业务名称”。这样我们就可以通过key前缀来区分不同的业务数据,一目了然。总结一下:“业务名:表名:id”比如我们要统计属于技术类型公众号的“Java技术栈”博主的文章数。set公众号:技术分类:Java技术栈100000?key太长会不会有问题?key是字符串,底层数据结构是SDS。SDS结构将包含元数据信息,例如字符串的长度和已分配空间的大小。随着字符串长度的增加,SDS的元数据也会占用更多的内存空间。所以当字符串过长时,我们可以使用适当的缩写形式。不要用bigkey?被骗了,导致报错,无法连接。因为Redis是单线程执行读写指令,如果有bigkey读写操作,会阻塞线程,降低Redis的处理效率。bigkey包括两种情况:键值对的值很大,比如值保存了2MB的String数据;键值对的值是一个元素很多的集合类型,比如一个保存了5万个元素的List集合。虽然Redis官方解释了key和string类型的值限制是512MB。整理了最新的Redis面试题。点击Java面试题库小程序,在线刷新题库。为防止网卡流量和慢查询,string类型控制在10KB以内,hash、list、set、zset元素个数不要超过5000个。?业务数据这么大怎么办?例如,杰作《XXX》得以保存。我们还可以通过gzip数据压缩来减小数据大小:/***使用gzip压缩字符串*/publicstaticStringcompress(Stringstr){if(str==null||str.length()==0){returnstr;}尝试(ByteArrayOutputStreamout=newByteArrayOutputStream();GZIPOutputStreamgzip=newGZIPOutputStream(out)){gzip.write(str.getBytes());}catch(IOExceptione){e.printStackTrace();}returnnewsun.misc.BASE64Encoder().encode(out.toByteArray());}/***使用gzip解压*/publicstaticStringuncompress(StringcompressedStr){if(compressedStr==null||compressedStr.length()==0){returncompressedStr;}byte[]compressed=newsun.misc.BASE64Decoder().decodeBuffer(compressedStr);;Stringdecompressed=null;try(ByteArrayOutputStreamout=newByteArrayOutputStream();ByteArrayInputStreamin=newByteArrayInputStream(compressed);GZIPInputStreamginzip=newGZIPInputStream=newbyte2]er1uffte2[4]er0[0][;intoffset=-1;while((offset=ginzip.read(buffer))!=-1){out.write(buffer,0,offset);}decompressed=out.toString();}catch(IOExceptione){e.printStackTrace();}关于turndecompressed;}集合类型如果是集合类型确实有很多元素。我们可以将一个大集合拆分成多个小集合来保存。使用高效的序列化和压缩方法来节省内存。我们可以使用高效的序列化方法和压缩方法来减小值的大小。protostuff和kryo这两种序列化方式比Java自带的序列化方式效率更高。以上两种序列化方式虽然节省内存,但是序列化后都是二进制数据,可读性太差。通常我们会把它序列化成JSON或者XML。为了避免数据占用较大的空间,我们可以使用压缩工具(snappy、gzip)将数据压缩后存储到Redis中。使用整数对象的共享池Redis内部维护了10000个整数对象,范围从0到9999,并将这些整数作为一个共享池。即使大量键值对存储0~9999范围内的整数,在Redis实例中,实际只存储一个整数对象,可以节省内存空间。需要注意的是,有两种情况不生效:1、如果Redis中设置了maxmemory,并且启用了LRU策略(allkeys-lru或volatile-lru策略),那么整型对象的共享池是不能被访问的用过的。?这是因为LRU需要统计每个键值对的使用时间,如果不同的键值对复用了一个整型对象,则无法统计。2.如果collection类型的数据是ziplist编码的,collection元素是整型,这有时,sharedpool是不能用的。?因为ziplist使用了紧凑的内存结构,判断整型对象的共享效率低下。命令使用说明有些命令的执行会造成很大的性能问题,需要特别注意。生产中禁用的指令Redis是单线程处理请求操作。如果我们执行一些操作量大、耗时长的命令,就会严重阻塞主线程,导致其他请求无法正常处理。KEYS:该命令需要对Redis的全局哈希表进行全表扫描,严重阻塞Redis主线程;?应该改用SCAN,批量返回符合条件的键值对,避免阻塞主线程。FLUSHALL:删除Redis实例如果数据量很大,会严重阻塞Redis的主线程;FLUSHDB,删除当前数据库中的数据,如果数据量大,也会阻塞Redis的主线程。?添加ASYNC选项,让FLUSHALL和FLUSHDB异步执行。我们也可以直接禁用,在配置文件中使用rename-command命令重命名这些命令,这样客户端就不能使用这些命令了。请谨慎使用MONITOR命令。MONITOR命令会不断地将监控的内容写入输出缓冲区。如果在线命令操作较多,输出缓冲区很快就会溢出,影响Redis的性能,甚至导致服务崩溃。因此,除非监控非常必要,否则我们只使用某些命令的执行(例如Redis性能突然变慢,我们想看看客户端执行了哪些命令)。慎用全操作命令,如获取集合中所有元素(HASH类型的hgetall、List类型的hgetalllrange、Set类型smembers、zrange等命令)。这些操作会扫描整个底层数据结构,导致Redis主线程阻塞。?如果业务场景需要全数据采集怎么办?解决方法有两种:使用SSCAN、HSCAN等命令批量返回采集数据;将大集合拆分成小集合,比如按时间、地区划分。数据存储规范冷热数据分离Redis虽然支持使用RDB快照和AOF日志来持久化数据,但这两种机制只是为了提供数据的可靠性保障,并不是为了扩展数据容量。不要将所有数据都存储在Redis中,而是将热点数据存储为缓存,这样可以充分利用Redis的高性能特性,利用宝贵的内存资源为热点数据服务。业务数据隔离不要把不相关的数据服务放在一个Redis中。一方面避免了业务交互,另一方面避免了单实例的扩容,在发生故障时可以减少影响面并快速恢复。设置过期时间在保存数据的时候,我建议大家根据业务使用数据的时间长短来设置数据的过期时间。写入Redis的数据会一直占用内存。如果数据继续增加,可能会达到机器内存的上限,导致内存溢出,导致服务崩溃。控制单实例的内存容量建议设置为2~6GB。这样无论是RDB快照还是主从集群进行数据同步,都可以快速完成,不会阻塞正常请求的处理。防止缓存雪崩避免集中过期键导致缓存雪崩。?什么是缓存雪崩?当某个时刻发生大规模的缓存失效时,会导致大量的请求直接打到数据库上,给数据库造成巨大的压力。如果是在高并发下,可能会瞬间导致数据库宕机。运维规范使用Cluster集群或Sentinel集群实现高可用;实例设置最大连接数,防止客户端连接过多导致实例负载过大,影响性能。不开启AOF或者开启AOF并配置为每秒刷新一次磁盘,避免磁盘IO拖慢Redis性能。设置合理的repl-backlog,减少主从全同步的概率,设置合理的slaveclient-output-buffer-limit,避免主从复制中断。根据实际场景适当设置内存淘汰策略。使用连接池操作Redis。
