高性能分布式缓存Redis缓存分类 安装 数据类型选择和应用场景 发布订阅 事务 Lua脚本 慢查询日志

性能分布式缓存Redis

性能分布式缓存Redis

1. 缓存发展史&缓存分类

1.1 大型网站中缓存的使用

在这里插入图片描述

访问量越大,响应力越差,用户体验越差
引入缓存、示意图如下:

在这里插入图片描述

性能
假如用户第一次访问数据库中的某些数据的话,这个过程是比较慢,毕竟是从硬盘中读取的。但是,如果说,用户访问的数据属于高频数据并且不会经常改变的话,那么我们就可以很放心地将该用户访问的数据存在缓存中。
这样有什么好处呢? 那就是保证用户下一次再访问这些数据的时候就可以直接从缓存中获取了。操作缓存就是直接操作内存,所以速度相当快
不过,要保持数据库和缓存中的数据的一致性。 如果数据库中的对应数据改变的之后,同步改变缓存中相应的数据即可

高并发:
一般像 MysqL 这类的数据库的 QPS 大概都在 1w 左右(4 核 8g) ,但是使用 Redis 缓存之后很容易达到 10w+,甚至最高能达到 30w+(就单机 redis 的情况,redis 集群的话会更高)。
QPS(Query Per Second):服务器每秒可以执行的查询次数
所以,直接操作缓存能够承受的数据库请求数量是远远大于直接访问数据库的,所以我们可以考虑把数据库中的部分数据转移到缓存中去,这样用户的一部分请求会直接到缓存,而不用经过数据库。进而,我们也就提高的系统整体的并发。

1.2 常见缓存的分类

分布式缓存
分布式缓存主要解决的是单机缓存的容量受服务器限制并且无法保存通用的信息。因为,本地缓存只在当前服务里有效,比如如果你部署了两个相同的服务,他们两者之间的缓存数据是无法共同的。
具有缓存功能的中间件:Redis、Memcache、Tair(阿里 、美团)等等

1.3 分布式缓存选型方案对比

Memcache和Redis区别
共同点 :

  1. 都是基于内存的数据库,一般都用来当做缓存使用。
  2. 都有过期策略。
  3. 两者的性能都非常高。

区别 :

  1. Redis 支持更丰富的数据类型(支持更复杂的应用场景)。Redis 不仅仅支持简单的 k/v 类型的数据,同时还提供 list,set,zset,hash 等数据结构的存储。Memcached 只支持最简单的 k/v 数据类型。
  2. Redis 支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用, 而 Memecache 把数据全部存在内存之中
  3. Redis 有灾难恢复机制。 因为可以把缓存中的数据持久化到磁盘上。
  4. Redis 在服务器内存使用完之后,可以将不用的数据放到磁盘上。但是,Memcached 在服务器内存使用完之后,就会直接报异常
  5. Memcached 没有原生的集群模式,需要依靠客户端来实现往集群中分片写入数据;但是 Redis目前是原生支持 cluster 模式的. 6. Memcached 是多线程,非阻塞 IO 复用的网络模型;Redis 使用单线程的多路 IO 复用模型。(Redis 6.0 引入了多线程 IO )
    相信看了上面的对比之后,我们已经没有什么理由可以选择使用 Memcached 来作为自己项目的分布式缓存了。

2. Redis概述&安装配置

2.1 概述

官网:https://redis.io
中文官网地址:http://www.redis.cn

在这里插入图片描述

简单来说 Redis 就是一个使用 C 语言开发的数据库,不过与传统数据库不同的是 Redis 的数据是存在内存中的 ,也就是它是内存数据库,所以读写速度非常快,因此 Redis 被广泛应用于缓存方向。
另外,Redis 除了做缓存之外,Redis 也经常用来做分布式锁,甚至是消息队列
Redis 提供了多种数据类型来支持不同的业务场景。Redis支持事务 、持久化、Lua 脚本、多种集群方案。

2.2Redis应用场景

  • 缓存使用,减轻DB压力
  • DB使用,用于临时存储数据(字典表,购买记录)
  • 解决分布式场景下Session分离问题(登录信息)
  • 任务队列(秒杀、抢红包等等) 乐观锁
  • 应用排行榜 zset
  • 签到 bitmap
  • 分布式锁
  • 冷热数据交换

2.3 安装&配置

官网:https://redis.io/download

在这里插入图片描述

Redis没有官方的windows版本,所以建议在linux系统上去运行
选择下载稳定版本、不稳定版本可以尝鲜、但是不推荐在生产环境中使用
安装
第一步:安装 C 语言需要的 GCC 环境

