redis狂神说笔记超详细(总结不易)

redis狂神说笔记/阿里云服务器连接

  • 一.安装
    • windows安装
    • linux安装
      • 1.官网地址:https://redis.io/download
      • 2.下载Xshell,Xftp免费版
      • 3.链接阿里云服务器
      • 4.安装Xftp,连接阿里云服务器
      • 5.![在这里插入图片描述](https://img-blog.csdnimg.cn/20210330172112103.png)
      • 6.文件上传到服务器,利用Xftp
      • 7.解压安装包
      • 8.进入解压后文件
      • 9.基本环境安装
  • 二.测试性能
  • 三.基础知识
    • redis是单线程的
    • redis-key
  • 四.五大基本数据类型
    • string(字符串)
    • list
    • set(集合)
    • hash
    • zset(有序集合)
  • 五.三种特殊数据类型
    • geospatial(地理位置)
      • geoadd
      • geopos
      • geodist
      • georadius已给定的经纬度为中心,找某一半径内的元素
      • georadiusbymember
      • geohash
      • geo底层实现原理
    • hyperloglog(基数统计)
    • bitmap
  • 六.事务
    • 正常执行事务
    • 放弃事务
    • 编译型异常(代码有问题,命令有问题)
    • 运行是异常(其他命令正常执行,错误命令抛出异常)
  • 七.redis实现乐观锁(面试常问)
    • redis监视测试
    • 测试多线程修改值,使用watch可以当作乐观锁操作
  • 八.jedis
    • 测试
      • 新建空项目
      • 导入依赖
      • 编码测试
  • 通过Jedis理解事务
  • 九.springboot整合
    • 新建模块
    • 整合测试
    • 自定义RedisTemplate
  • 十.redis配置文件详解
  • 十一.持久化之RDB(Redis DataBase)
  • 十二.持久化操作之aof(append only file)
  • 十三.redis订阅发布
  • 十四.redis主从复制
    • 环境配置
    • 主从复制之复制原理
      • 细节
      • 复制原理
    • 宕机后手动配置主机
  • 哨兵模式(自动选取老大)
  • redis缓存穿透和雪崩(面试高频,工作常用)
    • 缓存穿透(查不到)
    • 解决方案
    • 缓存击穿(查的多,过期)
    • 缓存雪崩

原教程链接:https://b23.tv/5BR8CX

一.安装

windows安装

资源:链接:https://pan.baidu.com/s/1CnMlSGMcZPWFc8qW_EgFwQ
提取码:w2ef
解压即安装
运行
redis狂神说笔记超详细(总结不易)_第1张图片
redis狂神说笔记超详细(总结不易)_第2张图片
ping返回pong连接成功
redis狂神说笔记超详细(总结不易)_第3张图片
redis狂神说笔记超详细(总结不易)_第4张图片

linux安装

1.官网地址:https://redis.io/download

2.下载Xshell,Xftp免费版

redis狂神说笔记超详细(总结不易)_第5张图片

收到邮件:
redis狂神说笔记超详细(总结不易)_第6张图片
无脑下一步安装

3.链接阿里云服务器

复制公网id
redis狂神说笔记超详细(总结不易)_第7张图片
新建连接输入公网id以及密码
redis狂神说笔记超详细(总结不易)_第8张图片

4.安装Xftp,连接阿里云服务器

5.redis狂神说笔记超详细(总结不易)_第9张图片

cd /

切换到根目录

6.文件上传到服务器,利用Xftp

redis狂神说笔记超详细(总结不易)_第10张图片

7.解压安装包

redis狂神说笔记超详细(总结不易)_第11张图片
程序一般放在opt目录下
redis狂神说笔记超详细(总结不易)_第12张图片
解压:
在这里插入图片描述

8.进入解压后文件

redis狂神说笔记超详细(总结不易)_第13张图片

9.基本环境安装

1.redis是c++写的

yum install gcc-c++

redis狂神说笔记超详细(总结不易)_第14张图片
2.make两次

make

redis狂神说笔记超详细(总结不易)_第15张图片
3.

make install

redis狂神说笔记超详细(总结不易)_第16张图片

4.redis默认安装路径为 /usr/local/bin
在这里插入图片描述
redis狂神说笔记超详细(总结不易)_第17张图片
5.将redis配置文件复制到当前目录下
redis狂神说笔记超详细(总结不易)_第18张图片
我们之后使用这个配置文件进行启动
6.redis默认不是后台启动,修改配置文件
vim redis.conf
redis狂神说笔记超详细(总结不易)_第19张图片
改为后台启动
退出
在这里插入图片描述

7.启动redis服务

cd ..
回到上级目录

redis狂神说笔记超详细(总结不易)_第20张图片
连接:
redis狂神说笔记超详细(总结不易)_第21张图片
在这里插入图片描述
8.查看redis进程是否开启
开启两个进程
在这里插入图片描述

9.关闭redis
在这里插入图片描述
redis狂神说笔记超详细(总结不易)_第22张图片
10.后面使用单机多redis启动集群测试

二.测试性能

redis-benchmark是一个压力测试工具,官方自带的性能测试工具
redis-benchmark 命令参数
redis狂神说笔记超详细(总结不易)_第23张图片
我们来简单测试一下

#测试:100个并发,10万个请求
redis-benchmark -h localhost -p 6379 -c 100 -n 100000

redis狂神说笔记超详细(总结不易)_第24张图片
redis狂神说笔记超详细(总结不易)_第25张图片

三.基础知识

redis默认有16个数据库,默认使用的是第0个,
1.可以使用select 进行切换数据库
在这里插入图片描述
2.查看数据库大小
在这里插入图片描述
3.清空dangqian数据库

flushdb

4.清空全部数据库内容

flushdb

redis是单线程的

redsi很快,redis基于内存操作,cpu不是redis性能瓶颈,redis的瓶颈是根据机器的内存和网络带宽,既然可以使用单线程来实现,就使用单线程。
redis是c语言写的,官方数据是100000+的QPS,这个不必同样使用key-value的Memecache差
redis为什么单线程还这么快
1.误区:高性能的服务器一定是多线程的?
2.误区二:多线程一定比单线程高!
核心:redis将所有数据全部放在内存中,所以说使用单线程去操作效率高,多线程(cpu上下文切换:耗时操作),对于内存系统来说,如果没有上下文切换效率是最高的,多次读写都是在一个cpu上的,在内存情况下,这个就是最佳方案!

Redis是开放源代码(BSD许可)的内存中数据结构存储,用作数据库缓存消息中间件MQ。Redis提供数据结构,例如字符串,哈希,列表,集合,带范围查询的排序集合,位图,超日志,地理空间索引和流。Redis具有内置的复制,Lua脚本,LRU驱逐,事务和不同级别的磁盘持久性,并通过Redis Sentinel和Redis Cluster自动分区提供了高可用性

redis-key

此图红色箭头有误,
exists name判断name是否存在
redis狂神说笔记超详细(总结不易)_第26张图片
redis狂神说笔记超详细(总结不易)_第27张图片
在这里插入图片描述
后面不会的命令可以在官网查
redis狂神说笔记超详细(总结不易)_第28张图片

四.五大基本数据类型

string(字符串)

==90%的程序员使用redis只会使用string类型。==api调用工程师
redis狂神说笔记超详细(总结不易)_第29张图片

127.0.0.1:6379> set views 0     
OK
127.0.0.1:6379> get views
"0"
127.0.0.1:6379> incr views    #自增一
(integer) 1
127.0.0.1:6379> incr views
(integer) 2
127.0.0.1:6379> get views
"2"
127.0.0.1:6379> decr views     #自减一
(integer) 1
127.0.0.1:6379> incrby views 10   #自增10
(integer) 11
127.0.0.1:6379> incrby views 10
(integer) 21
127.0.0.1:6379> decrby views 5   #自减5
(integer) 16
127.0.0.1:6379> 

截取字符串getrange

127.0.0.1:6379> clear
127.0.0.1:6379> keys *
(empty array)
127.0.0.1:6379> set key1 "fang"
OK
127.0.0.1:6379> get key1
"fang"
127.0.0.1:6379> getrange key1 0 2  #截取012字符串
"fan"
127.0.0.1:6379> getrange key1 0 -1 #获取全部字符串
"fang"
127.0.0.1:6379> 

替换setrange

127.0.0.1:6379> set key2 abcdefg
OK
127.0.0.1:6379> get key2
"abcdefg"
127.0.0.1:6379> setrange key2 1 xx
(integer) 7
127.0.0.1:6379> get key2
"axxdefg"
127.0.0.1:6379> 

setex #设置过期时间
setnx #不存在在设置(在分布式锁中常常使用

127.0.0.1:6379> clear
127.0.0.1:6379> setex key3 30 "hello"
OK
127.0.0.1:6379> ttl key3
(integer) 17
127.0.0.1:6379> setnx mykey "redsi" #如果mykey存在,则创建失败。
(integer) 1
127.0.0.1:6379> keys *
1) "mykey"
2) "key2"
3) "key1"
127.0.0.1:6379> ttl key3
(integer) -2
127.0.0.1:6379> setnx mykey "MongDB"
(integer) 0
127.0.0.1:6379> get mykey
"redsi"

