Redis

Redis 6笔记

文章目录

1.NoSQL

1.1技术发展

  • 技术分类
    • 解决功能性的问题:Java、Jsp、RDBMS、Tomcat、HTML、Linux、.JDBC、SVNw
    • 解决扩展性的问题:Struts、Spring、SpringMVC、Hibernate、Mybatis.
    • 解决性能的问题:NoSQL、Java线程、Hadoop、Nginx、MQ、ElasticSearch.

1.2 概述

NoSQL,Not Only SQL ,不仅仅是SQL

  • NoSQL不依赖业务逻辑方式存储,而以简单的key-vaue模式存储。因此大大的增加了数据库的扩展能力。

    • 不遵循SQL标准。·
    • 不支持ACID。
    • 远超于SQL的性能。
  • 使用场景

    • 对数据高并发的读写
    • 海量数据的读写
    • 对数据高可扩展性的
  • 不适用

    • 需要事务支持、
    • 基于sql的结构化查询存储,处理复杂的关系,需要即席查询。
    • (用不着sql的和用了sql也不行的情况,请考虑用NoSql)
  • 为性能而生

2. Redis

2.1概述安装与启动

  • 概述

    • 开源key-value存储系统
    • 数据类型更多:string(字符串)、Iist(链表)、set(集合)、set(sortedset-有序集合)和hash(哈希类型)
    • 这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰官的操作而且这些操作都是原子性的。
    • 在此基础上,Redis支持各种不同方式的排序
    • 与memcached一样,为了保证效率,数据都是缓存在内存中。
    • 区别的是Rdis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件。
    • 并且在此基础上实现了master-slave(主从)同步。
  • 安装

    • 网址:https://redis.io
      1. 需要在Linux系统中安装
      2. 传送到/opt中
      3. Linux需要有gcc编译器
        • yum install gcc
      4. tar -zxvf redis-6.2.6.tar.gz
      5. 进入 redis-6.2.6文件夹
      6. make 直接输入编译
      7. make install安装
      8. 安装目录到/usr.local/bin
  • 目录解释

    • redis-benchmark:性能测试工具,可以在自己本子运行,看看自己本子性能如何
    • redis-.check-aof:修复有问题的AOF文件,rdb和aof后面讲,
    • redis-check-dump:修复有问题的dump.rdb文件,
    • redis-sentinel:Redis集群使用,
    • redis-server:Redis服务器启动命令
    • redis-cli:客户端,操作入口,

2.1.1 Redis 后台启动

  • 拷贝一份redis.conf到其他目录

    • cp /opt/redis-6.2.5/redis.conf /myredis.
  • redis.conf文件内容改daemonize no改成daemonize yes

  • cd /usr/local/bin打开安装的目录

  • 启动:redis-server /myredis/redis.conf

  • 关闭:

    • kill -9 进程id
    • redis-cli shutdown
  • ps -ef | grep redis

    • 进行测试启动是否成功
  • 本地使用: Redis-cli -p 6379

2.1.2 相关概述

  • 端口号:6379

在这里插入图片描述

多路io复用,3个顾客申请黄牛买票,黄牛处理买票过程时,顾客可以去做其他事情,等待黄牛买票。

2.2常用五大数据类型

2.2.1Redis键(keys)

keys *

查看当前库所有的key

在这里插入图片描述

exists key

判断某个key是否存在

在这里插入图片描述

type key

查看key的类型

在这里插入图片描述

del key

删除指定的key

在这里插入图片描述

unlink key

根据value,选择非阻塞删除

仅将keys从keyspace元数据中删除,真正的删除会在后续异步操作。

在这里插入图片描述

expire key 10

为给定的key设置过期时间为10秒.

在这里插入图片描述

ttl key

查看还有多少秒过期

  • -1表示永不过期

  • -2表示已过期

select [int]

切换数据库,默认0号库,一共16个库

在这里插入图片描述

dbsize

查看当前数据库的key的数量

flushdb

清空当前库

flushall

通杀全部库

2.2.2 字符串(String)

简介

  • String是Redis最基本的类型,你可以理解成与Memcached一模一样的类型,一个key对应一个value。

  • String类型是二进制安全的。意味着Redis的string可以包含任何数据。比如jpg图片

或者序列化的对象。

二进制安全:指没有任何特殊格式意义的数据流,譬如没有转义码、终止码等。在C语言中的字符串的末尾需要\0,则不是二进制安全。

  • String类型是Redis最基本的数据类型,一个Redis中字符串value最多可以是512M

常用命令

set key value [ex 秒数] [px 毫秒数] [nx/xx]

  • 如果ex和px同时写,则以后面的有效期为准

  • nx:如果key不存在则建立

  • xx:如果key存在则修改其值

setnx key value

如果key不存在才添加值

setex key time value

设置key对应的值value,并设置有效期为time秒

get key

取值

mset key1 value1 key2 value2

一次设置多个值

msetnx key1 value1 key2 value2

一次设置不存在的多个值

mget key1 key2

一次获取多个值

getset key value

获取旧值并以旧换新

append key value

把value追加到key 的原值上

getrange key start stop

获取字符串中[start, stop]范围的值

  • 对于字符串的下标,左数从0开始,右数从-1开始
  • 注意:当start>length,则返回空字符串
  • 当stop>=length,则截取至字符串尾
  • 如果start所处位置在stop右边,则返回空字符串

incr key

自增,返回新值,如果incr一个不是int的value则返回错误,incr一个不存在的key,则设置key为1

incrby key 2

跳2自增

incrbyfloat by 0.7

自增浮点数

strlen key

取指定key的value值的长度

  • 自增1或者设置步长的变化 都是原子性操作

数据结构

​ String的数据结构为简单动态字符串(Simple Dynamic String.,缩写SDS)。是可以修改的字符串,内部结构实现上类似于Java的ArrayList,采用预分配冗余空间的方式来减少内存的频繁分配。

在这里插入图片描述

​ 如图中所示,内部为当前字符串实际分配的空间capacity一般要高于实际字符串长度len。当字符串长度小于1M时,扩容都是加倍现有的空间,如果超过1M,扩容时一次只会多扩1M的空间。需要注意的是字符串最大长度为512M。

2.2.3列表(List)

简介

Rdis列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)。

它的底层实际是个双向链表,对两端的操作性能很高,通过索引下标的操作中间的节点性能会较差。

常用命令

lpush/rpush key value1 value2 …

前插法/后插法一个或多个值,前插法遍历出来的结果是倒序的

rpoplpush key1 key2

从key1列表的右边弹出一个值插到key2的左边

