黑马微服务教程

本文详细介绍了微服务架构中的关键组件,从远程调用的RestTemplate开始,探讨了Eureka服务发现、Ribbon客户端负载均衡、Nacos注册中心的配置管理,以及Feign声明式HTTP客户端。接着,讲解了Spring Cloud Gateway网关的断言工厂、过滤器和跨域问题。此外,还涵盖了Docker的基础知识,包括数据卷、自定义镜像和docker-compose。最后,文章提到了消息队列MQ的重要性,以及Elasticsearch的安装与使用,包括ik分词器的配置。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 远程调用

通过RestTemplate实现基于http的远程调用:

@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();
    }
}

在controller中即可通过http地址调用其它服务的方法:

   @Autowired
   private RestTemplate restTemplate;

   @GetMapping("{orderId}")
   public Order queryOrderByUserId(@PathVariable("orderId") Long orderId) {
       // 根据id查询订单并返回
       Order order = orderService.queryOrderById(orderId);
       // 发起http请求
       User user = restTemplate.getForObject("http://userservice/user/" + order.getUserId(), User.class);
       order.setUser(user);
       return order;
   }

2. Eureka

Eureka是基于REST的服务发现框架,基本操作:

REST: Representational State Transfer,表现状态转移,是一种前后端分离的接口规范

  1. 每一个URI代表一种资源;
  2. 同一种资源有多种表现形式(xml/json);
  3. 所有的操作都是无状态的。
  4. 规范统一接口。
  5. 返回一致的数据格式。
  6. 可缓存(客户端可以缓存响应的内容)。

Eureka基本操作
代码如下:

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

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

3. Ribbon

为了:

如何在配置Eureka Client注册中心时不去硬编码Eureka Server的地址?
在微服务不同模块间进行通信时,如何不去硬编码服务提供者的地址?
当部署多个相同微服务时,如何实现请求时的负载均衡?

因此,Ribbon是一个客户端负载均衡器,基本原理如图:
Ribbon负载均衡
可以通过实现IRule接口实现全局的负载均衡策略:

7种负载均衡策略:

  1. RoundRobinRule: 轮询
  2. ZoneAvoidanceRule: 区域敏感策略(默认),根据服务所在区域(zone)的性能和服务的可用性来选择服务实例,在没有区域的环境下,即为轮询。
  3. WeightedResponseTimeRule: 权重策略,刚开始使用轮询策略并开启一个计时器,每一段时间收集一次所有服务提供者的平均响应时间,然后再给每个服务提供者附上一个权重,权重越高被选中的概率也越大
  4. RandomRule: 随机
  5. BestAvailableRule: 最小并发数策略,遍历服务提供者列表,选取连接数最小的⼀个服务实例。如果有相同的最小连接数,那么会调用轮询策略进行选取。
  6. RetryRule: 重试策略,轮询策略来获取服务,如果获取的服务实例为 null 或已经失效,则在指定的时间之内不断地进行重试来获取服务,如果超过指定时间依然没获取到服务实例则返回 null
  7. AvailabilityFilteringRule: 可用敏感性策略,先过滤掉非健康的服务实例,然后再选择连接数较小的服务实例
    @Bean
    public IRule randomRule() {
        return new RandomRule();
    }

也可以通过配置文件指定某微服务的负载均衡策略:

userservice: # 服务名
  ribbon:
    NFloadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
  • 懒加载:Ribbon会在首次访问时去创建负载均衡器,后续可以直接访问。这会造成一定的速度差异和某些bug,可以启用饥饿加载,即服务启动时拉取服务列表解决:
ribbon:
  eager-load:
    enabled: true
    clients: userservice # 指定对userservice启用饥饿加载
    	- userservice # 指定多个服务时换行加-
    	- orderservice

4. Nacos

我人傻了…公司的jdk安装包只有jre,合着我用了这么久的open jdk o(╥﹏╥)o 赶紧重装一个

解决了,继续继续


nacos是阿里巴巴的注册中心,单机模式记得改startup.cmd中的配置为standalone,使用nacos就要把eureka的配置都注掉,两者都遵循spring cloud Commons。
使用nacos:

// 单机模式启动nacos
E:\nacos\bin>startup.cmd -m standalone

// 父项目引入依赖
<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>

// 子项目引入服务发现依赖
<dependency>
	<groupId>com.alibaba.cloud</groupId>
	<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

// 子项目yml配置地址
spring:
  cloud:
    nacos:
      server-addr: localhost:8848 # nacos服务地址
      discovery:
        cluster-name: BJ # 集群名称