一次性获取,设置多个值:mset,mget

127.0.0.1:6379> clear
127.0.0.1:6379> keys *
(empty array)
127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3     #同时设置多个值
OK
127.0.0.1:6379> keys *
1) "k3"
2) "k2"
3) "k1"
127.0.0.1:6379> mget k1 k2 k3    #同时获取多个值
1) "v1"
2) "v2"
3) "v3"
127.0.0.1:6379> msetnx k1 v1 k4 v4   #msetnx是一个原子性操作,要么成功要么失败
(integer) 0
127.0.0.1:6379> get k4
(nil)
127.0.0.1:6379> 

对象
mset user:1{name:zhangsan,age:3}#设置一个user:1对象 值为json字符来保存一个对象

127.0.0.1:6379> mset user:1:name fang user:1:age 2
OK
127.0.0.1:6379> mget user:1:name user:1:age
1) "fang"
2) "2"
127.0.0.1:6379> 

先get在set-------getset

127.0.0.1:6379> clear
127.0.0.1:6379> getset db redis
(nil)
127.0.0.1:6379> get db
"redis"
127.0.0.1:6379> getset db mongodb
"redis"
127.0.0.1:6379> get db
"mongodb"

string类型的使用场景:value除了字符串还可以是数字
1.计数器
2.统计多单位数量
3.粉丝数
4.对象缓存存储!

list

在redis里面可以把list完成栈,队列,阻塞队列!
所有的list命令以l开头
1.

127.0.0.1:6379> lpush list one
(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
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> lrange list 0 1
1) "three"
2) "two"
倒着输出

redis狂神说笔记超详细(总结不易)_第30张图片
默认象栈
移除
lpop
rpop
redis狂神说笔记超详细(总结不易)_第31张图片
lindex 通过下表获得值

127.0.0.1:6379> lindex list 1
"one"
127.0.0.1:6379> lindex list 0
"two"

llen返回列表长度

127.0.0.1:6379> lpush list one
(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> llen list
(integer) 3

移除指定值
取关 uid
lrem

redis狂神说笔记超详细(总结不易)_第32张图片
trim 修剪:list截断

redis狂神说笔记超详细(总结不易)_第33张图片
rpop lpush,将列表右边元素移到另一个列表的左边

127.0.0.1:6379> rpush mylist "0"
(integer) 1
127.0.0.1:6379> rpush mylist "1"
(integer) 2
127.0.0.1:6379> rpush mylist "2"
(integer) 3
127.0.0.1:6379> rpoplpush mylist myother 
"2"
127.0.0.1:6379> lrange mylist 0 -1
1) "0"
2) "1"
127.0.0.1:6379> lrange myother 0 -1
1) "2"

lset list 0 item将下标为0的元素替换为item

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 value1
(integer) 1
127.0.0.1:6379> lrange list 0 0
1) "value1"
127.0.0.1:6379> lset list 0 item
OK
127.0.0.1:6379> lrange list 0 0
1) "item"
127.0.0.1:6379> 

linset 在指定值的前面或者后面插入值

127.0.0.1:6379> rpush mylist hello
(integer) 1
127.0.0.1:6379> rpush mylist world
(integer) 2
127.0.0.1:6379> linsert mylist before world other
(integer) 3
127.0.0.1:6379> lrange mylist 0 -1
1) "hello"
2) "other"
3) "world"
127.0.0.1:6379> 

小结:
1.实际上是一个双向链表
2.key不存在,创建新链表
3.移除所有元素,空链表也代表不存在
4.在两边插入或者改动值,效率高,中间元素,相对效率低
消息队列

set(集合)

set值是不能重复的
sdd:插入
smembers:查询指定set中所有值
sismember:查看值是否存在

127.0.0.1:6379> sadd myset hello
(integer) 1
127.0.0.1:6379> sadd myset fang
(integer) 1
127.0.0.1:6379> sadd myset lovefang
(integer) 1
127.0.0.1:6379> smembers myset
1) "lovefang"
2) "fang"
3) "hello"
127.0.0.1:6379> sismember myset hello
(integer) 1
127.0.0.1:6379> sismember myset hello1
(integer) 0
127.0.0.1:6379> scard myset        #获取集合元素个数
(integer) 3
127.0.0.1:6379> 

移除:srem myset hello

set:无序不重复集合
######################################
随机抽选元素
srandmember

127.0.0.1:6379> srandmember myset
"hello"
127.0.0.1:6379> srandmember myset
"fang"
127.0.0.1:6379> srandmember myset 2
1) "lovefang"
2) "fang"
127.0.0.1:6379> 

随机删除value

127.0.0.1:6379> smembers myset
1) "lovefang"
2) "fang"
3) "hello"
127.0.0.1:6379> spop myset
"hello"
127.0.0.1:6379> smembers myset
1) "lovefang"
2) "fang"

#######################################################
将一个指定的值移动到另外的set

127.0.0.1:6379> sadd myset2 1
(integer) 1
127.0.0.1:6379> smove myset myset2 fang
(integer) 1
127.0.0.1:6379> smembers myset2
1) "fang"
2) "1"
127.0.0.1:

###########################################################
微博,b站共同关注(并集)
数字集合类:
差集,交集,并集
redis狂神说笔记超详细(总结不易)_第34张图片
微博,将a用户所有关注的人放在一个集合中,将他的粉丝也放在一个集合中!
共同关注,共同爱好

