首页 文章 精选 留言 我的

精选列表

搜索[php扩展],共10000篇文章
优秀的个人博客,低调大师

stone-1.2.4 发布, 扩展任务池

1: 介绍 BeeTP是stone包下一款小型任务池组件,它支持单次任务,定时任务,聚合任务的处理,整体功能类似JUC包中的线程池。 2: 参考例子 public class HelloTask implements BeeTask { public Object call() { return "Hello World"; } } public class MyTaskTest { public static void main(String[] args) throws Exception { BeeTaskServiceConfig config = new BeeTaskServiceConfig(); config.setWorkerKeepAliveTime(TimeUnit.SECONDS.toMillis(10)); BeeTaskService service = new BeeTaskService(config); //作为单次任务 BeeTaskHandle handle = service.submit(new HelloTask()); //作为定时任务 service.scheduleAtFixedRate(new HelloTask(), 0, 2, TimeUnit.SECONDS); //打印单次任务结果 System.out.println("Result:" + handle.get()); } } 单次任务,定时任务,聚合任务的接口提交方法使用的均是基于BeeTask的实现。 3: 修改内容 1:任务池代码重构,并进行精简处理 2:修复任务句柄中的取消时的并发性bug 3:在重构的基础上增加树状聚合型任务: BeeTreeTask,源码如下 public interface BeeTreeTask<E> { /** * return pre-split sub tasks * * @return sub tasks of current task */ BeeTreeTask<E>[] getSubTasks(); /** * execute call with handle array of sub tasks * * @param subTaskHandles handle array of sub tasks * @return execution value of method call * @throws Exception occurred in execution */ E call(BeeTaskHandle<E>[] subTaskHandles) throws Exception; } 4: 版本下载 <dependency> <groupId>io.github.chris2018998</groupId> <artifactId>stone</artifactId> <version>1.2.4</version> </dependency> 5: 项目地址 https://gitee.com/Chris2018998/stone https://github.com/Chris2018998/stone

优秀的个人博客,低调大师

如何为 Longhorn 扩展对象存储能力

作者: 王海龙,Rancher 中国社区技术经理,Linux Foundation APAC Evangelist,负责 Rancher 中国技术社区的维护和运营。拥有 8 年的云计算领域经验,经历了 OpenStack 到 Kubernetes 的技术变革,无论底层操作系统 Linux,还是虚拟化 KVM 或是 Docker 容器技术都有丰富的运维和实践经验。 在云原生环境中,非常重要的环节之一就是提供一个可以使用标准协议与客户端交互的存储系统。大家可能会马上联想到简单存储服务 S3 (Simple Storage Service)。S3 是一个庞大的协议,涵盖了存储桶、对象、密钥、版本控制、ACL 和区域等关键概念。 Longhorn 是 Kubernetes 的分布式块存储系统,除了支持块存储以外,还提供了文件系统能力。那么,Longhorn 能否充当 S3 的服务器,并提供单一的 AWS S3 API 接口来访问后端数据存储呢? 当然可以!但是你可能需要一个 S3 网关来向外部客户端提供对数据的访问权限。 s3gw 项目让这一切设想变的可行! 什么是 s3gw? s3gw 是一项与 S3 兼容的服务,专注于在任何 PVC 支持的 Kubernetes 环境中进行部署,包括 Longhorn。s3gw自成立以来,关注重点一直在云原生部署上。但是,如果附加了某种形式的存储,s3gw 是可以部署在许多场景中的。 s3gw 基于 Ceph 的 RADOSGW (RGW),但它是作为独立服务运行的,没有 RADOS 集群,并且依赖于 SUSE 存储团队持续开发的存储后端。目前 s3gw 仍处于开发的早期阶段,但它已经可以用于测试和使用一些 S3 功能。 以上介绍来自 s3gw 官网的直译。大家可以把 s3gw 看作一个减配版的 minio。只不过 s3gw 为了补充 Rancher 产品组合,目前将重心放在和 Longhorn 的适配上,但该工具不限于 Rancher 产品。另外,s3gw 采用的是Apache License, Version 2.0,而 minio 部分组件采用的是GNU AGPLv3 license。 接下来,本文将介绍如何安装和使用 s3gw。 部署 s3gw s3gw 依赖一个基础的 Kubernetes 集群和 Longhorn。本次介绍将通过 Rancher 创建一个下游 K3s 集群,然后通过 Rancher 应用商店在下游 K3s 集群中安装 Longhorn 和 s3gw。 部署 Rancher 和 K3s 为了节省篇幅,部署 Rancher 和 K3s 的步骤就不在本文中详细描述,大家可参考 Rancher 和 K3s 文档进行部署。 演示环境: 操作系统:Ubuntu 20.04.5 LTS Rancher 版本:rancher/rancher:v2.7.0 下游 K3s 版本:v1.24.8+k3s1 s3gw chart:0.8.0 Longhorn chart:101.1.0+up1.3.2 部署 Longhorn 通过 Rancher 部署 Longhorn 非常简单,只需要进入下游 K3s 集群,在 Rancher 仪表板左侧Apps下的Charts选择 Longhorn 便可开始部署 Longhorn: 安装非常简单,你不需要更改 chart 中的任何默认值。最后,如果一切顺利,你应该会看到 Rancher 的控制台显示: 安装 Longhorn 后,你只需单击左侧 Rancher 菜单中的 Longhorn 菜单即可重定向到 Longhorn 的仪表板: 新安装的 Longhorn 显示仍然没有应用程序使用持久卷。 部署 s3gw 和 Longhorn 一样,你仍然可以使用Apps来安装 S3gw,你会在合作伙伴存储库中找到可用的 s3gw,如下所示: 你也可以为 s3gw 设置一些自定义的参数。 本次 Demo 取消了Create Storage Class选项,并且修改Storage Class为longhorn。因为 Longhorn 已经通过 Rancher App 进行部署,并且名称为longhorn。 安装完成后,如果一切顺利,你应该会看到 Rancher 的控制台显示: 在 Longhorn 仪表板上,你可以验证应用程序是否正在使用 Longhorn 持久卷: 以上,我们就完成了 s3gw 的安装,接下来,我们就可以验证 S3gw 的使用! 验证 s3gw 默认情况下,s3gw chart 配置一个 ingress,该 ingress 指向具有 FQDN 的 S3 网关:s3gw.local。因此,你必须在 /etc/hosts 中定义 s3gw.local 指向主机物理接口之一的 IP 地址。 如果部署 s3gw 时启用了 S3 UI,那么也需要将 S3 UI 的 FQDN 加到映射文件中,例如: 35.182.248.120 s3gw.local 35.182.248.120 s3gw-ui.local 要测试 S3 gateway,你可以依赖 s3cmd,它是一个流行的命令行 S3 客户端。 安装后,你可以从 https://raw.githubusercontent.com/aquarist-labs/s3gw-core/main/env/s3cmd.cfg 获取 s3cmd 配置文件并将其用于 s3gw。 需要修改文件中的access_key和secret_key 接下来,你需要做的就是创建一个目录,将 s3cmd.cfg 放入其中,最后调用 s3cmd。 创建 bucket $ s3cmd -c s3cmd.cfg mb s3://foo 上传文件到 bucket 中 让我们创建一个 1mb 的文件,填充一些随机数据并将其放入存 bucket 中: $ dd if=/dev/random bs=1k count=1k of=obj.1mb.bin $ s3cmd -c s3cmd.cfg put obj.1mb.bin s3://foo 列出 bucket 中包含的对象 $ s3cmd -c s3cmd.cfg ls s3://foo 2022-12-02 02:05 1048576 s3://foo/obj.1mb.bin 删除 bucket $ s3cmd -c s3cmd.cfg rm s3://foo/obj.1mb.bin Rancher 备份对接 S3gw Rancher 可以通过rancher-backup operator来备份和恢复任何 Kubernetes 集群上的 Rancher,备份文件可以保存在 S3 兼容的对象存储中。这样我们就可以利用rancher-backup operator将 Rancher 备份通过 s3gw 存储在 longhorn 中。 要实现以上的需求,只需要在创建 Rancher 备份时选择Use an S3-compatible object store,并设置 s3gw 的连接参数即可,以下是示例: 备份创建成功后,可以通过 s3cmd 确认 Rancher 备份是否上传到 s3gw: $ s3cmd -c s3cmd.cfg ls s3://foo 2022-12-02 02:09 1048576 s3://foo/obj.1mb.bin 2022-12-02 02:55 826155 s3://foo/rancher-backup-demo-455e4847-d18f-4ae5-a960-90ecc5768986-2022-12-02T02-55-36Z.tar.gz S3gw UI s3gw Web UI 提供了一种与 s3gw 服务交互的直观方式,包括用户和存储桶管理,以及对象资源管理器。 要访问 s3gw Web UI,可以使用在 chart 中配置的 S3 UI FQDN 访问 S3 Web UI(本例为:s3gw-ui.local),并使用 chart 中设置的Access key和Secret key作为凭证登录 S3 Web UI(本例为:test/test): 下面是当前 UI 版本的一些屏幕截图,但请记住,我们仍在积极开发它,它的功能还不完整。 总 结 本文主要介绍了如何在 Rancher 中安装 Longhorn/s3gw 以及使用 S3 网关系统。K3s、Rancher 和 Longhorn 是建立一个具有弹性和高性能存储环境的强大工具,如果你需要将存储暴露给外部客户端,那么 s3gw 是一个非常好的选择。

优秀的个人博客,低调大师

图解kubernetes资源扩展机制实现(上)

k8s目前主要支持CPU和内存两种资源,为了支持用户需要按需分配的其他硬件类型的资源的调度分配,k8s实现了设备插件框架(device plugin framework)来用于其他硬件类型的资源集成,比如现在机器学习要使用GPU等资源,今天来看下其内部的关键实现 1. 基础概念 1.1 集成方式 1.1.1 DaemonSet与服务 当我们要集成本地硬件的资源的时候,我们可以在当前节点上通过DaemonSet来运行一个GRPC服务,通过这个服务来进行本地硬件资源的上报与分配 1.1.2 服务注册设计 当提供硬件服务需要与kubelet进行通信的时候,则首先需要进行注册,注册的方式,则是通过最原始的底层的socket文件,并且通过Linux文件系统的inotify机制,来实现服务的注册 1.2 插件服务感知 1.2.1 Watcher Watcher主要是负责感知当前节点上注册的服务,当发现新的要注册的插件服务,则会产生对应的事件,注册到当前的kubelet中 1.2.2 期望状态与实际状态 这里的状态主要是指的是否需要注册,因为kubelet与对应的插件服务是通过网络进行通信的,当网络出现问题、或者对应的插件服务故障,则可能会导致服务注册失败,但此时对应的服务的socket还依旧存在,即对应的插件服务依旧存在 此时就会有两种状态:期望状态与实际状态, 因为socket存在所以服务的期望状态其实是需要注册这个插件服务,但是实际上因为某些原因,这个插件服务并没有完成注册,后续会不断的通过期望状态,调整实际状态,从而达到一致 1.2.3 协调器 协调器则就是完成上述两种状态之间操作的核心,其通过调用对应插件的回调函数,其实就是调用对应的grpc接口,来完成期望状态与实际状态的一致性 1.2.4 插件控制器 针对每种类型的插件,都会有对应的控制器,其实也就是实现对应设备注册和反注册并且完成底层资源的分配(Allocate)和收集(ListWatch)操作 2. 插件服务发现 2.1 核心数据结构 type Watcher struct { // 感知插件服务注册的socket的路径 path string fs utilfs.Filesystem // inotify监测插件服务socket变化 fsWatcher *fsnotify.Watcher stopped chan struct{} // 存储期望状态 desiredStateOfWorld cache.DesiredStateOfWorld } 2.2 初始化 初始化其实就是创建对应的目录 func (w *Watcher) init() error { klog.V(4).Infof("Ensuring Plugin directory at %s ", w.path) if err := w.fs.MkdirAll(w.path, 0755); err != nil { return fmt.Errorf("error (re-)creating root %s: %v", w.path, err) } return nil } 2.3 插件服务发现核心 go func(fsWatcher *fsnotify.Watcher) { defer close(w.stopped) for { select { case event := &lt;-fsWatcher.Events: //如果发现对应目录的文件的变化,则会触发对应的事件 if event.Op&amp;fsnotify.Create == fsnotify.Create { err := w.handleCreateEvent(event) if err != nil { klog.Errorf("error %v when handling create event: %s", err, event) } } else if event.Op&amp;fsnotify.Remove == fsnotify.Remove { w.handleDeleteEvent(event) } continue case err := &lt;-fsWatcher.Errors: if err != nil { klog.Errorf("fsWatcher received error: %v", err) } continue case &lt;-stopCh: // In case of plugin watcher being stopped by plugin manager, stop // probing the creation/deletion of plugin sockets. // Also give all pending go routines a chance to complete select { case &lt;-w.stopped: case &lt;-time.After(11 * time.Second): klog.Errorf("timeout on stopping watcher") } w.fsWatcher.Close() return } } }(fsWatcher) 2.4 补偿机制 其实补偿机制主要是在重新启动kubelet的时候,需要将之前已经存在的socket重新注册到当前的kubelet中 func (w *Watcher) traversePluginDir(dir string) error { return w.fs.Walk(dir, func(path string, info os.FileInfo, err error) error { if err != nil { if path == dir { return fmt.Errorf("error accessing path: %s error: %v", path, err) } klog.Errorf("error accessing path: %s error: %v", path, err) return nil } switch mode := info.Mode(); { case mode.IsDir(): if err := w.fsWatcher.Add(path); err != nil { return fmt.Errorf("failed to watch %s, err: %v", path, err) } case mode&amp;os.ModeSocket != 0: event := fsnotify.Event{ Name: path, Op: fsnotify.Create, } //TODO: Handle errors by taking corrective measures if err := w.handleCreateEvent(event); err != nil { klog.Errorf("error %v when handling create event: %s", err, event) } default: klog.V(5).Infof("Ignoring file %s with mode %v", path, mode) } return nil }) } 2.5 注册事件回调 注册其实就只需要感知到的socket文件路径传递给期望状态进行管理 func (w *Watcher) handlePluginRegistration(socketPath string) error { if runtime.GOOS == "windows" { socketPath = util.NormalizePath(socketPath) } // 调用期望状态进行更新 klog.V(2).Infof("Adding socket path or updating timestamp %s to desired state cache", socketPath) err := w.desiredStateOfWorld.AddOrUpdatePlugin(socketPath) if err != nil { return fmt.Errorf("error adding socket path %s or updating timestamp to desired state cache: %v", socketPath, err) } return nil } 2.6 删除事件回调 注册其实就只需要感知到的socket文件路径传递给期望状态进行管理 func (w *Watcher) handleDeleteEvent(event fsnotify.Event) { klog.V(6).Infof("Handling delete event: %v", event) socketPath := event.Name klog.V(2).Infof("Removing socket path %s from desired state cache", socketPath) w.desiredStateOfWorld.RemovePlugin(socketPath) } 3.期望状态与实际状态 3.1 插件信息 插件信息其实只是存储了对应socket的路径和最近更新的时间 type PluginInfo struct { SocketPath string Timestamp time.Time } 3.2 期望状态 期望状态与实际状态在数据结构上都是一样的,因为本质上只是为了存储插件的当前的状态信息,即更新时间,这里不在赘述 type desiredStateOfWorld struct { socketFileToInfo map[string]PluginInfo sync.RWMutex } type actualStateOfWorld struct { socketFileToInfo map[string]PluginInfo sync.RWMutex } 4.OperationExecutor 目前k8s中支持两大类的插件的管理一类是DevicePlugin即我们本文说的这些都是这种概念,一类是CSIPlugin,其中针对每一类DRiver的处理其实内部都是不一样的,那其实在操作之前就要先感知到当前的Driver是那种类型的 OperationExecutor主要就是做这件事的,其根据不同的plugin类型,生成不同的要执行的操作,即对应的Plugin类型获取对应的handler,就生成了一个要执行的操作 4.1 生成注册插件回调函数 4.1.1 通过socket连接对应的插件服务 registerPluginFunc := func() error { client, conn, err := dial(socketPath, dialTimeoutDuration) if err != nil { return fmt.Errorf("RegisterPlugin error -- dial failed at socket %s, err: %v", socketPath, err) } defer conn.Close() ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() infoResp, err := client.GetInfo(ctx, &amp;registerapi.InfoRequest{}) if err != nil { return fmt.Errorf("RegisterPlugin error -- failed to get plugin info using RPC GetInfo at socket %s, err: %v", socketPath, err) } 4.1.2 根据插件类型验证服务 handler, ok := pluginHandlers[infoResp.Type] if !ok { if err := og.notifyPlugin(client, false, fmt.Sprintf("RegisterPlugin error -- no handler registered for plugin type: %s at socket %s", infoResp.Type, socketPath)); err != nil { return fmt.Errorf("RegisterPlugin error -- failed to send error at socket %s, err: %v", socketPath, err) } return fmt.Errorf("RegisterPlugin error -- no handler registered for plugin type: %s at socket %s", infoResp.Type, socketPath) } if infoResp.Endpoint == "" { infoResp.Endpoint = socketPath } if err := handler.ValidatePlugin(infoResp.Name, infoResp.Endpoint, infoResp.SupportedVersions); err != nil { if err = og.notifyPlugin(client, false, fmt.Sprintf("RegisterPlugin error -- plugin validation failed with err: %v", err)); err != nil { return fmt.Errorf("RegisterPlugin error -- failed to send error at socket %s, err: %v", socketPath, err) } return fmt.Errorf("RegisterPlugin error -- pluginHandler.ValidatePluginFunc failed") } 4.1.3 注册插件到实际状态 err = actualStateOfWorldUpdater.AddPlugin(cache.PluginInfo{ SocketPath: socketPath, Timestamp: timestamp, }) if err != nil { klog.Errorf("RegisterPlugin error -- failed to add plugin at socket %s, err: %v", socketPath, err) } // 调用插件的注册回调函数 if err := handler.RegisterPlugin(infoResp.Name, infoResp.Endpoint, infoResp.SupportedVersions); err != nil { return og.notifyPlugin(client, false, fmt.Sprintf("RegisterPlugin error -- plugin registration failed with err: %v", err)) } 4.1.4 通知对应的服务注册成功 if err := og.notifyPlugin(client, true, ""); err != nil { return fmt.Errorf("RegisterPlugin error -- failed to send registration status at socket %s, err: %v", socketPath, err) } 4.2 通过socket构建注册client func dial(unixSocketPath string, timeout time.Duration) (registerapi.RegistrationClient, *grpc.ClientConn, error) { ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() c, err := grpc.DialContext(ctx, unixSocketPath, grpc.WithInsecure(), grpc.WithBlock(), grpc.WithContextDialer(func(ctx context.Context, addr string) (net.Conn, error) { return (&amp;net.Dialer{}).DialContext(ctx, "unix", addr) }), ) if err != nil { return nil, nil, fmt.Errorf("failed to dial socket %s, err: %v", unixSocketPath, err) } return registerapi.NewRegistrationClient(c), c, nil } 今天就先到这里,下一章会继续介绍如何组合上述组件以及默认的回调管理机制的实现,进探究到这里谢谢大家,感谢分享点赞,反转又不花钱 k8s源码阅读电子书地址: https://www.yuque.com/baxiaoshi/tyado3 > 微信号:baxiaoshi2020 > 关注公告号阅读更多源码分析文章 > 更多文章关注 www.sreguide.com > 本文由博客一文多发平台 OpenWrite 发布

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

用户登录
用户注册