>  기사  >  Java  >  예외 및 결과 소개(코드 예)

예외 및 결과 소개(코드 예)

不言
不言앞으로
2019-03-12 15:52:003982검색

이 기사는 예외 및 결과(코드 예제)에 대한 소개를 제공합니다. 이는 특정 참고 가치가 있으므로 도움이 될 수 있습니다.

분산 시스템 개발에서는 다양한 상태 코드와 오류 메시지를 가장 바깥쪽 호출자에게 전달해야 하는 경우가 많습니다. 이 호출자는 일반적으로 로그인 실패 및 매개변수 오류 등의 오류 메시지입니다.

가장 바깥쪽 인터페이스에 의해 노출되는 데이터는 일반적으로 {code, msg, data}와 유사한 json 형식이며 이에 대해서는 논쟁의 여지가 없습니다.

그런데 분산 시스템의 노드 간 RPC 호출과 노드 내 메소드 호출에서는 대개 ServiceException이나 Result를 사용하여 오류 정보를 전송합니다. 이 두 방법의 차이점은 무엇이며 어느 것이 더 좋나요? 이 기사에서는 이 문제를 탐구하기 위해 개발 효율성과 시스템 성능에 중점을 둡니다.

결과소개

오류 정보를 전송하는 비교적 일반적인 방법입니다. 일부 주요 제조업체에서는 이를 기술 사양으로 직접 설정하여 각 팀에서 이 방법을 채택하도록 강요하기도 합니다. 공통 결과 템플릿은 다음과 같습니다.

@Data
public class Result<T> {
    private int code; // 也可以是String等
    private String msg;
    private T data;
}

시스템 개발의 애플리케이션은 일반적으로 다음과 같습니다.

Result<UserModel> 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 패키지를 뚫는 이런 종류의 비정상 투명 전송은 결코 고립된 사례가 아닙니다. 내가 담당하는 시스템이 백엔드에서 가장 강력한 국내 거래 시스템을 호출할 때, 나는 가장 내부 트레이딩 센터로부터 TC를 받았는데, 거래가 비정상적이어서 문제를 해결할 때 5개 이상의 팀이 추적되었습니다.

ServiceException 소개

이름에서 알 수 있듯이 이 메서드는 예외 인터럽트를 사용하여 일반 논리와 예외 논리를 분할합니다.

시스템 개발에서 대부분의 오류는 서비스를 직접 중단하고 오류를 사용자에게 직접 피드백해야 하기 때문에 Result를 사용할 때 if(result.isFail()){return과 같은 내용을 작성해야 하는 경우가 많습니다. ...} 코드는 다음과 같습니다. ServiceException을 사용하면 유사한 코드 대부분을 생략할 수 있습니다.

일반적으로 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 인터럽트 로직을 직접 발생시킨 다음 이를 캡처합니다. 가장 바깥쪽의 Filter 또는 Aspect Exception에 의해 코드와 메시지를 추출하여 사용자에게 반환합니다.

사용된 실제 코드 로직은 다음과 유사합니다:

UserModel userModel = userService.query(userId); // userID不存在、不可用等隐藏在异常中
// ... 使用userModel

이 방법은 확실히 우아하고 능률적이며 개발 효율성을 향상하고 향후 유지 관리에 도움이 됩니다.

그러나 시장에서는 비정상적인 인터럽트를 사용하면 성능에 영향을 미친다는 소문이 많이 있습니다. 어떤 사람들은 간단한 성능 테스트를 통해 비정상적인 인터럽트의 성능이 결과를 반환하는 것보다 수백 배 빠르다는 결론을 내리기도 합니다.

성능 테스트

성능 문제를 염두에 두고 간단한 테스트도 진행했습니다. 구체적인 테스트 코드는

https://github.com/sisyphsu/b...

여기를 참조하세요. 성능 테스트를 위해 JMH를 사용하는데, 벤치마크할 때 golang 언어와 함께 제공되는 테스트 라이브러리가 정말 부럽습니다.

테스트 내부의 비즈니스 로직은 매우 간단합니다. System.currentTimeMillis()를 한 번 호출하고 긴 타임스탬프를 반환하면 됩니다.

예외 발생 성능 테스트에는 Result 반환 값과 Exception이 각각 사용됩니다. 이는 Java가 예외 발생 시 현재 스레드를 분석해야 하기 때문입니다. 호출 스택이 깊어질수록 성능 손실이 커집니다. 구체적인 스택 깊이 값은 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초, 100만분의 1초에 불과합니다.

