Redis

Redis

1、NoSQL数据库简介

1.1、技术发展

技术的分类:

  1. 解决功能性的问题:Java、Jsp、RDBMS、Tomcat、HTML、Linux、JDBC、SVN

  2. 解决扩展性的问题:Struts、Spring、SpringMVC、Hibernate、Mybatis

  3. 解决性能的问题:NoSQL、Java线程、Hadoop、Nginx、MQ、ElasticSearch

NoSQL的优点:

  1. 直接存储在内存中,减少CPU和存储的压力

  2. 能直接作为缓存使用,减少IO操作次数

1.2、NoSQL数据库

1.2.1、NoSQL数据库的特点

非关系型数据库,以简单的 key-value 模式存储

不遵循SQL标准,不支持ACID,性能远超SQL

1.2.2、NoSQL数据库的适用场景

高并发、海量数据、高可扩展性

NoSQL不适用的场景:需要事务支持的情况,或需要处理复杂关系的情况

(不需要用sql或用sql处理不了的情况,都可以考虑用nosql)

1.2.3、常见的NoSQL数据库

MemCache:不持久化,支持类型单一,一般作为sql数据库的辅助

Redis:覆盖了MemCache的绝大部分功能,支持持久化,支持多种数据结构存储

MongoDB:文档型数据库,类似json

2、Redis6概述和安装

Redis是一个开源的key-value存储系统。

和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set --有序集合)和hash(哈希类型)。

这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。

在此基础上,Redis支持各种不同方式的排序。

与memcached一样,为了保证效率,数据都是缓存在内存中。

区别的是Redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件。

并且在此基础上实现了master-slave(主从)同步。

2.1、应用场景

  1. 配合关系型数据库做高速缓存

    1. 处理高频次,热门的访问数据,从而降低数据库IO

    2. 分布式架构,做session共享

  2. 多样的数据结构存储持久化数据

    image-20220624160411063

2.2、安装Redis

Redis在linux系统下使用,需要先安装gcc

2.2.1、使用Docker配置

# 获取镜像
docker pull redis:latest

# 运行容器(将容器命名为docker-redis,公开6379端口,开启AOF)
docker run -d -p 6379:6379 --name docker-redis redis --appendonly yes

# 在运行中的容器中用 -it 选项来启动一个新的交互式会话
docker exec -it docker-redis /bin/bash

# 运行redis-cli(使redis在后台运行)
redis-cli

redis默认是没有配置的,需要手动添加配置 redis.conf (官网有提供下载)

http://www.zzvips.com/article/175222.html

redis.conf 的主要配置如下:

bind 127.0.0.1        #注释掉这部分,使redis可以外部访问
daemonize no #用守护线程的方式启动
requirepass xxx #给redis设置密码
appendonly yes #redis持久化,默认是no
tcp-keepalive 300 #防止出现远程主机强迫关闭了一个现有的连接的错误 默认是300

2.2.2、查看安装目录

redis-benchmark:性能测试工具

redis-check-aof:修复有问题的AOF文件,rdb和aof后面讲

redis-check-dump:修复有问题的dump.rdb文件

redis-sentinel:Redis集群使用

redis-server:Redis服务器启动命令

redis-cli:客户端,操作入口

2.2.3、前台启动

redis-server

2.2.4、后台启动(推荐)

首先备份配置文件 redis.conf ,然后将 redis.conf 中的 daemonize no 改为 daemonize yes 允许后台启动(在docker容器中运行redis可以省略这一步)

# 启动redis
redis-server /myredis/redis.conf

# 使用客户端访问
redis-cli

# 测试(返回PONG说明连接正常)
ping

# 退出客户端
quit

2.2.5、关闭redis

单实例关闭:redis-cli shutdown

多实例关闭,指定端口关闭:redis-cli -p 6379 shutdown

2.3、Redis相关知识介绍

2.3.1、6379端口

默认有16个数据库,类似数组下标从0开始,初始默认使用0号库

使用命令 select <dbid> 来切换数据库。如: select 8

统一密码管理,所有库同样密码。

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

flushdb清空当前库

flushall通杀全部库

2.3.2、单线程+多路IO复用技术

多路复用是指使用一个线程来检查多个文件描述符(Socket)的就绪状态,比如调用select和poll函数,传入多个文件描述符,如果有一个文件描述符就绪,则返回,否则阻塞直到超时。得到就绪状态后进行真正的操作可以在同一个线程里执行,也可以启动线程执行(比如使用线程池)

Memcache使用的是多线程+锁机制

3、常用五大数据类型

http://www.redis.cn/commands.html

3.0、数据库操作

# 切换数据库
select 1

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

# 清空当前数据库
flushdb

# 清空所有数据库
flushall

 

3.1、键 key

# keys 查询
# 查询当前库中所有的key
keys *

# 判断某个key是否存在,存在返回1,不存在返回0
exists key1

# 查看键的类型
type key1

# 删除指定的键对应的键值对
del key1

# 非阻塞删除(仅将keys从keyspace元数据中删除,真正的删除在后续异步操作中)
unlink key1

# 设置key的过期时间(单位是秒)
expire key1 10

# 查看key还有多久过期(-1表示永不过期,-2表示已过期)
ttl key1

 

3.2、字符串 String

3.2.1、简介

String类型是二进制安全的。意味着Redis的string可以包含任何数据。比如jpg图片或者序列化的对象。

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

3.2.2、常用命令

# set 设置键值对
# set <key> <value> [EX seconds|PX milliseconds|KEEPTTL] [NX|XX]
# 如果key不存在,则向数据库中添加key-value键值对
# 如果key存在,则重新设置其value
set key1 value1

# setnx 只有key不存在时才设置key的值
# setnx <key> <value>
setnx key2 value2

# setex 设置值的同时设置过期时间
# setex <key><过期时间><value>

# getset 获取旧值的同时设置新值
# getset <key><value>

参数说明:

*NX:当数据库中key不存在时,可以将key-value添加数据库

*XX:当数据库中key存在时,可以将key-value添加数据库,与NX参数互斥

*EX:key的超时秒数

*PX:key的超时毫秒数,与EX互斥

# get 获取value
# get <key>
get key1

# append 将value1添加到value的末尾
# append <key> <value>
append key1 value1

# strlen 获取值的长度
# strlen <key>
strlen key1

# incr 使数字值+1(如果值为空则设置为1)
incr k3

# decr 使数字值-1(如果值为空则设置为-1)
decr k3

# incrby / decrby 使数字值+n
incrby k3 100
decrby k3 50

Redis是单线程的,所有操作都具有原子性,所以以下命令msetnx中,只要有一个失败,这条命令就会失败

# mset 同时设置多个key-value
# mset <key1><value1> <key2><value2>
mset k4 1 k5 2

# mget 同时获取多个key的value
# mget <key1><key2>
mget k4 k5

# msetnx 同时设置多个新的key-value(如果key存在则失败)
# msetnx <key1> <value1> <key2> <value2>
msetnx k6 6 k7 7

 

# getrange 获取一定范围内的值(类似substring)
# getrange <key><起始位置><结束位置>

# setrange 设置一定范围内的值
# setrange <key><起始位置><value>

3.2.3、数据结构

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

image-20220624171100729

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

3.3、列表 list

3.3.1、简介

单键多值

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

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

3.3.2、常用命令

# lpush/rpush 从左边/右边插入多个值
# lpush <key> <value1> <value2>
lpush k1 v1 v2 v3

# lpop/rpop 从左边/右边弹出一个值(当键中所有值被弹出后,键被删除)
lpop k1

# rpoplpush 从list1右边弹出一个值,添加到list2左边
# rpoplpush <key1> <key2>
rpoplpush k1 k2

# lrange 按照下标获取元素(左边第一个为0,右边第一个为-1)
# lrange <key> <start> <stop>
lrange k1 0 -1

# lindex 根据下标获取元素
# lindex <key> <index>
lindex k1 2

# llen 获取列表长度
# llen <key>
llen k1

# linsert 向列表中插入值
# linsert <key> before <value> <newvalue> (在value后面插入new value)
linsert k1 before 2 4

# lrem 从左边删除
# lrem <key> <n> <value> 从左边开始删除n个值为value的元素
lrem k1 2 2

# lset 替换列表中指定下标的值
# lset <key> <index> <value>
lset k1 2 6

3.3.3、数据结构

List的数据结构为快速链表quickList。

在列表元素较少的情况下会使用一块连续的内存存储,这个结构是ziplist,也即是压缩列表。它将所有的元素紧挨着一起存储,分配的是一块连续的内存。

当数据量比较多的时候才会改成quicklist,将多个ziplist组成一个链。因为普通的链表需要的附加指针空间太大,会比较浪费空间。比如这个列表里存的只是int类型的数据,结构上还需要两个额外的指针prev和next。

image-20220624220629103

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

3.4、集合 set

3.4.1、简介

Redis set对外提供的功能与list类似是一个列表的功能,特殊之处在于set是可以自动排重的,当你需要存储一个列表数据,又不希望出现重复数据时,set是一个很好的选择,并且set提供了判断某个成员是否在一个set集合内的重要接口。

Redis的Set是string类型的无序集合。它底层其实是一个value为null的hash表,所以添加,删除,查找的复杂度都是O(1)

3.4.2、常用命令

# sadd 添加元素到集合set中(已存在的元素会被自动忽略)
# sadd <key> <value1> <value2>
sadd s1 1 2 3 4 1

