【SpringCloud】1.初识微服务

专栏目录
0.docker快速入门
1.初识微服务
2.Gateway网关路由
3.微服务配置管理

文章目录

  • 微服务
  • Spring Cloud
    • 单体架构
    • 微服务架构
    • 微服务拆分
      • 什么时候拆?
      • 怎么拆?
    • 服务调用
      • RestTemplate
    • 服务治理
      • 注册中心
      • Nacos注册中心
        • 快速使用
        • 服务注册
        • 服务发现
      • OpenFeign
        • 快速入门
        • 连接池
        • 日志配置


微服务

微服务是一种软件架构风格,以专注于单一职责的小型项目为基础,组合出复杂的大型应用。

微服务顾名思义,就是很小的服务,所以它属于面向服务架构的一种。通俗一点来说,微服务类似于古代著名的发明,活字印刷术,每个服务都是一个组件,通过编排组合的方式来使用,从而真正做到了独立、解耦、组件化、易维护、可复用、可替换、高可用、最终达到提高交付质量、缩短交付周期的效果。

从专业的角度来看,微服务架构是一种架构模式,它提倡将单一应用程序划分成一组小的服务,服务之间互相协调、互相配合,为用户提供最终价值。每个服务运行在其独立的进程中,服务与服务间采用轻量级的通信机制互相沟通。每个服务都围绕着具体业务进行构建,并且能够被独立的部署到生产环境、类生产环境等。另外,应当尽量避免统一的、集中式的服务管理机制,对具体的一个服务而言,应根据业务上下文,选择合适的语言、工具对其进行构建。

特点:

  • 能拆分就拆分
  • 纵向业务划分
  • 由单一组织负责
  • 细粒度
  • 业务逻辑存在于每一个服务中
  • 使用轻量级通信方式,如HTTP

Spring Cloud

Spring-Cloud官方文档

SpringCloud继承了各种微服务功能组件,并基于SpringBoot实现了组件的装配

注意SpringCloud、SpringBoot和JDK的版本适配

单体架构

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

优点:架构简单、部署成本低

缺点:团队协作成本高、系统发布效率低(如项目过大每次编译时间长等)、系统可用性差(无法应对高并发状况)

适合开发功能相对简单,规模较小的项目

通过maven的父子工程进行依赖管理

微服务架构

微服务架构是服务化思想指导下的一套最佳实践架构方案。

服务化: 将单体架构中功能模块拆分为多个独立项目

  • 粒度小–功能模块划分适度
  • 团队自治–每个项目由单独的开发团队或开发小组负责软件的完整生命周期
  • 服务自治–将模块分别进行打包和部署

微服务拆分

服务拆分一定要考虑几个问题:

  • 什么时候拆?
  • 如何拆?

什么时候拆?

一般情况下,对于一个初创的项目,首先要做的是验证项目的可行性。因此这一阶段的首要任务是敏捷开发,快速产出生产可用的产品,投入市场做验证。为了达成这一目的,该阶段项目架构往往会比较简单,很多情况下会直接采用单体架构,这样开发成本比较低,可以快速产出结果,一旦发现项目不符合市场,损失较小。

如果这一阶段采用复杂的微服务架构,投入大量的人力和时间成本用于架构设计,最终发现产品不符合市场需求,等于全部做了无用功。

所以,对于大多数小型项目来说,一般是先采用单体架构,随着用户规模扩大、业务复杂后再逐渐拆分为微服务架构。这样初期成本会比较低,可以快速试错。但是,这么做的问题就在于后期做服务拆分时,可能会遇到很多代码耦合带来的问题,拆分比较困难(前易后难)。

而对于一些大型项目,在立项之初目的就很明确,为了长远考虑,在架构设计时就直接选择微服务架构。虽然前期投入较多,但后期就少了拆分服务的烦恼(前难后易)。

怎么拆?