hash

map集合,key-Map集合

127.0.0.1:6379> hset myhash field1 fang
(integer) 1
127.0.0.1:6379> hget myhash field1
"fang"
127.0.0.1:6379> hmset myhash filed1 hello filed2 world1  ##一次设置多个值
OK
127.0.0.1:6379> hmget myhash filed1 filed2 ##一次取多个值
1) "hello"
2) "world1"
127.0.0.1:6379> hgetall myhash   #获取全部数据
1) "field1"
2) "fang"
3) "filed1"
4) "hello"
5) "filed2"
6) "world1"
127.0.0.1:6379> 

删除指定key:hdel myhash filed1
获取字段数量

127.0.0.1:6379> hlen myhash
(integer) 2
127.0.0.1:6379> 

####################################################
判断指定字段是否存在
hexists myhash filed1

####################################################
只获得所有的key
hkeys myhash
只获得所有的value
hvals myhash
####################################################
incr decr

127.0.0.1:6379> hset myhash field3 3
(integer) 1
127.0.0.1:6379> hincrby myhash field3 1
(integer) 4
127.0.0.1:6379> hincrby myhash field3 -1
(integer) 3
127.0.0.1:6379> 

hsetnx myhash filed4 hello  #如果不存在则可以设置
hsetnx myhash filed4 hello  #存在则不能设置

hash 变更的数据,user name age,尤其使用户信息之类的,经常变更的数据
hash更适合对象的存储,string更加适合字符串存储

zset(有序集合)

在set的基础上增加了一个值
set v1 k1
zset k1 score1 v1
排序如何实现

127.0.0.1:6379> zadd myset 1 one
(integer) 1
127.0.0.1:6379> zadd myset 2 two
(integer) 1
127.0.0.1:6379> zadd myset 3 three
(integer) 1
127.0.0.1:6379> zrange myset 0 -1
1) "one"
2) "two"
3) "three"
127.0.0.1:6379> 

##############################################
排序如何实现

27.0.0.1:6379> zadd salary 2500 1
(integer) 1
127.0.0.1:6379> zadd salary 5000 2
(integer) 1
127.0.0.1:6379> zadd salary 500 3
(integer) 1
127.0.0.1:6379> zrangebyscore salary -inf +inf   #从负无穷到正无穷排序
1) "3"
2) "1"
3) "2"
127.0.0.1:6379> zrangebyscore salary -inf +inf withscores   
1) "3"
2) "500"
3) "1"
4) "2500"
5) "2"
6) "5000"
127.0.0.1:6379> zrangebyscore salary -inf 2500 withscores  #工资小于2500的升序排列
1) "3"
2) "500"
3) "1"
4) "2500"
127.0.0.1:6379> 

##############################################
移除rem

127.0.0.1:6379> zrange salary 0 -1
1) "3"
2) "1"
3) "2"
127.0.0.1:6379> zrem salary 1 #移除指定元素
(integer) 1
127.0.0.1:6379> zrange salary 0 -1
1) "3"
2) "2"
127.0.0.1:6379> zcard salary   #获取有序集合中的个数
(integer) 2
127.0.0.1:6379> 

###################################################
获取指定区间的成员数量

127.0.0.1:6379> zadd myset 1 hello
(integer) 1
127.0.0.1:6379> zadd myset 2 world 3 fangqing
(integer) 2
127.0.0.1:6379> zcount myset 1 3
(integer) 3
127.0.0.1:6379> zcount myset 1 2
(integer) 2
127.0.0.1:6379> 

redis官网可以查看api的使用
案例思路;set 排序,存储班级成绩表,工资表排序
普通消息1,重要消息2,带权重进行排序判断!
排行榜应用实现

五.三种特殊数据类型

geospatial(地理位置)

朋友定位,附近的人,打车距离技术,这个功能可以推算地理位置信息,两地之间的距离,方圆几里的人!
可以查询一些测试数据
只有六个命令!

geoadd

添加数据
规则:两级无法添加,我们一般会下载城市数据,直接通过java程序一次性导入!
参数(纬度 经度 名称)
超过有效的经度纬度就会报错

127.0.0.1:6379> geoadd china:city 116.40 39.90 beijin
(integer) 1
127.0.0.1:6379> geoadd china:city 121.47 31.23 shanghai
(integer) 1
127.0.0.1:6379> geoadd china:city 106.50 29.53 chongqi
(integer) 1
127.0.0.1:6379> geoadd china:city 114.05 22.52 shengzhen
(integer) 1
127.0.0.1:6379> geoadd china:city 120.16 30.24 hangzhou 118.96 34.26 xian
(integer) 2
127.0.0.1:6379> 
 

geopos

获取当前定位:一定是一个坐标值

127.0.0.1:6379> geopos china:city beijin
1) 1) "116.39999896287918091"
   2) "39.90000009167092543"
127.0.0.1:6379> geopos china:city beijin chongqi
1) 1) "116.39999896287918091"
   2) "39.90000009167092543"
2) 1) "106.49999767541885376"
   2) "29.52999957900659211"
127.0.0.1:6379> 

geodist

两人之间的距离

127.0.0.1:6379> geodist china:city beijin shanghai
"1067378.7564"   #北京上海的==直线距离,默认单位为米==
127.0.0.1:6379> geodist china:city beijin shanghai km
"1067.3788"  

georadius已给定的经纬度为中心,找某一半径内的元素

我附近的人?(获得所有附近的人的地址,定位!)通过半径来查询

127.0.0.1:6379> georadius china:city 110 30 1000 km #以100经度30纬度为中心1000km为半径的圆内城市
1) "chongqi"
2) "shengzhen"
3) "hangzhou"
4) "xian"
127.0.0.1:6379> georadius china:city 110 30 500 km
1) "chongqi"
127.0.0.1:6379> georadius china:city 110 30 500 km withdist #显示到中心位置的距离
1) 1) "chongqi"
   2) "341.9374"
127.0.0.1:6379> georadius china:city 110 30 500 km withcoord #显示他人的定位信息
1) 1) "chongqi"
   2) 1) "106.49999767541885376"
      2) "29.52999957900659211"
127.0.0.1:6379> georadius china:city 110 30 500 km withdist withcoord count 1 #筛选出指定数量的结果
1) 1) "chongqi"
   2) "341.9374"
   3) 1) "106.49999767541885376"
      2) "29.52999957900659211"
127.0.0.1:6379> georadius china:city 110 30 500 km withdist withcoord count 2
1) 1) "chongqi"
   2) "341.9374"
   3) 1) "106.49999767541885376"
      2) "29.52999957900659211"
127.0.0.1:6379> georadius china:city 110 30 500 km withdist withcoord count 3
1) 1) "chongqi"
   2) "341.9374"
   3) 1) "106.49999767541885376"
      2) "29.52999957900659211"
127.0.0.1:6379> 

georadiusbymember

找出指定城市周围的位置

127.0.0.1:6379> georadiusbymember china:city beijin 1000 km
1) "xian"
2) "beijin"
127.0.0.1:6379> 

geohash

该命令返回长度为11的字符串

127.0.0.1:6379> geohash china:city beijin chongqi
1) "wx4fbxxfke0"    #将二维的经纬度转换为一维的字符串,如果字符串月接近,那么距离则越近
2) "wm5xzrybty0"