lrange key start stop

按照索引下标获得元素(从左到右)

0 -1 :遍历列表

lindex key index

按照索引下标获得元素(从左到右)

llen key

获得列表的长度


linsert <key> before/after <value> <newvalue>

在<value>之后或者之前插入<newvalue>

lrem <key> <n> <value>

从左边删除n个<value>

lset <key> <index> <value>

将列表key下标为index的值替换成value

数据结构

使用的是quicklist

  • 首先在列表元素较少的情况下会使用一块连续的内存存储,这个结构是ziplist,也即是压缩列表。
  • 它将所有的元素紧挨着一起存储,分配的是一块连续的内存。
  • 当数据量比较多的时候才会改成quicklist。
  • 因为普通的链表需要的附加指针空间太大,会比较浪费空间。比如这个列表里存的只是int类型的数据,结构上还需要两个额外的指针prev和next。

在这里插入图片描述

Redis将链表和ziplist结合起来组成了quicklist。.也就是将多个ziplist使用双向指针串起来使用。这样既满足了快速的插入删除性能,又不会出现太大的空间冗余。

2.2.4 集合(set)

简介

  • Redis set对外提供的功能与list类似是一个列表的功能,特殊之处在于set是可以自动排重的,当你需要存储一个列表数据,又不希望出现重复数据时,set是一个很好的选择,并且set提供了判断某个成员是否存在一个set集合内的重要功能,这个也是list所不能提供的。
  • Redis的Set是string类型的无序集合。它底层其实是一个value为null的hash表,所以添加,删除,查找的时间复杂度都是0(1)

常用命令

sadd key value1 value2 …

一次添加一个或者多个值。

smembers key

取出key所有的成员的值

sismember key value

判断集合是否存在value

srem key value1 value2

删除集合中的某个元素

spop key

随机弹出一个值

srandmenber key n

随机从集合取出n个值

smove key1 key2 value

把key1集合的value移到key2中

sinter key1 key2

取两集合的交集

sunion key1 key2

取两集合的并集

sdiff key1 key2

取两集合的差集(key1-key2)

数据结构

Set数据结构是dict字典,字典是用哈希表实现的。

2.2.5 哈希(Hash)

简介

Redis hash是一个键值对集合。

Redis hash是一个string类型的fieldvalue的映射表,hash特别适合用于存储对象。

类似Java里面的Map<String,Object>

用户ID为查找的key,存储的value用户对像包含姓名,年龄,生日等信息,如果用普通的key/value结构来存储,

在这里插入图片描述

常用命令

key可以声明成 key1:001 数字表示id号,也可以 China:beijing

hset key field value

给key集合中的field键赋值为value

hget key field

从key集合field取出value

hmset key1 field1 valule1 field2 value2 …

批量设置hash的值

hexists key1 field

查看哈希表的key中的field是否存在

hkeys key

列出该hash集合的所有field

hvals key

列出该hash集合的所有value

hincrby key field increment

为哈希表key中的域field的值加上增量1-1

hsetnx key field value

将哈希表key中的域field的值设置为value,当且仅当field不存在.

2.2.6有序集合(Zset)

简介

​ Redis有序集合zset与普通集合set非常相似,是一个没有重复元素的字符串集合。

​ 不同之处是有序集合的每个成员都关联了一个评分(score),这个评分(score)被用来按照从最低分到最高分的方式排序集合中的成员。集合的成员是唯一的,但是评分可以是重复了。

​ 因为元素是有序的,所以你也可以很快的根据评分(score)或者次序(position)来获取一个范围的元素。

常用命令

zadd key sore1 value1 sore2 value2

添加一个或多个value到有序集合key中

zrange key start stop [withscores]

返回下标从start到stop的value,按分数从小到大排序,最后一个参数表示的是连同分数一起显示。

zrangbyscore key mim max

返回有序集中中分数从min到max的成员,默认从小到大

zrevrangbyscore key max min

同上,改为了从大到小排列

zincrby key increment value

为集合key中的value元素的分数增量increment

zank key value

返回该值在集合中的排名,从0开始

数据结构

​ SortedSet(zset)是Redis提供的个非常特别的数据结构,一方面它等价于Java的数据结构Map<String,Double>,可以给每一个元素value赋予一个权重score,另一方面它又类似于TreeSet,内部的元素会按照权重score进行排序,可以得到每个元素的名次,还可以通过score的范围来获取元素的列表。

zset底层使用了两个数据结构,

(1)hash:

​ hash的作用就是关联元素value和权重score,保障元素value的唯一性,可以通过元素value找到相应的score值。

(2)跳跃表:

​ 跳跃表的目的在于给元素value排序,根据score的范围获取元素列表。

在这里插入图片描述

2.3 配置文件介绍

cd /etc/redis.conf

2.3.1Units单位

配置大小单位,开头定义了一些基本的度量单位,只支持bytes,不支持bit大小写不敏感.

在这里插入图片描述

2.3.2 includes包含部分

类似jsp中的nclude,多实例的情况可以把公用的配置文件提取出来

在这里插入图片描述

2.3.3 网络相关配置

bind

默认情况 bind=127.0.0.1 只能接受本机的访问请求。

不写的情况下,无限制接受任何 ip 地址的访问。

生产环境肯定要写你应用服务器的地址,服务器是需要远程访问的,所以需要将其注释掉,或者指定。

如果开启了***protected-mode***,那么在没有设定 bind ip 且没有设密码的情况下,Redis 只允许接受本机的响应。

在这里插入图片描述

protected-mode

如果想远程访问这要设置为no

在这里插入图片描述

port

端口号

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传在这里插入图片描述

tcp-backlog

设置 tcpbacklogbacklog 其实是一个连接队列,backlog 队列总和 = = = 未完成三次握手队列 + + + 已经完成三次握手队列。

在高并发环境下你需要一个高 backlog 值来避免慢客户端连接问题。

在这里插入图片描述

timeout

一个空闲的客户端维持多少秒会关闭,0 表示关闭该功能。即永不关闭。

空闲:心跳检测可以检测客户端,但是客户端没有发起请求。

在这里插入图片描述

tcp-keepalive

对访问客户端的一种心跳检测,每个 n 秒检测一次。

单位为秒,如果设置为 0,则不会进行 Keepalive 检测,建议设置成 60。

心跳:检查tcp连接情况,检测客户端是否仍保持连接。

在这里插入图片描述

空闲≠不心跳。

2.3.4 GENERAL通用

daemonize

是否为后台进程,设置为 yes

守护进程=后台启动。

在这里插入图片描述

pidfile