之前我们说过,微服务拆分时粒度要小,这其实是拆分的目标。具体可以从两个角度来分析:

  • 高内聚:每个微服务的职责要尽量单一,包含的业务相互关联度高、完整度高。
  • 低耦合:每个微服务的功能要相对独立,尽量减少对其它微服务的依赖,或者依赖接口的稳定性要强。

高内聚首先是单一职责,但不能说一个微服务就一个接口,而是要保证微服务内部业务的完整性为前提。目标是当我们要修改某个业务时,最好就只修改当前微服务,这样变更的成本更低。

一旦微服务做到了高内聚,那么服务之间的耦合度自然就降低了。

当然,微服务之间不可避免的会有或多或少的业务交互,比如下单时需要查询商品数据。这个时候我们不能在订单服务直接查询商品数据库,否则就导致了数据耦合。而应该由商品服务对应暴露接口,并且一定要保证微服务对外接口的稳定性(即:尽量保证接口外观不变)。虽然出现了服务间调用,但此时无论你如何在商品服务做内部修改,都不会影响到订单微服务,服务间的耦合度就降低了。

明确了拆分目标,接下来就是拆分方式了。我们在做服务拆分时一般有两种方式:

  • 纵向拆分
  • 横向拆分

所谓纵向拆分,就是按照项目的功能模块来拆分。例如黑马商城中,就有用户管理功能、订单管理功能、购物车功能、商品管理功能、支付功能等。那么按照功能模块将他们拆分为一个个服务,就属于纵向拆分。这种拆分模式可以尽可能提高服务的内聚性。

横向拆分,是看各个功能模块之间有没有公共的业务部分,如果有将其抽取出来作为通用服务。例如用户登录是需要发送消息通知,记录风控数据,下单时也要发送短信,记录风控数据。因此消息发送、风控数据记录就是通用的业务功能,因此可以将他们分别抽取为公共服务:消息中心服务、风控管理服务。这样可以提高业务的复用性,避免重复开发。同时通用业务一般接口稳定性较强,也不会使服务之间过分耦合。

服务调用

微服务进行了拆分,实现了数据和服务的隔离,需要在网络上进行相互调用

通过服务的接口进行相互调用,通过发起HTTP请求实现

RestTemplate

Spring提供的工具,可方便实现Http请求的发送

①注入Rest Template到Spring容器

②调用Api发起远程调用

示例:

// 利用RestTemplate.exchange()发起http请求,得到http的响应
ResponseEntity<List<ItemDTO>> response = restTemplate.exchange(
    "http://localhost:8081/items?ids={ids}",//请求url
    HttpMethod.GET,//请求方式
    null,//requestEntity
    new ParameterizedTypeReference<List<ItemDTO>>() {},
//查询的集合使用ParameterizedTypeReferrence
    Map.of("ids", CollUtil.join(itemIds, ","))//url中占位符的参数,将itemIds中用","连接
);
//解析响应
if(!response.getStatusCode().is2xxSuccessful()){
    // 查询失败,直接结束
    return;
}
List<ItemDTO> items = response.getBody();
if (CollUtils.isEmpty(items)) {
    return;
}

服务治理

服务调用时的服务地址是不确定的,无法事先写死

注册中心

  • 注册中心:记录并监控微服务各实例状态,推送服务变更信息
  • 服务提供者:提供服务,注册服务信息到注册中心,暴露服务接口,供其他服务调用
  • 服务消费者:使用服务,在注册中心订阅服务,得到服务的信息,调用其他服务的接口

心跳:所有服务定期向注册中心发送自己的服务信息

Nacos注册中心

阿里巴巴的产品,已加入SpringCloudAlibaba,除此之外还有Eureka,Consul等注册中心

快速使用
  • 需要建立Nacos的数据库,用来存放Nacos数据

数据库的SQL文件在../nacos/conf/

【SpringCloud】1.初识微服务_第1张图片

图片内容来自:黑马程序员

  • 需要配置文件/root/nacos/custom.env,关于数据库的配置

【SpringCloud】1.初识微服务_第2张图片

图片内容来自:黑马程序员

  • 部署至docker
