第1章 项目整体介绍

1.1 电商的用户行为

电商平台中的用户行为频繁且较复杂,系统上线运行一段时间后,可以收集到大量的用户行为数据,进而利用大数据技术进行深入挖掘和分析,得到感兴趣的商业指标并增强对风险的控制。

电商用户行为数据多样,整体可以分为用户行为习惯数据业务行为数据两大类。用户的行为习惯数据包括了用户的登录方式、上线的时间点及时长、点击和浏览页面、页面停留时间以及页面跳转等等,我们可以从中进行流量统计和热门商品的统计,也可以深入挖掘用户的特征;这些数据往往可以从web服务器日志中直接读取到。而业务行为数据就是用户在电商平台中针对每个业务(通常是某个具体商品)所作的操作,我们一般会在业务系统中相应的位置埋点,然后收集日志进行分析。业务行为数据又可以简单分为两类:一类是能够明显地表现出用户兴趣的行为,比如对商品的收藏、喜欢、评分和评价,我们可以从中对数据进行深入分析,得到用户画像,进而对用户给出个性化的推荐商品列表,这个过程往往会用到机器学习相关的算法;另一类则是常规的业务操作,但需要着重关注一些异常状况以做好风控,比如登录和订单支付。

1.2 项目主要模块

基于对电商用户行为数据的基本分类,我们可以发现主要有以下三个分析方向:

  • 1.热门统计
    利用用户的点击浏览行为,进行流量统计、近期热门商品统计等。

  • 2.偏好统计
    利用用户的偏好行为,比如收藏、喜欢、评分等,进行用户画像分析,给出个性化的商品推荐列表。

  • 3.风险控制
    利用用户的常规业务行为,比如登录、下单、支付等,分析数据,对异常情况进行报警提示。
    本项目限于数据,我们只实现热门统计和风险控制中的部分内容,将包括以下四大模块:实时热门商品统计、实时流量统计、恶意登录监控和订单支付失效监控。

    由于对实时性要求较高,我们会用flink作为数据处理的框架。在项目中,我们将综合运用flink的各种API,基于EventTime去处理基本的业务需求,并且灵活地使用底层的processFunction,基于状态编程和CEP去处理更加复杂的情形。

1.3 数据源解析

我们准备了一份淘宝用户行为数据集,保存为csv文件。本数据集包含了淘宝上某一天随机一百万用户的所有行为(包括点击、购买、收藏、喜欢)。数据集的每一行表示一条用户行为,由用户ID、商品ID、商品类目ID、行为类型和时间戳组成,并以逗号分隔。关于数据集中每一列的详细描述如下:

字段名 数据类型 说明
userId Long 加密后的用户ID
itemId Long 加密后的商品ID
categoryId Int 加密后的商品所属类别ID
behavior String 用户行为类型,包括(‘pv’, ‘’buy, ‘cart’, ‘fav’)
timestamp Long 行为发生的时间戳,单位秒

另外,我们还可以拿到web服务器的日志数据,这里以apache服务器的一份log为例,每一行日志记录了访问者的IP、userId、访问时间、访问方法以及访问的url,具体描述如下:

字段名 数据类型 说明
ip String 访问的 IP
userId Long 访问的 user ID
eventTime Long 访问时间
method String 访问方法 GET/POST/PUT/DELETE
url String 访问的 url

由于行为数据有限,在实时热门商品统计模块中可以使用UserBehavior数据集,而对于恶意登录监控和订单支付失效监控,我们只以示例数据来做演示。

第2章 实时热门商品统计

首先要实现的是实时热门商品统计,我们将会基于UserBehavior数据集来进行分析。
项目主体用Scala编写,采用IDEA作为开发环境进行项目编写,采用maven作为项目构建和管理工具。首先我们需要搭建项目框架。

2.1 创建Maven项目

2.1.1 项目框架搭建