存放 pid 文件的位置,每个实例会产生一个不同的 pid 文件。

在这里插入图片描述

loglevel

指定日志记录级别,Redis 总共支持四个级别:debug、verbose、notice、warning,默认为 notice

在这里插入图片描述

logfile

日志文件路径以及名称。

在这里插入图片描述

database

设定库的数量 默认16,默认数据库为 0,可以使用 SELECT 命令在连接上指定数据库 id

在这里插入图片描述

2.3.5 security安全

访问密码的查看、设置和取消。

在命令中设置密码,只是临时的。重启 redis 服务器,密码就还原了。

永久设置,需要在配置文件中进行设置。

密码

vm_redis:0>config get requirepass  //获取密码 
 1)  "requirepass"
 2)  ""
vm_redis:0>config set requirepass 123456  //设置密码
"OK"
vm_redis:0>config get requirepass 
 1)  "requirepass"
 2)  "123456"
vm_redis:0>ping
"PONG"
vm_redis:0>auth 123456  //使用密码登录
"OK"

2.3.6LIMITS限制

限制。

maxclients

设置 redis 同时可以与多少个客户端进行连接。

默认情况下为 10000 个客户端。

如果达到了此限制,redis 则会拒绝新的连接请求,并且向这些连接请求方发出 max number of clients reached 以作回应。

在这里插入图片描述

maxmemory

建议必须设置,否则,将内存占满,造成服务器宕机。

设置 redis 可以使用的内存量。一旦到达内存使用上限,redis 将会试图移除内部数据,移除规则可以通过 maxmemory-policy 来指定。

如果 redis 无法根据移除规则来移除内存中的数据,或者设置了不允许移除,那么 redis 则会针对那些需要申请内存的指令返回错误信息,比如 SET、LPUSH 等。

但是对于无内存申请的指令,仍然会正常响应,比如 GET 等。如果你的 redis 是主 redis( 说明你的 redis 有从 redis ),那么在设置内存使用上限时,需要在系统中留出一些内存空间给同步队列缓存,只有在你设置的是“不移除”的情况下,才不用考虑这个因素。

在这里插入图片描述

maxmemory-policy

volatile-lru:使用 LRU 算法移除 key,只对设置了过期时间的键(最近最少使用)。

allkeys-lru:在所有集合 key 中,使用 LRU 算法移除 key

volatile-random:在过期集合中移除随机的 key,只对设置了过期时间的键。

allkeys-random:在所有集合 key 中,移除随机的 key

volatile-ttl:移除那些 TTL 值最小的 key,即那些最近要过期的 key

noeviction:不进行移除。针对写操作,只是返回错误信息。

在这里插入图片描述

maxmemory-samples

设置样本数量,LRU 算法和最小 TTL 算法都并非是精确的算法,而是估算值,所以你可以设置样本的大小,redis 默认会检查这么多个 key 并选择其中 LRU 的那个。

一般设置 3 到 7 的数字,数值越小样本越不准确,但性能消耗越小。

在这里插入图片描述

2.3.7 SNAPSHOTTING 快照

,快照分rdb和aof

  • save “” 持久化操作的策略

    • save 900 1 如果900秒内有一次key进行了更新,则持久化一次

    • save 300 10 如果300秒内有10 key进行了更新,则持久化一次

    • save 60 10000 如果60秒 内有至少10000个key进行了更新,则持久化一次。

      在这里插入图片描述

  • stop-writes-on-bgsave-error yes 如果持久化过程出现错误,是否还要继续

  • rdbcompression yes 是否压缩rdb文件,需要消耗一些CPU资源

  • rdbchecksum yes 保存rdb文件的时候,进行错误的检查校验

  • dir ./ rdb文件保存的目录,默认当前目录下

  • dbfilename dump.rdb rdb文件的名字,默认dump

2.3.8 REPLICATION 主从复制

2.3.9 APPEND ONLY MODE模式

aof配置(了解,大部分情况下rdb已经足够使用,aof是对rdb的一个补充,并且性能不高,因此使用少)

appendonly no

默认不开启aof模式,如果我们要启用aof,一般情况下只需要将该项配置改为yes即可

2.4 Redis的发布与订阅

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

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

  1. 客户端可以订阅频道

在这里插入图片描述

  1. 当给这个频道发布消息后,消息就会发送给订阅的客户端

    在这里插入图片描述

常用命令

publish channel hello  # 发送信息到指定频道

psubscribe channel channel ..  # 订阅一个或多个频道

punsubscribe channel1 ... #退订一个或多个频道

测试

订阅端

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

发送端

127.0.0.1:6379> PUBLISH aguo "hello,redis" # 发布者发布消息到频道!
(integer) 1

原理

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

2.5 Redis6新数据类型

2.5.1 Bitmap

简介

Bitmaps 本身不是一种数据类型, 实际上它就是字符串(key-value) , 但是它可以对字符串的位进行操作。

Bitmaps 单独提供了一套命令, 所以在 Redis 中使用 Bitmaps 和使用字符串的方法不太相同。 可以把 Bitmaps 想象成一个以位为单位的数组, 数组的每个单元只能存储 0 和 1, 数组的下标在 Bitmaps 中叫做偏移量。

