admin管理员组

文章数量:1651830

喜欢狂神老师的一句话:

即使再小的帆,也能远航!!

与君共勉

笔记来源:狂神说Java-Redis学习

文章目录

  • Redis学习【狂神说java笔记】:
    • 概述:
    • Linux安装Redis
    • 性能测试:
    • 基础知识:
  • 五大数据类型:
      • 常见的有五种数据类型:
    • Redis-key
        • 基础语法:
    • String(字符串)
          • **单个自增自减**
          • **设置步调进行自增自减**
          • **获取字符串范围**
          • ***实现字符串的替换效果***
          • 设置过期时间
          • 批量设置(获取)多个键值对
          • 先Get 再 Set ` getset ` 常用
        • 基本语法:
    • List(列表)
          • 基本命令:
          • ` lpush ` ` rpush ` 新增元素
          • ` lrange ` 获取list的值
          • ` lpop ` ` rpop ` 移除元素
          • `llen`返回列表长度
          • ` lrem ` 移除指定的值
          • ` ltrim ` 修剪,截取留存
        • 组合命令
          • ` rpoplpush `移除并添加
    • set(集合)
          • ` sadd key value1 value2 ` 设置一个key
          • ` smembers key ` 查看当前key下所有元素
          • ` sismember key value` 查看key中指定的元素是否存在
          • **` *删除set集合中元素* `**
          • **`srandmember 随机抽取数`**
          • ` smove 将一个集合的值移动到另一个集合`
          • `spop key 随机删除key中的一个value `
          • 基本命令
    • Hash(哈希)
          • ` hset ` 设置一个kv
          • ` hget ` 获取对应的kv
          • ` hgetall` 获取所有的kv键值对
          • ` hdel ` 删除一个指定的键值对元素
    • zset(有序集合)
          • **`zadd`添加元素**
          • **` zrange`查询元素**
          • `基本命令`
  • 三种特殊数据类型:
    • geospatial 地理位置
          • `1 geoadd 添加地理位置`
          • ` 2 geopos 获取当前坐标值 一定是坐标值`
          • **`3 geodist 返回两个位置间的距离 `**
          • `4 georadius 用给定的的经纬度为中心,找出某一半径内的元素 `
          • `5 georadiusbymember 找出位于指定范围内的元素,中心点是由给定的位置元素决定 `
          • `6 geohash 返回11个字符组成的GeoHash字符串`
    • Hyperloglog
    • Bitmaps
  • 事务:
  • 监控:
  • Jedis:
      • jedis连接阿里云服务器redis步骤
      • 开始测试连接
          • 导入对应依赖
      • 测试事务:
  • SpringBoot整合
  • 自定义 RedisTemplate 实现 Json 序列化
          • json序列化操作步骤:
  • Redis.conf配置文件
      • 1 单位 unit
      • 2 包含 INCLUDES
      • 3 网络 NETWORK
      • 4 快照 SNAPSHOTTING
      • 5 复制 REPLICATION
      • 6 安全 SECRULITY
      • 7 限制 CLIENT
      • 8 AOF设置 APPEND ONLY 模式
  • Redis持久化
      • RDB(RedisDataBase)
        • 【重要】
      • AOF (Append Only File)
      • 如何在配置文件中进行搜索:
      • 修复AOF文件
      • AOF重写功能
      • 扩展:【理解重点】
  • Redis发布订阅(了解)
      • 实现测试:
      • 订阅端语法:
      • 发布端语法:
  • Redis主从复制
      • 概念:
      • **切记**:
      • **主从复制的作用主要包括:**
      • 为何一主二从?
      • **一个集群的最低配 【三台机器 一主二从】**
      • 主从复制需要的环境配置
      • 配置核心点:
      • 创建最低配集群的步骤:
        • 1、 开启一台主机 两台从机
        • 2、 复制3个配置文件,然后修改对应的信息
        • 3 、开启服务
        • 4 、 显示是否启动成功
  • 一主二从:
      • 设置主机:在从机中配置
      • 方式一:命令行配置【演示】
      • 方式二:配置文件【早期实际开发采用】
        • 情况一:主机断开
        • 情况二:从机断开
      • 复制原理:
      • **但是这两种方式都不会使用**
      • **会使用哨兵机制来完成主从复制**
      • 方式三:**哨兵模式**【核心】

Redis学习【狂神说java笔记】:

【Redis不区分大小写操作】

概述:

什么是Redis?

Redis(Remote Directory Server),中文译为远程字典服务

是一个开源的使用ANSI c语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据,并提供多种语言的AP1。
免费和开源! 是当下最热门的 NOSQL 技术之一!也被人们称之为结构化数据库!

Redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步

免费和开源!是当下最热门的 NoSQL 技术之一!也被人们称之为结构化数据库

Redis能干嘛?

  1. 内存存储,持久化,因为内存断电即失,并且Redis支持两种持久化方式,RDB / AOF
  2. 效率高,可用于高速缓存
  3. 消息发布订阅(消息队列)
  4. 地图信息分析
  5. 计数器(eg:微博浏览量)

特性

  1. 数据类型多样
  2. 持久化
  3. Redis集群
  4. 事务

下载

官网:https://redis.io/

Redis中文文档:http://www.redis/documentation.html

下载地址:进入官网下载即可(Windows版本需要在GitHub上下载,并且Redis版本已停更较长时间,不建议使用)

并且,Redis官方推荐在Linux服务器上进行搭建,使用云服务器比较合适。

在此我们此都是基于Linux记录

Linux安装Redis

6.0.6Linux 版本下载地址:http://download.redis.io/releases/redis-6.0.6.tar.gz

5.0.7tar.gz链接:https://pan.baidu/s/1fuzHX4IbX_vsiVgfnIZVwA
提取码:xxzy

  1. 下载安装包,redis-5.0.7.tar.gz
  2. 下载到Windows之后,用Xftp工具上传至Linux
  3. 解压安装包并将其解压到opt目录下(程序一般都考虑放此路径)
  4. 并且解压之后可以看见Redis的配置文件redis.conf
cd /opt
tar -zxvf redis-5.0.7.tar.gz

cd redis-5.0.7 
ls

同时还需要基本的环境搭建 依旧是在redis-5.0.7 目录下

# 保证Redis的正常运行,gcc的安装也是必要的使用yum安装
yum install gcc-c++
# 查看gcc版本
gcc -v
# 安装Redis所需要的环境 就是配置所有需要的环境
make
# 此命令只是为了确认当前所有环境全部安装完毕,可以选择不执行
make install 

make执行成功后

  1. Redis的安装,默认在/usr/local/bin下

    cd /usr/local/bin
    

  1. 提前准备好一个目录,然后在复制到新创建好的目录中
cd /usr/local/bin   #bin目录下
mkdir xxzconfig
cp /opt/redis-5.0.7/redis.conf xxzconfig
cd xxzconfig
ls

我们之后就使用这个复制的文件启动防止修改原生的改动不回去了

  1. Redis默认不是后台启动的,修改配置文件 !【为了后台也可以运行】
vim redis.conf    #进入配置文件

修改的信息就是图中划红线的位置,它的意思是指守护进程模式启动,即可以在后台运行Redis【默认都是NO】

修改之后 按Esc 退出修改模式 并输入 :wq保存退出

  1. 启动Redis服务

就在/usr/local/bin目录下

cd /usr/local/bin
redis-server xxzconfig/redis.conf  # 命令+配置文件
ps -ef | grep redis    #  查看有没有服务启动

  1. 启动成功后使用Redis客户端连接指定的端口号
redis-cli -p 6379

服务启动后进行端口连接并测试通路

ping成功显示PONG

  1. 测试连接:
redis-cli -p 端口号

11. 测试redis的进程是否开启
ps -ef|grep redis

  1. 如何关闭Redis服务
shutdown
exit

  1. 再次查询进程是否存在

    发现已经没有了redis 的进程了

性能测试:

redis-benchmark 是一个压力测试工具! 官方自带的性能测试工具!

测试环境都是在/usr/local/bin目录下

cd /usr/local/bin     
redis-server xxzconfig/redis.conf  #启动redis服务器命令+配置文件
redis-cli -p 6379     #使用Redis客户端连接指定的端口号【6379】
ps -ef | grep redis    #  查看有没有服务启动
redis-benchmark -h localhost -p 6379 -c 100 -n 100000
# 当前命令表示,性能测试,在本机,端口号6379,并发连接数100,每个连接10w个请求数量

结果展示:

# 当前命令表示,性能测试,在本机,端口号6379,并发连接数100,每个连接10w个请求数量
redis-benchmark -h localhost -p 6379 -c 100 -n 100000
# 测试结果如下,以Redis的set命令为例
====== SET ======
  100000 requests completed in 1.85 seconds # 十万个请求在1.85秒之内被处理
  100 parallel clients # 每次请求都有100个客户端在执行
  3 bytes payload # 一次处理3个字节的数据
  keep alive: 1 # 每次都保持一个服务器的连接,只用一台服务器处理这些请求

28.68% <= 1 milliseconds
97.99% <= 2 milliseconds
99.47% <= 3 milliseconds
99.59% <= 4 milliseconds
99.62% <= 5 milliseconds
99.68% <= 6 milliseconds
99.79% <= 7 milliseconds
99.90% <= 22 milliseconds
99.97% <= 23 milliseconds
100.00% <= 23 milliseconds # 所有的请求在23秒之内完成
54054.05 requests per second # 平均每秒处理54054.05个请求

基础知识:

Redis有16个数据库支持

vim redis.conf  #进入配置文件

并且初始数据库默认使用0号数据库(16个数据库对应索引0到15)

可以使用select命令切换数据库:select n(0-15)

选择数据库并进行切换查询【在Redis中,关键字语法不区分大小写!】

127.0.0.1:6379[1]> select 15  # 切换数据库
OK
127.0.0.1:6379[15]> dbsize    # 查询容量
(integer) 0
127.0.0.1:6379[15]> set name zhangyouxiu
OK
127.0.0.1:6379[15]> dbsize
(integer) 1
127.0.0.1:6379[15]> select 1
OK
127.0.0.1:6379[1]> dbsize
(integer) 0
127.0.0.1:6379[1]> select 15
OK
127.0.0.1:6379[15]> get name    
"zhangyouxiu"
127.0.0.1:6379[15]> keys *    # 查看对应数据库所有的key
1) "name"

清空数据库指令:

  • flushdb    #清空当前数据库
    
  • flushall    #清空删除16个数据库中的全部信息
    

Redis是单线程的(从Redis6.0.1开始支持多线程)

明白Redis是很快的,官方表示,Redis是基于内存操作,CPU不是Redis性能瓶颈,Redis的瓶颈是根据机器的内存和网络带

