﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>BlogJava-Jelver'Sky-文章分类-流媒体服务器</title><link>http://www.blogjava.net/jelver/category/31068.html</link><description>“一直很多人说我是天才，但我却相信这个世界没有天才。如果没有卓越的努力和娴熟的球性，你永远不会踢出精彩的足球。我从来不会低估努力的重要性，而这正是我对自己的要求。”
----------------------------------------------------------------------------罗纳尔迪尼奥(Ronaldinho)--------
“不要叫我神童！我不是神童！我所获得的一切成就，都是我用汗水和刻苦训练换来的！” --------------------------丁俊辉（中国斯诺克公开赛冠军）---
</description><language>zh-cn</language><lastBuildDate>Fri, 16 May 2008 04:49:32 GMT</lastBuildDate><pubDate>Fri, 16 May 2008 04:49:32 GMT</pubDate><ttl>60</ttl><item><title>用Java实现断点续传(HTTP)</title><link>http://www.blogjava.net/jelver/articles/200827.html</link><dc:creator>冰河快狼</dc:creator><author>冰河快狼</author><pubDate>Fri, 16 May 2008 03:12:00 GMT</pubDate><guid>http://www.blogjava.net/jelver/articles/200827.html</guid><wfw:comment>http://www.blogjava.net/jelver/comments/200827.html</wfw:comment><comments>http://www.blogjava.net/jelver/articles/200827.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/jelver/comments/commentRss/200827.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/jelver/services/trackbacks/200827.html</trackback:ping><description><![CDATA[<blockquote>本文介绍了一种利用 Java 来实现断点续传的方法。</blockquote><!--start RESERVED FOR FUTURE USE INCLUDE FILES--><!-- include java script once we verify teams wants to use this and it will work on dbcs and cyrillic characters --><!--end RESERVED FOR FUTURE USE INCLUDE FILES-->
<p><a name="1"><span class="atitle">断点续传的原理</span></a>&nbsp;<a href="http://www.ibm.com/developerworks/cn/java/joy-down/index.html"><font color="#000000">原文</font>http://www.ibm.com/developerworks/cn/java/joy-down/index.html</a></p>
<p>其实断点续传的原理很简单，就是在Http的请求上和一般的下载有所不同而已。 <br />
打个比方，浏览器请求服务器上的一个文时，所发出的请求如下： <br />
假设服务器域名为wwww.sjtu.edu.cn，文件名为down.zip。 <br />
GET /down.zip HTTP/1.1 <br />
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms- <br />
excel, application/msword, application/vnd.ms-powerpoint, */* <br />
Accept-Language: zh-cn <br />
Accept-Encoding: gzip, deflate <br />
User-Agent: Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0) <br />
Connection: Keep-Alive </p>
<p>服务器收到请求后，按要求寻找请求的文件，提取文件的信息，然后返回给浏览器，返回信息如下：</p>
<p>200 <br />
Content-Length=106786028 <br />
Accept-Ranges=bytes <br />
Date=Mon, 30 Apr 2001 12:56:11 GMT <br />
ETag=W/"02ca57e173c11:95b" <br />
Content-Type=application/octet-stream <br />
Server=Microsoft-IIS/5.0 <br />
Last-Modified=Mon, 30 Apr 2001 12:56:11 GMT </p>
<p>所谓断点续传，也就是要从文件已经下载的地方开始继续下载。所以在客户端浏览器传给 Web服务器的时候要多加一条信息--从哪里开始。 <br />
下面是用自己编的一个"浏览器"来传递请求信息给Web服务器，要求从2000070字节开始。 <br />
GET /down.zip HTTP/1.0 <br />
User-Agent: NetFox <br />
RANGE: bytes=2000070- <br />
Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2 </p>
<p>仔细看一下就会发现多了一行RANGE: bytes=2000070- <br />
这一行的意思就是告诉服务器down.zip这个文件从2000070字节开始传，前面的字节不用传了。 <br />
服务器收到这个请求以后，返回的信息如下： <br />
206 <br />
Content-Length=106786028 <br />
Content-Range=bytes 2000070-106786027/106786028 <br />
Date=Mon, 30 Apr 2001 12:55:20 GMT <br />
ETag=W/"02ca57e173c11:95b" <br />
Content-Type=application/octet-stream <br />
Server=Microsoft-IIS/5.0 <br />
Last-Modified=Mon, 30 Apr 2001 12:55:20 GMT </p>
<p>和前面服务器返回的信息比较一下，就会发现增加了一行： <br />
Content-Range=bytes 2000070-106786027/106786028 <br />
返回的代码也改为206了，而不再是200了。 </p>
<p>知道了以上原理，就可以进行断点续传的编程了。 </p>
<br />
<table cellspacing="0" cellpadding="0" width="100%" border="0">
    <tbody>
        <tr>
            <td><img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" /><br />
            <img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" /></td>
        </tr>
    </tbody>
</table>
<table class="no-print" cellspacing="0" cellpadding="0" align="right">
    <tbody>
        <tr align="right">
            <td><img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" /><br />
            <table cellspacing="0" cellpadding="0" border="0">
                <tbody>
                    <tr>
                        <td valign="middle"><img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" /><br />
                        </td>
                        <td valign="top" align="right"><a class="fbox" href="http://www.ibm.com/developerworks/cn/java/joy-down/index.html#main" cmimpressionsent="1"><strong>回页首</strong></a></td>
                    </tr>
                </tbody>
            </table>
            </td>
        </tr>
    </tbody>
</table>
<br />
<br />
<p><a name="2"><span class="atitle">Java实现断点续传的关键几点</span></a></p>
<ol>
    <li>(1)用什么方法实现提交RANGE: bytes=2000070-。 <br />
    当然用最原始的Socket是肯定能完成的，不过那样太费事了，其实Java的net包中提供了这种功能。代码如下： <br />
    <br />
    URL url = new URL("http://www.sjtu.edu.cn/down.zip"); <br />
    HttpURLConnection httpConnection = (HttpURLConnection)url.openConnection(); <br />
    <br />
    //设置User-Agent <br />
    httpConnection.setRequestProperty("User-Agent","NetFox"); <br />
    //设置断点续传的开始位置 <br />
    httpConnection.setRequestProperty("RANGE","bytes=2000070"); <br />
    //获得输入流 <br />
    InputStream input = httpConnection.getInputStream(); <br />
    <p>从输入流中取出的字节流就是down.zip文件从2000070开始的字节流。大家看，其实断点续传用Java实现起来还是很简单的吧。接下来要做的事就是怎么保存获得的流到文件中去了。 </p>
    <li>保存文件采用的方法。 <br />
    我采用的是IO包中的RandAccessFile类。 <br />
    操作相当简单，假设从2000070处开始保存文件，代码如下： <br />
    RandomAccess oSavedFile = new RandomAccessFile("down.zip","rw"); <br />
    long nPos = 2000070; <br />
    //定位文件指针到nPos位置 <br />
    oSavedFile.seek(nPos); <br />
    byte[] b = new byte[1024]; <br />
    int nRead; <br />
    //从输入流中读入字节流，然后写到文件中 <br />
    while((nRead=input.read(b,0,1024)) &gt; 0) <br />
    { <br />
    oSavedFile.write(b,0,nRead); <br />
    } <br />
    </li>
</ol>
<p>怎么样，也很简单吧。接下来要做的就是整合成一个完整的程序了。包括一系列的线程控制等等。 </p>
<br />
<table cellspacing="0" cellpadding="0" width="100%" border="0">
    <tbody>
        <tr>
            <td><img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" /><br />
            <img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" /></td>
        </tr>
    </tbody>
</table>
<table class="no-print" cellspacing="0" cellpadding="0" align="right">
    <tbody>
        <tr align="right">
            <td><img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" /><br />
            <table cellspacing="0" cellpadding="0" border="0">
                <tbody>
                    <tr>
                        <td valign="middle"><img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" /><br />
                        </td>
                        <td valign="top" align="right"><a class="fbox" href="http://www.ibm.com/developerworks/cn/java/joy-down/index.html#main" cmimpressionsent="1"><strong>回页首</strong></a></td>
                    </tr>
                </tbody>
            </table>
            </td>
        </tr>
    </tbody>
</table>
<br />
<br />
<p><a name="3"><span class="atitle">断点续传内核的实现</span></a></p>
<p>主要用了6个类，包括一个测试类。 <br />
SiteFileFetch.java负责整个文件的抓取，控制内部线程(FileSplitterFetch类)。 <br />
FileSplitterFetch.java负责部分文件的抓取。 <br />
FileAccess.java负责文件的存储。 <br />
SiteInfoBean.java要抓取的文件的信息，如文件保存的目录，名字，抓取文件的URL等。 <br />
Utility.java工具类，放一些简单的方法。 <br />
TestMethod.java测试类。 <br />
</p>
<p>下面是源程序：</p>
<table cellspacing="0" cellpadding="0" width="100%" border="0">
    <tbody>
        <tr>
            <td class="code-outline">
            <pre class="displaycode">/*
            **SiteFileFetch.java
            */
            package NetFox;
            import java.io.*;
            import java.net.*;
            public class SiteFileFetch extends Thread {
            SiteInfoBean siteInfoBean = null; //文件信息Bean
            long[] nStartPos; //开始位置
            long[] nEndPos; //结束位置
            FileSplitterFetch[] fileSplitterFetch; //子线程对象
            long nFileLength; //文件长度
            boolean bFirst = true; //是否第一次取文件
            boolean bStop = false; //停止标志
            File tmpFile; //文件下载的临时信息
            DataOutputStream output; //输出到文件的输出流
            public SiteFileFetch(SiteInfoBean bean) throws IOException
            {
            siteInfoBean = bean;
            //tmpFile = File.createTempFile ("zhong","1111",new File(bean.getSFilePath()));
            tmpFile = new File(bean.getSFilePath()+File.separator + bean.getSFileName()+".info");
            if(tmpFile.exists ())
            {
            bFirst = false;
            read_nPos();
            }
            else
            {
            nStartPos = new long[bean.getNSplitter()];
            nEndPos = new long[bean.getNSplitter()];
            }
            }
            public void run()
            {
            //获得文件长度
            //分割文件
            //实例FileSplitterFetch
            //启动FileSplitterFetch线程
            //等待子线程返回
            try{
            if(bFirst)
            {
            nFileLength = getFileSize();
            if(nFileLength == -1)
            {
            System.err.println("File Length is not known!");
            }
            else if(nFileLength == -2)
            {
            System.err.println("File is not access!");
            }
            else
            {
            for(int i=0;i&lt;nStartPos.length;i++)
            {
            nStartPos[i] = (long)(i*(nFileLength/nStartPos.length));
            }
            for(int i=0;i&lt;nEndPos.length-1;i++)
            {
            nEndPos[i] = nStartPos[i+1];
            }
            nEndPos[nEndPos.length-1] = nFileLength;
            }
            }
            //启动子线程
            fileSplitterFetch = new FileSplitterFetch[nStartPos.length];
            for(int i=0;i&lt;nStartPos.length;i++)
            {
            fileSplitterFetch[i] = new FileSplitterFetch(siteInfoBean.getSSiteURL(),
            siteInfoBean.getSFilePath() + File.separator + siteInfoBean.getSFileName(),
            nStartPos[i],nEndPos[i],i);
            Utility.log("Thread " + i + " , nStartPos = " + nStartPos[i] + ", nEndPos = " + nEndPos[i]);
            fileSplitterFetch[i].start();
            }
            // fileSplitterFetch[nPos.length-1] = new FileSplitterFetch(siteInfoBean.getSSiteURL(),
            siteInfoBean.getSFilePath() + File.separator + siteInfoBean.getSFileName(),nPos[nPos.length-1],nFileLength,nPos.length-1);
            // Utility.log("Thread " + (nPos.length-1) + " , nStartPos = " + nPos[nPos.length-1] + ",
            nEndPos = " + nFileLength);
            // fileSplitterFetch[nPos.length-1].start();
            //等待子线程结束
            //int count = 0;
            //是否结束while循环
            boolean breakWhile = false;
            while(!bStop)
            {
            write_nPos();
            Utility.sleep(500);
            breakWhile = true;
            for(int i=0;i&lt;nStartPos.length;i++)
            {
            if(!fileSplitterFetch[i].bDownOver)
            {
            breakWhile = false;
            break;
            }
            }
            if(breakWhile)
            break;
            //count++;
            //if(count&gt;4)
            // siteStop();
            }
            System.err.println("文件下载结束！");
            }
            catch(Exception e){e.printStackTrace ();}
            }
            //获得文件长度
            public long getFileSize()
            {
            int nFileLength = -1;
            try{
            URL url = new URL(siteInfoBean.getSSiteURL());
            HttpURLConnection httpConnection = (HttpURLConnection)url.openConnection ();
            httpConnection.setRequestProperty("User-Agent","NetFox");
            int responseCode=httpConnection.getResponseCode();
            if(responseCode&gt;=400)
            {
            processErrorCode(responseCode);
            return -2; //-2 represent access is error
            }
            String sHeader;
            for(int i=1;;i++)
            {
            //DataInputStream in = new DataInputStream(httpConnection.getInputStream ());
            //Utility.log(in.readLine());
            sHeader=httpConnection.getHeaderFieldKey(i);
            if(sHeader!=null)
            {
            if(sHeader.equals("Content-Length"))
            {
            nFileLength = Integer.parseInt(httpConnection.getHeaderField(sHeader));
            break;
            }
            }
            else
            break;
            }
            }
            catch(IOException e){e.printStackTrace ();}
            catch(Exception e){e.printStackTrace ();}
            Utility.log(nFileLength);
            return nFileLength;
            }
            //保存下载信息（文件指针位置）
            private void write_nPos()
            {
            try{
            output = new DataOutputStream(new FileOutputStream(tmpFile));
            output.writeInt(nStartPos.length);
            for(int i=0;i&lt;nStartPos.length;i++)
            {
            // output.writeLong(nPos[i]);
            output.writeLong(fileSplitterFetch[i].nStartPos);
            output.writeLong(fileSplitterFetch[i].nEndPos);
            }
            output.close();
            }
            catch(IOException e){e.printStackTrace ();}
            catch(Exception e){e.printStackTrace ();}
            }
            //读取保存的下载信息（文件指针位置）
            private void read_nPos()
            {
            try{
            DataInputStream input = new DataInputStream(new FileInputStream(tmpFile));
            int nCount = input.readInt();
            nStartPos = new long[nCount];
            nEndPos = new long[nCount];
            for(int i=0;i&lt;nStartPos.length;i++)
            {
            nStartPos[i] = input.readLong();
            nEndPos[i] = input.readLong();
            }
            input.close();
            }
            catch(IOException e){e.printStackTrace ();}
            catch(Exception e){e.printStackTrace ();}
            }
            private void processErrorCode(int nErrorCode)
            {
            System.err.println("Error Code : " + nErrorCode);
            }
            //停止文件下载
            public void siteStop()
            {
            bStop = true;
            for(int i=0;i&lt;nStartPos.length;i++)
            fileSplitterFetch[i].splitterStop();
            }
            }
            /*
            **FileSplitterFetch.java
            */
            package NetFox;
            import java.io.*;
            import java.net.*;
            public class FileSplitterFetch extends Thread {
            String sURL; //File URL
            long nStartPos; //File Snippet Start Position
            long nEndPos; //File Snippet End Position
            int nThreadID; //Thread's ID
            boolean bDownOver = false; //Downing is over
            boolean bStop = false; //Stop identical
            FileAccessI fileAccessI = null; //File Access interface
            public FileSplitterFetch(String sURL,String sName,long nStart,long nEnd,int id) throws IOException
            {
            this.sURL = sURL;
            this.nStartPos = nStart;
            this.nEndPos = nEnd;
            nThreadID = id;
            fileAccessI = new FileAccessI(sName,nStartPos);
            }
            public void run()
            {
            while(nStartPos &lt; nEndPos &amp;&amp; !bStop)
            {
            try{
            URL url = new URL(sURL);
            HttpURLConnection httpConnection = (HttpURLConnection)url.openConnection ();
            httpConnection.setRequestProperty("User-Agent","NetFox");
            String sProperty = "bytes="+nStartPos+"-";
            httpConnection.setRequestProperty("RANGE",sProperty);
            Utility.log(sProperty);
            InputStream input = httpConnection.getInputStream();
            //logResponseHead(httpConnection);
            byte[] b = new byte[1024];
            int nRead;
            while((nRead=input.read(b,0,1024)) &gt; 0 &amp;&amp; nStartPos &lt; nEndPos &amp;&amp; !bStop)
            {
            nStartPos += fileAccessI.write(b,0,nRead);
            //if(nThreadID == 1)
            // Utility.log("nStartPos = " + nStartPos + ", nEndPos = " + nEndPos);
            }
            Utility.log("Thread " + nThreadID + " is over!");
            bDownOver = true;
            //nPos = fileAccessI.write (b,0,nRead);
            }
            catch(Exception e){e.printStackTrace ();}
            }
            }
            //打印回应的头信息
            public void logResponseHead(HttpURLConnection con)
            {
            for(int i=1;;i++)
            {
            String header=con.getHeaderFieldKey(i);
            if(header!=null)
            //responseHeaders.put(header,httpConnection.getHeaderField(header));
            Utility.log(header+" : "+con.getHeaderField(header));
            else
            break;
            }
            }
            public void splitterStop()
            {
            bStop = true;
            }
            }
            /*
            **FileAccess.java
            */
            package NetFox;
            import java.io.*;
            public class FileAccessI implements Serializable{
            RandomAccessFile oSavedFile;
            long nPos;
            public FileAccessI() throws IOException
            {
            this("",0);
            }
            public FileAccessI(String sName,long nPos) throws IOException
            {
            oSavedFile = new RandomAccessFile(sName,"rw");
            this.nPos = nPos;
            oSavedFile.seek(nPos);
            }
            public synchronized int write(byte[] b,int nStart,int nLen)
            {
            int n = -1;
            try{
            oSavedFile.write(b,nStart,nLen);
            n = nLen;
            }
            catch(IOException e)
            {
            e.printStackTrace ();
            }
            return n;
            }
            }
            /*
            **SiteInfoBean.java
            */
            package NetFox;
            public class SiteInfoBean {
            private String sSiteURL; //Site's URL
            private String sFilePath; //Saved File's Path
            private String sFileName; //Saved File's Name
            private int nSplitter; //Count of Splited Downloading File
            public SiteInfoBean()
            {
            //default value of nSplitter is 5
            this("","","",5);
            }
            public SiteInfoBean(String sURL,String sPath,String sName,int nSpiltter)
            {
            sSiteURL= sURL;
            sFilePath = sPath;
            sFileName = sName;
            this.nSplitter = nSpiltter;
            }
            public String getSSiteURL()
            {
            return sSiteURL;
            }
            public void setSSiteURL(String value)
            {
            sSiteURL = value;
            }
            public String getSFilePath()
            {
            return sFilePath;
            }
            public void setSFilePath(String value)
            {
            sFilePath = value;
            }
            public String getSFileName()
            {
            return sFileName;
            }
            public void setSFileName(String value)
            {
            sFileName = value;
            }
            public int getNSplitter()
            {
            return nSplitter;
            }
            public void setNSplitter(int nCount)
            {
            nSplitter = nCount;
            }
            }
            /*
            **Utility.java
            */
            package NetFox;
            public class Utility {
            public Utility()
            {
            }
            public static void sleep(int nSecond)
            {
            try{
            Thread.sleep(nSecond);
            }
            catch(Exception e)
            {
            e.printStackTrace ();
            }
            }
            public static void log(String sMsg)
            {
            System.err.println(sMsg);
            }
            public static void log(int sMsg)
            {
            System.err.println(sMsg);
            }
            }
            /*
            **TestMethod.java
            */
            package NetFox;
            public class TestMethod {
            public TestMethod()
            { ///xx/weblogic60b2_win.exe
            try{
            SiteInfoBean bean = new SiteInfoBean("http://localhost/xx/weblogic60b2_win.exe","L:\\temp","weblogic60b2_win.exe",5);
            //SiteInfoBean bean = new SiteInfoBean("http://localhost:8080/down.zip","L:\\temp","weblogic60b2_win.exe",5);
            SiteFileFetch fileFetch = new SiteFileFetch(bean);
            fileFetch.start();
            }
            catch(Exception e){e.printStackTrace ();}
            }
            public static void main(String[] args)
            {
            new TestMethod();
            }
            }
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<img src ="http://www.blogjava.net/jelver/aggbug/200827.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/jelver/" target="_blank">冰河快狼</a> 2008-05-16 11:12 <a href="http://www.blogjava.net/jelver/articles/200827.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>从LiveJournal后台发展看大规模网站性能优化方法</title><link>http://www.blogjava.net/jelver/articles/196656.html</link><dc:creator>冰河快狼</dc:creator><author>冰河快狼</author><pubDate>Sun, 27 Apr 2008 14:15:00 GMT</pubDate><guid>http://www.blogjava.net/jelver/articles/196656.html</guid><wfw:comment>http://www.blogjava.net/jelver/comments/196656.html</wfw:comment><comments>http://www.blogjava.net/jelver/articles/196656.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/jelver/comments/commentRss/196656.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/jelver/services/trackbacks/196656.html</trackback:ping><description><![CDATA[<h2>一、LiveJournal发展历程</h2>
<a href="http://www.livejournal.com/">LiveJournal</a>是99年始于校园中的项目，几个人出于爱好做了这样一个应用，以实现以下功能：
<ul>
    <li>博客，论坛
    <li>社会性网络，找到朋友
    <li>聚合，把朋友的文章聚合在一起</li>
