datahub优雅的接入java-反射和字符串拼接实现

本文介绍了如何使用Java优雅地对接阿里云DataHub,针对原SDK的不足,通过反射和字符串拼接的方式实现了更方便的数据写入和消费。详细解释了字段获取、对象属性值自动填充的实现过程,并提到了使用中的注意事项,如字段命名规则和实体类命名规范。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

阿里云里面java对接datahub的语法和demo实在简陋, 新手刚使用的时候,真的有点惨不忍睹

下面是原sdk里面的demo

Maven Pom

<dependency>
  <groupId>com.aliyun.datahub</groupId>
  <artifactId>aliyun-sdk-datahub</artifactId>
  <version>2.19.0-public</version>
</dependency>

初始化

用户可以使用阿里云认证账号访问DataHub,并需要提供云账号AccessId和AccessKey,同时需要提供访问DataHub的服务地址。以下代码用于使用DataHub域名新建DataHubClient:

// Endpoint以Region: 华东1为例,其他Region请按实际情况填写
String endpoint = "http://dh-cn-hangzhou.aliyuncs.com";
String accessId = "<YourAccessKeyId>";
String accessKey = "<YourAccessKeySecret>";
// 创建DataHubClient实例
DatahubClient datahubClient = DatahubClientBuilder.newBuilder()
        .setDatahubConfig(
                new DatahubConfig(endpoint,
                        // 是否开启二进制传输,服务端2.12版本开始支持
                        new AliyunAccount(accessId, accessKey), true))
                        //专有云使用出错尝试将参数设置为           false
        // HttpConfig可不设置,不设置时采用默认值
        .setHttpConfig(new HttpConfig()
                .setCompressType(HttpConfig.CompressType.LZ4) // 读写数据推荐打开网络传输 LZ4压缩
                .setConnTimeout(10000))
        .build();

写入数据到DataHub

以Tuple类型Topic为例

    // 写入Tuple型数据
    public static void tupleExample(String project,String topic,int retryTimes) {
        // 获取schema
        RecordSchema recordSchema = datahubClient.getTopic(project,topic ).getRecordSchema();
        // 生成十条数据
        List<RecordEntry> recordEntries = new ArrayList<>();
        for (int i = 0; i < 10; ++i) {
            RecordEntry recordEntry = new RecordEntry();
            // 对每条数据设置额外属性,例如ip 机器名等。可以不设置额外属性,不影响数据写入
            recordEntry.addAttribute("key1", "value1");

            TupleRecordData data = new TupleRecordData(recordSchema);
            data.setField("field1", "HelloWorld");
            data.setField("field2", 1234567);
            recordEntry.setRecordData(data);
            recordEntries.add(recordEntry);
        }
        try {
            PutRecordsResult result = datahubClient.putRecords(project, topic, recordEntries);
            int i = result.getFailedRecordCount();
            if (i > 0) {
                retry(datahubClient, result.getFailedRecords(), retryTimes, project, topic);
            }
        }  catch (DatahubClientException e) {
            System.out.println("requestId:" + e.getRequestId() + "\tmessage:" + e.getErrorMessage());
        }
    }
    //重试机制
    public static void retry(DatahubClient client, List<RecordEntry> records, int retryTimes, String project, String topic) {
        boolean suc = false;
        while (retryTimes != 0) {
            retryTimes = retryTimes - 1;
            PutRecordsResult recordsResult = client.putRecords(project, topic, records);
            if (recordsResult.getFailedRecordCount() > 0) {
                retry(client,recordsResult.getFailedRecords(),retryTimes,project,topic);
            }
            suc = true;
            break;
        }
        if (!suc) {
            System.out.println("retryFailure");
        }
    }

创建订阅消费DataHub数据

