Redis笔记(狂神说)

搭配狂神说 Redis视频一起学习
狂神视频

一、Nosql概述

为什么使用Nosql

1、单机Mysql时代

在这里插入图片描述

90年代,一个网站的访问量一般不会太大,单个数据库完全够用。随着用户增多,网站出现以下问题

  1. 数据量增加到一定程度,单机数据库就放不下了
  2. 数据的索引(B+ Tree),一个机器内存也存放不下
  3. 访问量变大后(读写混合),一台服务器承受不住。

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

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

在这里插入图片描述

优化过程经历了以下几个过程:

  1. 优化数据库的数据结构和索引(难度大)
  2. 文件缓存,通过IO流获取比每次都访问数据库效率略高,但是流量爆炸式增长时候,IO流也承受不了
  3. MemCache,当时最热门的技术,通过在数据库和数据库访问层之间加上一层缓存,第一次访问时查询数据库,将结果保存到缓存,后续的查询先检查缓存,若有直接拿去使用,效率显著提升。

3、分库分表 + 水平拆分 + Mysql集群

在这里插入图片描述

4、如今最近的年代

如今信息量井喷式增长,各种各样的数据出现(用户定位数据,图片数据等),大数据的背景下关系型数据库(RDBMS)无法满足大量数据要求。Nosql数据库就能轻松解决这些问题。

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

在这里插入图片描述

为什么要用NoSQL ?

用户的个人信息,社交网络,地理位置。用户自己产生的数据,用户日志等等爆发式增长!
这时候我们就需要使用NoSQL数据库的,Nosql可以很好的处理以上的情况!

什么是Nosql

NoSQL = Not Only SQL(不仅仅是SQL)

Not Only Structured Query Language

关系型数据库:列+行,同一个表下数据的结构是一样的。

非关系型数据库:数据存储没有固定的格式,并且可以进行横向扩展。

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

Nosql特点

  1. 方便扩展(数据之间没有关系,很好扩展!)
  2. 大数据量高性能(Redis一秒可以写8万次,读11万次,NoSQL的缓存记录级,是一种细粒度的缓存,性能会比较高!)
  3. 数据类型是多样型的!(不需要事先设计数据库,随取随用)
  4. 传统的 RDBMS 和 NoSQL
传统的 RDBMS(关系型数据库)
- 结构化组织
- SQL
- 数据和关系都存在单独的表中 row col
- 操作,数据定义语言
- 严格的一致性
- 基础的事务
- ...
Nosql
- 不仅仅是数据
- 没有固定的查询语言
- 键值对存储,列存储,文档存储,图形数据库(社交关系)
- 最终一致性
- CAP定理和BASE
- 高性能,高可用,高扩展
- ...

了解:3V + 3高

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

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

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

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

真正在公司中的实践:NoSQL + RDBMS 一起使用才是最强的。

阿里巴巴演进分析

推荐阅读:阿里云的这群疯子https://yq.aliyun.com/articles/653511

1

在这里插入图片描述

# 商品信息
- 一般存放在关系型数据库:Mysql,阿里巴巴使用的Mysql都是经过内部改动的。

# 商品描述、评论(文字居多)
- 文档型数据库:MongoDB

# 图片
- 分布式文件系统 FastDFS
- 淘宝:TFS
- Google: GFS
- Hadoop: HDFS
- 阿里云: oss

# 商品关键字 用于搜索
- 搜索引擎:solr,elasticsearch
- 阿里:Isearch 多隆

# 商品热门的波段信息
- 内存数据库:Redis,Memcache

# 商品交易,外部支付接口
- 第三方应用

Nosql的四大分类

KV键值对

  • 新浪:Redis
  • 美团:Redis + Tair
  • 阿里、百度:Redis + Memcache

文档型数据库(bson数据格式):

  • MongoDB(掌握)
    • 基于分布式文件存储的数据库。C++编写,用于处理大量文档。
    • MongoDB是RDBMS和NoSQL的中间产品。MongoDB是非关系型数据库中功能最丰富的,NoSQL中最像关系型数据库的数据库。
  • ConthDB

列存储数据库

  • HBase(大数据必学)
  • 分布式文件系统

图关系数据库

用于广告推荐,社交网络

  • Neo4j、InfoGrid

4OvaB6.png

二、Redis入门

概述

Redis是什么?

Redis(Remote Dictionary Server ),即远程字典服务。

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

与memcached一样,为了保证效率,数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。

Redis能该干什么?

  1. 内存存储、持久化,内存是断电即失的,所以需要持久化(RDB、AOF)
  2. 高效率、用于高速缓冲
  3. 发布订阅系统
  4. 地图信息分析
  5. 计时器、计数器(eg:浏览量)
  6. 。。。

特性

  1. 多样的数据类型

  2. 持久化

  3. 集群

  4. 事务

环境搭建

官网:https://redis.io/

推荐使用Linux服务器学习。

windows版本的Redis已经停更很久了…

Windows安装

https://github.com/dmajkic/redis

  1. 解压安装包
    在这里插入图片描述
  2. 开启redis-server.exe
  3. 启动redis-cli.exe测试在这里插入图片描述

Linux安装

下载安装包!redis-5.0.8.tar.gz

解压Redis的安装包!程序一般放在 /opt 目录下

[root@tianhong /]# cd /home/tianhong	//现在存的位置
[root@tianhong tianhong]# mv redis-6.2.5.tar.gz /opt
[root@tianhong tianhong]# cd /opt
[root@tianhong opt]# ls
[root@tianhong opt]# tar -zxvf redis-6.2.5.tar.gz  //解压
[root@tianhong opt]# ls	//查看

[root@tianhong opt]# cd redis-6.2.5	//进入解压目录

基本环境安装

yum install gcc-c++
# 然后进入redis目录下执行
make
# 然后执行
make install
  • redis默认安装路径 /usr/local/bin

  • 将redis的配置文件复制到 程序安装目录 /usr/local/bin/kconfig

[root@tianhong redis-6.2.5]# cd /usr/local/bin	//1.redis默认安装路径 /usr/local/bin
[root@tianhong bin]# ls
chardetect	easy_install-3.6  jsondiff	    luajit	     redis-check-aof
cloud-id	easy_install-3.8  jsonpatch	    luajit-2.0.4     redis-check-rdb
cloud-init	jemalloc-config   jsonpointer	    mcrypt	     redis-cli
cloud-init-per	jemalloc.sh	  jsonschema	    mdecrypt	     redis-sentinel
easy_install	jeprof		  libmcrypt-config  redis-benchmark  redis-server
[root@tianhong bin]# mkdir myconfig
[root@tianhong bin]# cp /opt/redis-6.2.5/redis.conf myconfig 	//2.将redis的配置文件复制到 程序安装目录 /usr/local/bin/kconfig下
[root@tianhong bin]# cd  myconfig/
[root@tianhong myconfig]# ls
redis.conf
[root@tianhong myconfig]# vim redis.conf	//3.redis默认不是后台启动的,需要修改配置文件!//daemonize 改成yes保存退出
[root@tianhong myconfig]# cd ..
[root@tianhong bin]# redis-server myconfig/redis.conf 	//4.通过制定的配置文件启动redis服务
[root@tianhong bin]# redis-cli -p 6379	//5.使用redis-cli连接指定的端口号测试,Redis的默认端口6379
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> set name tianhong
OK
127.0.0.1:6379> get name
"tianhong"
  1. 查看redis进程是否开启(重连一个)
ps -ef|grep redis

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9PhN1jC1-1597890996513)(狂神说 Redis.assets/image-20200813114103769.png)]

  1. 关闭Redis服务 shutdown

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Y54EuOYm-1597890996514)(狂神说 Redis.assets/image-20200813114116691.png)]

  2. 再次查看进程是否存在

  3. 后面我们会使用单机多Redis启动集群测试

测试性能

redis-benchmark:Redis官方提供的性能测试工具,参数选项如下:

img

简单测试:

先启动服务
cd /usr/local/bin
redis-server myconfig/redis.conf
# 测试:100个并发连接 100000请求
redis-benchmark -h localhost -p 6379 -c 100 -n 100000

在这里插入图片描述

在这里插入图片描述

我花了14秒丢人了

基础知识

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是否存在

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
后面不会的命令可以在官网查 在这里插入图片描述

三、五大基本数据类型

string(字符串)

90%的程序员使用redis只会使用string类型api调用工程师
在这里插入图片描述

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除了字符串还可以是数字

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