특정 마이크로서비스의 단일 CPU 처리량이 1000QPS이고 그 중 10%가 불법 요청이라고 가정하면 비정상적인 중단으로 인한 성능 손실은 1만분의 1에 불과하고 서비스 시간에 미치는 영향은 0.001밀리초에 불과합니다.

성능 테스트에서 비즈니스 시간은 시스템 시간을 얻는 데만 소요되며 약 25ns가 소요됩니다. 이로 인해 비정상적인 중단으로 인한 성능 손실은 무서운 "수백 배"에 이릅니다. 그러나 비즈니스 시간이 25ns에서 25us 또는 25ms로 변경되면 어떻게 될까요?

성능 병목 현상에 대해 다시 이야기해 보겠습니다

시스템 성능을 분석할 때 크기 순서와 성능 병목 현상을 이해해야 하며, 성능 최적화의 딜레마에 빠지는 것을 기억해야 합니다.

대략적인 예를 들자면, 일반 서비스에서 인덱스를 이용한 DB 연산은 1~10밀리초, 분산 캐시에 접근하는 데는 3~30밀리초, 마이크로서비스 RPC의 네트워크 성능 손실은 3~10밀리초 정도 걸리며, 클라이언트와 서버 간의 네트워크에는 5~300밀리초 정도가 소요됩니다. 이 경우 성능 위험을 0.001밀리초로 최적화하는 것은 참깨를 따고 수박을 잃는 것과 같습니다.

저는 TCP와 유사한 기본 네트워크 프로토콜을 작성한 적이 있습니다. 고주파수 시나리오에서 알고리즘 최적화는 0.1마이크로초의 성능 최적화를 제공합니다. 이는 초당 처리량이 몇 배 또는 심지어 몇 배 향상된다는 의미입니다. 그러나 분산 호출의 빈도가 낮은 시나리오에서는 이러한 성능 이점이 소용이 없습니다.

또 다른 예를 들자면, 몇 년 전 동료들과 DB 데이터 테이블 설계에 대해 논의할 때 이제 주문 상태에 어떤 길이의 int를 사용해야 하는지에 대한 붉게 논쟁이 있었습니다. 생각해 보세요. 주문 상태를 1바이트로 최적화하면 수년 동안 1MB 미만의 디스크 공간만 절약할 수 있습니다.

RPC의 비정상적인 중단

Dubbo 및 HSF와 같은 원격 호출 프레임워크의 경우 비정상적인 중단을 사용하여 오류 정보를 전송할 때 주의할 점은 예외 유형이 다음과 같아야 한다는 것입니다. 보편적으로 설계되었습니다. 즉, 모든 마이크로서비스에서 참조되는 기본 유형입니다.

특정 공장의 기술 사양에 언급되어 있습니다:

1) 예외 반환 방법을 사용하면 호출자가 이를 포착하지 못하는 경우 런타임 오류가 발생합니다.

2) 스택 정보를 추가하지 않고 새로운 사용자 정의 예외만 추가하고 자체 오류 메시지를 추가하면 호출 측에서 문제를 해결하는 데 큰 도움이 되지 않습니다. 스택 정보를 추가하면 잦은 호출 오류의 경우 데이터 직렬화 및 전송 성능 손실도 문제가 된다.

이 기술 사양은 상당히 불만족스럽습니다.

먼저 비즈니스 예외는 호출자가 가장 바깥쪽 레이어에 투명하게 전송해야 합니다. 데이터 존재하지 않음, 로그인 실패, 사용자 잠금 등의 예외는 발견되면 소용이 없는 경우가 많습니다. 중간에 호출자.

두 번째는 성능 손실입니다. 이 저주파 데이터 직렬화 및 인트라넷 전송은 어떤 성능 손실을 갖게 됩니까? 스택 정보를 호출자에게 전송하는 것도 문제 해결에 도움이 됩니다. 스택에 있는 패키지에 따르면 하위 수준 오류를 찾기 위해 3번째와 4번째 레이어를 직접 우회했다고 할 수 있습니다. 시간이 많이 절약되었습니다.

결론

분산 마이크로서비스에서 비정상적인 중단을 사용하면 비즈니스 코드를 크게 간소화하고 성능에 미치는 영향을 최소화할 수 있습니다.

@NotNull, @Nullable 및 기타 주석의 도움으로 분산 개발이 바람처럼 빠르고 편리해질 수 있습니다. 복잡한 서비스 네트워크에서 비즈니스 예외는 개발자가 오류를 정확하게 찾아내고 호출 체인을 따라 계층별로 오류 지점을 추적하는 당황스러운 상황을 피할 수 있도록 도와줍니다.

위 내용은 예외 및 결과 소개(코드 예)의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 segmentfault.com에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제