视频地址
知识点概述
nosql 讲解
nosql 数据模型
nosql 四大分类
CAP
BASE
Redis 入门
五大基本数据类型
String
List
Set
Hash
Zset
三种特殊数据类型
geo
hyperloglog
bitmap
Redis 配置详解
Redis 持久化
RDB
AOF
Redis 事务操作
Redis 实现订阅发布
Redis 主从复制
Redis 哨兵模式
缓存穿透及解决方案
缓存击穿及解决方案
缓存雪崩及解决方案
基础API值Jedis详解
SpringBoot集成Redis操作
Redis的实践分析
- 单机MySQL的年代!
90年代, 一个基本的网站访问量一般不会太大,单个数据库完全足够!
那个时候,更多的去使用静态网站 Html ~ 服务器根本没有太大压力!
整个网站的瓶颈是什么?
一个网站出现以上的三种情况之一,那么就必须要晋级 !
- Memcache(缓存) + MySQL + 垂直拆分( 读写分离 )
网站80%的情况都是在读数据,每次都要去查询数据库的话就会十分的麻烦 ! 所以说我们希望减轻数据的压力,我们就可以使用缓存来保证效率!
发展过程 : 优化数据结构和索引 —> 文件缓存( IO ) —> Memcahce(当时最热门的技术! )
- 分库分表 + 水平拆分 + Mysql集群
技术和业务在发展的同时,对人的要求也越来越高 !
本质 : 数据库( 读 , 写)
MYSQL的引擎 :
MyISAM : 使用表锁, 十分影响效率 ! 高并发下就会出现严重的锁问题
Innodb: 行锁
不同的业务使用不同的表
慢慢的就开始使用分库分表来解决写的压力 ! MySQL 在那个年代推出了表分区! 并没有多少公司使用
MySQL 的集群 满足了那个年代的需求 !
- 如今最近的年代
技术爆炸 : 2010 ~ 2020 十年之间,世界已经发生了翻天覆地的变化; (定位, 也是一种数据, 音乐, 热榜)
MySQL 等关系型数据库就不够用了! 数据量很多,变化很快 ~ !
MySQL 有时使用它来存储一些比较大的文件,博客,图片 ! 数据库表很大,效率就低了 ! 如果有一种 数据库来专门处理这种数据,MySQL压力就变得十分小( 研究如何处理这些问题 ! ) 大数据的IO压力下,表几乎没法更大 !
目前一个基本的互联网项目 !
为什么要用NoSQL !
用户的个人信息,社交网络, 地理位置,用户自己产生的数据,用户日志等等爆发式增长 !
这个时候我们就需要使用NoSQL数据库的,NoSQL可以很好的处理以上的情况 !
NoSQL
NoSQL = not only SQL ( 不仅仅是SQL )
关系型数据库: 表格, 行, 列 ( POI )
泛指非关系型数据库的, 随着web2.0互联网的诞生 ! 传统的关系型数据库很难对付web 2.0时代 ! 尤其是超大规模的高并发的社区 ! 暴露出来很多难以克服的问题,NoSQL在当今大数据环境下十分迅速,Redis是发展最快的,而且是我们当下必须要掌握的一个技术 !
很多的数据类型用户的个人信息,社交网络,地理位置. 这些数据类型的存储不需要一个固定的格式 ! 不需要多余的操作就可以横向扩展的 !
NoSQL的特点
方便扩展(数据之间没有关系, 很好扩展 ! )
大数据量高性能( Redis 一秒写8万次, 读取11万, NoSQL的缓存记录级的,是一种细粒度的缓存,性能会比较高 ! )
数据类型是多样型的 ! ( 不需要事先设计数据库 ! 随取随用! 如果是数据量十分大的表,很多人就无法设计了)
传统的DBMS 和 NoSQL的区别
传统的 RDBMS
- 结构化组织
- SQL
- 数据和关系都存在单独的表中
- 操作,数据定义语言
_ 基础的事务
NoSQL
- 不仅仅是数据
- 没有固定的查询语言
- 键值对存储, 列存储,文档存储,图形数据库( 社交关系 )
- 最终一致性
- CAP定理 和 BASE ( 异地多活 ) 初级架构师
- 高性能, 高可用, 高可扩
了解: 3V + 3高
大数据地 3V 和 3高
大数据时代的3V: 主要是描述问题的
海量Volume
多样variety
大数据时代的3高: 主要是对程序的要求
真正在公司中的实践: NoSQL + RDBMS 一起使用才是最强的
技术没有高低之分,就看你如何去使用 !
kv键值对 :
文档型数据库(bson 格式和 json 一样 ) :
列存储数据库
图关系数据库
四者对比
Redis是什么?
redis,远程字典服务,是一个开源的使用 C语言编写的,支持网络,可基于内存亦可基于可持久化的日志型,key-value数据库,并提供多种语言的API,是当下最热门的NoSQL技术之一! 也被人们称之为结构化数据库 !
redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。
Redis的作用?
特性
如何进行学习
官方文档
redis默认有16个数据库
默认使用第0个数据库
使用select index 来切换数据库
127.0.0.1:6379[1]> keys * #查看当前数据库所有的key
(empty list or set)
清除当前数据库 flushdb
127.0.0.1:6379> flushdb
OK
清除全部数据库的内容
127.0.0.1:6379> flushall
OK
存值和取值的常用操作
127.0.0.1:6379> set name czp //设置 k=name v=czp 后面可以追加存放的数据库index
OK
127.0.0.1:6379> keys *
1) "name"
127.0.0.1:6379>
[root@czp ~]# redis-cli -p 6379
127.0.0.1:6379> get name //获取 k为name的值
"czp"
127.0.0.1:6379>
判断一个key是否存在
127.0.0.1:6379> exists name
(integer) 1
将一个值存入其他数据库
127.0.0.1:6379> keys *
1) "name"
127.0.0.1:6379> move name 0 #如果指定的数据库中的key存在就会报以下异常
(error) ERR source and destination objects are the same
127.0.0.1:6379> move name 1 #删除一个存储在1号数据库中的key
(integer) 1
127.0.0.1:6379>
为一个值设置过期时间
# 在设置值时为其指定过期时间
127.0.0.1:6379> set name czp ex 30
OK
127.0.0.1:6379> ttl name
(integer) 26
127.0.0.1:6379>
# 在设置好值之后使用expire为其指定过期时间
127.0.0.1:6379> set name czp
OK
127.0.0.1:6379> expire name 30
(integer) 1
127.0.0.1:6379>
获取一个值的过期时间
127.0.0.1:6379> set name czp
OK
127.0.0.1:6379> ttl name
(integer) -1
127.0.0.1:6379> expire name 30
(integer) 1
127.0.0.1:6379> ttl name
(integer) 27
127.0.0.1:6379>
如何删除一个指定的key
使用expire命名将其赋值为-2
-1 不关闭服务永久存在
查看指定key的值的类型
127.0.0.1:6379> set name czp
OK
127.0.0.1:6379> set age 1
OK
127.0.0.1:6379> type name
string
127.0.0.1:6379> type age
string
Redis 是单线程的 !
明白Redis是很快的,官方表示,Redis是基于内存操作的,CPU不是Redis的性能瓶颈,Redis的瓶颈是根据机器的内存和网络带宽,既然可以使用单线程来实现,就使用了单线程
Redis是C语言写的, 完全不同比样是key-value的memecache差!
Redis为什么单线程这么快?
cpu>内存>硬盘的速度
核心 : redis是将所有的数据全部放在内存中的,所以使用单线程去操作的效率是最高的, 多线程(CPU上下文会切换 !),对于内存系统来说,如果没有上下文切换效率是最高的 ! 多次读写都是在一个CPU上的,在内存的情况下,这个就是最佳方案 !
127.0.0.1:6379> set k1 v1 #设置值
OK
127.0.0.1:6379> get k1 #获取值
"v1"
127.0.0.1:6379> keys * #获得所有的key
1) "name"
2) "k1"
3) "age"
127.0.0.1:6379> exists k1 #判断一个key是否存在
(integer) 1
127.0.0.1:6379> append k1 czp #向一个值后面追加一个值,若不存在相当于设置值
(integer) 5
127.0.0.1:6379> get k1
"v1czp"
127.0.0.1:6379> strlen k1 #获取一个字符串的长度
(integer) 5
127.0.0.1:6379>
**********************自增和自减****************************
127.0.0.1:6379> set views 0
OK
127.0.0.1:6379> incr views #自增1
(integer) 1
127.0.0.1:6379> incr views
(integer) 2
127.0.0.1:6379> incr views
(integer) 3
127.0.0.1:6379> clear
127.0.0.1:6379> decr views #自减1
(integer) 2
127.0.0.1:6379> decr views
(integer) 1
127.0.0.1:6379> incrby views 10 #自定义步长自增
(integer) 11
127.0.0.1:6379> incrby views 10
(integer) 21
127.0.0.1:6379> decrby views 10 #自定义步长自减
(integer) 11
127.0.0.1:6379> decrby views 10
(integer) 1
*************************************************************
#字符串范围 range
127.0.0.1:6379> set k1 helloworld #设置字符串
OK
127.0.0.1:6379> getrange k1 0 3 #获取制定范围的字符串【0,3】
"hell"
127.0.0.1:6379> getrange k1 0 -1 #获取所有的字符串 和 get key 一样
"helloworld"
**********************************************************
#替换制定范围的字符串 开始的位置
127.0.0.1:6379> setrange k1 1 xx
(integer) 10
127.0.0.1:6379> get k1
"hxxloworld"
***********************************************************
# serex (set with expire) #设置过期时间
# serex (set if not expire) # 不存在设置(在分布式锁中常常使用!)
127.0.0.1:6379> setex k1 30 hello #设置k1 30秒后过期
OK
127.0.0.1:6379> ttl k1
(integer) 23
127.0.0.1:6379> get k1
"hello"
127.0.0.1:6379> setnx myk1 redis #如果myKey 不存在,创建myKey
(integer) 1
127.0.0.1:6379> keys *
1) "myk1"
127.0.0.1:6379> setnx myk1 mongodb #如果mykey存在,创建失败!
(integer) 0
127.0.0.1:6379> get myk1
"redis"
*************************************************************
mset
mget
127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3 #批量设置多个值
OK
127.0.0.1:6379> keys * #获取所有值
1) "k1"
2) "k3"
3) "k2"
4) "myk1"
127.0.0.1:6379> msetnx k1 v1 k2 v1 #批量设置(类似事务的原子性,要么一起成功,要么一起失败!)
(integer) 0
127.0.0.1:6379> get k1
"v1"
#对象
set user: 1{
name:zhangsan,age:3} #设置一个User:1对象 值用json来保存一个对象
#这里的key是一个巧妙的复用
127.0.0.1:6379> mset user:1:name zhangsan user:1:age 2
OK
127.0.0.1:6379> mget user:1:name
1) "zhangsan"
127.0.0.1:6379> mget user:1:age
1) "2"
*************************************************************
getset
#先获取值在设置值
127.0.0.1:6379> getset db redis #如果不存在,则返回nil
(nil)
127.0.0.1:6379> get db
"redis"
127.0.0.1:6379> getset db mongodb #如果存在,获取原来的值,并设置新的值
"mongobd"
String类似的使用场景 :value除了是我们的字符串还可以是我们的数字 !
基本的数据类型,列表
在redis里面,我们可以将list充当成栈,队列,阻塞队列!
所有的list命令都是用l来开头的
*************************************************************
127.0.0.1:6379> lpush list noe #将一个值或者多个值,插入到列表头部 (左边插入)
(integer) 1
127.0.0.1:6379> lpush list two
(integer) 2
127.0.0.1:6379> lpush list three
(integer) 3
127.0.0.1:6379> lrange list 0 -1 #获取list中的值 也可以通过一个具体的区间获取指定的值
1) "three"
2) "two"
3) "noe"
127.0.0.1:6379> lrange list 0 0
1) "three"
*************************************************************
127.0.0.1:6379> rpush list fore #将一个值或者多个值,插入到列表尾部 (右边插入)
(integer) 4
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "two"
3) "noe"
4) "fore"
*************************************************************
LPOP
RPOP
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "two"
3) "noe"
4) "fore"
127.0.0.1:6379> lpop list #从列表的开头弹出一个值
"three"
127.0.0.1:6379> rpop list #从列表的尾部弹出一个值
"fore"
127.0.0.1:6379> lrange list 0 -1
1) "two"
2) "noe"
*************************************************************
Lindex
127.0.0.1:6379> lindex list 1 # 通过list下标获取某一个值
"noe"
127.0.0.1:6379> lindex list 0
"two"
*************************************************************
Llen #获取指定的list中有多少个元素
127.0.0.1:6379> lpush list 0
(integer) 1
127.0.0.1:6379> lpush list 1
(integer) 2
127.0.0.1:6379> lpush list 2
(integer) 3
127.0.0.1:6379> llen list
(integer) 3
*************************************************************
移除指定的值!
lrem
127.0.0.1:6379> lrem list 1 1 #从list中移除值 1 :移除一个 1 :指定的值
(integer) 1
*************************************************************
trim 修剪
127.0.0.1:6379> lpush list two three fore five
(integer) 5
127.0.0.1:6379> lrange list 0 -1
1) "five"
2) "fore"
3) "three"
4) "two"
5) "one"
127.0.0.1:6379> ltrim list 1 2 #裁剪list 只保留下标为1和2的值
OK
127.0.0.1:6379> lrange list 0 -1
1) "fore"
2) "three"
*************************************************************
rpoplpush # 移除列表的最后一个元素并将其加入其他列表
127.0.0.1:6379> lrange list 0 -1
1) "fore"
2) "three"
127.0.0.1:6379> rpoplpush list mylist #将list的最后一个元素转移到mylist的开头
"three"
127.0.0.1:6379> lrange list 0 -1
1) "fore"
127.0.0.1:6379> lrange mylist 0 -1
1) "three"
*************************************************************
lset 为指定的list(下标)设置值 该list必须存在不存在则报错
127.0.0.1:6379> exists list
(integer) 0
127.0.0.1:6379> lset list 0 item
(error) ERR no such key
127.0.0.1:6379> lpush list onr
(integer) 1
127.0.0.1:6379> lset list 0 one
OK
127.0.0.1:6379> lrange list 0 -1
1) "one"
*************************************************************
linsert 向指定的位置前后插入值
127.0.0.1:6379> rpush list two
(integer) 2
127.0.0.1:6379> linsert list before one 1 # 向list的one之前插入1
(integer) 3
127.0.0.1:6379> linsert list after two 2 # 向list的two之后插入2
(integer) 4
127.0.0.1:6379> lrange list 0 -1
1) "1"
2) "one"
3) "two"
4) "2"
小结
消息排队 ! 消息队列 ! Lpush (从头进入)Rpop (从尾部取出), 栈 Lpush Lpop (从头部进,从头部出) !
set : 无序不重复,随机
127.0.0.1:6379> sadd set 0 #向集合中设置值
(integer) 1
127.0.0.1:6379> smembers set #获取集合中的所有值
1) "0"
127.0.0.1:6379> sismember set 0 #判断集合中是否存在值
(integer) 1
127.0.0.1:6379> scard set #获取set中元素的个数
(integer) 1
127.0.0.1:6379> srem set 0 #删除set中的元素
(integer) 1
127.0.0.1:6379> sadd set 0
(integer) 1
127.0.0.1:6379> sadd set 1
(integer) 1
127.0.0.1:6379> sadd set 2
(integer) 1
127.0.0.1:6379> sadd set 3
(integer) 1
127.0.0.1:6379> srandmember set #从set中随机抽出一个元素 后面可以添加选出元素的个数
"1"
127.0.0.1:6379> spop set #随机删除一些set集合中的元素
"1"
*************************************************************
127.0.0.1:6379> sadd set one
(integer) 1
127.0.0.1:6379> sadd set two
(integer) 1
127.0.0.1:6379> sadd set three
(integer) 1
127.0.0.1:6379> sadd set fore
(integer) 1
127.0.0.1:6379> smove set myset one #将set中指定的元素移动到指定集合
(integer) 1
127.0.0.1:6379> smembers set
1) "fore"
2) "two"
3) "three"
*************************************************************
微博,B站,共同关注!(并集)
数字集合类:
- 差集
- 交集
- 并集
127.0.0.1:6379> smembers key1
1) "d"
2) "c"
3) "b"
4) "a"
127.0.0.1:6379> smembers key2
1) "f"
2) "d"
3) "a"
4) "e"
127.0.0.1:6379> sdiff key1 key2 #查看key1-key2(差集)
1) "b"
2) "c"
127.0.0.1:6379> sinter key1 key2 #查看key1 与 key2 的交集
1) "d"
2) "a"
127.0.0.1:6379> sunion key1 key2 # 查看key1 与 key2 的并集
1) "f"
2) "b"
3) "a"
4) "d"
5) "c"
6) "e"
微博,a用户将所有关注的人放在一个set集合中! 将他的粉丝也放在一个集合中 !
共同关注, 共同爱好, 二度好友,推荐好友 ! (六度分割理论)
Map集合, key-value 本质和Spring类型没有什么区别,还是一个简单的key-value
127.0.0.1:6379> hset map name czp # 向map中存放值
(integer) 1
127.0.0.1:6379> hget map name # 从map中获取值
"czp"
127.0.0.1
127.0.0.1:6379> hgetall map #获取指定map中所有的值
1) "name"
2) "czp"
127.0.0.1:6379> hdel map name # 删除指定map中的值
(integer) 1
*************************************************************
127.0.0.1:6379> hset map name czp
(integer) 1
127.0.0.1:6379> hset map age 20
(integer) 1
127.0.0.1:6379> hset map gender 1
(integer) 1
127.0.0.1:6379> hlen map # 获取map中所有得元素个数
(integer) 3
127.0.0.1:6379> hexists map name # 判断一个键值对在map中是否存在
(integer) 1
*************************************************************
# 只获取所有的key
127.0.0.1:6379> hkeys map
1) "name"
2) "age"
3) "gender"
# 只获取所有的value
127.0.0.1:6379> hvals map
1) "czp"
2) "20"
3) "1"
*************************************************************
incr incrby decr decrby 前加一样使用
hash的应用
- 存放变更的数据,经常变动的信息,hash更适合于对象的存储,string更加适合字符串存储!
在set的基础上,增加一个值,set k1 v1 zset k1 score1 v1
127.0.0.1:6379> zadd set 1 one #向有序集合中添加一个值
(integer) 1
127.0.0.1:6379> zadd set 2 two 3 three 4 fore #向有序集合中添加多个值
(integer) 3
127.0.0.1:6379> zrange set 0 -1 # 查看有序集合中的值
1) "one"
2) "two"
3) "three"
4) "fore"
*************************************************************
对zset集合进行排序输出
127.0.0.1:6379> zadd salary 100 czp
(integer) 1
127.0.0.1:6379> zadd salary 1002 xuenai
(integer) 1
127.0.0.1:6379> zadd salary 3000 tuanzi1
(integer) 1
127.0.0.1:6379> zrangebyscore salary 0 -1
(empty list or set)
127.0.0.1:6379> zrangebyscore salary -inf +inf
1) "czp"
2) "xuenai"
3) "tuanzi1"
*************************************************************
# 带权值将有序集合排序输出(从小到大)
127.0.0.1:6379> zrangebyscore salary -inf +inf withscores
1) "czp"
2) "100"
3) "xuenai"
4) "1002"
5) "tuanzi1"
6) "3000"
# 带权值将有序集合排序输出(从大到小)
127.0.0.1:6379> zrevrange salary 0 -1
1) "tuanzi1"
2) "xuenai"
127.0.0.1:6379> zrevrange salary 0 -1 withscores
1) "tuanzi1"
2) "3000"
3) "xuenai"
4) "1002"
#移除有序集合中的元素
127.0.0.1:6379> zrange salary 0 -1
1) "czp"
2) "xuenai"
3) "tuanzi1"
127.0.0.1:6379> zrem salary czp
(integer) 1
127.0.0.1:6379> zrange salary 0 -1
1) "xuenai"
2) "tuanzi1"
127.0.0.1:6379> zcard salary #获取有序集合中元素的个数
(integer) 2
#获取指定区间的成员数量
127.0.0.1:6379> zcount salary 0 3000
(integer) 2
常用示例:
- set 排序, 存储班级成绩表, 工资表排序 !
- 普通消息, 重要消息, 带权消息判断
- 排行榜应用实现
朋友的定位,附近的人,打车距离计算。
经纬度查询网站:https://jingweidu.51240.com
geoadd
如何添加地理位置,两级无法直接添加,我们一般会直接下载城市数据,直接通过java程序一次性导入
127.0.0.1:6379> geoadd china:city 116.4 39.8 beijing #添加城市 那个组织 经度 维度 城市名字
(integer) 1
geopos 获取地理位置
127.0.0.1:6379> geopos china:city shanghai #获取指定城市的经度和维度
1) 1) "121.40000134706497192"
2) "31.59999989155001998"
127.0.0.1:6379> geopos china:city tianjing
1) 1) "117.29999810457229614"
2) "39.70000045331443772"
127.0.0.1:6379> geopos china:city beijing
1) 1) "116.39999896287918091"
2) "39.80000027249267447"
geodist 返回两个给定位置之间的距离
什么是基数 ?
A{1,3,5,7,9}
B{1,3,5,7,8}
基数(不重复的元素) = 5 ,可以接受误差 !
简介(作用)
网页的UV( 一个人访问一个网站多次,但是还是算作一个人 ! )
传统的方式, set 保存用户的id,然后统计set的数量判断!
这个方式如果保存大量的用户id,就会比较麻烦 ! 我们的目的是为了计数,而不是保存用户id
命令简介
127.0.0.1:6379> pfadd set a b c d e f g # 向hyperloglog中存放元素
(integer) 1
127.0.0.1:6379> pfcount set # 获取存放元素的个数
(integer) 7
127.0.0.1:6379> pfadd set a b c d e f g
(integer) 1
127.0.0.1:6379> pfcount set
(integer) 7
127.0.0.1:6379> pfadd set01 z a b d f g o
(integer) 1
127.0.0.1:6379> pfmerge set02 set set01 #将set 和 set01 合并为一个set 不包含重复元素
OK
127.0.0.1:6379> pfcount set02
(integer) 9
如果允许容错,那么一定可以使用Hyperloglog
位存储
常用示例:
统计用户信息, 活跃, 不活跃 ! 登录, 未登录 ! 打卡, 365 打卡 ! 只有两个状态的,都可以使用 bitmaps!
Bitmap 位图, 数据结构 ! 都是操作二进制位来进行记录, 就只有0 和 1两个状态 !
365 天 = 365 bit 1字节 = 8 bit 46字节左右即可存储!
测试 (使用bitma来记录 周一到周日的打卡)
127.0.0.1:6379> setbit user:cap 0 0 #存放数据
(integer) 0
127.0.0.1:6379> setbit user:cap 1 1
(integer) 0
127.0.0.1:6379> setbit user:cap 2 1
(integer) 0
127.0.0.1:6379> setbit user:cap 3 1
(integer) 0
127.0.0.1:6379> setbit user:cap 4 1
(integer) 0
127.0.0.1:6379> setbit user:cap 5 1
(integer) 0
127.0.0.1:6379> setbit user:cap 6 0
(integer) 0
查看打卡记录
127.0.0.1:6379> getbit user:cap 0
(integer) 0
127.0.0.1:6379> getbit user:cap 1
(integer) 1
127.0.0.1:6379> getbit user:cap 2
(integer) 1
统计操作, 统计打卡天数 !
127.0.0.1:6379> bitcount user:cap # 统计所有的打卡记录
(integer) 5
MySQL:ACID 原子性、一致性、独立性及持久性
Redis事务本质: 一组命令的集合! 一个事务中的所有命令都会被序列化,在事务执行过程中,按照顺序执行 !
一次性,顺序性,排他性 !执行一系列的命令
Redis事务没有隔离级别的概念 !
所有的命令在事务中,并没有直接被执行 !只有发起执行命令时才会执行
Redis: 单条命令是保证原子性的,但是事务不保证原子性 !
redis事务的三个阶段
开启事务( multi )
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> get k1
QUEUED
实行事务( exec )
127.0.0.1:6379> exec
1) OK
2) OK
3) "v1"
放弃事务 discard
编译时异常(代码有问题! 命令出错!) 事务中所有的命令都不会执行
运行时异常( 1/0 ), 如果事务队列中存在语法性错误,那么其他命令可以正常执行 ,错误命令抛出异常
监控 ! watch( 面试常问 )
悲观锁:
乐观锁:
Redis的监视测试
127.0.0.1:6379> set money 100
OK
127.0.0.1:6379> set out 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 20
QUEUED
127.0.0.1:6379> incrby out 20
QUEUED
127.0.0.1:6379> exec
1) (integer) 80
2) (integer) 20
测试多线程修改值,使用watch可以当做redis的乐观锁操作!
使用match监视某个值
在这个事务没有执行时值改变则事务一定执行失败
失败之后如何执行
先取消监视拿到新值在开启事务
我们要使用Java操作Redis
什么是Jedis 是Redis官方推荐的java连接开发工具 ! 使用Java操作Redis中间件
测试
1.导入对应的依赖
<dependency>
<groupId>redis.clientsgroupId>
<artifactId>jedisartifactId>
<version>3.2.0version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
<version>1.2.62version>
dependency>
2.编码测试:
package com.czp;
import redis.clients.jedis.Jedis;
public class TestPing {
public static void main(String[] args) {
// 1. new jedis对象
Jedis jedis = new Jedis("127.0.0.1",6379);
// jedis 所有的命令就是我们z之前学习的所有命令,
System.out.println(jedis.ping());
}
}
输出
命令和之前的命令一样
String
List
Hash
Set
ZSet
事务
说明 : 在SpringBoot2.x之后,原来使用的jedis被替换为了lettuce?
jedis : 采用的是直连的server,多个线程操作的话是不安全的,如果想要避免不安全,使用jedis pool连接池 ! BIO 模式
lettuce : 采用netty, 示例可以在多个线程中进行共享,不存在线程不安全的情况! 可以减少线程数据了, NIO模式
整合测试
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
spring:
redis:
host: 127.0.0.1
port: 6379
package com.czp.redis;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisTemplate;
@SpringBootTest
class RedisSpringboot02ApplicationTests {
@Autowired
private RedisTemplate<Object,Object> redisTemplate;
@Test
void contextLoads() {
// 清空数据库
// RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
// connection.flushAll();
// connection.flushDb();
// redisTemplate.opsForList();
redisTemplate.opsForValue().set("name", "车泽平");
Object name = redisTemplate.opsForValue().get("name");
System.out.println(name.toString());
}
}
自己配置RedisTemplate
package com.czp.redis.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.net.UnknownHostException;
@Configuration
public class RedisConfig {
// 固定模板, 直接使用
// 自定义一个RedisTemplate
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
/*
设置这些是 当redis的value设置为Jackson2JsonRedisSerializer,导致shiro反序列化时出错
直接使用JdkSerializationRedisSerializer不会出错,但是在RedisDesktopManager中,无法查看保存的数据
*/
ObjectMapper om = new ObjectMapper();
//在反序列化时忽略在JSON字符串中存在,而在Java中不存在的属性
om.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// key采用String的序列化方式
redisTemplate.setKeySerializer(stringRedisSerializer);
// hash的key也采用String的序列化方式
redisTemplate.setHashKeySerializer(stringRedisSerializer);
// value序列化方式采用jackson
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
// hash的value序列化方式采用jackson
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
启动的时候,就通过配置文件来启动 !
单位
包含
类似jsp的include标签
网络
bind 127.0.0.1 #绑定的ip 只有指定的ip可以访问redis服务
protected-mode no # 保护模式 默认为yes 关闭时此时外部网络可以直接访问
#开启protected-mode保护模式,需配置bind ip或者设置访问密码
port 6379 #端口号
通用配置 GENERAL
daemonize yes # 是否以守护进程的方式开启 默认为no
supervised no
pidfile /var/run/redis_6379.pid #如果以后台的方式运行, 我们就需要指定一个pid文件
# 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 # 日志级别
logfile /var/log/redis/redis.log # 日志的生成位置
databases 16 #默认的数据库数量
always-show-logo yes # 是否总是显示logo
快照
持久化,在规定的时间内,执行了多少次操作,则会持久化到文件.rdb .aof
redis是内存数据库,如果没有持久化,那么数据断电即失 !
# 如果900秒内,如果至少有一个key进行了修改,我们就进行持久化
save 900 1
# 如果300秒内,如果至少有十个key进行了修改,我们就进行持久化
save 300 10
# 如果60秒内,如果至少有10000个key进行了修改,我们就进行持久化
save 60 10000
#之后会自定义设置持久化
stop-writes-on-bgsave-error yes #持久化如果出错,是否还需要继续工作
rdbcompression yes #是否压缩rdb文件,需要消耗一些cpu的资源
rdbchecksum yes #保存rdb文件的时候,进行错误的校验
dir /var/lib/redis # rdb文件保存的目录
REPLICATION复制, 详见主从复制
SECURITY安全
# 设置redis登录密码
127.0.0.1:6379> config get requirepass
1) "requirepass"
2) ""
127.0.0.1:6379> config set requirepass chezeping
OK
127.0.0.1:6379> config get requirepass
(error) NOAUTH Authentication required.
127.0.0.1:6379> ping
(error) NOAUTH Authentication required.
127.0.0.1:6379> auth chezeping
OK
127.0.0.1:6379> config get requirepass
1) "requirepass"
2) "chezeping"
限制CLIENTS
maxclients 10000 # 设置能连接上redis的最大客户端的数量
maxmemory <bytes> # redis 配置最大的内存容量
maxmemory-policy noeviction # 内存达到上限的处理策略
# 移除一些过期的key
# 报错
APPEND ONLY MODE AOF的配置
appendonly no # 默认是不开启aof模式的, 默认是使用rdb方式持久化的,在大部分情况下,rdb完全够用!
appendfilename "appendonly.aof" #持久化文件的名字
# appendfsync always # 每次修改都会同步, 消耗性能
appendfsync everysec #每秒执行一次sync, 可能会丢失这1秒的数据
# appendfsync no # 不执行同步, 这个时候操作系统自己同步数据,速度是最快的!
具体配置,详见redis持久化aof
面试和公作,持久化都是重点 !
Redis是内存数据库,如果不将内存中的数据库状态保存到磁盘,那么一旦服务器进程退出,服务器中的数据状态也会消失,所以Redis提供了持久化功能!
什么是RDB
在指定的时间间隔内将内存中的数据集快照写入磁盘,也就是snapshot快照,它恢复时是将快照文件直接读入内存里
Redis会单独创建一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件,整个过程中,主线程是不进行任何io操作的,这就确保了极高的性能,如果需要进行大规模数据的恢复,且对于数据的恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效,RBD的缺点是最后一次持久化后的数据可能丢失,我们默认的就是RDB,一般情况下不需要修改这个配置 !
rdb保存的文件是 dump.rdb文件
触发机制
备份就自动生成一个dump.rdb文件
如何恢复rdb文件!
优点:
1. 适合大规模的数据恢复 ! dump.db
2. 对数据的完整性要求不高 !
缺点:
1. 需要一定的时间间隔进行操作 ! 如果redis意外宕机了,这个最后一次修改的数据就没有了 !
2. fork进程的时候, 会占用一定的内存空间
是什么
以日志的形式去记录每个写的操作,将redis执行的所有指令记录下来,只许追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作
aof保存的文件是appendonly.aof
重启之后aof就会生效
如果配置文件有错误,redis是启动不起来的
redis提供了一个工具 redis-check-aof --fix 文件
优点和缺点 !
优点 :
缺点 :
Redis发布订阅是一种消息通信机制: 发送者发送消息,订阅者接收消息
Redis客户端可以订阅任意数量的频道
订阅/发布消息图:
下图展示了频道channel1,以及订阅这个频道的三个客户端–client1 client2, client5之间的关系
当有新消息通过PUBLISH命令发送给频道channel1时,这个消息就会被发送给订阅它的三个客户端
命令
这些命令被广泛用于构建即时通信应用,比如网络聊天室和实时广播,实时提醒等
使用场景:
实时消息系统
主从复制,读写分离! 80%的情况下都是在进行读操作 !减缓服务器的压力 !架构中经常使用 ! 一主二从 !
只配置从库,不用配置主库 !
127.0.0.1:6379> info replication #查看当前库的信息
# Replication
role:master # 角色 master
connected_slaves:0 # 没有从机
master_repl_offset:0
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
127.0.0.1:6379>
集群搭建
默认情况下,每台Redis服务器都是主节点; 我们一般情况下只用配置从机就好了
127.0.0.1:6380> SLAVEOF 127.0.0.1 6379 #从机中配置 SLAVEOF 认老大
OK
127.0.0.1:6380> info replcation
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:9
master_sync_in_progress:0
slave_repl_offset:15
slave_priority:100
slave_read_only:1
connected_slaves:0
master_repl_offset:0
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
主机的显示
127.0.0.1:6379> info replication #未配置从机之前
# Replication
role:master
connected_slaves:0
master_repl_offset:0
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
127.0.0.1:6379> info replication #配置从机之后
# Replication
role:master
connected_slaves:1
slave0:ip=127.0.0.1,port=6380,state=online,offset=57,lag=0
master_repl_offset:57
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:2
repl_backlog_histlen:56
真实的主从配置应该在配置文件中进行配置,这样的话是永久的配置,用命令配置的是暂时的
细节
主机可以设置值,从机不能设置值(主机可以写,从机不能写只能读), 主机中的所有信息和数据,都会被从机保存!
测试:主机断开,从机依旧连接到主机,但是没有写操作了,这个时候,主机如果回来了,从机依旧可以直接获取到主机写的信息 !
复制原理
Slave 启动成功连接到master后会发送一个sync同步命令
master接到命令,启动后台的存盘进程,同时收集所有接受到的用于修改数据集命令,在后台进程执行完毕之后,master传送整个数据文件到slave,并完成一次完全同步
全量复制: slave服务在接收到数据库文件数据后,将其存盘并加载到内存中
增量复制: master继续将新的所有收集到的修改命令依次传给slave,完成同步
但是只要重新连接master,一次全量复制将被自动执行
层层链路
上一个M连接下一个slave
这个时候也可以完成我们的主从复制
如果没有老大了, 这个时候不能选择一个老大出来
127.0.0.1:6380> slaveof no one #如果主机中断,可以使用此命令将该主机设置为master
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=547,lag=0
master_repl_offset:547
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:2
repl_backlog_histlen:546
127.0.0.1:6380>
如果主机断开连接,我们可以使用 slaveof no one 让自己变成主机! 其他的节点就可以手动连接到最新的这个主节点
哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,他会独立运行.其原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例
这里的哨兵有两个作用
然而一个哨兵进程对Redis服务器进行监控,可能会出现问题,为此,我们可以使用多个哨兵进行监控.各个哨兵之间还会进行监控,这样就形成了多哨兵模式
假设主服务器宕机,哨兵1先检测到这个结果,系统并不会马上进行failover过程,仅仅是哨兵1主观的认为主服务器不可用,这个现象称为主观下线,当后面的哨兵也检测到主服务器不可用,并且达到一定值时,那么哨兵之间就会进行一次投票,投票的结果有一个哨兵发起,进行failover[故障转移操作],切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机,这个 过程称为客观下线
测试
我们目前是一主二从
1.配置sentinel.conf
#sentinel monitor 主机名称 ip 端口 1 代表主机挂了,slave投票看让谁接替成为主机,票数最多的就会成为主机
sentinel monitor redis 127.0.0.1 6379 1 在sentinel.conf中的配置
[root@czp bin]# redis-sentinel /etc/sentinel.conf
_._
_.-``__ ''-._
_.-`` `. `_. ''-._ Redis 3.2.12 (00000000/0) 64 bit
.-`` .-```. ```\/ _.,_ ''-._
( ' , .-` | `, ) Running in sentinel mode
|`-._`-...-` __...-.``-._|'` _.-'| Port: 26379
| `-._ `._ / _.-' | PID: 15660
`-._ `-._ `-./ _.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' | http://redis.io
`-._ `-._`-.__.-'_.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' |
`-._ `-._`-.__.-'_.-' _.-'
`-._ `-.__.-' _.-'
`-._ _.-'
`-.__.-'
15660:X 07 Apr 17:55:03.739 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
15660:X 07 Apr 17:55:03.742 # Sentinel ID is 361f41e5e981c0b35df316cb0d27768afc5936aa
15660:X 07 Apr 17:55:03.742 # +monitor master redis 127.0.0.1 6379 quorum 1
15660:X 07 Apr 17:55:03.742 * +slave slave 127.0.0.1:6380 127.0.0.1 6380 @ redis 127.0.0.1 6379
当主机宕机时哨兵进行的选举
15660:X 07 Apr 17:57:59.332 * +slave-reconf-done slave 127.0.0.1:6381 127.0.0.1 6381 @ redis 127.0.0.1 6379
15660:X 07 Apr 17:57:59.421 # +failover-end master redis 127.0.0.1 6379
15660:X 07 Apr 17:57:59.421 # +switch-master redis 127.0.0.1 6379 127.0.0.1 6380
15660:X 07 Apr 17:57:59.421 * +slave slave 127.0.0.1:6381 127.0.0.1 6381 @ redis 127.0.0.1 6380
15660:X 07 Apr 17:57:59.421 * +slave slave 127.0.0.1:6379 127.0.0.1 6379 @ redis 127.0.0.1 6380
15660:X 07 Apr 17:58:29.424 # +sdown slave 127.0.0.1:6379 127.0.0.1 6379 @ redis 127.0.0.1 6380 # 此时 6380为主机
6380显示
127.0.0.1:6380> info replication
# Replication
role:master
connected_slaves:1
slave0:ip=127.0.0.1,port=6381,state=online,offset=25891,lag=0
master_repl_offset:25891
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:2
repl_backlog_histlen:25890
若此时主机回来了,只能成为新主机的slave, 这就是哨兵模式的规则!
哨兵模式
优点:
缺点
哨兵模式的全部配置
#哨兵sentinel示例运行的端口 默认26379 如果有哨兵集群,我们还需要配置每个哨兵端口
port 26379
Redis缓存的使用,极大的提升了应用程序的性能和效率,特别是数据查询方面,但同时,它也带来一些问题,其中,尤为重要的是数据的一致性问题,从严格意义上讲,这个问题无解,如果对数据的一致性要求很高,那么就不能使用缓存
另外的一些典型问题就是,缓存穿透,缓存雪崩和缓存击穿.目前,业界也有比较流行的解决方案
概念
缓存穿透的概念很简单,用户想要查一个数据,发现redis内存数据库没有,也就是缓存没有命中,于是向持久层数据库查询.发现也没有,于是本次查询失败,当用户很多的时候,缓存都没有命中,于是都去请求了数据库,这会给持久层数据库造成很大的压力,这时候就相当于出现了缓存穿透
解决方案
布隆过滤器是一种数据结构,对所有可能查询的参数以hash形式存储,在控制层先进行校验,不符合则丢弃,从而避免了对底层存储系统的查询压力
当存储层不命中后,即使返回的空对象也将其缓存起来,同时设置一个过期时间,之后在访问这个数据将会从缓存中获取,保护了后端数据源
但是这种方法会存在两个问题
概述
这里需要注意和缓存穿透的区别,缓存击穿,是指一个key非常热点,在不停的扛着大并发,集中的访问一个key,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞
当某个key在过期的瞬间,有大量的请求并发的访问,这类数据一般就是热点数据,由于缓存过期,会同时访问数据库来查询最新的数据,会导致数据库压力过大
解决方案
设置热点数据永不过期
从缓存层面来看,没有设置过期时间,所以也不会出现热点key过期后产生的问题
加互斥锁
分布式锁: 使用分布式锁,保证对于每一个key同时只有一个线程去查询后端服务,其他线程没有获得分布式锁的权限,因此只需要等待即可,这种方式将高并发的压力转移到了分布式锁,因此对分布式锁的考验很大
概念
缓存雪崩, 是指在某一个时间段,缓存集中过期失效 redis宕机
产生雪崩的原因之一, 比如在写文本的时候,马上就要到双十二零点,很快就要迎来一波抢购,这波商品时间比较集中的加入了缓存,假设缓存一小时,那么到了凌晨1点钟的时候,这批商品的缓存就过期了,而对这批商品的访问查询,都落到了数据库上,对于数据库而言,就会产生周期性的压力波峰,于是所有的请求都会达到存储层,存储层的调用量就会暴增,造成存储层也挂掉的情况
其实集中过期也不是非常致命,比较致命的缓存雪崩,是缓存服务器某个节点宕机或断网,因为自然形成的缓存雪崩,一定是在某个时间段集中创建缓存,这个时候,数据库也是可以顶住压力的,无非就是对数据库产生周期性的压力罢了,而缓存服务节点的宕机,对数据库服务器造成的压力是不可预知的,很有可能瞬间就把数据库压垮
解决方案
这个思想的含义是,既然redis有可能挂掉,那我就多增设几台redis,这样一旦一台挂掉以后,其他的还可以继续工作,其实就是搭建的集群
这个解决方案的思想是指,在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量,比如对某一个key只允许一个线程查询和写缓存,其他线程等待
Redis缓存的使用,极大的提升了应用程序的性能和效率,特别是数据查询方面,但同时,它也带来一些问题,其中,尤为重要的是数据的一致性问题,从严格意义上讲,这个问题无解,如果对数据的一致性要求很高,那么就不能使用缓存
另外的一些典型问题就是,缓存穿透,缓存雪崩和缓存击穿.目前,业界也有比较流行的解决方案