【Flink】使用水位线实现热门商品排行以及Flink如何处理迟到元素

news2025/2/25 3:08:09

文章目录

  • 一 WaterMark
    • 1 水位线特点总结
    • 2 实时热门商品【重点】
      • (1)数据集
      • (2)实现思路
        • a 分流 - 开窗 - 聚合
          • 分流:
          • 开窗:
          • 聚合:
        • b 再分流 -- 统计
          • 再分流:
          • 统计:
      • (3)代码编写
  • 二 处理迟到元素
    • 1 什么是迟到元素
      • (1)代码编写
      • (2)测试
    • 2 处理策略
      • (1)抛弃迟到元素
      • (2)重定向迟到元素
        • a 将迟到数据发送到侧输出流中(不开窗口)
        • b 将迟到数据发送到侧输出流中(开窗口)

一 WaterMark

1 水位线特点总结

  • Flink 认为时间戳小于水位线的事件都已到达。

  • 水位线是一种逻辑时钟。

  • 水位线由程序员编程插入到数据流中。

  • 水位线是一种特殊的事件。

  • 在事件时间的世界里,水位线就是时间。

  • 水位线 = 观察到的最大时间戳 - 最大延迟时间 - 1 毫秒。

  • 水位线超过窗口结束时间,窗口闭合,默认情况下,迟到元素被抛弃。

  • Flink 会在流的最开始插入一个时间戳为负无穷大的水位线。

  • Flink 会在流的最末尾插入一个时间戳为正无穷大的水位线。

  • Watermark 必须单调递增,以确保任务的事件时间时钟在向前推进,而不是在后退,(Watermark 就是当前数据流的逻辑时钟)。

    水位线 = 观察到的最大时间戳 - 最大延迟时间 - 1 毫秒,显然单调递增。

  • Watermark 与数据的时间戳相关。

  • 在 Flink 中,Watermark 由应用程序开发人员生成,这通常需要对相应的领域有一定的了解。

  • 如果 Watermark 设置的延迟太久,收到结果的速度可能就会很慢,解决办法是在水位线到达之前输出一个近似结果。

  • 而如果 Watermark 到达得太早,则可能收到错误结果,不过 Flink 处理迟到数据的机制可以解决这个问题。

2 实时热门商品【重点】

求每个窗口中最热门的商品。

(1)数据集

数据集基本结构如下:用户id,商品id,所属品类id,数据类型(pv或buy),秒级时间戳。

在这里插入图片描述

(2)实现思路

a 分流 - 开窗 - 聚合

统计的结果是每一个窗口里面的每一个商品的pv次数。

  • 分流后变为键控流,键控流比DataStream多了一个泛型key,所以KeyedProcessFunction有三个泛型【key、输入、输出】。
  • 开窗后变为了WindowedStream,WindowedStream比键控流多个一个泛型window,所以ProcessWindowFunction中有四个泛型【输入、输出、key、窗口】。
  • 聚合后又变成了DateStream。

在这里插入图片描述

分流:

在这里插入图片描述

开窗:

在这里插入图片描述

聚合:

在这里插入图片描述

b 再分流 – 统计

再分流:

想统计每个窗口中的实时热门商品,再使用窗口结束时间(开始时间也可以)进行分流,效果是每一个时间窗口中不同商品id的浏览次数。

在这里插入图片描述

统计:

将ItemViewCount存放到列表状态变量中,使用ArrayList进行排序,最终输出。

在这里插入图片描述

