40分钟学 Go 语言高并发:并发下载器开发实战教程

news2024/11/23 15:14:59

并发下载器开发实战教程

一、系统设计概述

1.1 功能需求表

功能模块描述技术要点
分片下载将大文件分成多个小块并发下载goroutine池、分片算法
断点续传支持下载中断后继续下载文件指针定位、临时文件管理
进度显示实时显示下载进度和速度进度计算、速度统计
错误处理处理下载过程中的各种错误错误类型定义、重试机制
文件合并将下载的分片合并成完整文件文件操作、数据校验

1.2 核心结构设计

// 下载任务结构
type DownloadTask struct {
    URL           string
    TargetPath    string
    TotalSize     int64
    ChunkSize     int64
    Chunks        []*Chunk
    Progress      *Progress
    ErrorHandler  *ErrorHandler
    Concurrency   int
    RetryTimes    int
    RetryInterval time.Duration
}

// 分片信息
type Chunk struct {
    ID           int
    Start        int64
    End          int64
    Downloaded   int64
    Status       ChunkStatus
    TempFilePath string
}

// 进度信息
type Progress struct {
    TotalSize     int64
    Downloaded    int64
    Speed         float64
    Percentage    float64
    LastUpdate    time.Time
    StatusChannel chan StatusUpdate
}

// 错误处理器
type ErrorHandler struct {
    RetryTimes    int
    RetryInterval time.Duration
    Errors        chan error
    ErrorLog      *log.Logger
}

二、核心代码实现

2.1 主程序入口

package main

import (
    "fmt"
    "log"
    "os"
    "time"
)

func main() {
    // 创建下载任务
    task := &DownloadTask{
        URL:           "https://example.com/largefile.zip",
        TargetPath:    "largefile.zip",
        Concurrency:   5,
        RetryTimes:    3,
        RetryInterval: time.Second * 5,
    }

    // 初始化下载器
    downloader := NewDownloader(task)

    // 开始下载
    err := downloader.Start()
    if err != nil {
        log.Fatal(err)
    }
}

// NewDownloader 创建新的下载器实例
func NewDownloader(task *DownloadTask) *Downloader {
    return &Downloader{
        task:    task,
        progress: NewProgress(),
        errorHandler: NewErrorHandler(task.RetryTimes, task.RetryInterval),
    }
}

2.2 分片下载实现

// 分片管理
func (d *Downloader) splitTask() error {
    // 获取文件大小
    totalSize, err := d.getFileSize()
    if err != nil {
        return err
    }
    d.task.TotalSize = totalSize

    // 计算分片大小
    chunkSize := d.calculateChunkSize(totalSize)
    d.task.ChunkSize = chunkSize

    // 创建分片
    var chunks []*Chunk
    for i := 0; i < d.calculateChunkCount(); i++ {
        start := int64(i) * chunkSize
        end := start + chunkSize - 1
        if i == d.calculateChunkCount()-1 {
            end = totalSize - 1
        }

        chunk := &Chunk{
            ID:    i,
            Start: start,
            End:   end,
            Status: ChunkStatusPending,
            TempFilePath: fmt.Sprintf("%s.part%d", d.task.TargetPath, i),
        }
        chunks = append(chunks, chunk)
    }
    d.task.Chunks = chunks
    return nil
}

// 下载单个分片
func (d *Downloader) downloadChunk(chunk *Chunk) error {
    client := &http.Client{}
    req, err := http.NewRequest("GET", d.task.URL, nil)
    if err != nil {
        return err
    }

    // 设置Range头部
    req.Header.Set("Range", fmt.Sprintf("bytes=%d-%d", chunk.Start, chunk.End))

    resp, err := client.Do(req)
    if err != nil {
        return err
    }
    defer resp.Body.Close()

    // 创建临时文件
    tmpFile, err := os.Create(chunk.TempFilePath)
    if err != nil {
        return err
    }
    defer tmpFile.Close()

    // 写入数据并更新进度
    buffer := make([]byte, 32*1024)
    for {
        n, err := resp.Body.Read(buffer)
        if n > 0 {
            tmpFile.Write(buffer[:n])
            chunk.Downloaded += int64(n)
            d.updateProgress(int64(n))
        }
        if err != nil {
            if err == io.EOF {
                break
            }
            return err
        }
    }

    chunk.Status = ChunkStatusCompleted
    return nil
}

2.3 进度监控实现

