【Flink】使用CoProcessFunction完成实时对账、基于时间的双流join

news2024/11/25 12:26:17

文章目录

  • 零 处理函数回顾
  • 一 CoProcessFunction的使用
    • 1 CoProcessFunction使用
    • 2 实时对账
      • (1)使用离线数据源(批处理)
      • (2)使用高自定义数据源(流处理)
  • 二 基于时间的双流 Join
    • 1 基于间隔的 Join
      • (1)正向join
      • (2)反向join
    • 2 基于窗口的 Join

零 处理函数回顾

Flink 提供了 8 个 Process Function:

  • ProcessFunction:只能处理单条流DateStream,由于处理的是没有经过分流的流,所以自然没有办法注册定时器和状态变量,目前使用到的所有状态变量都是针对key所独有的,所以在对一条流进行keyBy以后,可以使用KeyedProcessFunction。
  • KeyedProcessFunction:使用key对数据做了逻辑分区(分流、分组),使用KeyedProcessFunction,配合字典状态MapState + 定时器可以模拟出完整的ProcessWindowFunction功能。
  • CoProcessFunction:处理双流合并最强API,想要完成需求需要使用大量的状态变量和定时器。
  • ProcessJoinFunction
  • BroadcastProcessFunction
  • KeyedBroadcastProcessFunction
  • ProcessWindowFunction:处理的是keyBy后再进行开窗的流。
  • ProcessAllWindowFunction

一 CoProcessFunction的使用

Flink sql中存在这样一个语法SELECT * FROM A INNER JOIN B WHERE A.id = B.id,A和B各为一条数据流,对于批处理来说,处理的A和B两张静态表笛卡尔积的过滤(先将A和B中相同id的数据shuffle到同一分区,然后再做笛卡尔积)。对Flink来说,需要处理的是两条流的等值内连接,对流A中和流B中一样的key做笛卡尔积。

对于两条输入流,DataStream API提供了CoProcessFunction这样的low-level操作。CoProcessFunction 提供了操作每一个输入流的方法: processElement1() 和 processElement2()。类似于 ProcessFunction,这两种方法都通过 Context 对象来调用。这个 Context 对象可以访问事件数据,定时器时间戳,TimerService,以及 side outputs。CoProcessFunction 也提供了 onTimer() 回调函数。

1 CoProcessFunction使用

使用CoProcessFunction完成底层内连接

public static void main(String[] args) throws Exception {
    StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
    env.setParallelism(1);

    DataStreamSource<Tuple2<String, Integer>> stream1 = env.fromElements(
            Tuple2.of("a", 1),
            Tuple2.of("b", 2),
            Tuple2.of("a",3)
    );

    DataStreamSource<Tuple2<String, String>> stream2 = env.fromElements(
            Tuple2.of("a", "a"),
            Tuple2.of("b", "b"),
            Tuple2.of("a","aa")
    );

    // 需要将以往的数据都存储下来
    stream1
            .keyBy(r -> r.f0)
            .connect(stream2.keyBy(r -> r.f0))
            // 泛型依次为:流一、流二、输出
            .process(new CoProcessFunction<Tuple2<String, Integer>, Tuple2<String, String>, String>() {
                private ListState<Tuple2<String,Integer>> listState1;
                private ListState<Tuple2<String,String>> listState2;

                @Override
                public void open(Configuration parameters) throws Exception {
                    super.open(parameters);
                    listState1 = getRuntimeContext().getListState(
                            new ListStateDescriptor<Tuple2<String, Integer>>("list1", Types.TUPLE(Types.STRING,Types.INT))
                    );

                    listState2 = getRuntimeContext().getListState(
                            new ListStateDescriptor<Tuple2<String, String>>("list2",Types.TUPLE(Types.STRING,Types.STRING))
                    );
                }

                @Override
                public void processElement1(Tuple2<String, Integer> value, Context ctx, Collector<String> out) throws Exception {
                    // 流一元素到来,存储到状态变量1中
                    listState1.add(value);
                    // 遍历流二中的所有元素,与下面遍历流一中的元素,共同组成了笛卡尔积
                    for(Tuple2<String,String> e : listState2.get()){
                        out.collect(value + " =>> " + e);
                    }
                }

                @Override
                public void processElement2(Tuple2<String, String> value, Context ctx, Collector<String> out) throws Exception {
                    listState2.add(value);
                    for (Tuple2<String,Integer> e : listState1.get()){
                        out.collect(e + " =>> " + value);
                    }
                }
            })
            .print();

    env.execute();
}

