Redis

lishihuan大约 64 分钟

Redis

参考open in new window

https://blog.csdn.net/love521314123/article/details/120412583open in new window

本地视频

https://gitee.com/infiniteStars/redis/tree/masteropen in new window

Redis6课程大纲
Redis6课程大纲

1. NoSQL概述

1.1 为什么用NoSQL

NoSQL数据库的产生就是为 了解决大规模数据集合多种数据种类带来的挑战,尤其是大数据应用难题,包括超大规模数据的存储。

(例如谷歌或Facebook每天为他们的用户收集万亿比特的数据)。这些类型的数据存储不需要固定的模 式,无需多余操作就可以横向扩展。

image-20230308202817638
image-20230308202817638

什么是NoSQL

  • NoSQL = Not Only SQL,意思:不仅仅是SQL;
  • 泛指非关系型数据库

Nosql特点

  • 方便扩展(数据之间没有关系,很好扩展!)
  • 大数据量高性能(Redis一秒可以写8万次,读11万次)
  • 数据类型是多样型的!(不需要事先设计数据库,随取随用)

Nosql的四大分类

KV键值对

  • 新浪:Redis
  • 美团:Redis + Tair
  • 阿里、百度:Redis + Memcache

文档型数据库(bson数据格式)

  • CouchDB
  • MongoDB
    • MongoDB 是一个基于分布式文件存储的数据库。由 C++ 语言编写。旨在为 WEB 应用提供可 扩展的高性能数据存储解决方案。
    • MongoDB 是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰 富,最像关系数据库的

列存储数据库

  • HBase(大数据必学)
  • 分布式文件系统

图关系数据库

  • 它不是放图形的,放的是关系 比如:朋友圈社交网络、广告推荐系统等。
  • 专注于构建关系图谱 Neo4J, InfoGrid
image-20230308204902467
image-20230308204902467

2. Redis入门

2.1. 简介

  1. 基于内存存储
  2. Key-Value类型的数据库
  3. 可以用作数据库、缓存和消息中间件

Redis是什么

redis(Remote Dictionary Server ,即远程字典服务),是使用 C语言编写的,基于内存存储,Key-Value类型的数据库。它支持网络,亦可持久化。可用作数据库,高速缓存和消息队列代理。是为了解决高并发、高扩展,大数据存储等一系列的问题而产生的数据库解决方案。

Redis与其他key-value缓存产品有以下三个特点

  • Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使 用。
  • Redis不仅仅支持简单的 key-value 类型的数据,同时还提供 字符串(strings),散列(hashes),列表(lists),集合(sets),有序集合(sorted sets),数据流(steam)。
  • Redis支持数据的备份,即master-slave模式的数据备份。

Redis能干嘛

  • 内存存储和持久化 (运行在内存中但是可以持久化到磁盘) :redis支持异步将内存中的数据写到硬盘上

  • 发布、订阅消息系统

  • 地图信息分析

  • 定时器、计数器

特性

  • 多样的数据类型
  • 持久化
  • 集群
  • 事务

2.2 优势

  • 速度快性能极高:redis数据读写速度非常快,因为它把数据都读取到内存当中操作,而且redis是用C语言编写的,是最“接近”操作系统的语言,所以执行速度相对较快。Redis能读的速度是11W次/s,写的速度是81W次/s 。
  • 丰富的数据类型:Redis支持二进制案例的 Strings, Lists, Hashes, Sets 及 Ordered Sets 数据类型操作。
  • 支持操作原子性:redis的所有操作都是原子性,支持事务,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行。单个操作是原子性的。多个操作也支持事务,即原子性,通过MULTI和EXEC指令包起来。
  • 支持丰富的特性:redis支持主从复制,主机会自动将数据同步到从机,可以进行读写分离。Redis还支持 publish/subscribe,通知, key 过期等等特性。

2.3 redis的应用场景

  • redis由于数据的读取和操作都在内存当中操作,读写的效率较高,所以经常被用来做数据的缓存。把一些需要频繁访问的数据,而且在短时间之内不会发生变化的,放入redis中进行操作。从而提高用户的请求速度和降低网站的负载,降低数据库的读写次数,就把这些数据放到缓存中。
  • 一些常用的实时计算的功能。需要实时变化和展示的功能,就可以把相关数据放在redis中进行操作。大大提高效率。
  • 消息队列,经常用来构建类似实时聊天系统的功能,大大提高应用的可用性。

2.4 Redis缺点

是数据库容量受到物理内存的限制,不能用作海量数据的高性能读写,因此Redis适合的场景主要局限在较小数据量的高性能操作和运算上

2.5 windows 下安装

下载网站:https://github.com/microsoftarchive/redis/releasesopen in new window

  • 下载msi 则需要安装

  • 下载zip 免安装

image-20230308230135263
image-20230308230135263

启动 redis-cli.exe 运行客户端

image-20230308230655405
image-20230308230655405

2.6 Linux 下安装

安装步骤

下面是简单描述,具体安装步骤请看 安装步骤

注意如果安装失败 可能是因为gcc版本低了, redis6以上 gcc版本不能低于 5,我们需要升级它

$ yum install gcc-c++  ## 需要安装 gcc 通过 gcc -v 检测版本 (需要执行升级 gcc)
$ wget http://download.redis.io/releases/redis-6.0.6.tar.gz 
$ tar xzf redis-6.0.6.tar.gz	  ## 解压			
$ mv redis-6.0.6 /usr/local/redis ## 移动并且重命名
$ cd /usr/local/redis
$ make 								##编译
$ make PREFIX=/usr/local/redis install ## 安装

## 备份conf文件
$ mkdir /usr/local/redis/myconf_backup  ## 创建myconf_backup 文件夹
$ cp /usr/local/redis/redis.conf /usr/local/redis/myconf_backup/redis.conf  ### 备份conf文件
## 设置 后台启动
$ vi /usr/local/redis/redis.conf   ## 配置redis为后台启动 将daemonize no 改成daemonize yes
## 设置开机启动
$ chmod +x /etc/rc.d/rc.local ## 给 rc.local 赋权,否则 开机启动无法执行
$ vi /etc/rc.local   ## 在/rc.local 中添加   /usr/local/bin/redis-server /usr/local/redis/redis.conf (意思就是开机调用这段开启redis的命令)

## 卸载
$ rm -rf /usr/local/redis

2.7 Redis的安装查询

  1. 登录到您安装了Redis的服务器或计算机上。
  2. 打开命令行终端(Windows上是命令提示符或PowerShell,Linux/macOS上是终端)。
  3. 根据操作系统类型执行相应的命令:
    • 在Windows上,输入以下命令并按回车键: where redis-server
    • 在Linux/macOS上,输入以下命令并按回车键:which redis-server
  4. 系统将返回Redis服务器的安装路径。例如,对于Windows,它可能是类似于 C:\Program Files\Redis\redis-server.exe 的路径,而对于Linux/macOS,它可能是 /usr/bin/redis-server/usr/local/bin/redis-server

如果找不到可以尝试使用find命令来搜索Redis可执行文件,如下所示:

find / -name redis-server

这将在整个文件系统中搜索redis-server文件,并显示其路径

2.8 基础知识说明

基础

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

## 查看 redis.conf ,里面有默认的配置
databases 16

select命令切换数据库

127.0.0.1:6379> select 7
OK
127.0.0.1:6379[7]>
# 不同的库可以存不同的数据

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

127.0.0.1:6379> dbsize
(integer) 0
127.0.0.1:6379> set name1 li
OK
127.0.0.1:6379> set name2 lihuan
OK
127.0.0.1:6379> dbsize
(integer) 2

keys * 查询所有的key

127.0.0.1:6379> keys *
1) "name"
2) "ceshi"
127.0.0.1:6379> 

flushdb:清空当前库

Flushall:清空全部的库

为什么redis是单线程

redis是将所有的数据都放在内存中。对于内存系统来说,没有上下文切换效率最高,而多线程会进行上下文切换(CPU做的)。所以,单线程去操作是效率最高的。

五大数据类型

它支持多种类型的数据结构,如 字符串(strings)open in new window散列(hashes)open in new window列表(lists)open in new window集合(sets)open in new window有序集合(sorted sets)open in new window 与范围查询, bitmapsopen in new windowhyperloglogsopen in new window地理空间(geospatial)open in new window 索引半径查询

http://c.biancheng.net/redis_command/open in new window

Redis-Key 一些基本命令

exists name    #是否存在name这个属性
expire name 10  #10秒后过期
ttl name    #查看当前key的剩余时间
type name  #查看当前数据类型

1. String (字符串类型)

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

