728x90

메시지 큐란?

메시지 큐 사이 비동기 통신 프로토콜을 제공 송신자  수신자 가 같은 시간에 메시지 큐와 상호 작용 필요가 없습니다 메시지를. 큐에 배치 된 메시지는받는 사람이 검색 할 때까지 저장됩니다.

메시지 큐 패러다임은 pub-sub 패턴 의 형제입니다 . 그러나 pub-sub 패턴을 사용하면 게시자 라고하는 메시지 발신자 가 어떤 구독자가 존재하는지 알지 못해도 채널을 통해 구독자 라고하는 수신자 에게 메시지를 게시 할 수 있습니다. 모든 구독자는 수신 된 메시지가 동시에 메시지를 수신 할 수있는 시점에 존재합니다..

 

Redis는 pub-sub 패턴 을 명시 적으로 지원합니다 . 그러나이 기사에서는 Redis의 List 기본 제공 유형을 사용하여 Message Queueing을 구현하는 방법을 살펴 봅니다.

 

게시물 끝에서 Redis가 지원하는 Job Dispatcher (Nodejs) 및 Job Consumer (Golang)를 구현하는 방법을 살펴 보겠습니다.

Redis의 List 데이터 구조에 대해 알아보기

Redis에는 삽입 순서로 정렬 된 문자열 목록 인 내장 목록 데이터 유형이 있습니다. 목록 LPUSH의 맨 앞 ( RPUSH) 또는 꼬리 ( )에 요소를 밀어 넣을 수 있습니다 .

LPUSH mylist a   # now the list is "a"
LPUSH mylist b   # now the list is "b","a"
RPUSH mylist c   # now the list is "b","a","c" (RPUSH was used)

다음은 목록에 사용할 수있는 모든 요소입니다.

목록 명령 (확대하려면 클릭)

redis-cli 작업

이 게시물에서는 기본 메시지 대기열 기능 을 구현 하기 위해 PUSH  POP 작업을 사용하는 방법을 살펴 봅니다 . AWS의 다음 다이어그램에 표시된 것처럼 메시지 대기열은 Redis 용 Amazon ElastiCache 의 사용 사례 중 하나 입니다.

그러나이 게시물에서는 시리즈의 첫 번째 기사에서 수행 한 로컬 Redis 설치를 사용합니다.

를 사용하여 명령을 시도해 보겠습니다. redis-cli먼저 1 부에서 Redis VM 에 연결해야합니다 .

$ vermin ssh vm_01

위와 같이 오른쪽에있는 두 개의 스택 터미널에서 실행했습니다. BLPOP jobQueue 0즉, 이름이 지정된 목록의 선두에서 POP를 실행 jobQueue하고 요소가 추가 될 때까지 영원히 기다립니다 (따라서 시간 제한 값 0).

그리고 세 번째 터미널 (오른쪽 중 하나)에서 목록 끝에 2 개의 요소 ( "Hello"및 "world")를 푸시했습니다.

두 요소를 푸시하면 각 요소가 클라이언트 세션에 의해 팝되고 있음을 알 수 있습니다.

다음 섹션에서는 Node.js와 Golang을 사용하여 간단한 작업 대기열을 구현하고 Redis를 메시지 대기열로 사용할 것입니다.

간단한 작업 대기열 구현

먼저 환경을 설정하겠습니다. Nodejs 앱을 사용하여 작업을 작업 대기열 ( job-dispatcher ) 로 발송 하고 Golang 앱을 사용하여 작업을 소비하고 작업 ( job-consumer )합니다. 디스패처와 소비자로부터 몇 가지 애플리케이션을 배포 할 것입니다.

이제 Redis VM 내부에 로컬 파일 시스템 디렉터리를 마운트 할 수 있는 vermin의 한 기능을 사용해 보겠습니다 . 호스트 OS 콘솔에서 다음을 작성하십시오.

$ mkdir -p ~/temp/mq
$ vermin mount vm_01 ~/temp/mq

호스트 OS에 코드를 작성한 다음 VM 내에서 실행 해 보겠습니다.

IntellijIDEA를 사용하지만 원하는 편집기를 사용할 수 있습니다 (VSCode도 좋은 선택입니다).

내부에 두 개의 디렉토리를 만들 것입니다 ~/temp/mq.

$ mkdir -p ~/temp/mq/{job-dispatcher,job-consumer}

먼저 Golang ( go-redis 사용)에서 작업 소비자를 구현 하고 redis-cli클라이언트 에서 메시지를 전송하여 테스트합니다 .

다음은 소스 코드입니다.

// ~/temp/mq/job-consumer/main.go
package main

import (
   "fmt"
   "github.com/go-redis/redis/v7"
   "log"
   "time"
)

const key = "myJobQueue"

func main() {

   c := redis.NewClient(&redis.Options{
      Addr: "localhost:6379",
   })

   fmt.Println("Waiting for jobs on jobQueue: ", key)

   go func() {
      for {
         result, err := c.BLPop(0*time.Second, key).Result()

         if err != nil {
            log.Fatal(err)
         }

         fmt.Println("Executing job: ", result[1])
      }
   }()

   // block for ever, used for testing only
   select {}
}
$ GOOS=linux go build
vermin@verminbox:~$ /vermin/job-consumer/job-consumer
Waiting for jobs on jobQueue:  myJobQueue
vermin@verminbox:~$ redis-cli
127.0.0.1:6379> RPUSH myJobQueue "100"
(integer) 1
redis-cli를 사용하여 작업 소비자 앱에 작업 보내기

다음 단계는 Nodejs 에서 작업 디스패처를 만들고 높은 빈도로 여러 작업을 보내는 클라이언트를 시뮬레이션하는 것입니다.

작업 디스패처 구현

Nodejs 내에서 Node Redis 를 Redis 클라이언트로 사용할 것입니다 . 소스 코드는 다음과 같습니다.

// ~/temp/mq/job-dispatcher/index.js
const redis = require("redis");
const client = redis.createClient({
    address: "localhost:6379"
});

client.on("error", function (error) {
    console.error(error);
});

let myArgs = process.argv.slice(2);
let start = Number(myArgs[0])
let end = start + 10000

console.log(end)

for (let i = start; i < end; i++) {
    client.rpush("myJobQueue", i);
}

client.quit();
 

하지만 먼저 job-consumer6 개의 다른 터미널에서 6 개의 인스턴스를 실행 해 보겠습니다 . ( /vermin/job-consumer/job-consumer이전 섹션에 표시된 명령 사용 )

 

그리고 job-dispatcher다음과 같이 두 개의 터미널 에서 2 를 실행 해 보겠습니다 .

$ cd /vermin/job-dispatcher
$ node index.js 0
$ cd /vermin/job-dispatcher
$ node index.js 10000

Redis의 Lists를 Message Queue 로 사용하고이를 사용 하여 NodeJs에서 job-dispatcher 를 구현하고 Golang에서 job-consumer 를 구현하여 Job Queue 를 구축하는 방법 을 살펴 보았습니다.

RabbitMQ 또는 ActiveMQ와 같은 완전한 메시지 브로커를 가져 오는 대신 간단한 메시지 큐가 필요한 경우이 방법을 간단한 대안으로 사용할 수 있습니다.

 

Github에서 디스패처 및 소비자에 대한 소스 코드를 찾을 수 있습니다. https://github.com/mhewedy-playground/redis-mq

 

게시물에서 본 구현은 기본 작업 대기열이지만 처리중인 작업이 포함 된 처리 대기열을 포함하는 고급 작업 대기열을 찾는 경우 명령 RPOPLPUSH및 BRPOPLPUSH설명서를 확인하여 신뢰할 수있는 대기열 사용 사례를 확인할 수 있습니다 .

 

참조 : Korean (ichi.pro)

반응형
728x90

1. 개요[편집]

수강 신청, 예매, 온라인 접수, 게시판 폭주 등 동시 접속 폭주로 발생하는 시스템 마비와 서비스 중단을 방지할 수 있는 솔루션을 말한다.

접속을 거부하거나 지연시키는 기존의 접근제어 솔루션과 달리 임계치를 초과하는 접속 요청은 대기 정보를 제공해 순차적으로 접속할 수 있도록 한 것이다. 기존 접속자의 접속을 최대한 보장함과 동시에 접속 예상시간과 대기 인원을 확인할 수 있도록 함으로써 웹 접속자의 이용 편의성도 개선한 솔루션이다.

2. 원리[편집]


서비스를 제공하는 메인 서버와 접속제어 솔루션이 위치한 대기 서버를 별개로 분리시킨 후, 모든 요청을 대기 서버로 리다이렉트 한다. 이후 접속제어 솔루션은 설정된 동시접속 수 및 초당 접속인원을 제어하며 대기순번 부여 및 API를 통한 메인 서버의 접근 권한을 제어한다.

대기순번 부여방식은 선착순 부여방식과 동시 부여방식이 있다. 선착순 부여방식은 예정된 시간부터 접속한 순서대로 순번을 부여하지만, 시간이 되자말자 새로고침 또는 접속을 하는 사람이 많아 자칫하면 대기 서버가 터져 버리거나 바이패스[1]가 작동해 메인 서버까지 터져버릴 수 있다.

동시 부여방식은 예정된 시간까지 접속해있던 모든 클라이언트에게 랜덤으로 대기순번을 부여하고, 이후 접속자는 제일 후순위로 배정하는 방법이다. 사람들이 순차적으로 접속하기 때문에 서버에 가해지는 부담은 적으나, 유리한 순번을 얻기 위해 중복 접속하는 경우가 많다.

3. 도입[편집]

대학교, 기업, 공공기관, 정부, 각종 시험 접수 관련 사이트 등.

주로 수강신청이나 명절 기차표 예매와 같이, 짧은 시간에 대량의 접속자가 몰리면서도 누락되거나 중복되는 등의 잘못된 정보가 발생하면 안 되는 상황에서 사용한다. 게임에서도 주말 저녁과 같이 접속자가 몰리면 품질 유지의 일환으로 대기열이 발생하기도 한다.

클라우드 시스템이 도입된 IT 서비스들에서는 의외로 접속제어를 찾아보긴 어렵고, 백엔드 시스템이 열악하고 각종 서버와 프레임워크로 연계된 정부와 공공기관 사이트에서 절찬리에 사용되고 있다.

4. 개발 업체[편집]

  • (주)에스티씨랩 - NetFUNNEL(넷퍼넬)
  • (주)웰컨 - TRACER v2.0
  • (주)데브와이 - MEGA-FENCE

 

퍼옴 : 나무위키 대량접속제어

반응형
728x90

✔개요

대기열 시스템의 탄생 배경

이번에 회사에서 새로운 상품을 오픈하기 위해 대대적인 마케팅?을 진행한다는 것을 전달받아 대기열 시스템을 설계, 개발을 진행했습니다. 


왜? 우리는 대기열 시스템이 필요했을까?

현재 대고객 서비스를 위한 개발 및 운영하고 있으며, 상품에 대한 주요정보 등을 코어영역와의 통신을 통해 처리하고 있다. (우리는 고객을 상대하는 채널이다.)
우리가 도입하려는 대기열 시스템은 고객의 동접을 대응하지 못할때 FIFO(선입선출) 방식으로 순차적으로 트래픽을 처리하기 위한 방안입니다.
레거시 시스템의 경우에는 On-Premise 환경으로 구성이 되어있어 트래픽이 몰릴 시 서버 확장 대응에 어려움이 있다. (오토 스케일링이 불가능) 신규 시스템의 경우에는 (클라우드 환경) 트래픽이 몰릴 시 서버 확장 대응에 용이 하다

