微服务技术栈

微服务

  • 单体架构:将业务的所有功能集中在一起开发,打成一个包部署。
    • 优点:架构简单、部署成本低
    • 缺点:耦合度高
  • 分布式架构:根据业务功能对系统进行拆分,每个业务模块作为独立项目开发,称为一个服务。
    • 优点:耦合度低、升级拓展方便
    • 问题:拆分粒度、服务之间的远程调用

微服务是一种经过良好架构设计的分布式架构方案,微服务架构特征:

  • 单一职责:微服务拆分粒度更小,每一个服务都对应唯一的业务能力,做到单一职责,避免重复业务开发
  • 面向服务
  • 自治
  • 隔离性强

image-20221227110315573

服务调用关系:

  • 服务提供者:暴露接口给其他微服务调用
  • 服务消费者:调用其他微服务提供的接口
  • 提供者与消费者角色是相对的

Eureka注册中心

在Eureka架构中,微服务角色有两类:

  • EurekaServer:服务端,注册中心
    • 记录服务信息
    • 心跳监控
  • EurekaClient:客户端
    • Provider:服务提供者,例如案例中的user-service
      • 注册自己的信息到EurekaServer
      • 每隔30秒向EurekaServer发送心跳
    • consumer:服务消费者,例如案例中的order-service
      • 根据服务名称从EurekaServer中拉取服务列表
      • 基于服务列表做负载均衡,选中一个微服务后发送远程调用

EurekaServer使用

  • 引依赖

        <dependencies>
    <!--        eureka服务端-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
            </dependency>
        </dependencies>
    
  • 加注解

    @EnableEurekaServer // 
    @SpringBootApplication
    public class EurekaApplication {
        public static void main(String[] args) {
            SpringApplication.run(EurekaApplication.class, args);
            System.out.println("Eureka-server 运行成功!");
        }
    }
    
  • 写配置信息

    server:
      port: 8082
    spring:
      application:
        name: eurekaserver
    eureka:
      client:
        service-url:
          defaultZone: http://localhost:8082/eureka
    

EurekaClient使用

  • 引入依赖

    <dependency> <!--是这个带starter的依赖-->
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
            </dependency>
    
  • 修改配置信息

    server:
      port: 8081
    spring:
      application:
        name: userservice
    eureka:
      client:
        service-url:
          defaultZone: http://localhost:8082/eureka
    

image-20221227161943691

三个服务都注册了。

服务发现

  • 引入Eureka-client依赖

  • 在application.yml中配置eureka的地址

  • 给RestTemplate添加@LoadBalanced注解

    @MapperScan("cn.itcast.order.mapper")
    @SpringBootApplication
    public class OrderApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(OrderApplication.class, args);
        }
        @Bean
        @LoadBalanced // 负载均衡的注解
        public RestTemplate restTemplate () {
            return new RestTemplate();
        }
    }
    
  • 用服务提供者的服务名称远程调用。

        public Order queryOrderById(Long orderId) {
            // 1.查询订单
            Order order = orderMapper.findById(orderId);
    
            String url = "http://userservice/user/" + order.getUserId(); // 请求地址
            User user = restTemplate.getForObject(url, User.class);
            order.setUser(user);
            // 4.返回
            return order;
        }
    

Ribbon负载均衡

image-20221227164357991

负载均衡的策略(两种方式):

  1. 代码方式:在order-service中的OrderApplication类中,定义一个新的IRule:

    @Bean
    public IRule randomRule() {
        return new RandomRule();
    }
    

    全局配置方式,即这个order服务 请求的所有服务 都使用 随机方式,负载均衡。

  2. 配置文件方式:在order-service的application.yml文件中,添加新的配置也可以修改规则:

    userservice:
    	ribbon:
    		NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 负载均衡规则
    

    指定某些服务,使用什么样的负载均衡规则。

饥饿加载

  • 开启饥饿加载

  • 指定饥饿加载的微服务名称

ribbon:
	eager-load:
		enabled: true # 开启饥饿加载,即在启动时加载这些类。不是等调用时,再加载(懒汉式)
		clients: userservice # 指定对userservice这个服务饥饿加载。这个是List,赋多个值时,如下
			- userservice
			- userservice

Nacos