yum install -y gcc-c++ 
yum install -y wget

第二步:下载并解压缩 Redis 源码压缩包

# 下载 
wget https://download.redis.io/releases/redis-6.2.4.tar.gz 
mkdir /usr/local/redis 
tar -zxvf redis-6.2.4.tar.gz -C /usr/local/redis 

在这里插入图片描述

第三步:编译 Redis 源码,进入 redis-6.2.4 目录,执行编译命令,进行安装

cd /usr/local/redis/redis-6.2.4/src 
make && make install 

执行完毕后安装成功!

启动
前端启动

  • 启动命令: redis-server ,直接运行 bin/redis-server 将以前端模式启动
  • 关闭命令: ctrl+c
  • 启动缺点:客户端窗口关闭则 redis-server 程序结束,不推荐使用此方法

启动图例:

在这里插入图片描述


后端启动(守护进程启动)
第一步:拷贝 redis-6.2.4/redis.conf 配置文件到 Redis 安装目录的 bin 目录

cp redis.conf /usr/local/redis

第二步:修改 redis.conf

vim redis.conf

第三步:修改 redis.conf
(1)修改daemonize no —> daemonize yes,目的是为了让redis启动在linux后台运行

在这里插入图片描述


(2)修改redis的工作目录:(名称随意)

在这里插入图片描述


第四步:启动服务

./redis-server redis.conf

在这里插入图片描述


查看进程

在这里插入图片描述


后端启动的关闭方式

./redis-cli shutdown

命令说明
redis-server :启动 redis 服务
redis-cli :进入 redis 命令客户端
redis-benchmark : 性能测试的工具
redis-check-aof : aof 文件进行检查的工具
redis-check-dump : rdb 文件进行检查的工具
redis-sentinel : 启动哨兵监控服务
Redis命令行客户端

  • 命令格式
./redis-cli -h 127.0.0.1 -p 6379
  • 参数说明
-h:redis服务器的ip地址 
-p:redis实例的端口号 

  • 认方式:如果不指定主机和端口也可以 认主机地址是127.0.0.1 认端口是6379
./redis-cli

2.4 ui

命令行已经足够强大,尤其是高版本,强大到怀疑人生
但是!它并不友好,业界有很多ui可供使用,典型的:Another Redis Desktop Manager
1)开源
源码地址:https://gitee.com/qishibo/AnotherRedisDesktopManager
编译包下载:https://github.com/qishibo/AnotherRedisDesktopManager/releases

2)支持多平台
Windows
Linux
Mac
3)基本使用
创建连接

在这里插入图片描述


主页监控:

在这里插入图片描述


基本操作:

在这里插入图片描述


命令行:

在这里插入图片描述

3. 数据类型选择&应用场景

在这里插入图片描述

Redis的Key的设计

  • 用:分割
  • 把表名转换为key前缀, 比如: user:
  • 第二段放置主键值
  • 第三段放置列名
    比如:用户表user, 转换为redis的key-value存储

    在这里插入图片描述

string字符串类型

  • 介绍 :string 数据结构是简单的 key-value 类型。虽然 Redis 是用 C 语言写的,但是 Redis 并没有使用 C 的字符串表示,而是自己构建了一种 简单动态字符串(simple dynamic string,SDS)。相比于 C 的原生字符串,Redis 的 SDS 不光可以保存文本数据还可以保存二进制数据,并且获取字符串长度复杂度为 O(1)(C 字符串为 O(N)),除此之外,Redis 的 SDS API 是安全的,不会造成缓冲区溢出。

  • 常用命令: set,get,strlen,exists,decr,incr,setex 等等。

  • 应用场景 :一般常用在需要计数的场景,比如用户的访问次数、热点文章的点赞转发数量等等。

  • 单值缓存
    SET key value
    GET key

  • 对象缓存
    MSET user:1:name zimu user:1:balance 1888
    MGET user:1:name user:1:balance

    在这里插入图片描述

  • 分布式锁(「SET if Not eXists」)

SETNX product:10001 true // 返回1代表获取锁成功
SETNX product:10001 false // 返回0代表获取锁失败
…执行业务操作
DEL product:10001 // 执行完业务 释放锁
SET product:10001 true ex 10 nx // 防止程序意外终止导致死锁

  • 计数器
    INCR article:readcount:101

    在这里插入图片描述

hash类型(散列表)

