阿里云redis学习笔记

什么是Redis?

Redis(Remote Dictionary Service) 即 远程字典服务!

是由C语言编写、支持网络、可基于内存也可以持久化的日志型(key-value)数据库,并且提供多种语言的API(Java、C、php等)

redis是当前最热门的NoSql技术之一,也被人们称为结构化数据库!

它是通过key-value的方式进行存储,免费开源!

Redis能干嘛?

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

特性

  1. 多样的数据类型

  2. 持久化

  3. 集群

  4. 事务

linux中下载Redis

1、进入redis官网 https://redis.io/ 点击下载

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YE2HlOK0-1616423125632)(C:\Users\86155\AppData\Roaming\Typora\typora-user-images\1616322043424.png)]

2、Xftp连接服务器,将下载gz包传过去

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-O0pisfex-1616423125634)(C:\Users\86155\AppData\Roaming\Typora\typora-user-images\1616322108674.png)]

3、Xshell连接服务器解压redis

#按顺序安装
tar -zxvy redis-6.2.1.tar.gz #解压 

yum -y install centos-release-scl

yum -y install devtoolset-9-gcc devtoolset-9-gcc-c++ devtoolset-9-binutils scl enable devtoolset-9 bash

echo "source /opt/rh/devtoolset-9/enable" >>/etc/profile

make #自动配置
make install 

#进入  /usr/local/bin 目录
cd /usr/local/bin
#创建配置文件夹
mkdir config
#将redis.conf复制过来
cp /opt/redis-6.2.1/redis.conf  config

需要注意的是scl命令启用只是临时的,退出shell或重启就会恢复原系统gcc版本。
如果要长期使用gcc 9.3的话:

echo “source /opt/rh/devtoolset-9/enable” >>/etc/profile

这样退出shell重新打开就是新版的gcc了

redis默认安装路径 /usr/local/bin 目录下

4、redis默认不是后台启动

vim redis.conf 找到daemonize 修改为yes

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IywJIEK9-1616423125636)(C:\Users\86155\AppData\Roaming\Typora\typora-user-images\1616322936187.png)]

redis-server config/redis.conf  #启动服务
redis-cli -p 6379 #设置端口
127.0.0.1:6379> ping 
PONG #连接成功

5、关闭redis

127.0.0.1:6379> shutdown
not connected> exit

测试性能

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Oyy7Za23-1616423125638)(C:\Users\86155\AppData\Roaming\Typora\typora-user-images\1616323722012.png)]

简单测试

#测试 100个并发连接 每个并发100000请求
redis-benchmark -h localhost -p 6379 -c 100 -n 100000

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CJCE0NGE-1616423125639)(C:\Users\86155\AppData\Roaming\Typora\typora-user-images\1616323976943.png)]

Redis的基础知识

redis中默认有16个数据库!!

默认使用第0个数据库

可以使用select 进行切换数据库

127.0.0.1:6379> select 3 #切换数据库
OK
127.0.0.1:6379[3]> DBSIZE #查看数据库大小
(integer) 0
127.0.0.1:6379[3]> set name linqin #创建 name存值为 liniqn
OK
127.0.0.1:6379[3]> get name #获取name值
"linqin"
127.0.0.1:6379[3]> DBSIZE #查看四号数据库大小 此时有1
(integer) 1
127.0.0.1:6379[3]> keys * #查看当前所有key
127.0.0.1:6379[3]> flushdb #清空当前数据库所有key
127.0.0.1:6379[3]> FLUSHALL #清空全部的数据库
127.0.0.1:6379> exists name #判断当前数据库里是否存在 key键name 如果返回0则不存在 1则存在
(integer) 0
127.0.0.1:6379> move name 1 #移出当前key
127.0.0.1:6379> del name  #删除name的key
127.0.0.1:6379> EXPIRE name 10 #十秒钟name键自动过期
127.0.0.1:6379> tll name #查看当前key的剩余时间

127.0.0.1:6379> type name #查看当前key的类型
string #string类型

#每个数据库都可以存

Redis是单线程的!

Redis是很快的,Redis是基于内存操作,cpu不是redis的性能瓶颈,redis的瓶颈是根据机器的内存和网络带宽,既然可以通过单线程实现就使用单线程

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

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

2、误区二:多线程(CPU上下文会切换)

CPU>内存>硬盘速度

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

5大基本数据类型

String类型

90%的java程序员只会使用String类型

127.0.0.1:6379> set name lin #设置值
OK
127.0.0.1:6379> append name "qin" #在name后面追加qin 如果当前可以不能存在,就相当于 set key	
(integer) 6  #显示字符总数
127.0.0.1:6379> get name #获得值
"linqin"
127.0.0.1:6379> STRLEN name #获取key的字符总数
(integer) 6