</ul>
LiveJournal采用了大量的开源软件，甚至它本身也是一个开源软件。
<p>在上线后，LiveJournal实现了非常快速的增长：</p>
<ul>
    <li>2004年4月份：280万注册用户。
    <li>2005年4月份：680万注册用户。
    <li>2005年8月份：790万注册用户。
    <li>达到了每秒钟上千次的页面请求及处理。
    <li>使用了大量MySQL服务器。
    <li>使用了大量通用组件。</li>
</ul>
<h2>二、LiveJournal架构现状概况</h2>
<p><img height="445" alt="livejournal_backend.png" src="http://www.example.net.cn/archives/livejournal_backend.png" width="600" /><br />
<br />
<h2>三、从LiveJournal发展中学习</h2>
<p>&nbsp;</p>
<p>LiveJournal从1台服务器发展到100台服务器，这其中经历了无数的伤痛，但同时也摸索出了解决这些问题的方法，通过对LiveJournal的学习，可以让我们避免LJ曾经犯过的错误，并且从一开始就对系统进行良好的设计，以避免后期的痛苦。</p>
<p>下面我们一步一步看LJ发展的脚步。</p>
<div id="a000073more">
<div id="more">
<h2>1、一台服务器</h2>
<p>一台别人捐助的服务器，LJ最初就跑在上面，就像Google开始时候用的破服务器一样，值得我们尊敬。这个阶段，LJ的人以惊人的速度熟悉的Unix的操作管理，服务器性能出现过问题，不过还好，可以通过一些小修小改应付过去。在这个阶段里LJ把CGI升级到了FastCGI。</p>
<p>最终问题出现了，网站越来越慢，已经无法通过优过化来解决的地步，需要更多的服务器，这时LJ开始提供付费服务，可能是想通过这些钱来购买新的服务器，以解决当时的困境。<br />
毫无疑问，当时LJ存在巨大的单点问题，所有的东西都在那台服务器的铁皮盒子里装着。</p>
<p><img height="187" alt="LJ-backend-7.png" src="http://www.example.net.cn/archives/LJ-backend-7.png" width="500" /></p>
<h2>2、两台服务器</h2>
<p>用付费服务赚来的钱LJ买了两台服务器：一台叫做Kenny的Dell 6U机器用于提供Web服务，一台叫做Cartman的Dell 6U服务器用于提供数据库服务。</p>
<p><img height="279" alt="LJ-backend-8.png" src="http://www.example.net.cn/archives/LJ-backend-8.png" width="218" /></p>
<p>LJ有了更大的磁盘，更多的计算资源。但同时网络结构还是非常简单，每台机器两块网卡，Cartman通过内网为Kenny提供MySQL数据库服务。<br />
<br />
暂时解决了负载的问题，新的问题又出现了：</p>
<ul>
    <li>原来的一个单点变成了两个单点。
    <li>没有冷备份或热备份。
    <li>网站速度慢的问题又开始出现了，没办法，增长太快了。
    <li>Web服务器上CPU达到上限，需要更多的Web服务器。</li>
