工作中遇到的一些技术

dynamic-tp动态线程池

动态线程池,可以通过nacos配置动态修改线程池参数,官网 以下是dynamic-tp与nacos集成的步骤,更详细配置项可以查看官网

  • 引入依赖坐标
<dependency>
    <groupId>org.dromara.dynamictp</groupId>
    <artifactId>dynamic-tp-spring-cloud-starter-nacos</artifactId>
    <version>1.1.3</version>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.dromara.dynamictp</groupId>
    <artifactId>dynamic-tp-extension-skywalking</artifactId>
    <version>1.1.3</version>
</dependency>
  • 添加配置项
spring:
  dynamic:
    tp:
      enabled: true                               # 是否启用 dynamictp,默认true
      enabledCollect: true                        # 是否开启监控指标采集,默认true
      collectorTypes: internal_logging          # 监控数据采集器类型(logging | micrometer | internal_logging | JMX),默认micrometer
      monitorInterval: 5                       # 监控时间间隔(报警检测、指标采集),默认5s
      executors:                               # 动态线程池配置,都有默认值,采用默认值的可以不配置该项,减少配置量
        - threadPoolName: dtpExecutor1         # 线程池名称,必填
          threadPoolAliasName: 测试线程池        # 线程池别名,可选
          executorType: common                 # 线程池类型 common、eager、ordered、scheduled、priority,默认 common
          corePoolSize: 6                      # 核心线程数,默认1
          maximumPoolSize: 8                   # 最大线程数,默认cpu核数
          queueCapacity: 2000                  # 队列容量,默认1024
          queueType: VariableLinkedBlockingQueue         # 任务队列,查看源码QueueTypeEnum枚举类,默认VariableLinkedBlockingQueue
          rejectedHandlerType: CallerRunsPolicy          # 拒绝策略,查看RejectedTypeEnum枚举类,默认AbortPolicy
          keepAliveTime: 60                              # 空闲线程等待超时时间,默认60
          threadNamePrefix: test                         # 线程名前缀,默认dtp
          allowCoreThreadTimeOut: false                  # 是否允许核心线程池超时,默认false
          waitForTasksToCompleteOnShutdown: true         # 参考spring线程池设计,优雅关闭线程池,默认true
          awaitTerminationSeconds: 5                     # 优雅关闭线程池时,阻塞等待线程池中任务执行时间,默认3,单位(s)
          preStartAllCoreThreads: false                  # 是否预热所有核心线程,默认false
          runTimeout: 200                                # 任务执行超时阈值,单位(ms),默认0(不统计)
          queueTimeout: 100                              # 任务在队列等待超时阈值,单位(ms),默认0(不统计)
          taskWrapperNames: ["swTrace"]                  # 任务包装器名称,继承TaskWrapper接口
          notifyEnabled: false                           # 是否开启报警,默认true

nacos作为配置中心

  • 引入依赖坐标
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
    <version>2.2.9.RELEASE</version>
</dependency>
  • 新增bootstrap.yml
spring:
  application:
    name: TEST-CONFIG
  cloud:
    nacos:
      config:
         server-addr: 127.0.0.1:8848
         username: root
         password: root
         file-extension: yaml
         extension-configs:
           - dataId: ${spring.application.name}.${spring.cloud.nacos.config.file-extension}
             group: DEFAULT_GROUP
             refresh: true
         refresh-enabled: true

---
spring:
  profiles: test
  cloud:
    nacos:
      config:
        namespace: test

guava-retry重试

  • 引入依赖坐标
<dependency>
    <groupId>com.github.rholder</groupId>
    <artifactId>guava-retrying</artifactId>
    <version>2.0.0</version>
</dependency>
  • 创建重试器
private static final Retryer<TestDTO> retryer;

static {
    retryer = RetryerBuilder.<TestDTO>newBuilder()
        // 重试条件
        .retryIfException() // 抛出异常时重试
        .retryIfResult(Predicates.isNull()) // 返回结果为null时重试
        // 等待策略:每次请求间隔3s
        .withWaitStrategy(WaitStrategies.fixedWait(3, TimeUnit.SECONDS))
        // 停止策略:尝试请求5次
        .withStopStrategy(StopStrategies.stopAfterAttempt(5))
        .build();
}
  • 使用重试
Callable<TestDTO> callable = new Callable<TestDTO>() {
    int times = 0;
    @Override
    public TestDTO call() throws Exception {
        times++;
        log.info("第{}次请求", times);
        try {
            // TODO 具体逻辑
            return new TestDTO();
        } catch (Exception e) {
            log.error("请求失败", e);
            return null;
        }
    }
};
try {
    retryer.call(callable);
} catch (Exception e) {
    log.error("重试请求异常", e);
}

