문제 상황 

현재 EC2 우분투에 Nginx - Uwsgi - Flask 로 서버가 구성되있는 상태이다.

동시접속자 테스트를 하기 위해서 Jmeter를 이용하여 API 요청을 100개, 200개, 300개를 하는데

100개, 200개일 때는 없던 error가 300개일 때는 20~30% 개씩 생기기 시작했다.

어떤 error인지 확인을 해보았고, 

응답코드가 502 인것을 확인을 하였다. 제일 먼저 아랫단인 uwsgi log를 확인해 봤다.

uwsgi에서는 502로 응답을 보낸 것이 없었다.

즉, uwsgi로 아예 요청이 들어오지 않았다는 것을 알 수 있었다.

그래서 그것보다 윗단인 nginx로그를 확인을 했다. 

2016/06/08 07:45:13 [error] 9913#0: *394 connect() to unix:/home/ubuntu/idletower/server.sock failed (11: Resource temporarily unavailable) while connecting to upstream, client: 175.196.62.240, server: , request: "GET /v1/test HTTP/1.1", upstream: "uwsgi://unix:/home/ubuntu/idletower/server.sock:", host: "52.193.46.113"
"/var/log/nginx/error.log" [readonly] 1646L, 533533C

라는 Error 로그를 발견할 수 있었다


 
해결과정

에러 로그에서 발견한

(11: Resource temporarily unavailable) while connecting to upstream

키워드를 이용해서 구글 검색을 했다. 


를 확인해보면 위의 에러로그는 socket 리소스가 일시적으로 사용할 수 없는 상태라는 것을 의미하고 있엇다.

그것은 현재 socket high load, busy 하다는 것이다.


현재 테스트 하는 서버에서는 uwsgi가 server.sock이라는 유닉스 소켓을 생성해서 그 socket을 이용해 nginx와 통신을 하는데

그 socket이 어떤 제한이 걸렸기 때문에 안되는 거라고 생각을 했다. 


더 검색을 하다

http://simp1e.tistory.com/39 이 곳을 참고하게 되었다.

위 링크를 따라서


sudo sysctl -a | grep somaxconn 


으로 socket max connection 128 밖에 되지 않는것을 확인하고 


sudo /sbin/sysctl -w net.core.somaxconn=1024


추가 시켰다. 다시 테스트를 해봤지만 별 차이가 없었다.

ulimit -a 

라는 명령어를 통해서 open files와, max user processes 수도 늘려봤지만, 별로 달라진게 없었다.

그러다 uwsgi  로그를 확인하던 중 

your server socket listen backlog is limited to 100 connections

라는 메세지를 발견하게 되었고, 구글링을 통해서 backlog를 증가시키는 법을 찾아냈다.


uwsgi 설정에서 

listen = 1024

를 입력해서 다시 서버를 Start 시키니, 1024로 backlog가 늘었다.


그리고 400개의 api를 보내봤는데 error가 발생하지는 않았다. 

여기서 net.core.somaxconn 의 갯수와는 무슨 상관이 있을까 싶어서 숫자를 줄이고 다시 서버를 시작하니


Listen queue size is greater than the system max net.core.somaxconn (1000).


라는 오류가 발생했다.



즉 socket backlog 갯수는 net.core.somaxconn 갯수와 같거나 작아야 하는 것이다. 

다 됐다고 생각하고 더 높은 인스턴스에서 1000개를 보내는 테스트를 하는데 에러가 몇 개정도 나서 확인 해보니

nginx에러 로그에서 Too many open files 이라는 것을 확인하게 되었다.

sudo sh -c "ulimit -n 65535 && exec su $LOGNAME" // open file


이렇게 다 늘리고 나니, 높은 인스턴스에서 정상적으로 1000개, 2000개에도 빠르게 모든 것을 처리하는 것을 확인할 수 있었다.


참고로 open file이나 process 제한을 영구적으로 변경하고 싶다면 

/etc/security/limits.conf 

의 맨 아랫줄에 
# open files
     ubuntu hard nofile 512000
     ubuntu soft no file 512000 

# process
     ubuntu hard nproc 512000
     ubuntu soft nproc 512000

을 추가해주자. 