//点位消费示例,并在消费过程中进行点位的提交
public static void example() {
  String shardId = "0";
  List<String> shardIds = Arrays.asList("0", "1");
  OpenSubscriptionSessionResult openSubscriptionSessionResult = datahubClient.openSubscriptionSession(Constant.projectName, Constant.topicName, subId, shardIds);
  SubscriptionOffset subscriptionOffset = openSubscriptionSessionResult.getOffsets().get(shardId);
  // 1、获取当前点位的cursor,如果当前点位已过期则获取生命周期内第一条record的cursor,未消费同样获取生命周期内第一条record的cursor
  String cursor = null;
  //sequence < 0说明未消费
  if (subscriptionOffset.getSequence() < 0) {
      // 获取生命周期内第一条record的cursor
      cursor = datahubClient.getCursor(Constant.projectName, Constant.topicName, shardId, CursorType.OLDEST).getCursor();
  } else {
      // 获取下一条记录的Cursor
      long nextSequence = subscriptionOffset.getSequence() + 1;
      try {
          //按照SEQUENCE getCursor可能报SeekOutOfRange错误,表示当前cursor的数据已过期
          cursor = datahubClient.getCursor(Constant.projectName, Constant.topicName, shardId, CursorType.SEQUENCE, nextSequence).getCursor();
      } catch (SeekOutOfRangeException e) {
          // 获取生命周期内第一条record的cursor
          cursor = datahubClient.getCursor(Constant.projectName, Constant.topicName, shardId, CursorType.OLDEST).getCursor();
      }
  }
  // 2、读取并保存点位,这里以读取Tuple数据为例,并且每1000条记录保存一次点位
  long recordCount = 0L;
  // 每次读取10条record
  int fetchNum = 10;
  while (true) {
      try {
          GetRecordsResult getRecordsResult = datahubClient.getRecords(Constant.projectName, Constant.topicName, shardId, schema, cursor, fetchNum);
          if (getRecordsResult.getRecordCount() <= 0) {
              // 无数据,sleep后读取
              Thread.sleep(1000);
              continue;
          }
          for (RecordEntry recordEntry : getRecordsResult.getRecords()) {
              //消费数据
              TupleRecordData data = (TupleRecordData) recordEntry.getRecordData();
              System.out.println("field1:" + data.getField("field1") + "\t"
                      + "field2:" + data.getField("field2"));
              // 处理数据完成后,设置点位
              ++recordCount;
              subscriptionOffset.setSequence(recordEntry.getSequence());
              subscriptionOffset.setTimestamp(recordEntry.getSystemTime());
              if (recordCount % 1000 == 0) {
                  //提交点位点位
                  Map<String, SubscriptionOffset> offsetMap = new HashMap<>();
                  offsetMap.put(shardId, subscriptionOffset);
                  datahubClient.commitSubscriptionOffset(Constant.projectName, Constant.topicName, subId, offsetMap);
                  System.out.println("commit offset successful");
              }
          }
          cursor = getRecordsResult.getNextCursor();
      } catch (SubscriptionOfflineException | SubscriptionSessionInvalidException e) {
          // 退出. Offline: 订阅下线; SubscriptionSessionInvalid: 表示订阅被其他客户端同时消费
          break;
      } catch (SubscriptionOffsetResetException e) {
          // 表示点位被重置,重新获取SubscriptionOffset信息,这里以Sequence重置为例
          // 如果以Timestamp重置,需要通过CursorType.SYSTEM_TIME获取cursor
          subscriptionOffset = datahubClient.getSubscriptionOffset(Constant.projectName, Constant.topicName, subId, shardIds).getOffsets().get(shardId);
          long nextSequence = subscriptionOffset.getSequence() + 1;
          cursor = datahubClient.getCursor(Constant.projectName, Constant.topicName, shardId, CursorType.SEQUENCE, nextSequence).getCursor();
      } catch (DatahubClientException e) {
          // TODO: 针对不同异常决定是否退出
      } catch (Exception e) {
          break;
      }
  }
}

 其实他这里面是有个逻辑的, 写这个sdk的哥们太粗心了, 好多细节没有说清楚, 关键网上关于datahub的资料少

首先这个field1字段是怎么来的

我们配置公钥私钥,项目名字,topic名字, 代码就可以自动联上阿里云datahub的仓库获取我们的配置信息,  就可以拿到我们配置的字段信息

 原理就是这样, 但是因为放数据的时候设置的为kv结构就代表我们需要不停的set数据进去,  这样的写法是很丑陋的, 最好是直接放一个对象进去, 自动获取对象的属性值.