list

在redis里面可以把list完成栈,队列,阻塞队列!
所有的list命令以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
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> lrange list 0 1
1) "three"
2) "two"
倒着输出

在这里插入图片描述

默认象栈
移除
lpop
rpop
在这里插入图片描述
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

在这里插入图片描述

trim 修剪:list截断

在这里插入图片描述
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站共同关注(交集)
数字集合类:
差集,交集,并集
在这里插入图片描述

微博,将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事务

  • 开启事务(multi)=
  • 命令入队
  • 执行事务(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可以当作乐观锁操作

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

在这里插入图片描述

执行事务之前另一个线程修改值
在这里插入图片描述

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十分熟悉

测试

新建空项目

在这里插入图片描述

在这里插入图片描述

2.新建新的maven模块
在这里插入图片描述

导入依赖

<!--导入jredis的包-->
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>3.2.0</version>
</dependency>
<!--fastjson-->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.70</version>
</dependency>

编码测试

a.连接数据库
b.操作命令
c.断开连接

开放端口6379

firewall-cmd --zone=public --add-port=6379/tcp --permanet

重启防火墙服务

systemctl restart firewalld.service

重启redis-server

redis-server myconfig/redis.conf 

操作命令

TestPing.java

public class TestPing {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("192.168.xx.xxx", 6379);
        String response = jedis.ping();
        System.out.println(response); // PONG
    }
}

事务

public class TestTX {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("39.99.xxx.xx", 6379);

        JSONObject jsonObject = new JSONObject();
        jsonObject.put("hello", "world");
        jsonObject.put("name", "kuangshen");
        // 开启事务
        Transaction multi = jedis.multi();
        String result = jsonObject.toJSONString();
        // jedis.watch(result)
        try {
            multi.set("user1", result);
            multi.set("user2", result);
            // 执行事务
            multi.exec();
        }catch (Exception e){
            // 放弃事务
            multi.discard();
        } finally {
            // 关闭连接
            System.out.println(jedis.get("user1"));
            System.out.println(jedis.get("user2"));
            jedis.close();
        }
    }
}

在这里插入图片描述
所有api都是上面对应的命令

通过Jedis理解事务

在这里插入图片描述

在这里插入图片描述

八、springboot整合

整合测试

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

  1. 导入依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

springboot 2.x后 ,原来使用的 Jedis 被 lettuce 替换

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

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

我们在学习SpringBoot自动配置的原理时,整合一个组件并进行配置一定会有一个自动配置类xxxAutoConfiguration,并且在spring.factories中也一定能找到这个类的完全限定名。Redis也不例外。

在这里插入图片描述

那么就一定还存在一个RedisProperties类

在这里插入图片描述

之前我们说SpringBoot2.x后默认使用Lettuce来替换Jedis,现在我们就能来验证了。

先看Jedis:

在这里插入图片描述

@ConditionalOnClass注解中有两个类是默认不存在的,所以Jedis是无法生效的

然后再看Lettuce:

在这里插入图片描述

完美生效。

现在我们回到RedisAutoConfiguratio

img

只有两个简单的Bean

  • RedisTemplate
  • StringRedisTemplate

当看到xxTemplate时可以对比RestTemplat、SqlSessionTemplate,通过使用这些Template来间接操作组件。那么这俩也不会例外。分别用于操作Redis和Redis中的String数据类型。

在RedisTemplate上也有一个条件注解,说明我们是可以对其进行定制化的

说完这些,我们需要知道如何编写配置文件然后连接Redis,就需要阅读RedisProperties

在这里插入图片描述

这是一些基本的配置属性。

在这里插入图片描述

还有一些连接池相关的配置。注意使用时一定使用Lettuce的连接池。

在这里插入图片描述

1、编写配置文件

# 配置redis,看着写
spring.redis.host=127.0.0.1		
spring.redis.port=6379

2、 使用RedisTemplate

@SpringBootTest
class Redis02SpringbootApplicationTests {

    @Autowired
    private RedisTemplate redisTemplate;

    @Test
    void contextLoads() {

        // redisTemplate 操作不同的数据类型,api和我们的指令是一样的
        // opsForValue 操作字符串 类似String
        // opsForList 操作List 类似List
        // opsForHah

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

        // 获取连接对象
        //RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
        //connection.flushDb();
        //connection.flushAll();

        redisTemplate.opsForValue().set("mykey","kuangshen");
        System.out.println(redisTemplate.opsForValue().get("mykey"));
    }
}

3、测试结果

此时我们回到Redis查看数据时候,惊奇发现全是乱码,可是程序中可以正常输出:

这时候就关系到存储对象的序列化问题,在网络中传输的对象也是一样需要序列化,否者就全是乱码。

我们转到看那个默认的RedisTemplate内部什么样子:

在这里插入图片描述

在最开始就能看到几个关于序列化的参数。

默认的序列化器是采用JDK序列化器

在这里插入图片描述

而默认的RedisTemplate中的所有序列化器都是使用这个序列化器:

在这里插入图片描述

后续我们定制RedisTemplate就可以对其进行修改。

RedisSerializer提供了多种序列化方案:

  1. 直接调用RedisSerializer的静态方法来返回序列化器,然后set

在这里插入图片描述

  1. 自己new 相应的实现类,然后set

在这里插入图片描述

定制RedisTemplate的模板:

我们创建一个Bean加入容器,就会触发RedisTemplate上的条件注解使默认的RedisTemplate失效。

@Configuration
public class RedisConfig {

   @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
        // 将template 泛型设置为 <String, Object>
        RedisTemplate<String, Object> template = new RedisTemplate();
        // 连接工厂,不必修改
        template.setConnectionFactory(redisConnectionFactory);
        /*
         * 序列化设置
         */
        // key、hash的key 采用 String序列化方式
        template.setKeySerializer(RedisSerializer.string());
        template.setHashKeySerializer(RedisSerializer.string());
        // value、hash的value 采用 Jackson 序列化方式
        template.setValueSerializer(RedisSerializer.json());
        template.setHashValueSerializer(RedisSerializer.json());
        template.afterPropertiesSet();
        
        return template;
    }
}

这样一来,只要实体类进行了序列化,我们存什么都不会有乱码的担忧了。

在这里插入图片描述

乱码问题得到解决

在这里插入图片描述

自定义Redis工具类

使用RedisTemplate需要频繁调用.opForxxx然后才能进行对应的操作,这样使用起来代码效率低下,工作中一般不会这样使用,而是将这些常用的公共API抽取出来封装成为一个工具类,然后直接使用工具类来间接操作Redis,不但效率高并且易用。

在企业开发中我们80%不会使用原生的方式去编写代码,一般使用工具类RedisUtils
网上可以直接搜索RedisUtils

工具类参考博客:

https://www.cnblogs.com/zeng1994/p/03303c805731afc9aa9c60dbbd32a323.html

https://www.cnblogs.com/zhzhlong/p/11434284.html

九、Redis.conf

容量单位不区分大小写,G和GB有区别

img

可以使用 include 组合多个配置问题

在这里插入图片描述

网络配置

在这里插入图片描述

日志输出级别

img

日志输出文件

在这里插入图片描述

持久化规则

由于Redis是基于内存的数据库,需要将数据由内存持久化到文件中

持久化方式:

  • RDB
  • AOF

在这里插入图片描述

RDB文件相关

在这里插入图片描述

在这里插入图片描述

主从复制

在这里插入图片描述

Security模块中进行密码设置

在这里插入图片描述

客户端连接相关

maxclients 10000  最大客户端数量
maxmemory <bytes> 最大内存限制
maxmemory-policy noeviction # 内存达到限制值的处理策略

redis 中的默认的过期策略是 volatile-lru

设置方式

config set maxmemory-policy volatile-lru 

maxmemory-policy 六种方式

  1. volatile-lru:只对设置了过期时间的key进行LRU(默认值)
  2. allkeys-lru : 删除lru算法的key
  3. volatile-random:随机删除即将过期key
  4. allkeys-random:随机删除
  5. volatile-ttl : 删除即将过期的
  6. noeviction : 永不过期,返回错误

AOF相关部分

在这里插入图片描述

在这里插入图片描述

十、持久化—RDB

面试和工作的重点

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

在指定时间间隔后,将内存中的数据集快照写入数据库 ;在恢复时候,直接读取快照文件,进行数据的恢复 ;

在这里插入图片描述

