分布式专题(5)- Nosql & Redis
什么是Nosql?
NoSQL(NoSQL = Not Only SQL ),意即“不仅仅是SQL”, 泛指非关系型的数据库,可以理解为SQL的一个有力补充。典型代表有MongDB、 Redis、Memcache等。特别是Redis,可以说是目前最火的Nosql数据库之一。
为什么选择Nosql?
随着 Internet的快速发展,越来越多的网站、应用系统需要支撑海量数据存储,高并发请求、高可用、高可扩展性等特性要求,传统的关系型数据库在应付这些调整已经显得力不从心,暴露了许多难以克服的问题。由此,各种各样的NoSQL数据库作为传统关系型数据的一个有力补充得到迅猛发展。此处推荐阅读《从Mysql到Nosql》和《NoSQL 还是 SQL ?》看看sql的发展历史和常用的几大类Nosql数据库。
无论是关系型数据库还是非关系型数据库,本质都是对数据进行增删改查而已,但Nosql和关系数据库之间还是有一些区别的,如在储存方法、存储结构、存储规范、存储扩展、查询方法、事务、性能上,二者都有较大的差异。对比如下:
储存方法:关系数据库是表格式的,因此存储在表的行和列中。在它们之间关联协作存储很容易,并且提取数据很方便。另一方面,Nosql数据库是一个庞大的组。通常存储在数据集中,就像文档、键值对或图形结构一样。
存储结构:关系数据库对应于结构化数据。数据表预先定义结构(列定义),结构描述数据的形式和内容。这对数据建模至关重要,虽然预定义结构带来了可靠性和稳定性,但修改这些数据更加困难。 Nosql数据库基于动态结构,使用和非结构化数据。由于Nosql数据库是动态结构,因此可以轻松适应数据类型和结构的变化。
存储规范:关系数据库的数据存储为了获得更高的规范性,数据被分段为最小的关系表,以避免重复并实现简化的空间利用。虽然管理非常清楚,但是当单个操作设计为多个表时,数据管理有点麻烦。虽然Nosql数据存储在平面数据集中,但数据通常可以复制。单个数据库很少分开,但作为一个整体存储,因此整个数据块更易于读写。
存储扩展:这可能是两者之间最大的区别,关系数据库是垂直扩展,这意味着要提高处理能力,需要使用速度更快的计算机。由于数据存储在关系表中,因此操作中的性能瓶颈可能涉及多个表,需要通过提高计算机性能来克服。虽然有很大的扩展空间,但最终会达到纵向扩张的上限。 Nosql数据库被扩展,其存储自然分布。您可以通过向资源池添加更多常见数据库服务器来共享负载。
查询方法:关系数据库通过结构化查询语言(我们通常称之为SQL)对数据库进行操作。 SQL支持数据库CURD操作非常强大,是业界的标准用法。 Nosql查询使用非结构化查询语言(UnQl)对块中的数据进行操作,这是非标准的。关系数据库表中的主键的概念对应于存储在Nosql中的文档的ID。关系数据库使用预定义的优化方法(例如索引)来加速查询操作,而Nosql更简单,更准确的数据访问模式。
事务:关系数据库遵循ACID规则(原子性、一致性、隔离、耐久性),而Nosql数据库遵循BASE原则(基本可用虚拟、软/灵活事务)(软状态)、最终一致性)。由于关系数据库中数据的高度一致性,对事务的支持非常好。关系数据库支持对事务原子性的细粒度控制,并且易于回滚事务。 Nosql数据库在CAP中是可选的(一致性、可用性、分区容差),因为基于节点的分布式系统难以完全满足,因此对事务的支持不是很好,尽管也可以使用事务,但不是Nosql的闪点。
性能:关系数据库的数据存储在磁盘中,面对高并发读写性能非常差,面对海量数据,效率非常低。 Nosql存储的格式是键值类型,存储在内存中,非常容易存储,数据一致性较弱。 Nosql不需要sql解析来提高读写性能。
怎么用Nosql?
对数据的处理无外乎都是增删改查。本篇主要是介绍Nosql三剑库MongoDb、Memcache和Redis的基本使用。特别是Redis,可以说是目前最火的Nosql数据库,也是面试必问点之一,本篇将花大篇幅对其作出说明。
下面我们通过实例来讲解Memcache、Mongodb即Redis的概念和使用。
Memcache
Memcache是一个高性能的分布式的内存对象缓存系统,通过在内存里维护一个统一的巨大的hash表,它能够用来存储各种格式的数据,包括图像、视频、文件以及数据库检索的结果等。简单的说就是将数据调用到内存中,然后从内存中读取,从而大大提高读取速度。
Memcache的工作流程:
MemCache的工作流程如下:先检查客户端的请求数据是否在memcached中,如有,直接把请求数据返回,不再对数据库进行任何操作;如果请求的数据不在memcached中,就去查数据库,把从数据库中获取的数据返回给客户端,同时把数据缓存一份到memcached中;每次更新数据库的同时更新memcached中的数据,保证一致性;当分配给memcached内存空间用完之后,会使用LRU(Least Recently Used,最近最少使用)策略加上到期失效策略,失效数据首先被替换,然后再替换掉最近未使用的数据。
Memcache的优点:
Memcached可以利用多核优势,单实例吞吐量极高,可以达到几十万QPS(取决于key、value的字节大小以及服务器硬件性能,日常环境中QPS高峰大约在4-6w左右)。适用于最大程度扛量。
支持直接配置为session handle。
Memcache的缺点:
只支持简单的key/value数据结构,不像Redis可以支持丰富的数据类型。
无法进行持久化,数据不能备份,只能用于缓存使用,且重启后数据全部丢失。
无法进行数据同步,不能将MC中的数据迁移到其他MC实例中。
Memcache内存分配采用Slab Allocation机制管理内存,value大小分布差异较大时会造成内存利用率降低,并引发低利用率时依然出现踢出等问题。需要用户注重value设计。
Memcache默认能接受的key的最大长度是250个字符,单个value的大小被限制在1M byte之内,不适合存储较大的数据。
Memcache的应用场景:
动态系统中减轻数据库负载,提升性能;
做缓存,适合多读少写,大数据量的情况(如人人网大量查询用户信息、好友信息、文章信息等)。
Mongodb
MongoDB是一个跨平台的,基于分布式文件存储的NoSQL数据库,由C++语言编写的。MongoDB是以文档的形式存储数据,数据结构由键值(key:value)对组成,类似JSON。
MongoDB 是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的。MongoDB的结构中,最小的单位为文档(类似MySQL的行),每一个文档用的是BSON形式来存储(类似JSON),文档的上一层为集合(类似MySQL的表),再上一级为库(类似MySQL的数据库)。
mongodb与mysql不同的是,mysql的每一次更新操作都会直接写入硬盘,但是mongo不会,做为内存型数据库,数据操作会先写入内存,然后再会持久化到硬盘中去。具体是在mongodb启动时,专门初始化一个线程不断循环(除非应用crash掉),用于在一定时间周期内来从defer队列中获取要持久化的数据并写入到磁盘的journal(日志)和mongofile(数据)处,当进行CUD操作时,记录(Record类型)都被放入到defer队列中以供延时批量(groupcommit)提交写入,默认的时间周期为90s。
Mongodb的优点:
更高的写负载,MongoDB拥有更高的插入速度。
处理很大的规模的单表,当数据表太大的时候可以很容易的分割表,添加1个新字段不会对旧表格有任何影响,整个过程会非常快速。
高可用性,设置M-S不仅方便而且很快,MongoDB还可以快速、安全及自动化的实现节点(数据中心)故障转移。
查询效率高,MongoDB支持二维空间索引,比如管道,因此可以快速及精确的从指定位置获取数据。MongoDB在启动后会将数据库中的数据以文件映射的方式加载到内存中。如果内存资源相当丰富的话,这将极大地提高数据库的查询速度。
支持持久化,Mongodb做为内存型数据库,数据操作会先写入内存,然后再会持久化到硬盘中去。
Mongodb的缺点:
不支持事务。
MongoDB占用内存过大 。
MongoDB没有成熟的维护工具。
Mongodb的应用场景
主要解决海量数据的访问效率问题。
Redis
Redis(Remote Dictionary Server)是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。是当前最热门的Nosql数据库之一,也是本篇要主要讲解的内容。以下内容主要是讲解它的安装和基本命令使用,及redis用做数据缓存(商品数据、新闻、热点数据)、单点登录、秒杀、抢购、网站访问排名、应用的模块开发等应用场景。
redis的优势:
运行在内存,性能极高,官方说法Redis能读的速度是110000次/s,写的速度是81000次/s 。
支持持久化,即可以将内存中的数据异步写入到硬盘中,同时不影响继续提供服务
支持数据结构丰富(string(字符串),list(链表),set(集合),zset(sorted set - 有序集合))和Hash(哈希类型)
Redis安装
1.下载redis安装包
wget http://download.redis.io/releases/redis-3.2.1.tar.gz
2.解压安装包并安装
tar xzf redis-3.2.1.tar.gz
cd redis-3.2.1
make
make时报错make: cc: Command not found make: * [adlist.o] Error 127,则需要安装gcc: yum -y install gcc
make时报错fatal error: jemalloc/jemalloc.h: No such file or directory,则需要改命令为:make MALLOC=libc
make完后 redis-3.2.1目录下会出现编译后的redis服务程序redis-server,还有用于测试的客户端程序redis-cli,两个程序位于安装目录 src 目录下
3.修改redis为后台运行
vim redis.conf
将daemonize no 改为 daemonize yes,如果不修改,该窗口将无法往下执行操作了
redis采用的是单进程多线程的模式。当redis.conf中选项daemonize设置成yes时,代表开启守护进程模式。在该模式下,redis会在后台运行,并将进程pid号写入至redis.conf选项pidfile设置的文件中,此时redis将一直运行,除非手动kill该进程。
4.启动服务端
cd src
./redis-server ../redis.conf
redis的配置文件也是挺重要的内容。可以参考《redis3.2 配置文件详解》
5.启动客户端
./redis-cli
在以上实例中我们连接到本地的 redis 服务。并可执行 PING 命令用于检测 redis 服务是否启动。如果需要在远程 redis 服务上执行命令,同样我们使用的也是 redis-cli 命令:redis-cli -h host -p port
6.关闭客户端、服务端
关闭服务端(客户端未连接时): ./redis-cli shutdown
关闭服务端(客户端连接时):shutdown save | nosave , 然后 quit
关闭客户端:quit,或者 exit,或者 Ctrl + C
Redis常用命令
Redis是一种键值对(K-V)数据库,Redis能够支持五种数据类型:string(字符串),list(链表),set(集合),zset(有序集合)和Hash(哈希类型),我们先来看下Redis的常用命令,常用命令熟悉了,后面看它实现的功能也就容易理解了。
Redis 键命令
Redis是一种键值对(K-V)数据库,它对数据的操作都是基于键,下面我们看看 Redis 键相关的基本命令:
keys pattern
查找所有符合给定模式( pattern)的 key 。
如 keys * 查找Redis中所有的键,但会影响CPU,且会造成redis锁,注意线上慎用该命令。
Redis在2.8.0版本新增了众望所归的scan操作,从此再也不用担心敲入了keys *, 然后举起双手看着键盘等待漫长的系统卡死了···
exists key
检查给定 key 是否存在。
type key
返回 key 所储存的值的类型。(五种类型:string、list、set、zset、hash,key不存在时返回none)
del key
该命令用于在 key 存在时删除 key。
expire key seconds
为给定 key 设置过期时间,以秒计
pexpire key milliseconds
设置 key 的过期时间以毫秒计。
expireat key timestamp
expireat 的作用和 expire 类似,都用于为 key 设置过期时间。 不同在于 expireat 命令接受的时间参数是 UNIX 时间戳(unix timestamp)。
pexpireat key milliseconds-timestamp
设置 key 过期时间的时间戳(unix timestamp) 以毫秒计
ttl key
以秒为单位,返回给定 key 的剩余生存时间(TTL, time to live)。( key 不存在时,返回 -2 。 当key 存在但未设置剩余生存时间时,返回 -1)
pttl key
以毫秒为单位返回 key 的剩余的过期时间。( key 不存在时,返回 -2 。 当key 存在但未设置剩余生存时间时,返回 -1)
persist key
移除 key 的过期时间,key 将持久保持。
randomkey
从当前数据库中随机返回一个 key 。(不建议使用)
rename key newkey
修改 key 的名称
renamenx key newkey
仅当 newkey 不存在时,将 key 改名为 newkey 。
move key db
将当前数据库的 key 移动到给定的数据库 db 当中。
dump key
序列化给定 key ,并返回被序列化的值。(序列化最终的目的是为了对象可以跨平台存储,和进行网络传输。而我们进行跨平台存储和网络传输的方式就是IO,而我们的IO支持的数据格式就是字节数组。)
restore key ttl serialized-value
反序列化给定的序列化值,并将它和给定的 key 关联。(参数 ttl 以毫秒为单位为 key 设置生存时间;如果 ttl 为 0 ,那么不设置生存时间)
Redis 字符串(string)
Redis字符串(string)类型是最常见到的数据结构,它既可以存储文字(比如“hello world”),又可以存储数字(比如整数10086和浮点数3.14),还可以存储二进制数据(比如10010100),字符串值能存储的最大容量为512M。
字符串(string)常用命令
set key value
设置指定 key 的值。(如果key已存在则覆盖其值)
setnx key value
只有在 key 不存在时设置 key 的值。
get key
获取指定 key 的值。
getrange key start end
返回 key 中字符串值的子字符
getset key value
将给定 key 的值设为 value ,并返回 key 的旧值(old value)。
mset key value [key value ...]
同时设置一个或多个 key-value 对。
mget key1 [key2..]
获取所有(一个或多个)给定 key 的值。
setex key seconds value
将值 value 关联到 key ,并将 key 的过期时间设为 seconds (以秒为单位)。
psetex key milliseconds value
这个命令和 SETEX 命令相似,但它以毫秒为单位设置 key 的生存时间,而不是像 SETEX 命令那样,以秒为单位。
strlen key
返回 key 所储存的字符串值的长度。
incr key
将 key 中储存的数字值增一。(若key不存在则生成一个新的key且值为1,若值非数字则报错)
incrby key increment
将 key 所储存的值加上给定的增量值(increment) 。(其它规则同incr key)
decr key
将 key 中储存的数字值减一。(其它规则同incr key)
decrby key decrement
key 所储存的值减去给定的减量值(decrement) 。(其它规则同incr key)
append key value
如果 key 已经存在并且是一个字符串, APPEND 命令将指定的 value 追加到该 key 原来值(value)的末尾。
Redis 列表(list)
Redis列表(list)类型是用来存储多个有序的字符串。在Redis中,可以对列表的两端进行插入(push)和弹出(pop)操作,还可以获取指定范围的元素列表、获取指定索引下标的元素等。列表是一种比较灵活的数据结构,它可以充当栈和队列的角色,在实际开发上有很多应用场景。
Redis 中每个 list 可以存储40多亿个元素(2的32次方-1个)。
列表(list)常用命令
rpush key value1 [value2]
在列表中添加一个或多个值,列表不存在时则创建并添加值。
rpushx key value
为已存在的列表添加值,列表不存在时则不会创建。
lpush key value1 [value2]
将一个或多个值插入到列表头部,列表不存在时则创建并添加值。
lpushx key value
将一个值插入到已存在的列表头部,列表不存在时则不会创建。
llen key
获取列表长度
lindex key index
通过索引获取列表中的元素,如 lindex key 0 获取列表中的首个元素, lindex key -1 获取列表中的最后一个元素
lset key index value
通过索引设置列表元素的值
lrange key start stop
获取列表指定范围内的元素,如 lrange key 0 -1 获取列表中的所有元素
linsert key BEFORE|AFTER value newValue
在列表的元素前或者后插入元素(newValue),当列表不存在或指定元素(value)不存在于列表中时不执行任何操作。
ltrim key start stop
对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除。
lrem key count value
根据参数 COUNT 的值,移除列表中与参数 VALUE 相等的元素。如count为0,则移除表中所有与 VALUE 相等的值;count不为0,则从头或尾起移除|count|个与value相等的元素。
lpop key
移出并获取列表的第一个元素
blpop key1 [key2 ] timeout
移出并获取列表的第一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。
rpop key
移出并获取列表的最后一个元素
rpoplpush source destination
移除列表的最后一个元素,并将该元素添加到另一个列表头部并返回。如果destination不存在则创建并添加。
brpop key1 [key2 ] timeout
移出并获取列表的最后一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。
brpoplpush source destination timeout
从列表中弹出一个值,将弹出的元素插入到另外一个列表中并返回它;如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。