从零到破万节点!支撑618大促背后的蚂蚁金服Kubernetes集群

2019年天猫618大促,蚂蚁金服首次在大促中对调度系统和技术栈全面应用Kubernetes,突破了Kubernetes单集群万节点的规模,总节点数达到数十万个,这是世界最大规模的 Kubernetes 集群之一,而这距离开发团队下载Kubernetes代码仅一年之久。

背景

去年6月份,蚂蚁金服的 Kubernetes开发团队刚刚下载Kubernetes代码,从零开始尝试在内部落地Kubernetes集群,并推动云原生实践。2019年天猫618大促,蚂蚁金服首次在调度系统和技术栈全量应用Kubernetes,平稳度过大促并突破 Kubernetes 单集群万节点规模,机房和集群数量达到数十个,总节点达到数十万个,这是世界最大规模的 Kubernetes 集群之一

蚂蚁金服的 Kubernetes开发团队是一个仅有十几人组成的小队团,他们用短短一年时间,通过扩展 Kubernetes 的方式,将蚂蚁金服老的调度系统的功能对齐,还将一系列令人兴奋的Kubernetes 功能带回并落地到蚂蚁金服。开发团队能在如此短时间内取得如此成绩,所依靠的正是云原生技术:开发团队使用云原生技术让开发和迭代敏捷化,同时让一切过程自动化。开发团队在落地 Kubernetes 过程中,就已经尝试将云原生的理念实践并执行,取得的优秀成果将为整个蚂蚁金服后续的全面云原生化提供优秀范本。

本文将分享蚂蚁金服的 Kubernetes开发团队如何使用云原生的技术去推进 Kubernetes 在蚂蚁金服的大规模落地,同时也会分享一些对于大规模 Kubernetes集群性能优化的经验。

将Kubernetes运行在Kubernetes上

云原生的核心理念是让应用无差别运行在任何一朵云上,即将应用变成云的 “原住民”。而蚂蚁金服的 Kubernetes 开发团队在项目开始时需要思考的是如何将 Kubernetes 云原生化的运行在各个机房,并在没有任何基础设施的云机房也能无差别运行Kubernetes

首先,新建一个Kubernetes是一项繁琐的事情:初始化一堆证书,包括安全的存储证书;有序拉起二十几个组件,同时组织好彼此之间的引用关系,保证后续能够方便地升级;最后还要做自动化故障恢复。

一旦拥有Kubernetes,如果某个应用提出应用发布、自动化运维等需求,可以很简单的完成。但是,如何才能让Kubernetes本身也享受到 Kubernetes 带来的强大功能呢?

那么,这句话应该如何理解呢?在蚂蚁金服启动落地 Kubernetes 时,就已经预见内部对“自动化运维和交付”的巨大依赖。蚂蚁金服需要管理几十个集群、几十万计算节点。同时,随着迭代的进行,扩展组件越来越多(到目前已经有三十多个)。

因此,在经过一系列讨论之后,蚂蚁金服决定将 Kubernetes运行在Kubernetes 之上,并且将组件和节点的交付过程封装成Operator。有了 Kubernetes 和 Operator 之后,想要交付一个 Kubernetes 集群给应用方就像提交Pod一样容易,并且集群的各种组件都能自动故障自愈。这可以理解为,用Operator这种云原生的方式交付整个基础设施。如果不是当初的这个决定,很难想象组件和节点在离开 Kubernetes 之后如何运维和自动化恢复。

为此,开发团队设计出了 Kube-on-Kube-Operator,它的用途是将各个机房交付给用户的 Kubernetes 集群(称为 “业务集群”) 的组件和服务也都跑在一个 Kubernetes (称为 “元集群”) 上。下图是Kube-on-Kube-Operator的核心架构图:

在拥有Kube-on-Kube-Operator 之后,蚂蚁金服能在分钟级甚至秒级创建一个 Kubernetes 集群。同时,所有业务集群 Kubernetes Master 组件都运行在元集群 Kubernetes 上,凭借 Kubernetes 的能力可以做到秒级故障恢复。

Kube-on-Kube-Operator 用云原生方式搞定了 Kubernetes Master 组件,那么 kubelet 和 Worker 节点呢?

众所周知,将一个 Worker 节点加入集群也需要一堆复杂的事情:生成随机证书,安装 Runtime 以及各种底层软件,设置 kubelet 启动参数等。对此,蚂蚁金服团队在思考能否像 Kubernetes 自动化维护 Pod 一样自动化维护 Worker 节点呢?