</ul>
<h2>3、四台服务器</h2>
<p>又买了两台，Kyle和Stan，这次都是1U的，都用于提供Web服务。目前LJ一共有3台Web服务器和一台数据库服务器。这时需要在3台Web服务器上进行负载均横。</p>
<p><img height="241" alt="LJ-backend-9.png" src="http://www.example.net.cn/archives/LJ-backend-9.png" width="499" /></p>
<p>LJ把Kenny用于外部的网关，使用mod_backhand进行负载均横。</p>
<p>然后问题又出现了：</p>
<ul>
    <li>单点故障。数据库和用于做网关的Web服务器都是单点，一旦任何一台机器出现问题将导致所有服务不可用。虽然用于做网关的Web服务器可以通过保持心跳同步迅速切换，但还是无法解决数据库的单点，LJ当时也没做这个。
    <li>网站又变慢了，这次是因为IO和数据库的问题，问题是怎么往应用里面添加数据库呢？</li>
</ul>
<h2>4、五台服务器</h2>
<p>又买了一台数据库服务器。在两台数据库服务器上使用了数据库同步(Mysql支持的Master-Slave模式)，写操作全部针对主数据库（通过Binlog，主服务器上的写操作可以迅速同步到从服务器上），读操作在两个数据库上同时进行(也算是负载均横的一种吧)。</p>
<p><img height="265" alt="LJ-backend-10.png" src="http://www.example.net.cn/archives/LJ-backend-10.png" width="500" /></p>
<p>实现同步时要注意几个事项：</p>
<ul>
    <li>读操作数据库选择算法处理，要选一个当前负载轻一点的数据库。
    <li>在从数据库服务器上只能进行读操作
    <li>准备好应对同步过程中的延迟，处理不好可能会导致数据库同步的中断。只需要对写操作进行判断即可，读操作不存在同步问题。</li>