# localhost:8848/nacos
docker run -d \
--name nacos \
--env-file ./nacos/custom.env \
-p 8848:8848 \
-p 9848:9848 \
-p 9849:9849 \
--restart=always \
nacos/nacos-server:v2.1.0-slim
服务注册

①引入nacos discovery依赖:

<dependency>
	<groupId>com.alibaba.cloudgroupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>

②配置Nacos地址

spring:
	application:
		name: #服务名称
	cloud:
		nacos: 
			server-addr: localhost:8848 #nacos地址
服务发现

①引入nacos discovery依赖

<dependency>
	<groupId>com.alibaba.cloudgroupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>

②配置nacos地址

spring:
  cloud:
    nacos:
      server-addr: 192.168.150.101:8848

③服务发现

private final DiscoveryClient discoveryClient;
private void handler(List<BeanClass> vos){
    //1.根据服务名称,拉取服务实例列表
    List<ServiceInstance> instances = discoveryClient.getInstances("服务名称");
    //2.负载均衡,选择一个实例
    ServiceInstance instance = instances.get(RandomUtil.randomInt(instances.size()));
    //3.获取实例的IP和端口
    URI uri = instance.getUri();
    //4.进行服务调用
}

OpenFeign

声明式的Http客户端,基于SpringMVC常见注解优化服务调用的Http请求

快速入门

①引入依赖,包括OpenFeign和负载均衡组件SpingCloudLoadBalancer

   
  <dependency>
      <groupId>org.springframework.cloudgroupId>
      <artifactId>spring-cloud-starter-openfeignartifactId>
  dependency>
  
  <dependency>
      <groupId>org.springframework.cloudgroupId>
      <artifactId>spring-cloud-starter-loadbalancerartifactId>
  dependency>

②启用OpenFeign,使用EnableFeignClients的注解在启动项上

@EnableFeignClients
@SpringBootApplication
public class ProjectApplication(){...}

③编写OpenFeign客户端

@FeignClient(value="服务名称")
public interface ServiceClient{
    @GetMapping("/select")//请求路径
    List<DTO> queryItemByIds(@RequetrParam("ids") Collection<Long> ids);//请求参数
}

④使用FeignClient实现远程调用

List<DTO> items = serviceClient.queryItemByIds(List.of(1,2,3));
连接池

Feign底层发起http请求,依赖于其它的框架。其底层支持的http客户端实现包括:

  • HttpURLConnection:默认实现,不支持连接池
  • Apache HttpClient :支持连接池
  • OKHttp:支持连接池

因此我们通常会使用带有连接池的客户端来代替默认的HttpURLConnection。比如,我们使用OK Http.

①引入依赖


<dependency>
  <groupId>io.github.openfeigngroupId>
  <artifactId>feign-okhttpartifactId>
dependency>

②开启连接池

feign:
  okhttp:
    enabled: true # 开启OKHttp功能
日志配置

OpenFeign只会在FeignClient所在包的日志级别为DEBUG时,才会输出日志。而且其日志级别有4级:

  • NONE:不记录任何日志信息,这是默认值。
  • BASIC:仅记录请求的方法,URL以及响应状态码和执行时间
  • HEADERS:在BASIC的基础上,额外记录了请求和响应的头信息
  • FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据。

Feign默认的日志级别就是NONE,所以默认我们看不到请求日志。

①定义日志级别

import feign.Logger;
import org.springframework.context.annotation.Bean;

public class DefaultFeignConfig {
    @Bean
    public Logger.Level feignLogLevel(){
        return Logger.Level.FULL;
    }
}

②配置日志

  • 局部生效:在某个FeignClient中配置,只对该客户端生效
@FeignClient(value = "item-service", configuration = DefaultFeignConfig.class)
//configuration使用定义的日志级别
  • 全局生效:在@EnableFeignClients中配置,针对所有客户端生效
@EnableFeignClients(defaultConfiguration = DefaultFeignConfig.class)

你可能感兴趣的:(微服务,微服务,spring,cloud,java)