当前位置: 首页 > 后端技术 > Java

Redis很笨,不明白使用规范就毁了

时间:2023-04-01 20:29:36 Java

这可能是最中肯的Redis使用规范了。码哥,我昨天被公司领导批评了。我在一家单身红娘互联网公司工作,双十一那天,我发起了一个下单送女朋友的活动。谁能想到,凌晨12点过后,用户激增,技术故障,用户无法下单。当时,老板怒了!查找后发现redis报Couldnotgetaresourcefromthepool。获取不到连接资源,集群中单个Redis的连接数高。于是各种修改最大连接数和等待连接数,虽然报错频率有所缓解,但是还是继续报错。后来经过离线测试,发现Redis中存储的字符数据非常大,平均1s返回数据。代码兄,能分享下Redis的使用规范吗?我要做一个只有速度快的真正的男人!为什么Redis这么快?在这篇文章中,我们知道Redis在高性能和节省内存方面下了很大的功夫。因此,只有规范地使用Redis,才能达到高性能和节省内存的目的。不然Redis再烂,我们也买不起。Redis的使用规范围绕着以下纬度展开:键值对使用规范;命令使用规范;数据存储规范;操作和维护规范。使用键值对有两点需要注意:好的键命名可以提供一个可读性强、可维护性高的键,便于定位问题和查找数据。Value要避免bigkey,选择高效的序列化和压缩,使用对象共享池,选择高效合适的数据类型(见《Redis 实战篇:巧用数据类型实现亿级数据统计》)。按键命名规范的按键命名规范,遇到问题可以轻松定位。Redis是一个没有Scheme的NoSQL数据库。所以需要依赖规范来建立它的Scheme语义,就像我们根据不同的场景建立不同的数据库一样。敲黑板,以“业务模块名称”为前缀(如数据库方案),以“冒号”隔开,再加上“具体业务名称”。这样我们就可以通过key前缀来区分不同的业务数据,一目了然。总结一下:“企业名:表名:id”比如我们要统计博主“CodegeByte”的粉丝数,属于公众号的技术类型。set公众号:技术类:码兄byte100000码兄,如果key太长,有什么问题吗?key是字符串,底层数据结构是SDS。SDS结构将包含元数据信息,例如字符串的长度和已分配空间的大小。随着字符串长度的增加,SDS的元数据也会占用更多的内存空间。所以当字符串过长时,我们可以使用适当的缩写形式。别用bigkey码哥,我会被坑,导致报错,无法连接。因为Redis是单线程执行读写指令,如果有bigkey的读写操作,会导致线程阻塞,降低Redis的处理效率。bigkey包括两种情况:键值对的值很大,比如值存储了2MB的String数据;键值对的值是一个有很多元素的集合类型,比如一个存储了50000个元素的List集合。虽然Redis官方说key和string类型的值限制都是512MB。防止网卡流量和慢查询,string类型要控制在10KB以内,hash、list、set、zset元素个数不要超过5000个。码哥,业务数据这么大怎么办?例如,杰作《金瓶梅》得以保存。我们还可以通过gzip数据压缩来减少数据大小:/***使用gzip压缩字符串*/publicstaticStringcompress(Stringstr){if(str==null||str.length()==0){返回海峡;}try(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.长度()==0){返回compressedStr;}byte[]compressed=newsun.misc.BASE64Decoder().decodeBuffer(compressedStr);;字符串解压缩=空;尝试(ByteArrayOutputStreamout=newByteArrayOutputStream();ByteArrayInputStreamin=newByteArrayInputStream(压缩);GZIPInputStreamginzip=newGZIPInputStream(in);){byte[]buffer=newbyte[1024];在t偏移=-1;while((offset=ginzip.read(buffer))!=-1){out.write(buffer,0,offset);}解压缩=out.toString();}catch(IOExceptione){e.printStackTrace();}returndecompressed;}集合类型如果集合类型的元素确实很多,我们可以将一个大集合拆分成多个小集合来保存,使用高效的序列化和压缩的方法。为了节省内存,我们可以使用高效的序列化方法和压缩方法来减小值的大小。protostuff和kryo这两种序列化方式比Java自带的序列化方式效率更高。以上两种序列化方式虽然节省内存,但是序列化后都是二进制数据,可读性太差。通常我们会把它序列化成JSON或者XML。为了避免数据占用较大的空间,我们可以使用压缩工具(snappy、gzip)将数据压缩后存储到Redis中。使用整数对象的共享池Redis内部维护了10000个整数对象,范围从0到9999,并将这些整数作为一个共享池。即使大量键值对存储0~9999范围内的整数,在Redis实例中,实际只存储一个整数对象,可以节省内存空间。需要注意的是,有两种情况是不生效的:Redis中设置了maxmemory,开启了LRU策略(allkeys-lru或volatile-lru策略),则整型对象的共享池无法使用。这是因为LRU需要统计每个键值对的使用时间。如果不同的键值对复用了一个整型对象,则无法统计。如果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类型的lrange、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。最后欢迎在留言区分享你常用的使用规范,一起交流讨论。好文推荐RedisCore:为什么这么快Redis持久化:AOF和RDB如何保证数据高可用Redis高可用:主从架构数据一致性同步原理Redis高可用:Sentinel集群原理Redis高可用:Cluster集群原理Redis实战:利用Bitmap实现亿级海量数据统计Redis实战:利用Geo类型实现附近人偶遇女神Redis新特性:多线程模型解读Redis6.0新特性:客户端缓存带来的革命