본문 바로가기

내일 배움 캠프/Weekly I learned

WIL 20230820

728x90

0. 기간 : 

20230814~ 20230820

1-1. 이번 주 동안 있었던 일

최종 프로젝트 시작

마이크로 서비스 아키텍처 공부

1-2. 이번 주 체크리스트

[ O ] 예쁜 말로 협업하였는가?

[ O ] 12시간 동안 몰입하였는가?

[ O ] 코딩 시 복사 + 붙여넣기를 하지 않았는가?

[ ? ] 친절한 개발자였는가?

[ O ] 이번 주를 평가하였을 때 주니어 개발자가 될 수 있겠는가?

 

2. 이번 주 소감

이번 주에는 많은 것들을 배울 수 있었다.

TCP서버를 처음으로 접하게 되었는데 너무 기뻤다.

3. 이번 주에 배운 것

3-1. 마이크로 서비스 아키텍처

: 서비스 단위로 서버를 나누어 운영하는 설계 방법

 

service> (tcp) > gateway > (http) > client

 

gateway : http 요청을 받는 또 다른 서버

 

DevOps의 에자일 방식 개발 

: 어플리케이션 단위별로 테스트  배포 > 과정 순환

 

3-2. Node.js TCP서버에서 query와 params를 이용하여 CRUD 하기

방법 

query

1. 파싱한 uri에서 uri.query를 변수에 담아 파라미터로 보내 진행한다.

2. TCP server에서 받은 패킷 안에서 정보를 빼내온다.

 

params

/users/1 로 접속을 허용하면서 값을 가져와야 한다고 가정하자.

1. localhost:8000으로 들어와서 Gateway를 지나기 전에 pathname을 split를 통해 가공한다. > /users 까지만 나오게

2. 뒤따라오는 params부분을 body의 JSON을 담은 객체 안에 넣어준다.

3. TCP server에서 받은 패킷 안에서 정보를 빼내온다.

 

 

app.js

import http from 'http';
import url from 'url';
import dotenv from 'dotenv';
import TcpClient from './classes/client';
import { makePacket } from './utils/makePacket';

dotenv.config();

const port = process.env.GATE_PORT;

let mapClients = {};
let mapUrls = {};
let mapResponse = {};
let mapRR = {};
let index = 0;

const server = http
  .createServer((req, res) => {
    const method = req.method;
    const uri = url.parse(req.url, true);
    const pathname = uri.pathname;

    if (method === 'POST') {
      let body = '';

      req.on('data', function (data) {
        body += data;
      });
      req.on('end', function () {
        let params;
        if (req.headers['content-type'] === 'application/json') {
          params = JSON.parse(body);
        }
        onRequest(res, method, pathname, params);
      });
    } else if (method === 'DELETE' || method === 'PATCH') {
      let body = '';

      req.on('data', function (data) {
        body += data;
      });
      req.on('end', function () {
        let params;
        if (req.headers['content-type'] === 'application/json') {
          params = JSON.parse(body);
        }
        const path = '/' + pathname.split('/')[1];
        if (params) {
          params.params = pathname.split('/')[2];
        } else {
          params = { params: pathname.split('/')[2] };
        }
        onRequest(res, method, path, params);
      });
    } else {
      onRequest(res, method, pathname, uri.query);
    }
  })
  .listen(port, () => {
    console.log(`Example app listening on port ${port}`);

    // Distributor 와 통신 처리
    const packet = makePacket('/distributes', 'POST', 0, {
      port: process.env.GATE_PORT,
      name: 'gate',
      urls: [],
    });

    let isConnectedDistributor = false;

    const clientDistributor = new TcpClient(
      process.env.HOST,
      process.env.DIS_PORT,
      (options) => {
        // 접속 이벤트
        isConnectedDistributor = true;
        clientDistributor.write(packet);
      },
      (options, data) => {
        onDistribute(data);
      }, // 데이터 수신 이벤트
      (options) => {
        isConnectedDistributor = false;
      }, // 접속 종료 이벤트
      (options) => {
        isConnectedDistributor = false;
      }, // 에러 이벤트
    );

    // 주기적인 Distributor 접속 상태 확인
    setInterval(() => {
      if (isConnectedDistributor !== true) {
        clientDistributor.connect();
      }
    }, 3000);

    // users 와 통신 처리
    const packetLogin = makePacket('/users', 'POST', 0, {
      port: process.env.USERS_PORT,
      name: 'users',
      urls: [],
    });
    let isConnectedLogin = false;

    const clientLogin = new TcpClient(
      process.env.HOST,
      process.env.USERS_PORT,
      (options) => {
        // 접속 이벤트
        isConnectedLogin = true;
        clientLogin.write(packetLogin);
      },
      (options, data) => {
        onLogin(data);
      }, // 데이터 수신 이벤트
      (options) => {
        isConnectedLogin = false;
      }, // 접속 종료 이벤트
      (options) => {
        isConnectedLogin = false;
      }, // 에러 이벤트
    );

    // 주기적인 Distributor 접속 상태 확인
    setInterval(() => {
      if (isConnectedLogin !== true) {
        clientLogin.connect();
      }
    }, 3000);
  });

