CSI-PVController-volumeWorker
volumeWorker()
与claim worker流程一样,从volumeQueue中取数据,也就是取出的都是PV,如果informer中有这个pv,就进入update流程。
- 定义workFunc:首先,定义了一个匿名函数
workFunc,这个函数是实际执行工作的地方。它返回一个布尔值quit,指示是否应该退出循环。 - 从队列中获取键:通过
ctrl.claimQueue.Get()从队列中获取一个键。如果quit为true,表示队列已经关闭,应该退出循环。 - 处理键:
- 使用
defer ctrl.claimQueue.Done(keyObj)确保在函数返回前标记这个键已经处理完毕。
- 使用
- 解析命名空间和名称:使用
cache.SplitMetaNamespaceKey(key)解析出命名空间和PVC名称。如果解析失败,记录错误日志并返回false,表示不需要退出循环。 - 尝试从Informer缓存中获取PVC:
- 使用
ctrl.claimLister.PersistentVolumeClaims(namespace).Get(name)尝试从Informer缓存中获取PVC对象。 - 如果获取成功,说明这个事件是添加、更新或同步事件,调用
ctrl.updateClaim(claim)更新PVC。 - 如果获取失败但不是“未找到”错误,记录错误日志并返回
false。
- 使用
- 处理删除事件:
- 如果上一步中PVC不在Informer缓存中,假设这是一个删除事件。
- 使用
ctrl.claims.GetByKey(key)从本地缓存中获取PVC对象。 - 如果获取失败或PVC不存在于本地缓存中,记录日志并返回
false。 - 如果获取成功,将对象转换为
*v1.PersistentVolumeClaim类型,并调用ctrl.deleteClaim(claim)删除PVC。
- 无限循环:
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 {//不断从队列中拿到pvkeyObj, 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-serverif err == nil {// The volume still exists in informer cache, the event must have// been add/update/syncctrl.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 cacheklog.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)//更新新的pvif 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()
- 处理未使用的
PersistentVolume:- 如果
volume.Spec.ClaimRef为nil,表示这个PersistentVolume是未使用的。此时,会将其状态更新为VolumeAvailable。
- 如果
- 处理预绑定的
PersistentVolume:- 如果
volume.Spec.ClaimRef不为nil但ClaimRef.UID为空,表示这个PersistentVolume已经被预留给一个PersistentVolumeClaim(PVC),但尚未绑定。此时,也会将其状态更新为VolumeAvailable。
- 如果
- 处理已绑定的
PersistentVolume:- 如果
volume.Spec.ClaimRef.UID不为空,表示这个PersistentVolume已经绑定到一个PersistentVolumeClaim。- 首先,尝试从本地缓存中获取对应的PVC对象。
- 如果本地缓存中没有找到,再尝试从API服务器获取。
- 如果仍然找不到,则认为这个PVC可能已经被删除。
- 如果
- 处理PVC不存在的情况:
- 如果找不到对应的PVC,且
PersistentVolume的状态不是VolumeReleased或VolumeFailed,则将其状态更新为VolumeReleased,并调用reclaimVolume方法进行处理。
- 如果找不到对应的PVC,且
- 处理PVC存在但UID不匹配的情况:
- 如果找到的PVC的UID与
PersistentVolume中保存的UID不匹配,说明原来的PVC已经被删除并重新创建了一个同名的PVC。此时,将claim设置为nil,并按照PVC不存在的情况处理。
- 如果找到的PVC的UID与
- 处理PVC和PV正常绑定的情况:
- 如果PVC和PV正常绑定(即
claim.Spec.VolumeName == volume.Name),则更新PersistentVolume的状态为VolumeBound。
- 如果PVC和PV正常绑定(即
- 处理PVC绑定到其他PV的情况:
- 如果PVC绑定到了其他的PV,则需要根据
PersistentVolume是否是动态分配的以及回收策略来决定如何处理。- 如果是动态分配的且回收策略为
Delete,则释放并删除这个PersistentVolume。 - 否则,尝试解除
PersistentVolume与PVC的绑定。
- 如果是动态分配的且回收策略为
- 如果PVC绑定到了其他的PV,则需要根据
- 处理体积模式不匹配:
- 如果在尝试绑定PVC和PV时发现体积模式不匹配,则记录事件并跳过同步。
- 加速绑定:
- 如果
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// necessarynewVolume, err := ctrl.updateVolumeMigrationAnnotations(volume)if err != nil {// Nothing was saved; we will fall back into the same// condition in the next call to this methodreturn err}volume = newVolume// [Unit test set 4]if volume.Spec.ClaimRef == nil {// Volume is unusedklog.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 methodreturn 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 methodreturn 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.PersistentVolumeClaimclaimName := 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 boolclaim, 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 methodreturn err}}if err = ctrl.reclaimVolume(volume); err != nil {// Release failed, we will fall back into the same condition// in the next call to this methodreturn 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 syncClaimreturn 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 itklog.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 syncklog.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 necessaryklog.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 methodreturn err}return nil} else {// Volume is bound to a claim, but the claim is bound elsewhereif 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 methodreturn err}}if err = ctrl.reclaimVolume(volume); err != nil {// Deletion failed, we will fall back into the same condition// in the next call to this methodreturn 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的回收策略执行相应的操作。
- 检查PV是否已迁移:
- 首先,代码检查PV的Annotations中是否有
pvutil.AnnMigratedTo的标记。如果PV已被迁移(即这个标记存在),则函数直接返回nil,表示不需要进行任何操作,因为迁移后的PV将由外部提供者管理。
- 首先,代码检查PV的Annotations中是否有
- 根据回收策略执行操作:
- 接下来,代码根据PV的
Spec.PersistentVolumeReclaimPolicy字段的值,决定如何回收PV。回收策略有三种:Retain、Recycle和Delete。 - Retain:
- 如果策略是
Retain,则记录一条日志信息,表示不需要对PV进行任何操作。Retain策略意味着PV在释放后不会被自动删除或清理,管理员需要手动处理它。
- 如果策略是
- Recycle(已弃用):
- 如果策略是
Recycle,则记录一条日志信息,并安排一个回收操作。回收操作通过ctrl.scheduleOperation方法安排,实际执行的是ctrl.recycleVolumeOperation(volume)函数。注意,Recycle策略在Kubernetes较新版本中已被弃用,因为它不保证数据的安全性和完整性。
- 如果策略是
- Delete:
- 如果策略是
Delete,则记录一条日志信息,并为删除操作创建一个时间戳条目(如果尚不存在)。这是通过ctrl.operationTimestamps.AddIfNotExist方法实现的。然后,安排一个删除操作,实际执行的是ctrl.deleteVolumeOperation(volume)函数。如果删除操作失败,会记录一个错误指标,但不会立即报告延迟,延迟报告将在PV最终被删除并捕获到删除事件时发生。
- 如果策略是
- 接下来,代码根据PV的
- 处理未知的回收策略:
- 如果PV的回收策略是未知的(即不是
Retain、Recycle或Delete中的任何一个),则更新PV的状态为Failed,并记录一个警告事件,事件类型为VolumeUnknownReclaimPolicy,消息为"Volume has unrecognized PersistentVolumeReclaimPolicy"。
- 如果PV的回收策略是未知的(即不是
// 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 PVreturn 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 capturedmetrics.RecordMetric(volume.Name, &ctrl.operationTimestamps, err)}return err})default:// Unknown PersistentVolumeReclaimPolicyif _, err := ctrl.updateVolumePhaseWithEvent(volume, v1.VolumeFailed, v1.EventTypeWarning, "VolumeUnknownReclaimPolicy", "Volume has unrecognized PersistentVolumeReclaimPolicy"); err != nil {return err}}return nil
}
unbindVolume()
- 深拷贝:通过
volume.DeepCopy()创建volume的一个深拷贝volumeClone。这是为了确保原始volume对象不会被修改,所有更改都将应用于volumeClone。 - 检查注解:接下来,代码检查持久卷上是否存在
pvutil.AnnBoundByController注解。这个注解用来指示持久卷是由控制器绑定的还是由用户预先绑定的。- 如果存在这个注解,说明持久卷是由控制器绑定的。因此,将
ClaimRef设置为nil,并删除pvutil.AnnBoundByController注解。如果删除后注解列表为空,则将注解字段设置为nil。 - 如果不存在这个注解,说明持久卷是用户预先绑定的。在这种情况下,只清除
ClaimRef中的UID字段。
- 如果存在这个注解,说明持久卷是由控制器绑定的。因此,将
- 更新持久卷:使用
ctrl.kubeClient.CoreV1().PersistentVolumes().Update方法尝试更新Kubernetes API服务器中的持久卷对象。这里使用context.TODO()作为上下文参数,表示将来可能会提供一个具体的上下文。如果更新失败,记录一条错误日志并返回错误。 - 更新内部缓存:调用
ctrl.storeVolumeUpdate方法尝试更新控制器的内部缓存中的持久卷对象。如果更新失败,记录一条错误日志并返回错误。 - 记录解绑成功:如果上述步骤都成功,使用
klog.V(4).Infof记录一条日志,表明持久卷已成功解绑。 - 更新持久卷状态:最后,调用
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 = nildelete(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
}
相关文章:
CSI-PVController-volumeWorker
volumeWorker() 与claim worker流程一样,从volumeQueue中取数据,也就是取出的都是PV,如果informer中有这个pv,就进入update流程。 定义workFunc:首先,定义了一个匿名函数workFunc,这个函数是实…...
0基础 | 硬件滤波 C、RC、LC、π型
一、滤波概念 (一)滤波定义 滤波是将信号中特定波段频率滤除的操作,是抑制和防止干扰的重要措施。通过滤波器实现对特定频率成分的筛选,确保目标信号的纯净度,提升系统稳定性。 (二)滤波器分…...
图论基础理论
在我看来,想要掌握图的基础应用,仅需要三步走。 什么是图(基本概念)、图的构造(打地基)、图的遍历方式(应用的基础) 只要能OK的掌握这三步、就算图论入门了!࿰…...
leaflet 之 获取中国某个行政区的经纬度边界(latLngBounds)
思路 在json文件中获取下面的四个点 组成东北,西南两组 { “southwest”: { “lat”: 35.950, “lng”: 120.000 },//西南方 “northeast”: { “lat”: 36.200, “lng”: 120.300 }//东北方 } 最西点经度(minLng) 最东点经度(maxLng&#x…...
企业级低代码平台的架构范式转型研究
在快速迭代的数字时代,低代码平台如同一股清流,悄然成为开发者们的新宠。 它利用直观易用的拖拽式界面和丰富的预制组件,将应用程序的开发过程简化到了前所未有的程度。通过封装复杂的编程逻辑和提供强大的集成能力,低代码平台让…...
怎么免费下载GLTF/GLB格式模型文件,还可以在线编辑修改
现在非常流行glb格式模型,和gltf格式文件,可是之类模型网站非常非常少 1,咱们先直接打开http://glbxz.com 官方glb下载网站 glbxz.com 2 可以搜索,自己想要的模型关键词 3,到自己想下载素材页面 4,…...
MyBatis 中 Mapper 传递参数的多种方法
# MyBatis Mapper 传递参数的多种方法及其优势 在使用 MyBatis 进行数据库操作时,Mapper 接口的参数传递是一个非常基础但又十分重要的部分。不同的参数传递方式适用于不同的场景,合理选择可以大大提高代码的可读性和维护性。本文将详细介绍几种常见的 …...
大模型到底是怎么产生的?一文揭秘大模型诞生全过程
前言 大模型到底是怎么产生的呢? 本文将从最基础的概念开始,逐步深入,用通俗易懂的语言为大家揭开大模型的神秘面纱。 大家好,我是大 F,深耕AI算法十余年,互联网大厂核心技术岗。 知行合一,不写水文,喜欢可关注,分享AI算法干货、技术心得。 【专栏介绍】: 欢迎关注《…...
2025年3月 Scratch图形化三级 真题解析 中国电子学会全国青少年软件编程等级考试
2025年3月Scratch图形化编程等级考试三级真题试卷 一、选择题 第 1 题 默认小猫角色,scratch运行程序后,下列说法正确的是?( ) A.小猫的颜色、位置在一直变化 B.小猫在舞台中的位置在一直变化,颜色…...
判断两个 IP 地址是否在同一子网 C
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> // 将点分十进制的 IP 地址转换为 32 位无符号整数 unsigned int ip_to_uint(const char *ip) { struct in_addr addr; if (inet_pton(AF_INET, ip, &am…...
DHCP中继
前言: DHCP Relay即DHCP中继,它是为解决DHCP服务器和DHCP客户端不在同一个广播域而提出的 DHCP中继 DHCP协议依赖广播通信(如客户端发送DHCP Discover报文),但广播报文无法跨越子网,因为: 路由…...
02 - spring security基于配置文件及内存的账号密码
spring security基于配置的账号密码 文档 00 - spring security框架使用01 - spring security自定义登录页面 yml文件中配置账号密码 spring:security:user:name: adminpassword: 123456yml文件中配置账号密码后,控制台将不再输出临时密码 基于内存的账号密码 …...
【贪心之摆动序列】
题目: 分析: 这里我们使用题目中给的第二个实例来进行分析 题目中要求我们序列当中有多少个摆动序列,摆动序列满足一上一下,一下一上,这样是摆动序列,并且要输出摆动序列的最长长度 通过上面的图我们可以…...
Spring Boot 中应用的设计模式
Spring Boot 中应用的设计模式详解 Spring Boot 作为 Spring 框架的扩展,广泛使用了多种经典设计模式。以下是主要设计模式及其在 Spring Boot 中的具体应用: 一、创建型模式 1. 工厂模式 (Factory Pattern) 应用场景: BeanFactory 和 Ap…...
0x25广度优先搜索+0x26广搜变形
1.一般bfs AcWing 172. 立体推箱子 #include<bits/stdc.h> using namespace std; int n,m; char s[505][505]; int vis[3][505][505]; int df[3][4]{{1,1, 2,2},{0,0,1,1}, {0,0,2,2}}; int dx[3][4]{{0,0,1,-2},{0,0,1,-1},{2,-1,0,0}}; int dy[3][4]{{1,-2,0,0},{2,…...
java面向对象02:回顾方法
回顾方法及加深 定义方法 修饰符 返回类型 break:跳出switch和return的区别 方法名 参数列表 package com.oop.demo01;//Demo01类 public class Demo01 {//main方法public static void main(String[] args) {}/*修饰符 返回值类型 方法名(...){//方法体return…...
数据结构day05
一 栈的应用(括号匹配) 各位同学大家好,在之前的小结中,我们学习了栈和队列这两种数据结构,那从这个小节开始,我们要学习几种栈和队列的典型应用。这个小节中,我们来看一下括号匹配问题…...
windows中搭建Ubuntu子系统
windows中搭建虚拟环境 1.配置2.windows中搭建Ubuntu子系统2.1windows配置2.1.1 确认启用私有化2.1.2 将wsl2设置为默认版本2.1.3 确认开启相关配置2.1.4重启windows以加载更改配置 2.2 搭建Ubuntu子系统2.2.1 下载Ubuntu2.2.2 迁移位置 3.Ubuntu子系统搭建docker环境3.1安装do…...
ImgTool_0.8.0:图片漂白去底处理优化工具
ImgTool_0.8.0 是一款专为Windows设计的免费、绿色便携式图片处理工具,支持 Windows 7/8/10/11 系统。其核心功能为漂白去底,可高效去除扫描件或手机拍摄图片中的泛黄、灰底及阴影,同时提供智能纠偏、透视校正等辅助功能࿰…...
BGP路由协议之对等体
IGP 可以通过组播报文发现直连链路上的邻居,而 BGP 是通过 TCP:179 来实现的。BGP 需要手工的方式去配置邻居。不需要直连,只要路由能通就可以建立邻居 IBGP 与 EBGP IBGP :(Internal BGP) :位于相同自治系统的 BGP 路由器之间的 BGP 邻接关…...
Python代码相关关系矩阵的三种展示热力图-条形图
本文将深入探讨三种常用的展示技巧:corr()函数、热力图和条形图。通过这些技术,可以更直观地理解数据中的关联性,为进一步的分析和决策提供有力支持。 一、corr()函数:基础相关性分析 1. corr()函数的基本用法 corr()函数是Pandas库中用于计算数据帧(DataFrame)中两两…...
esp32cam远程图传:AI Thinker ESP32-CAM -》 服务器公网 | 服务器 -》 电脑显示
用AI Thinker ESP32-CAM板子访问公网ip的5112端口并上传你的摄像头拍摄的图像视频数据,并写一段python程序打开弹窗接受图像实现超远程图像传输教程免费 1. 首先你要有一个公网ip也就是去买一台拥有公网的服务器电脑,我买的是腾讯云1年38元的服务器还可…...
CSI-PVController-claimWorker
claimWorker() claim worker中循环执行workFunc() claim worker从claimQueue中取数据,也就是取出的都是PVCworkFunc首先从队列中取出一个obj,然后拿name去informer缓存中尝试获取 如果在informer缓存。说明不是删除事件,执行updateClaim()函…...
【家政平台开发(40)】功能测试全解析:从执行到报告撰写
本【家政平台开发】专栏聚焦家政平台从 0 到 1 的全流程打造。从前期需求分析,剖析家政行业现状、挖掘用户需求与梳理功能要点,到系统设计阶段的架构选型、数据库构建,再到开发阶段各模块逐一实现。涵盖移动与 PC 端设计、接口开发及性能优化,测试阶段多维度保障平台质量,…...
[特殊字符] 第十七讲 | 随机森林:变量重要性识别与建模实战
📌 关键词:随机森林、变量重要性、建模、分类、回归、R语言、可解释性 🎯 一、随机森林到底是什么? 随机森林(Random Forest)是由 Breiman 于 2001 年提出的集成学习方法,本质是由多个决策树模型组成的“森林”,通过投票或平均的方式提高预测精度和泛化能力。 ✅ 支…...
AIDD-人工智能药物-pyecharts-gallery
给大家安利一个NSC期刊级别的图-pyecharts-gallery 网址 https://gallery.pyecharts.org pyecharts-gallery 英文文档在这 - English Introduction is Here 项目简介 项目基于 pyecharts 2.0.3 版本进行展示Apache ECharts (incubating) 官方实例 项目须知 项目代码结构…...
ARM裸机开发——交叉编译器
交叉编译器: 下载: 链接: https://releases.linaro.org/components/toolchain/binaries/4.9-2017.01/arm-linux-gnueabihf/ 根据核心板的单片机架构进行下载 解压: 首先交叉编译器的压缩包先下载到家目录下的某一个目录中&am…...
WPF轮播图动画交互 动画缩放展示图片
WPF轮播图动画交互 动画缩放展示图片 效果如下图: XAML代码: <Window x:Class"Caroursel.MainWindow"xmlns"http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x"http://schemas.microsoft.com/winfx/20…...
开启深度学习之旅
深度学习作为人工智能领域最激动人心的分支之一,正在改变我们与科技互动的方式。本文将为您提供深度学习的入门指南,帮助您踏上这一充满可能性的旅程。 一、深度学习基础概念 深度学习是机器学习的一个子集,它使用多层神经网络来模拟人脑的…...
TDengine 语言连接器(Go)
简介 driver-go 是 TDengine 的官方 Go 语言连接器,实现了 Go 语言 database/sql 包的接口。Go 开发人员可以通过它开发存取 TDengine 集群数据的应用软件。 Go 版本兼容性 支持 Go 1.14 及以上版本。 支持的平台 原生连接支持的平台和 TDengine 客户端驱动支持…...
