CSI-PVController-volumeWorker

volumeWorker()

与claim worker流程一样,从volumeQueue中取数据,也就是取出的都是PV,如果informer中有这个pv,就进入update流程。

  1. 定义workFunc:首先,定义了一个匿名函数workFunc,这个函数是实际执行工作的地方。它返回一个布尔值quit,指示是否应该退出循环。
  2. 从队列中获取键:通过ctrl.claimQueue.Get()从队列中获取一个键。如果quittrue,表示队列已经关闭,应该退出循环。
  3. 处理键
    • 使用defer ctrl.claimQueue.Done(keyObj)确保在函数返回前标记这个键已经处理完毕。
  4. 解析命名空间和名称:使用cache.SplitMetaNamespaceKey(key)解析出命名空间和PVC名称。如果解析失败,记录错误日志并返回false,表示不需要退出循环。
  5. 尝试从Informer缓存中获取PVC
    • 使用ctrl.claimLister.PersistentVolumeClaims(namespace).Get(name)尝试从Informer缓存中获取PVC对象。
    • 如果获取成功,说明这个事件是添加、更新或同步事件,调用ctrl.updateClaim(claim)更新PVC。
    • 如果获取失败但不是“未找到”错误,记录错误日志并返回false
  6. 处理删除事件
    • 如果上一步中PVC不在Informer缓存中,假设这是一个删除事件。
    • 使用ctrl.claims.GetByKey(key)从本地缓存中获取PVC对象。
    • 如果获取失败或PVC不存在于本地缓存中,记录日志并返回false
    • 如果获取成功,将对象转换为*v1.PersistentVolumeClaim类型,并调用ctrl.deleteClaim(claim)删除PVC。
  7. 无限循环for { ... }构造了一个无限循环,不断调用workFunc处理事件。如果workFunc返回true,则记录日志并退出循环。
// volumeWorker processes items from volumeQueue. It must run only once,
// syncVolume is not assured to be reentrant.
func (ctrl *PersistentVolumeController) volumeWorker() {
    workFunc := func() bool {
                //不断从队列中拿到pv
        keyObj, quit := ctrl.volumeQueue.Get()
        if quit {
            return true
        }
        defer ctrl.volumeQueue.Done(keyObj)
        key := keyObj.(string)
        klog.V(5).Infof("volumeWorker[%s]", key)
        _, name, err := cache.SplitMetaNamespaceKey(key)
        if err != nil {
            klog.V(4).Infof("error getting name of volume %q to get volume from informer: %v", key, err)
            return false
        }
        volume, err := ctrl.volumeLister.Get(name)//1/从informercache获取改pV,不需要直接访问api-server
        if err == nil {
            // The volume still exists in informer cache, the event must have
            // been add/update/sync
            ctrl.updateVolume(volume)
            return false
        }
        if !errors.IsNotFound(err) {
            klog.V(2).Infof("error getting volume %q from informer: %v", key, err)
            return false
        }
        // The volume is not in informer cache, the event must have been
        // "delete"
        volumeObj, found, err := ctrl.volumes.store.GetByKey(key)//该pv不在cache中,则删掉
        if err != nil {
            klog.V(2).Infof("error getting volume %q from cache: %v", key, err)
            return false
        }
        if !found {
            // The controller has already processed the delete event and
            // deleted the volume from its cache
            klog.V(2).Infof("deletion of volume %q was already processed", key)
            return false
        }
        volume, ok := volumeObj.(*v1.PersistentVolume)
        if !ok {
            klog.Errorf("expected volume, got %+v", volumeObj)
            return false
        }
        ctrl.deleteVolume(volume)
        return false
    }
    for {
        if quit := workFunc(); quit {
            klog.Infof("volume worker queue shutting down")
            return
        }
    }
}

updateVolume()

如果pv没有更新(与缓存中一致),就直接返回处理下一个,如果有更新,执行syncVolume

// updateVolume runs in worker thread and handles "volume added",
// "volume updated" and "periodic sync" events.
func (ctrl *PersistentVolumeController) updateVolume(volume *v1.PersistentVolume) {
    // Store the new volume version in the cache and do not process it if this
    // is an old version.
    new, err := ctrl.storeVolumeUpdate(volume)//更新新的pv
    if err != nil {
        klog.Errorf("%v", err)
    }
    if !new {
        return
    }
    err = ctrl.syncVolume(volume)
    if err != nil {
        if errors.IsConflict(err) {
            // Version conflict error happens quite often and the controller
            // recovers from it easily.
            klog.V(3).Infof("could not sync volume %q: %+v", volume.Name, err)
        } else {
            klog.Errorf("could not sync volume %q: %+v", volume.Name, err)
        }
    }
}

