Spring Cloud Kubernetes 实践 服务注册发现、服务动态配置

一、Spring Cloud Kubernetes

随着云计算和微服务架构的不断发展,k8sSpring Cloud成为了当今技术领域的两大热门话题。k8s作为一个开源的容器编排平台,已经在自动化部署、扩展和管理方面取得了巨大的成功,而Spring Cloud则以其丰富的生态和语言亲和性,为Java开发者提供了便利的微服务框架支持。在现如今的技术趋势下,两者融合已经成为了必要的方向。

首先,k8s的火热程度不言而喻。它不仅支持多种编程语言和框架,还提供了强大的自动化部署、扩展和管理能力,使得开发者可以根据自己的需求进行定制。此外,k8s的强大自动化能力也为开发、测试和运维团队带来了便利。

Spring Cloud作为微服务框架,提供了丰富的组件和工具,如服务注册与发现、负载均衡、熔断器等,可以帮助开发者快速构建微服务应用。并且针对Java开发者,提供了许多开箱即用的功能。然而,Spring Cloud主要适用于Java语言,对其他语言的支持有限,且在某些情况下可能显得有些重量级。

在面对这样的技术选择时,将k8sSpring Cloud融合使用是一个理想的选择。充分发挥它们各自的优势,构建更加稳定和高效的微服务架构。利用k8s进行容器的编排和部署,实现自动化管理。例如,通过k8sService机制,可以实现服务发现和负载均衡;通过k8sConfigMapSecrets,可以实现配置管理;通过k8sIngressGateway,可以实现智能路由。

在如此背景下 Sping Cloud 提供了 Spring Cloud Kubernetes 子项目,旨在将Spring Cloudk8s 结合起来。Spring Cloud Kubernetes 在保持原有开发模式的同时,几乎无感的做到对接 k8s ,使得在k8s环境中运行Spring Cloud应用程序变得更加容易和高效。例如:允许应用程序自动发现k8s集群中的其他服务,从而使得服务间通信更加便捷,更加高效实现服务发现与注册。还支持从k8sConfigMapSecrets中读取配置信息,并自动更新应用程序中的配置属性。

官方文档如下:

https://docs.spring.io/spring-cloud-kubernetes/reference/3.1-SNAPSHOT/index.html

下面使用 Spring Cloud Kubernetes 搭建两个 SpringBoot 服务,分别作为服务提供者、服务消费者实现服务的注册和发现,并且在消费者端使用 ConfigMap 作为动态配置。

角色replicas类型动态配置
provider2Deploymentconsumerconfig
consumer2Deployment

新建一个 Maven 项目作为父组件,在该项目中再创建两个SpringBoot Model

首先在父 pom 中增加依赖,声明SpringBootStringCloud 的版本:

<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">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>nacos-parent</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>pom</packaging>

    <name>service-parent</name>
    <url>http://maven.apache.org</url>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <spring-boot.version>2.3.7.RELEASE</spring-boot.version>
        <spring-cloud.version>Hoxton.SR9</spring-cloud.version>
    </properties>

    <modules>
        <module>provider-service</module>
        <module>consumer-service</module>
    </modules>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
</project>

二、服务提供者搭建

新建 SpringBoot model ,修改 pom 内容如下:

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.example</groupId>
    <artifactId>provider-service</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>provider-service</name>
    <description>provider-service</description>
    <parent>
        <groupId>org.example</groupId>
        <artifactId>nacos-parent</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    </properties>

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

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

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-kubernetes</artifactId>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>${spring-boot.version}</version>
                <configuration>
                    <mainClass>com.example.providerservice.ProviderServiceApplication</mainClass>
                </configuration>
                <executions>
                    <execution>
                        <id>repackage</id>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

修改 application.yml

server:
  port: 8091

spring:
  application:
    name: provider

启动类增加 @EnableDiscoveryClient 注解:

@EnableDiscoveryClient
@SpringBootApplication
public class ProviderServiceApplication {

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

}

创建一个测试接口,后面在服务消费端通过 OpenFeign 调用:

@RestController
public class ProviderController {

    /**
     * 提供服务接口
     */
    @GetMapping("/rpc")
    public String rpc() {
        return "provider from : " + getCurrentHostName();
    }

