Redis官方文档解读(开篇)

1.请参看redis官方文档

一.Redis简介

1.Redis是一个开源(BSD许可)的内存数据结构存储,用作数据库、缓存、消息代理(message broker)和流媒体引擎(streaming engine)Redis提供数据结构,如string,hash,list,set,sorted set with range query(带范围查询的排序集),bitmap,hyperloglog,geospatial index和streamRedis有内置复制,Lua脚本,LRU驱逐(LRU eviction),事务和不同级别的磁盘持久性,并通过Redis Sentinel和Redis Cluster自动分区提供高可用性

2.你可以在这些类型上运行原子操作,like appending to a string; incrementing the value in a hash; pushing an element to a list; computing set intersection, union and difference(集的交、并、差计算); or getting the member with highest ranking in a sorted set(获得排序集中排名最高的成员).

3.为了达到最佳性能,Redis使用内存数据集。根据您的用例,Redis可以通过定期将数据集转储到磁盘或将每个命令附加到基于磁盘的日志来持久化数据。

4.Redis支持异步复制,with fast non-blocking synchronization and auto-reconnection with partial resynchronization on net split.

5.Redis还包括:

  • 事务
  • 发布/订阅
  • Lua脚本
  • Keys with a limited time-to-live(生命有限的key)
  • LRU eviction of keys(LRU缓存淘汰)
  • 自动故障切换

二.Redis常见问题

2.1 Redis与其他键值存储有什么不同?

1.Redis在key-value数据库中有一个不同的演化路径,其中值可以包含更复杂的数据类型,并在这些数据类型上定义原子操作。Redis数据类型与基本的数据结构密切相关,并向程序员公开,没有额外的抽象层。

2.Redis is an in-memory but persistent on disk database,所以它代表了一种不同的权衡,在数据集不能大于内存的限制下可以实现非常高的读写速度。内存数据库的另一个优点是,与磁盘上相同的数据结构相比,复杂数据结构的内存表示更容易操作,所以Redis可以在很少的内部复杂性下做很多事情。同时两种磁盘存储格式(RDB和AOF)不需要适合随机访问,因此它们是紧凑的,并且总是以仅附加(append-only)的方式生成(甚至AOF日志旋转也是仅附加的操作,因为新版本是从内存中的数据副本生成的)。然而,与传统的磁盘存储相比,这种设计也面临不同的挑战。作为内存中的主要数据表示,Redis操作必须小心处理,以确保硬盘上总是有一个更新版本的数据集。

2.2 Redis的内存占用是多少?

1.给你几个例子(都是通过64位实例获得的):

  • 空实例使用约3MB内存。
  • 100万个小键(small keys)—>String Value pairs(字符串值对)使用约85MB的内存。
  • 1 Million Keys—>Hash value(哈希值),表示一个有5个字段的对象,使用大约160mb的内存。

2.使用redis-benchmark实用程序生成随机数据集,然后使用INFO内存命令检查使用的空间。

2.3 为什么Redis会将其整个数据集保存在内存中?

数据从内存服务,磁盘用于存储。如果真正的问题不是所需的总RAM,而是需要将数据集拆分为多个Redis实例,请阅读本文档中的分区页面以了解更多信息。

2.4 如何减少Redis的内存使用量?

如果可以,请使用Redis 32位实例。还可以充分利用small hashes, lists, sorted sets, and sets of integers(小散列、列表、排序集和整数集),因为Redis能够以更紧凑的方式在少数元素的特殊情况下表示这些数据类型内存优化页面中有更多信息。

2.5 如果Redis内存用完怎么办?

Redis有内置的保护功能,允许用户设置内存使用的最大限制,使用配置文件中的maxmemory选项来限制Redis可以使用的内存。如果达到此限制,Redis将开始给写命令回复一个错误(但将继续接受只读命令)。您还可以将Redis配置为在达到最大内存限制时evict keys(驱逐key)。

2.6 Redis如何使用多个cpu或内核?

CPU成为Redis的瓶颈的情况并不常见,因为Redis通常会受到内存或网络的限制。例如,当使用流水线(pipelining)的时候,一个运行在Linux系统上的Redis实例平均每秒可以发送100万个请求,所以如果你的应用程序主要使用O(N)或O(log(N))命令,它几乎不会占用太多的CPU。

2.7 为什么我的复制副本在其主实例中有不同数量的key?

