Redis基础入门

Redis5:讲义位置

Redis中文教程网站

redis命令手册

本机Redis安装路径

第一章 NOSQL概述

  • 什么是NOSQL?

    • NoSQL = Not Only SQL(不仅仅是 SQL) ,也解释为 non-relational(非关系型数据库)
    • 在NoSQL 数据库中数据之间是无联系的,无关系的。数据的结构是松散的,可变的
  • 为什么用NOSQL?

    • 关系型数据库的是IO 密集的应用。无法应对每秒上万次的读写请求,无法处理大量集中的高并发操作
    • 关系型数据库无法简单地通过增加硬件、服务节点来提高系统性能。
  • NOSQL的劣势?

    • 不支持标准的SQL,没有公认的SQL标准
    • 没有事务
    • 没有丰富的数据类型
    • 没有关系型数据库的约束,大多数也没有索引的概念
  • Redis是一个key-value结构的数据库,可以把redis看做是一个大的Map

    • Redis的作用:

      1. Redis是一个数据库,存储数据,可以对数据执行crud
      2. Redis可以作为Cache(缓存)使用,提供数据的查询的性能。
        • Redis作为缓存应该存储用户经常访问的数据。Redis主要提升的是查询的性能(select)
      3. 作为分布式锁
    • Cache(缓存):存在内存中的数据

      • user发起请求----控制器----Service----Dao—数据库
    • 使用Cache

      • user发起请求----控制器----Service----Redis

      • user发起请求----控制器----Service—Redis(没有数据)

        ​ Dao—数据库

  • Redi客户端:

    • redis-cli : redis自带的客户端,使用灵活,支持很多的命令
      • 使用方式 ./redis-cli , 默认是连接到 127.0.0.1 上面的6379的redis
      • 访问其他的服务器上的redis数据:
      • ./redis-cli -h ip -p port
      • ./redis-cli -h 192.168.1.10 -p 6379
    • 远程桌面客户端
    • .编程客户端
  • Redis客户端的功能:

  • 发送redis命令,编写redis命令

  • 显示服务端的处理结果,显示数据

  • 用户开发人员—在客户端中编写redis-----客户端把命令发送给服务端

    从服务端把处理结果发给—客户端—把数据呈现给用户

Redis的应用场景
  1. 计数器
    • incr指令
    • 使用场景:商品的浏览量,视频的播放次数
  2. 社交互动
    • 散列,集合
    • app中点赞,关注,踩等功能
  3. 排行榜
    • zset
    • 销量排行
  4. 最新列表
    • 通过 lpush 插入 id作为关键字,通过 ltrim 限制列表的数量。直接根据id查找对应的内容即可
  5. 分布式会话
    • 集群模式下,会搭建以redis 等内存数据库为中心的 会话服务,它不在由容器管理,而是由会话服务及内存数据库管理
  6. 高并发读写
    • 特别适合将方法的运行结果放入缓存,加快查询性能

第二章 Redis安装及使用

1. window下安装Redis

本机Redis安装路径

有三个文件

  1. redis-server.exe:服务端程序,首先启动
  2. redis-cli.exe:客户端程序, 启动客户端后,执行redis命令
  3. redis.windows.conf:redis的配置文件

使用redis

  1. 启动redis的服务端

    • 双击执行 redis-server.exe,启动的redis默认的端口是 6379
  2. 启动redis客户端,双击执行 redis-cli.exe

    • 默认是连接本机 127.0.0.1上的redis,端口是6379

第一个命令 ,创建字符类型的数据

  • set key value : 等同于 insert into

  • get key : 获取key的值, 等同于select

2. Linux下安装Redis

  1. 获取redis的安装 redis-4.0.13.tar.gz(redis.io)

  2. 上传安装文件到linux

  3. 解压缩文件 tar -zxvf redis-4.0.13.tar.gz -C /usr/local

  4. 开始安装gcc , yum -y install gcc

  5. 使用gcc 编译redis的源文件, 使用make
    在redis的安装根目录下,src的上级目录中执行 make

启动redis

  1. 前台启动
    ./redis-server redis.conf文件的路径

​ /usr/local/redis-4.0.13/src/redis-server
​ /usr/local/redis-4.0.13/redis.conf

  1. 后台启动
    相对路径 ./redis-server …/redis.conf &
    绝对路径 /usr/local/redis-4.0.13/src/redis-server /usr/local/redis-4.0.13/redis.conf &

启动客户端

​ 绝对路径 /usr/local/redis-4.0.13/src/redis-cli

关闭redis服务端:

  • 先进入redis客户端,再执行shutdown (先完成数据操作,然后再关闭)
  • 直接杀掉进程(不会考虑是否有数据正在执行操作,直接关闭应用)
    • ps -ef | grep redis
    • kill -9 redis的pid

3. Ubuntu安装redis

  1. sudo apt install redis-server
    
  2. 设置密码

    sudo vim /etc/redis/redis.config
    # 找到 # requirepass foobared这行,将 # 去掉。设置密码
    requirepass 123
    
  3. 开启远程访问

    #1. 关闭防火墙(上网查)
    
    #2. 
    sudo vim /etc/redis/redis.config
    #2.1 使用注释符号 # 注释掉bind 127.0.0.1这行
    
    #2.2 将 protected-mode yes 改为 no
    protected-mode no
    