打开IDEA,创建一个maven项目,命名为UserBehaviorAnalysis。由于包含了多个模块,我们可以以UserBehaviorAnalysis作为父项目,并在其下建一个名为HotItemsAnalysis的子项目,用于实时统计热门top N商品
在UserBehaviorAnalysis下新建一个 maven module作为子项目,命名为HotItemsAnalysis。

父项目只是为了规范化项目结构,方便依赖管理,本身是不需要代码实现的,所以UserBehaviorAnalysis下的src文件夹可以删掉

2.1.2 声明项目中工具的版本信息

我们整个项目需要的工具的不同版本可能会对程序运行造成影响,所以应该在最外层的UserBehaviorAnalysis中声明所有子模块共用的版本信息。
在pom.xml中加入以下配置:
① UserBehaviorAnalysis/pom.xml

<properties><flink.version>1.7.2</flink.version>
<scala.binary.version>2.11</scala.binary.version><kafka.version>2.2.0</kafka.version>
</properties>

2.1.3 添加项目依赖

对于整个项目而言,所有模块都会用到flink相关的组件,所以我们在UserBehaviorAnalysis中引入公有依赖:
① UserBehaviorAnalysis/pom.xml

<dependencies><dependency><groupId>org.apache.flink</groupId><artifactId>flink-scala_${scala.binary.version}</artifactId><version>${flink.version}</version></dependency><dependency><groupId>org.apache.flink</groupId><artifactId>flink-streaming-scala_${scala.binary.version}</artifactId><version>${flink.version}</version></dependency>
<dependency><groupId>org.apache.kafka</groupId>
<artifactId>kafka_${scala.binary.version}</artifactId>
<version>${kafka.version}</version>
</dependency>
<dependency><groupId>org.apache.flink</groupId><artifactId>flink-connector-kafka_${scala.binary.version}</artifactId><version>${flink.version}</version>
</dependency>
</dependencies>

同样,对于maven项目的构建,可以引入公有的插件

<build><plugins><!-- 该插件用于将Scala代码编译成class文件 --><plugin><groupId>net.alchim31.maven</groupId><artifactId>scala-maven-plugin</artifactId><version>3.4.6</version><executions><execution><!-- 声明绑定到maven的compile阶段 --><goals><goal>testCompile</goal></goals></execution></executions></plugin><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-assembly-plugin</artifactId><version>3.0.0</version><configuration><descriptorRefs><descriptorRef>jar-with-dependencies</descriptorRef></descriptorRefs></configuration><executions><execution><id>make-assembly</id><phase>package</phase><goals><goal>single</goal></goals></execution></executions></plugin></plugins>
</build>

在HotItemsAnalysis子模块中,我们并没有引入更多的依赖,所以不需要改动pom文件。

2.1.4 数据准备

在src/main/目录下,可以看到已有的默认源文件目录是java,我们可以将其改名为scala。将数据文件UserBehavior.csv复制到资源文件目录src/main/resources下,我们将从这里读取数据。
至此,我们的准备工作都已完成,接下来可以写代码了。

2.2 模块代码实现

我们将实现一个“实时热门商品”的需求,可以将“实时热门商品”翻译成程序员更好理解的需求:

每隔5分钟输出最近一小时内点击量最多的前N个商品。

将这个需求进行分解我们大概要做这么几件事情:

  • 抽取出业务时间戳,告诉Flink框架基于业务时间做窗口
  • 过滤出点击行为数据
  • 按一小时的窗口大小,每5分钟统计一次,做滑动窗口聚合(Sliding Window)
  • 按每个窗口聚合,输出每个窗口中点击量前N名的商品

2.2.1 程序主体

在src/main/scala下创建HotItems.scala文件,新建一个单例对象。定义样例类UserBehavior和ItemViewCount,在main函数中创建StreamExecutionEnvironment 并做配置,然后从UserBehavior.csv文件中读取数据,并包装成UserBehavior类型。代码如下:
② HotItemsAnalysis/src/main/scala/HotItems.scala