如果您使用的key生存时间有限(Redis过期),这是正常行为:

  • 主节点(primary)在第一次与复制副本(replica)同步时生成一个RDB文件。
  • RDB文件将不包括在主节点中已经过期但仍然在内存中的键。
  • 这些键仍然在Redis primary内存中,即使逻辑上已经过期。它们将被认为是不存在的,它们的内存将在以后被回收,或者增量地或显式地在访问时回收。虽然这些键在逻辑上不是数据集的一部分,但它们在INFO输出和DBSIZE命令中被考虑。
  • 当副本读取由主副本生成的RDB文件时,这组键不会被加载。

因此,拥有许多过期key的用户在副本中看到的key较少是很常见的。但是,从逻辑上讲,主副本和副本将具有相同的内容

三.Redis 客户端

在这里插入图片描述

四.redis数据类型

4.1 Strings

1.字符串是最基本的Redis value。Redis字符串是二进制安全的,这意味着Redis字符串可以包含任何类型的数据,例如JPEG图像或序列化的Ruby对象

##使用SET和GET命令是我们设置和检索字符串值的方式。
127.0.0.1:6379> set mykey somevalue
OK
127.0.0.1:6379> get mykey
"somevalue"
##注意,如果键已经存在,SET将替换已经存储到键中的任何现有值,即使该键与非字符串值相关联。
127.0.0.1:6379> type mylist
list
127.0.0.1:6379> set mylist hello
OK
127.0.0.1:6379> type mylist
string
127.0.0.1:6379> get mylist
"hello"

2.字符串值的最大长度为512 MB

3.使用Redis中的字符串可以做很多有趣的事情,例如:

  • 使用INCR系列中的命令将字符串用作原子计数器:INCR、DECR、INCRBY。
  • 使用Append命令追加到字符串。
  • 使用字符串作为GETRANGE和SETRANGE的随机访问向量。
  • 在小空间内编码大量数据,或者使用GETBIT和SETBIT创建一个Redis支持的Bloom过滤器。

4.例如,一个是原子增量:INCR是原子的是什么意思?即使多个客户端针对同一key发出INCR,也永远不会进入竞争条件。For instance, it will never happen that client 1 reads “10”, client 2 reads “10” at the same time, both increment to 11, and set the new value to 11

127.0.0.1:6379> set counter 100
OK
##INCR命令将字符串值解析为整数,将其递增1,最后将获得的值设置为新值。类似的命令还有INCRBY、DECR和DECRBY
127.0.0.1:6379> incr counter
(integer) 101
127.0.0.1:6379> incr counter
(integer) 102
127.0.0.1:6379> incrby counter 50
(integer) 152

5.在一个命令中设置或检索多个键的值对于减少延迟也很有用。因此,有MSET和MGET命令。使用MGET时,Redis返回一个值数组。

127.0.0.1:6379> mset a 10 b 20 c 30
OK
127.0.0.1:6379> mget a b c
1) "10"
2) "20"
3) "30"
4.2 Lists

1.Redis Lists是简单的字符串列表,按插入顺序排序。可以在Redis列表中添加元素,将新元素推送到列表的头部(左侧)或尾部(右侧)。LPUSH命令在头部插入一个新元素,而RPUSH命令在尾部插入一个新元素。当对空键执行其中一个操作时,将创建一个新列表。类似地,如果列表操作将清空列表,则从键空间中删除键。

##可以在一次调用中将多个元素推入列表:比如rpush mylist 1 2 3 4 5 "foo bar"
127.0.0.1:6379> LPUSH mylist a
(integer) 1
127.0.0.1:6379> lrange mylist 0 -1
1) "a"
127.0.0.1:6379> LPUSH mylist b
(integer) 2
127.0.0.1:6379> lrange mylist 0 -1
1) "b"
2) "a"
127.0.0.1:6379> RPUSH mylist c
(integer) 3
127.0.0.1:6379> lrange mylist 0 -1
1) "b"
2) "a"
3) "c"

2.Redis列表中定义的一个重要操作是弹出元素的能力。弹出元素是从列表中检索元素,同时将其从列表中删除的操作。您可以从左侧和右侧弹出元素

127.0.0.1:6379> lrange mylist 0 -1
1) "b"
2) "a"
3) "c"
127.0.0.1:6379> rpop mylist
"c"
127.0.0.1:6379> rpop mylist
"a"
127.0.0.1:6379> rpop mylist
"b"
127.0.0.1:6379> rpop mylist
(nil)
127.0.0.1:6379> 

