关于Redis的知识点,你都学会了吗?
目录
- Redis的介绍、优缺点、使用场景
- Linux中的安装
- 常用命令
- Redis各个数据类型及其使用场景
- Redis字符串(String)
- Redis哈希(Hash)
- Redis列表(List)
- Redis集合(Set)
- Redis有序集合(sorted set)
- Redis - 瑞士军刀
- 慢查询
- pipeline流水线
- 发布订阅
- bitmap
- HyperLogLog算法
- GEO
- Redis持久化,数据备份与恢复
- RDB
- AOF
- SpringBoot + Jedis + 1主2从3哨兵 实现Redis的高可用
- SpringBoot + Jedis + Redis Cluster代码案例
- 高可用
- 主从复制
- Redis事务
Redis的介绍、优缺点、使用场景
- Redis是什么: 开源的,基于键值的存储服务系统,支持多种数据类型,性能高,功能丰富
特性(主要有8个特性):
- 速度快:官方给出的结果是10W OPS,每秒10W的读写(为什么是10W,因为内存的相应时间是100纳秒-10万分之一秒)。数据存储在内存中;使用C语言开发;Redis使用单线程,减少上下文切换。本质原因是计算机存储介质的速度,内存比硬盘优几个数量级)。MemoryCache可以使用多核,性能上优于Redis。
- 持久化:Redis所有的数据保持在内存中,对数据的更新将异步地保存到磁盘上。断掉,宕机? RDB快照/AOF日志模式来确保。MemoryCache不提供持久化
- 多种数据结构:Redis提供字符串,HashTable, 链表,集合,有序集合;另外新版本的redis提供BitMaps位图,HyperLogLog超小内存唯一值计数,GEORedis3.2提供的地理位置定位。相比memocache只提供字符串的key-value结构
- 支持多种编程语言:Java,PHP,Ruby,Lua,Node
- 功能丰富: 发布订阅,支持Lua脚本,支持简单事务,支持pipline来提高客户端的并发效率
- 简单:单机核心代码23000行,让开发者容易吃透和定制化;不依赖外部库;单线程模型
- 主从复制:主服务器的数据可以同步到从服务器上,为高可用提供可能
- 高可用、分布式:2.8版本后提供Redis-Sentinel支持高可用;3.0版本支持分布式
- 典型应用场景:
- 缓存系统:缓存一些数据减少对数据库的访问,提高响应速度
- 计数器:类似微博的转发数,评论数,incr/decr的操作是原子性的不会出错
- 消息队列系统:发布订阅的模型,消息队列不是很强
- 排行版: 提供的有序集合能提供排行版的功能,例如粉丝数,关注数
- 实时系统:利用位图实现布隆过滤器,秒杀等
安装
- Linux中安装
wget http://download.redis.io/releases/redis-5.0.7.tar.gz
tar -zxvf redis-5.0.7.tar.gz
mv redis-5.0.7 /usr/local/redis 不需要先创建/usr/local.redis文件夹
cd /usr/local/redis
make
make install
vi redis.conf
* bind 0.0.0.0 开发访问
* daemonize yes 设置后台运行
redis-server ./redis.conf 启动
redis-cli 进入命令行,进行简单的命令操作
vi redis.conf
> requirepass password 修改密码
redis-cli 再次进入cmd
> shutdown save 关闭redis,同时持久化当前数据
redis-server ./redis.conf 再次启动redis
redis-cli 进入命令行
> auth password
将redis配置成系统服务,redis/utils中自带命令,我们只需修改参数
/usr/local/redis/utils/./install_server.sh
[root~ utils]# ./install_server.sh
Welcome to the redis service installer
Please select the redis port for this instance: [6379] 默认端口不管
Selecting default: 6379
Please select the redis config file name [/etc/redis/6379.conf] /usr/local/redis/redis.conf 修改配置文件路径
Please select the redis log file name [/var/log/redis_6379.log] /usr/local/redis/redis.log 修改日志文件路径
Please select the data directory for this instance [/var/lib/redis/6379] /usr/local/redis/data 修改数据存储路径
Please select the redis executable path [/usr/local/bin/redis-server]
Selected config:
Port : 6379
Config file : /usr/local/redis/redis.conf
Log file : /usr/local/redis/redis.log
Data dir : /usr/local/redis/data
Executable : /usr/local/bin/redis-server
Cli Executable : /usr/local/bin/redis-cli
chkconfig --list | grep redis 查看redis服务配置项
redis_6379 0:off 1:off 2:on 3:on 4:on 5:on 6:off
服务名是redis_6379
可执行文件说明
- redis-server: Redis服务器,启动Redis的
- redis-cli: Redis命令行客户端连接
- redis-benchmark: 对Redis做性能测试
- redis-check-aof: AOF文件修复工具
- redis-check-dump: RDB文件检查工具
- redis-sentinel: Sentinel服务器(2.8以后)
- 启动方式
- redis-server: 最简单的默认启动,使用redis的默认参数
- 动态参数启动:redis-server –port yourorderpoint
- 配置文件的方式: redis-server configpath
- 比较:
- 生产环境选择配置启动;单机多实例配置文件可以选择配置文件分开
- Redis客户端返回值
- 状态回复:ping->pong
- 错误恢复:执行错误的回复
- 整数回复:例如incr会返回一个整数
- 字符串回复: get
- 多行字符串回复:mget
- 常用配置
- daemonize: 是否是守护进程(y/n)
- port端口:默认是6379
- logfile:Redis系统日志
- dir:Redis工作目录
- 常用命令:在线练习http://try.redis.io/
redis-cli -h x.x.x.x -p x 连接
auth "password" 验证密码
redis-cli --raw可以避免中文乱码
exit 退出
select index 切换到指定的数据库
keys * 显示所有key,如果键值对多不建议使用,keys会遍历所有key,可以在从节点使用;时间复杂度O(N)
dbsize 算出所有的key的数量,只是数量;时间复杂度O(1)
exists key key是否存在,存在返回1,不存在返回0;时间复杂度O(1)
incr key 将key的值加一,是原子操作
decr key 将key的值加一,会出现复数,是原子操作
del key 删除key,删除成功返回1,失败返回0;时间复杂度O(1)
expire key seconds 设置过期时间,过期之后就不存在了;时间复杂度O(1)
ttl key 查看key剩余的过期时间,key不存在返回-2;key存在没设置过期时间返回-1;(TTL Time To Live)
persist key 去掉key的过期时间,再查看ttl key,返回值是-1,表示key存在并且没有设置过期时间
type key 查看类型;时间复杂度O(1)
config get * 获取配置信息
set key value插入值
sadd myset 1 2 3 4 插入set
get key获取值
del key删除key
cat redis.conf | grep -v "#" | grep -v "^$" 查看配置文件,去除所有的#,去除所有的空格
setnx key value #key不存在,才设置
set key value xx #可以存在,才设置
set key value [exporation EX seconds | PX milliseconds] [NX|EX]
mget key1 key2 key3 批量获取 1次mget=1次网络时间+n次命令时间;时间复杂度O(n)
mset key1 value1 key2 value2 批量插入;时间复杂度O(n)
n次get = n次网络时间 + n次命令时间,mget一次就能完成,省去大量的网络时间
getset key newvalue # set key newvalue并返回旧的value
append key value #将value追加到旧的value
strlen key #获取value的长度,中文占2个字节
incrbyfloat key 3.5 #增加key对应的值
set/get/del, incr(自增1)/decr(自减1)/incrby(incrby key n自增n)/decrby
getrange key start end #获取value从start到end的值
setrange key index value #设置指定下标为一个新的值
hset key field value #给key的field设置值
hget key field #获取key的field的值
hdel key field #删除key的field的值
hgetall key #获取key的所有值
hexists key field # 判断key的field是否存在
hlen key #获取key field的数量
hmset key field1 value1 field2 value2
hmget key field1 field2
hsetnx/hincrby/hdecry/hincrbyfloat
lpush key value1 value2...valueN #从左边插入
rpush key value1 value2...valueN #从右边插入
linsert key before|after value newValue
rinsert key before|after value newValue
lpop key #从左边弹出一个item
rpop key #从右边弹出一个item
lrem key count value #若count等于0或者不填,表示删除所有的value值相等的item;若count>0,表示从左到右删除最多count个value相等的item;若count<0,表示从右到左,删除最多Math.abs(count)个value相等的项
ltrim key start end #按照索引范围修剪列表,可以用来慢删除,因为全删除可能会阻塞redis
lrang key start end #获取key中从start到end的值
lindex key index #取第index的值
llen key #算出列表的长度
lset key index newValue #修改index的值为newValue
blpop key timeout #lpop阻塞版本,timeout是阻塞时间,timeout=0表示死等,lpop会立马返回,有时候数据更新不那么及时,或者消息队列中消息未及时处理,我们可以使用这个
brpop key timeout
lpush + LPOP = STACK
lpush + RPOP = QUEUE
lpush + ltrim = 有序的集合
lpush + rpop = 消息队列
sadd key value #不支持插入重复元素,失败返回0
srem key element #删除集合中的element元素
smembers key #查看集合元素
sinter key1 key2 #取出相同:交集
sdiff key1 key2 #取出key1中key2没有的元素:差集
sunion key1 key2 #取出二者所有的元素:并集
sdiff|sinter|sunion store key #将结果存到key中,有时候计算一次耗时
scard key #计算集合大小
sismember key element #判断element是否在集合中
srandmember #返回所有元素,结果是无序的,小心使用,可能结果很大
smembers key #获取集合中的所有元素
spop key #从集合中随机弹出一个元素
scan
SADD = Tagging
SPOP/SRANDMEMBER = Random item
SADD + SINTER = Social Graph
zadd key score element #添加score和element O(logN): 使用xx和跳表的数据结构
zrem key element #删除元素
zscore key element #返回元素的分数
zincrby key increScore element #增加或减少元素分数
zcard key #返回元素的总个数
zrank key element #获取element的排名
zrange key start end [withscores] #返回指定索引范围内的升序元素
zrangebyscore key minScore maxScore [withscore] #返回分数在minScore和maxScore之间的元素
zcount key minScore maxScore #返回有序集合内在指定分数范围内的个数
zremrangebyrank key start end #删除指定排名内的元素
zremrangebyscore key minScore maxScore #删除指定分数内的元素
zrevrang/zrevrange/集合间的操作zsetunion
info replication 查看分片,能够获取到主从的数量和状态
config get databases 获取所有数据库
数据结构和内部编码
- Reids支持5中存储的数据格式: String, Hash, List, Set, Sorted Set
String
- redis 的 string 可以包含任何数据。比如jpg图片或者序列化的对象,最大能存储 512MB。
- 使用场景:缓存/计数器/分布式锁/Web集群session共享/分布式系统全局序号(不用每次都拿,一次拿1000个放到内存中)…
- 常用命令:
- 实战:实现分布式的id生成器,可以使用incr的思路,但是实际中会比这复杂
hash
- 是一个键值(key=>value)对集合。Redis hash 是一个 string 类型的 field 和 value 的映射表,hash 特别适合用于存储对象。
- 实战:统计用户主页的访问量, hincrby user:1:info pageview count
- Redis集群架构下不太适合
list
- Redis 列表是简单的字符串列表,按照插入顺序排序。列表最多可存储 232 - 1 元素 (4294967295, 每个列表可存储40多亿)。
- 实战:微博按时间顺序展示消息
set
- 是 string 类型的无序集合,不允许插入重复元素,插入重复元素失败返回0。集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。
- 实战:抽奖系统(量不是很大的时候);like,star可以放到集合中;标签tag
zset
- 有序集合:有序且无重复元素,和 set 一样也是string类型元素的集合,且不允许重复的成员。不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。zset的成员是唯一的,但分数(score)却可以重复。
- 实战:排行榜
Redis客户端: Java的Jedis(Socket通信),Python的redis-py
瑞士军刀
慢查询
- 生命周期
两点说明:
- 慢查询发生在第3阶段,比如keys *等这些需要扫描全表的操作
- 客户端超时不一定慢查询,但慢查询是客户端超时的一个可能因素
两个配置
- slowlog-log-slower-than=n(微秒):命令执行时间超过x微秒,会被丢到一个固定长度的慢查询queue中;n<0表示不配置
- slowlog-max-len: 先进先出的队列,固定长度,保存在内存中(重启redis会消失)
配置方法
默认值
- config get slowlog-max-len=128
- config get slowlog-log-slower-than=10000
修改配置文件重启
动态配置
- config set slowlog-max-len 1000
- config set slowlog-log-slower-than 1000
常用命令
- slowlog get [n]:获取慢查询队列
- slowlog len: 获取慢查询队列的长度
- slowlog reset: 清空慢查询队列
运维经验
- slowlog-max-len不要设置过大,默认10ms,通常设置1ms,根据QPS来设置
- slowlog-log-slower-than不要设置过小,通常设置1000左右
- 定期持久化慢查询
pipeline流水线(批量操作)
当遇到批量网络命令的时候,n次时间=n次网络时间+n次命令时间。举个例子,北京到上海的距离是1300公里,光速是3万公里/秒,假设光纤传输速度是光速的2/3,也就是万公里/秒,那么一次命令的传输时间是 1300/20000*2(来回)=13毫秒, 什么是pipeline流水线,1次pipeline(n条命令)=1次网络时间+n次命令时间;pipeline命令在redis服务端会被拆分,因此pipeline命令不是一个原子的命令。注意每次pipeline携带数据量;pipeline每次只能作用在一个Redis节点上;M操作和pipeline的区别,M(mset)操作是redis的原生命令,是原子操作,pipeline不是原子操作
for(int i = 0; i < 10000; i++>) {
jedis.hset(key, field, value); //1万次hset差不多要50秒
for(0->100) {
Pipeline pipeline = jedis.pipelined();
for(0->100) {
pipeline.hset(key,field,value);
}
pipeline.syncAndReturnAll(); //拆分100次,每次100个命令,大概需要0.7秒
}
发布订阅:类似生产者消费者模型
- 角色:发布者(publisher),频道(channel),订阅者(subscriber); 发布者将消息发布到频道中,订阅者订阅相关的频道;
- API: publish/subscribe/unsubscribe
- publish channel message : publish sohu:tv “hello world”
- subscribe sohu:tv
- unsubscribe [channel]
- psubscribe [pattern] #订阅模式 sohu*
bitmap:位图:数据量很大的时候节省存储内存,数据量小了,不节省
hyperloglog(算法,数据结构):
- 极小空间完成独立数量统计,本质是个string
- api: pfadd key element[s]:向hyperloglog添加元素 pfcount key[s]:计算hyperloglog的独立总数 pfmerge key1 key2合并
GEO: 3.2提供的用于计算地理位置信息;数据类型是zset,可以使用zset的删除命令
- 使用场景:微信摇一摇看附近好友
- api:
- geo key longitude latitude member #增加地理位置信息
- geopos key member[n] #获取地理位置信息
- geodist key member1 membe2 [unit] m米 km千米 mi英里 ft尺 获取两地位置的距离
- georadius #算出指定范围内的地址位置信息的集合,语法复杂了点
- 总结下Redis数据结构和类型的常见用法
类型 | 简介 | 特性 | 使用场景 |
---|---|---|---|
String | 二进制安全 | 可以包含任何数据,比如JPG图片或者序列化的对象,一个键最大能存储512M | |
Hash | 键值对集合,即编程中的Map | 适合存储对象,并且可以向数据库中那样update一个属性(Memcache中需要将字符串反序列化成对象之后再修改属性,然后序列化回去) | 存取/读取/修改 用户信息 |
List | 双向链表 | 增删快,提供了操作某一元段元素的API | 1. 最新消息,按照时间线显示2. 消息队列 |
Set | 哈希表实现,元素不重复 | 添加/删除/修改的复杂度都是O(1),为集合提供求交集/并集/差集的操作 | 1. 打label/tag,如文章2. 查找共同好友3. 抽奖系统 |
Zset | 将Set中的元素增加一个double类型的权重score,按照score排序 | 数据插入集合就好序了 | 排行榜 |
Hyperloglog | 本质是string | 极小空间完成独立数据量统计 | 统计基数,不完全正确 |
GEO | 数据类型是zset | 存储地理位置信息,并提供计算距离等操作 | 微信摇一摇查看附近好友 |
Bitmap | 位图 | 数据量很大的时候节省存储内存,数据量小了不节省 |
- String 简单动态字符串 Simple Dynamic String, SDS
Redis没有直接使用C语言的传统字符串表示,而是自己构建了一种名为简单动态字符串(Simple Dynamic String, SDS)的抽象类型,并将SDS用作Redis的默认字符串表示。
每个sds.h/sdshdr结构表示一个SDS值:
struct sdshdr {
int len; // 记录buf数组中已经使用的字节数量
int free; // 记录buf数组中未使用字节的数量
char buf[]; // 字节数组,用于保存字符串。SDS遵循C字符串以空字符结尾的惯例
}
Redis持久化
- 持久化的作用:redis所有数据保存在内存中,对数据的更新将异步地保存到磁盘上。
- 主流数据库持久化实现方式:快照(MySQL Dump/Redis RDB),写日志(MySQL Binlog/Redis AOF)
RDB:
- 创建RDB文件(二进制文件)到硬盘中,启动后载入RDB文件到内存
三种触发机制
- save(同步) - 会产生阻塞
- 文件策略:如存在老的RDB文件,新的替换老的,新的会先生成到一个临时文件
- bgsave(异步) - 不会阻塞
- 客户端执行bgsave之后,redis会使用linux的一个fork()命令生成主进程的一个子进程(fork的操作会执行一个内存页的拷贝,使用copy-on-write策略),子进程会创建RDB文件,创建完毕后将成功的消息返回给redis。fork()出来的子进程执行快的话不会阻塞主进程,否则也会阻塞redis,阻塞的实际点就是生成出来这个子进程。由于是异步,在创建的过程中还有其他命令在执行,如何保证RDB文件是最新的呢?在数据量大的时候bgsave才能突出优点。
命令savebgsaveIO类型同步异步阻塞是是(阻塞发生在fork子进程复杂度O(n)O(n)优点不会消耗额外内存不阻塞客户端命令缺点阻塞客户端命令需要fork,消耗内存
- 自动触发:多少秒内有多少changes会异步(bgsave)生成一个RDB文件,如60秒内有1W条changes,默认的规则,可以改;不是很好吧,无法控制频率;另外两条是900秒内有1条changes, 300秒内有10条changes;
配置
- dbfilename dump.rdb
- dir ./
- stop-writes-on-bgsave-error yes 当bgsave发生错误是停止写RDB文件
- rdbcompression yes 采用压缩格式
- rdbchecksum yes 采用校验和
其他不能忽视的点:
全量复制;debug reload;shutdown save会执行rdb文件的生成
AOF:
- RDB现存问题:耗时,耗性能(fork,IO),不可控(突然宕机)
- AOF:redis中的cmd会先刷新到缓冲区,然后更具配置AOF的策略,异步存追加到AOF文件中,发生宕机后,可以通过- AOF恢复,基本上数据是完整的
- AOF的三种策略(配置的三种属性)
- always:来一条命令写一条;不丢失数据,IO开销较大
- everysec:每秒把缓冲区fsync到AOF文件;丢1秒数据
- no:操作系统决定什么时候把缓冲区同步到AOF就什么时候追加;不用配置,但是不可控,取决于操作系统
AOF重写
- 如果AOF文件很大的话,恢复会很慢,AOF的重写是优化一些命名,使其变成1条,对于过期数据没必要Log,本质是把过期的没有用的,重复的过滤掉,以此减少磁盘占用量,加速恢复。极端的例子,1亿次incr,实际只需要set counter n就够了
- 重写的两种方式
- bgrewriteaof:异步执行,redis fork出一个子进程,然后进行AOF重写
- AOF重写配置
- auto-aof-rewrite-min-size: AOF文件到达多大的时候才开始重写
- auto-aof-rewrite-percentage: AOF文件的增长率到达了多大才开始重写
统计
- aof_current_size AOF当前尺寸 字节
- aof_base_size AOF上次重启和重写的尺寸 字节,方便自动重写判断
重写触发机制(同时满足如下两条)
- aof_current_size > auto-aof-rewrite-min-size
- (aof_current_size - aof_base_size) / aof_base_size > auto-aof-rewrite-percentage
其他配置
- appendonly yes
- appendfilename “”
- appendfsync everysec
- dir /xx
- no-appendfsync-on-rewrite yes AOF在重启之后恢复,要权衡是否开启AOF日志追加的功能,这个时候IO很大,如果设置为yes,也就意味着在恢复之前的日志数据会丢失
RDB & AOF最佳策略:RDB优先于AOF先启用
- RDB:建议关掉,集中管理,在从节点开RDB
- AOF:建议开启,每秒刷盘
- 最佳策略:小分片(log文件分片)
常见问题
- fork操作:是一个同步操作,做一个内存页的拷贝;与内存量息息相关,内存越大,耗时越长;执行info命令,有个latest_fork_usec的值,看下上次fork执行耗时
- 进程外开销:
- CPU:RDB AOF文件生成,属于CPU密集型操作(不要和CPU密集型应用部署在一起,减少RDB AOF频率);内存:fork内存开销;硬盘:IO开销大,选用SSD磁盘
- AOF追加阻塞:主线程将命令刷到AOF缓冲区,同步线程同步命令到硬盘,同时主线程会对比上次fsync的时间,如果大于2秒就阻塞主线程,否则不阻塞,主线程这么做是为了达到每秒刷盘的目的,让子线程完成AOF,以此来达到数据同步。AOF发生阻塞怎么定位:redis日志/info persistence(aof_delayed_fsync累计阻塞次数,是累计,不好分清什么时候发生阻塞)
- 单机多实例部署
高可用
Redis主从复制
- 主从复制:单机故障/容量瓶颈/QPS瓶颈;一个master可以有多个slave,一个slave只能有一个master,数据必须是单流向,从master流向slave
复制的配置:
- 使用slaeof命令,在从redis中执行slave masterip:port使其成为master的从服务器,就能从master拉取数据了;执行slaveof no one清除掉不成为从节点,但是数据不清楚;
- 修改配置, slaveof ip port / slave-read-only yes(从节点只做都操作);配置要更改的话,要重启,所以选择的时候谨慎
全量复制
- run_id(使用info server可以看到run_id),重启之后run_id就没有了,当从服务器去复制主服务器,主服务器run_id会在从服务器上做一个标识,当从服务器发现主服务器的run_id发生了变化,说明主服务器发生了变化(重启或者什么的),那么从服务器就要把主服务器的数据都同步过来
- 偏移量:部分复制中的一个依据,后面说
- 解析下上面的全量复制的过程,slave向master发送psync的命令要去master全量复制数据(PSYNC ,其中?表示我不知道master的runId啊,第一次连嘛,-1表示我都要,这时候slava咱啥也不知道),master大人收到了小弟的请求之后,大方的把自己的runId/offset发了过去,小弟收到后先存下来;在master大人把自个的信息发给小弟之后,立马投入了创建快照RDB的工作,一个bgsave命令立马开工,RDB生产了就发给slave;咦,细心的我们发现你这不对啊,你master创建快照到创建完成这之间新增的数据咋办,master吭吭了两声,我在开始快照的那一刻,后期的所有写命令都额外往buffer中存了一份,来保证我给你的是完整的,当我发送完RDB之后,立马给你发buffer;slave小弟内心对master大人产生了膜拜之情,收到了RDB/buffer之后,先把自己的老数据flush掉,然后load RDB,把最新的buffer刷一遍,分分钟让自己向master看齐。
- 开销:bgsave时间, RDB文件网络传输时间,从节点清空数据时间,从节点加载RDB的时间,可能的AOF重写时间
- 解释下上面的部分复制的过程,当遇到网络抖动,那这段时间内数据在slave上就会发生丢失,那么这些数据slave是不知道的,在2.8之前redis会重新做一次全量复制,但是很显然这样做开销很大,2.8之后提出部分复制的功能;当matster发现slave连接不上的时候,master在进行写操作的时候,也会往缓冲区写,等到下一次slave连上之后,slave会发送一条pysnc {offset}{runId}的命令,其中offset是slave自己的,相当于告诉master我的偏移量是多少,master判断slave的offset在缓冲区内(缓冲区有start/end offset)就向slave发送continue命令,然后把这部分数据发送给slave;当master发现slave这个offset偏移量很大的时候,也就意味着slave丢失了很多数据,那么就进行一次全量复制
故障处理:
- master/slave宕机的情况,主从模式没有实现故障的完全自动转移
- 常见问题:
- 读写分离:读流量分摊到从节点,可能遇到复制数据延迟,也可能读到过期的数据,从节点故障怎么办
- 主从配置不一致:主从maxmemory不一致,可能会丢失数据;主从内存不一致
- 规避全量复制:第一次不可避免;小主节点,低峰处理(夜间);主节点重启后runId发生了变化
规避复制风暴 - 单机主节点复制风暴,如果是1主N从,当master重启之后,所有的slave都会发生全量复制,可想而知这样非常容易造成redis服务的不可用
Redis事务
Redis 事务可以一次执行多个命令, 并且带有以下三个重要的保证:
- 批量操作在发送 EXEC 命令前被放入队列缓存。
- 收到 EXEC 命令后进入事务执行,事务中任意命令执行失败,其余的命令依然被执行。
- 在事务执行过程,其他客户端提交的命令请求不会插入到事务执行命令序列中。
- Redis事务从开始到执行会经历以下三个阶段:开始事务 -> 命令入队 -> 执行事务。单个 Redis 命令的执行是原子性的,但 Redis 没有在事务上增加任何维持原子性的机制,所以 Redis 事务的执行并不是原子性的。事务可以理解为一个打包的批量执行脚本,但批量指令并非原子化的操作,中间某条指令的失败不会导致前面已做指令的回滚,也不会造成后续的指令不做。这是官网上的说明 From redis docs on transactions: 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.
- Redis 通过监听一个 TCP 端口或者 Unix socket 的方式来接收来自客户端的连接,当一个连接建立后,Redis 内部会进行以下一些操作:
- 首先,客户端 socket 会被设置为非阻塞模式,因为 Redis 在网络事件处理上采用的是非阻塞多路复用模型。
- 然后为这个 socket 设置 TCP_NODELAY 属性,禁用 Nagle 算法
- 然后创建一个可读的文件事件用于监听这个客户端 socket 的数据发送
- Redis 管道技术可以在服务端未响应时,客户端可以继续向服务端发送请求,并最终一次性读取所有服务端的响应。管道技术最显著的优势是提高了 redis 服务的性能。
Redis 分区
- 分区是分割数据到多个Redis实例的处理过程,因此每个实例只保存key的一个子集。
分区的优势:
- 通过利用多台计算机内存的和值,允许我们构造更大的数据库。
- 通过多核和多台计算机,允许我们扩展计算能力;通过多台计算机和网络适配器,允许我们扩展网络带宽。
分区的不足:
- 涉及多个key的操作通常是不被支持的。举例来说,当两个set映射到不同的redis实例上时,你就不能对这两个set执行交集操作。
- 涉及多个key的redis事务不能使用。
- 当使用分区时,数据处理较为复杂,比如你需要处理多个rdb/aof文件,并且从多个实例和主机备份持久化文件。
- 增加或删除容量也比较复杂。redis集群大多数支持在运行时增加、删除节点的透明数据平衡的能力,但是类似于客户端分区、代理等其他系统则不支持这项特性。然而,一种叫做presharding的技术对此是有帮助的。
- 分区类型:Redis 有两种类型分区。 假设有4个Redis实例 R0,R1,R2,R3,和类似user:1,user:2这样的表示用户的多个key,对既定的key有多种不同方式来选择这个key存放在哪个实例中。也就是说,有不同的系统来映射某个key到某个Redis服务,关注+转发后,私信【Redis】获取300多页的Redis实战学习笔记。
范围分区
- 最简单的分区方式是按范围分区,就是映射一定范围的对象到特定的Redis实例。比如,ID从0到10000的用户会保存到实例R0,ID从10001到 20000的用户会保存到R1,以此类推。这种方式是可行的,并且在实际中使用,不足就是要有一个区间范围到实例的映射表。这个表要被管理,同时还需要各 种对象的映射表,通常对Redis来说并非是好的方法。
哈希分区
- 另外一种分区方法是hash分区。这对任何key都适用,也无需是object_name:这种形式,像下面描述的一样简单:用一个hash函数将key转换为一个数字,比如使用crc32 hash函数。对key foobar执行crc32(foobar)会输出类似93024922的整数。对这个整数取模,将其转化为0-3之间的数字,就可以将这个整数映射到4个Redis实例中的一个了。93024922 % 4 = 2,就是说key foobar应该被存到R2实例中。注意:取模操作是取除的余数,通常在多种编程语言中用%操作符实现。