redis geo 性能分析及对问题的思考

文章目录

  • redis geo 性能分析及对问题的思考
    • 对 redis 进行压力测试
    • 对 redis 进行满载压力分析
      • georadiusGeneric
        • membersOfGeoHashBox
          • geohashGetDistance
          • decodeGeohash
          • zmalloc&free
        • addReplyDoubleDistance
        • addReplyBulkSds
    • 总结:redis geo 性能优化方案

(题外话,自己搭建博客没人看,以后还是在 csdn 和自己搭建的博客都发吧,屈服+++++)

对 redis 进行压力测试

将数据存入 redis 后(共 85318 条有效数据),通过压测工具redis-benchmark对 redis 进行压测。

redis-benchmark -h 127.0.0.1 -p 6379 -c 50 -n 1000 -q georadius outlet 112 -7 200 km withdist

并发数为 50,每个并发发送 1000 个请求,结果如下:

**注:**本文所用电脑为 MacBook Pro (16-inch, 2019),cpu 为:Intel® Core™ i7-9750H CPU @ 2.60GHz,内存为 16 g。

对 redis 进行满载压力分析

使用 Instruments 对 redis 进行满负荷时分析,压力测试命令如下:

redis-benchmark -h 127.0.0.1 -p 6379 -c 100 -n 50000 -q georadius outlet 112 -7 200 km withdist

说几点建议:

  • 多开几个压测的线程,多个线程并发执行,尽可能的去打满 redis,以遍更直观的看到 redis 的性能瓶颈。
  • 有条件的可以使用两个本地机器去模拟(必须为局域网,当然两根网线连着更好,非局域网可能会有网速对 redis 性能的影响),使用同一台电脑开启 redis 服务和进行压测时,压测开很多线程,一直发数据也是很消耗 cpu 资源的(这个东西属于 unstable,看内核调度,是不可控的)。所以为了控制变量,让 redis 能更好的发挥效率,推荐使用两个机器。

满负荷执行两分钟的结果如下(性能分析的 trace 文件,需要的自提):

注:强烈推荐使用 Linux 系统的 perf 对 redis 进行性能分析,虽然 mac 分析的很全面,但是生成不了火焰图导致结果很不直观,需要手动一条一条找数据。好在数据量不大,因此逐个分析吧(这是网上仅有的 mac 生成火焰图的方式链接,但是我没有测试成功)。

georadiusGeneric

可以看出 97.9% 的时间都花费在了这个函数上,这个函数其实就是一个回调,执行 georadius 的命令。

/* GEORADIUS wrapper function. */
void georadiusCommand(client *c) {georadiusGeneric(c, 1, RADIUS_COORDS);
}

这个命令下共有 3 个函数费时间,下面依次进行分析。

membersOfGeoHashBox

在这个函数中可以看到最费时间的 4 个函数(没按层级划分,只找到最费时的),分别为:

  • geohashGetDistance
  • decodeGeohash
  • zmalloc_usable
  • zfree

geohashGetDistance

这个函数是计算两点见经纬度的距离的,最费时间的函数是 asin 函数。

// 计算两点之间的距离
/* Calculate distance using haversine great circle distance formula. */
double geohashGetDistance(double lon1d, double lat1d, double lon2d, double lat2d) {double lat1r, lon1r, lat2r, lon2r, u, v;lat1r = deg_rad(lat1d);lon1r = deg_rad(lon1d);lat2r = deg_rad(lat2d);lon2r = deg_rad(lon2d);u = sin((lat2r - lat1r) / 2);v = sin((lon2r - lon1r) / 2);return 2.0 * EARTH_RADIUS_IN_METERS *asin(sqrt(u * u + cos(lat1r) * cos(lat2r) * v * v));
}

美团解决这个计算的方案:link,这个方案的问题是如果两点之间距离远,那么误差会比较大。

下图为耗时图:

decodeGeohash

这个函数中有两个函数费时间,分别耗时:2.4% 和 2.4%,耗时原因也是因为大量的计算。但是这个计算目前没有很好的解决方案。

  1. geohashDecode

这里最费时间的是将 hash 的经纬度还原的函数,将这个经纬度夹在一个很小的范围内。这个很小的范围就近似于这个点(因为 decode 也是这么算的,还原也得这还原)。