默认情况下, Redis 将数据库快照保存在名字为 dump.rdb的二进制文件中。文件名可以在配置文件中进行自定义。

工作原理


在进行 RDB 的时候,redis 的主线程是不会做 io 操作的,主线程会 fork 一个子线程来完成该操作;

  1. Redis 调用forks。同时拥有父进程和子进程。
  2. 子进程将数据集写入到一个临时 RDB 文件中。
  3. 当子进程完成对新 RDB 文件的写入时,Redis 用新 RDB 文件替换原来的 RDB 文件,并删除旧的 RDB 文件。

这种工作方式使得 Redis 可以从写时复制(copy-on-write)机制中获益(因为是使用子进程进行写操作,而父进程依然可以接收来自客户端的请求。)

在这里插入图片描述

触发机制

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

save

使用 save 命令,会立刻对当前内存中的数据进行持久化 ,但是会阻塞,也就是不接受其他操作了;

由于 save 命令是同步命令,会占用Redis的主进程。若Redis数据非常多时,save命令执行速度会非常慢,阻塞所有客户端的请求。

在这里插入图片描述

flushall命令

flushall 命令也会触发持久化 ;

触发持久化规则

满足配置条件中的触发条件 ;

可以通过配置文件对 Redis 进行设置, 让它在“ N 秒内数据集至少有 M 个改动”这一条件被满足时, 自动进行数据集保存操作。

在这里插入图片描述

在这里插入图片描述

bgsave

bgsave 是异步进行,进行持久化的时候,redis 还可以将继续响应客户端请求 ;

在这里插入图片描述

bgsave和save对比

命令 save bgsave
IO类型 同步 异步
阻塞? 是(阻塞发生在fock(),通常非常快)
复杂度 O(n) O(n)
优点 不会消耗额外的内存 不阻塞客户端命令
缺点 阻塞客户端命令 需要fock子进程,消耗内存

在这里插入图片描述

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

在这里插入图片描述

优缺点

优点:

  1. 适合大规模的数据恢复
  2. 对数据的完整性要求不高

缺点:

  1. 需要一定的时间间隔进行操作,如果redis意外宕机了,这个最后一次修改的数据就没有了。
  2. fork进程的时候,会占用一定的内容空间。

十一、持久化AOF

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

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

aof(append only file)保存的文件是appendonly.aof

默认为no需要手动进行开启

在这里插入图片描述

重启redis后读取.aof文件
在这里插入图片描述

在这里插入图片描述

如果aof文件有错误,这时候redis启动不起来,我们需要修复配置文件

redis给我们提供了一个工具

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

在这里插入图片描述

aof默认是文件无线追加,文件会越来越大
如果aof文件大于64m,fork一个新进程将我们的文件进行重写
在这里插入图片描述

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

RDB和AOP选择

RDB 和 AOF 对比

RDB AOF
启动优先级
体积
恢复速度
数据安全性 丢数据 根据策略决定

如何选择使用哪种持久化方式?

一般来说, 如果想达到足以媲美 PostgreSQL 的数据安全性, 你应该同时使用两种持久化功能。

如果你非常关心你的数据, 但仍然可以承受数分钟以内的数据丢失, 那么你可以只使用 RDB 持久化。

有很多用户都只使用 AOF 持久化, 但并不推荐这种方式: 因为定时生成 RDB 快照(snapshot)非常便于进行数据库备份, 并且 RDB 恢复数据集的速度也要比 AOF 恢复的速度要快。

十二、Redis发布与订阅

Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。

下图展示了频道 channel1 , 以及订阅这个频道的三个客户端 —— client2 、 client5 和 client1 之间的关系:

在这里插入图片描述

当有新消息通过 PUBLISH 命令发送给频道 channel1 时, 这个消息就会被发送给订阅它的三个客户端:

在这里插入图片描述

命令

  • PSUBSCRIBE pattern [pattern..] 订阅一个或多个符合给定模式的频道。
  • PUNSUBSCRIBE pattern [pattern..] 退订一个或多个符合给定模式的频道。
  • PUBSUB subcommand [argument[argument]] 查看订阅与发布系统状态。
  • PUBLISH channel message 向指定频道发布消息
  • SUBSCRIBE channel [channel..] 订阅给定的一个或多个频道。
  • SUBSCRIBE channel [channel..] 退订一个或多个频道

示例

------------订阅端----------------------
127.0.0.1:6379> SUBSCRIBE sakura # 订阅sakura频道
Reading messages... (press Ctrl-C to quit) # 等待接收消息
1) "subscribe" # 订阅成功的消息
2) "sakura"
3) (integer) 1
1) "message" # 接收到来自sakura频道的消息 "hello world"
2) "sakura"
3) "hello world"
1) "message" # 接收到来自sakura频道的消息 "hello i am sakura"
2) "sakura"
3) "hello i am sakura"

--------------消息发布端-------------------
127.0.0.1:6379> PUBLISH sakura "hello world" # 发布消息到sakura频道
(integer) 1
127.0.0.1:6379> PUBLISH sakura "hello i am sakura" # 发布消息
(integer) 1

-----------------查看活跃的频道------------
127.0.0.1:6379> PUBSUB channels
1) "sakura"

原理
每个 Redis 服务器进程都维持着一个表示服务器状态的 redis.h/redisServer 结构, 结构的 pubsub_channels 属性是一个字典, 这个字典就用于保存订阅频道的信息,其中,字典的键为正在被订阅的频道, 而字典的值则是一个链表, 链表中保存了所有订阅这个频道的客户端。

在这里插入图片描述

客户端订阅,就被链接到对应频道的链表的尾部,退订则就是将客户端节点从链表中移除。

缺点

  • 如果一个客户端订阅了频道,但自己读取消息的速度却不够快的话,那么不断积压的消息会使redis输出缓冲区的体积变得越来越大,这可能使得redis本身的速度变慢,甚至直接崩溃。
  • 这和数据传输可靠性有关,如果在订阅方断线,那么他将会丢失所有在短线期间发布者发布的消息。

应用

  1. 消息订阅:公众号订阅,微博关注等等(起始更多是使用消息队列来进行实现)
  2. 多人在线聊天室。

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

十四、Redis主从复制

概念和作用

概念

主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(Master/Leader),后者称为从节点(Slave/Follower), 数据的复制是单向的!只能由主节点复制到从节点(主节点以写为主、从节点以读为主)。

默认情况下,每台Redis服务器都是主节点,一个主节点可以有0个或者多个从节点,但每个从节点只能由一个主节点。

作用

  1. 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余的方式。
  2. 故障恢复:当主节点故障时,从节点可以暂时替代主节点提供服务,是一种服务冗余的方式
  3. 负载均衡:在主从复制的基础上,配合读写分离,由主节点进行写操作,从节点进行读操作,分担服务器的负载;尤其是在多读少写的场景下,通过多个从节点分担负载,提高并发量。
  4. 高可用基石:主从复制还是哨兵和集群能够实施的基础。

为什么使用集群

  1. 单台服务器难以负载大量的请求
  2. 单台服务器故障率高,系统崩坏概率大
  3. 单台服务器内存容量有限。

环境配置

127.0.0.1:6379> info replication
# Replication
role:master # 角色
connected_slaves:0 # 从机数量
master_replid:3b54deef5b7b7b7f7dd8acefa23be48879b4fcff
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

既然需要启动多个服务,就需要多个配置文件。每个配置文件对应修改以下信息:

  • 端口号
  • pid文件名
  • 日志文件名
  • rdb文件名

启动单机多服务集群:

在这里插入图片描述

一主二从配置

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

认老大!一主(79)二从(80,81)

使用SLAVEOF host port就可以为从机配置主机了。

在这里插入图片描述

然后主机上也能看到从机的状态:

在这里插入图片描述

我们这里是使用命令搭建,是暂时的,真实开发中应该在从机的配置文件中进行配置,这样的话是永久的。

在这里插入图片描述

使用规则

  1. 从机只能读,不能写,主机可读可写但是多用于写。
 127.0.0.1:6381> set name sakura # 从机6381写入失败
(error) READONLY You can't write against a read only replica.

127.0.0.1:6380> set name sakura # 从机6380写入失败
(error) READONLY You can't write against a read only replica.

