From a47476f9bb5f1575375fbfe51a9e65d1e49a10d3 Mon Sep 17 00:00:00 2001
From: wuyou <2213093478@qq.com>
Date: Fri, 14 Jan 2022 18:24:22 +0800
Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E7=89=88=E6=9C=AC1.1?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
_coverpage.md | 8 +-
_navbar.md | 1 +
_sidebar.md | 1 +
src/BigData/301.md | 4 +-
src/BigData/302.md | 4 +-
src/BigData/303.md | 531 +-----------------
src/BigData/304.md | 530 +++++++++++++++++
src/BigData/_sidebar.md | 5 +-
.../images/BigData/Apache-Hive架构图.png | Bin 0 -> 66607 bytes
src/Database/502.md | 8 +-
src/DevOps/602.md | 289 ++++++++--
src/DevOps/603.md | 32 ++
src/DevOps/701.md | 230 +++-----
src/DevOps/702.md | 437 --------------
src/DevOps/801.md | 201 +++++--
src/DevOps/802.md | 440 ++++++++++++++-
src/DevOps/901.md | 58 ++
src/DevOps/902.md | 3 +
src/DevOps/{803.md => 903.md} | 0
src/DevOps/{804.md => 904.md} | 0
src/DevOps/{805.md => 905.md} | 0
src/DevOps/_sidebar.md | 17 +-
.../images/DevOps/滚动发布示意图.jpg | Bin 0 -> 64450 bytes
src/DevOps/images/DevOps/灰度发布.png | Bin 0 -> 58006 bytes
.../images/DevOps/灰度发布示意图.jpg | Bin 0 -> 80665 bytes
.../images/DevOps/蓝绿发布示意图.jpg | Bin 0 -> 100769 bytes
src/Middleware/501.md | 75 ++-
src/Middleware/502.md | 86 +--
src/Middleware/503.md | 435 +++++++++++++-
src/Middleware/504.md | 111 +++-
src/Middleware/506.md | 312 +---------
src/Middleware/507.md | 91 +--
src/Middleware/508.md | 130 ++---
src/Middleware/509.md | 116 +++-
src/Middleware/510.md | 68 +--
src/Middleware/511.md | 176 ++----
src/Middleware/512.md | 161 +++++-
src/Middleware/513.md | 28 +
src/Middleware/_sidebar.md | 25 +-
.../images/Middleware/ChannelHandler.png | Bin 0 -> 23510 bytes
.../Middleware/ChannelOutboundHandler.png | Bin 0 -> 356672 bytes
.../ChannelPipeline入站和出站.png | Bin 0 -> 250212 bytes
.../ChannelPipeline内部结构.png | Bin 0 -> 395678 bytes
.../Middleware/ChannelPipeline结构图.png | Bin 0 -> 976550 bytes
.../ClientServerChannelPipeline.png | Bin 0 -> 83760 bytes
.../EventLoop通用的运行模式.png | Bin 0 -> 225113 bytes
.../Middleware/Netty事件调度层.png | Bin 0 -> 448484 bytes
.../Middleware/Netty内部逻辑的流转.png | Bin 0 -> 62137 bytes
.../images/Middleware/Netty协议.png | Bin 0 -> 327428 bytes
.../Netty异常处理的最佳实践.png | Bin 0 -> 192986 bytes
.../Netty统一异常处理验证.png | Bin 0 -> 366469 bytes
.../images/Middleware/Netty逻辑架构.png | Bin 0 -> 108264 bytes
.../Reactor线程模型运行机制.png | Bin 0 -> 76897 bytes
.../SampleOutBoundHandler执行结果.png | Bin 0 -> 365674 bytes
.../Middleware/主从Reactor多线程.png | Bin 0 -> 127471 bytes
.../images/Middleware/事件处理机制.png | Bin 0 -> 318662 bytes
.../images/Middleware/单Reactor单线程.png | Bin 0 -> 112809 bytes
.../images/Middleware/单Reactor多线程.png | Bin 0 -> 130909 bytes
src/OS/10.md | 32 +-
src/OS/11.md | 36 ++
src/OS/6.md | 75 ++-
src/OS/7.md | 26 +-
src/OS/8.md | 27 +-
src/OS/9.md | 15 +-
src/OS/_sidebar.md | 11 +-
src/OS/images/OS/IO请求流程.png | Bin 0 -> 123522 bytes
src/OS/images/OS/Zero-Copy.png | Bin 0 -> 40550 bytes
src/OS/images/OS/信号驱动IO.png | Bin 0 -> 87221 bytes
src/OS/images/OS/利用DMA技术.png | Bin 0 -> 18366 bytes
src/Open/1.md | 58 ++
src/Open/2.md | 11 +
src/Open/3.md | 11 +
src/Open/4.md | 4 +
src/Open/README.md | 5 +
src/Open/_sidebar.md | 5 +
src/Solution/1108.md | 31 +-
76 files changed, 2863 insertions(+), 2097 deletions(-)
create mode 100644 src/BigData/304.md
create mode 100644 src/BigData/images/BigData/Apache-Hive架构图.png
create mode 100644 src/DevOps/603.md
delete mode 100644 src/DevOps/702.md
create mode 100644 src/DevOps/901.md
create mode 100644 src/DevOps/902.md
rename src/DevOps/{803.md => 903.md} (100%)
rename src/DevOps/{804.md => 904.md} (100%)
rename src/DevOps/{805.md => 905.md} (100%)
create mode 100644 src/DevOps/images/DevOps/滚动发布示意图.jpg
create mode 100644 src/DevOps/images/DevOps/灰度发布.png
create mode 100644 src/DevOps/images/DevOps/灰度发布示意图.jpg
create mode 100644 src/DevOps/images/DevOps/蓝绿发布示意图.jpg
create mode 100644 src/Middleware/513.md
create mode 100644 src/Middleware/images/Middleware/ChannelHandler.png
create mode 100644 src/Middleware/images/Middleware/ChannelOutboundHandler.png
create mode 100644 src/Middleware/images/Middleware/ChannelPipeline入站和出站.png
create mode 100644 src/Middleware/images/Middleware/ChannelPipeline内部结构.png
create mode 100644 src/Middleware/images/Middleware/ChannelPipeline结构图.png
create mode 100644 src/Middleware/images/Middleware/ClientServerChannelPipeline.png
create mode 100644 src/Middleware/images/Middleware/EventLoop通用的运行模式.png
create mode 100644 src/Middleware/images/Middleware/Netty事件调度层.png
create mode 100644 src/Middleware/images/Middleware/Netty内部逻辑的流转.png
create mode 100644 src/Middleware/images/Middleware/Netty协议.png
create mode 100644 src/Middleware/images/Middleware/Netty异常处理的最佳实践.png
create mode 100644 src/Middleware/images/Middleware/Netty统一异常处理验证.png
create mode 100644 src/Middleware/images/Middleware/Netty逻辑架构.png
create mode 100644 src/Middleware/images/Middleware/Reactor线程模型运行机制.png
create mode 100644 src/Middleware/images/Middleware/SampleOutBoundHandler执行结果.png
create mode 100644 src/Middleware/images/Middleware/主从Reactor多线程.png
create mode 100644 src/Middleware/images/Middleware/事件处理机制.png
create mode 100644 src/Middleware/images/Middleware/单Reactor单线程.png
create mode 100644 src/Middleware/images/Middleware/单Reactor多线程.png
create mode 100644 src/OS/11.md
create mode 100644 src/OS/images/OS/IO请求流程.png
create mode 100644 src/OS/images/OS/Zero-Copy.png
create mode 100644 src/OS/images/OS/信号驱动IO.png
create mode 100644 src/OS/images/OS/利用DMA技术.png
create mode 100644 src/Open/1.md
create mode 100644 src/Open/2.md
create mode 100644 src/Open/3.md
create mode 100644 src/Open/4.md
create mode 100644 src/Open/README.md
create mode 100644 src/Open/_sidebar.md
diff --git a/_coverpage.md b/_coverpage.md
index 28b377a..6e7d2da 100644
--- a/_coverpage.md
+++ b/_coverpage.md
@@ -1,10 +1,10 @@

