SpringCloud完整教程

news2024/11/14 16:40:58

一下内容为本人在听黑马程序员的课程时整理的

  • 微服务技术栈在这里插入图片描述

在这里插入图片描述

⎛⎝≥⏝⏝≤⎛⎝ ⎛⎝≥⏝⏝≤⎛⎝ ⎛⎝≥⏝⏝≤⎛⎝ ⎛⎝≥⏝⏝≤⎛⎝

1、微服务框架

1.1、认识微服务

1.1.1、服务架构演变

**单体架构:**将业务的所有功能集中在一个项目中开发,打包成一个包部署

优点:

  • 架构简单
  • 部署成本低

缺点:

  • 耦合度高

**分布式架构:**根据业务功能对系统进行拆分,每个业务模块作为独立项目开发,称为一个服务

优点:

  • 降低服务耦合
  • 有利于服务升级扩展

服务治理:

分布式架构要考虑的问题:

  • 服务拆分力度如何?
  • 服务集群地址如何维护?
  • 服务之间如何实现远程调用?
  • 服务健康如何感知?

微服务

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

  • 单一职责:微服务拆分粒度更小,每一个服务都对应唯一的业务能力,做到单一职责,避免重复业务开发
  • 面向服务:微服务对外暴露业务接口
  • 自治:团队独立、技术独立、数据独立、部署独立
  • 隔离性强:服务调用做好隔离、容错、降级,避免出现级联问题

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

微服务结构:

微服务这种方案需要技术框架来落地,全球的互联网公司都在积极尝试自己的微服务落地技术。在国内最知名的就是SpringCloud和阿里巴巴的Dubbo

在这里插入图片描述

在这里插入图片描述

1.1.2、SpringCloud

SpringCloud

  • SpringCloud是目前国内使用最广泛的微服务框架
  • SpringCloud集成了各种微服务功能组件,并基于SpringBoot实现了这些组件的自动装配,从而提供了良好的开箱即用体验

在这里插入图片描述

  • SpringCloud与SpringBoot的版本兼容关系如下:

在这里插入图片描述

1.2、服务拆分即远程调用

服务拆分注意事项:

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

工程结构有两种:

  • 独立Project
  • Maven聚合

案例:拆分服务

  • 将hm-service中与商品管理相关功能拆分到一个微服务module中,命名为item-service
  • 将hm-service中与购物车有关的功能拆分到一个微服务module中,命名为cart-service

远程调用

