如何使用Spring Cloud – 简单服务流程(服务发现与API调用)

说起Spring Cloud那肯定要带上Spring Boot,业内人士对这两个东西必定不陌生。关于Spring Cloud的介绍,这里就不再过多的介绍。关于Spring Cloud搜索引擎搜索出来的资料并不乐观,可能向我一样的初学者,最需要的就是一份demo,先跑起来,至少做到麻雀虽小五脏俱全。

在这里还是要介绍以下Spring Cloud整个的工作流程。首先看一下Spring Cloud的工作流程,

Service Consumer -> Proxy Server -> Client(Load Balancer) -> Servie Discovery -> Target Service

这里就是要一个简单Service API调用的流程。下面看一下Spring Cloud中的组件与上面流程的对应关系。

Spring Cloud

Module

Restful API (Spring Boot)

Target Service

Eureka

Servie Discovery

Client & Load Balancer

Spring RestTemplate & Ribbon

Proxy(Edge) Server

Zuul

Service Consumer

Browser,Curl,Other Apps,etc

下面一步步介绍完成以上的简单demo

###数据结构及基础服务:

在这里使用Spring Boot快速的创建三个基础服务,并且其中三者之间存在逻辑依赖关系。

Company Service

Java

public class Company {

    private Long id;
    private String name;
    
    // Constructor、Getter、Setter @RestController
public class CompanyService {

    @RequestMapping("/company/{id}")
    public Company getCompanyById(@PathVariable("id") Long id){
        sleep();
        return new Company(id, "Company");
    }
	
