springboot3场景整合

博客须知

  • 本文来源于尚硅谷雷神的32.Web开发-【代码式】-WebMvcConfigurer使用_哔哩哔哩_bilibili以及他的语雀笔记2、SpringBoot3-Web开发 (yuque.com),我对视频内容进行了整合,由于记笔记时图片使用的是本地路径,所以导致博客的图片无法正常显示,如果有图片需求可以下载上方的pdf

  • 本篇博客内容相较于spring boot 3核心特性就没那么重要,核心特性我之前发过相关博客,传送门:Spring Boot 3核心特性【优化排版】-CSDN博客

  • 这个板块【对应视频p67-》p94的内容】的知识点感觉就是跟着过了一遍,了解为主

    • 如果是想要入门springboot的话直接去看核心特性就够了
    • 如果对这个板块感兴趣的内容建议自己去B站搜索对应课程,因为雷神这一块讲的确实太少了,完全就是过一轮
    • 最后AOT实践没有跟着做,感觉这一块对于后端开发来说学习的性价比不高【所以我看到第88集后面就没有继续了】

环境准备

云服务器

  • 购买云服务器产品阿里云腾讯云华为云 服务器开通,按量付费,省钱省心

  • 我这边是阿里云免费试用三个月,我之前好像用过oss产品,然后现在密码忘了,可以按以下步骤重置密码

    1. 创建好服务器后在控制台处点击远程连接测试登录image-20240327133831422

    2. 此时就可以修改密码了

      image-20240327134252282.

  • 注意每次使用完云服务器需要将其停机【下次启动时公网IP可能会改变,需要重新配置,但是磁盘信息是不会清空的】

    image-20240327161206824image-20240327161241432

  • 下载windterm用于管理云服务器:https://github.com/kingToolbox/WindTerm/releases/download/2.5.0/WindTerm_2.5.0_Windows_Portable_x86_64.zip ,填写云服务器相关信息,然后登录用户即可

    image-20240327133345785image-20240327134547135

云服务器安装组件

Docker安装

还不会docker的同学,参考【云原生实战(10~25集)】快速入门

https://www.bilibili.com/video/BV13Q4y1C7hS?p=10

以上课程有需要就去了解,我是没有去学习过的

  1. 安装docker的命令

    sudo yum install -y yum-utils
    sudo yum-config-manager \
        --add-repo \
        https://download.docker.com/linux/centos/docker-ce.repo
    sudo yum install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
    sudo systemctl enable docker --now #开机时启动docker,并且现在也启动
    
    • 一路输入y就行了image-20240327134914754
  2. 测试安装是否成功docker ps

    image-20240327135151106.

    • 如果没有显示上图的镜像信息,可能是cv的时候控制台漏了sudo systemctl enable docker --now,导致docker没有启动,所以重新输入一遍这条命令就行了
  3. 测试批量安装软件功能是否生效docker compose --help

    image-20240327135525099.

批量安装所有软件

创建 /prod 文件夹,用于存放配置文件

image-20240327140102569.

prometheus.yml
  • 在安装prometheus组件前需要提前准备好它的配置文件
global:
  scrape_interval: 15s
  evaluation_interval: 15s
scrape_configs:
  - job_name: 'prometheus'
    static_configs:
      - targets: ['localhost:9090']
  - job_name: 'redis'
    static_configs:
      - targets: ['redis:6379']
  - job_name: 'kafka'
    static_configs:
      - targets: ['kafka:9092']
docker-compose.yml
  • 编写批量安装的yaml文件,交给gpt生成

image-20240327135907228.

version: '3.9'
services:
  redis:
    image: redis:latest
    container_name: redis
    restart: always
    ports:
      - "6379:6379"
    networks:
      - backend
  zookeeper:
    image: bitnami/zookeeper:latest
    container_name: zookeeper
    restart: always
    environment:
      ZOOKEEPER_CLIENT_PORT: 2181
      ZOOKEEPER_TICK_TIME: 2000
    networks:
      - backend
  kafka:
    image: bitnami/kafka:3.4.0
    container_name: kafka
    restart: always
    depends_on:
      - zookeeper
    ports:
      - "9092:9092"
    environment:
      ALLOW_PLAINTEXT_LISTENER: yes
      KAFKA_CFG_ZOOKEEPER_CONNECT: zookeeper:2181
      KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
    networks:
      - backend  
  kafka-ui:
    image: provectuslabs/kafka-ui:latest
    container_name:  kafka-ui
    restart: always
    depends_on:
      - kafka
    ports:
      - "8080:8080"
    environment:
      KAFKA_CLUSTERS_0_NAME: dev
      KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS: kafka:9092
    networks:
      - backend
  prometheus:
    image: prom/prometheus:latest
    container_name: prometheus
    restart: always
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
    ports:
      - "9090:9090"
    networks:
      - backend
  grafana:
    image: grafana/grafana:latest
    container_name: grafana
    restart: always
    depends_on:
      - prometheus
    ports:
      - "3000:3000"
    networks:
      - backend
networks:
  backend:
    name: backend
批量安装
  • 命令docker compose -f docker-compose.yml up -d
    • 指定批量安装的配置文件
    • up -d表示以后台的方式启动