![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img-home.csdnimg.cn/images/20230724024159.png?origin_url=C%3A%5CUsers%5C13478%5CDesktop%5C%E8%87%AA%E5%AD%A6%E6%88%90%E6%89%8D%5CJavaWeb%5C%E5%BE%AE%E6%9C%8D%E5%8A%A1%E5%BC%在这里插入图片描述
Spring给我们提供了一个RestTemplate工具,可以方便的实现Http请求的发送。使用步骤如下:

1、注入RestTemplate到Spring容器

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

2、发起远程调用

public <T> ResponseEntity<T> exchange(
	Sring url, //请求路径
    HttpMethod method, //请求方式
    @Nullable HttpEntity<?> requestEntity, //请求实体,可以为空
    Class<T> responseType, //返回值类型
    Map<String,?> urlVariables //请求参数
)

1.3、服务治理

1.3.1、注册中心

服务治理中的三个角色分别是什么?

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

在这里插入图片描述

消费者如何知道提供者的地址号?

  • 服务提供者会在启动时注册自己信息到注册中心,消费者可以从注册中心订阅和拉取服务信息

消费者如何得知服务状态变更?

  • 服务提供者通过心跳机制向注册中心报告自己的健康状态,当心跳异常时注册中心会将异常服务剔除,并通知订阅了该服务的消费者

当提供者有多个实例时,消费者应该选择哪一个?

  • 消费者可以通过负载均衡算法,从多个实例中选择一个
1.3.2、Nacos注册中心

Nacos是目前国内企业中占比最多的注册中心组件。它是阿里巴巴的产品,目前已经加入SpringCloudAlibaba中

在这里插入图片描述

部署Nacos:‍‌‬⁠⁠‌‌⁠‍⁠‍‌‌‍‌‬‌day03-微服务01 - 飞书云文档 (feishu.cn)

1.3.3、服务注册

服务注册步骤:

1、引入nacos discovery依赖:

<!--nacos 服务注册发现-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

2、配置nacos

cloud:
nacos:
  discovery:
    server-addr: 192.168.88.132:8848
1.3.4、服务发现

消费者需要连接nacos以拉取和订阅服务,因此服务发现的前两步与服务注册时一样的,后面再加上服务调用即可:

  1. 引入nacos discovery依赖
  2. 配置nacos地址
  3. 服务发现
private final DiscoveryClient discoveryClient;

private void handleCartItems(List<CartVO> vos){
    //1.根据服务名称,拉取服务的实例列表
    List<ServiceInstance> instances = discoveryClient.getInstances("item-service");
    //2.负载均衡,挑选一个实例
    ServiceInstance instance = instances.get(RandomUtil.randomInt(instances.size()));
    //3.获取实例的IP和端口
    URU uri = instance.getUri();
    //....
}

1.4、OpenFeign

1.4.1、快速入 门

我们利用Nacos实现了服务的治理,利用RestTemplate实现了服务的远程调用。但是远程调用的代码太复杂了:

而且这种调用方式,与原本的本地方法调用差异太大,编程时的体验也不统一,一会儿远程调用,一会儿本地调用。

因此,我们必须想办法改变远程调用的开发模式,让远程调用像本地方法调用一样简单。而这就要用到OpenFeign组件了。

其实远程调用的关键点就在于四个:

  • 请求方式
  • 请求路径
  • 请求参数
  • 返回值类型

OpenFeign是一个声明式的http客户端,是SpringCloud在Eureka公司开源的Feign基础上改造而来的

其作用就是基于SpringMVC的常见注解,帮我们优雅的实现http请求的发送

OpenFeign已经被SpringCloud自动装配,实现非常简单

1、引入依赖

  <!--openFeign-->
  <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-openfeign</artifactId>
  </dependency>
  <!--负载均衡器-->
  <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-loadbalancer</artifactId>
  </dependency>

2、通过@EnableFeignClients注解,启用OpenFeign功能

@EnabeleFeignClients
@SpringBootApplication
public class CartApplication{  /....}

3、编写FeignClient

@FeignClient("item-service")
public interface ItemClient {

    @GetMapping("/items")
    List<ItemDTO> queryItemByIds(@RequestParam("ids") Collection<Long> ids);
}

4、使用FeignClient,实现远程调用

   private void handleCartItems(List<CartVO> vos) {
        // 1.获取商品id
        Set<Long> itemIds = vos.stream().map(CartVO::getItemId).collect(Collectors.toSet());
        //2.查询商品
        List<ItemDTO> items = itemClient.queryItemByIds(itemIds);
        if (CollUtils.isEmpty(items)){
            return;
        }
        // 3.转为 id 到 item的map
        Map<Long, ItemDTO> itemMap = items.stream().collect(Collectors.toMap(ItemDTO::getId, Function.identity()));
        // 4.写入vo
        for (CartVO v : vos) {
            ItemDTO item = itemMap.get(v.getItemId());
            if (item == null) {
                continue;
            }
            v.setNewPrice(item.getPrice());
            v.setStatus(item.getStatus());
            v.setStock(item.getStock());
        }
    }
1.4.2、连接池

OpenFeign对Http请求做了优雅的伪装,不过其底层发起http请求,依赖于其他的框架。这些框架可以自己选择,包括一下三种:

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

OpenFeign整合OKHttp的步骤:

1、引入依赖

<!--OK http 的依赖 -->
<dependency>
  <groupId>io.github.openfeign</groupId>
  <artifactId>feign-okhttp</artifactId>
</dependency>

2、开启连接池功能

feign:
  okhttp:
    enabled: true # 开启OKHttp功能
1.4.3、最佳实践

当定义的FeignClient不在SpringBootApplication的扫描包范围时,这些FeignClient无法使用。有两种解决方法:

方式一:指定FeignClient所在的包

@EnableFeignClients(basePackages = "com.hmall.api.client")

方式二:指定FeignClient字码节

@EnableFeignClients(clients={UserClient.class})
1.4.4、日志

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

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

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

要自定义级别需要声明一个类型为Logger.Level的Bean,在其中定义日志级别:

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

但此时这个Bean并未生效,想要配置某个FeignClient的日志,可以在@FeignClient注解中声明

@FeignClient(value = "item-service",configuration = DefaultFeignConfig.class)

如果想要全局配置,让所有的FeignClient都按照这个日志配置,则需要再@EnableFeignClients注解中声明:

@EnableFeignClients(defaultConfiguration = DefaultFeignConfig.class)

2、微服务-网关及配置管理

2.1、网关

网关就是网络的开关,负责请求的路由、转发、身份校验

在这里插入图片描述

在SpringCloud中网关的实现包括两种:

1、Spring Cloud Gateway

  • Spring官方出品
  • 基于WebFlux响应式编程
  • 无需调优即可获得优异性能

2、Netfilx Zuul

  • Netfilx出品
  • 基于Servlet的阻塞式编程
  • 需要调优才能获得与SpringCloudGateway类似的性能

2.2、网关路由

2.2.1、快速入门

1、创建新模块

2、引入网关依赖

<?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>hmall</artifactId>
        <groupId>com.heima</groupId>
        <version>1.0.0</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>hm-gateway</artifactId>

    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
    </properties>
    <dependencies>
        <!--common-->
        <dependency>
            <groupId>com.heima</groupId>
            <artifactId>hm-common</artifactId>
            <version>1.0.0</version>
        </dependency>
        <!--网关-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <!--nacos discovery-->
        <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-loadbalancer</artifactId>
        </dependency>
    </dependencies>
    <build>
        <finalName>${project.artifactId}</finalName>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

3、编写启动类

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

4、配置路由规则

在这里插入图片描述

server:
  port: 8080
spring:
  application:
    name: gateway
  cloud:
    nacos:
      server-addr: 192.168.88.132:8848
    gateway:
      routes:
        - id: item # 路由规则id,自定义,唯一
          uri: lb://item-service # 路由的目标服务,lb代表负载均衡,会从注册中心拉取服务列表
          predicates: # 路由断言,判断当前请求是否符合当前规则,符合则路由到目标服务
            - Path=/items/**,/search/** # 这里是以请求路径作为判断规则
        - id: cart
          uri: lb://cart-service
          predicates:
            - Path=/carts/**
        - id: user
          uri: lb://user-service
          predicates:
            - Path=/users/**,/addresses/**
        - id: trade
          uri: lb://trade-service
          predicates:
            - Path=/orders/**
        - id: pay
          uri: lb://pay-service
          predicates:
            - Path=/pay-orders/**

2.2.2、路由属性

网关路由对应的Java类型是RouteDefinition,其中常见的属性有:

  • id:路由唯一标示
  • uri:路由目标地址
  • predicates:路由断言,判断请求是否符合当前路由
  • filters:路由过滤器,对请求或响应做特殊处理

Spring提供了12种基本的RoutePredicateFactory实现:

路由过滤器

名称说明示例
After是某个时间点后的请求- After=2037-01-20T17:42:47.789-07:00[America/Denver]
Before是某个时间点之前的请求- Before=2031-04-13T15:14:47.433+08:00[Asia/Shanghai]
Between是某两个时间点之前的请求- Between=2037-01-20T17:42:47.789-07:00[America/Denver], 2037-01-21T17:42:47.789-07:00[America/Denver]
Cookie请求必须包含某些cookie- Cookie=chocolate, ch.p
Header请求必须包含某些header- Header=X-Request-Id, \d+
Host请求必须是访问某个host(域名)- Host=.somehost.org,.anotherhost.org
Method请求方式必须是指定方式- Method=GET,POST
Path请求路径必须符合指定规则- Path=/red/{segment},/blue/**
Query请求参数必须包含指定参数- Query=name, Jack或者- Query=name
RemoteAddr请求者的ip必须是指定范围- RemoteAddr=192.168.1.1/24
weight权重处理

2.3、网关登录校验

在这里插入图片描述

网关请求处理流程

在这里插入图片描述

2.3.1、自定义过滤器

网关过滤器有两种,分别是:

  • GatewayFilter:路由过滤器,作用于任意指定的路由;默认不生效,要配置到路由后生效
  • GlobalFilter:全局过滤器,作用范围是所有路由;声明后自动生效

两种过滤器的过滤方法签名完全一致

GlobalFilter

自定义GlobalFilter比较简单,直接实现GlobalFilter接口即可

@Component
public class MyGlobalFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // TODO 模拟登录校验逻辑
        ServerHttpRequest request = (ServerHttpRequest) exchange.getRequest();
        HttpHeaders headers = request.getHeaders();
        System.out.println("headers =" + headers);
        //放行
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return 0;
    }
}
2.3.2、实现登录校验

需求:在网关中基于过滤器实现登录校验功能

package com.hmall.gateway.filters;

import com.hmall.common.exception.UnauthorizedException;
import com.hmall.common.utils.CollUtils;
import com.hmall.gateway.config.AuthProperties;
import com.hmall.gateway.util.JwtTool;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.List;

@Component
@RequiredArgsConstructor
@EnableConfigurationProperties(AuthProperties.class)
public class AuthGlobalFilter implements GlobalFilter, Ordered {

    private final JwtTool jwtTool;

    private final AuthProperties authProperties;

    private final AntPathMatcher antPathMatcher = new AntPathMatcher();

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 1.获取Request
        ServerHttpRequest request = exchange.getRequest();
        // 2.判断是否不需要拦截
        if(isExclude(request.getPath().toString())){
            // 无需拦截,直接放行
            return chain.filter(exchange);
        }
        // 3.获取请求头中的token
        String token = null;
        List<String> headers = request.getHeaders().get("authorization");
        if (!CollUtils.isEmpty(headers)) {
            token = headers.get(0);
        }
        // 4.校验并解析token
        Long userId = null;
        try {
            userId = jwtTool.parseToken(token);
        } catch (UnauthorizedException e) {
            // 如果无效,拦截
            ServerHttpResponse response = exchange.getResponse();
            response.setRawStatusCode(401);
            return response.setComplete();
        }

        // TODO 5.如果有效,传递用户信息
        System.out.println("userId = " + userId);
        // 6.放行
        return chain.filter(exchange);
    }

    private boolean isExclude(String antPath) {
        for (String pathPattern : authProperties.getExcludePaths()) {
            if(antPathMatcher.match(pathPattern, antPath)){
                return true;
            }
        }
        return false;
    }

    @Override
    public int getOrder() {
        return 0;
    }
}
2.3.3、网关传递用户

在这里插入图片描述

需求:修改gateway模块中的登录校验拦截器,在校验成功后保存到下游请求头中。

提示:要修改转发到微服务的请求,需要用到ServerWebExchange类提供的API,示例如下:

exchange.mutate() //mutate就是对下游请求做更改
    .request(builder->builder.header("user-info",userInfo))
    .build();

需求:由于每个微服务都可能有获取登录用户的需求,因此我们直接在hm-common模块定义拦截器,这样微服务只需要引入依赖即可生效,无需重复编写

首先,修改登录校验拦截器的处理逻辑,保存用户信息到请求头中:

// TODO 5.如果有效,传递用户信息
String userInfo = userId.toString();
ServerWebExchange ex = exchange.mutate()
        .request(builder -> builder.header("user-info",userInfo))
        .build();

UserInfoInterceptor

public class UserInfoInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //1、获取登录用户信息
        String userInfo = request.getHeader("user-info");
        //2、判断是否获取了用户,如果有,存入ThreadLocal
        if(StrUtil.isNotBlank(userInfo)){
            UserContext.setUser(Long.valueOf(userInfo));
        }
        //3、放行
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        //清理用户
        UserContext.removeUser();
    }
}

MvcConfig

@Configuration
@ConditionalOnClass(DispatcherServlet.class)
public class MvcConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new UserInfoInterceptor());
    }
}

不过,需要注意的是,这个配置类默认是不会生效的,因为它所在的包是com.hmall.common.config,与其它微服务的扫描包不一致,无法被扫描到,因此无法生效。

基于SpringBoot的自动装配原理,我们要将其添加到resources目录下的META-INF/spring.factories文件中:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.hmall.common.config.MyBatisConfig,\
  com.hmall.common.config.MvcConfig

之前我们无法获取登录用户,所以把购物车服务的登录用户写死了,现在需要恢复到原来的样子。

找到cart-service模块的com.hmall.cart.service.impl.CartServiceImpl

    @Override
    public List<CartVO> queryMyCarts() {
        // 1.查询我的购物车列表
        List<Cart> carts = lambdaQuery().eq(Cart::getUserId, UserContext.getUser()).list();
        if (CollUtils.isEmpty(carts)) {
            return CollUtils.emptyList();
        }

        // 2.转换VO
        List<CartVO> vos = BeanUtils.copyList(carts, CartVO.class);

        // 3.处理VO中的商品信息
        handleCartItems(vos);

        // 4.返回
        return vos;
    }
2.3.4、OpenFeign传递用户

微服务项目中的很多业务要多个微服务共同合作完成,而这个过程也需要传递登录用户信息,例如:

在这里插入图片描述

OPenFeign中提供了一个拦截器接口,所有有OPenFeign发起的请求都会先调用拦截器处理请求

public class DefaultFeignConfig {

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

    @Bean
    public RequestInterceptor useInfoRequestInterceptor(){
        return new RequestInterceptor() {
            @Override
            public void apply(RequestTemplate requestTemplate) {
                Long userId = UserContext.getUser();
                if (userId!=null){
                    requestTemplate.header("user-info",userId.toString());
                }
            }
        };
    }

}

2.4、配置管理

  • 微服务重复配置过多,维护成本高
  • 业务配置经常变动,每次都要重启服务
  • 网关路由配置写死,如果变更要重启网关

在这里插入图片描述

2.4.1、配置共享

添加一些共享配置到Nacos中,包括Jdbc,MybatisPlus、日志、Swagger、OPenFeign等配置

在这里插入图片描述

2.4.2、拉取共享配置

基于NacosConfig拉取共享配置代替微服务的本地配置

在这里插入图片描述

1、引入依赖

  <!--nacos配置管理-->
  <dependency>
      <groupId>com.alibaba.cloud</groupId>
      <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
  </dependency>
  <!--读取bootstrap文件-->
  <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-bootstrap</artifactId>
  </dependency>

2、新建bootstrap.yaml

spring:
  application:
    name: cart-service # 服务名称
  profiles:
    active: dev
  cloud:
    nacos:
      server-addr: 192.168.150.101 # nacos地址
      config:
        file-extension: yaml # 文件后缀名
        shared-configs: # 共享配置
          - dataId: shared-jdbc.yaml # 共享mybatis配置
          - dataId: shared-log.yaml # 共享日志配置
          - dataId: shared-swagger.yaml # 共享日志配置
2.4.3、配置热更新

配置热更新:当修改配置文件中的配置时,微服务无需重启即可使配置生效

前提条件:

1、nacos中要有一个与微服务名有关的配置文件

在这里插入图片描述

2、微服务中要以特定方式读取需要热更新的配置属性(推荐第一种)

在这里插入图片描述

案例:实现购物车添加商品上限的配置热部署

需求:购物车的限定数量目前是写死在业务中的,将其改为读取配置文件属性,并将属性交给Nacos管理,实现热更新

@Data
@Component
@ConfigurationProperties(prefix = "hm.cart")
public class CartProperties {
    private Integer maxItems;
}

CartServiceImpl


private CartProperties cartProperties;

private void checkCartsFull(Long userId) {
        int count = Math.toIntExact(lambdaQuery().eq(Cart::getUserId, userId).count());
        if (count >= cartProperties.getMaxItems()) {
            throw new BizIllegalException(
                    StrUtil.format("用户购物车课程不能超过{}", cartProperties.getMaxItems()));
        }
    }

在这里插入图片描述

测试:购物车中只能添加一个商品

在这里插入图片描述

2.4.4、动态路由

要实现动态路由首先要将路由配置保存到Nacos,当Nacos中的路由配置变更时,推送最新配置到网关,实时更新网关中的路由信息

我们要完成两件事情:

1、监听Nacos配置变更的消息

在Nacos管网中给出了手动监听Nacos配置变更的SDK:Java SDK (nacos.io)

   private final NacosConfigManager nacosConfigManager;

    @PostConstruct
    public void  initRouteConfigListener() throws NacosException {
        //1.项目启动时,先拉取一次配置,并且添加配置监听器
        String configInfo = nacosConfigManager.getConfigService()
                .getConfigAndSignListener(dataId, group, 5000, new Listener() {
            @Override
            public Executor getExecutor() {
                return null;
            }

            @Override
            public void receiveConfigInfo(String s) {
                //2.监听到配置变更,需要去更新路由表
            }
        });
        //3.第一次读取到配置,也需要更新到路由表
        updateConfigInfo(configInfo);
    }

2、当配置变更时,将最新的路由信息更新到网关路由表

监听到路由信息后,可以利用RouteDefinitionWriter来更新路由表

public interface RouteDefinitionWriter{
    //更新理由到路由表,如果路由id重复,则会覆盖旧的路由
    Mono<Void> save(Mono<RouteDefinition> route);
    //根据路由id删除某个路由
    Mono<void> delete(Mono<String> routeId);
}

路由配置语法

为了方便解析从Nacos读取到底路由配置,推荐使用json格式的路由配置,模块如下:

在这里插入图片描述

public void updateConfigInfo(String configInfo){
    log.debug("监听到路由配置信息"+configInfo);
    //1.解析配置信息,转为RouteDefinition
    List<RouteDefinition> routeDefinitions = JSONUtil.toList(configInfo, RouteDefinition.class);
    //2.更新前先删除旧的路由表
    for (String routeId : routeIds) {
        writer.delete(Mono.just(routeId)).subscribe();
    }
    routeIds.clear();
    //3.判断是否有新的路由要更新
    if (CollUtils.isEmpty(routeDefinitions)){
        //无新路由配置,直接结束
        return;
    }
    //4.更新路由
    routeDefinitions.forEach(routeDefinition -> {
        //更新路由
        writer.save(Mono.just(routeDefinition)).subscribe();
        //记录路由id,方便将来删除
        routeIds.add(routeDefinition.getId());
    });
}

在Nacos中新增配置

在这里插入图片描述

[
    {
        "id": "item",
        "predicates": [{
            "name": "Path",
            "args": {"_genkey_0":"/items/**", "_genkey_1":"/search/**"}
        }],
        "filters": [],
        "uri": "lb://item-service"
    },
    {
        "id": "cart",
        "predicates": [{
            "name": "Path",
            "args": {"_genkey_0":"/carts/**"}
        }],
        "filters": [],
        "uri": "lb://cart-service"
    },
    {
        "id": "user",
        "predicates": [{
            "name": "Path",
            "args": {"_genkey_0":"/users/**", "_genkey_1":"/addresses/**"}
        }],
        "filters": [],
        "uri": "lb://user-service"
    },
    {
        "id": "trade",
        "predicates": [{
            "name": "Path",
            "args": {"_genkey_0":"/orders/**"}
        }],
        "filters": [],
        "uri": "lb://trade-service"
    },
    {
        "id": "pay",
        "predicates": [{
            "name": "Path",
            "args": {"_genkey_0":"/pay-orders/**"}
        }],
        "filters": [],
        "uri": "lb://pay-service"
    }
]

路由表的更新有一定的延迟

3、服务保护和分布式事务

3.1、雪崩问题

微服务调用链路中的某个服务故障,引起整个链路中所有微服务不可用,这就是雪崩

雪崩问题产生的原因是什么?

  • 微服务互相调用,服务提供者出现故障或阻塞
  • 服务调用者没有做好异常处理,导致自身故障
  • 调用链中的所有服务级联失败,导致整个集群故障

解决问题的思路?

  • 尽量避免服务出现故障或阻塞
    • 保证代码的健壮性
    • 保证网络畅通
    • 能应对较高的并发需求

3.2、解决方案

3.2.1、请求限流

限制访问微服务的请求的并发量,避免服务因流量激增出现故障

在这里插入图片描述

3.2.2、线程隔离

线程隔离:也叫做舱壁模式,模拟船舱隔板的防水原理。通过限制每个业务能使用的线程数量而将故障业务隔离,避免故障扩展。

在这里插入图片描述

3.2.3、服务断熔

服务断熔:由断路器统计请求的异常比例或慢调用比例,如果超出阈值则会熔断该业务,则拦截该接口的请求

熔断期间,所有的请求快速失败,全都做fallback逻辑

在这里插入图片描述

3.3.4、服务保护技术
** **SentinelHystrix
线程隔离信号量隔离线程池隔离/信号量隔离
熔断策略基于慢调用比例或异常比例基于异常比率
限流基于 QPS,支持流量整形有限的支持
Fallback支持支持
控制台开箱即用,可配置规则、查看秒级监控、机器发现等不完善
配置方式基于控制台,重启后失效基于注解或配置文件,永久生效

3.3、Sentinel

3.3.1、初识Sentinel

Sentinel是阿里巴巴开源的一款微服务流量控制组件

官网:https://b11et3un53m.feishu.cn/wiki/QfVrw3sZvihmnPkmALYcUHIDnff#YRqVd7bn8odK9mx5F1tccqcrn2l

使用步骤:

1、下载jar包

在这里插入图片描述

2、运行

java -Dserver.port=8090 -Dcsp.sentinel.dashboard.server=localhost:8090 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard.jar

在浏览器中输入localhost:8090
在这里插入图片描述

3.3.2、微服务整合

我们子啊cart-service模块中整合sentinel,连接sentinel-dashboard控制台,步骤如下:

1、引入sentinel依赖

<!--sentinel-->
<dependency>
    <groupId>com.alibaba.cloud</groupId> 
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

2、配置控制台

修改application.yaml文件,添加下面内容:

spring:
  cloud: 
    sentinel:
      transport:
        dashboard: localhost:8090

重启服务,建立与Sentinel的连接,在黑马商城中访问购物车的相关业务
在这里插入图片描述

3.3.3、簇点链路

簇点链路,就是单机调用链路。是一次请求进入服务后经过的每一个被Sentinel监控的资源链。默认Sentinel会监控SpringMVC的每一个EndPoint(http接口)。限流、熔断等都是针对簇点链路中的资源设置的。而资源名默认就是接口的请求路径:

在这里插入图片描述

RestFul风格的API请求一般都相同,这会导致簇点资源名称重复。因此我们要修改配置,把请求方式+请求路径作为簇点资源名称:

spring:
  cloud:
    sentinel:
      transport:
        dashboard: localhost:8090 #Sentinel的控制台地址
      http-method-specify: true #开启请求方式前缀

在这里插入图片描述

在这里插入图片描述

3.3.4、请求限流

在簇点链路后面点击流控按钮,即可对其做限流配置

Cloud.assets%5Cimage-20240808211321541.png&pos_id=img-2cbB1qiD-1723212019655)

在Jmeter中进行测试

在这里插入图片描述

如图,出现429代表实现限流
在这里插入图片描述

3.3.5、线程隔离

当商品服务出现阻塞或故障时,调用商品服务的购物车服务可能因此而被拖慢,甚至资源耗尽。所有必须限制购物车服务中查询商品这个业务的可用线程数,实现线程隔离

在Sentinel控制台中,会出现Feign接口的簇点资源,点击后面的流控按钮,即可配置线程隔离:

在这里插入图片描述

在ItemController中模拟业务延迟

 @ApiOperation("根据id批量查询商品")
    @GetMapping
    public List<ItemDTO> queryItemByIds(@RequestParam("ids") List<Long> ids){
        //模拟业务延迟
        ThreadUtil.sleep(500);
        return itemService.queryItemByIds(ids);
    }

限制购物车模块的tomcat线程数

server:
  port: 8082
  tomcat:
    threads:
      max: 25
    accept-count: 25
    max-connections: 100

在这里插入图片描述
在这里插入图片描述

可以看出大部分都异常了

3.3.6、Fallback

1、将FeignClient作为Sentinel的簇点资源:

feign:
  sentinel:
  	enabled: true

2、FeignClient的FallBack有两种配置方法:

  • 方式一:FallbackClass,无法对远程调用的异常做处理
  • 方式二:FallbackFactory,可以对远程调用的异常做处理,通常都会选这种
@Slf4j
public class ItemClientFallbackFactory implements FallbackFactory<ItemClient> {
    @Override
    public ItemClient create(Throwable cause) {
        return new ItemClient() {
            @Override
            public List<ItemDTO> queryItemByIds(Collection<Long> ids) {
                log.error("查询商品失败",cause);
                return CollUtils.emptyList();
            }

            @Override
            public void deductStock(List<OrderDetailDTO> items) {
                log.error("扣减商品库存失败",cause);
                throw new RuntimeException(cause);
            }
        };
    }
}

DefaultFeignConfig

  @Bean
    public ItemClientFallbackFactory itemClientFallbackFactory(){
        return new ItemClientFallbackFactory();
    }

ItemClient

@FeignClient(value = "item-service",fallbackFactory = ItemClientFallbackFactory.class)
public interface ItemClient {

}

在这里插入图片描述

在这里插入图片描述

3.3.7、服务熔断

​ 熔断是解决雪崩问题的主要手段。思路是由断路器统计服务调用异常的比例、慢请求比例,如果超出阈值则会熔断该服务。即拦截访问该服务的一切请求,而当服务恢复时,断路器会放行访问该服务的请求

在这里插入图片描述

在这里插入图片描述

3.4、分布式事务

​ 在分布式系统中,如果一个业务需要多个服务合作完成,而且每一个服务都有事务,多个事务必须同时成功或失败,这样的事务就是分布式事务。其中的每个服务就是一个分支事务。整个业务成为全局事务

下单业务,前端请求首先进入订单服务,创建订单并写入数据库。然后订单服务调用购物车服务和库存服务:

  • 购物车服务负责清理购物车信息
  • 库存服务负责扣减商品库存

在这里插入图片描述

3.4.1、初识Seata

Seata是2019年1月蚂蚁金服和阿里巴巴共同开源的分布式事务解决方案。致力于提供高性能和简单易用的分布式事务服务,为用户打造一站式的分布式解决方案

在这里插入图片描述

在这里插入图片描述
分布式事务解决思路:

在这里插入图片描述

Seata架构

Seata事务管理中有三个重要的角色:

  • TC(Transaction Cooridinator)-事务协调者:维护全局和分支事务的状态,协调全局事务提交或回滚
  • TM(Tansaction Manager)-事务管理器:定义全局事务的范围、开始全局事务、提交或回滚全局事务
  • RM(Resource Manager)-资源管理器:管理分支事务,与TC交谈以注册分支事务和报告分支事务的状态
3.4.2、部署TC服务

1、准备数据库表

导入数据库表seata

2、准备配置文件

导入seata目录到虚拟机

将nacos连接到网络

docker network connect hm-net nacos

3、Docker部署

docker run --name seata \
-p 8099:8099 \
-p 7099:7099 \
-e SEATA_IP=192.168.88.130 \
-v ./seata:/seata-server/resources \
--privileged=true \
--network hm-net \
-d \
seataio/seata-server:1.5.2

在浏览器中输入你的端口号:7099进入seata

在这里插入图片描述

3.4.3、微服务集成Seata

1、首先,要在项目中引入Seata依赖:

<!--统一配置管理-->
  <dependency>
      <groupId>com.alibaba.cloud</groupId>
      <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
  </dependency>
  <!--读取bootstrap文件-->
  <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-bootstrap</artifactId>
  </dependency>
  <!--seata-->
  <dependency>
      <groupId>com.alibaba.cloud</groupId>
      <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
  </dependency>

2、然后,在application.yml中添加配置,让微服务找到TC服务地址

seata:
  registry: # TC服务注册中心的配置,微服务根据这些信息去注册中心获取tc服务地址
    type: nacos # 注册中心类型 nacos
    nacos:
      server-addr: 192.168.88.130:8848 # nacos地址
      namespace: "" # namespace,默认为空
      group: DEFAULT_GROUP # 分组,默认是DEFAULT_GROUP
      application: seata-server # seata服务名称
      username: nacos
      password: nacos
  tx-service-group: hmall # 事务组名称
  service:
    vgroup-mapping: # 事务组与tc集群的映射关系
      hmall: "default"

在这里插入图片描述
如果jdk是11以上版本要在启动类上修改

在这里插入图片描述

运行成功:
在这里插入图片描述

3.4.4、XA模式

XA模范是X/Open组织定义的分布式事务处理(DTP,Distributed Transaction Processing)标准,XA规范描述了全局的TM与局部的RM之间的接口,几乎所有主流的关系型数据库都对XA规范提供了支持。Seata的XA模式如下:

一阶段工作:

  1. RM注册分支事务到TC
  2. RM执行分支业务sql但不提交
  3. RM报告执行状态到TC

二阶段工作:

  • TC检测各分支事务执行状态
    • a、如果都成功,通知所有RM提交事务
    • b、如果有失败,通知所有RM回滚事务
  • RM接收TC指令,提交或回滚事务

在这里插入图片描述

XA模式的有优点是什么?

  • 事务的强一致性,满足ACID原则
  • 常用数据库都支持,显示简单,并且没有代码侵入

XA模式的缺点是什么?

  • 因为一阶段需要锁定数据库资源,等待二阶段结束才释放,性能较差
  • 依赖关系型数据库实现事务

实现XA模式

Seata的starter已经完成了XA模式的自动配置,实现非常简单,步骤如下:

1、修改application.yml文件(每个参与事务的微服务),开启XA模式:

seata:
  data-source-proxy-mode: XA

2、给发起全局事务的入口方法添加@GlobalTransaction注解,本例中是OrderServiceImpl中的create方法:

在这里插入图片描述

3、重启服务并测试

3.4.5、AT模式

Seata主推的是AT模式,AT模式同样是分阶段提交的事务模型,不过缺弥补了XA模式中组员锁定周期过长的缺陷

阶段一RM的工作:

  • 注册分支事务
  • 记录undo-log(数据快照)
  • 执行业务sql并提交
  • 报告事务状态

阶段二提交时RM的工作:

  • 删除undo-log即可

阶段二回滚时RM的工作:

  • 根据undo-log恢复数据到更新前

在这里插入图片描述

简述AT模式与XA模式最大的区别是什么?

  • XA模式一阶段不提交事物,锁定资源;AT模式一阶段直接提交,不锁定资源。
  • XA模式依赖数据库机制实现回滚;AT模式利用数据快照实现数据回滚
  • XA模式强一致;AT模式最终

实现AT模式:

首先,添加资料汇总的Seata-at.sql到微服务对应的数据库中:

然后,修改application.yml文件,将事务模式修改为AT模式:

seata:
  data-source-proxy-mode: AT

913253" style=“zoom:50%;” />

如果jdk是11以上版本要在启动类上修改

[外链图片转存中…(img-QC5g9MwU-1723212019658)]

运行成功:

[外链图片转存中…(img-NEsbuGCP-1723212019658)]

3.4.4、XA模式

XA模范是X/Open组织定义的分布式事务处理(DTP,Distributed Transaction Processing)标准,XA规范描述了全局的TM与局部的RM之间的接口,几乎所有主流的关系型数据库都对XA规范提供了支持。Seata的XA模式如下:

一阶段工作:

  1. RM注册分支事务到TC
  2. RM执行分支业务sql但不提交
  3. RM报告执行状态到TC

二阶段工作:

  • TC检测各分支事务执行状态
    • a、如果都成功,通知所有RM提交事务
    • b、如果有失败,通知所有RM回滚事务
  • RM接收TC指令,提交或回滚事务

[外链图片转存中…(img-R3xeQSL1-1723212019658)]

XA模式的有优点是什么?

  • 事务的强一致性,满足ACID原则
  • 常用数据库都支持,显示简单,并且没有代码侵入

XA模式的缺点是什么?

  • 因为一阶段需要锁定数据库资源,等待二阶段结束才释放,性能较差
  • 依赖关系型数据库实现事务

实现XA模式

Seata的starter已经完成了XA模式的自动配置,实现非常简单,步骤如下:

1、修改application.yml文件(每个参与事务的微服务),开启XA模式:

seata:
  data-source-proxy-mode: XA

2、给发起全局事务的入口方法添加@GlobalTransaction注解,本例中是OrderServiceImpl中的create方法:
在这里插入图片描述

3、重启服务并测试

3.4.5、AT模式

Seata主推的是AT模式,AT模式同样是分阶段提交的事务模型,不过缺弥补了XA模式中组员锁定周期过长的缺陷

阶段一RM的工作:

  • 注册分支事务
  • 记录undo-log(数据快照)
  • 执行业务sql并提交
  • 报告事务状态

阶段二提交时RM的工作:

  • 删除undo-log即可

阶段二回滚时RM的工作:

  • 根据undo-log恢复数据到更新前

[外链图片转存中…(img-y05XX17y-1723212019659)]

简述AT模式与XA模式最大的区别是什么?

  • XA模式一阶段不提交事物,锁定资源;AT模式一阶段直接提交,不锁定资源。
  • XA模式依赖数据库机制实现回滚;AT模式利用数据快照实现数据回滚
  • XA模式强一致;AT模式最终

实现AT模式:

首先,添加资料汇总的Seata-at.sql到微服务对应的数据库中:

然后,修改application.yml文件,将事务模式修改为AT模式:

seata:
  data-source-proxy-mode: AT

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2042290.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

TypeScript 编译选项

编译TS 编译 .ts 文件&#xff1a; tsc app.ts执行命令tsc app.ts 可以把 单个文件 app.ts编译成 app.js。 这个命令需要每次编译时手动执行。 自动编译文件 tsc app.ts -w // 或者 tsc app.ts --watch通常 -w 或 --watch 指令用于启动 TypeScript 编译器的监视模式。 编译…

梅丽尔·斯特里普表演艺术家中心对外开放并恢复线下活动 体现了她的“卓越”

梅丽尔斯特里普表演艺术家中心对外开放并恢复线下活动 体现了她的“卓越” 2024-08-14 20:38 发布于&#xff1a;河北省 该中心将为美国演员工会和美国电视广播艺人协会的艺术家提供资源和机会&#xff0c;而且全部免费 同时命名的还有汤姆汉克斯和丽塔威尔逊放映室、妮可…

工业WiFi网关在工业领域的具体应用-天拓四方

工业WIFI网关在工业领域的应用已经变得极为广泛&#xff0c;它不仅是连接工业设备、传感器与云平台之间的桥梁&#xff0c;更是推动工业智能化和自动化的重要动力。下面将进一步详细介绍工业WiFi网关在工业领域的一些具体应用。 工业自动化生产&#xff1a; 在工业自动化生产…

算法力扣刷题记录 八十一【343. 整数拆分】

前言 动态规划第8篇。记录 八十一【343. 整数拆分】 一、题目阅读 给定一个正整数 n &#xff0c;将其拆分为 k 个 正整数 的和&#xff08; k > 2 &#xff09;&#xff0c;并使这些整数的乘积最大化。返回 最大乘积 。 示例 1: 输入: n 2 输出: 1 解释: 2 1 1, 1 …

货代企业转型海外仓,会面临哪些难点?

根据数据统计&#xff0c;跨境电商出口B2C物流市场规模已经达到3825亿元&#xff0c;其中海外仓物流以2102亿的规模以及55%的市场份额成为主流。从细分领域看&#xff0c;其中FBA物流、第三方仓、自建仓的市场规模分别达到1156亿、736亿、210亿。 在跨境物流行业中&#xff0c…

uniapp粘贴板地址识别 address-parse插件的使用

1&#xff1a; 插件安装 主要是依靠 address-parse 这个插件&#xff1a; 官网 收货地址自动识别 支持pc、h5、微信小程序 - DCloud 插件市场 // 首先需要引入插件 npm install address-parse --save 2&#xff1a;html部分 <view class""><view class&quo…

QT、C++简单界面设计

#include "mywidget.h"MyWidget::MyWidget(QWidget *parent): QWidget(parent) {---------------------窗口设置----------------------this->setWindowTitle("南城贤子摄影工作室");//设置窗口标题this->setWindowIcon(QIcon("d:\\Pictures\\C…

并发编程 | 线程池的手动创建

手动创建线程的弊端 在前面我们讲了手动创建线程的使用方式&#xff1a;当一个任务过来&#xff0c;创建一个线程&#xff0c;线程执行完了任务马上又销毁&#xff0c;然后下一次任务过来又重新创建线程&#xff0c;执行完任务再销毁&#xff0c;周而复始。 那么会导致这样一…

【数据结构】-----红黑树

目录 前言 一、what is it&#xff1f; 二、How achieve it&#xff1f; Ⅰ、结点类 Ⅱ、实现 插入 情况一&#xff1a;叔叔存在且为红色 情况二&#xff1a;叔叔不存在或者叔叔为黑色 旋转 验证 ①验证中序遍历 ②验证是否满足红黑树的性质 Ⅲ、完整实现代码 三、A…

【课程总结】day22:Qwen模型的体验

前言 在上一章【课程总结】day21&#xff08;下&#xff09;&#xff1a;大模型的三大架构及T5体验中&#xff0c;我们体验了Encoder-Decoder架构的T5模型。本章内容&#xff0c;我们将以Decoder-Only架构的Qwen模型入手&#xff0c;了解Qwen模型结构、聊天模板的概念以及通过…

类型注解-type hint

目录 一、基本介绍 1、为什么需要类型注解 2、类型注解作用和说明 二、变量的类型注解 1、基本语法 2、基本数据类型注解 3、实例对象类型注解 4、容器类型注解 5、容器详细类型注解 6、在注释中使用注解 三、函数(方法)的类型注解 1、基本语法 2、代码演示 四、…

WebService基础学习

一、XML回顾 二、HTTP协议回顾 三、复习准备 四、关于Web Service的几个问题 五、Web Service中的几个重要术语 六、开发webservice 七、WebService面试题

python面向对象三大特征之---封装,私有属性和私有方法,property功能; 继承,重写,object类;多态;类的深拷贝和浅拷贝

1.面向对象三大特征 封装 1.类属性的创建&#xff1a; 2.属性的访问&#xff1a; 私有属性和方法在类外访问的方法也有&#xff1a;不推荐 对象名._类名__私有方法() 对象名._类名__私有属性3.property功能 在Python中&#xff0c;property 是一个内置的功能&#xff0c;它…

jar包在linux无法直接获取resources文件夹下的文件

windows下&#xff0c;通过hutool的FileUtil.file()就可以获取到文件&#xff0c;通过MailUtil.send()将邮件带附件的方式成功&#xff0c;携带附件发邮件。 linux下部署&#xff0c;截图中的FileUtil.file()是拿不到文件的&#xff0c;报IOException while sending message&a…

「团结引擎1.2.0」正式上线!功能全面升级

「团结引擎 1.2.0」来啦&#xff0c;继上次大版本更新又过了三个月&#xff0c;这段时间我们的研发团队一直在收集用户反馈&#xff0c;更新引擎功能。 本次技术更新的内容&#xff0c;涵盖了微信小游戏、团结引擎车机版、OpenHarmony、Audio、Virtual Geometry、Open Euler/A…

开发食堂采购系统源码:优化供应链管理APP的技术路径

当下&#xff0c;开发一个食堂采购系统源码&#xff0c;并将其集成到供应链管理APP中&#xff0c;成为了优化供应链管理的关键路径之一。 一、食堂采购系统的需求分析 食堂采购系统是食堂日常运营中不可或缺的工具&#xff0c;其主要功能包括采购需求管理、供应商管理、订单管…

《中国数据库前世今生》——历史的深度与未来的展望

在探索科技与历史的交织中&#xff0c;我有幸观看了《中国数据库前世今生》这部纪录片。影片开头它不仅是一段技术演进的回顾&#xff0c;更是中国IT领域从跟随到引领的壮丽史诗。后续深刻研读了专家们的深刻讨论&#xff0c;通过这部纪录片&#xff0c;我深刻感受到了数据库技…

PMP–知识卡片--沟通模型

沟通过程中&#xff0c;发送方想把自己的想法传递给接收方&#xff0c;需要先对想法进行编码&#xff0c;将其变成语言或文字&#xff0c;再选择传递的方式&#xff0c;过程中会受到噪声的影响。这里的噪声是广义的&#xff0c;包括所有影响信息传递效果的因素&#xff0c;如杂…

《Ubuntu22.04环境下的ROS2学习笔记2》

一、在ROS2环境下创建功能包 如果您已经完成了上一小节的内容&#xff0c;那么接下来您一定渴望自己创建一个功能包来实现相应的功能。在ROS1中&#xff0c;您创建的功能包可以既写C/C&#xff0c;又写python&#xff0c;但ROS2中不允许用户这么做&#xff0c;您的C/C和python代…

UniApp的神器-开启前端开发的全新篇章

本文介绍了DIYGW UniApp可视化工具作为一款低代码开发平台的特点和优势。该工具采用拖拽式设计和模块化开发&#xff0c;能够快速转化想法为可运行应用&#xff0c;并支持多种平台部署。它具有所见即所得的设计体验、丰富的组件库、前后台通信模块和跨平台兼容性等特点。使用该…