在这里插入图片描述

  • 介绍 :hash 类似于 JDK1.8 前的 HashMap,内部实现也差不多(数组 + 链表)。不过,Redis 的hash 做了更多优化。另外,hash 是一个 string 类型的 field 和 value 的映射表,特别适合用于存储对象,后续操作的时候,你可以直接仅仅修改这个对象中的某个字段的值。 比如我们可以 hash数据结构来存储用户信息,商品信息等等。

  • 常用命令: hset,hmset,hexists,hget,hgetall,hkeys,hvals 等。

  • 应用场景: 系统中对象数据的存储。

  • 对象缓存

HMSET user {userId}:username zhangfei {userId}:password 123456
HMSET user 1:username zhangfei 1:password 123456
HMGET user 1:username 1:password

  • 电商购物车

    在这里插入图片描述

  • 购物车操作
    1)添加商品 —> hset cart:1001 10088 1
  1. 增加数量 —> hincrby cart:1001 10088 1
    3) 商品总数 —> hlen cart:1001
    4) 删除商品—> hdel cart:1001 10088
    5)获取购物车所有商品—> hgetall cart:1001

优点:
1)同类数据归类整合储存,方便数据管理
2)相比String操作消耗内存和cpu更小
3)相比String储存 更节省空间
缺点:
1)过期功能不能使用在field上,只能用在key上
2)Redis集群架构下不适合大规模使用
因为如果一个hash的key中的属性很多的话,只能存在一个redis节点上,那么这个节点压力会比其他节点压力大很多,造成redis集群下压力分配不均衡!

在这里插入图片描述

list列表类型

  • 介绍 :list 即是 链表。链表是一种非常常见的数据结构,特点是易于数据元素的插入和删除并且且可以灵活调整链表长度,但是链表的随机访问困难。许多高级编程语言都内置了链表的实现比如Java 中的 LinkedList,但是 C 语言并没有实现链表,所以 Redis 实现了自己的链表数据结构。Redis 的 list 的实现为一个 双向链表,即可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销。

  • 常用命令: rpush,lpop,lpush,rpop,lrange、llen 等。

  • 应用场景: 发布与订阅或者说消息队列、慢查询

  • 微信抽奖小程序

    在这里插入图片描述


    1)点击 参与抽奖 加入集合
    SADD key {userID}
    2)查看排行榜
    SMEMBERS key
    3)抽取count名中奖者
    SRANDMEMBER key [count] / SPOP key [count]

  • 集合操作实现微博、微信关注模型

    在这里插入图片描述


    首先了解一下set的集合操作,假如有三个集合

    在这里插入图片描述


    交集为:SINTER set1 set2 set3 ==> { c }
    并集为:SUNION set1 set2 set3 ==> { a,b,c,d,e }
    差集为:SDIFF set1 set2 set3 ==> { a }
    差集计算方式:set1 - (set2并set3) = {a、b、c} - {b、c、d、e} = {a} 只保留a中单独存在的元素
    共同关注A的人:可以用交集来实现
    我可能认识的人:可以使用差集来实现,把我关注的人求差集
    我关注的人也关注A:可以使用SISMEMBER 命令查看A是否在我关注的人的关注列表中,如果存在把这个人返回

sortedset有序集合类型

  • 介绍和 set 相比,sorted set 增加一个权重参数 score,使得集合中的元素能够按 score 进行有序排列,还可以通过 score 的范围来获取元素的列表。有点像是 Java 中 HashMap 和 TreeSet的结合体。
  • 常用命令: zadd,zcard,zscore,zrange,zrevrange,zrem 等。
  • 应用场景: 需要对数据根据某个权重进行排序的场景。比如在直播系统中,实时排行信息包含直播间在线用户列表,各种礼物排行榜,弹幕消息(可以理解为按消息维度的消息排行榜)等信息。

    在这里插入图片描述

  • Zset集合操作实现排行榜

    在这里插入图片描述

  1. 点击新闻,为其分值+1
    ZINCRBY hotNews:20210707 1 iphone13或有日落金玫瑰金
    2)展示当日排行前10
    ZREVRANGE hotNews:20210707 0 ,9 WITHscoreS

bitmap位图 类型

  1. 介绍bitmap 存储的是连续的二进制数字(0 和 1),通过 bitmap, 只需要一个 bit 位来表示某个元素对应的值或者状态,key 就是对应元素本身 。我们知道 8 个 bit 可以组成一个 byte,所以bitmap 本身会极大的节省储存空间。

    在这里插入图片描述

  2. 常用命令: setbit 、 getbit 、 bitcount 、 bitop
  3. 应用场景: 适合需要保存状态信息(比如是否签到、是否登录…)并需要进一步对这些信息进行分析的场景。比如用户签到情况、活跃用户情况、用户行为统计(比如是否点赞过某个视频)。
