redis初学习

redis是什么

Redis是C语言开发的一个开源的(遵从BSD协议)高性能键值对(key-value)的内存数据库,可以用作数据库、缓存、消息中间件等。它是一种NoSQL(not-only sql,泛指非关系型数据库)的数据库。

redis优点

1、性能优秀,数据在内存中,读写速度非常快,支持并发10W QPS;

2、单进程单线程,是线程安全的,采用IO多路复用机制;

3、丰富的数据类型,支持字符串(strings)、散列(hashes)、列表(lists)、集合(sets)、有序集合(sorted sets)等;

4、支持数据持久化。可以将内存中数据保存在磁盘中,重启时加载;

5、主从复制,哨兵,高可用;

6、可以用作分布式锁;

7、可以作为消息中间件使用,支持发布订阅

基本数据类型

1、**string是redis最基本的类型,**用于管理 redis 字符串值;string类型是二进制安全的,意思是redis的string类型可以包含任何数据,比如jpg图片或者序列化的对象。string类型的值最大能存储512M。

2、**Hash是一个键值(key-value)的集合。**redis的hash是一个string的key和value的映射表,Hash特别适合存储对象。

3、**list列表是简单的字符串列表,按照插入顺序排序。**可以添加一个元素到列表的头部(左边)或者尾部(右边)。

4、**set是string类型的无序集合。**集合是通过hashtable实现的。set中的元素是没有顺序的,而且是没有重复的。

5、**sorted set可以通过用户额外提供一个优先级(score)的参数来为成员排序,**并且是插入有序的,即自动排序。

缓存穿透

解释:缓存穿透是指查询一个一定不存在的数据(例如id=-1),由于缓存是不命中时被动写的,并且出于容错考虑,如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义。

解决方案:

  • 采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被 这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。
  • 如果一个查询返回的数据为空(不管是数据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。

缓存雪崩

**解释:**缓存雪崩是指在我们设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到DB,DB瞬时压力过重雪崩。

解决方案:

  • 原有的失效时间基础上增加一个随机值,比如1-5分钟。
  • 如果Redis是集群部署,将热点数据均匀分布在不同的Redis库中也能避免全部失效。
  • 设置热点数据永不过期,有更新操作就更新缓存就好了

缓存击穿

**解释:**对于一些设置了过期时间的key,如果这些key可能会在某些时间点被超高并发地访问,是一种非常“热点”的数据。缓存在某个时间点过期的时候,恰好在这个时间点对这个Key有大量的并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。

解决方案:

  • 设置数据不过时
  • 加互斥锁

淘汰策略

策略描述
volatile-lru从已设置过期时间的KV集中优先对最近最少使用(less recently used)的数据淘汰
volitile-ttl从已设置过期时间的KV集中优先对剩余时间短(time to live)的数据淘汰
volitile-random从已设置过期时间的KV集中随机选择数据淘汰
allkeys-lru从所有KV集中优先对最近最少使用(less recently used)的数据淘汰
allKeys-random从所有KV集中随机选择数据淘汰
noeviction不淘汰策略,若超过最大内存,返回错误信息

持久化

Redis的持久化策略有两种:

1、RDB:快照形式是直接把内存中的数据保存到一个dump的文件中,定时保存,保存策略。

2、AOF:把所有的对Redis的服务器进行修改的命令都存到一个文件里,命令的集合。Redis默认是快照RDB的持久化方式。

当Redis重启的时候,它会优先使用AOF文件来还原数据集,因为AOF文件保存的数据集通常比RDB文件所保存的数据集更完整。

Redis默认是快照RDB的持久化方式。

事务

理解原子性

原子性操作不会被其他线程打断

什么是事务

定义:事务就是指一系列操作,这些操作要么同时成功,要么同时失败,它是一种原子操作。原子就是一个整体,不可分割,事务中的这些操作也是一样

假设有这样一个场景:张三向李四转账 100 块钱,最少需要经历两个步骤

  1. 张三的银行卡中钱减100块
  2. 李四的银行卡中钱加100块

在没有事务的情况下,可能会导致 1 号操作执行成功,2 号操作执行失败,那结果是张三的钱少了 100 块,李四的钱却没增加,张三的 100 块凭空消失了,这肯定会出问题,所以这两部操作必须要放在事务中执行,要么转账成功,要么转账失败,这就是事务的作用

redis事务

redis 是单线程的程序,每个指令都保证原子性,但是不保证多个指令的原子性。

要使多个指令也具有原子性,可以使用redis事务

redis 事务保证两点:

  1. 执行一个事务时,所有的命令都会被顺序执行,且不会被其他客户端打断
  2. 执行一个事务时,所有命令要么同时成功,要么同时失败

如果 redis 开启 AOF 模式,在执行 redis 事务之前,redis 会先把事务写在磁盘中再执行命令。如果事务执行到一半,redis 进程突然就挂了,那么就会导致事务中只有一部分指令执行成功,还有一部分指令还未执行。这种情况下,在 redis 下一次启动时会检测到错误,并退出 redis。使用 redis-check-aof 工具可以对 AOF 文件进行修复,删除掉磁盘中的未执行成功的事务,然后重新启动加载 AOF 文件,redis 就恢复到事务没有执行创建的状态,就可以正常启动了

事务相关指令
multi

使用 multi 指令来开启事务

127.0.0.1:6379> set lisa 1000
OK

127.0.0.1:6379> set ben 1000
OK

127.0.0.1:6379> multi
OK

127.0.0.1:6379>incrby ben -100
QUEUED

127.0.0.1:6379>incrby lisa 100
QUEUED

multi 指令返回 ok,证明事务开启成功
开启事务后,接下来执行每条正确的指令都返回 QUEUE,代表命令被放入等待队列,但并没有执行

exec

使用 exec 命令来执行事务

127.0.0.1:6379> set lisa 1000
OK

127.0.0.1:6379> set ben 1000
OK

127.0.0.1:6379> multi
OK

127.0.0.1:6379>incrby ben -100
QUEUED

127.0.0.1:6379>incrby lisa 100
QUEUED

127.0.0.1:6379> exec   # 执行事务
1) (integer) 900
2) (integer) 1100

