日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

java 下载限速_Java 文件下载限流算法

發(fā)布時間:2024/3/26 java 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 java 下载限速_Java 文件下载限流算法 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

在做文件下載功能時,為了避免下載功能將服務器的帶寬打滿,從而影響服務器的其他服務。我們可以設計一個限流器來限制下載的速率,從而限制下載服務所占用的帶寬。

一、算法思路

定義一個數據塊chunk(單位 bytes)以及允許的最大速率 maxRate(單位 KB/s)。通過maxRate我們可以算出,在maxRate的速率下,通過一個數據塊大小的字節(jié)流所需要的時間 timeCostPerChunk。

之后,在讀取/寫入字節(jié)時,我們維護已經讀取/寫入的字節(jié)量 bytesWillBeSentOrReceive。

當bytesWillBeSentOrReceive達到一個數據塊的大小時,檢查期間消耗的時間(nowNanoTime-lastPieceSentOrReceiveTick)

如果期間消耗的時間小于timeCostPerChunk的值,說明當前的速率已經超過了 maxRate的速率,這時候就需要休眠一會來限制流量

如果速率沒超過或者休眠完后,將 bytesWillBeSentOrReceive=bytesWillBeSentOrReceive-chunkSize

之后在讀取/寫入數據時繼續(xù)檢查。

下面該算法的Java代碼實現(xiàn):

public synchronized void limitNextBytes(int len) {

//累計bytesWillBeSentOrReceive

this.bytesWillBeSentOrReceive += len;

//如果積累的bytesWillBeSentOrReceive達到一個chunk的大小,就進入語句塊操作

while (this.bytesWillBeSentOrReceive > CHUNK_LENGTH) {

long nowTick = System.nanoTime();

//計算積累數據期間消耗的時間

long passTime = nowTick - this.lastPieceSentOrReceiveTick;

//timeCostPerChunk表示單個塊最多需要多少納秒

//如果missedTime大于0,說明此時流量進出的速率已經超過maxRate了,需要休眠來限制流量

long missedTime = this.timeCostPerChunk - passTime;

if (missedTime > 0) {

try {

Thread.sleep(missedTime / 1000000, (int) (missedTime % 1000000));

} catch (InterruptedException e) {

LOGGER.error(e.getMessage(), e);

}

}

this.bytesWillBeSentOrReceive -= CHUNK_LENGTH;

//重置最后一次檢查時間

this.lastPieceSentOrReceiveTick = nowTick + (missedTime > 0 ? missedTime : 0);

}

}

二、限流的完整java代碼實現(xiàn)

限流器的實現(xiàn)

public class BandwidthLimiter {

private static final Logger LOGGER = LoggerFactory.getLogger(BandwidthLimiter.class);

//KB代表的字節(jié)數

private static final Long KB = 1024L;

//一個chunk的大小,單位byte。設置一個塊的大小為1M

private static final Long CHUNK_LENGTH = 1024 * 1024L;

//已經發(fā)送/讀取的字節(jié)數

private int bytesWillBeSentOrReceive = 0;

//上一次接收到字節(jié)流的時間戳——單位納秒

private long lastPieceSentOrReceiveTick = System.nanoTime();

//允許的最大速率,默認為 1024KB/s

private int maxRate = 1024;

//在maxRate的速率下,通過chunk大小的字節(jié)流要多少時間(納秒)

private long timeCostPerChunk = (1000000000L * CHUNK_LENGTH) / (this.maxRate * KB);

public BandwidthLimiter(int maxRate) {

this.setMaxRate(maxRate);

}

//動態(tài)調整最大速率

public void setMaxRate(int maxRate) {

if (maxRate < 0) {

throw new IllegalArgumentException("maxRate can not less than 0");

}

this.maxRate = maxRate;

if (maxRate == 0) {

this.timeCostPerChunk = 0;

} else {

this.timeCostPerChunk = (1000000000L * CHUNK_LENGTH) / (this.maxRate * KB);

}

}

public synchronized void limitNextBytes() {

this.limitNextBytes(1);

}

public synchronized void limitNextBytes(int len) {

this.bytesWillBeSentOrReceive += len;

while (this.bytesWillBeSentOrReceive > CHUNK_LENGTH) {

long nowTick = System.nanoTime();

long passTime = nowTick - this.lastPieceSentOrReceiveTick;

long missedTime = this.timeCostPerChunk - passTime;

if (missedTime > 0) {

try {

Thread.sleep(missedTime / 1000000, (int) (missedTime % 1000000));

} catch (InterruptedException e) {

LOGGER.error(e.getMessage(), e);

}

}

this.bytesWillBeSentOrReceive -= CHUNK_LENGTH;

this.lastPieceSentOrReceiveTick = nowTick + (missedTime > 0 ? missedTime : 0);

}

}

}