redis服务控制命令

/etc/init.d/redis-server start  # 启动服务端。(后台启动:通过 ps -ef | gref redis命令可以查看)
# 在 /bin目录下执行 ./redis-cli 即可启动客户端
/etc/init.d/redis-server stop # 关闭服务端
/etc/init.d/redis-server restart #重启

Ubuntu上的 Redis的安装路径在 /bin目录下

​ redis.config文件目录在:/etc/redis

RDM远程访问

在window通过远程访问的方式利用图形用户界面 访问虚拟机上的 redis

  • 输入虚拟机的ip地址,测试显示成功即可
image-20240226205430372

4. redis基本操作命令

用xshell(redis的服务端已开,客户端需手动打开,在 /bin/redis-cli)

讲义2.5节

1. 查看状态: ping

输入 ping,redis 给我们返回 PONG,表示 redis 服务运行正常

2. 查看当前数据库中key的数目: dbsize
3. 修改数据库个数

Redis 默认使用 16 个库,从 0 到 15。 对数据库个数的修改,在 redis.conf 文件中

databases 16

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

4. 切换库: select index

index 取值 [0, 15]

使用 db(index) 数据库

5. 删除当前库的数据: flushdb
6. 退出当前客户端: exit
7. Tab键代码提示

5. redis的key的操作命令

讲义2.6节

1. keys

keys pattern

  • 作用:查找所有符合模式 pattern 的 key。pattern 可以使用通配符。

通配符:

  • *:表示 0~多个字符,例如:keys * 查询所有的 key。
  • 尽量少用 keys *,实际项目中数据会非常多,会严重影响性能。redis是单线程的(数据存放在内存中),会一直查询key,直到所有的key查询完毕,期间其他命令都不能够被执行
  • 使用方法:keys wo*,匹配 word , wood
  • ?:表示单个字符,例如:keys wo?d , 匹配 word , wood
2. exists

语法:exists key [key…]

作用:判断 key 是否存在

返回值:整数

  • 使用单个key,存在 key 返回 1,其他返回 0。

  • 使用多个 key,返回这些key中存在的 key 的数量

3. expire

语法:expire key seconds

作用:设置 key 的生存时间,超过时间,key 自动删除(前提是key必须存在)。单位是秒。

返回值:设置成功返回数字 1,其他情况是 0 。

set redlight 张三 
expire redlight 5  # 5s后 key redlight自动删除
ttl redlight # 查看key的剩余生存时间
  • 用于实现验证码的倒计时操作

    set code1234 1234  # 验证码为1234
    expire code1234 60 # 设置验证码的存活时间
    
    ttl code1234 # 查询验证码的剩余存活时间:>=0 key没有过期, 验证码是有效;<0  key过期,验证码失效的
    
4. ttl

语法:ttl key

作用:以秒为单位,返回 key 的剩余生存时间(ttl: time to live)

返回值:

  • -1 :没有设置 key 的生存时间, key 永不过期。

  • -2:key 不存在

  • 数字:key 的剩余时间,秒为单位

5. type

语法:type key

作用:查看 key 所存储值value的数据类型

返回值:字符串表示的数据类型

  • none (key 不存在)
  • string (字符串)
  • list (列表)
  • set (集合)
  • zset (有序集)
  • hash (哈希表)
6. del

语法:del key [key…]

作用:删除存在的 key,不存在的 key 忽略。

返回值:数字,删除的 key 的数量。

第三章 Redis数据类型(5种)操作命令

讲义第3节

以下内容,我使用了linux上的redis(本机在Ubuntu),因为linux上的redis服务已经在后台启动了;然后使用RDM远程访问Ubuntu上的redis

1. String类型

  • 字符串类型是 Redis 中最基本的数据类型,它能存储任何形式的字符串,包括二进制数据,序列化后的数据,JSON 格式数据。

  • 一个String类型最多能够存储512M数据

  • redis区分大小写。key大小写不同是俩个不同的key

1. set命令
  • 将字符串值 value 设置到 key 中

  • 语法:set key value

  • 向已经存在的 key 设置新的 value,会覆盖原来的值

查看已经插入的 key:keys *

set k2 "linux"  # k2 = "linux"
set k2 "hello world"  # k2 = "hello world"

#存放json字符串(将java对象转为json字符串存到redis)
set k3 "{'name':'lisi', 'age':'20'}"
2. get
  • 获取 key 中设置的字符串值
  • 语法:get key
  • 返回值:对应key的value。若没有该key,返回nil(nil == null)
3. incr
  • 将 key 中储存的数字值加 1,并返回 value 加1后的值

    如果 key 不存在,则会创建key,并且 key 的值先被初始化为 0 再执行incr 操作(只能对数字类型的数据操作)

  • 语法:incr key

    set index 1 
    incr index 
    get index
    
  • 可作为计算器使用

    • 比如:获取当前系统中登录用户的数量
4. decr
  • 将 key 中储存的数字值减1,

    如果 key 不存在,则么创建key,并且 key 的值先被初始化为 0。再执行 decr 操作(只能对数字类型的数据操作)

  • 语法:decr key

5. append

语法:append key value

说明:如果 key 存在,则将 value 追加到 key 原来旧值的末尾

​ 如果 key 不存在,则将 key 设置值为 value

返回值:追加字符串之后的总长度

