메시지 큐사이 비동기 통신 프로토콜을 제공송신자와수신자가 같은 시간에 메시지 큐와 상호 작용 필요가 없습니다 메시지를. 큐에 배치 된 메시지는받는 사람이 검색 할 때까지 저장됩니다.
메시지 큐 패러다임은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의 사용 사례 중 하나 입니다.
수강 신청, 예매, 온라인 접수, 게시판 폭주 등 동시 접속 폭주로 발생하는 시스템 마비와 서비스 중단을 방지할 수 있는 솔루션을 말한다.
접속을 거부하거나 지연시키는 기존의 접근제어 솔루션과 달리 임계치를 초과하는 접속 요청은 대기 정보를 제공해 순차적으로 접속할 수 있도록 한 것이다. 기존 접속자의 접속을 최대한 보장함과 동시에 접속 예상시간과 대기 인원을 확인할 수 있도록 함으로써 웹 접속자의 이용 편의성도 개선한 솔루션이다.
서비스를 제공하는 메인 서버와 접속제어 솔루션이 위치한 대기 서버를 별개로 분리시킨 후, 모든 요청을 대기 서버로 리다이렉트 한다. 이후 접속제어 솔루션은 설정된 동시접속 수 및 초당 접속인원을 제어하며 대기순번 부여 및 API를 통한 메인 서버의 접근 권한을 제어한다.
대기순번 부여방식은 선착순 부여방식과 동시 부여방식이 있다. 선착순 부여방식은 예정된 시간부터 접속한 순서대로 순번을 부여하지만, 시간이 되자말자 새로고침 또는 접속을 하는 사람이 많아 자칫하면 대기 서버가 터져 버리거나 바이패스[1]가 작동해 메인 서버까지 터져버릴 수 있다.
동시 부여방식은 예정된 시간까지 접속해있던 모든 클라이언트에게 랜덤으로 대기순번을 부여하고, 이후 접속자는 제일 후순위로 배정하는 방법이다. 사람들이 순차적으로 접속하기 때문에 서버에 가해지는 부담은 적으나, 유리한 순번을 얻기 위해 중복 접속하는 경우가 많다.
이번에 회사에서 새로운 상품을 오픈하기 위해 대대적인 마케팅?을 진행한다는 것을 전달받아 대기열 시스템을 설계, 개발을 진행했습니다.
왜? 우리는 대기열 시스템이 필요했을까?
현재 대고객 서비스를 위한 개발 및 운영하고 있으며, 상품에 대한 주요정보 등을 코어영역와의 통신을 통해 처리하고 있다. (우리는 고객을 상대하는 채널이다.) 우리가 도입하려는 대기열 시스템은 고객의 동접을 대응하지 못할때 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를 적용할때 충분한 요건을 가진 자료형이다.
대기열 로직
고객 대기열 페이지 진입.
대기열 Queue에 고객 Key값 Insert
Batch에서 일정 시간마다 작업업열로 이동 가능한 Capability 확인
가능한 Capability 있으면 대기열에서 작업열로 이동.
대기열 페이지에 진입한 고객
현재 위치 확인, 대기열 전체 사이즈 및 작업열 이동여부 조회.
작업열 이동 가능 여부 확인.
작업열에 유효한 Key값인지 확인
기존 발급된 이력이나, 만료된 Key값인지 확인.
동접 Ticket 발부.
세션 유지.
고객 이탈시 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배로 가정해도 서비스가 버틸 수 있는 수치이다.
✔ 마무리
상품을 오픈하고 대기열 서비스를 오픈했었고, 큰 이슈 없이 프로모션들을 넘어갔다. 그리고 아쉽게 죽어있는 상태 ㅎㅎ 많은 사람들이 레거시에 대한 문제와 기술 도입에 대한 보수적인 의견을 가지고 있다고 생각한다. 틀린말은 아니다. 그걸 해결하고 극복하기 위해 내가 존재하며, 좋은 결과물을 도출해낼 것이다.
코인의 세계에 입문하게 되면, 다양한 코인 활동?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 andJ.philas well"
Burning
defintion > 코인 소각 또는 코인 버닝이란, 개인키가 없는 암호 화폐 지갑주소로 암호화폐를 전송하여 다시는 사용할 수 없도록 만드는 행위를 뜻한다. 여기서 Coin Burn 또는 간략히 Burn, Burning, 소각이라고도 칭한다. 해당 지갑 주소로 전송된 암호화폐를 다시 사용하려면 지갑을 열 수 있는 개인키를 알아야한다. 개인키가 없으면 사용이 불가능하다. 이처럼 개인키가 없는 지갑은 코인을 받을 수만 있고 꺼낼수가 없으므로, 코인을 이런 지갑으로 보내면 사실상 코인을 Burning 소각하는 거와 같다.
example > Burning 은 코인 발행회사나 암호화폐 거래소에서시장에 유통되는 코인의 공급량을 줄여 코인 가격을 상승시키기 위한 목적으로 시행되는 경우가 많다. 떄로는 새로운 블록을 생성하고, 신규 암호화폐를 발행하기 위해 소각의 방법을 사용하는 경우도 많다. 가령,소각 증명합의 알고리즘을 사용하면, 기존에 비트코인 등에서 사용하던 작업증명 방식을 사용하지 않고도 신규 블록 생성자에게 암호화폐를 보상으로 지급할 수 있다.
Minting
definition > 민팅(Minting) 이란 그림이나 영상 등 디지털 자산의 대체불가능토큰(NFT)을 생성하는 것을 일컫는 용어다. 민팅은화폐를 주조한다는 뜻의 영단어인 민트(Mint)를 옮긴 말에서 시작된 것으로, 블록체인 상에서 암호화폐를 발행하는 것을 의미한다. 암호화폐 코인이나 토큰이 만들어질 때마다 '민팅'의 과정을 거치는 셈으로, 하나의 암호화폐 당 수백만 개에서 수십억 개의 코인 또는 토큰이 민팅된다. 이에 따라 통상적으로 암호화폐의 '최대 공급량'은 민팅이 될 수 있는 최대한의 코인이나 토큰의 수를 의미하게 된다. 이렇게 민팅된 디지털 자산은 NFT 플랫폼에서 거래가 이뤄진다.
definition > 가상화폐 채굴은 블록체인이라고 알려진 퍼블릭 디지털 기록으로 거래를 기록하고 검증하는 절차를 말한다. 이를 위해 채굴자는 복잡한 수학 문제를 해결하고, 그 대가로 가상화폐라는 보상을 받게 된다.작업증명(PoW)절차에 따른 채굴의 결과로 새로운 블록이 생성됩니다. 새로운 블록을 채굴하면 블록체인의 연속성이 이어진다. 따라서 채굴은 두 가지 목적을 수행합니다: 신규 코인을 생성하고 기존의 모든 토큰 기록을 유지한다.
example
지난 6일(현지시간) 기준 비트코인(BTC) 총 공급량은 1850만여개를 넘어섰습니다.
비트코인은 2009년 처음 등장했을 때부터 발행할 수 있는 수량이 2100만개로 정해졌습니다. 기존 화폐(법정화폐, 디지털 화폐 등)와 달리 비트코인은 발행량을 늘리고 싶다고 해서 2100만개 이상을 발행할 수 없습니다.
앞으로 채굴할 수 있는 비트코인은 250만여개에 불과합니다.
출처 : 코인데스크 코리아 (http://www.coindeskkorea.com/news/articleView.html?idxno=77573)
Airdrop
definition > Air 공중에서 Drop 떨어뜨린다는 뜻으로서, 기존 암호화폐 소유자들에게무상으로 코인을 배분하여 지급하는 행위를 말한다. 주식에서무상증자와 유사한 개념이다.
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 전망이 다양한 굵직한 사건으로 매우 부정적으로 보여서 일단 다시 기회를 볼계획이다.
유저 요청에 대한 선 처리 : 유저의 요청이 WAS에 도달하기 전에 다양한 처리를 할 수 있다. 웹 애플리케이션 방화벽(WAF)를 설치하거나, 유저의 요청을 다른 위치로 보내도록 제어 할 수 있다.
캐싱 : 웹 서비스는 이미지, CSS, 자바스크립트 같은 정적인 페이지를 가지고 있다. 이런 정적 컨텐츠들을 NginX에서 대신 처리하는 것으로 응답 속도를 높일 수 있으며, WAS에 대한 부담을 줄일 수 있다. 컨텐츠들을 메모리에 캐시할 경우 서비스 할 경우 고성능의 웹 서비스를 만들 수도 있다.
웹 서비스의 성능을 높일 수 있는 가장 확실하고 손쉬운 방법은컨텐츠 캐시다. 요즘 웹 서버는 매우 바쁘다. 유저의 요청을 받아서 데이터베이스를 조회해서 즉석에서 HTML 페이지를 만들고 이미지와 CSS, 자바스크립트 등 다양한 오브젝트들을 함께 응답해야 한다. 웹 서버가 해야 하는 일이 늘어나면서리버스 프락시를 이용, 두 개 이상의WAS(Web Application Server)가 요청을 분산해서 처리하도록 구성을 한다.
유저가 요청을 내리면 WAS로 바로가지 않고, 리버스 프락시 서버를 한번 거치게 된다. 요청을 분산하기 위해서 프락시 서버를 한번 더 거쳐야 하는데, 모든 컨텐츠가 굳이 프락시 서버를 거칠 필요가 있을까 ?변하지 않는 컨텐츠의 경우에는 그냥 프락시 서버에서 직접 응답을 하게 하면 더 빠른 서비스를 제공 할 수 있지 않을까 ? 요즘 왠만한 웹 서비스들은 동적으로 컨텐츠를 서비스 한다. 하지만 조금만 들여다 보면, 많은 컨텐츠가 정적(변하지 않음)이라는 것을 알 수 있다. 이미지, CSS, Javascript, 동영상, PDF 뿐만 아니라 HTML 문서의 상당수도 정적이다. 유저 정보를 표시하는 HTML 문서는 (데이터베이스 등을 조회해서) 동적으로 만들어져야 하겠으나, 메뉴얼, 사이트 소개, 메뉴 등은 변하지 않는 컨텐츠들이다. 이들 컨텐츠들을 프락시 서버에서 서비스 한다면 더 빠르고 효율적으로 서비스 할 수 있을 것이다. 아래 그림을 보자.
리버스 프락시에서 정적인 페이지를 대신 응답한다. 이렇게 구성해서 얻을 수 있는 성능상의 잇점을 살펴보자.
네트워크 홉 이 줄어든다. WAS까지 요청을 보내지 않아도 된다. 1만큼의 네트워크 홉을 줄일 수 있다.
파일처리에 최적화 할 수 있다. 캐시 서버는 파일에 대한 "읽기/쓰기" 연산, 그 중에서도 읽기 연산을 주로 한다. 목적이 특정되므로 이에 맞게 최적화 할 수 있다. 당연하지만 NginX나 Apache 같은 전용 웹서버는 다양한 일을 하는 WAS(Django, RoR, Node)보다 빠르고 효율적으로 작동한다.
최적화가 쉽다. 정적 컨텐츠와 동적 컨텐츠를 분리 함으로써, 그에 맞는 최적화 방법을 사용 할 수 있다. 정적 컨텐츠를 Redis나 memcache, 램디스크 등으로 캐시할 수 있다.
테스트를 위해서 WAS 서버를 구성했다. 이 서버는 HTML, CSS, 자바스크립트, 이미지등을 서비스한다. NginX로 만들기로 했다. 아래는 설정파일이다.
# cat /etc/nginx/sites-available/default
server {
listen 80 default_server;
listen [::]:80 default_server;
root /var/www/html;
# Add index.php to the list if you are using PHP
index index.html index.htm index.nginx-debian.html;
server_name _;
location / {
try_files $uri $uri/ =404;
}
location ~* \.(?:manifest|appcache|html?|xml|json)$ {
expires -1;
}
location ~* \.(?:jpg|jpeg|gif|png|ico|cur|gz|svg|svgz|mp4|ogg|ogv|webm|htc)$ {
expires 1M;
access_log off;
add_header Cache-Control "public";
}
location ~* \.(?:css|js)$ {
expires 1y;
access_log off;
add_header Cache-Control "public";
}
}
이 설정파일은 기본적인 캐시 설정을 포함하고 있다. manifest, appcache, html, xml, json 파일들은 캐시를 하지 않는다. jpg,gif,gz 과 같은 파일들은 한달의 만료기간동안 캐시를 한다. css와 js 파일들은 1년을 캐시한다.curl로 index.html을 요청해보자.
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까지 전달된다.
캐시파일이 저장되는 위치와 저장 방식을 설정하기 위해서 사용한다.levels는 캐시파일을 어떻게 저장할지를 결정한다. 만약 levels를 설정하지 않는다면, 캐시파일은 현재 디렉토리에 저장이 된다. level을 설정할 경우, 서브디렉토리에 md5 해시된 파일이름으로 컨텐츠를 저장한다. level=1:2는 첫번째 단계의 디렉토리는 한글자, 두번째 단계의 디렉토리는 두 글자로 명명하라는 의미다. 예를 들어/tmp/nginx/a/bc디렉토리에 캐시 파일이 저장된다.keys_zone은 이 캐시를 가리키는 이름이다.my_zone은 캐시와 메타파일을 저장하기 위해서 10M의 공간을 할당 했다.
inactive지시어를 사용 하면, 일정 시간동안 접근이 없는 캐시파일을 할 수 있다. 위 설정에서는 한시간(60 minutes)동안 사용하지 않는 캐시파일은 삭제하도록 설정했다.
Joinc는Go로 직접 개발한 CMS(자칭 gowiki)로 운영하고 있다. 모니위키를 기반으로 하고 있기 때문에, 위키문법의 문서를HTML로 변환하는데 상당한 자원이 들어간다. 성능을 올리기 위해서 gowiki 서버 앞에 nginx 캐시서버를 두기로 했다.
/w 로 시작하는 URL의 컨텐츠는 정적인 컨텐츠이므로 캐시하기로 했다. 예를 들어 처음/w/FrontPage를 요청하면, Gowiki로 요청이 전달된다. Gowiki는 FrontPage 문서를 HTML로 변환해서 응답한다. 이 응답은 NginX 서버가 캐시를 하므로, 다음번 요청 부터는 캐시에 있는 데이터를 응답한다.동적인 컨텐츠는/api로 요청을 한다. 코드 실행기가 대표적인 경우인데, 이런 컨텐츠는 캐시하면 안 된다. 그리고 css, js, png, jpg 등도 캐시하기로 했다. NginX의 설정은 다음과 같다.
일단 캐시가된 컨텐츠의 경우, 컨텐츠의 내용이 바뀌더라도 여전히 캐시의 내용을 읽어오는 문제가 있다. 위키 특성상 문서를 자주 수정하게 되는데, 수정한 내용이 보이지 않는 것은 심각한 문제가 될 수 있다. 문제를 해결하기 위해서는 문서의 수정 후 해당 컨텐츠의 캐시를 찾아서 삭제하는 코드를 만들어야 했다. 직접 만들기 귀찮아서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 한 값은 아래와 같다.
모든 페이지들이 메뉴와 wiki 본문 으로 구성이 된다. 메뉴는 유저의 상태에 따라서 구성이 달라진다. 로그인 하기 전이라면 로그인 관련 메뉴를 출력하고,로그인 후라면 문서 편집과 같은 메뉴들이 출력된다. 컨텐츠가 동적으로 바뀌는 셈이다. 반면 Wiki 문서 내용은 정적 컨텐츠다. 만약 페이지 전체를 캐시를 해버리면, 메뉴 출력에 문제가 생길 것이다. 이 문제를 해결해야 했다.나는쿠기(cookie)를 이용해서 이 문제를 해결 했다. 아래와 같이 proxy_cache_key에cookie_login을 설정했다.
유저가 로그인에 성공하면 SetCookie("login", "true")로 login 쿠키를 설정한다. 로그아웃 하면 SetCookie("login","")로 쿠키를 삭제한다. 이렇게 하면 동일한 페이지라고 하더라도 로그인과 로그아웃 상태별로 캐시파일을 만들 수 있다. 간단하기는 하지만 캐시파일이 두배가 된다는 단점이 있다. 메뉴부분을 모두 자바스크립트로 처리하는 방법도 있는데, "개인 사이트에서 캐시공간이 늘어나 봤자 얼마나 늘어나겠나 ?"라는 생각에 패스하기로 했다(귀찮아서 패스했다는 얘기다).
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를 이용해 위치정보를 시각화하는 라이브러리입니다
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 inrange(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
importpyechartsprint("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]:
frompyechartsimport 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
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회차를 살펴보시면되겠지요 그중에 나온숫자를 걸러 내시고 안나온 숫자를 파악하십시오 그중 숫자중에 반드시 나옵니다