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

【k8s深入学习之 event 记录】初步了解 k8s event 记录机制

event 事件记录初始化

  • 一般在控制器都会有如下的初始化函数,初始化 event 记录器等参数
1. 创建 EventBroadcaster
  • record.NewBroadcaster(): 创建事件广播器,用于记录和分发事件。
  • StartLogging(klog.Infof): 将事件以日志的形式输出。
  • StartRecordingToSink: 将事件发送到 Kubernetes API Server,存储为 Event 资源。
2. 创建 EventRecorder
  • NewRecorder(scheme, source)从广播器中创建事件记录器。
    • scheme: 用于验证和序列化资源。
    • source: 指定事件的来源(如 example-controller)。
import "k8s.io/client-go/tools/record"func (c *controller) Initialize(opt *framework.ControllerOption) error {// ...// 1. 创建事件广播器 eventBroadcastereventBroadcaster := record.NewBroadcaster()// 将 event 记录到 logeventBroadcaster.StartLogging(klog.Infof)// 将 event 记录到 apiserver// c.kubeClient.CoreV1().Events("") 这个是创建一个可以操作任意 ns 下 event 的 clienteventBroadcaster.StartRecordingToSink(&corev1.EventSinkImpl{Interface: c.kubeClient.CoreV1().Events("")})// 2. 基于事件广播器 创建事件记录器 Recorderc.recorder = eventBroadcaster.NewRecorder(versionedscheme.Scheme, v1.EventSource{Component: "example-controller"})
}// 事件的记录
const Create-Reason = "PodCreate"
func (c *controller)Controller_Do_Something(pod *corev1.Pod){newPod:= pod.DeepCopy()// 生成个 event,并记录// 内容为 newPod 创建成功,event等级为 Normal,Reason 是 PodCreate,Message 是 Create Pod succeed// 之后 Recorder 内的 eventBroadcaster 会将此 event 广播出去,然后 eventBroadcaster 之前注册的日志记录和event存储逻辑会执行// 日志记录逻辑,会通过 klog.Infof 将此 event 打印出来// event存储逻辑,会将此 event 存储到 apiserverc.recorder.Event(newPod, v1.EventTypeNormal, Create-Reason, "Create Pod succeed")
}

源码解析

在这里插入图片描述

0- 总体逻辑设计

  1. 控制中心 Broadcaster

    • eventBroadcaster := record.NewBroadcaster() 创建一个公共数据源(或理解为总控中心,也可以称之为控制器,但不是k8s 控制器)

      • 返回的是eventBroadcasterImpl 结构体,其封装了Broadcaster结构体,因此 eventBroadcasterImpl 结构体的字段很丰富
    • Broadcaster 中的字段主要记录处理 event 的监听者watchers,以及分发的控制等

      • eventBroadcaster.StartLogging(klog.Infof) 就是一个 watcher
      • eventBroadcaster.StartRecordingToSink(&corev1.EventSinkImpl{Interface: c.kubeClient.CoreV1().Events("")}) 也是个 watcher
      • 这些 watcher 都会被记录到Broadcaster结构体的watchers map[int64]*broadcasterWatcher 的map 中
    • eventBroadcasterImplBroadcaster 基础上增加少量配置参数和控制函数

  2. Event 分发和 watcher 处理逻辑

    • eventBroadcaster := record.NewBroadcaster() 执行过程中会调用 watch.NewLongQueueBroadcaster(maxQueuedEvents, watch.DropIfChannelFull) 函数,其会开启 event 分发逻辑go m.loop()
      • go m.loop() 用于处理 event 的分发,读取eventBroadcasterImplincoming chan Event 通道传来的 event,分发给各个 watcher 的 result channel
        • incoming 中的 event 是由 Recorder 写入的,Recorder.Event 会生成个 event ,并发送到incomimg channel 中
        • go m.loop()函数会读取incoming通道中的 Event,发送个各个watcher, 然后各个watcher执行自己的逻辑(如记录为 info级别日志、或写入apiserver等)
      • 同时为了避免主进程的结束导致go m.loop()进程结束,NewLongQueueBroadcaster 还利用distributing sync.WaitGroup变量,进行 m.distributing.Add(1),让主进程等待(避免主进程快速结束,导致 loop 进程结束)
    • StartLoggingStartRecordingToSink 函数会调用 StartEventWatcher 函数, StartEventWatcher 函数将传入的参数变为一个 event处理函数 eventHandler, StartEventWatcher 函数同时会开启一个 go 协程,读取各自 watcher result channel 中的 event,之后用eventHandler进行处理(如记录为 info级别日志、或写入apiserver等)
  3. Event 产生逻辑

    • Recorder 是由 eventBroadcaster.NewRecorder 创建出来的,相当于对eventBroadcasterImplBroadcaster 的封装

    • Recorder.Event 会生成个 event ,并发送到incomimg channel 中

      • Recorder 会利用Broadcasterincoming channel 写入 event

      • Recorder 会利用BroadcasterincomingBlock,控制写入时的并发,避免同一时间写入 event 过多导致错乱(这部分逻辑在blockQueue 函数中)

1- 控制中心的创建 —— NewBroadcaster 函数

  • 创建的eventBroadcaster ,实际上就是创建一个 eventBroadcasterImpl 结构体,并传入一些配置参数进行初始化
  • 注意 eventBroadcasterImpl封装了Broadcaster结构体
    • 注意Broadcaster中有很多channelwatchers和分发相关控制、并发控制字段等
      • eventBroadcaster.StartLogging(klog.Infof) 就是一个 watcher
      • eventBroadcaster.StartRecordingToSink(&corev1.EventSinkImpl{Interface: c.kubeClient.CoreV1().Events("")}) 也是个 watcher
      • 这些 watcher 都会被记录到watchers map[int64]*broadcasterWatcher 的map 中
    • 基于eventBroadcaster 创建的Recorder,实际上级就是对eventBroadcasterImpl结构体的封装
    • 之后Recorder创建 event 时,会传入到eventBroadcasterImplBroadcaster
