微服务架构概述

微服务架构区别于传统的单体软件架构,是一种为了适应当前互联网后台服务的「三高需求:高并发、高性能、高可用」而产生的的软件架构。

单体软件架构

单体软件架构是将所有服务都写在一个单体应用程序中,并且各个功能模块间具有很强的耦合性。当其中一个模块需要维护时需要将整个应用进行重新发布。
image.png

优缺点

优点:
①开发简单,集中式管理
②基本不会重复开发
③功能都在本地,没有分布式的管理和调用消耗
缺点:
1、效率低:开发都在同一个项目改代码,相互等待,冲突不断
2、维护难:代码功功能耦合在一起,新人不知道何从下手
3、不灵活:构建时间长,任何小修改都要重构整个项目,耗时
4、稳定性差:一个微小的问题,都可能导致整个应用挂掉
5、扩展性不够:无法满足高并发下的业务需求

微服务架构

微服务架构是将各个功能模块分解为一个个的小的模块,每个模块之间独立运行相互不干扰,极大的降低里程序的耦合性,模块与模块之间的调用使用API及对外的接口,使用接口就可以调用其他功能模块。当其中一个功能模块进行维护更新时并不需要将其他功能模块一并发布更新,各个模块之间是相当于一个独立的应用程序。极大的提高了代码复用率,并不需要频繁的写一些重复的代码。所以微服务架构(MSA)的基础是将单个应用程序开发为一组小型独立服务,这些独立服务在自己的进程中运行,独立开发和部署。
image.png

优缺点

优点:

  1. 解决了复杂性的问题。它会将一种怪异的整体应用程序分解成一组服务。虽然功能总量 不变,但应用程序已分解为可管理的块或服务。每个服务都以RPC或消息驱动的API的形式定义了一个明确的边界;Microservice架构模式实现了一个模块化水平。
  2. 微服务架构使每个服务都能够由专注于该服务的团队独立开发。开发人员可以自由选择任何有用的技术,只要该服务符合API合同。当然,大多数组织都希望避免完全无政府状态并限制技术选择。然而,这种自由意味着开发人员不再有义务使用在新项目开始时存在的可能过时的技术。在编写新服务时,他们可以选择使用当前的技术。此外,由于服务相对较小,因此使用当前技术重写旧服务变得可行。
  3. Microservice架构模式使每个微服务都能独立部署。开发人员不需要协调部署本地服务的变更。这些变化可以在测试后尽快部署。例如,UI团队可以执行A | B测试,并快速迭代UI更改。Microservice架构模式使连续部署成为可能。
  4. Microservice架构模式使每个服务都可以独立调整。您可以仅部署满足其容量和可用性限制的每个服务的实例数。此外,您可以使用最符合服务资源要求的硬件。
  5. 关键点:复杂度可控,独立按需扩展,技术选型灵活,容错,可用性高

缺点:

  1. 微服务器的一个主要缺点是分布式系统而产生的复杂性。开发人员需要选择和实现基于消息传递或RPC的进程间通信机制。还必须编写代码来处理部分故障,因为请求的目的地可能很慢或不可用。
  2. 微服务器的另一个挑战是分区数据库架构。更新多个业务实体的业务交易是相当普遍的。但是,在基于微服务器的应用程序中,您需要更新不同服务所拥有的多个数据库。使用分布式事务通常不是一个选择,而不仅仅是因为CAP定理。
  3. 测试微服务应用程序也更复杂。服务类似的测试类将需要启动该服务及其所依赖的任何服务(或至少为这些服务配置存根)。
  4. 部署基于微服务的应用程序也更复杂。单一应用程序简单地部署在传统负载平衡器后面的一组相同的服务器上。每个应用程序实例都配置有基础架构服务(如数据库和消息代理)的位置(主机和端口)。微服务应用通常由大量服务组成。例如,每个服务将有多个运行时实例。更多的移动部件需要进行配置,部署,扩展和监控。还需要实现服务发现机制,使服务能够发现需要与之通信的任何其他服务的位置(主机和端口)。
  5. 关键点(挑战):多服务运维难度,系统部署依赖,服务间通信成本,数据一致性,系统集成测试,重复工作,性能监控等