	 //利用时间等待模拟Serivce调用时长
    private void sleep() {
        Random rand = new Random();
        int time = rand.nextInt(2000);

        try {
            Thread.sleep(time);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
public class Company {
 
    private Long id;
    private String name;
    
    // Constructor、Getter、Setter
}
 
@RestController
public class CompanyService {
 
    @RequestMapping("/company/{id}")
    public CompanygetCompanyById(@PathVariable("id") Long id){
        sleep();
        return new Company(id, "Company");
    }
 
 //利用时间等待模拟Serivce调用时长
    private void sleep() {
        Randomrand = new Random();
        int time = rand.nextInt(2000);
 
        try {
            Thread.sleep(time);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Employee Service

Java

public class Employee {

    private Long id;
    private Long companyId;
    private String name;
    
    // Constructor、Getter、Setter @RestController
public class EmployeeService {

    @RequestMapping("/employee/{id}")
    public Employee getEmployeeById(@PathVariable("id") Long id) {

        sleep();
        return new Employee(id,1L,"张三");
    }

    @RequestMapping("/employee")
    public List<Employee> getEmployeesByCompanyId(@RequestParam("companyId") Long companyId){
        List<Employee> employees = new ArrayList<>();

        employees.add(new Employee(1L, companyId, "张三"));
        employees.add(new Employee(2L, companyId, "李四"));
        employees.add(new Employee(3L, companyId, "王五"));

        sleep();
        return employees;
    }

    private void sleep() {
        Random rand = new Random();
        int time = rand.nextInt(2000);

        try {
            Thread.sleep(time);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}
public class Employee {
 
    private Long id;
    private Long companyId;
    private String name;
    
    // Constructor、Getter、Setter
}
 
@RestController
public class EmployeeService {
 
    @RequestMapping("/employee/{id}")
    public EmployeegetEmployeeById(@PathVariable("id") Long id) {
 
        sleep();
        return new Employee(id,1L,"张三");
    }
 
    @RequestMapping("/employee")
    public List<Employee> getEmployeesByCompanyId(@RequestParam("companyId") Long companyId){
        List<Employee> employees = new ArrayList<>();
 
        employees.add(new Employee(1L, companyId, "张三"));
        employees.add(new Employee(2L, companyId, "李四"));
        employees.add(new Employee(3L, companyId, "王五"));
 
        sleep();
        return employees;
    }
 
    private void sleep() {
        Randomrand = new Random();
        int time = rand.nextInt(2000);
 
        try {
            Thread.sleep(time);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
 
}

Product Service

Java

public class Product {

    private Long id;
    private Long companyId;
    private String sku;
    
    // Constructor、Getter、Setter 
}

@RestController
public class ProductService {

    private static final Logger LOG = LoggerFactory.getLogger(ProductService.class);

    @RequestMapping("/product/{id}")
    public Product getProductById(@PathVariable("id") Long id) {

        sleep();
        return new Product(id, 1L, "T001");
    }


    @RequestMapping("/product")
    public List<Product> getProductsByCompanyId(@RequestParam("companyId") Long companyId) {
        List<Product> products = new ArrayList<>();

        products.add(new Product(1L, companyId, "T001"));
        products.add(new Product(2L, companyId, "T002"));
        products.add(new Product(3L, companyId, "T003"));
        return products;
    }


    private void sleep() {
        Random rand = new Random();
        int time = rand.nextInt(3000);

        try {
            Thread.sleep(time);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
public class Product {
 
    private Long id;
    private Long companyId;
    private String sku;
    
    // Constructor、Getter、Setter
}
 
@RestController
public class ProductService {
 
    private static final LoggerLOG = LoggerFactory.getLogger(ProductService.class);
 
    @RequestMapping("/product/{id}")
    public ProductgetProductById(@PathVariable("id") Long id) {
 
        sleep();
        return new Product(id, 1L, "T001");
    }
 
 
    @RequestMapping("/product")
    public List<Product> getProductsByCompanyId(@RequestParam("companyId") Long companyId) {
        List<Product> products = new ArrayList<>();
 
        products.add(new Product(1L, companyId, "T001"));
        products.add(new Product(2L, companyId, "T002"));
        products.add(new Product(3L, companyId, "T003"));
        return products;
    }
 
 
    private void sleep() {
        Randomrand = new Random();
        int time = rand.nextInt(3000);
 
        try {
            Thread.sleep(time);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

这三个服务都应该是正常独立服务的。

所有的服务都需要在注册中心进行对当前服务的注册,因此要在三个服务的启动程序上加上注解 @EnableDiscoveryClient

在配置文件bootstrap.yml中加入以下配置

YAML

spring:
  application:
    name: company-service
  cloud:
    config:
      uri: ${vcap.services.${PREFIX:}configserver.credentials.uri:http://user:password@localhost:8888}
spring:
  application:
    name: company-service
  cloud:
    config:
      uri: ${vcap.services.${PREFIX:}configserver.credentials.uri:http://user:password@localhost:8888}

###服务中心

注册中心,在Demo中只用到最简单的服务发现与注册,只需要在启动程序加上注解 @EnableEurekaServer

@EnableDiscoveryClient

在application.yml中对注册服务进行配置

YAML

server:
  port: 8761
security:
  user:
    password: ${eureka.password}

eureka:
  client:
    registerWithEureka: false
    fetchRegistry: false
  server:
    waitTimeInMsWhenSyncEmpty: 0
  password: ${SECURITY_USER_PASSWORD:password}
server:
  port: 8761
security:
  user:
    password: ${eureka.password}
 
eureka:
  client:
    registerWithEureka: false
    fetchRegistry: false
  server:
    waitTimeInMsWhenSyncEmpty: 0
  password: ${SECURITY_USER_PASSWORD:password}

###远程调用Client Adapter

使用服务发现来寻找真实的服务地址,并获取API返回的数据。

在这里需要创建三个model,用来转换API结构

Java

public class CompanyAll {

    private Long id;
    private String name;
    private List<ProductDetail> productList;
    private List<EmployeeDetail> employeeList;

    public CompanyAll(Company company, List<Product> productList, List<Employee> employeeList) {
        this.id = company.getId();
        this.name = company.getName();
        if (employeeList != null) {
            this.productList = productList.stream().map(product ->
                    new ProductDetail(product.getId(), product.getSku())
            ).collect(Collectors.toList());
        }

        if (employeeList != null) {
            this.employeeList = employeeList.stream().map(employee ->
                    new EmployeeDetail(employee.getId(), employee.getName())
            ).collect(Collectors.toList());
        }

    }
    //Getter、Setter 
}

public class EmployeeDetail {
    private Long id;
    private String name;
    
   // Constructor、Getter、Setter
}

public class ProductDetail {

    private Long id;
    private String sku;
    
    // Constructor、Getter、Setter
}
public class CompanyAll {
 
    private Long id;
    private String name;
    private List<ProductDetail> productList;
    private List<EmployeeDetail> employeeList;
 
    public CompanyAll(Companycompany, List<Product> productList, List<Employee> employeeList) {
        this.id = company.getId();
        this.name = company.getName();
        if (employeeList != null) {
            this.productList = productList.stream().map(product ->
                    new ProductDetail(product.getId(), product.getSku())
            ).collect(Collectors.toList());
        }
 
        if (employeeList != null) {
            this.employeeList = employeeList.stream().map(employee ->
                    new EmployeeDetail(employee.getId(), employee.getName())
            ).collect(Collectors.toList());
        }
 
    }
    //Getter、Setter
}
 
public class EmployeeDetail {
    private Long id;
    private String name;
    
  // Constructor、Getter、Setter
}
 
public class ProductDetail {
 
    private Long id;
    private String sku;
    
    // Constructor、Getter、Setter
}

创建工具类AppUtil

在工具类中会调用到LoadBalancerClient,这个就是有Netflix Ribbon提供的Client。他会根据ServiceId(配置文件中的Service Name)向Eureka(注册服务器)获取服务地址。在这里也可以提供一个容错机制,极限情况下,全宕机时使用fallbackUri。

Java

@Component
public class AppUtil {

    @Autowired
    private LoadBalancerClient loadBalancer;

    public URI getRestUrl(String serviceId, String fallbackUri) {
        URI uri = null;
        try {
            ServiceInstance instance = loadBalancer.choose(serviceId);
            uri = instance.getUri();

        } catch (RuntimeException e) {
            uri = URI.create(fallbackUri);
        }

        return uri;
    }

    public <T> ResponseEntity<T> createOkResponse(T body) {
        return createResponse(body, HttpStatus.OK);
    }

    public <T> ResponseEntity<T> createResponse(T body, HttpStatus httpStatus) {
        return new ResponseEntity<>(body, httpStatus);
    }

    public <T> T json2Object(ResponseEntity<String> response, Class<T> clazz) {
        try {

            return (T) JSON.parseObject(response.getBody(), clazz);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

    }

    public <T> List<T> json2Objects(ResponseEntity<String> response, Class<T> clazz) {
        try {

            return JSON.parseArray(response.getBody(), clazz);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}
@Component
public class AppUtil {
 
    @Autowired
    private LoadBalancerClientloadBalancer;
 
    public URIgetRestUrl(String serviceId, String fallbackUri) {
        URIuri = null;
        try {
            ServiceInstanceinstance = loadBalancer.choose(serviceId);
            uri = instance.getUri();
 
        } catch (RuntimeException e) {
            uri = URI.create(fallbackUri);
        }
 
        return uri;
    }
 
    public <T> ResponseEntity<T> createOkResponse(T body) {
        return createResponse(body, HttpStatus.OK);
    }
 
    public <T> ResponseEntity<T> createResponse(T body, HttpStatushttpStatus) {
        return new ResponseEntity<>(body, httpStatus);
    }
 
    public <T> T json2Object(ResponseEntity<String> response, Class<T> clazz) {
        try {
 
            return (T) JSON.parseObject(response.getBody(), clazz);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
 
    }
 
    public <T> List<T> json2Objects(ResponseEntity<String> response, Class<T> clazz) {
        try {
 
            return JSON.parseArray(response.getBody(), clazz);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

当然这里还需要一个Client来对Api进行访问及Response数据处理

Java

@Component
public class RemoteServiceClient {

    @Autowired
    AppUtil appUtil;

    private RestTemplate restTemplate = new RestTemplate();

    public ResponseEntity<Company> getCompanyById(Long id) {

        URI uri = appUtil.getRestUrl("COMPANY-SERVICE", "http://localhost:57773/");
        String url = uri.toString() + "/company/" + id;

        ResponseEntity<String> resultStr = restTemplate.getForEntity(url, String.class);

        Company company = appUtil.json2Object(resultStr, Company.class);

        return appUtil.createOkResponse(company);
    }


    public ResponseEntity<List<Employee>> getEmployeesByCompanyId(Long id) {
        try {

            URI uri = appUtil.getRestUrl("EMPLOYEE-SERVICE", "http://localhost:58017");

            String url = uri.toString() + "/employee?companyId=" + id;

            ResponseEntity<String> resultStr = restTemplate.getForEntity(url, String.class);

            List<Employee> employees = appUtil.json2Objects(resultStr, Employee.class);

            return appUtil.createOkResponse(employees);
        } catch (Throwable t) {
            throw t;
        }
    }

    public ResponseEntity<List<Product>> getProductsByCompanyId(Long id) {
        try {

            URI uri = appUtil.getRestUrl("PRODUCT-SERVICE", "http://localhost:57750");

            String url = uri.toString() + "/product?companyId=" + id;

            ResponseEntity<String> resultStr = restTemplate.getForEntity(url, String.class);

            List<Product> employees = appUtil.json2Objects(resultStr, Product.class);

            return appUtil.createOkResponse(employees);
        } catch (Throwable t) {
            throw t;
        }
    }
}
@Component
public class RemoteServiceClient {
 
    @Autowired
    AppUtilappUtil;
 
    private RestTemplaterestTemplate = new RestTemplate();
 
    public ResponseEntity<Company> getCompanyById(Long id) {
 
        URIuri = appUtil.getRestUrl("COMPANY-SERVICE", "http://localhost:57773/");
        String url = uri.toString() + "/company/" + id;
 
        ResponseEntity<String> resultStr = restTemplate.getForEntity(url, String.class);
 
        Companycompany = appUtil.json2Object(resultStr, Company.class);
 
        return appUtil.createOkResponse(company);
    }
 
 
    public ResponseEntity<List<Employee>> getEmployeesByCompanyId(Long id) {
        try {
 
            URIuri = appUtil.getRestUrl("EMPLOYEE-SERVICE", "http://localhost:58017");
 
            String url = uri.toString() + "/employee?companyId=" + id;
 
            ResponseEntity<String> resultStr = restTemplate.getForEntity(url, String.class);
 
            List<Employee> employees = appUtil.json2Objects(resultStr, Employee.class);
 
            return appUtil.createOkResponse(employees);
        } catch (Throwable t) {
            throw t;
        }
    }
 
    public ResponseEntity<List<Product>> getProductsByCompanyId(Long id) {
        try {
 
            URIuri = appUtil.getRestUrl("PRODUCT-SERVICE", "http://localhost:57750");
 
            String url = uri.toString() + "/product?companyId=" + id;
 
            ResponseEntity<String> resultStr = restTemplate.getForEntity(url, String.class);
 
            List<Product> employees = appUtil.json2Objects(resultStr, Product.class);
 
            return appUtil.createOkResponse(employees);
        } catch (Throwable t) {
            throw t;
        }
    }
}

最终还是需要对外暴露一个端口来返回数据,这里也是一个Restful接口

Java

@RestController
public class RemoteAdapter {

    private static final Logger LOG = LoggerFactory.getLogger(RemoteAdapter.class);


    @Autowired
    RemoteServiceClient client;

    @Autowired
    AppUtil appUtil;

    @RequestMapping("/")
    public String welcome() {

        return "welcome to use my demo";
    }

    @RequestMapping("/company/{id}")
    public ResponseEntity<CompanyAll> getCompany(@PathVariable("id") Long id) {

        ResponseEntity<Company> companyResult = client.getCompanyById(id);

        if (!companyResult.getStatusCode().is2xxSuccessful()) {
            return appUtil.createResponse(null, companyResult.getStatusCode());
        }

        List<Product> products = null;
        try {
            ResponseEntity<List<Product>> productsResult = client.getProductsByCompanyId(id);
            if (!productsResult.getStatusCode().is2xxSuccessful()) {
                LOG.error("远程调用Product API 失败");
            } else {
                products = productsResult.getBody();
            }
        } catch (Throwable t) {
            LOG.error("远程调用Product API 异常 ", t);
            throw t;
        }

        List<Employee> employees = null;
        try {
            ResponseEntity<List<Employee>> employeeResult = null;
            employeeResult = client.getEmployeesByCompanyId(id);
            if (!employeeResult.getStatusCode().is2xxSuccessful()) {
                LOG.error("远程调用Employee API 失败");
            } else {
                employees = employeeResult.getBody();
            }

        } catch (Throwable t) {
            LOG.error("远程调用Employee API 失败");
            throw t;
        }


        return appUtil.createOkResponse(new CompanyAll(companyResult.getBody(), products, employees));
    }
}
@RestController
public class RemoteAdapter {
 
    private static final LoggerLOG = LoggerFactory.getLogger(RemoteAdapter.class);
 
 
    @Autowired
    RemoteServiceClientclient;
 
    @Autowired
    AppUtilappUtil;
 
    @RequestMapping("/")
    public String welcome() {
 
        return "welcome to use my demo";
    }
 
    @RequestMapping("/company/{id}")
    public ResponseEntity<CompanyAll> getCompany(@PathVariable("id") Long id) {
 
        ResponseEntity<Company> companyResult = client.getCompanyById(id);
 
        if (!companyResult.getStatusCode().is2xxSuccessful()) {
            return appUtil.createResponse(null, companyResult.getStatusCode());
        }
 
        List<Product> products = null;
        try {
            ResponseEntity<List<Product>> productsResult = client.getProductsByCompanyId(id);
            if (!productsResult.getStatusCode().is2xxSuccessful()) {
                LOG.error("远程调用Product API 失败");
            } else {
                products = productsResult.getBody();
            }
        } catch (Throwable t) {
            LOG.error("远程调用Product API 异常 ", t);
            throw t;
        }
 
        List<Employee> employees = null;
        try {
            ResponseEntity<List<Employee>> employeeResult = null;
            employeeResult = client.getEmployeesByCompanyId(id);
            if (!employeeResult.getStatusCode().is2xxSuccessful()) {
                LOG.error("远程调用Employee API 失败");
            } else {
                employees = employeeResult.getBody();
            }
 
        } catch (Throwable t) {
            LOG.error("远程调用Employee API 失败");
            throw t;
        }
 
 
        return appUtil.createOkResponse(new CompanyAll(companyResult.getBody(), products, employees));
    }
}

Ok,这里最后还是需要application.yml配置文件,对相关进行配置。当然bootstrap.yml的注册配置还是不能少的

YAML

server:
  port: 0

eureka:
  instance:
    leaseRenewalIntervalInSeconds: 10
    metadataMap:
      instanceId: ${vcap.application.instance_id:${spring.application.name}:${spring.application.instance_id:${random.value}}}
  client:
    registryFetchIntervalSeconds: 5
server:
  port: 0
 
eureka:
  instance:
    leaseRenewalIntervalInSeconds: 10
    metadataMap:
      instanceId: ${vcap.application.instance_id:${spring.application.name}:${spring.application.instance_id:${random.value}}}
  client:
    registryFetchIntervalSeconds: 5

###代理(边界)服务

边界服务在这里也demo中没有运用到太多的功能,这里只使用了路由转发的作用,因此也不用做过多的配置。只需要在启动程序上添加注解 @Controller @EnableZuulProxy即可

在application.yml需要对服务进行一些配置,由于在上述代码中提到了线程等待时间来模拟API调用时间,因此需要对ribbon的TimeOut时间设置到60秒。对zuul的配置最常用的是路由,对Client暴漏的服务接口进行路由转发

YAML

info:
  component: Zuul Server

endpoints:
  restart:
    enabled: true
  shutdown:
    enabled: true
  health:
    sensitive: false
hystrix:
  command:
    default:
      execution:
        timeout:
          enabled: false
ribbon:
  ReadTimeout: 60000
  ConnectTimeout: 6000

zuul:
  ignoredServices: "*"
  routes:
    service-adapter:
      path: /app/**

server:
  port: 8765

logging:
  level:
    ROOT: INFO
    org.springframework.web: INFO
info:
  component: Zuul Server
 
endpoints:
  restart:
    enabled: true
  shutdown:
    enabled: true
  health:
    sensitive: false
hystrix:
  command:
    default:
      execution:
        timeout:
          enabled: false
ribbon:
  ReadTimeout: 60000
  ConnectTimeout: 6000
 
zuul:
  ignoredServices: "*"
  routes:
    service-adapter:
      path: /app/**
 
server:
  port: 8765
 
logging:
  level:
    ROOT: INFO
    org.springframework.web: INFO

## 结果预览

来源:http://www.tuicool.com/articles/VbURJzQ

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值