当前位置: 首页 > 科技观察

微博Redis9年优化历史万亿日访问量

时间:2023-03-16 02:22:57 科技观察

一、微博Redis应用场景》活动,以及粉丝数、用户数、阅读数、转发数、评论数、广告推荐数,差评,音乐排行榜等都用到了Redis1.Business&Scale&Challenges线上业务包括前面提到的信息流、广告、用户关系等,还有你可能更感兴趣的热门搜索用户一般都是去看发生了什么事,引爆阅读量的话题,还有现在兵家必争之地的视频,微博上大大小小的企业都在用Redis。微博有100T+存储,1000+物理机,10000+Redis实例,对于我们面临的挑战,我们每天有万亿次的读写,对在线响应时间要求比较高。给个简单的例子,我们跨机房部署资源,但是有的业务部门甚至抱怨跨机房部署多了两毫秒的延迟(小妾真的做不到,万一单个机房出故障了呢?一些商业聚会真的很异想天开)。基本上四个9的响应时间是20毫秒。在成本方面,因为我们线上有大量的T需求,所以成本压力其实特别大。2.技术选型上图为微博数据库的技术选型。其实大家可以看到,它不仅包括Redis等NoSQL,还包括队列和存储。以后有机会可以跟大家分享下从0到1的搭建内部分享微博数据库大概用了2-3个小时,时间有限。这次只讲Redis部分。3、优化从2010年开始,我们在正式版2.0的基础上引入了Redis。九年过去了,我们主要做了以下几个方面的改进:Redis编码格式,在特殊场景下可以节省30%的空间成本;主从库有独立的拷贝线程;我们自定义了一些数据结构,比如:LongSet数据结构,它是一个“定长开放寻址Hash数组”,为Redisdict减少了很多额外的指针开销;在master复制方面,独立复制线程+全增量复制,这种情况下,如果网络主从暂时断开,只从当前pos点同步数据;在持久化方面,我们采用全量RDB加增量AOF复制;AOF写/刷盘,主线程->BIO,避免写造成的阻塞;着陆时间,不可控->cronsave可控;增加aofnumber,设置AOF的个数,避免写入速度过快,磁盘写满;高可用,我们不使用官方或社区开源的RedisHA,而是自己开发的一套RedisHA,保证故障时快速切换。微博有大量的技术场景,比如转发、点赞、阅读等,对于一些用户来说,他们非常关心这些指标。如果我们使用原来的Redis,会浪费很多存储空间,因为它的产品很特殊,它的key是一个用户的id,它的value是一个数字。我们先改了一个叫RedisCounter的版本,相当于只维护了一个哈希表,这样就节省了大量的Redis内存空间。当然,它有一个缺点,就是上线时间短,速度快,所以只支持单列单表。如果要收藏、转发、评论、点赞3个计数,需要部署三套资源。这样大家访问微博获取这3个号的速度就会变慢。而我们需要维护3套资源。为了应对这种场景,我们支持多列多表的方式。如果一个表写满了,我们可以继续写下一个表。在写入最后一个表的时候,我们可以将之前的表roll到磁盘,但是此时是不可读的。为了解决不可读的问题,我们想到了一个办法,把所有的表放到磁盘中,维护ddb的数据结构。历史数据存储在磁盘上。根据之前的统计,在90%以上的线上案例中,用户只访问了几个月的数据,所以一些长尾数据可以通过从磁盘读取数据来解决,而不会影响用户体验。微博还有一些存在判断行为,比如是否被点赞,是否被阅读。数据总量非常大。如果使用Redis,内存开销会特别高。所以我们修改了一个服务版本,兼容Redis协议,基于BloomFilter,开发了一个phantom版本,高性能,单线程网络处理机制,媲美Redis性能,低存储空间,每条记录占用1.2*Nwordssection(1%误判率,每增加0.6*N字节,误判率下降到原来的1/10,N为单个slot占用的比特数)。当然还有其他类型的数据库像我们最近用的队列,MySQL等等,这里就不展开了。Redis优化第一阶段小结:非阻塞落地增量复制->RDB+AOF在线热升级关系图自定义-内存降为1/10-性能等效计数自定义-内存降为1/4-性能提升BloomFilter3-5倍,但是我们做了那么多优化还是跟不上业务需求。2、微博中Redis的优化首先要明白为什么要优化。我们一般从三个方面来考虑:第一是业务端。目前线上业务方需要关心资源分配、容量规划等,比如内存是否满,磁盘是否满,是否使用MySQL,是否提前分库分表,以及QPS是否可以处理。我们希望对业务方屏蔽这些问题,他们只是工作,不必太在意资源细节。第二个是DBA。微博虽然不再处于快速增长的状态,但实际上还在以一定的速度增长,所以对DBA的需求还是很大的。另外,我们部门承担了微博所有的数据库服务,微博服务器也是最多的,所以对我们来说,需求多,变化多,挑战也大。从设计的角度,我们需要考虑如何更合理的设计Redis。总结起来就是三个方面:第一,高性能,读写快,访问快,响应时间快。二是能够支撑大容量的需求。第三是可扩展性。因为接触的业务方很多,你会发现一个问题。基本上很少有业务方能把自己的需求描述的很清楚。上线后经常发现内存不够用,或者写不上去。直播,所以这个时候我们需要在扩展性方面提供强有力的支持。我们可以把这三个方面理解为三座山。一、Cache服务服务化要解决三座大山,首先要将Cache服务化。是一种多级缓存服务,可以解决高访问和高并发的问题,实现高可用。基于该系统,还设计了一套后台程序,自动监控微博流量,支持自动扩缩容,实现快速攻克高峰,过峰后回收机器,实现流量的充分利用。资源。.当然这个系统还在不断完善中,希望以后会更加智能。ConfigService就是我们把配置放在配置中心,客户端从配置中心拉取配置。接入方式有两种,第一种是SDK,第二种支持多语言,请求通过Proxy路由到后端Cache。DBA只要使用管理平台,就可以快速扩缩资源。下面说说多级Cache。其实里面有四个角色:master、master-l1、slave、slave-l1。master和slave没有同步关系,而是根据角色的作用来命名的。master-l1有多组数据处理热点。master是保存全量数据的基准数据。slave一般用于多个机房的容灾,slave-l1用于多个数据集。机房数据同步,这种同步只保证最终数据的一致性。下面以阅读为例来说明这个过程。读取是先访问master-l1。如果没有命中,它将访问master。如果没有命中,它将访问从机。通过这三层,在大多数情况下,99%的热点Hold住了,然后1%的流量就会落到MySQL里面。如果有100万次读取,只会有10000个QPS传给MySQL。如果100万次的读取全部发给MySQL,对于MySQL来说成本会特别高,而众所周知,MySQL在高并发读写的情况下是非常昂贵的。很容易被击杀,短时间内无法恢复。Cacheservice目前支持mc和Redis两种协议。上图是我们DBA操作的扩缩容界面。这个业务一共有20个组,每个组有5个IP。5×20=100个实例。实际上,一百个实例在其中提供服务。有很多单集群服务可以支持百万级甚至千万级QPS的高并发访问,可以支持快速扩缩容。让我分享一下我们以前的成功案例。我们连续几年在春晚期间实现了1000+阿里云ECS的弹性扩缩容,多次实现平滑过渡不降级,支撑了微博春晚50%的核心流量。上图是我们内部系统集成来支撑系统,这里就不展开了。2、mcq服务是在之前的Cache服务的基础上实现的。我们在2018年上半年就和业务方合作做了排队服务。为什么要单独提出队列?因为内部或外部经常有人问,发微博的流程是什么?发表评论的流程是什么?数据如何解决?这些问题是一个关键部分它在队列中。发微博的时候其实是先写入队列,然后队列再写入后端MySQL。如果这时候MySQL宕机了,我们会有一个修复队列,有一个专门的Key来存放。部分数据,待MySQL恢复后,将这部分数据写入MySQL。上面还有一个BCP,因为我们在做这套的时候,其实是想在整个微博上宣传一下。去年,比特币非常受欢迎。我们也想购买比特币,在内部使用机器资源或者一些内部开源的东西,转换等价的材料,然后应用这个服务,但是最后这个计划没有实现。上图是一键报警和操作的监控画面。前面说了,我们把缓存变成了服务,但实际上我们并没有解决容量过大的问题。虽然内存的价格一直在下降,但是相对于硬盘来说,价格还是太高了。如果我们经常有5T或者10T这样的业务,都放在内存里面,其实我们的成本压力是非常大的。此外,我们需要向特别成本委员会申请资源。只有通过成本委员会的批准,我们才能得到这些机器,整个周期需要很长时间。3、Redis容量过大如何解决?为了解决容量过剩的问题,我们想把内存中的容量放到磁盘中。我们当时考虑了一些特性,比如支持冷热数据分离,比如把所有的历史数据或者全量数据存盘,然后支持持久化,数据主从复制,在线热升级。需要与Redis数据类型兼容。还与Redis复制兼容。基于之前的场景,这种方式特别适用于微博等属性。即使数据冷热不明显,比如上传T,每秒访问数K,这种方式也特别适用。先说处理模块,包含主线程和后台线程。主线程主要处理连接请求、协议解析、命令请求等。后台线程主要是复制线程,还有BIO线程。我们把烧写、写AOF等操作放在这个线程中,尽可能减少写对Redis造成的阻塞。还有一个BloomFilter,是基于布谷鸟算法优化的。初始化的时候指定Filter的容量,添加双向链表管理Hash冲突。从名字就可以猜到它是Redis+RocksDB的组合。为什么此时我们没有像上面提到的类似设计CounterserviceSSD那样自己设计,主要是因为我们设计的时候RocksDB并没有很多大型的应用。现在RocksDB已经很成熟了,也有很多成功的案例。另一个我们不自己开发的原因是,如果自己开发的话,适用性、性能、代码健壮性可能都不会那么好。因此,为了节省时间,我们使用RocksDB进行存储,避免重新发明轮子。LRU是为了加快访问速度。如果第一次访问时没有在内存中读取,就会从磁盘中读取。它实际上会放在内存中。下次读取时,会从LRU读取。读出来。这还涉及将数据从内存换入和换出磁盘。如果键或值特别大,性能会受到影响。这就是为什么我们不建议如前所述对特别大的键或值使用RedRocks。下面的架构图是将前面的处理模块和后端整合在一起形成的。总结一下:简单易用:完全兼容Redis,业务方无需做任何改动即可迁移到它上面;成本优势:将热数据或经常访问的数据放在内存中,将所有数据放在磁盘上。这是一个特别大的优势,可以突破内存容量的限制;高性能:热点数据存储在内存中,热点数据的访问性能媲美Redis。下图是性能压测报告,我们对比了set的随机写。4、不能满足新的需求?我们解决了大容量的问题,但是还有很多困难没有解决好。因此,我们借鉴了开源的经验,考察了Twemproxy、Codis、Corvus、Redis-Cluser的功能:其实我们在2015年就已经有了基于Twemproxy的业务,微博音乐、微博健康等在线服务、护照这3个业务已经上线。但是,我们内部并没有大规模推广。有两个主要原因。一是迁移还是比较耗时的,二是不可能更完美的动态添加节点,还有其他内部约束。.以上是我们的设计思路,一个是支持在线扩容,一个是支持多语言访问,因为我们要推广的是整个公司,而不是一个部门,所以为了推广方便,我们必须有这个功能,第三是面向服务的需求特性。下面简单说下proxy中各个模块的功能:端口自动增删改查:根据Vintage对proxy节点的配置,自动添加监控端口或删除已删除的端口,以及客户端被监控。Redis协议分析:分析Redis协议,判断需要路由的请求,对于非法和不支持的请求直接返回错误。路由:需要获取监听端口对应的后端及其槽位,根据端口、key和Redis命令选择一个后端,将请求路由到对应的后端,并将结果返回给客户端。配置监控:在Vintage中监控这个proxy的配置,包括端口变化、端口和后端变化、slot变化等,并通知端口监控模块和路由模块。指标监控:指标需要发送给Graphite进行监控。日志记录:生成用于跟踪目的的日志文件。在Redis存储方面:我们仍然使用我们内部修改的Redis版本。与之前的在线版本相比,这次我们增加了Mememory,内存编码优化等官方功能,以及一些新的内部功能。关于集群管理,无论是Redis还是MySQL,任何对资源的管理都可以归纳为这五个部分:资源申请、资源分配、业务上线、资源查询、资源变更。对于业务应用,需要有唯一的业务标识、QPS、数据类型。基于这些考虑,我们将对其进行分配、配置和部署。基于我们之前做过的这么多的优化和平台服务,用下图来概括比较合适,相当于服务的高可用、高性能和可扩展性。我们基本上都是用之前的方案来解决的。.三、未来展望无论是最开始的MySQL,还是后来的Oracle,都离不开SQL。如果能很好的解决数据一致性问题,Redis的应用场景会更广。现在很多公司都对Raft做了二次开发,我们以后也会在这方面进行投入。借用两句话来结束今天的演讲:“数据库其实就是要求你以最快的速度存储数据,然后以最方便的方式调出数据。”谢谢你!Q&AQ1:刚才你说你这里有一个新的类型,叫做LongSet。能介绍一下它的应用场景吗?A:应用场景在关系判断场景。在微博注意力向Cache的改造中,需要使用Redis作为缓存来存储用户注意力。List,原计划是使用Hash结构。但是由于使用Redis作为缓存,key可能会被去掉,所以在判断用户是否关注某人时可能不存在。这时候就需要从数据库中拉取用户的关注列表,直接HMSET到Hash结构中。但是RedisHSET一次对多个字段进行HSET时,性能比较低,会阻塞其他请求。同时,在模拟压测时发现,当缓存为空时,需要逐渐填充缓存才能稳定服务。这种情况下运维压力比较大,容错性很低,违背了我们做缓存的初衷,所以才有了这个改造。为了解决RedisHash结构HMSET性能低、内存利用率低的问题,对Redis进行了定制改造,新增了一个新的数据结构LongSet来存储元素为long类型的集合。Q2:你刚才的解释是热点数据放在Redis中,最开始放在RocksDB中。什么时候调用这些数据?什么时候调用这些数据?一定要有桌子吗?A:读取过程是先读取内存中的数据。对于数据,可以动态调整内存大小,与官方的maxMemory参数一致。只有一部分数据保留在内存中。如果内存中的数据没有被读取,则从磁盘中读取数据。