3.Redis允许我们使用列表用作一个有上限的集合,只记住最新的N个项,并使用LTRIM命令丢弃所有最旧的项。LTRIM命令类似于LRANGE,但它没有显示指定的元素范围,而是将该范围设置为新的列表值。将删除给定范围之外的所有元素。

127.0.0.1:6379> rpush mylist 1 2 3 4 5
(integer) 5
127.0.0.1:6379> lrange mylist 0 -1
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"
##告诉 Redis 只取索引 0 到 2 的列表元素,其他所有元素都将被丢弃。
127.0.0.1:6379> ltrim mylist 0 2
OK
127.0.0.1:6379> lrange mylist 0 -1
1) "1"
2) "2"
3) "3"
127.0.0.1:6379> 

4.列表的最大长度是2^32 - 1个元素(4294967295,每个列表包含超过40亿个元素)。从时间复杂度的角度来看,Redis list的主要特性是支持在头部和尾部附近进行常量时间的插入和删除元素,即使有数百万项插入。访问列表的极端附近的元素非常快,但如果您尝试访问一个非常大的列表的中间,就会很慢,因为这是一个O(N)操作。

5.你可以用Redis列表做很多有趣的事情,例如:

  • 在社交网络中建模时间轴,使用LPUSH在用户时间线上添加新元素,使用LRANGE检索最近插入的一些项目。
  • 您可以同时使用LPUSH和LTRIM来创建一个永远不会超过给定元素数量的列表,只会记住最新的N个元素
  • Lists can be used as a message passing(消息传递) primitive。流程之间的通信,使用消费者-生产者模式,生产者将项目推入列表,消费者(通常是工人)消费这些项目并执行操作。

6.Redis列表是通过链表实现的。这意味着,即使列表中有数百万个元素,在列表的开头或结尾添加新元素的操作也会在常量时间内执行。缺点是什么?在用数组实现的列表中,按索引访问元素的速度非常快(常量时间索引访问),而在用链表实现的列表中,按索引访问元素的速度则不那么快(在链表中,操作所需的工作量与被访问元素的索引成正比)。

7.Blocking operations on lists(阻塞列表上的操作)
(1)列表有一个特殊的特性,使其适合于实现队列,并且通常作为进程间通信系统的构建块:阻塞操作。想象一下,你想用一个进程将项目推送到一个列表中,然后使用另一个进程来实际处理这些项目。这是通常的生产者/消费者设置,可以通过以下简单方式实现:

  • 为了将项目推送到列表中,生产者调用LPUSH。
  • 为了从列表中提取/处理项,消费者调用RPOP。

但是,有时可能列表是空的,没有什么要处理的,因此RPOP只返回NULL。在这种情况下,消费者将被迫等待一段时间,并再次使用RPOP重试。这被称为轮询,在这种情况下不是一个好主意,因为它强制Redis和客户端处理无用的命令(当列表为空时,所有的请求都不会完成实际工作,他们只会返回NULL)。所以Redis实现了一个叫做BRPOP和BLPOP的命令,如果列表是空的,它们将会被阻塞:只有当一个新的元素被添加到列表中,或者当用户指定的超时时,它们才会返回给调用者

##可以使用0作为超时来永久等待元素,也可以指定多个而不是一个列表,以便同时等待多个列表,并在第一个列表收到元素时得到通知。返回一个双元素数组,因为它还包含键的名称
127.0.0.1:6379> brpop mylist 0
1) "mylist"
2) "1"
(7.53s)
##等待mylist列表中的元素,但如果5秒后没有元素可用则返回
127.0.0.1:6379> brpop mylist 5
(nil)
(5.02s)
4.3 Sets

1.Redis Set 是字符串的无序集合。可以在O(1)中添加、删除和测试成员是否存在(无论集合中包含多少元素,时间都是常数)。Redis Set 具有不允许重复成员的理想属性。一个 set 的最大成员数是2^32 - 1(4294967295,每个集合有超过40亿成员)。

127.0.0.1:6379> sadd myset 1 2 3
(integer) 3
127.0.0.1:6379> smembers myset
1) "1"
2) "2"
3) "3"
##检查一个元素是否存在:
127.0.0.1:6379> sismember myset 3
(integer) 1
127.0.0.1:6379> sismember myset 30
(integer) 0

4.4 Hashes