宽,既然可以使用单线程来实现,就使用单线程了! 所以也就使用了单线程!!

Redis是C语言编写,官方提供的数据为10万+的QPS(Queries-Per-Second,每秒内的查询次数)完全不比同样是使用 key-vale的

Memecache差!

Redis单线程为什么速度还是这么快?

  1. 误区1 :高性能的服务器一定是多线程的 ?

  2. 误区2 : 多线程( CPU上下文会切换!)一定比单线程效率高 !

先去CPU>内存>硬盘的速度要有所了解!

核心:

Redis是将所有的数据全部是放在内存中的,所以说使用单线程去操作效率就是最高的,多线程( CPU上下文会切换: 耗时的操作!!!)对于内存系统来说,如果没有上下文切换效率就是最高的!多次读写都是在一个CPU上的 ,在内存情况下,这就是最佳方案!

五大数据类型:

Redis 提供了多种数据类型来支持不同的业务场景,比如 String(字符串)、Hash(哈希)、 List (列表)、Set(集合)、Zset(有序集合)、Bitmaps(位图)、HyperLogLog(基数统计)、GEO(地理信息)、Stream(流),并且对数据类型的操作都是原子性的,因为执行命令由单线程负责的,不存在并发竞争的问题。

除此之外,Redis 还支持事务 、持久化、Lua 脚本、多种集群方案(主从复制模式、哨兵模式、切片机群模式)、发布/订阅模式,内存淘汰机制、过期删除机制等等。

常见的有五种数据类型:

String(字符串),Hash(哈希),List(列表),Set(集合)、Zset(有序集合)

数据类型可以存储的值操作
String字符串、整数或者浮点数对整个字符串或者字符串的其中一部分执行操作,对整数和浮点数执行自增或者自减操作
List列表从两端压入或者弹出元素,对单个或者多个元素进行修剪,只保留一个范围内的元素
Set无序集合添加、获取、移除单个元素,检查一个元素是否存在于集合中,计算交集、并集、差集,从集合里面随机获取元素
Hash包含键值对的无序散列表添加、获取、移除单个键值对,获取所有键值对,检查某个键是否存在
Zset有序集合添加、获取、删除元素,根据分值范围或者成员来获取元素,计算一个键的排名

Redis-key

cd /usr/local/bin                    #  切到redis目录        
redis-server xxzconfig/redis.conf    #  开启redis服务
redis-cli -p 6379                    #  #使用Redis客户端连接指定的端口号【6379】 
flushall                             #  清除所有数据库的信息

127.0.0.1:6379> set name zhangyouxiu
OK
127.0.0.1:6379> keys *
1) "name"
127.0.0.1:6379> set age 1
OK
127.0.0.1:6379> keys *
1) "age"
2) "name"
127.0.0.1:6379> exists name   # exists 查看当前key是否存在     
(integer) 1    # 返回1  说明存在这个值
127.0.0.1:6379> exists age     # exists 查看当前key是否存在
(integer) 1
127.0.0.1:6379> exists home    # exists 查看当前key是否存在
(integer) 0   # 返回0 说明不存在这个值
127.0.0.1:6379> MOVE name 1 # 将当前key移动到1号数据库
(integer) 1
127.0.0.1:6379> KEYS *
(empty list or set)
127.0.0.1:6379> SELECT 1 # 选择数据库
OK
127.0.0.1:6379[1]> KEYS *
1) "name"

127.0.0.1:6379[1]> EXPIRE age 15 # 设置当前key的过期时间,单位是秒  expire +  key +  过期时间
(integer) 1
127.0.0.1:6379[1]> ttl age # 查看指定key的存活时间,   ttl  查询
(integer) -2 # 返回-2表示当前key已经过期,如果为-1,表示永不过期

# 设置key过期时间和查询是否过期   -2表示当前key已经过期,如果为-1,表示永不过期
127.0.0.1:6379> expire age 15
(integer) 1
127.0.0.1:6379> ttl age
(integer) 11
127.0.0.1:6379> ttl age
(integer) 10
127.0.0.1:6379> ttl age
(integer) 9
127.0.0.1:6379> ttl age
(integer) 7
127.0.0.1:6379> ttl age
(integer) 1
127.0.0.1:6379> ttl age
(integer) 0
127.0.0.1:6379> ttl age
(integer) -2
127.0.0.1:6379> ttl age
(integer) -2
127.0.0.1:6379> get age
(nil)


#type + key  查询对应key的类型      
127.0.0.1:6379> type name
string
127.0.0.1:6379> type age  #因为已经过期 所以没有
none

基础语法:

设置过期命令可以在后期使用单点登录使用设置过期时间

#                                  基础语法:
# set key value	          设置一个key
# get key                 获取一个key对应value
# exists key              查询key是否存在
# move key n(数据库下标数字)	     将当前key移动到指定的几号数据库中   不常用
# keys *                  查询当前数据库中全部的key
# expire key time         设置当前key的过期时间
# ttl key                 查询当前key的存活时间
# type key                查看key的数据类型

String(字符串)

127.0.0.1:6379> set k1 xxz
OK
127.0.0.1:6379> get k1
"xxz"
127.0.0.1:6379> exists k1         # 查询key是否存在
(integer) 1
127.0.0.1:6379> append k1 "hello"    #给k1再继续追加value值
(integer) 8
127.0.0.1:6379> get k1
"xxzhello"
127.0.0.1:6379> strlen k1    # 查看当前k1的长度
(integer) 8
单个自增自减

【实际应用 在看功能 网站的播放量 浏览量 使用redis实现 可以持久化进数据库但缓存可以放redis】

127.0.0.1:6379> clear
127.0.0.1:6379> set views 0
OK
127.0.0.1:6379> get views
"0"
127.0.0.1:6379> incr views    # 浏览量 +1
(integer) 1
127.0.0.1:6379> incr views
(integer) 2
127.0.0.1:6379> get views
"2"
127.0.0.1:6379> decr views    # 浏览量   -1 
(integer) 1
设置步调进行自增自减

(就是直接设置增加减少量)

incrby 自增步调设置

decrby 自减步调设置

127.0.0.1:6379> incrby views 10   # 自增  且自增量为10
(integer) 11



127.0.0.1:6379> decrby views 3   # 自减,设置步长为3
(integer) 8
获取字符串范围

get range

127.0.0.1:6379> set k1 "hello,zhangyouxiu"
OK
127.0.0.1:6379> get k1
"hello,zhangyouxiu"
127.0.0.1:6379> append k1 "nihao"
(integer) 22
127.0.0.1:6379> get k1
"hello,zhangyouxiunihao"
127.0.0.1:6379> getrange k1 0 6  #获取字符串范围 有起始索引和结束索引,相当于Java中的subString()
"hello,z"
127.0.0.1:6379> getrange k1 0 -1  # 如果结束索引为-1,则表示当前截取的字符串为全部
"hello,zhangyouxiunihao"
实现字符串的替换效果
127.0.0.1:6379> get k1
"hello,zhangyouxiunihao"
127.0.0.1:6379> setrange k1 1 xxx   # 替换 k1 的下标为1 的值为 xxx
(integer) 22
127.0.0.1:6379> get k1       # 替换成功
"hxxxo,zhangyouxiunihao"


设置过期时间
# setex(set with expire) # 设置过期时间
# setnx(set with not exist) # 如果key不存在,创建(分布式锁中常用)

##################################     setex   设置过期时间 
127.0.0.1:6379> setex k2 20 "ceshi"   # 设置k2 过期时间20秒  值为ceshi
OK
127.0.0.1:6379> keys *
1) "k1"
2) "k2"
127.0.0.1:6379> ttl k2        # 查看指定key的存活时间,   ttl  查询
(integer) -2                 # 返回-2表示当前key已经过期,如果为-1,表示永不过期
127.0.0.1:6379> keys *
1) "k1"
127.0.0.1:6379> 

##################################     setnx 
127.0.0.1:6379> setnx mykey "redis"    # 如果mykey不存在,即创建成功  (1)
(integer) 1
127.0.0.1:6379> setnx mykey "12"     # 如果mykey不存在,即创建失败  (0)
(integer) 0
批量设置(获取)多个键值对
127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3 # 同时设置多个值
OK
127.0.0.1:6379> KEYS * 
1) "k2"
2) "k1"
3) "k3"
127.0.0.1:6379> mget k1 k2 k3 # 同时获取多个值
1) "v1"
2) "v2"
3) "v3"
# 也可以在这边的语法前面加上一个m,代表设置多个
127.0.0.1:6379> msetnx k1 vv1 k4 v4 
(integer) 0
# 但是这边同时设置多个值,如果有一个key已经存在,那么这一条设置语句便不会执行成功,
# 因为Redis单条语句是原子操作,要么同时成功,要么同时失败
127.0.0.1:6379> keys * 
1) "k2"
2) "k1"
3) "k3"

这是Redis中关于key的命名,可以用“:”来代表层次结构,可以对指定的key进行分类存储

# 这是Redis中关于key的命名,可以用“:”来代表层次结构,可以对指定的key进行分类存储
127.0.0.1:6379> mset user:1:name ceshi user:1:age 21
OK
127.0.0.1:6379> mget user:1:name user:1:age
1) "ceshi"
2) "21"

先获取当前key指定的value,如果不存在,会返回nil(null),然后设置新值

先Get 再 Set getset 常用
127.0.0.1:6379> getset k1 redis    # 如果不存在值,则返回nil
(nil)
127.0.0.1:6379> get k1    
"redis"
127.0.0.1:6379> getset k1 xxz    # 如果存在值 获取原来的值, 并设置新的值
"redis"
127.0.0.1:6379> get k1
"xxz"

数据结构是相同的

Redis中String这样的使用场景,value值可以是字符串,也可以是其他类型

主要用途

  • 计数器
  • 统计多单位的数量 类似B站统计 类似关注 粉丝播放量,等设置
  • 一个用户的粉丝数
  • 一个有过期时间的验证码
基本语法:

基本语法

# 语法:
# append key appendValue                 对指定key实现字符串拼接,如果key不存在,等同于set
# strlen key                             查看指定key的长度
# incr key                               对指定key进行自增,类似于Java中的i++
# decr key                               自减,类似于Java的i--
# incrby key n                           对指定key按照指定的步长值进行自增
# decrby key n						   按照指定的步长值自减
# setrange key index value               从指定key的索引开始,插入指定的value值。
# 如果key不存在且索引>1,那么当前的索引之前的数据,会用\x00代替并占用一个索引位置,相当于ASCII码中的null
# getrange key startIndex endIndex			将指定key按照索引的开始和结束范围进行截取,成为一个新的key
# setex key time value							 	 设置一个有存活时间的key
# setnx key value                     	如果这个key不存在,即创建
# mset key value ...								 	 设置多个key value
# mget key ...												获取多个key指定的value
# getset key value                    	先获取指定的key,然后再设置指定的value