# smembers 取出集合所有的值
# smembers <key>
smembers s1

# sismember 判断集合中是否有value值(有返回1,无返回0)
# sismember <key> <value>
sismember s1 2

# scard 返回集合的元素个数
# scard <key>
scard s1

# srem 删除集合中的某个元素
# srem <key> <value1> <value2>
srem s1 1 2

# spop 随机弹出集合中的一个值
# spop <key>
spop s1

# srandmember 随机取出集合中的n个值,但不删除
# srandmember <key> <n>
srandmember s1 2

# smove 将集合中的一个值value移动到另一个集合(如果目标集合不存在则自动创建)
# smove <source> <destination> <value>
smove s1 s2 1

# sinter 返回两个集合的交集元素
# sinter <key1> <key2>
sinter s1 s2

# sunion 返回两个集合的并集元素
# sunion <key1> <key2>
sunion s1 s2

# sdiff 返回两个集合的差集元素(key1中存在,key2中不存在的元素)
# sdiff <key1> <key2>
sdiff s1 s2

3.4.3、数据结构

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

Java中HashSet的内部实现使用的是HashMap,只不过所有的value都指向同一个对象。Redis的set结构也是一样,它的内部也使用hash结构,所有的value都指向同一个内部值。

3.5、哈希 Hash

3.5.1、简介

Redis hash 是一个键值对集合。

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

例如:用户ID为查找的key,存储的value用户对象包含姓名,年龄,生日等信息

普通存储方式:

截屏2022-06-24 22.51.39

Redis hash 的存储方式:

截屏2022-06-24 22.53.02

3.5.2、常用命令

# hset 对集合中的field赋值value
# hset <key> <field> <value>
hset user1 age 22

# hget 从集合的field取出value
# hget <key> <field>
hget user1 age

# hmset 批量设置hash的值field和value
# hmset <key> <field1> <value1> <field2> <value2>
hmset user1 name zhangsan age 22

# hexists 查看哈希表的key中是否存在特定的field
# hexists <key> <field>
hexists user1 age

# hkeys 列出该hash集合key所有的field
# hkeys <key>
hkeys user1

# hvals 列出该hash集合key所有的value
# hvals <key>
hvals user1

# hincrby 为hash集合中某个key的某个field加上增量
# hincrby <key> <field> <increment>
hincrby user1 age 1

# hsetnx 仅当field不存在时,将hash集合中field的值设置为value
# hsetnx <key> <field> <value>
hsetnx user1 birth 19980101

3.5.3、数据结构

Hash类型对应的数据结构是两种:ziplist(压缩列表),hashtable(哈希表)。当field-value长度较短且个数较少时,使用ziplist,否则使用hashtable。

3.6、有序集合 Zset

3.6.1、简介

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

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

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

访问有序集合的中间元素也是非常快的,因此你能够使用有序集合作为一个没有重复成员的智能列表。

3.6.2、常用命令

# zadd 将一个或多个key-value及其score加入到有序集合中
# zadd <key><score1><value1> <score2><value2>
zadd k1 1 1 2 2

# zrange 返回有序集合中下标在start和stop之间的值(加上withscore可以同时返回分数和值)
# zrange <key> <start> <stop> [withscores]
zrange k1 0 -1
zrange k1 0 -1 withscore

# zrangebyscore 返回有序集合中score在min和max之间的值(从小到大排列)
# zrevrangebyscore 返回有序集合中score在min和max之间的值(从大到小排列)
# zrangebyscore <key> <min> <max> [withscore]
zrangebyscore k1 1 3

# zincrby 为值对应的score加上增量
# zincrby <key> <increment> <value>
zincrby k1 2 1

# zrem 删除集合中的指定值
# zrem <key> <value>
zrem k1 1

# zcount 统计集合中分数在min和max区间的值的个数
# zcount <key> <min> <max>
zcount k1 0 1

# zrank 返回某个值在集合中的排名(从0开始)
# zrank <key> <value>
zrank k1 2

zset的一个应用案例:文章访问量排行榜

3.6.3、数据结构

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

zset底层使用了两个数据结构: (1)hash,hash的作用就是关联元素value和权重score,保障元素value的唯一性,可以通过元素value找到相应的score值。 (2)跳跃表,跳跃表的目的在于给元素value排序,根据score的范围获取元素列表。

3.6.4、跳跃表

平衡树或红黑树虽然效率高但结构复杂;链表查询需要遍历所有效率低。Redis采用的是跳跃表。跳跃表效率堪比红黑树,实现远比红黑树简单。

以寻找51这个值为例:

有序链表(共比较6次)

image-20220626101446169

跳跃表(共比较4次)

image-20220626101502223

 

4、Redis6配置文件详解

直接安装的redis配置文件路径为/opt/redis-3.2.5/redis.conf

使用docker的方式安装的redis默认没有配置文件,因此,需要去redis的官方网站下载对应版本的redis,使用里面的配置文件即可

4.1、Units

配置大小单位,只支持bytes不支持bit

对大小写不敏感

# Redis configuration file example.
#
# Note that in order to read the configuration file, Redis must be
# started with the file path as first argument:
#
# ./redis-server /path/to/redis.conf

# Note on units: when memory size is needed, it is possible to specify
# it in the usual form of 1k 5GB 4M and so forth:
#
# 1k => 1000 bytes
# 1kb => 1024 bytes
# 1m => 1000000 bytes
# 1mb => 1024*1024 bytes
# 1g => 1000000000 bytes
# 1gb => 1024*1024*1024 bytes
#
# units are case insensitive so 1GB 1Gb 1gB are all the same.

4.2、include

包含公用部分

多实例的情况可以把公用的配置文件提取出来

################################## INCLUDES ###################################

# Include one or more other config files here. This is useful if you
# have a standard template that goes to all Redis servers but also need
# to customize a few per-server settings. Include files can include
# other files, so use this wisely.
#
# Notice option "include" won't be rewritten by command "CONFIG REWRITE"
# from admin or Redis Sentinel. Since Redis always uses the last processed
# line as value of a configuration directive, you'd better put includes
# at the beginning of this file to avoid overwriting config change at runtime.
#
# If instead you are interested in using includes to override configuration
# options, it is better to use include as the last line.
#
# include /path/to/local.conf
# include /path/to/other.conf

4.3、modules

################################## MODULES #####################################

# Load modules at startup. If the server is not able to load modules
# it will abort. It is possible to use multiple loadmodule directives.
#
# loadmodule /path/to/my_module.so
# loadmodule /path/to/other_module.so

4.4、network

 ################################## NETWORK #####################################
 
 # By default, if no "bind" configuration directive is specified, Redis listens
 # for connections from all the network interfaces available on the server.
 # It is possible to listen to just one or multiple selected interfaces using
 # the "bind" configuration directive, followed by one or more IP addresses.
 #
 # Examples:
 #
 # bind 192.168.1.100 10.0.0.1
 # bind 127.0.0.1 ::1
 #
 # ~~~ WARNING ~~~ If the computer running Redis is directly exposed to the
 # internet, binding to all the interfaces is dangerous and will expose the
 # instance to everybody on the internet. So by default we uncomment the
 # following bind directive, that will force Redis to listen only into
 # the IPv4 loopback interface address (this means Redis will be able to
 # accept connections only from clients running into the same computer it
 # is running).
 #
 # IF YOU ARE SURE YOU WANT YOUR INSTANCE TO LISTEN TO ALL THE INTERFACES
 # JUST COMMENT THE FOLLOWING LINE.
 # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 # =============================================
 # 默认情况bind=127.0.0.1表示只能接受本机的访问请求
 # 不说明bind则表示无限制接受任何ip地址的访问
 bind 127.0.0.1
 
 # Protected mode is a layer of security protection, in order to avoid that
 # Redis instances left open on the internet are accessed and exploited.
 #
 # When protected mode is on and if:
 #
 # 1) The server is not binding explicitly to a set of addresses using the
 #   "bind" directive.
 # 2) No password is configured.
 #
 # The server only accepts connections from clients connecting from the
 # IPv4 and IPv6 loopback addresses 127.0.0.1 and ::1, and from Unix domain
 # sockets.
 #
 # By default protected mode is enabled. You should disable it only if
 # you are sure you want clients from other hosts to connect to Redis
 # even if no authentication is configured, nor a specific set of interfaces
 # are explicitly listed using the "bind" directive.
 # =============================================
 # 保护模式,如果开启了protected-mode,那么在没有设定bind ip且没有设密码的情况下,Redis只允许接受本机的响应
 protected-mode yes
 
 # Accept connections on the specified port, default is 6379 (IANA #815344).
 # If port 0 is specified Redis will not listen on a TCP socket.
 # =============================================
 # 端口号,默认6379不需要改变
 port 6379
 
 # TCP listen() backlog.
 #
 # In high requests-per-second environments you need an high backlog in order
 # to avoid slow clients connections issues. Note that the Linux kernel
 # will silently truncate it to the value of /proc/sys/net/core/somaxconn so
 # make sure to raise both the value of somaxconn and tcp_max_syn_backlog
 # in order to get the desired effect.
 # =============================================
 # 设置tcp的backlog,backlog其实是一个连接队列,backlog队列总和=未完成三次握手队列 + 已经完成三次握手队列。
 # 在高并发环境下需要一个高backlog值来避免慢客户端连接问题。
 # 注意Linux内核会将这个值减小到/proc/sys/net/core/somaxconn的值(128),所以需要确认增大/proc/sys/net/core/somaxconn和/proc/sys/net/ipv4/tcp_max_syn_backlog(128)两个值来达到想要的效果
 tcp-backlog 511
 
 # Unix socket.
 #
 # Specify the path for the Unix socket that will be used to listen for
 # incoming connections. There is no default, so Redis will not listen
 # on a unix socket when not specified.
 #
 # unixsocket /tmp/redis.sock
 # unixsocketperm 700
 
 # Close the connection after a client is idle for N seconds (0 to disable)
 # =============================================
 # 一个空闲的客户端维持多少秒会关闭,0表示关闭该功能。即永不关闭。
 timeout 0
 
 # TCP keepalive.
 #
 # If non-zero, use SO_KEEPALIVE to send TCP ACKs to clients in absence
 # of communication. This is useful for two reasons:
 #
 # 1) Detect dead peers.
 # 2) Take the connection alive from the point of view of network
 #   equipment in the middle.
 #
 # On Linux, the specified value (in seconds) is the period used to send ACKs.
 # Note that to close the connection the double of the time is needed.
 # On other kernels the period depends on the kernel configuration.
 #
 # A reasonable value for this option is 300 seconds, which is the new
 # Redis default starting with Redis 3.2.1.
 # =============================================
 # 对访问客户端的一种心跳检测,每个n秒检测一次。单位为秒,如果设置为0,则不会进行Keepalive检测,建议设置成60
 tcp-keepalive 300

