redis详解

概述

NoSQL

  • NoSQL(Not Only SQL)数据库泛指非关系型数据库 主要用于存储大量数据

  • NoSQL数据库的特点

    • 去掉了关系型数据库的关系型特性
  • 数据之间一旦没有了关系 则会大大提高扩展性与读写性能

    • 彻底改变了底层的数据存储机制 使用聚合模型来存储数据而不是表
  • NoSQL与关系型数据库不是排斥或取代的关系 在一个分布式情形下往往是结合使用的 应该根据数据的特点与使用情况将数据存放在适合的数据库中

  • 关系型数据库有MySQL、Oracle、DB2、SqlServer

  • 非关系型数据库有Redis、MongoDB、HBase

  • NoSQL数据模型

    • 聚合模型

      //BSON
      {
          id:1001,
          name:zhangsan,
          age:18,
          class:
          {
              id:01,
              name:class1
          }
      }
      

Redis

  • Redis(Remote Dictionary Server)是一个用C语言编写的、开源的、高性能的、在内存中运行的、支持数据持久化的NoSQL数据库 又称为缓存数据库

  • redis中的数据大多数的时间都是存储在内存中的 因此redis适合用来存储频繁访问但数据量较小的数据

  • redis的特点

    • 支持数据持久化

      ​ 即可以将存储在内存中的数据保存到磁盘中以便重启redis/计算机后可以再次加载并使用

    • 支持多种数据结构

      ​ redis不仅支持K-V类型的数据的存储 还支持list、set、zset、hash等数据结构的存储

    • 支持数据备份

      ​ 即master-slave模式的数据备份

在linux上安装redis

#先将安装包通过xftp传输到/opt目录下

#解压到/opt目录下
tar -zxvf redis-7.0.4.tar.gz -C /opt

#执行make命令
#作用是在redis目录的/src目录下生成一系列的可执行文件
cd /opt/redis-7.0.4
make

#报错:cc: Command not found
#这是因为没有安装gcc(是linux系统中的C/C++的编译器 相当于javac)
#安装gcc
yum -y install gcc
#查看gcc是否安装成功
gcc -v

#再次执行make命令
make

#报错:zmalloc.h:50:31: fatal error: jemalloc/jemalloc.h: No such file or directory
#这是因为第一次make时出错 生成了一些不完整的文件 因此需要清理一下才能继续make
#清理
make distclean

#再次执行make命令
make

#执行make install命令
#作用是把在redis目录的/src目录下的一系列可执行文件拷贝到/usr/local/bin目录下以便可以在任何地方执行redis命令
make install

#启动redis服务
#前台启动
redis-server
#后台启动
redis-server &
#后台启动且指定配置文件
#如果修改了配置文件 则在启动redis服务时必须指定配置文件 否则修改无效
redis-server redis.conf &

#测试redis性能
redis-benchmark
#throughput summary: 88888.89 requests per second

#关闭redis服务
#使用redis客户端关闭ip为127.0.0.1与端口号为6379的redis服务
#redis先完成数据操作然后再关闭
redis-cli shutdown
#使用redis客户端关闭ip为192.168.190.128与端口号为6380的redis服务
#redis先完成数据操作然后再关闭
redis-cli -h 192.168.190.128 -p 6380 shutdown
#使用kill命令关闭redis服务
#不管是否-9都是强制关闭 即可能会丢失数据
kill 8705

redis客户端

概述

  • redis客户端是一个程序 能通过网络连接到redis从而向redis发送命令并显示处理结果以实现与redis的交互
  • redis-cli(Redis Command Line Interface)是redis自带的基于命令行的redis客户端

命令

启动redis客户端

#连接ip为127.0.0.1端口号为6379的redis
redis-cli

#连接ip为192.168.190.128端口号为6379的redis
redis-cli -h 192.168.190.128

#连接ip为127.0.0.1端口号为6380的redis
redis-cli -p 6380

#连接ip为127.0.0.1端口号为6379密码为123456的redis
#默认启动redis客户端是不需要密码的
redis-cli -a 123456

关闭redis客户端

exit

quit

与redis客户端相关命令

#测试redis是否正常运行
#若返回PONG则正常 否则不正常
ping

#查看全部统计信息(包含server部分、clients部分、memory部分等的统计信息)
info
#仅查看server部分的统计信息
info server

#查看此redis的redis.conf配置文件中的所有参数信息
config get *
#查看此redis的redis.conf配置文件中的databases参数信息
config get databases

redis命令

数据库相关命令

  • redis默认自动创建出16个数据库实例(可在redis.conf配置文件中的databases参数处修改)并从0开始编号

  • redis数据库实例与关系型数据库实例相似 但又有所不同

    • redis数据库实例只能由redis服务来创建与维护 即开发人员不能自行创建/修改redis数据库实例

    • redis数据库实例的名称不能自定义 只能使用编号

    • redis各个数据库实例之间不是完全独立的 因此使用redis时应一个应用使用一个redis实例(使用不同的port来区分不同的redis实例)(redis实例本身所占存储空间非常小 因此生成多个redis实例是不会造成存储空间的浪费的)