Node-Operator 正是在这样的背景下诞生的,其使用声明式编程、面向终态的特性去管理 Worker节点。Node-Operator 的职责涵盖从云厂商购买计算节点开始到接管 Kubenertes 节点整个生命周期:从初始化证书、kubelet 以及节点必要组件,到自动化升级组件版本,再到最后的节点下线。同时,Node-Opeator 也会订阅 NPD(Node Problem Detector) 上报的节点故障信息,进行一系列自动化修复。下图是 Node-Operator 的核心架构图:

Kube-on-Kube-Operator 和Node-Operator 在生产环境中表现稳定,经历了数十个大迭代以及无数小迭代。同时,Kube-on-Kube-Operator自动化运维了数十个生产集群,为每日自动化功能测试和回归测试快速创建和销毁临时测试集群。Node-Operator接管了几乎整个蚂蚁金服的物理机生命周期,这种思想也继承到了下一代运维管控系统。

除了 Kube-on-Kube-Operator 和Node-Operator,在 “automate everything”信念的驱动下,蚂蚁金服还开发出了各种 Operator去交付蚂蚁金服的所有基础设施和应用。

自动化流水线与GitOps

自动化和 CI/CD 是云原生非常重视的理念,蚂蚁金服开发团队将其应用到研发过程中,让研发、测试和发布的一系列过程都自动化。

上线 Kubernetes 初期,在保持飞速迭代的情况下,也需要确保代码质量,团队的规定是:任何组件都要有单独的 e2e 测试集或者测试用例,同时每天晚上都会将最新代码放入全新的沙箱集群测试,另外还会定期或者触发式在生产集群运行功能验证测试。

为了提高效率并更好完成测试,开发团队建立了自动化流水线。最开始的自动化流水线是为了服务测试,在自动化流水线建立工作集,几乎所有工作集都通过调用 Kube-on-Kube-Operator 和 Node-Operator 建立了全新的沙箱集群,然后开始运行各自测试集的测试,最后销毁集群发送测试结果。

为了在自动化流水线上更方便的建立工作集,团队将所有组件和测试集都进行镜像化,使用Kube-on-Kube-Operator 和 Kubernetes 元集群可以方便、快速地建立测试用的沙箱集群,然后使用容器和配置注入的方式让测试集(Job on Kubernetes)运行在任何环境、指定任何集群跑测试。

后来,团队在流水线加上自动化发布:流水线将期望的组件发布完成,然后自动触发功能验证测试。如果发布或者测试失败,通过钉钉机器人通知对应负责人,如果成功,几乎可以做到无人值守发布。

Kubernetes 一个令人兴奋的特性就是将各种部署资源的声明统一和标准化:如果需要一个在线无状态服务,只需向 Kubernetes提交一个 Deployment;如果需要负载均衡,只需提交一个 Service;如果需要持久化存储卷,就提交一个PVC(PersistentVolumeClaim);如果需要保存配置文件或者密码存储,只需提交ConfigMap 或者 Secret,然后在 Pod 里面引用就可以。Kube-on-Kube-Operator 就是用这些标准的资源定义Kubernetes元集群发布各个 Kubernetes 业务集群的组件。

但是,在Kube-on-Kube-Operator 的使用过程中,团队发现Kubernetes 发布还是不够透明:Kube-on-Kube-Operator 承包了发布过程,即使它是一个非常轻量的触发器,用来将业务集群部署资源文件提交到 Kubernetes 元集群,但执行一次发布也需要比较繁琐的操作。这在快速的迭代团队中,教会新同事使用 Kube-on-Kube-Operator 声明版本、修改 Cluster(代表一个集群的 CRD) 版本引用,看起来不够 “敏捷”。Kube-on-Kube-Operator 俨然变成了一个 PaaS,但业务团队为什么要花精力学习一个 “非标准 PaaS” 的使用呢?

事实上,Kubernetes 已经将一切标准化,使用Git 自带的版本管理、 PR Review 功能就可以实现部署资源文件管理系统。

因此,开发团队引入 GitOps 理念, GitOps 基于经过验证的 DevOps 技术——大体类似于 CI/CD和声明式基础设施即代码,而构建为Kubernetes应用程序提供了一套联合的、可自动生成的生命周期框架。

