千家信息网

跟我学Spring Cloud(Finchley版)-02-构建分布式应用

发表于:2024-11-30 作者:千家信息网编辑
千家信息网最后更新 2024年11月30日,上一节( 跟我学Spring Cloud(Finchley版)-01-开篇 )说过,Spring Cloud是一个快速构建分布式应用的工具集。本节,我们就来编写一个简单的分布式应用,并探讨这个分布式应
千家信息网最后更新 2024年11月30日跟我学Spring Cloud(Finchley版)-02-构建分布式应用

上一节( 跟我学Spring Cloud(Finchley版)-01-开篇 )说过,Spring Cloud是一个快速构建分布式应用的工具集。本节,我们就来编写一个简单的分布式应用,并探讨这个分布式应用有哪些问题。

服务消费者 & 提供者

本书使用服务提供者与服务消费者来描述微服务之间的调用关系。下表解释了服务提供者与服务消费者。

表-服务提供者与服务消费者

名词定义
服务提供者服务的被调用方(即:为其他服务提供服务的服务)
服务消费者服务的调用方(即:依赖其他服务的服务)

以电影售票系统为例。如图,用户向电影微服务发起了一个购票的请求。在进行购票的业务操作前,电影微服务需要调用用户微服务的接口,查询当前用户的余额是多少、是不是符合购票标准等。在这种场景下,用户微服务就是一个服务提供者,电影微服务则是一个服务消费者。

围绕该场景,先来编写一个用户微服务,然后编写一个电影微服务。

TIPS

服务消费者和服务提供者描述的只是微服务之间的调用关系,一般成对出现。例如本文,用户微服务是是电影微服务的服务提供者,电影微服务是用户微服务的服务消费者。很多初学者和笔者交流时,会描述提供者如何如何……仿佛消费者和提供者是微服务的固有属性,这是不对的--例如A调用B,B调用C,那么B相对A就是提供者,B相对C就消费者。

Spring Boot/Spring Cloud应用开发套路

Spring Boot/Spring Cloud时代后,应用开发基本遵循三板斧

  • 加依赖
  • 加注解
  • 写配置

至于你的业务代码,该怎么写还怎么写。

TIPS

