Redis各种数据结构性能数据对比和性能优化实践

简介: 大量数据插入哈希MAP,运行一个多小时插入到2w多条的时候再也插入不进去了。想单个删除也很慢,跟插入差不多的慢。但是删除一整个key却很快。这和C语言的原理有关。删除key其实是删除的对value的引用,内存空间不需要重置,只在需要的时候将其重写。写过一个C语言基础入门的程序

很对不起大家,又是一篇乱序的文章,但是满满的干货,来源于实践,相信大家会有所收获。里面穿插一些感悟和生活故事,可以忽略不看。不过听大家普遍的反馈说这是其中最喜欢看的部分,好吧,就当学习之后轻松一下。


Redis各种数据结构性能数据对比


测试工具:perf4j


性能指标:平均值,最小值,最大值,方差


对比将814条数据按单条插入到哈希MAP和哈希SET:

1112728-20170719113722365-2134276988.png

对比从814条数据的哈希MAP和哈希SET中判断一个元素是否存在(map的hasKey和set的isMember):


1112728-20170719143350021-45811221.png


1112728-20170719144254646-141382267.png


大量数据插入哈希MAP,运行一个多小时插入到2w多条的时候再也插入不进去了。想单个删除也很慢,跟插入差不多的慢。但是删除一整个key却很快。这和C语言的原理有关。删除key其实是删除的对value的引用,内存空间不需要重置,只在需要的时候将其重写。写过一个C语言基础入门的程序:


#include<stdio.h>
main(){
    int a[5]={1,2,3,4,5};
    int b[5]={1,2,3};
    int c[]={1,2,3,4,5};
    static int d[5];
    int e[5];
    int f[0];
    int i;
    for(i=0;i<5;i++)printf("%10d",a[i]);printf("\n");
    for(i=0;i<5;i++)printf("%10d",b[i]);printf("\n");
    for(i=0;i<5;i++)printf("%10d",c[i]);printf("\n");
    for(i=0;i<5;i++)printf("%10d",d[i]);printf("\n");
    for(i=0;i<5;i++)printf("%10d",e[i]);printf("\n");
    for(i=0;i<5;i++)printf("%10d",f[i]);printf("\n");
}


运行结果:


1112728-20170726182916609-560873732.png


最后两行出现这个结果的原因是分配的地址空间原来是什么值就会显示什么值,所以是随机的。


性能优化实践


  前段时间我改了并发量最大的媒资接口的JVM启动参数,修改之前栈空间是4G,新生代是1G,基本没有full gc。minor gc相当频繁,平时1秒1次,并发量上去之后每秒4,5次。我将栈空间设置为8G,新生代设置为5G,平时大概4,5秒一次,估计并发量上去之后还是会达到1秒1次。当然有的机器会少些。因为我们公司用的是SLB的7层负载均衡,采取的是轮询策略,负载不是很均匀。我现在在试图从根本上解决问题,找到minor gc频繁的原因,最终要将minor gc口平时控制在12秒一次以上。12秒这个数据是怎么来的呢。


1112728-20170728110634993-840877453.png


看我标出来的这两个差值,大体新生代的垃圾回收时间是18ms。如果12m一次,就是12000ms中垃圾回收时间占18m,千分之1.5的时间用于垃圾回收,这个比率对于整体性能的影响就没那么大了。当然各个数组会随调整变化,到时候要看情况。


  之前我也参与过这个媒资接口的性能优化,一直没有成效是因为第一我对媒资接口不熟,我也不是最主要的负责人,很多事情做不了主。其实,一般解决这个问题的方式是横向扩展,其实我们有很多空余设备,德伟不肯试试,我也没办法。另外,媒资接口虽然是我们这边最重要的接口了,但是我总会被临时调到一些更紧急的项目中去,东一点西一点的。虽说觉得自己是个SMP(对称多核处理器),但是内核上下文切换也得保存现场吧,开销很大,做不深入,时间片又到了。这都是自己当初没有想好,没有自己的负责区域,所以会被来回调。所以现在就做两件事情。如今设备复用先不考虑了,先从根本上改善程序。


1112728-20170728113047040-170293695.png


