Giter VIP home page Giter VIP logo

ttstringgolangiot's Introduction

#smartgo SmartGo

smartgo整体架构图

smartgo是什么?

SmartGo是能够支持主流消息队列功能及满足物联网MQTT数千万长连接设备推送消息使用golang语言全新开发的一款分布式、队列模型的智能中间件,具有以下特点:

  • 支持point-point、pub-sub、request-reply等多种模式
  • 支持严格的消息顺序
  • 支持数百万长连接
  • 亿级消息堆积能力
  • 比较友好的分布式特性

当前最新版本功能支持:

  1. 将整个项目命名为smartgo-1.0.0
  2. 将项目中所有子工程命名为stg-*

如何开始?


开发规范必读

  • 源文件使用Unix换行、UTF-8文件编码,遵照golang内置格式化代码规范
  • 请在git clone命令之前执行git config --global core.autocrlf false,确保本地代码使用Unix换行格式
  • 请在非主干分支上开发,禁止提交本地未测试运行通过代码到线上分支
  • 每次提交及之前(正常来说需要先pull --rebase,解决冲突),对代码进行修改必须有相对应的解释说明
  • 正常组内开发人员提交代码,需要经过经过审核后方可提交(且需要有统一格式注释,参照注释类型3)

原始文档请参考:smartgo/docs/doc/目录:

SmartGo文档截图示例:

SmartGo-Store:

SmartGo-Broker:

SmartGo-Net:

SmartGo-Register:

SmartGo-Client:

Markdown文档概述(由于Markdown格式会缺失图片,细节请参考原始文档)

SmartGo-Store技术文档说明

针对版本V1.0.0

©

成都基础平台架构

2017/11/21

目 录

1 存储 4

1.1 概述 4

1.2 零拷贝技术 4

1.3 CommitLog 4

1.4 ConsumeQueue 7

1.5 索引 9

1.6 主从同步 11

1.7 刷盘 13

1.8 文件清理 13

附件一Smartgo开发者联系方式 15

  1. 1存储
  2. 1.1概述

存储模块主要包含存储Producer生产的消息、ConsumeQueue、索引等数据以及主从同步、刷盘、清理服务等。

  1. 1.2零拷贝技术

零拷贝是通过将文件映射到内存上,直接操作文件,相比于传统的io(首先要调用系统IO,然后要将数据从内核空间传输到用户空间),避免了很多不必要的数据拷贝,提高存储性能。 存储消息,使用了零拷贝,零拷贝包含以下两种方式:

方式 优点 缺点
mmap + write 即使频繁调用,使用小块文件传输,效率也很高 不能很好的利用 DMA 方式,会比 sendfile 多消耗 CPU,内存安全性控制复杂
sendfile 可以利用 DMA 方式,消耗 CPU 较少,大块文件传输效率高,无内存安全新问题 小块文件效率低于 mmap 方式

SmartGo采用mmap+write方式,因为有小块数据传输的需求,效果会比sendfile更好。

  1. 1.3CommitLog

CommitLog用于存储真实消息数据。CommitLog路径默认为用户工作目录/store/commitlog。

CommitLog存储目录结构:

commitlog

    - 00000000000000000000

    - 00000000001073741824

commitlog文件名生成的规则:

文件名的长度为20位,左边补零,剩余的为文件起始偏移量(第一个文件起始偏移量为0);

文件名字根据指定commitlog文件大小(默认文件大小为1G,可以通过MessageStoreConfig的mapedFileSizieCommitLog进行配置)递增,文件大小单位为字节。

例如:

默认commitlog文件大小为1G=1073741824b

第一文件的起始偏移量为0,不足20位进行补零,故文件名00000000000000000000,当第 一文件写满,第二文件的起始偏移量为1073741824,不足20位进行补零,故文件名为00000000001073741824,后面的文件名以此类推。

文件n起始偏移量 = size * (n- 1)

文件1起始偏移量 = 1073741824 * (1 - 1) = 0