GitOps将原先包裹在 PaaS 或者 Kube-on-Kube-Operator 的黑盒全部公开化和民主化。每个人都能从名为 kubernetes-resources 的 Git 仓库看到 Kubernets 组件目前在线上的部署状态:版本、规格、副本数、引用的资源。每个人都能参与发布,只要提交 PR,经过事先设定的管理员 Review 和 Approve 之后,就可以自动发布到线上并开始跑测试。团队使用 kustomize 的 base/overlays 功能做到各集群的差异化部署,同时又能避免同一个组件在各个Kubernetes 集群重复编写。

开发团队还将 GitOps 能力开放给业务方,为此建立了名为 partner-resources 的 Git 仓库,任何业务应用的开发同学都能访问该仓库并提交 PR,经过 Review 合并到Master 后,自动化流水线会将部署资源生效到指定 Kubernetes 集群,从而进行业务应用的云原生实践和落地。

可能很多同学会不由自主的将透明、民主和 “不安全”挂上钩。恰恰相反,kubernetes-resources 和 partner-resources 里面没有任何一个秘钥文件,只有权限声明(RBAC)文件。权限声明需要 Review 才能合入,而身份识别使用了 Kubernetes 自动注入秘钥(ServiceAccount)功能。在ServiceMesh 普及之后,秘钥文件更加被弱化,所有身份识别都依赖 Mesh。同时,蚂蚁金服运行的 Kubernetes 集群采用了最高的安全标准,所有链路都使用 TLS 双向加密和身份认证。

在云原生时代,Kubernetes集群其实已经是最好的元数据系统。同时,Kubernetes 各种 Workload 配合工作让用户提交的部署资源一直维持在期望状态;而 GitOps 拥有的版本记录、PR Review 功能等是最好的部署资源文件管理系统。即使目前还有一些路要走,比如目前Kubernetes 缺少灰度发布等更高级功能的 Workload,但是在不久的将来肯定能看到这些特性被放入Workload。

全面云原生化

Kubernetes 是云原生的基础,蚂蚁金服在过去一年从零到全面落地 Kubernetes ,并在期望的规模下做到优秀的吞吐量。可以说,过去一年完成了云原生的基础建设。

同时,非常多其它云原生技术在Kubernetes 集群内并行探索和落地,达到生产级别,甚至支持大促,比如ServiceMesh等。简单来看,蚂蚁金服落地云原生的目的可以总结为三点:标准化交付、提高研发效率和提高资源利用率。

其中,标准化交付比较好理解,蚂蚁金服围绕 Kubernetes 建设 PaaS 和应用交付体系,使用 Kubernetes 统一的资源声明方式交付所有应用,同时让所有应用自动化注入基础服务,如ServiceMesh,统一日志,Tracing 等;提高研发效率注重提高每个应用开发者的工作效率,让其使用 Serverless、CI/CD展开日常工作,让开发者背靠云原生技术栈做更敏捷的开发和迭代;最后,使用 Kubernetes 统一资源和调度,让所有的应用、Job、GPU 调度都使用 Kubernetes 集群统一的资源池。开发团队在 Kubernetes 统一资源池内做了一系列调度优化和混布技术,让资源利用率有质的提升。

大规模集群性能优化

如果按照Kubernetes最新版本提供的能力,做到单集群万节点并不是特别困难。但是,在这种背景下,让整个集群保持较高吞吐量和性能,并在618大促时依旧对各种响应做到及时反馈是不容易的。

蚂蚁金服通过系列压测和迭代将单集群做到了上万规模。然而,在这个过程中,整个团队发现不能太迷信压测数据,压测场景其实非常片面,而生产环境和压测环境有非常大差异,主要体现在 Kubernetes 扩展组件的客户端行为和一些极端情况。

举例来说,一个是 Daemonset,蚂蚁金服内部一个集群已经拥有十个左右的 Daemonset 系统 Agent,在如此大规模集群内上线一个使用 Kubernetes 客户端的不规范Daemonset 都可能使API Server 陷入崩溃状态;另一个是 Webhook,蚂蚁金服拥有一系列Webhook Server 扩展功能,它们的响应速度都会影响到 API Server 的性能,甚至引起内存和 goruntine 泄露。从上述示例不难发现,其实要将大规模集群维持在健康状态需要全链路优化和调优,而不仅仅局限于 API Server和etcd,更不是仅仅停留在压测数据。

开发团队将集群节点规模上升到万级别的时候,发现更多的瓶颈在 Kubernetes API Server。相对来说,etcd 的表现比较稳定。团队做了系列优化和调整,来让API Server 满足性能需求。

