当前位置: 首页 > news >正文

Flink 实时数仓(七)【DWS 层搭建(一)流量域汇总表创建】

前言

        今天开始 DWS 层的搭建,不知不觉又是周一,都忘了昨天是周末,近两年对我来说,周六日晚上八九点能打一小会篮球就算一周的休息了。不得不说自己真的是天生打工体质,每天不管多累,晚上十二点睡,第二天六点多七点准时自然醒,依然精神焕发,中午都不带困的;那既然老天给我这个特质让我像牛一样可以不知疲倦的工作,那我也希望是让我在热爱的领域发光发热;那既然这样,总得先让我找到个满意的工作吧哈哈哈 ...

1、DWS 层搭建

设计要点:

  • DWS层的设计参考指标体系(需求驱动);(前面的 DIM 和 DWD 的设计都是参考建模理论,是业务驱动)
  • DWS层表名的命名规范为dws_数据域_统计粒度_业务过程_统计周期(window)

        离线数仓中的 DWS 层的统计周期我们当时做的是 1/7/30 ,那实时数仓的统计周期当然不能这么大;离线数仓中每一天就相当于一个窗口,而在实时数仓当中,窗口都是秒级别的,我们这里开窗的大小选择 10 s,因为我们的可视化平台只能 10s 刷新一次,开得太小没有意义;(生产环境中可以更小比如 1s ,甚至可以不开窗。开窗还是不开窗是性能和时效性的取舍)

1.1、流量域来源关键词粒度页面浏览各窗口汇总表

主要任务

        从 Kafka 页面浏览明细(dwd_traffic_page_log)主题读取数据,过滤搜索行为,使用自定义 UDTF(一进多出)函数对搜索内容分词。统计各窗口各关键词出现频次,写入 ClickHouse。

1.1.1、思路分析

  • 在 DWD 层,我们对日志根据日志类型进行了分流,写入到了 5 个不同的主题当中
  • 现在我们需要统计搜索内容中的关键词,所以需要消费页面浏览日志
  • 使用分词器将搜索内容分为多个关键词
  • 划分窗口,词频统计后存储进 clickhouse

思考:既然用到分词,为啥不直接用 ES 存呢?

答:确实是要分词,但是我们这里是要做词频统计,ES 是对关键词做索引,相当于用 key(关键词)去获得 value(文档),而我们这里是要对 key 进行统计,所以不合适;

1.1.2、代码实现

1)IK 分词器工具类
public class KeywordUtil {public static List<String> analyze(String text){// 创建集合用于存放切分或的数据List<String> keywordList = new ArrayList<>();// 封装待分词内容StringReader reader = new StringReader(text);// 创建 IK 分词器(ik_smart 智能分词,ik_max_word: 尽可能分最多的词)IKSegmenter ikSegmenter = new IKSegmenter(reader,true);try {// 取出切分好的词Lexeme lexeme = null;while((lexeme = ikSegmenter.next())!=null){String keyword = lexeme.getLexemeText();keywordList.add(keyword);}} catch (IOException e) {e.printStackTrace();}return keywordList;}public static void main(String[] args) {List<String> list = analyze("Apple iPhoneXSMax (A2104) 256GB 深空灰色 移动联通电信4G手机 双卡双待");System.out.println(list);}
}
2)自定义 UDTF
@FunctionHint(output = @DataTypeHint("ROW<word STRING>"))
public class IkUDTF extends TableFunction<Row> {public void eval(String str){for (String word : KeywordUtil.analyze(str)) {collect(Row.of(word));}}
}
3) 消费页面浏览日志主题

我们相当于一个消费者去消费页面浏览主题,那么就需要先创建该表,也就需要先确定我们要的字段。在事件时间语义下使用窗口函数的时候我们需要指定事件时间的字段;

前面我们为了 join lookup 表的时候那样(要想 join lookup 表,必须要有一个处理时间字段):

 只不过我们现在需要指定一个事件时间,我们同样可以通过 DDL 中来指定:

对于这里的关键词需求而言,我们不需要保留 common 字段,所以建表如下: 

​
// TODO 3. 消费 Kafka dwd_traffic_page_log 主题String groupId = "dws_traffic_source_keyword_page_view_window";tableEnv.executeSql("CREATE TABLE dwd_traffic_page_log " +"       page map<string,string>, " +"       ts bigint , " +"       time_ltz AS TO_TIMESTAMP(FROM_UNIXTIME(ts/1000)), " +"       WATERMARK FOR time_ltz AS time_ltz - INTERVAL '2' SECOND " +MyKafkaUtil.getKafkaDDL("dwd_traffic_page_log", groupId));​