결국에 레거시 시스템에서 병목이 발생하고... 모든 사이트는 마비가 된다. 
우리는 장애전파(Circuit Breaker)를 막기 위해 대기열 시스템을 만들자!

 


✔ 설계시 생각한 방안

  • 1안
    • MQ(Kafka)를 활용한 대기열 시스템.
    • 장점
      • 미들웨어를 활용하므로써 정확한 MQ 시스템 구성
      • 속도 및 안정성 최고
      • 구축 경험이 있어 러닝커브 적음
    • 단점
      • 기존에 도입되지 않아서 구성 시간이 필요.
      • 단발성으로 사용하기에 비용적 측면으로 비효율적.
  • 2안
    • Redis를 활용한 대기열 시스템
    • 장점
      • List, Sorted Set와 같은 자료형을 사용하면 MQ 구현가능 할 듯
      • Cache의 장점 부각, 속도 최고.
      • 러닝커브 적음
    • 단점
      • Redis가 죽으면 끝이다.

 ✅ 우리는 2안을 선택했고 시간적인 여유가 없었고, 현명한 선택이라 생각한다.

 


 시스템구성

우리는 k8s 기반의 MSA 시스템 환경을 맞추기 위해, Spring Boot 기반의 Spring WebFlux를 도입하여 사용하였다.
Spring WebFlux는 Srping MVC와는 달리 Servlet과는 관계없이 만들어졌으며, WebFlux에서의 웹서버는 기본 설정은 Netty기반이지만 우리는 Spring Boot 에서 공식 지원 내장하는 (Lightweight, WebFlux에 적합, WebSocket 지원) Undertow를 적용했다.
WebFlux를 기반으로 논블록킹(Non-blocking) 비동기(Async) 프로그래밍을 도입했으며 Redis를 활용하였는데 reactive에 맞추어 제공되는 redis-reative 라이브러리를 사용햇다.

Fornt end와의 통신은 WebSocket과 REST API를 통해 통신을 진행하였으며,
Stomp(Simple Text Oriented Mssage Protocol)을 통해 WebSocket을 효율적으로 다루기 위한 프로토콜을 적용했다.
기존 WebSocket과는 다르게 Stomp는 Publisher/Subscriber 구조로 되어있다.
(즉, 구독한 사람들에게 메시지를 요청하면 메시지를 전달하는 형태)
대기열에서 작업열로 이동하는데 사용한 Spring-Batch도 도입하였다.

  • Java 11
  • Undertow
  • AWS WebServer
  • websocket (Stomp, SockJS)
  • Spring webflux
  • Redis-reactive
  • Junit5
  • Spring Batch

 


✔ 대기열의 구성은?

대기열의 구성은 오직 Redis와 Spring Boot 기반으로 구축하였다.
Redis 자료형 중 하나인 SortedSet을 사용했으며,
SortedSet은

  • Set과 Hash가 혼합된 타입이며,
  • 하나의 Key에 Score와 Value로 구성되어 있다.
  • Score기준으로 정렬이 가능하고
  • Queue 처럼 사용 가능하다

SortedSet 채택이유는 아래와 같다.

  • 각 명령어들의 효율적인 시간복잡도 O(1) ~ O(log(N)
    • 현재순위 조회 - Element의 현재위치 확인가능 ZRANK 명령어
      • 시간복잡도 - O(log(N))
    • 대기자 전체 크기 - Value의 전체 크기 ZCARD
      • 시간복잡도 - O(1)
    • 대기열에서 이동하는 Element Range 조회
      • 시간복잡도 - O(log(N))
    • 대기열의 Element 삭제
      • 시간복잡도 - O(log(N))
      • 자료형 List을 사용했을때는 Element 삭제시 시간복잡도 O(N)

 ✅ 우리가 대기열을 구성하기에 필요한 Queue를 적용할때 충분한 요건을 가진 자료형이다.

 


  • 대기열 로직
    1. 고객 대기열 페이지 진입.
    2. 대기열 Queue에 고객 Key값 Insert
    3. Batch에서 일정 시간마다 작업업열로 이동 가능한 Capability 확인
    4. 가능한 Capability 있으면 대기열에서 작업열로 이동.
  • 대기열 페이지에 진입한 고객
    1. 현재 위치 확인, 대기열 전체 사이즈 및 작업열 이동여부 조회.
    2. 작업열 이동 가능 여부 확인.
    3. 작업열에 유효한 Key값인지 확인
    4. 기존 발급된 이력이나, 만료된 Key값인지 확인.
    5. 동접 Ticket 발부.
    6. 세션 유지.
    7. 고객 이탈시 expire을 통해 세션 삭제.

 🔥 즉, 고객은 대기열 진입시 발급된 Key 값을 통해 작업열까지 진입하고, 티켓을 발급 받으면 티켓과 key값을 통해 고객 세션을 유지한다. 고객 세션이 만료되면 작업 가능한 Capability가 생긴다.


✔ WebSocket (Stomp)

WebSocket (Stomp) 사용 이유

  • 우리는 REST API를 통해서 통신을 진행할 수 있었지만 WebSocket을 사용했다.
  • 왜?
    • HTTP 통신의 Connection에 Cost를 줄이고자 하였고, WebSocket을 적용하였을때 Handshake를 최초에만 진행하여 전체적인 네트워크 Cost를 줄일 수 있다는걸 결론으로 도출했다.
    • WebSocket은 HTTP Protocol로 Connection을 초기에 맺고, ws(WebSocket),wss Protocol로 Upgrade한 후 서로에게 Heartbeat를 주기적으로 발생시켜 Connection을 유지하고 있는지 확인한다.
    • WebSocket(Stomp)은 브라우저에 대한 호환성 때문에도 채택을 진행했다.

 💡 즉, Cost를 절감해 고객의 유입폭이 더 증가 할 수 있다 라는 결과를 뽑았다. ( HTTP API 통신을 안한 것은 아니다. 필요에 따라 사용했다. )

WebSocket (Stomp) 연결

@Slf4j
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    private final StompHandler stompHandler;

    public WebSocketConfig(StompHandler stompHandler) {
        this.stompHandler = stompHandler;
    }

    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        config.setApplicationDestinationPrefixes("/app");
        config.enableSimpleBroker("/topic");
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/connect")
                .setAllowedOriginPatterns("*")
                .withSockJS()
                .setClientLibraryUrl("<https://cdnjs.cloudflare.com/ajax/libs/sockjs-client/1.3.0/sockjs.min.js>");
    }

    @Override
    public void configureClientInboundChannel(ChannelRegistration registration) {
//        registration.interceptors(new HeaderCheckInterceptor());
        registration.interceptors(stompHandler);
    }

}
  • registerStompEndpoints
    • Websocket Connection에 관련된 설정이다.
    • SockJS를 이용해 STOMP end-point를 설정해준다.
    • withSockJS()
      • 브라우저에서 Websocket을 지원하지 않을 경우 Fallback 옵션을 활성화 하는데 사용한다.
      Enable SockJS fallback options.
    • setAllowedOriginPatterns("*")
      • WebSocket에서 Cors 처리를 위한 허용 패턴.
      • 일정 버전부터 setAllowedOrigins 메서드가 사용되지 않아 setAllowedOriginPatterns을 사용함.
      Alternative to setAllowedOrigins(String...) that supports more flexible patterns for specifying the origins for which cross-origin requests are allowed from a browser. Please, refer to CorsConfiguration.setAllowedOriginPatterns(List) for format details and other considerations. By default this is not set. Since: 5.3.2
  • configureMessageBroker
    • 메시지브로커에 대한 Prefix 설정.
    • config.setApplicationDestinationPrefixes
      • Socket 통신시 End-Point 목적지의 Prefix 설정이다.
      • 즉, Client Side에서 Server 사이드로 보내는 Message
      Configure one or more prefixes to filter destinations targeting application annotated methods. For example destinations prefixed with "/app" may be processed by annotated methods while other destinations may target the message broker (e.g. "/topic", "/queue"). When messages are processed, the matching prefix is removed from the destination in order to form the lookup path. This means annotations should not contain the destination prefix. Prefixes that do not have a trailing slash will have one automatically appended.
    • config.enableSimpleBroker
      • Subscriber에게 메시지를 보낼때의 목적지의 Prefix 설정이다.
      • 즉, Server Side에서 Client Side로 보내는 Message
      Configure the prefix used to identify user destinations. User destinations provide the ability for a user to subscribe to queue names unique to their session as well as for others to send messages to those unique, user-specific queues. For example when a user attempts to subscribe to "/user/queue/position-updates", the destination may be translated to "/queue/position-updatesi9oqdfzo" yielding a unique queue name that does not collide with any other user attempting to do the same. Subsequently when messages are sent to "/user/{username}/queue/position-updates", the destination is translated to "/queue/position-updatesi9oqdfzo". The default prefix used to identify such destinations is "/user/".
  • configureClientInboundChannel
    • Inbound 메시지에 대한 Intercepter처리를 할 수 있다.
    • JWT 인증과 같은 인증 로직에 주로 이용하고 있으며, @ChannelInterceptor 어노테이션을 이용하니 필요하면 참고해보길 바란다.

publish / subscribe

@Slf4j
@RestController
public class SocketHandler {

    @MessageMapping("/sendMessage/{key}")
    @SendTo("/topic/public/{key}")
    public String hello(String str){
        log.info("Check in hello -> " + str);
        return "your message -> " + str;
    }

}
  • @MessageMapping
    • Publish Target Url
    • Client to Server
    Destination-based mapping expressed by this annotation. For STOMP over WebSocket messages this is AntPathMatcher-style patterns matched against the STOMP destination of the message.
  • @SendTo
    • Subscribers 한테 Message를 전송한다
    • Server to Client
  • {key}
    • 우리는 고객마다 고유한 End-Point를 Key값을 통해 지정했다.

Junit 을 이용한 Client Test

@ActiveProfiles("local")
class SocketControllerTest {

    final String TARGET_URI = "<http://localhost:30001/connect>";
		final String SENDMESSAGE_URI = "/app/sendMessage/123456";
		WebSocketStompClient stompClient;

    private List<Transport> createTransportClient(){
        List<Transport> transports = new ArrayList<>();
        transports.add(new WebSocketTransport(new StandardWebSocketClient()));
        return transports;
    }

    @BeforeEach
    public void setup() throws InterruptedException{
        stompClient = new WebSocketStompClient(new SockJsClient(createTransportClient()));

        stompClient.setMessageConverter(new MappingJackson2MessageConverter());
    }

		@Test
    public void contextLoad() throws ExecutionException, InterruptedException, TimeoutException {

            WebSocketHttpHeaders httpHeaders = new WebSocketHttpHeaders();
            httpHeaders.add("jwt" , "test");
            StompHeaders stompHeaders = new StompHeaders();
            StompSession stompSession = stompClient.connect(TARGET_URI, httpHeaders, stompHeaders, new StompSessionHandlerAdapter() {
            }).get(1, TimeUnit.SECONDS);

            // Send
            stompSession.send(SENDMESSAGE_URI, "test");

		}
}
  • Junit의 WebSocketStompClient을 사용하여 Server side와의 WebSocket 연동 Test를 진행.
    • 소스참고.

 


✔ Redis

대기열에 큰 Point를 가지고 있는 Redis에 대한 셋팅이다.

Redis reative 설정

@Slf4j
@Configuration
@EnableCaching
public class RedisConfiguration {