// 进度管理器
type Progress struct {
    mu            sync.Mutex
    downloaded    int64
    totalSize     int64
    startTime     time.Time
    lastUpdate    time.Time
    speedSamples  []float64
    statusChannel chan StatusUpdate
}

// 更新进度
func (p *Progress) Update(n int64) {
    p.mu.Lock()
    defer p.mu.Unlock()

    p.downloaded += n
    now := time.Now()
    duration := now.Sub(p.lastUpdate).Seconds()
    
    if duration >= 1.0 {
        speed := float64(n) / duration
        p.speedSamples = append(p.speedSamples, speed)
        if len(p.speedSamples) > 10 {
            p.speedSamples = p.speedSamples[1:]
        }
        p.lastUpdate = now
        
        // 计算平均速度
        var avgSpeed float64
        for _, s := range p.speedSamples {
            avgSpeed += s
        }
        avgSpeed /= float64(len(p.speedSamples))

        // 发送状态更新
        p.statusChannel <- StatusUpdate{
            Downloaded: p.downloaded,
            TotalSize: p.totalSize,
            Speed: avgSpeed,
            Percentage: float64(p.downloaded) / float64(p.totalSize) * 100,
        }
    }
}

// 显示进度
func (p *Progress) displayProgress() {
    for status := range p.statusChannel {
        fmt.Printf("\rProgress: %.2f%% Speed: %.2f MB/s", 
            status.Percentage,
            status.Speed/1024/1024)
    }
}

2.4 错误处理实现

// 错误处理器
type ErrorHandler struct {
    retryTimes    int
    retryInterval time.Duration
    errors        chan error
    errorLog      *log.Logger
}

// 错误重试
func (eh *ErrorHandler) RetryDownload(chunk *Chunk, downloadFunc func(*Chunk) error) error {
    var lastErr error
    for i := 0; i < eh.retryTimes; i++ {
        err := downloadFunc(chunk)
        if err == nil {
            return nil
        }
        lastErr = err
        eh.logError(fmt.Sprintf("Chunk %d download failed: %v, retry %d/%d", 
            chunk.ID, err, i+1, eh.retryTimes))
        time.Sleep(eh.retryInterval)
    }
    return fmt.Errorf("max retry times reached: %v", lastErr)
}

// 错误日志记录
func (eh *ErrorHandler) logError(msg string) {
    eh.errorLog.Println(msg)
}

2.5 完整下载管理器

// Downloader 主程序流程
func (d *Downloader) Start() error {
    // 1. 切分任务
    if err := d.splitTask(); err != nil {
        return err
    }

    // 2. 创建工作池
    workPool := make(chan *Chunk, d.task.Concurrency)
    var wg sync.WaitGroup

    // 3. 启动工作协程
    for i := 0; i < d.task.Concurrency; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            for chunk := range workPool {
                err := d.errorHandler.RetryDownload(chunk, d.downloadChunk)
                if err != nil {
                    d.errorHandler.errors <- err
                }
            }
        }()
    }

    // 4. 分发任务
    go func() {
        for _, chunk := range d.task.Chunks {
            workPool <- chunk
        }
        close(workPool)
    }()

    // 5. 启动进度显示
    go d.progress.displayProgress()

    // 6. 等待所有下载完成
    wg.Wait()

    // 7. 合并文件
    return d.mergeChunks()
}

// 合并分片文件
func (d *Downloader) mergeChunks() error {
    targetFile, err := os.Create(d.task.TargetPath)
    if err != nil {
        return err
    }
    defer targetFile.Close()

    for _, chunk := range d.task.Chunks {
        chunkFile, err := os.Open(chunk.TempFilePath)
        if err != nil {
            return err
        }

        _, err = io.Copy(targetFile, chunkFile)
        chunkFile.Close()
        os.Remove(chunk.TempFilePath)
        
        if err != nil {
            return err
        }
    }

    return nil
}

三、系统流程图

添加一个流程图来说明下载器的工作流程:
在这里插入图片描述
让我们再添加一个并发控制的状态图:
在这里插入图片描述

这个并发下载器的实现包含了以下核心特点:

  1. 分片下载设计

    • 根据文件大小自动计算合适的分片大小
    • 支持断点续传
    • 临时文件管理
    • 分片合并校验
  2. goroutine协作

    • 使用工作池控制并发数
    • 协程间通信采用channel
    • 优雅的协程退出机制
    • 资源释放保证
  3. 进度监控

    • 实时速度计算
    • 平均速度统计
    • 进度百分比显示
    • 状态更新通知

