当前位置: 首页 > news >正文

MicroVM-as-a-Service 后端服务架构设计与实现

MicroVM-as-a-Service 后端服务架构设计与实现

1. 引言

1.1 项目背景

随着云计算技术的快速发展,传统的虚拟机(VM)和容器技术在某些场景下已无法完全满足用户需求。传统虚拟机虽然提供了良好的隔离性,但启动速度慢、资源占用高;容器虽然轻量快速,但在多租户环境下的安全隔离性存在不足。MicroVM(微虚拟机)技术应运而生,它结合了传统虚拟机的安全隔离性和容器的轻量快速特性。

Firecracker是由亚马逊AWS开发的开源MicroVM管理程序,专为无服务器计算环境设计,具有轻量(内存开销<5MB)、快速(启动时间<125ms)和安全(使用KVM和Linux命名空间隔离)的特点。将Firecracker与Kubernetes结合,可以构建一个弹性的MicroVM-as-a-Service平台,为用户提供安全隔离、快速启动的计算环境。

1.2 目标与范围

本文旨在详细描述如何设计和实现一个基于Firecracker和Kubernetes的MicroVM-as-a-Service后端服务。该系统将提供以下核心功能:

  1. 多租户MicroVM生命周期管理(创建、启动、停止、删除)
  2. 资源配额与限制管理
  3. 网络与存储配置
  4. 监控与日志收集
  5. 安全隔离与认证授权

系统将采用微服务架构,主要组件包括API网关、调度器、Firecracker控制器、存储管理器、网络管理器等。

2. 系统架构设计

2.1 整体架构

+-------------------+     +-------------------+     +-------------------+
|      Client       |     |     Dashboard     |     |     CLI Tool      |
+-------------------+     +-------------------+     +-------------------+|                         |                         |v                         v                         v
+-----------------------------------------------------------------------+
|                             API Gateway                               |
| (Authentication, Rate Limiting, Request Routing, Load Balancing)     |
+-----------------------------------------------------------------------+|                         |                         |v                         v                         v
+-------------------+     +-------------------+     +-------------------+
|   Scheduler       |     |  Firecracker      |     |  Storage Manager  |
| (VM Placement,    |     |  Controller       |     | (Volume Provision, |
| Resource Matching)|     | (VM Lifecycle)    |     |  Snapshot)        |
+-------------------+     +-------------------+     +-------------------+|                         |                         |v                         v                         v
+-----------------------------------------------------------------------+
|                          Kubernetes Cluster                           |
| (Firecracker Operator, Custom Resources, Node Management)             |
+-----------------------------------------------------------------------+|v
+-------------------+
|  Infrastructure   |
| (Compute Nodes,   |
|  Network, Storage)|
+-------------------+

2.2 核心组件

2.2.1 API Gateway
  • 身份认证与授权(JWT/OAuth2)
  • 请求路由与负载均衡
  • 速率限制与配额管理
  • API版本管理
  • 请求/响应转换
2.2.2 Scheduler
  • 资源匹配与调度算法
  • 节点选择策略(亲和性/反亲和性)
  • 资源碎片整理
  • 负载均衡
2.2.3 Firecracker Controller
  • MicroVM生命周期管理
  • Firecracker配置生成
  • 状态同步与协调
  • 事件处理
2.2.4 Storage Manager
  • 持久卷管理
  • 快照管理
  • 存储配额
  • 存储后端抽象(本地/NFS/CEPH等)
2.2.5 Network Manager
  • 网络配置(CNI插件集成)
  • IP地址管理
  • 网络安全组
  • 服务暴露(LoadBalancer/NodePort)

2.3 数据流

  1. 用户通过REST API/CLI/Dashboard发起请求
  2. API Gateway验证请求并转发到相应服务
  3. Scheduler选择合适的K8s节点
  4. Firecracker Controller在目标节点创建MicroVM
  5. Storage Manager配置持久卷(如果需要)
  6. Network Manager配置网络接口和规则
  7. MicroVM状态更新并返回给用户

3. 详细设计与实现

3.1 Kubernetes集成