int geohashDecode(const GeoHashRange long_range, const GeoHashRange lat_range,const GeoHashBits hash, GeoHashArea *area) {if (HASHISZERO(hash) || NULL == area || RANGEISZERO(lat_range) ||RANGEISZERO(long_range)) {return 0;}area->hash = hash;uint8_t step = hash.step;uint64_t hash_sep = deinterleave64(hash.bits); /* hash = [LAT][LONG] */double lat_scale = lat_range.max - lat_range.min;double long_scale = long_range.max - long_range.min;// 分开经纬度,deinterleave64 返回的是 return x | (y << 32);// 所以通过 uint32_t 就可以分开uint32_t ilato = hash_sep;       /* get lat part of deinterleaved hash */uint32_t ilono = hash_sep >> 32; /* shift over to get long part of hash */// 这里是获取将这个点夹在中间的位置/* ---------------------------| *   |        |           ||     |        |           |---------------------------现在点在第一个位置。这个方法是不断二分,将这个点夹逼到一个很小的位置 *//* divide by 2**step.* Then, for 0-1 coordinate, multiply times scale and addto the min to get the absolute coordinate. */area->latitude.min =lat_range.min + (ilato * 1.0 / (1ull << step)) * lat_scale;area->latitude.max =lat_range.min + ((ilato + 1) * 1.0 / (1ull << step)) * lat_scale;area->longitude.min =long_range.min + (ilono * 1.0 / (1ull << step)) * long_scale;area->longitude.max =long_range.min + ((ilono + 1) * 1.0 / (1ull << step)) * long_scale;return 1;
}
  1. geohashDecodeAreaToLongLat

上面一个函数将点夹逼在一个很小的范围内,这个函数将夹逼的点还原(感觉整的像分析代码逻辑,本文不讲逻辑,只讲优化方案)。

// 将夹逼的点还原成原来的点
int geohashDecodeAreaToLongLat(const GeoHashArea *area, double *xy) {if (!xy) return 0;xy[0] = (area->longitude.min + area->longitude.max) / 2;if (xy[0] > GEO_LONG_MAX) xy[0] = GEO_LONG_MAX;if (xy[0] < GEO_LONG_MIN) xy[0] = GEO_LONG_MIN;xy[1] = (area->latitude.min + area->latitude.max) / 2;if (xy[1] > GEO_LAT_MAX) xy[1] = GEO_LAT_MAX;if (xy[1] < GEO_LAT_MIN) xy[1] = GEO_LAT_MIN;return 1;
}

下图为耗时图:

zmalloc&free

这个道理都懂,分配内存和释放内存所耗费的时间,大约耗时 20%。这里代码分析的少,感觉没有用内存池,觉得可以加一个内存池,来避免系统调用的开销?但 redis 设计者应该也想到这个方案了,他们肯定有自己的顾虑。

void *ztrymalloc_usable(size_t size, size_t *usable) {ASSERT_NO_SIZE_OVERFLOW(size);void *ptr = malloc(MALLOC_MIN_SIZE(size)+PREFIX_SIZE);if (!ptr) return NULL;...
}

下图为耗时图:

addReplyDoubleDistance

这个函数中最耗时的是调用 stdio 中的snprintf函数,耗时占 18.3%。

这个函数我觉得不能改造(天呐!怎么有人改造 stdio 中的函数)。如果要改造,通用性就不强了,如果有更好的方案欢迎聊聊。

/* The default addReplyDouble has too much accuracy.  We use this* for returning location distances. "5.2145 meters away" is nicer* than "5.2144992818115 meters away." We provide 4 digits after the dot* so that the returned value is decently accurate even when the unit is* the kilometer. */
void addReplyDoubleDistance(client *c, double d) {char dbuf[128];int dlen = snprintf(dbuf, sizeof(dbuf), "%.4f", d);addReplyBulkCBuffer(c, dbuf, dlen);
}

addReplyBulkSds

明显可以看出最耗时的也是库函数调用,试问,和解?我觉得无解。

总结:redis geo 性能优化方案

总的来说 redis 性能还是很 ok 的。学习代码和性能分析后,我们可以看出,redis geo 最耗时的代码主要集中在:大量的计算,库函数调用上。所以提出以下的解决方案:

  • redis 层面上

    • 可以采用精确度更低的计算距离的算法来提升速率。
    • 针对特定的业务,可以采用自己编写的函数来提升速率(损失通用性,增加性能)。
  • 调用层面上
    • 可以缓存地理位置的**“哈希方格”**来提高效率(基本上类似于上面的geohashDecode函数,方格大小按业务进行选择)。不建议一个点一个点缓存,基本不可能命中。白白消耗时间。
  • 物理层面上~~(大道至简…)~~
    • 增加机器。
    • 由于 redis 是单线程,每个机器上多放几个 redis,增加多核利用率。

注:本人分析的 redis 版本为测试版本(我自己的个人注释也在这里),从 redis fork 的分支。若需要分析稳定版本的请在网上自行下载。