1.Redis Hash 是字符串字段和字符串值之间的映射,因此它们是表示对象的完美数据类型。每个hash可以存储2^32 - 1个字段值对(field-value pairs)。

127.0.0.1:6379> HMSET user:1000 username antirez password P1pp0 age 34
OK
127.0.0.1:6379> HGETALL user:1000
1) "username"
2) "antirez"
3) "password"
4) "P1pp0"
5) "age"
6) "34"
127.0.0.1:6379> HSET user:1000 password 12345
(integer) 0
127.0.0.1:6379> HGETALL user:1000
1) "username"
2) "antirez"
3) "password"
4) "12345"
5) "age"
6) "34"
4.5 Sorted Sets

1.与Redis Set类似,Redis Sorted Set是字符串的非重复集合。不同之处在于,Sorted Set的每个成员都与一个分数相关联,该分数用于保持排序集从最小到最大的顺序。虽然成员是独一无二的,但分数可能会重复。

2.使用排序集,可以非常快速地添加、删除或更新元素(时间与元素数量的对数成正比)。由于元素是按顺序存储的,所以还可以通过分数或排名(位置)以非常快的方式获取范围。访问排序集的中间也是非常快的,因此您可以使用排序的集合作为非重复元素的智能列表,你可以快速访问你需要的一切:顺序元素、快速存在测试、快速访问中间元素

3.使用排序集,您可以:

  • 在一个大型在线游戏中建立一个排行榜,每次提交一个新分数时,你都可以使用ZADD更新它。您可以使用ZRANGE轻松检索顶级用户,还可以在给定用户名的情况下,使用ZRANK返回其在列表中的排名。同时使用ZRANK和ZRANGE,可以向用户显示与给定用户相似的分数。一切都很快。

4.按下列规则排列:

  • 如果A和B是两个得分不同的元素,那么如果A得分大于B得分,A>B。
  • 如果A和B的分数完全相同,那么如果A字符串在词典上大于B字符串,则A>B。A和B字符串不能相等,因为排序集只有唯一的元素。

5.排序集是通过一个包含跳跃表和哈希表的双端口数据结构实现的,所以每次我们添加一个元素Redis执行一个O(log(N))操作。这很好,但当我们请求排序的元素时,Redis根本不需要做任何工作,它已经全部排序。

127.0.0.1:6379> zadd hackers 1940 "Alan Kay" 1957 "Sophie Wilson" 1953 "Richard Stallman" 1949 "Anita Borg" 1965 "Yukihiro Matsumoto" 1914 "Hedy Lamarr" 1916 "Claude Shannon" 1969 "Linus Torvalds" 1912 "Alan Turing"
(integer) 9
127.0.0.1:6379> zrange hackers 0 -1
1) "Alan Turing"
2) "Hedy Lamarr"
3) "Claude Shannon"
4) "Alan Kay"
5) "Anita Borg"
6) "Richard Stallman"
7) "Sophie Wilson"
8) "Yukihiro Matsumoto"
9) "Linus Torvalds"
##WITHSCORES返回分数:
127.0.0.1:6379> zrange hackers 0 -1 withscores
 1) "Alan Turing"
 2) "1912"
 3) "Hedy Lamarr"
 4) "1914"
 5) "Claude Shannon"
 6) "1916"
 7) "Alan Kay"
 8) "1940"
 9) "Anita Borg"
10) "1949"
11) "Richard Stallman"
12) "1953"
13) "Sophie Wilson"
14) "1957"
15) "Yukihiro Matsumoto"
16) "1965"
17) "Linus Torvalds"
18) "1969"
##在范围内操作,让我们把1950年之前出生的所有人都算进去。
127.0.0.1:6379> zrangebyscore hackers -inf 1950
1) "Alan Turing"
2) "Hedy Lamarr"
3) "Claude Shannon"
4) "Alan Kay"
5) "Anita Borg"
##get-rank操作。可以问一个元素在有序元素集合中的位置。
127.0.0.1:6379> zrank hackers "Anita Borg"
(integer) 4
##也可以删除元素范围。让我们从排序的集合中删除所有出生于1940年到1960年之间的
127.0.0.1:6379> zremrangebyscore hackers 1940 1960
(integer) 4
4.6 Bitmaps and HyperLogLogs

Redis还支持bitmap和HyperLogLogs,它们实际上是基于String基类型的数据类型,但有自己的语义。

4.7 Streams

