Redis

redis事务

Redis中的事务(transaction)是一组命令的集合。事务同命令一样都是Redis的最小执行单元,一个事务中的命令要么都执行,要么都不执行。

使用MUUTI开始一个事务,然后把所有要在同一个事务中执行的命令都发送给 Redis后,我们使用EXEC命令告诉Redis将等待执行的事务队列中的所有命令(开始事务后,每一台输入命令都返回QUEUED的命令)按照发送顺序依次执行。命令在执行EXEC后才依次执行。
Redis保证一个事务中的所有命令要么都执行要么都不执行。如果在发送EXEC命令前客户端短线,则Redis会清空事务队列,事务中的命令都不会执行。
Redis事务保证在一个事务内的命令依次执行的过程中而不被其他命令插入

错误处理 - 语法错误
在事务中只要有一个命令有语法错误,执行EXEC后Redis就会直接返回错误,正确的语法也不会执行。
- 运行错误
运行错误指在命令的执行是出现的错误。 如果事务里的一条命令出现了语法错误,事务里其他的命令依然会继续执行。

Redis的事务没有关系型数据库提供的回滚(rollback)功能

WATCH监控命令
WATCH命令可以监控一个或者多个键,一旦其中有一个键被修改(或者被删除)之后的事务就不会被执行。监控一直EXEC执行前,也就是事务开始执行前。 所以在MULTI命令开始后可以修改WATCH监控的键值。
基于此可以实现+1的原子操作

WATCH $key
$value = GET $key
$value = $value + 1
MULTI 
set $key $value
EXEC

如果EXEC命令返回失败,则重新执行。

过期时间

Redis中可以使用EXPIRE命令设置一个键的过期时间,到期后Redis回自动删除它。
set key seconds

Redis缓存的实现
使用制redis键值的过期时间来定期淘汰不用的key,可能会导致占满内存,另一方面太短,就可能导致缓存命中率太低并且大量内存闲置。
可以限制redis能够使用的最大内存,并让Redis按照一定的规则淘汰不需要的缓存键,这种方式只讲Redis用作缓存键时非常有用。
修改配置文件的maxmemory参数,限制Redis的最大可用内存(单位字节),当超出这个限制时Redis会根据maxmemory-policy参数指定的策略来删除不需要的键直到Redis占用的内存小于指定内存。
maxmemory-policy支持的规则如表4-1所示。

规则 说明
volatile-lru 使用lru算法删除一个键(只对设置了过期时间的键)
allkeys-lru 使用lru算法删除一个键
volatile-random 随机删除一个键(只对设置过期时间的键)
allkeys-random 随机删除一个键
volatile-ttl 删除离过期时间最近的一个键
noeviction 不删除键,只返回错误

消息通知

任务队列
通知的过程可以使用任务队列来实现。与任务队列进行交互的实体有两类,一类是生产者(producer),另一类是消费者(consumer)。生产者会将需要处理的任务放入任务队列中,而消费者则不断地从任务队列中读入任务信息并执行。
任务队列的优点
- 松耦合 生产者与消费者无需直到彼此的实现细节,只需要约定好的任务的描述格式。 - 易于扩展
消费者可以有多个,而且可以分布在不同的服务器中。

要实现任务队列,只需要让生产者将任务使用LPUSH加入到某个键中,另一边让消费者不断地使用RPOP命令从该键中取出任务即可。

使用BRPOP代替RPOP BRPOP当列表中没有元素时,会一直阻塞,直到列表中有新元素加入。

持久化

Redis支持两种持久化方式,RDBAOF

RDB方式

RDB方式的持久化是通过快照(snapshotting)完成的,当符合一定的条件时Redis会自动地将内存中所有数据生成一份副本并存储在硬盘上,这个过程即为快照。
Redis会在以下情况下对数据进行快照:

快照过程 1. Redis使用fork函数复制一份当前进程(父进程)的副本(子进程)
2. 父进程继续接受并处理客户端发来的命令,而子进程开始将内存中的数据写入硬盘中的临时文件 3. 当子进程写完所有的数据后用该临时文件替换旧的RDB文件。 >在进行复制时操作系统会使用写时复制策略(copy-on-write), 即fork函数发生的一刻父子进程共享同一内存数据,当父进程要更改其中某片数据时,操作系统会将该片数据复制一份以保证子进程的数据不受影响,所以新的RDB文件存储的还是执行fork一刻的内存数据。

AOF方式

AOF是将Redis执行的每一条命令追加到硬盘文件中,这一过程会降低redis的性能

开启AOF

appendonly yes                  # 开启aof
appendfilename appendonly.aof   # aof文件名

优化aof文件,自动将内存中不存在的键值去掉

auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

数据大小为64mb后百分之百重写aof文件

同步硬盘数据
由于操作系统的缓存机制,每次执行的命令并没有真正的写入硬盘,而是进入了系统的硬盘缓存。在默认情况下30s执行一次同步,以便将硬盘缓存写入硬盘。但可以设置以使每次都可以写入内存

# appendfsync always
appendfsync everysec
# appendfsync no

默认为每秒执行一次写入操作 everysec

集群

主从复制

