我的编程空间,编程开发者的网络收藏夹
学习永远不晚

redis实践及思考

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

redis实践及思考

导语:当面临存储选型时是选择关系型还是非关系型数据库? 如果选择了非关系型的redis,redis常用数据类型占用内存大小如何估算的? redis的性能瓶颈又在哪里?

背景
前段时间接手了一个业务,响应时间达到 10s左右。 阅读源码后发现,每一次请求都是查询多个分表数据(task1,task2….),然后再join其他表(course,teacher..), 时间全部花在了大量磁盘I/O上。 脑袋一拍,重构,上redis!
为什么选择redis
拍脑袋做技术方案肯定是不行的,得用数据和逻辑说服别人才可以。

时延

时延=后端发起请求db(用户态拷贝请求到内核态)+ 网络时延 + 数据库寻址和读取
如果想要降低时延,只能减少请求数(合并多个后端请求)和减少数据库寻址和读取得时间。 从降低时延的角度,基于 单线程和内存的redis,每秒10万次得读写性能肯定远远胜过磁盘读写性能。

数据规模

以redis一组K-V为例(”hello” -> “world”),一个简单的set命令最终会产生4个消耗内存的结构。

redis实践及思考

关于Redis数据存储的细节,又要涉及到内存分配器(如jemalloc),简单说就是存储170字节,其实内存分配器会分配192字节存储。

redis实践及思考

那么总的花费就是

  • 一个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实践及思考
[ 最终方案 ]
redis瓶颈和优化

HGETALL

最终存储到redis中的数据结构如下图。

redis实践及思考

采用同步的方式对三个月(90天)进行HGETALL操作,每一天花费30ms,90次就是2700ms! redis操作读取应该是ns级别的,怎么会这么慢? 利用多核cpu计算会不会更快?

redis实践及思考
常识告诉我,redis指令执行速度 >> 网络通信(内网) > read/write等系统调用。 因此这里其实是I/O密集型场景,就算利用多核cpu,也解决不到根本的问题,最终影响redis性能, **其实是网卡收发数据 用户态内核态数据拷贝 **

pipeline

这个需求qps很小,所以网卡也不是瓶颈了,想要把需求优化到1s以内,减少I/O的次数是关键。 换句话说, 充分利用带宽,增大系统吞吐量。

于是我把代码改了一版,原来是90次I/O,现在通过redis pipeline操作,一次请求半个月,那么3个月就是6次I/O。 很开心,时间一下子少了1000ms。

redis实践及思考
redis实践及思考

pipeline携带的命令数

代码写到这里,我不经反问自己,为什么一次pipeline携带15个HGETALL命令,不是30个,不是40个? 换句话说,一次pipeline携带多少个HGETALL命令才会发起一次I/O?

我使用是golang的 redisgo  的客户端,翻阅源码发现,redisgo执行pipeline逻辑是 把命令和参数写到golang原生的bufio中,如果超过bufio默认最大值(4096字节),就发起一次I/O,flush到内核态。

redis实践及思考

redisgo编码pipeline规则 如下图, *表示后面参数加命令的个数,$表示后面的字符长度 ,一条HGEALL命令实际占45字节。

那其实90天数据,一次I/O就可以搞定了(90 * 45 < 4096字节)!

redis实践及思考

果然,又快了1000ms,耗费时间达到了1秒以内

redis实践及思考

对吞吐量和qps的取舍

笔者需求任务算是完成了,可是再进一步思考,redis的pipeline一次性带上多少HGETALL操作的key才是合理的呢? 换句话说,服务器吞吐量大了,可能就会导致qps急剧下降(网卡大量收发数据和redis内部协议解析,redis命令排队堆积,从而导致的缓慢),而想要qps高,服务器吞吐量可能就要降下来,无法很好的利用带宽。
对两者之间的取舍,同样是不能拍脑袋决定的,用压测数据说话!

简单写了一个压测程序,通过比较请求量和qps的关系,来看一下吞吐量和qps的变化,从而选择一个适合业务需求的值。

package main
import (
    "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
}
windows上单机版redis结果如下:
redis实践及思考