6. strlen
  • 语法:strlen key
  • 说明:返回 key 所储存的字符串值的长度
  • 返回值:
    • 如果key存在,返回字符串值的长度
    • key不存在,返回0
7. getrange
  • 语法:getrange key start end

  • 作用:获取 key 中字符串值从下标 start 开始到 end 结束的子字符串,包括 start 和 end, 负数表示从字符串的末尾开始,-1 表示最后一个字符(很像python)。【截取字串 [start, end],无论正负】

  • 返回值:截取的子字符串。

    set school bjpowernode
    getrange school 7  10
    getrange school -4 -1  # 这里不能写成 -1 -4
    getrange school 0 -1  # 相当于 get school
    
8. setrange
  • 语法:setrange key offset value
  • 说明:从下标 offset 开始,用 value 覆盖(替换)key 的存储的值。不存在的 key 做空白字符串。【替换字串】
  • 返回值:修改后的字符串的长度
9. mset
  • 语法:mset key value [key value…]
  • 说明:同时设置一个或多个 key-value 对
  • 返回值:OK
10. mget
  • 语法:mget key [key …]
  • 作用:获取所有(一个或多个)给定 key 的值
  • 返回值:包含所有 key 的列表。不存在的key返回nil

2. hash类型

hash类型类似于:key field value[field value…]。类似于k v(v = <k, v>[<k, v>…]。即 Map<K, Map<K, V>>

hash类型一般用于存储对象

// 对象封装的类
class Student {

   private String name;
   private String age;
   private String sex;
}

// data代表一个对象,封装了一个对象的属性
Map<String,String> data = new HashMap();
data.put("name","zs");
data.put("age","20);
data.put("sex","男");
         
// map存储这类对象的集合         
Map<String,Map<String,String> map = new ..
map.put("stu",data);
         
map:就是redis中的hash类型
user:叫做hash类型的key
name,age,sex:叫做field         
1. hset
  • 语法:hset hash表的key field value

  • 作用:将哈希表 key 中的域 field 的值设为 value,

    如果 key 不存在,则新建 hash 表,执行赋值。如果有 field,则覆盖值。

  • 返回值:

    • 如果 field 是 hash 表中新 field,且设置值成功,返回 1
    • 如果 field 已经存在,旧值覆盖新值,返回 0
  • hash表中一个key可以包含多个 field value(键值对),相当于Map集合中V的类型还是一个Map,即V还是一个键值对

2. hget
  • 语法:hget key field
  • 作用:获取哈希表 key 中给定域 field 的值
  • 返回值:field 域的值,如果 key 不存在或者 field 不存在返回 nil
3. hmset
  • 语法:hmset key field value [field value…]

  • 说明:同时将多个 field-value (域-值)设置到哈希表 key 中,此命令会覆盖已经存在的 field,

    hash 表 key 不存在,创建空的 hash 表,执行 hmset.

  • 返回值:设置成功返回 ok,如果失败返回一个错误

4. hmget
  • 语法:hmget key field [field…]
  • 作用:获取哈希表 key 中一个或多个给定域的值(给多个键值对赋值)
  • 返回值:返回和 field 顺序对应的值,如果 field 不存在,返回 nil
5. hgetall
  • 语法:hgetall key
  • 作用:获取哈希表 key 中所有的域和值
  • 返回值:以列表形式返回 hash 中域和域的值,key 不存在,返回空 hash
6. hdel
  • 语法:hdel key field [field…]
  • 作用:删除哈希表 key 中的一个或多个指定域 field,不存在 field 直接忽略
  • 返回值:成功删除的 field 的数量
7. hkeys
  • 语法:hkeys key
  • 作用:查看哈希表 key 中的所有 field 域
  • 返回值:包含所有 field 的列表,key 不存在返回空列表
8. hvals
  • 语法:hvals key
  • 作用:返回哈希表 中所有域的值
  • 返回值:包含哈希表所有域值的列表,key 不存在返回空列表
9. hexists
  • 语法:hexists key field
  • 作用:查看哈希表 key 中,给定域 field 是否存在
  • 返回值:如果 field 存在,返回 1,其他返回 0

3. list

1. lpush,lpop
  • 语法:lpush key value [value…]
  • 作用:将一个或多个值 value 插入到列表 key 的表头(最左边),从左边开始加入值,从左到右的顺序依次插入到表头
  • 返回值:数字,新列表的长度
2. rpush,rpop
  • 语法:rpush key value [value…]
  • 作用:将一个或多个值 value 插入到列表 key 的表尾(最右边),各个 value 值按从左到右的顺序依次插入到表尾
  • 返回值:数字,新列表的长度

rpop:从list右边弹出数据显示(会删除)

3. lrange
  • 语法:lrange key start stop
  • 作用:获取列表 key 中指定区间内的元素,0 表示列表的第一个元素,以 1 表示列表的第二个元素;start , stop 是列表的下标值,也可以负数的下标, -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。start ,stop 超出列表的范围不会出现错误。
  • 返回值:指定区间的列表

lrange key 0 -1 获取list中所有元素

4. lindex
  • 语法:lindex key index
  • 作用:获取列表 key 中下标为指定 index 的元素,列表元素不删除,只是查询。0 表示列表的第一个元素,以 1 表示列表的第二个元素;start , stop 是列表的下标值,也可以负数的下标, -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。
  • 返回值:指定下标的元素;index 不在列表范围,返回 nil
5. llen
  • 语法:llen key
  • 作用:获取列表 key 的长度
  • 返回值:数值,列表的长度;key 不存在返回 0
6. lrem
  • 语法:lrem key count value
  • 作用:根据参数 count 的值,移除列表中与参数 value 相等的元素,
    • count >0 ,从列表的左侧向右开始移除 count个 与 value 相等的值
    • count < 0 从列表的尾部开始移除 -count个 与 value 相等的值
    • count = 0 移除表中所有与 value 相等的值。
  • 返回值:数值,移除的元素个数
7. lset
  • 语法:lset key index value
  • 作用:将列表 key 下标为 index 的元素的值设置为 value。
  • 返回值:设置成功返回 ok ; key 不存在或者 index 超出范围返回错误信息
8. linsert
  • 语法:linsert key BEFORE|AFTER pivot value
  • 作用:将值 value 插入到列表 key 当中位于值 pivot 之前或之后的位置。key 不存在,pivot不在列表中,不执行任何操作。
  • 返回值:命令执行成功,返回新列表的长度。没有找到 pivot 返回 -1, key 不存在返回 0。

4. set

唯一 无序

1. sadd
  • 语法:sadd key member [member…]
  • 作用:将一个或多个 member 元素加入到集合 key 当中,已经存在于集合的 member 元素将被忽略,不会再加入。
  • 返回值:加入到集合的新元素的个数。不包括被忽略的元素。
2. smembers
  • 语法:smembers key
  • 作用:获取集合 key 中的所有成员元素,不存在的 key 视为空集合
3. sismember
  • 语法:sismember key member
  • 作用:判断 member 元素是否是集合 key 的成员
  • 返回值:member 是集合成员返回 1,其他返回 0 。
4. scard
  • 语法:scard key
  • 作用:获取集合里面的元素个数
  • 返回值:数字,key 的元素个数。其他情况返回 0 。
5. srem
  • 语法:srem key member [member…]
  • 作用:删除集合 key 中的一个或多个 member 元素,不存在的元素被忽略。
  • 返回值:数字,成功删除的元素个数,不包括被忽略的元素。
6. srandmember
  • 语法:srandmember key [count]
  • 作用:
    • 只提供 key,随机返回集合中一个元素,元素不删除,依然在集合中;
    • 提供了 count时
      • count 正数, 返回包含 count 个数元素的集合,集合元素各不相同。
      • count 是负数,返回一个 count 绝对值的长度的集合,集合中元素可能会重复多次。
  • 返回值:一个元素;多个元素的集合
7. spop
  • 语法:spop key [count]
  • 作用:随机从集合中删除一个元素, count 是删除的元素个数。
  • 返回值:被删除的元素,key 不存在或空集合返回 nil

5. 有序集合zset

zset 的每个元素都会关联一个分数(分数可以重复),redis 通过分数来为集合中的成员进行从小到大的排序。如果分数相同,按集合中的成员的字典顺序排序

1. zadd
  • 语法:zadd key score member [score member…]
  • 作用:将一个或多个 member 元素及其 score 值加入到有序集合 key 中,如果 member 存在集合中,则更新值;score 可以是整数或浮点数
  • 返回值:数字,新添加的元素个数
2. zrange
  • 语法:zrange key start stop [WITHSCORES]
  • 作用:查询有序集合,指定区间的内的元素。集合成员按 score 值从小到大来排序。start,stop 都是从 0 开始。0 是第一个元素,1 是第二个元素,依次类推。以 -1 表示最后一个成员,-2 表示倒数第二个成员。WITHSCORES 选项让 score 和 value 一同返回。
  • 返回值:自定区间的成员集合
3. zrevrange
  • 语法:zrevrange key start stop [WITHSCORES]
  • 作用:返回有序集 key 中,指定区间内的成员。其中成员的位置按 score 值递减(从大到小)来排列。其它同 zrange 命令。
  • 返回值:自定区间的成员集合
4. zrem
  • 语法:zrem key member [member…]
  • 作用:删除有序集合 key 中的一个或多个成员,不存在的成员被忽略
  • 返回值:被成功删除的成员数量,不包括被忽略的成员。
5. zcard
  • 语法:zcard key
  • 作用:获取有序集 key 的元素成员的个数
  • 返回值:key 存在返回集合元素的个数, key 不存在,返回 0
6. zrangebyscore
  • 语法:zrangebyscore key min max [WITHSCORES ] [LIMIT offset count]

  • 作用:获取有序集 key 中,所有 score 值介于 min 和 max 之间(包括 min 和 max)的成员,有序成员是按递增(从小到大)排序。min ,max 是包括在内,使用符号"( "表示不包括。

    min, max 可以使用 -inf ,+inf 表示最小和最大。limit 用来限制返回结果的数量和区间(分页)。withscores 显示 score 和 value

  • 返回值:指定区间的集合数据

    zrangebyscore sal 2000 (4000 withscores # [2000, 4000)
    
7. zrevrangebyscore
  • 语法:zrevrangebyscore key max min [WITHSCORES ] [LIMIT offset count]

  • 作用:返回有序集 key 中, score 值介于 max 和 min 之间(默认包括等于 max 或 min )的所有

    的成员。有序集成员按 score 值递减(从大到小)的次序排列。其他同 zrangebyscore

8. zcount
  • 语法:zcount key min max
  • 作用:返回有序集 key 中, score 值在 min 和 max 之间(默认包括 score 值等于 min 或 max )的
  • 成员的数量

第四章 高级话题

讲义第4节

1. redis事务

  • 事务是指一系列操作步骤,这一系列的操作步骤,要么完全地执行,要么完全地不执行。类似于OS中的原子操作

  • redis是支持事务的,但redis的事务是不支持自动回滚的。

1. 事务操作的命令
1. multi
  • 语法: multi
  • 作用:标记一个事务的开始。事务内的多条命令会按照先后顺序被放进一个队列当中。
  • 返回值:总是返回 ok
2. exec
  • 语法:exec
  • 作用:执行所有事务块内的命令
  • 返回值:事务内的所有执行语句内容,事务被打断,返回 nil
3. discard
  • 语法:discard
  • 作用:取消事务,放弃执行事务块内的所有命令
  • 返回值:总是返回 ok
4. watch
  • 语法:watch key [key …]
  • 作用:监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,
  • 那么事务将被打断。
  • 返回值:总是返回 ok
5. unwatch
  • 语法:unwatch
  • 作用:取消 WATCH 命令对所有 key 的监视。如果在执行 WATCH 命令之后, EXEC 命令或 DISCARD 命令先被执行了的话,那么就不需要再执行 UNWATCH 了
  • 返回值:总是返回 ok
2.事务的实现
1. 正常执行
multi # 开启事务
sadd name zhangsan
sadd name lisi  # 命令入队
exec  # 提交事务
2.入队命令错误
  1. 事务执行exec之前,入队命令错误(一般是语法错误),会放弃事务。(事务块中所有指令都不执行)

    multi # 开启事务
    set name zhangsan
    get name 
    incr k1 k2  # 入对命令语法错误
    exec  # 提交事务。但事务块中所有命令都不执行
    
  2. 事务执行exec之后,命令错误(不是语法错误,是逻辑错误),事务会提交

    multi # 开启事务
    set name zhangsan
    # 该命令语法并没有错误,但在这个脚本块中,name的类型是string,lpop指令是操作list类型的数据的。执行的时候出错
    lpop name  # 执行失败
    exec # set name zhangsan 正常执行
    get name
    
    • 在 exec 执行后的所产生的错误,即使事务中有某个/某些命令在执行时产生了错误,事务中的其他命令仍然会继续执行。
    • redis在事务失败时不进行回滚,而是是继续执行余下的命令【与mysql事务的不同之处】

3. 放弃事务
set age 10
multi # 开启事务
set age 20
discard # 放弃事务
get age  # 还是10,事务没有执行
3.watch机制

与mysql的锁机制不同。为了提高程序的性能,并在一定程度上解决 多事务执行时数据不安全问题,redis采用watch机制

WATCH 机制原理:

  • WATCH 机制:使用 WATCH 监视一个或多个 key , 跟踪 key 的 value 修改情况,如果有key 的 value 值在事务 EXEC 执行之前被修改了,整个事务被取消(让别人先做,类似于多线程中的主动退让)。EXEC 返回提示信息,表示事务已经失败。

  • WATCH 机制使得事务 EXEC 变的有条件,事务只有在被 WATCH 的 key 没有修改的前提下才能执行。不满足条件,事务被取消。使用 WATCH 监视了一个带过期时间的键,那么即使这个键过期了,事务仍然可以正常执行

  • 大多数情况下,不同的客户端会访问不同的键,相互同时竞争同一 key 的情况一般都很少,watch 能很好解决数据冲突的问题。

何时取消key的监视

①WATCH 命令可以被调用多次。对键的监视从 WATCH 执行之后开始生效,直到调用 EXEC 为止。不管事务是否成功执行,对所有键的监视都会被取消。

②当客户端断开连接时,该客户端对键的监视也会被取消。

③UNWATCH 命令可以手动取消对所有键的监视

案例
  • 准备工作:使用xshell开启俩个连接(连接虚拟机)。并且俩个连接都打开redis客户端(模拟俩个请求)

  • A 客户端(红色):WATCH 某个 key,同时执行事务;B 客户端(黄色):对 A 客户端 WATCH 的 key 修改其 value 值。

    1) 在 A 客户端:set age 10
    2) 在 A 客户端监视age: watch age
    3) 在 A 客户端开启事务: multi
    4) 在 A 客户端修改:set age 20
    5) 在 B 客户端修改:set age 100
    6) 在 A 客户端提交事务:exec
    7) 在 A 客户端查看 age 值:get age
    结论:
    	A 客户端执行的事务没有提交,因为 WATCH 的 age 的值已经被修改了,所以A客户端放弃事务。(乐观锁)
    

