SpringCloud

时间:2023-02-26 08:53:59

1.系统架构的演变

集中式架构 > 垂直拆分 > 分布式架构 > SOA > 微服务


1.1 集中式架构

  将业务的所有功能集中在一个项目中开发,打成一个包部署。
SpringCloud

  • 优点
    • 架构简单
    • 部署成本低
  • 缺点
    • 耦合度高(维护困难、升级困难)

1.2 垂直拆分

  当访问量逐渐增大,单一应用无法满足需求,此时为了应对更高的并发和业务需求,我们根据业务功能对系统进行拆分:
SpringCloud

  • 优点
    • 系统拆分实现了流量分担,解决了并发问题
    • 可以针对不同模块进行优化
    • 方便水平扩展,负载均衡,容错率提高
  • 缺点
    • 系统间相互独立,会有很多重复开发工作,影响开发效率

1.3 分布式服务

  当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。此时,用于提高业务复用及整合的分布式调用是关键。
SpringCloud

  • 优点
    • 将基础服务进行了抽取,系统间相互调用,提高了代码复用和开发效率
  • 缺点
    • 系统间耦合度变高,调用关系错综复杂,难以维护

1.4 流动计算架构(SOA)

  当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。此时,用于提高机器利用率的资源调度和治理中心(SOA)是关键
SpringCloud
服务治理要做什么?
1.服务注册中心,实现服务自动注册和发现,无需人为记录服务地址
2.服务自动订阅,服务列表自动推送,服务调用透明化,无需关心依赖关系
3.动态监控服务状态监控报告,人为控制服务状态

  • 缺点
    • 服务间会有依赖关系,一旦某个环节出错会影响较大
    • 服务关系复杂,运维、测试部署困难,不符合DevOps思想

1.5 微服务

微服务的架构特征:

  • 单一职责:微服务拆分粒度更小,每一个服务都对应唯一的业务能力,做到单一职责
  • 自治:自治是说服务间互相独立,互不干扰
    • 团队独立:每个服务都是一个独立的开发团队,人数不能过多。
    • 技术独立:因为是面向服务,提供Rest接口,使用什么技术没有别人干涉
    • 前后端分离:采用前后端分离开发,提供统一Rest接口,后端不用再为PC、移动段开发不同接口
    • 数据库分离:每个服务都使用自己的数据源
    • 部署独立,服务间虽然有调用,但要做到服务重启不影响其它服务。有利于持续集成和持续交付。每个服务都是独立的组件,可复用,可替换,降低耦合,易维护
  • 面向服务:面向服务是说每个服务都要对外暴露Rest风格服务接口API。并不关心服务的技术实现,做到与平台和语言无关,也不限定用什么技术实现,只要提供Rest的接口即可。
  • 隔离性强:服务调用做好隔离、容错、降级,避免出现级联问题
    SpringCloud

  微服务的上述特性其实是在给分布式架构制定一个标准,进一步降低服务之间的耦合度,提供服务的独立性和灵活性。做到高内聚,低耦合。

因此,可以认为微服务是一种经过良好架构设计的分布式架构方案 。


2.服务调用方式

2.1 RPC和HTTP

  无论是微服务还是SOA,都面临着服务间的远程调用。那么服务间的远程调用方式有哪些呢?常见的远程调用方式有以下2种:

  • RPC:Remote Produce Call远程过程调用,类似的还有RMI。自定义数据格式,基于原生TCP通信,速度快,效率高。早期的webservice,现在热门的dubbo,都是RPC的典型代表
  • Http:http其实是一种网络传输协议,基于TCP,规定了数据传输的格式。现在客户端浏览器与服务端通信基本都是采用Http协议,也可以用来进行远程服务调用。缺点是消息封装臃肿,优势是对服务的提供和调用方没有任何技术限定,*灵活,更符合微服务理念。

  如果你们公司全部采用Java技术栈,那么使用Dubbo作为微服务架构是一个不错的选择。相反,如果公司的技术栈多样化,而且你更青睐Spring家族,那么SpringCloud搭建微服务是不二之选。在我们的项目中,我们会选择SpringCloud套件,因此我们会使用Http方式来实现服务间调用。


2.2 Http客户端工具

已经有很多的http客户端工具,能够帮助我们做这些事情,例如:

  • HttpClient
  • OKHttp
  • URLConnection

接下来,不过这些不同的客户端,API各不相同


2.3 Spring的RestTemplate(封装Http客户端)

  Spring提供了一个RestTemplate模板工具类,对基于Http的客户端进行了封装,并且实现了对象与json的序列化和反序列化,非常方便。RestTemplate并没有限定Http的客户端类型,而是进行了抽象,目前常用的3种都有支持:

  • HttpClient
  • OkHttp
  • JDK原生的URLConnection(默认的)