image-20240327141211362image-20240327142831661

  • 测试各个容器的启动情况

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

  • 验证kafka【其它组件同理】是否启动成功:通过公网ip+kafka-ui暴露的端口来访问成功kafka的ui界面

    • 如果访问不了,可能是因为没有放行端口导致的,解决步骤如下
      1. 控制台点击服务器的示例image-20240327143745524
      2. 找到安全组的标签,然后管理规则image-20240327143908449
      3. 手动添加规则【这里源填的是我个人的IP地址】image-20240327144148729
    • 访问成功

    image-20240327144347655.

  • 访问redis可以下载https://redis.com/redis-enterprise/redis-insight/#insight-form,不过我之前都是用redisManager的,但是这个软件看上去整体更高级【收费】

Redis整合

环境搭建

  1. 勾选redis的场景

image-20240327153005607.

  1. 配置redis的连接主机【默认端口号为6379】
spring.data.redis.host=你的redis主机所在ip地址【即云服务器的公网IP】
  1. 测试
@RestController
public class RedisTestController {
    @Autowired
    StringRedisTemplate redisTemplate;
    @GetMapping("/count")
    public String count(){
        Long hello = redisTemplate.opsForValue().increment("hello");
        return "访问次数:"+hello;
    }
}

image-20240327154228796image-20240327154238408

自动配置原理

  1. META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports中导入了RedisAutoConfigurationRedisReactiveAutoConfigurationRedisRepositoriesAutoConfiguration

    • 所有属性绑定在RedisProperties
    • RedisReactiveAutoConfiguration属于响应式编程,不用管
    • RedisRepositoriesAutoConfiguration属于 JPA 操作,也不用管
  2. RedisAutoConfiguration 配置了以下组件

    • LettuceConnectionConfiguration: 给容器中注入了连接工厂LettuceConnectionFactory,和操作redis的客户端DefaultClientResources

    • RedisTemplate<Object, Object>: 给redis中存储任意对象,会使用jdk默认序列化方式,存储的对象必须实现序列化接口

    • StringRedisTemplate: 给redis中存储字符串【key-value都是字符串】,如果要存对象,需要开发人员自己进行序列化

定制化

序列化机制

  • 配置Json的序列化机制
@Configuration
public class AppRedisConfiguration {
    /**
     * 允许Object类型的key-value,都可以被转为json进行存储
     * @param redisConnectionFactory 自动配置好了连接工厂
     * @return
     */
    @Bean
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);
        //把对象转为json字符串的序列化工具
        template.setDefaultSerializer(new GenericJackson2JsonRedisSerializer());
        return template;
    }
}

redis客户端

  • RedisTemplate、StringRedisTemplate: 操作redis的工具类

    • 要从redis的连接工厂获取链接才能操作redis

    • 常见的操作Redis的客户端

      • Lettuce: 默认客户端
      • Jedis:可以使用以下方式进行切换
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    <exclusions>
        <exclusion>
            <groupId>io.lettuce</groupId>
            <artifactId>lettuce-core</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<!--        切换 jedis 作为操作redis的底层客户端-->
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
</dependency>

配置参考

spring.data.redis.host=8.130.74.183
spring.data.redis.port=6379
#spring.data.redis.client-type=lettuce
#设置lettuce的底层参数
#spring.data.redis.lettuce.pool.enabled=true
#spring.data.redis.lettuce.pool.max-active=8
#设置客户端类型,并且配置连接池信息
spring.data.redis.client-type=jedis 
spring.data.redis.jedis.pool.enabled=true
spring.data.redis.jedis.pool.max-active=8

接口文档【了解】

OpenAPI 3 与 Swagger

  • Swagger 可以快速生成实时接口文档,方便前后开发人员进行协调沟通,遵循 OpenAPI 规范,文档:https://springdoc.org/v2/
  • OpenAPI 3支持原生方式和响应式编程的Web开发

image-20240327161716355.

整合

  1. 导入场景
<dependency>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
    <version>2.1.0</version>
</dependency>
  1. 配置
# /api-docs endpoint custom path 默认 /v3/api-docs
springdoc.api-docs.path=/api-docs
# swagger 相关配置在  springdoc.swagger-ui
# swagger-ui custom path
springdoc.swagger-ui.path=/swagger-ui.html
springdoc.show-actuator=true

使用

常用注解

注解标注位置作用
@Tagcontroller 类标识 controller 作用
@Parameter参数标识参数作用
@Parameters参数参数多重说明
@Schemamodel 层的 JavaBean描述模型作用及每个属性
【可以标识在类或者类中的字段上】
@Operation方法描述方法作用
@ApiResponse方法描述响应状态码等

Docket配置

  • Docket相当于分组方式的API
  • 如果有多个Docket,配置如下【配置某个路径下属于哪个分组】
@Bean
public GroupedOpenApi publicApi() {
    return GroupedOpenApi.builder()
          .group("springshop-public")
          .pathsToMatch("/public/**")
          .build();
}
@Bean
public GroupedOpenApi adminApi() {
    return GroupedOpenApi.builder()
          .group("springshop-admin")
          .pathsToMatch("/admin/**")
          .addMethodFilter(method -> method.isAnnotationPresent(Admin.class))
          .build();
}
  • 如果只有一个Docket,可以配置如下