这里我们指定了 time_ltz 为事件时间字段以及乱序延迟时间最大为 2s,这里为什么不直接使用 ts 字段呢?这是因为 json 默认把数值类型都当做 bigint 来处理,而 Flink SQL 中,表的事件时间必须为 timestamp 类型,所以我们需要进行转换;

注意建表语句中尽量加 AS ,尤其字段涉及函数!

4)过滤出搜索数据
// TODO 4. 过滤出搜索数据
Table searchLog = tableEnv.sqlQuery("SELECT " +"page['item'] item, " +"time_ltz " +"FROM dwd_traffic_page_log " +"WHERE page['last_page_id' = 'search'] " +"AND page['item_type'] = 'keyword' " +"AND page['item'] is not null "
);
tableEnv.createTemporaryView("search_log_table",searchLog);
5)注册 udtf 函数并进行分词
// TODO 5. 注册 udtf & 分词
tableEnv.createTemporaryFunction("ik", IkUDTF.class);
Table splitTable = tableEnv.sqlQuery("SELECT " +"word, " +"time_ltz " +"FROM search_log_table " +"LATERAL TABLE(ik(item))"
);
tableEnv.createTemporaryView("split_table",splitTable);
6)分组、开窗、聚合

之前离线数仓写过窗口函数,但是都是没有边界的窗口。这里我们学习一下 Flink 中的三种窗口怎么用 Flink SQL 去写:

上面三种窗口分别对应:滚动,滑动和会话,下面是使用案例:

现在我们需要考虑将来写入到 ck 时,ck 应该采用什么引擎?

  • 选择 SummingMergeTree
    • 优点:自动预聚合,存储的内容少了,查询效率高
    • 缺点:只能做求和指标,比如峰值指标就做不了。再有假如数据消费后挂了(Flink 读取后数据写入到 ck 了,但是这时候挂了,Flink 恢复后会重新消费,ck 就会重复处理。如果是别的引擎还好,因为数据不是聚合的状态,而是一条一条存储的,我们可以对数据根据 uuid 进行区分是否已经处理过)
  • 选择ReplacingMergeTree
    • 它有去重的功能,但是是在任务挂掉的时候我们才用得到(保证一致性)
    • 可以做更多的指标
    • 缺点就是会存储更多的数据

那么,我们当然选择 ReplacingMergeTree ,现在我们需要考虑去重字段(在 ck 中去重字段比主键都重要):

  • 去重(order by 字段):
    • 根据 窗口时间(起始+终止)+关键词 进行去重(这里会添加一个 source 字段区分日志的来源,比如 search、cart、order)

窗口的起始和终止时间同样有特定的函数来获取:

最终,我们的代码:

        // TODO 6. 分组、开窗、聚合Table resultTable = tableEnv.sqlQuery("SELECT " +"    date_format(tumble_start(time_ltz,interval '10' second),'yyyy-MM-dd HH:mm:ss') stt," +"    date_format(tumble_end(time_ltz,interval '10' second),'yyyy-MM-dd HH:mm:ss') edt," +"    'search' source," +"    word keyword," +"    count(*) keyword_count," +"    unix_timestamp() ts" +"FROM split_table" +"GROUP BY word,tumble(time_ltz,interval '10' second)");
7)创建 ck 表格
create table if not exists dws_traffic_source_keyword_page_view_window
(stt           DateTime,edt           DateTime,source        String,keyword       String,keyword_count UInt64,ts            UInt64
) engine = ReplacingMergeTree(ts)partition by toYYYYMMDD(stt)order by (stt, edt, source, keyword);
8)ck 工具类

上面第 6 步之后,我们得到了开窗聚合后的一个结果,要写入 ck 我们需要先将动态表转为流:

// TODO 7. 将动态表转换为流DataStream<KeywordBean> dataStream = tableEnv.toAppendStream(resultTable, KeywordBean.class);

接着我们需要通过 JdbcSink 写出到 ck 集群中,因为之后每个聚合结果都是存在 DWS 层的,所以都会用到该 JdbcSink,所以我们统一封装成一个工具类:

public class ClickHouseUtil {// 泛型方法需要再返回值类型前面放一个泛型public static <T> SinkFunction<T> getSinkFunction(String sql) {return JdbcSink.sink(sql,new JdbcStatementBuilder<T>() {@SneakyThrows@Overridepublic void accept(PreparedStatement preparedStatement, T t) throws SQLException {// 利用反射获得 t 对象的属性Class<?> tClz = t.getClass();int index = 1;for (Field field : tClz.getDeclaredFields()) {field.setAccessible(true); // 防止访问呢 private 属性失败// 尝试获得字段上的注解TransientSink transientSink = field.getAnnotation(TransientSink.class);if (transientSink != null){continue;}// 获得字段值Object value = field.get(t);// 给占位符赋值preparedStatement.setObject(index++,value);}}}, new JdbcExecutionOptions.Builder().withBatchSize(5).withBatchIntervalMs(1000L).build(), new JdbcConnectionOptions.JdbcConnectionOptionsBuilder().withDriverName(GmallConfig.CLICKHOUSE_DRIVER).withUrl(GmallConfig.CLICKHOUSE_URL).build());}
}

在代码中我们有一个获取注解的操作,是为了防止 JavaBean 中的字段(可能是辅助字段)在 ck 表中并没有能对应上的,所以我们通过注解来甄别:

@Retention(RetentionPolicy.RUNTIME) // 生效时机: 运行时
@Target(ElementType.FIELD) // 该注解的作用域: 属性上
public @interface TransientSink {}

补全主程序: 

        // TODO 8. 写入 clickhouse// 插入字段顺序尽量和ck库的表保持一致dataStream.addSink(ClickHouseUtil.getSinkFunction("insert into dws_traffic_source_keyword_page_view_window " +"values(?,?,?,?,?,?)"));// TODO 9. 启动任务env.execute("DwsTrafficSourceKeywordPageViewWindow");

注意:因为我们是通过反射获取 Bean 对象字段来向 ck 表插入数据的,所以一定要保证 Bean 对象的顺序要和 ck 表对应上;

1.2、流量域版本-渠道-地区-访客类别粒度页面浏览各窗口汇总表

上面 DWS 的第一个需求我们是用 Flink SQL 来实现的,从这个需求开始,我们将使用 DataStream API 来实现;

1.2.1、需求分析

  • 维度有 4 个:版本,渠道,地区和访客类别;
  • 度量值有 5 个:会话数、页面浏览数、浏览总时长、独立访客数、跳出会话数等;

        关于独立访客数和跳出会话数我们之前在 DWD 层已经实现并分别写入到了 dwd_traffic_unique_visitor_detail(状态编程保存 lastVisitDate 实现) 和 dwd_traffic_user_jump_detail(Flink CEP 实现) 主题了;所以这里只需要分析前 2 个度量值怎么计算:

  • 会话数
    • 我们的数据中没有 session_id,但是要求也很简单:last_page_id 为 null 即代表一个新会话的开始
  • 页面浏览数(PV)
    • 页面浏览记录中每一行数据就是一个浏览记录(count(1) )
  • 浏览总时长
    • 还是从页面浏览记录中获取浏览时间(during_time)

思考:这三个度量值都可以从 dwd_traffic_page_log 中一次计算出来,但是怎么和另外两个来自不同主题的度量值聚合呢?

答:使用 join,根据 dws 表的粒度进行 join,但是在 SQL 中使用 join 的话也许好一点,可是我们使用 API 就比较复杂;所以其实我们还可以使用另一种方式实现——使用 union + 分组聚合也可以实现;

那么最终写入到 ck 中的字段其实一共有 12 个:4个维度字段 + 5 个度量值 + ts + 窗口起始、终止时间字段;

1.2.2、代码实现