127.0.0.1:6379> set views 0 #浏览量键
OK
127.0.0.1:6379> incr views #每次访问+1 自增1
(integer) 1
127.0.0.1:6379> get views #获取浏览量
"1"
127.0.0.1:6379> decr views #访问量-1 自减1
(integer) 0
127.0.0.1:6379> INCRBY views 10 #访问量一次增加10
(integer) 10
127.0.0.1:6379> DECRBY views 5 #访问量一次减少5
(integer) 5
######################################################
字符串范围
127.0.0.1:6379> GETRANGE name 0 2 #"lin" 查看字符串前三位

127.0.0.1:6379> SETRANGE name 2 aaa #修改key值第三位开始的后三位为a
(integer) 6
127.0.0.1:6379> get name
"liaaan"
######################################################
# setex key time linqin # 创建一个key time秒后失效
# setnx key2 redis  #不存在设置(在分布式锁中常常使用,如果key2存在则会创建失败)
######################################################
#mset 一次创建多个key-value 
127.0.0.1:6379> mset k1 v1  k2 v2 k3 v3
OK
127.0.0.1:6379> keys *
1) "k1"
2) "k3"
3) "k2"
#mget 获取多个值value
127.0.0.1:6379> mget k1 k2 k3
1) "v1"
2) "v2"
3) "v3"
#msetnx 是一个原子性操作,要么一起成功 要么一起失败
127.0.0.1:6379> msetnx k1 v2 k4 v4
(integer) 0
######################################################
#创建一个user对象 user:{id}:{filed}
127.0.0.1:6379> mset user:1:name linqin user:1:age 18
OK
127.0.0.1:6379> mget user:1:name user:1:age
1) "linqin"
2) "18"


127.0.0.1:6379> getset db redis #如果不存在值 则返回null 并创建db的键值
(nil)
127.0.0.1:6379> get db
"redis"
127.0.0.1:6379> getset db mongdb #如果存在值,获取原来的值,重置新的值
"redis"
127.0.0.1:6379> get db
"mongdb"

数据结构是相同的!

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

  • 计数器
  • 统计多单位的数量

List类型

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

所有list命令都是用L开头的

redis不区分大小写

#LPUSH 将一个值或多个值,插入到列表头部(左) 
#RPUSH 将一个值或多个值,插入到列表右部(右) 
127.0.0.1:6379> LPUSH list one two three for 
(integer) 5
#LRANGE 获取list中从左边读取的前五个值
127.0.0.1:6379> LRANGE list 0 4
1) "for"
2) "three"
3) "two"
4) "one"
5) "one"
######################################################
#LPOP 从list第一个元素开始移除
#RPOP 从list最后一个元素开始移除
127.0.0.1:6379> RPOP list 3
1) "q"
2) "b"
3) "o"
127.0.0.1:6379> LPOP list 1
1) "for"
127.0.0.1:6379> LRANGE list 0 -1
1) "three"
2) "two"
3) "one"
4) "one"
######################################################
127.0.0.1:6379> lindex list 0 #通过下标获取list的某个值
"three"
127.0.0.1:6379> Llen list #获取list集合 元素总数
(integer) 4
#移出list集合中指定个数的值 精确匹配
127.0.0.1:6379> Lrem list 1 one 
(integer) 1
#查询list所有 发现one少了一个 
127.0.0.1:6379> LRANGE list 0 -1
1) "three"
2) "two"
3) "one"
######################################################
#在muList创建四个元素
127.0.0.1:6379> RPUSH muList 0 1 2 3
(integer) 4
#LTRIM 通过该命令 截取第二位到第三位的值
127.0.0.1:6379> LTRIM muList 1 2
OK
#可以看到0和3没有了
127.0.0.1:6379> LRANGE muList 0 -1
1) "1"
2) "2"
#移出集合中的最后一个元素并移动到另一个list集合中
127.0.0.1:6379> RPOPLPUSH muList list
"2"
127.0.0.1:6379> LRANGE muList 0 -1 #查看当前集合 发现无2
1) "1"
127.0.0.1:6379> LRANGE list 0 -1 #查看目标集合 发现有2
1) "2"
2) "three"
3) "two"
4) "one"

#list修改第一个值为item(将列表中指定下标的值替换为另一个值,相当于更新操作,注意目标集合必须存在值,否则无法更新)
127.0.0.1:6379> lset list 0 item
OK
127.0.0.1:6379> Lrange list 0 -1
1) "item"
2) "three"
3) "two"
4) "one"

#before将thob插入到one的前面 
#after可以插入到one的后面
127.0.0.1:6379> LINSERt list before one thob
(integer) 5
127.0.0.1:6379> Lrange list 0 -1
1) "item"
2) "three"
3) "two"
4) "thob"
5) "one"

