-
golang redis获取所有key_redis实践及思考
2020-12-27 16:41:46redis的性能瓶颈又在哪里?背景前段时间接手了一个业务,响应时间达到10s左右。阅读源码后发现,每一次请求都是查询多个分表数据(task1,task2….),然后再join其他表(course,teacher..),时间全部花在了大量磁盘I/O...导语:当面临存储选型时是选择关系型还是非关系型数据库?如果选择了非关系型的redis,redis常用数据类型占用内存大小如何估算的?redis的性能瓶颈又在哪里?
背景
前段时间接手了一个业务,响应时间达到10s左右。阅读源码后发现,每一次请求都是查询多个分表数据(task1,task2….),然后再join其他表(course,teacher..),时间全部花在了大量磁盘I/O上。脑袋一拍,重构,上redis!为什么选择redis
拍脑袋做技术方案肯定是不行的,得用数据和逻辑说服别人才可以。时延
时延=后端发起请求db(用户态拷贝请求到内核态)+ 网络时延 + 数据库寻址和读取如果想要降低时延,只能减少请求数(合并多个后端请求)和减少数据库寻址和读取得时间。从降低时延的角度,基于单线程和内存的redis,每秒10万次得读写性能肯定远远胜过磁盘读写性能。数据规模
以redis一组K-V为例(”hello” -> “world”),一个简单的set命令最终会产生4个消耗内存的结构。
关于Redis数据存储的细节,又要涉及到内存分配器(如jemalloc),简单说就是存储170字节,其实内存分配器会分配192字节存储。
那么总的花费就是
一个dictEntry,24字节,jemalloc会分配32字节的内存块
一个redisObject,16字节,jemalloc会分配16字节的内存块
一个key,5字节,所以SDS(key)需要5+9=14个字节,jemalloc会分配16字节的内存块
- 一个value,5字节,所以SDS(value)需要5+9=14个字节,jemalloc会分配16字节的内存块
综上,一个dictEntry需要32+16+16+16=80个字节。
上面这个算法只是举个例子,想要更深入计算出redis所有数据结构的内存大小,可以参考这篇文章。笔者使用的是哈希结构,这个业务需求大概一年的数据量是200MB,从使用redis成本上考虑没有问题。需求特点
笔者这个需求背景读多写少,冷数据占比比较大,但数据结构又很复杂(涉及多个维度数据总和),因此只要启动定时任务离线增量写入redis,请求到达时直接读取redis中的数据,无疑可以减少响应时间。
[ 最终方案 ]
redis瓶颈和优化
HGETALL
最终存储到redis中的数据结构如下图。
采用同步的方式对三个月(90天)进行HGETALL操作,每一天花费30ms,90次就是2700ms!redis操作读取应该是ns级别的,怎么会这么慢?利用多核cpu计算会不会更快?
常识告诉我,redis指令执行速度 >> 网络通信(内网) > read/write等系统调用。因此这里其实是I/O密集型场景,就算利用多核cpu,也解决不到根本的问题,最终影响redis性能,**其实是网卡收发数据和用户态内核态数据拷贝**。
pipeline
这个需求qps很小,所以网卡也不是瓶颈了,想要把需求优化到1s以内,减少I/O的次数是关键。换句话说,充分利用带宽,增大系统吞吐量。于是我把代码改了一版,原来是90次I/O,现在通过redis pipeline操作,一次请求半个月,那么3个月就是6次I/O。很开心,时间一下子少了1000ms。
pipeline携带的命令数
代码写到这里,我不经反问自己,为什么一次pipeline携带15个HGETALL命令,不是30个,不是40个?换句话说,一次pipeline携带多少个HGETALL命令才会发起一次I/O?我使用是golang的redisgo 的客户端,翻阅源码发现,redisgo执行pipeline逻辑是 把命令和参数写到golang原生的bufio中,如果超过bufio默认最大值(4096字节),就发起一次I/O,flush到内核态。
redisgo编码pipeline规则如下图,*表示后面参数加命令的个数,$表示后面的字符长度,一条HGEALL命令实际占45字节。
那其实90天数据,一次I/O就可以搞定了(90 * 45 < 4096字节)!
果然,又快了1000ms,耗费时间达到了1秒以内
对吞吐量和qps的取舍
笔者需求任务算是完成了,可是再进一步思考,redis的pipeline一次性带上多少HGETALL操作的key才是合理的呢?换句话说,服务器吞吐量大了,可能就会导致qps急剧下降(网卡大量收发数据和redis内部协议解析,redis命令排队堆积,从而导致的缓慢),而想要qps高,服务器吞吐量可能就要降下来,无法很好的利用带宽。对两者之间的取舍,同样是不能拍脑袋决定的,用压测数据说话!简单写了一个压测程序,通过比较请求量和qps的关系,来看一下吞吐量和qps的变化,从而选择一个适合业务需求的值。
(左滑可查看完整代码)windows上单机版redis结果如下:package mainimport ( "crypto/rand" "fmt" "math/big" "strconv" "time" "github.com/garyburd/redigo/redis")const redisKey = "redis_test_key:%s"func main() { for i := 1; i < 10000; i++ { testRedisHGETALL(getPreKeyAndLoopTime(i)) }}func testRedisHGETALL(keyList [][]string) { Conn, err := redis.Dial("tcp", "127.0.0.1:6379") if err != nil { fmt.Println(err) return } costTime := int64(0) start := time.Now().Unix() for _, keys := range keyList { for _, key := range keys { Conn.Send("HGETALL", fmt.Sprintf(redisKey, key)) } Conn.Flush() } end := time.Now().Unix() costTime = end - start fmt.Printf("cost_time=[%+v]ms,qps=[%+v],keyLen=[%+v],totalBytes=[%+v]", 1000*int64(len(keyList))/costTime, costTime/int64(len(keyList)), len(keyList), len(keyList)*len(keyList[0])*len(redisKey))}//根据key的长度,设置不同的循环次数,平均计算,取除网络延迟带来的影响func getPreKeyAndLoopTime(keyLen int) [][]string { loopTime := 1000 if keyLen < 10 { loopTime *= 100 } else if keyLen < 100 { loopTime *= 50 } else if keyLen < 500 { loopTime *= 10 } else if keyLen < 1000 { loopTime *= 5 } return generateKeys(keyLen, loopTime)}func generateKeys(keyLen, looTime int) [][]string { keyList := make([][]string, 0) for i := 0; i < looTime; i++ { keys := make([]string, 0) for i := 0; i < keyLen; i++ { result, _ := rand.Int(rand.Reader, big.NewInt(100)) keys = append(keys, strconv.FormatInt(result.Int64(), 10)) } keyList = append(keyList, keys) } return keyList}
扩展 (分布式方案下pipeline操作)
需求最终是完成了,可是转念一想,现在都是集群版的redis,pipeline批量请求的key可能分布在不同的机器上,但pipeline请求最终可能只被一台redis server处理,那不就是会读取数据失败吗?于是,笔者查找几个通用的redis 分布式方案,看看他们是如何处理这pipeline问题的。redis cluster
redis cluster 是官方给出的分布式方案。 Redis Cluster在设计中没有使用一致性哈希,而是使用数据分片(Sharding)引入哈希槽(hash slot)来实现。一个 Redis Cluster包含16384(0~16383)个哈希槽,存储在Redis Cluster中的所有键都会被映射到这些slot中,集群中的每个键都属于这16384个哈希槽中的一个,集群使用公式slot=CRC16 key/16384来计算key属于哪个槽。比如redis cluster有5个节点,每个节点就负责一部分哈希槽,如果参数的多个key在不同的slot,在不同的主机上,那么必然会出错。因此redis cluster分布式方案是不支持pipeline操作,如果想要做,只有客户端缓存slot和redis节点的关系,在批量请求时,就通过key算出不同的slot以及redis节点,并行的进行pipeline。github.com/go-redis就是这样做的,有兴趣可以阅读下源码。
codis
市面上还流行着一种在客户端和服务端之间增设代理的方案,比如codis就是这样。对于上层应用来说,连接 Codis-Proxy 和直接连接 原生的 Redis-Server 没有的区别,也就是说codis-proxy会帮你做上面并行分槽请求redis server,然后合并结果在一起的操作,对于使用者来说无感知。总结
在做需求的过程中,发现了很多东西不能拍脑袋决定,而是前期做技术方案的时候,想清楚,调研好,用数据和逻辑去说服自己。相关阅读深入浅出百亿请求高可用Redis(codis)分布式集群揭秘
-
导师计划--数据结构和算法系列(上)
2020-12-09 04:46:22<p><strong>数据结构是计算机存储、组织数据的方式。数据结构是指相互直接存在一种或多种特殊关系的数据元素的集合。通常情况下,精心选择数据结构可以带来更高的运行或者存储效率。作为一名程序猿,更... -
最要命的是,File文件只能存储在机身存储的外部存储,这个区域是一个共享区域,如果用户手贱,私自删除数据也是有可能的 毋庸置疑使用SQLiteDatabase存储将是您最佳的选择 4.数据怎么存 解决了数据源和数据存...
-
SQL Server 2008数据库设计与实现(关系数据库实现的通关宝典)--随书源代码
2013-02-06 12:04:00Scott住在佛罗里达的Wellington,当他不坐在计算机前时,你会发现他和自己的家人在一起,或者骑着他的雅马哈摩托车轰鸣在当地的摩托车越野赛赛道上。你可以通过ScottKlein@SqlXml.com联系他。 目录 封面 -17 封底... -
oracle学习文档 笔记 全面 深刻 详细 通俗易懂 doc word格式 清晰 连接字符串
2017-05-06 20:26:52数据库(Database)是按照数据结构来组织、存储和管理数据的仓库,它产生于距今五十年前。简单来说是本身可视为电子化的文件柜——存储电子文件的处所,用户可以对文件中的数据运行新增、截取、更新、删除等操作。 ... -
1.2.2 有一批气象观测站,现需要获取这些站点的观测数据,并存储到 Hive 中。但是气象局只提供了 api 查询,每次只能查询单个观测点。那么如果能够方便快速地获取到所有的观测点的数据? 1.2.3 如何实现两金额数据...
-
Google Android开发入门与实战(09年度畅销榜TOP50)--详细书签版
2013-02-08 12:00:472.2.2 SDK的家在哪里——设定Android SDK Home 14 2.2.3 真的准备好了吗——开发环境验证 14 2.2.4 创建Android虚拟设备(AVD) 15 2.3 Linux一族——Ubuntu开发环境搭建 17 2.3.1 Java、Eclipse和ADT插件安装 17... -
net学习笔记及其他代码应用
2010-11-16 18:15:0943.try {}里有一个return语句,那么紧跟在这个try后的finally {}里的code会不会被执行,什么时候被执行,在return前还是后? 答:会执行,在return前执行。 44.两个对象值相同(x.equals(y) == true),但却可有不同... -
让Oracle跑得更快:Oracle10g性能分析与优化思路--详细书签版
2013-02-06 16:23:023.2.4 段头数据块 65 第4章 优化器 66 4.1 RBO基于规则的优化器 66 4.2 CBO基于成本的优化器 69 第5章 执行计划 85 5.1 Cardinality (基数) 85 5.2 SQL的执行计划 94 第6章 Hint 109 6.1 和优化器相关的Hint... -
你必须知道的495个C语言问题
2015-10-16 14:14:283.21 “无符号保护”和“值保护”规则的区别在哪里? 第4章 指针 基本的指针应用 4.1 指针到底有什么好处? 4.2 我想声明一个指针并为它分配一些空间,但却不行。这些代码有什么问题呢?char*p;*p=malloc(10); ... -
Oracle SQL高级编程(资深Oracle专家力作,OakTable团队推荐)--详细书签版
2013-02-04 12:43:52Karen Morton及其团队在本书中提供了专业的方案:先掌握语言特性,再学习Oracle为提升语言效率而加入的支持特性,进而将两者综合考虑并在工作中加以应用。作者通过总结各自多年的软件开发和教学培训经验,与大家... -
Oracle SQL高级编程(资深Oracle专家力作,OakTable团队推荐)--随书源代码
2013-02-04 12:49:33Karen Morton及其团队在本书中提供了专业的方案:先掌握语言特性,再学习Oracle为提升语言效率而加入的支持特性,进而将两者综合考虑并在工作中加以应用。作者通过总结各自多年的软件开发和教学培训经验,与大家... -
《你必须知道的495个C语言问题》
2010-03-20 16:41:183.21 “无符号保护”和“值保护”规则的区别在哪里? 42 第4章 指针 45 基本的指针应用 45 4.1 指针到底有什么好处? 45 4.2 我想声明一个指针并为它分配一些空间,但却不行。这些代码有什么问题呢?char ... -
你必须知道的495个C语言问题(高清版)
2010-03-31 16:24:093.21 “无符号保护”和“值保护”规则的区别在哪里? 42 第4章 指针 45 基本的指针应用 45 4.1 指针到底有什么好处? 45 4.2 我想声明一个指针并为它分配一些空间,但却不行。这些代码有什么问题呢?char ... -
c#学习笔记.txt
2008-12-15 14:01:21在后一种情况中,委托不仅存储对方法入口点的引用,还存储对调用其方法的对象的引用。与 C 函数指针不同,委托是完全面对对象的;与指向成员函数的 C 指针不同,委托同时封装对象实例和方法。委托声明定义从类 ... -
BigData NoSQL:ApsaraDB HBase数据存储与分析平台概览 Apache Sqoop: 是一个用来将Hadoop和关系型数据库中的数据相互转移的工具,可以将一个关系型数据库(MySQL ,Oracle ,Postgres等)中的数据导进到Hadoop的...
-
知识地图就是你前进的灯塔,有了它你知道目标在哪里,也知道差距在哪里,路漫漫其修远兮希望你能早日通关~ 目录(善用Ctrl+F) 学Java有哪些热门就业方向? 安卓开发 Java 后端开发 大数据/数据仓库 Java...
-
新版Android开发教程.rar
2010-12-14 15:49:11----------------------------------- Android 编程基础 1 封面----------------------------------- Android 编程基础 ...• SQLite SQLite SQLite SQLite 用作结构化的数据存储 • 多媒体支持 包括常见的音频、视频和... -
湖南文理学院2019上学期(大三下)计算机科学与技术专业网络安全,密码学复习提纲(可直接打印).pdf
2019-07-19 10:16:19分组密码和流密码的主要区别在哪里;攻击密码体制的主要方法有两种 什么是一次一密,为什么说它是一种绝对安全的密码体制,它是否实用,为什么? 什么是单向陷门函数,公钥密码算法应满足哪些要求 利用 GF(2^8)的... -
java面试题
2018-01-01 15:35:1554.2. Socket在哪里呢? 31 54.3. Socket是什么呢? 32 54.4. socket的实现步骤 37 55. Servlet 38 55.1. Servlet工作流程 38 55.2. servlet的生命周期 38 55.3. Servlet执行时一般实现哪几个方法? 38 56. 会话跟踪...
-
异常色散光纤激光器中的线性耗散孤子
-
Linux基础入门系列课程
-
leetcode算法第7题
-
龙芯生态应用开发基础:C语言精要
-
程序员必修基础套餐课
-
详解敏捷测试
-
leetcode算法第5题
-
2017年上半年 信息系统监理师 上午试卷 综合知识 软考真题【含答案和答案解析】
-
2010年上半年 信息系统监理师 上午试卷 综合知识 软考真题【含答案和答案解析】
-
C++MFC开发远程控制软件教程(VS2013)
-
基于杜鹃搜索的磷虾群算法解决工程优化问题
-
NearFi应用-源码
-
access应用的3个开发实例
-
Glasterfs 分布式网络文件系统
-
构建低成本高密度Wi-Fi网络实验床
-
从理论到试验台,WiFi DCF网络的性能评估
-
2009年下半年 信息系统管理工程师 上午试卷 综合知识 软考真题【含答案和答案解析】
-
具有超窄带宽的稳定锁模纳秒无Chi脉冲产生
-
mpsoc zcu104 上做hdmi 显示实验
-
基于Qt的LibVLC开发教程