# SETBIT 会返回之前位的值(认是 0)这里会生成 7 个位 
127.0.0.1:6379> setbit mykey 7 1 
(integer) 0 
127.0.0.1:6379> setbit mykey 7 0 
(integer) 1 
127.0.0.1:6379> getbit mykey 7 
(integer) 0 
127.0.0.1:6379> setbit mykey 6 1 
(integer) 0 
127.0.0.1:6379> setbit mykey 8 1 
(integer) 0 
# 通过 bitcount 统计被被设置为 1 的位的数量。 
127.0.0.1:6379> bitcount mykey 
(integer) 2

针对上面提到的一些场景,这里进行进一步说明。

使用场景一用户行为分析 很多网站为了分析你的喜好,需要研究你点赞过的内容

# 记录你喜欢过 001 号小姐姐 
127.0.0.1:6379> setbit beauty_girl_001:uid 1 1 

使用场景二统计活跃用户

面试题:现在系统有亿级的活跃用户,为了增强用户粘性,该如何实现签到、日活统计

使用时间作为 key,然后用户 ID 为 offset,如果当日活跃过就设置为 1
那么我该如果计算某几天/月/年的活跃用户呢(暂且约定,统计时间内只有有一天在线就称为活跃),有请下一个 redis 的命令

# 对一个或多个保存二进制位的字符串 key 进行位元操作,并将结果保存到 destkey 上。 
# BITOP 命令支持 AND 、 OR 、 NOT 、 XOR 这四种操作中的任意一种参数 
BITOP operation destkey key [key ...]copy to clipboardErrorcopied 

初始化数据:

127.0.0.1:6379> setbit 20210308 1 1 
(integer) 0 
127.0.0.1:6379> setbit 20210308 2 1 
(integer) 0 
127.0.0.1:6379> setbit 20210309 1 1 
(integer) 0

统计 20210308~20210309 在线活跃用户数: 2

127.0.0.1:6379> bitop or desk2 20210308 20210309 
(integer) 1 
127.0.0.1:6379> bitcount desk2 
(integer) 2

geo地理位置类型

概述
Redis 3.2 中增加了对GEO类型的支持GEO,Geographic,地理信息的缩写。该类型,就是元素的2维坐标,在地图上就是经纬度。redis基于该类型,提供了经纬度设置,查询,范围查询,距离查询,经纬度Hash等常见操作
应用场景:附近的人、摇一摇、附近的车、附近银行站点查询

在这里插入图片描述


环境要求

  1. redis版本需要3.2及以上
  2. 如果使用jedis操作redis,需要jedis版本为2.9及以上
  3. 如果使用spring data redis操作redis,需要spring data redis版本为1.8.0及以上

redis GEO常用命令
Tips:
在学习geo命令时会使用到经纬度坐标信息,可以在百度地图的拾取坐标系统中获取测试坐标信息,网址:http://api.map.baidu.com/lbsapi/getpoint/index.html

1. geoadd命令

为了进行地理位置相关操作, 我们首先需要将具体的地理位置记录起来, 这一点可以通过执行 geoadd命令来完成, 该命令的基本格式如下:

GEOADD location-set longitude latitude name [longitude latitude name ...]

此命令用于添加位置信息到集合中
以下代码展示了如何通过 GEOADD 命令, 将武汉、襄阳、宜昌、枝江、咸宁等数个湖北省的市添加到位置集合 hubeiCities 集合里面

此处添加武汉的坐标信息到hubeiCities集合中

geoadd hubeiCities 114.32538 30.534535 wuhan

此处添加襄阳、枝江、咸宁的坐标信息到hubeiCities集合中

geoadd hubeiCities 112.161882 32.064505 xiangyang 111.305197 30.708127 yichang 111.583717 30.463363 zhijiang 114.295174 29.885892 xianning

3. geodist命令

此命令用于计算两个位置之间的距离,基本语法如下:

GEOdisT location-set location-x location-y [unit]

可选参数 unit 用于指定计算距离时的单位, 它的值可以是以下单位的其中一个
m 表示单位为米。
km 表示单位为千米。
mi 表示单位为英里。
ft 表示单位为英尺
案例:分别以认距离单位和指定距离单位计算襄阳和武汉的距离

--不指定距离单位 
127.0.0.1:6381> geodist hubeiCities xiangyang wuhan 
"266889.7642" 
--指定距离单位km 
127.0.0.1:6381> geodist hubeiCities xiangyang wuhan km 
"266.8898"

4. georadius命令和georadiusbymember命令