(3)代码编写

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

        env
                .readTextFile("E:\\develop\\MyWork\\flink2022tutorial\\src\\main\\resources\\UserBehavior.csv")
                // 数据流ETL
                .map(new MapFunction<String, UserBehavior>() {
                    @Override
                    public UserBehavior map(String value) throws Exception {
                        String[] arr = value.split(",");
                        return new UserBehavior(
                                arr[0],arr[1],arr[2],arr[3],
                                Long.parseLong(arr[4]) * 1000L
                        );
                    }
                })
                // 获取pv数据
                .filter(r -> r.behavior.equals("pv"))
                // 分配水位线
                .assignTimestampsAndWatermarks(
                        // 此数据集针对时间戳已经做了ETL,所以设置延迟时间为0
                        WatermarkStrategy.<UserBehavior>forBoundedOutOfOrderness(Duration.ofSeconds(0))
                        .withTimestampAssigner(new SerializableTimestampAssigner<UserBehavior>() {
                            @Override
                            public long extractTimestamp(UserBehavior element, long recordTimestamp) {
                                return element.timestamp;
                            }
                        })
                )
                // 使用商品id对数据进行分流【分流】
                .keyBy(r -> r.itemId)
                // 每隔5分钟想查看过去一小时最热门的商品(滑动窗口)【开窗】
                .window(SlidingEventTimeWindows.of(Time.hours(1),Time.minutes(5)))
                // 增量聚合与全窗口聚合结合使用【聚合】
                .aggregate(new CountAgg(),new WindowResult())
                // 按照ItemViewCount的windowEnd进行分流,获取同一个窗口的统计信息【分流】
                .keyBy(r -> r.windowEnd)
                // 取前3名
                .process(new TopN(3))
                .print();


        env.execute();
    }

    // 将每一个到来的ItemViewCount统计信息存储到列表状态中,设置定时器,进行排序
    public static class TopN extends KeyedProcessFunction<Long,ItemViewCount,String>{
        private ListState<ItemViewCount> listState;
        private Integer n;

        public TopN(Integer n) {
            this.n = n;
        }

        @Override
        public void open(Configuration parameters) throws Exception {
            super.open(parameters);
            listState = getRuntimeContext().getListState(
                    new ListStateDescriptor<ItemViewCount>("list-state", Types.POJO(ItemViewCount.class))
            );
        }

        @Override
        public void processElement(ItemViewCount value, Context ctx, Collector<String> out) throws Exception {
            // 每来一条数据直接存储到列表状态中
            listState.add(value);
            // 一条流上元素的windowEnd相同,定时器只会注册一次
            ctx.timerService().registerEventTimeTimer(value.windowEnd + 1L);
        }

        @Override
        public void onTimer(long timestamp, OnTimerContext ctx, Collector<String> out) throws Exception {
            super.onTimer(timestamp, ctx, out);
            // 排序逻辑
            // 列表状态变量在底层是序列化过的,所以无法针对其直接排序
            ArrayList<ItemViewCount> itemViewCountArrayList = new ArrayList<>();
            for(ItemViewCount ivc : listState.get()) itemViewCountArrayList.add(ivc);
            // 手动对列表状态变量进行GC
            listState.clear();

            itemViewCountArrayList.sort(new Comparator<ItemViewCount>() {
                @Override
                public int compare(ItemViewCount o1, ItemViewCount o2) {
                    // 降序排列
                    return o2.count.intValue() - o1.count.intValue();
                }
            });

            // 取出窗口结束时间
            StringBuilder result = new StringBuilder();
            result
                    .append("======================================\n")
                    .append("窗口结束时间:" + new Timestamp(timestamp - 1L))
                    .append("\n");

            // 取出前几名,对数据ETL
            for(int i = 0; i < n; i++){
                ItemViewCount curr = itemViewCountArrayList.get(i);
                result
                        .append("第" + (i + 1) + "名的商品id是:【" + curr.itemId)
                        .append("】,浏览次数是:【" + curr.count +"】")
                        .append("\n");
            }
            result
                    .append("======================================\n");

            out.collect(result.toString());
        }
    }

    public static class WindowResult extends ProcessWindowFunction<Long,ItemViewCount,String, TimeWindow>{

        @Override
        public void process(String s, Context context, Iterable<Long> elements, Collector<ItemViewCount> out) throws Exception {
            out.collect(new ItemViewCount(s,elements.iterator().next(),context.window().getStart(),context.window().getEnd()));
        }
    }

    public static class CountAgg implements AggregateFunction<UserBehavior,Long,Long> {

        @Override
        public Long createAccumulator() {
            return 0L;
        }

        @Override
        public Long add(UserBehavior value, Long accumulator) {
            return accumulator + 1L;
        }

        @Override
        public Long getResult(Long accumulator) {
            return accumulator;
        }

        @Override
        public Long merge(Long a, Long b) {
            return null;
        }
    }

    //聚合结果,每一个商品在每一个窗口中的浏览量
    public static class ItemViewCount{
        public String itemId;
        public Long count;
        public Long windowStart;
        public Long windowEnd;

        public ItemViewCount() {
        }

        public ItemViewCount(String itemId, Long count, Long windowStart, Long windowEnd) {
            this.itemId = itemId;
            this.count = count;
            this.windowStart = windowStart;
            this.windowEnd = windowEnd;
        }

        @Override
        public String toString() {
            return "ItemViewCount{" +
                    "itemId='" + itemId + '\'' +
                    ", count=" + count +
                    ", windowStart=" + new Timestamp(windowStart) +
                    ", windowEnd=" + new Timestamp(windowEnd) +
                    '}';
        }
    }

    public static class UserBehavior{
        public String userId;
        public String itemId;
        public String categoryId;
        public String behavior;
        public Long timestamp;

        public UserBehavior() {
        }

        public UserBehavior(String userId, String itemId, String categoryId, String behavior, Long timestamp) {
            this.userId = userId;
            this.itemId = itemId;
            this.categoryId = categoryId;
            this.behavior = behavior;
            this.timestamp = timestamp;
        }

        @Override
        public String toString() {
            return "UserBehavior{" +
                    "userId='" + userId + '\'' +
                    ", itemId='" + itemId + '\'' +
                    ", categoryId='" + categoryId + '\'' +
                    ", behavior='" + behavior + '\'' +
                    ", timestamp=" + new Timestamp(timestamp) +
                    '}';
        }
    }
}