redis geo 性能分析及对问题的思考相关推荐

  1. redis批量操作及性能分析

    redis批量操作及性能分析 用过redis的小伙伴都知道,这东西是C/S的,就单纯的ser key vvv 就要走一次TCP,记得官方是说redis的qps将近10W,这...我不太敢相信,一秒钟之 ...

  2. redis性能分析工具redis-faina

    redis性能分析工具redis-faina redis-faina是一个通过解析redis的MONITOR命令,从而对redis实例进行性能诊断的工具. 该工具使用虽然简单,但是功能还是很不错,对于 ...

  3. Redis MGET性能衰减分析

    MGET是redis中较为常用的命令,用来批量获取给定key对应的value.因为redis使用基于RESP (REdis Serialization Protocol)协议的rpc接口,而redis ...

  4. redis提高oracle性能,redis性能分析与优化建议

    首先,并不是说redis是内存应用就完全没性能问题,用的不好,还是会出现各种状况,例如RDB频繁,碎片太多等. 性能分析 info信息: 在redis-cli进入登录界面后,输入info all,或者 ...

  5. [LBS学习笔记3]redis geo地理位置查询分析

    1.redis geo命令 1.1 redis6.2版本geo命令简介 redis自3.2版本开始,提供了地理位置相关的命令: GEOADD:添加空间元素 GEOPOS: 获取某个地理位置的坐标 GE ...

  6. 每秒1w+分布式事务--dtm的Redis存储性能测试分析

    概述 之前dtm给出了Mysql作为存储引擎的性能测试报告,在一个普通配置的机器上,2.68w IOPS,4核8G机器上,能够支持大约每秒900+分布式事务,能够满足大部分公司的业务需求. 此次带来的 ...

  7. Redis 的性能幻想与残酷现实(转)

    2011 年,当初选择 Redis 作为主要的内存数据存储,主要吸引我的是它提供多样的基础数据结构可以很方便的实现业务需求.另一方面又比较担心它的性能是否足以支撑,毕竟当时 Redis 还属于比较新的 ...

  8. 技术前沿:Redis推出性能碾压ES和Mongo的大杀器

    前沿技术早知道,弯道超车有希望 积累超车资本,从关注DD开始 近期官网给出了RedisJson(RedisSearch)的性能测试报告,可谓碾压其他NoSQL,下面是核心的报告内容,先上结论: 对于隔 ...

  9. Linux常用性能分析工具汇总

    文章目录 性能分析工具 top pstree mpstat vmstat pidstat perf proc tcpdump bcc工具箱 cachestat cachetop memleak fil ...

最新文章

  1. 云计算五种定义辨析[摘]
  2. LeetCode_Convert Sorted Array to Binary Search Tree(Java实现)
  3. linux中怎么删除只读变量,【Linux】【问题集锦】如何删除shell只读变量
  4. 参加ISCC2019线下赛感悟
  5. 在命令行中的vim编辑器加上行号
  6. html模块殃射,【Web前端问题】webpack打包后,module模块中的函数无法在html标签的事件中调用?...
  7. .NET 二维码生成(ThoughtWorks.QRCode)
  8. 基于JAVA+Servlet+JSP+MYSQL的电影院购票系统
  9. 期刊投稿状态_这些SCI期刊的投稿显示状态是什么意思?
  10. matlab最小费用最大流函数,Matlab最小费用最大流算法通用程序
  11. linux文件映射mmap
  12. windows oracle补丁包,Windows下oracle打补丁步骤
  13. docker安装redis【网易镜像方式】
  14. Redhat Linux系列(Fedora,CentOS,Redhat enterprise)中一些常用软件
  15. SuperCard与GBA
  16. Java工作流引擎:jBPM、Activiti以及SWF
  17. 《论语》与《资本论》
  18. RabbitMQ使用例子
  19. 【年终总结】回顾我平凡且不平凡的 2021
  20. 巧用Photoshop为透明玻璃杯抠图

热门文章

  1. SHOPYY建站平台推出免费CRM营销系统,20000封EDM邮件送不停!
  2. 计算机组成原理结果分析与实验体会,计算机组成原理实验报告心得体会.docx
  3. android课程表的实现
  4. 数据异常分析方法论总结
  5. OpenGL第四讲——颜色模式
  6. 深度盘点:PyTorch常用代码段合集
  7. 【第一讲】 Linux开发准备之电脑选择和VMware软件的安装
  8. 伍翀:大数据实时计算Flink SQL解密
  9. 云原生与大数据、AIoT、开源的碰撞之路——专访小米崔宝秋
  10. 抓取崔庆才个人博客网站前端源码