4.5、TLS/SSL

 

4.6、general

 # By default Redis does not run as a daemon. Use 'yes' if you need it.
 # Note that Redis will write a pid file in /var/run/redis.pid when daemonized.
 # =============================================
 # 是否为后台进程,应更改为yes
 daemonize no
 
 # If you run Redis from upstart or systemd, Redis can interact with your
 # supervision tree. Options:
 #   supervised no     - no supervision interaction
 #   supervised upstart - signal upstart by putting Redis into SIGSTOP mode
 #   supervised systemd - signal systemd by writing READY=1 to $NOTIFY_SOCKET
 #   supervised auto   - detect upstart or systemd method based on
 #                       UPSTART_JOB or NOTIFY_SOCKET environment variables
 # Note: these supervision methods only signal "process is ready."
 #       They do not enable continuous liveness pings back to your supervisor.
 supervised no
 
 # If a pid file is specified, Redis writes it where specified at startup
 # and removes it at exit.
 #
 # When the server runs non daemonized, no pid file is created if none is
 # specified in the configuration. When the server is daemonized, the pid file
 # is used even if not specified, defaulting to "/var/run/redis.pid".
 #
 # Creating a pid file is best effort: if Redis is not able to create it
 # nothing bad happens, the server will start and run normally.
 # =============================================
 # 存放pid文件的位置,每个实例会产生一个不同的pid文件
 pidfile /var/run/redis_6379.pid
 
 # Specify the server verbosity level.
 # This can be one of:
 # debug (a lot of information, useful for development/testing)
 # verbose (many rarely useful info, but not a mess like the debug level)
 # notice (moderately verbose, what you want in production probably)
 # warning (only very important / critical messages are logged)
 # =============================================
 # 指定日志记录级别,Redis总共支持四个级别:debug、verbose、notice、warning,默认为notice,四个级别根据使用阶段来选择,生产环境选择notice 或者warning
 loglevel notice
 
 # Specify the log file name. Also the empty string can be used to force
 # Redis to log on the standard output. Note that if you use standard
 # output for logging but daemonize, logs will be sent to /dev/null
 # =============================================
 # 日志文件路径和名称
 logfile ""
 
 # To enable logging to the system logger, just set 'syslog-enabled' to yes,
 # and optionally update the other syslog parameters to suit your needs.
 # syslog-enabled no
 
 # Specify the syslog identity.
 # syslog-ident redis
 
 # Specify the syslog facility. Must be USER or between LOCAL0-LOCAL7.
 # syslog-facility local0
 
 # Set the number of databases. The default database is DB 0, you can select
 # a different one on a per-connection basis using SELECT <dbid> where
 # dbid is a number between 0 and 'databases'-1
 # =============================================
 # 设定库的数量,默认16,默认数据库为0,可以使用SELECT <dbid>命令在连接上指定数据库id
 databases 16
 
 # By default Redis shows an ASCII art logo only when started to log to the
 # standard output and if the standard output is a TTY. Basically this means
 # that normally a logo is displayed only in interactive sessions.
 #
 # However it is possible to force the pre-4.0 behavior and always show a
 # ASCII art logo in startup logs by setting the following option to yes.
 always-show-logo yes

 

4.7、SNAPSHOTTING

 

4.8、REPLICATION

 

4.9、KEYS TRACKING

 

4.10、SECURITY

 # Warning: since Redis is pretty fast an outside user can try up to
 # 1 million passwords per second against a modern box. This means that you
 # should use very strong passwords, otherwise they will be very easy to break.
 # Note that because the password is really a shared secret between the client
 # and the server, and should not be memorized by any human, the password
 # can be easily a long string from /dev/urandom or whatever, so by using a
 # long and unguessable password no brute force attack will be possible.
 
 # Redis ACL users are defined in the following format:
 #
 #   user <username> ... acl rules ...
 #
 # For example:
 #
 #   user worker +@list +@connection ~jobs:* on >ffa9203c493aa99
 #
 # The special username "default" is used for new connections. If this user
 # has the "nopass" rule, then new connections will be immediately authenticated
 # as the "default" user without the need of any password provided via the
 # AUTH command. Otherwise if the "default" user is not flagged with "nopass"
 # the connections will start in not authenticated state, and will require
 # AUTH (or the HELLO command AUTH option) in order to be authenticated and
 # start to work.
 #
 # The ACL rules that describe what an user can do are the following:
 #
 # on           Enable the user: it is possible to authenticate as this user.
 # off         Disable the user: it's no longer possible to authenticate
 #               with this user, however the already authenticated connections
 #               will still work.
 # +<command>   Allow the execution of that command
 # -<command>   Disallow the execution of that command
 # +@<category> Allow the execution of all the commands in such category
 #               with valid categories are like @admin, @set, @sortedset, ...
 #               and so forth, see the full list in the server.c file where
 #               the Redis command table is described and defined.
 #               The special category @all means all the commands, but currently
 #               present in the server, and that will be loaded in the future
 #               via modules.
 # +<command>|subcommand   Allow a specific subcommand of an otherwise
 #                           disabled command. Note that this form is not
 #                           allowed as negative like -DEBUG|SEGFAULT, but
 #                           only additive starting with "+".
 # allcommands Alias for +@all. Note that it implies the ability to execute
 #               all the future commands loaded via the modules system.
 # nocommands   Alias for -@all.
 # ~<pattern>   Add a pattern of keys that can be mentioned as part of
 #               commands. For instance ~* allows all the keys. The pattern
 #               is a glob-style pattern like the one of KEYS.
 #               It is possible to specify multiple patterns.
 # allkeys     Alias for ~*
 # resetkeys   Flush the list of allowed keys patterns.
 # ><password> Add this passowrd to the list of valid password for the user.
 #               For example >mypass will add "mypass" to the list.
 #               This directive clears the "nopass" flag (see later).
 # <<password> Remove this password from the list of valid passwords.
 # nopass       All the set passwords of the user are removed, and the user
 #               is flagged as requiring no password: it means that every
 #               password will work against this user. If this directive is
 #               used for the default user, every new connection will be
 #               immediately authenticated with the default user without
 #               any explicit AUTH command required. Note that the "resetpass"
 #               directive will clear this condition.
 # resetpass   Flush the list of allowed passwords. Moreover removes the
 #               "nopass" status. After "resetpass" the user has no associated
 #               passwords and there is no way to authenticate without adding
 #               some password (or setting it as "nopass" later).
 # reset       Performs the following actions: resetpass, resetkeys, off,
 #               -@all. The user returns to the same state it has immediately
 #               after its creation.
 #
 # ACL rules can be specified in any order: for instance you can start with
 # passwords, then flags, or key patterns. However note that the additive
 # and subtractive rules will CHANGE MEANING depending on the ordering.
 # For instance see the following example:
 #
 #   user alice on +@all -DEBUG ~* >somepassword
 #
 # This will allow "alice" to use all the commands with the exception of the
 # DEBUG command, since +@all added all the commands to the set of the commands
 # alice can use, and later DEBUG was removed. However if we invert the order
 # of two ACL rules the result will be different:
 #
 #   user alice on -DEBUG +@all ~* >somepassword
 #
 # Now DEBUG was removed when alice had yet no commands in the set of allowed
 # commands, later all the commands are added, so the user will be able to
 # execute everything.
 #
 # Basically ACL rules are processed left-to-right.
 #
 # For more information about ACL configuration please refer to
 # the Redis web site at https://redis.io/topics/acl
 
 # ACL LOG
 #
 # The ACL Log tracks failed commands and authentication events associated
 # with ACLs. The ACL Log is useful to troubleshoot failed commands blocked
 # by ACLs. The ACL Log is stored in memory. You can reclaim memory with
 # ACL LOG RESET. Define the maximum entry length of the ACL Log below.
 acllog-max-len 128
 
 # Using an external ACL file
 #
 # Instead of configuring users here in this file, it is possible to use
 # a stand-alone file just listing users. The two methods cannot be mixed:
 # if you configure users here and at the same time you activate the exteranl
 # ACL file, the server will refuse to start.
 #
 # The format of the external ACL user file is exactly the same as the
 # format that is used inside redis.conf to describe users.
 #
 # aclfile /etc/redis/users.acl
 
 # IMPORTANT NOTE: starting with Redis 6 "requirepass" is just a compatiblity
 # layer on top of the new ACL system. The option effect will be just setting
 # the password for the default user. Clients will still authenticate using
 # AUTH <password> as usually, or more explicitly with AUTH default <password>
 # if they follow the new protocol: both will work.
 #
 # requirepass foobared
 # =============================================
 # 打开以上注释可以设置永久的密码(在命令行中设置重启redis后会还原)
 
 # Command renaming (DEPRECATED).
 #
 # ------------------------------------------------------------------------
 # WARNING: avoid using this option if possible. Instead use ACLs to remove
 # commands from the default user, and put them only in some admin user you
 # create for administrative purposes.
 # ------------------------------------------------------------------------
 #
 # It is possible to change the name of dangerous commands in a shared
 # environment. For instance the CONFIG command may be renamed into something
 # hard to guess so that it will still be available for internal-use tools
 # but not available for general clients.
 #
 # Example:
 #
 # rename-command CONFIG b840fc02d524045429941cc15f59e41cb7be6c52
 #
 # It is also possible to completely kill a command by renaming it into
 # an empty string:
 #
 # rename-command CONFIG ""
 #
 # Please note that changing the name of commands that are logged into the
 # AOF file or transmitted to replicas may cause problems.