好的,让我继续完成错误处理部分和补充其他重要内容:

  1. 错误处理
    • 完整的重试机制
    • 错误日志记录
    • 错误类型分类
    • 优雅降级策略

接下来补充一些重要的实现代码:

package main

import (
    "context"
    "fmt"
    "io"
    "log"
    "net/http"
    "os"
    "sync"
    "time"
)

// 错误类型定义
type DownloadError struct {
    ChunkID int
    Err     error
    Retried int
}

func (e *DownloadError) Error() string {
    return fmt.Sprintf("chunk %d download failed: %v (retried: %d times)", 
        e.ChunkID, e.Err, e.Retried)
}

// 状态更新结构
type StatusUpdate struct {
    ChunkID    int
    Downloaded int64
    Speed      float64
    Error      error
}

// 下载管理器
type Downloader struct {
    ctx          context.Context
    cancel       context.CancelFunc
    task         *DownloadTask
    progress     *Progress
    errorHandler *ErrorHandler
    wg           sync.WaitGroup
}

// 创建新的下载管理器
func NewDownloader(task *DownloadTask) *Downloader {
    ctx, cancel := context.WithCancel(context.Background())
    return &Downloader{
        ctx:          ctx,
        cancel:       cancel,
        task:         task,
        progress:     NewProgress(task.TotalSize),
        errorHandler: NewErrorHandler(task.RetryTimes, task.RetryInterval),
    }
}

// 下载器核心实现
func (d *Downloader) Start() error {
    // 1. 准备工作
    if err := d.prepare(); err != nil {
        return fmt.Errorf("preparation failed: %v", err)
    }

    // 2. 创建工作池
    workChan := make(chan *Chunk, d.task.Concurrency)
    statusChan := make(chan StatusUpdate, d.task.Concurrency)

    // 3. 启动工作协程
    for i := 0; i < d.task.Concurrency; i++ {
        d.wg.Add(1)
        go d.worker(workChan, statusChan)
    }

    // 4. 启动状态监控
    go d.monitorStatus(statusChan)

    // 5. 分发任务
    for _, chunk := range d.task.Chunks {
        select {
        case workChan <- chunk:
        case <-d.ctx.Done():
            return fmt.Errorf("download cancelled")
        }
    }

    // 6. 关闭工作通道
    close(workChan)

    // 7. 等待所有工作完成
    d.wg.Wait()

    // 8. 检查是否有错误发生
    if err := d.errorHandler.GetFatalError(); err != nil {
        return err
    }

    // 9. 合并文件
    return d.mergeChunks()
}

// 工作协程
func (d *Downloader) worker(workChan <-chan *Chunk, statusChan chan<- StatusUpdate) {
    defer d.wg.Done()

    for chunk := range workChan {
        err := d.downloadChunkWithRetry(chunk)
        if err != nil {
            statusChan <- StatusUpdate{
                ChunkID: chunk.ID,
                Error:   err,
            }
            continue
        }

        statusChan <- StatusUpdate{
            ChunkID:    chunk.ID,
            Downloaded: chunk.End - chunk.Start + 1,
        }
    }
}

// 带重试的分片下载
func (d *Downloader) downloadChunkWithRetry(chunk *Chunk) error {
    retries := 0
    for retries <= d.task.RetryTimes {
        err := d.downloadChunk(chunk)
        if err == nil {
            return nil
        }

        retries++
        if retries > d.task.RetryTimes {
            return &DownloadError{
                ChunkID: chunk.ID,
                Err:     err,
                Retried: retries - 1,
            }
        }

        // 记录重试日志
        log.Printf("Chunk %d download failed: %v, retrying (%d/%d)...", 
            chunk.ID, err, retries, d.task.RetryTimes)
        
        // 等待重试间隔
        select {
        case <-time.After(d.task.RetryInterval):
        case <-d.ctx.Done():
            return fmt.Errorf("download cancelled during retry")
        }
    }
    return nil
}