Redis流是一种数据结构,acts like an append-only log(其作用类似于仅附加的日志)。流对于按事件发生的顺序记录事件很有用。
详细内容请参看redis官方文档,这里不多介绍。

4.8 Geospatial indexes

Redis提供地理空间索引,用于查找给定地理半径内的位置。可以使用GEOADD命令将位置添加到地理空间索引中。然后使用GEORADIUS命令搜索给定半径内的位置。

五.redis的key

1.Redis key是二进制安全的,这意味着你可以使用任何二进制序列作为密钥,从"foo"之类的字符串到JPEG文件的内容。空字符串也是一个有效的键

2.key允许的最大大小为512mb

5.1 Altering and querying the key space(修改和查询键空间)

1.EXISTS命令返回1或0,以指示数据库中是否存在给定的键,而DEL命令则删除键和关联的值,无论该值是什么。还有TYPE命令返回存储在指定键的类型值

127.0.0.1:6379> set mykey hello
OK
127.0.0.1:6379> exists mykey
(integer) 1
127.0.0.1:6379> type mykey
string
127.0.0.1:6379> del mykey
(integer) 1
127.0.0.1:6379> exists mykel
(integer) 0
5.2 Key expiration(key过期)

1.key过期允许您为key设置超时时间,也称为“生存时间”或“TTL”。当生存时间结束时,该键将自动销毁。关于过期的信息被复制并保存在磁盘上,当你的Redis服务器保持停止时,时间实际上已经过去了(这意味着Redis保存了一个key的过期日期)。使用EXPIRE命令设置key的过期时间。

127.0.0.1:6379> set key some-value
OK
##5秒
127.0.0.1:6379> expire key 5
(integer) 1
127.0.0.1:6379> get key
"some-value"
127.0.0.1:6379> get key
(nil)
##方式二(10秒)
127.0.0.1:6379> set key 100 ex 10
OK
5.3 自动创建和删除键

三个规则:

  • 向聚合数据类型添加元素时,如果目标键不存在,则在添加元素之前创建一个空的聚合数据类型。
  • 从聚合数据类型中删除元素时,如果值仍然为空,则该键将自动销毁。Stream数据类型是此规则的唯一例外。
  • 使用空键调用只读命令,如LLEN(返回列表长度)或删除元素的写命令,总是会产生相同的结果,就好像该键包含命令期望找到的类型的空聚合类型一样。

六.redis事务

6.1 事务如何在Redis中工作

1.Redis事务允许在一个步骤中执行一组命令,它们围绕命令MULTI、EXEC、DISCARD和WATCH。

2.Redis事务提供了两个重要保证:

  • 事务中的所有命令都被序列化并按顺序执行。另一个客户端发送的请求永远不会在Redis事务的执行过程中被服务。这保证了这些命令是作为单个独立操作执行的。
  • EXEC命令触发事务中所有命令的执行,因此,如果客户机在调用EXEC命令之前与事务上下文中的服务器失去连接,则不会执行任何操作,相反,如果调用EXEC命令,则会执行所有操作。
6.2 用法

1.使用MULTI命令输入Redis事务。该命令总是以“OK”回复。此时,用户可以发出多个命令。Redis不会执行这些命令,而是将它们排队。调用EXEC后,所有命令都会执行。而调用DISCARD将清空事务队列并退出事务。EXEC返回一个应答数组,其中每个元素都是事务中单个命令的应答,顺序与命令发出的顺序相同。

127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> incr foo
QUEUED
127.0.0.1:6379(TX)> incr bar
QUEUED
127.0.0.1:6379(TX)> exec
1) (integer) 5
2) (integer) 1
6.3 事务中的错误

1.在事务处理过程中,可能会遇到两种命令错误:

  • 命令可能无法排队,因此在调用EXEC之前可能会出现错误。例如,该命令可能语法错误(参数数量错误,命令名称错误等),或者可能存在一些关键条件,如内存不足条件(如果服务器使用maxmemory指令配置了内存限制)。
  • 调用EXEC后,命令可能会失败,例如,因为我们对具有错误值的键执行了一个操作(比如对字符串值调用列表操作)。

2.从Redis 2.6.5开始,服务器将在命令累积期间检测到错误。然后,它将拒绝执行在EXEC期间返回错误的事务,从而放弃该事务。EXEC之后发生的错误不会以特殊方式处理:即使某些命令在事务执行过程中失败,也会执行所有其他命令