// 路径 mod/k8s.io/client-go@v0.29.0/tools/record/event.go
const maxQueuedEvents = 1000
type FullChannelBehavior int
const (WaitIfChannelFull FullChannelBehavior = iotaDropIfChannelFull
)// Creates a new event broadcaster.
func NewBroadcaster(opts ...BroadcasterOption) EventBroadcaster {c := config{sleepDuration: defaultSleepDuration,}for _, opt := range opts {opt(&c)}// 重点关注eventBroadcaster := &eventBroadcasterImpl{Broadcaster:   watch.NewLongQueueBroadcaster(maxQueuedEvents, watch.DropIfChannelFull),sleepDuration: c.sleepDuration,options:       c.CorrelatorOptions,}ctx := c.Contextif ctx == nil {ctx = context.Background()} else {// Calling Shutdown is not required when a context was provided:// when the context is canceled, this goroutine will shut down// the broadcaster.go func() {<-ctx.Done()eventBroadcaster.Broadcaster.Shutdown()}()}eventBroadcaster.cancelationCtx, eventBroadcaster.cancel = context.WithCancel(ctx)// 重点关注return eventBroadcaster
}// 路径 mod/k8s.io/apimachinery@v0.29.0/pkg/watch/mux.go
// NewLongQueueBroadcaster functions nearly identically to NewBroadcaster,
// except that the incoming queue is the same size as the outgoing queues
// (specified by queueLength).
func NewLongQueueBroadcaster(queueLength int, fullChannelBehavior FullChannelBehavior) *Broadcaster {m := &Broadcaster{watchers:            map[int64]*broadcasterWatcher{},incoming:            make(chan Event, queueLength),stopped:             make(chan struct{}),watchQueueLength:    queueLength,fullChannelBehavior: fullChannelBehavior,}m.distributing.Add(1)	// distributing sync.WaitGroup, 1 个进程go m.loop()  					// loop 进程,很关键! 处理 event 的分发,分发给 watcher 处理		return m
}
1.1- eventBroadcasterImpl 结构体
// 路径 mod/k8s.io/client-go@v0.29.0/tools/record/event.go
type eventBroadcasterImpl struct {*watch.Broadcaster  // 此处引用下面的结构体sleepDuration  time.Durationoptions        CorrelatorOptionscancelationCtx context.Contextcancel         func()
}// 路径 /mod/k8s.io/apimachinery@v0.29.0/pkg/watch/mux.go
// Broadcaster distributes event notifications among any number of watchers. Every event
// is delivered to every watcher.
type Broadcaster struct {watchers     map[int64]*broadcasterWatcher  // map 结构  id 和 watcher 的映射nextWatcher  int64													// 下一个 watcher 该分配的 iddistributing sync.WaitGroup									// 用于保证分发函数 loop 正常运行,避免主函数停止,导致 loop 函数停止// incomingBlock allows us to ensure we don't race and end up sending events// to a closed channel following a broadcaster shutdown.incomingBlock sync.Mutex										// 避免接收 event 时,event 过多导致的并发,因此需要锁进行控制incoming      chan Event										// 承接生成的 event,其他 watcher 会从此 channel 中读取 event 进行记录到 apiserver 或日志打印等stopped       chan struct{}									// 承接关闭广播器 Broadcaster 的停止信号// How large to make watcher's channel.watchQueueLength int// If one of the watch channels is full, don't wait for it to become empty.// Instead just deliver it to the watchers that do have space in their// channels and move on to the next event.// It's more fair to do this on a per-watcher basis than to do it on the// "incoming" channel, which would allow one slow watcher to prevent all// other watchers from getting new events.fullChannelBehavior FullChannelBehavior
}
1.2- EventBroadcaster 接口
// 路径 mod/k8s.io/client-go@v0.29.0/tools/record/event.go
// EventBroadcaster knows how to receive events and send them to any EventSink, watcher, or log.
type EventBroadcaster interface {// StartEventWatcher starts sending events received from this EventBroadcaster to the given// event handler function. The return value can be ignored or used to stop recording, if// desired.StartEventWatcher(eventHandler func(*v1.Event)) watch.Interface// StartRecordingToSink starts sending events received from this EventBroadcaster to the given// sink. The return value can be ignored or used to stop recording, if desired.StartRecordingToSink(sink EventSink) watch.Interface// StartLogging starts sending events received from this EventBroadcaster to the given logging// function. The return value can be ignored or used to stop recording, if desired.StartLogging(logf func(format string, args ...interface{})) watch.Interface// StartStructuredLogging starts sending events received from this EventBroadcaster to the structured// logging function. The return value can be ignored or used to stop recording, if desired.StartStructuredLogging(verbosity klog.Level) watch.Interface// NewRecorder returns an EventRecorder that can be used to send events to this EventBroadcaster// with the event source set to the given event source.NewRecorder(scheme *runtime.Scheme, source v1.EventSource) EventRecorderLogger// Shutdown shuts down the broadcaster. Once the broadcaster is shut// down, it will only try to record an event in a sink once before// giving up on it with an error message.Shutdown()
}
1.3- NewRecorder 接口的实现
  • Recorder 封装了 Broadcaster