</ul>
<h2>5、更多服务器</h2>
<p>有钱了，当然要多买些服务器。部署后快了没多久，又开始慢了。这次有更多的Web服务器，更多的数据库服务器，存在 IO与CPU争用。于是采用了BIG-IP作为负载均衡解决方案。</p>
<p><img height="483" alt="LJ-backend-11.png" src="http://www.example.net.cn/archives/LJ-backend-11.png" width="453" /></p>
<h2>6、现在我们在哪里：</h2>
<p><img height="357" alt="LJ-backend-1.png" src="http://www.example.net.cn/archives/LJ-backend-1.png" width="600" /></p>
<p>现在服务器基本上够了，但性能还是有问题，原因出在架构上。</p>
<p>数据库的架构是最大的问题。由于增加的数据库都是以Slave模式添加到应用内，这样唯一的好处就是将读操作分布到了多台机器，但这样带来的后果就是写操作被大量分发，每台机器都要执行，服务器越多，浪费就越大，随着写操作的增加，用于服务读操作的资源越来越少。</p>
<p><img height="195" alt="LJ-backend-2.png" src="http://www.example.net.cn/archives/LJ-backend-2.png" width="500" /></p>
<p>由一台分布到两台</p>
<p><img height="273" alt="LJ-backend-3.png" src="http://www.example.net.cn/archives/LJ-backend-3.png" width="500" /></p>
<p>最终效果</p>
<p>现在我们发现，我们并不需要把这些数据在如此多的服务器上都保留一份。服务器上已经做了RAID，数据库也进行了备份，这么多的备份完全是对资源的浪费，属于冗余极端过度。那为什么不把数据分布存储呢？</p>
<p>问题发现了，开始考虑如何解决。现在要做的就是把不同用户的数据分布到不同的服务器上进行存储，以实现数据的分布式存储，让每台机器只为相对固定的用户服务，以实现平行的架构和良好的可扩展性。</p>
<p>为了实现用户分组，我们需要为每一个用户分配一个组标记，用于标记此用户的数据存放在哪一组数据库服务器中。每组数据库由一个master及几个slave组成，并且slave的数量在2-3台，以实现系统资源的最合理分配，既保证数据读操作分布，又避免数据过度冗余以及同步操作对系统资源的过度消耗。</p>
<p><img height="324" alt="LJ-backend-4.png" src="http://www.example.net.cn/archives/LJ-backend-4.png" width="500" /></p>
<p>由一台（一组）中心服务器提供用户分组控制。所有用户的分组信息都存储在这台机器上，所有针对用户的操作需要先查询这台机器得到用户的组号，然后再到相应的数据库组中获取数据。</p>
<p>这样的用户架构与目前LJ的架构已经很相像了。</p>
<p>在具体的实现时需要注意几个问题：</p>
<ul>
    <li>在数据库组内不要使用自增ID，以便于以后在数据库组之间迁移用户，以实现更合理的I/O，磁盘空间及负载分布。
    <li>将userid，postid存储在全局服务器上，可以使用自增，数据库组中的相应值必须以全局服务器上的值为准。全局服务器上使用事务型数据库InnoDB。
    <li>在数据库组之间迁移用户时要万分小心，当迁移时用户不能有写操作。</li>
</ul>
<h2>7、现在我们在哪里</h2>
<p><img height="365" alt="LJ-backend-5.png" src="http://www.example.net.cn/archives/LJ-backend-5.png" width="500" /></p>
<p>问题：</p>
<ul>
    <li>一个全局主服务器，挂掉的话所有用户注册及写操作就挂掉。
    <li>每个数据库组一个主服务器，挂掉的话这组用户的写操作就挂掉。
    <li>数据库组从服务器挂掉的话会导致其它服务器负载过大。</li>
</ul>
<p>对于Master-Slave模式的单点问题，LJ采取了Master-Master模式来解决。所谓Master-Master实际上是人工实现的，并不是由MySQL直接提供的，实际上也就是两台机器同时是Master，也同时是Slave，互相同步。</p>
<p>Master-Master实现时需要注意：</p>
<ul>
    <li>一个Master出错后恢复同步，最好由服务器自动完成。
    <li>数字分配，由于同时在两台机器上写，有些ID可能会冲突。</li>
</ul>
<p>解决方案：<br />
<ul>
    <li>奇偶数分配ID，一台机器上写奇数，一台机器上写偶数
    <li>通过全局服务器进行分配(LJ采用的做法)。</li>
</ul>
<p>&nbsp;</p>
<p>Master-Master模式还有一种用法，这种方法与前一种相比，仍然保持两台机器的同步，但只有一台机器提供服务（读和写），在每天晚上的时候进行轮换，或者出现问题的时候进行切换。</p>
<h2>8、现在我们在哪里</h2>
<p><img height="349" alt="LJ-backend-6.png" src="http://www.example.net.cn/archives/LJ-backend-6.png" width="500" /></p>
<p>现在插播一条广告，MyISAM VS InnoDB。</p>
<p>使用InnoDB：</p>
<ul>
    <li>支持事务
    <li>需要做更多的配置，不过值得，可以更安全的存储数据，以及得到更快的速度。</li>
</ul>
<p>使用MyISAM：</p>
<ul>
    <li>记录日志（LJ用它来记网络访问日志）
    <li>存储只读静态数据，足够快。
    <li>并发性很差，无法同时读写数据（添加数据可以）
    <li>MySQL非正常关闭或死机时会导致索引错误，需要使用myisamchk修复，而且当访问量大时出现非常频繁。</li>
</ul>
<h2>9、缓存</h2>
<p>去年我写过<a href="http://www.example.net.cn/archives/2006/01/eoamemcachedoea.html">一篇文章介绍memcached</a>，它就是由LJ的团队开发的一款缓存工具，以key-value的方式将数据存储到分布的内存中。LJ缓存的数据：</p>
<ul>
    <li>12台独立服务器（不是捐赠的）
    <li>28个实例
    <li>30GB总容量
    <li>90-93%的命中率（用过squid的人可能知道，squid内存加磁盘的命中率大概在70-80%）</li>
</ul>
<p>如何建立缓存策略？</p>
<p>想缓存所有的东西？那是不可能的，我们只需要缓存已经或者可能导致系统瓶颈的地方，最大程度的提交系统运行效率。通过对MySQL的日志的分析我们可以找到缓存的对象。</p>
<p>缓存的缺点？</p>
<ul>
    <li>没有完美的事物，缓存也有缺点：
    <li>增大开发量，需要针对缓存处理编写特殊的代码。
    <li>管理难度增加，需要更多人参与系统维护。
    <li>当然大内存也需要钱。</li>