springdoc.packagesToScan=package1, package2
springdoc.pathsToMatch=/v1, /api/balance/**

OpenAPI配置

@Bean
public OpenAPI springShopOpenAPI() {
    return new OpenAPI()
          .info(new Info().title("SpringShop API")
          .description("Spring shop sample application")
          .version("v0.0.1")
          .license(new License().name("Apache 2.0").url("http://springdoc.org")))
          .externalDocs(new ExternalDocumentation()
          .description("SpringShop Wiki Documentation")
          .url("https://springshop.wiki.github.org/docs"));
}

Springfox 迁移

注解变化

原注解现注解作用
@Api@Tag描述Controller
@ApiIgnore@Parameter(hidden = true) @Operation(hidden = true) @Hidden描述忽略操作
@ApiImplicitParam@Parameter描述参数
@ApiImplicitParams@Parameters描述参数
@ApiModel@Schema描述对象
@ApiModelProperty(hidden = true)@Schema(accessMode = READ_ONLY)描述对象属性
@ApiModelProperty@Schema描述对象属性
@ApiOperation(value = “foo”, notes = “bar”)@Operation(summary = “foo”, description = “bar”)描述方法
@ApiParam@Parameter描述参数
@ApiResponse(code = 404, message = “foo”)@ApiResponse(responseCode = “404”, description = “foo”)描述响应

Docket配置

以前写法
@Bean
public Docket publicApi() {
    return new Docket(DocumentationType.SWAGGER_2)
          .select()
          .apis(RequestHandlerSelectors.basePackage("org.github.springshop.web.public"))
          .paths(PathSelectors.regex("/public.*"))
          .build()
          .groupName("springshop-public")
          .apiInfo(apiInfo());
}

@Bean
public Docket adminApi() {
    return new Docket(DocumentationType.SWAGGER_2)
          .select()
          .apis(RequestHandlerSelectors.basePackage("org.github.springshop.web.admin"))
          .paths(PathSelectors.regex("/admin.*"))
          .apis(RequestHandlerSelectors.withMethodAnnotation(Admin.class))
          .build()
          .groupName("springshop-admin")
          .apiInfo(apiInfo());
}
新写法
@Bean
public GroupedOpenApi publicApi() {
    return GroupedOpenApi.builder()
          .group("springshop-public")
          .pathsToMatch("/public/**")
          .build();
}
@Bean
public GroupedOpenApi adminApi() {
    return GroupedOpenApi.builder()
          .group("springshop-admin")
          .pathsToMatch("/admin/**")
          .addOpenApiMethodFilter(method -> method.isAnnotationPresent(Admin.class))
          .build();
}
添加OpenAPI组件
@Bean
public OpenAPI springShopOpenAPI() {
    return new OpenAPI()
          .info(new Info().title("SpringShop API")
          .description("Spring shop sample application")
          .version("v0.0.1")
          .license(new License().name("Apache 2.0").url("http://springdoc.org")))
          .externalDocs(new ExternalDocumentation()
          .description("SpringShop Wiki Documentation")
          .url("https://springshop.wiki.github.org/docs"));
}

远程调用

RPC(Remote Procedure Call):远程过程调用

image-20240327164858105.

本地调用和远程调用的区别

  • 本地过程调用: 不同方法都在同一个JVM运行

  • 远程过程调用

    • 会产生两种角色:服务提供者、服务消费者【调用提高者提供的功能】
    • 通过连接对方服务器进行请求\响应交互,来实现调用效果
  • !!!面试题:API/SDK的区别是什么?

    • api:接口(Application Programming Interface),可以远程提供功能
    • sdk:工具包(Software Development Kit),导入jar包,直接调用功能即可,一般局限于本地调用

远程调用场景

  • 开发过程中,经常需要调用别人写的功能

    • 如果是内部微服务,可以通过依赖cloud、注册中心、openfeign等进行调用
    • 如果是外部暴露的,可以发送 http 请求、或遵循外部协议进行调用
  • SpringBoot 整合提供了很多方式进行远程调用

    • 轻量级客户端方式

      • RestTemplate: 普通开发

      • WebClient: 响应式编程开发

      • Http Interface: 声明式编程

    • Spring Cloud分布式解决方案方式

      • Spring Cloud OpenFeign
    • 第三方框架

      • Dubbo
      • gRPC

WebClient

非阻塞、响应式HTTP客户端

环境搭建

  1. WebClient属于响应式编程的web开发,所以要导入响应式编程相关场景

image-20240327171731842.

  1. 阿里云试用天气查询的api,postman测试接口调用

image-20240403151449936image-20240403151503398

创建与配置

  • 发请求需要关注请求方式、请求路径、请求参数、请求头、请求体
  • 创建WebClient
    • WebClient.create()
    • WebClient.create(String baseUrl)
  • 还可以使用WebClient.builder() 配置更多参数项【了解】
    • uriBuilderFactory:自定义UriBuilderFactory ,定义 baseurl
    • defaultUriVariables:默认 uri 变量
    • defaultHeader:每个请求默认头
    • defaultCookie:每个请求默认 cookie
    • defaultRequest:Consumer 自定义每个请求
    • filter:过滤 client 发送的每个请求
    • exchangeStrategies:HTTP 消息 reader/writer 自定义
    • clientConnector:HTTP client 库设置
  • 远程调用阿里云的api【我这一块用的是万维易源的天气查询】
public Mono<String>  weather(String city){
    //1.创建webClient【获取到一个调用天气查询功能的客户端】
    WebClient webClient = WebClient.create();
    //2.准备查询参数
    Map<String,String> params=new HashMap<>();
    params.put("area",city);
    //3.定义请求的相关属性
    return  webClient.get()
            .uri("https://ali-weather.showapi.com/area-to-weather-date?area={area}",params)
            .accept(MediaType.APPLICATION_JSON)//接收json数据
            .header("Authorization", "APPCODE 你自己的appcode")//这一块具体得看你使用的产品
            .retrieve()
            .bodyToMono(String.class);//把响应体中的数据转成字符串类型
}
  • 测试结果

image-20240403151201933.

HTTP Interface

基础功能

  • Spring允许通过定义接口的方式,给任意位置发送 http 请求,实现远程调用,可以用来简化 HTTP 远程访问【需要webflux场景】
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
  • 定义接口【其中@RequestParam声明了发送出去的参数名】
public interface WeatherInterface {
    @GetExchange(url = "/area-to-weather-date",accept = "application/json")
    String getWeather(@RequestParam("area")String city//将传入的city参数以area的参数名发送出去
    ,@RequestHeader("Authorization") String header);
}
  • 注入接口组件
@Bean
WeatherInterface weatherInterface(){
    //1.创建webClient
    WebClient webClient = WebClient.builder().
            baseUrl("https://ali-weather.showapi.com")
            .codecs(clientCodecConfigurer -> {
                clientCodecConfigurer
                        .defaultCodecs()
                        .maxInMemorySize(256*1024*1024);
            }).build();
    //2.创建工厂
    HttpServiceProxyFactory factory = HttpServiceProxyFactory
            .builder(WebClientAdapter.forClient(webClient)).build();
    //3、获取代理对象
    return factory.createClient(WeatherInterface.class);
}
  • 自动注入该接口,然后调用方法
@Autowired
WeatherInterface weatherInterface;
@GetMapping("/weather")
public Mono<String> weather(@RequestParam("city") String city){
    return weatherInterface.getWeather(city,"APPCODE 6ca80ab7736c4666be72d368dcd8879c");
}

深层抽取

  • 将创建代理对象的工厂类单独抽取出来,之后就可以用这个工厂创建不同的代理对象【工厂组件就不指定具体的路径了】
//注入创建代理对象的工厂
@Bean
HttpServiceProxyFactory factory(){
    //1.创建webClient
    WebClient webClient = WebClient.builder()
            .codecs(clientCodecConfigurer -> {
                clientCodecConfigurer
                        .defaultCodecs()
                        .maxInMemorySize(256*1024*1024);
            }).build();
    //2.创建工厂
    return HttpServiceProxyFactory
            .builder(WebClientAdapter.forClient(webClient)).build();
}
//通过工厂创建代理对象
@Bean
WeatherInterface weatherInterface(HttpServiceProxyFactory factory){
    return factory.createClient(WeatherInterface.class);
}
  • 新增物流查询功能【云市场大部分请求参数和请求头都是大差不差的】

    1. postman测试api

    image-20240403155915573.

    1. 创建api调用接口
    public interface ExpressApi {
        @GetExchange(url = "https://wuliu.market.alicloudapi.com/kdi",accept = "application/json")
        Mono<String> getExpress(@RequestParam("no") String number);
    }
    
    1. 使用工厂创建接口代理
    @Bean
    ExpressApi expressApi(HttpServiceProxyFactory factory){
        return factory.createClient(ExpressApi.class);
    }
    
    1. 测试
    @GetMapping("/express")
    public Mono<String> express(@RequestParam("no") String no){
        return expressApi.getExpress(no,"APPCODE 你自己的appcode");
    }
    

    image-20240403160641533.

    1. 阿里云市场的用户认证头都是统一的,所以可以在工厂类配置默认的请求头
    WebClient webClient = WebClient.builder()
            .defaultHeader("Authorization","你自己的appcode")
            .codecs(clientCodecConfigurer -> {
                clientCodecConfigurer
                        .defaultCodecs()
                        .maxInMemorySize(256*1024*1024);
            }).build();
    
    1. 也可以抽取成可以配置的属性

    image-20240403161440007image-20240403161501688

消息服务

消息队列-场景

异步

image-20240403161817410.

解耦

image-20240403161843985.

削峰

image-20240403161906320.

缓冲
  • 其实缓存和削峰差不多呀

image-20240403161930989.

消息队列-Kafka

消息模式

image-20240403162959254.

Kafka工作原理

  • 副本就是本分区的数据在其它分区都有备份,本分区作为存储该数据的主分区,其它机器也是同理

image-20240403163742182.

SpringBoot整合

  • 参照:https://docs.spring.io/spring-kafka/docs/current/reference/html/#preface
<dependency>
    <groupId>org.springframework.kafka</groupId>
    <artifactId>spring-kafka</artifactId>
</dependency>
  • 配置kafka集群的所有节点:spring.kafka.bootstrap-servers=云服务器的公网ip:9092
    • 如果有多个服务器可以用,分割,或者使用数组的形式
  • KafkaAutoConfiguration提供如下功能
    • KafkaProperties:kafka的所有配置,以 spring.kafka开始
      • bootstrapServers:kafka集群的所有服务器地址
      • properties:参数设置
      • consumer:消费者1
      • producer:生产者
    • @EnabLeKafka:开启Kafka的注解驱动功能
    • KafkaTemplate:收发消息
    • KafkaAdmin:维护主题等
  • 代码方式创建主题
@EnableKafka
@Configuration
public class KafkaConfiguration {
    @Bean
    public NewTopic topic() {
        return TopicBuilder.name("thing")
                .partitions(1)
                .compact()
                .build();
    }
}

发送消息

发送string类型的消息

@SpringBootTest
class Boot07KafkaApplicationTests {
    @Autowired
    KafkaTemplate kafkaTemplate;
    @Test
    void contextLoads() throws ExecutionException, InterruptedException {
        StopWatch watch = new StopWatch();
        watch.start();
        CompletableFuture[] futures = new CompletableFuture[10000];
        for (int i = 0; i < 10000; i++) {
            CompletableFuture send = kafkaTemplate.send("order", "order.create."+i, "订单创建了:"+i);
            futures[i]=send;
        }
        CompletableFuture.allOf(futures).join();//阻塞等待所有的异步任务完成
        watch.stop();
        System.out.println("总耗时:"+watch.getTotalTimeMillis());
    }
}
  • 如果抛出主机不存在的信息

    1. 先去kafka-ui找到自己的kafka服务器的主机名image-20240403171925554

    2. 然后修改hosts文件中的域名映射image-20240403172032491

  • 发送成功

image-20240403172251447image-20240403172304479

发送自定义对象

  • kafka默认使用的是string类型的序列化机制,所以发送对象类型配置自定义序列化规则spring.kafka.producer.value-serializer

    1. 先ctrl点击配置文件的value-serializer,查看默认的序列化器private Class<?> valueSerializer = StringSerializer.class;

    2. 然后进入StringSerializer,发现其实现了序列化接口public class StringSerializer implements Serializer<String>

    3. 进入序列化接口,选择需要用到的序列化器【ctrl+h可以查看接口的实现类】

      image-20240403173242410.

    4. 最后配置需要的序列化规则image-20240403173314129

      spring.kafka.producer.value-serializer=org.springframework.kafka.support.serializer.JsonSerializer
      
  • 测试发送对象kafkaTemplate.send("news", "person",new Person(18,"里奥"));

image-20240403173645648.

监听消息

默认监听

  • 默认是监听项目启动之后的新的消息
  • 必须指定分组id,统一组内消息读取是竞争关系,不同组就是独立的
@Component
public class MyListener {
    @KafkaListener(topics ="order",groupId = "testKafka")
    public void newsListener(ConsumerRecord record){
        //ConsumerRecord用于获取消息的详细消息【属于哪个主题等等】
        Object key = record.key();
        Object value = record.value();
        System.out.println(key+"========"+value);
    }
}

监听所有消息

  • 如果要监听之前完整的消息需要设置偏移量
@KafkaListener(groupId = "test2",topicPartitions = {
        @TopicPartition(topic = "order",partitionOffsets = {
                @PartitionOffset(partition = "0",initialOffset = "0")
        })
})
public void orderListener(ConsumerRecord record){
    Object key = record.key();
    Object value = record.value();
    System.out.println(key+"========"+value);
}

小结

  • @EnableKafka+@KafkaListener用于监听消息

    • 监听者需要指定group-id
    • 收消息使用@KafkaListener+ConsumerRecord
    • spring.kafka绑定了kafka相关配置属性
  • 核心概念

    • 分区:分散存储,大量数据分散到不同节点
    • 副本:备份机制,每个小分区都有备份
    • 主题:消息是发送给某个主题的

Web安全

安全架构

关注的方面

  • 认证:Authentication

    • who are you?
    • 场景:登录系统,用户系统
  • 授权:Authorization

    • what are you allowed to do?

    • 场景:权限管理,用户授权

  • 攻击防护

    • XSS(Cross-site scripting)、CSRF(Cross-site request forgery)
    • CORS(Cross-Origin Resource Sharing)、SQL注入

拓展:权限模型【了解】

RBAC(Role Based Access Controll)
  • 用户(t_user)

    • id,username,password,xxx
    • 1,zhangsan
    • 2,lisi
  • 角色(t_role)

    • id,role_name
    • admin
    • hr
    • common_user
  • 用户_角色(t_user_role)【N对N关系需要中间表】

    • zhangsan,admin
    • zhangsan,common_user
    • lisi,hr
    • lisi,common_user
  • 角色_权限(t_role_perm)

    • admin,文件r
    • admin,文件w
    • admin,文件执行
    • admin,订单query、create、xxx
    • hr, 文件r
  • 权限(t_permission)

    • id,perm_id
    • 文件 r,w,x
    • 订单 query,create,xxx
ACL(Access Controll List)

直接用户和权限挂钩

  • 用户(t_user)

    • zhangsan
    • lisi
  • 权限(t_permission)

    • id,perm_id
    • 文件 r,w,x
    • 订单 query,create,xxx
  • 用户_权限(t_user_perm)

    • zhangsan,文件 r
    • zhangsan,文件 x
    • zhangsan,订单 query
@Secured("文件 r")
public void readFile(){
    //读文件
}

Spring Security 原理

过滤器链架构

  • Spring Security利用 FilterChainProxy 封装一系列拦截器链,实现各种安全拦截功能
  • Servlet三大组件:Servlet、Filter、Listener

image-20240405142804991.

FilterChainProxy

  • 可以对不同的路径定义不同的过滤规则

image-20240405142829713.

SecurityFilterChain

  • 所有的权限过滤器被spring security放在FilterChainProxy里面,这个代理里面每一个都是Security Filter
  • 所有的Security Filter合起来就叫SecurityFilterChain

image-20240405142918256.

使用

HttpSecurity

@Configuration
@Order(SecurityProperties.BASIC_AUTH_ORDER - 10)
public class ApplicationConfigurerAdapter extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
    http.antMatcher("/match1/**")
      .authorizeRequests()
        .antMatchers("/match1/user").hasRole("USER")
        .antMatchers("/match1/spam").hasRole("SPAM")
        .anyRequest().isAuthenticated();
    }
}

核心

  • WebSecurityConfigurerAdapter

  • @EnableGlobalMethodSecurity: 开启全局方法安全配置

    • @Secured
    • @PreAuthorize
    • @PostAuthorize
  • UserDetailService: 去数据库查询用户详细信息的service(用户基本信息、用户角色、用户权限)

  • 自动配置类:SecurityAutoConfiguration、SpringBootW/ebSecurityConfiguration、 SecurityFilterAutoConfiguration

    • security的所有配置在 SecurityProperties:以spring.security开头

    • 默认SecurityFitterChain组件

      • 所有请求都需要认证 (登录)

      • 开启表单登录:spring security提供一个默认登录页,未经登录的所有请求都需要登录

        image-20240405144322486.

      • httpbasic方式登录

    • @EnableWebSecurity开启功能

      • WebSecurityConfiguration生效:web安全配置
      • HttpSecurityConfiquration生效:http安全规则
      • @EnableGlobalAuthentication生效:全局认证生效【AuthenticationConfiquration:认证配置】

实战

1.引入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

2.配置安全规则

@Configuration
public class SecurityConfiguration {
    @Bean
    @Order(2147483642)
    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests(registry->{
            registry.requestMatchers("/").permitAll()//首页所有人都可以访问
                    .anyRequest().authenticated();//剩下的所有请求都需要认证(登录)
        });
        http.formLogin();//开启默认的表单登录功能
        return http.build();
    }
}
  • 配置自定义的登录页
//开启默认的表单登录功能
http.formLogin(formLogin -> {
    formLogin.loginPage("/login").permitAll();//自定义登录页的请求位置,并且允许所有人访问
});

image-20240405150101228.

3.配置登录信息

  • 使用UserDetailsService可以自定义登录信息
  • 自定义登录信息需要配置密码加密器
@Bean
UserDetailsService userDetailsService(){
    UserDetails zhangSan = User.withUsername("zhangsan")
            .password(passwordEncoder().encode("123456"))//密文存储
            .roles("hr", "admin")
            .build();
    //模拟在内存中保存用户信息
    InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager(zhangSan);
    return manager;
}
@Bean
PasswordEncoder passwordEncoder(){
    return new BCryptPasswordEncoder();
}

4.开启方法级别的权限控制

  • @EnableMethodSecurity:开启方法级别的精确权限控制
  • 配合权限控制注解使用,@PreAuthorize提前检验权限
@PreAuthorize("hasAuthority('hello')")
@GetMapping("/hello")
@ResponseBody
public String word(){
    return "Hello";
}

image-20240405152758633.

可观测性

基础知识

  • 可观测性 Observability:对线上应用进行观测、监控、预警
    • 健康状况【组件状态、存活状态】Health
    • 运行指标【cpu、内存、垃圾回收、吞吐量、响应成功率…】Metrics
    • 链路追踪

SpringBoot Actuator

实战

  1. 导入场景
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
  1. 暴露指标

    #暴露所有端点信息
    management.endpoints.web.exposure.include=* 
    
  2. 访问数据

    • 访问 http://localhost:8080/actuator:展示出所有可以用的监控端点
    • http://localhost:8080/actuator/beans
    • http://localhost:8080/actuator/configprops
    • http://localhost:8080/actuator/metrics:打印所有指标
    • http://localhost:8080/actuator/metrics/jvm.gc.pause
    • http://localhost:8080/actuator/endpointName/detailPath

image-20240405173534947.

Endpoint

  • 重要端点
    • threaddump:下载线程信息
    • heapdump:下载堆内存信息
    • metrics:监控指标
  • 常用端点一览表
ID描述
auditevents暴露当前应用程序的审核事件信息。需要一个AuditEventRepository组件
beans显示应用程序中所有Spring Bean的完整列表。
caches暴露可用的缓存。
conditions显示自动配置的所有条件信息,包括匹配或不匹配的原因。
configprops显示所有@ConfigurationProperties
env暴露Spring的属性ConfigurableEnvironment
flyway显示已应用的所有Flyway数据库迁移。 需要一个或多个Flyway组件。
health显示应用程序运行状况信息。
httptrace显示HTTP跟踪信息(默认情况下,最近100个HTTP请求-响应)。需要一个HttpTraceRepository组件。
info显示应用程序信息。
integrationgraph显示Spring integrationgraph 。需要依赖spring-integration-core
loggers显示和修改应用程序中日志的配置。
liquibase显示已应用的所有Liquibase数据库迁移。需要一个或多个Liquibase组件。
metrics显示当前应用程序的“指标”信息。
mappings显示所有@RequestMapping路径列表。
scheduledtasks显示应用程序中的计划任务。
sessions允许从Spring Session支持的会话存储中检索和删除用户会话。需要使用Spring Session的基于Servlet的Web应用程序。
shutdown使应用程序正常关闭。默认禁用。
startup显示由ApplicationStartup收集的启动步骤数据。需要使用SpringApplication进行配置BufferingApplicationStartup
threaddump执行线程转储。
heapdump返回hprof堆转储文件。
jolokia通过HTTP暴露JMX bean(需要引入Jolokia,不适用于WebFlux)。需要引入依赖jolokia-core
logfile返回日志文件的内容(如果已设置logging.file.namelogging.file.path属性)。支持使用HTTPRange标头来检索部分日志文件的内容。
prometheus以Prometheus服务器可以抓取的格式公开指标。需要依赖micrometer-registry-prometheus

定制端点

前置知识
  • 健康监控:返回存活、死亡
  • 指标监控:次数、率
  • 如下配置可以展示详细的健康信息
management.endpoint.health.enabled=true
management.endpoint.health.show-details=always
HealthEndpoint
  • 实现HealthEndpoint接口来定制组件的健康状态对象(Health)
  • 也可以通过继承AbstractHealthIndicator并重写doHealthCheck()就可以定制健康检查的逻辑
@Component
public class MyComponentIndicator extends AbstractHealthIndicator {
    @Autowired
    MyComponent component;
    @Override
    protected void doHealthCheck(Health.Builder builder) throws Exception {
        //自定义检查方法
        if (component.check() == 1) {
            builder.up()//存活
                    .withDetail("code", "200")
                    .withDetail("msg", "活的很健康")
                    .build();
        } else {
            builder.down()
                    .withDetail("msg", "死的很痛苦")
                    .build();//下线
        }
    }
}
@Component
public class MyComponent {
    public int check(){
        return 1;
    }
}
  • 测试结果

image-20240405205105550.

MetricsEndpoint
  • 自定义指标可以注入MeterRegistry来实现定制化
@Component
public class MyComponent {
    Counter counter=null;//定义计数器
    /**
     * 如果只有一个有参构造器,那定义的形参就会从容器中拿
     * @param meterRegistry 注入该组件来保存和统计所有指标
     */
    public MyComponent(MeterRegistry meterRegistry) {
        //得到一个名为helloCounter的计数器
        counter=meterRegistry.counter("helloCounter");
    }

    public int check(){
        return 1;
    }
    public void hello(){
        System.out.println("hello");
        counter.increment();//计数器加一
    }
}