小结

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

set集合

set集合中的值是无序且不能重复的集合

set的命令的是以s开头的

#set集合中添加元素 sadd
127.0.0.1:6379> sadd set1 one
(integer) 1
#set集合中不允许值重复 否则添加不成功
127.0.0.1:6379> sadd set1 one
(integer) 0
127.0.0.1:6379> sadd set1 two
(integer) 1
#查看set集合所有元素
127.0.0.1:6379> SMEMBERS set1
1) "two"
2) "one"
#查看set元素中是否存在one 存在返回1 不存在返回0
127.0.0.1:6379> SISMEMBER set1 one
(integer) 1

######################################################
127.0.0.1:6379> Scard set1 #获取set中的元素个数
(integer) 2
#srem 移除set中的指定元素 one和two
127.0.0.1:6379> srem set1 one two
(integer) 2
#移除成功则set为空
127.0.0.1:6379> SMEMBERS set1
(empty array)
#SRANDMEMBER 随机抽取选出三个(自定义)元素
127.0.0.1:6379> SRANDMEMBER ser1 3
1) "d"
2) "b"
3) "c"
127.0.0.1:6379> SRANDMEMBER ser1 2
1) "d"
2) "a"
#spop  删除随机的key 
127.0.0.1:6379> spop ser1
"b"
127.0.0.1:6379> spop ser1
"a"
127.0.0.1:6379> SMEMBERS ser1 #发现b和a没了
1) "d"
2) "c"
######################################################
#smove 将set2中的a 移动到ser1
127.0.0.1:6379> smove set2 ser1 a
(integer) 1
127.0.0.1:6379> SMEMBERS ser1 #多出了个a
1) "d"
2) "a"
3) "c"
127.0.0.1:6379> SMEMBERS set2 #少了个a
1) "d"
2) "b"
3) "c"
#sdiff 差集 查看两个set集合不同的
127.0.0.1:6379> SDIFF ser1 set2
1) "a" 
#sinter 交集 查看两个set集合都有的
127.0.0.1:6379> SINTER ser1 set2
1) "d"
2) "c"
#sunion 并集 两个集合合并在一个 重复的不合并
127.0.0.1:6379> SUNION ser1 set2
1) "d"
2) "a"
3) "b"
4) "c"

Hash(哈希)

Map集合,key-map!这个值是一个map集合

Hash的命令是以h开头

#通过hset存值 可以存入多个值
127.0.0.1:6379> hset myhash key value
(integer) 1
#通过hmset添加多个值
127.0.0.1:6379> hset myhash key3 value3 key4 value5
127.0.0.1:6379> hget myhash key
"value"

#通过hmget获取多个值的value
127.0.0.1:6379> hmget myhash key1 key2 key3 
1) (nil)
2) "value2"
3) "value3"
#根据hgetall查询所有的map值
127.0.0.1:6379> hgetall myhash
1) "key"
   "value"
2) "key2"
   "value2"

#hlen 获取哈希表中的key-map
127.0.0.1:6379> hlen myhash
(integer) 2

#hdel根据对应key的值进行删除
127.0.0.1:6379> hdel myhash key2
(integer) 1

#hexists 如果键存在 返回1 不存在返回0
127.0.0.1:6379> Hexists myhash key
(integer) 1

#通过hkeys 获取当前所有key键
127.0.0.1:6379> hkeys myhash
1) "key"
2) "key3"
3) "key4"
#hvals 获取当前所有key键对饮value值
127.0.0.1:6379> hvals myhash
1) "value"
2) "value3"
3) "value5"
#设置增量
127.0.0.1:6379> HINCRBY myhash key2 1


hash更适合存储对象

String适合存字符串

ZSER(有序集合)

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

#zadd 添加多个值
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 4 foree
(integer) 2
#ZRANGE 查询zset 从大到小
127.0.0.1:6379> ZRANGE myset 0 -1
1) "one"
2) "two"
3) "three"
4) "foree"

######################################################

#显示全部用户 从小到大排序 -inf是无穷小 +inf是无穷大 withscores显示条件 如果只查小于3的 只需要把+inf改为3即可
127.0.0.1:6379> ZRANGEBYSCORE myset -inf +inf  withscores
1) "one"
2) "1"
3) "two"
4) "2"
5) "three"
6) "3"
7) "foree"
8) "4"
#zrem 删除指定键值
127.0.0.1:6379> zrem myset one
(integer) 1
#zcard 查看myset中的元素总数
127.0.0.1:6379> zcard myset
(integer) 3
#zcount 查看1-4之间有多少个元素
127.0.0.1:6379> ZCOUNT myset 1 4
(integer) 3

三种特殊数据类型

geospatial 地理位置

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

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