geo底层实现原理

原理其实就是zset,我们可以使用zset命令操作geo!

127.0.0.1:6379> zrange china:city 0 -1 ##查看元素
1) "chongqi"
2) "shengzhen"
3) "hangzhou"
4) "shanghai"
5) "xian"
6) "beijin"
127.0.0.1:6379> zrem china:city beijin
(integer) 1
127.0.0.1:6379> zrange china:city 0 -1
1) "chongqi"
2) "shengzhen"
3) "hangzhou"
4) "shanghai"
5) "xian"
127.0.0.1:6379> 

hyperloglog(基数统计)

a{1,3,5,7,8,7}
b{1,2,5,7,8}
基数(不重复的元素)=5,可以接受误差
基数统计算法
优点:占用内存是固定的,2^64不同元素计数,只需要费12kb的内存,如果要从内存角都比较hyperloglog首选
网页的uv(一个人访问一个网站多次,但是还是算作一个人)
传统的方式,set保护用户的id,其元素不重复,然后就可以统计set中元素的数量作为标准判断
这个方式如果保持大量用户id就会比较麻烦,我们的目的就是为了计数,而不是保存用户id;
0.81%的错误率,可以接受

127.0.0.1:6379> pfadd mykey a b c d e f g h i j 
(integer) 1
127.0.0.1:6379> pfcount mykey #统计mykey元素基数数量
(integer) 10
127.0.0.1:6379> pfcount mykey2 i j z x c v b n m #
(integer) 0
127.0.0.1:6379> pfadd mykey2 i j z x c v b n m
(integer) 1
127.0.0.1:6379> pfcount mykey2
(integer) 9
127.0.0.1:6379> pfmerge mykey3 mykey mykey2 #合并两组到mykey3
OK
127.0.0.1:6379> pfcount mykey3
(integer) 15
127.0.0.1:6379> 

如果允许容错统计数量,可以使用hyperloglog
如果不允许使用set或者其他数据类型

bitmap

位存储
统计用户信息,活跃,不活跃,登入,为登入,打卡,365打卡!两个状态的都可以使用bitmaps
bitmaps位图,数据结构,都是操作二进制位来进行记录的,就只有0,1两个状态
365=365bit 1字节=8bit 46字节左右!
测试
记录周一到周日打卡
周一:1,周二:0,周三:0.。。。

127.0.0.1:6379> setbit sign 0 0
(integer) 0
127.0.0.1:6379> setbit sign 1 0
(integer) 0
127.0.0.1:6379> setbit sign 2 0
(integer) 0
127.0.0.1:6379> setbit sign 3 0
(integer) 0
127.0.0.1:6379> setbit sign 4 1
(integer) 0
127.0.0.1:6379> setbit sign 5 0
(integer) 0
127.0.0.1:6379> setbit sign 6 0
(integer) 0
127.0.0.1:6379> 

查看某一天是否打卡

127.0.0.1:6379> getbit sign 3
(integer) 0
127.0.0.1:6379> getbit sign 4
(integer) 1
127.0.0.1:6379> 

统计打卡天数,可以看到是否有缺勤
127.0.0.1:6379> bitcount sign
(integer) 1

六.事务

redis事务本质:一组命令的集合!一个事务中的所有命令都会被序列化,执行过程中按照顺序执行
一次性,顺序性,排他性,执行一些列的命令

redis单条命令是保持原子性,但是事务不保证原子性
redis事务没有隔离级别的概念
所有的命令在事务中,并没有被直接执行,只有发起执行命令才会被执行!exec
redis事务
a.开启事务(multi)
b.命令入队
c.执行事务(exec)

正常执行事务

127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> get k2
QUEUED
127.0.0.1:6379(TX)> set k3 v3
QUEUED
127.0.0.1:6379(TX)> exec
1) OK
2) OK
3) "v2"
4) OK
127.0.0.1:6379> 

放弃事务

discard

127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set k4 v4
QUEUED
127.0.0.1:6379(TX)> discard
OK
127.0.0.1:6379> keys *
1) "k3"
2) "k2"
3) "k1"
127.0.0.1:6379> 

编译型异常(代码有问题,命令有问题)

事务中所有命令都不会被执行

127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> set k3 v3
QUEUED
127.0.0.1:6379(TX)> getset k3
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379(TX)> set k4 v4
QUEUED
127.0.0.1:6379(TX)> exec
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get k1
(nil)
127.0.0.1:6379> 

运行是异常(其他命令正常执行,错误命令抛出异常)

127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> incr k1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> get k2
QUEUED
127.0.0.1:6379(TX)> exec
1) (error) ERR value is not an integer or out of range #虽然第一条命令报错,但是依旧正常执行
2) OK
3) "v2"
127.0.0.1:6379> get k2
"v2"
127.0.0.1:6379> 

七.redis实现乐观锁(面试常问)

悲观锁:很悲观,认为什么时候都会出现问题,无论什么都会加锁,效率降低
乐观锁:很乐观,认为什么时候都不会出现问题,所以不上锁,更新数据时会判断在此期间是否有人改动数据,version!
1.获取version
2.更新时比较version

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(TX)> decrby money 20
QUEUED
127.0.0.1:6379(TX)> incrby out 20
QUEUED
127.0.0.1:6379(TX)> exec
1) (integer) 80
2) (integer) 20
127.0.0.1:6379> 

测试多线程修改值,使用watch可以当作乐观锁操作

新开客户端模拟第二个线程

redis狂神说笔记超详细(总结不易)_第35张图片
执行事务之前另一个线程修改值
redis狂神说笔记超详细(总结不易)_第36张图片

127.0.0.1:6379> unwatch
OK #r#如果事务执行失败,先解锁
127.0.0.1:6379> watch money
OK## 获取最新值,再次监视
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> decrby money 1
QUEUED
127.0.0.1:6379(TX)> incrby money 1
QUEUED
127.0.0.1:6379(TX)> exec ##对比监视值是否发送变化,如果没有变化可以执行,变化则执行失败
1) (integer) 999
2) (integer) 1000
127.0.0.1:6379> 

八.jedis

我们要使用java操作redis
jedis是官方推荐的java连接开发工具,使用java操作redis中间件,如果你要使用java操作redis,那么你一定要对jedis十分熟悉

测试

新建空项目

redis狂神说笔记超详细(总结不易)_第37张图片
redis狂神说笔记超详细(总结不易)_第38张图片
2.新建新的maven模块
redis狂神说笔记超详细(总结不易)_第39张图片

导入依赖

redis狂神说笔记超详细(总结不易)_第40张图片

编码测试

a.连接数据库
b.操作命令
c.断开连接
redis狂神说笔记超详细(总结不易)_第41张图片
所有api都是上面对应的命令

通过Jedis理解事务

redis狂神说笔记超详细(总结不易)_第42张图片

redis狂神说笔记超详细(总结不易)_第43张图片

九.springboot整合

springboo操作数据库:spring-data jpa jdbc mongodb redis
springData也是和springboot其名的项目

新建模块

新建springboot模块
redis狂神说笔记超详细(总结不易)_第44张图片
spring Native不能勾选否则后面测试会失败
redis狂神说笔记超详细(总结不易)_第45张图片
redis狂神说笔记超详细(总结不易)_第46张图片
redis狂神说笔记超详细(总结不易)_第47张图片
记得改名,不然不会创建同级目录