List(列表)

Redis中的List列表可以做很多事情,可以将其看成数据结构中的栈,也可以是队列,或者阻塞队列

值可重复

list 类型的 所有的 命令 都是以L 开头的

基本命令:
# 命令:
# lpush  key value1 value2 ...                      设置一个key,从头部插入数据(头插法)
# rpush key value1 value2 ...										 设置一个key,从尾部插入数据(尾插法)
# lrange key startIndex endIndex                   返回列表中从开始索引到结束索引位置的value值
# lpop key                                         从key头部弹出一个value 即删除元素
# rpop key                                         从尾部弹出一个value
# lindex index                                     返回key中指定索引的value值
# lrem key n value                                 删除key中的指定的value值,n代表删除几个
# llen key                                         返回key的长度
# ltrlml key startIndex endIndex                   截取key,截取的范围从开始索引到结束索引
# lset key index value                             从当前key的索引开始插入指定的value值
# rpoplpush key1 key2                              从key1的尾部弹出一个元素,将此元素从key2的头部插入
# linsert key before|after oldValue newValue       从指定key中已存在的value的前面或者后面插入一个指定的value
lpush rpush 新增元素
lrange 获取list的值
# 从list头部插入一个或者多个元素(从左边插入,看命令首字母)
127.0.0.1:6379> keys *
(empty list or set)
127.0.0.1:6379> lpush list one    #lpush 从list头部插入一个或者多个元素(从左边插入,看命令首字母)
(integer) 1
127.0.0.1:6379> lpush list 2
(integer) 2
 # -1不代表实际位置的索引,它表示需要返回到这个列表的最后一个元素  0  -1 获取所有的值
127.0.0.1:6379> lrange list  0 -1   # lrange   获取list的值
1) "2"
2) "one"
127.0.0.1:6379> lrange list  0 0   # 通过区间获取具体的值
1) "2"
127.0.0.1:6379> rpush list rg      #rpush  从list头部插入一个或者多个元素(从左边插入,看命令首字母)
(integer) 3
127.0.0.1:6379> lrange list  0 -1
1) "2"
2) "one"
3) "rg"   
lpop rpop 移除元素
127.0.0.1:6379> lpop list      # 移除 list  的第一个 即头部元素
"2"
127.0.0.1:6379> lrange list  0 -1
1) "one"
2) "rg"
127.0.0.1:6379> rpop list   # 移除 list  的最后一个 即尾部元素
"rg"
127.0.0.1:6379> lrange list  0 -1
1) "one"

lindex 获取对应下标的值

127.0.0.1:6379> lpush list 4   #先push 值进入
(integer) 1
127.0.0.1:6379> lpush list 3
(integer) 2
127.0.0.1:6379> lpush list 2
(integer) 3
127.0.0.1:6379> lpush list 1
(integer) 4
127.0.0.1:6379> lrange list 0 -1
1) "1"
2) "2"
3) "3"
4) "4"
127.0.0.1:6379> lindex list 0
"1"
127.0.0.1:6379> lindex list 1
"2"
127.0.0.1:6379> lindex list 3
"4"
llen返回列表长度
127.0.0.1:6379> lrange list 0 -1
1) "1"
2) "2"
3) "3"
4) "4"
127.0.0.1:6379> llen list
(integer) 4
lrem 移除指定的值

例如真实环境下的 取关操作 取消对应的 uid

127.0.0.1:6379> lpush list 1   # 先存值
(integer) 5
127.0.0.1:6379> lrange list 0 -1   # 显示值
1) "1"
2) "1"
3) "2"
4) "3"
5) "4"
127.0.0.1:6379> lrem list 2 1   # 含义 :  移除 list中 2 代表数量几个  数量后跟的是删除的内容 
(integer) 2
127.0.0.1:6379> lrange list 0 -1    # 显示所有值
1) "2"
2) "3"
3) "4"
ltrim 修剪,截取留存
127.0.0.1:6379> lrange list 0 -1
1) "h1"
2) "h2"
3) "h3"
4) "h4"
127.0.0.1:6379> ltrim list 1 2   # 截取list列表,只保留从开始索引到结束索引的元素 
OK
127.0.0.1:6379> lrange list 0 -1
1) "h2"
2) "h3"
组合命令
rpoplpush移除并添加

移除列表的最后一个元素并添加进新的列表中

127.0.0.1:6379> lrange list 0 -1
1) "h1"
2) "h2"
3) "h3"
4) "h4"
127.0.0.1:6379> rpoplpush list newlist
"h4"
127.0.0.1:6379> lrange list 0 -1
1) "h1"
2) "h2"
3) "h3"
127.0.0.1:6379> lrange newlist 0 -1
1) "h4"

linsert

从列表中的某一个value值的前面或后面插入一个新的value值

127.0.0.1:6379> LPUSH mylist hello 
(integer) 1
127.0.0.1:6379> LPUSH mylist xxz
(integer) 2
127.0.0.1:6379> LRANGE mylist 0 -1 
1) "xxz"
2) "hello"
127.0.0.1:6379> LINSERT mylist after hello xxz 
(integer) 3
127.0.0.1:6379> LRANGE mylist 0 -1 
1) "xiaohuang"
2) "hello"
3) "xxz"
127.0.0.1:6379> LINSERT mylist before xxz nihao
(integer) 4
127.0.0.1:6379> LRANGE mylist 0 -1 
1) "nihao"
2) "xxz"
3) "hello"
4) "world"

List列表实际上它是一个数据结构的链表

  • 可以在Node节点的before或者after,left或者right插入值
    • key不存在,创建新链表
      • key存在,新增内容
        • 如果移除了所有了value,也代表不存在
          • 在Node节点的两边插入,效率最高!中间元素效率较低
            • Redis中可以将这个列表灵活的使用

栈(lpush lpop | rpush rpop),队列(lpush rpop | rpush lpop)

set(集合)

这里面的值一般不会重复 且无序

如果往set集合中添加一个重复的值,不会报错,但是也不会插入成功,因为set集合无序且不重复

sadd key value1 value2 设置一个key
smembers key 查看当前key下所有元素
sismember key value 查看key中指定的元素是否存在
127.0.0.1:6379> sadd k1 "xxz"    # 设置一个key  返回1 成功 0 失败
(integer) 1
127.0.0.1:6379> sadd k1 "nihao"
(integer) 1
127.0.0.1:6379> sadd k1 "xxz"   #如果往set集合中添加一个重复的值,不会报错,但是也不会插入成功,因为set集合无序且不重复
(integer) 0
127.0.0.1:6379> smembers k1     # 查看当前 key 下所有元素
1) "xxz"
2) "nihao"
127.0.0.1:6379> sismember k1 xxz    # 查看key中指定的value是否存在  返回1 存在 0 不存在
(integer) 1
127.0.0.1:6379> sismember k1 11
(integer) 0

scard * 查看集合中的元素个数

127.0.0.1:6379> scard myset # 查看myset集合中的元素个数
(integer) 2
*删除set集合中元素*
127.0.0.1:6379> SREM myset nihao # 删除set集合中nihao
(integer) 1
srandmember 随机抽取数
# 在一个无序集合中,随机抽取一个数
127.0.0.1:6379> smembers nums
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"
6) "6"
7) "7"
8) "8"
9) "9"
127.0.0.1:6379> srandmember nums # 随机抽取
"8"
127.0.0.1:6379> srandmember nums
"7"
127.0.0.1:6379> srandmember nums
"5"
127.0.0.1:6379> srandmember nums 2 # 随机抽取指定个数的元素 
1) "5"
2) "4"

smove 将一个集合的值移动到另一个集合
# 一个set集合中的值移动到另外一个set集合中
# 语法: smove source(源set集合,必须存在) destination(目标集合,被添加元素) value(必须是源set集合中存在的value)
127.0.0.1:6379> SMEMBERS set01
1) "hello"
2) "xiaohuang"
3) "world"
127.0.0.1:6379> SMEMBERS set02
1) "me"
127.0.0.1:6379> SMOVE set01 set02 xiaohuang # 将set01中的xiaohuang放入到set02中
(integer) 1
127.0.0.1:6379> SMEMBERS set02
1) "xiaohuang"
2) "me"
127.0.0.1:6379> SMEMBERS set01
1) "hello"
2) "world"
spop key 随机删除key中的一个value
# 删除指定key,随机删除key
# 以上面的数字集合为例
127.0.0.1:6379> SPOP nums # 随机删除一个set集合元素
"5"
127.0.0.1:6379> SPOP nums
"8"
127.0.0.1:6379> SPOP nums
"7"
127.0.0.1:6379> SMEMBERS nums
1) "1"
2) "2"
3) "3"
4) "4"
5) "6"
6) "9"

微博,B站等共同关注(并集)

数字集合类:

  • 差集 sdiff k1 k2
  • 并集 sunion k1 k2
  • 交集 sinter k1 k2
# 生活中的一个小现象,就比如说微信公众号,会有共同关注,还有QQ的共同好友
# 数学集合关系中的:交、并、补。微信公众号中的共同关注,以及QQ的共同好友,就是关系中的交!
127.0.0.1:6379> sadd k1 a
(integer) 1
127.0.0.1:6379> sadd k1 b
(integer) 1
127.0.0.1:6379> sadd k1 c
(integer) 1
127.0.0.1:6379> sadd k2 c
(integer) 1
127.0.0.1:6379> sadd k2 d
(integer) 1
127.0.0.1:6379> sadd k2 e
(integer) 1
127.0.0.1:6379> sdiff k1 k2   #两个集合都有c这个元素 k1 与 k2 之间的差集(以k1为主)
1) "b"
2) "a"
127.0.0.1:6379> sinter k1 k2   # k1 和 k2 之间的交集,公众号的共同关注,QQ中的共同好友就可以这么来实现
1) "c"
127.0.0.1:6379> sunion k1 k2   # k1 和 k2 之间的并集
1) "d"
2) "c"
3) "a"
4) "b"
5) "e"

微博,A用户将所有关注的人放在一个set集合中!

将它的粉丝也放在一个集合中!共同关注,共同爱好,二度好友,推荐好友!(六度分割理论 )

基本命令
# 命令:
# sadd key value1 value2 ...          设置一个key  返回1 成功 0 失败
# smembers key                        查看当前key下所有元素
# sismember key value                 查看key中指定的value是否存在
# scard key                           查看key的长度
# srem key value                      删除key中的指定value
# spop key                            随机删除key中的一个value
# srandmember key [n]                 随机查看当前key中的一个或者多个value
# smove key1 key2 key1Value           将key1中的value移动到key2中
# sdiff key1 key2                     两个key相交,求第一个key的补集
# sinter key1 key2                    两个key相交,求交集
# sunion key1 key2                    两个key相交,求并集