    /**
     * 获取当前系统名称
     */
    private String getCurrentHostName(){
        try {
            InetAddress inetAddress = InetAddress.getLocalHost();
            return inetAddress.getHostName();
        } catch (UnknownHostException e) {
            e.printStackTrace();
        }
        return null;
    }

}

建立 Dockerfile 文件,内容如下:

FROM java:8
MAINTAINER bxc
WORKDIR /app
ADD target/provider-service-0.0.1-SNAPSHOT.jar /app/app.jar
CMD ["java", "-jar", "app.jar"]

在这里插入图片描述

三、服务消费者搭建

消费者端通过 OpenFeign 调用服务提供者端的接口信息,并使用 ConfigMap 动态配置。

新建 SpringBoot model ,修改 pom 内容如下:

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.example</groupId>
    <artifactId>consumer-service</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>consumer-service</name>
    <description>consumer-service</description>
    <parent>
        <groupId>org.example</groupId>
        <artifactId>nacos-parent</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    </properties>

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

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

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-kubernetes</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-kubernetes-ribbon</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-kubernetes-config</artifactId>
        </dependency>

        <!--openfeign-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-actuator-autoconfigure</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>${spring-boot.version}</version>
                <configuration>
                    <mainClass>com.example.consumerservice.ConsumerServiceApplication</mainClass>
                </configuration>
                <executions>
                    <execution>
                        <id>repackage</id>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

修改配置文件,注意不要写在application.yml 中,新建一个bootstrap.yml文件,用来在程序引导时执行:

server:
  port: 8080

spring:
  application:
    name: consumer
  cloud:
    kubernetes:
      config:
        name: ${CONFIG_NAME:consumerconfig}
        namespace: ${NAMESPACE:cloud}
      reload:
        # 是否自动更新
        enabled: true
        # 监控configMap变化
        monitoring-config-maps: true
        # 监控secrets变化
        monitoring-secrets: true
        # 加载策略
        # refresh:只重新加载用@ConfigurationProperties或@RefreshScope注释的配置bean。此重新加载级别利用了Spring Cloud Context的刷新功能。
        # restart_context:整个Spring ApplicationContext被优雅地重新启动。使用新配置重新创建bean。为了使重启上下文功能正常工作,您必须启用并公开restart端点
        # shutdown:关闭Spring ApplicationContext以激活容器的重新启动。使用此级别时,请确保所有非守护进程线程的生命周期都绑定到ApplicationContext,并且已配置复制控制器或副本集以重新启动pod。
        strategy: restart_context
        #更新配置信息的模式:polling:主动拉取,event:事件通知
        mode: event
        #如果主动拉取,拉取的时间间隔,单位毫秒
        #period: 500

management:
  endpoint:
    restart:
      enabled: true
  endpoints:
    web:
      exposure:
        include: restart # 如果 strategy 是 restart_context ,这个端点需要开启

这里配置文件的名称和命名空间通过环境变量动态传递进来。

启动类增加 @EnableDiscoveryClient@EnableFeignClients 注解:

@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication
public class ConsumerServiceApplication {

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

}

使用 FeignClient 声明服务提供者的接口:

@Component
@FeignClient(value = "provider")
public interface ProviderClient {

    @GetMapping("/rpc")
    String rpc();

}

创建动态配置类:

@Component
@ConfigurationProperties(prefix = "param")
@Data
@RefreshScope
public class ParamProperties {
    private String text;
    private String name;
}

创建测试接口,分别测试 discoveryrpc 调用、动态配置:

@RestController
@RefreshScope
public class ConsumerController {

    @Resource
    ProviderClient providerClient;

    @Value("${param.param1:tmp}")
    private String param;

    @Resource
    private DiscoveryClient discoveryClient;

    @Resource
    ParamProperties paramProperties;

    /**
     * 查看 Discovery 信息
     */
    @GetMapping("/discoveryInfo")
    public Map<String, List<ServiceInstance>> getDiscoveryInfo() {
        Map<String, List<ServiceInstance>> result = new HashMap<>();
        discoveryClient.getServices().forEach(service -> {
            List<ServiceInstance> instances = discoveryClient.getInstances(service);
            result.put(service,instances);
        });
        return result;
    }

