浏览代码

Merge branch 'ljd/feature-20251229' into test

jiandong.liu 1 周之前
父节点
当前提交
e4ec4d9f4e
共有 55 个文件被更改,包括 5597 次插入54 次删除
  1. 1 0
      .gitignore
  2. 1 0
      pom.xml
  3. 1 1
      recommend-feature-client/pom.xml
  4. 49 0
      recommend-feature-client/src/main/java/com/tzld/piaoquan/recommend/feature/client/FeatureV2Client.java
  5. 9 2
      recommend-feature-client/src/main/java/com/tzld/piaoquan/recommend/feature/domain/ad/base/AdItemFeature.java
  6. 42 4
      recommend-feature-client/src/main/java/com/tzld/piaoquan/recommend/feature/domain/ad/base/AdRankItem.java
  7. 8 2
      recommend-feature-client/src/main/java/com/tzld/piaoquan/recommend/feature/domain/ad/base/UserAdFeature.java
  8. 9 2
      recommend-feature-client/src/main/java/com/tzld/piaoquan/recommend/feature/domain/video/base/ItemFeature.java
  9. 8 2
      recommend-feature-client/src/main/java/com/tzld/piaoquan/recommend/feature/domain/video/base/UserFeature.java
  10. 981 0
      recommend-feature-client/src/main/java/com/tzld/piaoquan/recommend/feature/model/feature/FeatureKeyProto.java
  11. 67 0
      recommend-feature-client/src/main/java/com/tzld/piaoquan/recommend/feature/model/feature/FeatureKeyProtoOrBuilder.java
  12. 110 0
      recommend-feature-client/src/main/java/com/tzld/piaoquan/recommend/feature/model/feature/FeatureV2.java
  13. 237 0
      recommend-feature-client/src/main/java/com/tzld/piaoquan/recommend/feature/model/feature/FeatureV2Service.java
  14. 288 0
      recommend-feature-client/src/main/java/com/tzld/piaoquan/recommend/feature/model/feature/FeatureV2ServiceGrpc.java
  15. 770 0
      recommend-feature-client/src/main/java/com/tzld/piaoquan/recommend/feature/model/feature/MultiGetFeatureRequest.java
  16. 33 0
      recommend-feature-client/src/main/java/com/tzld/piaoquan/recommend/feature/model/feature/MultiGetFeatureRequestOrBuilder.java
  17. 893 0
      recommend-feature-client/src/main/java/com/tzld/piaoquan/recommend/feature/model/feature/MultiGetFeatureResponse.java
  18. 58 0
      recommend-feature-client/src/main/java/com/tzld/piaoquan/recommend/feature/model/feature/MultiGetFeatureResponseOrBuilder.java
  19. 28 0
      recommend-feature-client/src/main/proto/com/tzld/piaoquan/recommend/feature/featureV2.proto
  20. 二进制
      recommend-feature-produce/.DS_Store
  21. 151 0
      recommend-feature-produce/pom.xml
  22. 171 0
      recommend-feature-produce/src/main/java/com/tzld/piaoquan/recommend/feature/produce/FeatureDiff.java
  23. 137 0
      recommend-feature-produce/src/main/java/com/tzld/piaoquan/recommend/feature/produce/ODPSToRedis.java
  24. 49 0
      recommend-feature-produce/src/main/java/com/tzld/piaoquan/recommend/feature/produce/VideoCleanExecutor.java
  25. 55 0
      recommend-feature-produce/src/main/java/com/tzld/piaoquan/recommend/feature/produce/VideoCleanODPSToHDFS.java
  26. 43 0
      recommend-feature-produce/src/main/java/com/tzld/piaoquan/recommend/feature/produce/model/DTSConfig.java
  27. 20 0
      recommend-feature-produce/src/main/java/com/tzld/piaoquan/recommend/feature/produce/service/CMDService.java
  28. 60 0
      recommend-feature-produce/src/main/java/com/tzld/piaoquan/recommend/feature/produce/service/DTSConfigService.java
  29. 34 0
      recommend-feature-produce/src/main/java/com/tzld/piaoquan/recommend/feature/produce/service/HDFSService.java
  30. 256 0
      recommend-feature-produce/src/main/java/com/tzld/piaoquan/recommend/feature/produce/service/ODPSService.java
  31. 76 0
      recommend-feature-produce/src/main/java/com/tzld/piaoquan/recommend/feature/produce/service/OSSService.java
  32. 100 0
      recommend-feature-produce/src/main/java/com/tzld/piaoquan/recommend/feature/produce/service/RedisService.java
  33. 49 0
      recommend-feature-produce/src/main/java/com/tzld/piaoquan/recommend/feature/produce/util/CommonCollectionUtils.java
  34. 143 0
      recommend-feature-produce/src/main/java/com/tzld/piaoquan/recommend/feature/produce/util/CompressionUtil.java
  35. 30 0
      recommend-feature-produce/src/main/java/com/tzld/piaoquan/recommend/feature/produce/util/DateUtils.java
  36. 39 0
      recommend-feature-produce/src/main/java/com/tzld/piaoquan/recommend/feature/produce/util/JSONUtils.java
  37. 174 0
      recommend-feature-produce/src/main/python/monitor.py
  38. 1 0
      recommend-feature-produce/src/main/python/vm.sh
  39. 8 2
      recommend-feature-service/pom.xml
  40. 6 0
      recommend-feature-service/src/main/java/com/tzld/piaoquan/recommend/feature/Application.java
  41. 8 8
      recommend-feature-service/src/main/java/com/tzld/piaoquan/recommend/feature/common/ThreadPoolFactory.java
  42. 10 1
      recommend-feature-service/src/main/java/com/tzld/piaoquan/recommend/feature/config/RedisTemplateConfig.java
  43. 10 1
      recommend-feature-service/src/main/java/com/tzld/piaoquan/recommend/feature/config/TairTemplateConfig.java
  44. 31 0
      recommend-feature-service/src/main/java/com/tzld/piaoquan/recommend/feature/grpcservice/FeatureV2GrpcService.java
  45. 5 4
      recommend-feature-service/src/main/java/com/tzld/piaoquan/recommend/feature/service/AdItemFeatureService.java
  46. 43 0
      recommend-feature-service/src/main/java/com/tzld/piaoquan/recommend/feature/service/DTSConfig.java
  47. 177 0
      recommend-feature-service/src/main/java/com/tzld/piaoquan/recommend/feature/service/FeatureV2Service.java
  48. 5 4
      recommend-feature-service/src/main/java/com/tzld/piaoquan/recommend/feature/service/UserAdFeatureService.java
  49. 8 2
      recommend-feature-service/src/main/java/com/tzld/piaoquan/recommend/feature/service/UserAndVideoFeatureService.java
  50. 5 4
      recommend-feature-service/src/main/java/com/tzld/piaoquan/recommend/feature/service/UserFeatureService.java
  51. 5 4
      recommend-feature-service/src/main/java/com/tzld/piaoquan/recommend/feature/service/VideoFeatureService.java
  52. 86 0
      recommend-feature-service/src/main/java/com/tzld/piaoquan/recommend/feature/util/CompressionUtil.java
  53. 7 5
      recommend-feature-service/src/main/java/com/tzld/piaoquan/recommend/feature/util/JSONUtils.java
  54. 2 2
      recommend-feature-service/src/main/resources/application-test.yml
  55. 0 4
      recommend-feature-service/src/main/resources/logback-spring.xml

+ 1 - 0
.gitignore

@@ -1,5 +1,6 @@
 # ---> Java
 *.class
+*.DS_Store
 
 # Mobile Tools for Java (J2ME)
 .mtj.tmp/

+ 1 - 0
pom.xml

@@ -18,6 +18,7 @@
     <modules>
         <module>recommend-feature-service</module>
         <module>recommend-feature-client</module>
+        <module>recommend-feature-produce</module>
     </modules>
 
     <dependencies>

+ 1 - 1
recommend-feature-client/pom.xml

@@ -10,7 +10,7 @@
     <modelVersion>4.0.0</modelVersion>
 
     <artifactId>recommend-feature-client</artifactId>
-    <version>1.1.16</version>
+    <version>1.1.24</version>
 
     <dependencies>
         <dependency>

+ 49 - 0
recommend-feature-client/src/main/java/com/tzld/piaoquan/recommend/feature/client/FeatureV2Client.java

@@ -0,0 +1,49 @@
+package com.tzld.piaoquan.recommend.feature.client;
+
+import com.tzld.piaoquan.recommend.feature.model.feature.FeatureKeyProto;
+import com.tzld.piaoquan.recommend.feature.model.feature.FeatureV2ServiceGrpc;
+import com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureRequest;
+import com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureResponse;
+import lombok.extern.slf4j.Slf4j;
+import net.devh.boot.grpc.client.inject.GrpcClient;
+import org.springframework.stereotype.Component;
+import org.springframework.util.CollectionUtils;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author dyp
+ */
+@Component
+@Slf4j
+public class FeatureV2Client {
+    @GrpcClient("recommend-feature")
+    private FeatureV2ServiceGrpc.FeatureV2ServiceBlockingStub client;
+
+    public Map<String, String> multiGetFeature(List<FeatureKeyProto> protos) {
+        if (CollectionUtils.isEmpty(protos)) {
+            return Collections.emptyMap();
+        }
+        MultiGetFeatureRequest request = MultiGetFeatureRequest.newBuilder()
+                .addAllFeatureKey(protos)
+                .build();
+        MultiGetFeatureResponse response = client.multiGetFeature(request);
+        if (response == null || !response.hasResult()) {
+            log.info("multiGetFeature grpc error");
+            return null;
+        }
+        if (response.getResult().getCode() != 1) {
+            log.info("multiGetFeature grpc code={}, msg={}", response.getResult().getCode(),
+                    response.getResult().getMessage());
+            return null;
+        }
+        if (response.getFeatureCount() == 0) {
+            log.info("multiGetFeature no feature");
+            return Collections.emptyMap();
+        }
+        return response.getFeatureMap();
+    }
+
+}

+ 9 - 2
recommend-feature-client/src/main/java/com/tzld/piaoquan/recommend/feature/domain/ad/base/AdItemFeature.java

