当前位置: 首页 > news >正文

工信部网站备案流程网站建设与维护的论述题

工信部网站备案流程,网站建设与维护的论述题,网站开发环境有哪些,网站没域名Redis 核心知识总结 认识 Redis 什么是 Redis#xff1f; Redis 是一个由 C 语言开发并且基于内存的键值型数据库#xff0c;对数据的读写操作都是在内存中完成#xff0c;因此读写速度非常快#xff0c;常用于缓存#xff0c;消息队列、分布式锁等场景。 有以下几个特…Redis 核心知识总结 认识 Redis 什么是 Redis Redis 是一个由 C 语言开发并且基于内存的键值型数据库对数据的读写操作都是在内存中完成因此读写速度非常快常用于缓存消息队列、分布式锁等场景。 有以下几个特征 为了满足不同的业务场景Redis 内置了多种数据类型实现比如 String(字符串)、Hash(哈希)、 List (列表)、Set(无序集合)、Zset(有序集合)、Bitmaps位图、HyperLogLog基数统计、GEO地理信息、Stream流。执行命令由单线程负责每个命令具备原子性。低延迟速度快基于内存、IO 多路复用、良好的编码。 – 什么是 IO 多路复用支持事务 、数据持久化、Lua 脚本、多种集群方案主从复制模式、哨兵模式、切片机群模式、发布/订阅模式内存淘汰机制、过期删除机制等等。没有外部依赖官方推荐生产环境使用 Linux 部署 Redis。支持多语言客户端。 Redis 为什么这么快 因为 Redis 内部做了非常多的性能优化比如 Redis 基于内存内存的访问速度是硬盘的上千倍Redis 基于 Reactor 模式设计开发了一套高效的事件处理模型主要是单线程事件循环和 IO 多路复用Redis 内置了多种优化过后的数据结构实现比如…性能非常高。 为什么要用分布式缓存 提高系统性能将部分数据存储在缓存中可以减少系统对于数据库的访问次数提高系统的性能。通过使用分布式缓存可以将缓存分布在多个节点上从而进一步提高系统的吞吐量和并发性能。提高系统可用性通过使用分布式缓存可以将缓存数据复制到多个节点上从而提高系统的可用性。当某个节点出现故障时可以自动切换到其他节点保证系统的正常运行。提高系统扩展性通过使用分布式缓存可以将缓存数据分散到多个节点上从而支持系统的水平扩展。当系统负载增加时可以通过添加节点来扩展系统的容量从而保证系统的稳定运行。降低成本通过使用分布式缓存可以减少系统对于数据库的访问次数从而降低数据库的负载和成本。在一些应用场景下使用分布式缓存可以替代部分数据库的功能从而进一步降低成本。 分布式缓存常见的技术选型方案有哪些 主要有三种。 第一种是 Memcacheed比较老牌的技术随着 Redis 的发展已经逐渐被淘汰了。第二种是 Tendis一款由腾讯开源的的类似 Redis 的分布式高性能 KV 存储数据库基于知名的开源项目 RocksDBopen in new window 作为存储引擎 100% 兼容 Redis 协议和 Redis4.0 所有数据模型但是其关注度并不高使用的公司也比较少。第三种就是 Redis目前主流的分布式缓存技术。 为什么腾讯开源的Tendis比较少公司使用 因为 Tendis 出现的时间较短相对于其他成熟的分布式 KV 存储系统Tendis 的用户基数和社区规模相对较小因此目前比较少公司使用。此外Tendis 的文档和资料相对较少不太方便开发人员学习和使用。 Redis 和 Memcached 有什么区别 先讲下两者的共同点 它们都是基于内存的数据库一般都用来当做缓存使用都有过期策略两者的性能都非常高。 再分析一下两者的区别 Redis 支持的数据类型更丰富而 Memcached 只支持最简单的 key-value 数据类型Redis 支持数据的持久化可以将内存中的数据保持在磁盘中Redis 重启的时候可以再次加载进行使用而 Memcached 没有持久化功能数据全部存在内存之中Memcached 重启或者挂掉之后数据就没了Redis 原生支持集群模式Memcached 没有原生的集群模式需要依靠客户端来实现往集群中分片写入数据Redis 支持发布订阅模型、Lua 脚本、事务等功能而 Memcached 不支持。 为什么用 Redis 作为 MySQL 的缓存 主要是因为 Redis 具备 【高性能】 和 【高并发】 两种特性。 1、Redis 具备高性能 例如用户第一次访问 MySQL 中的某些数据。这个过程会比较慢因为是从硬盘上读取的。而将该用户访问的数据缓存在 Redis 中下一次再访问这些数据的时候就可以直接从缓存中获取了缓存命中操作 Redis 缓存就是直接操作内存速度会非常快。 如果 MySQL 中的对应数据改变的之后同步改变 Redis 缓存中相应的数据即可不过这里会有 Redis 和 MySQL 双写一致性的问题。 2、Redis 具备高并发 单台设备的 Redis 的 QPSQuery Per Second每秒钟处理完请求的次数 是 MySQL 的 10 倍Redis 单机的 QPS 能轻松破 10w而 MySQL 单机的 QPS 很难破 1w。 所以直接访问 Redis 能够承受的请求是远远大于直接访问 MySQL 的可以考虑把数据库中的部分数据转移到缓存中去这样用户的一部分请求会直接到缓存这里而不用经过数据库。 Redis 数据结构 Redis 数据类型以及使用场景分别是什么 常见的有五种数据类型String字符串Hash哈希List列表Set集合、Zset有序集合。 后续随着版本的更新又支持了四种数据类型 BitMap2.2 版新增、HyperLogLog2.8 版新增、GEO3.2 版新增、Stream5.0 版新增。 分别对应的应用场景是 String 对应缓存对象、常规计数、分布式锁、共享 session 信息等。 List 对应消息队列 但是有两个问题生产者需要自行实现全局唯一 ID不能以消费组形式消费数据。 Hash 对应缓存对象、购物车等。 Set 对应聚合计算并集、交集、差集场景比如点赞、共同关注、抽奖活动等。 Zset 对应排序场景比如排行榜、电话和姓名排序等。 BitMap位图对应二值状态统计的场景比如签到、判断用户登陆状态、连续签到用户总数等 HyperLogLog基数统计对应海量数据基数统计的场景比如百万级网页 UV 计数等 GEO地理信息对应存储地理位置信息的场景比如滴滴打车 Stream流对应消息队列 相比于基于 List 类型实现的消息队列 有这两个特有的特性自动生成全局唯一消息ID支持以消费组形式消费数据。 五种常见的 Redis 数据类型是怎么实现 String 类型的内部实现 String 类型的底层的数据结构实现主要是 SDS简单动态字符串。之所以没有使用 C 语言的字符串表示是因为 SDS 相比于 C 的原生字符串 SDS 不仅可以保存文本数据还可以保存二进制数据。 这是因为 SDS 使用 len 属性的值而不是空字符来判断字符串是否结束 并且 SDS 的所有 API 都会以处理二进制的方式来处理 SDS 存放在 buf[] 数组里的数据。 所以 SDS 不光能存放文本数据而且能保存图片、音频、视频、压缩文件这样的二进制数据。 SDS 获取字符串长度的时间复杂度是 O(1)。 这是因为 C 语言的字符串并不记录自身长度所以获取长度的复杂度为 O(n)而 SDS 结构里用 len 属性记录了字符串长度所以复杂度为 O(1)。 Redis 的 SDS API 是安全的拼接字符串不会造成缓冲区溢出。 这是因为 SDS 在拼接字符串之前会检查 SDS 空间是否满足要求如果空间不够会自动扩容所以不会导致缓冲区溢出的问题。 List 类型的内部实现 3.2 之前List 类型的底层数据结构是由双向链表或压缩列表实现的 如果列表的元素个数小于 512 个列表每个元素的值都小于 64 字节Redis 会使用压缩列表作为 List 类型的底层数据结构 上面的 51264 都是默认值可由 list-max-ziplist-entries 配置 其他情况的话Redis 会使用双向链表作为 List 类型的底层数据结构 注意但是在 Redis 3.2 版本之后List 数据类型底层数据结构就只由 quicklist 实现了替代了双向链表和压缩列表。 Hash 类型的内部实现 Hash 类型的底层数据结构是由压缩列表或哈希表实现的 如果哈希类型元素个数小于 512 个所有值小于 64 字节的话Redis 会使用压缩列表作为 Hash 类型的底层数据结构其他情况的话Redis 会使用哈希表作为 Hash 类型的底层数据结构。 注意在 Redis 7.0 中压缩列表数据结构已经废弃了交由 listpack 数据结构来实现了。 Set 类型的内部实现 Set 类型的底层数据结构是由整数集合或哈希表实现的 如果集合中的元素都是整数且元素个数小于 512 个Redis 会使用整数集合作为 Set 类型的底层数据结构如果集合中的元素不满足上面条件则 Redis 会使用哈希表作为 Set 类型的底层数据结构。 Zset 类型的内部实现 在 7.0 之前Zset 类型的底层数据结构是由压缩列表或跳表实现的 如果有序集合的元素个数小于 128 个并且每个元素的值小于 64 字节时Redis 会使用压缩列表作为 Zset 类型的底层数据结构如果有序集合的元素不满足上面的条件Redis 会使用跳表作为 Zset 类型的底层数据结构 注意在 Redis 7.0 中压缩列表数据结构已经废弃了交由 listpack 数据结构来实现了。 Redis 线程模型 Redis 是单线程吗 是。但是Redis 程序不是单线程的因为 Redis 在启动的时候。会启动后台线程BIO Redis 在 2.6 版本会启动 2 个后台线程分别处理关闭文件、AOF 刷盘这两个任务 在 4.0 版本之后新增了一个新的后台线程用来异步释放 Redis 内存也就是 lazyfree 线程。 例如执行 unlink key、 flushdb async、 flushall async 等命令会把这些删除操作交给后台线程来执行好处是不会导致 Redis 主线程卡顿。因此当我们要删除一个大 key 的时候不要使用 del 命令删除因为 del 是在主线程处理的这样会导致 Redis 主线程卡顿因此我们应该使用 unlink 命令来异步删除大 key。 为什么 Redis 要为「关闭文件、AOF 刷盘、释放内存」这些任务启动后台线程 是因为这些任务的操作都是很耗时的如果把这些任务都放在主线程来处理那么 Redis 主线程就很容易发生阻塞这样就无法处理后续的请求了。 Redis 单线程指的是: 【 接收客户端请求 - 解析请求 - 进行数据读写等操作 - 发送数据给客户端 】 这个过程是由一个线程主线程来完成的所以我们常说 Redis 是单线程的。 Redis 单线程模式是怎样的 Redis 单线程模式是指 Redis 服务器在运行时只使用一个线程来处理客户端的请求和所有的数据操作这个线程同时也负责了网络 I/O、内存管理、磁盘同步等操作因此 Redis 的性能非常高。在单线程模式下Redis 使用了多路复用技术来实现非阻塞 I/O从而避免了线程切换和线程同步所带来的性能消耗。此外Redis 通过对数据的操作进行批量化和异步化来进一步提高性能。需要注意的是虽然 Redis 是单线程模式但是它可以通过多个进程或者多个实例的方式来进行横向扩展从而提高整个系统的性能和可伸缩性。 Redis 采用单线程为什么还这么快 主要有以下三个原因 Redis 的大部分操作都在内存中完成并且采用了高效的数据结构因此 Redis 性能表现的瓶颈可能是机器的内存或者网络带宽而并非 CPU既然 CPU 不是瓶颈那么自然就采用单线程的解决方案了Redis 采用单线程模型避免了多线程之间的竞争省去了多线程切换带来的时间和性能上的开销而且也不会导致死锁问题。Redis 采用了 I/O 多路复用机制来处理大量的客户端 Socket 请求。IO 多路复用机制是指一个线程处理多个 IO 流就是我们经常听到的 select/epoll 机制。简单来说在 Redis 只运行单线程的情况下该机制允许内核中同时存在多个监听 Socket 和已连接 Socket。内核会一直监听这些 Socket 上的连接请求或数据请求。一旦有请求到达就会交给 Redis 线程处理这就实现了一个 Redis 线程处理多个 IO 流的效果。 什么是客户端 Socket 请求 客户端 Socket 请求指的是客户端通过建立 Socket 连接向服务器发送请求的过程。在网络编程中Socket 是一种通信机制它提供了一种通过网络进行进程间通信的方式。 在客户端Socket 请求中客户端首先需要创建一个 Socket 对象并指定要连接的服务器的IP地址和端口号。然后客户端可以向服务器发送请求请求可以是任何形式的数据例如HTTP请求、FTP请求等。 客户端发送请求后服务器收到请求后会进行处理并返回响应给客户端。客户端接收到服务器返回的响应后可以对响应进行处理例如解析HTML页面、保存文件等。最后客户端关闭Socket连接释放资源。 客户端Socket请求是网络编程中非常常见的一种操作它可以实现各种网络应用程序例如网页浏览器、聊天工具、文件下载器等。 Redis 6.0 之前为什么使用单线程 众所周知Redis 的主要工作网络 I/O 和执行命令一直是单线程模型而单线程的程序是无法利用服务器的多核 CPU 的但是 CPU 并不是制约 Redis 性能表现的瓶颈所在所以 Redis 核心网络模型使用单线程并没有什么问题如果想要使用服务的多核 CPU可以在一台服务器上启动多个节点或者采用分片集群的方式。 还有一点就是使用单线程后可维护性高多线程模型虽然在某些方面表现优异但是它却引入了程序执行顺序的不确定性带来了并发读写的一系列问题比如增加了系统复杂度、同时可能存在线程切换、甚至加锁解锁、死锁造成的性能损耗。 Redis 6.0 之后为什么引入了多线程 这是因为在 Redis 6.0 版本之后也采用了多个 I/O 线程来处理网络请求这是因为随着网络硬件的性能提升Redis 的性能瓶颈有时会出现在网络 I/O 的处理上。 所以为了提高网络 I/O 的并行度Redis 6.0 对于网络 I/O 采用多线程来处理。但是对于命令的执行Redis 仍然使用单线程来处理所以大家不要误解 Redis 有多线程同时执行命令。 简单来说就是为了提升性能采用多线程来处理 网络 I/O Redis 持久化 Redis 如何实现数据不丢失 Redis 通过实现数据持久化的机制来保证数据不丢失这个机制会把数据存储到磁盘这样在 Redis 重启后就能够从磁盘中恢复原有的数据。 Redis 共有三种数据持久化的方式 AOF 日志每执行一条写操作命令就把该命令以追加的方式写入到一个文件里RDB 快照将某一时刻的内存数据以二进制的方式写入磁盘混合持久化方式Redis 4.0 新增的方式集成了 AOF 和 RBD 的优点 AOF 日志是如何实现的 Redis 在执行完一条写操作命令后就会把该命令以追加的方式写入到一个文件里然后 Redis 重启时会读取该文件记录的命令然后逐一执行命令的方式来进行数据恢复。 简单来说就是客服端 发送请求到 Redis 中然后会记录命令到 AOF 文件中 流程如下图 文件内容解释如下 文件先是用 [*3] 表示当前命令有三个部分每部分都是以 [$数字] 开头后面紧跟着具体的命令、键或值。然后这里的 [数字] 表示这部分中的命令、键或值一共有多少字节。 例如[$3 set] 表示这部分有 3 个字节也就是 [set] 命令这个字符串的长度。 为什么先执行命令再把数据写入日志呢 因为这么做有两个好处 避免额外的检查开销因为如果先将写操作命令记录到 AOF 日志里再执行该命令的话如果当前的命令语法有问题并且不进行命令语法检查的话该错误的命令记录到 AOF 日志里后Redis 在使用日志恢复数据时就可能会出错。不会阻塞当前写操作命令的执行因为当写操作命令执行成功后才会将命令记录到 AOF 日志。 当然这样做也会带来风险 数据可能会丢失执行写操作命令和记录日志是两个过程那当 Redis 在还没来得及将命令写入到硬盘时服务器发生宕机了这个数据就会有丢失的风险。可能阻塞其他操作由于写操作命令执行成功后才记录到 AOF 日志所以不会阻塞当前命令的执行但因为 AOF 日志也是在主线程中执行所以当 Redis 把日志文件写入磁盘的时候还是会阻塞后续的操作无法执行。 Redis 写入 AOF 日志的过程 具体来说就是 Redis 执行完写操作命令后会将命令追加到 server.aof_buf 缓冲区然后通过 write() 系统调用将 aof_buf 缓冲区的数据写入到 AOF 文件此时数据并没有写入到硬盘而是拷贝到了内核缓冲区 page cache等待内核将数据写入硬盘具体内核缓冲区的数据什么时候写入到硬盘由内核决定。 如图 AOF 写回策略有几种 Redis 提供了 3 种写回硬盘的策略控制的就是具体内核缓冲区的数据什么时候写入到硬盘的过程。 在 Redis.conf 配置文件中的 appendfsync 配置项可以有以下 3 种参数可填 Always这个单词的意思是「总是」所以它的意思是每次写操作命令执行完后同步将 AOF 日志数据写回硬盘Everysec默认这个单词的意思是「每秒」所以它的意思是每次写操作命令执行完后先将命令写入到 AOF 文件的内核缓冲区然后每隔一秒将缓冲区里的内容写回到硬盘No意味着不由 Redis 控制写回硬盘的时机转交给操作系统控制写回的时机也就是每次写操作命令执行完后先将命令写入到 AOF 文件的内核缓冲区再由操作系统决定何时将缓冲区内容写回硬盘。 这 3 个写回策略的优缺点如下 AOF 日志过大会触发什么机制 会触发 AOF 重写机制。 因为 AOF 日志是一个文件随着执行的写操作命令越来越多文件的大小也会越来越大相应的也就会带来性能问题。比如重启 Redis 后需要读 AOF 文件的内容以恢复数据如果文件过大整个恢复的过程就会很慢。 所以为了避免 AOF 文件越写越大Redis 提供了 AOF 重写机制当 AOF 文件的大小超过所设定的阈值后Redis 就会启用 AOF 重写机制来压缩 AOF 文件。 AOF 重写机制是怎么实现的 AOF 重写机制是在重写时读取当前数据库的所有键值对即数据然后将每一个键值对转换成一条命令记录到一个新的 AOF 文件等到全部记录完后就会将新的 AOF 文件替换掉现有的 AOF 文件。相当于去掉了历史命令压缩 AOF 文件。 举个例子在没有使用重写机制前假设前后执行了 set name xiaolin 和 set name xiaolincoding 这两个命令的话就会将这两个命令记录到 AOF 文件。但是在使用重写机制后就会读取 name 最新的 value键值对 然后用一条 set name xiaolincoding 命令记录到新的 AOF 文件之前的第一个命令就没有必要记录了因为它属于「历史」命令没有作用了。这样一来一个键值对在重写日志中只用一条命令就行了。 重写 AOF 日志的过程是怎样的 Redis 的重写 AOF 过程是由后台子进程 bgrewriteaof 来完成的由子进程来完成有两个好处 子进程进行 AOF 重写期间主进程可以继续处理命令请求从而避免阻塞主进程 子进程带有主进程的数据副本这里使用子进程而不是线程不会降低性能。 因为如果是使用线程多线程之间会共享内存那么在修改共享内存数据的时候需要通过加锁来保证数据的安全而这样就会降低性能。 而使用子进程创建子进程时父子进程是共享内存数据的不过这个共享的内存只能以只读的方式而当父子进程任意一方修改了该共享内存就会发生「写时复制」于是父子进程就有了独立的数据副本就不用加锁来保证数据安全。 触发重写机制后主进程就会创建重写 AOF 的子进程此时父子进程共享物理内存重写子进程只会对这个内存进行只读重写 AOF 子进程会读取数据库里的所有数据并逐一把内存数据的键值对转换成一条命令再将命令记录到重写日志新的 AOF 文件。 如果主进程修改了已经存在键值对会产生什么问题 在重写过程中主进程依然可以正常处理命令那问题来了重写 AOF 日志过程中如果主进程修改了已经存在 key-value那么会发生写时复制此时这个 key-value 数据在子进程的内存数据就跟主进程的内存数据不一致了这时要怎么办呢 为了解决这种数据不一致问题Redis 设置了一个 AOF 重写缓冲区这个缓冲区在创建 bgrewriteaof 子进程之后开始使用。 在重写 AOF 期间当 Redis 执行完一个写命令之后它会同时将这个写命令写入到 「AOF 缓冲区」和 「AOF 重写缓冲区」。 当子进程完成 AOF 重写工作后会向主进程发送一条信号主进程收到该信号后会调用一个信号处理函数该函数主要做以下工作 将 AOF 重写缓冲区中的所有内容追加到新的 AOF 的文件中使得新旧两个 AOF 文件所保存的数据库状态一致将新的 AOF 的文件进行改名覆盖现有的 AOF 文件。 信号函数执行完后主进程就可以继续像往常一样处理命令了。 具体过程如下图 什么是 RDB 快照 RDB 快照就是记录某一个瞬间的内存数据记录的是实际数据而 AOF 文件记录的是命令操作的日志而不是实际的数据。 因此在 Redis 恢复数据时 RDB 恢复数据的效率会比 AOF 高些因为直接将 RDB 文件读入内存就可以不需要像 AOF 那样还需要额外执行操作命令的步骤才能恢复数据。 为什么需要 RDB 快照 因为 AOF 日志记录的是操作命令不是实际的数据所以用 AOF 方法做故障恢复时需要全量把日志都执行一遍一旦 AOF 日志非常多势必会造成 Redis 的恢复操作缓慢。 为了解决这个问题Redis 增加了 RDB 快照。 有了 RDB为什么还需要 AOF 在 Redis 中RDB 和 AOF 都是持久化的方式用于将内存中的数据保存到磁盘中以便在 Redis 重启时能够重新加载数据。 RDB 持久化方式是将 Redis 在某个时间点上的数据集以快照的形式写入磁盘中因此 RDB 的优点是备份和恢复速度比较快且生成的文件比 AOF 文件小比较适合用于备份和灾难恢复。AOF 持久化方式是通过记录 Redis 服务器所执行的所有写命令来记录数据变化可以将每个写命令都写入日志文件中当 Redis 重启时通过读取 AOF 文件中的命令来重建原始数据因此 AOF 的优点是数据的安全性更好且适合用于数据的持久化存储。 综上所述尽管 RDB 可以提供快速备份和恢复的功能但 AOF 持久化方式可以提供更高级别的数据保护因此在一些关键业务场景下金融、电商我们需要同时使用 RDB 和 AOF 两种持久化方式以确保数据的完整性和可靠性。 RDB 是如何实现的会阻塞线程吗 Redis 提供了两个命令来生成 RDB 文件分别是 save 和 bgsave他们的区别就在于是否在「主线程」里执行 执行 save 命令就会在主线程生成 RDB 文件由于和执行操作命令在同一个线程所以如果写入 RDB 文件的时间太长会阻塞主线程执行 bgsave 命令会创建一个子进程来生成 RDB 文件这样可以避免主线程的阻塞 Redis 还可以通过配置文件的选项来实现每隔一段时间自动执行一次 bgsave 命令默认会提供以下配置 // 只要满足下面条件的任意一个就会执行 bgsave save 900 1 // 900 秒之内对数据库进行了至少 1 次修改 save 300 10 // 300 秒之内对数据库进行了至少 10 次修改 save 60 10000 // 60 秒之内对数据库进行了至少 1万 次修改。这里的选项名虽然叫 save但实际上执行的是 bgsave 命令也就是会创建子进程来生成 RDB 快照文件。 这里提一点Redis 的快照是全量快照也就是说每次执行快照都是把内存中的「所有数据」都记录到磁盘中。所以执行快照是一个比较重的操作如果频率太频繁可能会对 Redis 性能产生影响。如果频率太低服务器故障时丢失的数据会更多。 RDB 在执行快照的时候数据能修改吗 可以在执行 bgsave 过程中Redis 依然可以继续处理操作命令也就是数据是能被修改的关键的技术就在于写时复制技术。 什么是写时复制技术 写时复制Copy-On-Write简称为COW是一种常见的内存管理技术也被广泛应用于数据库系统中包括 Redis。 在 Redis 中写时复制是指当父进程复制自己创建的子进程时子进程与父进程共享相同的内存空间只有在子进程需要修改某个内存页面时才会将该页面复制一份到子进程的独立内存空间中从而实现了父子进程之间的内存隔离避免了频繁的内存复制提高了内存的使用效率。Redis 使用写时复制来实现主从复制和 Sentinel 高可用性以及 AOF 持久化中的 fork 操作。 因为在执行 bgsave 命令的时候会通过 fork() 创建子进程此时子进程和父进程是共享同一片内存数据的因为创建子进程的时候会复制父进程的页表但是页表指向的物理内存还是一个此时如果主线程执行读操作则主线程和 bgsave 子进程互相不影响。 如果主线程执行写操作则被修改的数据会复制一份副本然后 bgsave 子进程会把该副本数据写入 RDB 文件在这个过程中主线程仍然可以直接修改原来的数据。 为什么会有混合持久化 是为了集成 AOF 和 RDB 的优点Redis 4.0 提出了混合使用 AOF 日志和内存快照也叫混合持久化既保证了 Redis 重启速度又降低数据丢失风险。 先分析下 AOF 和 RDB 的优缺点 RDB 优点是数据恢复速度快但是快照的频率不好把握。频率太低丢失的数据就会比较多频率太高就会影响性能。 AOF 优点是丢失数据少安全性高但是数据恢复不快。 混合持久化是如何实现的 混合持久化工作在 AOF 日志重写过程。 当开启了混合持久化时在 AOF 重写日志时fork 出来的重写子进程会先将与主线程共享的内存数据以 RDB 方式写入到 AOF 文件然后主线程处理的操作命令会被记录在重写缓冲区里重写缓冲区里的增量命令会以 AOF 方式写入到 AOF 文件写入完成后通知主进程将新的含有 RDB 格式和 AOF 格式的 AOF 文件替换旧的的 AOF 文件。 也就是说使用了混合持久化AOF 文件的前半部分是 RDB 格式的全量数据后半部分是 AOF 格式的增量数。 这种实现方式有什么好处 这样的好处在于重启 Redis 加载数据的时候由于前半部分是 RDB 内容这样加载的时候速度会很快。 加载完 RDB 的内容后才会加载后半部分的 AOF 内容这里的内容是 Redis 后台子进程重写 AOF 期间主线程处理的操作命令可以使得数据更少的丢失。 简单来说就是既可以加快加载速度又可以减少数据的丢失。 混合持久化的优缺点是什么 优点 **结合了 RDB 和 AOF 持久化的优点**开头为 RDB 的格式使得 Redis 可以更快的启动同时结合 AOF 的优点又减低了大量数据丢失的风险。 缺点 **可读性差**因为在 AOF 文件中添加了 RDB 格式的内容会使得 AOF 文件的可读性变得很差**兼容性差**因为混合持久化机制是 Redis 4.0 版本之后才有的如果开启了混合持久化 AOF 文件就不能用在 Redis 4.0 之前的版本了。 Redis 集群 Redis 是如何实现高服务高可用的 要想设计一个高可用的 Redis 服务一定要从 Redis 的多服务节点来考虑比如 Redis 的主从复制、哨兵模式、切片集群。 主从复制 主从复制是 Redis 高可用服务的最基础的保证。 实现方案 就是将从前的一台 Redis 服务器同步数据到多台 Redis 从服务器上即一主多从的模式且主从服务器之间采用的是**「读写分离」**的方式。 主服务器和从服务器的区别 【主服务器】可以进行读写操作当发生写操作时自动将写操作同步给【从服务器】而【从服务器】一般是只读并接受【主服务器】同步过来写操作命令然后执行这条命令。 也就是说所有的数据修改只在主服务器上进行然后将最新的数据同步给从服务器这样就使得主从服务器的数据是一致的但不是强一致性。 注意主从服务器之间的命令复制是异步进行的。 具体来说 在主从服务器命令传播阶段主服务器收到新的写命令后会发送给从服务器。但是主服务器并不会等到从服务器实际执行完命令后再把结果返回给客户端而是主服务器自己在本地执行完命令后就会向客户端返回结果了。 如果从服务器还没有执行主服务器同步过来的命令主从服务器间的数据就不一致了。 所以无法实现强一致性保证主从数据时时刻刻保持一致数据不一致是难以避免的。 哨兵模式 Sentinel 问题 在使用 Redis 主从服务的时候会有一个问题就是当 Redis 的主从服务器出现故障宕机时需要手动进行恢复。 解决方案 为了解决这个问题Redis 增加了哨兵模式Redis Sentinel因为哨兵模式做到了可以监控主从服务器并且提供主从节点故障转移的功能。 总结 哨兵Sentinel机制是 Redis 在 2.8 版本以后提供的。 它的作用是实现主从节点故障转移。它会监测主节点是否存活如果发现主节点挂了它就会选举一个从节点切换为主节点并且把新主节点的相关信息通知给从节点和客户端。 哨兵一般是以集群的方式部署至少需要 3 个哨兵节点哨兵集群主要负责三件事情监控、选主、通知。 哨兵节点通过 Redis 的发布者/订阅者机制哨兵之间可以相互感知相互连接然后组成哨兵集群同时哨兵又通过 INFO 命令在主节点里获得了所有从节点连接信息于是就能和从节点建立连接并进行监控了。 1、第一轮投票判断主节点下线 当哨兵集群中的某个哨兵判定主节点下线主观下线后就会向其他哨兵发起命令其他哨兵收到这个命令后就会根据自身和主节点的网络状况做出赞成投票或者拒绝投票的响应。 当这个哨兵的赞同票数达到哨兵配置文件中的 quorum 配置项设定的值后这时主节点就会被该哨兵标记为「客观下线」。 2、第二轮投票选出哨兵leader 某个哨兵判定主节点客观下线后该哨兵就会发起投票告诉其他哨兵它想成为 leader想成为 leader 的哨兵节点要满足两个条件 第一拿到半数以上的赞成票第二拿到的票数同时还需要大于等于哨兵配置文件中的 quorum 值。 3、由哨兵 leader 进行主从故障转移 选举出了哨兵 leader 后就可以进行主从故障转移的过程了。该操作包含以下四个步骤 第一步在已下线主节点旧主节点属下的所有「从节点」里面挑选出一个从节点并将其转换为主节点选择的规则 过滤掉已经离线的从节点过滤掉历史网络连接状态不好的从节点将剩下的从节点进行三轮考察优先级、复制进度、ID 号。在每一轮考察过程中如果找到了一个胜出的从节点就将其作为新主节点。 第二步让已下线主节点属下的所有「从节点」修改复制目标修改为复制「新主节点」第三步将新主节点的 IP 地址和信息通过「发布者/订阅者机制」通知给客户端第四步继续监视旧主节点当这个旧主节点重新上线时将它设置为新主节点的从节点 思考哨兵是怎么实现的 切片集群模式 Cluster 场景 当 Redis 缓存数据量大到一台服务器无法缓存时就需要使用 Redis 切片集群Redis Cluster方案。 实现方式 它将数据分布在不同的服务器上以此来降低系统对单主节点的依赖从而提高 Redis 服务的读写性能。 Redis Cluster 方案 Redis Cluster 方案采用哈希槽Hash Slot来处理数据和节点之间的映射关系。 在 Redis Cluster 方案中一个切片集群共有 16384 个哈希槽这些哈希槽类似于数据分区每个键值对都会根据它的 key被映射到一个哈希槽中。 具体执行过程分为两大步 根据键值对的 key按照 CRC16 算法 计算一个 16 bit 的值。再用 16bit 值对 16384 取模得到 0~16383 范围内的模数每个模数代表一个相应编号的哈希槽。 哈希槽是如何被映射到具体的 Redis 节点上的 有两种方式 平均分配 在使用 cluster create 命令创建 Redis 集群时Redis 会自动把所有哈希槽平均分布到集群节点上。比如集群中有 9 个节点则每个节点上槽的个数为 16384/9 个。手动分配 可以使用 cluster meet 命令手动建立节点间的连接组成集群再使用 cluster addslots 命令指定每个节点上的哈希槽个数。 注意在手动分配哈希槽时需要把 16384 个槽都分配完否则 Redis 集群无法正常工作。 数据、哈希槽以及节点三者的映射分布关系 通过命令手动分配哈希槽比如节点 1 保存哈希槽 0 和 1节点 2 保存哈希槽 2 和 3如下所示 redis-cli -h 192.168.1.10 –p 6379 cluster addslots 0,1 redis-cli -h 192.168.1.11 –p 6379 cluster addslots 2,3在集群运行的过程中key1 和 key2 计算完 CRC16 值后对哈希槽总个数 4 进行取模再根据各自的模数结果就可以被映射到哈希槽 1对应节点1 和 哈希槽 2对应节点2。 集群脑裂导致数据丢失怎么办 什么是脑裂 就好比一个人有两个大脑不知道受谁控制。 比如出现了两个主节点的情况。 那么在 Redis 中集群脑裂产生数据丢失的现象是怎样的呢 在 Redis 主从架构中部署方式一般是「一主多从」主节点提供写操作从节点提供读操作。 如果主节点的网络突然发生了问题它与所有的从节点都失联了但是此时的主节点和客户端的网络是正常的这个客户端并不知道 Redis 内部已经出现了问题还在照样的向这个失联的主节点写数据过程A此时这些数据被旧主节点缓存到了缓冲区里因为主从节点之间的网络问题这些数据都是无法同步给从节点的。 这时哨兵也发现主节点失联了它就认为主节点挂了但实际上主节点正常运行只是网络出问题了于是哨兵就会在「从节点」中选举出一个 leader 作为主节点这时集群就有两个主节点了 —— 也就是脑裂出现了。 然后网络突然好了哨兵因为之前已经选举出一个新主节点了它就会把旧主节点降级为从节点A然后从节点A会向新主节点请求数据同步因为第一次同步是全量同步的方式此时的从节点A会清空掉自己本地的数据然后再做全量同步。所以之前客户端在过程 A 写入的数据就会丢失了也就是集群产生脑裂数据丢失的问题。 总结一句话就是由于网络问题集群节点之间失去联系。主从数据不同步哨兵重新平衡选举产生两个主服务。等网络恢复旧主节点会降级为从节点再与新主节点进行同步复制的时候由于从节点会清空自己的缓冲区所以导致之前客户端写入的数据丢失了。 解决方案 当主节点发现从节点下线或者通信超时的总数量小于阈值时就禁止主节点进行写数据直接把错误返回给客户端。 在 Redis 的配置文件中有两个参数我们可以设置 min-slaves-to-write x主节点必须要有至少 x 个从节点连接如果小于这个数主节点会禁止写数据。 min-slaves-max-lag x主从数据复制和同步的延迟不能超过 x 秒如果超过主节点会禁止写数据。 我们可以把 min-slaves-to-write 和 min-slaves-max-lag 这两个配置项搭配起来使用分别给它们设置一定的阈值假设为 N 和 T。 这两个配置项组合后的要求是主库连接的从库中至少有 N 个从库和主库进行数据复制时的 ACK 确认字符消息延迟不能超过 T 秒否则主库就不会再接收客户端的写请求了。 即使原主库是假故障它在假故障期间也无法响应哨兵心跳也不能和从库进行同步自然也就无法和从库进行 ACK 确认了。这样一来min-slaves-to-write 和 min-slaves-max-lag 的组合要求就无法得到满足原主库就会被限制接收客户端写请求客户端也就不能在原主库中写入新数据了。 等到新主库上线时就只有新主库能接收和处理客户端请求此时新写的数据会被直接写到新主库中。而原主库会被哨兵降为从库即使它的数据被清空了也不会有新数据丢失。 总结来说就是 解决方案是通过某种限制条件来禁止主节点进行写数据。 在 Redis 的配置文件中设置两个参数指明主节点至少要连接的从节点个数 N 以及主从数据复制和同步的延迟不能超过 T 秒一旦达不到这个组合要求就限制主节点接收客户端的写请求不再写入新数据等哨兵主从切换完成后新写的数据就会被直接写到新主节点上从而避免避免了闹裂现象的发生也就不会发生数据丢失了。 Redis 过期删除与内存淘汰 Redis 给缓存数据设置过期时间有啥用 主要有三个作用 释放空间当缓存中的数据过期后Redis 会自动将其删除从而释放空间。这样可以避免缓存中存储过多的过期数据占用过多的内存空间导致缓存性能下降。提高缓存命中率设置合理的缓存过期时间可以使得缓存中存储的数据都是最新的有效的数据。这样可以提高缓存命中率减少请求直接访问后端数据库的次数从而提高系统的性能。避免缓存污染问题过期时间可以避免缓存污染问题即缓存中存储了过期、损坏或者恶意的数据。当缓存中的数据过期后Redis 会自动将其删除从而避免应用程序获取到错误的数据。 如果过期时间设置得太短可能会导致缓存命中率降低请求直接访问后端数据库的次数增多 如果过期时间设置得太长可能会导致缓存中存储的数据不是最新的从而影响系统的性能。 Redis 使用过的过期删除策略是什么 Redis 使用的过期删除策略是「惰性删除定期删除」这两种策略配和使用。负责删除已过期的键值对。 每当我们对一个 key 设置了过期时间时Redis 会把该 key 带上过期时间存储到一个过期字典中。 当我们查询一个 key 时Redis 首先检查该 key 是否存在于过期字典中 如果不在则正常读取键值如果存在则会获取该 key 的过期时间然后与当前系统时间进行比对如果比系统时间大那就没有过期否则判定该 key 已过期。 什么是惰性删除策略 惰性删除策略的做法是不主动删除过期键每次从数据库访问 key 时都检测 key 是否过期如果过期则删除该 key返回 null 给客户端。 惰性删除策略的优点 因为每次访问时才会检查 key 是否过期所以此策略只会使用很少的系统资源因此惰性删除策略对 CPU 时间最友好。 惰性删除策略的缺点 如果一个 key 已经过期而这个 key 又仍然保留在数据库中那么只要这个过期 key 一直没有被访问它所占用的内存就不会释放造成了一定的内存空间浪费。所以惰性删除策略对内存不友好。 总结来说就是 占用的系统资源少对 CPU 时间友好但会浪费一定的内存空间对内存不友好。以空间换时间。 什么是定期删除策略 定期删除策略的做法是每隔一段时间「随机」从数据库中取出一定数量的 key 进行检查并删除其中的过期key。 具体流程是 从过期字典中随机抽取 20 个 key 检查这 20 个 key 是否过期并删除已过期的 key 如果本轮检查的已过期 key 的数量超过 5 个抽取的个数*25%也就是「已过期 key 的数量」占比「随机抽取 key 的数量」大于 25%则继续重复步骤 1 如果已过期的 key 比例小于 25%则停止继续删除过期 key然后等待下一轮再检查。 定期抽取 检查并删除 判断过期 key 是否超过 25% 定期删除策略的优点 通过限制删除操作执行的时长和频率来减少删除操作对 CPU 的影响同时也能删除一部分过期的数据减少了过期键对空间的无效占用。 定期删除策略的缺点 难以确定删除操作执行的时长和频率。如果执行的太频繁就会对 CPU 不友好如果执行的太少那又和惰性删除一样了过期 key 占用的内存不会及时得到释放。 定期删除对内存更加友好惰性删除对 CPU 更加友好。两者各有千秋所以 Redis 采用的是 定期删除惰性/懒汉式删除 。 Redis 持久化时对过期键会如何处理 Redis 持久化文件有两种格式RDB 和 AOF。 下面来说下过期键在这两种格式中的呈现状态。 先讲下 RDBRDB 文件分为两个阶段RDB 文件生成阶段和加载阶段。 **RDB 文件生成阶段**从内存状态持久化成 RDB文件的时候会对 key 进行过期检查过期的键「不会」被保存到新的 RDB 文件中因此 Redis 中的过期键不会对生成新 RDB 文件产生任何影响。**RDB 加载阶段**RDB 加载阶段时要看服务器是主服务器还是从服务器分别对应以下两种情况 如果 Redis 是「主服务器」运行模式的话在载入 RDB 文件时程序会对文件中保存的键进行检查过期键「不会」被载入到数据库中。所以过期键不会对载入 RDB 文件的主服务器造成影响如果 Redis 是「从服务器」运行模式的话在载入 RDB 文件时不论键是否过期都会被载入到数据库中。但由于主从服务器在进行数据同步时从服务器的数据会被清空。所以一般来说过期键对载入 RDB 文件的从服务器也不会造成影响。 再讲下 AOF 的情况AOF 文件也分为两个阶段AOF 文件写入阶段和 AOF 重写阶段。 AOF 文件写入阶段当 Redis 以 AOF 模式持久化时如果数据库某个过期键还没被删除那么 AOF 文件会保留此过期键当此过期键被删除后Redis 会向 AOF 文件追加一条 DEL 命令来显式地删除该键值。AOF 重写阶段执行 AOF 重写时会对 Redis 中的键值对进行检查已过期的键不会被保存到重写后的 AOF 文件中因此不会对 AOF 重写造成任何影响。 Redis 主从模式中对过期键会如何处理 当 Redis 运行在主从模式下时从库不会进行过期扫描从库对过期的处理是被动的依赖于主库。也就是说即使从库中的 key 过期了如果有客户端访问从库依然可以得到 key 对应的值像未过期的键值对一样返回。 从库的过期键处理依靠主服务器控制主库在 key 到期时会在 AOF 文件里增加一条 del 指令同步到所有的从库从库通过执行这条 del 指令来删除过期的 key。 Redis 内存满了会发生什么 在 Redis 的运行内存达到了某个阀值就会触发内存淘汰机制这个阀值就是我们设置的最大运行内存此值在 Redis 的配置文件中可以找到配置项为 maxmemory。 Redis 内存淘汰策略有哪些 Redis 内存淘汰策略一共有八种而这八种策略大体可分为「不进行数据淘汰」和「进行数据淘汰」两类策略。 1 - 4 - 7 1、不进行数据淘汰的策略 noevictionRedis3.0之后默认的内存淘汰策略它表示当运行内存超过最大设置内存时不淘汰任何数据而是不再提供服务直接返回错误。 2、进行数据淘汰的策略 针对「进行数据淘汰」这一类策略又可以细分为「在设置了过期时间的数据中进行淘汰」和「在所有数据范围内进行淘汰」这两类策略。 在设置了过期时间的数据中进行淘汰 volatile-random随机淘汰设置了过期时间的任意键值volatile-ttl优先淘汰更早过期的键值。volatile-lruRedis3.0 之前默认的内存淘汰策略淘汰所有设置了过期时间的键值中最久未使用的键值volatile-lfuRedis 4.0 后新增的内存淘汰策略淘汰所有设置了过期时间的键值中最少使用的键值 在所有数据范围内进行淘汰 allkeys-random随机淘汰任意键值;allkeys-lru淘汰整个键值中最久未使用的键值allkeys-lfuRedis 4.0 后新增的内存淘汰策略淘汰整个键值中最少使用的键值。 LRU 算法和 LFU 算法有什么区别 什么是 LRU 算法 LRU 全称是 Least Recently Used(lru) 翻译为最近最少使用会选择淘汰最近最少使用的数据。最久未使用 – 时间 传统 LRU 算法的实现是基于「链表」结构链表中的元素按照操作顺序从前往后排列最新操作的键会被移动到表头当需要内存淘汰时只需要删除链表尾部的元素即可因为链表尾部的元素就代表最久未被使用的元素。 但是 Redis 并没有使用这样的方式实现 LRU 算法因为传统的 LRU 算法存在两个问题 需要用链表管理所有的缓存数据这会带来额外的空间开销当有数据被访问时需要在链表上把该数据移动到头端如果有大量数据被访问就会带来很多链表移动操作会很耗时进而会降低 Redis 缓存性能。 Redis 是如何实现 LRU 算法的 Redis 实现的是一种近似 LRU 算法目的是为了更好的节约内存它的实现方式是在 Redis 的对象结构体中添加一个额外的字段用于记录此数据的最后一次访问时间。 当 Redis 进行内存淘汰时会使用随机采样的方式来淘汰数据它是随机取 5 个值此值可配置然后淘汰最久没有使用的那个。 Redis 实现的 LRU 算法的优点 不用为所有的数据维护一个大链表节省了空间占用不用在每次数据访问时都移动链表项提升了缓存的性能 但是 LRU 算法有一个问题无法解决缓存污染问题比如应用一次读取了大量的数据而这些数据只会被读取这一次那么这些数据会留存在 Redis 缓存中很长一段时间造成缓存污染。 因此在 Redis 4.0 之后引入了 LFU 算法来解决这个问题。 什么是缓存污染问题 缓存污染问题是指缓存中存储了错误的数据导致应用程序获取到的数据不正确。**通常是由于缓存中存储了过期的、损坏的或者恶意的数据造成的。**当应用程序从缓存中获取到错误的数据时可能会导致程序异常或者返回不正确的结果。为了避免缓存污染问题需要定期清理过期数据、设置合理的缓存过期时间、使用合法的缓存数据源等措施。 什么是 LFU 算法 LFU 全称是 Least Frequently Used 翻译为最不经常使用LFU 算法是根据数据访问次数来淘汰数据的它的核心思想是“如果数据过去被访问多次那么将来被访问的频率也更高”。最少使用 – 次数 所以 LFU 算法会记录每个数据的访问次数。当一个数据被再次访问时就会增加该数据的访问次数。这样就解决了偶尔被访问一次之后数据留存在缓存中很长一段时间的问题相比于 LRU 算法也更合理一些。 Redis 是如何实现 LFU 算法的 LFU 算法相比于 LRU 算法的实现多记录了「数据的访问频次」的信息。 高 16bit – 记录 key 的访问时间戳低 8bit – 记录 key 的访问频次 Redis 对象的结构如下 typedef struct redisObject {...// 24 bits用于记录对象的访问信息unsigned lru:24; ... } robj;Redis 对象头中的 lru 字段在 LRU 算法下和 LFU 算法下使用方式并不相同。 在 LRU 算法中Redis 对象头的 24 bits 的 lru 字段是用来记录 key 的访问时间戳因此在 LRU 模式下Redis可以根据对象头中的 lru 字段记录的值来比较最后一次 key 的访问时间长从而淘汰最久未被使用的 key。 在 LFU 算法中Redis对象头的 24 bits 的 lru 字段被分成两段来存储高 16bit 存储 ldt(Last Decrement Time)用来记录 key 的访问时间戳低 8bit 存储 logc(Logistic Counter)用来记录 key 的访问频次。 如下图所示 Redis 缓存设计生产问题 缓存雪崩 什么是缓存雪崩 缓存雪崩就是当大量缓存数据在同一时间过期失效或者 Redis 故障宕机时同时又有大量的用户请求都无法在 Redis 中处理于是全部请求都直接访问数据库从而导致数据库的压力骤增严重的会造成数据库宕机从而形成一系列连锁反应造成整个系统崩溃。 如何解决缓存雪崩 对于缓存雪崩问题我们可以采用两种方案解决。 将缓存失效时间随机打散我们可以在原有的失效时间基础上增加一个随机值比如 1 到 10 分钟这样每个缓存的过期时间都不重复了也就降低了缓存集体失效的概率。设置缓存不过期我们可以通过后台服务来更新缓存数据从而避免因为缓存失效造成的缓存雪崩也可以在一定程度上避免缓存并发问题。 缓存击穿 什么是缓存击穿 我们的业务通常会有几个数据会被频繁地访问比如秒杀活动这类被频地访问的数据被称为热点数据。 如果缓存中的某个热点数据过期了此时大量的请求访问了该热点数据就无法从缓存中读取直接访问数据库数据库很容易就被高并发的请求冲垮这就是缓存击穿的问题。 可以发现缓存击穿跟缓存雪崩很相似你可以认为缓存击穿是缓存雪崩的一个子集。缓存数据 包含 热点数据 如何解决缓存击穿 应对缓存击穿可以采取前面说到两种方案 互斥锁方案Redis 中使用 setNX 方法设置一个状态位表示这是一种锁定状态保证同一时间只有一个业务线程请求缓存未能获取互斥锁的请求要么等待锁释放后重新读取缓存要么就返回空值或者默认值。不让热点数据过期 不给热点数据设置过期时间由后台异步更新缓存或者在热点数据准备要过期前提前通知后台线程更新缓存以及重新设置过期时间。 缓存穿透 什么是缓存穿透 简单来说缓存穿透就是大量请求的 key 是不合理的既不存在于缓存中也不存在于数据库中。 导致这些请求直接到了数据库上根本没有经过缓存这一层对数据库造成了巨大的压力甚至可能直接就被这么多请求弄宕机了。 产生的原因 业务误操作缓存中的数据和数据库中的数据都被误删除了所以导致缓存和数据库中都没有数据黑客恶意攻击故意大量访问某些读取不存在数据的业务 如何应对缓存穿透 最基本的就是首先做好参数校验一些不合法的参数请求直接抛出异常信息返回给客户端。比如查询的数据库 id 不能小于 0、传入的邮箱格式不对的时候直接返回错误消息给客户端等等。 应对缓存穿透的方案常见的方案有三种。 非法请求的限制参数校验当有大量恶意请求访问不存在的数据的时候也会发生缓存穿透因此在 API 入口处我们要判断求请求参数是否合理请求参数是否含有非法值、请求字段是否存在如果判断出是恶意请求就直接返回错误给客户端避免进一步访问缓存和数据库。设置空值或者默认值当我们线上业务发现缓存穿透的现象时缓存和数据库都不存在可以针对查询的数据在缓存中设置一个空值或者默认值这样后续请求就可以从缓存中读取到空值或者默认值返回给应用而不会继续查询数据库。使用布隆过滤器快速判断数据是否存在避免通过查询数据库来判断数据是否存在我们可以在写入数据库数据时使用布隆过滤器做个标记然后在用户请求到来时业务线程确认缓存失效后可以通过查询布隆过滤器快速判断数据是否存在如果不存在就不用通过查询数据库来判断数据是否存在即使发生了缓存穿透大量请求只会查询 Redis 和布隆过滤器而不会查询数据库保证了数据库能正常运行Redis 自身也是支持布隆过滤器的。 总结 缓存异常会面临的三个问题缓存雪崩、击穿和穿透。 其中缓存雪崩和缓存击穿主要原因是数据不在缓存中而导致大量请求访问了数据库数据库压力骤增容易引发一系列连锁反应导致系统奔溃。不过一旦数据被重新加载回缓存应用又可以从缓存快速读取数据不再继续访问数据库数据库的压力也会瞬间降下来。因此缓存雪崩和缓存击穿应对的方案比较类似。 而缓存穿透主要原因是数据既不在缓存也不在数据库中。因此缓存穿透与缓存雪崩、击穿应对的方案不太一样。 如何设计一个缓存策略可以动态缓存热点数据呢 为什么需要设计 由于数据存储受限系统并不是将所有数据都需要存放到缓存中的而只是将其中一部分热点数据缓存起来所以我们要设计一个热点数据动态缓存的策略。 设计思路 热点数据动态缓存的策略总体思路通过数据最新访问时间来做排名并过滤掉不常访问的数据只留下经常访问的数据。 以电商平台场景中的例子现在要求只缓存用户经常访问的 Top 1000 的商品。具体细节如下 先通过缓存系统做一个排序队列比如存放 1000 个商品系统会根据商品的访问时间更新队列信息越是最近访问的商品排名越靠前同时系统会定期过滤掉队列中排名最后的 200 个商品然后再从数据库中随机读取出 200 个商品加入队列中这样当请求每次到达的时候会先从队列中获取商品 ID如果命中就根据 ID 再从另一个缓存数据结构中读取实际的商品信息并返回。 在 Redis 中可以用 zadd 方法和 zrange 方法来完成排序队列和获取 200 个商品的操作。 说说常见的缓存更新策略 常见的缓存更新策略共有3种 Cache Aside旁路缓存策略Read/Write Through读穿 / 写穿策略Write Back写回策略 实际开发中Redis 和 MySQL 的更新策略用的是 Cache Aside另外两种策略应用不了。 Cache Aside旁路缓存策略 Cache Aside旁路缓存策略是最常用的应用程序直接与「数据库、缓存」交互并负责对缓存的维护该策略又可以细分为「读策略」和「写策略」。 写策略的步骤 先更新数据库中的数据再删除缓存中的数据。 读策略的步骤 如果读取的数据命中了缓存则直接返回数据如果读取的数据没有命中缓存则从数据库中读取数据然后将数据写入到缓存并且返回给用户。 注意写策略的步骤的顺序不能倒过来即不能先删除缓存再更新数据库原因是在「读写」并发的时候会出现缓存和数据库的数据不一致性的问题。 为什么是删除缓存而不是更新缓存呢 因为删除一个数据相比更新一个数据更加轻量级出问题的概率更小。在实际业务中缓存的数据可能不是直接来自某一张数据库表也许来自多张底层数据表的聚合。 比如商品详情信息在底层可能会关联商品表、价格表、库存表等如果更新了一个价格字段那么就要更新整个数据库还要关联的去查询和汇总各个周边业务系统的数据这个操作会非常耗时。从另外一个角度不是所有的缓存数据都是频繁访问的更新后的缓存可能会长时间不被访问浪费空间所以说从计算资源和整体性能的考虑更新的时候删除缓存等到下次查询命中再填充缓存是一个更好的方案。 系统设计中有一个思想叫 Lazy Loading适用于那些加载代价大的操作删除缓存而不是更新缓存就是懒加载思想的一个应用。 为什么「先更新数据库再删除缓存」不会有数据不一致的问题 其实先更新数据库再删除缓存也会出现数据不一致性的问题但是在实际中这个问题出现的概率并不高。 例如请求 A 更新完数据库但还未删除缓存此时请求 B 命中了缓存并返回就导致了数据不一致。 出现的概率低是因为 缓存的写入非常快中间的时间差非常短通常只有几毫秒或者几十毫秒。 「先更新数据库再删除缓存」不会造成数据不一致的问题是因为在更新数据库的同时缓存并没有被删除而是在接下来的读取操作中被重新写入。具体来说当应用程序更新数据库时会先更新数据库中的数据然后再删除缓存中对应的数据。接着当下一次需要访问该数据时应用程序会重新从数据库中读取该数据并将其写入缓存中。这样缓存中存储的数据就是最新的与数据库中的数据一致。 适合场景 Cache Aside 策略适合读多写少的场景不适合写多的场景因为当写入比较频繁时缓存中的数据会被频繁地清理这样会对缓存的命中率有一些影响。 如果业务对缓存命中率有严格的要求那么可以考虑两种解决方案 一种做法是在更新数据时也更新缓存只是在更新缓存前先加一个分布式锁因为这样在同一时间只允许一个线程更新缓存就不会产生并发问题了。当然这么做对于写入的性能会有一些影响另一种做法同样也是在更新数据时更新缓存只是给缓存加一个较短的过期时间这样即使出现缓存不一致的情况缓存的数据也会很快过期对业务的影响也是可以接受。 Read/Write Through读穿 / 写穿策略 Read/Write Through读穿 / 写穿策略原则是应用程序只和缓存交互不再和数据库交互然后由缓存和数据库交互相当于更新数据库的操作由缓存自己代理了。 1、Read Through 读穿策略 先查询缓存中数据是否存在如果存在则直接返回如果不存在则由缓存组件负责从数据库查询数据并将结果写入到缓存组件最后缓存组件将数据返回给应用。 2、Write Through 写穿策略 当有数据更新的时候先查询要写入的数据在缓存中是否已经存在 如果缓存中数据已经存在则更新缓存中的数据并且由缓存组件同步更新到数据库中然后缓存组件告知应用程序更新完成。如果缓存中数据不存在直接更新数据库然后返回 特点以及适合场景是什么 Read Through/Write Through 策略的特点是 由缓存节点而非应用程序来和数据库打交道 在我们开发过程中相比 Cache Aside 策略要少见一些原因是我们经常使用的分布式缓存组件无论是 Memcached 还是 Redis 都不提供写入数据库和自动加载数据库中的数据的功能。 而我们在使用本地缓存的时候可以考虑使用这种策略。 Write Back写回策略 Write Back写回策略在更新数据的时候只更新缓存同时将缓存数据设置为脏的然后立马返回并不会更新数据库。对于数据库的更新会通过批量异步更新的方式进行。 Write Back写回策略也不能应用到我们常用的数据库和缓存的场景中因为 Redis 并没有异步更新数据库的功能。 适合什么场景 Write Back 是计算机体系结构中的设计比如 CPU 的缓存、操作系统中文件系统的缓存都采用了 Write Back写回策略。 Write Back 策略特别适合写多的场景因为发生写操作的时候 只需要更新缓存就立马返回了。比如写文件的时候实际上是写入到文件系统的缓存就返回了并不会写磁盘。 写回策略会带来什么问题 带来的问题是数据不是强一致性的而且会有数据丢失的风险因为缓存一般使用内存而内存是非持久化的所以一旦缓存机器掉电就会造成原本缓存中的脏数据丢失。所以你会发现系统在掉电之后之前写入的文件会有部分丢失就是因为 Page Cache 还没有来得及刷盘造成的。 如何保证缓存和数据库数据的一致性 一共两个操作 更新数据库删除缓存 使用 Cache Aside 旁路缓存策略并增加**「消息队列来重试缓存的删除」或「订阅 MySQL binlog 再操作缓存」**来保证两个操作都能执行成功。 1、只给缓存加上过期时间进行兜底会出现什么情况 可能会出现删除缓存操作失败的问题从而出现缓存中的数据是旧值数据库的是最新值需要过一段时间才会有更新生效的现象因为过期时间到了缓存数据重新写入。 2、如何保证两个操作都能执行成功 有两种方法 重试机制。订阅 MySQL binlog再操作缓存。 这两种方法有一个共同的特点都是采用异步操作缓存。 1. 重试机制 我们可以引入消息队列将第二个操作删除缓存要操作的数据加入到消息队列由消费者来操作数据。 如果应用删除缓存失败可以从消息队列中重新读取数据然后再次删除缓存这个就是重试机制。当然如果重试超过的一定次数还是没有成功我们就需要向业务层发送报错信息了。如果删除缓存成功就要把数据从消息队列中移除避免重复操作否则就继续重试。 2. 订阅 MySQL binlog再操作缓存 「先更新数据库再删缓存」的策略的第一步是更新数据库那么更新数据库成功就会产生一条变更日志记录在 binlog 里。 于是我们就可以通过订阅 binlog 日志拿到具体要操作的数据然后再执行缓存删除阿里巴巴开源的 Canal 中间件就是基于这个实现的。 Canal 的工作原理 哪些情况可能会导致 Redis 阻塞 执行时间复杂度为 O(n) 的命令时比如 KEYS *使用 save 命令生成 RDB 快照文件时AOF 日志记录刷盘重写时查找或删除大 key时清空数据库时集群扩容时CPU 竞争时网络环境差的时候 Redis 性能优化 大量 key 集中过期问题 有两种常见的方法 给 key 设置随机过期时间。开启 lazy-free惰性删除/延迟释放让 Redis 采用异步方式延迟释放 key 使用的内存将该操作交给单独的子线程处理避免阻塞主线程。 Redis 大 key 什么是 Redis 大 key 大 key 并不是指 key 的值很大而是指 key 对应的 value 很大。 一般而言下面这两种情况被称为大 key String 类型的值大于 10 KBHash、List、Set、ZSet 类型的元素的个数超过 5000个 大 key 会造成什么问题 bigkey 除了会消耗更多的内存空间和带宽还会对性能造成比较大的影响。 大 key 会带来以下四种影响 客户端超时阻塞。由于 Redis 执行命令是单线程处理然后在操作大 key 时会比较耗时那么就会阻塞 Redis从客户端这一视角看就是很久很久都没有响应。引发网络阻塞。每次获取大 key 产生的网络流量较大如果一个 key 的大小是 1 MB每秒访问量为 1000那么每秒会产生 1000MB 的流量这对于普通千兆网卡的服务器来说是灾难性的。阻塞工作线程。如果使用 del 删除大 key 时会阻塞工作线程这样就没办法处理后续的命令。内存分布不均。集群模型在 slot 分片均匀情况下会出现数据和查询倾斜情况部分有大 key 的 Redis 节点占用内存多QPS每秒查询率系统在单位时间内能够处理的请求数量 也会比较大。 如何找到大 key 有三种方法 通过 redis-cli --bigkeys 命令查找大 key使用 SCAN 命令查找大 key使用 RdbTools 第三方开源工具查找大 key。 如何删除大 key 有两种方法 分批次删除法根据不同的数据类型采用不同的命令进行输出异步删除法Redis 4.0版本以上用 unlink 命令代替 del 来删除。 如何处理 bigkey 分割 bigkey将一个 bigkey 分割为多个小 key。这种方式需要修改业务层的代码一般不推荐这样做。手动清理删除Redis 4.0 可以使用 UNLINK 命令来异步删除一个或多个指定的 key。Redis 4.0 以下可以考虑使用 SCAN 命令结合 DEL 命令来分批次删除。采用合适的数据结构比如使用 HyperLogLog 统计页面 UV。开启 lazy-free惰性删除/延迟释放 lazy-free 特性是 Redis 4.0 开始引入的指的是让 Redis 采用异步方式延迟释放 key 使用的内存将该操作交给单独的子线程处理避免阻塞主线程。 Redis hotkey热 Key 什么是 hotkey 简单来说如果一个 key 的访问次数比较多且明显多于其他 key 的话那这个 key 就可以看作是 hotkey。 hotkey 出现的原因主要是某个热点数据访问量暴增如重大的热搜事件、参与秒杀的商品。 hotkey 有什么危害 处理 hotkey 会占用大量的 CPU 和带宽可能会影响 Redis 实例对其他请求的正常处理。 此外如果突然访问 hotkey 的请求超出了 Redis 的处理能力Redis 就会直接宕机。这种情况下大量请求将落到后面的数据库上可能会导致数据库崩溃。 因此hotkey 很可能成为系统性能的瓶颈点需要单独对其进行优化以确保系统的高可用性和稳定性。 如何找到 hotkey 1、使用 Redis 自带的 --hotkeys 参数来查找。 2、使用 MONITOR 命令。 MONITOR 命令是 Redis 提供的一种实时查看 Redis 的所有操作的方式可以用于临时监控 Redis 实例的操作情况包括读写、删除等操作。 3、借助开源项目。 比如京东零售的 hotkeyopen in new window 这个项目不光支持 hotkey 的发现还支持 hotkey 的处理。 4、根据业务情况提前预估。 可以根据业务情况来预估一些 hotkey比如参与秒杀活动的商品数据等。 不过我们无法预估所有 hotkey 的出现比如突发的热点新闻事件等。 5、业务代码中记录分析。 在业务代码中添加相应的逻辑对 key 的访问情况进行记录分析。不过这种方式会让业务代码的复杂性增加一般也不会采用。 6、借助公有云的 Redis 分析服务。 如果你用的是公有云的 Redis 服务的话可以看看其是否提供了 key 分析功能一般都有提供。 如何解决 hotkey hotkey 的常见处理以及优化办法如下这些方法可以配合起来使用 读写分离主节点处理写请求从节点处理读请求。使用 Redis Cluster将热点数据分散存储在多个 Redis 节点上。二级缓存hotkey 采用二级缓存的方式进行处理将 hotkey 存放一份到 JVM 本地内存中可以用 Caffeine。 除了这些方法之外如果你使用的公有云的 Redis 服务话还可以留意其提供的开箱即用的解决方案。 慢查询命令 什么是慢查询命令 慢查询命令是指执行时间超过 Redis 配置的阈值的命令。 这个阈值可以通过 Redis 配置文件中的 slowlog-log-slower-than 参数进行设置默认值是 10000即 10 毫秒。 当 Redis 执行一个命令的时间超过了这个阈值这个命令就会被记录在 Redis 的慢查询日志中。慢查询日志会记录命令的执行时间、执行命令的客户端、命令的参数等信息。通过查看慢查询日志可以找到执行时间较长的命令进而优化 Redis 的性能。 为什么会有慢查询命令 这是因为 Redis 是单线程的如果某个命令执行时间过长就会阻塞其他命令的执行。为了避免这种情况的发生Redis 会记录所有执行时间超过一定阈值的命令并将其作为慢查询命令进行记录和统计以便开发人员进行优化和调整。 如何找到慢查询命令 有三种常用的方法 使用 SLOWLOG 命令SLOWLOG 命令可以查看 Redis 慢查询日志包括执行时间、客户端地址、命令名称和参数等信息。可以使用 SLOWLOG LEN 命令查看慢查询日志的长度使用SLOWLOG GET 命令获取指定的慢查询日志。使用 redis-slowlog 工具redis-slowlog 是一个命令行工具可以方便地查看 Redis 慢查询日志。通过运行 redis-slowlog 命令可以列出慢查询命令的执行时间、客户端地址、命令名称和参数等信息。使用 Redis 监控工具Redis 监控工具可以监控 Redis 的各种指标包括慢查询命令的执行时间。通过监控工具可以实时地查看 Redis 的性能指标并及时发现慢查询命令。 Redis 内存碎片 什么是内存碎片? 可以将内存碎片简单地理解为那些不可用的后续没办法再被分配存储其他数据的空闲内存。 比如操作系统为你分配了 32 字节的连续内存空间而你存储数据实际只需要使用 24 字节内存空间那这多余出来的 8 字节内存空间如果后续没办法再被分配存储其他数据的话就可以被称为内存碎片。 Redis 内存碎片虽然不会影响 Redis 性能但是会增加内存消耗。 为什么会有 Redis 内存碎片? 有 2 个比较常见的原因 1、Redis 存储存储数据的时候向操作系统申请的内存空间可能会大于数据实际需要的存储空间。 因为 Redis 对内存的分配和回收采用的是 jemalloc 或 libc 等第三方库这些库的内存分配策略可能会导致内存碎片。 2、频繁修改 Redis 中的数据也会产生内存碎片。 比如当 Redis 中的某个数据删除时Redis 通常不会轻易释放内存给操作系统。 如何查看 Redis 内存碎片的信息 使用 info memory 命令即可查看 Redis 内存相关的信息。 如何清理 Redis 内存碎片 Redis4.0-RC3 版本以后自带了内存整理可以避免内存碎片率过大的问题。 有两种方法 1、直接通过 config set 命令将 activedefrag 配置项设置为 yes 即可但可能会对 Redis 的性能产生影响。 通过 Redis 自动内存碎片清理机制可能会对 Redis 的性能产生影响。 可以设置相关参数来控制具体什么时候清理以及减少对 Redis 性能的影响。 2、重启 Redis 可以做到内存碎片重新整理但是需要重启Redis服务会导致一定的停机时间。 Redis 实战 延迟队列 什么是延迟队列 延迟队列是指把当前要做的事情往后推迟一段时间再做。 延迟队列的常见使用场景有以下几种 在淘宝、京东等购物平台上下单超过一定时间未付款订单会自动取消打车的时候在规定时间没有车主接单平台会取消你的单并提醒你暂时没有车主接单点外卖的时候如果商家在10分钟还没接单就会自动取消订单。 Redis 如何实现延迟队列 可以使用有序集合ZSet的方式来实现延迟消息队列的ZSet 有一个 Score 属性可以用来存储延迟执行的时间。 使用 zadd score1 value1 命令就可以一直往内存中生产消息。再利用 zrangebysocre 查询符合条件的所有待处理的任务通过循环执行队列任务即可。 Redis 管道有什么用 管道技术Pipeline是客户端提供的一种批处理技术用于一次处理多个 Redis 命令从而提高整个交互的性能。 使用管道技术可以解决多个命令执行时的网络等待它是把多个命令整合到一起发送给服务器端处理之后统一返回给客户端这样就免去了每条命令执行后都要等待的情况从而有效地提高了程序的执行效率。 Redis 事务 Redis 事务支持回滚吗 不支持。 Redis 中并没有提供回滚机制虽然 Redis 提供了 DISCARD 命令但是这个命令只能用来主动放弃事务执行把暂存的命令队列清空起不到回滚的效果。 事务执行过程中如果命令入队时没报错而事务提交后实际执行时报错了正确的命令依然可以正常执行所以这可以看出 Redis 并不一定保证原子性 MySQL 在执行事务时会提供回滚机制当事务执行发生错误时事务中的所有操作都会撤销已经修改的数据也会被恢复到事务执行前的状态。 为什么 Redis 不支持事务回滚 Redis 不支持事务回滚的原因有两个 官方认为 Redis 事务的执行时错误通常都是编程错误造成的这种错误通常只会出现在开发环境中而很少会在实际的生产环境中出现所以他认为没有必要为 Redis 开发事务回滚功能不支持事务回滚是因为这种复杂的功能和 Redis 追求的简单高效的设计主旨不符合。 这里不支持事务回滚指的是不支持事务运行时错误的事务回滚 Redis 分布式锁 如何用 Redis 实现分布式锁的 1、什么是分布式锁 分布式锁是用于分布式环境下并发控制的一种机制用于控制某个资源在同一时刻只能被一个应用所使用。 2、加锁 Redis 的 SET 命令有个 NX 参数可以实现「key不存在才插入」所以可以用它来实现分布式锁 如果 key 不存在则显示插入成功可以用来表示加锁成功如果 key 存在则会显示插入失败可以用来表示加锁失败。 基于 Redis 节点实现分布式锁时对于加锁操作我们需要满足三个条件。 加锁包括了读取锁变量、检查锁变量值和设置锁变量值三个操作但需要以原子操作的方式完成所以我们使用 SET 命令带上 NX 选项来实现加锁锁变量需要设置过期时间以免客户端拿到锁后发生异常导致锁一直无法释放所以我们在 SET 命令执行时加上 EX/PX 选项设置其过期时间锁变量的值需要能区分来自不同客户端的加锁操作以免在释放锁时出现误释放操作所以我们使用 SET 命令设置锁变量值时每个客户端设置的值是一个唯一值用于标识客户端 满足这三个条件的分布式命令如下 SET lock_key unique_value NX PX 10000 lock_key 就是 key 键unique_value 是客户端生成的唯一的标识区分来自不同客户端的锁操作NX 代表只在 lock_key 不存在时才对 lock_key 进行设置操作PX 10000 表示设置 lock_key 的过期时间为 10s这是为了避免客户端发生异常而无法释放锁。 简单来说三个条件就是需以原子操作完成、要有过期时间和一个用于标识客户端的标识。 3、解锁 用 Lua 脚本来进行解锁。 解锁的过程就是将 lock_key 键删除del lock_key但不能乱删要保证执行操作的客户端就是加锁的客户端。所以解锁的时候我们要先判断锁的 unique_value 是否为加锁客户端是的话才将 lock_key 键删除。 可以看到解锁是有两个操作这时就需要 Lua 脚本来保证解锁的原子性因为 Redis 在执行 Lua 脚本时可以以原子性的方式执行保证了锁释放操作的原子性。 基于 Redis 实现分布式锁有什么优缺点 1、优点 基于 Redis 实现分布式锁的优点 性能高效这是选择缓存实现分布式锁最核心的出发点。实现方便。很多研发工程师选择使用 Redis 来实现分布式锁很大成分上是因为 Redis 提供了 setnx 方法实现分布式锁很方便。避免单点故障因为 Redis 是跨集群部署的自然就避免了单点故障。 2、缺点 基于 Redis 实现分布式锁的缺点 超时时间不好设置。如果锁的超时时间设置过长会影响性能如果设置的超时时间过短会保护不到共享资源。比如在有些场景中一个线程 A 获取到了锁之后由于业务代码执行时间可能比较长导致超过了锁的超时时间自动失效注意 A 线程没执行完后续线程 B 又意外的持有了锁意味着可以操作共享资源那么两个线程之间的共享资源就没办法进行保护了。 那么如何合理设置超时时间呢 我们可以基于续约的方式设置超时时间先给锁设置一个超时时间然后启动一个守护线程让守护线程在一段时间后重新设置这个锁的超时时间。实现方式就是写一个守护线程然后去判断锁的情况当锁快失效的时候再次进行续约加锁当主线程执行完成后销毁续约锁即可不过这种方式实现起来相对复杂。 Redis 主从复制模式中的数据是异步复制的这样导致分布式锁的不可靠性。如果在 Redis 主节点获取到锁后在没有同步到其他节点时Redis 主节点宕机了此时新的 Redis 主节点依然可以获取锁所以多个应用服务就可以同时获取到锁。 Redis 如何解决集群情况下分布式锁的可靠性 为了保证集群环境下分布式锁的可靠性Redis 官方设计了一个分布式锁算法 Redlock红锁。 它是基于多个 Redis 节点的分布式锁即使有节点发生了故障锁变量仍然是存在的客户端还是可以完成锁操作。官方推荐是至少部署 5 个 Redis 节点而且都是主节点它们之间没有任何关系都是一个个孤立的节点。 Redlock 算法的基本思路是让客户端和多个独立的 Redis 节点依次请求申请加锁如果客户端能够和半数以上的节点成功地完成加锁操作那么我们就认为客户端成功地获得分布式锁否则加锁失败。 加锁失败会怎样 加锁失败后客户端会向所有 Redis 节点发起释放锁的操作释放锁的操作和在单节点上释放锁的操作一样只要执行释放锁的 Lua 脚本就可以了。 学习参考 图解Redis| 小林codingRedis常见面试题总结(下) | JavaGuide(Java面试 学习指南)
http://www.zqtcl.cn/news/594819/

