1、redis目录 背景 Redis简介 Redis是什么 功能与特点 内部实现(单机) 整体数据框架 基本数据结构 优化机制 索引优化 内存优化 持久化 主从备份 集群 Key-value数据库设计 与其他数据库对比 Redis不足 应用场景 国际上最大的redis用户是谁?在众多的NOSQL数据库与传统的关系数据库中为什么会出现redis? 传统的key-value数据库(文档,string类型)有什么不足?-数据结构需求 传统的关系型数据库IO操作性能问题?-性能需求 传统的内存数据库有什么不足?-可靠性需求背景背景 传统MySQL+ Memcached架构遇到的问题 1.MySQL需要不断
2、进行拆库拆表,Memcached也需不断跟着扩容,扩容和维护工作占据大量开发时间。 2.Memcached与MySQL数据库数据一致性问题。 3.Memcached数据命中率低或down机,大量访问直接穿透到DB,MySQL无法支撑。 4.跨机房cache同步问题。 Redis是一个开源的使用c语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value,多种数据结构的数据库,并提供多种语言(c,c#,java,javascript,perl,php,python,ruby,scala,erlang等)的API,仅有1万行代码。 稳定版本发布于2011年3月4日。从2010年3月15日
3、起,Redis的开发工作由VMware主持。 应用场景: 1.memory cache+mysql不能完全解决web2.0 需求 (mc穿透,跨机房问题,数据一致性 2.大量数据高速读写,数据结构需求。Redis简介功能特点数据库数据存储持久化高速读写集群部署动态扩展数据一致性事务性操作主从备份Sche-me free数据结构高并发vmredis支持支持目前仅单实例部署,但通过客户端做pre-sharding方案可搭建伪集群支持支持但不支持回滚支持,但是主机迭机后,不能自动选举丛机位为主机Key-value模型,支持支持(单线程的IO复用模型)支持一定量的并发数支持 性能测试:CPU为Xeon
4、 2.80GHz *4内存为4G硬盘为一块400G SATA盘操作系统为64位CentOS 5.3版本写测试:写入500万条记录,共耗时524秒,平均每秒写入数据9542笔。磁盘上的数据文件大小134M。读测试:成功读出500万条记录,共耗时184秒,平均每秒读出数据27174笔。整体数据结构redisServerredisDB *db.01.nredisDBint iddict *dict.redisDBint iddict *dict.redisDBint iddict *dict. 结构体redisServer对应服务器,字段db指向一个指针数组,数组元素值即各个数据库的入口地址。 结构
5、体redisDB对应数据库,用以保存数据库id,字典等信息。redisDBredisDBint iddict *dict.dictdictType *typedictht *ht0dictht *ht1.dicthtdictEntry *tableusigned long sizeusigned long used.dictTypeint (*hashFunc)(void *key).dicthtdictEntry *tableusigned long sizeusigned long used. dict即字典,type是字典类型,ditcht是哈希表。 函数指针hashFunc记录用以计算
6、hash值的hash函数。DictdictdictType *typedictht *ht0dictht *ht1.dicthtdictEntry *tableusigned long sizeusigned long used.01.size-1dictEntryvoid *keyvoid *val*nextNULLNULL 双重指针table指向一个数组,该数组记录各个dictEntry (条目?)的入口地址。size为table的大小,used是dictEntry的个数。 每个dictEntry对应一个存储对象,通过key和val指针可找到对应对象的key和value。used就是存储的
7、对象总数。key和value的值存在哪?dictEntryvoid *keyvoid *val*nextredisObjectunsigned type:4void *ptr.redisObjectunsigned type:4void *ptr.keyvalue redisObject可以看成是对要存储的各种对象(如String、List、Set等)的抽象,type(长4bit的整数)用于标识该对象所属的数据结构的种类,ptr指向对象在内存中的地址。一次查询流程 由key找到碰撞链入口的时间性能是O(1)。 在碰撞链中遍历查询的时间性能是O(n)。 Redis并不是简单的key-value存
8、储,它实际上是一个数据结构服务器。不仅可以用数据库预定义的数据结构存储的value,还支持这些数据结构的基本操作。预定义的数据结构有: String List of String Set of String Sorted Set of String HashTable of String基本数据结构String String是最简单的类型,值可以是任何种类的字符串(包括二进制数据)。 String的长度是int类型,最大长度为1GB 每个对象的key都以String存储 若数据库中所有value都为String,则Redis就像一个可持久化的memcachedstruct sdshdrint
9、lenint freechar bufredisObjecttype=stringvoid *ptr.List of StringListlistNode *headlistNode *tailunsigned int len.listNodeprev = nulllistNode *nextvoid *valuelistNodelistNode *prevlistNode *nextvoid *value List基于双向链表实现。 在头部或尾部添(删)一个结点,时间复杂度为常数级别 用List支持的一些操作,如lpush(头添加)、lpop(头删除),rpush、lpop可以很容易地实现栈
10、和队列 redis向外部提供的list中listNode保存的value是String类型的listNodelistNode *prevNext = nullvoid *valueHashTable of StringdictEntryvoid *keyvoid *val*nextredisObjecttype=stringvoid *ptr.redisObjecttypehashvoid *ptr.keydictdictType *typedictht *ht0dictht *ht1. redis数据组织的整体框架就是hashtable,hashtable of string只不过将hash
11、table特例化。 它还是以key-value形式存储,对hashtable of string中对象的一次成功查询要经过两次hash。set redis支持的集合有两种,无序集合set和有序集合zset。zset的数据结构比较复杂,尚未弄懂。暂时只知道它基于有序链表和hashtable实现(用hashtable组织元素,链表实现排序),set又是基于zset实现。 zset中的元素有一个权重参数score,使得集合中的元素能够按score进行有序排列 忽略zset的权重score就可以得到set的实现 集合提供了求交集、并集、差集等操作优化机制 索引优化 随着hashtable中key-va
12、lue对的不断增多,碰撞也越来越多,碰撞链越来越长,势必会影响到查询效率。为了保持查询效率,必须调整hashtable的索引结构,使每个索引上的碰撞链长度不至于太长。 内存优化 redis是个内存数据库,同等业务量下redis占用的内存越小越好。为了占用更小的内存,必须对内存进行优化。索引优化 rehash是在hashtable大小不能满足需求,造成过多碰撞后需要进行扩容时的操作。基本思想: 新建一张索引表,新表的索引空间为原表的两倍 遍历旧表中的所有dictEntry,调用hash函数计算得到每个dictEntry在新表中的索引,并添加到新表上。当所有dictEntry都添加到新表中,启用新
13、表,丢弃旧表 索引通过hashFunc(key)%size算得,因为新表索引空间是原来的两倍,在新表中发生碰撞的概率将会小于原表。这样原本在一个碰撞链上的多个dictEntry就可能分布到不同的新索引上,新表碰撞链的平均长度(dictEntry个数/size)理论是旧表的一半rehashdictdictType *typedictht *ht0dictht *ht1.dicthtdictEntry *tablesize0usigned long used.dicthtdictEntry *tablesize=2*size0usigned long used.01.size0-1dictEntr
14、yNULLNULLdictEntry01.size-1 ht1在平时为NULL,只有当rehash时用于指向新建的table。 当在rehash时如果有新的key-value要添加到数据库,添加到新表ht1中。hashFunc(key)%sizerehashdictdictType *typedictht *ht0dictht *ht1.dicthtdictEntry *tablesize2*size0usigned long used.01.size-1dictEntryNULLNULLdictEntryNULL 当旧表ht0中的所有dictEntry都rehash到新表后,让ht指向新表,
15、ht1指向空,释放旧表的内存。 rehash的结果是索引空间增至原来的2倍,碰撞链的平均长度used/size减小。rehash rehash是在hashtable需要扩容时进行,什么时候需要扩容? 每添加一个dictEntry到hashtable就要判断是否要进行扩容 redis默认当dictEntry的个数大于等于表容量size时进行扩容,即平均碰撞链长used/size刚超过1时就扩容,默认扩容到原来的两倍 默认的扩容条件可使平均碰撞链长保持在1以下,虽然保证了查询效率,但会频繁rehash,内存消耗大。可通过修改扩容条件减少rehash频率,但要改扩容条件,只有修改源代码内存优化 re
16、dis内存优化的策略有很多,比较常用的有两种,大体思想如下: 通过特殊的编码方式存储对象,以节省内存。如把数字字符串以整数方式存储,例如用整数存储数字符串“123”只需要1个字,对于数值较大的数字字符串,分割后用数组存储 当满足一定条件时,减小hashtable的容量,再rehash。默认在当used/size比不足10%时,将size改为used,再rehash得到容量是used两倍的新表,新表的容量将小于原表的1/5 快照(SnapShot) 当前数据的快照存成一个数据文件的持久化机制 频率控制: 过程:n Redis通过fork产生子进程n 父进程继续处理client请求,子进程负责将快
17、照写入临时文件n 子进程写完后,用临时文件替换原来的快照文件,然后子进程退出持久化-快照save 900 1 #900秒超过1个key被修改 save 300 10 #300秒超过10个key被修改 save 60 10000 #60 秒超过一万条被修改 优点: - 整个Redis数据库将只包含一个文件,这对于文件备份而言是非常完美的 -对于灾难恢复,可以非常轻松的将一个单独的文件压缩后再转移到其它存储介质上 - 性能最大化 -相比于AOF机制,如果数据集很大,RDB的启动效率会更高。 持久化-快照 会产生什么问题呢? 每次快照持久化都是将内存数据完整写入到磁盘一次,并不 是增量的只同步脏数据
18、。如果数据量大的话,而且写操作比较多,必然会引起大量的磁盘io操作,可能会严重影响性能。 另外由于快照方式是在一定间隔时间做一次的,所以如果redis意外down掉的话,就会丢失最后一次快照后的所有修改如果应用不能丢失任何修改的话,可以采用aof持久化方式 持久化-快照持久化-日志n AOF(append only file) 记录每次的读写操作的文件 在Redis配置文件中有一个叫appendonly的选项,可以写yes或no.这个选项就是负责是否开启AOF日志的开关. 日志文件通病,如果只增不减的话,那文件将会无限长大, 执行bgrewriterof命令,先给当前的所有数据做一个快照.然后
19、再在这个快照的基础上写接下来的日志,删除旧的日志.持久化-日志n Bgrewriterof内部实现l Redis 通过fork一个子进程,遍历数据,写入新临时文件l 父进程继续处理client请求,子进程继续写临时文件l 父进程把新写入的AOF写在缓冲区l 子进程写完退出,父进程接收退出消息,将缓冲区AOF写入临时文件l 临时文件重命名成appendonly.aof,原来文件被覆盖,整个过程完成 持久化-日志n 频率控制 ( redis.conf ): 写入磁盘时机命令: #appendonly yes /启用aof持久化方式# appendfsync always /每次收到写命令就立即强制
20、 写入磁盘,最慢的,但是保证完全的持久化,不推荐使用appendfsync everysec /每秒钟强制写入磁盘一次,在性能和持久化方面做了很好的折中,推荐# appendfsync no /完全依赖os,性能最好,持久化没保证save 900 1 #900秒超过1个key被修改 save 300 10 #300秒超过10个key被修改 save 60 10000 #60 秒超过一万条被修改持久化分析 快照易丢失数据,AOF模式较为安全,但日志重写时磁盘io开销大,容易导致服务器性能严重下降,并且需要足够的物理内存,这个比较危险,所以在高性能服务器上一般是用主从复制来进行持久化。主从架构sl
21、aveclientclientclientmastermastermasterslaveslaveslaveCONSISTENT HASH / 定容 readwrite同步数据SESSION主从复制 配置: 只需要在配置文件中加入如下配置slaveof 192.168.1.1 6379 #指定master的ip和端口. 存在问题: Slave从库在连接Master主库时,Master会进行内存快照,然后把整个快照文件发给Slave,也就是没有象MySQL那样有复制位置的概念,即无增量复制,这会给整个集群搭建带来非常多的问题。主从复制集群 假如业务增长很快,很快就会发现当前的容量已经不够了,Re
22、dis里面存储的数据很快就会超过物理内存大小,那么如何进行Redis的在线扩容 呢? 方案一:下一版本的redis自身支持在线扩容redis实例,这一功能正在开发中。 方案二:客户端做presharding方案客户端sharding方案 Node(redis实例)=hash(key)mod N; 对客户端需要set或get的每个key,做hash运算再取模,映射到N个实例中的一个,可以搭建多实例集群。 问题当系统规模增大或缩小时,需要动态增加或减少redis实例。对于增加实例情况后,N值将会变大,存储在原来实例上的所有key将要重做hash与取模运算(rehash过程),得到自己存储在新的re
23、dis实例位置。 重做rehash,迁移大量的key工作量非常大,管理复杂。Presharding方案 前提:Redis非常轻量,一个Redis实例占用的内存非常小(1M左右),所以在一台服务器上部署多个实例(32、64、128,1000.)完全没有问题。 思路:假设有N台主机,每台主机上部署M个实例,整个系统有T = N x M个实例,扩容前后实例总数不变。Presharding方案 前期多个实例部署在一台机器上,后期时每个实例独占一台物理机器内存,通过单机redis实例的内存增加,达到集群整体内存的大幅度上升。由于hash取模算法里的N值前后不变,避免rehash过程redis实例上的ke
24、y重排。 前期业务量小时,可配置少量廉价性能低机器满足业务,后期业务量大,配置很多台高性能高内存机器适应业务增长。如何在线迁移扩容 1.在新机器上启动好对应端口的Redis实例。 2.配置新端口为待迁移端口的从库。 3.待复制完成,与主库完成同步后,切换所有客户端配置到新的从库的端口。 4.配置从库为新的主库。 5.移除老的端口实例。 6.重复上述过程迁移好所有的端口到指定服务器上。 问题:redis实例增多后,导致运维管理成本增加,各个实例的开启关闭,aof文件与rdb文件的收集管理很繁琐。redis数据库结构设计 用户登录系统,记录用户登录信息的一个系统 关系型数据库的设计 KV数据库 K
25、v数据库记录为:或者 关系数据库中复杂多对多关系跨表查询,性能低下。 在结构化key-value数据库表现为在内存中对多个集 合的交集与并集运算 查找属于ruby又属于web的书 redis.sinter(tag.web, tag:ruby)关系型数据库表结构化key-value数据库表 Redis与Memcached的比较 1.网络IO模型 Memcached是多线程,非阻塞IO复用的网络模型。Redis使用单线程的IO复用模型,封装了一个AeEvent事件处理框架。 2.内存管理方面 Memcached使用预分配的内存池的方式, Redis使用现场申请内存的方式来存储数据,非临时数据是永远
26、不会被剔除的,还可以配置虚拟内存,获得高于物理内存的空间。Redis与Memcached的比较 3.数据一致性问题 Memcached提供了cas命令,可以保证多个并发访问操作同一份数据的一致性问题。 Redis没有提供cas 命令,并不能保证这点,Redis提供了事务的功能,可以保证一串 命令的原子性,中间不会被任何操作打断。 4.存储方式及其它方面Memcached基本只支持简单的key-value存储,不支持枚举,不支持持久化和复制等功能,Redis除key/value之外,支持数据结构,Redis可以直接扫描其dump文件,枚举出所有数据,还同时提供了持久化和复制等功能。Redis不足
27、之处 支持事务,将多个命令打包执行,但是任一命令有语法错误或key-value数据类型错误照常运行 数据结构不支持嵌套其他数据结构,比如类型为list的value不能再嵌套一个list。 单线程模型处理所客户请求的命令。对高并发支持不是很好 主从做冗余备份时,主机迭机后,不能从多个从机中自动选举出主机 当Dump.rdb文件中没有日志文件appendonly.aof最新数据时,不能从日志文件的最新命令的当前位置处增量更新dump.rdb数据文件。应用场景 1在主页中显示最新的项目列表。LPUSH用来插入一个内容ID,作为关键字存储在列表头部。LTRIM用来限制列表中的项目数最多为5000。 2
28、排行榜。排行榜按照得分进行排序。ZADD命令可以直接实现这个功能,而ZREVRANGE命令可以用来按照得分来获取前100名的用户,ZRANK可以用来获取用户排名应用场景 3.计数。进行各种数据统计,比如想知道什么时候封锁一个IP地址。INCRBY命令让这些变得很容易,通过原子递增保持计数;GET,SET用来重置计数器;过期属性用来确认一个关键字什么时候应该删除。 4.需要精准设定过期时间的应用 5.构建队列系统使用list可以构建队列系统,使用sorted set甚至可以构建有优先级的队列系统。应用场景 Sina微博关系+数字 sina微博中好友关系用hash存储,分为关注fromuid.fo
29、llowing与粉丝touid.follower key 为userid,fields 为 friends ids,value为addtime。添加一个关注:hset fromuid.following touid addtime 增加一个粉丝: hset touid.follower fromuid addtime相互关注:hsinter fromuid.following touid.follower 结束语当你尽了自己的最大努力时,失败也是伟大的,所以不要放弃,坚持就是正确的。When You Do Your Best, Failure Is Great, So DonT Give Up, Stick To The End谢谢大家荣幸这一路,与你同行ItS An Honor To Walk With You All The Way演讲人:XXXXXX 时 间:XX年XX月XX日
侵权处理QQ:3464097650--上传资料QQ:3464097650
【声明】本站为“文档C2C交易模式”,即用户上传的文档直接卖给(下载)用户,本站只是网络空间服务平台,本站所有原创文档下载所得归上传人所有,如您发现上传作品侵犯了您的版权,请立刻联系我们并提供证据,我们将在3个工作日内予以改正。