Spring Boot与Kubernetes云原生微服务实践 [持续更新中]

本文是 极客时间 杨波 《Spring Boot 与 Kubernetes 云原生微服务实践》 的学习笔记。如果对原课程有兴趣,欢迎扫码订阅(见文章末尾)

文章目录

    • 强类型客户端
    • dto
    • 数据校验
    • 全局错误处理
    • DTO 和 DMO
    • 框架层分环境配置
    • 主流网关
    • 安全架构
      • 单体
      • 微服务
      • 源码剖析
    • feign
    • 调用链和监控
    • 结构化日志
    • 集中异常监控 和 Sentry
    • SwitchHosts
    • Skywalking

强类型客户端

  • AccountClient in account-api
@FeignClient(name = "account-service", path = "/v1/account", url = "${staffjoy.account-service-endpoint}")
// TODO Client side validation can be enabled as needed
// @Validated
public interface AccountClient {
    @PostMapping(path = "/create")
    GenericAccountResponse createAccount(@RequestHeader(AuthConstant.AUTHORIZATION_HEADER) String authz, @RequestBody @Valid CreateAccountRequest request);
}
  • AccountController in account-svc
@RestController
@RequestMapping("/v1/account")
@Validated
public class AccountController {
    @PostMapping(path = "/create")
    @Authorize(value = {
                    AuthConstant.AUTHORIZATION_SUPPORT_USER,
                    AuthConstant.AUTHORIZATION_WWW_SERVICE,
                    AuthConstant.AUTHORIZATION_COMPANY_SERVICE
    })
    public GenericAccountResponse createAccount(@RequestBody @Valid CreateAccountRequest request) {
        AccountDto accountDto = accountService.create(request.getName(), request.getEmail(), request.getPhoneNumber());
        GenericAccountResponse genericAccountResponse = new GenericAccountResponse(accountDto);
        return genericAccountResponse;
    }
}
  • WhoAmIService in whoami

dto

  • @Data equals to @Getter @Setter @RequiredArgsConstructor @ToString @EqualsAndHashCode
  • @Builder: builder pattern
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class CreateAccountRequest {
    private String name;
    @Email(message = "Invalid email")
    private String email;
    @PhoneNumber
    private String phoneNumber;

    @AssertTrue(message = "Empty request")
    private boolean isValidRequest() {
        return StringUtils.hasText(name) || StringUtils.hasText(email) || StringUtils.hasText(phoneNumber);
    }
}

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class GenericAccountResponse extends BaseResponse {
    private AccountDto account;
}

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class ListAccountResponse extends BaseResponse {
    private AccountList accountList;
}

数据校验

  • PhoneNumber
@Documented
@Constraint(validatedBy = PhoneNumberValidator.class)
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface PhoneNumber {
    String message() default "Invalid phone number";
    Class[] groups() default {};
    Class[] payload() default {};
}
  • PhoneNumberValidator
public class PhoneNumberValidator implements ConstraintValidator {

    @Override
    public boolean isValid(String phoneField, ConstraintValidatorContext context) {
        if (phoneField == null) return true; // can be null
        return phoneField != null && phoneField.matches("[0-9]+")
                && (phoneField.length() > 8) && (phoneField.length() < 14);
    }
}

全局错误处理

  • Spring Boot 错误异常梳理

  • Spring mvc 错误异常处理

DTO 和 DMO

View Object
Data Transfer Object
Data Model Object
Domain Object
Persistent Object
互转 ModelMapper

public class AccountService {
    private AccountDto convertToDto(Account account) {
        return modelMapper.map(account, AccountDto.class);
    }
    private Account convertToModel(AccountDto accountDto) {
        return modelMapper.map(accountDto, Account.class);
    }
}

框架层分环境配置

EnvConfig

主流网关

Spring Boot与Kubernetes云原生微服务实践 [持续更新中]_第1张图片

安全架构