4.11、CLIENTS

# Set the max number of connected clients at the same time. By default
# this limit is set to 10000 clients, however if the Redis server is not
# able to configure the process file limit to allow for the specified limit
# the max number of allowed clients is set to the current file limit
# minus 32 (as Redis reserves a few file descriptors for internal uses).
#
# Once the limit is reached Redis will close all the new connections sending
# an error 'max number of clients reached'.
#
# IMPORTANT: When Redis Cluster is used, the max number of connections is also
# shared with the cluster bus: every node in the cluster will use two
# connections, one incoming and another outgoing. It is important to size the
# limit accordingly in case of very large clusters.
#
# maxclients 10000
# =============================================
# 设置redis同时可以与多少个客户端进行连接。
# 默认情况下为10000个客户端。
# 如果达到了此限制,redis则会拒绝新的连接请求,并且向这些连接请求方发出“max number of clients reached”以作回应。

4.12、MEMORY MANAGEMENT

# Set a memory usage limit to the specified amount of bytes.
# When the memory limit is reached Redis will try to remove keys
# according to the eviction policy selected (see maxmemory-policy).
#
# If Redis can't remove keys according to the policy, or if the policy is
# set to 'noeviction', Redis will start to reply with errors to commands
# that would use more memory, like SET, LPUSH, and so on, and will continue
# to reply to read-only commands like GET.
#
# This option is usually useful when using Redis as an LRU or LFU cache, or to
# set a hard memory limit for an instance (using the 'noeviction' policy).
#
# WARNING: If you have replicas attached to an instance with maxmemory on,
# the size of the output buffers needed to feed the replicas are subtracted
# from the used memory count, so that network problems / resyncs will
# not trigger a loop where keys are evicted, and in turn the output
# buffer of replicas is full with DELs of keys evicted triggering the deletion
# of more keys, and so forth until the database is completely emptied.
#
# In short... if you have replicas attached it is suggested that you set a lower
# limit for maxmemory so that there is some free RAM on the system for replica
# output buffers (but this is not needed if the policy is 'noeviction').
#
# maxmemory <bytes>
# =============================================
# 设置redis可以使用的内存量。
# 一旦到达内存使用上限,redis将会试图移除内部数据,移除规则可以通过maxmemory-policy来指定。
# 建议设置,否则,将内存占满会造成服务器宕机
# 如果redis无法根据移除规则来移除内存中的数据,或者设置了“不允许移除”,那么redis则会针对那些需要申请内存的指令返回错误信息,比如SET、LPUSH等。
# 但是对于无内存申请的指令,仍然会正常响应,比如GET等。如果你的redis是主redis(说明你的redis有从redis),那么在设置内存使用上限时,需要在系统中留出一些内存空间给同步队列缓存,只有在你设置的是“不移除”的情况下,才不用考虑这个因素。

# MAXMEMORY POLICY: how Redis will select what to remove when maxmemory
# is reached. You can select one from the following behaviors:
#
# volatile-lru -> Evict using approximated LRU, only keys with an expire set.
# allkeys-lru -> Evict any key using approximated LRU.
# volatile-lfu -> Evict using approximated LFU, only keys with an expire set.
# allkeys-lfu -> Evict any key using approximated LFU.
# volatile-random -> Remove a random key having an expire set.
# allkeys-random -> Remove a random key, any key.
# volatile-ttl -> Remove the key with the nearest expire time (minor TTL)
# noeviction -> Don't evict anything, just return an error on write operations.
#
# LRU means Least Recently Used
# LFU means Least Frequently Used
#
# Both LRU, LFU and volatile-ttl are implemented using approximated
# randomized algorithms.
#
# Note: with any of the above policies, Redis will return an error on write
# operations, when there are no suitable keys for eviction.
#
# At the date of writing these commands are: set setnx setex append
# incr decr rpush lpush rpushx lpushx linsert lset rpoplpush sadd
# sinter sinterstore sunion sunionstore sdiff sdiffstore zadd zincrby
# zunionstore zinterstore hset hsetnx hmset hincrby incrby decrby
# getset mset msetnx exec sort
#
# The default is:
#
# maxmemory-policy noeviction
# =============================================
# 设置移除规则
# volatile-lru:使用LRU算法移除key,只对设置了过期时间的键;(最近最少使用)
# allkeys-lru:在所有集合key中,使用LRU算法移除key
# volatile-random:在过期集合中移除随机的key,只对设置了过期时间的键
# allkeys-random:在所有集合key中,移除随机的key
# volatile-ttl:移除那些TTL值最小的key,即那些最近要过期的key
# noeviction:不进行移除。针对写操作,只是返回错误信息


# LRU, LFU and minimal TTL algorithms are not precise algorithms but approximated
# algorithms (in order to save memory), so you can tune it for speed or
# accuracy. For default Redis will check five keys and pick the one that was
# used less recently, you can change the sample size using the following
# configuration directive.
#
# The default of 5 produces good enough results. 10 Approximates very closely
# true LRU but costs more CPU. 3 is faster but not very accurate.
#
# maxmemory-samples 5
# =============================================
# 设置样本数量
# LRU算法和最小TTL算法都并非是精确的算法,而是估算值,所以你可以设置样本的大小,redis默认会检查这么多个key并选择其中LRU的那个。
# 一般设置3到7的数字,数值越小样本越不准确,但性能消耗越小。

# Starting from Redis 5, by default a replica will ignore its maxmemory setting
# (unless it is promoted to master after a failover or manually). It means
# that the eviction of keys will be just handled by the master, sending the
# DEL commands to the replica as keys evict in the master side.
#
# This behavior ensures that masters and replicas stay consistent, and is usually
# what you want, however if your replica is writable, or you want the replica
# to have a different memory setting, and you are sure all the writes performed
# to the replica are idempotent, then you may change this default (but be sure
# to understand what you are doing).
#
# Note that since the replica by default does not evict, it may end using more
# memory than the one set via maxmemory (there are certain buffers that may
# be larger on the replica, or data structures may sometimes take more memory
# and so forth). So make sure you monitor your replicas and make sure they
# have enough memory to never hit a real out-of-memory condition before the
# master hits the configured maxmemory setting.
#
# replica-ignore-maxmemory yes

# Redis reclaims expired keys in two ways: upon access when those keys are
# found to be expired, and also in background, in what is called the
# "active expire key". The key space is slowly and interactively scanned
# looking for expired keys to reclaim, so that it is possible to free memory
# of keys that are expired and will never be accessed again in a short time.
#
# The default effort of the expire cycle will try to avoid having more than
# ten percent of expired keys still in memory, and will try to avoid consuming
# more than 25% of total memory and to add latency to the system. However
# it is possible to increase the expire "effort" that is normally set to
# "1", to a greater value, up to the value "10". At its maximum value the
# system will use more CPU, longer cycles (and technically may introduce
# more latency), and will tollerate less already expired keys still present
# in the system. It's a tradeoff betweeen memory, CPU and latecy.
#
# active-expire-effort 1

 

5、Redis6的发布和订阅

5.1、什么是订阅和发布

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

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

5.2、发布和订阅

客户端可以订阅平到,当给这个频道发布消息后,消息会发送到订阅了这个频道的客户端

5.3、发布和订阅的命令行实现

# 打开一个客户端,订阅channel1
subscribe channel1

# 打开另一个客户端,给channel1发送消息hello(第一个客户端中可以看到消息hello)
publish channel1 hello

6、Redis6新数据类型

6.1、Bitmaps

6.1.1、简介

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

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

6.1.2、命令

# setbit 设置Bitmaps中某个偏移量的值(offset是偏移量的索引,从0开始,value只能为0或1,初始值为0)
# setbit <key> <offset> <value>
setbit k1 3 1

