Zookeeper之快速入门

前言

本篇文章主要还是让人快速上手入门,想要深入的话可以通过书籍系统的学习。

简介

是什么

可用于协调、构建分布式应用。

本质上是一个分布式的小文件存储系统。提供基于类似于文件系统的目录树方式的数据存储,并且可以对树中的节点进行有效管理。从而用来维护和监控你存储的数据的状态变化。通过监控这些数据状态的变化,从而可以达到基于数据的集群管理。

ZooKeeper 适用于存储和协同相关的关键数据,不适合用于大数据量存储。

应用场景

  • 配置中心
  • 注册中心
  • 分布式锁
  • 分布式队列
  • 负载均衡器
  • DNS服务
  • Master选举

安装

  • 下载地址:https://zookeeper.apache.org/releases.html

  • 创建数据存储目录

image.png

  • 拷贝zoo_sample.cfg并重命名为zoo_.cfg,打开并配置数据存储目录

image.png

  • 启动服务器和客户端

image.png

基础概念

存储模型

类似于文件系统的结构。

Zookeeper 的层次模型称作DataTree。DataTree 的每个节点叫作znode。不同于文件系统,每个节点都可以保存数据。每个节点都有一个版本(version),版本从0 开始计数。
DataTree

节点的分类

是否持久?

  • 持久性的:持久化到磁盘。
  • 临时性的:保存在内容中,Server宕机、Client超时未跟Server连接都会丢失(客户端关闭节点也消失)。

是否有序?

  • 持久有序的。
  • 临时有序的。

每个顺序节点,都分配一个唯一且是在之前基础上递增的整数。

客户端命令

帮助help

查询路径下的节点ls

查看根节点:ls /
查看子节点:ls /app1

创建节点

  • 普通节点

create /app1

  • 顺序节点

create -s /app2

  • 临时节点

create -e /app3

  • 临时有序节点

create -e -s /app4

查看节点

查询节点数据

get /app1

节点状态

stat /app1

## -------------------------节点的状态信息,也称为stat结构体---------------------
cZxid = 0x17f ## 该数据节点被创建时的事务id
#其中zxid表示的是zookeeper的事务ID,由64位数字组成,分为高32位和低32位
ctime = Sat Dec 21 19:47:36 CST 2019 ## 该数据节点创建时间
mZxid = 0x17f ## 该数据节点被修改时最新的事物id
mtime = Sat Dec 21 19:47:36 CST 2019 ## 该数据节点最后更新时间
pZxid = 0x183 ## 当前节点的父级节点事务ID
cversion = 4 ## znode子节点变化号,znode子节点修改次数
dataVersion = 0 ## znode数据变化号
aclVersion = 0 ## 访问控制列表的变化号 access control
ephemeralOwner = 0x0 ## 如果临时节点,表示当前节点的拥有者的sessionId。如果不是临时节点,则值为0
dataLength = 6 ## 数据长度
numChildren = 4 ## 子节点数据

修改、删除节点

  • 修改

set /app1 12345

  • 删除

delete /app20000000002

  • 删除多层节点

deleteall /app1

拥有子节点的父节点,无法使用delete删除

案例:使用JAVA API操作ZK

依赖
<dependency>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
            <version>3.4.8</version>
        </dependency>
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-client</artifactId>
            <version>4.0.0</version>
            <exclusions>
                <exclusion>
                    <groupId>org.apache.zookeeper</groupId>
                    <artifactId>zookeeper</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-framework</artifactId>
            <version>4.0.0</version>
            <exclusions>
                <exclusion>
                    <groupId>org.apache.zookeeper</groupId>
                    <artifactId>zookeeper</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-recipes</artifactId>
            <version>4.0.0</version>
        </dependency>
创建节点
  • 创建连接对象
    @Before
    public void initClient() {
        //初始化Zookeeper的客户端对象client
        String connectString = "127.0.0.1:2181";
        RetryPolicy retryPolicy = new ExponentialBackoffRetry(3000, 100);
        int sessionTimeoutMs = 60 * 1000;//当前客户端会话超时时间
        int connectionTimeoutMs = 15 * 1000;//连接超时时间
        client = CuratorFrameworkFactory
                .newClient(connectString, sessionTimeoutMs,
                        connectionTimeoutMs, retryPolicy);
    }

    @After
    public void destroy() {
        client.close();
    }
  • 创建节点