他只有六个命令

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-g7huu8OF-1616423125641)(C:\Users\86155\AppData\Roaming\Typora\typora-user-images\1616375642427.png)]

geoadd

#添加geoadd 添加地理地址
#规则:两级无法直接添加,我们一般会下载好城市数据,然后通过java一次性导入
# 有效的经度从-180到180
# 有效的纬度从-85.05112878到85.05112878
# 当坐标位置超出上述指定范围,该命令则会返回一个错误
#参数 key 值(经度、纬度、名称)
127.0.0.1:6379> geoadd china:city 116.40 39.90 beijing
(integer) 1
127.0.0.1:6379> geoadd china:city 111.47 27.25 shaodong
(integer) 1
127.0.0.1:6379> geoadd china:city 113.28 23.125 guangdong 
(integer) 1
127.0.0.1:6379> geoadd china:city 91.13 29.66 xizang
(integer) 1

geopos

#geopos 获取当前定位:一定是一个坐标值!
127.0.0.1:6379> geopos china:city beijing shaodong
1) 1) "116.39999896287918091"
   2) "39.90000009167092543"
2) 1) "111.47000223398208618"
   2) "27.25000015313309376"

geodist

#geodist获取中国地区中北京到邵东的直线距离 单位为km(可以设置)
127.0.0.1:6379> geodist china:city beijing shaodong km
"1478.6457"

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

#根据经度纬度为中心,找出某一半径内的元素显示器经度纬度,通过count来控制查询范围内几条数据
127.0.0.1:6379> GEORADIUS china:city 100 30 2000 km withcoord count 2
1) 1) "xizang"
   2) 1) "91.1300012469291687"
      2) "29.66000035782676747"
2) 1) "shaodong"
   2) 1) "111.47000223398208618"
      2) "27.25000015313309376"

GEORADIUSBYMEMBER

#查看指定地区为中心的方圆2000km的城市
127.0.0.1:6379> GEORADIUSBYMEMBER china:city beijing 2000 km
1) "shaodong"
2) "guangdong"
3) "beijing"

geohash

该命令返回11个字符的geohash的字符串(暂时用不到)

#返回一个代表当前经纬度的字符串	
#将二维的经纬度转换为一维的字符串,如果两个字符串越接近,那么则距离越近
127.0.0.1:6379> GEOHASH china:city beijing shaodong
1) "wx4fbxxfke0"
2) "wkz7089yz20"

GEO底层实现原理其实就是zset!我们可以通过zset命令在操作geo

#查看全部元素
127.0.0.1:6379> ZRANGE china:city 0 -1
1) "xizang"
2) "shaodong"
3) "guangdong"
4) "beijing"
#删除指定元素
127.0.0.1:6379> ZREM china:city xizang
(integer) 1

Hyoerloglog

什么是基数?

A{1,3,5,7,8,9,7} =6

B{1,3,5,7,8} =5

基数(不重复的元素)

简介

Redis 2.8.9版本就更新了Hyoerloglog数据结构!

Redis Hyperloglog基数统计的算法!

优点:占用内存是固定的,2^64不同元素的基数,只需要12kb!

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

传统方法,set保存用户id,然后可以统计set中的元素作为标准判断!

这种方式存储大量用户id,就会比较麻烦!我们目的是为了计数,而不是保存用户id

#pfadd 添加第一组元素
127.0.0.1:6379> pfadd mykey a b c d e f g 
(integer) 1
#pfcount 统计第一组的基数数量
127.0.0.1:6379> pfcount mykey 
(integer) 7
127.0.0.1:6379> pfadd mykey2 a c p r w q g v
(integer) 1
#pfmerge 合并第一组的第二组的基数数量(并集,重复的不再添加)
127.0.0.1:6379> PFMERGE mykey mykey2
OK
#查看总数为12 abcdefgrwqpv
127.0.0.1:6379> PFCOUNT mykey
(integer) 12

如果允许容错,一定可以使用Hyoerloglog!

如过不允许容错,就是用set或者自己的数据类型

Birmaps

位存储

统计用户信息,活跃,不活跃!登录、未登录!

使用bitmap来记录周一到周日的打卡!

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5iZVHQh9-1616423125642)(C:\Users\86155\AppData\Roaming\Typora\typora-user-images\1616381512606.png)]

查看某一天是否打卡

127.0.0.1:6379> getbit sign 0 #打卡
(integer) 1
127.0.0.1:6379> getbit sign 3 #未打卡
(integer) 0
127.0.0.1:6379> BITCOUNT sign #统计打卡记录
(integer) 5

事务

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

一次性、顺序性、排他性!执行一些命令

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

所有的命令在事务中,并没有直接执行,只有发起了执行命令才会执行