redis-server --port 6380 --slaveof 127.0.0.1 6379 原理
当从服务器启动后,会向主服务器发送SYNC命令。同时主数据库接收到SYNC命令后开始在后台保存快照(RDB过程),并将保存快照期间接收到的命令缓存起来。当完成快照后,Redis会将快照文件和所有缓存到的命令发送给从数据库。从数据库收到后载入快照文件然后执行收到的缓存命令。

Redis采用了乐观复制(optimistic replcation)的复制策略,容忍在一定时间内主从数据库是不同的,但是最终两者的数据会同步。

限制只有当数据同步给指定数量的从数据库时,主数据库才是采写的

max-slaves-to-write 3
max-slaves-max-log 10

最少3个从数据库连接,主数据库才是可写的 允许从数据库最长失去连接的时间为10s

哨兵

哨兵的作用就是监控Redis集群系统的运行情况。
功能 - 监控主数据库和从数据库是否正常运行 - 主数据库出现故障时自动将从数据库转换为主数据库

是一个独立的进程。 启动哨兵节点
配置文件
rentinel monitor mymaster 127.0.0.1 6379 1
1为法定票数
启动命令
redis-sentinel /etc/redis/sentinel.conf

哨兵节点启动后会与要监控的主数据库建立两条连接,这两个连接的建立方式与普通Redis客户端相同。 一条连接用来订阅主数据的__sentinel__:hello 频道以获取其他同样监控该数据库的哨兵节点的信息,另外哨兵也需要定期地向主数据库发动INFO命令来获取主数据库本身的信息。
和主数据库的连接建立完成后,哨兵会定期的执行下面3个操作。
- 每10s哨兵向主数据库和从数据库发送INFO命令 - 每2s哨兵会向主数据库和从数据库的__sentinel__:hello频道发送自已的信息 - 每1s哨兵会向主从数据库和其他哨兵节点发送PING命令

down-after-milliseconds 毫秒值小于1s,哨兵会每隔这么多s发送一个PING命令,大于1s则每一秒发送PING命令

哨兵的法定票数quorum设置为N/2+1(N为哨兵节点数量)

应用场景

缓存 string

消息队列 list BLPOP BRPOP

时间轴 list

排行榜 zset ZINCRBY ZREVRANGE ZRANGE

计数器 incr

分布式锁

Redis数据结构

字符串(简单动态字符串)

  /*  
 * 保存字符串对象的结构  
 */  
struct sdshdr {  

    // 用于记录buf中已经使用的空间长度
    int len;  
  
    // buf 中剩余可用空间的长度,初次分配空间一般没有空余,在对字符串修改的时候,会有剩余空间出现  
    int free;  
  
    // 字符数组,用于记录我们的字符串 
    char buf[];  
};

区别

|c字符串|SDS| |-|-| |获取字符串长度复杂度为O(n)|获取字符串长度复杂度为O(1)| |可能会缓冲溢出|不会发生溢出| |修改字符串长度N次必然需要执行N次内存分配|最多执行N次内存分配| |只可以保存内存数据|文本数据、二进制文件

链表

每一个链表节点可以使用listNode结构表示

typedef struct listNode{
      struct listNode *prev;
      struct listNode * next;
      void * value;  
}

typedef struct list{
    //表头节点
    listNode  * head;
    //表尾节点
    listNode  * tail;
    //链表长度
    unsigned long len;
    //节点值复制函数
    void *(*dup) (void *ptr);
    //节点值释放函数
    void (*free) (void *ptr);
    //节点值对比函数
    int (*match)(void *ptr, void *key);
}

特性 - 双端 - 无环 - 表头和表尾 - 长度计数器 - 多态

字典

Redis使用哈希表作为作为字典的底层实现,每个字典都有两个哈希表,一个正常使用,另一个用处是重新散列实现对哈希表的扩展或者压缩

使用链表来解决键冲突问题,被分配到同一个索引上的多个键值会构成一个单项链表

缓存穿透

查询一条数据库不存在的数据的数据时,也就是缓存和数据库都查询不到这条数据,但请求还是会打到数据库

解决方案

将不存在的key缓存起来,值设置为Null

缓存雪崩

大量的请求同时查询一个key时,这是key失效了,就会导致大量的请求打到数据库中,

在缓存中查询数据的时候使用互斥锁锁住它,但是降低了并发量

双缓存

redis 和 memcached有什么区别

redis的线程模型是什么?为什么单线程的Redis比多线程的memcached效率要高很多

redis的线程模型 redis 内部使用文件事件处理器,这个文件事件处理器是单线程的,所以redis是单线程的模型。采用IO多路复用机制同时监听多个socket,根据socket上的事件来选择对应的事件处理器进行处理

Reactor

IO多路复用模块会监听多个FD,当这些FD产生操作时,会向文件事件分发器传送事件,然后文件事件分发器在收到事件之后,会根据事件的类型将事件分发给对应的handler

内存操作
基于非阻塞的IO多路复用机制
单线程

redis 为什么是单线程的 - redis是基于内存的操作,cpu不是redis的瓶颈,只有可能是机器内存大小和网络宽带 并且单线程容易实现。不需要各种锁的性能消耗