WIL (21.09.13-21.09.17)

Docker

왜 쓸까

서버 환경을 구축하기위해서는 언어, 웹서버, 데이터베이스, 자동 배포 툴 등 여러 가지를 버전 관리하면서 설정해야한다. 서버를 운영하다보면 더 성능 좋은 서버로 옮겨가거나, (늘어난 접속량을 감당하기위해서) 서버를 추가하게 되면 똑같이 새로 설치를 해야한다. 같은 서버에 여러 서비스를 돌리거나, 각각이 다른 실행 환경에서 동작해야할 때 번거로워진다. 이에 대한 해결책으로 도커가 많이 사용되고 있다.

실행 원리

각 요소들이 설치된 모습을 '이미지' 란 형태로 박제해서 저장한다.
Git으로 저장된 내용들이 Github에 올려지는 것처럼 DockerHub에 저장되어 다운받아질 수 있다.
이미지로 저장된 항목들이 함께 연결돼서 동작하도록 설정된 상태를 명령어 텍스트나 문서 형태로 저장할 수도 있다. 이 문서만 갖고 있다면, 설치하는 과정을 어디서든 미리 지정된, 서비스에 필요한 설정대로 컴퓨터가 자동으로 재현할 수 있게 해준다.
도커는 컨테이너라 불리는 독립된 가상 공간에서 이미지를 복원한다. 컨테이너 덕분에 다른 버전의 자바를 돌리는 서비스들도 각각의 컨테이너 안에서 서로 방해받는 일 없이 실행될 수 있는 것이다.

Redis

캐시, 혹은 캐시서버 필요성

WEB-WAS-DB의 3티어 구조는 사용자가 늘어나면 점점 DB에 무리가 가기 시작한다.
매 transaction마다 DB가 있는 물리 디스크에 접근해야하기때문에 부하가 걸리고 느려지게 된다.
결국 빠르게 DB에 있는 데이터를 가져기 위해 캐시서버의 도입을 검토하게 된다.

캐시가 뭐였더라

캐시란 한 번 읽어온 데이터를 임의의 공간에 저장하여 다음에 읽을 때는 빠르게 결과값을 받을 수 있도록 도와주는 공간.
같은 요청이 여러번 들어오는 경우에는 캐시서버에서 바로 결과값을 반환해주기 때문에 DB 부하를 줄일 수 있음과 동시에 서비스의 개선도 이룰 수 있다.

웹 서버는 데이터를 DB에서 가져오기 전에 캐시 서버에 데이터가 있는 지 확인하고, 있다면 DB에 요청을 보내지 않고 바로 클라이언트에 응답한다. (cache hit)

만약 캐시 서버에 데이터가 없다면, 웹서버는 DB에 데이터를 요청하고, 응답받은 데이터를 다음 사용을 위해 캐시에 저장한 후에 클라이언트에 반환한다. (cache miss)

그래서 레디스가 뭐야

키-값 기반의 인-메모리 데이터 저장소
키-값 기반이기 때문에 쿼리를 따로 할 필요없이 결과를 바로 가져올 수 있다.
디스크에 데이터를 쓰는 구조가 아니라 메모리에서 데이터를 처리하기 때문에 속도가 상당히 빠르다.

다양한 데이터 구조를 제공하는 레디스

