diff --git a/数据库/Redis/01 Redis概述.md b/数据库/Redis/01 Redis概述.md index 3da3eb71..811179ab 100644 --- a/数据库/Redis/01 Redis概述.md +++ b/数据库/Redis/01 Redis概述.md @@ -9,7 +9,13 @@ Redis 是速度非常快的非关系型(NoSQL)内存键值数据库,可以 Redis 支持很多特性,例如将内存中的数据持久化到硬盘中,使用复制来扩展读性能,使用分片来扩展写性能。 +### 优势 +使用Redis有哪些好处? + +1. 速度快,因为数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1) +2. 支持丰富数据类型,支持string,list,set,sorted set,hash +3. 支持事务,操作都是原子性,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行(4) 丰富的特性:可用于缓存,消息,按key设置过期时间,过期后将会自动删除 ## 2 Redis 与 Memcached diff --git a/数据库/Redis/03 数据结构.md b/数据库/Redis/03 数据结构.md index 23494b90..394efd9b 100644 --- a/数据库/Redis/03 数据结构.md +++ b/数据库/Redis/03 数据结构.md @@ -1,7 +1,10 @@ # 底层数据结构 +> 参考文献 +> * [底层数据结构](https://www.cnblogs.com/ysocean/p/9080942.html#_label1) + - [底层数据结构](#底层数据结构) - - [1 简单动态字符串SDS](#1-简单动态字符串sds) + - [1 动态字符串SDS](#1-动态字符串sds) - [SDS 代码实现](#sds-代码实现) - [SDS 特点(与C字符串的不同)](#sds-特点与c字符串的不同) - [SDS API](#sds-api) @@ -13,7 +16,7 @@ - [字典的实现](#字典的实现) - [哈希算法](#哈希算法) - [解决键冲突](#解决键冲突) - - [rehash](#rehash) + - [Rehash](#rehash) - [哈希表的扩展与收缩](#哈希表的扩展与收缩) - [渐进式rehash](#渐进式rehash) - [字典API](#字典api) @@ -34,7 +37,7 @@ - [压缩列表API](#压缩列表api) -## 1 简单动态字符串SDS +## 1 动态字符串SDS Redis构建了 简单动态字符串(simple dynamic string,SDS)来表示字符串值。 SDS还被用作缓冲区:AOF缓冲区,客户端状态中的输入缓冲区。 @@ -283,7 +286,7 @@ Redis使用链地址法解决键冲突,每个哈希表节点都有个next指 ![collision](img/chap4/collision.png) -### rehash +### Rehash 随着操作的不断执行,哈希表保存的键值对会增加或减少。为了让哈希表的负载因子维持在合理范围,需要对哈希表的大小进行扩展或收缩,即通过执行rehash(重新散列)来完成: @@ -344,13 +347,19 @@ Redis使用跳跃表作为**有序集合键的底层实现**之一,如果有 在集群节点中,**跳跃表也被Redis用作内部数据结构**。 ### 跳表 代码实现 -![](image/2021-09-02-23-20-54.png) Redis的跳跃表由redis.h/zskiplistNode和redis.h/zskiplist两个结构定义,其中zskiplistNode代表跳跃表节点,zskiplist保存跳跃表节点的相关信息,比如节点数量、以及指向表头/表尾结点的指针等。 +![](image/2021-09-02-23-20-54.png) + +1. 由很多层结构组成; +2. 每一层都是一个有序的链表,排列顺序为由高层到底层,都至少包含两个链表节点,分别是前面的head节点和后面的nil节点; +3. 最底层的链表包含了所有的元素; +4. 如果一个元素出现在某一层的链表中,那么在该层之下的链表也全都会出现(上一层的元素是当前层的元素的子集); +5. 链表中的每个节点都包含两个指针,一个指向同一层的下一个链表节点,另一个指向下一层的同一个链表节点; -跳跃表是基于多指针有序链表实现的,可以看成多个有序链表。 ![skiplist](img/chap5/skiplist.png) +跳跃表是基于多指针有序链表实现的,可以看成多个有序链表。 ```c typedef struct zskiplist { diff --git a/数据库/Redis/07 AOF持久化.md b/数据库/Redis/07 AOF持久化.md index 5d109ac8..31601a32 100644 --- a/数据库/Redis/07 AOF持久化.md +++ b/数据库/Redis/07 AOF持久化.md @@ -52,3 +52,70 @@ Redis服务器采用单个线程来处理命令请求,所以将AOF重写程序 1. 将AOF重写缓冲区的内容写入新AOF文件。 2. 对新的AOF文件改名,覆盖现有的AOF文件。 + + +## 3 RDB和AOF区别 +* RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘,实际操作过程是fork一个子进程,先将数据集写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储。 +![](image/2021-04-07-23-24-02.png) + +* AOF持久化以日志的形式记录服务器所处理的每一个写、删除操作,查询操作不会记录,以文本的方式记录,可以打开文件看到详细的操作记录。 +![](image/2021-04-07-23-24-24.png) + +## 3 二者优缺点 +### RDB存在哪些优势呢? +1. 一旦采用该方式,那么你的整个Redis数据库将只包含一个文件,这对于文件备份而言是非常完美的。比如,你可能打算每个小时归档一次最近24小时的数据,同时还要每天归档一次最近30天的数据。通过这样的备份策略,一旦系统出现灾难性故障,我们可以非常容易的进行恢复。 + +2. 对于灾难恢复而言,RDB是非常不错的选择。因为我们可以非常轻松的将一个单独的文件压缩后再转移到其它存储介质上。 + +3. 性能最大化。对于Redis的服务进程而言,在开始持久化时,它唯一需要做的只是fork出子进程,之后再由子进程完成这些持久化的工作,这样就可以极大的避免服务进程执行IO操作了。 + +4. 相比于AOF机制,如果数据集很大,RDB的启动效率会更高。 + +### RDB又存在哪些劣势呢? + +1. 如果你想保证数据的高可用性,即最大限度的避免数据丢失,那么RDB将不是一个很好的选择。因为系统一旦在定时持久化之前出现宕机现象,此前没有来得及写入磁盘的数据都将丢失。 + +2. 由于RDB是通过fork子进程来协助完成数据持久化工作的,因此,如果当数据集较大时,可能会导致整个服务器停止服务几百毫秒,甚至是1秒钟。 + +### AOF的优势有哪些呢? +1. 该机制可以带来更高的数据安全性,即数据持久性。Redis中提供了3中同步策略,即每秒同步、每修改同步和不同步。事实上,每秒同步也是异步完成的,其效率也是非常高的,所差的是一旦系统出现宕机现象,那么这一秒钟之内修改的数据将会丢失。而每修改同步,我们可以将其视为同步持久化,即每次发生的数据变化都会被立即记录到磁盘中。可以预见,这种方式在效率上是最低的。至于无同步,无需多言,我想大家都能正确的理解它。 + +2. 由于该机制对日志文件的写入操作采用的是append模式,因此在写入过程中即使出现宕机现象,也不会破坏日志文件中已经存在的内容。然而如果我们本次操作只是写入了一半数据就出现了系统崩溃问题,不用担心,在Redis下一次启动之前,我们可以通过redis-check-aof工具来帮助我们解决数据一致性的问题。 + +3. 如果日志过大,Redis可以自动启用rewrite机制。即Redis以append模式不断的将修改数据写入到老的磁盘文件中,同时Redis还会创建一个新的文件用于记录此期间有哪些修改命令被执行。因此在进行rewrite切换时可以更好的保证数据安全性。 + +4. AOF包含一个格式清晰、易于理解的日志文件用于记录所有的修改操作。事实上,我们也可以通过该文件完成数据的重建。 + +### AOF的劣势有哪些呢? + +1. 对于相同数量的数据集而言,AOF文件通常要大于RDB文件。RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快。 + +2. 根据同步策略的不同,AOF在运行效率上往往会慢于RDB。总之,每秒同步策略的效率是比较高的,同步禁用策略的效率和RDB一样高效。 + +### 二者选择的标准 + +就是看系统是愿意牺牲一些性能,换取更高的缓存一致性(aof),还是愿意写操作频繁的时候,不启用备份来换取更高的性能,待手动运行save的时候,再做备份(rdb)。rdb这个就更有些 eventually consistent的意思了。 + +## 4 常用配置 +### RDB持久化配置 +* Redis会将数据集的快照dump到dump.rdb文件中。此外,我们也可以通过配置文件来修改Redis服务器dump快照的频率,在打开6379.conf文件之后,我们搜索save,可以看到下面的配置信息: + +``` +save 900 1 #在900秒(15分钟)之后,如果至少有1个key发生变化,则dump内存快照。 + +save 300 10 #在300秒(5分钟)之后,如果至少有10个key发生变化,则dump内存快照。 + +save 60 10000 #在60秒(1分钟)之后,如果至少有10000个key发生变化,则dump内存快照 +``` + +### AOF持久化配置 +* 在Redis的配置文件中存在三种同步方式,它们分别是: +``` +appendfsync always #每次有数据修改发生时都会写入AOF文件。 + +appendfsync everysec #每秒钟同步一次,该策略为AOF的缺省策略。 + +appendfsync no #从不同步。高效但是数据不会被持久化。 +``` + + \ No newline at end of file diff --git a/数据库/Redis/08 事务.md b/数据库/Redis/08 事务.md index 0d3f9561..8ec83c29 100644 --- a/数据库/Redis/08 事务.md +++ b/数据库/Redis/08 事务.md @@ -116,6 +116,48 @@ typedef struct redisDb { 服务器收到`EXEC`命令后,根据这个客户端是否打开了`REDIS_DIRTY_CAS`标识来决定是否执行事务。 +### 悲观锁与乐观锁 + +我正在买票`ticket -1 , money -100`而票只有1张, 如果在我multi之后,和exec之前, 票被别人买了,即ticket变成0了.我该如何观察这种情景,并不再提交 + +悲观的想法: + + 世界充满危险,肯定有人和我抢, 给 ticket上锁, 只有我能操作. [悲观锁] + +乐观的想法: + + 没有那么人和我抢,因此,我只需要注意, + --有没有人更改ticket的值就可以了 [乐观锁] + +Redis的事务中,启用的是乐观锁,只负责监测key没有被改动 + +```sh + +具体的命令---- watch命令 + +redis 127.0.0.1:6379> watch ticket +OK +redis 127.0.0.1:6379> multi +OK +redis 127.0.0.1:6379> decr ticket +QUEUED +redis 127.0.0.1:6379> decrby money 100 +QUEUED +redis 127.0.0.1:6379> exec +(nil) // 返回nil,说明监视的ticket已经改变了,事务就取消了. +redis 127.0.0.1:6379> get ticket +"0" +redis 127.0.0.1:6379> get money +"200" + +watch key1 key2 ... keyN +作用:监听key1 key2..keyN有没有变化,如果有变, 则事务取消 + +unwatch +作用: 取消所有watch监听 + +``` + ## 3 事务的ACID性质 Redis的事务总是具有原子性(atomicity)、一致性(consistency)、隔离性(isolation),且当Redis运行在某种特定的持久化模式下,事务也具有耐久性(durability)。 diff --git a/数据库/Redis/10 复制.md b/数据库/Redis/10 复制.md index 30ea5bdc..ef19baf2 100644 --- a/数据库/Redis/10 复制.md +++ b/数据库/Redis/10 复制.md @@ -158,4 +158,68 @@ typdef struct redisClient { 随着负载不断上升,主服务器可能无法很快地更新所有从服务器,或者重新连接和重新同步从服务器将导致系统超载。为了解决这个问题,可以创建一个中间层来分担主服务器的复制工作。中间层的服务器是最上层服务器的从服务器,又是最下层服务器的主服务器。 -![](image/2021-09-03-00-42-41.png) \ No newline at end of file +![](image/2021-09-03-00-42-41.png) + + +## 5 docker部署主从服务器 + +> 参考文献 +> * [docker实现redis主从](https://github.com/OMGZui/redis_m_s) + +### 命令行模式 + +```bash +# 拉取redis +docker pull redis + +# 主 +docker run -v $(pwd)/master/redis.conf:/usr/local/etc/redis/redis.conf --name redis-master redis redis-server /usr/local/etc/redis/redis.conf + +# 从1 --link redis-master:master master是别名 +docker run -v $(pwd)/slave1/redis.conf:/usr/local/etc/redis/redis.conf --name redis-slave1 --link redis-master:master redis redis-server /usr/local/etc/redis/redis.conf + +# 从2 +docker run -v $(pwd)/slave2/redis.conf:/usr/local/etc/redis/redis.conf --name redis-slave2 --link redis-master:master redis redis-server /usr/local/etc/redis/redis.conf + +``` + +### docker-compose模式 推荐 + +```bash +# 拉取redis +docker pull redis + +# 目录 +├── docker-compose.yml +├── master +│   ├── Dockerfile +│   └── redis.conf +├── redis.conf +├── slave1 +│   ├── Dockerfile +│   └── redis.conf +└── slave2 + ├── Dockerfile + └── redis.conf + +# 启动 +docker-compose up -d master slave1 slave2 + +# 查看主容器 +docker-compose exec master bash +root@cab5db8d544b:/data# redis-cli +127.0.0.1:6379> info Replication +# Replication +role:master +connected_slaves:2 +slave0:ip=172.23.0.3,port=6379,state=online,offset=1043,lag=0 +slave1:ip=172.23.0.4,port=6379,state=online,offset=1043,lag=0 +master_replid:995257c6b5ac62f7908cc2c7bb770f2f17b60401 +master_replid2:0000000000000000000000000000000000000000 +master_repl_offset:1043 +second_repl_offset:-1 +repl_backlog_active:1 +repl_backlog_size:1048576 +repl_backlog_first_byte_offset:1 +repl_backlog_histlen:1043 +``` \ No newline at end of file diff --git a/数据库/Redis/15 Redis使用场景.md b/数据库/Redis/14 Redis使用场景.md similarity index 100% rename from 数据库/Redis/15 Redis使用场景.md rename to 数据库/Redis/14 Redis使用场景.md diff --git a/数据库/Redis/15 Redis缓存.md b/数据库/Redis/15 Redis缓存.md new file mode 100644 index 00000000..0f848954 --- /dev/null +++ b/数据库/Redis/15 Redis缓存.md @@ -0,0 +1,58 @@ + + +## 1 缓存使用 + +### 缓存预热 + +缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统。这样就可以避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据! + +解决方法: +1、直接写个缓存刷新页面,上线时手工操作下 +2、数据量不大,可以在项目启动的时候自动进行加载 +3、定时刷新缓存 + + + +### 缓存降级 + +当访问量剧增、服务出现问题(如响应时间慢或不响应)或非核心服务影响到核心流程的性能时,仍然需要保证服务还是可用的,即使是有损服务。系统可以根据一些关键数据进行自动降级,也可以配置开关实现人工降级。 + +降级的最终目的是保证核心服务可用,即使是有损的。而且有些服务是无法降级的(如加入购物车、结算)。 + +在进行降级之前要对系统进行梳理,看看系统是不是可以丢卒保帅;从而梳理出哪些必须誓死保护,哪些可降级 + + +## 2 Redis作为缓存可能会出现的问题 + + +### 缓存雪崩 + +问题:当我们的缓存失效或者redis挂了,那么这个时候的请求都会直接走数据库,就会给数据库造成极大的压力,导致数据库也挂了 + +解决: + +1. 对缓存设置不同的过期时间,这样就不会导致缓存同时失效 +2. 建立redis集群,保证服务的可靠性 + +### 缓存穿透 + +问题:当有大量用户不走我们设置的键值,就会直接走数据库,就会给数据库造成极大的压力,导致数据库也挂了 + +解决: + +1. 参数过滤和提醒,引导用户走我们的设置的键值 +2. 对不合法的参数进行空对象缓存,并设置较短的过期时间 + +### 缓存与数据库读写一致 + +问题:如果一直是读的话,是没问题的,但是更新操作会导致数据库已经更新了,缓存还是旧的数据 + +解决: + +并发下解决数据库与缓存不一致的思路:将删除缓存、修改数据库、读取缓存等的操作积压到队列里边,实现串行化。 + +- 先删除缓存,再更新数据库。在高并发下表现不如意,在原子性被破坏时表现优异 + +- 先更新数据库,再删除缓存(Cache Aside Pattern设计模式)。在高并发下表现优异,在原子性被破坏时表现不如意 + + diff --git a/数据库/Redis/附录1 Redis笔记.md b/数据库/Redis/附录1 Redis笔记.md deleted file mode 100644 index 47a7cf78..00000000 --- a/数据库/Redis/附录1 Redis笔记.md +++ /dev/null @@ -1,795 +0,0 @@ -# redis笔记 - - - -- [redis笔记](#redis笔记) - - [零、redis是什么](#零redis是什么) - - [一、redis与memcached比较](#一redis与memcached比较) - - [二、安装](#二安装) - - [三、配置](#三配置) - - [四、通用key操作](#四通用key操作) - - [五、redis中的5中数据结构](#五redis中的5中数据结构) - - [1. 字符串(string)](#1-字符串string) - - [2. 列表(list)链表支持 有序 可重复](#2-列表list链表支持-有序-可重复) - - [3. 集合(set)无序 不可重复](#3-集合set无序-不可重复) - - [4. 哈希(hash)键值对 key => value](#4-哈希hash键值对--key--value) - - [5. 有序集合(zset)键值对 成员 => 分值 成员必须唯一](#5-有序集合zset键值对--成员--分值-成员必须唯一) - - [六、Redis详细数据类型](#六redis详细数据类型) - - [1、string 字符串](#1string-字符串) - - [2、list 列表](#2list-列表) - - [3、set 集合](#3set-集合) - - [4、sorted set 有序集合](#4sorted-set-有序集合) - - [5、hash 哈希](#5hash-哈希) - - [6、bitmap 位图](#6bitmap-位图) - - [7、geo 地理位置类型](#7geo 地理位置类型) - - [8、hyperLogLog 基数统计](#8hyperloglog-基数统计) - - [七、redis事务](#七redis事务) - - [1、mysql事务与redis事务比较](#1mysql事务与redis事务比较) - - [2、悲观锁与乐观锁](#2悲观锁与乐观锁) - - [八、发布订阅](#八发布订阅) - - [九、持久化](#九持久化) - - [1、redis 快照rdb](#1redis-快照rdb) - - [2、redis 日志aof](#2redis-日志aof) - - [十、redis主从复制](#十redis主从复制) - - [十一、redis表设计](#十一redis表设计) - - [十二、面试](#十二面试) - - [1、缓存雪崩](#1缓存雪崩) - - [2、缓存穿透](#2缓存穿透) - - [3、缓存与数据库读写一致](#3缓存与数据库读写一致) - - [十三、docker实现redis主从](#十三docker实现redis主从) - - [1、命令行模式](#1命令行模式) - - [2、docker-compose模式 推荐](#2docker-compose模式-推荐) - - [十四、参考资料](#十四参考资料) - - - -## 零、redis是什么 - -redis是什么,是一种非关系型数据库,统称nosql。 - -## 一、redis与memcached比较 - -- 1、redis受益于“持久化”可以做存储(storge),memcached只能做缓存(cache) -- 2、redis有多种数据结构,memcached只有一种类型`字符串(string)` - -## 二、安装 - -安装最新稳定版 - -```sh -# 源码安装redis-4.0 -# 下载 -wget http://download.redis.io/releases/redis-4.0.1.tar.gz -# 解压 -tar zxvf redis-4.0.1.tar.gz -cd redis-4.0.1 -# 编译 -make && make install -/usr/local/bin/redis-server -v -``` - -## 三、配置 - -- redis-benchmark redis性能测试工具 -- redis-check-aof 检查aof日志的工具 -- redis-check-rdb 检查rdb日志的工具 -- redis-cli 连接用的客户端 -- redis-server 服务进程 - -```sh -# 地址 -bind 0.0.0.0 - -# 保护模式 -protected-mode no - -# 端口 -port 6380 - -tcp-backlog 511 -timeout 0 -tcp-keepalive 300 - -# 守护进程模式 -daemonize yes - -supervised no - -# 进程id文件 -pidfile /usr/local/redis/run/redis.pid - -# 日志等级 -loglevel notice - -# 日志位置 -logfile /usr/local/redis/logs/redis.log - -# 数据个数 -databases 16 - -always-show-logo yes - -# after 900 sec (15 min) if at least 1 key changed -# after 300 sec (5 min) if at least 10 keys changed -# after 60 sec if at least 10000 keys changed -save 900 1 -save 300 10 -save 60 10000 - -stop-writes-on-bgsave-error yes - -# rdb开启 -rdbcompression yes -rdbchecksum yes -dbfilename dump.rdb -rdb-del-sync-files no -dir ./ - -# 主从 -# +------------------+ +---------------+ -# | Master | ---> | Replica | -# | (receive writes) | | (exact copy) | -# +------------------+ +---------------+ - -acllog-max-len 128 -# 密码 -requirepass omgzui -lazyfree-lazy-eviction no -lazyfree-lazy-expire no -lazyfree-lazy-server-del no -replica-lazy-flush no -lazyfree-lazy-user-del no -oom-score-adj no -oom-score-adj-values 0 200 800 - -# aof -appendonly yes -appendfilename "appendonly.aof" -# appendfsync always -appendfsync everysec -# appendfsync no -no-appendfsync-on-rewrite no -auto-aof-rewrite-percentage 100 -auto-aof-rewrite-min-size 64mb -aof-load-truncated yes -aof-use-rdb-preamble yes - -lua-time-limit 5000 - -# 从服务器 -# cluster-announce-ip 10.1.1.5 -# cluster-announce-port 6379 -# cluster-announce-bus-port 6380 - -slowlog-log-slower-than 10000 -slowlog-max-len 128 -latency-monitor-threshold 0 -notify-keyspace-events "" -hash-max-ziplist-entries 512 -hash-max-ziplist-value 64 -list-max-ziplist-size -2 -list-compress-depth 0 -set-max-intset-entries 512 -zset-max-ziplist-entries 128 -zset-max-ziplist-value 64 -hll-sparse-max-bytes 3000 -stream-node-max-bytes 4096 -stream-node-max-entries 100 -activerehashing yes -client-output-buffer-limit normal 0 0 0 -client-output-buffer-limit replica 256mb 64mb 60 -client-output-buffer-limit pubsub 32mb 8mb 60 -hz 10 -dynamic-hz yes -aof-rewrite-incremental-fsync yes -rdb-save-incremental-fsync yes -jemalloc-bg-thread yes - - -``` - -## 四、通用key操作 - -1. keys 查询 - -```sh -在redis里,允许模糊查询key -有3个通配符 - ? [] --: 通配任意多个字符 -?: 通配单个字符 -[]: 通配括号内的某1个字符 - -``` - -1. keys 查询 -2. del 删除 -3. rename 重命名 -4. move 移到另外一个库 -5. randomkey 随机 -6. exists 存在 -7. type 类型 -8. ttl 剩余生命周期 -9. expire 设置生命周期 -10. persist 永久有效 -11. flushdb 清空 - -## 五、redis中的5中数据结构 - -### 1. 字符串(string) - -- set - - set name shengj -> OK -- get - - get name -> "shengj" -- del - - del name -> (integer) 1 - - get name -> (nil) -- mset - - mset name shengj age 23 sex male -> OK -- mget - - mget age sex - - ```sh - 1) "23" - 2) "male" - ``` - -- setrange - - setrange sex 2 1 将sex的第3个字符改成1 -> (integer) 4 - - get sex -> "ma1e" -- append - - append name GG -> (integer) 8 - - get name -> "shengjGG" -- getrange - - getrange name 1 2 -> "he" -- incr 自增 -- incrby 自增一个量级 -- incrbyfloat 自增一个浮点数 -- decr 递减 -- decrby 递减一个量级 -- decrbyfloat 递减一个浮点数 -- setbit 设置二进制位数 -- getbit 获取二进制表示 -- bitop 位操作 - ---- - -### 2. 列表(list)链表支持 有序 可重复 - -- rpush 右边插入 - - rpush list item1 -> (integer) 1 - - rpush list item2 -> (integer) 2 - - rpush list item3 -> (integer) 3 -- lrange 列出链表值 - - lrange list 0 -1 - - ```sh - 1) "item1" - 2) "item2" - 3) "item3" - ``` - -- lindex - - lindex list 1 -> "item2" -- lpop - - lpop list -> "item1" - - lrange list 0 -1 - - ```sh - 1) "item2" - 2) "item3" - ``` - -- ltrim - - ltrim list 3 0 -> OK - - lrange list 0 -1 -> (empty list or set) -- lpush 左边插入 -- rpop 右边删除 -- lrem - ---- - -### 3. 集合(set)无序 不可重复 - -- sadd 增加 - - sadd set item1 -> (integer) 1 - - sadd set item2 -> (integer) 1 - - sadd set item3 -> (integer) 1 - - sadd set item1 -> (integer) 0 已存在 -- smembers 所有集合元素 - - smembers set - - ```sh - 1) "item3" - 2) "item2" - 3) "item1" - ``` - -- sismember 存不存在 - - sismember set item1 -> (integer) 1 - - sismember set item -> (integer) 0 不存在 -- srem 移除元素 - - srem set item1 -> (integer) 1 - - smembers set - - ```sh - 1) "item3" - 2) "item2" - ``` - -- spop 随机删除一个元素 -- srandmember 随机获取一个元素 -> 抽奖 -- scard 多少个元素 -- smove 移动 -- sinter 交集 -- sinterstore 交集并赋值 -- suion 并集 -- sdiff 差集 - ---- - -### 4. 哈希(hash)键值对 key => value - -- hset 设置一个 - - hset hash key1 value1 -> (integer) 1 - - hset hash key2 value2 -> (integer) 1 - - hset hash key3 value3 -> (integer) 1 - - hset hash key1 value1 -> (integer) 0 已存在 -- hgetall 获取全部 - - hgetall hash - - ```sh - 1) "key1" - 2) "value1" - 3) "key2" - 4) "value2" - 5) "key3" - 6) "value3" - ``` - -- hget 获取一个 - - hget hash key1 -> "value1" -- hdel 删除 - - hdel hash key1 -> (integer) 1 - - hgetall hash - - ```sh - 1) "key2" - 2) "value2" - 3) "key3" - 4) "value3" - ``` - -- hmset 设置多个 -- hmget 获取多个 -- hlen 个数 -- hexists 是否存在增长 -- hinrby 增长 -- hkeys 所有的key -- hvals 所有的值 - ---- - -### 5. 有序集合(zset)键值对 成员 => 分值 成员必须唯一 - -- zadd 增加 - - zadd zset 100 item1 -> (integer) 1 - - zadd zset 200 item2 -> (integer) 1 - - zadd zset 300 item3 -> (integer) 1 - - zadd zset 100 item1 -> (integer) 0 已存在 -- zrange 按分值排序 - - zrange zset 0 -1 withscores - - ```sh - 1) "item1" - 2) "100" - 3) "item2" - 4) "200" - 5) "item3" - 6) "300" - ``` - -- zrangebyscore 按分值的一部分排序 - - zrangebyscore zset 0 200 withscores - - ```sh - 1) "item1" - 2) "100" - 3) "item2" - 4) "200" - ``` - -- zrem 删除 - - zrem zset item1 -> (integer) 1 - - zrange zset 0 -1 withscores - - ```sh - 1) "item2" - 2) "200" - 3) "item3" - 4) "300" - ``` - -- zrank 排名升序 -- zremrangebyscore 按分值删除一部分 -- zremrangebyrank 按排名删除一部分 -- zcard 个数 - -## 六、Redis详细数据类型 - -首先,来看一下 Redis 的核心数据类型。Redis 有 8 种核心数据类型,分别是 : - -- string 字符串类型; -- list 列表类型; -- set 集合类型; -- sorted set 有序集合类型; -- hash 类型; -- bitmap 位图类型; -- geo 地理位置类型; -- HyperLogLog 基数统计类型。 - -### 1、string 字符串 - - string 是 Redis 的最基本数据类型。可以把它理解为 Mc 中 key 对应的 value 类型。string 类型是二进制安全的,即 string 中可以包含任何数据。 - Redis 中的普通 string 采用 raw encoding 即原始编码方式,该编码方式会动态扩容,并通过提前预分配冗余空间,来减少内存频繁分配的开销。 - 在字符串长度小于 1MB 时,按所需长度的 2 倍来分配,超过 1MB,则按照每次额外增加 1MB 的容量来预分配。 - Redis 中的数字也存为 string 类型,但编码方式跟普通 string 不同,数字采用整型编码,字符串内容直接设为整数值的二进制字节序列。 - 在存储普通字符串,序列化对象,以及计数器等场景时,都可以使用 Redis 的字符串类型,字符串数据类型对应使用的指令包括 set、get、mset、incr、decr 等。 - -### 2、list 列表 - - Redis 的 list 列表,是一个快速双向链表,存储了一系列的 string 类型的字串值。list 中的元素按照插入顺序排列。插入元素的方式,可以通过 lpush 将一个或多个元素插入到列表的头部,也可以通过 rpush 将一个或多个元素插入到队列尾部,还可以通过 lset、linsert 将元素插入到指定位置或指定元素的前后。 - list 列表的获取,可以通过 lpop、rpop 从对头或队尾弹出元素,如果队列为空,则返回 nil。还可以通过 Blpop、Brpop 从队头/队尾阻塞式弹出元素,如果 list 列表为空,没有元素可供弹出,则持续阻塞,直到有其他 client 插入新的元素。这里阻塞弹出元素,可以设置过期时间,避免无限期等待。最后,list 列表还可以通过 LrangeR 获取队列内指定范围内的所有元素。Redis 中,list 列表的偏移位置都是基于 0 的下标,即列表第一个元素的下标是 0,第二个是 1。偏移量也可以是负数,倒数第一个是 -1,倒数第二个是 -2,依次类推。 - list 列表,对于常规的 pop、push 元素,性能很高,时间复杂度为 O(1),因为是列表直接追加或弹出。但对于通过随机插入、随机删除,以及随机范围获取,需要轮询列表确定位置,性能就比较低下了。 - feed timeline 存储时,由于 feed id 一般是递增的,可以直接存为 list,用户发表新 feed,就直接追加到队尾。另外消息队列、热门 feed 等业务场景,都可以使用 list 数据结构。 - 操作 list 列表时,可以用 lpush、lpop、rpush、rpop、lrange 来进行常规的队列进出及范围获取操作,在某些特殊场景下,也可以用 lset、linsert 进行随机插入操作,用 lrem 进行指定元素删除操作;最后,在消息列表的消费时,还可以用 Blpop、Brpop 进行阻塞式获取,从而在列表暂时没有元素时,可以安静的等待新元素的插入,而不需要额外持续的查询。 - -### 3、set 集合 - - set 是 string 类型的无序集合,set 中的元素是唯一的,即 set 中不会出现重复的元素。Redis 中的集合一般是通过 dict 哈希表实现的,所以插入、删除,以及查询元素,可以根据元素 hash 值直接定位,时间复杂度为 O(1)。 - 对 set 类型数据的操作,除了常规的添加、删除、查找元素外,还可以用以下指令对 set 进行操作。 - sismember 指令判断该 key 对应的 set 数据结构中,是否存在某个元素,如果存在返回 1,否则返回 0; - sdiff 指令来对多个 set 集合执行差集; - sinter 指令对多个集合执行交集; - sunion 指令对多个集合执行并集; - spop 指令弹出一个随机元素; - srandmember 指令返回一个或多个随机元素。 - set 集合的特点是查找、插入、删除特别高效,时间复杂度为 O(1),所以在社交系统中,可以用于存储关注的好友列表,用来判断是否关注,还可以用来做好友推荐使用。另外,还可以利用 set 的唯一性,来对服务的来源业务、来源 IP 进行精确统计。 - -### 4、sorted set 有序集合 - - Redis 中的 sorted set 有序集合也称为 zset,有序集合同 set 集合类似,也是 string 类型元素的集合,且所有元素不允许重复。 - 但有序集合中,每个元素都会关联一个 double 类型的 score 分数值。有序集合通过这个 score 值进行由小到大的排序。有序集合中,元素不允许重复,但 score 分数值却允许重复。 - 有序集合除了常规的添加、删除、查找元素外,还可以通过以下指令对 sorted set 进行操作。 - zscan 指令:按顺序获取有序集合中的元素; - zscore 指令:获取元素的 score 值; - zrange指令:通过指定 score 返回指定 score 范围内的元素; - 在某个元素的 score 值发生变更时,还可以通过 zincrby 指令对该元素的 score 值进行加减。 - 通过 zinterstore、zunionstore 指令对多个有序集合进行取交集和并集,然后将新的有序集合存到一个新的 key 中,如果有重复元素,重复元素的 score 进行相加,然后作为新集合中该元素的 score 值。 - sorted set 有序集合的特点是: - 所有元素按 score 排序,而且不重复; - 查找、插入、删除非常高效,时间复杂度为 O(1)。 - 因此,可以用有序集合来统计排行榜,实时刷新榜单,还可以用来记录学生成绩,从而轻松获取某个成绩范围内的学生名单,还可以用来对系统统计增加权重值,从而在 dashboard 实时展示。 - -### 5、hash 哈希 - - Redis 中的哈希实际是 field 和 value 的一个映射表。 - hash 数据结构的特点是在单个 key 对应的哈希结构内部,可以记录多个键值对,即 field 和 value 对,value 可以是任何字符串。而且这些键值对查询和修改很高效。 - 所以可以用 hash 来存储具有多个元素的复杂对象,然后分别修改或获取这些元素。hash 结构中的一些重要指令,包括:hmset、hmget、hexists、hgetall、hincrby 等。 - hmset 指令批量插入多个 field、value 映射; - hmget 指令获取多个 field 对应的 value 值; - hexists 指令判断某个 field 是否存在; - 如果 field 对应的 value 是整数,还可以用 hincrby 来对该 value 进行修改。 - -### 6、bitmap 位图 - - Redis 中的 bitmap 位图是一串连续的二进制数字,底层实际是基于 string 进行封装存储的,按 bit 位进行指令操作的。bitmap 中每一 bit 位所在的位置就是 offset 偏移,可以用 setbit、bitfield 对 bitmap 中每个 bit 进行置 0 或置 1 操作,也可以用 bitcount 来统计 bitmap 中的被置 1 的 bit 数,还可以用 bitop 来对多个 bitmap 进行求与、或、异或等操作。 - bitmap 位图的特点是按位设置、求与、求或等操作很高效,而且存储成本非常低,用来存对象标签属性的话,一个 bit 即可存一个标签。可以用 bitmap,存用户最近 N 天的登录情况,每天用 1 bit,登录则置 1。个性推荐在社交应用中非常重要,可以对新闻、feed 设置一系列标签,如军事、娱乐、视频、图片、文字等,用 bitmap 来存储这些标签,在对应标签 bit 位上置 1。对用户,也可以采用类似方式,记录用户的多种属性,并可以很方便的根据标签来进行多维度统计。bitmap 位图的重要指令包括:setbit、 getbit、bitcount、bitfield、 bitop、bitpos 等。 - -### 7、geo 地理位置类型 - - 在移动社交时代,LBS 应用越来越多,比如微信、陌陌中附近的人,美团、大众点评中附近的美食、电影院,滴滴、优步中附近的专车等。要实现这些功能,就得使用地理位置信息进行搜索。地球的地理位置是使用二维的经纬度进行表示的,我们只要确定一个点的经纬度,就可以确认它在地球的位置。 - Redis 在 3.2 版本之后增加了对 GEO 地理位置的处理功能。Redis 的 GEO 地理位置本质上是基于 sorted set 封装实现的。在存储分类 key 下的地理位置信息时,需要对该分类 key 构建一个 sorted set 作为内部存储结构,用于存储一系列位置点。 - 在存储某个位置点时,首先利用 Geohash 算法,将该位置二维的经纬度,映射编码成一维的 52 位整数值,将位置名称、经纬度编码 score 作为键值对,存储到分类 key 对应的 sorted set 中。 - 需要计算某个位置点 A 附近的人时,首先以指定位置 A 为中心点,以距离作为半径,算出 GEO 哈希 8 个方位的范围, 然后依次轮询方位范围内的所有位置点,只要这些位置点到中心位置 A 的距离在要求距离范围内,就是目标位置点。轮询完所有范围内的位置点后,重新排序即得到位置点 A 附近的所有目标。 - 使用 geoadd,将位置名称(如人、车辆、店名)与对应的地理位置信息添加到指定的位置分类 key 中; - 使用 geopos 方便地查询某个名称所在的位置信息; - 使用 georadius 获取指定位置附近,不超过指定距离的所有元素; - 使用 geodist 来获取指定的两个位置之间的距离。 - 这样,是不是就可以实现,找到附近的餐厅,算出当前位置到对应餐厅的距离,这样的功能了? - Redis GEO 地理位置,利用 Geohash 将大量的二维经纬度转一维的整数值,这样可以方便的对地理位置进行查询、距离测量、范围搜索。但由于地理位置点非常多,一个地理分类 key 下可能会有大量元素,在 GEO 设计时,需要提前进行规划,避免单 key 过度膨胀。 - Redis 的 GEO 地理位置数据结构,应用场景很多,比如查询某个地方的具体位置,查当前位置到目的地的距离,查附近的人、餐厅、电影院等。GEO 地理位置数据结构中,重要指令包括 geoadd、geopos、geodist、georadius、georadiusbymember 等。 - -### 8、hyperLogLog 基数统计 - - Redis 的 hyperLogLog 是用来做基数统计的数据类型,当输入巨大数量的元素做统计时,只需要很小的内存即可完成。HyperLogLog 不保存元数据,只记录待统计元素的估算数量,这个估算数量是一个带有 0.81% 标准差的近似值,在大多数业务场景,对海量数据,不足 1% 的误差是可以接受的。 - Redis 的 HyperLogLog 在统计时,如果计数数量不大,采用稀疏矩阵存储,随着计数的增加,稀疏矩阵占用的空间也会逐渐增加,当超过阀值后,则改为稠密矩阵,稠密矩阵占用的空间是固定的,约为12KB字节。 - 通过 hyperLoglog 数据类型,你可以利用 pfadd 向基数统计中增加新的元素,可以用 pfcount 获得 hyperLogLog 结构中存储的近似基数数量,还可以用 hypermerge 将多个 hyperLogLog 合并为一个 hyperLogLog 结构,从而可以方便的获取合并后的基数数量。 - hyperLogLog 的特点是统计过程不记录独立元素,占用内存非常少,非常适合统计海量数据。在大中型系统中,统计每日、每月的 UV 即独立访客数,或者统计海量用户搜索的独立词条数,都可以用 hyperLogLog 数据类型来进行处理。 - -## 七、redis事务 - -### 1、mysql事务与redis事务比较 - -|比较|mysql|redis| -|---|---|---| -|开启|start transaction|multi| -|语句|普通sql语句|普通redis命令| -|失败|rollback|discard| -|成功|commit|exec| - -如果已经成功执行了2条语句, 第3条语句出错. - -rollback后,前2条的语句影响消失. - -discard只是结束本次事务,前2条语句造成的影响仍然还在 - -### 2、悲观锁与乐观锁 - -我正在买票`ticket -1 , money -100`而票只有1张, 如果在我multi之后,和exec之前, 票被别人买了,即ticket变成0了.我该如何观察这种情景,并不再提交 - -悲观的想法: - - 世界充满危险,肯定有人和我抢, 给 ticket上锁, 只有我能操作. [悲观锁] - -乐观的想法: - - 没有那么人和我抢,因此,我只需要注意, - --有没有人更改ticket的值就可以了 [乐观锁] - -Redis的事务中,启用的是乐观锁,只负责监测key没有被改动 - -```sh - -具体的命令---- watch命令 - -redis 127.0.0.1:6379> watch ticket -OK -redis 127.0.0.1:6379> multi -OK -redis 127.0.0.1:6379> decr ticket -QUEUED -redis 127.0.0.1:6379> decrby money 100 -QUEUED -redis 127.0.0.1:6379> exec -(nil) // 返回nil,说明监视的ticket已经改变了,事务就取消了. -redis 127.0.0.1:6379> get ticket -"0" -redis 127.0.0.1:6379> get money -"200" - -watch key1 key2 ... keyN -作用:监听key1 key2..keyN有没有变化,如果有变, 则事务取消 - -unwatch -作用: 取消所有watch监听 - -``` - -## 八、发布订阅 - -订阅端: subscribe 频道名称 - -发布端: publish 频道名称 发布内容 - -## 九、持久化 - -### 1、redis 快照rdb - -有限制,还是容易数据丢失,恢复快 - -```sh - -save 900 1 # 900内,有1条写入,则产生快照 -save 300 1000 # 如果300秒内有1000次写入,则产生快照 -save 60 10000 # 如果60秒内有10000次写入,则产生快照 -(这3个选项都屏蔽,则rdb禁用) - -stop-writes-on-bgsave-error yes # 后台备份进程出错时,主进程停不停止写入? -rdbcompression yes # 导出的rdb文件是否压缩 -Rdbchecksum yes # 导入rbd恢复时数据时,要不要检验rdb的完整性 -dbfilename dump.rdb # 导出来的rdb文件名 -dir ./ //rdb的放置路径 - -``` - -### 2、redis 日志aof - -```sh - -appendonly no # 是否打开 aof日志功能 - -appendfsync always # 每1个命令,都立即同步到aof. 安全,速度慢 -appendfsync everysec # 折衷方案,每秒写1次 -appendfsync no # 写入工作交给操作系统,由操作系统判断缓冲区大小,统一写入到aof. 同步频率低,速度快, - - -no-appendfsync-on-rewrite yes: # 正在导出rdb快照的过程中,要不要停止同步aof -auto-aof-rewrite-percentage 100 #aof文件大小比起上次重写时的大小,增长率100%时,重写 -auto-aof-rewrite-min-size 64mb #aof文件,至少超过64M时,重写 -``` - - 注: 在dump rdb过程中,aof如果停止同步,会不会丢失? - 答: 不会,所有的操作缓存在内存的队列里, dump完成后,统一操作. - - 注: aof重写是指什么? - 答: aof重写是指把内存中的数据,逆化成命令,写入到.aof日志里.以解决 aof日志过大的问题. - - 问: 如果rdb文件,和aof文件都存在,优先用谁来恢复数据? - 答: aof - - 问: 2种是否可以同时用? - 答: 可以,而且推荐这么做 - - 问: 恢复时rdb和aof哪个恢复的快 - 答: rdb快,因为其是数据的内存映射,直 接载入到内存,而aof是命令,需要逐条执行 - -## 十、redis主从复制 - -```sh -Master配置: -1:关闭rdb快照(备份工作交给slave) -2:可以开启aof - -slave配置: -1: 声明slave-of -2: 配置密码[如果master有密码] -3: [某1个]slave打开 rdb快照功能 -4: 配置是否只读[slave-read-only] - - -``` - -## 十一、redis表设计 - -主键表 - -|列名|操作|备注| -|--|--|--| -|global:user_id|incr|全局user_id| -|global:post_id|incr|全局post_id| - ---- - -mysql用户表 - -|列名|操作|备注|| -|--|--|--|--| -|user_id|user_name|password|authsecret| -|1|shengj|123456|,./!@#| - -redis用户表 - -|列名|操作|备注|| -|--|--|--|--| -|user:user_id|user:user_id:*:user_name|user:user_id:*:password|user:user_id:*:authsecret| -|1|shengj|123456|,./!@#| - ---- - -mysql发送表 - -|列名|操作|备注||| -|--|--|--|--|--| -|post_id|user_id|user_name|time|content| -|1|1|shengj|1370987654|测试内容| - -redis发送表 - -|列名|操作|备注||| -|--|--|--|--|--| -|post:post_id|post:post_id:*:user_id|post:post_id:*:user_name|post:post_id:*:time|post:post_id:*:content| -|1|1|shengj|1370987654|测试内容| - ---- - -关注表:following -> set user_id - -粉丝表:follower -> set user_id - -推送表:receivepost -> list user_ids - -拉取表:pullpost -> zset user_ids - -## 十二、面试 - -### 1、缓存雪崩 - -问题:当我们的缓存失效或者redis挂了,那么这个时候的请求都会直接走数据库,就会给数据库造成极大的压力,导致数据库也挂了 - -解决: - -1. 对缓存设置不同的过期时间,这样就不会导致缓存同时失效 -2. 建立redis集群,保证服务的可靠性 - -### 2、缓存穿透 - -问题:当有大量用户不走我们设置的键值,就会直接走数据库,就会给数据库造成极大的压力,导致数据库也挂了 - -解决: - -1. 参数过滤和提醒,引导用户走我们的设置的键值 -2. 对不合法的参数进行空对象缓存,并设置较短的过期时间 - -### 3、缓存与数据库读写一致 - -问题:如果一直是读的话,是没问题的,但是更新操作会导致数据库已经更新了,缓存还是旧的数据 - -解决: - -并发下解决数据库与缓存不一致的思路:将删除缓存、修改数据库、读取缓存等的操作积压到队列里边,实现串行化。 - -- 先删除缓存,再更新数据库 - -在高并发下表现不如意,在原子性被破坏时表现优异 - -- 先更新数据库,再删除缓存(Cache Aside Pattern设计模式) - -在高并发下表现优异,在原子性被破坏时表现不如意 - -## 十三、docker实现redis主从 - -[docker实现redis主从](https://github.com/OMGZui/redis_m_s) - -### 1、命令行模式 - -```bash -# 拉取redis -docker pull redis - -# 主 -docker run -v $(pwd)/master/redis.conf:/usr/local/etc/redis/redis.conf --name redis-master redis redis-server /usr/local/etc/redis/redis.conf - -# 从1 --link redis-master:master master是别名 -docker run -v $(pwd)/slave1/redis.conf:/usr/local/etc/redis/redis.conf --name redis-slave1 --link redis-master:master redis redis-server /usr/local/etc/redis/redis.conf - -# 从2 -docker run -v $(pwd)/slave2/redis.conf:/usr/local/etc/redis/redis.conf --name redis-slave2 --link redis-master:master redis redis-server /usr/local/etc/redis/redis.conf - -``` - -### 2、docker-compose模式 推荐 - -```bash -# 拉取redis -docker pull redis - -# 目录 -├── docker-compose.yml -├── master -│   ├── Dockerfile -│   └── redis.conf -├── redis.conf -├── slave1 -│   ├── Dockerfile -│   └── redis.conf -└── slave2 - ├── Dockerfile - └── redis.conf - -# 启动 -docker-compose up -d master slave1 slave2 - -# 查看主容器 -docker-compose exec master bash -root@cab5db8d544b:/data# redis-cli -127.0.0.1:6379> info Replication -# Replication -role:master -connected_slaves:2 -slave0:ip=172.23.0.3,port=6379,state=online,offset=1043,lag=0 -slave1:ip=172.23.0.4,port=6379,state=online,offset=1043,lag=0 -master_replid:995257c6b5ac62f7908cc2c7bb770f2f17b60401 -master_replid2:0000000000000000000000000000000000000000 -master_repl_offset:1043 -second_repl_offset:-1 -repl_backlog_active:1 -repl_backlog_size:1048576 -repl_backlog_first_byte_offset:1 -repl_backlog_histlen:1043 -``` - -## 十四、参考资料 - -- [redis](https://redis.io/) -- [Docker:创建Redis集群](https://lw900925.github.io/docker/docker-redis-cluster.html) diff --git a/数据库/Redis/附录1 面试问题.md b/数据库/Redis/附录1 面试问题.md new file mode 100644 index 00000000..4e7b1397 --- /dev/null +++ b/数据库/Redis/附录1 面试问题.md @@ -0,0 +1,101 @@ + + +### 1 redis相比memcached有哪些优势? + +1. Redis不仅仅支持简单的k/v类型的数据,同时还提供list,set,zset,hash等数据结构的存储。 +2. Redis支持数据的备份,即master-slave模式的数据备份。 +3. Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用。 + + +### 3 redis常见性能问题和解决方案? + +(1) Master最好不要做任何持久化工作,如RDB内存快照和AOF日志文件 +(2) 如果数据比较重要,某个Slave开启AOF备份数据,策略设置为每秒同步一次 +(3) 为了主从复制的速度和连接的稳定性,Master和Slave最好在同一个局域网内 +(4) 尽量避免在压力很大的主库上增加从库 +(5) 主从复制不要用图状结构,用单向链表结构更为稳定,即:Master <- Slave1 <- Slave2 <- Slave3... + + +### 4 MySQL里有2000w数据,redis中只存20w的数据,如何保证redis中的数据都是热点数据? +相关知识:redis 内存数据集大小上升到一定大小的时候,就会施行数据淘汰策略。 + +redis 提供 6种数据淘汰策略: + +voltile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰 +volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰 +volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰 +allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰 +allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰 +no-enviction(驱逐):禁止驱逐数据 + + +### 6. Redis 常见的性能问题都有哪些?如何解决? + +1).Master写内存快照,save命令调度rdbSave函数,会阻塞主线程的工作,当快照比较大时对性能影响是非常大的,会间断性暂停服务,所以Master最好不要写内存快照。 +2).Master AOF持久化,如果不重写AOF文件,这个持久化方式对性能的影响是最小的,但是AOF文件会不断增大,AOF文件过大会影响Master重启的恢复速度。Master最好不要做任何持久化工作,包括内存快照和AOF日志文件,特别是不要启用内存快照做持久化,如果数据比较关键,某个Slave开启AOF备份数据,策略为每秒同步一次。 +3).Master调用BGREWRITEAOF重写AOF文件,AOF在重写的时候会占大量的CPU和内存资源,导致服务load过高,出现短暂服务暂停现象。 +4). Redis主从复制的性能问题,为了主从复制的速度和连接的稳定性,Slave和Master最好在同一个局域网内 + + +### 7 redis 最适合的场景 + +Redis最适合所有数据in-momory的场景,虽然Redis也提供持久化功能,但实际更多的是一个disk-backed的功能,跟传统意义上的持久化有比较大的差别,那么可能大家就会有疑问,似乎Redis更像一个加强版的Memcached,那么何时使用Memcached,何时使用Redis呢? + + +1. 会话缓存(Session Cache) + + 最常用的一种使用Redis的情景是会话缓存(session cache)。用Redis缓存会话比其他存储(如Memcached)的优势在于:Redis提供持久化。当维护一个不是严格要求一致性的缓存时,如果用户的购物车信息全部丢失,大部分人都会不高兴的,现在,他们还会这样吗? + + 幸运的是,随着 Redis 这些年的改进,很容易找到怎么恰当的使用Redis来缓存会话的文档。甚至广为人知的商业平台Magento也提供Redis的插件。 + +2. 全页缓存(FPC) + + 除基本的会话token之外,Redis还提供很简便的FPC平台。回到一致性问题,即使重启了Redis实例,因为有磁盘的持久化,用户也不会看到页面加载速度的下降,这是一个极大改进,类似PHP本地FPC。 + + 再次以Magento为例,Magento提供一个插件来使用Redis作为全页缓存后端。 + + 此外,对WordPress的用户来说,Pantheon有一个非常好的插件 wp-redis,这个插件能帮助你以最快速度加载你曾浏览过的页面。 + +3. 队列 + + Reids在内存存储引擎领域的一大优点是提供 list 和 set 操作,这使得Redis能作为一个很好的消息队列平台来使用。Redis作为队列使用的操作,就类似于本地程序语言(如Python)对 list 的 push/pop 操作。 + + 如果你快速的在Google中搜索“Redis queues”,你马上就能找到大量的开源项目,这些项目的目的就是利用Redis创建非常好的后端工具,以满足各种队列需求。例如,Celery有一个后台就是使用Redis作为broker,你可以从这里去查看。 + +4. 排行榜/计数器 + + Redis在内存中对数字进行递增或递减的操作实现的非常好。集合(Set)和有序集合(Sorted Set)也使得我们在执行这些操作的时候变的非常简单,Redis只是正好提供了这两种数据结构。所以,我们要从排序集合中获取到排名最靠前的10个用户–我们称之为“user_scores”,我们只需要像下面一样执行即可: + + 当然,这是假定你是根据你用户的分数做递增的排序。如果你想返回用户及用户的分数,你需要这样执行: + + ZRANGE user_scores 0 10 WITHSCORES + + Agora Games就是一个很好的例子,用Ruby实现的,它的排行榜就是使用Redis来存储数据的,你可以在这里看到。 + +5. 发布/订阅 + + 最后(但肯定不是最不重要的)是Redis的发布/订阅功能。发布/订阅的使用场景确实非常多。我已看见人们在社交网络连接中使用,还可作为基于发布/订阅的脚本触发器,甚至用Redis的发布/订阅功能来建立聊天系统!(不,这是真的,你可以去核实)。 + + Redis提供的所有特性中,我感觉这个是喜欢的人最少的一个,虽然它为用户提供如果此多功能。 + + + + + +### 8 Redis 常见性能问题和解决方案? +(1) Master 最好不要做任何持久化工作,如 RDB 内存快照和 AOF 日志文件 +(2) 如果数据比较重要,某个 Slave 开启 AOF 备份数据,策略设置为每秒同步一次 +(3) 为了主从复制的速度和连接的稳定性, Master 和 Slave 最好在同一个局域网内 +(4) 尽量避免在压力很大的主库上增加从库 +(5) 主从复制不要用图状结构,用单向链表结构更为稳定,即: Master <- Slave1 <- Slave2 <-Slave3… + + + +### 9 Redis怎么保证原子性的? +对于Redis而言,命令的原子性指的是:一个操作的不可以再分,操作要么执行,要么不执行。 +Redis的操作之所以是原子性的,是因为Redis是单线程的。Redis本身提供的所有API(单个命令)都是原子操作,Redis中的事务其实是要保证批量操作的原子性。 + +多个命令在并发中也是原子性的吗? +不一定, 举例:1、将get和set改成单命令操作;2、incr + +如何解决:使用Redis的事务,或者使用Redis+Lua的方式实现 diff --git a/数据库/Redis/附录2 Redis持久化.md b/数据库/Redis/附录2 Redis持久化.md deleted file mode 100644 index fae81fea..00000000 --- a/数据库/Redis/附录2 Redis持久化.md +++ /dev/null @@ -1,77 +0,0 @@ -# Redis持久化 -> 参考文献 -> * [https://www.cnblogs.com/shizhengwen/p/9283824.html](https://www.cnblogs.com/shizhengwen/p/9283824.html) -## 概述 -* Redis是一种高级key-value数据库。它跟memcached类似,不过数据可以持久化,而且支持的数据类型很丰富。有字符串,链表,集 合和有序集合。支持在服务器端计算集合的并,交和补集(difference)等,还支持多种排序功能。所以Redis也可以被看成是一个数据结构服务 器。 -* Redis的所有数据都是保存在内存中,然后不定期的通过异步方式保存到磁盘上(这称为“半持久化模式”);也可以把每一次数据变化都写入到一个append only file(aof)里面(这称为“全持久化模式”)。 - -* 由于Redis的数据都存放在内存中,如果没有配置持久化,redis重启后数据就全丢失了,于是需要开启redis的持久化功能,将数据保存到磁盘上,当redis重启后,可以从磁盘中恢复数据。redis提供两种方式进行持久化: - * 一种是RDB持久化(原理是将Reids在内存中的数据库记录定时dump到磁盘上的RDB持久化), - * 另外一种是AOF(append only file)持久化(原理是将Reids的操作日志以追加的方式写入文件)。 -* 那么这两种持久化方式有什么区别呢,改如何选择呢?网上看了大多数都是介绍这两种方式怎么配置,怎么使用,就是没有介绍二者的区别,在什么应用场景下使用。 - -## 2 二者的区别 -* RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘,实际操作过程是fork一个子进程,先将数据集写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储。 -![](image/2021-04-07-23-24-02.png) - -* AOF持久化以日志的形式记录服务器所处理的每一个写、删除操作,查询操作不会记录,以文本的方式记录,可以打开文件看到详细的操作记录。 -![](image/2021-04-07-23-24-24.png) - -## 3 二者优缺点 -### RDB存在哪些优势呢? -1. 一旦采用该方式,那么你的整个Redis数据库将只包含一个文件,这对于文件备份而言是非常完美的。比如,你可能打算每个小时归档一次最近24小时的数据,同时还要每天归档一次最近30天的数据。通过这样的备份策略,一旦系统出现灾难性故障,我们可以非常容易的进行恢复。 - -2. 对于灾难恢复而言,RDB是非常不错的选择。因为我们可以非常轻松的将一个单独的文件压缩后再转移到其它存储介质上。 - -3. 性能最大化。对于Redis的服务进程而言,在开始持久化时,它唯一需要做的只是fork出子进程,之后再由子进程完成这些持久化的工作,这样就可以极大的避免服务进程执行IO操作了。 - -4. 相比于AOF机制,如果数据集很大,RDB的启动效率会更高。 - -### RDB又存在哪些劣势呢? - -1. 如果你想保证数据的高可用性,即最大限度的避免数据丢失,那么RDB将不是一个很好的选择。因为系统一旦在定时持久化之前出现宕机现象,此前没有来得及写入磁盘的数据都将丢失。 - -2. 由于RDB是通过fork子进程来协助完成数据持久化工作的,因此,如果当数据集较大时,可能会导致整个服务器停止服务几百毫秒,甚至是1秒钟。 - -### AOF的优势有哪些呢? -1. 该机制可以带来更高的数据安全性,即数据持久性。Redis中提供了3中同步策略,即每秒同步、每修改同步和不同步。事实上,每秒同步也是异步完成的,其效率也是非常高的,所差的是一旦系统出现宕机现象,那么这一秒钟之内修改的数据将会丢失。而每修改同步,我们可以将其视为同步持久化,即每次发生的数据变化都会被立即记录到磁盘中。可以预见,这种方式在效率上是最低的。至于无同步,无需多言,我想大家都能正确的理解它。 - -2. 由于该机制对日志文件的写入操作采用的是append模式,因此在写入过程中即使出现宕机现象,也不会破坏日志文件中已经存在的内容。然而如果我们本次操作只是写入了一半数据就出现了系统崩溃问题,不用担心,在Redis下一次启动之前,我们可以通过redis-check-aof工具来帮助我们解决数据一致性的问题。 - -3. 如果日志过大,Redis可以自动启用rewrite机制。即Redis以append模式不断的将修改数据写入到老的磁盘文件中,同时Redis还会创建一个新的文件用于记录此期间有哪些修改命令被执行。因此在进行rewrite切换时可以更好的保证数据安全性。 - -4. AOF包含一个格式清晰、易于理解的日志文件用于记录所有的修改操作。事实上,我们也可以通过该文件完成数据的重建。 - -### AOF的劣势有哪些呢? - -1. 对于相同数量的数据集而言,AOF文件通常要大于RDB文件。RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快。 - -2. 根据同步策略的不同,AOF在运行效率上往往会慢于RDB。总之,每秒同步策略的效率是比较高的,同步禁用策略的效率和RDB一样高效。 - -### 二者选择的标准 - -就是看系统是愿意牺牲一些性能,换取更高的缓存一致性(aof),还是愿意写操作频繁的时候,不启用备份来换取更高的性能,待手动运行save的时候,再做备份(rdb)。rdb这个就更有些 eventually consistent的意思了。 - -## 4 常用配置 -### RDB持久化配置 -* Redis会将数据集的快照dump到dump.rdb文件中。此外,我们也可以通过配置文件来修改Redis服务器dump快照的频率,在打开6379.conf文件之后,我们搜索save,可以看到下面的配置信息: - -``` -save 900 1 #在900秒(15分钟)之后,如果至少有1个key发生变化,则dump内存快照。 - -save 300 10 #在300秒(5分钟)之后,如果至少有10个key发生变化,则dump内存快照。 - -save 60 10000 #在60秒(1分钟)之后,如果至少有10000个key发生变化,则dump内存快照 -``` - -### AOF持久化配置 -* 在Redis的配置文件中存在三种同步方式,它们分别是: -``` -appendfsync always #每次有数据修改发生时都会写入AOF文件。 - -appendfsync everysec #每秒钟同步一次,该策略为AOF的缺省策略。 - -appendfsync no #从不同步。高效但是数据不会被持久化。 -``` - - \ No newline at end of file diff --git a/数据库/Redis/附录3 Redis数据结构.md b/数据库/Redis/附录3 Redis数据结构.md deleted file mode 100644 index 5c44c76c..00000000 --- a/数据库/Redis/附录3 Redis数据结构.md +++ /dev/null @@ -1 +0,0 @@ -https://www.cnblogs.com/ysocean/p/9080942.html#_label1 \ No newline at end of file diff --git a/数据库/Redis/附录4 Redis常见性能问题.md b/数据库/Redis/附录4 Redis常见性能问题.md deleted file mode 100644 index a72529cc..00000000 --- a/数据库/Redis/附录4 Redis常见性能问题.md +++ /dev/null @@ -1,141 +0,0 @@ - -https://zhuanlan.zhihu.com/p/65180667 - -我们在碰到需要执行耗时特别久,且结果不频繁变动的 SQL,就特别适合将运行结果放入缓存。这样,后面的请求就去缓存中读取,使得请求能够迅速响应。 -![](image/2021-08-24-16-39-38.png) - - -在大并发的情况下,所有的请求直接访问数据库,数据库会出现连接异常。这个时候,就需要使用 Redis 做一个缓冲操作,让请求先访问到 Redis,而不是直接访问数据库。 -![](image/2021-08-24-16-39-58.png) - -1. 使用Redis有哪些好处? - -(1) 速度快,因为数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1) -(2) 支持丰富数据类型,支持string,list,set,sorted set,hash -(3) 支持事务,操作都是原子性,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行(4) 丰富的特性:可用于缓存,消息,按key设置过期时间,过期后将会自动删除 - - -2. redis相比memcached有哪些优势? - -(1) memcached所有的值均是简单的字符串,redis作为其替代者,支持更为丰富的数据类型 -(2) redis的速度比memcached快很多 -(3) redis可以持久化其数据 -3.redis常见性能问题和解决方案: - -(1) Master最好不要做任何持久化工作,如RDB内存快照和AOF日志文件 -(2) 如果数据比较重要,某个Slave开启AOF备份数据,策略设置为每秒同步一次 -(3) 为了主从复制的速度和连接的稳定性,Master和Slave最好在同一个局域网内 -(4) 尽量避免在压力很大的主库上增加从库 -(5) 主从复制不要用图状结构,用单向链表结构更为稳定,即:Master <- Slave1 <- Slave2 <- Slave3... - - -4. MySQL里有2000w数据,redis中只存20w的数据,如何保证redis中的数据都是热点数据 -相关知识:redis 内存数据集大小上升到一定大小的时候,就会施行数据淘汰策略。 - -redis 提供 6种数据淘汰策略: - -voltile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰 -volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰 -volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰 -allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰 -allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰 -no-enviction(驱逐):禁止驱逐数据 - - -6. Redis 常见的性能问题都有哪些?如何解决? - -1).Master写内存快照,save命令调度rdbSave函数,会阻塞主线程的工作,当快照比较大时对性能影响是非常大的,会间断性暂停服务,所以Master最好不要写内存快照。 -2).Master AOF持久化,如果不重写AOF文件,这个持久化方式对性能的影响是最小的,但是AOF文件会不断增大,AOF文件过大会影响Master重启的恢复速度。Master最好不要做任何持久化工作,包括内存快照和AOF日志文件,特别是不要启用内存快照做持久化,如果数据比较关键,某个Slave开启AOF备份数据,策略为每秒同步一次。 -3).Master调用BGREWRITEAOF重写AOF文件,AOF在重写的时候会占大量的CPU和内存资源,导致服务load过高,出现短暂服务暂停现象。 -4). Redis主从复制的性能问题,为了主从复制的速度和连接的稳定性,Slave和Master最好在同一个局域网内 -7, redis 最适合的场景 - -Redis最适合所有数据in-momory的场景,虽然Redis也提供持久化功能,但实际更多的是一个disk-backed的功能,跟传统意义上的持久化有比较大的差别,那么可能大家就会有疑问,似乎Redis更像一个加强版的Memcached,那么何时使用Memcached,何时使用Redis呢? - -如果简单地比较Redis与Memcached的区别,大多数都会得到以下观点: - -1 、Redis不仅仅支持简单的k/v类型的数据,同时还提供list,set,zset,hash等数据结构的存储。 -2 、Redis支持数据的备份,即master-slave模式的数据备份。 -3 、Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用。 -(1)、会话缓存(Session Cache) - -最常用的一种使用Redis的情景是会话缓存(session cache)。用Redis缓存会话比其他存储(如Memcached)的优势在于:Redis提供持久化。当维护一个不是严格要求一致性的缓存时,如果用户的购物车信息全部丢失,大部分人都会不高兴的,现在,他们还会这样吗? - -幸运的是,随着 Redis 这些年的改进,很容易找到怎么恰当的使用Redis来缓存会话的文档。甚至广为人知的商业平台Magento也提供Redis的插件。 - -(2)、全页缓存(FPC) - -除基本的会话token之外,Redis还提供很简便的FPC平台。回到一致性问题,即使重启了Redis实例,因为有磁盘的持久化,用户也不会看到页面加载速度的下降,这是一个极大改进,类似PHP本地FPC。 - -再次以Magento为例,Magento提供一个插件来使用Redis作为全页缓存后端。 - -此外,对WordPress的用户来说,Pantheon有一个非常好的插件 wp-redis,这个插件能帮助你以最快速度加载你曾浏览过的页面。 - -(3)、队列 - -Reids在内存存储引擎领域的一大优点是提供 list 和 set 操作,这使得Redis能作为一个很好的消息队列平台来使用。Redis作为队列使用的操作,就类似于本地程序语言(如Python)对 list 的 push/pop 操作。 - -如果你快速的在Google中搜索“Redis queues”,你马上就能找到大量的开源项目,这些项目的目的就是利用Redis创建非常好的后端工具,以满足各种队列需求。例如,Celery有一个后台就是使用Redis作为broker,你可以从这里去查看。 - -(4)、排行榜/计数器 - -Redis在内存中对数字进行递增或递减的操作实现的非常好。集合(Set)和有序集合(Sorted Set)也使得我们在执行这些操作的时候变的非常简单,Redis只是正好提供了这两种数据结构。所以,我们要从排序集合中获取到排名最靠前的10个用户–我们称之为“user_scores”,我们只需要像下面一样执行即可: - -当然,这是假定你是根据你用户的分数做递增的排序。如果你想返回用户及用户的分数,你需要这样执行: - -ZRANGE user_scores 0 10 WITHSCORES - -Agora Games就是一个很好的例子,用Ruby实现的,它的排行榜就是使用Redis来存储数据的,你可以在这里看到。 - -(5)、发布/订阅 - -最后(但肯定不是最不重要的)是Redis的发布/订阅功能。发布/订阅的使用场景确实非常多。我已看见人们在社交网络连接中使用,还可作为基于发布/订阅的脚本触发器,甚至用Redis的发布/订阅功能来建立聊天系统!(不,这是真的,你可以去核实)。 - -Redis提供的所有特性中,我感觉这个是喜欢的人最少的一个,虽然它为用户提供如果此多功能。 - - - - -7.4、缓存预热 - -缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统。这样就可以避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据! - -解决方法: -1、直接写个缓存刷新页面,上线时手工操作下 -2、数据量不大,可以在项目启动的时候自动进行加载 -3、定时刷新缓存 - - - -7.5、缓存降级 -当访问量剧增、服务出现问题(如响应时间慢或不响应)或非核心服务影响到核心流程的性能时,仍然需要保证服务还是可用的,即使是有损服务。系统可以根据一些关键数据进行自动降级,也可以配置开关实现人工降级。 - -降级的最终目的是保证核心服务可用,即使是有损的。而且有些服务是无法降级的(如加入购物车、结算)。 - -在进行降级之前要对系统进行梳理,看看系统是不是可以丢卒保帅;从而梳理出哪些必须誓死保护,哪些可降级 - - - - - -8、Redis 常见性能问题和解决方案? -(1) Master 最好不要做任何持久化工作,如 RDB 内存快照和 AOF 日志文件 -(2) 如果数据比较重要,某个 Slave 开启 AOF 备份数据,策略设置为每秒同步一次 -(3) 为了主从复制的速度和连接的稳定性, Master 和 Slave 最好在同一个局域网内 -(4) 尽量避免在压力很大的主库上增加从库 -(5) 主从复制不要用图状结构,用单向链表结构更为稳定,即: Master <- Slave1 <- Slave2 <-Slave3… - - - -9、为什么Redis的操作是原子性的,怎么保证原子性的? -对于Redis而言,命令的原子性指的是:一个操作的不可以再分,操作要么执行,要么不执行。 -Redis的操作之所以是原子性的,是因为Redis是单线程的。 -Redis本身提供的所有API(单个命令)都是原子操作,Redis中的事务其实是要保证批量操作的原子性。 - -多个命令在并发中也是原子性的吗? -不一定, 举例:1、将get和set改成单命令操作;2、incr - -如何解决:使用Redis的事务,或者使用Redis+Lua的方式实现 -———————————————— -版权声明:本文为CSDN博主「波波的小书房」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 -原文链接:https://blog.csdn.net/xl_1803/article/details/107901645 \ No newline at end of file