Hash(哈希)

Map集合,key-map集合 ==》 key-(key-value)

hset 设置一个kv
hget 获取对应的kv
127.0.0.1:6379> hset myhash field1 xxz  # set 一个具体的 key-vlaue 
(integer) 1
127.0.0.1:6379> hget myhash field1     # 获取对应的字段值
"xxz"
127.0.0.1:6379> hmset myhash field1 hello field2 world 
OK
127.0.0.1:6379> hmget myhash field1 field2
1) "hello"
2) "world"
hgetall 获取所有的kv键值对
127.0.0.1:6379> hgetall myhash # 获取myhash中全部的kv键值对
1) "field1"
2) "hello"
3) "field2"
4) "world"
hdel 删除一个指定的键值对元素
127.0.0.1:6379> hdel myhash field2   # 删除myhash一个指定的键值对元素
(integer) 1
127.0.0.1:6379> hgetall myhash
1) "field1"
2) "hello"

hlen 获取当前key中value长度

127.0.0.1:6379> hlen myhash       # 获取当前myhash中的value长度 
(integer) 1
127.0.0.1:6379> hexists myhash field1      # 判断当前hash中的kv键值对是否存在
(integer) 1
127.0.0.1:6379> hexists myhash field2
(integer) 0
127.0.0.1:6379> hkeys myhash  # 只获取myhash中的key
1) "field1"
127.0.0.1:6379> hvals myhash    # 只获取myhash中key对应的value
1) "hello"  
# 备注:Redis中,有自增,没有自减,即使是没有自减,也可以在自增的步长当中设置一个负数即可
# hincrby key field 步长值
127.0.0.1:6379> hset myhash num 3
(integer) 1
127.0.0.1:6379> hincrby myhash num 2
(integer) 5
127.0.0.1:6379> hincrby myhash num 2
(integer) 7
127.0.0.1:6379> hsetnx myhash k4 v4    # 如果hash中的一个元素不存在,即可创建
(integer) 1
127.0.0.1:6379> hsetnx myhash k4 vv4    # 存在,即创建失败
(integer) 0
# String也提到了使用“:”进行层次分割,不过hash更适合对象存储,String适合于文本的存储
127.0.0.1:6379> hmset user:1 name xxz age 22 sex boy
OK
127.0.0.1:6379> hgetall user:1
1) "name"
2) "xxz"
3) "age"
4) "22"
5) "sex"
6) "boy"

基本命令:

# hset key field value                 设置单个hash
# hget key field                       获取单个
# hmset key field1 v1 field2 v2        设置多个
# hmget key field                      获取多个
# hgetall key                          获取hash中全部的field-value
# hlen key                             获取hash长度
# hexists key field                    查询hash中指定的field是否存在
# hkeys key                            只获取hash中的field
# hvals key                            只获取hash中value
# hincrby key field n                  对hash中指定的field设置自增自减

zset(有序集合)

在set的基础上增加了一个score的值,相当于 zset k1 score v1,使用score来对当前key中元素进行排序

zadd添加元素
zrange查询元素
127.0.0.1:6379> zadd myset 1 one
(integer) 1
127.0.0.1:6379> zadd myset 2 two # zset集合添加一个值
(integer) 1
127.0.0.1:6379> zrange myset 0 -1
1) "one"
2) "two"
127.0.0.1:6379> zadd myset 3 three 4 four
(integer) 2
127.0.0.1:6379> zrange myset 0 -1 
1) "one"
2) "two"
3) "three"
4) "four"
# 实现元素的排序
# 根据zset中score的值来实现元素的排序
127.0.0.1:6379>  zadd salary 3500 zhangyouxiu 6500 xxz 3900 xx
(integer) 3     # 当前命令,inf在Unix系统中代表的意思是无穷,所以当前命令是指,将当前zset,以从小到大的形式进行排列
127.0.0.1:6379>  zrangebyscore salary -inf +inf 
1) "zhangyouxiu"
2) "xx"
3) "xxz"
127.0.0.1:6379> zrangebyscore salary -inf +inf withscores     # 在排列的同时,将score和指定的元素全部展示
1) "zhangyouxiu"
2) "3500"
3) "xx"
4) "3900"
5) "xxz"
6) "6500"
127.0.0.1:6379> zrevrange salary 0 -1 withscores   # 将数据从 大 到 小进行排列
1) "xxz"
2) "6500"
3) "xx"
4) "3900"
5) "zhangyouxiu"
6) "3500" 
127.0.0.1:6379> zrangebyscore salary -inf 4000 withscores  # 展示的同时还可以指示score的查询最大值,指定查询范围
1) "zhangyouxiu"
2) "3500"
3) "xx"
4) "3900"
127.0.0.1:6379> zrem salary zhangyouxiu    # 删除zset中的一个元素
(integer) 1
127.0.0.1:6379>  zrange salary 0 -1    查询从开始到结束索引的zset集合
1) "xx"
2) "xxz"
127.0.0.1:6379> zcard salary    查询hash长度
(integer) 2

127.0.0.1:6379>  ZADD myset 1 hello 2 world 3 zhangyouxiu 4 nihao 5 xx
(integer) 5   
# 语法:zcount key min max ,min和max包左也包右,它是一个闭区间
127.0.0.1:6379> zcount myset 2 5
(integer) 4
127.0.0.1:6379> keys *
1) "salary"
2) "myset"
127.0.0.1:6379>  zrange myset 0 -1 
1) "hello"
2) "world"
3) "zhangyouxiu"
4) "nihao"
5) "xx"

案例

其与的一些API,通过我们的学习吗,你们剩下的如果工作中有需要,这个时候你可以去查查看官方文档!
案例思路: set 排序 存储班级成绩表,工资表排序!
普通消息,1,重要消息 2,带权重进行判断!
排行榜应用实现,取Top N 测试!可以打开B站,排行榜,B站会根据视频的浏览量和弹幕量进行综合评分,进行排名

基本命令
# 基本命令
# zadd key score1 value1 score2 value2 ...              zset中添加一个或多个元素
# zrange key startIndex endIndex                        查询从开始到结束索引的zset集合
# zrangebyscore key min max [WITHSCORES]                对hash中按照指定数值进行升序排列
# zrevrange key startIndex endIndex                     对指定开始和结束索引进行降序排列
# zrem key field                                        删除hash中指定的field
# zcard key                                             查询hash长度
# zcount key [min max]                                  查询hash数量,还可以增加最大值和最小值的范围

三种特殊数据类型:

geospatial 地理位置

首先得有地理数据 一般会下载城市数据直接通过java程序一次性导入 不然得一个一个添加

实际应用:

朋友的定位,附近的人,打车距离计算 ?
Redis 的 Geo 在Redis3.2 版本就推出了! 这个功能可以推算地理位置的信息,两地之间的距离,方圆几里的人!

推荐一个网站查看指定城市的经纬度:http://www.jsons/lngcode/ 查询网站

对于这个关于地理的数据类型,它有6个命令

  • geoadd 添加地理位置
  • geodist 返回两个位置间的距离
  • geohash
  • geopos 获取当前坐标值
  • georadius 用给定的的经纬度为中心,找出某一半径内的元素
  • georadiusbymember

geoadd 添加地理位置

1 geoadd 添加地理位置
# geoadd 添加地理位置
# 语法:geoadd key 经度 纬度 城市名称 ...
# 注意:南北极无法直接添加。用添加城市数据来说,一般都会使用Java的Jedis来操作,而这些城市数据都是被下载下来通过JavaAPI调用
# 即 一般会下载城市数据直接通过java程序一次性导入
# 有效经度从-180到180度
# 有效纬度从-85.05112878 到 85.05112878 度。超过范围会出现(error) ERR invalid longitude,latitude pair
127.0.0.1:6379> geoadd china:city 108.94 34.26 xian
(integer) 1
127.0.0.1:6379> geoadd china:city 113.28 23.12 guangzhou 114.08 22.54 shenzhen
(integer) 2

geopos 获取当前坐标值

2 geopos 获取当前坐标值 一定是坐标值
# 语法:geopos key member1 member2 ...  获取当前坐标值  一定是坐标值
127.0.0.1:6379> geopos china:city xian
1) 1) "108.93999785184860229"
   2) "34.25999964418929977"

 # 获取一个或多个地理信息 (得先存进去)
127.0.0.1:6379> geopos china:city xian beijing
1) 1) "108.93999785184860229"
   2) "34.25999964418929977"
2) 1) "116.39999896287918091"
   2) "39.90000009167092543"

geodist 返回两个位置间的距离

3 geodist 返回两个位置间的距离

实例: 两人之间的距离

单位:

  • m表示单位米
  • km表示千米
  • mi表示英里
  • ft表示英尺
# 语法:geodist key member1 member2 [unit]
# 后面的unit加了中括号表示可选操作,即当前这个命令计算出来的结果为m(米)
127.0.0.1:6379> geodist china:city beijing xian      # 查看北京和西安两个位置的直线距离 [默认是米]
"911340.9035"
127.0.0.1:6379> geodist china:city xian beijing km
"911.3409"
127.0.0.1:6379> geodist china:city xian beijing mi
"566.2824"
127.0.0.1:6379> geodist china:city xian beijing ft
"2989963.5941"

georadius 用给定的的经纬度为中心,找出某一半径内的元素

4 georadius 用给定的的经纬度为中心,找出某一半径内的元素

实例:找 附近的人 ?(获得所有人的地址,定位!即经纬度)通过半径来查询

前提一定要存地理值即有值可查

# 语法:georadius key 经度 纬度 半径 [单位] [withdist(搜寻到的目标的经纬度)] [withdist(直线距离)] [count] 
127.0.0.1:6379> georadius china:city 111 31 1000 km # 以111经度31纬度为中心,1000km为半径搜寻在器范围之内的城市
1) "shenzhen"
2) "guangzhou"
3) "fuzhou"
4) "shanghai"
127.0.0.1:6379> georadius china:city 111 31 1000 km withcoord withdist # 追加参数,目标经纬度,直线距离
1) 1) "shenzhen"
   2) "989.2821"
   3) 1) "114.08000081777572632"
      2) "22.53999903789756587"
2) 1) "guangzhou"
   2) "905.0108"
   3) 1) "113.27999979257583618"
      2) "23.1199990030198208"
3) 1) "fuzhou"
   2) "978.4847"
   3) 1) "119.29999798536300659"
      2) "26.06999873822022806"
4) 1) "shanghai"
   2) "996.9549"
   3) 1) "121.47000163793563843"
      2) "31.22999903975783553"