文件2起始偏移量 = 1073741824 * (2 - 1) = 1073741824

通过commitlog文件名能够方便快速定位信息所在的文件。

文件Index = (消息的起始物理偏移量-最早的文件的起始偏移量)/文件大小,即 (1073741827-0)/1073741824=1,可得知该消息在队列中的第二个文件中:

commitlog文件的消息结构:

序号 字段 说明 字节数 备注
1 TotalSize 消息总长度 4
2 MagicCode MagicCode 4 MagicCode分为:MessageMagicCode、BlankMagicCode。MessageMagicCode表示正确的消息内容;BlankMagicCode表示CommitLog文件空间不足,采用空字节占位写满文件。
3 BodyCRC 消息内容CRC 4 BodyCRC的值是对消息内容(body)进行CRC32生成的32bit冗余校验码,用于确保消息的正确性。
4 QueueId 消息队列编号 4
5 Flag 消息标志 4
6 QueueOffset 消息队列位置 8 自增值,消息队列逻辑位置,通过该值才能查找到consume queue中的数据;QueueOffset * 20才是消息队列的物理偏移量。
7 PhysicalOffset 物理位置 8
8 SysFlag MessageSysFlag 4
9 BornTimestamp 生产消息时间戳 8
10 BornHost 生产消息的地址+端口 8
11 StoreTimestamp 存储消息时间戳 8
12 StoreHost 存储消息的地址+端口 8
13 ReconsumeTimes 重新消费消息次数 4
14 PreparedTransationOffset 8
15 BodyLength 消息内容长度 4
16 Body 消息内容 bodyLength
17 TopicLength Topic长度 1
18 Topic topic topicLength
19 PropertiesLength 附加属性长度 2
20 Properties 附加属性 propertiesLength

添加CommitLog数据,将数据写入到MapedFile,每个MapedFile对应着一个储存消息的二进制文件,MapedFile在创建时会映射到内存上,添加消息时将需要保存的数据写入内存,后续有刷盘服务会将内存中数据持久化到二进制物理文件中,下图是添加CommitLog数据的主要业务流程:

查询CommitLog数据,直接从映射的内存中根据物理偏移量以及数据大小,获取数据,下图是查询CommitLog数据的主要业务流程:

  1. 1.4ConsumeQueue

消费者逻辑队列,对应/store/consumequeue文件夹,每个消费队列文件目录机构如下:

consumequeue

-- topic

    -- queue id

            -- 00000000000000000000

            -- 00000000000000001040

            -- 00000000000000002080

consumequeue文件名生成规则:

commitlog文件名生成规则一致,需要注意的是:maped文件大小为=向上取整(指定size/消息位置信息size)* 消息位置信息size

例如:

指定消费队列文件大小=1024

消息位置信息size = 20

mapedFileSize = 向上取整(1024 / 20) * 20

mapedFileSize = 1040

consumequeue文件结构:

ConsumeQueue中并不需要存储消息的内容,而存储的是消息在CommitLog中的offset。也就是说,ConsumeQuue其实是CommitLog的一个索引文件。

序号 字段 说明 字节数 备注
1 CommitLog Offset CommitLog的起始物理偏移量physical offset 8
2 Size 消息的大小 4
3 Message Tag Hashcode 消息Tag的哈希值 8 用于订阅时消息过滤(订阅时如果指定了Tag,会根据HashCode来快速查找到订阅的消息)

ConsumeQueue是定长的结构,每条数据大小为20个字节,每个文件默认大小为600万个字节。Consumer消费消息的时候,需要2个步骤:首先读取ConsumeQueue得到offset,然后读取CommitLog得到消息内容。

添加消息时,添加消息到commitLog后会向分发服务添加一个分发请求,分发服务调用MessageStore添加消息位置信息,根据消息的Topic、QueueId获取ConsumeQueue,消息的位置信息追加到对应的消费队列中,最终保存的二进制文件中,主要流程如下图:

获取消息时, 首先根据Topic、QueueId获取ConsumeQueue,然后根据消息逻辑offset,获取消息的物理偏移量、消息的Size,最后根据消息的物理偏移量、消息的Size获取CommitLog数据,主要流程如下图:

  1. 1.5索引

IndexService(索引服务)

IndexService用于创建索引文件集合,当用户想要查询某个topic下某个key的消息时,能够快速响应。

Index File(索引文件)

IndexFile存储消息索引的文件,文件结构如下:

索引文件由三个部分组成:Header(索引文件头信息)、Slot Table(槽位信息)、Index Linked List(消息的索引内容)

Index Header:索引文件头信息由40个字节的数据组成。

序号 字段 说明 字节数 备注
1 BeginTimestamp 索引文件开始时间 8 第一个索引创建的时间
2 EndTimestamp 索引文件结束时间 8 最后一个索引创建的时间
3 BeginPhyOffset 索引文件开始的物理偏移量 8 第一个索引对应的CommitLog物理偏移量
4 EndPhyOffset 索引文件结束的物理偏移量 8 最后一个索引对应的CommitLog物理偏移量
5 HashSlotCount 索引文件占用的槽位数 4
6 IndexCount 索引的个数 4

Slot Table

Index Linked List:消息的索引内容链表,默认每个文件有2000W消息索引内容组成,每个消息索引内容为20个字节的数据。

序号 字段 说明 字节数 备注
1 KeyHash key的哈希值 4 topic-key(key是消息的key)的hashCode组成
2 PhyOffset commitLog的物理偏移量 8
3 Timestamp 索引创建的时间 4
4 NextIndexOffset 下一个索引的索引地址 4

IndexFile的创建过程:

首先在DispatchMessageService写入ConsumeQueue后,会再调用indexService.putRequest,添加索引请求;IndexService定时获取创建索引请求,调用IndexService的buildIndex进行创建索引。

  1. 1.6主从同步

在集群模式的部署方式中,Master与Slave配对是通过指定相同的brokerName参数来配对,Master的BrokerId必须是0,Slave的BrokerId必须是大于0的数。一个Master下面可以挂载多个Slave,同一个Master下的多个Slave通过指定不同的BrokerId来区分。

主从同步服务

存储模块启动时,会启动主从同步服务,主从同步服务主要的组成部分是:主从同步服务端、主从同步客户端

主从同步服务端

接收slave节点的连接请求,接收到请求后会建立主从连接,接受和传递主从之间数据。

主从连接

主从连接主要由主从写服务、主从读服务组成,主从写服务主要用于master传输同步数据,主从读服务主要用于接受slave节点发送的offset信息。

主从写服务传输的数据结构:

序号 字段 说明 字节数 备注
1 Offset commitLog物理偏移量 8 同步commitLog物理偏移量
2 BodySize 传输数据的大小 4
3 BodyData 传输数据的内容 BodyLength

主从同步客户端

连接master节点,定时上报offset以及接收master节点传输的同步数据。

主从同步客户端上报的数据结构:

序号 字段 说明 字节数 备注
1 Offset commitLog物理偏移量 8 slave节点的最大commitLog物理偏移量

主从同步客户端上报offset时,会获取当前最大CommitLog文件物理偏移量。如果HAClient是首次上报offset,并且上报的offset为0,master节点会获取最后一个CommitLog文件进行传输,其余的CommitLog文件不会进行同步。上报的offset不为0,master节点会从上报的offset进行同步。

  1. 1.7刷盘

RocketMQ刷盘有两种方式,分为:同步刷盘、异步刷盘。

同步刷盘:在消息到达MQ后,RocketMQ需要将数据持久化,同步刷盘是指数据到达内存之后,必须刷到commitlog日志之后才算成功,然后返回producer数据已经发送成功。

异步刷盘:数据到达内存之后,返回producer说数据已经发送成功,然后再写入commitlog日志。

RocketMQ默认是使用异步刷盘。

逻辑队列刷盘服务(FlushConsumeQueueService):用于将ConsumeQueue的File文件写入入里磁盘,