1、优先满足和保证 API Server 计算资源需求在常规部署模式下,API Server 会和Controller Manager、Scheduler 等核心组件一起部署在一台节点上,在Kube-on-Kube 架构下也是采用这种部署模式,以达到合理使用资源的目的。在这种部署架构下,将 API Server 的资源优先级设置到最高级别,也就是在 Kubernetes 资源级别表达里的 Guaranteed 级别,并且尽可能将物理节点所有资源都占用;同时将其他组件的优先级相对降低,即将其设置成 Burstable 级别,以保证API Server的资源需求。

2、均衡 API Server 负载
在 Kubernetes 架构下,所有组件均面向APIServer展开工作,因此组件对API Server的请求链路健康非常敏感,只要API Server发生升级、重启,所有组件几乎都会在秒级内发起新的一系列List/Watch请求。如果使用滚动升级模式逐个升级 API Server 的模式去升级 API Server,那么很有可能在升级之后,绝大多数客户端请求都会打在一个API Server实例上。

如果负载不均衡,使得API Server进入 “一人工作,多人围观” 的场面,那么很可能导致 API Server 发生雪崩效应。更糟糕的情况是,因为 Kubernetes 的 client-go 代码库使用了 TLS 链路复用的特性,客户端不会随着运行时间增长,因为链接重建将负载均衡掉。

开发团队研究后发现,这个问题可以通过优化客户端将请求平衡掉来解决,当前正在着手研发,成功后也会回馈给开源社区。在这个特性还未发布之前,可以通过设置升级 API Server 的策略使用 “先扩后缩” 来缓解该问题,即先将新版本的API Server全部创建出来,然后再下线老版本的 API Server。使用 Kubernetes 的 Deployment 表达三副本的 API Server 升级策略如下:

3、开启 NodeLease Feature
对于提升 Kubernetes 集群规模来说,NodeLease 是一个非常重要的 Feature 。在没有开启 NodeLease 之前,Kubelet 会使用 Update Node Status 的方式更新节点心跳,而一次这样的心跳会向 API Server 发送大约10 KB数据量。

在大规模场景下,API Server 处理心跳请求是非常大的开销。而开启 NodeLease 之后,Kubelet 会使用非常轻量的 NodeLease 对象(0.1 KB)更新请求替换老的 Update Node Status 方式,这大大减轻了 API Server 的负担。在上线NodeLease 功能之后,集群API Server 开销的 CPU 大约降低了一半。

4、修复请求链路中丢失 Context 的场景
众所周知,Go语言标准库的HTTP请求使用 request.Context() 方法获取的 Context 来判断客户端请求是否结束。如果客户端已经退出请求,而 API Server 还在处理请求,那么就可能导致请求处理 goruntine 残留和积压。

在API Server陷入性能瓶颈时,APIServer 已经来不及处理请求,而客户端发起的重试请求,会将 API Server带入雪崩效应:处理已取消请求的 goruntine 会积压的越多,直到 API Server 陷入OOM。开发团队找出了一系列在处理请求没有使用 Context 的场景,并向上游社区提交了修复方案,包括使用 client-go 可能导致的 goruntine;Admission 和 Webhook 可能引起的 goruntine 积压。

5、优化客户端行为
目前,API Server 限流功能只限制最大读和写并发数,而没有限制特定客户端请求并发量的功能。因此,API Server 其实是比较脆弱的,一个客户端频繁的 List 数目较大资源(如 Pod, Node 等)都有可能会让 API Server 陷入性能瓶颈。开发团队强制要求所有客户端使用 Informer 去 List/Watch 资源,并且禁止在处理逻辑里面直接调用 Client 去向 API Server List 资源。而社区开始重视这方面,通过引入 Priority and Fairness 特性去更加细粒度的控制客户端的请求限制。