Bit-map的基本思想就是用一个bit位来标记某个元素对应的Value,而Key即是该元素。由于采用了Bit为单位来存储数据,因此在存储空间方面,可以大大节省。(PS:划重点 节省存储空间

假设有这样一个需求:在20亿个随机整数中找出某个数m是否存在其中,并假设32位操作系统,4G内存

在Java中,int占4字节,1字节=8位(1 byte = 8 bit)

如果每个数字用int存储,那就是20亿个int,因而占用的空间约为 (2000000000*4/1024/1024/1024)≈7.45G

如果按位存储就不一样了,20亿个数就是20亿位,占用空间约为 (2000000000/8/1024/1024/1024)≈0.233G

常用命令

setbit key offset value

设置key中偏移量为offset的得位值为value,value只能是0\1

注:

  • 很多应用的用户id以一个指定数字(例如10000)开头,直接将用户id和Bitmaps的偏移量对应势必会造成一定的浪费,通常的做法是每次做setbit操作时将用户id减去这个指定数字。

  • 在第一次初始化Bitmaps时,假如偏移量非常大,那么整个初始化过程执行会比较慢,可能会造成Redis的阻塞。

get key offset

取出key偏移量为offset的值

bitcount key [start end]

统计字符串1个数量

注:start、end是以8个位为单位。1 2 ,表示第2个和第三个的字符组

bitop and\or\not\xor newKey oldkey1 oldkey2

做oldkey1 oldkey2位的与或非异或运算的结果放到newKey中

2.5.2 HyperLogLog

简介

  • 在工作当中,我们经常会遇到与统计相关的功能需求,比如统计网站 PV(PageView 页面访问量),可以使用 Redis 的 incr、incrby 轻松实现。但像 UV(UniqueVisitor 独立访客)、独立 IP 数、搜索记录数等需要去重和计数的问题如何解决?这种求集合中不重复元素个数的问题称为基数问题。

    解决基数问题有很多种方案:

    • 数据存储在 MySQL 表中,使用 distinct count 计算不重复个数。
    • 使用 Redis 提供的 hash、set、bitmaps 等数据结构来处理。
  • 以上的方案结果精确,但随着数据不断增加,导致占用空间越来越大,对于非常大的数据集是不切实际的。能否能够降低一定的精度来平衡存储空间?Redis 推出了 HyperLogLog。

    • Redis HyperLogLog 是用来做基数统计的算法,HyperLogLog 的优点是:在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定的、并且是很小的。
    • 在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。
    • 但是,因为 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以 HyperLogLog 不能像集合那样,返回输入的各个元素。所以存进去就取不出来了!
  • 什么是基数?

    • 比如数据集 {1, 3, 5, 7, 5, 7, 8},那么这个数据集的基数集为 {1, 3, 5 ,7, 8},基数 (不重复元素) 为 5。 基数估计就是在误差可接受的范围内,快速计算基数。

常用命令

pfadd key value1 [value2]…

添加数据,成功返回1,否则0

pfcount key

统计基数的个数

pfmerge newKey oldkey1 oldkey2

合并oldkey1 oldkey2的基数到newKey中,注意是取并集!

在这里插入图片描述

2.5.3 Geospatial

简介

Redis 3.2 中增加了对 GEO 类型的支持。GEO,Geographic,地理信息的缩写。该类型,就是元素的 2 维坐标,在地图上就是经纬度。redis 基于该类型,提供了经纬度设置,查询,范围查询,距离查询,经纬度 Hash 等常见操作。

2.6 Jedis操作

在java中连接操作Redis

  1. 导入依赖

    <dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
        <version>3.3.0</version>
    </dependency>
    
    
  2. 创建Jedis对象连接

    //创建Jedis对象连接
    Jedis jedis = new Jedis("192.168.198.142",6379);
    //测试连接
    String ping = jedis.ping();
    System.out.println(ping);
    
  3. 很多操作在Linux的操作是一样的

    jedis.select(0);
    jedis.keys("*");
    jedis.set("k1","123");
    jedis.mget("k1","k2");
    jedis.zadd("k4",300d,"java");//分数是double类型要加d
    
  4. 记得关闭连接

    jedis.close();
    

2.7springboot整合Redis

2.7.1 Redis的简单初始化

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

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

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

  1. 导入依赖

    方法一:创建工程选择依赖

    • 在这里插入图片描述

    • 在这里插入图片描述

    方法二:创建普通的springboot项目后,手动导入依赖

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    
  2. 简单配置

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

    这里演示一下看springboot源码来配置配置类

    • 打开所有的依赖文件夹,找到看图

在这里插入图片描述

  • 点击RedisProperties.class进入源码

在这里插入图片描述

  • 这里就是可以配置的属性。

在这里插入图片描述

  • 在右边,或者按下Alt+7打开左下角小窗口可以看到所有属性

    在这里插入图片描述

  1. 配置application.properties

    # 应用名称
    spring.application.name=redis_springboot
    # 应用服务 WEB 访问端口
    server.port=8080
    
    #配置Redis
    spring.redis.host=192.168.198.142
    spring.redis.port=6379 #默认的,配不配都行
    
  2. 使用在这里插入图片描述

  • 问题

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

    在这里插入图片描述

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

    • 默认的序列化使用JDK的方式,我们想要json的序列化方式,所有有了下一步。

2.7.2 Redis的自定义配置类

我们翻开RedisAutoConfiguration.class源码看到:

在这里插入图片描述

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

所以我们自己写个配置类

package com.aguo.configuration;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.net.UnknownHostException;

@Configuration
public class RedisConfiguration {
    // 这是我给大家写好的一个固定模板,大家在企业中,拿去就可以直接使用!
    // 自己定义了一个 RedisTemplate
    @Bean
    @SuppressWarnings("all")
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {

        // 我们为了自己开发方便,一般直接使用 <String, Object>
        RedisTemplate<String, Object> template = new RedisTemplate<String,
                Object>();
        template.setConnectionFactory(factory);

        // Json序列化配置
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new
                Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        // String 的序列化
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();

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

        return template;
    }
    @Bean
    @ConditionalOnMissingBean
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory)
            throws UnknownHostException {
        StringRedisTemplate template = new StringRedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        // Json序列化配置
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new
                Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        // String 的序列化
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();

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

然后一个工具类,因为我们实际开发中不可能用原生的代码去写,所以封装了一个工具类,类在Redis_springboot中有

@SpringBootTest
class RedisSpringbootApplicationTests {
    
    @Autowired
    RedisUtil redisUtil;//这里注入工具类
    @Test
    void contextLoads() {
        User user = new User(123, "臭宝");
        redisUtil.set("k2",user);
        System.out.println(redisUtil.get("k2"));
    }
}

2.8 事务和锁操作

2.8.1 Redis中使用事务


Redis 事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。

Redis 事务的主要作用就是串联多个命令防止别的命令插队


2.8.2 Multi、Exec、discard

Redis 事务中有 Multi、Exec 和 discard 三个指令,在 Redis 中,从输入 Multi 命令开始,输入的命令都会依次进入命令队列中,但不会执行,直到输入 Exec 后,Redis 会将之前的命令队列中的命令依次执行。而组队的过程中可以通过 discard 来放弃组队。

案列说明:

正常执行

#开始事务
	$multi
OK
	$set k1 v1
QUEUED			#组队中 QUEUED加入队列成功
	$set k2 v2
QUEUED			#组队中
	$EXEC		#执行
1)OK
2)OK
#两条都执行成功

组队时报错

#开始事务
	$multi
OK
	$set k1 v1
QUEUED			#组队成功
	$set k2
ERROR			#组队失败,报指令错误
	$EXEC		#执行
ERROR执行失败

执行时失败

#开始事务
	$multi
OK
	$set k1 v1
QUEUED			#组队成功
	$incr k1
QUEUED			#组队成功
	$set k2 v2
QUEUED			#组队成功
	$EXEC		#执行