2. 持久化

持久化可以理解为存储,就是将数据存储到一个不会丢失的地方,如果把数据放在内存中,电脑关闭或重启数据就会丢失,所以放在内存中的数据不是持久化的,而放在磁盘就算是一种持久化

redis 的数据存储在内存中,内存是瞬时的,如果 linux 宕机或重启,又或者 Redis 崩溃或重启,所有的内存数据都会丢失,为解决这个问题,Redis 提供两种机制对数据进行持久化存储,便于发生故障后能迅速恢复数据。

1. RDB(快照)
  • RDB:在指定的时间间隔内将内存中的数据集快照写入磁盘,数据恢复时将快照文件直接再读到内存
  • RDB 保存了在某个时间点的数据集(全部数据)。存储在一个二进制文件中,只有一个文件。默认是 dump.rdb。
  • RDB 技术非常适合做备份,可以保存最近一个小时,一天,一个月的全部数据。保存数据是在单独的进程中写文件,不影响 Redis 的正常使用。RDB 恢复数据时比其他 AOF 速度快。

如何实现?

  • RDB 方式的数据持久化,仅需在 redis.conf 文件中配置即可,默认配置是启用的。

  • 在配置文件 redis.conf 中搜索 SNAPSHOTTING,查找在注释开始和结束之间的关于 RDB的配置说明。配 SNAPSHOTTING 置地方有三处。

    1. 配置执行 RDB 生成快照文件的时间策略。(多个策略可以同时生效)
      • 对 Redis 进行设置,让它在“ N 秒内数据集至少有 M 个 key 改动”这一条件被满足时,自动保存一次数据集。
      • 配置格式:save ,如:
        • save 900 1
        • save 300 10
        • save 60 10000
    2. dbfilename:设置 RDB 的文件名,默认文件名为 dump.rdb
    3. dir:指定 RDB 文件的存储位置,默认是 ./ 当前目录(redis的安装目录的同级目录)
  • 配置步骤:

    1. 查看 ps -ef | grep redis ,如果 redis 服务启动,先停止(前面有讲)
    2. 修改 redis.conf 文件,修改前先备份,执行 cp redis.conf bak_redis.conf
    3. 查看默认启用的 RDB 文件:ll dump.rdb
    4. 编辑 redis.conf 增加 save 配置,修改文件名等。vim redis.conf

