From 78aa00cb4135cc8609ba7ea2449b5080096a1b28 Mon Sep 17 00:00:00 2001 From: FFananan Date: Fri, 5 Feb 2021 16:51:26 +0800 Subject: [PATCH 1/3] get through the data stream --- datacube/datacube-common/pom.xml | 14 ++-- .../record/FlattenedRecordClassFactory.java | 21 ++++++ .../flatten/parser/FlattenParserFactory.java | 5 +- .../FlattenedConversionTestLogParser.java | 17 +++++ .../record/FlattenedConversionTestLog.java | 28 +++++++ .../flatten/record/PublicTestClass.java | 20 +++++ .../common/flatten/record/TestClass.java | 7 ++ .../FlattenedConversionTestLogTest.java | 16 ++++ .../datacube/flatten/LogFlattenJob.java | 47 +++++++----- .../flatten/flatMapper/LogFlatMapper.java | 4 +- .../flatten/policy/CustomRollingPolicy.java | 20 +++++ .../resources/conv-flattened-streaming.conf | 75 ++++++++++++++----- datacube/datacube-proto/pom.xml | 2 +- 13 files changed, 230 insertions(+), 46 deletions(-) create mode 100644 datacube/datacube-common/src/main/java/com/attribution/datacube/common/factories/record/FlattenedRecordClassFactory.java create mode 100644 datacube/datacube-common/src/main/java/com/attribution/datacube/common/flatten/parser/FlattenedConversionTestLogParser.java create mode 100644 datacube/datacube-common/src/main/java/com/attribution/datacube/common/flatten/record/FlattenedConversionTestLog.java create mode 100644 datacube/datacube-common/src/main/java/com/attribution/datacube/common/flatten/record/PublicTestClass.java create mode 100644 datacube/datacube-common/src/main/java/com/attribution/datacube/common/flatten/record/TestClass.java create mode 100644 datacube/datacube-common/src/test/java/com/attribution/datacube/common/flatten/record/FlattenedConversionTestLogTest.java create mode 100644 datacube/datacube-flatten/src/main/java/com/attribution/datacube/flatten/policy/CustomRollingPolicy.java diff --git a/datacube/datacube-common/pom.xml b/datacube/datacube-common/pom.xml index 775add8..fecf9ef 100644 --- a/datacube/datacube-common/pom.xml +++ b/datacube/datacube-common/pom.xml @@ -14,7 +14,7 @@ 8 8 - 1.11.2 + 1.12.0 3.6.0 @@ -41,7 +41,7 @@ org.apache.flink - flink-connector-kafka_2.12 + flink-connector-kafka_2.11 ${flink.version} @@ -74,7 +74,7 @@ org.apache.avro avro - 1.8.2 + 1.9.2 com.fasterxml.jackson.core @@ -101,13 +101,13 @@ org.apache.flink - flink-parquet - 1.7.1 + flink-parquet_2.11 + ${flink.version} org.apache.parquet parquet-avro - 1.10.1 + 1.11.1 avro @@ -118,7 +118,7 @@ org.apache.flink flink-statebackend-rocksdb_2.11 - 1.7.0 + ${flink.version} org.apache.hadoop diff --git a/datacube/datacube-common/src/main/java/com/attribution/datacube/common/factories/record/FlattenedRecordClassFactory.java b/datacube/datacube-common/src/main/java/com/attribution/datacube/common/factories/record/FlattenedRecordClassFactory.java new file mode 100644 index 0000000..8456bf7 --- /dev/null +++ b/datacube/datacube-common/src/main/java/com/attribution/datacube/common/factories/record/FlattenedRecordClassFactory.java @@ -0,0 +1,21 @@ +package com.attribution.datacube.common.factories.record; + +import com.attribution.datacube.common.flatten.record.FlattenedClickLog; +import com.attribution.datacube.common.flatten.record.FlattenedConversionLog; +import com.attribution.datacube.common.flatten.record.FlattenedRecord; + +public class FlattenedRecordClassFactory { + public static Class getLogClass(String type) throws Exception { + switch (type) { + case "conversion": { + return FlattenedConversionLog.class; + } + case "click": { + return FlattenedClickLog.class; + } + default: { + throw new Exception("no such type"); + } + } + } +} diff --git a/datacube/datacube-common/src/main/java/com/attribution/datacube/common/flatten/parser/FlattenParserFactory.java b/datacube/datacube-common/src/main/java/com/attribution/datacube/common/flatten/parser/FlattenParserFactory.java index 29ab2dc..5779a9f 100644 --- a/datacube/datacube-common/src/main/java/com/attribution/datacube/common/flatten/parser/FlattenParserFactory.java +++ b/datacube/datacube-common/src/main/java/com/attribution/datacube/common/flatten/parser/FlattenParserFactory.java @@ -8,9 +8,12 @@ public static FlattenParser getFlattenedParser(String type) throws Exception { case "click": { return new FlattenedClickLogParser(); } - case "conversion" : { + case "conversion": { return new FlattenedConversionLogParser(); } + case "conversion_test": { + return new FlattenedConversionTestLogParser(); + } default: { throw new Exception("no such parser type"); } diff --git a/datacube/datacube-common/src/main/java/com/attribution/datacube/common/flatten/parser/FlattenedConversionTestLogParser.java b/datacube/datacube-common/src/main/java/com/attribution/datacube/common/flatten/parser/FlattenedConversionTestLogParser.java new file mode 100644 index 0000000..99e27e0 --- /dev/null +++ b/datacube/datacube-common/src/main/java/com/attribution/datacube/common/flatten/parser/FlattenedConversionTestLogParser.java @@ -0,0 +1,17 @@ +package com.attribution.datacube.common.flatten.parser; + +import com.attribution.datacube.common.flatten.record.FlattenedConversionTestLog; +import com.attribution.datacube.common.flatten.record.FlattenedRecord; +import com.google.protobuf.Message; +import com.tencent.attribution.proto.conv.Conv; + +public class FlattenedConversionTestLogParser extends FlattenParser{ + @Override + public FlattenedRecord parse(Message message) { + Conv.ConversionLog conversionLog = (Conv.ConversionLog) message; + return FlattenedConversionTestLog.builder() + .appId(conversionLog.getAppId()) + .convId(conversionLog.getConvId()) + .build(); + } +} diff --git a/datacube/datacube-common/src/main/java/com/attribution/datacube/common/flatten/record/FlattenedConversionTestLog.java b/datacube/datacube-common/src/main/java/com/attribution/datacube/common/flatten/record/FlattenedConversionTestLog.java new file mode 100644 index 0000000..0ef4ba3 --- /dev/null +++ b/datacube/datacube-common/src/main/java/com/attribution/datacube/common/flatten/record/FlattenedConversionTestLog.java @@ -0,0 +1,28 @@ +package com.attribution.datacube.common.flatten.record; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import lombok.*; +import org.apache.avro.reflect.Nullable; + +@AllArgsConstructor +@NoArgsConstructor +@Builder +@JsonPOJOBuilder +@Getter +@Setter +@ToString +public class FlattenedConversionTestLog extends FlattenedRecord { + @Nullable + @JsonProperty + public String appId; + + @Nullable + @JsonProperty + public String convId; + + @Override + public long getEventTime() { + return 0; + } +} diff --git a/datacube/datacube-common/src/main/java/com/attribution/datacube/common/flatten/record/PublicTestClass.java b/datacube/datacube-common/src/main/java/com/attribution/datacube/common/flatten/record/PublicTestClass.java new file mode 100644 index 0000000..c0ab305 --- /dev/null +++ b/datacube/datacube-common/src/main/java/com/attribution/datacube/common/flatten/record/PublicTestClass.java @@ -0,0 +1,20 @@ +package com.attribution.datacube.common.flatten.record; + +public class PublicTestClass { + public String appId; + + public String convId; + + @Override + public String toString() { + return "PublicTestClass{" + + "appId='" + appId + '\'' + + ", convId='" + convId + '\'' + + '}'; + } + + public PublicTestClass(String appId, String convId) { + this.appId = appId; + this.convId = convId; + } +} diff --git a/datacube/datacube-common/src/main/java/com/attribution/datacube/common/flatten/record/TestClass.java b/datacube/datacube-common/src/main/java/com/attribution/datacube/common/flatten/record/TestClass.java new file mode 100644 index 0000000..8b881b8 --- /dev/null +++ b/datacube/datacube-common/src/main/java/com/attribution/datacube/common/flatten/record/TestClass.java @@ -0,0 +1,7 @@ +package com.attribution.datacube.common.flatten.record; + +public class TestClass { + public Integer age; + + public String name; +} diff --git a/datacube/datacube-common/src/test/java/com/attribution/datacube/common/flatten/record/FlattenedConversionTestLogTest.java b/datacube/datacube-common/src/test/java/com/attribution/datacube/common/flatten/record/FlattenedConversionTestLogTest.java new file mode 100644 index 0000000..f985bfd --- /dev/null +++ b/datacube/datacube-common/src/test/java/com/attribution/datacube/common/flatten/record/FlattenedConversionTestLogTest.java @@ -0,0 +1,16 @@ +package com.attribution.datacube.common.flatten.record; + +import junit.framework.TestCase; +import org.junit.Test; + +import java.lang.reflect.Field; + +public class FlattenedConversionTestLogTest { + @Test + public void testLog() throws ClassNotFoundException { + Class aClass = Class.forName("com.attribution.datacube.common.flatten.record.TestClass"); + Field[] fields = aClass.getFields(); + System.out.println(fields.length); + } + +} \ No newline at end of file diff --git a/datacube/datacube-flatten/src/main/java/com/attribution/datacube/flatten/LogFlattenJob.java b/datacube/datacube-flatten/src/main/java/com/attribution/datacube/flatten/LogFlattenJob.java index a9d9dcc..21e3060 100644 --- a/datacube/datacube-flatten/src/main/java/com/attribution/datacube/flatten/LogFlattenJob.java +++ b/datacube/datacube-flatten/src/main/java/com/attribution/datacube/flatten/LogFlattenJob.java @@ -1,24 +1,30 @@ package com.attribution.datacube.flatten; import com.attribution.datacube.common.factories.datasource.StreamClientFactory; +import com.attribution.datacube.common.factories.record.FlattenedRecordClassFactory; import com.attribution.datacube.common.flatten.parser.FlattenParserFactory; import com.attribution.datacube.common.flatten.record.FlattenedConversionLog; import com.attribution.datacube.common.flatten.record.FlattenedRecord; import com.attribution.datacube.flatten.flatMapper.LogFlatMapper; +import com.attribution.datacube.flatten.policy.CustomRollingPolicy; import com.google.protobuf.Message; import com.tencent.attribution.proto.conv.Conv; import com.twitter.chill.protobuf.ProtobufSerializer; import com.typesafe.config.Config; import com.typesafe.config.ConfigFactory; -import org.apache.flink.api.common.functions.MapFunction; import org.apache.flink.core.fs.Path; import org.apache.flink.formats.parquet.avro.ParquetAvroWriters; +import org.apache.flink.runtime.state.filesystem.FsStateBackend; import org.apache.flink.streaming.api.CheckpointingMode; import org.apache.flink.streaming.api.datastream.DataStreamSource; import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator; import org.apache.flink.streaming.api.environment.CheckpointConfig; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.api.functions.sink.filesystem.StreamingFileSink; +import org.apache.flink.streaming.api.functions.sink.filesystem.bucketassigners.DateTimeBucketAssigner; +import org.apache.flink.streaming.api.functions.sink.filesystem.rollingpolicies.CheckpointRollingPolicy; +import org.apache.flink.streaming.api.functions.sink.filesystem.rollingpolicies.DefaultRollingPolicy; +import org.apache.flink.streaming.api.functions.sink.filesystem.rollingpolicies.OnCheckpointRollingPolicy; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -26,16 +32,15 @@ public class LogFlattenJob { private static final Logger LOG = LoggerFactory.getLogger(LogFlattenJob.class); public static void main(String[] args) throws Exception { - // todo 这里的逻辑就是消费数据,然后使用一个flatMapper来进行打平 - String env = "test"; - String jobName = "flattened"; + String env = args[0]; + String jobName = args[1]; // todo 这里的config需要增加配置 Config config = ConfigFactory.load("conv-flattened-streaming.conf") .getConfig(env).getConfig(jobName); String logType = config.getString("log-type"); - String path = config.getString("saving-dir"); + String savingPath = config.getString("saving-dir"); Config streamConfig = config.getConfig("stream-config"); @@ -44,16 +49,17 @@ public static void main(String[] args) throws Exception { String checkpointDir = config.getString("checkpoint-dir"); StreamExecutionEnvironment see = StreamExecutionEnvironment.getExecutionEnvironment(); - see.getConfig().registerTypeWithKryoSerializer(Conv.ConversionLog.class, ProtobufSerializer.class); // 设置checkpoint -// see.enableCheckpointing(checkpointInterval); -// CheckpointConfig checkpointConfig = see.getCheckpointConfig(); -// checkpointConfig.enableExternalizedCheckpoints(CheckpointConfig.ExternalizedCheckpointCleanup.RETAIN_ON_CANCELLATION); -// checkpointConfig.setCheckpointingMode(CheckpointingMode.EXACTLY_ONCE); -// checkpointConfig.setMinPauseBetweenCheckpoints(500); -// checkpointConfig.setCheckpointTimeout(60000); -// checkpointConfig.setMaxConcurrentCheckpoints(1); + see.enableCheckpointing(checkpointInterval); + CheckpointConfig checkpointConfig = see.getCheckpointConfig(); + checkpointConfig.setCheckpointingMode(CheckpointingMode.EXACTLY_ONCE); + checkpointConfig.enableExternalizedCheckpoints(CheckpointConfig.ExternalizedCheckpointCleanup.RETAIN_ON_CANCELLATION); + checkpointConfig.setMinPauseBetweenCheckpoints(10000); + checkpointConfig.setCheckpointTimeout(60000); + see.setStateBackend(new FsStateBackend(checkpointDir, true)); + + see.getConfig().registerTypeWithKryoSerializer(Conv.ConversionLog.class, ProtobufSerializer.class); DataStreamSource messageDataStreamSource = see .addSource(StreamClientFactory.getStreamClient(streamConfig).getSourceFunction()); @@ -62,15 +68,18 @@ public static void main(String[] args) throws Exception { // todo 中间的处理逻辑 SingleOutputStreamOperator flattenedResultStream = messageDataStreamSource - .flatMap(new LogFlatMapper(FlattenParserFactory.getFlattenedParser(logType))); - + .flatMap(new LogFlatMapper(FlattenParserFactory.getFlattenedParser(logType))) + .name("flatten map"); LOG.info("flat map done"); - StreamingFileSink sink = StreamingFileSink - .forBulkFormat(new Path(path), ParquetAvroWriters.forReflectRecord(FlattenedRecord.class)) + StreamingFileSink sink = StreamingFileSink + .forBulkFormat(new Path(savingPath), ParquetAvroWriters.forReflectRecord(FlattenedRecordClassFactory.getLogClass(logType))) + // 这里是设置多长时间函缓存一个文件 + .withBucketAssigner(new DateTimeBucketAssigner<>("yyyyMMdd")) .build(); - flattenedResultStream.addSink(sink); - see.execute("test kafka"); + flattenedResultStream.addSink(sink).name("storage sink"); + + see.execute("flatten log test"); } } diff --git a/datacube/datacube-flatten/src/main/java/com/attribution/datacube/flatten/flatMapper/LogFlatMapper.java b/datacube/datacube-flatten/src/main/java/com/attribution/datacube/flatten/flatMapper/LogFlatMapper.java index 8421890..9aee0be 100644 --- a/datacube/datacube-flatten/src/main/java/com/attribution/datacube/flatten/flatMapper/LogFlatMapper.java +++ b/datacube/datacube-flatten/src/main/java/com/attribution/datacube/flatten/flatMapper/LogFlatMapper.java @@ -19,7 +19,9 @@ public LogFlatMapper(FlattenParser flattenParser) { @Override public void flatMap(Message message, Collector collector) { System.out.println("get message"); - collector.collect(flattenParser.parse(message)); + FlattenedRecord flattenedrecord = flattenParser.parse(message); + System.out.println(flattenedrecord); + collector.collect(flattenedrecord); System.out.println("collect message done"); } } diff --git a/datacube/datacube-flatten/src/main/java/com/attribution/datacube/flatten/policy/CustomRollingPolicy.java b/datacube/datacube-flatten/src/main/java/com/attribution/datacube/flatten/policy/CustomRollingPolicy.java new file mode 100644 index 0000000..01a4c88 --- /dev/null +++ b/datacube/datacube-flatten/src/main/java/com/attribution/datacube/flatten/policy/CustomRollingPolicy.java @@ -0,0 +1,20 @@ +package com.attribution.datacube.flatten.policy; + +import com.attribution.datacube.common.flatten.record.FlattenedRecord; +import org.apache.flink.streaming.api.functions.sink.filesystem.PartFileInfo; +import org.apache.flink.streaming.api.functions.sink.filesystem.rollingpolicies.CheckpointRollingPolicy; + +import java.io.IOException; + +public class CustomRollingPolicy extends CheckpointRollingPolicy { + + @Override + public boolean shouldRollOnEvent(PartFileInfo partFileInfo, FlattenedRecord flattenedRecord) throws IOException { + return true; + } + + @Override + public boolean shouldRollOnProcessingTime(PartFileInfo partFileInfo, long l) throws IOException { + return true; + } +} diff --git a/datacube/datacube-flatten/src/main/resources/conv-flattened-streaming.conf b/datacube/datacube-flatten/src/main/resources/conv-flattened-streaming.conf index 0e94f78..12ca1c8 100644 --- a/datacube/datacube-flatten/src/main/resources/conv-flattened-streaming.conf +++ b/datacube/datacube-flatten/src/main/resources/conv-flattened-streaming.conf @@ -1,28 +1,69 @@ test { - flattened { - log-type = "conversion" - saving-dir = "file:////Users/zelfaliu/tmp/attribution-log" - checkpoint-dir = "cosn://attribution-log-test-1257943044/checkpoint-test" - checkpoint-interval = "1000" + flattened_cosn { + log-type: "conversion" + saving-dir: "cosn://attribution-log-test-1257943044/attribution-test/" + checkpoint-dir: "cosn://attribution-log-test-1257943044/checkpoint-test/" + checkpoint-interval: "60000" stream-config { - type = "kafka" - bootstrap-servers = "localhost:9092" - group-id = "test-consumer-group" - topic = "attribution_test" - pb-class-name = "com.tencent.attribution.proto.conv.Conv.ConversionLog" + type: "kafka" + bootstrap-servers: "10.43.32.128:9092" + group-id: "test-consumer-group" + topic: "attribution_test" + pb-class-name: "com.tencent.attribution.proto.conv.Conv.ConversionLog" } s3-config { - type = "tencent" + type: "tencent" } } - string { + flattened_hdfs { + log-type: "conversion" + saving-dir: "cosn://attribution-log-test-1257943044/attribution-test/" + checkpoint-dir: "hdfs://9.134.188.241:9000/checkpoint-test" + checkpoint-interval: "60000" stream-config { - type = "kafka" - bootstrap-servers = "localhost:9810" - group-id = "test-consumer-group" - topic = "string_test" - pb-class-name = "com.attribution.datacube.proto.conv.Conv.ConversionLog" + type: "kafka" + bootstrap-servers: "10.43.32.128:9092" + group-id: "test-consumer-group" + topic: "attribution_test" + pb-class-name: "com.tencent.attribution.proto.conv.Conv.ConversionLog" + } + s3-config { + type: "tencent" + } + } + + flattened_local { + log-type: "conversion" + saving-dir: "file:///Users/zelfaliu/tmp/attribution-test/" + checkpoint-dir: "file:///Users/zelfaliu/tmp/checkpoint-test/" + checkpoint-interval: "10000" + stream-config { + type: "kafka" + bootstrap-servers: "10.43.32.128:9092" + group-id: "test-consumer-group" + topic: "attribution_test" + pb-class-name: "com.tencent.attribution.proto.conv.Conv.ConversionLog" + } + s3-config { + type: "tencent" + } + } + + flattened_mix { + log-type: "conversion" + saving-dir: "cosn://attribution-log-test-1257943044/attribution-test/" + checkpoint-dir: "file:///Users/zelfaliu/tmp/checkpoint-test/" + checkpoint-interval: "10000" + stream-config { + type: "kafka" + bootstrap-servers: "10.43.32.128:9092" + group-id: "test-consumer-group" + topic: "attribution_test" + pb-class-name: "com.tencent.attribution.proto.conv.Conv.ConversionLog" + } + s3-config { + type: "tencent" } } } \ No newline at end of file diff --git a/datacube/datacube-proto/pom.xml b/datacube/datacube-proto/pom.xml index 1fb87f3..6a0df4b 100644 --- a/datacube/datacube-proto/pom.xml +++ b/datacube/datacube-proto/pom.xml @@ -14,7 +14,7 @@ 8 8 - 1.11.2 + 1.12.0 3.6.0 1.18.0 From e5cbc6c612878606fd49538a88906f324cf8ed3f Mon Sep 17 00:00:00 2001 From: zelfaliu Date: Mon, 1 Mar 2021 15:56:09 +0800 Subject: [PATCH 2/3] refactor code --- .../kafka/kafka_produce_message_example.go | 79 +++++++++++++++++++ .../kafka/kafka_attribution_store.go | 70 +++++++++++++--- .../kafka/kafka_attribution_store_test.go | 74 ----------------- .../parser/FlattenedConversionLogParser.java | 5 +- .../flatten/record/FlattenedClickLog.java | 2 +- .../record/FlattenedConversionLog.java | 2 +- datacube/datacube-flatten/pom.xml | 6 ++ .../datacube/flatten/BatchLogFlattenJob.java | 31 ++++++++ ...enJob.java => StreamingLogFlattenJob.java} | 46 ++++++++--- .../flatten/flatMapper/LogFlatMapper.java | 3 - .../flatten/tool/FlattenedMessageSchema.java | 14 ++-- .../resources/conv-flattened-streaming.conf | 33 +++++++- 12 files changed, 249 insertions(+), 116 deletions(-) create mode 100644 attribution/example/kafka/kafka_produce_message_example.go delete mode 100644 attribution/pkg/storage/attribution/kafka/kafka_attribution_store_test.go create mode 100644 datacube/datacube-flatten/src/main/java/com/attribution/datacube/flatten/BatchLogFlattenJob.java rename datacube/datacube-flatten/src/main/java/com/attribution/datacube/flatten/{LogFlattenJob.java => StreamingLogFlattenJob.java} (62%) diff --git a/attribution/example/kafka/kafka_produce_message_example.go b/attribution/example/kafka/kafka_produce_message_example.go new file mode 100644 index 0000000..49fd787 --- /dev/null +++ b/attribution/example/kafka/kafka_produce_message_example.go @@ -0,0 +1,79 @@ +package main + +import ( + "flag" + "fmt" + "github.com/TencentAd/attribution/attribution/pkg/storage/attribution/kafka" + click2 "github.com/TencentAd/attribution/attribution/proto/click" + "strconv" + "time" +) + +func main() { + flag.Parse() + + //conversionProducer := kafka.NewAmsKafkaAttributionStore().(*kafka.AmsKafkaAttributionStore) + clickProducer := kafka.NewAmsKafkaClickStore().(*kafka.AmsKafkaClickStore) + + index := 1 + + start := time.Now().Unix() + + for index <= 10 { + now := time.Now() + clickLog := &click2.ClickLog{ + ClickTime: now.Unix(), + ClickId: strconv.Itoa(index), + AdId: int64(index), + Platform: click2.Platform(index%3 + 1), + } + + if err := clickProducer.Store(clickLog); err != nil { + break + } + //// 转化的时间是点击的时间过10秒 + //conversionLog := &conv.ConversionLog{ + // EventTime: now.Add(10 * time.Second).Unix(), + // AppId: "appId " + strconv.Itoa(index), + // ConvId: "convId" + strconv.Itoa(index), + // Index: int32(index), + // MatchClick: &conv.MatchClick{ + // ClickLog: clickLog, + // }, + //} + //if err := conversionProducer.Store(conversionLog); err != nil { + // fmt.Println(err) + // fmt.Println("send message to kafka failed") + // break + //} + + time.Sleep(2 * time.Second) + + index++ + } + + //for index <= 15 { + // conversionLog := &conv.ConversionLog{ + // EventTime: time.Now().Unix(), + // AppId: "appId " + strconv.Itoa(index), + // ConvId: "convId" + strconv.Itoa(index), + // Index: int32(index), + // MatchClick: &conv.MatchClick{ + // ClickLog: nil, + // }, + // } + // if err := conversionProducer.Store(conversionLog); err != nil { + // fmt.Println(err) + // fmt.Println("send message to kafka failed") + // break + // } + // + // time.Sleep(2 * time.Second) + // + // index++ + //} + + end := time.Now().Unix() + + fmt.Println("cost time: ", end-start) +} diff --git a/attribution/pkg/storage/attribution/kafka/kafka_attribution_store.go b/attribution/pkg/storage/attribution/kafka/kafka_attribution_store.go index e14116b..13026a9 100644 --- a/attribution/pkg/storage/attribution/kafka/kafka_attribution_store.go +++ b/attribution/pkg/storage/attribution/kafka/kafka_attribution_store.go @@ -3,6 +3,7 @@ package kafka import ( "context" "flag" + click2 "github.com/TencentAd/attribution/attribution/proto/click" "github.com/TencentAd/attribution/attribution/proto/conv" "github.com/golang/glog" "github.com/golang/protobuf/proto" @@ -16,25 +17,50 @@ var ( Namespace: "attribution", Subsystem: "", Name: "ams_conv_kafka_store_count", - Help: "amd conv kafka store count", + Help: "ams conv kafka store count", }, []string{"conv_id", "status"}, ) - Address = flag.String("kafka_address", "localhost:9092", "kafka address, split with comma") - AttributionTopic = flag.String("attribution_kafka_topic", "attribution_test", "") - ClickTopic = flag.String("click_kafka_topic", "click_test", "") - ConversionTopic = flag.String("conversion_kafka_topic", "conversion_test", "") + AmsClickKafkaStoreCount = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Namespace: "attribution", + Subsystem: "", + Name: "ams_click_kafka_store_count", + Help: "ams click kafka store count", + }, + []string{"click_id", "status"}) + + Address = flag.String("kafka_address", "9.134.188.241:9093", "kafka address, split with comma") + AttributionTopic = flag.String("attribution_kafka_topic", "conversion_test", "") + ClickTopic = flag.String("click_kafka_topic", "click_test", "") + ConversionTopic = flag.String("conversion_kafka_topic", "conversion_test", "") ) func init() { prometheus.MustRegister(AmsConvKafkaStoreCount) } -type AmsKafkaAttributionStore struct { +type AmsStoreInterface interface { + Store(message interface{}) error +} + +type AmsKafkaStore struct { writer *kafka.Writer } +type AmsKafkaClickStore struct { + store *AmsKafkaStore +} + +type AmsKafkaAttributionStore struct { + store *AmsKafkaStore +} + +type AmsKafkaConversionStore struct { + store *AmsKafkaStore +} + func NewAmsKafkaAttributionStore() interface{} { writer := &kafka.Writer{ Addr: kafka.TCP(*Address), @@ -42,7 +68,7 @@ func NewAmsKafkaAttributionStore() interface{} { Balancer: &kafka.Hash{}, } - return &AmsKafkaAttributionStore{writer: writer} + return &AmsKafkaAttributionStore{store: &AmsKafkaStore{writer: writer}} } func NewAmsKafkaClickStore() interface{} { @@ -52,7 +78,7 @@ func NewAmsKafkaClickStore() interface{} { Balancer: &kafka.Hash{}, } - return &AmsKafkaAttributionStore{writer: writer} + return &AmsKafkaClickStore{store: &AmsKafkaStore{writer: writer}} } func NewAmsKafkaConversionStore() interface{} { @@ -62,7 +88,7 @@ func NewAmsKafkaConversionStore() interface{} { Balancer: &kafka.Hash{}, } - return &AmsKafkaAttributionStore{writer: writer} + return &AmsKafkaConversionStore{store: &AmsKafkaStore{writer: writer}} } func (a *AmsKafkaAttributionStore) Store(message interface{}) error { @@ -79,7 +105,6 @@ func (a *AmsKafkaAttributionStore) Store(message interface{}) error { } AmsConvKafkaStoreCount.WithLabelValues(conversionLog.ConvId, "success").Add(1) return nil - } func (a *AmsKafkaAttributionStore) doStore(conv *conv.ConversionLog) error { @@ -87,7 +112,30 @@ func (a *AmsKafkaAttributionStore) doStore(conv *conv.ConversionLog) error { if err != nil { return err } - err = a.writer.WriteMessages(context.Background(), kafka.Message{Value: value}) + err = a.store.writer.WriteMessages(context.Background(), kafka.Message{Value: value}) + if err != nil { + return err + } + return nil +} + +func (a *AmsKafkaClickStore) Store(message interface{}) error { + clickLog := message.(*click2.ClickLog) + if err := a.doStore(clickLog); err != nil { + AmsClickKafkaStoreCount.WithLabelValues(clickLog.GetClickId(), "fail").Add(1) + glog.Errorf("fail to store click result, err: %s", err) + return err + } + AmsClickKafkaStoreCount.WithLabelValues(clickLog.ClickId, "success").Add(1) + return nil +} + +func (a *AmsKafkaClickStore) doStore(click *click2.ClickLog) error { + value, err := proto.Marshal(click) + if err != nil { + return err + } + err = a.store.writer.WriteMessages(context.Background(), kafka.Message{Value: value}) if err != nil { return err } diff --git a/attribution/pkg/storage/attribution/kafka/kafka_attribution_store_test.go b/attribution/pkg/storage/attribution/kafka/kafka_attribution_store_test.go deleted file mode 100644 index 4c36f47..0000000 --- a/attribution/pkg/storage/attribution/kafka/kafka_attribution_store_test.go +++ /dev/null @@ -1,74 +0,0 @@ -package kafka - -import ( - "context" - "encoding/json" - "flag" - "fmt" - "github.com/TencentAd/attribution/attribution/proto/conv" - "github.com/golang/protobuf/proto" - "github.com/segmentio/kafka-go" - "github.com/stretchr/testify/assert" - "log" - "testing" - "time" -) - -func testNewAmsKafkaAttributionStore(t *testing.T) { - flag.Parse() - - store := NewAmsKafkaAttributionStore().(*AmsKafkaAttributionStore) - - c := &conv.ConversionLog{ - UserData: nil, - EventTime: 0, - AppId: "test appid!!!", - ConvId: "test convid!!!", - CampaignId: 0, - Index: 0, - MatchClick: &conv.MatchClick{ - ClickLog: nil, - MatchIdType: 1, - }, - OriginalContent: "hello world", - } - - assert.NoError(t, store.Store(c)) -} - -func testConsumer(t *testing.T) { - topic := "flink_result_test" - partition := 0 - - conn, err := kafka.DialLeader(context.Background(), "tcp", "localhost:9810", topic, partition) - if err != nil { - log.Fatal("failed to dial leader:", err) - } - - _ = conn.SetReadDeadline(time.Now().Add(10 * time.Second)) - batch := conn.ReadBatch(10e3, 1e6) // fetch 10KB min, 1MB max - - b := make([]byte, 10e3) // 10KB max per message - for { - read, err := batch.Read(b) - if err != nil { - assert.Error(t, err) - break - } - conversionLog := &conv.ConversionLog{} - if err := proto.Unmarshal(b[0:read], conversionLog); err != nil { - fmt.Println(string(b[0:read])) - } else { - value, _ := json.Marshal(conversionLog) - fmt.Println(string(value)) - } - } - - if err := batch.Close(); err != nil { - log.Fatal("failed to close batch:", err) - } - - if err := conn.Close(); err != nil { - log.Fatal("failed to close connection:", err) - } -} diff --git a/datacube/datacube-common/src/main/java/com/attribution/datacube/common/flatten/parser/FlattenedConversionLogParser.java b/datacube/datacube-common/src/main/java/com/attribution/datacube/common/flatten/parser/FlattenedConversionLogParser.java index e044786..a7e4d48 100644 --- a/datacube/datacube-common/src/main/java/com/attribution/datacube/common/flatten/parser/FlattenedConversionLogParser.java +++ b/datacube/datacube-common/src/main/java/com/attribution/datacube/common/flatten/parser/FlattenedConversionLogParser.java @@ -10,11 +10,10 @@ public class FlattenedConversionLogParser extends FlattenParser{ @Override public FlattenedRecord parse(Message message) { - System.out.println("start to parse message"); Conv.ConversionLog conversionLog = (Conv.ConversionLog)message; User.UserData userData = conversionLog.getUserData(); Click.ClickLog clickLog = conversionLog.getMatchClick().getClickLog(); - FlattenedConversionLog log = FlattenedConversionLog.builder() + return FlattenedConversionLog.builder() .eventTime(conversionLog.getEventTime()) .appId(conversionLog.getAppId()) .convId(conversionLog.getConvId()) @@ -62,7 +61,5 @@ public FlattenedRecord parse(Message message) { .billingEvent(clickLog.getBillingEventValue()) .platform(clickLog.getPlatformValue()) .build(); - System.out.println("parse message done"); - return log; } } diff --git a/datacube/datacube-common/src/main/java/com/attribution/datacube/common/flatten/record/FlattenedClickLog.java b/datacube/datacube-common/src/main/java/com/attribution/datacube/common/flatten/record/FlattenedClickLog.java index 7c6780f..5d08323 100644 --- a/datacube/datacube-common/src/main/java/com/attribution/datacube/common/flatten/record/FlattenedClickLog.java +++ b/datacube/datacube-common/src/main/java/com/attribution/datacube/common/flatten/record/FlattenedClickLog.java @@ -185,6 +185,6 @@ public class FlattenedClickLog extends FlattenedRecord{ @Override public long getEventTime() { - return 0; + return clickTime; } } diff --git a/datacube/datacube-common/src/main/java/com/attribution/datacube/common/flatten/record/FlattenedConversionLog.java b/datacube/datacube-common/src/main/java/com/attribution/datacube/common/flatten/record/FlattenedConversionLog.java index 04a1cfb..7c2e8ad 100644 --- a/datacube/datacube-common/src/main/java/com/attribution/datacube/common/flatten/record/FlattenedConversionLog.java +++ b/datacube/datacube-common/src/main/java/com/attribution/datacube/common/flatten/record/FlattenedConversionLog.java @@ -201,6 +201,6 @@ public class FlattenedConversionLog extends FlattenedRecord { @Override public long getEventTime() { - return getProcessTime(); + return eventTime; } } diff --git a/datacube/datacube-flatten/pom.xml b/datacube/datacube-flatten/pom.xml index 2f4b4d8..e33fe73 100644 --- a/datacube/datacube-flatten/pom.xml +++ b/datacube/datacube-flatten/pom.xml @@ -23,6 +23,12 @@ datacube-common 1.0-SNAPSHOT + + + com.fasterxml.jackson.core + jackson-core + 2.12.1 + diff --git a/datacube/datacube-flatten/src/main/java/com/attribution/datacube/flatten/BatchLogFlattenJob.java b/datacube/datacube-flatten/src/main/java/com/attribution/datacube/flatten/BatchLogFlattenJob.java new file mode 100644 index 0000000..2eb5739 --- /dev/null +++ b/datacube/datacube-flatten/src/main/java/com/attribution/datacube/flatten/BatchLogFlattenJob.java @@ -0,0 +1,31 @@ +package com.attribution.datacube.flatten; + +import com.typesafe.config.Config; +import com.typesafe.config.ConfigFactory; +import org.apache.flink.api.java.ExecutionEnvironment; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +public class BatchLogFlattenJob { + private static final Logger LOG = LoggerFactory.getLogger(BatchLogFlattenJob.class); + + public static void main(String[] args) { + String env = args[0]; + String jobName = args[1]; + + Config config = ConfigFactory.load("conv-flattened-streaming.conf") + .getConfig(env).getConfig(jobName); + + String logType = config.getString("log-type"); + String savingPath = config.getString("saving-dir"); + + Config batchConfig = config.getConfig("batch-config"); + + int checkpointInterval = config.getInt("checkpoint-interval"); + String checkpointDir = config.getString("checkpoint-dir"); + + ExecutionEnvironment ee = ExecutionEnvironment.getExecutionEnvironment(); + + } +} diff --git a/datacube/datacube-flatten/src/main/java/com/attribution/datacube/flatten/LogFlattenJob.java b/datacube/datacube-flatten/src/main/java/com/attribution/datacube/flatten/StreamingLogFlattenJob.java similarity index 62% rename from datacube/datacube-flatten/src/main/java/com/attribution/datacube/flatten/LogFlattenJob.java rename to datacube/datacube-flatten/src/main/java/com/attribution/datacube/flatten/StreamingLogFlattenJob.java index 21e3060..0010a30 100644 --- a/datacube/datacube-flatten/src/main/java/com/attribution/datacube/flatten/LogFlattenJob.java +++ b/datacube/datacube-flatten/src/main/java/com/attribution/datacube/flatten/StreamingLogFlattenJob.java @@ -3,15 +3,15 @@ import com.attribution.datacube.common.factories.datasource.StreamClientFactory; import com.attribution.datacube.common.factories.record.FlattenedRecordClassFactory; import com.attribution.datacube.common.flatten.parser.FlattenParserFactory; -import com.attribution.datacube.common.flatten.record.FlattenedConversionLog; import com.attribution.datacube.common.flatten.record.FlattenedRecord; import com.attribution.datacube.flatten.flatMapper.LogFlatMapper; -import com.attribution.datacube.flatten.policy.CustomRollingPolicy; +import com.attribution.datacube.flatten.tool.FlattenedMessageSchema; import com.google.protobuf.Message; import com.tencent.attribution.proto.conv.Conv; import com.twitter.chill.protobuf.ProtobufSerializer; import com.typesafe.config.Config; import com.typesafe.config.ConfigFactory; +import org.apache.flink.api.common.serialization.SimpleStringSchema; import org.apache.flink.core.fs.Path; import org.apache.flink.formats.parquet.avro.ParquetAvroWriters; import org.apache.flink.runtime.state.filesystem.FsStateBackend; @@ -22,14 +22,16 @@ import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.api.functions.sink.filesystem.StreamingFileSink; import org.apache.flink.streaming.api.functions.sink.filesystem.bucketassigners.DateTimeBucketAssigner; -import org.apache.flink.streaming.api.functions.sink.filesystem.rollingpolicies.CheckpointRollingPolicy; -import org.apache.flink.streaming.api.functions.sink.filesystem.rollingpolicies.DefaultRollingPolicy; -import org.apache.flink.streaming.api.functions.sink.filesystem.rollingpolicies.OnCheckpointRollingPolicy; +import org.apache.flink.streaming.connectors.kafka.FlinkKafkaProducer; +import org.apache.flink.streaming.connectors.kafka.internals.KafkaSerializationSchemaWrapper; +import org.apache.flink.streaming.connectors.kafka.partitioner.FlinkFixedPartitioner; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class LogFlattenJob { - private static final Logger LOG = LoggerFactory.getLogger(LogFlattenJob.class); +import java.util.Properties; + +public class StreamingLogFlattenJob { + private static final Logger LOG = LoggerFactory.getLogger(StreamingLogFlattenJob.class); public static void main(String[] args) throws Exception { String env = args[0]; @@ -43,8 +45,8 @@ public static void main(String[] args) throws Exception { String savingPath = config.getString("saving-dir"); Config streamConfig = config.getConfig("stream-config"); + Config streamSinkConfig = config.getConfig("stream-sink-config"); - // todo 这里后面需要加入 checkpoint int checkpointInterval = config.getInt("checkpoint-interval"); String checkpointDir = config.getString("checkpoint-dir"); @@ -73,12 +75,36 @@ public static void main(String[] args) throws Exception { LOG.info("flat map done"); StreamingFileSink sink = StreamingFileSink - .forBulkFormat(new Path(savingPath), ParquetAvroWriters.forReflectRecord(FlattenedRecordClassFactory.getLogClass(logType))) + .forBulkFormat( + new Path(savingPath), + ParquetAvroWriters.forReflectRecord(FlattenedRecordClassFactory.getLogClass(logType))) // 这里是设置多长时间函缓存一个文件 .withBucketAssigner(new DateTimeBucketAssigner<>("yyyyMMdd")) .build(); - flattenedResultStream.addSink(sink).name("storage sink"); + Properties properties = new Properties(); + properties.setProperty("bootstrap.servers", streamSinkConfig.getString("bootstrap-servers")); + // 设置broker的事务最大超时时间为5分钟,小于broker的默认事务超时时间15分钟才能正常工作 + properties.setProperty("transaction.timeout.ms", 1000 * 60 * 5 + ""); + +// FlinkKafkaProducer kafkaProducer = new FlinkKafkaProducer<>( +// streamSinkConfig.getString("topic"), +// new FlattenedMessageSchema(), +// properties); + + FlinkKafkaProducer kafkaProducer = new FlinkKafkaProducer<>( + streamSinkConfig.getString("topic"), // 目标 topic + new KafkaSerializationSchemaWrapper<>( + streamSinkConfig.getString("topic"), + new FlinkFixedPartitioner<>(), + false, + new FlattenedMessageSchema()), // 序列化 schema + properties, // producer 配置 + FlinkKafkaProducer.Semantic.AT_LEAST_ONCE); + + flattenedResultStream.addSink(sink).name("sink to file"); + + flattenedResultStream.addSink(kafkaProducer).name("sink to kafka"); see.execute("flatten log test"); } diff --git a/datacube/datacube-flatten/src/main/java/com/attribution/datacube/flatten/flatMapper/LogFlatMapper.java b/datacube/datacube-flatten/src/main/java/com/attribution/datacube/flatten/flatMapper/LogFlatMapper.java index 9aee0be..aae3d96 100644 --- a/datacube/datacube-flatten/src/main/java/com/attribution/datacube/flatten/flatMapper/LogFlatMapper.java +++ b/datacube/datacube-flatten/src/main/java/com/attribution/datacube/flatten/flatMapper/LogFlatMapper.java @@ -18,10 +18,7 @@ public LogFlatMapper(FlattenParser flattenParser) { @Override public void flatMap(Message message, Collector collector) { - System.out.println("get message"); FlattenedRecord flattenedrecord = flattenParser.parse(message); - System.out.println(flattenedrecord); collector.collect(flattenedrecord); - System.out.println("collect message done"); } } diff --git a/datacube/datacube-flatten/src/main/java/com/attribution/datacube/flatten/tool/FlattenedMessageSchema.java b/datacube/datacube-flatten/src/main/java/com/attribution/datacube/flatten/tool/FlattenedMessageSchema.java index 5bb038b..7103a24 100644 --- a/datacube/datacube-flatten/src/main/java/com/attribution/datacube/flatten/tool/FlattenedMessageSchema.java +++ b/datacube/datacube-flatten/src/main/java/com/attribution/datacube/flatten/tool/FlattenedMessageSchema.java @@ -1,6 +1,8 @@ package com.attribution.datacube.flatten.tool; import com.attribution.datacube.common.flatten.record.FlattenedRecord; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.flink.api.common.serialization.DeserializationSchema; import org.apache.flink.api.common.serialization.SerializationSchema; import org.apache.flink.api.common.typeinfo.TypeInformation; @@ -28,17 +30,13 @@ public boolean isEndOfStream(FlattenedRecord o) { @Override public byte[] serialize(FlattenedRecord o) { - byte[] bytes = null; + ObjectMapper objectMapper = new ObjectMapper(); try { - ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); - ObjectOutputStream outputStream = new ObjectOutputStream(byteArrayOutputStream); - outputStream.writeObject(o); - outputStream.flush(); - bytes = byteArrayOutputStream.toByteArray(); - } catch (IOException e) { + return objectMapper.writeValueAsBytes(o); + } catch (JsonProcessingException e) { e.printStackTrace(); } - return bytes; + return null; } @Override diff --git a/datacube/datacube-flatten/src/main/resources/conv-flattened-streaming.conf b/datacube/datacube-flatten/src/main/resources/conv-flattened-streaming.conf index 12ca1c8..caf388c 100644 --- a/datacube/datacube-flatten/src/main/resources/conv-flattened-streaming.conf +++ b/datacube/datacube-flatten/src/main/resources/conv-flattened-streaming.conf @@ -1,16 +1,41 @@ test { - flattened_cosn { + flattened_conversion { log-type: "conversion" - saving-dir: "cosn://attribution-log-test-1257943044/attribution-test/" + saving-dir: "cosn://attribution-log-test-1257943044/conversion-test/" checkpoint-dir: "cosn://attribution-log-test-1257943044/checkpoint-test/" checkpoint-interval: "60000" stream-config { type: "kafka" - bootstrap-servers: "10.43.32.128:9092" + bootstrap-servers: "9.134.188.241:9093" group-id: "test-consumer-group" - topic: "attribution_test" + topic: "conversion_test" pb-class-name: "com.tencent.attribution.proto.conv.Conv.ConversionLog" } + stream-sink-config { + bootstrap-servers: "9.134.188.241:9092" + topic: "conversion_sink_test" + } + s3-config { + type: "tencent" + } + } + + flattened_click { + log-type: "click" + saving-dir: "cosn://attribution-log-test-1257943044/click-test/" + checkpoint-dir: "cosn://attribution-log-test-1257943044/checkpoint-test/" + checkpoint-interval: "60000" + stream-config { + type: "kafka" + bootstrap-servers: "9.134.188.241:9093" + group-id: "test-consumer-group" + topic: "click_test" + pb-class-name: "com.tencent.attribution.proto.click.Click.ClickLog" + } + stream-sink-config { + bootstrap-servers: "9.134.188.241:9092" + topic: "click_sink_test" + } s3-config { type: "tencent" } From 31c83e90d65ff3d4f6c3dcd9c75b2010213d1b02 Mon Sep 17 00:00:00 2001 From: zelfaliu Date: Mon, 12 Apr 2021 17:09:25 +0800 Subject: [PATCH 3/3] add document --- datacube/README.md | 64 ++++++++ .../flatten/parser/FlattenParserFactory.java | 3 - .../parser/FlattenedClickLogParser.java | 3 + .../parser/FlattenedConversionLogParser.java | 3 + .../FlattenedConversionTestLogParser.java | 17 -- .../record/FlattenedConversionTestLog.java | 28 ---- .../flatten/record/PublicTestClass.java | 20 --- .../common/flatten/record/TestClass.java | 7 - .../FlattenedConversionTestLogTest.java | 16 -- .../parser/RTAPageviewRequestParserTest.java | 15 -- .../flatten/StreamingLogFlattenJob.java | 18 +-- .../resources/conv-flattened-streaming.conf | 20 +++ datacube/datacube-join/pom.xml | 32 ---- .../attribution/datacube/join/JoinJob.java | 71 --------- .../join/process/JoinProcessFunction.java | 148 ------------------ datacube/datacube-service/pom.xml | 27 ---- .../datacube/service/RTAStreamingProcess.java | 34 ---- .../src/main/resources/rta-streaming.conf | 11 -- datacube/imgs/image-20210408113817724.png | Bin 0 -> 118032 bytes datacube/pom.xml | 2 - 20 files changed, 97 insertions(+), 442 deletions(-) create mode 100644 datacube/README.md delete mode 100644 datacube/datacube-common/src/main/java/com/attribution/datacube/common/flatten/parser/FlattenedConversionTestLogParser.java delete mode 100644 datacube/datacube-common/src/main/java/com/attribution/datacube/common/flatten/record/FlattenedConversionTestLog.java delete mode 100644 datacube/datacube-common/src/main/java/com/attribution/datacube/common/flatten/record/PublicTestClass.java delete mode 100644 datacube/datacube-common/src/main/java/com/attribution/datacube/common/flatten/record/TestClass.java delete mode 100644 datacube/datacube-common/src/test/java/com/attribution/datacube/common/flatten/record/FlattenedConversionTestLogTest.java delete mode 100644 datacube/datacube-common/src/test/java/com/attribution/datacube/common/parser/RTAPageviewRequestParserTest.java delete mode 100644 datacube/datacube-join/pom.xml delete mode 100644 datacube/datacube-join/src/main/java/com/attribution/datacube/join/JoinJob.java delete mode 100644 datacube/datacube-join/src/main/java/com/attribution/datacube/join/process/JoinProcessFunction.java delete mode 100644 datacube/datacube-service/pom.xml delete mode 100644 datacube/datacube-service/src/main/java/com/attribution/datacube/service/RTAStreamingProcess.java delete mode 100644 datacube/datacube-service/src/main/resources/rta-streaming.conf create mode 100644 datacube/imgs/image-20210408113817724.png diff --git a/datacube/README.md b/datacube/README.md new file mode 100644 index 0000000..ab6c49f --- /dev/null +++ b/datacube/README.md @@ -0,0 +1,64 @@ +# README + +## 项目的启动 + +### 项目打包 + +进入到项目的根目录,使用maven进行打包 + +```shell +mvn clean install +``` + +### 修改配置文件 + +在`datacube/datacube-flatten/src/main/resources/conv-flattened-streaming.conf`文件中修改相关的配置 + +配置文件中的第一级设置了什么环境,如`test`和`prod` + +```json +prod { + flattened_conversion { + log-type: "conversion" + saving-dir: "cosn://attribution-log-test-1257943044/conversion-test/" + checkpoint-dir: "cosn://attribution-log-test-1257943044/checkpoint-test/" + checkpoint-interval: "60000" + stream-config { + type: "kafka" + bootstrap-servers: "9.134.188.241:9093" + group-id: "test-consumer-group" + topic: "conversion_test" + pb-class-name: "com.tencent.attribution.proto.conv.Conv.ConversionLog" + } + stream-sink-config { + bootstrap-servers: "9.134.188.241:9092" + topic: "conversion_sink_test" + } + } +} +``` + +这里以``prod`环境为例,第二级为`flattened_conversion`,可以根据不同的用途进行命名设置 + +主要修改的位置如下: + +- logtype:需要打平的对象类型, 可以是`click`和`conversion` +- saving-dir:打平之后的日志的存储文件路径 +- checkpoint-dir: checkpoint保存的文件路径 +- stream-config:数据源的相关配置 + - bootstrap-servers:kafka的服务器地址 + - topic:consumer的topic + - pb-class-name:需要消费的数据的所属proto类型 +- stream-sink-config:日志sink的相关配置 + - bootstrap-servers:kafka的服务器地址 + - topic:producer的topic + +### 任务的执行 + +使用flink的可视化界面来部署任务并执行 + +![image-20210408113817724](imgs/image-20210408113817724.png) + +这里上传了可执行jar包之后需要填入执行类的全限定名,还有运行参数,根据刚刚修改的配置文件,填入运行环境和运行的场景,比如`prod flattened_conversion` + +然后提交任务即可运行任务 diff --git a/datacube/datacube-common/src/main/java/com/attribution/datacube/common/flatten/parser/FlattenParserFactory.java b/datacube/datacube-common/src/main/java/com/attribution/datacube/common/flatten/parser/FlattenParserFactory.java index 5779a9f..2c7b574 100644 --- a/datacube/datacube-common/src/main/java/com/attribution/datacube/common/flatten/parser/FlattenParserFactory.java +++ b/datacube/datacube-common/src/main/java/com/attribution/datacube/common/flatten/parser/FlattenParserFactory.java @@ -11,9 +11,6 @@ public static FlattenParser getFlattenedParser(String type) throws Exception { case "conversion": { return new FlattenedConversionLogParser(); } - case "conversion_test": { - return new FlattenedConversionTestLogParser(); - } default: { throw new Exception("no such parser type"); } diff --git a/datacube/datacube-common/src/main/java/com/attribution/datacube/common/flatten/parser/FlattenedClickLogParser.java b/datacube/datacube-common/src/main/java/com/attribution/datacube/common/flatten/parser/FlattenedClickLogParser.java index 6409790..61deb69 100644 --- a/datacube/datacube-common/src/main/java/com/attribution/datacube/common/flatten/parser/FlattenedClickLogParser.java +++ b/datacube/datacube-common/src/main/java/com/attribution/datacube/common/flatten/parser/FlattenedClickLogParser.java @@ -6,6 +6,9 @@ import com.tencent.attribution.proto.click.Click; import com.tencent.attribution.proto.user.User; +/** + * 将click数据打平的parser,parse方法将Message类的数据打平成FlattenedRecord类的数据 + */ public class FlattenedClickLogParser extends FlattenParser{ @Override diff --git a/datacube/datacube-common/src/main/java/com/attribution/datacube/common/flatten/parser/FlattenedConversionLogParser.java b/datacube/datacube-common/src/main/java/com/attribution/datacube/common/flatten/parser/FlattenedConversionLogParser.java index a7e4d48..c5e0445 100644 --- a/datacube/datacube-common/src/main/java/com/attribution/datacube/common/flatten/parser/FlattenedConversionLogParser.java +++ b/datacube/datacube-common/src/main/java/com/attribution/datacube/common/flatten/parser/FlattenedConversionLogParser.java @@ -7,6 +7,9 @@ import com.tencent.attribution.proto.conv.Conv; import com.tencent.attribution.proto.user.User; +/** + * 将转化数据打平的parser + */ public class FlattenedConversionLogParser extends FlattenParser{ @Override public FlattenedRecord parse(Message message) { diff --git a/datacube/datacube-common/src/main/java/com/attribution/datacube/common/flatten/parser/FlattenedConversionTestLogParser.java b/datacube/datacube-common/src/main/java/com/attribution/datacube/common/flatten/parser/FlattenedConversionTestLogParser.java deleted file mode 100644 index 99e27e0..0000000 --- a/datacube/datacube-common/src/main/java/com/attribution/datacube/common/flatten/parser/FlattenedConversionTestLogParser.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.attribution.datacube.common.flatten.parser; - -import com.attribution.datacube.common.flatten.record.FlattenedConversionTestLog; -import com.attribution.datacube.common.flatten.record.FlattenedRecord; -import com.google.protobuf.Message; -import com.tencent.attribution.proto.conv.Conv; - -public class FlattenedConversionTestLogParser extends FlattenParser{ - @Override - public FlattenedRecord parse(Message message) { - Conv.ConversionLog conversionLog = (Conv.ConversionLog) message; - return FlattenedConversionTestLog.builder() - .appId(conversionLog.getAppId()) - .convId(conversionLog.getConvId()) - .build(); - } -} diff --git a/datacube/datacube-common/src/main/java/com/attribution/datacube/common/flatten/record/FlattenedConversionTestLog.java b/datacube/datacube-common/src/main/java/com/attribution/datacube/common/flatten/record/FlattenedConversionTestLog.java deleted file mode 100644 index 0ef4ba3..0000000 --- a/datacube/datacube-common/src/main/java/com/attribution/datacube/common/flatten/record/FlattenedConversionTestLog.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.attribution.datacube.common.flatten.record; - -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; -import lombok.*; -import org.apache.avro.reflect.Nullable; - -@AllArgsConstructor -@NoArgsConstructor -@Builder -@JsonPOJOBuilder -@Getter -@Setter -@ToString -public class FlattenedConversionTestLog extends FlattenedRecord { - @Nullable - @JsonProperty - public String appId; - - @Nullable - @JsonProperty - public String convId; - - @Override - public long getEventTime() { - return 0; - } -} diff --git a/datacube/datacube-common/src/main/java/com/attribution/datacube/common/flatten/record/PublicTestClass.java b/datacube/datacube-common/src/main/java/com/attribution/datacube/common/flatten/record/PublicTestClass.java deleted file mode 100644 index c0ab305..0000000 --- a/datacube/datacube-common/src/main/java/com/attribution/datacube/common/flatten/record/PublicTestClass.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.attribution.datacube.common.flatten.record; - -public class PublicTestClass { - public String appId; - - public String convId; - - @Override - public String toString() { - return "PublicTestClass{" + - "appId='" + appId + '\'' + - ", convId='" + convId + '\'' + - '}'; - } - - public PublicTestClass(String appId, String convId) { - this.appId = appId; - this.convId = convId; - } -} diff --git a/datacube/datacube-common/src/main/java/com/attribution/datacube/common/flatten/record/TestClass.java b/datacube/datacube-common/src/main/java/com/attribution/datacube/common/flatten/record/TestClass.java deleted file mode 100644 index 8b881b8..0000000 --- a/datacube/datacube-common/src/main/java/com/attribution/datacube/common/flatten/record/TestClass.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.attribution.datacube.common.flatten.record; - -public class TestClass { - public Integer age; - - public String name; -} diff --git a/datacube/datacube-common/src/test/java/com/attribution/datacube/common/flatten/record/FlattenedConversionTestLogTest.java b/datacube/datacube-common/src/test/java/com/attribution/datacube/common/flatten/record/FlattenedConversionTestLogTest.java deleted file mode 100644 index f985bfd..0000000 --- a/datacube/datacube-common/src/test/java/com/attribution/datacube/common/flatten/record/FlattenedConversionTestLogTest.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.attribution.datacube.common.flatten.record; - -import junit.framework.TestCase; -import org.junit.Test; - -import java.lang.reflect.Field; - -public class FlattenedConversionTestLogTest { - @Test - public void testLog() throws ClassNotFoundException { - Class aClass = Class.forName("com.attribution.datacube.common.flatten.record.TestClass"); - Field[] fields = aClass.getFields(); - System.out.println(fields.length); - } - -} \ No newline at end of file diff --git a/datacube/datacube-common/src/test/java/com/attribution/datacube/common/parser/RTAPageviewRequestParserTest.java b/datacube/datacube-common/src/test/java/com/attribution/datacube/common/parser/RTAPageviewRequestParserTest.java deleted file mode 100644 index 042f7da..0000000 --- a/datacube/datacube-common/src/test/java/com/attribution/datacube/common/parser/RTAPageviewRequestParserTest.java +++ /dev/null @@ -1,15 +0,0 @@ -//package com.attribution.datacube.common.parser; -// -//import com.attribution.datacube.proto.pageview.PageviewService; -//import org.junit.Test; -// -//public class RTAPageviewRequestParserTest { -// -// @Test -// public void test() { -// PageviewService.Pageview pageview = PageviewService.Pageview.getDefaultInstance(); -// RTAPageviewRequestParser parser = new RTAPageviewRequestParser(); -// System.out.println(parser.parse(pageview)); -// } -// -//} \ No newline at end of file diff --git a/datacube/datacube-flatten/src/main/java/com/attribution/datacube/flatten/StreamingLogFlattenJob.java b/datacube/datacube-flatten/src/main/java/com/attribution/datacube/flatten/StreamingLogFlattenJob.java index 0010a30..a047f52 100644 --- a/datacube/datacube-flatten/src/main/java/com/attribution/datacube/flatten/StreamingLogFlattenJob.java +++ b/datacube/datacube-flatten/src/main/java/com/attribution/datacube/flatten/StreamingLogFlattenJob.java @@ -11,7 +11,6 @@ import com.twitter.chill.protobuf.ProtobufSerializer; import com.typesafe.config.Config; import com.typesafe.config.ConfigFactory; -import org.apache.flink.api.common.serialization.SimpleStringSchema; import org.apache.flink.core.fs.Path; import org.apache.flink.formats.parquet.avro.ParquetAvroWriters; import org.apache.flink.runtime.state.filesystem.FsStateBackend; @@ -68,12 +67,13 @@ public static void main(String[] args) throws Exception { LOG.info("set datasource done"); - // todo 中间的处理逻辑 + // 中间的处理逻辑 SingleOutputStreamOperator flattenedResultStream = messageDataStreamSource .flatMap(new LogFlatMapper(FlattenParserFactory.getFlattenedParser(logType))) .name("flatten map"); LOG.info("flat map done"); + // 将数据落到cos存储 StreamingFileSink sink = StreamingFileSink .forBulkFormat( new Path(savingPath), @@ -87,25 +87,21 @@ public static void main(String[] args) throws Exception { // 设置broker的事务最大超时时间为5分钟,小于broker的默认事务超时时间15分钟才能正常工作 properties.setProperty("transaction.timeout.ms", 1000 * 60 * 5 + ""); -// FlinkKafkaProducer kafkaProducer = new FlinkKafkaProducer<>( -// streamSinkConfig.getString("topic"), -// new FlattenedMessageSchema(), -// properties); - + // 将数据落到kafka存储 FlinkKafkaProducer kafkaProducer = new FlinkKafkaProducer<>( - streamSinkConfig.getString("topic"), // 目标 topic + streamSinkConfig.getString("topic"), // 目标 topic new KafkaSerializationSchemaWrapper<>( streamSinkConfig.getString("topic"), new FlinkFixedPartitioner<>(), false, - new FlattenedMessageSchema()), // 序列化 schema - properties, // producer 配置 + new FlattenedMessageSchema()), // 序列化 schema + properties, // producer 配置 FlinkKafkaProducer.Semantic.AT_LEAST_ONCE); flattenedResultStream.addSink(sink).name("sink to file"); flattenedResultStream.addSink(kafkaProducer).name("sink to kafka"); - see.execute("flatten log test"); + see.execute("flatten log"); } } diff --git a/datacube/datacube-flatten/src/main/resources/conv-flattened-streaming.conf b/datacube/datacube-flatten/src/main/resources/conv-flattened-streaming.conf index caf388c..24668fa 100644 --- a/datacube/datacube-flatten/src/main/resources/conv-flattened-streaming.conf +++ b/datacube/datacube-flatten/src/main/resources/conv-flattened-streaming.conf @@ -91,4 +91,24 @@ test { type: "tencent" } } +} + +prod { + flattened_conversion { + log-type: "conversion" + saving-dir: "cosn://attribution-log-test-1257943044/conversion-test/" + checkpoint-dir: "cosn://attribution-log-test-1257943044/checkpoint-test/" + checkpoint-interval: "60000" + stream-config { + type: "kafka" + bootstrap-servers: "9.134.188.241:9093" + group-id: "test-consumer-group" + topic: "conversion_test" + pb-class-name: "com.tencent.attribution.proto.conv.Conv.ConversionLog" + } + stream-sink-config { + bootstrap-servers: "9.134.188.241:9092" + topic: "conversion_sink_test" + } + } } \ No newline at end of file diff --git a/datacube/datacube-join/pom.xml b/datacube/datacube-join/pom.xml deleted file mode 100644 index 05f0359..0000000 --- a/datacube/datacube-join/pom.xml +++ /dev/null @@ -1,32 +0,0 @@ - - - - attribution-datacube - com.attribution.datacube - 1.0-SNAPSHOT - - 4.0.0 - - datacube-join - - - 8 - 8 - - - - - com.attribution.datacube - datacube-common - 1.0-SNAPSHOT - - - com.attribution.datacube - datacube-proto - 1.0-SNAPSHOT - - - - \ No newline at end of file diff --git a/datacube/datacube-join/src/main/java/com/attribution/datacube/join/JoinJob.java b/datacube/datacube-join/src/main/java/com/attribution/datacube/join/JoinJob.java deleted file mode 100644 index 3d09444..0000000 --- a/datacube/datacube-join/src/main/java/com/attribution/datacube/join/JoinJob.java +++ /dev/null @@ -1,71 +0,0 @@ -//package com.attribution.datacube.join; -// -// -//import com.google.protobuf.Message; -//import com.twitter.chill.protobuf.ProtobufSerializer; -//import com.typesafe.config.Config; -//import com.typesafe.config.ConfigFactory; -//import com.attribution.datacube.common.factories.datasource.StreamClient; -//import com.attribution.datacube.common.factories.datasource.StreamClientFactory; -//import com.attribution.datacube.join.process.JoinProcessFunction; -//import com.attribution.datacube.proto.pageview.PageviewService; -//import org.apache.flink.api.common.functions.MapFunction; -//import org.apache.flink.api.java.functions.KeySelector; -//import org.apache.flink.api.java.tuple.Tuple2; -//import org.apache.flink.streaming.api.datastream.DataStream; -//import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator; -//import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; -// -//public class JoinJob { -// public static void main(String[] args) throws Exception { -// String env = args[0]; -// String jobName = args[1]; -// -// // todo 这里的config需要增加配置 -// Config config = ConfigFactory.load("rta-streaming.conf") -// .getConfig(env).getConfig(jobName); -// int checkpointInterval = config.getInt("checkpoint-interval"); -// String checkpointDir = config.getString("checkpoint-dir"); -// -// StreamExecutionEnvironment see = StreamExecutionEnvironment.getExecutionEnvironment(); -// see.getConfig().registerTypeWithKryoSerializer(PageviewService.Pageview.class, ProtobufSerializer.class); -// -// StreamClient requestStreamClient = StreamClientFactory.getStreamClient(config.getConfig("request").getConfig("stream-config")); -// StreamClient strategyStreamClient = StreamClientFactory.getStreamClient(config.getConfig("strategy").getConfig("stream-config")); -// -// DataStream> requestDataStream = see -// .addSource(requestStreamClient.getSourceFunction()) -// .name("join_request") -// .uid("join_request") -// .map(new MapFunction>() { -// @Override -// public Tuple2 map(Message message) throws Exception { -// return Tuple2.of("request", (PageviewService.Pageview) message); -// } -// }); -// -// DataStream> strategyDataStream = see -// .addSource(strategyStreamClient.getSourceFunction()) -// .name("join_strategy") -// .uid("join_strategy") -// .map(new MapFunction>() { -// @Override -// public Tuple2 map(Message message) throws Exception { -// return Tuple2.of("strategy", (PageviewService.Pageview) message); -// } -// }); -// -// // 这里是将两个数据流结合起来进行join操作 -// SingleOutputStreamOperator> enrichedRequestDataStream = -// (requestDataStream.union(strategyDataStream)) -// .keyBy(new KeySelector, String>() { -// @Override -// public String getKey(Tuple2 value) throws Exception { -// return value.f1.getTraceId(); -// } -// }) -// .process(new JoinProcessFunction()).name("process_function").uid("process_function"); -// -// see.execute("test kafka"); -// } -//} diff --git a/datacube/datacube-join/src/main/java/com/attribution/datacube/join/process/JoinProcessFunction.java b/datacube/datacube-join/src/main/java/com/attribution/datacube/join/process/JoinProcessFunction.java deleted file mode 100644 index 84e19c1..0000000 --- a/datacube/datacube-join/src/main/java/com/attribution/datacube/join/process/JoinProcessFunction.java +++ /dev/null @@ -1,148 +0,0 @@ -//package com.attribution.datacube.join.process; -// -//import com.attribution.datacube.proto.pageview.PageviewService; -//import org.apache.flink.api.common.state.ListState; -//import org.apache.flink.api.common.state.ListStateDescriptor; -//import org.apache.flink.api.common.state.ValueState; -//import org.apache.flink.api.common.state.ValueStateDescriptor; -//import org.apache.flink.api.java.tuple.Tuple; -//import org.apache.flink.api.java.tuple.Tuple2; -//import org.apache.flink.configuration.Configuration; -//import org.apache.flink.streaming.api.functions.KeyedProcessFunction; -//import org.apache.flink.util.Collector; -// -//import java.util.Iterator; -// -//public class JoinProcessFunction -// extends KeyedProcessFunction, Tuple2> { -// private transient ValueState requestState; -// private transient ListState strategyState; -// private transient ListState impressionState; -// -// @Override -// public void open(Configuration parameters) throws Exception { -// ValueStateDescriptor requestStateDescriptor = -// new ValueStateDescriptor<>("request state", PageviewService.Pageview.class); -// ListStateDescriptor strategyStateDescriptor = -// new ListStateDescriptor("strategy state", PageviewService.Pageview.class); -// ListStateDescriptor impressionStateDescriptor = -// new ListStateDescriptor("impression statae", PageviewService.Pageview.class); -// requestState = getRuntimeContext().getState(requestStateDescriptor); -// strategyState = getRuntimeContext().getListState(strategyStateDescriptor); -// impressionState = getRuntimeContext().getListState(impressionStateDescriptor); -// -// // todo 这里可能后期添加prometheus监控 -// } -// -// @Override -// public void processElement( -// Tuple2 value, -// Context ctx, -// Collector> out) -// throws Exception { -// // todo 这里实现join的逻辑 -// String dataSource = value.f0; -// PageviewService.Pageview message = value.f1; -// -// // TODO 添加用户画像 -// -// // 这里添加策略的数据 -// if ("request".equals(dataSource)) { -// PageviewService.Pageview request = PageviewService.Pageview.newBuilder() -// .setProcessTime(message.getProcessTime()) -// .setTraceId(message.getTraceId()) -// .setDevice(message.getDevice()) -// .setFromPlatform(message.getFromPlatform()) -// .setInnerCostMs(message.getInnerCostMs()) -// .build(); -// -// requestState.update(request); -// -// // 设置时间窗口为30分钟 -// // TODO 这里的30分钟应该添加到配置 -// ctx.timerService().registerEventTimeTimer(request.getProcessTime() * 1000L + 30 * 60 * 1000); -// -// Iterable strategies = strategyState.get(); -// if (strategies != null) { -// Iterator iterator = strategies.iterator(); -// while (iterator.hasNext()) { -// PageviewService.Pageview strategy = iterator.next(); -// out.collect(Tuple2.of("strategy", paddingData(request, strategy, "strategy"))); -// } -// } -// strategyState.clear(); -// } -// -// if ("strategy".equals(dataSource)) { -// PageviewService.Pageview request = requestState.value(); -// if (request == null) { -// strategyState.add(message); -// ctx.timerService().registerEventTimeTimer(message.getProcessTime() * 1000L + 30 * 60 * 1000); -// } else { -// out.collect(Tuple2.of("strategy", paddingData(request, message, "strategy"))); -// } -// } -// // TODO 后续还有imp需要处理 -// if ("impression".equals(dataSource)) { -// PageviewService.Pageview request = requestState.value(); -// if (request == null) { -// impressionState.add(message); -// ctx.timerService().registerEventTimeTimer(message.getProcessTime() * 1000L + 30 * 60 * 1000); -// } else { -// out.collect(Tuple2.of("impression", paddingData(request, message, "impression"))); -// } -// } -// } -// -// /** -// * 定时器,负责清理状态 -// * -// * @param timestamp -// * @param ctx -// * @param out -// * @throws Exception -// */ -// @Override -// public void onTimer(long timestamp, OnTimerContext ctx, Collector> out) throws Exception { -// requestState.clear(); -// Iterable strategies = strategyState.get(); -// for (PageviewService.Pageview strategy : strategies) { -// out.collect(Tuple2.of("strategy_empty", strategy)); -// } -// strategyState.clear(); -// } -// -// /** -// * 这个方法是将数据(比如策略、曝光、点击等)追加到请求的数据后面 -// * -// * @param request -// * @param paddingItem -// * @return -// */ -// private PageviewService.Pageview paddingData(PageviewService.Pageview request, PageviewService.Pageview paddingItem, String type) throws Exception { -// PageviewService.Pageview.Builder builder = PageviewService.Pageview.newBuilder(); -// builder.setProcessTime(request.getProcessTime()) -// .setTraceId(request.getTraceId()) -// .setDevice(request.getDevice()) -// .setFromPlatform(request.getFromPlatform()) -// .setInnerCostMs(request.getInnerCostMs()); -// -// switch (type) { -// case "strategy": { -// PageviewService.Pageview result = builder -// .addAllHitStrategy(paddingItem.getHitStrategyList()) -// .build(); -// return result; -// } -// case "impression": { -// PageviewService.Pageview result = builder -// .addAllImp(paddingItem.getImpList()) -// .build(); -// return result; -// } -// default: { -// throw new Exception("no such type"); -// } -// } -// } -//} diff --git a/datacube/datacube-service/pom.xml b/datacube/datacube-service/pom.xml deleted file mode 100644 index 11eaf8e..0000000 --- a/datacube/datacube-service/pom.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - attribution-datacube - com.attribution.datacube - 1.0-SNAPSHOT - - 4.0.0 - - datacube-service - - - 8 - 8 - - - - - com.attribution.datacube - datacube-common - 1.0-SNAPSHOT - - - - \ No newline at end of file diff --git a/datacube/datacube-service/src/main/java/com/attribution/datacube/service/RTAStreamingProcess.java b/datacube/datacube-service/src/main/java/com/attribution/datacube/service/RTAStreamingProcess.java deleted file mode 100644 index 48bd46d..0000000 --- a/datacube/datacube-service/src/main/java/com/attribution/datacube/service/RTAStreamingProcess.java +++ /dev/null @@ -1,34 +0,0 @@ -//package com.attribution.datacube.service; -// -//import com.google.protobuf.Message; -//import com.twitter.chill.protobuf.ProtobufSerializer; -//import com.typesafe.config.Config; -//import com.typesafe.config.ConfigFactory; -//import com.attribution.datacube.common.factories.datasource.StreamClientFactory; -//import com.attribution.datacube.proto.pageview.PageviewService; -//import org.apache.flink.streaming.api.datastream.DataStreamSource; -//import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; -// -//public class RTAStreamingProcess { -// public static void main(String[] args) throws Exception { -// String env = args[0]; -// String jobName = args[1]; -// -// // todo 这里的config需要增加配置 -// Config config = ConfigFactory.load("rta-streaming.conf") -// .getConfig(env).getConfig(jobName); -// int checkpointInterval = config.getInt("checkpoint-interval"); -// String checkpointDir = config.getString("checkpoint-dir"); -// -// StreamExecutionEnvironment see = StreamExecutionEnvironment.getExecutionEnvironment(); -// see.getConfig().registerTypeWithKryoSerializer(PageviewService.Pageview.class, ProtobufSerializer.class); -// -// DataStreamSource messageDataStreamSource = see -// .addSource(StreamClientFactory.getStreamClient(config).getSourceFunction()); -// -// // todo 中间的处理逻辑 -// messageDataStreamSource.print(); -// -// see.execute("test kafka"); -// } -//} diff --git a/datacube/datacube-service/src/main/resources/rta-streaming.conf b/datacube/datacube-service/src/main/resources/rta-streaming.conf deleted file mode 100644 index 8ab77b8..0000000 --- a/datacube/datacube-service/src/main/resources/rta-streaming.conf +++ /dev/null @@ -1,11 +0,0 @@ -test { - streaming-rta { - type = "kafka" - pb-class-name = "com.attribution.datacube.proto.pageview.PageviewService.Pageview" - checkpoint-interval = "60000" - checkpoint-dir = "" - topic = "" - group-id = "" - bootstrap-servers = "" - } -} \ No newline at end of file diff --git a/datacube/imgs/image-20210408113817724.png b/datacube/imgs/image-20210408113817724.png new file mode 100644 index 0000000000000000000000000000000000000000..5a1c16f76d3eec38c8a81ad6a5e0dc58c0512042 GIT binary patch literal 118032 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�uxR2uC|j~eov>9=y9A5^>W;_WNzE3z*SNTp=s z6pur2?;BS)=8D1}F&D92leumAg|dT-SEwSci(!|NW+g3NIJ>8xYihh`;mz23EYu!- zmJ@fR!a?p5eY1u1l1@sYz|a@?2!Q^W?3_G|R-ui+ksQ9|nG*Ma%820mw|A5p9~i%!;l{alS4eQIoN+1DKf5C!6k$NA^|7PP@@Y{qH!l^zD45Yq0ZWSGLg?BX#ftPiMcf8orfnPcr(SG)l-AcXej_vnf{Mt{{tJ*!KG?&{>q`?}{cU5#tzDPrSiWO4 zcW}h#d^1Z49lvt{C>It#ls?7#qf=;gX(lo*&Ws|?j@9chUHnRme z{{zdA^gFomcKLZVt23~metz}G>BZl<`K>ezn-2tp(&Bk;OAqCxzH~{c;f<8Nf5l8^ zi7tEfOcCK>@ujnm27&-aCSUD04vS5Pc*LG&bF8NU zp|*|KiBAPvPhTu|%YEfO-GFQ(ee?gI!^d4#mD2Rb4g;%Mh5*7QJ_GJ{n|wZ8cS;p{mOn=`X0DIMVuttUs>@r=AVgIAQsdw$h=_HgG- z68-LKk|?rG*^p&W#Ej)#f=iYZSjM-TcTHFdEj?y(iO{L7)FBRo7wag0!qmA>WDzH( z@1)BZkHdprre@(n2p>RfIeQ@ZZu8yQYw9JAtg_B8!h?Abi#=zHzX&L=yu2#Q6`%<} z!zjxU@WQ?AdB)3@lh;$|8}$v#k2*1_u`|b97%?1*5QsR+MKOPWu6YedR#Z{%;+;vRS05ka9$$<}Pn?0JB3R++Kw(3ej7K#8;IyHwyK?1x;lBdH~4eg)eU28L4jh{+-#iXWRaUT z2FEn_1NO=xkKpsF^6eq@)9>4w{GSls>?oxTOZ06n1m2wTdWyrj2`q&MZlJSH>6~j5 z)MKB1`{r;P_T-02OxQxm( zBLE;Y$T|rLiQO8=F^Pa<_3xZh?B9P_$u9#Tl$7%{CW4S=Hxujs%ITS=4JhjCH6p|1 znY()ci%8w%ri%!+Nv=Nt`R=~(p9sB9ou&N*+bdHjwL`^i9n}b3-r`J{X(Xuc?7tq7 z);rWG=6l%B)A=e>@o84}P*XufITMWsu!&26ky2G8~yx)EhKdrue&Fyk|OCfhh zN}p@jdPk4LdeHmvvBy44eRpuzc!EWDGAX4ywKKc#8e-o724%V!jSsd#qvj8iTQzFJ z@ykzm@htfT>^s5$CH}CN_3lQa8l*Hxk|kdw$;eu~j{){4# zbfNK&$1FW(f)6@|tej+JPu(!~*FIfA;FyakYnkc1zM$V%li+Vyi}Vsof}&rvAu01E zAKma08m4yNoAAzu&x}{*7}sslWb~10y50;y4dO+8vUN=M%Eo81HEwlFpDd}V4b+zR zYDD)ZL+-wT#&21dNz&CiY5y2$SC8F#GQ=Izg@}_Fu1_c8rl`rC#qm>8Wp);eGr}jS zZd@8_ct`;0K(F?yhgW*?S;4m zAhJJa%1C4Ug^>UL^;%dbmE?I1NN=)sGd`dAQq>)ZF6g3M3aXrvknsG0{K3Iom*^kn z+58&uLLsqByUAPk!0G4o-rywqQyy0>Rgdild%IZ2r{PJeGQ#!8)0~YIISKaR)7_r) zO%fT&_9QE8me=F&RW}>Y=~sxQ!E0x-4`UB1$`GG_HtHBjxS&hrX>yNP(M?KAkz+0J z%)=DZwHKa@axGt9)fxVzi0uDi_Qp+lS6*r<`BcRF7^fKf3qd#nq~O?>UCAu25J4&c4-zUUzC(;oCcY93}u2bYIkq-lu_!X#`~0_B-L5OChfLjtZSxuEba&m65CHmf38IIzGt;EGPsVGP(ZT1b6_KamPzu&l20~~&S%u>LKgAG#e!ys34b-pCxfn@E@c&Z>ne|ycmH!GP zL8Tr*H^6sDK=0%&AZIM;#(GnIZ2%+zdW z*6RP;U;NW->5sn+*dM$QgNhjV^E&wRTm9)-_tP9Uz4C24WFp{_-d`r{_ly7iHU9TC z|H~v5{_kx5&m;7IjnM|rwIY6$=X|FgoVeII7yQq|@|Wc%C?8A}3k`Eko%VQUV-DAH zX+QTrug$-RH`g;K+m z1TTV(Nz2jG%;f*;FFWcU*Z=P=|KmLW@4)`g2KE2Le1hlr%F2237Mr}C+P4v~cg|be z7jdIR*sX$^#o>?6#wwpVbZ-9QErZ?Zwc~P5UTzyW>Ut-06J?u-T&QbXb0OTx%*<>O z^CbRdMgQXS|24tkYF3Vund0$sLWPOWBZc?UPrKFnT&tQk*fSa8+RH3nDYiT+8S&=d z3kyVuaGLhbDmC=?9F2H9qr8oB?mhH>+gYsi8MO68@G0MO?;^My{C_it2aA{tBDOIz zu}&y*1bdVCi(i*^%*8@V<8JBVRw-M24z69GB*anemlyxrU4-@Z<9hGlg_2Q=igDS-zt6FUr1l{&gUb1W(D7KBc#p(TDhw88qh*us2FPvlZ zTiiZ_sLC|=9xI{E-z@A6`}dOvKjSO>J|GfbwzrY=%+R5X?>{c1-)Dj~73?-#$zJ23 zSi=G(GDhk7?5}Sc{7j&5=^EkEnJ#6g?}p|P|247;R^t^db2l)Nbdvb;3QXv`nUP@& zcay3gFE@p7pDz0|TEZ3-;!hs)4=_PugKZr{Q~e?{1b!WyX4K*yFC^-$-|s2Lw2_sg<0oxBy?-2a<(XF% z13^bdPqW~&37HgMP0@^hq%Xf-56n-Y%|TXEG9h~W7P0gP%`j$pI1j8?i%mv_xSTmW zY!jKgK;{3vYwoUg*V64*`xI57IpaH8H4~CGWZ7SWO8eIy5nRQp`6Ft5VOS=}!3?0S zE!qJIiP-SqsRz)}AiDeF%zt`-f6ndc;AaE7PBEQS=gibeMVs@t``ML%oDmT0)Wn{= zY!o{t_Wt)(MYSs{W!fRjigNP_>xNbvzYNMILF~H7zoxc+?6;}UU#qDpcjS7{!C(xP0Z%-wnr=bSpbM|VJbkX??Y}jla2=kPDt&25sPhMM_WyA3OpWbW7!7JJuj>56=J)$J@trR`J{pwJ zGyS&G&_dc>%9Q7%X^oFd%)twTI;=dZ3x{nQbNK&muvqwBmk%a6P+nawxZvQQN_bE4 zMOHk<{oU&no}%S1qWFqc3Qu{1w`tG!1qLQbElfbtO6*=m zoRN`67#J9oq&#W;SahQFf)DmL;||u5?xg+O+=CfT z$O7@kb~fblGmlQGVdpX*s&c8;E*0G30kE*BMbJfWmfA!gbNN;UbTs_L-)_!=>R=%q z4#+J4`%~?#n#d)F$9a5-!_@j>0#$xq=avng;63--4&W*k0YaAACSWg?16()l8C>hx zj7`+~Eo8HLSc(8&jm?Txm{^HSy%#g|Cd`KlXm1Dn#HNtl)r15k0Q>3tBzrs1gQX3i zs)joz0d@DIDnLM~(6|I-13mKa(+kN7i54@+Bx)SIeX$PeEt&m!(5F zsbDQM*jP)xxMLrbzJ>*Lo~=^*eRGYktp^$iw;=$m8DEO~cz*v^z>&V-0VIZFRkog} zT%eMawS$HMV~+#wyGWoCn*j72nmZS$l>X7EP(a^+6N`d&w8WE?Qc%{QC9nzi|1 zrLGSsDm9l$pL$Gjw=y~Q{L>sh6gp;I83zdNg+ZO%fK#&1sY?vG8i$Z1wrtdOy14cy zT8@eoD;Y$`^5fZl`_=Y;8aQimeY{Xtn9ET%(ssat8P+jm2-6V7psZ_$2db|5W0PE) z##1y#ZpVWc4>OqRk!CeiOu0ZU+^1Mk;KCAcs1KeGx+lr7Y>2FD_xRnM_XjLYv5kxh z&YvCSOueH|qFlqqV&5MTe*JA~L}yj49}$Rm!U1XwirwYG-42(r%Ayj=T?C{K{b=Hd z$U{)-PhioNeP=zY6k0Qe6UI^LiP4}TwT2F=l3xFTJAq|c-|bMbxvcAIZs9$X(=TeVJOwBmn6$fN)7wt?&6b^WI)Lbc^9{4s$r!=dI zo7hD9cFR)*z%RqMz6d1UW2n_K`iPGAW4heW4K||G^mTNXo0;}zd`0cY^D+8uP5}5> z?2In%WQ`Fl1}gGaBtVIZT?1@eZb?Hv@Sj!pp1XxM?Q6+#VgFwL5Wx`yZRe8r`8C z>DF&8j>1kg=Gmou9YU5!xYH;^DsRv7U$aJ*z&A>>I%T_WaLe8;k6B8G{hSAkZaQaa zT6sNH==*?(pi3i3E7{sLWseH6Rt8P*3A8A;+-Ph|)rxWuL$#in2KnMGjAK<86j^oS ztjRY6zwyGtx5MMrzpufc#tl9cdQs0@mR(X0(@+7m$<6iC?wQc*&LKk;z)`#tb`^a1 z{GcJlW+ab|Cz>5)*;*(M$z=a{{vl-$0rUQcXAyFPw(HxKb!JlkG-!f_HQW<<)Nr^B z5M{D#%dalx{B`Je9Qa0 zmF){wJO2>Kes~Spjij%4Jx!JucR32t`{vgs8!}uY5o8Gfnm8(+4%%mMQ#=$7#D#R0 z{nCzn>d@u6p)gM}2uvMM(f(UU8r*skxX~(%U3wDEJuj3S_J~wZr+kYled3h~ZMnvE z+Xv({)Z;Rb;2iR~{;LM9yrEQy0nlTXD-ztL!~U+t;8?5+?`tcfPtUd%Xi@Hi$u|PZ zW|Q6JV|#^TbGp8cX^XD~WT!Giz*cJ3`E}jJAor`%-DX@B8p&WD^z8t*tjyt(U%lA0 z`L~s*pn#GN8#J;v!d0?w71OXlbvwz)R-O`@&g}yOchCUk?WaqnsEwuiVrbv(w8tAy zxOdXWHp(mTas_Em80d|;J6er@+UYMJ1kI_=(HGt>JJ}^zGmFvC2OwCm z?M#wN{oeWkg4NU0lVK0)CsZ#KT*eD7uxCZGP2$=UWQeo2Z;oW4K=5%o%6M$~xr}oY zUMeu_oRa+NTkNBc6KMXb2YoK40=eFha%Xfn26>nBN4&hfT-gW9>A1d$rj_J3p)j@ z>Sj$fQ_Z8lv3khL=K;KYr08*61XOC?eVOtzadV65dEi8;hM7^fHJNHv9y6H&tj zgU)23rmauIYXb<4-#=9uwd4LsK2q&iI~ zXpR`SG1J>K(3ShhQmGuSHjczw-`+@|h6_4P8?>#PrU~~tM%Q(HkCa$WD~OQ2k-po3 zcH%G#_(qWs1qSqMWSSSWv2}IDjOa`g-AIZYY(rsVT|9f8W2U^z{=z~EA=5N#8@gB) znbRn%75`?km1-%|ve8&}k}F4PhcCtc(~Kzjj*?OLT52k3(B*bQ@(P{Y(%_^IMJLhP zZFf5kMz*Br7zd#S0zYB{ntFpquqBjuxefvXM(m-U*Z#vYt-do%IlP<{yy3V?ZEKdh ztGB!zleYH3y$dOUNJ*Fgg5<8Q^m^SX61bG~VL6*o1){&qjE3CZE=$D9e1|938|X*; zhuzF>xhrY1NztB-qcG#FR;MmAkBQWK&`XA+N#))*k~Md4W2wMklM0A8##Bi>Pzl>s z1yg~CCwO;2jX0>JtjNhGr5V$Uc&MG(hc1Vx?zM3~Ji$agK5k>F>fvI+lg(ci($4~g zpl3qQrI(wcFYr()>O^W-wE9Afu6#}a7-zTBMVNMNI|2aWE91cC>sGUA zH_kx6OO@ZCHo!U$skpgtqUk&*&!L0TocR-s}?K>rqLI3 zIJLKc3q=LY8^9f-Oo1X=lvL1MqBpGJFyvMwSkfMVcf(nFA{WHTUF41mI}3dI78Y#w z9)P3Tr@R@oXzZ;5gkKSZasU8xjCb(L$SY2ZMa^{}YIqljz`<8g8$rjMZxz>Zxqo5e zYe*meI3yRmy0@mcH=tArh4U28C=sAKo4nklmeu7@m0E_yop#htyHHaQc&5?0&EkZ* zJZgmBNv3i-;tpGGUF}Z|Ojn?nBin>y4tcAig*QRt>P_LX!7vMNV@L^V>hq9`t7r0b zQ5%3uV3H08;N6+Cw@R~=^2g?lDjYYtDivg8zeuA~F}O^bI){=}k9QB8Y)2>suE`q# zWW1ht%Dn{rto8-x$ir2dMitOVc0f`XG?8YTMZRoFx(RJJo9L_U<}D?2E_jFulUsv! znuEMTJ}hVhG)3X`NFQgmS?G)u8J2dxmm=4(5j4`sWwZn)r9uNmUp8e-2DVuV{j7z*Yf0uzXfDh53}~^h;vz427-R*YC@b81?lW7d zRxjD=X4Sl7i)!sidw~7kgsP9{EQZ<{YTZPq5&~v~o)2;nE%r>KAc)2#1NNZTghi^P zNf?%f6iy{0T)H#)ltp*eE6X~Q5V?qzgRIF1KI{y2}Oq3y?HZoyo7RJ zW8HLuHsAhC2GN2gtuAq;=B7#EQ{$5Y zBLr{>lvcO~99kfT-b73k? z``+4>>|!L6HsCOdkQh{Ad+nIBo`Wdc8-ienvr?|EjGEs}MV_o_Er0h15fvoHXWKR? z5cF@L)O!5XTVNZn?$*A~(z8DlDDR&PO;`35T&t;3!g^Iy>}gN(@vKsJCV$A6_D*TSqh^J2V?uSF`A)lig~9`+#u5sP z&G_ZVWx803yn&L$(ongcKdc0>j3)=>W78)Gx2K+`FP7AW3`yv^^0ew4^4|5X-f$B4 zn|a#@!g+U(U`bVM>wvWOjpsyD<|(*e*B$2B)WZ$b%v~YlsC6#{#o<4yx{%7gYuS9k z4$U1UKc5XIAu$lyO_dD-T!TS%{za*qa^-d3kGjo5Q(En(S>RjOJtan|@_<5-?-=A; zR)}C9)U=0{Wl*Z}z#*4?O@55+k~dI_Vg;89nvW&yQELa|VP`Ppw>Y~z7@V{-G`AV} z<|ftehP>@m9-P41Cv7@E?_3*yLs4i>uJuXqNi7zcLJYe-=1}px)>qkH3G0sBCOeHS z*#0|diQ#)qT-dSWC_mqO<0gz34p703y2=Avc&j-dvxlQh{V>eZaGHA;URQ;(7X57l z#y69isG;znPRVsU%R*8webG=nkfs7fq#m&-F2K0zc$KR{bXoC*1>5`O0e&`%v+KS+$2%z6$V zn-+7u*@3WVu^C<8YJ(d->yjy1?urrL9G-SRGJzZB8V$C}`0F&S+Vtr*_1jEo@6#7A znU*5zrCZ)C_ToPfFdrRuNa~w%aCVM$anLbS=K68Q`&z6$f(q;o;sl#F-Os|zkK8zJ z-6;=0@+FkRLzCrET>5A%)yz{DJEbNGo}l3_IWyILEyHu2lkcK+!yvSq%( zbmEnc0Ri@?=NG+`n-+w9tF&(TWahci?6GJjML(uECsan6u3o^soB~|(^{Gq9Vm`bNDKi?TEG*!-tgI8WN~lXjqM=TwSl@-{~EP2W4QzpMw$Kj zXg%ga6A@{?#)lgiNFv6+Q-ZCKSu~0C!6tPHIG0$(L-J(DkRZ={7s;+DaSX-vEyba{ zJ=>cLS9xnjY~CrHB?S_th1R`DQsh7E7J0dK#JePUJdZdPQr%&b>}ao|cWB;bxclqy zA{#a6OrB%em*_l!)+jgfaBdUhX?~t0q-+vIl+#JPNc5NZyhlmKm$Q43kI6$m#hmeA z9QC9q%hjXhq73s*IVJiM+@XEG+ z=W5;Ux)MEsEgnGP^j)Pna$7QrHdSE3UkO&ea5u`O^%kt6W;`E_@NB5j#W2oy8puUI z69Oplk1#gAN?E;~Q(mb2b~qjc#X=^e+pDQ8n1Rcx3~~iC5~=boVbS%^ZHNV~_(>nv z>=v<)1$RS+{gn>hS6)C##h@1rp1|yzFJbXw0drcGqj&hoP83030-Th8Cn#tjD`Sc* z#y-I!t6zc#MT)z1^M-*zbheh7(w{kMVg^OYZX4s(d($$X;XAGH*fZm^>SmMd4<0GU zOl~Wg4F?s#f{vDksQ-lWygd~RT5C%=KpSUg3~ztA?1LN`$1I(5ct28VUH(l4(YiE= z%+yLCaDk6?&}*$y>xz*O#7i=~@2a|s$}$;zyk6S)5zph2CWp;#SapzSExzsOvtCO1 zsL(JWC3e&Gf?kb&TDFX16@ZIM*D$HHw^x6RL_=%}aG92NF|#XAb@n+6&8ZMh#Wc$2K=HVXA?`oE>+HI|*1 ztsOT`eZE3#OO2sj)o)x`0jcq~XdYxNtp}g63=W8qUg`!w^W;OsTD{knA@dVkO)}CA zkE-6j=BcIX+diqdz`z{Pss-tzy<}%~deKvV8nL&HP?($m>?>}W2sRyN9{BT3QxrF4 zRwLD&Zf>jXF0wB?-d1Ijz5Bt?JKY4Ud|EA~1(R+^KfZ4ThYDuyU)ddH^Vk^k>;?z& z{#=|^Mdvze+)X&0f%gGiSt+A>b zS!iKiBKLabYiM|hjhSuDaI_VcRzsjgln-6R;+z`x>SEQEgpRf{MZANaldEtxK?1>H zHopcq{rCE0u2kyA439mCM){=M(bSUh{&)w>c*(f*6W2Y}X06oKfe^o#8Yes- zd}L2b9`3UpGGobaxJFv$H%VSEia*GeQ0Swyq&!E3{`vS2$+cjAd!o1N)s|g-E#+?C%0|wlWQ-%q9XV~6H5TwDdeW1o5Q80tDqD?_xXU#OFashrN!Wo-*b0ch{C=***GM@FE)YT-L z-QBj;pEV-@D|x-qN9!=N1Gj5?pgdc<9g)C;-zKZf99?f(DNP-WCVomX=by!ah)Kj(vD(SRFSKUE(fbHgLd1a$EG;xkI3qU2 z`hx{BKIKfNP)U3#`8?Ubj!Az1OjT<+wt8^R&aVz|+9UwYD>qPRrpvP&g9OZ<`RgbrHO_M;W3o#0?$#?pKtK9)JOdgPCalsQ3p+tR<*+~ zcC=2Xc3>|uv{69f*-LwBCPe*o9LrO=f?oPmVaDXa)IZ z3+!IZ#c@inx3Bj1Kt#?B%vEe`X9aC%@dZ(CitnATUkBaO4I|vD7`MbZ?l9Wbh}#}a z7gRUL_oqVlI_#B87+Bk-$A+TVxO_Mruh?d`m(QKNljyQLC7@EpV)K?#dH1-*^qV7{ zbw@l{t`@6iO}!sW4_;XRVVv0>!L|PW-o$Wp-T;~{=48&@#4w6$tZlf5`D__7V@)d_ z)E50x@470t^m9AfVQgYQ1uvl0*PkcZG@To6<2Vn`~``!LH@F-#5^VzEiN z@*^O0AX?zeln5WZfp%QLlavmhl!wC*B*rdz?*#o$k2iIJCB~aOL}dtrxn8T-hXmi} zd@+8$tzTzPSEdpA!E-isZgf7|^GngmL_(TJmPqB;ZaXtLsH~)#*aSJ!4 zqo5e#Tf93QjUNNQpm>gTxm7P>NG^kjDH#V4okk{LHKZQa9V#8TR}$E{!8JobKLep} zIV_X+_k;Sa{3~g9?zr9I8aRLBm`7SFapm6V`EtW=e&UZRFbo*IR~=AuglAQCLDh=% zjDVB^bkxD=iZ^E#l7F`D(CuN^y41{uL|m!FKzWtTP3|(A)R=u|`S63FC?`)4lvs$n zLW&*Cy}RjrEN{{cFEztc=2g-7A;@`k3g{1&qP@-t99Nz>@6GODFk9Z`W#UTnW8smR zS^W23ecjK4*ja(XJnTM-fLZ#Xe1Q@YHE-a5csaP7q!qtzgV2s&jE&J#hUQ)FN1l3f zq@2F4%GV;xqvWI(9&t*;5fU!R?nAkONA!9gZa(X%&fCyATa`YMJ9P*S z`xv+qD4kf#T$`r7(27hUv@S(M`Hjg3MINaF#UO)fy6*Wv)H-{X3E~y*y}LrY4~mdV z`*E{+KMxz+X>N05%O))MBMddT`J{HEq_vb%?oIX&%IvlwS1M~-9AX!MXZdNjxYyLF z`08PmCrY_7%G~RSV3u)jjtxpWd7zMpmx6^*oaQRv=VjkBw%g0FPx{BfP@ALJxyAtB zO;dIJ@UD5(bNUJ3Yjh<@Z09k`rp=MTYur_ivG51LfWlmKU1f%U1z1jAC(4){!czF7?8-;zU! z-Iz{+t#dpq=ZzS0QaR=G+=U3~=O5VvCp$(GvsCJ=@P>f8Oso+Fl-CvNi~XVaZ)HiH zIsPe$t`FXJsui1`3O8&648Hw!m7NpAXB4Fu(0&rYOq>&XCk{1ov6<$|#&`MfIBy;y zZcUkv?jOWoX{o5i3E@CVC_BStV{|uMAY+wUM(TX-)-D4giODM7CQ^Nvg9i`!Z zlj@tYc$Z!!XK8rZ5_Iab7flf{LEEeKL|XOK4_Bo~+oZ9bIKq=_jB+3RS+CU*x{Qqk zxDrgJLFyWiMVe@>9w4N^g6F+S>t)zyGoVU>`9T&ZrgoMWB$j!u)CMkg?Tp%HcKiF+ z-mA7YYafcl;IojW73;vYr!Ok>ZqhwhW)k(+jR=3ALZ7cD} z4AjHkqDi!Ut_)JQUFJs9Lb`$aatLo*(R~&?pcAFf^^NBO6;jceE$7y0U}WGC~u&KLF5V#pnBTrZ_#8C?31|-A-W#Z`?guA>QOf>7YXx* zYg^f&j`W!53KyPl5o4dks6NEYhkIPR=C527^B96%)Fwgz7W1-y~3kQs$IY2 z32l&I)4BWY(C=IT7R2m@9ZtX5cbx}`W9he^gi%jfA~`B2oH+a4IP4fogQ&0#EAD2c zWS4Q|t+X&oNumbIrMPStkG*83;9B8E|A1Sg$zqSTfGRmOZaPTErF@Zmhq9#kl};|r zrWV#bK^oPt+g{QQ#5C4K(Xpo$mr?SR0-|9-l=6+;pHR3CqjanRV!M-&2pCCvW(wAQ zeGthiWFh;WUDDCCD|6>XTRPw3iRm4@GahmT*9z1TdBVM>cKI|QKdw`_E!yAdGe(Q8 z&{DWkH;x)tn$?-XJ5<+fVuSWFI~M@=)1o3?Vsc86H)x$7tJv!>hEE>%Uik%f^Gg7h zK~E3lg56%~ChtqJdEKQJZ1XnpqGoCG0{|9Cg;2yH!%U-%nEt`<#P#ha8Dvq6t?d57 ze-vJ=W`Ul>N2s$4jWw0BY2#>K62k9T@tBQDQqIn50&4Nd1b{jyj6d-@;=yG9nzVLW zfDOyck!)3Nt~l>|O^u7r{K*4M!*gI41z;hSVQz&dDZUh;o(RcTj1)XoXrO3vSuC0C#%fN^w*W59@(EtCia1(hXa zB>y}VyZ77qPP@~R0$&C zuEf=Zje*i}oJhkEJ`1v*)U8d4HlYBioW=0yZ=@9asoDZzUId71?7uL{i=#~Yl;sVi z$)#%iFKQ|0v*q#5kOdtQG{mUuuBXJqXwHG?O=zEaG4}G`1}JaufPEmQ+Lpf(B6UT| zP`iFa)TDx{(B74_?jP>Rc@}N$m^epsh0|Ub!ZNbe=>{t)$+oG-pL`m-zL>yNDzQTA zGj+dL`(s__>K1zGc8RR_W;fYo?#x%-R>Ew< zRs&klQLWO0BA1=(-j3AGR*ewUEt)PgKP%Tzuy(I#Oefz8c|<;#R`J9fGg%rXhK4h9 zC|*yUaQY%8f7;LF!OX3mRM0vyD+r_|{F-;^}os&#K{ZUdx`%p}KX zGv4P=BPnk${N#k>!|08iv3++?C{Kyr@~tDToz|J(*VVJa`(f|3$e!c7E(1=GCp&4J z>zj8GxOt7=af^kIXw#^86GvO>F^)_>u4#0q)4NrtrEH8^e-JMj-drzBJ2tS{h%8Td zBV7rcn@!l6`v|~*cF$g6atnW6m_gG`K>e-zXE8=?1tXXCwrwhX!cvOa;D@7Cs#nhb z9uEKRh5iA9KmVIx@|T-A{RCQFrS*YT0w4p&S?=O2ldfdg*l0gbHAQX@BH|$tggIZZ zp%3;ZIp`8eB4_TJcy!3Q)ns0nir|if)G(Ir6vKJj>zp zo@1L$Ike&PcuTZl#d{a?L(1f)6BRCpk$9lg5LIO?stGom3MGp9K|a_X8A~{0m3CNF zNCAvIe|YOI&5x1HiWix`ZGArH1g`9tWp? z^ImQXk=1)m0!?){#lHpt+2#eQ>{5JF5q5nP{!g2+i6~&}w`;o@)Fn6b|IqfG0ZnCF z+lqjKA`VRv1stVFS9*t0KtMpGcMu}I2n3K4nuw>OmHsvr;=h`Ho8m)aq9s0qla;VtYMB~tlilWOkT~=pUK9r{g#eDv4b@mLjvBIRbkp)|k za_;)VO}xsvfz&rmQZY`^@0JE5`Y{r6<2G(G<1ot6TzKZtaQU&`V>A)4|BEQPXMXtO zk^YWBUBuuUOIq9nNPs-O083A7^Bb}A16(bB4ZmfrPHRwD94}~pkO)hF$CCcIN<=uN zZX>`%qLO-0G_jvIYh&C7REJtE>mlL?lE@r~Pd=qvA)@l^_e3Lk)yQX}t+MhWW7P!o zgBI&fz1ci7EZVT7mXeMmvbWd*i`)!ohGJ^JRQOrEhbt`)U_RaDW1UqDPEe8L){w;5 z?g$CL5yc;nNqTAsE+05V5&4iQXH}uFn!ad4Mfs`j0mRk#1i9ih*LJqJ=t!WBIN2cT zgEmFWpNhtuD~%l5d{6C{TOPb=ceE0Sj@)_hM$kT|n!B zWkrX5gmXiwz=CGG_~YS$p6b*&jxA@Eh_|a7%exd0)lRGu!`(bWQb6Kl1S5e+1u%x~ z%|T1wnkh|+_7f$h;IDVqsbtP|R6MtD0r zsSNSAtLEZkzg67W@;(?%CK#3F4mzzuwaai{@Hn!)+KDUcGWaD8WL4-q6ts)-hFtZ~ zxArsLc&#UthOipe7-=R>&733h)U0=_CC3A=KCM+j%;1RfK|Ljpfp*g53VZ$@{OCUO z%xKPdS|kiTMxma^rct*DEqioOw@WOwwtGT7mHYNf+L%$|+1rU4J3_(r8V^)?%?`g` ztlC|T;#tJ{aEx7ZyK{$k&p2{xXT`zae|X>PUwvGCygvZds(vXZ=W<1_b8Q<+cYW)i zhWu@g^at{is<=3}SLLT7AC5b=HaO+0`Anu$D3TW`rON3cdF7XCUNzQPr3^YnlQQjL zkTU(!CQUmfDn2^zzHZwgJKY^;5}bP0tIpw%&!F{42Hh%HI~mnkKQ>YHs5}LzC)%nC zezN~5=@PM=5d*}*Y7S9Ncq7^~+r&9`R*9q(6~QvJ+cD&aq(dW{FO{S+z#&I&OvsC) zU=ffidL_IH5VpzwfM1CsZM2>xZRXJb0mNAS$_`2Ql0kJWWlw7M8ub{@=mLd?*B@>l zfr1F14M`BOT=^~?nKUuW&K9Uf@t}x`(C)NHM6Co7HH-QU-caQLV-??bMf2b`?6+V< z!G}Fn0T_uT?X%?c0@A<~f5M(x@ncCzS3yl=(|t455z(!$`_?A3i@K_OJj3sRLig^Q zfDO`DRUyMqTCw}YS~DB!1}4VQ6-;6gNod9>qz-NMgG%S)P}M$b-gfsZEr~@UgODTD z2P5}Cq_l5eUH4L=JxmG9Hdw$ZnwEo-TxvhxNTZ(dw-!aPbWR@Dgg=e#1l(79$fwwa2W#|EcRyV-s(_M0)3O8e(k4 zN5|sIfr(GrQiW`IZJ1`{*|8n_kwvMzYX&Z5;jSkI@F7%Pe%Gh!ywYho)+M+CM&Y6>|US+*G>{*sWI>eQPr9uZzZNb2_bEDAO#`CgAY zt5U?wUe||N@-9-Jg@fOl7`1MvHSHHPaS@4+NjpUDi~eRx0!JRPS8b1K)9eUqma>|J zE&#m(cCA;jW-!ay7r_qbD|K5wJQ~97hg(T}qUIw5el4c^k>KDA7$3v?PygP$`lBSu z%Wu({Sn|CEsRi-tY{vICUwJpab4$X7=eXUBL8AaSLw+Jpqo*@Uh;>2R)>=8KH1;?iq|vxT?p_MV8GPFNjptsBKvd)=nhrSxpW^Bx6;Q?y9|bSJ_^! z6ZX*6Nhc~d%S7VfeoxWz=0K|^$}}oa8cBUThJ8Q3dHy$%D8o)ReMF)T7Ld7JXx%Rc zk(S@s#l4O6wc?mrC91n@63fGCFTRc6OM5bS1gP2$$`904Nc?F7+yDun;D(Dd+sXE) zRt_e*gKo|K6y@ryQyXn8==*QwupY(UDcvAfU4NW_^)uCqBgS~O3;MTl?E1OnK9y~q zU&2Y>vb)&wbpk?T<=A)ETj?e=xzVA@Hz~%u%<688a>*A9pQ)1OnXLK78u&xFxU6S} z*a@OMZN!_P!RPgv^8@(`+naTHYVQYE9Sad-*6(a3E=rEer$DdO^Z`jUCCA_H!3vBf z^QERwQZHKNse548&S6%+L1C*%<9h`s<7-B@z@ug{9s*96Ls-oMr)Q%CM6u53P&n2jFhnTo2 z5&l}6s3xKiF^AANZwEVTwAzbeWN(_*#3!$@A3aEbYMP%1C$F z`^3;UH`ISXJx_QvBI^#Y1)YB^r}d5srtNiY0v}htmofvtgE^B7>;gIBy`d_9l8u@Js71h*O-@jjOMBl5|g#bk~P^)N(* zzfm%n0F@$qPrwHjI=^&IoUkB<#^|R_Z%kB+!yi^@G#K1!3-g|^;h#D0S$*dz;AW4n z4ZHs{5ISn!l>QqxWXO_(C9z+-Gp;(wm(aX*NZuV@+Y$@l{2qFH$sfgAjBUF$HBt*& z6jwAZc&4VuHW|83RS=t%FvbT|2Z`u8BJn=QPZBoiSoidu3DrKc2La#OONDrPTg#6` zCkPZCzFS|lFO7q3UC#0!&!EXo4LFKUzJs+J)L^n27yJ*q2#Dt>!zUiXji|&B23(eJ zSDJ3xh9TI-ENQA^%)bk%EFer5bGK|Xwpv&S#p>f$RJ}=71*vK^;6#V!DbQ<;H;%tY z-feYdtw$~q^5MzR2Yx@>nuYAHga#Fo3?kw2-PyB~Y*arg%IoZ#>MIWxNaPXkLjPz` zDGlQY_Ej0-J=wqN3MO#IkZM(xg(1Jy^iKmu8bMc(k?j&+!ZxiJ5^-Zu{A4`E?phpU zUPJ2Y+S=2~kcv7%Q`EJKax~4u4LywfCoL(oS1&$G#I`F`tbMB~2J{X2whDLTHGGsf z=fT}>C$l!krplcUrYPZ$z5aV~KqK|)?MQvuC1njer$d|Gvbws}e(6G!bq~*0Ro#U` zdli`|VVj1fk|i@LyZkL_tUy2+UKWwT2r1v_GI(SnXRQ ztoEVt&eWK-)OK;VuY@SM?^2AvV^{QO&ZYL6Akwk-1z#%EIpJfq4ths;4z!$3eD##y zvA*8cZ5bh+Jtn*CS}V(Bgh88zzy>SZY(1@~R{bg(oU7XGJ(2rENL;ap=T%T7y(KJ) zCUUE6D#wVh%)BlC)7$q{oamNy#<5Gn^?FSnSHEwpSj*PUzQ`HMteX|v5cS^@WfM)s za?j-Z*O}sDhbNn))#`k%91O9d$MQ5_3(~~uJ&8mu1_@)CITTqYP`!P!iBDO_k#5Yo zjq$Bw?RK0!t9F9d{G{3r9a>;bZJ=i%$!PrGsn1mAVX7k%5{))n1sx-Q9k2DPN!pWB zJW34TUHE;Wu;HEG(35e*(}y}eE%w>iWJDOZO#?ouwxO#Cx^P8A`MfveNL|vviAF6b z(r}+Q$LvVLq-WhNV!#jfW@(@Yg`rS^j^pZ4lZ;a^^cs9HTTS{Srrib9OFRiCYp!wZ z+}=K30M%au_yT&Zr?^n55(DP%#8oB56dnN5U_-WvFk*EeRRgf-)a&ZdV)*^4oME-U zP=~*6nurMm%9Cb`K%^Mg%Wj`GSSAhAVi_KTceD!Y#dNZ`y3`?^>pVyEjMqOSoJWr8 zw|eZS0E3W_gk?b{6(AaGDJTz@H2e)q!>nw9o)(nW+!G8~!cnC!RUHJ_un$f6$9uEQaCW&DJ28nRKo$xrtt^fCM%w(W4 zbfU&rZKQX|GFx$vw3DsObwu*w^?foCY=P%W=@g)85BKs$`kPcanMA$kR1_S}Lzli- zO@pe={}{RzXwb59eT?NDmct;bR63-{8TX1u_M(Zm$;9}0ZTU`nSd#bbqp@P_=(uZb zh_&P#0!>yR0mBs~Ub`u3eXcGdbIlX<0%Y=KHc&lwGyd8b=LZ}0LtI88t-fir&R%M( zMOzm}pkdwCf(K7V*bW{LDs@ZOLAr<2FRs|D42k?R$QVqe&wP-1(lb--mV7V3W9Q3v z4}4p{beP-i)VVsLw&j)6HSsCy%@z@-gALEsI%UQ~(z+~%x5gIFTd}zBowcawOS%+zZy`403Eodh{W`PtmCm7HUzU3uDfjBx7{cjLXW+)=tmFzkKc5%T1l%C;si zwvifaAEb6|h-fgpgvw-=?L{QpnjBDduFqP{Y2ez1ZZ$>HPT%hGRJ^izH%8OHcYap@ zg*KbSR1uP$<2us_U)QD}`vFFAn1;C)qiy703TQxLPIyBR!NJrGxmpE1a7I9tefj}& zxZs*HD>Gl*L_rcb)L znw0$m-g5{$7F%MGIpWBdt#R1JsW9!W>imubsZ~Te-T3ZXV$VUS~$Qwt+ zl`Z~2Wnz0@QUEsWU7-lY4;7b)zXa;y=>mx-%5K{G57S5&iM>zw#~R7XM}Umrsm3gw z_HJdXAvLz|T=IFoWE=b?LNj1kqu@d|sNE;jb< zP-sExarUX^(H!3@(!58R;eX@4|F$Eycm-~*Hf2fxRG!zx&wGf{u(ID5u8ZMJC3AFBESJNDRMy}~<_ym==v=P!m=;1fzB zJT(UdCmd7hwur65#KweW@dKhrZF`T5ZKR^vB6j^%(a(w|ZtO0^nvB$n1+_ha4~)8- zKjakMFaL&h5(c4x#!NekyAG1VDw|0q;hPuKO?hl`}6Sp&0N2Ams>Y zA+dRE4v@4~yZixSVRO5X=-1|mIj}*=BL(ZxEO(ymeT~3aq^fC{C(%qo?u#?Z2%2@B8_0f8bF8uQAlM%<$&_ z^^^A_z$xJsnIzx3qMp zczoFLc9JjWXkX+AItYC0M}SyJx6)OfsAGRKR3W{xVx$uJrPOr9(hIzmmLy>SPfn*X5DKeO6&w%x5{7!awD3?{HaSso-|eWz|e*0noF*4V?hxUWu;>c-Bz1)KK%y<42MA8YEzjJlU6uaA9t+9&{Mv}r5tB}WNIJ=Bjf8kwl?h( zy@X<}{{nEkj*b}aPB74P%*u?YHq(Cf`o9Sc%p01AX!xbyPeV(J`Qdq;M3KWKQvX~+ zIgLOmFxSkx_C}}oxGoTJ7>p9&Cr-$8C%ISU?OE9xmw#RtQj@bU+#3JA!&6!Ez2Xnoh<1^ZAX>aPr0>Z{~ z2!26h<<{(xx0b(de6ga>GRL%X!4#5XX=c{3`5~pJ>2$d1PbNK+#h(vOf$a?X zl{<})IoYdbu|?ySrQG=*|I>8?nlp$xK`rc4YZ0E@-00oF+kS0zy#lfFKYub_AW%&s za-8s9DcWS}2Mc7+R5Z1?Bk%n23;ojr6w{Gqi#TzqKKUZ*Fe)%)=PZT^D_zrK;bH0# z``}>gxA$Q1a>CEv)$LmGilQhV>gpy(QqTyCS?tl2+QOr^vB&NCpFc+$8`ccuN8TAA zO}T4D6IfZk*pIfCc#{rnocqMC|?rzes#-vGhnVzo>`gYReH{{5=2Tz5%~%{s@TEX{=;x-uij*{x?_QIZsSiEF}%k z{$D?NzYwG=uO@}Kjuj01Z!9nH%Qdh-PaKr&|F3Hs_=-M0bu^K;_;ryG9IS$QM6*&L&d5#E~K7ace3~5|sfwA|}hdH1Na|yZ96>{wNsj8TR+9WTC zwQYCdWjOS!2_CNFq$Qe3ev6;Qp~o(;-|T$I_oq%KHm-8&o_kZuE*LFjS6a9*`Ez`B zZF~72azi7^8)+SYh^unBnmDRh=#@N?_J;#h2wtVsHW`q>{D#i>D|Y|vIrQIUPZj z`O1GSCC{}$Wyhnd5ZbTRGKQ^KNai&Eob8rH?f2K*mxHWM(v&ww?~r4TJ9{)ckPTEC z3Kr#b3p*mVOv;aXb{L9%kF!&T;N}~ZM||YS=k@1Fkk&O=c>QL_;||cmM`4)u;r;Qt zZ5INK>+c!at2p{NPIV178J-4dR{2SuzYa)0dkUh&rY%Ub$UOq#FPpIjPSrq=QAleI z2oe5&%e7xnF(o2_OjDQZ`D5)9B!65FP=@v>`&C&MM8)|E^)1cQoi~2I?Z4h!^P}JX zl)!_Q6vkmMnVNg=D&PMtkSJ_-0cdPNW@;-S_=KETtaX|Wyl!^^GBaLq?Pup@P9Tld zh=bIv=*F%$IFGHJ_v9TEMR^jIAGQDUjR&428yHD^h%!B$?2r_ui+O-4S7Uf|tUX-N z|NWnt|J&ipL}_r&+L2?dMkW??>a6cBl;WLd4{x4#md9xMM47=6G>vd?Y>tYbx0AG}_eHPJPrCuc zG2tV(hczn(k-SI~0_JIc;Fq71w&R_=st!0ZzcGRlUyksRuR}uZTJ5zQA&I}tpxKqyjO#DY`=o%;guMHg@p7lNk z9(ZYyaKT|usZ&2+shF2c#Iy`}ixu<1>pYS&+b`7?_(d1RjWzzq zm+%jHR_ffECzIo)YfV!qx0Sp}-58UjHM35Ssz%G+Z~vym3WP#Eygvzc=s5d6HSqa< zP-T2LE)3~bLhE_JHr=YOWK;jV#6REgzqXfo7fENURFi^a9hG&eVMk(t4;&5m-(i=> zNJF-NGF@{Svg}H9z0&9tRqqyLTM>?nxp)c0z?B|Gn2aXYBHT6@z5 zpPt(C^VI{Ieb3%kQ;SIiys|sbq?ztiCjMixttxoCVrzW&b%CM}nMUzuNL zsq_vP62F0Zin%f+55^7c+T@nP_R8wRzd~(SliYYINIArh=rvCrYsX;*V3)_sFSvTN z7s8f4#EFDX0CAVjz|+j5?!=~hwds8g*k%;_!9I8gLUF@}#hnuSiHR-<4 z)O{Ys@L%@1vtm;6{A zF%V3Ot!tH_GLiR75w?#_lJtbz4`zpBCThx#IyH7&tnPT)kU2JGWPAc)t>@+~6-~{z zDA!Kjq{&eTWci~WR>NOqf~M*8wsa%>CLa<^$Xse8JqrrEx<-z}bSBgBXIKMHko@w; z8&^!%1(>(FQkAd!A_1Y>W7 zN9V5rbPr)lrFzHq&3c$l>_dgqLiaL5E3lWVDo-VR59$h}Xz=bUD$p zSoCAIJOn>-R6Yvg`Y zJA2ZF@~MaXH}H!8<%Z13==#K*MHD`+swCg?-TmQ^zmOHtN+Uck?t@ziTjkX+Zoc^> zVCvCWg*`G1I;LMHH}N20)y)7#*t6aN9%h$a&j3PS1?a*HrF*%&WZ@XXh*=X1KanmU zoZ(obUtB|7QPC}e5}1!Wt37_qa$Jy0e@lAC*!jj{Ewf@2O{|WIqyBVFY>a{*C^m$a z7Tc%14jQ1ZFT_X3m;_=d-K)$!hx;p&oNC(vfA8tON?wlKt_u2kq||_8?NT<@;nu-q zjPT=*=$o*R`_#W%8Sx)n*P;!a=TC3Of_Y^F-SM|ts2Fc&?RmyC|NdW%R<3|n9}IIE zh!~u5X`xyJyT%Pv-$RT_tzMx3w08Lm2Xi?*zz)*8Hd>Oo-j*!xHj$+q<*R5 zEkq`uIz2sIbH6Hs`ECLxgot4&V3@d_k|V!i`PZh^ z0no98=c+kps)sJZFHZCPVyu5|E4k1MpgEt>EL+d?=@&o3-a=ytrGVb_9H=O*B8lWc zxc?D>(FbwDgRF}CdR=KHcYxN(;mJBr&x>ZhB|yxoH#wqT8--{oe-Ci1OTZ#<&bpw|R1p;lY_L%5sqRdSXh#yCrC%;#ShX8@Ee#e@**ov_) zr?W#6M(Fhs=}_*g67DYCMdj~|Ky#8`2L0K9kxO|an#&yNn8}A%4sfeRbB@Ix^C}se zb8%*h6nb>^@T#avN7P9Ek5|8bH*q0~v_Ycv*!LTY?+O^Z`zc z!}NV{Z-!BLuaR@Z@VfI+zsG83*ov~FVMCS%l>}zEoU7x6rGeSDp}v~BO;YK{MSfXX z**uE0R~&g%YARgRXNFAklkRV;Eu6{jhEXxP1Qez zm40Y6u#c8j{K9U%@}{y;ElI?ds?h0BtI_+OJbq;;FKUq6=!CbVh~mIO`uxy^?|>g%Qa{R&fi6cEpc@6cAGJ7ooiU>`>DmgC}#+V9R<(Ap9|&f(x7GR0_j# zpG3LKcjf0~Y^X8$;J6gu|Wf95gM(BvM-w-nAM!vS8I_9KMx6 z&tZ)m^Qgj^Aj@$TA2hIzsvHXcv5&>icvFW49dF83FQ`}ju0D$2<(Qkv>&7Tedl0;Hk_JZf#nW5`22x!Teq z41b<}l0*c7=c{y{lWz)nJP_k`XxayqIVdpH3P9pIm;eM0!w>oFLSSCHOB%qZUkt^0 zF`YVn-W@o!oWa=_uS*feU=P6zj{2Lir>Lc-2?yvSMT~=dLNvA5^ogpy`6N`nT^gma zj^ghScFNEoHK3P?u3fD}@foKKmj-13`3_m#0w190c^A_oHgm2D46Ii|3-a2S6F?h3 zlpn;>3cG@$%Ea{rFgX1;N1r47PQfMH7T_(ueVU29Dmel%;b@ZDH%y)NCZ?4^$c&o&m zfC~(*ox*IOhh8u_Edu#c5r2(metmZYK*hC?I=2!F1l`gyR+PNIDmdHUvr}TdNqM%&qDOQ!*O`Uh#{eV+gV9}dwuccZ`TjGOb&vn zWyB^{;;@GHCWx#9wTRPK-QO??n9Eh*O?@{MoC~RdcIEptR&B{|Oqa?Xfv&9wQHf~0 zU4QufF^)QcRH!GvvSMlp?xs zHuo_ImT+3G<^wQ?ec&;A3ZznVaZ!Y=Zy_wT;%@W1YL}TF4v=%Je12c@P}Rm9gUt(o zV8xl~;Esp}T%ye3Xl83xw6O6j>F)}bnckeuB-t}4de$Pp9QBmt;14aU<4R6TIDb4A ziI-VUYn>`uOqSW5K6RwCkS{AOWd3nNSL5R-rl=~F`cV~>#s9E`rLJ^+f2ca4TYgc3 znn{FbA~_1e2H9IKK=>M6mOU5x&K3~dIb_=JSDSW9c#<`uq+X2V5ty9o-+$qPOjJMV z_iUijY3KZ|Aiw_&{|JRW5iqZ~kHuIQ?B4w^DaSwl8c5CnVp?DzX1tzU6`Pa33Fbk6 zrDg@UvZ0fxn>`$9N=j(c5h((4HcmX=qQc z3#T7N`X9F3<`| ztQ)j<(IP~^#>CM`b0oJ3ocst=WzcUq1V1tS_TVJwlwT4Vx5>28^3qV`4kp7(#`LHR z3o+(aLN)9%cun>|_vAziwQO?r9%#~o(sHP8AL(XyGQ?D0=l<=N#4S*c)#Tl~bd~bl zlCWBahPi>)Q#W!0X710}TDX*~TBlt$(rku4?5_^5w5`{)Or`WidR3Ajgw?YHR@~c0 z;t^y;1*-z>FL3>r^lG163l{`kUOq5iZd+f?Ra>*+lKV@B7a78D=0%XLhM>v|102FF z*y&eu@X!dA#0PO10f+7jxgNsgU^ehl={RyO9y-P$1-oR9uutD8cSqiv3BP>D#1A{1 z$wOFLemuxO8KOMX-I0xWA{jnbRJ)u%CZurnct4+;a{@?)kUD&w@v%W+|Z@vCNrN4SAqjTo*L1L$?U`!)VdnD9i|3>VRePa8lmT z)4f8qQeX^jLw#YOK#%`On!s{adziJM6vJs*s&>hS-UK^9K;bCV;X8&~M?JxQa zP%;Ej%7ZY`V#(<>e)}f3TUaRz_e>ahldu?k`pEs{h=t?^1maXekq%;sWQT(zMq4%& zFFy6cPcQZ{lUUqG5tPFWqKcg{TA?+WJ^&Fmbom}qc%;P29Vn0PX1hlc38kD&ttp7YUx)RExfS9Ljl} z!@m|Bk~l(5y}mtPG^O0>94;-L$Cy^x-}E61r@%2f)26=$x0$Y|k{F zz<4m@l12f!euA9IVOrPFmhI?5m_wm6rFNAM1nk=|5+r?cDqS^Prqe`WCjoM;V@^6# zlxtCRAIPhApanY$vwSoZ5)QD<&tAitYH|Cb3e=5&ODVYq4ldg$_lW(f&jhLs?!iMP zhHs~?@2@Vw2C)MzH|+F|B9r5p?hjlaut}va|CO`7(e%ctT7=i!$9kvb^I@E@^WnbygyRA(II9rX4R zDAIaUOP;TTT-JYlg~|#kYBL#;6KF}MZa=LLsTkJXYSVgCVOo@*-V07blPPXJ;+3iu zy{FELH3QEK8y;!e{T`}WT*%Jqo*Z#iQV~lx%5#eBL;>gZEil)aPA&a@qR>ssA4m_Z zMY!-tUj{#=%W!iguE4RMwUTVu?hX2NQQ5k%pWTHiQ&fSRHAIK{^u}D?N2rO0!E&;D zPojH&i9>*C+gr86-La8D0%f6&OU-NepVAMD?@yDZVZJ@%hF5<&jlfutn52Dc2|3u` zII{UPdkDl85<_4o72k(PSYOoKJxdYS@ZJ;3P_z6_6@zay#8co--uSereklG)yiNzFU*YX-~ ziy4f8DI1Qr7Iwnm&hghe&s!mEM?PnR>lP$KHI_7*sC<`AjZIu+#EeLQpdr3orF}z=&LeSbig(R$^sWn4e2=_=KaGIeS zUXqm63}ve-&UnJltx{_-^WidgMYE%$-tROOMV8CSbaL>LG51%L<&mwCE#@|>2vRZ+ zC$KZfBDqJWUbm(~jkzy}4AvyBmfH><>c{ig8PVBXjZheamOQ)N5$mk5b#h;jsR$`% zWD;TT_;|}!ely>GWA9PlJOnEUOs@b~$t`&lLT&YH&M!QZy77B=NOFJwSPvUBiJ zuUl!L$v-}xH3@w()4InBj;1sE2g;7c=YK(i{zQmeufIu4dTjII8X1qxB#Aqer;{^N zFE^XJvU@o{mFk^Q(h{zydtb0e>^h|(^^W9xyk*7-ZK}`qw^{Qz?+M?uF@e&L+AGL^ z5UF;=Z0D_Xftv1C0bPCmeR17r;V&KULzoTOY$ivX>o;0;NRl1>EdA{IObmM3BCcWt zTSMeABO&1;<6jx@pYLt6wVh?SQur8ZBAoCWgW8#}OSgVE{GgoZPQ-ZgY090j(Hm2* z*&i2jc7~I6V^aI@v}rDV&-8bjpF^f~nG!z_VD{850c75&Fy_6H- zxKqIHr!KQM%SFrQQ4E;19j7ZW*Pj2VvOUN*HCcn&o26~4O886PFyDE%Uw~vc(tJ}% z-yk7sO1J2wKz%{K9k_P1&BMN0!fNpeH@wU{0U4%zA!SuG>W1SIGRLGd^PK5V#2QlgWMpyzEXl&T zuRWj2%9DFScF0cOK=Hu&?vR-jcg-?)`)e{9Bi6DJ`;^;R1)Ra&zC1>ZgRAkE5#`Np zWVcZ$a{33(Gv!*9!@GGlx>sXXHrGMf`usl|lQVOdFrQn_m^YFJuZ^wnZ@eO{j^r$f_Aymzn14B+4;+!`t#)e#wYM_LR2|4{F?d6FBaw=htJNxh_t5JVFFPosJCcQI8&ELFtRa6 zGNST%g6DRFI6c|Yi-gcm9|^`dt*O{0CE6=X@B(&7bhybJ$cFEs$R?ST*^&z>IG`)A zVHs~}IAkcI>bI%W7Spv5@rQmUy}Mb76_$voiuIcPa|a0Jnye5WcOEs56fi`EaE*-2 zu3+}N=?)csGc8M*8wIBjCG?lI>N6&zGcOW- zYFEhTJClXgn~Ci(gg;4iDg082+|E0(okdDE-W!x$afN#2e3bk%%Xo``vQ~kXTRIh% z3JVanD{19WTuld8!RVukUV*(oMis>fw2ij6r{t=6uVjWIRd=K(^<$SqoF6*mmRMop z;B_?ZqiL;i!V$SQu71NlnD%=3iNT(Oz!2`ILlH{Mf|uXpUO;q6O(>k}@ruGScuMgqGvmXKk-Z zKo*CjO^!#zt!W{?S8Re4KI#!2=ar%}40IO|_a!Xw=58q$TPX4qVHY=opW&bl$QBVn zwR|=c?$rV;5hu|i$5u^wcQ(P1@EFr>dxfYN-A}NE@2MpLUiPww6JS1a=4tNj=Fjwc z#@|G_bB(F5bDs6xeQ>whdBD}H;Zv82GPGsYW4#UYoSO7=)!3bLYBArqrdS$(0klq# zj8Sq1=+F4>Yo*t;YSR_MpUk^VJUrQzpCrVWee3QpvXSzS9=c*D{J@3~dDh0ARhGxf zdaa(^eh0XUFI@Qk>X_C`_FUhXC|YVI;_e5ISg`twnbMIPpyD5l!g)hZ4yv5jMA3k? z6unvIRjsFL{RIbJK-$(0w{oaKS#~%06kz3Keyh5HBf~Po@*n+LR5)ty^B zY)A~iAK@L&xh3n2kW5+74C6apH>ECM@rjh>vs06q+A~x4egy)*kXq>(7F~*?6qS4? zC+Z#5{7#8uUS-7@n=2lReec)8MW=q`6mIA-RSmudyS_>#<2+fteOERxJXn~rWd#r= ze?_gRaa&6S+Bsjep-I*mVrjj>GxbgXeJs6D3Y^p2IB<@#Se*-OPPTKc)WHkG*<_jA zQQoyUdTkc+8fyt%w<-3S`#s8jNPXC)a<~Hi{AGuk? zI*_Fxal7B73o&`qJ6RdZIWMZGzWM15uKzxT0B7qB5zM9utt~h>HTV!u8L?0z;uafI zR5{+kfVb9~6f7NSk=h@$%4L@)qPa%_uVD+``jA02(V_w5Zrl%8fh`Tsp(oGHJKA7^ z)iI8h*FetF=<>yfx<7CL9u>lds?nu2K{TMN8F?7b7y9&lHrx`RIW2f zk|!+=O;5@o1B}tD88gcZu7nF){Ea=W5k!eZqaWNw^Y68bC zSPVOrT~dNk6ax`MMVm!kp;T1kO1$d@{+glIq3iVJ92O4_p_RkKGQ8XmDyYJX|1fx7 zxB@`(O8q&lu@7Z8afuU&%k0BZRcbUg-`TWWc0f7NxAHoqv{#2haBI=mF^D724`MiA zCian5=F$e()QXIu-@(LdMl&N~!~A-AQ7xKy=}|X(USy z9NjYQF0E}bHUfA7tI-=$JmJt@PRST}1)=;3gW8H?L5(K>mA&dukqM?nKatPH$;si1SzN zAC)BSGYax+OF<(=@CJxxVAtiGk|%0p74{BXJQj~A!mmT@j2xHv2QOL0^RP)p#I{Uh z1myp?l$+YlH^H#RBCCD}=A1w6GHIs!eI~q8rmN5yGvN2Q_Qev;Li+fqD3k&~J2h*w znYS^wOn~<^-8ebDUk_^1lVF_A!Dm({2uHtJJ!fVuuD{Xlj(o+wc8TTZoxJXy0(fnQ zL8T{3ANQIi&KP5KZz%Ih2J9 z&jH?xO3n$aSAPp7N2*r_)2&C(>So`meow+2nndY!q!(ob>|zj2jfQVZD;o|sI|-u_ zjZ-4#3e<}F9@i&lz+i@A$Ja8#*WAI#GzGLdAq7xVJ*>tRFOM&UTw{|0bg>uU81`5bO zqan>=avsuG5u3^`p*TdCRC~1i&8utBZY;_lu{ZO@8y|q5G?a`f`8uVta z=s+Eb<@etU9klmF8rt)weKd}`9Vtuj8kz?hv9!DkkK~=HcY&SwR(h)OEXlBd(gt;w zu6gNNfq*0d^fJ+0)m0f;EhD*e@MEFP5DLYGB9e*zlF^Np$vDtn-e z>57IO6djp-)u4W`xW9PvELmZ}qUU=@U1ISCAov7CA2(_}0hBG6^GZXSG{Xam z&Rs;m1M&szT5b+^eyu3!o;k_(@_~6V$sji87H@brfaXbB=-uJZE?z!;zAQ1sIbTbf zZQ#M96J$^A&sBjYIha2g8@HJFgy_}e);=LZQOU!z40?4-Q{V`?k2$c+L+2y1*zyY+ zxdrPZn8;N_{M46&TQkbnRk(|0dFkAl*AP|yFDULr>)%nP7gEg4*f3!mEGBS0QI6-i z8@OkY?bW1piqJ-K06oL_9QkE`G$3S0&3pj5d!Lmn;%m;E?A!2+z{y%U$`P$ z%wm?-?MZf>TVMNBFX+c@WGRP%d1N%6I(^;)cU!=0khS0!ymELj*4SnLZ0I4g*YwGp zuFh~1I{l()EU>Q8(+i!qobp2GzGFVY&a=o&OnPIAU@;^S*qqxQEBZ&aM|pXvPr1t) z*dcWF1b{>C$=bKbfCPV?v!%MIU_EC|6}W0p)UAIW^n%w#YzjxLRt3Lm!Fs35C)C6Y z8-KaY@f^DC)Blv~j0{r2pdR=xcTZ5!@RB6Ei#kR2twbmg3d9Mu6YkzR*q^|ByR}>< z1)6d^K~LmewMD{x(I*hR(^nrw+7C)Be&xNN5a?XENiAjvC_8$4%@tMseAEpw&cJ+1PxFZngF9%lQo0b!4KH`Db`PI&*2 z<+)sd2S_NMi;U4oplVn%prkc+uOq2x@LY* zvz!vbbr!b0;%T2cK5seJ$z3z}0lOeucpnrgJkW9iyes$WC;RY{L|mMXYDCpD0&Xz{UVF zRxejRg69*ZRj*LKCtj4u$ds9BU)N_|M~=rhXdpt@5VG!KYu zF)q;ZLbst??$VB%pG25}Y_Styc!ZOn9R2uY>1Tbx^uijV{fwWY8P8^QnRM$IqT|Qb zAnzQwh{M8MjH<7UXvt!e`bg7WPZ8ea3H?9z-ZLtytZ4%k1W`~?au6^PB#Q(ENh%;Y z)8rr^NJcUU*r*^NAd(~}$BN;TpR8 zoU>2u+O=y}J@wRvaH=sj>UtQ2=guNU?Do9g_`vsT_9=bSWkOlk;4ek}%93ot=1C}Y z50|Y-f%UE$(>{1h0sBc#D=yu$TZkg$tcgcJ>o>Zej+W2?j@N;!Los-KHY%VDCwx@Q z)=ceKq0ciAdcKM-yB);*F|f!#ht8BP#0&yC;ecPwcC#)$MQoPaPEWiwIsmQ`!TuHW zh}*D51tQcR;{xCRu1k8#F<1OCHe+{9-p$NH>C#ZxoOMP~^li7IPC{DEpxI9{wv)Or{~j0-kJB-_w(Y|gr>Br|?z4ral_8fNCXg4T_{ zYd!KP*8|R@H@)U!g^F1OR3T1j?$!Uv4vDtb*)pOQ&$%8tFVo7Nis2;a{-1ddf^K$0c%LWm%&go1! z8BN%xJ9NDb4Vt&8htp*A+Vc&h+B4iTZ(~+X-NU^8+^XQB8RDyivWxdJuBPbn{$G5>cF?YzC*&ZoYv;n5A*BUUTQOm7|<3J|f>_r@UP*&^Xp4UO{$*Y<(}BGt(%^kb83>Q#jgQ>|F$ ziwqH|L#)*cRD{O{4D!W~&CTvNo zw@hbjPDxBTwwyCSu&>hGun4$%SrZW8a75vl5=>i~eu{H^6y)wvz?jfsC^d3XSMLm-XgyhS0$No= zBCz~m$bS;`I~U+63IFS}*Y3x!p5)Lm1mw_IKG68iG8iNwo29ji2W?IZMrA?Kh4Kf7 zmChKphna=L7CEMaElj_LUVh!`{;hC-oSdDA&Q**x=GPZxV~IS1+$mnXcmY$*P&jfg@?)U|ZE# zK3MHyEAX)GE&1W?TjIaD=P~HOl?EL-ozBu3x!)Z7AK|UV&JohugbVoh%yU(Blm%#>YS=kfEeDnr>1S0MG0d?KO z#e)f)zwIaS-A(`Rg3t~GgU_3maY*?7rhgo!{xAQCk)8swyZ7w^#sB@0-_3_P2N-;L zBwM@NFPQD4ca5J0DSs8nPWTHO8*1oS^%0?}KYw1)VsJudeQhm9w`teV(_-yx_wNRq zgikc6nG;l7Hldn~LX!a?Tf$S*v^2C86#2vCLM6}L-vVrICr?{GFP=U3uTgd3;zhD+ zO?qf)Y7Ty_EfvvBjH$Wn_`7K_e~-&QU}2okv8^`eE{uOtx09kkUiv}1gs8l{Zl}x- zt0C#uDYdrv_`&!ZUQB+NW^}~AL*_lB0troD|=Cf2VKX$Go80r+AxJ$F<{+ zML2K*HHri|u=2;S%nI$LnzhG}$4mG&Ya_N5z#M*7G*r{j(8y^f{6VTo*YGID98z`G zHk$nXILFo?{wS^gSPHm-kq%3G@I8N)TVl15SIHdQaK;XCRT9S*SDxC?Y+tKt?s!* zTOFXFr{c6mf-3w$bmz>?p*2Nmu!&}ASbGJgs?oMy?zXO}I5c)0aUGT-wen%!)(VHNa3rWChtFgzD^T!pvSbYk!7o~v9sr{)s}ij3)t${DnvJ+ zwZEyi$;c~RD9mvPq#Ss_mHp#8wksO0p7p4D;TPAnFDM!EulY$*Klycf`qr6cwp2Hu zOS;n1(#0aSrhbW{-gP=9=Ao)8;Tkc-=~@O?1b^dFw%<)k3&IR&1T{F}A}l>Rw|AXB zSHS3^>b`~RxDG`-2{H*?`oXBRi(m9^Sb$AsdRNwzEydn-nLGZ2?*H)P|7tVG9dKUX z#fQz7zswnZC8$+1MGyPxlEIrw-%b95hFtW3*3h$6Z&D(~y4*KS`u?M~zfs9Qv>ti^ zY)AS8AKo-6IxclowN<7}3NE&D{`c|V|HOJvlI0dBqi*hcLApHp)8~&#{h!P~RD{B3P_iXao9cgf=Ya?e+GEV2}3^o`=af zZx2&}iezY;M|2+VQwd)9{?1(&aq;mv^HnJ!UA9h;if(R5#VU6cq^k-uTPYs*i0peC zakM~_)WPngFyS6=v#o2Qo8Jo^I`#J#eP=BH^<(Wgut%babZ6;SIhG?cn!X?84=?>! zUx>AmfbGOokIOyqXo>yR#o&sPn;9yzsLV``ThT1_|QfQgt6dnjqy{lD}?O& zHG|m0EC(n+i-?G*Y-wqcye;T=C&?afZEa20$SBzo9R5&U$9x#IsZ8_ja-+oCxi0buWk-dwHX{rPKq?%0}3v>ImPUZsbJN={p4 zUuH+%ykYESyF=8ndRL4>w8Jk(7J35fBzi$n@2JchV#CsZ@DjhbHQMPr{<0Eqw0^gD#s$vNn0HsS`DZuobI+0cUL~QRs3vepH??HJ8v+ zDpYlwP$nqj_z# z9&J^xD=wivc$<%2q-WeV%$U!qjlQlcS-P-A<%09;q;+jzPcjF5Nd&)t#C^l=6p1_$m4+&hK}ze@#P-KK2FbPi%ZAo$qG| zq>rgjg-1l}i~-jNCWFI`K{6K?^KbmNpU3;t zEB(D_|8hepxC25Pb$1N(6@G)#{poFg{`2qG*pFKDlPnBhP_XDGcfz2GHlN=x+H5Wk z*?ehy0YW52;vtbyQL}(BXck*U0m=(N6QmUT-!0_7e8|NpFvAFCRdLWyYTNi3L9Wgg z8CAXEdt8RA^#H$kuEvJ@$x+t(CrSVEr&uw-rGe(K_dzYN+*p<>Ga^-r7^0-4)Erqf zl2=nR3JQ=EZI(wXMOr;Tb!pK^sP^AX=2y+6qLk6;>DdhJ_E!5=-lJ5aAcYWkz|Cyc zc3G>QGlL4G#x?1sab8c zstc(zPgv2jpN&v$wjTT{t;MaDqf%uTnWLE^LQyfJL|O?rGqsm>A9!f*@qYq~vnw)1gG*QnZtlS`4h z*+G|;P36q6eN?;D^!7=#B`2R$dJ?+SJ*r~GOM5{kiu3UcMmiLN$>XTVMhW0sMCg1p zg(0O`xxMCl`oxSMWr3aD{>2NvaVAnGIXjTyh!^bw{Z*FuhfKoek`zUD-t7Sn{;7I{ zC#xS}mIzpPS!BgUTI%WP>B`B;$y|kGFr&bA*r~D}F8OScIHMwcpn>TnM>1?GNmL2u z+Ui0<$D5{Fke4}3LKt}0)BS!V-wS+ax zG!Vm%xLeBrz>ydWQV=Lma*ySPK=o!Q!dGtvA@xBiUR^4-%njL7!uFH2 z8SRMr!oKZqdd#Qoa<*8V$+IeURS#<$Jo!D{ipi&C^@jGTy(Gk?nIT>2O>05yKb|W8 zTmdB((3mT4bodlew|9W7kVwZ~`NOXWT?ctk1~%J>Cq$ctgAHF{e+qHohQAwT{M3 zYVwR(U&Cfdu`CSZAF;?uoZ~0@29X5xt_^@fTD5Kr3ygK&TGpEqX;1U4L2Y^!R?r-I8ZiuyFQ@>cg z=cq(?KF*^;+et?F`fk(Y0?vzrCB`D4@W7$@g2O9IP-RjY%yLW!IcQzl!uBZultEMi z;OC{OpAs%bD9E(X@ z4#Uz~V-*j)SlJ>O*Ryiv5~<8~&K@m%3hd%&jevc0s{?Q$6@@m~StB8EeXI!f+#HtF1_Q5o6)`l2AMUU3O)R|e znxS4r!-)_#B7z)N@dR4EvwdiTj<``TTxvFZXKO|EL`D7Wn;&Zh-9p(|ED#pQX4zqm z>rrY|XRRag`|J1BB_bTgALU(kS>|kgGg`Cpk;LV*gF65Ek&{o3%?1Q;dG%z0bT!T= zBe$s_fl+{#A%{KKp@af(a}{8A89$g2sDDJx1b8SyP@`^I{M7MwT3XSufZkB4P3$#^&361sg_x73s{~Q+M9MsI*-yK z_V2O-txoHu#Zz-c+Sctn6um@KmhE?AV?=2l%v!}Yv(_N{S8+~}zQ0-*s30Loga|K# zHRQiJpz|&KlCG^|Uj+hSIR8LyVthZ%_hwXMAu#pw#eh^{IHQyc%agGL$_LLsgY%lE z73N)2P2WblAJ2lvmH`+{COuJ}YEn^6A98K4&Zbnq_1Qpug8mo%!>UHled8CX{%f7q zH5PRvkMeLKuidFljl>hGx0BRIVUUA~$SROaW-d@BiB&UnNtav95_2DjBRI?HJuYUE@h5$fKvm4+fjPm3s zR0)X!bO7B;m9q(1IBhWm@>6?dzAG^|hXFGR5+u{B8xvtgXBkb$Au|~u*Un04?gdhA zx(=sw4Zd*6oK@t0a!ectG)P=e^lYf2yMxfrR~iGR4T(>wI}z@707?Q}ip@Hm^{N~T zEspd#zKV%K=Rwg6+?bc&IL)jx!KN!o+@`pl-vI!mN)_*?ti}+FY&V3AYt{}NW|>^j zu`X5TI}!A#1wu@a$s+y??nXVEE!m%~v9S~fVKhIeijqM#F+^|)YGcT69_R#aC$O7ng!z_-_M&^o9xJ89~afk&Si0soqb0m7vb}!16HuK z_U^hkU%jSs)nEk2{w;tee#(^-3h5glFGVeckd9|KevyL|Iwl(+(=81kRd!`LnmnRt zq)%IXE0@g*VW~Ik{n`{O>Ff^nS+YU;@cl{WEaTj-&!(UCA3~#JF*%!Ymc42DfbxuL zU3Z5hh?0xwYd$-dgX}}aO?2P*S}iys)CVt#SKfN#s|0bR%fnW5-=)|*Xg?PRhx~0t(=)qY!xW#)Si13@p9rbU-p)?t`<=OT!YzmD(6U{P`lbmy=7kqKlx~^3^pYm1R!{%~W?q z9K3@Uo^n0jQNoFjKio$56WpCA2r)DmyYRKhTuzmGu__9W338exz@r`XoVIr8`lx1*tSUssI;`EoLGWC)iOtqM`^45Ug& zUq>Xrp!IdO?X9^!{&Do8=A2+@d)#?Frwxe8TM_%$J_E9|iY{(hNoc3t@QS9gxdpFU zBQt>lkHQ>wy{|H}@`@i*Ac@TLN;SE}Y^7Xwrhzl*J_ECrjeWTj-!9Q1z3x_5i0B2V zK$nPghkU48!FTcwJK%O$pYksM%7U|ghH}zGS<}+j|5rf>4>FDli}4o#Wg2?2TN%i1_cO})u06QU<>Q!0GPRj zSLLO{)ZBLL-bhRlT%Lby7~q$?oInZk@CHCA3gQRm#651gCX%C6vKobNda>wpJiuLk zj0k6Eu+xYDhwn;I64`{%klX=KqwOf+;yq1Jj*e`A9*-~TV^i7#eHZPR6Q4YG7Gmzp z&V%wLBL-XUPQ$8~A7i&k?_=Vggsp&WWZrqyUMebaocnlF%cuJHCup}nQ`13+FS0b; zVke?t|90T9Ib_z<@1+%;?06>p8fz8{_a5NiamcURwTNgi@5gdNAg?!HP@Q&l=O|+8 zYJA+U4v{bb&rlM*PM&w4!~D%M;6Slksom1A80N~aKiu`gEdY#7{ber@6QDfa1*OGz{0G_U%leiRMhkISG ztEw~&!PctxvFICHECTejC(fA^<#TfzwQrU{ErU^-YY`DHFbFy4*2C2K-_@}g588UI z(_(!;b5*_IPfi{A{1j;%+&o;VF#xW>SB70#7#H*r}G(NRQ*X^y56+-`YAX?~6awL{B(avn!fPi$2J)S$<2W@mK<1{rISN zg27COhhe~;T}zuqblZwxSrlsMk=De%Go|idHoEIG-Zn<8S3JDFthq(%cp;k5dXI(6uq$ZkXwhA+WclE_q$AbsaQueEyE5v;X z_5i1Ph4+muR=wvWa7NrC@_2NaBe0ldMT_U+RTSJgyD{eel%^_WNoYqWG9^pKCm&&X ztuw0cCn%KiCsw<-V-NBa^iEqD$V+Ou0Y*Bcafq-6$4$rxVDM8AEi`eWoxBnE+~4dN zWM&DAqJNjwk{7w8Ndd>GAD;bt~$vPe6`4VTC^Kh#QffhW^GY%D$y8V~Nm}q= z?~Ph`+5(nzBBr54WyIKARG92a@pjXN;dJ9U_qq3UM^ZfCZ79`~_ea z4ZDjAU%b8)k$lz#Jfr=f>_Td21k3j%7!;&Jqc}00A5~vlqF6qu=DU75G4#pj#m_eL z`-!?klWH?-YRAe=sUY8WQyHSF?Z=r2;vr{n^4;n2cUsFi4YIYR#)AjbV}*<|mce~X z^K@88c1opk7u>_ctJc&uHwy6Dt5Lyos1xX1ni5!5jx*ciaymrkQn9_6OUGDkHlTD- z2vK;kTo*=AQMNNb`;wh%Or-Q^Kos;UG&@V=jYR=W{UnQ6zmF8bX{vLkumMXIw1tYi zE{mN@m^}aIEWcrmq&#v{cgRCa{6^LS-TYE@yBr@`sSx_5!*70~H)|W!hms;zpDgrc zamI}nIP+#QP@s`a(pCqc4%Q}6d1lUBB9bVEYjcq=z5IdE~(ir9kyMxh==;kWYgJP zMf@4IOb@`U`VArWeqT_LycSoJ^oes^@?F2(skuw%hqCgkGRQyVFREjitHwJIQ$6%> z1~p7F*Ovgb=N^c;#nZ1To%7ZU6wXXJaLU`b2<6e6e}DF1*@CqN7bNCuC%5yx4|4Ly z%54o2sbvf%hYp>BA6{AqoK(YTP-S3HzB<*s$38!7+xGUl$XkOw#3t3Qz&&Ee%(+XX zrU3?^F5=)g2`FVq_7;Mc&Fhjk*bGgT1|N~%;Y||*$@(~+Xv@=b#w^%l!NK-`B(1gD z($bru&0cu-Cep<~RI&%;UNB-9P*gM3To-=zo-J$y(7C2EMO?gNTVH~Ad7&5ZK%urE zN~Q2zz=UxNR9m1xb%g$gvrv%UBP&G-Td*E`eq%99%U(-n z0>+1zA^I`9>r!lX_>4<>;`Sa<5T)?qDQ3Qs=?PO?<+m{lvAAFBW3|4N>D4w^S6I}s z$)Q!X25p5vNejkzJ_{K(UQysxPf~7|<$&3G6utMrJ#EB7scyvUThaCLjwQlTEf0>? zMbSqZ3ri7+5!G5mY{;^#rKS_?t=?&Z9;dH46LTeMNH1jX?Gc&649af{u%jn?=n%Ry zJoB@UUABZ;areyNJiW~PjsJn@d>4;~l52w;2>I^XL+{6g6D6lR2fBXl&oxpKqhUnxg7B)txsSl z97T+j?bgprT{sU5SYSD)4TU>#-1R-Z>Z!tHMa`-MsG+9BtE86@h>K(CJ|Fp@FPHi|_I9uL|6osA?N`oeIj7H9A*<+q>jc7c(dbpp2=IvzB+H@~70DFXQL4ebdGn z6j~}#?Ta+!MmY7LUT_re0Ze`TA=x?DDQ+ZD43$d325V04Cf|N@4s^bCqD|#-%T*NafR%{^`@m$%| zC)s?Jfe#7Gv!IZx@eD108^Pn8I#@E#fX=P2ukVi1dn_6mc+NPKUenp*kfJTt?Xta} z&oICirM~g)C2rL;Oj=@nfUm^MPre*5V0z5gm8E>{yy_rtG2C>8@M@Hsy?x=*4D+sI zM~htAI=~kzc~}Quw;txjc$7#w={XW>cGq8WKPFE6WR9vz1PgVyo+14{2>oZs!~8K6 zvxPZKR3u9(BnqMi9p9bm6loAjML0nS^Cuj!?~Xh3U>Q0_fIGFAgN?2-$g1AKCC7j3 zH~o>);1y36+m}z+13T}GvBZ?5#T4*EGYrz~MS?;p$jNsmoO3el>$&ZaxNFwcB7DwS zg?HG#=-i0dzKD>RQl!w#FS}uyLmWaE3|XLgF*QRY)T@~oryg#1S#P4M3|s%C4&UMR zN;^;lK`iS{<2QIPj{wQN&hZ2er`e^L7KH@C6dwffXiXqy&&?}(Z?T!tWoCm zURHGo(W>hGVhOT_vv_9c{T8!&MK8UyL;^-7;l8Q6L8??;_3G;fRjpEt1=Ku6_q4Xx70m-ry5c z`Uu1<0xejM4Kv|w2x_;kk*H@j`!KQQ3Y(r%iTvjJ2m#aM70b6Kd|H;Cl%i)VpfsK+X2_z$pJE;4k5w^vwtXE-Db6a6Dic19n<%2E z1zFG_)8y3I>uth|8B|s(XL^^*BI~j}tRMTS+qrg31Hs20qbUS@{Zw_M8E1KVEhRUq z!3*jN92Y>d>17b=-Sjpxe5}cgEMhv?pfMrGx52XU_4^c&zw-oKlG_3}nF$?2NXuvB zN(p2oFvK;sz1jeukr0vHIg4iHb51u;rM1axm?YI@&*k9(gmX10R*c}#wLO-psQkKa z_nb&u^3=lkOVHG~odAx~zSgS>+G^)Is6VZNjeu_R8Q@h0FyA;%jXzlr%Inx&2lcbT z3kz{?mV*G1ck_^Cd6R#axfI0_;XEd?E?OLN7TD9(0v2UI`o{WijYRRd85nfF_YMK3 zNl?1gJywsZ#KwsXFaN0}xrb$nHBs{>nH=4TJe_!hg9V7lsb_VT?9x*&SzBoG1=O+! z-{xW7?1tc0sq;oY#93MujNBGOFUkjKcSv-P&`hTnim-hdtjLqy-3R2~Q{%5PSBtpK zkqfL2Rq-x8w0H9oI)vpEj8GA-_l##)*K`XEq1Kbd=kfHni$?YK9kbX%K5Dz5l;+Cx zQ)`8cI}?^)h!Q)Orxz94hPC+Wml>wB84TMY%*Q5WTn3C=kGBL(v?j8S=H*8XLCW$6eg{_TLHlAdA% zSV*UQ>7)j52?0SNuYK~VcNwV*KQ{) z#}+m-64CETKQ0=(;%<1UjV#^&!*Jv`0fs}5^JQ10M$*@_6}i0zIgdDGiNWxAXENka zz~QX(zH3^e7xZXu$&)6>ZK)J78JqJ2;=Aeiam+BYP@UGGe{c+(mG?Pm7>Jr-lS^~* zz8a0I!m)0TJ7}FluJe)+m++}5~o3jcY4kuUow)?F+ z5IGkmi~Oq9|LlE)zh}IQY&*p&Gsn+b2kVv3eQ3>7jDrXA0}iO?19j^J$DS|12Pr?=zcM=EFY> z28BQMBmzhqG~dheSWcE2P{6X9eG3D#d<=p&dx$~bAiwC(PD_o)Fa-&b*0_>GA1OI? zspQG7v6bQ1TlTaTRkPjKiQeL~Ph*#Ky7q{8q|SSM-h3jv>yuAAP(GTDa3h2Y2*01kG2PknGEwk2NiPn&+nVvsl3n#%5ev)sm zeK6ntG>lo%nMO!u2F-0;G4|?hV&Y1p$yFmij+?@|X02an$*lFL%=J~_>vnCd7l?Yd zk=WSF_&X9emIgi0V1Y%?_Jf+QJ$cZnYd9aUF*3Nbm0X30ZM#}*$nCY4wxURHvJxAd z&H0572)0!pl5BHb*~EpMn#RKPZIRWLXqXhtXKt$btH&lTdDhKk0#WuYZ)=s}M>?n3 z?R3txxWsO@y2Oug5>Jk~^l~b!j;D^_KVU}dv^(uM7V#uAm+Qm?NEzsA&&vd?=~naa zz$!8?lD`6-%%sKVjX2^hRir7o6V;-AC1=87Sxl|~U<xi30~

t8`xWl3NdA;KY*JA6K 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 datacube-common datacube-proto - datacube-service - datacube-join datacube-flatten datacube-attribution