0%

使用 Sarama 往 Kafka 发数据

1 下载、安装 Sarama

1
go get github.com/Shopify/sarama 

2 Kafka 发数据 Demo

2.1 配置

1
2
3
4
config := sarama.NewConfig()
config.Producer.RequiredAcks = sarama.WaitForAll // 发送完数据需要leader和follower都确认
config.Producer.Partitioner = sarama.NewRandomPartitioner // 新选出一个partiton
config.Producer.Return.Successes = true // 成功交付的信息将在 success channel 返回

2.2 构建消息

1
2
3
msg := &sarama.ProducerMessage{}
msg.Topic = "web_log" // 主题
msg.Value = sarama.StringEncoder("this is a test log") // 消息内容

2.3 连接 Kafka

1
2
3
4
5
6
client, err := sarama.NewSyncProducer([]string{"127.0.0.1:9092"}, config)
if err != nil {
fmt.Println("producer closed, err:", err)
return
}
defer client.Close()

2.4 发送消息

1
2
3
4
5
6
pid, offset, err := client.SendMessage(msg)
if err != nil {
fmt.Println("send msg failed, err:", err)
return
}
fmt.Printf("pid:%v offset:%v\n", pid, offset)

2.5 消费者

在 Kafka 安装目录下官方提供了控制台消费者,执行脚本,

1
sh $KAFKA/bin/kafka-console-consumer.sh --bootstrap-server 127.0.0.1:9092 --topic "web_log" --from-beginning

img

然后运行发送消息的程序,

1
2
$ go run sarama.go
pid:0 offset:0

可以在控制台窗口看到消费者接收到了消息,

img

3 完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package main

import (
"fmt"

"github.com/Shopify/sarama"
)

func main() {
config := sarama.NewConfig()
config.Producer.RequiredAcks = sarama.WaitForAll // 发送完数据需要leader和follower都确认
config.Producer.Partitioner = sarama.NewRandomPartitioner // 新选出一个partiton
config.Producer.Return.Successes = true // 成功交付的信息将在 success channel 返回

// 构建一个消息
msg := &sarama.ProducerMessage{}
msg.Topic = "web_log"
msg.Value = sarama.StringEncoder("this is a test log")

// 连接Kafka
client, err := sarama.NewSyncProducer([]string{"127.0.0.1:9092"}, config)
if err != nil {
fmt.Println("producer closed, err:", err)
return
}
defer client.Close()

// 发送消息
pid, offset, err := client.SendMessage(msg)
if err != nil {
fmt.Println("send msg failed, err:", err)
return
}
fmt.Printf("pid:%v offset:%v\n", pid, offset)
}

Kafka 和 Zookeeper 搭建

1 安装 JDK

1.1 下载 JDK 压缩包

下载地址:

http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html

img

1.2 解压、移动

1
2
3
4
# 解压
tar -xzvf jdk-8u301-linux-x64.tar.gz
# 移动文件夹
sudo mv jdk1.8.0_301/ /opt/jdk8

1.3 配置 JDK 环境变量

在 ~/.bashrc 文件末尾添加以下几行,

1
2
3
export JAVA_HOME=/opt/jdk8
export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
export PATH=$PATH:$JAVA_HOME/bin

使环境变量生效,

1
source ~/.bashrc

2 下载 Kafka 压缩包、解压、移动

2.1 下载 Kafka 压缩包

1
wget https://dlcdn.apache.org/kafka/3.0.0/kafka_2.13-3.0.0.tgz

2.2 解压

1
tar -xzvf kafka_2.13-3.0.0.tgz

2.3 移动文件夹

1
sudo mv kafka_2.13-3.0.0 /opt/kafka

2.4 配置环境变量

在 ~/.bashrc 文件末尾添加以下几行,

1
2
export KAFKA=/opt/kafka
export PATH=$PATH:$KAFKA/bin

使环境变量生效,

1
source ~/.bashrc