Spring Cloud Alibaba实现微服务

核心组件

  1. 服务限流降级

默认支持 WebServlet、OpenFeign、RestTemplate、Spring Cloud Gateway, RocketMQ 限流降级功能的接入,可以在运行时通过控制台实时修改限流降级规则,还支持查看限流降级 Metrics 监控。可以使用Sentinel来实现服务限流熔断

  1. 服务注册与发现

服务的注册与发现可以使用Nacos,Nacos默认集成了Ribbon支持。

  1. 分布式配置管理
  2. 消息驱动能力
  3. 分布式事务
  4. 分布式任务调度

    基础工程结构设置

    1. |--工作区
    2. |--父工程
    3. |--服务提供方
    4. |--服务消费方
    5. |--网关服务
    微服务 - 图3
    使用父工程的配置文件来进行以来版本管理,在其中导入一下依赖 ```xml org.springframework.boot spring-boot-dependencies 2.3.2.RELEASE import pom org.springframework.cloud spring-cloud-dependencies Hoxton.SR9 import pom com.alibaba.cloud spring-cloud-alibaba-dependencies 2.2.6.RELEASE import pom
  1. <a name="LqcY0"></a>
  2. ## Nacos服务注册中心使用
  3. 在微服务中,各个服务之间要想相互调用需要使用服务注册和服务发现,目前常用的服务注册中心有Zookeeper(雅虎Apache),Eureka(Netfix),Nacos(Alibaba),Consul(Google)。其中Nacos孵化于阿里巴巴,具有服务注册、配置管理功能。
  4. <a name="gy8qZ"></a>
  5. ### Nacos下载与配置
  6. 下载地址
  7. ```xml
  8. https://github.com/alibaba/nacos/releases

配置:
创建名为nacos_config数据库,在下载后的conf文件夹中将nacos-mysql.sql导入nacos_config数据库。
image.png
数据库配置:
将上图数据库配置解除注释

  1. ### If use MySQL as datasource:
  2. spring.datasource.platform=mysql #此处为所使用的数据库类型
  3. ### Count of DB:
  4. db.num=1
  5. ### Connect URL of DB:
  6. db.url.0=jdbc:mysql://127.0.0.1:3306/nacos_config?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC
  7. db.user.0=root #数据库账号
  8. db.password.0=root #数据库密码

Nacos启动与访问

使用命令行进入Nacos的bin目录,使用以下命令启动:

  1. startup.cmd -m standalone

启动后打开浏览器输入:http://localhost:8848/nacos,进入nacos登陆界面,默认账号密码都为nacos

Nacos服务注册与调用

生产者服务注册

  1. 首先在服务提供方导入依赖

    1. <dependencies>
    2. <!--Web服务-->
    3. <dependency>
    4. <groupId>org.springframework.boot</groupId>
    5. <artifactId>spring-boot-starter-web</artifactId>
    6. </dependency>
    7. <!--服务的注册和发现依赖(将服务注册到nacos)-->
    8. <dependency>
    9. <groupId>com.alibaba.cloud</groupId>
    10. <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    11. </dependency>
    12. </dependencies>
  2. 修改application.yml文件 ```yaml server:

    服务端口号

    port: 8081 spring: application:

    注册到nacos后的服务名

    name: sca-provider cloud: nacos: discovery:

    1. #nacos的地址
    2. server-addr: localhost:8848
  1. 3. 创建启动类与控制类
  2. Spring Boot启动类
  3. ```java
  4. @SpringBootApplication
  5. public class NacosProviderApplication {
  6. public static void main(String[] args) {
  7. SpringApplication.run(NacosProviderApplication.class, args);
  8. }
  9. }

控制类

  1. @RestController
  2. public class ProviderController {
  3. @GetMapping("/provider/echo/{msg}")
  4. public String doRestEcho1(@PathVariable String msg) throws InterruptedException {
  5. return server + "Hello World" + msg;
  6. }
  7. }