// 优化的分片下载实现
func (d *Downloader) downloadChunk(chunk *Chunk) error {
    // 创建HTTP请求
    req, err := http.NewRequestWithContext(d.ctx, "GET", d.task.URL, nil)
    if err != nil {
        return err
    }

    // 设置Range头
    req.Header.Set("Range", fmt.Sprintf("bytes=%d-%d", chunk.Start+chunk.Downloaded, chunk.End))

    // 发送请求
    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        return err
    }
    defer resp.Body.Close()

    // 检查响应状态
    if resp.StatusCode != http.StatusPartialContent {
        return fmt.Errorf("unexpected status code: %d", resp.StatusCode)
    }

    // 打开或创建临时文件
    f, err := os.OpenFile(chunk.TempFilePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
    if err != nil {
        return err
    }
    defer f.Close()

    // 设置写入位置
    if _, err := f.Seek(chunk.Downloaded, io.SeekStart); err != nil {
        return err
    }

    // 创建带缓冲的写入器
    bufWriter := bufio.NewWriter(f)
    defer bufWriter.Flush()

    // 下载数据
    buffer := make([]byte, 32*1024)
    for {
        select {
        case <-d.ctx.Done():
            return fmt.Errorf("download cancelled")
        default:
            n, err := resp.Body.Read(buffer)
            if n > 0 {
                // 写入数据
                if _, err := bufWriter.Write(buffer[:n]); err != nil {
                    return err
                }
                
                // 更新进度
                chunk.Downloaded += int64(n)
                d.progress.Update(int64(n))
            }
            if err == io.EOF {
                return nil
            }
            if err != nil {
                return err
            }
        }
    }
}

// 状态监控
func (d *Downloader) monitorStatus(statusChan <-chan StatusUpdate) {
    ticker := time.NewTicker(time.Second)
    defer ticker.Stop()

    for {
        select {
        case status := <-statusChan:
            if status.Error != nil {
                d.errorHandler.HandleError(status.Error)
                continue
            }
            d.progress.Update(status.Downloaded)

        case <-ticker.C:
            d.displayProgress()

        case <-d.ctx.Done():
            return
        }
    }
}

// 显示下载进度
func (d *Downloader) displayProgress() {
    status := d.progress.GetStatus()
    fmt.Printf("\rProgress: %.2f%% Speed: %.2f MB/s", 
        status.Percentage,
        status.Speed/1024/1024)
}

// 优化的文件合并实现
func (d *Downloader) mergeChunks() error {
    // 创建目标文件
    targetFile, err := os.Create(d.task.TargetPath)
    if err != nil {
        return err
    }
    defer targetFile.Close()

    // 使用bufio提升写入性能
    bufWriter := bufio.NewWriter(targetFile)
    defer bufWriter.Flush()

    // 合并所有分片
    for _, chunk := range d.task.Chunks {
        // 打开分片文件
        chunkFile, err := os.Open(chunk.TempFilePath)
        if err != nil {
            return err
        }

        // 使用bufio提升读取性能
        bufReader := bufio.NewReader(chunkFile)

        // 复制数据
        _, err = io.Copy(bufWriter, bufReader)
        chunkFile.Close()
        
        // 删除临时文件
        os.Remove(chunk.TempFilePath)

        if err != nil {
            return fmt.Errorf("failed to merge chunk %d: %v", chunk.ID, err)
        }
    }

    return nil
}

func main() {
    // 创建下载任务
    task := &DownloadTask{
        URL:           "https://example.com/largefile.zip",
        TargetPath:    "largefile.zip",
        Concurrency:   5,
        RetryTimes:    3,
        RetryInterval: time.Second * 5,
        ChunkSize:     1024 * 1024 * 10, // 10MB per chunk
    }

    // 创建下载器
    downloader := NewDownloader(task)

    // 开始下载
    if err := downloader.Start(); err != nil {
        log.Fatal(err)
    }

    fmt.Println("\nDownload completed successfully!")
}

并发下载器的性能优化建议表:

优化方向具体措施预期效果
并发控制根据系统资源调整并发数避免资源竞争,提高整体性能
内存使用使用固定大小的缓冲区减少内存分配,避免GC压力
IO操作使用bufio进行缓冲IO减少系统调用,提高IO效率
错误处理实现智能重试机制提高下载成功率
进度计算批量更新进度减少锁竞争,提高并发效率

核心优化要点:

  1. 分片策略优化

    • 动态调整分片大小
    • 考虑网络状况
    • 考虑文件大小
    • 优化分片合并
  2. 内存管理优化

    • 使用对象池
    • 控制缓冲区大小
    • 及时释放资源
    • 避免内存泄漏
  3. 并发控制优化

    • 动态调整goroutine数量
    • 使用环形缓冲区
    • 实现背压机制
    • 优化锁策略
  4. IO性能优化

    • 使用bufio
    • 适当的buffer大小
    • 批量写入
    • 减少系统调用