@After
    public void destroy() {
        client.close();
    }

    @Test
    public void createNode() throws Exception {
        //3种方式,四种节点类型
        client.start();

        //方式1: 创建空节点
//        client.create().forPath("/app3");

        //方式2: 创建有内容节点
//        client.create().forPath("/app4", "app3Node".getBytes());

        //方式3: 创建多层节点
//        client.create().creatingParentsIfNeeded().forPath("/app5/a", "aa".getBytes());

        //节点节点类型1 : 持久节点                   CreateMode.PERSISTENT
        //节点节点类型2 : 临时节点【客户端关闭则节点消失】CreateMode.EPHEMERAL
//        client.create().withMode(CreateMode.EPHEMERAL).forPath("/app6", "app5Node".getBytes());


        //节点节点类型3 : 持久节点+自带序号 CreateMode.PERSISTENT_SEQUENTIAL
//        client.create().withMode(CreateMode.PERSISTENT_SEQUENTIAL).forPath("/app7", "app6Node".getBytes());


        //节点节点类型4 : 临时节点+自带序号【客户端关闭则节点消失】CreateMode.EPHEMERAL_SEQUENTIAL
//        client.create().withMode(CreateMode.EPHEMERAL_SEQUENTIAL).forPath("/app8", "app6Node".getBytes());
        //Thread.sleep(9000);//休眠9秒,观察效果
        //3.关闭客户端,释放资源
        client.close();
    }
修改节点数据
    @Test
    public void updateNode() throws Exception {
        client.start();
        client.setData().forPath("/app1", "app1Node".getBytes());
        client.close();
    }
查询节点数据
    @Test
    public void getNode() throws Exception {
        //1.开启客户端
        client.start();
        //2.获取节点数据
        byte[] nodeDateBytes = client.getData().forPath("/app1");
        System.out.println("节点数据 = " + new String(nodeDateBytes));
        //3.关闭客户端,释放资源
        client.close();
    }
删除节点
    @Test
    public void deleteNode() throws Exception {
        //1.开启客户端
        client.start();
        //2.获取节点数据
        //方式1: 删除一个节点
        //client.delete().forPath("/app3");
        //方式2: 递归删除多个节点
        //client.delete().deletingChildrenIfNeeded().forPath("/app3");
        //方式3: 强制删除【避免一些因为网络传输导致的删除不成功】
        client.delete().guaranteed().forPath("/app3");
        //3.关闭客户端,释放资源
        client.close();
    }

ZK监听机制详解

watch机制如何运作?

watch机制本质就是订阅发布!采用了推拉结合的模式。

  • **推 **: 服务端感知到内容变化了,会发送事件信息给关注watch节点的客户端。事件本身是轻量的不含变更内容,这是“推”。
  • : 客户端收到变更通知事件,客户端需要自己去拉变更数据,这是“拉”。

Watch机制允许客户端在ZooKeeper上的数据节点发生变化时获得通知。它提供了一种事件驱动的编程模型,使得应用程序可以实时地监控和响应数据的变化,而无需持续地查询。

当客户端在某个数据节点上注册了一个Watch,它会在以下三种情况下被触发:

  • 数据节点的创建:当指定节点被创建时,与该节点相关联的Watch会被触发。
  • 数据节点的删除:当指定节点被删除时,与该节点相关联的Watch会被触发。
  • 数据节点的数据更新:当指定节点的数据发生变化时,与该节点相关联的Watch会被触发。

监听原理详解

Zookeeper观察者Watcher由三个部分组成,涉及消息通信及数据存储。

  • 客户端Client
  • 客户端监听管理器WatchManager
  • Zookeeper服务器Server

监听过程:

  1. Client向Server注册监听事件。
  2. Client将观察者Watcher存储到客户端的WatchManager里。
  3. Server触发对应事件之后,向Client推送(Push)消息通知。
  4. Client线程从WatchManager拿取回调Watcher执行业务逻辑,拉取节点数据(Pull)。

