前言:最近项目中要用flink同步上游数据,临时突击学习了java版本的flink使用,本文介绍一些在同步数据中遇到的一些问题,有些思路是本人原创,在查找了很多资料后做出的选择,如果对您有帮助,感谢点赞,一键三连。
flink 介绍
Flink是一个框架和分布式处理引擎,用于对无限制和有限制的数据留进行有状态的计算。Flink被设计为可在所有常见的集群环境中运行,以内存速度和任何规模执行计算。
导入kafka-flink依赖
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-connector-kafka_2.11</artifactId>
<version>1.13.2</version>
</dependency>
核心代码
代码部分是集成了springboot的实现类,里面介绍了连接kafka的流程
@Component
@ConditionalOnProperty(name = "mysql-status", havingValue = "true", matchIfMissing = false)
public class MysqlKafkaSource {
private static final Logger log = LoggerFactory.getLogger(MysqlKafkaSource.class);
@Value("${spring.kafka.bootstrap-servers}")
private String kafkaServer;
@Value("${customer.flink.topic}")
private String topic;
@Autowired
private ApplicationContext applicationContext;
@Resource
private RedisTemplate redisTemplate;
/**
* 执行方法
*
* @throws Exception 异常
*/
@PostConstruct
public void execute() throws Exception {
final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setParallelism(1);
Properties properties = new Properties();
properties.setProperty("topic", topic);
properties.setProperty("bootstrap.servers", kafkaServer);
properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, ObjectDeserializer.class.getName());
DataStream<String> messageStream = env.addSource(new FlinkKafkaConsumer<String>(properties.getProperty("topic"),
new SimpleStringSchema(), properties));
//对数据做ETL,将数据封装成Bean
SingleOutputStreamOperator<ResultEngineInfo> dataStreamSource = messageStream.map(new MapFunction<String, ResultEngineInfo>() {
@Override
public ResultEngineInfo map(String value) throws Exception {
ResultEngineInfo resultEngineInfo = new ResultEngineInfo();
EngineInfo result = JSONUtil.toBean(value, EngineInfo.class);
// 处理数据
return resultEngineInfo;
}
});
// 判断redis里面的数据是否等齐了,如果没等齐则入redis,同时不入mysql
Map key = new HashMap(32);
dataStreamSource.filter(item -> {
if (Objects.nonNull(RedisUtil.getJedisClient().hget("lid", item.getUuid()))) {
return true;
} else {
key.put(item.getUuid(), item.toString());
return false;
}
}).returns(ResultEngineInfo.class);
redisTemplate.opsForHash().putAll("key", key);
//写入数据库
dataStreamSource.addSink((SinkFunction) applicationContext.getBean("resultSink"));
//启动任务
new Thread(() -> {
try {
env.execute("job");
} catch (Exception e) {
log.error(e.toString(), e);
}
}).start();
}
}
重点介绍遇到的问题,在flink流里面进行数据处理的时候,流里面只能使用静态的方法,而我的业务是要在流里面判断redis里面是否有这个数据,但是redisTemplate不能写在流里面,最后想到了处理方式就是使用工具类连接redis,不用redisTemplate进行连接。
同时我这里需要处理的业务是要连接mysql数据库查询业务表对应的信息,也不能引用service,需要采用Jdbc工具类进行连接,以下是部分代码。
以下是redis连接工具类
public class RedisUtil {
private static JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
private static JedisPool jedisPool = new JedisPool(jedisPoolConfig, "127.0.0.1");
// 直接得到一个 Redis 的连接
public static Jedis getJedisClient(){
return jedisPool.getResource();
}
public static void main(String[] args) {
RedisUtil.getJedisClient().hset("key","field","value");
System.out.println(RedisUtil.getJedisClient().exists("key"));
System.out.println(RedisUtil.getJedisClient().hkeys("key"));
}
}
public class JDBCUtil {
//1.加载驱动
static{
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//2.获取连接
public static Connection getConnection(){
Connection conn = null;
try {
conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/test", "root", "123456");
} catch (SQLException e) {
e.printStackTrace();
}
return conn;
}
//3.关闭连接
public static void close(Connection conn, Statement st, ResultSet rs){
//关闭连接
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//关闭statement
if(st != null){
try {
st.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//关闭结果集
if(rs != null){
try {
rs.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
//-------------------------------封装sql操作------------------------------
//查询返回List集合
public static <T> List<T> getList(Class<T> cls, String sql, Object... obj){
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try{
//1.获取连接
conn = getConnection();
//2.获取预处理对象
ps = conn.prepareStatement(sql);
//循环参数,如果没有就不走这里
for (int i = 1; i <= obj.length; i++) {
//注意:数组下标从0开始,预处理参数设置从1开始
ps.setObject(i, obj[i-1]);
}
//3.执行SQL语句
System.out.println(sql);
rs = ps.executeQuery();
//4.遍历结果集
//遍历之前准备:因为封装不知道未来会查询多少列,所以我们需要指定有多少列
ResultSetMetaData date = rs.getMetaData();//获取ResultSet对象的列编号、类型和属性
int column = date.getColumnCount();//获取列数
Field[] fields = cls.getDeclaredFields();//获取本类所有的属性
//创建一个list集合对象来存储查询数据
List<T> list = new ArrayList<T>();
//开始遍历结果集
while(rs.next()){
//创建类类型实例
T t = cls.newInstance();
for(int i = 1; i <= column; i++){
Object value = rs.getObject(i);//每一列的值
/**
*String columnName = date.getColumnName(i);//获取每一列名称
* 关于获取每一列名称,如果列取了别名的话,则不能用上面的方法取列的名称
* 用下面的方法
*/
String columnName = date.getColumnLabel(i);//获取每一列名称(别名)
//遍历所有属性对象
for (Field field : fields) {
//获取属性名
String name = field.getName();
field.setAccessible(true);//打破封装,忽略对封装修饰符的检测
/*if (name.equals(columnName)) {
String string = date.getColumnTypeName(i);//获取列类型名称
//如果列类型是Date类型,转换成字符串表现形式
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
String d = sdf.format(value);
//赋值:将数据库中查询的字段赋值给对应名称的属性
field.set(t, d);
}else{
field.set(t, value);
}*/
if (name.equals(columnName)) {
BeanUtils.copyProperty(t, name, value);
break;//增加效率,避免不必要的循环
}
}
}
list.add(t);
}
return list;
//5.关闭连接
}catch(Exception e){
e.printStackTrace();
}finally{
JDBCUtil.close(conn, ps, rs);
}
return null;
}
/**
* 增加、删除、修改
* @param sql sql语句
* @param obj 参数
* @return
*/
public static boolean getDML(String sql,Object... obj){
Connection conn = null;
PreparedStatement ps = null;
try{
conn = getConnection();
ps = conn.prepareStatement(sql);
for (int i = 1; i <= obj.length; i++) {
ps.setObject(i, obj[i-1]);
}
System.out.println(sql);
int update = ps.executeUpdate();
if (update > 0) {
return true;
}
}catch(Exception e){
e.printStackTrace();
}finally{
close(conn, ps, null);
}
return false;
}
//查询返回单个对象
public static <T> T getOneObject(Class<T> cls,String sql,Object... obj){
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try{
//1.获取连接
conn = getConnection();
//2.获取预处理对象
ps = conn.prepareStatement(sql);
//循环参数,如果没有就不走这里
for (int i = 1; i <= obj.length; i++) {
//注意:数组下标从0开始,预处理参数设置从1开始
ps.setObject(i, obj[i-1]);
}
//3.执行SQL语句
System.out.println(sql);
rs = ps.executeQuery();
//4.遍历结果集
//遍历之前准备:因为封装不知道未来会查询多少列,所以我们需要指定有多少列
ResultSetMetaData date = rs.getMetaData();//获取ResultSet对象的列编号、类型和属性
int column = date.getColumnCount();//获取列数
Field[] fields = cls.getDeclaredFields();//获取本类所有的属性
//开始遍历结果集
if(rs.next()){
//创建类类型实例
T t = cls.newInstance();
for(int i = 1; i <= column; i++){
Object value = rs.getObject(i);//每一列的值
String columnName = date.getColumnName(i);//获取每一列名称
//遍历所有属性对象
for (Field field : fields) {
//获取属性名
String name = field.getName();
field.setAccessible(true);//打破封装,忽略对封装修饰符的检测
if (name.equals(columnName)) {
BeanUtils.copyProperty(t, name, value);
}
}
}
return t;
}
//5.关闭连接
}catch(Exception e){
e.printStackTrace();
}finally{
JDBCUtil.close(conn, ps, rs);
}
return null;
}
//查询总记录数
public static Integer getCount(String sql,Object... obj){
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try{
//1.获取连接
conn = getConnection();
//2.获取预处理对象
ps = conn.prepareStatement(sql);
//循环参数,如果没有就不走这里
for (int i = 1; i <= obj.length; i++) {
//注意:数组下标从0开始,预处理参数设置从1开始
ps.setObject(i, obj[i-1]);
}
//3.执行SQL语句
System.out.println(sql);
rs = ps.executeQuery();
//开始遍历结果集
if(rs.next()){
return rs.getInt(1);
}
//5.关闭连接
}catch(Exception e){
e.printStackTrace();
}finally{
JDBCUtil.close(conn, ps, rs);
}
return null;
}
}