syncVolume()

  1. 处理未使用的PersistentVolume
    • 如果volume.Spec.ClaimRefnil,表示这个PersistentVolume是未使用的。此时,会将其状态更新为VolumeAvailable
  2. 处理预绑定的PersistentVolume
    • 如果volume.Spec.ClaimRef不为nilClaimRef.UID为空,表示这个PersistentVolume已经被预留给一个PersistentVolumeClaim(PVC),但尚未绑定。此时,也会将其状态更新为VolumeAvailable
  3. 处理已绑定的PersistentVolume
    • 如果volume.Spec.ClaimRef.UID不为空,表示这个PersistentVolume已经绑定到一个PersistentVolumeClaim
      • 首先,尝试从本地缓存中获取对应的PVC对象。
      • 如果本地缓存中没有找到,再尝试从API服务器获取。
      • 如果仍然找不到,则认为这个PVC可能已经被删除。
  4. 处理PVC不存在的情况
    • 如果找不到对应的PVC,且PersistentVolume的状态不是VolumeReleasedVolumeFailed,则将其状态更新为VolumeReleased,并调用reclaimVolume方法进行处理。
  5. 处理PVC存在但UID不匹配的情况
    • 如果找到的PVC的UID与PersistentVolume中保存的UID不匹配,说明原来的PVC已经被删除并重新创建了一个同名的PVC。此时,将claim设置为nil,并按照PVC不存在的情况处理。
  6. 处理PVC和PV正常绑定的情况
    • 如果PVC和PV正常绑定(即claim.Spec.VolumeName == volume.Name),则更新PersistentVolume的状态为VolumeBound
  7. 处理PVC绑定到其他PV的情况
    • 如果PVC绑定到了其他的PV,则需要根据PersistentVolume是否是动态分配的以及回收策略来决定如何处理。
      • 如果是动态分配的且回收策略为Delete,则释放并删除这个PersistentVolume
      • 否则,尝试解除PersistentVolume与PVC的绑定。
  8. 处理体积模式不匹配
    • 如果在尝试绑定PVC和PV时发现体积模式不匹配,则记录事件并跳过同步。
  9. 加速绑定
    • 如果PersistentVolume是待绑定的,且不是由控制器绑定的,则将其加入到PVC队列中,以加速绑定过程。
      这段代码涵盖了PersistentVolume生命周期中的多个状态转换和处理逻辑,确保了Kubernetes中持久化存储的正确管理和使用。