@@ -10,6 +10,14 @@ import lombok.Setter;
 @Getter
 @NoArgsConstructor
 public class AdItemFeature {
+
+    /**
+     * Gson 单例,避免每次调用创建新对象
+     */
+    private static final Gson GSON = new GsonBuilder()
+            .serializeSpecialFloatingPointValues()
+            .create();
+
     @Setter
     private String adId = "0";
 
@@ -154,8 +162,7 @@ public class AdItemFeature {
     }
 
     public String getValue() {
-        Gson gson = new GsonBuilder().serializeSpecialFloatingPointValues().create();
-        return gson.toJson(this);
+        return GSON.toJson(this);
     }
 
 

+ 42 - 4
recommend-feature-client/src/main/java/com/tzld/piaoquan/recommend/feature/domain/ad/base/AdRankItem.java

@@ -4,18 +4,26 @@ import lombok.Data;
 
 import java.util.HashMap;
 import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
 
 
 @Data
 public class AdRankItem implements Comparable<AdRankItem> {
     public long adId;
     private double score; // 记录最终的score
+    // 随机值,当score相等时 根据random排序。避免相同分数的CID,一直只出一个的情况
+    private int random;
 
     // 预估计算结果
     private double ctr;
     private double cvr;
     private double ecpm1;
     private double ecpm2;
+    private double str;
+    private double ros;
+    private double breakRate;
+    private double lrScore;
+    private double adDirectionScore;
 
     // 增加tf打分
     private double tf_ctr;
@@ -23,23 +31,53 @@ public class AdRankItem implements Comparable<AdRankItem> {
     private double score_type;
 
     // 初始传入
-    private double cpa;
     private double bid1;
     private double bid2;
+    private double pidLambda;
 
+    private double cpa;
+    private double weight;
+    private double cpm;
 
     // 记录Item侧用到的特征
     private AdItemFeature itemFeature;
     private String lrSampleString = "";
     private String lrSampleStringOrgin = "";
-    
+
+    // 联盟参数
+    private Long mediaId;
+
+    private Long videoId;
+
+    private String creativeCode;
+
+    // 广告主ID
+    private String adVerId;
+    // 计划id
+    private Long campaignId;
+    // 广告id
+    private Long id;
+    // 广告id
+    private Long skuId;
+    //客户id
+    private Long customerId;
+    //行业
+    private String profession;
+
+    // 特征
+    private Map<String, Object> ext = new HashMap<>();
+
+    private Map<String, String> featureMap = new ConcurrentHashMap<>();
+
+    private Map<String, Map<String, String>> metaFeatureMap = new HashMap<>();
+
     public AdRankItem() {
-        //TODO
+        // TODO
     }
 
     private Map<String, Double> rankerScore = new HashMap<>();
     private Map<String, Integer> rankerIndex = new HashMap<>();
-
+    private Map<String, Double> scoreMap = new HashMap<>();
 
     @Override
     public int compareTo(AdRankItem o) {

+ 8 - 2
recommend-feature-client/src/main/java/com/tzld/piaoquan/recommend/feature/domain/ad/base/UserAdFeature.java

@@ -12,6 +12,13 @@ import lombok.Setter;
 @Setter
 public class UserAdFeature {
 
+    /**
+     * Gson 单例,避免每次调用创建新对象
+     */
+    private static final Gson GSON = new GsonBuilder()
+            .serializeSpecialFloatingPointValues()
+            .create();
+
     private String mid = "0";
 
 
@@ -62,8 +69,7 @@ public class UserAdFeature {
     }
 
     public String getValue() {
-        Gson gson = new GsonBuilder().serializeSpecialFloatingPointValues().create();
-        return gson.toJson(this);
+        return GSON.toJson(this);
     }
 
 }

+ 9 - 2
recommend-feature-client/src/main/java/com/tzld/piaoquan/recommend/feature/domain/video/base/ItemFeature.java

@@ -8,6 +8,14 @@ import lombok.NoArgsConstructor;
 @Getter
 @NoArgsConstructor
 public class ItemFeature {
+
+    /**
+     * Gson 单例,避免每次调用创建新对象
+     */
+    private static final Gson GSON = new GsonBuilder()
+            .serializeSpecialFloatingPointValues()
+            .create();
+
     private String videoId = "0";
 
     private String upId = "0";
@@ -130,8 +138,7 @@ public class ItemFeature {
     }
 
     public String getValue() {
-        Gson gson = new GsonBuilder().serializeSpecialFloatingPointValues().create();
-        return gson.toJson(this);
+        return GSON.toJson(this);
     }
 
     public static ItemFeature defaultInstance(String videoId) {

+ 8 - 2
recommend-feature-client/src/main/java/com/tzld/piaoquan/recommend/feature/domain/video/base/UserFeature.java

@@ -9,6 +9,13 @@ import lombok.NoArgsConstructor;
 @NoArgsConstructor
 public class UserFeature {
 
+    /**
+     * Gson 单例,避免每次调用创建新对象
+     */
+    private static final Gson GSON = new GsonBuilder()
+            .serializeSpecialFloatingPointValues()
+            .create();
+
     private String uid = "0";
     private String mid = "0";
     // 当天统计量信息
@@ -86,8 +93,7 @@ public class UserFeature {
     }
 
     public String getValue() {
-        Gson gson = new GsonBuilder().serializeSpecialFloatingPointValues().create();
-        return gson.toJson(this);
+        return GSON.toJson(this);
     }
 
     public static UserFeature defaultInstance(String mid) {

+ 981 - 0
recommend-feature-client/src/main/java/com/tzld/piaoquan/recommend/feature/model/feature/FeatureKeyProto.java

@@ -0,0 +1,981 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: com/tzld/piaoquan/recommend/feature/featureV2.proto
+
+package com.tzld.piaoquan.recommend.feature.model.feature;
+
+/**
+ * Protobuf type {@code FeatureKeyProto}
+ */
+public final class FeatureKeyProto extends
+    com.google.protobuf.GeneratedMessageV3 implements
+    // @@protoc_insertion_point(message_implements:FeatureKeyProto)
+    FeatureKeyProtoOrBuilder {
+private static final long serialVersionUID = 0L;
+  // Use FeatureKeyProto.newBuilder() to construct.
+  private FeatureKeyProto(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+    super(builder);
+  }
+  private FeatureKeyProto() {
+    uniqueKey_ = "";
+    tableName_ = "";
+  }
+
+  @java.lang.Override
+  @SuppressWarnings({"unused"})
+  protected java.lang.Object newInstance(
+      UnusedPrivateParameter unused) {
+    return new FeatureKeyProto();
+  }
+
+  @java.lang.Override
+  public final com.google.protobuf.UnknownFieldSet
+  getUnknownFields() {
+    return this.unknownFields;
+  }
+  private FeatureKeyProto(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    this();
+    if (extensionRegistry == null) {
+      throw new java.lang.NullPointerException();
+    }
+    int mutable_bitField0_ = 0;
+    com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+        com.google.protobuf.UnknownFieldSet.newBuilder();
+    try {
+      boolean done = false;
+      while (!done) {
+        int tag = input.readTag();
+        switch (tag) {
+          case 0:
+            done = true;
+            break;
+          case 10: {
+            java.lang.String s = input.readStringRequireUtf8();
+
+            uniqueKey_ = s;
+            break;
+          }
+          case 18: {
+            java.lang.String s = input.readStringRequireUtf8();
+
+            tableName_ = s;
+            break;
+          }
+          case 26: {
+            if (!((mutable_bitField0_ & 0x00000001) != 0)) {
+              fieldValue_ = com.google.protobuf.MapField.newMapField(
+                  FieldValueDefaultEntryHolder.defaultEntry);
+              mutable_bitField0_ |= 0x00000001;
+            }
+            com.google.protobuf.MapEntry<java.lang.String, java.lang.String>
+            fieldValue__ = input.readMessage(
+                FieldValueDefaultEntryHolder.defaultEntry.getParserForType(), extensionRegistry);
+            fieldValue_.getMutableMap().put(
+                fieldValue__.getKey(), fieldValue__.getValue());
+            break;
+          }
+          default: {
+            if (!parseUnknownField(
+                input, unknownFields, extensionRegistry, tag)) {
+              done = true;
+            }
+            break;
+          }
+        }
+      }
+    } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+      throw e.setUnfinishedMessage(this);
+    } catch (java.io.IOException e) {
+      throw new com.google.protobuf.InvalidProtocolBufferException(
+          e).setUnfinishedMessage(this);
+    } finally {
+      this.unknownFields = unknownFields.build();
+      makeExtensionsImmutable();
+    }
+  }
+  public static final com.google.protobuf.Descriptors.Descriptor
+      getDescriptor() {
+    return com.tzld.piaoquan.recommend.feature.model.feature.FeatureV2.internal_static_FeatureKeyProto_descriptor;
+  }
+
+  @SuppressWarnings({"rawtypes"})
+  @java.lang.Override
+  protected com.google.protobuf.MapField internalGetMapField(
+      int number) {
+    switch (number) {
+      case 3:
+        return internalGetFieldValue();
+      default:
+        throw new RuntimeException(
+            "Invalid map field number: " + number);
+    }
+  }
+  @java.lang.Override
+  protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internalGetFieldAccessorTable() {
+    return com.tzld.piaoquan.recommend.feature.model.feature.FeatureV2.internal_static_FeatureKeyProto_fieldAccessorTable
+        .ensureFieldAccessorsInitialized(
+            com.tzld.piaoquan.recommend.feature.model.feature.FeatureKeyProto.class, com.tzld.piaoquan.recommend.feature.model.feature.FeatureKeyProto.Builder.class);
+  }
+
+  public static final int UNIQUE_KEY_FIELD_NUMBER = 1;
+  private volatile java.lang.Object uniqueKey_;
+  /**
+   * <code>string unique_key = 1;</code>
+   * @return The uniqueKey.
+   */
+  @java.lang.Override
+  public java.lang.String getUniqueKey() {
+    java.lang.Object ref = uniqueKey_;
+    if (ref instanceof java.lang.String) {
+      return (java.lang.String) ref;
+    } else {
+      com.google.protobuf.ByteString bs = 
+          (com.google.protobuf.ByteString) ref;
+      java.lang.String s = bs.toStringUtf8();
+      uniqueKey_ = s;
+      return s;
+    }
+  }
+  /**
+   * <code>string unique_key = 1;</code>
+   * @return The bytes for uniqueKey.
+   */
+  @java.lang.Override
+  public com.google.protobuf.ByteString
+      getUniqueKeyBytes() {
+    java.lang.Object ref = uniqueKey_;
+    if (ref instanceof java.lang.String) {
+      com.google.protobuf.ByteString b = 
+          com.google.protobuf.ByteString.copyFromUtf8(
+              (java.lang.String) ref);
+      uniqueKey_ = b;
+      return b;
+    } else {
+      return (com.google.protobuf.ByteString) ref;
+    }
+  }
+
+  public static final int TABLE_NAME_FIELD_NUMBER = 2;
+  private volatile java.lang.Object tableName_;
+  /**
+   * <code>string table_name = 2;</code>
+   * @return The tableName.
+   */
+  @java.lang.Override
+  public java.lang.String getTableName() {
+    java.lang.Object ref = tableName_;
+    if (ref instanceof java.lang.String) {
+      return (java.lang.String) ref;
+    } else {
+      com.google.protobuf.ByteString bs = 
+          (com.google.protobuf.ByteString) ref;
+      java.lang.String s = bs.toStringUtf8();
+      tableName_ = s;
+      return s;
+    }
+  }
+  /**
+   * <code>string table_name = 2;</code>
+   * @return The bytes for tableName.
+   */
+  @java.lang.Override
+  public com.google.protobuf.ByteString
+      getTableNameBytes() {
+    java.lang.Object ref = tableName_;
+    if (ref instanceof java.lang.String) {
+      com.google.protobuf.ByteString b = 
+          com.google.protobuf.ByteString.copyFromUtf8(
+              (java.lang.String) ref);
+      tableName_ = b;
+      return b;
+    } else {
+      return (com.google.protobuf.ByteString) ref;
+    }
+  }
+
+  public static final int FIELD_VALUE_FIELD_NUMBER = 3;
+  private static final class FieldValueDefaultEntryHolder {
+    static final com.google.protobuf.MapEntry<
+        java.lang.String, java.lang.String> defaultEntry =
+            com.google.protobuf.MapEntry
+            .<java.lang.String, java.lang.String>newDefaultInstance(
+                com.tzld.piaoquan.recommend.feature.model.feature.FeatureV2.internal_static_FeatureKeyProto_FieldValueEntry_descriptor, 
+                com.google.protobuf.WireFormat.FieldType.STRING,
+                "",
+                com.google.protobuf.WireFormat.FieldType.STRING,
+                "");
+  }
+  private com.google.protobuf.MapField<
+      java.lang.String, java.lang.String> fieldValue_;
+  private com.google.protobuf.MapField<java.lang.String, java.lang.String>
+  internalGetFieldValue() {
+    if (fieldValue_ == null) {
+      return com.google.protobuf.MapField.emptyMapField(
+          FieldValueDefaultEntryHolder.defaultEntry);
+    }
+    return fieldValue_;
+  }
+
+  public int getFieldValueCount() {
+    return internalGetFieldValue().getMap().size();
+  }
+  /**
+   * <code>map&lt;string, string&gt; field_value = 3;</code>
+   */
+
+  @java.lang.Override
+  public boolean containsFieldValue(
+      java.lang.String key) {
+    if (key == null) { throw new java.lang.NullPointerException(); }
+    return internalGetFieldValue().getMap().containsKey(key);
+  }
+  /**
+   * Use {@link #getFieldValueMap()} instead.
+   */
+  @java.lang.Override
+  @java.lang.Deprecated
+  public java.util.Map<java.lang.String, java.lang.String> getFieldValue() {
+    return getFieldValueMap();
+  }
+  /**
+   * <code>map&lt;string, string&gt; field_value = 3;</code>
+   */
+  @java.lang.Override
+
+  public java.util.Map<java.lang.String, java.lang.String> getFieldValueMap() {
+    return internalGetFieldValue().getMap();
+  }
+  /**
+   * <code>map&lt;string, string&gt; field_value = 3;</code>
+   */
+  @java.lang.Override
+
+  public java.lang.String getFieldValueOrDefault(
+      java.lang.String key,
+      java.lang.String defaultValue) {
+    if (key == null) { throw new java.lang.NullPointerException(); }
+    java.util.Map<java.lang.String, java.lang.String> map =
+        internalGetFieldValue().getMap();
+    return map.containsKey(key) ? map.get(key) : defaultValue;
+  }
+  /**
+   * <code>map&lt;string, string&gt; field_value = 3;</code>
+   */
+  @java.lang.Override
+
+  public java.lang.String getFieldValueOrThrow(
+      java.lang.String key) {
+    if (key == null) { throw new java.lang.NullPointerException(); }
+    java.util.Map<java.lang.String, java.lang.String> map =
+        internalGetFieldValue().getMap();
+    if (!map.containsKey(key)) {
+      throw new java.lang.IllegalArgumentException();
+    }
+    return map.get(key);
+  }
+
+  private byte memoizedIsInitialized = -1;
+  @java.lang.Override
+  public final boolean isInitialized() {
+    byte isInitialized = memoizedIsInitialized;
+    if (isInitialized == 1) return true;
+    if (isInitialized == 0) return false;
+
+    memoizedIsInitialized = 1;
+    return true;
+  }
+
+  @java.lang.Override
+  public void writeTo(com.google.protobuf.CodedOutputStream output)
+                      throws java.io.IOException {
+    if (!getUniqueKeyBytes().isEmpty()) {
+      com.google.protobuf.GeneratedMessageV3.writeString(output, 1, uniqueKey_);
+    }
+    if (!getTableNameBytes().isEmpty()) {
+      com.google.protobuf.GeneratedMessageV3.writeString(output, 2, tableName_);
+    }
+    com.google.protobuf.GeneratedMessageV3
+      .serializeStringMapTo(
+        output,
+        internalGetFieldValue(),
+        FieldValueDefaultEntryHolder.defaultEntry,
+        3);
+    unknownFields.writeTo(output);
+  }
+
+  @java.lang.Override
+  public int getSerializedSize() {
+    int size = memoizedSize;
+    if (size != -1) return size;
+
+    size = 0;
+    if (!getUniqueKeyBytes().isEmpty()) {
+      size += com.google.protobuf.GeneratedMessageV3.computeStringSize(1, uniqueKey_);
+    }
+    if (!getTableNameBytes().isEmpty()) {
+      size += com.google.protobuf.GeneratedMessageV3.computeStringSize(2, tableName_);
+    }
+    for (java.util.Map.Entry<java.lang.String, java.lang.String> entry
+         : internalGetFieldValue().getMap().entrySet()) {
+      com.google.protobuf.MapEntry<java.lang.String, java.lang.String>
+      fieldValue__ = FieldValueDefaultEntryHolder.defaultEntry.newBuilderForType()
+          .setKey(entry.getKey())
+          .setValue(entry.getValue())
+          .build();
+      size += com.google.protobuf.CodedOutputStream
+          .computeMessageSize(3, fieldValue__);
+    }
+    size += unknownFields.getSerializedSize();
+    memoizedSize = size;
+    return size;
+  }
+
+  @java.lang.Override
+  public boolean equals(final java.lang.Object obj) {
+    if (obj == this) {
+     return true;
+    }
+    if (!(obj instanceof com.tzld.piaoquan.recommend.feature.model.feature.FeatureKeyProto)) {
+      return super.equals(obj);
+    }
+    com.tzld.piaoquan.recommend.feature.model.feature.FeatureKeyProto other = (com.tzld.piaoquan.recommend.feature.model.feature.FeatureKeyProto) obj;
+
+    if (!getUniqueKey()
+        .equals(other.getUniqueKey())) return false;
+    if (!getTableName()
+        .equals(other.getTableName())) return false;
+    if (!internalGetFieldValue().equals(
+        other.internalGetFieldValue())) return false;
+    if (!unknownFields.equals(other.unknownFields)) return false;
+    return true;
+  }
+
+  @java.lang.Override
+  public int hashCode() {
+    if (memoizedHashCode != 0) {
+      return memoizedHashCode;
+    }
+    int hash = 41;
+    hash = (19 * hash) + getDescriptor().hashCode();
+    hash = (37 * hash) + UNIQUE_KEY_FIELD_NUMBER;
+    hash = (53 * hash) + getUniqueKey().hashCode();
+    hash = (37 * hash) + TABLE_NAME_FIELD_NUMBER;
+    hash = (53 * hash) + getTableName().hashCode();
+    if (!internalGetFieldValue().getMap().isEmpty()) {
+      hash = (37 * hash) + FIELD_VALUE_FIELD_NUMBER;
+      hash = (53 * hash) + internalGetFieldValue().hashCode();
+    }
+    hash = (29 * hash) + unknownFields.hashCode();
+    memoizedHashCode = hash;
+    return hash;
+  }
+
+  public static com.tzld.piaoquan.recommend.feature.model.feature.FeatureKeyProto parseFrom(
+      java.nio.ByteBuffer data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static com.tzld.piaoquan.recommend.feature.model.feature.FeatureKeyProto parseFrom(
+      java.nio.ByteBuffer data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static com.tzld.piaoquan.recommend.feature.model.feature.FeatureKeyProto parseFrom(
+      com.google.protobuf.ByteString data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static com.tzld.piaoquan.recommend.feature.model.feature.FeatureKeyProto parseFrom(
+      com.google.protobuf.ByteString data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static com.tzld.piaoquan.recommend.feature.model.feature.FeatureKeyProto parseFrom(byte[] data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static com.tzld.piaoquan.recommend.feature.model.feature.FeatureKeyProto parseFrom(
+      byte[] data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static com.tzld.piaoquan.recommend.feature.model.feature.FeatureKeyProto parseFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static com.tzld.piaoquan.recommend.feature.model.feature.FeatureKeyProto parseFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static com.tzld.piaoquan.recommend.feature.model.feature.FeatureKeyProto parseDelimitedFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input);
+  }
+  public static com.tzld.piaoquan.recommend.feature.model.feature.FeatureKeyProto parseDelimitedFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static com.tzld.piaoquan.recommend.feature.model.feature.FeatureKeyProto parseFrom(
+      com.google.protobuf.CodedInputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static com.tzld.piaoquan.recommend.feature.model.feature.FeatureKeyProto parseFrom(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+
+  @java.lang.Override
+  public Builder newBuilderForType() { return newBuilder(); }
+  public static Builder newBuilder() {
+    return DEFAULT_INSTANCE.toBuilder();
+  }
+  public static Builder newBuilder(com.tzld.piaoquan.recommend.feature.model.feature.FeatureKeyProto prototype) {
+    return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+  }
+  @java.lang.Override
+  public Builder toBuilder() {
+    return this == DEFAULT_INSTANCE
+        ? new Builder() : new Builder().mergeFrom(this);
+  }
+
+  @java.lang.Override
+  protected Builder newBuilderForType(
+      com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+    Builder builder = new Builder(parent);
+    return builder;
+  }
+  /**
+   * Protobuf type {@code FeatureKeyProto}
+   */
+  public static final class Builder extends
+      com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+      // @@protoc_insertion_point(builder_implements:FeatureKeyProto)
+      com.tzld.piaoquan.recommend.feature.model.feature.FeatureKeyProtoOrBuilder {
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return com.tzld.piaoquan.recommend.feature.model.feature.FeatureV2.internal_static_FeatureKeyProto_descriptor;
+    }
+
+    @SuppressWarnings({"rawtypes"})
+    protected com.google.protobuf.MapField internalGetMapField(
+        int number) {
+      switch (number) {
+        case 3:
+          return internalGetFieldValue();
+        default:
+          throw new RuntimeException(
+              "Invalid map field number: " + number);
+      }
+    }
+    @SuppressWarnings({"rawtypes"})
+    protected com.google.protobuf.MapField internalGetMutableMapField(
+        int number) {
+      switch (number) {
+        case 3:
+          return internalGetMutableFieldValue();
+        default:
+          throw new RuntimeException(
+              "Invalid map field number: " + number);
+      }
+    }
+    @java.lang.Override
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return com.tzld.piaoquan.recommend.feature.model.feature.FeatureV2.internal_static_FeatureKeyProto_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              com.tzld.piaoquan.recommend.feature.model.feature.FeatureKeyProto.class, com.tzld.piaoquan.recommend.feature.model.feature.FeatureKeyProto.Builder.class);
+    }
+
+    // Construct using com.tzld.piaoquan.recommend.feature.model.feature.FeatureKeyProto.newBuilder()
+    private Builder() {
+      maybeForceBuilderInitialization();
+    }
+
+    private Builder(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      super(parent);
+      maybeForceBuilderInitialization();
+    }
+    private void maybeForceBuilderInitialization() {
+      if (com.google.protobuf.GeneratedMessageV3
+              .alwaysUseFieldBuilders) {
+      }
+    }
+    @java.lang.Override
+    public Builder clear() {
+      super.clear();
+      uniqueKey_ = "";
+
+      tableName_ = "";
+
+      internalGetMutableFieldValue().clear();
+      return this;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Descriptors.Descriptor
+        getDescriptorForType() {
+      return com.tzld.piaoquan.recommend.feature.model.feature.FeatureV2.internal_static_FeatureKeyProto_descriptor;
+    }
+
+    @java.lang.Override
+    public com.tzld.piaoquan.recommend.feature.model.feature.FeatureKeyProto getDefaultInstanceForType() {
+      return com.tzld.piaoquan.recommend.feature.model.feature.FeatureKeyProto.getDefaultInstance();
+    }
+
+    @java.lang.Override
+    public com.tzld.piaoquan.recommend.feature.model.feature.FeatureKeyProto build() {
+      com.tzld.piaoquan.recommend.feature.model.feature.FeatureKeyProto result = buildPartial();
+      if (!result.isInitialized()) {
+        throw newUninitializedMessageException(result);
+      }
+      return result;
+    }
+
+    @java.lang.Override
+    public com.tzld.piaoquan.recommend.feature.model.feature.FeatureKeyProto buildPartial() {
+      com.tzld.piaoquan.recommend.feature.model.feature.FeatureKeyProto result = new com.tzld.piaoquan.recommend.feature.model.feature.FeatureKeyProto(this);
+      int from_bitField0_ = bitField0_;
+      result.uniqueKey_ = uniqueKey_;
+      result.tableName_ = tableName_;
+      result.fieldValue_ = internalGetFieldValue();
+      result.fieldValue_.makeImmutable();
+      onBuilt();
+      return result;
+    }
+
+    @java.lang.Override
+    public Builder clone() {
+      return super.clone();
+    }
+    @java.lang.Override
+    public Builder setField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return super.setField(field, value);
+    }
+    @java.lang.Override
+    public Builder clearField(
+        com.google.protobuf.Descriptors.FieldDescriptor field) {
+      return super.clearField(field);
+    }
+    @java.lang.Override
+    public Builder clearOneof(
+        com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+      return super.clearOneof(oneof);
+    }
+    @java.lang.Override
+    public Builder setRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        int index, java.lang.Object value) {
+      return super.setRepeatedField(field, index, value);
+    }
+    @java.lang.Override
+    public Builder addRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return super.addRepeatedField(field, value);
+    }
+    @java.lang.Override
+    public Builder mergeFrom(com.google.protobuf.Message other) {
+      if (other instanceof com.tzld.piaoquan.recommend.feature.model.feature.FeatureKeyProto) {
+        return mergeFrom((com.tzld.piaoquan.recommend.feature.model.feature.FeatureKeyProto)other);
+      } else {
+        super.mergeFrom(other);
+        return this;
+      }
+    }
+
+    public Builder mergeFrom(com.tzld.piaoquan.recommend.feature.model.feature.FeatureKeyProto other) {
+      if (other == com.tzld.piaoquan.recommend.feature.model.feature.FeatureKeyProto.getDefaultInstance()) return this;
+      if (!other.getUniqueKey().isEmpty()) {
+        uniqueKey_ = other.uniqueKey_;
+        onChanged();
+      }
+      if (!other.getTableName().isEmpty()) {
+        tableName_ = other.tableName_;
+        onChanged();
+      }
+      internalGetMutableFieldValue().mergeFrom(
+          other.internalGetFieldValue());
+      this.mergeUnknownFields(other.unknownFields);
+      onChanged();
+      return this;
+    }
+
+    @java.lang.Override
+    public final boolean isInitialized() {
+      return true;
+    }
+
+    @java.lang.Override
+    public Builder mergeFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      com.tzld.piaoquan.recommend.feature.model.feature.FeatureKeyProto parsedMessage = null;
+      try {
+        parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        parsedMessage = (com.tzld.piaoquan.recommend.feature.model.feature.FeatureKeyProto) e.getUnfinishedMessage();
+        throw e.unwrapIOException();
+      } finally {
+        if (parsedMessage != null) {
+          mergeFrom(parsedMessage);
+        }
+      }
+      return this;
+    }
+    private int bitField0_;
+
+    private java.lang.Object uniqueKey_ = "";
+    /**
+     * <code>string unique_key = 1;</code>
+     * @return The uniqueKey.
+     */
+    public java.lang.String getUniqueKey() {
+      java.lang.Object ref = uniqueKey_;
+      if (!(ref instanceof java.lang.String)) {
+        com.google.protobuf.ByteString bs =
+            (com.google.protobuf.ByteString) ref;
+        java.lang.String s = bs.toStringUtf8();
+        uniqueKey_ = s;
+        return s;
+      } else {
+        return (java.lang.String) ref;
+      }
+    }
+    /**
+     * <code>string unique_key = 1;</code>
+     * @return The bytes for uniqueKey.
+     */
+    public com.google.protobuf.ByteString
+        getUniqueKeyBytes() {
+      java.lang.Object ref = uniqueKey_;
+      if (ref instanceof String) {
+        com.google.protobuf.ByteString b = 
+            com.google.protobuf.ByteString.copyFromUtf8(
+                (java.lang.String) ref);
+        uniqueKey_ = b;
+        return b;
+      } else {
+        return (com.google.protobuf.ByteString) ref;
+      }
+    }
+    /**
+     * <code>string unique_key = 1;</code>
+     * @param value The uniqueKey to set.
+     * @return This builder for chaining.
+     */
+    public Builder setUniqueKey(
+        java.lang.String value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  
+      uniqueKey_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>string unique_key = 1;</code>
+     * @return This builder for chaining.
+     */
+    public Builder clearUniqueKey() {
+      
+      uniqueKey_ = getDefaultInstance().getUniqueKey();
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>string unique_key = 1;</code>
+     * @param value The bytes for uniqueKey to set.
+     * @return This builder for chaining.
+     */
+    public Builder setUniqueKeyBytes(
+        com.google.protobuf.ByteString value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  checkByteStringIsUtf8(value);
+      
+      uniqueKey_ = value;
+      onChanged();
+      return this;
+    }
+
+    private java.lang.Object tableName_ = "";
+    /**
+     * <code>string table_name = 2;</code>
+     * @return The tableName.
+     */
+    public java.lang.String getTableName() {
+      java.lang.Object ref = tableName_;
+      if (!(ref instanceof java.lang.String)) {
+        com.google.protobuf.ByteString bs =
+            (com.google.protobuf.ByteString) ref;
+        java.lang.String s = bs.toStringUtf8();
+        tableName_ = s;
+        return s;
+      } else {
+        return (java.lang.String) ref;
+      }
+    }
+    /**
+     * <code>string table_name = 2;</code>
+     * @return The bytes for tableName.
+     */
+    public com.google.protobuf.ByteString
+        getTableNameBytes() {
+      java.lang.Object ref = tableName_;
+      if (ref instanceof String) {
+        com.google.protobuf.ByteString b = 
+            com.google.protobuf.ByteString.copyFromUtf8(
+                (java.lang.String) ref);
+        tableName_ = b;
+        return b;
+      } else {
+        return (com.google.protobuf.ByteString) ref;
+      }
+    }
+    /**
+     * <code>string table_name = 2;</code>
+     * @param value The tableName to set.
+     * @return This builder for chaining.
+     */
+    public Builder setTableName(
+        java.lang.String value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  
+      tableName_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>string table_name = 2;</code>
+     * @return This builder for chaining.
+     */
+    public Builder clearTableName() {
+      
+      tableName_ = getDefaultInstance().getTableName();
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>string table_name = 2;</code>
+     * @param value The bytes for tableName to set.
+     * @return This builder for chaining.
+     */
+    public Builder setTableNameBytes(
+        com.google.protobuf.ByteString value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  checkByteStringIsUtf8(value);
+      
+      tableName_ = value;
+      onChanged();
+      return this;
+    }
+
+    private com.google.protobuf.MapField<
+        java.lang.String, java.lang.String> fieldValue_;
+    private com.google.protobuf.MapField<java.lang.String, java.lang.String>
+    internalGetFieldValue() {
+      if (fieldValue_ == null) {
+        return com.google.protobuf.MapField.emptyMapField(
+            FieldValueDefaultEntryHolder.defaultEntry);
+      }
+      return fieldValue_;
+    }
+    private com.google.protobuf.MapField<java.lang.String, java.lang.String>
+    internalGetMutableFieldValue() {
+      onChanged();;
+      if (fieldValue_ == null) {
+        fieldValue_ = com.google.protobuf.MapField.newMapField(
+            FieldValueDefaultEntryHolder.defaultEntry);
+      }
+      if (!fieldValue_.isMutable()) {
+        fieldValue_ = fieldValue_.copy();
+      }
+      return fieldValue_;
+    }
+
+    public int getFieldValueCount() {
+      return internalGetFieldValue().getMap().size();
+    }
+    /**
+     * <code>map&lt;string, string&gt; field_value = 3;</code>
+     */
+
+    @java.lang.Override
+    public boolean containsFieldValue(
+        java.lang.String key) {
+      if (key == null) { throw new java.lang.NullPointerException(); }
+      return internalGetFieldValue().getMap().containsKey(key);
+    }
+    /**
+     * Use {@link #getFieldValueMap()} instead.
+     */
+    @java.lang.Override
+    @java.lang.Deprecated
+    public java.util.Map<java.lang.String, java.lang.String> getFieldValue() {
+      return getFieldValueMap();
+    }
+    /**
+     * <code>map&lt;string, string&gt; field_value = 3;</code>
+     */
+    @java.lang.Override
+
+    public java.util.Map<java.lang.String, java.lang.String> getFieldValueMap() {
+      return internalGetFieldValue().getMap();
+    }
+    /**
+     * <code>map&lt;string, string&gt; field_value = 3;</code>
+     */
+    @java.lang.Override
+
+    public java.lang.String getFieldValueOrDefault(
+        java.lang.String key,
+        java.lang.String defaultValue) {
+      if (key == null) { throw new java.lang.NullPointerException(); }
+      java.util.Map<java.lang.String, java.lang.String> map =
+          internalGetFieldValue().getMap();
+      return map.containsKey(key) ? map.get(key) : defaultValue;
+    }
+    /**
+     * <code>map&lt;string, string&gt; field_value = 3;</code>
+     */
+    @java.lang.Override
+
+    public java.lang.String getFieldValueOrThrow(
+        java.lang.String key) {
+      if (key == null) { throw new java.lang.NullPointerException(); }
+      java.util.Map<java.lang.String, java.lang.String> map =
+          internalGetFieldValue().getMap();
+      if (!map.containsKey(key)) {
+        throw new java.lang.IllegalArgumentException();
+      }
+      return map.get(key);
+    }
+
+    public Builder clearFieldValue() {
+      internalGetMutableFieldValue().getMutableMap()
+          .clear();
+      return this;
+    }
+    /**
+     * <code>map&lt;string, string&gt; field_value = 3;</code>
+     */
+
+    public Builder removeFieldValue(
+        java.lang.String key) {
+      if (key == null) { throw new java.lang.NullPointerException(); }
+      internalGetMutableFieldValue().getMutableMap()
+          .remove(key);
+      return this;
+    }
+    /**
+     * Use alternate mutation accessors instead.
+     */
+    @java.lang.Deprecated
+    public java.util.Map<java.lang.String, java.lang.String>
+    getMutableFieldValue() {
+      return internalGetMutableFieldValue().getMutableMap();
+    }
+    /**
+     * <code>map&lt;string, string&gt; field_value = 3;</code>
+     */
+    public Builder putFieldValue(
+        java.lang.String key,
+        java.lang.String value) {
+      if (key == null) { throw new java.lang.NullPointerException(); }
+      if (value == null) { throw new java.lang.NullPointerException(); }
+      internalGetMutableFieldValue().getMutableMap()
+          .put(key, value);
+      return this;
+    }
+    /**
+     * <code>map&lt;string, string&gt; field_value = 3;</code>
+     */
+
+    public Builder putAllFieldValue(
+        java.util.Map<java.lang.String, java.lang.String> values) {
+      internalGetMutableFieldValue().getMutableMap()
+          .putAll(values);
+      return this;
+    }
+    @java.lang.Override
+    public final Builder setUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.setUnknownFields(unknownFields);
+    }
+
+    @java.lang.Override
+    public final Builder mergeUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.mergeUnknownFields(unknownFields);
+    }
+
+
+    // @@protoc_insertion_point(builder_scope:FeatureKeyProto)
+  }
+
+  // @@protoc_insertion_point(class_scope:FeatureKeyProto)
+  private static final com.tzld.piaoquan.recommend.feature.model.feature.FeatureKeyProto DEFAULT_INSTANCE;
+  static {
+    DEFAULT_INSTANCE = new com.tzld.piaoquan.recommend.feature.model.feature.FeatureKeyProto();
+  }
+
+  public static com.tzld.piaoquan.recommend.feature.model.feature.FeatureKeyProto getDefaultInstance() {
+    return DEFAULT_INSTANCE;
+  }
+
+  private static final com.google.protobuf.Parser<FeatureKeyProto>
+      PARSER = new com.google.protobuf.AbstractParser<FeatureKeyProto>() {
+    @java.lang.Override
+    public FeatureKeyProto parsePartialFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return new FeatureKeyProto(input, extensionRegistry);
+    }
+  };
+
+  public static com.google.protobuf.Parser<FeatureKeyProto> parser() {
+    return PARSER;
+  }
+
+  @java.lang.Override
+  public com.google.protobuf.Parser<FeatureKeyProto> getParserForType() {
+    return PARSER;
+  }
+
+  @java.lang.Override
+  public com.tzld.piaoquan.recommend.feature.model.feature.FeatureKeyProto getDefaultInstanceForType() {
+    return DEFAULT_INSTANCE;
+  }
+
+}
+

+ 67 - 0
recommend-feature-client/src/main/java/com/tzld/piaoquan/recommend/feature/model/feature/FeatureKeyProtoOrBuilder.java

@@ -0,0 +1,67 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: com/tzld/piaoquan/recommend/feature/featureV2.proto
+
+package com.tzld.piaoquan.recommend.feature.model.feature;
+
+public interface FeatureKeyProtoOrBuilder extends
+    // @@protoc_insertion_point(interface_extends:FeatureKeyProto)
+    com.google.protobuf.MessageOrBuilder {
+
+  /**
+   * <code>string unique_key = 1;</code>
+   * @return The uniqueKey.
+   */
+  java.lang.String getUniqueKey();
+  /**
+   * <code>string unique_key = 1;</code>
+   * @return The bytes for uniqueKey.
+   */
+  com.google.protobuf.ByteString
+      getUniqueKeyBytes();
+
+  /**
+   * <code>string table_name = 2;</code>
+   * @return The tableName.
+   */
+  java.lang.String getTableName();
+  /**
+   * <code>string table_name = 2;</code>
+   * @return The bytes for tableName.
+   */
+  com.google.protobuf.ByteString
+      getTableNameBytes();
+
+  /**
+   * <code>map&lt;string, string&gt; field_value = 3;</code>
+   */
+  int getFieldValueCount();
+  /**
+   * <code>map&lt;string, string&gt; field_value = 3;</code>
+   */
+  boolean containsFieldValue(
+      java.lang.String key);
+  /**
+   * Use {@link #getFieldValueMap()} instead.
+   */
+  @java.lang.Deprecated
+  java.util.Map<java.lang.String, java.lang.String>
+  getFieldValue();
+  /**
+   * <code>map&lt;string, string&gt; field_value = 3;</code>
+   */
+  java.util.Map<java.lang.String, java.lang.String>
+  getFieldValueMap();
+  /**
+   * <code>map&lt;string, string&gt; field_value = 3;</code>
+   */
+
+  java.lang.String getFieldValueOrDefault(
+      java.lang.String key,
+      java.lang.String defaultValue);
+  /**
+   * <code>map&lt;string, string&gt; field_value = 3;</code>
+   */
+
+  java.lang.String getFieldValueOrThrow(
+      java.lang.String key);
+}

+ 110 - 0
recommend-feature-client/src/main/java/com/tzld/piaoquan/recommend/feature/model/feature/FeatureV2.java

@@ -0,0 +1,110 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: com/tzld/piaoquan/recommend/feature/featureV2.proto
+
+package com.tzld.piaoquan.recommend.feature.model.feature;
+
+public final class FeatureV2 {
+  private FeatureV2() {}
+  public static void registerAllExtensions(
+      com.google.protobuf.ExtensionRegistryLite registry) {
+  }
+
+  public static void registerAllExtensions(
+      com.google.protobuf.ExtensionRegistry registry) {
+    registerAllExtensions(
+        (com.google.protobuf.ExtensionRegistryLite) registry);
+  }
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_MultiGetFeatureRequest_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_MultiGetFeatureRequest_fieldAccessorTable;
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_FeatureKeyProto_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_FeatureKeyProto_fieldAccessorTable;
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_FeatureKeyProto_FieldValueEntry_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_FeatureKeyProto_FieldValueEntry_fieldAccessorTable;
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_MultiGetFeatureResponse_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_MultiGetFeatureResponse_fieldAccessorTable;
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_MultiGetFeatureResponse_FeatureEntry_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_MultiGetFeatureResponse_FeatureEntry_fieldAccessorTable;
+
+  public static com.google.protobuf.Descriptors.FileDescriptor
+      getDescriptor() {
+    return descriptor;
+  }
+  private static  com.google.protobuf.Descriptors.FileDescriptor
+      descriptor;
+  static {
+    java.lang.String[] descriptorData = {
+      "\n3com/tzld/piaoquan/recommend/feature/fe" +
+      "atureV2.proto\032\031google/protobuf/any.proto" +
+      "\0320com/tzld/piaoquan/recommend/feature/co" +
+      "mmon.proto\"?\n\026MultiGetFeatureRequest\022%\n\013" +
+      "feature_key\030\001 \003(\0132\020.FeatureKeyProto\"\243\001\n\017" +
+      "FeatureKeyProto\022\022\n\nunique_key\030\001 \001(\t\022\022\n\nt" +
+      "able_name\030\002 \001(\t\0225\n\013field_value\030\003 \003(\0132 .F" +
+      "eatureKeyProto.FieldValueEntry\0321\n\017FieldV" +
+      "alueEntry\022\013\n\003key\030\001 \001(\t\022\r\n\005value\030\002 \001(\t:\0028" +
+      "\001\"\232\001\n\027MultiGetFeatureResponse\022\027\n\006result\030" +
+      "\001 \001(\0132\007.Result\0226\n\007feature\030\002 \003(\0132%.MultiG" +
+      "etFeatureResponse.FeatureEntry\032.\n\014Featur" +
+      "eEntry\022\013\n\003key\030\001 \001(\t\022\r\n\005value\030\002 \001(\t:\0028\0012X" +
+      "\n\020FeatureV2Service\022D\n\017MultiGetFeature\022\027." +
+      "MultiGetFeatureRequest\032\030.MultiGetFeature" +
+      "ResponseB8\n1com.tzld.piaoquan.recommend." +
+      "feature.model.featureP\001\210\001\001b\006proto3"
+    };
+    descriptor = com.google.protobuf.Descriptors.FileDescriptor
+      .internalBuildGeneratedFileFrom(descriptorData,
+        new com.google.protobuf.Descriptors.FileDescriptor[] {
+          com.google.protobuf.AnyProto.getDescriptor(),
+          com.tzld.piaoquan.recommend.feature.model.common.Common.getDescriptor(),
+        });
+    internal_static_MultiGetFeatureRequest_descriptor =
+      getDescriptor().getMessageTypes().get(0);
+    internal_static_MultiGetFeatureRequest_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_MultiGetFeatureRequest_descriptor,
+        new java.lang.String[] { "FeatureKey", });
+    internal_static_FeatureKeyProto_descriptor =
+      getDescriptor().getMessageTypes().get(1);
+    internal_static_FeatureKeyProto_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_FeatureKeyProto_descriptor,
+        new java.lang.String[] { "UniqueKey", "TableName", "FieldValue", });
+    internal_static_FeatureKeyProto_FieldValueEntry_descriptor =
+      internal_static_FeatureKeyProto_descriptor.getNestedTypes().get(0);
+    internal_static_FeatureKeyProto_FieldValueEntry_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_FeatureKeyProto_FieldValueEntry_descriptor,
+        new java.lang.String[] { "Key", "Value", });
+    internal_static_MultiGetFeatureResponse_descriptor =
+      getDescriptor().getMessageTypes().get(2);
+    internal_static_MultiGetFeatureResponse_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_MultiGetFeatureResponse_descriptor,
+        new java.lang.String[] { "Result", "Feature", });
+    internal_static_MultiGetFeatureResponse_FeatureEntry_descriptor =
+      internal_static_MultiGetFeatureResponse_descriptor.getNestedTypes().get(0);
+    internal_static_MultiGetFeatureResponse_FeatureEntry_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_MultiGetFeatureResponse_FeatureEntry_descriptor,
+        new java.lang.String[] { "Key", "Value", });
+    com.google.protobuf.AnyProto.getDescriptor();
+    com.tzld.piaoquan.recommend.feature.model.common.Common.getDescriptor();
+  }
+
+  // @@protoc_insertion_point(outer_class_scope)
+}

+ 237 - 0
recommend-feature-client/src/main/java/com/tzld/piaoquan/recommend/feature/model/feature/FeatureV2Service.java

@@ -0,0 +1,237 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: com/tzld/piaoquan/recommend/feature/featureV2.proto
+
+package com.tzld.piaoquan.recommend.feature.model.feature;
+
+/**
+ * Protobuf service {@code FeatureV2Service}
+ */
+public  abstract class FeatureV2Service
+    implements com.google.protobuf.Service {
+  protected FeatureV2Service() {}
+
+  public interface Interface {
+    /**
+     * <code>rpc MultiGetFeature(.MultiGetFeatureRequest) returns (.MultiGetFeatureResponse);</code>
+     */
+    public abstract void multiGetFeature(
+        com.google.protobuf.RpcController controller,
+        com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureRequest request,
+        com.google.protobuf.RpcCallback<com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureResponse> done);
+
+  }
+
+  public static com.google.protobuf.Service newReflectiveService(
+      final Interface impl) {
+    return new FeatureV2Service() {
+      @java.lang.Override
+      public  void multiGetFeature(
+          com.google.protobuf.RpcController controller,
+          com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureRequest request,
+          com.google.protobuf.RpcCallback<com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureResponse> done) {
+        impl.multiGetFeature(controller, request, done);
+      }
+
+    };
+  }
+
+  public static com.google.protobuf.BlockingService
+      newReflectiveBlockingService(final BlockingInterface impl) {
+    return new com.google.protobuf.BlockingService() {
+      public final com.google.protobuf.Descriptors.ServiceDescriptor
+          getDescriptorForType() {
+        return getDescriptor();
+      }
+
+      public final com.google.protobuf.Message callBlockingMethod(
+          com.google.protobuf.Descriptors.MethodDescriptor method,
+          com.google.protobuf.RpcController controller,
+          com.google.protobuf.Message request)
+          throws com.google.protobuf.ServiceException {
+        if (method.getService() != getDescriptor()) {
+          throw new java.lang.IllegalArgumentException(
+            "Service.callBlockingMethod() given method descriptor for " +
+            "wrong service type.");
+        }
+        switch(method.getIndex()) {
+          case 0:
+            return impl.multiGetFeature(controller, (com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureRequest)request);
+          default:
+            throw new java.lang.AssertionError("Can't get here.");
+        }
+      }
+
+      public final com.google.protobuf.Message
+          getRequestPrototype(
+          com.google.protobuf.Descriptors.MethodDescriptor method) {
+        if (method.getService() != getDescriptor()) {
+          throw new java.lang.IllegalArgumentException(
+            "Service.getRequestPrototype() given method " +
+            "descriptor for wrong service type.");
+        }
+        switch(method.getIndex()) {
+          case 0:
+            return com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureRequest.getDefaultInstance();
+          default:
+            throw new java.lang.AssertionError("Can't get here.");
+        }
+      }
+
+      public final com.google.protobuf.Message
+          getResponsePrototype(
+          com.google.protobuf.Descriptors.MethodDescriptor method) {
+        if (method.getService() != getDescriptor()) {
+          throw new java.lang.IllegalArgumentException(
+            "Service.getResponsePrototype() given method " +
+            "descriptor for wrong service type.");
+        }
+        switch(method.getIndex()) {
+          case 0:
+            return com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureResponse.getDefaultInstance();
+          default:
+            throw new java.lang.AssertionError("Can't get here.");
+        }
+      }
+
+    };
+  }
+
+  /**
+   * <code>rpc MultiGetFeature(.MultiGetFeatureRequest) returns (.MultiGetFeatureResponse);</code>
+   */
+  public abstract void multiGetFeature(
+      com.google.protobuf.RpcController controller,
+      com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureRequest request,
+      com.google.protobuf.RpcCallback<com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureResponse> done);
+
+  public static final
+      com.google.protobuf.Descriptors.ServiceDescriptor
+      getDescriptor() {
+    return com.tzld.piaoquan.recommend.feature.model.feature.FeatureV2.getDescriptor().getServices().get(0);
+  }
+  public final com.google.protobuf.Descriptors.ServiceDescriptor
+      getDescriptorForType() {
+    return getDescriptor();
+  }
+
+  public final void callMethod(
+      com.google.protobuf.Descriptors.MethodDescriptor method,
+      com.google.protobuf.RpcController controller,
+      com.google.protobuf.Message request,
+      com.google.protobuf.RpcCallback<
+        com.google.protobuf.Message> done) {
+    if (method.getService() != getDescriptor()) {
+      throw new java.lang.IllegalArgumentException(
+        "Service.callMethod() given method descriptor for wrong " +
+        "service type.");
+    }
+    switch(method.getIndex()) {
+      case 0:
+        this.multiGetFeature(controller, (com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureRequest)request,
+          com.google.protobuf.RpcUtil.<com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureResponse>specializeCallback(
+            done));
+        return;
+      default:
+        throw new java.lang.AssertionError("Can't get here.");
+    }
+  }
+
+  public final com.google.protobuf.Message
+      getRequestPrototype(
+      com.google.protobuf.Descriptors.MethodDescriptor method) {
+    if (method.getService() != getDescriptor()) {
+      throw new java.lang.IllegalArgumentException(
+        "Service.getRequestPrototype() given method " +
+        "descriptor for wrong service type.");
+    }
+    switch(method.getIndex()) {
+      case 0:
+        return com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureRequest.getDefaultInstance();
+      default:
+        throw new java.lang.AssertionError("Can't get here.");
+    }
+  }
+
+  public final com.google.protobuf.Message
+      getResponsePrototype(
+      com.google.protobuf.Descriptors.MethodDescriptor method) {
+    if (method.getService() != getDescriptor()) {
+      throw new java.lang.IllegalArgumentException(
+        "Service.getResponsePrototype() given method " +
+        "descriptor for wrong service type.");
+    }
+    switch(method.getIndex()) {
+      case 0:
+        return com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureResponse.getDefaultInstance();
+      default:
+        throw new java.lang.AssertionError("Can't get here.");
+    }
+  }
+
+  public static Stub newStub(
+      com.google.protobuf.RpcChannel channel) {
+    return new Stub(channel);
+  }
+
+  public static final class Stub extends com.tzld.piaoquan.recommend.feature.model.feature.FeatureV2Service implements Interface {
+    private Stub(com.google.protobuf.RpcChannel channel) {
+      this.channel = channel;
+    }
+
+    private final com.google.protobuf.RpcChannel channel;
+
+    public com.google.protobuf.RpcChannel getChannel() {
+      return channel;
+    }
+
+    public  void multiGetFeature(
+        com.google.protobuf.RpcController controller,
+        com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureRequest request,
+        com.google.protobuf.RpcCallback<com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureResponse> done) {
+      channel.callMethod(
+        getDescriptor().getMethods().get(0),
+        controller,
+        request,
+        com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureResponse.getDefaultInstance(),
+        com.google.protobuf.RpcUtil.generalizeCallback(
+          done,
+          com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureResponse.class,
+          com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureResponse.getDefaultInstance()));
+    }
+  }
+
+  public static BlockingInterface newBlockingStub(
+      com.google.protobuf.BlockingRpcChannel channel) {
+    return new BlockingStub(channel);
+  }
+
+  public interface BlockingInterface {
+    public com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureResponse multiGetFeature(
+        com.google.protobuf.RpcController controller,
+        com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureRequest request)
+        throws com.google.protobuf.ServiceException;
+  }
+
+  private static final class BlockingStub implements BlockingInterface {
+    private BlockingStub(com.google.protobuf.BlockingRpcChannel channel) {
+      this.channel = channel;
+    }
+
+    private final com.google.protobuf.BlockingRpcChannel channel;
+
+    public com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureResponse multiGetFeature(
+        com.google.protobuf.RpcController controller,
+        com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureRequest request)
+        throws com.google.protobuf.ServiceException {
+      return (com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureResponse) channel.callBlockingMethod(
+        getDescriptor().getMethods().get(0),
+        controller,
+        request,
+        com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureResponse.getDefaultInstance());
+    }
+
+  }
+
+  // @@protoc_insertion_point(class_scope:FeatureV2Service)
+}
+

+ 288 - 0
recommend-feature-client/src/main/java/com/tzld/piaoquan/recommend/feature/model/feature/FeatureV2ServiceGrpc.java

@@ -0,0 +1,288 @@
+package com.tzld.piaoquan.recommend.feature.model.feature;
+
+import static io.grpc.MethodDescriptor.generateFullMethodName;
+import static io.grpc.stub.ClientCalls.asyncBidiStreamingCall;
+import static io.grpc.stub.ClientCalls.asyncClientStreamingCall;
+import static io.grpc.stub.ClientCalls.asyncServerStreamingCall;
+import static io.grpc.stub.ClientCalls.asyncUnaryCall;
+import static io.grpc.stub.ClientCalls.blockingServerStreamingCall;
+import static io.grpc.stub.ClientCalls.blockingUnaryCall;
+import static io.grpc.stub.ClientCalls.futureUnaryCall;
+import static io.grpc.stub.ServerCalls.asyncBidiStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncClientStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncServerStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncUnaryCall;
+import static io.grpc.stub.ServerCalls.asyncUnimplementedStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall;
+
+/**
+ */
+@javax.annotation.Generated(
+    value = "by gRPC proto compiler (version 1.34.1)",
+    comments = "Source: com/tzld/piaoquan/recommend/feature/featureV2.proto")
+public final class FeatureV2ServiceGrpc {
+
+  private FeatureV2ServiceGrpc() {}
+
+  public static final String SERVICE_NAME = "FeatureV2Service";
+
+  // Static method descriptors that strictly reflect the proto.
+  private static volatile io.grpc.MethodDescriptor<com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureRequest,
+      com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureResponse> getMultiGetFeatureMethod;
+
+  @io.grpc.stub.annotations.RpcMethod(
+      fullMethodName = SERVICE_NAME + '/' + "MultiGetFeature",
+      requestType = com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureRequest.class,
+      responseType = com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureResponse.class,
+      methodType = io.grpc.MethodDescriptor.MethodType.UNARY)
+  public static io.grpc.MethodDescriptor<com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureRequest,
+      com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureResponse> getMultiGetFeatureMethod() {
+    io.grpc.MethodDescriptor<com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureRequest, com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureResponse> getMultiGetFeatureMethod;
+    if ((getMultiGetFeatureMethod = FeatureV2ServiceGrpc.getMultiGetFeatureMethod) == null) {
+      synchronized (FeatureV2ServiceGrpc.class) {
+        if ((getMultiGetFeatureMethod = FeatureV2ServiceGrpc.getMultiGetFeatureMethod) == null) {
+          FeatureV2ServiceGrpc.getMultiGetFeatureMethod = getMultiGetFeatureMethod =
+              io.grpc.MethodDescriptor.<com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureRequest, com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureResponse>newBuilder()
+              .setType(io.grpc.MethodDescriptor.MethodType.UNARY)
+              .setFullMethodName(generateFullMethodName(SERVICE_NAME, "MultiGetFeature"))
+              .setSampledToLocalTracing(true)
+              .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureRequest.getDefaultInstance()))
+              .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureResponse.getDefaultInstance()))
+              .setSchemaDescriptor(new FeatureV2ServiceMethodDescriptorSupplier("MultiGetFeature"))
+              .build();
+        }
+      }
+    }
+    return getMultiGetFeatureMethod;
+  }
+
+  /**
+   * Creates a new async stub that supports all call types for the service
+   */
+  public static FeatureV2ServiceStub newStub(io.grpc.Channel channel) {
+    io.grpc.stub.AbstractStub.StubFactory<FeatureV2ServiceStub> factory =
+      new io.grpc.stub.AbstractStub.StubFactory<FeatureV2ServiceStub>() {
+        @java.lang.Override
+        public FeatureV2ServiceStub newStub(io.grpc.Channel channel, io.grpc.CallOptions callOptions) {
+          return new FeatureV2ServiceStub(channel, callOptions);
+        }
+      };
+    return FeatureV2ServiceStub.newStub(factory, channel);
+  }
+
+  /**
+   * Creates a new blocking-style stub that supports unary and streaming output calls on the service
+   */
+  public static FeatureV2ServiceBlockingStub newBlockingStub(
+      io.grpc.Channel channel) {
+    io.grpc.stub.AbstractStub.StubFactory<FeatureV2ServiceBlockingStub> factory =
+      new io.grpc.stub.AbstractStub.StubFactory<FeatureV2ServiceBlockingStub>() {
+        @java.lang.Override
+        public FeatureV2ServiceBlockingStub newStub(io.grpc.Channel channel, io.grpc.CallOptions callOptions) {
+          return new FeatureV2ServiceBlockingStub(channel, callOptions);
+        }
+      };
+    return FeatureV2ServiceBlockingStub.newStub(factory, channel);
+  }
+
+  /**
+   * Creates a new ListenableFuture-style stub that supports unary calls on the service
+   */
+  public static FeatureV2ServiceFutureStub newFutureStub(
+      io.grpc.Channel channel) {
+    io.grpc.stub.AbstractStub.StubFactory<FeatureV2ServiceFutureStub> factory =
+      new io.grpc.stub.AbstractStub.StubFactory<FeatureV2ServiceFutureStub>() {
+        @java.lang.Override
+        public FeatureV2ServiceFutureStub newStub(io.grpc.Channel channel, io.grpc.CallOptions callOptions) {
+          return new FeatureV2ServiceFutureStub(channel, callOptions);
+        }
+      };
+    return FeatureV2ServiceFutureStub.newStub(factory, channel);
+  }
+
+  /**
+   */
+  public static abstract class FeatureV2ServiceImplBase implements io.grpc.BindableService {
+
+    /**
+     */
+    public void multiGetFeature(com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureRequest request,
+        io.grpc.stub.StreamObserver<com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureResponse> responseObserver) {
+      asyncUnimplementedUnaryCall(getMultiGetFeatureMethod(), responseObserver);
+    }
+
+    @java.lang.Override public final io.grpc.ServerServiceDefinition bindService() {
+      return io.grpc.ServerServiceDefinition.builder(getServiceDescriptor())
+          .addMethod(
+            getMultiGetFeatureMethod(),
+            asyncUnaryCall(
+              new MethodHandlers<
+                com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureRequest,
+                com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureResponse>(
+                  this, METHODID_MULTI_GET_FEATURE)))
+          .build();
+    }
+  }
+
+  /**
+   */
+  public static final class FeatureV2ServiceStub extends io.grpc.stub.AbstractAsyncStub<FeatureV2ServiceStub> {
+    private FeatureV2ServiceStub(
+        io.grpc.Channel channel, io.grpc.CallOptions callOptions) {
+      super(channel, callOptions);
+    }
+
+    @java.lang.Override
+    protected FeatureV2ServiceStub build(
+        io.grpc.Channel channel, io.grpc.CallOptions callOptions) {
+      return new FeatureV2ServiceStub(channel, callOptions);
+    }
+
+    /**
+     */
+    public void multiGetFeature(com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureRequest request,
+        io.grpc.stub.StreamObserver<com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureResponse> responseObserver) {
+      asyncUnaryCall(
+          getChannel().newCall(getMultiGetFeatureMethod(), getCallOptions()), request, responseObserver);
+    }
+  }
+
+  /**
+   */
+  public static final class FeatureV2ServiceBlockingStub extends io.grpc.stub.AbstractBlockingStub<FeatureV2ServiceBlockingStub> {
+    private FeatureV2ServiceBlockingStub(
+        io.grpc.Channel channel, io.grpc.CallOptions callOptions) {
+      super(channel, callOptions);
+    }
+
+    @java.lang.Override
+    protected FeatureV2ServiceBlockingStub build(
+        io.grpc.Channel channel, io.grpc.CallOptions callOptions) {
+      return new FeatureV2ServiceBlockingStub(channel, callOptions);
+    }
+
+    /**
+     */
+    public com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureResponse multiGetFeature(com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureRequest request) {
+      return blockingUnaryCall(
+          getChannel(), getMultiGetFeatureMethod(), getCallOptions(), request);
+    }
+  }
+
+  /**
+   */
+  public static final class FeatureV2ServiceFutureStub extends io.grpc.stub.AbstractFutureStub<FeatureV2ServiceFutureStub> {
+    private FeatureV2ServiceFutureStub(
+        io.grpc.Channel channel, io.grpc.CallOptions callOptions) {
+      super(channel, callOptions);
+    }
+
+    @java.lang.Override
+    protected FeatureV2ServiceFutureStub build(
+        io.grpc.Channel channel, io.grpc.CallOptions callOptions) {
+      return new FeatureV2ServiceFutureStub(channel, callOptions);
+    }
+
+    /**
+     */
+    public com.google.common.util.concurrent.ListenableFuture<com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureResponse> multiGetFeature(
+        com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureRequest request) {
+      return futureUnaryCall(
+          getChannel().newCall(getMultiGetFeatureMethod(), getCallOptions()), request);
+    }
+  }
+
+  private static final int METHODID_MULTI_GET_FEATURE = 0;
+
+  private static final class MethodHandlers<Req, Resp> implements
+      io.grpc.stub.ServerCalls.UnaryMethod<Req, Resp>,
+      io.grpc.stub.ServerCalls.ServerStreamingMethod<Req, Resp>,
+      io.grpc.stub.ServerCalls.ClientStreamingMethod<Req, Resp>,
+      io.grpc.stub.ServerCalls.BidiStreamingMethod<Req, Resp> {
+    private final FeatureV2ServiceImplBase serviceImpl;
+    private final int methodId;
+
+    MethodHandlers(FeatureV2ServiceImplBase serviceImpl, int methodId) {
+      this.serviceImpl = serviceImpl;
+      this.methodId = methodId;
+    }
+
+    @java.lang.Override
+    @java.lang.SuppressWarnings("unchecked")
+    public void invoke(Req request, io.grpc.stub.StreamObserver<Resp> responseObserver) {
+      switch (methodId) {
+        case METHODID_MULTI_GET_FEATURE:
+          serviceImpl.multiGetFeature((com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureRequest) request,
+              (io.grpc.stub.StreamObserver<com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureResponse>) responseObserver);
+          break;
+        default:
+          throw new AssertionError();
+      }
+    }
+
+    @java.lang.Override
+    @java.lang.SuppressWarnings("unchecked")
+    public io.grpc.stub.StreamObserver<Req> invoke(
+        io.grpc.stub.StreamObserver<Resp> responseObserver) {
+      switch (methodId) {
+        default:
+          throw new AssertionError();
+      }
+    }
+  }
+
+  private static abstract class FeatureV2ServiceBaseDescriptorSupplier
+      implements io.grpc.protobuf.ProtoFileDescriptorSupplier, io.grpc.protobuf.ProtoServiceDescriptorSupplier {
+    FeatureV2ServiceBaseDescriptorSupplier() {}
+
+    @java.lang.Override
+    public com.google.protobuf.Descriptors.FileDescriptor getFileDescriptor() {
+      return com.tzld.piaoquan.recommend.feature.model.feature.FeatureV2.getDescriptor();
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Descriptors.ServiceDescriptor getServiceDescriptor() {
+      return getFileDescriptor().findServiceByName("FeatureV2Service");
+    }
+  }
+
+  private static final class FeatureV2ServiceFileDescriptorSupplier
+      extends FeatureV2ServiceBaseDescriptorSupplier {
+    FeatureV2ServiceFileDescriptorSupplier() {}
+  }
+
+  private static final class FeatureV2ServiceMethodDescriptorSupplier
+      extends FeatureV2ServiceBaseDescriptorSupplier
+      implements io.grpc.protobuf.ProtoMethodDescriptorSupplier {
+    private final String methodName;
+
+    FeatureV2ServiceMethodDescriptorSupplier(String methodName) {
+      this.methodName = methodName;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Descriptors.MethodDescriptor getMethodDescriptor() {
+      return getServiceDescriptor().findMethodByName(methodName);
+    }
+  }
+
+  private static volatile io.grpc.ServiceDescriptor serviceDescriptor;
+
+  public static io.grpc.ServiceDescriptor getServiceDescriptor() {
+    io.grpc.ServiceDescriptor result = serviceDescriptor;
+    if (result == null) {
+      synchronized (FeatureV2ServiceGrpc.class) {
+        result = serviceDescriptor;
+        if (result == null) {
+          serviceDescriptor = result = io.grpc.ServiceDescriptor.newBuilder(SERVICE_NAME)
+              .setSchemaDescriptor(new FeatureV2ServiceFileDescriptorSupplier())
+              .addMethod(getMultiGetFeatureMethod())
+              .build();
+        }
+      }
+    }
+    return result;
+  }
+}

+ 770 - 0
recommend-feature-client/src/main/java/com/tzld/piaoquan/recommend/feature/model/feature/MultiGetFeatureRequest.java

@@ -0,0 +1,770 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: com/tzld/piaoquan/recommend/feature/featureV2.proto
+
+package com.tzld.piaoquan.recommend.feature.model.feature;
+
+/**
+ * Protobuf type {@code MultiGetFeatureRequest}
+ */
+public final class MultiGetFeatureRequest extends
+    com.google.protobuf.GeneratedMessageV3 implements
+    // @@protoc_insertion_point(message_implements:MultiGetFeatureRequest)
+    MultiGetFeatureRequestOrBuilder {
+private static final long serialVersionUID = 0L;
+  // Use MultiGetFeatureRequest.newBuilder() to construct.
+  private MultiGetFeatureRequest(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+    super(builder);
+  }
+  private MultiGetFeatureRequest() {
+    featureKey_ = java.util.Collections.emptyList();
+  }
+
+  @java.lang.Override
+  @SuppressWarnings({"unused"})
+  protected java.lang.Object newInstance(
+      UnusedPrivateParameter unused) {
+    return new MultiGetFeatureRequest();
+  }
+
+  @java.lang.Override
+  public final com.google.protobuf.UnknownFieldSet
+  getUnknownFields() {
+    return this.unknownFields;
+  }
+  private MultiGetFeatureRequest(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    this();
+    if (extensionRegistry == null) {
+      throw new java.lang.NullPointerException();
+    }
+    int mutable_bitField0_ = 0;
+    com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+        com.google.protobuf.UnknownFieldSet.newBuilder();
+    try {
+      boolean done = false;
+      while (!done) {
+        int tag = input.readTag();
+        switch (tag) {
+          case 0:
+            done = true;
+            break;
+          case 10: {
+            if (!((mutable_bitField0_ & 0x00000001) != 0)) {
+              featureKey_ = new java.util.ArrayList<com.tzld.piaoquan.recommend.feature.model.feature.FeatureKeyProto>();
+              mutable_bitField0_ |= 0x00000001;
+            }
+            featureKey_.add(
+                input.readMessage(com.tzld.piaoquan.recommend.feature.model.feature.FeatureKeyProto.parser(), extensionRegistry));
+            break;
+          }
+          default: {
+            if (!parseUnknownField(
+                input, unknownFields, extensionRegistry, tag)) {
+              done = true;
+            }
+            break;
+          }
+        }
+      }
+    } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+      throw e.setUnfinishedMessage(this);
+    } catch (java.io.IOException e) {
+      throw new com.google.protobuf.InvalidProtocolBufferException(
+          e).setUnfinishedMessage(this);
+    } finally {
+      if (((mutable_bitField0_ & 0x00000001) != 0)) {
+        featureKey_ = java.util.Collections.unmodifiableList(featureKey_);
+      }
+      this.unknownFields = unknownFields.build();
+      makeExtensionsImmutable();
+    }
+  }
+  public static final com.google.protobuf.Descriptors.Descriptor
+      getDescriptor() {
+    return com.tzld.piaoquan.recommend.feature.model.feature.FeatureV2.internal_static_MultiGetFeatureRequest_descriptor;
+  }
+
+  @java.lang.Override
+  protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internalGetFieldAccessorTable() {
+    return com.tzld.piaoquan.recommend.feature.model.feature.FeatureV2.internal_static_MultiGetFeatureRequest_fieldAccessorTable
+        .ensureFieldAccessorsInitialized(
+            com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureRequest.class, com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureRequest.Builder.class);
+  }
+
+  public static final int FEATURE_KEY_FIELD_NUMBER = 1;
+  private java.util.List<com.tzld.piaoquan.recommend.feature.model.feature.FeatureKeyProto> featureKey_;
+  /**
+   * <code>repeated .FeatureKeyProto feature_key = 1;</code>
+   */
+  @java.lang.Override
+  public java.util.List<com.tzld.piaoquan.recommend.feature.model.feature.FeatureKeyProto> getFeatureKeyList() {
+    return featureKey_;
+  }
+  /**
+   * <code>repeated .FeatureKeyProto feature_key = 1;</code>
+   */
+  @java.lang.Override
+  public java.util.List<? extends com.tzld.piaoquan.recommend.feature.model.feature.FeatureKeyProtoOrBuilder> 
+      getFeatureKeyOrBuilderList() {
+    return featureKey_;
+  }
+  /**
+   * <code>repeated .FeatureKeyProto feature_key = 1;</code>
+   */
+  @java.lang.Override
+  public int getFeatureKeyCount() {
+    return featureKey_.size();
+  }
+  /**
+   * <code>repeated .FeatureKeyProto feature_key = 1;</code>
+   */
+  @java.lang.Override
+  public com.tzld.piaoquan.recommend.feature.model.feature.FeatureKeyProto getFeatureKey(int index) {
+    return featureKey_.get(index);
+  }
+  /**
+   * <code>repeated .FeatureKeyProto feature_key = 1;</code>
+   */
+  @java.lang.Override
+  public com.tzld.piaoquan.recommend.feature.model.feature.FeatureKeyProtoOrBuilder getFeatureKeyOrBuilder(
+      int index) {
+    return featureKey_.get(index);
+  }
+
+  private byte memoizedIsInitialized = -1;
+  @java.lang.Override
+  public final boolean isInitialized() {
+    byte isInitialized = memoizedIsInitialized;
+    if (isInitialized == 1) return true;
+    if (isInitialized == 0) return false;
+
+    memoizedIsInitialized = 1;
+    return true;
+  }
+
+  @java.lang.Override
+  public void writeTo(com.google.protobuf.CodedOutputStream output)
+                      throws java.io.IOException {
+    for (int i = 0; i < featureKey_.size(); i++) {
+      output.writeMessage(1, featureKey_.get(i));
+    }
+    unknownFields.writeTo(output);
+  }
+
+  @java.lang.Override
+  public int getSerializedSize() {
+    int size = memoizedSize;
+    if (size != -1) return size;
+
+    size = 0;
+    for (int i = 0; i < featureKey_.size(); i++) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeMessageSize(1, featureKey_.get(i));
+    }
+    size += unknownFields.getSerializedSize();
+    memoizedSize = size;
+    return size;
+  }
+
+  @java.lang.Override
+  public boolean equals(final java.lang.Object obj) {
+    if (obj == this) {
+     return true;
+    }
+    if (!(obj instanceof com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureRequest)) {
+      return super.equals(obj);
+    }
+    com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureRequest other = (com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureRequest) obj;
+
+    if (!getFeatureKeyList()
+        .equals(other.getFeatureKeyList())) return false;
+    if (!unknownFields.equals(other.unknownFields)) return false;
+    return true;
+  }
+
+  @java.lang.Override
+  public int hashCode() {
+    if (memoizedHashCode != 0) {
+      return memoizedHashCode;
+    }
+    int hash = 41;
+    hash = (19 * hash) + getDescriptor().hashCode();
+    if (getFeatureKeyCount() > 0) {
+      hash = (37 * hash) + FEATURE_KEY_FIELD_NUMBER;
+      hash = (53 * hash) + getFeatureKeyList().hashCode();
+    }
+    hash = (29 * hash) + unknownFields.hashCode();
+    memoizedHashCode = hash;
+    return hash;
+  }
+
+  public static com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureRequest parseFrom(
+      java.nio.ByteBuffer data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureRequest parseFrom(
+      java.nio.ByteBuffer data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureRequest parseFrom(
+      com.google.protobuf.ByteString data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureRequest parseFrom(
+      com.google.protobuf.ByteString data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureRequest parseFrom(byte[] data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureRequest parseFrom(
+      byte[] data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureRequest parseFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureRequest parseFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureRequest parseDelimitedFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input);
+  }
+  public static com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureRequest parseDelimitedFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureRequest parseFrom(
+      com.google.protobuf.CodedInputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureRequest parseFrom(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+
+  @java.lang.Override
+  public Builder newBuilderForType() { return newBuilder(); }
+  public static Builder newBuilder() {
+    return DEFAULT_INSTANCE.toBuilder();
+  }
+  public static Builder newBuilder(com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureRequest prototype) {
+    return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+  }
+  @java.lang.Override
+  public Builder toBuilder() {
+    return this == DEFAULT_INSTANCE
+        ? new Builder() : new Builder().mergeFrom(this);
+  }
+
+  @java.lang.Override
+  protected Builder newBuilderForType(
+      com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+    Builder builder = new Builder(parent);
+    return builder;
+  }
+  /**
+   * Protobuf type {@code MultiGetFeatureRequest}
+   */
+  public static final class Builder extends
+      com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+      // @@protoc_insertion_point(builder_implements:MultiGetFeatureRequest)
+      com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureRequestOrBuilder {
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return com.tzld.piaoquan.recommend.feature.model.feature.FeatureV2.internal_static_MultiGetFeatureRequest_descriptor;
+    }
+
+    @java.lang.Override
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return com.tzld.piaoquan.recommend.feature.model.feature.FeatureV2.internal_static_MultiGetFeatureRequest_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureRequest.class, com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureRequest.Builder.class);
+    }
+
+    // Construct using com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureRequest.newBuilder()
+    private Builder() {
+      maybeForceBuilderInitialization();
+    }
+
+    private Builder(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      super(parent);
+      maybeForceBuilderInitialization();
+    }
+    private void maybeForceBuilderInitialization() {
+      if (com.google.protobuf.GeneratedMessageV3
+              .alwaysUseFieldBuilders) {
+        getFeatureKeyFieldBuilder();
+      }
+    }
+    @java.lang.Override
+    public Builder clear() {
+      super.clear();
+      if (featureKeyBuilder_ == null) {
+        featureKey_ = java.util.Collections.emptyList();
+        bitField0_ = (bitField0_ & ~0x00000001);
+      } else {
+        featureKeyBuilder_.clear();
+      }
+      return this;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Descriptors.Descriptor
+        getDescriptorForType() {
+      return com.tzld.piaoquan.recommend.feature.model.feature.FeatureV2.internal_static_MultiGetFeatureRequest_descriptor;
+    }
+
+    @java.lang.Override
+    public com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureRequest getDefaultInstanceForType() {
+      return com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureRequest.getDefaultInstance();
+    }
+
+    @java.lang.Override
+    public com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureRequest build() {
+      com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureRequest result = buildPartial();
+      if (!result.isInitialized()) {
+        throw newUninitializedMessageException(result);
+      }
+      return result;
+    }
+
+    @java.lang.Override
+    public com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureRequest buildPartial() {
+      com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureRequest result = new com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureRequest(this);
+      int from_bitField0_ = bitField0_;
+      if (featureKeyBuilder_ == null) {
+        if (((bitField0_ & 0x00000001) != 0)) {
+          featureKey_ = java.util.Collections.unmodifiableList(featureKey_);
+          bitField0_ = (bitField0_ & ~0x00000001);
+        }
+        result.featureKey_ = featureKey_;
+      } else {
+        result.featureKey_ = featureKeyBuilder_.build();
+      }
+      onBuilt();
+      return result;
+    }
+
+    @java.lang.Override
+    public Builder clone() {
+      return super.clone();
+    }
+    @java.lang.Override
+    public Builder setField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return super.setField(field, value);
+    }
+    @java.lang.Override
+    public Builder clearField(
+        com.google.protobuf.Descriptors.FieldDescriptor field) {
+      return super.clearField(field);
+    }
+    @java.lang.Override
+    public Builder clearOneof(
+        com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+      return super.clearOneof(oneof);
+    }
+    @java.lang.Override
+    public Builder setRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        int index, java.lang.Object value) {
+      return super.setRepeatedField(field, index, value);
+    }
+    @java.lang.Override
+    public Builder addRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return super.addRepeatedField(field, value);
+    }
+    @java.lang.Override
+    public Builder mergeFrom(com.google.protobuf.Message other) {
+      if (other instanceof com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureRequest) {
+        return mergeFrom((com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureRequest)other);
+      } else {
+        super.mergeFrom(other);
+        return this;
+      }
+    }
+
+    public Builder mergeFrom(com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureRequest other) {
+      if (other == com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureRequest.getDefaultInstance()) return this;
+      if (featureKeyBuilder_ == null) {
+        if (!other.featureKey_.isEmpty()) {
+          if (featureKey_.isEmpty()) {
+            featureKey_ = other.featureKey_;
+            bitField0_ = (bitField0_ & ~0x00000001);
+          } else {
+            ensureFeatureKeyIsMutable();
+            featureKey_.addAll(other.featureKey_);
+          }
+          onChanged();
+        }
+      } else {
+        if (!other.featureKey_.isEmpty()) {
+          if (featureKeyBuilder_.isEmpty()) {
+            featureKeyBuilder_.dispose();
+            featureKeyBuilder_ = null;
+            featureKey_ = other.featureKey_;
+            bitField0_ = (bitField0_ & ~0x00000001);
+            featureKeyBuilder_ = 
+              com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders ?
+                 getFeatureKeyFieldBuilder() : null;
+          } else {
+            featureKeyBuilder_.addAllMessages(other.featureKey_);
+          }
+        }
+      }
+      this.mergeUnknownFields(other.unknownFields);
+      onChanged();
+      return this;
+    }
+
+    @java.lang.Override
+    public final boolean isInitialized() {
+      return true;
+    }
+
+    @java.lang.Override
+    public Builder mergeFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureRequest parsedMessage = null;
+      try {
+        parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        parsedMessage = (com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureRequest) e.getUnfinishedMessage();
+        throw e.unwrapIOException();
+      } finally {
+        if (parsedMessage != null) {
+          mergeFrom(parsedMessage);
+        }
+      }
+      return this;
+    }
+    private int bitField0_;
+
+    private java.util.List<com.tzld.piaoquan.recommend.feature.model.feature.FeatureKeyProto> featureKey_ =
+      java.util.Collections.emptyList();
+    private void ensureFeatureKeyIsMutable() {
+      if (!((bitField0_ & 0x00000001) != 0)) {
+        featureKey_ = new java.util.ArrayList<com.tzld.piaoquan.recommend.feature.model.feature.FeatureKeyProto>(featureKey_);
+        bitField0_ |= 0x00000001;
+       }
+    }
+
+    private com.google.protobuf.RepeatedFieldBuilderV3<
+        com.tzld.piaoquan.recommend.feature.model.feature.FeatureKeyProto, com.tzld.piaoquan.recommend.feature.model.feature.FeatureKeyProto.Builder, com.tzld.piaoquan.recommend.feature.model.feature.FeatureKeyProtoOrBuilder> featureKeyBuilder_;
+
+    /**
+     * <code>repeated .FeatureKeyProto feature_key = 1;</code>
+     */
+    public java.util.List<com.tzld.piaoquan.recommend.feature.model.feature.FeatureKeyProto> getFeatureKeyList() {
+      if (featureKeyBuilder_ == null) {
+        return java.util.Collections.unmodifiableList(featureKey_);
+      } else {
+        return featureKeyBuilder_.getMessageList();
+      }
+    }
+    /**
+     * <code>repeated .FeatureKeyProto feature_key = 1;</code>
+     */
+    public int getFeatureKeyCount() {
+      if (featureKeyBuilder_ == null) {
+        return featureKey_.size();
+      } else {
+        return featureKeyBuilder_.getCount();
+      }
+    }
+    /**
+     * <code>repeated .FeatureKeyProto feature_key = 1;</code>
+     */
+    public com.tzld.piaoquan.recommend.feature.model.feature.FeatureKeyProto getFeatureKey(int index) {
+      if (featureKeyBuilder_ == null) {
+        return featureKey_.get(index);
+      } else {
+        return featureKeyBuilder_.getMessage(index);
+      }
+    }
+    /**
+     * <code>repeated .FeatureKeyProto feature_key = 1;</code>
+     */
+    public Builder setFeatureKey(
+        int index, com.tzld.piaoquan.recommend.feature.model.feature.FeatureKeyProto value) {
+      if (featureKeyBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        ensureFeatureKeyIsMutable();
+        featureKey_.set(index, value);
+        onChanged();
+      } else {
+        featureKeyBuilder_.setMessage(index, value);
+      }
+      return this;
+    }
+    /**
+     * <code>repeated .FeatureKeyProto feature_key = 1;</code>
+     */
+    public Builder setFeatureKey(
+        int index, com.tzld.piaoquan.recommend.feature.model.feature.FeatureKeyProto.Builder builderForValue) {
+      if (featureKeyBuilder_ == null) {
+        ensureFeatureKeyIsMutable();
+        featureKey_.set(index, builderForValue.build());
+        onChanged();
+      } else {
+        featureKeyBuilder_.setMessage(index, builderForValue.build());
+      }
+      return this;
+    }
+    /**
+     * <code>repeated .FeatureKeyProto feature_key = 1;</code>
+     */
+    public Builder addFeatureKey(com.tzld.piaoquan.recommend.feature.model.feature.FeatureKeyProto value) {
+      if (featureKeyBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        ensureFeatureKeyIsMutable();
+        featureKey_.add(value);
+        onChanged();
+      } else {
+        featureKeyBuilder_.addMessage(value);
+      }
+      return this;
+    }
+    /**
+     * <code>repeated .FeatureKeyProto feature_key = 1;</code>
+     */
+    public Builder addFeatureKey(
+        int index, com.tzld.piaoquan.recommend.feature.model.feature.FeatureKeyProto value) {
+      if (featureKeyBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        ensureFeatureKeyIsMutable();
+        featureKey_.add(index, value);
+        onChanged();
+      } else {
+        featureKeyBuilder_.addMessage(index, value);
+      }
+      return this;
+    }
+    /**
+     * <code>repeated .FeatureKeyProto feature_key = 1;</code>
+     */
+    public Builder addFeatureKey(
+        com.tzld.piaoquan.recommend.feature.model.feature.FeatureKeyProto.Builder builderForValue) {
+      if (featureKeyBuilder_ == null) {
+        ensureFeatureKeyIsMutable();
+        featureKey_.add(builderForValue.build());
+        onChanged();
+      } else {
+        featureKeyBuilder_.addMessage(builderForValue.build());
+      }
+      return this;
+    }
+    /**
+     * <code>repeated .FeatureKeyProto feature_key = 1;</code>
+     */
+    public Builder addFeatureKey(
+        int index, com.tzld.piaoquan.recommend.feature.model.feature.FeatureKeyProto.Builder builderForValue) {
+      if (featureKeyBuilder_ == null) {
+        ensureFeatureKeyIsMutable();
+        featureKey_.add(index, builderForValue.build());
+        onChanged();
+      } else {
+        featureKeyBuilder_.addMessage(index, builderForValue.build());
+      }
+      return this;
+    }
+    /**
+     * <code>repeated .FeatureKeyProto feature_key = 1;</code>
+     */
+    public Builder addAllFeatureKey(
+        java.lang.Iterable<? extends com.tzld.piaoquan.recommend.feature.model.feature.FeatureKeyProto> values) {
+      if (featureKeyBuilder_ == null) {
+        ensureFeatureKeyIsMutable();
+        com.google.protobuf.AbstractMessageLite.Builder.addAll(
+            values, featureKey_);
+        onChanged();
+      } else {
+        featureKeyBuilder_.addAllMessages(values);
+      }
+      return this;
+    }
+    /**
+     * <code>repeated .FeatureKeyProto feature_key = 1;</code>
+     */
+    public Builder clearFeatureKey() {
+      if (featureKeyBuilder_ == null) {
+        featureKey_ = java.util.Collections.emptyList();
+        bitField0_ = (bitField0_ & ~0x00000001);
+        onChanged();
+      } else {
+        featureKeyBuilder_.clear();
+      }
+      return this;
+    }
+    /**
+     * <code>repeated .FeatureKeyProto feature_key = 1;</code>
+     */
+    public Builder removeFeatureKey(int index) {
+      if (featureKeyBuilder_ == null) {
+        ensureFeatureKeyIsMutable();
+        featureKey_.remove(index);
+        onChanged();
+      } else {
+        featureKeyBuilder_.remove(index);
+      }
+      return this;
+    }
+    /**
+     * <code>repeated .FeatureKeyProto feature_key = 1;</code>
+     */
+    public com.tzld.piaoquan.recommend.feature.model.feature.FeatureKeyProto.Builder getFeatureKeyBuilder(
+        int index) {
+      return getFeatureKeyFieldBuilder().getBuilder(index);
+    }
+    /**
+     * <code>repeated .FeatureKeyProto feature_key = 1;</code>
+     */
+    public com.tzld.piaoquan.recommend.feature.model.feature.FeatureKeyProtoOrBuilder getFeatureKeyOrBuilder(
+        int index) {
+      if (featureKeyBuilder_ == null) {
+        return featureKey_.get(index);  } else {
+        return featureKeyBuilder_.getMessageOrBuilder(index);
+      }
+    }
+    /**
+     * <code>repeated .FeatureKeyProto feature_key = 1;</code>
+     */
+    public java.util.List<? extends com.tzld.piaoquan.recommend.feature.model.feature.FeatureKeyProtoOrBuilder> 
+         getFeatureKeyOrBuilderList() {
+      if (featureKeyBuilder_ != null) {
+        return featureKeyBuilder_.getMessageOrBuilderList();
+      } else {
+        return java.util.Collections.unmodifiableList(featureKey_);
+      }
+    }
+    /**
+     * <code>repeated .FeatureKeyProto feature_key = 1;</code>
+     */
+    public com.tzld.piaoquan.recommend.feature.model.feature.FeatureKeyProto.Builder addFeatureKeyBuilder() {
+      return getFeatureKeyFieldBuilder().addBuilder(
+          com.tzld.piaoquan.recommend.feature.model.feature.FeatureKeyProto.getDefaultInstance());
+    }
+    /**
+     * <code>repeated .FeatureKeyProto feature_key = 1;</code>
+     */
+    public com.tzld.piaoquan.recommend.feature.model.feature.FeatureKeyProto.Builder addFeatureKeyBuilder(
+        int index) {
+      return getFeatureKeyFieldBuilder().addBuilder(
+          index, com.tzld.piaoquan.recommend.feature.model.feature.FeatureKeyProto.getDefaultInstance());
+    }
+    /**
+     * <code>repeated .FeatureKeyProto feature_key = 1;</code>
+     */
+    public java.util.List<com.tzld.piaoquan.recommend.feature.model.feature.FeatureKeyProto.Builder> 
+         getFeatureKeyBuilderList() {
+      return getFeatureKeyFieldBuilder().getBuilderList();
+    }
+    private com.google.protobuf.RepeatedFieldBuilderV3<
+        com.tzld.piaoquan.recommend.feature.model.feature.FeatureKeyProto, com.tzld.piaoquan.recommend.feature.model.feature.FeatureKeyProto.Builder, com.tzld.piaoquan.recommend.feature.model.feature.FeatureKeyProtoOrBuilder> 
+        getFeatureKeyFieldBuilder() {
+      if (featureKeyBuilder_ == null) {
+        featureKeyBuilder_ = new com.google.protobuf.RepeatedFieldBuilderV3<
+            com.tzld.piaoquan.recommend.feature.model.feature.FeatureKeyProto, com.tzld.piaoquan.recommend.feature.model.feature.FeatureKeyProto.Builder, com.tzld.piaoquan.recommend.feature.model.feature.FeatureKeyProtoOrBuilder>(
+                featureKey_,
+                ((bitField0_ & 0x00000001) != 0),
+                getParentForChildren(),
+                isClean());
+        featureKey_ = null;
+      }
+      return featureKeyBuilder_;
+    }
+    @java.lang.Override
+    public final Builder setUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.setUnknownFields(unknownFields);
+    }
+
+    @java.lang.Override
+    public final Builder mergeUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.mergeUnknownFields(unknownFields);
+    }
+
+
+    // @@protoc_insertion_point(builder_scope:MultiGetFeatureRequest)
+  }
+
+  // @@protoc_insertion_point(class_scope:MultiGetFeatureRequest)
+  private static final com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureRequest DEFAULT_INSTANCE;
+  static {
+    DEFAULT_INSTANCE = new com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureRequest();
+  }
+
+  public static com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureRequest getDefaultInstance() {
+    return DEFAULT_INSTANCE;
+  }
+
+  private static final com.google.protobuf.Parser<MultiGetFeatureRequest>
+      PARSER = new com.google.protobuf.AbstractParser<MultiGetFeatureRequest>() {
+    @java.lang.Override
+    public MultiGetFeatureRequest parsePartialFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return new MultiGetFeatureRequest(input, extensionRegistry);
+    }
+  };
+
+  public static com.google.protobuf.Parser<MultiGetFeatureRequest> parser() {
+    return PARSER;
+  }
+
+  @java.lang.Override
+  public com.google.protobuf.Parser<MultiGetFeatureRequest> getParserForType() {
+    return PARSER;
+  }
+
+  @java.lang.Override
+  public com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureRequest getDefaultInstanceForType() {
+    return DEFAULT_INSTANCE;
+  }
+
+}
+