image-20240405210422145.

整合Prometheus + Grafana

环境搭建

  1. 安装【之前在云服务器中批量创建过了】
#安装prometheus:时序数据库
docker run -p 9090:9090 -d \
-v pc:/etc/prometheus \
prom/prometheus
#安装grafana;默认账号密码 admin:admin
docker run -d --name=grafana -p 3000:3000 grafana/grafana
  1. 导入依赖,产生prometheus需要的时序数据
<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
  1. 导入prometheus会多一个监控端点

image-20240406141713902image-20240406141737810

项目上线

  1. 打包当前项目【clean+package】

    image-20240406142251601.

  2. 部署到linux服务器

    1. 需要先安装上传工具yum install lrzsz

    2. 然后运行rz命令就可以选择本地上传的文件

    image-20240406142522999.

  3. 安装openjdk【以下命令来源于gpt】

        # 下载openjdk
    wget https://download.oracle.com/java/17/latest/jdk-17_linux-x64_bin.tar.gz
    # 创建文件夹,之后将openjdk安装到该位置
    mkdir -p /opt/java
    tar -xzf jdk-17_linux-x64_bin.tar.gz -C /opt/java/
    sudo vi /etc/profile
    #======在配置文件结尾追加以下内容========
    export JAVA_HOME=/opt/java/安装的jdk名称
    export PATH=$PATH:$JAVA_HOME/bin
    #====================================
    #让环境变量生效
    source /etc/profile
    
  4. java -version检查环境是否生效

    image-20240406143545828.

  5. 项目启动

    image-20240406143757111image-20240406143915856

  6. 后台启动java应用,就不会阻塞linux服务器nohup java -jar boot3-actuator-0.0.1-SNAPSHOT.jar > output.log 2>&1 &

  7. 同一台机器可以通过访问私网地址的actuator/prometheus获取指标信息curl 私网ip:9999/actuator/prometheus

    image-20240406144814260.