// syncVolume is the main controller method to decide what to do with a volume.
// It's invoked by appropriate cache.Controller callbacks when a volume is
// created, updated or periodically synced. We do not differentiate between
// these events.
func (ctrl *PersistentVolumeController) syncVolume(volume *v1.PersistentVolume) error {
    klog.V(4).Infof("synchronizing PersistentVolume[%s]: %s", volume.Name, getVolumeStatusForLogging(volume))
    // Set correct "migrated-to" annotations on PV and update in API server if
    // necessary
    newVolume, err := ctrl.updateVolumeMigrationAnnotations(volume)
    if err != nil {
        // Nothing was saved; we will fall back into the same
        // condition in the next call to this method
        return err
    }
    volume = newVolume
    // [Unit test set 4]
    if volume.Spec.ClaimRef == nil {
        // Volume is unused
        klog.V(4).Infof("synchronizing PersistentVolume[%s]: volume is unused", volume.Name)
        if _, err := ctrl.updateVolumePhase(volume, v1.VolumeAvailable, ""); err != nil {
            // Nothing was saved; we will fall back into the same
            // condition in the next call to this method
            return err
        }
        return nil
    } else /* pv.Spec.ClaimRef != nil */ {
        // Volume is bound to a claim.
        if volume.Spec.ClaimRef.UID == "" {
            // The PV is reserved for a PVC; that PVC has not yet been
            // bound to this PV; the PVC sync will handle it.
            klog.V(4).Infof("synchronizing PersistentVolume[%s]: volume is pre-bound to claim %s", volume.Name, claimrefToClaimKey(volume.Spec.ClaimRef))
            if _, err := ctrl.updateVolumePhase(volume, v1.VolumeAvailable, ""); err != nil {
                // Nothing was saved; we will fall back into the same
                // condition in the next call to this method
                return err
            }
            return nil
        }
        klog.V(4).Infof("synchronizing PersistentVolume[%s]: volume is bound to claim %s", volume.Name, claimrefToClaimKey(volume.Spec.ClaimRef))
        // Get the PVC by _name_
        var claim *v1.PersistentVolumeClaim
        claimName := claimrefToClaimKey(volume.Spec.ClaimRef)
        obj, found, err := ctrl.claims.GetByKey(claimName)
        if err != nil {
            return err
        }
        if !found {
            // If the PV was created by an external PV provisioner or
            // bound by external PV binder (e.g. kube-scheduler), it's
            // possible under heavy load that the corresponding PVC is not synced to
            // controller local cache yet. So we need to double-check PVC in
            //   1) informer cache
            //   2) apiserver if not found in informer cache
            // to make sure we will not reclaim a PV wrongly.
            // Note that only non-released and non-failed volumes will be
            // updated to Released state when PVC does not exist.
            if volume.Status.Phase != v1.VolumeReleased && volume.Status.Phase != v1.VolumeFailed {
                obj, err = ctrl.claimLister.PersistentVolumeClaims(volume.Spec.ClaimRef.Namespace).Get(volume.Spec.ClaimRef.Name)
                if err != nil && !apierrors.IsNotFound(err) {
                    return err
                }
                found = !apierrors.IsNotFound(err)
                if !found {
                    obj, err = ctrl.kubeClient.CoreV1().PersistentVolumeClaims(volume.Spec.ClaimRef.Namespace).Get(context.TODO(), volume.Spec.ClaimRef.Name, metav1.GetOptions{})
                    if err != nil && !apierrors.IsNotFound(err) {
                        return err
                    }
                    found = !apierrors.IsNotFound(err)
                }
            }
        }
        if !found {
            klog.V(4).Infof("synchronizing PersistentVolume[%s]: claim %s not found", volume.Name, claimrefToClaimKey(volume.Spec.ClaimRef))
            // Fall through with claim = nil
        } else {//找到
            var ok bool
            claim, ok = obj.(*v1.PersistentVolumeClaim)
            if !ok {
                return fmt.Errorf("Cannot convert object from volume cache to volume %q!?: %#v", claim.Spec.VolumeName, obj)
            }
            klog.V(4).Infof("synchronizing PersistentVolume[%s]: claim %s found: %s", volume.Name, claimrefToClaimKey(volume.Spec.ClaimRef), getClaimStatusForLogging(claim))
        }
        if claim != nil && claim.UID != volume.Spec.ClaimRef.UID {
            // The claim that the PV was pointing to was deleted, and another
            // with the same name created.
            klog.V(4).Infof("synchronizing PersistentVolume[%s]: claim %s has different UID, the old one must have been deleted", volume.Name, claimrefToClaimKey(volume.Spec.ClaimRef))
            // Treat the volume as bound to a missing claim.
            claim = nil
        }
        if claim == nil {
            // If we get into this block, the claim must have been deleted;
            // NOTE: reclaimVolume may either release the PV back into the pool or
            // recycle it or do nothing (retain)
            // Do not overwrite previous Failed state - let the user see that
            // something went wrong, while we still re-try to reclaim the
            // volume.
            if volume.Status.Phase != v1.VolumeReleased && volume.Status.Phase != v1.VolumeFailed {
                // Also, log this only once:
                klog.V(2).Infof("volume %q is released and reclaim policy %q will be executed", volume.Name, volume.Spec.PersistentVolumeReclaimPolicy)
                if volume, err = ctrl.updateVolumePhase(volume, v1.VolumeReleased, ""); err != nil {
                    // Nothing was saved; we will fall back into the same condition
                    // in the next call to this method
                    return err
                }
            }
            if err = ctrl.reclaimVolume(volume); err != nil {
                // Release failed, we will fall back into the same condition
                // in the next call to this method
                return err
            }
            if volume.Spec.PersistentVolumeReclaimPolicy == v1.PersistentVolumeReclaimRetain {
                // volume is being retained, it references a claim that does not exist now.
                klog.V(4).Infof("PersistentVolume[%s] references a claim %q (%s) that is not found", volume.Name, claimrefToClaimKey(volume.Spec.ClaimRef), volume.Spec.ClaimRef.UID)
            }
            return nil
        } else if claim.Spec.VolumeName == "" {
            if pvutil.CheckVolumeModeMismatches(&claim.Spec, &volume.Spec) {
                // Binding for the volume won't be called in syncUnboundClaim,
                // because findBestMatchForClaim won't return the volume due to volumeMode mismatch.
                volumeMsg := fmt.Sprintf("Cannot bind PersistentVolume to requested PersistentVolumeClaim %q due to incompatible volumeMode.", claim.Name)
                ctrl.eventRecorder.Event(volume, v1.EventTypeWarning, events.VolumeMismatch, volumeMsg)
                claimMsg := fmt.Sprintf("Cannot bind PersistentVolume %q to requested PersistentVolumeClaim due to incompatible volumeMode.", volume.Name)
                ctrl.eventRecorder.Event(claim, v1.EventTypeWarning, events.VolumeMismatch, claimMsg)
                // Skipping syncClaim
                return nil
            }
"http://pv.kubernetes.io/bound-by-controller" 的annotation 说明pv、pvc正在绑定中
            if metav1.HasAnnotation(volume.ObjectMeta, pvutil.AnnBoundByController) {
                // The binding is not completed; let PVC sync handle it
                klog.V(4).Infof("synchronizing PersistentVolume[%s]: volume not bound yet, waiting for syncClaim to fix it", volume.Name)
            } else {
                // Dangling PV; try to re-establish the link in the PVC sync
                klog.V(4).Infof("synchronizing PersistentVolume[%s]: volume was bound and got unbound (by user?), waiting for syncClaim to fix it", volume.Name)
            }
            // In both cases, the volume is Bound and the claim is Pending.
            // Next syncClaim will fix it. To speed it up, we enqueue the claim
            // into the controller, which results in syncClaim to be called
            // shortly (and in the right worker goroutine).
            // This speeds up binding of provisioned volumes - provisioner saves
            // only the new PV and it expects that next syncClaim will bind the
            // claim to it.
            ctrl.claimQueue.Add(claimToClaimKey(claim))
            return nil
        } else if claim.Spec.VolumeName == volume.Name {
            // Volume is bound to a claim properly, update status if necessary
            klog.V(4).Infof("synchronizing PersistentVolume[%s]: all is bound", volume.Name)
            if _, err = ctrl.updateVolumePhase(volume, v1.VolumeBound, ""); err != nil {
                // Nothing was saved; we will fall back into the same
                // condition in the next call to this method
                return err
            }
            return nil
        } else {
            // Volume is bound to a claim, but the claim is bound elsewhere
            if metav1.HasAnnotation(volume.ObjectMeta, pvutil.AnnDynamicallyProvisioned) && volume.Spec.PersistentVolumeReclaimPolicy == v1.PersistentVolumeReclaimDelete {
                // This volume was dynamically provisioned for this claim. The
                // claim got bound elsewhere, and thus this volume is not
                // needed. Delete it.
                // Mark the volume as Released for external deleters and to let
                // the user know. Don't overwrite existing Failed status!
                if volume.Status.Phase != v1.VolumeReleased && volume.Status.Phase != v1.VolumeFailed {
                    // Also, log this only once:
                    klog.V(2).Infof("dynamically volume %q is released and it will be deleted", volume.Name)
                    if volume, err = ctrl.updateVolumePhase(volume, v1.VolumeReleased, ""); err != nil {
                        // Nothing was saved; we will fall back into the same condition
                        // in the next call to this method
                        return err
                    }
                }
                if err = ctrl.reclaimVolume(volume); err != nil {
                    // Deletion failed, we will fall back into the same condition
                    // in the next call to this method
                    return err
                }
                return nil
            } else {
                // Volume is bound to a claim, but the claim is bound elsewhere
                // and it's not dynamically provisioned.
                if metav1.HasAnnotation(volume.ObjectMeta, pvutil.AnnBoundByController) {
                    // This is part of the normal operation of the controller; the
                    // controller tried to use this volume for a claim but the claim
                    // was fulfilled by another volume. We did this; fix it.
                    klog.V(4).Infof("synchronizing PersistentVolume[%s]: volume is bound by controller to a claim that is bound to another volume, unbinding", volume.Name)
                    if err = ctrl.unbindVolume(volume); err != nil {
                        return err
                    }
                    return nil
                } else {
                    // The PV must have been created with this ptr; leave it alone.
                    klog.V(4).Infof("synchronizing PersistentVolume[%s]: volume is bound by user to a claim that is bound to another volume, waiting for the claim to get unbound", volume.Name)
                    // This just updates the volume phase and clears
                    // volume.Spec.ClaimRef.UID. It leaves the volume pre-bound
                    // to the claim.
                    if err = ctrl.unbindVolume(volume); err != nil {
                        return err
                    }
                    return nil
                }
            }
        }
    }
}
reclaimVolume()

