NoSQL:
仅仅是一个概念,泛指非关系型的数据库,区别于关系数据库,它们不保证关系数据的ACID特性。
1. redis介绍
1.1 为什么要使用NoSql?
传统的数据库遇到的瓶颈
传统的关系数据库具有不错的性能,高稳定型,久经历史考验,而且使用简单,功能强大,同时也积累 了大量的成功案例。在互联网领域,MySQL成为了绝对靠前的王者,毫不夸张的说,MySQL为互联网的发展做出了卓越的贡献。
现在网站的特点:
(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模型,通过复制模型也能实现高可用。
1.2 redis概念
全称:REmote DIctionary Server(远程字典服务器)。是完全开源免费的,用C语言编写的, 遵守BCD协议。是一个高性能的(key/value)分布式内存数据库(可以理解为map集合),基于内存运行并支持持久化的NoSQL数据库。
Redis 与其他 key - value 缓存产品有以下三个特点
(1) Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用
(2) Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储
(3) Redis支持数据的备份,即master-slave(主从)模式的数据备份
Redis优势
(1) 性能极高 – Redis能读的速度是110000次/s,写的速度是81000次/s 。
(2) 丰富的数据类型 – Redis支持二进制案例的 Strings, Lists, Hashes, Sets 及 Ordered Sets 数据类型操作。
(3) 原子 – Redis的所有操作都是原子性的,同时Redis还支持对几个操作全并后的原子性执行。
(4) 丰富的特性 – Redis还支持 publish/subscribe, 通知, key 过期等等特性
(5) 采用单线程!!!,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;
(6) 使用多路I/O复用模型,非阻塞IO(单线程下的并发);
Redis应用场景
(1) 缓存(数据查询,短连接,新闻内容,商品内容等),使用最多
(2) 聊天室在线好友列表
(3) 任务队列(秒杀,抢购,12306等)
(4) 应用排行榜
(5) 网站访问统计
(6) 数据过期处理(可以精确到毫秒)
(7) 分布式集群架构中的session问题
Redis下载
(1)Http://redis.io/ 英文地址
(2)Http://www.redis.cn/ 中文地址
2. Linux下安装Redis
2.1 Redis的编译环境
Redis是C语言开发的,安装redis需要先去官网下载源码进行编译,编译需要依赖于GCC编译环境,如果CentOS上没有安装gcc编译环境,需要提前安装,安装命令如下:(这里我们使用root用户处理这些操作)
1 | [root@localhost ~] |
2.2 Redis的安装
(1) 使用SecureFXPortable上传Redis安装文件到Linux目录
(2)上传Redis安装文件
(3)解压redis文件
1 | [root@localhost local]# tar -zxvf redis-5.0.5.tar.gz |
(4)编译Redis(编译,将.c文件编译为.o文件)
1 | [root@localhost local]# cd redis-5.0.5 |
(5) 安装
1 | [root@localhost redis-5.0.5]# make PREFIX=/mysoft/redis install |
(6)安装之后的bin目录
(7) Copy文件
Redis启动需要一个配置文件,可以修改端口号信息。将redis解压的文件夹中的redis.conf文件复制到安装目录
1 | [root@localhost redis-5.0.5]# cp redis.conf /mysoft/redis |
2.3 Redis的启动
Redis的前端模式启动
直接运行bin/redis-server将使永前端模式启动,前端模式启动的缺点是启动完成后,不能再进行其他操作,如果要操作必须使用ctrl+c,同时redis-server程序结束,不推荐此方法。
1 | [root@localhost bin] |
Redis的后端启动
修改redis.conf配置文件,设置:daemonize yes,然后可以使用后端模式启动。
1 | [root@localhost redis] |
启动时,指定配置文件(这里所在文件夹是redis)
1 | [root@localhost redis] |
Redis默认端口:6379,通过当前服务进行查看
1 | [root@localhost redis] |
2.4客户端访问redis
如果想要通过指令来操作redis,可以使用redis的客户端进行操作,在bin文件夹下运行redis-cli
该指令默认连接的127.0.0.1 ,端口号是6379
1 | [root@localhost bin]# ./redis-cli |
如果想要连接指定的ip地址以及端口号,则需要按照
1 | redis-cli -h ip地址 -p 端口号 |
Redis的停止
(1) 强制结束程序。强制终止Redis进程可能会导致redis持久化数据丢失。
语法: kill -9 pid
pid的获取:ps aux | grep -i redis
(2) 正确停止Redis的方式应该是向Redis发送SHUTDOWN命令,方法为(关闭默认的端口)
1 | [root@localhost redis] |
2.5 第三方工具(redis-desktop-manager)操作redis
1、向第三方工具开放ip
注意:需要关闭linux防火墙并且修改redis.conf文件中的bind参数
1 | bind linux的ip地址 |
2、此时如果通过redis客户端访问的时候,代码如下:(先进行检测)
1 | ./redis-cli -h 192.168.25.130 -p 6379 |
3、使用第三方工具连接
3. Redis数据结构
Redis是一种基于内存的数据库,并且提供一定的持久化功能,它是一种键值(key-value)数据库,使用 key 作为索引找到当前缓存的数据,并且返回给程序调用者。当前的 Redis 支持 6 种数据类型,它们分别是字符串(String)、列表(List)、集合(set)、哈希结构
(hash)、有序集合(zset)和基数(HyperLogLog)
4. Redis常用指令
命令学习网站:http://doc.redisfans.com/index.html
4.1 String 类型
赋值语法:SET key value
1 | 127.0.0.1:6379> set k1 zhangsan |
取值语法: GET key
1 | 127.0.0.1:6379> get k1 |
设置多个键语法: MSET key value [key value …]
1 | 127.0.0.1:6379> mset k2 lisi k3 wangwu |
获取多个键值语法: MGET key [key …]
1 | 127.0.0.1:6379> mget k2 k3 |
删除语法:DEL key
1 | 127.0.0.1:6379> del k3 |
4.2 字符串数字的递增与递减
递增数字:当存储的字符串是整数时,Redis提供了一个实用的命令INCR,其作用是让当前键值递增,并返回递增后的值。
递增数字语法: INCR key
递减数值语法: DECR key
增加指定的整数语法: INCRBY key increment
减少指定的整数 语法:DECRBY key decrement
递增
1 | 127.0.0.1:6379> incr num |
递减
1 | 127.0.0.1:6379> decr num |
指定步长:(了解)
1 | 127.0.0.1:6379> incrby num2 2 |
4.3 Hash散列(了解)
hash叫散列类型,它提供了字段和字段值的映射。字段值只能是字符串类型,不支持散列类型、集合类型等其它类型。相当于是对象格式的存储
赋值语法: HSET key field value
设置一个字段值, HSET命令不区分插入和更新操作,当执行插入操作时HSET命令返回1,当执行更新操作时返回0.
1 | 127.0.0.1:6379> hset user1 username zhangsan |
取值语法: HGET key field
1 | 127.0.0.1:6379> hget user1 username |
设置多个字段语法:HMSET key field value [field value ...]
1 | 127.0.0.1:6379> hmset user1 password 123 age 20 |
取多个值语法: HMGET key field [field ...]
1 | 127.0.0.1:6379> hmget user1 password age |
获取所有字段值语法:HGETALL key
1 | 127.0.0.1:6379> hgetall user1 |
删除字段语法:HDEL key field [field ...]
1 | 127.0.0.1:6379> hdel user1 username |
可以支持部分修改。
4,4 队列List
Redis的list是采用来链表来存储,双向链表存储数据,特点:增删快、查询慢(Linkedlist).这个队列是有序的。
向列表左边增加元素: LPUSH key value [value …]
从列表左边弹出元素: LPOP key(临时存储,弹出后,从队列中清除)
1 | 127.0.0.1:6379> lpush alist a1 a2 123 |
向列表右边增加元素 : RPUSH key value [value …]
从列表右边弹出元素: RPOP key
1 | 127.0.0.1:6379> rpush blist a1 a2 345 |
获取列表中元素的个数: LLEN key
1 | 127.0.0.1:6379> llen blist |
查看列表语法:LRANGE key start stop
1 | 127.0.0.1:6379> lrange blist 0 3 |
将返回start、stop之间的所有元素(包含两端的元素),索引从0开始,可以是负数,如:“-1”代表最后的一个元素。
示例: LPUSH comment1 ‘{“id”:1,“name”:“商品","date":1430295077289}'
临时存储。先进先出。使用双向链表:
左边进,右边出
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20127.0.0.1:6379> lpush stulist stu1
(integer) 1
127.0.0.1:6379> lpush stulist stu2
(integer) 2
127.0.0.1:6379> lpush stulist stu3
(integer) 3
127.0.0.1:6379> lpush stulist stu4
(integer) 4
127.0.0.1:6379> lpush stulist stu4
(integer) 5
127.0.0.1:6379> lpush stulist stu5
(integer) 6
127.0.0.1:6379> rpop stulist
"stu1"
127.0.0.1:6379> rpop stulist
"stu2"
127.0.0.1:6379> rpop stulist
"stu3"
127.0.0.1:6379> rpop stulist
"stu4"右边进,左边出
1
2
3
4
5
6
7
8
9
10
11
12
13
14127.0.0.1:6379> rpush clist stu1
(integer) 1
127.0.0.1:6379> rpush clist stu2
(integer) 2
127.0.0.1:6379> rpush clist stu3
(integer) 3
127.0.0.1:6379> rpush clist stu4
(integer) 4
127.0.0.1:6379> rpush clist stu5
(integer) 5
127.0.0.1:6379> lpop clist
"stu1"
127.0.0.1:6379> lpop clist
"stu2"
4.5 Set集合
Set集合类型:无序、不可重复
增加元素语法:SADD key member [member …]
删除元素语法: SREM key member [member …]
获得集合中的所有元素 : smembers key
1 | 127.0.0.1:6379> sadd ulist user1 |
判断元素是否在集合中: SISMEMBER key member
1 | 127.0.0.1:6379> smembers ulist |
4.6 Zset有序集合(了解)
Sortedset又叫zset,是有序集合,可排序的,但是唯一。 Sortedset和set的不同之处,是会给set中的元素添加一个分数,然后通过这个分数进行排序。
增加元素:ZADD key score member [score member …]
向有序集合中加入一个元素和该元素的分数(score),如果该元素已经存在则会用新的分数替换原有的分数。
1 | 127.0.0.1:6379> zadd num1 20 stu1 30 stu2 40 stu3 |
添加带分数(可用学生成绩,销售数量等来做分数,方便计算排序):
获得排名在某个范围的元素列表,并按照元素分数降序返回
语法:ZREVRANGE key start stop [WITHSCORES]
1 | 127.0.0.1:6379> zadd num1 10 stu4 |
获取元素的分数 :ZSCORE key member
1 | 127.0.0.1:6379> zscore num1 stu2 |
删除元素ZREM key member [member …]
1 | 127.0.0.1:6379> zrem num1 stu2 |
获得元素的分数的可以在命令尾部加上WITHSCORES参数
1 | 127.0.0.1:6379> zrevrange num1 0 4 withscores |
应用:商品销售量;学生排名等
给某一个属性加分数或减分,减分时使用负数:
实例:
4.7 HyoperLogLog命令
HyperLogLog是一种使用随机化的算法,以少量内存提供集合中唯一元素数量的近似值。
HyperLogLog 可以接受多个元素作为输入,并给出输入元素的基数估算值:
基数:集合中不同元素的数量。比如 {‘apple’, ‘banana’, ‘cherry’, ‘banana’, ‘apple’} 的基数就是 3 。
估算值:算法给出的基数并不是精确的,可能会比实际稍微多一些或者稍微少一些,但会控制在合理的范围之内。
HyperLogLog 的优点
即使输入元素的数量或者体积非常非常大,计算基数所需的空间总是固定的、并且是很小的。在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。但是,因为 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以 HyperLogLog 不能像集合那样,返回输入的各个元素。
1 | redis 127.0.0.1:6379> PFADD mykey "redis" |
4.8 其他命令
(1) keys返回满足给定pattern 的所有key
(2) exists确认一个key 是否存在,存在返回1
(3) del删除一个key
(4) rename重命名key:rename oldkey newkey
(5) type返回值的类型: type key
(6)设置key的生存时间:缓存的数据一般都是需要设置生存时间的,即:到期后数据销毁。
EXPIRE key seconds
key在多少秒后会自动删除TTL key
查看key剩余的生存时间PERSIST key
清除生存时间
(7) 获取服务器信息和统计:info
(8) 删除当前选择数据库中的所有key:flushdb
(9) 删除所有数据库中的所有key:flushal
1 | keys user* //查询以user开头的key |
4.9 Redis的多数据库
一个redis实例key包括多个数据库,客户端可以指定连接某个redis实例的哪个数据库,就好比一个mysql中创建多个数据库,客户端连接时指定连接哪个数据库。
一个redis实例最多可提供16个数据库,下标从0-15,客户端默认连接第0号数据库,也可以通过select选择连接哪个数据库,如下连接1号库: 类似mysql的use
将key的数据移动到1号数据库: move key 数据库编号
5. Redis的事务管理
Redis 事务可以一次执行多个命令, 并且带有以下两个重要的保证:
事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行。
一个事务从开始到执行会经历以下三个阶段:
开始事务。
命令入队。
执行事务。
实例
以下是一个事务的例子, 它先以 MULTI 开始一个事务, 然后将多个命令入队到事务中, 最后由 EXEC 命令触发事务, 一并执行事务中的所有命令:
示例1:
1 | 127.0.0.1:6379> multi |
示例2:
1 | 127.0.0.1:6379> multi |
6. Redis发布订阅模式
Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。
Redis 客户端可以订阅任意数量的频道。
下图展示了频道 channel1 , 以及订阅这个频道的三个客户端 —— client2 、 client5 和 client1 之间的关系:
当有新消息通过 PUBLISH 命令发送给频道 channel1 时, 这个消息就会被发送给订阅它的三个客户端:
在我们实例中我们创建了订阅频道名为 redisMessage:
1 | 127.0.0.1:6379> subscribe redisMessage |
现在,我们先重新开启个 redis 客户端,然后在同一个频道 redisMessage 发布两次消息,订阅者就能接收到消息。
1 | 127.0.0.1:6379> publish redisMessage "demo1 test" |
订阅者的客户端会显示如下消息
1 | 127.0.0.1:6379> subscribe redisMessage |
7. Jedis连接Redis
第一步:创建项目,导入依赖
1 | <dependency> |
注意:
1)确认远程工具是否可以连接VMredis
2)确认防火墙是否关闭或放行
service iptables stop
service iptables status
第二步:链接服务器
方案一:单实例链接
1
2
3
4
5
6
7
8
9Jedis jedis = new Jedis(“ip地址”, 端口号);//建立链接
//使用
public static void main(String[] args) {
Jedis jedis=new Jedis("192.168.197.129",6379);
//设置值
jedis.set("java001","java工程师");
String java001 = jedis.get("java001");
System.out.println(java001);
}常见异常:
解决方案:
虚拟机客户端连接的ip是127.0.0.1,意思是连接的本机,其他机器无法连接,这里需要修改配置文件,将连接地址改为虚拟机的地址,就可以了.修改redis.conf文件里面的 bind 连接地址,将连接地址改为自己虚拟机的ip(2.5提到了)方案二:连接池
jedis连接池连接,后面会使用Spring的配置文件来整合。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26// 1.获取连接池配置对象,设置配置项
JedisPoolConfig config = new JedisPoolConfig();
// 1.1最大的连接数
config.setMaxTotal(30);
// 1.2最大的空闲
config.setMaxIdle(10);
// 2.获取连接池
JedisPool jedisPool = new JedisPool(config, "192.168.197.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();
}
}
8. Redis持久化方式
什么是Redis持久化?
由于redis的值放在内存中,为防止突然断电等特殊情况的发生,需要对数据进行持久化备份。即将内存数据保存到硬盘。
8.1Redis 持久化存储方式
8.1.1 RDB持久化
RDB 是以二进制文件,是在某个时间点将数据写入一个临时文件,持久化结束后,用这个临时文件替换上次持久化的文件,达到数据恢复。
优点:使用单独子进程来进行持久化,主进程不会进行任何 IO 操作,保证了 redis 的高性能
缺点:RDB 是间隔一段时间进行持久化,如果持久化之间 redis 发生故障,会发生数据丢失。所以这种方式更适合数据要求不严谨的时候
这里说的这个执行数据写入到临时文件的时间点是可以通过配置来自己确定的,通过配置redis 在 n 秒内如果超过m 个 key 被修改这执行一次 RDB 操作。这个操作就类似于在这个时间点来保存一次 Redis 的所有数据,一次快照数据。所有这个持久化方法也通常叫做 snapshots。
RDB 默认开启,redis.conf 中的具体配置参数如下;
1 | #dbfilename:持久化数据存储在本地的文件 |
8.1.2 AOF持久化
Append-Only File,将“操作 + 数据”以格式化指令的方式追加到操作日志文件的尾部,在 append 操作返回后(已写入到文件或者将要写入),才进行实际的数据变更,“日志文件”保存了历史所有的操作过程;当 server 需要数据恢复时,可以直接 replay 此日志文件,即可还原所有的操作过程。AOF 相对可靠,AOF 文件内容是字符串,非常容易阅读和解析。
优点:可以保持更高的数据完整性
如果设置追加 file 的时间是 1s,如果 redis 发生故障,最多会丢失 1s 的数据;且如果日志写入不完整支持 redis-check-aof 来进行日志修复;AOF 文件没被 rewrite 之前(文件过大时会对命令进行合并重写),可以删除其中的某些命令(比如误操作的 flushall)。
缺点:AOF 文件比 RDB 文件大,且恢复速度慢。
我们可以简单的认为 AOF 就是日志文件,此文件只会记录“变更操作”(例如:set/del 等),如果 server 中持续的大量变更操作,将会导致 AOF 文件非常的庞大,意味着 server 失效后,数据恢复的过程将会很长;事实上,一条数据经过多次变更,将会产生多条 AOF 记录,其实只要保存当前的状态,历史的操作记录是可以抛弃的;因为 AOF持久化模式还伴生了“AOF rewrite”。
AOF 默认关闭,开启方法,修改配置文件 reds.conf:appendonly yes
1 | ##此选项为aof功能的开关,默认为“no”,可以通过“yes”来开启aof功能 |
AOF 是文件操作,对于变更操作比较密集的 server,那么必将造成磁盘 IO 的负荷加重;此外 linux 对文件操作采取了“延迟写入”手段,即并非每次 write 操作都会触发实际磁盘操作,而是进入了 buffer 中,当 buffer 数据达到阀值时触发实际写入(也有其他时机),这是 linux 对文件系统的优化,但是这却有可能带来隐患,如果 buffer 没有刷新到磁盘,此时物理机器失效(比如断电),那么有可能导致最后一条或者多条 aof 记录的丢失。通过上述配置文件,可以得知 redis 提供了 3 中 aof 记录同步选项:
- always:每一条 aof 记录都立即同步到文件,这是最安全的方式,也以为更多的磁盘操作和阻塞延迟,是 IO 开支较大。
- everysec:每秒同步一次,性能和安全都比较中庸的方式,也是 redis 推荐的方式。如果遇到物理服务器故障,有可能导致最近一秒内 aof 记录丢失(可能为部分丢失)。
- no:redis 并不直接调用文件同步,而是交给操作系统来处理,操作系统可以根据 buffer 填充情况 / 通道空闲时间等择机触发同步;这是一种普通的文件操作方式。性能较好,在物理服务器故障时,数据丢失量会因 OS 配置有关。
其实,我们可以选择的太少,everysec 是最佳的选择。如果你非常在意每个数据都极其可靠,建议你选择一款“关系性数据库”。
8.1.3 AOF与RDB区别
RDB: 快照式,到达触发条件执行一次持久化
RDB是在某个时间点将数据写入一个临时文件,持久化结束后,用这个临时文件替换上次持久化的文件,达到数据恢复。
优点:使用单独子进程来进行持久化,主进程不会进行任何IO操作,保证了redis的高性能
缺点:RDB是间隔一段时间进行持久化,如果持久化之间redis发生故障,会发生数据丢失。所以这种方式更适合数据要求不严谨的时候
AOF:
Append-only file,将“操作 + 数据”以格式化指令的方式追加到操作日志文件的尾部,在append操作返回后(已经写入到文件或者即将写
入),才进行实际的数据变更,“日志文件”保存了历史所有的操作过程;当server需要数据恢复时,可以直接replay此日志文件,即可还原
所有的操作过程。AOF相对可靠,它和mysql中bin.log、apache.log、zookeeper中txn-log简直异曲同工。AOF文件内容是字符串,非常
容易阅读和解析。优点:可以保持更高的数据完整性,如果设置追加file的时间是1s,如果redis发生故障,最多会丢失1s的数据;且如果
日志写入不完整支持redis-check-aof来进行日志修复;AOF文件没被rewrite之前(文件过大时会对命令进行合并重写),可以删除其中
的某些命令(比如误操作的flushall)。
缺点:AOF文件比RDB文件大,且恢复速度慢。
9. Redis主从复制
持久化保证了即使redis服务重启也不会丢失数据,但是当redis服务器的硬盘损坏了可能会导致数据丢失,通过redis的主从复制机制就可以避免这种单点故障(单台服务器的故障)。
主redis中的数据和从上的数据保持实时同步,当主redis写入数据时通过主从复制机制复制到两个从服务上。主从复制不会阻塞master,在同步数据时,master 可以继续处理client 请求.
主机master配置:无需配置
推荐主从模式同步数据:
工作中一般选用:一主两从或一主一从
数据会同步到从服务器。在这个集群中的几台服务器上都有同样的数据。
主从搭建步骤:
主机:不用配置。仅仅只需要配置从机,从机slave配置:(这里是伪集群)
第一步:复制出一个从机,注意使用root用户
1 | [root@localhost myapps]# cp redis/ redis1 -r |
第二步:修改从机的redis.conf
语法:replicaof // replicaof 主机ip 主机端口号
提示:检索文件: 输入:/replicaof 当前页没有时,输入n,查找下一页
第三步:修改从机的port地址为6380
在从机redis.conf中修改
第四步:清除从机中的持久化文件
1 | [root@localhost bin]# rm -rf appendonly.aof dump.rdb |
第五步:启动从机
1 | [root@localhost redis1] |
第六步:启动6380的客户端
1 | [root@localhost redis1]# ./bin/redis-cli -p 6380 |
停止客户端: ./bin/redis-cli -p 6380 shutdown
注意:
Ø 主机一旦发生增删改操作,那么从机会自动将数据同步到从机中
Ø 从机不能执行写操作,只能读
1 | 127.0.0.1:6380> get username |
9.1 复制的过程原理
当从库和主库建立MS(master slaver)关系后,会向主数据库发送SYNC命令;(从发命令)
主库接收到SYNC命令后会开始在后台保存快照(RDB持久化过程),并将期间接收到的写命令缓存起来;(主:rdb持久化+缓存写命令)
快照完成后,主Redis会将快照文件和所有缓存的写命令发送给从Redis;(主:发信息给从)
从Redis接收到后,会载入快照文件并且执行收到的缓存命令;(从:载入快照+执行命令)
主Redis每当接收到写命令时就会将命令发送从Redis,保证数据的一致;【内部完成,所以不支持客户端在从机人为写数据。】
复制架构中出现宕机情况?
从Redis宕机:重启就好
主Redis宕机:从数据库(从机)
中执行SLAVEOF NO ONE命令,断开主从关系并且提升为主库继续服务[把一个从做为主机,这个时候新主机[之前的从机]就具备写入的能力];主服务器修好后,重新启动后,执行SLAVEOF命令,将其设置为从库[老主机设置为从机]。[手动执行,过程复杂,容易出错。]是否有更好的方案?
10. Redis哨兵模式
哨兵模式:给集群分配一个站岗的。
哨兵的作用就是对Redis系统的运行情况监控,它是一个独立进程,它的功能:
- 监控主数据库和从数据库是否运行正常;
- 主数据出现故障后自动将从数据库转化为主数据库;
如果主机宕,开启选举工作,选择一个从做主机。
第一步:配置哨兵:
哨兵主要是用来监听主服务器的,所以一般把哨兵部署在从服务器上监听。
配置哨兵
启动哨兵进程,首先需要创建哨兵配置文件vi sentinel.conf,可从源码配置redis-5.0.5/sentinel.conf中复制内容,也可以直接自定义该文件到bin目录下
在配置中输入:sentinel monitor mastername 内网IP(127.0.0.1) 6379 1
说明:
mastername 监控主数据的名称,自定义
127.0.0.1:监控主数据库的IP;
6379:端口
1:最低通过票数
第二步:启动哨兵:
哨兵是一个单独的进程,启动之前确保主从服务是正常的。先启动主服务,后启动从服务
把日志写入指定的文件
1 | [root@localhost bin]# ./redis-sentinel ./sentinel.conf >sent.log & |
启动redis服务后,程序会自动配置文件sentinel.conf,并生成内容,注意:若再起启动需要删除下生成的内容。
哨兵启动方式:
1 | [root@localhost bin] |
哨兵进程控制台:为master数据库添加了一个监控.
同时多了哨兵进程:
查询配置文件sentinel.conf中生成的内容:
启动哨兵的时候,修改了哨兵的配置文件。如果需要再次启动哨兵,需要删除myid唯一标示。
(保险的做法就是启动的一次,新配置一次)
第三步:主机宕机
机房意外:断电了。硬件故障:硬盘坏了。
列表
1 | 杀死主机 |
哨兵控制台:从库自动提升为主库。
哨兵工作,链接之前的从机确认:
1 | 127.0.0.1:6381> info replication |
哨兵替代运维。自动监控完成。
同时也会自动修改redis.conf的主从配置文件。
1 | replicaof 127.0.0.1 6380 |
指向了新主机。再次启动原有的主机,原有的主机会变为从机。
总结:
主从集群:主机有写入权限。从机没有,只有可读。
意外宕机方案:
手动恢复:人为重启服务器,主机宕,把从机设置为主机。
自动恢复:使用哨兵监控。自动切换主从。
11. Redis集群方案
redis-cluster架构图
(1)所有的redis节点彼此互联(PING-PONG机制), 内部使用二进制协议优化传输速度和带宽.
(2)节点的fail是通过集群中超过半数的节点检测有效时整个集群才生效.
(3)客户端与redis节点直连,不需要中间proxy层.客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可
(4)redis-cluster把所有的物理节点映射到[0-16383]slot上,cluster 负责维护node<->slot<->value
Redis 集群中内置了 16384 个哈希槽,当需要在 Redis 集群中放置一个 key-value 时,redis 先对 key 使用 crc16算法算出一个结果,然后把结果对 16384 求余数,这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽,redis 会根据节点数量大致均等的将哈希槽映射到不同的节点
redis-cluster投票:容错
心跳机制(ping-pong机制)
(1)集群中所有master参与投票,如果半数以上master节点与其中一个master节点通信超过(cluster-node-timeout),认为该master节点挂掉.
(2):什么时候整个集群不可用(cluster_state:fail)?
Ø 如果集群任意master挂掉,且当前master没有slave,则集群进入fail状态。也可以理解成集群的[0-16383]slot映射不完全时进入fail状态。
Ø 如果集群超过半数以上master挂掉,无论是否有slave,集群进入fail状态。
11.1 集群搭建步骤
第一步:安装redis
第二步:创建集群目录
1 | [root@localhost redis] |
第三步:在集群目录下创建节点目录
搭建集群最少也得需要3台主机,如果每台主机再配置一台从机的话,则最少需要6台机器。 设计端口如下:创建6个redis实例,需要端口号7001~7006
1 | [root@localhost myapps]# cp redis/ redis-cluster/7001 -r |
第四步:如果存在持久化文件,则删除
1 | [root@localhost 7001]# rm -rf appendonly.aof dump.rdb |
第五步:修改redis.conf配置文件,打开Cluster-enable yes
说明:cluster-enable 是否支持集群
第六步:修改端口
第七步:复制出7002-7006机器
1 | root@localhost redis-cluster]# cp 7001/ 7002 -r |
第八步:修改7002-7006机器的端口
第九步:启动7001-7006这六台机器,写一个启动脚本:自定义shel脚本
[root@localhost redis-cluster]# vi startall.sh
1 | cd 7001 |
第十步:修改start-all.sh文件的权限[root@localhost redis-cluster]# chmod u+x startall.sh
第十一步:启动所有的实例[root@localhost redis-cluster]# ./startall.sh
第十二步:创建集群(关闭防火墙)
注意:在任意一台上运行 不要在每台机器上都运行,一台就够了 redis 5.0.5中使用redis-cli —cluster替代redis-trib.rb,命令如下redis-cli --cluster create ip:port ip:port --cluster-replicas 1
1 | [root@localhost redis_cluster]# cd /home/admin/myapps/redis-cluster/7001/bin |
11.2 连接集群
命令:[root@localhost 7001]# ./bin/redis-cli -h 127.0.0.1 -p 7001 -c
-c:指定是集群连接
1 | [root@localhost 7001]# ./bin/redis-cli -h 127.0.0.1 -p 7001 -c127.0.0.1:7001> set username java123 |
关闭防火墙:service iptables stop
查看防火墙状态:service iptables status
11.3 查看集群信息
1 | 127.0.0.1:7003> cluster info |
集群节点信息
1 | 127.0.0.1:7003> cluster nodes |
11.4 Jedis连接集群
12.7.1 关闭防火墙
注意:如果redis重启,需要将redis中生成的dumlsp.rdb和nodes.conf文件删除,然后再重启。
12.7.2 代码实现
1 | <dependency> |
注意jedis的版本,其他版本有可能报错:java.lang.NumberFormatException: For input string: “7002@17002”
1 | public static void main(String[] args) throws IOException { |
12. 问题
12.1 缓存
缓存穿透,缓存击穿,缓存雪崩问题
什么是缓存?
广义的缓存就是在第一次加载某些可能会复用数据的时候,在加载数据的同时,将数据放到一个指定的地点做保存。再下次加载的时候,从这个指定地点去取数据。这里加缓存是有一个前提的,就是从这个地方取数据,比从数据源取数据要快的多。java狭义一些的缓存,主要是指三大类
- 虚拟机缓存(ehcache,JBoss Cache)
- 分布式缓存(redis,memcache)
- 数据库缓存
正常来说,速度由上到下依次减慢
缓存取值图:
12.1.1 缓存雪崩
缓存雪崩产生的原因
缓存雪崩通俗简单的理解就是:由于原有缓存失效(或者数据未加载到缓存中),新缓存未到期间(缓存正常从Redis中获取,如下图)所有原本应该访问缓存的请求都去查询数据库了,而对数据库CPU和内存造成巨大压力,严重的会造成数据库宕机,造成系统的崩溃。
缓存失效的时候如下图:
解决方案:
1:在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。虽然能够在一定的程度上缓解了数据库的压力但是与此同时又降低了系统的吞吐量。
1 | public Users getByUsers(Long id) { |
注意:加锁排队只是为了减轻数据库的压力,并没有提高系统吞吐量。假设在高并发下,缓存重建期间key是锁着的,这是过来1000个请求999个都在阻塞的。同样会导致用户等待超时,这是个治标不治本的方法。
2: 分析用户的行为,不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。
12.1.2 缓存穿透
是指用户查询数据,在数据库没有,自然在缓存中也不会有。这样就导致用户查询的时候,在缓存中找不到,每次都要去数据库再查询一遍,然后返回空。这样请求就绕过缓存直接查数据库,这也是经常提的缓存命中率问题。
解决方案:
1.如果查询数据库也为空,直接设置一个默认值存放到缓存,这样第二次到缓冲中获取就有值了,而不会继续访问数据库,这种办法最简单粗暴。
2.把空结果,也给缓存起来,这样下次同样的请求就可以直接返回空了,既可以避免当查询的值为空时引起的缓存穿透。同时也可以单独设置个缓存区域存储空值,对要查询的key进行预先校验,然后再放行给后面的正常缓存处理逻辑。
注意:再给对应的ip存放真值的时候,需要先清除对应的之前的空缓存。
1 | public String getByUsers2(Long id) { |
12.1.3 缓存击穿
对于一些设置了过期时间的key,如果这些key可能会在某些时间点被超高并发地访问,是一种非常“热点”的数据。
这个时候,需要考虑一个问题:缓存被“击穿”的问题,这个和缓存雪崩的区别在于这里针对某一key缓存,前者则是很多key。
热点key:
某个key访问非常频繁,当key失效的时候有大量线程来构建缓存,导致负载增加,系统崩溃。
解决办法:
①使用锁,单机用synchronized,lock等,分布式用分布式锁。
②缓存过期时间不设置,而是设置在key对应的value里。如果检测到存的时间超过过期时间则异步更新缓存。
12.2 分布式锁
使用分布式锁要满足的几个条件:
- 系统是一个分布式系统(关键是分布式,单机的可以使用ReentrantLock或者synchronized代码块来实现)
- 共享资源(各个系统访问同一个资源,资源的载体可能是传统关系型数据库或者NoSQL)
- 同步访问(即有很多个进程同时访问同一个共享资源。)
什么是分布式锁?
线程锁:主要用来给方法、代码块加锁。当某个方法或代码使用锁,在同一时刻仅有一个线程执行该方法或该代码段。线程锁只在同一JVM中有效果,因为线程锁的实现在根本上是依靠线程之间共享内存实现的,比如synchronized是共享对象头,显示锁Lock是共享某个变量(state)。
进程锁:为了控制同一操作系统中多个进程访问某个共享资源,因为进程具有独立性,各个进程无法访问其他进程的资源,因此无法通过synchronized等线程锁实现进程锁。
分布式锁:当多个进程不在同一个系统中,用分布式锁控制多个进程对资源的访问。
应用的场景
线程间并发问题和进程间并发问题都是可以通过分布式锁解决的,但是强烈不建议这样做!因为采用分布式锁解决这些小问题是非常消耗资源的!分布式锁应该用来解决分布式情况下的多进程并发问题才是最合适的。(即多个jvm)
有这样一个情境,线程A和线程B都共享某个变量X。
如果是单机情况下(单JVM),线程之间共享内存,只要使用线程锁就可以解决并发问题。如果是分布式情况下(多JVM),线程A和线程B很可能不是在同一JVM中,这样线程锁就无法起到作用了,这时候就要用到分布式锁来解决。
分布式锁可以基于很多种方式实现,比如zookeeper、redis…。不管哪种方式,他的基本原理是不变的:用一个状态值表示锁,对锁的占用和释放通过状态值来标识。
这里主要讲如何用redis实现分布式锁。
使用redis的setNX命令实现分布式锁
实现的原理
Redis为单进程单线程模式,采用队列模式将并发访问变成串行访问,且多客户端对Redis的连接并不存在竞争关系。redis的SETNX命令
可以方便的实现分布式锁。
- 基本命令解析
setNX(SET if Not eXists)