image.png
假如我们监听B节点,那么也可以监听B1、B2,但是孙子节点B11无法监听到。
image.png
注意:watcher设置后一旦触发一次后就会失效,如果要想一直监听,需要在process回调函数里重新注册相同的 watcher。

Apache Curator框架的监听实现:
Curator对watcher机制做了优化,Curator引入了Cache的概念用来实现对Zookeeper服务器端进行事件监听。Cache是Curator对事件监听的包装,其对事件的监听可以近似看做是一个本地缓存视图和远程Zookeeper视图的对比过程。而且Curator会自动的再次监听,我们就不需要自己手动的重复监听了。

**Curator客户端的Cache共有三种模式: **

  • 监听某节点的变化。
  • 监听某节点的子节点变化。
  • 监听整个树变化。

监听的三种模式

监听某节点的变化
public class Demo02Watch {


    CuratorFramework client;
    @Before
    public void initClient() {
        //初始化Zookeeper的客户端对象client
        String connectString = "127.0.0.1:2181";
        RetryPolicy retryPolicy = new ExponentialBackoffRetry(3000, 100);
        int sessionTimeoutMs = 60 * 1000;//当前客户端会话超时时间
        int connectionTimeoutMs = 15 * 1000;//连接超时时间
        client =  CuratorFrameworkFactory.newClient(connectString, sessionTimeoutMs, connectionTimeoutMs, retryPolicy);
    }
    @After
    public void destroy(){
        client.close();
    }

    /**
     * 1.监听节点数据变化
     */
    @Test
    public void listenNode() throws Exception {
        //1.启动客户端
        client.start();
        System.out.println("连接Zookeeper成功~~~~~~~~~~~~~~~~~~~");
        //2.创建节点监听对象NodeCache : 设置监听节点、监听回调方法
        NodeCache nodeCache = new NodeCache(client, "/hero");
        //设置监听节点
        ChildData currentData = nodeCache.getCurrentData();
        System.out.println("当前节点数据 = " + currentData);
        //开启监听
        nodeCache.start(true);
        //设置监听回调方法
        nodeCache.getListenable().addListener(new NodeCacheListener() {
            /**
             * 如果节点数据有变化,回调当前方法
             */
            @Override
            public void nodeChanged() throws Exception {
                //获取当前节点的数据
                ChildData currentData = nodeCache.getCurrentData();
                //获取最新节点名称
                String path = currentData.getPath();
                System.out.println("节点名称 = " + path);
                //获取最新节点数据
                byte[] currentDataByte = currentData.getData();
                System.out.println("修改后节点数据" + new String(currentDataByte));
                System.out.println("--------------->>");
            }
        });
        //从输入流中读取数据的下一个字节。
        //阻塞主线程
        //System.in.read();
        Thread.sleep(100000);
    }

}

监听节点的子节点

PathChildrenCache是用来监听指定节点的子节点变化情况【新增、修改、删除】。
启动模式:

  1. NORMAL:这是默认的启动模式。在NORMAL模式下,PathChildrenCache会从指定的路径开始监听子节点的变化,并将子节点的当前状态缓存在本地。当有子节点增加、删除或更新时,PathChildrenCache会触发相应的事件通知,并更新本地缓存。
  2. BUILD_INITIAL_CACHE:在使用该模式启动PathChildrenCache之前,需要先调用rebuild()方法来构建初始的缓存。在BUILD_INITIAL_CACHE模式下,PathChildrenCache会首先从ZooKeeper服务器获取指定路径下的所有子节点,并将它们的状态缓存在本地。然后,PathChildrenCache开始监听子节点的变化,并在本地缓存的基础上进行更新。
  3. POST_INITIALIZED_EVENT:在使用该模式启动PathChildrenCache之前,需要先调用rebuild()方法来构建初始的缓存,类似于BUILD_INITIAL_CACHE模式。不同之处在于,使用POST_INITIALIZED_EVENT模式启动PathChildrenCache后,它会在完成初始缓存构建后,发送一个PathChildrenCacheEvent.Type.INITIALIZED类型的事件通知。