127.0.0.1:6379> set name sakura
OK
127.0.0.1:6379> get name
"sakura"
  1. 当主机断电宕机后,默认情况下从机的角色不会发生变化 ,集群中只是失去了写操作,当主机恢复以后,又会连接上从机恢复原状。

  2. 当从机断电宕机后,若不是使用配置文件配置的从机,再次启动后作为主机是无法获取之前主机的数据的,若此时重新配置称为从机,又可以获取到主机的所有数据。这里就要提到一个同步原理。

  3. 第二条中提到,默认情况下,主机故障后,不会出现新的主机,有两种方式可以产生新的主机:

  • 从机手动执行命令slaveof no one,这样执行以后从机会独立出来成为一个主机
  • 使用哨兵模式(自动选举)

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

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

十五、哨兵模式

更多信息参考博客:https://www.jianshu.com/p/06ab9daf921d

主从切换技术的方法是:当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工干预,费事费力,还会造成一段时间内服务不可用。这不是一种推荐的方式,更多时候,我们优先考虑哨兵模式

单机单个哨兵

5PhKPg.png

哨兵的作用:

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

多哨兵模式

5PhQ2j.png

哨兵的核心配置

sentinel monitor mymaster 127.0.0.1 6379
  • 数字1表示 :当一个哨兵主观认为主机断开,就可以客观认为主机故障,然后开始选举新的主机。

测试

redis-sentinel xxx/sentinel.conf

成功启动哨兵模式

在这里插入图片描述

此时哨兵监视着我们的主机6379,当我们断开主机后:

在这里插入图片描述

哨兵模式优缺点

优点:

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

缺点:

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

哨兵模式的全部配置

完整的哨兵模式配置文件 sentinel.conf

# Example sentinel.conf
 
# 哨兵sentinel实例运行的端口 默认26379
port 26379
 
# 哨兵sentinel的工作目录
dir /tmp
 
# 哨兵sentinel监控的redis主节点的 ip port 
# master-name  可以自己命名的主节点名字 只能由字母A-z、数字0-9 、这三个字符".-_"组成。
# quorum 当这些quorum个数sentinel哨兵认为master主节点失联 那么这时 客观上认为主节点失联了
# sentinel monitor <master-name> <ip> <redis-port> <quorum>
sentinel monitor mymaster 127.0.0.1 6379 1
 
# 当在Redis实例中开启了requirepass foobared 授权密码 这样所有连接Redis实例的客户端都要提供密码
# 设置哨兵sentinel 连接主从的密码 注意必须为主从设置一样的验证密码
# sentinel auth-pass <master-name> <password>
sentinel auth-pass mymaster MySUPER--secret-0123passw0rd
 
 
# 指定多少毫秒之后 主节点没有应答哨兵sentinel 此时 哨兵主观上认为主节点下线 默认30秒
# sentinel down-after-milliseconds <master-name> <milliseconds>
sentinel down-after-milliseconds mymaster 30000
 
# 这个配置项指定了在发生failover主备切换时最多可以有多少个slave同时对新的master进行 同步,
这个数字越小,完成failover所需的时间就越长,
但是如果这个数字越大,就意味着越 多的slave因为replication而不可用。
可以通过将这个值设为 1 来保证每次只有一个slave 处于不能处理命令请求的状态。
# sentinel parallel-syncs <master-name> <numslaves>
sentinel parallel-syncs mymaster 1
 
 
 
# 故障转移的超时时间 failover-timeout 可以用在以下这些方面: 
#1. 同一个sentinel对同一个master两次failover之间的间隔时间。
#2. 当一个slave从一个错误的master那里同步数据开始计算时间。直到slave被纠正为向正确的master那里同步数据时。
#3.当想要取消一个正在进行的failover所需要的时间。  
#4.当进行failover时,配置所有slaves指向新的master所需的最大时间。不过,即使过了这个超时,slaves依然会被正确配置为指向master,但是就不按parallel-syncs所配置的规则来了
# 默认三分钟
# sentinel failover-timeout <master-name> <milliseconds>
sentinel failover-timeout mymaster 180000
 
# SCRIPTS EXECUTION
 
#配置当某一事件发生时所需要执行的脚本,可以通过脚本来通知管理员,例如当系统运行不正常时发邮件通知相关人员。
#对于脚本的运行结果有以下规则:
#若脚本执行后返回1,那么该脚本稍后将会被再次执行,重复次数目前默认为10
#若脚本执行后返回2,或者比2更高的一个返回值,脚本将不会重复执行。
#如果脚本在执行过程中由于收到系统中断信号被终止了,则同返回值为1时的行为相同。
#一个脚本的最大执行时间为60s,如果超过这个时间,脚本将会被一个SIGKILL信号终止,之后重新执行。
 
#通知型脚本:当sentinel有任何警告级别的事件发生时(比如说redis实例的主观失效和客观失效等等),将会去调用这个脚本,
#这时这个脚本应该通过邮件,SMS等方式去通知系统管理员关于系统不正常运行的信息。调用该脚本时,将传给脚本两个参数,
#一个是事件的类型,
#一个是事件的描述。
#如果sentinel.conf配置文件中配置了这个脚本路径,那么必须保证这个脚本存在于这个路径,并且是可执行的,否则sentinel无法正常启动成功。
#通知脚本
# sentinel notification-script <master-name> <script-path>
  sentinel notification-script mymaster /var/redis/notify.sh
 
# 客户端重新配置主节点参数脚本
# 当一个master由于failover而发生改变时,这个脚本将会被调用,通知相关的客户端关于master地址已经发生改变的信息。
# 以下参数将会在调用脚本时传给脚本:
# <master-name> <role> <state> <from-ip> <from-port> <to-ip> <to-port>
# 目前<state>总是“failover”,
# <role>是“leader”或者“observer”中的一个。 
# 参数 from-ip, from-port, to-ip, to-port是用来和旧的master和新的master(即旧的slave)通信的
# 这个脚本应该是通用的,能被多次调用,不是针对性的。
# sentinel client-reconfig-script <master-name> <script-path>
sentinel client-reconfig-script mymaster /var/redis/reconfig.sh

十六、缓存穿透与雪崩

缓存穿透(查不到)

概念

在默认情况下,用户请求数据时,会先在缓存(Redis)中查找,若没找到即缓存未命中,再在数据库中进行查找,数量少可能问题不大,可是一旦大量的请求数据(例如秒杀场景)缓存都没有命中的话,就会全部转移到数据库上,造成数据库极大的压力,就有可能导致数据库崩溃。网络安全中也有人恶意使用这种手段进行攻击被称为洪水攻击。

解决方案

布隆过滤器

对所有可能查询的参数以Hash的形式存储,以便快速确定是否存在这个值,在控制层先进行拦截校验,校验不通过直接打回,减轻了存储系统的压力。

在这里插入图片描述

缓存空对象

一次请求若在缓存和数据库中都没找到,就在缓存中方一个空对象用于处理后续这个请求。

在这里插入图片描述

这样做有一个缺陷:存储空对象也需要空间,大量的空对象会耗费一定的空间,存储效率并不高。解决这个缺陷的方式就是设置较短过期时间

即使对空值设置了过期时间,还是会存在缓存层和存储层的数据会有一段时间窗口的不一致,这对于需要保持一致性的业务会有影响。

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

概念

相较于缓存穿透,缓存击穿的目的性更强,一个存在的key,在缓存过期的一刻,同时有大量的请求,这些请求都会击穿到DB,造成瞬时DB请求量大、压力骤增。这就是缓存被击穿,只是针对其中某个key的缓存不可用而导致击穿,但是其他的key依然可以使用缓存响应。

比如热搜排行上,一个热点新闻被同时大量访问就可能导致缓存击穿。

解决方案

  1. 设置热点数据永不过期

这样就不会出现热点数据过期的情况,但是当Redis内存空间满的时候也会清理部分数据,而且此种方案会占用空间,一旦热点数据多了起来,就会占用部分空间。

  1. 加互斥锁(分布式锁)

在访问key之前,采用SETNX(set if not exists)来设置另一个短期key来锁住当前key的访问,访问结束再删除该短期key。保证同时刻只有一个线程访问。这样对锁的要求就十分高。

缓存雪崩

概念

大量的key设置了相同的过期时间,导致在缓存在同一时刻全部失效,造成瞬时DB请求量大、压力骤增,引起雪崩。

在这里插入图片描述

解决方案

  • redis高可用

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

  • 限流降级

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

  • 数据预热

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

笔记参考自:https://blog.csdn.net/weixin_43873227/article/details/106107270 > 搭配狂神说 Redis视频一起学习
狂神视频