对于懒人,可使用Spring Initilizr(IDEA、Spring Tool Suite等IDE上均有集成,也可在http://start.spring.io 使用网页版)创建应用,它会给你生成项目的依赖以及项目的骨架。后续,笔者会以番外的形式更新相关教程。

编写服务提供者【用户微服务】

  • 创建一个Maven项目,依赖如下:

    4.0.0com.itmuch.cloudmicroservice-simple-provider-user0.0.1-SNAPSHOTjar  org.springframework.boot  spring-boot-starter-parent  2.0.7.RELEASE  UTF-8  1.8      org.springframework.boot    spring-boot-starter-web        org.springframework.boot    spring-boot-starter-data-jpa          com.h3database    h3          org.projectlombok    lombok    true              org.springframework.cloud      spring-cloud-dependencies      Finchley.SR2      pom      import                  org.springframework.boot      spring-boot-maven-plugin      

    其中,spring-boot-starter-web 提供了Spring MVC的支持;spring-boot-starter-data-jpa 提供了Spring Data JPA的支持;h3 是一种内嵌的数据库,语法和MySQL类似(笔者实在没有动力为了简单的演示再写一大堆内容去演示怎么安装MySQL数据库);lombok 则是一款开发利器,可以帮助你简化掉N多冗余代码。

    WARNING

    • Lombok之前,必须为你的IDE安装Lombok插件!可参考:

    TIPS

    • Lombok快速上手:
    • Lombok官方网站:
  • 创建实体类:

    @Entity@Data@NoArgsConstructor@AllArgsConstructorpublic class User {@Id@GeneratedValue(strategy = GenerationType.AUTO)private Long id;@Columnprivate String username;@Columnprivate String name;@Columnprivate Integer age;@Columnprivate BigDecimal balance;}
  • 创建DAO:

    @Repositorypublic interface UserRepository extends JpaRepository {}
  • 创建Controller:

    @RequestMapping("/users")@RestControllerpublic class UserController {@Autowiredprivate UserRepository userRepository;@GetMapping("/{id}")public Optional findById(@PathVariable Long id) {  return this.userRepository.findById(id);}}

    其中, @GetMapping,是Spring 4.3提供的新注解。它是一个组合注解,等价于@RequestMapping(method = RequestMethod.GET),用于简化开发。同理还有@PostMapping@PutMapping@DeleteMapping@PatchMapping等。

  • 编写启动类:

    @SpringBootApplicationpublic class ProviderUserApplication {public static void main(String[] args) {  SpringApplication.run(ProviderUserApplication.class, args);}/** * 初始化用户信息 * 注:Spring Boot2不能像1.x一样,用spring.datasource.schema/data指定初始化SQL脚本,否则与actuator不能共存 * 原因详见: * https://github.com/spring-projects/spring-boot/issues/13042 * https://github.com/spring-projects/spring-boot/issues/13539 * * @param repository repo * @return runner */@BeanApplicationRunner init(UserRepository repository) {  return args -> {    User user1 = new User(1L, "account1", "张三", 20, new BigDecimal(100.00));    User user2 = new User(2L, "account2", "李四", 28, new BigDecimal(180.00));    User user3 = new User(3L, "account3", "王五", 32, new BigDecimal(280.00));    Stream.of(user1, user2, user3)      .forEach(repository::save);  };}}

    @SpringBootApplication是一个组合注解,它整合了@Configuration@EnableAutoConfiguration@ComponentScan注解,并开启了Spring Boot程序的组件扫描和自动配置功能。在开发Spring Boot程序的过程中,常常会组合使用@Configuration@EnableAutoConfiguration@ComponentScan等注解,所以Spring Boot提供了@SpringBootApplication,来简化开发。

    在启动时,我们使用了ApplicationRunner init(UserRepository repository) 初始化了三条数据,分别是张三、李四、王五。@Bean 则是一个方法注解,作用是实例化一个Bean并使用该方法的名称命名。类似于XML配置方式的<bean id="init" class="...ApplicationRunner"/>

  • 编写配置文件application.yml

    server:# 指定Tomcat端口port: 8000spring:jpa:  # 让hibernate打印执行的SQL  show-sql: truelogging:level:  root: INFO  # 配置日志级别,让hibernate打印出执行的SQL参数  org.hibernate: INFO  org.hibernate.type.descriptor.sql.BasicBinder: TRACE  org.hibernate.type.descriptor.sql.BasicExtractor: TRACE

    传统Web应用开发中,常使用properties格式文件作为配置文件。Spring Boot以及Spring Cloud支持使用properties或者yml格式的文件作为配置文件。

    yml文件格式是YAML(Yet Another Markup Language)编写的文件格式,YAML和properties格式的文件可互相转换,例如本节中的application.yml,就等价于如下的properties文件:

    server.port=8000spring.jpa.show-sql=truelogging.level.root=INFOlogging.level.org.hibernate=INFOlogging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACElogging.level.org.hibernate.type.descriptor.sql.BasicExtractor=TRACE

    从中不难看出,YAML比properties结构清晰;可读性、可维护性也更强,并且语法非常简洁。因此,本书使用YAML格式作为配置文件。但,yml有严格的缩进,并且key与value之间使用: 分隔,冒号后的空格不能少,请大家注意

测试

访问http://localhost:8000/users/1 ,可获得结果:

{"id":1,"username":"account1","name":"张三","age":20,"balance":100.00}

编写服务消费者【电影微服务】

我们已经编写了一个服务提供者(用户微服务),本节来编写一个服务消费者(电影微服务)。该服务非常简单,它使用RestTemplate调用用户微服务的API,从而查询指定id的用户信息。

  • 创建一个Maven项目,ArtifactId是microservice-simple-consumer-movie

  • 加依赖:

    org.springframework.bootspring-boot-starter-web
  • 创建实体类:

    @Data@AllArgsConstructor@NoArgsConstructorpublic class User {private Long id;private String username;private String name;private Integer age;private BigDecimal balance;}
  • 创建启动类:

    @SpringBootApplicationpublic class ConsumerMovieApplication {@Beanpublic RestTemplate restTemplate() {  return new RestTemplate();}public static void main(String[] args) {  SpringApplication.run(ConsumerMovieApplication.class, args);}}
  • 创建Controller:

    @RequestMapping("/movies")@RestControllerpublic class MovieController {@Autowiredprivate RestTemplate restTemplate;@GetMapping("/users/{id}")public User findById(@PathVariable Long id) {  // 这里用到了RestTemplate的占位符能力  User user = this.restTemplate.getForObject("http://localhost:8000/users/{id}", User.class, id);  // ...电影微服务的业务...  return user;}}

    由代码可知,Controller使用RestTemplate调用用户微服务的RESTful API。

  • 编写配置文件application.yml

    server:port: 8010

拓展阅读

本文使用RestTemplate实现了基于HTTP的远程调用,事实上,Spring 5开始,WebFlux提供了Reactive的Web Client:WebClinet使用方式和RestTemplate基本类似,但性能更强,吞吐更好。有兴趣的可前往 了解。在这里,笔者对WebClient做了一些简单的封装,也可关注:

测试

访问:http://localhost:8010/movies/users/1 ,结果如下:

{"id":1,"username":"account1","name":"张三","age":20,"balance":100.00}

存在的问题

至此,我们已经实现了这个最简单的分布式应用,应用之间通过HTTP通信。代码非常简单,但这些简单的代码里,存在着若干问题:

  • 应用没有监控,没有画板,一切指标都没有。在这个Growth Hack逐渐成为主流的时代,不弄个Dashboard把系统压力、QPS、CPU、内存、日活啥的可视化,你好意思出来混吗……

  • 地址硬编码问题--电影微服务中将用户微服务的地址写死,如果用户微服务地址发生变化,难道要重新上线电影微服务吗?

    你可能会质疑:用户微服务地址为什么会变,让它保持不变就行了啊,这不是问题。这里举两个例子:

    例1:如果你用Docker,那么地址几乎每次启动都会变……

    例2:你之前用的是TXYun,后来你想把用户微服务迁移到Aliyun。这个时候IP就会发生变化。我相信你不会乐意找到哪些服务调用了用户微服务的接口,然后所有调用用户微服务的服务统一修改地址……

  • 负载均衡如何考虑?难道得在电影微服务和用户微服务之间加个NGINX做负载均衡吗?听起来是可行的,但如果有10000+服务(这并不夸张,我司的微服务数目是这个数字乘以N,N >= m,哈哈哈)那这个NGINX的配置得有多复杂……

  • 服务之间没有容错机制,相信对技术有激情的你已经不止一次听过容错、降级、fallback、回退之类的词汇。

  • 如果应用发生故障,你怎么迅速找到问题所在?

  • 用户认证和授权呢?被狗吃了吗?

如上词汇,你可能看得懂,你也可能看不懂。没有关系,请继续阅读,笔者将会用通俗的语言去描述,在你看完本系列后,你会知道,原来那些所谓的高大上的理论、术语、技术,原来也就是这么回事儿。

配套代码

  • GitHub:
    • https://github.com/eacdy/spring-cloud-study/tree/master/2018-Finchley/microservice-simple-provider-user
    • https://github.com/eacdy/spring-cloud-study/tree/master/2018-Finchley/microservice-simple-consumer-movie
  • Gitee:
    • https://gitee.com/itmuch/spring-cloud-study/tree/master/2018-Finchley/microservice-simple-provider-user
    • https://gitee.com/itmuch/spring-cloud-study/tree/master/2018-Finchley/microservice-simple-consumer-movie

原文:http://www.itmuch.com/spring-cloud/finchley-2/ 转载请说明出处。

干货分享

0