topN功能是一个非常常见的功能,比如查看最近几分钟的阅读最高数,购买最高数。

  flink实现topN的功能也非常方便,下面就开始构建一个flink topN的程序。

  还是像上篇博客一样,从kafka读取数据,然后进行计算和数据转换,最后sink到mysql中。

  假设有个需求,实现一个统计每5分钟最高购买数的商品。

  使用maven创建一个工程,具体步骤可以参考上边博文。然后创建一个数据库表,用于存储最终的结果集。语句如下:

CREATE TABLE `itembuycount` (
`id` mediumint NOT NULL auto_increment,
`itemId` bigint(255) NOT NULL,
`buyCount` bigint(11) DEFAULT NULL,
`createDate` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci

  创建一个表对应的pojo类文件UserAction。里边主要是用户id,商品id,用户的行为,pv用户浏览,buy用户购买,cart加购物车,fav加入收藏。

package myflinktopn.pojo;

/**
* @author huangqingshi
* @Date 2019-12-13
*/
public class UserAction { public long userId; //用户id
public long itemId; //商品id
public int categoryId; //商品分类id
public String behavior; //用户行为(pv, buy, cart, fav)
public long timestamp; //操作时间戳 public long getUserId() {
return userId;
} public void setUserId(long userId) {
this.userId = userId;
} public long getItemId() {
return itemId;
} public void setItemId(long itemId) {
this.itemId = itemId;
} public int getCategoryId() {
return categoryId;
} public void setCategoryId(int categoryId) {
this.categoryId = categoryId;
} public String getBehavior() {
return behavior;
} public void setBehavior(String behavior) {
this.behavior = behavior;
} public long getTimestamp() {
return timestamp;
} public void setTimestamp(long timestamp) {
this.timestamp = timestamp;
}
}

  接下来创建一个kafka主题,存储发送和接受数据使用。

./bin/kafka-topics.sh --create --zookeeper localhost: --replication-factor  --partitions  --topic USER_ACTION

  kafka的主题创建好了之后,写一个程序往kafka里边写数据,一秒写一条。

package myflinktopn.kafka;

import com.alibaba.fastjson.JSON;
import myflinktopn.pojo.UserAction;
import org.apache.commons.lang3.RandomUtils;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerRecord; import java.util.Arrays;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.TimeUnit; /**
* @author huangqingshi
* @Date 2019-12-07
*/
public class KafkaWriter { //本地的kafka机器列表
public static final String BROKER_LIST = "localhost:9092";
//kafka的topic
public static final String TOPIC_USER_ACTION = "USER_ACTION";
//key序列化的方式,采用字符串的形式
public static final String KEY_SERIALIZER = "org.apache.kafka.common.serialization.StringSerializer";
//value的序列化的方式
public static final String VALUE_SERIALIZER = "org.apache.kafka.common.serialization.StringSerializer";
//用户的行为列表
public static final List<String> userBehaviors = Arrays.asList("pv", "buy", "cart", "fav"); public static void writeToKafka() throws Exception{
Properties props = new Properties();
props.put("bootstrap.servers", BROKER_LIST);
props.put("key.serializer", KEY_SERIALIZER);
props.put("value.serializer", VALUE_SERIALIZER); KafkaProducer<String, String> producer = new KafkaProducer<>(props); UserAction userAction = new UserAction();
userAction.setUserId(RandomUtils.nextLong(1, 100));
userAction.setItemId(RandomUtils.nextLong(1, 1000));
userAction.setCategoryId(RandomUtils.nextInt(1, 30));
userAction.setBehavior(userBehaviors.get(RandomUtils.nextInt(0, 3)));
userAction.setTimestamp(System.currentTimeMillis()); //转换成JSON
String userActionJson = JSON.toJSONString(userAction); //包装成kafka发送的记录
ProducerRecord<String, String> record = new ProducerRecord<String, String>(TOPIC_USER_ACTION, null,
null, userActionJson);
//发送到缓存
producer.send(record);
System.out.println("向kafka发送数据:" + userActionJson);
//立即发送
producer.flush(); } public static void main(String[] args) {
while(true) {
try {
//每1秒写一条数据
TimeUnit.SECONDS.sleep(1);
writeToKafka();
} catch (Exception e) {
e.printStackTrace();
} }
} }

  接下来还是创建数据库的连接工具类。