1)OK
2)ERROR执行失败
3)OK
#1,3成功,2失败

redis事务中,若执行中的某一条失败了并不会影响其他的指令执行!有原子性但没有一致性!

2.8.3 悲观与乐观锁

悲观锁 (Pessimistic Lock)

​ 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会 block 直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。

乐观锁

​ 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量。Redis 就是利用这种 check-and-set 机制实现事务的。

乐观锁操作

WATCH key [key …]

在执行 multi 之前,执行 watch key1 [key2],可以监视一个 (或多个) key ,如果在事务执行之前这个 (或这些) key 被其他命令所改动,那么事务将被打断

#初始化
	$set debt 100
OK
	$set balance 10
OK
#开始事务
#开始事务
	$multi
OK
	$decrby balance 1
QUEUED
	$incrby debt10
QUEUED
	$exec
#若其他进程没有修改balance,那么以上两句将执行成功
1)10
2)

unwatch

取消 WATCH 命令对所有 key 的监视。如果在执行 WATCH 命令之后,EXEC 命令或 DISCARD 命令先被执行了的话,那么就不需要再执行 UNWATCH 了。

2.8.4 redis事务三特性

  • 单独的隔离操作 :事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
  • 没有隔离级别的概念 :队列中的命令没有提交之前都不会实际被执行,因为事务提交前任何指令都不会被实际执行。
  • 不保证原子性 :事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚

2.8.5 双重校验锁

    public class Singleton {
 
        private static volatile Singleton singleton = null;
 
        private Singleton() {
        }
 
        public static Singleton getInstance(){
            //第一次校验singleton是否为空
            if(singleton==null){
                synchronized (Singleton.class){
                    //第二次校验singleton是否为空
                    if(singleton==null){
                        singleton = new Singleton();
                    }
                }
            }
            return singleton;
        }
    }

为什么是双重校验锁实现单例模式呢?

  • 第一次校验:也就是第一个if(singleton==null),这个是为了代码提高代码执行效率,由于单例模式只要一次创建实例即可,所以当创建了一个实例之后,再次调用getInstance方法就不必要进入同步代码块,不用竞争锁。直接返回前面创建的实例即可。

  • 第二次校验:也就是第二个if(singleton==null),这个校验是防止二次创建实例,假如有一种情况,当singleton还未被创建时,线程t1调用getInstance方法,由于第一次判断singleton==null,此时线程t1准备继续执行,但是由于资源被线程t2抢占了,此时t2页调用getInstance方法,同样的,由于singleton并没有实例化,t2同样可以通过第一个if,然后继续往下执行,同步代码块,第二个if也通过,然后t2线程创建了一个实例singleton。此时t2线程完成任务,资源又回到t1线程,t1此时也进入同步代码块,如果没有这个第二个if,那么,t1就也会创建一个singleton实例,那么,就会出现创建多个实例的情况,但是加上第二个if,就可以完全避免这个多线程导致多次创建实例的问题。

所以说:两次校验都必不可少。

2.9 Redis持久化

面试和工作,持久化都是重点!

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

2.9.1 RDB(Redis DataBase)

  • 什么是RDB

    在这里插入图片描述

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

​ Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程 都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何IO操作的。 这就确保了极高的性能。

​ 如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那 RDB方式要比AOF方式更加的高效。RDB的缺点是最后一次持久化后的数据可能丢失。我们默认的就是 RDB,一般情况下不需要修改这个配置!

有时候在生产环境我们会将这个文件进行备份!

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

在这里插入图片描述

  • 触发机制

    1、save的规则满足的情况下,会自动触发rdb规则

    2、执行 flushall 命令,也会触发我们的rdb规则!

    3、退出redis,也会产生 rdb 文件! 备份就自动生成一个 dump.rdb

在这里插入图片描述

ps:第一条表示3600秒设置一个key后会触发rdb。

  • 恢复机制

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

    2、查看需要存在的位置

127.0.0.1:6379> config get dir
1) "dir"
2) "/usr/local/bin" # 如果在这个目录下存在 dump.rdb 文件,启动就会自动恢复其中的数据

几乎就他自己默认的配置就够用了,但是我们还是需要去学习!

  • 优缺
    • 优点:

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

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

    • 缺点:

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

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

2.9.2 AOF (Append Only File)

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

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

Aof保存的是 appendonly.aof 文件

append

在这里插入图片描述

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

​ 如果这个 aof 文件有错位,这时候 redis 是启动不起来的吗,我们需要修复这个aof文件 redis 给我们提供了一个工具 redis-check-aof --fix在安装目录下有。

命令是:redis-check-aof --fix appendonly.aof

重写规则说明

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

在这里插入图片描述

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

在这里插入图片描述

这里补充什么是重写?

  • AOF 持久化是通过保存被执行的写命令来记录数据库状态的,所以AOF文件的大小随着时间的流逝一定会越来越大;影响包括但不限于:对于Redis服务器,计算机的存储压力;AOF还原出数据库状态的时间增加;
  • 为了解决AOF文件体积膨胀的问题,Redis提供了AOF重写功能:Redis服务器可以创建一个新的AOF文件替代现有的AOF文件,新旧两个文件所保存的数据库状态是相同的,但是新的AOF文件不会包含任何浪费空间的冗余命令,通常体积会较旧AOF文件小很多
  • AOF重写并不需要对原有AOF文件进行任何的读取,写入,分析等操作,这个功能是通过读取服务器当前的数据库状态来实现的。也就是当前保存了什么值就记录什么值。

2.10 Redis主从复制

主机数据更新后根据配置和策略, 自动同步到备机的 master/slaver 机制,Master 以写为主,Slaver 以读为主。

在这里插入图片描述

  • 优点
    • 读写分离,性能扩展
    • 容灾 快速回复
    • 一主多从

2.10.1 搭建一主两从

  1. 得到redis.conf文件

    cp .. /myredis/redis.conf
    
  2. 创建多个配置文件

    touch redis6379.conf
    touch redis6379.conf
    touch redis6379.conf
    
  3. 编辑内容

    # redis6379.conf
    include /opt/etc/redis.conf
    pidfile /var/run/redis_6379.pid
    port 6379
    dbfilename dump6379.rdb
    
    # redis6380.conf
    include /opt/etc/redis.conf
    pidfile /var/run/redis_6380.pid
    port 6380
    dbfilename dump6380.rdb
    
    # redis6381.conf
    include /opt/etc/redis.conf
    pidfile /var/run/redis_6381.pid
    port 6381
    dbfilename dump6381.rdb
    
  4. 启动redis

    redis-server /myredis/redis6379.conf
    redis-server /myredis/redis6380.conf
    redis-server /myredis/redis6381.conf
    

    在这里插入图片描述

  5. 查看redis的主从情况

    info replication

    在这里插入图片描述

    默认情况下,所有的redis都是主机

  6. 设置从机指令

    slaveof ip port

    127.0.0.1:6380> slaveof 127.0.0.1 6379 #第一个从机
    127.0.0.1:6381> slaveof 127.0.0.1 6379 #第二个从机
    

    可以将slaveof 127.0.0.1 6379放到redis6381.conf的配置文件中,启动即是从机

  7. 再次查看主机的情况

    在这里插入图片描述

  8. 成功