这段Go代码是Kubernetes中PersistentVolumeController(PV控制器)的一部分,用于处理PersistentVolume(PV)的回收操作。PersistentVolume是Kubernetes中的一种存储资源,用于为集群中的Pod提供持久化存储。每个PV都有一个回收策略(PersistentVolumeReclaimPolicy),用于定义当PV不再需要时应该如何处理它。这段代码实现了根据PV的回收策略执行相应的操作。

  1. 检查PV是否已迁移
    • 首先,代码检查PV的Annotations中是否有pvutil.AnnMigratedTo的标记。如果PV已被迁移(即这个标记存在),则函数直接返回nil,表示不需要进行任何操作,因为迁移后的PV将由外部提供者管理。
  2. 根据回收策略执行操作
    • 接下来,代码根据PV的Spec.PersistentVolumeReclaimPolicy字段的值,决定如何回收PV。回收策略有三种:RetainRecycleDelete
    • Retain
      • 如果策略是Retain,则记录一条日志信息,表示不需要对PV进行任何操作。Retain策略意味着PV在释放后不会被自动删除或清理,管理员需要手动处理它。
    • Recycle(已弃用):
      • 如果策略是Recycle,则记录一条日志信息,并安排一个回收操作。回收操作通过ctrl.scheduleOperation方法安排,实际执行的是ctrl.recycleVolumeOperation(volume)函数。注意,Recycle策略在Kubernetes较新版本中已被弃用,因为它不保证数据的安全性和完整性。
    • Delete
      • 如果策略是Delete,则记录一条日志信息,并为删除操作创建一个时间戳条目(如果尚不存在)。这是通过ctrl.operationTimestamps.AddIfNotExist方法实现的。然后,安排一个删除操作,实际执行的是ctrl.deleteVolumeOperation(volume)函数。如果删除操作失败,会记录一个错误指标,但不会立即报告延迟,延迟报告将在PV最终被删除并捕获到删除事件时发生。
  3. 处理未知的回收策略
    • 如果PV的回收策略是未知的(即不是RetainRecycleDelete中的任何一个),则更新PV的状态为Failed,并记录一个警告事件,事件类型为VolumeUnknownReclaimPolicy,消息为"Volume has unrecognized PersistentVolumeReclaimPolicy"。