优劣

  • 优点:由于存储的是数据快照文件,恢复数据很方便,也比较快

  • 缺点:

    1. 会丢失最后一次快照以后更改的数据。如果你的应用能容忍一定数据的丢失,那么使用 rdb 是不错的选择【做缓存】;如果你不能容忍一定数据的丢失,使用 rdb 就不是一个很好的选择。
    2. 由于需要经常操作磁盘,RDB 会分出一个子进程。如果你的 redis 数据库很大的话,子进程占用比较多的时间,并且可能会影响 Redis 暂停服务一段时间(millisecond 级别),如果你的数据库超级大并且你的服务器 CPU 比较弱,有可能是会达到一秒。
2. AOF
  • Append-only File(AOF),Redis 每次接收到一条改变数据的命令时,它将把该命令写到一个 AOF 文件(文本文件)中(只记录写操作,读操作不记录),当 Redis 重启时,它通过执行 AOF 文件中所有的命令来恢复数据

如何实现

AOF 方式的数据持久化,仅需在 redis.conf 文件中配置即可

配置项:

  1. appendonly:默认是 no,改成 yes 即开启了 aof 持久化
  2. appendfilename:指定 AOF 文件名,默认文件名为 appendonly.aof
  3. dir :指定 RDB 和 AOF 文件存放的目录,默认是 ./
  4. appendfsync:配置向 aof 文件写命令数据的策略:
    • no:不主动进行同步操作,而是完全交由操作系统来做(即每 30 秒一次),比较快但不是很安全
    • always:每次执行写入都会执行同步,慢一些但是比较安全。
    • everysec:每秒执行一次同步操作,比较平衡,介于速度和安全之间。这是默认项。
  5. auto-aof-rewrite-min-size:允许重写的最小 AOF 文件大小,默认是 64M 。当 aof 文件大于 64M 时,开始整理 aof 文件,去掉无用的操作命令。缩小 aop 文件。