# getbit 获取Bitmaps中某个偏移量的值
# getbit <key> <offset>
getbit k1 3

# bitcount 统计被设置为1的偏移量的数量,start和end表示开始和结束的字节索引(1byte=8bit)
# bitcount <key> [start end]
bitcount k1
bitcount k1 1 3

# bitop 复合操作,可以做多个Bitmaps的交/并/非/异或,并将结果保存在destkey中
# bitop and/or/not/xor <destkey> <key1> <key2>
bitop and r1 k1 k2

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

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

注意setbit设置或清除的是bit位置,而bitcount计算的是byte位置。

6.1.3、Bitmaps和set的对比

当数据量特别大,譬如网站独立访问的活跃用户非常多时,使用Bitmaps更节省内存空间;

但如果活跃用户很少,set更节省空间,因为Bitmaps要存储大量的0

6.2、HyperLogLog

6.2.1、简介

统计网站PV(PageView页面访问量),可以使用Redis的incr、incrby轻松实现。但像UV(UniqueVisitor,独立访客)、独立IP数、搜索记录数等需要去重和计数的问题如何解决?这种求集合中不重复元素个数的问题称为基数问题。

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

(1)数据存储在MySQL表中,使用distinct count计算不重复个数

(2)使用Redis提供的hash、set、bitmaps等数据结构来处理

以上的方案结果精确,但随着数据不断增加,导致占用空间越来越大,对于非常大的数据集是不切实际的。

能否能够降低一定的精度来平衡存储空间?Redis推出了HyperLogLog

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

注意:HyperLogLog只会存储基数,而不会存储任何元素

6.2.2、命令

# pfadd 添加指定元素到HyperLogLog中
# pfadd <key> <element>
pfadd hll 1

# pfcount 计算近似基数,可以将多个key合并计算
# pfcount <key>
pfcount hll1
pfcount hll1 hll2

# pfmerge 将多个key合并,结果存储在另一个key中
# pfmerge <destkey> <sourcekey1> <sourcekey2>
pfmerge hll3 hll1 hll2

6.3、Geospatial

6.3.1、简介

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

6.3.2、命令

# geoadd 添加地理位置(分别设置经度纬度名称)
# geoadd <key> <longitude> <latitude> <member>
geoadd china:city 121.47 31.23 shanghai

# geopos 获取指定地区的坐标值
# geopos <key> <member>

# geodist 获取两个位置之间的直线距离(支持设置距离单位,默认为米)
# geodist <key> <member1> <member2> [m|km|ft|mi]

# georadius 以给定的经纬度为中心,找出某个半径内的元素
# georadius <key> <longitude> <latitude> radius [m|km|ft|mi]

两极无法直接添加,一般会下载城市数据,直接通过 Java 程序一次性导入。

有效的经度从 -180 度到 180 度。有效的纬度从 -85.05112878 度到 85.05112878 度。

当坐标位置超出指定范围时,该命令将会返回一个错误。

已经添加的数据无法再次添加。

7、Jedis操作Redis6

7.1、使用Jedis

7.1.1、导入jar包

<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.2.0</version>
</dependency>

7.1.2、连接准备

禁用Linux的防火墙:Linux(CentOS7)里执行命令 systemctl stop/disable firewalld.service

redis.conf 中注释掉 bind 127.0.0.1 ,然后设置 protected-mode no

7.1.3、测试连接

public class JedisDemo01 {

public static void main(String[] args) {
// 创建Jedis对象
Jedis jedis = new Jedis("106.15.77.8", 6379);
// 测试连接
String ping = jedis.ping();
System.out.println("ping = " + ping);
// 关闭连接
jedis.close();
}

}

7.1.4、使用各种数据类型

// jedis.xxx

7.2、案例

要求:

1、输入手机号,点击发送后随机生成6位数字码,2分钟有效

2、输入验证码,点击验证,返回成功或失败

3、每个手机号每天只能输入3次

截屏2022-06-26 17.40.03

8、Redis6与SpringBoot整合

8.1、整合步骤

  1. 在pom.xml中引入依赖

<!-- redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<!-- spring2.X集成redis所需common-pool2-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.6.0</version>
</dependency>
  1. application.properties/yml配置redis配置

#Redis服务器地址
spring.redis.host=192.168.140.136
#Redis服务器连接端口
spring.redis.port=6379
#Redis数据库索引(默认为0)
spring.redis.database= 0
#连接超时时间(毫秒)
spring.redis.timeout=1800000
#连接池最大连接数(使用负值表示没有限制)
spring.redis.lettuce.pool.max-active=20
#最大阻塞等待时间(负数表示没限制)
spring.redis.lettuce.pool.max-wait=-1
#连接池中的最大空闲连接
spring.redis.lettuce.pool.max-idle=5
#连接池中的最小空闲连接
spring.redis.lettuce.pool.min-idle=0
  1. 添加redis配置类

@EnableCaching
@Configuration
public class RedisConfig extends CachingConfigurerSupport {

@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
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);
template.setConnectionFactory(factory);
//key序列化方式
template.setKeySerializer(redisSerializer);
//value序列化
template.setValueSerializer(jackson2JsonRedisSerializer);
//value hashmap序列化
template.setHashValueSerializer(jackson2JsonRedisSerializer);
return template;
}

@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
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);
// 配置序列化(解决乱码的问题),过期时间600秒
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(600))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
.disableCachingNullValues();
RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
return cacheManager;
}
}
  1. 测试

@RestController
@RequestMapping("/redisTest")
public class RedisTestController {
@Autowired
private RedisTemplate redisTemplate;

@GetMapping
public String testRedis() {

//设置值到redis
redisTemplate.opsForValue().set("name","lucy");
//从redis获取值
String name = (String)redisTemplate.opsForValue().get("name");

return name;
}
}

9、Redis6的事务操作

9.1、事务定义

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

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

9.2、Multi、Exec、discard

从输入multi命令开始,输入的命令都会依次进入命令队列中,但不会执行,直到输入exec后,Redis会将之前的命令队列中的命令依次执行。

组队的过程中可以通过discard来放弃组队。

image-20220711143634499

multi
...
...
exec 或 discard

9.3、Redis事务的特点

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

  • 没有隔离级别的概念:redis的事务只是把命令放到了队列里,并没有执行,而是等到最后一起执行,从而事务间就不会导致数据脏读、不可重复读、幻读的问题

  • 不保证原子性:执行事务时,如果有命令执行失败,事务会继续执行,不会执行回滚操作,不会影响后续的操作

9.4、事务的错误处理

组队中某个命令出现了报告错误,执行时整个的所有队列都会被取消。

image-20220711144510313

如果执行阶段某个命令报出了错误,则只有报错的命令不会被执行,而其他的命令都会执行,不会回滚。

image-20220711144527159

9.5、悲观锁、乐观锁

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

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

9.6、Redis中乐观锁的实现

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

watch balance1 balance2

unwatch 取消 WATCH 命令对所有 key 的监视。

如果在执行 WATCH 命令之后,EXEC 命令或 DISCARD 命令先被执行了的话,那么就不需要再执行 UNWATCH 了。

9.7、秒杀案例

完整代码详见项目

9.7.1、设置乐观锁解决超卖问题

乐观锁可能会导致很多请求都失败。先点的没秒到,后点的可能秒到了。

    //监视库存
jedis.watch(kcKey);

//......

//7 秒杀过程
//使用事务
Transaction multi = jedis.multi();
//组队操作
multi.decr(kcKey);
multi.sadd(userKey,uid);
//执行
List<Object> results = multi.exec();
if(results == null || results.size()==0) {
System.out.println("秒杀失败了....");
jedis.close();
return false;
}

 

9.7.2、使用连接池解决超时问题

public class JedisPoolUtil {
private static volatile JedisPool jedisPool = null;

private JedisPoolUtil() {
}

public static JedisPool getJedisPoolInstance() {
if (null == jedisPool) {
synchronized (JedisPoolUtil.class) {
if (null == jedisPool) {
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxTotal(200);
poolConfig.setMaxIdle(32);
poolConfig.setMaxWaitMillis(100*1000);
poolConfig.setBlockWhenExhausted(true);
poolConfig.setTestOnBorrow(true); // ping PONG

jedisPool = new JedisPool(poolConfig, "192.168.44.168", 6379, 60000 );
}
}
}
return jedisPool;
}

public static void release(JedisPool jedisPool, Jedis jedis) {
if (null != jedis) {
jedisPool.returnResource(jedis);
}
}

}

链接池参数:

  • MaxTotal:控制一个pool可分配多少个jedis实例,通过pool.getResource()来获取;如果赋值为-1,则表示不限制;如果pool已经分配了MaxTotal个jedis实例,则此时pool的状态为exhausted。

  • maxIdle:控制一个pool最多有多少个状态为idle(空闲)的jedis实例;

  • MaxWaitMillis:表示当borrow一个jedis实例时,最大的等待毫秒数,如果超过等待时间,则直接抛JedisConnectionException;

  • testOnBorrow:获得一个jedis实例的时候是否检查连接可用性(ping());如果为true,则得到的jedis实例均是可用的;

9.7.3、库存遗留问题

乐观锁修改版本号导致大量的请求失败(例如2000个请求秒杀500个商品,最后商品大概率会有剩余)

使用LUA脚本语言可以解决