具体的实现是这样的:   

@Service
public class PushDataHubService {

    private final static Logger logger = LoggerFactory.getLogger(PushDataHubService.class);

    @Value("${datahub.endpoint}")
    private String endpoint;
    @Value("${datahub.accessId}")
    private String accessId;
    @Value("${datahub.accessKey}")
    private String accessKey;
    @Value("${datahub.projectName}")
    private String projectName;
    @Value("${datahub.shardId}")
    private String shardId;

    @Async
    public void writeToDataHub(String topicName, Object o) {
        try {
            DatahubClient client = PushDataHubUtil.getDataHubClient(endpoint, accessId, accessKey);
            PushDataHubUtil.write(client, projectName, topicName, shardId, o);
            logger.info("数据推送datahub成功");
        } catch (Exception e) {
            System.out.println("推送数据到DataHub异常:{}," + e.getMessage() +
                    "topicName:{}" + topicName);
        }
    }
}

PushDataUtil

@Component
@Configuration
public class PushDataHubUtil {


    /**
     * 获取DataHub客户端
     */
    public static DatahubClient getDataHubClient(String endpoint, String accessId, String accessKey) {
        return DatahubClientBuilder.newBuilder()
                .setDatahubConfig(new DatahubConfig(endpoint,
                        // 是否开启二进制传输,服务端2.12版本开始支持
                        new AliyunAccount(accessId, accessKey), false))
                // 专有云使用出错尝试将参数设置为 false
                // HttpConfig可不设置,不设置时采用默认值
                .setHttpConfig(new HttpConfig().setConnTimeout(60000).setReadTimeout(30000)).build();
    }

    public static void write(DatahubClient datahubClient, String projectName, String topicName, String shardId, Object o) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {

        List<RecordEntry> recordEntries = new ArrayList<>();

        System.out.println("projectName: " + projectName + ",topicName: " + topicName);

        RecordSchema recordSchema = datahubClient.getTopic(projectName, topicName).getRecordSchema();

        RecordEntry recordEntry = new RecordEntry();

        TupleRecordData data  = new TupleRecordData(recordSchema);

        List<Field> fields = recordSchema.getFields();
        
        for (Field field : fields) {
            String name = field.getName();
            Object invoke = get(o.getClass(), name).invoke(o);
            if (invoke != null && invoke.getClass().isInstance(LocalDateTime.class)) {
                String format = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format((LocalDateTime) invoke);
                data.setField(name, format);
            } else if (field.getType().equals(BIGINT)) {
                data.setField(name, invoke);
            } else {
                data.setField(name, invoke != null ? invoke.toString() : null);
            }
        }
        
        recordEntry.setRecordData(data);
        recordEntry.setShardId(shardId);
        recordEntries.add(recordEntry);

        datahubClient.putRecords(projectName, topicName, recordEntries);
        recordEntries.clear();
    }

    public static Method get(Class<?> obj, String name) throws NoSuchMethodException {
        StringBuilder methodName = new StringBuilder("get");
        for (String s : name.split("_")) {
            StringBuilder append = methodName.append(s.substring(0, 1).toUpperCase()).append(s.substring(1));
        }
        return obj.getDeclaredMethod(methodName.toString());
    }

}

解析一下, 你也可以自己debug跑一下,  除了datahub本身的代码,剩下的主要是使用字符串拼接和反射,具体操作就是通过datahub本身的方法获取配置在远端datahub中schema里面的字段名,拼接上get,同时去掉"_" , 在进行首字母大写, 这样就是形成了我们的get方法名,通过返回获取值

    如果你要抄上面的代码的话,需要注意几点,  不然无法使用

1: datahub里面schema里面的字段名必须以 xxx_xx 形式, 就是说必须有"_",  如果没有可以修改下下面get方法的代码, 

2:第二你传入对象的实体类里面必须使用驼峰命名 比如msgInfo 这样

就是这两点, 剩下有问题的话粘贴出去自己跑跑

        

就这样, 爱你么么么么么哒  略

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值