三大特殊数据类型
1 geospatial(地理位置)
1.geospatial将指定的地理空间位置(纬度、经度、名称)添加到指定的key中。
这些数据将会存储到sorted set这样的目的是为了方便使用GEORADIUS或者GEORADIUSBYMEMBER命令对数据进行半径查询等操作。
2.sorted set使用一种称为Geohash的技术进行填充。经度和纬度的位是交错的,以形成一个独特的52位整数。
sorted set的double score可以代表一个52位的整数,而不会失去精度。(有兴趣的同学可以学习一下Geohash技术,使用二分法构建唯一的二进制串)
3.有效的经度是-180度到180度
有效的纬度是-85.05112878度到85.05112878度
应用场景
查看附近的人
微信位置共享
地图上直线距离的展示
2 Hyperloglog(基数)
什么是基数? 不重复的元素
hyperloglog 是用来做基数统计的,其优点是:输入的提及无论多么大,hyperloglog使用的空间总是固定的12KB ,利用12KB,它可以计算2^64个不同元素的基数!非常节省空间!但缺点是估算的值,可能存在误差
应用场景
网页统计UV (浏览用户数量,同一天同一个ip多次访问算一次访问,目的是计数,而不是保存用户)
传统的方式,set保存用户的id,可以统计set中元素数量作为标准判断。
但如果这种方式保存大量用户id,会占用大量内存,我们的目的是为了计数,而不是去保存id。
3 Bitmaps(位存储)
Redis提供的Bitmaps这个“数据结构”可以实现对位的操作。Bitmaps本身不是一种数据结构,实际上就是字符串,但是它可以对字符串的位进行操作。
可以把Bitmaps想象成一个以位为单位数组,数组中的每个单元只能存0或者1,数组的下标在bitmaps中叫做偏移量。单个bitmaps的最大长度是512MB,即2^32个比特位。
应用场景
两种状态的统计都可以使用bitmaps,例如:统计用户活跃与非活跃数量、登录与非登录、上班打卡等等。
Redis事务
事务本质:一组命令的集合
1 数据库事务与redis事务
数据库的事务
数据库事务通过ACID(原子性、一致性、隔离性、持久性)来保证。
数据库中除查询操作以外,插入(Insert)、删除(Delete)和更新(Update)这三种操作都会对数据造成影响,因为事务处理能够保证一系列操作可以完全地执行或者完全不执行,因此在一个事务被提交以后,该事务中的任何一条SQL语句在被执行的时候,都会生成一条撤销日志(Undo Log)。
redis事务
redis事务提供了一种“将多个命令打包, 然后一次性、按顺序地执行”的机制, 并且事务在执行的期间不会主动中断 —— 服务器在执行完事务中的所有命令之后, 才会继续处理其他客户端的其他命令。
Redis中一个事务从开始到执行会经历开始事务(muiti)、命令入队和执行事务(exec)三个阶段,事务中的命令在加入时都没有被执行,直到提交时才会开始执行(Exec)一次性完成。
一组命令中存在两种错误不同处理方式
1.代码语法错误(编译时异常)所有命令都不执行
2.代码逻辑错误(运行时错误),其他命令可以正常执行 (该点不保证事务的原子性)
为什么redis不支持回滚来保证原子性
这种做法的优点:
Redis 命令只会因为错误的语法而失败(并且这些问题不能在入队时发现),或是命令用在了错误类型的键上面:这也就是说,从实用性的角度来说,失败的命令是由编程错误造成的,而这些错误应该在开发的过程中被发现,而不应该出现在生产环境中。
因为不需要对回滚进行支持,所以 Redis 的内部可以保持简单且快速。
鉴于没有任何机制能避免程序员自己造成的错误, 并且这类错误通常不会在生产环境中出现, 所以 Redis 选择了更简单、更快速的无回滚方式来处理事务。
事务监控
悲观锁:认为什么时候都会出现问题,无论做什么操作都会加锁。
乐观锁:认为什么时候都不会出现问题,所以不会上锁!更新数据的时候去判断一下,在此期间是否有人修改过这个数据。
使用cas实现乐观锁
redis使用watch key监控指定数据,相当于加乐观锁
watch保证事务只能在所有被监视键都没有被修改的前提下执行, 如果这个前提不能满足的话,事务就不会被执行。
watch执行流程
Redis持久化
Redis是一种内存型数据库,一旦服务器进程退出,数据库的数据就会丢失,为了解决这个问题Redis供了两种持久化的方案,将内存中的数据保存到磁盘中,避免数据的丢失
两种持久化方式:快照(RDB文件)和追加式文件(AOF文件),下面分别为大家介绍两种方式的原理。
RDB持久化方式会在一个特定的间隔保存那个时间点的数据快照。
AOF持久化方式则会记录每一个服务器收到的写操作。在服务启动时,这些记录的操作会逐条执行从而重建出原来的数据。写操作命令记录的格式跟Redis协议一致,以追加的方式进行保存。
Redis的持久化是可以禁用的,就是说你可以让数据的生命周期只存在于服务器的运行时间里。
两种方式的持久化是可以同时存在的,但是当Redis重启时,AOF文件会被优先用于重建数据。
1 RDB持久化
RDB持久化产生的文件是一个经过压缩的二进制文件,这个文件可以被保存到硬盘中,可以通过这个文件还原数据库的状态,它可以手动执行,也可以在redis.conf配置文件中配置,定时执行。
工作原理
在进行RDB时,redis的主进程不会做io操作,会fork一个子进程来完成该操作:
Redis 调用forks。同时拥有父进程和子进程。
子进程将数据集写入到一个临时 RDB 文件中。
当子进程完成对新 RDB 文件的写入时,Redis 用新 RDB 文件替换原来的 RDB 文件,并删除旧的 RDB 文件。
这种工作方式使得 Redis 可以从写时复制(copy-on-write)机制中获益(因为是使用子进程进行写操作,而父进程依然可以接收来自客户端的请求)
触发机制
在Redis中RDB持久化的触发分为两种:自己手动触发与自动触发。
主动触发
save命令是同步的命令,会占用主进程,会造成阻塞,阻塞所有客户端的请求
bgsave
bgsave是异步进行,进行持久化的时候,redis还可以将继续响应客户端请求
bgsave和save对比
自动触发
save自动触发配置,见下面配置,满足m秒内修改n次key,触发rdb
# 时间策略 save m n m秒内修改n次key,触发rdb
save 900 1
save 300 10
save 60 10000
# 文件名称
dbfilename dump.rdb
# 文件保存路径
dir /home/work/app/redis/data/
# 如果持久化出错,主进程是否停止写入
stop-writes-on-bgsave-error yes
# 是否压缩
rdbcompression yes
# 导入时是否检查
rdbchecksum yes
从节点全量复制时,主节点发送rdb文件给从节点完成复制操作,主节点会触发bgsave命令;
执行flushall命令,会触发rdb
退出redis,且没有开启aof时
优点:
RDB 的内容为二进制的数据,占用内存更小,更紧凑,更适合做为备份文件;
RDB 对灾难恢复非常有用,它是一个紧凑的文件,可以更快的传输到远程服务器进行 Redis 服务恢复;
RDB 可以更大程度的提高 Redis 的运行速度,因为每次持久化时 Redis 主进程都会 fork() 一个子进程,进行数据持久化到磁盘,Redis 主进程并不会执行磁盘 I/O 等操作;
与 AOF 格式的文件相比,RDB 文件可以更快的重启。
缺点:
因为 RDB 只能保存某个时间间隔的数据,如果中途 Redis 服务被意外终止了,则会丢失一段时间内的 Redis 数据。
RDB 需要经常 fork() 才能使用子进程将其持久化在磁盘上。如果数据集很大,fork() 可能很耗时,并且如果数据集很大且 CPU 性能不佳,则可能导致 Redis 停止为客户端服务几毫秒甚至一秒钟。
2 AOF(Append Only File)
以日志的形式来记录每个写的操作,将Redis执行过的所有指令记录下来(读操作不记录),只许追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。
AOF配置项
# 默认不开启aof 而是使用rdb的方式
appendonly no
# 默认文件名
appendfilename "appendonly.aof"
# 每次修改都会sync 消耗性能
# appendfsync always
# 每秒执行一次 sync 可能会丢失这一秒的数据
appendfsync everysec
# 不执行 sync ,这时候操作系统自己同步数据,速度最快
# appendfsync no
AOF的整个流程大体来看可以分为两步,一步是命令的实时写入(如果是appendfsync everysec 配置,会有1s损耗),第二步是对aof文件的重写。
AOF 重写机制
随着Redis的运行,AOF的日志会越来越长,如果实例宕机重启,那么重放整个AOF将会变得十分耗时,而在日志记录中,又有很多无意义的记录,比如我现在将一个数据 incr一千次,那么就不需要去记录这1000次修改,只需要记录最后的值即可。所以就需要进行 AOF 重写。
Redis 提供了bgrewriteaof指令用于对AOF日志进行重写,该指令运行时会开辟一个子进程对内存进行遍历,然后将其转换为一系列的 Redis 的操作指令,再序列化到一个日志文件中。完成后再替换原有的AOF文件,至此完成。
同样的也可以在redis.config中对重写机制的触发进行配置:
通过将no-appendfsync-on-rewrite设置为yes,开启重写机制;auto-aof-rewrite-percentage 100意为比上次从写后文件大小增长了100%再次触发重写;
auto-aof-rewrite-min-size 64mb意为当文件至少要达到64mb才会触发制动重写。
触发方式
手动触发:bgrewriteaof
自动触发 就是根据配置规则来触发,当然自动触发的整体时间还跟Redis的定时任务频率有关系。
优点
1、数据安全,aof 持久化可以配置 appendfsync 属性,有 always,每进行一次 命令操作就记录到 aof 文件中一次。
2、通过 append 模式写文件,即使中途服务器宕机,可以通过 redis-check-aof 工具解决数据一致性问题。
3、AOF 机制的 rewrite 模式。AOF 文件没被 rewrite 之前(文件过大时会对命令 进行合并重写),可以删除其中的某些命令(比如误操作的 flushall))
缺点
1、AOF 文件比 RDB 文件大,且恢复速度慢。
2、数据集大的时候,比 rdb 启动效率低。
3 rdb与aof对比
发布与订阅
redis发布与订阅是一种消息通信的模式:发送者(pub)发送消息,订阅者(sub)接收消息。
redis通过PUBLISH和SUBSCRIBE等命令实现了订阅与发布模式,这个功能提供两种信息机制,分别是订阅/发布到频道、订阅/发布到模式的客户端。
1 频道(channel)
订阅
发布
完整流程
发布者发布消息
发布者向频道channel:1发布消息hi
127.0.0.1:6379> publish channel:1 hi
(integer) 1
订阅者订阅消息
127.0.0.1:6379> subscribe channel:1
Reading messages... (press Ctrl-C to quit)
1) "subscribe" // 消息类型
2) "channel:1" // 频道
3) "hi" // 消息内容
执行subscribe后客户端会进入订阅状态,仅可以使subscribe、unsubscribe、psubscribe和punsubscribe这四个属于"发布/订阅"之外的命令
订阅频道后的客户端可能会收到三种消息类型
subscribe。表示订阅成功的反馈信息。第二个值是订阅成功的频道名称,第三个是当前客户端订阅的频道数量。
message。表示接收到的消息,第二个值表示产生消息的频道名称,第三个值是消息的内容。
unsubscribe。表示成功取消订阅某个频道。第二个值是对应的频道名称,第三个值是当前客户端订阅的频道数量,当此值为0时客户端会退出订阅状态,之后就可以执行其他非"发布/订阅"模式的命令了。
数据结构
基于频道的发布订阅模式是通过字典数据类型实现的
struct redisServer {
// ...
dict *pubsub_channels;
// ...
};
其中,字典的键为正在被订阅的频道, 而字典的值则是一个链表, 链表中保存了所有订阅这个频道的客户端。
订阅
当使用subscribe订阅时,在字典中找到频道key(如没有则创建),并将订阅的client关联在链表后面。
当client 10执行subscribe channel1 channel2 channel3时,会将client 10分别加到 channel1 channel2 channel3关联的链表尾部。
发布
发布时,根据key,找到字典汇总key的地址,然后将msg发送到关联的链表每一台机器。
退订
遍历关联的链表,将指定的地址删除即可。
2 模式(pattern)
pattern使用了通配符的方式来订阅
通配符中?表示1个占位符,表示任意个占位符(包括0),?表示1个以上占位符。
所以当使用 publish命令发送信息到某个频道时, 不仅所有订阅该频道的客户端会收到信息, 如果有某个/某些模式和这个频道匹配的话, 那么所有订阅这个/这些频道的客户端也同样会收到信息。
订阅发布完整流程
发布者发布消息
127.0.0.1:6379> publish b m1
(integer) 1
127.0.0.1:6379> publish b1 m1
(integer) 1
127.0.0.1:6379> publish b11 m1
(integer) 1
订阅者订阅消息
127.0.0.1:6379> psubscribe b*
Reading messages... (press Ctrl-C to quit)
1) "psubscribe"
2) "b*"
3) (integer) 3
1) "pmessage"
2) "b*"
3) "b"
4) "m1"
1) "pmessage"
2) "b*"
3) "b1"
4) "m1"
1) "pmessage"
2) "b*"
3) "b11"
4) "m1"
数据结构
pattern属性是一个链表,链表中保存着所有和模式相关的信息。
struct redisServer {
// ...
list *pubsub_patterns;
// ...
};
// 链表中的每一个节点结构如下,保存着客户端与模式信息
typedef struct pubsubPattern {
redisClient *client;
robj *pattern;
} pubsubPattern;
数据结构图如下:
订阅
当有信的订阅时,会将订阅的客户端和模式信息添加到链表后面。
发布
当发布者发布消息时,首先会发送到对应的频道上,在遍历模式列表,根据key匹配模式,匹配成功将消息发给对应的订阅者。
完成的发布伪代码如下
def PUBLISH(channel, message):
# 遍历所有订阅频道 channel 的客户端
for client in server.pubsub_channels[channel]:
# 将信息发送给它们
send_message(client, message)
# 取出所有模式,以及订阅模式的客户端
for pattern, client in server.pubsub_patterns:
# 如果 channel 和模式匹配
if match(channel, pattern):
# 那么也将信息发给订阅这个模式的客户端
send_message(client, message)
退订
使用punsubscribe,可以将订阅者退订,将改客户端移除出链表。