// API 호출 처리
export function onRequest(res, method, pathname, params) {
  const key = method + pathname;
  const client = mapUrls[key];
  if (client == null) {
    res.writeHead(404);
    res.end();
  } else {
    const packet = makePacket(pathname, method, index, params);

    mapResponse[`key_${index}`] = res;
    index++;
    if (mapRR[key] == null) {
      mapRR[key] = 0;
    }

    mapRR[key]++;
    client[mapRR[key] % client.length].write(packet);
  }
}

export function onDistribute(data) {
  for (let n in data.params) {
    const node = data.params[n];
    const key = node.host + ':' + node.port;

    if (mapClients[key] == null && node.name !== 'gate') {
      const client = new TcpClient(
        node.host,
        node.port,
        onCreateClient,
        onReadClient,
        onEndClient,
        onErrorClient,
      );

      mapClients[key] = {
        client: client,
        info: node,
      };

      for (let m in node.urls) {
        const key = node.urls[m];
        if (mapUrls[key] == null) {
          mapUrls[key] = [];
        }
        mapUrls[key].push(client);
      }
      client.connect();
    }
  }
}

export function onLogin(data) {
  for (let n in data.params) {
    const node = data.params[n];
    const key = node.host + ':' + node.port;

    if (mapClients[key] == null && node.name !== 'users') {
      const client = new TcpClient(
        node.host,
        node.port,
        onCreateClient,
        onReadClient,
        onEndClient,
        onErrorClient,
      );

      mapClients[key] = {
        client: client,
        info: node,
      };

      for (let m in node.urls) {
        const key = node.urls[m];
        if (mapUrls[key] == null) {
          mapUrls[key] = [];
        }
        mapUrls[key].push(client);
      }
      client.connect();
    }
  }
}

// 마이크로서비스 접속 이벤트 처리
function onCreateClient(options) {
  console.log('onCreateClient');
}

// 마이크로서비스 응답 처리
function onReadClient(options, packet) {
  console.log('onReadClient', packet);

  mapResponse[`key_${packet.key}`].writeHead(200, { 'Content-Type': 'application/json' });
  mapResponse[`key_${packet.key}`].end(JSON.stringify(packet));
  delete mapResponse[`key_${packet.key}`]; // http 응답 객체 삭제
}

// 마이크로서비스 접속 종료 처리
function onEndClient(options) {
  const key = options.host + ':' + options.port;
  console.log('onEndClient', mapClients[key]);

  for (let n in mapClients[key].info.urls) {
    const node = mapClients[key].info.urls[n];
    delete mapUrls[node];
  }
  delete mapClients[key];
}

// 마이크로서비스 접속 에러 처리
function onErrorClient(options) {
  console.log('onErrorClient');
}

3-3. 망형 토폴로지에 대한 고찰