使用步骤:
一、在Spring容器中注册RestTemplate
可以在启动类位置注册:

@SpringBootApplication
public class HttpDemoApplication {

	public static void main(String[] args) {
		SpringApplication.run(HttpDemoApplication.class, args);
	}

	@Bean
	public RestTemplate restTemplate() {
   
		return new RestTemplate();
	}
}

二、在测试类中注入,并调用其他服务的接口

@RunWith(SpringRunner.class)
@SpringBootTest(classes = HttpDemoApplication.class)
public class HttpDemoApplicationTests {

	@Autowired
	private RestTemplate restTemplate;

	@Test
	public void httpGet() {
        // 调用springboot案例中的rest接口
		User user = this.restTemplate.getForObject("http://localhost/user/1", User.class);
		System.out.println(user);
	}
}

通过RestTemplate的getForObject()方法,传递url地址及实体类的字节码,RestTemplate会自动发起请求,接收响应,并且帮我们对响应结果进行反序列化。
SpringCloud


3.SpringCloud

  SpringCloud集成了各种微服务功能组件,并基于SpringBoot实现了这些组件的自动装配,从而提供了良好的开箱即用体验。
SpringCloud


3.1 微服务场景模拟

案例:从订单模块查询订单的同时,根据订单中包含的用户编号查询出用户信息
在进行模拟之前,我们先了解两个概念:服务拆分、远程调用


3.2服务拆分

  在集中式架构中,我们会将订单服务和用户服务的功能写在同一个项目中,缺点也很明显:功能模块耦合度高,不利于开发和维护
  但是在分布式架构中,我们会将服务进行拆分,微服务也同样如此

一、服务拆分原则:

  • 不同微服务,不要重复开发相同业务
  • 微服务数据独立,不要访问其它微服务的数据库
  • 微服务可以将自己的业务暴露为接口,供其它微服务调用
    SpringCloud

二、服务拆分示例:
SpringCloud
cloud-demo:父工程,管理依赖

  • order-service:订单微服务,负责订单相关业务
  • user-service:用户微服务,负责用户相关业务

3.3 提供者和消费者

在调用关系中,我们要明确服务提供者和服务消费者

  • 服务提供者:
    • 提供接口给其他微服务
  • 服务消费者:
    • 调用其他微服务的接口

SpringCloud


3.4 微服务案例实现

一、服务提供者对外提供接口
服务提供者只需要对外提供接口即可,不需要其他操作

@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserService userService;

    /**
     * 根据用户编号查询用户信息
     */
    @GetMapping("/{id}")
    public User queryById(@PathVariable("id") Long id) {
        return userService.queryById(id);
    }
}

二、服务消费者注册RestTemplate
服务站消费者想要调用远程服务(Http方式)的前提是注册RestTemplate(此处是JDK原生的URLConnection), 我们可以在启动类中,注册RestTemplate实例

@SpringBootApplication
public class OrderApplication {

    public static void main(String[] args) {
        SpringApplication.run(OrderApplication.class, args);
    }

    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

三、服务消费者远程调用服务
使用RestTemplate的getForObject()方法进行服务调用

@RestController
@RequestMapping("order")
public class OrderController {

   @Autowired
   private OrderService orderService;

    @Autowired
    private RestTemplate restTemplate;

    @GetMapping("{orderId}")
    public Order queryOrderByUserId(@PathVariable("orderId") Long orderId) {
        Order order = orderService.queryOrderById(orderId);
        // 远程调用(Http方式)
        String url = "http://localhost:8081/user/" + order.getUserId();
        User user = restTemplate.getForObject(url, User.class);
        order.setUser(user);
        return order;
    }
}

四、流程图
SpringCloud


3.5 存在的问题

问题引入
SpringCloud

  • order-service在发起远程调用的时候,该如何得知user-service实例的ip地址和端口?
  • 有多个user-service实例地址,order-service调用时该如何选择?
  • order-service如何得知某个user-service实例是否依然健康,是不是已经宕机?

问题总结

  • 在consumer中,我们把url地址硬编码到了代码中,不方便后期维护
  • consumer需要记忆provider的地址,如果出现变更,可能得不到通知,地址将失效
  • consumer不清楚provider的状态,服务宕机也不知道
  • provider只有1台服务,不具备高可用性
  • 即便provider形成集群,consumer还需自己实现负载均衡

其实上面说的问题,概括一下就是分布式服务必然要面临的问题