一、Nosql概述

为什么使用Nosql

1、单机Mysql时代

在这里插入图片描述

90年代,一个网站的访问量一般不会太大,单个数据库完全够用。随着用户增多,网站出现以下问题

  1. 数据量增加到一定程度,单机数据库就放不下了
  2. 数据的索引(B+ Tree),一个机器内存也存放不下
  3. 访问量变大后(读写混合),一台服务器承受不住。

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

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

在这里插入图片描述

优化过程经历了以下几个过程:

  1. 优化数据库的数据结构和索引(难度大)
  2. 文件缓存,通过IO流获取比每次都访问数据库效率略高,但是流量爆炸式增长时候,IO流也承受不了
  3. MemCache,当时最热门的技术,通过在数据库和数据库访问层之间加上一层缓存,第一次访问时查询数据库,将结果保存到缓存,后续的查询先检查缓存,若有直接拿去使用,效率显著提升。

3、分库分表 + 水平拆分 + Mysql集群

在这里插入图片描述

4、如今最近的年代

如今信息量井喷式增长,各种各样的数据出现(用户定位数据,图片数据等),大数据的背景下关系型数据库(RDBMS)无法满足大量数据要求。Nosql数据库就能轻松解决这些问题。

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

在这里插入图片描述

为什么要用NoSQL ?

用户的个人信息,社交网络,地理位置。用户自己产生的数据,用户日志等等爆发式增长!
这时候我们就需要使用NoSQL数据库的,Nosql可以很好的处理以上的情况!

什么是Nosql

NoSQL = Not Only SQL(不仅仅是SQL)

Not Only Structured Query Language

关系型数据库:列+行,同一个表下数据的结构是一样的。

非关系型数据库:数据存储没有固定的格式,并且可以进行横向扩展。

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

Nosql特点

  1. 方便扩展(数据之间没有关系,很好扩展!)
  2. 大数据量高性能(Redis一秒可以写8万次,读11万次,NoSQL的缓存记录级,是一种细粒度的缓存,性能会比较高!)
  3. 数据类型是多样型的!(不需要事先设计数据库,随取随用)
  4. 传统的 RDBMS 和 NoSQL
传统的 RDBMS(关系型数据库)
- 结构化组织
- SQL
- 数据和关系都存在单独的表中 row col
- 操作,数据定义语言
- 严格的一致性
- 基础的事务
- ...
Nosql
- 不仅仅是数据
- 没有固定的查询语言
- 键值对存储,列存储,文档存储,图形数据库(社交关系)
- 最终一致性
- CAP定理和BASE
- 高性能,高可用,高扩展
- ...

了解:3V + 3高

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

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

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

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

真正在公司中的实践:NoSQL + RDBMS 一起使用才是最强的。

阿里巴巴演进分析

推荐阅读:阿里云的这群疯子https://yq.aliyun.com/articles/653511

1

在这里插入图片描述

# 商品信息
- 一般存放在关系型数据库:Mysql,阿里巴巴使用的Mysql都是经过内部改动的。

# 商品描述、评论(文字居多)
- 文档型数据库:MongoDB

# 图片
- 分布式文件系统 FastDFS
- 淘宝:TFS
- Google: GFS
- Hadoop: HDFS
- 阿里云: oss

# 商品关键字 用于搜索
- 搜索引擎:solr,elasticsearch
- 阿里:Isearch 多隆

# 商品热门的波段信息
- 内存数据库:Redis,Memcache

# 商品交易,外部支付接口
- 第三方应用

Nosql的四大分类

KV键值对

  • 新浪:Redis
  • 美团:Redis + Tair
  • 阿里、百度:Redis + Memcache

文档型数据库(bson数据格式):

  • MongoDB(掌握)
    • 基于分布式文件存储的数据库。C++编写,用于处理大量文档。
    • MongoDB是RDBMS和NoSQL的中间产品。MongoDB是非关系型数据库中功能最丰富的,NoSQL中最像关系型数据库的数据库。
  • ConthDB

列存储数据库

  • HBase(大数据必学)
  • 分布式文件系统

图关系数据库

用于广告推荐,社交网络

  • Neo4j、InfoGrid

4OvaB6.png

二、Redis入门

概述

Redis是什么?

Redis(Remote Dictionary Server ),即远程字典服务。

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

与memcached一样,为了保证效率,数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。

Redis能该干什么?

  1. 内存存储、持久化,内存是断电即失的,所以需要持久化(RDB、AOF)
  2. 高效率、用于高速缓冲
  3. 发布订阅系统
  4. 地图信息分析
  5. 计时器、计数器(eg:浏览量)
  6. 。。。

特性

  1. 多样的数据类型

  2. 持久化

  3. 集群

  4. 事务

环境搭建

官网:https://redis.io/

推荐使用Linux服务器学习。

windows版本的Redis已经停更很久了…

Windows安装

https://github.com/dmajkic/redis

  1. 解压安装包
    在这里插入图片描述
  2. 开启redis-server.exe
  3. 启动redis-cli.exe测试在这里插入图片描述

Linux安装

下载安装包!redis-5.0.8.tar.gz

解压Redis的安装包!程序一般放在 /opt 目录下

[root@tianhong /]# cd /home/tianhong	//现在存的位置
[root@tianhong tianhong]# mv redis-6.2.5.tar.gz /opt
[root@tianhong tianhong]# cd /opt
[root@tianhong opt]# ls
[root@tianhong opt]# tar -zxvf redis-6.2.5.tar.gz  //解压
[root@tianhong opt]# ls	//查看

[root@tianhong opt]# cd redis-6.2.5	//进入解压目录

基本环境安装

yum install gcc-c++
# 然后进入redis目录下执行
make
# 然后执行
make install
  • redis默认安装路径 /usr/local/bin

  • 将redis的配置文件复制到 程序安装目录 /usr/local/bin/kconfig

[root@tianhong redis-6.2.5]# cd /usr/local/bin	//1.redis默认安装路径 /usr/local/bin
[root@tianhong bin]# ls
chardetect	easy_install-3.6  jsondiff	    luajit	     redis-check-aof
cloud-id	easy_install-3.8  jsonpatch	    luajit-2.0.4     redis-check-rdb
cloud-init	jemalloc-config   jsonpointer	    mcrypt	     redis-cli
cloud-init-per	jemalloc.sh	  jsonschema	    mdecrypt	     redis-sentinel
easy_install	jeprof		  libmcrypt-config  redis-benchmark  redis-server
[root@tianhong bin]# mkdir myconfig
[root@tianhong bin]# cp /opt/redis-6.2.5/redis.conf myconfig 	//2.将redis的配置文件复制到 程序安装目录 /usr/local/bin/kconfig下
[root@tianhong bin]# cd  myconfig/
[root@tianhong myconfig]# ls
redis.conf
[root@tianhong myconfig]# vim redis.conf	//3.redis默认不是后台启动的,需要修改配置文件!//daemonize 改成yes保存退出
[root@tianhong myconfig]# cd ..
[root@tianhong bin]# redis-server myconfig/redis.conf 	//4.通过制定的配置文件启动redis服务
[root@tianhong bin]# redis-cli -p 6379	//5.使用redis-cli连接指定的端口号测试,Redis的默认端口6379
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> set name tianhong
OK
127.0.0.1:6379> get name
"tianhong"
  1. 查看redis进程是否开启(重连一个)
ps -ef|grep redis

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9PhN1jC1-1597890996513)(狂神说 Redis.assets/image-20200813114103769.png)]

  1. 关闭Redis服务 shutdown

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Y54EuOYm-1597890996514)(狂神说 Redis.assets/image-20200813114116691.png)]

  2. 再次查看进程是否存在

  3. 后面我们会使用单机多Redis启动集群测试

测试性能

redis-benchmark:Redis官方提供的性能测试工具,参数选项如下:

img

简单测试:

先启动服务
cd /usr/local/bin
redis-server myconfig/redis.conf
# 测试:100个并发连接 100000请求
redis-benchmark -h localhost -p 6379 -c 100 -n 100000

在这里插入图片描述

在这里插入图片描述

我花了14秒丢人了

基础知识

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是否存在

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
后面不会的命令可以在官网查 在这里插入图片描述

三、五大基本数据类型

string(字符串)

90%的程序员使用redis只会使用string类型api调用工程师
在这里插入图片描述

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除了字符串还可以是数字

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

list