二 处理迟到元素

1 什么是迟到元素

(1)代码编写

迟到元素:到来的数据包含的时间戳小于当前水位线。

具体实例见以下代码:

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

    env
            .socketTextStream("localhost",9999)
            .map(new MapFunction<String, Tuple2<String,Long>>() {
                @Override
                public Tuple2<String, Long> map(String value) throws Exception {
                    String[] arr = value.split(" ");
                    return Tuple2.of(arr[0],Long.parseLong(arr[1]) * 1000L);
                }
            })
            .assignTimestampsAndWatermarks(
                    // 使用延迟时间为0可以使用另一种写法(forBoundedOutOfOrderness(Duration.ofSeconds(0)))
                    WatermarkStrategy.<Tuple2<String, Long>>forMonotonousTimestamps()
                    .withTimestampAssigner(
                            new SerializableTimestampAssigner<Tuple2<String, Long>>() {
                                @Override
                                public long extractTimestamp(Tuple2<String, Long> element, long recordTimestamp) {
                                    return element.f1;
                                }
                            }
                    )
            )
            // 使用ProcessFunction实现:流处理,且处理的是没有经过keyBy的流,所以只能使用processElement
            // 不能使用状态变量和定时器
            // 其中定时器只能在运行时才会报错,即使在编译期写出来编译器也不会报错
            .process(new ProcessFunction<Tuple2<String, Long>, String>() {
                @Override
                public void processElement(Tuple2<String, Long> value, Context ctx, Collector<String> out) throws Exception {
                    if(value.f1 < ctx.timerService().currentWatermark()){
                        out.collect("【" + value + "】元素迟到了");
                    }else{
                        out.collect("【" + value + "】元素没有迟到");
                    }
                }
            })
            .print();

    env.execute();
}

(2)测试

依次输入

水位线 = 观察到的最大时间戳 - 最大延迟时间 - 1 毫秒,显然单调递增。
a 1 【水位线:1 - 0 -1ms = 9999ms】  元素携带时间戳为1s,1s > 负无穷大(当前水位线),没有迟到
a 2 【水位线:2 - 0 -1ms = 19999ms】 元素携带时间戳为2s, 2s > 9999ms(当前水位线),没有迟到
a 1 【水位线:2 - 0 -1ms = 19999ms】 元素携带时间戳为1s, 1s < 19999ms(当前水位线),迟到

