本文共 19280 字,大约阅读时间需要 64 分钟。
redis配置文件
redis.conf
安装
#下载地址:http://redis.io/download#安装步骤:#安装gccyum install gcc# 把下载好的redis‐5.0.3.tar.gz放在/usr/local文件夹下,并解压wget http://download.redis.io/releases/redis‐5.0.3.tar.gztar xzf redis‐5.0.3.tar.gzcd redis‐5.0.3
持久化
# 生成日志文件的位置dir ./ # 生成快照的名称dbfilename “dump.rdb”# 每900秒内如果有1条指令执行生成RDB快照save 900 1 # 开启AOF持久化配置 no 关闭 yes 开启appendonly yes # 生成AOF的名称appendfilename "appendonly.aof"# AOF持久化方式 有三种#always 每次有新的命令追加到AOF文件时就执行一次fsync,非常慢,也非常安全#everysec 每秒fsync一次,足够快,并且在故障时只会丢失1秒种的数据。#no 从不fsync ,将数据交给操作系统来处理。更快,也更不安全的选择。appendfsync everysec #AOF文件至少要达到64M才会自动重写,文件太小恢复数据本来就根快重新的意义不大auto-aof-rewrite-min-size 64mb# AOF文件自上一次重写后文件大小增长了100% 则再次触发重写auto-aof-rewrite-percentage 100 # redis 4.0之后可配置混合持久化(必须先开启AOF)aof-use-rdb-preamble yes
集群通用
#关闭防火墙# systemctl stop firewalld # 临时关闭防火墙# systemctl disable firewalld # 禁止开机启动#启动端口号port 6379# 启动方式 no 前台启动 yes 后台启动daemonize no #pid进程号写入pidfile配置文件pidfile /var/run/redis_6379.pidlogfile 6379.log# 访问控制# 是否开启保护模式 yes 是 no 不是 开启保护模式必须使用密码或绑定ip的方式才可以访问protected-mode no#如果要设置密码需要增加如下配置#设置redis访问密码requirepass zhuge# 集群访问密码masterauth zhuge #bind绑定的是自己机器网卡的ip,如果有多块网卡可以配多个ip,代表允许客户端通过机器的哪些网卡ip去访问,内网一般可以不配置bind,注释掉即可bind 127.0.0.1
主从复制
# 从本机6379的redis实例复制数据,Redis5.0之前使用slaveofreplicaof 192.168.13.60 6379 # 配置从节点只读replica-read-only yes
哨兵架构
# 是否用守护线程的方式启动 启动后储非手动kill该进程否则一直执行daemonize yse# sentinel monitor# quorum是一个数字,指明当有多少个sentinel认为一个master失效时(值一般为:sentinel总数/2 +1),master才算真正失效sentinel monitor mymaster 127.0.0.1 6379 2
redis集群搭建
#redis集群需要至少三个master节点,我们这里搭建三个master节点,并且给每个master再搭建一个slave节点,总共6个redis节点#启动集群模式cluster-enabled yes#集群节点信息文件,这里800x最好和port对应上cluster-config-file nodes-8001.conf# 链接超时时间cluster-node-timeout 10000# 写数据成功最少同步的slave数量,这个数量可以模仿大于半数机制配置,比如集群总共三个节点可以配置1,加上leader就是2,超过了半数 min-slaves-to-write 1 # 为no时,表示当负责一个插槽的主库下线且没有相应的从库进行故障恢复时,集群仍然可用,如果为yes则集群不可用。cluster-require-full-coverage no
命令
#后端启动redis命令 ./src/redis-server ./redis.conf #停止redis服务 ./src/redis-cli SHUTDOWN /etc/init.d/redis start redis-cli shutdown #启动redis客户端 # -h IP -a 密码 -p 端口 -c表示集群访问 --pipe 管道 ./src/redis-cli ./src/redis-cli -h 127.0.0.1 -a mm -p 3679 --pipe ./redis-cli -c -h 192.168.0.104 -p 6381 -a wkf # 主动持久化 save # 后台主动持久化 bgsave #AOF重写 bgrewriteaof #启动哨兵 src/redis‐sentinel sentinel‐26379.conf # 创建 redisClusterredis-cli -a wkf --cluster create --cluster-replicas 1 192.168.0.102:6381 192.168.0.102:6382 192.168.0.102:6383 192.168.0.102:6384 192.168.0.102:6385 192.168.0.102:6386 # 查看集群信息cluster info# 查看节点列表cluster nodes# 添加节点到集群中 192.168.0.102:6381 是集群中任意节点../src/redis-cli -a wkf --cluster add-node 192.168.0.102:6387 192.168.0.102:6381# 将新增的节点转换为可用主节点,分配槽位# 会提示三次 第一次分配多少槽位 第二次 分配的节点id 第三次从那个节点分配all或某个节点redis-cli -a wkf --cluster reshard 192.168.0.102:6387# 分配从节点,进入从节点 执行以下命令 7ea30a99b2e147e8758e15937c7f39a710264344为主节点id cluster replicate 7ea30a99b2e147e8758e15937c7f39a710264344#删除从节点redis-cli -a zhuge --cluster del-node 192.168.0.61:8008 a1cfe35722d151cf70585cee21275565393c0956# 删除主节点# 第一次问转移多少槽位# 第二次问转到那个节点# 第三次问从哪里转 第一次输入要删除的节点id 第二次输入done 之后通过删除从节点的方式删除主节点./redis-cli -a wkf --cluster reshard 192.168.0.102:6381
info
Info:查看redis服务运行信息,分为 9 大块,每个块都有非常多的参数,这 9 个块分别是:Server 服务器运行的环境参数Clients 客户端相关信息Memory 服务器运行内存统计数据Persistence 持久化信息Stats 通用统计数据Replication 主从复制相关信息CPU CPU 使用情况Cluster 集群信息KeySpace 键值对统计数量信息
概念
RDB快照
在默认情况下, Redis 将内存数据库快照保存在名字为 dump.rdb 的二进制文件中。
你可以对 Redis 进行设置, 让它在“ N 秒内数据集至少有 M 个改动”这一条件被满足时, 自动保存一次 数据集。 比如说, 以下设置会让 Redis 在满足“ 60 秒内有至少有 1000 个键被改动”这一条件时, 自动保存一次 数据集:save 60 1000 //关闭RDB只需要将所有的save保存策略注释掉即可
还可以手动执行命令生成RDB快照,进入redis客户端执行命令save或bgsave可以生成dump.rdb文件,
每次命令执行都会将所有redis内存快照到一个新的rdb文件里,并覆盖原有rdb快照文件。AOF
快照功能并不是非常耐久(durable): 如果 Redis 因为某些原因而造成故障停机, 那么服务器将丢失
最近写入、且仍未保存到快照中的那些数据。从 1.1 版本开始, Redis 增加了一种完全耐久的持久化方 式: AOF 持久化,将修改的每一条指令记录进文件appendonly.aof中(先写入os cache,每隔一段时间 fsync到磁盘)写时复制
bgsave的写时复制(COW)机制
Redis 借助操作系统提供的写时复制技术(Copy-On-Write, COW),在生成快照的同时,依然可以正常
处理写命令。简单来说,bgsave 子进程是由主线程 fork 生成的,可以共享主线程的所有内存数据。 bgsave 子进程运行后,开始读取主线程的内存数据,并把它们写入 RDB 文件。此时,如果主线程对这些 数据也都是读操作,那么,主线程和 bgsave 子进程相互不影响。但是,如果主线程要修改一块数据,那 么,这块数据就会被复制一份,生成该数据的副本。然后,bgsave 子进程会把这个副本数据写入 RDB 文 件,而在这个过程中,主线程仍然可以直接修改原来的数据。save与bgsave
save与bgsave对比:
命令 | save | bgsave |
---|---|---|
IO类型 | 同步 | 异步 |
是否阻塞redis其他命令 | 是 | 否(在生成子进程执行调用fork函数时会有短暂的阻塞) |
复杂度 | O(n) | O(n) |
优点 | 不会消耗额外的内存 | 不阻塞客户端命令 |
缺点 | 阻塞客户端命令 | 需要fork子进程,消耗内存 |
RDB和AOF
RDB和AOF,我应该用那一个?
命令 | RDB | AOF |
---|---|---|
启动优先级 | 低 | 高 |
体积 | 小 | 大 |
恢复速度 | 快 | 慢 |
数据安全性 | 容易丢数据 | 根据策略决定 |
生产环境都可以启用,redis启动时如果既有rdb文件又有aof文件会优先选择aof文件进行恢复数据,因为aof一般来说数据更全一点
redis 冷备
(1)写crontab定时调度脚本去做数据备份
(2)每小时都copy一份rdb的备份,到一个目录中去,仅仅保留最近48小时的备份 (3)每天都保留一份当日的rdb的备份,到一个目录中去,仅仅保留最近1个月的备份 (4)每次copy备份的时候,都把太旧的备份给删了 (5)每天晚上将当前服务器上所有的数据备份,发送一份到远程的云服务上去每小时copy一次备份,删除48小时前的数据的代码:
crontab -e 0 * * * * sh /usr/local/redis/copy/redis_rdb_copy_hourly.shredis_rdb_copy_hourly.sh #!/bin/sh cur_date=`date +%Y%m%d%k`rm -rf /usr/local/redis/snapshotting/$cur_datemkdir /usr/local/redis/snapshotting/$cur_datecp /var/redis/6379/dump.rdb /usr/local/redis/snapshotting/$cur_date del_date=`date -d -48hour +%Y%m%d%k`rm -rf /usr/local/redis/snapshotting/$del_date
每天copy一次备份的代码:
crontab -e 0 0 * * * sh /usr/local/redis/copy/redis_rdb_copy_daily.shredis_rdb_copy_daily.sh #!/bin/sh cur_date=`date +%Y%m%d`rm -rf /usr/local/redis/snapshotting/$cur_datemkdir /usr/local/redis/snapshotting/$cur_datecp /var/redis/6379/dump.rdb /usr/local/redis/snapshotting/$cur_date del_date=`date -d -1month +%Y%m%d`rm -rf /usr/local/redis/snapshotting/$del_date
Redis主从工作原理
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oQYrkPa7-1656499168073)(主从.png)]
如果你为master配置了一个slave,不管这个slave是否是第一次连接上Master,它都会发送一个PSYNC
命令给master请求复制数据。master收到PSYNC命令后,会在后台进行数据持久化通过bgsave生成最新的rdb快照文件,持久化期
间,master会继续接收客户端的请求,它会把这些可能修改数据集的请求缓存在内存中。当持久化进行完 毕以后,master会把这份rdb文件数据集发送给slave,slave会把接收到的数据进行持久化生成rdb,然后 再加载到内存中。然后,master再将之前缓存在内存中的命令发送给slave。当master与slave之间的连接由于某些原因而断开时,slave能够自动重连Master,如果master收到了多
个slave并发连接请求,它只会进行一次持久化,而不是一个连接一次,然后再把这一份持久化的数据发送 给多个并发连接的slave。主从复制(全量复制)流程图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NfJ2IV88-1656499168075)(主从同步(全量)].png)
数据部分复制
当master和slave断开重连后,一般都会对整份数据进行复制。但从redis2.8版本开始,redis改用可以支
持部分数据复制的命令PSYNC去master同步数据,slave与master能够在网络连接断开重连后只进行部分 数据复制(断点续传)。 master会在其内存中创建一个复制数据用的缓存队列,缓存最近一段时间的数据,master和它所有的 slave都维护了复制的数据下标offset和master的进程id,因此,当网络连接断开后,slave会请求master 继续进行未完成的复制,从所记录的数据下标开始。如果master进程id变化了,或者从节点数据下标 offset太旧,已经不在master的缓存队列里了,那么将会进行一次全量数据的复制。主从复制(部分复制,断点续传)流程图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jMYD4aL0-1656499168076)(主从同步(全量)].png)
如果有很多从节点,为了缓解主从复制风暴(多个从节点同时复制主节点导致主节点压力过大),可以做如
下架构,让部分从节点与从节点(与主节点同步)同步数据[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XN98aBGx-1656499168077)(主从风暴.png)]
Redis Lua脚本
Redis在2.6推出了脚本功能,允许开发者使用Lua语言编写脚本传到Redis中执行。使用脚本的好处如下:
1、减少网络开销:本来5次网络请求的操作,可以用一个请求完成,原先5次请求的逻辑放在redis服务器 上完成。使用脚本,减少了网络往返时延。这点跟管道类似。2、原子操作:Redis会将整个脚本作为一个整体执行,中间不会被其他命令插入。管道不是原子的,不过
redis的批量操作命令(类似mset)是原子的。3、替代redis的事务功能:redis自带的事务功能很鸡肋,报错不支持回滚,而redis的lua脚本几乎实现了
常规的事务功能,支持报错回滚操作,官方推荐如果要使用redis的事务功能可以用redis lua替代。官网文档上有这样一段话:
A Redis script is transactional by definition, so everything you can do with a Redis transaction, you can also do with a script, and usually the script will be both simpler and faster.
从Redis2.6.0版本开始,通过内置的Lua解释器,可以使用EVAL命令对Lua脚本进行求值。EVAL命令的格
式如下:EVAL script numkeys key [key ...] arg [arg ...]
script参数是一段Lua脚本程序,它会被运行在Redis服务器上下文中,这段脚本不必(也不应该)定义为一
个Lua函数。numkeys参数用于指定键名参数的个数。键名参数 key [key …] 从EVAL的第三个参数开始算 起,表示在脚本中所用到的那些Redis键(key),这些键名参数可以在 Lua中通过全局变量KEYS数组,用1 为基址的形式访问( KEYS[1] , KEYS[2] ,以此类推)。 在命令的最后,那些不是键名参数的附加参数 arg [arg …] ,可以在Lua中通过全局变量ARGV数组访问, 访问的形式和KEYS变量类似( ARGV[1] 、 ARGV[2] ,诸如此类)。例如127.0.0.1:6379> eval "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 key1 key2 first second2 1) "key1" 2) "key2" 3) "first" 4) "second"
其中 “return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}” 是被求值的Lua脚本,数字2指定了键名参数的数
量, key1和key2是键名参数,分别使用 KEYS[1] 和 KEYS[2] 访问,而最后的 first 和 second 则是附加 参数,可以通过 ARGV[1] 和 ARGV[2] 访问它们。 在 Lua 脚本中,可以使用redis.call()函数来执行Redis命令 注意,不要在Lua脚本中出现死循环和耗时的运算,否则redis会阻塞,将不接受其他的命令, 所以使用 时要注意不能出现死循环、耗时的运算。redis是单进程、单线程执行脚本。管道不会阻塞redis。例:
# redis.call 设置值和获取值local count = redis.call('get', KEYS[1])redis.call('set', KEYS[1], a-b) # 脚本 eval "redis.call('set',KEYS[1],ARGV[1]) return 0 " 1 qwer 123
主从和JedisCluster
问题 | 主从 | JedisCluster |
---|---|---|
访问瞬断 | 所有都不能访问 | 单个节点无法访问,其他节点正常使用 |
存储大量数据 | 单个节点内设置值过大会导致持久化 | 多节点存储,比单节点持久化更快 |
并发 | 只有一个主节点提供服务无法支持很高的并发 | 多个节点提供服务 |
JedisCluster
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Rd1hBsfo-1656499168077)(高可用集群模式.png)]
会有16384(HASH槽)的逻辑分片,每个存入redis的key都需要通过crc16算法拿到一个hash值来确定分配到那台服务器上。
JedisCluster在分配主从的时候会最先考虑主节点和从节点不在一台机器上
JedisCluster会将集群的配置信息存到reids.conf中,在重启时会自动启动JedisCluster
跳转重定位
当客户端向一个错误的节点发出了指令,该节点会发现指令的 key 所在的槽位并不归自己管理,这时它会向客户端发送一个特殊的跳转指令携带目标操作的节点地址,告诉客户端去连这个节点去获取数据。客户端收到指令后除了跳转到正确的节点上去操作,还会同步更新纠正本地的槽位映射表缓存,后续所有 key 将使用新的槽位映射表。
Redis集群节点间的通信机制
redis cluster节点间采取gossip协议进行通信
- 维护集群的元数据(集群节点信息,主从角色,节点数量,各节点共享的数据等)有两种方式:集中式和gossip
集中式:
优点在于元数据的更新和读取,时效性非常好,一旦元数据出现变更立即就会更新到集中式的存储中,其他节点读取的时候立即就可以立即感知到;不足在于所有的元数据的更新压力全部集中在一个地方,可能导致元数据的存储压力。 很多中间件都会借助zookeeper集中式存储元数据。
gossip:
gossip协议包含多种消息,包括ping,pong,meet,fail等等。
meet:某个节点发送meet给新加入的节点,让新节点加入集群中,然后新节点就会开始与其他节点进行通信;
ping:每个节点都会频繁给其他节点发送ping,其中包含自己的状态还有自己维护的集群元数据,互相通过ping交换元数据(类似自己感知到的集群节点增加和移除,hash slot信息等);
pong: 对ping和meet消息的返回,包含自己的状态和其他信息,也可以用于信息广播和更新;
fail: 某个节点判断另一个节点fail之后,就发送fail给其他节点,通知其他节点,指定的节点宕机了。
gossip协议的优点在于元数据的更新比较分散,不是集中在一个地方,更新请求会陆陆续续,打到所有节点上去更新,有一定的延时,降低了压力;缺点在于元数据更新有延时可能导致集群的一些操作会有一些滞后。
gossip通信的10000端口
每个节点都有一个专门用于节点间gossip通信的端口,就是自己提供服务的端口号+10000,比如7001,那么用于节点间通信的就是17001端口。 每个节点每隔一段时间都会往另外几个节点发送ping消息,同时其他几点接收到ping消息之后返回pong消息。
网络抖动
真实世界的机房网络往往并不是风平浪静的,它们经常会发生各种各样的小问题。比如网络抖动就是非常常见的一种现象,突然之间部分连接变得不可访问,然后很快又恢复正常。
为解决这种问题,Redis Cluster 提供了一种选项cluster-node-timeout,表示当某个节点持续 timeout 的时间失联时,才可以认定该节点出现故障,需要进行主从切换。如果没有这个选项,网络抖动会导致主从频繁切换 (数据的重新复制)。
Redis集群选举原理分析
当slave发现自己的master变为FAIL状态时,便尝试进行Failover,以期成为新的master。由于挂掉的master可能会有多个slave,从而存在多个slave竞争成为master节点的过程, 其过程如下:
1.slave发现自己的master变为FAIL
2.将自己记录的集群currentEpoch加1,并广播FAILOVER_AUTH_REQUEST 信息
3.其他节点收到该信息,只有master响应,判断请求者的合法性,并发送FAILOVER_AUTH_ACK,对每一个epoch只发送一次ack
4.尝试failover的slave收集master返回的FAILOVER_AUTH_ACK
5.slave收到超过半数master的ack后变成新Master(这里解释了集群为什么至少需要三个主节点,如果只有两个,当其中一个挂了,只剩一个主节点是不能选举成功的)
6.slave广播Pong消息通知其他集群节点。
从节点并不是在主节点一进入 FAIL 状态就马上尝试发起选举,而是有一定延迟,一定的延迟确保我们等待FAIL状态在集群中传播,slave如果立即尝试选举,其它masters或许尚未意识到FAIL状态,可能会拒绝投票
•延迟计算公式:
DELAY = 500ms + random(0 ~ 500ms) + SLAVE_RANK * 1000ms
•SLAVE_RANK表示此slave已经从master复制数据的总量的rank。Rank越小代表已复制的数据越新。这种方式下,持有最新数据的slave将会首先发起选举(理论上)。
集群脑裂数据丢失问题
redis集群没有过半机制会有脑裂问题,网络分区导致脑裂后多个主节点对外提供写服务,一旦网络分区恢复,会将其中一个主节点变为从节点,这时会有大量数据丢失。
规避方法可以在redis配置里加上参数(这种方法不可能百分百避免数据丢失,参考集群leader选举机制):
min-slaves-to-write 1 //写数据成功最少同步的slave数量,这个数量可以模仿大于半数机制配置,比如集群总共三个节点可以配置1,加上leader就是2,超过了半数
注意:这个配置在一定程度上会影响集群的可用性,比如slave要是少于1个,这个集群就算leader正常也不能提供服务了,需要具体场景权衡选择。
是否完整才能
集群是否完整才能对外提供服务
当redis.conf的配置cluster-require-full-coverage为no时,表示当负责一个插槽的主库下线且没有相应的从库进行故障恢复时,集群仍然可用,如果为yes则集群不可用。
至少需要三个master
Redis集群为什么至少需要三个master节点,并且推荐节点数为奇数?
因为新master的选举需要大于半数的集群master节点同意才能选举成功,如果只有两个master节点,当其中一个挂了,是达不到选举新master的条件的。
奇数个master节点可以在满足选举该条件的基础上节省一个节点,比如三个master节点和四个master节点的集群相比,大家如果都挂了一个master节点都能选举新master节点,如果都挂了两个master节点都没法选举新master节点了,所以奇数的master节点更多的是从节省机器资源角度出发说的。
批量操作
Redis集群对批量操作命令的支持
对于类似mset,mget这样的多个key的原生批量操作命令,redis集群只支持所有key落在同一slot的情况,如果有多个key一定要用mset命令在redis集群上操作,则可以在key的前面加上{XX},这样参数数据分片hash计算的只会是大括号里的值,这样能确保不同的key能落到同一slot里去,示例如下:
mset {user1}:1:name zhuge {user1}:1:age 18
假设name和age计算的hash slot值不一样,但是这条命令在集群下执行,redis只会用大括号里的 user1 做hash slot计算,所以算出来的slot值肯定相同,最后都能落在同一slot。
Redis哨兵高可用架构
sentinel哨兵是特殊的redis服务,不提供读写服务,主要用来监控redis实例节点。
哨兵架构下client端第一次从哨兵找出redis的主节点,后续就直接访问redis的主节点,不会每次都通过 sentinel代理访问redis的主节点,当redis的主节点发生变化,哨兵会第一时间感知到,并且将新的redis 主节点通知给client端(这里面redis的client端一般都实现了订阅功能,订阅sentinel发布的节点变动消息)哨兵leader选举流程
当一个master服务器被某sentinel视为下线状态后,该sentinel会与其他sentinel协商选出sentinel的leader进行故障转移工作。每个发现master服务器进入下线的sentinel都可以要求其他sentinel选自己为sentinel的leader,选举是先到先得。同时每个sentinel每次选举都会自增配置纪元(选举周期),每个纪元中只会选择一个sentinel的leader。如果所有超过一半的sentinel选举某sentinel作为leader。之后该sentinel进行故障转移操作,从存活的slave中选举出新的master,这个选举过程跟集群的master选举很类似。
哨兵集群只有一个哨兵节点,redis的主从也能正常运行以及选举master,如果master挂了,那唯一的那个哨兵节点就是哨兵leader了,可以正常选举新master。
不过为了高可用一般都推荐至少部署三个哨兵节点。为什么推荐奇数个哨兵节点原理跟集群奇数个master节点类似。
Jedis
创建链接
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();jedisPoolConfig.setMaxTotal(20);jedisPoolConfig.setMaxIdle(10);jedisPoolConfig.setMinIdle(5); // timeout,这里既是连接超时又是读写超时,从Jedis 2.8开始有区分connectionTimeout和soTimeout的构造函数JedisPool jedisPool = new JedisPool(jedisPoolConfig, "192.168.0.104", 6379, 3000, null);
普通操作示例
Jedis jedis = jedisPool.getResource();System.out.println(jedis.set("wkf", "1123"));System.out.println(jedis.get("wkf"));
管道示例
//管道的命令执行方式:cat redis.txt | redis-cli -h 127.0.0.1 -a password - p 6379 --pipePipeline pl = jedis.pipelined();for (int i = 0; i < 10; i++) { pl.incr("pipelineKey"); pl.set("zhuge" + i, "zhuge"); //模拟管道报错 /* pl.setbit("zhuge", -1, true);*/}List
lua脚本示例
//模拟一个商品减库存的原子操作//lua脚本命令执行方式:redis-cli --eval /tmp/test.lua , 10jedis.set("product_stock_10016", "15"); //初始化商品10016的库存String script = " local count = redis.call('get', KEYS[1]) " + " local a = tonumber(count) " + " local b = tonumber(ARGV[1]) " + " if a >= b then " + " redis.call('set', KEYS[1], a-b) " + //模拟语法报错回滚操作 " bb == 0 " + " return 1 " + " end " + " return 0 ";Object obj = jedis.eval(script, Arrays.asList("product_stock_10016"), Arrays.asList("10"));System.out.println(obj);
redis哨兵集群
JedisPoolConfig config = new JedisPoolConfig();config.setMaxTotal(20);config.setMaxIdle(10);config.setMinIdle(5);String masterName = "mymaster";Setsentinels = new HashSet ();sentinels.add(new HostAndPort("192.168.0.104",26379).toString());sentinels.add(new HostAndPort("192.168.0.104",26380).toString());sentinels.add(new HostAndPort("192.168.0.104",26381).toString());//JedisSentinelPool其实本质跟JedisPool类似,都是与redis主节点建立的连接池//JedisSentinelPool并不是说与sentinel建立的连接池,而是通过sentinel发现redis主节点并与其建立连接JedisSentinelPool jedisSentinelPool = new JedisSentinelPool(masterName, sentinels, config, 3000, null);Jedis jedis = null;try { jedis = jedisSentinelPool.getResource(); System.out.println(jedis.set("sentinel666", "zhuge")); System.out.println(jedis.get("sentinel666"));} catch (Exception e) { e.printStackTrace();} finally { //注意这里不是关闭连接,在JedisPool模式下,Jedis会被归还给资源池。 if (jedis != null) jedis.close();}
redis集群
JedisPoolConfig config = new JedisPoolConfig(); config.setMaxTotal(20); config.setMaxIdle(10); config.setMinIdle(5); SetjedisClusterNode = new HashSet (); jedisClusterNode.add(new HostAndPort("192.168.0.104", 6381)); jedisClusterNode.add(new HostAndPort("192.168.0.104", 6382)); jedisClusterNode.add(new HostAndPort("192.168.0.104", 6383)); jedisClusterNode.add(new HostAndPort("192.168.0.104", 6384)); jedisClusterNode.add(new HostAndPort("192.168.0.104", 6385)); jedisClusterNode.add(new HostAndPort("192.168.0.104", 6386)); JedisCluster jedisCluster = null; try { //connectionTimeout:指的是连接一个url的连接等待时间 //soTimeout:指的是连接上一个url,获取response的返回等待时间 jedisCluster = new JedisCluster(jedisClusterNode, 6000, 5000, 10, "wkf", config);// System.out.println(jedisCluster.set("wkf", "zhuge")); System.out.println(jedisCluster.get("wkf")); } catch (Exception e) { e.printStackTrace(); } finally { if (jedisCluster != null) jedisCluster.close(); }
RedisTemplate
StringRedisTemplate与RedisTemplate详解
spring 封装了 RedisTemplate 对象来进行对redis的各种操作,它支持所有的 redis 原生的 api。在 RedisTemplate中提供了几个常用的接口方法的使用,分别是private ValueOperationsvalueOps;private HashOperations hashOps;private ListOperations listOps;private SetOperations setOps;private ZSetOperations zSetOps;
RedisTemplate中定义了对5种数据结构操作
redisTemplate.opsForValue();//操作字符串redisTemplate.opsForHash();//操作hashredisTemplate.opsForList();//操作listredisTemplate.opsForSet();//操作setredisTemplate.opsForZSet();//操作有序set
StringRedisTemplate继承自RedisTemplate,也一样拥有上面这些操作。
StringRedisTemplate默认采用的是String的序列化策略,保存的key和value都是采用此策略序列化保存 的。 RedisTemplate默认采用的是JDK的序列化策略,保存的key和value都是采用此策略序列化保存的。Redis客户端命令对应的RedisTemplate中的方法列表:
String类型结构 | |
---|---|
Redis | RedisTemplate rt |
set key value | rt.opsForValue().set(“key”,“value”) |
get key | rt.opsForValue().get(“key”) |
del key | rt.delete(“key”) |
strlen key | rt.opsForValue().size(“key”) |
getset key value | rt.opsForValue().getAndSet(“key”,“value”) |
getrange key start end | rt.opsForValue().get(“key”,start,end) |
append key value | rt.opsForValue().append(“key”,“value”) |
Hash结构 | |
hmset key field1 value1 field2 value2… | rt.opsForHash().putAll(“key”,map) //map是一个集合对象 |
hset key field value | rt.opsForHash().put(“key”,“field”,“value”) |
hexists key field | rt.opsForHash().hasKey(“key”,“field”) |
hgetall key | rt.opsForHash().entries(“key”) //返回Map对象 |
hvals key | rt.opsForHash().values(“key”) //返回List对象 |
hkeys key | rt.opsForHash().keys(“key”) //返回List对象 |
hmget key field1 field2… | rt.opsForHash().multiGet(“key”,keyList) |
hsetnx key field value | rt.opsForHash().putIfAbsent(“key”,“field”,“value” |
hdel key field1 field2 | rt.opsForHash().delete(“key”,“field1”,“field2”) |
hget key field | rt.opsForHash().get(“key”,“field”) |
List结构 | |
lpush list node1 node2 node3… | rt.opsForList().leftPush(“list”,“node”) |
rt.opsForList().leftPushAll(“list”,list) //list是集合对象 | |
rpush list node1 node2 node3… | rt.opsForList().rightPush(“list”,“node”) |
rt.opsForList().rightPushAll(“list”,list) //list是集合对象 | |
lindex key index | rt.opsForList().index(“list”, index) |
llen key | rt.opsForList().size(“key”) |
lpop key | rt.opsForList().leftPop(“key”) |
rpop key | rt.opsForList().rightPop(“key”) |
lpushx list node | rt.opsForList().leftPushIfPresent(“list”,“node”) |
rpushx list node | rt.opsForList().rightPushIfPresent(“list”,“node”) |
lrange list start end | rt.opsForList().range(“list”,start,end) |
lrem list count value | rt.opsForList().remove(“list”,count,“value”) |
lset key index value | rt.opsForList().set(“list”,index,“value”) |
Set结构 | |
sadd key member1 member2… | rt.boundSetOps(“key”).add(“member1”,“member2”,…) |
rt.opsForSet().add(“key”, set) //set是一个集合对象 | |
scard key | rt.opsForSet().size(“key”) |
sidff key1 key2 | rt.opsForSet().difference(“key1”,“key2”) //返回一个集合对象 |
sinter key1 key2 | rt.opsForSet().intersect(“key1”,“key2”)//同上 |
sunion key1 key2 | rt.opsForSet().union(“key1”,“key2”)//同上 |
sdiffstore des key1 key2 | rt.opsForSet().differenceAndStore(“key1”,“key2”,“des”) |
sinter des key1 key2 | rt.opsForSet().intersectAndStore(“key1”,“key2”,“des”) |
sunionstore des key1 key2 | rt.opsForSet().unionAndStore(“key1”,“key2”,“des”) |
sismember key member | rt.opsForSet().isMember(“key”,“member”) |
smembers key | rt.opsForSet().members(“key”) |
spop key | rt.opsForSet().pop(“key”) |
srandmember key count | rt.opsForSet().randomMember(“key”,count) |
srem key member1 member2… | rt.opsForSet().remove(“key”,“member1”,“member2”,…) |
哨兵配置
spring: redis: sentinel: #哨兵模式 master: mymaster #主服务器所在集群名称 nodes: 192.168.0.104:26379,192.168.0.104:26380,192.168.0.104:26381
集群配置
spring: redis: cluster: nodes: 192.168.0.104:6381,192.168.0.104:6382,192.168.0.104:6383,192.168.0.104:6384,192.168.0.104:6385,192.168.0.104:6386
转载地址:https://blog.csdn.net/wangkaifeng1165/article/details/125527565 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!