3 运行 Kafka

3.1 启动 Zookeeper

1
zookeeper-server-start.sh $KAFKA/config/zookeeper.properties

img

3.2 启动 Kafka

另外打开一个终端窗口,

1
kafka-server-start.sh $KAFKA/config/server.properties

img

日志收集项目

1 项目背景

每个业务系统都有日志,当系统出现问题时,需要通过日志信息定位和解决问题。当系统机器比较少时,登录到服务器上查看即可。当系统机器规模巨大,登录到机器上查看⼏乎不现实(分布式的系 统,⼀个系统部署在十几台机器上)。

2 解决方案

把机器上的⽇志实时收集,统⼀的存储到中⼼系统。 在对这些⽇志建⽴索引,通过搜索即可快速找到对 应的⽇志记录 通过提供⼀个界⾯友好的web⻚⾯实现⽇志检索与展示。

3 面临的问题

实时⽇质量⾮常⼤,每天处理几十亿条。 ⽇志准实时收集,延迟控制在分钟级别。 能够⽀持水平扩 展。

4 架构设计

项目架构

  • LogAgent:⽇志收集客户端,⽤来收集服务器上的日志
  • Kafka:⾼吞吐量的分布式队列
  • ElasticSearch:开源的搜索引擎,提供基于HTTP RESTful的web接口
  • Kibaba:开源的ES数据分析和可视化⼯具
  • Hadoop:分布式计算框架,能够对⼤量数据进行分布式处理的平台
  • Storm:⼀个免费并开源的分布式实时计算系统

通知子 goroutine 退出的三种方式

1 全局变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package main

import (
"fmt"
"sync"
"time"
)

var wg sync.WaitGroup
var exit bool

func worker() {
defer wg.Done()
for {
fmt.Println("worker")
time.Sleep(time.Second)
if exit {
break
}
}
}

func main() {
wg.Add(1)
go worker()
time.Sleep(time.Second * 5)
exit = true
wg.Wait()
fmt.Println("over")
}
  • 定义全局变量 exit 用于表示是否要求子 goroutine 退出
  • 父 goroutine 中设置全局变量 exit 的值
  • 子 goroutine 中循环检测全局变量 exit 的值,如果为 true,则结束这个子 goroutine

2 Channel

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package main

import (
"fmt"
"sync"
"time"
)

var wg sync.WaitGroup

func worker(exitChan chan struct{}) {
defer wg.Done()
LOOP:
for {
fmt.Println("worker")
time.Sleep(time.Second)
select {
case <-exitChan:
break LOOP
default: // 注意:如果没有default,select会阻塞在等待exitChan有值
}
}
}

func main() {
var exitChan = make(chan struct{})
wg.Add(1)
go worker(exitChan)
time.Sleep(time.Second * 3)
exitChan <- struct{}{}
close(exitChan)
wg.Wait()
fmt.Println("over")
}
  • 定义无缓冲的管道 exitChan 用于表示是否要求子 goroutine 退出
  • select 多路复用,监听管道 exitChan 中是否有值。如果有值就结束子 goroutine;如果没有值,就执行默认操作
  • 注意:如果没有default,select会阻塞在等待exitChan有值,因为这里定义的管道是无缓冲的

3 Context

3.1 context.WithCancel

3.1.1 开启1个子goroutine

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package main

import (
"context"
"fmt"
"sync"
"time"
)

var wg sync.WaitGroup

func worker(ctx context.Context) {
defer wg.Done()
LOOP:
for {
fmt.Println("worker")
time.Sleep(time.Second)
select {
case <-ctx.Done():
break LOOP
default: // 注意:如果没有default,select会阻塞在等待ctx.Done()有值
}
}
}