127.0.0.1:6379> georadius china:city 111 31 1000 km withcoord withdist count 2 # 还可以限制查询的结果条数,只显示两条
1) 1) "guangzhou"
   2) "905.0108"
   3) 1) "113.27999979257583618"
      2) "23.1199990030198208"
2) 1) "fuzhou"
   2) "978.4847"
   3) 1) "119.29999798536300659"
      2) "26.06999873822022806"

georadiusbymember 找出位于指定范围内的元素,中心点是由给定的位置元素决定

5 georadiusbymember 找出位于指定范围内的元素,中心点是由给定的位置元素决定
# 语法:georadiusbymember  key member 长度 [unit]单位
127.0.0.1:6379> georadiusbymember china:city xian 2500 km
1) "xian"
2) "beijing"
127.0.0.1:6379> georadiusbymember china:city xian 1000 km    # 找出以xian为中心,1000km为半径搜索
1) "xian"
2) "beijing"

geohash 返回一个或多个位置元素的geohash

6 geohash 返回11个字符组成的GeoHash字符串

返回一个或多个元素的GeoHash表示,该命令返回11个字符组成的GeoHash字符串

# geohash  key member
127.0.0.1:6379> geohash  china:city beijing 
1) "wx4fbxxfke0"
127.0.0.1:6379> geohash china:city xian beijing
1) "wqj6yuzdvy0"
2) "wx4fbxxfke0"
# 将二维的经纬度通过一定的策略转换为一维的52位的字符串编码,如果两个字符串越接近,则距离越近

geospatial的底层,实际上它就是一个zset集合,我们可以使用zset命令来操作 geo!

127.0.0.1:6379> zrange china:city 0 -1   # 查看地图中所有的元素
1) "xian"
2) "beijing"
127.0.0.1:6379> zrem china:city xian   # 移除指定元素
(integer) 1

Hyperloglog

前提就是允许容错 使用内存是固定的就是 12 k

什么是基数

A{1,3,5,7,8,7} 这里7重复了

B{1,3,5,7,8} 这里的1 3 5 7 8 都没重复 所以他的基数为5 个

基数:(不重复元素个数)=5

基数是指一个集合中不重复的元素的个数 划重点 是一个集合中不重复的 不是两个集合做对比

基数计算(cardinality counting)指的是统计一批数据中的不重复元素的个数

简介

Hyperloglog是Redis2.8.9更新的,它是一种数据结构,主要是针对于基数统计的算法

优点,占用的内存很小且是固定的 ,只需要使用12KB的内存即可统计2^64的数据 如果要从内存角度来比较的话 Hyperloglog 首选 !

在实际业务中,网页的UV(Unique Visitor,独立访客),一个人访问一个网站多次,只能算作是一个

用传统的方式,set集合保存用户的id,(因为set是无序集合重复会被干掉)然后统计set中元素个数作为标准来判断。

这个方式保存id会造成大量的内存用来浪费给保存用户id了,目的是为了计数,而不是为了保存用户id

Hyperloglog计数的错误率在0.81%,用来执行UV任务,可以忽略不计

# 语法:
# pfadd key value1 value2...                创建一组数据集,如果数据集中有相同的元素就会有去重效果
# pfcount key                               查看元素的长度
# pfmerge key3 key1 key2                    将两组元素合并成一个新数组,并带有去重效果,相当于数学中的并集
127.0.0.1:6379> pfadd mykey a b c d e f g h i j  # 创建第一组元素mykey
(integer) 1
127.0.0.1:6379> pfcount mykey    # 统计mykey元素的基数数量
(integer) 10
127.0.0.1:6379> pfadd mykey2 i j k l m n   # 创建第二组元素mykey2
(integer) 1
127.0.0.1:6379> pfcount mykey2    # 统计mykey2元素的基数数量
(integer) 6
127.0.0.1:6379> pfmerge key3 mykey mykey2  # 合并两组 mykey mykey2 => key3 并集
OK
127.0.0.1:6379> pfcount key3  # 统计并集元素个数
(integer) 14  

后期在做页面统计 或者数量统计的 都可以使用 Hyperloglog 前提就是允许容错

如果在项目中允许容错,可以使用 Hyperloglog

如果不行,就可以直接使用 set或者Java的 HashMap 来 实现

Bitmaps

Bitmaps是一种位存储的数据类型

统计疫情感染人数,活跃人数与不活跃人数,登录打卡操作,两个状态的都可以使用Bitmaps

Bitmaps 位图,数据结构都是操作二进制位进行记录,就只有 0 和和 1 两个状态的

365 天 = 365bit 1字节 = 8 bit 46字节左右!

# 语法:
# setbit key offset value                    设置一个key,在指定的offset位置上设置一个value,这个value只能是0或者1
# getbit key offset                          获取指定key上的offset位的value值
# bitcount key [start] [end]                 在指定key中计算被设置为 1 的比特位的数量。即统计操作
# bitop operation destKey key1 key2 ...      对一个或者多个key进行二进制的逻辑运算
# bitops key bit [start] [end]               指定key中返回value中第一个出现0或1的offset    

使用Bitmaps来记录周一到周天的打卡 0 作未打卡 1为打卡

周一:1 周二:0 周三:0…

设置好后 我们来测试查看一下周四 也就是下标是3 的打卡记录

查看某一天是否打卡

127.0.0.1:6379> getbit sign 3  # 查看周三的打卡记录 
(integer) 1
127.0.0.1:6379> getbit sign 5
(integer) 0

统计操作

127.0.0.1:6379> bitcount sign   # 统计所有的打卡次数,就可以看到是否全勤! 
(integer) 3

事务:

Redis单条命令保持原子性,但是Redis事务不保证原子性

**Redis事务没有隔离级别的概念 ! ** 所有命令在事务中,并没有直接执行!只有发起执行命令的时候才会执行

Redis事务的本质: 就是一组命令的集合即一组命令一起执行

在事务的执行过程中,会按照顺序执行 执行一系列的命令 至少需要这些特性: 一次性,顺序性,排他性,

	# 命令:
# multi            开启事务
#                  命令入队
# exec             执行事务
# discard          关闭事务即放弃事务

正常执行事务!

127.0.0.1:6379> multi    #开启事务
OK
#命令入队
127.0.0.1:6379> set k1 v1   
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> get k2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> exec    # 执行事务
1) OK
2) OK
3) "v2"
4) OK

每一组事务在执行完之后就是结束了 想要事务操作得从新开启事务

放弃事务

127.0.0.1:6379>  multi    #开启事务
OK
# 命令入队
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> set k4 v4
QUEUED
127.0.0.1:6379> discard   # 取消(结束事务) 也就表示 上面的命令都未执行
OK
127.0.0.1:6379> get k4   # 获取为null
(nil)

编译时异常(代码有问题,命令有错误)事务中的所有命令都不会被执行!

127.0.0.1:6379> mutli
(error) ERR unknown command `mutli`, with args beginning with: 
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 v1 
QUEUED
127.0.0.1:6379> set k2 v2 
QUEUED
127.0.0.1:6379> set k3 v3 
QUEUED
127.0.0.1:6379> getset k3   # 错误的命令 
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379> set k4 v4
QUEUED
127.0.0.1:6379> exec   # 执行事务报错
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get k4  # 所有的命令都不会被执行
(nil)

运行时异常(1/0)如果事务中存在语法性错误那么执行命令的时候,其他命令是可以正常执行的【错误命令抛出异常】

127.0.0.1:6379> set k1 "v1"
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> incr k1   # 执行的时候会失败
QUEUED
127.0.0.1:6379> set k2 v2 
QUEUED
127.0.0.1:6379> set k3 v3 
QUEUED
127.0.0.1:6379> get k3
QUEUED
127.0.0.1:6379> exec
1) (error) ERR value is not an integer or out of range # 虽然第一条命令报错了但却依旧是正常执行成功了
2) OK
3) OK
4) "v3"
127.0.0.1:6379> get k2
"v2"
127.0.0.1:6379> get k3
"v3"

类似 一个人加 100 另一人减 100 加成功了减却没成功 在MySQL中是不允许的 而在Redis中是允许的

监控! Watch 类比MySQL中的 version

监控:

悲观锁:

  • 很悲观,认为什么时候都会出问题,无论做什么都会加锁!

乐观锁:

  • 很乐观,认为什么时候都不会出问题,所以不会加锁!更新数据的时候去判断一下,在此期间是否有人修改过这个数据,

  • 获取version

  • 更新的时候比较version

Redis监视测试

127.0.0.1:6379> set money 100
OK
127.0.0.1:6379> get money
"100"
127.0.0.1:6379> watch money
OK
127.0.0.1:6379> multi    # 开启事务
OK
127.0.0.1:6379> decrby money 20   # 这里指 减少20
QUEUED
127.0.0.1:6379> incrby out 20    # 这里指花的是 20
QUEUED
127.0.0.1:6379> exec
1) (integer) 80
2) (integer) 20

测试多线程修改值,使用watch可以当作Redis的乐观锁操作

127.0.0.1:6379> watch money   # 监视 moeny
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> decrby money 10
QUEUED
127.0.0.1:6379> incrby out 10
QUEUED
127.0.0.1:6379> exec # 执行之前,另外一个线程,修改了我们的值,这个时候,就会导致事务执行失败!
(nil)
127.0.0.1:6379> unwatch  #  解锁即解除监视
OK
127.0.0.1:6379> watch   # 再监视的是 更新后的值 即重新监视的值
ok

Jedis:

我们要使用java来操作Redis

什么是Jedis ?是 Redis 官方推荐的 java连接开发工具 ! 使用 java 操作 Redis 中间件!如果你要使用 java 操作 redis ,那么一定要
对Jedis 十分的熟悉!

测试

测试之前我们需要先将redis中的一些默认配置修改可以让我们进行通过IDEA来连接 redis

jedis连接阿里云服务器redis步骤

首先修改配置文件

1、命令行输入vim redis.conf修改配置文件
2、在redis.conf配置文件中把 bind 127.0.0.1这一行注释掉

3、在redis.conf配置文件中把 protected-mode 设置成 no 即可开启远程访问

4、设置访问密码(远程连接为安全起见都设置上)

redis.conf配置文件中输入/查找关键字“requirepass”,修改将注释去掉,并将后面对应的字段设置成自己想要的密码,输入:wq保存退出,重启redis服务即可。**注意:**一定需要重启redis服务

firewall-cmd --list-ports  #查看开放的所有端口
firewall-cmd --query-port=**/tcp  #查看指定端口是否开放
最直接方法杀死进程 再重启
# ps -ef | grep redis 
kill **
kill -9 **   # 强制杀死进程号

通过redis的客户端程序的shutdown命令来重启redis

