flink实战--读写Hbase

简介

         在Flink文档中,提供connector读取源数据和把处理结果存储到外部系统中。但是没有提供数据库的connector,如果要读写数据库,官网给出了异步IO(Asynchronous I/O)专门用于访问外部数据,详细可看:

https://ci.apache.org/projects/flink/flink-docs-release-1.6/dev/stream/operators/asyncio.html

还有一种方法是继承RichSourceFunction,重写里面的方法,所有的数据库flink都可以通过这两种方式进行数据的读写,这里以hbase为例进行说明。

flink读写Hbase

读取hbase,自定义source即可,自定义source参考上篇博客:https://blog.csdn.net/aA518189/article/details/88726467

写入HBase提供两种方式

  1. 第一种:实现OutputFormat接口
  2. 第二种:继承RichSinkFunction重写父类方法(推荐使用第二种方式 :自定义sink

注意事项

由于flink是一条一条的处理数据,所以我们在插入hbase的时候不能来一条flush一下,不然会给hbase造成很大的压力,而且会产生很多线程导致集群崩溃,本身亲自测试过,所以线上任务必须控制flush的频率。

做法:我们可以在open方法中定义一个变量,然后在写入hbase时比如500条flush一次,或则加入一个list,判断list的大小满足某个阈值flush一下

实现OutputFormat接口

实现方式:

我们需要自己自定义一个hbase的操作类实现OutputFormat接口,重写里面的抽象方法,也就是下面的抽象方法

public interface OutputFormat extends Serializable {
	void configure(Configuration parameters);
	void open(int taskNumber, int numTasks) throws IOException;
	void writeRecord(IT record) throws IOException;
	void close() throws IOException;
}

抽象方法说明

configure

configure方法主要用于:配置输出格式。由于输出格式是通用的,因此是无参数的,这个方法是输出格式根据配置值设置基本字段的地方,此方法总是在实例化输出格式上首先调用,但是我们不会这个方法做实际测操作。

open

用于打开输出格式的并行实例,以存储其并行实例的结果,调用此方法时,将确保配置该方法的输出格式。所以在open方法中我们会进行hbase的连接,配置,建表等操作。

writeRecord

用于将数据写入数据源,所以我们会在这个方法中调用写入hbase的API

close

这个不用说了就是关闭数据源的连接

导入依赖


 org.apache.hbase
 hbase-client
 1.2.4
 

 org.apache.hbase
 hbase-server
 1.2.4

实例

public class HBaseOutputFormat implements OutputFormat> {
    private org.apache.hadoop.conf.Configuration conf = null;
    private Connection conn = null;
    private Table table = null;
    @Override
    public void configure(Configuration parameters) {
    }
    @Override
    public void open(int taskNumber, int numTasks) throws IOException {
        HbaseUtil.setConf("ip1,ip2,ip3", "2181");
        conn = HbaseUtil.connection;
        HbaseUtil.createTable("flink_test2","info");
    }
    @Override
    public void writeRecord(Tuple5 record) throws IOException {
        Put put = new Put(Bytes.toBytes(record.f0+record.f4));
        put.addColumn(Bytes.toBytes("info"), Bytes.toBytes("uerid"), Bytes.toBytes(record.f0));
        put.addColumn(Bytes.toBytes("info"), Bytes.toBytes("behavior"), Bytes.toBytes(record.f3));
        ArrayList putList = new ArrayList<>();
        putList.add(put);
        //设置缓存1m,当达到1m时数据会自动刷到hbase
        BufferedMutatorParams params = new BufferedMutatorParams(TableName.valueOf("flink_test2"));
        params.writeBufferSize(1024 * 1024); //设置缓存的大小
        BufferedMutator mutator = conn.getBufferedMutator(params);
        mutator.mutate(putList);
        if(putList.size()>=500){
        mutator.flush();
        putList.clear();
      }
    }
    public void close() throws IOException {
        if (table != null) {
            table.close();
        }
        if (conn != null) {
            conn.close();
        }
    }
}

如何使用

val env: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment
  env.enableCheckpointing(5000)
  env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
  env.getCheckpointConfig.setCheckpointingMode(CheckpointingMode.EXACTLY_ONCE)
  val dataStream =  env.addSource()
...........
  dataStream.writeUsingOutputFormat(new HBaseOutputFormat());//写入Hbase
--------------------- 

继承RichSinkFunction重写父类方法

  使用方式和第一种方式一样,不再叙述,直接上代码:

public class HbaseSinkUtil extends RichSinkFunction {
    private Connection conn = null;
    private Table table = null;
    private HbaseUtil hbaseUtil;
    private BufferedMutatorParams params;
    private String Column_family="info";
    private String tName="event_etl";
    private BufferedMutator mutator;
    private ArrayList putList;
    private int  count;
    SimpleDateFormat df;
    @Override
    public void open(Configuration parameters) throws Exception {
        super.open(parameters);
        hbaseUtil=new HbaseUtil();
        hbaseUtil.setConf("ip", "2181");
        conn = hbaseUtil.connection;
        params = new BufferedMutatorParams(TableName.valueOf(tName));
        mutator= conn.getBufferedMutator(params);
        putList= new ArrayList<>();
        //设置缓存1m,当达到5m时数据会自动刷到hbase
        params.writeBufferSize(1024 * 1024*5); //设置缓存的大小
        count=0;
    }
    @Override
    public void invoke(String value, Context context) throws Exception {
        JSONObject jsonObject = JSON.parseObject(value);
        String user_id = jsonObject.get("user_id").toString();
        String platform = jsonObject.get("platform").toString();
        // 一个put操作一行数据,并设置rowkey名称
        Put put = new Put(Bytes.toBytes(StringUtil.reverse(String.valueOf(time_local)) +anchor_id));
        put.addColumn(Bytes.toBytes(Column_family), Bytes.toBytes("user_id"), Bytes.toBytes(user_id));
        put.addColumn(Bytes.toBytes(Column_family), Bytes.toBytes("platform"), Bytes.toBytes(platform));
        mutator.mutate(put);
        //每满2000条刷新一下数据
        if(count>=2000){
            mutator.flush();
            count=0;
        }
        count++;
    }
    @Override
    public void close() throws Exception {
        super.close();
        if (table != null) {
            table.close();
        }
        if (conn != null) {
            conn.close();
        }
        putList.clear();
    }
}

上述代码涉及的hbaseUtil类的实现参考博客:https://blog.csdn.net/aA518189/article/details/85298889

如果flink程序写入hbase的时候报如下错误:

Caused by: java.lang.ClassNotFoundException: org.apache.commons.pool2.impl.EvictionConfig
	at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
	at org.apache.flink.runtime.execution.librarycache.FlinkUserCodeClassLoaders$ChildFirstClassLoader.loadClass(FlinkUserCodeClassLoaders.java:129)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
	... 4 more

解决方案:在pom.xml文件中添加如下依赖即可:

        
             org.apache.commons
            commons-pool2
            2.0
        

扫一扫加入大数据技术交流群,了解更多大数据技术,还有免费资料等你哦

扫一扫加入大数据技术交流群,了解更多大数据技术,还有免费资料等你哦

扫一扫加入大数据技术交流群,了解更多大数据技术,还有免费资料等你哦

flink实战--读写Hbase_第1张图片

 

 

 

 

 

 

你可能感兴趣的:(flink)