redis是怎么实现的

redis是怎么实现的,第1张

第一:Redis 是什么?

Redis是基于内存、可持久化的日志型、Key-Value数据库 高性能存储系统,并提供多种语言的API

第二:出现背景

数据结构(Data Structure)需求越来越多, 但memcache中没有, 影响开发效率

性能需求, 随着读 *** 作的量的上升需要解决,经历的过程有: 

数据库读写分离(M/S)–>数据库使用多个Slave–>增加Cache (memcache)–>转到Redis

解决写的问题: 

水平拆分,对表的拆分,将有的用户放在这个表,有的用户放在另外一个表;

可靠性需求 

Cache的"雪崩"问题让人纠结 

Cache面临着快速恢复的挑战

开发成本需求 

Cache和DB的一致性维护成本越来越高(先清理DB, 再清理缓存, 不行啊, 太慢了!) 

开发需要跟上不断涌入的产品需求 

硬件成本最贵的就是数据库层面的机器,基本上比前端的机器要贵几倍,主要是IO密集型,很耗硬件;

维护性复杂 

一致性维护成本越来越高; 

BerkeleyDB使用B树,会一直写新的,内部不会有文件重新组织;这样会导致文件越来越大;大的时候需要进行文件归档,归档的 *** 作要定期做; 

这样,就需要有一定的down time;

基于以上考虑, 选择了Redis

第三:Redis 在新浪微博中的应用

Redis简介

1 支持5种数据结构

支持strings, hashes, lists, sets, sorted sets 

string是很好的存储方式,用来做计数存储。sets用于建立索引库非常棒;

2 K-V 存储 vs K-V 缓存

新浪微博目前使用的98%都是持久化的应用,2%的是缓存,用到了600+服务器 

Redis中持久化的应用和非持久化的方式不会差别很大: 

非持久化的为8-9万tps,那么持久化在7-8万tps左右; 

当使用持久化时,需要考虑到持久化和写性能的配比,也就是要考虑redis使用的内存大小和硬盘写的速率的比例计算;

3 社区活跃

Redis目前有3万多行代码, 代码写的精简,有很多巧妙的实现,作者有技术洁癖 

Redis的社区活跃度很高,这是衡量开源软件质量的重要指标,开源软件的初期一般都没有商业技术服务支持,如果没有活跃社区做支撑,一旦发生问题都无处求救;

Redis基本原理

redis持久化(aof) append online file: 

写log(aof), 到一定程度再和内存合并 追加再追加, 顺序写磁盘, 对性能影响非常小

1 单实例单进程

Redis使用的是单进程,所以在配置时,一个实例只会用到一个CPU; 

在配置时,如果需要让CPU使用率最大化,可以配置Redis实例数对应CPU数, Redis实例数对应端口数(8核Cpu, 8个实例, 8个端口), 以提高并发: 

单机测试时, 单条数据在200字节, 测试的结果为8~9万tps;

2 Replication

过程: 数据写到master–>master存储到slave的rdb中–>slave加载rdb到内存。 

存储点(save point): 当网络中断了, 连上之后, 继续传 

Master-slave下第一次同步是全传,后面是增量同步;、

3 数据一致性

长期运行后多个结点之间存在不一致的可能性; 

开发两个工具程序: 

1对于数据量大的数据,会周期性的全量检查; 

2实时的检查增量数据,是否具有一致性;

对于主库未及时同步从库导致的不一致,称之为延时问题; 

对于一致性要求不是那么严格的场景,我们只需要要保证最终一致性即可; 

对于延时问题,需要根据业务场景特点分析,从应用层面增加策略来解决这个问题; 

例如: 

1新注册的用户,必须先查询主库; 

2注册成功之后,需要等待3s之后跳转,后台此时就是在做数据同步。

第四:分布式缓存的架构设计

1架构设计

由于redis是单点,项目中需要使用,必须自己实现分布式。基本架构图如下所示:

2分布式实现

通过key做一致性哈希,实现key对应redis结点的分布。

一致性哈希的实现:

l        hash值计算:通过支持MD5与MurmurHash两种计算方式,默认是采用MurmurHash,高效的hash计算

l        一致性的实现:通过java的TreeMap来模拟环状结构,实现均匀分布

3client的选择

对于jedis修改的主要是分区模块的修改,使其支持了跟据BufferKey进行分区,跟据不同的redis结点信息,可以初始化不同的 ShardInfo,同时也修改了JedisPool的底层实现,使其连接pool池支持跟据key,value的构造方法,跟据不同 ShardInfos,创建不同的jedis连接客户端,达到分区的效果,供应用层调用

4模块的说明

l        脏数据处理模块,处理失败执行的缓存 *** 作。

l        屏蔽监控模块,对于jedis *** 作的异常监控,当某结点出现异常可控制redis结点的切除等 *** 作。

整个分布式模块通过hornetq,来切除异常redis结点。对于新结点的增加,也可以通过reload方法实现增加。(此模块对于新增结点也可以很方便实现)

对于以上分布式架构的实现满足了项目的需求。另外使用中对于一些比较重要用途的缓存数据可以单独设置一些redis结点,设定特定的优先级。另外对 于缓存接口的设计,也可以跟据需求,实现基本接口与一些特殊逻辑接口。对于cas相关 *** 作,以及一些事物 *** 作可以通过其watch机制来实现。

声明:所有博客服务于分布式框架,作为框架的技术支持及说明,框架面向企业,是大型互联网分布式企业架构,后期会介绍linux上部署高可用集群项目。

在秒杀等高并发场景下,既要保证库存安全,也要拥有极高的系统性能。从存储结构上,很多同学会选用Redis,毕竟Redis的单线程 *** 作特性,很好地避免了线程安全的问题,同时具备极高的读写性能。