#切换数据库成数据库index
#默认使用的是第0个数据库
select index

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

#清空当前数据库
flushdb

#清空所有数据库
flushall

key相关命令

keys

#查找当前数据库中所有值符合模式pattern的key
#*:表示0或多个字符
#?:表示任意1个字符
#[]:表示[]内的1个字符
keys pattern
#查找当前数据库中所有的key
keys *

#查找当前数据库中值符合"wo?d"的key(如word、wood)
keys wo?d

#查找当前数据库中值符合"wo[ro]d"的key(如word、wood)
keys wo[ro]d

exists

#查找当前数据库中是否存在某key/某些key 返回0/存在的key的数量
exists key [key ...]

move

#将key从当前数据库中移动到数据库db中 返回0/1
#此key在原数据库中会被删除
move key db

ttl

#查看当前数据库中某key的ttl(time to live)(剩余生存时间) 以s为单位
#返回-2(key不存在)/-1(此key没有设置ttl(即此key永不过期))/..
ttl key

expire

#设置当前数据库中某key的ttl为seconds(即若超过了seconds后此key就会被自动删除) 返回0/1
expire key seconds

type

#查看当前数据库某key的数据类型
#返回none(key不存在)/string/list/set/hash/zset
type key

rename

#重命名当前数据库中key的值为newkey
#若key不存在/key与newkey相同会报错
#若newkey已存在则会覆盖
rename key newkey

del

#删除当前数据库中某key/某些key
#返回0/删除成功的个数
del key [key ...]

数据类型相关命令

string

  • 单key单value

  • 能存储任何类型的数据(如二进制数据、序列化后的数据、json化的对象、图片)

  • 最大能存储512M的数据

set
#设置key的值为value
#若key已存在则会覆盖
set key value
get
#获取key的值
#若key不存在则会返回nil
get key
append
#追加value到key的值的末尾
#若key不存在则相当于set
#返回append后此key的值的长度
append key value
strlen
#获取key的值的长度
#若key不存在则会返回0
strlen key
incr
#将key的值+1
#若key不存在则会先set key 0然后再incr
#返回incr后此key的值
#incr只能对数值型的数据(即integer)incr 若对非数值型的数据incr则会报错
incr key
decr
#将key的值-1
#若key不存在则会先set key 0然后再decr
#返回decr后此key的值
#decr只能对数值型的数据(即integer)decr 若对非数值型的数据decr则会报错
decr key
incrby
#将key的值+increment
#若key不存在则会先set key 0然后再incrby
#返回incrby后此key的值
#incrby只能对数值型的数据(即integer)incrby 若对非数值型的数据incrby则会报错
incrby key increment
decrby
#将key的值-decrement
#若key不存在则会先set key 0然后再decrby
#返回decrby后此key的值
#decrby只能对数值型的数据(即integer)decrby 若对非数值型的数据decrby则会报错
decrby key decrement
getrange
#获取区间为[start, end]的key的值
#start与end都可以为负数 如-1表示倒数第1个字符
#start与stop越界不会报错
getrange key start end
get address
#"guangdong"
getrange address -4 -1
#"dong"
getrange address 0 -1
#"guangdong"
setrange
#使用value来覆盖掉key的从下标为offset开始的值
setrange key offset value
setex
#即set与expire的组合
#若key已存在则会覆盖
setex key seconds value
setnx
#即set的升级版
#若key已存在则set不成功 当key不存在时才会set成功
#返回0/1
#setnx(set if not exists)
setnx key value
mset
#即set的升级版
#同时set一个/多个
#若key已存在则会覆盖
mset key value [key value ...]
mget
#即get的升级版
#同时get一个/多个
#若key不存在则会返回nil
mget key [key ...]
msetnx
#即mset与setnx的组合
#只要有一个key已存在则set不成功 当所有key都不存在时才会set成功
#返回0/1
msetnx key value [key value ...]

list

  • 是string类型的有序(指取出有序)可重复集合(单key多value)
  • 能将数据添加到list的头部 也能将数据添加到list的尾部 也能将数据添加到list的中间(但效率会大大降低)
  • 底层是一个链表结构
lpush
#将一个/多个element插入到key的头部
#返回lpush后此key的值的长度
lpush key element [element ...]
rpush
#将一个/多个element插入到key的尾部
#返回rpush后此key的值的长度
rpush key element [element ...]
lrange
#获取key的区间为[start, stop]的值
#start与stop都可以为负数
#start与stop越界不会报错
lrange key start stop
lpop
#删除并返回key的第1个/前count个element
#若key不存在则会返回nil
lpop key [count]
rpop
#删除并返回key的倒数第1个/后count个element
#若key不存在则会返回nil
rpop key [count]
lindex
#获取key的下标为index的值
#index可以为负数
#若key不存在则会返回nil
lindex key index
llen
#获取key的值的长度
#若key不存在则会返回0
llen key
lrem
#删除key中值与element相等的值
#count > 0:表示从key的头部开始删除key中count个值与element相等的值
#count < 0:表示从key的尾部开始删除key中count个值与element相等的值
#count = 0:表示删除key中全部值与element相等的值
#返回删除的数量
lrem key count element
ltrim
#获取key的区间为[start, stop]的值并赋值给key
#start与stop都可以为负数
#start与stop越界不会报错
ltrim key start stop
lset
#将element赋值给key的下标为index的值
#index可以为负数
#若key不存在/index越界会报错
lset key index element
linsert
#将element插入到key中值为pivot的前一个/后一个的位置
#返回linsert后此key的值的长度
#若key不存在则会返回0 若pivot不存在则会返回-1
linsert key before|after pivot element