启动控制类后就可以发现Nocos的服务列表中已经出现注册的服务。

消费者服务注册与服务调用

  1. 导入依赖:

    1. <dependencies>
    2. <!--Web服务-->
    3. <dependency>
    4. <groupId>org.springframework.boot</groupId>
    5. <artifactId>spring-boot-starter-web</artifactId>
    6. </dependency>
    7. <!--服务的注册和发现依赖-->
    8. <dependency>
    9. <groupId>com.alibaba.cloud</groupId>
    10. <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    11. </dependency>
    12. </dependencies>
  2. 修改application.yml文件

    1. server:
    2. #服务端口号
    3. port: 8090
    4. spring:
    5. application:
    6. #注册到nacos后的服务名
    7. name: sca-consumer
    8. cloud:
    9. nacos:
    10. discovery:
    11. #nacos的地址
    12. server-addr: localhost:8848
  3. 创建启动类并实现服务消费

    1. /**
    2. * 服务消费方对象的启动类
    3. * <p>业务描述:当客户端向服务消费放发起请求时,消费方调用服务提供方的api,进而获取服务提供方的数据</p>
    4. *
    5. * @Author: 一拳超人
    6. * @Date: 2021/9/15 10:39
    7. */
    8. @SpringBootApplication
    9. @EnableFeignClients
    10. public class NacosConsumerApplication {
    11. public static void main(String[] args) {
    12. SpringApplication.run(NacosConsumerApplication.class, args);
    13. }
    14. /**
    15. * 构建RestTemplate对象,并将此对象交由Spring管理,通过此对象进行远程服务调用
    16. * @return RestTemplate对象
    17. */
    18. @Bean
    19. public RestTemplate restTemplate() {
    20. return new RestTemplate();
    21. }
    22. @RestController
    23. public class ConsumerController {
    24. @Value("${spring.application.name}")
    25. private String appName;
    26. @Autowired
    27. private RestTemplate restTemplate;
    28. //调用服务提供方API
    29. @GetMapping("/consumer/doRestEcho1")
    30. public String doRestEcho1() {
    31. consumerService.doGetResource();
    32. //1.定义要用的API
    33. String url = "http://localhost:8081/provider/echo/" + appName;
    34. //2.定义访问API的方法
    35. return restTemplate.getForObject(url, String.class);
    36. }
    37. }
    38. }

    负载均衡设计与实现

Spring Cloud提供了名为Ribbon的组件,其内部默认集成了LoadBalancerClient负载均衡,可以使用注入LoadBalancerClient对象的或使用@LoadBalance注解来调用LoadBalancerClient的负载均衡方法,其默认为轮询的方式实现负载均衡。

image.png

方式一:注入LoadBalancerClient对象

LoadBalancerClient接口继承了ServiceInstanceChooser接口且只有一个实现类为
RibbonLoadBalancerClientServiceInstanceChooser接口主要功能是使用serviceId从 LoadBalancer 中为指定服务选择一个 ServiceInstance(系统中的服务的pojo实例接口)并返回与之对应的ServiceInstance。其中LoadBalancerClient接口与ServiceInstanceChooser接口源码如下:

  1. public interface ServiceInstanceChooser {
  2. ServiceInstance choose(String serviceId);
  3. }
  4. public interface LoadBalancerClient extends ServiceInstanceChooser {
  5. //使用从负载均衡器中挑选出的服务实例来执行请求内容。
  6. <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException;
  7. <T> T execute(String serviceId, ServiceInstance serviceInstance,
  8. LoadBalancerRequest<T> request) throws IOException;
  9. URI reconstructURI(ServiceInstance instance, URI original);
  10. }

其中<T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException;
<T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException;实现类是使用来自 LoadBalancer注解的 的 ServiceInstance 对象为指定的服务执行请求。其参数解释如下:

  1. serviceId是用于查找LoadBalancer的服务ID,
  2. request为在实现负载均衡执行前或执行后的操作,
  3. serviceInstance为执行请求的服务。