配置Prometheus拉取数据

  1. 修改Prometheus.yaml,拉取某个应用的指标数据
 - job_name: '任务名【随便取】'
    metrics_path: '/actuator/prometheus' #指定抓取的路径
    static_configs:
      - targets: ['自己的私网地址【公网也可以,但是会慢一点】:9999']
        labels:
          nodename: 'app-demo'
  1. 重启Prometheus,刷新配置

image-20240406150216643.

  1. 拉取数据【在status的targets中】

image-20240406151907595.

配置Grafana监控面板

  • 添加数据源(Prometheus)

    1. 在Grafana的Connections下的data sources目录中配置Prometheus的访问地址
    2. 因为都是在docker安装的,所以写容器的名称就能访问到Prometheusimage-20240406152700212
  • 添加面板:可以去dashboard市场找一个自己喜欢的面板,也可以自己开发面板Dashboards | Grafana Labs

    1. 复制面板idimage-20240406152240185

    2. 在Grafana主页导入该面板image-20240406152342371

    3. 好像12900的面板即支持springboot应用又支持Prometheus【如果无法导入数据源或者监控面板没信息就选择12900】

      image-20240406153632273.

image-20240406153912240.

AOT

AOT与JIT

基础概念

  • AOT(Ahead-of-Time提前编译)::程序执行前,全部被编译成机器码
  • JIT(Just in Time即时编译): 程序边编译,边运行;
  • 编译: 源代码(.c、.cpp、.go、.java) =编译=》 机器码
  • 语言
    • 编译型语言:编译器
    • 解释型语言:解释器