这两个命令都可以用于获取指定范围内的元素,也即查找特定范围之内的其他存在的地点。比如找出地点A范围200米之内的所有地点,找出地点B范围50公里之内的所有地点等等。

这两个命令的作用一样, 只是指定中心点的方式不同: georadius 使用用户给定的经纬度作为计算范围时的中心点, 而 georadiusbymember 则使用储存在位置集合里面的某个地点作为中心点。
以下是这两个命令的基本语法

GEORADIUS location-set longitude latitude radius m|km|ft|mi [WITHCOORD] 
[WITHdisT] [ASC|DESC] [COUNT count] 
GEORADIUSBYMEMBER location-set location radius m|km|ft|mi [WITHCOORD] [WITHdisT] 
[ASC|DESC] [COUNT count] 

这两个命令的各个参数的意义如下:
m|km|ft|mi 指定的是计算范围时的单位;
如果给定了WITHCOORD,那么在返回匹配的位置时会将位置的经纬度一并返回;
如果给定了WITHdisT , 那么在返回匹配的位置时会将位置与中心点之间的距离一并返回;
认情况下, GEORADIUS 和 GEORADIUSBYMEMBER 的结果是未排序的, ASC 可以让查找结果根据距离从近到远排序, 而 DESC 则可以让查找结果根据从远到近排序;
COUNT参数用于指定要返回的结果数量

下面通过案例分别演示georadius命令和georadiusbymember命令
GEORADIUS案例:
在hubeiCities位置集合中查找距离经纬度为112.927076,28.235653(长沙)500km以内的位置信息,
查找结果中应包含不超过5个位置的坐标信息,距离信息,并按距离由近到远排序。
查询代码如下:

127.0.0.1:6379> georadius hubeiCities 112.927076 28.235653 500 km withcoord withdist asc count 5
1) 1) "xianning"
   2) "226.6716"
   3) 1) "114.29517298936844"
      2) "29.885892172825898"
2) 1) "zhijiang"
   2) "279.9154"
   3) 1) "111.58371716737747"
      2) "30.463362486231127"
3) 1) "wuhan"
   2) "289.3798"
   3) 1) "114.32538002729416"
      2) "30.534534921664211"
4) 1) "yichang"
   2) "316.6777"
   3) 1) "111.30519658327103"
      2) "30.708127834982697"
5) 1) "xiangyang"
   2) "432.1767"
   3) 1) "112.16188341379166"
      2) "32.064505287046998"

GEORADIUSBYMEMBER案例:
在hubeiCities位置集合中查找距离襄阳200km以内的位置信息【这里指定的目标位置只能是hubeiCities中存在的位置,而不能指定位置坐标】,查找结果中应包含不超过2个位置的坐标信息,距离信息,并按距离由远到近排序。
查询代码如下:

127.0.0.1:6379> georadiusbymember hubeiCities xiangyang 200 km withcoord withdist desc count 2
1) 1) "zhijiang"
   2) "186.3784"
   3) 1) "111.58371716737747"
      2) "30.463362486231127"
2) 1) "yichang"
   2) "171.3950"
   3) 1) "111.30519658327103"
      2) "30.708127834982697"

4.Redis高级应用&拓展功能

4.1 发布订阅

Redis提供了发布订阅功能,可以用于消息的传输
Redis的发布订阅机制包括三个部分,publisher,subscriber和Channel

在这里插入图片描述


发布者和订阅者都是Redis客户端,Channel则为Redis服务器端。
发布者将消息发送到某个的频道,订阅了这个频道的订阅者就能接收到这条消息。

指令详情

  • SUBSCRIBE / PSUBSCRIBE : 订阅,精确、或者按匹配符
  • UNSUBSCRIBE / PUNSUBSCRIBE : 退订,精确、或者按匹配符
  • PUBLISH : 发送
  • PUBSUB :查看消息列表

频道/模式的订阅与退订
subscribe:订阅 subscribe channel1 channel2 …
Redis客户端1订阅频道1和频道2

127.0.0.1:6379> subscribe ch1 ch2 
Reading messages... (press Ctrl-C to quit) 
1) "subscribe" 
2) "ch1" 
3) (integer) 1 
1) "subscribe" 
2) "ch2" 
3) (integer) 2 

publish:发布消息 publish channel message
Redis客户端2将消息发布在频道1和频道2上

127.0.0.1:6379> publish ch1 hello 
(integer) 1 
127.0.0.1:6379> publish ch2 world 
(integer) 1 

Redis客户端1接收到频道1和频道2的消息

1) "message" 
2) "ch1" 
3) "hello" 
1) "message" 
2) "ch2" 
3) "world"

unsubscribe:退订 channel
Redis客户端1退订频道1

