第一: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缓存和数据库一致性)等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)