// reclaimVolume implements volume.Spec.PersistentVolumeReclaimPolicy and
// starts appropriate reclaim action.
func (ctrl *PersistentVolumeController) reclaimVolume(volume *v1.PersistentVolume) error {
	if migrated := volume.Annotations[pvutil.AnnMigratedTo]; len(migrated) > 0 {
		// PV is Migrated. The PV controller should stand down and the external
		// provisioner will handle this PV
		return nil
	}
	switch volume.Spec.PersistentVolumeReclaimPolicy {
	case v1.PersistentVolumeReclaimRetain:
		klog.V(4).Infof("reclaimVolume[%s]: policy is Retain, nothing to do", volume.Name)

	case v1.PersistentVolumeReclaimRecycle:
		klog.V(4).Infof("reclaimVolume[%s]: policy is Recycle", volume.Name)
		opName := fmt.Sprintf("recycle-%s[%s]", volume.Name, string(volume.UID))
		ctrl.scheduleOperation(opName, func() error {
			ctrl.recycleVolumeOperation(volume)
			return nil
		})

	case v1.PersistentVolumeReclaimDelete:
		klog.V(4).Infof("reclaimVolume[%s]: policy is Delete", volume.Name)
		opName := fmt.Sprintf("delete-%s[%s]", volume.Name, string(volume.UID))
		// create a start timestamp entry in cache for deletion operation if no one exists with
		// key = volume.Name, pluginName = provisionerName, operation = "delete"
		ctrl.operationTimestamps.AddIfNotExist(volume.Name, ctrl.getProvisionerNameFromVolume(volume), "delete")
		ctrl.scheduleOperation(opName, func() error {
			_, err := ctrl.deleteVolumeOperation(volume)
			if err != nil {
				// only report error count to "volume_operation_total_errors"
				// latency reporting will happen when the volume get finally
				// deleted and a volume deleted event is captured
				metrics.RecordMetric(volume.Name, &ctrl.operationTimestamps, err)
			}
			return err
		})

	default:
		// Unknown PersistentVolumeReclaimPolicy
		if _, err := ctrl.updateVolumePhaseWithEvent(volume, v1.VolumeFailed, v1.EventTypeWarning, "VolumeUnknownReclaimPolicy", "Volume has unrecognized PersistentVolumeReclaimPolicy"); err != nil {
			return err
		}
	}
	return nil
}
unbindVolume()
  1. 深拷贝:通过volume.DeepCopy()创建volume的一个深拷贝volumeClone。这是为了确保原始volume对象不会被修改,所有更改都将应用于volumeClone
  2. 检查注解:接下来,代码检查持久卷上是否存在pvutil.AnnBoundByController注解。这个注解用来指示持久卷是由控制器绑定的还是由用户预先绑定的。
    • 如果存在这个注解,说明持久卷是由控制器绑定的。因此,将ClaimRef设置为nil,并删除pvutil.AnnBoundByController注解。如果删除后注解列表为空,则将注解字段设置为nil
    • 如果不存在这个注解,说明持久卷是用户预先绑定的。在这种情况下,只清除ClaimRef中的UID字段。
  3. 更新持久卷:使用ctrl.kubeClient.CoreV1().PersistentVolumes().Update方法尝试更新Kubernetes API服务器中的持久卷对象。这里使用context.TODO()作为上下文参数,表示将来可能会提供一个具体的上下文。如果更新失败,记录一条错误日志并返回错误。
  4. 更新内部缓存:调用ctrl.storeVolumeUpdate方法尝试更新控制器的内部缓存中的持久卷对象。如果更新失败,记录一条错误日志并返回错误。
  5. 记录解绑成功:如果上述步骤都成功,使用klog.V(4).Infof记录一条日志,表明持久卷已成功解绑。
  6. 更新持久卷状态:最后,调用ctrl.updateVolumePhase方法将持久卷的状态更新为v1.VolumeAvailable,表示持久卷现在是可用的,可以被新的持久卷申领绑定。如果更新状态失败,返回错误。
