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

来源:互联网 发布:烽火体育淘宝 编辑:程序博客网 时间:2024/06/06 06:57

说起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 @RestControllerpublic 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} @RestControllerpublic 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 @RestControllerpublic 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} @RestControllerpublic 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 }@RestControllerpublic 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@RestControllerpublic 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: 8761security:  user:    password: ${eureka.password}eureka:  client:    registerWithEureka: false    fetchRegistry: false  server:    waitTimeInMsWhenSyncEmpty: 0  password: ${SECURITY_USER_PASSWORD:password}
server:  port: 8761security:  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、Setterpublic class EmployeeDetail {    private Long id;    private String name;      // Constructor、Getter、Setterpublic class ProductDetail {     private Long id;    private String sku;        // Constructor、Getter、Setter}

创建工具类AppUtil

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

Java

@Componentpublic 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);        }    }}
@Componentpublic 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

@Componentpublic 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;        }    }}
@Componentpublic 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

@RestControllerpublic 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));    }}
@RestControllerpublic 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: 0eureka:  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 Serverendpoints:  restart:    enabled: true  shutdown:    enabled: true  health:    sensitive: falsehystrix:  command:    default:      execution:        timeout:          enabled: falseribbon:  ReadTimeout: 60000  ConnectTimeout: 6000zuul:  ignoredServices: "*"  routes:    service-adapter:      path: /app/**server:  port: 8765logging:  level:    ROOT: INFO    org.springframework.web: INFO
info:  component: Zuul Server endpoints:  restart:    enabled: true  shutdown:    enabled: true  health:    sensitive: falsehystrix:  command:    default:      execution:        timeout:          enabled: falseribbon:  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