使用建议:

  1. 根据实际需求调整参数

    • 并发数
    • 分片大小
    • 缓冲区大小
    • 重试策略
  2. 监控关键指标

    • CPU使用率
    • 内存占用
    • 磁盘IO
    • 网络带宽
  3. 做好错误处理

    • 完整的日志记录
    • 合理的重试策略
    • 优雅的降级处理
    • 用户友好的错误提示
  4. 进行充分测试

    • 单元测试
    • 性能测试
    • 压力测试
    • 异常场景测试

这个并发下载器的实现考虑了实际应用中的各种场景,包括:

  1. 网络不稳定
  2. 断点续传需求
  3. 大文件处理
  4. 资源限制
  5. 错误恢复
  6. 性能优化

通过这些特性和优化,可以实现一个稳定高效的文件下载器。


怎么样今天的内容还满意吗?再次感谢观众老爷的观看,关注GZH:凡人的AI工具箱,回复666,送您价值199的AI大礼包。最后,祝您早日实现财务自由,还请给个赞,谢谢!

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

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

相关文章

《Object类》

目录 一、Object类 1.1 定义与地位 1.2 toString()方法 1.3 equals()方法 1.4 hashcode()方法 一、Object类 1.1 定义与地位 Object类是Java语言中的根类&#xff0c;所有的类&#xff08;除了Object类&#xff09;都直接或间接继承自Object。这就意味着在Java中&#xf…

Vercel 设置自动部署 GitHub 项目

Vercel 设置自动部署 GitHub 项目 问题背景 最近 Vercel 调整了其部署政策&#xff0c;免费版用户无法继续使用自动部署功能&#xff0c;除非升级到 Pro 计划。但是&#xff0c;我们可以通过配置 Deploy Hooks 来实现同样的自动部署效果。 解决方案 通过设置 Vercel 的 Dep…

2023年9月GESPC++一级真题解析

一、单选题&#xff08;每题2分&#xff0c;共30分&#xff09; 题号 123456789101112131415 答案 CDBCDBACACBBDDA 1. 我们通常说的 “ 内存 ” 属于计算机中的&#xff08;&#xff09;。 A. 输出设备 B. 输 ⼊ 设备 C. 存储设备 D. 打印设备 【答案】 C 【考纲知识点】…

Laravel对接SLS日志服务

Laravel对接SLS日志服务&#xff08;写入和读取&#xff09; 1、下载阿里云的sdk #通过composer下载 composer require alibabacloud/aliyun-log-php-sdk#对应的git仓库 https://github.com/aliyun/aliyun-log-php-sdk2、创建sdk请求的service <?phpnamespace App\Ser…

SQL注入--报错注入--理论

什么是报错注入&#xff1f; 正常用户向数据库查询数据&#xff0c;查询语句出现错误时会返回报错信息。 如果数据库对查询语句的输入和数据的输出没有进行合理检测&#xff0c;攻击者就可以通过构造语句让报错信息中包含数据库的内容。 基本利用形式 updatexml注入 函数形…

ECharts柱状图-带圆角的堆积柱状图,附视频讲解与代码下载

引言&#xff1a; 在数据可视化的世界里&#xff0c;ECharts凭借其丰富的图表类型和强大的配置能力&#xff0c;成为了众多开发者的首选。今天&#xff0c;我将带大家一起实现一个柱状图图表&#xff0c;通过该图表我们可以直观地展示和分析数据。此外&#xff0c;我还将提供…

002 MATLAB语言基础

01 变量命名规则 变量名只能由字母、数字和下划线组成&#xff0c;且必须以字母开头&#xff1b; 变量名区分字母的大小写&#xff1b; 变量名不能超过最大长度限制&#xff1b; 关键字不能作为变量名&#xff0c;如for、end和if等&#xff1b; 注意&#xff1a;存变量命名时…

Java 对象头、Mark Word、monitor与synchronized关联关系以及synchronized锁优化

1. 对象在内存中的布局分为三块区域&#xff1a; &#xff08;1&#xff09;对象头&#xff08;Mark Word、元数据指针和数组长度&#xff09; 对象头&#xff1a;在32位虚拟机中&#xff0c;1个机器码等于4字节&#xff0c;也就是32bit&#xff0c;在64位虚拟机中&#xff0…

RTL8211F 1000M以太网PHY指示灯

在RK3562 Linux5.10 SDK里面已支持该芯片kernel-5.10/drivers/net/phy/realtek.c&#xff0c;而默认是没有去修改到LED配置的&#xff0c;我们根据硬件设计修改相应的寄存器配置&#xff0c;该PHY有3个LED引脚&#xff0c;我们LED0不使用&#xff0c;LED1接绿灯&#xff08;数据…