  • 服务管理
    • 如何自动注册和发现
    • 如何实现状态监管
    • 如何实现动态路由
  • 服务如何实现负载均衡
  • 服务如何解决容灾问题
  • 服务如何实现统一配置

4.Eureka注册中心

问题分析
  在刚才的案例中,user服务对外提供服务,需要对外暴露自己的地址。而order服务(调用者)需要记录服务提供者的地址。将来地址出现变更,还需要及时更新。这在服务较少的时候并不觉得有什么,但是在现在日益复杂的互联网环境,一个项目肯定会拆分出十几,甚至数十个微服务。此时如果还人为管理地址,不仅开发困难,将来测试、发布上线都会非常麻烦,这与DevOps的思想是背道而驰的。


4.1 Eureka的作用:

SpringCloud

一、order-service怎么知道user-service的实例地址
获取地址信息流程:

  • user-service实例启动后,将自己的信息注册到eureka-server(Eureka服务端),这个叫注册服务
  • eureka-server保存服务名称服务实例地址列表的映射关系
    • 集群时, 一个服务名称可能对应多个服务实例地址
  • provider根据服务名称,拉取实例地址列表。这个叫服务发现或服务拉取

二、order-service怎么从多个user-service实例中选择具体的实例

  • order-service从实例列表中利用负载均衡算法选中一个实例地址
  • 向该实例地址发起远程调用

三、order-service如何得知某个user-service实例是否依然健康,是不是已经宕机?

  • user-service服务会每隔一段时间(默认30秒)向eureka-server发起请求,报告自己状态,称为心跳
  • 当超过一定时间没有发送心跳时,eureka-server会认为微服务实例故障,将该实例从服务列表中剔除
  • order-service拉取服务列表时,就能将故障实例排除了

4.2 搭建eureka-server

一、引入eureka依赖
引入SpringCloud为eureka提供的starter依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>

使用SpringCloud的组件,例如spring-cloud-starter-netflix-eureka-server,需要引入spring-cloud-dependencies

  • spring-cloud-dependencies:定义了SpringCloud组件的版本号

Springcloud的版本依赖问题(最全,包含springCloud所有的版本)

我们可以在父工程中定义

<dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Hoxton.SR10</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
</dependencyManagement>

二、编写启动类
启动类一定要添加@EnableEurekaServer注解,开启eureka的注册中心功能

package cn.itcast.eureka;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

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

三、编写application.yml

server:
  port: 10086 # 端口
spring:
  application:
    name: eureka-server # 应用名称, 会在Eureka中显示
eureka:
  client:
    service-url:  # EurekaServer的地址,现在是自己的地址,如果是集群,需要加上其它Server的地址。
      defaultZone: http://127.0.0.1:${server.port}/eureka

四、启动服务
启动微服务,然后在浏览器访问:http://127.0.0.1:10086
SpringCloud
SpringCloud


4.3 provider进行服务注册

下面,我们将user-service注册到eureka-server中去。

一、引入依赖
引入下面的eureka-client依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

二、编写application.yml

spring:
  application:
    name: user-service # 应用名称,注册到eureka后的服务名称 
eureka:
  client:
    service-url: # EurekaServer地址
      defaultZone: http://127.0.0.1:10086/eureka

三、启动多个user-service实例
为了演示一个服务有多个实例的场景,我们添加一个SpringBoot的启动配置,再启动一个user-service。将第二个实例的端口改为8082
SpringCloud

查看eureka-server管理页面:

SpringCloud


4.4 consumer实现服务发现

一、引入依赖(依赖与服务注册一直)
之前说过,服务发现、服务注册统一都封装在eureka-client依赖,因此这一步与服务注册时一致。

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

二、编写application.yml(配置与服务注册一致)
服务发现也需要知道eureka地址,因此第二步与服务注册一致,都是配置eureka信息:

spring:
  application:
    name: order-service
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10086/eureka

启动项目后,查看eureka-server管理页面:

SpringCloud

三、服务拉取和负载均衡
  最后,我们要去eureka-server中拉取user-service服务的实例列表,并且实现负载均衡。不过这些动作不用我们去做,只需要添加一些注解即可。

在注册RestTemplate时,添加@LoadBalanced注解,实现负载均衡
SpringCloud

四、修改远程接口路径
修改order-service服务中的cn.itcast.order.service包下的OrderService类中的queryOrderById方法。修改访问的url路径,用服务名代替ip、端口:
SpringCloud
spring会自动帮助我们从eureka-server端,根据userservice这个服务名称,获取实例列表,而后完成负载均衡。