redis狂神说笔记超详细(总结不易)_第48张图片
说明在springboot2.x之后,原来的jedis被替换为lettuce
jedis采用直连,多个线程操作的化是不安全的,如果想要避免不安全,使用jedis pool连接池。bio模式
lettuce:采用netty,实例可以在多个线程中共享,不存在线程不安全的情况,可以减少线程数量。更像nio模式

redis狂神说笔记超详细(总结不易)_第49张图片
ctrl+f搜索redis
redis狂神说笔记超详细(总结不易)_第50张图片
redis狂神说笔记超详细(总结不易)_第51张图片
源码分析

    @ConditionalOnSingleCandidate(RedisConnectionFactory.class)
    //我们可以自己定义一个redisTemplate来替换这个默认的!
    public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
    	//默认的RedisTemplate没有过多的设置,redis对象都需要序列化!
    	//两个泛型都是object类型,我们使用需要强制转换
        RedisTemplate template = new RedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnSingleCandidate(RedisConnectionFactory.class)
    //由于String是Redis中最常使用的类型,所有单独提出来一个bean
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
        StringRedisTemplate template = new StringRedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

在这里插入图片描述

整合测试

1.导入依赖
操作redis

        
            org.springframework.boot
            spring-boot-starter-data-redis
        

2.配置连接
redis狂神说笔记超详细(总结不易)_第52张图片

3.测试

redis狂神说笔记超详细(总结不易)_第53张图片
redis狂神说笔记超详细(总结不易)_第54张图片

redis狂神说笔记超详细(总结不易)_第55张图片
redis狂神说笔记超详细(总结不易)_第56张图片

在这里插入图片描述
redis狂神说笔记超详细(总结不易)_第57张图片

自定义RedisTemplate

所有的对象都需要序列化不然会报错
redis狂神说笔记超详细(总结不易)_第58张图片

企业中所有的pojo类一般都需要序列化
redis狂神说笔记超详细(总结不易)_第59张图片
自定义序列化,企业开发中可以直接使用

package com.example.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
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 {
    //编写自己的redsiTemplate
    @Bean
    @SuppressWarnings("all")
    public RedisTemplate redisTemplate(RedisConnectionFactory factory) {

        RedisTemplate template = new RedisTemplate();
        template.setConnectionFactory(factory);

        // 序列化配置 解析任意对象
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        // json序列化利用ObjectMapper进行转义
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        // 2.序列化String类型
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();

        // key采用String的序列化方式
        template.setKeySerializer(stringRedisSerializer);
        // hash的key也采用String的序列化方式
        template.setHashKeySerializer(stringRedisSerializer);
        // value序列化方式采用jackson
        template.setValueSerializer(jackson2JsonRedisSerializer);
        // hash的value序列化方式采用jackson
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        template.afterPropertiesSet();

        return template;
    }

}

redis狂神说笔记超详细(总结不易)_第60张图片
乱码问题得到解决

redis狂神说笔记超详细(总结不易)_第61张图片