    @Value("${spring.redis.host}")
    private String host;
    @Value("${spring.redis.port}")
    private int port;

    
    @Bean
    public RedisConnectionFactory redisConnectionFactory() {
        return new LettuceConnectionFactory(host, port);
    }

    @Bean
    public RedisTemplate<String, Object> redisTemplate() {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory());
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        return redisTemplate;
    }

    @Bean
    public ReactiveRedisTemplate<String, String> reactiveRedisTemplate(ReactiveRedisConnectionFactory connectionFactory) {
        RedisSerializer<String> serializer = new StringRedisSerializer();
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(String.class);
        RedisSerializationContext serializationContext = RedisSerializationContext
                .<String, String>newSerializationContext()
                .key(serializer)
                .value(jackson2JsonRedisSerializer)
                .hashKey(serializer)
                .hashValue(jackson2JsonRedisSerializer)
                .build();

        return new ReactiveRedisTemplate<>(connectionFactory, serializationContext);
    }

}
  • Redis를 Reative 방식으로 Template 설정.

주로 사용한 RedisUtils

@Slf4j
@Component
@AllArgsConstructor
public class RedisUtils {

    private final RedisTemplate<String ,Object> redisTemplate;

    private final ReactiveRedisTemplate<String, String> reactiveRedisTemplate;

		/**
     * @desc: Sorted Set 삭제.
     */
		public Mono<Long> delete(String key){
       return reactiveRedisTemplate.delete(key);
    }

		/**
     * @desc: Sorted Set 조회
     */
		public Mono<String> getValue(String key){
        return reactiveRedisTemplate.opsForValue().get(key);
    }

		/**
     * @desc: RedisTemplate에 SortedSet 초기화.
     */
    public ReactiveZSetOperations opsForZSet(){
        return reactiveRedisTemplate.opsForZSet();
    }

		/**
     * @desc: Sorted Set 자료형 사이즈
     */
    public Mono<Long> zCard(String str){
        ReactiveZSetOperations z = reactiveRedisTemplate.opsForZSet();
        return z.size(str);
    }

		/**
     * @desc: Sorted Set 자료형 start ~ end 까지 조회.
     */
    public Flux<String> zRange(String key, Long start, Long end){
        return opsForZSet().range(key, Range.closed(start, end));
    }

		/**
     * @desc: Sorted Set 자료형 Value의 현재위치 조회.
     */
    public Mono<Long> getzRank(String key, String value){
        return opsForZSet().rank(key, value);
    }
}

 


✔ 스트레스 테스트

고민한 설계와 개발을 끝냈으니 성능 테스트를 진행했다.
테스트 Tool 선택에 대한 고민이 컸다. nGrinder, JMeter들을 사용했으나 정확한 테스트가 진행되지 못했고, JUnit을 통해 부하 테스트를 진행했다.

시나리오

  • Connection → Subscribe → Send 와 같은 시나리오로 부하 테스트를 진행했다.

Stress Size

  • 약 15분간 총 12만건 진입
    • 분당 약 7천명
    • 초당 약 120명

결과

아래 수치를 보면 더 정확하게 확인이 가능하다.

  • Redis는 역시나 강력했고, 12만명의 동접자를 충분히 버틸 수 있는 수치가 측정되었다.
  • CPU와 RAM이 20% 까지는 쳤지만 이정도 수치를 3~4배로 가정해도 서비스가 버틸 수 있는 수치이다.

 


 

✔ 마무리

상품을 오픈하고 대기열 서비스를 오픈했었고, 큰 이슈 없이 프로모션들을 넘어갔다. 그리고 아쉽게 죽어있는 상태 ㅎㅎ
많은 사람들이 레거시에 대한 문제와 기술 도입에 대한 보수적인 의견을 가지고 있다고 생각한다.
틀린말은 아니다. 그걸 해결하고 극복하기 위해 내가 존재하며, 좋은 결과물을 도출해낼 것이다.

 

퍼옴 :

제제의 개발 발자취

반응형

'BigData > 웹대기' 카테고리의 다른 글

Redis를 사용하여 작업 대기열 구현  (0) 2023.04.03
대량접속제어 기본개념 (나무위키)  (0) 2023.04.03
728x90

출처 : [STEP 1] Coin Activity (Burning, Minting, Mining, Staking, Airdrop, Pegging, Whitelist) (joinc.co.kr)

OVERVIEW

코인의 세계에 입문하게 되면, 다양한 코인 활동? Coin Activity 용어들을 접하게 된다. 가령, 민팅, 마이닝, 에어드랍, 버닝, 페깅, 추가적으로 화이트리스트 정도가 있다.

2020년도 Tokenomics 관련 회사 대표님과 ( 자유로운 자수성가 부자 ) 압구정 시추안하우스에서 오랜만에 근황 Talk 할겸 식사하였는데, 이때 처음으로 Airdrop 단어를 접하였다. 그 대표님은 반려동물 Theme 을 이용하여 NFT 를 발행하려는 Tokenomics 사업을 런칭하기 직전이었고, 그때 Whitelist에 한하여 Airdrop 을 하실 예정이고 나에게 제안할거라 감사하게 말해주셨다. 하지만 Blockchain 및 Coin 산업에 많이 가깝지 않았던 나는 질문했다.

"아, 아이폰 에어드랍으로 Coin을 전송해주신다는건가요?"

아 이 얼마나 획기적이고 놀라운 발상이란 말인가? 대표님과 심층적으로 Business 논의하고 집에 돌아와서 Airdrop 이라고 검색해보고 좌절했다. 그래도 학생들을 가르치고, 큰 Tech 산업에서 PO도 경험해본 내가 저런 독창적인 질문을 하다니, 지성이면 감천이다. 그 때 이후로 오랜만에 열심히 공부했고, 중요한 Coin Activity Term 들을 정리했고 숙지헀다. 그 값진 공부 히스토리를 공유한다.

참고로 이 Contents는 Yundream과 J.Phil의 통합 Masterpiece이니, To readers here, say "Thanks Yundream and J.phil as well"


Burning

  • defintion > 코인 소각 또는 코인 버닝이란, 개인키가 없는 암호 화폐 지갑주소로 암호화폐를 전송하여 다시는 사용할 수 없도록 만드는 행위를 뜻한다. 여기서 Coin Burn 또는 간략히 Burn, Burning, 소각이라고도 칭한다. 해당 지갑 주소로 전송된 암호화폐를 다시 사용하려면 지갑을 열 수 있는 개인키를 알아야한다. 개인키가 없으면 사용이 불가능하다. 이처럼 개인키가 없는 지갑은 코인을 받을 수만 있고 꺼낼수가 없으므로, 코인을 이런 지갑으로 보내면 사실상 코인을 Burning 소각하는 거와 같다.
  • example > Burning 은 코인 발행회사나 암호화폐 거래소에서 시장에 유통되는 코인의 공급량을 줄여 코인 가격을 상승시키기 위한 목적으로 시행되는 경우가 많다. 떄로는 새로운 블록을 생성하고, 신규 암호화폐를 발행하기 위해 소각의 방법을 사용하는 경우도 많다.
    가령, 소각 증명 합의 알고리즘을 사용하면, 기존에 비트코인 등에서 사용하던 작업증명 방식을 사용하지 않고도 신규 블록 생성자에게 암호화폐를 보상으로 지급할 수 있다.



Minting

  • definition > 민팅(Minting) 이란 그림이나 영상 등 디지털 자산의 대체불가능토큰(NFT)을 생성하는 것을 일컫는 용어다. 민팅은 화폐를 주조한다는 뜻의 영단어인 민트(Mint)를 옮긴 말에서 시작된 것으로, 블록체인 상에서 암호화폐를 발행하는 것을 의미한다. 암호화폐 코인이나 토큰이 만들어질 때마다 '민팅'의 과정을 거치는 셈으로, 하나의 암호화폐 당 수백만 개에서 수십억 개의 코인 또는 토큰이 민팅된다. 이에 따라 통상적으로 암호화폐의 '최대 공급량'은 민팅이 될 수 있는 최대한의 코인이나 토큰의 수를 의미하게 된다. 이렇게 민팅된 디지털 자산은 NFT 플랫폼에서 거래가 이뤄진다.
  • reference



Mining

  • definition > 가상화폐 채굴은 블록체인이라고 알려진 퍼블릭 디지털 기록으로 거래를 기록하고 검증하는 절차를 말한다. 이를 위해 채굴자는 복잡한 수학 문제를 해결하고, 그 대가로 가상화폐라는 보상을 받게 된다. 작업증명(PoW) 절차에 따른 채굴의 결과로 새로운 블록이 생성됩니다. 새로운 블록을 채굴하면 블록체인의 연속성이 이어진다. 따라서 채굴은 두 가지 목적을 수행합니다: 신규 코인을 생성하고 기존의 모든 토큰 기록을 유지한다.
  • example
지난 6일(현지시간) 기준 비트코인(BTC) 총 공급량은 1850만여개를 넘어섰습니다.

비트코인은 2009년 처음 등장했을 때부터 발행할 수 있는 수량이 2100만개로 정해졌습니다. 기존 화폐(법정화폐, 디지털 화폐 등)와 달리 비트코인은 발행량을 늘리고 싶다고 해서 2100만개 이상을 발행할 수 없습니다.
앞으로 채굴할 수 있는 비트코인은 250만여개에 불과합니다.

