learn.lianglianglee.com/专栏/高并发系统设计40问/加餐 数据的迁移应该如何做?.md.html
2022-08-14 03:40:33 +08:00

347 lines
30 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<!-- saved from url=(0046)https://kaiiiz.github.io/hexo-theme-book-demo/ -->
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no">
<link rel="icon" href="/static/favicon.png">
<title>加餐 数据的迁移应该如何做?.md.html</title>
<!-- Spectre.css framework -->
<link rel="stylesheet" href="/static/index.css">
<!-- theme css & js -->
<meta name="generator" content="Hexo 4.2.0">
</head>
<body>
<div class="book-container">
<div class="book-sidebar">
<div class="book-brand">
<a href="/">
<img src="/static/favicon.png">
<span>技术文章摘抄</span>
</a>
</div>
<div class="book-menu uncollapsible">
<ul class="uncollapsible">
<li><a href="/" class="current-tab">首页</a></li>
</ul>
<ul class="uncollapsible">
<li><a href="../">上一级</a></li>
</ul>
<ul class="uncollapsible">
<li>
<a href="/专栏/高并发系统设计40问/00 开篇词 为什么你要学习高并发系统设计?.md.html">00 开篇词 为什么你要学习高并发系统设计?</a>
</li>
<li>
<a href="/专栏/高并发系统设计40问/01 高并发系统:它的通用设计方法是什么?.md.html">01 高并发系统:它的通用设计方法是什么?</a>
</li>
<li>
<a href="/专栏/高并发系统设计40问/02 架构分层:我们为什么一定要这么做?.md.html">02 架构分层:我们为什么一定要这么做?</a>
</li>
<li>
<a href="/专栏/高并发系统设计40问/03 系统设计目标(一):如何提升系统性能?.md.html">03 系统设计目标(一):如何提升系统性能?</a>
</li>
<li>
<a href="/专栏/高并发系统设计40问/04 系统设计目标(二):系统怎样做到高可用?.md.html">04 系统设计目标(二):系统怎样做到高可用?</a>
</li>
<li>
<a href="/专栏/高并发系统设计40问/05 系统设计目标(三):如何让系统易于扩展?.md.html">05 系统设计目标(三):如何让系统易于扩展?</a>
</li>
<li>
<a href="/专栏/高并发系统设计40问/06 面试现场第一期:当问到组件实现原理时,面试官是在刁难你吗?.md.html">06 面试现场第一期:当问到组件实现原理时,面试官是在刁难你吗?</a>
</li>
<li>
<a href="/专栏/高并发系统设计40问/07 池化技术:如何减少频繁创建数据库连接的性能损耗?.md.html">07 池化技术:如何减少频繁创建数据库连接的性能损耗?</a>
</li>
<li>
<a href="/专栏/高并发系统设计40问/08 数据库优化方案(一):查询请求增加时,如何做主从分离?.md.html">08 数据库优化方案(一):查询请求增加时,如何做主从分离?</a>
</li>
<li>
<a href="/专栏/高并发系统设计40问/09 数据库优化方案(二):写入数据量增加时,如何实现分库分表?.md.html">09 数据库优化方案(二):写入数据量增加时,如何实现分库分表?</a>
</li>
<li>
<a href="/专栏/高并发系统设计40问/10 发号器如何保证分库分表后ID的全局唯一性.md.html">10 发号器如何保证分库分表后ID的全局唯一性</a>
</li>
<li>
<a href="/专栏/高并发系统设计40问/11 NoSQL在高并发场景下数据库和NoSQL如何做到互补.md.html">11 NoSQL在高并发场景下数据库和NoSQL如何做到互补</a>
</li>
<li>
<a href="/专栏/高并发系统设计40问/12 缓存:数据库成为瓶颈后,动态数据的查询要如何加速?.md.html">12 缓存:数据库成为瓶颈后,动态数据的查询要如何加速?</a>
</li>
<li>
<a href="/专栏/高并发系统设计40问/13 缓存的使用姿势(一):如何选择缓存的读写策略?.md.html">13 缓存的使用姿势(一):如何选择缓存的读写策略?</a>
</li>
<li>
<a href="/专栏/高并发系统设计40问/14 缓存的使用姿势(二):缓存如何做到高可用?.md.html">14 缓存的使用姿势(二):缓存如何做到高可用?</a>
</li>
<li>
<a href="/专栏/高并发系统设计40问/15 缓存的使用姿势(三):缓存穿透了怎么办?.md.html">15 缓存的使用姿势(三):缓存穿透了怎么办?</a>
</li>
<li>
<a href="/专栏/高并发系统设计40问/16 CDN静态资源如何加速.md.html">16 CDN静态资源如何加速</a>
</li>
<li>
<a href="/专栏/高并发系统设计40问/17 消息队列:秒杀时如何处理每秒上万次的下单请求?.md.html">17 消息队列:秒杀时如何处理每秒上万次的下单请求?</a>
</li>
<li>
<a href="/专栏/高并发系统设计40问/18 消息投递:如何保证消息仅仅被消费一次?.md.html">18 消息投递:如何保证消息仅仅被消费一次?</a>
</li>
<li>
<a href="/专栏/高并发系统设计40问/19 消息队列:如何降低消息队列系统中消息的延迟?.md.html">19 消息队列:如何降低消息队列系统中消息的延迟?</a>
</li>
<li>
<a href="/专栏/高并发系统设计40问/20 面试现场第二期:当问到项目经历时,面试官究竟想要了解什么?.md.html">20 面试现场第二期:当问到项目经历时,面试官究竟想要了解什么?</a>
</li>
<li>
<a href="/专栏/高并发系统设计40问/21 系统架构每秒1万次请求的系统要做服务化拆分吗.md.html">21 系统架构每秒1万次请求的系统要做服务化拆分吗</a>
</li>
<li>
<a href="/专栏/高并发系统设计40问/22 微服务架构:微服务化后,系统架构要如何改造?.md.html">22 微服务架构:微服务化后,系统架构要如何改造?</a>
</li>
<li>
<a href="/专栏/高并发系统设计40问/23 RPC框架10万QPS下如何实现毫秒级的服务调用.md.html">23 RPC框架10万QPS下如何实现毫秒级的服务调用</a>
</li>
<li>
<a href="/专栏/高并发系统设计40问/24 注册中心:分布式系统如何寻址?.md.html">24 注册中心:分布式系统如何寻址?</a>
</li>
<li>
<a href="/专栏/高并发系统设计40问/25 分布式Trace横跨几十个分布式组件的慢请求要如何排查.md.html">25 分布式Trace横跨几十个分布式组件的慢请求要如何排查</a>
</li>
<li>
<a href="/专栏/高并发系统设计40问/26 负载均衡:怎样提升系统的横向扩展能力?.md.html">26 负载均衡:怎样提升系统的横向扩展能力?</a>
</li>
<li>
<a href="/专栏/高并发系统设计40问/27 API网关系统的门面要如何做呢.md.html">27 API网关系统的门面要如何做呢</a>
</li>
<li>
<a href="/专栏/高并发系统设计40问/28 多机房部署:跨地域的分布式系统如何做?.md.html">28 多机房部署:跨地域的分布式系统如何做?</a>
</li>
<li>
<a href="/专栏/高并发系统设计40问/29 Service Mesh如何屏蔽服务化系统的服务治理细节.md.html">29 Service Mesh如何屏蔽服务化系统的服务治理细节</a>
</li>
<li>
<a href="/专栏/高并发系统设计40问/30 给系统加上眼睛:服务端监控要怎么做?.md.html">30 给系统加上眼睛:服务端监控要怎么做?</a>
</li>
<li>
<a href="/专栏/高并发系统设计40问/31 应用性能管理:用户的使用体验应该如何监控?.md.html">31 应用性能管理:用户的使用体验应该如何监控?</a>
</li>
<li>
<a href="/专栏/高并发系统设计40问/32 压力测试:怎样设计全链路压力测试平台?.md.html">32 压力测试:怎样设计全链路压力测试平台?</a>
</li>
<li>
<a href="/专栏/高并发系统设计40问/33 配置管理:成千上万的配置项要如何管理?.md.html">33 配置管理:成千上万的配置项要如何管理?</a>
</li>
<li>
<a href="/专栏/高并发系统设计40问/34 降级熔断:如何屏蔽非核心系统故障的影响?.md.html">34 降级熔断:如何屏蔽非核心系统故障的影响?</a>
</li>
<li>
<a href="/专栏/高并发系统设计40问/35 流量控制:高并发系统中我们如何操纵流量?.md.html">35 流量控制:高并发系统中我们如何操纵流量?</a>
</li>
<li>
<a href="/专栏/高并发系统设计40问/36 面试现场第三期:你要如何准备一场技术面试呢?.md.html">36 面试现场第三期:你要如何准备一场技术面试呢?</a>
</li>
<li>
<a href="/专栏/高并发系统设计40问/37 计数系统设计(一):面对海量数据的计数器要如何做?.md.html">37 计数系统设计(一):面对海量数据的计数器要如何做?</a>
</li>
<li>
<a href="/专栏/高并发系统设计40问/38 计数系统设计50万QPS下如何设计未读数系统.md.html">38 计数系统设计50万QPS下如何设计未读数系统</a>
</li>
<li>
<a href="/专栏/高并发系统设计40问/39 信息流设计(一):通用信息流系统的推模式要如何做?.md.html">39 信息流设计(一):通用信息流系统的推模式要如何做?</a>
</li>
<li>
<a href="/专栏/高并发系统设计40问/40 信息流设计(二):通用信息流系统的拉模式要如何做?.md.html">40 信息流设计(二):通用信息流系统的拉模式要如何做?</a>
</li>
<li>
<a class="current-tab" href="/专栏/高并发系统设计40问/加餐 数据的迁移应该如何做?.md.html">加餐 数据的迁移应该如何做?</a>
</li>
<li>
<a href="/专栏/高并发系统设计40问/期中测试 10道高并发系统设计题目自测.md.html">期中测试 10道高并发系统设计题目自测</a>
</li>
<li>
<a href="/专栏/高并发系统设计40问/用户故事 从“心”出发,我还有无数个可能.md.html">用户故事 从“心”出发,我还有无数个可能</a>
</li>
<li>
<a href="/专栏/高并发系统设计40问/结束语 学不可以已.md.html">结束语 学不可以已</a>
</li>
</ul>
</div>
</div>
<div class="sidebar-toggle" onclick="sidebar_toggle()" onmouseover="add_inner()" onmouseleave="remove_inner()">
<div class="sidebar-toggle-inner"></div>
</div>
<script>
function add_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.add('show')
}
function remove_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.remove('show')
}
function sidebar_toggle() {
let sidebar_toggle = document.querySelector('.sidebar-toggle')
let sidebar = document.querySelector('.book-sidebar')
let content = document.querySelector('.off-canvas-content')
if (sidebar_toggle.classList.contains('extend')) { // show
sidebar_toggle.classList.remove('extend')
sidebar.classList.remove('hide')
content.classList.remove('extend')
} else { // hide
sidebar_toggle.classList.add('extend')
sidebar.classList.add('hide')
content.classList.add('extend')
}
}
function open_sidebar() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.add('show')
overlay.classList.add('show')
}
function hide_canvas() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.remove('show')
overlay.classList.remove('show')
}
</script>
<div class="off-canvas-content">
<div class="columns">
<div class="column col-12 col-lg-12">
<div class="book-navbar">
<!-- For Responsive Layout -->
<header class="navbar">
<section class="navbar-section">
<a onclick="open_sidebar()">
<i class="icon icon-menu"></i>
</a>
</section>
</header>
</div>
<div class="book-content" style="max-width: 960px; margin: 0 auto;
overflow-x: auto;
overflow-y: hidden;">
<div class="book-post">
<p id="tip" align="center"></p>
<div><h1>加餐 数据的迁移应该如何做?</h1>
<p>你好,我是唐扬。</p>
<p>在“数据库优化方案(二):写入数据量增加时,如何实现分库分表?”中我曾经提到,由于 MySQL 不像 MongoDB 那样支持数据的 Auto Sharding自动分片所以无论是将 MySQL 单库拆分成多个数据库,还是由于数据存储的瓶颈,不得不将多个数据库拆分成更多的数据库时,你都要考虑如何做数据的迁移。</p>
<p>其实,在实际工作中,不只是对数据库拆分时会做数据迁移,**很多场景都需要你给出数据迁移的方案,**比如说某一天,你的老板想要将应用从自建机房迁移到云上,那么你就要考虑将所有自建机房中的数据,包括 MySQLRedis消息队列等组件中的数据全部迁移到云上这无论对哪种规模的公司来说都是一项浩瀚的工程所以你需要在迁移之前准备完善的迁移方案。</p>
<p>“数据的迁移”的问题比较重要,也比较繁琐,也是开发和运维同学关注的重点。在课程更新的过程中,我看到有很多同学,比如 @每天晒白牙,@枫叶 11@撒旦的堕落等等,在留言区询问如何做数据迁移,所以我策划了一期加餐,准备从数据库迁移和缓存迁移两个方面,带你掌握数据迁移的方法,也带你了解数据迁移过程中,需要注意的关键点,尽量让你避免踩坑。</p>
<h2>如何平滑地迁移数据库中的数据</h2>
<p>你可能会认为:数据迁移无非是将数据从一个数据库拷贝到另一个数据库,可以通过 MySQL 主从同步的方式做到准实时的数据拷贝;也可以通过 mysqldump 工具将源库的数据导出,再导入到新库,<strong>这有什么复杂的呢?</strong></p>
<p>其实,这两种方式只能支持单库到单库的迁移,无法支持单库到多库多表的场景。而且即便是单库到单库的迁移,迁移过程也需要满足以下几个目标:</p>
<p>迁移应该是在线的迁移,也就是在迁移的同时还会有数据的写入;</p>
<p>数据应该保证完整性,也就是说在迁移之后需要保证新的库和旧的库的数据是一致的;</p>
<p>迁移的过程需要做到可以回滚,这样一旦迁移的过程中出现问题,可以立刻回滚到源库,不会对系统的可用性造成影响。</p>
<p>如果你使用 Binlog 同步的方式,在同步完成后再修改代码,将主库修改为新的数据库,这样就不满足可回滚的要求,一旦迁移后发现问题,由于已经有增量的数据写入了新库而没有写入旧库,不可能再将数据库改成旧库。</p>
<p>一般来说,我们有两种方案可以做数据库的迁移。</p>
<h4>“双写”方案</h4>
<p>第一种方案我称之为双写,其实说起来也很简单,它可以分为以下几个步骤:</p>
<p>\1. 将新的库配置为源库的从库,用来同步数据;如果需要将数据同步到多库多表,那么可以使用一些第三方工具获取 Binlog 的增量日志(比如开源工具 Canal在获取增量日志之后就可以按照分库分表的逻辑写入到新的库表中了。</p>
<p>\2. 同时,我们需要改造业务代码,在数据写入的时候,不仅要写入旧库,也要写入新库。当然,基于性能的考虑,我们可以异步地写入新库,只要保证旧库写入成功即可。**但是,我们需要注意的是,**需要将写入新库失败的数据记录在单独的日志中,这样方便后续对这些数据补写,保证新库和旧库的数据一致性。</p>
<p>\3. 然后,我们就可以开始校验数据了。由于数据库中数据量很大,做全量的数据校验不太现实。你可以抽取部分数据,具体数据量依据总体数据量而定,只要保证这些数据是一致的就可以。</p>
<p>\4. 如果一切顺利,我们就可以将读流量切换到新库了。由于担心一次切换全量读流量可能会对系统产生未知的影响,所以这里**最好采用灰度的方式来切换,**比如开始切换 10% 的流量,如果没有问题再切换到 50% 的流量,最后再切换到 100%。</p>
<p>\5. 由于有双写的存在,所以在切换的过程中出现任何的问题,都可以将读写流量随时切换到旧库去,保障系统的性能。</p>
<p>\6. 在观察了几天发现数据的迁移没有问题之后,就可以将数据库的双写改造成只写新库,数据的迁移也就完成了。</p>
<p>**其中,最容易出问题的步骤就是数据校验的工作,**所以,我建议你在未开始迁移数据之前先写好数据校验的工具或者脚本,在测试环境上测试充分之后,再开始正式的数据迁移。</p>
<p>如果是将数据从自建机房迁移到云上,你也可以使用这个方案,**只是你需要考虑的一个重要的因素是:**自建机房到云上的专线的带宽和延迟,你需要尽量减少跨专线的读操作,所以在切换读流量的时候,你需要保证自建机房的应用服务器读取本机房的数据库,云上的应用服务器读取云上的数据库。这样在完成迁移之前,只要将自建机房的应用服务器停掉,并且将写入流量都切到新库就可以了。</p>
<p><img src="assets/b88aefdb07049f2019c922cdb9cb3154.jpg" alt="img" /></p>
<p>这种方案是一种比较通用的方案,无论是迁移 MySQL 中的数据,还是迁移 Redis 中的数据,甚至迁移消息队列都可以使用这种方式,<strong>你在实际的工作中可以直接拿来使用。</strong></p>
<p>这种方式的**好处是:**迁移的过程可以随时回滚,将迁移的风险降到了最低。**劣势是:**时间周期比较长,应用有改造的成本。</p>
<h4>级联同步方案</h4>
<p>这种方案也比较简单,比较适合数据从自建机房向云上迁移的场景。因为迁移上云,最担心云上的环境和自建机房的环境不一致,会导致数据库在云上运行时,因为参数配置或者硬件环境不同出现问题。</p>
<p>所以,我们会在自建机房准备一个备库,在云上环境上准备一个新库,通过级联同步的方式在自建机房留下一个可回滚的数据库,具体的步骤如下:</p>
<p>\1. 先将新库配置为旧库的从库,用作数据同步;</p>
<p>\2. 再将一个备库配置为新库的从库,用作数据的备份;</p>
<p>\3. 等到三个库的写入一致后,将数据库的读流量切换到新库;</p>
<p>\4. 然后暂停应用的写入,将业务的写入流量切换到新库(由于这里需要暂停应用的写入,所以需要安排在业务的低峰期)。</p>
<p><img src="assets/3a2e08181177529c3229c789c2081b2b.jpg" alt="img" /></p>
<p>**这种方案的回滚方案也比较简单,**可以先将读流量切换到备库,再暂停应用的写入,将写流量切换到备库,这样所有的流量都切换到了备库,也就是又回到了自建机房的环境,就可以认为已经回滚了。</p>
<p>上面的级联迁移方案可以应用在,将 MySQL 从自建机房迁移到云上的场景,也可以应用在将 Redis 从自建机房迁移到云上的场景,<strong>如果你有类似的需求可以直接拿来应用。</strong></p>
<p>这种方案<strong>优势是</strong>简单易实施,在业务上基本没有改造的成本;<strong>缺点是</strong>在切写的时候需要短暂的停止写入,对于业务来说是有损的,不过如果在业务低峰期来执行切写,可以将对业务的影响降至最低。</p>
<h2>数据迁移时如何预热缓存</h2>
<p>另外,在从自建机房向云上迁移数据时,我们也需要考虑缓存的迁移方案是怎样的。那么你可能会说:缓存本来就是作为一个中间的存储而存在的,我只需要在云上部署一个空的缓存节点,云上的请求也会穿透到云上的数据库,然后回种缓存,对于业务是没有影响的。</p>
<p>你说的没错,但是你还需要考虑的是缓存的命中率。</p>
<p>如果你部署一个空的缓存,那么所有的请求就都穿透到数据库,数据库可能因为承受不了这么大的压力而宕机,这样你的服务就会不可用了。<strong>所以,缓存迁移的重点是保持缓存的热度。</strong></p>
<p>刚刚我提到Redis 的数据迁移可以使用双写的方案或者级联同步的方案,所以在这里我就不考虑 Redis 缓存的同步了,而是以 Memcached 为例来说明。</p>
<h4>使用副本组预热缓存</h4>
<p>在“缓存的使用姿势(二):缓存如何做到高可用?”中,我曾经提到,为了保证缓存的可用性,我们可以部署多个副本组来尽量将请求阻挡在数据库层之上。</p>
<p>数据的写入流程是写入 Master、Slave 和所有的副本组,而在读取数据的时候,会先读副本组的数据,如果读取不到再到 Master 和 Slave 里面加载数据,再写入到副本组中。**那么,我们就可以在云上部署一个副本组,**这样,云上的应用服务器读取云上的副本组,如果副本组没有查询到数据,就可以从自建机房部署的主从缓存上加载数据,回种到云上的副本组上。</p>
<p><img src="assets/abc0b5e4c80097d8e02000b30e7ea9c6.jpg" alt="img" /></p>
<p>当云上部署的副本组足够热之后,也就是缓存的命中率达到至少 90%,就可以将云机房上的缓存服务器的主从都指向这个副本组,这时迁移也就完成了。</p>
<p>**这种方式足够简单,不过有一个致命的问题是:**如果云上的请求穿透云上的副本组,到达自建机房的主从缓存时,这个过程是需要跨越专线的。</p>
<p>这不仅会占用较多专线的带宽,同时专线的延迟相比于缓存的读取时间是比较大的,一般,即使是本地的不同机房之间的延迟也会达到 2ms3ms那么一次前端请求可能会访问十几次甚至几十次的缓存一次请求就会平白增加几十毫秒甚至过百毫秒的延迟会极大地影响接口的响应时间因此在实际项目中我们很少使用这种方案。</p>
<p>**但是,这种方案给了我们思路,**让我们可以通过方案的设计在系统运行中自动完成缓存的预热,所以,我们对副本组的方案做了一些改造,以尽量减少对专线带宽的占用。</p>
<h4>改造副本组方案预热缓存</h4>
<p>改造后的方案对读写缓存的方式进行改造,步骤是这样的:</p>
<p>\1. 在云上部署多组 mc 的副本组,自建机房在接收到写入请求时,会优先写入自建机房的缓存节点,异步写入云上部署的 mc 节点;</p>
<p>\2. 在处理自建机房的读请求时,会指定一定的流量,比如 10%,优先走云上的缓存节点,这样虽然也会走专线穿透回自建机房的缓存节点,但是流量是可控的;</p>
<p>\3. 当云上缓存节点的命中率达到 90% 以上时,就可以在云上部署应用服务器,让云上的应用服务器完全走云上的缓存节点就可以了。</p>
<p><img src="assets/7f41a529a322e396232ac7963ec082f4.jpg" alt="img" /></p>
<p>使用了这种方式,我们可以实现缓存数据的迁移,又可以尽量控制专线的带宽和请求的延迟情况,<strong>你也可以直接在项目中使用。</strong></p>
<h2>课程小结</h2>
<p>以上我提到的数据迁移的方案,都是我在实际项目中,经常用到的、经受过实战考验的方案,希望你能通过这节课的学习,将这些方案运用到你的项目中,解决实际的问题。与此同时,我想再次跟你强调一下本节课的重点内容:</p>
<p>双写的方案是数据库、Redis 迁移的通用方案,**你可以在实际工作中直接加以使用。**双写方案中最重要的,是通过数据校验来保证数据的一致性,这样就可以在迁移过程中随时回滚;</p>
<p>如果你需要将自建机房的数据迁移到云上,那么也可以考虑**使用级联复制的方案,**这种方案会造成数据的短暂停写,需要在业务低峰期执行;</p>
<p>缓存的迁移重点,是保证云上缓存的命中率,你可以**使用改进版的副本组方式来迁移,**在缓存写入的时候,异步写入云上的副本组,在读取时放少量流量到云上副本组,从而又可以迁移部分数据到云上副本组,又能尽量减少穿透给自建机房造成专线延迟的问题。</p>
<p>**如果你作为项目的负责人,**那么在迁移的过程中,你一定要制定周密的计划:如果是数据库的迁移,那么数据的校验应该是你最需要花费时间来解决的问题。</p>
<p>如果是自建机房迁移到云上,那么专线的带宽一定是你迁移过程中的一个瓶颈点,你需要在迁移之前梳理清楚,有哪些调用需要经过专线,占用带宽的情况是怎样的,带宽的延时是否能够满足要求。你的方案中也需要尽量做到在迁移过程中,同机房的服务,调用同机房的缓存和数据库,尽量减少对于专线带宽资源的占用。</p>
</div>
</div>
<div>
<div style="float: left">
<a href="/专栏/高并发系统设计40问/40 信息流设计(二):通用信息流系统的拉模式要如何做?.md.html">上一页</a>
</div>
<div style="float: right">
<a href="/专栏/高并发系统设计40问/期中测试 10道高并发系统设计题目自测.md.html">下一页</a>
</div>
</div>
</div>
</div>
</div>
</div>
<a class="off-canvas-overlay" onclick="hide_canvas()"></a>
</div>
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v652eace1692a40cfa3763df669d7439c1639079717194" integrity="sha512-Gi7xpJR8tSkrpF7aordPZQlW2DLtzUlZcumS8dMQjwDHEnw9I7ZLyiOj/6tZStRBGtGgN6ceN6cMH8z7etPGlw==" data-cf-beacon='{"rayId":"70997f6c8b948b66","version":"2021.12.0","r":1,"token":"1f5d475227ce4f0089a7cff1ab17c0f5","si":100}' crossorigin="anonymous"></script>
</body>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-NPSEEVD756"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag('js', new Date());
gtag('config', 'G-NPSEEVD756');
var path = window.location.pathname
var cookie = getCookie("lastPath");
console.log(path)
if (path.replace("/", "") === "") {
if (cookie.replace("/", "") !== "") {
console.log(cookie)
document.getElementById("tip").innerHTML = "<a href='" + cookie + "'>跳转到上次进度</a>"
}
} else {
setCookie("lastPath", path)
}
function setCookie(cname, cvalue) {
var d = new Date();
d.setTime(d.getTime() + (180 * 24 * 60 * 60 * 1000));
var expires = "expires=" + d.toGMTString();
document.cookie = cname + "=" + cvalue + "; " + expires + ";path = /";
}
function getCookie(cname) {
var name = cname + "=";
var ca = document.cookie.split(';');
for (var i = 0; i < ca.length; i++) {
var c = ca[i].trim();
if (c.indexOf(name) === 0) return c.substring(name.length, c.length);
}
return "";
}
</script>
</html>