Redis学习笔记(B站狂神说)(自己总结方便复习)

Redis学习笔记B站狂神说

redis: 非关系型数据库

一.NoSQL概述

1.为什么要用Nosql

1.单机Mysql的年代
Redis学习笔记(B站狂神说)(自己总结方便复习)_第1张图片

思考一下,这种情况下:整个网站的瓶颈是什么?

1.数据量如果太大,一个机器放不下

2.数据的索引(B+Tree),一个机器内存也放不下

3.访问量(读写混合),一个服务器承受不了~

只要你开始出现以上三种情况之一,name你就必须要晋级!

2.Memcached(缓存) + Mysql +垂直拆分(读写分离)

网站80%的情况都是在读,每次都要去查询数据库的话十分麻烦!所以说我们希望减轻数据的压力,我们可以使用缓存来保证效率!

发展过程: 优化数据结构和索引–>文件缓存(io)–>Memcached(当时最热门的技术!)

Redis学习笔记(B站狂神说)(自己总结方便复习)_第2张图片

3.分库分表+水平拆分+MySQL集群

本质:数据库(读.写)

早些年

MyISAM:表锁,十分影响效率!高并发下就会出现严重的锁问题

Innodb: 行锁.

慢慢的就开始使用分库分表来解决写的压力!MySQL在那个年代退出了表分区!这个并没有多少公司使用!

Mysql的集群,很好满足了那个年代的所有需求!

Redis学习笔记(B站狂神说)(自己总结方便复习)_第3张图片

4.如今最近的年代

Redis学习笔记(B站狂神说)(自己总结方便复习)_第4张图片

5.目前一个基本的互联网项目

Redis学习笔记(B站狂神说)(自己总结方便复习)_第5张图片

为什么要NoSQL!

用户的个人信息,社交网路,地理位置.用户自己产生的数据,用户日志等等爆发式增长

这时候我们就需要NoSQL数据库,Nosql可以很好的处理以上的情况!

2.什么是NoSQL

NoSQL

NoSQL = not only SQL(不仅仅是SQL)

泛指非关系型数据库的,随着web2.0互联网的诞生!传统的关系型数据库很难对付web2.0时代!尤其是超大规模的高并发的社区!暴露出来很多难以克服的问题,NoSQL在当今大数据环境下发展得十分迅速,Redis是发展最快的,而且我们当下需要掌握的一个技术!

很多的数据类型用户的个人信息,社交网络,地理位置,这些数据类型的存储不需要一个固定的格式!不需要多余的操作就可以横向扩展的! Map 使用键值对控制!

NoSQL特点

解耦!

1.方便扩展(数据之间没有关系,很好扩展!)

2.大数据量高性能(Redis 一秒写8万次,读取11万,CoSQL的缓存记录,是一个细粒度的缓存,性能会比较高!)

3.数据类型是多样性的!(不需要事先设计数据库!随取随用!如果是数据十分大的表,很多人就无法设计了!)

4.传统RDBMS和NoSQL()

传统的RDBMS
- 结构化组织
- sql
- 数据和关系都存在单独的表中
- 操作,数据定义语言
- 严格的一致性
- 基础的事务
....
Nosql
- 不仅仅是数据
- 没有固定的查询语言
- 键值对存储,列存储,文档存储,图形数据库(社交关系)
- 最终一致性
- CAP定理和BASE (异地多活) 
- 高性能,高可用,高可扩
- ...

了解3V + 3高

大数据时代的3V:主要是描述问题的

  1. 海量的Volume
  2. 多样的Variety
  3. 实时Velocity

大数据时代的3高:主要是对程序的要求

  1. 高并发
  2. 高可扩
  3. 高性能

真正在公司中的实践: NoSQL + RDBMS一起使用才是最强的,阿里巴巴的架构演进!

3.阿里巴巴演进

Redis学习笔记(B站狂神说)(自己总结方便复习)_第6张图片

“IOE”这三样标配:

I(IBM,服务器提供商,他们提供的服务器俗称“小型机”)

O(Oracle,数据库提供商,他们的软件是著名的“甲骨文商业数据库”)

E(EMC,存储设备提供商,他们提供的是“集中式存储”)

4.NoSQL的四大分类

KV键值对

  • 新浪 : Redis
  • 美团 : Redis + Tair
  • 阿里 . 百度 : Redis + memecache

文档型数据库(bson格式 和json一样)

  • MongoDB (一般必须掌握)
    • MongoDB是一个基于分布式文件存储的数据库,C++编写,主要来处理大量的文档!
    • MongoDB是一个介于关系型数据库和非关系型数据中中间的产品! MongoDB 是非关系型数据库中功能最丰富的,最像关系型数据库的!
  • ConthDB

列存储数据库

  • HBase
  • 分布式文件系统

图关系数据库

Redis学习笔记(B站狂神说)(自己总结方便复习)_第7张图片

他不是存图形的,放的是关系,比如朋友圈社交网络,广告推荐!

  • Neo4j,InfoGrid;

四者对比

Redis学习笔记(B站狂神说)(自己总结方便复习)_第8张图片

二.Redis入门

1.概述

Redis 是什么?

Redis (Remove Dictionary Server) 即远程字典服务

是一个开源的使用ANSI C语言编写,支持网络,可基于内存亦可持久化的日志型,Key-Value数据库,并提供多种语言的API

Redis学习笔记(B站狂神说)(自己总结方便复习)_第9张图片

redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。

免费和开源,是当下最热门的NoSQL技术之一!也被人们称之为结构化数据库!

Redis 能干什么?

  1. 内存存储,持久化,内存中是断电既失,所以说持久化很重要(rdb,aof)
  2. 效率高,可以用于高速缓存
  3. 发布订阅系统
  4. 地图信息分析
  5. 计时器,计数器(浏览量!)

特性

  1. 多样的数据类型
  2. 持久化
  3. 集群
  4. 事务

学习中需要用到的东西

  1. 官网: https://redis.io/
  2. 中文网: http://www.redis.cn/
  3. Redis学习笔记(B站狂神说)(自己总结方便复习)_第10张图片

注意: windows在Github上下载(停更很久了)

Redis推荐都是在Linux服务器上搭建的,我们是基于Linux学习的!

windows太老了就不下了

2.Linux下载安装

外网面板地址: http://123.173.106.102:8888/68f08b2c
内网面板地址: http://192.168.189.128:8888/68f08b2c
username: ihut3yud
password: 78899d11
If you cannot access the panel,
release the following panel port [8888] in the security group
若无法访问面板,请检查防火墙/安全组是否有放行面板[8888]端口

1.首先下载安装包redis-6.0.8.tar.gz

官网:https://redis.io/

Redis学习笔记(B站狂神说)(自己总结方便复习)_第11张图片

2.解压redis的安装包tar -zxvf redis-6.0.8.tar.gz

程序都放在/opt文件下

Redis学习笔记(B站狂神说)(自己总结方便复习)_第12张图片

在这里插入图片描述

3.进入解压后的文件,我们可以看到redis的配置文件

在这里插入图片描述

4.基本的环境安装