출처 : 코인데스크 코리아 (http://www.coindeskkorea.com/news/articleView.html?idxno=77573)



Airdrop



Staking

  • definition > Staking 은 지분증명 (POS) 알고리즘을 사용하는 블록체인 네크워크에서 구현이 가능하다. 대표적으로 EOS, XYZ, ATOM 등이 거론되며, Staking은 PoS 알고리즘을 사용하는 가상화폐를 소유한 이가 일정량의 화폐를 예치하면서 시작되며, 이때 예치자는 자신이 기여한 가상화폐의 지분율에 비례해 의사 결정 권한을 가진다. 이 과정에서 블록체인 네트워크 운영에도 참여하게 된다. 네트워크 운영자는 투자자들의 가상화폐를 활용해 시스템을 운영하게 되고, 이후 추가로 얻어진 가상화폐 수익을 투자자들에게 배분한다.
  • example > 투자자는 24시간 동안 자신의 컴퓨터에 노드를 운영해 블록 생성을 검증해야 하는데, 이러한 번거로움 때문에 스테이킹은 가상화폐 거래소나 지갑 업체가 대행하는 경우가 많다. 블록체인 네트워크 운영자 입장에서는 스테이킹 활동을 통해 시장에 풀린 유동성을 일부 동결함으로써 시세를 용이하게 조정할 수 있다는 장점이 있다. 투자자 또한 스테이킹을 통해 추가 보상을 얻을 수 있다. 다만 일정 지분량을 고정한다는 부담은 투자자가 고려해야 할 지점이다.
  • example, EOS > 다른 코인들과 다르게 독특한 '스테이킹'(staking)과 '언스테이킹 '(unstaking)이라는 개념을 가지고 있다. 언스테이킹은 기본상태라고도 하며 이오스 (EOS)가 유동성을 가지고 있는 상태로 이오스 코인 전송 및 거래가 가능한 상태이다. 스테이킹 상태는 락업 상태라고 하며, 이오스가 유동성을 가지고 있지 않은 상태로, 이오스 코인 전송 및 거래가 불가능하다. 그러나 이오스가 스테이킹된 만큼의 중앙처리장치(CPU)와 네트워크 자원을 활용할 수 있고, 1 EOS 당 30개의 블록생성자 (BP)에게 투표가 가능하다
  • background 곧 하기 합의 알고리즘 개념에 대해서도 Posting 예정
    • POS (Proof of Staking)
    • POW (Proof or Work)



Pegging

  • definition > 못을 박아서 고정하기라는 뜻이다. 그냥 가치를 고정했다는 뜻으로 이해하면 되는데, Stable Coin은 달러에 페깅된 코인이다. 즉, 아 달러와 가치와 동일한 가치를 지나는구나. 잠깐! Stable Coin을 2022년도 중순에 모르는 사람은 아마도 뉴스를 안보거나 onmyway에 강한 사람일텐데, Luna 에 대해서 검색해보길
  • example, reserving of pegged assets > USDC, USDT
    • Stable Coin은 고정 자산이 뒷받침하는 완전히 담보화된 시스템을 말하며, 차익거래자는 가격 안정화를 도와 인센티브를 받는다. 스테이블코인의 가격이 고정 자산보다 낮을 때, 차익거래자는 더 저렴한 스테이블코인을 살 수 있으며, 이는 각각 미화 1달러로 상환할 수 있다. 마찬가지로, 가격이 고정된 자산보다 높을 때, 그들은 이익을 얻기 위해 동전을 팔 수 있다.
    • 고정 환율제 Fixed Exchange Rate는 정부가 특정 화폐의 환율을 일정한 수준에서 고정하고, 이 환율을 유지하기 위해 중앙은행이 외환시장에 개입하도록 한 제도이다.가령, 1달러 = 1000원을 계속 유지하는 제도, 금의 가치와 화폐의 가치를 동일시하는 '금본위제' 달러와 화폐의 가치를 동일시 하는 'Dollar 페그제'도 고정환율제의 한 종류이다. 장단점을 간단하게 설명한다면, 장점은 환율 변동의 불확실성이 사라져 수출입 및 외국의 자본유출입이 원활해진다. 단점은 국가가 환율통제에 실패할 경우 큰 경제적 혼란을 초래한다.



Whitelist

  • definition > 기업들은 기업이 가진 블록체인 기술의 장점과 장래 유망성을 공개하고 특정 기간동안의 ICO 를 통해 BTC, ETH 등 코인을 투자자들에게 받아 투자금을 모으게 된다. 회사에서는 그 보상으로 투자자들에게 토큰을 투자지분별로 나누어 지급한다. 이 때 전체 파이에서 일부를 팀원/계약용/마케팅 등의 명목으로 빼고 남은 비율을 투자지분별로 나누어 지급하는데, 그 비율은 토큰별로 상이하다 (홈페이지나 백서에 기재된다) 추후 개발과정을 통해 별도의 메인넷 (블록체인)을 구축하게 되면 (사실 이 부분은 좀 헷갈림.) 코인으로 성장하게 된다. 이 때 프리세일이라고 하는 ICO보다 그 이전에 보너스 %를 추가로 제공하는 사전판매를 하기도 하며, 어느 정도 이상의 투자자/투자금액을 모아 할인을 받아 토큰을 구매하는 공동구매를 하는 경우도 있다.
  • example > 아쉽게도, JOINC 관련하여 주위 관심있는 투자자들 위해, Whitelist를 작성하였고 Reader or Educator를 위한 사업을 진행하고 있어서 같이 출시하려고 했지만 From holder's perspective around the middle of 2022, Coin 전망이 다양한 굵직한 사건으로 매우 부정적으로 보여서 일단 다시 기회를 볼계획이다.



퍼옴 : [STEP 1] Coin Activity (Burning, Minting, Mining, Staking, Airdrop, Pegging, Whitelist) (joinc.co.kr)

반응형
728x90

출처 : NginX를 이용한 static 컨텐츠 서비스 와 캐시 설정 (joinc.co.kr)

1. NginX를 이용한 static 컨텐츠 서비스

NginX를 리버스 프락시 서버로 사용하는 이유는 다음과 같다.

  1. 로드밸런싱 : 유저의 요청을 웹 애플리케이션 서버(WAS)로 분산 할 수 있다.
  2. 유저 요청에 대한 선 처리 : 유저의 요청이 WAS에 도달하기 전에 다양한 처리를 할 수 있다. 웹 애플리케이션 방화벽(WAF)를 설치하거나, 유저의 요청을 다른 위치로 보내도록 제어 할 수 있다.
  3. 캐싱 : 웹 서비스는 이미지, CSS, 자바스크립트 같은 정적인 페이지를 가지고 있다. 이런 정적 컨텐츠들을 NginX에서 대신 처리하는 것으로 응답 속도를 높일 수 있으며, WAS에 대한 부담을 줄일 수 있다. 컨텐츠들을 메모리에 캐시할 경우 서비스 할 경우 고성능의 웹 서비스를 만들 수도 있다.

이중 3번, Nginx를 이용해서 스태틱 페이지를 캐싱해서 서비스하는 방법을 테스트한다.

2. Web Contents 캐시에 대해서

2.1. 서버 캐시

웹 서비스의 성능을 높일 수 있는 가장 확실하고 손쉬운 방법은 컨텐츠 캐시다. 요즘 웹 서버는 매우 바쁘다. 유저의 요청을 받아서 데이터베이스를 조회해서 즉석에서 HTML 페이지를 만들고 이미지와 CSS, 자바스크립트 등 다양한 오브젝트들을 함께 응답해야 한다. 웹 서버가 해야 하는 일이 늘어나면서 리버스 프락시를 이용, 두 개 이상의 WAS(Web Application Server)가 요청을 분산해서 처리하도록 구성을 한다.

유저가 요청을 내리면 WAS로 바로가지 않고, 리버스 프락시 서버를 한번 거치게 된다. 요청을 분산하기 위해서 프락시 서버를 한번 더 거쳐야 하는데, 모든 컨텐츠가 굳이 프락시 서버를 거칠 필요가 있을까 ? 변하지 않는 컨텐츠의 경우에는 그냥 프락시 서버에서 직접 응답을 하게 하면 더 빠른 서비스를 제공 할 수 있지 않을까 ? 요즘 왠만한 웹 서비스들은 동적으로 컨텐츠를 서비스 한다. 하지만 조금만 들여다 보면, 많은 컨텐츠가 정적(변하지 않음)이라는 것을 알 수 있다. 이미지, CSS, Javascript, 동영상, PDF 뿐만 아니라 HTML 문서의 상당수도 정적이다. 유저 정보를 표시하는 HTML 문서는 (데이터베이스 등을 조회해서) 동적으로 만들어져야 하겠으나, 메뉴얼, 사이트 소개, 메뉴 등은 변하지 않는 컨텐츠들이다. 이들 컨텐츠들을 프락시 서버에서 서비스 한다면 더 빠르고 효율적으로 서비스 할 수 있을 것이다. 아래 그림을 보자.

리버스 프락시에서 정적인 페이지를 대신 응답한다. 이렇게 구성해서 얻을 수 있는 성능상의 잇점을 살펴보자.

  1. 네트워크 홉 이 줄어든다. WAS까지 요청을 보내지 않아도 된다. 1만큼의 네트워크 홉을 줄일 수 있다.
  2. 파일처리에 최적화 할 수 있다. 캐시 서버는 파일에 대한 "읽기/쓰기" 연산, 그 중에서도 읽기 연산을 주로 한다. 목적이 특정되므로 이에 맞게 최적화 할 수 있다. 당연하지만 NginX나 Apache 같은 전용 웹서버는 다양한 일을 하는 WAS(Django, RoR, Node)보다 빠르고 효율적으로 작동한다.
  3. 최적화가 쉽다. 정적 컨텐츠와 동적 컨텐츠를 분리 함으로써, 그에 맞는 최적화 방법을 사용 할 수 있다. 정적 컨텐츠를 Redis나 memcache, 램디스크 등으로 캐시할 수 있다.

3. NginX 캐시 매커니즘

NginX 서버의 캐시 매커니즘은 아래와 같다.

  1. NginX 캐시 서버가 컨텐츠 요청을 받는다.
  2. 요청 URL을 Key로 컨텐츠가 캐시돼 있는지 확인한다.
  3. 처음 요청이므로 캐시 실패(MISS) 한다.
  4. WAS서버로 요청을 보낸다.
  5. WAS서버로 부터 응답이 오면 캐시를 할지를 검사한다.
  6. 응답을 디스크에 저장한다.
  7. 다음 번에 같은 URI로 요청이 오면, 캐시 적중(HIT)한다. 요청은 WAS로 전달되지 않으며, 캐시서버가 대신 응답한다.

4. 테스트 구성

아래와 같은 서비스를 구성하기로 했다.

WAS 서버는 CSS, 이미지, Javascript를 포함한 동적인 컨텐츠를 서비스 한다. WAS 앞에는 Nginx가 WAS 서버들을 로드밸런싱 한다. WAS는 로드밸런서의 역할 뿐만 아니라 static 페이지들을 캐시하고 있다가 대신 서비스하는 캐시서버 역할을 한다.

  • Nginx는 WAS(Web application Server)나 다른 웹 서버(Nginx나 apache 같은)의 앞에 배치 할 수 있다. 로드밸런서로서의 역할 과 캐시 서버로의 역할을 동시에 수행 할 수 있다.!
  • Nginx는 일반적인 HTTP 요청 뿐만 아니라 FastCGI uWSGI 요청의 결과를 캐시할 수 있다. 이 기능은 특히 CMS(Content Management System)의 결과물을 캐시하는데 유용하게 사용 할 수 있다.
  • 캐시서버를 둠으로써, 애플리케이션 서버의 부하를 분산 할 수 있다.

5. WAS 서버 구성

테스트를 위해서 WAS 서버를 구성했다. 이 서버는 HTML, CSS, 자바스크립트, 이미지등을 서비스한다. NginX로 만들기로 했다. 아래는 설정파일이다.

 
 
이 설정파일은 기본적인 캐시 설정을 포함하고 있다. manifest, appcache, html, xml, json 파일들은 캐시를 하지 않는다. jpg,gif,gz 과 같은 파일들은 한달의 만료기간동안 캐시를 한다. css와 js 파일들은 1년을 캐시한다.curl로 index.html을 요청해보자.
$ curl -XGET -I 192.168.56.30/index.html
HTTP/1.1 200 OK
Server: nginx/1.9.3 (Ubuntu)
Date: Mon, 09 May 2016 14:59:36 GMT
Content-Type: text/html
Content-Length: 7261
Last-Modified: Mon, 09 May 2016 14:54:30 GMT
Connection: keep-alive
ETag: "5730a4a6-1c5d"
Expires: Mon, 09 May 2016 14:59:35 GMT
Cache-Control: no-cache
Accept-Ranges: bytes

Expires Date가 같은 시간임을 알 수 있다. 요청시간과 동시에 컨텐츠가 만료됐다. 따라서 클라이언트는 요청 결과를 캐시하지 않는다. 또한 Cache-Control: no-cache로 이 컨텐츠를 캐시하지 말라고 지시하고 있다.css파일을 요청해 보자.

# curl -XGET -I 192.168.56.30/css/foundation.css
HTTP/1.1 200 OK
Server: nginx/1.9.3 (Ubuntu)
Date: Mon, 09 May 2016 15:10:52 GMT
Content-Type: text/css
Content-Length: 105995
Last-Modified: Fri, 15 Apr 2016 07:10:18 GMT
Connection: keep-alive
ETag: "571093da-19e0b"
Expires: Tue, 09 May 2017 15:10:52 GMT
Cache-Control: max-age=31536000
Cache-Control: public
Accept-Ranges: bytes
 
Expires시간이 현재시간으로 부터 1년 후임을 알 수 있다. 이 규칙에 의해서 foundation.css는 최대 1년 동안 캐시가 된다.

6. 캐시 서버 설정

WAS 서버 설정이 끝났다. 이제 캐시서버를 만든다. 이 캐시서버는 WAS 서버를 로드밸런싱하면서 동시에 주요 컨텐츠들을 캐싱하는 역할을 한다. 유저의 요청은 캐시서버를 거치는데, 요청 결과가 캐시되어 있다면 (WAS까지 요청을 보내지 않고)캐시서버가 즉시 결과를 응답한다.역시 NginX 서버를 설치하고 아래와 같이 설정했다.

# cat /etc/nginx/site-available/default
server {
    listen 80 default_server;
    root /var/www/;
    index index.html index.htm;

    charset utf-8;

    location / {
        include proxy_params;
        proxy_pass http://192.168.56.30;
    }
}

우선은 간단한 reverse proxy로 설정했다. 캐시서버는 192.168.56.31:80에서 유저 요청을 기다린다. 만약 유저 요청이 도착하면, proxy_pass에 설정된 192.168.56.30으로 요청을 전달한다. curl을 이용해서 /css/foundation.css를 요청해 보자. 캐시서버가 컨텐츠를 캐시하지 않고 있기 때문에, 이 요청은 WAS까지 전달된다.

# curl -XGET -I 192.168.56.31/css/foundation.css
HTTP/1.1 200 OK
Server: nginx/1.9.3 (Ubuntu)
Date: Tue, 10 May 2016 14:05:56 GMT
Content-Type: text/css
Content-Length: 105995
Connection: keep-alive
Last-Modified: Fri, 15 Apr 2016 07:10:18 GMT
ETag: "571093da-19e0b"
Expires: Wed, 10 May 2017 14:05:56 GMT
Cache-Control: max-age=31536000
Cache-Control: public
Accept-Ranges: bytes
이제 NginX가 WAS의 컨텐츠를 캐시하도록 설정을 바꿔보자.
proxy_cache_path /tmp/nginx levels=1:2 keys_zone=my_zone:10m inactive=60m;
proxy_cache_key "$scheme$request_method$host$request_uri";
server {
    listen 80 default_server;
    root /var/www/;
    index index.html index.htm;

    charset utf-8;

    location / {
        proxy_cache my_zone;
        add_header X-Proxy-Cache $upstream_cache_status;

        include proxy_params;
        proxy_pass http://192.168.56.30;
    }
}

6.1. proxy_cache_path

캐시파일이 저장되는 위치와 저장 방식을 설정하기 위해서 사용한다. levels는 캐시파일을 어떻게 저장할지를 결정한다. 만약 levels를 설정하지 않는다면, 캐시파일은 현재 디렉토리에 저장이 된다. level을 설정할 경우, 서브디렉토리에 md5 해시된 파일이름으로 컨텐츠를 저장한다. level=1:2는 첫번째 단계의 디렉토리는 한글자, 두번째 단계의 디렉토리는 두 글자로 명명하라는 의미다. 예를 들어 /tmp/nginx/a/bc 디렉토리에 캐시 파일이 저장된다.keys_zone은 이 캐시를 가리키는 이름이다. my_zone은 캐시와 메타파일을 저장하기 위해서 10M의 공간을 할당 했다.

 

inactive지시어를 사용 하면, 일정 시간동안 접근이 없는 캐시파일을 할 수 있다. 위 설정에서는 한시간(60 minutes)동안 사용하지 않는 캐시파일은 삭제하도록 설정했다.

6.2. proxy_cache_key

각 캐시파일을 구분하기 위한 Key 규칙을 설정한다. 기본 설정 값은 $scheme$proxy_host$uri$is_args$args이다. "$host$request_uri $cookie_user"와 같이 사용 할 경우 유저 쿠키 별로 캐시를 구성할 수도 있다.

6.3. proxy_cache

location블럭내에서 사용한다. my_zone 캐시를 사용하도록 설정했다. add_header X-Proxy-Cache $upstream_cache_status 헤더를 추가했다. 이 헤더에는 HIT, MISS, MYPASS와 같은 캐시 적중 상태정보가 설정된다.curl를 이용해서 컨텐츠를 요청해 보자.

 
아직 캐시가 없어서 MISS가 설정된 걸 확인 할 수 있다. 캐시를 하기 위해서는 WAS로 부터 최소한 한 번 이상의 요청이 이루어져야 한다. 다시 한번 요청을 해보자.
 
캐시가 성공(HIT)한 것을 알 수 있다. 이 요청은 WAS로 전달되지 않고, 캐시 서버가 대신 응답한다. 캐시서버 디렉토리에서 캐시파일을 확인 할 수 있다.
# cd /tmp/nginx/c/6f
# ls
d34ee92169a4e9dbb366919f06d756fc
이제 Ngix는 클라이언트의 Cache-Control 헤더 요청을 무시한다. 만약 클라이언트의 Cache-Control 요청을 허용하고 싶다면, 아래와 같이 설정을 바꿔야 한다.
server {
    listen 80 default_server;
    root /var/www/;
    index index.html index.htm;

    charset utf-8;

    location / {
        proxy_cache my_zone;
        proxy_cache_bypass $http_cache_control;
        add_header X-Proxy-Cache $upstream_cache_status;

        include proxy_params;
        proxy_pass http://192.168.56.30;
    }
}

curl로 Cache-Control을 테스트해보자.

 
X-Proxy-Cache가 BYPASS로 설정된 걸 알 수 있다. 이 요청은 WAS 서버로 직접 전달된다. tcpdump로 Cache-Control을 설정했을 때와 그렇지 않았을 때를 테스트 해보자.

6.4. 캐시 상태

앞서 X-Proxy-Cache를 이용해서 캐시의 상태를 반환하고 있다. MISS, HIT, BYPASS외에 몇 가지 상태들이 더 있다.

MISS 캐시를 찾을 수 없다. 요청은 WAS 까지 전달된다. WAS 응답이 끝나면 캐시가 만들어질 것이다.
BYPASS 캐시서버의 캐시를 무시하고, WAS까지 요청을 전달한다. 캐시서버는 아마도 캐시를 가지고 있을 것이다.
EXPIRED 캐시 만료시간이 초과했다. 캐시서버는 WAS로 요청을 전달해서 캐시를 업데이트 한다.
STALE 서버가 제대로 응답하지 않아서, 낡은(stale) 캐시를 서비스하고 있다.
UPDATING 정확한 목적을 모르겠다. 좀 더 살펴봐야 할 듯
REVALIDATED 캐시가 여전히 유효하다. 클라이언트가 if-modified-since를 설정했을 때 리턴한다.
HIT 성공적으로 캐시를 서비스 했다.

7. Joinc에 적용

Joinc는 Go로 직접 개발한 CMS(자칭 gowiki)로 운영하고 있다. 모니위키를 기반으로 하고 있기 때문에, 위키문법의 문서를 HTML로 변환하는데 상당한 자원이 들어간다. 성능을 올리기 위해서 gowiki 서버 앞에 nginx 캐시서버를 두기로 했다.

/w 로 시작하는 URL의 컨텐츠는 정적인 컨텐츠이므로 캐시하기로 했다. 예를 들어 처음 /w/FrontPage를 요청하면, Gowiki로 요청이 전달된다. Gowiki는 FrontPage 문서를 HTML로 변환해서 응답한다. 이 응답은 NginX 서버가 캐시를 하므로, 다음번 요청 부터는 캐시에 있는 데이터를 응답한다.동적인 컨텐츠는 /api로 요청을 한다. 코드 실행기가 대표적인 경우인데, 이런 컨텐츠는 캐시하면 안 된다. 그리고 css, js, png, jpg 등도 캐시하기로 했다. NginX의 설정은 다음과 같다.

# cat /etc/nginx/site-available/default
proxy_cache_path /tmp/nginx levels=1:2 keys_zone=my_zone:100m inactive=1440m;
proxy_cache_key "$scheme$request_method$host$request_uri";
server {
    listen 80 default_server;
    root /var/www/;
    index index.html index.htm;

    charset utf-8;

    location / {

        include proxy_params;
        proxy_pass http://127.0.0.1:8000;
    }

    location /w/ {
        proxy_cache my_zone;
        proxy_cache_valid any 30m;
        add_header X-Proxy-Cache $upstream_cache_status;
        add_header Cache-Control "public";
        expires 1y;

        include proxy_params;
        proxy_pass http://127.0.0.1:8000;
    }

    location ~* \.(?:css|js|html)$ {
        proxy_cache my_zone;
        proxy_cache_valid any 30m;
        add_header X-Proxy-Cache $upstream_cache_status;
        add_header Cache-Control "public";
        expires 1y;

        include proxy_params;
        proxy_pass http://127.0.0.1:8000;
    }
}

7.1. 캐시 삭제

일단 캐시가된 컨텐츠의 경우, 컨텐츠의 내용이 바뀌더라도 여전히 캐시의 내용을 읽어오는 문제가 있다. 위키 특성상 문서를 자주 수정하게 되는데, 수정한 내용이 보이지 않는 것은 심각한 문제가 될 수 있다. 문제를 해결하기 위해서는 문서의 수정 후 해당 컨텐츠의 캐시를 찾아서 삭제하는 코드를 만들어야 했다. 직접 만들기 귀찮아서 nginx-cache-purge라는 프로그램을 다운로드 해서 사용했다. 그럭저럭 잘 작동하는 것 같다. 예를 들어서 /w/man/12/nginx 문서를 수정했다면 ./nginx-cache-purge /w/man/12/nginx /tmp/nginx 수행하면 된다.혹은 컨텐츠의 key를 md5 한 값을 데이터베이스에 저장해서 문서 수정시 삭제하는 방법이 있다. 위 설정에 의하면 /w/man/12/nginx의 key는 "httpGETwww.joinc.co.kr/w/man/12/nginx/static"이다. 이걸 md5 한 값은 아래와 같다.

# echo -n "httpGETwww.joinc.co.kr/w/man/12/nginx/static" | md5sum
0c9aa652815494e45573c077c9015c60 -
 
이 캐시파일의 정확한 위치는 "/tmp/nginx/0/c6/0c9aa652815494e45573c077c9015c60"다. 디렉토리는 md5 해시의 마지막 값으로 알아낼 수 있다.

7.2. 성능 테스트

크롬 개발자도구를 이용해서 응답속도만 테스트 했다. static 의 응답시간을 확인하자.캐시 적용 전

캐시 적용 후

캐시 적용 된 후 47msec 에서 5msec로 줄어든 걸 확인 할 수 있다.

7.3. 하나의 HTML 문서에 정적 컨텐츠와 동적 컨텐츠가 함께 있을 때

Joinc 사이트는 아래와 같이 구성된다.

모든 페이지들이 메뉴와 wiki 본문 으로 구성이 된다. 메뉴는 유저의 상태에 따라서 구성이 달라진다. 로그인 하기 전이라면 로그인 관련 메뉴를 출력하고,로그인 후라면 문서 편집과 같은 메뉴들이 출력된다. 컨텐츠가 동적으로 바뀌는 셈이다. 반면 Wiki 문서 내용은 정적 컨텐츠다. 만약 페이지 전체를 캐시를 해버리면, 메뉴 출력에 문제가 생길 것이다. 이 문제를 해결해야 했다.나는 쿠기(cookie)를 이용해서 이 문제를 해결 했다. 아래와 같이 proxy_cache_key에 cookie_login을 설정했다.

proxy_cache_key "$scheme$request_method$host$request_uri$cookie_login";
 
유저가 로그인에 성공하면 SetCookie("login", "true")로 login 쿠키를 설정한다. 로그아웃 하면 SetCookie("login","")로 쿠키를 삭제한다. 이렇게 하면 동일한 페이지라고 하더라도 로그인과 로그아웃 상태별로 캐시파일을 만들 수 있다. 간단하기는 하지만 캐시파일이 두배가 된다는 단점이 있다. 메뉴부분을 모두 자바스크립트로 처리하는 방법도 있는데, "개인 사이트에서 캐시공간이 늘어나 봤자 얼마나 늘어나겠나 ?"라는 생각에 패스하기로 했다(귀찮아서 패스했다는 얘기다).

 

반응형

'WEB & WAS > nginx' 카테고리의 다른 글

nginx와 Tomcat 구성  (0) 2016.08.22
728x90

 

Python에서 데이터 시각화하는 다양한 방법

  • Python에서 데이터 시각화할 때 사용하는 다양한 라이브러리를 정리한 글입니다
  • 데이터 분석가들은 주로 Python(또는 R, SQL)을 가지고 데이터 분석을 합니다
    • R에는 ggplot이란 시각화에 좋은 라이브러리가 있는 반면 Python에는 어느 춘추전국시대처럼 다양한 라이브러리들이 있습니다
    • 각 라이브러리들마다 특징이 있기 때문에, 자유롭게 사용하면 좋을 것 같습니다
    • Zeppelin도 시각화할 때 사용할 수 있지만, 라이브러리는 아니기 때문에 이 문서에선 다루지 않습니다
    • 데이터 시각화 관련한 꿀팁은 카일 스쿨 2주차를 참고해주세요 :)
  • 저는 Jupyter Notebook으로 레포트를 작성해, 레포트의 보는 사람이 누구인지에 따라 다르게 그래프를 그렸습니다
    • 직접 그래프를 더 탐색해보고 싶은 목적이 있는 분들에게 보내는 그래프라면 동적인(Interactive) 그래프를 그렸습니다
    • 반복적인 레포트는 정적인 그래프 기반으로 작성한 후, 추가적인 내용이 궁금하면 대시보드로 가도록 유도했습니다
  • 이 문서는 맥북에서 작성되었으며, 부족한 내용이 있으면 연락주세요 :)
  • 현재 프론트단의 문제로 자바스크립트 기반 그래프는 처음엔 보이지 않을 수 있습니다. 10초 뒤 새로고침하면 나올거에요-!
  • plot.ly, pyecharts는 웹에서 정말 강력합니다. 꼭 직접 사용해보세요!
  • 라이브러리
    • matplotlib
    • seaborn
    • plotnine
    • folium
    • plot.ly
    • pyecharts