即可在8848端口进入nacos管理界面

localhost:8848

4.1 服务多级存储模型

优先访问本地集群,跨地区调用延迟较高,通过cluster-name配置集群名称
在这里插入图片描述
通过配置

userservice:
  ribbon:
    NFloadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule # nacos负载均衡规则

优先选择本地集群,在本地内随机,本地不可用则跨集群访问,并输出警告信息。

4.2 权重配置

在nacos页面可以管理服务的权重,权重配置在[0,1],越小越低,且成比例。e.g. A-0.1, B-1, 则B的访问概率是A的十倍。

0权重的作用:0不会被访问,比如版本升级时替换服务器设0升级,升级后用小权重测试,没问题再把旧服务器设0升级

4.3 环境隔离

使用命名空间隔离开发、测试、上线服务,不同空间服务不可见
在这里插入图片描述

// 子项目yml配置地址
spring:
  cloud:
    nacos:
      server-addr: localhost:8848 # nacos服务地址
      discovery:
        cluster-name: BJ # 集群名称
        namespace: 001 # 命名空间,不自己写则自动生成uuid
        ephemeral: false # 非临时实例

4.4 nacos和eureka的异同

服务拉取: eureka没30秒从注册中心拉取一次,nacos不仅拉取,当服务变更时注册中心会主动推送;
服务注册: eureka心跳检测,nacos临时实例心跳检测,异常服务自动踢出,非临时实例不用检测,注册中心主动询问,不会自动踢出。
集群: 都默认AP模式,nacos集群中有临时实例时切换到CP模式。
在这里插入图片描述

4.5 nacos配置管理

在这里插入图片描述
以服务名称-环境.yaml格式命名配置文件(userservice-dev.yaml),写所有有热更新需求的配置。
由于nacos地址放在application.yml中会导致顺序错误,所以需要用优先级最高的bootstrap.yml存放nacos相关配置。
在这里插入图片描述
使用方法:

// 引入依赖
<dependency>
	<groupId>com.alibaba.cloud</groupId>
	<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>

// bootstrap配置
spring:
  application:
    name: userservice #服务名
  profiles:
    active: dev # 开发环境
  cloud:
    nacos:
      server-addr: localhost:8848 # nacos服务地址
      discovery:
        cluster-name: BJ # 集群名称
      config:
        file-extension: yaml # 文件后缀名

热更新方法:

  1. @Value("${pattern.dateformat}")属性注入的方法所在类上加注解@RefreshScope
  2. 使用配置类并加上@ConfigurationProperties(prefix = "pattern")

多环境配置:
在这里插入图片描述
多配置优先级:
在这里插入图片描述

4.6 nacos集群

Alt
集群配置存入数据库,所以:

  1. 搭建mysql集群
  2. 配置nacos节点信息、数据库信息
  3. 启动nacos节点
  4. 配置Nginx反向代理即可

5. Feign

因为RestTemplate需要拼接URL,在实际项目中不现实,所以可以使用声明式http客户端Feign优雅的发送http:

// 引入依赖
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

// 启用feign
@EnableFeignClients

// feign配置类
@FeignClient("userservice")
public interface UserClient {

    /**
     * 根据用户id查找用户表
     * @return User
     */
    @GetMapping("/user/{id}")
    User findById(@PathVariable("id") Long id);
}

// 注入并使用
   @Autowired
   private UserClient userClient;
   

配置类或yml中feign可以配置的内容:
在这里插入图片描述
性能优化: feign底层的客户端通过URLConnection实现,不支持连接池。因此可以用支持连接池的Apache HttpClient或者OKHttp代替,同时把日志级别调成NONE或BASIC节省性能。

// 引入依赖
<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-httpclient</artifactId>
</dependency>

// 配置
feign:
#  client:
#    config:
#      default: # 所有,或者针对某个服务如userservice
#        loggerLevel: FULL
  httpclient:
    enabled: true # 开启feign对httpclient的支持
    max-connections: 200 # 最大连接数
    max-connections-per-route: 50 # 每个路径的最大连接数

Feign的实现方案:

  1. controllerfeignclient继承同一接口,但会造成紧耦合
  2. feignclient、POJO和feign的默认配置都定义到一个项目中,会造成引入多余的方法。

6. 网关

在这里插入图片描述
目前主要有Zuul和spring cloud gateway两种,前者是基于servlet的阻塞式,后者是基于webflux的响应式,所以一般都用后者。配置方法:
创建gateway模块,引入依赖、配置并启动服务即可:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