hystrix降级和ribben超时时间

# ribbon的超时时间
ribbon:
  ReadTimeout: 9000 # 默认1秒
  ConnectTimeout: 9000 # 默认1秒
  # 配置需要立即加载的其他服务(饿加载)
  eager-load:
    enabled: true
    clients: TEST-JOB

feign:
  hystrix:
    enabled: true  # 开启hystrix,老版本配置,启动类上使用@EnableHystrix 注解

feign:
  circuitbreaker:
    enabled: true    # 开启hystrix,新版本配置,启动类上使用@EnableCircuitBreaker 注解


# hystrix降级的超时时间
hystrix:
  command:
    default: # 全局使用配置超时时间 只针对具体服务可以写服务名
      execution:
        timeout:
          enabled: true
        isolation:
          thread:
            timeoutInMilliseconds: 9000 # 默认1秒 一般Hystrix 的超时时间必须不小于 ribbon 设置的超时时间,这是为了ribbon的重试机制有时间执行
  threadpool:
    TEST-CONFIG: # 针对具体服务设置线程池 默认全局使用default
      coreSize: 10 # 核心线程数 默认10
      maximumSize: 20 # 最大线程数 默认10
      maxQueueSize: 20 # 队列最大长度 默认-1
      allowMaximumSizeToDivergeFromCoreSize: true # 允许最大线程数超过核心线程数 默认false
      queueSizeRejectionThreshold: 20 # 队列大小拒绝阈值 默认5

ribbon默认重试1次,即当超过ribbon时间后会再次执行一次,可以设置重试次数或者关闭。很多时候我们认为feign调用有重试,实际上是因为openFeign集成了ribbon并开启了重试。

hystrix 设置降级超时时间后,如果没有配置 fallback,那么 hystrix 的超时就不会生效,而是由 ribbon 来控制

hystrix降级

// feign调用中使用降级,定义RemoteUserFallbackFactory类实现ExampleService接口实现降级方法
@FeignClient(value = "服务名", fallbackFactory = RemoteUserFallbackFactory.class)

// 如果其他方法上使用@HystrixCommand 注解,并指定了 fallbackMethod 属性,该属性指定了当该方法执行失败时的回退方法
@HystrixCommand(fallbackMethod="fallback", commandProperties = {
  @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1000" ),
  @HystrixProperty(name = "execution.timeout.enabled", value = "true")
})

// 当然也可以指定commandKey,然后在配置文件中配置超时时间,如下:
@HystrixCommand(fallbackMethod="fallback",commandKey="userGetKey")
// 配置文件如下
hystrix:
  command:
    userGetKey:
      execution:
        timeout:
          enabled: true
        isolation:
          thread:
            timeoutInMilliseconds: 1000

实际上feign也有超时时间,默认feign连接超时时间是10秒,读取超时时间是60秒。

feign:  
  client:  
    config:  
      default:  
        connectTimeout: 6000  
        readTimeout: 6000  
      具体服务名:  
        connectTimeout: 6000  
        readTimeout: 6000

# 最新版本的设置是Spring下openFeign,这种是用的OpenFeign,因为Springcloud不兼容feign了。上边的不生效可以试试
spring:
  cloud:
    openfeign:
      client:
        config:
          default:
            connectTimeout: 6000
            readTimeout: 6000

注意:OpenFeign 和 Ribbon 的超时时间只会有一个生效两者是二选一的,且 OpenFeign 有配置则优先生效,未配置则使用 Ribbon 配置

curl

curl -H 'Content-Type: application/json' -X POST -o --location 'https://www.xxx.com' -d '{"aooliType": "0"}'
-H: 请求头 
-X: 请求方式 
-d: 请求体
-o --location: 如果有些https地址不安全可以添加该参数跳过验证
curl -o /dev/null -s -w "total time: %{time_total} seconds\n"  命令中加入这些参数可以测试curl调用url用时多少秒

traceId和tid

作为分布式链路追踪技术中,一般使用有spring-cloud-sleuth(zipkin),skywalking等等。traceId是sleuth中的概念,而在skywalking中类似概念的是tid,它们都表示一个链路的唯一id,可以通过这个id将一次微服务调用串联起来

spring-cloud-sleuth

  • 引入依赖坐标
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-sleuth</artifactId>
    <version>2.2.1.RELEASE</version>