//在企业开发中我们80%不会使用原生的方式去编写代码,一般使用工具类RedisUtils
网上可以直接搜索RedisUtils
```
import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**

  • redisTemplate封装
    */
    @Component
    public class RedisUtils {

    //此写法可防止RedisTemplate 注入失败
    private static RedisTemplate redisTemplate;
    @Autowired
    public void setRedisTemplate(RedisTemplate redisTemplate) {
    RedisUtils.redisTemplate = redisTemplate;
    }

    /**

    • 指定缓存失效时间
    • @param key 键
    • @param time 时间(秒)
    • @return
      */
      public boolean expire(String key,long time){
      try {
      if(time>0){
      redisTemplate.expire(key, time, TimeUnit.SECONDS);
      }
      return true;
      } catch (Exception e) {
      e.printStackTrace();
      return false;
      }
      }

    /**

    • 根据key 获取过期时间
    • @param key 键 不能为null
    • @return 时间(秒) 返回0代表为永久有效
      */
      public long getExpire(String key){
      return redisTemplate.getExpire(key,TimeUnit.SECONDS);
      }

    /**

    • 判断key是否存在
    • @param key 键
    • @return true 存在 false不存在
      */
      public boolean hasKey(String key){
      try {
      return redisTemplate.hasKey(key);
      } catch (Exception e) {
      e.printStackTrace();
      return false;
      }
      }

    /**

    • 删除缓存
    • @param key 可以传一个值 或多个
      */
      @SuppressWarnings(“unchecked”)
      public void del(String … key){
      if(key!=null&&key.length>0){
      if(key.length==1){
      redisTemplate.delete(key[0]);
      }else{
      redisTemplate.delete(CollectionUtils.arrayToList(key));
      }
      }
      }

    //String=
    /**

    • 普通缓存获取
    • @param key 键
    • @return 值
      */
      public Object get(String key){
      return key==null?null:redisTemplate.opsForValue().get(key);
      }

    /**

    • 普通缓存放入
    • @param key 键
    • @param value 值
    • @return true成功 false失败
      */
      public static boolean set(String key,Object value) {
      try {
      redisTemplate.opsForValue().set(key, value);
      return true;
      } catch (Exception e) {
      e.printStackTrace();
      return false;
      }
      }

    /**

    • 普通缓存放入并设置时间
    • @param key 键
    • @param value 值
    • @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
    • @return true成功 false 失败
      */
      public boolean set(String key,Object value,long time){
      try {
      if(time>0){
      redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
      }else{
      set(key, value);
      }
      return true;
      } catch (Exception e) {
      e.printStackTrace();
      return false;
      }
      }

    /**

    • 递增
    • @param key 键
    • @param delta 要增加几(大于0)
    • @return
      */
      public long incr(String key, long delta){
      if(delta<0){
      throw new RuntimeException(“递增因子必须大于0”);
      }
      return redisTemplate.opsForValue().increment(key, delta);
      }

    /**

    • 递减
    • @param key 键
    • @param delta 要减少几(小于0)
    • @return
      */
      public long decr(String key, long delta){
      if(delta<0){
      throw new RuntimeException(“递减因子必须大于0”);
      }
      return redisTemplate.opsForValue().increment(key, -delta);
      }

    //Map=
    /**

    • HashGet
    • @param key 键 不能为null
    • @param item 项 不能为null
    • @return 值
      */
      public Object hget(String key,String item){
      return redisTemplate.opsForHash().get(key, item);
      }

    /**

    • 获取hashKey对应的所有键值
    • @param key 键
    • @return 对应的多个键值
      */
      public Map hmget(String key){
      return redisTemplate.opsForHash().entries(key);
      }

    /**

    • HashSet
    • @param key 键
    • @param map 对应多个键值
    • @return true 成功 false 失败
      */
      public boolean hmset(String key, Map map){
      try {
      redisTemplate.opsForHash().putAll(key, map);
      return true;
      } catch (Exception e) {
      e.printStackTrace();
      return false;
      }
      }

    /**

    • HashSet 并设置时间
    • @param key 键
    • @param map 对应多个键值
    • @param time 时间(秒)
    • @return true成功 false失败
      */
      public boolean hmset(String key, Map map, long time){
      try {
      redisTemplate.opsForHash().putAll(key, map);
      if(time>0){
      expire(key, time);
      }
      return true;
      } catch (Exception e) {
      e.printStackTrace();
      return false;
      }
      }

    /**

    • 向一张hash表中放入数据,如果不存在将创建
    • @param key 键
    • @param item 项
    • @param value 值
    • @return true 成功 false失败
      */
      public boolean hset(String key,String item,Object value) {
      try {
      redisTemplate.opsForHash().put(key, item, value);
      return true;
      } catch (Exception e) {
      e.printStackTrace();
      return false;
      }
      }

    /**

    • 向一张hash表中放入数据,如果不存在将创建
    • @param key 键
    • @param item 项
    • @param value 值
    • @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
    • @return true 成功 false失败
      */
      public boolean hset(String key,String item,Object value,long time) {
      try {
      redisTemplate.opsForHash().put(key, item, value);
      if(time>0){
      expire(key, time);
      }
      return true;
      } catch (Exception e) {
      e.printStackTrace();
      return false;
      }
      }

    /**

    • 删除hash表中的值
    • @param key 键 不能为null
    • @param item 项 可以使多个 不能为null
      */
      public void hdel(String key, Object… item){
      redisTemplate.opsForHash().delete(key,item);
      }

    /**

    • 判断hash表中是否有该项的值
    • @param key 键 不能为null
    • @param item 项 不能为null
    • @return true 存在 false不存在
      */
      public boolean hHasKey(String key, String item){
      return redisTemplate.opsForHash().hasKey(key, item);
      }

    /**

    • hash递增 如果不存在,就会创建一个 并把新增后的值返回
    • @param key 键
    • @param item 项
    • @param by 要增加几(大于0)
    • @return
      */
      public double hincr(String key, String item,double by){
      return redisTemplate.opsForHash().increment(key, item, by);
      }

    /**

    • hash递减
    • @param key 键
    • @param item 项
    • @param by 要减少记(小于0)
    • @return
      */
      public double hdecr(String key, String item,double by){
      return redisTemplate.opsForHash().increment(key, item,-by);
      }

    //set=
    /**

    • 根据key获取Set中的所有值
    • @param key 键
    • @return
      */
      public Set sGet(String key){
      try {
      return redisTemplate.opsForSet().members(key);
      } catch (Exception e) {
      e.printStackTrace();
      return null;
      }
      }

    /**

    • 根据value从一个set中查询,是否存在
    • @param key 键
    • @param value 值
    • @return true 存在 false不存在
      */
      public boolean sHasKey(String key,Object value){
      try {
      return redisTemplate.opsForSet().isMember(key, value);
      } catch (Exception e) {
      e.printStackTrace();
      return false;
      }
      }

    /**

    • 将数据放入set缓存
    • @param key 键
    • @param values 值 可以是多个
    • @return 成功个数
      */
      public long sSet(String key, Object…values) {
      try {
      return redisTemplate.opsForSet().add(key, values);
      } catch (Exception e) {
      e.printStackTrace();
      return 0;
      }
      }

    /**

    • 将set数据放入缓存
    • @param key 键
    • @param time 时间(秒)
    • @param values 值 可以是多个
    • @return 成功个数
      */
      public long sSetAndTime(String key,long time,Object…values) {
      try {
      Long count = redisTemplate.opsForSet().add(key, values);
      if(time>0) {
      expire(key, time);
      }
      return count;
      } catch (Exception e) {
      e.printStackTrace();
      return 0;
      }
      }

    /**

    • 获取set缓存的长度
    • @param key 键
    • @return
      */
      public long sGetSetSize(String key){
      try {
      return redisTemplate.opsForSet().size(key);
      } catch (Exception e) {
      e.printStackTrace();
      return 0;
      }
      }

    /**

    • 移除值为value的
    • @param key 键
    • @param values 值 可以是多个
    • @return 移除的个数
      */
      public long setRemove(String key, Object …values) {
      try {
      Long count = redisTemplate.opsForSet().remove(key, values);
      return count;
      } catch (Exception e) {
      e.printStackTrace();
      return 0;
      }
      }
      //=list===

    /**

    • 获取list缓存的内容
    • @param key 键
    • @param start 开始
    • @param end 结束 0 到 -1代表所有值
    • @return
      */
      public List lGet(String key, long start, long end){
      try {
      return redisTemplate.opsForList().range(key, start, end);
      } catch (Exception e) {
      e.printStackTrace();
      return null;
      }
      }

    /**

    • 获取list缓存的长度
    • @param key 键
    • @return
      */
      public long lGetListSize(String key){
      try {
      return redisTemplate.opsForList().size(key);
      } catch (Exception e) {
      e.printStackTrace();
      return 0;
      }
      }

    /**

    • 通过索引 获取list中的值
    • @param key 键
    • @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
    • @return
      */
      public Object lGetIndex(String key,long index){
      try {
      return redisTemplate.opsForList().index(key, index);
      } catch (Exception e) {
      e.printStackTrace();
      return null;
      }
      }

    /**

    • 将list放入缓存
    • @param key 键
    • @param value 值
    • @return
      */
      public boolean lSet(String key, Object value) {
      try {
      redisTemplate.opsForList().rightPush(key, value);
      return true;
      } catch (Exception e) {
      e.printStackTrace();
      return false;
      }
      }

    /**

    • 将list放入缓存
    • @param key 键
    • @param value 值
    • @param time 时间(秒)
    • @return
      */
      public boolean lSet(String key, Object value, long time) {
      try {
      redisTemplate.opsForList().rightPush(key, value);
      if (time > 0) {
      expire(key, time);
      }
      return true;
      } catch (Exception e) {
      e.printStackTrace();
      return false;
      }
      }

    /**

    • 将list放入缓存
    • @param key 键
    • @param value 值
    • @return
      */
      public boolean lSet(String key, List value) {
      try {
      redisTemplate.opsForList().rightPushAll(key, value);
      return true;
      } catch (Exception e) {
      e.printStackTrace();
      return false;
      }
      }

    /**

    • 将list放入缓存
    • @param key 键
    • @param value 值
    • @param time 时间(秒)
    • @return
      */
      public boolean lSet(String key, List value, long time) {
      try {
      redisTemplate.opsForList().rightPushAll(key, value);
      if (time > 0) {
      expire(key, time);
      }
      return true;
      } catch (Exception e) {
      e.printStackTrace();
      return false;
      }
      }

    /**

    • 根据索引修改list中的某条数据
    • @param key 键
    • @param index 索引
    • @param value 值
    • @return
      */
      public boolean lUpdateIndex(String key, long index,Object value) {
      try {
      redisTemplate.opsForList().set(key, index, value);
      return true;
      } catch (Exception e) {
      e.printStackTrace();
      return false;
      }
      }

    /**

    • 移除N个值为value
    • @param key 键
    • @param count 移除多少个
    • @param value 值
    • @return 移除的个数
      */
      public long lRemove(String key,long count,Object value) {
      try {
      Long remove = redisTemplate.opsForList().remove(key, count, value);
      return remove;
      } catch (Exception e) {
      e.printStackTrace();
      return 0;
      }
      }

}

```

自动装配并使用

    @Autowired
    private RedisUtils redisUtils;

redis狂神说笔记超详细(总结不易)_第62张图片