// unbindVolume rolls back previous binding of the volume. This may be necessary
// when two controllers bound two volumes to single claim - when we detect this,
// only one binding succeeds and the second one must be rolled back.
// This method updates both Spec and Status.
// It returns on first error, it's up to the caller to implement some retry
// mechanism.
func (ctrl *PersistentVolumeController) unbindVolume(volume *v1.PersistentVolume) error {
	klog.V(4).Infof("updating PersistentVolume[%s]: rolling back binding from %q", volume.Name, claimrefToClaimKey(volume.Spec.ClaimRef))

	// Save the PV only when any modification is necessary.
	volumeClone := volume.DeepCopy()

	if metav1.HasAnnotation(volume.ObjectMeta, pvutil.AnnBoundByController) {
		// The volume was bound by the controller.
		volumeClone.Spec.ClaimRef = nil
		delete(volumeClone.Annotations, pvutil.AnnBoundByController)
		if len(volumeClone.Annotations) == 0 {
			// No annotations look better than empty annotation map (and it's easier
			// to test).
			volumeClone.Annotations = nil
		}
	} else {
		// The volume was pre-bound by user. Clear only the binding UID.
		volumeClone.Spec.ClaimRef.UID = ""
	}

	newVol, err := ctrl.kubeClient.CoreV1().PersistentVolumes().Update(context.TODO(), volumeClone, metav1.UpdateOptions{})
	if err != nil {
		klog.V(4).Infof("updating PersistentVolume[%s]: rollback failed: %v", volume.Name, err)
		return err
	}
	_, err = ctrl.storeVolumeUpdate(newVol)
	if err != nil {
		klog.V(4).Infof("updating PersistentVolume[%s]: cannot update internal cache: %v", volume.Name, err)
		return err
	}
	klog.V(4).Infof("updating PersistentVolume[%s]: rolled back", newVol.Name)

	// Update the status
	_, err = ctrl.updateVolumePhase(newVol, v1.VolumeAvailable, "")
	return err
}
内容概要:本文详细介绍了利用二维时域有限差分法(2D FDTD)对光子晶体90度弯曲波导进行数值仿真的Matlab代码实现。该仿真方法旨在精确分析光子晶体波导在弯曲结构下的光传输特性,揭示其导光机制与缺陷模式的调控原理。资源包含完整的Matlab程序代码,支持对空间网格划分、介电常数分布、边界条件(如PML吸收边界)及光源参数等关键仿真要素的灵活设置与优化,便于用户复现结果并开展深入研究。通过仿真可直观获得光场在波导中的传播动态、透射谱特性以及能量损耗情况,为高性能光子器件的设计与优化提供理论依据和技术支持。; 适合人群:具备电磁场理论、光学基础和Matlab编程能力,从事光子学、集成光学或纳米光子器件研究的研究生、科研人员及工程技术开发者。; 使用场景及目标:①学习和掌握FDTD方法在周期性介质(光子晶体)器件仿真中的具体应用流程;②研究90度弯波导的光传输性能,分析弯曲损耗来源并探索低损耗结构优化方案;③作为光子集成电路中关键无源器件的设计与教学参考案例,服务于学术研究与工程实践。; 阅读建议:建议结合光子晶体能带理论与FDTD算法基本原理进行系统学习,运行代码时应逐步调整结构参数与仿真设置,观察光场演化和输出结果的变化,以深化对物理现象的理解,并可在此基础上拓展至其他复杂光子结构(如分束器、谐振腔)的仿真分析。
内容概要:本文系统研究了基于共识的捆绑算法(Consensus-Based Bundle Algorithm, CBBA)在多智能体多任务分配中的应用,重点聚焦于远程太空船交会与维修任务中的相对运动规划(RPO)问题。通过构建多航天器协同任务场景,采用Matlab代码实现了CBBA算法的全过程仿真,展示了其在分布式决策框架下高效完成任务分配的能力。研究深入探讨了任务收益建模、路径规划约束、通信延迟与动态重规划等关键环节,验证了CBBA在确保任务分配一致性、避免资源冲突、适应动态环境变化以及优化整体任务效能方面的优越性能,为复杂空间任务中的自主协同提供了可靠的技术路径。; 适合人群:具备控制理论、航天动力学、分布式优化或多智能体系统等相关背景,从事航天任务规划、智能优化算法研究或相关工程实践的研究生、科研人员及航空航天领域工程师。; 使用场景及目标:①为多航天器在轨服务(如交会对接、空间维修)提供高效、鲁棒的分布式任务分配解决方案;②深入理解CBBA算法的核心机制及其在高动态、强约束空间任务中的适应性与优化潜力;③推动分布式人工智能算法在航天工程实际系统中的集成与应用验证。; 阅读建议:建议读者结合提供的Matlab代码,重点剖析任务建模逻辑、收益函数设计、共识迭代过程及收敛性分析模块,通过修改场景参数进行仿真实验,以深化对多智能体协同决策机制与算法性能边界条件的理解。
内容概要:本文研究了一种计及自适应预测修正的微电网模型预测控制(MPC)优化调度方法,并提供了基于Matlab的完整代码实现。该方法融合自适应预测机制与MPC滚动优化框架,有效应对微电网中可再生能源出力波动、负荷需求不确定性等多重挑战,显著提升调度决策的精度与系统鲁棒性。通过构建动态反馈校正机制,实时修正预测模型误差,优化未来时段的运行策略,实现对微电网内部分布式电源、储能系统及可控负荷的协同调控,达成经济性、稳定性与环保性多目标的综合优化。所提方法具有较强的工程实用性与理论价值,为现代智能微电网的能量管理系统提供了可靠的技术支撑。; 适合人群:具备电力系统分析、优化控制理论基础及Matlab编程能力的研究生、科研人员,以及从事微电网、智能配电系统、新能源并网等领域技术研发的工程技术人员。; 使用场景及目标:①应用于高校与科研机构开展微电网优化调度算法的仿真研究与性能验证;②服务于电力企业或能源科技公司开发先进能量管理系统(EMS),提升微电网运行效率与可再生能源消纳能力;③作为自动化、电气工程等专业的高级教学案例,帮助学生深入理解MPC在复杂能源系统中的建模、优化与反馈控制全过程。; 阅读建议:建议读者结合Matlab代码逐模块分析算法实现流程,重点掌握预测模型构建、滚动优化求解及反馈修正机制的设计逻辑,可通过调整预测时域、权重系数与扰动场景等参数进行仿真实验,深入理解各环节对系统性能的影响。
内容概要:本文围绕电力系统短期负荷预测问题,深入研究了基于极限学习机(ELM)及其智能优化算法的应用方法,提出并实现了白鲸优化算法(BWO)和鹭鹰优化算法(IBOA)对ELM模型的关键参数进行寻优的技术路径。通过Matlab编程实现,优化后的模型有效提升了预测精度,降低了原始ELM因随机初始化带来的不稳定性和误差波动,增强了模型在面对电力负荷不确定性变化时的泛化能力和鲁棒性。研究系统阐述了ELM的基本原理、两种新型群智能优化算法的搜索机制及其在解决非线性参数优化问题上的优势,并通过实验对比验证了优化模型在均方根误差(RMSE)、平均绝对百分比误差(MAPE)等指标上的显著优越性,为电力系统负荷预测提供了高效可靠的解决方案。; 适合人群:具备电力系统分析、人工智能算法理论基础及Matlab编程能力的高校研究生、科研机构研究人员以及电力公司从事负荷预测、电网调度与能源管理的工程技术人员。; 使用场景及目标:①应用于电网调度中心的短期负荷预测业务,提高预测准确性,保障电力供需平衡;②为智能优化算法在电力工程领域的落地应用提供可复现的技术范例;③支撑电力市场出清、发电计划制定、储能系统配置及需求侧响应等关键决策环节; 阅读建议:建议读者结合提供的Matlab代码进行实践操作,重点理解ELM网络结构搭建、适应度函数设计、优化算法迭代流程及预测结果后处理等关键步骤,通过调整数据集和参数设置,深入掌握模型调优技巧,并尝试将该方法迁移至风电、光伏功率预测等相似时序预测任务中。
下载代码方式:https://pan.quark.cn/s/d305330341ec 在当代科技领域中,华为作为中国顶尖的科技企业,持续研发先进技术以优化用户的使用感受。鸿蒙操作系统(HarmonyOS)是由华为独立设计的一款面向多场景的分布式操作系统,其目标在于消除不同设备间的隔阂,促成无障碍的联合工作。本指南将详尽阐释在非华为品牌的个人电脑上,如何运用鸿蒙超级终端、多屏联动(多视窗)特性以及NFC芯片,使这些功能得到充分的发挥。 鸿蒙超级终端作为鸿蒙系统的关键特性之一,它将多样化的设备整合为一个统一体,使用户能够在多个设备之间无拘无束地转换和共享资源。对于非华为电脑的使用者而言,或许需要借助华为的电脑助手软件或特定的鸿蒙OS应用来实现与鸿蒙设备的对接。在完成相关软件的安装和配置后,用户能够借助超级终端特性将第三方电脑与华为手机、平板及其他鸿蒙设备进行配对,达成文件交换、屏幕显示同步乃至跨设备操作。 多屏联动(多视窗)特性是华为为增强工作效率而策划的特色功能。在非华为电脑上运用这一特性,用户能够将手机或平板的显示界面投射到电脑上,甚至可以在电脑上直接操控移动设备的应用,达成两个显示界面间的流畅配合。例如,用户可以在电脑上撰写文档的同时,在手机上查阅资料,两者同步进行,显著提升了工作效率。 NFC(近场通信)芯片是物联网技术的一种实践,它能够储存数据并与具备NFC功能的设备展开互动。在华为的生态系统里,NFC芯片常被用于迅速启动特定任务,如激活多屏联动。只需将设定了相应指令的NFC芯片贴附在电脑或手机上,轻轻触碰,就能自动启动多屏联动,极为便捷。 在实践这个指南的过程中,用户应留意以下几点: 1. 保证你的非华为电脑具备NFC功能,并且已安装了最新的华为电脑助...
内容概要:本文提出了一种基于非合作博弈理论的居民负荷分层调度模型,并采用双层鲸鱼优化算法进行求解,旨在应对风电出力不确定性下的电力系统负荷调度问题。该模型通过构建系统运营商与居民用户之间的双层博弈架构,上层以最小化负荷峰谷差为目标制定激励性电价信号,下层用户则在电价引导下优化用电行为以降低电费支出,最终实现纳什均衡状态。双层鲸鱼优化算法被用于高效求解该嵌套优化问题,在保证全局寻优能力的同时提升了收敛精度。仿真结果表明,该模型能有效实现削峰填谷,改善负荷曲线形态,增强电网对可再生能源的消纳能力,具有良好的应用前景。; 适合人群:具备一定电力系统基础知识和优化算法背景的研究生、科研人员及从事智能电网、需求响应、能源管理等领域的工程技术人员。; 使用场景及目标:①应用于高比例可再生能源接入的配电系统中,实现居民侧负荷的智能化调控;②为电力公司设计分时电价或激励型需求响应机制提供理论依据与技术支持;③作为双层优化、智能算法与博弈论在能源系统中融合应用的教学与研究案例。; 阅读建议:读者应重点关注非合作博弈的建模逻辑与双层优化问题的分解方法,建议结合Matlab代码实现部分,动手复现仿真过程,深入理解鲸鱼算法在上下层迭代求解中的实现细节,并尝试将其推广至多主体能源交互、虚拟电厂调度等更广泛的场景中。
源码链接: https://pan.quark.cn/s/a4b39357ea24 在深度学习领域,卷积神经网络(Convolutional Neural Network, CNN)是处理序列数据和图像数据的重要工具。 Keras 是一个高级神经网络API,它提供了便捷的方式来构建和训练CNN模型。 本文将深入探讨Keras中的`Conv1D`和`Conv2D`层的区别,帮助读者更好地理解和应用这两个关键组件。 `Conv1D`和`Conv2D`的主要区别在于它们处理的数据维度。 `Conv1D`主要用于一维数据,如时间序列分析、文本分类等,而`Conv2D`则用于二维数据,如图像处理。 1. 数据维度: - `Conv1D`:该层接受一维输入,形状通常是 `(batch_size, time_steps, features)`。 在这里,`time_steps`表示序列的长度,`features`是每个时间步的特征数量。 - `Conv2D`:该层处理二维输入,例如图像,其形状为 `(batch_size, height, width, channels)`。 `height`和`width`代表图像的高度和宽度,`channels`通常对应RGB图像的三个颜色通道或单通道灰度图像。 2. 卷积核(Kernel): - `Conv1D`的卷积核也是一维的,沿着输入的时间轴进行滑动,对每个时间步的特征进行卷积操作。 - `Conv2D`的卷积核是二维的,它同时在图像的高度和宽度方向上滑动,可以捕获空间上的局部特征。 3. 参数设置: - `kernel_size`:对于`Conv1D`,它是一个整数,表示卷积核在时间轴上的跨度。 对于`Conv2D`,它是一个包含两个整数...
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值