配置步骤

  1. 停止运行的 redis ,备份要修改的 redis.conf
  2. 查看 redis 安装目录/src 下有无 .aof 文件。默认是在 redis 的当前目录
  3. 编辑 redis.conf
    1. 设置 appendonly 为 yes 即可。
    2. 查看 appendfsync 的当前策略。
    3. 查看 appendfilname 的文件名称
  4. 在 redis 客户端执行写入命令
  5. 查看 aof 文件

总结

  1. append-only 文件是另一个可以提供完全数据保障的方案;

  2. AOF 文件会在操作过程中变得越来越大。比如,如果你做一百次加法计算,最后你只会在数据库里面得到最终的数值,但是在你的 AOF 里面会存在 100 次记录,其中 99 条记录对最终的结果是无用的;但 Redis 支持在不影响服务的前提下在后台重构 AOF 文件,让文件得以整理变小

  3. 可以同时使用这两种方式,redis 默认优先加载 aof 文件(aof 数据最完整);

3. 主从复制

​ 通过持久化功能,Redis 保证了即使在服务器重启的情况下也不会丢失(或少量丢失)数据,但是由于数据是存储在一台服务器上的,如果这台服务器出现故障,比如硬盘坏了,也会导致数据丢失。

为了避免单点故障,我们需要将数据复制多份部署在多台不同的服务器上,即使有一台服务器出现故障其他服务器依然可以继续提供服务。这就要求当**一台服务器上的数据更新后,自动将更新的数据同步到其他服务器上,**那该怎么实现呢?Redis 的主从复制。