使用 exec 执行事务后,会按顺序返回事务中每条指令的返回结果

discard

如果事务创建成功,但你又不想执行了,使用 discard 命令来取消

127.0.0.1:6379> multi
OK

127.0.0.1:6379>incrby ben -100
QUEUED

127.0.0.1:6379>incrby lisa 100
QUEUED

127.0.0.1:6379> discard                 # 取消事务
OK
redis事务乐观锁

**定义:**乐观锁,即认为所有时候都不会出现问题,它不会去加锁,他会在修改数据的时候判断这个数据是否被改动过,如果没有被其他人改动过,就更新数据

watch

通过 watch 命令来监视这个 key/value,如果发现该 key/value 被修改,则调用 exec 时事务不会被执行

127.0.0.1:6379> watch lisa ben #监视 lisa 和 ben
OK

127.0.0.1:6379> multi
OK

127.0.0.1:6379>incrby ben -100
QUEUED

127.0.0.1:6379>incrby lisa 100
QUEUED

================ 这时候值被其他客户端修改=======================

127.0.0.1:6379> exec   # 事务执行失败
(nil)
unwatch

取消监视,与 watch 命令相对
在执行 exec 后,不管是否执行成功,watch 命令都会 unwatched
客户端退出后,watch 命令也会 unwatched

127.0.0.1:6379> watch lisa ben #监视 lisa 和 ben
OK

127.0.0.1:6379> multi
OK

127.0.0.1:6379>incrby ben -100
QUEUED

127.0.0.1:6379>incrby lisa 100
QUEUED

127.0.0.1:6379> unwatch  # 取消监视
OK
和事务相关的错误

在 redis 事务中,可能会发生两种错误

1、编译型错误

multi 后,exec 调用之前,由于输入不正确的 redis 命令导致的错误

127.0.0.1:6379> multi
OK

127.0.0.1:6379> sets lisa 22   # 事务中命令输错
(error) ERR unknown command `sets`, with args beginning with: `lisa`, `22`, 

127.0.0.1:6379> set lisa 22
QUEUED

127.0.0.1:6379> exec   # 事务执行不成功
(error) EXECABORT Transaction discarded because of previous errors.
2、运行时错误

exec 调用之后,由于 redis 命令执行不成功导致的错误

127.0.0.1:6379> set test abc
OK

127.0.0.1:6379> multi
OK

127.0.0.1:6379> incr test   # 由于test中存的不是 integer,在exec时会出错
QUEUED

127.0.0.1:6379> set info aaa
QUEUED