结果如下图:

在这里插入图片描述

由上可以看出,机器时间、处理时间是不存在迟到元素的,只有在事件时间中,才会存在迟到元素。对于事件时间来说,水位线就是它的逻辑时钟。

水位线可以用来平衡计算的完整性和延迟两方面。除非选择一种非常保守的水位线策略 (最大延时设置的非常大,以至于包含了所有的元素,但结果是非常大的延迟),否则总需要处理迟到的元素。

迟到的元素是指当这个元素来到时,这个元素所对应的窗口已经计算完毕了 (也就是说水位线已经没过窗口结束时间了)。这说明迟到这个特性只针对事件时间。

2 处理策略

DataStream API 提供了三种策略来处理迟到元素

  • 直接抛弃迟到的元素。
  • 将迟到的元素发送到另一条流中去。
  • 可以更新窗口已经计算完的结果,并发出计算结果。

(1)抛弃迟到元素

抛弃迟到的元素是事件时间窗口操作符的默认行为。也就是说一个迟到的元素不会创建一个新的窗口。

process function 可以通过比较迟到元素的时间戳和当前水位线的大小来很轻易的过滤掉迟到元素。

(2)重定向迟到元素

迟到的元素也可以使用旁路输出 (side output) 特性(侧输出流)被重定向到另外的一(或n)条流中去。迟到元素所组成的旁路输出流可以继续处理或者 sink 到持久化设施中去。

a 将迟到数据发送到侧输出流中(不开窗口)

// 定义侧输出流的名字(标签)
private static OutputTag<String> lateElement = new OutputTag<String>("late-element"){};

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

    SingleOutputStreamOperator<String> result = env
            // 自定义数据源
            .addSource(new SourceFunction<Tuple2<String, Long>>() {
                @Override
                public void run(SourceContext<Tuple2<String, Long>> ctx) throws Exception {
                    // 指定时间戳并发送数据,如果两个时间戳不同,以指定的时间戳为准
                    ctx.collectWithTimestamp(Tuple2.of("hello word", 1000L), 1000L);
                    // 发送水位线
                    ctx.emitWatermark(new Watermark(999L));
                    ctx.collectWithTimestamp(Tuple2.of("hello flink", 2000L), 2000L);
                    ctx.emitWatermark(new Watermark(1999L));
                    ctx.collectWithTimestamp(Tuple2.of("hello late", 1000L), 1000L);
                }

                @Override
                public void cancel() {

                }
            })
            .process(new ProcessFunction<Tuple2<String, Long>, String>() {
                @Override
                public void processElement(Tuple2<String, Long> value, Context ctx, Collector<String> out) throws Exception {
                    if (value.f1 < ctx.timerService().currentWatermark()) {
                        // 发送到测输出流
                        ctx.output(lateElement, "迟到元素【" + value + "】已发送到侧输出流:");
                    } else {
                        out.collect("元素【" + value + "】正常到达!");
                    }
                }
            });

    result.print("正常到达的元素:");

    result.getSideOutput(lateElement).print("侧输出流中的迟到元素:");

    env.execute();
}

b 将迟到数据发送到侧输出流中(开窗口)

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

    SingleOutputStreamOperator<String> result = env
            .addSource(new SourceFunction<String>() {
                @Override
                public void run(SourceContext<String> ctx) throws Exception {
                    ctx.collectWithTimestamp("a", 1000L);
                    ctx.emitWatermark(new Watermark(999L));
                    ctx.collectWithTimestamp("a", 2000L);
                    ctx.emitWatermark(new Watermark(999L));
                    ctx.collectWithTimestamp("a", 4000L);
                    // 关闭 0-5 秒的窗口:必须销毁窗口,迟到数据才会被发送到侧输出流
                    ctx.emitWatermark(new Watermark(4999L));
                    ctx.collectWithTimestamp("a late", 3000L);
                }

                @Override
                public void cancel() {

                }
            })
            .keyBy(r -> 1)
            // 开窗口
            .window(TumblingEventTimeWindows.of(Time.seconds(5)))
            // 将迟到元素输出到测输出流
            .sideOutputLateData(new OutputTag<String>("late") {
                                }
            )
            .process(new ProcessWindowFunction<String, String, Integer, TimeWindow>() {
                @Override
                public void process(Integer integer, Context context, Iterable<String> elements, Collector<String> out) throws Exception {
                    out.collect("窗口中共有:" + elements.spliterator().getExactSizeIfKnown() + "个元素");
                }
            });

    // 获取正常到达数据
    result.print("正常元素:");

    // 获取迟到元素,根据字符串id识别侧输出标签,可以保证其实单例
    result.getSideOutput(new OutputTag<String>("late"){}).print("迟到元素:");

    env.execute();
}

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

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