Matplotlib


  • Python에서 아마 가장 많이 쓰는 라이브러리입니다
  • pandas의 Dataframe을 바로 시각화할 때도 내부적으로 matplotlib을 사용합니다
  • 설치 pip3 install matplotlib
  • Matplotlib Tutorials
  • 데이터 사이언스 스쿨

import pandas as pd import numpy as np import matplotlib import matplotlib.pyplot as plt print("Matplotlib version", matplotlib.__version__) %matplotlib inline %config InlineBackend.figure_format = 'retina'

Matplotlib version 2.2.3

Figure의 구성 요소

  • 처음 matplotlib을 사용해 그래프를 그릴 때, 그래프와 관련된 명칭을 (영어 표현으로) 몰라 애를 먹었습니다
  • 여기 나온 표현을 숙지해두기만 해도 좋을 것 같습니다

Figure

  • Figure는 그림이 그려지는 도화지라고 생각할 수 있습니다
    • 우선 Figure를 그린 후, plt.subplots로 도화지를 분할해 각 부분에 그래프를 그리는 방식으로 진행합니다
    • plt.figure를 명시적으로 표현해주는 것이 좋으나, plot 함수에서 자동으로 figure를 생성하기 때문에 자주 사용하진 않습니다
    • 그러나 현재 figure에 접근해야 할 필요성이 있다면, plt.gcf()로 접근할 수 있습니다
  • size를 조절하고 싶은 경우엔 fig.set_size_inches(18.5, 10.5)
    • 또는 plt.figure(figsize=(10,5))
    • 또는 plt.rcParams['figure.figsize'] = (10,7)