package myflinktopn.db;

import com.alibaba.druid.pool.DruidDataSource;

import java.sql.Connection;

/**
* @author huangqingshi
* @Date 2019-12-07
*/
public class DbUtils { private static DruidDataSource dataSource; public static Connection getConnection() throws Exception {
dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/testdb");
dataSource.setUsername("root");
dataSource.setPassword("root");
//设置初始化连接数,最大连接数,最小闲置数
dataSource.setInitialSize(10);
dataSource.setMaxActive(50);
dataSource.setMinIdle(5);
//返回连接
return dataSource.getConnection();
} }

  接下来写sink到数据库的MySqlSink类,用于将结果接数据保存到数据库。

package myflinktopn.sink;

import myflinktopn.TopNJob;
import myflinktopn.db.DbUtils;
import myflinktopn.pojo.UserAction;
import org.apache.flink.configuration.Configuration;
import org.apache.flink.streaming.api.functions.sink.RichSinkFunction; import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.Timestamp;
import java.util.List; /**
* @author huangqingshi
* @Date 2019-12-07
*/
public class MySqlSink extends RichSinkFunction<List<TopNJob.ItemBuyCount>> { private PreparedStatement ps;
private Connection connection; @Override
public void open(Configuration parameters) throws Exception {
super.open(parameters);
//获取数据库连接,准备写入数据库
connection = DbUtils.getConnection();
String sql = "insert into itembuycount(itemId, buyCount, createDate) values (?, ?, ?); ";
ps = connection.prepareStatement(sql);
System.out.println("-------------open------------");
} @Override
public void close() throws Exception {
super.close();
//关闭并释放资源
if(connection != null) {
connection.close();
} if(ps != null) {
ps.close();
}
System.out.println("-------------close------------");
} @Override
public void invoke(List<TopNJob.ItemBuyCount> topNItems, Context context) throws Exception {
for(TopNJob.ItemBuyCount itemBuyCount : topNItems) {
ps.setLong(1, itemBuyCount.itemId);
ps.setLong(2, itemBuyCount.buyCount);
ps.setTimestamp(3, new Timestamp(itemBuyCount.windowEnd));
ps.addBatch();
} //一次性写入
int[] count = ps.executeBatch();
System.out.println("-------------invoke------------");
System.out.println("成功写入Mysql数量:" + count.length); }
}

  接下来咱们看一下实现TopNJob的全部代码,然后再继续分析下里边的细节。

package myflinktopn;

