背景:近期项目需要,引入flink,研究了下flink,步步踩坑终于可以单独运行,也可发布到集群运行,记录下踩坑点。开发环境:idea+springboot(2.3.5.RELEASSE)+kafka(2.8.1)+mysql(8.0.26)。废话不多说,直接上可执行代码。
以下代码实现了某个时间间隔,设备不上传数据,判断为离线的逻辑
一、项目application创建
/**
* flink任务提交application
*
* @author wangfenglei
*/
@SpringBootApplication(scanBasePackages = {"com.wfl.firefighting.flink","com.wfl.firefighting.util"})
public class DataAnalysisFlinkApplication {
public static void main(String[] args) {
SpringApplication.run(DataAnalysisFlinkApplication.class, args);
}
}
二、设备状态计算主体,从kafka接收数据,然后通过KeyedProcessFunction函数进行计算,然后把离线设备输出到mysql sink,更新设备状态
/**
* 从kafka读取数据,计算设备状态为离线后写入mysql
*
* @author wangfenglei
*/
@Component
@ConditionalOnProperty(name = "customer.flink.cal-device-status", havingValue = "true", matchIfMissing = false)
public class DeviceDataKafkaSource {
private static final Logger log = LoggerFactory.getLogger(CalDeviceOfflineFunction.class);
@Value("${spring.kafka.bootstrap-servers:localhost:9092}")
private String kafkaServer;
@Value("${spring.kafka.properties.sasl.jaas.config}")
private String loginConfig;
@Value("${customer.flink.cal-device-status-topic}")
private String topic;
@Autowired
private ApplicationContext applicationContext;
/**
* 执行方法
*
* @throws Exception 异常
*/
@PostConstruct
public void execute() throws Exception {
final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.enableCheckpointing(5000);
env.setParallelism(1);
Properties properties = new Properties();
//kafka的节点的IP或者hostName,多个使用逗号分隔
properties.setProperty("bootstrap.servers", kafkaServer);
//kafka的消费者的group.id
properties.setProperty("group.id", "data-nanlysis-flink-devicestatus");
//设置kafka安全认证机制为PLAIN
properties.setProperty("sasl.mechanism", "PLAIN");
//设置kafka安全认证协议为SASL_PLAINTEXT
properties.setProperty("security.protocol", "SASL_PLAINTEXT");
//设置kafka登录验证用户名和密码
properties.setProperty("sasl.jaas.config", loginConfig);
FlinkKafkaConsumer<String> myConsumer = new FlinkKafkaConsumer<>(topic, new SimpleStringSchema(), properties);
DataStream<String> stream = env.addSource(myConsumer);
stream.print().setParallelism(1);
DataStream<String> deviceStatus = stream
//进行转换只获取设备序列码
.map(data -> CommonConstant.GSON.fromJson(data, MsgData.class).getDevSn())
//按照设备序列码分组
.keyBy(new KeySelector<String, String>() {
@Override
public String getKey(String value) throws Exception {
return value;
}
})
//进行计算,判断周期内是否有新数据上传,没有则输出认为设备离线
.process((CalDeviceOfflineFunction) applicationContext.getBean("calDeviceOfflineFunction"));
//写入数据库
deviceStatus.addSink((SinkFunction) applicationContext.getBean("deviceStatusSink"));
//启动任务
new Thread(() -> {
try {
env.execute("deviceStatusFlinkJob");
} catch (Exception e) {
log.error(e.toString(), e);
}
}).start();
}
}
说明:
1、通过@ConditionalOnProperty开关形式控制程序是否执行,后续此模块可以开发多个flink执行任务,通过开关的形式提交需要的job
2、通过springboot的@PostConstruct注解,让项目application启动时,自动执行job
3、用Thread线程执行任务提交,否则application启动时会一直flink执行中
4、日志打印,需要使用slf4j,跟flink自己依赖jar包打印日志保持一致,如此在flink集群执行时可以打印日志
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
private static final Logger log = LoggerFactory.getLogger(CalDeviceOfflineFunction.class);
5、kafka连接开启了登录验证,配置见application.yml。kafka登录验证server端配置见官网资料,后续有时间写个文章记录下
三、设备离线计算
/**
* KeyedProcessFunction 为每个设备序列码维护一个state,并且会把间隔时间内(事件时间)内没有更新的设备序列码输出:
* 对于每条记录,CalDeviceOfflineFunction 修改最后的时间戳。
* 该函数还会在间隔时间内调用回调(事件时间)