有了限流器后,現(xiàn)在我們要對下載功能做限流。因為java的io流的設計是裝飾器模式,因此我們可以方便的封裝一個我們自己的InputStream

public class LimitInputStream extends InputStream {

private InputStream inputStream;

private BandwidthLimiter bandwidthLimiter;

public LimitInputStream(InputStream inputStream, BandwidthLimiter bandwidthLimiter) {

this.inputStream = inputStream;

this.bandwidthLimiter = bandwidthLimiter;

}

@Override

public int read() throws IOException {

if (bandwidthLimiter != null) {

bandwidthLimiter.limitNextBytes();

}

return inputStream.read();

}

@Override

public int read(byte[] b, int off, int len) throws IOException {

if (bandwidthLimiter != null) {

bandwidthLimiter.limitNextBytes(len);

}

return inputStream.read(b, off, len);

}

@Override

public int read(byte[] b) throws IOException {

if (bandwidthLimiter != null && b.length > 0) {

bandwidthLimiter.limitNextBytes(b.length);

}

return inputStream.read(b);

}

}

后面我們使用這個LimitInputStream來讀取文件,每次讀取一塊數據,限流器都會檢查當前的速率是否超過指定的最大速率。這樣就能間接的達到限制下載速率的目的了。

附上SpringMVC的一個下載限流的demo:

@GetMapping("/limit")

public void limitDownloadFile(String file, HttpServletResponse response) throws IOException {

LOGGER.info("download file");

if (file == null) {

file = "/tmp/test.txt";

}

File downloadFile = new File(file);

FileInputStream fileInputStream = new FileInputStream(downloadFile);

response.setContentType("application/x-msdownload;");

response.setHeader("Content-disposition", "attachment; filename=" + new String(downloadFile.getName()

.getBytes("utf-8"), "ISO8859-1"));

response.setHeader("Content-Length", String.valueOf(downloadFile.length()));

ServletOutputStream outputStream = null;

try {

LimitInputStream limitInputStream = new LimitInputStream(fileInputStream, new BandwidthLimiter(1024));

long beginTime = System.currentTimeMillis();

outputStream = response.getOutputStream();

byte[] bytes = new byte[1024];

int read = limitInputStream.read(bytes, 0, 1024);

while (read != -1) {

outputStream.write(bytes);

read = limitInputStream.read(bytes, 0, 1024);

}

LOGGER.info("download use {} ms", System.currentTimeMillis() - beginTime);

} finally {

fileInputStream.close();

if (outputStream != null) {

outputStream.close();

}

LOGGER.info("download success!");

}

}

三、注意點

使用這個算法要注意一個問題,就是chunk的塊大小不能設置的太小,即CHUNK_LENGTH不能設置的太小。否則容易造成明明maxRate設置的很大,但是實際下載速率卻很小的問題。

假設CHUNK_LENGTH就設置為1024 bytes,每次讀取的塊大小也是1024 bytes,maxRate 為 64M/s。那么我們可以計算出timeCostPerChunk約等于15258納秒。

再如果真正的速率是100M/s,也就是每秒差不多會調用limitNextBytes方法100000次,由于每次讀取消耗的時間極短,因此每次進入該方法都要sleep 15258納秒之后再讀取下一個塊的數據。如果沒有算上線程調度的時間,就算1秒內休眠100000次也完全沒什么問題。但是線程的休眠和喚醒都需要內核來進行,線程上下文切換的時間應該遠大于15258納秒,這時候頻繁的休眠就會導致線程暫停運行的時間和我們預期的不符。由于休眠時間過長,最終導致實際的下載速率大大的低于maxRate。

因此,我們需要調大CHUNK_LENGTH,盡量讓timeCostPerChunk的值遠大于線程調度的時間,減少線程調度對限流造成的影響。

四、具體demo的github地址

總結

以上是生活随笔為你收集整理的java 下载限速_Java 文件下载限流算法的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。