Redis单条命令式保证原子性,但是事务不保证原子性!,要么同时成功,要么同时失败

redis的事务:

  • 开启事务(Multi)
  • 命令入队(添加常用命令)
  • 执行事务(exec)

正常执行事务

127.0.0.1:6379> multi #开启事务
OK
127.0.0.1:6379(TX)> set k1 v1 #命令入队 添加k1
QUEUED
127.0.0.1:6379(TX)> get k1 #命令入队 查询k1
QUEUED
127.0.0.1:6379(TX)> exec #执行事务
1) OK
2) "v1"

放弃事务 DISCARD放弃事务后队列中的命令不会执行

127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set k2 v2 k3 v3
QUEUED
127.0.0.1:6379(TX)> DISCARD #放弃事务
OK

编译时异常(代码有问题!命令有错!),事务中所有命令都不会被执行

127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set k5 v5 
QUEUED
127.0.0.1:6379(TX)> getset k4  #错误的命令
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379(TX)> set k4 v4 k6 v6
QUEUED
127.0.0.1:6379(TX)> exec #执行事务爆错 所有的命令都不会执行
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get k4
(nil)

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

127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set q1 qw
QUEUED
127.0.0.1:6379(TX)> incr q1 #自增 
QUEUED
127.0.0.1:6379(TX)> set q2 v2
QUEUED
127.0.0.1:6379(TX)> get q2
QUEUED
127.0.0.1:6379(TX)> exec
1) OK
#虽然该命令报错 但其他命令依旧正常执行成功了
2) (error) ERR value is not an integer or out of range
3) OK
4) "v2"

监控

悲观锁:

  • 什么时候都可能出问题,无论做什么都会加锁

乐观锁:

  • 很乐观,认为什么时候都不会出问题,所以不会加锁,更新数据时去判断一下,再次期间是否有人修改过这个数据
  • 获取version
  • 更新时比较version

Redis测监视测试

127.0.0.1:6379> set money 100 out 0
(error) ERR syntax error
127.0.0.1:6379> watch money #监听money对象
OK
127.0.0.1:6379> multi #事务正常结束,数据期间没有发生变动,这个时候就正常执行成功!
OK
127.0.0.1:6379(TX)> DECRBY money 50
QUEUED
127.0.0.1:6379(TX)> INCRBY out 50
QUEUED
127.0.0.1:6379(TX)> exec
1) (integer) -50
2) (integer) 50

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

127.0.0.1:6379> watch money #开启监听 money
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> DECRBY money 50
QUEUED
127.0.0.1:6379(TX)> INCRBY out 50
QUEUED
127.0.0.1:6379(TX)> exec #执行之前另一个进程修改了值,这个时候导致事务执行失败
(nil)

如果修改失败,获取最新的值即可

Jedis

我们要使用java来操作Redis

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

测试

难点!!!

1、导入对应的依赖

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

2、编码测试:

  • 连接数据库
  • 操作命令
  • 断开连接
1、找到redis.conf配置 
	1.1、设置redis的密码 requirepass 要设置密码
	1.2、注释bind 127.0.0.1 表示只能用linux连接 其他不行 所以注释掉
	1.3、daemonize yes #以守护进程的方式运行 默认为no
	1.4、protected-mode no #保护模式
	
	
2、查看端口是否开启
	2.1、firewall-cmd --query-port=6379/tcp 查看端口是否打开如果是no则手动打开
	2.2、firewall-cmd --zone=public --add-	port=6379/tcp --permanent


成功则控制台输出 PONG

SpringBoot整合

1、导入依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.4</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.redisspringboot</groupId>
    <artifactId>redis</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>redis</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

2、配置连接

#配置redis
spring.redis.host=xxxxxxx #阿里云的公网
spring.redis.port=6379
spring.redis.password=linqin #redis密码

3、测试连接

// 这就是之前 RedisAutoConfiguration 源码中的 Bean
@Autowired
private RedisTemplate redisTemplate;

@Test
void contextLoads() {
	/** redisTemplate 操作不同的数据类型,API 和 Redis 中的是一样的
	 * opsForValue 类似于 Redis 中的 String
	 * opsForList 类似于 Redis 中的 List
	 * opsForSet 类似于 Redis 中的 Set
	 * opsForHash 类似于 Redis 中的 Hash
	 * opsForZSet 类似于 Redis 中的 ZSet
	 * opsForGeo 类似于 Redis 中的 Geospatial
	 * opsForHyperLogLog 类似于 Redis 中的 HyperLogLog
	 */
    
    // 除了基本的操作,常用的命令都可以直接通过redisTemplate操作,比如事务……

// 和数据库相关的操作都需要通过连接操作
//RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
//connection.flushDb();

redisTemplate.opsForValue().set("key", "呵呵");
    
System.out.println(redisTemplate.opsForValue().get("key"));
}