Axes

  • Axes는 plot이 그려지는 공간입니다

Axis

  • plot의 축입니다

fig = plt.figure() fig.suptitle('figure sample plots') fig, ax_lst = plt.subplots(2, 2, figsize=(8,5)) ax_lst[0][0].plot([1,2,3,4], 'ro-') ax_lst[0][1].plot(np.random.randn(4, 10), np.random.randn(4,10), 'bo--') ax_lst[1][0].plot(np.linspace(0.0, 5.0), np.cos(2 * np.pi * np.linspace(0.0, 5.0))) ax_lst[1][1].plot([3,5], [3,5], 'bo:') ax_lst[1][1].plot([3,7], [5,4], 'kx') plt.show()

 

df = pd.DataFrame(np.random.randn(4,4))

df.plot(kind='barh')

Out[4]:

  • ggplot 스타일로 그리고 싶다면 아래 옵션 추가

plt.style.use('ggplot') pd.options.display.mpl_style = 'default'

fig = plt.figure() fig.suptitle('ggplot style') fig, ax_lst = plt.subplots(2, 2, figsize=(8,5)) ax_lst[0][0].plot([1,2,3,4], 'ro-') ax_lst[0][1].plot(np.random.randn(4, 10), np.random.randn(4,10), 'bo--') ax_lst[1][0].plot(np.linspace(0.0, 5.0), np.cos(2 * np.pi * np.linspace(0.0, 5.0))) ax_lst[1][1].plot([3,5], [3,5], 'bo:') ax_lst[1][1].plot([3,7], [5,4], 'kx') plt.show()

 

Seaborn


  • seaborn은 matplotlib을 기반으로 다양한 색 테마, 차트 기능을 추가한 라이브러리입니다
  • matplotlib에 의존성을 가지고 있습니다
  • matplotlib에 없는 그래프(히트맵, 카운트플랏 등)을 가지고 있습니다
  • 설치pip3 install seaborn
  • Seaborn Tutorials

import seaborn as sns print("Seaborn version : ", sns.__version__) sns.set() sns.set_style('whitegrid') sns.set_color_codes()

Seaborn version : 0.9.0

current_palette = sns.color_palette() sns.palplot(current_palette)

relplot

tips = sns.load_dataset("tips") sns.relplot(x="total_bill", y="tip", hue="smoker", style="smoker", data=tips)

Out[11]:

df = pd.DataFrame(dict(time=np.arange(500), value=np.random.randn(500).cumsum())) g = sns.relplot(x="time", y="value", kind="line", data=df) g.fig.autofmt_xdate()

Catplot

sns.catplot(x="day", y="total_bill", hue="smoker", col="time", aspect=.6, kind="swarm", data=tips)

Out[12]:

titanic = sns.load_dataset("titanic") g = sns.catplot(x="fare", y="survived", row="class", kind="box", orient="h", height=1.5, aspect=4, data=titanic.query("fare > 0")) g.set(xscale="log");

Pairplot

iris = sns.load_dataset("iris") sns.pairplot(iris)

Out[16]:

g = sns.PairGrid(iris) g.map_diag(sns.kdeplot) g.map_offdiag(sns.kdeplot, n_levels=6);

Heatmap

flights = sns.load_dataset("flights") flights = flights.pivot("month", "year", "passengers") plt.figure(figsize=(10, 10)) ax = sns.heatmap(flights, annot=True, fmt="d")

Plotnine


  • plotnine은 R의 ggplot2에 기반해 그래프를 그려주는 라이브러리입니다
  • R로 시각화하는 것이 익숙하신 분들에게 좋을 것 같습니다. 저는 사용해보진 않았습니다!
  • 공식 문서
  • 설치pip3 install plotnine

import plotnine from plotnine import * print("plontnine version :",plotnine.__version__)

plontnine version : 0.4.0