若想实现简单的负载均衡可以使用LoadBalancerClient接口继承了ServiceInstanceChooser接口的choos方法来传入要调用的服务名。

简单示例:

在控制类中注入LoadBalancerClient对象,使用LoadBalancerClient的方法choose来获取服务实例ServiceInstance,在使用服务实例中的get方法来获取服务的IP与端口号,在使用restTemplate的get类型的方法来调用服务。

  1. @RestController
  2. @RequestMapping("/consumer")
  3. public class TestController {
  4. @Value("${spring.application.name}")
  5. private String applicationName;
  6. @Autowired
  7. private RestTemplate restTemplate;
  8. @Autowired
  9. private LoadBalancerClient loadBalancerClient;
  10. @GetMapping("/loadBalanceGetName")
  11. public String loadBalanceTransferProvider() {
  12. ServiceInstance provider = loadBalancerClient.choose("provider");
  13. String host = provider.getHost();
  14. int port = provider.getPort();
  15. String url = StrUtil.format("http://{}:{}/provider/name/{}", host, port, applicationName);
  16. return restTemplate.getForObject(url, String.class);
  17. }
  18. }

方式二:使用@LoadBalance注解

@LoadBalanced注解由Spring Cloud的commons模块提供,在构造RestTemplate时添加@LoadBalanced注解就可以在RestTemplate中添加LoadBalancerClient,以实现客户端负载均衡。

详细说明:https://www.cnblogs.com/yourbatman/p/11532729.html

作用:

  1. 开启负载均衡
  2. 为RestTemplate添加LoadBalancerInterceptor拦截器,使用loadbalance拦截器进行ip:port的替换,将请求的地址中的服务名替换为具体的服务地址。
  3. 当Spring容器中具有多个相同的bean时可以通过@LoadBalanced进行区分,方便在注入标明你所要注入的具体的bean,消除歧义
  4. @LoadBalanced就是一个修饰符,和@Mapper,@Service一样,标注了@Mapper的bean都会自动注入到Bean中。

简单示例:

构造RestTemplate,在其中添加注解@LoadBalanced。

  1. @Configuration
  2. public class RestTemplateConfig {
  3. @LoadBalanced
  4. @Bean
  5. public RestTemplate loadBalancedRestTemplate() {
  6. return new RestTemplate();
  7. }
  8. }

实现远程调用

  1. @RestController
  2. @RequestMapping("/consumer")
  3. public class TestController {
  4. @Value("${spring.application.name}")
  5. private String applicationName;
  6. @Autowired
  7. private RestTemplate loadBalancedRestTemplate;
  8. @GetMapping("/annotationGetName")
  9. public String annotationLoadBalanceTransfer() {
  10. String url = StrUtil.format("http://provider/provider/name/{}", applicationName);
  11. return loadBalancedRestTemplate.getForObject(url, String.class);
  12. }
  13. }

方式三:使用Feign实现远程服务调用

