Nosql三步曲1. NoSQL三步曲
朱国能-Gallen.Chu
http://www.84zhu.com
guoneng.zhu@gmail.com
2. 目录
海选
• NoSQL选型
• Redis面对面
相亲
结婚
• Redis实践
4. NoSQL选型
用MongoDB的一句设计哲学开始
• Databases are specializing – the “one size
fits all” approach no longer applies.
5. 我们为什么要使用NOSQL非关系数据库?
High
• 对数据库高并发读写的需求
performance
• 对海量数据的高效率存储和访
Huge Storage
问的需求
High Scalability
• 对数据库的高可扩展性和高可
&& High
Availability 用性的需求
7. 选型原则
高并发快
速读写
CPU资源
可用性
消耗
文档齐全/
一致性
社区活跃
原则
客户端
可扩展
/API支持
海量数据
…… 的高效读
写
8. 选型原则
Consistency
CAP Availability
Tolerance of
network
Partition
Basically
三大基石
Available
BASE Soft state
Eventually
最终一致性 consistent
理论依据 五分钟法则
10. 我们需要什么?
• 高并发快速读写
• 海量数据高效读写
• 高可用性
• 排行、热门、分类数据结构灵活
• 简单的API、客户端支持
• 文档完善、社区活跃
• 支持良好、版本更新活跃
• 可以容忍最终一致性
• 可以容忍client分区、集群
12. What is Redis?
What is
sponsored by:
• 全名 REmote DIctionary Server
• Redis is an open source, advanced key-value store. It is often
referred to as a data structure server since keys can
contain strings, hashes, lists, sets and sorted sets.
• key value store? cache? memory database? data structure
server?
13. 优点
• FAST both read and write
• data can dump to disk
• master-slave
• many useful data structure
• Active community/ Antirez
14. 缺点
• Physical memory limit
• Auto-sharding / Cluster
• Scale ability
• Automatic failover switch
15. Fast
• 根据 Redis 官方的测试结果:在 50 个并发的情况下请求 10w 次,写
的速度是 110000次/s,读的速度是 81000 次/s
• 测试环境:
• 1. 50 个并发,请求 100000 次
• 2. 读和写大小为 256bytes 的字符串
• 3. Linux2.6 Xeon X3320 2.5GHz 的服务器上
• 4. 通过本机的 loopback interface 接口上执行
• 地址:
• http://code.google.com/p/redis/wiki/Benchmarks
• http://redis.io/topics/benchmarks
18. Why Fast
• Libevent。和Memcached不同,Redis并没有选择libevent。Libevent
为了迎合通用性造成代码庞大(目前Redis代码还不到libevent的1/3)及
牺牲了在特定平台的不少性能。Redis用libevent中两个文件修改实现
了自己的epoll event loop(4)。业界不少开发者也建议Redis使用另外
一个libevent高性能替代libev,但是作者还是坚持Redis应该小巧并去
依赖的思路。一个印象深刻的细节是编译Redis之前并不需要执行
./configure。
• CAS问题。CAS是Memcached中比较方便的一种防止竞争修改资源
的方法。CAS实现需要为每个cache key设置一个隐藏的cas token,
cas相当value版本号,每次set会token需要递增,因此带来CPU和内
存的双重开销,虽然这些开销很小,但是到单机10G+ cache以及
QPS上万之后这些开销就会给双方相对带来一些细微性能差别(5)。
19. Useful data structure
键(keys) 值(values)
page:index.html <html><head>[...] String
users_logged_in_today { 1, 2, 3, 4, 5 } Sets
latest_post_ids [201, 204, 209,..] List
users_and_scores joe ~ 1.3483 ZSets
bert ~ 93.4
fred ~ 283.22
chris ~ 23774.17
20. DUMP
• snapshot(RDB)
– 定时将内存的快照(snapshot)持久化到硬盘,这种方法缺点是持久
化之后如果出现crash则会丢失一段数据。
• AOP(append only mode)
– 写入内存数据的同时将操作命令保存到日志文件,在一个并发更
改上万的系统中,命令日志是一个非常庞大的数据,管理维护成
本非常高,恢复重建时间会非常长,这样导致失去aof高可用性本
意。
21. RDB
• Fork一个进程,利用copy on write原理,遍历所有的db的hash
table,进行整库的dump
• Save命令,shutdown命令,slave启动都会触发
• 利用LZF进行压缩
• 持久化触发条件:
• #sava 900 1
• #save 300 10
• #save 60
24. 存储模式
• VM方式:将数据分页存放,由应用(如Redis)或者操作系统(如Varnish)将访
问量较少的页即冷数据swap到磁盘上,访问多的页面由磁盘自动换出到内存
中。应用实现VM缺点是代码逻辑复杂,如果业务上冷热数据边界并不分明,
则换入换出代价太高,系统整体性能低。不少抢鲜的网友在微博上也反馈过
使用VM种种不稳定情况。 (目前Redis使用的方式)
• 磁盘方式:所有的数据读写访问都是基于磁盘,由操作系统来只能的缓存访
问的数据。由于现代操作系统都非常聪明,会将频繁访问的数据加入到内存
中,因此应用并不需要过多特殊逻辑。MongoDB就是这种设计方式。这种方
式也有一些已知的缺点,比如操作MMap写入磁盘由操作系统控制,操作系
统先写哪里后写哪里应用并不知情,如果写入过程中发生了crash则数据一致
性会存在问题。 (作者称其为MongoDB的方式)
• Diskstore:实际原理和mysql+memcache方式类似,只不过将两者功
能合二为一到一个底层服务中,简化了调用。(作者打算使用的新方
式,alpha版本,Demo)
25. Replication
• master可以有多个slave
• 除了多个slave连到相同的master外,slave也可以连接其他slave形成
图状结构
• 主从复制不会阻塞master。也就是说当一个或多个slave与master进
行初次同步数据时,master可以继续处理client发来的请求。相反
slave在初次同步数据时则会阻塞不能处理client的请求。
• 主从复制可以用来提高系统的可伸缩性,我们可以用多个slave 专门用
于client的读请求,比如sort操作可以使用slave来处理。也可以用来
做简单的数据冗余
• 可以在master禁用数据持久化,只需要注释掉master 配置文件中的
所有save配置,然后只在slave上配置数据持久化
• slaveof 172.16.3.35 6379 #指定master的ip和端口
• slave重连无增量复制,slave表重建
27. 应用场景
• 取最新N个数据的操作
• 排行榜应用,取TOP N操作
• 需要精准设定过期时间的应用
• 计数器应用
• Uniq操作,获取某段时间所有数据排重值
• 实时系统,反垃圾系统
• Pub/Sub构建实时消息系统
• 构建队列系统
• 缓存
28. 2.4版本改进
• 对Sorted Sets的内存优化
• RDB文件持久化提速
• 提供批量写入功能
• 改用jemalloc的内存分配模式
• 减少 copy-on-write 使用
• INFO输出内容增强
• VM机制彻底废弃
• 总的来说2.4版本会在各方面有性能上的提升
29. Why 6379
Why?
6379
6379在是手机按键上MERZ对应的号码
MERZ取自意大利歌女Alessia Merz的名字
31. Redis容量规划及建议
• 数据项: value保存的内容是什么,如用户资料
• 业务数据类型: 如String, List, set
• 数据大小: 如100字节
• 记录数: 如100万条(决定是否需要拆分)
• 数据增多如何扩展?
• 读写瓶颈如何扩展?
• 不仅要规划还要严密监控!
• sharding策略与HA(rolling/pre-sharding/双写/多slave)
• 使用Pipeline减小网络IO/原子操作
• Redis的最佳使用方式是 in-memory
32. Redis数据库的键值设计(一)
mysql> select * from login;
+---------+----------------------------+-------------------------+--------------------------------------+
| user_id | name | login_times | last_login_time |
+---------+----------------------------+-------------------------+--------------------------------------+
| 1 | Doug.Wang | 5| 2011-10-01 00:00:00 |
| 2 | Jeff.Tu | 1| 2011-10-01 00:00:00 |
| 3 | Gallen.Chu | 2| 2011-11-01 00:00:00 |
+----------+---------------------------+-------------------------+-------------------------------------+
key表名:主键值:列名
Set login:1:login_times 5
Set login:2:login_times 1
Set login:3:login_times 2
Set login:1:last_login_time 2011-10-1
Set login:2:last_login_time 2011-10-1
Set login:3:last_login_time 2011-11-1
Set login:1:name “Doug.Wang”
Set login:2:name “Jeff.Tu””
Set login:3:name “Gallen.Chu”
33. Redis数据库的键值设计(二)
排序需求
zadd post:post_times 15 1
zadd post:post_times 11 2
zadd post:post_times 28 3
#对该应用的评论次数自增1
ret = r.zincrby(“post:post_times", 1, app_id)
获得评论次数最多的应用,逆序排列取的排名第N的应用
ret = r.zrevrange(“post:post_times", 0, N-1)
34. Redis数据库的键值设计(三)
Set app:1:name "冒泡音乐盒"
Set app:2:name "股票指南"
Set app:3:name "幻想三国"
sadd appTag:application 1
sadd appTag:application 2
sadd appTag:tools 2
sadd appTag:webgame 3
即属于application又属于tools的应用?
inter_list = redis.sinter("appTag:application", "appTag:tools")
即属于application,但不属于tools的应用
inter_list = redis.sdiff("appTag:application", "appTag:tools")
属于application和属于tools的书的合集?
inter_list = redis.sunion("appTag:application", "appTag:tools")
35. Example
• A_start 10, A_end 20
• B_start 30, B_end 40
• redis 127.0.0.1:6379> zadd ranges 10 A_start (integer) 1
• redis 127.0.0.1:6379> zadd ranges 20 A_end (integer) 1
• redis 127.0.0.1:6379> zadd ranges 30 B_start (integer) 1
• redis 127.0.0.1:6379> zadd ranges 40 B_end (integer) 1
• redis 127.0.0.1:6379> zrangebyscore ranges (15 +inf LIMIT 0 1 1)
"A_end“
• redis 127.0.0.1:6379> zrangebyscore ranges (25 +inf LIMIT 0 1 1)
"B_start"
36. 使用hset代替set
对象:skyapp
id : 1
name : 天气助手
appid : 2914
created_time : 132013777338
HSET skyapp:1 name “天气助手”
HSET skyapp:1 appid 2914
HSET skyapp:1 created_time 132013777338
• 结构化数据存取
---------------------------------------------------
• 提高内存使用率 HLEN skyapp:1 == 3
HGET skyapp:1 name == “天气助手”
HKEYS skyapp:1 == name , appid, created_time
HGETALL skyapp:1 ==
name => 天气助手
time => 2914
created_time => 132013777338
38. 不要使用VM
• Redis VM 不靠谱,作者在2.4版本中已经彻底放弃了VM
– 症状:
• 代码逻辑复杂
• 系统整体性能低
• 如业务冷热数据边界不分明,换入换出代价太高
• 数据DUMP的时候内存耗光,CPU100%
– 方案:
• 加大物理内存
• 数据sharding
40. 集群架构(二)
单机多节点 ( 节点数=CPU - 1 ) 分布式缓存 (一致性哈希)
Redis1:6380 每天2点 6G
redis
redis
Redis2:6381 每天3点 6G
Redis3:6382 每天4点 6G
预留6G redis redis
分时间段 bgrewriteaof
充分提高内存使用率
42. 集群架构(四)
•All nodes are directly connected with a
service channel.
•TCP baseport+4000, example 6379 ->
10379.
•Node to Node protocol is binary,optimized
for bandwidth and speed.
•Clients talk to nodes as usually, using ascii
protocol, with minor additions.
•Nodes don't proxy queries.
45. 附:参数说明(一)
配置项 说明
daemonize no 是否以后台进程运行,默认为no
pidfile 如以后台进程运行,则需指定一个pid,默认/var/run/redis.pid
port 6379 监听端口,默认为6379
bind 127.0.0.1 绑定主机IP,默认值为127.0.0.1
timeout 300 超时时间,默认为300(秒)
loglevel 日志记录等级,有4个可选值,debug,verbose(默认值),notice,warning
logfile 日志记录方式,默认值为stdout
slaveof 当本机为从服务时,设置主服务的IP及端口。在启动时,REDIS会自动从
MASTER上把数据先同步过来,而无需我们手动进行。MASTER上每有一次
落地保存,会自动向SLAVE进行同步。
masterauth 当本机为从服务时,设置主服务的连接密码
requirepass 连接密码
maxclients 最大客户端连接数,默认不限制
maxmemory 设置最大内存,达到最大内存设置后,Redis会先尝试清除已到期或即将到期
的Key,当此方法处理后,任到达最大内存设置,将无法再进行写入操作。
46. 附:参数说明(二)
配置项 说明
save 900 1 900秒内有1个改变,
save 300 10 300秒内有10个改变,
save 60 10000 60秒内有10000个改变,
redis就会内存中的key保存到数据库文件中去
rdbcompression 存储至本地数据库时是否压缩数据,默认为yes
dbfilename 本地数据库文件名,默认值为dump.rdb
dir 本地数据库存放路径,默认值为 ./
Appendonly 是否在每次更新操作后进行日志记录,开启AOF
appendfilename 更新日志文件名,默认值为appendonly.aof
appendfsync 新日志条件,共有3个可选值。no表示等操作系统进行数据缓存同步到磁盘,
always表示每次更新操作后手动调用fsync()将数据写到磁盘,everysec表示
每秒同步一次(默认)
hash-max- 当hash中包含超过指定元素个数并且最大的元素没有超过临界时,hash将以
zipmap-entries 一种特殊的编码方式(大大减少内存使用)来存储
hash-max- hash中一个元素的最大值
zipmap-value
47. 附:参数说明(三)
配置项 说明
vm-enabled 是否使用虚拟内存,默认值为no
vm-swap-file 虚拟内存文件路径,默认值为/tmp/redis.swap,不可多个Redis实例共享
vm-max-memory 将所有大于vm-max-memory的数据存入虚拟内存,无论vm-max-memory
设置多小,所有索引数据都是内存存储的 (Redis的索引数据就是keys),也就
是说,当vm-max-memory设置为0的时候,其实是所有value都存在于磁盘。
默认值为0
vm-page-size 虚拟内存文件以块存储,默认每块32bytes
vm-pages 虚拟内在文件的最大数,默认134217728
vm-max-threads 可以设置访问swap文件的线程数,设置最好不要超过机器的核数,如果设置
为0,那么所有对swap文件的操作都是串行的.可能会造成比较长时间的延
迟,但是对数据完整性有很好的保证.