// 配置
server:
  port: 10010

spring:
  application:
    name: gateway # 服务名称
  cloud:
    nacos:
      server-addr: localhost:8848
    gateway:
      routes: # 路由网关配置
        - id: user-service # 路由id,唯一即可
          uri: lb://userservice # 路由目标地址交给负载均衡,或者写http交给固定地址
          predicates: # 路由断言,根据断言规则判断是否符合路由规则
            - Path=/user/** #按路径匹配

路由原理解释:
在这里插入图片描述

6.1 断言工厂

根据官方教程使用即可
在这里插入图片描述

6.2 过滤器

过滤器可以对进入网关的请求和微服务返回的响应做处理,并形成过滤器链,配置文件中添加即可。
全局过滤器的优先级需要手动指定,另两种过滤器自动从1递增,优先级不同按优先级执行,相同则默认>路由>全局。
在这里插入图片描述

	filters: # 对某个路由生效
  		- AddRequestHeader=X-Request-red, blue
default-filters: # 对所有路由生效
  - AddRequestHeader=X-Request-red, blue

可以在方法中通过@RequestHeader(value = "truth", required = false)非必须的添加请求头以实现过滤。

6.2 全局过滤器

网关过滤器只能用默认配置,可以实现GlobelFilter接口实现自定义的过滤器。

// 指定过滤器的优先级[-2147483648, 2147483647],越小越高
@Order(-1)
@Component
public class AuthorizeFilter implements GlobalFilter {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 1.获取请求参数
        ServerHttpRequest request = exchange.getRequest();
        MultiValueMap<String, String> queryParams = request.getQueryParams();
        // 2.获取参数中需要的属性
        String authorization = queryParams.getFirst("authorization");
        // 3.判断参数值放行或拦截
        if ("admin".equals(authorization)) {
            return chain.filter(exchange);
        }
        exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
        return exchange.getResponse().setComplete();
    }
}

6.3 网关跨域问题

跨域问题: 浏览器禁止请求的发起者和服务端发生跨域(域名不一致)Ajax请求,导致请求被浏览器拦截。在配置文件中配置网关即可。

spring:
  cloud:
    gateway:
      globalcors:
        add-to-simple-url-handler-mapping: true
        corsConfigurations:
          '[/**]':
            allowedOrigins: # 允许哪些网站的跨域请求
              - "http://localhost:8090"
              - "http://www.leyou.com"
            allowedMethods: # 允许哪些ajax请求方式
              - "GET"
              - "POST"
              - "DELETE"
              - "PUT"
              - "OPTIONS"
            allowedHeaders: "*" # 允许的头信息
            allowCredentials: true # 是否允许cookie
            maxAge: 360000 # 跨域检测有效期,有效期内直接放行,不再检查

7. Docker

Docker是一个快速交付和运行应用的技术,解决了两个部署时的问题:

  1. 大型项目依赖关系复杂,各个组件兼容性冲突:将应用+依赖+函数库+配置一起打包,形成可移植的只读镜像实例化成容器,利用沙箱机制相互隔离。
  2. 开发、测试、生产环境差异:镜像中包含了系统函数库,使应用不依赖系统,直接操作Linux内核,所以可以在任意Linux系统中运行。

Tips: Linux操作系统=硬件+Linux内核+系统应用(Ubuntu, centOS…),容器跳过了系统应用直接操作内核。

对比虚拟机,虚拟机是在操作系统中模拟硬件,然后运行另一个系统。虚拟机多了一层从系统,并且Hypervisor会对硬件资源也进行虚拟化,而容器Docker会直接使用宿主机的硬件资源。
在这里插入图片描述
基本命令:

  • docker images
  • docker pull/push
  • docker rmi 删除镜像
  • docker save/load
  • docker ps/rm/logs/exec
    • 查看、删除容器
    • logs name 日志(-f 持续跟踪)
    • exec -it name bash it创建容器输入输出终端,bash进入容器后执行Linux终端命令,此时进入容器内部Linux环境,具体文件位置查询官方文档
  • docker run 根据文档配置参数
  • docker --help 核心命令☺

Tips: 小坑——容器stop之后仍然存在(等待着重新启动),所以不能修改,只有rm之后重新创建容器才行。

7.1 数据卷

镜像中一般不包括vim,所以对容器的修改很麻烦,因此docker提供了数据卷修改的方式,即数据卷同步映射到容器内文件——高级的说法就是容器与数据解耦。通过docker volume操作数据卷,命令如图:
在这里插入图片描述
创建的数据卷会映射到宿主机的固定位置,如图所示:
在这里插入图片描述
通过-v 数据卷:容器内地址挂载数据卷,不存在的数据卷自动创建,然后直接操作数据卷即可:

docker run --name ng -p 80:80 -d -v html:/usr/share/nginx/html nginx

7.2 自定义镜像

通过分层提高镜像的复用性
在这里插入图片描述
通过dockerfile自定义镜像,其本质是描述镜像构建过程的指令文件,构建过程依次是:
在这里插入图片描述
其中FROM-ENV-RUN是安装环境过程,可以利用现有的基础镜像简化(如java-alpine)

7.3 docker compose

docker compose基于compose文件快速部署分布式应用,无需手动一个个创建和运行容器,将dockfile转换成yaml格式的配置文件指定每个容器如何运行。
——了解即可,Portainer,k8s才是主流
在这里插入图片描述

7.4 镜像仓库

搭建docker私服:修改daemon.json—重命名镜像,以服务器地址为前缀—push/pull


8. 消息队列

为什么需要消息队列(异步通信),同步调用的缺点:

  • 耦合度高,每次业务调整都需要修改代码
  • 调用链过长,导致性能下降
  • 资源浪费,每个服务在等待过程中占用资源
  • 级联问题,一崩全崩

异步调用的流程:
在这里插入图片描述
异步调用的优点:

  • 削峰限流
  • 耦合度低
  • 吞吐量提升

带来的问题:

  • 可靠性依赖于消息队列
  • 业务解耦,不易跟踪

常用消息队列对比:
在这里插入图片描述

8.1 MQ安装

15672为管理端口,5672为消息端口
在这里插入图片描述
默认账号密码均为guest
rabbitMQ的结构:

  • channel:操作MQ的工具
  • exchange:路由消息到队列中
  • queue:缓存消息
  • virtual host:虚拟主机,是对队列和交换机的逻辑分组,可以创建多个,互相隔离

在这里插入图片描述
消息队列基本例子:

// 生产者
public class producerTest {
    @Test
    public void testSend() throws IOException, TimeoutException {
        // 创建连接参数
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("114.116.88.244");
        factory.setPort(5672);
        factory.setVirtualHost("/");
        factory.setUsername("guest");
        factory.setPassword("guest");
        // 连接
        Connection connection = factory.newConnection();
        // 创建通道
        Channel channel = connection.createChannel();
        // 创建队列
        String queueName = "simple.queue";
        channel.queueDeclare(queueName, false, false, false, null);
        // 发送消息
        String message = "hello world!";
        channel.basicPublish("", queueName, null, message.getBytes());
        // 后续操作
        System.out.println("消息【" + message + "】发送成功!");
        channel.close();
        connection.close();
    }
}

// 消费者
public class ConsumerTest {
    @Test
    public void consumer() throws IOException, TimeoutException {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("114.116.88.244");
        factory.setPort(5672);
        factory.setVirtualHost("/");
        factory.setUsername("guest");
        factory.setPassword("guest");
        // 连接
        Connection connection = factory.newConnection();
        // 创建通道,避免消费者先到,不会重复创建
        Channel channel = connection.createChannel();
        // 创建队列
        String queueName = "simple.queue";
        channel.queueDeclare(queueName, false, false, false, null);
        // 消费消息
        channel.basicConsume(queueName,true, new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String message = new String(body);
                System.out.println("消息【" + message + "】获取成功!");
            }
        });
        // 后续处理,消费消息时只是使回调函数与通道绑定,然后便继续执行
        System.out.println("等待接收消息。。。");
    }
}

// 输出
等待接收消息。。。
消息【hello world!】获取成功!

可见消费者是通过回调函数接收消息,不影响后续流程。

注意: rabbitmq具有消费预取机制,perfetch=100,即每个队列先平均拿100条消息,处理完再拿,会影响自定义的分配机制,可以修改为1。

消息队列基本流程,生产者和消费者都需要声明队列,避免队列不存在:
在这里插入图片描述

8.2 SpringAMQP

AMQP:高级消息队列协议,用于应用程序间传递业务消息的开放标准;

SpringAMQP:基于AMQP的API规范,spring-amqp为基础抽象,spring-rabbit为底层实现

发布订阅模型:
在这里插入图片描述

注: 消息发送和接收必须使用相同的序列化方式,默认时jdk序列化,推荐改为json.


9. ElasticSearch

一个开源的分布式搜索引擎,用于搜索、日志统计分析、系统监控等功能,是目前最流行的搜索引擎技术。
在这里插入图片描述
elastic search搜索过程:
在这里插入图片描述
与MySQL的区别:

  • MySQL适合事务性操作,确保数据的安全和一致性;
  • ES适合海量数据的搜索、分析、计算;
  • 即搜索业务用ES,存储业务用MySQL
    在这里插入图片描述

9.1 ES的安装

黑马yyds!

9.1.1 部署单点es
  1. 创建网络

因为我们还需要部署kibana容器,因此需要让es和kibana容器互联。这里先创建一个网络:

docker network create es-net
  1. 加载镜像

这里我们采用elasticsearch的7.17.1版本的镜像,

docker pull elasticsearch:7.17.1

同理还有kibana也需要这样做。

  1. 运行

运行docker命令,部署单点es:

docker run -d \
	--name es \
    -e "ES_JAVA_OPTS=-Xms512m -Xmx512m" \
    -e "discovery.type=single-node" \
    -v es-data:/usr/share/elasticsearch/data \
    -v es-plugins:/usr/share/elasticsearch/plugins \
    --privileged \
    --network es-net \
    -p 9200:9200 \
    -p 9300:9300 \
elasticsearch:7.17.1

命令解释:

  • -e "cluster.name=es-docker-cluster":设置集群名称
  • -e "http.host=0.0.0.0":监听的地址,可以外网访问
  • -e "ES_JAVA_OPTS=-Xms512m -Xmx512m":内存大小
  • -e "discovery.type=single-node":非集群模式
  • -v es-data:/usr/share/elasticsearch/data:挂载逻辑卷,绑定es的数据目录
  • -v es-logs:/usr/share/elasticsearch/logs:挂载逻辑卷,绑定es的日志目录
  • -v es-plugins:/usr/share/elasticsearch/plugins:挂载逻辑卷,绑定es的插件目录
  • --privileged:授予逻辑卷访问权
  • --network es-net :加入一个名为es-net的网络中
  • -p 9200:9200:端口映射配置

在浏览器中输入:http://ip:9200 即可看到elasticsearch的响应结果:

9.1.2 部署kibana

kibana可以给我们提供一个elasticsearch的可视化界面,便于我们学习。

  1. 部署

运行docker命令,部署kibana

docker run -d \
--name kibana \
-e ELASTICSEARCH_HOSTS=http://es:9200 \
--network=es-net \
-p 5601:5601  \
kibana:7.17.1
  • --network es-net :加入一个名为es-net的网络中,与elasticsearch在同一个网络中
  • -e ELASTICSEARCH_HOSTS=http://es:9200":设置elasticsearch的地址,因为kibana已经与elasticsearch在一个网络,因此可以用容器名直接访问elasticsearch
  • -p 5601:5601:端口映射配置

kibana启动一般比较慢,需要多等待一会,可以通过命令:

docker logs -f kibana

查看运行日志,在浏览器输入地址访问:http://192.168.150.101:5601,即可看到结果

9.1.3 ik分词

ES自带分词器对中文支持很差,所以需要额外安装ik分词器,ik分词器两种模式:

  • ik_smart:最少切分
  • ik_max_word:最细切分
POST _analyze
{
  "text": ["ES真好用!"],
  "analyzer": "ik_max_word"
}

但分词器本质上是通过自己的字典进行匹配,会有大量不包含的词语,所以可以自己配置字典,在IKAnalyzer.cfg.xml中配置

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
	<comment>IK Analyzer 扩展配置</comment>
	<!--用户可以在这里配置自己的扩展字典 -->
	<entry key="ext_dict"></entry>
	 <!--用户可以在这里配置自己的扩展停止词字典-->
	<entry key="ext_stopwords"></entry>
	<!--用户可以在这里配置远程扩展字典 -->
	<!-- <entry key="remote_ext_dict">words_location</entry> -->
	<!--用户可以在这里配置远程扩展停止词字典-->
	<!-- <entry key="remote_ext_stopwords">words_location</entry> -->
</properties>

9.2 ES的操作

es中mapping的使用,ES不允许修改索引库,只能添加字段:
在这里插入图片描述
在这里插入图片描述
索引库操作:
在这里插入图片描述

文档操作:
在这里插入图片描述

结语

到此对于微服务的各种中间件有了基础的认识,此教程的ES详细使用和高级篇将在后续项目的实践中进行总结,开启微服务项目!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值