- 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!