序列化

1、创建一个是user实体类

@Data
@AllArgsConstructor
@NoArgsConstructor
@Component
// 实体类序列化在后面加上 implements Serializable 
public class User {
    private String name;
    private  int age;
}

2、编写测试类,先不序列化

    @Test
    void test() {
        User user = new User("霖磬", 18);
        // 使用 JSON 序列化
        //String s = new ObjectMapper().writeValueAsString(user);
        // 这里直接传入一个对象
        redisTemplate.opsForValue().set("user", user);
        System.out.println(redisTemplate.opsForValue().get("user"));
    }

3、不序列化则会报错

执行报错, 我们可以在实体类上实现序列化

实体类序列化在后面加上 implements Serializable 即可

在这里插入图片描述

所以一般实体类都要序列化

添加后测试成功!!

自定义模板

redisTemplate 模板

@Configuration
public class RedisConfig {
    /**
     *  编写自定义的 redisTemplate
     *  这是一个比较固定的模板
     */
    @Bean
    @SuppressWarnings("all")
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
        // 为了开发方便,直接使用<String, Object>
        RedisTemplate<String, Object> template = new RedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
           // Json 配置序列化
    // 使用 jackson 解析任意的对象
    Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
    // 使用 objectMapper 进行转义
    ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
    objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
    jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
    // String 的序列化
    StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();

    // key 采用 String 的序列化方式
    template.setKeySerializer(stringRedisSerializer);
    // Hash 的 key 采用 String 的序列化方式
    template.setHashKeySerializer(stringRedisSerializer);
    // value 采用 jackson 的序列化方式
    template.setValueSerializer(jackson2JsonRedisSerializer);
    // Hash 的 value 采用 jackson 的序列化方式
    template.setHashValueSerializer(jackson2JsonRedisSerializer);
    // 把所有的配置 set 进 template
    template.afterPropertiesSet();

    return template;
}
}

redis工具类

狂神-Redis-工具类

Redis持久化

Redis是内存数据库,如果不将内存中的数据库状态保存到磁盘,那么一旦服务器进程退出,服务器中存储的数据也会消失,所以redis提供了持久化功能!

RDB

redis 是内存数据库,断电及失,因此需要持久化,默认使用RDB,一般情况下我们无需修改RDB配置,即可使用。
Redis会单独创建一个fork子进程来进行持久化,子进程中循环所有的数据,将数据写入到二进制文件中,会先将数据 写入到一个临时文件中,待持久化过程都结束了,在用这个临时文件替换上次持久化好了的文件。整个过程中,主进程是不进行任何IO操作的,确保极高的性能,如果需要进行大规模数据的回复,且对数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加高效。RDB的去电就是最后一次持久化后的数据可能丢失。
RDB保存的文件就是dump.rdb
当我们修改了配置文件后,直接使用save命令就可以保存修改

关于快照的配置都在SNAPSHOTTING 项中

触发机制

在配置文件中的save,例如60秒内修改了5个key就会触发RDB,生成一个dump.rdb文件
执行flushall 命令,也会触发我们的RDB规则
退出redis,输入命令shutdown,就会关闭redis服务,这个时候也会生成RDB文件。

如何恢复RDB文件

1、只需要将rdb文件放在我们redis启动目录就可以,redis启动的时候会自动检查demp.rdb恢复其中的数据!
2、查看rdb文件保存的位置,输入命令config get dir 如果在该目录下存在,就会自动恢复其中的数据

RDB的优点

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

RDB的缺点

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

AOF

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

append

ppendonly no 默认不开启,一般如果要启用,只需要将该项改为yes即可,其他配置默认。

重启redis配置文件就可以生效了!

如何修复aof

当我们的aof文件有错误,这个时候redis会跑不起来

那么如何修复呢?

在我们的 /usr/local/bin 下面会有一个redis-check-aof 文件

进入到该目录并执行:redis-check-aof --fix appendonly.aof

一路Y下去,则可以修复aof文件

优点和缺点!

如果aof文件大于64M,太大了! fork则会创建一个新的进程来将我们的文件重写!(因为aof默认就是文件的无限追加,文件会越来越大)

优点:

  1. 每一次修改都同步,文件的完整性会更加好
  2. 默认开启每秒同步一次,可能会丢失一秒的失去
  3. 从不同步,效率是最高的!

缺点:

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

Redis发布订阅

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

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

订阅/发布消息图:

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

命令

  • PUBLISH channel message 将信息发送到指定的频道(发送者)
  • PSUBSCRIBE pattern [pattern …] 订阅一个或多个给定模式的频道
  • PUNSUBSCRIBE [pattern [pattern …]] 退订所有给定模式的频道
  • SUBSCRIBE channel [channel …] 订阅给定的一个或多个频道的信息
  • UNSUBSCRIBE [channel [channel …]] 退订频道