Complier与Interpreter

image-20240406154600628.

对比项编译器解释器
机器执行速度快,因为源代码只需被转换一次慢,因为每行代码都需要被解释执行
开发效率慢,因为需要耗费大量时间编译快,无需花费时间生成目标代码,更快的开发和测试
调试难以调试编译器生成的目标代码容易调试源代码,因为解释器一行一行地执行
可移植性(跨平台)不同平台需要重新编译目标平台代码同一份源码可以跨平台执行,因为每个平台会开发对应的解释器
学习难度相对较高,需要了解源代码、编译器以及目标机器的知识相对较低,无需了解机器的细节
错误检查编译器可以在编译代码时检查错误解释器只能在执行代码时检查错误
运行时增强可以动态增强

AOT与JIT对比

JITAOT
优点1.具备实时调整能力 2.生成最优机器指令
3.根据代码运行情况优化内存占用
1.速度快,优化了运行时编译时间和内存消耗
2.程序初期就能达最高性能 3.加快程序启动速度
缺点1.运行期边编译速度慢 2.初始编译不能达到最高性能1.程序第一次编译占用时间长 2.牺牲高级语言一些特性
  • 在 OpenJDK 的官方 Wiki 上,介绍了HotSpot 虚拟机一个相对比较全面的、即时编译器(JIT)中采用的优化技术列表【了解】
    • 可使用:-XX:+PrintCompilation 打印JIT编译信息