我做了一晚上的测试,凌晨4点才睡。得出来一个结论:我们的定时加载本地缓存对性能有影响。第二天我在地铁上就发消息到群里说找到性能上不去的根了。第二天我们开会讨论了,大家最后的决定是我的想法可以试一试。但是从统计图表上看,内存,CPU,499和响应时间关联不大。我说我们系统目前的现状也不会是一个原因造成的,定时加载本地缓存确实和内存定期的峰值很匹配,确实是问题。既然是问题,就要一点点解决。先抛开问题不谈,发现自己的另一个问题:话说的太直,太满。


  之前发生了一件事,我好好反省了一下,很多方面。


  有次照例中午和部门里一个小姑娘吃饭。她让我给她提提建议,怎样能去阿里。她说你看我人还挺可爱的,能不能靠点别的去阿里。我人实在是太直了,我就向她阐述了确实如果实力不够是去不了的。越聊到最后发现她并不是在问我的意见,只是想让我带她去阿里。我发现这个还是按照自己的思路说下去。结果人家小姑娘气的一下午没搭理我。确实我话说的太直太满,是我要解决的问题。也确实我认为只有实力到了才能去也只是我的想法,人家有人家的方法,只是我不知道,而且不愿意用而已。我总认为达到一个目标过程更重要,其实哪是人人都这么想的。思维不够开放。我经常给别的公司推荐人才,如果觉得这个人实力不够,我都不会给推。这倒没有错,但是对别人应该有更多的鼓励,遇到委婉的人说话要更委婉一点。


  这个小姑娘聊天中提到你看我虽然有些问题自己解决不了,但是我可以找别人解决啊,最终问题都解决了。她是觉得自己是可以干活的,那么去阿里是没问题的。其实据我了解,阿里不招这种要不要无所谓的。虽然我也没进阿里,我是这么想的。这个小姑娘虽然没去阿里,但是去的公司也都可以。但把这个归因于自己很可爱,很招人喜欢我有点不赞同。物有本末,事有始终。反思一下我自己,记得11年,换工作形式一片大好的时候,我虽然没有换工作,确实出去面过试。面试其实问题回答的都不是很好,但是都给了offer。其中面了华为的,面试官说咱俩一个学校的,咱们东大的人基础不应该这么差啊。结果什么都不会,还是要我了。究其原因,要我是因为学校比较好,然后去的公司都比较好,人不笨,觉得还是有培养价值的,反正成本也不是很高。和可不可爱关系不是很大,额,我说话是不是又太直了。其实我当初如果遇到了更多的挫折,现在技术能力应该好不少。所以现在我很珍惜遇到的问题,时间许可的范围内,解决不好过程,不去追求结果。特别是做技术的,长远稳定性,前瞻性,很重要。还有一点,女孩子其实在技术工作中面对的劣势远远大于优势,我自然也遇到过,但是我自己不太清楚是哪些。因为这个劣势体现在根本不会给我面试的机会。能让我面试的,我相信面试的过程还是很公平公正的。至少我经历过的过与不过都有理有据。


  我声称自己是个技术吃货,也就是这半年的事情。之前,一直是个技术二货。上家公司在五道口清华科技园,是朋友在那边,把我挖过去的。去的时候我就很明确的说因为这边不是很忙,我家孩子小,我要先照顾好家。分清当前事情的主次是必要的。可是记得那时候每天11点就去吃饭了,吃完饭还要去买个肯德基套餐带着去旁边清华大学,到下午3点再回去上班。很多该做的没做,该学的没学。当初的时间利用起来,现在不至于这么时不时不由自主的来一句:I am so stupid. 发现身边很多做技术的女孩子,包括自己在内,对技术现状,对各大公司的形势,各个方面摄取的信息太少。不知者无畏,反而会比较自大。前段时间自己也是很浮躁,其实技术基础都不是很扎实。我有个学弟,工作三年的时候就出去创业做CTO了。旁边也有很多人很年轻有为的,很早就不做技术了,直接转管理。我原来在想:我和他们走的路都不一样。我要尽量更多的时间活跃在底层一线,因为想要工作到60岁。提升的过早,以后会越早达到瓶颈。想法很符合自身的情况,但是实际上努力不够。行远自迩,登高自卑。时刻保持危机感,强化学习意识。


    回到最初的问题。定时加载本地缓存。我试过,对这个服务来说,已加载在本地缓存的数据获取速度比远程(其实这里测试集中缓存和数据库速度差不多)快几十倍,而且很稳定。但是最初加载的时候,我们设定启动后50秒暴露服务,也还是不能保证加载完,会导致服务重启发生短暂的连接池溢出。而且我感觉我们的dubbo连接池设置了700,太大了,反而导致响应慢。开会讨论将缓存全量更新时间由本来的一小时或者半小时设置为12小时试试结果。结果由于发生了一些非技术原因现在正式环境还没有试,测试环境没有对比。但是开头介绍的redis缓存完全可以派上用场。在key值1000个以下的小本地缓存可以第一次加载的时候从redis缓存里取,redis缓存由单独的后台服务控制更新,记录最后更新时间。其实更新服务我放到离线服务里了。如果最后更新时间发生变化才会再次取最新数据。我观察了一个星期,字典值,TV值,字典配置值一个星期就没变过。耗费那么大的性能去更新,好心疼。然后20几万占100M多栈内存的明星数据,定时全量和增量执行更新,启动都需要几十秒做这个事情,当初写这个代码的哥哥,你写的时候真的测试过这么做可以提高性能吗[汗]