set

  • 是string类型的无序(指第1次取出无序 第2次及以后取出都为第1次的顺序)不重复集合(单key多value)
sadd
#添加一个/多个member到key中
#注意 set为不重复集合
#返回添加成功的个数
sadd key member [member ...]
smembers
#获取key中所有的元素
smembers key
sismember
#判断member是否是key中的元素
#返回0/1
sismember key member
scard
#获取key中的元素个数
#若key不存在则会返回0
scard key
srem
#删除key中的一个/多个member
#返回删除成功的个数
srem key member [member ...]
srandmember
#从key中随机获取1个/count个元素
#count > 0:表示获取的元素之间不会重复
#count < 0:表示获取的元素之间可以重复
srandmember key [count]
spop
#从key中随机删除1个/count个元素
#若key不存在/key的值为空则会返回nil
spop key [count]
smove
#将member从source中移动到destination中 返回0/1
#若source中不存在member则会返回0 若destination中存在member则只在source中删除member
#此member在source中会被删除
smove source destination member
sdiff
#返回key/返回所有第1个key中有但其余的key中没有的元素
sdiff key [key ...]
sinter
#返回key/返回所有key的交集
sinter key [key ...]
sunion
#返回key/返回所有key的并集
sunion key [key ...]

hash

  • 是string类型的field与value的映射表(单key单value)
  • 适合用来存储对象(对象通常有属性与值)
hset
#添加1个/多个field与value到key中
#若key中的field已存在则会覆盖
#返回添加成功的个数
hset key field value [field value ...]
hget
#获取key中field的值
#若key不存在/field不存在则会返回nil
hget key field
hmset
#即hset
hmset key field value [field value ...]
hmget
#获取key中一个/多个field的值
#若key不存在/field不存在则会返回nil
hmget key field [field ...]
hgetall
#获取key中所有的field与value
hgetall key
hdel
#删除key中一个/多个field
#返回删除成功的个数
hdel key field [field ...]
hlen
#获取key中field的数量
#若key不存在则会返回0
hlen key
hexists
#判断key中是否存在field
#返回0/1
hexists key field
hkeys
#获取key中所有的field
hkeys key
hvals
#获取key中所有的field的value
hvals key
hincrby
#将key的field的value+increment
#若key不存在/field不存在则会先hset key field 0然后再hincrby
#返回hincrby后此field的value
#hincrby只能对数值型的数据(即integer)hincrby 若对非数值型的数据hincrby则会报错
hincrby key field increment
hincrbyfloat
#将key的field的value+increment(increment可以为小数)
#若key不存在/field不存在则会先hset key field 0然后再hincrbyfloat
#返回hincrbyfloat后此field的value
#hincrbyfloat只能对数值型的数据(即integer)hincrbyfloat 若对非数值型的数据hincrbyfloat则会报错
hincrbyfloat key field increment
hsetnx
#即hset的升级版
#若key的field已存在则hset不成功 当key的field不存在时才会hset成功
#返回0/1
hsetnx key field value

zset

  • 是string类型的无序(指取出无序 因为按升序排序了)不重复集合(单key多value)
  • zset的每个元素都会关联一个score(score可重复) 而redis通过score来为zset中的元素进行升序排序