在redis里面可以把list完成栈,队列,阻塞队列!
所有的list命令以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
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> lrange list 0 1
1) "three"
2) "two"
倒着输出

在这里插入图片描述

默认象栈
移除
lpop
rpop
在这里插入图片描述
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

在这里插入图片描述

trim 修剪:list截断

在这里插入图片描述
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站共同关注(交集)
数字集合类:
差集,交集,并集
在这里插入图片描述

微博,将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事务

  • 开启事务(multi)=
  • 命令入队
  • 执行事务(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可以当作乐观锁操作

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

在这里插入图片描述

执行事务之前另一个线程修改值
在这里插入图片描述

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十分熟悉

测试

新建空项目

在这里插入图片描述

在这里插入图片描述

2.新建新的maven模块
在这里插入图片描述

导入依赖

<!--导入jredis的包-->
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>3.2.0</version>
</dependency>
<!--fastjson-->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.70</version>
</dependency>

编码测试

a.连接数据库
b.操作命令
c.断开连接

开放端口6379

firewall-cmd --zone=public --add-port=6379/tcp --permanet

重启防火墙服务

systemctl restart firewalld.service

重启redis-server

redis-server myconfig/redis.conf 

操作命令

TestPing.java

public class TestPing {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("192.168.xx.xxx", 6379);
        String response = jedis.ping();
        System.out.println(response); // PONG
    }
}

事务

public class TestTX {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("39.99.xxx.xx", 6379);

        JSONObject jsonObject = new JSONObject();
        jsonObject.put("hello", "world");
        jsonObject.put("name", "kuangshen");
        // 开启事务
        Transaction multi = jedis.multi();
        String result = jsonObject.toJSONString();
        // jedis.watch(result)
        try {
            multi.set("user1", result);
            multi.set("user2", result);
            // 执行事务
            multi.exec();
        }catch (Exception e){
            // 放弃事务
            multi.discard();
        } finally {
            // 关闭连接
            System.out.println(jedis.get("user1"));
            System.out.println(jedis.get("user2"));
            jedis.close();
        }
    }
}

在这里插入图片描述
所有api都是上面对应的命令

通过Jedis理解事务

在这里插入图片描述

在这里插入图片描述

八、springboot整合

整合测试

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

  1. 导入依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

springboot 2.x后 ,原来使用的 Jedis 被 lettuce 替换

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

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

我们在学习SpringBoot自动配置的原理时,整合一个组件并进行配置一定会有一个自动配置类xxxAutoConfiguration,并且在spring.factories中也一定能找到这个类的完全限定名。Redis也不例外。

在这里插入图片描述

那么就一定还存在一个RedisProperties类

在这里插入图片描述

之前我们说SpringBoot2.x后默认使用Lettuce来替换Jedis,现在我们就能来验证了。

先看Jedis:

在这里插入图片描述

@ConditionalOnClass注解中有两个类是默认不存在的,所以Jedis是无法生效的

然后再看Lettuce:

在这里插入图片描述

完美生效。

现在我们回到RedisAutoConfiguratio

img

只有两个简单的Bean

  • RedisTemplate
  • StringRedisTemplate

当看到xxTemplate时可以对比RestTemplat、SqlSessionTemplate,通过使用这些Template来间接操作组件。那么这俩也不会例外。分别用于操作Redis和Redis中的String数据类型。

在RedisTemplate上也有一个条件注解,说明我们是可以对其进行定制化的

说完这些,我们需要知道如何编写配置文件然后连接Redis,就需要阅读RedisProperties

在这里插入图片描述

这是一些基本的配置属性。

在这里插入图片描述

还有一些连接池相关的配置。注意使用时一定使用Lettuce的连接池。

在这里插入图片描述

1、编写配置文件

# 配置redis,看着写
spring.redis.host=127.0.0.1		
spring.redis.port=6379

2、 使用RedisTemplate

@SpringBootTest
class Redis02SpringbootApplicationTests {

    @Autowired
    private RedisTemplate redisTemplate;

    @Test
    void contextLoads() {

        // redisTemplate 操作不同的数据类型,api和我们的指令是一样的
        // opsForValue 操作字符串 类似String
        // opsForList 操作List 类似List
        // opsForHah

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

        // 获取连接对象
        //RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
        //connection.flushDb();
        //connection.flushAll();

        redisTemplate.opsForValue().set("mykey","kuangshen");
        System.out.println(redisTemplate.opsForValue().get("mykey"));
    }
}

3、测试结果

此时我们回到Redis查看数据时候,惊奇发现全是乱码,可是程序中可以正常输出:

这时候就关系到存储对象的序列化问题,在网络中传输的对象也是一样需要序列化,否者就全是乱码。

我们转到看那个默认的RedisTemplate内部什么样子:

在这里插入图片描述

在最开始就能看到几个关于序列化的参数。

默认的序列化器是采用JDK序列化器

在这里插入图片描述

而默认的RedisTemplate中的所有序列化器都是使用这个序列化器:

在这里插入图片描述

后续我们定制RedisTemplate就可以对其进行修改。

RedisSerializer提供了多种序列化方案:

  1. 直接调用RedisSerializer的静态方法来返回序列化器,然后set

在这里插入图片描述

  1. 自己new 相应的实现类,然后set

在这里插入图片描述

定制RedisTemplate的模板:

我们创建一个Bean加入容器,就会触发RedisTemplate上的条件注解使默认的RedisTemplate失效。

@Configuration
public class RedisConfig {

   @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
        // 将template 泛型设置为 <String, Object>
        RedisTemplate<String, Object> template = new RedisTemplate();
        // 连接工厂,不必修改
        template.setConnectionFactory(redisConnectionFactory);
        /*
         * 序列化设置
         */
        // key、hash的key 采用 String序列化方式
        template.setKeySerializer(RedisSerializer.string());
        template.setHashKeySerializer(RedisSerializer.string());
        // value、hash的value 采用 Jackson 序列化方式
        template.setValueSerializer(RedisSerializer.json());
        template.setHashValueSerializer(RedisSerializer.json());
        template.afterPropertiesSet();
        
        return template;
    }
}

这样一来,只要实体类进行了序列化,我们存什么都不会有乱码的担忧了。

在这里插入图片描述

乱码问题得到解决

在这里插入图片描述

自定义Redis工具类

使用RedisTemplate需要频繁调用.opForxxx然后才能进行对应的操作,这样使用起来代码效率低下,工作中一般不会这样使用,而是将这些常用的公共API抽取出来封装成为一个工具类,然后直接使用工具类来间接操作Redis,不但效率高并且易用。

在企业开发中我们80%不会使用原生的方式去编写代码,一般使用工具类RedisUtils
网上可以直接搜索RedisUtils

工具类参考博客:

https://www.cnblogs.com/zeng1994/p/03303c805731afc9aa9c60dbbd32a323.html

https://www.cnblogs.com/zhzhlong/p/11434284.html

九、Redis.conf

容量单位不区分大小写,G和GB有区别

img

可以使用 include 组合多个配置问题

在这里插入图片描述

网络配置

在这里插入图片描述

日志输出级别

img

日志输出文件

在这里插入图片描述

持久化规则

由于Redis是基于内存的数据库,需要将数据由内存持久化到文件中

持久化方式:

  • RDB
  • AOF

在这里插入图片描述

RDB文件相关

在这里插入图片描述

在这里插入图片描述

主从复制

在这里插入图片描述

Security模块中进行密码设置

在这里插入图片描述

客户端连接相关

maxclients 10000  最大客户端数量
maxmemory <bytes> 最大内存限制
maxmemory-policy noeviction # 内存达到限制值的处理策略

redis 中的默认的过期策略是 volatile-lru

设置方式

config set maxmemory-policy volatile-lru 

maxmemory-policy 六种方式

  1. volatile-lru:只对设置了过期时间的key进行LRU(默认值)
  2. allkeys-lru : 删除lru算法的key
  3. volatile-random:随机删除即将过期key
  4. allkeys-random:随机删除
  5. volatile-ttl : 删除即将过期的
  6. noeviction : 永不过期,返回错误

AOF相关部分

在这里插入图片描述

在这里插入图片描述

十、持久化—RDB

面试和工作的重点

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

在指定时间间隔后,将内存中的数据集快照写入数据库 ;在恢复时候,直接读取快照文件,进行数据的恢复 ;

在这里插入图片描述

默认情况下, Redis 将数据库快照保存在名字为 dump.rdb的二进制文件中。文件名可以在配置文件中进行自定义。