命令说明
set 命令open in new window设置给定 key 的值。如果 key 已经存储其他值, SET 就覆写旧值,且无视类型。设置操作成功完成时,返回 OK
get 命令open in new window获取指定 key 的值。如果 key 不存在,返回 nil 。如果key 储存的值不是字符串类型,返回一个错误。
getset 命令open in new window设置指定 key 的值,并返回 key 旧的值。
mset 命令open in new windowMset 命令用于同时设置一个或多个 key-value 对
mget 命令open in new window返回所有(一个或多个)给定 key 的值。 如果给定的 key 里面,有某个 key 不存在,那么这个 key 返回特殊值 nil 。
setex 命令open in new window将值 value 关联到 key ,并将 key 的过期时间设为 seconds (以秒为单位)。
setnx 命令open in new windowkey不存在创建,存在则创建失败
msetnx 命令open in new windowMsetnx 命令用于所有给定 key 都不存在时,同时设置一个或多个 key-value 对。 原子性操作,要么一起成功,要么一起失败
setrange 命令open in new window用 value 参数覆写给定 key 所储存的字符串值,从偏移量 offset 开始。(替换
getrange 命令open in new window指定开始和结束,返回子字符串。不改变原来的值,只是截取指定范围的子字符串 返回截取
getrange key 0 -1 # 获取所有的字符串
strlen 命令open in new window获取指定 key 所储存的字符串值的长度。当 key 储存的不是字符串值时,返回一个错误。
psetex 命令open in new windowpsetex 命令以毫秒为单位设置 key 的生存时间。 ## expire name 10 #10秒后过期
incr 命令open in new window将 key 中储存的数字值增一。
incrby 命令open in new window将 key 所储存的值加上给定的增量值(increment) 。
incrbyfloat 命令open in new window将 key 所储存的值加上给定的浮点增量值(increment) 。
decr 命令open in new window将 key 中储存的数字值减一。
decrby 命令open in new windowkey 所储存的值减去给定的减量值(decrement) 。
append 命令open in new window追加字符串,如果key 不存在则执行创建

常规key-value缓存应用: 常规计数:微博数,粉丝数等。

set key1 v1			 # 添加 一个 key为key1 value为 v1 的字符串	
append key1 "hello"   # 在后边添加内容,双引号可以加也可以不加(追加)
strlen key1  # 获取字符串的长度

=============数值加减==================
set views 0
incr views  #增加1(自增1)
decr views  #降低一(自减1)
incrby views 10  # 一次增加10 (10是步长,一次增加10)
decrby views 5  #一次减少5

============================
getrange key1 0 3   # 获取【0,3】的字符串
getrange key1 0 -1   # 获取所有的字符串
setrange key1  1 xx   # 从1开始往后替换

============================
setex key3 30 123   # 设置30秒后自动过期
setnx  mykey  hello  # key不存在创建,存在则创建失败
	## 如果 name存在,则返回的是 0 
    127.0.0.1:6379> setnx name 123 ## 此时因为name存在所以返回 0
    (integer) 0
    127.0.0.1:6379> setnx name2 123 ## 因为name2 不存在则设置成功 返回 OK
    OK

===========================
mset key1 v1 key2 v2 key3 v3  #批量创建key
mget key1 key2 key3
msetnx key1 v1 key4 v4   # msetnx 原子性操作,要么一起成功,要么一起失败

==========================
set user:1 {name:dz,age:18}   # 创建一个对象
get user:1    # "{name:dz,age:3}"

2.列表(list)

Redis列表是按插入顺序排序的字符串列表。你可以添加一个元素导列表的头部(左边)或者尾部(右边)。一个列表最多可以包含 232- 1 个元素(4294967295,每个列表超过40亿个元素)。

命令说明
lpush 命令open in new window将一个或多个值插入到列表头部。 如果 key 不存在,一个空列表会被创建并执行 LPUSH 操作。 当 key 存在但不是列表类型时,返回一个错误。
rpush 命令open in new windowRpush 命令用于将一个或多个值插入到列表的尾部(最右边)。
lpushx 命令open in new window将一个或多个值插入到已存在的列表头部,列表不存在时操作无效。
rpushx 命令open in new window将一个或多个值插入到已存在的列表尾部(最右边)。如果列表不存在,操作无效。
lpop 命令open in new window移除并返回列表的第一个元素。
rpop 命令open in new window移除并返回列表的最后一个元素。
rpoplpush 命令open in new window移除列表的最后一个元素,并将该元素添加到另一个列表并返回。
lrem 命令open in new window根据参数 COUNT 的值,移除列表中与参数 VALUE 相等的元素
llen 命令open in new window返回列表的长度。 如果列表 key 不存在,则 key 被解释为一个空列表,返回 0 。 如果 key 不是列表类型,返回一个错误。
lindex 命令open in new window通过索引获取列表中的元素。你也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。
linsert 命令open in new window在列表的元素前或者后插入元素。 当指定元素不存在于列表中时,不执行任何操作。 当列表不存在时,被视为空列表,不执行任何操作。 如果 key 不是列表类型,返回一个错误。
lset 命令open in new window通过索引来设置元素的值。
lrange 命令open in new window返回列表中指定区间内的元素,区间以偏移量 START 和 END 指定。其中 0 表示列表的第一个元素, 1 表示列表的第二个元素。-1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素
ltrim 命令open in new window对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除。会修改元素list的大小
blpop 命令open in new window移出并获取列表的第一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。
brpop 命令open in new window移出并获取列表的最后一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。
brpoplpush 命令open in new window从列表中弹出一个值,将弹出的元素插入到另外一个列表中并返回它; 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。

Lrem 根据参数 COUNT 的值,移除列表中与参数 VALUE 相等的元素。

COUNT 的值可以是以下几种:

  • count > 0 :从表头开始向表尾搜索,移除与 VALUE 相等的元素,数量为 COUNT 。
  • count < 0 :从表尾开始向表头搜索,移除与 VALUE 相等的元素,数量为 COUNT 的绝对值。
  • count = 0 :移除表中所有与 VALUE 相等的值。
# ===================================================
# Lpush:将一个或多个值插入到列表头部。(左)
# rpush:将一个或多个值插入到列表尾部。(右)
# lrange:返回列表中指定区间内的元素,区间以偏移量 START 和 END 指定。
# 其中 0 表示列表的第一个元素, 1 表示列表的第二个元素,以此类推。
# 你也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此
类推。
# ===================================================
127.0.0.1:6379> lpush list l1
(integer) 1
127.0.0.1:6379> lpush list l2
(integer) 2
127.0.0.1:6379> lpush list l3
(integer) 3
127.0.0.1:6379> lpush list l4
(integer) 4
127.0.0.1:6379> rpush list l0  ## 添加
(integer) 5
127.0.0.1:6379> lrange list 0 -1 ##查询list 全部数据 0表示从第一位开始,-1 表示倒数第一位。
1) "l4"
2) "l3"
3) "l2"
4) "l1"
5) "l0"

# ===================================================
# lpop 命令用于移除并返回列表的第一个元素。当列表 key 不存在时,返回 nil 。
# rpop 移除列表的最后一个元素,返回值为移除的元素。
# ===================================================

127.0.0.1:6379> lpop list
"l4"
127.0.0.1:6379> rpop list
"l0"
127.0.0.1:6379> lrange list 0 -1
1) "l3"
2) "l2"
3) "l1"
# ===================================================
# Lindex,按照索引下标获得元素(-1代表最后一个,0代表是第一个)
# ===================================================
127.0.0.1:6379> lindex list 0 ## 查询右侧第一个,数据的顺序是l3, l2,l1 所以从左侧第一个取的 l3
"l3"
127.0.0.1:6379> lrange list 0 -1
1) "l3"
2) "l2"
3) "l1"
127.0.0.1:6379> llen list ## 查询长度
(integer) 3
# ===================================================
# lrem key 根据参数 COUNT 的值,移除列表中与参数 VALUE 相等的元素。
# ===================================================
127.0.0.1:6379> RPUSH mylist "hello"
(integer) 1
127.0.0.1:6379> RPUSH mylist "hello"
(integer) 2
127.0.0.1:6379> RPUSH mylist "foo"
(integer) 3
127.0.0.1:6379> RPUSH mylist "hello"
(integer) 4
127.0.0.1:6379> LREM mylist -2 "hello" ## count=-2 表示从表尾开始向表头搜索,移除value值为 hello的元素,数量为 COUNT 的绝对值,也即|-2| =2 
(integer) 2
127.0.0.1:6379> lrange mylist 0 -1 ## cong 
1) "hello"
2) "foo"
# ===================================================
# Ltrim key 对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区
间之内的元素都将被删除。
# ===================================================
127.0.0.1:6379> lpush list l1
(integer) 1
127.0.0.1:6379> lpush list l2
(integer) 2
127.0.0.1:6379> lpush list l3
(integer) 3
127.0.0.1:6379> lpush list l4
(integer) 4
127.0.0.1:6379> ltrim list 0 2 ## 注意此时list 元素 l4,l3,l2,l1  只保留从左下表为0 开始,到下标为 2的元素
OK
127.0.0.1:6379> lrange list 0 -1
1) "l4"
2) "l3"
3) "l2"
# ===================================================
# rpoplpush 移除列表的最后一个元素,并将该元素添加到另一个列表并返回。
# ===================================================
127.0.0.1:6379> rpush mylist "hello"
(integer) 1
127.0.0.1:6379> rpush mylist "foo"
(integer) 2
127.0.0.1:6379> rpush mylist "bar"
(integer) 3
127.0.0.1:6379> rpoplpush mylist myotherlist
"bar"
127.0.0.1:6379> lrange mylist 0 -1
1) "hello"
2) "foo"
127.0.0.1:6379> lrange myotherlist 0 -1
1) "bar"

# ===================================================
# lset key index value 将列表 key 下标为 index 的元素的值设置为 value 。
# ===================================================
127.0.0.1:6379> exists list # 对空列表(key 不存在)进行 LSET
(integer) 0
127.0.0.1:6379> lset list 0 item # 报错
(error) ERR no such key
127.0.0.1:6379> lpush list "value1" # 对非空列表进行 LSET
(integer) 1
127.0.0.1:6379> lrange list 0 0
1) "value1"
127.0.0.1:6379> lset list 0 "new" # 更新值
OK
127.0.0.1:6379> lrange list 0 0
1) "new"
127.0.0.1:6379> lset list 1 "new" # index 超出范围报错
(error) ERR index out of range
# ================================

3. Set 命令

Redis的Set是String类型的无序集合,它是通过HashTable实现的

Redis set 数据类型由键值对组成,这些键值对具有无序唯一的性质,这与 Python 的 set 相似。当集合中最后一个元素被移除之后,该数据结构也会被自动删除,内存也同样会被收回。

由于 set 集合可以实现去重,因此它有很多适用场景,比如用户抽奖活动,使用 set 集合可以保证同一用户不被第二次选中。

通过SMEMBERS 进行查看

Redis set 常用的命令如下所示:

命令说明
SADDopen in new window向集合中添加一个或者多个元素,并且自动去重
SCARDopen in new window返回集合中元素的个数
SDIFFopen in new window求两个或对多个集合的差集
SDIFFSTOREopen in new window求两个集合或多个集合的差集,并将结果保存到指定的集合(key)中 SDIFFSTORE new_myset myset1 myset2
SINTERopen in new window求两个或多个集合的交集(返回的是集合交际的元素)
SINTERSTOREopen in new window求两个或多个集合的交集,并将结果保存到指定的集合(key)中
SMEMBERSopen in new window查看集合中所有元素
SMOVEopen in new window将集合中的元素移动到指定的集合中
SPOPopen in new window弹出指定数量的元素
SRANDMEMBERopen in new window随机从集合中返回指定数量的元素,默认返回 1个
SREMopen in new window删除一个或者多个元素,若元素不存在则自动忽略
SUNIONopen in new window求两个或者多个集合的并集
SUNIONSTOREopen in new window求两个或者多个集合的并集,并将结果保存到指定的集合(key)中
# ===================================================
# sadd 将一个或多个成员元素加入到集合中,不能重复
# smembers 返回集合中的所有的成员。
# sismember 命令判断成员元素是否是集合的成员。
# ===================================================
127.0.0.1:6379> sadd myset "hello"
(integer) 1
127.0.0.1:6379> sadd myset "kuangshen"
(integer) 1
127.0.0.1:6379> sadd myset "kuangshen"
(integer) 0
127.0.0.1:6379> SMEMBERS myset
1) "kuangshen"
2) "hello"
127.0.0.1:6379> SISMEMBER myset "hello"
(integer) 1
127.0.0.1:6379> SISMEMBER myset "world"
(integer) 0
# ===================================================
# scard,获取集合里面的元素个数
# ===================================================
127.0.0.1:6379> scard myset
(integer) 2
# ===================================================
# srem key value 用于移除集合中的一个或多个成员元素
# ===================================================
127.0.0.1:6379> srem myset "kuangshen"
(integer) 1
127.0.0.1:6379> SMEMBERS myset
1) "hello"
# ===================================================
# srandmember key 命令用于返回集合中的一个随机元素。
# ===================================================
127.0.0.1:6379> SMEMBERS myset
1) "kuangshen"
2) "world"
3) "hello"
127.0.0.1:6379> SRANDMEMBER myset
"hello"
127.0.0.1:6379> SRANDMEMBER myset 2
1) "world"
2) "kuangshen"
127.0.0.1:6379> SRANDMEMBER myset 2
1) "kuangshen"
2) "hello"
# ===================================================
# spop key 用于移除集合中的指定 key 的一个或多个随机元素
# ===================================================
127.0.0.1:6379> SMEMBERS myset
1) "kuangshen"
2) "world"
3) "hello"
127.0.0.1:6379> spop myset
"world"
127.0.0.1:6379> spop myset
"kuangshen"
127.0.0.1:6379> spop myset
"hello"
# ===================================================
# smove SOURCE DESTINATION MEMBER
# 将指定成员 member 元素从 source 集合移动到 destination 集合。
# ===================================================
127.0.0.1:6379> sadd myset "hello"
(integer) 1
127.0.0.1:6379> sadd myset "world"
(integer) 1
127.0.0.1:6379> sadd myset "kuangshen"
(integer) 1
127.0.0.1:6379> sadd myset2 "set2"
(integer) 1
127.0.0.1:6379> smove myset myset2 "kuangshen"
(integer) 1
127.0.0.1:6379> SMEMBERS myset
1) "world"
2) "hello"
127.0.0.1:6379> SMEMBERS myset2
1) "kuangshen"
2) "set2"
# ===================================================
- 数字集合类
- 差集: sdiff
- 交集: sinter
- 并集: sunion
# ===================================================
127.0.0.1:6379> sadd key1 "a"
(integer) 1
127.0.0.1:6379> sadd key1 "b"
(integer) 1
127.0.0.1:6379> sadd key1 "c"
(integer) 1
127.0.0.1:6379> sadd key2 "c"
(integer) 1
127.0.0.1:6379> sadd key2 "d"
(integer) 1
127.0.0.1:6379> sadd key2 "e"
(integer) 1
127.0.0.1:6379> SDIFF key1 key2 # 差集
1) "a"
2) "b"
127.0.0.1:6379> SINTER key1 key2 # 交集
1) "c"
127.0.0.1:6379> SUNION key1 key2 # 并集
1) "a"
2) "b"
3) "c"
4) "e"
5) "d"

# ===================================================
# smove SOURCE DESTINATION MEMBER
# 将指定成员 member 元素从 source 集合移动到 destination 集合。
# ===================================================
127.0.0.1:6379> SADD myset1 www.biancheng.net www.baidu.com www.jd.com  #向集合添加元素
(integer) 3
127.0.0.1:6379> SADD myset2 www.biancheng.net www.baidu.com stackoverflow.com
(integer) 3
127.0.0.1:6379> SMEMBERS myset1 #查看所有元素
1) "www.baidu.com"
2) "www.biancheng.net"
3) "www.jd.com"
127.0.0.1:6379> SMEMBERS myset2
1) "stackoverflow.com"
2) "www.baidu.com"
3) "www.biancheng.net"
127.0.0.1:6379> SDIFFSTORE new_myset myset1 myset2  # 将成员保存到mysite目标集合中
(integer) 1
127.0.0.1:6379> SMEMBERS new_myset
1) "www.jd.com"

Hash 命令

Hash(哈希散列)是 Redis 基本数据类型之一,它以字符串映射表的形式来进行存储。Hash 特别适合用于存储对象。常用的命令如下所示:

kv模式不变,但V是一个键值对

命令说明
HDELopen in new window用于删除一个或多个哈希表字段
HEXISTSopen in new window用于确定哈希字段是否存在
HGETopen in new window获取存储在 key 中的哈希字段的值
HGETALLopen in new window获取存储在 key 中的所有哈希字段值
HINCRBYopen in new window为存储在 key 中的哈希表指定字段做整数增量运算
HKEYSopen in new window获取存储在 key 中的哈希表的所有字段
HLENopen in new window获取存储在 key 中的哈希表的字段数量
HSETopen in new window用于设置存储在 key 中的哈希表字段的值
HVALSopen in new window用于获取哈希表中的所有值
# ===================================================
# hset、hget 命令用于为哈希表中的字段赋值 。
# hmset、hmget 同时将多个field-value对设置到哈希表中。会覆盖哈希表中已存在的字段。
# hgetall 用于返回哈希表中,所有的字段和值。
# hdel 用于删除哈希表 key 中的一个或多个指定字段
# ===================================================
127.0.0.1:6379> hset myhash field1 "kuangshen"
(integer) 1
127.0.0.1:6379> hget myhash field1
"kuangshen"
127.0.0.1:6379> HMSET myhash field1 "Hello" field2 "World"
OK
127.0.0.1:6379> HGET myhash field1
"Hello"
127.0.0.1:6379> HGET myhash field2
"World"
127.0.0.1:6379> hgetall myhash
1) "field1"
2) "Hello"
3) "field2"
4) "World"
127.0.0.1:6379> HDEL myhash field1
(integer) 1
127.0.0.1:6379> hgetall myhash
1) "field2"
2) "World"
# ===================================================
# hlen 获取哈希表中字段的数量。
# ===================================================
127.0.0.1:6379> hlen myhash
(integer) 1
127.0.0.1:6379> HMSET myhash field1 "Hello" field2 "World"
OK
127.0.0.1:6379> hlen myhash
(integer) 2
# ===================================================
# hexists 查看哈希表的指定字段是否存在。
# ===================================================
127.0.0.1:6379> hexists myhash field1
(integer) 1
127.0.0.1:6379> hexists myhash field3
(integer) 0
# ===================================================
# hkeys 获取哈希表中的所有域(field)。
# hvals 返回哈希表所有域(field)的值。
# ===================================================
127.0.0.1:6379> HKEYS myhash
1) "field2"
2) "field1"
127.0.0.1:6379> HVALS myhash
1) "World"
2) "Hello"
# ===================================================
# hincrby 为哈希表中的字段值加上指定增量值。
# ===================================================
127.0.0.1:6379> hset myhash field 5
(integer) 1
127.0.0.1:6379> HINCRBY myhash field 1
(integer) 6
127.0.0.1:6379> HINCRBY myhash field -1
(integer) 5
127.0.0.1:6379> HINCRBY myhash field -10
(integer) -5
# ===================================================
# hsetnx 为哈希表中不存在的的字段赋值 。
# ===================================================
127.0.0.1:6379> HSETNX myhash field1 "hello"
(integer) 1 # 设置成功,返回 1 。
127.0.0.1:6379> HSETNX myhash field1 "world"
(integer) 0 # 如果给定字段已经存在,返回 0 。
127.0.0.1:6379> HGET myhash field1
"hello"

Zset 命令

zset 是 Redis 提供的最具特色的数据类型之一,首先它是一个 set,这保证了内部 value 值的唯一性,其次它给每个 value 添加了一个 score(分值)属性,通过对分值的排序实现了有序化。比如用 zset 结构来存储学生的成绩,value 值代表学生的 ID,score 则是的考试成绩。我们可以对成绩按分数进行排序从而得到学生的的名次。

下面列出了 zset 的常用命令,如下所示:

命令说明
ZADDopen in new window用于将一个或多个成员添加到有序集合中,或者更新已存在成员的 score 值
ZCARDopen in new window获取有序集合中成员的数量
ZCOUNTopen in new window用于统计有序集合中指定 score 值范围内的元素个数
ZINCRBYopen in new window用于增加有序集合中成员的分值
ZINTERSTOREopen in new window求两个或者多个有序集合的交集,并将所得结果存储在新的 key 中
ZRANGEopen in new window返回有序集合中指定索引区间内的成员数量
ZRANGEBYLEXopen in new window返回有序集中指定字典区间内的成员数量
ZRANGEBYSCOREopen in new window返回有序集合中指定分数区间内的成员
ZRANKopen in new window返回有序集合中指定成员的排名
ZREMopen in new window移除有序集合中的一个或多个成员
ZREMRANGEBYRANKopen in new window移除有序集合中指定排名区间内的所有成员
ZREMRANGEBYSCOREopen in new window移除有序集合中指定分数区间内的所有成员
ZREVRANGEopen in new window返回有序集中指定区间内的成员,通过索引,分数从高到低
ZREVRANKopen in new window返回有序集合中指定成员的排名,有序集成员按分数值递减(从大到小)排序
ZSCOREopen in new window返回有序集中,指定成员的分数值
ZUNIONSTOREopen in new window求两个或多个有序集合的并集,并将返回结果存储在新的 key 中
# ===================================================
# zadd 将一个或多个成员元素及其分数值加入到有序集当中。
# zrange 返回有序集中,指定区间内的成员
# ===================================================
127.0.0.1:6379> zadd myset 1 "one"
(integer) 1
127.0.0.1:6379> zadd myset 2 "two" 3 "three"
(integer) 2
127.0.0.1:6379> ZRANGE myset 0 -1
1) "one"
2) "two"
3) "three"
# ===================================================
# zrangebyscore 返回有序集合中指定分数区间的成员列表。有序集成员按分数值递增(从小到大)
次序排列。
# ===================================================
127.0.0.1:6379> zadd salary 2500 xiaoming
(integer) 1
127.0.0.1:6379> zadd salary 5000 xiaohong
(integer) 1
127.0.0.1:6379> zadd salary 500 kuangshen
(integer) 1
# Inf无穷大量+∞,同样地,-∞可以表示为-Inf。
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf # 显示整个有序集
1) "kuangshen"
2) "xiaoming"
3) "xiaohong"
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf withscores # 递增排列
1) "kuangshen"
2) "500"
3) "xiaoming"
4) "2500"
5) "xiaohong"
6) "5000"
127.0.0.1:6379> ZREVRANGE salary 0 -1 WITHSCORES # 递减排列
1) "xiaohong"
2) "5000"
3) "xiaoming"
4) "2500"
5) "kuangshen"
6) "500"
127.0.0.1:6379> ZRANGEBYSCORE salary -inf 2500 WITHSCORES # 显示工资 <=2500
的所有成员
1) "kuangshen"
2) "500"
3) "xiaoming"
4) "2500"
# ===================================================
# zrem 移除有序集中的一个或多个成员
# ===================================================
127.0.0.1:6379> ZRANGE salary 0 -1
1) "kuangshen"
2) "xiaoming"
3) "xiaohong"
127.0.0.1:6379> zrem salary kuangshen
(integer) 1
127.0.0.1:6379> ZRANGE salary 0 -1
1) "xiaoming"
2) "xiaohong"
# ===================================================
# zcard 命令用于计算集合中元素的数量。
# ===================================================
127.0.0.1:6379> zcard salary
(integer) 2
OK
# ===================================================
# zcount 计算有序集合中指定分数区间的成员数量。
# ===================================================
127.0.0.1:6379> zadd myset 1 "hello"
(integer) 1
127.0.0.1:6379> zadd myset 2 "world" 3 "kuangshen"
(integer) 2
127.0.0.1:6379> ZCOUNT myset 1 3
(integer) 3
127.0.0.1:6379> ZCOUNT myset 1 2
(integer) 2
# ===================================================
# zrank 返回有序集中指定成员的排名。其中有序集成员按分数值递增(从小到大)顺序排列。
# ===================================================
127.0.0.1:6379> zadd salary 2500 xiaoming
(integer) 1
127.0.0.1:6379> zadd salary 5000 xiaohong
(integer) 1
127.0.0.1:6379> zadd salary 500 kuangshen
(integer) 1
127.0.0.1:6379> ZRANGE salary 0 -1 WITHSCORES # 显示所有成员及其 score 值
1) "kuangshen"
2) "500"
3) "xiaoming"
4) "2500"
5) "xiaohong"
6) "5000"
127.0.0.1:6379> zrank salary kuangshen # 显示 kuangshen 的薪水排名,最少
(integer) 0
127.0.0.1:6379> zrank salary xiaohong # 显示 xiaohong 的薪水排名,第三
(integer) 2
# ===================================================
# zrevrank 返回有序集中成员的排名。其中有序集成员按分数值递减(从大到小)排序。
# ===================================================
127.0.0.1:6379> ZREVRANK salary kuangshen # 狂神第三
(integer) 2
127.0.0.1:6379> ZREVRANK salary xiaohong # 小红第一
(integer) 0

三种特殊数据类型

Geospatial 地理位置

Redis GEO 有很多应用场景,举一个简单的例子,你一定点过外卖,或者用过打车软件,在这种 APP上会显示“店家距离你有多少米”或者“司机师傅距离你有多远”,类似这种功能就可以使用 Redis GEO 实现。数据库中存放着商家所处的经纬度,你的位置则由手机定位获取,这样 APP 就计算出了最终的距离。再比如微信中附近的人、摇一摇、实时定位等功能都依赖地理位置实现。

GEO 主要用于存储地理位置信息(纬度、经度、名称)添加到指定的key中。

在 Redis 3.2 版本中,新增了存储地理位置信息的功能,即 GEO(英文全称 geographic),它的底层通过 Redis 有序集合(zset)实现。不过 Redis GEO 并没有与 zset 共用一套的命令,而是拥有自己的一套命令。

Redis GEO 提供了 6 个常用命令:

序号命令说明
1GEOADD将指定的地理空间位置(纬度、经度、名称)添加到指定的 key 中。
2GEOPOS从 key 里返回所有给定位置元素的位置(即经度和纬度)
3GEODIST返回两个地理位置间的距离,如果两个位置之间的其中一个不存在, 那么命令返回空值。
4GEORADIUS根据给定地理位置坐标(经纬度)获取指定范围内的地理位置集合。
5GEORADIUSBYMEMBER根据给定地理位置(具体的位置元素)获取指定范围内的地理位置集合。
6GEOHASH获取一个或者多个的地理位置的 GEOHASH 值。
7ZREM通过有序集合的 zrem 命令实现对地理位置信息的删除。

1) GEOADD

将指定的地理空间位置(经度、纬度、名称)添加到指定的 key 中。语法格式如下:

GEOADD key longitude latitude member [longitude latitude member ...]  
  • longitude:位置地点所处的经度;
  • latitude:位置地点所处的纬度;
  • member:位置名称。

将给定的经纬度的位置名称(经度、纬度、名称)与 key 相对应,这些数据以有序集合的形式进行储存。

GEOADD命令以标准的x,y形式接受参数, 所以用户必须先输入经度,然后再输入纬度。GEOADD命令能够记录的坐标数量是有限的,如果位置非常接近两极(南极/北极)区域,那么将无法被索引到。因此当您输入经纬度时,需要注意以下规则:

  • 有效的经度介于 -180 度至 180 度之间。
  • 有效的纬度介于 -85.05112878 度至 85.05112878 度之间。

注意:如果您输入一个超出范围的经纬度时,GEOADD 命令将返回一个错误。

#添加城市地理位置
127.0.0.1:6379> geoadd city 116.20 39.56 beijing 120.52 30.40 shanghai
(integer) 2
#查询城市地理位置
127.0.0.1:6379> GEOPOS city beijing shanghai
1) 1) "116.19999736547470093"
   2) "39.56000019952067248"
2) 1) "120.52000075578689575"
   2) "30.39999952668997452"

2) GEODIST

该命令用于获取两个地理位置间的距离。返回值为双精度浮点数,其语法格式如下:

GEODIST key member1 member2 [unit]

参数 unit 是表示距离单位,取值如下所示:

  • m 表示单位为米;
  • km 表示单位为千米;
  • mi 表示单位为英里;
  • ft 表示单位为英尺。

注意:如果没有指出距离单位,那么默认取值为m。示例如下:

127.0.0.1:6379> GEODIST city beijing shanghai
"1091868.8970"
127.0.0.1:6379> GEODIST city beijing shanghai km
"1091.8689"
127.0.0.1:6379> GEODIST city beijing shanghai mi
"678.4576"

注意:计算举例时存在 0.5% 左右的误差,这是由于 Redis GEO 把地球假设成了完美的球体。

3) GEORADIUS

以给定的经纬度为中心,计算出 key 包含的地理位置元素与中心的距离不超过给定最大距离的所有位置元素,并将其返回。

GEORADIUS key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC]

参数说明:

  • WITHDIST :在返回位置元素的同时, 将位置元素与中心之间的距离也一并返回。
  • WITHCOORD :返回位置元素的经度和维度。
  • WITHHASH :采用 GEOHASH 对位置元素进行编码,以 52 位有符号整数的形式返回有序集合的分值,该选项主要用于底层调试,实际作用不大。
  • COUNT:指定返回位置元素的数量,在数据量非常大时,可以使用此参数限制元素的返回数量,从而加快计算速度。

注意:该命令默认返回的是未排序的位置元素。通过 ASC 与 DESC 可让返回的位置元素以升序或者降序方式排列。

命令应用示例如下:

#添加几个地理位置元素
127.0.0.1:6379> GEOADD city 106.45 29.56 chongqing 120.33 36.06 qingdao 103.73 36.03 lanzhou
(integer) 3
127.0.0.1:6379> GEOADD city 106.71 26.56 guiyang
(integer) 1
#以首都的坐标为中心,计算各个城市距离首都的距离,最大范围设置为1500km
#同时返回距离,与位置元素的经纬度
127.0.0.1:6379> GEORADIUS city 116.41 39.91 1500 km WITHCOORD WITHDIST
1) 1) "chongqing"
   2) "1465.5618"
   3) 1) "106.4500012993812561"
      2) "29.56000053864853072"
2) 1) "lanzhou"
   2) "1191.2793"
   3) 1) "103.72999995946884155"
      2) "36.03000049919800318"
3) 1) "shanghai"
   2) "1121.4882"
   3) 1) "120.52000075578689575"
      2) "30.39999952668997452"
4) 1) "qingdao"
   2) "548.9304"
   3) 1) "120.3299984335899353"
      2) "36.05999892411877994"
5) 1) "beijing"
   2) "42.8734"
   3) 1) "116.19999736547470093"
      2) "39.56000019952067248"

4) GEORADIUSBYMEMBER

根据给定的地理位置坐标(即经纬度)获取指定范围内的位置元素集合。其语法格式如下:

GEORADIUSBYMEMBER key member radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DES]

  • m :米,默认单位;
  • km :千米(可选);
  • mi :英里(可选);
  • ft :英尺(可选);
  • ASC:根据距离将查找结果从近到远排序;
  • DESC:根据距离将查找结果从远到近排序。

该命令与 GEORADIUS 命令相似,不过它选择的中心的是具体的位置元素,而非经纬度坐标。示例如下:

#以贵阳为中心,最大距离不超过900km
127.0.0.1:6379> GEORADIUSBYMEMBER city guiyang 900 km WITHCOORD WITHDIST
1) 1) "guiyang"
   2) "0.0000"
   3) 1) "106.70999854803085327"
      2) "26.56000089385899798"
#只有重庆符合条件
2) 1) "chongqing"
   2) "334.6529"
   3) 1) "106.4500012993812561"
      2) "29.56000053864853072"

5) GEOHASH

返回一个或多个位置元素的哈希字符串,该字符串具有唯一 ID 标识,它与给定的位置元素一一对应。示例如下:

127.0.0.1:6379> GEOHASH city lanzhou beijing shanghai
1) "wq3ubrjcun0"
2) "wx49h1wm8n0"
3) "wtmsyqpuzd0"