case class UserBehavior(userId: Long, itemId: Long, categoryId: Int, behavior: String, timestamp: Long)
case class ItemViewCount(itemId: Long, windowEnd: Long, count: Long)object HotItems {def main(args: Array[String]): Unit = {// 创建一个 StreamExecutionEnvironmentval env = StreamExecutionEnvironment.getExecutionEnvironment// 设定Time类型为EventTime
env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)// 为了打印到控制台的结果不乱序,我们配置全局的并发为1,这里改变并发对结果正确性没有影响
env.setParallelism(1)val stream = env
// 以window下为例,需替换成自己的路径.readTextFile("YOUR_PATH\\resources\\UserBehavior.csv").map(line => {val linearray = line.split(",")UserBehavior(linearray(0).toLong, linearray(1).toLong, linearray(2).toInt, linearray(3), linearray(4).toLong)})// 指定时间戳和watermark
.assignAscendingTimestamps(_.timestamp * 1000)env.execute("Hot Items Job")}

这里注意,我们需要统计业务时间上的每小时的点击量,所以要基于EventTime来处理。那么如果让Flink按照我们想要的业务时间来处理呢?这里主要有两件事情要做。
第一件是告诉Flink我们现在按照EventTime模式进行处理,Flink默认使用ProcessingTime处理,所以我们要显式设置如下:

env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime);

第二件事情是指定如何获得业务时间,以及生成Watermark。Watermark是用来追踪业务事件的概念,可以理解成EventTime世界中的时钟,用来指示当前处理到什么时刻的数据了。由于我们的数据源的数据已经经过整理,没有乱序,即事件的时间戳是单调递增的,所以可以将每条数据的业务时间就当做Watermark。这里我们用 assignAscendingTimestamps来实现时间戳的抽取和Watermark的生成。
注:真实业务场景一般都是乱序的,所以一般不用assignAscendingTimestamps,而是使用

BoundedOutOfOrdernessTimestampExtractor。

.assignAscendingTimestamps(_.timestamp * 1000)

这样我们就得到了一个带有时间标记的数据流了,后面就能做一些窗口的操作。

2.2.2 过滤出点击事件

在开始窗口操作之前,先回顾下需求“每隔5分钟输出过去一小时内点击量最多的前N个商品”。由于原始数据中存在点击、购买、收藏、喜欢各种行为的数据,但是我们只需要统计点击量,所以先使用filter将点击行为数据过滤出来。

.filter(_.behavior == "pv")

2.2.3 设置滑动窗口,统计点击量

由于要每隔5分钟统计一次最近一小时每个商品的点击量,所以窗口大小是一小时,每隔5分钟滑动一次。即分别要统计[09:00, 10:00), [09:05, 10:05), [09:10, 10:10)…等窗口的商品点击量。是一个常见的滑动窗口需求(Sliding Window)。

    .keyBy("itemId").timeWindow(Time.minutes(60), Time.minutes(5)).aggregate(new CountAgg(), new WindowResultFunction());

我们使用.keyBy(“itemId”)对商品进行分组,使用.timeWindow(Time size, Time slide)对每个商品做滑动窗口(1小时窗口,5分钟滑动一次)。然后我们使用 .aggregate(AggregateFunction af, WindowFunction wf) 做增量的聚合操作,它能使用AggregateFunction提前聚合掉数据,减少state的存储压力。较之 .apply(WindowFunction wf) 会将窗口中的数据都存储下来,最后一起计算要高效地多。这里的CountAgg实现了AggregateFunction接口,功能是统计窗口中的条数,即遇到一条数据就加一。

// COUNT统计的聚合函数实现,每出现一条记录就加一
class CountAgg extends AggregateFunction[UserBehavior, Long, Long] {override def createAccumulator(): Long = 0Loverride def add(userBehavior: UserBehavior, acc: Long): Long = acc + 1override def getResult(acc: Long): Long = accoverride def merge(acc1: Long, acc2: Long): Long = acc1 + acc2
}