相关文章:

  • 建筑中级职称查询网站百度指数功能模块
  • 建设网站只慧聪网怎样做网站友情链接
  • 德阳网站开发dedecms5.7装饰公司网站模板
  • 下步我院将建设网站信息保密浙江温州网络公司
  • 一键建站网站seo关键词快速排名介绍
  • 自己做网站 什么wordpress博客文章加密
  • 怎样做音视频宣传网站wordpress 推送
  • 网站图片上传代码专业的企业进销存软件定制
  • 商品网站模板wordpress文章推荐
  • 十里堡网站建设做吃的教程网站
  • 比较好的源码网站河南网站seo推广
  • 做网站推广什么好网站界面结构
  • 龙岗网站优化常见的渠道推广方式有哪些
  • wordpress 后台乱码成都百度推广优化
  • 大连 响应式网站wordpress保存图片不显示
  • 二手车网站建站网站建设企业建站要求
  • 海山免费网站建设做视频网站如何赚钱
  • 网站增加点击率 怎样做小店面设计装修网
  • 一 美食 视频网站模板下载安装外国优秀网站欣赏
  • 网站服务器部署重庆涪陵网站建设公司
  • php网站开发实践要做网站照片怎么处理
  • 网站短期就业培训班搜集关键词的网站
  • 社区网站开发淘宝网站打算找人做
  • 政务类网站网页管理平台
  • 淘宝联盟微信里做网站花卉市场网站建设基本步骤
  • 做网站广告语网站注册建设
  • 仓山福州网站建设哪个是网站建设里面的
  • 开网站流程开发公司起名
  • 免费建站优化网站基本设置
  • 网站建设需要上传数据库吗seo自己做网站吗