6) ZREM

用于删除指定的地理位置元素,示例如下:

127.0.0.1:6379> zrem city beijing shanghai
(integer) 2

HyperLogLog

HyperLoglog 是 Redis 重要的数据类型之一,它非常适用于海量数据的计算、统计,其特点是占用空间小,计算速度快。

HyperLoglog 采用了一种基数估计算法,因此,最终得到的结果会存在一定范围的误差(标准误差为 0.81%)。每个 HyperLogLog key 只占用 12 KB 内存,所以理论上可以存储大约2^64个值,而 set(集合)则是元素越多占用的内存就越多,两者形成了鲜明的对比 。

基数定义

基数定义:一个集合中不重复的元素个数就表示该集合的基数,比如集合 {1,2,3,1,2} ,它的基数集合为 {1,2,3} ,所以基数为 3。HyperLogLog 正是通过基数估计算法来统计输入元素的基数。

HyperLoglog 不会储存元素值本身,因此,它不能像 set 那样,可以返回具体的元素值。HyperLoglog 只记录元素的数量,并使用基数估计算法,快速地计算出集合的基数是多少。

场景应用

HyperLogLog 也有一些特定的使用场景,它最典型的应用场景就是统计网站用户月活量,或者网站页面的 UV(网站独立访客)数据等。

UV 与 PV(页面浏览量) 不同,UV 需要去重,同一个用户一天之内的多次访问只能计数一次。这就要求用户的每一次访问都要带上自身的用户 ID,无论是登陆用户还是未登陆用户都需要一个唯一 ID 来标识。

当一个网站拥有巨大的用户访问量时,我们可以使用 Redis 的 HyperLogLog 来统计网站的 UV (网站独立访客)数据,它提供的去重计数方案,虽说不精确,但 0.81% 的误差足以满足 UV 统计的需求。

常用命令

命令说明
PFADD key element [element ...]添加指定元素到 HyperLogLog key 中。
PFCOUNT key [key ...]返回指定 HyperLogLog key 的基数估算值。
PFMERGE destkey sourcekey [sourcekey ...]将多个 HyperLogLog key 合并为一个 key。

基本命令

HyperLogLog 提供了三个常用命令,分别是PFADDPFCOUNTPFMERGE

下面看一组实例演示:假设有 6 个用户(user01-user06),他们分别在上午 8 与 9 点访问了www.biancheng.netC语言中文网。

#向指定的key中添加用户
127.0.0.1:6379> PFADD user:uv:2021011308 user01 user02 user03
(integer) 1
#向指定的key中添加用户
127.0.0.1:6379> PFADD user:uv:2021011309 user04 user05
(integer) 1
#统计基数值
127.0.0.1:6379> PFCOUNT user:uv:2021011308
(integer) 3
#重复元素不能添加成功,其基数仍然为3
127.0.0.1:6379> PFADD user:uv:2021011308 user01 user02
(integer) 0
127.0.0.1:6379> PFCOUNT user:uv:2021011308
(integer) 3
#添加新元素值
127.0.0.1:6379> PFADD user:uv:2021011308 user06
(integer) 1
#基数值变为4
127.0.0.1:6379> PFCOUNT user:uv:2021011308
(integer) 4
#统计两个key的基数值
127.0.0.1:6379> PFCOUNT user:uv:2021011308 user:uv:2021011309
(integer) 6
#将两个key值合并为一个
127.0.0.1:6379> PFMERGE user:uv:2021011308-09 user:uv:2021011308 user:uv:2021011309
OK
#使用合并后key统计基数值
127.0.0.1:6379> PFCOUNT user:uv:2021011308-09
(integer) 6

Bitmap 位图

在平时开发过程中,经常会有一些 bool 类型数据需要存,例如:需要统计用户的某些信息,如活跃或不活跃,登录或者不登录;又如 需要记录用户一年的打卡情况,打卡了是1, 没有打卡是0,如果使用普通的 key/value存储,则要记录 365条记录,如果用户量很大,需要的空间也会很大,所以 Redis 提供了 Bitmap 位图这中数据结构, Bitmap 就是通过操作二进制位来进行记录,即为 0 和 1

位图应用原理

某网站要统计一个用户一年的签到记录,若用 sring 类型存储,则需要 365 个键值对。若使用位图存储,用户签到就存 1,否则存 0。最后会生成 11010101... 这样的存储结果,其中每天的记录只占一位,一年就是 365 位,约为 46 个字节。如果只想统计用户签到的天数,那么统计 1 的个数即可。

位图操作的优势,相比于字符串而言,它不仅效率高,而且还非常的节省空间。

Redis 的位数组是自动扩展的,如果设置了某个偏移位置超出了现有的内容范围,位数组就会自动扩充。

位图常用命令

1) SETBIT命令

用来设置或者清除某一位上的值,其返回值是原来位上存储的值。key 在初始状态下所有的位都为 0 ,语法格式如下:

SETBIT key offset value

其中 offset 表示偏移量,从 0 开始。示例如下:

127.0.0.1:6379> SET user:1 a
OK
#设置偏移量为0
127.0.0.1:6379> SETBIT user:1 0 1
(integer) 0
#当对应位的字符是不可打印字符,redis会以16进制形式显示
127.0.0.1:6379> GET user:1
"\xe1"
2) GETBIT命令

用来获取某一位上的值。示例如下:

127.0.0.1:6379> GETBIT user:1 0
(integer) 1

当偏移量 offset 比字符串的长度大,或者当 key 不存在时,返回 0。

redis> EXISTS bits
(integer) 0
redis> GETBIT bits 100000
(integer) 0
3) BITCOUNT命令

统计指定位区间上,值为 1 的个数。语法格式如下:

BITCOUNT key [start end]

示例如下:

127.0.0.1:6379> BITCOUNT user:1
(integer) 8

通过指定的 start 和 end 参数,可以让计数只在特定的字节上进行。start 和 end 参数和 GETRANGEopen in new window 命令的参数类似,都可以使用负数,比如 -1 表示倒数第一个位, -2 表示倒数第二个位。

Redis事务

Redis 事务的本质是一组命令的集合,会按顺序执行队列中的命令,但事务不保证原子性,且没有回滚

Redis事务特性

  • Redis事务的概念: Redis 事务的本质是一组命令的集合。事务支持一次执行多个命令,一个事务中所有命令都会被序列 化。在事务执行过程,会按照顺序串行化执行队列中的命令,其他客户端提交的命令请求不会插入到事 务执行命令序列中。

总结说:redis事务就是一次性顺序性排他性的执行一个队列中的一系列命令。

  • Redis事务没有隔离级别的概念: 批量操作在发送 EXEC 命令前被放入队列缓存,并不会被实际执行!

  • Redis不保证原子性: Redis中,单条命令是原子性执行的,但事务不保证原子性,且没有回滚。事务中任意命令执行失败,其余的命令仍会被执行。

(原子性:所有命令要么全部执行成功,要么全部失败回滚。)

注意:Redis 不支持事务回滚,原因在于 Redis 是一款基于内存的存储系统,其内部结构比较简单,若支持回滚机制,则让其变得冗余,并且损耗性能,这与 Redis 简单、快速的理念不相符合。


Redis事务命令

命令说明
MULTI开启一个事务
EXEC执行事务中的所有命令
WATCH key [key ...]在开启事务之前用来监视一个或多个key 。如果事务执行时这些 key 被改动过,那么事务将被打断。
DISCARD取消事务。
UNWATCH取消 WATCH 命令对 key 的监控。

Redis事务的三个阶段:

  • 开始事务 (multi) /ˈmʌlti/

  • 命令入队

  • 执行事务(exec)/ɪɡˈzek/

放弃事务:DISCARD /dɪˈskɑːd/

正常执行

image-20230725214926203
image-20230725214926203

放弃事务DISCARD

image-20230725215127803
image-20230725215127803

若在事务队列中存在命令性错误(类似于java编译性错误),则执行EXEC命令时,所有命令都不会 执行

image-20230725215546906
image-20230725215546906

若在事务队列中存在语法性错误(类似于java的1/0的运行时异常),则执行EXEC命令时,其他正确 命令会被执行,错误命令抛出异常。

image-20230725215606731
image-20230725215606731

Watch 监控

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

  • 乐观锁: 乐观锁(Optimistic Lock),顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会 上锁。但是在更新的时候会判断一下再此期间别人有没有去更新这个数据,可以使用版本号等机制,乐 观锁适用于多读的应用类型,这样可以提高吞吐量,乐观锁策略:提交版本必须大于记录当前版本才能 执行更新。

测试:

1、初始化信用卡可用余额和欠额

127.0.0.1:6379> set balance 100
OK
127.0.0.1:6379> set debt 0
OK

2、使用watch检测balance,事务期间balance数据未变动,事务执行成功

127.0.0.1:6379> watch balance
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> decrby balance 20
QUEUED
127.0.0.1:6379> incrby debt 20
QUEUED
127.0.0.1:6379> exec
1) (integer) 80
2) (integer) 20

3、使用watch检测balance,事务期间balance数据变动,事务执行失败!

# 窗口一
127.0.0.1:6379> watch balance
OK
127.0.0.1:6379> MULTI # 执行完毕后,执行窗口二代码测试
OK
127.0.0.1:6379> decrby balance 20
QUEUED
127.0.0.1:6379> incrby debt 20
QUEUED
127.0.0.1:6379> exec # 修改失败!
(nil)
# 窗口二
127.0.0.1:6379> get balance
"80"
127.0.0.1:6379> set balance 200
OK
# 窗口一:出现问题后放弃监视,然后重来!
127.0.0.1:6379> UNWATCH # 放弃监视
OK
127.0.0.1:6379> watch balance
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> decrby balance 20
QUEUED
127.0.0.1:6379> incrby debt 20
QUEUED
127.0.0.1:6379> exec # 成功!
1) (integer) 180
2) (integer) 40

说明:

  • 当 EXEC 被调用时,不管事务是否成功执行,对所有键的监视都会被取消。

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

  • 使用无参数的 UNWATCH 命令可以手动取消对所有键的监视。对于一些需要改动多个键的事务,有时候程序需要同时对多个键进行加锁,然后检查这些键的当前值是否符合程序的要求。当值达不到要求时,就可以使用 UNWATCH 命令来取消目前对键的监视,中途放弃这个事务,并等待事务的下次尝试。