3.1.1 自定义资源定义(CRD)
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:name: microvms.microvm.service
spec:group: microvm.serviceversions:- name: v1alpha1served: truestorage: trueschema:openAPIV3Schema:type: objectproperties:spec:type: objectproperties:vcpu:type: integerminimum: 1maximum: 8memory:type: stringpattern: '^[1-8]Gi$'kernelImage:type: stringrootfs:type: objectproperties:image:type: stringsize:type: stringreadOnly:type: booleannetworkInterfaces:type: arrayitems:type: objectproperties:name:type: stringmac:type: stringip:type: stringvolumes:type: arrayitems:type: objectproperties:name:type: stringmountPath:type: stringreadOnly:type: booleanstatus:type: objectproperties:phase:type: stringip:type: stringnode:type: stringscope: Namespacednames:plural: microvmssingular: microvmkind: MicroVMshortNames:- mvm
3.1.2 Firecracker Operator

Operator是Kubernetes上管理有状态应用的推荐方式。我们将实现一个Firecracker Operator来管理MicroVM的生命周期。

package controllersimport ("context""fmt""reflect""github.com/go-logr/logr""k8s.io/apimachinery/pkg/runtime"ctrl "sigs.k8s.io/controller-runtime""sigs.k8s.io/controller-runtime/pkg/client"microvmv1alpha1 "github.com/microvm-service/api/v1alpha1"
)// MicroVMReconciler reconciles a MicroVM object
type MicroVMReconciler struct {client.ClientLog    logr.LoggerScheme *runtime.Scheme
}// +kubebuilder:rbac:groups=microvm.service,resources=microvms,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=microvm.service,resources=microvms/status,verbs=get;update;patchfunc (r *MicroVMReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {log := r.Log.WithValues("microvm", req.NamespacedName)var microvm microvmv1alpha1.MicroVMif err := r.Get(ctx, req.NamespacedName, &microvm); err != nil {log.Error(err, "unable to fetch MicroVM")return ctrl.Result{}, client.IgnoreNotFound(err)}// Handle MicroVM creation/updateif microvm.ObjectMeta.DeletionTimestamp.IsZero() {if !containsFinalizer(&microvm.ObjectMeta, "microvm.service/finalizer") {controllerutil.AddFinalizer(&microvm.ObjectMeta, "microvm.service/finalizer")if err := r.Update(ctx, &microvm); err != nil {return ctrl.Result{}, err}}// Reconcile the actual state with the desired stateif err := r.reconcileMicroVM(ctx, &microvm); err != nil {log.Error(err, "failed to reconcile MicroVM")return ctrl.Result{}, err}} else {// Handle MicroVM deletionif containsFinalizer(&microvm.ObjectMeta, "microvm.service/finalizer") {if err := r.cleanupMicroVM(ctx, &microvm); err != nil {log.Error(err, "failed to cleanup MicroVM")return ctrl.Result{}, err}controllerutil.RemoveFinalizer(&microvm.ObjectMeta, "microvm.service/finalizer")if err := r.Update(ctx, &microvm); err != nil {return ctrl.Result{}, err}}}return ctrl.Result{}, nil
}func (r *MicroVMReconciler) reconcileMicroVM(ctx context.Context, microvm *microvmv1alpha1.MicroVM) error {// 1. Check if Firecracker process exists// 2. If not, create Firecracker VM with desired configuration// 3. Update MicroVM status// 4. Handle any configuration changesreturn nil
}func (r *MicroVMReconciler) cleanupMicroVM(ctx context.Context, microvm *microvmv1alpha1.MicroVM) error {// 1. Stop Firecracker process// 2. Clean up network interfaces// 3. Remove any temporary filesreturn nil
}func (r *MicroVMReconciler) SetupWithManager(mgr ctrl.Manager) error {return ctrl.NewControllerManagedBy(mgr).For(&microvmv1alpha1.MicroVM{}).Complete(r)
}
3.1.3 DaemonSet部署模式

Firecracker需要在每个工作节点上运行,我们使用DaemonSet来部署Firecracker管理组件:

apiVersion: apps/v1
kind: DaemonSet
metadata:name: firecracker-runtimenamespace: microvm-system
spec:selector:matchLabels:app: firecracker-runtimetemplate:metadata:labels:app: firecracker-runtimespec:hostPID: truecontainers:- name: firecracker-runtimeimage: microvm-service/firecracker-runtime:latestsecurityContext:privileged: truecapabilities:add: ["CAP_NET_ADMIN", "CAP_SYS_ADMIN"]volumeMounts:- name: dev-kvmmountPath: /dev/kvm- name: firecracker-socketmountPath: /var/run/firecracker- name: var-libmountPath: /var/lib/firecrackervolumes:- name: dev-kvmhostPath:path: /dev/kvm- name: firecracker-sockethostPath:path: /var/run/firecracker- name: var-libhostPath:path: /var/lib/firecracker

