浅谈Redis
2007 年,一位程序员和朋友一起创建了一个网站。为了解决这个网站的负载问题,他自己定制了一个数据库。于2009 年开发,称之为Redis。这位意大利程序员是萨尔瓦托勒·桑菲利波(Salvatore Sanfilippo),他被称为Redis之父,更广为人知的名字是Antirez。
1.Redis简介
REmote DIctionary Server(Redis) 是一个开源的使用 ANSI C 语言编写、遵守 BSD 协议、支持网络、可基于内存、分布式、可选持久性的键值对(Key-Value)存储数据库,并提供多种语言的 API。
Redis 通常被称为数据结构服务器,因为值(value)可以是字符串(String)、哈希(Hash)、列表(list)、集合(sets)和有序集合(sorted sets)等类型。
2.内存模型
首先可以进行Redis的内存模型学习,对Redis的使用有很大帮助,例如OOM时定位、内存使用量评估等。
通过info memory命令查看内存的使用情况。
主要参数:
- used_memory:从Redis角度使用了多少内存,即Redis分配器分配的内存总量(单位是字节),包括使用的虚拟内存(即swap);
- used_memory_rss:从操作系统角度实际使用量,即Redis进程占据操作系统的内存(单位是字节),包括进程运行本身需要的内存、内存碎片等,不包括虚拟内存。一般情况下used_memory_rss都要比used_memory大,因为Redis频繁删除读写等操作使得内存碎片较多,而虚拟内存的使用一般是非极端情况下是不怎么使用的;
- mem_fragmentation_ratio:即内存碎片比率,该值是used_memory_rss / used_memory的比值;mem_fragmentation_ratio一般大于1,且该值越大,内存碎片比例越大。如果mem_fragmentation_ratio<1,说明Redis使用了虚拟内存,由于虚拟内存的媒介是磁盘,比内存速度要慢很多,当这种情况出现时,应该及时排查,如果内存不足应该及时处理,如增加Redis节点、增加Redis服务器的内存、优化应用等。一般来说,mem_fragmentation_ratio在1.03左右是比较健康的状态(对于jemalloc来说);
- mem_allocator:即Redis使用的内存分配器,一般默认是jemalloc。
2.1 Redis内存划分
- 数据
作为数据库,数据是最主要的部分,这部分占用的内存会统计在used_memory中。
- 进程本身运行需要的内存
这部分内存不是由jemalloc分配,因此不会统计在used_memory中。
- 缓冲内存
缓冲内存包括:
- 客户端缓冲区:存储客户端连接的输入输出缓冲;
- 复制积压缓冲区:用于部分复制功能;
- aof_buf:用于在进行AOF重写时,保存最近的写入命令。
这部分内存由jemalloc分配,因此会统计在used_memory中。
- 内存碎片
内存碎片是Redis在数据更改频繁分配、回收物理内存过程中产生的。
内存碎片不会统计在used_memory中。
2.2 Redis数据存储的细节
下面看一张经典的图。
- jemalloc
无论是DictEntry对象,还是RedisObject、SDS对象,都需要内存分配器(如jemalloc)分配内存进行存储。Redis在编译时便会指定内存分配器;内存分配器可以是 libc 、jemalloc或者tcmalloc,默认是jemalloc。当Redis存储数据时,会选择大小最合适的内存块进行存储。
jemalloc划分的内存单元如下图所示:
- dictEntry
每个dictEntry都保存着一个键值对,key值保存一个sds结构体,value值保存一个redisObject结构体。
- redisObject
前面说到,Redis对象有5种类型;无论是哪种类型,Redis都不会直接存储,而是通过RedisObject对象进行存储。
RedisObject的每个字段的含义和作用如下:
- type
type字段表示对象的类型,占4个比特;目前包括REDIS_STRING(字符串)、REDIS_LIST (列表)、REDIS_HASH(哈希)、REDIS_SET(集合)、REDIS_ZSET(有序集合)。
- encoding
encoding表示对象的内部编码,占4个比特(redis-3.0)。
- lru
lru记录的是对象最后一次被命令程序访问的时间,占据的比特数不同的版本有所不同(如4.0版本占24比特,2.6版本占22比特)。
- refcount
refcount记录的是该对象被引用的次数,类型为整型。refcount的作用,主要在于对象的引用计数和内存回收:
- 当创建新对象时,refcount初始化为1;
- 当有新程序使用该对象时,refcount加1;
- 当对象不再被一个新程序使用时,refcount减1;
- 当refcount变为0时,对象占用的内存会被释放。
Redis中被多次使用的对象(refcount>1)称为共享对象。Redis为了节省内存,当有一些对象重复出现时,新的程序不会创建新的对象,而是仍然使用原来的对象。这个被重复使用的对象,就是共享对象。目前共享对象仅支持整数值的字符串对象。
共享对象的具体实现
Redis的共享对象目前只支持整数值的字符串对象。之所以如此,实际上是对内存和CPU(时间)的平衡:共享对象虽然会降低内存消耗,但是判断两个对象是否相等却需要消耗额外的时间。
对于整数值,判断操作复杂度为O(1);
对于普通字符串,判断复杂度为O(n);
而对于哈希、列表、集合和有序集合,判断的复杂度为O(n^2)。
虽然共享对象只能是整数值的字符串对象,但是5种类型都可能使用共享对象(如哈希、列表等的元素可以使用)。
就目前的实现来说,Redis服务器在初始化时,会创建10000个字符串对象,值分别是09999的整数值;当Redis需要使用值为09999的字符串对象时,可以直接使用这些共享对象。10000这个数字可以通过调整参数REDIS_SHARED_INTEGERS(4.0中是OBJ_SHARED_INTEGERS)的值进行改变。
- ptr
ptr指针指向具体的数据,如前面的例子中,set hello world,ptr指向包含字符串world的SDS。
- SDS
Redis没有直接使用C字符串(即以空字符‘\0’结尾的字符数组)作为默认的字符串表示,而是使用了SDS。SDS是简单动态字符串(Simple Dynamic String)的缩写。
)
通过SDS的结构可以看出,buf数组的长度=free+len+1(其中1表示字符串结尾的空字符);所以,一个SDS结构占据的空间为:free所占长度+len所占长度+ buf数组的长度=4+4+free+len+1=free+len+9。
为什么使用SDS而不直接使用C字符串?
SDS在C字符串的基础上加入了free和len字段,带来了很多好处:
- 获取字符串长度:SDS是O(1),C字符串是O(n)。
- 缓冲区溢出:使用C字符串的API时,如果字符串长度增加(如strcat操作)而忘记重新分配内存,很容易造成缓冲区的溢出;而SDS由于记录了长度,相应的API在可能造成缓冲区溢出时会自动重新分配内存,杜绝了缓冲区溢出。
- 修改字符串时内存的重分配:对于C字符串,如果要修改字符串,必须要重新分配内存(先释放再申请),因为如果没有重新分配,字符串长度增大时会造成内存缓冲区溢出,字符串长度减小时会造成内存泄露。而对于SDS,由于可以记录len和free,因此解除了字符串长度和空间数组长度之间的关联,可以在此基础上进行优化——空间预分配策略(即分配内存时比实际需要的多)使得字符串长度增大时重新分配内存的概率大大减小;惰性空间释放策略使得字符串长度减小时重新分配内存的概率大大减小。
- 存取二进制数据:SDS可以,C字符串不可以。因为C字符串以空字符作为字符串结束的标识,而对于一些二进制文件(如图片等),内容可能包括空字符串,因此C字符串无法正确存取;而SDS以字符串长度len来作为字符串结束标识,因此没有这个问题。
3.持久化Persistence
持久化的功能:Redis是内存数据库,数据都是存储在内存中,为了避免进程退出导致数据的永久丢失,需要定期将Redis中的数据以某种形式(数据或命令)从内存保存到硬盘。当下次Redis重启时,利用持久化文件实现数据恢复。除此之外,为了进行灾难备份,可以将持久化文件拷贝到一个远程位置。
Redis持久化分为RDB持久化和AOF持久化,前者将当前数据保存到硬盘,后者则是将每次执行的写命令保存到硬盘(类似于MySQL的Binlog)。由于AOF持久化的实时性更好,即当进程意外退出时丢失的数据更少,因此AOF是目前主流的持久化方式,不过RDB持久化仍然有其用武之地。
3.1 RDB持久化
RDB(Redis Database)持久化方式能够在指定的时间间隔能对你的数据进行快照存储。一般通过bgsave命令会创建一个子进程,由子进程来负责创建RDB文件,父进程(即Redis主进程)则继续处理请求。
图片中的5个步骤所进行的操作如下:
- Redis父进程首先判断:当前是否在执行save,或bgsave/bgrewriteaof(后面会详细介绍该命令)的子进程,如果在执行则bgsave命令直接返回。bgsave/bgrewriteaof 的子进程不能同时执行,主要是基于性能方面的考虑:两个并发的子进程同时执行大量的磁盘写操作,可能引起严重的性能问题。
- 父进程执行fork操作创建子进程,这个过程中父进程是阻塞的,Redis不能执行来自客户端的任何命令;
- 父进程fork后,bgsave命令返回”Background saving started”信息并不再阻塞父进程,并可以响应其他命令;
- 子进程进程对内存数据生成快照文件;
- 子进程发送信号给父进程表示完成,父进程更新统计信息。
这里补充一下第4点是如何生成RDB文件的。一定有读者也有疑问:在同步到磁盘和持续写入这个过程是如何处理数据不一致的情况呢?生成快照RDB文件时是否会对业务产生影响?
- 通过 fork 创建的子进程能够获得和父进程完全相同的内存空间,父进程对内存的修改对于子进程是不可见的,两者不会相互影响;
- 通过 fork 创建子进程时不会立刻触发大量内存的拷贝,采用的是写时拷贝COW (Copy On Write)。内核只为新生成的子进程创建虚拟空间结构,它们来复制于父进程的虚拟究竟结构,但是不为这些段分配物理内存,它们共享父进程的物理空间,当父子进程中有更改相应段的行为发生时,再为子进程相应的段分配物理空间;
3.2 AOF持久化
AOF(Append Only File)持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据,AOF命令以redis协议追加保存每次写的操作到文件末尾。Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大。
AOF的执行流程包括:
3.3 命令追加(append)
Redis先将写命令追加到缓冲区aof_buf,而不是直接写入文件,主要是为了避免每次有写命令都直接写入硬盘,导致硬盘IO成为Redis负载的瓶颈。
3.4 文件写入(write)和文件同步(sync)
根据不同的同步策略将aof_buf中的内容同步到硬盘;
Linux 操作系统中为了提升性能,使用了页缓存(page cache)。当我们将 aof_buf 的内容写到磁盘上时,此时数据并没有真正的落盘,而是在 page cache 中,为了将 page cache 中的数据真正落盘,需要执行 fsync / fdatasync 命令来强制刷盘。这边的文件同步做的就是刷盘操作,或者叫文件刷盘可能更容易理解一些。
AOF缓存区的同步文件策略由参数appendfsync控制,各个值的含义如下:
- always:命令写入aof_buf后立即调用系统write操作和系统fsync操作同步到AOF文件,fsync完成后线程返回。这种情况下,每次有写命令都要同步到AOF文件,硬盘IO成为性能瓶颈,Redis只能支持大约几百TPS写入,严重降低了Redis的性能;即便是使用固态硬盘(SSD),每秒大约也只能处理几万个命令,而且会大大降低SSD的寿命。可靠性较高,数据基本不丢失。
- no:命令写入aof_buf后调用系统write操作,不对AOF文件做fsync同步;同步由操作系统负责,通常同步周期为30秒。这种情况下,文件同步的时间不可控,且缓冲区中堆积的数据会很多,数据安全性无法保证。
- everysec:命令写入aof_buf后调用系统write操作,write完成后线程返回;fsync同步文件操作由专门的线程每秒调用一次。everysec是前述两种策略的折中,是性能和数据安全性的平衡,因此是Redis的默认配置,也是我们推荐的配置。
有同学可能会疑问为什么always策略还是不能100%保障数据不丢失,例如在开启AOF的情况下,有一条写命令,Redis在写命令执行完,写aof_buf未成功的情况下宕机了?
不能,Redis就不能100%保证数据不丢失。
void flushAppendOnlyFile(int force) { ssize_t nwritten; int sync_in_progress = 0; mstime_t latency; if (sdslen(server.aof_buf) == 0) return; if (server.aof_fsync == AOF_FSYNC_EVERYSEC) sync_in_progress = bioPendingJobsOfType(REDIS_BIO_AOF_FSYNC) != 0; if (server.aof_fsync == AOF_FSYNC_EVERYSEC && !force) { /* With this append fsync policy we do background fsyncing. * If the fsync is still in progress we can try to delay * the write for a couple of seconds. */ if (sync_in_progress) { if (server.aof_flush_postponed_start == 0) { /* No previous write postponing, remember that we are * postponing the flush and return. */ server.aof_flush_postponed_start = server.unixtime; return; } else if (server.unixtime - server.aof_flush_postponed_start < 2) { /* We were already waiting for fsync to finish, but for less * than two seconds this is still ok. Postpone again. */ return; } /* Otherwise fall trough, and go write since we can't wait * over two seconds. */ server.aof_delayed_fsync++; redisLog(REDIS_NOTICE,"Asynchronous AOF fsync is taking too long (disk is busy?). Writing the AOF buffer without waiting for fsync to complete, this may slow down Redis."); } } /* We want to perform a single write. This should be guaranteed atomic * at least if the filesystem we are writing is a real physical one. * While this will save us against the server being killed I don't think * there is much to do about the whole server stopping for power problems * or alike */ latencyStartMonitor(latency); nwritten = write(server.aof_fd,server.aof_buf,sdslen(server.aof_buf)); latencyEndMonitor(latency); /* We want to capture different events for delayed writes: * when the delay happens with a pending fsync, or with a saving child * active, and when the above two conditions are missing. * We also use an additional event name to save all samples which is * useful for graphing / monitoring purposes. */ if (sync_in_progress) { latencyAddSampleIfNeeded("aof-write-pending-fsync",latency); } else if (server.aof_child_pid != -1 || server.rdb_child_pid != -1) { latencyAddSampleIfNeeded("aof-write-active-child",latency); } else { latencyAddSampleIfNeeded("aof-write-alone",latency); } latencyAddSampleIfNeeded("aof-write",latency); /* We performed the write so reset the postponed flush sentinel to zero. */ server.aof_flush_postponed_start = 0; if (nwritten != (signed)sdslen(server.aof_buf)) { static time_t last_write_error_log = 0; int can_log = 0; /* Limit logging rate to 1 line per AOF_WRITE_LOG_ERROR_RATE seconds. */ if ((server.unixtime - last_write_error_log) > AOF_WRITE_LOG_ERROR_RATE) { can_log = 1; last_write_error_log = server.unixtime; } /* Log the AOF write error and record the error code. */ if (nwritten == -1) { if (can_log) { redisLog(REDIS_WARNING,"Error writing to the AOF file: %s", strerror(errno)); server.aof_last_write_errno = errno; } } else { if (can_log) { redisLog(REDIS_WARNING,"Short write while writing to " "the AOF file: (nwritten=%lld, " "expected=%lld)", (long long)nwritten, (long long)sdslen(server.aof_buf)); } if (ftruncate(server.aof_fd, server.aof_current_size) == -1) { if (can_log) { redisLog(REDIS_WARNING, "Could not remove short write " "from the append-only file. Redis may refuse " "to load the AOF the next time it starts. " "ftruncate: %s", strerror(errno)); } } else { /* If the ftruncate() succeeded we can set nwritten to * -1 since there is no longer partial data into the AOF. */ nwritten = -1; } server.aof_last_write_errno = ENOSPC; } /* Handle the AOF write error. */ if (server.aof_fsync == AOF_FSYNC_ALWAYS) { /* We can't recover when the fsync policy is ALWAYS since the * reply for the client is already in the output buffers, and we * have the contract with the user that on acknowledged write data * is synced on disk. */ redisLog(REDIS_WARNING,"Can't recover from AOF write error when the AOF fsync policy is 'always'. Exiting..."); exit(1); } else { /* Recover from failed write leaving data into the buffer. However * set an error to stop accepting writes as long as the error * condition is not cleared. */ server.aof_last_write_status = REDIS_ERR; /* Trim the sds buffer if there was a partial write, and there * was no way to undo it with ftruncate(2). */ if (nwritten > 0) { server.aof_current_size += nwritten; sdsrange(server.aof_buf,nwritten,-1); } return; /* We'll try again on the next call... */ } } else { /* Successful write(2). If AOF was in error state, restore the * OK state and log the event. */ if (server.aof_last_write_status == REDIS_ERR) { redisLog(REDIS_WARNING, "AOF write error looks solved, Redis can write again."); server.aof_last_write_status = REDIS_OK; } } server.aof_current_size += nwritten; /* Re-use AOF buffer when it is small enough. The maximum comes from the * arena size of 4k minus some overhead (but is otherwise arbitrary). */ if ((sdslen(server.aof_buf)+sdsavail(server.aof_buf)) < 4000) { sdsclear(server.aof_buf); } else { sdsfree(server.aof_buf); server.aof_buf = sdsempty(); } /* Don't fsync if no-appendfsync-on-rewrite is set to yes and there are * children doing I/O in the background. */ if (server.aof_no_fsync_on_rewrite && (server.aof_child_pid != -1 || server.rdb_child_pid != -1)) return; /* Perform the fsync if needed. */ if (server.aof_fsync == AOF_FSYNC_ALWAYS) { /* aof_fsync is defined as fdatasync() for Linux in order to avoid * flushing metadata. */ latencyStartMonitor(latency); aof_fsync(server.aof_fd); /* Let's try to get this data on the disk */ latencyEndMonitor(latency); latencyAddSampleIfNeeded("aof-fsync-always",latency); server.aof_last_fsync = server.unixtime; } else if ((server.aof_fsync == AOF_FSYNC_EVERYSEC && server.unixtime > server.aof_last_fsync)) { if (!sync_in_progress) aof_background_fsync(server.aof_fd); server.aof_last_fsync = server.unixtime; }}
那么从上面redis-3.0的源码及上下文
if (server.aof_fsync == AOF_FSYNC_ALWAYS)
分析得出,其实我们每次执行客户端命令的时候操作并没有写到aof文件中,只是写到了aof_buf内存当中,只有当下一个事件来临时,才会去fsync到disk中,从redis的这种策略上我们也可以看出,redis和mysql在数据持久化之间的区别,redis的数据持久化仅仅就是一个附带功能,并不是其主要功能。
结论:Redis即使在配制appendfsync=always的策略下,还是会丢失一个事件循环的数据。
3.5 文件重写(rewrite)
定期重写AOF文件,达到压缩的目的。
AOF重写是AOF持久化的一个机制,用来压缩AOF文件,通过fork一个子进程,重新写一个新的AOF文件,该次重写不是读取旧的AOF文件进行复制,而是读取内存中的Redis数据库,重写一份AOF文件,有点类似于RDB的快照方式。
文件重写之所以能够压缩AOF文件,原因在于:
- 过期的数据不再写入文件
- 无效的命令不再写入文件:如有些数据被重复设值(set mykey v1, set mykey v2)、有些数据被删除了(sadd myset v1, del myset)等等
- 多条命令可以合并为一个:如sadd myset v1, sadd myset v2, sadd myset v3可以合并为sadd myset v1 v2 v3。不过为了防止单条命令过大造成客户端缓冲区溢出,对于list、set、hash、zset类型的key,并不一定只使用一条命令;而是以某个常量为界将命令拆分为多条。这个常量在redis.h/REDIS_AOF_REWRITE_ITEMS_PER_CMD中定义,不可更改,3.0版本中值是64。
3.5.1 文件重写时机
相关参数:
- aof_current_size:表示当前 AOF 文件空间
- aof_base_size:表示上一次重写后 AOF 文件空间
- auto-aof-rewrite-min-size: 表示运行 AOF 重写时文件的最小体积,默认为64MB
- auto-aof-rewrite-percentage: 表示当前 AOF 重写时文件空间(aof_current_size)超过上一次重写后 AOF 文件空间(aof_base_size)的比值多少后会重写。
同时满足下面两个条件,则触发 AOF 重写机制:
- aof_current_size 大于 auto-aof-rewrite-min-size
- 当前 AOF 相比上一次 AOF 的增长率:(aof_current_size - aof_base_size)/aof_base_size 大于或等于 auto-aof-rewrite-percentage
3.5.2 文件重写流程
流程说明:
- 执行AOF重写请求。
如果当前进程正在执行bgrewriteaof重写,请求不执行。
如果当前进程正在执行bgsave操作,重写命令延迟到bgsave完成之后再执行。
- 父进程执行fork创建子进程,开销等同于bgsave过程。
3.1 主进程fork操作完成后,继续响应其它命令。所有修改命令依然写入AOF文件缓冲区并根据appendfsync策略同步到磁盘,保证原有AOF机制正确性。
3.2 由于fork操作运用写时复制技术,子进程只能共享fork操作时的内存数据由于父进程依然响应命令,Redis使用“AOF”重写缓冲区保存这部分新数据,防止新的AOF文件生成期间丢失这部分数据。
- 子进程依据内存快照,按照命令合并规则写入到新的AOF文件。每次批量写入硬盘数据量由配置aof-rewrite-incremental-fsync控制,默认为32MB,防止单次刷盘数据过多造成硬盘阻塞。
5.1 新AOF文件写入完成后,子进程发送信号给父进程,父进程更新统计信息。
5.2 父进程把AOF重写缓冲区的数据写入到新的AOF文件。
5.3 使用新的AOF文件替换老的AOF文件,完成AOF重写。
Redis 为什么考虑使用 AOF 而不是 WAL 呢?
很多数据库都是采用的 Write Ahead Log(WAL)写前日志,其特点就是先把修改的数据记录到日志中,再进行写数据的提交,可以方便通过日志进行数据恢复。
但是 Redis 采用的却是 AOF(Append Only File)写后日志,特点就是先执行写命令,把数据写入内存中,再记录日志。
如果先让系统执行命令,只有命令能执行成功,才会被记录到日志中。因此,Redis 使用写后日志这种形式,可以避免出现记录错误命令的情况。
另外还有一个原因就是:AOF 是在命令执行后才记录日志,所以不会阻塞当前的写操作。
4.复制Replication
主从复制过程大体可以分为3个阶段:连接建立阶段(即准备阶段)、数据同步阶段、命令传播阶段;下面分别进行介绍。
4.1 连接建立阶段
- 保存主节点信息
- 建立socket连接
- 发送ping命令
- 身份验证
- 发送从节点端口信息
4.2 数据同步阶段
执行流程:
-
全量复制(完整重同步)
Redis通过psync命令进行全量复制的过程如下:
-
- 从节点判断无法进行部分复制,向主节点发送全量复制的请求;或从节点发送部分复制的请求,但主节点判断无法进行部分复制;具体判断过程需要在讲述了部分复制原理后再介绍;
- 主节点收到全量复制的命令后,执行bgsave,在后台生成RDB文件,并使用一个缓冲区(称为复制缓冲区)记录从现在开始执行的所有写命令;
- 主节点的bgsave执行完成后,将RDB文件发送给从节点;从节点首先清除自己的旧数据,然后载入接收的RDB文件,将数据库状态更新至主节点执行bgsave时的数据库状态;
- 主节点将前述复制缓冲区中的所有写命令发送给从节点,从节点执行这些写命令,将数据库状态更新至主节点的最新状态;
- 如果从节点开启了AOF,则会触发bgrewriteaof的执行,从而保证AOF文件更新至主节点的最新状态。
-
部分复制(部分重同步)
- 复制偏移量
主节点和从节点分别维护一个复制偏移量(offset),代表的是主节点向从节点传递的字节数;主节点每次向从节点传播N个字节数据时,主节点的offset增加N;从节点每次收到主节点传来的N个字节数据时,从节点的offset增加N。
- 复制积压缓冲区
复制积压缓冲区是由主节点维护的、固定长度的、先进先出(FIFO)队列,默认大小1MB;当主节点开始有从节点时创建,其作用是备份主节点最近发送给从节点的数据。注意,无论主节点有一个还是多个从节点,都只需要一个复制积压缓冲区。
- 服务器运行ID(runid)
每个Redis节点(无论主从),在启动时都会自动生成一个随机ID(每次启动都不一样),由40个随机的十六进制字符组成;runid用来唯一识别一个Redis节点。
4.3 命令传播阶段
主->从:PING。
每隔指定的时间,主节点会向从节点发送PING命令,这个PING命令的作用,主要是为了让从节点进行超时判断。
从->主:REPLCONF ACK
在命令传播阶段,从节点会向主节点发送REPLCONF ACK命令,频率是每秒1次;命令格式为:REPLCONF ACK {offset},其中offset指从节点保存的复制偏移量。
5.架构模式
5.1 哨兵模式
5.1.1 哨兵模式工作原理
- 每个 Sentinel 以每秒一次的频率向它所知的 Master,Slave 以及其他 Sentinel 节点发送一个
PING
命令; - 如果一个实例(instance)距离最后一次有效回复 PING 命令的时间超过配置文件
own-after-milliseconds
选项所指定的值,则这个实例会被 Sentinel 标记为主观下线; - 如果一个 Master 被标记为主观下线,那么正在监视这个 Master 的所有 Sentinel 要以每秒一次的频率确认 Master 是否真的进入主观下线状态;
- 当有足够数量的 Sentinel(大于等于配置文件指定的值)在指定的时间范围内确认 Master 的确进入了主观下线状态,则 Master 会被标记为客观下线;
- 如果 Master 处于 ODOWN 状态,则投票自动选出新的主节点。将剩余的从节点指向新的主节点继续进行数据复制;
- 在正常情况下,每个 Sentinel 会以每 10 秒一次的频率向它已知的所有 Master,Slave 发送
INFO
命令;当 Master 被 Sentinel 标记为客观下线时,Sentinel 向已下线的 Master 的所有 Slave 发送 INFO 命令的频率会从 10 秒一次改为每秒一次; - 若没有足够数量的 Sentinel 同意 Master 已经下线,Master 的客观下线状态就会被移除。若 Master 重新向 Sentinel 的 PING 命令返回有效回复,Master 的主观下线状态就会被移除。
主要缺陷:单个节点的写能力,存储能力受到单机的限制,动态扩容困难复杂。
5.2 集群模式
为了解决哨兵模式存储受单机的限制,这里引入分片概念。
5.2.1 分片
Redis Cluster 采用虚拟哈希槽分区,所有的键根据哈希函数映射到 0 ~ 16383 整数槽内,计算公式:HASH_SLOT = CRC16(key) % 16384
。每一个节点负责维护一部分槽以及槽所映射的键值数据。
Redis Cluster 提供了灵活的节点扩容和缩容方案。在不影响集群对外服务的情况下,可以为集群添加节点进行扩容也可以下线部分节点进行缩容。可以说,槽是 Redis Cluster 管理数据的基本单位,集群伸缩就是槽和数据在节点之间的移动。
参考文献
- Redis replication | Docs
- Redis persistence | Docs
- 最通俗易懂的 Redis 架构模式详解 - 哈喽沃德先生 - 博客园
- https://mp.weixin.qq.com/s/V2MKyMKtCO1skgWWXBnrgg
- Linux写时拷贝技术(copy-on-write) - as_ - 博客园
- 深入学习Redis(1):Redis内存模型 - 编程迷思 - 博客园
- https://zhuanlan.zhihu.com/p/187596888
- Redis AOF重写阻塞问题分析-腾讯云开发者社区-腾讯云
- Redis持久化——AOF(二) - 明王不动心 - 博客园
相关文章:
浅谈Redis
2007 年,一位程序员和朋友一起创建了一个网站。为了解决这个网站的负载问题,他自己定制了一个数据库。于2009 年开发,称之为Redis。这位意大利程序员是萨尔瓦托勒桑菲利波(Salvatore Sanfilippo),他被称为Redis之父,更…...
整数的个数(信息学奥赛一本通-1067)
【题目描述】 给定k(1<k<100)个正整数,其中每个数都是大于等于1,小于等于10的数。写程序计算给定的k个正整数中,1,5和10出现的次数。 【输入】 输入有两行:第一行包含一个正整数k,第二行包含k个正整数…...
macos的图标过大,这是因为有自己的设计规范
苹果官方链接:App 图标 | Apple Developer Documentation 这个在官方文档里有说明,并且提供了sketch 和 ps 的模板。 figma还提供了模板: Figma...
C++17 命名空间的新特性:简化与优化的典范
文章目录 1. 简化的嵌套命名空间1.1 背景与问题1.2 C17的解决方案1.3 实际应用场景1.4 注意事项 2. 声明多个名称的using声明2.1 背景与问题2.2 C17的解决方案2.3 实际应用场景2.4 注意事项 3. 属性命名空间的简化3.1 背景与问题3.2 C17的解决方案3.3 实际应用场景3.4 注意事项…...
使用python-docx包进行多文件word文字、字符批量替换
1、首先下载pycharm。 2、改为中文。 3、安装python-docx包。 搜索包名字,安装。 4、新建py文件,写程序。 from docx import Documentdef replace1(array1):# 替换词典(标签值按实际情况修改)dic {替换词1: array1[0], 替换…...
模块初阶学习
当我们在过去想要实现一个功能时,例如Swap交换函数时,我们需要不断考虑参数的正确与否。如果是在c语言,我们还需要不断更改函数名字,以防止函数名重复。在c我们可以通过函数名重载解决这个问题,但还是有一些小问题&…...
华为 Ascend 平台 YOLOv5 目标检测推理教程
1. 背景介绍 随着人工智能技术的快速发展,目标检测在智能安防、自动驾驶、工业检测等领域中扮演了重要角色。YOLOv5 是一种高效的目标检测模型,凭借其速度和精度的平衡广受欢迎。 华为 Ascend 推理框架(ACL)是 Ascend CANN 软件…...
16.好数python解法——2024年省赛蓝桥杯真题
问题描述 一个整数如果按从低位到高位的顺序,奇数位(个位、百位、万位…)上的数字是奇数,偶数位(十位、千位、十万位…)上的数字是偶数,我们就称之为“好数”。 给定一个正整数N,请计算从1到N一共有多少个好数。 输入格式 一个整数N。 输出格式 一个整数代表答案。 样例输入 1 …...
在WSL使用gnome终端
默认在windows11环境下使用WSL会打开windows终端,如果想要使用gnome终端可以进行如下操作 确保 WSLg 已启用: WSLg 默认在 Windows 11 和最新版本的 WSL 2 中启用。 检查 WSL 版本: wsl --list --verbose 如果未启用 WSLg,请更…...
评估篇| 大模型评测综述
在传统的自然语言任务下,如分类等,经常会用精确率、F1等指标,来评测模型的好坏。随着大模型技术研究的快速发展,以往的指标,对于大模型评估显得过于单薄。如何准确地评估大语言模型在不同维度的能力水平,已经成为当前研究的热点问题。为了全面考察大语言模型的有效性,研…...
Ubuntu下载zenodo文件Ubuntu download zenodo
一般数据集文件会比较大,直接下载单个压缩包很慢。可以使用代码多线程下载小文件。 环境 Ubuntu22.04 示例代码 pip3 install zenodo_get zenodo_get https://zenodo.org/records/13715870参考 https://github.com/dvolgyes/zenodo_get...
OpenHarmony 5.0.2 Release来了!
版本概述 OpenHarmony 5.0.2 Release版本对标准系统的能力进行持续完善,以快速迭代的方式推出API 14,相比5.0.1 Release版本,重点做出了如下特性新增或增强: 进一步增强ArkUI、图形图像的能力,提供更多组件的高级属性…...
蓝桥杯3519 填充 | 分类讨论
题目传送门 很简单,遍历一次字符串,将‘?’作为0或1处理,发现00和11统计次数即可。 s str(input()) cnt 0 arr [00, 11, 0?, ?0, 1?, ?1, ??] i0 while i < len(s)-1:if s[i:(i2)] in arr:i 2cnt 1else:i 1 print(cnt)END✨...
均值(信息学奥赛一本通-1060)
【题目描述】 给出一组样本数据,包含n个浮点数,计算其均值,精确到小数点后4位。 【输入】 输入有两行,第一行包含一个整数n(n小于100),代表样本容量;第二行包含n个绝对值不超过1000的…...
Windows Docker Desktop安装及使用 Docker 运行 MySQL
Docker Desktop是Docker的官方桌面版,专为Mac和Windows用户设计,提供了一个简单易用的界面来管理和运行Docker容器。它集成了Docker引擎,为开发人员提供了一个快速、可靠、可扩展的方式来构建、运行和管理应用。DockerDesktop的优势在于&…...
关于使用微服务的注意要点总结
一、防止过度设计 微服务的拆分一定要结合团队人员规模来考虑,笔者就曾遇到过一个公司的项目,是从外部采购回来的,微服务划分为十几个应用,我们在此项目基础上进行自行维护和扩展。由于公司业务规模不大,而且二次开发的…...
对于RocksDB和LSM Tree的一些理解
LSM Tree的读写过程 HBase、LevelDB,rocksDB(是一个引擎)底层的数据结构是LSM Tree适合写多读少的场景,都是追加写入内存中的MemTable,写入一条删除(或修改)标记,而不用去访问实际的…...
Pyecharts之特殊图表的独特展示
在数据可视化的世界里,除了常见的柱状图、折线图、饼图等,还有一些特殊的图表可以为我们带来独特的展示效果,帮助我们以更有趣、更直观的方式呈现数据。Pyecharts 为我们提供了多种特殊图表的绘制功能,本文将介绍象形图、水球图和…...
【Uniapp-Vue3】动态设置页面导航条的样式
1. 动态修改导航条标题 uni.setNavigationBarTitle({ title:"标题名称" }) 点击修改以后顶部导航栏的标题会从“主页”变为“动态标题” 2. 动态修改导航条颜色 uni.setNavigationBarColor({ backgroundColor:"颜色" }) 3. 动态添加导航加载动画 // 添加加…...
图像处理算法研究的程序框架
目录 1 程序框架简介 2 C#图像读取、显示、保存模块 3 C动态库图像算法模块 4 C#调用C动态库 5 演示Demo 5.1 开发环境 5.2 功能介绍 5.3 下载地址 参考 1 程序框架简介 一个图像处理算法研究的常用程序逻辑框架,如下图所示 在该框架中,将图像处…...
c语言操作符(详细讲解)
目录 前言 一、算术操作符 一元操作符: 二元操作符: 二、赋值操作符 代码例子: 三、比较操作符 相等与不相等比较操作符: 大于和小于比较操作符: 大于等于和小于等于比较操作符: 四、逻辑操作符 逻辑与&…...
神经网络|(四)概率论基础知识-古典概型
【1】引言 前序学习了线性回归的基础知识,了解到最小二乘法可以做线性回归分析,但为何最小二乘法如此准确,这需要从概率论的角度给出依据。 因此从本文起,需要花一段时间来回顾概率论的基础知识。 【2】古典概型 古典概型是我…...
省市区三级联动
引言 在网页中,经常会遇到需要用户选择地区的场景,如注册表单、地址填写等。为了提供更好的用户体验,我们可以实现一个三级联动的地区选择器,让用户依次选择省份、城市和地区。 效果展示: 只有先选择省份后才可以选择…...
阿里云服务器部署windows随手笔记(Vue+SpringBoot)
服务器管理 创建管理实例 阿里云登录 - 欢迎登录阿里云,安全稳定的云计算服务平台 注意:需要开放端口: 点击实例ID/名称——安全组——安全组列表——管理规则—— 安全组详情——入方向——手动添加端口号(例如目的8080&…...
79,【3】BUUCTF WEB [GXYCTF2019]BabysqliV3.0
进入靶场 现在做多了其他类型,老喜欢这个页面了,老朋友admin password 老规矩,桌面有啥就传啥 第一次点击上传什么都不显示 点了两次就有下面开头的那段话了 他在最后还偷偷骂了一句 确实连不上 再回顾一下题目 buuctf打不开了 只能看别人…...
【问题】Chrome安装不受支持的扩展 解决方案
此扩展程序已停用,因为它已不再受支持 Chromium 建议您移除它。详细了解受支持的扩展程序 此扩展程序已停用,因为它已不再受支持 详情移除 解决 1. 解压扩展 2.打开manifest.json 3.修改版本 将 manifest_version 改为3及以上 {"manifest_ver…...
【AI日记】25.01.25
【AI论文解读】【AI知识点】【AI小项目】【AI战略思考】【AI日记】【读书与思考】 AI kaggle 比赛:Forecasting Sticker Sales 读书 书名:法治的细节 律己 AI:8 小时,良作息:00:30-8:30, 良短视频&…...
C语言程序设计:算法程序的灵魂
文章目录 C语言程序设计:算法程序的灵魂算法数据结构程序数据结构算法数值运算算法非数值运算算法 简单的算法举例【例2.1】求12345【例2.2】有50个学生,要求输出成绩在80分以上的学生的学号和成绩 简单的算法举例【例2.3】判定2000—2500年中的每一年是…...
jupyter配置说明
使用以下命令修改jupyter的配置文件参数: vim /root/.jupyter/jupyter_lab_config.py #这里填写远程访问的IP名,填*则默认是主机IP名 c.ServerApp.ip * # 这里的密码填写上面生成的密钥 c.ServerApp.password ************************************…...
医学图像分割 sliver07_肝脏数据集处理
医学图像分割 sliver07_肝脏数据集处理 先简单介绍一下sliver07 数据集 Sliver07 数据集 Sliver07 (Segmentation of the Liver Competition 2007) 是由 MICCAI(医学图像计算与计算机辅助干预学会)组织的经典医学图像分割数据集,主要用于肝…...
1.25寒假作业
web:[UUCTF 2022 新生赛]ez_rce 打开环境,先理解代码,注重代码审计的能力 定义以get传参的方式传参code函数,所以后面我们肯定要以’code...‘的方式去实现操作,后面禁用了一系列的字符,包括执行函数和一些…...
gorm中关于事务的一些东西
对于赶时间friends,可以只看每个问题的前几点,不用看后面的代码示例!!! 一. tx : db.Begin()开启事务后,怎么结束或者是关闭事务,是通过tx.Rollback()还是tx.Commit()? 在 GORM 中,…...
【Flask】在Flask应用中使用Flask-Limiter进行简单CC攻击防御
前提条件 已经有一个Flask应用。已经安装了Flask和redis服务。 步骤1:安装Redis和Flask-Limiter 首先,需要安装redis和Flask-Limiter库。推荐在生产环境中使用Redis存储限流信息。 pip install redis Flask-Limiter Flask-Limiter会通过redis存储限…...
竞赛算法总结
滑动窗口 1. 数据规模通常是10的5次方 2. 通常用于与字串相关的问题 3. 通常与哈希表配合 查看当前元素的状态 蓝桥_15. 挑选子串-CSDN博客 蓝桥_全部都有的子序列-CSDN博客 ai总结: 滑动窗口算法虽然很强大,但它的解题思路和实现上也有一些关键点可以注意。以下是一…...
记交叉编译asio_dtls过程
虽然编译成功了,但是还是有一些不妥的地方,参考一下就行了。 比如库的版本选择就有待商榷,我这里不是按照项目作者的要求严格用对应的版本编译的,这里也可以注意一下。 编译依赖库asio 下载地址, 更正一下,我其实用…...
【PyCharm】将包含多个参数的 shell 脚本配置到执行文件来调试 Python 程序
要配置 PyCharm 以使用包含多个参数的 shell 脚本(如 run.sh)来调试 Python 程序,您可以按照以下步骤操作: 创建一个新的运行/调试配置: 在 PyCharm 中,点击“运行”菜单旁边的齿轮图标,选择“…...
PID如何调试,如何配置P,I,D值,如何适配pwm的定时器配置,如何给小车配电源
首先你要搞清楚PID公式原理 PID算法解析PID算法解析_pid滤波算法-CSDN博客 然后你要明白调试原理 首先要确定一个电源 电源决定了你后面调试时电机转动速度大小和pwm占空比的关系,电源电压越大那要转到同一速度所需的占空比越小,反之电源电压越小那要…...
微服务学习-Nacos 注册中心实战
1. 注册中心的设计思路 1.1. 微服务为什么会用到注册中心? 服务与服务之间调用需要有服务发现功能;例如订单服务调用库存服务,库存服务如果有多个,订单服务到底调用那个库存服务呢(负载均衡器)࿰…...
音频 PCM 格式 - raw data
文章目录 raw 音频格式:PCM其他音频格式:mp31. 无损压缩音频(类比 PNG 图像)2. 有损压缩音频(类比 JPEG 图像) 试了一下科大讯飞的音频识别云 api,踩了点坑 与本文无关:讯飞的 api 使…...
什么是波士顿矩阵,怎么制作?AI工具一键生成战略分析图!
当今商业环境瞬息万变,每个企业都面临着越来越多的挑战与机遇。如何科学合理地进行战略管理,成为了每个企业决策者必须直面的重要课题。 在众多战略管理框架中,波士顿矩阵作为一种经典的战略管理工具,因其简洁明了的分析方式而广…...
基于微信小程序的助农扶贫系统设计与实现(LW+源码+讲解)
专注于大学生项目实战开发,讲解,毕业答疑辅导,欢迎高校老师/同行前辈交流合作✌。 技术范围:SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容:…...
Maui学习笔记-SignalR简单介绍
SignalR是ASP.NET Core中的一个库,支持服务器与其连接的客服端之间的双象通信,它允许服务器立即将更新的消息推送到客服端,而不是要求客户端轮询服务器来获取更新 创建项目 使用SignalR在服务器实时发送消息给客服端,客服端拿到消息后在UI页面更新 首先创建一个Web API项目 …...
【学习笔记】深度学习网络-深度前馈网络(MLP)
作者选择了由 Ian Goodfellow、Yoshua Bengio 和 Aaron Courville 三位大佬撰写的《Deep Learning》(人工智能领域的经典教程,深度学习领域研究生必读教材),开始深度学习领域学习,深入全面的理解深度学习的理论知识。 在之前的文章中介绍了深度学习中用…...
C#,入门教程(05)——Visual Studio 2022源程序(源代码)自动排版的功能动画图示
上一篇: C#,入门教程(04)——Visual Studio 2022 数据编程实例:随机数与组合https://blog.csdn.net/beijinghorn/article/details/123533838https://blog.csdn.net/beijinghorn/article/details/123533838 新来的徒弟们交上来的C#代码&#…...
每日进步一点点(网安)
1.1 level5 查看源码关键部分 $str strtolower($_GET["keyword"]); $str2str_replace("<script","<scr_ipt",$str); $str3str_replace("on","o_n",$str2);<input namekeyword value".$str3.">关键…...
代理模式 - 代理模式的应用
引言 代理模式(Proxy Pattern)是一种结构型设计模式,它允许你提供一个代理对象来控制对另一个对象的访问。代理对象通常会在客户端和目标对象之间起到中介的作用,从而可以在不改变目标对象的情况下,增加额外的功能或控…...
机器学习-线性回归(对于f(x;w)=w^Tx+b理解)
一、𝑓(𝒙;𝒘) 𝒘T𝒙的推导 学习线性回归,我们那先要对于线性回归的表达公示,有所认识。 我们先假设空间是一组参数化的线性函数: 其中权重向量𝒘 ∈ R𝐷 …...
【Salesforce】审批流程,代理登录 tips
审批流程权限 审批流程权限问题解决方案代理登录代理登录后Logout 审批流程权限 前几天,使用审批流程,但是是两个sandbox,同样的配置,我有管理员权限。但是profile不是管理员,只是通过具备管理员权限的permission set…...
第20篇:Python 开发进阶:使用Django进行Web开发详解
第20篇:使用Django进行Web开发 内容简介 在上一篇文章中,我们深入探讨了Flask框架的高级功能,并通过构建一个博客系统展示了其实际应用。本篇文章将转向Django,另一个功能强大且广泛使用的Python Web框架。我们将介绍Django的核…...
Elastic Agent 对 Kafka 的新输出:数据收集和流式传输的无限可能性
作者:来 Elastic Valerio Arvizzigno, Geetha Anne 及 Jeremy Hogan 介绍 Elastic Agent 的新功能:原生输出到 Kafka。借助这一最新功能,Elastic 用户现在可以轻松地将数据路由到 Kafka 集群,从而实现数据流和处理中无与伦比的可扩…...