func main() {
ctx, cancel := context.WithCancel(context.Background())
wg.Add(1)
go worker(ctx)
time.Sleep(time.Second * 3)
cancel() // 通知子 goroutine 结束
wg.Wait()
fmt.Println("over")
}
  • context.WithCancel 函数返回父context的子context,和一个函数对象cancel用于通知子goroutine结束
  • context内部维护了一个管道,context对象ctx调用Done方法,返回只读形式的管道
  • 子goroutine中select多路复用,监听管道ctx.Done。如果有值,则结束子goroutine;否则,执行默认操作
  • 调用 cancel 函数对象,通知子goroutine结束

3.1.2 开启多个子goroutine

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package main

import (
"context"
"fmt"
"sync"
"time"
)

var wg sync.WaitGroup

func workerGroup(ctx context.Context) {
go worker(ctx)
go worker(ctx)
}

func worker(ctx context.Context) {
defer wg.Done()
LOOP:
for {
fmt.Println("worker")
time.Sleep(time.Second)
select {
case <-ctx.Done():
break LOOP
default: // 注意:如果没有default,select会阻塞在等待ctx.Done()有值
}
}
}

func main() {
ctx, cancel := context.WithCancel(context.Background())
wg.Add(2)
go workerGroup(ctx)
time.Sleep(time.Second * 3)
cancel() // 通知子 goroutine 结束
wg.Wait()
fmt.Println("over")
}
  • 使用context可以很方便地处理通知多个子goroutine结束的情况

3.2 context.WithDeadline

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package main

import (
"context"
"fmt"
"sync"
"time"
)

var wg sync.WaitGroup

func worker(ctx context.Context) {
defer wg.Done()
LOOP:
for {
fmt.Println("worker")
time.Sleep(time.Millisecond * 10)
select {
case <-ctx.Done():
break LOOP
default: // 注意:如果没有default,select会阻塞在等待exitChan有值
}
}
}

func main() {
deadline := time.Now().Add(time.Millisecond * 50)
ctx, cancel := context.WithDeadline(context.Background(), deadline)
wg.Add(1)
go worker(ctx)
time.Sleep(time.Second * 3)
cancel() // 通知子 goroutine 结束
wg.Wait()
fmt.Println("over")
}
  • 上面的代码,定义了一个50ms过期的deadline
  • 调用context.WithDeadline函数,得到子上下文ctx,和一个取消函数cancel
  • 子goroutine中select多路复用,监听管道ctx.Done。如果有值,则结束子goroutine;否则,执行默认操作
  • 两种情况会向管道 ctx.Done 中发送结束信号:① 截止时间到,ctx对象自动向内部维护的管道发送结束信号 ② 手动调用 cancel 函数,通知子 goroutine 结束

3.3 context.WithTimeout

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package main

import (
"context"
"fmt"
"sync"
"time"
)

var wg sync.WaitGroup

func worker(ctx context.Context) {
defer wg.Done()
LOOP:
for {
fmt.Println("worker")
time.Sleep(time.Millisecond * 10)
select {
case <-ctx.Done():
break LOOP
default: // 注意:如果没有default,select会阻塞在等待exitChan有值
}
}
}

func main() {
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*50)
wg.Add(1)
go worker(ctx)
time.Sleep(time.Second * 3)
cancel() // 通知子 goroutine 结束
wg.Wait()
fmt.Println("over")
}
  • 调用context.WithTimeout函数,指定超时时间,得到子上下文ctx,和一个取消函数cancel
  • 子goroutine中select多路复用,监听管道ctx.Done。如果有值,则结束子goroutine;否则,执行默认操作
  • 两种情况会向管道 ctx.Done 中发送结束信号:① 超时时间到,ctx对象自动向内部维护的管道发送结束信号 ② 手动调用 cancel 函数,通知子 goroutine 结束

MongoDB 学习笔记

1 MongoDB 简介

1.1 数据库的分类

1.1.1 关系型数据库(RDBMS)

  • MySQL、Oracle、SQL Server、DB2 ……
  • 关系数据库中全部是表(所谓“关系”,通俗点说就是二维表)