测试

#订阅端:
127.0.0.1:6379> SUBSCRIBE Linqin
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "Linqin"
3) (integer) 1
#等待读取推送的信息 
1) "message" #消息
2) "Linqin" #那个频道
3) "lin" #消息内容
#发送端
127.0.0.1:6379> PUBLISH Linqin lin
(integer) 1

Pub/Sub从字面上理解就是发布与订阅,在Redis中,你可以设定对某一个key值进行信息发布及消息订阅,当一个key值上进行消息发布,所有订阅它的客户端都会收到相应的消息,这一功能最明显的用法就是用作实时消息系统,比如普通的即时聊天,群聊等功能.

使用场景:

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

Redis主从复制

概念

就是将一台redis服务器的数据,复制到其他的redis服务器,前者称为主节点(master/leader,主节点不用配置,redis默认单机就是主节点),后者称为从节点(slave/follower);数据的复制是单向的,只能由主节点到从节点。master以写为主,slave以读为主。主从复制,读写分离。
一个主节点可以有多个从节点(或者没有),但一个从节点只能有一个主节点

环境配置

只配置从库,不用配置主库!

#info replication  查看当前的库的信息
127.0.0.1:6379> info replication  
# Replication
role:master # 角色 主机
connected_slaves:0 #没有丛机
master_failover_state:no-failover
master_replid:c542680b75c50f8c40fd239520511b774c00b333
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0

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

  1. 端口 port 6379 进程占用的端口
  2. pid 名字 记录了进程的ID,文件带有锁.可以防止程序的多次启动
  3. log 文件名 明确日志文件的位置
  4. dump rdb 名字 持久化文件位置

修改完毕后启动三个集群,可以通过进程信息查看

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qH95H8XX-1616423125651)(C:\Users\86155\AppData\Roaming\Typora\typora-user-images\1616415185743.png)]

一主二从

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

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

#SLAVEOF 127.0.0.1 6379 找谁当老大!
127.0.0.1:6380> SLAVEOF 127.0.0.1 6379
OK
127.0.0.1:6380> info replication
# Replication
role:slave 			  #当前角色为从机
master_host:127.0.0.1 #主机悉尼
master_port:6379      
master_link_status:down
master_last_io_seconds_ago:-1
master_sync_in_progress:0
slave_repl_offset:1
master_link_down_since_seconds:1616415530
slave_priority:100
slave_read_only:1
connected_slaves:0
master_failover_state:no-failover
master_replid:9790356335c630ac453febc23161ae0dde83eab7
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


#主机信息
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:2 #从机数
#从机数据
slave0:ip=127.0.0.1,port=6380,state=online,offset=98,lag=0
slave1:ip=127.0.0.1,port=6381,state=online,offset=98,lag=0
master_failover_state:no-failover
master_replid:1763017794afdd893f30feb4c40da8b138048618
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:98
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:98

如果配置了密码 则需要在redis.conf中配置masterauth 密码

真实的从主配置应该在配置文件中,这样的才是永久的,使用命令的是临时的!!

该怎么配置呢?

设置主从的时候,可以选择手动命令配置,也可以选择redis.conf配置文件配置

在配置文件配置的时候找到replicaof & masterauth

并进行设置

在进行手动设置的时候,采取的是一主二从的连接方式

以下1号机为主机,2、3号机为从机

细节

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

测试 主机断开连接 从机依旧连接主机 但是没有写操作 主机恢复了,从机依旧可以直接获取到主机写的新数据

复制原理

主写,从读,当强行往从机写会报错。

  • 当主挂了以后,从机会等待,不会翻身做主人,会等主机恢复,这个时候因为没有主机也就没有写操作,所有的从机数据都不会更新,因此会存在读到旧数据的可能。这里可以参考slave-serve-stale-data yes的配置策略。(启用哨兵模式后就不会这样)

  • 当从机挂了,恢复连接后,不会去主动找主机。相当是断开了连接。这是因为没有在配置文件中将该redis设置为从机,就是slaveof无效,因此这个时候它就变成了主机,不会去连其他主机。但是只要重新配置为从机,马上就会拥有全部主机的信息,这是因为什么呢?因为当slave启动成功连接到master后会发送一个sync同步命令,master接到命令后,启动后台的存盘进程,同事手机所有接收到的用于修改数据集命令,在后台进程执行完毕后,master将传送整个数据文件到slaver,并完成一次完全同步,这就是全量复制。之后master又有更新,会同步给其他从机,这就是增量复制。

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

  • 增量复制:master继续将新的所有收集到的修改命令依次传给slave,完成同步
    只要重新连接了主机,就一定会有一次全量复制