相关文章

【Hack The Box】Linux练习-- Seventeen

HTB 学习笔记 【Hack The Box】Linux练习-- Seventeen &#x1f525;系列专栏&#xff1a;Hack The Box &#x1f389;欢迎关注&#x1f50e;点赞&#x1f44d;收藏⭐️留言&#x1f4dd; &#x1f4c6;首发时间&#xff1a;&#x1f334;2022年9月7日&#x1f334; &#x1f…

SpringBoot结合Liquibase实现数据库变更管理

《从零打造项目》系列文章 工具 比MyBatis Generator更强大的代码生成器 ORM框架选型 SpringBoot项目基础设施搭建 SpringBoot集成Mybatis项目实操 SpringBoot集成MybatisPlus项目实操 SpringBoot集成Spring Data JPA项目实操 数据库变更管理 数据库变更管理&#xff1a;Li…

[附源码]Python计算机毕业设计Django的党务管理系统

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

行为型模式-命令模式

package per.mjn.pattern.command;import java.util.HashMap; import java.util.Map;// 订单类 public class Order {// 餐桌号码private int diningTable;// 点的餐品和份数private Map<String, Integer> foodDir new HashMap<>();public int getDiningTable() {…

[附源码]计算机毕业设计springboot高校车辆管理系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

家居建材企业竞争白热化,如何通过供应商协同系统转型升级,提高核心竞争力

伴随房地产高景气时代逐渐退去&#xff0c;新房销售红利期或已接近尾声&#xff0c;家居建材需求正迈入平稳发展新阶段&#xff0c;企业之间竞争更加白热化。在面对数字化时代的快速发展&#xff0c;很多家居建材企业已达成这样的共识&#xff1a;数字化是企业未来发展的必由之…

人工智能岗位可以考什么证书?考试难不难?

最近几年人工智能在市场上的热度越来越大&#xff0c;很多企业都会利用这个项目来发展自己新渠道&#xff0c;那么想进入这一行的人需要怎么提升自己的技能呢&#xff1f;那就是考取人工智能相关的证书&#xff0c;目前阿里云人工智能是国内市场最热门的认证分为两个等级&#…

(2)点云库PCL学习——剔除点云值

1、主要参考 (1) 点云离群点剔除 — open3d python_Coding的叶子的博客-CSDN博客_离群点去除 (2) open3d之点云异常值去除&#xff08;笔记5&#xff09;_Satellite_H的博客-CSDN博客 2、剔除的方法 2.1无效值剔除 详见我的上一篇blob &#xff08;1&#xff09;点云库…

C语言—指针进阶(详解篇)

目录 1.字符指针 1.1字符指针定义 1.2 字符指针用法 2.指针数组 2.1 指针数组定义及使用 3.数组指针 3.1 数组指针定义 3.2 &数组名和数组名 3.3 数组指针的基本用法 4. 数组参数、指针参数 5. 函数指针 5.1 函数指针定义既基本使用 5.2 有趣的代码 6. 函…

BMS 信息资源e分享平台

今天分享的是一款关于医学的企业内部实战系统。当时某药企内部面临现状是医学人力资源有限、信息量需求大、信息资源传递途径受限&#xff0c;覆盖范围小。为解决目前面临的问题&#xff0c;提高信息资源的统一性、准确性和安全性&#xff0c;优化资源获取流程&#xff0c;提高…

