Skip to content

Commit

Permalink
[feat]: Redis 장애 복구 API 추가 (#131)
Browse files Browse the repository at this point in the history
## 작업사항
1. Redis 서버 장애 시, 데이터가 모두 사라질 경우를 대비해서, RDS를 활용하여 Redis에 데이터를 추가하는 작업 로직 추가

## 관계된 이슈, PR 
#115
  • Loading branch information
epitone authored Apr 11, 2022
1 parent 6cc469c commit 47c6ac8
Show file tree
Hide file tree
Showing 7 changed files with 99 additions and 5 deletions.
14 changes: 14 additions & 0 deletions apps/api/src/summonerRecord/SummonerRecordApiQueryRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { plainToInstance } from 'class-transformer';
import { SummonerRecord } from '@app/entity/domain/summonerRecord/SummonerRecord.entity';
import { createQueryBuilder, EntityRepository, Repository } from 'typeorm';
import { SummonerRecordId } from '@app/entity/domain/summonerRecord/SummonerRecordId';
import { SummonerRecordSummonerId } from '@app/entity/domain/summonerRecord/SummonerRecordSummonerId';

@EntityRepository(SummonerRecord)
export class SummonerRecordApiQueryRepository extends Repository<SummonerRecord> {
Expand All @@ -19,6 +20,11 @@ export class SummonerRecordApiQueryRepository extends Repository<SummonerRecord>
return plainToInstance(SummonerRecordId, row);
}

async findAllSummonerRecordId(): Promise<SummonerRecordSummonerId[]> {
const row = await this.findAllSummonerId();
return plainToInstance(SummonerRecordSummonerId, row);
}

private async findOneBySummonerId(summonerId: string) {
const queryBuilder = createQueryBuilder()
.select(['id'])
Expand All @@ -35,4 +41,12 @@ export class SummonerRecordApiQueryRepository extends Repository<SummonerRecord>
.where(`summonerRecord.summonerId =:summonerId`, { summonerId });
return await queryBuilder.getRawOne();
}

private async findAllSummonerId() {
const queryBuilder = createQueryBuilder()
.select(['summoner_id'])
.from(SummonerRecord, 'summonerRecord');

return await queryBuilder.getRawMany();
}
}
26 changes: 25 additions & 1 deletion apps/push/src/push/PushApiConsumer.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,41 @@
import { PushApiService } from './PushApiService';
import { PushJobService } from './../../../../libs/common-config/src/job/src/PushJobService';
import { Process, Processor } from '@nestjs/bull';
import { Logger } from '@nestjs/common';
import { Job } from 'bull';
import { plainToInstance } from 'class-transformer';
import { RiotApiJobs } from '@app/common-config/job/RiotApi';
import { PushRiotApi } from './dto/PushRiotApi';

@Processor('PushQueue')
export class PushApiConsumer {
private readonly logger = new Logger(PushApiConsumer.name);

constructor(private readonly pushJobService: PushJobService) {}
constructor(
private readonly pushJobService: PushJobService,
private readonly pushApiService: PushApiService,
) {}

@Process('summonerList')
async getSummonerIdQueue(job: Job) {
await this.pushJobService.send(job.data['summonerId']);
this.logger.log(`${job.data['summonerId']} topic 푸시를 전송했습니다.`);
}

@Process('recoverList')
async getRecoverQueue(job: Job) {
const redisClient = await this.pushApiService.getRedisClient();
const riotApiResponse = plainToInstance(
PushRiotApi,
await RiotApiJobs(job.data['summonerId']),
);
await this.pushApiService.changeRecord(
riotApiResponse,
redisClient,
job.data['summonerId'],
);

await this.pushApiService.addRedisSet(redisClient, job.data['summonerId']);
this.logger.log(`${job.data}를 Redis에 추가했습니다.`);
}
}
13 changes: 12 additions & 1 deletion apps/push/src/push/PushApiController.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ResponseEntity } from '@app/common-config/response/ResponseEntity';
import { Controller, Get, Inject } from '@nestjs/common';
import { Controller, Get, Inject, Post } from '@nestjs/common';
import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston';
import { Logger } from 'winston';
import { PushApiService } from './PushApiService';
Expand All @@ -21,4 +21,15 @@ export class PushApiController {
return ResponseEntity.ERROR_WITH('메시지 전송에 실패했습니다.');
}
}