zadd
#添加一个/多个member与score(可以是小数)到key中
#注意 zset为不重复集合
#若key中的member已存在则会覆盖
#返回添加成功的个数
zadd key score member [score member ...]
zrange
#获取key的区间为[start, stop]的元素(key中的元素按照score升序排序)
#start与stop都可以为负数
#start与stop越界不会报错
#withscores:表示连同元素相对应的score一起返回
zrange key start stop [withscores]
zrangebyscore
#获取key中score区间为[min, max]的元素(key中的元素按照score升序排序)
#"(":表示此区间为开区间
#withscores:表示连同元素相对应的score一起返回
#limit:表示对结果集合做进一步的筛选 获取结果集合中从下标为offset开始的count个元素
zrangebyscore key [(]min [(]max [withscores] [limit offset count]
zrem
#删除key中的一个/多个member
#返回删除成功的个数
zrem key member [member ...]
zcard
#获取key中的元素个数
#若key不存在则会返回0
zcard key
zcount
#获取key中score区间为[min, max]的元素的个数
#"(":表示此区间为开区间
zcount key [(]min [(]max
zrank
#获取key中的member按照score升序排序后的排名(从0开始排名)
#若member不存在则会返回nil
zrank key member
zscore
#获取key中的member的score
#若member不存在则会返回nil
zscore key member
zrevrank
#获取key中的member按照score降序排序后的排名(从0开始排名)
#若member不存在则会返回nil
zrevrank key member
zrevrange
#获取key的区间为[start, stop]的元素(key中的元素按照score降序排序)
#start与stop都可以为负数
#start与stop越界不会报错
#withscores:表示连同元素相对应的score一起返回
zrevrange key start stop [withscores]
zrevrangebyscore
#获取key中score区间为[min, max]的元素(key中的元素按照score降序排序)
#"(":表示此区间为开区间
#withscores:表示连同元素相对应的score一起返回
#limit:表示对结果集合做进一步的筛选 获取结果集合中从下标为offset开始的count个元素
zrevrangebyscore key max min [withscores] [limit offset count]

redis配置文件

#一般的 会将bind指定为服务器上的某一个真实ip
#但若配置了bind 则表示只能在bind指定的ip所在的计算机上连接redis服务 其它计算机都不能连接此redis
bind:连接redis服务时所能使用的ip

#默认为6379
port:redis服务运行时所占用的端口号

#注意 一旦修改了bind与port 则启动redis客户端/关闭redis服务时就要指定-h与-p 否则仍然是对ip为127.0.0.1端口号为6379的redis服务进行操作 当然 启动redis服务时也要采用后台启动且指定配置文件的方式

#服务端会每隔Xs向已连接但处于空闲的客户端发起一次ACK请求以检查客户端是否挂掉 而对于无响应的客户端则会关闭其连接
#若设置成0则表示不会进行tcp连接保活策略
tcp-keepalive:tcp连接保活策略 单位为s

#开发阶段通常设置成debug 上线阶段通常设置成notice/warning
loglevel:日志级别

#redis在运行过程中会输出日志 默认输出到控制台上 若配置了logfile则日志会输出到指定的文件中
#配置日志文件时要确保文件所处的目录是存在的 文件可以不存在
logfile:日志文件

#默认为16
databases:redis服务创建的数据库实例个数

#默认不配置 若配置则还得需要参数protected-mode为yes时才会生效 此时启动redis客户端得带-a参数
requirepass:启动redis客户端时的密码

#rdb配置参数
#默认为在1m内key改变了10000次/在5m内key改变了100次/在1h内key改变了1次
#若想要禁用rdb则得将所有的save都注释掉/使用save ""并删除dump.rdb文件并重启redis才行
save seconds changes:快照的触发条件(即在seconds秒内key改变了changes次后redis就会进行rdb持久化) 单位为s
stop-writes-on-bgsave-error:在bgsave后快照出现错误时是否停止执行写操作以保持内存中的数据与磁盘中的数据的一致性
rdbcompression:是否消耗一定的CPU以让redis采用LZF算法对存储在磁盘中的dump.rdb文件进行压缩
rdbchecksum:是否消耗一定的性能以让redis采用CRC64算法对生成的dump.rdb文件进行数据的校验
#默认为dump.rdb
dbfilename:rdb生成的文件名
#默认为./ 表示当前目录
dir:rdb生成的文件所在的目录名

#aof配置参数
#默认为no
appendonly:是否开启aof
#默认为appendonly.aof
appendfilename:aof生成的文件名
#默认为appendonlydir
appenddirname:aof生成的文件所在的目录名
#默认为everysec
#always:每次数据发生变化时都会立即追加 性能较差但数据的完整较好
#everysec:每秒记录一次(当数据发生变化时redis会记录下来并追加到aof缓存区中(可看成是一个队列))
#no:不会即时同步数据 而是由系统定时的同步(一般为30s)(当数据发生变化时redis会记录下来并追加到aof缓存区中)
appendfsync:aof异步持久化策略
#默认为no
#若为no 则表示在aof重写时也会将发生变化的数据记录下来并追加到旧appendonly.aof文件中 这就会导致可能会出现IO阻塞的情况/IO阻塞情况严重(因为aof重写为IO操作 追加到旧appendonly.aof文件中也为IO操作) 若为yes 则表示不会即时同步数据 而是会把发生变化的数据记录下来并追加到aof缓存区中 而这时候可能就不会出现IO阻塞的情况/IO阻塞情况不会太严重
no-appendfsync-on-rewrite:在aof重写时是否将appendfsync设置成no
#默认为100
auto-aof-rewrite-percentage:aof重写的基准百分比
#默认为64mb
auto-aof-rewrite-min-size:aof重写的基准值
#以上两个参数共同表示若追加后的新appendonly.aof文件的大小比上次重写过后的appendonly.aof文件的大小要大100%且大小超过了64mb则进行aof重写

redis持久化

  • redis是内存数据库 即将数据存储在内存中 虽然会加快读取数据的速度但数据不安全(若redis所在的服务器发生宕机后则redis数据库中的数据会全部丢失) 因此redis提供了持久化的策略 即在经过指定的时间后将内存中的数据保存到磁盘中以便宕机后/重启redis服务后也能再次从磁盘中加载回来

RDB

  • RDB(redis database)是redis默认开启的持久化策略

概述

  • rdb是指在经过指定的时间后执行指定次数的写操作以将内存中的数据写入到磁盘中且会在指定的目录下生成dump.rdb文件(每次同步数据都会生成此文件(当执行flushdb/flushall/shutdown时也会生成此文件 只不过生成的文件是空文件) 新的会覆盖旧的(实际上新生成的此文件的名称不是叫dump.rdb 而是叫其它的名称 但之后会重命名成dump.rdb以替换旧文件)) 而当redis服务重启时则会加载此文件以恢复数据

rdb原理

  • redis会先fork()出一个与当前进程一样(指的是所有数据都一样)的进程 然后让此进程作为原进程的子进程并进行持久化

  • 在整个持久化的过程中 原进程是不需要进行任何的IO操作的 这就保证了极高的性能

rdb优点

  • 数据恢复的速度快 适合用来恢复大量的数据

rdb缺点

  • 数据的一致性与完整性较差(因为保存数据的子进程可能会出现问题且在最后一次持久化之后的数据可能会丢失)
  • 数据保存的速度慢(因为每次保存都是保存所有数据)

手动rdb

  • 即手动的用当前redis数据库实例中的所有数据来生成一个dump.rdb文件
同步保存
#注意 因为此命令会阻塞redis服务 因此此命令通常是作为保存数据的最后手段来使用的
#但此命令不会消耗额外的内存
#一般的 此命令通常在保存数据的子进程出现问题时才能使用
save
异步保存
#此命令不会阻塞redis服务
#此命令会消耗额外的内存(因为要创建子进程)
bgsave

AOF

  • AOF(append only file)是redis默认不开启的持久化策略 是为了弥补rdb的缺点才出现的

概述

  • aof是采用日志的形式来记录每个写操作并追加到指定的目录下的appendonly.aof文件 而当redis服务重启时则会根据此文件的内容将记录的每个写操作重新执行一遍以恢复数据

aof原理

  • 采用日志的形式来记录每个写操作(读操作不记录)并追加(而不是覆盖)到指定的目录下的appendonly.aof文件

  • 但在实际开发中 此文件可能会出现异常从而导致数据恢复失败 则此时可以通过命令

    redis-check-aof --fix appendonly.aof
    

    来修复此文件(修复原理:在此文件中将出现异常的部分及往后的写操作全部去掉)

aof优点

  • 数据的一致性与完整性较好
  • 数据保存的速度快(因为是追加)

aof缺点

  • 数据恢复的速度慢(因为要重新执行日志文件中全部的写操作)

aof重写

  • 因为aof采用的是追加的方式 因此会出现appendonly.aof文件的大小越来越大的问题 而为了解决此问题 redis增加了重写机制
概述
  • aof重写指的是当appendonly.aof文件的大小超过了指定的阈值时(默认为若追加后的新appendonly.aof文件的大小比上次重写过后的appendonly.aof文件的大小要大100%且大小超过了64mb则进行aof重写) redis就会压缩此文件中的内容
aof重写原理
  • redis会先fork()出一个与当前进程一样的进程 然后让此进程作为原进程的子进程并遍历的读取子进程中的redis数据库实例并会用几条命令来记录这些数据以代替先前的几十条命令

    #假设旧appendonly.aof文件记录的是
    rpush l1 a b c
    rpush l1 d
    rpush l1 e f
    lpop l1
    lpop l1
    rpush l1 g
    
    #则重写后的新appendonly.aof文件记录的是
    rpush l1 c d e f g
    
aof重写缓存区
  • 在对子进程的redis数据库实例进行遍历与记录的过程中 主进程可能又会进行写操作 但这时候的写操作是不会同步到子进程中的 而这就会出现新appendonly.aof文件仍然记录不全的问题 而为了解决此问题 redis增加了aof重写缓存区
  • 也可看成是一个队列
  • 在redisfork()出子进程时会同时启动aof重写缓存区 而对于之后的主进程中的新的写操作redis会将此操作记录下来并在追加到旧appendonly.aof文件的同时也追加到aof重写缓存区中 当子进程重写完成并生成了新appendonly.aof文件后(实际上新生成的此文件的名称不是叫appendonly.aof 而是叫其它的名称 但之后会重命名成appendonly.aof以替换旧文件)会向主进程发送信号表明已完成重写 而主进程在收到信号后会将aof重写缓存区中的内容全部追加到新appendonly.aof文件中并将新文件重命名以替换旧文件
手动aof重写
bgrewriteaof

总结

  • 若只用redis做缓存 则可以关闭持久化策略
  • 若想要开启redis的持久化策略 则建议rdb与aof都开启(两者可以同时开启 不冲突 若aof是可用的 则redis恢复数据时会自动使用aof来加载数据(因为数据的一致性与完整性较好))

redis事务

命令

#开启事务
#开启事务后redis会将后续的命令逐个放进队列中
multi

#执行事务(即执行队列中所有的命令)并退出事务
#若将命令放进队列的过程中报错则不会执行队列中的所有命令且执行结果报错
#若将命令放进队列的过程正常但执行事务时队列中的某一个命令报错则此命令的执行结果报错但不会影响其它命令
#不管被监控的key是否在本事务外被修改了 只要一执行exec 则redis就会将watch监控的全部key恢复成未被监控的状态
#返回队列中的每条命令的执行结果/nil(被监控的key在本事务外被修改了)
exec

#清除队列中的所有命令并退出事务
#还会将watch监控的全部key恢复成未被监控的状态
discard

#监控key
#若被监控的key在本事务外(先watch再multi)被修改了 则本事务中的所有命令都不会被执行(相当于乐观锁)
watch key [key ...]

#将watch监控的全部key恢复成未被监控的状态
unwatch

总结

  • 能保证事务的序列化:即事务中的所有命令都会按照顺序有序的执行
  • 事务在执行的过程中能保证不会被其它的客户端的命令所打断 除非使用了watch来监控了某些key且这些被监控的key在本事务外被修改了
  • 只能保证事务的部分原子性:事务在执行的过程中若某一个命令在放进队列的过程中报错则本事务中的所有命令均不执行(能保证事务的原子性) 事务在执行的过程中若某一个命令执行失败 其后的命令仍然会被执行(不能保证事务的原子性)
  • redis事务不支持回滚:因为redis的内部已经进行了简化 在以不支持回滚为代价的情况下能加快运行速度 且redis的命令只会因错误的语法/命令用在了错误的key上而失败 即redis认为 错误的代码是导致命令的失败的主要原因 而这个原因应该在开发的过程中就会被发现并解决 而不应该出现在生产环境中 因此redis事务不支持回滚

redis消息发布与订阅

  • 是一种消息通信模式:消息发布者往频道上发布消息后 所有的此频道的消息订阅者就都能够接收到此消息
  • 可以订阅任意数量的频道

命令

#订阅一个/多个频道
#返回订阅的频道的消息
subscribe channel [channel ...]

#发布消息message到频道channel上
#返回接收到此消息的订阅者的数量
publish channel message

#订阅一个/多个符合模式pattern的频道(可以使用通配符*)
#返回订阅的频道的消息
psubscribe pattern [pattern ...]

redis主从复制

  • 指master/slave机制 即当主机的数据更新后 根据配置与策略 自动将更新的数据同步到从机
  • 特点
    • 主少从多
    • 主写从读
    • 读写分离
    • 配从配设主
    • 主断从待命 从断重新配
    • 一台主机可以配置多台从机 一台从机又可以配置多台从机 从而能够形成一个庞大的集群架构 这样做能减轻主机的压力 但却增加了服务器之间的延迟

一主二从

#将redis.conf文件从linux中下载到windows中
sz /opt/redis-7.0.4/redis.conf

#复制两份并改名为redis6379.conf、redis6380.conf、redis6381.conf

#分别修改三个配置文件中的port、pidfile、logfile、dbfilename
#redis_6379.pid文件存放的是redis服务进程的pid 作用为能防止创建多个redis服务进程副本

#上传这三个文件到linux中
rz

#启动三个redis服务
redis-server redis6379.conf &
redis-server redis6380.conf &
redis-server redis6381.conf &

#启动三个redis客户端
redis-cli -p 6379
redis-cli -p 6380
redis-cli -p 6381

#查看主从信息
#默认情况下 所有的redis客户端一开始都是主机、都没有从机、都能进行读与写
#role:此客户端的角色(master/slave)
#connected_slaves:此客户端的从机的数量
#master_host:主机的ip
#master_port:主机的端口号
#master_link_status:主机的状态(up为在线 down为断开)
info replication

#三台主机之间的数据相互独立
127.0.0.1:6379> keys *
(empty array)
127.0.0.1:6379> rpush l1 a b c
(integer) 3
127.0.0.1:6379> keys *
1) "l1"

127.0.0.1:6380> keys *
(empty array)

127.0.0.1:6381> keys *
(empty array)

#配置主从关系:配从不配主
slaveof host port

127.0.0.1:6380> slaveof 127.0.0.1 6379
OK
127.0.0.1:6380> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6379
master_link_status:up

127.0.0.1:6381> slaveof 127.0.0.1 6379
OK
127.0.0.1:6381> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6379
master_link_status:up

127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:2
slave0:ip=127.0.0.1,port=6380,state=online,offset=756,lag=0
slave1:ip=127.0.0.1,port=6381,state=online,offset=756,lag=0

#全量复制
#当主从关系确定后 redis会自动的将主库上现有的数据同步到从库中
127.0.0.1:6380> keys *
1) "l1"

127.0.0.1:6381> keys *
1) "l1"

#增量复制
#每当主机上进行了对数据的写操作 则此操作也会自动同步到从机中
127.0.0.1:6379> rpush l2 d e f
(integer) 3
127.0.0.1:6379> keys *
1) "l1"
2) "l2"