首先判断是否到达了刷盘时间,如果到达了,那么全盘通刷;否则,遍历所有的ConsumeQueue,调用cq.commit(flushConsumeQueueLeastPages)进行刷盘,flushConsumeQueueLeastPages是目前文件的未刷盘大小达到flushConsumeQueueLeastPages*OS_PAGE_SIZE(1024*4)个,才进行刷盘。

逻辑队列刷盘服务:定时将ConsumeQueue的数据从内存写入到文件。

  1. 1.8文件清理

存储服务启动时,会启动定时清理文件服务,定时清除服务会每分钟定时清理CommitLog、ConsumeQueue文件。

清理CommitLog文件服务

清理CommitLog文件,需要满足以下任意一条件:

1、消息文件过期(默认72小时),且到达清理时点(默认是凌晨4点),删除过期文件。

2、消息文件过期(默认72小时),且磁盘空间达到了水位线(默认75%),删除过期文件。

3、磁盘已经达到必须释放的上限(85%水位线)的时候,则开始批量清理文件(无论是否过期),直到空间充足。

注:若磁盘空间达到危险水位线(默认90%),出于保护自身的目的,broker会拒绝写入服务。

清理ConsumeQueue文件服务

定时清理小于最小CommitLog物理偏移量的ConsumeQueue的文件。

SmartGo-Broker 技术文档说明

针对版本V1.0.0

©

成都基础平台架构

2017/11/21

目 录

1 概述 4

2 Borker模块交互 4

2.1 Registry 4

2.2 Client 4

2.3 Net 4

2.4 Store 5

2.5 Broker 5

3 专业术语 5

3.1 Topic 5

3.1 ConsumerOffset 5

3.2 SubscriptionGroup 5

4 Broker实现原理 5

4.1 Topic 管理 5

4.2 ConsumerOffset管理 7

4.3 SubscriptionGroup管理 9

4.4 发送消息 10

4.5 消费消息 11

4.6 主从同步 12

4.7 Hold 13

4.8 消息统计 14

4.9 Producer、Consumer连接管理 15

附件一 Smargo开发者联系方式 16

  1. 1概述

Broker消息中转角色,负责存储消息,转发消息,一般也称为 Server。在 JMS 规范中称为 Provider。Broker通过自身实现方法并且发布,提供Client调用。也就是说相对于Client,Broker是一个Service。

Broker在Smargo的角色很多,它是给Producer、Consumer提供服务的Service、又是调用Registry服务的Client、而它自身还承载着运维接口、消息统计等一系列的功能。

  1. 2Borker模块交互
  2. 2.1Registry

每个Broker与每个Registry保持长连接。

启动时会向每一个Registry注册,启动过后Broker每隔30秒向Register发送心跳,注册和发送心跳都包含了将自身的clusterName,brokerName,topic信息发。如果Broker 2分钟内没有发送心跳数据,则断开连接。

 Broker挂掉或者断开,Registry会有自动感应,会更新删除该Broker与Topic的关系。

  1. 2.2Client

每个Client通过Registry拿到BrokerList地址,Client与BrokerList保持长连接。

Producer向Broker发送消息,Broker负者处理解析消息,然后转发到Stroe进行消息持久化。

Consumer从Broker拉取消息进行消费,Broker会维护Consumer与Topic之间订阅关系,并且会维护与Topic消费的Offset。

  1. 2.3Net

Broker通过Net创建Service(目前端口为10911),注册并发布服务,供Client调用。

Broker通过Net创建Client,调用Registry的方法。

  1. 2.4Store

Broker收到消息,经过一些列的验证,解析,重新封装后将消息交给Store做后续的处理。

  1. 2.5Broker

Broker主节点之间没有交互,主节点与备节点同步Topic信息,Consumer Offset,延迟队列的Offset,订阅关系等。

  1. 3专业术语
  2. 3.1Topic

Topic是一个消息主题,一个在线Producer实例只能对应一个Topic,一个在线Consumer实例可以对应多个Topic,一条消息必须属于一个Topic。

  1. 1.1ConsumerOffset