문제

내가 구축한 건 API와 API사이의 다리이다.

서비스와 서비스를 이어야 한다.

시도 

1. distributor와 비슷한 users.topology.js 파일을 만든다.

2. Users 전체 정보 조회 url을 기준으로 정보 조회가 가능한 서버를 만든다.

3. 함수를 불러오면 실행이 된다.

 

users.topology.js

import TcpServer from '../classes/server';
import { makePacket } from '../utils/makePacket.js';
import dotenv from 'dotenv';
dotenv.config();

// 접속 노드 관리 오브젝트
// Server 클래스 상속
class usersTopology extends TcpServer {
  map = {};

  constructor() {
    super('users', process.env.USERS_PORT, [
      'POST/users',
      'GET/users',
      'PATCH/users',
      'DELETE/users',
      'POST/users/login',
    ]);
  }

  // 노드 접속 이벤트 처리
  onCreate(socket) {
    console.log('onCreate', socket.remoteAddress, socket.remotePort);
    this.sendInfo(socket);
  }

  // 노드 접속 해제 이벤트 처리
  onClose(socket) {
    const key = socket.remoteAddress + ':' + socket.remotePort;
    console.log('onClose', socket.remoteAddress, socket.remotePort);
    delete this.map[key];
    this.sendInfo();
  }

  // 노드 등록 처리
  onRead(socket, json) {
    const key = socket.remoteAddress + ':' + socket.remotePort;
    console.log('onRead', socket.remoteAddress, socket.remotePort, json);

    if (json.uri === '/users' && json.method === 'GET') {
      this.map[key] = {
        socket: socket,
      };
      this.map[key].info = json.params;
      this.map[key].info.host = socket.remoteAddress;
      this.sendInfo();
    }
  }

  // 패킷 전송
  write(socket, packet) {
    socket.write(JSON.stringify(packet) + '¶');
  }

  // 접속 노드 혹은 특정 소켓에 접속 노드 정보 전파
  sendInfo(socket) {
    const packet = makePacket('/users', 'GET', 0, []);

    for (let n in this.map) {
      packet.params.push(this.map[n].info);
    }

    if (socket) {
      this.write(socket, packet);
    } else {
      for (let n in this.map) {
        this.write(this.map[n].socket, packet);
      }
    }
  }
}

//객체 생성
new usersTopology();

해결

이제 user.topology.js를 통해 유저 서비스에 접근하여 정보를 어디서든지 볼 수 있다.

단, 실시간 업데이트가 안 된다. setInterval을 이용하여 도전 중이다.

알게 된 점

망형 토폴로지와 마이크로 <서비스> 아키텍처는

서비스 단위로 연결을 한다. (뇌피셜이나 느낌이 좋다.)

 

3-4. 망형 토폴로지의 DB에서 수정된 정보 조회 시 반영

문제

수정은 되는데 수정된 내용이 반영되지 않는다.

시도

 this.connectToUsers(process.env.HOST, process.env.USERS_PORT, (data) => {
          // Users 접속
          this.data = data;
        });

DB를 작업이 필요할 때 1번 더 불러온다.

해결

속도가 느리나 되기는 됨. 두 세 번 클릭해야 되고 운이 좋으면 한 번에 됨.

알게 된 점

성능 개선이 필요하다. 

4. 배운 것들을 토대로 적용할 미래

서버를 이해하는 개발자 

운이 좋으면 서버 개발자

5. 다음주 목표

수정 된 데이터 반영 속도 개선

마이크로 서비스 아키텍처 공부

뼈대 제작 및 백엔드 시작

6. 나에게 응원 마디

내배캠 기간 중에 제일 잘했다.

'내일 배움 캠프 > Weekly I learned' 카테고리의 다른 글

WIL 20230903  (0) 2023.09.03
WIL 20230827  (0) 2023.08.27
WIL 20230813  (0) 2023.08.13
WIL 20230806  (0) 2023.08.06
WIL 20230730  (0) 2023.07.30