127.0.0.1:6380> keys *
1) "l2"
2) "l1"

127.0.0.1:6381> keys *
1) "l2"
2) "l1"

#测试
#主写从读 读写分离
127.0.0.1:6380> rpush l3 a
(error) READONLY You can't write against a read only replica.

127.0.0.1:6381> rpush l3 a
(error) READONLY You can't write against a read only replica.

#主断从待命
[root@localhost redis-7.0.4]# redis-cli -p 6379 shutdown

127.0.0.1:6380> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6379
master_link_status:down

127.0.0.1:6381> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6379
master_link_status:down

[root@localhost redis-7.0.4]# redis-server redis6379.conf &

127.0.0.1:6380> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6379
master_link_status:up

127.0.0.1:6381> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6379
master_link_status:up

127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:2
slave0:ip=127.0.0.1,port=6380,state=online,offset=2601,lag=1
slave1:ip=127.0.0.1,port=6381,state=online,offset=2601,lag=1

#从断重新配
[root@localhost redis-7.0.4]# redis-cli -p 3681 shutdown
[1]+  Done                    redis-server redis6381.conf
[root@localhost redis-7.0.4]# redis-server redis6381.conf &
[1] 4313
[root@localhost redis-7.0.4]# redis-cli -p 6381
127.0.0.1:6381> info replication
# Replication
role:master
connected_slaves:0