在后续越来越多的系统 Daemonset 上线之后,团队发现,即使做到了所有客户端使用 Informer,集群内的 List Pod 请求依旧很多。这主要是 Kubelet 和 Daemonset 带来的,可以用 Bookmark 特性来解决这个问题,在未上线 Bookmark 的集群内,可以调整 API Server 对资源的更新事件缓存量来缓解该问题 (如 --watch-cache-sizes=node#1000,pod#5000),同时调整 Daemonset Re-Watch 的时间间隔:

结束语

在蚂蚁金服云原生实践和落地的过程,开发团队认识到,项目顺利实践与开源社区的帮助密切相关。除了Kubernetes,蚂蚁金服团队还使用了其它开源项目,比如 Prometheus。Prometheus 的意义在于标准化Metrics 和其查询语句,任何应用都可以使用 Prometheus 埋点并记录 Metrics,而蚂蚁金服通过自研采集任务调度系统,以及数据持久化方案,使得Prometheus 数据不会有任何 “断点” ,同时还支持永久历史数据查询。

目前,蚂蚁金服已向 Kubernetes 社区提交了许多大规模场景下的性能提升和优化方案,上面提到在Kubernetes API Server性能优化过程中发现的问题,以及修复最新Kubernetes版本中的许多 Daemonset 的 bug ,将Daemonset的生产可用性提高一个层级。同时,蚂蚁金服也将众多技术开源给社区,包括金融级分布式框架SOFAStack。

可以说,全面实现云原生是每个开发者都需要参与的“革命”,而蚂蚁金服也将作为发起者和分享者参与其中。


原文链接
本文为云栖社区原创内容,未经允许不得转载。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/518292.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MongoDB 定位 oplog 必须全表扫描吗?

MongoDB oplog (类似于 MySQL binlog) 记录数据库的所有修改操作,除了用于主备同步;oplog 还能玩出很多花样,比如 全量备份 增量备份所有的 oplog,就能实现 MongoDB 恢复到任意时间点的功能通过 oplog&am…

JavaScript-方法

方法的定义 方法就是把函数放在对象里面 var wang {name: 网络,birth: 2020,// 方法age: function () {// 今年 - 出生的年var now_year new Date().getFullYear();return now_year-this.birth} } // 属性 wang.name // 方法,一定要带() kuangshen.age()拆开上面…

Python程序员30行代码素描表白!网友:花里胡哨

总有人说程序员不够浪漫!其实我们只是没时间而已,等我们有时间了,还有普通人什么事儿?最近就有一个小伙伴上热搜了!原来他用Python给可爱的女朋友画了一幅素描!不到30行代码,一起来学学给她一个…

解读NoSQL最新现状和趋势:云NoSQL数据库将成重要增长引擎

NoSQL最早起源于1998年,但从2009年开始,NoSQL真正开始逐渐兴起和发展。回望历史应该说NoSQL数据库的兴起,完全是十年来伴随互联网技术,大数据数据的兴起和发展,NoSQL在面临大数据场景下相对于关系型数据库运用&#xf…

使用EMR-Kafka Connect进行数据迁移

1.背景 流式处理中经常会遇到Kafka与其他系统进行数据同步或者Kafka集群间数据迁移的情景。使用EMR Kafka Connect可以方便快速的实现数据同步或者数据迁移。 Kafka Connect是一种可扩展的、可靠的,用于在Kafka和其他系统之间快速地进行流式数据传输的工具。例如可…

亚信安全发布“安全定义边界”2020发展理念 赋能企业在5G时代的数字化安全运营能力

2020年4月21日,以“信行合一 聚势致远”为主题的亚信安全2020合作伙伴大会正式在云端拉开帷幕,超过1500家生态伙伴相聚云端,在为期3天的大会上共话安全,共商发展。会上,亚信安全正式发布“安全定义边界”2020发展理念&…

JavaScript-Date日期对象

<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Title</title> </head> <body> <script>const now new Date(); // Tue Aug 10 2021 15:32:27 GMT0800 (中国标准时间)now.g…

如何实现input输入框自带清除按钮

最近&#xff0c;项目中需要&#xff0c;在输入框获取焦点是动态显示“”图标。即在输入框中输入内容时&#xff0c;右边显示“”按钮&#xff1b;输入框为空时&#xff0c;“”按钮消失。难点在于获取焦点的同时&#xff0c;获取输入内容。 注意&#xff1a;本例子的样式基于…

python-解码 decode 报错的问题

当解码使用默认的 decode() 拨错时&#xff0c;可以设置 errors 参数的值来解决 print(line) line_de_replace line.decode(errorsreplace).strip() # 用&#xff1f;代替 print("line_de_replace:", line_de_replace) line_de_ignore line.decode(errorsignore)…

字节码技术在模块依赖分析中的应用

背景 近年来&#xff0c;随着手机业务的快速发展&#xff0c;为满足手机端用户诉求和业务功能的迅速增长&#xff0c;移动端的技术架构也从单一的大工程应用&#xff0c;逐步向模块化、组件化方向发展。以高德地图为例&#xff0c;Android 端的代码已突破百万行级别&#xff0…

华为发布基于自进化AI的HiSec Insight安全态势感知系统

2020年4月21日&#xff0c;华为举办“安全新视界&#xff0c;AI知未然”主题线上发布会&#xff0c;邀请第三方研究机构、行业客户和合作伙伴共同探讨安全态势感知系统的演进方向&#xff0c;并见证华为HiSec Insight安全态势感知系统的全新面世。华为HiSec Insight安全态势感知…

“做好大数据测试,我是认真的!”

阿里妹导读&#xff1a;大数据已然是当下的重要课题&#xff0c;大大小小的企业在重视大数据的同时&#xff0c;也渐渐重视大数据质量的问题。阿里巴巴测试开发专家小郅&#xff0c;今天会分享他对数据测试的系统性思考。文章内容架构清晰&#xff0c;内容较长&#xff0c;建议…

从安全到镜像流水线,Docker 最佳实践与反模式一览

作者 | Timothy Mugayi译者 | 弯月&#xff0c;责编 | 夕颜封图 | CSDN付费下载自视觉中国出品 | CSDN&#xff08;ID:CSDNnews&#xff09;在使用Docker的大部分时间里&#xff0c;我们并不关心其内部的工作原理。仅凭启动一个Docker容器并且让应用程序运行良好&#xff0c;并…

ChaosBlade 发布对 C++ 应用混沌实验的支持

前言 为满足 C 应用系统故障演练&#xff0c;阿里妈妈安全生产团队开源了 C 混沌实验执行器&#xff0c;填补了 C 应用混沌工程实验的空白&#xff0c;其遵循《混沌实验模型》&#xff0c;可通过 ChaosBlade 工具直接执行。项目详情点击这里&#xff01; 。 本文重点介绍该执…

TortoiseGitPlink提示输入密码解决方法

文章目录一、现象二 、解决方法2.1. 打开TortoiseGit 下的puttygen工具2.2. 点击load&#xff0c;加载私钥2.3. 生成一个新的私钥2.4. 项目拉取2.5. 配置新的私钥一、现象 二 、解决方法 2.1. 打开TortoiseGit 下的puttygen工具 双击D:\software\TortoiseGit\bin下面的puttyg…

引领高并发直播场景进入毫秒时代,阿里云发布超低延时直播服务

近日&#xff0c;阿里云上线超低延时直播服务RTS&#xff08;Real-time Streaming&#xff09;&#xff0c;该服务在视频直播产品的基础上&#xff0c;进行全链路延时监控、传输协议改造等底层技术优化&#xff0c;支持千万级并发场景下的毫秒级延迟直播能力&#xff0c;保障低…

JavaScript-面向对象 class 继承

class继承 class 关键字是在ES6引入的 ES6之前的写法&#xff1a; function Student(name) {this.name name } // 给Student新增一个方法 Student.prototype.hello function () {alert(Hello) }ES6的写法&#xff1a; // 定义一个 学生的 类 class Student1{constructor(…

我26岁,月薪一万,刚实现“黄焖鸡自由”(苦笑)

今天是CSDN微信公众号千万粉丝达成的日子&#xff0c;因此&#xff0c;“千万粉丝狂欢节”来了&#xff01;第一弹超值福利来袭&#xff0c;前方高能&#xff1a;「粉丝节限定版一卡通」重磅上线&#xff01;可看该大牛老师全部课程&#xff01;课程涵盖热门的Java、Python和AI…

Android侧滑原来可以这么优雅

前言 侧滑手势在Android App应用得非常广泛&#xff0c;常见的使用场景包括&#xff1a;滑动抽屉、侧滑删除、侧滑返回、下拉刷新以及侧滑封面等。由于这些使用场景实在是太通用了&#xff0c;各路大神们八仙过海各显神通&#xff0c;每种侧滑场景都开源出了很多非常实用的框架…

TortoiseGit状态图标不能正常显示的解决办法

文章目录一. 运行环境一、方案11.1. 右键点击桌面空白处&#xff0c;打开TortoiseGit的Settings1.2. 修改Icon Overlays的Status cache1.3. 重启电脑&#xff0c;你就会发现你的小乌龟箭头出来了。二、方案2一. 运行环境 版本说明Windows 10 64bit操作系统TortoiseGit-2.12.0.…