127.0.0.1:6379> unsubscribe ch1 
1) "unsubscribe" 
2) "ch1" 
3) (integer) 0

psubscribe :模式匹配 psubscribe +模式
Redis客户端1订阅所有以ch开头的频道

127.0.0.1:6379> psubscribe ch* 
Reading messages... (press Ctrl-C to quit) 
1) "psubscribe" 
2) "ch*" 
3) (integer) 1

Redis客户端2发布信息在频道5上

127.0.0.1:6379> publish ch5 helloworld 
(integer) 1 

Redis客户端1收到频道5的信息

1) "pmessage" 
2) "ch*" 
3) "ch5" 
4) "helloworld" 

punsubscribe 退订模式

127.0.0.1:6379> punsubscribe ch* 
1) "punsubscribe" 
2) "ch*" 
3) (integer) 0

使用场景
在Redis哨兵模式中,哨兵通过发布与订阅的方式与Redis主服务器和Redis从服务器进行通信
Redisson是一个分布式锁框架,在Redisson分布式锁释放的时候,是使用发布与订阅的方式通知
注:重业务的消息,推荐用消息队列

4.2 事务

所谓事务(Transaction) ,是指作为单个逻辑工作单元执行的一系列操作

ACID回顾

  • Atomicity(原子性):构成事务的的所有操作必须是一个逻辑单元,要么全部执行,要么全部不执行。
  • Consistency(一致性):数据库在事务执行前后状态都必须是稳定的或者是一致的。
  • Isolation(隔离性):事务之间不会相互影响。
  • Durability(持久性):事务执行成功后必须全部写入磁盘。

Redis事务
Redis 事务的本质是一组命令的集合
Redis的事务是通过multi、exec、discard和watch这四个命令来完成的。
Redis的单个命令都是原子性的,所以这里需要确保事务性的对象是命令集合。
Redis将命令集合序列化并确保处于同一事务的命令集合连续且不被打断的执行
Redis不能保障失败回滚
注意事项
注意!redis的事务远远弱于MysqL,严格意义上,它不能叫做事务,只是一个命令打包的批处理,不能保障失败回滚。
这是官方文档的原话:

It's important to note that even when a command fails, all the other commands in 
the queue are processed – Redis will not stop the processing of commands.

原理分析

  • 调用multi指令后,redis其实是开启了一个命令队列,后续的命令被提交到队列(还没有执行)
  • 期间出现问题了(比如down机),终止操作,队列清空
  • 到exec命令后,批量提交,事务完成

操作演示

127.0.0.1:6379> multi 
OK
127.0.0.1:6379(TX)> set s1 1 
QUEUED 
127.0.0.1:6379(TX)> get s1 
QUEUED 
127.0.0.1:6379(TX)> exec 
1) OK 
2) "1"

关于回滚
注意!回滚要看两种情况:

  • 直接语法错误,redis完全无法执行,Redis 2.6.5之前的版本不会回滚,之后版本整个事务回滚
  • 执行期的错误,redis不会回滚,其他正确的指令会照样执行

验证:错误的命令,导致回滚(版本:6.0)

#旧value是a 
127.0.0.1:9010> set a a 
OK
127.0.0.1:9010> get a 
"a" 
#开启事务 
127.0.0.1:9010> multi 
OK
#设置成b,语法没问题,进入队列 
127.0.0.1:9010> set a b 
QUEUED 
#语法错误! 
127.0.0.1:9010> set a 
(error) ERR wrong number of arguments for 'set' command 
#提交事务:失败,操作被回滚 
127.0.0.1:9010> exec 
(error) EXECABORT Transaction discarded because of prevIoUs errors. 
#最终结果:a没有被修改 
127.0.0.1:9010> get a 
"a" 

验证:命令语法对,但是数据类型不对,执行期间才会被发现!

#旧值a 
127.0.0.1:9010> get a 
"a" 
#开启事务 
127.0.0.1:9010> multi 
OK
#正确的语法,没毛病! 
127.0.0.1:9010> set a b 
QUEUED 
#语法也对,但是类型肯定是不对的,这不是一个list! 
#会进入队列,执行期才会发现这个问题 
127.0.0.1:9010> lpush a 1 
QUEUED 
#提交事务! 
#发现正确的1号命令执行ok,2号错误 
127.0.0.1:9010> exec 
1) OK 
2) (error) WRONGTYPE Operation against a key holding the wrong kind of value
#最终结果,a被修改,事务没有回滚! 
127.0.0.1:9010> get a 
"b"