1 配置Nacos

  1. 下载,Nacos

  2. 在bin目录,打开cmd,使用startup.cmd -m standalone启动。注意,路径中不能有中文

  3. 引入依赖,配置Nacos信息

    父工程:

    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-alibaba-dependencies</artifactId>
        <version>2.2.5.RELEASE</version>
        <type>pom</type>
        <scope>import</scope>
    </dependency>
    

    客户端:

    <!-- nacos客户端依赖包 -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    

    服务的yml文件

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

    此时,启动服务,会在http://127.0.0.1:8848/中发现注册的服务。账号和密码都为nacos

image-20221228145715005

2 服务多级存储模型

Nacos服务分级存储模型:

  • 一级是服务,UserService
  • 二级是集群,例如杭州或上海
  • 三级是实例,例如杭州机房的某台部署了UserService的服务器

如何设置实例的集群属性:

  • 修改application.yml文件的spring.cloud.nacos.discovery.cluster-name属性

3 服务实例的权重设置

通过设置服务实例的权重,可以调节服务的请求量,可以用在服务升级时(用户无感知)。值为0~1

image-20221228151501257

4 环境隔离

  • namespace用来做环境隔离
  • 每个namespace都有唯一id
  • 不同namespace下的服务不可见

5 Nacos与Eureka的异同

  • 相同点
    • 都支持服务注册和服务拉取
    • 都支持服务提供者心跳方式做健康检测
  • 区别
    • Nacos支持服务端主动检测提供者状态:临时实例采用心跳模式,非临时实例采用主动检测模式
    • 临时实例心跳不正常会被删除,非临时实例则不会被剔除
    • Nacos支持服务列表的消息推送模式,服务列表更新更及时
    • Nacos集群默认采用AP方式,当急群众存在非临时实例时,采用CP模式;Eureka采用AP方式

6 Nacos配置管理

将配置交给Nacos管理的步骤:

  • 在Nacos中添加配置文件
  • 在微服务中引入nacos的config依赖
  • 在微服务中添加bootstrap.yml,配置nacos地址、当前环境、服务名称、文件后缀名。这些决定了程序启动时去nacos读取哪个文件。

7 配置热更新

热更新:不需要重启服务,就能更改配置文件

两种方式:

  • 通过@Value注解注入,结合 在类上面使用@RefreshScope
  • 通过@ConfigurationProperties注入,自动刷新

注意事项:

  • 不是所有的配置都适合放到配置中心,维护起来比较麻烦。只放经常变动的
  • 建议将一些关键参数,需要运行时调整的参数放到nacos配置中心,一般都是自定义配置

Feign

为了解决RestTemplate的发送HTTP请求不优雅的问题,使用Feign优雅的发送HTTP请求。

1 使用步骤

  1. 引入依赖

            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-openfeign</artifactId>
            </dependency>
    
  2. 在order-service的启动类上,添加注解,开启Feign的功能

    @MapperScan("cn.itcast.order.mapper")
    @SpringBootApplication
    @EnableFeignClients   // 添加注解,开启Feign的功能
    public class OrderApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(OrderApplication.class, args);
        }
    }
    
  3. 编写Feign客户端:

    @FeignClient("userservice")
    public interface UserClient {
        @GetMapping("/user/{id}")
        User findById(@PathVariable("id") Long id);
    }
    
  4. 使用

    @Service
    public class OrderService {
    
        @Autowired
        private OrderMapper orderMapper;
        @Autowired
        private RestTemplate restTemplate;
        @Autowired
        private UserClient userClient;
    
        public Order queryOrderById(Long orderId) {
            // 1.查询订单
            Order order = orderMapper.findById(orderId);
    
    //        String url = "http://userservice/user/" + order.getUserId();
    //        User user = restTemplate.getForObject(url, User.class);
            User user = userClient.findById(order.getUserId());
            order.setUser(user);
            // 4.返回
            return order;
        }
    }
    
  5. 启动服务,访问 http://localhost:8080/order/102,测试成功。自动负载均衡,集成了Ribbon

2 日志配置

两种方式

  • 配置文件,feign.client.config.xxx.loggerLevel
    • 如果xxx是default则代表全局
    • 如果xxx是服务名称,例如UserService则代表某服务
  • java代码配置Logger.Level这个Bean
    • 如果在@EnableFeignClients注解声明则代表全局
    • 如果在@FeignClient注解中声明则代表某服务

网关