2 实时对账

需求分析:如网上购物,创建订单,拉起第三方支付,支付成功后,第三方支付的服务器会发送一条回调通知,告诉购物app,已经支付成功,然后app后端会将订单状态由未支付改为已支付。现由于某种原因,app没有收到这条回调通知,导致钱被扣了,app中的订单状态仍然为未支付状态。

现在需要完成的任务是:下订单的操作和第三方发回的回调通知,这两条流对下账,保证订单状态及时修改。

(1)使用离线数据源(批处理)

代码如下,一事件到达后5s内另一事件没到达即认定对账失败:

public static void main(String[] args) throws Exception{
    StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
    env.setParallelism(1);

    SingleOutputStreamOperator<Event> orderStream = env
            .fromElements(
                    Event.of("order-1", "order", 1000L),
                    Event.of("order-2", "order", 2000L)
            )
            .assignTimestampsAndWatermarks(
                    WatermarkStrategy.<Event>forMonotonousTimestamps()
                            .withTimestampAssigner(new SerializableTimestampAssigner<Event>() {
                                @Override
                                public long extractTimestamp(Event element, long recordTimestamp) {
                                    return element.timestamp;
                                }
                            })
            );

    SingleOutputStreamOperator<Event> weChatStream = env
            .fromElements(
                    Event.of("order-1", "weChat", 3000L),
                    Event.of("order-3", "weChat", 4000L)
            )
            .assignTimestampsAndWatermarks(
                    WatermarkStrategy.<Event>forMonotonousTimestamps()
                            .withTimestampAssigner(new SerializableTimestampAssigner<Event>() {
                                @Override
                                public long extractTimestamp(Event element, long recordTimestamp) {
                                    return element.timestamp;
                                }
                            })
            );

    // 完成对账
    orderStream
            .keyBy(r -> r.orderId)
            .connect(weChatStream.keyBy(r -> r.orderId))
            .process(new MatchFunction())
            .print();

    env.execute();
}

public static class MatchFunction extends CoProcessFunction<Event,Event,String>{

    private ValueState<Event> orderState;
    private ValueState<Event> weChatState;

    @Override
    public void open(Configuration parameters) throws Exception {
        super.open(parameters);
        orderState = getRuntimeContext().getState(
                new ValueStateDescriptor<Event>("order", Types.POJO(Event.class))
        );

        weChatState = getRuntimeContext().getState(
                new ValueStateDescriptor<Event>("weChat",Types.POJO(Event.class))
        );
    }

    @Override
    public void processElement1(Event value, Context ctx, Collector<String> out) throws Exception {
        if(weChatState.value() == null){
            // 下订单order事件先到达
            orderState.update(value);
            ctx.timerService().registerEventTimeTimer(value.timestamp + 5000L);
        }else{
            out.collect("ID为【" + value.orderId + "】的订单对账成功,微信事件先到达");
            weChatState.clear();
        }
    }

    @Override
    public void processElement2(Event value, Context ctx, Collector<String> out) throws Exception {
        if(orderState.value() == null){
            // 微信支付事件先到达
            weChatState.update(value);
            ctx.timerService().registerEventTimeTimer(value.timestamp + 5000L);
        }else {
            out.collect("ID为【" + value.orderId + "】的订单对账成功,订单事件先到达");
            orderState.clear();
        }
    }

    @Override
    public void onTimer(long timestamp, OnTimerContext ctx, Collector<String> out) throws Exception {
        super.onTimer(timestamp, ctx, out);
        if(orderState.value() != null){
            // 订单事件现先到达,但5s内微信支付事件没达到
            out.collect("id为【" + orderState.value().orderId + "】的订单对账失败,微信事件5s内未到达");
            orderState.clear();
        }
        if(weChatState.value() != null){
            out.collect("id为【" + weChatState.value().orderId + "】的订单对账失败,订单事件5s内未到达");
        }
    }
}

public static class Event{
    public String orderId;
    public String eventType;
    public Long timestamp;

    public Event() {
    }

    public Event(String orderId, String eventType, Long timestamp) {
        this.orderId = orderId;
        this.eventType = eventType;
        this.timestamp = timestamp;
    }