yum install gcc-c++
(出现pid锁定问题执行命令`rm -f /var/run/yum.pid
`)
make
(make出现错误看我博文
https://blog.csdn.net/qq_43649223/article/details/109118117)
make install# 两次make确认执行完毕

Redis学习笔记(B站狂神说)(自己总结方便复习)_第13张图片

5.redis的默认安装路径/usr/local/bin

在这里插入图片描述

6.将redis配置文件,复制到我们当前文件(防止发生意外备份一下)

当前目录:/usr/local/bin

Redis学习笔记(B站狂神说)(自己总结方便复习)_第14张图片

之后就用这个配置文件启动redis

7.配置redis.conf(默认不是后台启动修改配置文件)

进入redis.conf

Redis学习笔记(B站狂神说)(自己总结方便复习)_第15张图片

8.启动redis服务

通过指定的配置文件启动服务

redis-server Yuconfig/redis.conf

redis-cli -p 6379

ping出现pong成功

set name 名字设置名字

keys *查看所有信息

Redis学习笔记(B站狂神说)(自己总结方便复习)_第16张图片

9.使用redis-cli进行连接测试

``redis-cli -p 6279`

10.查看redis进程是否开启

Redis学习笔记(B站狂神说)(自己总结方便复习)_第17张图片

将这个终端放在这,再开一个终端

执行ps -ef|gerp redis命令

查看redis进程是否开启

Redis学习笔记(B站狂神说)(自己总结方便复习)_第18张图片

11.如何关闭redis服务?

shutdown关闭

exit退出

在这里插入图片描述

12.再次查看redis进程是否关闭

ps -ef|grep redis

在这里插入图片描述

3.性能测试

redis-benchmark是一个压力测试工具!

官方自带的性能测试工具

redis-benchmark 命令参数

Redis学习笔记(B站狂神说)(自己总结方便复习)_第19张图片

# 测试: 100个并发测试 100000请求
redis-benchmark -h localhost -p 6370 -c 100 -n 100000

Redis学习笔记(B站狂神说)(自己总结方便复习)_第20张图片

Redis学习笔记(B站狂神说)(自己总结方便复习)_第21张图片

4.基础知识

redis默认有16个数据库

默认使用的是第0个

可以使用select进行切换

127.0.0.1:6379> select 3  # 选择数据库
OK
127.0.0.1:6379[3]> get name # 显示名字
(nil)
127.0.0.1:6379[3]> set name codeyuaiiao # 设置名字
OK
127.0.0.1:6379[3]> get name # 显示名字
"codeyuaiiao"
127.0.0.1:6379[5]> DBSIZE # 查看DB大小
(integer) 0

Redis学习笔记(B站狂神说)(自己总结方便复习)_第22张图片

keys *(查看数据库所有key)

在这里插入图片描述

flushall 清除所有数据库

flushdb清除当前数据库

Redis学习笔记(B站狂神说)(自己总结方便复习)_第23张图片

为什么redis端口号是6379 ? 人名-手机-对应数字

redis是单线程的!

明白Redis是很快的,官方表示,redis是基于内存操作的,cpu不是redis性能瓶颈,redis的瓶颈是根据机器的内存和网络带宽,既然可以使用单线程来实现,就是用单线程了! 所以就使用单线程了!

Redis是C语言写的,官方提供的数据为10万+的QPS,完全不比同样使用Key-value的Memecache差!

Redis为什么单线程还这么快?

1.误区: 高性能的服务器一定是多线程的?

2.误区: 多线程(CPU上下文切换) 一定比单线程效率高

CPU>内存>硬盘速度(juc)

核心: redis是将所有的数据全部放在内存中的,所以说使用单线程去操作效率就是最高的,多线程(CPU上下文会切换:耗时的操作!!!) ,对于内存系统来说,如果没有上下文切换效率就是最高的! 多次读写对都是在一个CPU上的,在内存情况下,这个就是最佳的方案

三.五大数据类型

Redis学习笔记(B站狂神说)(自己总结方便复习)_第24张图片

Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库缓存消息中间件。 它支持多种类型的数据结构,如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 与范围查询, bitmaps, hyperloglogs 和 地理空间(geospatial) 索引半径查询。 Redis 内置了 复制(replication),LUA脚本(Lua scripting), LRU驱动事件(LRU eviction),事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)。

Redis-Key

1.exists name # 判断当前key是否存在 存在为1 反之为0

2.move name # 移除当前key

3.expire name 10 # 设置key的过期时间,单位是秒

4.ttl name # 查看当前key的剩余时间

5.type name # 查看key的类型

exists name # 判断当前key是否存在 存在为1 反之为0
(integer)1
move name 1 # 移除当前key 1代表当前数据库
(integer)1 
expire name 10 # 设置key的过期时间,单位是秒
(integer)1 
ttl name # 查看当前key的剩余时间
3
type name # 查看key的类型
String

不会的命令官网直接搜索

在这里插入图片描述

String

1.append name “shanjiao” # 追加字符串,如果当前key不存在相当于setkey

2.strlen name # 查看指定key的长度

3.incr views # views自增1

4.decr views # views自减1

5.incrby views 10 # 自定义设置自增步长

6.decrby views 5 # 自定义设置自减步长

#############################################################
127.0.0.1:6379> set name codeyuaiiao # 设置key
OK
127.0.0.1:6379> get name # 查看key
"codeyuaiiao"
127.0.0.1:6379> exists name # 判断某一个key是否存在
(integer) 1
127.0.0.1:6379> append name "shanjiao" # 追加字符串,如果当前key不存在相当于setkey
(integer) 19
127.0.0.1:6379> get name
"codeyuaiiaoshanjiao"
127.0.0.1:6379> strlen name # 查看指定key的长度
(integer) 19
127.0.0.1:6379> 
#############################################################
127.0.0.1:6379> set views 0 #设置views为0  初始化浏览量
OK
127.0.0.1:6379> get views
"0"
127.0.0.1:6379> incr views  # 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> decr views	# views自减1
(integer) 2
127.0.0.1:6379> decr views
(integer) 1
127.0.0.1:6379> decr views
(integer) 0
127.0.0.1:6379> decr views
(integer) -1
127.0.0.1:6379> incrby views 10 # 自定义设置自增步长
(integer) 9
127.0.0.1:6379> incr views
(integer) 10
127.0.0.1:6379> decrby views 5 # 自定义设置自减步长
(integer) 5
127.0.0.1:6379> 
#######################################################
# 字符串范围 range
127.0.0.1:6379> set name zhangyinjie
OK
127.0.0.1:6379> get name
"zhangyinjie"
127.0.0.1:6379> getrange name 0 5  # 截取字符串
"zhangy"
127.0.0.1:6379> getrange name 0 -1 # 获取所有字符串
"zhangyinjie"

# 替换
127.0.0.1:6379> set name2 codeyuaiiao
OK
127.0.0.1:6379> get name2
"codeyuaiiao"
127.0.0.1:6379> setrange name2 1 qwe # 替换字符串
(integer) 11
127.0.0.1:6379> get name2
"cqweyuaiiao"
127.0.0.1:6379> 
###########################################################
# setex (set with expire) #设置过期时间
# setnx (set if not exist) # 不存在再设置 (在分布式锁中会常常使用!)
127.0.0.1:6379> setex name 30 "hello codeyuaiiao" #设置name的值为hello codeyuaiiao 30秒后过期
OK
127.0.0.1:6379> ttl name # 查看过期时间
(integer) 21
127.0.0.1:6379> setnx mykey "Hello yuaiiao" # 如果mykey不存在,创建mykey
(integer) 1
127.0.0.1:6379> get mykey
"Hello yuaiiao"
127.0.0.1:6379> keys *
1) "mykey"
127.0.0.1:6379> setnx mykey "Hello" # mykey存在 创建失败
(integer) 0
127.0.0.1:6379> get mykey
"Hello yuaiiao"
127.0.0.1:6379> 
##########################################################
# mset # 批量设置key
# mget # 批量获取key值
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 k5 v5 # msetnx 是一个原子性的操作,要么一起成功,要么一起失败
(integer) 0
127.0.0.1:6379> get k5
(nil)
127.0.0.1:6379> 
#############################################################
# 对象
set user:1 {
     name:zhangsan,age:3} # 设置一个user:1 对象 的值为json字符来保存一个对象
# 这里的key是一个巧妙的设计: user:{id} {filed},如此设计在redis中完全可以

127.0.0.1:6379> set user:1 {
     name:zhangsan,age:3}
OK
127.0.0.1:6379> get user:1
"{name:zhangsan,age:3}"
127.0.0.1:6379> mset user:2:name zhaosi user:2:age 4
OK
127.0.0.1:6379> get user:2:name
"zhaosi"
127.0.0.1:6379> mget user:2:name user:2:age
1) "zhaosi"
2) "4"
127.0.0.1:6379> 
####################################################################
# getset # 先get再set
127.0.0.1:6379> getset name yuaiiao  # 如果不存在值 , 则返回nil
(nil)
127.0.0.1:6379> get name
"yuaiiao"
127.0.0.1:6379> getset name zhangsan # 如果存在值,显示原来的值,并且设置新的值
"yuaiiao"
127.0.0.1:6379> get name
"zhangsan"
127.0.0.1:6379> 

数据结构是相同的!

String类似的使用场景: value除了是我们的字符串还可以是我们的数字

  • 计数器
  • 统计多单位的数量
  • 粉丝数*
  • 对象缓存存储

List

在redis中,我们可以把list玩成,栈,队列,阻塞队列!

所有的list命令都是以 l 开头的

###########################################################
# lpush 左放
# rpush 右放
# L 左
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 # 获取list中的值
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> lrange list 0 1 # 通过区间获取list中的值
1) "three"
2) "two"
127.0.0.1:6379> 
# R 右
127.0.0.1:6379> rpush list four # 将一个值或多个值,插入到列表头部(右)
(integer) 4
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "two"
3) "one"
4) "four"
127.0.0.1:6379> 
###########################################################
# Lpop
# Rpop
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "two"
3) "one"
4) "four"
127.0.0.1:6379> Lpop list # 移除第一个
"three"
127.0.0.1:6379> Rpop list # 移除最后一个
"four"
127.0.0.1:6379> lrange list 0 -1 
1) "two"
2) "one"
127.0.0.1:6379> 
###########################################################
# Lindex
127.0.0.1:6379> lrange list 0 -1
1) "four"
2) "three"
3) "two"
4) "one"
127.0.0.1:6379> lindex list 1 # 通过下标获取list中某一个值
"three"
127.0.0.1:6379> lindex list 4
(nil)
127.0.0.1:6379> lindex list 3
"one"
127.0.0.1:6379> 
###########################################################
# Llen # 获取长度
127.0.0.1:6379> lrange list 0 -1
1) "four"
2) "three"
3) "two"
4) "one"
127.0.0.1:6379> llen list # 返回列表的长度
(integer) 4
127.0.0.1:6379> 
###########################################################
# Lrem移除指定的值
127.0.0.1:6379> lrange list 0 -1 
1) "four"
2) "four"
3) "three"
4) "two"
5) "one"
127.0.0.1:6379> lrem list 1 one # 移除list集合中指定个数的value,精确匹配
(integer) 1
127.0.0.1:6379> lrange list 0 -1
1) "four"
2) "four"
3) "three"
4) "two"
127.0.0.1:6379> lrem list 2 four
(integer) 2
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "two"
127.0.0.1:6379> 
###########################################################
# Ltrim 修剪 ; list截断
127.0.0.1:6379> lpush list hello
(integer) 1
127.0.0.1:6379> lpush list hello1
(integer) 2
127.0.0.1:6379> lpush list hello2
(integer) 3
127.0.0.1:6379> lpush list hello3
(integer) 4
127.0.0.1:6379> ltrim list 1 2 # 通过下标截取指定长度 这个list已经被改变了,只是剩下截取的值
OK
127.0.0.1:6379> lrange list 0 -1
1) "hello2"
2) "hello1"
127.0.0.1:6379> 
###########################################################
rpoplpush # 移除列表的最后一个元素,将它移动到新的列表中!

127.0.0.1:6379> lrange list 0 -1
1) "hello3"
2) "hello2"
3) "hello1"
127.0.0.1:6379> rpoplpush list newlist # 移除列表中的最后一个元素, 将它移动到新的列表中
"hello1"
127.0.0.1:6379> lrange list 0 -1 # 查看原来列表
1) "hello3"
2) "hello2"
127.0.0.1:6379> lrange newlist 0 -1 # 查看目标列表中,确实存在该值
1) "hello1"
127.0.0.1:6379> 
###########################################################
# lset # 将列表中指定下标的值替换为另一个值,更新操作

127.0.0.1:6379> exists list # 判断这个列表是否存在
(integer) 0
127.0.0.1:6379> lset list 0 test # 如果不存在列表我们去更新就会报错
(error) ERR no such key
127.0.0.1:6379> lpush list test
(integer) 1
127.0.0.1:6379> lset list 0 hello # 如果存在,更新当前下标的值
OK
127.0.0.1:6379> lrange list 0 0
1) "hello"
127.0.0.1:6379> lset list 1 test1 # 如果不存在就会报错
(error) ERR index out of range
127.0.0.1:6379> 
###########################################################
# Linsert # 将某个具体的value插入到列表中某个元素的前面或者后面

127.0.0.1:6379> rpush list one
(integer) 1
127.0.0.1:6379> rpush list two
(integer) 2
127.0.0.1:6379> lrange list 0 -1
1) "one"
2) "two"
127.0.0.1:6379> linsert list before "two" "hello"
(integer) 3
127.0.0.1:6379> lrange list 0 -1
1) "one"
2) "hello"
3) "two"
127.0.0.1:6379> linsert list after "two" "end"
(integer) 4
127.0.0.1:6379> lrange list 0 -1
1) "one"
2) "hello"
3) "two"
4) "end"
127.0.0.1:6379> 
###########################################################

小结

  • 他实际上是一个链表,before Bode after, left, right 都可以插入值
  • 如果key不存在,创建新的链表
  • 如果key存在,新增内容
  • 如果移除了所有值,空链表,也就代表不存在
  • 在两边插入或者改动值,效率最高!中间元素,相对俩手效率会低一点~

消息队列,消息排队!(Lpush Rpop) , 栈 (Lpush Lpop)!

Set(集合)

set中的值是不能重复的

###########################################################
# sadd 
# smembers
# sismember
127.0.0.1:6379> sadd list hello # set集合中添加元素
(integer) 1
127.0.0.1:6379> sadd list one
(integer) 1
127.0.0.1:6379> sadd list two
(integer) 1
127.0.0.1:6379> smembers list # 查看指定set的所有值
1) "two"
2) "hello"
3) "one"
127.0.0.1:6379> sismember list hello # 判断某一个值是不是在set集合中
(integer) 1
127.0.0.1:6379> sismember list end
(integer) 0
127.0.0.1:6379> 
###########################################################
# scard # 查看set集合中元素个数
# srem  # 移除set集合中的某个元素
127.0.0.1:6379> srem list hello # 移除set集合中的指定元素
(integer) 1
127.0.0.1:6379> scard list # 获取set集合中的内容元素个数
(integer) 2
127.0.0.1:6379> smembers list
1) "two"
2) "one"
127.0.0.1:6379> 
###########################################################
# srandmember # 随机抽选出一个元素

127.0.0.1:6379> smembers list # 随机抽选出一个元素
1) "codeyuaiiao"
2) "two"
3) "one"
4) "yuaiiao"
127.0.0.1:6379> srandmember list
"codeyuaiiao"
127.0.0.1:6379> srandmember list
"one"
127.0.0.1:6379> srandmember list
"two"
127.0.0.1:6379> srandmember list
"one"
127.0.0.1:6379> srandmember list
"yuaiiao"
127.0.0.1:6379> srandmember list
"yuaiiao"
127.0.0.1:6379> srandmember list 1
1) "yuaiiao"
127.0.0.1:6379> srandmember list 2 # 随机抽选出指定个数的元素
1) "two"
2) "codeyuaiiao"
127.0.0.1:6379> 
###########################################################
随机删除key 删除指定的key
# spop 
127.0.0.1:6379> smembers list
1) "codeyuaiiao"
2) "two"
3) "one"
4) "yuaiiao"
127.0.0.1:6379> spop list # 随机弹出一个元素
"yuaiiao"
127.0.0.1:6379> spop list
"codeyuaiiao"
127.0.0.1:6379> smembers list
1) "two"
2) "one"
127.0.0.1:6379> 
###########################################################
# smove 移动
127.0.0.1:6379> sadd myset hello
(integer) 1
127.0.0.1:6379> sadd myset byebye
(integer) 1
127.0.0.1:6379> sadd newset 1111
(integer) 1
127.0.0.1:6379> smembers myset
1) "hello"
2) "byebye"
127.0.0.1:6379> smembers newset
1) "1111"
127.0.0.1:6379> smove myset newset hello # 将一个指定的值移动到另一个set集合中
(integer) 1
127.0.0.1:6379> smembers myset
1) "byebye"
127.0.0.1:6379> smembers newset
1) "hello"
2) "1111"
127.0.0.1:6379> 
###########################################################
# 差集:sdiff
# 交集:sinter
# 并集:sunion
127.0.0.1:6379> sadd key1 a
(integer) 1
127.0.0.1:6379> sadd key1 b
(integer) 1
127.0.0.1:6379> sadd key1 c
(integer) 1
127.0.0.1:6379> sadd key2 c
(integer) 1
127.0.0.1:6379> sadd key2 d
(integer) 1
127.0.0.1:6379> sadd key2 e
(integer) 1
127.0.0.1:6379> sdiff key1 key2  # 差集
1) "a"
2) "b"
127.0.0.1:6379> sinter key1 key2  # 交集
1) "c"
127.0.0.1:6379> sunion key1 key2  # 并集
1) "a"
2) "b"
3) "c"
4) "e"
5) "d"
127.0.0.1:6379> 
###########################################################

微博,b站 将A用户的所有关注的人放到set集合中,将他的粉丝也放在集合中

共同关注,共同爱好,二度好友,六度分割理论

Hash

相当于Map集合,key-value!只是value是map,key-map

set myhash field codeyuaiiao

###########################################################
# hset 添加元素(一个&多个)
# hget 获取元素
# hmget 获取多个指定字段值
# hgetall 获取全部元素
# hdel 删除元素(一个&多个)
127.0.0.1:6379> hset myhash field1 yuaiiao # 添加一个具体的值
(integer) 1
127.0.0.1:6379> hset myhash field2 code
(integer) 1
127.0.0.1:6379> hset myhash field2 codeyuaiiao
(integer) 0
127.0.0.1:6379> hset myhash field3 codeyuaiiao
(integer) 1
127.0.0.1:6379> hget myhash field1 # 获取一个字段值
"yuaiiao"
127.0.0.1:6379> hset myhash field4 hello field5 byebye # 添加多个字段值
(integer) 2
127.0.0.1:6379> hmget myhash field3 field4 # 获取多个指定字段值
1) "codeyuaiiao"
2) "hello"
127.0.0.1:6379> hgetall myhash # 获取全部字段值
 1) "field1"
 2) "yuaiiao"
 3) "field2"
 4) "codeyuaiiao"
 5) "field3"
 6) "codeyuaiiao"
 7) "field4"
 8) "hello"
 9) "field5"
10) "byebye"
127.0.0.1:6379> hdel myhash field1 field2 # 删除一个或多个指定字段值
(integer) 2
127.0.0.1:6379> hgetall myhash
1) "field3"
2) "codeyuaiiao"
3) "field4"
4) "hello"
5) "field5"
6) "byebye"
127.0.0.1:6379> 
###########################################################
# hlen 获取字段数量
127.0.0.1:6379> hset myhash field1 hello
(integer) 1
127.0.0.1:6379> hset myhash field2 byebye
(integer) 1
127.0.0.1:6379> hset myhash field3 yuaiiao
(integer) 1
127.0.0.1:6379> hgetall myhash
1) "field1"
2) "byebye"
3) "field2"
4) "byebye"
5) "field3"
6) "yuaiiao"
127.0.0.1:6379> hlen myhash # 获取hash表的字段数量
(integer) 3
127.0.0.1:6379> 
###########################################################
# hexists 判断字段是否存在
127.0.0.1:6379> hgetall myhash
1) "field1"
2) "byebye"
3) "field2"
4) "byebye"
5) "field3"
6) "yuaiiao"
127.0.0.1:6379> hexists myhash field1 # 判断hash表中指定字段是否存在
(integer) 1
127.0.0.1:6379> hexists myhash field4
(integer) 0
127.0.0.1:6379> 
###########################################################
# hkeys # 获取所有field
# hvals # 获取所有value
127.0.0.1:6379> hkeys myhash # 获取所有field
1) "field1"
2) "field2"
3) "field3"
127.0.0.1:6379> hvals myhash # 获取所有value
1) "byebye"
2) "byebye"
3) "yuaiiao"
127.0.0.1:6379> 
###########################################################
# hinceby # 自增

127.0.0.1:6379> hincrby myhash field3 2 # 指定增量
(integer) 8
127.0.0.1:6379> hincrby myhash field3 -2 # 指定减量
(integer) 6
127.0.0.1:6379> hgetall myhash 
1) "field1"
2) "hello"
3) "field2"
4) "byebye"
5) "field3"
6) "6"
127.0.0.1:6379> hsetnx myhash field4 yuaiiao # 如果不存在可以设置
(integer) 1
127.0.0.1:6379> hsetnx myhash field4 code # 如果存在则不可设置
(integer) 0
127.0.0.1:6379>hgetall myhash
1) "field1"
2) "hello"
3) "field2"
4) "byebye"
5) "field3"
6) "6"
7) "field4"
8) "yuaiiao"
127.0.0.1:6379> 
###########################################################

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

Zset

在set的基础上,增加了一个值,set k1 v1 zset k1 score1 v1

###########################################################
# zset 添加有序元素
# zrange 查看所有元素
127.0.0.1:6379> zadd myzset 1 one # 添加一个元素
(integer) 1
127.0.0.1:6379> zadd myzset 2 two 3 three # 添加两个元素
(integer) 2
127.0.0.1:6379> zrange myzset 0 -1 # 查看所有元素
1) "one"
2) "two"
3) "three"
127.0.0.1:6379> 
###########################################################
# 排序如何实现
# zrangebyscore
# zrevrange
# -inf 负无穷
# -inf 正无穷
127.0.0.1:6379> zadd salary 2500 zhangsan # 添加三个用户
(integer) 1
127.0.0.1:6379> zadd salary 5000 lisi
(integer) 1
127.0.0.1:6379> zadd salary 500 yuaiiao
(integer) 1
127.0.0.1:6379> zrangebyscore salary -inf +inf # 显示全部用户,从小到大
1) "yuaiiao"
2) "zhangsan"
3) "lisi"
127.0.0.1:6379> zrevrange salary 0 -1 # 显示全部用户,从大到小
1) "lisi"
2) "zhangsan"
3) "yuaiiao"
127.0.0.1:6379> zrangebyscore salary -inf +inf withscores # 显示全部的用户并且附带成绩
1) "yuaiiao"
2) "500"
3) "zhangsan"
4) "2500"
5) "lisi"
6) "5000"
127.0.0.1:6379> zrevrange salary 0 -1 withscores # 显示全部的成绩从高到底
1) "lisi"
2) "5000"
3) "zhangsan"
4) "2500"
5) "yuaiiao"
6) "500"
127.0.0.1:6379> zrangebyscore salary -inf 2500 withscores # 显示工资小于2500员工的升序排序
1) "yuaiiao"
2) "500"
3) "zhangsan"
4) "2500"
127.0.0.1:6379> 
###########################################################
# zrem # 移除元素
127.0.0.1:6379> zrange salary 0 -1 
1) "yuaiiao"
2) "zhangsan"
3) "lisi"
127.0.0.1:6379> zrem salary lisi # 移除有序集合中的指定元素
(integer) 1
127.0.0.1:6379> zrange salary 0 -1
1) "yuaiiao"
2) "zhangsan"
127.0.0.1:6379> zcard salary  # 获取有序集合中的个数
(integer) 2
127.0.0.1:6379> 
###########################################################
# zcount 查看区间内的元素个数
127.0.0.1:6379> zadd test 1 one 2 two 3 three
(integer) 3
127.0.0.1:6379> zcount test 1 2 # 获取指定区间的成员个数
(integer) 2
127.0.0.1:6379> zcount test 1 3
(integer) 3
127.0.0.1:6379> 
###########################################################

其余的一些API,工作中遇到就到官方文档查询

案例思路: set 排序 ,存储班级成绩表,工资表排序

普通消息,1 , 重要消息 2 带权重进行判断!

排行榜应用实现,取 Top N 测试!

三种特殊数据类型

geospatial (地理位置)

朋友的定位,附近的人,打车距离计算?

redis的 Geo 在redis3.2版本就推出了,这个功能可以推算地理位置的信息, 两地之间的距离,方圆几里的人

可以查询一些测试数据:http://www.jsons.cn/lngcode/

只有六个命令

Redis学习笔记(B站狂神说)(自己总结方便复习)_第25张图片

geoadd (添加)

# geoadd 添加地理位置
# 有效地经度和纬度
127.0.0.1:6379> geoadd china:city 116.40 39.90 beijing
(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 113.28 23.12 guangzhou
(integer) 1
127.0.0.1:6379> geoadd china:city 114.08 22.54 shenzhen
(integer) 1
127.0.0.1:6379> geoadd china:city 120.15 30.28 hangzhou 106.50 29.53 chongqing
(integer) 2
127.0.0.1:6379> 

geopos (查询)

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

# 查询
127.0.0.1:6379> geopos china:city beijing
1) 1) "116.39999896287918091"
   2) "39.90000009167092543"
127.0.0.1:6379> geopos china:city beijing shenzhen
1) 1) "116.39999896287918091"
   2) "39.90000009167092543"
2) 1) "114.08000081777572632"
   2) "22.53999903789756587"
127.0.0.1:6379> 

geodist (距离)

两人之间的距离

单位 m km

127.0.0.1:6379> geodist china:city beijing shanghai
"1067378.7564"
127.0.0.1:6379> geodist china:city beijing shanghai km # 北京到上海的直线距离
"1067.3788"
127.0.0.1:6379> geodist china:city beijing shenzhen km # 北京到深圳的直线距离
"1943.2550"
127.0.0.1:6379> 

georadius (查找指定位置范围内的目标)

withcoord 显示经纬度

withdist 显示距离

count 显示数量

127.0.0.1:6379> georadius china:city 110 30 1000 km 
1) "chongqing"
2) "shenzhen"
3) "guangzhou"
4) "hangzhou"
127.0.0.1:6379> georadius china:city 110 30 900 km 
1) "chongqing"
2) "guangzhou"
127.0.0.1:6379> georadius china:city 110 30 900 km withcoord # 查找范围内的城市并显示经纬度
1) 1) "chongqing"
   2) 1) "106.49999767541885376"
      2) "29.52999957900659211"
2) 1) "guangzhou"
   2) 1) "113.27999979257583618"
      2) "23.1199990030198208"
127.0.0.1:6379> georadius china:city 110 30 900 km withhash
1) 1) "chongqing"
   2) (integer) 4026042091628984
2) 1) "guangzhou"
   2) (integer) 4046533745880732
127.0.0.1:6379> georadius china:city 110 30 900 km withcount
(error) ERR syntax error
127.0.0.1:6379> georadius china:city 110 30 900 km withdist
1) 1) "chongqing"
   2) "341.9374"
2) 1) "guangzhou"
   2) "831.7713"
127.0.0.1:6379> georadius china:city 110 30 900 km withcoord count 1 
1) 1) "chongqing"
   2) 1) "106.49999767541885376"
      2) "29.52999957900659211"
127.0.0.1:6379> georadius china:city 110 30 900 km withcoord count 2
1) 1) "chongqing"
   2) 1) "106.49999767541885376"
      2) "29.52999957900659211"
2) 1) "guangzhou"
   2) 1) "113.27999979257583618"
      2) "23.1199990030198208"
127.0.0.1:6379> 

georadiusbymember (查找指定城市范围内的目标)

127.0.0.1:6379> georadiusbymember china:city beijing 1000 km 
1) "beijing"
127.0.0.1:6379> georadiusbymember china:city shanghai 1000 km 
1) "hangzhou"
2) "shanghai"
127.0.0.1:6379> 

(了解)geohash(返回指定位置得hash表示字符)

# 将二维的经纬度,换为了一维的字符串,
# 字符串越像,越接近
127.0.0.1:6379> geohash china:city beijing shanghai 
1) "wx4fbxxfke0"
2) "wtw3sj5zbj0"
127.0.0.1:6379> 

删除(geo底层其实是Zset)

127.0.0.1:6379> zrange china:city 0 -1
1) "chongqing"
2) "shenzhen"
3) "guangzhou"
4) "hangzhou"
5) "shanghai"
6) "beijing"
127.0.0.1:6379> zrem china:city chongqing # 删除
(integer) 1
127.0.0.1:6379> zrange china:city 0 -1
1) "shenzhen"
2) "guangzhou"
3) "hangzhou"
4) "shanghai"
5) "beijing"
127.0.0.1:6379> 

hyperloglog (求基数)

什么是基数?

A{1,3.,5,7,9}

B{1,3,5,7,9,9}

基数(不重复的元素) =AB合并=5 , 可以接受误差

简介

Redis 2.8.9 版本就跟新了Hyperloglog数据结构!

Redis Hyperloglog 基数统计的算法!

优点: 占用的内存是固定的, 2^64 不同的元素的基数,只需要12kb的内存即可,如果从内存角度来比较,且允许有误差(0.81%)Hyperloglog首选

网页的UV(一个人访问一个网站多次,但是还是算作一个人!)

传统的方式,set保存用户的id ,然后就可以统计set中元素数量作为标准!

set集合(无序,不重复)

这个方式如果保存大量的用户id,就会比较麻烦! 我们的目的是为了技术,而不是保存用户id

pfadd 添加

pfcount 计数

pfmerge 合并

测试使用

# pfadd 添加
# pfcount 计数
# pfmerge 合并
127.0.0.1:6379> pfadd key a b c g s k a l o i u # 添加元素
(integer) 1
127.0.0.1:6379> pfadd key1 s k i y o l a k v f s
(integer) 1
127.0.0.1:6379> pfcount key # 查看数量
(integer) 10
127.0.0.1:6379> pfcount key1
(integer) 9
127.0.0.1:6379> pfmerge key2 key key1 # key key1 合并到key2中
OK
127.0.0.1:6379> pfcount key2
(integer) 13

如果允许容错要用 hyperloglog

不允许就用 set 或者自己的数据类型

bitmap (判断状态)

统计用户信息(活跃,不活跃) (登录,未登录)

打卡(打卡,未打卡)

两个状态的都可以使用bitmap

bitmap 位图 , 数据结构! 都是操作二进制位来进行记录的,就只有0和1两个状态

1字节 = 8bit

setbit 设置

getbit 获取

bitcount 统计为1的次数

测试

使用bitmaps 来记录一周的打卡情况!

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

查看某一天是否打卡

127.0.0.1:6379> getbit login 2
(integer) 1
127.0.0.1:6379> getbit login 6
(integer) 0
127.0.0.1:6379> 

统计操作,统计打卡的天数

127.0.0.1:6379> bitcount login # 统计为1的次数
(integer) 4
127.0.0.1:6379> 

这些在生活中或开发中,都有十分多的应用场景,学习了,就是多一个思路.技多不压身

四.事务

Redis事务本质: 一组命令的集合! 一个事务中的所有命令都会被序列化,在事务执行过程中,会按照顺序执行!

一次性,顺序性,排他性! 执行一系列的命令!

------队列 set get set 执行-----

Redis 事务没有隔离级别的概念!

所有的命令在事务中,并没有直接被执行,只有发起执行命令的时候才会执行! Exec

Redis单条命令是保证原子性的,但是事务不保证原子性!

redis的事务命令:

  • 开启事务(multi)
  • 命令入队(—正常命令—)
  • 执行事务(exec)

正常执行事务

127.0.0.1:6379> multi # 开启事务
OK
127.0.0.1:6379> set key hello # 执行命令
QUEUED
127.0.0.1:6379> set key1 yuaiiao
QUEUED
127.0.0.1:6379> get key
QUEUED
127.0.0.1:6379> get key1
QUEUED
127.0.0.1:6379> exec # 执行事务
1) OK
2) OK
3) "hello"
4) "yuaiiao"
127.0.0.1:6379> 

放弃事务 discard

127.0.0.1:6379> multi 
OK
127.0.0.1:6379> set key 1
QUEUED
127.0.0.1:6379> set key2 3
QUEUED
127.0.0.1:6379> discard # 放弃事务
OK
127.0.0.1:6379> get key2
(nil)
127.0.0.1:6379> 

编译型异常(diamante有问题! 命令错了!) ,事务中所有的命令都不会被执行

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> getset k3 v3
QUEUED
127.0.0.1:6379> getset k3 # 错误命令
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379> set k4 v4
QUEUED
127.0.0.1:6379> exec # 执行事务报错
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get k2 # 事务执行失败,得不到值
(nil)
127.0.0.1:6379> 

运行时异常(1/0) , 如果事务队列中存在语法性 , 那么执行命令的时候,其他的命令是可以正常执行的,错误命令抛出异常!

127.0.0.1:6379> set k1 "v1"  # 字符串
OK
127.0.0.1:6379> multi 
OK
127.0.0.1:6379> incr k1 # 字符串 自增1 错误
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 k2
QUEUED
127.0.0.1:6379> exec # 错误的命令报错但是其余命令都能执行
1) (error) ERR value is not an integer or out of range 
2) OK
3) OK
4) "v2"
127.0.0.1:6379> get k2 # 其余命令正常执行
"v2"
127.0.0.1:6379> get k3
"v3"
127.0.0.1:6379> 

监控 Watch (面试: redis–watch监控实现秒杀系统)

悲观锁

  • 很悲观,认为什么时候都会出问题,无论做什么都会加锁

乐观锁

  • 很乐观,认为什么时候都不会出现问题
  • 获取version
  • 更新的的时候比较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> 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
127.0.0.1:6379> 

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

127.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 # 执行之前,另一个线程,修改了我们的值,就会导致事务执行失败!
					 #  127.0.0.1:6379> get money
						"80"
					 #  127.0.0.1:6379> set money 1000
					 #  OK
(nil)
127.0.0.1:6379> 

如果修改失败,获取最新的就好

127.0.0.1:6379> unwatch # 释放锁(监控)
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 out 20
QUEUED
127.0.0.1:6379> exec
1) (integer) 980
2) (integer) 40
127.0.0.1:6379> 

五.jedis

我们要使用java来操作redis, 知其然并知其所以然,授人以鱼不如授人以渔,学习不能急躁,慢慢来会很快

什么是jedis?

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

1.导入依赖


<dependencies>
    
    <dependency>
        <groupId>redis.clientsgroupId>
        <artifactId>jedisartifactId>
        <version>3.3.0version>
    dependency>
    
    <dependency>
        <groupId>com.alibabagroupId>
        <artifactId>fastjsonartifactId>
        <version>1.2.70version>
    dependency>
dependencies>

2.编码测试

  • 连接数据库
  • 操作命令
  • 断开连接

六.SpringBoot整合

SpringBoot操作数据 :Spring-data jpa jdbc mongodb redis !

SpringData 也是和 SpringBoot 齐名的项目!

说明 :在 SpringBoot2.x之后, 原来使用的jedis 被替换为了 lettuce ?

jedis :采用的直连, 多个线程操作的话 ,是不安全的, 如果想要避免不安全, 使用jedis pool 连接池 ! 更像 BIO模式.

lettuce : 采用netty , 实例可以再多个线程中进行共享,不存在线程不安全的情况 ! 可以减少线程数据了, 更像NIO模式.

源码分析

Redis学习笔记(B站狂神说)(自己总结方便复习)_第26张图片

整合测试

  1. 导入依赖

    
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-data-redisartifactId>
    dependency>
    
  2. 配置连接

    # 配置redis
    spring.redis.host=192.168.189.128
    spring.redis.port=6379
    spring.redis.password=123456
    
  3. 测试

测试test

@SpringBootTest
class RedisSpringbootApplicationTests {
     

    @Autowired
    @Qualifier("redisTemplate")
    private RedisTemplate redisTemplate;

    @Autowired
    private RedisUtil redisUtil;
    

    @Test
    void contextLoads() {
     
        //redisTemplate 操作不同的数据类型 , API和我们的指令一样
        //opsForValue 操作字符串 类似Spring
        //opsForList  操作List
        //opsForSet   操作Set
        //opsForHash
        //opsForvZset
        //opsForGeo
        //opsForHyperLogLog

        //除了基本的操作,我们常用的方法都可以直接通过redisTemplate操作, 比如事务 ,和基本的CRUD

        //获取redis的操作对象
//        RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
//        connection.flushDb();
//        connection.flushAll();


        redisTemplate.opsForValue().set("mykey","hello yuaiiao");
        System.out.println(redisTemplate.opsForValue().get("mykey"));

    }

}

Redis学习笔记(B站狂神说)(自己总结方便复习)_第27张图片

关于对象的保存

Redis学习笔记(B站狂神说)(自己总结方便复习)_第28张图片

编写一个自己的redisTemplate

RedisConfig

// RedisConfig

@Configuration
public class RedisConfig {
     

    public RedisConfig() {
     
    }

    @Bean
    @SuppressWarnings("all")
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
     
        RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
        template.setConnectionFactory(factory);
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        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;
    }
}

RedisUtil 工具类

// RedisUtil

@Component
public final class RedisUtil {
     

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    // =============================common============================
    /**
     * 指定缓存失效时间
     * @param key  键
     * @param time 时间(秒)
     */
    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 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)
     */
    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)
     */
    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
     */
    public Object hget(String key, String item) {
     
        return redisTemplate.opsForHash().get(key, item);
    }

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

    /**
     * HashSet
     * @param key 键
     * @param map 对应多个键值
     */
    public boolean hmset(String key, Map<String, Object> 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<String, Object> 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)
     */
    public double hincr(String key, String item, double by) {
     
        return redisTemplate.opsForHash().increment(key, item, by);
    }


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


    // ============================set=============================

    /**
     * 根据key获取Set中的所有值
     * @param key 键
     */
    public Set<Object> 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 键
     */
    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代表所有值
     */
    public List<Object> 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 键
     */
    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倒数第二个元素,依次类推
     */
    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 值
     */
    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  时间(秒)
     */
    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<Object> 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<Object> 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;
        }

    }

}
//测试test
@Test
void test1(){
     
    redisUtil.set("name","yuaiiao");
    System.out.println(redisUtil.get("name"));
}

所有的redis操作,其实对于java开发人员来说,十分的简单,更重要的是要去理解redis的思想和每一种数据结构的用处和作用场景.

七.Redis.conf详解

启动的时候就通过配置文件启动的

Redis 中这些小的配置 才会让你脱颖而出

单位

Redis学习笔记(B站狂神说)(自己总结方便复习)_第29张图片

1.对大小写不敏感

包含

Redis学习笔记(B站狂神说)(自己总结方便复习)_第30张图片

包含多个配置文件

网络

bind 127.0.0.1  # 绑定的ip
protected-mode yes # 保护模式
port 6379 # 端口设置

通用 GENERAL

daemonize yes # 以守护进程的方式运行,默认是no , 我们需要自己开启为yes
pidfile /var/run/redis_6379.pid # 如果以后台方式运行,我们就需要指定一个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
logfile "" # 日志的文件位置名
databases 16 # 数据库的数量,默认是16个数据库
always-show-logo yes # 是否总是显示LOGO

快照

持久化,在规定的时间内,执行了多少次操作,则会持久化到文件,.rdb.aof

redis是内存数据库,如果没有持久化,那么数据断电及失!

# 如果900s内,如果至少有一个1 key进行了修改,我们及进行持久化操作
save 900 1
# 如果300s内,如果至少10 key进行了修改,我们及进行持久化操作
save 300 10
# 如果60s内,如果至少10000 key进行了修改,我们及进行持久化操作
save 60 10000
# 我们之后学习持久化,会自己定义这个测试!

stop-writes-on-bgsave-error yes # 持久化如果出错,是否还需要继续工作!

rdbcompression yes # 是否压缩rdb文件.需要消耗一些cpu资源!

rdbchecksum yes # 保存rdb文件的时候,进行错误的检查!

dir ./ # rdb 文件保存大的目录

REPLICATION 复制 , 我们后面讲解主从复制,时候再进行讲解

SECURITY 安全

Redis学习笔记(B站狂神说)(自己总结方便复习)_第31张图片

限制 CLIENTS

maxclients 10000 # 设置能连接上tedis 的最大客户端的数量
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 : 永不过期,返回错误

APPEND ONLY MODE 模式 aof配置

appendonly no # 默认是不开启

appendfilename "appendonly.aof" # 持久化的文件的名字

#appendfsync always  # 每次修改都会 sync(同步). 消耗性能
appendfsync everysec # 美妙执行一侧 sync, 可能会丢失这1s的数据
# appendfsync no     # 不执行sync ,这个时候操作系统自己同步数据,速度最快!

具体的配置,我们在Redis持久化中解决

八.Redis持久化

RDB(Redis DataBase)

在主从复制中,rdb就是备用了! 从机上面!

什么是RDB

Redis学习笔记(B站狂神说)(自己总结方便复习)_第32张图片

在指定的时间间隔内将内存中的数据集快照写入磁盘,也就是行话讲的Snapshot快照,它恢复时是将快照文件直接读到内存里.

Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中, 带持久化过程都结束了,再用这个临时文件替换上次持久化好的文件. 整个过程中,主进程是不进行任何IO操作的.这就确保了极高的性能.如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感.那RDB方式要不AOF方式更加的高效.RDB的缺点是最后一次持久化后的数据可能丢失. 我们默认的就是RDB, 一般情况下不需要修改这个配置!

RDB保存的文件是,dump.rdb都是在我们的配置文件中快照中进行配置的!

在这里插入图片描述

在这里插入图片描述

触发机制

  1. save 的规则满足的情况下,会自动触发rdb规则
  2. 执行flushall命令 , 也会触发我们的rdb 规则!
  3. 退出redis,也会产生rdb文件!

备份就会自动生成一个dump.rdb

Redis学习笔记(B站狂神说)(自己总结方便复习)_第33张图片

如何恢复rdb文件!

  1. 只需要将rdb 文件放在我们redis启动目录就可以,redis启动的时候会自动检查dump.rdb恢复其中的数据!

  2. 查看需要存在的位置

  3.  127.0.0.1:6379> config get dir
     1) "dir"
     2) "/usr/local/bin" # 入伙在这个目录下存在dump.rdb 文件,启动就会自动恢复其中的数据
    
  4. 默认的配置就可以了

优点&缺点

优点:

  1. 适合大规模的数据恢复!

  2. 对数据的完整性要求不高!

缺点:

  1. 需要一定的时间间隔进行操作! 如果redis意外宕机了,这个最后一个修改的数据就没有了!

  2. fork进程的时候,会占用一定的内容空间!

AOF(Append Only File)

将我们的所有命令都记录下来,history , 恢复的时候就把这个文件全部在执行一遍

是什么

Redis学习笔记(B站狂神说)(自己总结方便复习)_第34张图片

以日志的形式来记录每个写操作,将Redis执行过的所有指令记录下来(读操作不记录) , 只许追加文件但不可以改写文件,redis 启动之初会读取改文件重新构建数据,换言之,redis重启的话就跟据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作

Aof保存的是 appendonly.aof文件

append

Redis学习笔记(B站狂神说)(自己总结方便复习)_第35张图片

默认是不开启的,我们需要手动进行配置! 我们只需要将appendonly 改为yes就开启了aof!

重启,redis 就可以生效了!

如果这个aof文件有错位,这时候 redis是启动不起来的,我们需要修复这个aof文件

redis 给我提供了一个工具redis-check-aof --fix

Redis学习笔记(B站狂神说)(自己总结方便复习)_第36张图片

如果文件正常,重启就可以直接恢复了!

Redis学习笔记(B站狂神说)(自己总结方便复习)_第37张图片

重写规则说明

aof默认就是文件的无限追加 , 文件会越来越大!

Redis学习笔记(B站狂神说)(自己总结方便复习)_第38张图片

如果aof文件大于64m,太大了 ! fork一个新的进程来将我们的文件进行重写!

优点&缺点

appendonly no # 默认是不开启aof模式的, 默认是使用rdb方式持久化的,再大部分情况下,rdb完全够用!
Appendfilename "appendonly.aof" # 持久化的文件的名字

# appendfsync always # 每次修改都会sync .消耗性能
appendfsync everysec # 每秒执行一次sync,可能会丢失这1s的数据!
# appendfsync no     # 不执行sync ,这个时候操作系统自己同步数据,速度最快!

优点:

  1. 每一次修改都同步,文件的完整性会更好!
  2. 每秒同步一次,可能会丢失一秒的数据
  3. 从不同步,效率最高!

缺点:

  1. 相对于数据文件来说, aof远远大于rdb,修复的速度也比rdb慢!
  2. aof运行效率也要比rdb慢,所以我们redis默认的配置就是rdb持久化!

扩展

在这里插入图片描述

Redis学习笔记(B站狂神说)(自己总结方便复习)_第39张图片

Redis学习笔记(B站狂神说)(自己总结方便复习)_第40张图片

九.Redis发布订阅

Redis 发布订阅(pub/sub)是一种消息通信模式: 发送者(pub)发送消息,订阅者(sub)接受消息.微博,微信,关注系统!

Redis 客户端可以订阅任意数量的频道.

订阅/发布消息图:

第一个: 消息发送者, 第二个 :频道 第三个 :消息订阅者!

Redis学习笔记(B站狂神说)(自己总结方便复习)_第41张图片

命令

这些命令被广泛用于构建即时通信应用,比如网络聊天室和实时广播,实时提醒

Redis学习笔记(B站狂神说)(自己总结方便复习)_第42张图片

订阅端

127.0.0.1:6379> subscribe codeyuaiiao # 订阅一个频道 codeyuaiiao
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "codeyuaiiao"
3) (integer) 1
# 等待读取推送
1) "message" # 消息
2) "codeyuaiiao" # 那个频道的消息
3) "hello world" # 消息的具体内容
1) "message"
2) "codeyuaiiao"
3) "hello yuhaijiao"

发送端:

127.0.0.1:6379> publish codeyuaiiao "hello world" # 发布者发布消息到频道
(integer) 1
127.0.0.1:6379> publish codeyuaiiao "hello yuhaijiao" # 发布者发布消息到频道
(integer) 1
127.0.0.1:6379> 

原理

Redis学习笔记(B站狂神说)(自己总结方便复习)_第43张图片

Redis学习笔记(B站狂神说)(自己总结方便复习)_第44张图片

通过publish命令向订阅者发送消息,redis-server会使用给定的频道作为键,在它所维护的channel字典中查找记录了订阅这个频道的所有客户端的链表,遍历这个链表,将消息发布给所有订阅者.

Redis学习笔记(B站狂神说)(自己总结方便复习)_第45张图片

在这里插入图片描述

使用场景:

  1. 实时消息系统!
  2. 事实聊天! (频道当做聊天室,将信息回显给所有人即可! )
  3. 订阅,关注系统都是可以的!

稍微复杂的场景我们就会使用 消息中间件MQ

十.Redis主从复制

概念

主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器.前者称为主节点(master/leader),后者称为从节点(slave/follower);==数据的复制是单向的,只能由主节点到从节点.==Master以写为主,Slave以读为主.

默认情况下,每台Redis服务器都是主节点;且一个主节点可以有多个从节点(或没有从节点),但一个从节点只能有一个主节点.

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

  1. 数据冗余: 主从复制实现了数据的热备份,使持久化之外的一种数据冗余方式.
  2. 故障恢复:当主节点出现问题是,可以由从节点提供服务,实现快速的故障恢复;实际上是一种服务的冗余.
  3. 负载均衡: 在主从复制的基础上,配合读写分离,可以由主节点提供写服务(既写Redis数据是应用连接主节点,读Redis数据时应用连接从节点),分担服务器负载;尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量.
  4. 高可用(集群)基石:除了上诉作用以外,主从复制还是烧饼和集群能够实施的基础,因此说主从复制是Redis高可用的基础.

一般来说,要将Redis运用于工程项目中,只是用一台Redis是万万不能的(宕机 ,一主二从),原因如下:

  1. 从结构上,单个Redis服务器会发生单点故障,并且一台服务需要处理所有的请求负载,压力较大;
  2. 从容量上,单个Redis服务器内存容量有限,就算一台Redis服务器内存容量为256G,也不能将所有内存用作Redis存储内存,一般来说,单台Redis 最大使用内存不应该超过20G.
  3. 电商网站上的商品,一般都是一次上传,无数次浏览的,说专业的就是"多读少写".

这种场景,我们可以使用如下这种架构:

Redis学习笔记(B站狂神说)(自己总结方便复习)_第46张图片

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

环境配置

Redis学习笔记(B站狂神说)(自己总结方便复习)_第47张图片

复制3个配置文件,然后修改对应的信息

  1. 端口
  2. pid名字
  3. log文件名字
  4. dump.rdb名字

修改完毕之后,启动我们的3个redis服务器,可以通过进程信息查

Redis学习笔记(B站狂神说)(自己总结方便复习)_第48张图片

一主二从

默认情况下, 每台Redis 服务器都是主节点 ; 我们一般情况下只用配置从机就好了!

slaveof 127.0.0.1 6379 # slaveof host 6379 设定谁是主机
info replication 查询信息
slaveof 主机ip 端口号 # 设置从机 slaveof 127.0.0.1 6379

Redis学习笔记(B站狂神说)(自己总结方便复习)_第49张图片

如果两个都配置完了,就有两个从机了

Redis学习笔记(B站狂神说)(自己总结方便复习)_第50张图片

真实的从主配置应该在配置文件中配置,这样的话是永久的, 我们这里使用的是命令,暂时的!

细节

主机可以写, 从机不能写只能读! 主机中的所有信息和数据,都会自动被从机保存.

主机写:

Redis学习笔记(B站狂神说)(自己总结方便复习)_第51张图片

从机读:

Redis学习笔记(B站狂神说)(自己总结方便复习)_第52张图片

测试: 主机断开连接,从机依旧连接到主机的, 但是没有写操作, 这个时候, 主机如果回来了,从机依旧可以直接直接获取到主机写的信息!

如果是使用命令行, 来配置的主从,这个时候如果重启了,就会变成主机 ! 只要变为从机, 立马就会从主机中获取值!

复制原理

Slave启动成功连接到master后会发送一个sync同步命令

Master接到命令,启动后台的存盘进程,同时手机所有接受到的用于修改数据集命令, 在后台进程完毕之后,master将传送整个数据问价难道slave,并完成一次完全同步.

全量复制: 二slave服务在接收到数据库文件数据后, 将其存盘并加载到内存中.

增量复制: Master继续将新的所有收集到的修改命令依次传给slave,完成同步.

但是只要是重新连接master, 一次完全同步(全量复制)将被自动执行! 我们的数据一定可以在从机中看到!

层层链路

上一个M连接下一个S!

Redis学习笔记(B站狂神说)(自己总结方便复习)_第53张图片

这时候也可以完成我们的主从复制!

如果没有老大了,这个时候能不能选择一个老大出来呢? 手动!

谋朝篡位

如果主机断开了连接, 我们可以使用slaveof no one 让自己变成主机! 其他的节点就可以手动连接到最新的这个主节点(手动)! 如果这个时候老大修复了, 那就重新连接!

哨兵模式

(自动选举老大)

主从切换技术的方法是:当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工干预,费时费力,还会造成一段时间内服务不可用. 这不是一个推荐的方式.更多时候,我们优先考虑哨兵模式.Redis从2.8开始正式提供了Sentinel(哨兵)架构来解决这个问题.

谋朝篡位的自动版,能够后台监控主机是否故障,如果故障了根据投票数自动从库转换为主库

哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它会独立运行.其原理是哨兵通过他送命令,等待Redis服务器响应,从而监控运行的多个Redis实例.

Redis学习笔记(B站狂神说)(自己总结方便复习)_第54张图片

这里的哨兵有两个作用:

  • 通过发送命令,让Redis服务器返回监控其运行状态,包括主服务器和从服务器.
  • 当哨兵检测到master宕机,会自动将slave切换成master,然后通过订阅模式通知其他的从服务器,修改配置文件,让他们切换主机.

然而一个烧饼进程对Redis服务器进行监控,可能会出现问题,为此我们可以使用多个哨兵进行监控.各个哨兵之间还会进行监控,这样就形成了多哨兵模式.

Redis学习笔记(B站狂神说)(自己总结方便复习)_第55张图片

假设主服务器宕机,哨兵1先检测到这个结果,系统不会马上进行failover过程,仅仅是哨兵1主管的认为主服务器不可用,这个现象成为主观下线.当后面的哨兵也检测到主服务器不可用,并且数量到大一定值时,那么哨兵之间就会进行一个投票,投票的结果由一个哨兵发起,进行failover[故障转移]操作.切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机,这个过程称为客观下线.

测试

我们目前的状态是 一主二从

  1. 配置哨兵配置文件 sentinel.conf
# sentinel monitor 被监控的名称 host port 1
sentinel monitor myredis 127.0.0.1 6379 1

后面的这个数字1,代表主机挂了,slave投票看让谁接替称为主机,票数最多的,就会成为主机

启动

redis-sentinel YuConfig/sentinel.conf

[root@localhost bin]# redis-sentinel YuConfig/sentinel.conf 
5748:X 04 Nov 2020 15:59:17.801 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
5748:X 04 Nov 2020 15:59:17.801 # Redis version=6.0.8, bits=64, commit=00000000, modified=0, pid=5748, just started
5748:X 04 Nov 2020 15:59:17.801 # Configuration loaded
5748:X 04 Nov 2020 15:59:17.803 * Increased maximum number of open files to 10032 (it was originally set to 1024).
                _._                                                  
           _.-``__ ''-._                                             
      _.-``    `.  `_.  ''-._           Redis 6.0.8 (00000000/0) 64 bit
  .-`` .-```.  ```\/    _.,_ ''-._                                   
 (    '      ,       .-`  | `,    )     Running in sentinel mode
 |`-._`-...-` __...-.``-._|'` _.-'|     Port: 26379
 |    `-._   `._    /     _.-'    |     PID: 5748
  `-._    `-._  `-./  _.-'    _.-'                                   
 |`-._`-._    `-.__.-'    _.-'_.-'|                                  
 |    `-._`-._        _.-'_.-'    |           http://redis.io        
  `-._    `-._`-.__.-'_.-'    _.-'                                   
 |`-._`-._    `-.__.-'    _.-'_.-'|                                  
 |    `-._`-._        _.-'_.-'    |                                  
  `-._    `-._`-.__.-'_.-'    _.-'                                   
      `-._    `-.__.-'    _.-'                                       
          `-._        _.-'                                           
              `-.__.-'                                               

5748:X 04 Nov 2020 15:59:17.804 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
5748:X 04 Nov 2020 15:59:17.821 # Sentinel ID is f876de823361a0896cdd4c39d29dd9eb189c4c7c
5748:X 04 Nov 2020 15:59:17.821 # +monitor master myredis 127.0.0.1 6379 quorum 1

如果Master节点断开了,这个时候就会从从机中随机选择一个服务器(这里面有一个投票算法底层)

Redis学习笔记(B站狂神说)(自己总结方便复习)_第56张图片

哨兵日志!

Redis学习笔记(B站狂神说)(自己总结方便复习)_第57张图片

如果主机此时回来了,只能诡兵道心得主机下,当做从机,这就是哨兵模式的规则!

哨兵模式

优点:

  1. 哨兵集群,基于主从复制模式,所有的主从配置优点,他全有.
  2. 主从可以切换,故障可以转移,系统的可用性就会更好.
  3. 哨兵模式就是主从模式的升级,手动到自动,更加健壮.

缺点:

  1. Redis 不好在线寇蓉,集群容量一旦到达上限,在线扩容就十分麻烦.
  2. 实现哨兵模式的配置其实是很麻烦的,里面有很多选择!

哨兵模式的 全部配置

Redis学习笔记(B站狂神说)(自己总结方便复习)_第58张图片

Redis学习笔记(B站狂神说)(自己总结方便复习)_第59张图片

Redis学习笔记(B站狂神说)(自己总结方便复习)_第60张图片

port 26379

dir /tmp

sentinel monitor mymaster 127.0.0.1 6379 2

sentinel auth-pass mymaster MySUPER--secret-0123password

sentinel down-after-milliseconds mymaster 30000

sentinel parallel-syncs mymaster 1

sentinel  failover-timeout mymaster 180000

sentinel notification-script mymaster /var/redis/notify.sh

sentinel client-reconfig-script mymaster /var/redis/reconfig.sh # 一般运维配置

十一.*Redis缓存穿透和雪崩

在这里我们不会详细的分析解决方案的底层!

Redis 缓存的使用,极大地提升了应用程序的性能和效率,特别是数据查询方面. 但同时, 他也带来了一些问题,其中, 最重要的问题就是数据的一致性问题,从严格意义上来讲,这个问题无解. 如果对数据的一致性要求很高,那么就不能使用缓存.

另外的一些典型问题就是, 缓存穿透 , 缓存击穿和缓存雪崩.目前业界也都有比较流行的解决方案.

Redis学习笔记(B站狂神说)(自己总结方便复习)_第61张图片

缓存穿透

概念

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

解决方案两种

1.布隆过滤器

布隆过滤器是一种数据结构 , 对所有可能查询的参数以hash形式存储, 在控制层先进行校验, 不符合则丢弃, 从而避免不了对底层存储系统的查询压力;

Redis学习笔记(B站狂神说)(自己总结方便复习)_第62张图片

2.缓存空对象

当存储层不命中后 , 即使返回的空对象也将其缓存起来, 同时会设置一个过期时间, 之后再访问这个数据将会从缓存中获取,保护了后端数据源;

Redis学习笔记(B站狂神说)(自己总结方便复习)_第63张图片

但是这种方法会存在两个问题:

  1. 如果空值能够被缓存起来, 这就意味着缓存需要更多的空间存储更多地键 , 因为这当中可能会有很多的空值的键;
  2. 及时对空值设置了过期时间 , 还是会存在缓存层和存储层的数据会有一段时间窗口的不一致, 这对于需要保持一致性的业务会有影响.

缓存击穿(量太大,缓存过期!)

概念

这里需要注意和缓存击穿的区别, 缓存击穿, 是指一个key非常热点, 在不停的扛着大并发,大并发集中对这一个点进行访问, 当这个key在失效的的瞬间, 持续的大并发就穿破缓存, 直接请求数据库, 就像在一个屏障上凿开了一个洞.

当某个key在过期的瞬间,有大量的请求并发访问,这类数据一般是热点数据, 由于魂村过期, 会同时访问数据库来查询最新数据, 并且回写缓存, 会导致数据库瞬间压力过大.

解决方案

设置热点数据永不过期

从缓存层面来看, 没有设置过期时间,所以不会出现热点key过期后产生的问题.

加互斥锁

分布式锁: 使用分布式锁,保证对于每个key 同时只有一个线程去查询后端服务, 其他线程没有获得分布式锁的权限, 因此只需要等待即可. 这种方式将高并发的压力转移到了分布式锁,因此对分布式锁的考验很大.

缓存雪崩

缓存雪崩, 是指在摸一个时间段, 缓存集中过期失效. Redis宕机!

产生雪崩的原因之一, 比如马上就要到双十二零点 , 很快就会迎来一波抢购, 这波商品时间比较集中的放入了缓存,假设缓存一个小时,那么到了凌晨一点中的时候, 这批商品的缓存就都过期了. 而对这批商品的访问查询,都落到了数据库上, 对于数据库而言, 就会产生周期性的压力波峰. 于是所有的请求都会到达存储层, 存储层的调用量会暴增, 造成存储层也会挂掉的情况.

Redis学习笔记(B站狂神说)(自己总结方便复习)_第64张图片

其实集中过期, 倒不是非常致命的, 比较致命的缓存雪崩, 是缓存服务器某个节点宕机或断网 . 因为自然形成的缓存雪崩, 一定是在某个时间段集中创建缓存, 这个时候 , 数据库也是可以顶住压力的. 无非就是对数据库数据库产生周期性的压力而已. 而缓存服务节点的宕机, 对数据库服务器造成的压力是不可遇见的. 很有可能瞬间就把数据库压垮的.

解决方案

redis高可用

这个思想的含义是, 既然redis有可能挂掉, 那我多增设几台redis,这样一台挂掉之后其他的还可以继续工作,其实就是搭建的集群. (异地多活!)

限流降级

这个解决方案的思想是, 在缓存失效后 , 通过枷锁或者队列来控制读数据库写缓存的线程数量. 比如对某个key只允许一个线程查询数据和写缓存, 其他线程等待.

数据预热

数据加热的含义就是在正式部署前, 我先把可能的数据预先访问一遍, 这样可能大量访问的数据就会加载到缓存中. 在即将发生大并发访问前手动触发加载不同的key , 设置不同的过期时间, 让缓存失效的时间点尽量均匀.

完结撒花✿✿ヽ(°▽°)ノ✿

看到最后的帮忙点个 谢谢,这个对我真的很重要!
在这里插入图片描述

你可能感兴趣的:(java,学习全过程笔记,数据库,学习笔记,数据库,jedis,Redis)