import com.alibaba.fastjson.JSONObject;
import myflinktopn.kafka.KafkaWriter;
import myflinktopn.pojo.UserAction;
import myflinktopn.sink.MySqlSink;
import org.apache.flink.api.common.functions.AggregateFunction;
import org.apache.flink.api.common.functions.FilterFunction;
import org.apache.flink.api.common.serialization.SimpleStringSchema;
import org.apache.flink.api.common.state.ListState;
import org.apache.flink.api.common.state.ListStateDescriptor;
import org.apache.flink.api.java.tuple.Tuple;
import org.apache.flink.api.java.tuple.Tuple1;
import org.apache.flink.configuration.Configuration;
import org.apache.flink.streaming.api.TimeCharacteristic;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.functions.KeyedProcessFunction;
import org.apache.flink.streaming.api.functions.timestamps.BoundedOutOfOrdernessTimestampExtractor;
import org.apache.flink.streaming.api.functions.windowing.WindowFunction;
import org.apache.flink.streaming.api.windowing.time.Time;
import org.apache.flink.streaming.api.windowing.windows.TimeWindow;
import org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumer010;
import org.apache.flink.util.Collector; import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Properties; /**
* @author huangqingshi
* @Date 2019-12-13
*/
public class TopNJob { //最对延迟到达的时间
public static final long MAX_EVENT_DELAY = 10L; public static void main(String[] args) throws Exception {
//构建流执行环境
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
//设置并行度1,方便打印
env.setParallelism(1); /** ProcessingTime:事件被处理的时间。也就是由机器的系统时间来决定。
EventTime:事件发生的时间。一般就是数据本身携带的时间。
*/
//设置下eventTime,默认为processTime即系统处理时间,我们需要统计一小时内的数据,也就是数据带的时间eventTime
env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime); //kafka
Properties prop = new Properties();
prop.put("bootstrap.servers", KafkaWriter.BROKER_LIST);
prop.put("zookeeper.connect", "localhost:2181");
prop.put("group.id", KafkaWriter.TOPIC_USER_ACTION);
prop.put("key.serializer", KafkaWriter.KEY_SERIALIZER);
prop.put("value.serializer", KafkaWriter.VALUE_SERIALIZER);
prop.put("auto.offset.reset", "latest"); DataStreamSource<String> dataStreamSource = env.addSource(new FlinkKafkaConsumer010<>(
KafkaWriter.TOPIC_USER_ACTION,
new SimpleStringSchema(),
prop
)); //从kafka里读取数据,转换成UserAction对象
DataStream<UserAction> dataStream = dataStreamSource.map(value -> JSONObject.parseObject(value, UserAction.class)); //将乱序的数据进行抽取出来,设置watermark,数据如果晚到10秒的会被丢弃
DataStream<UserAction> timedData = dataStream.assignTimestampsAndWatermarks(new UserActionTSExtractor()); //为了统计5分钟购买的最多的,所以我们需要过滤出购买的行为
DataStream<UserAction> filterData = timedData.filter(new FilterFunction<UserAction>() {
@Override
public boolean filter(UserAction userAction) throws Exception {
return userAction.getBehavior().contains("buy");
}
}); //窗口统计点击量 滑动的窗口 5分钟一次 统计一小时最高的 比如 [09:00, 10:00), [09:05, 10:05), [09:10, 10:10)…
DataStream<ItemBuyCount> windowedData = filterData
.keyBy("itemId")
.timeWindow(Time.minutes(60L), Time.minutes(5L))
.aggregate(new CountAgg(), new WindowResultFunciton()); //Top N 计算最热门的商品
DataStream<List<ItemBuyCount>> topItems = windowedData
.keyBy("windowEnd")
//点击前3的商品
.process(new TopNHotItems(3)); topItems.addSink(new MySqlSink());
//topItems.print();
env.execute("Top N Job");
} /**
* 用于行为时间戳抽取器,最多十秒延迟,也就是晚到10秒的数据会被丢弃掉
*/
public static class UserActionTSExtractor extends BoundedOutOfOrdernessTimestampExtractor<UserAction> { public UserActionTSExtractor() {
super(Time.seconds(MAX_EVENT_DELAY));
} @Override
public long extractTimestamp(UserAction userAction) {
return userAction.getTimestamp();
}
} /**
* 商品购买量(窗口操作的输出类型)
*/
public static class ItemBuyCount {
public long itemId; //商品ID;
public long windowEnd; //窗口结束时间戳
public long buyCount; //购买数量 public static ItemBuyCount of(long itemId, long windowEnd, long buyCount) {
ItemBuyCount itemBuyCount = new ItemBuyCount();
itemBuyCount.itemId = itemId;
itemBuyCount.windowEnd = windowEnd;
itemBuyCount.buyCount = buyCount;
return itemBuyCount;
}
} /**
*
* COUNT 聚合函数实现,每出现一条记录加一。AggregateFunction<输入,汇总,输出>
*/
public static class CountAgg implements AggregateFunction<UserAction, Long, Long> { @Override
public Long createAccumulator() {
return 0L;
} @Override
public Long add(UserAction userAction, Long acc) {
return acc + 1;
} @Override
public Long getResult(Long acc) {
return acc;
} @Override
public Long merge(Long acc1, Long acc2) {
return acc1 + acc2;
}
} /**
* 用于输出结果的窗口WindowFunction<输入,输出,键,窗口>
*/
public static class WindowResultFunciton implements WindowFunction<Long, ItemBuyCount, Tuple, TimeWindow> { @Override
public void apply(
Tuple key, //窗口主键即itemId
TimeWindow window, //窗口
Iterable<Long> aggregationResult, //集合函数的结果,即count的值
Collector<ItemBuyCount> collector //输出类型collector
) throws Exception { Long itemId = ((Tuple1<Long>) key).f0;
Long count =aggregationResult.iterator().next();
collector.collect(ItemBuyCount.of(itemId, window.getEnd(), count)); }
} /**
* 求某个窗口中前N名的热门点击商品,key为窗口时间戳,输出为Top N 的结果字符串
*/
public static class TopNHotItems extends KeyedProcessFunction<Tuple, ItemBuyCount, List<ItemBuyCount>> { private final int topSize; public TopNHotItems(int topSize) {
this.topSize = topSize;
} //用于存储商品与购买数的状态,待收齐同一个窗口的数据后,再触发 Top N 计算
private ListState<ItemBuyCount> itemState; @Override
public void open(Configuration parameters) throws Exception {
super.open(parameters);
//状态注册
ListStateDescriptor<ItemBuyCount> itemViewStateDesc = new ListStateDescriptor<ItemBuyCount>(
"itemState-state", ItemBuyCount.class
);
itemState = getRuntimeContext().getListState(itemViewStateDesc);
} @Override
public void processElement(
ItemBuyCount input,
Context context,
Collector<List<ItemBuyCount>> collector
) throws Exception {
//每条数据都保存到状态
itemState.add(input);
//注册 windowEnd+1 的 EventTime Timer, 当触发时,说明收集好了所有 windowEnd的商品数据
context.timerService().registerEventTimeTimer(input.windowEnd + 1);
} @Override
public void onTimer(long timestamp, OnTimerContext ctx, Collector<List<ItemBuyCount>> out) throws Exception {
//获取收集到的所有商品点击量
List<ItemBuyCount> allItems = new ArrayList<ItemBuyCount>();
for(ItemBuyCount item : itemState.get()) {
allItems.add(item);
}
//提前清除状态中的数据,释放空间
itemState.clear();
//按照点击量从大到小排序
allItems.sort(new Comparator<ItemBuyCount>() {
@Override
public int compare(ItemBuyCount o1, ItemBuyCount o2) {
return (int) (o2.buyCount - o1.buyCount);
}
}); List<ItemBuyCount> itemBuyCounts = new ArrayList<>();
//将排名信息格式化成String,方便打印
StringBuilder result = new StringBuilder();
result.append("========================================\n");
result.append("时间:").append(new Timestamp(timestamp-1)).append("\n");
for (int i=0;i<topSize;i++) {
ItemBuyCount currentItem = allItems.get(i);
// No1: 商品ID=12224 购买量=2
result.append("No").append(i).append(":")
.append(" 商品ID=").append(currentItem.itemId)
.append(" 购买量=").append(currentItem.buyCount)
.append("\n");
itemBuyCounts.add(currentItem);
}
result.append("====================================\n\n"); out.collect(itemBuyCounts); }
} }

  以上里边的步骤:

  1. 构建流执行的环境。

  2. 设置并行度为1,为了方便下边打印。但是实际上可以不用设置并行度。

  3. 设置流时间的特性,即EventTime。流时间一共有三种时间:

    1)ProcessingTime时间,即算子处理的时间,默认flink的时间特性。

    2)EventTime,事件处理时间,也就是程序所带的时间戳。此例子将使用由业务产生的时间戳作为时间戳。

    3)IntestionTime,即到达flink的时间。

  4. 连接kafka的属性读取数据。将kafka的数据转换成pojo对象。

  5. 将乱序的数据使用BoundedOutOfOrdernessTimestampExtractor进行时间戳抽取和设置watermark。

  6. 进行数据过滤,将useAction为buy的数据进行过滤出来,其他的直接丢弃。

  7. 采用滑动窗口的方式对数据进行汇总并返回指定类型的数据。一小时为一个窗口,每五分钟往后滑动一下。数据的形式为[9:00 10:00), [9:05 10:05), [9:10 10:10)依次类推。

  8. 最后是将数据排序,然后获取topN的数据。

  接下来咱们再针对里边的细节看一下,如果数据是生序的数据,则采用AscendingTimestampExtractor进行时间戳抽取,无序可以采用上边5条说的类。有序的只需要进行时间戳抽取,抽取的时间戳就认为是整个的时间,也就是watermark时间,可以理解为全局时间。无序抽取的时候设置一个最晚到达的时间。举个例子,如果一条数据时间为9:04:55, 到达的时间为9:05:05,这样这条数据还是属于窗口[9:00 9:05:01)里边的,而不是[9:05:01 9:10:01)。

  这个聚合方法aggregate(new CountAgg(), new WindowResultFunciton())里边声明了两个类,一个是聚合功能类,一个是结果窗口功能类。也就是使用第一个类将数据进行汇聚,第二个类将数据进行合并汇总并且输出自己定义的窗口结果集ItemBuyCount。

  最后就是topN功能,这个类实现了KeyedProcessFunction,里边有三个方法 open, processElement, onTimer, 里边还有一个ListState<ItemBuyCount>,用于收集和记录状态信息,保证数据exactly-once语义。

  open方法用于进行状态注册。

  processElement把数据状态进行添加到list里边,然后注册时间时间windowEnd+1,即数据超过了windowEnd的时候就会触发onTimer时间。

  onTimer用于把五分钟收集好的数据进行处理,从数据状态记录中把数据拿出来,然后清理数据状态,释放空间。然后将拿出来的数据进行排序,最后整理成sink所需要的结果集。

  最后就把数据进行sink,保存到数据库。

  下面看一下数据运行的结果,kafkaWriter打印的日志。

