GDmarket : ๊ทผ๋๋ง์ผ - ๊ทผ๊ฑฐ๋ฆฌ ๋์ฌ ๋ง์ผ (Premium)
- ๊ทผ๋๋ง์ผ
- Saga
- CQRS
- Correlation
- Req/Res
- Gateway
- Deploy / Pipeline
- Circuit Breaker
- Autoscale (HPA)
- Zero-downtime deploy (Readiness Probe)
- Config Map/ Persistence Volume
- Polyglot
- Self-healing (Liveness Probe)
- ๋ฌผ๊ฑด๊ด๋ฆฌ์๋ ๋ฌผ๊ฑด์ ๋ฑ๋กํ ์ ์๋ค
- ๋ฌผ๊ฑด๊ด๋ฆฌ์๋ ๋ฌผ๊ฑด์ ์ญ์ ํ ์ ์๋ค.
- ๋์ฌ์๋ ๋ฌผ๊ฑด์ ์ ํํ์ฌ ์์ฝํ๋ค.
- ๋์ฌ์๋ ์์ฝ์ ์ทจ์ํ ์ ์๋ค.
- ์์ฝ์ด ์๋ฃ๋๋ฉด ํด๋น ๋ฌผ๊ฑด์ ๋์ฌ๋ถ๊ฐ ์ํ๋ก ๋ณ๊ฒฝ๋๋ค.
- ๋์ฌ์๊ฐ ๊ฒฐ์ ํ๋ค.
- ๋์ฌ์๋ ๊ฒฐ์ ๋ฅผ ์ทจ์ํ ์ ์๋ค.
- ๋ฌผ๊ฑด๊ด๋ฆฌ์๋ ๋ฌผ๊ฑด์ ๋์ฌํด์ค๋ค.
- ๋์ฌ์๊ฐ ๋์ฌ์์ฒญ์ ์ทจ์ํ ์ ์๋ค.
- ๋ฌผ๊ฑด์ด ๋ฐ๋ฉ๋๋ฉด ๋ฌผ๊ฑด์ ๋์ฌ๊ฐ๋ฅ ์ํ๋ก ๋ณ๊ฒฝ๋๋ค.
- ๋ฌผ๊ฑด๊ด๋ฆฌ์๋ ๋ฌผ๊ฑด ํตํฉ์ํ๋ฅผ ์ค๊ฐ์ค๊ฐ ์กฐํํ ์ ์๋ค.
- (Premium) ๋ฌผ๊ฑด์ด ๋ฑ๋ก๋๋ฉด ๋ฑ๋ก ์๋์ด ๋ฐ์ํ๋ค.
- (Premium) ๋ฌผ๊ฑด์ด ์ญ์ ๋๋ฉด ์ญ์ ์๋์ด ๋ฐ์ํ๋ค.
- ํธ๋์ญ์
- ๊ฒฐ์ ์น์ธ์ด ๋์ง ์์ ๊ฑด์ ๊ฒฐ์ ์์ฒญ์ด ์๋ฃ๋์ง ์์์ผํ๋ค. Sync ํธ์ถ
- ๋ฑ๋ก ์๋์ด ๋ฐ์๋์ง ์์ ๊ฑด์ ๋ฌผ๊ฑด๋ฑ๋ก์ด ์๋ฃ๋์ง ์์์ผ ํ๋ค. Sync ํธ์ถ (Premium)
- ์ฅ์ ๊ฒฉ๋ฆฌ
- ๋ฌผ๊ฑด๊ด๋ฆฌ์์คํ ์ด ์ํ๋์ง ์๋๋ผ๋ ๋์ฌ ์์ฒญ์ 365์ผ 24์๊ฐ ๋ฐ์ ์ ์์ด์ผ ํ๋ค. > Async
- ๊ฒฐ์ ์์คํ ์ด ๊ณผ์ค๋๋ฉด ๊ฒฐ์ ์์ฒญ์ ์ ์๋์ ๋ฐ์ง ์๊ณ ๊ฒฐ์ ๋ฅผ ์ ์ ํ์ ํ๋๋ก ์ ๋ํ๋ค. > Circuit breaker
- (Premium) ์๋์์คํ ์ด ์ํ๋์ง ์๋๋ผ๋ ๋ฌผ๊ฑด ์ญ์ ์์ฒญ์ 365์ผ 24์๊ฐ ๋ฐ์ ์ ์๋ค > Async
- (Premium) ์๋์์คํ ์ด ๊ณผ์ค๋๋ฉด ๋ฌผ๊ฑด๋ฑ๋ก์ ์ ์๋์ ๋ฐ์ง ์๊ณ ์๋์ ์ ์ ํ์ ํ๋๋ก ์ ๋ํ๋ค. > Circuit breaker
- ์ฑ๋ฅ
- ๋ฌผ๊ฑด๊ด๋ฆฌ์๊ฐ ๋ฑ๋กํ ๋ฌผ๊ฑด์ ํตํฉ์ํ๋ฅผ ๋ณ๋๋ก ํ์ธํ ์ ์์ด์ผ ํ๋ค. > CQRS
- Pub/Sub์ ๊ตฌํํ๋ค.
- (Pub) ํธ์ถ ์๋น์ค ๋ฐ Event : item / item์ด ์ญ์ ๋จ
// item > Item.java
@PreRemove
public void onPreRemove() {
ItemDeleted itemDeleted = new ItemDeleted();
itemDeleted.setItemNo(this.getItemNo());
itemDeleted.setAlarmStatus("DeleteAlerted");
ObjectMapper objectMapper = new ObjectMapper();
String json = null;
try {
json = objectMapper.writeValueAsString(itemDeleted);
} catch (JsonProcessingException e) {
throw new RuntimeException("JSON format exception", e);
}
KafkaProcessor processor = ItemApplication.applicationContext.getBean(KafkaProcessor.class);
MessageChannel outputChannel = processor.outboundTopic();
outputChannel.send(org.springframework.integration.support.MessageBuilder
.withPayload(json)
.setHeader(MessageHeaders.CONTENT_TYPE, MimeTypeUtils.APPLICATION_JSON)
.build());
System.out.println("@@@@@@@ itemDeleted to Json @@@@@@@");
System.out.println(itemDeleted.toJson());
}- (Sub) ํผํธ์ถ ์๋น์ค ๋ฐ Policy : alarm / alarm ์ํ ๋ณ๊ฒฝ (Alerted -> DeleteAlerted)
// alarm > PolicyHandler.java
@StreamListener(KafkaProcessor.INPUT)
public void wheneverItemDeleted_(@Payload ItemDeleted itemDeleted){
if(itemDeleted.isMe()){
System.out.println("##### listener : " + itemDeleted.toJson());
if("DeleteAlerted".equals(itemDeleted.getAlarmStatus())){
Alarm alarm = (Alarm) alarmManagementRepository.findByItemNo(itemDeleted.getItemNo()).get(0);
alarm.setAlarmStatus("DeleteAlerted");
alarmManagementRepository.save(alarm);
}
}
}- command์ query์ ์ญํ ์ ๋ถ๋ฆฌํ๋ค. (view ๊ตฌํ)
- item ์ด ๋ฑ๋ก๋ ๋ ์๋์ํ(AlarmStatus)๋ฅผ View๋ฅผ ํตํด ํ์ธํ ์ ์๋๋ก ๊ตฌํ
- item ์ฝ๋ ๊ตฌํ
// item > Item.java
@PostPersist
public void onPostPersist(){
ItemRegistered itemRegistered = new ItemRegistered();
itemRegistered.setItemNo(this.getItemNo());
itemRegistered.setItemName(this.getItemName());
itemRegistered.setItemPrice(this.getItemPrice());
itemRegistered.setItemStatus("Rentable");
itemRegistered.setRentalStatus("NotRenting");
itemRegistered.setAlarmStatus("Alerted");
// view๋ฅผ ์ํด Kafka Send
ObjectMapper objectMapper = new ObjectMapper();
String json = null;
try {
json = objectMapper.writeValueAsString(itemRegistered);
} catch (JsonProcessingException e) {
throw new RuntimeException("JSON format exception", e);
}
KafkaProcessor processor = ItemApplication.applicationContext.getBean(KafkaProcessor.class);
MessageChannel outputChannel = processor.outboundTopic();
outputChannel.send(org.springframework.integration.support.MessageBuilder
.withPayload(json)
.setHeader(MessageHeaders.CONTENT_TYPE, MimeTypeUtils.APPLICATION_JSON)
.build());
System.out.println("@@@@@@@ ItemRegistered to Json @@@@@@@");
System.out.println(itemRegistered.toJson());
}- view ์ฝ๋ ๊ตฌํ
// item > ItemInfoViewHandler.java
@StreamListener(KafkaProcessor.INPUT)
public void whenItemRegistered_then_CREATE_1 (@Payload ItemRegistered itemRegistered) {
try {
if (itemRegistered.isMe()) {
// view ๊ฐ์ฒด ์์ฑ
ItemInfo itemInfo= new ItemInfo();
// view ๊ฐ์ฒด์ ์ด๋ฒคํธ์ Value ๋ฅผ set ํจ
itemInfo.setItemNo(itemRegistered.getItemNo());
itemInfo.setItemName(itemRegistered.getItemName());
itemInfo.setItemStatus(itemRegistered.getItemStatus());
itemInfo.setItemPrice(itemRegistered.getItemPrice());
itemInfo.setAlarmStatus(itemRegistered.getAlarmStatus());
// view ๋ ํ์ง ํ ๋ฆฌ์ save
itemInfoRepository.save(itemInfo);
}
}catch (Exception e){
e.printStackTrace();
}
}- ๊ฐ ๋ง์ดํฌ๋ก ์๋น์ค๋ ์ํธ ๊ด๋ จ ํค๋ฅผ ๊ฐ๋๋ค.
- CQRS ๊ตฌํ์ ์ํด, Alarm๊ณผ ItemInfo๋ ์ํธ ๊ด๋ จ ํค 'itemNo', 'alarmStatus'๋ฅผ ๊ฐ๋๋ค.
- Alarm.java
- ItemInfo.java
- Sync ํธ์ถ์ ๊ตฌํํ๋ค.
- (Req) ํธ์ถ ์๋น์ค ๊ตฌํ
// item.java > onPostPersist()
// alarm REQ/RES
System.out.println("@@@ Alarm @@@");
System.out.println("@@@ ItemNo : " + getItemNo());
gdmarketpremium.external.Alarm alarm = new gdmarketpremium.external.Alarm();
alarm.setAlarmStatus("Alerted");
alarm.setAlarmNo(getItemNo());
alarm.setItemNo(getItemNo());- (Res) ํผํธ์ถ ์๋น์ค ๊ตฌํ
// AlarmService.java
@FeignClient(name="alarm", url="${api.alarm.url}")
public interface AlarmService {
@RequestMapping(method= RequestMethod.POST, path="/alarms")
public void alert(@RequestBody Alarm alarm);
}- ๊ฐ ๋ง์ดํฌ๋ก์๋น์ค๋ gateway๋ฅผ ํตํด์ ํธ์ถํ ์ ์๋ค
- gateway ์๋น์ค > application.yml ํ์ผ์ ๊ตฌํํ๋ค.
- local ์ธํ
- docker ์ธํ
- item ๋ฑ๋กํจ
http POST item:8080/items/ itemName=Camera itemPrice=100 itemStatus=Rentable rentalStatus=NotRenting
- Sync ํธ์ถ๋ก alarm ์์ฑ๋จ (Req/Res)
http alarm:8080/alarms
- CQRS : alarmStatus๋ฅผ ํ์ธํ ์ ์์
http item:8080/itemInfoes
- item ์ญ์ ํจ
http DELETE item:8080/items/1
- Async ํธ์ถ๋ก alarm ์ํ ๋ณ๊ฒฝ๋จ (Alerted -> DeleteAlerted) (Pub/Sub)
http alarm:8080/alarms
- gateway๋ก reservation ์๋น์ค GET ํธ์ถ
http gateway:8080/reservations
- (1-1) ์ปจํ ์ด๋๋ผ์ด์ง : ๋ํ๋ก์ด ์์ฑ ํ์ธ (gateway ์๋น์ค๋ api ์ฌ์ฉ)
kubectl create deploy gateway --image=gdpremiumacr.azurecr.io/gateway:latest
- (1-2) ์ปจํ ์ด๋๋ผ์ด์ง : ๋ํ๋ก์ด ์์ฑ ํ์ธ (๊ทธ ์ธ ์๋น์ค๋ yml ์ฌ์ฉ)
kubectl apply -f kubernetes/deployment.yml
- (2) ์ปจํ ์ด๋๋ผ์ด์ง: ์๋น์ค ์์ฑ ํ์ธ (gateway ํฌํจ๋ชจ๋ ์๋น์ค ๋์ผ)
kubectl expose deploy gateway --type="ClusterIP" --port=8080
- ๋ชจ๋ ์๋น์ค๊ฐ ์ Running ๋จ์ ํ์ธ
kubectl get all
- CirCuit Breaker Framework : Spring FeignClient + Hystrix ์ฌ์ฉ
- (ํธ์ถ) item ์๋น์ค > application.yml : timeoutInMilliseconds 610 ์ผ๋ก ์ค์
hystrix:
command:
default:
execution.isolation.thread.timeoutInMilliseconds: 610
- (ํผํธ์ถ) alarm ์๋น์ค > Alarm.java : sleep 440 + ๋๋ค 220 ์ผ๋ก ์ค์
Thread.currentThread().sleep((long) (400 + Math.random() * 220));
- siege ํด์ ํตํ ์ํท ๋ธ๋ ์ด์ปค ๋์ ํ์ธ
- ๋์์ฌ์ฉ์ 10๋ช , 30์ด ๋์ siege ๋ถํ ํ ์คํธ ์ค์
siege -c10 -t30S -r10 -v --content-type "application/json" 'http://item:8080/items POST {"itemName":"Camera"}'
- item ์์คํ ์ ๋ํ replica ๋ฅผ ๋์ ์ผ๋ก ๋๋ ค์ฃผ๋๋ก HPA ๋ฅผ ์ค์ ํ๋ค.
- item ์์คํ ์ resource ์ ์ ํ์ ๊ฑธ์ด๋๋ค.
// item > deployment.yml
resources:
limits:
cpu: 500m
requests:
cpu: 200m
- HPA ์ค์ ์ CPU ์ฌ์ฉ๋์ด 15ํ๋ก๋ฅผ ๋์ด์๋ฉด replica ๋ฅผ 10๊ฐ๊น์ง ๋๋ ค์ค๋ค.
- CirCuit Breaker์ ๋์ผํ ๋ฐฉ๋ฒ์ผ๋ก ์ํฌ๋ก๋๋ฅผ 50์ด ๊ฑธ์ด์ค๋ค.
kubectl autoscale deploy reservation --min=1 --max=10 --cpu-percent=15
kubectl exec -it pod/siege-5459b87f86-wpwkt -c siege -- /bin/bash
siege -c250 -t50S -r1000 -v --content-type "application/json" 'http://item:8080/items POST {"itemName":"Camera"}'
- ์คํ ์ค์ผ์ผ ๋ชจ๋ํฐ๋ง์ ํ๋ค.
kubectl get deploy reservation -w
- alarm ์๋น์ค๋ฅผ REQ๋ก ํธ์ถํ๋ item ์๋น์ค์ config map์ ๊ตฌํํ๋ค.
- item > application.yml : local
- item > application.yml : docker
- item > deployment.yml : env ์ธํ
- item > external > AlarmService.java : env ์ฌ์ฉ
- config map ์์ฑ ๋ฐ ํ์ธ
# ์์ฑ
kubectl create configmap newurl --from-literal=url=http://alarm:8080
# ํ์ธ
kubectl get configmap newurl -o yaml