1.1.2 非关系型数据库(NoSQL)

  • NoSQL是Not Only SQL的缩写,“不仅仅是SQL”
  • Redis:键值对数据库,常用作缓存
  • MongoDB:文档数据库

1.2 MongoDB 概述

MongoDB 的数据模型是面向文档的,所谓文档是一种类似于JSON的结构,简单理解 MongoDB 数据库中存的是各种各样的JSON(BSON)。

2 MongoDB 安装

2.1 安装依赖包

1
sudo apt-get install libcurl3 openssl

2.2 下载、解压、移动文件

1
wget https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-debian92-5.0.2.tgz
  • 解压
1
tar -xzvf mongodb-linux-x86_64-debian92-5.0.2.tgz
  • 移动文件
1
mv mongodb-linux-x86_64-debian92-5.0.2 /opt/mongodb

2.3 配置环境变量

在 ~/.bashrc 文件中添加一行:

1
export PATH=$PATH:/opt/mongodb/bin

使环境变量生效,

1
source ~/.bashrc

3 MongoDB 服务启动、停止

3.1 前台启动

1
mongod --dbpath <数据存储目录>

3.2 后台启动

1
mongod --dbpath <数据存储目录> --logpath <日志文件路径> --fork

3.3 停止服务

1
mongod --dbpath <数据存储目录> --logpath <日志文件路径> --shutdown

4 基本使用

4.1 数据库对象

  • 数据库:是一个仓库,可以存放多个集合。
  • 集合:类似于数组,可以存放多个文档。
  • 文档:数据库中的最小单位,我们存储和操作的内容都是文档。

4.2 常用命令

命令 功能
show databases 显示所有的数据库
use <数据库名> 切换数据库(支持自动创建数据库)
db 显示当前所处的数据库
show collections 显示当前数据库中所有的集合
db.<集合名>.insert(<文档内容>) 向集合中插入文档
db.<集合名>.find() 查询集合中的文档

5 CRUD

5.1 插入文档

5.1.1 ObjectId

  • 当插入文档时,如果没有显式地给出 “_id” 字段值,则 MongoDB 会自动为文档生成一个独一无二的 “_id” 字段值。
1
{ "_id" : ObjectId("6141e9058a2bd158b67c5a70"), "name" : "孙悟空", "age" : 500, "description" : "齐天大圣,唐僧的徒弟" }

5.1.2 insert

  • 在集合中插入一个或多个文档
  • json 对象的形式插入一个文档
1
db.npc.insert({name:"孙悟空", age:500, description:"齐天大圣,唐僧的徒弟"});
  • json 对象数组的形式插入多个文档

命令,

1
db.npc.insert([{name:"猪八戒", age:300, description:"天蓬元帅"}, {name:"沙悟净", age:200, description:"卷帘大将"}]); 

返回,

1
2
3
4
5
6
7
8
9
10
BulkWriteResult({    
"writeErrors" : [ ],
"writeConcernErrors" : [ ], "writeConcernErrors" : [ ], "writeErrors" : [ ], "writeConcernErrors" : [ ],
"nInserted" : 2,
"nUpserted" : 0,
"nMatched" : 0,
"nModified" : 0,
"nRemoved" : 0,
"upserted" : [ ]
})

5.1.3 insertOne

  • 在集合中插入一个文档

命令,

1
db.npc.insertOne({name:"唐僧", age: 800, description:"金蝉子"}); 

返回,

1
2
3
4
{                                                                                                                               
"acknowledged" : true,
"insertedId" : ObjectId("61420315cb3a9ab2eb5a6975")
}

5.1.4 insertMany

  • 在集合中插入多个文档

命令,

1
db.npc.insertMany([{name:"蜘蛛精", age:400, description:"妖精"}, {name:"白骨精", age:300, description:"妖精"}]);  

返回,