    /**
     * 测试RPC调用
     */
    @GetMapping("/rpc")
    public String rpc() {
        String providerData = providerClient.rpc();
        return "sunncess: consumer from : " + getCurrentHostName() + " ; " + providerData;
    }

    /**
     * 测试动态配置
     */
    @GetMapping("/config")
    public String getConfig() {
        return "success: current config : " + param+": "+paramProperties.toString();
    }

    /**
     * 获取当前系统名称
     */
    private String getCurrentHostName() {
        try {
            InetAddress inetAddress = InetAddress.getLocalHost();
            return inetAddress.getHostName();
        } catch (UnknownHostException e) {
            e.printStackTrace();
        }
        return null;
    }

}

建立 Dockerfile 文件,内容如下:

FROM java:8
MAINTAINER bxc
WORKDIR /app
ADD target/consumer-service-0.0.1-SNAPSHOT.jar /app/app.jar
CMD ["java", "-jar", "app.jar"]

四、部署到 K8s

4.1 打包上传镜像

这里我将镜像打包后上传至私有的 harbor 仓库中,如果没有 harbor 仓库也可以将镜像上传至每个 k8s node 节点中,然后使用 docker load 到本地镜像仓库中。

# 打包成 jar 包
mvn clean package
# 构建镜像
cd consumer-service
docker build -t consumer-service:1.0 .
# 上传至 harbor
docker tag consumer-service:1.0 11.0.1.150/image/consumer-service:1.0 .
docker push 11.0.1.150/image/consumer-service:1.0

# 构建镜像
cd ../provider-service
docker build -t provider-service:1.0 .
# 上传至 harbor
docker tag provider-service:1.0 11.0.1.150/image/provider-service:1.0 .
docker push 11.0.1.150/image/provider-service:1.0

4.2 k8s 部署服务

首先创建 cloud 命名空间,后续所有服务都建立在该命名空间下:

kubectl create ns cloud

由于默认的 ServiceAccount 是没有权限访问K8SAPI Server资源的,直接启动服务读取 ConfigMap 会报错 Forbidden!Configured service account doesn't have access. ,所以在启动服务前,先创建一个分配权限的 ServiceAccount,但是需要注意:由于指定了 ServiceAccount,该 ServiceAccount 同时也需要给 Services 的权限,不然 discoveryInfo 读取不到 Service 的信息:

vi role.yml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: config-reader
  namespace: cloud
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: cloud
  name: pod-reader