2.10.2 一些问题

  • 如果主机宕机

    • 写操作无法进行,但是读操作仍然可以在从机上进行。
    • 当主机恢复,写操作恢复,从机从主机上拷贝信息。
  • 如果从机宕机

    • 一主二仆:另外一个从机正常读,主机正常写

      1647163593192
    • 薪火相传:该从机的下家从机无法获取主机的数据。1647163593192

    • 如果恢复,默认会变成主机。当然,如果redis.conf文件已经配置好slaveof的指令,启动后即是从机,数据全量恢复

复制原理

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

Master 接到命令,启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令,在后台进程执行 完毕之后,master将传送整个数据文件到slave,并完成一次完全同步

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

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

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

  • 当主机宕机手机设置主机

    • 谋朝篡位

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

2.10.3 哨兵模式

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

在这里插入图片描述

  1. 创建并编辑 sentinel.conf 文件

    vi /myredis/sentinel.conf
    
    sentinel monitor mymaster 172.16.88.168 6379 1
    
    # mymaster:监控对象起的服务器名称
    # 1:是哨兵用来判断某个 Redis 服务器是否下线的参数,表示投票需要的"最少法定人数",比如有10个sentinal哨兵都在监控某一个master节点,如果需要至少6个哨兵发现master挂掉后,才认为master真正down掉,那么这里就配置为6,最小配置1台master,1台 slave
    
  2. 启动哨兵

    redis-sentinel  /myredis/sentinel.conf
    

    redis-sentinel在redis安装目录下

    在这里插入图片描述

  3. [测试] 关闭主机

    在这里插入图片描述

    1. 主机从6379端口转变为6380
    2. 6381变成了6380的从机
    3. 6379变成了从机
    4. 6379客观下线

    主观下线:认为主机离线的哨兵数量处于1~max之间

    客观下线:认为主机离线的哨兵数量处于>max.

    max:认为客观下线的临界值,可以手动配置。

    4.主机恢复后

    变成了别人的奴隶,不再是皇帝

    选举规则

    • 根据优先级别,slave-priority/replica-priority,优先选择优先级靠前的。

    • 根据偏移量,优先选择偏移量大的。

    • 根据 runid,优先选择最小的服务。

2.11Redis集群

容量不够,redis 如何进行扩容?

并发写操作, redis 如何分摊?

主从模式,薪火相传模式,主机宕机,导致ip地址发生变化,应用程序中配置需要修改对应的主机地址、端口等信息,非常麻烦不好维护。

之前采用的是代理模式,自从redis3.0之后,无中心化集群

代理模式

在这里插入图片描述

以前的集群方式:

采用中心代理来处理和分发请求,但是因为用户、订单、商品、代理这4个模块都有可能会gg,所以在各自模块的基础上均进行横向主从复制。

2.11.1 无中心化配置

Redis集群实现了对redis的水平扩容,即启动Nredis节点,将整个数据库分布存储在这N个节点中,每个节点存储总数据的1/N

redis集群通过分区来提供一定程 (partition) 度的可用性 (availability) :即使集群中有一部分失效或者无法进行通信,集群也可以继续处理命令请求。

在这里插入图片描述

2.11.2 搭建集群环境

  1. 创建配置文件

    共创建 :6379,6380,6381,:3689,3690,6391端口的Redis配置文件,对应为主从关系

    vi 编辑器中 :%s/old/new 可以快速替换字符

    # 以redis6379.conf为例
    include /opt/etc/redis.conf
    pidfile /var/run/redis_6379.pid # 更改
    port 6379 # 更改
    dbfilename dump6379.rdb # 更改
    
    cluster-enabled yes # 打开集群模式
    cluster-config-file nodes-6379.conf # 设置节点配置文件名称,需要更改
    cluster-node-timeout 15000 # 设置节点失联事件,超过该时间(ms),集群自动进行主从切换
    
  2. 启动所有的Redis

    img

  3. 将6个节点Redis组成集群

    确保当前配置目录下的nodes-xxx.conf文件的存在img

    # 进入redis安装目录
    /opt/redis-6.2.6/src
    
    # 执行
    redis-cli --cluster create --cluster-replicas 1 192.168.198.142:6379 192.168.198.142:6380 192.168.198.142:6381 192.168.198.142:6389 192.168.198.142:6390 192.168.198.142:6391
    #注意:以上的ip地址必须为Linux的真实ip地址,不能127.0.0.1或者localhost
    

    查看默认的主从情况,输入yes表示确认默认配置

    在这里插入图片描述

  4. 使用redis集群

    redis-cli -c -p PORT
    cluster nodes # 命令查看集群信息
    

    -c 采用集群策略连接,设置数据会自动切换到相应的写主机。

    因为现在是集群情况下,所以需要加-c参数,端口号随便,因为是责任链模式,集群策略会负责转发到相应的Redis并处理请求。

    img