</ul>
<h2>10、Web访问负载均衡</h2>
<p>在数据包级别使用BIG-IP，但BIG-IP并不知道我们内部的处理机制，无法判断由哪台服务器对这些请求进行处理。反向代理并不能很好的起到作用，不是已经够快了，就是达不到我们想要的效果。</p>
<p>所以，LJ又开发了<a href="http://www.danga.com/perlbal/">Perlbal</a>。特点：</p>
<ul>
    <li>快，小，可管理的http web 服务器/代理
    <li>可以在内部进行转发
    <li>使用Perl开发
    <li>单线程，异步，基于事件，使用epoll , kqueue
    <li>支持Console管理与http远程管理，支持动态配置加载
    <li>多种模式：web服务器，反向代理，插件
    <li>支持插件：GIF/PNG互换？</li>
</ul>
<h2>11、MogileFS</h2>
<p>LJ使用开源的<a href="http://www.danga.com/mogilefs/">MogileFS</a>作为分布式文件存储系统。MogileFS使用非常简单，它的主要设计思想是：</p>
<ul>
    <li>文件属于类（类是最小的复制单位）
    <li>跟踪文件存储位置
    <li>在不同主机上存储
    <li>使用MySQL集群统一存储分布信息
    <li>大容易廉价磁盘</li>