聚合操作.aggregate(AggregateFunction af, WindowFunction wf)的第二个参数WindowFunction将每个key每个窗口聚合后的结果带上其他信息进行输出。我们这里实现的WindowResultFunction将<主键商品ID,窗口,点击量>封装成了ItemViewCount进行输出。

// 商品点击量(窗口操作的输出类型)
case class ItemViewCount(itemId: Long, windowEnd: Long, count: Long)

代码如下:

// 用于输出窗口的结果
class WindowResultFunction extends WindowFunction[Long, ItemViewCount, Tuple, TimeWindow] {override def apply(key: Tuple, window: TimeWindow, aggregateResult: Iterable[Long],collector: Collector[ItemViewCount]) : Unit = {val itemId: Long = key.asInstanceOf[Tuple1[Long]].f0val count = aggregateResult.iterator.nextcollector.collect(ItemViewCount(itemId, window.getEnd, count))}
}

现在我们就得到了每个商品在每个窗口的点击量的数据流

2.2.4 计算最热门Top N商品

为了统计每个窗口下最热门的商品,我们需要再次按窗口进行分组,这里根据ItemViewCount中的windowEnd进行keyBy()操作。然后使用ProcessFunction实现一个自定义的TopN函数TopNHotItems来计算点击量排名前3名的商品,并将排名结果格式化成字符串,便于后续输出。

 .keyBy("windowEnd").process(new TopNHotItems(3));  // 求点击量前3名的商品

ProcessFunction是Flink提供的一个low-level API,用于实现更高级的功能。它主要提供了定时器timer的功能(支持EventTime或ProcessingTime)。本案例中我们将利用timer来判断何时收齐了某个window下所有商品的点击量数据。由于Watermark的进度是全局的,在processElement方法中,每当收到一条数据ItemViewCount,我们就注册一个windowEnd+1的定时器(Flink框架会自动忽略同一时间的重复注册)。windowEnd+1的定时器被触发时,意味着收到了windowEnd+1的Watermark,即收齐了该windowEnd下的所有商品窗口统计值。我们在onTimer()中处理将收集的所有商品及点击量进行排序,选出TopN,并将排名信息格式化成字符串后进行输出。

这里我们还使用了ListState来存储收到的每条ItemViewCount消息,保证在发生故障时,状态数据的不丢失和一致性。ListState是Flink提供的类似Java List接口的State API,它集成了框架的checkpoint机制,自动做到了exactly-once的语义保证。

  // 求某个窗口中前 N 名的热门点击商品,key 为窗口时间戳,输出为 TopN 的结果字符串class TopNHotItems(topSize: Int) extends KeyedProcessFunction[Tuple, ItemViewCount, String] {private var itemState : ListState[ItemViewCount] = _override def open(parameters: Configuration): Unit = {super.open(parameters)// 命名状态变量的名字和状态变量的类型val itemsStateDesc = new ListStateDescriptor[ItemViewCount]("itemState-state", classOf[ItemViewCount])// 定义状态变量itemState = getRuntimeContext.getListState(itemsStateDesc)}override def processElement(input: ItemViewCount, context: KeyedProcessFunction[Tuple, ItemViewCount, String]#Context, collector: Collector[String]): Unit = {// 每条数据都保存到状态中itemState.add(input)// 注册 windowEnd+1 的 EventTime Timer, 当触发时,说明收齐了属于windowEnd窗口的所有商品数据// 也就是当程序看到windowend + 1的水位线watermark时,触发onTimer回调函数context.timerService.registerEventTimeTimer(input.windowEnd + 1)}override def onTimer(timestamp: Long, ctx: KeyedProcessFunction[Tuple, ItemViewCount, String]#OnTimerContext, out: Collector[String]): Unit = {// 获取收到的所有商品点击量val allItems: ListBuffer[ItemViewCount] = ListBuffer()import scala.collection.JavaConversions._for (item <- itemState.get) {allItems += item}// 提前清除状态中的数据,释放空间itemState.clear()// 按照点击量从大到小排序val sortedItems = allItems.sortBy(_.count)(Ordering.Long.reverse).take(topSize)// 将排名信息格式化成 String, 便于打印val result: StringBuilder = new StringBuilderresult.append("====================================\n")result.append("时间: ").append(new Timestamp(timestamp - 1)).append("\n")for(i <- sortedItems.indices){val currentItem: ItemViewCount = sortedItems(i)// e.g.  No1:  商品ID=12224  浏览量=2413result.append("No").append(i+1).append(":")
