Exception 과 Result

동기 화https://sulin.me/2019/T2ZXZB....
분포 식 시스템 개발 에서 우 리 는 다양한 상태 코드, 오류 정 보 를 최 외층 의 호출 자 에 게 전달 해 야 한다. 이 호출 자 는 보통 http/api 인터페이스, 오류 정보 , 등 이다.
가장 바깥쪽 인터페이스 에 노출 된 데 이 터 는 보통 {code, msg, data} 과 같은 json 형식 이라는 점 에서 논란 이 되 지 않 는 다.
그러나 분포 식 시스템 의 노드 간 RPC 호출, 노드 내부 방법 호출 에서 보통 ServiceException 또는 Result 방식 으로 잘못된 정 보 를 전달 하 는데 이 두 가지 방식 은 어떤 차이 가 있 고 어느 것 이 좋 고 어느 것 이 나 쁜 가?본 고 는 이 문 제 를 연구 하 는 데 중심 을 두 었 다.Result 소개
이것 은 비교적 흔히 볼 수 있 는 잘못된 정보 전달 방식 으로 일부 공장 은 심지어 이 를 기술 규범 으로 설정 하여 각 팀 에 게 이런 방식 을 사용 하도록 강요한다.일반적인 Result 템 플 릿 은 다음 과 같 습 니 다.
@Data
public class Result {
    private int code; //     String 
    private String msg;
    private T data;
}

시스템 개발 에서 의 응용 은 보통 다음 과 같다.
Result userModelResult = userService.query(userId);
if (!userModelResult.isSuccess() || userModelResult.getData != null) {
    return Result.fail(userModelResult); //     
}
UserModel userModel = userModelResult.getData();
if (userModel.getStatus() != UserStatusEnum.NORMAL) {
    return Result.fail("user unavaliable"); //      
}
// ...     UserModel

비교적 복잡 한 분포 식 마이크로 서비스 환경 에서 유사 한 코드 가 매우 많 고 서비스 에 의존 하 는 모든 호출 은 유사 한 오류 논리 와 수반 된다.
이러한 패턴 은 유사 Golang 언어 에서 의 오류 코드 처리 와 유사 하 다. 이것 은 Golang 이 비교적 비난 을 받 는 부분 이다. 즉, 모든 단계 에서 잘못된 판단 을 해 야 한 다 는 것 이다.
더 가혹 한 현실 은 Result 패키지 가 있 음 에 도 불구 하고 백 엔 드 시스템 Exception 이 전달 되 는 것 이다.제 가 접 한 실제 응용 프로그램 에서 이러한 돌파 Result 봉 인 된 이상 한 스 포 일 러 는 사례 가 아 닙 니 다. 제 가 맡 은 시스템 은 더 백 엔 드 의 국내 최 강 거래 시스템 을 호출 할 때 가장 내부 거래 센터 TC 의 업무 이상 을 받 은 적 이 있 습 니 다. 문 제 를 조사 할 때 추적 하 는 팀 만 5 개가 아 닙 니 다.ServiceException 소개
말 그대로 정상 논리 와 이상 논 리 를 분리 하 는 방식 이다.
시스템 개발 에서 대부분의 오 류 는 서 비 스 를 직접 중단 하고 오 류 를 사용자 에 게 직접 피드백 해 야 한다. 그 렇 기 때문에 우 리 는 을 사용 할 때 유사 한 Result 코드 를 자주 써 야 한다.그리고 if(result.isFail()){return…} 을 사용 하면 우 리 는 비슷 한 코드 의 대부분 을 생략 할 수 있다.
보통 ServiceException 은 이렇게 정의 할 수 있다.
@Getter
public class ServiceException extends RuntimeException {
    private final int code;
    private final String msg;
    public ApiException() {
        this(-1, null);
    }
    public ApiException(Code code) {
        this(code, null);
    }
    public ApiException(Code code, String msg) {
        super(msg);
        this.code = code;
        this.msg = msg;
    }
}