+ 33 - 0
recommend-feature-client/src/main/java/com/tzld/piaoquan/recommend/feature/model/feature/MultiGetFeatureRequestOrBuilder.java

@@ -0,0 +1,33 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: com/tzld/piaoquan/recommend/feature/featureV2.proto
+
+package com.tzld.piaoquan.recommend.feature.model.feature;
+
+public interface MultiGetFeatureRequestOrBuilder extends
+    // @@protoc_insertion_point(interface_extends:MultiGetFeatureRequest)
+    com.google.protobuf.MessageOrBuilder {
+
+  /**
+   * <code>repeated .FeatureKeyProto feature_key = 1;</code>
+   */
+  java.util.List<com.tzld.piaoquan.recommend.feature.model.feature.FeatureKeyProto> 
+      getFeatureKeyList();
+  /**
+   * <code>repeated .FeatureKeyProto feature_key = 1;</code>
+   */
+  com.tzld.piaoquan.recommend.feature.model.feature.FeatureKeyProto getFeatureKey(int index);
+  /**
+   * <code>repeated .FeatureKeyProto feature_key = 1;</code>
+   */
+  int getFeatureKeyCount();
+  /**
+   * <code>repeated .FeatureKeyProto feature_key = 1;</code>
+   */
+  java.util.List<? extends com.tzld.piaoquan.recommend.feature.model.feature.FeatureKeyProtoOrBuilder> 
+      getFeatureKeyOrBuilderList();
+  /**
+   * <code>repeated .FeatureKeyProto feature_key = 1;</code>
+   */
+  com.tzld.piaoquan.recommend.feature.model.feature.FeatureKeyProtoOrBuilder getFeatureKeyOrBuilder(
+      int index);
+}