简单示例:

  1. 添加依赖

    1. <dependency>
    2. <groupId>org.springframework.cloud</groupId>
    3. <artifactId>spring-cloud-starter-openfeign</artifactId>
    4. </dependency>
  2. 在启动类上添加@EnableFeignClients注解启用feign客户端;

    1. @SpringBootApplication
    2. @EnableFeignClients
    3. public class ConsumerApplication {
    4. public static void main(String[] args) {
    5. SpringApplication.run(ConsumerApplication.class, args);
    6. }
    7. }
  3. 定义Http请求API,基于此API借助OpenFeign访问远端服务,使用注解@FeignClient 定义feign客户端

    • @FeignClient 用于描述远程服务调用接口
    • name = “” 参数为服务名
    • contextId为当前bean名称,若没有指定contextId默认采用@FeignClient注解中name的名字作为bean名称
    • fallbackFactory用于服务调用超时等现象发生时,一种应对措施或处理机制。
      1. @FeignClient(name = "sca-provider", contextId = "RemoteProviderService", fallbackFactory = RemoteProviderFallbackFactory.class)
      2. public interface RemoteProviderService {
      3. /**
      4. * 调用远程服务
      5. *
      6. * @param msg 信息
      7. * @return 字符串
      8. */
      9. @GetMapping("/provider/echo/{msg}")
      10. String echoMessage(@PathVariable("msg") String msg);
      11. }
      配置服务调用超时处理机制
      1. @Slf4j//日志注解
      2. @Component
      3. public class FeignRemoteProviderFallbackFactory implements FallbackFactory<FeignRemoteProvider> {
      4. @Override
      5. public FeignRemoteProvider create(Throwable throwable) {
      6. log.error("服务调用失败{}", throwable.getMessage());
      7. return msg -> "<h1>服务忙请稍后访问</h1>";//lamda表达式语法
      8. }
      9. }
  4. 启用feign实现远程调用 ```java @RestController @RequestMapping(“/consumer”) public class TestController { @Value(“${spring.application.name}”) private String applicationName;

    @Autowired private FeignRemoteProvider feignRemoteProvider;

    @GetMapping(“/feignTransferGetName”) public String feignTransfer() {

    1. return feignRemoteProvider.echoMessage(applicationName);

    } }

  1. <a name="f4Sa4"></a>
  2. ## Nacos配置中心使用
  3. 1. 添加依赖
  4. ```xml
  5. <dependency>
  6. <groupId>com.alibaba.cloud</groupId>
  7. <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
  8. </dependency>
  1. 修改配置文件,将配置文件修改为bootstrap.yml

    1. server:
    2. port: 9000
    3. spring:
    4. application:
    5. name: provider #注册到nacos的服务名
    6. cloud:
    7. nacos:
    8. discovery:
    9. server-addr: localhost:8848
    10. config:
    11. server-addr: localhost:8848 #配置中心ip地址与端口号
    12. file-extension: yml #表示配置文件格式
  2. 在nacos配置中心新建配置

其中Data ID需要与服务名一致,配置中心配置格式需要与配置文件中设置的file-extension格式一致,在配置内容中添加日志配置,设置日志级别为error
image.png

  1. 修改Controller,测试能否正常打印日志,成功将会在控制台正常打印当前时间 ```java @Slf4j @RestController @RequestMapping(“provider”) public class TestController { @Value(“${server.port:8080}”) private String port;

    @GetMapping(“/name/{msg}”) public String getApplicationName(@PathVariable String msg) {

    1. log.info("当前时间:{}", System.currentTimeMillis());
    2. return "欢迎!" + msg + "来到" + port + "端口";

    } }

  1. 测试成功如下图所示:<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/21402661/1632109065678-190d3060-5d45-4b9b-9adf-4ee10a2b8bc8.png#clientId=uecc6836b-7a00-4&from=paste&height=157&id=u927ae1e5&margin=%5Bobject%20Object%5D&name=image.png&originHeight=314&originWidth=1269&originalType=binary&ratio=1&size=56734&status=done&style=none&taskId=u66aacca5-78cb-4929-b08b-1a155946b64&width=634.5)
  2. <a name="NHG3U"></a>
  3. ### Nacos配置中心动态刷新
  4. Nacos配置中心可以实现当配置中心的配置更新后可以动态更新项目配置,从而可以实现不去重启服务和热部署就可以更新项目配置。<br />实现配置动态更新需要使用`@RefreshScope`注解,这一注解作用为将@Bean定义放入refresh scope便捷注释(即`@Scope("refresh")`)。 以这种方式注释的 Bean 可以在运行时刷新,任何使用它们的组件将在下一次方法调用时获得一个新实例,完全初始化并注入所有依赖项。`@Scope("refresh")`为允许在运行时动态刷新 bean Scope 实现,当每次刷新bean时,在下次访问bean时将会创建一个新的实例。从而实现动态更新配置。<br />源码如下:
  5. ```java
  6. @Target({ ElementType.TYPE, ElementType.METHOD })
  7. @Retention(RetentionPolicy.RUNTIME)
  8. @Scope("refresh")
  9. @Documented
  10. public @interface RefreshScope {
  11. //用于AOP动态代理
  12. ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;
  13. }