</dependency>
  • 修改日志文件(logback.spring.xml)
<!-- 输出日志到文件 -->
<appender name="rollFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <!-- 正在记录的日志文件的路径及文件名 -->
    <file>${app.log_dir}/log_error.log</file>
    <!--日志文件输出格式 X-B3-TraceId X-B3-SpanId X-B3-ParentSpanId是sleuth中固定的名词-->
    <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
        <layout class="ch.qos.logback.classic.PatternLayout">
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [${appName},%x{X-B3-TraceId},%x{X-B3-SpanId},%x{X-B3-ParentSpanId}] --[%thread] %logger %msg</pattern>
        </layout>
        <!-- 此处设置字符集 -->
        <charset>UTF-8</charset>
    </encoder>
    <!-- 日志记录器指定拆分策略,按日期,按大小记录 -->
    <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
        <!--按照时间拆分文件名 如果以.gz或.zip结尾可以压缩-->
        <fileNamePattern>${log.path}/error/log-error-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
        <!--按照文件大小拆分-->
        <maxFileSize>100MB</maxFileSize>
        <!--日志文件保留天数-->
        <maxHistory>15</maxHistory>
    </rollingPolicy>
</appender>
  • 以上配置代码中输出log即可,但是在多线程中需要将TraceId传过去
@Autowired
private Tracer tracer;

public void test(String[] args) {
    Span span = tracer.currentSpan();
    CompletableFuture.supplyAsync(() -> {
        tracer.withSpanInScope(span);
        log.info("业务逻辑开始");
        // TODO 业务逻辑
        return new TestDTO();
    }).exceptionally(throwable -> {
        log.error("请求失败", throwable);
        return null;
    });
}

skywalking

  • 引入依赖坐标
<dependency>
    <groupId>org.apache.skywalking</groupId>
    <artifactId>apm-toolkit-logback-1.x</artifactId>
    <version>8.7.0</version>
</dependency>
<dependency>
    <groupId>org.apache.skywalking</groupId>
    <artifactId>apm-toolkit-trace</artifactId>
    <version>8.11.0</version>
</dependency>
  • 修改日志文件(logback.spring.xml)
<!-- 输出日志到文件 -->
<appender name="rollFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <!-- 正在记录的日志文件的路径及文件名 -->
    <file>${app.log_dir}/log_error.log</file>
    <!--日志文件输出格式 tid是skywalking中固定的名词-->
    <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
        <layout class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.TraceIdPatternLogbackLayout">
           <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%tid] --[%thread] %logger %msg</pattern>
        </layout>
        <!-- 此处设置字符集 -->
        <charset>UTF-8</charset>
    </encoder>
    <!-- 日志记录器指定拆分策略,按日期,按大小记录 -->
    <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
        <!--按照时间拆分文件名 如果以.gz或.zip结尾可以压缩-->
        <fileNamePattern>${log.path}/error/log-error-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
        <!--按照文件大小拆分-->
        <maxFileSize>100MB</maxFileSize>
        <!--日志文件保留天数-->
        <maxHistory>15</maxHistory>
    </rollingPolicy>
</appender>
  • 以上配置代码中输出log即可,但是在多线程中需要处理下
CompletableFuture.supplyAsync(SupplierWrapper.of(() -> {
    log.info("业务逻辑开始");
    // TODO 业务逻辑
    return new TestDTO();
})).exceptionally(FunctionWrapper.of(throwable -> {
    log.error("请求失败", throwable);
    return null;
}));

// 如果多线程不需要返回值
CompletableFuture.runAsync(RunnableWrapper.of(() -> {
    log.info("业务逻辑开始");
    // TODO 业务逻辑
}));

skywalking是在jar启动时通过javaagent参数执行的

-javaagent:/home/appadmin/skywalking_agent/skywalking-agent.jar -Dskywalking.agent.service_name=TEST_CONFIG -Dskywalking.agent.instance_name=TEST_CONFIG_${ip}_${PORT}
指定jar包路径以及服务名和实例名

jasypt加密

整合SpringBoot项目

  • 引入依赖坐标
<dependency>
    <groupId>com.github.ulisesbocchio</groupId>
    <artifactId>jasypt-spring-boot-starter</artifactId>
    <version>2.1.0</version>
</dependency>
  • 启动参数添加jasypt密钥

本地启动:Idea Program arguments添加参数--jasypt.encryptor.password=密钥

服务器启动:SpringBoot启动参数添加--jasypt.encryptor.password=密钥

  • 通过代码生成密文,将生成的密文替换为原本的密码,格式为ENC(密文)