127.0.0.1:6379> exec
1) (error) ERR value is not an integer or out of range
2) OK

从执行结果看出,在 exec 时,即使其中有命令出错,后面的命令不会被影响,依旧会被执行
从这里也可以看出,redis 不支持回滚

发布/订阅

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Edure0Ro-1606642501533)(https://i.loli.net/2020/11/29/hwnzKFIAGWiLRsm.png)]

  • PUBLISH 命令向通道发送信息,此客户端称为publisher 发布者;

  • SUBSCRIBE 向命令通道订阅信息,此客户端称为subscriber 订阅者;

  • redis 中 发布订阅模块的名字叫着 PubSub,也就是 PublisherSubscriber;

  • 一个发布者向一个通道发送消息,订阅者可以向多个通道订阅消息;当发布者向通道发布消息后,如果有订阅者订阅该通道,订阅者就会收到消息;

PubSub模块命令

  • subscribe: 订阅一个或者多个频道;

  • unsubscribe: 退订一个或者多个频道;

  • publish: 向通道发送消息;

  • psubscribe: 订阅给定模式相匹配的所有频道;

  • punsubscribe: 退订 给定模式所有的频道,若未指定模式,退订所有频道;

客户端实现

需要开启两个 redis-cli 客户端。

# 第一个 redis-cli 客户端
127.0.0.1:6379> SUBSCRIBE redisChat

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

现在,我们先重新开启个 redis 客户端,然后在同一个频道 redisChat 发布两次消息,订阅者就能接收到消息。

# 第二个 redis-cli 客户端
127.0.0.1:6379> PUBLISH redisChat "Redis PUBLISH test"

(integer) 1

# 订阅者的客户端会显示如下消息

1) "message"
2) "redisChat"
3) "Learn redis by test.com"

redis 管道技术

为什么使用管道技术

单条命令的执行步骤:

  • 客户端把命令发送到服务器,然后阻塞客户端,等待着从socket读取服务器的返回结果
  • 服务器处理命令并将结果返回给客户端

按照这样的描述,每个命令的执行时间 = 客户端发送时间+服务器处理和返回时间+一个网络来回的时间

其中一个网络来回的时间是不固定的,它的决定因素有很多,比如客户端到服务器要经过多少跳,网络是否拥堵等等。但是这个时间的量级也是最大的,也就是说一个命令的完成时间的长度很大程度上取决于网络开销。

优化
  • Redis 管道技术可以在服务端未响应时,客户端可以继续向服务端发送请求,并最终一次性读取所有服务端的响应。

  • 使用管道时,多个命令只会进行一次read()wrtie()系统调用,因此使用管道会提升Redis服务器处理命令的速度,随着管道中命令的增多,服务器每秒处理请求的数量会线性增长,最后会趋近于不使用管道的10倍。

实例

查看 redis 管道,只需要启动 redis 实例并输入以下命令:

$(echo -en "PING\r\n SET testkey redis\r\nGET testkey\r\nINCR visitor\r\nINCR visitor\r\nINCR visitor\r\n"; sleep 10) | nc localhost 6379

+PONG
+OK
redis
:1
:2
:3

在返回的结果中这些命令一次性向 redis 服务提交,并最终一次性读取所有服务端的响应

redis脚本

redis脚本简介

脚本使用 Lua 解释器来执行脚本。 Redis 从2.6版本开始,通过内嵌支持 Lua 环境。执行脚本的常用命令为 EVAL。

redis lua脚本的好处
  • 减少网络开销。可以将多个请求通过脚本的形式一次发送,减少网络延迟。
  • 原子操作。redis将整个脚本当做一个整体去执行,中间不会被其他命令插入。因此无需担心脚本执行过程中会出现竞态条件
  • 复用。客户端发送的脚本会永久保存在redis中,这样,可以复用这一脚本而不用使用代码完成相同的逻辑。
redis lua脚本如何使用
  • EVAL script numkeys key [key …] arg [arg …]

    • 说明:EVAL命令使用lua解释器执行脚本

    • 参数:

      script :参数是一段lua5.1脚本程序,也可以是lua脚本的地址。

      numkeys:用于指定键名参数的个数

      keyN:从EVAL的第三个参数开始算起,表示在脚本中所有用到的redis键(key)

      argN:附加参数,在lua脚本中通过全局变量ARGV数组的形式使用

实例