-# lemon 1.0
+# lemon 1.1
> 一款优秀的开源笔记
-- 操作系统、算法、解决方案
-- JAVA、JVM、数据库、中间件
-- 架构、DevOps、大数据
+- JAVA、JVM、数据库、中间件
+- 操作系统、算法、解决方案、学习笔记
+- 架构、DevOps、大数据、开放性问题
[回到博客](https://view6view.club/)
[点击进入](./README.md)
\ No newline at end of file
diff --git a/_navbar.md b/_navbar.md
index 2e29fcf..801e195 100644
--- a/_navbar.md
+++ b/_navbar.md
@@ -9,5 +9,6 @@
* [✍ 大数据](src/BigData/README)
* [✍ DevOps](src/DevOps/README)
* [✍ 解决方案](src/Solution/README)
+ * [✍ 开放性问题](src/Open/README)
* [✍ 科班学习](src/university/README)
* [✍ 其它](src/Others/README)
\ No newline at end of file
diff --git a/_sidebar.md b/_sidebar.md
index 2e29fcf..801e195 100644
--- a/_sidebar.md
+++ b/_sidebar.md
@@ -9,5 +9,6 @@
* [✍ 大数据](src/BigData/README)
* [✍ DevOps](src/DevOps/README)
* [✍ 解决方案](src/Solution/README)
+ * [✍ 开放性问题](src/Open/README)
* [✍ 科班学习](src/university/README)
* [✍ 其它](src/Others/README)
\ No newline at end of file
diff --git a/src/BigData/301.md b/src/BigData/301.md
index 5da7fe8..a855525 100644
--- a/src/BigData/301.md
+++ b/src/BigData/301.md
@@ -1,3 +1 @@
-在Storm中,需要先设计一个实时计算结构,我们称之为拓扑(topology)。之后,这个拓扑结构会被提交给集群,其中主节点(master node)负责给工作节点(worker node)分配代码,工作节点负责执行代码。在一个拓扑结构中,包含spout和bolt两种角色。数据在spouts之间传递,这些spouts将数据流以tuple元组的形式发送;而bolt则负责转换数据流。
-
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/src/BigData/302.md b/src/BigData/302.md
index 07801f1..5da7fe8 100644
--- a/src/BigData/302.md
+++ b/src/BigData/302.md
@@ -1,3 +1,3 @@
-Spark Streaming,即核心Spark API的扩展,不像Storm那样一次处理一个数据流。相反,它在处理数据流之前,会按照时间间隔对数据流进行分段切分。Spark针对连续数据流的抽象,我们称为DStream(Discretized Stream)。 DStream是小批处理的RDD(弹性分布式数据集), RDD则是分布式数据集,可以通过任意函数和滑动数据窗口(窗口计算)进行转换,实现并行操作。
+在Storm中,需要先设计一个实时计算结构,我们称之为拓扑(topology)。之后,这个拓扑结构会被提交给集群,其中主节点(master node)负责给工作节点(worker node)分配代码,工作节点负责执行代码。在一个拓扑结构中,包含spout和bolt两种角色。数据在spouts之间传递,这些spouts将数据流以tuple元组的形式发送;而bolt则负责转换数据流。
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/src/BigData/303.md b/src/BigData/303.md
index 6c59437..07801f1 100644
--- a/src/BigData/303.md
+++ b/src/BigData/303.md
@@ -1,530 +1,3 @@
-Apache Flink是一个**框架**和**分布式处理引擎**,用于在**无界**和**有界**数据流上进行**有状态的计算**。Flink被设计为在所有常见的集群环境中运行,以内存中的速度和任何规模执行计算。
+Spark Streaming,即核心Spark API的扩展,不像Storm那样一次处理一个数据流。相反,它在处理数据流之前,会按照时间间隔对数据流进行分段切分。Spark针对连续数据流的抽象,我们称为DStream(Discretized Stream)。 DStream是小批处理的RDD(弹性分布式数据集), RDD则是分布式数据集,可以通过任意函数和滑动数据窗口(窗口计算)进行转换,实现并行操作。
-
-
-针对流数据+批数据的计算框架。把批数据看作流数据的一种特例,延迟性较低(毫秒级),且能保证消息传输不丢失不重复。
-
-Flink创造性地统一了流处理和批处理,作为流处理看待时输入数据流是无界的,而批处理被作为一种特殊的流处理,只是它的输入数据流被定义为有界的。Flink程序由Stream和Transformation这两个基本构建块组成,其中Stream是一个中间结果数据,而Transformation是一个操作,它对一个或多个输入Stream进行计算处理,输出一个或多个结果Stream。
-
-
-
-### 处理无界和有界数据
-
-数据可以作为无界流或有界流被处理:
-
-- **Unbounded streams**(无界流)有一个起点,但没有定义的终点。它们不会终止,而且会源源不断的提供数据。无边界的流必须被连续地处理,即事件达到后必须被立即处理。等待所有输入数据到达是不可能的,因为输入是无界的,并且在任何时间点都不会完成。处理无边界的数据通常要求以特定顺序(例如,事件发生的顺序)接收事件,以便能够推断出结果的完整性。
-- **Bounded streams**(有界流)有一个定义的开始和结束。在执行任何计算之前,可以通过摄取(提取)所有数据来处理有界流。处理有界流不需要有序摄取,因为有界数据集总是可以排序的。有界流的处理也称为批处理。
-
-
-
-**Apache Flink擅长处理无界和有界数据集**。对时间和状态的精确控制使Flink的运行时能够在无边界的流上运行任何类型的应用程序。有界流由专门为固定大小的数据集设计的算法和数据结构在内部处理,从而产生出色的性能。
-
-
-
-### 分层API
-
-Flink提供了三层API。每个API在简洁性和表达性之间提供了不同的权衡,并且针对不同的使用场景
-
-
-
-
-
-### 应用场景
-
-Apache Flink是开发和运行许多不同类型应用程序的最佳选择,因为它具有丰富的特性。Flink的特性包括支持流和批处理、复杂的状态管理、事件处理语义以及确保状态的一致性。此外,Flink可以部署在各种资源提供程序上,例如YARN、Apache Mesos和Kubernetes,也可以作为裸机硬件上的独立集群进行部署。配置为高可用性,Flink没有单点故障。Flink已经被证明可以扩展到数千个内核和TB级的应用程序状态,提供高吞吐量和低延迟,并支持世界上一些最苛刻的流处理应用程序。
-
-下面是Flink支持的最常见的应用程序类型:
-
-- Event-driven Applications(事件驱动的应用程序)
-- Data Analytics Applications(数据分析应用程序)
-- Data Pipeline Applications(数据管道应用程序)
-
-
-
-#### Event-driven Applications
-
-Event-driven Applications(事件驱动的应用程序)。事件驱动的应用程序是一个有状态的应用程序,它从一个或多个事件流中获取事件,并通过触发计算、状态更新或外部操作对传入的事件作出反应。
-
-事件驱动的应用程序基于有状态的流处理应用程序。在这种设计中,数据和计算被放在一起,从而可以进行本地(内存或磁盘)数据访问。通过定期将检查点写入远程持久存储,可以实现容错。下图描述了传统应用程序体系结构和事件驱动应用程序之间的区别。
-
-
-
-代替查询远程数据库,事件驱动的应用程序在本地访问其数据,从而在吞吐量和延迟方面获得更好的性能。可以定期异步地将检查点同步到远程持久存,而且支持增量同步。不仅如此,在分层架构中,多个应用程序共享同一个数据库是很常见的。因此,数据库的任何更改都需要协调,由于每个事件驱动的应用程序都负责自己的数据,因此更改数据表示或扩展应用程序所需的协调较少。
-
-对于事件驱动的应用程序,Flink的突出特性是savepoint。保存点是一个一致的状态镜像,可以用作兼容应用程序的起点。给定一个保存点,就可以更新或调整应用程序的规模,或者可以启动应用程序的多个版本进行A/B测试。
-
-典型的事件驱动的应用程序有:
-
-- 欺诈检测
-- 异常检测
-- 基于规则的提醒
-- 业务流程监控
-- Web应用(社交网络)
-
-
-
-#### Data Analytics Applications
-
-Data Analytics Applications(数据分析应用程序)。传统上的分析是作为批处理查询或应用程序对已记录事件的有限数据集执行的。为了将最新数据合并到分析结果中,必须将其添加到分析数据集中,然后重新运行查询或应用程序,结果被写入存储系统或作为报告发出。
-
-有了复杂的流处理引擎,分析也可以以实时方式执行。流查询或应用程序不是读取有限的数据集,而是接收实时事件流,并在使用事件时不断地生成和更新结果。结果要么写入外部数据库,要么作为内部状态进行维护。Dashboard应用程序可以从外部数据库读取最新的结果,也可以直接查询应用程序的内部状态。
-
-Apache Flink支持流以及批处理分析应用程序,如下图所示:
-
-
-
-典型的数据分析应用程序有:
-
-- 电信网络质量监控
-- 产品更新分析及移动应用实验评估
-- 消费者技术中实时数据的特别分析
-- 大规模图分析
-
-
-
-#### Data Pipeline Applications
-
-Data Pipeline Applications(数据管道应用程序)。提取-转换-加载(ETL)是在存储系统之间转换和移动数据的常用方法。通常,会定期触发ETL作业,以便将数据从事务性数据库系统复制到分析数据库或数据仓库。
-
-数据管道的作用类似于ETL作业。它们转换和丰富数据,并可以将数据从一个存储系统移动到另一个存储系统。但是,它们以连续流模式运行,而不是周期性地触发。因此,它们能够从不断产生数据的源读取记录,并以低延迟将其移动到目的地。例如,数据管道可以监视文件系统目录中的新文件,并将它们的数据写入事件日志。另一个应用程序可能将事件流物化到数据库,或者增量地构建和完善搜索索引。
-
-下图描述了周期性ETL作业和连续数据管道之间的差异:
-
-
-
-与周期性ETL作业相比,连续数据管道的明显优势是减少了将数据移至其目的地的等待时间。此外,数据管道更通用,可用于更多场景,因为它们能够连续消费和产生数据。
-
-典型的数据管道应用程序有:
-
-- 电商中实时搜索索引的建立
-- 电商中的持续ETL
-
-
-
-### 安装Flink
-
-https://flink.apache.org/downloads.html
-
-下载安装包,这里下载的是 flink-1.10.1-bin-scala_2.11.tgz
-
-安装参考 https://ci.apache.org/projects/flink/flink-docs-release-1.10/getting-started/tutorials/local_setup.html
-
-```shell
-./bin/start-cluster.sh # Start Flink
-```
-
-
-
-访问 http://localhost:8081
-
-
-
-运行 WordCount 示例
-
-
-
-
-
-
-
-
-
-### 商品实时推荐
-
-基于Flink实现的商品实时推荐系统。flink统计商品热度,放入redis缓存,分析日志信息,将画像标签和实时记录放入Hbase。在用户发起推荐请求后,根据用户画像重排序热度榜,并结合协同过滤和标签两个推荐模块为新生成的榜单的每一个产品添加关联产品,最后返回新的用户列表。
-
-#### 系统架构
-
-
-
-在日志数据模块(flink-2-hbase)中,又主要分为6个Flink任务:
-
-- **用户-产品浏览历史 -> 实现基于协同过滤的推荐逻辑**
-
- 通过Flink去记录用户浏览过这个类目下的哪些产品,为后面的基于Item的协同过滤做准备 实时的记录用户的评分到Hbase中,为后续离线处理做准备。数据存储在Hbase的p_history表
-
-- **用户-兴趣 -> 实现基于上下文的推荐逻辑**
-
- 根据用户对同一个产品的操作计算兴趣度,计算规则通过操作间隔时间(如购物 - 浏览 < 100s)则判定为一次兴趣事件 通过Flink的ValueState实现,如果用户的操作Action=3(收藏),则清除这个产品的state,如果超过100s没有出现Action=3的事件,也会清除这个state。数据存储在Hbase的u_interest表
-
-- **用户画像计算 -> 实现基于标签的推荐逻辑**
-
- v1.0按照三个维度去计算用户画像,分别是用户的颜色兴趣,用户的产地兴趣,和用户的风格兴趣.根据日志不断的修改用户画像的数据,记录在Hbase中。数据存储在Hbase的user表
-
-- **产品画像记录 -> 实现基于标签的推荐逻辑**
-
- 用两个维度记录产品画像,一个是喜爱该产品的年龄段,另一个是性别。数据存储在Hbase的prod表
-
-- **事实热度榜 -> 实现基于热度的推荐逻辑**
-
- 通过Flink时间窗口机制,统计当前时间的实时热度,并将数据缓存在Redis中。通过Flink的窗口机制计算实时热度,使用ListState保存一次热度榜。数据存储在redis中,按照时间戳存储list
-
-- **日志导入**
-
- 从Kafka接收的数据直接导入进Hbase事实表,保存完整的日志log,日志中包含了用户Id,用户操作的产品id,操作时间,行为(如购买,点击,推荐等)。数据按时间窗口统计数据大屏需要的数据,返回前段展示。数据存储在Hbase的con表
-
-
-
-#### 推荐引擎逻辑
-
-**基于热度的推荐逻辑**
-
-
-
-根据用户特征,重新排序热度榜,之后根据两种推荐算法计算得到的产品相关度评分,为每个热度榜中的产品推荐几个关联的产品。
-
-
-
-**基于产品画像的产品相似度计算方法**
-
-基于产品画像的推荐逻辑依赖于产品画像和热度榜两个维度,产品画像有三个特征,包含color/country/style三个角度,通过计算用户对该类目产品的评分来过滤热度榜上的产品。
-
-
-
-在已经有产品画像的基础上,计算item与item之间的关联系,通过余弦相似度来计算两两之间的评分,最后在已有物品选中的情况下推荐关联性更高的产品。
-
-| 相似度 | A | B | C |
-| ------ | ---- | ---- | ---- |
-| A | 1 | 0.7 | 0.2 |
-| B | 0.7 | 1 | 0.6 |
-| C | 0.2 | 0.6 | 1 |
-
-
-
-**基于协同过滤的产品相似度计算方法**
-
-根据产品用户表(Hbase) 去计算公式得到相似度评分:
-
-
-
-
-**前台推荐页面**
-
-当前推荐结果分为3列,分别是热度榜推荐,协同过滤推荐和产品画像推荐:
-
-
-
-
-### 实时计算TopN热榜
-
-本案例将实现一个“实时热门商品”的需求,我们可以将“实时热门商品”翻译成程序员更好理解的需求:每隔5分钟输出最近一小时内点击量最多的前 N 个商品。将这个需求进行分解我们大概要做这么几件事情:
-
-- 抽取出业务时间戳,告诉 Flink 框架基于业务时间做窗口
-- 过滤出点击行为数据
-- 按一小时的窗口大小,每5分钟统计一次,做滑动窗口聚合(Sliding Window)
-- 按每个窗口聚合,输出每个窗口中点击量前N名的商品
-
-
-
-#### 数据准备
-
-这里我们准备了一份淘宝用户行为数据集(来自[阿里云天池公开数据集](https://tianchi.aliyun.com/datalab/index.htm))。本数据集包含了淘宝上某一天随机一百万用户的所有行为(包括点击、购买、加购、收藏)。数据集的组织形式和MovieLens-20M类似,即数据集的每一行表示一条用户行为,由用户ID、商品ID、商品类目ID、行为类型和时间戳组成,并以逗号分隔。关于数据集中每一列的详细描述如下:
-
-| 列名称 | 说明 |
-| :--------- | :------------------------------------------------- |
-| 用户ID | 整数类型,加密后的用户ID |
-| 商品ID | 整数类型,加密后的商品ID |
-| 商品类目ID | 整数类型,加密后的商品所属类目ID |
-| 行为类型 | 字符串,枚举类型,包括(‘pv’, ‘buy’, ‘cart’, ‘fav’) |
-| 时间戳 | 行为发生的时间戳,单位秒 |
-
-你可以通过下面的命令下载数据集到项目的 `resources` 目录下:
-
-```shell
-$ cd my-flink-project/src/main/resources
-$ curl https://raw.githubusercontent.com/wuchong/my-flink-project/master/src/main/resources/UserBehavior.csv > UserBehavior.csv
-```
-
-这里是否使用 curl 命令下载数据并不重要,你也可以使用 wget 命令或者直接访问链接下载数据。关键是,**将数据文件保存到项目的 `resources` 目录下**,方便应用程序访问。
-
-
-
-#### 编写程序
-
-
-
-#### 创建模拟数据源
-
-我们先创建一个 `UserBehavior` 的 POJO 类(所有成员变量声明成`public`便是POJO类),强类型化后能方便后续的处理。
-
-```java
-/**
- * 用户行为数据结构
- **/
-public static class UserBehavior {
- public long userId; // 用户ID
- public long itemId; // 商品ID
- public int categoryId; // 商品类目ID
- public String behavior; // 用户行为, 包括("pv", "buy", "cart", "fav")
- public long timestamp; // 行为发生的时间戳,单位秒
-}
-```
-
-接下来我们就可以创建一个 `PojoCsvInputFormat` 了, 这是一个读取 csv 文件并将每一行转成指定 POJO
-类型(在我们案例中是 `UserBehavior`)的输入器。
-
-```java
-// UserBehavior.csv 的本地文件路径
-URL fileUrl = HotItems2.class.getClassLoader().getResource("UserBehavior.csv");
-Path filePath = Path.fromLocalFile(new File(fileUrl.toURI()));
-// 抽取 UserBehavior 的 TypeInformation,是一个 PojoTypeInfo
-PojoTypeInfo pojoType = (PojoTypeInfo) TypeExtractor.createTypeInfo(UserBehavior.class);
-// 由于 Java 反射抽取出的字段顺序是不确定的,需要显式指定下文件中字段的顺序
-String[] fieldOrder = new String[]{"userId", "itemId", "categoryId", "behavior", "timestamp"};
-// 创建 PojoCsvInputFormat
-PojoCsvInputFormat csvInput = new PojoCsvInputFormat<>(filePath, pojoType, fieldOrder);
-```
-
-下一步我们用 `PojoCsvInputFormat` 创建输入源。
-
-```java
-DataStream dataSource = env.createInput(csvInput, pojoType);
-```
-
-这就创建了一个 `UserBehavior` 类型的 `DataStream`。
-
-
-
-#### EventTime与Watermark
-
-当我们说“统计过去一小时内点击量”,这里的“一小时”是指什么呢? 在 Flink 中它可以是指 ProcessingTime ,也可以是 EventTime,由用户决定。
-
-- **ProcessingTime**:**事件被处理的时间**。也就是由机器的系统时间来决定
-- **EventTime**:**事件发生的时间**。一般就是数据本身携带的时间
-
-在本案例中,我们需要统计业务时间上的每小时的点击量,所以要基于 EventTime 来处理。那么如果让 Flink 按照我们想要的业务时间来处理呢?这里主要有两件事情要做:
-
-- 告诉 Flink 我们现在按照 EventTime 模式进行处理,Flink 默认使用 ProcessingTime 处理,所以我们要显式设置下。
-
- ```java
- env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime);
- ```
-
-- 指定如何获得业务时间,以及生成 Watermark。Watermark 是用来追踪业务事件的概念,可以理解成 EventTime 世界中的时钟,用来指示当前处理到什么时刻的数据了。由于我们的数据源的数据已经经过整理,没有乱序,即事件的时间戳是单调递增的,所以可以将每条数据的业务时间就当做 Watermark。这里我们用 `AscendingTimestampExtractor` 来实现时间戳的抽取和 Watermark 的生成。
-
-
-
-**注意**:真实业务场景一般都是存在乱序的,所以一般使用 `BoundedOutOfOrdernessTimestampExtractor`。
-
-```java
-DataStream timedData = dataSource
- .assignTimestampsAndWatermarks(new AscendingTimestampExtractor() {
- @Override
- public long extractAscendingTimestamp(UserBehavior userBehavior) {
- // 原始数据单位秒,将其转成毫秒
- return userBehavior.timestamp * 1000;
- }
- });
-```
-
-这样我们就得到了一个带有时间标记的数据流了,后面就能做一些窗口的操作。
-
-
-
-#### 过滤出点击事件
-
-在开始窗口操作之前,先回顾下需求“每隔5分钟输出过去一小时内点击量最多的前 N 个商品”。由于原始数据中存在点击、加购、购买、收藏各种行为的数据,但是我们只需要统计点击量,所以先使用 `FilterFunction` 将点击行为数据过滤出来。
-
-```java
-DataStream pvData = timedData
- .filter(new FilterFunction() {
- @Override
- public boolean filter(UserBehavior userBehavior) throws Exception {
- // 过滤出只有点击的数据
- return userBehavior.behavior.equals("pv");
- }
- });
-```
-
-
-
-#### 窗口统计点击量
-
-由于要每隔5分钟统计一次最近一小时每个商品的点击量,所以窗口大小是一小时,每隔5分钟滑动一次。即分别要统计 [09:00, 10:00), [09:05, 10:05), [09:10, 10:10)… 等窗口的商品点击量。是一个常见的滑动窗口需求(Sliding Window)。
-
-```java
-DataStream windowedData = pvData
- // 对商品进行分组
- .keyBy("itemId")
- // 对每个商品做滑动窗口(1小时窗口,5分钟滑动一次)
- .timeWindow(Time.minutes(60), Time.minutes(5))
- // 做增量的聚合操作,它能使用AggregateFunction提前聚合掉数据,减少 state 的存储压力
- .aggregate(new CountAgg(), new WindowResultFunction());
-```
-
-
-
-**CountAgg**
-
-这里的`CountAgg`实现了`AggregateFunction`接口,功能是统计窗口中的条数,即遇到一条数据就加一。
-
-```java
-/**
- * COUNT 统计的聚合函数实现,每出现一条记录加一
- **/
-public static class CountAgg implements AggregateFunction {
- @Override
- public Long createAccumulator() {
- return 0L;
- }
-
- @Override
- public Long add(UserBehavior userBehavior, Long acc) {
- return acc + 1;
- }
-
- @Override
- public Long getResult(Long acc) {
- return acc;
- }
-
- @Override
- public Long merge(Long acc1, Long acc2) {
- return acc1 + acc2;
- }
-}
-```
-
-
-
-**WindowFunction**
-
-`.aggregate(AggregateFunction af, WindowFunction wf)` 的第二个参数`WindowFunction`将每个 key每个窗口聚合后的结果带上其他信息进行输出。这里实现的`WindowResultFunction`将主键商品ID,窗口,点击量封装成了`ItemViewCount`进行输出。
-
-```java
-/**
- * 用于输出窗口的结果
- **/
-public static class WindowResultFunction implements WindowFunction {
- @Override
- public void apply(
- Tuple key, // 窗口的主键,即 itemId
- TimeWindow window, // 窗口
- Iterable aggregateResult, // 聚合函数的结果,即 count 值
- Collector collector // 输出类型为 ItemViewCount
- ) throws Exception {
- Long itemId = ((Tuple1) key).f0;
- Long count = aggregateResult.iterator().next();
- collector.collect(ItemViewCount.of(itemId, window.getEnd(), count));
- }
-}
-
-/**
- * 商品点击量(窗口操作的输出类型)
- **/
-public static class ItemViewCount {
- public long itemId; // 商品ID
- public long windowEnd; // 窗口结束时间戳
- public long viewCount; // 商品的点击量
- public static ItemViewCount of(long itemId, long windowEnd, long viewCount) {
- ItemViewCount result = new ItemViewCount();
- result.itemId = itemId;
- result.windowEnd = windowEnd;
- result.viewCount = viewCount;
- return result;
- }
-}
-```
-
-现在我们得到了每个商品在每个窗口的点击量的数据流。
-
-
-
-#### TopN计算最热门商品
-
-为了统计每个窗口下最热门的商品,我们需要再次按窗口进行分组,这里根据`ItemViewCount`中的`windowEnd`进行`keyBy()`操作。然后使用 `ProcessFunction` 实现一个自定义的 TopN 函数 `TopNHotItems` 来计算点击量排名前3名的商品,并将排名结果格式化成字符串,便于后续输出。
-
-```java
-DataStream topItems = windowedData
- .keyBy("windowEnd")
- .process(new TopNHotItems(3)); // 求点击量前3名的商品
-```
-
-`ProcessFunction` 是 Flink 提供的一个 low-level API,用于实现更高级的功能。它主要提供了定时器 timer 的功能(支持EventTime或ProcessingTime)。本案例中我们将利用 timer 来判断何时**收齐**了某个 window 下所有商品的点击量数据。由于 Watermark 的进度是全局的,
-
-在 `processElement` 方法中,每当收到一条数据(`ItemViewCount`),我们就注册一个 `windowEnd+1` 的定时器(Flink 框架会自动忽略同一时间的重复注册)。`windowEnd+1` 的定时器被触发时,意味着收到了`windowEnd+1`的 Watermark,即收齐了该`windowEnd`下的所有商品窗口统计值。我们在 `onTimer()` 中处理将收集的所有商品及点击量进行排序,选出 TopN,并将排名信息格式化成字符串后进行输出。
-
-这里我们还使用了 `ListState` 来存储收到的每条 `ItemViewCount` 消息,保证在发生故障时,状态数据的不丢失和一致性。`ListState` 是 Flink 提供的类似 Java `List` 接口的 State API,它集成了框架的 checkpoint 机制,自动做到了 exactly-once 的语义保证。
-
-```java
-/**
- * 求某个窗口中前 N 名的热门点击商品,key 为窗口时间戳,输出为 TopN 的结果字符串
- **/
-public static class TopNHotItems extends KeyedProcessFunction {
- private final int topSize;
- public TopNHotItems(int topSize) {
- this.topSize = topSize;
- }
-
- // 用于存储商品与点击数的状态,待收齐同一个窗口的数据后,再触发 TopN 计算
- private ListState itemState;
-
- @Override
- public void open(Configuration parameters) throws Exception {
- super.open(parameters);
- // 状态的注册
- ListStateDescriptor itemsStateDesc = new ListStateDescriptor<>("itemState-state", ItemViewCount.class);
- itemState = getRuntimeContext().getListState(itemsStateDesc);
- }
-
- @Override
- public void processElement(ItemViewCount input, Context context, Collector collector) throws Exception {
- // 每条数据都保存到状态中
- itemState.add(input);
- // 注册 windowEnd+1 的 EventTime Timer, 当触发时,说明收齐了属于windowEnd窗口的所有商品数据
- context.timerService().registerEventTimeTimer(input.windowEnd + 1);
- }
-
- @Override
- public void onTimer(long timestamp, OnTimerContext ctx, Collector out) throws Exception {
- // 获取收到的所有商品点击量
- List allItems = new ArrayList<>();
- for (ItemViewCount item : itemState.get()) {
- allItems.add(item);
- }
- // 提前清除状态中的数据,释放空间
- itemState.clear();
- // 按照点击量从大到小排序
- allItems.sort(new Comparator() {
- @Override
- public int compare(ItemViewCount o1, ItemViewCount o2) {
- return (int) (o2.viewCount - o1.viewCount);
- }
- });
-
- // 将排名信息格式化成 String, 便于打印
- StringBuilder result = new StringBuilder();
- result.append("====================================\n");
- result.append("时间: ").append(new Timestamp(timestamp-1)).append("\n");
- for (int i=0;i 实现基于协同过滤的推荐逻辑**
+
+ 通过Flink去记录用户浏览过这个类目下的哪些产品,为后面的基于Item的协同过滤做准备 实时的记录用户的评分到Hbase中,为后续离线处理做准备。数据存储在Hbase的p_history表
+
+- **用户-兴趣 -> 实现基于上下文的推荐逻辑**
+
+ 根据用户对同一个产品的操作计算兴趣度,计算规则通过操作间隔时间(如购物 - 浏览 < 100s)则判定为一次兴趣事件 通过Flink的ValueState实现,如果用户的操作Action=3(收藏),则清除这个产品的state,如果超过100s没有出现Action=3的事件,也会清除这个state。数据存储在Hbase的u_interest表
+
+- **用户画像计算 -> 实现基于标签的推荐逻辑**
+
+ v1.0按照三个维度去计算用户画像,分别是用户的颜色兴趣,用户的产地兴趣,和用户的风格兴趣.根据日志不断的修改用户画像的数据,记录在Hbase中。数据存储在Hbase的user表
+
+- **产品画像记录 -> 实现基于标签的推荐逻辑**
+
+ 用两个维度记录产品画像,一个是喜爱该产品的年龄段,另一个是性别。数据存储在Hbase的prod表
+
+- **事实热度榜 -> 实现基于热度的推荐逻辑**
+
+ 通过Flink时间窗口机制,统计当前时间的实时热度,并将数据缓存在Redis中。通过Flink的窗口机制计算实时热度,使用ListState保存一次热度榜。数据存储在redis中,按照时间戳存储list
+
+- **日志导入**
+
+ 从Kafka接收的数据直接导入进Hbase事实表,保存完整的日志log,日志中包含了用户Id,用户操作的产品id,操作时间,行为(如购买,点击,推荐等)。数据按时间窗口统计数据大屏需要的数据,返回前段展示。数据存储在Hbase的con表
+
+
+
+#### 推荐引擎逻辑
+
+**基于热度的推荐逻辑**
+
+
+
+根据用户特征,重新排序热度榜,之后根据两种推荐算法计算得到的产品相关度评分,为每个热度榜中的产品推荐几个关联的产品。
+
+
+
+**基于产品画像的产品相似度计算方法**
+
+基于产品画像的推荐逻辑依赖于产品画像和热度榜两个维度,产品画像有三个特征,包含color/country/style三个角度,通过计算用户对该类目产品的评分来过滤热度榜上的产品。
+
+
+
+在已经有产品画像的基础上,计算item与item之间的关联系,通过余弦相似度来计算两两之间的评分,最后在已有物品选中的情况下推荐关联性更高的产品。
+
+| 相似度 | A | B | C |
+| ------ | ---- | ---- | ---- |
+| A | 1 | 0.7 | 0.2 |
+| B | 0.7 | 1 | 0.6 |
+| C | 0.2 | 0.6 | 1 |
+
+
+
+**基于协同过滤的产品相似度计算方法**
+
+根据产品用户表(Hbase) 去计算公式得到相似度评分:
+
+
+
+
+**前台推荐页面**
+
+当前推荐结果分为3列,分别是热度榜推荐,协同过滤推荐和产品画像推荐:
+
+
+
+
+### 实时计算TopN热榜
+
+本案例将实现一个“实时热门商品”的需求,我们可以将“实时热门商品”翻译成程序员更好理解的需求:每隔5分钟输出最近一小时内点击量最多的前 N 个商品。将这个需求进行分解我们大概要做这么几件事情:
+
+- 抽取出业务时间戳,告诉 Flink 框架基于业务时间做窗口
+- 过滤出点击行为数据
+- 按一小时的窗口大小,每5分钟统计一次,做滑动窗口聚合(Sliding Window)
+- 按每个窗口聚合,输出每个窗口中点击量前N名的商品
+
+
+
+#### 数据准备
+
+这里我们准备了一份淘宝用户行为数据集(来自[阿里云天池公开数据集](https://tianchi.aliyun.com/datalab/index.htm))。本数据集包含了淘宝上某一天随机一百万用户的所有行为(包括点击、购买、加购、收藏)。数据集的组织形式和MovieLens-20M类似,即数据集的每一行表示一条用户行为,由用户ID、商品ID、商品类目ID、行为类型和时间戳组成,并以逗号分隔。关于数据集中每一列的详细描述如下:
+
+| 列名称 | 说明 |
+| :--------- | :------------------------------------------------- |
+| 用户ID | 整数类型,加密后的用户ID |
+| 商品ID | 整数类型,加密后的商品ID |
+| 商品类目ID | 整数类型,加密后的商品所属类目ID |
+| 行为类型 | 字符串,枚举类型,包括(‘pv’, ‘buy’, ‘cart’, ‘fav’) |
+| 时间戳 | 行为发生的时间戳,单位秒 |
+
+你可以通过下面的命令下载数据集到项目的 `resources` 目录下:
+
+```shell
+$ cd my-flink-project/src/main/resources
+$ curl https://raw.githubusercontent.com/wuchong/my-flink-project/master/src/main/resources/UserBehavior.csv > UserBehavior.csv
+```
+
+这里是否使用 curl 命令下载数据并不重要,你也可以使用 wget 命令或者直接访问链接下载数据。关键是,**将数据文件保存到项目的 `resources` 目录下**,方便应用程序访问。
+
+
+
+#### 编写程序
+
+
+
+#### 创建模拟数据源
+
+我们先创建一个 `UserBehavior` 的 POJO 类(所有成员变量声明成`public`便是POJO类),强类型化后能方便后续的处理。
+
+```java
+/**
+ * 用户行为数据结构
+ **/
+public static class UserBehavior {
+ public long userId; // 用户ID
+ public long itemId; // 商品ID
+ public int categoryId; // 商品类目ID
+ public String behavior; // 用户行为, 包括("pv", "buy", "cart", "fav")
+ public long timestamp; // 行为发生的时间戳,单位秒
+}
+```
+
+接下来我们就可以创建一个 `PojoCsvInputFormat` 了, 这是一个读取 csv 文件并将每一行转成指定 POJO
+类型(在我们案例中是 `UserBehavior`)的输入器。
+
+```java
+// UserBehavior.csv 的本地文件路径
+URL fileUrl = HotItems2.class.getClassLoader().getResource("UserBehavior.csv");
+Path filePath = Path.fromLocalFile(new File(fileUrl.toURI()));
+// 抽取 UserBehavior 的 TypeInformation,是一个 PojoTypeInfo
+PojoTypeInfo pojoType = (PojoTypeInfo) TypeExtractor.createTypeInfo(UserBehavior.class);
+// 由于 Java 反射抽取出的字段顺序是不确定的,需要显式指定下文件中字段的顺序
+String[] fieldOrder = new String[]{"userId", "itemId", "categoryId", "behavior", "timestamp"};
+// 创建 PojoCsvInputFormat
+PojoCsvInputFormat csvInput = new PojoCsvInputFormat<>(filePath, pojoType, fieldOrder);
+```
+
+下一步我们用 `PojoCsvInputFormat` 创建输入源。
+
+```java
+DataStream dataSource = env.createInput(csvInput, pojoType);
+```
+
+这就创建了一个 `UserBehavior` 类型的 `DataStream`。
+
+
+
+#### EventTime与Watermark
+
+当我们说“统计过去一小时内点击量”,这里的“一小时”是指什么呢? 在 Flink 中它可以是指 ProcessingTime ,也可以是 EventTime,由用户决定。
+
+- **ProcessingTime**:**事件被处理的时间**。也就是由机器的系统时间来决定
+- **EventTime**:**事件发生的时间**。一般就是数据本身携带的时间
+
+在本案例中,我们需要统计业务时间上的每小时的点击量,所以要基于 EventTime 来处理。那么如果让 Flink 按照我们想要的业务时间来处理呢?这里主要有两件事情要做:
+
+- 告诉 Flink 我们现在按照 EventTime 模式进行处理,Flink 默认使用 ProcessingTime 处理,所以我们要显式设置下。
+
+ ```java
+ env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime);
+ ```
+
+- 指定如何获得业务时间,以及生成 Watermark。Watermark 是用来追踪业务事件的概念,可以理解成 EventTime 世界中的时钟,用来指示当前处理到什么时刻的数据了。由于我们的数据源的数据已经经过整理,没有乱序,即事件的时间戳是单调递增的,所以可以将每条数据的业务时间就当做 Watermark。这里我们用 `AscendingTimestampExtractor` 来实现时间戳的抽取和 Watermark 的生成。
+
+
+
+**注意**:真实业务场景一般都是存在乱序的,所以一般使用 `BoundedOutOfOrdernessTimestampExtractor`。
+
+```java
+DataStream timedData = dataSource
+ .assignTimestampsAndWatermarks(new AscendingTimestampExtractor() {
+ @Override
+ public long extractAscendingTimestamp(UserBehavior userBehavior) {
+ // 原始数据单位秒,将其转成毫秒
+ return userBehavior.timestamp * 1000;
+ }
+ });
+```
+
+这样我们就得到了一个带有时间标记的数据流了,后面就能做一些窗口的操作。
+
+
+
+#### 过滤出点击事件
+
+在开始窗口操作之前,先回顾下需求“每隔5分钟输出过去一小时内点击量最多的前 N 个商品”。由于原始数据中存在点击、加购、购买、收藏各种行为的数据,但是我们只需要统计点击量,所以先使用 `FilterFunction` 将点击行为数据过滤出来。
+
+```java
+DataStream pvData = timedData
+ .filter(new FilterFunction() {
+ @Override
+ public boolean filter(UserBehavior userBehavior) throws Exception {
+ // 过滤出只有点击的数据
+ return userBehavior.behavior.equals("pv");
+ }
+ });
+```
+
+
+
+#### 窗口统计点击量
+
+由于要每隔5分钟统计一次最近一小时每个商品的点击量,所以窗口大小是一小时,每隔5分钟滑动一次。即分别要统计 [09:00, 10:00), [09:05, 10:05), [09:10, 10:10)… 等窗口的商品点击量。是一个常见的滑动窗口需求(Sliding Window)。
+
+```java
+DataStream windowedData = pvData
+ // 对商品进行分组
+ .keyBy("itemId")
+ // 对每个商品做滑动窗口(1小时窗口,5分钟滑动一次)
+ .timeWindow(Time.minutes(60), Time.minutes(5))
+ // 做增量的聚合操作,它能使用AggregateFunction提前聚合掉数据,减少 state 的存储压力
+ .aggregate(new CountAgg(), new WindowResultFunction());
+```
+
+
+
+**CountAgg**
+
+这里的`CountAgg`实现了`AggregateFunction`接口,功能是统计窗口中的条数,即遇到一条数据就加一。
+
+```java
+/**
+ * COUNT 统计的聚合函数实现,每出现一条记录加一
+ **/
+public static class CountAgg implements AggregateFunction {
+ @Override
+ public Long createAccumulator() {
+ return 0L;
+ }
+
+ @Override
+ public Long add(UserBehavior userBehavior, Long acc) {
+ return acc + 1;
+ }
+
+ @Override
+ public Long getResult(Long acc) {
+ return acc;
+ }
+
+ @Override
+ public Long merge(Long acc1, Long acc2) {
+ return acc1 + acc2;
+ }
+}
+```
+
+
+
+**WindowFunction**
+
+`.aggregate(AggregateFunction af, WindowFunction wf)` 的第二个参数`WindowFunction`将每个 key每个窗口聚合后的结果带上其他信息进行输出。这里实现的`WindowResultFunction`将主键商品ID,窗口,点击量封装成了`ItemViewCount`进行输出。
+
+```java
+/**
+ * 用于输出窗口的结果
+ **/
+public static class WindowResultFunction implements WindowFunction {
+ @Override
+ public void apply(
+ Tuple key, // 窗口的主键,即 itemId
+ TimeWindow window, // 窗口
+ Iterable aggregateResult, // 聚合函数的结果,即 count 值
+ Collector collector // 输出类型为 ItemViewCount
+ ) throws Exception {
+ Long itemId = ((Tuple1) key).f0;
+ Long count = aggregateResult.iterator().next();
+ collector.collect(ItemViewCount.of(itemId, window.getEnd(), count));
+ }
+}
+
+/**
+ * 商品点击量(窗口操作的输出类型)
+ **/
+public static class ItemViewCount {
+ public long itemId; // 商品ID
+ public long windowEnd; // 窗口结束时间戳
+ public long viewCount; // 商品的点击量
+ public static ItemViewCount of(long itemId, long windowEnd, long viewCount) {
+ ItemViewCount result = new ItemViewCount();
+ result.itemId = itemId;
+ result.windowEnd = windowEnd;
+ result.viewCount = viewCount;
+ return result;
+ }
+}
+```
+
+现在我们得到了每个商品在每个窗口的点击量的数据流。
+
+
+
+#### TopN计算最热门商品
+
+为了统计每个窗口下最热门的商品,我们需要再次按窗口进行分组,这里根据`ItemViewCount`中的`windowEnd`进行`keyBy()`操作。然后使用 `ProcessFunction` 实现一个自定义的 TopN 函数 `TopNHotItems` 来计算点击量排名前3名的商品,并将排名结果格式化成字符串,便于后续输出。
+
+```java
+DataStream topItems = windowedData
+ .keyBy("windowEnd")
+ .process(new TopNHotItems(3)); // 求点击量前3名的商品
+```
+
+`ProcessFunction` 是 Flink 提供的一个 low-level API,用于实现更高级的功能。它主要提供了定时器 timer 的功能(支持EventTime或ProcessingTime)。本案例中我们将利用 timer 来判断何时**收齐**了某个 window 下所有商品的点击量数据。由于 Watermark 的进度是全局的,
+
+在 `processElement` 方法中,每当收到一条数据(`ItemViewCount`),我们就注册一个 `windowEnd+1` 的定时器(Flink 框架会自动忽略同一时间的重复注册)。`windowEnd+1` 的定时器被触发时,意味着收到了`windowEnd+1`的 Watermark,即收齐了该`windowEnd`下的所有商品窗口统计值。我们在 `onTimer()` 中处理将收集的所有商品及点击量进行排序,选出 TopN,并将排名信息格式化成字符串后进行输出。
+
+这里我们还使用了 `ListState` 来存储收到的每条 `ItemViewCount` 消息,保证在发生故障时,状态数据的不丢失和一致性。`ListState` 是 Flink 提供的类似 Java `List` 接口的 State API,它集成了框架的 checkpoint 机制,自动做到了 exactly-once 的语义保证。
+
+```java
+/**
+ * 求某个窗口中前 N 名的热门点击商品,key 为窗口时间戳,输出为 TopN 的结果字符串
+ **/
+public static class TopNHotItems extends KeyedProcessFunction {
+ private final int topSize;
+ public TopNHotItems(int topSize) {
+ this.topSize = topSize;
+ }
+
+ // 用于存储商品与点击数的状态,待收齐同一个窗口的数据后,再触发 TopN 计算
+ private ListState itemState;
+
+ @Override
+ public void open(Configuration parameters) throws Exception {
+ super.open(parameters);
+ // 状态的注册
+ ListStateDescriptor itemsStateDesc = new ListStateDescriptor<>("itemState-state", ItemViewCount.class);
+ itemState = getRuntimeContext().getListState(itemsStateDesc);
+ }
+
+ @Override
+ public void processElement(ItemViewCount input, Context context, Collector collector) throws Exception {
+ // 每条数据都保存到状态中
+ itemState.add(input);
+ // 注册 windowEnd+1 的 EventTime Timer, 当触发时,说明收齐了属于windowEnd窗口的所有商品数据
+ context.timerService().registerEventTimeTimer(input.windowEnd + 1);
+ }
+
+ @Override
+ public void onTimer(long timestamp, OnTimerContext ctx, Collector out) throws Exception {
+ // 获取收到的所有商品点击量
+ List allItems = new ArrayList<>();
+ for (ItemViewCount item : itemState.get()) {
+ allItems.add(item);
+ }
+ // 提前清除状态中的数据,释放空间
+ itemState.clear();
+ // 按照点击量从大到小排序
+ allItems.sort(new Comparator() {
+ @Override
+ public int compare(ItemViewCount o1, ItemViewCount o2) {
+ return (int) (o2.viewCount - o1.viewCount);
+ }
+ });
+
+ // 将排名信息格式化成 String, 便于打印
+ StringBuilder result = new StringBuilder();
+ result.append("====================================\n");
+ result.append("时间: ").append(new Timestamp(timestamp-1)).append("\n");
+ for (int i=0;im1(q`~I1`d7inO
z3og1(Z|S{j)n2QrnlJ@9aU^&=crY+9BuNQTB``2R9OzFP767^f1a8iQff0jAiVCZ^
zgP&wV8DNNG54hc*IbNcEMFS|pqS80DYWpWC)pHd#)qjImt@svh6`?G!LeW%TU&z4N
z4ae9Gn2KM0T}yALPWmypy34R>YjVTs^_84C7^7$E8os{sJ-MnjXd
zAP0yN17UG&2?gD6X6M9fa3d7G30!Zw0y-H5(c>Q%GFLmk+??b$85f%;!=eU6ej;8P
z$KkyC23e6Pw6b0YoVR$GyBipC;XF;9)7RstFPKwLx5bNpIT$}rndU68U3fHemZY+X
z0kuW^q+m9P?Li6Cp
z)Z5lCmX0?{Qz3w?oEO`$v9^wr0j%zdz{ul8IO9(iMsg%t@qD~NJETP8jCAA*I(Rub
zYU};+aAuNVyi98P)UaDftra?(H84yvIGTZo=O%|Xru}M=xnI1$#iu2AwY+u=4gkyX
z%nSw9c-Ppud(|swv16OKL#-@I>;)HVZN_959tGPL;7{VcKkD)GX3i7E4n1KdkX>hq
zZ#TKq`sfZHO-Z8naQuC__%nAw_si_E>p`}GF5VC
z3CN0QAtjroqgpXsQoy`K*Ai2cw*%9&v$5>`%h_%D!nvT|J+2%-2MuPfPQ*_R6AsI1I=L?7Pa@2j#e`;!YF|LwV!ll60n1wJl&cj6Zt3e3=Ya^&8j-t@mo}EaN1*
z3TnvJ_fy9$LwQywYAdA?taYP%-$B~Sn;I;gElZF+FI5uz3G!|Qq_Ib4$w#z=XyBx=
znUDpo$AVQZH^&%?{Q^x44t+`)UkID#t(HjbFWXjgI|WfJ`VzR#Zq6p-pZxV0j$6eE
zQ=hiOkH5iK>vA{HcwLtHzD}35vUh0Iy6<-_XS-7*%j2xN{mut?E%0+4%f9PG
zF872HBV23T4zVlVeJ$&MzXqPXzn(6-PRD9RPr5MTaU*F0B_ToXvmh9ZJ+3$yt(gR{
zBr3GMb$Fa?bUl{6ALW$5wP1~_q|(&vwQxS6@w7GmlK3{LoxeWKNI=X{fRD?Kkhd%~GhnB2cN)Jca$|73EV&9$X7f4pt*
zqva`6xUOvq24wy4;I)m1a>Rf=5upofKF7HeEQ2sg+o@KVWL_S@OXl5Z<=Z3%>G9NB
zw}s8(N?=o8<}#tD7(5<tR2rFy}zz3W<>}z7(Dzr&Q8FusSw}nYz%@}
zYd-YHrQ=DqJ^aMu-0xeJh2Of3XtAs%oBqJ0gM;_9e=nPMw6z_TF;8g~^0#>OGvOz2
zI?~vuV)JjB`4y<($5aOTGzF>`H*l>YDEziFTf=9%X3ZN++G-F>F&N)|DL=Cx@Yi>k
z!8@rP>o&+;S%QBraHIhmt|X|9%iXzhz>V!cSsi!28_s+e$8Iplo|v+y>KKi!@u5q9
zn#>IbmPAB(8s6NBFGUH|eP2Ci8otx#tN(p{+KF+pJk78?K$yLBHd4shhBt5qI<03$
zl9}{Gb;5q##N2;=;A~Vhu+E_8;^8SMqtfupRYVT!@&cXJzD(XbsjxfD;H~f^m9%n?
zz7xMi?ys{E_^A>;uhybJ(i)x@W5Bd^-0_-r~#46HUy|C*%JS_bFeO^g<3mbF_KxEQ<^xCCsdV
zHFo>Cq0W>V-rnAivGXJljqsnxbokHF
z^;J|G{QhuDcffT#Y!8g_$}i$!JzB=P_G$a5@Y@|-(`LtlEAQJqU0=0(sml|sHM<_J
zlx}w%EES5sqm1y3Fp-i`ba!`$5q&MKnPU0mjt^dT&=G~KO9t4J7t+rAR-U%qxyW^j
z-ClH;5F1wnZcQ}ygjn*&X7b%>}_Hc&)vcFZaI*uK#mKrmqNbtfHwLXLd75V}!s{QZyJMa?1RboNge9i_Vp$IiXESM*
z6Y{$54R7l1Z?P2wF03ALP0{mmQ;5JHpFS
z0Ey}`?3&t8_q9EByMO$}WHt*bKhvAm_hogHelnn6#pNoE>lpTec)N;M
ze*Yh)&MbR4^3yHr@Z|cjy;Myo7Ibq^4?qrF8c8=I`{41Y;G`Eskb1z_TcXWh>LA%x@W
z|Ls-?vNjTI$Tcaq5X3D1ZDh;=9(OuQDYg0OpFTlz`2w1YW=IkMMD_leOY9Pe@3E#+
z2aEszFcAea;;z?cltD-6mtWX)Bnp*Us^a1>(xWmeDzn*sZ{OUiD3S;ZQ(-lD0{s2`
zjrzi`UY_@Yl4l+fS@ELqqWYJXmd3_TeaNWZu_TE3_+DlY>k6flG2B<^>0kz8vP)D-
zjee1Sqo-R+&;l(oeP-8Oxw_!{)`(<$(M3jqQoA~@+f#)N94A}-JGbxjzyb=!dJhpW
zN$-j~%Qx$K@R(F8kKQ4UxSU?9F)@9iuW$FJnYqbj^Ybj9V+J+F$AXtr5f5EecLxda
zN9C0S?njfueeF-=^{GYr!obp1i#WkcMC6^bd9zdZ{;
zJN_=-8>9@JO>?mS;a8cCJZaecvD*
znZUi>^W?6HfdHm1624zwUM^&QS{I8VJB%2dlvihB5CxO3(_Cr*Kh4kN1dFTSY18w$
zU2H2le35sn3Qi`Y$o|l)-tOUfMqiBOJZI>D21PkvdxPXF(W;|}+$0-ViV#wFOIu8NKGKdLY}eDja$5AB@QA$rawyMpBd)86_3_hpW62-
zP-N?A!pow(D8?l8nS}KZ`owg!3!0F_b{AjpFATL`!VkEH5SUUNdyyRfAZZ@eKpw|^
zoLU}_jbQCL^_pm2DR4E8-J?)pECP(JY$M9-Td=KaDoa&*u#yM}dGLVL=Xwv6r4W^0
zrEz)Y2mb)&1CsM-?t)9>%k_!}|3%!#!{k63`$_QspcLGRQmEEuyimmAzi5vQ1c4Hs
zz!}uRKd9`=0Lj#Jr$dDghx!k+3y49$2EY)a{s*MYW}rUzba)c}nf}K^G$7|t+$9Y2
z-(g2n!G;>SlaULf{_8FNXBvaffM0%0kET0|>}D>hL|>pJKoe&qkKHU3btIu1{_@Ek
z0lfG1z0q#zaUeDQ6H8m2`etpzR{?XidF9@)33x>fBb$Cr=
zoeaELMjG364*wWO4RA&ceKmVq^q|^~@N4wmEkJ>%zN?#OZJMhNj^Ysvcbl>QqYSlo
zf~_MkN5ePE-|2N9HqMys2Ci-{?e}Wx{aNCq_
zd{4W!(R*oH@a;i2j$1DYl0=(1LI(&2Hq4O0h(TJ~?QvD=D{4(a}=LZ(n2L=SSx5sU#
z?Ly`Eu6CVZ#-J}0u*wM9UqK-T1qtUgkVx`BySjY0J+h&jFV*FWlCp@5hz^U241=KP
zk^R4D)Pt^)h=`ch4%bO>^?WJk&n!;+X(z+
zycP>hfp{Z;#rEADM#8~~jMFMKx;Ka>0UgXNHyAbSsLU&^UeZkFKCgha#oE_-YcWF2
zi~rG~rMxW_`knPhwoyz;^lRMJcT{>sELKn2>cPSzuCh-#sDDWt8F4flj*l{P-+k3P
z4S!(;i7n{)I4oa9fV&6~&P@n9K{vOv4{OIK-yP!%Y^zRzo${CRVuwZsW_?diTb5~Pq7S*NeU;bFez{^1FzA{X7~Hybli!AN`%>k{&=`M
ze+A!PoMA1uW5nwCaRv5HfqRwe#KHJ?JRv=Z2VDgxrQPoJA{UapB%8ePS}3|E2KtzZ
z+hc)(pRRTnt3V>u-{sTf)1%1sLuPfg)0B?y3~HV8jsn{MR)^vH{`0MtnKv-W(J2h`
zG%4KFgp+b~#>vg8`4tz92GE=;N(R_$BEO}s&*N6pKjX7hVl%2bIv%0o`e5__%V$h%$D{te=`;4qG@IHjjCfoHo%oy0h2xh(dp
zLeXh1(QelHWGxFaJP@`uW0{F@nSn>%~@+yync3x4Y{+_>IW)D
zwmBU>{A$OB7^omnWw41aTg0GIn`V5gVrMmdY#e-kD0FLGlWmLl>~WHpRFDf3I{P>i
zd52RSQ-dK-q+GK*+YOpaXRvJ$GX;HFZvx}1ESA^hM+T;TL%oIT@|$&!JN(5HlIW}d#%OgyjkyfPkWF`
zzNdi7w_R=ZY0&(<2$4;_knhJzdD}74Tj84j&>tpUKh&|MRu33RPs%7Uk$cYgHdd`O
zU95HUvYD2BmKtSDa}L|};5T(!Q5;?Gh=*p%S=0&ugGnHW>?D8~x$;#G*@sRzS$PRu
zc~OGNsdaOac4eH)Jq)Lfu^WQ#7|NhH>lkm>CAyzQI*8W$7SxW$KZOjU3iDF_#y~CU
zBv8aZRHz^J?<#kr&-Hk|Sr3_!$No!jkQK*!j)#d|VU}@IjmuHwtHa!L^r6C+xe}-*
zHk1C2Cd=23vy>;L{%OOxj;zn1)gEi;LSDZqPyLDh`MtsWY%gMp*$5xnwww)_@zEzj
zy+pfHjAVZYRfz|k4cS(JqKwl?=$`BqTGOutqo-II4L`I3VkplZ$2j<7KpGPOn
z$%pQE5yZV6Z11a_Q0AEUo8-s%XmuB(x#Np6m3JMSX6Cal4e}#aJ#k@
z{hOcuZkx@TQ|9cf*BR3?!F2~PWHsB6K3(5kKv(~*+v2)^7xk`&SW@t8*L`5onni*2
z^TB?9A=B$S39_!^O|4Ss!uUzr(x1h#e5!NJzR^H(ps6#bveJj-qyaGFs@(`8^uIf5
z=~vv>_X#$Z$LhzD2MzLK*iKpmWtK_*kRsAzz4kiokim;sV_4TMaM8RNsr-
zU$p=^Mou!vk95c!I@sLX_ueOEhv){c8>pNeuCMTQdr#!XDk#z}%jC!N)i|d>LDO
z!S;np4YKfOpU70|);Ai$MChcTZ5x|h8$wRTrxK-5{%7`^9dr?hn79fT7bm$AhBit=
zEaz}zU?6-%oD>L3NvQrX9T;wpo$o!j%hPj~7$GjaLTW12gIIE%NXPCQ-W&GeOEbd-
zWin(!RvWO4lGPiK#U&gCC=}G#vh8UdXoiWORn|qtCbaPflb&XS{s-YrEcVApk7IWD0Prf#pTlF3p!86~%jktn&H&G6@
z37_ttmhbft@p}?GavdP*;E*B996t}C31j7-XFuPo7*FY47x}2EArTR4tgg1Y<)Hes
zy*4P==FC?c5I~0nmjpf~xK#Lmop&u-`-Oo6@=#b3U}Ilc1O()^*f{Wzim@Q0x_~86
zQSh#iDZ6r&rqZf;L;bVRmG*-1BUG4ldl9F5-3*&-|W|fs9fU0Tw>}dPU~{rWeG(*9=Aaq=Vu?M
ziP3;|vr!_B4&z4(-B-x9?s3UjQa!PDj5!aXA`)s?6mLV4FWn(%DrK;m+41xd0cFAe
ziOY1M!7^*^kwVz>Bpw$PsG#5(%>ti57X3T;CA|ePg#hvMOkQa4vC>
zRegDVYZmOTStZ)1FzTb_M5dRnSNUYef2
zy_x!WaXJU1FWQfD6x}j+eCF-k9T;#uYt8$Ez=(bl_`18S^ttY<&h6{sTm>p76V6IV
z82Xw*D4Z?*Zu(OrLtz1RCWt*5e*&)tR`ahEN$gkoK0F^y?v6aO@wDIOo2tU^tA1PO
z_cDIIBa11QfqNunaiuw$P5UhN1}=XCr9kuFS^)X^z2R~M_Ve|8*P}jNw9FYf(txIX
zL6>Yo#g_1ax*#jL<4ntBrrlU~*>LVZxBCuM>}|%O;Y|)l7jKHDo%r_jJqE|Du&9{AR>*F(De5eJ-gd@ZKW5M+zPx;X`pUEEmG^t
zirq!Au;mhL@3am-f94N__sYyyeWTI9?_^c3tCr6thNwJd`5X)hS?;N>7=T6EZr(+)
z_vVdO7v}9Ir%Mj~s3R4EwUxjcBM-jVqZ&-*7>7WE?Sr#ZeYS^f7X6098wZ!qycYw@
zy(Wp}X1-KsJ-_rRi2{@Zm&Y3nYQo1tP~HmMijVZTt`N0iHj(_h?pXb<{lDt8*i7_%
zMu)EvPPV9nxo6Dn@Bj4eo#Bs;L?gqW6YAZM-9iSh-!aZe7TL1)IY!FR@tgd+?{+5O
z{=3fMnq~({y0|xp$Wv|;iYz@2LS6qe7^5)7h7AQ5%J*U+21KzT4V%&EbOOF+JDatb^u_T*AwVgPPL
zrjYom97faSp@^zGfmeMbP6!xB{CTkPk>R!Z;!|H96)a6KI|`8;>8`onam{HBdKI_O
zvUY*J8z8yri(_*Hy!hRFkZ
z(z<#~GB%;p*t$a0HZNG|QH3gv(6VEhFTw(mE*xOqa`1wm5VIWaFfY3HB>M)@;3>Py
zq5R_i$EpYE0S5D>sg4_LbU8#hYcj$@`WpcgkkIm07l6ebTIWEeXtmE$+)`YYpF)u!
zO}^p%lJw8L1>a8((o|89Q2ag>Vj4e~x9o_QF^k9uuGAE0gNeu+!>6eV_d5sQ9Oq|`
zkXZ^dyB89oT#oy9GpLN9NX&0|-9^Nu)HO7S%*nY8
z1YWdVre>H$#=f$)XdMb8HQ|XE#1W*MwZR{o;SR2J^`Vxm0b!(>sMUE}?msy2(c>ZW
zQO7BCW$oqH?Z_|Swc|&O4b~4hQ}N^1I;Skc<2)niBHBbbGrnQrQ>6VH!E~eoWhz!Q
z&l0RaO9^7I)e<26g{5hoVE*s*U?xZvIhi#p|Nb|((M1Z1qG}u0DCYfpP3(pSippf}
z$~FHdvk^fKio^cu6APmRG-raf{Yv-Za2l@ovTJo_7{l#ze{8xm9JB@;S6jir1sV6b
z$VOL(k22-!k9dm>$C##ed2_5yY$~LHsO^PH-Q%5;j3A5Xu;?rno1@G3H$&FwUFbVl
z{4yCdIa@~kR#$eIO{$nQV_t5gWhzj$ls6m(Ey2U4xXnZ+KZ2xjUVxKc8f6vRx=N@1
zaE?}>vd?Cu{A(1#AkJnx#Tt_o*?z#BWScmKj(0i7{54?(#
zccg<3ltCIuGXe!)iN8bIi&<#?*6;Lfe?m$rjgscgBQch%9Kan}ZuKz?0?^ljA2IkZ
z!8Ak9$z=R`UR`4yfEpE`mCqDF2Pi{0%Lt$O$DBBvc{C>BtU1OyiDAH#4z#35HnQ#)
z6)AjZ2U5UAhVe0Xu0wIXMh_=rTX0$?uQQOXo#S^G_@fxHl#>Y7CwW|Y(E(b?9K9y4
zWY__P;I8@|=!Z~zjtY)V+Ao7dp62$CDH%Y}jz#nL$$;2%8eA7T`WyK#_Q-s97AR%c
zX@XD+USmz@U4saDGcPd>FBo{YucIjhNd-U9)!xV0@aY)2vmD}V$~%{+gG9Tw;BunK
zaswbn1f;4xzn=m9!K?v{{0xRb5e_^r*z9Ds>e`}DSa<{dm*kh@6i_76)z;KT23F;)
zR=iYBaa3Jw-1aC+>8?C^sH-aHWue@8(({
z$H+Mq#P?Z9`DY`r;}51O{vM;I(exfsyZ}9d`jHNx&jLmI`>;t&{&Ve!@_`fO`2^+v
z;Kcm{C;C4U{*yZx9ul|MLXQX?}goa7KpM{%Dpas5=D?ToD!2xdvW9PL9#|
z_&9f#W!xzN6F|zp57j>y0aO@)GBlRXS=C3LhW4SgOi|tMPBhIAvM6Qo_8$PwwqAbppk@MyDLpQPK?xM!=#_wk-#-<+Kj`tYU#U0gisA$FBh7I
z8kIq?2}vWZwFbn|2RJ%q&`Z@SCU8Hj){2abV-{wCUe^tE<8pv;=)&vgx`1`uXgMe{
z4Iaga9*{u^7Kr30Rs_t0?9?Z6g?c&MJDA`su2FaqCFVp08#0|9lO(W9AZJfmbCkma
zX^8!h*66DgNys?7eiO_WYsp&HFghp^$k7b2iEVB5(b@6GggEPcZ(IfV(QL3g3ktTd
za+N?SR7klH`RD@R3qp(%C^#4dRv&TwaGZ?R>(2_%1cq^$|NKa#v9+iICRDU7`09SB
zDC2=PR)Puq03nEnmr1CK!<{|;j=i>VMmusR3pOVTDK9G9^oqCW3khhm>0mcdGJY!3
z54+D+gpxoQPSac9`itp>`jak_FFRAAg40p?f7i(IL5i|*T9RlSD$OWGRdk!iWD_3Mh{)2eQ3$QPGcbkbmx_Ab`i&IMKjKeN+?uV@+;oIoMrm
zVW9*dV>VcV{5#;sklo5n4{ur!y!0Cq|C#UcHT<6tQmz|gPYEw)r`JO20LZ
zqRIE1S#3B_mvl4|!_c5E?MPAm3F9bAdr0Fz
zPR`=MR^M5|yx#H4(M!ve%?S1tBZ117l4`q6$Inqqs93#>VJJy17xp$6Gru5)^4xV9
zh&2!Dr6KhwgnWQiUvgPLKn@4aR*R0`xKt)POnXSYA4k8GP^RAcQ1CrjE~CxnV($31
z1*k3N<0(K|y&c+!EU#i<7{n^#t<%QBqX&aN$(vBA2{fCp0yYv7%@mmE=|IZuIh(oJ
zKJ(K0{X8-utPV%*FdZD=S35jH&giI-RD9mw|1Sj^P+`vxl^L5)7<-Xlm=!KcbNhEl2E%Es*phmaoABF5GuX8!+SVj!;c@
zVTrH7ONit+4>b;l>;fZyptbx7gJ6~jZ`vW%g!!k#c5a`JYw1$`w^)0xiQm2Q_NJ1yP_%8;fnx-yon@!vhR9dXx?`{2l4>K8p)rbE~fLc|h_n
z_gJcP$_oePnxWB`WX0+dG5Y%l^*Gkr1XGSlk7l-T-c0tQEbm1KqT_jL(N0%sx?nfa
z_N_O&DBI0e9_X7mO$Vt)Mo;1r=I0|(2CMB49d~aZjm1JM9YgltWD)@+-PAq3e`$ku
zF4Vv8lhErfIS~fBXIXb17U@J5N`$f(G}M#%&iA5|>OF^prahrGhd!~!+}LYAwnSi(
z-4vU9|Ey0g%;yFh2Y%;sZboy35vk}!VFqb5L=5%k+2iTX=;AIBd7j2f5!ALy^}^-G
z2O=#aj5aO*4U-ZPpN$IvddGI@2Gf1FEMnAcTyQ(a~KDnR_Okeaq(fee{acgG`$=a7fu~D1SbNIf!sV6iR
zMpOvAGd~#ox|yr3Yh-9N(Xs_^Bove#pDT^OXBTjQW|U(`8cPWXLlU-dl#Tw!*J8CpM9
zB{S+gu(1u9$QnBzj39!7xGw|vW%n7~QtylCHyuG2_jmox7QpS#@r-!}2?(_sqnjS8
zP`bJpD1su&R8s@t%l;7i1U=bbShAKf4?e&Q6EW4f8j`*SccN<8Hu3zdSIcv}_N|EV
zl_u@z14YM%Y+Yqd_zlKBmz03{9@VJ%il|!-OjT%MaAsR&CWrcq6>cpS!
zN3_SZKH)QG4QJJv!?2vKox1VC&)%_^s?}uCMh4E2z~a>GDHsl(BsRuVj%vNT#87ad
zm*u!#mM@kZ|6H@YD3=Na@aOnMZ-PG0n3e|7rS`vr{Gj6rOB4*h&n5__3hp!Pc5jX{
zZee`;sLcQJTNGLt(f!}vFqS?gEeRh
zIV01oW6n5G7rpN#$5=n&;w&jaW;#|tOpOb2A;tpW%=uH*?5Pa^YW7j<;S+PVLSZ>Y
zhov0bAnW5jTNq3DXiZ*j9-&t4(^za%wQez+d7S7*uEDyE1kP;q62`@`ic12%9vw#J
zZEo0FMHOx@n8Z8bdvkRWs%FH@6@x@zYcsHpdXlF&3fB0e0jR8+tc
zRWU`tuBs@OR)_FZZXZ7Ik7Bdx1toh49ZqM(BVJtyb{7KTXI9)kE)fJhR0{l*)yOEs
zpBCD)<~BiG+cnUHUHGQdbEsS@s0mSFY*lL4%rtlz!ZaU)C4dGK8~P;(8f-KRETxY&
z!-qhBE~IoyNJ=xVW&r;O=Iww4(9zw$*(v5Am1P=~UUM?1JGa+DHY)0hVs10+@lyeyDy;a8)U5Y-dV4TjF{TP^|l
zFw3DrPaxi3oQ?ZP4AP_1u+)LWN=Qm+a6s)JT*4=q-zG8+zL4}`{AfVU(H_>4in{_O
zU_MA(vp;A_8L5pyj2hGsbpCuWIx8B<;r}qY9RLV_%{j3&5uhJYL9=84&A76VoaXbt
z*RcqoN~Qh4uSN8rR}ccZ>IM*RX-tYM@HS=wpK;b+!l&u`=bNUS`^G~eV!$CWDkkP!
zx!0dA(r+7?Iw@ihMLl^RMNL+eV1h0h?z%};`kkhfOXN!&@qszU8qfiyGo}YOJmv*G
zrHr!I+S+2nG>M?03(|$Ywi9%etCZ{2Ybdmc3<6Lf6f6WH97zA{gQsa*Z}9mSBhhYmDoSQQM2U2;Lqvl%$zgG4;^Q
z#XtvTB1{2A+1&N@o1s`be2n7k_;DcOve!fQvpF3MXQld$tUj@{CK&N(tz;`BT;^=W
zM_b{nG7aplf3q0*bW1~WK+i@e7;|EGN9rwmPen)Oc?W258qo
z8w;uhwgb#c*skr0l>SrcW(*DP4o6B!Ny++kxZQAZX{l6YaVoP8f5>(Gs(A4qF}ftY
zad!%QdwW~@6o1=aL&sLHIa9=)CHSuv{8ye@!TFamNO83sjkiBr+lSyHRtzr_n0X#J
z_Fe6ge`47(xGDf=OeLCXg&_ZL;t<41%mLMC(Boqx4!?gtDmY3s%xGtP+27egOhdw<
z5LWwTU5riK3<}JA=vy{4|Dgb5P3GDLQ{hg-a3<7CwkHa+Fi0?586X(WTrK>=ZjOC?
zL#76GJ&0?A`c?y2vq%0I{nef+L@K*ZyeP#l?%|d`CYcp~ib)Q%pyhA`Rj+}bQvl>F
z!qFLakzR7K7sD6N?a9tSX`278Wmu{n&xX`pE{cXK`~$vBXMa{munf}ZuzcU7KN=2F
z0*IN1t@TkqSNsSFgqSCt$t0M`vL1eV77M~8G9YI?#uKoIlh;uvr)vIX-Dy`E=l2h?
z`cQ>fr;MHUPwdSRNJ6&3>`%Dxf7(%6j1SQUr4r{D|FchypTJLY`&3yR6_eJ<$|)5P
zm%Sk4Q}u?H$7Rwmus|jt+690$4uk!s%Uka;bJjVo>xWZ{ox%4atz!FN2&AOA-g~Jx
zI@c!E45Zr*BV8Kj;=M{mfO~m5uDZOuJb6vkSyZABt}<_RJ>}!)|Dtx|b${0G{TP0T
zv_)`AII8jFG>h#j_x$kS`>+YaofgR{`gXI}X878v=|Vd_UndbJgkrO@`#skRdm@7`
zf9xJ%AGvhb@NxPzBy#_+Mcipdj+}!QCCIW09Epq_e8TVaS+iLzYYkq0
zskioi4*_@O(tGXeBXsJ(6L6SO=O^I5XFSpg{lPmE9%;-wqo5=-FAKIjWDreGMOBPh
zu9l39yf<30N{_!GLX|BKF8@g{B*3tNx$iqqBEHy0k48z&HkCKGH+ex)gNCt4StV6C
z=1B6Z-JBH85Ix+alA)N-%mm7KYTI5~;}fUTzOUwzCP(O(=zxU~e6)jWL
z;r5cQRZE4+?7XkR{9Z8wU3UHU9u=B6S!hWT755jSEVMgf
z-8OULgoAw$lAjNbqp-iDGlu_HL;fGy)V231!NHlH+j-#{lJlaHm!&iy-DipZWnK;=*Sg~1rE$#!r57?
zmZ-pfJhCq_n_`esitWY8Ud#N16EHHS3+#f0Afnl8>qhE?rFk1pow<*4ZJ<+e{uw5T
zHfoA_z(L>E{hS1ctnj(n^KeVKriidOB8F4S;gYB1p@$2u3C3L#yJ`mi`}P(R^8zPT
z*yeFa{b`MqzT3u46eyP`ng!&!ACO#aImmRCx7-OS%%3^DX#pKl|5dxu@+u})&<{-6
zoi5kiRp||ea$MYRv*R`cM)1yMsw54CwpmaaYbqdzm5c*&SOk3RxxqNuJk8&AFx9kj
z4Ef@ZsB@NJQHg*;3aR~pXQh8o4K{MiAQ#uRXrGx3DFDlPNGP9IIAM|X^z=Ar^|0)D
zu;)W3gsr%ltoT;>*`{xcTFALgb|K>vw^IN=5cbH^$MG-jdcwg$9ovhWI
z8>YKvg_7*ep6OEEzVUxO+UfP;h)R^cu^_=8Y7EA}ptweY?8*MyZOJF#(V%JRY0&B(
zs0sV2RJB4MJKtDs8-LoAy|_jbtECPzEEt9)PonGBo(!cYm~qY$^9%-}j+7s>Mf8Lg
zK_G0;*g%~p`Uv_7<@j7;e5&Z&9`}^_uWaQUk3Q;JqFWCc_P=_aQyCFo7PJ7r>d^@$b*z3
zeG6E5zJ0?th6_Yx0hPoFFkz}u1VH>X)&5f8vU{lid9iul^*L+p3B6ThLP|JDd)7WM
z#8c0Nu}M$85tlC+k}3#1mXp)~8j`4O(vJTX@~u{*Q#M9=W@ZNdGi();^dJ*oWa>u8
zny}}@)^?K3;cW`SD&1l()8fii^c8~aDs6V40rYya>vmf7gk3}>;Ndz}I7v9U&rJRI
z+9dD+61u6JLKEd|Q#_~^IdI}7NT>Ic1UI4)YY(M^Wwxg-^z1b=10%c}QT>>?PuZ-i
zf71q|Ob&5L&R!%8biTE6g-HZnd^FKY^S;`G+`Zd5!nPEWjp7utGQfg4UQ6z459=m~
z)1eb9q3($gx84A~9W#7r3<)7pu?YoMETr+lolH%(^&7t`o_1HiKFdXxXp+Uyh?J}l
zmvV}@5%@4-SSyzk;1d)nT5-`ZGLFM$uF&44;WP!hIxY(JeQRiKO$L3EH(Y-@-z7O^
z)6VNUN(pymvKkB-6V|
z#MK=a7}PQH{GZeX9GK&6Y3^YfK)7$rn)mm=A;-rt;w!#IGB!!dL%Hr@{tal=4DZ6N
zWsFbC5&h#_1SFIryVvNQ(XSrQ%EgwtJe>ZvrqMGy*EiMsM&sGsCY~(PZr}GuDZ~>O
zT64Nr8l4}9^`^&OTVq72+c!2~oRe|KhI&lQa?#*2Rhj@aJg9By^=0QoPR`c}g{_Qb
zw83n2HW8jSg%CtT%W@u^lZw%3?hju%eFrKBx$=3&bhD)Jf!a!$=6?Hbjh79|<<=)_
zTPchn{unC;piU+jZ~3mhH^V~acm8N|@FNC4^I)-2xH<;?v(I_YFWK@xnO?N+J~u-@
zO@u`qUecTz`jHTR>-}-x+5ZiZ{@9B-J`
zd`c|P>38_>`I!*|ufbCVN&9XYhX+bBWHAxWkIg(_L@A(uEDd9^&1lZEyLmGP6nUr!
zCfHZ2{^|s>h^Rl*+#|>U`8=eYrEmPF;hsge!6R0m9FmDB$NMjyM0-#;2S+;cvg7wm
z3QA(U+R1o8oK46RR!F=q96OS$ezLhIXV=PSZ&a_^ae-;ujV7q;v~7graE8J2{%B0!
z8TH)nUG{yxa8+f^MqGyf-F-HLef{Psdjm(IYC*mnFXZ;!ouch5>Ks!7c^76(?|0_a
z%!!Nsy+-6FtR;45cvNe{+#e$5HPKmXRQzLrBSt6$M430Bs&pWeiC4spEHMtCi$)9mO>tuIN^
zb(tr}Q}yacMUR8XVk|ho`XCjOY~Y*qpexPP-Z7L-N4SUk>E7$0dpAqA{;xpL*UhvQ
z^94);j06iF>$P|gW=!vm9-FZa$M}eCzK3!dL2d@0Coe)|Z1tJVJdL*M1GQL8_l{Z9W9wPtTYq`IBQI=>-c*|gj9D{ZZ)ww2t%(nmxL2H^Da
z8fYcAxmrIF!Xkwv_O=?fNo{Xw^DSZ*VtZDR7@L^m>Zn@~T({KyS#&7jY5dWVZBCM`^i2)Ze`zm;ysj8XVYvw!U2K%FtTMmBz1T
z2a?-rZ2a)#i}<@FR+^;
zbty57Ykg>TKWaQ0tCZV25BGO>9$=UT2Tx{vRW_Md9!}7|VQCktp*N*u%18BBR}uw?
zS>Nr37WxoW!wdy#)J+QqHLUHAGTMNGW>Gb!IOB+|sFzM70Xh~2z%0t!g1*vI(%-ZN
zQe4yrOvHW`a7oZ;P6IwhD#9YcU`r5P1-{@Q$Xxu}kiRv_^Dt1D=~O9{1#snrV1=C+
z9d)SE5of7sHK#EAJ7D-gYvT9%&QJ7ZNC~_#6B3aUyQ8Idx@#aTrZ&GN}7dXT&
zFdw%ne;WG(;^3+UYO7kDHWO{)uawvT{tK%9!e#zr1pW)QA~{o;)-gf9T~G|LgaG*%
z*y~kM>!o5=qbwsX^|yup8Yq4-sv0KXuXpd;r0YywN0J_#^;c+;gGvPZ?>4_^ksZ)v
zsiaJV=fEyCO4j1Kg$-*o5#j${!v{(e`eh>>*cHhCgSvnhga|m_BQ|_7xU*eC8Ss3T
zFJq>nj$>|C
zs7FPn9QGR=HcX>17|gy#ah-8J(7hBOQZ8IiNH3->v0tv1+uwMyid;`;vvM`j;{TaSBiPAEA~ELdcrC)3pYgGW^<38OW(-N(Gxlebwlo0|b
zjOX48)~4oDJR`*=2+>0du3&)GbE8{9-=?QG-;bBqPnk1n%b7*sr!CO+hn=~}Du~ce
z1jxUgx;shfihiP>fg~JrY^u$X-0Hs}^}8ejIId%O-v57COl3R-+Mc`te84^ZmTWA&
z|7iSu{nPF3{s=u@R7*?i@o0zF!s%K*P8MwDG=*&c^uDcPb$R0CPe1rlzL@yeikb?71j9y*?j#loo0
z2`-xX&k?wDOj9)`1Qr^_81F7dM3@nm;pQm%|Jayv2;b#Cf9?3Yy)O^iZ%fzDBD$BbCq)@#7|fRi!TfXk!*yowTq{Ze-dm4`p*^rO
zPLxOwv8xyh8kks^5v1pWw
zI-MM9@a@3ojrMgJbk*7hW+l05CF+&47irqVhTqnA#b|bAVyiVr*AYUGw=}E>dXreg
zumz|##;Q_@ef$f|u0j?FrJ6Wv%yInMe{=G6HT!yE!%JBh{2qwy^Rg`6CEt>7Q#2(m
zw5`9JDPtcME<$jk))b(4GIavFkR~(*$OwI>_W)F_YO)MQGuCXcI7jy1)Z(im12Ca6
zLsM_~cVnK(yhd5rQObhL$y$yllQcmYDWeh5>cFj7K!Jj>lcja$_5r|g_95#GsX)$x
z(NIoUX2kBtr%MeGAQTV$Ge6+oM<8+V_b5$sE&a!Z#T-o)R;S}MgwAYL&}bl-MrTQX
z@|L;~K|0M@X-;qEdSbAO1TWA3NLcb*w&KLE4Nuy)i^o~zw%yEMq|q$L(n|>i=7$O%
zsjg0dVga&Bp||}@NBp--M*nx(#=?R~DUjTz3m%Q5FX6Jg*#4x+vy^j@2v0G~`0zNM
z=+Z5Dvsfh8A7xXf^oAe*PT}p0WP9ia8=iRj`P+k`W;?!k>GCPeAi4`mRgfWqxXG%&
zAQR7Dves|XQgX67TBpY#k6B)uUMK+EfneN({ysF#Y7_~%yzHLz_oyQM?U-5klhzr5e~LjEqZ!D4_(evI)sj7+a0pJ{YRo^Vd<
z=?th-obT3~_3*{>H|c{k-_0*;`;3@NC{^!P0Dghgq5Sr?{e}VgA`<1`ke?7p77M#A
z)iBFh;fouB&zP41Qx}C&^*Ae#dGZ-xaMR+ZGe6KNXx)>DhUC1@Xy&jv6(etOu~!K}Nn)HjYbSrZxm+co%`
z5&4Y`MChEAl}O$VW_Q3K1{bVIe4cwvpdOWigFT9Ib%K_wjN5XJa)C(@*EwI2S2rYejc3xkjjQ
z*41%m8{};T$SCfwj}k=TwJ5pG-)<=--sy!))qPb0Tp!e|Y9oKwQO5Bw1@ZrFYMem$
zuB4J^`>|OQFS^auyO(JWDK{GI-ssP64*t^Jx$R!V@PC%Ox_fxexfT@q%GO>aC9uD&
zk8^)SB>b(u!>>rCKJyfFVOJYxSzt*Un+HdP!0>Pxy|zwqEkI`AkC|(L12);QSSBUH
z(OA#MQ~Ossf5e4Vlz2VRD+!Y%+AG?x%)0=l)K@bh5cH1ZIT&+u=*|fk1aD
zh#l+Wn1B$f{%ifsaJrM*|G(?8FhDV;{2k?=$|8UL?dc(n7@gO>(bSKX0X*SzR}a-3
zem*v41%cf>tUrfKiIgjxLOvfI6HU-91y?-45vaDl53fLt3h=w2O{xcYtT;cH$5mwU
z5jOg|sybS3%|*ZXFi4!Fv}kw3YG`d4j11niF=>wNWYKbbE`x9N&@L#Y^^SPNtRi~d
zvx@H~aRp9us&C@Hp(+yYdb)nrZoBEZp;pJwgyWR{lp~TNfYr4imsZVmce4F!c&p2k
ziZHol<^l)d^sG1E7vHVP23{;|nl}f!HH7@OC&=>GTd5$IDD}VjR6*irN`YPPkJH!l
z>(8Z8KiXBUOhg}q5`*0hc%qu!}<<*gZsJL=&P_j
z1G>FtV@Cw#TJe0I)sB+h?*y>1c`#=;1Xl=z^p(BrUgkV$@-wPMCLf@bzdB4uryWp+
z5T0c}bP6FlrB`LJ!2Z-{0U$E!KtLu2;P!Vw&rvY;oC_T>YIr~ZGV)bjJ8^0;Ru@^o
zmDL#iTW>`GDl+!lwD_z4lK`Ty$^^%$uYS_(3X|j8lh4(PTLPdrH1y$%&Ix^~C=56v
zJKupY+XQg>H$`bcpWI@47(=6AX3-021%<}|_L3#I+WLpb0ME+((+A+7GIceknaFYog()RmrP!`<
zP_XK;X3#v5ITqOY;c3a7q0w+gTe#1DNEn>%xzpiwh(M_IDVaQv)kK8Jp9G_g&xufle_tT`
zHa~5@2yURnhI`Ip3p%N6u2Fg;cqmhm`)__V?q;Xii|&avcjO{uH10PE<&w>VU4w$P
z_<$}fTeD179bL1=P7PQ8N5yh04^Ok@^RNS5o-S&lz(MRth1gN>)CI}}v{1S&a!%G3
zhs$!_a#HuD+TV=ZQ*T+gc87-!mn^rcdA5sVCn)-%ggATxE3V^d_Qm%7Tev>
zYg~R%6RdJFB;>Yz)&69wx0r+Q)BLh)7>nMOqP-+AD3_L%a^yORx%AGJ?7NW`mj;Xr
z82$(Z?lAsAdBpLcW9M)PQ3@fwwKS$gYt5PU`H317eyI!49?d_V11wRxZT5h0bNW3t
zTCApTX;WmT%nhozM)wJZ54<-&SLKRv8Z(3>z&w`ww0O|kJx0fgX?Wf(dfTxYxcFqZ
zdW$=}zuDlR85mg6Mmj&Rto^m{++Q~mtHpeyLpir~G^RNE)2DG|T*HIS?NFs|x7jtY
zqE7$fLaOG!KS5wRuH%Lo=O5Pl2O0V&bh-{*FM=VkL*y73029eoy@jbrkol&Jlsjx*
z>U(%eSW58}WJKok97oxLh$auE^b6>)7JqJGF<~$iCN~c~a}^+y+shES1R9c<$u_fX
zQ45&f1p8e>Z4?ae>V6=}(x|u#m=gw~>-z?2YYUV>QeO%g{aKx*YLTMLC;mK(H;cqtr$c^Ae(rBUile#H(wuGrP;i=Xf*hm3=t}wiFtUi+U*Y(xF=nGN5c1p
z%jw#^y3eii;C(b0t8D@RZ2Rr#=z^xPcMcZqGYUd(vod*asFOz*nTd&O2E6U_Xmb`$V$5W;q8t{hf@BZoZ*zrudVu
zEPR>{Tt8AfMZ*8(GWNRvW4cWQU-K1|pAWtv36M5_M%E2TCczvD;{;dty$s+EXe>8A
z1szGLl5Ye3xB0it%SjSLX8(z}$dhJ9Ng{oGiYlZmUk1^YaYXzi$&t7tF_j*ARXj-(
zT8x1t4nhqUP|k|tyZ=lvrtR}dP|&~eAE~|8_B?@*C$tnn>yiA^cR<|Af<9bqC&>
z6H|CEr`HqgMc2XpGOvK3+a@OTpEsmstNxh^%
zBDDfpGS8lf4l+b-yMO41iophxU>dx*%;2gd#O1F(A@1Zzqo@qf6x4nt@-n
zih`8!<`PFKKePkQDMg$P#@C;IDCMOlNW1fnK7A=EK8R|BugBr2|1E-z0HCP3k2lGz
zFPFrZ1*3t88HmOKcrSQw9}~6`8O$FRDSDw$%|wNoacOX!Fd_=c3KZnlY#JJDZF^ZD
zHanXS9c_vlqZXkr*Kdyre=V1f4$fxBee86*P;M
z+2v-8J+lNzNpKDj!aq#he52n!Pv6GF$m`J$(HI*?5^n8H3egZP_xiQ)-TGMs{3bFuJepkmy#t)o
zN1-TGb}zn266_tNdeOMgr!M$|iWAN8{;KB)bZ{Cs#bB^b7mSr=??$!KD2&h>u#X^z
z?&F4n8B205rCIuNi+FWsqerc5o&@RxrTq176^HF{6&dD?1r>Iq4@ha=JWa&Eg}l*w
zz(b7^^|Q}}rzM>v5n`;FBoV1-juO(BI>4=zM!82^B&3zE#Ao^81P8qU1W?Be6dJ4$
z3q&Kb!}0jht3Z1#9Cb@?n>^8>c^9xz+`p~hnGWl+W?8qx^^D>9oYWjs%@H3;>ZB43
zyZRi$C%i2RTMx;s7sco2+bcltWG@-L15z!+bQ2GG1Ac>a`NLp&a!lxAHAjafe<$YS
zAmC#0DW%$S!*dsrX-vNPK=x|$T~^AElVyBEKWXIF_QA!gdyRlCtI_TzE&%HojvCA9
zK&7)XCMJgM@5q@H@d4fEddruu?zEfd?bYUxB>LyA_#78iPkPT!5b+SM=I2%AfpZhM
z^zV_i@qVP(j^6ygkfPBHu231Pn9{#xI~kt#&Ebmhh!Mo?ORU~V+*weo9z&J8I~d|KmD6+E!YPtEKq3VSBl%Eqw&|1$+F`9zQy|yak(`)
zK-`X_iRhSouv3-MvZK%*ULa+-l6R@*os^Bbi-yXY*91#@*Ddf{_2K??2QAgbxyQ+
zgCendg1_MlT~x8gRQUU(HxupklFOr=XdrV5!$X8>wVWS~+-NRVFMs<;t%v2}d9Y7&
zk|WRrVGa;!xgQN!4m$UP!-+8c#$r8Ji~lU7ws$7dw!uzp-CkLi1W|nRJIwv+3QVUBZgZ
zpRJK0)0xZ|ZdWc3vZlvVV@K|pj@?*o`q?^)KPM5pA_EQRafBt~2l3#Vv#8W)o44=F
z_R8+n0+LTLv^ktkvPI1k2g)rgvQ-;S5EN%%H|`WRc=`yhm`ERkcYxE+&ioU(F>I@e
z=5y`$ku~twn;s=XvOllL9Rf`OU~E1ERR+OxF4@>0HMx<~v2AEzeI-
zT>Fi92o$Y=X~zY4n;yBUs`oUd!^N=Vu%Y38A+ol{o{slFm)gJ8?ql=WPp!~*fq$;i
z{DT4dwz3BNajZx7Y+v^fwBTJ_Rt9KdLEvK%hzD0pernvEg8rMFopj0!tS
zI@(LRtIyBRw=|uzJHL0&G7_bd&bJKt(Uzg~0Q|(>!N!TMfYXgPdF}Rf+%?4d6hJ4#
zf=esJXa{(4(BPh(k$A7(-^~_^N3xq~nvK;sG|f|M{AEfy+Z-;k6okeWb3AP_~uye4N~e~o>GqB&+)Md_ZVzR3SetX7I|V!7l@p2;92MCn
z5;oT-K4?U#i}jQZP@2g~o&8M2fjq4&f%7!p$Xc#CiX7}X>lu+~>d;pNjyMD_+p=eG
z6(}C$;~D1ALA90%d`OVU1erJgTgijqb1I!A68*ZWBq-L6@FvSI7Ks$awgI{&us6M@w5FYpvhN6Z0L0(561&Rxdu323+
zWAg&x2QRYN9lVwHrzIT1Komy_0Uy&|2pw!?TG-y|f9$Z9%le!o(tr=g5b>CvglU9Ig6`$UBgRNYcdABf^>_e(@YxA>w(P;8}
zNi$BzIVOW^hS#kJ}5|QDu-e^IUl{Y>0gMdxG3vYfURoZZFW$}8=78vS>Q01Yo`JSiqzMW
zHV5|X`pljg;4mF1yZMRPh#6{8P*7xsqn52V47o4(
zH)bDQ@r=!3Yi{dG1W(Z=^LV{}Ju#o&)P;v_DjlYH%2&S0a^oCphm1`DLHfQzrh0h7Kf;!FBnB?-UP8vJ|?5bCpxB|x||
z!Q*g(C#9ftiYzZ2ZAO@^1k8?p8m0Uy%xtw^R~As5<8FgU+fp|a@5WvsUu0*}-ECHK
zBExNkUld_u2DfWDtnT_@l%>hIm$?T>cJI(f4Ovgva_&Zws>fB+SXhSJ77MiNs~!U<
zZPw-}^>1N#ujqk`f>B3jr^ov0jQS_}i!(*ucgfCVs3Qi`yZs_Hpr^6Yt;(qZbEwNo
z*sSI{YrD>FCd|bJt_)WG}Es`(V!?(?ONAHZ|1zZ?MKM;`JggN
zo+@26SVx1-YJpsYPN@OH9|DF}S_p#xK><1qZ?D^8D%i>(15@!FgkrQ1JJZ#Sm0*(;AFT@y}9iV;k}L
zDqss$T=?v_Tob*#{GzI6Ee$r)GM|%rXFTg04Mv9}8rNrGPIDX~fl{f%wxb2uJm0qS
zQtu|yy?I4Ew`q;RqO4TD&s*rS%;&X0FmhUzfu62JF+K8hBb)OjTrbKk}wi{hB&a4B#66w1v0-_~<+QtbG2g%%Kbj
z|5l;{u%mdJptgT=6Y6iba$?R2yo?=4-vhgX@mH{IxL
z9Wkj*c&A6mlr9sN!6XbZQXu$eIslT{7Jd+>I}PL6fA;mcp}X>p%_8*Ikp-g=a^OH9
zIC7SpM`69$==`xzDN?xtz?cV!#4sQ2qFSb`cUo?phGr_msQfHbjH>l0vr`Pd=LHdr*(0
zUs4m+<8rrkX93U19gcqYJMwr@3J1uq0V@2)f`_lbJ(1epAqg3X`T2@_*
zczHfE5}nR^8*}T%86SOlag2&2b>QfyKvucqX^i**`qdRvH8
zM8SofkzpQb`7ADvQ#Th7(llyFN}GkpMOMLAP;6grb_sE2kan`Vq!q*})7^bK&Wu*(
zF|%btq_kO!XRql+Ug(wBTQ&~~-GiH%iW(e4+%
zMZKDmqg4t~f0%0`Rp*Ub#16OhpSIp9gOD$i^BjsiGQBe1=2%L`u=OlrxLWnE4lZa9
zwpFTh2f!t)g@Jn$GE1AP5!%iLOE6*#v%97e+g@KixX4$j)@#}~_^WIi^`VdFXjOtV
zWwzG;tv;lV+$^c!0{UGygW#?H8!tYDe~51SgNv0AYj%dKgr5UiVy#;dGP)iIMJKR!
zy`nmSV|fY%O8V4|oBxd#|HYGL;F{=ZH&*~$CLMdtjkZPyhmpO;@X!Q1?|
zzeAFyV^Zs=xz;jg)kLvHwi0eL7!_a>PHzC7_4rW1h8Bn0)y}aS4cMGVw^e7ydDL&X
z?zCLVp7-tkv6_e!XNG!*&-|%$UB^g|A16aml~
z42QGQr?b7u>bl^iWc-kO!mCzV>r7YS=O-$*;k(!Y8MGT6bT$H`+uy@5K_9DSH{P1x
z+`bnmmt+u?y8@tnsgVKfPdZ9mc$@qU85lSdR)wH6=@mp-kKNlo>c{mk=|dAvny*Yf
z)YK5t?E(xDXgq#U!=@YYAZmm$YqShGA3H#-?>{4mRw2Yfv}YMLPU(sRfJl7O-nq?X
z#0;ASMIfo8Ds4@=!j8oX%?ZiB6F0UAu^G>0{$J@b$GfVdeKGdNhNKdNb&ih3l8vw#
zctvx`qmy$bwO#OeHM5b_4u$b+u4t48fb$LbE%gC2=;MJ4;>O8vGJ~1B{h*v~n<)E-
z)dlZQb!P)6nFkYam}?YHqkB8@1cWfGCxvZ3I&Y+RO%ht$b8yUpL#e!w%E3W?^oCJj
z3&z^I%$Frw;vyY1IZ6-SCqYU+=`aSu`AUy`2d|5`9!=G--qGnR9rYP7bh&nL_WOn2
zu-@NQuED~jWMpoqyVx}{>@EK$I}Oici%E2^dGab4^k(6ew^CVgql9*%RLUCYsL2fB
zmr>dx_ID_)uKlhKFEa9-x5Sg!lPnk8%B02=75l5HjFOcZ!4IKzbj?3XM$kbP8_0h{wOBB|&+E#?{|BT{A^?+H>D=}?
zoGRb+{cVe@+G0yXG|)pZg%&3Au{S)JpPCgZmjH3XuS>)gS)V2yLbx!KBM$7QK(bzA
zgs-%Jj$ABodF~uqRHRRllA5DZWcg4KBmgv7=-L8K4)^=eQ6Xd&u;-C7fi-Ihz==gk
zwZ#4N)GU2x=bxzD`?Hm3qxvTHa75rvuwXFdsz*V++UNhxs66T>yWqIM|ochc;l?4dFs#0#1plQr5p3q
z!)hsGINj#Tg_51cU&l8)Riu%$q?8aAavfZ-z#myGikqACp07O9uTd3}-1hfy_Mf$=
z9+ron%6~s<=Z;0JibYQKtbg)94e6#y=mbjw^w~=m{1Xh@pcA6{P60Oou=lTEJjMP6
zLm>?}2lBgu$q7(CE5RI9qMR5}OXiAnsfkjD6aOKg$}SxHaew(XrHI^DwBiRh*xCuU
zic~^~nvB7akpo=r7E@x8Zxi!Fh-
zTRhYU*Wu+j%AUJ@~
z)HO<_Y6)mn-1v0_kk-9f8oQyR-0*>PsM6>i{v)Q~2TL}z!Q_m|X09?YDX=idhscXI
z2CA|9oBQehAd&0NtZJfTnPNY6g6h!)RaZ~bXdGs`8Xe`Xnl8yLG`LzSA!OJ8Rg@2e
z)K!9*zKrCt7cKns8iegJaR@(^K#$Q@G?@||FBI;lo)%?q!UngAY#fR9)bW1bD;e($
zvc))bOfC%tOE8%o$i3GLpb?36nt&LITska>?V`sZj|PB5*zNPtT6>o86>uAJOMTQD
zsGU%oj_|}lhW`MaDTg2Lx1G7T2IdbIeNxaOmMX1JA~-q~9EfpZKyIY63%kE7$WE|w
zm|AoY^nMtS*V`i*+42{s@$)%T%_WrzwcCkSrItxBdm%4@D(mY^Y*vbgTGuv%)w`8L
zO}~g_NFxqYt6)-XeP1moLu109^VMg*DvJfO>(91^6zlCU@6X4zh}YM_T--fBsB=K(
zLl$8)i7e=tHqhp~G`S863D&ACH{>W&
zrGpcr4vD;i%O9`k;%Ub++qkt%`hI_b`k{R6VS3958fSA~6*JD6&BW<+x>wZNTszj7
zQlN9Py_V)jf7UW!B+>a?xa=PQngN`OZ?6oL!t6+C>-{tno=Lxz>xcX22DY{%dH3l&
zkd&1Z<6g;i%uWstO6h8Er!!F*ny*uktAffZYfg>Y?_i;Fb-yC2ld^U-iTD&I328WC
z>_m#Sf4D2OU=Y_(e`aJK{|LTYy)T1!QRD`S`{jrQibCvoZlSmIETJXB&8bbwy6tDJ
zVR*-hKYwl71C@%f1?WIn#{02C$vhTThcv*O%4E7yJeR9>hYA|eV+bYnDc!}`@%`1_
zALnG8Vw)tnEh8gyv|{;bX7?GqEi&|oKuh66mu!a)UYq;|IHjGHF;(n-Nmq=juEy(l
zz7AR4fgjZ2s125v)yN(A9IMizoxEiHP=bP;uMZe)yUPR~DAybhV>pSsEtABs3YrCb
zB=SL$`vG0DLNP$=aT)nGHP;6KD?;h~?&fVY9ue9|ry<%b1D{KUdJtRW5iWCS`QEIz
z^i7eK^TWr>@x8YizKQPpeli0GOL?SS^)bavvAaBx!-Kc6`7w#ePkYdyC3^0C)OnJ4
zJ9e1R&DD!-ap4!+(1qg(AObLYSDx+W_!ner*0uA`>`4sX|^X8%Y>fJ@^2K~6o
zpA=QSzq`Gi+;JpeJXy$hk-b)8SzVaoV`rZo;qoaP23FOQnCEW@$L3L06kRkG39_e!foBOo3dYBfH^P=-ZfBsmJ
zK{qdz`t_o~EqbjNq8Bka7-nYXz{Pl2y6*Fp$RA({{_pP3VvX-r=D_36`^CLZ@Tqy~
zl%F(vB+V#cq&b8)Rjr8CXJ6Gvj=gu&g;K4Fb{rUk?jE!pwzCJ4aBn0F^PfnufNcHZrA4~r?6NFWx>0OU#{uubHY&y
z_Dl(~g|?@W8cCy#&^xI-5JVDBw>2YG0ER*nknk|MBBo7beYhWRIh`|1_p3O`B!{=%
zO4f0sdx(0M30^nv-sh$TaK4eBE%^*Z*yDem|Nv
zr^&>!aP#DHei=7a3&lOXo6HQkIqHnpJVkp$TvHRZMJ9ry$7Lo!^sL@rx&67ez1arM
zc)Ly=QL=8cr4aeY=%N=*TebVPV#n(^biZ0_zAB(1|5$X}Grh@0$cTX{QnOB4@b6!f
zwbofrLs(}Pb~(9Z7vjPmWCKV_B+NX~+1S4ZS0*Lja$X5%~8G>QGgsmp!>na;LP*0<@b
z`_EO2sbTz&k$85GDxnFS^)1;#T7DY&tto}xtK4_q&Cv`RRm$K1e+cuoT4kU(pHrS^S;I8Uwq?bu7o^7Ds&&W7)lX|br<0WiAbcA>s-FJiH0B?p9g
zP#y}xX6_%+Z_nYbg$dG091qX+H*Mf>Ic%O7eqxDGPv1Lc+ysC*wQ6!=47+`T=}|@y
z#QRBpnv_0Pg8V_EPJNyk919m~u7w&)X)c)vvnv|a)_Fb_I-9XF2bf%^v=$j`pHa}4
z3gtOKG;^u+B)7y0IezEQ=c*5ks(XD4kG!BUr9g@eP-IAp8URV1kSixpkT_S@)-mQe
zJig)!N0nI1v0|lD_SKn{sU_)bH?Ng*uclxYm}W!gf<+s0w4*33yFq}QnO5h^Rp9Lc
zhY8ZYUVWNM4>ujO#X|kp!Y<7j-PWo~P0--jNxYiXdh?$#oNzS*1?(+YHjbk#MY2cK
zzIW*Jlg!BmPcebDyWjsrmRLNjjs5G;6;drm|Mn87n68&UWj0}?3OY1A#=8#7&<3Dt
zTO2!|(TFE1j%piR(DRAS3OgGV21Evp3Kgc@;H6A4i@-Bz3DH9#Jq(_XJIqD7e=*KdZ2
zIcmvwUtH~P)(H?!c~rAPm56v0)BTyT9rejgv&XP37|SbihO^LM`*$_JwGjhbwX{fZ
zw_9ulU)5jC7EBPBDy$8%%*%N+aU!dXd9==Zl
zAtkqDV|}^RdGWe*S`vmgpLFgLqo?nOE3kc42Ji?)FbdUz%X%;T5(d2ybPa8_SnhQK
zM^k>>g0GH;19}(u!yeA4%k5FFka+i^GFvPxCjA#9v+7s(l|p^tlrK7-6InxHBXhE~
z7ikfaC9*>PuGkL>f2_Jy7EzejLyIW0gE$Ii{F?G{;)JKtzUrsFqHN_AAAkQc8&4uD
z8%yiYhTL&=S^{z=mAto^9dew2AdmI5JF3*a3)+CS(Y|EDn1cv6c@&v7W5$#z8FO!L
z^V#98C?Fy6n0zft;sW3j;!6DB0D|`B=R#)k_V4#TngS;E77r
zPQXkANS#d1KQn1fUa<0FzU3lh@Esc5G_G35mQZ?q(`m;S-N;k8OfUm$IRv-0%U(h`
zu$*1mZ8MNQh1D_@x!G#+AWOyeiE>QL(97iz9>|s(qVTqcgWyPGJ7>g;K1NI7)N11$8*)j>e_0OG9
z*kr+5r4{az4XMl)2hqBDkSwv~NUv9?kl@dL92w+kb2V+W!c)WvosyP-0pet|RN~$K8&lKf%mi|P7e8H<1M)lp
zDB)g+;2q=1W%y!lcy6axP0EWUQ!|m2x+mn
zQgIvK>kww=!KB_MDI_*x@7gSiL0pt>Q1Ba7?W^nG1%EL>Vf5B&wc|C4a~aXRt5ZMa
zD~1sB2G5}-Z(gOOE~J~@4eD{&9VeGR!i)nv&ulMvFu*xlipb{tj&KqFQCKQXl3W}f
ztMe0{btX|%aa4*B6Tx&b`2t%C#r{}7kE?YJ;-^T#5Mt4&5RDMR#PNpUm{%b$Hx8w)
zb;0<1MSqX?P7EVB{t2EQj?5sbft>T70=&m$@GBFmlq(f#i{>0q1NWONJEoK?@D}TZ
zjy))dOivz>OoXsM$iiQ7-N!c`QX?BrM&pR?h(QG@(>@kOM=u4LOV~7$Ycz9H+GC^Y
z>w;I&iN1{?0xBY&GGMcORs*7dy1JB~vesDbp1$+4K(7Ipc3XsX|4j5PkMbrXe>ke6
zf4xh8e!SGFI?I?{qx$G6nz(F%A6(aOQM9P;6LF6A~52QeEq_|yEU7Ap|L
zM*oa>4044|{ewdQwFl`DIAW=vEqrSdF3kJ`&H#PKP%4NW{`U?s%KH{L63>H0a&+?*
z*)TD5Zw}=RdQR=yl_ZM({_m)?RuV5oBc=tP%pUq&>+|FQmM+GAqIU0Cpczm
zI|wja-D;7_icZpReVe@=7jL!XltDJDmc(a3#Yr^JEnc0;KTsweTm+?2EjP1?d=VYQ
zXq$yD4d;q05*3ub?zRlNs*DcmTtkcvmz5T}w60@TRtmi}Z#%iIY*CNLW(!o6dCBC@
zTeZb$vU)=4@skk9WHH1)abNO~H*+=kFuU2B<*WxAXL0NBs799m>
z*WYTj6P4G-G_k&5NvCm2u3maJ1?C-#*J$cyKcz{)8xM9_4l2ZYhv|r@G`z5?-Q+ti
z?ZPx5ZyiEn-BQZiZyMj)_CTPc)*)qb00fk2x}&cgJHD)S7hb>EWP9A+IOm>dCY_V!onxHc7`a+1Jmy
z(Gaj{-f0)inZ`vKQQvWu`5gTNc)Xj;)hX@ZY4iPD0tB7{$5>v-C-XAAFpbQjxh5S8s+jK5bEQdjl
zWl8dU2OA%7>=ff4Pi3+p-_5anymoitxNbJV1fBlB7htUL_D2>O^&iAcD4LVkkwQwS
z32ZCO!EgT#)5YrpH8T?()w0`uqjS)lnXp
z?WK+wW5eW{ABza0G=`My{8IsF1px}{jglG#|K`tNlLUWqYGI
zPo~SelMCdhll?Z)k_Gg_D=50qjStVgw;+ob0-oLL%ijZplgzhVdJr=(CX4TR1488I
zGgK|PSDn)?)vSpto8y}cKv&~?Uo~>$9m;|rNYI_Mf!wr5K{A8oM=o0{*ktl!W`2o$
z#I)TluHbk7KF&x(UCnp^Hyvu}8*KXkJ#8I&;x9H0SsmM7qTBhraT`qX)d>vz)fph@
zqvm;*mEG(XVDD3o2GcrZTLN7|^-58QB+xsA=gTG*T_(g4IYwd-B;}9Cj!A6`S$;+V
zTB~czNy?DWJLHOIjT|l%2BD7OMj4CE=N|NjkF*AP*iaY2ay!^%oIWi|vQ8`%rUX;2
zU?1CCqgQ}}3aNvH5vl90zcXNCCy@X{JL-i0VU6%1Hs@vN>7WvuMl9Qe6l^-GI6DR}
zkHw|-?*&40-h`S;hV9{<-1M;D^Mnv{-C~FPPn$9!*&v^1dMs_TXDU)}dj<(nh6XKZUQyl^vJ&&E2UPlFbFqYhV0Tl9XwXZ}
zJu&HbmcsFF0$=YM_5k#HGGagLn~yK$le17!Q3C1{V>!iAmg9csbbeadUT)DI+}>Q7sX>8Y!=gY5=3A&EY%${{YrNDZkG7G4@9S
z>P{#p%>xNLEsD&YT>#}26(}yeghdd^IEh9ivh^xl+=G_687g8Zhv~18Qh=E;65|jW
z8fyv?Pc2!n`zi0;zHUWZ^cu@jk(n~P4=-=s{#K)
zHHNQRu{ScbojYxAg@R<~R@@SfC~%KF1kiZGpsqJkO8z^=BC3Pr_#x0_Cw`nJ;Zzi(
ze7kxfHuzZaPxzqOLJqM-iC$aI!q1Wgef0PQYxt+$u07@Cncp*8!}k(=DqsxtJWkv<
zYR@nAe4ypx>$rUg`(g8egI9j89&QZ^{yPQC?Qw~T5|RF}(trF!U-f^H!#C3Ac#rVr
z&j?5b$q_?%W_ZD8G4sSlE`V(i9}ox;h19_e`2b_!F)&g9`U1)DxsYxKtww9}Fr|W3
zRfvgL1bcAChmVua=q@=!rFgViJyJpd#yPo9LC$m^BSf4q!CgaNJ_Ak+0g#OL0Ahv}
zdR;l<(-;#OP*xOZbV1SJwpYJHA2J6EpsZ;afs9FP)=$}Byhv6Y#4CktDLtKmJ?Kci
zLj|Fi&=IdjSpf4?bXxF@xX2j)SVdVhifRf*N&)`er4=Lk?v-fL6QeJjx*l&{s`vOI
z4fC-H1PiY~b5h~zv@py?|1r4tNmFah;AR;EN%FumsY4M9wT6a+B1t|M=HzG1{@Ozu
z1lp2#LW4z3+V$hizfkQ^Cwv9y&KkAIgSq9o3I9TE-Qo9--1R^w)qhfZJ%rxdg1pqPEhGKI}HTzHuPr`MKj?NiPSP3I;G{7n?d1+a~p1Gh*iB4#dg
zNSVxT(L^xGv_M~=A3%?=kw6rG3D^OtlooNYAs7vHmpdZ6oM5LYN!~~U0(M}I2
zJ>|oauvf7`0X+cD(%V=-OsEzKQ1J>vV?0Ju8ck4Pa2uzzegS-d(xQ-59;7Fy1N1@~
zD8Lw!aGxc?ABw@SnT8{jOb9e-#FJYX4G(v3^a7zG$P-+b#W6(Hr!f674hjg31Ak4g
z?qAcwJ+is2Tcxg(8@FszznC*-@o4EPaH-_~8nuaSG745j*Q9J%))oY3f(HA)?w
z@7PPInH?(2PcAYx3tmY5gQ0e^(^)C07Tb{IZ4f;5oQc^#K
zKc9T)vFLf|W9WQjP?7h7p-!OuRZw1LZs%6H>y$<ItFWcuxukKQtp6l5DQobcmmz<*<^zm;WGO;3Eh*#n{sjnH}
z>Z2e}a7W>dV2`+q(LBLB%M?e%M=Io_Kc(ap$50`9jDTp6ick@`L~7_eG*S`3U5H~k
z8X*PHXIZJlZcS$$oDpFS+(o~l19dEtk~!sr4Uuj7{bq3QV=h+R&xCs)>bL012G&X)
zf;ug_0Wx%|I&|&vu>HTS*|cfftlpc}Z24*Bx{=jn*mZ`v<>^x${;239pl*gvqHrr)
z*)lsT+-0Bzux6G!$oR7{A}erSIIuAaC_nSSj5;>iY=buLq>qdZdD-{JK1PvP{a{^W
zyMB5?Fo;;_l&b=Y9+d
zNxDfY5Gf~#5~lFS6acx{Iswm}l&E}!=?hIXG3i^ci!%Mqdh
zKMjGgz)u&Xe#TYKPfj>TlV!(Z5k7xCdq#PI5$j{pxgiPt6>y#MHr&sCi;#ld{;a^?
ztb&l|7Zu6QD(4$b=SD;UG%RU+^Z}hokKq`>5sEOcx|!!!&)5{_)qNeg_mPr&pVzCe
z|GGAghIfcW13@9+0;vSa7~?uKw=zu!muVW=f8W@Sy_U>bw4;4!Tfr@lYw;GHYFJTX
zAt5>^O~;@Y((Cm%>Ful#7zQAwA+C@jFC4R}P4%V`KiCJsDkvdTnN?C~5IWVRon1|S
zmb$Oq`)eH+_*v3vU$40~XD!B0^!ZW@ibjKZ06+U=4Oou90td)$D~f4Mfy4k9bNl-F
zp%Se6tWKY5Z%lmi`!l}%PnJ42!S;Vh7XxGZ#-}BGm0#y?
zxc`kLUqt>FF%6HtNP)Kh^3I>UHYEJ3>Hp+wOws;x3gFZJ#2E=U1St)6V1lRz;N;bb
zuU~0oW&M4yMJvhIT=L_mdiOrWF7)Zq==c#}Q1dIZRM?_WgyBDL&Yb)#zyA?Ve$0!N
zhFhME$Bv#&suAG=sA8HaN(lx--33t2DupA4fH<;~zup!9a|-=^IS92A@+XxzTaW>*
z7HsgOZ0tiGzH)p^T4h248lsH9uN}=yfbuC%o+Xl>C7KI^|D^1=G&hIb8U6|RQyg}~
zaA|~p$tnUq$WT-u5CucmGQzSNSCJUG^hBd?inOQSw}k&gu>E}rZF>9vCIx_*hKkdy
zC<6?9`6TwySRJg>tT5dBY`M609=Z3i`P{uvPVT@gIe-o#iSqXR)}77-bZAw*xR>nX
z!{dUJp9=mmoke(;o}v3kuNv517u%&~alyaPwR6X!;a_M-1*}jo$o@0}fCvuBlS*PJ
zVP5;2t#3dTVdDwq0>L)b7laiWKwo9dTT9RorL4AIT03vKf5+0cZ*Odm()(GWRv@*R
zJ}wE?hVxGpqh==T&Tj5ckG}gE6&_j~trlZGT1p@U!{6^-yj)NKvIC)FqU*2$w=n4a
zZUr~N_ID$_>BavO6o6t@@Dv2`n6%RRH3B5BG3#+&UaZr^sSgHB*gUN9Z@k#tvtp}3
zi$ZosMv;3TNQ2Pof@8pU1*z%;=}_SvQmg3pSz|jd%p^^nb^dByXZ;dcf&6t@mKiXA
zwb{&R^EZ#~x}1D}iKTYFWoveB;w7gSLR*fBO%5I@Sk6!!Gv#^SYzWj=g6)^Z@U{UZ
z2LU$hPnrIKhD)UOB-)C}yAgvjd9n86r#=`wVau>4LPNA1v^ZpUucZ<+003DBhfAy>
zpfy2!;^Ih8qXGDUqHzEXfwD2t*hnf28DZv#5hKcm+EXRIG5j#Lf*V13NX?=j-3n7A
zdS%P^E1C(mzaQ;QZ~mX4fXJ-EPy6Ef>Z~1M>x~bdX=|B)3nmY4z;QMuyUjh?a#(Ck
zLYB2hP*6TgQC6E;J;k>6q^cErcUgD0k;=@$5>fSfTKh_UR$b^qPJZ5}%ba`yee4+U
zun5>FuJ4>`z2INySz@}#0&aN%U9tTP)9OOS?%jFKy@ncvgAE}UVYeaAJk}PBu;vv<
z?*-+p9IapzKoimkLOq44XbHJ1Z=hBrE!w*0%JR(bQqz)I)a*=0o|cZ;P~4$i^_UL-K3&&rc$E{O8H+}6=qAPyt_m#`|5T7nvgAmBN+nBt^ph%)47
z2>~-0*7orD4_k8R+jUY)WJc{}Xhl4+w0DLl7Q+onWV8W=1aPB+uo9wUff@)#I?iBa
zGloMpm56R=ePd{A6Koqp&oGrCZ&CnXxQN#hm#0}zh&$vPAg6{6Pk_x(W+M*JfJs7Z
zs+mCp^RjSxxQ5&)EvacqY=VDWsC$tWMWrW9q(V-#B#ABshC{C=
zi&$E{P1bElV1V;H)&PvKSej1$#0B*@sE3=`0T+5}a2Od8Iq5%i>H3Ll78Q+!76Hvz
zS{=M7VhS9@B$WwegcF)HMpo1`lz)yFPz0k3C}0j#1`}ZanFh+HQufK8N%i-}F~RoV
zOJ7s&|H~8rm&C*<#2T=4q61Ts=OhSjLjaPJg&IKuNy>m3W-|pCAymK(LR~Q#kmC<5
ziUfBc=zoE`Wq{&-Hp|T(>xi4lT
zA>mHWTH+2OplKKr)Dxe4oe_ONXQC|yCWs4krjd?7Z9{hCXa%kEt(MLCE@L=PPfy>z
zeY-J~B=?pfJUrap-Tk|l-vry=y$MVO$m0|Mhd221$%f_6stjnL9X*7tXdv+cFIX@@
z!{=+2*dx-;63pwxKNj;-=C}lq3)aGJFOg#
z4OHdH^qd-khD6zk@ib>-+!q?+i<{Z;jq2p>+qa!Mb*faUvQd>P7Zv8*xw8ij9yGyr
zE^0DUQB4&1>lFAiY+b+@ddDAN%ond0^2~uapFRJS+h@1`7cc%b@xOR#peloEHsHxg
zvM(YFkG^>`XDVYj$AAOYizvyl6DL@K|6C_ChN2rw8FWPp7vi-W+s4u<
zOyw|9z(fHP1^y-qkQoHCe1K;?4CzRzU<&^Z1sKt=MuX_HU^MLNXfIc`ABN!8{?8{j#RLV2hOqh6v5ld1%n;@K0r#hA8&lhuC}5(%
zf13j8C%fj%*&ChwSM$}rdwl!m&FB}Ye>H==oh~B{8*6?msFNO?-nw-2#Xlyz_RYmL
zOAkDy6=HclK+qP9B2iOl$XS3Ho$2>yDZr+jNWKPgGw_D;g6S^4Q`a#m$-fifO|Si5
zpn&q;*7;+8dUDA+V9gH&75W;hAME^f!_7?iEB(^2T)_VEUQy*tAS)iZWmGepWet#qmE?2}AfX=7CxOw$W<+&}pRR4?$3Wb?iY-MFj
zK3QmGHm8o>GI{=?xHoTJzlwkUIPPgux`v)uIQqW&HMHF9R{j8PiJ9u{jT>3bCikuE
zKwMdSf8(&I#4Nd%7iGS^e?2W?{DA7tj9v3D4^EqW^{oTMr*VT9oP7g{HU+!KVG8T@LbI;H(q0HYfGDS={W+yj~M$@V0oAfx7ps2V%3LxhgPk7S*lsR
z@^z~;_Hg&`@bvZ#2nzCXG$XYqi$)$?q!OpXwOX|=C;`7o-EnqwR4N_=)8JRlpf&tB{*lzc_N}bZY)~gL_xDrA=A*%u7=vB7V7^m7`xJPOI%b
zO@A%+=&>z2s);o&$*h|5s5g9cn`52VEuFogWoX~RHF{60(R_F2^|ostDND@hR~>D_
zX`k;NxH?ATT4%(Bu0<4DEk-s_FvB^{C-TZcwv_E4Zwh?PV^{?8Iudz{DX0~*F2phN
z3g*YOS}aH5zSAkKVWTC5g5vi(6(aY%mWDPU7aaE(k+^Mw?cCFWOeM`d6yQ~lej71h
zL2U7XJGTE^%}Mikbl7ZCb1&NxyOK;!BHiaClHYRNHA%aG&
zVdUMjBbsQhzWY+mnXA=G-YiR{)F$U7@6#OQrKR9T5m0DG9HS_V(HDZg2R-4#=IYpRZ_AijO_w8YB0ND0pm``2x|
zXBpbLs;h`oYqgq}uU}_~U7Vdn5F16xn{(R!u;!^)u8^w|;x!o`W;d?BT&K*E*PMKC
zl$(s)r7}bZzyg5Ca~Jty!;wG*h@?)}nfdyrot``K6nlnf&c3B|nQC6nS$g|qon1Ws
ztbophZN4th9K*FL9HGKRi}AxY%FOer=Qpf7pRdh|wne4MCudH&R-}KwDu6{Dr?7VO
z%J0Vfu9LTLVIEhRU?CODu*)VhB%*Nz{)>b&B{Narn-l;ZsGeUxa{8fD-I^9#`?gq{
z9qY-s=KFAA)nMW2(b8Wgtbgg(bz*%#F7d$BL5ogDU%#rfZx!HcMo28S@+w)WiVt8W
z_0G;4ywoSKY*}BiMlJTW#T=UiVzCr%1fuJg0pxrDRJr>#w{J7z#lFYJ&(QXsT|G;3!h@?bX6zHx$)O6tv(
zvsOI~9kwFOQS)l+x&a`m%nS2c)m=yHbFu@$XU3~IwXUoZWaBi?4lmnq
zBULUX=oY(&v~OG95oZj^0pT9Cdi>bpbdObAmR%EDmgv;9il>4yBMP$|F%*!vwOMeb
zwHh!_c1ptSj1v>vv}_0R?mRc!UG!Q{-hs8ephc$v73eEO%r2Jc#6*F=N&ypW|5eJF
z(wQjm6$QZDAy-NAH=DY#@MGoaai>mH8Gm$f%jmHc`@b1*ba_Y3x{eX^tjykBIeaTI
zf2#$9YPpD{+8{^e^{0{|O^5ax*uVuIuEipGK(n7`HHI$s>B@F%)^PHmIwL1^aF$}j
zFOE~iN-;;B^7!I`J#m=!2B4r6_(M~xYL(a{q)}<>mx&pi+)N^+J+2sLKrR11KIX#7
z!zDupTBN3=KqsD+cy0gIqqWL3_u(`eRk~ViAK+#CRfUZ122nWW_>}Hrj->e1iCHmv
zVET*mJCXuAFB{V`-}CXAI$8e8ZCe(VliGpN1*!--13-)AzWy>yPiWuXKKR?FH}=+=
zm$&Y`uRg0$i;}W;ZxUX;diLnv%`0b596Wk9M(G{awP}I)J!{wWs`A@t|6(PJ2j}135wafKD>;%aN=-@(1GUZA5*m&Z6=ZTNJQD@UOKf#l>zbw
zdf5v_qN8yK-P=p+qV_&Wr@;&bswD<@u9ds>spD%zkfGCM-C5S7$D*fYN3EOF(kI6X
z3@UIQ4o1`ld6>$ks{QX!0JyD%M%6RW_w{+qHH&2yHr5JuVs2%rhI1vk+_F&D#cPT_
zb}r!~pW@D`2~}Mue0mZYwlR`jiS663i&@o*0n4Yy
z#(Bn@7YZ)m;Lba15))o0UOp1z)oJbMx*p<;8}Hun4#lgsZQsC}_Ao_Ys3RPpl>m8h
zt|&cp&)uPFZTgWRWm>M1N+o>Ch3PF@FSwlr+X1P4aE+$pC$w)@uaLcz>)-3l?u~o)
zA3T0`$L~jZ&aCvvgZ=q?b36Vac~6h8ns59ks`ak6u{XOC$lKcC(<9?OgWW*J-95if
zns%Evvygw%zK9jY6?M8r_|P7+->3A-`#a}vkMXSEyLCwz7Gg-D<<&`XH?Q4#Z1p@9
zvpnC|bJ;&h6BBIzlhii7E00rv#1q=n5$HfN7_U=4*fz4)g_L{g%B*dpdR*pT#HgkI
zP)A!i#cv$E>1^lT^AS
zP2#zQW9A-Hhcqksa?c4}*nmOZs=7mai#I{P4qZC0;_VBuwNc-RBme1s0cD0xl&{Ic
z+YROBk`JdQH9h3pcFK=++%n{$qyPXQ07*naRADFfE+HYA*TxwJcc&$#LaNrRTCsqgm{UIAK6%iVH)Z=xjT~LQ
z(-{8apC5j^FuR@=nLCh*AN>g`1IT2H-ySb=MHZ@us66w(+Xx9D6&sA}s5A*z4xO~CT}1KtfNmp6V#9k+u)pbRprHAO(v~r!ih9m=rTy#ArgOhbYy?5
zdpCd;vXJvU5CyD
zSs}lF9QHnS(0_Rbnh=4RYZ26%h9t06M41V-QEsS_XvH#Y7ysWblKJjM{M#&ZCr=Lt
zGx{iU1iS?yJ?ZhwWT}gfx4pp&QI05|ol>K-!Gu=B3<^;sj4G&E=u&6nQ=}xmIH;gL
zb2zF_O^AP$thIFY^sqPk?EU)8Q4Dp;>*t9|J5OJy+~#WNlMduFQkgDH6fjZXuTVgc
zm?4*lMQZ4|&)T%h@Kw8GpQ8kMd
zDp{;(XzK;nRb(M1;@%!#G;jYK?}lybI!if`ZO!i8t6RpcpS|>A`n$L9cyoIPTR;^V
zSYbICaWHm-Y@HRof6?@56Gsi}*QHIJvPBCNYtU`>70ZU>wjNm2uacw0DyYSrb=#J7
z3Q9aMqi>hGrAzvH6fRbxN~KC+wR^3O&LSNFi<+4LQYJ(TIjY3Qi_h5abY!5Jpjn
zF$^FW0H&rLo6)vuhxTo{b#B*t6`gd;MqjQ^1t(&x)awH8=l@CDPsS`&H
z?+@~pDW)fHqjB2~F798&QDT{syq)WoF5&B5q*#fnRjbs9=(XknR+N+R;6FvDfB!vb
zed0xWG;qb0Br--s0W{B6v~;$sIqkx=-$phnmESu!
z*xx%i^oPZl-cssZ6HL-}u4yV$L5-gROs;@;6p@CEnlm$WI{Al3H*dd8!F=JHm>ajW
zNg2?qrkq)_=!m-Z_=A(e>{CyStv`()v3*j6RmO+pOxq&n;FFrf!!swYylYYQmoAlG
zU0VcxBMEHLr~iveehmXZUVi>g?CI*lTz?VtfE_Um0-pomD?DsoZ=X8jj=6W?Vx>dc
zjU8H{O0{yqPFN1k_!16M$5QP^uWQpg_WZfCmu}sQe)Qy7{EPTE!98mPDo8gfrbSA|
zNov9kGQCqhIWTeXz`1v9L)_O#wwUq!xv1W(DHcbst8MITltc|i7cg?z0f1ej?(8pz
zc?11!=E%q(^g@nQe@xD@vy)2X<`xzloX(>C6jD$E*Or!?MnzyNXX7ooJIFf?Va-7>I6
z*Y#OddrjLM9%PYxd*`AVlR72q+sw=b~HNT>#=*_J|8&?fm
z9MWK6BSmILx>A*vkwxkh1lWgYm09VTsxA~
zh((%=^o-2pbl4@ZuR!wz1?aN!gmdLM+xecorz?Cl|?}vK8^l
zxUHiG&cDf(9WbP`f97PQfv6*Ps@-pDghcf5z>2$Bin9KJfOq1mdNxBMB&l%4BAbxD
zYfm@1x?$|P+aF@D?2o;?e-(SbG`Rh|2`yYM&h0w)bP8jPLa~sEbE7%?hgP?gkgAyi
z_$C{F2v^2+q>73pqWC+P;(3WvQewJG{vu(e3l@x7GVYq9X3N^ox1YMYedc8Id-2M*
z+_o8$&F;pl-t*gMOtHHET+O+mZfus8NUE=xHu>tjPBU~ruOC)z;L0ihEZ_#8u(s>I
zvvF1XOq)>6nh+k=f*3FG60uE1khkg84P(~b`4D&IK |