作用:

  • 对用户请求做身份认证、权限校检
  • 将用户请求路由到微服务,并实现负载均衡
  • 对用户请求做限流

image-20221229143319224

用户发送请求,网关和自己的配置文件做对比,看符合哪一个路由规则。找到对应的微服务,从注册中心拉取服务列表,做负载均衡,发送请求给对应的服务。

1 网关搭建的步骤

  1. 创建项目,引入依赖

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <parent>
            <artifactId>cloud-demo</artifactId>
            <groupId>cn.itcast.demo</groupId>
            <version>1.0</version>
        </parent>
        <modelVersion>4.0.0</modelVersion>
    
        <artifactId>gateway</artifactId>
    
        <dependencies>
    <!--        nacos 服务注册返现依赖-->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-gateway</artifactId>
            </dependency>
        </dependencies>
    </project>
    
  2. 配置application.yml,包括服务基本信息、nacos地址、路由

    server:
      port: 8085
    spring:
      application:
        name: gateway
      cloud:
        nacos:
          server-addr: localhost:8848
        gateway:
          routes:
            - id: user-service # 路由id 路由的唯一标识,不能重复
              uri: lb://userservice # 路由uri,路由的目标地址,http代表固定地址,lb表示根据服务名负载均衡
              predicates: # 路由断言,判断路由规则,
                - Path=/user/**
            - id: order-service
              uri: lb://orderservice
              predicates:
                - Path=/order/**
    

2 网关过滤器

过滤器的作用?

  • 对路由的请求或响应做加工处理,比如添加请求头

  • 配置在路由下的过滤器只对当前路由的请求生效

  • 配置

    server:
      port: 8085
    spring:
      application:
        name: gateway
      cloud:
        nacos:
          server-addr: localhost:8848
        gateway:
          routes:
            - id: user-service
              uri: lb://userservice
              predicates:
                - Path=/user/**
              filters:
                - # xxxxxx 添加相应的过滤器,并且只对当前的路由起作用
            - id: order-service
              uri: lb://orderservice
              predicates:
                - Path=/order/**
          default-filters:
            - #xxxx,对全局路由起作用
    

defaultFilters的作用是什么?

  • 对所有路由都生效的过滤器

3 全局过滤器

GlobalFilter

自定义类,实现GlobalFilter接口,添加@Order注解:

package cn.itcast.gateway;

import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

/**
 * @author yongheng98
 * @date 2022/12/29
 * @time 15:03
 */
@Component
@Order(-1)
public class Filter implements GlobalFilter {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 获取请求参数
        MultiValueMap<String, String> params = exchange.getRequest().getQueryParams();
        String authorization = params.getFirst("authorization");
        // 校检请求参数
        if ("admin".equals(authorization)) {
            return chain.filter(exchange);
        }
        // 设置状态码
        exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
        // 拦截
        return exchange.getResponse().setComplete();
    }
}

作用:

  • 对所有路由都生效的过滤器,并且可以自定义处理逻辑(区别于defaultfilters)

实现全局过滤器的步骤:

  • 实现GlobalFilter接口
  • 添加@Order注解或实现Ordered接口编写处理逻辑

4 过滤器的执行顺序

  • order值越小,优先级越高
  • 当order值相同时,顺序时defaultFilter最先,然后是局部的路由器过滤器,最后是全局的过滤器

5 跨域问题

image-20221229152041394

Docker

【待补充】

MQ

同步调用优点:

  • 时效性强,可以立即得到结果

同步调用的问题:

  • 耦合度高
  • 性能和吞吐能力下降
  • 有额外的资源消耗
  • 有级联失败问题

异步通信的优点:

  • 耦合度低
  • 吞吐量提升
  • 故障隔离
  • 流量削峰

异步通信的缺点:

  • 依赖于Broker的可靠性、安全性、吞吐能力
  • 架构复杂了,业务没有明显的流程线,不好追踪

1 RabbitMQ

docker 运行rabbitmq

docker run -d --hostname my-rabbit --name rabbit -p 15672:15672 -p 5673:5672 rabbitmq

进入容器内部执行

rabbitmq-plugins enable rabbitmq_management

访问 http://linuxip:15672 用户名和密码默认都是guest

输入命令:exit退出容器目录.

