GO로 만드는 약국들의 마스크 재고 정보를 알려주는 서버

😷MaskInfo_API - 3/17/2020 ~ 3/19/2020 😷

약국 이름을 검색하면 약국 이름이 들어간 모든 약국 코드와 위치를 알려주며, 약국 코드를 검색하면 그에 맞는 마스크 재고량과 재고 갱신 시간 등을 알려주는 API Server

📦 사용한 패키지

"encoding/json"
"fmt"
"net/http"
"strconv"
"sync"
"github.com/labstack/echo"
"github.com/labstack/echo/middleware"

🖥️ 사용한 서버 프레임워크

https://echo.labstack.com [GO - echo]

🧚‍♂️모든 사이트를 고루틴을 통해 이동하는 코드

// 전국 약국 정보들이 들어간 54개의 사이트에 고루틴(쓰레드)를 통해 동시에 접속
for i := 1; i <= 54; i++ {
			wg.Add(1)
			go api.Drugstore(name, i, &wg, ch)
		}

🌱 사이트의 코드를 읽어오는 코드

// 사이트 코드를 모두 읽어온 후 defer 함수를 통해 작업이 끝나면 자동으로 사이트 접속 종료
resp, err := http.Get(address)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()

📃 사이트의 코드에서 원하는 값을 필터링하는 코드

// 구조체 생성 후 JSON 태그를 통해 원하는 값만 GO value로 변경
type Result struct {
	Count      int         `json:"count"`
	Page       string      `json:"page"`
	StoreInfos []StoreInfo `json:"storeInfos"`
	Sales      []Sales     `json:"sales"`
}

type StoreInfo struct {
	Code string `json:"code"`
	Addr string `json:"addr"`
	Name string `json:"name"`
}

type Sales struct {
	Code       string `json:"code"`
	CreatedAt  string `json:"created_at"`
	RemainStat string `json:"remain_stat"`
	StockAt    string `json:"stock_at"`
}   

🦍 채널을 통해 일치하는 값을 전송하는 코드 (마지막 업데이트 - 코드 수정)

// 고루틴(쓰레드)가 돌아가는 도중 채널을 통해 일치하는 값이 나왔을 때 반환이 아닌 전송함으로써 고루틴(쓰레드)간의 교착 상태가 발생하지 않음 
for _, v := range result.StoreInfos {
		if v.Name == name {
			// for i := 1; i <= 51; i++ {
			// 	go masks(v.Code, v.Addr, v.Name, i, wg)
			// }
			// jsonBytes, _ := json.Marshal(v)	// 마지막 업데이트 - 코드 삭제 
			// jsonString := string(jsonBytes)  // 마지막 업데이트 - 코드 삭제 
			// ch <- jsonString    //채널을 통한 값 전송 // 마지막 업데이트 - 코드 삭제
			ch <- v // JSON으로 다시 디코딩 할 필요 없이 인터페이스(모든 타입 담을 수 있는 타입) 형으로 전송 
		}
	}

⛓️ waitgroup을 통해 동기화

// wg.Add(1) = 작업이 시작할 때마다 wg 값을 1 증가시킴 
// wg.Done() = 작업이 끝날때마다 wg 값을 1 감소시킴
// wg.Wait() = wg 값이 0이 되기 전까지 다음 코드로 넘어가지 않음 
wg.Add(1)
wg.Done()
wg.Wait()

🎮 채널을 통해 들어온 값을 순회하며 슬라이스에 추가 시킴

//append를 통해 기존의 배열 값에 추가된 채널 값을 합침 
for i := range ch {
			slice1 = append(slice1, i)	// append함수는 첫번째 인자 + 두번 째 인자 의 값을 반환 // 첫 번쨰 인자와 두 번째 인자 타입 같아야함 
		}

📔 인코딩을 하여 슬라이스를 다시 JSON 값으로 변경하여 반환 (마지막 업데이트 - 코드 수정)

// jsonBytes, _ := json.Marshal(slice1)	// 코드 삭제 
// return c.JSONBlob(http.StatusOK, jsonBytes)	// 코드 삭제 
return c.JSON(http.StatusOk, slice1) // @api /drugstore
return c.JSON(http.StatusOk, <-ch)	// @api /masks

🍭 어떤 시간에 사용자가 어떤 웹 브라우저를 통하여 어떤 api 를 사용했는지 로그 출력

// Echo 프레임워크에서 코드를 읽어보면 내가 포맷 형식을 설정할 수 있음.
e.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{
		Format: `Time = ${time_rfc3339}], Status = ${status}, Method = ${method}, Uri = ${uri}, User_agent = ${user_agent}, Error = ${error},  Remote_ip = ${remote_ip}` + "\n",
}))