.append("  商品ID=").append(currentItem.itemId)
.append("  浏览量=").append(currentItem.count).append("\n")}result.append("====================================\n\n")// 控制输出频率,模拟实时滚动结果Thread.sleep(1000)out.collect(result.toString)}}

最后我们可以在main函数中将结果打印输出到控制台,方便实时观测:

.print();

至此整个程序代码全部完成,我们直接运行main函数,就可以在控制台看到不断输出的各个时间点统计出的热门商品。

2.2.5 完整代码

最终完整代码如下:

case class UserBehavior(userId: Long, itemId: Long, categoryId: Int, behavior: String, timestamp: Long)case class ItemViewCount(itemId: Long, windowEnd: Long, count: Long)object HotItems {def main(args: Array[String]): Unit = {val env = StreamExecutionEnvironment.getExecutionEnvironmentenv.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)env.setParallelism(1)val stream = env.readTextFile("YOUR_PATH\\resources\\UserBehavior.csv").map(line => {val linearray = line.split(",")UserBehavior(linearray(0).toLong, linearray(1).toLong, linearray(2).toInt, linearray(3), linearray(4).toLong)}).assignAscendingTimestamps(_.timestamp * 1000).filter(_.behavior=="pv").keyBy("itemId").timeWindow(Time.minutes(60), Time.minutes(5)).aggregate(new CountAgg(), new WindowResultFunction())    .keyBy(1).process(new TopNHotItems(3)).print()env.execute("Hot Items Job")}// COUNT 统计的聚合函数实现,每出现一条记录加一class CountAgg extends AggregateFunction[UserBehavior, Long, Long] {override def createAccumulator(): Long = 0Loverride def add(userBehavior: UserBehavior, acc: Long): Long = acc + 1override def getResult(acc: Long): Long = accoverride def merge(acc1: Long, acc2: Long): Long = acc1 + acc2}// 用于输出窗口的结果class WindowResultFunction extends WindowFunction[Long, ItemViewCount, Tuple, TimeWindow] {override def apply(key: Tuple, window: TimeWindow, aggregateResult: Iterable[Long],collector: Collector[ItemViewCount]) : Unit = {val itemId: Long = key.asInstanceOf[Tuple1[Long]].f0val count = aggregateResult.iterator.nextcollector.collect(ItemViewCount(itemId, window.getEnd, count))}}// 求某个窗口中前 N 名的热门点击商品,key 为窗口时间戳,输出为 TopN 的结果字符串class TopNHotItems(topSize: Int) extends KeyedProcessFunction[Tuple, ItemViewCount, String] {private var itemState : ListState[ItemViewCount] = _override def open(parameters: Configuration): Unit = {super.open(parameters)// 命名状态变量的名字和状态变量的类型val itemsStateDesc = new ListStateDescriptor[ItemViewCount]("itemState-state", classOf[ItemViewCount])// 从运行时上下文中获取状态并赋值itemState = getRuntimeContext.getListState(itemsStateDesc)}override def processElement(input: ItemViewCount, context: KeyedProcessFunction[Tuple, ItemViewCount, String]#Context, collector: Collector[String]): Unit = {// 每条数据都保存到状态中itemState.add(input)// 注册 windowEnd+1 的 EventTime Timer, 当触发时,说明收齐了属于windowEnd窗口的所有商品数据// 也就是当程序看到windowend + 1的水位线watermark时,触发onTimer回调函数context.timerService.registerEventTimeTimer(input.windowEnd + 1)}override def onTimer(timestamp: Long, ctx: KeyedProcessFunction[Tuple, ItemViewCount, String]#OnTimerContext, out: Collector[String]): Unit = {// 获取收到的所有商品点击量val allItems: ListBuffer[ItemViewCount] = ListBuffer()import scala.collection.JavaConversions._for (item <- itemState.get) {allItems += item}// 提前清除状态中的数据,释放空间itemState.clear()// 按照点击量从大到小排序val sortedItems = allItems.sortBy(_.count)(Ordering.Long.reverse).take(topSize)// 将排名信息格式化成 String, 便于打印val result: StringBuilder = new StringBuilderresult.append("====================================\n")result.append("时间: ").append(new Timestamp(timestamp - 1)).append("\n")for(i <- sortedItems.indices){val currentItem: ItemViewCount = sortedItems(i)// e.g.  No1:  商品ID=12224  浏览量=2413result.append("No").append(i+1).append(":")
.append("  商品ID=").append(currentItem.itemId)
.append("  浏览量=").append(currentItem.count).append("\n")}result.append("====================================\n\n")// 控制输出频率,模拟实时滚动结果Thread.sleep(1000)out.collect(result.toString)}}
}