单体

  • cookie+session:只会在登录过的服务器才有session,会出现偶发BUG
  • Sticky Session:需要负载均衡器支持,统一session请求,打到一个server(nginx支持)
  • session同步复制机制
  • 无状态回话:数据存于浏览器端,但浏览器session有大小限制
  • 集中状态会话:服务器端session统一存于一处(如 redis),高扩展,高可用

微服务

Spring Boot与Kubernetes云原生微服务实践 [持续更新中]_第2张图片

Spring Boot与Kubernetes云原生微服务实践 [持续更新中]_第3张图片
Spring Boot与Kubernetes云原生微服务实践 [持续更新中]_第4张图片

Json Web Token
JWT: Header+Payload+Signature
base64(Header)+"."+base64(Payload)+"."+base64(Signature)
不保证传输的安全性,但保证其不可篡改性

源码剖析

  • Sessions in xyz.staffjoy.common.auth
    包含 loginUser, logout, getToken, 把jwt存于cookie
  • Sign in xyz.staffjoy.common.crypto
    生成jwt/token的相关代码
  • AuthRequestInterceptor in xyz.staffjoy.faraday.core.interceptor
    网关拦截器,获得sessionToken。如果需要登录,重定向到登录界面。登录用户的 userId会写入Header中
  • FeignRequestHeaderInterceptor in xyz.staffjoy.common.auth
    微服务拦截器,从header中获取userId
  • AuthContext in xyz.staffjoy.common.auth
    帮助函数,从 RequestContextHolder.getRequestAttributes() 中获得 Request Header 的相关信息

调用网关必须进行Auth,内部服务之间,只需要带header,比较服务请求是谁就可以了(user_id)

feign

feign源码剖析
Feign使用教程

调用链和监控

Spring Boot与Kubernetes云原生微服务实践 [持续更新中]_第5张图片
作者推荐CAT

结构化日志

  • StaffjoyConfig
    @PostConstruct
    public void init() {
        // init structured logging
        StructLog4J.setFormatter(JsonFormatter.getInstance());

        // global log fields setting, 所有日志都会包含env, service 信息
        StructLog4J.setMandatoryContextSupplier(() -> new Object[]{
                "env", activeProfile,
                "service", appName});
    }
  • AccountService
public AccountDto create(String name, String email, String phoneNumber) {
        // ...

        LogEntry auditLog = LogEntry.builder()
                .authorization(AuthContext.getAuthz())
                .currentUserId(AuthContext.getUserId())
                .targetType("account")
                .targetId(account.getId())
                .updatedContents(account.toString())
                .build();

        logger.info("created account", auditLog);
		
		// ...
    }

集中异常监控 和 Sentry

  • StaffjoyConfig
    @Bean
    public SentryClient sentryClient() {

        SentryClient sentryClient = Sentry.init(staffjoyProps.getSentryDsn());
        sentryClient.setEnvironment(activeProfile);
        sentryClient.setRelease(staffjoyProps.getDeployEnv());
        sentryClient.addTag("service", appName);

        return sentryClient;
    }
    
    @PreDestroy
    public void destroy() {
        sentryClient().closeConnection();
    }
  • StaffjoyProps
@ConfigurationProperties(prefix="staffjoy.common")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class StaffjoyProps {
    @NotBlank
    private String sentryDsn;
    @NotBlank
    // DeployEnvVar is set by Kubernetes during a new deployment so we can identify the code version
    private String deployEnv;
}
  • ServiceHelper
    public void handleError(ILogger log, String errMsg) {
        log.error(errMsg);
        if (!envConfig.isDebug()) {
            sentryClient.sendMessage(errMsg);
        }
    }

    public void handleException(ILogger log, Exception ex, String errMsg) {
        log.error(errMsg, ex);
        if (!envConfig.isDebug()) {
            sentryClient.sendException(ex);
        }
    }

SwitchHosts

Skywalking

你可能感兴趣的:(笔记,java,源码剖析)