Element Tree 를 이용한다. python3.5 기준으로 내장되어있다. 


1. Xml이 문자열일 때


다음과 같은 Xml string이 있다.






getroot 함수는 root 태그인 GXG_RES 라는 태그를 받아온다.

그리고 그 하위 태그는 바로 result이기 때문에 result를 find하고,

다시 또 거기서 하위를 찾는 등으로 찾을 수 있다.

findall을 이용해서 모든 태그를 다 찾을 수도 있다. 


2.xml이 파일일 때




나머지는 위와 동일하게 할 수 있다. 






개발을 하다보면 시간, 날짜와 관련된 일을 할 때가 종종 있다. 

개발 중 파이썬 Datetime 모듈을 이용해서 사용한 몇가지를 정리해야겠다.



1) 현재시간 보다 (얼만큼) 전, 후 



한국 시간의 경우 utc보다 9시간 뒤이다. 즉 utc가 오전 9시라면, 한국시간은 저녁 6시인 것이다. 

utc 시간을 이용해 9시간 뒤의 한국시간을 구하기 위해서는 utc시간에 +9시간을 하면 되는 것이다. 

그럴 때 쓰는 것이 datetime.timedelta 이다.




이런식으로 계산할 수 있다. 물론 timedelta() 괄호 안에 parameter로는 minutes, days, 등이 들어갈 수 있겠다. 





2) String으로 표현된 datetime을 datetime 객체로 변환 시키기



클라이언트로 부터 클라이언트의 시간을 받았지만 그 시간의 형태는 string이다. 이것을 datetime 객체로 바꿀 필요가 있다


그리고 나서 type을 확인해보면 datetime.datetime 임을 알 수 있다. 






3) datetime 간의 시간 차이


내가 직면한 문제는 서버의 시간은 utc를 쓰지만 클라이언트는 클라이언트 개인 시간을 쓰기 때문에, 

서버 시간과 클라이언트 시간의 차이를 구해서 그 시간만큼을 timegap으로 사용해야 하는 것이었다. 


간단하게 차이 연산으로 해결할 수가 있다. 



여기서 



라고 확인을 해보면 

32406.608232 이라는 값이 나온다. 


반대로 utc_time - kor_time을 해서 똑같이 확인을 해보면

-32406.608232 이라는 값이 나온다.


한국 시간이 utc 시간보다 더 빠르기 때문에 한국 시간에서 utc 시간을 빼면 양수가 나오고, 반대의 경우 음수가 나오는 것이다.

이것은 초 단위 시간 차이이기 때문에 분 차이로 알고 싶으면 /60, 시간 차이로 알고 싶으면 /3600을 하면 된다. 


나는 시간을 알고 싶기 때문에 /3600을 해보겠다. 


결과는 9.00183562 즉 9시간이다 라는 것을 알 수 있다. 

필자는 단순히 몇 시간 차이가 나는지를 구할 필요가 있었기 때문에, 반올림을 하여서

9라는 결과값을 얻을 수 있었다. 










게임 내 인앱 결제의 보안성을 위해서 클라이언트가 지급 받은 영수증과 Google API를 이용하여서
그 영수증이 유효한 영수증인지를 확인할 필요가 있다.

서버 검증이 진행되는 순서는 다음과 같다.
  1. 클라이언트의 요청이 오면 developerPayload(임의의 String)를 발급하고, 서버 DB등에 저장을 한다.
  2. 클라이언트가 서버에게 발급받은 developerPayload를 이용해서 구글 플레이에 결제요청을 하고, 그 결과로 받은 developerPayload를 서버에 보낸다.
  3. 서버는 클라이언트에게 받은 developerPayload가 자신이 발급한 developerPayload가 맞는지 확인한다. 
  4. developerPayload가 일치하는지를 확인하고 그 결과를 보내준다.
  5. 일치한다면 클라이언트는 서버에 영수증 정보를 보내고, 서버는 그 영수s증 정보를 가지고 Google API에 검증요청을 한다.
  6. 검증이 완료되면 Client에게 상품을 지급한다.

여기서 3번 이후 과정에 대해서 자세히 설명해 보겠다.



구글 개발자 콘솔, 구글 플레이 개발자 콘솔 설정 