[vite.js]按需加载自动注册组件

最近(后知后觉)发现各大ui组件库的按需引入&#xff0c;在使用vite构建项目的时候&#xff0c;都推荐使用unplugin-vue-components插件自动解析ui组件来自动注册&#xff1b;就是说不需要再import { ... } from ..了&#xff0c;该插件会自动帮助解析并注册成组件。其实之前用n…

数字文档管理不能落后的 5 个原因

数字文档管理不能落后的 5 个原因 信息管理对于几乎每个行业的组织都至关重要。从财富 500 强企业到医疗机构&#xff0c;您处理文件的地点和方式都很重要。如果您坚持基于纸张的流程&#xff0c;那么您可能会落后于其他企业而且冒着很大的风险。 在许多组织中&#xff0c;数字…

Linux的Jdk安装教程

liunx下Jdk安装教程 1.创建jdk的安装目录&#xff08;/usr/local/src&#xff09; 确保安装的目录是空的&#xff0c;如果不是空的&#xff0c;删除一下&#xff0c;或者放在自己其他的目录也可以 mkdir -p /usr/local/src/jdk这里可能会出现之前安装过jdk&#xff0c;可以用下…

用HTML+CSS做一个漂亮简单的轻量级图片相册博客网站(web前端期末大作业)

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

web表单(详解)

目录 1. 表单的概述 1.1 表单组成 2. 表单标记 2.1 input标记 2.2 select标记 2.3 textarea标记 3.HTML5新增标记 3.1 datalist标记 3.2 date 输入类型 3.3 color输入类型 3.4 button标记 3.5 details标记和summary标记 3.6 progress标记 3.7 meter标记 4 综合…

【pen200-lab】10.11.1.5

pen200-lab 学习笔记 【pen200-lab】10.11.1.5 &#x1f525;系列专栏&#xff1a;pen200-lab &#x1f389;欢迎关注&#x1f50e;点赞&#x1f44d;收藏⭐️留言&#x1f4dd; &#x1f4c6;首发时间&#xff1a;&#x1f334;2022年11月29日&#x1f334; &#x1f36d;作者…

阿里资深架构师谈 Java 进阶攻略:7 大技能 +12 份进阶笔记 + 面试 150 题

以下都是阿里大牛推荐的主流技术&#xff0c;当你全部掌握上述的这些技术那么你就已经是 P8 级别&#xff0c;而且你也已经形成了自己的体系&#xff0c;当更加新潮的技术出来时那么你自己稍微花点时间就能吃透&#xff0c;毕竟那时候你已经不是以前的那个你了&#xff0c; 懂底…

竞赛——【蓝桥杯】2022年12月第十四届蓝桥杯模拟赛第二期C/C++

1、最小的2022 问题描述 请找到一个大于 2022 的最小数&#xff0c;这个数转换成二进制之后&#xff0c;最低的 6 个二进制为全为 0 。 请将这个数的十进制形式作为答案提交。 答案提交 这是一道结果填空的题&#xff0c;你只需要算出结果后提交即可。本题的结果为一个整数…

分享 2022 年最受欢迎的黑科技工具(二)

Hello, everybody &#xff0c;2022 年最受欢迎的黑科技工具&#xff08;二&#xff09;&#xff0c;收藏一波吧&#xff0c;您的在看、转发、点赞就是对tuonioooo最大的支持&#xff01; 1.Sampler 项目地址&#xff1a;https://github.com/sqshq/sampler 官网地址&#xf…

Fmoc-PEG4-NHS酯,1314378-14-7 含有Fmoc保护胺和NHS酯

●英文&#xff1a;Fmoc-PEG4-NHS酯 ●外观以及性质&#xff1a;粘性液体或固体粉末&#xff0c;一般取决于分子量&#xff0c;是一种含有Fmoc保护胺和NHS酯的PEG连接剂。亲水性PEG间隔物增加了在水介质中的溶解度。Fmoc基团可在碱性条件下脱保护以获得游离胺&#xff0c;其可…