一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第26天,点击查看活动详情。
前言
昨天,我们完成了使用taro框架进行跨端开发的系列文章。今天本来想在前端找几个方向写小Demo的,但是没有发现比较有创意和亮点的项目。
最终,我决定在4月份的最后5天给大家介绍一下我在3月份参与到新公司大数据项目的一些落地实践,以及最终实现超多数据量的数据落盘工作(粗略估计5W/秒)
现状和需要解决的问题
我现在的公司所属的行业比较传统,是煤炭行业,对于数据的收集需求较大。在较大的矿场当中,产生的数据很多,对数据的实时性和准确性有着较高的要求。因此我需要实现数据超大的吞吐量,一开始定的是最少支持2W/秒。
初始的解决方案
因为我们的业务数据较为统一,且吞吐量需求较高。我们一开始定下的落地方案就是基于kafka来实现的,相信参与大数据开发的读者都比较清楚kafka的超高吞吐量。
我们一开始的实现方案为:将一个topic中的数据分发给不同的消费者进行消费,A消费者定义自己的group.id只用来生成心跳信息。同理B消费者只用来记录数据至持久化存储
第一阶段的实现方案
我们一开始其实有一个写好的解决方案了,但是效率有点跟不上,所以我们团队就从0开始死磕kafka的数据生产和消费功能。我们第一版的实现方案很简单:试一下kafka官方的案例的瓶颈是多少
基本环境准备
- 我们第一步就是准备一个自己的单机kafka,大家可以参考这篇文章搭建:win 10 kafka搭建\
- 下一步因为我们的技术栈是Spring Boot所以我们就基于Spring Boot来整合kafka,我简单的说一下整合步骤
- 初始化一个spring boot项目。含有starter和starter-web即可
- 添加kafka依赖,例如下面所示
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
<version>2.1.0.RELEASE</version>
</dependency>
- 自己初始化而不是使用kafka初始化
我们都知道kafka有autoconfig,但是我们有时候会针对于特殊的数据生产者和消费者有特殊的配置。所以我们直接就把producer放在InitializingBean之后进行自动初始化。
步骤为\ - 取消kafka的自动装配,在application启动类中,将kafka的自动配置config去除即可
@SpringBootApplication(exclude = KafkaAutoConfiguration.class)
- 初始化代码,我只贴出初始化的位置了,大家可以按照自己的kafka进行初始化
@Data
@Component
public class KafkaProduceInit implements InitializingBean {
private KafkaProducer producer;
@Override
public void afterPropertiesSet() throws Exception {
this.setProducer(.....);
}
}
- 如何1秒发送2W+数据 我们想要实现2W/S的数据落盘,首先要实现2W+/S的发送效率,这里我们使用多线程来发送数据。多线程发送数据完全支撑起了我们的从2W/S到5W/S的数据生产工作,示例代码如下
int threadNumber = 10;
int sendNum = 5000;
// 初始化countDown参数为线程个数
final CountDownLatch cdl = new CountDownLatch(threadNumber);
// 使用线程池
//获取系统处理器个数,作为线程池数量
int nThreads = Runtime.getRuntime().availableProcessors();
ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
.setNamePrefix("demo-pool-").build();
long start = System.currentTimeMillis();
ExecutorService service = new ThreadPoolExecutor(nThreads, 200, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());
//遍历启动10个线程发送数据
for (int index = 0; index < threadNumber; index++) {
int startIndex = index;
//执行线程
service.submit(() -> {
for (int sendIndex = startIndex * sendNum; sendIndex < (startIndex + 1) * sendNum; sendIndex++) {
kafkaService.sendDemoMsg(sendIndex, startIndex);
}
cdl.countDown();
});
}
//线程启动后调用countDownLatch方法
try {
cdl.await();//需要捕获异常,当其中线程数为0时这里才会继续运行
} catch (InterruptedException e) {
e.printStackTrace();
Thread.currentThread().interrupt();
}
long end = System.currentTimeMillis();
log.info("end - start = " + (end - start) / (double) 1000);
- 我们今天先试一下单个消费者单个分区的消费效率
@KafkaListener(topics = "testTopic")
public void listenTestTopic(String message) {
log.info("testTopic" + message);
}
我们可以试了几次之后就会发现,效率其实非常低,明天我们优化kafka listener的消费逻辑,试一下分片消费的效率
结语
今天写的代码比较少,主要是给大家一个概念,如果需要大数据大吞吐量的话,大家可以考虑kafka,明天我们更新kafka的分片消费,欢迎各位多多点赞关注!