epiphany框架改进


  既然我要写文,就不得不提一下我的开源框架进展。这是一个离线数据推送的框架,支持全量,增量和手动发送。几个部分可分开和整合部署。用户可以灵活的选取全部部署,或者部分部署。或者在需要的情况下进行升级,降级处理。目前最新版本的改进是支持全量模式耗时长的数据优先运行的策略,以达到总体数据各个线程间耗时平均。当然用户可以自己决定是否使用此策略。在全量增量同时运行的情况下,支持both模式和yield模式。both模式即运行全量的情况下也运行增量,yield模式即全量运行时增量暂停,待全量运行完接暂停时间点继续运行。


 我在写框架的时候,必定用到很多测试和性能监控的东西。其中JVM我打开了很多参数,发出来供大家参考,红框标出的是一些监控,测试时可以打开。


1112728-20170728203836305-728092142.png


 程序在跑,今晚够呛能睡觉。


相关文章
|
5月前
|
Java 数据挖掘 数据处理
(Pandas)Python做数据处理必选框架之一!(一):介绍Pandas中的两个数据结构;刨析Series:如何访问数据;数据去重、取众数、总和、标准差、方差、平均值等;判断缺失值、获取索引...
Pandas 是一个开源的数据分析和数据处理库,它是基于 Python 编程语言的。 Pandas 提供了易于使用的数据结构和数据分析工具,特别适用于处理结构化数据,如表格型数据(类似于Excel表格)。 Pandas 是数据科学和分析领域中常用的工具之一,它使得用户能够轻松地从各种数据源中导入数据,并对数据进行高效的操作和分析。 Pandas 主要引入了两种新的数据结构:Series 和 DataFrame。
609 0
|
6月前
|
消息中间件 缓存 NoSQL
Redis各类数据结构详细介绍及其在Go语言Gin框架下实践应用
这只是利用Go语言和Gin框架与Redis交互最基础部分展示;根据具体业务需求可能需要更复杂查询、事务处理或订阅发布功能实现更多高级特性应用场景。
396 86
|
6月前
|
存储 消息中间件 NoSQL
Redis数据结构:别小看这5把“瑞士军刀”,用好了性能飙升!
Redis提供5种基础数据结构及多种高级结构,如String、Hash、List、Set、ZSet,底层通过SDS、跳表等实现高效操作。灵活运用可解决缓存、计数、消息队列、排行榜等问题,结合Bitmap、HyperLogLog、GEO更可应对签到、UV统计、地理位置等场景,是高性能应用的核心利器。
|
6月前
|
存储 缓存 NoSQL
Redis持久化深度解析:数据安全与性能的平衡艺术
Redis持久化解决内存数据易失问题,提供RDB快照与AOF日志两种机制。RDB恢复快、性能高,但可能丢数据;AOF安全性高,最多丢1秒数据,支持多种写回策略,适合不同场景。Redis 4.0+支持混合持久化,兼顾速度与安全。根据业务需求选择合适方案,实现数据可靠与性能平衡。(238字)
|
6月前
|
存储 缓存 NoSQL
Redis基础命令与数据结构概览
Redis是一个功能强大的键值存储系统,提供了丰富的数据结构以及相应的操作命令来满足现代应用程序对于高速读写和灵活数据处理的需求。通过掌握这些基础命令,开发者能够高效地对Redis进行操作,实现数据存储和管理的高性能方案。
193 12
|
6月前
|
存储 消息中间件 NoSQL
【Redis】常用数据结构之List篇:从常用命令到典型使用场景
本文将系统探讨 Redis List 的核心特性、完整命令体系、底层存储实现以及典型实践场景,为读者构建从理论到应用的完整认知框架,助力开发者在实际业务中高效运用这一数据结构解决问题。
|
6月前
|
存储 缓存 NoSQL
【Redis】 常用数据结构之String篇:从SET/GET到INCR的超全教程
无论是需要快速缓存用户信息,还是实现高并发场景下的精准计数,深入理解String的特性与最佳实践,都是提升Redis使用效率的关键。接下来,让我们从基础命令开始,逐步揭开String数据结构的神秘面纱。
|
存储 算法
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
371 59
|
9月前
|
编译器 C语言 C++
栈区的非法访问导致的死循环(x64)
这段内容主要分析了一段C语言代码在VS2022中形成死循环的原因,涉及栈区内存布局和数组越界问题。代码中`arr[15]`越界访问,修改了变量`i`的值,导致`for`循环条件始终为真,形成死循环。原因是VS2022栈区从低地址到高地址分配内存,`arr`数组与`i`相邻,`arr[15]`恰好覆盖`i`的地址。而在VS2019中,栈区先分配高地址再分配低地址,因此相同代码表现不同。这说明编译器对栈区内存分配顺序的实现差异会导致程序行为不一致,需避免数组越界以确保代码健壮性。
198 0
栈区的非法访问导致的死循环(x64)
|
存储 C语言 C++
【C++数据结构——栈与队列】顺序栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现顺序栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 1.初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储
753 77