    public static Event of(String orderId,String eventType,Long timestamp){
        return new Event(orderId,eventType,timestamp);
    }

    @Override
    public String toString() {
        return "Event{" +
                "orderId='" + orderId + '\'' +
                ", eventType='" + eventType + '\'' +
                ", timestamp=" + new Timestamp(timestamp) +
                '}';
    }
}

如果修改下述代码,理论上是对账不成功的,但输出结果为对账成功。

Event.of("order-1", "weChat", 30000L)

因为对于离线数据集,只会在数据集的开始插入一个负无穷大,数据集的末尾插入一个正无穷大,当30s微信事件到达后,由于水位线分流原则,此时的水位线仍然为负无穷大,在订单事件和微信事件之间并不存在触发6s水位线的定时事件。

只需要切换数据源即可,此段程序的逻辑并无问题,只不过其处理的是有限的数据集。

(2)使用高自定义数据源(流处理)

将数据源更改为:

SingleOutputStreamOperator<Event> orderStream = env
        .addSource(new SourceFunction<Event>() {
            @Override
            public void run(SourceContext<Event> ctx) throws Exception {
                ctx.collectWithTimestamp(Event.of("order-1","order",1000L),1000L);
                ctx.emitWatermark(new Watermark(999L));
                ctx.collectWithTimestamp(Event.of("order-2","order",3000L),3000L);
                ctx.emitWatermark(new Watermark(8001L));
            }

            @Override
            public void cancel() {

            }
        });

SingleOutputStreamOperator<Event> weChatStream = env
        .addSource(new SourceFunction<Event>() {
            @Override
            public void run(SourceContext<Event> ctx) throws Exception {
                ctx.collectWithTimestamp(Event.of("order-1","weChat",4000L),4000L);
                ctx.emitWatermark(new Watermark(3999L));
                ctx.emitWatermark(new Watermark(8001L));
                ctx.collectWithTimestamp(Event.of("order-2","weChat",9000L),9000L);
            }

            @Override
            public void cancel() {

            }
        });

二 基于时间的双流 Join

数据流操作的另一个常见需求是对两条数据流中的事件进行联结(connect)或 Join。Flink DataStream API 中内置有两个可以根据时间条件对数据流进行 Join 的算子:基于间隔的 Join 和基于窗口的 Join。

如果 Flink 内置的 Join 算子无法表达所需的 Join 语义,那么可以通过 CoProcessFunction、BroadcastProcessFunction 或 KeyedBroadcastProcessFunction 实现自定义的 Join逻辑。

注意,要设计的 Join 算子需要具备高效的状态访问模式及有效的状态清理策略。

1 基于间隔的 Join

基于间隔的 Join 会对两条流中拥有相同键值以及彼此之间时间戳不超过某一指定间隔的事件进行 Join。

下图展示了两条流(A 和 B)上基于间隔的 Join,如果 B 中事件的时间戳相较于 A中事件的时间戳不早于 1 小时且不晚于 15 分钟,则会将两个事件 Join 起来。Join 间隔具有对称性,因此上面的条件也可以表示为 A 中事件的时间戳相较 B 中事件的时间戳不早于 15 分钟且不晚于 1 小时。

在这里插入图片描述

基于间隔的 Join 目前只支持事件时间以及 INNER JOIN 语义(无法发出未匹配成功的事件)。

Join 成功的事件对会发送给 ProcessJoinFunction。下界和上界分别由负时间间隔和正时间间隔来定义,例如 between(Time.hour(-1), Time.minute(15))。在满足下界值小于上界值的前提下,你可以任意对它们赋值。例如,允许出现 B 中事件的时间戳相较 A 中事件的时间戳早 1~2 小时这样的条件。

基于间隔的 Join 需要同时对双流的记录进行缓冲。对第一个输入而言,所有时间戳大于当前水位线减去间隔上界的数据都会被缓冲起来;对第二个输入而言,所有时间戳大于当前水位线加上间隔下界的数据都会被缓冲起来。注意,两侧边界值都有可能为负。

上图中的 Join 需要存储数据流 A 中所有时间戳大于当前水位线减去 15 分钟的记录,以及数据流 B 中所有时间戳大于当前水位线减去 1 小时的记录。不难想象,如果两条流的事件时间不同步,那么 Join 所需的存储就会显著增加,因为水位线总是由“较慢”的那条流来决定。