**小结 :**watch指令类似于乐观锁,在事务提交时,如果watch监控的多个KEY中任何KEY的值已经被其他客户端 更改,则使用EXEC执行事务时,事务队列将不会被执行,同时返回Nullmulti-bulk应答以通知调用者事 务执行失败。

Redis.conf

unit单位

配置文件 unit单位 对大小写不敏感!

image-20230813222504286
image-20230813222504286

包含

类似jsp中的include,多实例的情况可以把公用的配置文件提取出来

image-20230813222931158
image-20230813222931158

网络

1. bind

默认情况bind=127.0.0.1只能接受本机的访问请求 不写的情况下,无限制接受任何ip地址的访问 (即是,注掉bing 后可以接受任何ip地址访问)

如果开启了protected-mode,那么在没有设定bind ip且没有设密码的情况下,Redis只允许接受本机的响应

2.protected-mode

/prəˈtektɪd/

Redis中的protected-mode是一个安全机制,用于保护Redis实例免受未经授权的访问。当protected-mode启用时,Redis只能在本地主机上进行访问,即只允许来自localhost(127.0.0.1)的连接。

当protected-mode处于禁用状态时,Redis将允许来自任意IP地址的连接。这在某些情况下可能存在安全风险,因为未经授权的访问者可能会尝试利用Redis的功能进行恶意操作。因此,启用protected-mode是一种推荐的安全设置,特别是在公共网络环境中使用Redis时。

要启用或禁用protected-mode,可以在Redis配置文件(redis.conf)中找到相应的设置项。默认情况下,protected-mode处于启用状态,配置文件中的相关选项如下:

protected-mode yes

如果您希望禁用protected-mode,可以将该选项修改为:

protected-mode no

然后保存配置文件并重新启动Redis实例,使更改生效

3.Port

端口号,默认 6379

4.tcp-backlog

设置tcp的backlog,backlog其实是一个连接队列,backlog队列总和=未完成三次握手队列 + 已经完成三次握手队列。 在高并发环境下你需要一个高backlog值来避免慢客户端连接问题。 注意Linux内核会将这个值减小到/proc/sys/net/core/somaxconn的值(128),所以需要确认增大/proc/sys/net/core/somaxconn和/proc/sys/net/ipv4/tcp_max_syn_backlog(128)两个值来达到想要的效果

5.timeout

一个空闲的客户端维持多少秒会关闭,0表示关闭该功能。即永不关闭。

6.tcp-keepalive

对访问客户端的一种心跳检测,每个n秒检测一次。 单位为秒,如果设置为0,则不会进行Keepalive检测,建议设置成60

通用 GENERAL

image-20230813223053731
image-20230813223053731
image-20230813223106326
image-20230813223106326

1.daemonize

daemonize yes # 是否为后台进程,设置为yes  守护进程,后台启动

2.pidfile

存放pid文件的位置,每个实例会产生一个不同的pid文件

3.loglevel

指定日志记录级别,Redis总共支持四个级别:debug、verbose、notice、warning,默认为notice 四个级别根据使用阶段来选择,生产环境选择notice 或者warning

4.logfile

日志文件名称

logfile ""  ## 默认是空,不记录日志文件
logfile /var/log/redis/redis.log ## 手动指定日志文件存在位置

5.databases 16

设定库的数量 默认16,默认数据库为0,可以使用SELECT <dbid>命令在连接上指定数据库id

快照

持久化, 在规定的时间内,执行了多少次操作,则会持久化到文件 .rdb. aof redis 是内存数据库,如果没有持久化,那么数据断电及失!

stop-writes-on-bgsave-error yes  ## 当Redis无法写入磁盘的话,直接关掉Redis的写操作。推荐yes
rdbcompression  yes ## 压缩文件  对于存储到磁盘中的快照,可以设置是否进行压缩存储。如果是的话,redis会采用LZF算法进行压缩。如果你不想消耗CPU来进行压缩的话,可以设置为关闭此功能。推荐yes.
rdbchecksum yes ## 检查完整性   在存储快照后,还可以让redis使用CRC64算法来进行数据校验,但是这样做会增加大约10%的性能消耗,如果希望获取到最大的性能提升,可以关闭此功能 推荐yes.
replica-serve-stale-data yes # 
image-20230813223236220
image-20230813223245409
image-20230813223245409

SECURITY 安全

  • 默认是没有密码!

  • 在命令中设置密码,只是临时的。重启redis服务器,密码就还原了。

  • 永久设置,需要再配置文件中进行设置。

image-20230814215341977
image-20230814215341977
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> config get requirepass # 获取redis的密码
1) "requirepass"
2) ""
127.0.0.1:6379> config set requirepass "123456" # 设置redis的密码
OK
127.0.0.1:6379> config get requirepass # 发现所有的命令都没有权限了
(error) NOAUTH Authentication required.
127.0.0.1:6379> ping
(error) NOAUTH Authentication required.
127.0.0.1:6379> auth 123456 # 使用密码进行登录!
OK
127.0.0.1:6379> config get requirepass
1) "requirepass"
2) "123456"

限制 CLIENTS

image-20230813223511423
image-20230813223511423
image-20230813223518163
image-20230813223518163
image-20230813223527260
image-20230813223527260
maxclients 10000 # 设置能连接上redis的最大客户端的数量
maxmemory <bytes> # redis 配置最大的内存容量
maxmemory-policy noeviction # 内存到达上限之后的处理策略
    1、volatile-lru:只对设置了过期时间的key进行LRU(默认值)
    2、allkeys-lru : 删除lru算法的key
    3、volatile-random:随机删除即将过期key
    4、allkeys-random:随机删除
    5、volatile-ttl : 删除即将过期的
    6、noeviction : 永不过期,返回错误

APPEND ONLY 模式 aof配置

appendonly no

appendfilename "appendonly.aof"
image-20230813223650337
image-20230813223650337
image-20230813223658102
image-20230813223658102

Redis的持久化


需要了解:

  1. 持久化的2种方式,RDB和AOF
  2. 分别如何开启
  3. 对比下的优缺点
  4. 如何选择

Redis 是内存数据库,如果不将内存中的数据库状态保存到磁盘,那么一旦服务器进程退出,服务器中的数据库状态也会消失。所以 Redis 提供了持久化功能!

Redis 提供了2个不同形式的持久化方式。

  • RDB(Redis DataBase)

  • AOF(Append Of File)

1. RDB(Redis DataBase)

什么是RDB

在主从复制中,rdb就是备用的!在从机上面!

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

备份是如何执行的

image-20230815202653623
image-20230815202653623

Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何IO操作的。这就确保了极高的性能。如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效。RDB的缺点是最后一次持久化后的数据可能丢失。我们默认的就是RDB,一般情况下不需要修改这个配置!

有时候在生产环境我们会将这个文件进行备份!

rdb保存的文件是dump.rdb 都是在我们的配置文件中快照中进行配置的!

image-20230815202517344
image-20230815202517344
[root@localhost bin]# pwd
/usr/local/redis/bin
[root@localhost bin]# ls
dump.rdb  redis-benchmark  redis-check-aof  redis-check-rdb  redis-cli  redis-sentinel  redis-server ## 存在 redis/bin目录下

触发机制

  • save的规则满足的情况下,会自动触发rdb规则
  • 执行 flushall 命令,也会触发我们的rdb规则!
  • 退出redis,也会产生 rdb 文件!

备份就自动生成一个 dump.rdb

如何恢复rdb文件!

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

  • 关闭Redis

  • 先把备份的文件拷贝到工作目录下

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

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

[root@localhost bin]# ./redis-cli -h 192.168.75.130 -p 6379 -a 123456
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
192.168.75.130:6379> config get dir
1) "dir"
2) "/"     # 如果在这个目录下存在 dump.rdb 文件,启动就会自动恢复其中的数据
192.168.75.130:6379> 

优点:

  • 适合大规模的数据恢复

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

  • 节省磁盘空间

  • 恢复速度快

缺点:

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

如何停止

动态停止RDB:redis-cli config set save ""#save后给空值,表示禁用保存策略

2. AOF(Append Only File)

是什么

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

AOF和RDB同时开启

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

AOF启动/修复/恢复

默认是不开启的,我们需要手动进行配置!我们只需要将 appendonly 改为yes就开启了 aof!重启,redis 就可以生效了!

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

img
img
appendonly no # 默认是不开启aof模式的,默认是使用rdb方式持久化的,在大部分所有的情况下,rdb完全够用!
appendfilename "appendonly.aof" # 持久化的文件的名字
# appendfsync always # 每次修改都会 sync。消耗性能
appendfsync everysec # 每秒执行一次 sync,可能会丢失这1s的数据!
# appendfsync no # 不执行 sync,这个时候操作系统自己同步数据,速度最快!
# rewrite 重写,

正常恢复

  • 修改默认的appendonly no,改为yes

  • 将有数据的aof文件复制一份保存到对应目录(查看目录:config get dir)

  • 重启redis然后重新加载

异常恢复

  • 修改默认的appendonly no,改为yes

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

  • 备份被写坏的AOF文件

  • 恢复:重启redis,然后重新加载

image-20230815222519824
image-20230815222519824

重写规则说明

AOF同步频率设置

appendfsync always  ##始终同步,每次Redis的写入都会立刻记入日志;性能较差但数据完整性比较好
appendfsync everysec  # 每秒同步,每秒记入日志一次,如果宕机,本秒的数据可能丢失。
appendfsync no # redis不主动进行同步,把同步时机交给操作系统。

优势/缺点

  • 优点

    • 备份机制更稳健,丢失数据概率更低
    • 可读的日志文本,通过操作AOF稳健,可以处理误操作。
  • 缺点

    • 相对于数据文件来说,aof远远大于 rdb,修复的速度也比 rdb慢!
    • Aof 运行效率也要比 rdb 慢

RDB和AOF对比

image-20230815220250247
image-20230815220250247

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

同时开启两种持久化方式

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

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

性能建议

  • 因为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 同时倒掉,会丢失十几分钟的数据,启动脚本也要比较两个 Master/Slave 中的 RDB文件,载入较新的那个,微博就是这种架构。

Redis 发布订阅

发布订阅模式详解

Redis发布/订阅(Pub/Sub)是一种通信机制,将数据推到某个信息管道中,其他客户端可通过订阅这些管道来获取推送信息,以此用于消息的传输。

