假设,有一家科技公司,处理复杂的数学计算,进行尖端的生物科学研究,并为用户提供服务:

  • 黄金分割数列计算

  • 蜜蜂繁殖规律(计算每只雄峰/雌蜂的祖先数量)

在为用户提供服务之前,首先确保通过认证避免公司的计算资源被用户滥用;同时为用户提供REST访问方式。

业务场景

Worker

黄金分割运算

interface FibonacciService{
  long term(int n);
}

@Service
class FibonacciServiceImpl implements FibonacciService{
  @Override
  public long term(int n){
    if(n==0){
      return 0;
    }else if(n==1){
      return 1;
    }

    return term(n-1)+term(n-2);
  }
}

将黄金分割运算提供服务,首先引入ServiceComb依赖

<dependency>
  <groupId>org.apache.servicecomb</groupId>
  <artifactId>spring-boot-starter-provider</artifactId>
</dependency>

提供Restful和RPC端点

@RestSchema(schemaId="fibonacciRestEndpoint")
@RequestMapping("/fibonacci")
@Controller
public class FibonacciRestEndpoint implements FibonacciEndpoint{
  private final FibonacciService fibonacciService;

  @Autowired
  FibonacciRestEndpoint(FibonacciService fibonacciService){
    this.fibonacciService=fibonacciService;
  }

  @Override
  @RequestMapping(value="/term",method=RequestMethod.GET)
  @ResponseBody
  public long term(int n){
    return fibonacciService.term(n);
  }
}
@RpcSchema(schemaId="fibonacciRpcEndpoint")
public class FibonacciRpcEndpoint implements FibonacciEndpoint{
  private final FibonacciService fibonacciService;

  @Autowired
  public FibonacciRpcEndpoint(FibonacciService fibonacciService){
    this.fibonacciService=fibonacciService;
  }

  @Override
  public long term(int n){
    return fibonacciService.term(n);
  }
}

@RestSchema和@RpcSchema注释两个端点之后,ServiceComb会自动生成对应的服务端点契约。

microservice.yaml 配置端点端口,并注册到Service Center

APPLICATION_ID: company
service_description:
  name: worker
  version: 0.0.1
cse:
  service:
    registry:
      address: http://sc.servicecomb.io:30100
  highway:
    address: 0.0.0.0:7070
  rest:
    address: 0.0.0.0:8080

worker应用启动入口

@SpringBootApplication
@EnableServiceComb
public class WorkerApplication{
  public static void main(String[] args){
    SpringApplication.run(WorkerApplication.class,args);
  }
}

Bulletin Board

Service Center提供契约和服务注册、发现功能,并且检验服务提供方和消费方的契约是否匹配。

Beekeeper

密封的祖先数量符合黄金分割数列模型,定义黄金数列运算接口

public interface FibonacciCalculator{
  long term(int n);
}

密封繁殖规律研究服务

interface BeekeeperService{
  long ancestorsOfDroneAt(int generation);
  long ancestorsOfQueenAt(int generation);
}

class BeekeeperServiceImpl implements BeekeeperService {
  private final FibonacciCalculator fibonacciCalculator;

  BeekeeperServiceImpl(FibonacciCalculator fibonacciCalculator){
    this.fibonacciCalculator=fibonacciCalculator;
  }

  @Override
  public long ancestorsOfDroneAt(int generation){
    if(generation<=0){
      return 0;
    }
    return fibonacciCalculator.term(generation+1);
  }

  @Override
  public long ancestorsOfQueenAt(int generation){
    if(generation<=0){
      return 0;
    }
    return fibonacciCalculator.term(generation+2);
  }
}

从Service Center获取worker服务

@Configuration
class BeekeeperConfig {
  @RpcReference(microserviceName="worker",schemaId="fibonacciRpcEndpoint")
  private FibonacciCalculator fibonacciCalculator;

  @Bean
  BeekeeperService beekeeperService(){
    return new BeekeeperServiceImpl(fibonacciCalculator);
  }
}

beekeeper的服务端点

@RestSchema(schemaId="beekeeperRestEndpoint")
@RequestMapping("/rest")
@Controller
public class BeekeeperController{
  private static final Logger logger=LoggerFactory.getLogger(BeekeeperController.class);

  private final BeekeeperService beekeeperService;
  @Autowired
  BeekeeperController(BeekeeperService beekeeperService){
   this.beekeeperService=beekeeperService;
  }