2.11.3 集群操作

  1. redis cluster 如何分配这六个节点?

    一个集群至少要有三个主节点

    选项==–cluster-replicas 1==,表示希望为集群中的每一个节点创建一个从节点。

    分配原则尽量保证每个主数据库运行在不同的 IP 地址,每个从库和主库不在一个 IP 地址上。

    img

  2. 什么是插槽 Slot?

    • 一个 Redis 集群包含 16384 个插槽(hash slot), 数据库中的每个键都属于这 16384 个插槽的其中一个。

    • 集群使用公式 CRC16(key) % 16384 来计算键 key 属于哪个槽, 其中 CRC16(key) 语句用于计算键 keyCRC16 校验和 。

    • 集群中的每个节点负责处理一部分插槽。 例如, 如果一个集群可以有主节点, 其中:

      • 节点 A 负责处理 0 号至 5460 号插槽。
      • 节点 B 负责处理 5461 号至 10922 号插槽。
      • 节点 C 负责处理 10923 号至 16383 号插槽。

      img

  3. 如何在集群中录入值

    • redis-cli 每次录入、查询键值,redis 都会计算出该 key 应该送往的插槽,如果不是该客户端对应服务器的插槽,redis 会报错,并告知应前往的 redis 实例地址和端口。
    • redis-cli* 客户端提供了 –c 参数实现自动重定向。例如 redis-cli -c –p 6379 登入后,再录入、查询键值对可以自动重定向。
    [root@localhost src]# redis-cli -c -p 6379
    

    设置单个值:

    127.0.0.1:6379> $set k1 v2
    -> Redirected to slot [12706] located at 192.168.198.142:6381
    OK #重定向到了81的主机上赋值
    
    192.168.198.142:6381> $hset user name Tom age 21 nation China
    -> Redirected to slot [5474] located at 192.168.198.142:6380
    (integer) 3 #重定向到了80的主机上赋值
    

    设置多个值

    192.168.198.142:6380> $mset username{class1} aguo id{class1} 1925110
    OK #表示没有重定向
    

    注意:可以通过来定义组的概念,从而使key中内相同内容的键值对放到一个slot中去。

  4. 如何查询集群中的值?

    每个主机只能查询自己范围内部的插槽。

    $cluster keyslot #查询某个 key 的 slot
    
    $cluster countkeysinslot #查询某个 slot 是否有值。
    
    $CLUSTER GETKEYSINSLOT #返回 count 个 slot 槽中的键。
    

2.11.4 故障恢复

  1. 主机gg,从机篡位,复活变从机

    6379主机gg,那么身为6379主机的从机638015s后会篡位成功。15s在配置文件中更改:cluster-node-timeout 15000 # 设置节点失联事件,超过该时间(ms),集群自动进行主从切换

  2. 若一段插槽的主从机均宕机

    • 根据redis.conf的 cluster-require-full-coverage决定。

      • yes

        整个集群都挂掉

      • no

        该插槽数据全都不能使用,也无法存储。

2.11.5 集群的优缺点

优点

  • 实现扩容;
  • 分摊压力;
  • 无中心配置相对简单。

缺点

  • 多键操作是不被支持的,

    比如不能使用mset k1 v2 k2 v2多个key赋值,因为这是一条指令,k1,k2计算出来的哈希值可能不是指向同个Redis,也就是不同插槽,Redis无法这样赋值。

    mset k1{h} v1 k2{h} v2,这个分组形式就行,因为此时都是以“h”计算哈希值

  • 多键的 Redis 事务是不被支持的。lua 脚本不被支持;

  • 由于集群方案出现较晚,很多公司已经采用了其他的集群方案,而代理或者客户端分片的方案想要迁移至***redis cluster***,需要整体迁移而不是逐步过渡,复杂度较大。

2.11.6 集群的Jedis开发

public class JedisClusterTest {
  public static void main(String[] args) { 
     Set<HostAndPort>set =new HashSet<HostAndPort>();
     set.add(new HostAndPort("172.16.88.168",6379)); // 任何一个端口
     JedisCluster jedisCluster = new JedisCluster(set);
     jedisCluster.set("k1", "v1");
     System.out.println(jedisCluster.get("k1"));
  }
}

2.12Redis中的应用问题

2.12.1 缓存穿透

img

原因

key 对应的数据在数据源并不存在,每次针对此 key 的请求从缓存获取不到,请求都会压到数据源,从而可能压垮数据源。

比如:应用服务器接收到了一个id=-1的请求,但是这个请求在缓冲中不存在,于是转发查询数据库也查询不到,黑客利用该漏洞可能压垮服务器!

解决方法

  • 对空值缓存

    如果一个查询返回的数据为空(不管是数据是否不存在),仍然把这个空结果(null)进行缓存,设置空结果的过期时间会很短,最长不超过五分钟。

  • 设置可访问的名单(白名单):

    使用 bitmaps 类型定义一个可以访问的名单,名单 id 作为 bitmaps 的偏移量,每次访问和 bitmap 里面的 id 进行比较,如果访问 id 不在 bitmaps 里面,进行拦截,则不允许访问。

  • 采用布隆过滤器

    布隆过滤器(Bloom Filter)是1970年由布隆提出的。它实际上是一个很长的二进制向量(位图)和一系列随机映射函数(哈希函数)。

    布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。

    将所有可能存在的数据哈希到一个足够大的 bitmaps 中,一个一定不存在的数据会被这个 bitmaps 拦截掉,从而避免了对底层存储系统的查询压力。

  • 进行实时监控

    当发现 Redis 的命中率开始急速降低,需要排查访问对象和访问的数据,和运维人员配合,可以设置黑名单限制服务。

2.12.2缓存击穿

img

原因

key 对应的数据存在,但在 redis 中过期,此时若有大量并发请求过来,这些请求发现缓存过期一般都会从后端***DB*** 加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端 DB 压垮。

比如:应用服务器短时间内接收到了极多个id=10的请求,但是这个请求在缓冲中刚刚过期,于是大量请求均转发查询数据库查询不到,压垮数据库!

  1. 数据库访问压力瞬间增大。
  2. redis 中没有出现大量 key 过期,redis 正常运行。
  3. (即某个经常访问的 key 过期,突然有大量访问这个数据)

如何解决

  • 预先设置热门数据:

    在redis高峰访问之前,把一些热门数据提前存入到redis里面,加大这些热门数据key的时长

  • 实时调整

    现场监控哪些数据热门,实时调整key的过期时长。

  • 使用锁

    锁第一请求缓存的线程,阻塞其他的线程,拥有锁线程从数据库获取到数据后,缓存到Redis中,后续线程便可以直接访问Redis。

2.12.3 缓存雪崩

在这里插入图片描述

原因

​ 短时间内待查询的key大量过期。其对储存系统的冲击非常大!

解决方案

  • 构建多级缓存架构

    nginx 缓存 + redis 缓存 + 其他缓存(***ehcache***等)

  • 使用锁或队列:

    用加锁或者队列的方式保证来保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的并发请求落到底层存储系统上。不适用高并发情况。

  • 设置过期标志更新缓存:

    记录缓存数据是否过期(设置提前量),如果过期会触发通知另外的线程在后台去更新实际 key 的缓存。

  • 将缓存失效时间分散开:

    比如我们可以在原有的失效时间基础上增加一个随机值,比如 1~5 分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。

2.12.4 分布式锁

