Add km module kafka

This commit is contained in:
leewei
2023-02-14 16:27:47 +08:00
parent 229140f067
commit 0b8160a714
4039 changed files with 718112 additions and 46204 deletions

View File

@@ -0,0 +1,106 @@
#### 环境要求
- Java11
- IDEA 示例以2022.3.1版本为准,其他版本可能存在差异)
- Gradle 5.6.2 (自动下载,无需提前准备)
#### 环境配置
##### 1. **下载源码**
```
$ git clone https://giturl/kafka.git
```
##### 2. **切换分支**
```
$ git checkout -b kafka-2.5.0-d-350-dev origin/kafka-2.5.0-d-350
```
##### 3. **生成IDEA项目文件**
```
$ cd kafka
$ ./gradlew idea
```
然后使用IDEA打开kafka.ipr文件
##### 4. **IDEA配置**
下载Scala插件 ![kafka-dev-idea-scala-plugin](assets/kafka-dev-idea-scala-plugin.png)
Project Settings -> Project -> Project JDK改为Java11 ![IDEA JDK](assets/kafka-dev-jdk.png)
##### 5. **生成消息协议文件**
```
./gradlew jar
或者
./gradlew clients:processMessages
```
> 至此已经可以正常在IDEA下查看Kafka源码了
##### 6. **编译Kafka Release包**
```
./gradlew clean releaseTarGz
```
或者IDEA上执行
![Release](assets/kafka-dev-build-release.png)
#### 运行环境
##### 1. **修改配置文件**
**config/server.properties**
主要修改以下配置
```
broker.id=0 // Broker节点ID可以保持默认
listeners=PLAINTEXT://:9092 // 监听地址,或者认证方式参考以下额外参数
log.dirs=kafka-logs // 数据目录
zookeeper.connect=localhost:2181 // ZK地址
认证方式额外参数(根据实际情况调整):
listeners=SASL_PLAINTEXT://:9093,PLAINTEXT://:9092
sasl.enabled.mechanisms=PLAIN
security.inter.broker.protocol=SASL_PLAINTEXT
sasl.mechanism.inter.broker.protocol=PLAIN
增强版Kafka额外配置参数
cluster.id=0 // LogiKM中接入的集群ID
gateway.url=http://logikm-ip:8080/gateway // LogiKM地址
```
##### 2. **修改IDEA运行配置**
![kafka-dev-idea-run-config](assets/kafka-dev-idea-run-config.png)
> 记得Add VM options
```
VM options: -Dlog4j.configuration=file:config/log4j.properties -Dkafka.logs.dir=server-logs
Main class: kafka.Kafka
Program arguments: config/server.properties
如果是认证方式额外参数:
-Djava.security.auth.login.config=config/kafka_server_jaas.conf
```
##### 3. **运行Kafka**
![kafka-dev-run-kafka](assets/kafka-dev-run-kafka.png)

View File