  @RequestMapping(value="/drone/ancestors/{generation}",method=GET,produces=APPLICATION_JSON_UTF8_VALUE)
  @ResponseBody
  public Ancestor ancestorsOfDrone(@PathVariable int generation){
    logger.info("Received request to find the number of ancestors of drone at generation {}".generation);
    return new Ancestor(beekeeperService.ancestorsOfDroneAt(generation));
  }

  @RequestMapping(value="/queen/ancestors/{generation}",method=GET,produces=APPLICATION_JSON_UTF8_VALUE)
  @ResponseBody
  public Ancestor ancestorsOfQueen(@PathVariable int generation){
    logger.info("Received request to find the number of ancestors of queen at generation {}",generation);
    return new Ancestor(beekeeperService.ancestorsOfQueenAt(generation));
  }
}

class Ancestor{
  private long ancestors;
  Ancestor(){

  }

  Ancestor(long ancestors){
    this.ancestors=ancestors;
  }

  public long getAncestors(){
    return ancestors;
  }
}

配置microservice.yaml

APPLICATION_ID: company
service_description:
  name: beekeeper
  version: 0.0.1
cse:
  service:
    registry:
      address: http://sc.servicecomb.io:30100
  rest:
    address: 0.0.0.0:8090
  handler:
    chain:
      Consumer:
        default: bizkeeper-consumer.loadbalance
  references:
    worker:
      version-rule: 0.0.1

定义beekeeper的应用入口

@SpringBootApplication
@EnableServiceComb
public class BeekeeperApplication{
  public static void main(Stirng[] args){
    SpringApplication.run(BeekeeperApplication.class,args);
  }
}

Doorman

屏蔽非法用户,提供安全保障。

认证采用JSON Web Token(JWT)机制。authenticate方法根据用户名和密码确认用户存在,并返回对应的JWT token。用户登录后的每次请求都需要带上JWT token;而validate方法将验证token是否有效。

public interface AuthenticationService{
  String authenticate(String username,String password);

  String validate(String token);
}

认证服务端点

@RestSchema(schemaId="authenticationRestEndpoint")
@Controller
@RequestMapping("/rest")
public class AuthenticationController{
  private static final Logger logger=LoggerFactory.getLogger(AuthenticationController.class);
  static final String USERNAME="username";
  static final String PASSWORD="password";
  static final String TOKEN="token";

  private final AuthenticationService authenticationService;

  @Autowired
  AuthenticateionController(AuthenticationService authenticationService){
    this.authenticationService=authenticationService;
  }

  @RequestMapping(value="/login",method=POST,produces=TEXT_PLAIN_VALUE)
  public ResponseEntity<String> login(@RequestParam(USERNAME) String username,@RequestParam(PASSWORD) String password){
    logger.info("Received login request from user {}",username);
    String token=authenticationService.authenticate(username,password);
    HttpHeaders headers=new HttpHeaders();
    headers.add(AUTHORIZATION,TOKEN_PREFIX+token);

    logger.info("Authenticated user {} successfully",username);
    return new ResponseEntity<>("Welcome,"+username,headers,OK);
  }

  @RequestMapping(value="/validate",method=POST,consumes=APPLICATION_JSON_UTF8_VALUE,produces=TEXT_PLAIN_VALUE)
  @ResponseBody
  public String validate(@RequestBody Token token){
    logger.info("Received validation request of token {}",token);
    return authenticationService.validate(token.getToken());
  }
}

class Token{
  private String token;
  Token(){

  }

  public String getToken(){
    return token;
  }

  @Override
  public String toString(){
    return "Token{token='"+token+"'}";
  }
}

microservice.yaml 配置

APPLICATION_ID: company
service_description:
  name: doorman
  version: 0.0.1
cse:
  service:
    registry:
      address: http://sc.servicecomb.io: 30100
    rest:
      address: 0.0.0.0:9090

服务应用入口

@SpringBootApplication
@EnableServiceComb
public class DoormanApplication{
  public static void main(String[] args){
    SpringApplication.run(DoormanApplication.class,args);
  }
}

Manager

首先引入依赖

<dependency>
  <groupId>org.apache.servicecomb</groupId>
  <artifactId>spring-boot-starter-discovery</artifactId>
</dependency>

用户认证服务

当用户发送非登录请求时,需要验证用户是否合法,ServiceComb会自动查询对应服务并发送请求到地址中的服务端端点

@Service
public class AuthenticationService {

  private static final Logger logger=LoggerFactory.getLogger(AuthenticationService.class);
  private static final String DOORMAN_ADDRESS="cse://doorman";