watch
Redis Watch 命令用于监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断
关于上面的操作,如果遇到各种错误,multi可以自动帮你回滚
watch命令提供了另一种机制,它通过监控某个key的变动,来决定是不是回滚。
主要应用于高并发的正常业务场景下,处理并发协调。

1)使用语法
watch key

multi
…do somethings…
exec

2)操作演练
key无变动时

#初始化,a=a , b=1 
127.0.0.1:9010> set balance 80 
OK
127.0.0.1:9010> set name zimu 
OK
#监控a的变动 
127.0.0.1:9010> watch balance 
OK
#开启事务,内部对b进行操作 
127.0.0.1:9010> multi 
OK
127.0.0.1:9010> set name zimulaoshi 
QUEUED 
127.0.0.1:9010> exec 
1) OK 
#提交事务后,b正常被改写 
127.0.0.1:9010> get name 
"zimulaoshi"

如果watch的key发生了变化,注意有意思的事情来了……

#开启两个终端 T1, T2

#T1执行过程与上面一致 
#以下是T1的操作过程: 
#初始化,a=a , b=1 
127.0.0.1:9010> set balance 80 
OK
127.0.0.1:9010> set name zimu 
OK
#监控a的变动 
127.0.0.1:9010> watch balance 
OK
#开启事务,内部对b进行操作 
127.0.0.1:9010> multi 
OK
127.0.0.1:9010> set name zimu 
QUEUED 
# !!!这一步注意切换到T2: 
#在T1的watch和exec之间执行一个 set a 123,a的值被别的终端修改了!!! 
#再切回T1,注意!exec得不到ok,得到了一个nil,说明队列被清空了! 
127.0.0.1:9010> exec 
(nil) 
#来查看b的值,没有被改为2,事务回滚了! 
127.0.0.1:9010> get b 
"1" 

3)原理剖析
在exec执行事务的一瞬间,判断监控的key是否变动
变动则取消事务队列,直接不执行
无变动则执行,提交事务,参考流程图:

在这里插入图片描述

4.3 Lua脚本

lua是一种轻量小巧的脚本语言,用标准C语言编写并以源代码形式开放, 其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能

Lua应用场景:

Redis使用lua脚本
版本:自2.6.0起可用,通过内置的lua编译/解释器,可以使用EVAL命令对lua脚本进行求值。
时间复杂度:取决于执行的脚本。
使用Lua脚本的好处:

  • 减少网络开销。可以将多个请求通过脚本的形式一次发送,减少网络时延。
  • 原子操作。redis会将整个脚本作为一个整体执行,中间不会被其他命令插入。因此在编写脚本的过程中无需担心会出现竞态条件,无需使用事务。
  • 复用。客户端发送的脚本会永久存在redis中,这样,其他客户端可以复用这一脚本而不需要使用代码完成相同的逻辑。

如何使用 EVAL命令
命令格式:

EVAL script numkeys key [key ...] arg [arg ...]

命令说明:

  • script :参数是一段 Lua 5.1 脚本程序。脚本不必(也不应该)定义为一个 Lua 函数
  • numkeys : 用于指定键名参数的个数。
  • key [key …] ,是要操作的键,可以指定多个,在lua脚本中通过 KEYS[1] , KEYS[2] 获取
  • arg [arg …] ,附加参数,在lua脚本中通过 ARGV[1] , ARGV[2] 获取

实例:

eval "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 key1 key2 first second

lua脚本中调用Redis命令

  • redis.call():

返回值就是redis命令执行的返回值
如果出错,则返回错误信息,不继续执行

  • redis.pcall():

返回值就是redis命令执行的返回值
如果出错,则记录错误信息,继续执行
注意事项
在脚本中,使用return语句将返回值返回给客户端,如果没有return,则返回nil

eval "return redis.call('set',KEYS[1],ARGV[1])" 1 n1 zhaoyun

命令行里使用
如果直接使用 redis-cli 命令,格式会有点不一样:

redis-cli --eval lua_file key1 key2 , arg1 arg2 arg3

注意的地方:

  • eval 后面参数是lua脚本文件, .lua 后缀
  • 不用写 numkeys ,而是使用 , 隔开。注意 , 前后有空格。

示例:
incrbymul.lua

local num = redis.call('GET', KEYS[1]); 
if not num then 
    return 0; 
else
    local res = num * ARGV[1]; 
    redis.call('SET',KEYS[1], res); 
    return res; 
end

命令行运行:

$ redis-cli --eval incrbymul.lua lua:incrbymul , 8 
(integer) 0 
$ redis-cli incr lua:incrbymul 
(integer) 1 
$ redis-cli --eval incrbymul.lua lua:incrbymul , 8 
(integer) 8 
$ redis-cli --eval incrbymul.lua lua:incrbymul , 8 
(integer) 64 
$ redis-cli --eval incrbymul.lua lua:incrbymul , 2 
(integer) 128 

由于redis没有提供命令可以实现将一个数原子性的乘以N倍,这里我们就用Lua脚本实现了,运行过程中确保不会被其它客户端打断。

4.4 慢查询日志

概述
问:首先我们需要知道redis的慢查询日志有什么用?
问:日常在使用redis的时候为什么要用慢查询日志?
客户端请求的生命周期的完整生命周期,4个阶段

在这里插入图片描述


注意:慢查询统计步骤3的时间,所以没有慢查询并不代表客户端没有超时问题。换句话说。redis的慢查询记录时间指的是不包括像客户端响应、发送回复等IO操作,而单单是执行一个查询命令所耗费的时间

一个问题:
查询日志是为了记录执行时间超过给定时长的redis命令请求
第二个问题:
让使用者更好地监视和找出在业务中一些慢redis操作,找到更好的优化方法

设置和查看SLOWLOG
查询配置相关的参数
slowlog-log-slower-than :选项指定执行时间超过多少微秒(认1秒=1,000,000微秒)的命令请求会被记录到日志上。
例:如果这个选项的值为100,那么执行时间超过100微秒的命令就会被记录到慢查询日志; 如果这个选项的值为500 , 那么执行时间超过500微秒的命令就会被记录到慢查询日志;
**slowlog-max-len :选项指定服务器最多保存多少条慢查询日志。**服务器使用先进先出的方式保存多条慢查询日志: 当服务器储存的慢查询日志数量等于slowlog-max-len选项的值时,服务器在添加一条新的慢查询日志之前,会先将最旧的一条慢查询日志删除
例:如果服务器slowlog-max-len的值为100,并且假设服务器已经储存了100条慢查询日志, 那么如果服务器打算添加一条新日志的话,它就必须先删除目前保存的最旧的那条日志, 然后再添加新日志。

在Redis中有两种修改配置的方法,一种是修改配置文件,另一种是使用config set命令动态修改
查询配置相关的命令

  1. config set slowlog-log-slower-than 20000
  2. config set slowlog-max-len 1024
  3. showlog get # 查看慢查询日志

查询日志的访问和管理

1. 获取[n条]慢查询队列 slowlog get [n]
  1. 获取查询队列的当前长度 slowlog len
  2. 清空慢查询队列 slowlog reset
    查询日志的使用案例
  3. 设置慢查询时长: config set slowlog-log-slower-than 0 # 0表示将所有命令都记录为慢查询
  4. 设置最多保存多少条慢查询日志: config set slowlog-max-len 3
  5. 获得慢查询日志: slowlog get

查询日志的组成
    慢查询日志由以下四个属性组成:

  • 标识ID
  • 发生时间戳,
  • 命令耗时,
  • 执行命令和参数

在生产环境中,慢查询功能可以有效地帮助我们找到Redis可能存在的瓶颈,但在实际使用过程中要注意以下几点:
1、slowlog-max-len:线上建议调大慢查询表,记录慢查询时Redis会对长命令做阶段操作,并不会占用大量内存.增大慢查询列表可以减缓慢查询被剔除的可能,例如线上可设置为1000以上.
2、slowlog-log-slower-than:认值超过10毫秒判定为慢查询,需要根据Redis并发量调整该值.
3、慢查询只记录命令的执行时间,并不包括命令排队和网络传输时间.因此客户端执行命令的时间会大于命令的实际执行时间.因为命令执行排队机制,慢查询会导致其他命令级联阻塞,因此客户端出现请求超时时,需要检查该时间点是否有对应的慢查询,从而分析是否为慢查询导致的命令级联阻塞.
4、由于慢查询日志是一个先进先出的队列,也就是说如果慢查询比较多的情况下,可能会丢失部分慢查询命令,为了防止这种情况发生,可以定期执行slowlog get命令将慢查询日志持久化到其他存储中(例如:MysqL等),然后可以通过可视化工具进行查询.

相关文章

在笔者近 3 年的 Java 一线开发经历中,尤其是一些移动端、用...
这一篇文章拖了有点久,虽然在项目中使用分布式锁的频率比较...
本文梳理总结了一些 Java 互联网项目中常见的 Redis 缓存应用...
书接上回,消息通知系统(notification-system)作为一个独立...
Redis 是目前互联网后端的热门中间件之一,在许多方面都有深...
在Java Spring 项目中,数据与远程数据库的频繁交互对服务器...