层层链络

上一个M链接下一个S

A是B的主机 B是C的主机 A宕机时不影响 B和C读取

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

执行: SLAVEOF no one 【我为大哥!其他节点自动连接到这个主节点】

如果这个时候老大修复了,就只能重新连接

自动哨兵

这个是重点
什么是哨兵模式?
哨兵模式就是自动选取主机的方法。在上面的案例中我们使用一主二从的方式配置了redis集群,可以发现如果当主机宕机,就会存在一些问题,例如无法写数据等等,哨兵模式就是自动切换主机。
哨兵默认端口26379

哨兵的两个作用

  • 通过发送命令,让redis服务器返回监控其运行状态,包括主服务器和从服务器。
    哨兵是一个单独的进程,会通过网络发送命令,等待redis服务器响应,判断监控的对象是否正常。如果不正常或发生故障,则哨兵会根据投票数自动将从库转换为主库

    img

  • 当哨兵检测到master炖鸡,会自动将slave切换成master,然后通过发布订阅模式,通知其他的从服务器,修改配置文件让其切换主机。
    然而一个哨兵进程对redis服务器进行监控,可能会出现问题,为此,我们可以使用多个哨兵进行监控,各个哨兵之间还会进行监控,这样就形成了多哨兵模式哨兵模式也可以集群,如果我们配置单个哨兵来监视所有主从集群,一旦哨兵所在的主机宕机,那么哨兵也就不存在了,因此也可以配置哨兵集群,哨兵之间不仅监视redis,还要各自监视。

    在这里插入图片描述

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

哨兵集群的配置与redis的集群配置类似,在配置文件中分别指定哨兵的端口,然后以配置文件的方式启动。

测试

我们目前的状态是 一主二从 所以我们要去配置哨兵

  1. 配置哨兵配置文件sentinel.conf
# sentinel monitor 被监控的名称 host port 1
sentinel monitor myredis 127.0.0.1 6379 1
# 如果有密码 则添加密码
sentinel auth-pass myredis linqin

后面的这个数字,代表主机挂了,slave投票看让数来成为主机,投票做多的就会成为主机

如果主机宕机之后,这个时候哨兵会选择一个从机作为主机

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aB8KNDn6-1616423125653)(C:\Users\86155\AppData\Roaming\Typora\typora-user-images\1616419631985.png)]

faliover 故障转移

哨兵模式

如果主机此时回来了,只能归并到新主机下,当从机,这就是哨兵模式

优点:

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

缺点:

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

哨兵模式的全部配置

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

Redis缓存穿透和雪崩

缓存穿透

概念

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

解决方案

  • 布隆过滤器:布隆过滤器是一种数据结构,对所有可能查询的参数 一hash的形式存储。在控制层先进行校验,不符合的则丢弃,从而避免了对底层存储系统的查询压力。
    如果想判断一个元素是不是在一个集合里,一般想到的是将所有元素保存起来,然后通过比较确定。链表,树等等数据结构都是这种思路. 但是随着集合中元素的增加,我们需要的存储空间越来越大,检索速度也越来越慢。不过世界上还有一种叫作散列表(又叫哈希表,Hash table)的数据结构。它可以通过一个Hash函数将一个元素映射成一个位阵列(Bit Array)中的一个点。这样一来,我们只要看看这个点是不是 1 就知道可以集合中有没有它了。这就是布隆过滤器的基本思想。

    • 但是这种方法会存在两个问题:
      • 如果空值能够被缓存起来,这就意味着缓存需要更多的空间存储更多的键,因为这当中可能会导致很多的空值的键;
      • 即使对空值设置了过期时间,还是会存在缓存层和存储层的数据会有一段时间窗口的不一致,这对于需要保持一致性的业务会有影响

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

概述

缓存击穿的意思就是,例如当微博热搜,这个时候这个点的访问量巨大,然后在某个时刻该热点的缓存过期或者其他 情况导致缓存中暂时没有该热点,这个时候依然大量并发就会全部集中到数据库去查询最新数据并回写缓存,很有可能在这一瞬间就导致数据库崩溃。这就是缓存击穿。

解决方法

设置热点数据永不过期
从缓存层面来看,没有设置过期时间,所以不会出现热点key过期后产生的问题。但是有可能会逐渐的让redis缓存增加,内存占用庞大。

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

缓存雪崩

概念

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

例如快要双十一0点的抢购高峰的时候,这个时候商品时间比较几种的放入了缓存中,假设缓存一小时,那么到了1点的时候,这批商品的缓存信息失效,对这批商品的查询都会落入到数据库中,对数据库而言,就会产生周期性的波峰压力。有可能会引起数据库挂掉的情况

解决方案

redis高可用

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

限流降级

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

数据预热

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值