</ul>
<p>到目前为止就这么多了，更多文档可以在<a href="http://www.danga.com/words/">http://www.danga.com/words/</a>找到。<a href="http://www.danga.com/">Danga.com</a>和<a href="http://www.livejournal.com/">LiveJournal.com</a>的同学们拿这个文档参加了两次MySQL Con，两次OS Con，以及众多的其它会议，无私的把他们的经验分享出来，值得我们学习。在web2.0时代快速开发得到大家越来越多的重视，但良好的设计仍是每一个应用的基础，希望web2.0们在成长为Top500网站的路上，不要因为架构阻碍了网站的发展。</p>
<p>参考资料：<a href="http://www.danga.com/words/2005_oscon/oscon-2005.pdf">http://www.danga.com/words/2005_oscon/oscon-2005.pdf</a></p>
<p>感谢<a href="http://nicotinestain.bokee.com/">向静</a>推荐了这篇文档给我。</p>
</div>
</div>
<img src ="http://www.blogjava.net/jelver/aggbug/196656.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/jelver/" target="_blank">冰河快狼</a> 2008-04-27 22:15 <a href="http://www.blogjava.net/jelver/articles/196656.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>电影服务器流媒体关键技术</title><link>http://www.blogjava.net/jelver/articles/196409.html</link><dc:creator>冰河快狼</dc:creator><author>冰河快狼</author><pubDate>Sun, 27 Apr 2008 02:31:00 GMT</pubDate><guid>http://www.blogjava.net/jelver/articles/196409.html</guid><wfw:comment>http://www.blogjava.net/jelver/comments/196409.html</wfw:comment><comments>http://www.blogjava.net/jelver/articles/196409.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/jelver/comments/commentRss/196409.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/jelver/services/trackbacks/196409.html</trackback:ping><description><![CDATA[<strong>引言</strong> <br />
<br />
近年来，随着计算机技术、压缩技术以及网络技术的发展，网络中的流媒体业务也得到了飞速的发展和应用。所谓流媒体就是指在 Internet/Intranet中使用流式传输技术的连续时基媒体。本文中着重介绍视频流。目前在Internet上传输视频还有许多困难，其根本原因在于Internet的无连接每包转发机制主要是为突发性的数据传输而设计的，不适用于对连续媒体流的传输。为了在Internet上有效地、高质量地传输视频流，还需要多种技术的支持，例如基于视频的压缩编码技术、应用层QoS技术、连续媒体分布服务、流服务器、媒体同步技术和相关协议等。 <br />
<br />
其中，原始视/音频经过视/音频压缩算法的预压缩存储在存储设备中。响应客户请求时，流服务器从存储设备中获得视/音频数据，应用层QoS控制模块根据网络状态和QoS要求来改变视/音频比特流。然后通过传输协议把压缩过的比特流打包并且发送到网上。由于拥塞数据包可能出现丢包或者过度时延。为了提高视 /音频的传输质量，网络中配置了连续流媒体分布式服务。对于成功传输的数据包，它们首先通过传输层，然后在进行视/音频解码前经过应用层处理。为了获得在播放中的视频和音频的同步，还需要媒体同步机制。从上图中可以看出，这六个部分有着紧密的联系而且都是流媒体结构的组成部分。<br />
<br />
<strong>2 流媒体中的关键技术<br />
<br />
</strong><br />
<br />
2.1 视频压缩及编码 <br />
<br />
目前网络是异构性的，缺乏QoS质量控制，并且带宽也在很大范围内变化。传统的不可扩展性视频编码的目标是将视频压缩成适合一个或者几个固定码率的码流，是面向存储的，因此不适合网络传输。为了适应网络带宽的变化，面向传输的可扩展性编码的思想应运而生。可扩展性编码[2]就是将多媒体数据压缩编码成多个流，其中一个可以独立解码，产生粗糙质量的视频序列，它适应最低的网络带宽，称为基本层码流；其他的码流可以按层为单位在任何地点截断，称为增强层，用来覆盖网络带宽变化的动态范围，它们不可以单独解码，而只能与基本层和它以前的增强层联合在一起解码，用来提高观看效果。因此，可扩展性码流具有一定的网络带宽适应能力。 <br />
<br />
可扩展性编码主要分为时域可扩展性编码、空域可扩展性编码和质量可扩展性编码。可以选择在时间、空间和信噪比（SNR）中的一个或者几个方面实现扩展。考虑到编码效率和复杂性两方面，MPEG组织采纳了精细可扩展性编码（FGS）和渐进的精细可扩展性编码（PFGS）[3]。精细可扩展性视频编码采用位平面（bitplane）编码，它的基本层使用基于分块运动补偿和DCT变换的编码方式达到网络传输的最低要求，增强层使用位平面编码技术对DCT残差进行编码来覆盖网络带宽的变化范围，它每一帧的增强层码流可以在任何地点截断，解码器重建的视频质量和收到并解码的比特数成正比，它可以实现连续的增强层速率控制。FGS虽然具有很好的可扩展性，但是效率太低，PFGS在保留了FGS所具有的网络带宽自适应和错误恢复能力的同时，还有效地提高了编码效率。但是可扩展性编码的效率较非可扩展性编码而言，还有一定差距。为了进一步压缩FGS和PFGS的基本层码流，有专家提出一种称为精细的空域可扩展性（Fine-Granularity Spatially Scalable,FGSS）的视频编码算法，使低分辨率和高分辨率的增强层码流都可以在任何地点截断，具有极强的网络带宽适应能力和错误恢复功能，同时保持了空域可扩展性编码的多分辨率特性，它可以满足拥有不同网络带宽和不同分辨率接收设备的许多用户的需求，性能得到了更大的提高。 <br />
<br />
结合多种视频编码技术来适应网络上的QoS波动是今后可扩展性视频编码的发展方向。比如，可扩展性视频编码可以适应网络带宽的变化；错误弹性编码可以适应丢包；DCVC（Delay Cognizant Video Coding）可以适应网络时延。这三种技术的结合可以更好地提供一种应对网络QoS波动的解决方案。<br />
<br />
2.2 应用层QoS控制技术由于目前的Internet只提供Best-effort的服务，所以需要通过应用层的机制来实现QoS的控制。QoS控制技术主要集中在对网络带宽的变化进行响应和处理分组丢失的技术上，主要可以分为两类：拥塞控制技术和差错控制技术。 <br />
<br />
拥塞控制的目的是采用某种机制应对和避免网络阻塞，降低时延和丢包率。常用的拥塞控制机制有速率控制和速率整形。对于视频流，拥塞控制的主要方法是速率控制。速率控制机制试图使一个视频连接的需求与整个连接链路的可用带宽相匹配，这样可以同时使网络拥塞和包丢失率达到最小。速率控制机制主要包括基于源端的、基于目的的以及混合速率控制。在基于源端的控制机制中，视频源端收集反馈信息，进行控制计算并采取相应的控制动作。这种方法在因特网中被率先采用,但是在异构网络中的运行情况并不是很好。基于目的端的控制机制则主要根据所接收的视频流的状况向上层反映相应的统计信息，实时调整缓冲及播放内容，并力图使节奏均匀，这种机制使用较少。混合性速率控制的方法兼有前二者的特点，即目的端增加减少通道，而源端同时根据反馈调整各个通道的速率。混合速率控制方法的一个例子是目标集分组的方法。 <br />
<br />
拥塞控制只能减少数据包的丢失，但是网络中不可避免的会存在数据包丢失，而且到达时延过大的分组也会被认为没有用而被丢弃，从而降低了视频质量。要改善视频质量就需要一定的差错控制机制。差错控制机制包括： <br />
<br />
（1）前向纠错（FEC）：FEC是通过在传输的码流中加入用于纠错的冗余信息，在遇到包丢失的情况时，利用冗余信息恢复丢失的信息。它的不足是增加了编码时延和传输带宽。 <br />
<br />
（2）延迟约束的重传。通常流的播放有时间限制，因此，仅有当重传的时间小于正常的播放时间时，重传才是有价值的。 <br />
<br />
（3）错误弹性编码（Error-Resilient Encoding）：在编码中通过适当的控制使得发生数据的丢失后能够最大限度的减少对质量的影响。在Internet环境下，最典型的方法是多描述编码（MDC）。MDC把原始的视频序列压缩成多位流，每个流对应一种描述，都可以提供可接受的视觉质量。多个描述结合起来提供更好的质量。该方法的优点是实现了对数据丢失的鲁棒性和增强的质量。其缺点是相比单描述编码（SDC），它在压缩的效率上受到影响。而且由于在多描述之间必须加入一定的相关性信息，这进一步降低了压缩的效率。 <br />
<br />
（4）错误的取消（concealment）：错误的取消是指当错误已经发生后，接受端通过一定的方法尽量削弱对人的视觉影响。主要的方法是时间和空间的插值（Interpolation）。近年来的研究还包括最大平滑恢复，运动补偿时间预测等。<br />
<br />
2.3 连续媒体分布服务 <br />
<br />
连续媒体分布服务（continuous media distribution services）的目的是在Internet 尽力服务的基础上提供QoS和高效的音/视频传输，包括网络过滤（Network Filtering）、应用层组播（Application-Level Multicast）、内容复制（Content Replication）等，下面分别进行详细介绍。<br />
<br />
网络过滤：网络过滤是拥塞控制的一种，不仅可以提高视频质量，还可以提高带宽利用率。不同于发送端的速率整形，网络过滤是在流服务器和客户端之间的传输路径上通过虚拟信道连入过滤器，该过滤器根据网络的拥塞状态实现速率的整形。网络过滤通常采用的是丢帧过滤器（frame-dropping filter），其基本方法是客户端根据网络丢包率向过滤器发送请求来增减丢帧速率，以调节媒体流的带宽。这种速率整形可以在拥塞点进行，这样可以提高速率控制的效率和拥塞控制的响应时间。<br />
<br />
应用层组播：IP层的组播存在诸如可扩展性、网络管理和对高层应用的支持（例如差错控制，流量控制和拥塞控制）等屏障。应用层组播机制打破了IP组播的一些障碍，其目的在于构建网络上的组播服务，可以以更灵活的方式实现组播控制。它允许独立的 CSPs和ASPs等建立它们的Internet组播网络，这些组播网络可以互连成为更大的媒体组播网络。媒体组播网络可以利用内容分布网络的互连，通过在不同种类的服务提供者（比如ISPs、CSPs和ASPs等）之间的应用层的对等关系来构建。媒体组播网络中每个具有组播能力的节点称为媒体桥（MediaBridge），它做为应用层的路由。每个媒体桥和一个或多个相邻的媒体桥通过明确的配置互连，这个互连建立了应用层重叠拓扑。媒体桥在媒体组播网络中用分布式应用层组播路由算法来确定一条优化的虚拟组播路径。如果网络不通或者过度拥挤，媒体组播网络会自动的根据应用层路由规则来重新确定路径。并且，只有当下游客户端需要某媒体内容时，媒体桥才会传输它。这就确保了不管客户端的数目而只有一个媒体流，从而节约了网络带宽。<br />
<br />
内容复制：内容/媒体复制是提高媒体传输系统可扩展性的一项重要技术。内容复制具有以下优点： <br />
<br />
（1）降低网络连接的带宽消耗。 <br />
<br />
（2）减轻流服务器负荷。 <br />
<br />
（3）缩短客户端时延。 <br />
<br />
（4）提高有效性。它主要有两种形式：caching（缓存）和mirroring（镜像）。镜像是把原始媒体内容拷贝到网络上其他分散的备份服务器中。用户可以从最近的备份服务器上获得媒体数据。缓存则是从原服务器中获得媒体文件，然后传输给客户端，同时在本地做备份。如果缓存中已经存在客户端需要的数据，缓存就会把本地拷贝传给用户而不是从传送原服务器中的媒体数据。<br />
<br />
2.4 流服务器视频服务器在流媒体服务中起着非常重要的作用。当视频服务器响应客户的视频流请求以后，它从存储系统读入一部分视频数据到对应于这个视频流的特定缓存中，再把缓存的内容通过网络接口发送给相应客户，保证视频流的连续输出。目前存在三种类型的视频服务器结构[4]： <br />
<br />
（1）通用主机方法。采用计算机主机作为视频服务器。它的主要功能是存储、选择、传送数据。缺点是系统成本高而且不利于发挥主机功能。 <br />
<br />
（2）紧耦合多处理机。把一些可以大量完成某指令或者专门功能的硬件单元组合成的专用系统级联起来，就构成了紧耦合多处理机实现的视频服务器。这种服务器费用低、性能高、功能强，但是扩展性较差。 <br />
<br />
（3）调谐视频服务器。这种服务器主板上有一个独特微码的嵌入式仿真器控制。通过在主板中插入更多的服务通路，可以方便地进行扩展。<br />
<br />
对于流服务器，如何更有效支持VCR交互控制功能；如何设计磁盘阵列上多媒体对象高效可靠的存储和检索；如何设计更好的可伸缩多媒体服务器；如何设计兼有奇偶和镜像特性的容错存储系统是目前研究的重点。<br />
<br />
2.5 媒体同步 <br />
<br />
所谓媒体同步是指保持一个数据流或者不同媒体流之间的时间关系。通常有三种类型的同步控制：流内（intra-stream）同步、流间（inter- stream）同步和对象间（inter-object）同步。由于网络时延，导致媒体流在传输过程中失去同步关系，媒体同步机制可以确保客户端正确地恢复媒体流的同步。媒体同步机制实际上就是在媒体内或者媒体间说明其时间关系。说明时间关系的方法有：基于间隔的方法、基于轴的方法、基于控制流的方法和基于事件的方法。对于连续媒体，应用最为广泛的说明方法是基于轴的说明或时间戳。时间戳法是在每个媒体的数据流单元中加进统一的时间戳或时间码，具有相同时间戳的信息单元将同时予以表现。在发送时，将各个媒体都按时间顺序分成单元，在同一个时间轴上，给每个单元都打上一个时间戳，处于同一时标的各个媒体单元具有相同的时间戳。在各个媒体到达终端后，让具有相同时间戳的媒体单元同时进行表现，这样就得到了媒体之间同步的效果。对与终端系统而言，同步机制包括阻止（preventive）机制和纠正（corrective）机制。前者是主要通过减小延迟和抖动来减少同步错误，后者主要是在发生同步错误之后恢复同步。考虑到Internet传输的延迟随机性，同步错误是不可避免的。因此，在接受方的错误补偿是必须的。 <br />
<br />
另外，同步多媒体集成语言SMIL（Synchronized Multimedia Integration Language）是由3W(World Wide Web Consortium)组织规定的多媒体操纵语言。可以实现多个流和文本信息在播放时的时间同步控制和空间位置布置。通过SMIL还可以实现一定的用户交互功能。<br />
<br />
2.6 流媒体相关协议<br />
<br />
2.6.1 实时传输协议（RTP）与实时传输控制协议（RTCP） <br />
<br />
RTP(Real-time Transport Protocol)和RTCP（Real-time Control Protocol）都是基于IP的应用层协议。RTP为实时音/视频数据提供端到端的传送服务，包括有效载荷类型标识、序列标号、时间标签和源标识，可以提供时间信息和实现流同步。由于TCP中重传机制会引起时延，通常RTP运行于UDP之上，但是也可以在TCP或者ATM等协议之上运行。RTP本身并不提供可靠的传送机制，也不提供流量控制或者拥塞控制，而是通过与RTCP配合使用，使传输效率最佳。RTCP用来监视服务质量和在会议过程中交换信息。它提供QoS反馈、参与者标识、控制包缩放、媒体间同步等服务。RTCP包中包含已发数据包的数量、丢失数据包数量等统计资料。服务器可以根据这些信息动态的改变传输速率甚至有效载荷类型。<br />
<br />
2.6.2 实时流协议（RTSP）RTSP（Real-time Streaming Protocol）是由RealNetworks和Netscape共同提出的一个应用层协议。它可以在媒体服务器和客户端之间建立和控制连续的音/视频媒体流，协同更低层协议RTP、RSVP等一起来提供基于Internet的整套流式服务。RTSP提供了一种可扩展框架，使得可控的、点播的实时数据的传送成为可能。它提供用于音频和视频流的&#8220;VCR模式&#8221;远程控制功能，例如暂停、快进、快退和定位。支持单播和组播。RTSP还提供选择发送通道的方法（如UDP、组播UDP和TCP）和基于RTP的发送机制。RTSP像是媒体服务器和客户端之间的&#8220;网络远程控制&#8221;，它提供多种服务，如从媒体服务器上检索媒体、邀请媒体服务器进入会议、添加媒体到现成节目。RTSP在语法和操作上类似于HTTP，因此许多HTTP的扩展机制都可以移植于RTSP上。在 RTSP中，每个节目和媒体流由RTSP URL确定，全部节目和媒体特性都在节目描述文件中给予了描述，包括编码、语言、RTSP URLs、目的地址、端口号以及其他参数。但是，不同于HTTP的无状态和非对称，RTSP是有状态的、对称的协议。RTSP的服务器保持会话状态以连接 RTSP流的请求，并且服务器和客户端都可以发出请求。<br />
<br />
2.6.3 资源预留协议（RSVP）资源预留协议[5]（Resource Reserve Protocol）是运行于传输层的一个网络控制协议。RSVP允许数据流的接受方请求特殊的端到端QoS。RSVP是非路由协议，它同路由器协同工作，在传输路径的路由器上预留必要的带宽，减少网络的时延和抖动。RSVP的流程是单一的，并不区分发送方和接受方，且支持单播和组播，适应于可变成员个数和路由。RSVP领域的发展非常迅速，但是目前它的应用只限于在测试的小Intranet网络上。<br />
<br />
<strong>3 结论</strong>技术的进步和用户的需求促进了流媒体应用的迅速发展。在远程教育、数字图书馆、电子商务、视频点播、交互电视、远程医疗、网络音/视频、实时多媒体会议等方面，流媒体技术都起到很重要的作用。本文对网络中流媒体业务的关键技术和相关协议做了研究，并探讨了未来流媒体技术发展的方向。我们相信，随着流媒体应用的不断普及，宽带流媒体技术及其应用必然会在未来的网络中发挥更重要的作用，并在一定程度上改变人们使用网络的方式。
<img src ="http://www.blogjava.net/jelver/aggbug/196409.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/jelver/" target="_blank">冰河快狼</a> 2008-04-27 10:31 <a href="http://www.blogjava.net/jelver/articles/196409.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>一种高效流媒体电影服务器的设计(转)</title><link>http://www.blogjava.net/jelver/articles/196400.html</link><dc:creator>冰河快狼</dc:creator><author>冰河快狼</author><pubDate>Sun, 27 Apr 2008 01:53:00 GMT</pubDate><guid>http://www.blogjava.net/jelver/articles/196400.html</guid><wfw:comment>http://www.blogjava.net/jelver/comments/196400.html</wfw:comment><comments>http://www.blogjava.net/jelver/articles/196400.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/jelver/comments/commentRss/196400.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/jelver/services/trackbacks/196400.html</trackback:ping><description><![CDATA[<p><strong>1　引言<br />
<br />
</strong>&nbsp;&nbsp;&nbsp;&nbsp; 随着网络技术的发展，主干网与宽带网接入技术的日臻成熟，网络视频的传输成为 Internet应用的一个亮点。为了提高视频数据在网上的传输效率，并实现视频的实时播放，流媒体技术的研究与应用得到了很大发展。其中，流媒体服务器技术在流媒体的应用中发挥了关键的作用。<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp; 传统的网络传输数据的方法是文件下载：用户需要准备大量的磁盘空间，并花大量的时间等待下载结束。但是，当数据变成海量的视频数据时，在数据处理量大，数据吞吐量高，视频播放实时性，客户连接请求数目大，连接时间长等一些情况下，传统的服务器技术已经无法高效的满足要求。面对这样的技术要求，本文从 CPU调度，资源控制内存分配，I／O总线管理这三个方面出发，综合的给出了一种高效的服务器设计方法，从而有效的提高了视频流式传输时的效率。该方法也具有很强的可扩展性，当服务器的CPU数目增加时，服务器的性能将能得到成倍的增长。<br />
<strong>2　流媒体服务器的设计</strong><br />
<br />
<strong>2．1　流媒体服务器的功能<br />
<br />
</strong>流媒体在播放前不是完全下载整个文件，而是把开始部分内容存入内存，数据流是随时传送随时播放。<br />
<br />
流媒体服务器提供的流式传输方式有两种：顺序流式传输和实时流式传输两种方式。顺序流式传输是顺序下载，在下载文件的同时用户可观看在线媒体。实时流式传输与顺序流式传输不同，实时流式传输总是实时传送，特别适合现场事件。实时流式传输必须匹配连接带宽，这意味着图像质量会因网络速度降低而变差。<br />
<br />
在流式传输时，流媒体数据具有实时性，等时性等基本特点，流服务期和客户终端要保证各种媒体间的同步关系，因此，流媒体传输对&#8220;最大延时&#8221;，&#8220;延时抖动&#8221;等QoS参数都有严格要求。<br />
<br />
<strong>2．2　流媒体服务器协议栈的设计<br />
<br />
</strong>在TCP／IP参考模型中，传输层通信协议TCP和UDP都不能满足流媒体传输的QoS要求。由于TCP协议采用滑动窗口控制机制，数据传送随着流控窗口动态的启动和关闭，难以满足流媒体实时和等时的传送要求。UDP协议的无连接特点能够提高传输速率，虽然可以在某种程度上满足流媒体的实时性要求，但是由于其本身的不可靠性，也无法满足流媒体传输的需要。<br />
<br />
针对传输层协议的矛盾，为了实现流媒体在IP上的实时传送播放，设计流媒体服务器时需要在传输层协议（TCP／UDP）和应用层之间增加一个通信控制层。在增加的通信控制层，采用相应的实时传输协议，主要有：数据流部分的实时传输协议RTP（Real－time Transport Protocol），用于控制部分的实时传输控制协议RTCP（Real－time Transport图1　流媒体服务协议栈Streaming Protocol）。RTP协议主要是用来传送实时的流媒体信息，数据报主要包括多媒体数据，以及所携带负载的时间戳，顺序号等。RTCP协议的数据报主要包括了接收者收到某个多媒体流的服务质量信息，用于对服务器端的反馈。<br />
<br />
流媒体服务器的协议栈如图1所示。 </p>
<p align="center"><img src="http://www.chinavideo.org/images/stories/news/200610/20061010_01.jpg" align="baseline" border="0"  alt="" /></p>
<p><br />
<br />
当服务器收到RTSP请求，它首先产生RTSP请求对象。服务器通过RTSP协议的应答信息将请求的内容以流会话（streaming session）的形式描述，内容包括数据流包含多少个流、媒体类型、和编解码格式。一个流会话由一个或多个数据流组成，如视频流和音频流等。实际的数据流通过RTP协议传递到客户端。RTP在一对一或一对多的传输情况下工作，其目的是提供时间信息和实现流同步。RTP本身并不能为顺序传送数据包提供可靠的传送机制，它依靠RTCP一起提供流量控制和拥塞控制服务。在RTP会话期间，各连接者监视下层网络的性能，并将相关信息放入RTCP包，周期性地传送 RTCP包来通知发送方。发送方也可以用RTCP包提供每次的会话信息，包中含有已发送的数据包的数量、丢失的数据包的数量等统计资料。因此服务器可以利用这些信息动态地改变传输速率，甚至改变有效载荷类型。RTP和RTCP配合使用，因有效的反馈和最小的开销使传输效率最佳化。<br />
<br />
流媒体服务器的功能框图如图2所示。</p>
<p align="center"><img src="http://www.chinavideo.org/images/stories/news/200610/20061010_02.jpg" align="baseline" border="0"  alt="" /></p>
<p><br />
<br />
<strong>2．3　高效流媒体服务器的设计<br />
<br />
</strong>通过流媒体服务器的协议栈的设计，可以明确流媒体服务器是在传输层协议（TCP，UDP）上解释RTP，RTCP，RTSP协议的，所有的客户连接请求都是以TCP的端口获得的，流媒体数据也都是打成RTP包，通过UDP端口发出去的，因此，对于TCP，UDP端口事件的调度以及如何把大量的流媒体数据从磁盘空间传递到网络上成为制约流媒体服务器性能的主要因素。<br />
<br />
流媒体服务器面对一个单一的客户，完成的过程如下：<br />
<br />
1）在客户端发出RTSP连接请求后，服务器通过对TCP端口的监听，读入请求。<br />
<br />
2）解析请求内容，调入相应的流媒体文件。<br />
<br />
3）形成RTP包，分发数据流包，获得RTCP包。<br />
<br />
4）数据包发送完毕，关闭连接。<br />
<br />
上述过程如果采用传统的服务器设计方法实现，一般的办法是用一个线程不断的读入用户的连接请求，然后将这些请求分派给其它的工作线程，这些工作线程则分别循环往复的完成以下的工作：<br />
<br />
1）解析请求内容。<br />
<br />
2）发送RTP包，发送接收RTCP包<br />
<br />
3）判断数据发送完毕，关闭连接。服务器的结构如图3所示。</p>
<p align="center"><img src="http://www.chinavideo.org/images/stories/news/200610/20061010_03.jpg" align="baseline" border="0"  alt="" /></p>
<p><br />
<br />
操作系统在对流媒体服务器的功能实现上采用以应用进程（线程）为中心的系统资源管理方式。操作系统采用虚拟内存方式，应用程序的虚拟内存空间映射到物理内存，物理内存与CPU之间有Cache。当流媒体数据从磁盘上到网络上进行传递时，要在不同的系统空间中进行多次传递拷贝，如图4所示。</p>
<p align="center"><img src="http://www.chinavideo.org/images/stories/news/200610/20061010_04.jpg" align="baseline" border="0"  alt="" /></p>
<p><br />
<br />
图4显示了磁盘数据到网络接口的传递路径：首先，数据对象从磁盘拷贝到内存中，然后，数据在内存中由于每个工作线程的空间不同，进行空间拷贝，即从核心空间拷贝到进程空间，接着，数据再从进程空间拷贝到核心空间，最后数据再拷贝到网络上。由于流媒体数据要经过RTP的打包处理，因此，数据在拷贝到网络上之前，还要拷贝到Cache，再从Cache中拷贝到CPU寄存器，进行打包处理。<br />
<br />
从图3可以看出，随着工作线程数n的增大，系统在各线程之间的切换开销将急剧增加，从操作系统的角度来看，由于每个线程都涉及对硬盘视频数据的读取，各自独立的线程越多，对硬盘读写的数据量就越大，数据在不同空间的拷贝增加，操作系统将不断的进行页面切换，服务器的性能随着客户连接数的增多，效率将急剧下降。<br />
<br />
为了改善上述设计的缺点，需重新设计服务器的服务程序结构。根据服务器在流媒体传输中所要完成的功能，可以看出，&lt;服务器在客户有连接请求时，解析连接请求和关闭连接请求是很短的过程服务器的大部分时间都用于给每个客户发送RTP数据流包。发送RTP数据流包包括服务器把相应的媒体文件调入内存，打包发送，以及获得RTCP包的反馈。因此，可以设计一个单独的处理线程，专门用于给客户发送RTP的数据流包。<br />
<br />
改进的流媒体服务器设计如图5所示，完成主要的功能通过两个线程来实现。<br />
<br />
事件线程负责检测客户连接，以及客户发的RTCP包的到来。事件线程通过对TCP端口的检测，在有连接请求时建立可维护客户信息的RTSP会话，在每一个客户的RTSP会话的存活期内，事件线程不断的把向客户发送RTP包这一任务放入处理线程的队列，直到RTSP会话终止，事件线程再把关闭连接的任务放入队列。<br />
<br />
事件线程把对不同客户的服务（发送RTP数据包）以任务的形式放入队列后，处理线程对对列中的任务依次进行处理，也就是说，处理线程根据客户的不同，不断的把相应的流媒体文件形成RTP包，依次发出，直到视频服务的终止。<br />
<br />
可以看的出来，处理线程虽然对所有的客户提供服务，但是执行的任务始终是发送RTP数据流包，同时，该线程所访问的媒体文件被调入内存后，可以被同一线程的其它任务重复调用，这样的设计，不但减少了随着客户数增加而造成的系统频繁切换的资源损失，也减少了访问硬盘数据的次数，缩短了访问时间，也发挥了指令局部性效率提高的优点。设图5　改进后的处理流程计中，解析客户的连接请求也放入处理线程的队列中，在处理线程中进行处理，虽然解析连接的任务不同于大部分发送RTP包的任务，但是该任务消耗时间少，所以对对系统性能的影响并不大。 </p>
<p align="center"><img src="http://www.chinavideo.org/images/stories/news/200610/20061010_05.jpg" align="baseline" border="0"  alt="" /></p>
<p><br />
<br />
采用图5的设计方法，服务器还可以根据客户端反馈的RTCP包得知客户端的网络状况，采取一定的策略针对不同的客户网络质量进行RTP包发送任务的调度，对于带宽质量好的客户，事件线程可以多放一些RTP包到队列中；对于由于网络质量造成的即将超时的客户连接，服务器也可以通过任务调度的方式进行特殊处理，最大限度的提供不同质量的客户连接服务。<br />
<br />
上述的设计是在硬件具有一个CPU的情况下，当系统硬件具有的CPU个数为n时（n＞1，事实上做为流媒体服务器的硬件需求，一个CPU是远远不能满足系统需求的），程序将采用n个处理线程来分担事件线程调度的任务，利用系统的可扩展性来提高性能。<br />
<br />
<strong>2．4　性能分析<br />
<br />
</strong>新设计的方案充分发挥了指令局部性和数据局部性的优点。处理线程在处理队列任务时，由于队列中每一个操作所作的事都一样（如不停的发RTP 包），因此每个操作的指令序列都是完全相同的；而且，当客户在连接后要求获得热点节目的媒体文件时，指令操作的数据也将非常相近，都在一个范围很小的区域内。这样，经过几次操作后，当指令被加载到Cache中，数据加载到内存中后，就不再需要从磁盘中去调，极大提高了程序的性能。<br />
<br />
新设计的方案可以实现流媒体数据在内存中连续存放，快速存取以及Cache预取，资源管理方式从以应用线程为中心的资源管理与分配转变为以数据为中心的资源管理与分配。<br />
<br />
衡量流媒体服务器的性能可以根据服务CPU的负载情况来衡量。CPU的使用情况主要和连接的客户数目，客户端的操作如读取，快进，搜索等相关。对于一个22kbps的文件集，改进的服务器设计方案与传统的服务器设计方案的性能优劣比较曲线，如图6所示。<br />
<br />
图6体现了随着CPU数的增加，传统设计方案的性能变化不大，改进的设计方案在多个CPU的系统里，充分利用了CPU所带的Cache的强大功能，CPU的总体负载明显下降，可以获得性能的极大提高。</p>
<p align="center"><img src="http://www.chinavideo.org/images/stories/news/200610/20061010_06.jpg" align="baseline" border="0"  alt="" /></p>
<p><br />
<br />
<strong>3　结束语<br />
<br />
</strong>本文从系统可扩展方面提出了实现高效的流媒体服务器的解决方案。除了系统可扩展方面，设计一个完善的高效流媒体服务器，还需要在实时的服务器操作系统，系统资源管理（CPU管理，内存管理，磁盘资源管理），文件管理，服务器磁盘调度方面进行高效的设计，这需要我们进一步研究和设计。</p>
 <img src ="http://www.blogjava.net/jelver/aggbug/196400.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/jelver/" target="_blank">冰河快狼</a> 2008-04-27 09:53 <a href="http://www.blogjava.net/jelver/articles/196400.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>