mirror of
https://github.com/zhwei820/learn.lianglianglee.com.git
synced 2025-10-17 23:43:44 +08:00
917 lines
28 KiB
HTML
917 lines
28 KiB
HTML
<!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>28 答疑解惑(二):我的100元哪儿去了?.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="/专栏/消息队列高手课/00 开篇词 优秀的程序员,你的技术栈中不能只有“增删改查”.md.html">00 开篇词 优秀的程序员,你的技术栈中不能只有“增删改查”.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/消息队列高手课/00 预习 怎样更好地学习这门课?.md.html">00 预习 怎样更好地学习这门课?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/消息队列高手课/01 为什么需要消息队列?.md.html">01 为什么需要消息队列?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/消息队列高手课/02 该如何选择消息队列?.md.html">02 该如何选择消息队列?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/消息队列高手课/03 消息模型:主题和队列有什么区别?.md.html">03 消息模型:主题和队列有什么区别?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/消息队列高手课/04 如何利用事务消息实现分布式事务?.md.html">04 如何利用事务消息实现分布式事务?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/消息队列高手课/05 如何确保消息不会丢失.md.html">05 如何确保消息不会丢失.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/消息队列高手课/06 如何处理消费过程中的重复消息?.md.html">06 如何处理消费过程中的重复消息?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/消息队列高手课/07 消息积压了该如何处理?.md.html">07 消息积压了该如何处理?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/消息队列高手课/08 答疑解惑(一) 网关如何接收服务端的秒杀结果?.md.html">08 答疑解惑(一) 网关如何接收服务端的秒杀结果?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/消息队列高手课/09 学习开源代码该如何入手?.md.html">09 学习开源代码该如何入手?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/消息队列高手课/10 如何使用异步设计提升系统性能?.md.html">10 如何使用异步设计提升系统性能?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/消息队列高手课/11 如何实现高性能的异步网络传输?.md.html">11 如何实现高性能的异步网络传输?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/消息队列高手课/12 序列化与反序列化:如何通过网络传输结构化的数据?.md.html">12 序列化与反序列化:如何通过网络传输结构化的数据?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/消息队列高手课/13 传输协议:应用程序之间对话的语言.md.html">13 传输协议:应用程序之间对话的语言.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/消息队列高手课/14 内存管理:如何避免内存溢出和频繁的垃圾回收?.md.html">14 内存管理:如何避免内存溢出和频繁的垃圾回收?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/消息队列高手课/15 Kafka如何实现高性能IO?.md.html">15 Kafka如何实现高性能IO?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/消息队列高手课/16 缓存策略:如何使用缓存来减少磁盘IO?.md.html">16 缓存策略:如何使用缓存来减少磁盘IO?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/消息队列高手课/17 如何正确使用锁保护共享数据,协调异步线程?.md.html">17 如何正确使用锁保护共享数据,协调异步线程?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/消息队列高手课/18 如何用硬件同步原语(CAS)替代锁?.md.html">18 如何用硬件同步原语(CAS)替代锁?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/消息队列高手课/19 数据压缩:时间换空间的游戏.md.html">19 数据压缩:时间换空间的游戏.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/消息队列高手课/20 RocketMQ Producer源码分析:消息生产的实现过程.md.html">20 RocketMQ Producer源码分析:消息生产的实现过程.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/消息队列高手课/21 Kafka Consumer源码分析:消息消费的实现过程.md.html">21 Kafka Consumer源码分析:消息消费的实现过程.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/消息队列高手课/22 Kafka和RocketMQ的消息复制实现的差异点在哪?.md.html">22 Kafka和RocketMQ的消息复制实现的差异点在哪?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/消息队列高手课/23 RocketMQ客户端如何在集群中找到正确的节点?.md.html">23 RocketMQ客户端如何在集群中找到正确的节点?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/消息队列高手课/24 Kafka的协调服务ZooKeeper:实现分布式系统的“瑞士军刀”.md.html">24 Kafka的协调服务ZooKeeper:实现分布式系统的“瑞士军刀”.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/消息队列高手课/25 RocketMQ与Kafka中如何实现事务?.md.html">25 RocketMQ与Kafka中如何实现事务?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/消息队列高手课/26 MQTT协议:如何支持海量的在线IoT设备.md.html">26 MQTT协议:如何支持海量的在线IoT设备.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/消息队列高手课/27 Pulsar的存储计算分离设计:全新的消息队列设计思路.md.html">27 Pulsar的存储计算分离设计:全新的消息队列设计思路.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
<a class="current-tab" href="/专栏/消息队列高手课/28 答疑解惑(二):我的100元哪儿去了?.md.html">28 答疑解惑(二):我的100元哪儿去了?.md.html</a>
|
||
|
||
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/消息队列高手课/29 流计算与消息(一):通过Flink理解流计算的原理.md.html">29 流计算与消息(一):通过Flink理解流计算的原理.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/消息队列高手课/30 流计算与消息(二):在流计算中使用Kafka链接计算任务.md.html">30 流计算与消息(二):在流计算中使用Kafka链接计算任务.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/消息队列高手课/31 动手实现一个简单的RPC框架(一):原理和程序的结构.md.html">31 动手实现一个简单的RPC框架(一):原理和程序的结构.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/消息队列高手课/32 动手实现一个简单的RPC框架(二):通信与序列化.md.html">32 动手实现一个简单的RPC框架(二):通信与序列化.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/消息队列高手课/33 动手实现一个简单的RPC框架(三):客户端.md.html">33 动手实现一个简单的RPC框架(三):客户端.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/消息队列高手课/34 动手实现一个简单的RPC框架(四):服务端.md.html">34 动手实现一个简单的RPC框架(四):服务端.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/消息队列高手课/35 答疑解惑(三):主流消息队列都是如何存储消息的?.md.html">35 答疑解惑(三):主流消息队列都是如何存储消息的?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/消息队列高手课/加餐 JMQ的Broker是如何异步处理消息的?.md.html">加餐 JMQ的Broker是如何异步处理消息的?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/消息队列高手课/结束语 程序员如何构建知识体系?.md.html">结束语 程序员如何构建知识体系?.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>28 答疑解惑(二):我的100元哪儿去了?</h1>
|
||
|
||
<p>你好,我是李玥。</p>
|
||
|
||
<p>今天这节课,是我们的“消息队列高手课第二阶段进阶篇的最后一节课,照例,我们在每一阶段的最后,安排一节课进行热点问题的答疑,针对同学们遇到的一些共同的问题,统一来进行详细的解答。</p>
|
||
|
||
<h2>1. 我的 100 元哪儿去了?聊聊并发调用情况下的幂等性</h2>
|
||
|
||
<p>在期中测试中,有这样一道题。</p>
|
||
|
||
<p>如果可以保证以下这些操作的原子性,哪些操作在并发调用的情况下具备幂等性?</p>
|
||
|
||
<ul>
|
||
|
||
<li>A. f(n, a):给账户 n 转入 a 元</li>
|
||
|
||
<li>B. f(n, a):将账户 n 的余额更新为 a 元</li>
|
||
|
||
<li>C. f(n, b, a):如果账户 n 当前的余额为 b 元,那就将账户的余额更新为 n 元</li>
|
||
|
||
<li>D. f(n, v, a):如果账户 n 当前的流水号等于 v,那么给账户的余额加 a 元,并将流水号加一</li>
|
||
|
||
</ul>
|
||
|
||
<p>这道题的正确答案是 D。很多同学都留言提问,选项 B 中,将账户 n 的余额更新为 a 元,这个操作不具备幂等性吗?</p>
|
||
|
||
<p>如果单单只是考虑这个操作,执行一次和执行多次,对系统的影响是一样的,账户 n 的余额都是 a 元。所以,这个操作确实是幂等的。但请你注意审题,我们的题目中说的是:“哪些操作<strong>在并发调用的情况下</strong>具备幂等性?”在并发调用的情况下,我们再来看一下 B 这个选项的操作是否还具备幂等性。</p>
|
||
|
||
<p>假设,账户余额 100 元,依次执行 2 次转账:</p>
|
||
|
||
<ol>
|
||
|
||
<li>将账户余额设为 200 元;</li>
|
||
|
||
<li>将账户余额设为 300 元;</li>
|
||
|
||
</ol>
|
||
|
||
<p>经过两次转账后,账户余额应该是 300 元。</p>
|
||
|
||
<p>再次注意,我们的题目中说的是在并发调用的情况下。</p>
|
||
|
||
<p>按照时间顺序,就有可能会出现下面这种情况:</p>
|
||
|
||
<ol>
|
||
|
||
<li>t0 时刻客户端发送请求:将账户余额设为 200 元。</li>
|
||
|
||
<li>t1 时刻服务端收到请求,账户余额由 100 元变为 200 元,然后服务端发出给客户端操作成功的响应,但是这个响应在网络传输过程中丢失了。</li>
|
||
|
||
<li>t2 时刻客户端发送请求:将账户余额设为 300 元。</li>
|
||
|
||
<li>t3 时刻服务端收到请求,账户余额由 200 元变为 300 元,然后服务端发出给客户端操作成功的响应。</li>
|
||
|
||
<li>t4 时刻客户端:收到“将账户余额设为 300 元”这个请求的成功响应,本次调用成功。</li>
|
||
|
||
<li>t5 时刻客户端由于没收到“将账户余额设为 300 元”这个请求的成功响应,重新发送请求:将账户余额设为 200 元。</li>
|
||
|
||
<li>t6 时刻服务端收到请求,账户余额由 300 元变为 200 元,然后服务端给客户端发出操作成功的响应。</li>
|
||
|
||
<li>t7 时刻客户端收到响应,本次重试调用成功。</li>
|
||
|
||
</ol>
|
||
|
||
<p>结果,账户余额错误地变成了 200 元。</p>
|
||
|
||
<p>同学,请把我的 100 块钱还给我!通过这个题,我们可以总结出来,<strong>一个操作是否幂等,还跟调用顺序有关系</strong>,在线性调用情况下,具备幂等性的操作,在并发调用时,就不一定具备幂等性了。如果你在设计系统的时候,没有注意到这个细节,那系统就有可能出现我们上面这个例子中的错误,在生产系中,这是非常危险的。</p>
|
||
|
||
<h2>2. Kafka 和 RocketMQ 如何通过选举来产生新的 Leader?</h2>
|
||
|
||
<p>在《[22 | Kafka 和 RocketMQ 的消息复制实现的差异点在哪?]》这节课中,我给你讲了这两个消息队列是如何通过复制来保证数据一致性的。当 Broker 节点发生故障时,它们都是通过选举机制,来选出新的 Leader 来继续提供服务。当时限于篇幅,我们并没有深入进去来讲选举的实现原理。那 Kafka 和 RocketMQ(Dledger)都是怎么来实现的选举呢?</p>
|
||
|
||
<p>先来说 Kafka 的选举,因为 Kafka 的选举实现比较简单。严格地说,Kafka 分区的 Leader 并不是选举出来的,而是 Controller 指定的。Kafka 使用 ZooKeeper 来监控每个分区的多个副本,如果发现某个分区的主节点宕机了,Controller 会收到 ZooKeeper 的通知,这个时候,Controller 会从 ISR 节点中选择一个节点,指定为新的 Leader。</p>
|
||
|
||
<p>在 Kafka 中 Controller 本身也是通过 ZooKeeper 选举产生的。接下来我要讲的,Kafka Controller 利用 ZooKeeper 选举的方法,你一定要记住并学会,因为这种方法非常简单实用,并且适用性非常广泛,在设计很多分布式系统中都可以用到。</p>
|
||
|
||
<p>这种选举方法严格来说也不是真正的“选举”,而是一种抢占模式。实现也很简单,每个 Broker 在启动后,都会尝试在 ZooKeeper 中创建同一个临时节点:/controller,并把自身的信息写入到这个节点中。由于 ZooKeeper 它是一个<strong>可以保证数据一致性的分布式存储</strong>,所以,集群中只会有一个 Broker 抢到这个临时节点,那它就是 Leader 节点。其他没抢到 Leader 的节点,会 Watch 这个临时节点,如果当前的 Leader 节点宕机,所有其他节点都会收到通知,它们会开始新一轮的抢 Leader 游戏。</p>
|
||
|
||
<p>这就好比有个玉玺,也就是皇帝用的那个上面雕着龙纹的大印章,谁都可以抢这个玉玺,谁抢到谁做皇帝,其他没抢到的人也不甘心,时刻盯着这个玉玺,一旦现在这个皇帝驾崩了,所有人一哄而上,再“抢”出一个新皇帝。这个算法虽然不怎么优雅,但胜在简单直接,并且快速公平,是非常不错的选举方法。</p>
|
||
|
||
<p>但是这个算法它依赖一个“玉玺”,也就是一个<strong>可以保证数据一致性的分布式存储</strong>,这个分布式存储不一定非得是 ZooKeeper,可以是 Redis,可以是 MySQL,也可以是 HDFS,只要是可以保证数据一致性的分布式存储,都可以充当这个“玉玺”,所以这个选举方法的适用场景也是非常广泛的。</p>
|
||
|
||
<p>再来说 RocketMQ/Dledger 的选举,在 Dledger 中的 Leader 真的是通过投票选举出来的,所以它不需要借助于任何外部的系统,仅靠集群的节点间投票来达成一致,选举出 Leader。一般这种自我选举的算法,为了保证数据一致性、避免集群分裂,算法设计的都非常非常复杂,我们不太可能自己来实现这样一个选举算法,所以我在这里不展开讲。Dledger 采用的是<a href="https://raft.github.io/">Raft 一致性算法</a>,感兴趣的同学可以读一下这篇<a href="https://raft.github.io/raft.pdf">经典的论文</a>。</p>
|
||
|
||
<p>像 Raft 这种自我选举的算法,相比于上面介绍的抢占式选举,优点是不需要借助外部系统,完全可以实现自我选举。缺点也非常明显,就是算法实在是太复杂了,非常难实现。并且,往往集群中的节点要通过多轮投票才能达成一致,这个选举过程是比较慢的,一次选举耗时几秒甚至几十秒都有可能。</p>
|
||
|
||
<p>我们日常在设计一些分布式的业务系统时,如果需要选举 Leader,还是采用 Kafka 的这种“抢玉玺”的方法更加简单实用。</p>
|
||
|
||
<h2>3. 为什么说 Pulsar 存储计算分离的架构是未来消息队列的发展方向?</h2>
|
||
|
||
<p>在上节课《[27 | Pulsar 的存储计算分离设计:全新的消息队列设计思路]》中,我给你留的思考题是:为什么除了 Pulsar 以外,大多数的消息队列都没有采用存储计算分离的设计呢?这个问题其实是一个发散性的问题,并没有什么标准答案。因为,本来架构设计就是在权衡各种利弊,做出取舍和选择,并没有绝对的对错之分。</p>
|
||
|
||
<p>很多同学在课后的留言中,都已经给出了自己的思路和想法,而且有些同学的想法和我个人的观点不谋而合。在这里我也和你分享一下我对这个问题的理解和看法。</p>
|
||
|
||
<p>早期的消息队列,主要被用来在系统之间异步交换数据,大部分消息队列的存储能力都比较弱,不支持消息持久化,不提倡在消息队列中堆积大量的消息,这个时期的消息队列,本质上是一个数据的管道。</p>
|
||
|
||
<p>现代的消息队列,功能上看似没有太多变化,依然是收发消息,但是用途更加广泛,数据被持久化到磁盘中,大多数消息队列具备了强大的消息堆积能力,只要磁盘空间足够,可以存储无限量的消息,而且不会影响生产和消费的性能。这些消息队列,本质上已经演变成为分布式的存储系统。</p>
|
||
|
||
<p>理解了这一点,你就会明白,为什么大部分消息队列产品,都不使用存储计算分离的设计。为一个“分布式存储系统”做存储计算分离,计算节点就没什么业务逻辑需要计算的了。而且,消息队列又不像其他的业务系统,可以直接使用一些成熟的分布式存储系统来存储消息,因为性能达不到要求。分离后的存储节点承担了之前绝大部分功能,并且增加了系统的复杂度,还降低了性能,显然是不划算的。</p>
|
||
|
||
<p>那为什么 Pulsar 还要采用这种存储和计算分离的设计呢?我们还是需要用发展的眼光看问题。我在上节课说过,Pulsar 的这种架构,很可能代表了未来消息队列的发展方向。为什么这么说呢?你可以看一下现在各大消息队列的 Roadmap(发展路线图),Kafka 在做 Kafka Streams,Pulsar 在做 Pulsar Functions,其实大家都在不约而同的做同一件事儿,就是流计算。</p>
|
||
|
||
<p>原因是什么呢?现有的流计算平台,包括 Storm、Flink 和 Spark,它们的节点都是无状态的纯计算节点,是没有数据存储能力的。所以,现在的流计算平台,它很难做大量数据的聚合,并且在数据可靠性保证、数据一致性、故障恢复等方面,也做得不太好。</p>
|
||
|
||
<p>而消息队列正好相反,它很好地保证了数据的可靠性、一致性,但是 Broker 只具备存储能力,没有计算的功能,数据流进去什么样,流出来还是什么样。同样是处理实时数据流的系统,一个只能计算不能存储,一个只能存储不能计算,那未来如果出现一个新的系统,既能计算也能存储,如果还能有不错的性能,是不是就会把现在的消息队列和流计算平台都给替代了?这是很有可能的。</p>
|
||
|
||
<p>对于一个“带计算功能的消息队列”来说,采用存储计算分离的设计,计算节点负责流计算,存储节点负责存储消息,这个设计就非常和谐了。</p>
|
||
|
||
<p>到这里,我们课程的第二个模块–进阶篇,也就全部结束了。进阶篇的中讲解知识有一定的难度,特别是后半部分的几节源码分析课,从评论区同学们的留言中,我也能感受到,有些同学学习起来会有些吃力。</p>
|
||
|
||
<p>我给同学们的建议是,除了上课时听音频和读文稿之外,课后还要自己去把源代码下载下来,每一个流程从头到尾读一遍源码,最好是打开单步调试模式,一步一步地跟踪一下执行过程。读完源码之后,还要把类图、流程图或者时序图画出来,只有这样才能真正理解实现过程。</p>
|
||
|
||
<p>从下节课开始,我们的课程就进入最后一个模块:案例篇。在这个模块中,我会带你一起动手来写代码,运用我们在课程中所学的知识,来做一些实践的案例。首先我会带你一起做一个消息队列和流计算的案例,你可以来体会一下现在的流计算平台它是什么样的。然后,我们还会用进阶篇中所学到的知识,来一起实现一个类似 Dubbo 的 RPC 框架。</p>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<div>
|
||
|
||
<div style="float: left">
|
||
|
||
<a href="/专栏/消息队列高手课/27 Pulsar的存储计算分离设计:全新的消息队列设计思路.md.html">上一页</a>
|
||
|
||
</div>
|
||
|
||
<div style="float: right">
|
||
|
||
<a href="/专栏/消息队列高手课/29 流计算与消息(一):通过Flink理解流计算的原理.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":"70997983bd703cfa","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>
|
||
|