由三部分组成:发布者(Publisher)、频道(Channel)、订阅者(Subscriber)。

发布者发布的消息分到不同的频道,不需要知道什么样的订阅者者订阅。订阅者对一个或多个频道感兴趣,只需要接收感兴趣的消息,不需要知道什么样的发布者发布。主要目的是解除消息的发布者与订阅者之间的耦合关系。发布者和订阅者都是Redis客户端,频道则是服务器端。

常用命令汇总

下表列出了 redis 发布订阅常用命令:

序号命令及描述
1[PSUBSCRIBE pattern pattern ...]open in new window 订阅一个或多个符合给定模式的频道。
2[PUBSUB subcommand argument [argument ...]]open in new window 查看订阅与发布系统状态。
3PUBLISH channel messageopen in new window 将信息发送到指定的频道。
4[PUNSUBSCRIBE pattern [pattern ...]]open in new window 退订所有给定模式的频道。
5[SUBSCRIBE channel channel ...]open in new window 订阅给定的一个或多个频道的信息。
6[UNSUBSCRIBE channel [channel ...]]open in new window 指退订给定的频道。

实例演示

演示了发布订阅是如何工作的。在我们实例中我们创建了订阅频道名为redisChat

第一步:打开一个 redis 客户端,订阅频道 redisChat

redis> SUBSCRIBE redisChat 
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "redisChat"
3) (integer) 1

第二步:另外再打开一个 redis 客户端,在频道redisChat上发布消息

redis> PUBLISH redisChat "Redis is a great caching technique"
(integer) 1

redis> PUBLISH redisChat "Learn redis by w3cschool.cc" 
(integer) 1

第三步:查看第一次打开的 redis 客户端,会显示接受到的消息

1) "message"
2) "redisChat"
3) "Redis is a great caching technique"

1) "message"
2) "redisChat"
3) "Learn redis by w3cschool.cc" 

模式匹配

PSUBSCRIBE命令订阅一个或多个符合给定模式的频道。每个模式以*作为匹配符,例如 news.*匹配所有以 news.开头的频道(news.one、news.global.today 等等)。

redis> PSUBSCRIBE new.*
1) "psubscribe"
2) "new.*"
3) (integer) 1
1) "pmessage"
2) "new.*"      # 消息匹配的模式
3) "new.one"    # 消息本身的目标频道

注:当订阅客户端同时订阅某种模式和符合该模式的具体某个频道时,那么会接收到发布者推送的信息两次,两次接收的信息格式不同,一个为 message 类型,另一个为 pmessage 类型,消息内容一致。

取消订阅

UNSUBSCRIBE命令为取消订阅频道,与订阅频道命令相同,也有对应匹配模式PUNSUBSCRIBE

如果没有填写指定频道,即一个无参数的UNSUBSCRIBE被调用执行,那么该客户端订阅的所有频道都会被退订。

由于Redis的订阅操作是阻塞式的,因此一旦客户端订阅了某个频道或模式,就将会一直处于订阅状态直到退出。在SUBSCRIBEPSUBSCRIBEUNSUBSCRIBEPUNSUBSCRIBE命令中,其返回值都包含了该客户端当前订阅的频道和模式的数量,当这个数量变为0时,该客户端会自动退出订阅状态。

使用场景:

1、实时消息系统! 2、事实聊天!(频道当做聊天室,将信息回显给所有人即可!) 3、订阅,关注系统都是可以的!

稍微复杂的场景我们就会使用 消息中间件 MQ

基本命令应用

Redis主从复制

https://www.cnblogs.com/sang-bit/p/15260045.htmlopen in new window

  • 主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(Master/Leader),后者称为从节点(Slave/Follower), 数据的复制是单向的!只能由主节点复制到从节点(主节点以写为主、从节点以读为主)。

  • 默认情况下,每台Redis服务器都是主节点,一个主节点可以有0个或者多个从节点,但每个从节点只能由一个主节点。

主从模式解析

主从模式的结构图如下:

Redis主从模式 图1:Redis 主从模式

如图 1 所示,Redis 主机会一直将自己的数据复制给 Redis 从机,从而实现主从同步。在这个过程中,只有 master 主机可执行写命令,其他 salve 从机只能只能执行读命令,这种读写分离的模式可以大大减轻 Redis 主机的数据读取压力,从而提高了Redis 的效率,并同时提供了多个数据备份。主从模式是搭建 Redis Cluster 集群最简单的一种方式。

作用

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

故障恢复:当主节点故障时,从节点可以暂时替代主节点提供服务,是一种服务冗余的方式

负载均衡**(读写分离**):在主从复制的基础上,配合读写分离,由主节点进行写操作,从节点进行读操作,分担服务器的负载;尤其是在多读少写的场景下,通过多个从节点分担负载,提高并发量。

高可用基石:主从复制还是哨兵和集群能够实施的基础。

主从模式实现

环境配置

127.0.0.1:6379> info replication   #查看当前库的信息
# Replication
role:master
connected_slaves:0    #从机的数量

一主二从配置

**默认情况下,每台Redis服务器都是主节点;**我们一般情况下只用配置从机就好了!

开启主从复制的方式有两种:

通过命令 SLAVEOF no one 设置从机为主机

  • 配置配置文件

    image-20230820222043037
    image-20230820222043037
    # 主节点ip port
    # replicaof <masterip> <masterport>
    # 主节点的认证密码(可选)
    # masterauth <master-password>
    
  • 显示命令开启

    客户端使用该命令slaveof [ip] [port]

    INFO REPLLICATION命令可查看节点信息

使用规则

  • 从机只能读,不能写,主机可读可写但是 多用于写。

  • 当主机断电宕机后,默认情况下从机的角色不会发生变化 ,集群中只是失去了写操作,当主机恢复以后,又会连接上从机恢复原状。

  • 当从机断电宕机后,若不是使用配置文件配置的从机,再次启动后作为主机是无法获取之前主机的数据的,若此时重新配置称为从机,又可以获取到主机的所有数据。

第二条中提到,默认情况下,主机故障后,不会出现新的主机,有两种方式可以产生新的主机:

  • 从机手动执行命令 slaveof no one ,这样执行以后从机会独立出来成为一个主机
  • 使用哨兵模式(自动选举)

模拟测试

在同一个服务器上,将配置文件复制3个

复制3个配置文件,然后修改对应的信息 1、端口 2、pid 名字 3、log文件名字 4、dump.rdb 名字

修改完毕之后,启动我们的3个redis服务

主从模式不足

主从模式并不完美,它也存在许多不足之处,下面做了简单地总结:

  • 1) Redis 主从模式不具备自动容错和恢复功能,如果主节点宕机,Redis 集群将无法工作,此时需要人为干预,将从节点提升为主节点。
  • 2) 如果主机宕机前有一部分数据未能及时同步到从机,即使切换主机后也会造成数据不一致的问题,从而降低了系统的可用性。
  • 3) 因为只有一个主节点,所以其写入能力和存储能力都受到一定程度地限制。
  • 4) 在进行数据全量同步时,若同步的数据量较大可能会造卡顿的现象。

缓存穿透和雪崩

Jedis

Jedis是Redis官方推荐的Java连接开发工具。使用Java操作Redis的中间件

使用

1)导入依赖

<dependencies>
    <dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
        <version>3.2.0</version>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.62</version>
    </dependency>
</dependencies>

2)连接到 redis 服务

import redis.clients.jedis.Jedis;
public class Ping {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("127.0.0.1",6379);
        System.out.println("连接成功");
        //查看服务是否运行
        System.out.println("服务正在运行: "+jedis.ping());
    }
}

常用API

1)基本操作

public class TestPassword {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("127.0.0.1", 6379);
        //验证密码,如果没有设置密码这段代码省略
        // jedis.auth("password");
        jedis.connect(); //连接
        jedis.disconnect(); //断开连接
        jedis.flushAll(); //清空所有的key
    }
}

2)对key操作的命令

public class TestKey {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("127.0.0.1", 6379);
        System.out.println("清空数据:"+jedis.flushDB());
        System.out.println("判断某个键是否存在:"+jedis.exists("username"));
        System.out.println("新增<'username','kuangshen'>的键值对:"+jedis.set("username", "kuangshen"));
        System.out.println("新增<'password','password'>的键值对:"+jedis.set("password", "password"));
        System.out.print("系统中所有的键如下:");
        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("随机返回key空间的一个:"+jedis.randomKey());
        System.out.println("重命名key:"+jedis.rename("username","name"));
        System.out.println("取出改后的name:"+jedis.get("name"));
        System.out.println("按索引查询:"+jedis.select(0));
        System.out.println("删除当前选择数据库中的所有key:"+jedis.flushDB());
        System.out.println("返回当前数据库中key的数目:"+jedis.dbSize());
        System.out.println("删除所有数据库中的所有key:"+jedis.flushAll());
    }
}

3)对String操作的命令

public class TestString {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("127.0.0.1", 6379);
        jedis.flushDB();
        System.out.println("===========增加数据===========");
        System.out.println(jedis.set("key1","value1"));
        System.out.println(jedis.set("key2","value2"));
        System.out.println(jedis.set("key3", "value3"));
        System.out.println("删除键key2:"+jedis.del("key2"));
        System.out.println("获取键key2:"+jedis.get("key2"));
        System.out.println("修改key1:"+jedis.set("key1", "value1Changed"));
        System.out.println("获取key1的值:"+jedis.get("key1"));
        System.out.println("在key3后面加入值:"+jedis.append("key3", "End"));
        System.out.println("key3的值:"+jedis.get("key3"));
        System.out.println("增加多个键值对:"+jedis.mset("key01","value01","key02","value02","key03","value03"));
        System.out.println("获取多个键值对:"+jedis.mget("key01","key02","key03"));
        System.out.println("获取多个键值对:"+jedis.mget("key01","key02","key03","key04"));
        System.out.println("删除多个键值对:"+jedis.del("key01","key02"));
        System.out.println("获取多个键值对:"+jedis.mget("key01","key02","key03"));
        jedis.flushDB();
        System.out.println("===========新增键值对防止覆盖原先值==============");
        System.out.println(jedis.setnx("key1", "value1"));
        System.out.println(jedis.setnx("key2", "value2"));
        System.out.println(jedis.setnx("key2", "value2-new"));
        System.out.println(jedis.get("key1"));
        System.out.println(jedis.get("key2"));
        System.out.println("===========新增键值对并设置有效时间=============");
        System.out.println(jedis.setex("key3", 2, "value3"));
        System.out.println(jedis.get("key3"));
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(jedis.get("key3"));
        System.out.println("===========获取原值,更新为新值==========");
        System.out.println(jedis.getSet("key2", "key2GetSet"));
        System.out.println(jedis.get("key2"));
        System.out.println("获得key2的值的字串:"+jedis.getrange("key2", 2,4));
    }
}