127.0.0.1:6380> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6379
master_link_status:up

127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:1
slave0:ip=127.0.0.1,port=6380,state=online,offset=2895,lag=1

127.0.0.1:6381> slaveof 127.0.0.1 6379
OK

127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:2
slave0:ip=127.0.0.1,port=6380,state=online,offset=3203,lag=0
slave1:ip=127.0.0.1,port=6381,state=wait_bgsave,offset=0,lag=0

#从机上位
[root@localhost redis-7.0.4]# redis-cli -p 6379 shutdown
[1]+  Done                    redis-server redis6379.conf

127.0.0.1:6380> slaveof no one
OK
127.0.0.1:6380> info replication
# Replication
role:master
connected_slaves:0

127.0.0.1:6381> slaveof 127.0.0.1 6380
OK
127.0.0.1:6381> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6380
master_link_status:up

127.0.0.1:6380> info replication
# Replication
role:master
connected_slaves:1
slave0:ip=127.0.0.1,port=6381,state=online,offset=5051,lag=0

[root@localhost redis-7.0.4]# redis-server redis6379.conf &
[1] 4686
[root@localhost redis-7.0.4]# redis-cli -p 6379
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:0

127.0.0.1:6379> slaveof 127.0.0.1 6381
OK
127.0.0.1:6379> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6381
master_link_status:up