2.2.6 更换Kafka 作为数据源

实际生产环境中,我们的数据流往往是从Kafka获取到的。如果要让代码更贴近生产实际,我们只需将source更换为Kafka即可:

val properties = new Properties()
properties.setProperty("bootstrap.servers", "localhost:9092")
properties.setProperty("group.id", "consumer-group")
properties.setProperty("key.deserializer",
"org.apache.kafka.common.serialization.StringDeserializer")
properties.setProperty("value.deserializer",
"org.apache.kafka.common.serialization.StringDeserializer")
properties.setProperty("auto.offset.reset", "latest")val env = StreamExecutionEnvironment.getExecutionEnvironment
env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
env.setParallelism(1)val stream = env.addSource(new FlinkKafkaConsumer[String]("hotitems", new SimpleStringSchema(), properties))

当然,根据实际的需要,我们还可以将Sink指定为Kafka、ES、Redis或其它存储,这里就不一一展开实现了。

Flink项目实践【一】实时热门商品统计相关推荐

  1. Flink 零基础实战教程:如何计算实时热门商品

    在上一篇入门教程中,我们已经能够快速构建一个基础的 Flink 程序了.本文会一步步地带领你实现一个更复杂的 Flink 应用程序:实时热门商品.在开始本文前我们建议你先实践一遍上篇文章,因为本文会沿 ...

  2. 99.Spark大型电商项目-各区域热门商品统计-模块介绍

    目录 各区域热门商品统计 作业提交 大数据方向的职业发展规划 用户行为分析意义 本篇文章记录各区域热门商品统计-模块介绍. 各区域热门商品统计 需求:根据用户指定的日期范围,统计各个区域下的最热门的t ...

  3. 2021年大数据Flink(四十):​​​​​​​Flink模拟双十一实时大屏统计

    目录 Flink模拟双十一实时大屏统计 需求 数据 编码步骤: 1.env 2.source 3.transformation 4.使用上面聚合的结果,实现业务需求: 5.execute 参考代码 实 ...

  4. 人工智能-推荐系统-模块01:离线统计模块【使用SparkSQL(基于Scala语言/Python语言)进行离线统计分析:历史热门商品统计、近期热门商品统计、商品平均评分统计...】

    一.基于scala语言的SparkSQL离线统计分析 1.将数据导入MongoDB数据库 DataLoader.scala import com.mongodb.casbah.commons.Mong ...

  5. JavaWeb商城项目笔记--- Day1 (热门商品,热销商品)

    功能出现场景 在线的商场中,前端最近界面总会有一块区域用来显示销售量最高的,最新上架的和类似的这种的商品. 一些联想到的功能:热销,热评等 功能解决思路 核心还是对数据库进行查询,然后响应给前端信息, ...

  6. SparkSql 项目实战 | 各区域热门商品Top3

    数据源 链接:https://pan.baidu.com/s/1lUbGmA10yOgUL4Rz2KAGmw 提取码:yh57 源码在github:https://github.com/lidongl ...

  7. [Scala] Flink项目小彩蛋(六)

    传送区 [Scala] Flink项目实例系列(零) [Scala] Flink项目实时热门商品统计(一) [Scala] Flink项目实时流量统计(二) [Scala] Flink项目恶意登录监控 ...

  8. 电商推荐系统(上):推荐系统架构、数据模型、离线统计与机器学习推荐、历史热门商品、最近热门商品、商品平均得分统计推荐、基于隐语义模型的协同过滤推荐、用户商品推荐列表、商品相似度矩阵、模型评估和参数选取

    文章目录 第1章 项目体系架构设计 1.1 项目系统架构 1.2 项目数据流程 1.3 数据模型 第2章 工具环境搭建 2.1 MongoDB(单节点)环境配置 2.2 Redis(单节点)环境配置 ...

  9. spark企业级电商分析平台项目实践(一)项目介绍和需求分析

    前言 这个专栏的系列文章,是一个电商分析平台项目实践过程中的记录和总结. 基于 spark2.4.x 和 scala2.11.x 一. 项目概述 访问电商网站时,我们的一些访问行为会产生相应的埋点日志 ...

