一、NoSQL
1、概述
为了解决高并发、高可扩展、高可用、大数据存储问题而产生的数据库解决方案,就是NoSql数据库。
NoSQL,泛指非关系型的数据库,NoSQL即Not-Only SQL,它可以作为关系型数据库的良好补充
2、为什么使用NoSQL
1 Mysql时代
在90年代,一个网站的访问量一般都不大,用单个数据库完全可以轻松应付。
在那个时候,更多的都是静态网页,动态交互类型的网站不多。
服务器根本没有太大的压力!
思考:随着时代的发展,网站的瓶颈是什么?
1.数据量的总大小 一个机器放不下时
2.数据的索引(B+ Tree)一个机器的内存放不下时(数据量大)
3.访问量(读写混合)一个实例不能承受
如果满足了上述1 or 3个,需要升级
2 Memcached+MySQL+垂直拆分
后来,随着访问量的上升,几乎大部分使用MySQL架构的网站在数据库上都开始出现了性能问题,web程序不再仅仅专注在功能上,同时也在追求性能。程序员们开始大量的使用缓存技术来缓解数据库的压力,优化数据库的结构和索引。
- 开始比较流行的是通过文件缓存来缓解数据库压力
- 是当访问量继续增大的时候,多台web机器通过文件缓存不能共享,大量的小文件缓存也带了了比较高的IO压力。在这个时候,Memcached就自然的成为一个非常时尚的技术产品。
- Memcached作为一个独立的分布式的缓存服务器,为多个web服务器提供了一个共享的高性能缓存服务
- 由于数据库的写入压力增加,Memcached只能缓解数据库的读取压力。由此引出数据库的从复制技术来达到读写分离,以提高读写性能和读库的可扩展
- Mysql的master-slave模式成为这个时候的网站标配了
3 分库分表+水平拆分+MySQL集群
在Memcached的高速缓存,MySQL的主从复制,读写分离的基础之上,这时MySQL主库的写压力开始出现瓶颈,而数据量的持续猛增,由于MyISAM使用表锁(锁定某张表),在高并发下会出现严重的锁问题,大量的高并发MySQL应用开始使用InnoDB引擎代替MyISAM,InnoDB是行锁(锁定指定行记录)。
同时,开始流行使用分表分库来缓解写压力和数据增长的扩展问题。这个时候,分表分库成了一个热门技术,是面试的热门问题也是业界讨论的热门技术问题。也就在这个时候,MySQL推出了还不太稳定的表分区,这也给技术实力一般的公司带来了希望。虽然MySQL推出了MySQL Cluster集群,但性能也不能很好满足互联网的要求,只是在高可靠性上提供了非常大的保证。
出现发展瓶颈
MySQL数据库也经常存储一些大文本字段,导致数据库表非常的大,在做数据库恢复的时候就导致非常的慢,不容易快速恢复数据库。比如1000万4KB大小的文本就接近40GB的大小,如果能把这些数据从MySQL省去,MySQL将变得非常的小。关系数据库很强大,但是它并不能很好的应付所有的应用场景。MySQL的扩展性差(需要复杂的技术来实现),大数据下IO压力大,表结构更改困难,正是当前使用MySQL的开发人员面临的问题。
4 解决方案
MySQL 等关系型数据库就不够用了!数据量很多,变化很快~!
MySQL 有的使用它来存储一些比较大的文件,博客,图片!数据库表很大,效率就低了!如果有一种数 据库来专门处理这种数据
用户的个人信息,社交网络,地理位置。用户自己产生的数据,用户日志等等爆发式增长! 这时候我们就需要使用NoSQL数据库的,Nosql可以很好的处理以上的情况
5 NoSQL特点
- 方便扩展(数据之间没有关系,很好扩展!)
- 大数据量高性能(Redis 一秒写8万次,读取11万,NoSQL的缓存记录级,是一种细粒度的缓存,性能会比较高!)
- 数据类型是多样型的!(不需要事先设计数据库!随取随用!)
- 传统 RDBMS 和 NoSQL对比
- RDBMS
- 高度组织化结构化数据
- 结构化查询语言(SQL)
- 数据和关系都存储在单独的表中。
- 数据操纵语言,数据定义语言
- 严格的一致性
- 基础事务- NoSQL
- 代表着不仅仅是SQL
- 没有声明性查询语言
- 没有预定义的模式
- 键 - 值对存储,列存储,文档存储,图形数据库
- 最终一致性,而非ACID属性
- 非结构化和不可预知的数据
- CAP定理
- 高性能,高可用性和可伸缩性
- 一般来说现在公司更多的情况是Mysql+NoSQL共同使用
3、新时代的 3V、3高
3V
- 海量Velume
- 多样Variety
- 实时Velocity
3高
- 高并发
- 高可扩
- 高性能
4、使用场景
缓存(数据查询、短连接、新闻内容、商品内容等等)。(最多使用)
分布式集群架构中的session分离。
聊天室的在线好友列表。
任务队列。(秒杀、抢购、12306等等)
应用排行榜。
网站访问统计。
数据过期处理(可以精确到毫秒)
二、NoSQL四大分类
1、分类
KV键值对
- 新浪:BerkeleyDB+redis
- 美团:redis+tair
- 阿里、百度:memcache+redis
文档型数据库
- CouchDB
- MongoDB
- 这个数据库目前市面上使用较多, 前端也常使用
- MongoDB 是一个基于分布式文件存储的数据库。由 C++ 语言编写。旨在为 WEB 应用提供可扩展的高性能数据存储解决方案。
- MongoDB 是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的。
列存数据库
- Cassandra, HBase
- 分布式文件系统
图关系数据库
- 它不是放图形的,放的是关系比如:朋友圈社交网络、广告推荐系统,社交网络,推荐系统等。专注于构建关系图谱
- Neo4J, InfoGrid
2、对比图
三、分布式相关理念
1、ACID
- A (Atomicity) 原子性
原子性很容易理解,也就是说事务里的所有操作要么全部做完,要么都不做,事务成功的条件是事务里的所有操作都成功,只要有一个操作失败,整个事务就失败,需要回滚
- C (Consistency) 一致性
一致性也比较容易理解,也就是说数据库要一直处于一致的状态,事务的运行不会改变数据库原本的一致性约束。
- I (Isolation) 独立性
所谓的独立性是指并发的事务之间不会互相影响,如果一个事务要访问的数据正在被另外一个事务修改,只要另外一个事务未提交,它所访问的数据就不受未提交事务的影响。比如现有有个交易是从A账户转100元至B账户,在这个交易还未完成的情况下,如果此时B查询自己的账户,是看不到新增加的100元的
- D (Durability) 持久性
持久性是指一旦事务提交后,它所做的修改将会永久的保存在数据库上,即使出现宕机也不会丢失。
2、CAP理论
- C:Consistency(强一致性)
- A:Availability(可用性)
- P:Partition tolerance(分区容错性)
CAP理论在分布式存储系统中,最多只能实现上面的两点。而由于当前的网络硬件肯定会出现延迟丢包等问题,所以分区容忍性是我们必须需要实现的。(异地多活)
所以我们只能在一致性和可用性之间进行权衡,没有NoSQL系统能同时保证这三点。
1 如何选择
CA 传统Oracle数据库
AP 大多数网站架构的选择
CP Redis、Mongodb
一致性和可用性之间取一个平衡。多余大多数web应用,其实并不需要强一致性。
因此牺牲C换取P,这是目前分布式数据库产品的方向
2 原因
对关系数据库来说,插入一条数据之后立刻查询,是肯定可以读出来这条数据的,但是对于很多web应用来说,并不要求这么高的实时性,比方说发一条消息之 后,过几秒乃至十几秒之后,我的订阅者才看到这条动态是完全可以接受的。
对复杂的SQL查询,特别是多表关联查询的需求
任何大数据量的web系统,都非常忌讳多个大表的关联查询,以及复杂的数据分析类型的报表查询,特别是SNS类型的网站,从需求以及产品设计角 度,就避免了这种情况的产生。往往更多的只是单表的主键查询,以及单表的简单条件分页查询,SQL的功能被极大的弱化了
3 图解
3、解决关系数据库弱势(BASE)
BASE其实是下面三个术语的缩写:
基本可用(Basically Available):当分布式系统出现不可预见的故障时,允许损失部分可用性,保障系统的基本可用
如:部分用户双十一高峰期淘宝页面卡顿或降级处理
软状态(Soft state):允许系统中的数据存在中间状态,既系统的不同节点的数据副本之间的数据同步过程存在延时,并认为这种延时不会影响系统可用性
如:12306网站卖火车票,请求会进入排队队列
最终一致(Eventually consistent):所有的数据在经过一段时间的数据同步后,最终能够达到一个一致的状态
理财产品首页充值总金额短时不一致
它的思想是通过让系统放松对某一时刻数据一致性的要求来换取系统整体伸缩性和性能上改观。为什么这么说呢,缘由就在于大型系统往往由于地域分布和极高性能的要求,不可能采用分布式事务来完成这些指标,要想获得这些指标,我们必须采用另外一种方式来完成,这里BASE就是解决这个问题的办法
4、分布式与集群
什么是分布式?
分布式系统是由一组通过网络进行通信、为了完成共同的任务而协调工作的计算机节点组成的系统。分布式系统的出现是为了用廉价的、普通的机器完成单个计算机无法完成的计算、存储任务。其目的是利用更多的机器,处理更多的数据。
简单来说:
分布式:不同的多台服务器上面部署不同的服务模块(工程),他们之间通过Rpc/Rmi之间通信和调用,对外提供服务和组内协作。
什么是集群?
通过多台计算机完成同一个工作,达到更高的效率
四、Redis入门
1、Redis是什么
Redis:REmote DIctionary Server(远程字典服务器)
是完全开源免费的,用C语言编写的,遵守BSD协议,
是一个高性能的(key/value)分布式内存数据库,基于内存运行
并支持持久化的NoSQL数据库,是当前最热门的NoSql数据库之一,
也被人们称为数据结构服务器
2、Redis的特点
- Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用
- Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储
- Redis支持数据的备份,即master-slave模式的数据备份
3、Redis能做什么
- 内存存储和持久化:redis支持异步将内存中的数据写到硬盘上,同时不影响继续服务
- 取最新N个数据的操作,如:可以将最新的10条评论的ID放在Redis的List集合里面
- 模拟类似于HttpSession这种需要设定过期时间的功能
- 发布、订阅消息系统
- 定时器、计数器
下载地址
4、Redis安装
提示: redis有window版和Linux版(推荐)
1 Windows版本
- windows版本下载后解压就行
- 想要在windows上打开redis, 要先打开服务器端, 再打开客户端
2 Linux版本
- 先安装好VM与Linux系统
- 在Linux下安装gcc环境
yum install gcc-c++
- 下载源码包上传到Linux系统的/opt目录下
- 或者可以进入/opt目录使用以下命令下载
wget http://download.redis.io/releases/redis-5.0.5.tar.gz
- 复制压缩文件到/usr/local/目录下,解压
[root@admin opt]# cp redis-5.0.5.tar.gz /usr/local/src/
[root@admin opt]# cd /usr/local/src/
[root@admin src]# tar -zxvf redis-5.0.5.tar.gz
- 进入解压后的文件目录,编译,安装
$ cd redis-5.0.5
$ make
$ make install PREFIX=/usr/local/redis-5.0.5
3 Redis启动
前端启动
有弊端, 页面不能退出, 退出就是服务器停止了
- 进入启动目录
- cd /usr/local/redis-5.0.5/bin/
- 执行启动服务器
- ./redis-server
ctrl+c退出
后台启动
后台启动需要修改redis的配置文件, 可以复制一份配置文件从redis源码种到/usr/local/的redis目录下
之后启动redis的时候用这个文件作为启动项
[root@admin bin]# cd /usr/local/src/redis-5.0.5
[root@admin redis-5.0.5]# cp redis.conf /usr/local/redis-5.0.5/
[root@admin redis-5.0.5]# vim /usr/local/redis-5.0.5/redis.conf
以修改后的配置文件来启动服务器
[root@admin redis-5.0.5]# cd /usr/local/redis-5.0.5/bin/
[root@admin bin]# ./redis-server /usr/local/redis-5.0.5/redis.conf
启动客户端
#同样在src目录下
[root@admin bin]# ./redis-cli
127.0.0.1:6379>
# 显示的命令发生改变表示成功
127.0.0.1:6379> set k1 hello
OK
127.0.0.1:6379> get k1
"hello"
127.0.0.1:6379>
5、压力测试
redis-benchmark 是一个压力测试工具
使用参考
https://www.runoob.com/redis/redis-benchmarks.html
指令
redis-benchmark [option] [option value]
测试1000个实例
[root@admin bin]# ./redis-benchmark -n 1000 -q
PING_INLINE: 166666.67 requests per second
PING_BULK: 124999.99 requests per second
SET: 142857.14 requests per second
GET: 166666.67 requests per second
INCR: 124999.99 requests per second
LPUSH: 166666.67 requests per second
RPUSH: 142857.14 requests per second
LPOP: 200000.00 requests per second
RPOP: 200000.00 requests per second
SADD: 166666.67 requests per second
HSET: 124999.99 requests per second
SPOP: 166666.67 requests per second
LPUSH (needed to benchmark LRANGE): 200000.00 requests per second
LRANGE_100 (first 100 elements): 76923.08 requests per second
LRANGE_300 (first 300 elements): 32258.06 requests per second
LRANGE_500 (first 450 elements): 21276.60 requests per second
LRANGE_600 (first 600 elements): 17543.86 requests per second
MSET (10 keys): 142857.14 requests per second
Redis 为什么单线程还这么快?
误区1:高性能的服务器一定是多线程的?
误区2:多线程(CPU上下文会切换!)一定比单线程效率高! 先去CPU>内存>硬盘的速度要有所了解!
核心:redis 是将所有的数据全部放在内存中的,所以说使用单线程去操作效率就是高的,多线程 (CPU上下文会切换:耗时的操作!!!),对于内存系统来说,如果没有上下文切换效率就是高 的!多次读写都是在一个CPU上的,在内存情况下,这个就是佳的方案!
6、Redis库相关指令
- 默认16个数据库,类似数组下表从零开始,初始默认使用零号库
- select令切换数据库
127.0.0.1:6379> select 2
OK
127.0.0.1:6379[2]>
- dbsize查看当前数据库的key的数量
127.0.0.1:6379[2]> select 0
OK
127.0.0.1:6379> dbsize
(integer) 5
127.0.0.1:6379>
- flflushdb:清空当前库
127.0.0.1:6379> set k1 abc
OK
127.0.0.1:6379> set k2 cba
OK
127.0.0.1:6379> dbsize
(integer) 6
127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> dbsize
(integer) 0
127.0.0.1:6379>
- Flushall:通杀全部库
7、关于Keys的相关指令
1 设置key的生存时间
Redis在实际使用过程中更多的用作缓存,然而缓存的数据一般都是需要设置生存时间的,即:到期后数据销毁。
基本语法
EXPIRE key seconds #设置key的生存时间(单位:秒)key在多少秒后会自动删除
TTL key #查看key剩余的生存时间
PERSIST key #清除生存时间
PEXPIRE key milliseconds #生存时间设置单位为:毫秒
案例
127.0.0.1:6379> set test 1 #设置test的值为1
OK
127.0.0.1:6379> get test #获取test的值
"1"
127.0.0.1:6379> expire test 5 #设置test的生存时间为5秒
(integer) 1
127.0.0.1:6379> ttl test #查看test的生于生成时间还有2秒删除
(integer) 2
127.0.0.1:6379> ttl test
(integer) -2
127.0.0.1:6379> get test #获取test的值,已经删除
(nil)
2 keys
返回满足给定pattern 的所有key
基本语法
keys * # 可以是*也可以是相关的某字段*
案例
127.0.0.1:6379> set a1 1
OK
127.0.0.1:6379> set a2 2
OK
127.0.0.1:6379> set asd 3
OK
127.0.0.1:6379> set a34 4
OK
127.0.0.1:6379> keys a*
1) "a2"
2) "a1"
3) "a34"
4) "asd"
127.0.0.1:6379>
3 exists
确认一个key 是否存在
示例:从结果来看,数据库中不存在HongWan 这个key,但是age 这个key 是存在的
基本语法
exists key
案例
127.0.0.1:6379> exists asd
(integer) 1
127.0.0.1:6379> exists a3
(integer) 0
4 del
删除一个key
基本语法
del key
案例
127.0.0.1:6379> del asd
(integer) 1
127.0.0.1:6379> exists asd
(integer) 0
5 rename
重命名key
基本语法
rename oldKey newKey
6 type
返回值的类型
基本语法
type key
案例
127.0.0.1:6379> type addr
string
127.0.0.1:6379> type myzset2
zset
127.0.0.1:6379> type mylist
list
8、Redis常用数据类型
- 字符串类型(String)
- 散列类型(Hash)
- 列表类型(List)
- 集合类型(Set)
- 有序集合类型(SortedSet)
1 String
赋值
语法:SET key value
127.0.0.1:6379> set test 123
OK
取值
语法:GET
key 127.0.0.1:6379> get test
"123“
设置/获取多个键值
语法:
MSET key value [key value …]
MGET key [key …]
127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3
OK
127.0.0.1:6379> get k1
"v1"
127.0.0.1:6379> mget k1 k3
1) "v1"
2) "v3"
取值并赋值
语法:GETSET key value
127.0.0.1:6379> getset s2 222
"111"
127.0.0.1:6379> get s2
"222"
删除
语法:DEL key
127.0.0.1:6379>del test
(integer) 1
数值增减
当存储的字符串是整数时,Redis提供了一个实用的命令INCR,其作用是让当前键值递增,并返回递增后的值。
语法:INCR key
127.0.0.1:6379> incr num
(integer) 1
127.0.0.1:6379> incr num
(integer) 2
127.0.0.1:6379> incr num
(integer) 3
######################################
增加指定的整数
语法:INCRBY key increment
127.0.0.1:6379> incrby num 2
(integer) 5
127.0.0.1:6379> incrby num 2
(integer) 7
127.0.0.1:6379> incrby num 2
(integer) 9
################################
递减数值
语法:DECR key
127.0.0.1:6379>decr num
(integer) 9
127.0.0.1:6379>decr num
(integer) 8
####################################
减少指定的整数
语法:DECRBY key decrement
127.0.0.1:6379> decr num
(integer) 6
127.0.0.1:6379> decr num
(integer) 5
127.0.0.1:6379> decrby num 3
(integer) 2
127.0.0.1:6379> decrby num 3
(integer) -1
#######################################
向尾部追加值
APPEND的作用是向键值的末尾追加value。如果键不存在则将该键的值设置为value,即相当于SET key value。返回值是追加后字符串的总长度
语法:APPEND key value
127.0.0.1:6379> set str hello
OK
127.0.0.1:6379> append str " world!"
(integer) 12
127.0.0.1:6379> get str
"hello world!"
获取字符串长度
STRLEN命令返回键值的长度,如果键不存在则返回0。
语法:STRLEN key
127.0.0.1:6379> strlen str
(integer) 0
127.0.0.1:6379> set str hello
OK
127.0.0.1:6379> strlen
str (integer) 5
2 Hash
hash叫散列类型,它提供了字段和字段值的映射。字段值只能是字符串类型,不支持散列类型、集合类型等其它类型。如下:
赋值
HSET命令不区分插入和更新操作,当执行插入操作时HSET命令返回1,当执行更新操作时返回0。
#一次只能设置一个字段值
语法:HSET key field value
127.0.0.1:6379> hset user username zhangsan
(integer) 1
#一次可以设置多个字段值
语法:HMSET key field value [field value ...]
127.0.0.1:6379> hmset user age 20 username lisi
OK
#当字段不存在时赋值,类似HSET,区别在于如果字段存在,该命令不执行任何操作
语法:HSETNX key field value
127.0.0.1:6379> hsetnx user age 30
#如果user中没有age字段则设置age值为30,否则不做任何操作
(integer) 0
取值
#一次只能获取一个字段值
语法:HGET key field
127.0.0.1:6379> hget user username
"zhangsan“
#一次可以获取多个字段值
语法:HMGET key field [field ...]
127.0.0.1:6379> hmget user age username
1) "20"
2) "lisi"
#获取所有字段值
语法:HGETALL key
127.0.0.1:6379> hgetall user
1) "age"
2) "20"
3) "username"
4) "lisi"
删除字段
可以删除一个或多个字段,返回值是被删除的字段个数
#语法:HDEL key field [field ...]
127.0.0.1:6379> hdel user age
(integer) 1
127.0.0.1:6379> hdel user age name
(integer) 0
127.0.0.1:6379> hdel user age username
(integer) 1
3 List
ArrayList与LinkedList的区别
ArrayList使用数组方式存储数据,所以根据索引查询数据速度快,而新增或者删除元素时需要设计到位移操作,所以比较慢。
LinkedList使用双向链表方式存储数据,每个元素都记录前后元素的指针,所以插入、删除数据时只是更改前后元素的指针指向即可,速度非常快。然后通过下标查询元素时需要从头开始索引,所以比较慢,但是如果查询前几个元素或后几个元素速度比较快。
Redis的list
列表类型(list)可以存储一个有序的字符串列表,常用的操作是向列表两端添加元素,或者获得列表的某一个片段。
列表类型内部是使用双向链表(double linked list)实现的,所以获取越接近两端的元素速度就越快。这意味着即使是一个有几千万个元素的列表,获取头部或尾部的10条记录也是极快的
向列表两端增加元素
# 向列表左边增加元素
语法:LPUSH key value [value ...]
127.0.0.1:6379> lpush list 1 2 3
(integer) 3
# 向列表右边增加元素
语法:RPUSH key value [value ...]
127.0.0.1:6379> rpush list a b c
(integer) 3
查看列表
LRANGE命令是列表类型最常用的命令之一,获取列表中的某一片段,将返回start、stop之间的所有元素(包含两端的元素),索引从0开始。索引可以是负数,如:“-1”代表最后边的一个元素。
#语法:LRANGE key start stop
127.0.0.1:6379> lrange list 0 5
1) "3"
2) "2"
3) "1"
4) "a"
5) "b"
6) "c"
从列表两端弹出元素
LPOP命令从列表左边弹出一个元素,会分两步完成:
第一步是将列表左边的元素从列表中移除
第二步是返回被移除的元素值。
语法:
LPOP key
RPOP key
127.0.0.1:6379> lpop list
"c“
127.0.0.1:6379> rpop list
"3“
获取列表中元素的个数
语法:LLEN key
127.0.0.1:6379> llen list
(integer) 4
获得/设置指定索引的元素值
#获得指定索引的元素值
语法:LINDEX key index
127.0.0.1:6379> lindex list 2
"1"
#设置指定索引的元素值
语法:LSET key index value
127.0.0.1:6379> lset list 2 2
OK
127.0.0.1:6379> lrange list 0 -1 ``#//查询所有
1) "6"
2) "5"
3) "2"
4) "2"
向列表中插入元素
该命令首先会在列表中从左到右查找值为pivot的元素,然后根据第二个参数是BEFORE还是AFTER来决定将value插入到该元素的前面还是后面。
#语法:LINSERT key BEFORE|AFTER pivot value
127.0.0.1:6379> lrange list 0 -1
1) "3"
2) "2"
3) "1"
127.0.0.1:6379> linsert list after 3 4
(integer) 4
127.0.0.1:6379> lrange list 0 -1
1) "3"
2) "4"
3) "2"
4) "1"
将元素从一个列表转移到另一个列表中
语法:RPOPLPUSH source destination
127.0.0.1:6379> rpoplpush list newlist "
1"
127.0.0.1:6379> lrange newlist 0 -1
1) "1"
127.0.0.1:6379> lrange list 0 -1
1) "3"
2) "4"
3) "2"
4 Set
集合中的数据是不重复且没有顺序。
集合类型的常用操作是向集合中加入或删除元素、判断某个元素是否存在等
Redis还提供了多个集合之间的交集、并集、差集的运算。
增加/删除元素
#语法:SADD key member [member ...]/增加
127.0.0.1:6379> sadd key1 a b c
(integer) 3
127.0.0.1:6379> sadd key1 a
(integer) 0
#语法:SREM key member [member ...]/删除 1
27.0.0.1:6379> srem set c
d (integer) 1
获得集合中的所有元素
语法:SMEMBERS key
127.0.0.1:6379> smembers key1
1) "b"
2) "a”
判断元素是否在集合中
语法:SISMEMBER key member
127.0.0.1:6379> sismember set a
(integer) 1
127.0.0.1:6379> sismember set h
(integer) 0
运算命令
集合的差集运算A-B
属于A并且不属于B的元素构成的集合
语法:SDIFF key [key ...]
127.0.0.1:6379> sadd setA 1 2 3
(integer) 3
127.0.0.1:6379> sadd setB 2 3 4
(integer) 3
127.0.0.1:6379> sdiff setA setB
1) "1"
127.0.0.1:6379> sdiff setB setA
1) "4"
集合的交集运算A∩B
属于A且属于B的元素构成的集合。
语法:SINTER key [key ...]
127.0.0.1:6379> sinter setA setB
1) "2"
2) "3"
集合的并集运算A∪B
属于A或者属于B的元素构成的集合
语法:SUNION key [key ...]
127.0.0.1:6379> sunion setA setB
1) "1"
2) "2"
3) "3"
4) "4"
获得集合中元素的个数
语法:SCARD key
127.0.0.1:6379> smembers setA
1) "1"
2) "2"
3) "3"
127.0.0.1:6379> scard setA
(integer) 3
从集合中弹出一个元素
注意:由于集合是无序的,所有SPOP命令会从集合中随机选择一个元素弹出
语法:SPOP key
127.0.0.1:6379> spop setA
"1“
5 SortedSet :zset
在集合类型的基础上,有序集合类型为集合中的每个元素都关联一个分数,这使得我们不仅可以完成插入、删除和判断元素是否存在在集合中,还能够获得分数最高或最低的前N个元素、获取指定分数范围内的元素等与分数有关的操作。
在某些方面有序集合和列表类型有些相似。
1、二者都是有序的。
2、二者都可以获得某一范围的元素。
但是,二者有着很大区别:
1、列表(List)类型是通过链表实现的,获取靠近两端的数据速度极快,而当元素增多后,访问中间数据的速度
会变慢。
2、有序集合(SortedSet)类型使用散列表实现,所有即使读取位于中间部分的数据也很快。
3、列表中不能简单的调整某个元素的位置,但是有序集合可以(通过更改分数实现)
4、有序集合要比列表类型更耗内存
增加元素
向有序集合中加入一个元素和该元素的分数,如果该元素已经存在则会用新的分数替换原有的分数。返回值是新加入
到集合中的元素个数,不包含之前已经存在的元素。
语法:ZADD key score member [score member ...]
127.0.0.1:6379> zadd scoreboard 80 zhangsan 89 lisi 94 wangwu
(integer) 3
127.0.0.1:6379> zadd scoreboard 97 lisi
(integer) 0
获取元素的分数
语法:ZSCORE key member
127.0.0.1:6379> zscore scoreboard lisi
"97"
删除元素
移除有序集key中的一个或多个成员,不存在的成员将被忽略。
当key存在但不是有序集类型时,返回一个错误。
语法:ZREM key member [member ...]
127.0.0.1:6379> zrem scoreboard lisi
(integer) 1
获得排名在某个范围的元素列表
按照元素分数从小到大的顺序返回索引从start到stop之间的所有元素(包含两端的元素)
语法:ZRANGE key start stop [WITHSCORES]
127.0.0.1:6379> zrange scoreboard 0 2
1) "zhangsan"
2) "wangwu"
3) "lisi“
按照元素分数从大到小的顺序返回索引从start到stop之间的所有元素(包含两端的元素)
语法:ZREVRANGE key start stop [WITHSCORES]
127.0.0.1:6379> zrevrange scoreboard 0 2
1) " lisi "
2) "wangwu"
3) “zhangsan”
如果需要获得元素的分数的可以在命令尾部加上***WITHSCORES***参数
127.0.0.1:6379> zrange scoreboard 0 1 WITHSCORES
1) "zhangsan"
2) "80"
3) "wangwu"
4) "94"
获得指定分数范围的元素
语法:ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]
127.0.0.1:6379> ZRANGEBYSCORE scoreboard 90 97 WITHSCORES
1) "wangwu"
2) "94"
3) "lisi"
4) "97"
127.0.0.1:6379> ZRANGEBYSCORE scoreboard 70 100 limit 1 2
1) "wangwu"
2) "lisi"
增加某个元素的分数
返回值是更改后的分数
语法:ZINCRBY key increment【要增加的分数】 member
127.0.0.1:6379> ZINCRBY scoreboard 4 lisi
"101“
获得集合中元素的数量
语法:ZCARD key
127.0.0.1:6379> ZCARD scoreboard
(integer) 3
获得指定分数范围内的元素个数
语法:ZCOUNT key min max
127.0.0.1:6379> ZCOUNT scoreboard 80 90
(integer) 1
按照排名范围删除元素
语法:ZREMRANGEBYRANK key start stop
127.0.0.1:6379> ZREMRANGEBYRANK scoreboard 0 1
(integer) 2
127.0.0.1:6379> ZRANGE scoreboard 0 -1
1) "lisi"
按照分数范围删除元素
语法:ZREMRANGEBYSCORE key min max
127.0.0.1:6379> zadd scoreboard 84 zhangsan
(integer) 1
127.0.0.1:6379> ZREMRANGEBYSCORE scoreboard 80 100
(integer) 1
获取元素的排名
# 从小到大
语法:ZRANK key member
127.0.0.1:6379> ZRANK scoreboard lisi
(integer) 0
##############################
# 从大到小
语法:ZREVRANK key member
127.0.0.1:6379> ZREVRANK scoreboard zhangsan
(integer) 1
五、Redis中的事务
1、概述
事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行。
2、使用
MULTI 命令用于开启一个事务,它总是返回 OK 。 MULTI 执行之后, 客户端可以继续向服务器发送任意多条命令,这些命令不会立即被执行, 而是被放到一个队列中, 当 EXEC命令被调用时, 所有队列中的命令才会被执行。另一方面, 通过调用 DISCARD , 客户端可以清空事务队列, 并放弃执行事务。
Redis事务的执行过程:
开启事务(multi)
命令入队(…)
执行事务(exec)
正常事务流程
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> get k3
QUEUED
127.0.0.1:6379> EXEC
1) OK
2) OK
3) OK
4) "v3"
127.0.0.1:6379>
放弃事务
如果放弃事务则所有指令不会执行
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set k4 v4
QUEUED
127.0.0.1:6379> set k5 v5
QUEUED
127.0.0.1:6379> get k5
QUEUED
127.0.0.1:6379> DISCARD
OK
127.0.0.1:6379> get k4
(nil)
3、事务中异常
如果在执行事务的过程中 指令出现了错误 会发生什么?
编译型异常(代码有问题! 命令有错!) ,事务中所有的命令都不会被执行
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set k5 v5
QUEUED
127.0.0.1:6379> set k6 v6
QUEUED
127.0.0.1:6379> getset k7
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379> get k5
QUEUED
127.0.0.1:6379> exec
(error) EXECABORT Transaction discarded because of previous errors.#会报异常
127.0.0.1:6379>
运行时异常, 如果事务队列中存在语法性,那么执行命令的时候,其他命令是可以正常执行 的,错误命令抛出异常!
127.0.0.1:6379> FLUSHDB
OK
127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> INCR k1 #语法没错 运行会出错
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> get k2
QUEUED
127.0.0.1:6379> EXEC
1) (error) ERR value is not an integer or out of range
2) OK
3) "v2"
4、watch监控
1 乐观锁、悲观锁
悲观锁
很悲观,认为什么时候都会出问题,无论做什么都会加锁!
传统数据库中常常体现为 表锁、行锁
乐观锁
很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,
乐观锁策略:提交版本必须大于记录当前版本才能执行更新
2 watch使用
使用watch监控某个内容, 如果期间没有发生别人的改动那么事务可以正常执行成功
127.0.0.1:6379> set money 100
OK127.0.0.1:6379> set moneyout 0
OK
127.0.0.1:6379> WATCH money # 监控money
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> DECRBY money 10
QUEUED
127.0.0.1:6379> INCRBY moneyout 10
QUEUED
127.0.0.1:6379> EXEC #如果在事务执行更新的时候原money值没有被改动则可以正常执行
1) (integer) 90
2) (integer) 10
3 watch乐观锁发生
可以使用多个客户端来模拟去修改被watch的key从而查看事务能否被正常执行
# 客户端1 在exec执行前的操作
127.0.0.1:6379> FLUSHDB
OK
127.0.0.1:6379> set money 100
OK
127.0.0.1:6379> set moneyout 0
OK
127.0.0.1:6379> WATCH money
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> DECRBY money 20
QUEUED
127.0.0.1:6379> INCRBY moneyout 20
QUEUED
###################
#在执行exec之前 切换到客户端2 修改money的值
127.0.0.1:6379> get money
"100"
127.0.0.1:6379> set money 500
OK
############# 切换到客户端1 执行exec操作
127.0.0.1:6379> exec
(nil) # 在这里会发现执行失败
## 想要成功执行需要重新监控最新的money数据, 先解锁再监控
127.0.0.1:6379> UNWATCH
OK127.0.0.1:6379> WATCH money
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> DECRBY money 20
QUEUED
127.0.0.1:6379> INCRBY moneyout 20
QUEUED
127.0.0.1:6379> EXEC
1) (integer) 480
2) (integer) 20
127.0.0.1:6379>
4 总结
- 单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
- 没有隔离级别的概念:队列中的命令没有提交之前都不会实际的被执行
- 不保证原子性:redis同一个事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚
六、Redis的配置文件
关于redis启动的时候相关配置
单位
包含
可以加载别的配置文件进来
网络模块
配置了相关的网络限制, 端口,以及保护模式
bind 127.0.0.1 # 如果需要允许被连接可以设置为 0.0.0.0
# 注意:如果开放连接则需要 设置密码并且关闭保护模式
protected-mode yes # 保护模式
port 6379 #端口
连接超时
可以设置连接的超时时间, 如果超过这个时间则自动断开连接
相关通用配置
daemonize yes # 是否开启守护进程(后台启动)
pidfile /var/run/redis_6379.pid # 进程文件的配置
# Specify the server verbosity level.
# This can be one of:
# debug (a lot of information, useful for development/testing)
# verbose (many rarely useful info, but not a mess like the debug level)
# notice (moderately verbose, what you want in production probably)
# warning (only very important / critical messages are logged)
loglevel notice # 关于日志级别的设置databases 16 # 默认的数据库个数
always-show-logo yes # 服务器在启动的时候是否显示图标
快照
save 900 1 每隔900秒有1次增删改则生成rdb文件
save 300 10 每隔300秒有10次增删改则生成rdb文件
save 60 10000 每隔60秒有10000次增删改则生成rdb文件
stop-writes-on-bgsave-error yes # 持久化出错是否继续工作
rdbcompression yes # 是否压缩rdb文件
rdbchecksum yes #检查rdb文件
dbfilename dump.rdb # rdb文件自动生成的, 文件名
dir ./ #rdb文件自动生成的位置
安全
redis默认是没有密码的, 实际上是可以设置密码的
127.0.0.1:6379> CONFIG GET requirepass
1) "requirepass"
2) ""
127.0.0.1:6379> CONFIG SET requirepass "123456"
OK
127.0.0.1:6379> CONFIG GET requirepass
(error) NOAUTH Authentication required.
127.0.0.1:6379> AUTH 123456
OK
127.0.0.1:6379> CONFIG GET requirepass
1) "requirepass"
2) "123456"
127.0.0.1:6379> CONFIG SET requirepass ""
OK
127.0.0.1:6379>
限制
maxclients 10000 # 设置能连接上redis的大客户端的数量
maxmemory <bytes> # redis 配置大的内存容量
maxmemory-policy noeviction # 内存到达上限之后的处理策略
1、volatile-lru:只对设置了过期时间的key进行LRU(默认值)
2、allkeys-lru : 删除lru算法的key
3、volatile-random:随机删除即将过期key
4、allkeys-random:随机删除
5、volatile-ttl : 删除即将过期的
6、noeviction : 永不过期,返回错误
AOF
除了rdb的另一种持久化方式
appendonly no # 默认是不开启aof模式的,默认是使用rdb方式持久化的,在大部分所有的情况下, rdb完全够用!
appendfilename "appendonly.aof" # 持久化的文件的名字
# appendfsync always # 每次修改都会 sync。消耗性能
appendfsync everysec # 每秒执行一次 sync,可能会丢失这1s的数据!
# appendfsync no # 不执行 sync,这个时候操作系统自己同步数据,速度快
七、Redis持久化
1、RDB持久化
RDB方式的持久化是通过快照(snapshotting)完成的,当符合一定条件时Redis会自动将内存中的数据进行快照并持久化到硬盘。
RDB是Redis默认采用的持久化方式。
具体的执行流程:
Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何IO操作的。 这就确保了极高的性能。如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那 RDB方式要比AOF方式更加的高效。RDB的缺点是后一次持久化后的数据可能丢失。我们默认的就是 RDB,一般情况下不需要修改这个配置!
1 RDB配置文件
持久化条件配置
save 开头的一行就是持久化配置,可以配置多个条件(每行配置一个条件),每个条件之
save 900 1
save 300 10
save 60 10000
配置快照文件目录
配置dir指定rdb快照文件的位置
# Note that you must specify a directory here, not a file name.
dir ./ #当前运行目录
配置快照文件的名称
设置dbfifilename指定rdb快照文件的名称
# The filename where to dump the DB
dbfilename dump.rdb
Redis启动后会读取RDB快照文件,将数据从硬盘载入到内存。根据数据量大小与结构和服务器性能不同,这个时间也不同。通常将记录一千万个字符串类型键、大小为1GB的快照文件载入到内存中需要花费20~30秒钟。
只需要将rdb文件放在我们redis启动目录就可以,redis启动的时候会自动检查dump.rdb 恢复其中的数据
2 触发情况与优缺点
触发情况
- save的规则满足的情况下,会自动触发rdb规则
- 执行 flflushall 命令,也会触发我们的rdb规则!
- 退出redis,也会产生 rdb 文件!
优点
- 适合大规模的数据恢复
- 对数据的完整性要不高!
缺点
- 需要一定的时间间隔进程操作!如果redis意外宕机了,这个后一次修改数据就没有的了!
- fork进程的时候,会占用一定的内容空间!
如果数据很重要以至于无法承受任何损失,则可以考虑使用AOF方式进行持久化
2、AOF(Append Only File)
默认情况下Redis没有开启AOF(append only fifile)方式的持久化
以日志的形式来记录每个写操作,将Redis执行过的所有指令记录下来(读操作不记录),只许追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis重启的话就根据日志文件 的内容将写指令从前到后执行一次以完成数据的恢复工作
Aof保存的appendonly.aof文件
1 配置文件
可以通过修改redis.conf配置文件中的appendonly参数开启
appendonly yes
#开启AOF持久化后每执行一条会更改Redis中的数据的命令,Redis就会将该命令写入硬盘中的AOF文件。
#AOF文件的保存位置和RDB文件的位置相同,都是通过dir参数设置的
dir ./
#默认的文件名是appendonly.aof,可以通过appendfilename参数修改
appendfilename appendonly.aof
如果这个 aof 文件有错位,这时候 redis 是启动不起来的吗,我们需要修复这个aof文件
redis 给我们提供了一个工具 redis-check-aof --fifix
语法
redis-check-aof --fix aof文件位置
2 优缺点及规则
aof 默认就是文件的无限追加,文件会越来越大!
如果 aof 文件大于 64m,太大了! fork一个新的进程来将我们的文件进行重写!
特点
# appendfsync always
appendfsync everysec
# appendfsync no
- 每一次修改都同步,文件的完整会更加好!
- 每秒同步一次,可能会丢失一秒的数据
- 从不同步,效率高的!
缺点
- 相对于数据文件来说,aof远远大于 rdb,修复的速度也比 rdb慢!
- Aof 运行效率也要比 rdb 慢,所以我们redis默认的配置就是rdb持久化
补充点
- RDB 持久化方式能够在指定的时间间隔内对你的数据进行快照存储
- AOF 持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始 的数据,AOF命令以Redis 协议追加保存每次写的操作到文件末尾,Redis还能对AOF文件进行后台重 写,使得AOF文件的体积不至于过大
- 只做缓存,如果你只希望你的数据在服务器运行的时候存在,你也可以不使用任何持久化
- 同时开启两种持久化
- 在这种情况下,当redis重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF 文件保存的数据集要比RDB文件保存的数据集要完整。
- RDB 的数据不实时,同时使用两者时服务器重启也只会找AOF文件,那要不要只使用AOF呢?作者建议不要,因为RDB更适合用于备份数据库(AOF在不断变化不好备份),快速重启,而且不会有 AOF可能潜在的Bug,留着作为一个万一的手段。
- 性能建议
- 因为RDB文件只用作后备用途,建议只在Slave上持久化RDB文件,而且只要15分钟备份一次就够 了,只保留 save 900 1 这条规则
- 如果Enable AOF ,好处是在恶劣情况下也只会丢失不超过两秒数据,启动脚本较简单只load自 己的AOF文件就可以了,代价一是带来了持续的IO,二是AOF rewrite 的后将 rewrite 过程中产 生的新数据写到新文件造成的阻塞几乎是不可避免的。只要硬盘许可,应该尽量减少AOF rewrite 的频率,AOF重写的基础大小默认值64M太小了,可以设到5G以上,默认超过原大小100%大小重 写可以改到适当的数值。
- 如果不Enable AOF ,仅靠 Master-Slave Repllcation 实现高可用性也可以,能省掉一大笔IO,也 减少了rewrite时带来的系统波动。代价是如果Master/Slave 同时倒掉,会丢失十几分钟的数据, 启动脚本也要比较两个 Master/Slave 中的 RDB文件,载入较新的那个,微博就是这种架构。
八、Redis发布/订阅
1、概述
进程间的一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。
比如微信公共号、微博关注
Redis 客户端可以订阅任意数量的频道。
下图展示了频道 channel1 , 以及订阅这个频道的三个客户端 —— client2 、 client5 和 client1 之间的关系:
当有新消息通过 PUBLISH 命令发送给频道 channel1 时, 这个消息就会被发送给订阅它的三个客户端:
2、命令
3、案例
订阅与发布案例分为两个端来进行展示, 一个是订阅端一个是发布端
订阅端
127.0.0.1:6379> SUBSCRIBE demo1
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "demo1"
3) (integer) 1
1) "message" #信息
2) "demo1" #频道
3) "hello " #内容
发送端
127.0.0.1:6379> PUBLISH demo1 "hello "
(integer) 1
实现过程
通过 SUBSCRIBE 命令订阅某频道后,redis-server 里维护了一个字典,字典的键就是一个个 频道!, 而字典的值则是一个链表,链表中保存了所有订阅这个 channel 的客户端。SUBSCRIBE 命令的关键, 就是将客户端添加到给定channel 的订阅链表中
通过 PUBLISH 命令向订阅者发送消息,redis-server 会使用给定的频道作为键,在它所维护的 channel 字典中查找记录了订阅这个频道的所有客户端的链表,遍历这个链表,将消息发布给所有订阅者
使用场景
- 聊天室
- 订阅相关系统
- 实时消息
九、主从复制
1、概述
主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点 (master/leader),后者称为从节点(slave/follower);数据的复制是单向的,只能由主节点到从节点。Master以写为主,Slave以读为主。
默认情况下,每台Redis服务器都是主节点;
且一个主节点可以有多个从节点(或没有从节点),但一个从节点只能有一个主节点。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-C5uLmUfu-1621732220300)(Redis.assets/image-20210521150136903.png)]
主从复制,读写分离! 80% 的情况下都是在进行读操作!减缓服务器的压力!架构中经常使用! 一主 二从!
2、作用
- 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。
- 故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复;实际上是一种服务的冗余。
- 负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务(即写Redis数据时应用连接主节点,读Redis数据时应用连接从节点),分担服务器负载;尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量。
- 高可用基石:除了上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是Redis高可用的基础。
注意:
一般来说,要将Redis运用于工程项目中,只使用一台Redis是万万不能的(宕机),原因如下:
- 从结构上,单个Redis服务器会发生单点故障,并且一台服务器需要处理所有的请求负载,压力较 大;
- 从容量上,单个Redis服务器内存容量有限,就算一台Redis服务器内存容量为256G,也不能将所有 内存用作Redis存储内存,一般来说,单台Redis大使用内存不应该超过20G
3、配置
关于环境配置只要配置从库就行了, 因为redis自己本身是默认主库的
查看当前主机的信息
#指令:info replication
127.0.0.1:6379> info replication
# Replication
role:master # master表示主机
connected_slaves:0
master_replid:b294f44a9ecc4f711aa0884488acd11a554f8174
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
1 命令配置
第一步:先要将redis.conf文件进行复制,对几个内容进行修改防止冲突
- 端口
- pid名字
- log文件名
- dump.rdb名字
[root@admin ~]# cd /usr/local/redis-5.0.5/
[root@admin redis-5.0.5]# cp redis.conf redis6380.conf
[root@admin redis-5.0.5]# ls
bin redis6380.conf redis.conf
[root@admin redis-5.0.5]# cp redis.conf redis6381.conf
# 复制完配置文件后 分别修改相关内容
分别启动redis 加载相关的新配置
启动
[root@admin bin]# ./redis-server /usr/local/redis-5.0.5/redis6380.conf
[root@admin bin]# ps -ef | grep redis
root 3923 3912 0 Apr20 pts/1 00:00:00 ./redis-cli
root 5142 1 0 01:24 ? 00:00:00 ./redis-server 127.0.0.1:6379
root 5146 3314 0 01:24 pts/0 00:00:00 ./redis-cli
root 5202 1 0 01:38 ? 00:00:00 ./redis-server 127.0.0.1:6380
root 5207 5155 0 01:38 pts/3 00:00:00 grep redis
[root@admin bin]# ./redis-cli -p 6380
127.0.0.1:6380>
[root@admin ~]# cd /usr/local/redis-5.0.5/bin/
[root@admin bin]# ./redis-server /usr/local/redis-5.0.5/redis6381.conf
[root@admin bin]# ./redis-cli -p 6381
127.0.0.1:6381>
通过命令将80和81配置问从机
127.0.0.1:6380> SLAVEOF 127.0.0.1 6379
OK
127.0.0.1:6380> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6379
master_link_status:up
master_last_io_seconds_ago:3
master_sync_in_progress:0
slave_repl_offset:14
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:4a1fb677e51cab983856e01b2b1fe430a85d7278
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:14
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1repl_backlog_histlen:14
在主机中查看详情 发现会多了从机
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:2
slave0:ip=127.0.0.1,port=6380,state=online,offset=84,lag=1
slave1:ip=127.0.0.1,port=6381,state=online,offset=84,lag=0
master_replid:4a1fb677e51cab983856e01b2b1fe430a85d7278
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:84
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:84
注意
- 主机可以写,从机不能写只能读!主机中的所有信息和数据,都会自动被从机保存!
- 从机只能读取内容!
- 主机断开连接,从机依旧连接到主机的,但是没有写操作,这个时候,主机如果回来了,从机依 旧可以直接获取到主机写的信息!
命令行配置弊端
如果是使用命令行,来配置的主从,这个时候如果从机重启了,就会变回主机!但是只要变为从机,立马就会从主机中获取值!
命令行的弊端可以使用配置文件来解决 (只要redis启动的时候加载的是这个配置文件就可以生效)
变成从机从主机中获取值的原因:
Slave 启动成功连接到 master 后会发送一个sync同步命令
Master 接到命令,启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令,在后台进程执行 完毕之后,master将传送整个数据文件到slave,并完成一次完全同步。
- 全量复制:而slave服务在接收到数据库文件数据后,将其存盘并加载到内存中。
- 增量复制:Master 继续将新的所有收集到的修改命令依次传给slave,完成同步
- 但是只要是重新连接master,一次完全同步(全量复制)将被自动执行! 我们的数据一定可以在从机中看到!
用文件配置去修改主从复制文件中一开始要改的几个内容和上面命令配置是相同的
额外修改内容如下
手动配置 主机ip,主机端口、如果有密码要设置密码
启动测试
[root@admin bin]# ./redis-server /usr/local/redis-5.0.5/redis6382.conf
[root@admin bin]# ./redis-cli -p 6382
127.0.0.1:6382> INFO replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6379
master_link_status:up
master_last_io_seconds_ago:3
master_sync_in_progress:0
slave_repl_offset:966
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:4a1fb677e51cab983856e01b2b1fe430a85d7278
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:966
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:939
repl_backlog_histlen:28
十、哨兵模式
1、出现前景
除了上述的主从复制的
上一个Slave可以是下一个slave的Master,Slave同样可以接收其他
slaves的连接和同步请求,那么该slave作为了链条中下一个的master,可以有效减轻master的写压力
如何实现?
注意:想要方便的进行模式切换可以先把配置文件中的内容先注释掉
实现过程
首先要启动三个不同端口的redis客户端
第一台作为主机、第二台是第一台的从机,但是是第三台的主机,第三台是第二台的从机
# 第二台为第一台从机
127.0.0.1:6380> SLAVEOF 127.0.0.1 6379
OK
127.0.0.1:6380> info replication
# 第三台为第二台从机
127.0.0.1:6381> SLAVEOF 127.0.0.1 6380
OK
127.0.0.1:6381> INFO replication
情景
如果主机断开,那么第二台和第三台可以主机选举主机(手动)
127.0.0.1:6380> SLAVEOF no one
OK
127.0.0.1:6380> info replication
# Replication
role:master
connected_slaves:1
slave0:ip=127.0.0.1,port=6381,state=online,offset=854,lag=0
master_replid:0b6dbb0d11816f16c1649974dbc6bfad50bc204a
master_replid2:4c9971a5c31f16590a0a2105830421d3b4035d3d
master_repl_offset:854second_repl_offset:855
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:854
127.0.0.1:6380>
手动设置了新的主机之后再,之前的主机再重新连接也没有用了
由此诞生了哨兵模式(自动选举主机)
2、概念
主从切换技术的方法是:当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工干预,费事费力,还会造成一段时间内服务不可用。这不是一种推荐的方式,更多时候,我们优先考虑哨兵模式。Redis从2.8开始正式提供了Sentinel(哨兵) 架构来解决这个问题。
哨兵,能够后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库。 哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它会独 立运行。其原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例
单哨兵
哨兵作用:
- 通过发送命令,让Redis服务器返回监控其运行状态,包括主服务器和从服务器。
- 当哨兵监测到master宕机,会自动将slave切换成master,然后通过发布订阅模式通知其他的从服 务器,修改配置文件,让它们切换主机。
问题:
然而一个哨兵进程对Redis服务器进行监控,可能会出现问题,为此,我们可以使用多个哨兵进行监控。 各个哨兵之间还会进行监控,这样就形成了多哨兵模式。
多哨兵
假设主服务器宕机,哨兵1先检测到这个结果,系统并不会马上进行failover过程,仅仅是哨兵1主观的认 为主服务器不可用,这个现象成为主观下线。当后面的哨兵也检测到主服务器不可用,并且数量达到一 定值时,那么哨兵之间就会进行一次投票,投票的结果由一个哨兵发起,进行failover[故障转移]操作。 切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机,这个过程称为 客观下线。
3、实现哨兵模式
首先自己编写一个比较简单的哨兵配置文件
# 编写文件
[root@admin redis-5.0.5]# vim sentinel.conf
# 简单的配置文件内容
# sentinel monitor 被监控的名称 host port 1
sentinel monitor redisdemo 127.0.0.1 6379 1
进入启动目录,启动哨兵
[root@admin redis-5.0.5]# cd bin/
[root@admin bin]# ./redis-sentinel /usr/local/redis-5.0.5/sentinel.conf
断开主机,这时候哨兵会自动心跳检测,如果出现问题则会自动推选新的主机
等断开的主机重新连接上之后,不会变成老大,而是变成新推选的主机的从机
详细配置文件
复制代码
# Example sentinel.conf
# 哨兵sentinel实例运行的端口 默认26379
port 26379
# 哨兵sentinel的工作目录
dir /tmp
# 哨兵sentinel监控的redis主节点的 ip port
# master-name 可以自己命名的主节点名字 只能由字母A-z、数字0-9 、这三个字符".-_"组成。
# quorum 当这些quorum个数sentinel哨兵认为master主节点失联 那么这时 客观上认为主节点失联了
# sentinel monitor <master-name> <ip> <redis-port> <quorum>
sentinel monitor mymaster 127.0.0.1 6379 2
# 当在Redis实例中开启了requirepass foobared 授权密码 这样所有连接Redis实例的客户端都要提供密码
# 设置哨兵sentinel 连接主从的密码 注意必须为主从设置一样的验证密码
# sentinel auth-pass <master-name> <password>
sentinel auth-pass mymaster MySUPER--secret-0123passw0rd
# 指定多少毫秒之后 主节点没有应答哨兵sentinel 此时 哨兵主观上认为主节点下线 默认30秒
# sentinel down-after-milliseconds <master-name> <milliseconds> sentinel down-after-milliseconds mymaster 30000
# 这个配置项指定了在发生failover主备切换时最多可以有多少个slave同时对新的master进行 同步,
#这个数字越小,完成failover所需的时间就越长,
#但是如果这个数字越大,就意味着越 多的slave因为replication而不可用。
#可以通过将这个值设为 1 来保证每次只有一个slave 处于不能处理命令请求的状态。
# sentinel parallel-syncs <master-name> <numslaves>
sentinel parallel-syncs mymaster 1
# 故障转移的超时时间 failover-timeout 可以用在以下这些方面:
#4.当进行failover时,配置所有slaves指向新的master所需的最大时间。不过,即使过了这个超时,slaves依然会被正确配置为指向master,但是就不按parallel-syncs所配置的规则来了
# 默认三分钟
# sentinel failover-timeout <master-name> <milliseconds>
sentinel failover-timeout mymaster 180000
# SCRIPTS EXECUTION
#配置当某一事件发生时所需要执行的脚本,可以通过脚本来通知管理员,例如当系统运行不正常时发邮件通知相关人员。
#对于脚本的运行结果有以下规则:
#若脚本执行后返回1,那么该脚本稍后将会被再次执行,重复次数目前默认为10
#若脚本执行后返回2,或者比2更高的一个返回值,脚本将不会重复执行。
#如果脚本在执行过程中由于收到系统中断信号被终止了,则同返回值为1时的行为相同。
#一个脚本的最大执行时间为60s,如果超过这个时间,脚本将会被一个SIGKILL信号终止,之后重新执行。
#通知型脚本:当sentinel有任何警告级别的事件发生时(比如说redis实例的主观失效和客观失效等等),将会去调用这个脚 本,
#这时这个脚本应该通过邮件,SMS等方式去通知系统管理员关于系统不正常运行的信息。调用该脚本时,将传给脚本两个参数,
#一个是事件的类型,
#一个是事件的描述。
#如果sentinel.conf配置文件中配置了这个脚本路径,那么必须保证这个脚本存在于这个路径,并且是可执行的,否则 sentinel无法正常启动成功。
#通知脚本
# sentinel notification-script <master-name> <script-path>
sentinel notification-script mymaster /var/redis/notify.sh
# 客户端重新配置主节点参数脚本
# 当一个master由于failover而发生改变时,这个脚本将会被调用,通知相关的客户端关于master地址已经发生改变的信 息。
# 以下参数将会在调用脚本时传给脚本:
# <master-name> <role> <state> <from-ip> <from-port> <to-ip> <to-port>
# 目前<state>总是“failover”,
# <role>是“leader”或者“observer”中的一个。
# 参数 from-ip, from-port, to-ip, to-port是用来和旧的master和新的master(即旧的slave)通信的
# 这个脚本应该是通用的,能被多次调用,不是针对性的。# sentinel client-reconfig-script <master-name> <script-path>
sentinel client-reconfig-script mymaster /var/redis/reconfig.sh
4、优缺点
- 优点
- 哨兵集群,基于主从复制模式,所有的主从配置优点,它全有
- 主从可以切换,故障可以转移,系统的可用性就会更好(高可用)
- 哨兵模式就是主从模式的升级,手动到自动,更加健壮!
- 缺点
- Redis 不好啊在线扩容的,集群容量一旦到达上限,在线扩容就十分麻烦!
- 实现哨兵模式的配置其实是很麻烦的,里面有很多选择!
十一、缓存穿透&雪崩
1、缓存穿透
1 概述
缓存穿透的概念很简单,用户想要查询一个数据,发现redis内存数据库没有,也就是缓存没有命中,于 是向持久层数据库查询。发现也没有,于是本次查询失败。当用户很多的时候,缓存都没有命中(秒 杀!),于是都去请求了持久层数据库。这会给持久层数据库造成很大的压力,这时候就相当于出现了 缓存穿透。
2 解决方案
布隆过滤器
对所有可能查询的参数以hash形式存储,在控制层先进行校验,不符合则丢弃,从而避免了对底层存储系统的查询压力;
缓存空对象
当存储层不命中后,即使返回的空对象也将其缓存起来,同时会设置一个过期时间,之后再访问这个数据将会从缓存中获取,保护了后端数据源;
缺点:
- 如果空值能够被缓存起来,这就意味着缓存需要更多的空间存储更多的键,因为这当中可能会有很多的空值的键
- 即使对空值设置了过期时间,还是会存在缓存层和存储层的数据会有一段时间窗口的不一致,这对于需要保持一致性的业务会有影响
2、缓存击穿
1 概述
这里需要注意和缓存击穿的区别,缓存击穿,是指一个key非常热点,在不停的扛着大并发,大并发集中 对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一 个屏障上凿开了一个洞。 当某个key在过期的瞬间,有大量的请求并发访问,这类数据一般是热点数据,由于缓存过期,会同时访 问数据库来查询新数据,并且回写缓存,会导使数据库瞬间压力过大。
2 解决方案
设置热点数据永不过期
从缓存层面来看,没有设置过期时间,所以不会出现热点 key 过期后产生的问题。
加互斥锁
分布式锁:使用分布式锁,保证对于每个key同时只有一个线程去查询后端服务,其他线程没有获得分布 式锁的权限,因此只需要等待即可。这种方式将高并发的压力转移到了分布式锁,因此对分布式锁的考 验很大。
3、缓存雪崩
1 概述
缓存雪崩,是指在某一个时间段,缓存集中过期失效。Redis宕机!
产生雪崩的原因之一,比如在写本文的时候,马上就要到双十二零点,很快就会迎来一波抢购,这波商 品时间比较集中的放入了缓存,假设缓存一个小时。那么到了凌晨一点钟的时候,这批商品的缓存就都 过期了。而对这批商品的访问查询,都落到了数据库上,对于数据库而言,就会产生周期性的压力波 峰。于是所有的请求都会达到存储层,存储层的调用量会暴增,造成存储层也会挂掉的情况。
2 解决方案
redis高可用
即使个别节点、个别机器、甚至是机房宕掉,依然可以提供服务,比如 Redis Sentinel 和 Redis Cluster 都实现了高可用。 其实就是搭建的集群。
限流降级
在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
数据预热
数据加热的含义就是在正式部署之前,我先把可能的数据先预先访问一遍,这样部分可能大量访问的数 据就会加载到缓存中。在即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让 缓存失效的时间点尽量均匀。
十二、Jedis
1、概述
什么是Jedis 是 Redis 官方推荐的 java连接开发工具! 使用Java 操作Redis 中间件!如果你要使用 java操作redis,那么一定要对Jedis 十分的熟悉!可以理解成是连接数据库需要JDBC一样
1 导入依赖
<dependencies>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.2.0</version>
</dependency>
</dependencies>
2 联通测试代码
import redis.clients.jedis.Jedis;
public class JedisTest {
public static void main(String[] args) {
// 关于Jedis的联通测试
Jedis jedis = new Jedis("127.0.0.1",6379);
System.out.println(jedis.ping());
}
}
3 事务代码
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;
public class JedisTransactionTest {
public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1", 6379);
//开启事务
Transaction multi = jedis.multi();
// 利用事务对象来进行正常的所有redis存储值的操作
try {
multi.set("k1", "v1");
multi.set("k1", "v1");
// 执行事务
multi.exec();
} catch (Exception e) {
// 如果发生异常则放弃
multi.discard();
e.printStackTrace();
} finally {
// 取值查看结果
System.out.println(jedis.get("k1"));
// 关闭连接
jedis.close();
}
}
}
大并发就穿破缓存,直接请求数据库,就像在一 个屏障上凿开了一个洞。 当某个key在过期的瞬间,有大量的请求并发访问,这类数据一般是热点数据,由于缓存过期,会同时访 问数据库来查询新数据,并且回写缓存,会导使数据库瞬间压力过大。