1)建表语句
create table if not exists dws_traffic_vc_ch_ar_is_new_page_view_window
(stt     DateTime,edt     DateTime,vc      String,ch      String,ar      String,is_new  String,uv_ct   UInt64,sv_ct   UInt64,pv_ct   UInt64,dur_sum UInt64,uj_ct   UInt64,ts      UInt64
) engine = ReplacingMergeTree(ts)partition by toYYYYMMDD(stt)order by (stt, edt, vc, ch, ar, is_new);
2)创建 ck 表对应的 Bean
import lombok.AllArgsConstructor;
import lombok.Data;@Data
@AllArgsConstructor
public class TrafficPageViewBean {// 窗口起始时间String stt;// 窗口结束时间String edt;// app 版本号String vc;// 渠道String ch;// 地区String ar;// 新老访客状态标记String isNew ;// 独立访客数Long uvCt;// 会话数Long svCt;// 页面浏览数Long pvCt;// 累计访问时长Long durSum;// 跳出会话数Long ujCt;// 时间戳Long ts;
}
3)读取三个主题的数据
        // TODO 3. 读取三个主题的数据String uvTopic = "dwd_traffic_unique_visitor_detail";String ujdTopic = "dwd_traffic_user_jump_detail";String topic = "dwd_traffic_page_log";String groupId = "dws_traffic_channel_page_view_window";DataStreamSource<String> uvDS = env.addSource(MyKafkaUtil.getFlinkKafkaConsumer(uvTopic, groupId));DataStreamSource<String> ujdDS = env.addSource(MyKafkaUtil.getFlinkKafkaConsumer(ujdTopic, groupId));DataStreamSource<String> pageDS = env.addSource(MyKafkaUtil.getFlinkKafkaConsumer(topic, groupId));
4)统一数据格式

将 3 个主题的数据进行格式统一,方便后面 union + 聚合

        // TODO 4. 统一数据格式SingleOutputStreamOperator<TrafficPageViewBean> trafficWithUvDS = uvDS.map(line -> {JSONObject jsonObject = JSONObject.parseObject(line);JSONObject common = jsonObject.getJSONObject("common");return new TrafficPageViewBean("", "",common.getString("vc"),common.getString("ch"),common.getString("ar"),common.getString("is_new"),1L, 0L, 0L, 0L, 0L,common.getLong("ts"));});SingleOutputStreamOperator<TrafficPageViewBean> trafficWithUJ = ujdDS.map(line -> {JSONObject jsonObject = JSONObject.parseObject(line);JSONObject common = jsonObject.getJSONObject("common");return new TrafficPageViewBean("", "",common.getString("vc"),common.getString("ch"),common.getString("ar"),common.getString("is_new"),0L, 0L, 0L, 0L, 1L,common.getLong("ts"));});SingleOutputStreamOperator<TrafficPageViewBean> trafficWithSvPvDurSumDS = pageDS.map(line -> {JSONObject jsonObject = JSONObject.parseObject(line);JSONObject common = jsonObject.getJSONObject("common");JSONObject page = jsonObject.getJSONObject("page");return new TrafficPageViewBean("", "",common.getString("vc"),common.getString("ch"),common.getString("ar"),common.getString("is_new"),0L,page.getString("last_page_id") == null ? 1L : 0L,1L,page.getLong("during_time"),0L,common.getLong("ts"));});

注意: trafficWithUJ 这条流本就存在延时,所以很可能下面在 union 的时候,窗口都关闭了它还没来,所以我们只能给水位线的最大乱序等待时间 + 判定为用户跳出的最大时间(也就是超时时间);

5)三流 union

对三条流进行 union 然后提取出事件时间生成水位线,之后就需要开窗聚合了,而开窗聚合我们一般都会指定 keyby 再开窗,全窗口几乎不用;而 keyby 的字段我们选择 4 个维度(可以用 String  拼接也可以用一个四元组 Tuple4)

窗口分类:

  • OpWindow:windowAll()
  • KeyedWindow:window()
    • 时间:滚动、滑动、会话
    • 计数:滚动、滑动
// TODO 5. 三条流进行 unionDataStream<TrafficPageViewBean> unionDS = trafficWithUvDS.union(trafficWithUJDS, trafficWithSvPvDurSumDS);// TODO 6. 提取事件时间(去 ts 字段生成水位线)SingleOutputStreamOperator<TrafficPageViewBean> trafficPageViewWithWaterMarkDS = unionDS.assignTimestampsAndWatermarks(WatermarkStrategy.<TrafficPageViewBean>forBoundedOutOfOrderness(Duration.ofSeconds(14)).withTimestampAssigner(new SerializableTimestampAssigner<TrafficPageViewBean>() {@Overridepublic long extractTimestamp(TrafficPageViewBean element, long recordTimestamp) {return element.getTs();}}));// TODO 7. 分组开窗聚合(按照维度做keyby)WindowedStream<TrafficPageViewBean, Tuple4<String, String, String, String>, TimeWindow> windowedStream = trafficPageViewWithWaterMarkDS.keyBy(new KeySelector<TrafficPageViewBean, Tuple4<String, String, String, String>>() {@Overridepublic Tuple4<String, String, String, String> getKey(TrafficPageViewBean value) throws Exception {return Tuple4.of(value.getAr(),value.getCh(),value.getIsNew(),value.getVc());}}).window(TumblingEventTimeWindows.of(Time.seconds(10)));