// 路径 mod/k8s.io/client-go@v0.29.0/tools/record/event.go// NewRecorder returns an EventRecorder that records events with the given event source.
func (e *eventBroadcasterImpl) NewRecorder(scheme *runtime.Scheme, source v1.EventSource) EventRecorderLogger {return &recorderImplLogger{recorderImpl: &recorderImpl{scheme, source, e.Broadcaster, clock.RealClock{}}, logger: klog.Background()}
}type recorderImplLogger struct {*recorderImpllogger klog.Logger
}type recorderImpl struct {scheme *runtime.Schemesource v1.EventSource*watch.Broadcasterclock clock.PassiveClock
}
1.3- loop(event的分发)
// // 路径 /mod/k8s.io/apimachinery@v0.29.0/pkg/watch/mux.go
// loop receives from m.incoming and distributes to all watchers.
func (m *Broadcaster) loop() {// Deliberately not catching crashes here. Yes, bring down the process if there's a// bug in watch.Broadcaster.for event := range m.incoming {if event.Type == internalRunFunctionMarker {event.Object.(functionFakeRuntimeObject)()continue}m.distribute(event)  // 将 event 分发给 watcher}m.closeAll()m.distributing.Done()
}// distribute sends event to all watchers. Blocking.
func (m *Broadcaster) distribute(event Event) {if m.fullChannelBehavior == DropIfChannelFull {for _, w := range m.watchers {select {case w.result <- event: // 将 event 发送到 watcher 的 result channel,等待 watcher 进行处理case <-w.stopped:default: // Don't block if the event can't be queued.}}} else {for _, w := range m.watchers {select {case w.result <- event:case <-w.stopped:}}}
}
1.4 event 的产生
// 路径 mod/k8s.io/client-go@v0.29.0/tools/record/event.go// EventRecorder knows how to record events on behalf of an EventSource.
type EventRecorder interface {// Event constructs an event from the given information and puts it in the queue for sending.// 'object' is the object this event is about. Event will make a reference-- or you may also// pass a reference to the object directly.// 'eventtype' of this event, and can be one of Normal, Warning. New types could be added in future// 'reason' is the reason this event is generated. 'reason' should be short and unique; it// should be in UpperCamelCase format (starting with a capital letter). "reason" will be used// to automate handling of events, so imagine people writing switch statements to handle them.// You want to make that easy.// 'message' is intended to be human readable.//// The resulting event will be created in the same namespace as the reference object.Event(object runtime.Object, eventtype, reason, message string)// Eventf is just like Event, but with Sprintf for the message field.Eventf(object runtime.Object, eventtype, reason, messageFmt string, args ...interface{})// AnnotatedEventf is just like eventf, but with annotations attachedAnnotatedEventf(object runtime.Object, annotations map[string]string, eventtype, reason, messageFmt string, args ...interface{})
}func (recorder *recorderImpl) Event(object runtime.Object, eventtype, reason, message string) {recorder.generateEvent(klog.Background(), object, nil, eventtype, reason, message)
}func (recorder *recorderImpl) generateEvent(logger klog.Logger, object runtime.Object, annotations map[string]string, eventtype, reason, message string) {ref, err := ref.GetReference(recorder.scheme, object)if err != nil {logger.Error(err, "Could not construct reference, will not report event", "object", object, "eventType", eventtype, "reason", reason, "message", message)return}if !util.ValidateEventType(eventtype) {logger.Error(nil, "Unsupported event type", "eventType", eventtype)return}event := recorder.makeEvent(ref, annotations, eventtype, reason, message)event.Source = recorder.sourceevent.ReportingInstance = recorder.source.Hostevent.ReportingController = recorder.source.Component// NOTE: events should be a non-blocking operation, but we also need to not// put this in a goroutine, otherwise we'll race to write to a closed channel// when we go to shut down this broadcaster.  Just drop events if we get overloaded,// and log an error if that happens (we've configured the broadcaster to drop// outgoing events anyway).sent, err := recorder.ActionOrDrop(watch.Added, event)if err != nil {logger.Error(err, "Unable to record event (will not retry!)")return}if !sent {logger.Error(nil, "Unable to record event: too many queued events, dropped event", "event", event)}
}// Action distributes the given event among all watchers, or drops it on the floor
// if too many incoming actions are queued up.  Returns true if the action was sent,
// false if dropped.
func (m *Broadcaster) ActionOrDrop(action EventType, obj runtime.Object) (bool, error) {m.incomingBlock.Lock()defer m.incomingBlock.Unlock()// Ensure that if the broadcaster is stopped we do not send events to it.select {case <-m.stopped:return false, fmt.Errorf("broadcaster already stopped")default:}select {case m.incoming <- Event{action, obj}:return true, nildefault:return false, nil}
}
1.5 watcher 的处理
eventBroadcaster.StartLogging(klog.Infof)// StartLogging starts sending events received from this EventBroadcaster to the given logging function.
// The return value can be ignored or used to stop recording, if desired.
func (e *eventBroadcasterImpl) StartLogging(logf func(format string, args ...interface{})) watch.Interface {return e.StartEventWatcher(func(e *v1.Event) {  // 对应 下面 eventHandlerlogf("Event(%#v): type: '%v' reason: '%v' %v", e.InvolvedObject, e.Type, e.Reason, e.Message)})
}// StartEventWatcher starts sending events received from this EventBroadcaster to the given event handler function.
// The return value can be ignored or used to stop recording, if desired.
func (e *eventBroadcasterImpl) StartEventWatcher(eventHandler func(*v1.Event)) watch.Interface {watcher, err := e.Watch()if err != nil {klog.FromContext(e.cancelationCtx).Error(err, "Unable start event watcher (will not retry!)")}go func() {  // 直接运行了defer utilruntime.HandleCrash()for {select {case <-e.cancelationCtx.Done():watcher.Stop()returncase watchEvent := <-watcher.ResultChan():  // 从 watcher result channel 中取出 event event, ok := watchEvent.Object.(*v1.Event)if !ok {// This is all local, so there's no reason this should// ever happen.continue}eventHandler(event) // 对 event 进行处理 }}}()return watcher
}

附录1 | 示例详解

以下是一个完整的 EventRecorderEventBroadcaster 实例化的代码示例,展示如何在 Kubernetes 控制器中记录事件。代码包含详细注释,适合用于实际开发或学习:


代码示例

package mainimport ("context""fmt""time"corev1 "k8s.io/api/core/v1""k8s.io/apimachinery/pkg/runtime"metav1 "k8s.io/apimachinery/pkg/runtime/schema""k8s.io/client-go/kubernetes""k8s.io/client-go/kubernetes/fake""k8s.io/client-go/tools/record""k8s.io/client-go/util/workqueue""k8s.io/klog/v2"
)// 主要逻辑的入口
func main() {// 1. 创建 Kubernetes 客户端// 这里我们使用 fake 客户端进行演示,生产环境应替换为真实的 `kubernetes.Clientset`clientset := fake.NewSimpleClientset()// 2. 创建事件广播器(EventBroadcaster)// 事件广播器是事件处理的核心,负责分发事件到日志和 API ServereventBroadcaster := record.NewBroadcaster()// 3. 启动日志记录功能// 通过 `klog.Infof` 输出事件到控制台或日志文件eventBroadcaster.StartLogging(klog.Infof)// 4. 启动事件的 API Server 记录功能// 配置事件接收器,将事件发送到 API Server,通过 `kubeClient.CoreV1().Events` 接口记录事件eventBroadcaster.StartRecordingToSink(&corev1.EventSinkImpl{Interface: clientset.CoreV1().Events(""),})// 5. 创建事件记录器(EventRecorder)// EventRecorder 用于开发者实际记录事件recorder := eventBroadcaster.NewRecorder(scheme(), // 提供资源的模式信息,常用的是 `runtime.NewScheme()` 或自定义的 schemecorev1.EventSource{Component: "example-controller"},)// 6. 模拟一个 Kubernetes 对象(如 Pod)的引用// 事件通常需要与具体的 Kubernetes 资源关联objRef := &corev1.ObjectReference{Kind:       "Pod",                  // 资源类型Namespace:  "default",              // 命名空间Name:       "example-pod",          // 资源名称UID:        "12345-abcde-67890",    // 唯一标识符APIVersion: "v1",                   // API 版本}// 7. 使用 EventRecorder 记录事件// 记录一个正常类型的事件(EventTypeNormal)recorder.Eventf(objRef, corev1.EventTypeNormal, "PodCreated", "Successfully created Pod %s", objRef.Name)// 模拟一个警告事件(EventTypeWarning)recorder.Eventf(objRef, corev1.EventTypeWarning, "PodFailed", "Failed to create Pod %s due to insufficient resources", objRef.Name)// 模拟一个控制器逻辑的操作processQueue(recorder, objRef)// 等待事件记录完成time.Sleep(2 * time.Second)
}// 模拟处理队列的函数
func processQueue(recorder record.EventRecorder, objRef *corev1.ObjectReference) {// 创建一个工作队列queue := workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter())// 将任务添加到队列queue.Add("task1")// 模拟处理队列中的任务for queue.Len() > 0 {item, _ := queue.Get()defer queue.Done(item)// 记录一个事件,表明任务已处理recorder.Eventf(objRef, corev1.EventTypeNormal, "TaskProcessed", "Successfully processed task: %v", item)fmt.Printf("Processed task: %v\n", item)}
}// 创建 Scheme 用于事件记录器
// Scheme 是 Kubernetes 中资源的模式定义,用于确定资源类型和序列化方式
func scheme() *runtime.Scheme {s := runtime.NewScheme()// 添加 CoreV1 资源到 Scheme 中corev1.AddToScheme(s)metav1.AddToGroupVersion(s, schema.GroupVersion{Version: "v1"})return s
}

代码详解

1. 创建 EventBroadcaster
  • record.NewBroadcaster(): 创建事件广播器,用于记录和分发事件。
  • StartLogging(klog.Infof): 将事件以日志的形式输出。
  • StartRecordingToSink: 将事件发送到 Kubernetes API Server,存储为 Event 资源。
2. 创建 EventRecorder
  • NewRecorder(scheme, source)从广播器中创建事件记录器。
    • scheme: 用于验证和序列化资源。
    • source: 指定事件的来源(如 example-controller)。
3. 创建对象引用
  • ObjectReference: 用于标识事件关联的 Kubernetes 资源。包括类型、名称、命名空间、UID 等信息。
4. 记录事件
  • Eventf
    用于记录事件,包括:
    • 类型corev1.EventTypeNormal(正常)或 corev1.EventTypeWarning(警告)。
    • 原因:事件发生的原因(如 PodCreated)。
    • 消息:事件的详细描述。
5. 模拟队列任务
  • 使用 workqueue 模拟处理任务,记录任务完成时的事件。

运行结果

日志输出

事件将输出到日志(通过 klog):

I1119 12:34:56.123456   12345 example.go:52] Event(v1.ObjectReference{...}): type: 'Normal' reason: 'PodCreated' message: 'Successfully created Pod example-pod'
I1119 12:34:56.123457   12345 example.go:53] Event(v1.ObjectReference{...}): type: 'Warning' reason: 'PodFailed' message: 'Failed to create Pod example-pod due to insufficient resources'
Processed task: task1
事件存储

如果使用真实客户端,事件会存储在集群中,可通过 kubectl 查看:

kubectl get events
事件输出
LAST SEEN   TYPE      REASON         OBJECT            MESSAGE
5s          Normal    PodCreated     Pod/example-pod   Successfully created Pod example-pod
5s          Warning   PodFailed      Pod/example-pod   Failed to create Pod example-pod due to insufficient resources
5s          Normal    TaskProcessed  Pod/example-pod   Successfully processed task: task1

代码用途

  • 日志记录和事件管理: 帮助开发者跟踪控制器的运行状态和资源变更。
  • 任务队列处理: 将业务逻辑与事件机制结合,记录每个关键操作的状态。

以上代码展示了如何使用 EventRecorderEventBroadcaster 实现 Kubernetes 控制器中的事件管理,适合用于开发自定义控制器或调试集群事件处理逻辑。

附录2 | stoped 变量的作用

NewBroadcaster 函数中的 stopped 通道用于实现对 Broadcaster 对象的停止和关闭控制。具体来说,它的作用是在广播器的生命周期中进行信号传递,用于通知 Broadcaster 是否已经停止运行。

详细分析:

1. 通道的定义:
stopped: make(chan struct{}),

stopped 是一个无缓冲的通道,类型为 struct{}。无缓冲的通道用于信号传递,表示某个事件的发生,而不需要传递具体数据。这里的 struct{} 是一个空结构体,占用零内存,因此不会传递任何实际数据。

2. 停止广播器的作用:

stopped 通道用于在广播器停止时传递一个信号。通常这种信号用于通知相关的 goroutine 或者处理流程,广播器已经停止工作,可以做一些清理操作或者退出。

3. 与 go m.loop() 的配合:
go m.loop()

NewBroadcaster 中,启动了一个新的 goroutine 来执行 m.loop()。这个 loop 方法通常是处理传入事件并进行分发的核心逻辑。loop 方法可能会定期检查 stopped 通道,判断是否已经收到停止信号。

4. 典型的停止逻辑(假设)
func (m *Broadcaster) loop() {for {select {case event := <-m.incoming:// 处理事件逻辑case <-m.stopped:// 收到停止信号后,退出 goroutinereturn}}
}

在这个假设的 loop 实现中,select 语句等待从 m.incoming 通道接收事件,或者等待 stopped 通道的信号。当收到 stopped 通道的信号时,loop 方法退出,从而停止事件的分发。

5. 停止广播器的触发:

在实际代码中,某个外部操作可能会通过 close(m.stopped) 或者发送一个信号到 m.stopped 通道,来通知 Broadcaster 停止工作。比如在处理完所有事件或者发生错误时,可能会调用停止操作:

close(m.stopped)

或者

m.stopped <- struct{}{}

这样 loop 就会检测到停止信号并退出。


总结

stopped 通道在 Broadcaster 中的作用是提供一个停止信号,通知正在运行的 goroutine(如 loop 方法)停止执行。这种设计使得 Broadcaster 能够优雅地处理停止操作,确保所有 goroutine 都能够适时退出并清理资源。

相关文章:

【k8s深入学习之 event 记录】初步了解 k8s event 记录机制

event 事件记录初始化 一般在控制器都会有如下的初始化函数&#xff0c;初始化 event 记录器等参数 1. 创建 EventBroadcaster record.NewBroadcaster(): 创建事件广播器&#xff0c;用于记录和分发事件。StartLogging(klog.Infof): 将事件以日志的形式输出。StartRecording…...

InterHub:为自动驾驶提供密集互动事件的自然驾驶轨迹数据集

InterHub 是一个为自动驾驶领域设计的自然驾驶轨迹数据集&#xff0c;它通过深入挖掘自然驾驶记录中的密集互动事件而构建。 InterHub 的特点在于其形式化的方法&#xff0c;这使得数据集能够精确描述和提取多智能体之间的互动事件&#xff0c;揭示了现有自动驾驶解决方案的局限…...

鸿蒙Next星河版基础用例

目录&#xff1a; 1、鸿蒙箭头函数的写法2、鸿蒙数据类型的定义3、枚举的定义以及使用4、position绝对定位及层级zIndex5、字符串的拼接转换以及数据的处理(1)字符串转数字(2)数字转字符串(3)布尔值转换情况(4)数组的增删改查 6、三元表达式7、鸿蒙for循环的几种写法7.1、基本用…...

ScribblePrompt 医学图像分割工具,三种标注方式助力图像处理

ScribblePrompt 的主要目标是简化医学图像的分割过程&#xff0c;这在肿瘤检测、器官轮廓描绘等应用中至关重要。相比依赖大量人工标注数据&#xff0c;该工具允许用户通过少量输入&#xff08;例如简单的涂鸦或点位&#xff09;来引导模型优化分割结果。这种方式减少了医学专家…...

PKO-LSSVM-Adaboost班翠鸟优化最小二乘支持向量机结合AdaBoost分类模型

PKO-LSSVM-Adaboost班翠鸟优化最小二乘支持向量机结合AdaBoost分类模型 目录 PKO-LSSVM-Adaboost班翠鸟优化最小二乘支持向量机结合AdaBoost分类模型效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.PKO-LSSVM-Adaboost班翠鸟优化最小二乘支持向量机结合AdaBoost分类模…...

5G学习笔记之随机接入

目录 1. 概述 2. MSG1 2.1 选择SSB 2.2 选择Preamble Index 2.3 选择发送Preamble的时频资源 2.4 确定RA-RNTI 2.5 确定发送功率 3. MSG2 4. MSG3 5. MSG4 6. 其它 6.1 切换中的随机接入 6.2 SI请求的随机接入 6.3 通过PDCCH order重新建立同步 1. 概述 随机接入…...

爬虫专栏第二篇:Requests 库实战:从基础 GET 到 POST 登录全攻略

简介&#xff1a;本文聚焦 Requests 库的强大功能与应用实战。首先介绍其安装步骤及版本选择要点&#xff0c;随后深入讲解 GET 请求&#xff0c;以百度页面为例&#xff0c;展示如何发起基本 GET 请求、巧妙添加 headers 与参数以精准搜索&#xff0c;以及正确设置 encoding 避…...

Android Studio更改项目使用的JDK

一、吐槽 过去&#xff0c;在安卓项目中配置JDK和Gradle的过程非常直观&#xff0c;只需要进入Android Studio的File菜单中的Project Structure即可进行设置&#xff0c;十分方便。 原本可以在这修改JDK: 但大家都知道&#xff0c;Android Studio的狗屎性能&#xff0c;再加…...

鸿蒙技术分享:Navigation页面管理-鸿蒙@fw/router框架源码解析(二)

本文是系列文章&#xff0c;其他文章见&#xff1a; 鸿蒙fw/router框架源码解析&#xff08;一&#xff09;-Router页面管理 鸿蒙fw/router框架源码解析&#xff08;三&#xff09;-Navigation页面容器封装 鸿蒙fw/router框架源码解析&#xff08;四&#xff09;-路由Hvigor插件…...

数据结构:树

树的基本定义&#xff1a; 树是一种数据结构&#xff0c;它是由n&#xff08;n>1&#xff09;个有限节点组成一个具有层次关系的集合。把它叫做 “树” 是因为它看起来像一棵倒挂的树&#xff0c;也就是说它是根朝上&#xff0c;而叶朝下的。它具有以下的特点&#xff1a; …...

矩阵sum,prod函数

s u m sum sum表示求和, p r o d prod prod表示求乘积 s u m sum sum函数 对于矩阵&#xff0c;可以对某一行或某一列求和&#xff0c;也可以对矩阵整体求和 s u m ( a , 1 ) sum(a,1) sum(a,1)计算每一列的和 s u m ( a , 2 ) sum(a,2) sum(a,2)计算每一行的和 计算矩阵整体…...

Cursor安装与使用,5分钟完成需求

Cursor简单介绍 Cursor是一款基于AI的代码编辑器&#xff0c;旨在帮助开发者更高效地编写和管理代码。它提供了智能代码补全、AI对话和跨文件编辑等创新功能。 一、安装下载 1、下载cursor&#xff1a;https://www.cursor.com/ 2、注册账号&#xff0c;直接拿自己的邮箱登录…...

嵌入式系统应用-LVGL的应用-平衡球游戏 part1

平衡球游戏 part1 1 平衡球游戏的界面设计2 界面设计2.1 背景设计2.2 球的设计2.3 移动球的坐标2.4 用鼠标移动这个球2.5 增加边框规则2.6 效果图 3 为小球增加增加动画效果3.1 增加移动效果代码3.2 具体效果图片 平衡球游戏 part2 第二部分文章在这里 1 平衡球游戏的界面设计…...

Vue基本语法

Options API 选项式/配置式api 需要在script中的export default一个对象对象中可以包含data、method、components等keydata是数据&#xff0c;数据必须是一个方法&#xff08;如果是对象&#xff0c;会导致多组件的时候&#xff0c;数据互相影响&#xff0c;因为对象赋值后&…...

UIE与ERNIE-Layout:智能视频问答任务初探

内容来自百度飞桨ai社区UIE与ERNIE-Layout&#xff1a;智能视频问答任务初探&#xff1a; 如有侵权&#xff0c;请联系删除 1 环境准备 In [2] # 安装依赖库 !pip install paddlenlp --upgrade !pip install paddleocr --upgrade !pip install paddlespeech --upgrade In …...

Mac启动服务慢问题解决,InetAddress.getLocalHost().getHostAddress()慢问题。

项目启动5分钟&#xff0c;很明显有问题。像网上其他的提高jvm参数就不说了&#xff0c;应该不是这个问题&#xff0c;也就快一点。 首先找到自己的电脑名称&#xff08;用命令行也行&#xff0c;只要能找到自己电脑名称就行&#xff0c;这里直接在共享里看&#xff09;。 复制…...

Django 视图层

from django.shortcuts import render, HttpResponse, redirectfrom django.http import JsonResponse1. render: 渲染模板 def index(request):print(reverse(index))return render(request, "index.html")return render(request, index.html, context{name: lisi})…...

HickWall 详解

优质博文&#xff1a;IT-BLOG-CN 一、监控分类 【1】Tracing调用链&#xff1a; 【2】Logging日志&#xff1a; 【3】Metrics指标&#xff1a;在应用发布之后&#xff0c;会长时间存在的度量维度。某个接口的请求量、响应时间。 Metrics数据模型 二、Metirc 接入 【1】pom…...

开源的跨平台SQL 编辑器Beekeeper Studio

一款开源的跨平台 SQL 编辑器&#xff0c;提供 SQL 语法高亮、自动补全、数据表内容筛选与过滤、连接 Web 数据库、存储历史查询记录等功能。该编辑器支持 SQLite、MySQL、MariaDB、Postgres 等主流数据库&#xff0c;并兼容 Windows、macOS、Linux 等桌面操作系统。 项目地址…...

Linux应用层学习——Day4(进程处理)

system #include<stdio.h> #include<stdlib.h>int main(int argc, char const *argv[]) {//使用标准库函数创建子进程//int system (const char *__command);//const char *__command:使用linux命令直接创建一个子进程//return:成功返回0 失败返回失败编号int sys…...

起别名typedef

#include<stdio.h> //typedef int myType1; //typedef char myType2; typedef struct { int a; int b; }po; int main() { /*myType1 a5; myType2 bo; printf("%d\n",a); printf("%c\n",b);*/ po p;//不需要加struct关键…...

【Linux内核】ashmem pin/unpin

前言 在 Linux 内核的 ASHMEM&#xff08;Android Shared Memory&#xff09;实现中&#xff0c;pin 和 unpin 操作主要用于管理共享内存的生命周期和可用性。这些操作有助于确保在内存使用期间&#xff0c;特定的共享内存区域不会被回收或释放。 Pin 操作 定义 Pin 操作用…...

【docker】docker网络六种网络模式

Docker 网络模式总结 网络模式描述使用场景bridge默认的网络模式&#xff0c;容器之间通过虚拟网桥通信&#xff0c;容器与宿主机隔离。单机部署、本地开发、小型项目host容器与宿主机共享网络堆栈&#xff0c;容器直接使用宿主机的 IP 地址。高性能网络应用、日志处理、大量与…...

永磁同步电机谐波抑制算法(11)——基于矢量比例积分调节器(vector PI controller,VPI controller)的谐波抑制策略

1.前言 相比于传统的谐振调节器&#xff0c;矢量比例积分调节器&#xff08;vector PI controller&#xff0c;VPI controller&#xff09;多一个可调零点&#xff0c;能够实现电机模型的零极点对消。因此VPI调节器也被广泛应用于交流控制/谐波抑制中。 2.参考文献 [1] A. G…...

排序算法中稳定性的意义和作用

多关键字排序&#xff1a;当需要对数据进行多个关键字排序时&#xff0c;稳定性变得非常重要。例如&#xff0c;先按次要关键字排序&#xff0c;再按主要关键字排序。如果排序算法是稳定的&#xff0c;那么在按主要关键字排序后&#xff0c;次要关键字的顺序将被保留。保持关联…...

网站怎么防御https攻击

HTTPS攻击&#xff0c;它不仅威胁到网站的数据安全&#xff0c;还可能影响用户隐私和业务稳定运行。 HTTPS攻击主要分为以下几种类型&#xff1a; 1.SSL劫持&#xff1a;攻击者通过中间人攻击手段&#xff0c;篡改HTTPS流量&#xff0c;从而实现对数据的窃取或伪造。 2.中间人攻…...

gitignore 不起作用

.gitignore不起作用 文件已提交至远程仓库&#xff0c;已经被Git跟踪。清除缓存.gitignore位置可能不是与 .git隐藏文件夹同级目录。将文件移至同级目录缓存未清除 清除缓存 清楚git缓存步骤 进入项目路径 清除本地当前的Git缓存 git rm -r --cached . 应用.gitignore等本地…...

Hive学习基本概念

基本概念 hive是什么&#xff1f; Facebook 开源&#xff0c;用于解决海量结构化日志的数据统计。 基于Hadoop的一个数据仓库工具&#xff0c;可以将结构化的数据文件映射为一张表&#xff0c;并提供类SQL查询功能 本质是将HQL转化为MapReduce程序。 Hive处理的数据存储在H…...

在 Ubuntu 使用 fonts-noto-cjk 设置 Matplotlib 支持中文的完整教程

在 Ubuntu 使用 fonts-noto-cjk 设置 Matplotlib 支持中文的完整教程 1. 为什么需要配置中文字体&#xff1f;2. 安装 fonts-noto-cjk安装命令&#xff1a;检查字体安装是否成功 3. 配置 Matplotlib 支持中文3.1 手动加载字体3.2 设置全局字体&#xff08;可选&#xff09;修改…...

《C++ Primer Plus》学习笔记|第10章 对象和类 (24-12-2更新)

文章目录 10.3 类的构造函数和析构函数10.3.2 使用构造函数显式地调用构造函数隐式地调用构造函数使用对象指针 10.3.3默认构造函数10.3.4 析构函数析构函数示例 10.4 this指针三个const的作用 10.5 对象数组10.6 类作用域10.9 复习题1.什么是类&#xff1f;2.类如何实现抽象、…...

SpringMVC接收数据

一、访问路径设置: RequestMapping注解的作用就是将请求的URL地址和处理请求的方式(handler方法)关联起来&#xff0c;建立映射关系;SpringMVC接收到指定的请求&#xff0c;就会来找到在映射关系中对应的方法来处理这个请求 1.精准路径匹配: 在RequestMapping注解指定URL地址…...

Python数组拆分(array_split())

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…...

Git 使用总结

下载 git bash&#xff1a;http://git-scm.com/download/win 第一次使用 git 时&#xff0c;配置用户信息&#xff1a; git config --global user.email "your.emailexample.com" 从github仓库中下载项目到本地&#xff0c;修改后重新上传&#xff1a; git clone 项…...

NaviveUI框架的使用 ——安装与引入(图标安装与引入)

文章目录 概述安装直接引入引入图标样式库 概述 &#x1f349;Naive UI 是一个轻量、现代化且易于使用的 Vue 3 UI 组件库&#xff0c;它提供了一组简洁、易用且功能强大的组件&#xff0c;旨在为开发者提供更高效的开发体验&#xff0c;特别是对于构建现代化的 web 应用程序。…...

YOLOv11 NCNN安卓部署

YOLOv11 NCNN安卓部署 之前自己在验证更换relu激活函数重新训练部署模型的时候&#xff0c;在使用ncnn代码推理验证效果很好&#xff0c;但是部署到安卓上cpu模式会出现大量的错误检测框&#xff0c;现已更换会官方默认的权重 前言 YOLOv11 NCNN安卓部署 目前的帧率可以稳定…...

多线程安全单例模式的传统解决方案与现代方法

在多线程环境中实现安全的单例模式时&#xff0c;传统的双重检查锁&#xff08;Double-Checked Locking&#xff09;方案和新型的std::once_flag与std::call_once机制是两种常见的实现方法。它们在实现机制、安全性和性能上有所不同。 1. 传统的双重检查锁方案 双重检查锁&am…...

golang debug调试

1. 本地调试 1&#xff1a;Add Configurations 添加配置文件&#xff08;Run kind &#xff1a;Directory&#xff09; 2&#xff1a;进入run运行窗口 3&#xff1a;debug断点调试模式 1. Resume Program (继续运行) 图标: ▶️ 或 ► 快捷键: F9&#xff08;Windows/Linux&a…...

安装 RabbitMQ 服务

安装 RabbitMQ 服务 一. RabbitMQ 需要依赖 Erlang/OTP 环境 (1) 先去 RabbitMQ 官网&#xff0c;查看 RabbitMQ 需要的 Erlang 支持&#xff1a;https://www.rabbitmq.com/ 进入官网&#xff0c;在 Docs -> Install and Upgrade -> Erlang Version Requirements (2) …...

pandas 大数据获取某列所有唯一值

目录 方法1: 方法2: 方法3 处理大数据: 方法1: data.groupby().groups.keys() import pandas as pd# 假设我们有以下的数据 data = {RTDR_name: [A, B, A, C, B, A],value: [1, 2, 3, 4, 5, 6] }# 创建 DataFrame temp_data = pd.DataFrame(data)# 获取 RTDR_name 列的…...

【AI系统】LLVM 架构设计和原理

LLVM 架构设计和原理 在上一篇文章中&#xff0c;我们详细探讨了 GCC 的编译过程和原理。然而&#xff0c;由于 GCC 存在代码耦合度高、难以进行独立操作以及庞大的代码量等缺点。正是由于对这些问题的意识&#xff0c;人们开始期待新一代编译器的出现。在本节&#xff0c;我们…...

Node.js 中的文件系统(fs)模块详解与代码示例

Node.js 中的文件系统&#xff08;fs&#xff09;模块详解与代码示例 Node.js 的 fs 模块提供了与文件系统交互的能力&#xff0c;包括文件的读写、目录的管理等。以下是 fs 模块中一些常用方法的详细解释和代码示例&#xff1a; 1. 异步读取文件内容 作用&#xff1a;异步读…...

TinyXML2的一些用法

TinyXML2 原始字符串字面量 TinyXML21. XML文档操作1.1 LoadFile(const char* filename)1.2SaveFile(const char* filename)1.3RootElement()1.4Parse(const char* xml) 2.元素操作2.1 FirstChildElement(const char* name nullptr)2.2 NextSiblingElement(const char* name …...

【Vue3】从零开始创建一个VUE项目

【Vue3】从零开始创建一个VUE项目 手动创建VUE项目附录 package.json文件报错处理: Failed to get response from https://registry.npmjs.org/vue-cli-version-marker 相关链接&#xff1a; 【VUE3】【Naive UI】&#xff1c;NCard&#xff1e; 标签 【VUE3】【Naive UI】&…...

springboot370高校宣讲会管理系统(论文+源码)_kaic

毕 业 设 计&#xff08;论 文&#xff09; 高校宣讲会管理系统设计与实现 摘 要 传统办法管理信息首先需要花费的时间比较多&#xff0c;其次数据出错率比较高&#xff0c;而且对错误的数据进行更改也比较困难&#xff0c;最后&#xff0c;检索数据费事费力。因此&#xff0c…...

navicat连接mysql 8.0以上版本2059错误

安装了最新版本8.0.4的mysql之后&#xff0c;使用navicat链接提示以下错误。原因是因为mysql8.0 之前的版本中加密规则是 mysql_native_password&#xff0c;而 mysql8.0 之后的版本加密规则是caching_sha2_password 处理方案 解决方案1&#xff1a;下载安装最新版本navicat…...

SQL优化与性能——C++与SQL性能优化

在开发高效的数据库应用程序时&#xff0c;性能优化至关重要&#xff0c;尤其是当系统规模逐渐扩大或并发请求增加时。数据库操作往往是应用程序性能的瓶颈所在&#xff0c;因此&#xff0c;在 C 应用程序中合理优化数据库操作&#xff0c;管理数据库连接池、使用批量插入与更新…...

AI高中数学教学视频生成技术:利用通义千问、MathGPT、视频多模态大模型,语音大模型,将4个模型融合 ,生成高中数学教学视频,并给出实施方案。

大家好&#xff0c;我是微学AI&#xff0c;今天给大家介绍一下AI高中数学教学视频生成技术&#xff1a;利用通义千问、MathGPT、视频多模态大模型&#xff0c;语音大模型&#xff0c;将4个模型融合 &#xff0c;生成高中数学教学视频&#xff0c;并给出实施方案。本文利用专家模…...

vscode远程连接ssh

一. 使用vscode里的ssh查件连不上远程的解决方法 删除Windows上的known_host文件&#xff0c;该文件会在连接之后自动生成&#xff0c;用于验证远程服务器的身份。 konwn_host和id_rsa&#xff0c;id_rsa.pub的关系 &#xff08;1&#xff09;konwn_host用于客户端验证远程服务…...

学习ASP.NET Core的身份认证(基于Session的身份认证2)

基于Session的身份认证通过后&#xff0c;后续访问控制器的函数时该如何控制访问权限&#xff1f;虽然可以按上篇文章方式在需要做控制的函数开头检查Session的用户标识&#xff0c;可以写个全局通用检查类供所需函数调用&#xff0c;但还是有更简便的方法&#xff0c;本文学习…...

深度学习基本单元结构与输入输出维度解析

深度学习基本单元结构与输入输出维度解析 在深度学习领域&#xff0c;模型的设计和结构是理解其性能和应用的关键。本文将介绍深度学习中的基本单元结构&#xff0c;包括卷积神经网络&#xff08;CNN&#xff09;、反卷积&#xff08;转置卷积&#xff09;、循环神经网络&…...