使用Telnet远程登陆执行命令

Telnet一般可用于查看ip+port是否可以连通,实际上Telnet和SSH一样均为用于访问远程计算机的通信协议,以实现远程控制和维护。Telnet在建立连接时直接传输明文数据, SSH传输的数据都经过加密处理, 提供了Telnet所不具备的安全性保障。

以下是使用Telnet远程执行redis指令流程

telnet ip 3306

ctrl+] # 进入输入命令模式,注意ctrl+]之后需要先输入一个回车才能输入其他命令

AUTH redis密码

select 0 # 选择redis库 redis有0~15个库

ping # pong ping/pong验证连接是否正常

redis其他指令...

Spring中的一些接口

ApplicationContextAware接口  setApplicationContext()方法:可以获取Spring容器applicaitonContext

InitializingBean接口 afterPropertiesSet()方法:在bean中属性初始化后的处理方法,凡是继承该接口的类,在bean的属性初始化后都会执行该方法

ApplicationRunner 和 CommandLineRunner接口 run()方法:可以在应用启动后执行一些代码。二者之间的主要区别在于它们对输入参数的处理方式。
CommandLineRunner 直接接收一个 String[] 数组作为参数,这意味着开发者需要自己解析和处理这些参数。而 ApplicationRunner 则接收一个 ApplicationArguments 对象,该对象封装了启动参数,并提供了一些有用的方法来方便地处理这些参数

CompletionService

CompletionService 是 Java 中用于处理一批异步任务的工具类,它允许你以异步的方式提交任务,并在任务完成时按照完成顺序获取结果。CompletionService 的底层原理主要基于阻塞队列和线程池。

CompletionService 使用一个阻塞队列来保存已完成的任务。当一个任务完成时,它会被放入队列中。阻塞队列的选择通常是 LinkedBlockingQueue,它是一个先进先出的队列,确保按照任务完成的顺序排列。

CompletionService 通常与 Executor 框架一起使用。创建一个线程池并将其传递给 CompletionService 的构造函数。这个线程池负责执行提交的任务。

当想要获取已完成的任务的结果时,可以调用 CompletionService 的 take() 或 poll() 方法。这些方法会从阻塞队列中取出已完成的任务的 Future,并返回它。如果队列为空,take() 方法会阻塞,而 poll() 方法会返回 null。

List<Future<Result>> resultFutureList = new ArrayList<>();
ExecutorService executorService = Executors.newFixedThreadPool(5);
ExecutorCompletionService<String> completionService = new ExecutorCompletionService<>(executorService);
try {
  // callable数量
  for (Callable<T> task : tasks) {
    resultFutureList.add(completionService.submit(task));
  }

  // 获取执行结果
  for (Callable<T> task : tasks) {
    // 设置最多1分钟的获取任务等待时间 避免长时间耗在这里
    Future<Result> resultFuture = completionService.poll(1, TimeUnit.MILLTSECONDS);
    if (resultFuture == null) {
        ...
        break;
    }
    Result result = resultFuture.get();
    ...
  }
} finally {
  executorService.shutdown();
}

CompletableFuture

Java 8 引入CompletableFuture 类可以解决Future 的这些缺陷。CompletableFuture 除了提供了更为好用和强大的 Future 特性之外,还提供了函数式编程、异步任务编排组合(可以将多个异步任务串联起来,组成一个完整的链式调用)等能力。

List<CompletableFuture<Result>> futureList = new ArrayList<>(2);

futureList.add(CompletableFuture.supplyAsync(() -> {
  // 任务1
})).exceptionally(throwable -> {
  // 异常处理
});

futureList.add(CompletableFuture.supplyAsync(() -> {
  // 任务2
})).exceptionally(throwable -> {
  // 异常处理
});

for (CompletableFuture<Result> future : futureList) {
  Result result = future.get();
}

实现CountDownLatch效果

CompletableFuture<Void> future1 = CompletableFuture.runAsync(() -> {
   // 任务1
}, pool);
CompletableFuture<Void> future2 = CompletableFuture.runAsync(() -> {
    // 任务2
}, pool);

CompletableFuture.allOf(future1, future2).join();

supplyAsync方法会有返回值,这个值最终会成为这个 CompletableFuture 对象的结果。runAsync方法没有返回值,所以 runAsync 返回的 CompletableFuture 对象通过 get 方法总是返回的是 null

posted @ 2024-07-14 19:30  OverZeal  阅读(23)  评论(0编辑  收藏  举报