​ 随着业务发展的需要,原单体单机部署的系统被演化成分布式集群系统后,由于分布式系统多线程、多进程并且分布在不同机器上,这将使原单机部署情况下的并发控制锁策略失效,单纯的Java API并不能提供分布式锁的能力。为了解决这个问题就需要一种跨JM的互斥机制来控制共享资源的访问,这就是分布式锁要解决的问题!

  • 分布式锁主流的实现方案:

    1. 基于数据库实现分布式锁
    2. 基于缓存(Redis等)
    3. 基于Zookeeper.
  • 每一种分布式锁解决方室都有各白的优快点:

  1. 性能:redis最高

  2. 可靠性:zookeeper最高

基于redis实现分布式锁

  1. 第一种方式
$setnx lock 10 #设置锁
$setnx lock 10 #因为setnx只有原来key才能设置成功,所以这了设置失败
$del lock #删除锁
# 为了避免忘记释放 我们应该设置锁的有效期
$expire lock 10 #设置10秒

但是如果执行了第一句断电,那么锁将永远无法释放

  1. 第二种方式(优化)
set key value [ex 秒数] [px 毫秒数] [nx/xx]
$set lock 10 ex 12 nx
#10,lock的值,nx,lock不存在时才能设置值,ex,有效期,xx:如果key存在则修改其值

2.12.5 一些疑难

  • redis方式设置的锁对于Redis集群有用吗?

    有用。

    因为对于key值为lock,集群(cluster)会通过哈希算法,得出lock的哈希值从而得到其插槽在哪个主节点,主节点负责收录该lock,而该主节点的从机copy相同数据,java仍然能从任意主节点获取到该lock。

    因此,lock既不会因为集群而丢失从而失去作用!

2.12.6 Java代码测试

@GetMapping("testLock")
public void testLock(){
    //1获取锁,setne ,顺便设置过期时间
    Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", "111",3,TimeUnit.SECONDS);
    //2获取锁成功、查询num的值
    if(lock){
        Object value = redisTemplate.opsForValue().get("num");
        //2.1判断num为空return
        if(StringUtils.isEmpty(value)){
            return;
        }
        //2.2有值就转成成int
        int num = Integer.parseInt(value+"");
        //2.3把redis的num加1
        redisTemplate.opsForValue().set("num", ++num);
        //2.4释放锁,del
        redisTemplate.delete("lock");

    }else{
        //3获取锁失败、每隔0.1秒再获取
        try {
            Thread.sleep(100);
            testLock();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}


2.12.7 UUID防止误删

在这里插入图片描述

为了避免a复活后,把b的锁给释放了,最后一系列的问题

为此应该多一个判断是否是你的锁,虽然是共享锁,都是一样的,但是可以上锁之后在设置时间,还要给每个用户的这把锁都来一个uuid

java代码

@GetMapping("testLock")
public void testLock(){
	String uuid = UUID.randomUUID().toString();
    //1获取锁,setne ,顺便设置过期时间
    Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid,3,TimeUnit.SECONDS);
    //2获取锁成功、查询num的值
    if(lock){
       ...
        String lockUuid = (String)redisTemplate.opsForValue().get("lock");
        if(uuid.equals(lockUuid)){
             //2.4释放锁,del
        	redisTemplate.delete("lock");
        }
    }else{
        //3获取锁失败、每隔0.1秒再获取
        try {
            Thread.sleep(100);
            testLock();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

2.12.8 原子性删除

问题又来了

在这里插入图片描述

结果a把b的锁误删了,因为a已经验证过那个锁是自己的,但是自己的锁刚好过期,所以删除的时候会删掉b的锁去了。

@GetMapping("testLockLua")
public void testLockLua() {
    //1 声明一个uuid ,将做为一个value 放入我们的key所对应的值中
    String uuid = UUID.randomUUID().toString();
    //2 定义一个锁:lua 脚本可以使用同一把锁,来实现删除!
    String skuId = "25"; // 访问skuId 为25号的商品 100008348542
    String locKey = "lock:" + skuId; // 锁住的是每个商品的数据

    // 3 获取锁
    Boolean lock = redisTemplate.opsForValue().setIfAbsent(locKey, uuid, 3, TimeUnit.SECONDS);

    // 第一种: lock 与过期时间中间不写任何的代码。
    // redisTemplate.expire("lock",10, TimeUnit.SECONDS);//设置过期时间
    // 如果true
    if (lock) {
        // 执行的业务逻辑开始
        // 获取缓存中的num 数据
        Object value = redisTemplate.opsForValue().get("num");
        // 如果是空直接返回
        if (StringUtils.isEmpty(value)) {
            return;
        }
        // 不是空 如果说在这出现了异常! 那么delete 就删除失败! 也就是说锁永远存在!
        int num = Integer.parseInt(value + "");
        // 使num 每次+1 放入缓存
        redisTemplate.opsForValue().set("num", String.valueOf(++num));
        /*使用lua脚本来锁*/
        // 定义lua 脚本
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        // 使用redis执行lua执行
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
        redisScript.setScriptText(script);
        // 设置一下返回值类型 为Long
        // 因为删除判断的时候,返回的0,给其封装为数据类型。如果不封装那么默认返回String 类型,
        // 那么返回字符串与0 会有发生错误。
        redisScript.setResultType(Long.class);
        // 第一个要是script 脚本 ,第二个需要判断的key,第三个就是key所对应的值。
        redisTemplate.execute(redisScript, Arrays.asList(locKey), uuid);
    } else {
        // 其他线程等待
        try {
            // 睡眠
            Thread.sleep(1000);
            // 睡醒了之后,调用方法。
            testLockLua();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

2.13 Redis 6的新功能

2.13.1 ACL权限控制更细致

2.13.2 IO多线程

IO多线程其实指客户端交互部分的网络IO交互处理模块多线程,而非执行命令多线程。Redis6执行命令依然是单线程

2.13.3 工具支持Cluster

内嵌了集群工具Cluster

2.12.4 RESP3新的Redis通信协议:

优化服务端与客户端之间通信

2.13.5 Client side caching客户端缓存

基于RESP3协议实现的客户端缓存功能。为了进一步提升缓存的性能,将客户端经常访问的数据cache到客户端。减少TCP网络交互。

2.13.6 Proxy集群代理模式

Proy功能,让Cluster拥有像单实例一样的接入方式,降低大家使用cluster的门槛。不过需要注意的是代理不改变Cluster的功能限制,不支持的命令还是不会支持,比如跨slot的多Key操作。

2.13.7 Modules API

Redis6中模块API开发进展非常大,因为Redis Labs为了开发复杂的功能,从一开始就用上Redis模块。Redis可以变成一个框架,利用Modules来构建不同系统,而不需要从头开始写然后还要BSD许可。Redis开始就是一个向编写各种系统开放的平台。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值