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

【博客687】k8s informer的list-watch机制剖析

k8s informer的list-watch机制剖析

1、list-watch场景:

client-go中的reflector模块首先会list apiserver获取某个资源的全量信息,然后根据list到的rv来watch资源的增量信息。希望使用client-go编写的控制器组件在与apiserver发生连接异常时,尽量的re-watch资源而不是re-list

2、list-watch主要做的三件事:

informer的list-watch逻辑主要做三个事情:

  • 1、List部分逻辑:设置分页参数;执行list方法;将list结果同步进DeltaFIFO队列中,其实是调用store中的Replace方法。

  • 2、定时同步:定时同步以协程的方式运行,使用定时器实现定期同步,Store中的Resync操作。

  • 3、Watch部分逻辑:在for循环里;执行watch函数获取resultchan;监听resultchan中数据并处理;

过程细节剖析:

  • 1、第一次list资源会设置资源版本号为空,旧版会设为0,拉完后就更新资源版本,后面watch的时候只要关心比这个资源版本大的资源。list的时候会把ListWatch对象包裹在pager对象里 ,这个对象的作用是控制分页查询,比如资源对象太多时,为了防止过大的网络IO,pager可以通过控制url的limit和continue参数来指定一次请求获取的资源数量。

  • 2、watch的时候会开启一个死循环,ListerWatcher会返回要一个watch对象及其内部的一条channel,没有数据时则一直阻塞监听channel,只要有新资源变化就会停止阻塞,然后就根据事件类型往DeltaFIFO里面更新数据,最后会更新最新资源版本。

  • 3、每次向apiserver发起watch请求,如果大概8分钟内都没有任何事件,则apiserver会主动断开连接,断开连接则会关闭watch对象的channel ,Reflector监听channel结束,然后会再次构建watch对象并发起watch请求。

  • 4、ListAndWatch()会被Run()调用。Run()里面把ListAndWatch()包裹在了一个重试函数wait.Until()里面,ListAndWatch()正常情况下是死循环,一旦ListAndWatch()发送错误就会返回,wait.Until()在指定时间后又会重新执行ListAndWatch() 。这一步也叫所谓的ReList。再一次list资源时会尝试传入一个上次list到或最新watch到的资源版本,但并不保证可以成功list,比如watch到的Pod的资源版本和PodList的资源版本没有任何关联,Pod的更新不代表PodList的更新,这里只是尝试一下而已,如果list失败了就把url参数resourceVersion置为空,这样就能拉最新的列表。

概括:

  • 通过list机制来获取全量资源,然后使用那个resourceversion并通过watch模式来增量更新,后续每次watch到新的变化后除了更新cache,还会更新resourceversion,并用新的resourceversion去watch

3、list-watch源码剖析:

// ListAndWatch 函数首先列出所有的对象,并在调用的时候获得资源版本,然后使用该资源版本来进行 watch 操作。
// 如果 ListAndWatch 没有初始化 watch 成功就会返回错误。
func (r *Reflector) ListAndWatch(stopCh <-chan struct{}) error {klog.V(3).Infof("Listing and watching %v from %s", r.expectedTypeName, r.name)var resourceVersion stringoptions := metav1.ListOptions{ResourceVersion: r.relistResourceVersion()}// 1.List部分逻辑:设置分页参数;执行list方法;将list结果同步进DeltaFIFO队列中;if err := func() error {initTrace := trace.New("Reflector ListAndWatch", trace.Field{"name", r.name})defer initTrace.LogIfLong(10 * time.Second)var list runtime.Objectvar paginatedResult boolvar err errorlistCh := make(chan struct{}, 1)panicCh := make(chan interface{}, 1)go func() {defer func() {if r := recover(); r != nil {panicCh <- r}}()// Attempt to gather list in chunks, if supported by listerWatcher, if not, the first// list request will return the full response.// 如果listerWatcher支持,则尝试以块的形式收集列表,如果不支持,则收集第一个列表请求将返回完整响应pager := pager.New(pager.SimplePageFunc(func(opts metav1.ListOptions) (runtime.Object, error) {return r.listerWatcher.List(opts)}))switch {case r.WatchListPageSize != 0:pager.PageSize = r.WatchListPageSizecase r.paginatedResult:// We got a paginated result initially. Assume this resource and server honor// paging requests (i.e. watch cache is probably disabled) and leave the default// pager size set.case options.ResourceVersion != "" && options.ResourceVersion != "0":// User didn't explicitly request pagination.//// With ResourceVersion != "", we have a possibility to list from watch cache,// but we do that (for ResourceVersion != "0") only if Limit is unset.// To avoid thundering herd on etcd (e.g. on master upgrades), we explicitly// switch off pagination to force listing from watch cache (if enabled).// With the existing semantic of RV (result is at least as fresh as provided RV),// this is correct and doesn't lead to going back in time.//// We also don't turn off pagination for ResourceVersion="0", since watch cache// is ignoring Limit in that case anyway, and if watch cache is not enabled// we don't introduce regression.pager.PageSize = 0}// 如果过期或者不合法 resourceversion 则进行重试list, paginatedResult, err = pager.List(context.Background(), options)if isExpiredError(err) || isTooLargeResourceVersionError(err) {r.setIsLastSyncResourceVersionUnavailable(true)// Retry immediately if the resource version used to list is unavailable.// The pager already falls back to full list if paginated list calls fail due to an "Expired" error on// continuation pages, but the pager might not be enabled, the full list might fail because the// resource version it is listing at is expired or the cache may not yet be synced to the provided// resource version. So we need to fallback to resourceVersion="" in all to recover and ensure// the reflector makes forward progress.list, paginatedResult, err = pager.List(context.Background(), metav1.ListOptions{ResourceVersion: r.relistResourceVersion()})}close(listCh)}()select {case <-stopCh:return nilcase r := <-panicCh:panic(r)case <-listCh:}if err != nil {return fmt.Errorf("failed to list %v: %v", r.expectedTypeName, err)}// We check if the list was paginated and if so set the paginatedResult based on that.// However, we want to do that only for the initial list (which is the only case// when we set ResourceVersion="0"). The reasoning behind it is that later, in some// situations we may force listing directly from etcd (by setting ResourceVersion="")// which will return paginated result, even if watch cache is enabled. However, in// that case, we still want to prefer sending requests to watch cache if possible.//// Paginated result returned for request with ResourceVersion="0" mean that watch// cache is disabled and there are a lot of objects of a given type. In such case,// there is no need to prefer listing from watch cache.if options.ResourceVersion == "0" && paginatedResult {r.paginatedResult = true}r.setIsLastSyncResourceVersionUnavailable(false) // list was successfulinitTrace.Step("Objects listed")// listMetaInterface, err := meta.ListAccessor(list)if err != nil {return fmt.Errorf("unable to understand list result %#v: %v", list, err)}// 获取资源版本号resourceVersion = listMetaInterface.GetResourceVersion()initTrace.Step("Resource version extracted")// 将资源对象转换为资源列表,讲runtime.Object 对象转换为[]runtime.Object对象items, err := meta.ExtractList(list)if err != nil {return fmt.Errorf("unable to understand list result %#v (%v)", list, err)}initTrace.Step("Objects extracted")// 将资源对象列表中的资源和版本号存储在store中if err := r.syncWith(items, resourceVersion); err != nil {return fmt.Errorf("unable to sync list result: %v", err)}initTrace.Step("SyncWith done")// 更新resourceVersion r.setLastSyncResourceVersion(resourceVersion)initTrace.Step("Resource version updated")return nil}(); err != nil {return err}// 2.定时同步:定时同步以协程的方式运行,使用定时器实现定期同步resyncerrc := make(chan error, 1)cancelCh := make(chan struct{})defer close(cancelCh)go func() {resyncCh, cleanup := r.resyncChan()defer func() {cleanup() // Call the last one written into cleanup}()for {select {case <-resyncCh:case <-stopCh:returncase <-cancelCh:return}// 如果ShouldResync 为nil或者调用返回true,则执行Store中的Resync操作if r.ShouldResync == nil || r.ShouldResync() {klog.V(4).Infof("%s: forcing resync", r.name)// 将indexer的数据和deltafifo进行同步if err := r.store.Resync(); err != nil {resyncerrc <- errreturn}}cleanup()resyncCh, cleanup = r.resyncChan()}}()// 3.在for循环里;执行watch函数获取resultchan;监听resultchan中数据并处理;for {// give the stopCh a chance to stop the loop, even in case of continue statements further down on errorsselect {case <-stopCh:return nildefault:}timeoutSeconds := int64(minWatchTimeout.Seconds() * (rand.Float64() + 1.0))options = metav1.ListOptions{ResourceVersion: resourceVersion,// We want to avoid situations of hanging watchers. Stop any wachers that do not// receive any events within the timeout window.TimeoutSeconds: &timeoutSeconds,// To reduce load on kube-apiserver on watch restarts, you may enable watch bookmarks.// Reflector doesn't assume bookmarks are returned at all (if the server do not support// watch bookmarks, it will ignore this field).AllowWatchBookmarks: true,}// start the clock before sending the request, since some proxies won't flush headers until after the first watch event is sentstart := r.clock.Now()w, err := r.listerWatcher.Watch(options)if err != nil {// If this is "connection refused" error, it means that most likely apiserver is not responsive.// It doesn't make sense to re-list all objects because most likely we will be able to restart// watch where we ended.// If that's the case begin exponentially backing off and resend watch request.//  如果这是“连接被拒绝”错误,则意味着 apiserver 很可能没有响应。// 重新列出所有对象是没有意义的,因为我们很可能能够重新启动// 看我们结束的地方。// 如果是这种情况,开始指数级后退并重新发送监视请求if utilnet.IsConnectionRefused(err) {<-r.initConnBackoffManager.Backoff().C()continue}return err}if err := r.watchHandler(start, w, &resourceVersion, resyncerrc, stopCh); err != nil {if err != errorStopRequested {switch {case isExpiredError(err):// Don't set LastSyncResourceVersionUnavailable - LIST call with ResourceVersion=RV already// has a semantic that it returns data at least as fresh as provided RV.// So first try to LIST with setting RV to resource version of last observed object.klog.V(4).Infof("%s: watch of %v closed with: %v", r.name, r.expectedTypeName, err)default:klog.Warningf("%s: watch of %v ended with: %v", r.name, r.expectedTypeName, err)}}return nil}}
}4.4 LastSyncResourceVersion:获取上一次同步的资源版本func (r *Reflector) LastSyncResourceVersion() string {r.lastSyncResourceVersionMutex.RLock()defer r.lastSyncResourceVersionMutex.RUnlock()return r.lastSyncResourceVersion
}4.5 resyncChan:返回一个定时通道和清理函数,清理函数就是停止计时器。这边的定时重新同步是使用定时器实现的。func (r *Reflector) resyncChan() (<-chan time.Time, func() bool) {if r.resyncPeriod == 0 {return neverExitWatch, func() bool { return false }}// The cleanup function is required: imagine the scenario where watches// always fail so we end up listing frequently. Then, if we don't// manually stop the timer, we could end up with many timers active// concurrently.t := r.clock.NewTimer(r.resyncPeriod)return t.C(), t.Stop
}
4.6 syncWith:将从apiserver list的资源对象结果同步进DeltaFIFO队列中,调用队列的Replace方法实现。func (r *Reflector) syncWith(items []runtime.Object, resourceVersion string) error {found := make([]interface{}, 0, len(items))for _, item := range items {found = append(found, item)}return r.store.Replace(found, resourceVersion)
}4.7 watchHandler:watch的处理:接收watch的接口作为参数,watch接口对外方法是Stop和Resultchan,前者关闭结果通道,后者获取通道。func (r *Reflector) watchHandler(start time.Time, w watch.Interface, resourceVersion *string, errc chan error, stopCh <-chan struct{}) error {eventCount := 0// Stopping the watcher should be idempotent and if we return from this function there's no way// we're coming back in with the same watch interface.defer w.Stop()loop:for {select {case <-stopCh:return errorStopRequestedcase err := <-errc:return errcase event, ok := <-w.ResultChan():if !ok {break loop}if event.Type == watch.Error {return apierrors.FromObject(event.Object)}if r.expectedType != nil {if e, a := r.expectedType, reflect.TypeOf(event.Object); e != a {utilruntime.HandleError(fmt.Errorf("%s: expected type %v, but watch event object had type %v", r.name, e, a))continue}}// 判断期待的类型和监听到的事件类型是否一致if r.expectedGVK != nil {if e, a := *r.expectedGVK, event.Object.GetObjectKind().GroupVersionKind(); e != a {utilruntime.HandleError(fmt.Errorf("%s: expected gvk %v, but watch event object had gvk %v", r.name, e, a))continue}}// 获取事件对象meta, err := meta.Accessor(event.Object)if err != nil {utilruntime.HandleError(fmt.Errorf("%s: unable to understand watch event %#v", r.name, event))continue}newResourceVersion := meta.GetResourceVersion()// 对事件类型进行判断,并进行对应操作switch event.Type {case watch.Added:err := r.store.Add(event.Object)if err != nil {utilruntime.HandleError(fmt.Errorf("%s: unable to add watch event object (%#v) to store: %v", r.name, event.Object, err))}case watch.Modified:err := r.store.Update(event.Object)if err != nil {utilruntime.HandleError(fmt.Errorf("%s: unable to update watch event object (%#v) to store: %v", r.name, event.Object, err))}case watch.Deleted:// TODO: Will any consumers need access to the "last known// state", which is passed in event.Object? If so, may need// to change this.err := r.store.Delete(event.Object)if err != nil {utilruntime.HandleError(fmt.Errorf("%s: unable to delete watch event object (%#v) from store: %v", r.name, event.Object, err))}case watch.Bookmark:// 表示监听已在此处同步,只需更新// A `Bookmark` means watch has synced here, just update the resourceVersiondefault:utilruntime.HandleError(fmt.Errorf("%s: unable to understand watch event %#v", r.name, event))}*resourceVersion = newResourceVersion// 更新 resource version 版本, 下次使用该 resourceVersion 来 watch 监听.r.setLastSyncResourceVersion(newResourceVersion)if rvu, ok := r.store.(ResourceVersionUpdater); ok {rvu.UpdateResourceVersion(newResourceVersion)}eventCount++}}watchDuration := r.clock.Since(start)// 如果 watch 退出小于 一秒, 另外一条事件也没拿到, 则打条错误日志if watchDuration < 1*time.Second && eventCount == 0 {return fmt.Errorf("very short watch: %s: Unexpected watch close - watch lasted less than a second and no items received", r.name)}klog.V(4).Infof("%s: Watch close - %v total %v items received", r.name, r.expectedTypeName, eventCount)return nil
}4.8 relistResourceVersion:relistResourceVersion 函数获得反射器 relist 的资源版本,如果资源版本非 0,
则表示根据资源版本号继续获取,当传输过程中遇到网络故障或者其他原因导致中断,下次再连接时,会根据资源版本号继续传输未完成的部分。
可以使本地缓存中的数据与Etcd集群中的数据保持一致,该函数实现如下所示:// 如果最后一次relist的结果是HTTP 410(Gone)状态码,则返回"",这样relist将通过quorum读取etcd中可用的最新资源版本。
// 返回使用 lastSyncResourceVersion,这样反射器就不会使用在relist结果或watch事件中watch到的资源版本更老的资源版本进行relist了
// 当 r.lastSyncResourceVersion 为 "" 时这里为 "0",当使用 r.lastSyncResourceVersion 失败时这里为 ""
// 区别是 "" 会直接请求到 etcd,获取一个最新的版本,而 "0" 访问的是 cache
// 第一次使用0,出错了使用"",否则用lastSyncResourceVersion
// 注意:第一次不会直接全量list etcd,是全量list apiserver
func (r *Reflector) relistResourceVersion() string {r.lastSyncResourceVersionMutex.RLock()defer r.lastSyncResourceVersionMutex.RUnlock()if r.isLastSyncResourceVersionUnavailable {// 因为反射器会进行分页List请求,如果 lastSyncResourceVersion 过期了,所有的分页列表请求就都会跳过 watch 缓存// 所以设置 ResourceVersion="",然后再次 List,重新建立反射器到最新的可用资源版本,从 etcd 中读取,保持一致性。return ""}if r.lastSyncResourceVersion == "" {// 反射器执行的初始 List 操作的时候使用0作为资源版本。return "0"}return r.lastSyncResourceVersion
}4.9 setLastSyncResourceVersion:用于存储已被Reflector处理的最新资源对象的ResourceVersion,r.setLastSyncResourceVersion方法用于更新该值。
lastSyncResourceVersion属性为Reflector struct的一个属性,
func (r *Reflector) setLastSyncResourceVersion(v string) {r.lastSyncResourceVersionMutex.Lock()defer r.lastSyncResourceVersionMutex.Unlock()r.lastSyncResourceVersion = v
}// setIsLastSyncResourceVersionUnavailable 设置是否返回具有lastSyncResourceVersion 的最后一个列表或监视请求“过期”或“资源版本太大”错误。
func (r *Reflector) setIsLastSyncResourceVersionUnavailable(isUnavailable bool) {r.lastSyncResourceVersionMutex.Lock()defer r.lastSyncResourceVersionMutex.Unlock()r.isLastSyncResourceVersionUnavailable = isUnavailable
}

相关文章:

【博客687】k8s informer的list-watch机制剖析

k8s informer的list-watch机制剖析 1、list-watch场景&#xff1a; client-go中的reflector模块首先会list apiserver获取某个资源的全量信息&#xff0c;然后根据list到的rv来watch资源的增量信息。希望使用client-go编写的控制器组件在与apiserver发生连接异常时&#xff0c…...

用Python获取链家二手房房源数据,做可视化图分析数据

前言 数据采集的步骤是固定: 发送请求, 模拟浏览器对于url地址发送请求获取数据, 获取网页数据内容 --> 请求那个链接地址, 返回服务器响应数据解析数据, 提取我们需要的数据内容保存数据, 保存本地文件 所需模块 win R 输入cmd 输入安装命令 pip install 模块名 (如果你…...

Yield Guild Games:社区更新 — 2023 年第二季度

本文重点介绍了 Yield Guild Games (YGG) 2023 年第二季度社区更新中涵盖的关键主题&#xff0c;包括公会发展计划 (GAP) 第 3 季的总结、YGG 领导团队的新成员以及 YGG 的最新消息地区公会网络和广泛的游戏合作伙伴生态系统。 在 YGG 品牌焕然一新的基础上&#xff0c;第二季…...

Stable Diffusion - 运动服 (Gymwear Leggings) 风格服装与背景的 LoRA 配置

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://spike.blog.csdn.net/article/details/132179050 测试模型&#xff1a;DreamShaper 8 运动裤 (Gymwear Leggings) 是紧身的裤子&#xff0c;通常用于健身、瑜伽、跑步等运动。运动裤的…...

js-7:javascript原型、原型链及其特点

1、原型 JavaScript常被描述为一种基于原型的语言-每个对象拥有一个原型对象。 当试图访问一个对象的属性时&#xff0c;它不仅仅在该对象上搜寻&#xff0c;还会搜寻该对象的原型&#xff0c;以及该对象的原型的原型&#xff0c;依次层层向上搜索&#xff0c;直到找到一个名字…...

无涯教程-Perl - continue 语句函数

可以在 while 和 foreach 循环中使用continue语句。 continue - 语法 带有 while 循环的 continue 语句的语法如下- while(condition) {statement(s); } continue {statement(s); } 具有 foreach 循环的 continue 语句的语法如下- foreach $a (listA) {statement(s); } co…...

【贪心算法】leetcode刷题

贪心算法无固定套路。 核心思想&#xff1a;先找局部最优&#xff0c;再扩展到全局最优。 455.分发饼干 两种思路&#xff1a; 1、从大到小。局部最优就是大饼干喂给胃口大的&#xff0c;充分利用饼干尺寸喂饱一个&#xff0c;全局最优就是喂饱尽可能多的小孩。先遍历的胃口&a…...

PyMySQL库版本引起的python执行sql编码错误

前言 长话短说&#xff0c;之前在A主机&#xff08;centos7.9&#xff09;上运行的py脚本拿到B主机上&#xff08;centos7.9&#xff09;运行报错&#xff1a; UnicodeEncodeError: latin-1 codec cant encode characters in position 265-266: ordinal not in range(256)两个…...

第二章-算法

第二章-算法 数据结构和算法的关系 算法是解决特定问题求解步骤的描述&#xff0c;在计算机中表现为指令的有限序列&#xff0c;并且每条指令表示一个或多个操作。 算法的特性 算法有五个基本特征&#xff1a;输入、输出、有穷性、确定性和可行性。 输入&#xff1a;算法具…...

‘vue’不是内部或外部命令,也不是可运行的程序或批处理文件的原因及解决方法

今天我在用node.js的时候&#xff0c;结果出现如下错误&#xff1a; C:\Users\xiesj> vue -v vue不是内部或外部命令&#xff0c;也不是可运行的程序或批处理文件。 原因&#xff1a; 1、确定npm是否已正确安装&#xff1f; 2、确定vue以及vue-cli已正确安装&#xff1f;…...

HBase API

我们之后的实际开发中不可能在服务器那边直接使用shell命令一直敲的&#xff0c;一般都是通过API进行操作的。 环境准备 新建Maven项目&#xff0c;导入Maven依赖 <dependencies><dependency><groupId>org.apache.hbase</groupId><artifactId>…...

Qt6之QListWidget——Qt仿ToDesk侧边栏(1)

一、 QLitWidget概述 注意&#xff1a;本文不是简单翻译Qt文档或者接口函数&#xff0c;而侧重于无代码Qt设计器下演示使用。 QListWidget也称列表框类&#xff0c;它提供了一个类似于QListView提供的列表视图&#xff0c;但是它具有一个用于添加和删除项的经典的基于项的接口…...

Prometheus技术文档--基本安装-docker安装并挂载数据卷-《十分钟搭建》

一、查看可安装的版本 docker search prom/prometheus 二、拉取镜像 docker pull prom/prometheus 三、查看镜像 docker images 四、书写配置文件-以及创建挂载目录 宿主机挂载目录位置&#xff1a; 以及准备对应的挂载目录&#xff1a; /usr/local/docker/promethues/se…...

Android 数据库之GreenDAO

GreenDAO 是一款开源的面向 Android 的轻便、快捷的 ORM 框架&#xff0c;将 Java 对象映射到 SQLite 数据库中&#xff0c;我们操作数据库的时候&#xff0c;不再需要编写复杂的 SQL语句&#xff0c; 在性能方面&#xff0c;greenDAO 针对 Android 进行了高度优化&#xff0c;…...

kotlin 编写一个简单的天气预报app(六)使用recyclerView显示forecast内容

要使用RecyclerView显示天气预报的内容 先在grandle里添加recyclerView的引用 implementation androidx.recyclerview:recyclerview:1.3.1创建一个RecyclerView控件&#xff1a;在布局文件中&#xff0c;添加一个RecyclerView控件&#xff0c;用于显示天气预报的列表。 这是一…...

jpa Page 1 of 0 containing UNKNOWN instances错误关于like问题的解决记录

导致这个问题的原因很多&#xff0c;这里记录一下我碰到的问题和解决方法。 网上有说时 pageNo要从0开始&#xff0c;我的不是这个问题。 在使用springboot jpa时&#xff0c;发现使用 t.ip like %?5% 语句&#xff0c;如果数据库记录的ip is null时&#xff0c;将查询不到该…...

Python实战之使用Python进行数据挖掘详解

一、Python数据挖掘 1.1 数据挖掘是什么&#xff1f; 数据挖掘是从大量的、不完全的、有噪声的、模糊的、随机的实际应用数据中&#xff0c;通过算法&#xff0c;找出其中的规律、知识、信息的过程。Python作为一门广泛应用的编程语言&#xff0c;拥有丰富的数据挖掘库&#…...

scala 加载properties文件

利用java.util.Properties加载 import java.io.FileInputStream import java.util.Properties object LoadParameter {//动态获取properties文件可配置参数val props new Properties()def getParameter(s:String,filePath:String): String {props.load(new FileInputStream(f…...

备战秋招012(20230808)

文章目录 前言一、今天学习了什么&#xff1f;二、动态规划1.概念2.题目 总结 前言 提示&#xff1a;这里为每天自己的学习内容心情总结&#xff1b; Learn By Doing&#xff0c;Now or Never&#xff0c;Writing is organized thinking. 提示&#xff1a;以下是本篇文章正文…...

QT中定时器的使用

文章目录 概述步骤 概述 Qt中使用定时器大致有两种&#xff0c;本篇暂时仅描述使用QTimer实现定时器 步骤 // 1.创建定时器对象 QTimer *timer new QTimer(this);// 2.开启一个定时器&#xff0c;5秒触发一次 timer->start(5000); // 3.建立信号槽连接&am…...

【UE4】多人联机教程(重点笔记)

效果 1. 创建房间、搜索房间功能 2. 根据指定IP和端口加入游戏 步骤 1. 新建一个第三人称角色模板工程 2. 创建一个空白关卡&#xff0c;这里命名为“InitMap” 3. 新建一个控件蓝图&#xff0c;这里命名为“UMG_ConnectMenu” 在关卡蓝图中显示该控件蓝图 打开“UMG_Connec…...

【go】GIN参数重复绑定报错EOF问题

文章目录 1 问题描述2 解决&#xff1a;替换为ShouldBindBodyWith 1 问题描述 在 Gin 框架中&#xff0c;当多次调用 ShouldBind() 或 ShouldBindJSON() 方法时&#xff0c;会导致请求体的数据流被读取多次&#xff0c;从而出现 “EOF” 错误。 例如在api层绑定了参数&#x…...

关于MySQL中的binlog

介绍 undo log 和 redo log是由Inno DB存储引擎生成的。 在MySQL服务器架构中&#xff0c;分为三层&#xff1a;连接层、服务层&#xff08;server层&#xff09;、执行层&#xff08;存储引擎层&#xff09; bin log 是 binary log的缩写&#xff0c;即二进制日志。 MySQL…...

我维护电脑的方法

无论是学习还是工作&#xff0c;电脑都是IT人必不可少的重要武器&#xff0c;一台好电脑除了自身配置要经得起考验&#xff0c;后期主人对它的维护也是决定它寿命的重要因素&#xff01; 你日常是怎么维护你的“战友”的呢&#xff0c;维护电脑运行你有什么好的建议吗&#xff…...

AP51656 电流采样降压恒流驱动IC RGB PWM深度调光 LED电源驱动

产品描述 AP51656是一款连续电感电流导通模式的降压恒流源&#xff0c;用于驱动一颗或多颗串联LED 输入电压范围从 5 V 到 60V&#xff0c;输出电流 可达 1.5A 。根据不同的输入电压和 外部器件&#xff0c; 可以驱动高达数十瓦的 LED。 内置功率开关&#xff0c;采用电流采样…...

Python爬虫的解析(学习于b站尚硅谷)

目录 一、xpath  1.xpath插件的安装  2. xpath的基本使用  &#xff08;1&#xff09;xpath的使用方法与基本语法&#xff08;路径查询、谓词查询、内容查询&#xff08;使用text查看标签内容&#xff09;、属性查询、模糊查询、逻辑运算&#xff09;  &#xff08;2&a…...

python的virtualenv虚拟环境无法激活activate

目录 问题描述&#xff1a; 解决办法&#xff1a; 解决结果&#xff1a; 问题描述&#xff1a; PS D:\pythonProject\pythonProject\DisplayToolLibs\venv\Scripts> .\activate .\activate : 无法加载文件 D:\pythonProject\pythonProject\DisplayToolLibs\venv\Scripts\…...

uniapp中token操作:存储、获取、失效处理。

实现代码 存储token:uni.setStorageSync(token, res.data.result);获取token:uni.getStorageSync(token);清除token&#xff1a;uni.setStorageSync(token, ); 应用场景 在登录操作中&#xff0c;保存token pwdLogin() {....this.$axios.request({url: .....,method: post,p…...

乐鑫科技 2022 笔试面试题

岗位:嵌入式软件实习生。 个人情况:本科双非电子信息工程,硕士华五软件工程研一在读;本科做过一些很水的项目 ,也拿项目搞了一些奖,相对来说嵌入式方向比较对口。 时间线及面试流程 2021.04.02 笔试 题目分为选择题和编程题,选择题二十题,编程题两题; 选择题基本…...

实现UDP可靠性传输

文章目录 1、TCP协议介绍1.1、ARQ协议1.2、停等式1.3、回退n帧1.4、选择性重传 1、TCP协议介绍 TCP协议是基于IP协议&#xff0c;面向连接&#xff0c;可靠基于字节流的传输层协议 1、基于IP协议&#xff1a;TCP协议是基于IP协议之上传输的&#xff0c;TCP协议报文中的源端口IP…...

海南房地产网站建设/aso应用商店优化原因

Win7IE主页被锁定怎么解除&#xff1f;如果某个程序或者代码篡改了IE浏览器主页&#xff0c;我们重新修改该主页就可以了&#xff0c;但如果某程序得到一些权限&#xff0c;直接修改组策略&#xff0c;并且让IE主页设置变成灰色~想来砸电脑的心思都有了&#xff0c;这时候我们该…...

wordpress系统介绍/深圳知名网络优化公司

在当今竞争激烈的市场环境下&#xff0c;企业想要获得更多的市场份额&#xff0c;拓展更多的客户&#xff0c;必须要进行拓客工作。而在拓客过程中&#xff0c;采集工具是必不可少的工具之一。采集工具可以帮助企业快速获取目标客户的信息&#xff0c;并进行有效的沟通和跟进&a…...

网站备案要如何取消/南京seo收费

一、模板概述定制数据结构模板&#xff0c;这当然比直接分析16进制的原始数据要方便得多&#xff0c;而且不容易出错。你编辑好数据结构模板保存后&#xff0c;数据模板就生效了。这样你就可以分析来自硬盘、内存等一些数据&#xff0c;这些数据将套用你数据结构模板来显示数据…...

企业形象网站开发/店铺推广怎么做

转自&#xff1a;http://blog.csdn.net/zephyr_be_brave/article/details/8944967 一. 分页存储管理 1.基本思想 用户程序的地址空间被划分成若干固定大小的区域&#xff0c;称为“页”&#xff0c;相应地&#xff0c;内存空间分成若干个物理块&#xff0c;页和块的大小相等。…...

淄博网站制作哪家好/crm管理系统

Redis深度历险分为两个部分&#xff0c;单机Redis和分布式Redis。本文为分布式Redis深度历险系列的第一篇&#xff0c;主要内容为Redis的复制功能。Redis的复制功能的作用和大多数分布式存储系统一样&#xff0c;就是为了支持主从设计&#xff0c;主从设计的好处有以下几点&…...

做盗版网站/中山百度推广公司

关于拼图和逆序数的关系可以看看这个 http://www.guokr.com/question/579400/ 然后求逆序数在判断就行了 按题意生成原始排列&#xff0c;观察发现&#xff0c;每一轮数后方比该数小的数的数量&#xff08;即对逆序对数的贡献&#xff09;呈等差数列形式&#xff0c;公差p-1&am…...