1
2
3
4
5
6
7
{                                                                                                                               
"acknowledged" : true,
"insertedIds" : [
ObjectId("614206cec4c4d45fc611c481"),
ObjectId("614206cec4c4d45fc611c482") ObjectId("614206cec4c4d45fc611c482") ObjectId("614206cec4c4d45fc611c481"), ObjectId("614206cec4c4d45fc611c482")
]
}

5.2 查询文档

5.2.1 db.collection.find

  • 查询集合中符合条件的文档,返回的是一个结果数组
  • 传入一个对象作为筛选条件
  • 例如:

查询,

1
db.npc.find({age:300}) 

返回,

1
2
{ "_id" : ObjectId("6142027bcb3a9ab2eb5a6973"), "name" : "猪八戒", "age" : 300, "description" : "天蓬元帅" }            
{ "_id" : ObjectId("614206cec4c4d45fc611c482"), "name" : "白骨精", "age" : 300, "description" : "妖精" }

5.2.2 db.collection.findOne

  • 查询集合中符合条件的第一个文档,返回的是一个对象
  • 例如:

查询,

1
db.npc.findOne({age:300})

返回,

1
2
3
4
5
{                                                                                                                               
"_id" : ObjectId("6142027bcb3a9ab2eb5a6973"), "name" : "猪八戒",
"age" : 300,
"description" : "天蓬元帅"
}

5.2.3 db.collection.find.count

  • 查询集合中所有符合条件文档的数量
  • 例如:

查询,

1
db.npc.find({age:300}).count()

返回,

1
2

5.3 修改文档

5.3.1 db.collection.update

  • 语法
1
db.<集合名>.update(<查询条件>, <修改/新对象>, <选项>)
  • 默认情况下,会使用新对象来替换旧对象,注意:新对象直接替换旧对象,不保留原对象的值。
1
2
3
4
5
> db.npc.update({age:300}, {name:"猪八戒", age:300, address:"高老庄"})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.npc.find({age:300})
{ "_id" : ObjectId("6142027bcb3a9ab2eb5a6973"), "name" : "猪八戒", "age" : 300, "address" : "高老庄" }
{ "_id" : ObjectId("614206cec4c4d45fc611c482"), "name" : "白骨精", "age" : 300, "description" : "妖精" }
  • 如果需要修改指定的属性,使用 $set
1
2
3
4
> db.npc.update({name:"猪八戒"}, {$set:{description:"天蓬元帅"}})   
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.npc.find({name:"猪八戒"})
{ "_id" : ObjectId("6142027bcb3a9ab2eb5a6973"), "name" : "猪八戒", "age" : 300, "address" : "高老庄", "description" : "天蓬元帅" }
  • 如果需要删除指定的属性,使用 $unset
1
2
3
4
> db.npc.update({name:"猪八戒"}, {$unset:{address:""}}) 
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.npc.find({name:"猪八戒"})
{ "_id" : ObjectId("6142027bcb3a9ab2eb5a6973"), "name" : "猪八戒", "age" : 300, "description" : "天蓬元帅" }
  • update 默认只会修改一个,设置选项 multi: true,可支持修改多个文档对象
1
2
3
4
5
> db.npc.update({description:"妖精"}, {$set:{gender:"女"}}, {multi:true})   
WriteResult({ "nMatched" : 2, "nUpserted" : 0, "nModified" : 2 })
> db.npc.find({description:"妖精"})
{ "_id" : ObjectId("614206cec4c4d45fc611c481"), "name" : "蜘蛛精", "age" : 400, "description" : "妖精", "gender" : "女" }
{ "_id" : ObjectId("614206cec4c4d45fc611c482"), "name" : "白骨精", "age" : 300, "description" : "妖精", "gender" : "女" }

5.3.2 db.collection.updateMany

  • 同时修改多个符合条件的文档
  • 相当于 update 设置选项 multi: true

5.3.3 db.collection.updateOne

  • 修改一个符合条件的文档
  • update 默认

5.3.4 db.collection.replaceOne

  • 替换一个符合条件的文档

