title: 中间件之Pulsar的概念与特性
date: 2023-2-2 22:07:19
tags:
- pulsar
- kafka
categories: 中间件
toc_number: true
password: 123456qqq
message: 请联系微信:codewj,获取本文密码
篇幅有限
完整内容及源码关注公众号:ReverseCode,发送 冲
为什么要学习 Apache Pulsar
什么是云原生
云原生的概念是2013年Matt Stine提出的,到目前为止, 云原生的概念发生了多次变更, 目前最新对云原生定义为: DevOps+持续交付+微服务+容器而符合云原生架构的应用程序是: 采用开源堆栈(K8S+Docker)进行容器化,基于微服务架构提高灵活性和可维护性,借助敏捷方法、DevOps支持持续迭代和运维自动化,利用云平台设施实现弹性伸缩、动态调度、优化资源利用率。
Apache pulsar基本介绍
Apache Pulsar 是一个云原生企业级的发布订阅(pub-sub)消息系统,最初由Yahoo开发,并于2016年底开源,现在是Apache软件基金会顶级开源项目。Pulsar在Yahoo的生产环境运行了三年多,助力Yahoo的主要应用,如Yahoo Mail、Yahoo Finance、Yahoo Sports、Flickr、Gemini广告平台和Yahoo分布式键值存储系统Sherpa。
Apache Pulsar的功能与特性:
-
多租户模式
- 租户和命名空间(namespace)是 Pulsar 支持多租户的两个核心概念。
- 在租户级别,Pulsar 为特定的租户预留合适的存储空间、应用授权与认证机制。
- 在命名空间级别,Pulsar 有一系列的配置策略(policy),包括存储配额、流控、消息过期策略和命名空间之间的隔离策略。
-
灵活的消息系统
- Pulsar 做了队列模型和流模型的统一,在 Topic 级别只需保存一份数据,同一份数据可多次消费。以流式、队列等方式计算不同的订阅模型大大提升了灵活度。
- 同时pulsar通过**事务采用Exactly-Once(精准一次)**在进行消息传输过程中, 可以确保数据不丢不重
-
云原生架构
- Pulsar 使用计算与存储分离的云原生架构,数据从 Broker 搬离,存在共享存储内部。上层是无状态 Broker,复制消息分发和服务;下层是持久化的存储层 Bookie 集群。Pulsar 存储是分片的,这种构架可以避免扩容时受限制,实现数据的独立扩展和快速恢复
-
segmented Sreams(分片流)
-
Pulsar 将无界的数据看作是分片的流,分片分散存储在分层存储(tiered storage)、BookKeeper 集群分片存储和 Broker 节点缓存上,而对外提供一个统一的、无界数据的视图。其次,不需要用户显式迁移数据,减少存储成本并保持近似无限的存储。
-
支持跨地域复制
-
Pulsar 中的跨地域复制是将 Pulsar 中持久化的消息在多个集群间备份。在 Pulsar 2.4.0 中新增了复制订阅模式(Replicated-subscriptions),在某个集群失效情况下,该功能可以在其他集群恢复消费者的消费状态,从而达到热备模式下消息服务的高可用。
Apache Pulsar组件介绍
层级存储:
- Infinite Stream: 以流的方式永久保存原始数据bookKeeper(kafka默认保存7天)
- 分区的容量不再受限制
- 充分利⽤云存储或现有的廉价存储 ( 例如 HDFS)
- 数据统⼀表征:客户端无需关心数据究竟存储在哪⾥
Pulsar IO(Connector) 连接器:
- Pulsar IO 分为输入(Input)和输出(Output)两个模块,输入代表数据从哪里来,通过 Source 实现数据输入。输出代表数据要往哪里去,通过 Sink 实现数据输出。
- Pulsar 提出了 Connector (也称为 Pulsar IO),用于解决 Pulsar 与周边系统的集成问题,帮助用户高效完成工作。
- 目前 pulsar IO 支持非常多的连接集成操作: 例如HDFS 、Spark、Flink 、Flume 、ES 、HBase等
Pulsar Funcations(轻量级计算框架)
- Pulsar Functions 是一个轻量级的计算框架,可以给用户提供一个部署简单、运维简单、API 简单的 FASS(Function as a service)平台。Pulsar Functions 提供基于事件的服务,支持有状态与无状态的多语言计算,是对复杂的大数据处理框架的有力补充。Pulsar Functions 的设计灵感来自于 Heron 这样的流处理引擎,
- Pulsar Functions 将会拓展 Pulsar 和整个消息领域的未来。使用 Pulsar Functions,用户可以轻松地部署和管理 function,通过 function 从 Pulsar topic 读取数据或者生产新数据到 Pulsar topic完成流式处理。
Pulsar与kafka的对比
-
模型概念
- Kafka: producer – topic – consumer group – consumer
- Pulsar: producer – topic -subsciption- consumer
-
消息消费模式
- Kafka: 主要集中在流(Stream) 模式, 对单个partition是独占消费, 没有共享(Queue)的消费模式(如果需要共享需要独立在不同消费组)
- Pulsar: 提供了统一的消息模型和API. 流(Stream) 模式 – 独占和故障切换订阅方式 ; 队列(Queue)模式 – 多消费者共享订阅的方式
-
消息确认(ack)
- Kafka: 使用偏移量 offset(大量数据消费数据失败重新消费时容易重复消费)
- Pulsar: 使用专门的cursor管理. 累积确认和kafka效果一样; 提供单条或选择性确认(ack不会重复消费)
-
消息保留
- Kafka: 根据设置的保留期来删除消息(7天), 有可能消息没被消费, 过期后被删除, 不支持TTL
- Pulsar: 消息只有被所有订阅消费后才会删除, 不会丢失数据,. 也运行设置保留期, 保留被消费的数据 , 支持TTL
Apache Kafka和Apache Pulsar都有类似的消息概念。 客户端通过主题与消息系统进行交互。 每个主题都可以分为多个分区。 然而,Apache Pulsar和Apache Kafka之间的根本区别在于Apache Kafka是以分区为存储中心,而Apache Pulsar是以Segment为存储中心。
Apache Pulsar将高性能的流(Apache Kafka所追求的)和灵活的传统队列(RabbitMQ所追求的)结合到一个统一的消息模型和API中。 Pulsar使用统一的API为用户提供一个支持流和队列的系统,且具有同样的高性能。
性能对比:
Pulsar 表现最出色的就是性能,Pulsar 的速度比 Kafka 快得多,美国德克萨斯州一家名为 GigaOm (https://gigaom.com/) 的技术研究和分析公司对 Kafka 和 Pulsar 的性能做了比较,并证实了这一点。
kafka目前存在的痛点:
-
Kafka 很难进行扩展,因为 Kafka 把消息持久化在 broker 中,迁移主题分区时,需要把分区的数据完全复制到其他 broker 中,这个操作非常耗时。
-
当需要通过更改分区大小以获得更多的存储空间时,会与消息索引产生冲突,打乱消息顺序。因此,如果用户需要保证消息的顺序,Kafka 就变得非常棘手了。
-
如果分区副本不处于 ISR(同步)状态,那么 leader 选取可能会紊乱。一般地,当原始主分区出现故障时,应该有一个 ISR 副本被征用,但是这点并不能完全保证。若在设置中并未规定只有 ISR 副本可被选为 leader 时,选出一个处于非同步状态的副本做 leader,这比没有 broker 服务该 partition 的情况更糟糕。
-
使用 Kafka 时,你需要根据现有的情况并充分考虑未来的增量计划,规划 broker、主题、分区和副本的数量,才能避免 Kafka 扩展导致的问题。这是理想状况,实际情况很难规划,不可避免会出现扩展需求。
-
Kafka 集群的分区再均衡会影响相关生产者和消费者的性能。
-
发生故障时,Kafka 主题无法保证消息的完整性(特别是遇到第 3 点中的情况,需要扩展时极有可能丢失消息)。
-
使用 Kafka 需要和 offset 打交道,这点让人很头痛,因为 broker 并不维护 consumer 的消费状态。
-
如果使用率很高,则必须尽快删除旧消息,否则就会出现磁盘空间不够用的问题。
-
众所周知,Kafka 原生的跨地域复制机制(MirrorMaker)有问题,即使只在两个数据中心也无法正常使用跨地域复制。因此,甚至 Uber 都不得不创建另一套解决方案来解决这个问题,并将其称为 uReplicator (https://eng.uber.com/ureplicator/)。
-
要想进行实时数据分析,就不得不选用第三方工具,如 Apache Storm、Apache Heron 或 Apache Spark。同时,你需要确保这些第三方工具足以支撑传入的流量。
-
Kafka 没有原生的多租户功能来实现租户的完全隔离,它是通过使用主题授权等安全功能来完成的。
Apache Pulsar的集群架构
架构基本介绍
单个 Pulsar 集群由以下三部分组成:
-
多个 broker 负责处理和负载均衡 producer 发出的消息,并将这些消息分派给 consumer;Broker 与 Pulsar 配置存储交互来处理相应的任务,并将消息存储在 BookKeeper 实例中(又称 bookies);Broker 依赖 ZooKeeper 集群处理特定的任务,等等。
-
多个 bookie 的 BookKeeper 集群负责消息的持久化存储。
-
一个zookeeper集群,用来处理多个Pulsar集群之间的协调任务。
Apache Pulsar提供的组件介绍
Brokers介绍
Pulsar的broker是一个无状态组件, 主要负责运行另外的两个组件:
-
一个 HTTP 服务器service discovery, 它暴露了 REST 系统管理接口以及在生产者和消费者之间进行 Topic查找的API。
-
一个调度分发器dispatcher, 它是异步的TCP服务器,通过自定义 二进制协议应用于所有相关的数据传输。
出于性能考虑,消息通常从Managed Ledger缓存cache中分派出去,除非积压超过缓存大小。如果积压的消息对于缓存来说太大了, 则Broker将开始从BookKeeper那里读取Entries(Entry同样是BookKeeper中的概念,相当于一条记录)。
最后,为了支持全局Topic异地复制,Broker会控制Replicators追踪本地发布的条目,并把这些条目用Java 客户端重新发布到其他区域
*Zookeeper的元数据存储(每个pulsar集群都有一套zk集群做配置存储,在2.10.版本后可插拔组件)
Pulsar使用Apache Zookeeper进行元数据存储、集群配置和协调
-
配置存储: 存储租户,命名域和其他需要全局一致的配置项
-
每个集群有自己独立的ZooKeeper保存集群内部配置和协调信息,例如归属信息,broker负载报告,BookKeeper ledger信息(这个是BookKeeper本身所依赖的)等等。
基于bookKeeper持久化存储
Apache Pulsar 为应用程序提供有保证的信息传递, 如果消息成功到达broker, 就认为其预期到达了目的地。
为了提供这种保证,未确认送达的消息需要持久化存储直到它们被确认送达。这种消息传递模式通常称为持久消息传递. 在Pulsar内部,所有消息都被保存并同步N份,例如,2个服务器保存四份,每个服务器上面都有镜像的RAID存储。
Pulsar用 Apache bookKeeper作为持久化存储。 bookKeeper是一个分布式的预写日志(WAL)系统,有如下几个特性特别适合Pulsar的应用场景:
-
使pulsar能够利用独立的日志,称为ledgers. 可以随着时间的推移为topic创建多个Ledgers
-
它为处理顺序消息提供了非常有效的存储
-
保证了多系统挂掉时Ledgers的读取一致性
-
提供不同的Bookies之间均匀的IO分布的特性
-
它在容量和吞吐量方面都具有水平伸缩性。能够通过增加bookies立即增加容量到集群中,并提升吞吐量
-
Bookies被设计成可以承载数千的并发读写的ledgers。 使用多个磁盘设备 (一个用于日志,另一个用于一般存储) ,这样Bookies可以将读操作的影响和对于写操作的延迟分隔开(读写分离)。
基于bookKeeper持久化存储
Ledger是一个只追加的数据结构,并且只有一个写入器,这个写入器负责多个bookKeeper存储节点(就是Bookies)的写入。 Ledger的条目会被复制到多个bookies。 Ledgers本身有着非常简单的语义:
-
Pulsar Broker(管理ledeger)可以创建ledeger,添加内容到ledger和关闭ledger。
-
当一个ledger被关闭后,除非明确的要写数据或者是因为写入器挂掉导致ledger关闭,ledger只会以只读模式打开。
-
最后,当ledger中的条目不再有用的时候,整个ledger可以被删除(ledger分布是跨Bookies的)。
Pulsar 代理
Pulsar客户端和Pulsar集群交互的一种方式就是直连Pulsar brokers。然而,在某些情况下,这种直连既不可行也不可取,因为客户端并不知道broker的地址。 例如在云环境或者Kubernetes以及其他类似的系统上面运行Pulsar,直连brokers就基本上不可能了。
Pulsar proxy 为这个问题提供了一个解决方案, 为所有的broker提供了一个网关, 如果选择运行了Pulsar Proxy. 所有的客户都会通过这个代理而不是直接与brokers通信
Apache Pulsar的Local与分布式集群构建
Apache Pulsar本地Local模式
Standalone Local单机本地模式, 是pulsar最简单的安装方式, 此种方式仅适用于测试学习使用, 并无法作为开发中使用。
下载Apache pulsar2.8.1 https://pulsar.apache.org/en/download/
服务器系统要求:
Currently, Pulsar is available for 64-bit macOS, Linux, and Windows. To use Pulsar, you need to install 64-bit JRE/JDK 8 or later versions. (目前,Pulsar可用于64位macOS、Linux和Windows。使用Pulsar需要安装64位JRE/JDK 8或更高版本。)
- Apache Pulsar的Local模式构建
cd /export/software
rz 上传即可apache-pulsar-2.8.1-bin.tar.gz 上传Pulsar安装包到linux服务器中,并解压
tar -zxvf apache-pulsar-2.8.1-bin.tar.gz -C /export/server
cd /export/server && ln -s apache-pulsar-2.8.1-bin puslar_2.8.1 构建软连接:
cd /export/server/puslar_2.8.1/bin 启动单机模式Pulsar
./pulsar standalone
- Apache Pulsar的Local模式基本使用
在pulsar的bin目录下, 专门提供了一个pulsar-client的客户端工具, Pulsar-Clinet工具允许使用者在运行的集群中消费并发送消息到Pulsar Topic中.
./pulsar-client consume my-topic -s "first-subscription"
./pulsar-client produce my-topic --messages "hello-pulsar"
Apache Pulsar分布式集群模式
- 分布式集群模式构建:搭建 Pulsar 集群至少需要 3 个组件:ZooKeeper 集群、BookKeeper 集群和 broker 集群(Broker 是 Pulsar 的自身实例)。这三个集群组件如下:
- ZooKeeper 集群(3 个 ZooKeeper 节点组成)
- bookie 集群(也称为 BookKeeper 集群,3 个 BookKeeper 节点组成)
- broker 集群(3 个 Pulsar 节点组成)
Pulsar 的安装包已包含了搭建集群所需的各个组件库。无需单独下载 ZooKeeper 安装包和 BookKeeper 安装包。(在实际中,zookeeper我们并不仅仅应用在pulsar上, 包括HBase等其他的组件也需要依赖, 所以我们此处zookeeper使用外置zk集群环境)
注意: 如果是在内网测试环境搭建集群,为了避免防火墙造成端口开启繁琐,可以关闭服务器防火墙。
分布式模式 最低需要三台服务器进行安装操作。
搭建Zookeeper集群
Zookeeper集群具有以下特点:
- Zookeeper集群有一个leader服务器和多个follower服务器,leader是运行时动态选举出来的。
- Zookeeper集群中只要由半数以上的节点可用,Zookeeper集群就能正常提供服务。
- Zookeeper集群中每个服务器保存一份相同的数据副本,客户端无论连接哪台服务器,数据都是一致的。
- 更新请求顺序进行,来自同一个客户端的更新请求按其发送的顺序依次执行。
- 数据更新原子性,一次数据要么更新成功,要么更新失败。
- 实时性,在一定时间范围内,客户端能读到最新数据。
mkdir /opt/zkdata1 /opt/zkdata2 /opt/zkdata3 创建三个dataDir
touch ./zkdata1/myid ./zkdata2/myid ./zkdata3/myid 分别在三个dataDir中创建myid文件,并在myid文件中指定服务器标识
echo 1 > ./zkdata1/myid
echo 2 > ./zkdata2/myid
echo 3 > ./zkdata3/myid
创建三个Zookeeper配置文件,并添加集群配置
zoo1.cfg:
tickTime=2000
initLimit=10
syncLimit=5
# 指定数据存储位置
dataDir=/opt/zkdata1
# 指定客户端连接的端口
clientPort=2181
# 指定集群中服务器
# 端口1:用于数据同步
# 端口2:用于leader选举
server.1=192.168.88.161:2880:3881
server.2=192.168.88.162:2882:3883
server.3=192.168.88.163:2884:3885
zoo2.cfg:
tickTime=2000
initLimit=10
syncLimit=5
# 指定数据存储位置
dataDir=/opt/zkdata2
# 指定客户端连接的端口
clientPort=2182
# 指定集群中服务器
# 端口1:用于数据同步
# 端口2:用于leader选举
server.1=192.168.88.161:2880:3881
server.2=192.168.88.162:2882:3883
server.3=192.168.88.163:2884:3885
zoo3.cfg:
tickTime=2000
initLimit=10
syncLimit=5
# 指定数据存储位置
dataDir=/opt/zkdata3
# 指定客户端连接的端口
clientPort=2183
# 指定集群中服务器
# 端口1:用于数据同步
# 端口2:用于leader选举
server.1=192.168.88.161:2880:3881
server.2=192.168.88.162:2882:3883
server.3=192.168.88.163:2884:3885
**启动zkserver **
./bin/zkServer.sh start ./conf/zoo1.cfg
./bin/zkServer.sh start ./conf/zoo2.cfg
./bin/zkServer.sh start ./conf/zoo3.cfg
客户端连接
[root@192.168.88.161 zookeeper]# ./bin/zkCli.sh -server 192.168.88.161:2183
[zk: 192.168.88.161:2183(CONNECTED) 1] ls /
[zookeeper]
[zk: 192.168.88.161:2183(CONNECTED) 2] create /mynode1 mydata1
Created /mynode1
[root@192.168.88.161 zookeeper]# ./bin/zkCli.sh -server 192.168.88.161:2181
[zk: 192.168.88.161:2181(CONNECTED) 1] ls /
[mynode1, zookeeper]
查看状态角色
./bin/zkServer.sh status ./conf/zoo1.cfg 其中Mode就是zk server的角色
- LOOKING:表示当前集群中没有leader节点,需要进行选举。
- LEADING:表示当前节点为leader。
- FOLLOWING:表示leader已经被选出,当前节点为follower。
- OBSERVER:表示当前节点为observer。
-
服务器1和服务器2启动后进入LOOKING状态,开始进行Leader选举。
-
每个服务器都会发出一个投票,在初始阶段,服务器1和服务器2将投票自己作为Leader服务器,每次投票都会包含推荐服务器的myid和zxid,然后将每个投票发送给集群中的其他服务器。
-
集群中的每个服务器接收到投票后,首先判断投票的有效性,例如是否为当前轮投票,是否来自处于LOOKING状态的服务器。
-
每个服务器都会将其他服务器的投票和自己的投票进行对比,首先对比zxid,较大zxid的服务器优先作为leader。如果zxid一样,则比较myid,myid较大的优先作为leader。
-
由于是集群初始化阶段,每个服务器的zxid都为0,所以开始比较myid,服务器2的myid较大,所以服务器2获胜。服务器1将其投票更新为投给服务器2,并将投票重新发送给服务器2,此时服务器的票数已经超过集群节点半数,服务器2被选为leader。
-
一旦确定了leader,每个服务器将更新自己的状态。服务器1将状态改为FOLLOWING,服务器2将状态改为LEADING,当服务器3启动时,发现已经有一个Leader,不再进行选举,直接将状态从LOOKING改为FOLLOWING。
当作为leader的服务器2被停止后,又会重新进行leader选举。停止服务器2:./bin/zkServer.sh stop ./conf/zoo2.cfg ,再查看各服务器角色Mode,服务器3被选举为leader,原因是服务器3最近创建了节点,拥有了比服务器1更大的zxid,所以服务器3被选举为leader。(节点的变化会导致zxid递增)
运行流程
在Zookeeper集群中,客户端可用连接集群中任意一台服务器进行操作。
如果是读请求,则直接从当前服务器读取数据。
如果是写请求则分为以下步骤:
-
如果请求的服务器不是leader,则当前请求的服务器会把请求转发给leader。
-
leader将写请求广播给各个服务器,各个服务器写成功后会通知leader。
-
当leader收到大多数服务器写成功的通知,则表示写入成功,leader将通知之前的接收请求的服务器,由它进一步的通知客户端写入成功
搭建Pulsar集群
cd /export/software
rz 上传即可apache-pulsar-2.8.1-bin.tar.gz 将下载的pulsar安装包上传到linux服务器, 并解压
tar -zxvf apache-pulsar-2.8.1-bin.tar.gz -C /export/server
cd /export/server && ln -s apache-pulsar-2.8.1-bin puslar_2.8.1 构建软连接
修改bookkeeper集群配置文件
cd /export/server/pulsar_2.8.1/conf/
vim bookkeeper.conf
修改其第56行:修改本地ip地址
advertisedAddress=192.168.88.161
修改其39行:
journalDirectory=/export/server/pulsar_2.8.1/tmp/journal
修改其389行:
ledgerDirectories=/export/server/pulsar_2.8.1/tmp/ledger
修改602行:
zkServers=192.168.88.161:2181,192.168.88.162:2181,192.168.88.163:2181
修改broker集群的配置文件
cd /export/server/pulsar_2.8.1/conf/
vim broker.conf
修改第98行: 修改集群的名称
clusterName=pulsar-cluster
修改第23行: 配置zookeeper地址
zookeeperServers=192.168.88.161:2181,192.168.88.162:2181,192.168.88.163:2181
修改第26行: 配置zookeeper地址
configurationStoreServers=192.168.88.161:2181,192.168.88.162:2181,192.168.88.163:2181
修改第44行: 更改为本地ip地址
advertisedAddress=192.168.88.161
将配置好bookies目录和brokers目录发送到第二台和第三台
cd /export/server
scp -r apache-pulsar-2.8.1/ node2:$PWD
scp -r apache-pulsar-2.8.1/ node3:$PWD
在第二台和第三台节点上分别配置软连接
cd /export/server
ln -s apache-pulsar-2.8.1/ pulsar_2.8.1
修改第二台和第三台的broker的地址和bookies地址
node2:
cd /export/server/pulsar_2.8.1/conf/
vim bookkeeper.conf
修改其第56行:修改本地ip地址
advertisedAddress=192.168.88.162
vim broker.conf
修改第44行: 更改为本地ip地址
advertisedAddress=192.168.88.162
第三台节点: 都更改为对应IP地址或者主机名即可
Apache Pulsar的分布式集群模式启动(只初始化一次)
cd /export/server/zookeeper/bin 启动zk
./zkServer.sh start
注意: 三个节点依次都要启动, 启动后 通过
./zkServer.sh status
查看状态, 必须看到一个leader 和两个follower 才可以使用
首先初始化Pulsar集群元数据:
cd /export/server/pulsar_2.8.1/bin
./pulsar initialize-cluster-metadata \
--cluster pulsar-cluster \
--zookeeper 192.168.88.161:2181,192.168.88.162:2181,192.168.88.163:2181 \
--configuration-store 192.168.88.161:2181,192.168.88.162:2181,192.168.88.163:2181 \
--web-service-url http://192.168.88.161:8080,192.168.88.162:8080,192.168.88.163:8080 \
--web-service-url-tls https://192.168.88.161:8443,192.168.88.162:8443,192.168.88.163:8443 \
--broker-service-url pulsar://192.168.88.161:6650,192.168.88.162:6650,192.168.88.163:6650 \
--broker-service-url-tls pulsar+ssl://192.168.88.161::6651,192.168.88.162:6651,192.168.88.163:6651
接着初始化bookkeeper集群: 若出现提示输入Y/N: 请输入Y
./bookkeeper shell metaformat
cd /export/server/pulsar_2.8.1/bin 启动bookkeeper服务
./pulsar-daemon start bookie
注意: 三个节点都需要依次启动
验证是否启动: 可三台都检测
./bookkeeper shell bookiesanity
提示:
Bookie sanity test succeeded 认为启动成功
cd /export/server/pulsar_2.8.1/bin 启动Broker
./pulsar-daemon start broker
注意: 三个节点都需要依次启动
检测是否启动:
./pulsar-admin brokers list pulsar-cluster
测试
./pulsar-client consume persistent://public/default/test -s "consumer-test" 模拟开启消费者监听数据 永久://租户/名称空间/topicName
./pulsar-client produce persistent://public/default/test --messages "hello-pulsar" 模拟生产一条数据
Apache Pulsar可视化监控部署
下载地址: https://dist.apache.org/repos/dist/release/pulsar/pulsar-manager/pulsar-manager-0.2.0/apache-pulsar-manager-0.2.0-bin.tar.gz
cd /export/software
rz 上传 apache-pulsar-manager-0.2.0-bin.tar.gz
解压操作:
tar -zxf apache-pulsar-manager-0.2.0-bin.tar.gz -C /export/server/
cd /export/server/pulsar-manager
接着再次解压:
tar -xvf pulsar-manager.tar
cd /export/server/pulsar-manager/pulsar-manager
cp -r ../dist ui
cd /export/server/pulsar-manager/pulsar-manager
./bin/pulsar-manager
CSRF_TOKEN=$(curl http://192.168.88.161:7750/pulsar-manager/csrf-token)
curl \
-H "X-XSRF-TOKEN: $CSRF_TOKEN" \
-H "Cookie: XSRF-TOKEN=$CSRF_TOKEN;" \
-H 'Content-Type: application/json' \
-X PUT http://192.168.88.161:7750/pulsar-manager/users/superuser \
-d '{"name": "pulsar", "password": "pulsar", "description": "test", "email": "username@test.org"}'
访问http://192.168.88.161:7750/ui/index.html 用户名: pulsar密码: pulsar
点击 new Enirconment 构建新环境,连接pulsar
点击 pulsar_cluster,进入管理界面
Apache Pulsar的主要组件介绍与命令使用
多租户模式
多租户是一种架构,目的是为了让多用户环境下使用同一套程序,且保证用户间数据隔离
简单讲:在一台服务器上运行单个应用实例,它为多个租户(客户)提供服务。
Apache Pulsar 最初诞生于雅虎,当时就是为了解决雅虎内部各个部门之间数据的协调,所以多租户特性显得至关重用,Pulsar 从诞生之日起就考虑到多租户这一特性,并在后续的实现过程中,将其不断的完善。 多租户这一特性,使得各个部门之间可以共享同一份数据,不用单独部署独立的系统来操作数据,很好的保证了各部门间数据一致性的问题,同时简化维护成本。
什么是多租户
Pulsar 的多租户设计符合上述要求:
-
使用身份验证、授权和 ACL(访问控制列表)确保其安全性
-
为每个租户强制执行存储配额
-
支持在运行时更改隔离机制,从而实现操作成本低和管理简单
persistent://tenant/namespace/topic
从URL中可以看出tenant(租户)是topic最基本的单元(比命名空间和topic名称更为基本)
tenant 代表的是租户名,它是一个资源的隔离单位,一个 tenant 下可以有多个 namespace。namespace 用来管理其下面所属的 topics,可以在 namespace 级别给 topic 设置相应的策略,比如 retention,backlog,ratelimit。一个 namespace 下又可以有多个 topic,他们的权限大小也是由上到下。Pulsar 的多租户通过 tenant、namespace、topic 形成多级管理系统。
Pulsar多租户的相关特性_安全性(认证和授权)
一个多租户系统需要在租户内提供系统级别的安全性,细分来讲,主要可以归类为一下两点:
-
租户只能访问它有权限访问的 topics
-
不允许访问它无法访问的 topics
在 Pulsar 中,多租户的安全性是通过身份验证和授权机制实现的。当 client 连接到 pulsar broker 时,broker 会使用身份验证插件来验证此客户端的身份,然后为其分配一个 string 类型的 role token。role token 主要有如下作用:
- 判断 client 是否有对 topics 进行生产或消费消息的权限
- 管理租户属性的配置
Pulsar 目前支持一下几种身份认证, 同时支持自定义实现自己的身份认证程序
- TLS 客户端身份认证
- 雅虎的身份认证系统: Athenz
- Kerberos
- JSON Web Token 认证
当身份认证系统识别出客户端的 role token 之后,Pulsar broker 会使用授权系统来告诉客户端当前你可以执行哪些操作。授权操作是在 tenant 级别进行配置的,这意味着在 Pulsar 集群中,允许用户根据不同的角色设定多个授权方案。具体的权限操作是在 namespace 级别进行设置和管理的,例如:针对某一个 topic 是否具有 produce 或 consume 的权限归属于 namespace 这个级别来控制。换句话来说:在 tenant 级别 用户可以配置,什么样的 role 拥有对哪些 tenant 操作的权限,在 namespace 级别用户可以配置,针对某一 topic 当前role拥有什么样的权限,又回到了开头所介绍的,namespace 主要用来管理它所包含的 topics 的属性。
Pulsar多租户的相关特性_隔离性
隔离性主要分为如下两种:
软隔离: 通过磁盘配额,流量控制和限制等手段
存储:
Apache Pulsar 使用Bookkeeper来作为其存储层, bookie是Bookkeeper的实例, Bookkeeper本身就是具有I/O分离(读写分离)的特性,可以很好的做好IO隔离, 提升读写的效率
同时, 不同的租户可以为不同的NameSpace配置不同的存储配额, 当租户内消息的大小达到了存储配额的限制, Pulsar会采取相应的措施, 例如: 阻止消息生成, 抛异常 或丢弃数据等Broker:
每个Borker使用的内存资源都是有上限的, 当Broker达到配置的CPU或内存使用的阈值后, Pulsar会迅速的将流量转移到负载较小的Broker处理
在生产和消费方面, Pulsar都可以进行流量控制,租户可以配置发送和接收的速率,避免出现一个客户端占用当前Broker的所有处理资源
硬隔离: 物理资源隔离
Pulsar 允许将某些租户或名称空间与特定 Broker 进行隔离。这可确保这些租户或命名空间可以充分利用该特定 Broker 上的资源。
Pulsar多租户的相关操作
cd /export/server/brokers/bin
./pulsar-admin tenants list 获取租户列表
cd /export/server/brokers/bin
./pulsar-admin tenants create my-tenant
在创建租户时,可以使用-r 或者 --admin-roles标志分配管理角色。可以用逗号分隔的列表指定多个角色。-c或者--allowed-clusters指定跨地域复制的集群
./pulsar-admin tenants create my-tenant --admin-roles role1,role2,role3 创建租户
./pulsar-admin tenants create my-tenant -r role1
pulsar-admin tenants get my-tenant 获取租户配置
{
"adminRoles": [
"admin1",
"admin2"
],
"allowedClusters": [
"cl1",
"cl2"
]
}
cd /export/server/brokers/bin
./pulsar-admin tenants update my-tenant 基于update可以更新租户的相关配置信息
cd /export/server/brokers/bin
./pulsar-admin tenants delete my-tenant 删除租户,在删除的时候, 如果库下已经有名称空间, 是无法删除的,需要先删除名称空间
Pulsar的名称空间
什么是名称空间
namespace是Pulsar中最基本的管理单元,在namespace这一层面,可以设置权限,调整副本设置,管理跨集群的消息复制,控制消息策略和执行关键操作。一个主题topic可以继承其所对应的namespace的属性,因此我们只需对namespace的属性进行设置,就可以一次性设置该namespace中所有主题topic的属性。
namespace有两种,分别是本地的namespace和全局的namespace:
-
本地namespace——仅对定义它的集群可见。
-
全局namespace——跨集群可见,可以是同一个数据中心的集群,也可以是跨地域中心的集群,这依赖于是否在namespace中设置了跨集群拷贝数据的功能。
虽然本地namespace和全局namespace的作用域不同,但是只要对他们进行适当的设置,都可以跨团队和跨组织共享。一旦生产者获得了namespace的写入权限,那么它就可以往namespace中的所有topic主题写入数据,如果某个主题不存在,则在生产者第一次写入数据时动态创建。
cd /export/server/brokers/bin
./pulsar-admin namespaces create test-tenant/test-namespace 在指定的租户下创建名称空间
./pulsar-admin namespaces list test-tenant 获取某租户下所有的名称空间列表
./pulsar-admin namespaces delete test-tenant/test-namespace 删除名称空间
./pulsar-admin namespaces policies test-tenant/test-namespace
Pulsar NameSpace(名称空间) 相关操作_高级操作
cd /export/server/brokers/bin
pulsar-admin namespaces set-clusters test-tenant/ns1 --clusters cl2 设置复制集群
pulsar-admin namespaces get-clusters test-tenant/ns1 获取给定命名空间复制集群的列表
pulsar-admin namespaces set-backlog-quota --limit 10G --limitTime 36000 --policy producer_request_hold test-tenant/ns1 设置backlog quota 策略
--policy 的值选择:
producer_request_hold:broker 暂停运行,并不再持久化生产请求负载
producer_exception:broker 抛出异常,并与客户端断开连接。
consumer_backlog_eviction:broker 丢弃积压消息
pulsar-admin namespaces get-backlog-quotas test-tenant/ns1 获取 backlog quota 策略
{
"destination_storage": {
"limit": 10,
"policy": "producer_request_hold"
}
}
pulsar-admin namespaces remove-backlog-quota test-tenant/ns1 移除backlog quota 策略
pulsar-admin namespaces set-persistence --bookkeeper-ack-quorum 2 --bookkeeper-ensemble 3 --bookkeeper-write-quorum 2 --ml-mark-delete-max-rate 0 test-tenant/ns1 设置持久化策略, 持久化策略可以为给定命名空间下 topic 上的所有消息配置持久等级
参数说明:
Bookkeeper-ack-quorum:每个 entry 在等待的 acks(有保证的副本)数量,默认值:0
Bookkeeper-ensemble:单个 topic 使用的 bookie 数量,默认值:0
Bookkeeper-write-quorum:每个 entry 要写入的次数,即两个副本,默认值:0
Ml-mark-delete-max-rate:标记-删除操作的限制速率(0表示无限制),默认值:0.0
pulsar-admin namespaces get-persistence test-tenant/ns1 获取持久化策略
{
"bookkeeperEnsemble": 3,
"bookkeeperWriteQuorum": 2,
"bookkeeperAckQuorum": 2,
"managedLedgerMaxMarkDeleteRate": 0
}
pulsar-admin namespaces set-message-ttl --messageTTL 100 test-tenant/ns1 设置消息存活时间
pulsar-admin namespaces get-message-ttl test-tenant/ns1 获取消息的存活时间
pulsar-admin namespaces remove-message-ttl test-tenant/ns1 删除消息的存活时间
pulsar-admin namespaces set-dispatch-rate test-tenant/ns1 \
--msg-dispatch-rate 1000 \
--byte-dispatch-rate 1048576 \
--dispatch-rate-period 1 设置Topic的消息发送的速率
参数说明:
--msg-dispatch-rate : 每dispatch-rate-period秒钟发送的消息数量
--byte-dispatch-rate : 每dispatch-rate-period秒钟发送的总字节数
--dispatch-rate-period : 设置发送的速率, 比如 1 表示 每秒钟
pulsar-admin namespaces get-dispatch-rate test-tenant/ns1 获取topic的消息发送速率
{
"dispatchThrottlingRatePerTopicInMsg" : 1000,
"dispatchThrottlingRatePerTopicInByte" : 1048576,
"ratePeriodInSecond" : 1
}
pulsar-admin namespaces set-subscription-dispatch-rate test-tenant/ns1 \
--msg-dispatch-rate 1000 \
--byte-dispatch-rate 1048576 \
--dispatch-rate-period 设置Topic的消息接收的速率
参数说明:
--msg-dispatch-rate : 每dispatch-rate-period秒钟接收的消息数量
--byte-dispatch-rate : 每dispatch-rate-period秒钟接收的总字节数
--dispatch-rate-period : 设置接收的速率, 比如 1 表示 每秒钟
pulsar-admin namespaces get-subscription-dispatch-rate test-tenant/ns1 获取topic的消息接收速率
{
"dispatchThrottlingRatePerTopicInMsg" : 1000,
"dispatchThrottlingRatePerTopicInByte" : 1048576,
"ratePeriodInSecond" : 1
}
7.1- 设置Topic的消息复制集群的速率
pulsar-admin namespaces set-replicator-dispatch-rate test-tenant/ns1 \
--msg-dispatch-rate 1000 \
--byte-dispatch-rate 1048576 \
--dispatch-rate-period 1
参数说明:
--msg-dispatch-rate : 每dispatch-rate-period秒钟复制集群的消息数量
--byte-dispatch-rate : 每dispatch-rate-period秒钟复制集群的总字节数
--dispatch-rate-period : 设置复制集群的速率, 比如 1 表示 每秒钟
pulsar-admin namespaces get-replicator-dispatch-rate test-tenant/ns1 获取topic的消息复制集群的速率
{
"dispatchThrottlingRatePerTopicInMsg" : 1000,
"dispatchThrottlingRatePerTopicInByte" : 1048576,
"ratePeriodInSecond" : 1
}
Pulsar的topic相关操作
什么是Topic
Topic,话题主题的含义, 在一个名称空间下, 可以定义多个Topic 通过Topic进行数据的分类划分, 将不同的类别的消息放置到不同Topic, 消费者也可以从不同Topic中获取到相关的消息, 是一种更细粒度的消息划分操作, 同时在Topic下可以划分为多个分片, 进行分布式的存储操作, 每个分片下还存在有副本操作, 保证数据不丢失, 当然这些分片副本更多是由bookkeeper来提供支持
Pulsar 提供持久化与非持久化两种topic。 持久化topic是消息发布、消费的逻辑端点。 持久化topic地址的命名格式如下:persistent://tenant/namespace/topic
非持久topic应用在仅消费实时发布消息与不需要持久化保证的应用程序。 通过这种方式,它通过删除持久消息的开销来减少消息发布延迟。 非持久化topic地址的命名格式如下:non-persistent://tenant/namespace/topic
Pulsar Topic(主题) 相关操作_基础操作
bin/pulsar-admin topics create persistent://my-tenant/my-namespace/my-topic 创建一个没有分区的topic
bin/pulsar-admin topics create-partitioned-topic persistent://my-tenant/my-namespace/my-topic --partitions 4 创建一个有分区的topic
注意: 不管是有分区还是没有分区, 创建topic后,如果没有任何操作, 60s后pulsar会认为此topic是不活动的, 会自动进行删除, 以避免生成垃圾数据
相关配置:
Brokerdeleteinactivetopicsenabenabled : 默认值为true 表示是否启动自动删除
BrokerDeleteInactiveTopicsFrequencySeconds: 默认为60s 表示检测未活动的时间
./pulsar-admin namespaces list my-tenant 列出当前某个名称空间下的所有Topic
./pulsar-admin topics list my-tenant/my-namespace
./pulsar-admin topics update-partitioned-topic persistent://my-tenant/my-namespace/my-topic --partitions 8 我们可针对有分区的topic去更新其分区的数量
bin/pulsar-admin topics delete persistent://my-tenant/my-namespace/my-topic 删除没有分区的topic
bin/pulsar-admin topics delete-partitioned-topic persistent://my-tenant/my-namespace/my-topic 删除有分区的topic
Pulsar Topic(主题) 相关操作_高级操作
pulsar-admin topics grant-permission --actions produce,consume --role application1 persistent://test-tenant/ns1/tp1 授权赋予角色
pulsar-admin topics grant-permission --actions produce,consume --role application1 persistent://test-tenant/ns1/tp1 获取权限
pulsar-admin topics revoke-permission --role application1 persistent://test-tenant/ns1/tp1 取消权限
{
"application1": [
"consume",
"produce"
]
}
Apache Pulsar的JAVA API相关使用操作
新增依赖
<dependency>
<groupId>org.apache.pulsar</groupId>
<artifactId>pulsar-client-all</artifactId>
<version>2.8.1</version>
</dependency>
管理租户
public static void main(String[] args) throws Exception {
// 1. 创建Pulsar的Admin管理对象
String serviceHttpUrl = "http://node1:8080,node2:8080,node3:8080";
PulsarAdmin pulsarAdmin = PulsarAdmin.builder().serviceHttpUrl(serviceHttpUrl).build();
//2. 基于Pulsar的Admin对象进行相关的操作
//2.1: 如何创建 租户操作
HashSet<String> allowedClusters = new HashSet<>();
allowedClusters.add("pulsar-cluster");
TenantInfo config = TenantInfo.builder().allowedClusters(allowedClusters).build();
pulsarAdmin.tenants().createTenant("pulsar_t",config);
// 2.2: 查看当前有那些租户
/*List<String> tenants = pulsarAdmin.tenants().getTenants();
for (String tenant : tenants) {
System.out.println("租户信息为:"+tenant);
}*/
//2.3: 删除租户操作
//pulsarAdmin.tenants().deleteTenant("pulsar_t");
//3. 关闭管理对象
pulsarAdmin.close();
}
管理namespace
public static void main(String[] args) throws Exception {
//1. 创建Pulsar的Admin管理对象
String serviceHttpUrl = "http://node1:8080,node2:8080,node3:8080";
PulsarAdmin pulsarAdmin = PulsarAdmin.builder().serviceHttpUrl(serviceHttpUrl).build();
//2. 执行相关的操作
//2.1 如何创建名称空间
// pulsarAdmin.namespaces().createNamespace("pulsar_t/pulsar_n");
//2.2 获取在某个租户下, 一共有那些名称空间:
/*List<String> namespaces = pulsarAdmin.namespaces().getNamespaces("pulsar_t");
for (String namespace : namespaces) {
System.out.println(namespace);
}*/
//2.3: 删除名称空间
pulsarAdmin.namespaces().deleteNamespace("pulsar_t/pulsar_n");
//3. 关闭admin对象
pulsarAdmin.close();
}
管理Topic
public static void main(String[] args) throws Exception {
//1. 创建Pulsar的Admin管理对象
String serviceHttpUrl = "http://node1:8080,node2:8080,node3:8080";
PulsarAdmin pulsarAdmin = PulsarAdmin.builder().serviceHttpUrl(serviceHttpUrl).build();
//2. 执行相关的操作
//2.1: 创建 Topic相关的操作: 有分区和没有分区, 以及持久化和非持久化
pulsarAdmin.topics().createNonPartitionedTopic("persistent://pulsar_t/pulsar_n/t_topic5");
//pulsarAdmin.topics().createNonPartitionedTopic("non-persistent://pulsar_t/pulsar_n/t_topic2");
// pulsarAdmin.topics().createPartitionedTopic("persistent://pulsar_t/pulsar_n/t_topic3",5);
//pulsarAdmin.topics().createPartitionedTopic("non-persistent://pulsar_t/pulsar_n/t_topic5",5);
//2.2: 查询当前有那些topic:
/*List<String> topicList = pulsarAdmin.topics().getList("pulsar_t/pulsar_n");
for (String topic : topicList) {
System.out.println(topic);
}*/
List<String> topicList = pulsarAdmin.topics().getPartitionedTopicList("pulsar_t/pulsar_n");
for (String topic : topicList) {
System.out.println(topic);
}
//2.3 修改Topic 分片的数量
//pulsarAdmin.topics().updatePartitionedTopic("persistent://pulsar_t/pulsar_n/t_topic3",7);
//2.4 一共有多少个分片呢
//int partitions = pulsarAdmin.topics().getPartitionedTopicMetadata("persistent://pulsar_t/pulsar_n/t_topic3").partitions;
//System.out.println(partitions);
//2.5: 删除Topic
//pulsarAdmin.topics().deletePartitionedTopic("persistent://pulsar_t/pulsar_n/t_topic3");
//3. 关闭admin对象
pulsarAdmin.close();
}
数据同步生产
public static void main(String[] args) throws Exception {
//1. 创建Pulsar的客户端对象
PulsarClient pulsarClient = PulsarClient.builder().serviceUrl("pulsar://node1:6650,node2:6650,node3:6650").build();
//2. 通过客户端创建生产者的对象
Producer<String> producer = pulsarClient.newProducer(Schema.STRING)
.topic("persistent://public/default/test_src")
.create();
//3. 使用生产者发送数据
producer.send("hello java API pulsar ...");
System.out.println("数据生产完成....");
//4. 释放资源
producer.close();
pulsarClient.close();
}
数据异步生产
public static void main(String[] args) throws Exception {
//1. 创建Pulsar的客户端对象
PulsarClient pulsarClient = PulsarClient.builder().serviceUrl("pulsar://node1:6650,node2:6650,node3:6650").build();
//2. 通过客户端构建生产者的对象
Producer<String> producer = pulsarClient.newProducer(Schema.STRING)
.topic("persistent://pulsar_t/pulsar_n/t_topic1")
.create();
//3. 进行数据发送操作
// 发现数据并没有生产成功, 主要原因是
// 因为采用异步的发送方案, 这种发送方案会先将数据写入到客户端缓存中, 当缓存中数据达到一批后 才会进行发送操作
producer.sendAsync("hello async pulsar...2222");
System.out.println("数据生产成功....");
// 可以发送完成后, 让程序等待一下, 让其将缓冲区中数据刷新到pulsar上 然后在结束
Thread.sleep(1000);
//4. 释放资源
producer.close();
pulsarClient.close();
}
数据Scheme模式
public static void main(String[] args) throws Exception {
//1. 获取pulsar的客户端对象
ClientBuilder clientBuilder = PulsarClient.builder();
clientBuilder.serviceUrl("pulsar://node1:6650,node2:6650,node3:6650");
PulsarClient client = clientBuilder.build();
//2. 通过客户端创建生产者的对象
AvroSchema<User2> schema = AvroSchema.of(SchemaDefinition.<User2>builder().withPojo(User2.class).build());
// AvroSchema<User2> schema = AvroSchema.of(User2.Class);
Producer<User2> producer = client.newProducer(schema)
.topic("persistent://pulsar_t/pulsar_n/my-topic3").create();
//3. 发送消息:
User2 user = new User2();
user.setName("张三");
user.setAge(20);
user.setAddress("北京");
user.setRowkey("rk001");
user.setFamilyName("C1");
producer.send(user);
Thread.sleep(10000);
//4. 释放资源
producer.close();
client.close();
}
数据同步消费
public static void main(String[] args) throws Exception{
//1. 创建pulsar的客户端的对象
PulsarClient pulsarClient = PulsarClient.builder().serviceUrl("pulsar://node1:6650,node2:6650,node3:6650").build();
//2. 基于客户端构建消费者对象
Consumer<String> consumer = pulsarClient.newConsumer(Schema.STRING)
.topic("txn_t4")
.subscriptionName("sub_04")
.subscriptionType(SubscriptionType.Exclusive)
.subscribe();
//3. 循环从消费者读取数据
while(true) {
//3.1: 接收消息
Message<String> message = consumer.receive();
//3.2: 获取消息
String msg = message.getValue();
//3.3: 处理数据--- 业务操作
System.out.println("消息数据为:"+msg);
//3.4: ack确认操作
consumer.acknowledge(message);
// 如果消费失败了, 可以采用try catch方式进行捕获异常, 捕获后, 可以进行告知没有消费
//consumer.negativeAcknowledge(message);
}
}
数据Schema模式
public static void main(String[] args) throws Exception {
//1. 创建Pulsar的客户端对象
PulsarClient pulsarClient = PulsarClient.builder().serviceUrl("pulsar://node1:6650,node2:6650,node3:6650").build();
//2. 基于客户端对象构建消费者对象
Consumer<User> consumer = pulsarClient.newConsumer(AvroSchema.of(User.class))
.topic("persistent://pulsar_t/pulsar_n/my_tt04")
.subscriptionName("sub_05")
.subscribe();
//3. 循环读取数据操作
while(true){
//3.1: 接收消息
Message<User> message = consumer.receive();
//3.2: 获取消息数据
User msg = message.getValue();
System.out.println(msg);
}
}
数据批处理
public static void main(String[] args) throws Exception{
//1. 构建Pulsar的客户端对象
PulsarClient pulsarClient = PulsarClient.builder().serviceUrl("pulsar://node1:6650,node2:6650,node3:6650").build();
//2. 通过客户端构建消费者对象
Consumer<String> consumer = pulsarClient.newConsumer(Schema.STRING)
.topic("persistent://pulsar_t/pulsar_n/t_topic1")
.subscriptionName("sub_04")
// 设置支持批量读取参数配置
.batchReceivePolicy(
BatchReceivePolicy.builder()
.maxNumBytes(1024 * 1024)
.maxNumMessages(100)
.timeout(2000, TimeUnit.MILLISECONDS)
.build()
)
.subscribe();
//3. 循环读取数据
while (true) {
//3.1 读取消息(批量)
Messages<String> messages = consumer.batchReceive();
//3.2: 获取消息数据
for (Message<String> message : messages) {
String msg = message.getValue();
System.out.println("消息数据为:"+msg);
//3.3 ack确认
consumer.acknowledge(message);
}
}
}