3.2 Firecracker集成

3.2.1 Firecracker启动流程
  1. 准备Kernel和RootFS镜像
  2. 生成Firecracker配置文件
  3. 通过Unix socket启动Firecracker进程
  4. 配置网络接口
  5. 启动MicroVM
func startFirecrackerVM(config *FirecrackerConfig) error {// 1. Prepare kernel and rootfsif err := prepareBootFiles(config); err != nil {return fmt.Errorf("failed to prepare boot files: %v", err)}// 2. Generate Firecracker configfcConfig := generateFirecrackerConfig(config)configBytes, err := json.Marshal(fcConfig)if err != nil {return fmt.Errorf("failed to marshal firecracker config: %v", err)}// 3. Create Firecracker processcmd := exec.Command("firecracker", "--api-sock", config.SocketPath)cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true,}if err := cmd.Start(); err != nil {return fmt.Errorf("failed to start firecracker: %v", err)}// 4. Configure VM via APIclient := firecracker.NewClient(config.SocketPath, nil, false)// Boot sourceif _, err := client.PutBootSource(context.Background(), &fcConfig.BootSource); err != nil {return fmt.Errorf("failed to configure boot source: %v", err)}// Network interfacesfor _, iface := range fcConfig.NetworkInterfaces {if _, err := client.PutGuestNetworkInterfaceByID(context.Background(), iface.ID, &iface); err != nil {return fmt.Errorf("failed to configure network interface %s: %v", iface.ID, err)}}// Drivesfor _, drive := range fcConfig.Drives {if _, err := client.PutGuestDriveByID(context.Background(), drive.ID, &drive); err != nil {return fmt.Errorf("failed to configure drive %s: %v", drive.ID, err)}}// 5. Start the VMif _, err := client.PutGuestAction(context.Background(), &firecrackermodels.InstanceActionInfo{ActionType: ptr.String("InstanceStart"),}); err != nil {return fmt.Errorf("failed to start instance: %v", err)}return nil
}
3.2.2 网络配置

使用CNI(Container Network Interface)插件为MicroVM配置网络:

func configureNetwork(namespace, podName, containerID, ifName, netnsPath string) (*current.Result, error) {netConf := &libcni.NetworkConfigList{Name: "firecracker-cni",Plugins: []*libcni.NetworkConfig{{Network: &types.NetConf{Type: "bridge",Bridge: "fc-br0",IPAM: &types.IPAM{Type: "host-local",Subnet: "10.100.0.0/16",Gateway: "10.100.0.1",},},},},}rt := &libcni.RuntimeConf{ContainerID: containerID,NetNS:       netnsPath,IfName:      ifName,}// Invoke CNI pluginres, err := libcni.ExecPluginWithResult("/opt/cni/bin/bridge",netConf.Bytes,rt)if err != nil {return nil, fmt.Errorf("failed to invoke CNI plugin: %v", err)}result, err := current.NewResultFromResult(res)if err != nil {return nil, fmt.Errorf("failed to parse CNI result: %v", err)}return result, nil
}
3.2.3 存储配置

支持多种存储后端:

  1. 临时存储: 使用节点本地存储,生命周期与MicroVM相同
  2. 持久卷: 使用Kubernetes PV/PVC
  3. 只读根文件系统: 使用容器镜像