5.4 删除文档

5.4.1 db.collection.remove

  • 语法
1
2
3
4
db.collection.remove(   
<query>,
<justOne>
)
  • 根据条件来删除文档,传递条件的方式和 find() 一样
  • 默认情况下会删除多个
  • 第二个参数传递 true,则只会删除一个

5.4.2 db.collection.deleteOne

  • 删除一个符合条件的文档

5.4.3 db.collection.deleteMany

  • 删除多个符合条件的文档

5.4.4 db.collection.drop

  • 删除集合

5.4.5 db.dropDatabase

  • 删除数据库

5.5 sort、limit、skip

5.5.1 sort

  • 查询文档时,默认情况是按照 _id 的值进行排列(升序)
  • sort 可以用来指定文档排序的规则
  • 需要传递一个对象用来指定排序的规则,1 表示升序,-1 表示降序
  • 例如 employee 对象中有一字段 salary,要求按照工资升序排列,
1
db.employees.find().sort({salary:1})

5.5.2 limit

  • 取出集合中前 n 个文档
  • 例如,查询职工表中前两个职工,
1
db.employees.find().limit(2)

5.5.3 skip

  • 跳过集合中前 n 个文档
  • 例如,查询职工表中从第 3 个开始的所有职工,
1
db.employees.find().skip(2)

5.5.4 调用顺序

  • 以上三个函数可以组合使用,代码中 sort、limit、skip 的顺序可以任意调换,但是实际执行的顺序依次是:
1
sort    =>    skip    =>    limit

5.6 投影

  • 查询时,可以在第二个参数位置来设置查询结果的投影
  • 例如,查询所有职工的姓名和工资,
1
db.employees.find({}, {name:1, salary:1})
  • 默认情况下,会显示 _id 字段,可设置 _id: 0 去除
1
db.employees.find({}, {name:1, salary:1, _id:0})

6 文档之间的关系

6.1 一对一

  • 使用内嵌文档的形式表示,其实就是在其中一个文档中加一个引用字段

6.2 一对多

  • 开发中常见的关系,例子:
1 n
父亲 孩子
用户 订单
文章 评论
  • 也可以通过内嵌文档的形式映射一对多的关系,比如在订单表中加一个字段,指向用户表中的某一个用户
  • 通常是在 “多” 的一方的表中加一个字段

6.3 多对多

  • 新建一个文档用来表示多对多的关系,字段是关系双方的 id
  • 例如,学生和选课是多对多的关系,一个学生可以选择多门课,一个课程可以被多个学生选,存在课程表和学生表,则选课关系如下:
id course_id student_id

Thrift IDL 学习笔记

​ Thrift 可以实现跨语言的接口描述,并通过代码生成引擎将 thrift 文件中定义的数据结构和服务转换成目标语言的代码。

1 类型

1.1 基础数据类型

类型 含义
bool 布尔类型,占用一个字节(和C++中一样)
byte 有符号的字符(相当于 signed char)
i16 16位的有符号整数(相当于short)
i32 32位的有符号整数(相当于 int)
i64 64位的有符号整数 (相当于 long long)
double 64 位的浮点数
binary 字符数组
string 字符串

1.2 容器类型