@Post('redis')
async addRedis(): Promise<ResponseEntity<string>> {
try {
await this.pushApiService.recoverRedis();
return ResponseEntity.OK_WITH('Redis에 데이터 추가를 성공했습니다.');
} catch (error) {
this.logger.error(error);
return ResponseEntity.ERROR_WITH('Redis에 데이터 추가를 실패했습니다.');
}
}
}
2 changes: 2 additions & 0 deletions apps/push/src/push/PushApiModule.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { SummonerRecordApiModule } from './../../../api/src/summonerRecord/SummonerRecordApiModule';
import { PushJobModule } from './../../../../libs/common-config/src/job/src/PushJobModule';
import { Module } from '@nestjs/common';
import { WinstonModule } from 'nest-winston';
Expand All @@ -17,6 +18,7 @@ import { ScheduleModule } from '@nestjs/schedule';
getBullQueue(),
RedisModule.register(RedisModuleConfig),
ScheduleModule.forRoot(),
SummonerRecordApiModule,
],
controllers: [PushApiController],
providers: [PushApiService, PushApiConsumer],
Expand Down
35 changes: 33 additions & 2 deletions apps/push/src/push/PushApiService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,20 @@ import { InjectQueue } from '@nestjs/bull';
import { Injectable } from '@nestjs/common';
import { Queue } from 'bull';
import { Interval } from '@nestjs/schedule';
import { SummonerRecordApiQueryRepository } from '../../../api/src/summonerRecord/SummonerRecordApiQueryRepository';

@Injectable()
export class PushApiService {
constructor(
@InjectQueue('PushQueue')
private pushQueue: Queue,
private readonly redisService: RedisService,
private readonly summonerRecordApiQueryRepository?: SummonerRecordApiQueryRepository,
) {}

// @Interval('pushCronTask', 10000)
async addMessageQueue(): Promise<void> {
const redisClient: Redis = this.redisService.getClient();
const redisClient: Redis = await this.getRedisClient();
const summonerIds = await redisClient.smembers('summonerId');
// summonerIds.map(async summonerId => {
// const riotApiResponse = plainToInstance(
Expand Down Expand Up @@ -60,6 +62,21 @@ export class PushApiService {
// }
}

async recoverRedis(): Promise<void> {
const summonerIds =
await this.summonerRecordApiQueryRepository.findAllSummonerRecordId();

await Promise.all(
summonerIds.map(async summonerId => {
await this.recoverRedisQueue(summonerId.summonerId);
}),
);
}

public async getRedisClient(): Promise<Redis> {
return this.redisService.getClient();
}

private async compareRecord(
riotApiResponse: PushRiotApi,
redisResponse: string[],
Expand All @@ -80,7 +97,17 @@ export class PushApiService {
);
}

private async changeRecord(
private async recoverRedisQueue(summonerId: string) {
return await this.pushQueue.add(
'recoverList',
{
summonerId,
},
{ delay: 10000, removeOnComplete: true },
);
}

public async changeRecord(
riotApiResponse: PushRiotApi,
redisClient: Redis,
summonerId: string,
Expand All @@ -94,4 +121,8 @@ export class PushApiService {
riotApiResponse.tier,
);
}

public async addRedisSet(redisClient: Redis, summonerId: string) {
await redisClient.sadd('summonerId', summonerId);
}
}
12 changes: 12 additions & 0 deletions libs/entity/src/domain/summonerRecord/SummonerRecordSummonerId.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Expose } from 'class-transformer';

export class SummonerRecordSummonerId {
@Expose({ name: 'summoner_id' })
summonerId: string;

static from(summonerId: string): SummonerRecordSummonerId {
const dto = new SummonerRecordSummonerId();
dto.summonerId = summonerId;
return dto;
}
}
2 changes: 1 addition & 1 deletion nest-cli.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"collection": "@nestjs/schematics",
"sourceRoot": "apps/api/src",
"sourceRoot": "apps/push/src",
"monorepo": true,
"projects": {
"entity": {
Expand Down

0 comments on commit 47c6ac8

Please sign in to comment.