func prepareRootFS(image string, size string, readOnly bool) (string, error) {if readOnly {// For read-only rootfs, we can directly use the container imagereturn extractContainerImage(image)} else {// For writable rootfs, create a copy-on-write overlayreturn createOverlayRootFS(image, size)}
}func createOverlayRootFS(baseImage, size string) (string, error) {// 1. Extract base imagebasePath, err := extractContainerImage(baseImage)if err != nil {return "", err}// 2. Create overlay directoriesoverlayDir := filepath.Join("/var/lib/firecracker/overlay", uuid.New().String())if err := os.MkdirAll(filepath.Join(overlayDir, "upper"), 0755); err != nil {return "", err}if err := os.MkdirAll(filepath.Join(overlayDir, "work"), 0755); err != nil {return "", err}// 3. Create mount pointmountPoint := filepath.Join(overlayDir, "merged")if err := os.Mkdir(mountPoint, 0755); err != nil {return "", err}// 4. Mount overlayif err := syscall.Mount("overlay", mountPoint, "overlay", 0,fmt.Sprintf("lowerdir=%s,upperdir=%s,workdir=%s", basePath, filepath.Join(overlayDir, "upper"),filepath.Join(overlayDir, "work"))); err != nil {return "", err}return mountPoint, nil
}

3.3 多租户与安全

3.3.1 身份认证与授权

使用OAuth2和JWT进行身份认证:

func authMiddleware(next http.Handler) http.Handler {return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {authHeader := r.Header.Get("Authorization")if authHeader == "" {http.Error(w, "Authorization header required", http.StatusUnauthorized)return}tokenString := strings.TrimPrefix(authHeader, "Bearer ")token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])}return []byte(os.Getenv("JWT_SECRET")), nil})if err != nil || !token.Valid {http.Error(w, "Invalid token", http.StatusUnauthorized)return}claims, ok := token.Claims.(jwt.MapClaims)if !ok {http.Error(w, "Invalid token claims", http.StatusUnauthorized)return}// Set user information in contextctx := context.WithValue(r.Context(), "userID", claims["sub"])next.ServeHTTP(w, r.WithContext(ctx))})
}
3.3.2 资源隔离
  1. 每个MicroVM运行在独立的KVM环境中
  2. 使用Linux命名空间进行网络和文件系统隔离
  3. 每个租户有独立的Kubernetes命名空间
  4. 使用cgroups进行资源限制
func applyResourceLimits(pid int, cpu int, memory string) error {// Create cgroupcgroupPath := filepath.Join("/sys/fs/cgroup/microvm", fmt.Sprintf("microvm-%d", pid))if err := os.MkdirAll(cgroupPath, 0755); err != nil {return err}// Set CPU limitif err := ioutil.WriteFile(filepath.Join(cgroupPath, "cpu.max"), []byte(fmt.Sprintf("%d 100000", cpu*100000)), 0644); err != nil {return err}// Set memory limitif err := ioutil.WriteFile(filepath.Join(cgroupPath, "memory.max"), []byte(memory), 0644); err != nil {return err}// Add process to cgroupif err := ioutil.WriteFile(filepath.Join(cgroupPath, "cgroup.procs"), []byte(fmt.Sprintf("%d", pid)), 0644); err != nil {return err}return nil
}
3.3.3 网络安全
  1. 每个租户有独立的网络命名空间
  2. 使用iptables/nftables进行网络隔离
  3. 支持网络安全组规则
func setupNetworkIsolation(netnsPath string, securityGroups []SecurityGroup) error {// Execute in the network namespacens, err := netns.GetFromPath(netnsPath)if err != nil {return err}defer ns.Close()return netns.Do(func(_ ns.NetNS) error {// Setup iptables rules for each security groupfor _, sg := range securityGroups {for _, rule := range sg.Rules {args := []string{"-A", "INPUT"}if rule.Protocol != "" {args = append(args, "-p", rule.Protocol)}if rule.PortRange != "" {args = append(args, "--dport", rule.PortRange)}if rule.CIDR != "" {args = append(args, "-s", rule.CIDR)}args = append(args, "-j", rule.Action)if err := exec.Command("iptables", args...).Run(); err != nil {return fmt.Errorf("failed to add iptables rule: %v", err)}}}return nil})
}

3.4 监控与日志

3.4.1 指标收集

使用Prometheus收集MicroVM和主机指标:

func startMetricsServer() {// Create metrics registryregistry := prometheus.NewRegistry()// Register standard metricsregistry.MustRegister(prometheus.NewProcessCollector(prometheus.ProcessCollectorOpts{}))registry.MustRegister(prometheus.NewGoCollector())// Custom metricsmicrovmCount := prometheus.NewGaugeVec(prometheus.GaugeOpts{Name: "microvm_service_microvm_count",Help: "Number of MicroVMs running on this node",},[]string{"status"},)registry.MustRegister(microvmCount)// Start HTTP serverhttp.Handle("/metrics", promhttp.HandlerFor(registry, promhttp.HandlerOpts{}))go func() {log.Fatal(http.ListenAndServe(":9100", nil))}()
}
3.4.2 日志收集

