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