文章目录
SQL 与 NOSQL 的区别和联系
主要区别:
1.数据存储结构不同
SQL 数据存在特定结构的表中;而NoSQL则更加灵活和可扩展,存储方式可以省是JSON文档、哈希表或者其他方式。SQL通常以数据库表形式存储数据。
举个栗子,存个学生借书数据:
而 NoSQL 存储方式比较灵活,比如使用类 JSON 文件存储上表中熊大的借阅数据:
2. 表/数据集合的数据的关系
在SQL中,必须定义好表和字段结构后才能添加数据
,例如定义表的主键(primary key),索引(index),触发器(trigger),存储过程(stored procedure)等。表结构可以在被定义之后更新,但是如果有比较大的结构变更的话就会变得比较复杂。在NoSQL中,数据可以在任何时候任何地方添加,不需要先定义表
。
3.复杂查询
可以用 SQL 语句方便的在一个表以及多个表之间做非常复杂的数据查询。join 查询等等。
4.数据的耦合性
SQL中不允许删除已经被使用的外部数据,例如审核人表中的"熊三"已经被分配给了借阅人熊大,那么在审核人表中将不允许删除熊三这条数据,以保证数据完整性
。- 而NoSQL中则没有这种强耦合的概念,可以随时删除任何数据。
5.事务功能
- SQL中如果多张表数据需要同批次被更新,即如果其中一张表更新失败的话其他表也不能更新成功。这种场景可以通过事务来控制,可以在所有命令完成后再统一提交事务。
- 而 NoSQL 中没有事务这个概念,每一个数据集的操作都是原子级的。
总结一下:
目前许多大型互联网项目都会选用MySQL(或任何关系型数据库) + NoSQL
的组合方案。
关系型数据库适合存储结构化数据,如用户的帐号、地址:
-
1)这些数据通常需要做结构化查询(嗯,好像是废话),比如join,这时候,关系型数据库就要胜出一筹
-
2)这些数据的规模、增长的速度通常是可以预期的
-
3)事务性、一致性
NoSQL适合存储非结构化数据,如文章、评论:
-
1)这些数据通常用于模糊处理,如全文搜索、机器学习
-
2)这些数据是海量的,而且增长的速度是难以预期的,
-
3)根据数据的特点,NoSQL数据库通常具有无限(至少接近)伸缩性
-
4)按key获取数据效率很高,但是对 join 或其他结构化查询的支持就比较差
基于它们的适用范围不同,目前主流架构才会采用组合方案,一个也不能少。目前为止,还没有出现一个能够通吃各种场景的数据库,而且根据CAP理论,这样的数据库是不存在的。
什么是 redis?有什么优势?与 memcached 的比较?
redis是什么?
Redis 是一个开源的,Key-Value 类型的内存存储系统,它可以用作数据库、缓存和消息中间件。
它支持多种类型的数据结构,如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 与范围查询, bitmaps, hyperloglogs 和 地理空间(geospatial) 索引半径查询。
Redis 内置了 复制(replication),LUA脚本(Lua scripting), LRU驱动事件(LRU eviction),事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)。
优势
- 速度快,因为数据存放在内存中;
- 支持多种数据类型,支持String,List,Set,Sorted set,Hash(底层相当于一个Map结构);
- 提供了RDB和AOF两种持久化方式;
- 丰富的特性:可用作缓存,消息,可以按key设置过期时间,过期后将会自动删除;
- 支持事务,操作都是原子性。
Redis为什么这么快 ?
1、完全基于内存
,绝大部分请求是纯粹的内存操作,非常快速。数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1);
2、数据结构简单
,对数据操作也简单,Redis中的数据结构是专门进行设计的;
3、采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;
4、使用多路I/O复用模型,非阻塞IO
;
5、使用底层模型不同,它们之间底层实现方式以及与客户端之间通信的应用协议不一样,Redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求;(jemalloc,tcmalloc)
redis 为什么是单线程的?
- 不需要各种锁的性能消耗
- CPU消耗。采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU。
- 因为单线程的已经很快了,如果还不满足就起集群的方式来解决。
与memcached 的比较?
-
memcached 所有的值均是简单的字符串,redis 作为其替代者,支持更为丰富的数据类型
-
redis 的速度比 memcached 快很多 redis 的速度比 memcached 快很多(memcached:单进程多线程模型)
3.redis 可以持久化其数据
redis支持的数据类型
它支持多种类型的数据结构,如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 与范围查询, bitmaps, hyperloglogs 和 地理空间(geospatial) 索引半径查询。
具体见:Redis 数据结构与对象
关于 bitmaps, hyperloglogs 和 地理空间(geospatial) 索引半径查询。 还需要有时间再研究一下,先把基础的全搞懂。
Redis 有哪几种数据淘汰策略?
Maxmemory 配置,就是当指定的内存限制大小达到时,需要选择不同的行为,也就是回收内存的策略。
-
volatile-lru:从设置
过期
时间的数据集(server.db[i].expires)中挑选出最近最少使用的数据淘汰
。没有设置过期时间的key不会被淘汰,这样就可以在增加内存空间的同时保证需要持久化的数据不会丢失。 -
volatile-ttl:除了淘汰机制采用LRU,策略基本上与volatile-lru相似,
从设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
,ttl 值越大越优先被淘汰。 -
volatile-random:
从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
。当内存达到限制无法写入非过期时间的数据集时,可以通过该淘汰策略在主键空间中随机移除某个key。 -
allkeys-lru:
从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰,该策略要淘汰的key面向的是全体key集合,而非过期的key集合。这个应该就是在redis数据结构与对象上讲的对象的空转时长来操作的。
。 -
allkeys-random:
从数据集(server.db[i].dict)中选择任意数据淘汰。
-
no-enviction:
禁止驱逐数据,也就是当内存不足以容纳新入数据时,新写入操作就会报错
,请求可以继续进行,线上任务也不能持续进行,采用no-enviction策略可以保证数据不被丢失,这也是系统默认的一种淘汰策略。
redis 的LRU也和MYSQL一样进行了对应于自己的优化,MYSQL的优化见:InnoDB 存储引擎架构与索引的实现
具体参考官网:http://www.redis.cn/topics/lru-cache.html
大方面就是: Redis单机数据库的实现,持久化以及过期淘汰策略
如何优雅的使用 redis 实现一个分布式锁?
参考:http://www.redis.cn/topics/distlock.html
惊群效应
什么是惊群效应
惊群效应(thundering herd)是指多进程(多线程)在同时阻塞等待同一个事件的时候(休眠状态),如果等待的这个事件发生,那么他就会唤醒等待的所有进程(或者线程),但是最终却只能有一个进程(线程)获得这个时间的“控制权”,对该事件进行处理,而其他进程(线程)获取“控制权”失败,只能重新进入休眠状态,这种现象和性能浪费就叫做惊群效应。
惊群效应消耗了什么,即有什么坏处
- 上下文切换(context switch)过高会导致 CPU 像个搬运工,频繁地在寄存器和运行队列之间奔波,更多的时间花在了进程(线程)切换,而不是在真正工作的进程(线程)上面。直接的消耗包括 CPU 寄存器要保存和加载(例如程序计数器)、系统调度器的代码需要执行。间接的消耗在于多核 cache 之间的共享数据。
- 为了确保只有一个进程(线程)得到资源,需要对资源操作进行加锁保护,加大了系统的开销。
Linux 解决方案之 Accept
Linux 2.6 版本之前,监听同一个 socket 的进程会挂在同一个等待队列上,当请求到来时,会唤醒所有等待的进程。
Linux 2.6 版本之后,通过引入一个标记位 WQ_FLAG_EXCLUSIVE
,解决掉了 accept 惊群效应。
// 当accept的时候,如果没有连接则会一直阻塞(没有设置非阻塞)
// 其阻塞函数就是:inet_csk_accept(accept的原型函数)
struct sock *inet_csk_accept(struct sock *sk, int flags, int *err)
{
...
// 等待连接
error = inet_csk_wait_for_connect(sk, timeo);
...
}
static int inet_csk_wait_for_connect(struct sock *sk, long timeo)
{
...
for (;;) {
// 只有一个进程会被唤醒。
// 非exclusive的元素会加在等待队列前头,
// exclusive 的元素会加在所有非exclusive元素的后头。
prepare_to_wait_exclusive(sk_sleep(sk), &wait,TASK_INTERRUPTIBLE);
}
...
}
void prepare_to_wait_exclusive(wait_queue_head_t *q, wait_queue_t *wait, int state)
{
unsigned long flags;
// 设置等待队列的flag为 EXCLUSIVE,设置这个就是表示一次只会有一个进程被唤醒,
// 我们等会就会看到这个标记的作用。
// 注意这个标志,唤醒的阶段会使用这个标志。
wait->flags |= WQ_FLAG_EXCLUSIVE;
spin_lock_irqsave(&q->lock, flags);
if (list_empty(&wait->task_list))
// 加入等待队列
__add_wait_queue_tail(q, wait);
set_current_state(state);
spin_unlock_irqrestore(&q->lock, flags);
}