我们先来看下库存系统设计的几大核心要点:

1 库存安全:既要保证线程安全,也要防止出现超卖

2 同步响应:业务场景基本不允许异步响应库存扣减结果

3 性能极限:在seckill场景下,性能总是被要求越高越好

我们来看下如何利用Redis来解决上面的三个问题。

一库存安全

利用Redis来做库存扣减,避免超限的"方法"很多,坑也很多,我们先来看下常用的陷阱有哪些。

1 先获取当前库存值进行比较,再进行扣减

defdecr_stock():conn=redis_conn()key="productA"current_storage=connget(key)current_storage_int=int(current_storage)ifcurrent_storage_int<=0 :return0result=conndecr(key)returnresult

我们先在Redis中拿到当前的库存值,然后check是否已经扣减到了零,如果已经扣减到了零,则直接return;否则,就利用Redis的decr原子 *** 作进行扣减,同时返回扣减后的库存值。

这种方法的问题很明显,在并发条件下,会出现脏读,设想一个场景,AB两个请求进来,A获取的库存值为1,B获取的库存值为1,然后两个请求都被发到redis中进行扣减 *** 作,然后这种场景下,A最后得到的库存值为0;但是B最后得到的库存值为-1,超限。

2 先扣减库存,再做比较,跟进情况是否做回滚

defdecr_stock():conn=redis_conn()key="productA"current=conndecr(key)ifcurrent>=0:returncurrentelse:          #回滚库存connincr(key)return0

直接先对库存值进行扣减,得到当前的库存值;然后,对此库存值进行check,如果库存>=0,则返回库存值,如果库存<0,则回滚库存,以便于防止负库存量的存在。

Redis Decr命令:DECR 命令会返回键 key 在执行减1 *** 作之后的值。

这种做法引入了两个新的问题:

1)如果大批量的并发请求过来,redis承受的写 *** 作的量,是加倍的,因为回滚库存的存在导致的。所以这种情况下,高并发量进来,极有可能将redis的写 *** 作打出极限值,然后会出现很多redis写失败的错误警告

2) Redis的Decr *** 作和回滚 *** 作无法保证原子性,在宕机情况下,容易产生数据不一致

3先扣库存,然后通过整数溢出控制,根据情况进行回滚

defdecr_stock():conn=redis_conn()key="productA"current=conndecr(key)      #通过整数控制溢出的做法ifcheck_overflow(current):returncurrentelse:          #回滚库存connincr(key)return0  defcheck_overflow(stock):      #如果当前库存未被递减到0,则check_number为int类型,isinstance方法检测结果为true      #如果当前库存已被递减到负数,则check_number为long类型,isinstance方法检测结果为falsecheck_number=sysmaxint - stockcheck_result=isinstance(check_number,int)returncheck_result

这种做法和方法2类似,只是比对部分由直接和0比对,变成了通过检测integer是否溢出的方式来进行。这样就彻底解决了高并发情况下,直接和零比对,限制不住的问题了。

虽然此种做法,相对于做法二说来,要靠谱很多,但是仍然解决不了在高并发情况下,redis写并发量加倍的问题,极有可能某个促销活动,在开始的那一刻,直接将redis的写 *** 作打出问题来。

4基于分布式锁的库存扣减

defdecr_stock():key ="productA"    lock = getLock(key)iflocked ==1:        current_storage = connget(key)        current_storage_int = int(current_storage)ifcurrent_storage_int<=0:return0        result = conndecr(key)returnresultelse:return"someone in it"

Redis在28以后支持Lua脚本的原子性 *** 作,可以用来做分布式锁,解决超限的问题。

5 All in Lua

defstorage_scenario_six():        conn = redis_conn()lua ="""                local storage = rediscall('get','storage_seckill')                if  storage ~= false then                    if tonumber(storage) > 0 then                        return rediscall('decr','storage_seckill')                    else                        return 'storage is zero now, can't perform decr action'                    end                else                    return rediscall('set','storage_seckill',10)                end              """result = conneval(lua,0)        print(result)

二、同步响应

如果只用Redis来进行存储,处理完数据直接返回前端即可。如果还要持久化到DB,要尽量避免直接 *** 作DB,因为DB往往是最大的IO瓶颈,如果要异步落库到DB,比如使用MQ。要注意处理Redis扣减和消息发送的原子性处理。

三、性能

官网上redis的读写性能能到10W/QPS左右,这个量级应该可以解决绝大部分的场景。

但是经常有同学在压测的时候达不到这个性能,主要还是卡在网络环境上,在5W/QPS的时候,带宽就超过10M/s了。所有想追求Redis的极致性能,最好还是在同机房进行调用。

Redis是一个开源的内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。

它支持多种类型的数据结构,如字符串(Strings),散列(Hashes),列表(Lists),集合(Sets),有序集合(SortedSets或者是ZSet)与范围查询,Bitmaps,和地理空间(Geospatial)索引半径查询。其中常见的数据结构类型有String、List、Set、Hash、ZSet这5种。

Redis是一个nosql数据库,可以存储key-value值。因为其底层实现中,数据读写是基于内存,速度非常快,所以常用于缓存;进而因其为独立部署的中间件,常用于分布式缓存的实现方案。

常用场景有:缓存、秒杀控制、分布式锁。

虽然其是基于内存读写,但底层也有持久化机制;同时具备集群模式;不用担心其可用性。

关于Redis的使用,可以参考《Redis的使用方法、常见应用场景》

以上就是关于redis是怎么实现的全部的内容,包括:redis是怎么实现的、利用Redis设计库存系统的苦与乐、Redis简介以及和其他缓存数数据库的区别(redis缓存和数据库一致性)等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/sjk/10185382.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-05-06
下一篇 2023-05-06

发表评论

登录后才能评论

评论列表(0条)

保存