panic: runtime error: invalid memory address or nil pointer dereference with bigger data

panic: runtime error: invalid memory address or nil pointer dereference with bigger data

我正在使用 Apache Prediction IO 开发推荐引擎。在事件服务器之前,我有一个 GO api 来监听来自客户和进口商的事件。在特定情况下,当客户使用进口商时,我收集进口身份,并从进口商 api 发送 json 到 GO api。例如,如果用户导入包含 45000 个数据的 csv,我将这 45000 个身份发送到 GO api 中的 json,如 {"barcodes":[...]}。预测 IO 事件服务器需要特定形状的数据。

type ItemEvent struct {
    Event      string              `json:"event"`
    EntityType string              `json:"entityType"`
    EntityId   string              `json:"entityId"`
    Properties map[string][]string `json:"properties"`
    EventTime  time.Time           `json:"eventTime"`
}


type ItemBulkEvent struct {
    Event     string    `json:"event"`
    Barcodes []string  `json:"barcodes"`
    EventTime time.Time `json:"eventTime"`
}

ItemEvent 是我将从 GO Api 发送到事件服务器的最终数据。 ItemBulkEvent 是我从进口商那里收到的数据 api。

func HandleItemBulkEvent(w http.ResponseWriter, r *http.Request) {
    var itemBulk model.ItemBulkEvent
    err := decode(r,&itemBulk)
    if err != nil {
        log.Fatalln("handleitembulkevent -> ",err)
        util.RespondWithError(w,400,err.Error())
    }else {
        var item model.ItemEvent
        item.EventTime = itemBulk.EventTime; item.EntityType = "item"; item.Event = itemBulk.Event
        itemList := make([]model.ItemEvent,0,50)
        for index, barcode := range itemBulk.Barcodes{
            item.EntityId = barcode
            if (index > 0 && (index % 49) == 0){
                itemList = append(itemList, item)
                go sendBulkItemToEventServer(w,r,itemList)
                itemList = itemList[:0]
            }else if index == len(itemBulk.Barcodes) - 1{
                itemList = append(itemList, item)
                itemList = itemList[:( (len(itemBulk.Barcodes) - 1) % 49)]
                go sendBulkItemToEventServer(w,r,itemList) // line 116 
                itemList = itemList[:0]
            } else{
                itemList = append(itemList, item)
            }
        }
        util.RespondWithJSON(w,200,"OK")
    }
}

HandleItemBulkEvent 是批量更新的处理函数。在这一步中,我应该提到预测 io 的批量上传。通过 rest api 预测 io 事件服务器每个请求需要 50 个事件。所以我创建了一个包含 50 个上限和一个项目的列表。我使用相同的项目,每次都更改身份部分(条形码)并添加到列表中。在每 50 个项目中,我使用一个处理程序函数将该列表发送到事件服务器,然后清理列表等等。

func sendBulkItemToEventServer(w http.ResponseWriter, r *http.Request, itemList []model.ItemEvent){
    jsonedItem,err := json.Marshal(itemList)
    if err != nil{
        log.Fatalln("err marshalling -> ",err.Error())
    }
    // todo: change url to event server url
    resp, err2 := http.Post(fmt.Sprintf("http://localhost:7070/batch/events.json?accessKey=%s",
        r.Header.Get("Authorization")),
        "application/json",
        bytes.NewBuffer(jsonedItem))
    if err2 != nil{
        log.Fatalln("err http -> " , err.Error()) // line 141
    }
    defer resp.Body.Close()
}

sendBulkItemToEventServer 函数整理传入的项目列表并向预测 io 的事件服务器发出 post 请求。在这部分中,当我尝试使用 5000+- 项时它运行良好,但是当我尝试使用 45000 项时,应用程序崩溃并出现以下错误。

panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0xc05938]

goroutine 620 [running]:
api-test/service.sendBulkItemToEventServer(0x1187860, 0xc00028e0e0, 0xc00029c200, 0xc00011c000, 0x31, 0x32)
        /home/kadirakinkorkunc/Desktop/playground/recommendation-engine/pio_api/service/CollectorService.go:141 +0x468
created by api-test/service.HandleItemBulkEvent
        /home/kadirakinkorkunc/Desktop/playground/recommendation-engine/pio_api/service/CollectorService.go:116 +0x681

Debugger finished with exit code 0

知道如何解决这个问题吗?

edit:正如 Burak Serdar 在答案中提到的,我通过在发送前使用编组解决了 err、err2 混淆和数据竞争问题。现在它给了我真正的错误(res,err2)我猜。

2020/08/03 15:11:55 err http ->  Post "http://localhost:7070/batch/events.json?accessKey=FJbGODbGzxD-CoLTdwTN2vwsuEEBJEZc4efrSUc6ekV1qUYAWPu5anDTyMGDoNq1": read tcp 127.0.0.1:54476->127.0.0.1:7070: read: connection reset by peer

对此有什么想法吗?

您的程序中有几个错误。运行时错误是因为您正在检查 err2 是否不为零,但是您正在打印 err,而不是 err2err 为零,因此是运行时错误。

这意味着 err2 不为零,因此您应该看看错误是什么。

您提到您以 50 条为一组发送消息,但该实现是错误的。您将元素添加到 itemList,然后用 itemList 启动一个 goroutine,然后截断它并再次开始填充。这是一场数据竞争,您的 goroutines 将看到 itemList 个实例正在被处理程序修改。当您向 goroutine 提交一个时,无需截断,只需创建一个新的 itemList,这样每个 goroutine 都可以有自己的副本。

如果你想继续使用同一个切片,你可以编组切片,然后将 JSON 消息传递给 goroutine 而不是切片。

您收到的错误是您向其发出请求的服务器发送的错误。检查 以了解有关错误的更多信息。

很可能是下面的for循环

for index, barcode := range itemBulk.Barcodes{

迭代次数过多,并且由于您使用单独的 go 例程来创建请求,所有请求同时发生,这要么使服务器过载,要么使其故意关闭连接。