做外汇的国外新闻网站/百度经验手机版官网
文章目录
- 前言
- (一) 创建topic
- (二) 生产者
- (三)消费者
- 1. At-most-once Kafka Consumer
- 2. At-least-once kafka consumer
- 3. 使用subscribe实现Exactly-once
- 4. 使用assign实现Exactly-once
前言
本文主要是以kafka 09的client为例子,详解kafka client的使用,包括kafka消费者的三种消费语义at-most-once, at-least-once, 和 exactly-once message ,生产者的使用等。
(一) 创建topic
bin/kafka-topics --zookeeper localhost:2181 --create --topic normal-topic --partitions 2 --replication-factor 1
(二) 生产者
public class ProducerExample {public static void main(String[] str) throws InterruptedException, IOException {System.out.println("Starting ProducerExample ...");sendMessages();}private static void sendMessages() throws InterruptedException, IOException {Producer<String, String> producer = createProducer();sendMessages(producer);// Allow the producer to complete sending of the messages before program exit.Thread.sleep(20);}private static Producer<String, String> createProducer() {Properties props = new Properties();props.put("bootstrap.servers", "localhost:9092");props.put("acks", "all");props.put("retries", 0);// Controls how much bytes sender would wait to batch up before publishing to Kafka.props.put("batch.size", 10);props.put("linger.ms", 1);props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");return new KafkaProducer(props);}private static void sendMessages(Producer<String, String> producer) {String topic = "normal-topic";int partition = 0;long record = 1;for (int i = 1; i <= 10; i++) {producer.send(new ProducerRecord<String, String>(topic, partition, Long.toString(record),Long.toString(record++)));}}
}
(三)消费者
消费者注册到kafka有多种方式:
subscribe:这种方式在新增topic或者partition或者消费者增加或者消费者减少的时候,会进行消费者组内消费者的再平衡。
assign:这种方式注册的消费者不会进行rebalance。
上面两种方式都是可以实现,三种消费语义的。具体API的使用请看下文。
1. At-most-once Kafka Consumer
做多一次消费语义是kafka消费者的默认实现。配置这种消费者最简单的方式是
1). enable.auto.commit设置为true。
2). auto.commit.interval.ms设置为一个较低的时间范围。
3). consumer.commitSync()不要调用该方法。
由于上面的配置,就可以使得kafka有线程负责按照指定间隔提交offset。
但是这种方式会使得kafka消费者有两种消费语义:
a.最多一次语义->at-most-once
消费者的offset已经提交,但是消息还在处理,这个时候挂了,再重启的时候会从上次提交的offset处消费,导致上次在处理的消息部分丢失。
b. 最少一次消费语义->at-least-once
消费者已经处理完了,但是offset还没提交,那么这个时候消费者挂了,就会导致消费者重复消费消息处理。但是由于auto.commit.interval.ms设置为一个较低的时间范围,会降低这种情况出现的概率。
代码如下:
public class AtMostOnceConsumer {public static void main(String[] str) throws InterruptedException {System.out.println("Starting AtMostOnceConsumer ...");execute();}private static void execute() throws InterruptedException {KafkaConsumer<String, String> consumer = createConsumer();// Subscribe to all partition in that topic. 'assign' could be used here// instead of 'subscribe' to subscribe to specific partition.consumer.subscribe(Arrays.asList("normal-topic"));processRecords(consumer);}private static KafkaConsumer<String, String> createConsumer() {Properties props = new Properties();props.put("bootstrap.servers", "localhost:9092");String consumeGroup = "cg1";props.put("group.id", consumeGroup);// Set this property, if auto commit should happen.props.put("enable.auto.commit", "true");// Auto commit interval, kafka would commit offset at this interval.props.put("auto.commit.interval.ms", "101");// This is how to control number of records being read in each pollprops.put("max.partition.fetch.bytes", "135");// Set this if you want to always read from beginning.// props.put("auto.offset.reset", "earliest");props.put("heartbeat.interval.ms", "3000");props.put("session.timeout.ms", "6001");props.put("key.deserializer","org.apache.kafka.common.serialization.StringDeserializer");props.put("value.deserializer","org.apache.kafka.common.serialization.StringDeserializer");return new KafkaConsumer<String, String>(props);}private static void processRecords(KafkaConsumer<String, String> consumer) {while (true) {ConsumerRecords<String, String> records = consumer.poll(100);long lastOffset = 0;for (ConsumerRecord<String, String> record : records) {System.out.printf("\n\roffset = %d, key = %s, value = %s", record.offset(), record.key(), record.value());lastOffset = record.offset();}System.out.println("lastOffset read: " + lastOffset);process();}}private static void process() throws InterruptedException {// create some delay to simulate processing of the message.Thread.sleep(20);}
}
2. At-least-once kafka consumer
实现最少一次消费语义的消费者也很简单。
1). 设置enable.auto.commit为false
2). 消息处理完之后手动调用consumer.commitSync()
这种方式就是要手动在处理完该次poll得到消息之后,调用offset异步提交函数consumer.commitSync()。建议是消费者内部实现密等,来避免消费者重复处理消息进而得到重复结果。最多一次发生的场景是消费者的消息处理完并输出到结果库(也可能是部分处理完),但是offset还没提交,这个时候消费者挂掉了,再重启的时候会重新消费并处理消息。
代码如下:
public class AtLeastOnceConsumer {public static void main(String[] str) throws InterruptedException {System.out.println("Starting AutoOffsetGuranteedAtLeastOnceConsumer ...");execute();}private static void execute() throws InterruptedException {KafkaConsumer<String, String> consumer = createConsumer();// Subscribe to all partition in that topic. 'assign' could be used here// instead of 'subscribe' to subscribe to specific partition.consumer.subscribe(Arrays.asList("normal-topic"));processRecords(consumer);}private static KafkaConsumer<String, String> createConsumer() {Properties props = new Properties();props.put("bootstrap.servers", "localhost:9092");String consumeGroup = "cg1";props.put("group.id", consumeGroup);// Set this property, if auto commit should happen.props.put("enable.auto.commit", "true");// Make Auto commit interval to a big number so that auto commit does not happen,// we are going to control the offset commit via consumer.commitSync(); after processing // message.props.put("auto.commit.interval.ms", "999999999999");// This is how to control number of messages being read in each pollprops.put("max.partition.fetch.bytes", "135");props.put("heartbeat.interval.ms", "3000");props.put("session.timeout.ms", "6001");props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");props.put("value.deserializer","org.apache.kafka.common.serialization.StringDeserializer");return new KafkaConsumer<String, String>(props);}private static void processRecords(KafkaConsumer<String, String> consumer) throws {while (true) {ConsumerRecords<String, String> records = consumer.poll(100);long lastOffset = 0;for (ConsumerRecord<String, String> record : records) {System.out.printf("\n\roffset = %d, key = %s, value = %s", record.offset(), record.key(), record.value());lastOffset = record.offset();}System.out.println("lastOffset read: " + lastOffset);process();// Below call is important to control the offset commit. Do this call after you// finish processing the business process.consumer.commitSync();}}private static void process() throws InterruptedException {// create some delay to simulate processing of the record.Thread.sleep(20);}
}
3. 使用subscribe实现Exactly-once
使用subscribe实现Exactly-once 很简单,具体思路如下:
1). 将enable.auto.commit设置为false。
2). 不调用consumer.commitSync()。
3). 使用subcribe定于topic。
4). 实现一个ConsumerRebalanceListener,在该listener内部执行
consumer.seek(topicPartition,offset),从指定的topic/partition的offset处启动。
5). 在处理消息的时候,要同时控制保存住每个消息的offset。以原子事务的方式保存offset和处理的消息结果。传统数据库实现原子事务比较简单。但对于非传统数据库,比如hdfs或者nosql,为了实现这个目标,只能将offset与消息保存在同一行。
6). 实现密等,作为保护层。
代码如下:
public class ExactlyOnceDynamicConsumer {private static OffsetManager offsetManager = new OffsetManager("storage2");public static void main(String[] str) throws InterruptedException {System.out.println("Starting ExactlyOnceDynamicConsumer ...");readMessages();}private static void readMessages() throws InterruptedException {KafkaConsumer<String, String> consumer = createConsumer();// Manually controlling offset but register consumer to topics to get dynamically// assigned partitions. Inside MyConsumerRebalancerListener use// consumer.seek(topicPartition,offset) to control offset which messages to be read.consumer.subscribe(Arrays.asList("normal-topic"),new MyConsumerRebalancerListener(consumer));processRecords(consumer);}private static KafkaConsumer<String, String> createConsumer() {Properties props = new Properties();props.put("bootstrap.servers", "localhost:9092");String consumeGroup = "cg3";props.put("group.id", consumeGroup);// Below is a key setting to turn off the auto commit.props.put("enable.auto.commit", "false");props.put("heartbeat.interval.ms", "2000");props.put("session.timeout.ms", "6001");// Control maximum data on each poll, make sure this value is bigger than the maximum // single message sizeprops.put("max.partition.fetch.bytes", "140");props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");return new KafkaConsumer<String, String>(props);}private static void processRecords(KafkaConsumer<String, String> consumer) {while (true) {ConsumerRecords<String, String> records = consumer.poll(100);for (ConsumerRecord<String, String> record : records) {System.out.printf("offset = %d, key = %s, value = %s\n", record.offset(), record.key(), record.value());// Save processed offset in external storage.offsetManager.saveOffsetInExternalStore(record.topic(), record.partition(), record.offset());}}}
}
public class MyConsumerRebalancerListener implements org.apache.kafka.clients.consumer.ConsumerRebalanceListener {private OffsetManager offsetManager = new OffsetManager("storage2");private Consumer<String, String> consumer;public MyConsumerRebalancerListener(Consumer<String, String> consumer) {this.consumer = consumer;}public void onPartitionsRevoked(Collection<TopicPartition> partitions) {for (TopicPartition partition : partitions) {offsetManager.saveOffsetInExternalStore(partition.topic(), partition.partition(), consumer.position(partition));}}public void onPartitionsAssigned(Collection<TopicPartition> partitions) {for (TopicPartition partition : partitions) {consumer.seek(partition, offsetManager.readOffsetFromExternalStore(partition.topic(), partition.partition()));}}
}
/**
* The partition offset are stored in an external storage. In this case in a local file system where
* program runs.
*/
public class OffsetManager {private String storagePrefix;public OffsetManager(String storagePrefix) {this.storagePrefix = storagePrefix;}/*** Overwrite the offset for the topic in an external storage.** @param topic - Topic name.* @param partition - Partition of the topic.* @param offset - offset to be stored.*/void saveOffsetInExternalStore(String topic, int partition, long offset) {try {FileWriter writer = new FileWriter(storageName(topic, partition), false);BufferedWriter bufferedWriter = new BufferedWriter(writer);bufferedWriter.write(offset + "");bufferedWriter.flush();bufferedWriter.close();} catch (Exception e) {e.printStackTrace();throw new RuntimeException(e);}}/*** @return he last offset + 1 for the provided topic and partition.*/long readOffsetFromExternalStore(String topic, int partition) {try {Stream<String> stream = Files.lines(Paths.get(storageName(topic, partition)));return Long.parseLong(stream.collect(Collectors.toList()).get(0)) + 1;} catch (Exception e) {e.printStackTrace();}return 0;}private String storageName(String topic, int partition) {return storagePrefix + "-" + topic + "-" + partition;}
}
4. 使用assign实现Exactly-once
使用assign实现Exactly-once 也很简单,具体思路如下:
1). 将enable.auto.commit设置为false。
2). 不调用consumer.commitSync()。
3). 调用assign注册kafka消费者到kafka
4). 初次启动的时候,调用consumer.seek(topicPartition,offset)来指定offset。
5). 在处理消息的时候,要同时控制保存住每个消息的offset。以原子事务的方式保存offset和处理的消息结果。传统数据库实现原子事务比较简单。但对于非传统数据库,比如hdfs或者nosql,为了实现这个目标,只能将offset与消息保存在同一行。
6). 实现密等,作为保护层。
代码如下:
public class ExactlyOnceStaticConsumer {private static OffsetManager offsetManager = new OffsetManager("storage1");public static void main(String[] str) throws InterruptedException, IOException {System.out.println("Starting ExactlyOnceStaticConsumer ...");readMessages();}private static void readMessages() throws InterruptedException, IOException {KafkaConsumer<String, String> consumer = createConsumer();String topic = "normal-topic";int partition = 1;TopicPartition topicPartition =registerConsumerToSpecificPartition(consumer, topic, partition);// Read the offset for the topic and partition from external storage.long offset = offsetManager.readOffsetFromExternalStore(topic, partition);// Use seek and go to exact offset for that topic and partition.consumer.seek(topicPartition, offset);processRecords(consumer);}private static KafkaConsumer<String, String> createConsumer() {Properties props = new Properties();props.put("bootstrap.servers", "localhost:9092");String consumeGroup = "cg2";props.put("group.id", consumeGroup);// Below is a key setting to turn off the auto commit.props.put("enable.auto.commit", "false");props.put("heartbeat.interval.ms", "2000");props.put("session.timeout.ms", "6001");// control maximum data on each poll, make sure this value is bigger than the maximum // single message sizeprops.put("max.partition.fetch.bytes", "140");props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");return new KafkaConsumer<String, String>(props);}/*** Manually listens for specific topic partition. But, if you are looking for example of how to * dynamically listens to partition and want to manually control offset then see* ExactlyOnceDynamicConsumer.java*/private static TopicPartition registerConsumerToSpecificPartition(KafkaConsumer<String, String> consumer, String topic, int partition) {TopicPartition topicPartition = new TopicPartition(topic, partition);List<TopicPartition> partitions = Arrays.asList(topicPartition);consumer.assign(partitions);return topicPartition;}/*** Process data and store offset in external store. Best practice is to do these operations* atomically. */private static void processRecords(KafkaConsumer<String, String> consumer) throws {while (true) {ConsumerRecords<String, String> records = consumer.poll(100);for (ConsumerRecord<String, String> record : records) {System.out.printf("offset = %d, key = %s, value = %s\n", record.offset(), record.key(), record.value());offsetManager.saveOffsetInExternalStore(record.topic(), record.partition(), record.offset());}}}
}
相关文章:

MQ:kafka-消费者的三种语义
文章目录 前言(一) 创建topic(二) 生产者(三)消费者1. At-most-once Kafka Consumer2. At-least-once kafka consumer3. 使用subscribe实现Exactly-once4. 使用assign实现Exactly-once 前言 本文主要是以kafka 09的client为例子,详解kafka c…...

中国1km分辨率SSP119情景(SSP119、SSP245 SSP585),模式逐月降水量数据集(2021-2100)
目录 简介 摘要 代码 引用 网址推荐 知识星球 机器学习 干旱监测平台 中国1km分辨率SSP119情景EC-Earth3模式逐月降水量数据集(2021-2100) 简介 该数据集为中国多情景多模式逐月降水量数据,空间分辨率为0.0083333(约1km),时间为2021年1月-2100年…...

21天掌握javaweb-->第8天:前后端分离架构与Axios请求
前后端分离架构概念 前后端分离架构是一种现代Web应用开发模式,其中前端和后端分别独立开发和部署,通过API进行数据交互。这种架构使得前端专注于用户界面和用户体验,而后端则专注于业务逻辑和数据处理。 优势 开发效率高:前后端可以并行开发,减少了开发时间。技术栈灵活…...

基于阻塞队列的生产者消费者模型动画演示
一个基于阻塞队列的生产者消费者模型的动画演示: 这是打包好的程序。程序是用 QT 写的。 通过网盘分享的文件:CP模型.7z 链接: https://pan.baidu.com/s/1YjC7YiSqHGqdr6bbffaDWg?pwde6g5 提取码: e6g5 CP模型...

DHCP和BOOTP选项及DHCP协议操作详解
DHCP和BOOTP选项及DHCP协议操作详解 DHCP与BOOTP简介 1. BOOTP(Bootstrap Protocol) 功能:提供静态配置的IP分配。用途:在早期用于无盘工作站启动时获取IP地址和基本配置。缺点:只能提供静态IP配置,无法动…...

数据结构--链表和单链表详解及实现
一.前言 数据结构思维导图如下,灰色标记的是之前讲过的,本文将带你走近单链表(红色标记部分),希望大家有所收获🌹🌹 二.链表的定义和概念 在讲单链表之前,我们先学习一下链表 2.1 链表的定义 链表是一种…...

vue3基础知识
书接上文,这篇继续来学习vue3的核心语法,可以先看上一篇再来看这篇效果更好。 1. computed computed 用于创建 计算属性,即基于其他响应式数据的值动态计算并缓存的属性。它的主要作用是优化性能和提高代码的可维护性,避免不必要…...

【Linux系统】Ubuntu 缓冲区机制
在Ubuntu中,和其他操作系统有个不一样的机制:缓冲区。这篇文章是对与缓冲区的详细介绍。 在 Ubuntu 中(以及其他基于 Linux 的操作系统),缓冲区(Buffer)是内核用于优化 I/O 操作的重要机制。它…...

ChatGPT 最新推出的 Pro 订阅计划,具备哪些能力 ?
OpenAI 最近推出了 ChatGPT Pro,这是一个每月收费 200 美元的高级订阅计划,旨在为用户提供对 OpenAI 最先进模型和功能的高级访问。 以下是 ChatGPT Pro 的主要功能和能力: 高级模型访问: o1 模型:包括 o1 和 o1 Pro…...

数据结构理论
内容来源青岛大学数据结构与算法课程,链接:数据结构与算法基础(青岛大学-王卓)_哔哩哔哩_bilibili 绪论 数据结构概述 数据结构和算法的定义:我们如何把现实中大量而复杂的问题以特定的数据类型和特定的存储结构保存…...

es 3期 第14节-全文文本分词查询
#### 1.Elasticsearch是数据库,不是普通的Java应用程序,传统数据库需要的硬件资源同样需要,提升性能最有效的就是升级硬件。 #### 2.Elasticsearch是文档型数据库,不是关系型数据库,不具备严格的ACID事务特性ÿ…...

六安市第二届网络安全大赛复现
misc 听说你也喜欢俄罗斯方块? ppt拼接之后 缺三个角补上 flag{qfnh_wergh_wqef} 流量分析 流量包分离出来一个压缩包 出来一张图片 黑色代表0白色代表1 101010 1000 rab 反的压缩包 转一下 密码:拾叁拾陆叁拾贰陆拾肆 密文:4p4n5758…...

Sarcomere仿人灵巧手ARTUS,20个自由度拓宽机器人作业边界
Sarcomere Dynamics 是一家深度技术先驱,通过开发和商业化仿人机械来改变机器人行业。专注于为科研人员,系统集成商和制造商提供更实惠、更轻便且更灵活的末端执行器替代品。凭借创新的致动器技术,创造了一款紧凑、轻便且非常坚固的机械手Art…...

Django drf 基于serializers 快速使用
1. 安装: pip install djangorestframework 2. 添加rest_framework到您的INSTALLED_APPS设置。 settings.pyINSTALLED_APPS [...rest_framework, ] 3. 定义模型 models.pyfrom django.db import modelsclass BookModel(models.Model):name models.CharField(max_length64)…...

pycharm集成环境中关于安装sklearn库报错问题分析及解决
在输入pip install sklearn后,出现如下提示: pip install sklearn Collecting sklearn Using cached sklearn-0.0.post12.tar.gz (2.6 kB) Installing build dependencies ... done Getting requirements to build wheel ... error error: subprocess-…...

AI - 浅聊一下基于LangChain的AI Agent
AI - 浅聊一下基于LangChain的AI Agent 大家好,今天我们来聊聊一个很有意思的主题: AI Agent ,就是目前非常流行的所谓的AI智能体。AI的发展日新月异,都2024年末了,如果此时小伙伴们对这个非常火的概念还不清楚的话&a…...

《【Linux】深入理解进程管理与 fork 系统调用的实现原理》
一、引言 在 Linux 操作系统中,进程管理是核心功能之一。进程是操作系统进行资源分配和调度的基本单位。理解进程管理的原理以及 fork 系统调用的实现对于深入掌握 Linux 系统的运行机制至关重要。本文将深入探讨 Linux 中的进程管理以及 fork 系统调用的实现原理&a…...

docker-compose部署skywalking 8.1.0
一、下载镜像 #注意 skywalking-oap-server和skywalking java agent版本强关联,版本需要保持一致性 docker pull elasticsearch:7.9.0 docker pull apache/skywalking-oap-server:8.1.0-es7 docker pull apache/skywalking-ui:8.1.0二、部署文件docker-compose.yam…...

AI 总结的的 AI 学习路线
一、入门阶段:数学基础与编程语言 数学基础 线性代数 当年白纸黑字推演, 都是泪啊,草稿本都用了一卷。 学习向量、矩阵的基本概念,包括向量的加法、减法、点积和叉积,矩阵的乘法、转置等运算。例如,在计算…...

离散傅里叶级数(DFS)详解
1. 引言 离散傅里叶级数(Discrete Fourier Series, DFS)是信号处理领域中一项基础且重要的数学工具,用于分析和处理周期性的离散信号。它通过将离散时间信号表示为一组正弦和余弦的和,从而使得信号在频域上得到更清晰的描述。与连…...

Java 类加载机制详解
🧑 博主简介:CSDN博客专家,历代文学网(PC端可以访问:https://literature.sinhy.com/#/literature?__c1000,移动端可微信小程序搜索“历代文学”)总架构师,15年工作经验,…...

1.1 Beginner Level学习之“编写简单的发布服务器和订阅服务器”(第十一节)
学习大纲: 1. 编写发布服务器节点 在 ROS 中,节点是连接到 ROS 网络的可执行文件。我创建了一个名为 talker 的发布者节点,它会向一个主题 chatter 不断发送消息。 首先,进入你的工作包 beginner_tutorials(假设你已…...

AIQuora:开启论文写作新篇章
在这个信息爆炸的时代,学术写作已成为研究者不可或缺的技能。然而,面对繁重的写作任务,许多学者和学生常常感到力不从心。AIQuora,一个专业的文理工科论文智能写作助手,以其免费开题报告生成功能,为学术写作…...

【C语言】库函数常见的陷阱与缺陷(1):字符串处理函数
目录 一、 strcpy 函数 1.1. 功能与常见用法 1.2. 陷阱与缺陷 1.3. 安全替代 1.4. 代码示例 二、strcat 函数 2.1. 功能与常见用法 2.2. 陷阱与缺陷 2.3. 安全替代 2.4. 代码示例 三、strcmp 函数 3.1. 功能与常见用法 3.2. 陷阱与缺陷 3.3. 安全替代 3.4. 代…...

Mysql索引原理及优化——岁月云实战笔记
根据Mysql索引原理及优化这个博主的视频学习,对现在的岁月云项目中mysql进行优化,我要向这个博主致敬,之前应用居多,理论所知甚少,于是将学习到东西,应用下来,看看是否有好的改观。 1 索引原理…...

AGCRN论文解读
一、创新点 传统GCN只能基于静态预定义图建模全局共享模式,而AGCRN通过两种GCN的增强模块(NAPL、DAGG)实现了更精细的节点特性学习和图结构生成。 1 节点自适应参数学习模块(NAPL) 传统GCN通过共享参数(权重…...

Python机器学习笔记(五、决策树集成)
集成(ensemble)是合并多个机器学习模型来构建更强大模型的方法。这里主要学习两种集成模型:一是随机森林(random forest);二是梯度提升决策树(gradient boosted decision tree)。 1…...

Kafka单机及集群部署及基础命令
目录 一、 Kafka介绍1、kafka定义2、传统消息队列应用场景3、kafka特点和优势4、kafka角色介绍5、分区和副本的优势6、kafka 写入消息的流程 二、Kafka单机部署1、基础环境2、iptables -L -n配置3、下载并解压kafka部署包至/usr/local/目录4、修改server.properties5、修改/etc…...

如何使用 Python 实现链表的反转?
在Python中实现链表的反转可以通过几种不同的方法。这里,我将向你展示如何使用迭代和递归两种方式来反转链表。 1. 迭代方法 迭代方法是通过遍历链表,逐个节点地改变其指向来实现反转的。 class ListNode: def __init__(self, val0, nextNone): …...

react跳转传参的方法
传参 首先下载命令行 npm react-router-dom 然后引入此代码 前面跳转的是页面 后面传的是你需要传的参数接参 引入此方法 useLocation():这是 react-router-dom 提供的一个钩子,用于获取当前路由的位置对象location.state:这是从其他页面传…...