learn.lianglianglee.com/专栏/Redis 核心原理与实战/37 实战:Redis哨兵模式(上).md.html
2022-05-11 18:52:13 +08:00

1109 lines
28 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>37 实战Redis哨兵模式.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="/专栏/Redis 核心原理与实战/01 Redis 是如何执行的.md">01 Redis 是如何执行的.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/02 Redis 快速搭建与使用.md">02 Redis 快速搭建与使用.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/03 Redis 持久化——RDB.md">03 Redis 持久化——RDB.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/04 Redis 持久化——AOF.md">04 Redis 持久化——AOF.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/05 Redis 持久化——混合持久化.md">05 Redis 持久化——混合持久化.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/06 字符串使用与内部实现原理.md">06 字符串使用与内部实现原理.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/07 附录:更多字符串操作命令.md">07 附录:更多字符串操作命令.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/08 字典使用与内部实现原理.md">08 字典使用与内部实现原理.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/09 附录:更多字典操作命令.md">09 附录:更多字典操作命令.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/10 列表使用与内部实现原理.md">10 列表使用与内部实现原理.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/11 附录:更多列表操作命令.md">11 附录:更多列表操作命令.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/12 集合使用与内部实现原理.md">12 集合使用与内部实现原理.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/13 附录:更多集合操作命令.md">13 附录:更多集合操作命令.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/14 有序集合使用与内部实现原理.md">14 有序集合使用与内部实现原理.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/15 附录:更多有序集合操作命令.md">15 附录:更多有序集合操作命令.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/16 Redis 事务深入解析.md">16 Redis 事务深入解析.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/17 Redis 键值过期操作.md">17 Redis 键值过期操作.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/18 Redis 过期策略与源码分析.md">18 Redis 过期策略与源码分析.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/19 Redis 管道技术——Pipeline.md">19 Redis 管道技术——Pipeline.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/20 查询附近的人——GEO.md">20 查询附近的人——GEO.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/21 游标迭代器过滤器——Scan.md">21 游标迭代器过滤器——Scan.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/22 优秀的基数统计算法——HyperLogLog.md">22 优秀的基数统计算法——HyperLogLog.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/23 内存淘汰机制与算法.md">23 内存淘汰机制与算法.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/24 消息队列——发布订阅模式.md">24 消息队列——发布订阅模式.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/25 消息队列的其他实现方式.md">25 消息队列的其他实现方式.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/26 消息队列终极解决方案——Stream.md">26 消息队列终极解决方案——Stream.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/27 消息队列终极解决方案——Stream.md">27 消息队列终极解决方案——Stream.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/28 实战:分布式锁详解与代码.md">28 实战:分布式锁详解与代码.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/29 实战:布隆过滤器安装与使用及原理分析.md">29 实战:布隆过滤器安装与使用及原理分析.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/30 完整案例:实现延迟队列的两种方法.md">30 完整案例:实现延迟队列的两种方法.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/31 实战:定时任务案例.md">31 实战:定时任务案例.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/32 实战RediSearch 高性能的全文搜索引擎.md">32 实战RediSearch 高性能的全文搜索引擎.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/33 实战Redis 性能测试.md">33 实战Redis 性能测试.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/34 实战Redis 慢查询.md">34 实战Redis 慢查询.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/35 实战Redis 性能优化方案.md">35 实战Redis 性能优化方案.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/36 实战Redis 主从同步.md">36 实战Redis 主从同步.md.html</a>
</li>
<li>
<a class="current-tab" href="/专栏/Redis 核心原理与实战/37 实战Redis哨兵模式.md">37 实战Redis哨兵模式.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/38 实战Redis 哨兵模式(下).md">38 实战Redis 哨兵模式(下).md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/39 实战Redis 集群模式(上).md">39 实战Redis 集群模式(上).md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/40 实战Redis 集群模式(下).md">40 实战Redis 集群模式(下).md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/41 案例Redis 问题汇总和相关解决方案.md">41 案例Redis 问题汇总和相关解决方案.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/42 技能学习指南.md">42 技能学习指南.md.html</a>
</li>
<li>
<a href="/专栏/Redis 核心原理与实战/43 加餐Redis 的可视化管理工具.md">43 加餐Redis 的可视化管理工具.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>37 实战Redis哨兵模式</h1>
<p>上一篇我们讲了主从复制模式,它是属于 Redis 多机运行的基础,但这种模式本身存在一个致命的问题,当主节点奔溃之后,需要人工干预才能恢复 Redis 的正常使用。</p>
<p>例如,我们有 3 台服务器做了主从复制,一个主服务器 A 和两个从服务器 B、C当 A 发生故障之后,需要人工把 B 服务器设置为主服务器,同时再去 C 服务器设置成从服务器并且从主服务器 B 同步数据如果是发生在晚上或者从服务器节点很多的情况下对于人工来说想要立即实现恢复的难度很多所以我们需要一个自动的工具——Redis Sentinel哨兵模式来把手动的过程变成自动的让 Redis 拥有自动容灾恢复failover的能力。</p>
<p>哨兵模式如下所示:</p>
<p><img src="assets/d2f3e040-8574-11ea-a60a-8194ba77d48b" alt="哨兵模式.png" /></p>
<blockquote>
<p>小贴士Redis Sentinel 的最小分配单位是一主一从。</p>
</blockquote>
<h3>Redis Sentinel 搭建</h3>
<p>Redis 官方提供了 Redis Sentinel 的功能,它的运行程序保存在 src 目录下,如图所示:</p>
<p><img src="assets/1342e380-8575-11ea-8405-779d62018a7f" alt="image.png" /></p>
<p>我们需要使用命令 <code>./src/redis-sentinel sentinel.conf</code> 来启动 Sentinel可以看出我们在启动它时必须设置一个 sentinel.conf 文件,这个配置文件中必须包含监听的主节点信息:</p>
<pre><code>sentinel monitor master-name ip port quorum
</code></pre>
<p>例如:</p>
<pre><code>sentinel monitor mymaster 127.0.0.1 6379 1
</code></pre>
<p>其中:</p>
<ul>
<li>master-name 表示给监视的主节点起一个名称;</li>
<li>ip 表示主节点的 IP</li>
<li>port 表示主节点的端口;</li>
<li>quorum 表示确认主节点下线的 Sentinel 数量,如果 quorum 设置为 1 表示只要有一台 Sentinel 判断它下线了,就可以确认它真的下线了。</li>
</ul>
<p>注意:如果主节点 Redis 服务器有密码,还必须在 sentinel.conf 中添加主节点的密码,不然会导致 Sentinel 不能自动监听到主节点下面的从节点。</p>
<p>所以如果 Redis 有密码sentinel.conf 必须包含以下内容:</p>
<pre><code>sentinel monitor mymaster 127.0.0.1 6379 1
sentinel auth-pass mymaster pwd654321
</code></pre>
<p>当我们配置好 sentinel.conf 并执行启动命令 <code>./src/redis-sentinel sentinel.conf</code> 之后Redis Sentinel 就会被启动,如下图所示:</p>
<p><img src="assets/b8339430-8574-11ea-ab1b-af991e0896fe" alt="image.png" /></p>
<p>从上图可以看出 Sentinel 只需配置监听主节点的信息,它会自动监听对应的从节点。</p>
<h3>启动 Sentinel 集群</h3>
<p>上面我们演示了单个 Sentinel 的启动,但生产环境我们不会只启动一台 Sentinel因为如果启动一台 Sentinel 假如它不幸宕机的话,就不能提供自动容灾的服务了,不符合我们高可用的宗旨,所以我们会在不同的物理机上启动多个 Sentinel 来组成 Sentinel 集群,来保证 Redis 服务的高可用。</p>
<p>启动 Sentinel 集群的方法很简单,和上面启动单台的方式一样,我们只需要把多个 Sentinel 监听到一个主服务器节点,那么多个 Sentinel 就会自动发现彼此,并组成一个 Sentinel 集群。</p>
<p>我们启动第二个 Sentinel 来试一下,执行结果如下:</p>
<pre><code class="language-shell">[@iZ2ze0nc5n41zomzyqtksmZ:redis2]$ ./src/redis-sentinel sentinel.conf
5547:X 19 Feb 2020 20:29:30.047 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
5547:X 19 Feb 2020 20:29:30.047 # Redis version=5.0.5, bits=64, commit=00000000, modified=0, pid=5547, just started
5547:X 19 Feb 2020 20:29:30.047 # Configuration loaded
_._
_.-``__ ''-._
_.-`` `. `_. ''-._ Redis 5.0.5 (00000000/0) 64 bit
.-`` .-```. ```\/ _.,_ ''-._
( ' , .-` | `, ) Running in sentinel mode
|`-._`-...-` __...-.``-._|'` _.-'| Port: 26377
| `-._ `._ / _.-' | PID: 5547
`-._ `-._ `-./ _.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' | http://redis.io
`-._ `-._`-.__.-'_.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' |
`-._ `-._`-.__.-'_.-' _.-'
`-._ `-.__.-' _.-'
`-._ _.-'
`-.__.-'
5547:X 19 Feb 2020 20:29:30.049 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
5547:X 19 Feb 2020 20:29:30.049 # Sentinel ID is 6455f2f74614a71ce0a63398b2e48d6cd1cf0d06
5547:X 19 Feb 2020 20:29:30.049 # +monitor master mymaster 127.0.0.1 6379 quorum 1
5547:X 19 Feb 2020 20:29:30.049 * +slave slave 127.0.0.1:6377 127.0.0.1 6377 @ mymaster 127.0.0.1 6379
5547:X 19 Feb 2020 20:29:30.052 * +slave slave 127.0.0.1:6378 127.0.0.1 6378 @ mymaster 127.0.0.1 6379
5547:X 19 Feb 2020 20:29:30.345 * +sentinel sentinel 6455f2f74614a71ce0a63398b2e48d6cd1cf0d08 127.0.0.1 26379 @ mymaster 127.0.0.1 6379
</code></pre>
<p>从以上启动命令可以看出,比单机模式多了最后一行发现其他 Sentinel 服务器的命令,说明这两个 Sentinel 已经组成一个集群了。</p>
<p>Sentinel 集群示意图如下:</p>
<p><img src="assets/91c09780-8574-11ea-a330-69dc969564d3" alt="哨兵模式-多哨兵.png" /></p>
<p>一般情况下 Sentinel 集群的数量取大于 1 的奇数,例如 3、5、7、9而 quorum 的配置要根据 Sentinel 的数量来发生变化,例如 Sentinel 是 3 台,那么对应的 quorum 最好是 2如果 Sentinel 是 5 台,那么 quorum 最好是 3它表示当有 3 台 Sentinel 都确认主节点下线了,就可以确定主节点真的下线了。</p>
<p>与 quorum 参数相关的有两个概念:主观下线和客观下线。</p>
<p>当 Sentinel 集群中,有一个 Sentinel 认为主服务器已经下线时它会将这个主服务器标记为主观下线Subjectively DownSDOWN然后询问集群中的其他 Sentinel是否也认为该服务器已下线当同意主服务器已下线的 Sentinel 数量达到 quorum 参数所指定的数量时Sentinel 就会将相应的主服务器标记为客观下线Objectively downODOWN然后开始对其进行故障转移。</p>
<h3>自动容灾测试</h3>
<p>前面我们已经搭建了 Redis Sentinel接下来我们就尝试一下自动容灾的功能为了模拟故障我们先把主节点手动 kill 掉,执行命令如下:</p>
<pre><code class="language-shell">[@iZ2ze0nc5n41zomzyqtksmZ:~]$ ps -ef|grep redis #找到主节点的进程id
root 5186 1 0 16:54 ? 00:00:23 ./src/redis-server *:6377
root 5200 1 0 16:56 ? 00:00:22 ./src/redis-server *:6378
root 5304 5287 0 17:31 pts/2 00:00:00 redis-cli -a pwd654321
root 5395 5255 0 18:26 pts/1 00:00:19 ./src/redis-sentinel *:26379 [sentinel]
root 5547 5478 0 20:29 pts/4 00:00:02 ./src/redis-sentinel *:26377 [sentinel]
root 5551 5517 0 20:29 pts/5 00:00:00 redis-cli -h 127.0.0.1 -p 26377 -a pwd654321
root 5568 5371 0 20:48 pts/0 00:00:00 grep --color=auto redis
root 28517 1 0 Feb13 ? 00:15:33 ./src/redis-server *:6379
[@iZ2ze0nc5n41zomzyqtksmZ:~]$ kill -9 28517 #关闭主节点服务
</code></pre>
<p>这个时候我们在连接上另一台 Redis 服务器,查看当前主从服务器信息,执行命令如下:</p>
<pre><code class="language-shell">[@iZ2ze0nc5n41zomzyqtksmZ:~]$ redis-cli -h 127.0.0.1 -p 6377 -a pwd654321 2&gt;/dev/null
127.0.0.1:6377&gt; role
1) &quot;master&quot;
2) (integer) 770389
3) 1) 1) &quot;127.0.0.1&quot;
2) &quot;6378&quot;
3) &quot;770389&quot;
</code></pre>
<p>可以看出之前的从服务 6377 被提升为主服务器了,还剩下一台从服务 6378而之前的主服务器 6379 被我们手动下线了,可以看出 Sentinel 已经完美的完成的它的故障自动转移的任务。</p>
<h3>主服务竞选规则</h3>
<p>上面我们模拟了 Redis Sentinel 自动容灾恢复,那接下来我们来看一下,主服务器竞选的规则和相关设置项。</p>
<h4><strong>新主节点竞选优先级设置</strong></h4>
<p>我们可以 redis.conf 中的 replica-priority 选项来设置竞选新主节点的优先级,它的默认值是 100它的最大值也是 100这个值越小它的权重就越高例如从节点 A 的 replica-priority 值为 100从节点 B 的值为 50从节点 C 的值为 5那么在竞选时从节点 C 会作为新的主节点。</p>
<h4><strong>新主节点竞选规则</strong></h4>
<p>新主节点的竞选会排除不符合条件的从节点,然后再剩余的从节点按照优先级来挑选。首先来说,存在以下条件的从节点会被排除:</p>
<ol>
<li>排除所有已经下线以及长时间没有回复心跳检测的疑似已下线从服务器;</li>
<li>排除所有长时间没有与主服务器通信,数据状态过时的从服务器;</li>
<li>排除所有优先级replica-priority为 0 的服务器。</li>
</ol>
<p>符合条件的从节点竞选顺序:</p>
<ol>
<li>优先级最高的从节点将会作为新主节点;</li>
<li>优先级相等则判断复制偏移量,偏移量最大的从节点获胜;</li>
<li>如果以上两个条件都相同,选择 Redis 运行时随机生成 ID 最小那个为新的主服务器。</li>
</ol>
<h4><strong>旧主节点恢复上线</strong></h4>
<p>如果之前的旧主节点恢复上线,会作为从节点运行在主从服务器模式中。</p>
<h3>哨兵工作原理</h3>
<p>哨兵的工作原理是这样的,首先每个 Sentinel 会以每秒钟 1 次的频率,向已知的主服务器、从服务器和以及其他 Sentinel 实例,发送一个 PING 命令。</p>
<p>如果最后一次有效回复 PING 命令的时间超过 down-after-milliseconds 所配置的值(默认 30s那么这个实例会被 Sentinel 标记为主观下线。</p>
<p>如果一个主服务器被标记为主观下线,那么正在监视这个主服务器的所有 Sentinel 节点,要以每秒 1 次的频率确认 主服务器的确进入了主观下线状态。</p>
<p>如果有足够数量quorum 配置值)的 Sentinel 在指定的时间范围内同意这一判断,那么这个主服务器被标记为客观下线。此时所有的 Sentinel 会按照规则协商自动选出新的主节点。</p>
<blockquote>
<p>注意:一个有效的 PING 回复可以是:+PONG、-LOADING 或者 -MASTERDOWN。如果返回值非以上三种回复或者在指定时间内没有回复 PING 命令, 那么 Sentinel 认为服务器返回的回复无效non-valid)。</p>
</blockquote>
<h3>小结</h3>
<p>本文我们讲了主从模式的步骤,需要手动切换故障服务器的弊端,引出了 Sentinel 模式,可以实现监控和自动容灾,我们通过 Redis 提供的 Redis-Sentinel 来启动哨兵模式,当我们启动多个哨兵模式监视同一个主节点时,它们就会彼此发现形成一个新的高可用的 Sentinel 网络。同时我们讲了 Sentinel 的工作原理是通过 PING 命令来检查节点是否存活的,并通过配置项和复制偏移量 ID 来确定新主节点,下文我们讲一下哨兵管理命令和代码实战。</p>
</div>
</div>
<div>
<div style="float: left">
<a href="/专栏/Redis 核心原理与实战/36 实战Redis 主从同步.md">上一页</a>
</div>
<div style="float: right">
<a href="/专栏/Redis 核心原理与实战/38 实战Redis 哨兵模式(下).md">下一页</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":"709974017fd33d60","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>