LUA:

将复杂的或者多步的redis操作,写为一个脚本,一次提交给redis执行,减少反复连接redis的次数。提升性能。

LUA脚本是类似redis事务,有一定的原子性,不会被其他命令插队,可以完成一些redis事务性的操作。

但是注意redis的lua脚本功能,只有在Redis 2.6以上的版本才可以使用。

利用lua脚本淘汰用户,解决超卖问题。

redis 2.6版本以后,通过lua脚本解决争抢问题,实际上是redis 利用其单线程的特性,用任务队列的方式解决多任务并发问题。

local userid=KEYS[1]; 
local prodid=KEYS[2];
local qtkey="sk:"..prodid..":qt";
local usersKey="sk:"..prodid.":usr';
local userExists=redis.call("sismember",usersKey,userid);
if tonumber(userExists)==1 then
return 2; --当前用户已经秒杀过--
end
local num= redis.call("get" ,qtkey);
if tonumber(num)<=0 then
return 0; --秒杀结束--
else
redis.call("decr",qtkey);
redis.call("sadd",usersKey,userid);
end
return 1; --秒杀成功--

 

public class SecKill_redisByScript {

private static final org.slf4j.Logger logger =LoggerFactory.getLogger(SecKill_redisByScript.class) ;

public static void main(String[] args) {
JedisPool jedispool = JedisPoolUtil.getJedisPoolInstance();

Jedis jedis=jedispool.getResource();
System.out.println(jedis.ping());

Set<HostAndPort> set=new HashSet<HostAndPort>();

// doSecKill("201","sk:0101");
}

static String secKillScript ="local userid=KEYS[1];\r\n" +
"local prodid=KEYS[2];\r\n" +
"local qtkey='sk:'..prodid..\":qt\";\r\n" +
"local usersKey='sk:'..prodid..\":usr\";\r\n" +
"local userExists=redis.call(\"sismember\",usersKey,userid);\r\n" +
"if tonumber(userExists)==1 then \r\n" +
" return 2;\r\n" +
"end\r\n" +
"local num= redis.call(\"get\" ,qtkey);\r\n" +
"if tonumber(num)<=0 then \r\n" +
" return 0;\r\n" +
"else \r\n" +
" redis.call(\"decr\",qtkey);\r\n" +
" redis.call(\"sadd\",usersKey,userid);\r\n" +
"end\r\n" +
"return 1" ;

static String secKillScript2 =
"local userExists=redis.call(\"sismember\",\"{sk}:0101:usr\",userid);\r\n" +
" return 1";

public static boolean doSecKill(String uid,String prodid) throws IOException {

JedisPool jedispool = JedisPoolUtil.getJedisPoolInstance();
Jedis jedis=jedispool.getResource();

//String sha1= .secKillScript;
String sha1= jedis.scriptLoad(secKillScript);
Object result= jedis.evalsha(sha1, 2, uid,prodid);

String reString=String.valueOf(result);
if ("0".equals( reString ) ) {
System.err.println("已抢空!!");
}else if("1".equals( reString ) ) {
System.out.println("抢购成功!!!!");
}else if("2".equals( reString ) ) {
System.err.println("该用户已抢过!!");
}else{
System.err.println("抢购异常!!");
}
jedis.close();
return true;
}
}

 

10、Redis6持久化——RDB

10.1、RDB是什么

Redis DataBase

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

10.2、备份如何执行

Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件dump.rdb。 (使用临时文件保证了数据的安全,避免数据不完整)

整个过程中,主进程是不进行任何IO操作的,这就确保了极高的性能。如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效。RDB的缺点是最后一次持久化后的数据可能丢失。

image-20220711191619299

10.3、Fork

Fork的作用是复制一个与当前进程一样的进程。新进程的所有数据(变量、环境变量、程序计数器等) 数值都和原进程一致,但是是一个全新的进程,并作为原进程的子进程

在Linux程序中,fork()会产生一个和父进程完全相同的子进程,但子进程在此后多会exec系统调用,出于效率考虑,Linux中引入了“写时复制技术”

一般情况父进程和子进程会共用同一段物理内存,只有进程空间的各段的内容要发生变化时,才会将父进程的内容复制一份给子进程。

10.4、RDB相关配置

在redis.conf中可以配置RDB

############### redis.conf ###################
# 配置文件名称,默认为dump.rdb
dbfilename dump.rdb

# 配置rdb文件的保存路径,默认在redis启动时命令行所在的目录下
dir ./

# 默认的快照配置
# xx秒内有xx个key发生变化则进行持久化
# 禁用:不设置save指令,或者给save传入空字符串
#save 3600 1
#save 30 10
#save 60 10000

# 设置当Redis无法写入磁盘的话,是否直接关掉Redis的写操作。推荐设为yes
stop-writes-on-bgsave-error

# 压缩存储
# 对于存储到磁盘中的快照,可以设置是否进行压缩存储。如果是的话,redis会采用LZF算法进行压缩。
# 如果你不想消耗CPU来进行压缩的话,可以设置为关闭此功能。推荐yes
rdbcompression yes

# 检查完整性
# 在存储快照后,还可以让redis使用CRC64算法来进行数据校验,
# 但是这样做会增加大约10%的性能消耗,如果希望获取到最大的性能提升,可以关闭此功能
# 推荐yes
rdbchecksum yes

10.5、RDB相关命令

# 只管保存,其它不管,全部阻塞。手动保存。不建议
save

# Redis会在后台异步进行快照操作, 快照同时还可以响应客户端请求。推荐
bgsave

# 获取最后一次成功执行快照的时间
lastsave

# 执行flushall命令,也会产生dump.rdb文件,但里面是空的,无意义
flushall

# 动态停止RDB,禁用保存策略
redis-cli config set save ""#save

10.6、RDB的优缺点

优势:

  • 适合大规模的数据恢复

  • 对数据完整性和一致性要求不高更适合使用

  • 节省磁盘空间

  • 恢复速度快

缺点:

  • Fork的时候,内存中的数据被克隆了一份,大致2倍的膨胀性需要考虑

  • 虽然Redis在fork时使用了写时拷贝技术,但是如果数据庞大时还是比较消耗性能。

  • 在备份周期在一定间隔时间做一次备份,所以如果Redis意外down掉的话,就会丢失最后一次快照后的所有修改。

image-20220711195207655

10.7、RDB的备份和恢复

先通过config get dir 查询rdb文件的目录

将*.rdb的文件拷贝到别的地方

rdb的恢复:

  1. 关闭Redis

  2. 先把备份的文件拷贝到工作目录下 cp dump2.rdb dump.rdb

  3. 启动Redis, 备份数据会直接加载

11、Redis6持久化——AOF

11.1、AOF是什么

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

11.2、AOF备份和恢复

AOF默认不开启,可以在redis.conf中配置文件名称,默认为 appendonly.aof

############### redis.conf #################
# appendonly默认为no,说明AOF不开启
appendonly no

# 设置备份文件名称,默认为appendonly.aof
appendfilename "appendonly.aof"

# AOF文件的保存路径,同RDB的路径一致。

如果AOF和RDB同时开启,系统默认取AOF的数据(数据不会存在丢失)

AOF的备份机制和性能虽然和RDB不同, 但是备份和恢复的操作同RDB一样,都是拷贝备份文件,需要恢复时再拷贝到Redis工作目录下,启动系统即加载。

如遇到AOF文件损坏,可以通过/usr/local/bin/redis-check-aof--fix appendonly.aof进行恢复

11.3、AOF同步频率设置

# 始终同步,每次Redis的写入都会立刻记入日志;性能较差但数据完整性比较好
appendfsync always

# 每秒同步,每秒记入日志一次,如果宕机,本秒的数据可能丢失。
appendfsync everysec

# redis不主动进行同步,把同步时机交给操作系统。
appendfsync no

 

11.4、Rewrite压缩

11.4.1、什么是重写压缩

AOF采用文件追加方式,文件会越来越大,为避免出现此种情况,新增了重写机制, 当AOF文件的大小超过所设定的阈值时,Redis就会启动AOF文件的内容压缩,只保留可以恢复数据的最小指令集。可以使用命令bgrewriteaof触发重写

AOF文件持续增长而过大时,会fork出一条新进程来将文件重写(也是先写临时文件最后再rename),redis4.0版本后的重写,实际上就是把rdb的快照,以二级制的形式附在新的aof头部,作为已有的历史数据,替换掉原来的流水账操作。

no-appendfsync-on-rewrite:

  • 如果 no-appendfsync-on-rewrite=yes ,不写入aof文件只写入缓存,用户请求不会阻塞,但是在这段时间如果宕机会丢失这段时间的缓存数据。(降低数据安全性,提高性能)

  • 如果 no-appendfsync-on-rewrite=no, 还是会把数据往磁盘里刷,但是遇到重写操作,可能会发生阻塞。(数据安全,但是性能降低)

11.4.2、重写压缩的触发机制

重写压缩的触发机制(何时重写):

  • Redis会记录上次重写时的AOF大小,默认配置是当AOF文件大小是上次rewrite后大小的一倍且文件大于64M时触发

  • 重写虽然可以节约大量磁盘空间,减少恢复时间。但是每次重写还是有一定的负担的,因此设定Redis要满足一定条件才会进行重写。

auto-aof-rewrite-percentage:设置重写的基准值,文件达到100%时开始重写(文件是原来重写后文件的2倍时触发)