ConsumerOffset主要记录了Consumer GroupName与Topic每个Queue的消费进度。

  1. 3.2SubscriptionGroup

SubscriptionGroup用来管理订阅组的订阅信息,包含订阅权限、重试队列,重试次数等。

  1. 4Broker实现原理
  2. 4.1Topic 管理

 默认Topic

目前Broker启动时会生成六个默认的Topic,OFFSET_MOVED_EVENT、SELF_TEST_TOPIC、DEFAULT_TOPIC、BENCHMARK_TOPIC、集群名称Topic、Broker名称Topic。其中DEFAULT_TOPIC最为关键,应为Topic的创建会以DEFAULT_TOPIC为模板进行创建。目前Smargo中DEFAULT_TOPIC的读写队列默认为16个;并且是一个可读可写Topic。

 持久化

每个Broker会将其下的每一个Topic进行统一的持久化,这些Topic被全部保存到一个以JSON的形式都保存到一个文件中,Smargo保存Topic文件的路径为/当前用户目录下/store/config/topic.json文件。该文件主要保存了每一个Topic的主要信息如:TopicName(topic名称)、ReadQueueNums(读队列个数)、WriteQueueNums(写队列个数)、Perm(topic权限)、Order(是否为顺序Topic)、topicSysFlag(系统标识)。

文件存储内容如下:

{

"topicConfigTable": {

    "topicConfigTable": {

        "%RETRY%consumerGroupId-example-200": {

            "SEPARATOR": "",

            "topicName": "%RETRY%consumerGroupId-example-200",

            "readQueueNums": 1,

            "writeQueueNums": 1,

            "perm": 6,

            "topicFilterType": 0,

            "topicSysFlag": 0,

            "order": false

        }

    }

},

"dataVersion": {

    "timestamp": 1511333414604049700,

    "counter": 2023

}

}

 初始化

在Broker启动时,Broker会将Topic.json文件进行加载,在内存中维护一套Topic名称与Topic对象之间的关系,对Topic进行任何操作,都会更新内存所维护的关系以及Topic.json的文件。

 创建Topic

创建Topic由Client发起,Broker没有检测到Client所需要发送的Topic,其创建如图所示:

在创建Topic的过程中,会将创建的Topic的队列数与DefaultTopic队列数对比,取其小的队列数为新建Topic的队列数。创建成功后会立马向所有Registry注册。

 其他操作

如果对原有的Topic进行了操作,会第一时间将内存维护的信息进行更新并且会刷入磁盘中。Broker启动时会开启一个线程,每隔30秒向Registry注册,将更新的Topic维护到Registry中。

  1. 4.2ConsumerOffset管理

ConsumerOffset主要管理的是订阅组与Topic Queue消费进度的管理。具体流程如下:

 初始化

在Broker启动时,Broker会将ConsumerOffset.json文件进行加载,在内存中维护一套以订阅组名称与Topic名称组合为key,以当前Topic队列消费offset为value的关系。

 持久化

在Broker启动时候,Broker会开启一个线程每个5秒对Client上报的Consumer与Topic Offset进行持久化。

Smargo保存consumerOffset文件的路径为/当前用户目录下/store/config/consumerOffset.json文件。存储结构如下:

{

"offsets": {

    groupName@TopicName: {

        queue 1: offset,

    …

    queue x: offset,

    }

}

}

  1. 4.3SubscriptionGroup管理

SubscriptionGroup用来管理订阅组的订阅信息,包含订阅权限、重试队列,重试次数等。其流程如下:

Consumer通过心跳服务进行对SubscriptionGroup来维护。

 持久化

每当Broker维护SubscriptionGroup关系发生改变,都会进行一次持久化。Smargo保存subscriptionGroup文件的路径为/当前用户目录下/store/config/subscriptionGroup.json。存储结构如下:

{

topicName: {

        "groupName": "xx", //订阅组名称

        "consumeEnable": true, //是否可以消费

        "consumeFromMinEnable": true, //是否允许从队列最小位置开始消费,线上默认会设置为false

        "consumeBroadcastEnable": true, //是否允许广播方式消费

        "retryQueueNums": 1, //消费失败的消息放到一个重试队列,每个订阅组配置几个重试队列

        "retryMaxTimes": 16, //重试消费最大次数,超过则投递到死信队列,不再投递,并报警

        "brokerId": 0,  //从哪个Broker开始消费

        "whichBrokerWhenConsumeSlowly": 0  //发现消息堆积后,将Consumer的消费请求重定向到另外一台Slave机器

    }

},

"dataVersion": {

    "timestamp": 1511342161274071800,

    "counter": 3

}

}

  1. 4.4发送消息

整个消息的发送流程分为两个步骤流程:

 Consumer回退消息

针对Consumer消费失败消息投放重试队列,Broker接收到消息检测到如果是消费失败的Consumer端回退消息。会经历一下流程:

  1. a)检测当前消息的订阅组是否存在。
  2. b)检测当前Broker是否有写入权限
  3. c)获取到重试队列Topic(重试队列Topic一般为%RETRY%+groupName),计算QueueID。
  4. d)如果当前消息消费次数大于设置重试消费次数则投入死信队列(死信队列Topic一般为%DLQ% +GroupName)消息将不会再被消费。
  5. e)如果当前消息消费次数小于设置重试消费次数,则会将当前消费次数+3个等级延迟,延迟该消息的消费。
  6. f)重新组装消息对象调用store服务。
  7. g)统计

 Producter发送正常消息

正常消息的发送情况远没有重试消息流程复杂,其流程如下:

  1. a)检查发送消息的合法性(Topic、Broker权限等)。

  2. b)重新组装消息对象调用store服务。

  3. c)统计

  4. 4.5消费消息

Bolt在consumer端的消费方式有两种:一种push(推)、一种pull(拉)。不管是pull与push对Broker而言都是由Consumer主动发起的pull操作。其主要流程如下:

![](data:image/*;base64,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Jat8iYAIiEDuCEhyclckSpAIiIAIlJVApKWgZc18gfJVvqWgBYJfpqQ+9NBDI0eOLFOO/LzYOkRdmRNo0F514OWoCDMvPBLA7hd5SEZSaVClSopkXDudO3e+4oor4j5VoPCqWnkorMbtVQdeTh4y0EoaHn/8cdswnANRWrGjZ0UAAm+++SYVadCgQU8++aSAiEDiBKrQXpVcchKvEzIoAiIgAiLQNAFNH2ganR4UAREQARGIR0CSE4+XQouACIiACDRNoOSSQ98oI4r8bRqQHhQBR4CxHKrTHXfcISYi0A4CVWivSi457agWsikCIiACItAcAU0faI6bnhIBERABEYhNQF5ObGR6QAREQAREoDkCkpzmuOkpERABERCB2ARKLjlVGI6LXeZ6oFkCmj7QLDk9F4lAFdqrkktOpHJWIBEQAREQgVQIaPpAKpgViQiIgAiIwDnnyMtRLRABERABEUiJgCQnJdCKRgREQAREoOSSU4XhOFXi1Aho+kBqqKsZURXaq5JLTjUrrnItAiIgAvkkoOkD+SwXpUoEREAESkhAXk4JC1VZEgEREIF8EpDk5LNclCoREAERKCGBkktOFYbjSlgr85olTR/Ia8mUJF1VaK9KLjklqYnKhgiIgAiUgoCmD5SiGJUJERABESgCAXk5RSglpVEEREAESkFAklOKYlQmREAERKAIBEouOVUYjitCNStJGjV9oCQFmddsVKG9Krnk5LVqKV0iIAIiUEUCmj5QxVJXnkVABEQgEwLycjLBrkhFQAREoIoEJDlVLHXlWQREQAQyIVByyanCcFwm9aaakWr6QDXLPbVcV6G9KrnkpFZXFJEIiIAIiECHBDR9oENECiACIiACIpAMAXk5yXCUFREQAREQgQ4JSHI6RKQAIiACIiACyRAoueRUYTgumYogKxEIaPpABEgK0jyBKrRXJZec5gtfT4qACIiACCRNQNMHkiYqeyIgAiIgAnUIyMtR1RABERABEUiJgCQnJdCKRgREQAREoOSSU4XhOFXi1Aho+kBqqKsZURXaq5JLTjUrrnItAiIgAvkkoOkD+SwXpUoEREAESkhAXk4JC1VZEgEREIF8EpDk5LNclCoREAERKCGBkktOFYbjSlgr85olTR/Ia8mUJF1VaK9KLjklqYnKhgiIgAiUgkCxpw9Mnz79nXfeabogrr322ieeeKLpx/WgCIiACIhALALF9nJa0RswvfXWW7FgKbAIiIAIiEArBIrt5eCmNK0crTzbCnE9KwIiIAKVJVBsL6eyxaaMZ0JA0wcywV6dSKswfUBejvrWqvOLjpHTmsOE33//fadOf/zJ8LdDWz169NiwYcO5557bYUgFEAEjgOQwunznnXfeddddZWUiL6esJat8NU8AUak5TGhKE0VvCHbkyJHPP/+8+UToyeoRQGkYYC6x3vzx58Ovq7gl28p4TCvPFpeYUh6FAD+KwYMHE7LpCSYTJ0785JNPnn/++T59+kSJUWFEoCIE5OVUpKCVTREQARHInoAkJ/syUApEQAREwMZy6H3hb4lplLZj7fTp08899xxTjPbv33/mzJnWi5CqQGcLI3utm5KFnBNQx1rOC6isyavC9IFySg56gzYcOHAg8ap55ZVXomSJm5XBXBGQ5OSqOJSYMhEop+TYy0Lnzp1nz5594403dunSpfUye+mll5YvX47DVO4pjK2DKoEFSU4JClFZyCeBco7l0J8G7gULFtxyyy2J6A3WMIWA8cGM6xIBERABEYhLoJyS8/bbbwMC/yYujsbhzWA7+uuSTaesiYAIFJFAFaYPlFNy2lTbzGFKZDJCm1IosyIgAiKQZwKSnDyXjtImAiJQIQJV2H1AklOhCq2sioAIiEC2BCQ52fJX7CIgAiJQIQLlnCTdvv3T2me5QpUu91mNNUlai45zX56FSWAVloLKyylMdVRCc0jAFh3TUrABaFLzSjCFwUmTJuUwv0qSCLRIQF5OPIDJejmPPvro0KFDnc1du3bdfffdlqCPP/54xowZR48edenr3r37qlWrevfuHU7xt99+u3jx4gkTJpipKNeLL77Iatl6BqNYKHGY6F6OFh2XuBooa+0gIMmJR7V1yQlrSTgFpi7cX79+/axZs/iwYsWKm266CYnau3evCz9t2jSTKPRj0aJF9XIyYMCAlStXXnzxxS4A79Hz588fNmwYL+bz5s3L4TFifo7CWguHtWvXjh49msRzLI1pMwoaXXHrsVq3bh3rrm644YZf/epXnIvT4eEFd9xxB4vAHn744WQXgbHVxcKFCwcNGvTkk0/Gq6AKLQI5J8ALXXEvfpNc4fTXu996TpO1jBKwl4Glig/866fwo48+evDBB785e/GBf//j7OXC22cevPnmm/k2Yu788MQYiDSi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999/307ORaFROPSG5NmEOnyswJlvha0+zSRc0wfiUdP0gXi8ihm6mtMH6pWVzQtgDKbmODzyMHbsWL5qXNRuiN4P1mAFZVIVx5+YUG8w36XNzXQIxG6rX12fXuBfC4ycrFixws7RISK3TtafkuBWnloA9DupbBbIjiQnXmFJcuLxKmZoSU465ZaC5KSTkaRi0e4DSZGUHREQAREQARE4R9MHVAlEQASyIcAsuKb3fXEDNtkkvT2xavpAe7jKqgiIgAiIQCUJyMupZLEr0yIgAiKQBQFJThbUFacIiIAIhAho9wFVChEQAREQARFIjIAmScdDqUnS8XgVM7QmSee/3Ir4S2QF6zvvvJMIW7IfOKwoEbMpGFHHWgqQFYUIiIAInJOU3oDStikq4iUvJ16pFfHdKl4OFfqcc+Tl5L8WFPGXmFSak7KTSSnLy8kEuyIVAREQgSoSkORUsdSVZxEQARHIhIAkJxPsilQEREAEqkhAklPFUleeRUAERCATApKcTLArUhEQARGoIgFJThVLXXkWAREQgUwISHIywa5IRUAERKCKBCQ5VSx15VkEREAEMiEgyckEuyIVAREQgSoSkORUsdSVZxEQARHIhIAkJxPsilQEREAEqkhAklPFUleeRUAERCATApKcTLArUhEQARGoIgFJThVLXXkWAREQgUwISHIywa5IRUAERKCKBCQ5VSx15VkEREAEMiEgyckEuyIVAREQgSoSkORUsdSVZxEQARHIhIAkJxPsilQEREAEqkhAklPFUleeRUAERCATApKcTLArUhEQARGoIgFJThVLXXkWAREQgUwISHIywa5IRUAERKCKBCQ5VSx15VkEREAEMiEgyckEuyIVAREQgSoSkORUsdSVZxEQARHIhIAkJxPsilQEREAEqkhAklPFUleeRUAERCATApKcTLArUhEQARGoIgFJThVLXXkWAREQgUwISHIywa5IRUAERKCKBCQ5VSx15VkEREAEMiEgyckEuyIVAREQgSoSkORUsdSVZxEQARHIhIAkJxPsilQEREAEqkhAklPFUleeRUAERCATApKcTLArUhEQARGoIgFJThVLXXkWAREQgUwISHIywa5IRUAERKCKBCQ5VSx15VkEREAEMiEgyckEuyIVAREQgSoSkORUsdSVZxEQARHIhIAkJxPsilQEREAEqkhAklPFUleeRUAERCATApKcTLArUhEQARGoIgFJThVLXXkWAREQgUwISHIywa5IRUAERKCKBCQ5VSx15VkEREAEMiEgyckEuyIVAREQgSoSkORUsdSVZxEQARHIhIAkJxPsilQEREAEqkhAklPFUleeRUAERCATApKcTLArUhFolcDp06dbNaHnRSB1ApKc1JErQhFogcBrr722YMGC4cOHr1u3rgUzelQEsiEgycmGu2IVgVgEnNLMmTNn06ZNZ86cifW4AotATghIcnJSEEqGCNQgEFaa789egiUCBSUgySlowSnZpSXAIA1+zOzZs6+99lrn05jM8LfT2au0mVfGyk6gU6HfmPhNUkBvvfVWoJjq3W+9NNtnufW0yUJSBPhRDB48OOWqNWjQIGK88cYbt2/f7jJiGmN/k8pdmeyEf/t5zl1SrUdSdjJhJS8nE+yKVARqEwi/Ahb6pbCtxTxkyJC22pfxdhCQlxOPaqHfL+JltcKhM/FyXNWiY40hHHwdXuHdNAEnPM7dufPOO++6664Kl1Lxsp5U65GUnUwIysvJBLsiFYG6BLp06XLLLbc88sgjO3bsePjhh8eOHdu5c2cbwrFONvk9qj3FJSDJKW7ZKeXlJ8DQzsKFC8PaU/6cK4clJSDJKWnBKlvlIhDWnnLlT7mpCgFJTlVKWvksBwGnPZMnTy5HjpSLShGQ5FSquJXZ8hBgyKc8mVFOKkNAklOZolZGRUAERCBrApokHa

ttstringgolangiot's People

Contributors

wjl2017 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.