+ 893 - 0
recommend-feature-client/src/main/java/com/tzld/piaoquan/recommend/feature/model/feature/MultiGetFeatureResponse.java

@@ -0,0 +1,893 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: com/tzld/piaoquan/recommend/feature/featureV2.proto
+
+package com.tzld.piaoquan.recommend.feature.model.feature;
+
+/**
+ * Protobuf type {@code MultiGetFeatureResponse}
+ */
+public final class MultiGetFeatureResponse extends
+    com.google.protobuf.GeneratedMessageV3 implements
+    // @@protoc_insertion_point(message_implements:MultiGetFeatureResponse)
+    MultiGetFeatureResponseOrBuilder {
+private static final long serialVersionUID = 0L;
+  // Use MultiGetFeatureResponse.newBuilder() to construct.
+  private MultiGetFeatureResponse(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+    super(builder);
+  }
+  private MultiGetFeatureResponse() {
+  }
+
+  @java.lang.Override
+  @SuppressWarnings({"unused"})
+  protected java.lang.Object newInstance(
+      UnusedPrivateParameter unused) {
+    return new MultiGetFeatureResponse();
+  }
+
+  @java.lang.Override
+  public final com.google.protobuf.UnknownFieldSet
+  getUnknownFields() {
+    return this.unknownFields;
+  }
+  private MultiGetFeatureResponse(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    this();
+    if (extensionRegistry == null) {
+      throw new java.lang.NullPointerException();
+    }
+    int mutable_bitField0_ = 0;
+    com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+        com.google.protobuf.UnknownFieldSet.newBuilder();
+    try {
+      boolean done = false;
+      while (!done) {
+        int tag = input.readTag();
+        switch (tag) {
+          case 0:
+            done = true;
+            break;
+          case 10: {
+            com.tzld.piaoquan.recommend.feature.model.common.Result.Builder subBuilder = null;
+            if (result_ != null) {
+              subBuilder = result_.toBuilder();
+            }
+            result_ = input.readMessage(com.tzld.piaoquan.recommend.feature.model.common.Result.parser(), extensionRegistry);
+            if (subBuilder != null) {
+              subBuilder.mergeFrom(result_);
+              result_ = subBuilder.buildPartial();
+            }
+
+            break;
+          }
+          case 18: {
+            if (!((mutable_bitField0_ & 0x00000001) != 0)) {
+              feature_ = com.google.protobuf.MapField.newMapField(
+                  FeatureDefaultEntryHolder.defaultEntry);
+              mutable_bitField0_ |= 0x00000001;
+            }
+            com.google.protobuf.MapEntry<java.lang.String, java.lang.String>
+            feature__ = input.readMessage(
+                FeatureDefaultEntryHolder.defaultEntry.getParserForType(), extensionRegistry);
+            feature_.getMutableMap().put(
+                feature__.getKey(), feature__.getValue());
+            break;
+          }
+          default: {
+            if (!parseUnknownField(
+                input, unknownFields, extensionRegistry, tag)) {
+              done = true;
+            }
+            break;
+          }
+        }
+      }
+    } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+      throw e.setUnfinishedMessage(this);
+    } catch (java.io.IOException e) {
+      throw new com.google.protobuf.InvalidProtocolBufferException(
+          e).setUnfinishedMessage(this);
+    } finally {
+      this.unknownFields = unknownFields.build();
+      makeExtensionsImmutable();
+    }
+  }
+  public static final com.google.protobuf.Descriptors.Descriptor
+      getDescriptor() {
+    return com.tzld.piaoquan.recommend.feature.model.feature.FeatureV2.internal_static_MultiGetFeatureResponse_descriptor;
+  }
+
+  @SuppressWarnings({"rawtypes"})
+  @java.lang.Override
+  protected com.google.protobuf.MapField internalGetMapField(
+      int number) {
+    switch (number) {
+      case 2:
+        return internalGetFeature();
+      default:
+        throw new RuntimeException(
+            "Invalid map field number: " + number);
+    }
+  }
+  @java.lang.Override
+  protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internalGetFieldAccessorTable() {
+    return com.tzld.piaoquan.recommend.feature.model.feature.FeatureV2.internal_static_MultiGetFeatureResponse_fieldAccessorTable
+        .ensureFieldAccessorsInitialized(
+            com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureResponse.class, com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureResponse.Builder.class);
+  }
+
+  public static final int RESULT_FIELD_NUMBER = 1;
+  private com.tzld.piaoquan.recommend.feature.model.common.Result result_;
+  /**
+   * <code>.Result result = 1;</code>
+   * @return Whether the result field is set.
+   */
+  @java.lang.Override
+  public boolean hasResult() {
+    return result_ != null;
+  }
+  /**
+   * <code>.Result result = 1;</code>
+   * @return The result.
+   */
+  @java.lang.Override
+  public com.tzld.piaoquan.recommend.feature.model.common.Result getResult() {
+    return result_ == null ? com.tzld.piaoquan.recommend.feature.model.common.Result.getDefaultInstance() : result_;
+  }
+  /**
+   * <code>.Result result = 1;</code>
+   */
+  @java.lang.Override
+  public com.tzld.piaoquan.recommend.feature.model.common.ResultOrBuilder getResultOrBuilder() {
+    return getResult();
+  }
+
+  public static final int FEATURE_FIELD_NUMBER = 2;
+  private static final class FeatureDefaultEntryHolder {
+    static final com.google.protobuf.MapEntry<
+        java.lang.String, java.lang.String> defaultEntry =
+            com.google.protobuf.MapEntry
+            .<java.lang.String, java.lang.String>newDefaultInstance(
+                com.tzld.piaoquan.recommend.feature.model.feature.FeatureV2.internal_static_MultiGetFeatureResponse_FeatureEntry_descriptor, 
+                com.google.protobuf.WireFormat.FieldType.STRING,
+                "",
+                com.google.protobuf.WireFormat.FieldType.STRING,
+                "");
+  }
+  private com.google.protobuf.MapField<
+      java.lang.String, java.lang.String> feature_;
+  private com.google.protobuf.MapField<java.lang.String, java.lang.String>
+  internalGetFeature() {
+    if (feature_ == null) {
+      return com.google.protobuf.MapField.emptyMapField(
+          FeatureDefaultEntryHolder.defaultEntry);
+    }
+    return feature_;
+  }
+
+  public int getFeatureCount() {
+    return internalGetFeature().getMap().size();
+  }
+  /**
+   * <code>map&lt;string, string&gt; feature = 2;</code>
+   */
+
+  @java.lang.Override
+  public boolean containsFeature(
+      java.lang.String key) {
+    if (key == null) { throw new java.lang.NullPointerException(); }
+    return internalGetFeature().getMap().containsKey(key);
+  }
+  /**
+   * Use {@link #getFeatureMap()} instead.
+   */
+  @java.lang.Override
+  @java.lang.Deprecated
+  public java.util.Map<java.lang.String, java.lang.String> getFeature() {
+    return getFeatureMap();
+  }
+  /**
+   * <code>map&lt;string, string&gt; feature = 2;</code>
+   */
+  @java.lang.Override
+
+  public java.util.Map<java.lang.String, java.lang.String> getFeatureMap() {
+    return internalGetFeature().getMap();
+  }
+  /**
+   * <code>map&lt;string, string&gt; feature = 2;</code>
+   */
+  @java.lang.Override
+
+  public java.lang.String getFeatureOrDefault(
+      java.lang.String key,
+      java.lang.String defaultValue) {
+    if (key == null) { throw new java.lang.NullPointerException(); }
+    java.util.Map<java.lang.String, java.lang.String> map =
+        internalGetFeature().getMap();
+    return map.containsKey(key) ? map.get(key) : defaultValue;
+  }
+  /**
+   * <code>map&lt;string, string&gt; feature = 2;</code>
+   */
+  @java.lang.Override
+
+  public java.lang.String getFeatureOrThrow(
+      java.lang.String key) {
+    if (key == null) { throw new java.lang.NullPointerException(); }
+    java.util.Map<java.lang.String, java.lang.String> map =
+        internalGetFeature().getMap();
+    if (!map.containsKey(key)) {
+      throw new java.lang.IllegalArgumentException();
+    }
+    return map.get(key);
+  }
+
+  private byte memoizedIsInitialized = -1;
+  @java.lang.Override
+  public final boolean isInitialized() {
+    byte isInitialized = memoizedIsInitialized;
+    if (isInitialized == 1) return true;
+    if (isInitialized == 0) return false;
+
+    memoizedIsInitialized = 1;
+    return true;
+  }
+
+  @java.lang.Override
+  public void writeTo(com.google.protobuf.CodedOutputStream output)
+                      throws java.io.IOException {
+    if (result_ != null) {
+      output.writeMessage(1, getResult());
+    }
+    com.google.protobuf.GeneratedMessageV3
+      .serializeStringMapTo(
+        output,
+        internalGetFeature(),
+        FeatureDefaultEntryHolder.defaultEntry,
+        2);
+    unknownFields.writeTo(output);
+  }
+
+  @java.lang.Override
+  public int getSerializedSize() {
+    int size = memoizedSize;
+    if (size != -1) return size;
+
+    size = 0;
+    if (result_ != null) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeMessageSize(1, getResult());
+    }
+    for (java.util.Map.Entry<java.lang.String, java.lang.String> entry
+         : internalGetFeature().getMap().entrySet()) {
+      com.google.protobuf.MapEntry<java.lang.String, java.lang.String>
+      feature__ = FeatureDefaultEntryHolder.defaultEntry.newBuilderForType()
+          .setKey(entry.getKey())
+          .setValue(entry.getValue())
+          .build();
+      size += com.google.protobuf.CodedOutputStream
+          .computeMessageSize(2, feature__);
+    }
+    size += unknownFields.getSerializedSize();
+    memoizedSize = size;
+    return size;
+  }
+
+  @java.lang.Override
+  public boolean equals(final java.lang.Object obj) {
+    if (obj == this) {
+     return true;
+    }
+    if (!(obj instanceof com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureResponse)) {
+      return super.equals(obj);
+    }
+    com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureResponse other = (com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureResponse) obj;
+
+    if (hasResult() != other.hasResult()) return false;
+    if (hasResult()) {
+      if (!getResult()
+          .equals(other.getResult())) return false;
+    }
+    if (!internalGetFeature().equals(
+        other.internalGetFeature())) return false;
+    if (!unknownFields.equals(other.unknownFields)) return false;
+    return true;
+  }
+
+  @java.lang.Override
+  public int hashCode() {
+    if (memoizedHashCode != 0) {
+      return memoizedHashCode;
+    }
+    int hash = 41;
+    hash = (19 * hash) + getDescriptor().hashCode();
+    if (hasResult()) {
+      hash = (37 * hash) + RESULT_FIELD_NUMBER;
+      hash = (53 * hash) + getResult().hashCode();
+    }
+    if (!internalGetFeature().getMap().isEmpty()) {
+      hash = (37 * hash) + FEATURE_FIELD_NUMBER;
+      hash = (53 * hash) + internalGetFeature().hashCode();
+    }
+    hash = (29 * hash) + unknownFields.hashCode();
+    memoizedHashCode = hash;
+    return hash;
+  }
+
+  public static com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureResponse parseFrom(
+      java.nio.ByteBuffer data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureResponse parseFrom(
+      java.nio.ByteBuffer data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureResponse parseFrom(
+      com.google.protobuf.ByteString data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureResponse parseFrom(
+      com.google.protobuf.ByteString data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureResponse parseFrom(byte[] data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureResponse parseFrom(
+      byte[] data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureResponse parseFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureResponse parseFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureResponse parseDelimitedFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input);
+  }
+  public static com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureResponse parseDelimitedFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureResponse parseFrom(
+      com.google.protobuf.CodedInputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureResponse parseFrom(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+
+  @java.lang.Override
+  public Builder newBuilderForType() { return newBuilder(); }
+  public static Builder newBuilder() {
+    return DEFAULT_INSTANCE.toBuilder();
+  }
+  public static Builder newBuilder(com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureResponse prototype) {
+    return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+  }
+  @java.lang.Override
+  public Builder toBuilder() {
+    return this == DEFAULT_INSTANCE
+        ? new Builder() : new Builder().mergeFrom(this);
+  }
+
+  @java.lang.Override
+  protected Builder newBuilderForType(
+      com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+    Builder builder = new Builder(parent);
+    return builder;
+  }
+  /**
+   * Protobuf type {@code MultiGetFeatureResponse}
+   */
+  public static final class Builder extends
+      com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+      // @@protoc_insertion_point(builder_implements:MultiGetFeatureResponse)
+      com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureResponseOrBuilder {
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return com.tzld.piaoquan.recommend.feature.model.feature.FeatureV2.internal_static_MultiGetFeatureResponse_descriptor;
+    }
+
+    @SuppressWarnings({"rawtypes"})
+    protected com.google.protobuf.MapField internalGetMapField(
+        int number) {
+      switch (number) {
+        case 2:
+          return internalGetFeature();
+        default:
+          throw new RuntimeException(
+              "Invalid map field number: " + number);
+      }
+    }
+    @SuppressWarnings({"rawtypes"})
+    protected com.google.protobuf.MapField internalGetMutableMapField(
+        int number) {
+      switch (number) {
+        case 2:
+          return internalGetMutableFeature();
+        default:
+          throw new RuntimeException(
+              "Invalid map field number: " + number);
+      }
+    }
+    @java.lang.Override
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return com.tzld.piaoquan.recommend.feature.model.feature.FeatureV2.internal_static_MultiGetFeatureResponse_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureResponse.class, com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureResponse.Builder.class);
+    }
+
+    // Construct using com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureResponse.newBuilder()
+    private Builder() {
+      maybeForceBuilderInitialization();
+    }
+
+    private Builder(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      super(parent);
+      maybeForceBuilderInitialization();
+    }
+    private void maybeForceBuilderInitialization() {
+      if (com.google.protobuf.GeneratedMessageV3
+              .alwaysUseFieldBuilders) {
+      }
+    }
+    @java.lang.Override
+    public Builder clear() {
+      super.clear();
+      if (resultBuilder_ == null) {
+        result_ = null;
+      } else {
+        result_ = null;
+        resultBuilder_ = null;
+      }
+      internalGetMutableFeature().clear();
+      return this;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Descriptors.Descriptor
+        getDescriptorForType() {
+      return com.tzld.piaoquan.recommend.feature.model.feature.FeatureV2.internal_static_MultiGetFeatureResponse_descriptor;
+    }
+
+    @java.lang.Override
+    public com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureResponse getDefaultInstanceForType() {
+      return com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureResponse.getDefaultInstance();
+    }
+
+    @java.lang.Override
+    public com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureResponse build() {
+      com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureResponse result = buildPartial();
+      if (!result.isInitialized()) {
+        throw newUninitializedMessageException(result);
+      }
+      return result;
+    }
+
+    @java.lang.Override
+    public com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureResponse buildPartial() {
+      com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureResponse result = new com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureResponse(this);
+      int from_bitField0_ = bitField0_;
+      if (resultBuilder_ == null) {
+        result.result_ = result_;
+      } else {
+        result.result_ = resultBuilder_.build();
+      }
+      result.feature_ = internalGetFeature();
+      result.feature_.makeImmutable();
+      onBuilt();
+      return result;
+    }
+
+    @java.lang.Override
+    public Builder clone() {
+      return super.clone();
+    }
+    @java.lang.Override
+    public Builder setField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return super.setField(field, value);
+    }
+    @java.lang.Override
+    public Builder clearField(
+        com.google.protobuf.Descriptors.FieldDescriptor field) {
+      return super.clearField(field);
+    }
+    @java.lang.Override
+    public Builder clearOneof(
+        com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+      return super.clearOneof(oneof);
+    }
+    @java.lang.Override
+    public Builder setRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        int index, java.lang.Object value) {
+      return super.setRepeatedField(field, index, value);
+    }
+    @java.lang.Override
+    public Builder addRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return super.addRepeatedField(field, value);
+    }
+    @java.lang.Override
+    public Builder mergeFrom(com.google.protobuf.Message other) {
+      if (other instanceof com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureResponse) {
+        return mergeFrom((com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureResponse)other);
+      } else {
+        super.mergeFrom(other);
+        return this;
+      }
+    }
+
+    public Builder mergeFrom(com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureResponse other) {
+      if (other == com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureResponse.getDefaultInstance()) return this;
+      if (other.hasResult()) {
+        mergeResult(other.getResult());
+      }
+      internalGetMutableFeature().mergeFrom(
+          other.internalGetFeature());
+      this.mergeUnknownFields(other.unknownFields);
+      onChanged();
+      return this;
+    }
+
+    @java.lang.Override
+    public final boolean isInitialized() {
+      return true;
+    }
+
+    @java.lang.Override
+    public Builder mergeFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureResponse parsedMessage = null;
+      try {
+        parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        parsedMessage = (com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureResponse) e.getUnfinishedMessage();
+        throw e.unwrapIOException();
+      } finally {
+        if (parsedMessage != null) {
+          mergeFrom(parsedMessage);
+        }
+      }
+      return this;
+    }
+    private int bitField0_;
+
+    private com.tzld.piaoquan.recommend.feature.model.common.Result result_;
+    private com.google.protobuf.SingleFieldBuilderV3<
+        com.tzld.piaoquan.recommend.feature.model.common.Result, com.tzld.piaoquan.recommend.feature.model.common.Result.Builder, com.tzld.piaoquan.recommend.feature.model.common.ResultOrBuilder> resultBuilder_;
+    /**
+     * <code>.Result result = 1;</code>
+     * @return Whether the result field is set.
+     */
+    public boolean hasResult() {
+      return resultBuilder_ != null || result_ != null;
+    }
+    /**
+     * <code>.Result result = 1;</code>
+     * @return The result.
+     */
+    public com.tzld.piaoquan.recommend.feature.model.common.Result getResult() {
+      if (resultBuilder_ == null) {
+        return result_ == null ? com.tzld.piaoquan.recommend.feature.model.common.Result.getDefaultInstance() : result_;
+      } else {
+        return resultBuilder_.getMessage();
+      }
+    }
+    /**
+     * <code>.Result result = 1;</code>
+     */
+    public Builder setResult(com.tzld.piaoquan.recommend.feature.model.common.Result value) {
+      if (resultBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        result_ = value;
+        onChanged();
+      } else {
+        resultBuilder_.setMessage(value);
+      }
+
+      return this;
+    }
+    /**
+     * <code>.Result result = 1;</code>
+     */
+    public Builder setResult(
+        com.tzld.piaoquan.recommend.feature.model.common.Result.Builder builderForValue) {
+      if (resultBuilder_ == null) {
+        result_ = builderForValue.build();
+        onChanged();
+      } else {
+        resultBuilder_.setMessage(builderForValue.build());
+      }
+
+      return this;
+    }
+    /**
+     * <code>.Result result = 1;</code>
+     */
+    public Builder mergeResult(com.tzld.piaoquan.recommend.feature.model.common.Result value) {
+      if (resultBuilder_ == null) {
+        if (result_ != null) {
+          result_ =
+            com.tzld.piaoquan.recommend.feature.model.common.Result.newBuilder(result_).mergeFrom(value).buildPartial();
+        } else {
+          result_ = value;
+        }
+        onChanged();
+      } else {
+        resultBuilder_.mergeFrom(value);
+      }
+
+      return this;
+    }
+    /**
+     * <code>.Result result = 1;</code>
+     */
+    public Builder clearResult() {
+      if (resultBuilder_ == null) {
+        result_ = null;
+        onChanged();
+      } else {
+        result_ = null;
+        resultBuilder_ = null;
+      }
+
+      return this;
+    }
+    /**
+     * <code>.Result result = 1;</code>
+     */
+    public com.tzld.piaoquan.recommend.feature.model.common.Result.Builder getResultBuilder() {
+      
+      onChanged();
+      return getResultFieldBuilder().getBuilder();
+    }
+    /**
+     * <code>.Result result = 1;</code>
+     */
+    public com.tzld.piaoquan.recommend.feature.model.common.ResultOrBuilder getResultOrBuilder() {
+      if (resultBuilder_ != null) {
+        return resultBuilder_.getMessageOrBuilder();
+      } else {
+        return result_ == null ?
+            com.tzld.piaoquan.recommend.feature.model.common.Result.getDefaultInstance() : result_;
+      }
+    }
+    /**
+     * <code>.Result result = 1;</code>
+     */
+    private com.google.protobuf.SingleFieldBuilderV3<
+        com.tzld.piaoquan.recommend.feature.model.common.Result, com.tzld.piaoquan.recommend.feature.model.common.Result.Builder, com.tzld.piaoquan.recommend.feature.model.common.ResultOrBuilder> 
+        getResultFieldBuilder() {
+      if (resultBuilder_ == null) {
+        resultBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+            com.tzld.piaoquan.recommend.feature.model.common.Result, com.tzld.piaoquan.recommend.feature.model.common.Result.Builder, com.tzld.piaoquan.recommend.feature.model.common.ResultOrBuilder>(
+                getResult(),
+                getParentForChildren(),
+                isClean());
+        result_ = null;
+      }
+      return resultBuilder_;
+    }
+
+    private com.google.protobuf.MapField<
+        java.lang.String, java.lang.String> feature_;
+    private com.google.protobuf.MapField<java.lang.String, java.lang.String>
+    internalGetFeature() {
+      if (feature_ == null) {
+        return com.google.protobuf.MapField.emptyMapField(
+            FeatureDefaultEntryHolder.defaultEntry);
+      }
+      return feature_;
+    }
+    private com.google.protobuf.MapField<java.lang.String, java.lang.String>
+    internalGetMutableFeature() {
+      onChanged();;
+      if (feature_ == null) {
+        feature_ = com.google.protobuf.MapField.newMapField(
+            FeatureDefaultEntryHolder.defaultEntry);
+      }
+      if (!feature_.isMutable()) {
+        feature_ = feature_.copy();
+      }
+      return feature_;
+    }
+
+    public int getFeatureCount() {
+      return internalGetFeature().getMap().size();
+    }
+    /**
+     * <code>map&lt;string, string&gt; feature = 2;</code>
+     */
+
+    @java.lang.Override
+    public boolean containsFeature(
+        java.lang.String key) {
+      if (key == null) { throw new java.lang.NullPointerException(); }
+      return internalGetFeature().getMap().containsKey(key);
+    }
+    /**
+     * Use {@link #getFeatureMap()} instead.
+     */
+    @java.lang.Override
+    @java.lang.Deprecated
+    public java.util.Map<java.lang.String, java.lang.String> getFeature() {
+      return getFeatureMap();
+    }
+    /**
+     * <code>map&lt;string, string&gt; feature = 2;</code>
+     */
+    @java.lang.Override
+
+    public java.util.Map<java.lang.String, java.lang.String> getFeatureMap() {
+      return internalGetFeature().getMap();
+    }
+    /**
+     * <code>map&lt;string, string&gt; feature = 2;</code>
+     */
+    @java.lang.Override
+
+    public java.lang.String getFeatureOrDefault(
+        java.lang.String key,
+        java.lang.String defaultValue) {
+      if (key == null) { throw new java.lang.NullPointerException(); }
+      java.util.Map<java.lang.String, java.lang.String> map =
+          internalGetFeature().getMap();
+      return map.containsKey(key) ? map.get(key) : defaultValue;
+    }
+    /**
+     * <code>map&lt;string, string&gt; feature = 2;</code>
+     */
+    @java.lang.Override
+
+    public java.lang.String getFeatureOrThrow(
+        java.lang.String key) {
+      if (key == null) { throw new java.lang.NullPointerException(); }
+      java.util.Map<java.lang.String, java.lang.String> map =
+          internalGetFeature().getMap();
+      if (!map.containsKey(key)) {
+        throw new java.lang.IllegalArgumentException();
+      }
+      return map.get(key);
+    }
+
+    public Builder clearFeature() {
+      internalGetMutableFeature().getMutableMap()
+          .clear();
+      return this;
+    }
+    /**
+     * <code>map&lt;string, string&gt; feature = 2;</code>
+     */
+
+    public Builder removeFeature(
+        java.lang.String key) {
+      if (key == null) { throw new java.lang.NullPointerException(); }
+      internalGetMutableFeature().getMutableMap()
+          .remove(key);
+      return this;
+    }
+    /**
+     * Use alternate mutation accessors instead.
+     */
+    @java.lang.Deprecated
+    public java.util.Map<java.lang.String, java.lang.String>
+    getMutableFeature() {
+      return internalGetMutableFeature().getMutableMap();
+    }
+    /**
+     * <code>map&lt;string, string&gt; feature = 2;</code>
+     */
+    public Builder putFeature(
+        java.lang.String key,
+        java.lang.String value) {
+      if (key == null) { throw new java.lang.NullPointerException(); }
+      if (value == null) { throw new java.lang.NullPointerException(); }
+      internalGetMutableFeature().getMutableMap()
+          .put(key, value);
+      return this;
+    }
+    /**
+     * <code>map&lt;string, string&gt; feature = 2;</code>
+     */
+
+    public Builder putAllFeature(
+        java.util.Map<java.lang.String, java.lang.String> values) {
+      internalGetMutableFeature().getMutableMap()
+          .putAll(values);
+      return this;
+    }
+    @java.lang.Override
+    public final Builder setUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.setUnknownFields(unknownFields);
+    }
+
+    @java.lang.Override
+    public final Builder mergeUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.mergeUnknownFields(unknownFields);
+    }
+
+
+    // @@protoc_insertion_point(builder_scope:MultiGetFeatureResponse)
+  }
+
+  // @@protoc_insertion_point(class_scope:MultiGetFeatureResponse)
+  private static final com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureResponse DEFAULT_INSTANCE;
+  static {
+    DEFAULT_INSTANCE = new com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureResponse();
+  }
+
+  public static com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureResponse getDefaultInstance() {
+    return DEFAULT_INSTANCE;
+  }
+
+  private static final com.google.protobuf.Parser<MultiGetFeatureResponse>
+      PARSER = new com.google.protobuf.AbstractParser<MultiGetFeatureResponse>() {
+    @java.lang.Override
+    public MultiGetFeatureResponse parsePartialFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return new MultiGetFeatureResponse(input, extensionRegistry);
+    }
+  };
+
+  public static com.google.protobuf.Parser<MultiGetFeatureResponse> parser() {
+    return PARSER;
+  }
+
+  @java.lang.Override
+  public com.google.protobuf.Parser<MultiGetFeatureResponse> getParserForType() {
+    return PARSER;
+  }
+
+  @java.lang.Override
+  public com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureResponse getDefaultInstanceForType() {
+    return DEFAULT_INSTANCE;
+  }
+
+}
+

+ 58 - 0
recommend-feature-client/src/main/java/com/tzld/piaoquan/recommend/feature/model/feature/MultiGetFeatureResponseOrBuilder.java

@@ -0,0 +1,58 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: com/tzld/piaoquan/recommend/feature/featureV2.proto
+
+package com.tzld.piaoquan.recommend.feature.model.feature;
+
+public interface MultiGetFeatureResponseOrBuilder extends
+    // @@protoc_insertion_point(interface_extends:MultiGetFeatureResponse)
+    com.google.protobuf.MessageOrBuilder {
+
+  /**
+   * <code>.Result result = 1;</code>
+   * @return Whether the result field is set.
+   */
+  boolean hasResult();
+  /**
+   * <code>.Result result = 1;</code>
+   * @return The result.
+   */
+  com.tzld.piaoquan.recommend.feature.model.common.Result getResult();
+  /**
+   * <code>.Result result = 1;</code>
+   */
+  com.tzld.piaoquan.recommend.feature.model.common.ResultOrBuilder getResultOrBuilder();
+
+  /**
+   * <code>map&lt;string, string&gt; feature = 2;</code>
+   */
+  int getFeatureCount();
+  /**
+   * <code>map&lt;string, string&gt; feature = 2;</code>
+   */
+  boolean containsFeature(
+      java.lang.String key);
+  /**
+   * Use {@link #getFeatureMap()} instead.
+   */
+  @java.lang.Deprecated
+  java.util.Map<java.lang.String, java.lang.String>
+  getFeature();
+  /**
+   * <code>map&lt;string, string&gt; feature = 2;</code>
+   */
+  java.util.Map<java.lang.String, java.lang.String>
+  getFeatureMap();
+  /**
+   * <code>map&lt;string, string&gt; feature = 2;</code>
+   */
+
+  java.lang.String getFeatureOrDefault(
+      java.lang.String key,
+      java.lang.String defaultValue);
+  /**
+   * <code>map&lt;string, string&gt; feature = 2;</code>
+   */
+
+  java.lang.String getFeatureOrThrow(
+      java.lang.String key);
+}

+ 28 - 0
recommend-feature-client/src/main/proto/com/tzld/piaoquan/recommend/feature/featureV2.proto

@@ -0,0 +1,28 @@
+syntax = "proto3";
+
+import "google/protobuf/any.proto";
+import "com/tzld/piaoquan/recommend/feature/common.proto";
+
+option java_multiple_files = true;
+option java_package = "com.tzld.piaoquan.recommend.feature.model.feature";
+option java_generic_services = true;
+
+message MultiGetFeatureRequest {
+  repeated FeatureKeyProto feature_key = 1;
+}
+
+message FeatureKeyProto {
+  string unique_key = 1;
+  string table_name = 2;
+  map<string, string> field_value = 3;
+}
+
+message MultiGetFeatureResponse {
+  Result result = 1;
+  map<string, string> feature = 2;
+}
+
+
+service FeatureV2Service {
+  rpc MultiGetFeature (MultiGetFeatureRequest) returns (MultiGetFeatureResponse);
+}

二进制
recommend-feature-produce/.DS_Store


+ 151 - 0
recommend-feature-produce/pom.xml

