分类 云原生 下的文章

基础架构

kafka是一个开源的分布式消息系统,具有高吞吐、低延迟、高容错、高并发等特点

avatar


Producer:消息的生产者

Cluster:集群,由多台broker组成

Broker:kafka的实例

Topic:消息的主题,信息在kafka上以Topic为分类进行存储,一个Broker上可以有多个Topic

Partition:Topic的分区,每个Partition的内容不同,起负载作用,提高吞吐量。具体表现为一个文件夹,下面又包含多个segment,segment下面又有index,log,timeindex等文件,log是存放文件的地方,另外两个是索引

Replication:同一个Partition在不同的Broker上存有副本,这些副本当中由一个leader和多个follower组成,当一个leader挂了,会从剩下的follower当中选出新的leader,副本不能存在于同一台机器

Consumer:消费者

Consumer group:由多个消费者组成的组,同一个partition只能被同一组中的一个消费者消费

生产模式

avatar

生产者产生数据后,只会发给leader,follower上的备份信息需要从leader机上pull。

1.生产者从集群获取leader信息
2.生产者把消息发送给leader
3.leader将消息写入磁盘
4.followers从leader获取数据
5.follower消息落盘并给leader发个ack
6.leader给producer发送ack

如果topic存在多个partition,那么按照以下情形选择写入的分区:
1.如有指定,则写入指定的分区
2.如无指定,但是设置了数据的key,则根据key的hash来选取
3.如果以上都没有,则轮询

ack应答机制:生产者在给kafka发送数据的时候,可以选择0,1,all三种参数
0:生产者发送数据后不需要等到集群返回
1:leader应答了就可以继续
all:所有都ack了才可以继续

如往不存在的topic发送数据,则kafka会自动创建topic,partition和replication默认都是1

avatar
partition是一个有序不可变的消息记录集合,当有新的消息会被写到partition的末尾,每个消息都有一个唯一的标识符offset。但是要注意在不同的partition之间卡夫卡不能保证消息的顺序
kafka可以设置一个保留期限,超过期限的数据将会被清除,或者也可以基于大小来限制

avatar
利用segment+offset来寻找数据:
先找到offset的368801message所在的segment文件(利用二分法查找),这里找到的就是在第二个segment文件。
打开找到的segment中的.index文件(也就是368796.index文件,该文件起始偏移量为368796+1,我们要查找的offset为368801的message在该index内的偏移量为368796+5=368801,所以这里要查找的相对offset为5)。由于该文件采用的是稀疏索引的方式存储着相对offset及对应message物理偏移量的关系,所以直接找相对offset为5的索引找不到,这里同样利用二分法查找相对offset小于或者等于指定的相对offset的索引条目中最大的那个相对offset,所以找到的是相对offset为4的这个索引。
根据找到的相对offset为4的索引确定message存储的物理偏移位置为256。打开数据文件,从位置为256的那个地方开始顺序扫描直到找到offset为368801的那条Message。

https://blog.csdn.net/wanghailan1818/article/details/125166287


gin

https://www.cnblogs.com/jakelin/p/15604267.html
https://gin-gonic.com/zh-cn/docs/quickstart/

