flink 维表关联多种方式

目录

 

1.实时查询维表 

 2.预加载全量数据

3.LRU 缓存

4.广播变量


1.实时查询维表 

优点:维表数据实时更新,可以做到实时同步到。

缺点:访问压力大,如果失败会造成线程阻塞。

实时查询维表是指用户在Flink算子中直接访问外部数据库。这种方式可以保证数据是最新的,但是当我们流计算数据过大,会对外部系统带来巨大的访问压力,比如:连接失败,连接池满等情况,就会导致线程阻塞。task等待数据返回.

核心就是通过Map算子中建立访问外部系统的连接。核心代码如下:

  public static void main(String[] args) throws Exception {

        final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        final DataStreamSource dataStreamSource = env.socketTextStream("192.168.8.174", 9999);
        dataStreamSource.map(new Sync()).printToErr();

        env.execute("start...");
    }


    static class Sync extends RichMapFunction {

        private Connection connection = null;

        //初始化建立连接
        @Override
        public void open(Configuration parameters) throws Exception {
            super.open(parameters);
            Class.forName("com.mysql.jdbc.Driver");  //注册数据库驱动
            connection = DriverManager.getConnection("jdbc:mysql://192.168.8.181:3306/niu?characterEncoding=UTF-8", "root", "Fy123.com");
        }

        @Override
        public String map(String isa) throws Exception {
            //根据isa 查询 vin
            PreparedStatement pst = connection.prepareStatement("select vin from fy_records where isa = ?");
            pst.setString(1, isa);
            ResultSet resultSet = pst.executeQuery();
            String vin = null;
            while (resultSet.next()) {
                vin = resultSet.getString(1);
            }
            pst.close();
            return isa + "_" + vin;
        }


        //注意要关闭连接
        @Override
        public void close() throws Exception {
            super.close();
            connection.close();
        }
    }

 

 2.预加载全量数据

优点:避免频繁访问而导致连接和性能的问题

缺点:一旦维表发生更新,flink任务无法感知。不过可以通过定时来拉取维表数据.

当任务启动时,就将维表数据全部加载到内存中,然后数据在内存中进行关联,不需要直接访问外部数据库。核心代码如下:

 
    public static void main(String[] args) throws Exception {

        final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        final DataStreamSource dataStreamSource = env.socketTextStream("192.168.8.174", 9999);
        dataStreamSource.map(new WhoLoad()).printToErr();
        env.execute("start...");

    }


    static class WhoLoad extends RichMapFunction {

        private Logger logger = LoggerFactory.getLogger(WhoLoad.class);
        //缓存
        private Map cache = new HashMap();

        //每隔1分钟重新加载一次mysql数据到内存
        @Override
        public void open(Configuration parameters) throws Exception {
            super.open(parameters);
            //初始化0,等上次任务完成后,等待10秒执行本次任务  定义了一个10秒的定时器,定时执行查询数据库的方法
            Executors.newScheduledThreadPool(3).scheduleWithFixedDelay(new Runnable() {
                                                                           @Override
                                                                           public void run() {
                                                                               try {
                                                                                   load();
                                                                               } catch (ClassNotFoundException e) {
                                                                                   e.printStackTrace();
                                                                               } catch (SQLException e) {
                                                                                   e.printStackTrace();
                                                                               }
                                                                           }
                                                                       },
                    0,     //初始化时间
                    10,    //任务间隔时间
                    TimeUnit.MILLISECONDS); //时间单位
        }

        @Override
        public String map(String s) throws Exception {
            final String vin = cache.get(s);
            return "根据isa 获取到的vin : " + vin;
        }

        //加载mysql数据到内存
        private void load() throws ClassNotFoundException, SQLException {
            Class.forName("com.mysql.jdbc.Driver");  //注册数据库驱动
            Connection connection = DriverManager.getConnection("jdbc:mysql://192.168.8.181:3306/niu?characterEncoding=UTF-8", "root", "Fy123.com");
            //根据isa 查询 vin
            PreparedStatement pst = connection.prepareStatement("select isa,vin from fy_records");
            ResultSet resultSet = pst.executeQuery();
            while (resultSet.next()) {
                String isa = resultSet.getString("isa");
                String vin = resultSet.getString("vin");
                cache.put(isa, vin);
            }
            pst.close();
            connection.close();
        }

    }

 