最新文章

  1. Boost库之circular_buffer
  2. python中数据分析的流程为-利用python进行数据分析——histogram
  3. Creating a custom ComboBox item renderer in Flex
  4. 如何在linux环境下安装kvm,如何在Linux发行版上安装和配置KVM和Open vSwitch?
  5. Kubernetes 入门(3)集群安装
  6. 无人驾驶入门(基本流程)
  7. Python并发编程系列之多进程(multiprocessing)
  8. 感应联动不是梦,穿透屏幕“闻”见花香你敢信?
  9. 《奠基计算机网络》所需软件 下载地址
  10. 孤读Paper——《CenterNet:Objects as Points》
  11. 阶段1 语言基础+高级_1-3-Java语言高级_05-异常与多线程_第4节 等待唤醒机制_3_等待唤醒案例代码实现...
  12. iOS底层:PAGEZERO的作用
  13. 大地测量学白塞尔大地主题解算
  14. 使用多种算法挖掘Alexa域名数据
  15. 【PHP】openssl_sign(): supplied key param cannot be coerced into a private key
  16. 决策树的特性及优缺点
  17. SAP的统驭科目 - 什么是SAP的统驭科目
  18. None和nan、NaN、NAN
  19. Struts2+Spring+Hibernate 三大框架的合并集成
  20. 【51单片机】定时器产生指定延迟的delay函数

热门文章

  1. Springboot整合通用mapper进阶1
  2. 对称式三辊卷板机的设计(论文+CAD图纸)
  3. Opencv的视频捕捉功能+多线程
  4. 计算机考研英语能换日语吗,我是计算机专科毕业 想考大外日语专业的研究生 关键问题是我能报考吗?...
  5. 计算机汉字中那个有标记,如何在手机键盘上标记音调
  6. 关于Java String 不可变性的分析
  7. 112页PPT | 元宇宙的技术构成与未来展望(附下载)
  8. LabVIEW前面板占满整个屏幕
  9. 简述SPI总线通信协议笔记
  10. svm matlab 画图,SVM简单代码实现MATLAB