n = 10 df = pd.DataFrame({'x': np.arange(n), 'y': np.arange(n), 'yfit': np.arange(n) + np.tile([-.2, .2], n//2), 'cat': ['a', 'b']*(n//2)})

(ggplot(df) + geom_col(aes('x', 'y', fill='cat')) + geom_point(aes('x', y='yfit', color='cat')) + geom_path(aes('x', y='yfit', color='cat')) )

Out[39]:

df2 = pd.DataFrame({ 'letter': ['Alpha', 'Beta', 'Delta', 'Gamma'] * 2, 'pos': [1, 2, 3, 4] * 2, 'num_of_letters': [5, 4, 5, 5] * 2 }) (ggplot(df2) + geom_col(aes(x='letter',y='pos', fill='letter')) + geom_line(aes(x='letter', y='num_of_letters', color='letter'), size=1) + scale_color_hue(l=0.45) # some contrast to make the lines stick out + ggtitle('Greek Letter Analysis') )

Out[41]:

Folium


  • folium은 지도 데이터(Open Street Map)에 leaflet.js를 이용해 위치정보를 시각화하는 라이브러리입니다
  • 자바스크립트 기반이라 interactive하게 그래프를 그릴 수 있습니다
  • 한국 GeoJSON 데이터는 southkorea-maps에서 확인할 수 있습니다
  • 참고 자료
  • 그 외에도 pydeck, ipyleaflet 등으로 지도 시각화를 할 수 있습니다
  • 설치pip3 install folium

import folium print("folium version is", folium.__version__)

folium version is 0.6.0

m = folium.Map(location=[37.5502, 126.982], zoom_start=12) folium.Marker(location=[37.5502, 126.982], popup="Marker A", icon=folium.Icon(icon='cloud')).add_to(m) folium.Marker(location=[37.5411, 127.0107], popup="한남동", icon=folium.Icon(color='red')).add_to(m) m

Out[76]:

 

Plot.ly


  • plotly는 Interactive 그래프를 그려주는 라이브러리입니다
  • Scala, R, Python, Javascript, MATLAB 등에서 사용할 수 있습니다
  • 시각화를 위해 D3.js를 사용하고 있습니다
  • 사용해보면 사용이 쉽고, 세련된 느낌을 받습니다
  • Online과 offline이 따로 존재합니다(온라인시 api key 필요)
  • plotly cloud라는 유료 모델도 있습니다
  • 설치pip3 install plotly
  • 참고 자료

import plotly print("plotly version :", plotly.__version__)

plotly version : 3.1.1

plotly.offline.init_notebook_mode() plotly.offline.iplot({ "data": [{ "x": [1, 2, 3], "y": [4, 2, 5] }], "layout": { "title": "hello world" } })

 

11.522.5322.533.544.55Export to plot.ly »hello world

 

 

 

 

 

import plotly.figure_factory as ff import plotly.plotly as py import plotly.graph_objs as go df = pd.read_csv("https://raw.githubusercontent.com/plotly/datasets/master/school_earnings.csv") table = ff.create_table(df) plotly.offline.iplot(table, filename='jupyter-table1')

Export to plot.ly »SchoolWomenMenGapMIT9415258Stanford9615155Harvard11216553U.Penn9214149Princeton9013747Chicago7811840Georgetown9413137Tufts7611236Yale7911435Columbia8611933Duke9312431Dartmouth8411430NYU679427Notre Dame7310027Cornell8010727Michigan628422Brown729220Berkeley718817Emory688214UCLA647814SoCal72819

 

 

 

 

 

data = [go.Bar(x=df.School, y=df.Gap)] plotly.offline.iplot(data, filename='jupyter-basic_bar')

MITStanfordHarvardU.PennPrincetonChicagoGeorgetownTuftsYaleColumbiaDukeDartmouthNYUNotre DameCornellMichiganBrownBerkeleyEmoryUCLASoCal0102030405060Export to plot.ly »

 

 

 

 

 

data = [dict( visible = False, line=dict(color='#ff0000', width=6), name = '𝜈 = '+str(step), x = np.arange(0,10,0.01), y = np.sin(step*np.arange(0,10,0.01))) for step in np.arange(0,5,0.1)] data[10]['visible'] = True steps = [] for i in range(len(data)): step = dict( method = 'restyle', args = ['visible', [False] * len(data)], ) step['args'][1][i] = True # Toggle i'th trace to "visible" steps.append(step) sliders = [dict( active = 10, currentvalue = {"prefix": "Frequency: "}, pad = {"t": 50}, steps = steps )] layout = dict(sliders=sliders) fig = dict(data=data, layout=layout) plotly.offline.iplot(fig, filename='Sine Wave Slider')

02468−1−0.500.51Export to plot.ly »Frequency: step-10step-0step-5step-10step-15step-20step-25step-30step-35step-40step-45

 

 

 

 

 

pyecharts


  • Baidu에서 데이터 시각화를 위해 만든 Echarts.js의 파이썬 버전입니다
  • 정말 다양한 그래프들이 내장되어 있어 레포트를 작성할 때 좋습니다!
  • 자바스크립트 기반이기 때문에 Interactive한 그래프를 그려줍니다
  • 공식 문서
  • 설치pip3 install pyecharts

import pyecharts print("pyecharts version : ", pyecharts.__version__)

pyecharts version : 0.5.8

attr = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] v1 = [2.0, 4.9, 7.0, 23.2, 25.6, 76.7, 135.6, 162.2, 32.6, 20.0, 6.4, 3.3] v2 = [2.6, 5.9, 9.0, 26.4, 28.7, 70.7, 175.6, 182.2, 48.7, 18.8, 6.0, 2.3] bar = pyecharts.Bar("Bar chart", "precipitation and evaporation one year") bar.add("precipitation", attr, v1, mark_line=["average"], mark_point=["max", "min"]) bar.add("evaporation", attr, v2, mark_line=["average"], mark_point=["max", "min"]) bar.height = 500 bar.width = 800 bar

Out[109]:

 

attr = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] v1 = [2.0, 4.9, 7.0, 23.2, 25.6, 76.7, 135.6, 162.2, 32.6, 20.0, 6.4, 3.3] v2 = [2.6, 5.9, 9.0, 26.4, 28.7, 70.7, 175.6, 182.2, 48.7, 18.8, 6.0, 2.3] bar = pyecharts.Bar("Bar chart", "precipitation and evaporation one year") bar.use_theme("dark") bar.add("precipitation", attr, v1, mark_line=["average"], mark_point=["max", "min"]) bar.add("evaporation", attr, v2, mark_line=["average"], mark_point=["max", "min"]) bar.height = 500 bar.width = 800 bar

Out[110]:

 

title = "bar chart2" index = pd.date_range("8/24/2018", periods=6, freq="M") df1 = pd.DataFrame(np.random.randn(6), index=index) df2 = pd.DataFrame(np.random.rand(6), index=index) dfvalue1 = [i[0] for i in df1.values] dfvalue2 = [i[0] for i in df2.values] _index = [i for i in df1.index.format()] bar = pyecharts.Bar(title, "Profit and loss situation") bar.add("profit", _index, dfvalue1) bar.add("loss", _index, dfvalue2) bar.height = 500 bar.width = 800 bar

Out[111]:

 

from pyecharts import Bar, Line, Overlap attr = ['A','B','C','D','E','F'] v1 = [10, 20, 30, 40, 50, 60] v2 = [38, 28, 58, 48, 78, 68] bar = Bar("Line Bar") bar.add("bar", attr, v1) line = Line() line.add("line", attr, v2) overlap = Overlap() overlap.add(bar) overlap.add(line) overlap

Out[112]:

 

from pyecharts import Pie attr = ['A','B','C','D','E','F'] v1 = [10, 20, 30, 40, 50, 60] v2 = [38, 28, 58, 48, 78, 68] pie = Pie("pie chart", title_pos="center", width=600) pie.add("A", attr, v1, center=[25, 50], is_random=True, radius=[30, 75], rosetype='radius') pie.add("B", attr, v2, center=[75, 50], is_randome=True, radius=[30, 75], rosetype='area', is_legend_show=False, is_label_show=True) pie

Out[113]:

 

bar = Bar("가로 그래프") bar.add("A", attr, v1) bar.add("B", attr, v2, is_convert=True) bar.width=800 bar

Out[114]:

 

import random attr = ["{}th".format(i) for i in range(30)] v1 = [random.randint(1, 30) for _ in range(30)] bar = Bar("Bar - datazoom - slider ") bar.add("", attr, v1, is_label_show=True, is_datazoom_show=True) bar

Out[115]:

 

days = ["{}th".format(i) for i in range(30)] days_v1 = [random.randint(1, 30) for _ in range(30)] bar = Bar("Bar - datazoom - xaxis/yaxis") bar.add( "", days, days_v1, is_datazoom_show=True, datazoom_type="slider", datazoom_range=[10, 25], is_datazoom_extra_show=True, datazoom_extra_type="slider", datazoom_extra_range=[10, 25], is_toolbox_show=False, ) bar

Out[116]:

 

3D

from pyecharts import Bar3D bar3d = Bar3D("3D Graph", width=1200, height=600) x_axis = [ "12a", "1a", "2a", "3a", "4a", "5a", "6a", "7a", "8a", "9a", "10a", "11a", "12p", "1p", "2p", "3p", "4p", "5p", "6p", "7p", "8p", "9p", "10p", "11p" ] y_axis = [ "Saturday", "Friday", "Thursday", "Wednesday", "Tuesday", "Monday", "Sunday" ] data = [ [0, 0, 5], [0, 1, 1], [0, 2, 0], [0, 3, 0], [0, 4, 0], [0, 5, 0], [0, 6, 0], [0, 7, 0], [0, 8, 0], [0, 9, 0], [0, 10, 0], [0, 11, 2], [0, 12, 4], [0, 13, 1], [0, 14, 1], [0, 15, 3], [0, 16, 4], [0, 17, 6], [0, 18, 4], [0, 19, 4], [0, 20, 3], [0, 21, 3], [0, 22, 2], [0, 23, 5], [1, 0, 7], [1, 1, 0], [1, 2, 0], [1, 3, 0], [1, 4, 0], [1, 5, 0], [1, 6, 0], [1, 7, 0], [1, 8, 0], [1, 9, 0], [1, 10, 5], [1, 11, 2], [1, 12, 2], [1, 13, 6], [1, 14, 9], [1, 15, 11], [1, 16, 6], [1, 17, 7], [1, 18, 8], [1, 19, 12], [1, 20, 5], [1, 21, 5], [1, 22, 7], [1, 23, 2], [2, 0, 1], [2, 1, 1], [2, 2, 0], [2, 3, 0], [2, 4, 0], [2, 5, 0], [2, 6, 0], [2, 7, 0], [2, 8, 0], [2, 9, 0], [2, 10, 3], [2, 11, 2], [2, 12, 1], [2, 13, 9], [2, 14, 8], [2, 15, 10], [2, 16, 6], [2, 17, 5], [2, 18, 5], [2, 19, 5], [2, 20, 7], [2, 21, 4], [2, 22, 2], [2, 23, 4], [3, 0, 7], [3, 1, 3], [3, 2, 0], [3, 3, 0], [3, 4, 0], [3, 5, 0], [3, 6, 0], [3, 7, 0], [3, 8, 1], [3, 9, 0], [3, 10, 5], [3, 11, 4], [3, 12, 7], [3, 13, 14], [3, 14, 13], [3, 15, 12], [3, 16, 9], [3, 17, 5], [3, 18, 5], [3, 19, 10], [3, 20, 6], [3, 21, 4], [3, 22, 4], [3, 23, 1], [4, 0, 1], [4, 1, 3], [4, 2, 0], [4, 3, 0], [4, 4, 0], [4, 5, 1], [4, 6, 0], [4, 7, 0], [4, 8, 0], [4, 9, 2], [4, 10, 4], [4, 11, 4], [4, 12, 2], [4, 13, 4], [4, 14, 4], [4, 15, 14], [4, 16, 12], [4, 17, 1], [4, 18, 8], [4, 19, 5], [4, 20, 3], [4, 21, 7], [4, 22, 3], [4, 23, 0], [5, 0, 2], [5, 1, 1], [5, 2, 0], [5, 3, 3], [5, 4, 0], [5, 5, 0], [5, 6, 0], [5, 7, 0], [5, 8, 2], [5, 9, 0], [5, 10, 4], [5, 11, 1], [5, 12, 5], [5, 13, 10], [5, 14, 5], [5, 15, 7], [5, 16, 11], [5, 17, 6], [5, 18, 0], [5, 19, 5], [5, 20, 3], [5, 21, 4], [5, 22, 2], [5, 23, 0], [6, 0, 1], [6, 1, 0], [6, 2, 0], [6, 3, 0], [6, 4, 0], [6, 5, 0], [6, 6, 0], [6, 7, 0], [6, 8, 0], [6, 9, 0], [6, 10, 1], [6, 11, 0], [6, 12, 2], [6, 13, 1], [6, 14, 3], [6, 15, 4], [6, 16, 0], [6, 17, 0], [6, 18, 0], [6, 19, 0], [6, 20, 1], [6, 21, 2], [6, 22, 2], [6, 23, 6] ] range_color = ['#313695', '#4575b4', '#74add1', '#abd9e9', '#e0f3f8', '#ffffbf', '#fee090', '#fdae61', '#f46d43', '#d73027', '#a50026'] bar3d.add( "", x_axis, y_axis, [[d[1], d[0], d[2]] for d in data], is_visualmap=True, visual_range=[0, 20], visual_range_color=range_color, grid3d_width=200, grid3d_depth=80, ) bar3d.width=700 bar3d.height=500 bar3d

Out[117]:

 

from pyecharts import Boxplot boxplot = Boxplot("Box plot") x_axis = ['expr1', 'expr2', 'expr3', 'expr4', 'expr5'] y_axis = [ [850, 740, 900, 1070, 930, 850, 950, 980, 980, 880, 1000, 980, 930, 650, 760, 810, 1000, 1000, 960, 960], [960, 940, 960, 940, 880, 800, 850, 880, 900, 840, 830, 790, 810, 880, 880, 830, 800, 790, 760, 800], [880, 880, 880, 860, 720, 720, 620, 860, 970, 950, 880, 910, 850, 870, 840, 840, 850, 840, 840, 840], [890, 810, 810, 820, 800, 770, 760, 740, 750, 760, 910, 920, 890, 860, 880, 720, 840, 850, 850, 780], [890, 840, 780, 810, 760, 810, 790, 810, 820, 850, 870, 870, 810, 740, 810, 940, 950, 800, 810, 870] ] _yaxis = boxplot.prepare_data(y_axis) boxplot.add("boxplot", x_axis, _yaxis) boxplot

Out[118]:

 

from pyecharts import Funnel attr = ["A", "B", "C", "D", "E", "F"] value = [20, 40, 60, 80, 100, 120] funnel = Funnel("퍼널 그래프") funnel.add( "퍼널", attr, value, is_label_show=True, label_pos="inside", label_text_color="#fff", ) funnel.width=700 funnel.height=500 funnel

Out[119]:

 

from pyecharts import Gauge gauge = Gauge("Gauge Graph") gauge.add("이용률", "가운데", 66.66) gauge

Out[120]:

 

 

카일스쿨 유튜브 채널을 만들었습니다. 데이터 사이언스, 성장, 리더십, BigQuery 등을 이야기할 예정이니, 관심 있으시면 구독 부탁드립니다 :)

이 글이 도움이 되셨다면 추천 클릭을 부탁드립니다 :)

 