image-20240406160014654image-20240406160030673

JVM

架构

  • JVM:既有解释器,又有编译器(JIT:即时编译)

image-20240406160128049.

Java的执行过程

  • 建议阅读
    • 美团技术:https://tech.meituan.com/2020/10/22/java-jit-practice-in-meituan.html
    • openjdk官网:https://wiki.openjdk.org/display/HotSpot/Compiler

image-20240406160357941image-20240406160717170

  • jvm里面会判断一段代码是解释执行还是编译执行
    • 如果是编译执行,就编译成机器码并且放到缓存里面,下次用到的时候,直接取
    • 如果是解释执行,就直接解释执行,并且统计解释执行的次数,当次数达到某个阈值的时候,就触发编译,将机器码放在缓存中
    • 热点代码:调用次数非常多的代码

JVM编译器

  • JVM中集成了两种编译器,Client Compiler 和 Server Compiler
    • Client Compiler注重启动速度和局部的优化
    • Server Compiler更加关注全局优化,性能更好,但由于会进行更多的全局分析,所以启动速度会慢
  • Client Compiler
    • HotSpot VM带有一个Client Compiler C1编译器
    • 这种编译器启动速度快,但是性能比较Server Compiler来说会差一些
    • 编译后的机器码执行效率没有C2的高
  • Server Compiler
    • Hotspot虚拟机中使用的Server Compiler有两种:C2Graal
    • 在Hotspot VM中,默认的Server Compiler是C2编译器