工作原理


在进行 RDB 的时候,redis 的主线程是不会做 io 操作的,主线程会 fork 一个子线程来完成该操作;

  1. Redis 调用forks。同时拥有父进程和子进程。
  2. 子进程将数据集写入到一个临时 RDB 文件中。
  3. 当子进程完成对新 RDB 文件的写入时,Redis 用新 RDB 文件替换原来的 RDB 文件,并删除旧的 RDB 文件。

这种工作方式使得 Redis 可以从写时复制(copy-on-write)机制中获益(因为是使用子进程进行写操作,而父进程依然可以接收来自客户端的请求。)

在这里插入图片描述

触发机制

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

save

使用 save 命令,会立刻对当前内存中的数据进行持久化 ,但是会阻塞,也就是不接受其他操作了;

由于 save 命令是同步命令,会占用Redis的主进程。若Redis数据非常多时,save命令执行速度会非常慢,阻塞所有客户端的请求。

在这里插入图片描述

flushall命令

flushall 命令也会触发持久化 ;

触发持久化规则

满足配置条件中的触发条件 ;

可以通过配置文件对 Redis 进行设置, 让它在“ N 秒内数据集至少有 M 个改动”这一条件被满足时, 自动进行数据集保存操作。

在这里插入图片描述

在这里插入图片描述

bgsave

bgsave 是异步进行,进行持久化的时候,redis 还可以将继续响应客户端请求 ;

在这里插入图片描述

bgsave和save对比

命令 save bgsave
IO类型 同步 异步
阻塞? 是(阻塞发生在fock(),通常非常快)
复杂度 O(n) O(n)
优点 不会消耗额外的内存 不阻塞客户端命令
缺点 阻塞客户端命令 需要fock子进程,消耗内存

在这里插入图片描述

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

在这里插入图片描述

优缺点

优点:

  1. 适合大规模的数据恢复
  2. 对数据的完整性要求不高

缺点:

  1. 需要一定的时间间隔进行操作,如果redis意外宕机了,这个最后一次修改的数据就没有了。
  2. fork进程的时候,会占用一定的内容空间。

十一、持久化AOF

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

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

aof(append only file)保存的文件是appendonly.aof

默认为no需要手动进行开启

在这里插入图片描述

重启redis后读取.aof文件
在这里插入图片描述

在这里插入图片描述

如果aof文件有错误,这时候redis启动不起来,我们需要修复配置文件

redis给我们提供了一个工具

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

在这里插入图片描述

aof默认是文件无线追加,文件会越来越大
如果aof文件大于64m,fork一个新进程将我们的文件进行重写
在这里插入图片描述

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

RDB和AOP选择

RDB 和 AOF 对比

RDB AOF
启动优先级
体积
恢复速度
数据安全性 丢数据 根据策略决定

如何选择使用哪种持久化方式?

一般来说, 如果想达到足以媲美 PostgreSQL 的数据安全性, 你应该同时使用两种持久化功能。

如果你非常关心你的数据, 但仍然可以承受数分钟以内的数据丢失, 那么你可以只使用 RDB 持久化。

有很多用户都只使用 AOF 持久化, 但并不推荐这种方式: 因为定时生成 RDB 快照(snapshot)非常便于进行数据库备份, 并且 RDB 恢复数据集的速度也要比 AOF 恢复的速度要快。

十二、Redis发布与订阅

Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。

下图展示了频道 channel1 , 以及订阅这个频道的三个客户端 —— client2 、 client5 和 client1 之间的关系:

在这里插入图片描述

当有新消息通过 PUBLISH 命令发送给频道 channel1 时, 这个消息就会被发送给订阅它的三个客户端:

在这里插入图片描述

命令

  • PSUBSCRIBE pattern [pattern..] 订阅一个或多个符合给定模式的频道。
  • PUNSUBSCRIBE pattern [pattern..] 退订一个或多个符合给定模式的频道。
  • PUBSUB subcommand [argument[argument]] 查看订阅与发布系统状态。
  • PUBLISH channel message 向指定频道发布消息
  • SUBSCRIBE channel [channel..] 订阅给定的一个或多个频道。
  • SUBSCRIBE channel [channel..] 退订一个或多个频道

示例

------------订阅端----------------------
127.0.0.1:6379> SUBSCRIBE sakura # 订阅sakura频道
Reading messages... (press Ctrl-C to quit) # 等待接收消息
1) "subscribe" # 订阅成功的消息
2) "sakura"
3) (integer) 1
1) "message" # 接收到来自sakura频道的消息 "hello world"
2) "sakura"
3) "hello world"
1) "message" # 接收到来自sakura频道的消息 "hello i am sakura"
2) "sakura"
3) "hello i am sakura"

--------------消息发布端-------------------
127.0.0.1:6379> PUBLISH sakura "hello world" # 发布消息到sakura频道
(integer) 1
127.0.0.1:6379> PUBLISH sakura "hello i am sakura" # 发布消息
(integer) 1

-----------------查看活跃的频道------------
127.0.0.1:6379> PUBSUB channels
1) "sakura"

原理
每个 Redis 服务器进程都维持着一个表示服务器状态的 redis.h/redisServer 结构, 结构的 pubsub_channels 属性是一个字典, 这个字典就用于保存订阅频道的信息,其中,字典的键为正在被订阅的频道, 而字典的值则是一个链表, 链表中保存了所有订阅这个频道的客户端。

在这里插入图片描述

客户端订阅,就被链接到对应频道的链表的尾部,退订则就是将客户端节点从链表中移除。

缺点

  • 如果一个客户端订阅了频道,但自己读取消息的速度却不够快的话,那么不断积压的消息会使redis输出缓冲区的体积变得越来越大,这可能使得redis本身的速度变慢,甚至直接崩溃。
  • 这和数据传输可靠性有关,如果在订阅方断线,那么他将会丢失所有在短线期间发布者发布的消息。

应用

  1. 消息订阅:公众号订阅,微博关注等等(起始更多是使用消息队列来进行实现)
  2. 多人在线聊天室。

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

十四、Redis主从复制

概念和作用

概念

主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(Master/Leader),后者称为从节点(Slave/Follower), 数据的复制是单向的!只能由主节点复制到从节点(主节点以写为主、从节点以读为主)。

默认情况下,每台Redis服务器都是主节点,一个主节点可以有0个或者多个从节点,但每个从节点只能由一个主节点。

作用

  1. 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余的方式。
  2. 故障恢复:当主节点故障时,从节点可以暂时替代主节点提供服务,是一种服务冗余的方式
  3. 负载均衡:在主从复制的基础上,配合读写分离,由主节点进行写操作,从节点进行读操作,分担服务器的负载;尤其是在多读少写的场景下,通过多个从节点分担负载,提高并发量。
  4. 高可用基石:主从复制还是哨兵和集群能够实施的基础。

为什么使用集群

  1. 单台服务器难以负载大量的请求
  2. 单台服务器故障率高,系统崩坏概率大
  3. 单台服务器内存容量有限。

环境配置

127.0.0.1:6379> info replication
# Replication
role:master # 角色
connected_slaves:0 # 从机数量
master_replid:3b54deef5b7b7b7f7dd8acefa23be48879b4fcff
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

既然需要启动多个服务,就需要多个配置文件。每个配置文件对应修改以下信息:

  • 端口号
  • pid文件名
  • 日志文件名
  • rdb文件名

启动单机多服务集群:

在这里插入图片描述

一主二从配置

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

认老大!一主(79)二从(80,81)

使用SLAVEOF host port就可以为从机配置主机了。

在这里插入图片描述

然后主机上也能看到从机的状态:

在这里插入图片描述

我们这里是使用命令搭建,是暂时的,真实开发中应该在从机的配置文件中进行配置,这样的话是永久的。

在这里插入图片描述

使用规则

  1. 从机只能读,不能写,主机可读可写但是多用于写。
 127.0.0.1:6381> set name sakura # 从机6381写入失败
(error) READONLY You can't write against a read only replica.

127.0.0.1:6380> set name sakura # 从机6380写入失败
(error) READONLY You can't write against a read only replica.