如何选用合适的模式?

  • 如果只关心子节点的当前状态,并希望在子节点变化时及时得到通知,可以使用NORMAL模式。
  • 如果需要在启动时获取所有子节点的初始状态,并维护一个本地缓存,可以选择BUILD_INITIAL_CACHE模式。
  • 如果需要在初始缓存构建完成后得到一个初始化完成的事件通知,可以选择POST_INITIALIZED_EVENT模式,其它方面跟BUILD_INITIAL_CACHE差不多。

触发回调的事件类型有哪些?

  1. Type.CONNECTION_RECONNECTED 重新连接
  2. Type.connection_lost 连接丢失
  3. Type.connection_suspended 连接暂停
  4. Type.INITIALIZED 初始化
  5. Type.CHILD_REMOVED 子节点移除
  6. Type.CHILD_ADDED 子节点添加
  7. Type.CHILD_UPDATED 子节点修改

    /**
     * 2.监听当前节点的子节点变化,不含节点数据
     */
    @Test
    public void listenSubNode2() throws Exception {
        //1.启动客户端
        client.start();
        System.out.println("连接Zookeeper成功~~~~~~~~~~~~~~~~~~~");
        //2.创建节点监听对象PathChildrenCache :
        //参数3,是否缓存数据
        PathChildrenCache childrenCache = new PathChildrenCache(client, "/hero", true);
        // 开启监听
        childrenCache.start(PathChildrenCache.StartMode.POST_INITIALIZED_EVENT);
        // 设置监听回调方法
        childrenCache.getListenable().addListener((client, event) -> {
            //获取修改的数据
            byte[] bytes = event.getData().getData();
            System.out.println("节点内数据 = " + new String(bytes));
            //获取被修改的子节点
            System.out.println("节点名称 = " + event.getData().getPath());
            //获取事件类型
            //PathChildrenCacheEvent.Type.CONNECTION_RECONNECTED 重新连接
            //PathChildrenCacheEvent.Type.connection_lost 连接丢失
            //PathChildrenCacheEvent.Type.connection_suspended 连接暂停
            //PathChildrenCacheEvent.Type.INITIALIZED 初始化
            //PathChildrenCacheEvent.Type.CHILD_REMOVED 子节点移除
            //PathChildrenCacheEvent.Type.CHILD_ADDED 子节点添加
            //PathChildrenCacheEvent.Type.CHILD_UPDATED 子节点修改
            PathChildrenCacheEvent.Type type = event.getType();
            System.out.println("事件触发类型 = " + type);
            System.out.println("--------------------------------------------------->>");
        });
        //3.阻塞程序
        System.in.read();
    }

监听所有节点
/**
 * 3.树形监听所有下级节点变化【模式1+模式2】,含节点数据变更
 */
@Test
public void treeCache() throws Exception {
    //1.启动客户端
    client.start();
    System.out.println("连接Zookeeper成功~~~~~~~~~~~~~~~~~~~");
    //2.创建节点监听对象TreeCache :
    TreeCache treeCache = new TreeCache(client, "/hero");
    //启动缓存
    treeCache.start();
    //添加监听回调方法
    treeCache.getListenable().addListener(new TreeCacheListener() {
        @Override
        public void childEvent(CuratorFramework client, TreeCacheEvent event) throws Exception {
            //获取修改的数据
            byte[] bytes = event.getData().getData();
            System.out.println("节点内数据 = " + new String(bytes));
            //获取被修改的子节点
            System.out.println("节点名称 = " + event.getData().getPath());
            //获取事件类型
            //TreeCacheEvent.Type.CONNECTION_RECONNECTED 重新连接
            //TreeCacheEvent.Type.connection_lost 连接丢失
            //TreeCacheEvent.Type.connection_suspended 连接暂停
            //TreeCacheEvent.Type.INITIALIZED 初始化
            //TreeCacheEvent.Type.NODE_REMOVED 子节点移除
            //TreeCacheEvent.Type.NODE_ADDED 子节点添加
            //TreeCacheEvent.Type.NODE_UPDATED 子节点修改
            TreeCacheEvent.Type type = event.getType();
            System.out.println("事件触发类型 = " + type);
            System.out.println("--------------------------------------------------->>");
        }
    });
    //3.阻塞程序
    System.in.read();
}

结尾

假如你认真读完本篇,那么你已经超越很多人了。