下面的例子定义了一个基于间隔的 Join,解决恶意刷单行为,具体做法:将用户下订单事件与之前10分钟的pv事件进行join。

(1)正向join

public static void main(String[] args) throws Exception{
    StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
    env.setParallelism(1);

    SingleOutputStreamOperator<Example3.Event> orderStream = env
            .fromElements(
                    Example3.Event.of("user-1", "order", 20 * 60 * 1000L)
            )
            .assignTimestampsAndWatermarks(
                    WatermarkStrategy.<Example3.Event>forMonotonousTimestamps()
                            .withTimestampAssigner(
                                    new SerializableTimestampAssigner<Example3.Event>() {
                                        @Override
                                        public long extractTimestamp(Example3.Event element, long recordTimestamp) {
                                            return element.timestamp;
                                        }
                                    }
                            )
            );

    SingleOutputStreamOperator<Example3.Event> pvStream = env
            .fromElements(
                    Example3.Event.of("user-1", "pv", 5 * 60 * 1000L),
                    Example3.Event.of("user-1", "pv", 10 * 60 * 1000L),
                    Example3.Event.of("user-1", "pv", 12 * 60 * 1000L),
                    Example3.Event.of("user-1", "pv", 21 * 60 * 1000L)
            )
            .assignTimestampsAndWatermarks(
                    WatermarkStrategy.<Example3.Event>forMonotonousTimestamps()
                            .withTimestampAssigner(
                                    new SerializableTimestampAssigner<Example3.Event>() {
                                        @Override
                                        public long extractTimestamp(Example3.Event element, long recordTimestamp) {
                                            return element.timestamp;
                                        }
                                    }
                            )
            );

    orderStream
            .keyBy(r -> r.orderId)
            .intervalJoin(pvStream.keyBy(r -> r.orderId))
            .between(Time.minutes(-10),Time.minutes(0))
            .process(new ProcessJoinFunction<Example3.Event, Example3.Event, String>() {
                @Override
                public void processElement(Example3.Event left, Example3.Event right, Context ctx, Collector<String> out) throws Exception {
                    out.collect(left + " => " + right);
                }
            })
            .print("orderStream JOIN pvStream:");

    env.execute();
}

(2)反向join

将两条流调换顺序join

pvStream
        .keyBy(r -> r.orderId)
        .intervalJoin(orderStream.keyBy(r -> r.orderId))
        .between(Time.minutes(0),Time.minutes(10))
        .process(new ProcessJoinFunction<Example3.Event, Example3.Event, String>() {
            @Override
            public void processElement(Example3.Event left, Example3.Event right, Context ctx, Collector<String> out) throws Exception {
                out.collect(right + " => " + left);
            }
        })
        .print("pvStream JOIN orderStream:");

2 基于窗口的 Join

顾名思义,基于窗口的 Join 需要用到 Flink 中的窗口机制。其原理是将两条输入流中的元素分配到公共窗口中并在窗口完成时进行 Join(或 Cogroup)。

下图展示了 DataStream API 中基于窗口的 Join 是如何工作的,此种方式限制了两条流的开窗大小必须相同。

在这里插入图片描述

下面的例子展示了如何定义基于窗口的 Join,实际上是一个基于窗口的笛卡尔积。