类型 含义
list T类型元素的列表
set 无序集合(相当于 C++ 中的unordered_set
map<K, V> 哈希映射

1.3 结构体类型

从概念上看,和 C 语言中的结构体 struct 相似,会被代码生成引擎转换成面向对象语言的Class。

1
2
3
4
5
6
7
8
struct Tweet {
1: required i32 userId;
2: required string userName;
3: required string text;
4: optional Location loc;
5: optional TweetType tweetType = TweetType.TWEET // 5
16: optional string language = "english"
}

1.4 异常类型

Exception 在语法和功能上几乎和Struct等同,然而在语义上不同,可以在定义服务的时候声明可能抛出的异常类型。

1.5 服务类型

使用Service关键字声明的服务。

1
2
3
4
5
6
7
8
9
10
11
12
13
service Twitter {
// A method definition looks like C code. It has a return type, arguments,
// and optionally a list of exceptions that it may throw. Note that argument
// lists and exception list are specified using the exact same syntax as
// field lists in structs.
void ping(), //
bool postTweet(1:Tweet tweet) throws (1:TwitterUnavailable unavailable), //
TweetSearchResult searchTweets(1:string query); //

// The 'oneway' modifier indicates that the client only makes a request and
// does not wait for any response at all. Oneway methods MUST be void.
oneway void zip() //
}

2 Typedef

相当于 C++ 中的 typedef,可以为底层数据类型起一个别名。

1
2
typedef i32 MyInteger  
typedef Tweet ReTweet

3 Enum

概念上和 C 语言中的枚举类型相似,可以定义一组常量的集合。默认值从0开始,也可以自己指定,接受10进制和16进制形式的数值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
enum TweetType {
TWEET,
RETWEET = 2,
DM = 0xa,
REPLY
}

struct Tweet {
1: required i32 userId;
2: required string userName;
3: required string text;
4: optional Location loc;
5: optional TweetType tweetType = TweetType.TWEET //

16: optional string language = "english"
}

4 注释

Thrift 支持 bash 风格注释、C++风格单行和多行注释。

1
2
3
4
5
6
7
8
# This is a valid comment.

/*
* This is a multi-line comment.
* Just like in C.
*/

// C++/Java style single-line comments work just as well.

5 命名空间

概念上和 C++ 中的命名空间相似,提供了一种代码的组织方式,可以避免名字冲突。

1
2
namespace cpp com.example.project  
namespace java com.example.project
  1. 转换成 C++ 风格的命名空间嵌套,
1
namespace com { namespace example { namespace project {
  1. 转换成 Java 的包声明
1
package com.example.project

6 Include

可以在thrift文件中使用include关键字包含另外一个thrift文件,和C++不同的是,使用include进的thrift文件中的内容需要显式地带上前缀。

1
2
3
4
5
6
7
include "tweet.thrift"            

...
struct TweetSearchResult {
1: list<tweet.Tweet> tweets; // 注意:需要带上前缀

}

7 常量

使用 const 关键字可以定义常量,对于复合数据类型和结构体用JSON格式表示。

1
2
3
const i32 INT_CONST = 1234;    

const map<string,string> MAP_CONST = {"hello": "world", "goodnight": "moon"}

8 定义结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct Location {                            

1: required double latitude;
2: required double longitude;
}
struct Tweet {
1: required i32 userId;

2: required string userName;

3: required string text;
4: optional Location loc;

16: optional string language = "english"

}
  1. 对于结构体中的每一个属性,都 必须有一个 唯一的正的 整数作为标识
  2. 属性可以被标记为 required (必须的)或 optional(可选的)
  3. Struct 可以嵌套
  4. 可以为某一个属性指定默认值
  5. 在同一个thrift文件可以定义多个Struct
  6. Struct 不支持继承,所以不能拓展(extends)另一个Struct

9 定义Service

1
2
3
4
5
6
7
8
9
10
11
12
13
service Twitter {
// A method definition looks like C code. It has a return type, arguments,
// and optionally a list of exceptions that it may throw. Note that argument
// lists and exception list are specified using the exact same syntax as
// field lists in structs.
void ping(), //
bool postTweet(1:Tweet tweet) throws (1:TwitterUnavailable unavailable), //
TweetSearchResult searchTweets(1:string query); //

// The 'oneway' modifier indicates that the client only makes a request and
// does not wait for any response at all. Oneway methods MUST be void.
oneway void zip() //
}
  1. 函数声明可以用 逗号 或 分号 间隔
  2. 参数和返回值可以是 基础数据类型 和 结构体
  3. Void 也可以作为返回值类型
  4. 使用 throws 关键字声明函数可能会抛出的异常