扩展 (分布式方案下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就是这样做的,有兴趣可以阅读下源码。

redis实践及思考

codis

市面上还流行着一种在客户端和服务端之间增设代理的方案,比如codis就是这样。 对于上层应用来说,连接 Codis-Proxy 和直接连接 原生的 Redis-Server 没有的区别,也就是说codis-proxy会帮你做上面并行分槽请求redis server,然后合并结果在一起的操作,对于使用者来说无感知。
总结
在做需求的过程中,发现了很多东西不能拍脑袋决定,而是前期做技术方案的时候,想清楚,调研好,用数据和逻辑去说服自己。

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

redis实践及思考

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Redis 定长队列探索及实践

这篇文章主要介绍了Redis 定长队列探索及实践,文章围绕主题展开详细的内容介绍,具有一定的参考价值,需要的小伙伴可以参考一下
2022-11-13

Redis 定长队列探索及实践

目录一、业务背景二、技术选型三、技术原理3.1 Lua 脚本3.2 List 对象3.3 Set 对象四、技术应用4.1 生产消息4.2 消费消息4.3 注意android事项五、线上效果六、适用场景七、总结一、业务背景从技术的角度来说,
2022-08-09

Golang在分布式系统的实践与思考

go 语言在分布式系统中的实战应用主要集中在并发性、通信和容错性方面。在分布式工作队列的示例中,go 语言通过管道实现任务通信,使用协程构建工作者池,并通过 cron 定时器添加任务。最佳实践包括选择正确的通信机制、设计弹性系统、监控和度量
Golang在分布式系统的实践与思考
2024-05-11

对HashMap的思考及手写实现

作者:张丰哲原文:https://www.jianshu.com/p/b638f19aeb64HashMap是Java中常用的集合,而且HashMap的一些思想,对于我们平时解决业务上的一些问题,在思路上有帮助,基于此,本篇博客将分析Has
2023-06-02

Redis键值设计的实践

目录1 优雅的key结构2 拒绝BigKey2.1 判断BigKey2.2 BigKey的危害2.3 如何发现BigKey2.4 如何删除BigKey3 恰当的数据类型3.1 存储对象3.2 Hash优化在Redis中,良好的键值设计可以达
2023-01-30

redis stream 实现消息队列的实践

目录Redis 实现消息对列4中方法发布订阅list 队列zset 队列Stream 队列基本命令xadd 生产消息读取消息xgroup 消费者组xreadgroup 消费消息Pending 等待列表消息确认消息转移信息监控SpringBo
2022-08-10

JS前端组件设计以业务为导向实践思考

这篇文章主要为大家介绍了JS前端组件设计以业务为导向实践思考,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2023-03-13

redis实例是什么意思

redis 实例是指一个独立的运行进程,用于存储和管理数据。每个实例拥有自己的配置、数据、持久化策略和客户端连接。它可用于缓存、消息队列、数据库和会话管理等用例。Redis 实例一个 Redis 实例是指一个正在运行的 Redis 服务器
redis实例是什么意思
2024-06-12

基于NodeJS的前后端分离的思考与实践(六)Nginx + Node.js + Java 的软件栈部署实践

淘宝网线上应用的传统软件栈结构为 Nginx + Velocity + Java,即:在这个体系中,Nginx 将请求转发给 Java 应用,后者处理完事务,再将数据用 Velocity 模板渲染成最终的页面。 引入 Node.js 之后,
2022-06-04

财务系统绩效考核的重要性及实践

本文将探讨财务系统绩效考核的必要性,并介绍几种常见的绩效考核方法和实践案例,以帮助组织更好地利用财务数据评估员工表现并促进持续改进。在现代企业中,财务系统绩效考核起着至关重要的作用。它不仅能够帮助企业实现目标、提高效率,还能为员工提供一个明确的反馈机制。下面将详细介绍几种常见的绩效考核方法和实践案例。1.基本薪酬
财务系统绩效考核的重要性及实践
2023-12-28

Redis实现用户关注的项目实践

本文详细介绍了使用Redis实现用户关注系统的项目实践,包括数据结构设计、关注和取消关注操作、获取关注者和被关注者的方法。该方法具备高性能、灵活性、可扩展性和易用性的优点。
Redis实现用户关注的项目实践
2024-04-02

Redis哨兵机制总结与实践

文章简介 本文将通过理论+实践的方式从头到尾总结Redis中的哨兵机制。文章内容从主从复制的弊端、如何解决弊端、什么是哨兵、哨兵监控的图形结构、哨兵监控的原理、如何配置哨兵、哨兵与主从复制的关系等方面来演示。 文中相关资料下载地址:链接: https://pa
Redis哨兵机制总结与实践
2015-01-15

Springboot+redis+Vue实现秒杀的项目实践

本文主要介绍了Springboot+redis+Vue实现秒杀的项目实践,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
2022-11-13

vue 长列表数据刷新的实现及思考

这篇文章主要为大家介绍了vue 长列表数据刷新的实现及思考,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2023-05-14

RabbitMQ学习及实践3--Spr

根据学习的RabbitMQ知识配了一个SpringMVC的实现。这是一个完整的工程,view的部分使用freeMarker,持久化操作是通过mybatis实现。    整个工程的目录结构如下:src下的相关包的解释:    controll
2023-01-31

编程热搜

目录