如果你对其感兴趣,可以看看下一篇,我们基于ZK手写一个简单的分布式锁,加强实践,巩固知识。https://blog.csdn.net/qq_38974073/article/details/135293504

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/277858.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Unity中Shader裁剪空间推导(在Shader中使用)

文章目录 前言一、在Shader中使用转化矩阵1、在顶点着色器中定义转化矩阵2、用 UNITY_NEAR_CLIP_VALUE 区分平台矩阵3、定义一个枚举用于区分当前是处于什么相机 二、我们在DirectX平台下&#xff0c;看看效果1、正交相机下2、透视相机下3、最终代码 前言 在上一篇文章中&…

Linux驱动开发学习笔记6《蜂鸣器实验》

目录 一、蜂鸣器驱动原理 二、硬件原理分析 三、实验程序编写 1、 修改设备树文件 &#xff08;1&#xff09;添加pinctrl节点 &#xff08;2&#xff09;添加BEEP设备节点 &#xff08;3&#xff09;检查PIN 是否被其他外设使用 2、蜂鸣器驱动程序编写 3、编写测试AP…

【JS逆向学习】快乐学堂

逆向目标 登陆接口&#xff1a;https://www.91118.com/passport/Account/LoginPost?r0.20790763112591337&kdsyes&username13127519353&passbb3mlkFBqqo%3D&recordPwd1&ckcode5719&fscodeklxt&invite 加密参数&#xff1a; r&#xff1a;0.2079…

机器学习模型可解释性的结果分析

模型的可解释性是机器学习领域的一个重要分支&#xff0c;随着 AI 应用范围的不断扩大&#xff0c;人们越来越不满足于模型的黑盒特性&#xff0c;与此同时&#xff0c;金融、自动驾驶等领域的法律法规也对模型的可解释性提出了更高的要求&#xff0c;在可解释 AI 一文中我们已…

Linux开发工具——gdb篇

Linux下调试工具——gdb 文章目录 makefile自动化构建工具 gdb背景 gdb的使用 常用命令 总结 前言&#xff1a; 编写代码我们使用vim&#xff0c;编译代码我们使用gcc/g&#xff0c;但是我们&#xff0c;不能保证代码没问题&#xff0c;所以调试是必不可少的。与gcc/vim一样&…

Python中使用SQLite数据库的方法2-2

3.3.2 创建表单及字段 通过“3.2 创建Cursor类的对象”中创建的Cursor类的对象cur创建表单及字段&#xff0c;代码如图5所示。 图5 创建表单及字段 从图5中可以看出&#xff0c;通过Cursor类的对象cur调用了Cursor类的execute()方法来执行SQL语句。该方法的参数即为要指定的S…

在电商行业中,如何采集电商数据使用数据分析提高业务绩效

数据分析丨知识点丨电商数据采集 福利指路&#xff1a;文章底部领取《数据分析全家桶》 随着电子商务的不断发展&#xff0c;越来越多的企业开始使用数据分析来提高业务绩效。数据分析可以帮助电商企业更好地理解市场和客户&#xff0c;以制定更有针对性的营销策略和产品方案。…

ksuser.dll文件缺失怎么办?软件或游戏无法启动,一键自动修复

很多小伙伴反馈&#xff0c;自己的电脑中了病毒&#xff0c;被杀毒软件清理后&#xff0c;在打开游戏或软件的时候&#xff0c;经常会报错“提示无法找到ksuser.dll文件&#xff0c;建议重新安装软件或游戏”。自己根据提示重装后&#xff0c;还是报错&#xff0c;不知道应该怎…

两向量叉乘值为对应平行四边形面积--公式推导

两向量叉乘值为对应平行四边形面积--公式推导 介绍 介绍

[电磁学]大学物理陈秉乾老师课程笔记

主页有博主其他上万字的精品笔记,都在不断完善ing~ 第一讲 绪论,库仑定律 主要讲解了电磁学中的库伦定律和电场的相关概念&#xff0c;介绍了电荷和电磁相互作用的规律&#xff0c;并讲解了电场强度和电势的概念。 03:14 &#x1f393; 库伦定律&#xff1a;电势能与电荷的关…

【JAVA核心知识】分布式事务框架Seata