@@ -0,0 +1,129 @@
#### 1. 功能范围
功能包含增加节点、集群均衡、范围均衡、Topic黑名单。Topic迁移、节点下线本次不实现设计的时候需考虑进去避免后期无法实现或者成本过高。容量红线触发自动均衡本期不考虑均衡算法优先实现Topic leader、副本、磁盘均衡后续逐步实现其他维度。
#### 2. 指标采集
通过KS3采集指标写入ES需要计算到每个分区的指标只要Leader分区的指标副本指标根据Leader分区指标进行推导
**采集周期:** 每分钟
**相关指标:** 共计4个分区磁盘使用、分区流入流量、分区流出流量、分区CPU使用率
1. 分区Log磁盘使用大小关联指标kafka.log:type=Log,name=Size,topic=yy_topic_b,partition=0
2. 分区流入流量根据当前节点的Leader数计算出每个分区平均流入流量关连指标kafka.server:type=BrokerTopicMetrics,name=BytesInPerSec,topic=yy_topic_b Attributes=OneMinuteRate
3. 分区流出流量根据当前节点的Leader数计算出每个分区平均流量关连指标kafka.server:type=BrokerTopicMetrics,name=BytesOutPerSec,topic=yy_topic_b Attributes=OneMinuteRate
4. 分区CPU使用率当前节点CPU使用率 \* PartitonBytesIn / (BrokerBytesIn \* 3 + BrokerReplicationBytesIn \*1)副本CPU使用率为Leader使用率 / 3关连指标java.lang:type=OperatingSystem Attributes=ProcessCpuLoad本期不考虑
**计算方法:** 计算方法有两种一种是由分区驱动首先生产1分钟整个集群的指标快照然后根据Topic元数据分别查询每个Topic分区的Leader所在节点根据当前节点的Leader数量平均计算当前分区的指标。
另一种是在采集时进行计算由指标驱动指标由多个采集节点进行采集每个采集节点需要维护对应Topic的元数据元数据有效期为1分钟采集到Topic指标之后根据当前节点Leader分布情况分别计算出各个分区的平均指标比如Topic A的BytesIn指标为10240当前节点分布的Leader分区有0、5那么0分区和5分区的指标值分别为5120可由标准采集扩展processor进行计算处理
**放弃方案:**
由采集模块采集标准指标写入ES然后再由样本模块定期查询ES进行分区维度指标计算需要额外协调或者降低实时性否则很难确定是否采集完成
**存储格式:**
| **Cluster** | **IP** | **Broker** **ID** | **Topic** | **Partition** | **NW** **IN** | **NW** **OUT** | **Disk** **Size** | **Cpu** **Load** | **Create** **Time** |
| ----------- | ------ | ------------------ | --------- | ------------- | -------------- | --------------- | ------------------ | ----------------- | -------------------- |
#### 3. 模型计算
- 根据集群配置查询最近N小时的样本数据计算每个Topic分区的平均指标
- 根据集群的副本、Leader、Topic、Rack等元数据信息抽象出虚拟集群(Class类),根据均衡目标算法来改变虚拟集群(Class类)中的元数据分布。
- 根据真实集群与虚拟集群元数据分布的不同提取出需要的副本迁移任务、Leader切换任务
#### 4. 均衡算法
输入参数指标计算周期、均衡节点范围、均衡维度有顺序、各维度均衡区间、Topic黑名单、下线节点列表应用到副本、迁移Topic列表应用到副本
输出结果迁移总览、迁移概览、迁移明细、reassignment json file其中迁移明细为副本从哪个节点迁移到哪个节点上
均衡维度:
1. Rack感知(副本迁移)
1. 默认维度
2. 迁移策略
- Broker每个副本AR分配列表存在2个以上相同的Rack则迁移到不同的Rack
2. Topic Leader数平均分布(Leader切换、副本迁移)
1. 默认维度
2. 迁移策略
- 每台Broker的Topic Leader分布数\>=Topic的Leader总数/broker总数
3. Topic副本数平均分布(副本迁移)
1. 默认维度
2. 迁移策略
- 每台Broker相同Topic副本分布个数\>upperLimit迁出
- 每台Broker相同Topic副本分布个数\<lowerLimit迁入
4. 磁盘使用率平均分布(副本迁移)
1. 可选维度
2. 迁移策略
- 磁盘使用率\>upperThreshold副本降序迁出
- 磁盘使用率\<lowerThreshold迁入
5. 网络流入使用率平均分布(副本迁移)
1. 可选维度
2. 迁移策略
- 流入\> upperThreshold副本降序迁出
- 流入\<lowerThreshold迁入
6. 网络流出使用率平均分布(Leader切换、副本迁移)
1. 可选维度
2. 迁移策略
- 流出\>upperThreshold副本降序进行Leader切换
- 切换后流量依然\> upperThreshold则需要把Leader副本迁出
- 流出\<lowerThreshold则需要把follower切换成Leader
- 切换后流量依然\< lowerThreshold则需要进行副本迁入
7. CPU使用率平均分布
1. 可选维度
2.
#### 5. 均衡执行
输入参数并行度、限速、执行策略、reassignment json file
处理:
- 并行度定义导致有流入或流出流量各计算一个并行度流出统计在Leader上比如一个分区0 \[10,11,12\] -\> \[10,11,15\] 那么10计入一个并行度15计入一个并行度
- 根据并行度拆分任务对于小于2.4版本Kafka按照并行度拆分各个小的子任务分别执行每个节点迁入或迁出副本数不大于并行度限制。对于大于等于2.4版本Kafka根据节点并行度动态增加副本迁移数。
- 根据集群metadata元数据查询所有URP分区优先迁移