rules:
  - apiGroups: [""]
    resources: ["pods","configmaps","secrets","endpoints","services"]
    verbs: ["get", "watch", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: config-reader-rolebinding
  namespace: cloud
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: pod-reader
  namespace: cloud
subjects:
  - kind: ServiceAccount
    name: config-reader
    namespace: cloud

kubectl apply -f role.yml

查看:
在这里插入图片描述

下面部署服务,注意:服务需要指定上面创建的 ServiceAccount ,不然没有权限获取 ConfigMap 配置以及 Service 信息。

vi cloud.yml
apiVersion: v1
kind: Service
metadata:
  name: consumer
  namespace: cloud
  labels:
    app: consumer
spec:
  type: NodePort
  ports:
    - port: 8080
      name: client
      nodePort: 31880
      targetPort: 8080
  selector:
    app: consumer

---
kind: ConfigMap
apiVersion: v1
metadata:
  name: consumerconfig
  namespace: cloud
data:
  application.yml: |-
    param:
      param1: abc
      text: textabc
      name: nameabc

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: consumer
  namespace: cloud
spec:
  replicas: 2
  selector:
    matchLabels:
      app: consumer
  template:
    metadata:
      labels:
        app: consumer
    spec:
      serviceAccountName: config-reader
      containers:
        - name: consumer
          image: consumer-service:1.0
          ports:
            - containerPort: 8080
              name: server
          env:
            - name: CONFIG_NAME
              value: consumerconfig
            - name: NAMESPACE
              value: cloud

---
apiVersion: v1
kind: Service
metadata:
  name: provider
  namespace: cloud
  labels:
    app: provider-svc
spec:
  type: ClusterIP
  ports:
    - port: 8091
      targetPort: 8091
  selector:
    app: provider

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: provider
  namespace: cloud
spec:
  replicas: 2
  selector:
    matchLabels:
      app: provider
  template:
    metadata:
      labels:
        app: provider
    spec:
      serviceAccountName: config-reader
      containers:
        - name: provider
          image: provider-service:1.0
          ports:
            - containerPort: 8091
              name: server

注意这里配置信息 ConfigMap 的名字为 consumerconfig ,需要传递到服务消费者服务中,并且记住此时的配置内容:

在这里插入图片描述

启动服务:

kubectl apply -f cloud.yml

查看 Pod

kubectl get pods -n cloud

在这里插入图片描述

都处于 Running 状态,查看其中一个的启动日志,看是否正常启动:

kubectl logs consumer-77c674cc96-l58jv -n cloud

在这里插入图片描述

服务启动正常。

五、测试

5.1 服务注册发现测试

调用 http:{node ip}:31880/discoveryInfo 查看服务信息:

在这里插入图片描述

结果完整信息如下:

{
    "provider": [
        {
            "instanceId": "47d501db-646c-401a-9d84-63ef5909cd10",
            "serviceId": "provider",
            "host": "10.233.111.243",
            "port": 8091,
            "uri": "http://10.233.111.243:8091",
            "secure": false,
            "metadata": {
                "app": "provider-svc",
                "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"v1\",\"kind\":\"Service\",\"metadata\":{\"annotations\":{},\"labels\":{\"app\":\"provider-svc\"},\"name\":\"provider\",\"namespace\":\"cloud\"},\"spec\":{\"ports\":[{\"port\":8091,\"targetPort\":8091}],\"selector\":{\"app\":\"provider\"},\"type\":\"ClusterIP\"}}\n"
            },
            "namespace": null,
            "scheme": "http"
        },
        {
            "instanceId": "493dad3f-76b4-46a2-b882-3607ac3751f4",
            "serviceId": "provider",
            "host": "10.233.111.244",
            "port": 8091,
            "uri": "http://10.233.111.244:8091",
            "secure": false,
            "metadata": {
                "app": "provider-svc",
                "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"v1\",\"kind\":\"Service\",\"metadata\":{\"annotations\":{},\"labels\":{\"app\":\"provider-svc\"},\"name\":\"provider\",\"namespace\":\"cloud\"},\"spec\":{\"ports\":[{\"port\":8091,\"targetPort\":8091}],\"selector\":{\"app\":\"provider\"},\"type\":\"ClusterIP\"}}\n"
            },
            "namespace": null,
            "scheme": "http"
        }
    ],
    "consumer": [
        {
            "instanceId": "a21109cd-ca86-4b0d-a9ca-b8c0efa336c8",
            "serviceId": "consumer",
            "host": "10.233.111.245",
            "port": 8080,
            "uri": "http://10.233.111.245:8080",
            "secure": false,
            "metadata": {
                "app": "consumer",
                "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"v1\",\"kind\":\"Service\",\"metadata\":{\"annotations\":{},\"labels\":{\"app\":\"consumer\"},\"name\":\"consumer\",\"namespace\":\"cloud\"},\"spec\":{\"ports\":[{\"name\":\"client\",\"nodePort\":31880,\"port\":8080,\"targetPort\":8080}],\"selector\":{\"app\":\"consumer\"},\"type\":\"NodePort\"}}\n",
                "port.client": "8080"
            },
            "namespace": null,
            "scheme": "http"
        },
        {
            "instanceId": "591e43a8-6d30-4395-915e-570275ee0336",
            "serviceId": "consumer",
            "host": "10.233.111.246",
            "port": 8080,
            "uri": "http://10.233.111.246:8080",
            "secure": false,
            "metadata": {
                "app": "consumer",
                "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"v1\",\"kind\":\"Service\",\"metadata\":{\"annotations\":{},\"labels\":{\"app\":\"consumer\"},\"name\":\"consumer\",\"namespace\":\"cloud\"},\"spec\":{\"ports\":[{\"name\":\"client\",\"nodePort\":31880,\"port\":8080,\"targetPort\":8080}],\"selector\":{\"app\":\"consumer\"},\"type\":\"NodePort\"}}\n",
                "port.client": "8080"
            },
            "namespace": null,
            "scheme": "http"
        }
    ]
}

可以看到服务提供者和服务消费的基本信息都获取到了,其中的 ip 地址就是 pod 的地址:

kubectl get pods -n cloud -o wide

在这里插入图片描述

调用 http:{node ip}:31880/rpc 测试 RPC 远程调用,并且多次调用:

在这里插入图片描述

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

经过多次调用可以看到负载均衡效果。

5.2 服务动态配置测试

调用 http:{node ip}:31880/config 查看配置信息:

在这里插入图片描述

正常读取到 ConfigMap 中的配置信息,下面修改 ConfigMap 信息:

kubectl edit configmap consumerconfig -n cloud

修改信息如下:

在这里插入图片描述
然后保存后再次调用查看配置接口:

在这里插入图片描述

配置已生效。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

区间预测 | PSO-RF-KDE的粒子群优化随机森林结合核密度估计多变量回归区间预测(Matlab)

区间预测 | PSO-RF-KDE的粒子群优化随机森林结合核密度估计多变量回归区间预测&#xff08;Matlab&#xff09; 目录 区间预测 | PSO-RF-KDE的粒子群优化随机森林结合核密度估计多变量回归区间预测&#xff08;Matlab&#xff09;效果一览基本介绍程序设计参考资料 效果一览 基…

傲软录屏(ApowerREC)一款简单好用的录屏软件,中文破姐版 v1.6.9.6(240501)

软件介绍 傲软录屏&#xff0c;是由ApowerREC开发的一款高级录屏软件&#xff0c;兼容多个操作系统平台&#xff0c;包括Windows、Mac以及基于安卓和iOS的设备。这款专业工具具备捕捉各类屏幕活动的能力&#xff0c;确保音视频同步&#xff0c;无论用户是进行电脑桌面操作、参…

C++string类使用大全

目录 温馨提示&#xff1a;这篇文章有约两万字 什么是string类&#xff1f; 一. 定义和初始化string对象 1.string的构造函数的形式&#xff1a; 2.拷贝赋值运算符 3.assign函数 二.string对象上的操作 1.读写string对象 2.读取未知数量的string对象 3.使用getline …

软件工程毕业设计选题100例

文章目录 0 简介1 如何选题2 最新软件工程毕设选题3 最后 0 简介 学长搜集分享最新的软件工程业专业毕设选题&#xff0c;难度适中&#xff0c;适合作为毕业设计&#xff0c;大家参考。 学长整理的题目标准&#xff1a; 相对容易工作量达标题目新颖 1 如何选题 最近非常多的…

Mac brew安装Redis之后更新配置文件的方法

安装命令 brew install redis 查看安装位置命令 brew list redis #查看redis安装的位置 % brew list redis /usr/local/Cellar/redis/6.2.5/.bottle/etc/ (2 files) /usr/local/Cellar/redis/6.2.5/bin/redis-benchmark /usr/local/Cellar/redis/6.2.5/bin/redis-check-ao…

高级商务谈判口才培训教程(3篇)

高级商务谈判口才培训教程&#xff08;3篇&#xff09; 高级商务谈判口才培训教程&#xff08;**篇&#xff09;&#xff1a;基础篇 一、前言 在高级商务谈判中&#xff0c;口才不仅是交流的工具&#xff0c;更是策略执行的关键。本教程将从基础出发&#xff0c;带领大家逐步掌…

【PHP】安装指定版本Composer

1、下载指定版本composer.phar文件&#xff1a;https://github.com/composer/composer/releases 2、将下载的文件添加到全局路径&#xff1a; sudo mv composer.phar /usr/local/bin/composer 3、赋予权限&#xff1a; sudo chmod x /usr/local/bin/composer 4、查看compos…

52.HarmonyOS鸿蒙系统 App(ArkTS)配置文件添加多个权限方法

52.HarmonyOS鸿蒙系统 App(ArkTS)配置文件添加多个权限方法 module.json5

前端开发者如何在项目里控制修改组件的样式

1为了让自己快速下班&#xff0c;修改样式应该是占据大部分时间&#xff0c;在很多组件库的项目里&#xff0c;都会提到主题设置。 比如element的scss配置变量&#xff0c;通常有人喜欢直接引入css样式来快速完成任务&#xff0c;然后在全局覆盖这些选择器对应的样式&#xff0…

OpenCV(二)—— 车牌定位

从本篇文章开始我们进入 OpenCV 的 Demo 实战。首先&#xff0c;我们会用接下来的三篇文章介绍车牌识别 Demo。 1、概述 识别图片中的车牌号码需要经过三步&#xff1a; 车牌定位&#xff1a;从整张图片中识别出牌照&#xff0c;主要操作包括对原图进行预处理、把车牌从整图…

信号知识详解

目录 1、信号的产生 2、core 核心转储 3、信号的保存 4、信号的处理 信号是linux系统提供的&#xff0c;让用户或进程给其他进程发送异步信息的一种方式。 常见的信号处理方式&#xff1a; 1、默认行为 2、忽略 3、自定义 1、信号的产生 1、kill命令 我们可以使用命令 k…

过渡与动画

单元素/组件过渡 Vue在插入、更新或者移除 DOM 时&#xff0c;提供多种不同方式的过渡效果&#xff08;一个淡入淡出的效果&#xff09; 在条件渲染&#xff08;使用v-if&#xff09;、条件展示&#xff08;使用v-show&#xff09;、动态组件、组件根节点等情形中&#xff0c;可…

【火猫DOTA2】电竞世界杯DOTA2项目将在7月份的前三周举办

1、电竞世界杯将于今年7月3日至8月25日在沙特利雅得举办。近日主办方公布了各个项目的举办时间,其中DOTA2项目将在7月份的前三周举办。转载:火猫TV资讯https://www.huomaotv.com/ 目前Falcons、XG、GG和Liquid这五支赢得了足够EPT积分的队伍已经确定直邀沙特。剩下的三个名额还…

SpringBoot集成Kafka开发

4.SpringBoot集成Kafka开发 4.1 创建项目 4.2 配置文件 application.yml spring:application:name: spring-boot-01-kafka-basekafka:bootstrap-servers: 192.168.2.118:90924.3 创建生产者 package com.zzc.producer;import jakarta.annotation.Resource; import org.spri…

MATLAB 数据输出

MATLAB 数据输出 数据导出(或输出)在 MATLAB 的意思是写入文件。MATLAB 允许您在另一个读取 ASCII 文件的应用程序中使用您的数据。为此&#xff0c;MATLAB 提供了几个数据导出选项。 您可以创建以下类型的文件- 数组中的矩形、分隔的ASCII数据文件。 击键的日记&#xff08…

Linux系统安装Redis7(详细版)

Linux系统安装Redis7 一、windows安装redis二、Linux安装Redis下载redis编辑redis7.conf文件启动redis-server服务如何关闭redis服务设置Redis开机自启动 一、windows安装redis Window 下安装 下载地址&#xff1a;https://github.com/dmajkic/redis/downloads 下载到的Redi…

6.k8s中的secrets资源

一、Secret secrets资源&#xff0c;类似于configmap资源&#xff0c;只是secrets资源是用来传递重要的信息的&#xff1b; secret资源就是将value的值使用base64编译后传输&#xff0c;当pod引用secret后&#xff0c;k8s会自动将其base64的编码&#xff0c;反编译回正常的字符…

OpenCV(一) —— OpenCV 基础

1、OpenCV 简介 OpenCV&#xff08;Open Source Computer Vision Library&#xff09;是一个基于 BSD 许可开源发行的跨平台的计算机视觉库。可用于开发实时的图像处理、计算机视觉以及模式识别程序。由英特尔公司发起并参与开发&#xff0c;以 BSD 许可证授权发行&#xff0c…

【QT学习】14.线程学习

一。线程了解 线程是计算机科学中的一个重要概念&#xff0c;它是操作系统能够进行运算调度的最小单位。线程是进程中的一个执行流&#xff0c;一个进程可以包含多个线程。与进程相比&#xff0c;线程更轻量级&#xff0c;可以更高效地利用计算机资源。 线程有以下几个特点&…

vue3+ts 原生 js drag drop 实现

vue3ts 原生 js drag drop 实现 一直以来没有涉及的一个领域就是 drag drop 拖动操作&#xff0c;研究了下&#xff0c;实现了&#xff0c;所以写个教程。 官方说明页面及实例&#xff1a;https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API 最终效果&…
最新文章