NoSQL 数据库
一、NoSQL 数据库简介
NoSQL(Not Only SQL)数据库是一种非关系型数据库,它不依赖于传统的关系型数据库管理系统(RDBMS)的表格模型。NoSQL 数据库通常是为了解决大规模数据集合的存储和检索问题而设计的,特别是在 大数据 和高并发 的应用场景中。
以下是 NoSQL 数据库的主要优势:
- 可扩展性:NoSQL 数据库可以轻松地扩展到处理海量数据,而无需昂贵的硬件或复杂的配置。
- 高并发性:NoSQL 数据库可以处理大量并发请求,使其非常适合处理实时数据和高流量应用程序。
- 灵活性:NoSQL 数据库提供灵活的数据模型,可以轻松适应不断变化的数据需求,而无需更改架构。
- 高可用性:NoSQL 数据库通常具有高可用性,并提供冗余和故障转移功能,以确保数据始终可用。
同时 NoSQL 数据库也存在一些不足:
- 数据一致性:NoSQL 数据库为了提高系统的可用性和分区容忍性,牺牲了一致性,采用最终一致性模型(BASE),这与关系型数据库的 ACID 属性(原子性、一致性、隔离性、持久性)不同。这意味着在某些情况下,NoSQL 数据库可能无法保证所有节点看到的数据是一致的。
- 事务处理:NoSQL 数据库通常不支持传统的 ACID 事务,使得跨多个操作维持数据的完整性比较困难,即难以支持复杂的事务操作。
- 查询功能:NoSQL 数据库的查询功能不如关系型数据库强大,这可能会限制复杂查询和数据分析的效率和灵活性。
- 数据建模:NoSQL 数据库的非关系数据模型需要不同的建模技术,如文档型、键值型、列存储和图形数据库等。虽然提供了灵活性,但需要较高的学习成本。
二、NoSQL 数据库类型
分类 | 场景 | 结构 | 产品 |
---|---|---|---|
键值数据库 | 内容缓存,主要用于处理大量数据的高访问负载,也用于一些日志系统等 | Key-Value 的键值对,通常用 hash table 来实现 | Redis,Memcached |
列存储数据库 | 分布式的文件系统 | 以列簇式存储,将同一列数据存在一起 | Cassandra,HBase |
文档型数据库 | Web 应用 | 以 JSON、BSON、XML 等格式记录的文档 | MongoDB,ElasticSearch |
图形数据库 | 社交网络,推荐系统,知识图谱等 | 图结构 | Neo4J |
三、常见 NoSQL 数据库
(一) Redis
Redis(Remote Dictionary Server)是一个开源的高性能键值对(key-value)数据库。Redis 的数据保存在内存中,是一款内存数据库,读写速度非常快,通常用作数据库、缓存和消息传递系统。
1. 数据结构与操作指令
Redis 支持 8 种数据结构,分别用于不同的业务场景:
数据结构 | 说明 | 场景 |
---|---|---|
字符串(String) | 文本数据,二进制数据 | - 缓存功能:存储对象、配置信息等。 - 计数器:实现简单的计数功能。 - 消息队列:存储消息内容。 |
列表(List) | 一个有序集合,支持在两端推入和弹出元素 | - 消息队列:实现先进先出(FIFO)的消息队列。 - 保持时序:展示最新文章、最新动态。 |
集合(Set) | 无序集合,自动去重 | - 标签系统:存储用户标签。 - 交并差运算:找出共同好友。 |
有序集合(ZSet) | 与 Set 类似,但每个元素关联了一个分数,可以进行排序 | - 排行榜:根据分数排序。 - 范围查询:实现范围查询功能。 |
哈希表(Hash) | 键值对集合,可以存储对象 | - 对象信息:存储用户信息、订单信息等。 - 缓存记录:存储复杂数据结构。 |
位图(Bitmap) | 使用 bit 位来存储信息,适合做标记或计数 | - 记录状态:标记用户是否在线。 - 签到功能:标记用户签到状态。 |
基数统计(HyperLogLog) | 用于基数统计,可以估算集合中不同元素的数量 | - 大数据计数:统计网站 UV。 |
地理空间索引(Geospatial) | 存储地理位置信息,可以进行地理位置查询 | - 就近推荐:根据地理位置查找附近的用户或服务。 - 地图服务:存储和查询地理位置信息。 |
Redis 常见的操作指令如下:
类型 | 指令 |
---|---|
字符串(String) | SET key value:设置字符串的值。 GET key:获取字符串的值。 INCR key:将字符串(整数值)增加 1。 DECR key:将字符串(整数值)减少 1。 APPEND key value:向字符串追加值。 |
列表(Lists) | LPUSH key value:在列表头部插入元素。 RPUSH key value:在列表尾部插入元素。 LPOP key:在列表头部弹出元素。 RPOP key:在列表尾部弹出元素。 LRANGE key start stop:获取列表中指定范围的元素。 |
集合(Sets) | SADD key member [member …]:向集合添加一个或多个成员。 SREM key member [member …]:从集合中删除一个或多个成员。 SPOP key:从集合中随机移除并返回一个成员。 SCARD key:获取集合的成员数。 |
有序集合(ZSet) | ZADD key score member [score member …]:向有序集合添加一个或多个成员,每个成员关联一个分数。 ZREM key member [member …]:从有序集合中删除一个或多个成员。 ZRANGE key start stop [WITHSCORES]:获取有序集合中指定分数范围的成员。 |
哈希表(Hash) | HSET key field value:在哈希表中设置字段的值。 HGET key field:从哈希表中获取字段的值。 HGETALL key:获取哈希表中所有的字段和值。 |
位图(Bitmap) | SETBIT key offset value:在字符串对象中设置位的值。 GETBIT key offset:获取字符串对象中位的值。 |
基数统计(HyperLogLog) | PFADD key element [element …]:向 HyperLogLog 中添加元素。 PFCOUNT key:返回 HyperLogLog 的近似基数。 |
地理空间索引(Geospatial) | GEOADD key longitude latitude member [longitude latitude member …]:将地理空间位置的元素添加到键中。 GEODIST key member1 member2:返回两个地理空间位置之间的距离。 GEORADIUS key longitude latitude radius m|km [WITHCOORD] [WITHDIST] [WITHHASH]:返回位于给定坐标周围指定半径内的所有元素。 |
键管理 | KEYS pattern:查找所有匹配给定模式的键。 DEL key [key …]:删除一个或多个键。 EXPIRE key seconds:设置键的生存时间。 |
事务 | MULTI:开始一个事务块。 EXEC:执行事务块中的所有命令。 WATCH key [key …]:监视一个或多个键,如果在执行事务期间这些键被修改,则事务将失败。 |
持久化 | SAVE:将数据同步到磁盘。 BGSAVE:在后台异步保存数据到磁盘。 |
2. 持久化机制
Redis 是内存数据库,在默认情况下数据是易失的,如果服务器宕机,内存中的数据会丢失。为了避免这种情况,Redis 提供了持久化机制来确保数据的持久存储。Redis 有两种持久化机制:
RDB(Redis DataBase)
RDB 持久化是默认的持久化方式,它会在指定的时间间隔内,将内存中的数据以快照的形式写入磁盘。这种方式可以快速恢复数据,但如果发生故障,可能造成数据丢失。AOF(Append-Only File)
AOF 持久化是将 Redis 执行的每次写命令都追加到日志文件中,可以做到秒级数据持久化。当 Redis 重启时,会重新执行这些命令来恢复数据,恢复速度较慢。
RDB 适合大规模数据备份和恢复,而 AOF 适合需要高数据安全性的场景。
Redis 4.0 引入了混合持久化,这种模式结合了 RDB 和 AOF 的优点。在这种模式下,AOF 文件的开始部分是 RDB 数据的二进制 dump,紧接着是追加的 AOF 日志。这样既可以快速还原数据,又能保留足够的操作日志来保证数据的完整性。
3. Redis 集群
Redis 集群是通过将多个 Redis 节点连接在一起实现高可用性、数据分片和负载均衡的技术。Redis 集群的优势在于高可用性、负载均衡、容灾恢复、数据分片和易于扩展。
Redis 集群有三种组织模式:
- 主从复制模式
通过将一个 Redis 节点(主节点)的数据复制到其他节点(从节点)实现数据冗余和备份。主节点负责写操作,从节点负责读操作,实现读写分离。
- 优点:配置简单;实现数据冗余和读写分离
- 缺点:无法自动故障转移;首单节点内存限制
- 场景:数据备份、读写分离和在线升级的场景
- 哨兵模式
在主从模式的基础上添加哨兵节点,实现自动故障转移。哨兵节点监控主从节点,当主节点故障时自动选举新的主节点。
- 优点:实现数据冗余和读写分离;自动故障转移,提高可用性
- 缺点:配置和管理相对复杂
- 场景:对可用性要求较高的场景
- Cluster 模式
通过数据分片和分布式存储,实现负载均衡和大规模数据存储。采用去中心化架构,减少单点故障风险。数据被划分为 16384 个哈希槽(slots),每个节点负责一部分槽位。
- 优点:实现大规模数据存储、负载均衡和自动故障转移
- 缺点:配置和管理比较复杂,复杂操作受限
- 场景:大规模数据存储和高性能要求的场景
4. Redis 与数据库同步
实际系统中,Redis 经常与数据库配合使用,通过缓存、消息队列、实时分析和分布式锁等功能,提升系统的性能和可扩展性。为了维护缓存与数据库的数据一致性,需要将 Redis 与数据库进行同步。
Redis 与数据库的同步机制有多种,常见的是 缓存旁路策略。
- 读数据 时,先查询缓存,如果缓存中存在所需的数据,则直接返回。如果缓存中没有,则查询数据库,获取数据并写入缓存。
- 写数据 时,先更新数据库,然后直接删除缓存中对应的数据,下次读取时会自动从数据库中加载最新数据到缓存。如果更新数据库成功,删除缓存失败,可使用缓存更新重试机制。
5. 过期删除与内存淘汰
过期删除和内存淘汰都是将一些数据从 Redis 中删掉,区别在于:
- 过期删除 :从 时间角度 考虑,当数据超过了设置的生命周期时,需要把过期数据从内存中删掉。常用的删除策略有:
- 定时删除:每个设置过期时间的键都需要创建一个定时器,到过期时间就会立即清除。对内存友好,对 CPU 不友好(会占用大量的 CPU 资源去清理)。
- 惰性删除:只有当访问一个 key 时,才会判断该 key 是否已过期,过期则清除。对 CPU 友好,对内存不友好(可能存在大量过期 key 没有被删除)。
- 定期删除:每隔一段时间会描一批 key,并清除其中已过期的 key。通过限制删除操作执行的时长和频率来减少删除操作对 CPU 时间的影响,达到 CPU 与内存平衡的效率。
- 内存淘汰 :从 空间角度 考虑,当 Redis 存储容量超过了内存限制,需要从内存中淘汰一些老数据,才能存入新数据。常用的淘汰策略有:
- noeviction(默认策略):不淘汰任何数据,但是内存满时不允许写入新数据。保证数据完整性,但影响可用性。
- volatile-lru(最近最少使用):从已设置过期时间的键中淘汰最近最少使用的数据,适合缓存场景。
- allkeys-lru(全局最近最少使用):从所有键中淘汰最近最少使用的数据,适合缓存场景。
- volatile-random(随机淘汰):从已设置过期时间的键中随机淘汰。
- allkeys-random(全局随机淘汰):从所有键中随机淘汰。
- volatile-lru(最小 TTL 优先):从已设置过期时间的键中淘汰剩余生存时间最短的,适合缓存即将过期的数据。
6. 缓存常见问题
问题 | 现象 | 应对 |
---|---|---|
缓存穿透 | 查询一个在缓存和数据库中都不存在的数据,由于在缓存中找不到,每次请求都要到数据库查询,然后返回空结果。如果这种请求很多,会对数据库造成很大压力。 | - 缓存无效 key:将查询结果为空的操作也进行缓存,但设置一个较短的过期时间。 - 布隆过滤器:在查询前使用布隆过滤器判断数据是否存在,如果不存在则直接返回,如果存在再进行数据库查询。 - 查询校验:校验查询参数,非法查询直接返回异常。 |
缓存击穿 | 某个热点缓存数据在某个时间点过期,而此时如果有大量请求访问这个数据,会导致数据库在同一时间点被大量请求击中,造成数据库压力突增。 | - 延长失效:设置热点数据永不过期或者过期时间很长。 - 互斥锁:请求数据写入缓存前,先获取互斥锁,保证只有一个请求会落到数据库,数据加载完后再释放锁。 - 缓存预热:提前加载热点数据到缓存中。 |
缓存雪崩 | 缓存服务器宕机或大量缓存在同一时间过期,导致大量请求直接访问数据库,造成数据库压力过大,甚至宕机。 | - Redis 集群:避免单机故障。 - 接口限流:避免同时发起大量请求。 - 多级缓存:使用本地缓存 +Redis 缓存,降低单点缓存失效的影响。 - 随机过期:给缓存数据设置随机过期时间,避免大量缓存同时过期。 |
热 key 问题 | 某些 key 的请求量特别大,导致这些 key 成为热点数据,可能会因为访问量过大而拖垮整个缓存系统。 | - 热点数据分散:将热点数据分散到不同的缓存节点,避免单点过热。 - 读写分离:对热点数据进行读写分离,通过多个副本分担读请求。 |
大 key 问题 | 某些 key 存储了过大的数据,这些数据在存储和网络传输上都会带来问题,影响缓存的性能。 | - 数据拆分:将大 key 数据拆分成多个小 key 数据,在集群上均衡存储。 - 及时清理: 定时清理过期的大 key 数据。 - 合理存储:将不适用 Redis 能力的数据存至其它存储,并在 Redis 中删除。 |
(二) MongoDB
MongoDB 是一个流行的文档型 NoSQL 数据库,具有高性能、高可用性和可扩展性。MongoDB 存储 BSON(二进制 JSON)格式的数据,它是一种类 JSON 的格式,除了字符串、数值、日期、数组等数据结构,还能存储地理数据、对象、二进制数据等复杂的数据结构。
1. MongoDB 集群
MongoDB 集群主要有两种模式:
- 副本集(Replica Set)
副本集是 MongoDB 中用于 实现数据冗余、高可用性和读负载均衡 的主要方式。
副本集是一组 MongoDB 进程实例的集合,其中的数据相互复制,并自动进行故障转移。副本集包含一个主节点(Primary)和多个从节点(Secondary),主从节点通过 oplog 来同步数据。
主节点负责处理所有写操作,并将操作记录到 oplog,从节点读取 oplog 并应用,实现数据同步。副本集内所有主从节点都存储相同的数据,保证了数据的一致性。
当主节点不可用时,从节点通过竞选机制,从中选取一个主节点。当之前的主节点恢复使用时,作为从节点重新加入副本集。MongoDB 副本集类似于 Redis 的哨兵模式,使得 MongoDB 可以实现自动故障转移,提升了可用性。
- 分片(Sharding)
分片是 MongoDB 用于 存储海量数据和高并发读写 的架构模式。
分片是指数据划分为多个分片(shards),每个分片可以是一个副本集,数据根据分片键(shard key)分布在不同的分片上。每个分片存储在不同的机器节点上,通过水平扩展,实现大规模数据的存储和处理。
分片策略有两种:
哈希分片:通过计算分片键的字段值的哈希值来分布数据
- 优点:数据分布均衡
- 缺点:处理基于范围的查询比较低效
- 场景:随机的数据访问,无法确定特定的数据范围
范围分片:根据分片键的字段值范围将数据分布到不同的分片上
- 优点:容易数据倾斜
- 缺点:范围查询效率高,数据局部性好,有利于某些分析操作
- 场景:数据按某个属性连续增长,需要按区间进行查询
(三) Elasticsearch
Elasticsearch(简称 ES)是一个开源的、分布式的、高度可扩展的全文搜索引擎,它能快速(近实时)存储、搜索和分析大量数据(包括文本、数字、地理空间、结构化和非结构化数据等所有类型),广泛用于日志统计、系统监控、地理位置查询等场景。
1. ES 集群
ES 天生就是分布式的,主要采用分片模式。分片的目的是分割巨大的索引,将数据分散到集群内各处。分片分为主分片和副本分片,通常一个主分片有多个副本分片。主分片负责处理处理请求和存储数据,副本分片只负责存储数据,是主分片的数据备份。
2. 倒排索引
ES 分片数据中存储的是倒排索引(Inverted Index)。倒排索引是一种索引数据结构,它将文档中出现的关键词映射到包含这些关键词的所有文档的列表上。
换句话说,倒排索引是一个从“词 -> 文档”的映射关系,这种结构特别适合用于全文检索,它能够快速地找到包含指定关键词的所有文档,而不用扫描整个文档集合。