#这时的6381既是从机又是主机
127.0.0.1:6381> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6380
master_link_status:up
...
connected_slaves:1
slave0:ip=127.0.0.1,port=6379,state=online,offset=5275,lag=1
...

复制原理

全量复制

  • slave连接到master后会向master发送一个psync命令 此命令有两个参数 分别为run_id与复制偏移量repl_offset 但若为第一次全量复制 则slave是不知道master的run_id与repl_offset的 因此在第一次的全量复制中 slave会向master发送psync ? -1命令 而当master接收到此命令后就会知道这是此slave的第一次全量复制 就会向此slave发送run_id与repl_offset 而slave则会保存此信息以便之后的全量复制(实际上只要是配置主从关系(包含重新配)都是第一次全量复制 即slave向master发送的命令一定为psync ? -1 而这里引入"第一次全量复制"与"参数run_id与repl_offset"的概念是为了给部分复制做铺垫) 同时master还会执行bgsave命令以生成dump.rdb文件并启动复制缓存区(作用同aof重写缓存区)(也可看成是一个队列)(从master执行bgsave命令开始到生成dump.rdb文件再到将此文件发送给slave完成为止的过程中 若master有写操作 则redis会记录下此操作并追加到复制缓存区中 当master将dump.rdb文件发送完成给slave之后就会将复制缓存区中的内容发送给slave 而slave则会清空自己的数据并加载master发送过来的dump.rdb文件与复制缓存区中的内容以完成同步)(在slave清空自己的数据并加载dump.rdb文件与复制缓存区中的内容的过程中 若master还有写操作 则slave同步此操作是增量复制)

    #查看run_id
    info server
    # Server
    ...
    run_id:8ca6d65f973fe88cb5253f8e311cb5dddb3c43b6
    ...
    
    #查看repl_offset
    info replication
    # Replication
    ...
    master_repl_offset:18183
    ...
    
  • 开销很大

    • bgsave命令需要额外的内存以fork()出子进程
    • master的dump.rdb文件通过网络传输给slave的时间
    • slave清空自己的数据的时间
    • slave加载dump.rdb文件的时间
    • 若启动了aof 则还会有aof重写的开销

增量复制

  • master将新接收到的所有写操作按顺序依次传给slave执行以完成同步