auto-aof-rewrite-min-size:设置重写的基准值,最小文件64MB。达到这个值开始重写。

系统载入时或者上次重写完毕时,Redis会记录此时AOF大小,设为base_size,如果Redis的AOF当前大小>= base_size +base_size*100% (默认)且当前大小>=64mb(默认)的情况下,Redis会对AOF进行重写。

11.4.3、重写流程

  1. bgrewriteaof触发重写,判断是否当前有bgsave或bgrewriteaof在运行,如果有,则等待该命令结束后再继续执行。

  2. 主进程fork出子进程执行重写操作,保证主进程不会阻塞。

  3. 子进程遍历redis内存中数据到临时文件,客户端的写请求同时写入aof_buf缓冲区和aof_rewrite_buf重写缓冲区保证原AOF文件完整以及新AOF文件生成期间的新的数据修改动作不会丢失。

  4. 1).子进程写完新的AOF文件后,向主进程发信号,父进程更新统计信息。 2).主进程把aof_rewrite_buf中的数据写入到新的AOF文件。

  5. 使用新的AOF文件覆盖旧的AOF文件,完成AOF重写。

image-20220713125004490

11.5、AOF持久化流程

  1. 客户端的请求写命令会被append追加到AOF缓冲区内;

  2. AOF缓冲区根据AOF持久化策略[always,everysec,no]将操作sync同步到磁盘的AOF文件中;

  3. AOF文件大小超过重写策略或手动重写时,会对AOF文件rewrite重写,压缩AOF文件容量;

  4. Redis服务重启时,会重新load加载AOF文件中的写操作达到数据恢复的目的;

image-20220713125146201

11.6、AOF的优缺点

优点:

  • 备份机制更稳健,丢失数据概率更低。

  • 可读的日志文本,通过操作AOF稳健,可以处理误操作。

缺点:

  • 比起RDB占用更多的磁盘空间。

  • 恢复备份速度要慢。

  • 每次读写都同步的话,有一定的性能压力。

  • 存在个别Bug,造成恢复不能。

image-20220713125314730

11.7、两种持久化方式的比较

11.7.1、选择哪种持久化方式

官方推荐两个都启用。 如果对数据不敏感,可以选单独用RDB。 不建议单独用 AOF,因为可能会出现Bug。 如果只是做纯内存缓存,可以都不用。

11.7.2、官网建议

  • RDB持久化方式能够在指定的时间间隔能对你的数据进行快照存储

  • AOF持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据,AOF命令以redis协议追加保存每次写的操作到文件末尾.

  • Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大

  • 只做缓存:如果你只希望你的数据在服务器运行的时候存在,你也可以不使用任何持久化方式.

  • 同时开启两种持久化方式

  • 在这种情况下,当redis重启的时候会优先载入AOF文件来恢复原始的数据, 因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整.

  • RDB的数据不实时,同时使用两者时服务器重启也只会找AOF文件。那要不要只使用AOF呢?建议不要,因为RDB更适合用于备份数据库(AOF在不断变化不好备份), 快速重启,而且不会有AOF可能潜在的bug,留着作为一个万一的手段。

  • 性能建议:

    • 因为RDB文件只用作后备用途,建议只在Slave上持久化RDB文件,而且只要15分钟备份一次就够了,只保留save 900 1这条规则。

    • 如果使用AOF,好处是在最恶劣情况下也只会丢失不超过两秒数据,启动脚本较简单只load自己的AOF文件就可以了。

    • 代价,一是带来了持续的IO,二是AOF rewrite的最后将rewrite过程中产生的新数据写到新文件造成的阻塞几乎是不可避免的。

    • 只要硬盘许可,应该尽量减少AOF rewrite的频率,AOF重写的基础大小默认值64M太小了,可以设到5G以上。

    • 默认超过原大小100%大小时重写可以改到适当的数值。

12、Redis6的主从复制

12.1、什么是主从复制

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

12.2、主从复制的作用

  • 读写分离,性能扩展

  • 容灾快速恢复

image-20220713141910648

主服务器只能有一个,否则会产生冲突

12.3、测试主从复制

  1. 修改redis.conf,并复制到myredis文件夹中

    1. 开启daemonize

    2. 关掉appendonly或者换名字

  2. 新建redis6379.conf、redis6380.conf、redis6381.conf

    ###########redis6379.conf############
    include /myredis/redis.conf
    pidfile /var/run/redis_6379.pid
    port 6379
    dbfilename dump6379.rdb

    ###########redis6380.conf############
    include /myredis/redis.conf
    pidfile /var/run/redis_6380.pid
    port 6380
    dbfilename dump6380.rdb

    ###########redis6381.conf############
    include /myredis/redis.conf
    pidfile /var/run/redis_6381.pid
    port 6381
    dbfilename dump6381.rdb
  3. 启动三个redis服务

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

    # 查看三台主机的运行情况
    redis-cil -p 6379
    info replication
  4. 配置从机

    slaveof <主机ip> <主机端口>

    # 例如在6380和6381上运行
    slaveof 127.0.0.1 6379

    在主机上写,在从机上可以读取数据

12.4、主从复制的三个特点

12.4.1、一主二仆

从机重启需要重设slaveof,然后会从头开始复制主服务器上的所有数据

主机挂掉后从机会原地待命,不会变为主机

主机重新上线后,主机新增记录,从机仍能顺利复制

12.4.2、薪火相传

上一个Slave可以是下一个slave的Master,Slave同样可以接收其他 slaves的连接和同步请求,那么该slave作为了链条中下一个的master, 可以有效减轻master的写压力,去中心化降低风险。

中途变更转向:会清除之前的数据,重新建立拷贝最新的

从服务器都无法写数据

12.4.3、反客为主

当一个master宕机后,后面的slave可以立刻升为master,其后面的slave不用做任何修改。

slaveof no one 将从机变为主机。

12.5、主从复制原理

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

  2. Master接到sync命令启动后台的存盘进程(持久化),生成rdb文件,同时收集所有接收到的用于修改数据集命令, 在后台进程执行完毕之后,master将传送整个rdb数据文件到slave,以完成一次完全同步

  3. 每次主服务器进行写操作之后,和从服务器进行数据同步

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

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

  6. 只要是重新连接master,一次完全同步(全量复制)将被自动执行

12.6、哨兵模式

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

12.6.1、使用步骤

  1. 在自定义的myredis目录下新建sentinel.conf文件

  2. 配置哨兵,填写内容

    ########### sentinel.conf ###########
    # mymaster为监控对象起的服务器名称, 1 为至少有多少个哨兵同意迁移的数量。
    sentinel monitor mymaster 127.0.0.1 6379 1
  3. 启动哨兵

    redis-sentinel  /myredis/sentinel.conf 
  4. 当主机挂掉,从机选举产生新的主机,原主机重启后会变为从机

12.6.2、复制延时

由于所有的写操作都是先在Master上操作,然后同步更新到Slave上,所以从Master同步到Slave机器有一定的延迟,当系统很繁忙的时候,延迟问题会更加严重,Slave机器数量的增加也会使这个问题更加严重。

12.6.3、故障恢复

image-20220713170410909

选择条件:

  • 优先级在redis.conf中配置:slave-priority 100replica-priority 100,值越小优先级越高

  • 偏移量是指获得原主机数据最全的

  • 每个redis实例启动后都会随机生成一个40位的runid

12.6.4、Java中使用主从复制

创建JedisSentinelPool连接池

private static JedisSentinelPool jedisSentinelPool=null;

public static Jedis getJedisFromSentinel(){
if(jedisSentinelPool==null){
Set<String> sentinelSet=new HashSet<>();
sentinelSet.add("192.168.11.103:26379");

JedisPoolConfig jedisPoolConfig =new JedisPoolConfig();
jedisPoolConfig.setMaxTotal(10); //最大可用连接数
jedisPoolConfig.setMaxIdle(5); //最大闲置连接数
jedisPoolConfig.setMinIdle(5); //最小闲置连接数
jedisPoolConfig.setBlockWhenExhausted(true); //连接耗尽是否等待
jedisPoolConfig.setMaxWaitMillis(2000); //等待时间
jedisPoolConfig.setTestOnBorrow(true); //取连接的时候进行一下测试 ping pong


jedisSentinelPool=new JedisSentinelPool("mymaster",sentinelSet,jedisPoolConfig);
return jedisSentinelPool.getResource();
}else{
return jedisSentinelPool.getResource();
}
}

13、Redis6集群

13.1、什么是集群

redis3.0之后提供了无中心化集群方式(任何一台服务器都可以作为集群的入口),代替传统的代理主机来解决容量不够和并发的问题

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

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

13.2、集群搭建

13.2.1、修改配置文件

######### redis6379.conf #########
include /home/bigdata/redis.conf
port 6379
pidfile "/var/run/redis_6379.pid"
dbfilename "dump6379.rdb"
dir "/home/bigdata/redis_cluster"
logfile "/home/bigdata/redis_cluster/redis_err_6379.log"

# 打开集群模式
cluster-enabled yes

# 设定节点配置文件名
cluster-config-file nodes-6379.conf

# 设定节点失联时间,超过该时间(毫秒),集群自动进行主从切换
cluster-node-timeout 15000

拷贝多个redis.conf文件,79 80 81 89 90 91

可以使用vim的替换操作::%s/6379/6380