向kafka发送数据:{"behavior":"pv","categoryId":7,"itemId":19,"timestamp":1576325365016,"userId":56}

  执行TopNJob,MysqlSink执行的日志打印。

-------------invoke------------
成功写入Mysql数量:3
[myflinktopn.TopNJob$ItemBuyCount@611553e4, myflinktopn.TopNJob$ItemBuyCount@19739780, myflinktopn.TopNJob$ItemBuyCount@a76f1f0]
-------------invoke------------
成功写入Mysql数量:3
[myflinktopn.TopNJob$ItemBuyCount@3db32bd3, myflinktopn.TopNJob$ItemBuyCount@10f855ae, myflinktopn.TopNJob$ItemBuyCount@6a022d0b]
-------------invoke------------
成功写入Mysql数量:3
[myflinktopn.TopNJob$ItemBuyCount@4ae16ab8, myflinktopn.TopNJob$ItemBuyCount@6a8a29a3, myflinktopn.TopNJob$ItemBuyCount@2326adfb]

  写入到数据库的记录如下:

   写入的时间都在同一个时刻,获取前三条购买最多的,所以三条写入的时间都一样的。

  还有在运行的时候要注意kafkaWriter要多谢一些,因为要收集5分钟的数据,所以至少得跑5分钟。

  最后我把代码放到git上了,可以进行访问:https://github.com/stonehqs/flink-topn ,有不对的地方,欢迎指正。

  