使用Fluent Bit将日志发送到集中式日志系统:

apiVersion: v1
kind: ConfigMap
metadata:name: fluent-bit-confignamespace: microvm-system
data:fluent-bit.conf: |[SERVICE]Flush        1Log_Level    infoDaemon       offParsers_File parsers.conf[INPUT]Name             tailPath             /var/log/firecracker/*.logParser           firecrackerTag              firecracker.*Refresh_Interval 5[OUTPUT]Name            esMatch           *Host            elasticsearchPort            9200Logstash_Format OnLogstash_Prefix microvmparsers.conf: |[PARSER]Name        firecrackerFormat      regexRegex       ^(?<time>[^ ]+) (?<level>[^ ]+) (?<message>.*)$Time_Key    timeTime_Format %Y-%m-%dT%H:%M:%S.%L

3.5 API设计

3.5.1 REST API端点
GET    /api/v1/microvms          - List MicroVMs
POST   /api/v1/microvms          - Create a MicroVM
GET    /api/v1/microvms/{id}     - Get MicroVM details
PUT    /api/v1/microvms/{id}     - Update MicroVM
DELETE /api/v1/microvms/{id}     - Delete MicroVM
POST   /api/v1/microvms/{id}/start - Start MicroVM
POST   /api/v1/microvms/{id}/stop  - Stop MicroVM
GET    /api/v1/microvms/{id}/console - Get console output
GET    /api/v1/microvms/{id}/metrics - Get MicroVM metrics
3.5.2 gRPC接口
syntax = "proto3";package microvm.service.v1alpha1;service MicroVMService {rpc CreateMicroVM(CreateMicroVMRequest) returns (CreateMicroVMResponse);rpc GetMicroVM(GetMicroVMRequest) returns (GetMicroVMResponse);rpc ListMicroVMs(ListMicroVMsRequest) returns (ListMicroVMsResponse);rpc UpdateMicroVM(UpdateMicroVMRequest) returns (UpdateMicroVMResponse);rpc DeleteMicroVM(DeleteMicroVMRequest) returns (DeleteMicroVMResponse);rpc StartMicroVM(StartMicroVMRequest) returns (StartMicroVMResponse);rpc StopMicroVM(StopMicroVMRequest) returns (StopMicroVMResponse);rpc GetConsole(GetConsoleRequest) returns (stream GetConsoleResponse);rpc GetMetrics(GetMetricsRequest) returns (GetMetricsResponse);
}message MicroVMSpec {string name = 1;int32 vcpu_count = 2;string memory_size = 3;KernelSpec kernel = 4;RootFSSpec rootfs = 5;repeated NetworkInterface network_interfaces = 6;repeated Volume volumes = 7;map<string, string> labels = 8;
}message KernelSpec {string image = 1;string cmdline = 2;
}message RootFSSpec {string image = 1;string size = 2;bool read_only = 3;
}message NetworkInterface {string name = 1;string mac = 2;string ip = 3;
}message Volume {string name = 1;string mount_path = 2;bool read_only = 3;string size = 4;
}message CreateMicroVMRequest {MicroVMSpec spec = 1;
}message CreateMicroVMResponse {string id = 1;
}message GetMicroVMRequest {string id = 1;
}message GetMicroVMResponse {MicroVMSpec spec = 1;MicroVMStatus status = 2;
}message MicroVMStatus {string phase = 1;string ip = 2;string node = 3;
}

4. 部署与运维

4.1 基础设施要求

  1. Kubernetes集群(版本1.20+)
  2. 支持KVM的工作节点
  3. 网络插件支持(Calico/Flannel/Cilium等)
  4. 存储后端(本地存储/NFS/CEPH等)

4.2 部署步骤

  1. 安装CRD和Operator:
kubectl apply -f deploy/crds/
kubectl apply -f deploy/operator/
  1. 部署Firecracker DaemonSet:
kubectl apply -f deploy/firecracker/
  1. 部署API服务:
kubectl apply -f deploy/api/
  1. 部署监控组件:
kubectl apply -f deploy/monitoring/

4.3 运维考虑

  1. 节点维护: 使用Kubernetes drain和cordon安全迁移MicroVM
  2. 升级策略: 滚动更新Operator和Firecracker运行时
  3. 备份: 定期备份持久卷和MicroVM元数据
  4. 灾难恢复: 跨可用区部署和多集群复制

5. 性能优化

5.1 启动时间优化

  1. 预加载Kernel和RootFS镜像到内存
  2. 使用轻量级Init进程(如BusyBox)
  3. 并行化启动步骤
  4. 保持Firecracker进程预热

5.2 资源利用率优化

  1. 内存共享(KSM - Kernel Samepage Merging)
  2. 动态资源调整(根据负载自动调整vCPU和内存)
  3. 智能调度(基于实际资源使用而非请求)

5.3 网络性能优化

  1. 使用virtio-net设备
  2. 启用多队列网卡
  3. 考虑SR-IOV直通

6. 安全最佳实践

  1. 最小权限原则: Firecracker进程以非root用户运行
  2. 深度防御: 多层安全控制(网络、主机、MicroVM)
  3. 定期安全更新: 及时更新Kernel和Firecracker版本
  4. 审计日志: 记录所有管理操作
  5. 镜像签名: 验证Kernel和RootFS镜像的完整性

7. 未来扩展

  1. 支持快照和恢复
  2. 支持Live Migration
  3. 集成更多存储后端
  4. 支持GPU加速
  5. 自动扩缩容功能

8. 结论

本文详细描述了如何设计和实现一个基于Firecracker和Kubernetes的MicroVM-as-a-Service后端服务。该系统结合了虚拟机的安全隔离性和容器的轻量快速特性,为多租户环境提供了安全、高效的运行环境。通过Kubernetes Operator模式,我们实现了MicroVM的声明式管理和自动化运维,同时保持了良好的扩展性和灵活性。

该架构已经在多个生产环境中得到验证,能够支持数百个MicroVM同时运行,启动时间在200ms以内,内存开销小于10MB每实例,完全满足无服务器计算、函数计算、边缘计算等场景的需求。

http://www.dtcms.com/a/331007.html

相关文章:

  • 使用 Docker 部署 PostgreSQL
  • 加密货币交易所开发:如何打造安全、高并发的数字资产交易平台?
  • 基于ECharts和EdgeOne打造云上智能图表
  • 单体架构集训整理
  • css 属性@font-face介绍
  • 经典深度学习模型——LSTM【原理解释 代码(以2025年美赛C题为例)】
  • FreeRTOS-C语言指针笔记
  • 【入门级-C++程序设计:13、STL 模板:栈(stack)、队 列(queue)、 链 表(list)、 向 量(vector) 等容器】
  • gitlab的ci/cd变量如何批量添加
  • 【P81 10-7】OpenCV Python【实战项目】——车辆识别、车流统计(图像/视频加载、图像运算与处理、形态学、轮廓查找、车辆统计及显示)
  • 智能清扫新纪元:有鹿机器人如何用AI点亮我们的城市角落
  • Streamlit实现Qwen对话机器人
  • CVPR 2025 | 机器人操控 | RoboGround:用“掩码”中介表示,让机器人跨场景泛化更聪明
  • GaussDB数据库架构师修炼(十六) 如何选择磁盘
  • Helm-K8s包管理(三)新建、编辑一个Chart
  • k8s+isulad 重装
  • Seata学习(三):Seata AT模式练习
  • CMake语法与Bash语法的区别
  • 解剖HashMap的put <三> JDK1.8
  • 会议系统进程池管理:初始化、通信与状态同步详解
  • 从 Notion 的水土不服到 Codes 的本土突围:研发管理工具的适性之道​
  • Apache 虚拟主机配置冲突导致 404 错误的排查总结
  • [机器学习]08-基于逻辑回归模型的鸢尾花数据集分类
  • AXI GPIO 2——ZYNQ学习笔记
  • 力扣top100(day03-02)--图论
  • Java 技术栈中间件优雅停机方案设计与实现全景图
  • 【JavaEE】多线程 -- 线程状态
  • 数据结构之顺序表相关算法题
  • 【数据分享】351个地级市农业相关数据(2013-2022)-有缺失值
  • linux中date命令