@@ -0,0 +1,151 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>recommend-feature</artifactId>
+        <groupId>com.tzld.piaoquan</groupId>
+        <version>1.0.0</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>recommend-feature-produce</artifactId>
+
+    <properties>
+        <!--        <spark.version>3.3.1</spark.version>-->
+        <!--        <scala.version>2.12.15</scala.version>-->
+        <spark.version>2.3.0</spark.version>
+        <scala.version>2.11.8</scala.version>
+        <emr.version>2.0.0</emr.version>
+        <java.version>1.8</java.version>
+        <odps.version>0.48.4-public</odps.version>
+        <fastjson.version>1.2.45</fastjson.version>
+
+        <maven.compiler.source>8</maven.compiler.source>
+        <maven.compiler.target>8</maven.compiler.target>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.spark</groupId>
+            <artifactId>spark-core_2.11</artifactId>
+            <version>${spark.version}</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>io.netty</groupId>
+                    <artifactId>netty-all</artifactId>
+                </exclusion>
+                <exclusion>
+                    <artifactId>slf4j-log4j12</artifactId>
+                    <groupId>org.slf4j</groupId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>io.netty</groupId>
+            <artifactId>netty-all</artifactId>
+            <version>4.1.17.Final</version>
+        </dependency>
+        <dependency>
+            <groupId>com.aliyun.emr</groupId>
+            <artifactId>emr-maxcompute_2.11</artifactId>
+            <version>${emr.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.scala-lang</groupId>
+            <artifactId>scala-library</artifactId>
+            <version>${scala.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>fastjson</artifactId>
+            <version>${fastjson.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>redis.clients</groupId>
+            <artifactId>jedis</artifactId>
+            <version>5.1.3</version>
+        </dependency>
+        <dependency>
+            <groupId>com.aliyun.odps</groupId>
+            <artifactId>odps-sdk-core</artifactId>
+            <version>${odps.version}</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.codehaus.jackson</groupId>
+                    <artifactId>jackson-mapper-asl</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.codehaus.jackson</groupId>
+                    <artifactId>jackson-core-asl</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>com.aliyun.odps</groupId>
+            <artifactId>odps-sdk-commons</artifactId>
+            <version>${odps.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <version>1.18.24</version>
+        </dependency>
+        <dependency>
+            <groupId>com.ctrip.framework.apollo</groupId>
+            <artifactId>apollo-client</artifactId>
+            <version>1.8.0</version>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-simple</artifactId>
+            <version>1.7.28</version>
+        </dependency>
+        <dependency>
+            <groupId>com.aliyun.oss</groupId>
+            <artifactId>aliyun-sdk-oss</artifactId>
+            <version>3.17.4</version>
+        </dependency>
+        <!-- Snappy compression library -->
+        <dependency>
+            <groupId>org.xerial.snappy</groupId>
+            <artifactId>snappy-java</artifactId>
+            <version>1.1.8.4</version>
+        </dependency>
+<!--        <dependency>-->
+<!--            <groupId>com.squareup.okhttp3</groupId>-->
+<!--            <artifactId>okhttp</artifactId>-->
+<!--            <version>3.14.9</version>-->
+<!--        </dependency>-->
+        <dependency>
+            <groupId>com.squareup.okhttp</groupId>
+            <artifactId>okhttp</artifactId>
+            <version>2.7.5</version>
+        </dependency>
+    </dependencies>
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-assembly-plugin</artifactId>
+                <version>2.5.5</version>
+                <configuration>
+                    <descriptorRefs>
+                        <descriptorRef>jar-with-dependencies</descriptorRef>
+                    </descriptorRefs>
+                    <finalName>${project.name}</finalName>
+                </configuration>
+                <executions>
+                    <execution>
+                        <id>make-assembly</id>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>single</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+</project>

+ 171 - 0
recommend-feature-produce/src/main/java/com/tzld/piaoquan/recommend/feature/produce/FeatureDiff.java

@@ -0,0 +1,171 @@
+package com.tzld.piaoquan.recommend.feature.produce;
+
+import com.google.common.reflect.TypeToken;
+import com.tzld.piaoquan.recommend.feature.produce.service.ODPSService;
+import com.tzld.piaoquan.recommend.feature.produce.util.JSONUtils;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang.StringUtils;
+import org.apache.spark.SparkConf;
+import org.apache.spark.api.java.JavaRDD;
+import org.apache.spark.api.java.JavaSparkContext;
+import org.apache.spark.api.java.function.Function;
+
+import java.util.*;
+
+/**
+ * @author dyp
+ */
+@Slf4j
+public class FeatureDiff {
+
+    private static ODPSService odpsService = new ODPSService();
+
+    private static Set<String> abCodes;
+
+    static {
+        abCodes = new HashSet<>();
+        abCodes.add("ab0");
+        abCodes.add("ab1");
+        abCodes.add("ab2");
+        abCodes.add("ab3");
+//        tableToCol = new HashMap<>();
+//
+//        tableToCol.put("alg_mid_feature_play", "c1_feature");
+//        tableToCol.put("alg_mid_feature_share_and_return", "c2_feature");
+//        tableToCol.put("alg_mid_feature_play_tags", "c3_feature");
+//        tableToCol.put("alg_mid_feature_return_tags", "c4_feature");
+//        tableToCol.put("alg_mid_feature_share_tags", "c5_feature");
+//        tableToCol.put("alg_mid_feature_feed_exp_share_tags", "c6_feature");
+//        tableToCol.put("alg_mid_feature_feed_exp_return_tags", "c7_feature");
+//        tableToCol.put("alg_mid_feature_sharecf", "c8_feature");
+//        tableToCol.put("alg_mid_feature_returncf", "c9_feature");
+//        tableToCol.put("alg_vid_feature_all_exp", "b1_feature");
+//        tableToCol.put("alg_vid_feature_all_share", "b2_feature");
+//        tableToCol.put("alg_vid_feature_all_return", "b3_feature");
+////        tableToCol.put("alg_vid_feature_head_play", "b4_feature");
+////        tableToCol.put("alg_vid_feature_feed_play", "b5_feature");
+//        tableToCol.put("alg_vid_feature_exp2share", "b6_feature");
+//        tableToCol.put("alg_vid_feature_share2return", "b7_feature");
+//        tableToCol.put("alg_vid_feature_feed_noflow_exp", "b8_feature");
+//        tableToCol.put("alg_vid_feature_feed_noflow_root_share", "b9_feature");
+//        tableToCol.put("alg_vid_feature_feed_noflow_root_return", "b10_feature");
+//        tableToCol.put("alg_vid_feature_feed_flow_exp", "b11_feature");
+//        tableToCol.put("alg_vid_feature_feed_flow_root_share", "b12_feature");
+//        tableToCol.put("alg_vid_feature_feed_flow_root_return", "b13_feature");
+////        tableToCol.put("alg_vid_feature_feed_apptype_exp", "b14_feature");
+////        tableToCol.put("alg_vid_feature_feed_apptype_root_share", "b15_feature");
+////        tableToCol.put("alg_vid_feature_feed_apptype_root_return", "b16_feature");
+//        tableToCol.put("alg_vid_feature_feed_province_exp", "b17_feature");
+//        tableToCol.put("alg_vid_feature_feed_province_root_share", "b18_feature");
+//        tableToCol.put("alg_vid_feature_feed_province_root_return", "b19_feature");
+    }
+
+    private static void addUser(Map<String, String> tableToCol) {
+        tableToCol.put("alg_mid_feature_play", "c1_feature");
+        tableToCol.put("alg_mid_feature_share_and_return", "c2_feature");
+        tableToCol.put("alg_mid_feature_play_tags", "c3_feature");
+        tableToCol.put("alg_mid_feature_return_tags", "c4_feature");
+        tableToCol.put("alg_mid_feature_share_tags", "c5_feature");
+        tableToCol.put("alg_mid_feature_feed_exp_share_tags", "c6_feature");
+        tableToCol.put("alg_mid_feature_feed_exp_return_tags", "c7_feature");
+        tableToCol.put("alg_mid_feature_sharecf", "c8_feature");
+        tableToCol.put("alg_mid_feature_returncf", "c9_feature");
+    }
+
+    private static void addVideo(Map<String, String> tableToCol) {
+        tableToCol.put("alg_vid_feature_all_exp", "b1_feature");
+        tableToCol.put("alg_vid_feature_all_share", "b2_feature");
+        tableToCol.put("alg_vid_feature_all_return", "b3_feature");
+//        tableToCol.put("alg_vid_feature_head_play", "b4_feature");
+//        tableToCol.put("alg_vid_feature_feed_play", "b5_feature");
+        tableToCol.put("alg_vid_feature_exp2share", "b6_feature");
+        tableToCol.put("alg_vid_feature_share2return", "b7_feature");
+        tableToCol.put("alg_vid_feature_feed_noflow_exp", "b8_feature");
+        tableToCol.put("alg_vid_feature_feed_noflow_root_share", "b9_feature");
+        tableToCol.put("alg_vid_feature_feed_noflow_root_return", "b10_feature");
+        tableToCol.put("alg_vid_feature_feed_flow_exp", "b11_feature");
+        tableToCol.put("alg_vid_feature_feed_flow_root_share", "b12_feature");
+        tableToCol.put("alg_vid_feature_feed_flow_root_return", "b13_feature");
+//        tableToCol.put("alg_vid_feature_feed_apptype_exp", "b14_feature");
+//        tableToCol.put("alg_vid_feature_feed_apptype_root_share", "b15_feature");
+//        tableToCol.put("alg_vid_feature_feed_apptype_root_return", "b16_feature");
+        tableToCol.put("alg_vid_feature_feed_province_exp", "b17_feature");
+        tableToCol.put("alg_vid_feature_feed_province_root_share", "b18_feature");
+        tableToCol.put("alg_vid_feature_feed_province_root_return", "b19_feature");
+    }
+
+    public static void main(String[] args) {
+
+
+        Map<String, String> tableToCol = new HashMap<>();
+        if (args[3].equals("0")) {
+            addVideo(tableToCol);
+        } else if (args[3].equals("1")) {
+            addUser(tableToCol);
+        } else {
+            addVideo(tableToCol);
+            addUser(tableToCol);
+        }
+
+
+        SparkConf sparkConf = new SparkConf()
+                // .setMaster("local")
+                .setAppName("odps sync to redis");
+        JavaSparkContext jsc = new JavaSparkContext(sparkConf);
+
+        // ODPS
+        log.info("read odps");
+        String project = "loghubods";
+        String table = "alg_recsys_sample_all_new";
+        String partition = "dt=" + args[1] + ",hh=" + args[2];
+
+        int partitionNum = Integer.valueOf(args[0]);
+        JavaRDD<Map<String, String>> fieldValues = odpsService.read(jsc, project, table, partition, partitionNum);
+        if (fieldValues == null) {
+            log.info("odps empty");
+            return;
+        }
+
+        long diffCount =
+                fieldValues.repartition(partitionNum).filter(new Function<Map<String, String>,
+                        Boolean>() {
+                    @Override
+                    public Boolean call(Map<String, String> map) throws Exception {
+
+                        if (StringUtils.isNotBlank(map.get("flowpool"))) {
+                            return false;
+                        }
+
+                        if (!abCodes.contains(map.get("abcode"))) {
+                            return false;
+                        }
+
+                        Map<String, Map<String, String>> metaFeatureMap = JSONUtils.fromJson(map.get("metafeaturemap"),
+                                new TypeToken<Map<String, Map<String, String>>>() {
+                                }, Collections.emptyMap());
+                        for (Map.Entry<String, String> e : tableToCol.entrySet()) {
+                            Map<String, String> offline = JSONUtils.fromJson(map.get(e.getValue()), new TypeToken<Map<String, String>>() {
+                            }, Collections.emptyMap());
+
+                            Map<String, String> online = metaFeatureMap.getOrDefault(e.getKey(), new HashMap<>());
+
+
+                            if (offline.size() != online.size()) {
+                                return true;
+                            }
+
+                            for (Map.Entry<String, String> offlineE : offline.entrySet()) {
+                                if (!StringUtils.equals(online.get(offlineE.getKey()), offlineE.getValue())) {
+                                    return true;
+                                }
+                            }
+                        }
+                        return false;
+                    }
+                }).count();
+
+        log.info("diff count {}", diffCount);
+
+    }
+
+}

+ 137 - 0
recommend-feature-produce/src/main/java/com/tzld/piaoquan/recommend/feature/produce/ODPSToRedis.java

@@ -0,0 +1,137 @@
+package com.tzld.piaoquan.recommend.feature.produce;
+
+import com.alibaba.fastjson.JSONObject;
+import com.tzld.piaoquan.recommend.feature.produce.model.DTSConfig;
+import com.tzld.piaoquan.recommend.feature.produce.service.CMDService;
+import com.tzld.piaoquan.recommend.feature.produce.service.DTSConfigService;
+import com.tzld.piaoquan.recommend.feature.produce.service.ODPSService;
+import com.tzld.piaoquan.recommend.feature.produce.service.RedisService;
+import com.tzld.piaoquan.recommend.feature.produce.util.JSONUtils;
+import lombok.extern.slf4j.Slf4j;
+import com.squareup.okhttp.*;
+import org.apache.commons.collections.MapUtils;
+import org.apache.commons.lang3.math.NumberUtils;
+import org.apache.spark.SparkConf;
+import org.apache.spark.api.java.JavaRDD;
+import org.apache.spark.api.java.JavaSparkContext;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author dyp
+ */
+@Slf4j
+public class ODPSToRedis {
+    private static CMDService cmdService = new CMDService();
+    private static ODPSService odpsService = new ODPSService();
+
+    public static void main(String[] args) {
+
+        log.info("args {}", JSONUtils.toJson(args));
+
+        Map<String, String> argMap = cmdService.parse(args);
+
+//        argMap.put("project", "loghubods");
+//        argMap.put("table", "alg_video_source_feature_day");
+//        argMap.put("dt", "20250610");
+//        argMap.put("hh", "13");
+//        argMap.put("mi", "00");
+//        argMap.put("env", "test");
+//        argMap.put("odpsBatchSize", "300000");
+//        argMap.put("retry", "1");
+
+
+        if (MapUtils.isEmpty(argMap)) {
+            log.error("args is empty");
+            return;
+        }
+
+        SparkConf sparkConf = new SparkConf()
+                //.setMaster("local")
+                .setAppName("odps sync to redis : " + argMap.get("table"));
+        for (Map.Entry<String, String> e : argMap.entrySet()) {
+            sparkConf.set(e.getKey(), e.getValue());
+        }
+        JavaSparkContext jsc = new JavaSparkContext(sparkConf);
+
+        // ODPS
+        log.info("read odps");
+        String env = argMap.get("env");
+        DTSConfigService dtsConfigService = new DTSConfigService(env);
+        DTSConfig config = dtsConfigService.getDTSConfig(argMap);
+        if (config == null || !config.selfCheck()) {
+            log.error("dts config error");
+            return;
+        }
+
+        long count = 0;
+        int retry = NumberUtils.toInt(argMap.getOrDefault("retry", "50"), 50);
+        while (count <= 0 && retry-- >= 0) {
+            count = odpsService.count(config, argMap);
+            if (count <= 0) {
+                if (retry <= 0) {
+                    try {
+                        // TODO 报警
+                        String feishuWebhook = "https://open.feishu.cn/open-apis/bot/v2/hook/540d4098-367a-4068-9a44" +
+                                "-b8109652f07c";
+                        MediaType mediaType = MediaType.parse("application/json");
+                        JSONObject param = new JSONObject();
+                        param.put("msg_type", "text");
+                        Map<String, String> map = new HashMap<>();
+                        map.put("text", argMap.get("table") + " 使用兜底数据");
+                        param.put("content", map);
+                        RequestBody body = RequestBody.create(mediaType, param.toJSONString());
+                        Request request = new Request.Builder()
+                                .url(feishuWebhook)
+                                .method("POST", body)
+                                .addHeader("Content-Type", "application/json")
+                                .build();
+                        OkHttpClient client = new OkHttpClient();
+                        client.newCall(request).execute();
+                    } catch (Throwable e) {
+                        log.error("send feishu alert error ", e);
+                    }
+                    Map<String, String> partitionMap = odpsService.getLastestPartition(config, argMap);
+                    argMap.putAll(partitionMap);
+                    log.info("partitionMap {} argMap {}", JSONUtils.toJson(partitionMap), JSONUtils.toJson(argMap));
+                    count = odpsService.count(config, argMap);
+                    if (count <= 0) {
+                        return;
+                    }
+                } else {
+                    try {
+                        log.info("sleep {}", retry);
+                        Thread.sleep(60000);
+                    } catch (InterruptedException e) {
+                        e.printStackTrace();
+                    }
+                }
+            }
+        }
+
+        log.info("odps count {}", count);
+        long odpsBatchSize = NumberUtils.toLong(argMap.getOrDefault("odpsBatchSize", "200000"));
+        argMap.put("partitionNum", String.valueOf(count / odpsBatchSize + 1));
+
+        JavaRDD<Map<String, String>> fieldValues = odpsService.read(jsc, config, argMap);
+        if (fieldValues == null) {
+            log.info("odps empty");
+            return;
+        }
+        //log.info("odps count {}", fieldValues.count());
+
+        // RDD
+        log.info("sync redis");
+        RedisService redisService = new RedisService(env);
+        int partitionNum = NumberUtils.toInt(argMap.get("partitionNum"), 10);
+        fieldValues.repartition(partitionNum).foreachPartition(iterator -> {
+            redisService.mSetV2(iterator, config);
+        });
+
+        redisService.setMonitor(config, argMap);
+
+        jsc.stop();
+    }
+
+}

+ 49 - 0
recommend-feature-produce/src/main/java/com/tzld/piaoquan/recommend/feature/produce/VideoCleanExecutor.java

@@ -0,0 +1,49 @@
+package com.tzld.piaoquan.recommend.feature.produce;
+
+import com.tzld.piaoquan.recommend.feature.produce.service.OSSService;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang.math.NumberUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.spark.SparkConf;
+import org.apache.spark.api.java.JavaRDD;
+import org.apache.spark.api.java.JavaSparkContext;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author dyp
+ */
+@Slf4j
+public class VideoCleanExecutor {
+    public static void main(String[] args) {
+        String file = args[0];
+        int repartition = NumberUtils.toInt(args[1], 160);
+        int sync = NumberUtils.toInt(args[2], 0);
+
+
+        log.info("hdfs file {}", file);
+        SparkConf sparkConf = new SparkConf()
+                //.setMaster("local")
+                .setAppName("VideoCleanExecutor");
+        JavaSparkContext jsc = new JavaSparkContext(sparkConf);
+
+        JavaRDD<String> multiFileRDD = jsc.textFile(file);
+
+        log.info("count {}", multiFileRDD.count());
+        OSSService ossService = new OSSService();
+        multiFileRDD.repartition(repartition).foreachPartition(s -> {
+            List<String> objectNames = new ArrayList<>();
+            while (s.hasNext()) {
+                String[] data = StringUtils.split(s.next(), "\t");
+                objectNames.add(data[2]);
+            }
+            if (sync == 1) {
+                ossService.transToDeepColdArchive("art-pubbucket", objectNames);
+            } else {
+                ossService.transToDeepColdArchive2("art-pubbucket", objectNames);
+            }
+        });
+    }
+
+}

+ 55 - 0
recommend-feature-produce/src/main/java/com/tzld/piaoquan/recommend/feature/produce/VideoCleanODPSToHDFS.java

@@ -0,0 +1,55 @@
+package com.tzld.piaoquan.recommend.feature.produce;
+
+import com.tzld.piaoquan.recommend.feature.produce.service.HDFSService;
+import com.tzld.piaoquan.recommend.feature.produce.service.ODPSService;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.spark.SparkConf;
+import org.apache.spark.api.java.JavaRDD;
+import org.apache.spark.api.java.JavaSparkContext;
+
+import java.util.Map;
+
+/**
+ * @author dyp
+ */
+@Slf4j
+public class VideoCleanODPSToHDFS {
+    public static void main(String[] args) {
+
+        String path = "/dyp/oss/video_clean/";
+        try {
+            HDFSService hdfsService = new HDFSService();
+            hdfsService.delete(path);
+        } catch (Exception e) {
+            log.info("hdfs error ", e);
+        }
+
+        SparkConf sparkConf = new SparkConf()
+                //.setMaster("local")
+                .setAppName("VideoCleanODPSToHDFS");
+        JavaSparkContext jsc = new JavaSparkContext(sparkConf);
+
+        ODPSService odpsService = new ODPSService();
+        JavaRDD<Map<String, String>> record = odpsService.read(jsc, "loghubods", "not_active_videos_di", "dt=20240617",
+                90);
+
+        JavaRDD<String> data = record.map(r -> {
+            StringBuilder sb = new StringBuilder();
+            sb.append(r.get("videoid"));
+            sb.append("\t");
+            sb.append(r.get("video_path"));
+            sb.append("\t");
+            sb.append(r.get("transed_video_path"));
+            sb.append("\t");
+            sb.append(r.get("cover_img_path"));
+            sb.append("\t");
+            sb.append(r.get("self_cover_img_path"));
+            sb.append("\t");
+            sb.append(r.get("share_moment_img_path"));
+            return sb.toString();
+        });
+
+        data.coalesce(90).saveAsTextFile(path);
+    }
+
+}

+ 43 - 0
recommend-feature-produce/src/main/java/com/tzld/piaoquan/recommend/feature/produce/model/DTSConfig.java

@@ -0,0 +1,43 @@
+package com.tzld.piaoquan.recommend.feature.produce.model;
+
+import lombok.Data;
+import lombok.extern.slf4j.Slf4j;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * @author dyp
+ */
+@Data
+@Slf4j
+public class DTSConfig implements Serializable {
+    private ODPS odps;
+    private Redis redis;
+
+    @Data
+    public static class ODPS implements Serializable {
+        private String table;
+        private List<String> cols;
+        private List<String> partition;
+    }
+
+    @Data
+    public static class Redis implements Serializable {
+        private String prefix;
+        private List<String> key;
+        private long expire;
+    }
+
+    public boolean selfCheck() {
+        if (odps == null) {
+            log.error("odps not config");
+            return false;
+        }
+        if (redis == null) {
+            log.error("redis not config");
+            return false;
+        }
+        return true;
+    }
+}

+ 20 - 0
recommend-feature-produce/src/main/java/com/tzld/piaoquan/recommend/feature/produce/service/CMDService.java

@@ -0,0 +1,20 @@
+package com.tzld.piaoquan.recommend.feature.produce.service;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * https://help.aliyun.com/zh/maxcompute/user-guide/java-sdk-1/?spm=a2c4g.11174283.0.0.6d0111c1E15lI3
+ *
+ * @author dyp
+ */
+public class CMDService {
+
+    public Map<String, String> parse(String[] args) {
+        Map<String, String> map = new HashMap<>();
+        for (int i = 0; i < args.length - 1; i += 2) {
+            map.put(args[i].substring(1), args[i + 1]);
+        }
+        return map;
+    }
+}

+ 60 - 0
recommend-feature-produce/src/main/java/com/tzld/piaoquan/recommend/feature/produce/service/DTSConfigService.java

@@ -0,0 +1,60 @@
+package com.tzld.piaoquan.recommend.feature.produce.service;
+
+import com.ctrip.framework.apollo.ConfigService;
+import com.google.common.reflect.TypeToken;
+import com.tzld.piaoquan.recommend.feature.produce.model.DTSConfig;
+import com.tzld.piaoquan.recommend.feature.produce.util.JSONUtils;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+/**
+ * https://help.aliyun.com/zh/maxcompute/user-guide/java-sdk-1/?spm=a2c4g.11174283.0.0.6d0111c1E15lI3
+ *
+ * @author dyp
+ */
+@Slf4j
+public class DTSConfigService {
+
+    public DTSConfigService(String env) {
+        System.setProperty("app.id", "recommend-feature");
+
+        if (StringUtils.equals(env, "prod")) {
+            System.setProperty("apollo.meta", "http://apolloconfig-internal.piaoquantv.com");
+        } else {
+            System.setProperty("apollo.meta", "http://devapolloconfig-internal.piaoquantv.com");
+        }
+    }
+
+    public DTSConfig getDTSConfig(Map<String, String> argMap) {
+
+        List<DTSConfig> dtsConfigs = JSONUtils.fromJson(
+                ConfigService.getAppConfig().getProperty("dts.config", ""),
+                new TypeToken<List<DTSConfig>>() {
+                },
+                Collections.emptyList());
+        if (CollectionUtils.isEmpty(dtsConfigs)) {
+            log.error("DTSConfig not config");
+            return null;
+        }
+        Optional<DTSConfig> optional = dtsConfigs.stream()
+                .filter(c -> c.getOdps() != null && StringUtils.equals(c.getOdps().getTable(), argMap.get("table")))
+                .findFirst();
+        if (!optional.isPresent()) {
+            log.error("table {} not config", argMap.get("table"));
+            return null;
+        }
+        return optional.get();
+    }
+
+    public List<DTSConfig> getAllDTSConfig() {
+
+        return JSONUtils.fromJson(ConfigService.getAppConfig().getProperty("dts.config.v2", ""), new TypeToken<List<DTSConfig>>() {
+        }, Collections.emptyList());
+    }
+}

+ 34 - 0
recommend-feature-produce/src/main/java/com/tzld/piaoquan/recommend/feature/produce/service/HDFSService.java

@@ -0,0 +1,34 @@
+package com.tzld.piaoquan.recommend.feature.produce.service;
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.FileSystem;
+import org.apache.hadoop.fs.Path;
+
+import java.io.IOException;
+import java.io.Serializable;
+
+/**
+ * @author dyp
+ */
+@Slf4j
+public class HDFSService implements Serializable {
+    private FileSystem fSystem;
+
+    public HDFSService() throws IOException {
+        fSystem = FileSystem.get(new Configuration());
+    }
+
+    public boolean createDir(String dir) throws IOException {
+        Path dirPath = new Path(dir);
+        if (!fSystem.exists(dirPath)) {
+            fSystem.mkdirs(dirPath);
+        }
+        return true;
+    }
+
+    public boolean delete(String path) throws IOException {
+        return fSystem.delete(new Path(path));
+    }
+
+}

+ 256 - 0
recommend-feature-produce/src/main/java/com/tzld/piaoquan/recommend/feature/produce/service/ODPSService.java

@@ -0,0 +1,256 @@
+package com.tzld.piaoquan.recommend.feature.produce.service;
+
+import com.aliyun.odps.Instance;
+import com.aliyun.odps.Odps;
+import com.aliyun.odps.OdpsException;
+import com.aliyun.odps.TableSchema;
+import com.aliyun.odps.account.Account;
+import com.aliyun.odps.account.AliyunAccount;
+import com.aliyun.odps.data.Record;
+import com.aliyun.odps.data.SimpleJsonValue;
+import com.aliyun.odps.task.SQLTask;
+import com.aliyun.odps.utils.StringUtils;
+import com.google.common.base.Joiner;
+import com.tzld.piaoquan.recommend.feature.produce.model.DTSConfig;
+import com.tzld.piaoquan.recommend.feature.produce.util.CommonCollectionUtils;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.math.NumberUtils;
+import org.apache.spark.aliyun.odps.OdpsOps;
+import org.apache.spark.api.java.JavaRDD;
+import org.apache.spark.api.java.JavaSparkContext;
+import org.apache.spark.api.java.function.Function2;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * https://help.aliyun.com/zh/maxcompute/user-guide/java-sdk-1/?spm=a2c4g.11174283.0.0.6d0111c1E15lI3
+ *
+ * @author dyp
+ */
+@Slf4j
+public class ODPSService {
+    private final String accessId = "LTAIWYUujJAm7CbH";
+    private final String accessKey = "RfSjdiWwED1sGFlsjXv0DlfTnZTG1P";
+//    private final String odpsUrl = "http://service.odps.aliyun.com/api";
+//    private final String tunnelUrl = "http://dt.cn-hangzhou.maxcompute.aliyun.com";
+    private final String odpsUrl = "http://service.cn-hangzhou-vpc.maxcompute.aliyun-inc.com/api";
+    private final String tunnelUrl = "http://dt.cn-hangzhou-vpc.maxcompute.aliyun-inc.com";
+    private final String sqlFormat = "select %s from %s where 1=1 %s ;";
+    private final String countSqlFormat = "select count(1) as count from %s where 1=1 %s ;";
+    private final String latestPartitionSqlFormat = "select %s from %s where 0=1 %s ;";
+
+
+    public JavaRDD<Map<String, String>> read(JavaSparkContext jsc, DTSConfig config, Map<String, String> argMap) {
+        String project = argMap.get("project");
+        if (StringUtils.isBlank(project)) {
+            return null;
+        }
+
+        String table = argMap.get("table");
+        if (StringUtils.isBlank(table)) {
+            return null;
+        }
+
+        StringBuilder sb = new StringBuilder();
+        for (String p : config.getOdps().getPartition()) {
+            sb.append(p);
+            sb.append(" = ");
+            sb.append(argMap.get(p));
+            sb.append(",");
+        }
+        String partition = sb.deleteCharAt(sb.length() - 1).toString();
+        int partitionNum = NumberUtils.toInt(argMap.get("partitionNum"), 10);
+
+
+        OdpsOps odpsOps = new OdpsOps(jsc.sc(), accessId, accessKey, odpsUrl, tunnelUrl);
+
+        JavaRDD<Map<String, String>> readData = odpsOps.readTableWithJava(project, table, partition,
+                new RecordToMap(config.getOdps().getCols()), partitionNum);
+        return readData;
+    }
+
+    public JavaRDD<Map<String, String>> read(JavaSparkContext jsc, String project, String table, String partition,
+                                             int partitionNum) {
+        OdpsOps odpsOps = new OdpsOps(jsc.sc(), accessId, accessKey, odpsUrl, tunnelUrl);
+
+        JavaRDD<Map<String, String>> readData = odpsOps.readTableWithJava(project, table, partition,
+                new RecordToMap(), partitionNum);
+        return readData;
+    }
+
+    static class RecordToMap implements Function2<Record, TableSchema, Map<String, String>> {
+        private List<String> cols;
+
+        public RecordToMap(List<String> cols) {
+            this.cols = cols;
+        }
+
+        public RecordToMap() {
+        }
+
+        @Override
+        public Map<String, String> call(Record r, TableSchema schema) {
+            Map<String, String> map = new HashMap<>();
+            for (int i = 0; i < schema.getColumns().size(); i++) {
+                if (cols == null || cols.contains(r.getColumns()[i].getName())) {
+                    Object obj = r.get(i);
+                    if (obj instanceof SimpleJsonValue) {
+                        map.put(r.getColumns()[i].getName(), ((SimpleJsonValue) obj).toString());
+                    } else if (obj instanceof Long) {
+                        map.put(r.getColumns()[i].getName(), ((Long) obj) + "");
+                    } else {
+                        map.put(r.getColumns()[i].getName(), r.getString(i));
+                    }
+                }
+            }
+            return map;
+        }
+    }
+
+    public long count(DTSConfig config, Map<String, String> argMap) {
+        String project = argMap.get("project");
+        String table = argMap.get("table");
+
+        StringBuilder sb = new StringBuilder();
+        for (String partition : config.getOdps().getPartition()) {
+            sb.append(" and ");
+            sb.append(partition);
+            sb.append(" = ");
+            sb.append(argMap.get(partition));
+        }
+        String condition = sb.toString();
+
+        Account account = new AliyunAccount(accessId, accessKey);
+        Odps odps = new Odps(account);
+        odps.setEndpoint(odpsUrl);
+        odps.setDefaultProject(project);
+
+        String sql = String.format(countSqlFormat, table, condition);
+        List<Record> records;
+        try {
+            Instance i = SQLTask.run(odps, sql);
+            i.waitForSuccess();
+            records = SQLTask.getResult(i);
+        } catch (OdpsException e) {
+            log.error("request odps error", e);
+            return 0;
+        }
+
+        return Integer.valueOf(records.get(0).getString(0));
+    }
+
+    public Map<String, String> getLastestPartition(DTSConfig config, Map<String, String> argMap) {
+        String project = argMap.get("project");
+        String table = argMap.get("table");
+        // dt = MAX_PT('alg_sence_type_feature')
+        StringBuilder sb = new StringBuilder();
+        sb.append("1");
+        for (String partition : config.getOdps().getPartition()) {
+            sb.append(",max(");
+            sb.append(partition);
+            sb.append(") as ");
+            sb.append(partition);
+        }
+        String cols = sb.toString();
+
+        sb = new StringBuilder();
+        for (String partition : config.getOdps().getPartition()) {
+            sb.append(" OR ");
+            sb.append(partition);
+            sb.append(" = ");
+            sb.append("MAX_PT('");
+            sb.append(table);
+            sb.append("')");
+        }
+        String condition = sb.toString();
+
+        Account account = new AliyunAccount(accessId, accessKey);
+        Odps odps = new Odps(account);
+        odps.setEndpoint(odpsUrl);
+        odps.setDefaultProject(project);
+
+        String sql = String.format(latestPartitionSqlFormat, cols, table, condition);
+        log.info(sql);
+        List<Record> records;
+        try {
+            Instance i = SQLTask.run(odps, sql);
+            i.waitForSuccess();
+            records = SQLTask.getResult(i);
+        } catch (OdpsException e) {
+            log.error("request odps error", e);
+            return Collections.emptyMap();
+        }
+
+        if (records.size() > 0) {
+            Map<String, String> map = new HashMap<>();
+            Record record = records.get(0);
+            for (int i = 0; i < record.getColumnCount(); i++) {
+                map.put(record.getColumns()[i].getName(), record.getString(i));
+            }
+            return map;
+        }
+        return Collections.emptyMap();
+
+    }
+
+    /**
+     * @return k: 列名 v:值
+     */
+    public List<Map<String, String>> read(DTSConfig config, Map<String, String> argMap) {
+
+        String project = argMap.get("project");
+        if (StringUtils.isBlank(project)) {
+            return Collections.emptyList();
+        }
+
+        String table = argMap.get("table");
+        if (StringUtils.isBlank(table)) {
+            return Collections.emptyList();
+        }
+
+        StringBuilder sb = new StringBuilder();
+        for (String partition : config.getOdps().getPartition()) {
+            sb.append(" and ");
+            sb.append(partition);
+            sb.append(" = ");
+            sb.append(argMap.get(partition));
+        }
+
+        return read(project, table, config.getOdps().getCols(), sb.toString());
+    }
+
+    private List<Map<String, String>> read(String project,
+                                           String table,
+                                           List<String> colNames,
+                                           String condition) {
+        Account account = new AliyunAccount(accessId, accessKey);
+        Odps odps = new Odps(account);
+        odps.setEndpoint(odpsUrl);
+        odps.setDefaultProject(project);
+
+        String sql = String.format(sqlFormat, Joiner.on(",").join(colNames), table, condition);
+
+        List<Record> records;
+        try {
+            Instance i = SQLTask.run(odps, sql);
+            i.waitForSuccess();
+            records = SQLTask.getResult(i);
+        } catch (OdpsException e) {
+            log.error("request odps error", e);
+            return Collections.emptyList();
+        }
+
+        List<Map<String, String>> fieldValues = CommonCollectionUtils.toList(records, r -> {
+            Map<String, String> map = new HashMap<>();
+            for (int i = 0; i < r.getColumnCount(); i++) {
+                map.put(r.getColumns()[i].getName(), r.getString(i));
+            }
+            return map;
+        });
+
+        return fieldValues;
+    }
+}

+ 76 - 0
recommend-feature-produce/src/main/java/com/tzld/piaoquan/recommend/feature/produce/service/OSSService.java

@@ -0,0 +1,76 @@
+package com.tzld.piaoquan.recommend.feature.produce.service;
+
+import com.aliyun.oss.OSS;
+import com.aliyun.oss.OSSClientBuilder;
+import com.aliyun.oss.model.CopyObjectRequest;
+import com.aliyun.oss.model.CopyObjectResult;
+import com.aliyun.oss.model.ObjectMetadata;
+import lombok.extern.slf4j.Slf4j;
+
+import java.io.Serializable;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author dyp
+ */
+@Slf4j
+public class OSSService implements Serializable {
+    private String accessId = "LTAI5tHMkNaRhpiDB1yWMZPn";
+    private String accessKey = "XLi5YUJusVwbbQOaGeGsaRJ1Qyzbui";
+    private String endpoint = "https://oss-cn-hangzhou-internal.aliyuncs.com";
+
+    public void transToDeepColdArchive(String bucketName, List<String> objectNames) {
+        OSS ossClient = new OSSClientBuilder().build(endpoint, accessId, accessKey);
+        for (String objectName : objectNames) {
+            try {
+                if (objectName.startsWith("http")) {
+                    continue;
+                }
+                CopyObjectRequest request = new CopyObjectRequest(bucketName, objectName, bucketName, objectName);
+                ObjectMetadata objectMetadata = new ObjectMetadata();
+                objectMetadata.setHeader("x-oss-storage-class", "DeepColdArchive");
+                request.setNewObjectMetadata(objectMetadata);
+                CopyObjectResult result = ossClient.copyObject(request);
+            } catch (Exception e) {
+                log.error("transToDeepColdArchive error {} {}", objectName, e.getMessage(), e);
+            }
+        }
+        if (ossClient != null) {
+            ossClient.shutdown();
+        }
+    }
+
+    public void transToDeepColdArchive2(String bucketName, List<String> objectNames) {
+        OSS ossClient = new OSSClientBuilder().build(endpoint, accessId, accessKey);
+        CountDownLatch cdl = new CountDownLatch(objectNames.size());
+        ExecutorService es = Executors.newFixedThreadPool(3);
+        for (String objectName : objectNames) {
+            es.submit(() -> {
+                try {
+                    if (!objectName.startsWith("http")) {
+                        CopyObjectRequest request = new CopyObjectRequest(bucketName, objectName, bucketName, objectName);
+                        ObjectMetadata objectMetadata = new ObjectMetadata();
+                        objectMetadata.setHeader("x-oss-storage-class", "DeepColdArchive");
+                        request.setNewObjectMetadata(objectMetadata);
+                        ossClient.copyObject(request);
+                    }
+                } catch (Exception e) {
+                    log.error("transToDeepColdArchive error {} {}", objectName, e.getMessage(), e);
+                }
+                cdl.countDown();
+            });
+        }
+        try {
+            cdl.await(1, TimeUnit.HOURS);
+        } catch (InterruptedException e) {
+            log.error("transToDeepColdArchive error", e);
+        }
+        if (ossClient != null) {
+            ossClient.shutdown();
+        }
+    }
+}

+ 100 - 0
recommend-feature-produce/src/main/java/com/tzld/piaoquan/recommend/feature/produce/service/RedisService.java

@@ -0,0 +1,100 @@
+package com.tzld.piaoquan.recommend.feature.produce.service;
+
+import com.tzld.piaoquan.recommend.feature.produce.model.DTSConfig;
+import com.tzld.piaoquan.recommend.feature.produce.util.CompressionUtil;
+import com.tzld.piaoquan.recommend.feature.produce.util.JSONUtils;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.collections.MapUtils;
+import org.apache.commons.lang3.StringUtils;
+import redis.clients.jedis.Jedis;
+import redis.clients.jedis.Pipeline;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * https://help.aliyun.com/zh/maxcompute/user-guide/java-sdk-1/?spm=a2c4g.11174283.0.0.6d0111c1E15lI3
+ *
+ * @author dyp
+ */
+public class RedisService implements Serializable {
+    private int port = 6379;
+    private String password = "";
+    private String hostName = "";
+
+    public RedisService(String env) {
+        if (StringUtils.equals(env, "prod")) {
+            password = "Wqsd@2019";
+            hostName = "r-bp1pi8wyv6lzvgjy5z.redis.rds.aliyuncs.com";
+        } else {
+            password = "Wqsd@2019";
+            hostName = "r-bp1wwqqkjqwwkxgbup.redis.rds.aliyuncs.com";
+        }
+    }
+
+    public void mSetV2(Iterator<Map<String, String>> dataIte, DTSConfig config) {
+        Map<String, String> batch = new HashMap<>();
+        Jedis jedis = new Jedis(hostName, port);
+        jedis.auth(password);
+        long expire = config.getRedis().getExpire();
+        while (dataIte.hasNext()) {
+            Map<String, String> record = dataIte.next();
+            String redisKey = redisKey(record, config);
+            String value = JSONUtils.toJson(record);
+            batch.put(redisKey, value);
+            if (batch.size() >= 5000) {
+                mSet(jedis, batch, expire, TimeUnit.SECONDS);
+                batch.clear();
+            }
+        }
+        if (MapUtils.isNotEmpty(batch)) {
+            mSet(jedis, batch, expire, TimeUnit.SECONDS);
+        }
+        jedis.close();
+    }
+
+    public void setMonitor(DTSConfig config, Map<String, String> argMap) {
+        Jedis jedis = new Jedis(hostName, port);
+        jedis.auth(password);
+        long expire = config.getRedis().getExpire();
+        String redisKey = "ttl:" + argMap.get("table");
+        jedis.setex(redisKey, expire, "");
+    }
+
+    private void mSet(Jedis jedis, Map<String, String> batch, long expire, TimeUnit timeUnit) {
+        long expireSeconds = timeUnit.toSeconds(expire);
+
+        Pipeline pipeline = jedis.pipelined();
+        for (Map.Entry<String, String> e : batch.entrySet()) {
+            // pipeline.setex(e.getKey(), expireSeconds, e.getValue());
+            // 压缩后的key,升级后进行替换
+            try {
+                pipeline.setex(e.getKey(), expireSeconds, CompressionUtil.snappyCompress(e.getValue()));
+            } catch (Exception ex) {
+            }
+        }
+        pipeline.sync();
+
+    }
+
+    public String redisKey(Map<String, String> fieldValueMap, DTSConfig config) {
+        // Note:写入和读取的key生成规则应保持一致
+        List<String> fields = config.getRedis().getKey();
+        if (CollectionUtils.isEmpty(fields)) {
+            return config.getRedis().getPrefix();
+        }
+        StringBuilder sb = new StringBuilder();
+        if (StringUtils.isNotBlank(config.getRedis().getPrefix())) {
+            sb.append(config.getRedis().getPrefix());
+        }
+        for (String field : fields) {
+            sb.append(":");
+            sb.append(fieldValueMap.get(field));
+        }
+        return sb.toString();
+    }
+}

+ 49 - 0
recommend-feature-produce/src/main/java/com/tzld/piaoquan/recommend/feature/produce/util/CommonCollectionUtils.java

@@ -0,0 +1,49 @@
+package com.tzld.piaoquan.recommend.feature.produce.util;
+
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.collections.MapUtils;
+
+import java.util.*;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+/**
+ * @author dyp
+ */
+public class CommonCollectionUtils {
+    public static <T, R> List<R> toList(Collection<T> list, Function<T, R> map) {
+        if (CollectionUtils.isEmpty(list)) {
+            return Collections.emptyList();
+        }
+        return list.stream().map(map).collect(Collectors.toList());
+    }
+
+    public static <T, K, V> Map<K, V> toMap(List<T> list, Function<T, K> keyFunc, Function<T, V> valueFunc) {
+        if (CollectionUtils.isEmpty(list)) {
+            return Collections.emptyMap();
+        }
+        return list.stream().collect(Collectors.toMap(keyFunc::apply, valueFunc::apply));
+    }
+
+    public static <T, K, V> Map<K, V> toMap(T[] list, Function<T, K> keyFunc, Function<T, V> valueFunc) {
+
+        if (list == null || list.length == 0) {
+            return Collections.emptyMap();
+        }
+        return Arrays.stream(list).collect(Collectors.toMap(keyFunc::apply, valueFunc::apply));
+    }
+
+    public static <K, V> Map<K, V> merge(Map<K, V> map1, Map<K, V> map2) {
+        if (MapUtils.isEmpty(map1)) {
+            return map2;
+        }
+        if (MapUtils.isEmpty(map2)) {
+            return map1;
+        }
+
+        Map<K, V> map = new HashMap<>();
+        map.putAll(map1);
+        map.putAll(map2);
+        return map;
+    }
+}

+ 143 - 0
recommend-feature-produce/src/main/java/com/tzld/piaoquan/recommend/feature/produce/util/CompressionUtil.java

@@ -0,0 +1,143 @@
+package com.tzld.piaoquan.recommend.feature.produce.util;
+
+import com.google.common.collect.Lists;
+import net.jpountz.lz4.*;
+import org.xerial.snappy.Snappy;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+import java.util.List;
+
+
+/**
+ * @author dyp
+ */
+public class CompressionUtil {
+    public static String lz4Compress(String input) {
+        byte[] data = input.getBytes(StandardCharsets.UTF_8);
+        LZ4Factory factory = LZ4Factory.fastestInstance();
+        LZ4Compressor compressor = factory.fastCompressor();
+        int maxCompressedLength = compressor.maxCompressedLength(data.length);
+        byte[] compressed = new byte[maxCompressedLength];
+        int compressedLength = compressor.compress(data, 0, data.length, compressed, 0, maxCompressedLength);
+        byte[] result = new byte[compressedLength];
+        System.arraycopy(compressed, 0, result, 0, compressedLength);
+        return Base64.getEncoder().encodeToString(result);
+    }
+
+    public static String lz4Decompress(String input) {
+        // 将Base64编码的字符串解码为字节数组
+        byte[] data = Base64.getDecoder().decode(input);
+        LZ4Factory factory = LZ4Factory.fastestInstance();
+        LZ4SafeDecompressor decompressor = factory.safeDecompressor();
+
+        // 创建一个缓冲区来存储解压缩后的数据
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+
+        // 使用ByteBuffer来处理压缩数据
+        ByteBuffer buffer = ByteBuffer.wrap(data);
+
+        // 假设每次读取的块大小为4KB(可以根据实际情况调整)
+        byte[] chunk = new byte[4096];
+        byte[] decompressedChunk = new byte[4096 * 2]; // 解压缩后的块可能会更大
+
+        while (buffer.hasRemaining()) {
+            int remaining = Math.min(buffer.remaining(), chunk.length);
+            buffer.get(chunk, 0, remaining);
+
+            // 解压缩当前块
+            int decompressedLength = decompressor.decompress(chunk, 0, remaining, decompressedChunk, 0);
+
+            // 将解压缩后的数据写入输出流
+            outputStream.write(decompressedChunk, 0, decompressedLength);
+        }
+
+        // 返回解压后的数据
+        return new String(outputStream.toByteArray(), StandardCharsets.UTF_8);
+    }
+
+    public static String snappyCompress(String input) throws IOException {
+        byte[] inputBytes = input.getBytes(StandardCharsets.UTF_8);
+        byte[] compressedBytes = Snappy.compress(inputBytes);
+        return Base64.getEncoder().encodeToString(compressedBytes);
+    }
+
+    // 将Snappy压缩后的String解压缩回String
+    public static String snappyDecompress(String compressedInput) throws IOException {
+        byte[] compressedBytes = Base64.getDecoder().decode(compressedInput);
+        byte[] decompressedBytes = Snappy.uncompress(compressedBytes);
+        return new String(decompressedBytes, StandardCharsets.UTF_8);
+    }
+
+    public static void main(String[] args) {
+        List<String> lz4Strs = Lists.newArrayList(
+                "8hZ7ImZlYXR1cmUiOiJ7XCJyb3NfMWRcIjpcIjAuNjY1MjY4XCIsGAAUNxgAYzc3NTg0MhgAFnYwAGUwMTgyMDkYAAUwAGEwMjM3OTMYADdzdHIwAFUyNzM3MRgABjAAUTMwNjY3GAAldm8wAHI4LjI5NDk1SAAAGAADMAB0Ny45NDk0MBgAB5AAVjE1MTA0GAADMACBMC4xODkxMzZgAAHwABU02QBEMjM1MtkARXNfMzAZAEIzOTMxkgAB8gAFMgBSMDI0OTHDAAAZAAYyAGEwMjUyMDRkAAH0AAYyAEIzNDQzMgAAGQAHMgBCMzQwORkARHZpZXcnAXEzNDgxOC4wSgABGAADxwBkMzU1NTMyGQAAJwEDYwBjOC4zNzE3YwAAKAEEYwByOC4zMTc4NpUAASkBAzIAcjAuMjA4NTX5AAAZAAQyAAAZADM5NjQrAURfY250lgBTMi43MjF0AQIZAASXAFMuMTgyNDYCVHNoYXJlMwAzOTUzrgACFwADMQBEMTA5MBkAAeAABJYAYzg4NDA5MDMAARoABJcAZDE1NTAwMfwAAn8ABDYAcjMuMjI3NDP2AQIbAAQ3AGMzLjI1MzERAlVyX3JhdJwAcjAuMDA2NjkCAQMbAAOgAAAbADM3NDcEAVRldHVybtIAMzYzNJ8AAxgAAzMAQzg0NTkZAALrAAWdADQwNDTSAAIaAAScAEk1Mjg00QAZNIgDMzI0OH4CAGwBGTSMAyQzNowDA9MAA0ABAbkAIzcxkAMCHAAEbwACHAATNNwDAr0ABKUASzIyMDLYAAQ3AFMzOTA2N9oAAVwBAKYABW8AYTczMjA4M6kCBB0ABDkAIDAulAETMWABAnAAAdMBA2wEJTUyMgEDSwEBHgADUAFLNjcyNJIAAR8ABLIAVDE4NDM3cgEIQAAEfwBUMzI0OTVCAAE7ADNoYXMpAAOCACkyM4UCBiAAA4QANDI2NYYDAroBBiIAA0IAJDk1GwEMIQADQwA1MTA5MQIMhQADRQElNjh7AQsiAATLAEQxMjAw6wAMaAAEEAFEMzAwOO4ADCQABEgAMjUyNdEA8Al9IiwidmlkZW9fc291cmNlIjoiQUdDIn0=",
+                "8hZ7ImZlYXR1cmUiOiJ7XCJyb3NfMWRcIjpcIjAuODgzMTE0XCIsGAAUNxgAYzk5Nzg4MhgAFnYwAFUwNDY1NRcABS8AUjA1NDM1RwA3c3RyLwBCNTI3MUcAABgACDAAMTQ2OUgAJXZvMACFNy43MTk5MzYYAAMwAHI3Ljc4ODE2eAAYdo8ARzM1OTMYAAMwAHIwLjQyMzMxMAAB7wASNNgAgTEuMDIwODAxYQAAGQAlMzAZAFMxNjM5MxkAFXYyAADbACI1MJIAABgABDEAABgAIzg2MQAA2wAEYwAAGQBBMzkzOEoAABkACDIAQTc3NDcZAER2aWV3JgGWNzA0NzE1MC4wGgADyACEMzY3ODk3OTMbAAAqAQNnAHI3Ljk0MjM2cwEAQwEEZwBkNy44ODY1WwEIyQBTNDM3MzB/AAnKAFI0NjI4ODIAVHJfY250mQBjMy41NDUwjwECGgAEmQBiLjY2NzA5FwFVc2hhcmU0AFM3MTQ2NrEAAhoAAzQAczIwMDM5MDUbAAIBAQPNAIg1NTU3ODUzNxwABdAAczYwNTk3MzgcAAKHAARtAWQzLjYzNTmoAgG8AAQ4AGMzLjUxMDgDAkVfcmF0owCBMC4wMTMxMzFvAQMbAAOkAAAbADI0ODKxAmVyZXR1cm7ZAEQyODA0ogADGwADNgBkMTk5OTY2wQEC9QAEogAQMkkDIzAxvgACHAAEowBzNDM5MjE4ORwAAdoAGTSbAzIwOTiwAQQcAANxAACfAzo1NzDdAANMAQDDADM1MTTKAgLeAARxAAAcADI2NzGcAgPEAASpAGUzMDYwMTWNAAL8AAQ5AGQ0NDY0MTniAASOAAQ7AABWAzQ2OTRRAQLHAAQ7AAAdACQwMG4BAlcAAv0BAnoDdDI1MzI1MDIFAQchAAPtAHQxNTU3MzY4BQMDtwACQwAD9AB0MjQzMDQ4OXcCCCMABIcAeTM1MjA3MjN/AjNoYXMrAAMPBSs5MoUCBiIAA40AVDU0NTI5jAICzgEGJAADRgBUMzcyMzavAAwkAANIAGQyMDA4MjixAAHNAAdIAAPXAFQ4NDE2MxoBCyQABNgAajEyNzE1NGACDkoAZDI5Nzk4N90BDJQABEwAYTQzNzY1MiMB8AB9IiwidmlkZW9fc291cmM6BuBVR0MtYXV0b3BpY2sifQ==",
+                "8hZ7ImZlYXR1cmUiOiJ7XCJyb3NfMWRcIjpcIjAuNzI1OTA4XCIsGAAUNxgAYzk4ODA5NRgAFnYwAGUwMzIxNzMYAAUwAEIwNDU2FwA3c3RyLwBRNDQzMjEvAAAYAAcvACI2MXYAJXZvLwCBOC41ODM0NDQvAAAYAAMvAHQ3LjczNjE3GAAHjgBWMjc2MTUYAAMwAHIwLjM1MzAwjwAB7gAWNNcAQTE3ODdhAAAZAC8zMBkAAhd2MgBSMDQ1MTnCAAAZAAYyAAcZAADbAAZkADMwNDZ8AAAYAAgxAAQYAER2aWV3JAGBNDY4OTY3LjCUAAEZAAPGAHQzMjM1ODg0GgAAJwEDYwAAEAFCNDIyOTMAFm9kAAsZAAjHAEMzNDk1iQEAGQAGlgAHGQBUcl9jbnSYAHIzLjIxOTgwXQECGgADmQBiNC4yMTM3HAJUc2hhcmUzAFMyMDc4NbAAAhkAAzIAYzE0OTQzMxoAAeQABC4BeDM3NTc1MjkbAASbAAobAAKCAAQ3ACA0LrcCITQyAgECGwAENwALGwA1cmF0oAB0MC4wMDk5OTYAARsAA6IAYzAuMDEwOFMBZHJldHVybtYAUzE1MDg4oQADGgADNQBUMTQ3NjWhAQLxAAShAFQxNzI5NTYAAhsABKEACRsAAbwAGTSQAzM0MDmhAgCPARk0lAM5NDg3vgAEbwABvwAjNjGnAgLaAARwAAocAAPbAANKAlQxNjk4MKcAA90ABDgAChwAA4wABnEAQjk3ODAEAgTEAAQ5AAkcAAJwAAL0AQJsA1QxMjk1MPECA3QAASAABFYBXzE0MjI3IQABBH0AZDEzMTMzM3kBCEMABIMACiIAAV4AM2hhcyoAA/wEQzQ2ODbXAQshAAOJAFMzNTA0MSIAAsMBDUQARDIwODOqAAwjAANGAGMxNDk5MjJHAAtpAATRAF8zOTg3MCMAAwTSAAgjAAxqAARHABAxJwUETAMMJQAESQAGJQDwAH0iLCJ2aWRlb19zb3VyYx4G4FVHQy1oYW5kcGljayJ9",
+                "8hZ7ImZlYXR1cmUiOiJ7XCJyb3NfMWRcIjpcIjAuOTA4NTM1XCIsGAAUNxgAYzg5Mzk4OBgAFnYwAGUwMzk5NjcYAAUwAGEwNDA5NjkYADdzdHIwAFU0Mzk5MRgABzAAMjU4MkgAJXZvMABjNy43MTcyMAAAGAADMAByNy43NDAwMUgAGHaQAFEzMDg0NF8AABcAAy8AcjAuMzE3MDlfAAHvABQ02ABUOTAzNjPYAERzXzMwGQBSODUyNDXBAAHxAAUyAEUwNDE3MgAXdjIAYTA0MDU4NnwAAfMABzIAMjYxOEsAABkABmQAQzA0NzbEADRpZXclAaExMTExNTI0Ni4wTAABGwADyQCUMTAxNTU4NjQyHAAAKwEDaAByNy45MzU5OYEAACwBBGgAgjcuODMzOTMzTgAALQEDMgBmMC4zMzEyGAAEMQABLwEjOTQGAkRfY250mwCBMy44MTMyMjJLAAIaAAOaAHIzLjk2NDM3TQBUc2hhcmU0AFQ0ODg5N7EAAhoAAzQAdDQ2NTQxMDHMAADoAASbADAxODFBAhk3BQEEoACTMzg4MTk0NTY1OgACiQAEOwBjNC4wMzAx7wACGwAFOQBjLjg3NjM0CAJFX3JhdKYAcjAuMDEwNDiOAQMbAAOnAAEbACIzM1YCZXJldHVybtwAVDQ0MjQ4hwACGwADNgBzNDE2MDcwORwAAvgABKMAVTgzNzkw4QECHAAEpAAwMTg03gATNDkAAcEAGTShAzM1MjeMAACBARk0pQMjMDlFAwPDAARyAALEABI1HQIDHAAEcgABHAAEFQECxAAENwBrNzU3MTYy4QAEOAB9MTU3NTUwOaoAA8MBZTAuOTAzN3MAAqsABDwAAGADMjA4OS8CA1QBAekCA4kEZDM0MjgzORsCCCEAA18BdTMyMjAzOTMDAgK4AAJDAAOBAIQ2MDA4ODQwN0oBByMABIcAkzEyMzQyNDM0MyQAAUAAM2hhc3EAA40AVDExNjUwwgILIwADjwBkMTA0OTUyjgAC0gENSABUNDkwNzNrAAwkAANJAH80NjcwMDkwkAAEA9oAZDE4Nzg3NXkBC5IABNwAZDQwNjQ0MbwBDG8ABOIBZDgzNzc5NWcDDCYABEwAcTE4NTE1OTaXAPAAfSIsInZpZGVvX3NvdXJjRQbgb2xkVmlkZW9QdXNoIn0=",
+                "8hZ7ImZlYXR1cmUiOiJ7XCJyb3NfMWRcIjpcIjAuOTIyMzIzXCIsGAAUNxgAYzg2MTY1NxgAFnYwAGUwNTAwNTgYAAUwAGEwNDcwNTIYADhzdHIwAEU0Mjc0GAAGMABRNTQ2MDYYACV2bzAAdTcuNjU4NjkXAAMvAHQ3LjgyNjU1GAAHjwBSMzgzMzh3AAAYAAMwAIEwLjM2ODI1NUgAAe8AFDTYAFI5MzAwMUkAABkAJDMwGQBSODk2NjWpAAHxAAUyAFYwNTE0MfIABjIAYTA0ODg5MWQAAfMABzIAMjUyODwBABkABzIAQzU0NTL1ADRpZXcmAaEzNTUxMjA3MC4wTQABGwADygCUMjQyMzQ3MTg5HAAALAEDaQB0Ny42NzU4NP4ABmkAdDcuNzU3MzkZABV2MgByMC4zOTQ2N5sAABkABpsAUjM3OTI2mwBVcl9jbnScAGMuODQwOTkAAQEaAAObAGMzLjYxODlNAFRzaGFyZTQAczE5MjczOTSzAAIbAAM1AHkxMzIzMzY56wAE0ACTNTgwOTY2MzE1OQABCAEEogCjMTA3OTkyMzk5Nx4AA6YAAzwAczMuODY3NTnJAgGnAAQ6AHMzLjcxNjUwDQJFX3JhdKkAdDAuMDEzMDM2AAEbAAOpAAIbABIw3wFlcmV0dXJu3wBUNzc3NjeSAQMcAAM3AIMxMTQwMjkwMaUAAvwABOEAejMyMTE5OTUdAASnAIM1ODg4MzgwNDoAAd8AGTSpAzcwMDShARg0rQNDNTk1MqsAA+EAAxgBAccAKTI5/QAEcgABHAAUMSYDA+MAAzgAdTI5ODcyMTeOAAIeAAQ6AIw1Mjc5ODQyNqwAAz0AdzAuOTY3Njg0Ahg0agNDOTE0OTgDAz0BAeoBA5MEhDEzNjE0NjkyXAACegABIgADZAF/ODkyNDU0OCIAAQRqAZ8yMjkyOTQwNzNGAAAExQCENDA5NTc4MDbJAQFjADNoYXOUAAOPAFQ0NjI4MXQCCyMAA5AAZDMxNTA4NmsAAvMBDUgAXzE5MzIxJQAGA0oAdDEzMjY2NTdYAQtvAATdAGQ3NzIzNzFGAgslAATeAH8xNDIwNjQ3lgAGA6sBdDMwODY5NjKQAwy9AAROAHE1NzcwMzc1JwDwAH0iLCJ2aWRlb19zb3VyY1UGsHRyYW5zcG9ydCJ9",
+                "8hZ7ImZlYXR1cmUiOiJ7XCJyb3NfMWRcIjpcIjAuNDkzMDIzXCIsGAAUNxgAYzc5NTI3ORgAFnYwAGUwMjI2NzQYAAUwAGEwMzIyODUYADdzdHIwAEI0NTk4SAAAGAAGMABRNDA1OTYwACV2bzAAcjguOTI5MjRIAAAYAAMwAIM3Ljk2MDgyNzAAB5AARDIwMjRHAAePAFQyNTcwMacARXNfMTTYAEY4MjAzGQAkMzAZAFQ2NzI2NwkBF3YyAFQwMzEzOTIAF3YyAFIwMjc1NqsAANsABmQAUjA0MDE0MgAAGQAHMgAzNDA5VQFEdmlldyYBYjkzNTAuMNwAABcAA/UAZDI1NDYwORkAACUBA8YAcjcuODc4NjBiAAAmAQRiAHI4LjI1NTkwxgAAEAEGlABSMjQ3MzOIAQAZAAQyAHIwLjIyNzU3WQFUcl9jbnSVAHIyLjcxNzk0MwACGgADmACBNC4yMzA1NzGxAFRzaGFyZTQAJDQzyAACFwADMQBUMTAzMzbIAADhAASXAGgzNDEzNzMaAASYAHMxMDI5MzM1GwACfwAENgByNC4xOTk0NYAAAhsABDcAdDMuNDQ1NTPPADVyYXScAIEwLjAwODM0MrYAAxsAA6AAABsAMzc2M1EAVGV0dXJu0gA0MjEyhAACGAADMwA6ODIy6wAEnQBTMTM3MDQzAAIFAQScAFM0MjE4MRoAAbYAHzSHAwQAhgEZNIsDMjMwNe0BA7gABG0AAbkAIjQ3AAMDHAAEbwAAHAASOO0AAroABDUAVDEwNzE3iAACGwAENABEMjgzN70ABaMAA3UCdDAuNzgwMTVsAAKkAAQ5AAA/AzMxMjKlAAJVAAjiAjQxODnnAQgeAANNAVo2NTQzOJIAAj0AA3sANjg0NAsCBz8ABH4AVDIzNDI1YAABOgAzaGFzaAAD6QQUN2AACx8AA4IANDE5NEAAAtECDUEADNsCBiEABEMANDAzNrACC2UABHoBNDI1NU4CCyIABMoAJTgy6QANiAADDQEAMgMEvwEMJAAERwBBNDIyN2oA8AB9IiwidmlkZW9fc291cmMBBpB1bmtub3duIn0=",
+                "8hZ7ImZlYXR1cmUiOiJ7XCJyb3NfMWRcIjpcIjAuNTk3NDY0XCIsGAAUNxgAVDc0OTM1GAAWdjAAYzAyMTkxMjAAFnYwAGEwMzMxODkYADdzdHIwAFUzNjY3NRgABjAAMjQ0Mi8AJXZvLwBxOC4yNTY3OC4AABcAAy4AZTguMTAzMBgAB44AYzE4MDkyMTAAB44AUjI2ODkzGAAB7gAUNNcAYTg0MDU4NjEAABkAJDMwGQBSNzU3OTSoAADYAAYyAGMwMzg1MzcyABd2MgBSMDMzNTbDAAHyAAYyADM0NThLAAAZAAcyAAb0ADRpZXckAZEyMzg0NDI4LjBkAAEaAAP4AIQyMzY3MDUyNRsAACkBA2YAcjcuOTIyMzfKAAArAQRmAHI3Ljk0ODg0/AAAFAEGygBiMzA1MzAzZgAJygBSMjY2ODNLAFVyX2NudJoAYi43NzA1NJABAhoAA5oAcjMuMjUyMjAgAlRzaGFyZTQARDg3NDTMAAIZAAMzAGUxMDQ4MzbMAADnAASbAIQ1OTA0OTI2MegAABwABNAAkzEyMjM0Nzc1Mh0AAocABDoAczMuNDYxMTi4AQG8AAQ5AHMzLjMzMjc1ugFFX3JhdKQAcjAuMDA3OTC8AQMbAAOmAHMwLjAxMDIwUQBUZXR1cm7aAFQ1MjI0N4YAAhoAAzUAYzc4NTU5NhsAAvUABKEAeTI3MDcxMjQcAASiAGQ1NDE4NzUSAQHZABk0mgMoNjKWARk0ngM0NzcymAEC2wADGQIAwQAyMTEzmgEDHAAEcQAAHAAyMDA3AwIDwwAFqQBjMjc1NTcwqQADHQAEOQAgNDHGAAUdAABoAQCqAAVzAFc4NTg4MkECGTRaA0M2NDg3bAECdAAB5AIDggRVNDMxMzndAQcgAANcAWQ2MzY1NzRdAQOYAAJBAAN/AHQxODAyNzkyPgIIIwAEwACDMzI2NDY4NDPeAAE/ADNoYXMrAAOKAEQxODg1ugILIgADjABFMjQxNSMAAugBDUYANTg3NswDDCMAA0cAZDEwNTA2NBQCC2sABJABAPYAHzUkAAQE1wBzMTIzMjM0OdYAAocDB5EAAyABTzI2NDmUAAcETABhNTM2OTYziQHwAH0iLCJ2aWRlb19zb3VyYzYGwHVzZXJ1cGxvYWQifQ==",
+                "8hZ7ImZlYXR1cmUiOiJ7XCJyb3NfMWRcIjpcIjAuMzk3OTczXCIsGAAUNxgAYzQ0NTY4NRgAFnYwAGUwMjA5NDgYAAUwAGEwMTk2NzEYADdzdHIwAFU1MjYzNxgABjAAQjQ0MTNIACV2bzAAcjguNDUxODlIAAAYAAMwACA4LhcAITU2SAAYdpAAUjE3NzA1wAAAGAADMABiMC4xNjc1XwAB7wAVNNgAMzcyN2AAABkAJDMwGQBSODYzMjlhAAHxAAUyAGEwMTgzNDl6AAAZAAYyAGEwMzYwNjQZAAHzAAYyAEIzODgwMgAAGQAHMgBCNDE3N1UBRHZpZXcmAYE5NTgwMjUuMEsAARkAA8cAdDQ0OTE4ODcaAAAoAQNlAHI4LjY5MjgyMgAWb2QAdjguMDQyOTARAQQxAHQwLjE1OTUwWgEIyABDMjkwMPkARF9jbnSWAHMyLjUwMDQ5RQEBGgADlwAgMi4sAQOOAVRzaGFyZTQAUzUwNDI4rwACGQADMwBjMTk4MjYxGgAB4wAEmQB4ODg1NzkwORsABDEBgzM1Nzk5ODc2HAAChAAEOAAQMgIBIjYz6gACGwAFOABiLjkxNDExmwFVcl9yYXShAHQwLjAwODM31QABGwADowAAGwAjNzTEAmRyZXR1cm7XAEQyMDA2ogADGgADNQBTODgzNjKgAALxAASgAFQzNDM3NrsBAhsABKAAaTE0OTU1M9cAGTSTAzI2ODEzAwHYABk0lwMyNDQ1WwIDvgAEcAAAvwAqNjj1AARxAAAcACQ5MhwAA9sAAxkCYzE2MjUzMMMAA90ABDgAZTEyOTEwOR0AAIABAKkAAzoAcjAuNDc3MTDQAgSqAAQ7AABRAzMyMDSfAQJzAAL4AQJxA1QxNjk2MhECA3cAASAAA1kBVDc1Mjc1WgEIIAAE8ABzMTQxMjg0NDYDCCIABIMAdDEwMzg0MTFlAAFeADNoYXMrAAMCBTQ4MDKJAQshAAOJAEQzMzU2AgECxwENRABENTA1N78DDCMAA0YAVDE5ODgwGgMLaQAE0gA6NjAzIwMGaQAE0wBfMzI5ODVrAAUESABUMzQwNjWvAgyQAARKAGExNDk3NzAmAPAAfSIsInZpZGVvX3NvdXJjJgbg5YWo6Z2ic3BpZGVyIn0=",
+                "8hZ7ImZlYXR1cmUiOiJ7XCJyb3NfMWRcIjpcIjAuODA2MjEzXCIsGAAVNxgARDY2NDAYABZ2MABjMDUwNTA2MAAWdjAAYTA1Mzg1MhgAN3N0cjAAQjYyNjQwAAAYAAYwAEI2MjE1GAAldm8wAIE3LjQzNjY4NEgAABgAAzAAUzcuMjc4jwAYdo8AVDM3NTU5RwAHjwBhMzkxOTM5RwAB7wAWNNgARTU3NDEZACQzMBkAVzk3NzY18QAFMgBSMDU0NjHaAADyAAYyADMwNTgKAQDaAAcxAEI2MzA4YwAAGQAIMQBBOTM4N3wARHZpZXclAZYyNDczMDc5LjAaAAP3AHUxMzMxMDA2GwAAKQEDZwBUNy40NjC6AQAqAQRnAABDATYwNjgUAQaZAFQ0MDc0MVwBCMoAUjQzMTQymQBUcl9jbnSaAGgzLjg5MTkZAAOZAIEzLjg2MzI4OLIAVHNoYXJlMwBjMTU0OTI4zAACGgADNABjODI3MzA0GgACAAEDzACIMjAwNzM1ODUcAATPAHQzNTYwMTU3HAADnwADOQByMy44ODM3M9MAAhsABDgAVDQuMjA0YgJVcl9yYXSjAHQwLjAxMjk31wABGwADpAAAGwAjMzlQAFVldHVybtgARTI0OTCGAAIbAAM1AEU3MTY3vwEC8wAEdAFzMTI2NjI1NtkAAhwABKIAIDIxdQEE3AEBvgAZNJkDIjM3YAEEGwADbwBlMC44NjQxHAABvwAEcAAAwABBNDA2MpgBAxwABHAAABwAEzOYAgPBAAQ3AHMxMDk2MjUwpwADHQAEOAB0MjA2NzAzMh0AACACAKgAA4MBAI4ANDM1NU0BAh0ABDsAAFYDOTQ0NlABAvoBAnYDRTkyODg6AQN3AAEgAAPrAGU1MjE2NzMjAQK1AAEhAAS6AG84MTc4MjUiAAEEhAB0MTUzNTk0NoQBAT4AM2hhc24AAwgFUzMyMDkz4gALIgADiwBUMTg1NTNFAALJAQ1GAGMxNTUzOTdHAAwkAANIAFk4Mjk0MwYDB0cAA1QBVDI4MjI2SAALjwAE1wBUNDkxNjZIAAxsAAQeAVUxMjY5NB4BDCYABEsAYTIxMjEyMP4B8AB9IiwidmlkZW9fc291cmMxBvAI5YWo6Z2ic3BpZGVyLWF1dG9waWNrIn0=",
+                "8hZ7ImZlYXR1cmUiOiJ7XCJyb3NfMWRcIjpcIjAuODk5NTQ3XCIsGAAVNxgAUzM3MTIzGAAWdjAAZTA0MzgyNhgABTAAYTA0NzE5MRgAOHN0cjAANTg3MhcABi8AQjU2MzdfACV2by8AgTcuNzYzMzE1MAAAGAADMAB0Ny44NTk3MzAAB48AUjM0MDIzjwAAGAADMACBMC4zNzA5MDhIAAHvABQ02ABlOTAzNTY5GQAkMzAZAFQ4NTE5MAkBF3YyADUwNTExABd2MQBXMDQ2OTfyAAVjAEMwNTcySgAACwEHMgA0NTUxrQA0aWV3JQGhMTk0MTkxMjYuMJcAARsAA8kAlDE3NzU2ODA2MxwAACwBA2kAcjcuNTAyNDflAAAtAQRpAHY3LjYwNzA3FgEEMgB0MC4zODc4MF8BCM0AUjM1NzMxYAFUcl9jbnScAGkzLjY0NjgaAAObAGMzLjU3MjSsAVRzaGFyZTQAVDk0NjEwsgACGgADNACEMTAwMDk5ODnOAADqAASdAJg0NDUxOTE3NTEdAAXTAIM2MzA4MTY0MB0AA6QAA9gAgTMuODI4MjY0PwECGwAEOQBjMy42NTc5ggJVcl9yYXSnAHIwLjAxMjAxQAEDGwADqAAAGwAiMzLcAWRyZXR1cm7cAGQ4NTEwNjSGAAIbAAM1AHM4Mzc5NTkyHAAC+AAE3QBrMjU0Njc5HQAEpAB0NDIwNzM2MDoAAcEAGTSjAzc3MzDdABk0pwM5NTM0wwAEcwABxAAiNTB8AwPfAARyAAAcADoyODThAANNASAyMxcBFDmHAQP/AAQ6AIMzNTg0Mjc5OOYABawAAz0AczAuOTYyNTdgAwBJAhk0ZANDODM5M64AAjwBAe0CA40EETa3AgVbAAchAANiAYQ2NTg2MTM1N30AByIABPYAjzE3MjY0NzcwRgABBMQAdTI3MjY1ODlxAQGEADNoYXPrAAOOAFQyMzMzN5QBCyMAA5AAZDIzNDU2M2sAAgQDDUgAXzk0ODQ2JAAFA0kAdDEwMDMxMjbeAgtuAATcAGQ2MDExMTB9AgslAATdAGo5Nzk4NTR+AweUAAOoAYMyMzkwNjcwNUoBDLsABE0AcTQwNTQ4OTlxAfAAfSIsInZpZGVvX3NvdXJjTAbg5Z6C55u0c3BpZGVyIn0="
+        );
+
+        List<String> snappyStrs = Lists.newArrayList(
+                "mQyQeyJmZWF0dXJlIjoie1wicm9zXzFkXCI6XCIwLjY2NTI2OFwiLAkYADcRGBg3NzU4NDJcCRgAdhkwFDAxODIwORUYFTAUMDIzNzkzBRgIc3RyHTAQMjczNzEVGBkwEDMwNjY3BRgEdm8VMBg4LjI5NDk1CUgBGA0wGDcuOTQ5NDARGB2QEDE1MTA0GRgNMBwwLjE4OTEzNgVgBfAANBXZDDIzNTIR2QxzXzMwFRkMMzkzMQmSAHIBYhUyEDAyNDkxCcMBGRkyFDAyNTIwNAVkBfQZMgwzNDQzCTIBGR0yDDM0MDkJGQx2aWV3MScYMzQ4MTguMAVKBRgNxxQzNTU1MzIRGSEnDWMUOC4zNzE3DWMBGRFjGDguMzE3ODYJlQB2MscAEDIwODU1CfkBGREyGDAuMjA5NjQtKwxfY250EZYQMi43MjEtdAkZEZcQLjE4MjRNNhBzaGFyZREzCDk1Mw2uCRcNMQwxMDkwERkF4BXIEDg0MDkwDTMFGhGXFDE1NTAwMRH8CX8RNhgzLjIyNzQzKfYJGxE3FDMuMjUzMU0REHJfcmF0FZwYMC4wMDY2OSkCDRsNoAEbCDc0Ny0EEGV0dXJuEdIINjM0DZ8NGA0zDDg0NTkNGQnrFZ0IMDQ0EdIJGhGcDDUyODQy0QAANDKIAwgyNDhNfiGFBDRzEWoUMC43NzM2cYwAXwnTTQgFuQQ3MW2QCRwRbwkcADRt3A3VDTgMMjIwMjrYABE3EDM5MDY3DdoRihE3GDAuNzMyMDg9eQA0MkIDDDQzMzEtYAlwJdONbAQ1MjUyLh4ALVAMNjcyNDqSAAk9BDRkhaoQMTg0Mzcxci5AABW4DDI0OTURQgU7CGhhcw0pDYIEMjMyhQIZIAA3CWQIMjY1cYYpuhkiDUIEOTUxGz4hAA1DCDEwOVUxPoUADcoENjg1ezoiABHLDDEyMDAR6z5oABFGEDMwMDg0Lac+JAARSAg1MjUJ0Vx9IiwidmlkZW9fc291cmNlIjoiQUdDIn0=",
+                "1AyQeyJmZWF0dXJlIjoie1wicm9zXzFkXCI6XCIwLjg4MzExNFwiLAkYBDdkDRgYOTk3ODgyXAkYAHYZMBAwNDY1NRUXFS8UMDU0MzU0BRgIc3RyHS8MNTI3MQlHARguMAAINDY5BTAEdm8VMBw3LjcxOTkzNhUYDTAYNy43ODgxNgl4AHYujwAMMzU5Mx0YDTAYMC40MjMzMQkwBe8ENGQF2BwxLjAyMDgwMQVhARkEMzAVGRAxNjM5Mw0ZBHZfETIUMC4wNTUwCZIIcm92FTEBGAQ4Ng0xAdsuMQAMMzkzOAVKARkRYwEyDDc3NDcFGQx2aWV3MSYgNzA0NzE1MC4wGRoNyBwzNjc4OTc5MxEbISoNyhw3Ljk0MjM2NAk0BG9yFWcUNy44ODY1MVsuyQAQNDM3MzANfzLKABA0NjI4OAkyFHJfY250X00eFDMuNTQ1MC2PCRoRmRQuNjY3MDkpFxBzaGFyZRU0EDcxNDY2DbEJGg00GDIwMDM5MDUNGykBDc0cNTU1Nzg1MzcuHAAxNxw3NjA1OTczOA0cCYcRORQzLjYzNTlRqAW8ETgUMy41MTA4TQMMX3JhdBWjGDAuMDEzMTNNBQkbDaQBGwg0ODJJsRRyZXR1cm4V2QwyODA0EaINGw02FDE5OTk2NjHBCfURogAyYUkEMDENvgkcEaMYNDM5MjE4OQ0cBb8ANDKbAwgwOTgpsBEcDXEYMC45OTU3MDbdAC1MAcMINTE0TcoJ3hFxARwINjcxSZwNxBE4FDMwNjAxNRWNCfwRORQ0NDY0MTkR4hGOETthVgg2OTQxUQRjbgHHETsBHQQwMDFuCVcp/QBkZZMcMjUzMjUwMi5J/i4hAA3tGDE1NTczNjhxBS4iABGBGDI0MzA0ODlRdy4jABGHGDM1MjA3MjMyfwIIaGFzDSsNjAQ5MjqFAhkiDY0UNTQ1Mjk4Da0pzhkkDUYQMzcyMzYRrz4kAA1IFDIwMDgyOBGxJRAdSC3LEDg0MTYzMRo6JAAR2BQxMjcxNTQ2YAJGSgAUMjk3OTg3Md0+lAARTBQ0Mzc2NTIlIzh9IiwidmlkZW9fc291cmPBOjRVR0MtYXV0b3BpY2sifQ==",
+                "uAyQeyJmZWF0dXJlIjoie1wicm9zXzFkXCI6XCIwLjcyNTkwOFwiLAkYADcRGBg5ODgwOTVcCRgAdhkwFDAzMjE3MxUYFTAMMDQ1NgkXCHN0ch0vEDQ0MzIxBS8BGB0vBDYxCXYEdm8VLxw4LjU4MzQ0NAUvARgNLxg3LjczNjE3ERgdjhAyNzYxNRkYDTAYMC4zNTMwMAmPBe4ENGQN7xQ5ODE3ODcFYQEZBDMwUhkABHZfGTIQMDQ1MTkJwghyb3YdMh0ZAdsuMgAANg18ARgZYwAwFRgMdmlldzEkHDQ2ODk2Ny4wBZQFGQA3CZQYMzIzNTg4NBEaIScANAkaIRAMNDIyOQkzBG9yFWQ6GQAuxwAMMzQ5NS2JAHYyxwAdGRByX2NudBGYHDMuMjE5ODA0BWUJGg2ZFDQuMjEzN0kcEHNoYXJlETMQMjA3ODUNsAkZDTIUMTQ5NDMzDRoF5BHLGDM3NTc1MjkuGwAxMTYbAAByCZwtAgQ0LkG3BDQyHZ0RNzobAAhyYXQVoBgwLjAwOTk5ETYFGw2iFDAuMDEwOC1TFHJldHVybhHWEDE1MDg4DaENGg01EDE0NzY1MaEJ8RGhEDE3Mjk1ETYJGxGhMhsABdcANDKQAwg0MDlNoQHzBDRzEW4YMC45ODQ4NzK+ABFvBb8ENjFNpwnaEXA2HAAN2y1IEDE2OTgwEacNHBE4NhwADYwRORQwLjk3ODBJBBEcETkyHAAJcCn0AGRlhRAxMjk1MFHxLiAAMVYQMTQyMjdOIQAAMRG2FDMxMzMzNDaUAQVjEYM2IgAFGwhoYXMNbQ2IDDQ2ODYNQzohAA2JEDM1MDQxDSIpw0JEABAyMDgzN1ojAA1GFDE0OTkyMg0kOmkAEdEQMzk4NzBWIwAR0i4jAEKNADEYoSdxTD4lABFJGSU4fSIsInZpZGVvX3NvdXJjwR40VUdDLWhhbmRwaWNrIn0=",
+                "3wyQeyJmZWF0dXJlIjoie1wicm9zXzFkXCI6XCIwLjkwODUzNVwiLAkYADcRGBg4OTM5ODhcCRgAdhkwFDAzOTk2NxUYFTAUMDQwOTY5BRgIc3RyHTAQNDM5OTEVGB0wCDU4MglIBHZvFTAUNy43MTcyDTABGA0wGDcuNzQwMDEJSAB2LpAAEDMwODQ0BV8BFw0vGDAuMzE3MDkJXwXvBDRkFfAIMzYzEdgMc18zMBEZEDg1MjQ1CcEAcgFhFTIMMDQxNxUyAHYdMhQwNDA1ODYFfAXzHTIINjE4CUsBGRlkDDA0NzYNxAhpZXcxJSQxMTExNTI0Ni4wBUwFGw3JIDEwMTU1ODY0MhEcISsNaBg3LjkzNTk5CYEBGRFoHDcuODMzOTMzCU4yzAAMMzMxMhkYETElLwQ5NE0GDF9jbnQRmxwzLjgxMzIyMgVLCRoNmhgzLjk2NDM3CU0Qc2hhcmURNBA0ODg5NxGxCRoNNBg0NjU0MTAxEcwlAw3NCDE4MUFBADcyBQERoCAzODgxOTQ1NjUNOgmJETsUNC4wMzAxDe8JGxU5FC44NzYzNE0IDF9yYXQVphgwLjAxMDQ4KY4NGw2nBRsIMzM0CfUQZXR1cm4V3BA0NDI0OBGHCRsNNhg0MTYwNzA5DRwJ+BGjEDgzNzkwNeEJHBGkCDE4NAHeADQNOQXBBDRzEXFhoQg1MjcNjCGbBDRzEXIUMC44OTA5bUUNwxFyCcQANUkdDRwRcgUcMRUJxBE3FDc1NzE2MjrhABE4GDE1NzU1MDlCqgAtwwGrBDM3FXMEY24BqxE8GDAuODUwODlJLw11SQEAZGWZFDM0MjgzOVEbLiEALV8YMzIyMDM5M1UDAGUllwlDDYEcNjAwODg0MDcxSh0jEYcgMTIzNDI0MzQzDSQFQAhoYXMNLA2NEDExNjUwUcI6IwANjxQxMDQ5NTIRjinSQkgAEDQ5MDczEWs+JAANSRg0NjcwMDkwWpAADdoUMTg3ODc1MXk6kgAR3BQ0MDY0NDExvD5vABFLFDgzNzc5NXFnPiYAEUwYMTg1MTU5NgWXOH0iLCJ2aWRlb19zb3VyY8FFNG9sZFZpZGVvUHVzaCJ9",
+                "7AyQeyJmZWF0dXJlIjoie1wicm9zXzFkXCI6XCIwLjkyMjMyM1wiLAkYADcRGBg4NjE2NTdcCRgAdhkwFDA1MDA1OBUYFTAUMDQ3MDUyBRgIc3RyLjAADDQyNzQVGBkwEDU0NjA2BRgEdm8VMBg3LjY1ODY5FRcNLxg3LjgyNjU1ERgdjxAzODMzOAl3CHZvdhEwHDAuMzY4MjU1BUgF7wQ0ZBHwDDMwMDEJSQEZBDMwERkUODk2NjU0DTIEdl8ZMhAwNTE0MRnyGTIUMDQ4ODkxBTIF8xVkEDA1NTI4KTwBGR0yDDU0NTIN9QhpZXcxJiQzNTUxMjA3MC4wBU0FGw36IDI0MjM0NzE4OREcISwENGQFzRg3LjY3NTg0Ef4AchVpGDcuNzU3MzkRGS7NABQzOTQ2NzcJZwRvdhUyGDAuMzc5MjYJmxByX2NudBWcFC44NDA5OS0ABRoNmxQzLjYxODkNTRBzaGFyZRE0GDE5MjczOTQNswkbDTUYMTMyMzM2OTLrABHQIDU4MDk2NjMxNQ05JQgxPSQxMDc5OTIzOTk3DR4JjBE8GDMuODY3NTlNyQWnEToYMy43MTY1ME0NDF9yYXQVqRgwLjAxMzAzETYFGwA3KV0JGwAwKd8UcmV0dXJuFd8QNzc3Njcxkg0cDTccMTE0MDI5MDENpQn8FaUUMjExOTk1Nh0AEacYNTg4ODM4MDE2BcQANDKpAwgwMDQ9oQQ0cxF0GDAuODU5NTINqwnGEXMFxwQyOTL9ABFyBRwAMXEmDeNNXBgyOTg3MjE3FY4JHhE6HDUyNzk4NDI2DeYVrA09GDAuOTY3NjhdNAQ0cxU8FDAuOTE0OW04DVgl6o2THDEzNjE0NjkyEVwJegUiLWQYODkyNDU0OE4iABGCIDIyOTI5NDA3M0pGABHFHDQwOTU3ODA2MckFYwhoYXMNcg2PEDQ2MjgxUXQ6IwANkBQzMTUwODYRaynzQkgAEDE5MzIxYiUADUoYMTMyNjY1NzFYOm8AEd0YNzcyMzcxMQ3bOiUAEd4UMTQyMDY0NQFClgAANEnnGDMwODY5NjJxkD4nABFOGDU3NzAzNzUFJ3R9IiwidmlkZW9fc291cmNlIjoidHJhbnNwb3J0In0=",
+                "lgyQeyJmZWF0dXJlIjoie1wicm9zXzFkXCI6XCIwLjQ5MzAyM1wiLAkYADcRGBg3OTUyNzlcCRgAdhkwFDAyMjY3NBUYFTAUMDMyMjg1BRgIc3RyHTAMNDU5OAlIARgZMBA0MDU5NgUwBHZvFTAYOC45MjkyNAlIARgNMBw3Ljk2MDgyNw0wHZAMMjAyNBFHAHYRLxgwLjI1NzAxEacQc18xNGQN8BQ3ODIwMzQFSCEICDMwZA0ZEDY3MjY3MQkAdh0yFDAzMTM5NA0yAHYdMhAwMjc1NgmrAdsZZBAwNDAxNAkyARkZZAwwNDA5LVUMdmlldzEmFDkzNTAuMAViBRcN9RQyNTQ2MDkRGSElDcYYNy44Nzg2MAliARkRYhg4LjI1NTkwCcYAdjLGABAyNDczMymIARkRMhgwLjIyNzU3KVkQcl9jbnQRlRgyLjcxNzk0CTMJGg2YHDQuMjMwNTcxBbEQc2hhcmURNAQ0MxHICRcNMRAxMDMzNhHIBfgNyRQzNDEzNzMuGgARmBgxMDI5MzM1DRsJfxE2GDQuMTk5NDUJgAkbETcYMy40NDU1MxHPCHJhdBWcHDAuMDA4MzQyBbYAcgkbDaABGwg3NjMNURBldHVybhHSCDIxMhGECRgNMwg4MjI26wARnRAxMzcwNA0zKQURnBA0MjE4MQ0aBbYANFqHAwhfY24BHA1qGDAuNzkzMDUp7Q24EW0FuQQ0N2kADRwRbwEcADgJ7Qm6ETUQMTA3MTcRiAkbETQMMjgzNxG9EYcRNxgwLjc4MDE1EWwJpBE5GDAuNjcxMjINpQlVKeoEZFyBaAgxODkx5y4eAA3jEDY1NDM4NpIACT1NJwg4NDRZCwBlJZ8FIBF+EDIzNDI1EWAFGghoYXMNKQ2BADcRYDofAA2CCDE5NBFAKbVCQQA+2wIZIRFDCDAzNlGwOmUAEcgIMjU1UU46IgARygQ4MhXpQogALQ1hMjG/PiQAEUcMNDIyNwVqbH0iLCJ2aWRlb19zb3VyY2UiOiJ1bmtub3duIn0=",
+                "zgyQeyJmZWF0dXJlIjoie1wicm9zXzFkXCI6XCIwLjU5NzQ2NFwiLAkYBDdkDRgYNzQ5MzU0XAkYAHYZMBQwMjE5MTIVGBUwFDAzMzE4OQUYCHN0ch0wEDM2Njc1FRgZMAg0NDIJLwR2bxUvGDguMjU2NzgFLgEXDS4UOC4xMDMwFRgdjhQxODA5MjENMAB2ETAYMC4yNjg5MwkYBe4ENGQN1xQ4NDA1ODYFMQEZBDMwERkQNzU3OTQJqAByAWIVMhQwMzg1MzcNMgB2HTIQMDMzNTYJwwXyGTIINDU4DUsBGRlkHfQIaWV3MSQgMjM4NDQyOC4wBWQFGg34HDIzNjcwNTI1ERshKQ1mGDcuOTIyMzcJygEZEWYYNy45NDg4NAn8AHYyygAUMzA1MzAzCWYyygAQMjY2ODMJSxByX2NudBWaFC43NzA1NCmQCRoNmhwzLjI1MjIwNAVNEHNoYXJlETQMODc0NBHMCRkNMxQxMDQ4MzYVzABpIQENzRw1OTA0OTI2MRHoARwR0CAxMjIzNDc3NTINHQmHEToYMy40NjExOC24AF8BvBE5HDMuMzMyNzU3BaMUcl9yYXRlEaQYMC4wMDc5MCm8DRsNphgwLjAxMDIwDVEQZXR1cm4RNhA1MjI0NxGGCRoNNRQ3ODU1OTYNGwn1EaEYMjcwNzEyNDIcABGiFDU0MTg3NTESBb4ANDKaAwQ2Mi6WAQQ0cxFwGDAuNzQ3NzIxmAnbLUwBwQgxMTMpmg3cEXEBHAgwMDdJAw3DETgYMjI3NTU3MA2pDR0ROQQ0MQHGFR0haAGqFXMQODU4ODJdQQQ0cxU7GDAuNzY0ODctbCk3JeEIMWRcgYIQNDMxMzk13R0gLVwUNjM2NTc0MV0uIQARfxwxODAyNzkyMTa7AAVkEcAcMzI2NDY4NDMNIwUcCGhhcw1ODYoQMTg4NThWIgANjAwyNDE1FSMp6EJGAAg4NzZ1zD4jAA1HFDEwNTA2NFEUPo0ALVUB9gA1WiQAEdcYMTIzMjM0OQ20Pm4AEUoMMjY0OWaUABFMFDUzNjk2MyWJOH0iLCJ2aWRlb19zb3VyY8E2LHVzZXJ1cGxvYWQifQ==",
+                "wAyQeyJmZWF0dXJlIjoie1wicm9zXzFkXCI6XCIwLjM5Nzk3M1wiLAkYADcRGBg0NDU2ODVcCRgAdhkwFDAyMDk0OBUYFTAUMDE5NjcxBRgIc3RyHTAQNTI2MzcVGBkwDDQ0MTMJSAR2bxUwGDguNDUxODkJSAEYDTAEOC4BFwQ1NgVIAHYukAAQMTc3MDUJwAEYDTAUMC4xNjc1CV8F7wA0FdgINzI3DWABGQQzMBEZEDg2MzI5CWEAcgFhFTIUMDE4MzQ5BXoBGRkyFDAzNjA2NAUZBfMZMgwzODgwCTIBGR0yDDQxNzcpVQx2aWV3MSYcOTU4MDI1LjAFSwB2ARkEN2Qlnxg0NDkxODg3ERohKA1lGDguNjkyODIJMgRvchVkGDguMDQyOTA5ERExGDAuMTU5NTAxWgB2FTIUMC4yOTAwDfkMX2NudBGWGDIuNTAwNDktRQUaDZcEMi4hLC2OEHNoYXJlETQQNTA0MjgNrwkZDTMUMTk4MjYxDRoF4xGZGDg4NTc5MDkuGwAxMRwzNTc5OTg3Ng0cCYQROAAyIQIENjMJ6gkbFTgYLjkxNDExNCUeEHJfcmF0FaEYMC4wMDgzNxHVBRsNowEbBDc0TcQUcmV0dXJuEdcMMjAwNhGiDRoNNRA4ODM2Mg2gCfERoBAzNDM3NjG7CRsRoBQxNDk1NTMy1wAANDKTAwg2ODFpMwXYBDRzEW4YMC40NDQ0NUlbEdkENGRFLAG/BDY4NvUAEXEBHAQ5MhEcDdsNOBQxNjI1MzANww0cETgUMTI5MTA5FR1BBAGpDToYMC40NzcxMEnQER0ROxgwLjg2MjA0LZ8Jcyn4Ca8QMTY5NjJRES4gAC1ZFDc1Mjc1MjaXAAlADX4YMTQxMjg0NEoiABGDGDEwMzg0MTERZQU+CGhhcw1tDYgIODAyMYk6IQANiQwzMzU2MQIpx0JEAAw1MDU3cb8+IwANRhAxOTg4MHEaOmkAEdIINjAzNiMDGWkR0xAzMjk4NV5rABFIEDM0MDY1Ua8+kAARShAxNDk3N2l3OH0iLCJ2aWRlb19zb3VyY8EmNOWFqOmdonNwaWRlciJ9",
+                "1AyQeyJmZWF0dXJlIjoie1wicm9zXzFkXCI6XCIwLjgwNjIxM1wiLAkYADcVGAw2NjQwERgAdhkwGDA1MDUwNlwJMAB2GTAUMDUzODUyBRgIc3RyHTAMNjI2NAkwARgVYAF3ADUJGAR2bxUwHDcuNDM2Njg0BUgBGA0wEDcuMjc4DY8Adi6PABAzNzU1ORFHHY8UMzkxOTM5BUcF7wQ0ZBHwEDY1NzQxFRkEMzARGRA5Nzc2NR3xFTIQMDU0NjEJ2ghyb3YdMggwNTgtCgHaHTEMNjMwOAljARkZYxQwNTkzODcFfAx2aWV3MSUgMjQ3MzA3OS4wGRoN9xgxMzMxMDA2FRshKQ2YEDcuNDYwMboBGRFnIUMIMDY4ORQRMhwwLjQwNzQxNAlmMsoAEDQzMTQyCZkQcl9jbnQRmhQzLjg5MTkuGQANmRwzLjg2MzI4OAVMEHNoYXJlETMUMTU0OTI4DcwJGg00FDgyNzMwNA0aKQANzBwyMDA3MzU4NS4cABHPGDM1NjAxNTcRHA2fDTkYMy44ODM3MwnTCRsROBA0LjIwNFFiEHJfcmF0FaMYMC4wMTI5NxHXBRsNpAEbBDM5DVAQZXR1cm4V2AwyNDkwFYYJGw01DDcxNjc1vwnzEaEYMTI2NjI1Ng3ZCRwRogQyMSF1MdwFvgA0MpkDBDM3KWARGw1vFDAuODY0MRUcBb8RcAHACDQwNk2uCRwRcAEcADNNmA3BETcYMTA5NjI1MA2nDR0ROBgyMDY3MDMyER1BIAGoLYMBjggzNTUxTQRjbgEdETsYMC45NzQ0NjJQASn6AGRljww5Mjg4OQIpVQUgDesUNTIxNjczNSMdIRF/FDgxNzgyNU4iABGEGDE1MzU5NDYxhAVfCGhhcw1NDYkQMzIwOTMN4joiAA2LEDE4NTUzEUUpyUJGABQxNTUzOTcNRz4kAAA3CfMUODI5NDM0DSQ6awAR1RAyODIyNhFIOiQAEdcQNDkxNjYRSD5sABFJEDEyNjk0NR4+JgARSxQyMTIxMjAl/jh9IiwidmlkZW9fc291cmPBMVjlhajpnaJzcGlkZXItYXV0b3BpY2sifQ==",
+                "5gyQeyJmZWF0dXJlIjoie1wicm9zXzFkXCI6XCIwLjg5OTU0N1wiLAkYADcVGBQzNzEyM1wJGAB2GTAUMDQzODI2FRgVMBQwNDcxOTEFGAhzdHIuMAAIODcyFRcZLww1NjM3CV8Edm8VLxw3Ljc2MzMxNQUwARgNMBg3Ljg1OTczETAdjxAzNDAyMwmPCHZvdhEwHDAuMzcwOTA4BUgF7wA0EdgUOTAzNTY5FRkEMzARGRA4NTE5MDEJBHZfGTIIMDUxFTEAdh0xEDA0Njk3HfIVYwwwNTcyDUohCxljDDA1NTERrQhpZXcxJSQxOTQxOTEyNi4wBZcFGw35IDE3NzU2ODA2MxEcISwNaRg3LjUwMjQ3CeUBGRFpGDcuNjA3MDc5FhEyGDAuMzg3ODAxXy7NABAzNTczMSlgFHJfY250X00hFDMuNjQ2ODIaAA2bFDMuNTcyNC2sEHNoYXJlETQQOTQ2MTARsgkaDTQcMTAwMDk5ODkRziUFDc8gNDQ1MTkxNzUxLh0AFdMcNjMwODE2NDANHQ2kDTscMy44MjgyNjQlPwkbETkUMy42NTc5TYIQcl9yYXQVpxgwLjAxMjAxKUANGw2oARsEMzIp3BRyZXR1cm4R3BQ4NTEwNjQRhgkbDTUYODM3OTU5Mg0cCfgRohQyNTQ2Nzk6HQARpBg0MjA3MzYwEToFwQA0MqMDCDczMB3dBDRzEXMYMC44MzUzNDLDABFzBcQENTBpfA3fEXIBHAgyODQ24QAtTQQyMyEXADkxhw3kETocMzU4NDI3OTgN5hWsDT0YMC45NjI1N21gQUkENHMVPBgwLjg4MzkzDa4pPEkDAGSFjQA2RbcVWx0hLWIcNjU4NjEzNTcRfR0iEYEcMTcyNjQ3NzBORgARxBgyNzI2NTg5NXEFhAhoYXMNUA2OEDIzMzM3MZQ6IwANkBQyMzQ1NjMRaynyQkgAEDk0ODQ2XiQADUkYMTAwMzEyNlHeOm4AEdwYNjAxMTEwNA3+OiUAEd0UOTc5ODU0Nn4DHZQtqBwyMzkwNjcwNQ1MPrsAEU0YNDA1NDg5OSVxOH0iLCJ2aWRlb19zb3VyY8FMNOWeguebtHNwaWRlciJ9"
+        );
+        testSnappyDecompress(snappyStrs, 1);
+//        testSnappyDecompress(snappyStrs, 10);
+//        testSnappyDecompress(snappyStrs, 100);
+//        testSnappyDecompress(snappyStrs, 1000);
+//        testSnappyDecompress(snappyStrs, 10000);
+
+//        testLz4Decompress(lz4Strs, 1);
+//        testLz4Decompress(lz4Strs, 10);
+//        testLz4Decompress(lz4Strs, 100);
+//        testLz4Decompress(lz4Strs, 1000);
+//        testLz4Decompress(lz4Strs, 10000);
+    }
+
+    private static void testSnappyDecompress(List<String> strs, int num) {
+        try {
+            long startTime = System.currentTimeMillis();
+            for (int i = num; i > 0; i--) {
+                for (String str : strs) {
+                    System.out.println(CompressionUtil.snappyDecompress(str));
+                }
+            }
+            System.out.println(num + " : snappy decompress cost: " + (System.currentTimeMillis() - startTime) + "ms");
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    private static void testLz4Decompress(List<String> strs, int num) {
+        try {
+            long startTime = System.currentTimeMillis();
+            for (int i = num; i > 0; i--) {
+                for (String str : strs) {
+                    CompressionUtil.lz4Decompress(str);
+                }
+            }
+            System.out.println(num + " : lz4 decompress cost: " + (System.currentTimeMillis() - startTime) + "ms");
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+
+}

+ 30 - 0
recommend-feature-produce/src/main/java/com/tzld/piaoquan/recommend/feature/produce/util/DateUtils.java

@@ -0,0 +1,30 @@
+package com.tzld.piaoquan.recommend.feature.produce.util;
+
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+
+/**
+ * @author dyp
+ */
+public final class DateUtils {
+    public static String getCurrentDateStr(String format) {
+        SimpleDateFormat dateFormat = new SimpleDateFormat(format);
+        Date currentDate = new Date();
+        return dateFormat.format(currentDate);
+    }
+
+    public static int getCurrentHour() {
+        Calendar calendar = Calendar.getInstance();
+        return calendar.get(Calendar.HOUR_OF_DAY);
+    }
+
+    public static String getBeforeDaysDateStr(String format, int d) {
+        SimpleDateFormat dateFormat = new SimpleDateFormat(format);
+        Calendar calendar = Calendar.getInstance();
+        calendar.setTime(new Date());
+        calendar.add(Calendar.DAY_OF_MONTH, -d);
+        Date previousDate = calendar.getTime();
+        return dateFormat.format(previousDate);
+    }
+}

+ 39 - 0
recommend-feature-produce/src/main/java/com/tzld/piaoquan/recommend/feature/produce/util/JSONUtils.java

@@ -0,0 +1,39 @@
+package com.tzld.piaoquan.recommend.feature.produce.util;
+
+import com.google.common.reflect.TypeToken;
+import com.google.gson.Gson;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+
+import java.io.Serializable;
+
+@Slf4j
+public class JSONUtils implements Serializable {
+
+    private static final Gson GSON = new Gson();
+
+    public static String toJson(Object obj) {
+        if (obj == null) {
+            return "";
+        }
+        try {
+            return GSON.toJson(obj);
+        } catch (Exception e) {
+            log.error("toJson exception", e);
+            return "";
+        }
+    }
+
+    public static <T> T fromJson(String value, TypeToken<T> typeToken, T defaultValue) {
+        if (StringUtils.isBlank(value)) {
+            return defaultValue;
+        }
+        try {
+            return GSON.fromJson(value, typeToken.getType());
+        } catch (Exception e) {
+            log.error("fromJson error! value=[{}]", value, e);
+        }
+        return defaultValue;
+    }
+
+}

+ 174 - 0
recommend-feature-produce/src/main/python/monitor.py

@@ -0,0 +1,174 @@
+import json5
+import redis
+import requests
+from typing import List, Dict, Any
+
+# Redis连接配置(根据实际情况修改)
+REDIS_CONFIG = {
+    'host': 'r-bp1pi8wyv6lzvgjy5z.redis.rds.aliyuncs.com',
+    'port': 6379,
+    'db': 0,
+    'password': 'Wqsd@2019'
+}
+
+# 飞书机器人Webhook地址(根据实际情况修改)
+FEISHU_WEBHOOK = "https://open.feishu.cn/open-apis/bot/v2/hook/540d4098-367a-4068-9a44-b8109652f07c"
+APOLLO_SERVER = "http://apolloconfig-internal.piaoquantv.com"
+APP_ID = "recommend-feature"
+NAMESPACE = "application"  # 或 "application"
+
+THRESHOLD=3000
+
+
+
+def get_apollo_config(
+    config_server_url: str,
+    app_id: str,
+    namespace_name: str = "application",
+    cluster_name: str = "default",
+    timeout: int = 5
+) -> Dict[str, Any]:
+    """
+    通过带缓存的Http接口从Apollo读取配置
+    
+    :param config_server_url: Apollo配置服务的地址
+    :param app_id: 应用的appId
+    :param namespace_name: Namespace的名字,默认为"application"
+    :param cluster_name: 集群名,默认为"default"
+    :param timeout: 请求超时时间(秒),默认为5
+    :return: 解析后的配置字典
+    :raises: ValueError - 当配置解析失败时
+    :raises: requests.RequestException - 当HTTP请求失败时
+    """
+    # 构造请求URL
+    url = f"{config_server_url}/configfiles/json/{app_id}/{cluster_name}/{namespace_name}"
+    
+    try:
+        # 发送HTTP GET请求
+        response = requests.get(url, timeout=timeout)
+        response.raise_for_status()  # 检查HTTP错误
+        
+        # 解析响应内容
+        config_data = response.json()
+        
+        # 处理非properties类型的namespace(返回内容在content字段中)
+        if "content" in config_data and isinstance(config_data["content"], str):
+            try:
+                return json5.loads(config_data["content"])
+            except json5.JSONDecodeError as e:
+                raise ValueError(f"Failed to parse namespace content: {e}")
+        
+        return config_data
+    
+    except json5.JSONDecodeError as e:
+        raise ValueError(f"Invalid JSON response: {e}")
+    except requests.RequestException as e:
+        raise requests.RequestException(f"Failed to get Apollo config: {e}")
+
+def parse_apollo_config(config_str: str) -> List[Dict[str, Any]]:
+    """解析Apollo配置"""
+    try:
+        config = json5.loads(config_str)
+        return config
+    except json5.JSONDecodeError as e:
+        raise ValueError(f"Invalid JSON config: {e}")
+
+def get_table_list(config: List[Dict[str, Any]]) -> List[str]:
+    """从配置中获取table列表"""
+    return [item['odps']['table'] for item in config]
+
+def check_redis_ttl(redis_conn: redis.Redis, table_name: str, threshold: int = 3600) -> bool:
+    """
+    检查Redis key的TTL是否小于阈值
+    :param redis_conn: Redis连接
+    :param threshold: 阈值(秒)
+    :return: 如果TTL小于阈值返回True,否则返回False
+    """
+    # 这里假设我们检查第一个匹配的key的TTL
+    # 实际情况可能需要根据业务逻辑调整
+    key = f"ttl:{table_name}"
+    print(key)
+    
+    ttl = redis_conn.ttl(key)
+    print(ttl)
+    return ttl, ttl < threshold
+
+def send_feishu_alert(message: str, webhook_url: str = FEISHU_WEBHOOK) -> bool:
+    """
+    发送飞书消息报警
+    :param message: 报警消息内容
+    :param webhook_url: 飞书机器人Webhook地址
+    :return: 是否发送成功
+    """
+    headers = {
+        "Content-Type": "application/json"
+    }
+    
+    payload = {
+        "msg_type": "text",
+        "content": {
+            "text": message
+        }
+    }
+    
+    try:
+        response = requests.post(webhook_url, headers=headers, json=payload)
+        response.raise_for_status()
+        return True
+    except requests.RequestException as e:
+        print(f"Failed to send Feishu alert: {e}")
+        return False
+
+def main():
+    # 1. 解析Apollo配置
+    try:
+        # 获取配置
+        all_config = get_apollo_config(
+            config_server_url=APOLLO_SERVER,
+            app_id=APP_ID,
+            namespace_name=NAMESPACE
+        )
+
+        config = json5.loads(all_config["dts.config"])
+        print(config)
+
+    except ValueError as e:
+        print(f"Error parsing config: {e}")
+        return
+    
+    # 2. 获取table列表
+    table_list = get_table_list(config)
+    print(f"Table list: {table_list}")
+    
+    # 3. 初始化Redis连接
+    try:
+        redis_conn = redis.Redis(**REDIS_CONFIG)
+        redis_conn.ping()  # 测试连接
+    except redis.RedisError as e:
+        print(f"Failed to connect to Redis: {e}")
+        return
+    
+    # 4. 遍历配置检查TTL
+    alert_msg=[]
+    for item in config:
+        redis_config = item['redis']
+        prefix = redis_config['prefix']
+        table_name = item['odps']['table']
+        
+        try:
+            ttl,check=check_redis_ttl(redis_conn, table_name, THRESHOLD)
+            if check:
+                alert_msg.append(f"Table: {table_name} Expire: {ttl}")
+        except redis.RedisError as e:
+            print(f"Error checking Redis TTL for table {table_name}: {e}")
+
+        # 5. 发送飞书报警
+    if alert_msg:  # 如果有报警消息才发送
+        alert_msg_str = "\n".join(alert_msg)  # 用换行符连接所有消息
+        if not send_feishu_alert(f"Redis TTL Alert: TTL is less than {THRESHOLD}s!\n{alert_msg_str}"):
+            print("Failed to send Feishu alert")
+    else:
+        print("No Redis TTL alerts found.")
+
+if __name__ == "__main__":
+    main()

+ 1 - 0
recommend-feature-produce/src/main/python/vm.sh

@@ -0,0 +1 @@
+source myenv/bin/activate

+ 8 - 2
recommend-feature-service/pom.xml

@@ -91,7 +91,7 @@
         <dependency>
             <groupId>com.tzld.piaoquan</groupId>
             <artifactId>recommend-feature-client</artifactId>
-            <version>1.1.16</version>
+            <version>1.1.19</version>
         </dependency>
         <dependency>
             <groupId>com.google.protobuf</groupId>
@@ -101,7 +101,7 @@
         <dependency>
             <groupId>org.projectlombok</groupId>
             <artifactId>lombok</artifactId>
-            <version>1.16.8</version>
+            <version>1.18.16</version>
         </dependency>
 
         <dependency>
@@ -124,6 +124,12 @@
             <artifactId>commons-collections4</artifactId>
             <version>4.1</version>
         </dependency>
+        <!-- Snappy compression library -->
+        <dependency>
+            <groupId>org.xerial.snappy</groupId>
+            <artifactId>snappy-java</artifactId>
+            <version>1.1.8.4</version>
+        </dependency>
     </dependencies>
     <build>
         <finalName>recommend-feature-service</finalName>

+ 6 - 0
recommend-feature-service/src/main/java/com/tzld/piaoquan/recommend/feature/Application.java

@@ -1,5 +1,7 @@
 package com.tzld.piaoquan.recommend.feature;
 
+import com.alibaba.fastjson.parser.ParserConfig;
+import com.alibaba.fastjson.serializer.SerializeConfig;
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
 import org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration;
@@ -23,6 +25,10 @@ import org.springframework.context.annotation.EnableAspectJAutoProxy;
 @EnableAspectJAutoProxy
 public class Application {
     public static void main(String[] args) {
+        // 禁用 FastJSON ASM,避免 Metaspace 泄漏
+        ParserConfig.getGlobalInstance().setAsmEnable(false);
+        SerializeConfig.getGlobalInstance().setAsmEnable(false);
+
         SpringApplication.run(Application.class, args);
     }
 }

+ 8 - 8
recommend-feature-service/src/main/java/com/tzld/piaoquan/recommend/feature/common/ThreadPoolFactory.java

@@ -12,26 +12,26 @@ import java.util.concurrent.TimeUnit;
  */
 public final class ThreadPoolFactory {
     private final static ExecutorService DEFAULT = new CommonThreadPoolExecutor(
-            32,
-            32,
+            128,
+            128,
             0L, TimeUnit.SECONDS,
             new LinkedBlockingQueue<>(1000),
             new ThreadFactoryBuilder().setNameFormat("DEFAULT-%d").build(),
             new ThreadPoolExecutor.AbortPolicy());
-    public final static ExecutorService RECALL = new CommonThreadPoolExecutor(
-            128,
-            128,
+    private final static ExecutorService MULTI_GET_FEATURE = new CommonThreadPoolExecutor(
+            256,
+            256,
             0L, TimeUnit.SECONDS,
             new LinkedBlockingQueue<>(1000),
-            new ThreadFactoryBuilder().setNameFormat("RecallService-%d").build(),
+            new ThreadFactoryBuilder().setNameFormat("MultiGetFeaturePool-%d").build(),
             new ThreadPoolExecutor.AbortPolicy());
 
     public static ExecutorService defaultPool() {
         return DEFAULT;
     }
 
-    public static ExecutorService recallPool() {
-        return RECALL;
+    public static ExecutorService multiGetFeaturePool() {
+        return MULTI_GET_FEATURE;
     }
 
 }

+ 10 - 1
recommend-feature-service/src/main/java/com/tzld/piaoquan/recommend/feature/config/RedisTemplateConfig.java

@@ -2,6 +2,7 @@ package com.tzld.piaoquan.recommend.feature.config;
 
 import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
 import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.boot.context.properties.ConfigurationProperties;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
@@ -14,9 +15,14 @@ import org.springframework.data.redis.connection.lettuce.LettucePoolingClientCon
 import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.data.redis.serializer.StringRedisSerializer;
 
+import java.time.Duration;
+
 @Configuration
 public class RedisTemplateConfig {
 
+    @Value("${spring.redis.timeout:1000}")
+    private long redisTimeout;
+
     @Bean("redisPool")
     @ConfigurationProperties(prefix = "spring.redis.lettuce.pool")
     public GenericObjectPoolConfig<LettucePoolingClientConfiguration> redisPool() {
@@ -34,7 +40,10 @@ public class RedisTemplateConfig {
     public LettuceConnectionFactory factory(GenericObjectPoolConfig<LettucePoolingClientConfiguration> redisPool,
                                             RedisStandaloneConfiguration redisConfig) {
         LettuceClientConfiguration lettuceClientConfiguration =
-                LettucePoolingClientConfiguration.builder().poolConfig(redisPool).build();
+                LettucePoolingClientConfiguration.builder()
+                        .poolConfig(redisPool)
+                        .commandTimeout(Duration.ofMillis(redisTimeout))
+                        .build();
         return new LettuceConnectionFactory(redisConfig, lettuceClientConfiguration);
     }
 

+ 10 - 1
recommend-feature-service/src/main/java/com/tzld/piaoquan/recommend/feature/config/TairTemplateConfig.java

@@ -2,6 +2,7 @@ package com.tzld.piaoquan.recommend.feature.config;
 
 import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
 import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.boot.context.properties.ConfigurationProperties;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
@@ -14,9 +15,14 @@ import org.springframework.data.redis.connection.lettuce.LettucePoolingClientCon
 import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.data.redis.serializer.StringRedisSerializer;
 
+import java.time.Duration;
+
 @Configuration
 public class TairTemplateConfig {
 
+    @Value("${spring.tair.timeout:1000}")
+    private long tairTimeout;
+
     @Bean("tairPool")
     @ConfigurationProperties(prefix = "spring.tair.lettuce.pool")
     public GenericObjectPoolConfig<LettucePoolingClientConfiguration> tairPool() {
@@ -34,7 +40,10 @@ public class TairTemplateConfig {
     public LettuceConnectionFactory factory(@Qualifier("tairPool") GenericObjectPoolConfig<LettucePoolingClientConfiguration> tairPool,
                                             @Qualifier("tairConfig") RedisStandaloneConfiguration tairConfig) {
         LettuceClientConfiguration lettuceClientConfiguration =
-                LettucePoolingClientConfiguration.builder().poolConfig(tairPool).build();
+                LettucePoolingClientConfiguration.builder()
+                        .poolConfig(tairPool)
+                        .commandTimeout(Duration.ofMillis(tairTimeout))
+                        .build();
         return new LettuceConnectionFactory(tairConfig, lettuceClientConfiguration);
     }
 

+ 31 - 0
recommend-feature-service/src/main/java/com/tzld/piaoquan/recommend/feature/grpcservice/FeatureV2GrpcService.java

@@ -0,0 +1,31 @@
+package com.tzld.piaoquan.recommend.feature.grpcservice;
+
+import com.tzld.piaoquan.recommend.feature.client.ProtobufUtils;
+import com.tzld.piaoquan.recommend.feature.model.feature.FeatureV2ServiceGrpc;
+import com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureRequest;
+import com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureResponse;
+import com.tzld.piaoquan.recommend.feature.service.FeatureV2Service;
+import io.grpc.stub.StreamObserver;
+import lombok.extern.slf4j.Slf4j;
+import net.devh.boot.grpc.server.service.GrpcService;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * @author dyp
+ */
+@GrpcService
+@Slf4j
+public class FeatureV2GrpcService extends FeatureV2ServiceGrpc.FeatureV2ServiceImplBase {
+
+    @Autowired
+    private FeatureV2Service featureV2Service;
+
+    @Override
+    public void multiGetFeature(MultiGetFeatureRequest request,
+                                StreamObserver<MultiGetFeatureResponse> responseObserver) {
+        MultiGetFeatureResponse response = featureV2Service.multiGetFeature(request);
+        responseObserver.onNext(response);
+        responseObserver.onCompleted();
+    }
+
+}

+ 5 - 4
recommend-feature-service/src/main/java/com/tzld/piaoquan/recommend/feature/service/AdItemFeatureService.java

@@ -31,10 +31,11 @@ public class AdItemFeatureService  extends AbstractFeatureService<String, AdItem
             return feature;
         };
 
-        int maximumSize = 10000;
-        int refreshAfterWrite = 60;
-        int expireAfterWrite = 60;
-        int expireAfterAccess = 60;
+        // 优化缓存配置:减小大小和过期时间,降低内存占用
+        int maximumSize = 5000;
+        int refreshAfterWrite = 30;
+        int expireAfterWrite = 30;
+        int expireAfterAccess = 30;
         initLocalCache(maximumSize, refreshAfterWrite, expireAfterWrite, expireAfterAccess);
     }
 

+ 43 - 0
recommend-feature-service/src/main/java/com/tzld/piaoquan/recommend/feature/service/DTSConfig.java

@@ -0,0 +1,43 @@
+package com.tzld.piaoquan.recommend.feature.service;
+
+import lombok.Data;
+import lombok.extern.slf4j.Slf4j;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * @author dyp
+ */
+@Data
+@Slf4j
+public class DTSConfig implements Serializable {
+    private ODPS odps;
+    private Redis redis;
+
+    @Data
+    public static class ODPS implements Serializable {
+        private String table;
+        private List<String> cols;
+        private List<String> partition;
+    }
+
+    @Data
+    public static class Redis implements Serializable {
+        private String prefix;
+        private List<String> key;
+        private long expire;
+    }
+
+    public boolean selfCheck() {
+        if (odps == null) {
+            log.error("odps not config");
+            return false;
+        }
+        if (redis == null) {
+            log.error("redis not config");
+            return false;
+        }
+        return true;
+    }
+}

+ 177 - 0
recommend-feature-service/src/main/java/com/tzld/piaoquan/recommend/feature/service/FeatureV2Service.java

@@ -0,0 +1,177 @@
+package com.tzld.piaoquan.recommend.feature.service;
+
+import com.ctrip.framework.apollo.spring.annotation.ApolloJsonValue;
+import com.google.common.base.Strings;
+import com.tzld.piaoquan.recommend.feature.common.ThreadPoolFactory;
+import com.tzld.piaoquan.recommend.feature.model.common.Result;
+import com.tzld.piaoquan.recommend.feature.model.feature.FeatureKeyProto;
+import com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureRequest;
+import com.tzld.piaoquan.recommend.feature.model.feature.MultiGetFeatureResponse;
+import com.tzld.piaoquan.recommend.feature.util.CompressionUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+import java.util.concurrent.CompletableFuture;
+import java.util.stream.Collectors;
+
+/**
+ * @author dyp
+ */
+@Service
+@Slf4j
+public class FeatureV2Service {
+
+    private static final int BATCH_SIZE = 300;
+
+    @Qualifier("redisTemplate")
+    @Autowired
+    private RedisTemplate<String, String> redisTemplate;
+
+    @ApolloJsonValue("${dts.config:}")
+    private List<DTSConfig> newDtsConfigs;
+
+    public MultiGetFeatureResponse multiGetFeature(MultiGetFeatureRequest request) {
+        int keyCount = request.getFeatureKeyCount();
+
+        if (keyCount == 0) {
+            return MultiGetFeatureResponse.newBuilder()
+                    .setResult(Result.newBuilder().setCode(1))
+                    .build();
+        }
+
+        List<FeatureKeyProto> featureKeys = request.getFeatureKeyList();
+
+        // 1. 生成 Redis Key 列表
+        List<String> redisKeys = featureKeys.stream()
+                .map(this::redisKey)
+                .collect(Collectors.toList());
+
+        // 2. 分批并行查询 Redis
+        List<String> values = batchMultiGet(redisKeys);
+
+        // 3. 并行解压缩
+        values = batchDecompress(values);
+
+        // 4. 构建响应
+        MultiGetFeatureResponse.Builder builder = MultiGetFeatureResponse.newBuilder();
+        builder.setResult(Result.newBuilder().setCode(1));
+        for (int i = 0; i < keyCount; i++) {
+            builder.putFeature(featureKeys.get(i).getUniqueKey(),
+                    Strings.nullToEmpty(values.get(i)));
+        }
+        return builder.build();
+    }
+
+    /**
+     * 分批并行查询 Redis
+     */
+    private List<String> batchMultiGet(List<String> redisKeys) {
+        if (CollectionUtils.isEmpty(redisKeys)) {
+            return Collections.emptyList();
+        }
+
+        // 如果数量小于批次大小,直接查询
+        if (redisKeys.size() <= BATCH_SIZE) {
+            List<String> result = redisTemplate.opsForValue().multiGet(redisKeys);
+            return result != null ? result : Collections.emptyList();
+        }
+
+        // 分批
+        List<List<String>> batches = partition(redisKeys, BATCH_SIZE);
+
+        // 并行查询
+        List<CompletableFuture<List<String>>> futures = batches.stream()
+                .map(batch -> CompletableFuture.supplyAsync(() -> {
+                    List<String> result = redisTemplate.opsForValue().multiGet(batch);
+                    return result != null ? result : new ArrayList<String>();
+                }, ThreadPoolFactory.multiGetFeaturePool()))
+                .collect(Collectors.toList());
+
+        // 合并结果(保持顺序)
+        return futures.stream()
+                .map(CompletableFuture::join)
+                .flatMap(List::stream)
+                .collect(Collectors.toList());
+    }
+
+    /**
+     * 并行解压缩
+     */
+    private List<String> batchDecompress(List<String> values) {
+        if (CollectionUtils.isEmpty(values)) {
+            return Collections.emptyList();
+        }
+
+        // 如果数量小于批次大小,直接解压
+        if (values.size() <= BATCH_SIZE) {
+            return values.stream()
+                    .map(CompressionUtil::snappyDecompress)
+                    .collect(Collectors.toList());
+        }
+
+        // 分批并行解压
+        List<List<String>> batches = partition(values, BATCH_SIZE);
+
+        List<CompletableFuture<List<String>>> futures = batches.stream()
+                .map(batch -> CompletableFuture.supplyAsync(() ->
+                        batch.stream()
+                                .map(CompressionUtil::snappyDecompress)
+                                .collect(Collectors.toList()),
+                        ThreadPoolFactory.multiGetFeaturePool()))
+                .collect(Collectors.toList());
+
+        return futures.stream()
+                .map(CompletableFuture::join)
+                .flatMap(List::stream)
+                .collect(Collectors.toList());
+    }
+
+    /**
+     * 将列表分批
+     */
+    private <T> List<List<T>> partition(List<T> list, int size) {
+        List<List<T>> partitions = new ArrayList<>();
+        for (int i = 0; i < list.size(); i += size) {
+            partitions.add(list.subList(i, Math.min(i + size, list.size())));
+        }
+        return partitions;
+    }
+
+    // Note:写入和读取的key生成规则应保持一致
+    private String redisKey(FeatureKeyProto fk) {
+
+        Optional<DTSConfig> optional = newDtsConfigs.stream()
+                .filter(c -> c.getOdps() != null && StringUtils.equals(c.getOdps().getTable(), fk.getTableName()))
+                .findFirst();
+        if (!optional.isPresent()) {
+            log.error("table {} not config", fk.getTableName());
+            return "";
+        }
+        DTSConfig config = optional.get();
+
+        // Note:写入和读取的key生成规则应保持一致
+        List<String> fields = config.getRedis().getKey();
+        if (CollectionUtils.isEmpty(fields)) {
+            return config.getRedis().getPrefix();
+        }
+        StringBuilder sb = new StringBuilder();
+        if (StringUtils.isNotBlank(config.getRedis().getPrefix())) {
+            sb.append(config.getRedis().getPrefix());
+        }
+        for (String field : fields) {
+            sb.append(":");
+            sb.append(fk.getFieldValueMap().get(field));
+        }
+        return sb.toString();
+    }
+
+}

+ 5 - 4
recommend-feature-service/src/main/java/com/tzld/piaoquan/recommend/feature/service/UserAdFeatureService.java

@@ -27,10 +27,11 @@ public class UserAdFeatureService extends AbstractFeatureService<String, UserAdF
             return feature;
         };
 
-        int maximumSize = 10000;
-        int refreshAfterWrite = 60;
-        int expireAfterWrite = 60;
-        int expireAfterAccess = 60;
+        // 优化缓存配置:减小大小和过期时间,降低内存占用
+        int maximumSize = 5000;
+        int refreshAfterWrite = 30;
+        int expireAfterWrite = 30;
+        int expireAfterAccess = 30;
         initLocalCache(maximumSize, refreshAfterWrite, expireAfterWrite, expireAfterAccess);
     }
 

+ 8 - 2
recommend-feature-service/src/main/java/com/tzld/piaoquan/recommend/feature/service/UserAndVideoFeatureService.java

@@ -22,8 +22,14 @@ import java.util.stream.Collectors;
 @Service
 public class UserAndVideoFeatureService {
 
-    ExecutorService executorService = new ThreadPoolExecutor(16, 16, 0L,
-            TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>());
+    /**
+     * 线程池:使用有界队列防止任务无限堆积
+     */
+    ExecutorService executorService = new ThreadPoolExecutor(
+            16, 16, 0L, TimeUnit.MILLISECONDS,
+            new LinkedBlockingQueue<>(500),           // 有界队列,容量 500
+            new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略:调用者执行
+    );
 
     private final static Logger log = LoggerFactory.getLogger(UserAndVideoFeatureService.class);
     private final static String USER_FEATURE_KEY = "recommend.feature.user.recsys.info.{mid}";

+ 5 - 4
recommend-feature-service/src/main/java/com/tzld/piaoquan/recommend/feature/service/UserFeatureService.java

@@ -25,10 +25,11 @@ public class UserFeatureService extends AbstractFeatureService<String, UserFeatu
         };
         super.defaultValueFunc = k -> UserFeature.defaultInstance(k);
 
-        int maximumSize = 10000;
-        int refreshAfterWrite = 60;
-        int expireAfterWrite = 60;
-        int expireAfterAccess = 60;
+        // 优化缓存配置:减小大小和过期时间,降低内存占用
+        int maximumSize = 5000;
+        int refreshAfterWrite = 30;
+        int expireAfterWrite = 30;
+        int expireAfterAccess = 30;
         initLocalCache(maximumSize, refreshAfterWrite, expireAfterWrite, expireAfterAccess);
     }
 

+ 5 - 4
recommend-feature-service/src/main/java/com/tzld/piaoquan/recommend/feature/service/VideoFeatureService.java

@@ -32,10 +32,11 @@ public class VideoFeatureService extends AbstractFeatureService<String, ItemFeat
         };
         super.defaultValueFunc = k -> ItemFeature.defaultInstance(k);
 
-        int maximumSize = 10000;
-        int refreshAfterWrite = 60;
-        int expireAfterWrite = 60;
-        int expireAfterAccess = 60;
+        // 优化缓存配置:减小大小和过期时间,降低内存占用
+        int maximumSize = 5000;
+        int refreshAfterWrite = 30;
+        int expireAfterWrite = 30;
+        int expireAfterAccess = 30;
 
         initLocalCache(maximumSize, refreshAfterWrite, expireAfterWrite, expireAfterAccess);
     }

+ 86 - 0
recommend-feature-service/src/main/java/com/tzld/piaoquan/recommend/feature/util/CompressionUtil.java

@@ -0,0 +1,86 @@
+package com.tzld.piaoquan.recommend.feature.util;
+
+import lombok.extern.slf4j.Slf4j;
+import net.jpountz.lz4.LZ4Compressor;
+import net.jpountz.lz4.LZ4Factory;
+import net.jpountz.lz4.LZ4SafeDecompressor;
+import org.apache.commons.lang3.StringUtils;
+import org.xerial.snappy.Snappy;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+
+
+/**
+ * @author dyp
+ */
+@Slf4j
+public class CompressionUtil {
+    public static String lz4Compress(String input) {
+        byte[] data = input.getBytes(StandardCharsets.UTF_8);
+        LZ4Factory factory = LZ4Factory.fastestInstance();
+        LZ4Compressor compressor = factory.fastCompressor();
+        int maxCompressedLength = compressor.maxCompressedLength(data.length);
+        byte[] compressed = new byte[maxCompressedLength];
+        int compressedLength = compressor.compress(data, 0, data.length, compressed, 0, maxCompressedLength);
+        byte[] result = new byte[compressedLength];
+        System.arraycopy(compressed, 0, result, 0, compressedLength);
+        return Base64.getEncoder().encodeToString(result);
+    }
+
+    public static String lz4Decompress(String input) {
+        // 将Base64编码的字符串解码为字节数组
+        byte[] data = Base64.getDecoder().decode(input);
+        LZ4Factory factory = LZ4Factory.fastestInstance();
+        LZ4SafeDecompressor decompressor = factory.safeDecompressor();
+
+        // 创建一个缓冲区来存储解压缩后的数据
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+
+        // 使用ByteBuffer来处理压缩数据
+        ByteBuffer buffer = ByteBuffer.wrap(data);
+
+        // 假设每次读取的块大小为4KB(可以根据实际情况调整)
+        byte[] chunk = new byte[4096];
+        byte[] decompressedChunk = new byte[4096 * 2]; // 解压缩后的块可能会更大
+
+        while (buffer.hasRemaining()) {
+            int remaining = Math.min(buffer.remaining(), chunk.length);
+            buffer.get(chunk, 0, remaining);
+
+            // 解压缩当前块
+            int decompressedLength = decompressor.decompress(chunk, 0, remaining, decompressedChunk, 0);
+
+            // 将解压缩后的数据写入输出流
+            outputStream.write(decompressedChunk, 0, decompressedLength);
+        }
+
+        // 返回解压后的数据
+        return new String(outputStream.toByteArray(), StandardCharsets.UTF_8);
+    }
+
+    public static String snappyCompress(String input) throws IOException {
+        byte[] inputBytes = input.getBytes(StandardCharsets.UTF_8);
+        byte[] compressedBytes = Snappy.compress(inputBytes);
+        return Base64.getEncoder().encodeToString(compressedBytes);
+    }
+
+    // 将Snappy压缩后的String解压缩回String
+    public static String snappyDecompress(String compressedInput) {
+        if (StringUtils.isBlank(compressedInput)) {
+            return "";
+        }
+        try {
+            byte[] compressedBytes = Base64.getDecoder().decode(compressedInput);
+            byte[] decompressedBytes = Snappy.uncompress(compressedBytes);
+            return new String(decompressedBytes, StandardCharsets.UTF_8);
+        } catch (Exception e) {
+            log.error("snappyDecompress error compressedInput {}", compressedInput, e);
+            return "";
+        }
+    }
+
+}

+ 7 - 5
recommend-feature-service/src/main/java/com/tzld/piaoquan/recommend/feature/util/JSONUtils.java

@@ -1,6 +1,5 @@
 package com.tzld.piaoquan.recommend.feature.util;
 
-import com.alibaba.fastjson.JSONObject;
 import com.google.common.reflect.TypeToken;
 import com.google.gson.Gson;
 import lombok.extern.slf4j.Slf4j;
@@ -9,13 +8,17 @@ import org.apache.commons.lang3.StringUtils;
 @Slf4j
 public class JSONUtils {
 
+    /**
+     * Gson 单例,避免每次调用创建新对象
+     */
+    private static final Gson GSON = new Gson();
 
     public static String toJson(Object obj) {
         if (obj == null) {
             return "";
         }
         try {
-            return new Gson().toJson(obj);
+            return GSON.toJson(obj);
         } catch (Exception e) {
             log.error("toJson exception", e);
             return "";
@@ -23,14 +26,13 @@ public class JSONUtils {
     }
 
     public static <T> T fromJson(String value, TypeToken<T> typeToken, T defaultValue) {
-
         if (StringUtils.isBlank(value)) {
             return defaultValue;
         }
         try {
-            return JSONObject.parseObject(value, typeToken.getType());
+            return GSON.fromJson(value, typeToken.getType());
         } catch (Exception e) {
-            log.error("parseObject error! value=[{}]", value, e);
+            log.error("fromJson error! value=[{}]", value, e);
         }
         return defaultValue;
     }

+ 2 - 2
recommend-feature-service/src/main/resources/application-test.yml

@@ -14,7 +14,7 @@ eureka:
 
 spring:
   redis:
-    hostName: r-bp1pi8wyv6lzvgjy5z.redis.rds.aliyuncs.com
+    hostName: r-bp1wwqqkjqwwkxgbup.redis.rds.aliyuncs.com
     port: 6379
     password: Wqsd@2019
     timeout: 1000
@@ -25,7 +25,7 @@ spring:
         max-idle: 8
         min-idle: 0
   tair:
-    hostName: r-bp1pi8wyv6lzvgjy5z.redis.rds.aliyuncs.com
+    hostName: r-bp1wwqqkjqwwkxgbup.redis.rds.aliyuncs.com
     port: 6379
     password: Wqsd@2019
     timeout: 1000

+ 0 - 4
recommend-feature-service/src/main/resources/logback-spring.xml

@@ -316,10 +316,6 @@
 
     <root level="info">
         <appender-ref ref="CONSOLE"/>
-        <appender-ref ref="DEBUG_FILE"/>
-        <appender-ref ref="INFO_FILE"/>
-        <appender-ref ref="WARN_FILE"/>
-        <appender-ref ref="ERROR_FILE"/>
         <appender-ref ref="loghubAppenderInfo"/>
         <appender-ref ref="loghubAppenderWarn"/>
         <appender-ref ref="loghubAppenderError"/>