시스템 내부 구성 요 소 는 ServiceException, , , 등 이상 상황 을 만 났 을 때 직접 논 리 를 중단 한 다음 에 가장 바깥쪽 ServiceException 또는 Filter 에서 이상 을 포착 하여 그 중의 Aspectcode 을 추출 하여 사용자 에 게 되 돌려 준다.
실제 사용 하 는 코드 논 리 는 다음 과 같 습 니 다.
UserModel userModel = userService.query(userId); // userID   、          
// ...   userModel

이런 방식 은 현저히 우아 하고 간소화 되 어 개발 효율 의 향상 과 후기 유지 에 도움 이 된다.
그러나 이상 중단 을 사용 하면 성능 에 영향 을 미 칠 수 있다 는 소문 이 돌 고 있 으 며, 간단 한 성능 테스트 를 통 해 이상 중단 성능 을 내 놓 는 데 걸 리 는 시간 이 Result 로 돌아 가 는 것 보다 수백 배 나 빠르다 는 소문 도 있다.
성능 테스트
성능 문제 에 대해 저도 간단 한 테스트 를 했 습 니 다. 구체 적 인 테스트 코드 는 다음 과 같 습 니 다.
https://github.com/sisyphsu/b...
여기 서 msg 를 사용 하여 성능 테스트 를 실시 합 니 다. JMH 라 고 하면 정말 부 럽 습 니 다 benchmark 언어 자체 golang 라 이브 러 리 가 부 럽 습 니 다. 정말 편리 합 니 다.
내부 의 업무 논 리 를 테스트 하 는 것 은 매우 간단 하 다. 단지 한 번 test 을 호출 하고 시간 표를 되 돌려 준다.
성능 테스트 에 서 는 각각 System.currentTimeMillis() 반환 값 과 던 지기 long 를 사용 하여 이상 한 성능 테스트 를 던 지고 서로 다른 깊이 의 호출 스 택 테스트 를 추가 합 니 다. 이것 은 Result 이상 을 던 질 때 현재 Exception 의 스 택 을 분석 해 야 하기 때 문 입 니 다. 호출 스 택 이 깊 을 수록 성능 손실 이 크기 때 문 입 니 다.구체 적 인 창고 깊이 추출 값 은 1, 10, 100:
Test.test                  avgt    5  0.027 ± 0.001  us/op
Test.testException         avgt    5  1.060 ± 0.045  us/op
Test.testDeep10Exception   avgt    5  1.826 ± 0.122  us/op
Test.testDeep100Exception  avgt    5  9.802 ± 0.411  us/op