image-20240308150003585

​ Redis 提供了复制(replication)功能来自动实现多台 redis 服务器的数据同步(每天 19点新闻联播,基本从 cctv1-8,各大卫视都会播放)

​ 我们可以通过部署多台 redis,并在配置文件中指定这几台 redis 之间的主从关系主负责写入数据,同时把写入的数据实时同步到从机器,这种模式叫做主从复制,即master/slave,并且 redis 默认 master 用于写,slave 用于读,向 slave 写数据会导致错误

1. 读写分离(主从复制)
  • 主负责写入数据,从负责读取数据。主从关系是在 redis的配置文件中指定的
  • 主可以写,可以读;从只能读不能写
1. 主从复制的实现

见讲义

2. 容灾处理
  • 当 Master 服务出现故障,需手动将 slave 中的一个提升为 master,剩下的 slave 挂至新的master 上(冷处理:机器挂掉了,再处理)
  • 命令:
    • slaveof no one,将一台 slave 服务器提升为 Master (提升某 slave 为 master)
    • slaveof 127.0.0.1 6382 (将 slave 挂至新的 master 上)
  • 后续会介绍 哨兵模式,由哨兵自动进行容灾处理
3. 相关命令
  • 进入客户端需指定端口:./redis-cli -p 6380
  • 不配置启动默认都是主 master
  • info replication 查看 redis 服务器所处角色
4. 总结

1、一个 master 可以有多个 slave

2、slave 下线,读请求的处理性能下降

3、master 下线,写请求无法执行

4、当 master 发生故障,需手动将其中一台 slave 使用 slaveof no one 命令提升为 master,其它 slave 执行 slaveof 命令指向这个新的 master,从新的 master 处同步数据

5、主从复制模式的故障转移需要手动操作,要实现自动化处理,这就需要 Sentinel 哨兵,实现故障自动转移

2. 高可用 Sentinel哨兵

​ 上面一节中,如果要进行容灾处理,必须 运维人员手动处理,更改redis的配置文件。例如一个主redis故障,需要手动将一个从redis 设置为主redis,并修改其他的从redis的配置。而哨兵可以自动进行上述操作

Sentinel 哨兵是 redis 官方提供的高可用方案,可以用它来监控多个 Redis 服务实例的运行情况。Redis Sentinel 是一个运行在特殊模式下的 Redis 服务器。Redis Sentinel 是在多个Sentinel 进程环境下互相协作工作的。

  • Sentinel哨兵在 redis安装目录下(与redis-service处于同一位置),名为 redis-sentinel的程序

  • 哨兵程序的配置文件在 redis.conf的同级目录下,默认名为 sentinel.conf

Sentinel 系统有三个主要任务:

  • 监控:Sentinel 不断的检查主服务和从服务器是否按照预期正常工作。
  • 提醒:被监控的 Redis 出现问题时,Sentinel 会通知管理员或其他应用程序。
  • 自动故障转移:监控的主 Redis 不能正常工作,Sentinel 会开始进行故障迁移操作。将一个从服务器升级新的主服务器。让其他从服务器挂到新的主服务器。同时向客户端提供新的主服务器地址。【若之前的主redis重新工作,则该主redis变为 从redis】
image-20240308154749247

​ 多个哨兵监控着主redis,当多数redis(少数服从多数)任务主redis已经无法工作时,就会进行故障迁移操作(哨兵有多个,奇数个。默认端口 26379)

监控

1)Sentinel 会不断检查 Master 和 Slave 是否正常

2)如果 Sentinel 挂了,就无法监控,所以需要多个哨兵,组成 Sentinel 网络,一个健康的Sentinel 至少有 3 个 Sentinel 应用。彼此在独立的物理机器或虚拟机。

3)监控同一个 Master 的 Sentinel 会自动连接,组成一个分布式的 Sentinel 网络,互相通信并交换彼此关于被监 控服务器的信息

4)当一个 Sentinel 认为被监控的服务器已经下线时,它会向网络中的其它 Sentinel 进行确认,判断该服务器是 否真的已经下线

5)如果下线的服务器为主服务器,那么 Sentinel 网络将对下线主服务器进行自动故障转移,通过将下线主服务器的某个从服务器提升为新的主服务器,并让其从服务器转移到新的主服务器下,以此来让系统重新回到正常状态

6)下线的旧主服务器重新上线,Sentinel 会让它成为从,挂到新的主服务器下

总结

  • 主从复制,解决了读请求的分担,从节点下线,会使得读请求能力有所下降,Master 下线,写请求无法执行

  • Sentinel 会在 Master 下线后自动执行故障转移操作,提升一台 Slave 为 Master,并让其它Slave 成为新 Master 的 Slave

4. 安全设置

1. 设置密码

Redis 默认是没有密码的,

配置文件中设置密码:

  • 修改 redis.conf。 使用 vim 命令,找到 requirepass 行
  • 在其下方输入 requirepass “密码”

访问有密码的redis:

  1. 在连接客户端时使用 -a 密码。如 ./redis-cli -h ip -p port -a password
  2. 在连接到客户端后,使用命令 “auth 密码”,命令执行成功后,可以正常使用 Redis
2. 修改默认端口

redis.conf 中修改 port 6379,将其修改为自己指定的端口(可随意),端口 1024 是保留给操作系统使用的。用户可以使用的范围是 1024-65535