实例:

  1. 修改Controller

    1. @Slf4j
    2. @RefreshScope
    3. @RestController
    4. @RequestMapping("/provider")
    5. public class ConfigRefresh {
    6. @Value("${logging.level.work.gong98:error}")
    7. private String level;
    8. @GetMapping("/getLogLevel")
    9. public String getLogLevel() {
    10. log.error("error");
    11. log.warn("warn");
    12. log.info("info");
    13. log.debug("debug");
    14. log.trace("trace");
    15. return "当前日志级别为:" + level;
    16. }
    17. }
  2. 将配置中心的日志级别修改为info,访问url地址进行测试

测试结果如下:
image.png

Nacos配置中心架构

Nacos配置中心管理模型由三部分构成,分别为命名空间(NameSpace)、分组(Group)、服务(Service)或集群(DataID)。
微服务 - 图8

  • namespace:命名空间,对不同环境进行隔离,例如开发使用开发环境,部署使用生产环境。
  • group:分组,将若干个服务或配置集归为一组。
  • Service/Data ID:服务或集群,对应为某一个服务或一个配置集。

使用命名空间

Nacos中默认有一个public命名空间,当没有指定命名空间时,所有的配置文件都会在默认的命名空间中。

  1. 首先创建命名空间,当不指定命名空间ID时会自动生成。

image.png

  1. 修改配置文件,指定命名空间

    1. server:
    2. port: 9000
    3. spring:
    4. application:
    5. name: provider
    6. cloud:
    7. nacos:
    8. discovery:
    9. server-addr: localhost:8848
    10. config:
    11. server-addr: localhost:8848
    12. file-extension: yml
    13. namespace: 5cb7beb6-41f3-4758-a1fe-241c9d17d2d1 #指定命名空间id

    实现分组

  2. 创建新配置文件并指定分组

image.png

  1. 修改配置文件指定分组

    1. server:
    2. port: 9000
    3. tomcat:
    4. threads:
    5. max: 500
    6. spring:
    7. application:
    8. name: provider
    9. cloud:
    10. nacos:
    11. discovery:
    12. server-addr: localhost:8848
    13. config:
    14. server-addr: localhost:8848
    15. file-extension: yml
    16. namespace: 5cb7beb6-41f3-4758-a1fe-241c9d17d2d1
    17. group: GROUP_TEST #指定分组
  2. 创建Controller进行测试

    1. @RestController
    2. @RequestMapping("provider")
    3. public class TestController {
    4. @Value("${server.tomcat.threads.max:200}")
    5. private Integer maxThread;
    6. @GetMapping("/max")
    7. public String getMaxThread() {
    8. return "最大线程数为:" + maxThread;
    9. }
    10. }

    共享配置设计及读取

    当同一个namespace的多个配置文件中都有相同配置时,可以对这些配置进行提取,然后存储到nacos配置中心的一个或多个指定配置文件,只需要在需要使用的服务中进行配置指定即可。

  3. 创建共享配置

image.png

  1. 在配置文件中进行指定共享配置

    1. server:
    2. port: 9000
    3. tomcat:
    4. threads:
    5. max: 500
    6. spring:
    7. application:
    8. name: provider
    9. cloud:
    10. nacos:
    11. discovery:
    12. server-addr: localhost:8848
    13. config:
    14. server-addr: localhost:8848
    15. file-extension: yml
    16. namespace: 5cb7beb6-41f3-4758-a1fe-241c9d17d2d1
    17. group: GROUP_TEST
    18. shared-configs[0]: #配置列表,表示为一个共享配置
    19. data-id: app-public-dev.yml #指定共享配置的配置名称
    20. refresh: true #默认false,共享配置更新,引用此配置的地方是否要更新
  2. 读取共享配置进行测试 ```java @RestController @RequestMapping(“provider”) public class TestController { @Value(“${page.pageSize:10}”) private Integer pageSize;

    @GetMapping(“/page”) public String getPageSize() {

    1. return "页面数量:" + pageSize;

    } }

```