使用flink实现一个topN的程序的更多相关文章

  1. Flink 另外一个分布式流式和批量数据处理的开源平台

    Apache Flink是一个分布式流式和批量数据处理的开源平台. Flink的核心是一个流式数据流动引擎,它为数据流上面的分布式计算提供数据分发.通讯.容错.Flink包括几个使用 Flink引擎创 ...

  2. 使用flink实现一个简单的wordcount

    使用flink实现一个简单的wordcount 一.背景 二.需求 三.前置条件 1.jdk版本要求 2.maven版本要求 四.实现步骤 1.创建 flink 项目 2.编写程序步骤 1.创建Str ...

  3. 手把手教你写一个RN小程序!

    时间过得真快,眨眼已经快3年了! 1.我的第一个App 还记得我14年初写的第一个iOS小程序,当时是给别人写的一个单机的相册,也是我开发的第一个完整的app,虽然功能挺少,但是耐不住心中的激动啊,现 ...

  4. 用c-free 5写一个入门的程序

    本文记录了在windows系统中使用C-FREE 5新建一个Hello HoverTree程序的步骤. 安装好C-Free 5之后,打开.新建一个工程: 附C-Free 5下载:http://hove ...

  5. 字符串混淆技术应用 设计一个字符串混淆程序 可混淆.NET程序集中的字符串

    关于字符串的研究,目前已经有两篇. 原理篇:字符串混淆技术在.NET程序保护中的应用及如何解密被混淆的字符串  实践篇:字符串反混淆实战 Dotfuscator 4.9 字符串加密技术应对策略 今天来 ...

  6. 第一个C语言程序

    从第一个C语言程序了解C语言 了解关键字 了解函数 注释 C语言的执行流程 标识符 C语言的学习重难点 从第一个C语言程序了解C语言 上图是一个在控制台上显示“Hello, World!”的C语言源代 ...

  7. 第六章第一个linux个程序:统计单词个数

    第六章第一个linux个程序:统计单词个数 从本章就开始激动人心的时刻——实战,去慢慢揭开linux神秘的面纱.本章的实例是统计一片文章或者一段文字中的单词个数.  第 1 步:建立 Linu x 驱 ...

  8. fir.im Weekly - 如何做一个出色的程序员

    做一个出色的程序员,困难而高尚.本期 fir.im Weekly 精选了一些实用的 iOS,Android 开发工具和源码分享,还有一些关于程序员的成长 Tips 和有意思有质量的线下活动~ How ...

  9. Java基础-接口中国特色社会主义的体制中有这样的现象:地方省政府要坚持党的领导和按 照国务院的指示进行安全生产。请编写一个java应用程序描述上述的体制现象。 要求如下: (1)该应用程序中有一个“党中央”接口:CentralPartyCommittee,该接口中 有个“坚持党的领导”方法:void partyLeader() (2)该应用程序中有一个“国务院”抽象类:StateCouncil,

    36.中国特色社会主义的体制中有这样的现象:地方省政府要坚持党的领导和按 照国务院的指示进行安全生产.请编写一个java应用程序描述上述的体制现象. 要求如下: (1)该应用程序中有一个“党中央”接口 ...

