Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

高速でダウンロードできるツールを Go で実装した。

More than 3 years have passed since last update.

Code-Hex/pget - GitHub  GitHub stars

Parallel file download client

linux カーネルのダウンロードが約1分で終わる様子
asciicast

これは何なのか:question:

簡単に言うと pget は Go 言語製の高速ダウンロードツールです。 wget っぽく気軽に実行できるように pget と名付けました。

作成の経緯:beer:

たまたま Nodejs で Range header に対してスレッドを使って分割ダウンロードするという ここの記事 を見かけたので今回これを並列処理を得意とする Go 言語で実装してみようと思い作成しました。

調べてみると Python や、Java などのほとんどの言語ですでに実装されてるようですが、どれもただ使えればいいという感じだったので、割と本気で作ってみました。(調べられてないだけの可能性高いです:sweat:

仕組み:bulb:

ファイルをダウンロードする時、ほとんどの場合がリダイレクトを行いファイルを配信するための URL をクライアントへ渡してダウンロードが行われます。この理由から、 pget では初めにリダイレクト先の URL を取得し、その URL へ複数の goroutine を使用して、 Range access を行いファイルを分割でダウンロードして最後にそれらの分割されたファイルを結合し、元のファイルを作成するといった方法をとっています。

Range access:satellite:

Range access は「どの位置からどの位置までダウンロードする」と要求を送ることでその範囲分のファイルをダウンロードするといったものです。よくブラウザなどでダウンロードを中断した時に、後からダウンロードを再開できるレジューム機能に使われています。
Range access に対応しているサーバへアクセスするとそのヘッダには

Accept-Ranges: bytes

という記述が存在します。この時クライアント側で Range access するには

Range: bytes=0-999

と指定して要求を行います。 こちら のサイトの解説がとても解りやすいです。

goroutine による分割並列ダウンロード:arrow_down:

Range access でファイルの範囲を指定してダウンロードできるということを説明しました。Range access の仕組みを利用すれば分割でダウンロードすることができます。その上それらの分割ファイルを同時にダウンロードすることができれば、理想時間としてその分割した個数倍ダウンロード速度が速くなるということになります。

結合:twisted_rightwards_arrows:

pget では分割されたファイル名の末尾に番号が振られます。ダウンロードが完了した後からのファイルを作成し、分割ダウンロードされたファイル名の番号の昇順でからのファイルに書き込んでいきます。そうすることで元のファイルが作成されます。これが分割並列ダウンロードクライアントの仕組みです。

pget の特徴:exclamation:

記事作成時 (7/13) の pget には次のような特徴があります。

ダウンロードを途中で中断した場合でも、ダウンロードを再開できる。 resume.gif

好きなディレクトリへ名前をつけて保存ができる。
output.gif

他にも

  • タイムアウト設定ができる
  • pget の最新版がリリースされているか確認ができる
  • Windows でも動く

個人的に高速でダウンロードができて、レジュームができるのは強いと思ってます:

工夫点:bangbang:

かなり頑張ったのが error 処理です。分割ダウンロードの際、 goroutine は非同期での処理になるので、もし一つの goroutine でエラーが起こった場合全ての goroutine を終了させなければいけません。そしてそのエラーの原因を取得しなければいけないのですが、これが難しかった...

これらは :package: context を利用することで解決できました。詳しくは

https://github.com/Code-Hex/pget/blob/master/requests.go#L67
https://github.com/Code-Hex/pget/blob/master/ch.go

のコードを読んだ方が把握しやすいと思います:thumbsup:

インストール:tada:

Homebrew

brew tap Code-Hex/pget
brew install pget

go get

$ go get -u github.com/Code-Hex/pget/cmd/pget

バイナリ配布も行っています!

考察

-p オプションで起動する goroutine を指定することができますが、搭載されているCPUの数(ハイパースレッディングも考慮する)よりもスケールはしません。なので -p 100 とかしてもそこまで高速化されません。しかし僕のノートブックだと 4 ですが、分割ダウンロードということもあって -p 4 よりも -p 6 の方が早かったりします。CPUの数より少し大きい方が早いようです。

補足ですが、あまりにもたくさんの数で実行するとサーバーに負荷がかかるのでほどほどにお願いします。

最後に

もしよかったら pget で Star をつけてもらえるとモチベーションが上がります。そしてなにより、Go 言語を始めたばかりですので、まだおかしい部分があるはずです。そういった部分を issue や PR、Qiita のコメントで教えてもらえるととても嬉しいです。
pget を是非使ってみてください!

codehex
IQは4億、偏差値は3億、合計7億のアカウントです。
http://codehex.hateblo.jp/
mercari
フリマアプリ「メルカリ」を、グローバルで開発しています。
https://tech.mercari.com/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away