[root@xxz bin]# redis-server xxzconfig/redis.conf
2001:C 18 Aug 2023 12:32:45.799 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
2001:C 18 Aug 2023 12:32:45.799 # Redis version=5.0.7, bits=64, commit=00000000, modified=0, pid=2001, just started
2001:C 18 Aug 2023 12:32:45.799 # Configuration loaded
[root@xxz bin]# redis-cli -p 6379
127.0.0.1:6379> ping   # 先ping一次 失败显示需要登录密码
(error) NOAUTH Authentication required.   #(错误)NOAUTH认证要求。证明密码设置成功  需要密码登录
127.0.0.1:6379> auth *****  # auth后跟的是设置的密码
OK
127.0.0.1:6379> ping    # 再ping一次 成功
PONG
配置项参数说明
daemonizeno/yes默认为 no,表示 Redis 不是以守护进程的方式运行,通过修改为 yes 启用守护进程。
pidfile文件路径当redis以守护进程方式运行时,会把进程pid写入自定义的文件中
port6379指定redis监听端口,默认端口为6379
bind127.0.0.1绑定的主机地址
timeout0客户端闲置多少秒之后关闭连接,如果指定为0,表示不启用该功能
loglevelnotice指定日志记录级别,支持四个级别:debug、verbose、notice、warning,默认为 notice。
logfilestdout日志记录方式,默认为标准输出
databases16设置数据库的数量(0-15个)共16个,Redis 默认选择的是 0 库,可以使用 SELECT 命令来选择使用哪个数据库储存数据。
save[seconds] [changes]可以同时配置三种模式:save 900 1; save 300 10; save 60 10000表示在规定的时间内,执行了规定次数的写入或者修改操作,redis就会将数据同步到指定的磁盘文件中。比如900s 内做了一次更改,Redis 就会自动执行数据同步。
rdbcompressionyes/no当数据存储到本地数据库时是否要压缩数据,默认为yes
dbfilenamedump.rdb指定本地存储数据库的文件名,默认为dump.rdb
dir./指定本地数据库存放目录
slaveof < masterip> < masterport>主从复制配置选项当本机为slave服务时,设置master服务的IP地址以及端口,在redis启用时,它自动与master主机进行数据同步
requirepass默认关闭密码配置项,默认关闭,用于设置 Redis 连接密码。如果配置了连接密码,客户端连接 Redis 时需要通过 密码认证。
maxmemory < bytes>最大内存配置项指定redis最大内存限制,redis在启动时会把数据加载到内存中,达到最大内存后,redis会尝试清除已经到期或者即将到期的key,当此方法处理后,如果仍然到达最大内存设置,将无法再进行写入操作,但可以读取操作
appendfilenameappendonly.aof指定AOF持久化时保存数据的文件名,默认为appendonly.aof
glueoutputbufyes

开始测试连接

  1. 导入对应依赖
<!--导入jedis的包-->
    <dependencies>
    <!-- https://mvnrepository/artifact/redis.clients/jedis -->
    <dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
        <version>4.3.1</version>
    </dependency>
<!--fastjson-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.75</version>
        </dependency>
    </dependencies>
  1. 编码测试(这里测试的是远程云服务器上的redis,设置了密码需要加上)

    • 连接数据库
    • 操作命令
    • 断开连接
    public class TestPing {
        public static void main(String[] args) {
            //Jedis jedis = new Jedis("服务器的外网ip",端口号);
            Jedis jedis = new Jedis("*.***.76.***",6379);
            jedis.auth("设置的redis密码");
            System.out.println(jedis.ping());   //测试ping
        }
    }
    

    测试运行:

package com.xxz;
import redis.clients.jedis.Jedis;
import java.util.Set;
public class TestApi {
        public static void main(String[] args) {
            Jedis jedis = new Jedis("*.130.***.253", 6379);
            jedis.auth("***");
            System.out.println("清空数据:"+jedis.flushDB());
            System.out.println("判断某个键是否存在:"+jedis.exists("username"));
            System.out.println("新增<'username','zhangyouxiu'>的键值对:"+jedis.set("username","zhangyouxiu"));
            System.out.println("新增<'password','password'>的键值对:"+jedis.set("password","password"));
            System.out.println("系统中所有的键如下:");
            Set<String> keys = jedis.keys("*");
            System.out.println(keys);

            System.out.println("删除键password:"+jedis.del("password"));
            System.out.println("判断password是否存在:"+jedis.exists("password"));
            System.out.println("查看键username所存储的值的类型:"+jedis.type("username"));
            System.out.println("按索引查询:"+jedis.select(0));
            System.out.println("删除当前选择数据库中的所有key"+jedis.flushDB());
            System.out.println("返回当前数据库中key的数目:"+jedis.dbSize());
            System.out.println("删除所有数据库中的所有key:"+jedis.flushAll());
        }
    }

测试事务:

package com.xxz;
import org.json.JSONObject;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;

public class TestTX {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("*.***.76.***", 6379);
        jedis.auth("mima");
        jedis.flushDB();
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("name", "zhangyouxiu");
        jsonObject.put("age", "23");
        jsonObject.put("sex", "boy");
        Transaction multi = jedis.multi(); //  开启事务
        String user = jsonObject.toString();
        try {
            multi.set("user1", user);
            multi.set("user2", user);
            multi.exec();
        } catch (Exception e) {
            multi.discard();     // 出现问题,放弃事务
            e.printStackTrace();
        } finally {
            System.out.println(jedis.mget("user1", "user2"));
            jedis.close(); // 关闭连接
        }
    }
}

127.0.0.1:6379> KEYS *   # 去到远程redis查看  确实存进去了
1) "user1"
2) "user2"

SpringBoot整合

说明:从SpringBoot2.x之后,原先使用的 Jedis 被 lettuce 替代

Jedis:采用直连,模拟多个线程操作会出现安全问题。为避免此问题,需要使用Jedis Pool连接池!更像BIO模式

lettuce:采用netty网络框架,对象可以在多个线程中被共享,完美避免线程安全问题,减少线程数据,更像NIO模式

源码分析:

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
  @Bean
  @ConditionalOnMissingBean(name = "redisTemplate") // 我们可以自己定义一个redisTemplate来替换这个默认的!
    //@ConditionalOnMissingBean 
    //它是修饰bean的一个注解,主要实现的是,当你的bean被注册之后,如果而注册相同类型的bean,就不会成功,它会保证你的      bean只有一个,即你的实例只有一个。
  public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)
        throws UnknownHostException {
     // 默认的RedisTemplate直接使用此类内部默认设置操作数据,但是Redis对象需要序列化
      // 默认的 RedisTemplate 没有过多的设置,redis 对象都是需要序列化!
     // 两个泛型都是 object,object 的类型,我们后使用需要强制转换 大都是RedisTemplate<String, Object>
     RedisTemplate<Object, Object> template = new RedisTemplate<>();
     template.setConnectionFactory(redisConnectionFactory);
     return template;
  }

  @Bean
  @ConditionalOnMissingBean  // 由于 string 是redis中最常使用的类型,所以说单独提出来了一个bean! 	
  public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory)
        throws UnknownHostException {
     StringRedisTemplate template = new StringRedisTemplate();
     template.setConnectionFactory(redisConnectionFactory);
     return template;
  }
}

整合测试

  1. 导入依赖
  2. 配置连接 (即端口 外机 ip 和连接密码)
  3. 测试【出现中文乱码问题】
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
# 配置Redis
spring.redis.host=服务器的外网ip   # 连接地址 
spring.redis.port=6379  # 端口号
spring.redis.password=*****  # 因为远程连接你如果设置密码了 就需要密码登录

配置使用 lettuce 不要使用jedis 因为源码中 只有 lettuce注入成功了 jedis 未生效

这边的配置,需要注意的是,SpringBoot整合的是Lettuce,如果在配置文件中添加额外的配置,比如Redis的最大等待时间、超时时间等,在对应的RedisProperties类所映射的配置文件中,属性名称一定要加上带有lettuce,如果加上jedis,它默认不会生效

     @Resouce
     private RedisTemplate redisTempTate;
     @Test
     void contextLoads() {
    // redisTemplate 操作不同的数据类型,api和我们的指令是一样的
    //opsForVaTue       操作字符串 类似string
    // opsForList        操作List 类似List
    //opsForSet
    // opsForHash
    //opsForzset
    //opsForGeo
    // opsForHyperLogLog
    // 除了进本的操作,我们常用的方法都可以直接通过redisTemplate操作,比如事务,和基木的CRUD
    // 获取redis的连接对象
    //RedisConnection connection = redisTemplate.getConnectionFactory().getConnection():
    //connection.flushDb();
    //connection .fTushA110;
     redisTemplate.opsForValue().set("mykey""关注神说公众号"); //中文远程redis 会乱码  控制台正常输出
     System.out.printIn(redisTemplate.opsForValue().get("mykey"));
    }
}

原始序列化结果是控制台正常输出,但去云服务器看却是乱码

127.0.0.1:6379> keys *
1) "\xac\xed\x00\x05t\x00\x02k1"

原因就是需要使用序列化 与RedisTemplate默认序列化有关

展示RedisTemplate的部分源码

这边默认使用JDK的序列化方式,可以自定义一个配置类,采用其他的序列化方式我们可能使用的是 json 来序列化

  defaultSerializer = new JdkSerializationRedisSerializer(
      classLoader != null ? classLoader : this.getClass().getClassLoader());
   }

当我们自定义类的时候 默认的类便会失效

真实的开发中都会使用 json 来实现序列化

自定义的RedisTemplate模板 拿去即用

package com.xxz.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
public class RedisConfig {

    /**
     * 固定模板,拿去直接使用
     * @return template
     */

    // 编写自己的redisTemplate
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);

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

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

        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }
}

自定义 RedisTemplate 实现 Json 序列化

关于对象的保存:

json序列化操作步骤:

就是为了解决默认使用的 jdk 序列化导致的 本地控制台正常输出 而远程redis 输出的中文乱码问题

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

application.properties或者yaml中配置

# 配置Redis
spring.redis.host=服务器的外网ip   # 连接地址 
spring.redis.port=6379  # 端口号
spring.redis.password=*****  # 因为远程连接你如果设置密码了 就需要密码登录

使用序列化之后 远程再查看就会正常显示

测试

测试用的pojo:

package com.xxz.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.stereotype.Component;
import java.io.Serializable;
@Component
@AllArgsConstructor
@NoArgsConstructor
@Data
public class User implements Serializable {
    private String name;
    private Integer age;
}
package com.xxz.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
public class RedisConfig {

    /**
     * 固定模板,拿去直接使用
     * @return template
     */

    // 编写自己的redisTemplate
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);

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

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

        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }
}

测试

package com.xxz;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.xxz.pojo.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;

@SpringBootTest
class RedisJedisSpringbootApplicationTests {

