Redis学习笔记
Redis学习手册
NoSQL介绍
NoSQL(Not Only SQL)不仅仅是 SQL,泛指非关系型数据库。NoSQL数据库是为了解决大规模数据集合多重数据种类带来的挑战,特别是超大规模数据的存储。
NoSQL数据库的一个显著特点就是去掉了关系数据库的关系型特性,数据之间一旦没有关系,使得扩展性、读写性能都大幅度提高。
NoSQL和传统的关系型数据库不是排斥和取代的关系,在一个分布式应用中往往是结合使用的。复杂的互联网应用都是多数据源、多数据类型的,应该根据数据的使用情况和特点,存放在合适的数据库中。
Redis介绍
Redis(Remote Dictionary Server)是一个开源的使用 ANSI C语言编写、支持网络、可基于内存亦可持久化的 Key-Value数据库。Key为字符类型,其值(value)可以是:
- 字符串(String)
- 哈希(Map)
- 列表(list)
- 集合(set)
- 有序集合(sorted set)
每种数据类型都有自己的专属命令,所以它通常也被称为数据结构服务器。
Redis的特点
支持数据持久化
Redis支持数据的持久化,可以将内存中的数据保存在磁盘上,重启的时候可以再次加载进行使用。
支持多种数据结构
Redis不仅仅支持简单的 key-value 类型的数据,同时还提供 list, set, zset, hash等数据结构的存储。
支持数据备份
Redis支持数据的备份,即 master-slave 模式的数据备份
Redis安装和使用
Windows上安装Redis
Windows版本的 Redis是 Microsoft的开源部门提供的 Redis,适合开发人员学习使用,生产环境中使用 Linux系统上的 Redis。
下载
安装
下载的 Redis-x64-3.2.100.zip解压后,即可使用。
启动
1)双击 redis-server.exe
2)cmd下输入redis-server.exe redis.windows.conf
关闭
按 ctrl+c 推出 Redis服务程序
Linux安装 Redis
下载
wget https://mirrors.huaweicloud.com/redis/redis-6.0.9.tar.gz
安装
1)解压文件
2) 在解压后的 Redis 目录下编译 Redis文件
3)安装Redis
启动Redis
1)前台启动 ./redis-server,此时不能推出当前窗口,否则应用终止
2)后台启动 ./redis-server &,此时即便关闭窗口,redis进程依旧存在
关闭 Redis
1)使用 redis客户端关闭,向服务器发出关闭命令
2)kill pid
注意:如果修改了 redis的配置文件 redis.conf就需要在启动时指定配置文件,否则修改无效!!!
Redis客户端
Redis客户端是一个程序,通过网络连接到 Redis服务器,从而实现跟 Redis服务器的交互。Redis客户端发送命令,同时显示 Redis服务器的处理结果。
redis-cli(Redis Command Line Interface)是 Redis自带的基于命令行的 Redis客户端,用于与服务端交互,我们可以使用该客户端来执行 redis的各种命令。
1)直接连接 redis(默认 ip:127.0.0.1,端口:6379)
2)指定 IP 和端口号连接 redis:reids-cli -h ip地址 -p 端口号
3)推出 Redis客户端:quit 或者 exit
Redis基本知识
测试 Redis性能:redis-benchmark
Redis沟通命令:ping
查看 Redis服务器的统计信息:info [section]
Redis默认使用 16个库
Redis默认使用16个库,从0到15。对数据库个数的修改,在 redis.conf文件中 databases 16,理论上可以配置无限多个。
Redis的库和关系型数据库中的数据库实例类似,但又有所不同。比如,Redis中各个库不能自定义命名,只能用序号标识,Redis中各个库不是完全独立的,使用时最好一个应用使用一个 Redis实例,不建议一个 Redis实例中保存多个应用的数据。
Redis实例本身所占内存空间其实是很小的,因此不会造成存储空间的浪费。
切换库命令:select db
默认使用第0个
查看当前数据库中 key的数目:dbsize
查看当前数据库中有哪些key:keys pattern
pattern可以使用通配符:
- *:表示 0或多个字符
- ?:表示单个字符
- []:表示选择[]内的一个字符
判断key是否存在:exists key [key…]
返回key存在的数量
移动key到指定的数据库中:move key db
移动key到指定的数据库中,该key在原库中被删除
移动成功返回1,失败返回0
查看key的剩余生存时间:ttl key
以秒为单位,返回 -1表示 key永不过期,返回 -2表示key不存在
设置key的生存时间:expire key seconds
设置key的生存时间,超过时间,key自动删除,单位是秒
设置成功返回数字 1,其他情况返回数字 0
查看key所存储值的数据类型:type key
返回值:数据类型,若key不存在则返回none
重命名key:rename key newkey
将 key改名为 newkey,当 key和 newkey相同,或者 key不存在时,返回一个错误。
删除存在的 key:del key [key..]
删除存在的 key,不存在的忽略,返回删除的数量
清空当前库:flushdb
清空所有数据库:flushall
**获取 Redis的所有配置值:config get ***
Redis数据类型
5种数据结构
字符串类型 string
字符串类型是 Redis种最基本的数据类型,它能存储任何形式的字符串,包括二进制数据,序列化后的数据,JSON对象甚至是一张图片。最大为 512M。
哈希类型 hash
哈希类型是一个 string类型的 field和 value的映射表,特别适合用于存储对象
列表类型 list
列表类型是简单的字符串列表,按照插入顺序排列,可以添加一个元素到列表的头部或者尾部
集合类型 set
集合类型是无序集合,内部成员唯一,集合中不能出现重复的数据
有序集合类型 zset
有序集合类似集合,每个成员都会关联一个分数,用作排序依据
字符串类型
字符串类型是Redis中最基本的数据类型。
基本命令
set
将字符串值 value设置到 key中
语法:set key value
向已经存在的 key设置新的 value,会覆盖原理的值
get
获取 key对应的 value值
语法:get key
incr
将 key中存储的数字值加 1,如果 key不存在,则 key的值先被初始化为 0,再执行 incr操作
语法:incr key
decr
将 key中存储的数字值减 1,如果 key不存在,则 key的值先被初始化为 0,再执行 decr操作
语法:decr key
incr、decr 在实现关注人数上,文章的点击数上可以有效运用
incrby
将字符串 key所存储的值加上指定的增量值,若 key不存在,则先初始化为0再执行 increby命令,返回增量之后的 key值
语法:incrby key offset
decrby
将字符串 key所存储的值减去减量值。
语法:decrby key offset
setex
设置字符串key值,并将 key的生存时间设为 seconds值,若 key已经存在,将覆盖旧值
语法:setex key seconds value
setnx
若字符串 key不存在,则 set值;否则不设置值
语法:setnx key value
append
如果 key存在,则在 value后追加存储;如果 key不存在,则把追加值当作 value。返回之家字符串后的总长度
语法:append key value
strlen
返回 key所存储的字符串值的长度,若key不存在则返回 0
语法:strlen key
getrange
获取 key中字符串值从 start开始到 end结束的子串,包括 start和 end,-1表示最后一个字符
语法:getrange key start end
setrange
用指定值覆盖 key对应的原值,从 offset开始,不存在的 key视作空白字符串
语法:setrange key offset value
mset
同时设置多个 key-value 键值对
语法:mset key value[key value…]
mget
获取指定的多个 key对应的多个值
语法:mget key[key…]
哈希类型
哈希类型是一个 string类型的 field和 value的映射表,hash适合用于存储对象。
基本命令
hset
将哈希表 key 中的 field设置为 value,如果 key不存在则新建哈希表,如果已有 field,则覆盖值。若是新的 field,则返回 1;若已有 field,则返回 0。
语法:hset key field value
hget
获取 key中 field的值,若 key不存在或 field不存在则返回 nil
语法:hget key field
hmset
同时将多个 field-value 设置到 同一个key中。
语法:hmset key field value[field value…]
hmget
获取指定 key的多个 field的值,返回对应顺序的值,若不存在则返回 nil
语法:hmget key field[field…]
hgetall
获取 key中所有的域和值
语法:hgetall key
hdel
删除 key中的一个或多个指定域 field,返回成功删除的 field的数量
语法:hdel key field[field…]
hkeys
查看指定 key中所有的 field
语法:hkeys key
hvals
返回指定 key中所有的 value
语法:hvals key
hexists
判断指定 key中是否存在指定的 field
语法:hexists key field
hincrby
给哈希表 key中的 field域增加 int值
语法:hincrby key field int
hincrbyfloat
给哈希表 key中的 field域增加 float值
语法:hincrbyfloat key field float
hsetnx
当域 field在哈希表 key中不存在时,创建该域并设置值为 value
语法:hsetnx key field value
列表类型
列表是简单的字符串列表,按照插入顺序排序。
基本命令
lpush
将一个或多个 value插入到列表 key的表头(最左边),返回新列表的长度
语法:lpush key value [value…]
rpush
将一个或多个 value插入到列表 key的表尾(最右边),返回新列表的长度
语法:rpush key value [value]
lrange
获取列表中指定区间内的元素,包含 start和 end
语法:lrange key start stop
lindex
获取列表 key中下标为指定 index的元素,若 index不在列表的范围内,返回 nil。列表元素不删除,只是查询。
语法:lindex hey index
llen
获取列表 key的长度,key不存在返回 0
语法:llen key
lrem
在列表中删除 count个值为 value的字符串,若 count>0 则从左侧开始移除,若 count<0 则从右侧开始移除,若count=0则删除所有与 value相等的值。
lset
将列表 key下标为 index的元素的值设置为 value
语法:lset key index value
linsert
将值 value插入到列表 key中位于值 pivot之前或之后的位置,返回新列表的长度;若 key不存在,返回 0;若 pivot不在列表中,返回 -1。
语法:linsert key BEFORE | AFTER pivot value
lpop
移除并返回列表 key头部(列表左侧)的第一个元素
语法:lpop key
rpop
移除并返回列表尾部(列表右侧)的第一个元素
语法:rpop key
ltrim
截取列表 key指定下标区间内的元素并赋值给 key,超出范围则为全部
语法:ltrim key startIndex endIndex
集合类型
集合成员唯一,但却是无序的。
基本命令
sadd
将一个或多个 member元素加入到集合 key当中,已经存在于集合的 member元素将被忽略,返回加入到集合的新元素的个数。
语法:sadd key member [member…]
smembers
获取集合 key中的所有成员元素,不存在的 key视为空集合
sismember
判断 member元素是否是集合 key的成员,若是则返回 1,其他返回 0
语法:sismember key member
scard
获取集合里面的元素个数
语法:scard key
srem
删除集合 key中的一个或多个 member元素,不存在的元素被忽略,返回成功删除的元素个数
语法:srem key member [member…]
srandmember
随机返回集合key 中的 count个元素,若count值为负数时可能会出现重复
语法:srandmember key [count]
spop
随机从集合 key中删除 count个元素
语法:spop key [count]
smove
将 member元素从 src集合移动到 dest集合,若 member不存在,smove不执行并返回 0
语法:smove src dest member
sdiff
返回指定集合的差集,以第一个集合为准进行比较,即第一个集合中有但在其他集合中都没有的元素组成的集合。
语法:sdiff key key [key…]
sinter
返回指定集合的交集,即所有集合中都有的元素组成的集合
语法:sinter key key [key…]
sunion
返回指定集合的并集,即指定的所有集合元素组成的大集合,如果元素有重复,则保留一个
语法:sunion key key [key…]
有序集合类型
有序集合和集合类似,都不允许有重复元素。但是 zset的每个元素都会关联一个分数作为排序依据
基本命令
zadd
将多个 member元素及其score值加入到有序集合 key中,如果 member已存在集合中,则会覆盖原来的值;score可以是整数和浮点数
语法:zadd key score member [score member…]
zrange
查询有序集合 key中指定区间内的元素,集合元素按 score值从小到大来排序。withscores选项让 score和 value一起展示
语法:zrange key startIndex endIndex [withscores]
zrevrange
返回有序集合 key中指定区间内的元素,按从大到小来排序
语法:zrevrange key start stop [withscores]
zrangebyscore
获取有序集合 key中 score值介于 min和 max之间的所有成员,从小到大排序;可使用 (min 表示开区间,withscores同时显示 score和 value,limit语句限制返回结果的数量和区间,表示从第 offset个开始,取 count个
语法:zrangebyscore key min max [withcores] [limit offset count]
zrem
删除有序集合 key中的多个成员 member
语法:zrem key member [member…]
zcard
获取有序集合 key的元素成员的个数
语法:zcard key
zcount
返回有序集合 key中,score值在 min和 max之间的成员数量
语法:zcount key min max
zrank
获取指定元素 member在有序集合 key中成员的排名,按照从小到大顺序排列,从0开始
语法:zrank key member
zscore
获取有序集合 key中元素 member的分数
语法:zscore key member
Redis高级
Redis配置文件
redis.conf 在 Redis的安装目录(/opt/redis-5.0.2)下,redis在启动时会加载这个配置文件,在运行时按照配置进行工作。这个文件有时候我们会单独拿出来存放,因此 redis启动时必须指定使用哪个配置文件,此文件才会生效。
网络相关配置
bind
绑定 IP 地址,其他及其可以通过此 IP 访问 Redis,默认绑定 127.0.0.1,也可以修改为本机的 IP地址。
port
配置 Redis占用的端口,默认为 6379
tcp-keepalive
TCP连接的保证存活策略,单位为秒,若设置为0,则不会进行检测。
假如配置为 60秒,则 server端会每 60秒向连接空闲的客户端发起一次 ACK请求,以检查客户端是否存活,,对于无响应的客户端则会直接关闭连接。
常规配置
loglevel
日志级别,开发阶段可以设置为 debug,生产阶段通常设置为 notice 或者 warning
logfile
指定日志文件名,如果不指定,Redis只进行标准输出。
要保证日志文件所在的目录必须存在,文件可以不存在。
databases
配置 Redis数据库的个数,默认是 16个
安全配置
requirepass
配置 Redis的访问密码,默认不配置密码,即访问不需要密码验证。
此配置项需要在 protected-mode=yes时起作用。
使用密码登录客户端:redis-cli -h 127.0.0.1 -p 6379 -a pwd
RDB配置
save
save <seconds> <seconds>,如果 Redis在 secons秒内 key改变了 changes次,Redis就把快照内的数据保存到磁盘中一次。
默认的策略是:
- 1分钟内改变了1万次
- 5分钟内改变了10次
- 15分钟内改变了1次
stop-writes-on-bgsave-error
当 bgsave快照操作出错时停止写数据到磁盘,这样能保证内存数据和磁盘数据的一致性;但如果不在乎这种一致性,要在 bgsave快照操作出错时继续写操作,可配置为 no
rdbcompression
设置对于存储到磁盘中的快照是否进行压缩,设置为 yes时,Redis会采用 LZF算法进行压缩;如果不想消耗 CPU进行压缩的话,可以设置为 no
rdbchecksum
在存储快照以后,还可以让 Redis使用 CRC64算法来进行数据校验,但这样会消耗一定的性能。
dbfilename
Redis持久化数据生成的文件名,默认为 dump.rdb,也可以自己配置
dir
Redis持久化数据生成文件保存的目录,默认是 ./ 即redis的启动目录
AOF配置
appendonly
是否开启 AOF,yes表示开启,no表示关闭,默认为 no
appendfilename
AOF保存文件名
appendfsync
AOF异步持久化策略:
- always:同步持久化,每次发生数据变化就写入到磁盘中,性能较差但数据完整性好。
- everysec:每秒异步记录一次,默认值
- no:不及时同步,由操作系统决定何时同步
no-appendfsync-on-rewrite
重写时是否可以运用 appendsync,默认为 no,可以保证数据的安全性
auto-aof-rewrite-percetage
设置重写的基准百分比
auto-aof-rewrite-min-size
设置重写的基准值
Redis的持久化
Redis是内存数据库,它把数据存储在内存中,这样在加快读取速度的同时也对数据安全性产生了新的问题。当 Redis所在服务器宕机后,Redis数据库里的所有数据将会全部丢失。为了解决这个问题,Redis提供了持久化功能——RDB 和 AOF
RDB
RDB(Redis DataBase)是 Redis默认的持久化方案,在指定的时间间隔内,执行指定次数的写操作,则会将内存中的数据写入到磁盘中。即在指定目录下生成一个 dump.rdb文件,Redis重启后会通过加载 dump.rdb文件来恢复数据。
RDB原理
Redis会复制一个与当前进程一样的进程,新进程的所有数据(变量、环境变量、程序计数器等)都和原进程一致,但却是一个全新的进程,并作为原进程的子进程,来进行持久化。
整个过程中,主进程是不进行任何 IO操作的,这确保了极高的性能。
如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感的,那 RDB方式要比 AOF方式更加高效。
RDB的缺点是最后一次持久化后的数据可能丢失。
RDB保存的文件
RDB保存的文件是 dump.rdb文件,位置保存在 Redis的启动目录。
Redis每次同步数据到磁盘都会生成一个 dump.rdb文件,新的 dump.rdb会覆盖旧的 dump.rdb文件。
配置 RDB持久化策略
在 redis.conf的快照配置中,配置 RDB保存的策略
在客户端执行 FLUSHDB或者 FLUSHALL或者 SHUTDOWN时,也会把快照中的数据保存到 dump.rdb中,只不过这种操作已经把数据清空了,保存的也是空文件,没有意义。
手动保存 RDB快照
save命令执行一个同步保存操作,将当前 Redis实例的所有数据快照(snapshot)以 RDB文件的形式保存到硬盘。
由于 save指令会阻塞所有客户端,所以保存数据库的任务通常由 BGSAVE命令异步地执行,而 save作为保存数据的最后手段来使用,当负责保存数据的后台子进程不幸出现问题时使用。
RDB数据恢复
通过脚本将 Redis产生的 dump.rdb文件备份,每次启动 Redis前,将备份的 dump.rdb文件替换到 Redis相应的目录下,Redis启动时会加载 dump.rdb文件,并且把数据读到内存中。
RDB小结
Redis默认开启 RDB持久化方式,适合大规模的数据恢复但它的数据一致性和完整性较差。
AOF
AOF(Append Only File),Redis默认不开启。它的出现是为了弥补 RDB的不足(数据的不一致性),所以它采用日志的形式来记录每个写操作,并追加到文件中。Redis重启会根据日志文件的内容将指令从前到后执行一次以完成符间的恢复工作。
AOF原理
Redis以日志的形式来记录每个写操作,将 Redis执行过的所有写指令记录下来(读操作不记录),只允许追加文件但是不可以改写文件。
Redis启动之初会读取该文件重新构建数据,换言之,Redis重启之后就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。
AOF保存的文件
AOF保存的文件是 appendonly.aof文件,位置保存在 Redis的启动目录中。
如果开启了 AOF,Redis每次记录写操作都会往 appendonly.aof文件追加新的日志内容。
配置 AOF持久化策略
在 redis.conf的 “APPEND ONLY MODE”配置模块中,配置 AOF保存策略。
AOF数据恢复
通过脚本将 Redis产生的 appendonly.aof文件备份,每次启动 Redis前,把备份的 appendonly.aof文件替换到 Redis相应的目录下,只要开启 AOF的功能,Redis每次启动都会根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。
但在实际的开发中,可能会因为某些原因导致 appendonly.aof文件格式异常,从而导致数据还原失败,可以通过命令 redis-check-aof-fix appendonly.aof
进行修复。
并且会把出现异常的部分往后所有写操作日志去掉。
AOF的重写
AOF采用文件追加方式,文件会越来越大。为了避免出现此种情况,新增了重写机制,当 AOF文件的大小超过设定的阈值时,Redis就会启动 AOF文件的内容压缩,只保留可以恢复数据的最小指令集。
AOF文件持续增长而过大时,会 fork出一条新进程来讲文件重写(也是先写临时文件最后再 rename),遍历新进程的内存中数据,每条记录有一条 Set语句。重写 AOF文件的操作,并没有读取旧的 AOF文件,而是将整个内存中的数据库内容的命令的方式重写了一个新的 AOF文件,这点类似快照。
Redis会记录上次重写的 AOF大小,默认配置是当 AOF文件大小是上次 rewrite后大小的一倍且文件大于 64M时触发。当然,也可以再配置文件中进行配置。
AOF小结
Redis需要手动开启 AOF持久化方式,AOF的数据完整性比 RDB高,但记录内容多了,会影响数据恢复的效率。
关于 Redis持久化的使用:若只打算用 Redis做缓存,可以关闭持久化。若打算使用 Redis的持久化,建议 RDB和 AOF都开启。其实 RDB更适合做数据的备份,留一后手。AOF出问题了,还有 RDB。
AOF和 RDB模式可以同时启用,二者并不冲突。如果 AOF是可用的,那么 Redis启动时将自动加载 AOF,这个文件能够提供更好的持久性保障。
Redis的事务
Redis的事务允许再一次单独的步骤中执行一组命令,并且能够保证将一个事务中的所有命令序列化,然后按顺序执行。
在一个 Redis事务中,Redis要么执行其中的所有命令,要么什么都不执行,即 Redis的事务要能够保证序列化和原子性。
常用命名
multi
用于标记事务块的开始,Redis会将后续的命令逐个放入队列中,然后才能使用 exec命令原子化地执行这个命令序列。
exec
在一个事务中执行所有先前放入队列的命令,然后恢复正常的连接状态。
如果在把命令压入队列的过程中报错,则整个队列中的全部命令都不会执行。
如果在压入队列的过程中正常,在执行队列中的命令时报错,则只会影响到本条命令的执行结果,其他命令正常执行(错误命令后的命令也会执行)。
discard
清除所有先前在一个事务中放入队列的命令,并且结束事务。
如果使用了 watch命令,那么 discard命令就会将当前连接监控的所有键取消监控。
watch
当某个事务需要按条件执行时,就要使用这个命令将给定的键设置为受监控的。
如果被监控的 key值在本事务外被修改时,则本事务的所有指令都不会执行。
watch命令相当于关系型数据库中的乐观锁
unwatch
清除某个键的被控制状态
如果在 watch命令之后调用了 exec或 discard命令,那么就不需要手动调用 unwatch命令了
事务小结
- 单独的隔断操作:事务中的的所有命令都会序列化地执行,并且在执行过程中,不会被其他客户端发来的命令请求打断,除非使用 watch命令监控某些键
- 不保证事务的原子性:Redis同一个事务中如果一条命令执行失败,其后的命令仍然可能会被执行,Redis的事务没有回滚。Redis已经在系统内部进行了简化,以保证运行的速度。
Redis消息的发布与订阅
Redis发布订阅
Redis发布订阅(pub / sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。Redis客户端可以订阅任意数量的频道。
消息订阅者订阅频道
消息发布者发布消息到频道
常用命令
subscribe
订阅一个或多个频道的信息
语法:subscribe channel [channel…]
publish
将信息发送到指定的频道
语法:publish chanel message
psubscribe
订阅一个或多个符合给定模式的频道,模式以 * 作为通配符。
语法:psubscribe pattern [pattern…]
Redis的主从复制
主机数据更新后根据配置和策略,自动同步到从机。
master/slave机制,master以写为主,slave以读为主
可以选择一台主机配多台从机,一台从机再配多台从机,从而实现了庞大的集群架构,同时也减轻了一台主机的压力,缺点是增加了服务器间的延迟。
一主二从搭建
一台服务器模拟三台主机
1)将 redis.conf拷贝三份,分别命名为 redis6379.conf redis6380.conf redis6381.conf
2)修改三个文件的 port端口i,pid文件名,日志文件名,rdb文件名
port 6379
pidfile /var/run/redis_6379.conf
logfile "6379.log"
dbfilename dump6379.rdb
3)分别打开三个窗口模拟三台服务器
4)查询主从信息:info replication
5)6379主机上进行写操作
6)设置主从关系,在 6380和 6381两台机器上分别执行命令 slaveof 127.0.0.1 6379
或者在配置文件中加入 slaveof 127.0.0.1 6379;如果主机上设置了密码,就需要设置 masterauth为主机的密码
7)全量复制:在 6380和 6381上分别执行命令 get k1
8)增量复制:在 6379上执行命令:set k2 v2;然后在 6380和 6381上分别执行命令:get k2
9)读写分离:在 6380和 6381上执行写操作:set k3 v3
10)主机宕机后从机原地待命
11)主机恢复后,一切正常:重启 6379并执行写命令:set k4 v4;在 6380和 6381上分别执行 get k4
12)从机宕机:主机的从机连接数少 1
13)从机宕机后恢复:如果主从关系是通过命令行配置的则从机变为另一台主机,如果是配置文件配置的则变回从机
复制原理
全量复制
slave启动成功连接到 master后会发送一个 sync命令;master接到命令启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令,在后台进程执行完毕后,master将传送整个数据文件到 slave以完成一次完全同步。
slave服务在接收到数据库文件数据后,就会存盘并加载到内存中。
只要是重新连接 master,一次完全同步(全量复制)将会被自动执行
增量复制
master将新的所有收集到的修改命令依次传给 slave,完成同步
Redis的哨兵模式
哨兵模式原理
从机上位的自动实现。Redis提供了哨兵的命令,哨兵命令是一个独立的进程,通过发送命令来监控主从服务器的运行状态。如果检测到 master故障了根据投票数自动将某个 slave转换为 master,然后通过消息订阅模式通知其他 slave,让他们切换主机。
然而,单独一个哨兵进程对 Redis服务器进行监控可能会出现问题,为此,我们可以使用多哨兵进行监控。
sentinel有三个主要任务:
- 监控:sentinel不断的检查主服务和从服务器是否按照预期正常工作
- 提醒:被监控的 Redis出现问题时,sentinel会通知管理员或其他应用程序
- 自动故障转移:监控的主 Redis不能正常工作,sentinel会开始进行故障迁移操作。将一个从服务器升级为新的主服务器,让其他从服务器挂到新的主服务器上,同时向客户端提供新的主服务器地址。
哨兵模式搭建
1)复制三分 sentinel.conf文件,分别命名为 sentinel26380.conf sentinel26381.conf sentinel26382.conf
2)分别修改三份配置文件,类似如下:
port 26380
sentinel monitor mymaster 127.0.0.1 6379 2
3)启动主从 Redis
4)启动 sentinel:./redis-sentinel ../sentinel26380.conf
5)主机宕机
6)sentinel起作用,日志文件如下:
7)新的 Redis加入 sentinel系统,自动加入 master
8)监控
- sentinel 会不断检查 master和 slave是否正常
- 如果 sentinel挂了,就无法监控了,因此需要多个哨兵组成 sentinel网络。一个监控的 sentinel网络至少有 3个 sentinel应用,彼此在独立的物理机器或虚拟机上。
- 监控同一个 master的 sentinel会自动连接,组成一个分布式的 sentinel网络,互相通信并交换彼此关于被监控服务器的信息
- 当一个 sentinel认为被监控的服务器已经下线时,它会向网络中其他的 sentinel进行确认,判断该服务器是否真的已经下线
- 如果下线的服务器为主服务器,那么 sentinel网络将对下线的主服务器进行自动故障转移,通过将下线的主服务器的某个从服务提升为新的主服务器,并让其从服务器转移到新的主服务器下,来让系统重新回到正常状态
- 下线的旧主服务器重新上线后,sentinel会让它成为从服务器,挂到新的主服务器下
Jedis操作 Redis
<!-- pom.xml -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.3.0</version>
</dependency>
// 处理字符串
public class RedisString {
public static void main(String[] args) {
// 创建 Jedis对象,连接到 Redis,需要提供 IP和 port
Jedis jedis = new Jedis("127.0.0.1", 6379);
// 添加字符串
jedis.set("breakfast", "包子");
String mybreak = jedis.get("breakfast")
System.out.println("我的早餐1:" + mybeak);
// 追加内容
jedis.append("breakfast", "和鸡蛋");
mybreak = jedis.get("breakfast");
System.out.println("我的早餐2:" + mybreak);
// 一次设置多个 key-value
jedis.mset("lunch", "红烧肉", "dinner", "牛肉面");
// 获取多个 key的 value
List<String> dinners = jedis.mget("lunch", "dinner");
dinners.forEach(item -> System.out.println("我吃的是:" + item));
}
}
// 使用Jedis连接池
public class RedisUtils {
// 定义连接池对象
private static JedisPool pool = null;
// 创建连接池
public static JedisPool open(String host, int port) {
if(pool == null) {
// 使用 JedisPool
JedisPoolConfig config = new JedisPoolConfig();
// 设置最大的Jedis实例数(连接池中Jedis实例的个数,默认是8)
config.setMaxTotal(10);
// 设置最大的空闲实例数,用来保留足够的连接,以便快速获取到 Jedis对象
config.setMaxIdle(3);
// 提前检查 Jedis对象,为 true获取的 Jedis一定是可用的
config.setTestOnBorrow(true);
// 创建 Jedis连接池
pool = new JedisPool(config, host, port);
}
return pool;
}
// 关闭连接池
public static void close() {
if(pool != null) {
pool.close();
}
}
}
// 使用 Jedis连接池处理哈希
@Test
public void test01() {
// 创建连接池
JedisPool pool = RedisUtils.open("127.0.0.1", 6379);
Jedis jedis = null;
try {
// 从连接池中获取 Jedis对象
jedis = pool.getResource();
// 设置哈希类型,key field value
jedis.hset("loginuser", "username", "zhangsan");
System.out.println("username的值:" + jedis.hget("loginuser", "username"));
} catch(Exception e) {
e.printStackTrace();
} finally {
if(jedis != null) {
// 使用完的连接池对象,放回连接池
jedis.close();
}
}
}
@Test
public void test02 {
JedisPool pool = RedisUtils.open("127.0.0.1", 6379);
Jedis jedis = null;
try {
jedis = pool.getResource();
// jedis.hmset(String, Map<String, String>)
Map<String, String> map = new HashMap<>();
map.put("username", "lisi");
map.put("age", "20");
map.put("password", "123456");
// 设置多个值,使用 Map存入 Redis
jedis.hmset("logininfo", map);
// 从 Redis中获取数据
Lsit<String> values = jedis.hgetAll("logininfo");
for(String value : values) {
Syetem.out.println("value:" + value);
}
jedis.hdel("logininfo", "age");
System.out.println("age是:" + jedis.hget("logininfo", "age"));
} catch(Exception e) {
e.printStackTrace();
} finally {
if(jedis != null) {
jedis.close();
}
}
}
// java处理列表
@Test
public void test03() {
Jedis jedis = new Jedis("127.0.0.1", 6379);
String key = "framework";
jedis.del(key);
jedis.lpush(key, "mybatis");
jedis.lpush(key,"hibernate", "spring", "springmvc");
List<String> lists = jedis.lrange(key, 0, -1);
for(String str : lists) {
System.out.println("列表数据:" + str);
}
System.out.println("列表长度:" + jedis.llen(key));
// 从列表右侧插入数据
jedis.rpush(key, "struts", "webwork");
System.out.println("第一个下标值:" + jedis.lindex(key, 0));
for(long i = 0, len = jedis.llen(key); i < len; i++) {
System.out.println("弹出内容:" + jedis.lpop(key));
}
}
// java处理集合
@Test
public void test03() {
Jedis jedis = new Jedis("127.0.0.1", 6379);
String key = "course";
// 添加一个数据
jedis.sadd(key, "html");
// 添加多个数据
jedis.sadd(key, "css", "javascript", "mysql", "spring");
// 获取集合中的所有成员
Set<String> sets = jedis.smembers(key);
for(String set : sets) {
System.out.println("集合 set成员:" + set);
}
// 判断元素是否在集合内
System.out.println("struts还有吗?" + jedis.sismember(key, "struts"));
}
// java处理有序集合
@Test
public void test04() {
Jedis jedis = new Jedis("127.0.0.1", 6379);
String key = "salary";
jedis.del(key);
jedis.zadd(key, 2000D, "john");
// 添加多个值
Map<String, Double> map = new HashMap<>();
map.put("tom", 3500D);
map.put("marry", 1500D);
jedis.zadd(key, map);
// 查询返回全部的数据,没有score
Set<String> sets = jedis.zrangeByScore(key, "-inf", "+inf");
for(String set : sets) {
System.out.println("从大到小排序为:" + set);
}
// 带 score查询数据
Set<Tuple> tuples = jedis.zrangeByScoreWithScores(key, "-inf", "+inf");
for(String tuple : tuples) {
System.out.println("数据:" + tuple.getElement()+" 分值:" + tuple.getScore());
}
}
// java处理 Redis事务
public class RedisTransaction {
public static void main(String[] args) {
JedisPool pool = RedisUtils.open("127.0.0.1", 6379);
Jedis jedis = null;
try {
// 开启事务
Transaction trans = jedis.multi();
trans.set("breakfast", "包子");
trans.mset("lunch", "红烧肉", "dinner", "牛肉面");
List<Object> resultList = trans.exec();
// 事务的处理结果
for(Object result : resultLsit) {
System.out.println("成功的事务操作:" + result);
} catch (Exception e) {
e.printStackTrace();
} finally {
if(jedis != null) {
jedis.close();
}
}
}
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 通过 API 将Deepseek响应流式内容输出到前端
· AI Agent开发,如何调用三方的API Function,是通过提示词来发起调用的吗