十.redis配置文件详解

启动时通过配置文件来启动

redis狂神说笔记超详细(总结不易)_第63张图片
redis狂神说笔记超详细(总结不易)_第64张图片
redis狂神说笔记超详细(总结不易)_第65张图片

redis狂神说笔记超详细(总结不易)_第66张图片

redis狂神说笔记超详细(总结不易)_第67张图片
redis狂神说笔记超详细(总结不易)_第68张图片
redis狂神说笔记超详细(总结不易)_第69张图片

十一.持久化之RDB(Redis DataBase)

面试和工作的重点
redis是内存数据库,如果不将内存中的数据保存到磁盘,那么一旦服务器进程退出,服务器中的数据状态也会消失,所以redis提供了持久化的功能
原理是redis会单独创建(fork)一个与当前线程一模一样的子进程来进行持久化,这个子线程的所有数据(变量。环境变量,程序程序计数器等)都和原进程一模一样,会先将数据写入到一个临时文件中,待持久化结束了,再用这个临时文件替换上次持久化好的文件,整个过程中,主进程不进行任何的io操作,这就确保了极高的性能。
我们默认的就是RDB,一般情况下不需要修改这个配置
如果需要进行大规模数据恢复,且对于数据的完整性回复不是非常的敏感,那么RDB方式比AOF的方式更加高效。RDb的缺点是最后一次持久化的数据可能会丢失
rdb保存的文件是dump.rdb

搜索snapshot
redis狂神说笔记超详细(总结不易)_第70张图片

redis狂神说笔记超详细(总结不易)_第71张图片
删除dump.rdb文件
redis狂神说笔记超详细(总结不易)_第72张图片
一分钟内设置5次key,自动生成dump.rdb文件
redis狂神说笔记超详细(总结不易)_第73张图片
redis狂神说笔记超详细(总结不易)_第74张图片

redis狂神说笔记超详细(总结不易)_第75张图片
进程被占用,重启云服务器

触发机制
1.满足save规则
2.执行flushdb命令
3.退出redis,都会产生rdb文件

如何恢复rdb文件
1.只需要将rdb文件放在redis启动目录就可以,redis启动时就会自动检测dum.rdb文件,恢复其中数据
2.rdb文件存放的位置

redis狂神说笔记超详细(总结不易)_第76张图片
优点:
1.适合大规模的数据恢复
2.对数据的完整性要求不高
缺点
1.需要一定时间间隔进程操作,如果redis意外宕机,这个最后一次修改的数据就没了
2.fork进程的时候,会占用一定的内存空间

因此有时候生产环境会将这个文件备份

十二.持久化操作之aof(append only file)

追加文件
将我们所以的命令都记录下来,回复的时候将这个文件全部执行一遍

以日志的形式来记录每一个写操作,将Redis执行过的所有写指令记录下来(读操作不记录),只许追加文件不许改写文件,redis重启之处会会读取该文件重新构建数据,inother words,如果redis重启就会根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。

aof保存的文件是appendonly.aof

默认为no需要手动进行开启
redis狂神说笔记超详细(总结不易)_第77张图片

重启redis后读取.aof文件
redis狂神说笔记超详细(总结不易)_第78张图片

redis狂神说笔记超详细(总结不易)_第79张图片

如果aof文件有错位,这时候redis启动不起来,我们需要修复配置文件
redis给我们提供了一个工具

redis-check-aof --fix appendonly.aof
优点:
1.每一次修改都同步,文件的完整性会更好
2.每秒同步一次可能会丢失一秒的数据
3.从不同不效率最高
缺点
1.对于数据文件来说,aof’远大于rdb,修复速度比rdb慢
2.aof运行效率要比rdb慢,所以redis的默认配置是rdb持久化。

redis狂神说笔记超详细(总结不易)_第80张图片

aof默认是文件无线追加,文件会越来越大
如果aof文件大于64m,fork一个新进程将我们的文件进行重写
redis狂神说笔记超详细(总结不易)_第81张图片

只做缓存,如果你只希望数据在服务器运行的时候存在,你可以不使用持久化

十三.redis订阅发布

redis发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接受消息。微信,微博,关注系统
redis客户端可以订阅任意数量的频道
订阅/发布消息图
第一个:消息发送者
第二个:频道
第三个:消息订阅者

订阅频道
在这里插入图片描述
发布消息
在这里插入图片描述
接收到消息
redis狂神说笔记超详细(总结不易)_第82张图片

原理:

1.实时消息系统(注册相当于订阅系统频道,收到系统信息)
2.实时聊天(频道当作聊天室,将信息回显给所有人即可)
3.订阅关注系统
稍微复杂的系统我们会使用消息中间件MQ

十四.redis主从复制

主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(master/leader),后者称为从节点(slave/follower);数据的复制是单向的,只能由主节点到从节点
默认情况下,每台Redis服务器都是主节点;且一个主节点可以有多个从节点(或没有从节点),但一个从节点只能有一个主节点。

redis狂神说笔记超详细(总结不易)_第83张图片
master处理写请求,slave处理读请求
主从复制,读写分离,80%的情况下都在进行读操作!减缓服务器压力,架构中经常使用
最低一主二从

主从复制的作用主要包括:

数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余的方式。
故障恢复:当主节点故障时,从节点可以暂时替代主节点提供服务,是一种服务冗余的方式
负载均衡:在主从复制的基础上,配合读写分离,由主节点进行写操作,从节点进行读操作,分担服务器的负载;尤其是在多读少写的场景下,通过多个从节点分担负载,提高并发量。
高可用(集群)基石:主从复制还是哨兵和集群能够实施的基础。
一般来说,要将Redis运用于工程项目中,只使用一台Redis是万万不能的(宕机),原因如下:
1、从结构上,单个Redis服务器会发生单点故障,并且一台服务器需要处理所有的请求负载,压力较 大;

2、从容量上,单个Redis服务器内存容量有限,就算一台Redis服务器内存容量为256G,也不能将所有 内存用作Redis存储内存,一般来说,单台Redis大使用内存不应该超过20G

电商网站上的商品,一般都是一次上传,无数次浏览的,说专业点也就是"多读少写"。

对于这种场景,我们可以使如下这种架构:
redis狂神说笔记超详细(总结不易)_第84张图片

在公司的中,主从复制必须要使用,因为在真实项目中不可能单机使用redis!

环境配置

只配置从库,不用配置主库
查看当前库的信息

127.0.0.1:6379> info replication//查看当前库的信息
# Replication
role:master//角色
connected_slaves:0//没有从机
master_failover_state:no-failover
master_replid:2987407e2725e84643b9b79b471ccd0e6fda32c4
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

复制配置文件,开启四个会话,模拟单机多服务环境
正常情况使用多机多服务
redis狂神说笔记超详细(总结不易)_第85张图片

修改端口
在这里插入图片描述
修改为后台启动
在这里插入图片描述

在这里插入图片描述
修改日志

redis狂神说笔记超详细(总结不易)_第86张图片

修改rdb文件名
redis狂神说笔记超详细(总结不易)_第87张图片
启动三个redis服务,可以通过进程信息查看
redis狂神说笔记超详细(总结不易)_第88张图片

主从复制之复制原理