Screen Shot 2020-03-23 at 15 18 22

API - FUNCTION

@GET /drugstore

  • Request : name (QueryParam) [약국 이름]
  • Response : 약국 이름, 약국 주소, 약국 코드 (JSON) Screen Shot 2020-03-23 at 15 23 05

@GET /masks

  • Request : code (QueryParam) [약국 코드]
  • Response : 해당 약국 마스크 재고량이 갱신된 시간, 마스크 재고량, 최근 재고 갱신 시간 (JSON)

Screen Shot 2020-03-23 at 15 27 30

2020/3/23 - 마지막 코드 수정

  1. JSON 값이 들어있는 웹페이지 1 ~ 50 개를 고루틴(쓰레드)을 통해 들어간 후 사용자가 검색한 값만 찾아 채널을 통해 비동기 전송 : 속도 증가
  2. waitgroup, channel을 통해 2단 동기화 : waitgroup을 통해 1 ~ 50 개의 고루틴(쓰레드)이 모두 끝나기 전까지 기다림, channel을 통해 모든 비동기 전송 값을 받으면 클라이언트에 값을 반환
  3. channel의 버퍼 크기 수정 : 채널의 버퍼 크기를 50으로 해놨더니 신세게약국(80개 존재)을 검색하면 오류가 발생했다. 채널 버퍼 크기를 50에서 500으로 수정해줬다.
  4. JSON으로 다시 인코딩할 필요 없이 slice 와 채널 값 그대로 반환 : Echo 프레임워크에서 제공하는 JSON 반환은 어떤 값이든 자동으로 JSON으로 바꿔줌
  5. 채널 값을 반환하는 mask api 또한 slice로 변환 : 채널에 값이 들어오지 않으면 무한 로딩되는 것을 개선

🙇‍♂️ Mask Api를 만들면서 배운 점 🙇‍♂️

Github Repository - https://github.com/jjmin321/MaskInfo_api

처음 프로젝트를 진행하려고 할 때 어떻게 값을 갖고올 수 있을지 고민하다가, 크롤링 하듯이 웹사이트 전체 코드를 읽어와서 디코딩을 해준 뒤 사용할 수 있었다.

2. 🖥️ Echo 프레임워크 사용법 🖥️

간단하고 성능이 괜찮은 프레임워크를 고민하다가, GO언어 서버 프레임워크인 Echo를 사용하게 되었다.

3. 🍾 Goroutine - channel - 동기화 사용법 🍾

GO에서 주로 사용하는 고루틴(쓰레드) - 채널 을 통해 비동기적으로 값을 발신, 수신 하고 동기화를 통해 값을 모두 받은 뒤 프로그램을 종료하는 방식에 더욱 더 익숙해질 수 있었다.

4. 👨‍🌾 채널을 순회한 뒤 슬라이스에 값을 추가시키는 법 👨‍🌾

GO에서 slice - append를 통해 채널값을 순회하여 기존의 배열 값에 추가된 채널 값을 합치는 법에 익숙해질 수 있었다.

5. 💠 인코딩을 하여 슬라이스를 다시 JSON으로 바꾸는 법 💠

Echo 에서는 인코딩 되지 않은 값을 자동으로 인코딩 해서 JSON값으로 반환해준다는 것을 알게 되었다.

6. 🍭 어떤 사용자가 들어왔는지 로그를 통해 확인하는 법 🍭

middleware.LoggerWithConfig 함수를 통하여 내가 포맷 형식을 지정하고, 지정한 포맷 형식에 맞게 어떤 사용자가 들어왔는지 로그를 출력하는 법을 알게 되었다.

7. 🚛 번외 🚛

  1. 버퍼크기는 절대 작게해서는 안된다. (처음에 예상값만큼 했다가 오류가 나서 수정했다. 또한 버퍼 크기를 넓혀도 큰 성능차이가 없어서 버퍼 크기를 크게 하는 것이 좋을 것 같다.)
  2. Json으로 다시 인코딩 할 필요 없이 반환하면 된다. (처음에 인코딩 후 반환했다가 계속 JSON 값에 이상한 문자가 추가되어서 당황했다.)
  3. 웬만하면 채널 값을 순회해서 슬라이스에 추가시킨 후 반환하는 것이 좋다. (채널에 값이 들어오지 않거나, 예상값을 벗어나면 동기화되어있어 무한로딩이 된다. 따라서 슬라이스로 반환하는 것이 좋다.)

Categories:

Updated:

Leave a comment