얼핏 보면 이상 스 택 깊이 가 100 인 성능 손실 은 일반 방법 에서 사용 하 는 360 배 이 고 어떤 사람들 은 이런 이유 로 자바 의 이상 중단 성능 손실 이 심각 하 다 는 결론 을 내 렸 다.
성능 의 영향 을 분석 하 다
그러나 주의해 야 할 시간 단 위 는 마이크로 초 일 뿐 밀리초 의 1000 분 의 1, 초의 백만 분 의 1 이다.
만약 에 특정한 마이크로 서비스 단일 CPU 의 스루풋 이 1000 QPS 이 고 그 중 10% 가 불법 요청 이 라 고 가정 하면 이상 하 게 중 단 된 성능 손실 도 만 분 의 1 에 불과 하고 서비스 소모 에 대한 영향 도 0.001 밀리초 에 불과 하 다.
성능 테스트 에서 Java 는 시스템 시간 만 얻 을 뿐 대략 Thread 소모 된다.그 렇 기 때문에 이상 하 게 중 단 된 성능 손실 이 공포 의 '수백 배' 에 이 르 렀 다. 그러나 업무 소모 시간 이 에서 25ns, 25ns 로 바 뀌 면?
성능 병목 재 론
우 리 는 시스템 성능 을 분석 할 때 반드시 그의 수량 급 과 성능 병목 을 파악 하고 성능 최적화 의 곤경 에 빠 진 것 을 기억 해 야 한다.
예 를 들 어 일반적인 서비스 에서 색인 을 이용 한 DB 작업 은 1 ~ 10 밀리초 사이 이 고 분포 식 Cache 를 방문 하 는 데 걸 리 는 시간 은 3 ~ 30 밀리초 사이 이 며 마이크로 서비스 RPC 의 네트워크 성능 손실 은 3 ~ 10 밀리초 사이 이 며 클 라 이언 트 와 서버 간 의 네트워크 소모 시간 은 5 ~ 300 밀리초 사이 이다.이런 상황 에서 0.001 밀리초 의 성능 을 최적화 하 는 위험 은 참깨 를 주 워 수박 을 잃 어 버 리 는 것 과 다름없다.
저 는 TCP 와 유사 한 바 텀 네트워크 프로 토 콜 을 쓴 적 이 있 습 니 다. 그런 고주파 장면 에서 알고리즘 최적화 가 0.1 초 동안 의 성능 최적화 를 가 져 온 것 은 매 초 스루풋 이 몇 할, 심지어 몇 배의 향상 을 의미 하지만 분포 식 호출 의 저주파 장면 에서 이런 성능 은 쓸모 가 없습니다.
또 다른 예 로 몇 년 전에 저 와 동료 들 이 DB 데이터 시트 디자인 을 토론 할 때 주문 상태 에서 어떤 길 이 를 사용 하 는 지 25us 때문에 다 투 는 얼굴 이 빨 개 지고 목이 굵 었 습 니 다. 지금 생각해 보면 주문 상태 에서 최 적 화 된 1 개의 바이트 도 오 랜 세월 동안 1MB 의 디스크 공간 을 절약 하지 못 했 을 뿐 무슨 소 용이 있 습 니까?
RPC 의 이상 중단
Dubbo, HSF 와 같은 원 격 호출 프레임 워 크 를 사용 할 때 이상 중단 을 사용 하여 잘못된 정 보 를 전달 하 는 데 주의해 야 할 것 은 이상 유형 이 통용 되 는, 즉 각 마이크로 서비스 가 인용 하 는 기본 유형 으로 설계 되 어야 한 다 는 것 이다.
모 공장 의 기술 규범 에서 다음 과 같이 말 했다.
1) 이상 반환 방식 을 사용 하여 호출 자가 캡 처 하지 않 으 면 실행 중 오류 가 발생 합 니 다.
2) 스 택 정 보 를 추가 하지 않 고 new 사용자 정의 이상 일 뿐 이해 하 는 error message 를 추가 하면 호출 단 에서 문 제 를 해결 하 는 데 도움 이 되 지 않 습 니 다.스 택 정 보 를 추가 하면 자주 호출 오류 가 발생 하 는 상황 에서 데이터 직렬 화 와 전송 성능 손실 도 문제 다.
나 는 이런 기술 규범 에 대해 상당히 그렇게 생각 하지 않 는 다.
우선 업무 이상 은 원래 호출 자가 최 외층 에 전달 해 야 한다. 예 를 들 어 25ms, int, 등 이상 은 중간의 호출 자가 포착 해도 소 용이 없다.
그 다음은 허튼소리 이다. 이런 저주파 데이터 의 직렬 화 와 내부 망 전송 은 어떤 성능 손실 이 있 을 까?스 택 정 보 를 호출 자 에 게 전달 하 는 것 도 고장 검사 에 도움 이 됩 니 다. 저 는 TC 의 이상 스 택 정 보 를 받 은 적 이 있 습 니 다. 스 택 에 있 는 package 에 따라 3, 4 층 을 돌아 서 바 텀 오류 가 발생 한 곳 을 찾 았 습 니 다. 많은 시간 을 절약 했다 고 할 수 있 습 니 다.
결론.
분포 식 마이크로 서비스 에서 이상 중단 을 사용 하면 업무 코드 를 대폭 간소화 할 수 있 고 성능 에 미 치 는 영향 도 미미 하 다.
보조 , 등 주석 으로 분포 식 개발 을 바람 처럼 빠 르 고 편리 하 게 할 수 있다.복잡 한 서비스 네트워크 에서 업무 이상 도 개발 자 들 이 정확 한 포 지 셔 닝 오 류 를 쉽게 찾 을 수 있 고 사람들 이 체인 을 따라 고장 점 을 추적 하 는 난처 한 상황 을 피 할 수 있다.

좋은 웹페이지 즐겨찾기