部分复制

  • 仅redis2.8及之后的版本有部分复制
  • 在slave因网络等的原因与master断开后 若再进行一次全量复制则会变得"既耗时又耗力"(即全量复制的开销很大) 因此redis会启动复制缓存区并将之后的在master之中的写操作追加到复制缓存区中 直至slave重连后slave会发送psync run_id repl_offset命令给master 而master也会知道这是非第一次全量复制 这时若repl_offset的值在复制缓存区的范围中 则master就会将从repl_offset开始直至队列的末尾的数据传给slave以完成同步(此为部分复制 降低了开销) 而若repl_offset的值不在复制缓存区的范围中 则说明slave已经断开许久了 丢失了太多数据了 这时就只能进行全量复制了
  • 可以通过改变复制缓存区的容量大小(默认为1mb)来调整是否希望slave断开许久后仍能使用部分复制 即调整复制缓存区的范围以是否希望让repl_offset的值位于此范围中

哨兵模式

  • 即从机上位的自动版
  • 哨兵通过发送命令来监控主从服务器的运行状态 若监测到master发生了故障则哨兵会通过投票数自动的将某一个slave转换成master 然后通过消息发布与订阅以通知其它的slave让它们切换原master成现master
  • 哨兵执行命令是一个独立的进程
  • 一个哨兵进程来监控redis服务器可能会出现问题 因此可以使用多哨兵模式
  • 哨兵模式的三个任务
    • 监控
    • 提醒
    • 故障自动迁移
#在windows中创建redis_sentinel.conf文件并编辑
#以下代码表示哨兵会监控ip为127.0.0.1port为6379的服务器 若此服务器发生故障后会进行投票 当投票数>=1时自动进行切换主从关系的操作
sentinel monitor dc-redis 127.0.0.1 6379 1

#上传到linux中
rz

#新开一个vm选项卡以前台启动哨兵
redis-sentinel redis_sentinel.conf

#测试
#主机宕机
[root@localhost redis-7.0.4]# redis-cli -p 6379 shutdown
[1]+  Done                    redis-server redis6379.conf

...
#转换master6379成6381
3711:X 07 Aug 2022 10:36:42.342 # +switch-master dc-redis 127.0.0.1 6379 127.0.0.1 6381
#切换6380的主从关系
3711:X 07 Aug 2022 10:36:42.342 * +slave slave 127.0.0.1:6380 127.0.0.1 6380 @ dc-redis 127.0.0.1 6381
#切换6379的主从关系
3711:X 07 Aug 2022 10:36:42.342 * +slave slave 127.0.0.1:6379 127.0.0.1 6379 @ dc-redis 127.0.0.1 6381
...

#重进6380客户端
127.0.0.1:6380> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6381
master_link_status:up

#重进6381客户端
127.0.0.1:6381> info replication
# Replication
role:master
connected_slaves:1
slave0:ip=127.0.0.1,port=6380,state=online,offset=27898,lag=1

#主机重连后变从机
[root@localhost redis-7.0.4]# redis-server redis6379.conf &
[1] 3843
[root@localhost redis-7.0.4]# redis-cli -p 6379
127.0.0.1:6379> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6381
master_link_status:up

127.0.0.1:6381> info replication
# Replication
role:master
connected_slaves:2
slave0:ip=127.0.0.1,port=6380,state=online,offset=49516,lag=0
slave1:ip=127.0.0.1,port=6379,state=online,offset=49516,lag=0

总结

  • redis主从复制的最大的缺点就是延迟 在slave备份master的写操作这一过程中是有延迟的 且当系统/网络繁忙/slave数量增加时 这一延迟问题会更加严重

Jedis

  • 是redis官方推荐的在java中操作redis的技术

  • 步骤

    • 新建maven模块并在pom文件中添加jedis依赖

      <!-- jedis依赖 -->
      <dependency>
          <groupId>redis.clients</groupId>
          <artifactId>jedis</artifactId>
          <version>4.2.3</version>
      </dependency>
      
    • 关闭linux的防火墙、编辑redis配置文件、重启redis服务

      #关闭linux的防火墙
      systemctl stop firewalld
      
      #编辑redis配置文件
      #注释掉bind参数以让外部的任意ip都能访问到此redis
      #关闭保护模式以让外部ip可以直接访问到此redis
      protected-mode no
      
      #重启redis服务
      redis-cli -p 6379 shutdown
      redis-server redis6379.conf &
      
    • 在idea中编写代码以在java中远程操控redis

      public static void main(String[] args) {
          //连接redis
          Jedis jedis = new Jedis("192.168.190.128", 6379);
      
          //测试redis命令
          System.out.println(jedis.ping());
      
          System.out.println(jedis.rpush("l1", "a", "b", "c"));
          Set<String> set = jedis.keys("*");
          for (String s : set) {
              System.out.println(s);
          }
          List<String> list = jedis.lrange("l1", 0, -1);
          for (String s : list) {
              System.out.println(s);
          }
      }
      
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值