View File

@@ -0,0 +1,165 @@
# Kafka引擎-DiDi Kafka HA方案
## 背景
- 多区域集群部署跨Region提供数据灾备能力解决跨机房带宽、延迟问题优先本地读写
- 同区域多集群部署跨AZ提供集群切换能力切换对用户无感知无需修改本地配置或者重启应用
- 消费者Offset同步尽量减少集群切换时导致的数据重复消费
## 开源方案
MirrorMaker主要基于消费者→ 生产者模型实现先由消费者将数据拉取下来拆包然后再由生产者封包发送到备份Topic需要额外资源部署MM服务在Kafka 2.4版本增加了MM2的实现2.7版本增加Group offset同步MM2和MM对比最大的改变是MM基于Topic维度进行同步MM2基于分区维度进行同步这样就可以实现保证消息Offset相对一致
![ha-replication-mm2](assets/ha-replication-mm2.png)
## 滴滴方案
### 设计方案
整个方案同时具备**数据复制的能力**和**灾备切换的能力**
通过使用基于副本复制数据的Fetcher实现跨集群数据复制能力能保证主备Topic数据和Offset完全一致并且不依赖外部资源数据复制链路简单高效。同时能够实现跨Kafka版本实现数据复制。
通过在服务端控制客户端元数据的方式实现灾备切换能力配合Gateway使用在客户端不需要修改任何配置的情况下实现跨集群灾备切换并保证在源集群未宕机的情况下客户端生产、消费数据不重不丢。
#### 数据复制能力
![ha-replication-plus1](assets/ha-replication-plus1.png)
1. Topic分为正常Topic和镜像Topic两种镜像Topic和正常Topic保持完全一致包括Topic名称、分区数、消息Offset、Leader epoch、配置
2. 镜像Topic只能由MirrorFetcher进行数据写入外部生产者无法对镜像Topic写入数据
3. 镜像Topic的Follower和正常Topic的Follower功能完全一致
4. 镜像Topic的Leader既有正常Topic的Leader部分特性也有正常Topic的Follower的部分特性镜像Topic的Leader相对于主Topic的Leader来说它是一个Follower而相对于镜像Topic的Follower来说它又是一个Leader
5. 镜像Topic可以提供数据读取服务可以降低主Topic流出带宽压力
6. 组件MirrorFetcher负责同步Topic数据包括普通Topic和内部Topic \_\_consumer_offsets\_\_consumer_offsets用于同步消费组的offset和metadata信息
7. 组件MirrorCoordinator负责管理MirrorFetcher线程源集群Leader切换启动新任务、停止任务
8. 组件MirrorCoordinator负责同步Topic config、ACL、分区数量主Topic分区扩容之后备Topic分区会自动扩容备Topic分区数\>=主Topic分区数
#### 灾备切换能力
1. 组件Forwarder负责将客户端请求转发发生切时请求会转发到备集群中去
2. 组件Breaker负责阻断客户端请求致使客户端进行元数据变更达到切换目的
**转发的请求:** Gateway、Broker会根据客户端标识转发以下请求
- API_VERSIONS
- METADATA
- FIND_COORDINATOR
- INIT_PRODUCER_ID
**阻断的请求:** Broker会根据客户端标识阻断以下请求
- PRODUCE
- FETCH
- OFFSET_COMMIT
- OFFSET_FETCH
- JOIN_GROUP
- HEARTBEAT
- OFFSET_FOR_LEADER_EPOCH
**生产者灾备流程图**
![ha-replication-plus2](assets/ha-replication-plus2.png)
**消费者灾备流程图**
![ha-replication-plus3](assets/ha-replication-plus3.png)
### 使用规范
1. 目前版本只能实现两个集群间互备,不能加入第三个集群
2. Topic高可用不适合用在事务、幂等场景
3. 保证AppID只在同一个集群中使用如果AppID出现在多个集群中只有最后一个集群中的Topic是可用的
4. 尽量保证AppID的权限范围最小每个生产者或消费者实例组使用一个AppID最佳
5. 集群超级管理账号不能被用作高可用AppID
用户平台接入集群的超级管理员账号不能被配置为HA AppID否则引发集群元数据无法访问问题
6. 主备集群中Topic \_\_consumer_offsets的分区数要相同
目前版本Group offset和Group metadata是通过MirrorFetcher实现同步所以要保证两个集群的分区数相同
7. (建议) 生产者retries设置防止切换过程中丢数
Kafka2.0版本以下需要手动设置Kafka2.1+ retries默认值从0改为2147483647
8. 建议消费者客户端避免使用版本2.5.0, 2.5.1, 2.6.0, 2.6.1, 2.7.0建议使用2.6.2, 2.7.1, 2.8.0以上版本
以上客户端存在查找协调器BUG导致客户端不再消费数据 [*KAFKA-10793*](https://issues.apache.org/jira/browse/KAFKA-10793)
9. 建议建议在有条件的情况下保证各个集群的BrokerID唯一尽量降低BrokerID重复
BrokerID唯一可以更优雅的进行集群灾备切换不唯一也可以实现不做强制限制
### 适用场景
#### Topic跨集群迁移
Topic保障等级变化、集群容量限制等因素导致Topic需要从一个集群迁移到另外一个集群上DiDi Kafka HA方案可以在不需要用户配合的情况下随意在集群间迁移Topic
迁移中
![ha-replication-plus4](assets/ha-replication-plus4.png)
迁移后
![ha-replication-plus5](assets/ha-replication-plus5.png)
#### 集群迁移机房或者集群升级
由于业务调整或者机房调整需要将集群迁移到另外一个机房目前一般会采用扩容节点再缩容的方式实现往往我们在迁移集群的过程中会伴随着集群版本升级或者一些系统参数调整给迁移带来很大的风险。DiDi Kafka HA方案可以在两个独立的集群上实现Topic迁移不需要用户配合同时可以跨版本迁移。迁移流程同上
集群升级跨大版本集群升级风险比较大升级节点对整个集群的稳定性都可能产生影响一些系统配置调整也很难在集群滚动升级中实现比如ControlPlane与DataPlane的拆分。DiDi Kafka HA方案可以实现部分Topic增量迁移降低整个升级过程中的风险。
#### 跨集群Topic灾备
对于保障等级比较高的业务提供Topic跨集群容灾功能当集群不稳定或者不可用时可以快速切换到备集群进行正常读写操作
正常运行
![ha-replication-plus6](assets/ha-replication-plus6.png)
集群不可用时切换到备集群
![ha-replication-plus7](assets/ha-replication-plus7.png)
#### Topic读写分离、就近读、负载均衡
对于一些热点Topic下游有过多消费者一般流出带宽压力比较大可以复制出多个副本Topic来降低这种压力。
复制出一个读Topic降低主Topic读取压力
![ha-replication-plus8](assets/ha-replication-plus8.png)
也可以通过跨区域复制Topic降低专线带宽压力
![ha-replication-plus9](assets/ha-replication-plus9.png)

View File

@@ -0,0 +1,226 @@
## 背景
目前灾备方案使用客户端User唯一标识做灾备切换能有效保障整个客户端元数据统一切换。目前用户存量场景存在Kafka User共享使用情况但是各个客户端对Topic的访问是互相独立的为了兼容这种场景实现客户端能独立进行灾备切换而不影响其他客户端连带切换。
## 方案
### Client端兼容方案二
**client.id 格式:** ~~HA#AppId#TopicName~~ InstanceID
一个InstanceID可以对应多个Topic
**约束:** AppID下每个客户端实例使用相同的InstanceID并且不同客户端实例的InstanceID不能重复
> 客户端实例定义最好是每个生产者、消费者实例使用一套InstanceID次之是应用粒度一个应用使用一套InstanceID但是存在访问多个Topic会存在一些关联切换情况
>
> 注意客户端每次启动都使用相同的InstanceID不能每次启动都生成新的InstanceID
**InstanceID生成方式**
1. 由平台申请生成InstanceID可以在Topic申请时为每一个Topic产生一个InstanceID或者多个Topic申请一个InstanceID
2. 由用户自己控制直接在客户端设置一个ID
**InstanceID和Topic对应关系**
1. 声明式由平台申请InstanceID与Topic绑定关系
2. 自动化:从引擎侧上报的连接信息中自动获取(客户端未启动过这种场景获取不到)
引擎侧上报用户访问信息已有TopicName、AppID、访问类型生产、消费、ClientID
**平台切换:**
用户选择切换方式:用户维度、用户+InstanceID维度
用户维度跟现有逻辑保持一致用户下所有Topic同时切换
用户+InstanceID维度切换Topic时如果用户下InstanceID绑定了Topic则只切换这个InstanceID否则整个用户进行切换
**Gateway、引擎处理**
如果AppID+InstanceID绑定了集群则按这个集群进行转发否则按AppID粒度进行转发
### Server端兼容方案
#### 现状
灾备Gateway、Broker会根据客户端标识转发以下请求
- API_VERSIONS
- METADATAnull、empty、some topics
- FIND_COORDINATOR
- INIT_PRODUCER_ID // 无事务、幂等场景可忽略
Broker会根据客户端标识阻断以下请求
- PRODUCE
- FETCH
- OFFSET_COMMIT
- OFFSET_FETCH
- JOIN_GROUP
- HEARTBEAT
- OFFSET_FOR_LEADER_EPOCH
#### 流程
每个节点建立连接后会发送API_VERSIONS请求获取节点协议版本信息
**生产者:**
1. 生产者启动后会先向引导地址发送METADATAtopics=empty请求只请求集群地址的元数据并更新缓存中metadata的nodes信息
2. 当发送一条消息后生产者刷新元数据发送METADATAtopics=some topics请求
**消费者:**
1. 消费者启动后会向引导地址发送METADATAtopics=empty请求只请求集群地址的元数据并更新缓存中metadata的nodes信息
2. 消费者启动后会向引导地址发送FIND_COORDINATOR请求获取Coordinator地址
3. 客户端向Coordinator发起连接后续发送JOIN_GROUP、SYNC_GROUP
4. 随后会更新客户端元数据,发送元数据请求
如果订阅类型为AUTO_TOPICS会向引导地址发送METADATAtopics=some topics请求
如果订阅类型为AUTO_PATTERN则会向引导地址发送**METADATAtopics=null**请求null为all topics元数据请求
**请求归类:**
- **无信息**
- API_VERSIONS
- METADATAnull、empty
- **含Topic信息**(多个)
- METADATAsome topics
- PRODUCE
- FETCH
- OFFSET_FOR_LEADER_EPOCH
- **含Group信息**
- FIND_COORDINATOR
- OFFSET_COMMIT
- OFFSET_FETCH
- JOIN_GROUP
- HEARTBEAT
#### 改造
根据更细粒度客户端标识进行转发和阻断对于生产者使用AppID#TopicName消费者使用AppID#TopicName或AppID##GroupName作为客户端标识
> 限制1要求AppID#TopicName或AppID##GroupName全局唯一
>
> 限制2要求客户端只访问一个Topic待定
1. **客户端标识获取**
通过解析请求协议内容获取对应的标识符
Topic AppID#TopicName
GroupAppID##GroupName
2. **请求协议处理**
根据优先级AppID##GroupName、AppID#TopicName、AppID获取客户端标识是否绑定集群如果绑定集群进行响应转发或者阻断
3. **无标识请求处理**
- API_VERSIONS
客户端发送API_VERSIONS到Gateway由Gateway直接响应给客户端GW的APIs信息不进行转发由于GW是基于2.5版本的灾备也是基于2.5版本),这里需要判断是不是灾备场景,非灾备场景继续按原有逻辑进行处理
- METADATA
empty情况这种情况是客户端第一次启动时发出的请求nodes信息由Gateway直接响应给客户端GW地址不进行转发
null情况这种情况时消费者采用正则的方式进行订阅需要返回用户有权限的所有Topic列表Gateway无法处理这种场景
4. **多Topic处理**
方案1只处理单一Topic情况如果出现多个Topic按AppID粒度进行转发或阻断
方案2平台维护AppID#TopicList与集群绑定关系当请求的Topic集合为维护的子集时按照细粒度转发否则使用AppID粒度进行转发或阻断
5. **平台改造**
维护平台增加AppID#TopicName和AppID##GroupName与集群绑定关系
切换如果AppID下Topic维护了细粒度绑定关系切换时则不关联其他Topic未维护细粒度绑定关系的Topic切换时不关联出其他已经维护了细粒度绑定关系的Topic
#### 总结
**前提假设:** 1. Gateway版本与Broker版本一致 2. 明确了解用户客户端访问Topic范围
**局限性:** 不支持消费者采用正则模式的订阅方式
只有在平台管理员能够明确了解共享用户的使用情况下才可以使用这种兼容方案否则可能会导致客户端访问集群元数据混乱部分Topic访问失败的情况。平台做大之后平台管理员很难了解每一个用户的使用情况也许当下是规范的后续可能会产生使用交叉情况所以这个兼容方案制作临时使用长期优先在平台侧进行细粒度管控。
### Client端兼容方案一
通过在客户端增加client.id配置生成更细粒度的标识由服务器端进行识别转发
client.id 格式: HA#AppId#TopicName
1. 在Gateway和Broker的ZK中维护AppId#TopicName与集群的绑定关系
2. Gateway和Broker识别客户端ClientID以HA开头则此ClientID是一个细粒度灾备标识根据AppId#TopicName获取集群绑定关系,将请求转发到实际集群
---
**0.10.0.0 Protocol version**
METADATA V1满足正常使用
GROUP_COORDINATOR_REQUEST V0 10 FindCoordinatorRequest V1+增加key_type默认为0group
API_VERSIONS V0
Gateway为2.5版本如果客户端是2.5服务端是0.10客户端将以2.5版本协议进行发送

