目录
了解NoSQL
泛指非关系型的数据库,NoSQL数据库的产生是为了解决大规模数据集合,多重数据种类带来的挑战,尤其是大数据应用难题。
现在网站的特点:
(1) 高并发读写
Web2.0网站,数据库并发负载非常高,往往达到每秒上万次的读写请求
(2) 高容量存储和高效存储
Web2.0网站通常需要在后台数据库中存储海量数据,如何存储海量数据并进行高效的查询往往是一个 挑战
(3) 高扩展性和高可用性
随着系统的用户量和访问量与日俱增,需要数据库能够很方便的进行扩展、维护
NoSql数据库的优势
(1) 易扩展
NoSQL数据库种类繁多,但是一个共同的特点都是去掉关系数据库的关系型特性。数据之间无关系,这 样就非常容易扩展。也无形之间,在架构的层面上带来了可扩展的能力。
(2)大数据量,高性能
NoSQL数据库都具有非常高的读写性能,尤其在大数据量下,同样表现优秀。这得益于它的无关系性, 数据库的结构简单。一般MySQL使用Query Cache,每次表的更新Cache就失效,是一种大粒度的 Cache,在针对web2.0的交互频繁的应用,Cache性能不高。而NoSQL的Cache是记录级的,是一种细 粒度的Cache,所以NoSQL在这个层面上来说就要性能高很多了。
(3)灵活的数据模型
NoSQL无需事先为要存储的数据建立字段,随时可以存储自定义的数据格式。而在关系数据库里,增删 字段是一件非常麻烦的事情。如果是非常大数据量的表,增加字段简直就是一个噩梦。这点在大数据量 的web2.0时代尤其明显。
(4) 高可用
NoSQL在不太影响性能的情况,就可以方便的实现高可用的架构。比如Cassandra,HBase模型,通过 复制模型也能实现高可用
Redis介绍
一个高性能的(key/value)分布式内存数据库,基于内存运行并支持持久化的NoSQL数据库,是当前最热门的NoSql数据库之一
Redis 与其他 key - value 缓存产品有以下三个特点
(1) Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用
(2) Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储
(3) Redis支持数据的备份,即master-slave(主从)模式的数据备份
Redis优势
(1)性能极高
(2)原子 – Redis的所有操作都是原子性的,同时Redis还支持对几个操作全并后的原子性执行。
采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;
(3) 使用多路I/O复用模型,非阻塞IO;
Redis应用场景
(1) 缓存(数据查询,短连接,新闻内容,商品内容等),使用最多
(2) 聊天室在线好友列表
(3) 任务队列(秒杀,抢购,12306等)
(4) 应用排行榜
(5) 网站访问统计
(6) 数据过期处理(可以精确到毫秒)
(7) 分布式集群架构中的session问题
在Linux下启动Redis
后端启动方式:(修改redis.conf配置文件,设置:daemonize yes)
在redis目录下 ./bin/redis-server ./redis.conf
如果想要通过指令来操作redis,可以使用redis的客户端进行操作,在bin文件夹下运行redis-cli(该指令默认连接的127.0.0.1 ,端口号是6379)
如果想要连接指定的ip地址以及端口号,则需要按照 ./redis-cli -hip地址 -p 端口号
ip地址可在redis.conf下修改
Redis数据类型
Redis是一种基于内存的数据库,并且提供一定的持久化功能,它是一种键值(key-value)数据库,使用 key 作为索引找到当前缓存的数据,并且返回给程序调用者。
当前的 Redis 支持 6 种数据类型,它们分别是字符串(String)、列表(List)、集合(set)、哈希结构(hash)、有序集合(zset)和基数(HyperLogLog)
Redis常用指令操作数据库
命令学习网站:http://doc.redisfans.com/index.html
下面的指令在linux下进行
String类型
赋值语法:SET key value
取值语法: GET key
设置多个键语法: MSET key value [key value …]
获取多个键值语法: MGET key [key …]
删除语法:DEL key
字符串数字的递增与递减
当存储的字符串是整数时,Redis提供了一个实用的命令INCR,其作用是让当前键值递增,并返回递增后的值
递增数字语法: INCR key
递减数值语法: DECR key
增加指定的整数语法: INCRBY key 指定的整数
减少指定的整数 语法:DECRBY key 指定的整数
Hash散列
字段和字段值的映射。字段值只能是字符串类型,不支持散列类型、集合类型等其它类型。相当于是对象格式的存储
赋值语法: HSET key field value
获取值语法: HGET key field
设置多个字段语法: HMSET key field value [field value ...]
获取多个值语法: HMGET key field [field ...]
获取所有字段值语法:HGETALL key
删除字段语法:HDEL key field [field ...]
队列List
采用来链表来存储,双向链表存储数据,特点:增删快、查询慢(Linkedlist).这个队列是有序的。
向列表左边增加元素: LPUSH key value [value ...]
从列表左边弹出元素: LPOP key(临时存储,弹出后,从队列中清除)
向列表右边增加元素 : RPUSH key value [value ...]
从列表右边弹出元素: RPOP key
获取列表中元素的个数: LLEN key
查看列表语法:LRANGE key start stop
将返回start、stop之间的所有元素(包含两端的元素),索引从0开始,可以是负数,如:“-1”代表最后的一个元素。
无序集合Set
无序、不可重复
增加元素语法:SADD key member [member ...]
删除元素语法: SREM key member [member ...]
获得集合中的所有元素 : smembers key
判断元素是否在集合中: SISMEMBER key member
有序集合ZSet
Sortedset又叫zset,是有序集合,可排序的,但是唯一。 zset和set的不同之处,是会给set中的元素添加一个分数,然后通过这个分数进行排序。
增加元素:ZADD key score member [score member ...]
向有序集合中加入一个元素和该元素的分数(score),如果该元素已经存在则会用新的分数替换原有的分数。
添加带分数(可用学生成绩,销售数量等来做分数,方便计算排序):
语法:ZREVRANGE key start stop [WITHSCORES]
获得排名在某个范围的元素列表,并按照元素分数降序返回。获得元素的分数的可以在命令尾部加上WITHSCORES参数
获取元素的分数 :ZSCORE key member
删除元素ZREM key member [member ...]
给某一个属性加减值:ZINCRBY key 加减数 member
减分时使用负数
HyoperLogLog
一种使用随机化的算法,以少量内存提供集合中唯一元素数量的近似值
HyperLogLog 可以接受多个元素作为输入,并给出输入元素的基数估算值:
基数:集合中不同元素的数量。比如 {‘apple’, ‘banana’, ‘cherry’, ‘banana’, ‘apple’} 的基数就是 3 。
估算值:算法给出的基数并不是精确的,可能会比实际稍微多一些或者稍微少一些,但会控制在合理的范围之内。
HyperLogLog 的优点是,即使输入元素的数量或者体积非常非常大,计算基数所需的空间总是固定的、并且是很小的。
PFADD key element [element …]:将指定的元素添加到指定的HyperLogLog 中
PFCOUNT key [key …]:返回给定 HyperLogLog 的基数估算值
PFMERGE destkey sourcekey [sourcekey …]:将多个 HyperLogLog 合并为一个 HyperLogLog
其他指令
1、keys 返回满足给定pattern 的所有key
keys user* //查询以user开头的key
keys * //查询所有的key
2、exists确认一个key 是否存在,存在返回1
3、 del删除一个key
4、rename重命名key:rename oldkey newkey
5、 type返回值的类型: type key
6、设置key的生存时间:EXPIRE key seconds
TTL key 查看key剩余的生存时间,PERSIST key 清除生存时间
7、删除当前选择数据库中的所有key:flushdb
8、删除所有数据库中的所有key:flushall (谨慎)
9、获取服务器信息和统计:info
一个redis实例key包括多个数据库,客户端可以指定连接某个redis实例的哪个数据库,就好比一个mysql中创建多个数据库,客户端连接时指定连接哪个数据库
一个redis实例最多可提供16个数据库,下标从0-15,客户端默认连接第0号数据库,也可以通过select选择连接哪个数据库
将key的数据移动到1号数据库: move key 数据库编号
事务管理
Redis 事务可以一次执行多个命令, 并且带有以下两个重要的保证:
事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行。
一个事务从开始到执行会经历以下三个阶段:
开始事务。
命令入队。
执行事务。
以 MULTI 开始一个事务, 然后将多个命令入队到事务中, 最后由 EXEC 命令触发事务, 一并执行事务中的所有命令:
Redis发布订阅模式
Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。
Redis 客户端可以订阅任意数量的频道。
下图展示了频道 channel1 , 以及订阅这个频道的三个客户端 —— client2 、 client5 和 client1 之间的关系:
当有新消息通过 PUBLISH 命令发送给频道 channel1 时, 这个消息就会被发送给订阅它的三个客户端:
实例:创建订阅频道名为 redisMessage:
我们先重新开启个 redis 客户端,然后在同一个频道 myredis发布两次消息,订阅者就能接收到消息。
用Java连接Redis
1、创建项目,导入依赖
<dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.7.2</version> </dependency>
注意:
确认远程服务器是否可以ping通: ping vm的ip地址
确认防火墙是否关闭或放行 systemctl stop firewalld systemctl status firewalld
2、连接服务器
方案一:单实例连接
Jedis jedis = new Jedis("192.168.109.129", 6379);
jedis.set("key1","value1");
System.out.println(jedis.get("key1"));
可能遇见异常
解决办法:虚拟机客户端连接的ip是127.0.0.1,意思是连接的本机,其他机器无法连接,这里需要修改配置文件,将连接地址改为虚拟机的地址,就可以了.
修改redis.conf文件里面的 bind 连接地址,将连接地址改为自己虚拟机的ip
方案二:连接池
后面会使用Spring的配置文件来整合
public class Main {
@Test
public void fun(){
// 1.获取连接池配置对象,设置配置项
JedisPoolConfig config = new JedisPoolConfig();
// 1.1最大的连接数
config.setMaxTotal(30);
// 1.2最大的空闲
config.setMaxIdle(10);
// 2.获取连接池
JedisPool jedisPool = new JedisPool(config, "192.168.109.129", 6379);
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
// 3.设置数据
jedis.set("name", "张三");
String name = jedis.get("name");
System.out.println("name=" + name);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (jedis != null) {
jedis.close();
}
// 4.虚拟机关闭的时候,释放资源
if (jedisPool != null) {
jedisPool.close();
}
}
}
}
Redis持久化方式
由于redis的值放在内存中,为防止突然断电等特殊情况的发生,需要对数据进行持久化备份。即将内存数据保存到硬盘。
RDB持久化
RDB 是以二进制文件,是在某个时间点将数据写入一个临时文件,持久化结束后,用这个临时文件替换上次持久化的文件,达到数据恢复。
优点:使用单独子进程来进行持久化,主进程不会进行任何 IO 操作,保证了 redis 的高性能
缺点:RDB 是间隔一段时间进行持久化,如果持久化之间 redis 发生故障,会发生数据丢失。所以这种方式更适合数据要求不严谨的时候
AOF持久化
将“操作 + 数据”以格式化指令的方式追加到操作日志文件的尾部,在 append 操作返回后(已经 写入到文件或者将要写入),才进行实际的数据变更,“日志文件”保存了历史所有的操作过程;当 server 需要数据恢复时,可以直接 replay 此日志文件,即可还原所有的操作过程。AOF 相对可靠,AOF 文件内容是字符串,非常容易阅读和解析
优点:可以保持更高的数据完整性
缺点:AOF 文件比 RDB 文件大,且恢复速度慢。
Redis主从复制
主从复制是指将一台Redis服务器的数据复制到其他Redis服务器。前者称为主节点(master),后者称为从节点(slave),数据的复制时单项的,只能由主节点到从节点,master以写为主,slave只能进行读。
默认情况下,每台Redis服务器都是主节点,且一个主节点可以有多个从节点,但一个从节点只能有一个主节点。
作用
1、持久化保证了即使redis服务重启也不会丢失数据,但是当redis服务器的硬盘损坏了可能会导致数据丢失,通过redis的主从复制机制就可以避免这种单点故障(单台服务器的故障)。
2、主redis中的数据和从上的数据保持实时同步,当主redis写入数据时通过主从复制机制复制到两个从服务上。
3、主从复制,读写分离,80%情况下都是进行读操作,减缓服务器的压力。
Redis哨兵模式
当复制过程中出现宕机的时候
对于从机宕机只需要重启就行
但是主机宕机,需要从其下的一个从机断开主从关系并且提升为主机,主机重启好后设置为从机
缺点:手动执行,过程复杂,容易出错
哨兵模式:给集群分配一个站岗的。
哨兵的作用就是对Redis系统的运行情况监控,它是一个独立进程,它的功能:
1. 监控主数据库和从数据库是否运行正常;
2. 主数据出现故障后自动将从数据库转化为主数据库;
如果主机宕,开启选举工作,选择一个从做主机。
Redis集群
所有的redis节点彼此互联(PING-PONG机制)
redis-cluster把所有的物理节点映射到[0-16383]slot上,cluster 负责维护
Redis 集群中内置了 16384 个哈希槽,当需要在 Redis 集群中放置一个 key-value 时,redis 先对 key 使用 crc16算法算出一个结果,然后把结果对 16384 求余数,这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽,redis 会根据节点数量大致均等的将哈希槽映射到不同的节点
节点的fail是通过集群中超过半数的节点检测有效时整个集群才生效.
集群不可用(cluster_state:fail)
如果集群任意master挂掉,且当前master没有slave,则集群进入fail状态。也可以理解成集群的[0-16383]slot映射不完全时进入fail状态。
如果集群超过半数以上master挂掉,无论是否有slave,集群进入fail状态。
集群搭建
首先创建集群目录
[root@localhost redis]# mkdir redis-cluster
在集群目录下创建节点目录
搭建集群最少也得需要3台主机,如果每台主机再配置一台从机的话,则最少需要6台机器。 设计端口如下:创建6个redis实例,需要端口号7001~7006
cp redis/ redis-cluster/7001
如果存在持久化文件,则删除(dump.rdb)
修改redis.conf配置文件,打开Cluster-enable yes
修改端口
启动7001-7006这六台机器,写一个启动脚本:自定义shel脚本
cd 7001
./bin/redis-server ./redis.conf
cd ..
cd 7002
./bin/redis-server ./redis.conf
cd ..
cd 7003
./bin/redis-server ./redis.conf
cd ..
cd 7004
./bin/redis-server ./redis.conf
cd ..
cd 7005
./bin/redis-server ./redis.conf
cd ..
cd 7006
./bin/redis-server ./redis.conf
cd ..
修改start-all.sh文件的权限
chmod u+x startall.sh
启动所有的实例
./startall.sh
创建集群(关闭防火墙)
在任意一台上运行 不要在每台机器上都运行,一台就够了
然后到7001下的bin目录下执行:
[root@localhost bin]# ./redis-cli --cluster create 192.168.109.131:7001 192.168.109.131:7002 192.168.109.131:7003 192.168.109.131:7004 192.168.109.131:7005 192.168.109.131:7006 --cluster-replicas 1
其中192.168.109.131为linux对应ip
连接集群
./bin/redis-cli -h 192.168.109.131 -p 7001 -c
-c:指定是集群连接
Redis缓存雪崩、穿透和击穿***
缓存概念
广义的缓存就是在第一次加载某些可能会复用数据的时候,在加载数据的同时,将数据放到一个指定的地点做保存。再下次加载的时候,从这个指定地点去取数据。
缓存雪崩
由于原有缓存失效(或者数据未加载到缓存中),新缓存未到期间所有原本应该访问缓存的请求都去查询数据库了,而对数据库CPU和内存造成巨大压力,严重的会造成数据库宕机,造成系统的崩溃
解决方案:
1、在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。虽然能够在一定的程度上缓解了数据库的压力但是与此同时又降低了系统的吞吐量。同样会导致用户等待超时,这是个治标不治本的方法。
2、分析用户的行为,不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。
缓存穿透(查不到导致)
缓存穿透是指用户查询数据,在数据库没有,自然在缓存中也不会有。这样就导致用户查询的时候,在缓存中找不到,每次都要去数据库再查询一遍,然后返回空。这样请求就绕过缓存直接查数据库,这也是经常提的缓存命中率问题。当用户很多的时候,都去查询数据库,会给数据库造成很大压力
解决方案:
1、缓存空对象:如果查询数据库也为空,直接设置一个空对象存放到缓存,这样第二次到缓冲中获取就有值了,而不会继续访问数据库,这种办法最简单粗暴。但是请求为空的次数过多,存放的缓存过多也会影响性能。
2、布隆过滤器:是一种数据结构,对所有可能查询的参数以hash的形式存储,以控制层先进行校验,不符合则丢弃,从而避免了对底层存储系统的压力
缓存击穿(量太大,缓存过期)
对于一些设置了过期时间的key,如果这些key可能会在某些时间点被超高并发地访问,是一种非常“热点”的数据。这个时候,需要考虑一个问题:缓存被“击穿”的问题,比如热点的key在过期再到恢复的间隙,可能会有很多请求。会导致数据库瞬间压力过大。这个和缓存雪崩的区别在于这里针对某一key缓存,前者则是很多key。
解决方案:
1、设置热点key永不过期:但是一直缓存浪费空间,redis空间满了会清理空间。
2、加互斥锁:分布式锁