13.2.2、启动6个redis服务

redis-server redis6379.conf
......

13.2.3、将6个节点合成一个集群

组合之前,请确保所有redis实例启动后,nodes-xxxx.conf文件都生成正常。

cd  /opt/redis-6.2.1/src

# 以下使用真实ip地址
redis-cli --cluster create --cluster-replicas 1 192.168.11.101:6379 192.168.11.101:6380 192.168.11.101:6381 192.168.11.101:6389 192.168.11.101:6390 192.168.11.101:6391

--replicas 1 表示采用最简单的方式配置集群,一台主机,一台从机,正好三组。

13.2.4、集群连接

使用-c采用集群策略连接

redis-cli -c -p 6379

通过 cluster nodes 命令查看集群信息

13.3、集群的节点分配

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

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

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

13.4、slots

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

集群使用公式 CRC16(key) % 16384 来计算键 key 属于哪个槽, 其中 CRC16(key) 语句用于计算键 key 的 CRC16 校验和。(作用是将每个键值分散到各个节点中存储)

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

节点 A 负责处理 0 号至 5460 号插槽。

节点 B 负责处理 5461 号至 10922 号插槽。

节点 C 负责处理 10923 号至 16383 号插槽。

一个插槽中可以存储多少个key?❓

13.5、向集群中存入值

在redis-cli每次录入、查询键值,redis都会计算出该key应该送往的插槽,如果不是该客户端对应服务器的插槽,redis会报错,并告知应前往的redis实例地址和端口。

redis-cli客户端提供了 –c 参数实现自动重定向。如 redis-cli -c –p 6379 登入后,再录入、查询键值对可以自动重定向。

不在一个slot下的键值,不能使用mget,mset等多键操作。

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

# 定义一个user组,将键值存储到同一个slot
mset name{user} lucy age{user} 22

13.6、查询集群中的值

只能查询自己插槽范围内的值

# 查询键k1的插槽值
clustor keyslot k1

# 查询某个插槽中有几个键
clustor countkeysinslot xxx

# 返回count个slot槽中的键
clustor getkeysinslot xxx <count>

13.7、故障恢复

如果主节点下线,从节点可以自动升为主节点,原来的主节点变为从机

如果某一段插槽的主从都挂掉,而redis.conf中的参数 cluster-require-full-coverage 为 yes ,那么整个集群都挂掉

如果某一段插槽的主从都挂掉,而redis.conf中的参数 cluster-require-full-coverage 为 no ,那么集群可以服务,但该插槽数据全都不能使用,也无法存储。

13.8、集群的Jedis开发

即使Jedis连接的不是主机,集群也会自动切换主机存储。主机写,从机读。

无中心化主从集群。无论从哪台主机写的数据,其他主机上都能读到数据。

public class JedisClusterTest {
public static void main(String[] args) {
// 创建JedisCluster对象
Set<HostAndPort>set =new HashSet<HostAndPort>();
set.add(new HostAndPort("192.168.31.211",6379));
JedisCluster jedisCluster=new JedisCluster(set);
// 进行操作
jedisCluster.set("k1", "v1");
System.out.println(jedisCluster.get("k1"));
}
}

13.9、集群的优缺点

优点:

  • 实现扩容

  • 分摊压力

  • 无中心配置相对简单

缺点:

  • 多键操作是不被支持的

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

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

14、Redis6应用问题解决

14.1、缓存穿透

14.1.1、问题描述

key对应的数据在数据源并不存在,每次针对此key的请求从缓存获取不到,请求都会压到数据源,从而可能压垮数据源。比如用一个不存在的用户id获取用户信息,不论缓存还是数据库都没有,若黑客利用此漏洞进行攻击可能压垮数据库。

现象:应用服务器压力变大,redis命中率降低,导致一直查询数据库,最终数据库崩溃

redis正常运行,但没有起到缓存作用,频繁直接操作数据库

发生缓存穿透的情况:

  1. redis查询不到数据库

  2. 出现很多非正常url访问(遭受攻击)

一个一定不存在缓存及查询不到的数据,由于缓存是不命中时被动写的,并且出于容错考虑,如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义。

14.1.2、解决方案

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

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

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

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

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

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

14.2、缓存击穿

14.2.1、问题描述

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

现象:数据库访问压力瞬时增加,redis中没有出现大量key过期,redis正常运行

redis中某个热点数据key过期了,大量并发请求查询该key,导致数据库瞬间承受大量压力

14.2.2、解决方案

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

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

  3. 使用锁:(会降低效率) 在缓存失效的时候(判断拿出来的值为空),不是立即去load db。 先使用缓存工具的某些带成功操作返回值的操作(比如Redis的SETNX)去set一个mutex key 当操作返回成功时,再进行load db的操作,并回设缓存,最后删除mutex key; 当操作返回失败,证明有线程在load db,当前线程睡眠一段时间再重试整个get缓存的方法。

image-20220713192832641

14.3、缓存雪崩

14.3.1、问题描述

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

缓存雪崩与缓存击穿的区别在于这里针对很多key缓存,前者则是某一个key正常访问

在极小的时间段内大量的key集中过期

14.3.2、解决方案

  1. 构建多级缓存架构:nginx缓存 + redis缓存 +其他缓存(ehcache等)

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

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

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

14.4、分布式锁

14.4.1、问题描述

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

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

  1. 基于数据库实现分布式锁

  2. 基于缓存(Redis等)

  3. 基于Zookeeper

每一种分布式锁解决方案都有各自的优缺点:

  1. 性能:redis最高

  2. 可靠性:zookeeper最高 这里,我们就基于redis实现分布式锁。

14.4.2、解决方案

使用redis实现分布式锁

set k1 v1 nx ex 10
  • EX second :设置键的过期时间为 second 秒。 SET key value EX second 效果等同于 SETEX key second value

  • PX millisecond :设置键的过期时间为 millisecond 毫秒。 SET key value PX millisecond 效果等同于 PSETEX key millisecond value

  • NX :只在键不存在时,才对键进行设置操作。 SET key value NX 效果等同于 SETNX key value

  • XX :只在键已经存在时,才对键进行设置操作。

上锁的同时设置过期时间可以防止上锁后突然出现异常导致无法设置过期时间

14.4.3、代码实现

  1. 设置锁,添加过期时间

@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();
}
}
}
  1. 设置uuid防止锁因为上一个业务逻辑过期而被误释放

解决:setnx获取锁时,设置一个指定的唯一值(例如:uuid);释放前获取这个值,判断是否自己的锁

image-20220713195438235

  1. 删除操作缺乏原子性,可能出现以下场景:

    index1执行删除时,查询到的lock值确实和uuid相等,index1执行删除前,lock刚好过期时间已到,被redis自动释放,index2获取了lock,index1执行删除,此时会把index2的lock删除

解决:使用LUA脚本

@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);

if (lock) {
// 执行的业务逻辑开始
// 获取缓存中的num 数据
Object value = redisTemplate.opsForValue().get("num");
// 如果是空直接返回
if (StringUtils.isEmpty(value)) {
return;
}
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();
}
}
}

为了确保分布式锁可用,我们至少要确保锁的实现同时满足以下四个条件:

  • 互斥性。在任意时刻,只有一个客户端能持有锁。

  • 不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。

  • 解铃还须系铃人。加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了。

  • 加锁和解锁必须具有原子性。

15、Redis6新功能

15.1、ACL

Redis ACL是Access Control List(访问控制列表)的缩写,该功能允许根据可以执行的命令和可以访问的键来限制某些连接。

在Redis 5版本之前,Redis 安全规则只有密码控制 还有通过rename 来调整高危命令比如 flushdb , KEYS* , shutdown 等。Redis 6 则提供ACL的功能对用户进行更细粒度的权限控制 : (1)接入权限:用户名和密码 (2)可以执行的命令 (3)可以操作的 KEY

15.1.1、命令

# 展现用户权限列表
acl list

# 查看添加权限指令类别和具体命令
acl cat
acl cat string

# 查看当前用户
acl whoami

# 创建用户
acl setuser user1
# 设置有用户名、密码、ACL权限、并启用的用户
acl setuser user2 on >password ~cached:* +get

# 切换用户 auth+用户名+密码
auth user2 password

截屏2022-07-13 20.15.00

 

15.2、IO多线程

Redis6终于支撑多线程了,告别单线程了吗? IO多线程其实指客户端交互部分的网络IO交互处理模块多线程,而非执行命令多线程。Redis6执行命令依然是单线程。

15.2.1、原理架构

Redis 6 加入多线程,但跟 Memcached 这种从 IO处理到数据访问多线程的实现模式有些差异。Redis 的多线程部分只是用来处理网络数据的读写和协议解析,执行命令仍然是单线程。之所以这么设计是不想因为多线程而变得复杂,需要去控制 key、lua、事务,LPUSH/LPOP 等等的并发问题。

另外,多线程IO默认也是不开启的,需要再配置文件中配置

io-threads-do-reads yes 
io-threads 4

15.3、工具支持 Cluster

之前老版Redis想要搭集群需要单独安装ruby环境,Redis 5 将 redis-trib.rb 的功能集成到 redis-cli 。另外官方 redis-benchmark 工具开始支持 cluster 模式了,通过多线程的方式对多个分片进行压测。

 
posted @ 2022-08-31 01:45  Colin13  阅读(21)  评论(0编辑  收藏  举报