通过IIC访问模块寄存器[ESP--1]

上一节中&#xff0c;我们简单使用ESP函数来从主机视角扫描所有的IIC设备|上一篇文章的链接|&#xff0c;但是并不存在主从机之间的交流。这显然是不合理的&#xff0c;这个小节我们来学习如何实现主从机之间的通信 模块的寄存器 不说最简单的电阻电容电感&#xff0c;稍微复…

Spring Cloud Netflix 系列:Eureka 经典实战案例和底层原理解析

文章目录 前言Eureka 简介架构设计工作流程 项目 demo 构建Eureka Server 的搭建Eureka Client 的配置补充说明 运行效果 深入使用Eureka 注册中心添加认证搭建 Eureka 集群实现高可用双节点集群搭建 运行效果补充说明为什么要配置 不同host 原理解析服务注册、心跳续期详细流程…

数字赋能,气象引领 | 气象景观数字化服务平台重塑京城旅游生态

在数字化转型的浪潮中&#xff0c;旅游行业正以前所未有的速度重塑自身&#xff0c;人民群众对于高品质、个性化旅游服务需求的日益增长&#xff0c;迎着新时代的挑战与机遇&#xff0c;为开展北京地区特色气象景观预报&#xff0c;打造“生态气象旅游”新业态&#xff0c;助推…

(python)unittest框架

unittest unnitest介绍 TestCase测试用例 书写真正的用例脚本

Hadoop 系列 MapReduce:Map、Shuffle、Reduce

文章目录 前言MapReduce 基本流程概述MapReduce 三个核心阶段详解Map 阶段工作原理 Shuffle 阶段具体步骤分区&#xff08;Partition&#xff09;排序&#xff08;Sort&#xff09;分组&#xff08;Combine 和 Grouping&#xff09; Reduce 阶段工作原理 MapReduce 应用场景Map…

微服务即时通讯系统的实现(服务端)----(1)

目录 1. 项目介绍和服务器功能设计2. 基础工具安装3. gflags的安装与使用3.1 gflags的介绍3.2 gflags的安装3.3 gflags的认识3.4 gflags的使用 4. gtest的安装与使用4.1 gtest的介绍4.2 gtest的安装4.3 gtest的使用 5 Spdlog日志组件的安装与使用5.1 Spdlog的介绍5.2 Spdlog的安…

uniapp发布android上架应用商店权限

先看效果&#xff1a; 实现原理&#xff1a; 一、利用uni.addInterceptor的拦截器&#xff0c;在一些调用系统权限前拦截&#xff0c;进行弹窗展示&#xff0c;监听确定取消实现业务逻辑。 二、弹窗是原生nativeObj进行drawRect绘制的 三、权限申请调用使用的 plus.android.…

AmazonS3集成minio实现https访问

最近系统全面升级到https&#xff0c;之前AmazonS3大文件分片上传直接使用http://ip:9000访问minio的方式已然行不通&#xff0c;https服务器访问http资源会报Mixed Content混合内容错误。 一般有两种解决方案&#xff0c;一是升级minio服务&#xff0c;配置ssl证书&#xff0c…

【代码pycharm】动手学深度学习v2-08 线性回归 + 基础优化算法

课程链接 线性回归的从零开始实现 import random import torch from d2l import torch as d2l# 人造数据集 def synthetic_data(w,b,num_examples):Xtorch.normal(0,1,(num_examples,len(w)))ytorch.matmul(X,w)bytorch.normal(0,0.01,y.shape) # 加入噪声return X,y.reshape…

英文版本-带EXCEL函数的数据分析

一、问题&#xff1a; 二、表格内容 三、分析结果 四、具体的操作步骤&#xff1a; 销售工作表公式设计与数据验证 类别&#xff08;Category&#xff09;列公式&#xff1a; 在Category列&#xff08;假设为D列&#xff09;&#xff0c;根据ProductCode在Catalogue工作表中查找…

Ease Monitor 会把基础层,中间件层的监控数据和服务的监控数据打通,从总体的视角提供监控分析

1. 产品定位 Ease Monitor 有如下的产品定位&#xff1a; 关注于整体应用的SLA。 主要从为用户服务的 API 来监控整个系统。 关联指标聚合。 把有关联的系统及其指示聚合展示。主要是三层系统数据&#xff1a;基础层、平台中间件层和应用层。 快速故障定位。 对于现有的系统…