https://pkg.go.dev/github.com/gin-gonic/[email protected]#RouterGroup.StaticFile
可能会涉及到的其他包:
net/http
os
render(内置了
binding(内置了

解决gin无法安装问题

go env -w GO111MODULE=on
go env -w GOPROXY=https://goproxy.io,direct

# 生成 mod 文件(切换到模块根目录)
go mod init modulename

# 清理无效依赖,增加缺失依赖
go mod tidy

go get -u github.com/gin-gonic/gin 

简单案例

第一个程序

package main

import (
    //"fmt"
    "net/http"
    "os"

    "github.com/gin-gonic/gin"
)

func main(){
    engine := gin.Default() //生成一个实例
    engine.GET("ping",func(c *gin.Context) {
        c.String(http.StatusOK,"pong") //申明一个路由,告诉gin触发的url和要显示的信息
    })
    engine.Run(port()) //运行
}

func port() string{
    port := os.Getenv("PORT") //获取名为PORT的参数
    if len(port) == 0 {
        port = "8080"
    }
    return ":" + port
}

运行这个程序,会看到程序监听在8080端口,我们用浏览器去访问8080

我们再尝试访问我们设置的ping,可以看到回复了一个我们设置的pong

avatar

加一点小细节

package main

import (
    "net/http"
    "os"

    "github.com/gin-gonic/gin"
)

func port() string {
    port := os.Getenv("PORT")
    if len(port) == 0 {
        port = "8080"
    }
    return ":" + port
}

type Book struct {
    Title  string `json:"书名"`
    Author string `json:"作者"`
    Date   string `json:"出版日期,omitempty"`
    ISBN   string `json:"isbn"`
}

var Booklist = map[string]Book{
    "1101100011": {Title: "go语言圣经", Author: "Alan A. A. Donovan, Brian W. Kernighan", Date: "2016-03", ISBN: "1101100011"},
    "1145141919": {Title: "U.N", Author: "anyone", ISBN: "1145141919"},
}

func AllBooks() []Book { //把booklist从map转为切片输出
    value := make([]Book, len(Booklist))
    inx := 0
    for _, book := range Booklist {
        value[inx] = book
        inx++
    }
    return value
}

func Getbook(isbn string) (Book, bool) {
    book, isfound := Booklist[isbn]
    return book, isfound
}

func Update(isbn string, book Book) bool { //如果这本书存在,那么更新
    _, exist := Booklist[isbn]
    if exist {
        Booklist[isbn] = book
    }
    return exist
}

func Create(book Book) (string, bool) { //创建书本
    _, exist := Booklist[book.ISBN]
    if exist {
        return "", false
    } else {
        Booklist[book.ISBN] = book
        return book.ISBN, true
    }
}

func Delete(isbn string) bool {
    _, exist := Booklist[isbn]
    if exist {
        delete(Booklist, isbn)
        return true
    } else {
        return false
    }
}

func main() {
    engine := gin.Default()
    engine.GET("/ping", func(c *gin.Context) {
        c.String(http.StatusOK, "pong")
    })

    engine.GET("hello", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{"message": "hello gin!"}) //type H map[string]any
    })

    engine.LoadHTMLGlob("./*.html")            //加载并渲染html,另外有个类似的 LoadHTMLFiles ,但是需要一个个列举文件
    engine.StaticFile("/ico", "./favicon.ico") //将某个文件作为静态资源访问

    engine.GET("/", func(c *gin.Context) {
        c.HTML(http.StatusOK, "index.html", gin.H{"title": "here is the main website!"}) //type H map[string]any
    })

    engine.GET("/api/books", func(c *gin.Context) { //以json格式打印所有的书
        c.JSON(http.StatusOK, AllBooks())
    })

    engine.GET(("/api/books/:isbn"), func(c *gin.Context) { //通过isbn查找书
        isbn := c.Params.ByName("isbn") //byname会返回匹配name的第一个参数的值,如果没有那就返回空字符串
           //isbn := c.Query("isbn")  //也可以使用c.Query(),用?传参,但是注意路由部分不能和其他的重复
        book, found := Getbook(isbn)
        if found {
            c.JSON(http.StatusOK, book)
        }
    })

    engine.PUT("/api/books/:isbn", func(c *gin.Context) { //如果存在,那么更新
        isbn := c.Params.ByName("isbn")
        var book Book
        if c.BindJSON(&book) == nil { //bind系列方法的目的是自动提取HTTP请求中的各种参数到结构体中,内部调用ShouldBindWith和MustBind,如果发生错误会400返回
            exist := Update(isbn, book)
            if exist {
                c.Status(http.StatusOK)
            } else {
                c.Status(http.StatusNotFound)
            }
        }
    })

    engine.POST("/api/books", func(c *gin.Context) { //上传书本
        var book Book
        if c.BindJSON(&book) == nil {
            isbn, ok := Create(book)
            if ok {
                c.Header("new-book's-location", "/api/books/"+isbn)
                c.Status(http.StatusCreated)
            } else {
                c.Status(http.StatusConflict)
            }
        }
    })

    engine.DELETE("/api/books/:isbn", func(c *gin.Context) {
        isbn := c.Params.ByName("isbn")
        status := Delete(isbn)
        if status {
            c.Status(http.StatusOK)
        } else {
            c.Status(http.StatusNotFound)
        }
    })

    engine.Run(port())
}
/*
[GIN-debug] GET    /                         --> main.main.func3 (3 handlers)
[GIN-debug] GET    /api/books                --> main.main.func4 (3 handlers)
[GIN-debug] GET    /api/books/:isbn          --> main.main.func5 (3 handlers)
[GIN-debug] PUT    /api/books/:isbn          --> main.main.func6 (3 handlers)
[GIN-debug] POST   /api/books                --> main.main.func7 (3 handlers)
[GIN-debug] DELETE /api/books/:isbn          --> main.main.func8 (3 handlers)
*/
//当然,这样写十分不安全,只有在这个练习题里面才能这么干,生产环境这么写黑客们就要笑嘻了

使用postman来测试这个api是否正常

GET获取书籍列表


通过isbn找书


通过isbn更新书本信息,要注意这里使用json格式的话得用编码后的字段


上传书本信息


删除书本
![

在docker上部署微服务

FROM golang:latest #表明使用的底层镜像
#RUN apt update && apt install git #pull下来的镜像有git就不用装
#RUN go env -w GO111MODULE=on && go env -w GOPROXY=https://goproxy.io,direct && go env -w GOSUMDB=off && go install github.com/gin-gonic/[email protected] && mkdir /go/src/mircoservice-1 2> /dev/null
RUN go env -w GO111MODULE=on && go env -w GOPROXY=https://goproxy.io,direct && go env -w GOSUMDB=off && mkdir /go/src/mircoservice-1 && cd /go/src/mircoservice-1 && git clone https://github.com/gin-gonic/gin.git && go mod init &&  go mod tidy && go get -u github.com/gin-gonic/gin
ENV SOURCES /go/src/mircoservice-1/
COPY . ${SOURCES}
RUN cd ${SOURCES} && CGO_ENABLED=0 go build ./example.go
WORKDIR ${SOURCES}
CMD ${SOURCES}example
EXPOSE 8080 #这只是一个声明,没有实际的端口映射作用

go get 国内即使设置了代理网也很差,可能得多试几次才能成功,如果换成go install会因为包不是main而编译失败退出导致镜像构建失败

[root@iZbp15ywdll3dqiq4ija6lZ go-example]# docker run -it -d -p 8080:8080 go-service:v3 
7b1e634789394d0118b1d1222bae95de4e21f1ff6d2aeb23746cde0560a948e3
[root@iZbp15ywdll3dqiq4ija6lZ go-example]# curl 127.0.0.1:8080
<h1>hello!</h1></br>
<h2>here is the main website!</h2>
<b>enjoy your day!</b></br>[root@iZbp15ywdll3dqiq4ija6lZ go-example]#