Seata 基本信息 GitHub&#xff1a;https://github.com/seata/seatastars: 20.6k 最新版本&#xff1a; v1.6.1 Dec 22, 2022 官方文档&#xff1a;http://seata.io/zh-cn/index.html 注意 官方仅仅支持同步调用。 官方在FAQ中表示对于异步框架需要自行支持。 具体的扩展思…

【Maven】<scope>provided</scope>

在Maven中&#xff0c;“provided”是一个常用的依赖范围&#xff0c;它表示某个依赖项在编译和测试阶段是必需的&#xff0c;但在运行时则由外部环境提供&#xff0c;不需要包含在最终的项目包中。下面是对Maven scope “provided”的详细解释&#xff1a; 编译和测试阶段可用…

关于2024年度PMI认证考试计划的通知

尊敬的考生&#xff1a; 经PMI和中国国际人才交流基金会研究决定&#xff0c;2024年度中国大陆地区计划举办四次PMI认证考试&#xff0c;3月、6月、8月、11月各举办一次&#xff0c;具体考试日期另行公布。如遇特殊情况需变更考试计划的&#xff0c;将提前另行通知。 PMI&#…

Ubuntu安装K8S(1.28版本,基于containrd)

原文网址&#xff1a;Ubuntu安装K8S(1.28版本&#xff0c;基于containrd&#xff09;-CSDN博客 简介 本文介绍Ubuntu安装K8S的方法。 官网文档&#xff1a;这里 1.安装K8S 1.让apt支持SSL传输 sudo apt-get update sudo apt-get -y install apt-transport-https ca-certi…

超声波清洗机实测!北柏、希亦、洁盟超声波清洗机哪款清洁强?

眼镜清洗其实是一件非常重要的事情&#xff0c;一定不要忽视&#xff0c;表面看眼镜已经清洗干净了&#xff0c;实际眼镜缝隙中的杂污渍还没清洁到位&#xff0c;时间一长就会非常容易滋生细菌以及长螨虫&#xff01;为了杜绝这种情况发生&#xff0c;大家务必重视起清洗眼镜&a…

SpringBoot2.7.12整合Knife4j

SpringBoot2.7.12整合Knife4j 是什么 Knife4j是一个集Swagger2 和 OpenAPI3为一体的增强解决方案 添加依赖 <!--引入Knife4j的官方start包,该指南选择Spring Boot版本<3.0,开发者需要注意--> <dependency><groupId>com.github.xiaoymin</groupId>&l…

Zookeeper-Zookeeper特性与节点数据类型详解

1.Zookeeper介绍 ZooKeeper 是一个开源的分布式协调框架&#xff0c;是Apache Hadoop 的一个子项目&#xff0c;主要用来解决分布式集群中应用系统的一致性问题。Zookeeper 的设计目标是将那些复杂目容易出错的分布式一致性服务封装起来&#xff0c;构成一高效可靠的原语集&…

如何本地搭建FastDFS文件服务器并实现远程访问【内网穿透】

文章目录 前言1. 本地搭建FastDFS文件系统1.1 环境安装1.2 安装libfastcommon1.3 安装FastDFS1.4 配置Tracker1.5 配置Storage1.6 测试上传下载1.7 与Nginx整合1.8 安装Nginx1.9 配置Nginx 2. 局域网测试访问FastDFS3. 安装cpolar内网穿透4. 配置公网访问地址5. 固定公网地址5.…

交换两个数字的三种方法-LeetCode做题总结 344

344. 反转字符串 题解Java知识点交换两个数字的三种方法1、temp2、异或3、 题解 class Solution {public void reverseString(char[] s) {char temp;for(int i0,js.length-1; i<j; i,j--) {temp s[i];s[i] s[j];s[j] temp;}} }Java知识点 交换两个数字的三种方法 1、t…

Flutter BottomSheet 拖动分两段展示

第一段 第二段 实现思路 通过 GestureDetector 的 Drag 方法&#xff0c;动态改变Dialog的高度&#xff0c;通过设置一个最大高度和最小高度分成两层进行展示 实现 常用的展示BottomSheet的方法为 showModalBottomSheet /// 设置最高最好以高度的比例进行设置&#xff0c;方…