第五章 Jedis操作Redis

更多的内容讲义第五章

1. Jedis的下载

2. java应用使用Jedis

通过maven创建quickstart

  1. 添加依赖

    <dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
        <version>2.9.3</version>
    </dependency>
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-pool2</artifactId>
        <version>2.6.0</version>
    </dependency>
    
  2. 编写代码

    springboot为我们提供了两个模板类操作Redis数据库,此处不在讲

    1. 使用 Jedis操作redis。

      Jedis 几乎涵盖了 Redis 的所有命令。操作 Redis 的命令在 Jedis 中以方法的形式出现。jedis 完全兼容 redis 2.8.x and 3.x.x

      public class StringRedisPrimary {
          public static void main(String[] args) {
              // 创建redis对象,通过Jedis的方法,操作 Redis数据
              String host = "192.168.230.138";  // redis所在的虚拟机的ip地址
              int port = 6379;  // redis运行的端口
              Jedis jedis = new Jedis(host, port);
              // 如果redis设置了密码,要执行下述语句
              // jedis.auth("123");
      
              // 通过jedis的方法操作Redis
              jedis.set("break","banana");
      
              // 获取数据
              String value = jedis.get("break");
              System.out.println(value);
      
              jedis.mset("lunch", "红烧肉", "dinner", "面条");
              List<String> values = jedis.mget("break", "lunch", "dinner");
              values.forEach(v -> System.out.println(v));
      
              // redis当缓存:查询id=1的student,key=student:1
              // 先查redis,查不到就去查数据库
              if (jedis.exists("student")) {
                  String student = jedis.get("student");
              } else {
                  // 访问数据库,得到student对象
                  // 把student对象转换成json字符串,并将数据保存到redis中
                  jedis.set("student:1", "{student}");
              }
          }
      }
      
    2. 使用commons-pool2连接池操作redis

      Jedis 对象并不是线程安全的,在多线程下使用同一个 Jedis 对象会出现并发问题。为了避免每次使用 Jedis 对象时都需要重新构建,Jedis 提供了 JedisPool。JedisPool 是基于Commons Pool 2 实现的一个线程安全的连接池

      创建线程池对象的工具类

      public class RedisUtil {
          // 线程池对象
          private static JedisPool pool;
      
          // 创建线程池
          public static JedisPool open(String host, int port) {
              if (pool == null) {
                  // 在配置器中设置线程池的参数
                  JedisPoolConfig config = new JedisPoolConfig();
                  // 最大线程数量
                  config.setMaxTotal(100);
                  // 设置空闲数
                  config.setMaxIdle(2);
                  // 设置检查项为true,避免null的情况(确保拿到的线程对象一定可用)
                  config.setTestOnBorrow(true);
                  // 创建 JedisPool
                  pool = new JedisPool(config, host, port, 6000); // 超时时间,密码
              }
              return pool;
          }
      
          // 关闭线程池
          public static void close() {
              if (pool!=null) {
                  pool.close();
              }
          }
      }
      

      使用

      package org.example;
      
      import redis.clients.jedis.Jedis;
      import redis.clients.jedis.JedisPool;
      
      import java.util.List;
      
      public class StringRedisPrimary {
          public static void main(String[] args) {
              // 创建redis对象,通过Jedis的方法,操作 Redis数据
              String host = "192.168.230.138";  // redis所在的虚拟机的ip地址
              int port = 6379;  // redis运行的端口
              Jedis jedis = null;
              JedisPool pool = null;
              try {
                  // 通过线程池对象获取线程对象
                  pool = RedisUtil.open(host, port);
                  jedis = pool.getResource();
                  
                  // 这里写操作redis数据库的语句
              }finally {
                  // 把使用完毕的jedis放回到线程池中,让其他的客户端使用
                  if (jedis != null) {
                      jedis.close();
                  }
          }
      }
      
       // 最大线程数量
              config.setMaxTotal(100);
              // 设置空闲数
              config.setMaxIdle(2);
              // 设置检查项为true,避免null的情况(确保拿到的线程对象一定可用)
              config.setTestOnBorrow(true);
              // 创建 JedisPool
              pool = new JedisPool(config, host, port, 6000); // 超时时间,密码
          }
          return pool;
      }
      
      // 关闭线程池
      public static void close() {
          if (pool!=null) {
              pool.close();
          }
      }
      

      }

      
      使用
      
      ```java
      package org.example;
      
      import redis.clients.jedis.Jedis;
      import redis.clients.jedis.JedisPool;
      
      import java.util.List;
      
      public class StringRedisPrimary {
          public static void main(String[] args) {
              // 创建redis对象,通过Jedis的方法,操作 Redis数据
              String host = "192.168.230.138";  // redis所在的虚拟机的ip地址
              int port = 6379;  // redis运行的端口
              Jedis jedis = null;
              JedisPool pool = null;
              try {
                  // 通过线程池对象获取线程对象
                  pool = RedisUtil.open(host, port);
                  jedis = pool.getResource();
                  
                  // 这里写操作redis数据库的语句
              }finally {
                  // 把使用完毕的jedis放回到线程池中,让其他的客户端使用
                  if (jedis != null) {
                      jedis.close();
                  }
          }
      }
      
  • 22
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值