随机推荐

  1. 一个excel(20M)就能干趴你的poi,你信吗?

    自从上一篇:一个普通类就能干趴你的springboot,你信吗?后,很巧的是这次又发现一个问题,所以有了这篇文章,还是想沿用上篇的”流水帐“的方式查找问题和解决问题.这篇文章主要是因为使用POI导入一 ...

  2. fpm打包神奇rpm包升级python2.7.16

    fpm打包神器参考文档:https://www.cnblogs.com/flintlovesam/p/6594635.html FPM的安装:安装ruby环境和gem命令: yum -y instal ...

  3. C++图像加Lidar点云转写rosbag

    近期需要处理一批Lidar+image的数据,拿到的是其他格式,但要转存成rosbag使用,参考部分网上做法,完成并记录. 1.Lidar处理 主要是将Lidar点云信息按点转为pcl::PointX ...

  4. 自学python的高效学习方法【python秘籍】

    随着互联网的发展,数据科学概念的普及,Python火得一塌糊涂,为此很多小伙伴想学这门语言,苦于没有正确的学习方法,大部分都放弃了,所以我想总结下经验来帮助大家高效学完python技术!第一.首先学习 ...

  5. lqb 基础练习 十进制转十六进制

    基础练习 十进制转十六进制 时间限制:1.0s   内存限制:512.0MB     问题描述 十六进制数是在程序设计时经常要使用到的一种整数的表示方式.它有0,1,2,3,4,5,6,7,8,9,A ...

  6. ArcGIS API For Javascript :如何制作地图切换器

    大部分情况下我们开发会使用原生的地图切换器,由于每个项目的页面风格不同,业务场景不同,因此需要做一些样式不同的地图切换器. 首先可以照猫画虎,自己照着地图切换器的样式抄一个,或者看看主流的地图切换器都 ...

  7. 力扣(LeetCode)计数质数 个人题解

    统计所有小于非负整数 n 的质数的数量. 示例: 输入: 10 输出: 4 解释: 小于 10 的质数一共有 4 个, 它们是 2, 3, 5, 7 . 一般方法,也就是一般人都会用的,将数从2到它本 ...

  8. 力扣(LeetCode)三个数的最大乘积 个人题解

    给定一个整型数组,在数组中找出由三个数组成的最大乘积,并输出这个乘积. 示例 1: 输入: [1,2,3] 输出: 6 示例 2: 输入: [1,2,3,4] 输出: 24 注意: 给定的整型数组长度 ...

  9. 学习记录:《C++设计模式——李建忠主讲》4.“单一职责”模式

    单一职责模式:在软件组件的设计中,如果责任划分的不清晰,使用继承得到的结果往往是随着需求的变化,子类急剧膨胀,同时充斥着重复代码,这时候的关键是划清责任. 典型模式:装饰模式(Decorator).桥 ...

  10. k8s 获取 Pod ip 添加到环境变量

    0x00 事件 有一个需要将 Pod 自身的 ip 地址添加到环境变量的需求,可以在 yaml 文件的 env 中这样设置: env: - name: POD_OWN_IP_ADDRESS value ...