WEB后端_Day10(NoSQL:介绍、应用场景、不足之处、分类、常见 NoSQL 介绍、关系型数据库与NoSQL的区别、Redis)
1.NoSQL 介绍
随着大数据时代的到来,越来越多的网站、应用系统需要支撑海量数据存储、高并发请求、高可用、高可扩展性等特性要求,传统的关系型数据库在应付这些调整已经显得力不从心,暴露了许多难以克服的问题。由此,各种各样的NoSQL(Not Only SQL)数据库作为传统关系型数据的一个有力补充得到迅猛发展。
NoSQL(Not only SQL)数据库,可以理解为区别于关系型数据库如mysql、oracle等的非关系型数据库。
2.NoSQL 应用场景
NoSQL 作为分布式系统的实现,海量数据永久性存储、非结构化数据存储、超大规模数据高效读写、超强水平扩展能力等这些特征让 NoSQL 得到了广泛应用。
3.NoSQL 不足之处
事务支持、关联特性,甚至于 SQL 查询,这些却是 NoSQL 的短板,也决定了 NoSQL 尚且取代不了关系型数据库。
4.NoSQL 分类
通常情况下,我们将 NoSQL 按功能特性不同分为4大类,即键值型、面向列族存储、文档型以及图数据库,参考下面表格:
类型 | 特点 | 应用 | 案例 |
---|---|---|---|
键值型 | 简单数据存储形式,通过键来访问值 | 图像存储 | RedisMemcacheDBBerkeley DB |
可以通过key快速查询到其value | 基于键的文件系统 | ||
一般来说,存储不管value的格式,照单全收 | 设计为可扩展系统 | ||
列族 | 稀疏矩阵存储形式,通过行列作为键 | 网络爬虫结果存储 | HbaseCassandraAccumulo |
方便存储结构化和半结构化数据 | 大数据交互式查询 | ||
方便数据压缩提供数据查询IO优势 | 软一致性 | ||
文档型 | 讲层次化的数据结构存储形式 | 文档搜索 | MongoDBCouchDBCouchbase |
文档存储一般用类似json的格式存储 | 互联网内容管理 | ||
对某些字段建立索引以实现关系型数据库的某些功能 | 高度变化的数据 | ||
图存储 | 适用于关联性要求高的问题 | 社交网络 | Neo4jFlockDBInfiniteGraph |
图形关系的最佳存储 | 欺诈侦测 | ||
使用传统关系数据库来解决的话性能低下,而且设计使用不方便 | 强关联的数据 |
5.常见 NoSQL 介绍
以下四种 NoSQL 是最常使用的:
- Redis:基于内存、支持持久化的键值型数据库
- HBase:面向列、高效随机读写的 NoSQL
- MongoDB:查询高效、支持多索引的文档型数据库。
1、Redis
主要特点:
- 高性能
- 纯内存访问(非数据同步无需读取磁盘)
- 单线程
- 非阻塞多路IO复用
2、HBase:
HBase是一个开源的非关系型分布式数据库,它参考了谷歌的BigTable建模,实现的编程语言为Java。它是Apache软件基金会的Hadoop项目的一部分,运行于HDFS文件系统之上,为 Hadoop 提供类似于BigTable 规模的服务。因此,它可以容错地存储海量稀疏的数据。
HBase是一个高可靠、高性能、面向列、可伸缩的分布式数据库,是谷歌BigTable的开源实现,主要用来存储非结构化和半结构化的松散数据。HBase的目标是处理非常庞大的表,可以通过水平扩展的方式,利用廉价计算机集群处理由超过10亿行数据和数百万列元素组成的数据表。
主要应用在海量数据存储、超大规模随机读写访问的场景。
主要特点:
- 随机读写访问
- 分布式、面向列
- 强一致性
- 底层数据存储在 HDFS 之上
3、MongoDB:
MongoDB 是一个分布式、面向文档的 NoSQL 数据库,用于大容量数据存储,提供统一的数据格式(bson),支持不同类型的索引。适用于存放对象或Json格式数据、追求高性能的业务场景。
主要特点:
- 面向文档,非常灵活
- 支持各种类型的索引
- 复制和故障切换,实现高可用性
- 自动分片,易于扩展
6.关系型数据库与NoSQL的区别
6.1 RDBMS
- 高度组织化结构化数据
- 结构化查询语言(SQL)
- 数据和关系都存储在单独的表中。
- 数据操纵语言,数据定义语言
- 严格的一致性
- 基础事务
- ACID
6.2 NoSQL
- 代表着不仅仅是SQL
- 没有声明性查询语言
- 没有预定义的模式
- 键 - 值对存储,列存储,文档存储,图形数据库
- 最终一致性,而非ACID属性
- 非结构化和不可预知的数据
- CAP定理
- 高性能,高可用性和可伸缩性
6.3.分布式数据库中的CAP原理(了解)
聊到NoSQL不得不提著名的CAP理论,全称 Consistency Available and Partition tolerance,即一致性(C)、可用性(A)与分区容错性(P),这是 Eric Brewer 教授提出的分布式系统设计理念,并给出了定论:任何分布式系统只能同时满足其中二点,无法做到三者兼顾。这可以说是 NoSQL 数据库的理论基石,至今 NoSQL 领域也称得上是百花齐放了,一直也没有哪一款 NoSQL 同时兼顾着这三点特性。
CAP定理:
-
- Consistency(强一致性), 数据一致更新,所有数据变动都是同步的
- Availability(高可用性), 好的响应性能
- Partition tolerance(分区容错性) 可靠性
- Consistency(强一致性), 数据一致更新,所有数据变动都是同步的
P: 系统中任意信息的丢失或失败不会影响系统的继续运作。
定理:任何分布式系统只可同时满足二点,没法三者兼顾。
CAP理论的核心是:一个分布式系统不可能同时很好的满足一致性,可用性和分区容错性这三个需求
因此,根据 CAP 原理将 NoSQL 数据库分成了满足 CA 原则、满足 CP 原则和满足 AP 原则三 大类:
- CA - 单点集群,满足一致性,可用性的系统,通常在可扩展性上不太强大。
- CP - 满足一致性,分区容忍性的系统,通常性能不是特别高。
- AP - 满足可用性,分区容忍性的系统,通常可能对一致性要求低一些。
CAP理论的核心是:一个分布式系统不可能同时很好的满足一致性,可用性和分区容错性这三个需求
因此,根据 CAP 原理将 NoSQL 数据库分成了满足 CA 原则、满足 CP 原则和满足 AP 原则三 大类:
- CA - 单点集群,满足一致性,可用性的系统,通常在可扩展性上不太强大。
- CP - 满足一致性,分区容忍性的系统,通常性能不是特别高。
- AP - 满足可用性,分区容忍性的系统,通常可能对一致性要求低一些。
CAP理论就是说在分布式存储系统中,最多只能实现上面的两点。
而由于当前的网络硬件肯定会出现延迟丢包等问题,所以分区容忍性是我们必须需要实现的。
所以我们只能在一致性和可用性之间进行权衡,没有NoSQL系统能同时保证这三点。
举例:
- CA:传统Oracle数据库
- AP:大多数网站架构的选择
- CP:Redis、Mongodb
注意:分布式架构的时候必须做出取舍。
一致性和可用性之间取一个平衡。多余大多数web应用,其实并不需要强一致性。因此牺牲C换取P,这是目前分布式数据库产品的方向。
7.Redis
7.1.简介
Redis:REmote DIctionary Server(远程字典服务器)是完全开源免费的,用C语言编写的,遵守BSD协议,是一个高性能的(key/value)分布式内存数据库,基于内存运行并支持持久化的NoSQL数据库,是当前最热门的NoSql数据库之一,也被人们称为数据结构服务器
Redis是用C语言开发的一个开源的高性能键值对(key-value)数据库,官方提供测试数据,50个并发执行100000个请求,读的速度是110000次/s,写的速度是81000次/s ,且Redis通过提供多种键值数据类型来适应不同场景下的存储需求。
7.1.1.目前为止Redis支持的键值数据类型如下:
-
字符串类型 string
-
哈希类型 hash
-
列表类型 list
-
集合类型 set
-
有序集合类型 sortedset
7.1.2. redis的应用场景
• 缓存(数据查询、短连接、新闻内容、商品内容等等)
• 聊天室的在线好友列表
• 任务队列。(秒杀、抢购、12306等等)
• 应用排行榜
• 网站访问统计
• 数据过期处理(可以精确到毫秒
• 分布式集群架构中的session分离
7.2.下载
下载 https://redis.io/
中文网:http://www.redis.cn/
7.3.安装
7.3.1.环境准备
redis是C语言开发,安装redis需要先将官网下载的源码进行编译,编译依赖gcc环境。如果没有gcc环境,需要安装gcc:(环境已经导入完成)
yum install gcc-c++
7.3.2.开始安装
步骤1:将Windows下下载的压缩文件redis-5.0.7.tar.gz上传到Linux下。
步骤2: 解压文件
tar -zxvf /opt/softes/redis-5.0.5.tar.gz -C redis/
步骤3:编译redis (编译,将.c文件编译为.o文件)
进入到解压后的目录 进行编译
make install
安装完后,在/usr/local/redis/bin下有几个可执行文件
redis-benchmark ----性能测试工具
redis-check-aof ----AOF文件修复工具
redis-check-rdb----RDB文件检查工具(快照持久化文件)
redis-cli ----命令行客户端
redis-server ----redis服务器启动命令
步骤5: copy文件
redis启动需要一个配置文件,可以修改端口号等信息。
将加压目录下的redis.conf 拷贝到/usr/local下
7.3.3.启动-前端启动:(了解)-无法部署集群
启动redis,客户端连接: 连接6379端口
redis-cli -h ip地址 -p 端口
退出客户端:quit
redis的关闭:
- ①查询到PID,kill -9 pid 【断电,非正常关闭,一般不用,否则造成数据丢失】
- ②正常关闭 【正常关闭,数据保存】
停止redis服务
[root@localhost bin]# ./redis-cli shutdown
7.3.4.Redis启动-后端模式
修改redis.conf配置文件, daemonize yes 以后端模式启动。
vim /usr/local/redis.conf
[root@localhost local]# ./bin/redis-server redis.conf
7.4.Redis客户端-RedisDesktopManager
7.4.1.下载 Redis Desktop Manager
官网下载:https://redisdesktop.com/download
github地址:https://github.com/uglide/RedisDesktopManager/releases
7.4.2.安装Redis Desktop Manager
redis-desktop-manager-0.8.8.384.exe 傻瓜式安装,点击下一步就行。
安装完成桌面会生成如下快捷图标,双击启动如右图。
7.4.3.RedisDesktopManager连接redis
在使用这个连接redis的时候,发现连接不上。
第一步:在自己的本机外面试下能不能连接上虚拟机,打开cmd,使用 ping 192.168.65.128 (192.168.65.128这个为虚拟机的ip地址 查看虚拟机的ip地址:命令 ifconfig)
如果能ping的通,这个没问题,表示外部可以访问得到。访问不到可以,把虚拟机的防火墙给关了,最省事,命令: service iptables stop
设置防火墙规则:firewall-cmd --zone=public --add-port=6379/tcp --permanent
第二步:在redis的配置文件(redis.conf)里面是否设置了requirepass 表示连接的密码,如果没有设置,不好意思,连接不上
第三步: 在redis的配置文件里面,是否把bind 127.0.0.1 给注释掉,如果没有,连接不上
以上这些你都做了,应该就没有问题了
如果还是连接不上,你首先在你虚拟机里面登录上redis的客户端 进入到src目录层级下面。
和启动redis一样,命令: ./redis-cli
进入到客户端之后:命令:auth 1234(你设置的密码)
看一下是否ok。
我之前在这里踩了坑,明明我的配置文件里面设置了访问密码,却没有生效,为了保险起见,还是再来看下,密码是否生效了
如果ok,那就没有问题,如果出现了错误,说你密码没有设置,那就直接用命令设置:config set requirepass 1234(这个为你设置的访密码)
然后再试下命令 auth 1234(出现ok就好了)
这样就应该可以顺利的连接上redis了。
设置密码之后通过客户端退出服务端:
7.5.Redis相关基础知识
7.5.1.查看数据库
默认16个数据库,类似数组下表从零开始,初始默认使用零号库
config get databases
7.5.2.切换数据库
SELECT index
默认使用0号库
设置默认库
7.5.3.Dbsize查看当前数据库的key的数量
7.5.4.Flushdb:清空当前库
7.5.5.Flushall;通杀全部库
7.5.6.统一密码管理,16个库都是同样密码,要么都OK要么一个也连接不上
7.5.7.Redis索引都是从零开始
7.6.Redis的五大数据类型
Redis是一个“数据库”,当然它是一个基于缓存的非关系型数据库。
Redis一共有5种常用的数据类型:字符串(string)、列表(list)、哈希(hash)、集合(set)、有序集合(zset)。Redis是一个key-value形式的存储系统,key是一个“字符串”,而value对应的则是前面提到的5种数据类型。
在对Redis进行正式接触前,我们先来认识这5种数据类型。
字符串(string)
这是最常见和最容易理解的一种数据类型,它表示存储在redis中的值是一个“字符串”类型的数据。但实际上,它还能存储整型数据,后面我们将通过INCR命令,对值进行自增操作。
列表(list)
列表(list)也可以理解为数组,和在Java中的List类型类似。略微不同的是,Java中的列表可以是泛型类型,也就是说Java中的List数据结构可以是字符串、整型等。而在redis中列表中的数据类型则只有字符串类型。
哈希(hash)
又称“散列”,这种数据类型类似于Java中的Map类型。初学者可能会疑惑,前面的“字符串”类型,一个key一个value不就是Map类型么。
实际上,redis是一种key-value形式的存储系统,我们所说的redis数据类型指的是value的数据类型。所以哈希(hash)也就是value是类似Map的一种数据类型。在后面的章节中我们会更直观的感受到。
集合(set)
set类型在redis中被称为集合,同样它和Java的Set集合相同。和redis的列表(list)类似,不同地是,列表(list)的数据是可以重复的且是插入有序,而集合(set)中的数据是不可重复的且是无序。
有序集合(zset)
有序集合(zset)尽管看起来是集合(set)类型多了“有序”的特性。但实际上,可以说它和哈希(hash)更相似。因为它和哈希(hash)一样也是Map类型,不同地是它的key是实际上的成员,而value则是用于排序的“分值”。这个特性能帮助我们快速的实现“点赞数最高倒序排列”等功能。
7.7.命令
我们通过redis-cli命令进入redis命令行交互。
Redis命令十分丰富,包括的命令组有Cluster、Connection、Geo、Hashes、HyperLogLog、Keys、Lists、Pub/Sub、Scripting、Server、Sets、Sorted Sets、Strings、Transactions一共14个redis命令组两百多个redis命令,Redis中文命令大全。您可以通过下面的检索功能快速查找命令,已下是全部已知的redis命令列表。如果您有兴趣的话也可以查看我们的网站结构图,它以节点图的形式展示了所有redis命令。
http://www.redis.cn/commands.html#set
7.7.1.字符串(string)
7.7.1.1.读/写/删简单命令
写命令通过set关键字实现,set [key] [value]。
读命令通过get关键字实现,get [key]。
字符串数据类型还有一个mset表示同时写入一个或多个字符串值,mset [key1] [value1] [key2] [value2]。
7.7.1.2.自增/自减命令
自增+1命令通过incr关键字实现,incr [key]。
自减 -1 decr
增加/减去一个指定的值
自增任意浮点数通过incrbyfloat,incrbyfloat [key] [float]。
浮点数只能加 不能减
7.7.1.3.字符串操作命令
redis中对字符串类型的数据类型不仅可以使用上述命令,它甚至还能像Java一样进行值得追加、获取子串等。
追加value值给指定key到末尾通过append,apppend [key] [append_string]。
接着这个示例,通过命令getrange获取子字符串,getrange [key] [start] [end]。
同样是这个示例,我们通过命令setrange替换子字符串为给定值,我们会给出两个关键参数,第一个参数是[start]表示从哪里开始替换,第二个参数是[value]表示替换的内容,setrange [key] [start] [value]。
7.7.1.4.二进制位命令
任何数据在操作系统中都是以二进制位形式存储的,字符串类型中redis提供了对其进行二进制位操作。通常情况下运用可能不多,但可以通过它实现一些“巧妙”的设计。
例如,在钉钉消息中,我们发送一条消息会显示“已读”和“未读”的人,我们需要将这两个信息存储在redis中,应该怎么设计?
我们设计一条消息的key值结构为“[user_id]:[msg_id]”,所以key=“1:100”就表示“用户ID为1发送的消息ID为100”。注意,此时如果用户ID=2的人读了这条消息,就通过命令setbit 1:100 2 1写入,如果用户ID=100的人读了这条消息,就通过setbit 1:100 10 1。这条命令的含义表示对key=1:100的二进制第2位写入1,对key=1:100的二进制第10位写入1,1表示已读,0则表示未读。
上面我们就初始化好了一个bitmap(位图)。接下来,当用户ID=2和用户ID=10读了这条消息。
我们通过getbit命令可以判断出哪些用户是否已读这条消息,例如,我们判断用户ID=3是否已读这条消息
我们还可以通过bitcount统计值为1的数量,也就是有多少人已读这条消息。
最后还有一个关于二进制位的命令bittop [operation] [result] [key1] [key2],可以对多个key值的二进制位进行二进制运算,包括并AND、或OR、异或XOR、非NOT,计算结果保存在[result]中。
查看库中所有的键
7.7.2.列表(list)
列表就是一个键 对应该多个值
7.7.2.1.推入/弹出常用命令
通过rpush、lpush,将一个或多个值向右或向左推入。
rpush [key] [value1] [value2],将value值推入到列表的右端。
lpush [key] [value1] [value2],将value值推入到列表的左端。
通过rpop、lpop,移除并返回列表中最后端、最左端的元素。
在介绍完推入和弹出命令后,接下来将介绍与列表范围查看的命令。
lrange [key] [start] [end]命令用于返回列表从[start]到[end]位置范围内的所有元素,注意,位于[start]、[end]的元素也会被返回,
lindex [key] [index]命令用于返回指定位置[index]的元素,
lrange和lindex均不会修改原本的列表值,但ltrim则用于“修建”列表值。
ltrim [key] [start] [end]表示只保留列表从[start]到[end]范围的所有元素,注意,包含位于[start]、[end]的元素,
列表基本的命令就是上面这些,还有一些比较“高级”的命令:将元素从一个队列移动到另外一个队列,或者阻塞式的执行弹出命令知道有新元素新加入到列表中。
7.7.3.哈希(hash)
hash可以理解为map 键还是String 值是map类型
hmset命令可写入hash类型的值,hmset [key] [field1] [value1] [field2] [value2]。
hlen返回hash包含的键值对数量,hlen [key]
hgetall返回hash包含的所有键值对,hmgetall [key]。
hexists命令检查给定的field是否存在hash值中,返回0表示不存在,返回1表示存在,hexists [key] [field]。
hkeys获取hash包含的所有field键,hkeys [key]。
hvals获取hash包含的所有field对应的value值,hvals [key]。
hincrby给hash中指定的field键自增任意整数(和字符串类型的incrby类似),hincrby [key] [field] [number]。
hincrbyfloat给hash中指定的filed键自增任意浮点数(和字符串类型的incrbyfloat类似),hincrbyfloat [key] [field] [number]。
hdel删除hash中指定的filed,hmdel [key] [field]。
7.7.4.集合(set)
在上一章节讲到,集合(set)是以无序方式存储各不相同元素的数据类型。它和Java中的Set类型类似。它同样具有新增、删除、读取等基本操作,还有两个集合之间运算的操作。
7.7.4.1.读/写等常用命令
sadd命令将一个或多个元素添加到集合里,并返回被添加元素中原本并不存在集合中的元素数量,sadd [key] [member] [member]。
集合的差集
7.7.5.有序(sorted set)
这个集合和上边的集合的区别就在于 有序集合会给每一个元素指定一个分数 有序也是根据元素后边的分数来进行排序的
zscore命令返回成员member的分数
zrange [key] [start] [stop] (withscores)
zcount [key] [min_score] [max_score]返回分数介于min_score和max_score之间的成员数量。
zincrby [key] [incrment] [member]命令用于给member成员的分数加上incrment。
zrem命令用于删除有序集合中指定的成员,zrem [key] [member1] [member2]……。
7.8.Transactions
7.8.1.事务定义
可以一次执行多个命令,本质是一组命令的集合。一个事务中的所有命令都会序列化,按顺序地串行化执行而不会被其它命令插入,不许加塞
7.8.2.事务的作用
一个队列中,一次性、顺序性、排他性的执行一系列命令
7.8.3.取消事务执行
取消事务执行,命令将不会被执行。
执行过程中,当其中的命令出现错误命令时
事务中出现执行时错误,类似Java的运行时异常,执行事务时,部分命令会被执行成功,也即是不保证原子性。
7.8.4.使用watch监视
使用watch监视key在事务之前被改动,正常未被改动时的情况,所有命令正常执行
使用watch监视key,此时在事务执行前key被改动,事务将取消不会执行所有命令。
我们现在一个redis客户端中执行watch命令。
我们再次回到第一个客户端,开始输入事务的命令块。
可看到通过exec执行事务时,事务并没有执行成功,而是返回“nil”。
三阶段
- 开启:以MULTI开始一个事务
- 入队:将多个命令入队到事务中,接到这些命令并不会立即执行,而是放到等待执行的事务队列里面
- 执行:由EXEC命令触发事务
redis 的事务拥有以下特性
- 如果在执行 exec 之前事务中断了,那么所有的命令都不会执行
- 如果某个命令语法错误,不仅会导致该命令入队失败,整个事务都将无法执行
- 如果执行了 exec 命令之后,那么所有的命令都会按序执行
- 当 redis 在执行命令时,如果出现了错误,那么 redis 不会终止其它命令的执行,这是与关系型数据库事务最大的区别,redis 事务不会因为某个命令执行失败而回滚
7.8.5.redis 事务的缺陷
不满足原子性
- 与关系型数据库的事务不同,redis 事务是不满足原子性的,一个事务执行过程中,其他事务或 client 是可以对相应的 key 进行修改的
- 想要避免这样的并发性问题就需要使用 WATCH 命令,但是通常来说,必须经过仔细考虑才能决定究竟需要对哪些 key 进行 WATCH 加锁
- 额外的 WATCH 会增加事务失败的可能,而缺少必要的 WATCH 又会让我们的程序产生竞争条件
后执行的命令无法依赖先执行命令的结果
- 由于事务中的所有命令都是互相独立的,在遇到 exec 命令之前并没有真正的执行,所以我们无法在事务中的命令中使用前面命令的查询结果
- 我们唯一可以做的就是通过 watch 保证在我们进行修改时,如果其它事务刚好进行了修改,则我们的修改停止,然后应用层做相应的处理
事务中的每条命令都会与 redis 服务器进行网络交互
- redis事务开启之后,每执行一个操作返回的都是queued,这里就涉及到客户端与服务器端的多次交互