###第一种情况
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> lpush mylist a
QUEUED
127.0.0.1:6379(TX)> lpushxx mylist a
(error) ERR unknown command `lpushxx`, with args beginning with: `mylist`, `a`, 
127.0.0.1:6379(TX)> exec
(error) EXECABORT Transaction discarded because of previous errors.

###第二种情况
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> lpush mylist a
QUEUED
127.0.0.1:6379(TX)> get mylist
QUEUED
127.0.0.1:6379(TX)> lpush mylist b
QUEUED
127.0.0.1:6379(TX)> exec
1) (integer) 3
2) (error) WRONGTYPE Operation against a key holding the wrong kind of value
3) (integer) 4

3.Redis不支持事务回滚,因为支持回滚会对Redis的简单性和性能产生重大影响。

6.4 Optimistic locking using check-and-set (使用检查和设置的乐观锁定)

1.WATCH用于为Redis事务提供检查和设置(CAS)行为

2.被监视的键是为了检测针对它们的更改。如果在EXEC命令之前修改了至少一个被监视的key,则整个事务将中止,EXEC将返回一个Null回复,通知事务失败

3.举例,假设我们需要将一个键的值原子地增加1(假设Redis没有INCR)。第一个尝试可能是:

val = GET mykey
val = val + 1
SET mykey $val

只有当我们有一个客户端在给定的时间内执行操作时,这种方法才能可靠地工作。如果多个客户端试图在同一时间增加key值,则会出现竞争条件。例如,客户端A和B将读取旧值,例如10。两个客户端都会将该值增加到11,最后SET作为键的值。所以最终的值将是11,而不是12。

WATCH mykey
val = GET mykey
val = val + 1
MULTI
SET mykey $val
EXEC

使用上述代码,如果存在竞争条件,并且在调用WATCH和调用EXEC之间的时间内,另一个客户端修改了val的结果,则事务将失败。
我们只需要重复操作,希望这次不会有新的竞争。这种形式的锁定称为乐观锁定。

4.那么WATCH到底是什么呢?这是一个使EXEC有条件的命令:我们要求Redis仅在没有修改任何被监视的键的情况下执行事务。这包括客户端所做的修改,比如写命令,以及Redis本身所做的修改,比如过期或逐出。如果在监视key和接收到EXEC之间修改了key,则整个事务将被中止

  • 在6.0.9之前的Redis版本中,过期的密钥不会导致事务中止。
  • 事务中的命令不会触发watch条件,因为它们只在发送EXEC之前排队。
  • 可以多次调用WATCH。简单地说,所有WATCH调用都将具有监视从调用开始到调用EXEC为止的更改的效果。

5.当调用EXEC时,所有键都是UNWATCHed的,无论事务是否被中止。此外,当客户端连接关闭时,everything gets UNWATCHed

6.也可以使用UNWATCH命令(不带参数)来刷新所有被监视的键。有时这很有用,因为我们乐观地锁定了一些key,因为我们可能需要执行一个事务来更改这些key,但在读取key的当前内容后,我们不想继续。当这种情况发生时,我们只需调用UNWATCH,这样连接就可以自由地用于新的事务。

7.使用WATCH来实现ZPOP
一个很好的例子说明了WATCH如何用于创建Redis不支持的新原子操作,即实现ZPOP(ZPOPMIN、ZPOPMAX及其阻塞变体仅在版本5.0中添加),这是一个以原子方式从排序集弹出分数较低的元素的命令。这是最简单的实现:如果EXEC失败(即返回空回复),我们只需重复该操作。

WATCH zset
element = ZRANGE zset 0 0
MULTI
ZREM zset element
EXEC

七.Redis可编程性->使用Lua和Redis函数扩展Redis

1.Redis提供了一个编程接口,允许你在服务器上执行自定义脚本。在Redis 7及以上,你可以使用Redis函数来管理和运行你的脚本。在Redis 6.2及以下版本中,您可以使用Lua脚本和EVAL命令对服务器进行编程。

2.Redis中的“可编程性”一词意味着服务器能够执行任意用户定义的逻辑。我们将这些逻辑片段称为脚本。用户脚本在Redis中是通过一个嵌入的沙箱脚本引擎执行的。目前,Redis支持一个脚本引擎,Lua 5.1解释器。

7.1 运行脚本