注意:这里在设置水位线延迟时间时,我们设置为 14,因为需求中包含用户跳出会话数,而跳出这个需求本就存在延迟(我们在 DWD 层设置了两种判断跳出策略(前提是按照 mid 分区):1. last_page_id = null & 下一条数据的 last_page_id 也为 null  2. last_page_id = null & 超时时间达到 10s 视作跳出)

6) 聚合

回顾一下窗口聚合函数:

  • 增量聚合函数:来一条计算一条(效率高,存储数据量小)
  • 全量聚合函数:可以求平均值和百分比,可以获取窗口信息

与增量聚合函数不同,全窗口函数需要先收集窗口中的数据,并在内部缓存起来,等到窗口要输出结果的时候再取出数据进行计算。很明显,这就是典型的批处理思路了——先攒数据,等一批都到齐了再正式启动处理流程。

        但是把计算放到窗口关闭才去计算无疑是低效的,毕竟如果数据量比较大的时候,这种方式肯定没有增量聚合函数计算的快。那为什么还要使用这种方式呢?这是因为有些场景下,我们要做的计算必须基于全部的数据才有效(比如求平均值),这时做增量聚合就没什么意义了

那么,现在我们应该对 keyby 后的数据流进行聚合,把相同 key 的度量值进行累加,那么我们应该选用哪种聚合函数呢?

选用增量聚合函数其实可以实现度量值的累加,但是由于我们的 ck 表中还有两个窗口字段需要补充(窗口起始和终止时间),所以我们需要获取窗口信息,那这就只能使用全量聚合函数了,毕竟全量窗口函数才能获得窗口信息;但是全窗口函数的计算往往是放到最后才执行的,这就很难受,那能不能结合二者的优点呢?

其实是可以的,我们在之前学习Flink 窗口的时候是讲过的:

增量聚合函数处理计算会更高效。举一个最简单的例子,对一组数据求和。大量的数据连续不断到来,全窗口函数只是把它们收集缓存起来,并没有处理;到了窗口要关闭、输出结果的时候,再遍历所有数据依次叠加,得到最终结果。而如果我们采用增量聚合的方式,那么只需要保存一个当前和的状态,每个数据到来时就会做一次加法,更新状态;到了要输出结果的时候,只要将当前状态直接拿出来就可以了。增量聚合相当于把计算量“均摊”到了窗口收集数据的过程中,自然就会比全窗口聚合更加高效、输出更加实时。
        而全窗口函数的优势在于提供了更多的信息,可以认为是更加“通用”的窗口操作。它只负责收集数据、提供上下文相关信息,把所有的原材料都准备好,至于拿来做什么我们完全可以任意发挥。这就使得窗口计算更加灵活,功能更加强大。
所以在实际应用中,我们往往希望兼具这两者的优点,把它们结合在一起使用。Flink 的Window API 就给我们实现了这样的用法。

