前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Flink DataStream维度表Join的简单方案

Flink DataStream维度表Join的简单方案

作者头像
王知无-import_bigdata
发布2019-12-05 11:04:20
2.1K0
发布2019-12-05 11:04:20
举报

在编写基于Flink的ETL程序时,我们经常需要用维度数据丰富我们接入的流式数据,如通过商品ID获得商品名称、通过商品分类ID获得分类名称等等。而维度表基本都位于外部存储,换句话说,就是要解决一个无界的流式表与一个有界的码表或半静态表做join操作的问题。

一般情况下的首选方案是Flink内置的异步I/O机制,必要时还得配合使用高效的缓存(如Guava提供的LoadingCache)减少对外部数据源的请求压力。由于今天时间紧张,所以不深入谈它的原理和用法了,之后会再提。看官如果想了解的话,可以先参考官方文档和FLIP-12给出的设计细节。

但是,异步I/O对于那种变化缓慢并且规模不大的维度数据,就显得有些杀鸡用牛刀了。我们完全可以自己做个轻量级的实现。下面举出一个示例,它从订单日志中取出站点ID、城市ID,然后从存储在MySQL的维度表中获取站点名和城市名,并写回订单日志。

代码语言:javascript
复制
  public static final class MapWithSiteInfoFunc
    extends RichMapFunction<String, String> {
    private static final Logger LOGGER = LoggerFactory.getLogger(MapWithSiteInfoFunc.class);
    private static final long serialVersionUID = 1L;

    private transient ScheduledExecutorService dbScheduler;
    private Map<Integer, SiteAndCityInfo> siteInfoCache;

    @Override
    public void open(Configuration parameters) throws Exception {
      super.open(parameters);
      siteInfoCache = new HashMap<>(1024);

      dbScheduler = new ScheduledThreadPoolExecutor(1, r -> {
        Thread thread = new Thread(r, "site-info-update-thread");
        thread.setUncaughtExceptionHandler((t, e) -> {
          LOGGER.error("Thread " + t + " got uncaught exception: " + e);
        });
        return thread;
      });

      dbScheduler.scheduleWithFixedDelay(() -> {
        try {
          QueryRunner queryRunner = new QueryRunner(JdbcUtil.getDataSource());
          List<Map<String, Object>> info = queryRunner.query(SITE_INFO_QUERY_SQL, new MapListHandler());

          for (Map<String, Object> item : info) {
            siteInfoCache.put((int) item.get("site_id"), new SiteAndCityInfo(
              (int) item.get("site_id"),
              (String) item.getOrDefault("site_name", ""),
              (long) item.get("city_id"),
              (String) item.getOrDefault("city_name", "")
            ));
          }

          LOGGER.info("Fetched {} site info records, {} records in cache", info.size(), siteInfoCache.size());
        } catch (Exception e) {
          LOGGER.error("Exception occurred when querying: " + e);
        }
      }, 0, 10 * 60, TimeUnit.SECONDS);
    }

    @Override
    public String map(String value) throws Exception {
      JSONObject json = JSON.parseObject(value);
      int siteId = json.getInteger("site_id");

      String siteName = "", cityName = "";
      SiteAndCityInfo info = siteInfoCache.getOrDefault(siteId, null);
      if (info != null) {
        siteName = info.getSiteName();
        cityName = info.getCityName();
      }

      json.put("site_name", siteName);
      json.put("city_name", cityName);
      return json.toJSONString();
    }

    @Override
    public void close() throws Exception {
      siteInfoCache.clear();
      ExecutorUtils.gracefulShutdown(10, TimeUnit.SECONDS, dbScheduler);
      JdbcUtil.close();

      super.close();
    }

    private static final String SITE_INFO_QUERY_SQL = "...";
  }

这段代码的思路很直接:用一个RichMapFunction封装整个join过程,用一个单线程的调度线程池每隔10分钟请求MySQL,拉取想要的维度表数据存入HashMap,再根据日志中的ID查HashMap就完事了。为了安全,在RichMapFunction的close()方法里要记得关闭线程池和连接。

上述代码中的QueryRunner和MapListHandler来自Apache Commons框架里的JDBC工具DBUtils。JdbcUtil中则封装了MySQL连接的参数与DBCP2里的基本连接池BasicDataSource,很简单,看官可以自行实现。

本文来源:https://www.jianshu.com/p/21f60a37b83a

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-11-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 大数据技术与架构 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
云数据库 SQL Server
腾讯云数据库 SQL Server (TencentDB for SQL Server)是业界最常用的商用数据库之一,对基于 Windows 架构的应用程序具有完美的支持。TencentDB for SQL Server 拥有微软正版授权,可持续为用户提供最新的功能,避免未授权使用软件的风险。具有即开即用、稳定可靠、安全运行、弹性扩缩等特点。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档