1.Redis提供了两种运行脚本的方法。

  • 首先,从Redis 2.6.0开始,EVAL命令支持运行服务器端脚本。然而,使用它们意味着脚本逻辑是你的应用程序的一部分(而不是Redis服务器的扩展)。随着应用程序的增长,这种方法可能会变得更难开发和维护。
  • 其次,在v7.0中添加,Redis函数本质上是脚本。因此,函数将脚本编写从应用程序逻辑中分离出来,并支持脚本的独立开发、测试和部署。要使用函数,首先需要加载函数,然后所有连接的客户端都可以使用它们。在这种情况下,将函数加载到数据库成为一个管理部署任务(例如,加载Redis模块),这将脚本与应用程序分离。

2.当运行一个脚本或函数时,Redis保证它的原子执行。脚本的执行在整个时间内阻塞所有的服务器活动,类似于事务的语义。这些语义意味着脚本的所有效果要么还没有发生,要么已经发生。执行脚本的阻塞语义在任何时候都适用于所有连接的客户端。

7.2 最大执行时间

1.脚本受最大执行时间限制(默认设置为5秒)。这个默认超时是巨大的,因为脚本通常在不到一毫秒的时间内运行。

7.3 Redis函数

详细内容请参看Redis官方文档之Redis函数

7.4 Lua脚本
7.4.1 开始

1.我们将使用EVAL命令开始编写脚本。在本例中,EVAL接受两个参数。第一个参数是由脚本的Lua源代码组成的字符串。脚本不需要包含Lua函数的任何定义。它只是一个运行在Redis引擎上下文中的Lua程序。

127.0.0.1:6379> EVAL "return 'Hello, scripting!'" 0
"Hello, scripting!"
7.4.2 脚本参数化

1.让应用程序根据其需要动态地生成脚本源代码是可能的,尽管这是非常不明智的。您可以将它们参数化,并传递执行它们所需的任何参数。

127.0.0.1:6379> EVAL "return ARGV[1]" 0 Hello
"Hello"
127.0.0.1:6379> EVAL "return ARGV[1]" 0 Parameterization!
"Parameterization!

2.理解Redis在输入参数之间的区别是至关重要的(哪些参数是键名、哪些不是)。在上面的例子中,Hello和Parameterization!是脚本的常规输入参数。因为脚本不涉及任何键,所以我们使用数字参数0来指定没有键名参数。执行上下文通过键和ARGV全局运行时变量为脚本提供参数。KEYS表在脚本执行之前预先填充了提供给脚本的所有key name参数,而ARGV表的用途类似,只是用于常规参数。

7.4.3 通过脚本与Redis交互

1.可以通过redis.call()或redis.pcall()从Lua脚本调用Redis命令。这两者几乎完全相同。两者都执行一个Redis命令及其提供的参数,如果这些参数表示一个格式良好的命令。但是,这两个函数之间的区别在于处理运行时错误(例如语法错误)的方式。调用redis.call()函数引发的错误将直接返回给执行它的客户端。相反,调用redis.pcall()函数时遇到的错误将被返回到脚本的执行上下文,以进行可能的处理。

##脚本接受一个键名和一个值作为输入参数。当执行时,脚本调用SET命令以字符串值“bar”设置输入键foo
127.0.0.1:6379> EVAL "return redis.call('SET', KEYS[1], ARGV[1])" 1 foo bar
OK
7.4.4 脚本缓存

1.在此之前,我们一直使用EVAL命令来运行脚本。每当调用EVAL时,我们也会在请求中包含脚本的源代码。重复调用EVAL来执行同一组参数化脚本,既浪费了网络带宽,也在Redis中有一些开销。当然,节省网络和计算资源是关键,因此Redid为脚本提供了一种缓存机制。

2.使用EVAL执行的每个脚本都存储在服务器保存的专用缓存中。The cache’s contents are organized by the scripts’ SHA1 digest sums, so the SHA1 digest sum of a script uniquely identifies it in the cache。您可以通过运行EVAL并在之后调用INFO来验证此行为。您将注意到used_memory_scripts_eval和number_of_cached_scripts指标随着每执行一个新脚本而增长。如上所述,动态生成的脚本是一种反模式。在应用程序运行时生成脚本可能会耗尽主机用于缓存它们的内存资源。相反,脚本应该尽可能的通用,并通过它们的参数提供定制的执行。