分层编译

基础概念
  • Java 7开始引入了分层编译(Tiered Compiler)的概念,它结合了C1C2的优势,追求启动速度和峰值性能的一个平衡
  • 分层编译将JVM的执行状态分为了五个层次
    • 解释执行
    • 执行不带profiling的C1代码
    • 执行仅带方法调用次数以及循环回边执行次数profiling的C1代码
    • 执行带所有profiling的C1代码
    • 执行C2代码
  • profiling就是收集能够反映程序执行状态的数据。其中最基本的统计数据就是方法的调用次数,以及循环回边的执行次数
不同编译路径分析

image-20240406161525175.

  • 图中第①条路径,代表编译的一般情况,热点方法从解释执行到被3层的C1编译,最后被4层的C2编译

  • 如果方法比较小(比如Java服务中常见的getter/setter方法),3层的profiling没有收集到有价值的数据,JVM就会断定该方法对于C1代码和C2代码的执行效率相同,就会执行图中第②条路径。在这种情况下,JVM会在3层编译之后,放弃进入C2编译,直接选择用1层的C1编译运行

  • C1忙碌的情况下,执行图中第③条路径,在解释执行过程中对程序进行profiling ,根据信息直接由第4层的C2编译

  • 前文提到C1中的执行效率是1层>2层>3层第3层一般要比第2层慢35%以上,所以在C2忙碌的情况下,执行图中第④条路径。这时方法会被2层的C1编译,然后再被3层的C1编译,以减少方法在3层的执行时间

  • 如果编译器做了一些比较激进的优化,比如分支预测,在实际运行时发现预测出错,这时就会进行反优化,重新进入解释执行,图中第⑤条执行路径代表的就是反优化

  • 总的来说,C1的编译速度更快,C2的编译质量更高,分层编译的不同编译路径,也就是JVM根据当前服务的运行情况来寻找当前服务的最佳平衡点的一个过程。从JDK 8开始,JVM默认开启分层编译

云原生:Cloud Native
  • 原生应用:任意编程语言写的应用

  • 存在的问题

    • java应用的jar包是解释执行的过程,只有热点代码才编译成机器码,初始启动速度慢,初始处理请求数量少
    • 大型云平台,要求每一种应用都必须秒级启动。每个应用都要求效率高
  • 希望的效果

    • java应用也能提前被编译成机器码,随时急速启动,一启动就急速运行,最高性能

    • 编译成机器码的好处

      • 另外的服务器还需要安装Java环境

      • 编译成机器码可以在当前平台直接运行

  • 原生镜像:native-image(机器码、本地镜像),把应用打包成能适配本机平台的可执行文件(机器码、本地镜像)

GraalVM

基础概念

  • 官方网址
  • GraalVM是一个高性能的JDK,旨在加速用Java和其他JVM语言编写的应用程序的执行,同时还提供JavaScript、Python和许多其他流行语言的运行时。
  • GraalVM提供了两种运行Java应用程序的方式
    • 在HotSpot JVM上使用Graal即时编译器(JIT)
    • 作为预先编译(AOT)的本机可执行文件运行(本地镜像)
  • GraalVM的多语言能力使得在单个应用程序中混合多种编程语言成为可能,同时消除了外部语言调用的成本

架构

image-20240406163213829.

安装

  • 这一块去看雷神的笔记7、AOT (yuque.com),要安装杂七杂八的,感觉对java学习没什么太大必要

image-20240406163513002.

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值