127.0.0.1:6379> set name sakura
OK
127.0.0.1:6379> get name
"sakura"
  1. 当主机断电宕机后,默认情况下从机的角色不会发生变化 ,集群中只是失去了写操作,当主机恢复以后,又会连接上从机恢复原状。

  2. 当从机断电宕机后,若不是使用配置文件配置的从机,再次启动后作为主机是无法获取之前主机的数据的,若此时重新配置称为从机,又可以获取到主机的所有数据。这里就要提到一个同步原理。

  3. 第二条中提到,默认情况下,主机故障后,不会出现新的主机,有两种方式可以产生新的主机:

  • 从机手动执行命令slaveof no one,这样执行以后从机会独立出来成为一个主机
  • 使用哨兵模式(自动选举)

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

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

十五、哨兵模式

更多信息参考博客:https://www.jianshu.com/p/06ab9daf921d

主从切换技术的方法是:当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工干预,费事费力,还会造成一段时间内服务不可用。这不是一种推荐的方式,更多时候,我们优先考虑哨兵模式

单机单个哨兵

5PhKPg.png

哨兵的作用:

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

多哨兵模式

5PhQ2j.png

哨兵的核心配置

sentinel monitor mymaster 127.0.0.1 6379
  • 数字1表示 :当一个哨兵主观认为主机断开,就可以客观认为主机故障,然后开始选举新的主机。

测试

redis-sentinel xxx/sentinel.conf

成功启动哨兵模式

在这里插入图片描述

此时哨兵监视着我们的主机6379,当我们断开主机后:

在这里插入图片描述

哨兵模式优缺点

优点:

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

缺点:

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

哨兵模式的全部配置

完整的哨兵模式配置文件 sentinel.conf

# Example sentinel.conf
 
# 哨兵sentinel实例运行的端口 默认26379
port 26379
 
# 哨兵sentinel的工作目录
dir /tmp
 
# 哨兵sentinel监控的redis主节点的 ip port 
# master-name  可以自己命名的主节点名字 只能由字母A-z、数字0-9 、这三个字符".-_"组成。
# quorum 当这些quorum个数sentinel哨兵认为master主节点失联 那么这时 客观上认为主节点失联了
# sentinel monitor <master-name> <ip> <redis-port> <quorum>
sentinel monitor mymaster 127.0.0.1 6379 1
 
# 当在Redis实例中开启了requirepass foobared 授权密码 这样所有连接Redis实例的客户端都要提供密码
# 设置哨兵sentinel 连接主从的密码 注意必须为主从设置一样的验证密码
# sentinel auth-pass <master-name> <password>
sentinel auth-pass mymaster MySUPER--secret-0123passw0rd
 
 
# 指定多少毫秒之后 主节点没有应答哨兵sentinel 此时 哨兵主观上认为主节点下线 默认30秒
# sentinel down-after-milliseconds <master-name> <milliseconds>
sentinel down-after-milliseconds mymaster 30000
 
# 这个配置项指定了在发生failover主备切换时最多可以有多少个slave同时对新的master进行 同步,
这个数字越小,完成failover所需的时间就越长,
但是如果这个数字越大,就意味着越 多的slave因为replication而不可用。
可以通过将这个值设为 1 来保证每次只有一个slave 处于不能处理命令请求的状态。
# sentinel parallel-syncs <master-name> <numslaves>
sentinel parallel-syncs mymaster 1
 
 
 
# 故障转移的超时时间 failover-timeout 可以用在以下这些方面: 
#1. 同一个sentinel对同一个master两次failover之间的间隔时间。
#2. 当一个slave从一个错误的master那里同步数据开始计算时间。直到slave被纠正为向正确的master那里同步数据时。
#3.当想要取消一个正在进行的failover所需要的时间。  
#4.当进行failover时,配置所有slaves指向新的master所需的最大时间。不过,即使过了这个超时,slaves依然会被正确配置为指向master,但是就不按parallel-syncs所配置的规则来了
# 默认三分钟
# sentinel failover-timeout <master-name> <milliseconds>
sentinel failover-timeout mymaster 180000
 
# SCRIPTS EXECUTION
 
#配置当某一事件发生时所需要执行的脚本,可以通过脚本来通知管理员,例如当系统运行不正常时发邮件通知相关人员。
#对于脚本的运行结果有以下规则:
#若脚本执行后返回1,那么该脚本稍后将会被再次执行,重复次数目前默认为10
#若脚本执行后返回2,或者比2更高的一个返回值,脚本将不会重复执行。
#如果脚本在执行过程中由于收到系统中断信号被终止了,则同返回值为1时的行为相同。
#一个脚本的最大执行时间为60s,如果超过这个时间,脚本将会被一个SIGKILL信号终止,之后重新执行。
 
#通知型脚本:当sentinel有任何警告级别的事件发生时(比如说redis实例的主观失效和客观失效等等),将会去调用这个脚本,
#这时这个脚本应该通过邮件,SMS等方式去通知系统管理员关于系统不正常运行的信息。调用该脚本时,将传给脚本两个参数,
#一个是事件的类型,
#一个是事件的描述。
#如果sentinel.conf配置文件中配置了这个脚本路径,那么必须保证这个脚本存在于这个路径,并且是可执行的,否则sentinel无法正常启动成功。
#通知脚本
# sentinel notification-script <master-name> <script-path>
  sentinel notification-script mymaster /var/redis/notify.sh
 
# 客户端重新配置主节点参数脚本
# 当一个master由于failover而发生改变时,这个脚本将会被调用,通知相关的客户端关于master地址已经发生改变的信息。
# 以下参数将会在调用脚本时传给脚本:
# <master-name> <role> <state> <from-ip> <from-port> <to-ip> <to-port>
# 目前<state>总是“failover”,
# <role>是“leader”或者“observer”中的一个。 
# 参数 from-ip, from-port, to-ip, to-port是用来和旧的master和新的master(即旧的slave)通信的
# 这个脚本应该是通用的,能被多次调用,不是针对性的。
# sentinel client-reconfig-script <master-name> <script-path>
sentinel client-reconfig-script mymaster /var/redis/reconfig.sh

十六、缓存穿透与雪崩

缓存穿透(查不到)

概念

在默认情况下,用户请求数据时,会先在缓存(Redis)中查找,若没找到即缓存未命中,再在数据库中进行查找,数量少可能问题不大,可是一旦大量的请求数据(例如秒杀场景)缓存都没有命中的话,就会全部转移到数据库上,造成数据库极大的压力,就有可能导致数据库崩溃。网络安全中也有人恶意使用这种手段进行攻击被称为洪水攻击。

解决方案

布隆过滤器

对所有可能查询的参数以Hash的形式存储,以便快速确定是否存在这个值,在控制层先进行拦截校验,校验不通过直接打回,减轻了存储系统的压力。

在这里插入图片描述

缓存空对象

一次请求若在缓存和数据库中都没找到,就在缓存中方一个空对象用于处理后续这个请求。

在这里插入图片描述

这样做有一个缺陷:存储空对象也需要空间,大量的空对象会耗费一定的空间,存储效率并不高。解决这个缺陷的方式就是设置较短过期时间

即使对空值设置了过期时间,还是会存在缓存层和存储层的数据会有一段时间窗口的不一致,这对于需要保持一致性的业务会有影响。

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

概念

相较于缓存穿透,缓存击穿的目的性更强,一个存在的key,在缓存过期的一刻,同时有大量的请求,这些请求都会击穿到DB,造成瞬时DB请求量大、压力骤增。这就是缓存被击穿,只是针对其中某个key的缓存不可用而导致击穿,但是其他的key依然可以使用缓存响应。

比如热搜排行上,一个热点新闻被同时大量访问就可能导致缓存击穿。

解决方案

  1. 设置热点数据永不过期

这样就不会出现热点数据过期的情况,但是当Redis内存空间满的时候也会清理部分数据,而且此种方案会占用空间,一旦热点数据多了起来,就会占用部分空间。

  1. 加互斥锁(分布式锁)

在访问key之前,采用SETNX(set if not exists)来设置另一个短期key来锁住当前key的访问,访问结束再删除该短期key。保证同时刻只有一个线程访问。这样对锁的要求就十分高。

缓存雪崩

概念

大量的key设置了相同的过期时间,导致在缓存在同一时刻全部失效,造成瞬时DB请求量大、压力骤增,引起雪崩。

在这里插入图片描述

解决方案

  • redis高可用

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

  • 限流降级

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

  • 数据预热

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

笔记参考自:https://blog.csdn.net/weixin_43873227/article/details/106107270

posted @ 2021-10-09 09:14  你我不在年少  阅读(436)  评论(0编辑  收藏  举报