目录 redis入门(三) 目录 前言 事务 原理 Lua脚本 安装 脚本命令 集群搭建工具 redis-trib.rb redis官方集群搭建 集群横向扩展 故障转移 redis管理 参考文档 redis入门(三) 目录 redis入门(一) redis入门(二) redis入门(三) 前言 在前两章介绍了Redis的一些常用的API与功能,在本章会对一些其他功能包括事务、脚本、Redis集群搭建工具以及集群动态扩容与故障转移方式进行讲解。 事务 在关系型数据库,我们可以通过事务(transaction)的方式执行数据库级别的原子性操作。在Redis中也提供简单的事务功能。 Redis通过MULTI、EXEC、WATCH等命令来实现功能。它提供了一种将多个命令请求打包,然后一次性、按顺序地执行多个命令的机制。 将一组需要一起执行的命令放到MULTI和EXEC两个命令之间。MULTI命令代表事务开始,EXEC命令代表事务结束,它们之间的命令是原子顺序执行的,在MULTI执行之后,的写命令会直接返回QUEUE,当输入EXEC后会将所有命令一起执行。 127.0.0.1:6379> multi OK 127.0.0.1:6379> set a 1 QUEUED 127.0.0.1:6379> set b 2 QUEUED 127.0.0.1:6379> exec 1) OK 2) OK 若不使用事务时,客户端发送的命令会立即被服务端执行。使用事务时,当客户端发送的命令是EXEC、DISCARD、WATCH、MULTI命令之一时,服务器立即执行该命令,若不是上述四个命令,则会将命令加入到一个事务队列中,然后向客户端返回QUEUE回复。 由于开启事务时命令不会直接被执行,而是直接入队,因此Redis也不会立即发现运行时错误(比如hash的键使用string的命令执行)。因此当事务中某条命令执行失败时,其他命令还是可以可以正常执行。因此使用EXEC和MULTI命令时Redis仅保证打包的若干条命令以原子性顺序执行,而不保证事务中的命令全部执行成功。 127.0.0.1:6379> multi OK 127.0.0.1:6379> set a 5 QUEUED 127.0.0.1:6379> hset b n 1 QUEUED 127.0.0.1:6379> set c 2 QUEUED 127.0.0.1:6379> exec 1) OK 2) (error) WRONGTYPE Operation against a key holding the wrong kind of value 3) OK 为了实现真正的事务,Redis引入了WATCH实现乐观锁。在MULTI命令执行之前,可以通过WATCH监控指定的键,若在EXEC提交事务之前,数据发生了变化,则事务执行失败。 客户端A 127.0.0.1:6379> WATCH a OK 127.0.0.1:6379> multi OK 127.0.0.1:6379> set b 1 QUEUED 127.0.0.1:6379> set a 6 QUEUED 127.0.0.1:6379> set c 3 QUEUED 127.0.0.1:6379> exec (nil) 127.0.0.1:6379> 客户端B 127.0.0.1:6379> set a 7 OK 时间点 时间序号 客户端A 客户端B 1 WATCH a 2 multi 3 set b 1 4 set a 6 5 set c 3 set a 7 6 exec 客户端B在客户端A执行WATCH之后exec命令提交前执行了一条SET命令,客户端A提交EXEC命令则会提交失败。 原理 每个Redis数据库都会有一个字典用于保存被监视的键,而字典的的值是一个链表。该列表记录了所有监视该键的客户端。因此Redis服务器就清楚的知道哪些键被哪些客户端监视了。 当被监视的键被修改时,会触发一个动作将被修改键的客户端的该键的“脏数据”标志打开,表示该键的事务已经被破坏。 当客户端提交事务的时候,服务器会判断这个客户端是否存在被监视的键打开了“脏数据”标志。若该标识被打开,则说明客户端所以监视的键已经被修改,服务器就会拒绝事务提交。 Lua脚本 Redis从2.6版本开始引入对Lua脚本的支持。 Lua语言是在1993年由巴西一个大学研究小组发明,其设计目标是作为嵌入式程序移植到其他应用程序,它是由C语言实现的。 在redis服务器中内嵌了一个Lua环境,在redis服务启动之前会对该Lua环境进行一些修改,确保Lua环境满足redis服务器的需要。 安装 在ubuntu可以通过以下命令下载 curl -R -O http://www.lua.org/ftp/lua-5.3.5.tar.gz tar zxf lua-5.3.5.tar.gz cd lua-5.3.5 make linux test 在我本地编译Lua时报错,可以参考编译lua-5.3.5时出错解决方法解决。 windows环境下安装可以参考Lua在Windows下的安装、配置、运行 由于redis中存在Lua环境,因此即使不在本地安装Lua环境redis也是可以正常执行Lua脚本的也没关系。 脚本命令 Redis提供了2个执行lua脚本的命令EVAL和EVALSHA。 EVAL 命令格式为eval 脚本内容 key个数 key列表 参数列表 127.0.0.1:6379> eval ' return KEYS[1] .. ARGV[1]' 1 hello world "helloworld" redis中key下标从1开始 lua通过..链接两个字符串 我们也可以通redis-cli --eval直接执行脚本文件 将return KEYS[1] .. ARGV[1]保存到文件中,命名为helloworld.lua 通过--eval 脚本路径 key列表 , 参数列表 注意key列表和参数列表中间的,左右两边至少都要有一个空格,否则会报错 jake@Jake-PC:~/tool$ redis/src/redis-cli -p 26379 --eval lua-5.3.5/helloworld.lua hello , world "helloworld" EVALSHA 当脚本比较大时,每次发送脚本会占用一定的网络带宽。redis提供了讲脚本缓存的方式减少脚本传输的开销。首先需要通过script load命令将lua脚本加载到redis服务端,获得到脚本的SHA1校验和。然后通过evalsha命令执行校验和的脚本。 127.0.0.1:6379> script load 'return KEYS[1] .. ARGV[1]' "dc8235f4444d746adf3374579406c129fb1f0f0a" 127.0.0.1:6379> evalsha dc8235f4444d746adf3374579406c129fb1f0f0a 1 hello world "helloworld" 每个被EVAL执行成功过的lua脚本,在lua环境都会有与该脚本对应的lua函数。函数名为f_加四十位的SHA1校验和。 function f_dc8235f4444d746adf3374579406c129fb1f0f0a() return KEYS[1] .. ARGV[1] end 我们还可以将脚本文件内容进行加载,通过evalsha获取其SHA1校验和。在linux环境下可以通过cat读取文件内容, jake@Jake-PC:~/tool$ redis/src/redis-cli -a test1 script load "$(cat lua-5.3.5/helloworld.lua)" "dc8235f4444d746adf3374579406c129fb1f0f0a" 需要使用$()或``将cat lua-5.3.5/helloworld.lua包起来 在windows环境下使用powershell可以通过Get-Content读取文本 PS C:\Users\Dm_ca> redis-cli -a test1 script load (Get-Content F:\Study\helloworld.lua) "dc8235f4444d746adf3374579406c129fb1f0f0a" lua和redis互操作 由于redis支持调用Lua脚本,而且Lua存在调用redis的API,这样我们就可以将一系列Lua脚本以原子性的执行。在lua中可以通过redis.call(command,key[param1, param2…])调用redis命令。 127.0.0.1:26379> eval 'return redis.call("set",KEYS[1],ARGV[1])' 1 "hello" , "redis" OK 对于redis中调用lua更多细节可以看EVAL script SCRIPT EXISTS 通过该命令输入SHA1校验和可以检查对应的脚本是否存在。 SCRIPT FLUSH 该命令用于清除服务器中所有和lua脚本相关的信息,并关闭当前lua环境重新创建一个新的lua环境 SCRIPT KILL 在每次执行lua脚本之前,redis服务器都会在lua环境中设置一个超时处理钩子,若脚本执行超过配置的lua-time-limit的时长时,可以通过该命令停止当前的lua脚本执行。 若当前lua脚本有写入操作,则无法在使用该脚本停止执行。只能通过SHUTDOWN nosave命令关闭。 脚本复制 当服务器开启了主从复制时,写命令的脚本也会复制到从服务器以确保从服务器的数据和主服务器一致。 但是EVALSHA命令比较特殊,因为有可能主服务器加载了脚本,从服务器还没有来的及同步。所以会存在主服务器字典中存在函数缓存但是从服务器不存在的情况。因此当Redis要求主服务器在传播EVALSHA命令的时候,必须确保EVALSHA命令要执行的脚本已经被所有从服务器载入过,如果不能确保这一点的话,主服务器会将EVALSHA命令转换成一个等价的EVAL命令,然后通过传播EVAL命令来代替EVALSHA命令。 集群搭建工具 redis-trib.rb 在上一节我们讲解了集群搭建的原理和过程。但是一步步都采取纯手工搭建会不太方便,而且也容易出错。在redis5.0以前可以通过redis-trib.rb进行集群搭建。它是Ruby实现Redis集群管理工具。 Ruby 是一种开源的面向对象程序设计的服务器端脚本语言,在 20 世纪 90 年代中期由日本的松本行弘(まつもとゆきひろ/Yukihiro Matsumoto)设计并开发。 Ruby 可运行于多种平台,如 Windows、MAC OS 和 UNIX 的各种版本。 下面我在windows平台上搭建集群。 准备ruby环境。 在windows环境可以到RubyInstaller下载安装。 下载安装完可以通过ruby -v确定是否安装成功,以及当前ruby版本号。 安装完成会弹出一个框询问安装MSYS2,我们可以不需要。 安装rubygem redis依赖: C:\Users\Dm_ca>gem install redis Fetching redis-4.1.3.gem Successfully installed redis-4.1.3 Parsing documentation for redis-4.1.3 Installing ri documentation for redis-4.1.3 Done installing documentation for redis after 2 seconds 1 gem installed 下载redis-trib.rb 从github上下载3.2或4.0版本redis的redis-trib.rb代码保存到文件redis-trib.rb中。 4.0的redis-trib.rb做了一定的优化,尽可能让主从处于不同的主机,同时当创建集群时若主从处于同一个ip也会进行提示。 5.0的redis-trib.rb已经不支持了,redis-cli已经集成了redis-trib.rb的功能,当使用redis-trib.rb时会提示使用redis-cli cluster执行。 创建7579~75846个端口的配置文件,启动6个redis服务,配置可以参考上一章的集群搭建配置 start redis-server.exe redis-7579.conf start redis-server.exe redis-7580.conf start redis-server.exe redis-7581.conf start redis-server.exe redis-7582.conf start redis-server.exe redis-7583.conf start redis-server.exe redis-7584.conf 为了方便暂时没有作为windows服务启动 通过命令ruby redis-trib.rb create --replicas host1:port1 ... hostN:portN命令创建集群,如ruby redis-trib.rb create --replicas 1 127.0.0.1:7579 127.0.0.1:7580 127.0.0.1:7581 127.0.0.1:7582 127.0.0.1:7583 127.0.0.1:7584 F:\Study\redis\redis集群>ruby redis-trib.rb create --replicas 1 127.0.0.1:7579 127.0.0.1:7580 127.0.0.1:7581 127.0.0.1:7582 127.0.0.1:7583 127.0.0.1:7584 >>> Creating cluster >>> Performing hash slots allocation on 6 nodes... Using 3 masters: 127.0.0.1:7579 127.0.0.1:7580 127.0.0.1:7581 Adding replica 127.0.0.1:7583 to 127.0.0.1:7579 Adding replica 127.0.0.1:7584 to 127.0.0.1:7580 Adding replica 127.0.0.1:7582 to 127.0.0.1:7581 >>> Trying to optimize slaves allocation for anti-affinity [WARNING] Some slaves are in the same host as their master M: 4e4f38cce9a8dd1d7871e327e69c2a2556c90ad7 127.0.0.1:7579 slots:0-5460 (5461 slots) master M: 426942a0f20ccbfa9d68c1b0809800c31d10c0ab 127.0.0.1:7580 slots:5461-10922 (5462 slots) master M: 1149113de5253841923789e79c362ad745509f5c 127.0.0.1:7581 slots:10923-16383 (5461 slots) master S: e7e172a7cba6e052d36d0a330225f48f3858fb72 127.0.0.1:7582 replicates 1149113de5253841923789e79c362ad745509f5c S: 7fb04e281546d6f0d0e0a3a06b5f80b101eae00f 127.0.0.1:7583 replicates 4e4f38cce9a8dd1d7871e327e69c2a2556c90ad7 S: b872de59634dd245c0452b46832994bf04a6c08b 127.0.0.1:7584 replicates 426942a0f20ccbfa9d68c1b0809800c31d10c0ab Can I set the above configuration? (type 'yes' to accept): yes >>> Nodes configuration updated >>> Assign a different config epoch to each node >>> Sending CLUSTER MEET messages to join the cluster Waiting for the cluster to join... >>> Performing Cluster Check (using node 127.0.0.1:7579) M: 4e4f38cce9a8dd1d7871e327e69c2a2556c90ad7 127.0.0.1:7579 slots:0-5460 (5461 slots) master 1 additional replica(s) M: 1149113de5253841923789e79c362ad745509f5c 127.0.0.1:7581 slots:10923-16383 (5461 slots) master 1 additional replica(s) S: 7fb04e281546d6f0d0e0a3a06b5f80b101eae00f 127.0.0.1:7583 slots: (0 slots) slave replicates 4e4f38cce9a8dd1d7871e327e69c2a2556c90ad7 S: b872de59634dd245c0452b46832994bf04a6c08b 127.0.0.1:7584 slots: (0 slots) slave replicates 426942a0f20ccbfa9d68c1b0809800c31d10c0ab M: 426942a0f20ccbfa9d68c1b0809800c31d10c0ab 127.0.0.1:7580 slots:5461-10922 (5462 slots) master 1 additional replica(s) S: e7e172a7cba6e052d36d0a330225f48f3858fb72 127.0.0.1:7582 slots: (0 slots) slave replicates 1149113de5253841923789e79c362ad745509f5c [OK] All nodes agree about slots configuration. >>> Check for open slots... >>> Check slots coverage... [OK] All 16384 slots covered. 由于我绑定ip没修改,主从使用的都是127.0.0.1,因此会有警告提示[WARNING] Some slaves are in the same host as their master。 生产配置需要绑定实际的ip,同时将protected-mode设置为no,否则只允许本机访问。 查看集群状态,可以看到已经存在三主三从,同时虚拟槽都自动分配了。 F:\Study\redis\redis集群>redis-cli -p 7479 cluster nodes 0f6cf326c23af6399e6c3bfc374b04efbac015d3 127.0.0.1:7381@17381 master - 0 1572935809989 3 connected 10923-16383 96b98672b59fd85f8743fde5b354abdb38ee3b47 127.0.0.1:7479@17479 myself,slave 0f6cf326c23af6399e6c3bfc374b04efbac015d3 0 1572935809000 4 connected 231ee8d2ea76e1c7fe2031c127b8a055be87a3f4 127.0.0.1:7379@17379 master - 0 1572935810997 1 connected 0-5460 e4ce1b6f872c6f72f776d534ce1dc783a0cf3655 127.0.0.1:7480@17480 slave 231ee8d2ea76e1c7fe2031c127b8a055be87a3f4 0 1572935808000 1 connected a9a4296b46db50459c5efb00d205fff671c2bfd0 127.0.0.1:7380@17380 master - 0 1572935810000 2 connected 5461-10922 dcae00d572280d795fcbc82b899f2f1a50f1397c 127.0.0.1:7481@17481 slave a9a4296b46db50459c5efb00d205fff671c2bfd0 0 1572935809000 6 connected redis官方集群搭建 在redis5.0开始官方集成了redis-trib.rb。可以直接通过cluster create命令创建集群。 将六个redis服务启动 jake@Jake-PC:~/tool/demo/redis-cluster/redis$ src/redis-server data/redis-7379.config jake@Jake-PC:~/tool/demo/redis-cluster/redis$ src/redis-server data/redis-7380.config jake@Jake-PC:~/tool/demo/redis-cluster/redis$ src/redis-server data/redis-7381.config jake@Jake-PC:~/tool/demo/redis-cluster/redis$ src/redis-server data/redis-7479.config jake@Jake-PC:~/tool/demo/redis-cluster/redis$ src/redis-server data/redis-7480.config jake@Jake-PC:~/tool/demo/redis-cluster/redis$ src/redis-server data/redis-7481.config 创建集群 redis-cli --cluster create 127.0.0.1:7379 127.0.0.1:7380 127.0.0.1:7381 127.0.0.1:7479 127.0.0.1:7480 127.0.0.1:7481 --cluster-replicas 1 在craete后面指定redis服务的ip和端口,最后通过--cluster-replicas表示每个主节点创建一个从节点。 5.0的集群--cluster-replicas参数可以放到create后面也可以放到最后,但是4.0的集群--replicas需要放到create后面,不能放最后。 jake@Jake-PC:~/tool/demo/redis-cluster/redis$ src/redis-cli --cluster create 127.0.0.1:7379 127.0.0.1:7380 127.0.0.1:7381 127.0.0.1:7479 127.0.0.1:7480 127.0.0.1:7481 --cluster-replicas 1 >>> Performing hash slots allocation on 6 nodes... Master[0] -> Slots 0 - 5460 Master[1] -> Slots 5461 - 10922 Master[2] -> Slots 10923 - 16383 Adding replica 127.0.0.1:7480 to 127.0.0.1:7379 Adding replica 127.0.0.1:7481 to 127.0.0.1:7380 Adding replica 127.0.0.1:7479 to 127.0.0.1:7381 >>> Trying to optimize slaves allocation for anti-affinity [WARNING] Some slaves are in the same host as their master M: 5591d1f47c15f919e6c492325bca0a385b581471 127.0.0.1:7379 slots:[0-5460] (5461 slots) master M: 157bee0bd158456fc7e27eb165299ed33df769a0 127.0.0.1:7380 slots:[5461-10922] (5462 slots) master M: eb63ce6942fd606a81113fe07622908931356057 127.0.0.1:7381 slots:[10923-16383] (5461 slots) master S: 3eb412b810c4bd9a3c0f4ab1f4af8ee03d06d818 127.0.0.1:7479 replicates 5591d1f47c15f919e6c492325bca0a385b581471 S: 30c2792fe726a9174c07f7d2db5704f5e9ef3b66 127.0.0.1:7480 replicates 157bee0bd158456fc7e27eb165299ed33df769a0 S: 56250f10afe111ce206169588300b39a6447ed91 127.0.0.1:7481 replicates eb63ce6942fd606a81113fe07622908931356057 Can I set the above configuration? (type 'yes' to accept): yes >>> Nodes configuration updated >>> Assign a different config epoch to each node >>> Sending CLUSTER MEET messages to join the cluster Waiting for the cluster to join ....... >>> Performing Cluster Check (using node 127.0.0.1:7379) M: 5591d1f47c15f919e6c492325bca0a385b581471 127.0.0.1:7379 slots:[0-5460] (5461 slots) master 1 additional replica(s) S: 30c2792fe726a9174c07f7d2db5704f5e9ef3b66 127.0.0.1:7480 slots: (0 slots) slave replicates 157bee0bd158456fc7e27eb165299ed33df769a0 S: 56250f10afe111ce206169588300b39a6447ed91 127.0.0.1:7481 slots: (0 slots) slave replicates eb63ce6942fd606a81113fe07622908931356057 S: 3eb412b810c4bd9a3c0f4ab1f4af8ee03d06d818 127.0.0.1:7479 slots: (0 slots) slave replicates 5591d1f47c15f919e6c492325bca0a385b581471 M: 157bee0bd158456fc7e27eb165299ed33df769a0 127.0.0.1:7380 slots:[5461-10922] (5462 slots) master 1 additional replica(s) M: eb63ce6942fd606a81113fe07622908931356057 127.0.0.1:7381 slots:[10923-16383] (5461 slots) master 1 additional replica(s) [OK] All nodes agree about slots configuration. >>> Check for open slots... >>> Check slots coverage... [OK] All 16384 slots covered. 通过该命令可以自动集群握手并自动分配槽。 jake@Jake-PC:~/tool/demo/redis-cluster/redis$ src/redis-cli -p 7379 cluster nodes 30c2792fe726a9174c07f7d2db5704f5e9ef3b66 127.0.0.1:7480@17480 slave 157bee0bd158456fc7e27eb165299ed33df769a0 0 1572926323116 5 connected 5591d1f47c15f919e6c492325bca0a385b581471 127.0.0.1:7379@17379 myself,master - 0 1572926322000 1 connected 0-5460 56250f10afe111ce206169588300b39a6447ed91 127.0.0.1:7481@17481 slave eb63ce6942fd606a81113fe07622908931356057 0 1572926320090 6 connected 3eb412b810c4bd9a3c0f4ab1f4af8ee03d06d818 127.0.0.1:7479@17479 slave 5591d1f47c15f919e6c492325bca0a385b581471 0 1572926322107 4 connected 157bee0bd158456fc7e27eb165299ed33df769a0 127.0.0.1:7380@17380 master - 0 1572926321098 2 connected 5461-10922 eb63ce6942fd606a81113fe07622908931356057 127.0.0.1:7381@17381 master - 0 1572926320000 3 connected 10923-16383 集群横向扩展 由于使用redis-cli --cluster和redis-trib.rb命令格式大同小异,下面我都在linux下使用redis-cli --cluster执行命令。 添加两个配置节点分别为7382和7482用于集群扩展。 启动新的节点 jake@Jake-PC:~/tool/demo/redis-cluster/redis$ src/redis-server data/redis-7382.config jake@Jake-PC:~/tool/demo/redis-cluster/redis$ src