SingleOutputStreamOperator<TrafficPageViewBean> resultDS = windowedStream.reduce(new ReduceFunction<TrafficPageViewBean>() {@Overridepublic TrafficPageViewBean reduce(TrafficPageViewBean value1, TrafficPageViewBean value2) throws Exception {value1.setSvCt(value1.getSvCt() + value2.getSvCt());value1.setUvCt(value1.getUvCt() + value2.getUvCt());value1.setUvCt(value1.getUjCt() + value2.getUjCt());value1.setPvCt(value1.getPvCt() + value2.getPvCt());value1.setDurSum(value1.getDurSum() + value2.getDurSum());return value1;}}, new WindowFunction<TrafficPageViewBean, TrafficPageViewBean, Tuple4<String, String, String, String>, TimeWindow>() {@Overridepublic void apply(Tuple4<String, String, String, String> stringStringStringStringTuple4, TimeWindow window, Iterable<TrafficPageViewBean> input, Collector<TrafficPageViewBean> out) throws Exception {// 获取数据TrafficPageViewBean next = input.iterator().next();// 补充信息next.setStt(DateFormatUtil.toYmdHms(window.getStart()));next.setEdt(DateFormatUtil.toYmdHms(window.getEnd()));// 修改 tsnext.setTs(System.currentTimeMillis());// 输出数据out.collect(next);}});

这样,我们既高效地完成了窗口聚合(增量聚合),也拿到了窗口信息(全量聚合获得起止时间); 

7)写出到 clickhouse
        // TODO 8. 写入 clickhouseresultDS.addSink(ClickHouseUtil.getSinkFunction("insert into dws_traffic_channel_page_view_window " +"values(?,?,?,?,?,?,?,?,?,?,?,?)"));// TODO 9. 启动任务env.execute("DwsTrafficVcChArIsNewPageViewWindow");

总结

        至此,流量域两张汇总表创建完毕,关于流量域就剩一张表明天完成,先去吃饭;

相关文章:

Flink 实时数仓(七)【DWS 层搭建(一)流量域汇总表创建】

前言 今天开始 DWS 层的搭建&#xff0c;不知不觉又是周一&#xff0c;都忘了昨天是周末&#xff0c;近两年对我来说&#xff0c;周六日晚上八九点能打一小会篮球就算一周的休息了。不得不说自己真的是天生打工体质&#xff0c;每天不管多累&#xff0c;晚上十二点睡&#xff0…...

Python和PyCharm的安装激活及Python新手入门指南

一、软件介绍 Python 是一种解释型、面向对象、动态数据类型的高级程序设计语言。于 1989 年底由 Guido van Rossum 发明&#xff0c;第一个公开发行版发行于 1991 年。 当然也有很多小伙伴不清楚python与pycharm的区别和联系&#xff0c;接下来给大家简单介绍一下&#xff1…...

Apache Flink窗口机制解析:滚动窗口与滑动窗口的比较与应用

Apache Flink是一个开源的流处理框架&#xff0c;用于实现大规模数据流的处理和分析。在处理数据流时&#xff0c;窗口操作是一种常见的方法&#xff0c;它允许对数据流中连续的项目进行分组。Flink提供了多种窗口类型&#xff0c;其中滚动窗口&#xff08;Tumbling Window&…...

为什么《程序员修炼之道》评分能到 9.1?

大家好&#xff0c;我是 方圆。开始接触到《程序员修炼之道&#xff1a;通向务实的最高境界》这本书是在豆瓣图书的高分榜单上&#xff0c;它的评分高达 9.1&#xff0c;其中有条蛮有意思的书评非常吸引我&#xff1a;“这本书我读过 5 遍信不信&#xff0c;每个字都磨出了感情…...

接口自动化测试框架中动态参数接口,加密接口,签名接口你们是怎么处理的?

动态参数&#xff1a;可通过热加载形式&#xff08;在代码执行过中自动去yaml里面执行外部的函数&#xff09; 接口测试加密解密简介&#xff1a; 对称加密&#xff08;私钥加密&#xff0c;只有一个密钥&#xff09;AES,DES,BASE64 特点是&#xff1a;加密和解密有相同的密钥…...

【hadoop】常用命令

集群信息 查看hadoop版本 hadoop version查询hdfs系统中的namenode # 方式一 hdfs getconf -namenodes# 方式二 hdfs getconf -confKey dfs.namenode.http-address获取NameNode restful接口 hdfs getconf -confKey dfs.namenode.http-address hdfs getconf -confKey dfs.na…...

时间同步--- ntp与ptp

时间同步 1. 什么是NTP时间&#xff1f;什么是PTP时间&#xff1f; NTP时间&#xff08;Network Time Protocol 时间&#xff09;: NTP即网络时间协议&#xff08;Network Time Protocol&#xff09;&#xff0c;它是一种用于同步计算机时间的网络协议。NTP可以将所有参与的计…...

CSDN 僵尸粉 机器人

CSDN 僵尸粉 机器人 1. 前言 不知道什么时候开始每天创作2篇就有1500流量爆光&#xff0c;每次都能收获一些关注和收藏&#xff0c;感觉还是挻开心的感觉CSDN人气还是挻可以的以前各把月一个收藏和关注都没有写的动力了。 2. 正文 后面又连接做了2天的每日创建2篇任务&…...

【Material-UI】File Upload Button 组件详解

文章目录 一、基础实现1. component"label"2. 隐藏的输入元素 二、样式和交互增强1. 自定义按钮样式2. 交互提示 三、支持多文件上传四、无障碍性&#xff08;Accessibility&#xff09;1. 提供 aria-label 或 aria-labelledby2. 支持键盘导航 五、高级用法和集成1. …...

计算机组成原理 - 中央处理器

中央处理器 考纲内容 CPU的功能和基本结构指令执行过程数据通路的功能和基本结构控制器的功能和工作原理异常和中断机制 异常和终端的基本概念&#xff1b;异常和中断的分类&#xff1b;异常和中断的检测与响应指令流水线 指令流水线的基本概念&#xff1b;指令流水线的基本实…...

C++笔试练习笔记【5】:最小花费爬楼梯(有题目链接) 初识动态规划

文章目录 题目思路代码 动态规划简介**一、什么是动态规划****二、动态规划的应用场景****三、动态规划的基本步骤****四、动态规划的优缺点** 题目 题目链接&#xff1a;https://www.nowcoder.com/practice/9b969a3ec20149e3b870b256ad40844e?tpld230&tpld39751&ru/…...

数据结构----------贪心算法

什么是贪心算法&#xff1f; 贪心算法&#xff08;Greedy Algorithm&#xff09;是一种在问题求解过程中&#xff0c;每一步都采取当前状态下最优&#xff08;即最有利&#xff09;的选择&#xff0c;从而希望导致最终的全局最优解的算法策略。 贪心算法的核心思想是做选择时&…...

C++初学(11)

不知不觉就第11篇了QWQ 11.1、指针和自由存储空间 之前提到了计算机程序在存储数据时必须跟踪的3个基本属性&#xff1a; &#xff08;1&#xff09;信息存储在何处&#xff1b; &#xff08;2&#xff09;存储的值为多少&#xff1b; &#xff08;3&#xff09;存储的信息…...

Vba选择cad中不同类型图元(Select Case True语句和like用法)

Select Case True 是一个常见的VBA编程技巧&#xff0c;用于在多个条件之间进行选择。具体来说&#xff0c;Select Case True 语句的每个 Case 语句都包含一个布尔表达式&#xff0c;这些表达式会逐个与 True 进行比较。当其中一个表达式的结果为 True 时&#xff0c;对应的代码…...

Kafka基本讲解

Kafka基本讲解 一&#xff1a;Kafka介绍 Kafka是分布式消息队列&#xff0c;主要设计用于高吞吐量的数据处理和消息传输&#xff0c;适用于日志处理、实时数据管道等场景。Kafka作为实时数仓架构的核心组件&#xff0c;用于收集、缓存和分发实时数据流&#xff0c;支持复杂的…...

thinkphp6项目初始化配置方案二次修正版本

数据返回统一格式 app/BaseController.php新增文件内容在末尾,并在构造函数中实例化数据模型类 // 成功统一返回格式 function Result($data, $msg , $code 200, $httpCode 200): \think\response\Json {$res [code > $code,msg > $msg,data > $data];return j…...

XXE靶机教学

arp-scan -l主机发现 arp-scan -l 端口扫描 nmap -p- 192.168.48.139 服务探测 nmap -p80,5355 -sT -sC -sV 192.168.48.139 目录扫描 dirsearch -u http://192.168.48.139 访问robots.txt 发现两个可访问路径 burp抓包 测试是否存在xxe漏洞 <?xml version "1.…...

干货 | 2024步入数字化转型深水区,云原生业务稳定性如何保障(免费下载)

云原生业务的稳定性保障是一个涉及多个层面的复杂任务&#xff0c;以下是一些关键措施和策略&#xff0c;以确保云原生业务的高效稳定运行&#xff1a; 一、平台安全性评估与加固 云原生平台安全评估&#xff1a;对云原生平台&#xff08;如Kubernetes、Docker等&#xff09;…...

for(char c:s),std::vector<int> numbers 和std::int numbers[],.size()和.sizeof()区别

在C中当需要对某个容器或数组进行遍历时我们可以使用以下语句&#xff0c;c将会被赋值为s中的元素 for(char c:s)://s可以是任何满足条件的容器或数组for(int c:s):for(double c:s):for(float c:s):在C中我们来区分std::vector numbers {1, 2, 3, 4, 5};和std::int numbers[] …...

桌面云备份可以删除吗?安不安全

桌面云备份可以删除吗&#xff1f;答案是可以的。如果用户不需要这些备份或者想要释放存储空间&#xff0c;桌面云备份是可以进行删除的&#xff0c;并且删除桌面云备份是一个相对安全的过程&#xff0c;但需要注意以下几点来确保操作的安全性和数据的完整性。 一、桌面云备份…...

【Linux】shell脚本忽略错误继续执行

在 shell 脚本中&#xff0c;可以使用 set -e 命令来设置脚本在遇到错误时退出执行。如果你希望脚本忽略错误并继续执行&#xff0c;可以在脚本开头添加 set e 命令来取消该设置。 举例1 #!/bin/bash# 取消 set -e 的设置 set e# 执行命令&#xff0c;并忽略错误 rm somefile…...

Qt/C++开发监控GB28181系统/取流协议/同时支持udp/tcp被动/tcp主动

一、前言说明 在2011版本的gb28181协议中&#xff0c;拉取视频流只要求udp方式&#xff0c;从2016开始要求新增支持tcp被动和tcp主动两种方式&#xff0c;udp理论上会丢包的&#xff0c;所以实际使用过程可能会出现画面花屏的情况&#xff0c;而tcp肯定不丢包&#xff0c;起码…...

【Oracle APEX开发小技巧12】

有如下需求&#xff1a; 有一个问题反馈页面&#xff0c;要实现在apex页面展示能直观看到反馈时间超过7天未处理的数据&#xff0c;方便管理员及时处理反馈。 我的方法&#xff1a;直接将逻辑写在SQL中&#xff0c;这样可以直接在页面展示 完整代码&#xff1a; SELECTSF.FE…...

Mybatis逆向工程,动态创建实体类、条件扩展类、Mapper接口、Mapper.xml映射文件

今天呢&#xff0c;博主的学习进度也是步入了Java Mybatis 框架&#xff0c;目前正在逐步杨帆旗航。 那么接下来就给大家出一期有关 Mybatis 逆向工程的教学&#xff0c;希望能对大家有所帮助&#xff0c;也特别欢迎大家指点不足之处&#xff0c;小生很乐意接受正确的建议&…...

基于uniapp+WebSocket实现聊天对话、消息监听、消息推送、聊天室等功能,多端兼容

基于 ​UniApp + WebSocket​实现多端兼容的实时通讯系统,涵盖WebSocket连接建立、消息收发机制、多端兼容性配置、消息实时监听等功能,适配​微信小程序、H5、Android、iOS等终端 目录 技术选型分析WebSocket协议优势UniApp跨平台特性WebSocket 基础实现连接管理消息收发连接…...

高频面试之3Zookeeper

高频面试之3Zookeeper 文章目录 高频面试之3Zookeeper3.1 常用命令3.2 选举机制3.3 Zookeeper符合法则中哪两个&#xff1f;3.4 Zookeeper脑裂3.5 Zookeeper用来干嘛了 3.1 常用命令 ls、get、create、delete、deleteall3.2 选举机制 半数机制&#xff08;过半机制&#xff0…...

Java-41 深入浅出 Spring - 声明式事务的支持 事务配置 XML模式 XML+注解模式

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; &#x1f680; AI篇持续更新中&#xff01;&#xff08;长期更新&#xff09; 目前2025年06月05日更新到&#xff1a; AI炼丹日志-28 - Aud…...

C++ 求圆面积的程序(Program to find area of a circle)

给定半径r&#xff0c;求圆的面积。圆的面积应精确到小数点后5位。 例子&#xff1a; 输入&#xff1a;r 5 输出&#xff1a;78.53982 解释&#xff1a;由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982&#xff0c;因为我们只保留小数点后 5 位数字。 输…...

【HTTP三个基础问题】

面试官您好&#xff01;HTTP是超文本传输协议&#xff0c;是互联网上客户端和服务器之间传输超文本数据&#xff08;比如文字、图片、音频、视频等&#xff09;的核心协议&#xff0c;当前互联网应用最广泛的版本是HTTP1.1&#xff0c;它基于经典的C/S模型&#xff0c;也就是客…...

python报错No module named ‘tensorflow.keras‘

是由于不同版本的tensorflow下的keras所在的路径不同&#xff0c;结合所安装的tensorflow的目录结构修改from语句即可。 原语句&#xff1a; from tensorflow.keras.layers import Conv1D, MaxPooling1D, LSTM, Dense 修改后&#xff1a; from tensorflow.python.keras.lay…...