相同key在mysql新增数据前后测试结果:

 

3.LRU 缓存

如果维表的数据比较大,无法一次性全部加载到内存中,可以使用LRU策略加载维表数据。因为热点数据会经常被使用,会常驻到我们的缓存中,这种方式会有一定的数据延迟,并且需要额外设置每条数据的失效时间

整体思路就是利用flink的RichAsyncFunction读取hbase的数据到缓存中,在关联查询时先去查缓存,如果缓存不存在,利用客户端查找hbase,然后插入到缓存中。

核心代码如下:


    org.hbase
    asynchbase
    1.8.2
  private Logger logger = LoggerFactory.getLogger(LRU.class);

    String table = "";
    Cache cache = null;
    private HBaseClient client = null;

    @Override
    public void open(Configuration parameters) throws Exception {
        super.open(parameters);
        //创建hbase客户端
        final HBaseClient hBaseClient = new HBaseClient("192.168.8.174", "7071");
        cache = CacheBuilder.newBuilder()
                //最多存储10000条
                .maximumSize(10000)
                //过期时间1分钟
                .expireAfterWrite(60, TimeUnit.SECONDS)
                .build();
    }

    @Override
    public void asyncInvoke(String input, ResultFuture resultFuture) {
        JSONObject jsonObject = JSONObject.parseObject(input);
        Integer cityId = jsonObject.getInteger("city_id");
        String userName = jsonObject.getString("user_name");
        String items = jsonObject.getString("items");
        //读缓存
        String cacheCityName = cache.getIfPresent(cityId);
        //如果缓存获取失败再从hbase获取维度数据
        if(cacheCityName != null){
            Order order = new Order();
            order.setCityId(cityId);
            order.setItems(items);
            order.setUserName(userName);
            order.setCityName(cacheCityName);
            final ArrayList orders = new ArrayList<>();
            orders.add(order);
            resultFuture.complete((scala.collection.Iterable) orders.iterator());
        }else {
            client.get(new GetRequest(table,String.valueOf(cityId))).addCallback((Callback>) ar -> {
                for (KeyValue kv : ar) {
                    String value = new String(kv.value());
                    Order order = new Order();
                    order.setCityId(cityId);
                    order.setItems(items);
                    order.setUserName(userName);
                    order.setCityName(value);
                    final ArrayList orders = new ArrayList<>();
                    orders.add(order);
                    resultFuture.complete((scala.collection.Iterable) orders.iterator());
                    cache.put(String.valueOf(cityId), value);
                }
                return null;
            });
        }


    }

4.广播变量

广播变量会讲数据在每个节点都保存一份。


    public static void main(String[] args) throws Exception {


        final ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment();

        final DataSource source = env.fromElements(1, 2, 3);
        //准备广播变量  ,广播变量数据会在每个节点保存一份
        final ArrayList list = new ArrayList<>();
        list.add("a");
        list.add("b");
        list.add("c");
        list.add("d");
        final DataSource collection = env.fromCollection(list);

        source.map(new RichMapFunction() {
             List collectionNasme = null;
            @Override
            public void open(Configuration parameters) throws Exception {
                super.open(parameters);
                //获取广播变量
                collectionNasme = getRuntimeContext().getBroadcastVariable("collectionNasme");
            }
            @Override
            public String map(Integer integer) throws Exception {
                final String s = collectionNasme.get(integer);
                return s;
            }
            //注册广播变量
        }).withBroadcastSet(collection,"collectionNasme").printToErr();


    }

 

 

 

 

 

 

你可能感兴趣的:(flink,flink)