다양한 데이터 구조를 제공하는 특성 덕분에 레디스는 다양한 용도로 활용될 수 있다고 한다.

  1. Strings : 단순한 키-값 매핑 구조
  2. Lists : Array형식의 데이터구조 (처음과 끝
    List를 사용하면 처음과 끝에 데이터를 넣고 빼는 것은 속도가 빠르다. (Linked List를 생각하면 되는건가?) 그지만 중간에 데이터를 삽입할 때는 어려움이 있다.
  3. Sets : 순서가 없는 Strings 데이터 집합. Sets에서는 중복된 데이터는 하나로 처리한다.
  4. Sorted Sets : Sets와 같은 구조이지만, Score를 통해서 순서를 정할 수 있다.
  5. Hashes : 키-값의 구조를 여러개 가진 object 타입을 저장하기 좋은 구조 (해쉬테이블)

ioredis 로 node.js 환경에서 redis 사용하기

설치
yarn add ioredis connect-redis express-session
yarn add @types/ioredis -D
import Redis from 'ioredis';
기본 사용법
const Redis = require("ioredis");
const redis = new Redis(); // uses defaults unless given configuration object

// ioredis supports the node.js callback style
redis.get("foo", function (err, result) {
  if (err) {
    console.error(err);
  } else {
    console.log(result); // Promise resolves to "bar"
  }
});

// Or ioredis returns a promise if the last argument isn't a function
redis.get("foo").then(function (result) {
  console.log(result); // Prints "bar"
});

// Most responses are strings, or arrays of strings
redis.zadd("sortedSet", 1, "one", 2, "dos", 4, "quatro", 3, "three");
redis.zrange("sortedSet", 0, 2, "WITHSCORES").then((res) => console.log(res)); 
// Promise resolves to ["one", "1", "dos", "2", "three", "3"] as if the command was ` redis> ZRANGE sortedSet 0 2 WITHSCORES `

// All arguments are passed directly to the redis server:
redis.set("key", 100, "EX", 10);

// ioredis supports all Redis commands:
redis.set("foo", "bar"); 
Redis 에 연결하기
new Redis(); // Connect to 127.0.0.1:6379
new Redis(6380); // 127.0.0.1:6380
new Redis(6379, "192.168.1.1"); // 192.168.1.1:6379
new Redis("/tmp/redis.sock");
new Redis({
 port: 6379, // Redis port
 host: "127.0.0.1", // Redis host
 family: 4, // 4 (IPv4) or 6 (IPv6)
 password: "auth",
 db: 0,
});
Pub/Sub 기능 사용하기
/* publisher.ts */
import Redis from "ioredis";
const redis = new Redis();

setInterval(() => {
 const message = { foo: Math.random() };
 // Publish to my-channel-1 or my-channel-2 randomly.
 const channel = `my-channel-${1 + Math.round(Math.random())}`;

 // Message can be either a string or a buffer
 redis.publish(channel, JSON.stringify(message));
 console.log("Published %s to %s", message, channel);
}, 1000);
/* subscriber.ts */
import Redis from "ioredis";
const redis = new Redis();

redis.subscribe("my-channel-1", "my-channel-2", (err, count) => {
  if (err) {
    // Just like other commands, subscribe() can fail for some reasons,
    // ex network issues.
    console.error("Failed to subscribe: %s", err.message);
  } else {
    // `count` represents the number of channels this client are currently subscribed to.
    console.log(
      `Subscribed successfully! This client is currently subscribed to ${count} channels.`
    );
  }
});

redis.on("message", (channel, message) => {
  console.log(`Received ${message} from ${channel}`);
});

// There's also an event called 'messageBuffer', which is the same as 'message' except
// it returns buffers instead of strings.
// It's useful when the messages are binary data.
redis.on("messageBuffer", (channel, message) => {
  // Both `channel` and `message` are buffers.
  console.log(channel, message);
});

Axios-Mock-Adapter

왜 쓸까

API에서 내려주지 않는 케이스에 대해 테스트가 필요할 때 mock data 를 생성해서 활용할 수 있다.

사용 방법

필요한 모듈 설치

yarn add axios
yarn add axios-mock-adapter @types/axios-mock-adapter -D

GET request

import axios from 'axios';
import MockAdapter from "axios-mock-adapter';

let mock = new MockAdapter(axios);

// Mock GET request to /users when param `searchText` is 'John'
// arguments for reply are (status, data, headers)
mock.onGet("/users", { params: { searchText: "John" } }).reply(200, {
  users: [{ id: 1, name: "John Smith" }],
});

axios
  .get("/users", { params: { searchText: "John" } })
  .then(function (response) {
    console.log(response.data);
  });

params 를 사용할 때는 꼭 모든 keyvalue 가 옵션을 통과하도록 매치해야한다.

import axios from "axios";
import MockAdapter from "axios-mock-adapter";

// This sets the mock adapter on the default instance
let mock = new MockAdapter(axios);

// Mock GET request to /users when param `searchText` is 'John'
// arguments for reply are (status, data, headers)
mock.onGet("/users", { params: { searchText: "John" } }).reply(200, {
 users: [{ id: 1, name: "John Smith" }],
});

axios
 .get("/users", { params: { searchText: "John" } })
 .then(function (response) {
   console.log(response.data);
 });

좋은 웹페이지 즐겨찾기