먼저 구글 API를 사용하기 위해서는 Credential을 만들어야 한다.

에서 프로젝트를 생성을 한다.



생성을 한 후에 좌측 상단에 있는 메뉴 버튼을 눌러서 ‘권한’ 으로 이동한다. 


여기서 서비스 계정 만들기를 클릭한다. 



이렇게 생성을 하면 자동으로 내 컴퓨터에 json 파일이 다운로드 받아진다. 
이 json 파일 내부에는 여러가지 정보가 기술되어 있다. 그리고 이 json 파일은 프로젝트 내부에서 접근할 수 있도록 관리를 한다. 

그리고 API 관리자 개요에 들어가서 만든 프로젝트를 사용하도록 사용 버튼을 누른다.

여기까지가 google 개발자 콘솔에서 하는 작업이다.


이제 실제로 앱을 등록하는 Google Play Console에 들어간다.
왼쪽에 있는 설정을 누른다.
API 액세스에 들어가면 시작하기가 나오고 밑에 프로젝트들이 나열되어 있는데 이전에 만든 프로젝트를 클릭해서 사용하게 한다. 

그러면 이제 사용준비가 완료가 된 것이다.



서버구현

파이썬을 이용하기 때문에 구글 api를 사용하기 위해서 

     $ pip install --upgrade google-api-python-client

를 이용해서 클라이언트 모듈을 설치를 한다. 



이제 구글 API를 사용하기 위해서는 먼저 사용자가 API를 사용하기에 적합한 사용자인지를 검증해야한다. 
그래서 Credentials 객체를 생성하도록 한다. 우리는 json파일을 받았기 때문에 다음과 같이 코드를 작성한다. 

from oauth2client.service_account import ServiceAccountCredentials
scopes = ['https://www.googleapis.com/auth/androidpublisher']

credentials = ServiceAccountCredentials.from_json_keyfile_name(
    'app/google/Idletower-174c42ca2fb9.json'scopes)


생성한 credentials 객체의 authorize 메소드를 이용해서 http 인스턴스를 생성하도록 한다. 

from httplib2 import Http

http_auth = credentials.authorize(Http())

여기서 

Client가 구글 플레이 스토어로 부터 받는 영수증 정보는 다음과 같다. 
{
    "orderId":"1299912121692121212121.1320812112121",
    "packageName":"com.test.runrunrun",
    "productId":"com.test.runrunrun.item_001",
    "purchaseTime":1393377621833,
    "purchaseState":0,
    "purchaseToken":"sekadakadaaxgjhsrLn61wGoyAl..._8-l8zsUhVpY7o7Zq08s"
}
- orderId : 주문번호
- packageName : 앱의 패키지명(구글 내에서는 중복되지 않습니다.)
- productId : 상품 ID
- purchaseTime : 구매시각 ms 입니다.
- purchaseState : 구매상태
- purchaseToken : 구매 token



위의 정보 중에 Server가 필요로 하는 것은 세 가지 이다.
     packageName,  productId,  purchaseToken


googleapiclient.discovery.build를 이용해서 build를 다음과 같이한다. 
그리고 클라이언트에서 받아온 productId, packageName, token을 이용해서 api 호출을 한다. 

from googleapiclient.discovery import build

androidpublisher = build('androidpublisher''v2'credentials=credentialshttp=http_auth)
product = androidpublisher.purchases().products().\
    get(productId=productIdpackageName=packageNametoken=token)

purchase = product.execute()


purchase는 다음과 같은 정보를 나타내고 JSON 객체이기 때문에 바로 사용이 가능하다. 

{
 
"kind": "androidpublisher#productPurchase",
  "purchaseTimeMillis": long,
  "purchaseState": integer, // 0 이면 구매 된 것, 1이면 cancel 된것
  "consumptionState": integer, // 0 이면 아직 consume이 되지 않았고, 1이면 consumed된 것.
  "developerPayload": string
}

이 것을 이용해서 구매 확인을 하고 물품을 지급하면 된다. 





http://gyus.me/?p=418

http://flask-docs-kr.readthedocs.org/ko/latest/errorhandling.html



출처 : 스포카 기술 블로그

https://spoqa.github.io/2012/08/03/about-python-coding-convention.html



+ Recent posts