127.0.0.1:6379> eval "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 string1 string2 first second
1) "string1"
2) "string2"
3) "first"
4) "second"

127.0.0.1:6379> EVAL "redis.call('set',KEYS[1],ARGV[1]);redis.call('set',KEYS[2],ARGV[2]);return 'ok';" 2 string1 string2 str1111 str2222
"ok"

注:

  • {}在lua里是指数据类型table,类似数组。

  • redis.call()可以调用redis命令,redis.pcall()有着相同的功能

  • EVALSHA sha1 numkeys key [key …] arg [arg …]

    • 说明:

      • EVALSHA命令根据给定的sha1校验码,执行缓存在redis中的lua脚本;
      • 将脚本缓存到服务器可以使用SCRIPT LOAD命令实现。
      • 这个命令和EVAL命令基本相似,除了传的第一个参数不同之外,其他都一样
    • 参数:

      • sha1:通过SCRIPT LOAD生成的sha1校验码

      • 其他参数和EVAL命令一样

    • 返回:lua脚本中指定的返回值

实例

127.0.0.1:6379> SCRIPT LOAD "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}"
"a42059b356c875f0717db19a51f6aaca9ae659ea"
127.0.0.1:6379> EVALSHA a42059b356c875f0717db19a51f6aaca9ae659ea 2 string1 string2 first second
1) "string1"
2) "string2"
3) "first"
4) "second"

SCRIPT LOAD script

  • 说明:

    • SCRIPT LOAD命令用于将脚本script添加到脚本缓存中,并不立即执行该脚本,如果给定的脚本已经在缓存中存在了,那不执行任何操作;

    • EVAL命令也会将脚本script添加到脚本缓存中,但是会立即执行;在脚本加入到缓存之后,通过EVALSHA命令,可以使用脚本的sha1校验和调用该脚本;

    • 脚本可以在缓存中保存无限长的时间,直到执行 SCRIPT FLUSH命令为止。

  • 参数:script(lua脚本)

  • 返回:给定的lua脚本的sha1校验码

实例

127.0.0.1:6379> SCRIPT LOAD "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}"
"a42059b356c875f0717db19a51f6aaca9ae659ea"
  • SCRIPT EXISTS sha1 [sha1 …]
    • 说明:用于校验指定的脚本是否已被保存到缓存中
    • 参数:shaN(指定的脚本的sha1校验码)
    • 返回:一个列表,包含0和1;0代表指定的脚本未被保存到缓存中,1代表指定脚本已被保存到缓存中

实例

127.0.0.1:6379> SCRIPT EXISTS a42059b356c875f0717db19a51f6aaca9ae659ea
1) (integer) 1
10.117.8.188:6379> SCRIPT EXISTS np2059b356c875f0717db19a51f6aaca9ae659ea
1) (integer) 0
10.117.8.188:6379> SCRIPT EXISTS a42059b356c875f0717db19a51f6aaca9ae659ea np2059b356c875f0717db19a51f6aaca9ae659ea
1) (integer) 1
2) (integer) 0
  • SCRIPT FLUSH
    • 说明:用于清除所有脚本缓存
    • 返回:总是返回OK

实例

127.0.0.1:6379> SCRIPT FLUSH
OK
10.117.8.188:6379> SCRIPT EXISTS a42059b356c875f0717db19a51f6aaca9ae659ea
1) (integer) 0
  • SCRIPT KILL
    • 说明:杀死正在执行的脚本,当且仅当该脚本没有执行任何写操作,该命令才生效;主要用于终止执行时间过长的脚本,例如某脚本进入死循环;SCRIPT KILL 执行之后,当前正在执行的脚本会被终止,执行这个脚本的客户端会从EVAL命令的阻塞中退出,并返回一个错误
    • 返回: 执行成功返回OK,失败则返回一个错误信息

实例

12.0.0.1:6379> SCRIPT KILL
(error) NOTBUSY No scripts in execution right now. //没有脚本在执行
(error) ERR Sorry the script already executed write commands against the dataset. You can either wait the script termination or kill the server in an hard way using the SHUTDOWN NOSAVE command.//尝试杀死一个执行过写操作的脚本
(error) ERR Error running script (call to f_694a5fe1ddb97a4c6a1bf299d9537c7d3d0f84e7): Script killed by user with SCRIPT KILL... // 脚本被杀死后,客户端返回的错误信息
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值