public static void main(String[] args) throws Exception{
    StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
    env.setParallelism(1);

    SingleOutputStreamOperator<Tuple2<String, Integer>> stream1 = env
            .fromElements(Tuple2.of("a", 1), Tuple2.of("b", 1))
            .assignTimestampsAndWatermarks(
                    WatermarkStrategy.<Tuple2<String, Integer>>forMonotonousTimestamps()
                            .withTimestampAssigner(
                                    new SerializableTimestampAssigner<Tuple2<String, Integer>>() {
                                        @Override
                                        public long extractTimestamp(Tuple2<String, Integer> element, long recordTimestamp) {
                                            return element.f1;
                                        }
                                    }
                            )
            );

    SingleOutputStreamOperator<Tuple2<String, Integer>> stream2 = env
            .fromElements(Tuple2.of("a", 2), Tuple2.of("a", 3), Tuple2.of("b", 2), Tuple2.of("b", 3))
            .assignTimestampsAndWatermarks(
                    WatermarkStrategy.<Tuple2<String, Integer>>forMonotonousTimestamps()
                            .withTimestampAssigner(
                                    new SerializableTimestampAssigner<Tuple2<String, Integer>>() {
                                        @Override
                                        public long extractTimestamp(Tuple2<String, Integer> element, long recordTimestamp) {
                                            return element.f1;
                                        }
                                    }
                            )
            );

    stream1
            .join(stream2)
            // 指定第一条的key
            .where(r -> r.f0)
            // 指定第二条的key
            .equalTo(r -> r.f0)
            .window(TumblingEventTimeWindows.of(Time.seconds(5)))
            .apply(new JoinFunction<Tuple2<String, Integer>, Tuple2<String, Integer>, String>() {
                @Override
                public String join(Tuple2<String, Integer> first, Tuple2<String, Integer> second) throws Exception {
                    return first + " => " + second;
                }
            })
            .print();

    env.execute();
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【自然语言处理(NLP)】基于预训练模型的机器阅读理解

【自然语言处理&#xff08;NLP&#xff09;】基于预训练模型的机器阅读理解 作者简介&#xff1a;在校大学生一枚&#xff0c;华为云享专家&#xff0c;阿里云专家博主&#xff0c;腾云先锋&#xff08;TDP&#xff09;成员&#xff0c;云曦智划项目总负责人&#xff0c;全国高…

【Pandas数据处理100例】(九十二):Pandas中的transform()函数使用方法

前言 大家好,我是阿光。 本专栏整理了《Pandas数据分析处理》,内包含了各种常见的数据处理,以及Pandas内置函数的使用方法,帮助我们快速便捷的处理表格数据。 正在更新中~ ✨ 🚨 我的项目环境: 平台:Windows10语言环境:python3.7编译器:PyCharmPandas版本:1.3.5N…

Linux总结

目录学习阶段基本介绍概述Linux和UnixLinux和Windows的比较Linux目录结构远程登录Linux为什么需要远程登录LinuxXShell工具Xftp工具Linux命令关机/重启命令用户登录/注销运行级别Linux系统的7个运行级别运行级别原理帮助指令man获得帮助信息help指令执行历史命令用户管理添加用…

我的网站被攻击了,运维大佬给了我自动封禁ip的脚本。

我的网站被攻击了&#xff0c;发现友圈最近出现这种情况的还不少&#xff0c;真是神奇了&#xff0c;这事也能扎堆发生。 分享出来给大家&#xff0c;万一以后用得着呢~ 故事背景 我的一个小网站最近总是收到云监控报警&#xff0c;一个部署在4核8G单机上的小网站。 查了log…

微信开发者工具 / 反编译工具CrackMinApp 下载安装

微信开发者工具 / 反编译工具CrackMinApp 下载安装 文章目录微信开发者工具 / 反编译工具CrackMinApp 下载安装前言一、微信开发者工具下载安装二、反编译工具CrackMinApp安装三、导入反编译后的文件四、友情提示总结前言 微信开发者工具介绍&#xff1a;微信提供的微信小程序…

CVPR 2022 视频全景分割新 Benchmark:VIPSeg

关注公众号&#xff0c;发现CV技术之美今天向大家分享 CVPR 2022 论文『Large-scale Video Panoptic Segmentation in the Wild: A Benchmark』,介绍一个新的视频全景分割&#xff08;Video Panoptic Segmentation&#xff09;领域 Benchmark&#xff1a;VIPSeg。论文链接&…

GB/T 10707 橡胶燃烧性能

GB/T 10707&#xff1a;Rubber-Determination of the burning GB/T 10707&#xff1a;橡胶燃烧性能的测定 GB/T 10707橡胶燃烧性能的测定–适用范围&#xff1a; 本标准规定了在实验室环境下测定橡胶燃烧性能的两种方法&#xff1a;氧指数法和垂直燃烧法 本标准适用于在实验…

云原生丨MLOps与DevOps的区别

MLOps 是机器学习 (ML) 工程的很重要的一个部分&#xff0c;专注于简化和加速将 ML 模型交付到生产以及维护和监控它们的过程。 MLOps 涉及不同团队之间的协作&#xff0c;包括数据科学家、DevOps 工程师、IT 专家等。 MLOps 可以帮助组织创建和提高其 AI 和机器学习解决方案…

卧式钢筋切割机设计

目 录 1 绪论 1 1.1 国内外钢筋切割技术的发展状况 1 1.2 冷轧带肋钢筋的概述 2 1.2.1 钢筋的种类 2 1.2.2 冷轧带肋钢筋的表面形式 3 1.2.3 冷轧带肋钢筋基本性能 3 1.3 课题的提出和意义 4 2 对钢筋类金属材料弹塑性弯曲的分析 4 2.1 概述 5 2.2 弹塑性弯曲的变形过程 6 3 切…

再次安装torch踩过的坑

没有多余空间 我用conda 从新创建了一个项目环境&#xff0c;安装了一些基础的库。然后当我下载安装torch的时候&#xff0c;报错说安装的空间不足&#xff0c;我看了一下&#xff0c;torch确实比较大&#xff0c;一个多G&#xff0c;但是之前也没有出现过这个问题。 一开始以…

python中如何打印日志信息

日志打印方式 常见的Python日志打印方式为使用内置函数print()或者logging模块打印日志。 print()只能将日志打印至控制台&#xff0c;不推荐此方式logging模块默认将日志打印至控制台&#xff0c;也可以配置打印到指定日志文件&#xff0c;推荐使用此方式 logging模块 日志…

[附源码]JAVA毕业设计高速公路服务区管理系统(系统+LW)

[附源码]JAVA毕业设计高速公路服务区管理系统&#xff08;系统LW&#xff09; 目运行 环境项配置&#xff1a; Jdk1.8 Tomcat8.5 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项…

二分查找 binarySearch 适合初学分析的例子

递归代码&#xff1a; #include <cstdio> #include <algorithm> #define MAX 5 using namespace std;int binarySearch(int x,int a[],int left,int right);int main() {int a[MAX]{1,3,4,5,9};printf("find %d location is %d\n",4,binarySearch(4,a,0…

Sap中的RFC接口

文章目录1 Definition2 Call process3. Communication4 Communication module5 RFC version .6 RFC and Web service7 Remote object maintain8 Call RFC9 Summary1 Definition 2 Call process 3. Communication 4 Communication module 5 RFC version . 6 RFC and Web service…

Linux驱动: rtc子系统

1. 前言 限于作者能力水平&#xff0c;本文可能存在的谬误&#xff0c;因此而给读者带来的损失&#xff0c;作者不做任何承诺。 2. 背景 本文分析代码基于Linux 3.10内核&#xff0c;硬件平台为嵌入式ARM32平台. 3. rtc子系统 3.1 相关代码文件列表 drivers/rtc/class.c …

代码文档

为您的团队和您未来的自己代码文档。 Intuition 代码告诉你_怎么_做&#xff0c;注释告诉你_为什么_。——杰夫阿特伍德 可以通过代码文档来进一步组织代码&#xff0c;让其他人&#xff08;以及未来的自己&#xff09;更容易轻松地导航和扩展它。在完成编写代码库的那一刻就最…

HTML+CSS大作业:众志成城 抗击疫情 抗击疫情网页制作作业 疫情防控网页设计

&#x1f389;精彩专栏推荐 &#x1f4ad;文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;毕设项目精品实战案例 (10…

某金融机构身份国产化LDAP创新实践——国产自主可控 LDAP目录服务建设经验分享

一、项目背景 自2019年以来&#xff0c;金融行业信创发展进程加快。从2020年一期试点的47家到2021年二期试点198家&#xff0c;2022年三期试点启动的同时也进入全面推广阶段&#xff0c;试点范围由大型银行、证券、保险等机构向中小型金融机构渗透&#xff0c;涉及全行业5000余…

怎么架设魔兽世界服务器?

怎么架设魔兽世界服务器&#xff1f; 准备工具&#xff1a; 1、装有windows98/2000/xp/2003系统、内存至少256M的电脑一台 2、魔兽服务器端一个 3、mysql4.0.2&#xff08;不要用最新的5.0&#xff0c;有问题&#xff09; mysql-control-center0.9.4 MyODBC-standard-3.5…

Kamiya丨Kamiya艾美捷人β2-微球蛋白ELISA说明书

Kamiya艾美捷人β2-微球蛋白ELISA预期用途&#xff1a; 人β2-微球蛋白ELISA是一种高度敏感的双位点酶联免疫测定&#xff08;ELISA&#xff09;人类生物样品中β2-微球蛋白的定量测定。仅供研究使用。 β2-微球蛋白&#xff08;B2M&#xff09;是一种11 kDA蛋白。它形成MHC I…