    @Autowired
    @Qualifier("redisTemplate")  //  用来指定注入bean的名称
    private RedisTemplate redisTemplate;

    @Test
    public void test() throws JsonProcessingException {
        // 真实开发一般都是用json来传递对象
        User user = new User("xxz", 11);
        redisTemplate.opsForValue().set("user", user);
        System.out.println(redisTemplate.opsForValue().get("user"));
    }
}

控制台和远程reids 都会正常输出 而不会导致 远程端中文乱码

控制台输出结果

远程redis结果:

Redis.conf配置文件

1 单位 unit

unit 单位,Redis配置文件中的单位对大小写不敏感

启动的时候,就通过配置文件来启动。

2 包含 INCLUDES

包含 可以在Redis启动的时候再加载一些除了Redis.conf之外的其他的配置文件,和Spring的import,jsp的include类似

网络

3 网络 NETWORK

表示Redis启动时开放的端口默认与本机绑定

# bind 127.0.0.1   绑定的ip
# protected-mode no  保护模式  protected-mode yes  默认为yes修改为no表示可以开启远程访问 
# port 6379     端口设置
# timeout 0     服务器闲置多长时间(秒)后被关闭,如果这个这个数值为0,表示这个功能不起作用

通用配置 GENERAL

# daemonize yes      是否以守护进程的方式运行,即【后台运行方式】,一般默认为no,需要手动改为yes	不然一退出进程便会结束
# pidfile /var/run/redis_6379.pid   # 如果以后台方式运行我们需要指定一个pid文件,在Redis启动时创建,退出时删除


# 日志级别
# 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)   警告
loglevel notice
# 日志会生成文件 
logfile ""             # 这是生成的文件 日志的文件位置名
databases 16           # 数据库支持数量,默认16个
always-show-logo yes   # 是否一直显示logo

快照

4 快照 SNAPSHOTTING

持久化, 在规定的时间内,执行了多少次操作,则会持久化到文件 .rdb .aof

Redis是一个内存数据库,如果不采用持久化对数据进行保存,会出现断电即失的情况。

# 在900秒内,至少有一个key被修改(添加),就会进行持久化操作
save 900 1
# 在300秒内,至少有10个key被修改,就会进行持久化操作
save 300 10
# 在60秒内,至少有1万个key被修改,就会进行持久化操作【高并发场景】
save 60 10000
# 之后学习持久化 设置自己定义的
top-writes-on-bgsave-error yes   # 持久化的时候出现错误,是否持续工作

rdbcompression yes          # 是否压缩 rdb 文件,需要消耗CPU资源! 

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

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

复制

5 复制 REPLICATION

提示: 先看 一主而从的知识

一般情况下配置 只需要配置从机

一主二从 是集群的最低配置

而真实开发中是以配置文件配置集群的 可以永久有效

在每个 从机 的 REPLICATION 下设置配置文件 不用改

命令行配置属于暂时性配置,关机即失

安全 SECRULITY

6 安全 SECRULITY

可以在配置文件中设置Redis的登录密码 ,默认是没有密码的

config get requirepass        #  获取密码
config set requirepass ***    #  设置密码 

限制 CLIENT

7 限制 CLIENT

maxclients 10000     #   Redis允许存在的客户端的最大数量,默认有一万个

maxmemory <bytes>    #    Redis配置最大的内存容量	

maxmemory-policy noeviction    #  内存达到上限之后默认的处理策略

处理策略有以下几种 [但大部分不会让管理这里]

  • noeviction: 默认策略,不淘汰,永不过期,如果内存已满,添加数据是报错。
  • allkeys-lru: 在所有键中,选取最近最少使用的数据抛弃。
  • volatile-lru: 在设置了过期时间的所有键中,选取最近最少使用的数据抛弃。
  • allkeys-random: 在所有键中,随机抛弃删除。
  • volatile-random: 在设置了过期时间的所有键,随机抛弃删除。
  • volatile-ttl: 在设置了过期时间的所有键,抛弃存活时间最短的数据,删除即将过期的。

8 AOF设置 APPEND ONLY 模式

Redis 默认使用的是 RDB

AOF 其实就是将你的记录再记录一次 非常的缓慢 就如同追加。

appendonly no   # 模式默认不开启 aof 模式, 默认开启的是持久化模式是RDB,在大部分情况下,RDB的模式完全够用

appendfilename "appendonly.aof"   # aof 持久化文件的名字   rdb文件后缀是  .rdb

=======================================================
 对于 appendfsync 它有以下几个属性 :     同步: sync
appendfsync everysec # 数据不同步,每秒记录一次 默认执行的就是这个
# appendfsync always   表示每次修改都会进行数据同步,速度较慢,消耗性能
# appendfsync no       不执行 sync,这个时候操作系统自己同步数据,速度最快!!
=========================================================

Redis持久化

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

Redis 是内存数据库,如果不将内存中的数据库状态保存到磁盘,那么一旦服务器进程退出,服务器中的数据库状态也会消失。所

以 Redis 提供了持久化功能 !

RDB(RedisDataBase)

什么是RDB

在主从复制中,RDB就是放来备用的,在从机上面不占主机内存,相对来说方便一点,相对于AOF我们几乎不使用

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

Redis会单独创建( fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上

次持久化好的文件。整个过程中,主进程是不进行任何 IO 操作的。这就确保了极高的性能。如果需要进行大规模数据的恢复,且对于数

据恢复的完整性不是非常敏感,那RDB 方式要比 AOF 方式更加的高效。

fork是指复制一个和当前一模一样的进程作为原进程的子进程

RDB 的缺点是最后一次持久化后的数据【宕机的情况下】可能丢失

我们默认的就是使用的是RDB 一般情况不需要修改配置

【重要】

有时候生产环境我们会将这个文件备份,放置宕机的时候丢失最后一次数据文件

RDB保存的文件就是dump.rdb文件 都是在配置文件中的快照中配置

测试RDB操作

触发机制:

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

2 执行 flushall 命令,也会触发RDB规则,但是没有意义,因为文件内容为空

3 退出 redis ,也会产生 RDB 文件 (退出Redis默认执行save命令)

如何恢复 RDB 文件 !

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

2 · 查看需要存放的位置

[root@xxz bin]# redis-cli
127.0.0.1:6379> auth ****
OK
127.0.0.1:6379> config get dir
1) "dir"
2) "/usr/local/bin"   # 如果	在这个目录下存在 dump.rdb 文件,启动就会自动恢复其中的数据

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

RDB 优点:

  1. 适合大规模数据修复! 即恢复 dump.rdb
  2. 如果你对数据完整性的要求不高 !

缺点:

  1. 需要一定的时间间隔进行操作!如果Redis 意外宕机了 这个最后一次修改数据就没有了!
  2. fork 进程的时候 会占用一定的内存空间 !!

AOF (Append Only File)

它用日志的形式来记录每一个写操作,将我们的所有命令都记录下来,history,恢复的时候就把这个文件全部在执行一遍!

是什么?

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

Aof 保存的是 appendonly.aof

append 开启aof持久化

如何在配置文件中进行搜索:

首先 vim redis.conf 进入后按指令搜索

:/ 加搜索内容,n下一个

默认是不开启的,需要手动的去配置,修改完配置之后,只需重新启动就可

只需要将 appendoply 改为yes就开启了 aof !

注意: 如果AOF和RDB模式在配置文件中都有开启的话,为了保证数据的安全性,在Redis启动时会优先使用AOF

测试

开启 aof 持久化 即将no 改为 yes

并删除 dump.rdb 保证准确性

[root@xxz bin]# ls
cloud-id    cloud-init-per  jsondiff   jsonpointer  normalizer       redis-check-aof  redis-cli       redis-server
cloud-init  dump.rdb        jsonpatch  jsonschema   redis-benchmark  redis-check-rdb  redis-sentinel  xxzconfig
[root@xxz bin]# rm -rf dump.rdb   # 移除 rdb持久化保存的文件
[root@xxz bin]# ls    # 移除成功
cloud-id    cloud-init-per  jsonpatch    jsonschema  redis-benchmark  redis-check-rdb  redis-sentinel  xxzconfig
cloud-init  jsondiff        jsonpointer  normalizer  redis-check-aof  redis-cli        redis-server

再去将redis 关闭 shutdown 重启之后就会发现 出现了 aof 持久化文件

修复AOF文件

必要条件:

  • aof 持久化文件内容出错
  • 使用修复文件加命令 来修复 问题
  • redis-check-aof --fix appendonly.aof 修复指令

避免因为修改了aof文件 导致redis的启动失败 需要使用修复aof文件的指令

如果appendonly.aof内部发生错误,咋办?Redis中提供了一个可以修复aof文件的修复工具叫做redis-check-aof

          redis-check-aof --fix appendonly.aof    # 修复指令

# redis-check-aof --fix appendonly.aof 
0x             167: Expected \r\n, got: 6769
AOF analyzed: size=383, ok_up_to=351, diff=32
This will shrink the AOF from 383 bytes, with 32 bytes, to 351 bytes
Continue? [y/N]: y   # 输入y
Successfully truncated AOF # 表示修复成功

修复之后就能解决因为修改aof文件导致 不能启动redis 的问题

AOF 优点和缺点

appendonly no   # 模式默认不开启 aof 模式, 默认开启的是持久化模式是RDB,在大部分情况下,RDB的模式完全够用

appendfilename "appendonly.aof"   # aof 持久化文件的名字   rdb文件后缀是  .rdb

=======================================================
 对于 appendfsync 它有以下几个属性 :     同步: sync
appendfsync everysec # 数据不同步,每秒记录一次 默认执行的就是这个
# appendfsync always   表示每次修改都会进行数据同步,速度较慢,消耗性能
# appendfsync no       不执行 sync,这个时候操作系统自己同步数据,速度最快!!
=========================================================

AOF重写功能

重写规则说明

为了解决AOF文件体积膨胀的问题,Redis提供了AOF重写功能:Redis服务器可以创建一个新的AOF文件来替代现有的AOF文件,新旧两个文件所保存的数据库状态是相同的

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

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

优点:

1、每一次修改都同步,文件的完整会更加好!

2、每秒同步一次,可能会丢失一秒的数据

3、从不同步,效率最高的 !

缺点

1、每一次修改都同步,文件的完整会更加好!

2、每秒同步一次,可能会丢失一秒的数据

扩展:【理解重点】

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

2、AOF 持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据,AOF命令以Redis 协议追

加保存每次写的操作到文件末尾,Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大。

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

4、同时开启两种持久化方式:

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

    保存的数据集要完整。

  • RDB 的数据不实时,同时使用两者时服务器重启也只会找AOF文件,那要不要只使用AOF呢 ?作者建议不要,因为RDB更适合用于

    备份数据库(AOF在不断变化不好备份 ),快速重启,而不会有AOF可能潜在的Bug,留着作为一个万一的手段。

