NoSQL
简介
NoSQL指的是非关系型数据库,用于超大规模数据的存储。这类数据库一般存储的是非结构化数据,如key-value、文档、图像等不需要固定模式,无需多有操作就可以横向扩展。
CAP定理
在计算科学中,CAP定理又称为布鲁尔定理,它指的是对于一个分布式系统来说,不可能同时满足以下三点:
- 一致性(Consistency)所有节点在同一时间具有相同的数据
- 可用性(Avaliability) 保证每个请求不管成功或者失败都有响应
- 分隔容忍(Partition tolerance)系统中任意信息的丢失或失败都不会影响系统的继续运作
CAP原理核心是一个分布式系统不可能同时很好满足一致性、可用性和分区容错性三个需求,最多只能同时较好的满足两个。因此根据CAP原理将NoSQL数据库分成满足CA原则、满足CP原则和满足AP原则三大类:
- CA:单点集群,妈祖一致性、可用性的系统,通常在可扩展性上不太强大
- CP:满足一致性、分区容忍性的系统,通常性能并不是特别高
- AP:满足可用性,分区容忍性的系统,通常可能对一致性要求低一些
NoSQL分类
类型 | 部分代表 | 特点 |
---|---|---|
列存储 | Hbase、Cassandra、Hypertable | 顾名思义,是按列存储数据的。最大的特点是方便存储结构化和半结构化数据,方便做数据压缩,对针对某一列或者某几列的查询有非常大的IO优势。 |
文档存储 | MongoDB、CouchDB | 文档存储一般用类似json的格式存储,存储的内容是文档型的。这样也就有机会对某些字段建立索引,实现关系数据库的某些功能。 |
对象存储 | db4o、Versant | 通过类似面向对象语言的语法操作数据库,通过对象的方式存取数据。 |
key-value存储 | Tokyo Cabinet / Tyrant、Berkeley DB、MemcacheDB、Redis | 可以通过key快速查询到其value。一般来说,存储不管value的格式,照单全收。(Redis包含了其他功能) |
图存储 | Neo4J、FlockDB | 图形关系的最佳存储。使用传统关系数据库来解决的话性能低下,而且设计使用不方便。 |
xml数据库 | Berkeley DB XML 、BaseX | 高效的存储XML数据,并支持XML的内部查询语法,比如XQuery,Xpath。 |
数据库
Redis
redis是一个高性能的key-value数据库,是为了解决高并发、高扩展、大数据存储等一系列问题产生的数据库解决方案。Redis本质是一个key-value类型的内存数据库,整个数据库系统加载在内存当中进行操作,定期通过异步操作把数据库数据持久化,因此redis的性能非常出色。另外Redis支持保存多种数据结构,如string、list、set,hash等。Redis的主要缺点是数据库容量受到物理内存的限制,不能用作海量数据的高性能读写。
数据类型
string
- 常用命令:set/get/decr/incr/mget等
- 应用场景:普通的key/value存储都可以,string在redis内部默认是一个字符串
Hash
-
常用命令:hget/hset/hgetall等
-
应用场景 :我们要存储一个用户信息对象数据,其中包括用户ID、用户姓名、年龄和生日,通过用户ID我们希望获取该用户的姓名或者年龄或者生日;
-
实现方式:Redis的Hash实际是内部存储的Value为一个HashMap,并提供了直接存取这个Map成员的接口。如图所示,Key是用户ID, value是一个Map。这个Map的key是成员的属性名,value是属性值。这样对数据的修改和存取都可以直接通过其内部Map的Key(Redis里称内部Map的key为field),也就是通过 key(用户ID) + field(属性标签) 就可以操作对应属性数据。当前HashMap的实现有两种方式:当HashMap的成员比较少时Redis为了节省内存会采用类似一维数组的方式来紧凑存储,而不会采用真正的HashMap结构,这时对应的value的redisObject的encoding为zipmap,当成员数量增大时会自动转成真正的HashMap,此时redisObject的encoding字段为int。
List
- 常用命令:lpush/rpush/lpop/rpop/lrange等;
- 应用场景:Redis set对外提供的功能与list类似是一个列表的功能,特殊之处在于set是可以自动排重的,当你需要存储一个列表数据,又不希望出现重复数据时,set是一个很好的选择,并且set提供了判断某个成员是否在一个set集合内的重要接口,这个也是list所不能提供的;
- 实现方式:set 的内部实现是一个 value永远为null的HashMap,实际就是通过计算hash的方式来快速排重的,这也是set能提供判断一个成员是否在集合内的原因。
Sorted Set
- 常用命令:zadd/zrange/zrem/zcard等;
- 应用场景:Redis sorted set的使用场景与set类似,区别是set不是自动有序的,而sorted set可以通过用户额外提供一个优先级(score)的参数来为成员排序,并且是插入有序的,即自动排序。当你需要一个有序的并且不重复的集合列表,那么可以选择sorted set数据结构,比如twitter 的public timeline可以以发表时间作为score来存储,这样获取时就是自动按时间排好序的。
- 实现方式:Redis sorted set的内部使用HashMap和跳跃表(SkipList)来保证数据的存储和有序,HashMap里放的是成员到score的映射,而跳跃表里存放的是所有的成员,排序依据是HashMap里存的score,使用跳跃表的结构可以获得比较高的查找效率,并且在实现上比较简单。
各种数据类型的应用场景:
类型 | 简介 | 特性 | 场景 |
---|---|---|---|
string 字符串 | 二进制安全 | 可以包含任何数据,如jpg图像或者序列化对象,一个键最大能存储512M | 适合多种应用场景 |
Hash 字典 | 键值对集合,即编程语言中的map类型 | 适合存储对象,并且可以像数据库中update一个属性一样只修改某一项属性值 | 存储、读取、修改用户属性 |
List 列表 | 链表,双向链表 | 增删快,提供了某一段元素的API | 最新消息排行等功能;消息队列 |
Set 集合 | 哈希表实现,元素不重复 | 1. 添加、删除、查找的复杂度都是O(1);2. 为集合提供了求交集、并集、差集等操作 | 1. 共同好友;2、 利用唯一性,统计访问网站的所有独立ip;3. 好友推荐时,根据tag求交集,大于某个阈值就可以推荐 |
Sorted Set 有序集合 | 将Set中的元素增加了一个权重参数score,元素按照score有序排序 | 数据插入集合时,已经进行天然排序 | 1. 排行榜;2. 待权重消息队列 |
Redis应用场景
- 会话缓存:如存储token、用户密码等
- 消息队列
- 排行榜/点赞数等计数器
- 发布订阅
缓存失效策略
- viliate-lru:从已设置过期时间的数据集中挑选最近最少使用的数据淘汰
- volatile-ttl:从已设置过期时间的数据集中挑选将要过期的数据淘汰
- volatile-random:从已设置过期时间的数据集中任意选择数据淘汰
- allkeys-lru:从数据集中挑选最近最少使用的数据淘汰
- allkeys-random:从数据集中任意选择数据淘汰
- no-enviction(驱动):禁止驱逐数据
redis事务
在redis中MULTI/EXEC/DISCARD/WATCH这四个命令是实现事务的基石。
-
在事务中的所有命令都将会被串行化的顺序执行,事务执行期间,Redis不会再为其它客户端的请求提供任何服务,从而保证了事物中的所有命令被原子的执行。
-
和关系型数据库中的事务相比,在Redis事务中如果有某一条命令执行失败,其后的命令仍然会被继续执行。
-
我们可以通过MULTI命令开启一个事务,有关系型数据库开发经验的人可以将其理解为"BEGIN TRANSACTION"语句。在该语句之后执行的命令都将被视为事务之内的操作,最后我们可以通过执行EXEC/DISCARD命令来提交/回滚该事务内的所有操作。这两个Redis命令可被视为等同于关系型数据库中的COMMIT/ROLLBACK语句。
-
在事务开启之前,如果客户端与服务器之间出现通讯故障并导致网络断开,其后所有待执行的语句都将不会被服务器执行。然而如果网络中断事件是发生在客户端执行EXEC命令之后,那么该事务中的所有命令都会被服务器执行。
-
当使用Append-Only模式时,Redis会通过调用系统函数write将该事务内的所有写操作在本次调用中全部写入磁盘。然而如果在写入的过程中出现系统崩溃,如电源故障导致的宕机,那么此时也许只有部分数据被写入到磁盘,而另外一部分数据却已经丢失。Redis服务器会在重新启动时执行一系列必要的一致性检测,一旦发现类似问题,就会立即退出并给出相应的错误提示。此时,我们就要充分利用Redis工具包中提供的redis-check-aof工具,该工具可以帮助我们定位到数据不一致的错误,并将已经写入的部分数据进行回滚。修复之后我们就可以再次重新启动Redis服务器了。
redis持久化
- RDB:在指定的时间间隔对数据进行快照存储
- AOF:记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始数据
RDB
在redis中RDB持久化的触发分为两种:手动触发以及Redis定时触发。
-
手动触发
手动触发持久化的操作有两个:save
和bgsave
,区别在于是否阻塞Redis主线程的执行。 -
自动触发
save m n
是指在m秒内,如果有n个键发生改变,则自动触发持久化。flushall
用于清空redis数据库,在生产环境下一定慎用。- 主从同步触发:在redis主从复制中,当从节点执行全量复制操作时,主节点会执行bgsave命令,并将RDB文件发送给从节点,该过程会自动触发redis持久化。
RDB的优点:RDB 的内容为二进制的数据,占用内存更小,更紧凑,更适合做为备份文件;RDB 对灾难恢复非常有用,它是一个紧凑的文件,可以更快的传输到远程服务器进行 Redis 服务恢复;RDB 可以更大程度的提高 Redis 的运行速度,因为每次持久化时 Redis 主进程都会 fork 一个子进程,进行数据持久化到磁盘,Redis 主进程并不会执行磁盘 I/O 等操作;与后面介绍的 AOF 格式的文件相比,RDB 文件可以更快的重启。缺点:因为 RDB 只能保存某个时间间隔的数据,如果中途 Redis 服务被意外终止了,则会丢失一段时间内的 Redis 数据;RDB 需要经常 fork 才能使用子进程将其持久化在磁盘上。如果数据集很大,fork 可能很耗时,并且如果数据集很大且 CPU 性能不佳,则可能导致在几毫秒甚至一秒钟内 Redis 无法为客户端服务。
AOF
AOF(Append Only File)指的是追加到文件,所以 AOF 我们可以猜测它应该类似于日志文件一样,事实上也确实如此。但它和我们熟悉的数据库的预写式日志(Write Ahead Log,WAL)不同,WAL 是先把修改的数据记到日志文件中,然后执行相关操作;而 AOF 正好相反,Redis 是先执行命令、将数据写入内存,然后才记录日志。
优点: AOF 持久化保存的数据更加完整,AOF 提供了三种保存策略:每次操作保存、每秒钟保存一次、跟随系统的持久化策略保存,其中每秒保存一次,从数据的安全性和性能两方面考虑是一个不错的选择,也是 AOF 默认的策略,即使发生了意外情况,最多只会丢失 1s 钟的数据;AOF 采用的是命令追加的写入方式,所以不会出现文件损坏的问题,即使由于某些意外原因,导致了最后操作的持久化数据写入了一半,也可以通过 redis-check-aof 工具轻松的修复;AOF 持久化文件,非常容易理解和解析,它是把所有 Redis 键值操作命令,以文件的方式存入了磁盘。即使不小心使用 flushall 命令删除了所有键值信息,只要使用 AOF 文件,重启 Redis 即可恢复之前误删的数据,但前提是把 AOF 文件中最后的 flushall 命令删除之后再恢复,否则恢复完之后就又 flushall 了。缺点 对于相同的数据集来说,AOF 文件要大于 RDB 文件;在 Redis 负载比较高的情况下,RDB 比 AOF 性能更好;RDB 使用快照的形式来持久化整个 Redis 数据,而 AOF 只是将每次执行的命令追加到 AOF 文件中,因此从理论上说,RDB 比 AOF 更健壮。
参考:https://www.cnblogs.com/traditional/p/13296648.html
MongoDB
MongoDB 是一款流行的开源文档型数据库,具有高性能、高可用和易于扩展的特性。MongoDB将数据存储为一个文档,数据结构有键值对(key:value)组成。MongoDB文档类似于JSON对象。字段值可以包含其他文档、数组以及文档数据。以下是MongoDB的一些应用案例:
- Craiglist:使用MongoDB的存档数十亿记录
- FourSquare:基于位置的社交网络,在Amazon EC2的服务器上使用MongoDB分享数据
- Shutterfly:以互联网为基础的社会和个人出版服务,使用mongodb的各种持久化数据存储的要求
MongDB安装
- 拉取最新的mongodb版本镜像:
docker pull mongo:lastest
。 - 由于数据库数据需要持久化,因此需要新建一个挂载mongodb的文件夹,在这里假设成
/home/mongodb/data
- 启动镜像,执行以下的命令:
docker run -p 27017:27017 -v /home/mongodb/data:/data/db --name mongodb -d mongo
。此时mongodb可以进行操作了,但是不安全,因为还没有设置账号密码。任何人只要知道你的地址即可访问你的数据库。 - 在运行的镜像中,进入到容器的bin/bash环境,执行
mongo admin
进入mongodb,然后执行以下语句创建用户:db.createUser({ user: 'admin', pwd: 'Aa123456', roles: [ { role: "userAdminAnyDatabase", db: "admin" } ] });
- 重新运行容器:
docker run -p 27017:27017 -v /home/mongodb/data:/data/db --name mongodb -d mongo` --auth
在这里不推荐使用docker安装mongodb。
可视化界面工具
这里使用MongoDB Compass作为可视化界面工具。
概念解析
关系型数据库概念 | MongoDB概念 | 说明 |
---|---|---|
database | database | 数据库 |
table | collection | 数据库表/集合 |
row | document | 数据库记录行/文档 |
column | field | 数据字段/域 |
index | index | 索引 |
用户角色
用户角色 | 权限 |
---|---|
Read | 允许用户读取指定数据库 |
readWrite | 允许用户读写指定数据库 |
dbAdmin | 允许用户在指定数据库中执行管理函数,如索引创建、删除,查看统计或访问system.profile |
userAdmin | 允许用户向system.users集合写入,可以找指定数据库里创建、删除和管理用户 |
clusterAdmin | 只在admin数据库中可用,赋予用户所有分片和复制集相关函数的管理权限。 |
readAnyDatabase | 只在admin数据库中可用,赋予用户所有数据库的读权限 |
readWriteAnyDatabase | 只在admin数据库中可用,赋予用户所有数据库的读写权限 |
userAdminAnyDatabase | 只在admin数据库中可用,赋予用户所有数据库的userAdmin权限 |
dbAdminAnyDatabase | 只在admin数据库中可用,赋予用户所有数据库的dbAdmin权限。 |
root | 只在admin数据库中可用。超级账号,超级权限。 |
相关教程
HBase
关于HBase,我接触不多,以下内容摘抄自HBase 百度百科。HBase是一个分布式的、面向列的开源数据库。HBase不同于一般的关系数据库,它是一个适合于非结构化数据存储的数据库。另一个不同的是HBase基于列的而不是基于行的模式。
Neo4j
Neo4j是一个高性能的,NOSQL图形数据库,它将结构化数据存储在网络上而不是表中。它是一个嵌入式的、基于磁盘的、具备完全的事务特性的Java持久化引擎,但是它将结构化数据存储在网络(从数学角度叫做图)上而不是表中。Neo4j也可以被看作是一个高性能的图引擎,该引擎具有成熟数据库的所有特性。程序员工作在一个面向对象的、灵活的网络结构下而不是严格、静态的表中——但是他们可以享受到具备完全的事务特性、企业级的数据库的所有好处。Neo4j因其嵌入式、高性能、轻量级等优势,越来越受到关注。
Python实现
我已经用mongodb以及redis去模拟一个登录过程,详情可以参考我的repo:Flask-HTTPAuth
Redis
Redis的操作主要列出一些比较常用的命令代码以及对应的功能。
连接redis
import redis
r = redis.Redis(host=localhost, port=port, password=password, db='1')
r.set('name', 'pd')
val = r.get('name')
print(val)
r.close()
'''
连接池的方式链接
好处:使用connection pool来管理对一个redis server的所有连接,避免每次建立、释放连接的开销。
'''
pool = redis.ConnectionPool(host=localhost, port=port, password=password, max_connections=1000)
r = redis.Redis(connection_pool=pool)
r.set('test', 'test')
在上述代码中涉及r.set()
,里面涉及到以下的参数:
- ex:设置过期时间(秒),
r.set('food', 'mutton', ex=3)
- px:设置过期时间毫秒,
r.set('food', 'beef', px=3)
- nx:如果为True,则只有name不存在时,当前set操作才执行,
r.set('fruit', 'watermelon', nx=True)
- xx:如果为True,则只有name存在时,当前set操作才执行,
r.set('fruit', 'watermelon', xx=True)
具体的操作可以参考官网的api。
MongoDB
以一个用户信息为例:
# mongodb操作类
import pymongo
class MongoClient():
def __init__(self, host, port, *args, **kwargs):
self._mongo = pymongo.MongoClient(host=host, port=port, password=kwargs['password'], username=kwargs['username'])
def database(self, database):
db= self._mongo[database]
return db
def close(self):
self._mongo.close()
from .mongodb import MongoClient
from bson import ObjectId
def regist(userinfo):
try:
client = MongoClient(config.MONGODB_HOST, int(config.MONGODB_PORT), username = config.MONGODB_USER, password= config.MONGODB_PASSWORD)
db = client.database('test')
result = db['user'].insert_one(userinfo)
return str(result.inserted_id)
except Exception as e:
raise e
finally:
client.close()
def updateuser(condition, userinfo):
try:
client = MongoClient(config.MONGODB_HOST, int(config.MONGODB_PORT), username = config.MONGODB_USER, password= config.MONGODB_PASSWORD)
db = client.database('test')
result = db['user'].update_one(condition, {'$set':userinfo})
return result
except Exception as e:
raise e
finally:
client.close()
def deleteUser(objectid):
try:
condition={}
condition['_id'] = ObjectId(objectid)
client = MongoClient(config.MONGODB_HOST, int(config.MONGODB_PORT), username = config.MONGODB_USER, password= config.MONGODB_PASSWORD)
db = client.database('test')
result = db['user'].delete_one(condition)
return result
except Exception as e:
raise e
finally:
client.close()