  private final RestTemplate restTemplate;
  AuthenticationService(){
    this.restTemplate=RestTemplateBuilder.create();
    this.restTemplate.setErrorHandler(new ResponseErrorHandler(){
      @Override
      public boolean hasError(ClientHttpResponse clientHttpResponse) throws IOException{
        return false;
      }

      @Override
      public void handleError(ClientHttpResponse clientHttpResponse) throws IOException{

      }
    });
  }

  @HystrixCommand(fallbackMethod="timeout")
  public ResponseEntity<String> validate(String token){
    logger.info("validating token {}",token);
    ResponseEntity<String> responseEntity=restTemplate.postForEntity(
      DOORMAN_ADDRESS+"/rest/validate",
      validationRequest(token),
      String,class
    );

    if(!responseEntity.getStatusCode().is2xxSuccessful()){
      logger.warn("No such user found with token {}",token);
    }
    logger.info("validated request of token {} to be user {}",token,responseEntity,getBody());
    return responseEntity;
  }

  private ResponseEntity<String> timeout(String token){
    logger.warn("Request to validate token {} timeout",token);
    return new ResponseEntity<>(REQUEST_TIMEOUT);
  }

  private HttpEntity<Token> validationRequest(String token){
    HttpHeaders headers=new HttpHeaders();
    headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
    return new HttpEntity<>(new Token(token),headers);
  }
}

请求过滤

使用ZuulFilter实现过滤用户请求,调用authenticationService.validate(token)认证用户

@Component
class AuthenticationAwareFilter extends ZuulFilter{
  private static final Logger logger=LoggerFactory.getLogger(AuthenticationAwareFilter.class);
  private static final String LOGIN_PATH="/login";

  private final AuthenticationService authenticationService;
  private final PathExtractor pathExtractor;

  @Autowired
  AuthenticationAwareFilter(AuthenticationService authenticationService,PathExtractor pathExtractor){
    this.authenticationService=authenticationService;
    this.pathExtractor=pathExtractor;
  }

  @Overrid
  public String filterType(){
    return "pre";
  }

  @Override
  public int filterOrder(){
    return 1;
  }

  @Override
  public Object run(){
    filter();
    return null;
  }

  private void filter(){
    RequestContext context=RequestContext.getCurrentContext();
    if(doesNotContainToken(context)){
      logger.warn("No token found in request header");
      rejectRequest(context);
    }else{
      String token=token(context);
      ResponseEntity<String> responseEntity=authenticationService.validate(token);
      if(!responseEntity.getStatusCode().is2xxSuccessful()){
        logger.warn("Unauthorize token {} and request rejected",token);
        rejectRequest(context);
      }else{
        logger.info("Token {} validated",token);
      }
    }
  }

  private void rejectRequest(RequestContext context){
    context.setResponseStatusCode(SC_FORBIDDEN);
    context.setSendZuulResponse(false);
  }

  private boolean doesNotContainToken(RequestContext context){
    return authorizationHeader(context)==null || !authorizationHeader(context).startsWith(TOKEN_PREFIX);
  }

  private String token(RequestContext context){
    return authorizationHeader(context).replace(TOKEN_PREFIX,"");
  }

  private String authorizationHeader(RequestContext context){
    return context.getRequest().getHeader(AUTHORIZATION);
  }
}

application.yaml 中定义路由规则

zuul:
  routes:
    doorman:
      serviceId: doorman
      sensitiveHeaders:
    worker:
      serviceId: worker
    beekeeper:
      serviceId: beekeeper

ribbon:
  eureka:
    enabled: false

microservice.yaml 定义服务中心地址

APPLICATION_ID: company
service_description:
  name: manager
  version: 0.0.1
cse:
  service:
    registry:
      address: http://sc.servicecomb.io:30100

应用入口

@SpringBootApplication
@EnableCircuitBreaker
@EnableZuulProxy
@EnableDiscoveryClient
@EnableServiceComb
public class ManagerApplication{
  public static void main(String[] args){
    SpringApplication.run(ManagerApplication.class,args);
  }
}

项目归档(Project Archive)

manager会将每次用户请求缓存,这里采用Spring Cache Abstraction

人力资源(Human Resouce)

从运维层面保证服务的可靠性,主要包括:

  • 弹性伸缩:用户请求量增多与回落

  • 健康检查

  • 滚动升级

【参考】

1。 微信:https://mp.weixin.qq.com/s/Y9EvbMmSAit9X7AgH--Cxg

results matching ""

    No results matching ""