5、性能建议:

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

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

    了持续的IO,二是AOF rewrite 的最后将 rewrite 过程中产生的新数据写到新文件造成的阻塞几乎是不可避免的。只要硬盘许可,应该

    尽量减少AOF rewrite 的频率,AOF重写的基础大小默认值64M太小了,可以设到5G以上,默认超过原大小100%大小重写可以改到

    适当的数值。

  • 如果不Enable AOF,仅靠 Master-Slave Repllcation 实现高可用性也可以,能省掉一大笔IO,也减少了rewrite时带来的系统波动。

    代价是如果Master/Slave 同时宕掉(比入最暴力的宕机 断电情况 就会丢失所有的数据 如果有AOF 最多丢失两秒的数据),会丢失十

    几分钟的数据,启动脚本也要比较两个 Master{主机} / Slave {从机} 中的 RDB文件,载入较新的那个,微博就是这种架构。

Redis发布订阅(了解)

Redis 发布订阅(pub/sub)是一种**消息通信模式:**发送者pub)发送消息,订阅者(sub)接收消息。 比如微信,微博,关注系统

Redis 客户端可以订阅任意数量的频道。比如我们可以同时关注很多博主

订阅/发布消息图:

第一个:消息发送者(谁去发消息) 第二个 : 频道 第三个 : 消息订阅者!

消息队列不一定是redis做的 但是redis 可以做发布订阅

命令

这些都是用来实现数据通信的命令,现实中的场景可以是网络聊天室,广播等

实现测试:

测试

同时开启两个窗口来模拟订阅端发布端 但是都是同一端口

订阅端(我们自己)端口: 6379

首先订阅一个频道 一旦订阅便自动实时监听随时来自发布端的信息

订阅端语法:

subscribe 订阅频道名

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

发布端(关注的博主) 端口号: 6379

发布端语法:

publish + 自身频道名称 + 要发布的信息

127.0.0.1:6379> publish zhangyouxiu "hello,nihao"
(integer) 1
127.0.0.1:6379> publish zhangyouxiu "nihao ,redis"
(integer) 1

而订阅端会自动实时的接收这些信息

原理:

Redis是C语言编写,在实现消息的发布和订阅的时候,Redis将其封装在一个后缀名为点c的文件中,pubsub.c文件,

了解发布和订阅机制的底层实现,籍此加深对 Redis 的理解。Redis 通过 PUBLISH、SUBSCRIBE 和 PSUBSCRIBE 等命令实现发布和

订阅功能。通过 SUBSCRIBE 命令订阅某频道后,redis-server 里维护了一个字典,字典的键就是一个个频道,而字典的值则是一个链

表,链表中保存了所有订阅这个 channel 的客户端。SUBSCRIBE 命令的关键,就是将客户端添加到给定 channel 的订阅链表中

通过 PUBLISH 命令向订阅者发送消息,redis-server 会使用给定的频道作为键,在它所维护的 channel 字典中查找记录了订阅这个频道

的所有客户端的链表,遍历这个链表,将消息发布给所有订阅者。

Pub/Sub 从字面上理解就是发布 ( Publish ) 与订阅( Subscribe ),在Redis中,你可以设定对某一个key值进行消息发布及消息订阅,当一

个key值上进行了消息发布后,所有订阅它的客户端都会收到相应的消息。这一功能最明显的用法就是用作实时消息系统,比如普通的即

时聊天,群聊等功能。

使用场景

1 实时消息系统!

2 实时聊天(频道当作聊天室,将信息回显给所有人即可)

3 简易的 订阅,关注系统

稍微复杂的场景我们就需要使用消息中间件 RabbitMQ,RocketMQ,kafka… 等专业的来实现

Redis主从复制

从机不能写 只能读

命令行关键语法 非配置文件创建

# info replication     # 查询当前库的状态信息
# slaveof + 主机ip + 端口  找个认老大即可
127.0.0.1:6380> slaveof 127.0.0.1 6379   # slaveof + 主机ip + 端口  找个认老大即可

# slaveof no one    #  当主机宕机后 从机输入这个命令自己成为老大

聊redis的主从复制 更多的是在聊 哨兵模式 但需要从基础来

概念:

主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(master/leader),后者称为从节点

(slave/follower); 数据的复制是单向的只能由主节点到从节点 Master以写为主,Slave 以读为主。

切记

默认情况下,每台Redis服务器都是主节点 ; 且一个主节点可以有多个从节点(或没有从节点),但一个从节点只能有一个主节点。

主从复制的作用主要包括:

1、数据冗余: 主从复制实现了数据的热备份,是持久化之外的一种数据几余方式。

2、故障恢复: 当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复;实际上是一种服务的几余。

3、负载均衡: 在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务(即写Redis数据时应用连接主节

点,读Redis数据时应用连接从节点),分担服务器负载,尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis

服务器的并发量。

4、高可用(集群)基石:除了上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是Redis高可用的基础。

为何一主二从?

一般来说,要将Redis运用于工程项目中,只使用一台Redis是万万不能的(存在宕机的可能),原因如下:

1、从结构上,单个Redis服务器会发生单点故障,并且一台服务器需要处理所有的请求负载,压力较大;

2、从容量上,单个Redis服务器内存容量有限,就算一台Redis服务器内存容量为256G,也不能将所有内存用作Redis存储内存一般来

​ 说, **单台Redis最大使用内存不应该超过20G。**若单台超过,建议立即切换为 集群。

电商网站上的商品,一般都是一次上传,无数次浏览的,说专业点也就是"多读少写”

对于这种场景,我们可以使如下这种架构:

主从复制,读写分离! 80% 的情况下都是在进行 读操作 减缓服务器的压力。

一个集群的最低配 【三台机器 一主二从】

只要在公司中,主从复制就是必须要使用的,真实的项目中不可能使用单机的Redis ,因为会存在性能瓶颈。

配置环境

主从复制需要的环境配置

配置核心点:

只配置从库,不配置主库。因为Redis默认认为自己就是一个主库.

语法:

# info replication     # 查询当前库的状态信息
127.0.0.1:6379> INFO replication 
# Replication
role:master           # 角色 主机
connected_slaves:0    # 没有 从机     连接的节点数量:0
master_replid:e0cf93432565d1cbb4e719d8b1c818d846801e61
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0

创建最低配集群的步骤:

单机多集群【一台服务器上部署多个服务】正常实际开发是多机多服务

1、 开启一台主机 两台从机
2、 复制3个配置文件,然后修改对应的信息
  • 修改不同的端口号
  • pid 名字
  • 日志名字 即 log 名字
  • dump.rbd 的文件名字

先复制三份配置文件 分别为 79 80 81

[root@xxz xxzconfig]# cp redis.conf redis80.conf      # 复制文件
                    # mv redis999.conff redis81.conf  # 文件名称修改

然后去修改 不同的配置文件 以6380 为示例:

1 端口号 6379 -》 6380

2 pid 和 日志修改

3 rdb 持久化文件名称修改

3 、开启服务

修改完每个不同的配置文件后开始 启动【因为redis是以配置文件的方式启动的】

1 、 启动 6379 端口的redis

2 、启动 6380 端口的redis

3 、启动 6381 端口的redis

4 、 显示是否启动成功

集群环境搭建成功!

一主二从:

切记【默认情况下,每台Redis服务器都是主节点】

查看当前库的信息

# info replication     # 查询当前库的状态信息

未配置之前你不管开启多少服务 查询数据库状态都会是表示为 主机且连接节点 为 0

一般情况下配置 只需要配置从机

技巧方法 : 认老大找一个当老大

因为主机可以使用三台中任意一台 这里测试使用 (6379 作主机)从机 【6380 6381 作从机】那我们就去80 81 上面配置

设置主机:在从机中配置

方式一:命令行配置【演示】

当前方式属于 命令行配置 这种不是永久的 关机即失 是暂时的 而真实的集群开发配置是需要在配置文件中配置的

语法: slaveof + 主机ip + 端口

127.0.0.1:6380> slaveof 127.0.0.1 6379   # slaveof + 主机ip + 端口  找个认老大即可

然后再去查看 就会发现6379端口启动的服务作为主机了

role: 当前角色: 从机 分别在从机中认老大结束后 查看主机 是否连接了两个从机 配置几个 就有几个从机

真实集群开发的配置是在配置文件中配置的

方式二:配置文件【早期实际开发采用】

因为只有在配置文件中配置是 永久配置

这样的配置属于永久配置 只要你这个服务一启动 就代表为 从机

配置方式 去掉 配置文件中的注释 # 并在 replicaof 后将主机的 信息配置上去

细节

所有的 的请求 都是交给 主机来完成的 所有的 的请求都是交给 从机完成的

主机可以写,从机不能写只能读!

主机中的所有信息和数据,都会自动被从机保存!

测试

6379 主机

127.0.0.1:6379> set k1 v1     # 主机中设置值 测试从机中是不是都能自动获取到
127.0.0.1:6379> ok

6380 从机1

127.0.0.1:6380> keys *    #  从机1 6380
1) "k1"
127.0.0.1:6380> get k1
"v1"

6381 从机2

127.0.0.1:6381> keys *     #  从机2 6381
1) "k1"
127.0.0.1:6381> get k1
"v1"

结果显示都可以进行自动获取到主机创建的值 且从机只能读 不能写

没有配置哨兵模式

出现问题处: 假如主机宕机关机后 其他从机 还依旧只是从机 因此引入哨兵机制

情况一:主机断开

主机断开后, 从机 依旧连接到主机 但是因为主机没有写操作了 这个时候 如果主机恢复了, 从机 依旧可以直接获取到主机写入的信

息!【保证了高可用性】

情况二:从机断开

如果从机断开后 ,再重启 ,会默认恢复为主机 因为是从机以命令行配置的, 关机即失 而恢复后每一个都会默认为主节点。只要变为

从机,立马就会从主机中获取值!

复制原理

复制原理:

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

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

据文件到slave,并完成一次完全同步

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

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

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

以在从机中看到。

层层链路

上一个M(主)链接下一个S(从)!

这时候也就完成了我们的主从复制!

但是这两种方式都不会使用

因为都是人需要主动去操控,

会使用哨兵机制来完成主从复制

方式三:哨兵模式【核心】

如果没有老大了 ,这个时候能不能自己选择一个老大出来

客观的讲即为 谋朝篡位 即当 从机手动成为老大的时候 原先的主机就算恢复了 也没用 但会是独立的新主机存在

【手动操作】

当主机断掉后 从机可以使用这个命令来使自己成为主机 其他的节点就可以手动连接到最新的这个主节点

slaveof no one   # 从机让自己成为主机 在主机宕机的情况下   这也是手动输入的

本文标签: Redis