View File

@@ -0,0 +1,53 @@
**测试环境:**
单条消息大小1KB随机10000条
测试环境CPU: E5-2630 v4 20C40T 内存128G 磁盘2T NVME ES3600P 版本Dkafka2.5.0 JVM-Xmx8G -Xms8G
服务器端参数num.network.threads=6 num.io.threads=32 num.replica.fetchers=10 didi.mirror.num.fetchers=10
客户端参数batch.size=16384 linger.ms=100
主集群单个Topic 10个分区2个副本Leader都分布到同一台机器上副本都分布在同一台机器上
备集群单个Topic 10个分区1个副本Leader都分布到同一台机器上
线程数副本fetcher线程10、Mirror fetcher线程10、MM tasks数10
**非压缩场景**
| 性能对比 | 主集群写入 | 副本同步 | Mirror同步 | MM2同步 |
| :-------: | :--------: | :------: | :---------: | :---------: |
| MessageIn | 626399/s | - | 537576/s | 263450/s |
| BytesIn | 619MB/s | 540MB/s | **538MB/s** | **259MB/s** |
| CPU使用率 | 898.3% | - | 284% | 1558% |
**LZ4压缩场景**
| 性能对比 | 主集群写入 | 副本同步 | Mirror同步 | MM2同步 |
| :-------: | :--------: | :------: | :-----------: | :---------: |
| MessageIn | 770503/s | - | 542501/s | 242615/s |
| BytesIn | 761.5MB/s | 570MB/s | **557.8MB/s** | **245MB/s** |
| CPU使用率 | 757.7% | - | 352% | 1802% |
**结论:** Mirror同步性能和副本同步原理相同性能接近Mirror同步在不使用额外资源的情况下性能比MM2同步高出2倍+**MM2同步在需要额外18核心CPU的情况下达到Mirror同步不到一半的性能**。压测场景下属于理想环境每次发送的Batch都是满载的性能效果会比较好实际线上环境会比较复杂而副本同步和Mirror同步受Batch大小因素基本无影响一次可以拉取多个batch进行写入MM2的性能会受Batch大小因素影响会比较大具体详见以下补充测试。
**PS**
1. 由于是随机消息所以lz4压缩率比较低
2. 由于缺少系统监控图表cpu使用率使用瞬时值上下会有一些偏差
3. 副本同步缺乏消息数指标,流量以刚刚出现积压时流量为准
**补充LZ4压缩场景 batch.size=5120 模拟batch内消息不足**
| 性能对比 | 主集群写入 | 副本同步 | Mirror同步 | MM2同步 |
| :-------: | :--------: | :------: | :-----------: | :---------: |
| MessageIn | 570503/s | - | 549520/s | 102615/s |
| BytesIn | 598.2MB/s | 570MB/s | **549.7MB/s** | **105MB/s** |
| CPU使用率 | 2010% | - | 328.8% | 1284% |
在一个batch包含消息数比较少请求数多的情况下MM2性能明显降低而Mirror同步基本无影响性能相差5倍

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 621 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 999 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 440 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 383 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 195 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 568 KiB