几个基础概念:

  • channel:操作MQ的工具
  • exchange:路由消息到队列中
  • queue:缓存消息
  • virtual host:虚拟主机,是对queue、exchange等资源的逻辑分组

端口:通信用的是5672,控制台用的是15672

2 SpringAMQP

发布者端设置

  1. 引入SpringAMQP的依赖

    <dependency>
    	<groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-amqp</artifactId>
    </dependency>
    
  2. 在publisher服务中编写application.yml,添加mq连接信息

    spring:
    	rabbitmq:
    		host: localhost # 主机名
    		port: 5672 # 端口
    		virtual-host: / # 虚拟主机
    		username: guest # 用户名
    		password: guest # 密码
    
  3. 在publisher服务中新建一个测试类,编写测试方法

消费者端设置

  1. 引入amqp的starter依赖
  2. 配置RabbitMQ地址
  3. 定义类,添加@Component注解
  4. 类中声明方法添加@RabbitListener注解方法参数就是消息

注意:消息一旦消费就会从队列中删除,RabbitMQ没有消息回溯功能。

通过调整application的prefetch来控制消费者预取的消息数量。

3 交换机Exchange

作用:

  • 接收publisher发送的消息
  • 将消息按照规则路由到与之绑定的队列
  • 不能缓存消息,路由失败,消息丢失
  • FanoutExchange的会将消息路由到每个绑定的队列

声明队列、交换机、绑定关系的Bean是什么?

  • Queue
  • FanoutExchange
  • Binding

DirectExchange:通过队列的key,决定将消息发送给哪个queue。

image-20221230163310686

注解式声明Queue、Binding、DirectExchange

image-20221230163016583

TopicExchange

image-20221230163531549

4 消息转换器

SpringAMQP中的消息的序列化和反序列化是怎么实现的?

  • 利用MessageConverter实现的,默认是JDK的序列化

  • 注意发送方与接收方必须使用相同的MessageConverter。

  • 使用

    image-20221230164748673

Spring中默认的MessageConverter是基于JDK序列化,自己指定一个基于json的MessageConverter,覆盖Spring的默认实现。

ElasticSearch

一个开源的分布式搜索引擎,可以用来实现搜索、日志统计、分析、系统监控等功能。

什么是Elastic stack,ELK

  • 是以ElasticSearch为核心的技术栈,包括beats、Logstash、kibana、ElasticSearch

什么是Lucene

  • 是Apache的开源搜索引擎类库,提供了搜索引擎的核心API

正向索引和倒排索引

image-20221231091939477

  • 正向索引:基于id,查找词条。查询词条时,必须先找到文档,而后判断是否包含词条。
  • 倒排索引:基于词条定位id。对文档内容分词,对词条创建索引,并记录词条所在文档信息。查询时先根据词条查询到文档id,而后获取到文档。

基础概念

  • 文档:ES是面向文档存储的,可以是数据库中的一条商品数据,一个订单信息。
  • 文档数据会被序列化为json格式后存储在ElasticSearch中
  • 索引:相同类型的文档的集合。
  • 映射:索引中文档的字段约束信息,类似表的结构约束。

与Mysql对比:

image-20221231094919953

  • Mysql:更擅长事务类型的操作,可以确保数据的安全性和一致性
  • ElasticSearch:擅长海量数据的搜索、分析、计算

安装、使用。。。。跳过

面试

1 常见的SpringCloud组件有哪些

  • 注册中心组件:Eureka、Nacos等
  • 负载均衡组件:Ribbon
  • 远程调用组件:OpenFeign
  • 网管组件:Zuul、Gateway
  • 服务保护组件:Hystrix、Sentinel
  • 服务配置管理组件:SpringCloudConfig、Nacos

2 Nacos的服务注册表结构

Nacos采用了数据的分级存储模型,最外层是NameSpace,用来隔离环境。然后是Group,用来对服务分组。接下来就是服务(Service)了,一个服务包含多个实例,但是可能处于不同机房,因此Service下有多个集群(Cluster),Cluster下是不同的实例(Instance)。

对应到Java代码中,Nacos采用了一个多层的Map来表示。结构为Map<String, Map<String, Service>> ,其中最外层的Map的key就是namespaceId,值是一个Map。内层的Map的key是group拼接serviceName,值是Service对象。

Service对象内部又是一个Map,key是集群名称,值是Cluster对象。而Cluster对象内部维护了Instance的集合。