3.通过调用script LOAD命令并提供其源代码,脚本被加载到服务器的缓存中。服务器不执行脚本,而是编译脚本并将其加载到服务器的缓存中。加载后,可以使用服务器返回的SHA1 digest执行缓存的脚本。下面是一个加载然后执行缓存脚本的例子:

127.0.0.1:6379> SCRIPT LOAD "return 'Immabe a cached script'"
"c664a3bf70bd1d45c4284ffebb65a6f2299bfc9f"
127.0.0.1:6379> EVALSHA c664a3bf70bd1d45c4284ffebb65a6f2299bfc9f 0
"Immabe a cached script"
7.4.5 脚本命令

Redis SCRIPT提供了几种控制脚本子系统的方法。

  • SCRIPT FLUSH:这个命令是强制Redis刷新脚本缓存的唯一方法。
  • SCRIPT EXISTS:给定一个或多个SHA1 digests作为参数,该命令返回一个由1和0组成的数组。1表示特定的SHA1被识别为脚本缓存中已经存在的脚本。0的意思是,带有这个SHA1的脚本之前没有被加载过(或者至少从上次调用scriptflush之后就没有加载过)。
  • SCRIPT LOAD script:将指定的脚本注册到Redis脚本缓存中。
  • SCRIPT KILL:除了关闭服务器之外,这个命令是中断长时间运行的脚本(又称慢脚本)的唯一方法。当脚本的执行时间超过设置的最大执行时间阈值时,该脚本将被视为慢。SCRIPT KILL命令只能用于在执行过程中没有修改数据集的脚本(因为停止只读脚本并不违反脚本引擎保证的原子性)。
  • SCRIPT DEBUG:控制使用内置的Redis Lua脚本调试器。
7.4.6 脚本复制

略。详细内容请参看官方文档

7.4.7 Lua API 以及在 Redis 中调试 Lua 脚本

略。详细内容请参看官方文档

八.Redis pipelining(Redis流水线)

如何通过批量Redis命令优化往返时间->Redis流水线是一种通过一次发出多个命令而不等待每个命令的响应来提高性能的技术。

8.1 Request/Response protocols and round-trip time (RTT)请求/响应协议和往返时间(RTT)

1.Redis是一个使用客户端-服务器模型的TCP服务器,也被称为请求/响应协议。这意味着通常一个请求是通过以下步骤完成的:客户端向服务器发送一个查询,并从套接字中读取服务器响应,通常是以阻塞的方式。服务器处理命令并将响应发送回客户端。例如,四个命令的序列是这样的:客户端和服务器通过网络连接。无论网络延迟是多少,数据包从客户端传输到服务器,再从服务器返回到客户端以承载应答都需要时间。这个时间被称为RTT(往返时间)。当客户端需要在一行中执行许多请求时(例如向同一列表中添加许多元素,或用许多键填充数据库),很容易看出这对性能的影响。例如,如果RTT时间为250毫秒(在互联网上的链接速度非常慢的情况下),即使服务器每秒能够处理100k个请求,我们也可以每秒最多处理四个请求。

Client: INCR X
Server: 1
Client: INCR X
Server: 2
Client: INCR X
Server: 3
Client: INCR X
Server: 4

2.可以实现请求/响应服务器,这样即使客户端还没有读取旧的响应,它也能够处理新请求。通过这种方式,可以向服务器发送多个命令,而无需等待响应,并最终在一个步骤中读取响应。这被称为流水线,使用流水线,我们的第一个例子的操作顺序将如下:当客户端使用流水线发送命令时,服务器将被迫使用内存对响应进行排队。

Client: INCR X
Client: INCR X
Client: INCR X
Client: INCR X
Server: 1
Server: 2
Server: 3
Server: 4
8.2 这不只是RTT的问题

1.Pipelining不仅仅是一种减少与往返时间相关的延迟成本的方法,它实际上大大提高了你可以在一个给定的Redis服务器每秒执行的操作数量。这是因为,如果不使用pipelining,从访问数据结构和生成应答的角度来看,为每个命令提供服务是非常便宜的,但从执行套接字I/O的角度来看,它的成本非常高。这涉及到调用read()和write()系统调用,这意味着从用户域到内核域。上下文切换是一个巨大的速度损失。

2.当使用pipelining时,许多命令通常通过一个read()系统调用来读取,多个响应通过一个write()系统调用来传递。正因为如此,每秒执行的查询总数最初随着管道的增加几乎线性增加,最终达到不使用管道获得基线的10倍

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值