4)对List操作命令

public class TestSet {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("127.0.0.1", 6379);
        jedis.flushDB();
        System.out.println("============向集合中添加元素(不重复)============");
        System.out.println(jedis.sadd("eleSet", "e1","e2","e4","e3","e0","e8","e7","e5"));
        System.out.println(jedis.sadd("eleSet", "e6"));
        System.out.println(jedis.sadd("eleSet", "e6"));
        System.out.println("eleSet的所有元素为:"+jedis.smembers("eleSet"));
        System.out.println("删除一个元素e0:"+jedis.srem("eleSet", "e0"));
        System.out.println("eleSet的所有元素为:"+jedis.smembers("eleSet"));
        System.out.println("删除两个元素e7和e6:"+jedis.srem("eleSet", "e7","e6"));
        System.out.println("eleSet的所有元素为:"+jedis.smembers("eleSet"));
        System.out.println("随机的移除集合中的一个元素:"+jedis.spop("eleSet"));
        System.out.println("随机的移除集合中的一个元素:"+jedis.spop("eleSet"));
        System.out.println("eleSet的所有元素为:"+jedis.smembers("eleSet"));
        System.out.println("eleSet中包含元素的个数:"+jedis.scard("eleSet"));
        System.out.println("e3是否在eleSet中:"+jedis.sismember("eleSet",
                                                           "e3"));
        System.out.println("e1是否在eleSet中:"+jedis.sismember("eleSet",
                                                           "e1"));
        System.out.println("e1是否在eleSet中:"+jedis.sismember("eleSet", "e5"));
        System.out.println("=================================");
        System.out.println(jedis.sadd("eleSet1", "e1","e2","e4","e3","e0","e8","e7","e5"));
        System.out.println(jedis.sadd("eleSet2", "e1","e2","e4","e3","e0","e8"));
        System.out.println("将eleSet1中删除e1并存入eleSet3 中:"+jedis.smove("eleSet1", "eleSet3", "e1"));//移到集合元素
        System.out.println("将eleSet1中删除e2并存入eleSet3中:"+jedis.smove("eleSet1", "eleSet3", "e2"));
        System.out.println("eleSet1中的元素:"+jedis.smembers("eleSet1"));
        System.out.println("eleSet3中的元素:"+jedis.smembers("eleSet3"));
        System.out.println("============集合运算=================");
        System.out.println("eleSet1中的元素:"+jedis.smembers("eleSet1"));
        System.out.println("eleSet2中的元素:"+jedis.smembers("eleSet2"));
        System.out.println("eleSet1和eleSet2的交 集:"+jedis.sinter("eleSet1","eleSet2"));
        System.out.println("eleSet1和eleSet2的并集:"+jedis.sunion("eleSet1","eleSet2"));
        System.out.println("eleSet1和eleSet2的差集:"+jedis.sdiff("eleSet1","eleSet2"));//eleSet1中有,eleSet2中没有
        jedis.sinterstore("eleSet4","eleSet1","eleSet2");//求交集并将交集保存到 dstkey的集合
        System.out.println("eleSet4中的元素:"+jedis.smembers("eleSet4"));
    }
}

5) 对Hash的操作命令

public class TestHash {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("127.0.0.1", 6379);
        jedis.flushDB();
        Map<String,String> map = new HashMap<>();
        map.put("key1","value1");
        map.put("key2","value2");
        map.put("key3","value3");
        map.put("key4","value4");
        //添加名称为hash(key)的hash元素
        jedis.hmset("hash",map);
        //向名称为hash的hash中添加key为key5,value为value5元素
        jedis.hset("hash", "key5", "value5");
        System.out.println("散列hash的所有键值对为:"+jedis.hgetAll("hash"));//return Map<String,String>
        System.out.println("散列hash的所有键为:"+jedis.hkeys("hash"));//return Set<String>
        System.out.println("散列hash的所有值为:"+jedis.hvals("hash"));//return List<String>
        System.out.println("将key6保存的值加上一个整数,如果key6不存在则添加 key6:"+jedis.hincrBy("hash", "key6", 6));
        System.out.println("散列hash的所有键值对为:"+jedis.hgetAll("hash"));
        System.out.println("将key6保存的值加上一个整数,如果key6不存在则添加key6:"+jedis.hincrBy("hash", "key6", 3));
        System.out.println("散列hash的所有键值对为:"+jedis.hgetAll("hash"));
        System.out.println("删除一个或者多个键值对:"+jedis.hdel("hash","key2"));
        System.out.println("散列hash的所有键值对为:"+jedis.hgetAll("hash"));
        System.out.println("散列hash中键值对的个数:"+jedis.hlen("hash"));
        System.out.println("判断hash中是否存在 key2:"+jedis.hexists("hash","key2"));
        System.out.println("判断hash中是否存在 key3:"+jedis.hexists("hash","key3"));
        System.out.println("获取hash中的值:"+jedis.hmget("hash","key3"));
        System.out.println("获取hash中的 值:"+jedis.hmget("hash","key3","key4"));
    }
}

事务

package com.kuang.multi;
import com.alibaba.fastjson.JSONObject;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;
public class TestMulti {
    public static void main(String[] args) {
        //创建客户端连接服务端,redis服务端需要被开启
        Jedis jedis = new Jedis("127.0.0.1", 6379);
        jedis.flushDB();
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("hello", "world");
        jsonObject.put("name", "java");
        //开启事务
        Transaction multi = jedis.multi();
        String result = jsonObject.toJSONString();
        try{
            //向redis存入一条数据
            multi.set("json", result);
            //再存入一条数据
            multi.set("json2", result);
            //这里引发了异常,用0作为被除数
            int i = 100/0;
            //如果没有引发异常,执行进入队列的命令
            multi.exec();
        }catch(Exception e){
            e.printStackTrace();
            //如果出现异常,回滚
            multi.discard();
        }finally{
            System.out.println(jedis.get("json"));
            System.out.println(jedis.get("json2"));
            //最终关闭客户端
            jedis.close();
        }
    }
}

SpringBoot整合

springBoot2.x 后官方推荐的jedis被替换为 lettuce /ˈletɪs/

  • jedis : 采用的直连,多个线程操作的话,是不安全的,如果想要避免不安全的,使用 jedis pool 连接池! 更像 BIO 模式
  • lettuce : 采用netty,实例可以再多个线程中进行共享,不存在线程不安全的情况!可以减少线程数据了,更像 NIO 模式

redis的自动配置类RedisAutoConfiguration

image-20230809204012282
image-20230809204012282

@ConditionalOnMissingBean(name = "redisTemplate")

当名为 "redisTemplate" 的 Bean 不存在时,执行该注解所标注的配置或创建动作。

@Bean
@ConditionalOnMissingBean(name = "redisTemplate") // 我们可以自己定义一个redisTemplate来替换这个默认的!
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
    // 默认的 RedisTemplate 没有过多的设置,redis 对象都是需要序列化!
    // 两个泛型都是 Object, Object 的类型,我们后使用需要强制转换 <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. 导入依赖
<!-- 操作redis -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
  1. 配置连接
# 配置redis
spring.redis.host=127.0.0.1
spring.redis.port=6379
  1. 测试
@SpringBootTest
class Redis02SpringbootApplicationTests {
    @Autowired
    private RedisTemplate redisTemplate;
    @Test
    void contextLoads() {
        // redisTemplate 操作不同的数据类型,api和我们的指令是一样的
        // opsForValue 操作字符串 类似String
        // opsForList 操作List 类似List
        // opsForSet
        // opsForHash
        // opsForZSet
        // opsForGeo
        // opsForHyperLogLog

        redisTemplate.opsForValue().set("mykey","关注狂神说公众号");
        System.out.println(redisTemplate.opsForValue().get("mykey"));

        // 除了进本的操作,我们常用的方法都可以直接通过redisTemplate操作,比如事务,和基本的 CRUD
        // 获取redis的连接对象
        // RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
        // connection.flushDb();
        // connection.flushAll();
    }
}

自定义 redisTemplate

package com.test.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 是 Spring 框架中的注解之一,用于将类标记为配置类。配置类是用来定义和配置 Spring 应用程序的组件、bean 和其他配置元素的类。
 */
@Configuration
public class RedisConfig {
	// 自己定义了一个 RedisTemplate
	@Bean
	@SuppressWarnings("all")
	public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
		// 我们为了自己开发方便,一般直接使用 <String, Object>
		RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
		template.setConnectionFactory(factory);

		// Json序列化配置
		Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
		ObjectMapper om = new ObjectMapper();
		om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
		om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
		jackson2JsonRedisSerializer.setObjectMapper(om);

		// 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();

		return template;

	}
}

redis图形化工具

参考:https://blog.csdn.net/weixin_52826368/article/details/126256943open in new window

使用记录

1. 中文乱码

redis配置设置序列化后,为什么传入中文value,在redis服务器上get到的还是"\xe9\x99\x88\xe7\x8b\xac\xe7\xa7\x80"?序列化没成功吗

redis自身编码导致吧 中文乱码 启动加--raw redis-cli --raw

连接外网首先有在redis.conf改几个设置

1.daemonize yes 2.注释 bind 127.0.0.1 3.protected-mode no 其次看一下自己防火墙对端口号开没开放 firewall-cmd --query-port=6379/tcp 如果是yes就是开放的 然后 redis-server lconfig/redis.conf redis-cli -h 自己外网端口号 -p 6379 最后ping一下如果pong就成功了

redis连接命令url怎么设置账号密码

redis://host:port

redis://host:port/db_number

redis://[:password]@host:port

redis://[:password]@host:port/db_number

其中:如果密码中有特殊字符则,通过 %40 转义

例如 密码是 Semdo@2022 ==> redis://:Semdo%402022@192.168.1.130:6379