默认情况下,每台Redis服务器都是主节点
默认情况下三台都是主机,我们一般情况下只要配置从机!认老大
一主(79)二从(80,81)
redis狂神说笔记超详细(总结不易)_第89张图片
给从机配主人
redis狂神说笔记超详细(总结不易)_第90张图片
主机有slave信息
redis狂神说笔记超详细(总结不易)_第91张图片
在这里插入图片描述
真实的主从配置应该在配置文件中配置,这样的话是永久的,我们这里使用的命令是暂时的
authorize,配置文件配置地点
redis狂神说笔记超详细(总结不易)_第92张图片

细节

主机可以设置值(可以写),从机不能(只能读)
主机中所有的数据都会被从机自动保存
主机写
redis狂神说笔记超详细(总结不易)_第93张图片
从机读,但是不能写

redis狂神说笔记超详细(总结不易)_第94张图片

主机断开连接,从机依旧能能连接到主机,但是没有写操作,这个时候,主机如果回来,从机依旧可以以获取主机信息
如果是使用命令行配置的主从,如果从机重启就会自动变为主机,只要变为从机,立马会从主机中获取值

复制原理

slave启动成功连接到一个master后会发送一个sync同步命令
master接受到命令,启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令,在后台进程执行完毕后,master将传送整个数据文件到slave,并完成一次完全同步。
全量复制:slave服务在接受到数据库文件数据后,将其存盘并加载到内存中。
增量复制:Master继续将所收集到的修改命令一次传给slave,完成同步
但是只要是重新连接一次master,全量复制会被自动执行,我们的数据可以在从机中看到

宕机后手动配置主机

即做主机又做从机,此时依旧为slave
redis狂神说笔记超详细(总结不易)_第95张图片
设置6380为主节点
redis狂神说笔记超详细(总结不易)_第96张图片
6080节点信息,依旧为从机
在这里插入图片描述
谋朝篡位
当主机宕机对从机执行

slave no one

从机变主机

哨兵模式(自动选取老大)

主从切换的方法是:当主服务器宕机后,需要手动把一台服务器切换为主服务器,需要人工干预,费时费力,还会造成一段时间内服务器不可用。更多的时候我们考虑的是哨兵模式,redis从2.8开始正式提供了sentinel架构来解决这个问题。
谋朝篡位的自动版,能够后台监控主机是否故障,如果故障根据投票数自动将从库转换为主库。
哨兵模式是一种特殊的模式,首先redis提供了哨兵命令,哨兵是一个独立的进程,作为进程,他会独立运行,其原理是哨兵通过发送命令,等待redis服务器响应,从而监控运行的多个redis实例

redis狂神说笔记超详细(总结不易)_第97张图片
哨兵的作用
1.通过发送命令,让redis返回其运行状态,包括主服务器和从服务器
2.当哨兵检测到master宕机,会自动将slave切换成master,然后通过发布订阅模式通知其他从服务器修改配置文件,让他们切换为主机
然而一个哨兵进程对redis服务器进行监控,可能会出现问题,为此,我们可以使用多个哨兵进行监控,各个哨兵相互监控,这样就形成了多哨兵模式。
redis狂神说笔记超详细(总结不易)_第98张图片
假设主服务器宕机,哨兵1先检测到这个结果,系统并不会立马进行failover过程,仅仅是哨兵1主观的认为主服务器不可用,这个现象称为主观下线。当后面的哨兵也检测到主服务器不可用,并且达到一定数量,那么哨兵之间会进行一次投票,投票的结果由一个哨兵发起,进行failover(故障转移操作)。q切换成功后,通过发布订阅模式,让各个哨兵把自己的监控的从服务器实现切换主机,这个过称为客观下线
测试
1.配置哨兵配置文件,名字不能错
redis狂神说笔记超详细(总结不易)_第99张图片
myredis:被监控的名称
1:代表主机挂了,slave投票看让谁成为主机
在这里插入图片描述
2.配置完成,启动哨兵

在这里插入图片描述
查看哨兵日志

redis狂神说笔记超详细(总结不易)_第100张图片
如果主机此时回来,只能归并到新的主机下,当作从机,这就是哨兵模式的规则。

优点:
1.哨兵集群,基于主从复制模式,所有主从配置的优点他都有
2.主从可以切换,故障可以转移,系统可用性会更好
3.哨兵模式就是主从模式的升级,手动到自动,更加健壮
缺点:
1.redis不好在线扩容,集群容量一旦到达上限,在线扩容就十分麻烦
2.实现哨兵模式的配置其实是很麻烦的,里面有很多选择

哨兵模式的全部配置
。。。。。。。省略

这些配置一般是由运维配置

redis缓存穿透和雪崩(面试高频,工作常用)

redis狂神说笔记超详细(总结不易)_第101张图片

缓存穿透(查不到)

概念
用户想要查询一个数据,发现redis内存数据库没有,也就是缓存没有命中,于是向持久层数据库查询。发现也没有,于是本次查询失败,当用户很多的时候,缓存都没有命中(秒杀),于是都去请求了持久层数据库。这就会给持久层数据库造成大压力,这时候就相当于出现了缓存穿透。

解决方案

布隆过滤器
布隆过滤器是一种数据结构,对所有可能查询的参数以hash形式存储,先在控制层进行校验,不符合规则丢弃,从而避免了对底层存储系统的查询压力
缓存空对象
当存储层不命中后,即使返回空对象也将其缓存起来,同时会设置一个过期时间,之后在访问这个数据将会从缓存中获取,保护了后端的数据源
但是这个方法存在两个问题
1.如果空值能被缓存下来,这就意味着缓存需要更多的空间存储更多的键,因为这个当中可能会有很多的空值和键
2.即使对空值设置了过期时间,还会存在缓存层和存储层的数据会有一段时间窗口不一致,这对于需要保持一致性的业务会有影响

缓存击穿(查的多,过期)

微博服务器宕机(热点)
缓存击穿是指一个key非常热点,不停的扛着大量并发,大并发集中对这个点进行访问,当key在这个瞬间失效,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。
当某个key在过期的瞬间,有大量请求并发访问,这类数据一般是热点数据,由于缓存过期,会同时访问数据库来查询最新的数据,并且回写缓存,会导致数据库压力瞬间过大。

缓存雪崩

是指在某个时间段,缓存集中过期失效。redis宕机
缓存雪崩的原因之一,比如写文本时,马上就要到双十二零点,很快就迎来一波抢购,这波商品比较集中的放入了缓存假设缓存缓存一个小时。到凌晨一点钟,这批商品的缓存就都过期了。而对于这批商品的查询访问都落到数据库上,数据库产生周期性的压力波值。于是所有的请求都会达到存储层,存储层的调用量就会爆增,造成存储器也会挂掉的情况。
其实集中过期不是最致命的,比较致命的缓存雪崩是缓存服务器某个节点宕机或者断网。因为自然原因形成的缓存雪崩,一定是在某个时间段集中创建缓存,这个时候数据库也会顶住压力,无非是对数据库产生周期性的压力而已。而缓存服务节点的宕机,对数据库服务器造成的压力是不可预知的,很有可能瞬间就把数据库压垮。
双十一:停到一些服务。如退款(保证主要服务可用)
解决方案
1.redis高可用
2.限流降级(springcloud讲过)
3.数据预热

你可能感兴趣的:(笔记)