출처 : 어쩐지오늘 https://zzsza.github.io/development/2018/08/24/data-visualization-in-python/

반응형

'Language > Python' 카테고리의 다른 글

파이썬으로 구글메일 보내기  (0) 2020.12.24
파이썬을 이용한 시스템 트레이딩  (0) 2016.08.04
728x90

[Tomcat] 톰캣 오류 처리하기 만들기 ErrorReportValve Custom Tomcat Valve

프로그램 자료/Java & Spring 2019. 10. 31. 11:31

출처1 : http://jagadesh4java.blogspot.com/2014/09/custom-error-page-in-tomcat.html

출처2 : https://aspiresoftware.in/blog/catalinatomcat-custom-error-report-valve-to-handle-errors-exceptions/

출처3 : https://github.com/theand/til-by-heesang/blob/master/md/java/tomcat_invalid_character_found_in_request.md

출처4 : https://server0.tistory.com/39

 

얼마전에 납품한 솔루션의 웹 보안 리포트를 받았다. 

특정 url에서 페이지 없음 오류가 떠야하는데 톰캣 에러가 예쁘게 뜨더라.

 

대충 이렇게?? 

 

좀 찾다보니까 톰캣 8.5.31(?) 이상, 9.0.8(?) 이상, 8.0.52(?) 버전부터는 RFC 7230, RFC 3986에 의해 특수문자를 받지 않는다고 하더라.

이미 톰캣 설정에 relaxedQueryChars를 사용하고 있어서 당당히 [] 문자를 넣었다. 

 

그런데 다음과 같은 url로 접속하면 여전히 톰캣 오류가 뜨는 증상이 있었다.

http://127.0.0.1:8080/[

 

어떻게 해야하나 고민했었는데 톰캣에서는 Valve라는 것을 자바 클래스로 만들고, 이걸 상속받아서 구현하면 가져다가 쓸 수 있다고 하더라.

 

package com.motolies.config;

 

import java.io.BufferedWriter;

import java.io.IOException;

import java.io.OutputStreamWriter;

import java.util.logging.Logger;

 

import org.apache.catalina.connector.Request;

import org.apache.catalina.connector.Response;

import org.apache.catalina.valves.ErrorReportValve;

 

public class CustomErrorReportValve extends ErrorReportValve {

 

    // Create a simple logger

private static final Logger logger = Logger.getLogger(CustomErrorReportValve.class.getName());



    @Override

    protected void report(Request request, Response response, Throwable t) {

        try {

            BufferedWriter out = new BufferedWriter(new OutputStreamWriter(response.getOutputStream(), "UTF8"));

            out.write("<!DOCTYPE html>");

            out.write("<html xmlns=\"http://www.w3.org/1999/xhtml\" xmlns:th=\"http://www.thymeleaf.org\">");

            out.write("<head>");

            out.write(" <meta charset=\"utf-8\"/>");

            out.write(" <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\"/>");

            out.write(" <meta name=\"description\" content=\"\"/>");

            out.write("</head>");

            out.write("<body>   ");

            out.write(" <script type=\"text/javascript\">");

            out.write("     alert(\"비정상적인 접근입니다.\");");

            out.write("     history.back();");

            out.write(" </script>");

            out.write("</body>");

            out.write("</html>");

            out.close();

 

            // Log the error with your favorite logging framework...

     logger.severe("Uncaught throwable was thrown: " + t.getMessage());

 

        } catch (IOException e) {

            e.printStackTrace();

        }

    }

}

 

대충 위와 같은 클래스를 만들고 jar 파일로 만들어야 한다. 

 

jar는 다음과 같이 만들면 된다. 

 

 

해당 파일을 우클릭해서 Export 클릭.

 

Export type에 JAR file 선택.

 

만들어질 위치 선택하고 Finish.

 

그럼 jar파일이 만들어지는데 이것을 다음의 경로에 복사한다.

 

..\tomcat\lib

 

아마 이미 많은 jar파일들이 있을 것이다. 

 

마지막으로 tomcat\conf\server.xml 에서 수정을 좀 해야한다. 

 

<Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true"

errorReportValveClass="com.motolies.config.CustomErrorReportValve">

<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"

prefix="localhost_access_log" suffix=".txt" pattern="%h %l %u %t &quot;%r&quot; %s %b" />

</Host>

 

다음과 같이 빨간색 부분을 추가해주고 톰캣을 재시작하면 된다.

 

 

반응형
728x90

import pandas_datareader.data as web

import datetime

import matplotlib.pyplot as plt

from zipline.api import order, symbol

# order : zipline 백테스팅 시뮬레이션 주문 실행 함수

# symbol : 참조할 데이터에 대한 심볼 등록

#from zipline.algorithm import TradingAlgorithm

from zipline import run_algorithm

from zipline.utils.factory import create_simulation_parameters

# create_simulation_parameters : 초기 금액 설정에 사용

def initialize(context):

pass

def handle_data(context, data):

# order을 통해 AAPL 심볼 주식을 1주 매수

order(symbol('AAPL'), 1)

start = datetime.datetime(2010, 1, 2)

end = datetime.datetime(2016, 3, 19)

data = web.DataReader("AAPL", "yahoo", start, end)

# 새로운 dataframe 객체 만들기

data2 = data[['Adj Close']]

# dataframe의 column의 이름 바꾸기

data2.columns = ['AAPL']

data2 = data.tz_localize("UTC")

data2.head()

# sim_params = create_simulation_parameters(capital_base=100000000)

algo = run_algorithm(start = data2.index[0], end = data2.index[-1], capital_base = 1000000, initialize=initialize, handle_data=handle_data)

plt.plot(algo.index, algo.portfolio_value)

plt.show()

-----

파이썬으로 배우는 알고리즘 트레이딩에 있는 예제에서 변경,

Tradingalgorithm 함수는 과거의 zipline api의 함수인 것으로 확인,

run_algorithm으로 변경

algo = run_algorithm(start = data2.index[0], end = data2.index[-1], capital_base = 1000000, initialize=initialize, handle_data=handle_data)

맨 끝에 data = data 를 추가했을 경우에는 에러 발생.

이것에 대한 이유는 차차 확인해보기로.

 

[출처] Zipline 1.4.1 버전 오류 해결|작성자 PKB

반응형
728x90

import smtplib

from email.mime.text import MIMEText 

 

smtp = smtplib.SMTP('smtp.gmail.com'587)

smtp.ehlo()      # say Hello

smtp.starttls()  # TLS 사용시 필요

smtp.login('나의아이디@gmail.com''비밀번호'

 

msg = MIMEText('본문 테스트 메시지')

msg['Subject'= '테스트'

msg['To'= '보낼아이디@보낼메일주소.com'

smtp.sendmail('나의아이디@gmail.com''보낼아이디@보낼메일주소.com', msg.as_string())

 

smtp.quit()

반응형
728x90

로또 당첨번호 예측 이론/알고리즘 모음 (2) - 제외수, 달력, 미출현

4. 로또번호 45개 숫자 중에서 필요 없는 숫자 걸러내기 (제외수) 

45개의 숫자 중 15개를 걸러내는 방법이 있습니다 
로또사이트 같은 곳은 나올 확률이 많은 번호를 우선적으로 조합시켜주기 때문에 거기에 조합받은 숫자를 활용해서 45개의 숫자 중 15개의 쓸데 없는 숫자를 걸러내고 
나머지 30개의 숫자 안에 그 회차 로또번호 6개가 다 나오기를 기대하는 겁니다. 

2번 3번 4번의 방법대로 하셔서 거기에 예상번호를 1번 로또번호 45개중 필요없는 숫자를 걸러내서 
남은 숫자중에 중복되는 숫자가 나올확률이 엄청 많더군요 

밑에 3가지 방법을 활용하시고 마지막으로 로또 사이트로 숫자를 골라내는것이지요 
무료회원가입만으로 로또번호 10조합을 받을수 있거든요 
그리고 그 10조합안에 그주 로또번호  6개가 다 나오더군요 
그리고 45개숫자중에 15개의 쓸데없는 숫자는 걸러지고 30개의 번호안에 찾을수 있으니 확률을 더욱 높여지고요 
10게임을 받으셨으면 그 번호대로 찍으시라는게 아니고 거기서 조합을 좀 하셔야 합니다 
 쓸데없는 번호는 걸려내야 하거든요 

그 10게임안에 로또번호가 80~100% 나오더군요 

   10게임을 쭉 전체적으로 보시고 중복된숫자도 있고 나오지 않는 숫자도 있고 그렇습니다 
   나오는 숫자를 메모지에 다 적으세요 그러면 나오지 않는 숫자가 있을겁니다 
   나오지 않는 숫자는 당첨번호에 거의 나오지 않더군요 
   그러면 45개의 번호에서 30~34개로 확 줄어듭니다 
   확률이 확 높아 졌지요 

- 이제 30개안팍의 번호중 골라내야 하는데요 
   중복된 숫자 세번나오는 번호가 나올 확률이 많습니다 
   예를 들어 1,2,23,25,28,44와 5,13,23,36,42와 6,17,15,23,26,45가 있으면 23이 세번중복된 숫자이지요? 
   그럼 23이 80%이상 나오더라고요 
   세번 중복된 숫자는 많이 나오지만 네번이나 다섯번 혹은 두번중복된 숫자는 잘 안나오네요 

- 그리고 한줄에 같은 번대 수가 3개 나오는 번호가 당첨확률이 높습니다 
   예를 들어 12,23,26,29,30,42 가 있으면 20번대가 3개 있으니까 23,26,29 중에 하나는 나올 확률이 높더군요 


5. 달력에서 숫자찾기 

   이번에는 다른 방식으로 발견한 건데요 이건 좀 많이 신기하게도 
   달력에서 번호를 찾아내는 겁니다 예를 들어 6월 17일날이 로또당첨번호발표일이라면 
   그 달 6월의 월요일을 살펴보세요 한달의 월요일은 총 4번정도 있지요 
   그 4개의 숫자중에서 엄청 많이 나오더군요 많게는 2개도 나올경우가 있습니다 
   나올확률은 60~70%이상입니다 
   

6. 전회차 10회동안 안나온 숫자 파악하기 (미출현) 

    이번회차가 예를 들어 234회라고 하면 전10회차에 나온 로또번호 숫자를 파악하시기 바랍니다 
    이번회차가 234회차라면 224~233회차를 살펴보시면되겠지요 
    그중에 나온숫자를 걸러 내시고 안나온 숫자를 파악하십시오 
    그중 숫자중에 반드시 나옵니다

반응형

+ Recent posts