<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>JoyUp.ME - Keep Trying Anyway...</title>
	<atom:link href="http://blog.joyup.me/feed" rel="self" type="application/rss+xml" />
	<link>http://blog.joyup.me</link>
	<description>一个博客，一段文字；写给以后的自己...</description>
	<lastBuildDate>Tue, 17 Apr 2012 08:31:34 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3.1</generator>
		<item>
		<title>深入解析ASP.NET MVC的生命周期</title>
		<link>http://blog.joyup.me/archives/inside-the-aspnet-mvc4-02-mvc-life-cycle.html</link>
		<comments>http://blog.joyup.me/archives/inside-the-aspnet-mvc4-02-mvc-life-cycle.html#comments</comments>
		<pubDate>Wed, 14 Mar 2012 17:27:20 +0000</pubDate>
		<dc:creator>jianphu</dc:creator>
				<category><![CDATA[DotNet]]></category>
		<category><![CDATA[MVC]]></category>
		<category><![CDATA[mvc4]]></category>
		<category><![CDATA[生命周期]]></category>

		<guid isPermaLink="false">http://blog.moozi.net/?p=142</guid>
		<description><![CDATA[这一章我们这副图来了解MVC的整个执行流程。
另外：
1、IIS是通过“C:\Windows\Microsoft.NET\Framework\v4.0.30319\Config\web.config”配置文件中的<add name="UrlRoutingModule-4.0" type="System.Web.Routing.UrlRoutingModule" />这个配置走到UrlRoutingModule的。
2、MVC中有4种Filter，分别是IActionFilter/IAuthorizationFilter/IExceptionFilter/IResultFilter，这些内容，请听下回分解^_^。
3、MVC框架中非常重要的一部分是Service Location，所有的扩展都是建立在Service Location之上。
]]></description>
			<content:encoded><![CDATA[<p>上一篇《<a title="深入解析ASP.NET MVC4——01.开篇" href="http://blog.moozi.net/archives/inside-the-aspnet-mvc4-01-introduce.html">深入解析ASP.NET MVC4——01.开篇</a>》简单地介绍了一下MVC框架，接下来我们从MVC的生命周期来了解MVC的整个执行流程。</p>
<p><a href="http://blog.moozi.net/wp-content/uploads/2012/03/mvc-life-cycle.png"><img class="aligncenter size-full wp-image-143" title="mvc-life-cycle" src="http://blog.moozi.net/wp-content/uploads/2012/03/mvc-life-cycle.png" alt="" width="691" height="1175" /></a></p>
<p>（备注：这是我使用Reflector查看System.Web和System.Web.Mvc这2个程序集画出来的图，如有错误，欢迎指正。）</p>
<p>图上的内容我就不再解说了，再补充以下几点：</p>
<ol>
<li>IIS是通过“C:\Windows\Microsoft.NET\Framework\v4.0.30319\Config\web.config”配置文件中的&lt;add name=”UrlRoutingModule-4.0&#8243; type=”System.Web.Routing.UrlRoutingModule” /&gt;这个配置走到UrlRoutingModule的。</li>
<li>MVC中有4种Filter，分别是IActionFilter/IAuthorizationFilter/IExceptionFilter/IResultFilter，这些内容，请听下回分解^_^。</li>
<li>MVC框架中非常重要的一部分是Service Location，所有的扩展都是建立在Service Location之上。</li>
</ol>
<p>&nbsp;</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.joyup.me/archives/inside-the-aspnet-mvc4-02-mvc-life-cycle.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>使用事务自动回滚来实现单元测试</title>
		<link>http://blog.joyup.me/archives/use-the-transaction-rollback-to-unit-test.html</link>
		<comments>http://blog.joyup.me/archives/use-the-transaction-rollback-to-unit-test.html#comments</comments>
		<pubDate>Wed, 14 Mar 2012 13:38:53 +0000</pubDate>
		<dc:creator>jianphu</dc:creator>
				<category><![CDATA[C#技术]]></category>
		<category><![CDATA[DotNet]]></category>
		<category><![CDATA[DDD]]></category>
		<category><![CDATA[TDD]]></category>
		<category><![CDATA[TransactionScope]]></category>
		<category><![CDATA[xunit]]></category>
		<category><![CDATA[单元测试]]></category>

		<guid isPermaLink="false">http://blog.moozi.net/?p=140</guid>
		<description><![CDATA[我们没有使用TDD，所以单元测试最麻烦的就是准备测试的基础数据。我们现在是使用内存仓储来做单元测试，要为每个仓储都构造基础数据，非常麻烦。
前几天看xunit的源码，看到AutoRollbackAttribute这个特性，异常的兴奋 ^_^。怎么就忘了用事务的自动回滚呢？
AutorollbackAttribute使用了.Net Framework自带的TransactionScope。TransactionScope在.NET 2.0中就已经有了，可用于分布式事务。用这种方法来做数据的自动回滚也有一些不足：
1、数据库要支持事务。
2、内部数据库操作的逻辑里没有事务的实现。
很庆幸的是我们的项目正好都满足上面的2点，唯一不足的就是mongodb不支持事务。所以就需要混合仓储实现了，事务数据库使用真实的仓储，mongodb使用内存仓储。]]></description>
			<content:encoded><![CDATA[<p>我们没有使用TDD，所以单元测试最麻烦的就是准备测试的基础数据。我们现在是使用内存仓储来做单元测试，要为每个仓储都构造基础数据，非常麻烦。</p>
<p>前几天看xunit的源码，看到AutoRollbackAttribute这个特性，异常的兴奋 ^_^。怎么就忘了用事务的自动回滚呢？</p>
<p>我们看AutorollbackAttribute的具体实现：</p>
<pre class="brush:csharp">public class AutoRollbackAttribute : BeforeAfterTestAttribute
{
    IsolationLevel isolationLevel = IsolationLevel.Unspecified;
    TransactionScope scope;
    TransactionScopeOption scopeOption = TransactionScopeOption.Required;
    long timeoutInMS = -1;

    /// &lt;summary&gt;
    /// Gets or sets the isolation level of the transaction.
    /// Default value is &lt;see cref="IsolationLevel"/&gt;.Unspecified.
    /// &lt;/summary&gt;
    public IsolationLevel IsolationLevel
    {
        get { return isolationLevel; }
        set { isolationLevel = value; }
    }

    /// &lt;summary&gt;
    /// Gets or sets the scope option for the transaction.
    /// Default value is &lt;see cref="TransactionScopeOption"/&gt;.Required.
    /// &lt;/summary&gt;
    public TransactionScopeOption ScopeOption
    {
        get { return scopeOption; }
        set { scopeOption = value; }
    }

    /// &lt;summary&gt;
    /// Gets or sets the timeout of the transaction, in milliseconds.
    /// By default, the transaction will not timeout.
    /// &lt;/summary&gt;
    public long TimeoutInMS
    {
        get { return timeoutInMS; }
        set { timeoutInMS = value; }
    }

    /// &lt;summary&gt;
    /// Rolls back the transaction.
    /// &lt;/summary&gt;
    public override void After(MethodInfo methodUnderTest)
    {
        scope.Dispose();
    }

    /// &lt;summary&gt;
    /// Creates the transaction.
    /// &lt;/summary&gt;
    public override void Before(MethodInfo methodUnderTest)
    {
        TransactionOptions options = new TransactionOptions();
        options.IsolationLevel = isolationLevel;
        if (timeoutInMS &gt; 0)
            options.Timeout = new TimeSpan(timeoutInMS * 10);
        scope = new TransactionScope(scopeOption, options);
    }
}</pre>
<p>这里使用了.Net Framework自带的TransactionScope。TransactionScope在.NET 2.0中就已经有了，可用于分布式事务。用这种方法来做数据的自动回滚也有一些不足：</p>
<ol>
<li>数据库要支持事务。</li>
<li>内部数据库操作的逻辑里没有事务的实现。</li>
</ol>
<p>很庆幸的是我们的项目正好都满足上面的2点，唯一不足的就是mongodb不支持事务。所以就需要混合仓储实现了，事务数据库使用真实的仓储，mongodb使用内存仓储。</p>
<p>项目中是用VS自带的单元测试框架，也不想因为这一个特性而改用xunit，那就只能动手把这个迁移到VS的单元测试框架里了。</p>
<pre class="brush:csharp">/// &lt;summary&gt;
/// 单元测试基类
/// &lt;/summary&gt;
[TestClass]
public class BaseUnitTest
{
    IsolationLevel _isolationLevel = IsolationLevel.Unspecified;
    TransactionScopeOption _scopeOption = TransactionScopeOption.Required;
    TransactionScope _transactionScope;
    bool _openAutoRollback = true;

    /// &lt;summary&gt;
    /// 构造函数
    /// &lt;/summary&gt;
    /// &lt;param name="autoRollback"&gt;是否开启自动回滚，默认开启&lt;/param&gt;
    public BaseUnitTest(bool autoRollback = true)
    {
        _openAutoRollback = autoRollback;
    }

    /// &lt;summary&gt;
    /// 自动回滚事务初始化
    /// &lt;/summary&gt;
    [TestInitialize]
    public void AutoRollbackBefore()
    {
        if (_openAutoRollback)
        {
            var options = new TransactionOptions();
            options.IsolationLevel = _isolationLevel;
            options.Timeout = new TimeSpan(0, 1, 0);
            _transactionScope = new TransactionScope(_scopeOption, options);
        }
    }

    /// &lt;summary&gt;
    /// 自动回滚事务回滚并释放对象
    /// &lt;/summary&gt;
    [TestCleanup]
    public void AutoRollbackAfter()
    {
        if (_openAutoRollback)
        {
            if (_transactionScope == null)
                throw new InvalidOperationException("未初始化TransactionScope");
            //回滚事务
            _transactionScope.Dispose();
            //释放事务对象
            _transactionScope = null;
            //移除所有的缓存
            RemoveHttpRuntimeCache();
        }
    }

    /// &lt;summary&gt;
    /// 移除所有的HttpRuntime缓存
    /// &lt;/summary&gt;
    [DebuggerStepThrough]
    private void RemoveHttpRuntimeCache()
    {
        var cache = HttpRuntime.Cache.GetEnumerator();
        var keys = new List&lt;string&gt;();
        while (cache.MoveNext())
        {
            keys.Add(cache.Key.ToString());
        }
        foreach (var key in keys)
        {
            HttpRuntime.Cache.Remove(key);
        }
    }

    /// &lt;summary&gt;
    /// 设置不自动回滚事务
    /// &lt;/summary&gt;
    protected void SetAutoRollbackIsUnavailabled()
    {
        _openAutoRollback = false;
    }
}</pre>
<p>上面的RemoveHttpRuntimeCache是因为我们在项目中有使用HttpRuntime缓存，关系数据库中的数据回滚后会导致缓存和数据库不一致，所以一但有开启事务的自动回滚，也要相应的清空内存缓存。</p>
<p>方法很简单，跟大家分享一下，和TransactionScope相关的知识，不清楚的同学可以看下<a href="http://msdn.microsoft.com/zh-cn/library/system.transactions.transactionscope(v=vs.80).aspx" target="_blank">MSDN关于“TransactionScope”的文档</a>。</p>
<p>&nbsp;</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.joyup.me/archives/use-the-transaction-rollback-to-unit-test.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>在MVC 3.0中使用MS Chart</title>
		<link>http://blog.joyup.me/archives/using-ms-chart-in-mvc-3.html</link>
		<comments>http://blog.joyup.me/archives/using-ms-chart-in-mvc-3.html#comments</comments>
		<pubDate>Tue, 10 May 2011 04:48:40 +0000</pubDate>
		<dc:creator>jianphu</dc:creator>
				<category><![CDATA[DotNet]]></category>
		<category><![CDATA[MVC]]></category>
		<category><![CDATA[MSChart]]></category>
		<category><![CDATA[雷达图]]></category>

		<guid isPermaLink="false">http://blog.moozi.net/?p=127</guid>
		<description><![CDATA[MsChart是微软制作的功能强大的图表工具，用它可以很方便的建立各种图表。博客园里也有各种MS Chart在MVC中的用法，都不是很满意，要么要加handler，要么在视图中用，感觉各种不好。琢磨了下，也google了一些资料，最后决定用下面这种方式来实现。]]></description>
			<content:encoded><![CDATA[<p>MsChart是微软制作的功能强大的图表工具，用它可以很方便的建立各种图表。<a href="http://www.google.com.hk/search?hl=zh-CN&amp;newwindow=1&amp;safe=strict&amp;q=mschart+mvc+site%3Acnblogs.com&amp;oq=mschart+mvc+site%3Acnblogs.com&amp;aq=f&amp;aqi=&amp;aql=1&amp;gs_sm=e&amp;gs_upl=778l1705l0l1849l4l4l0l0l0l1l356l653l2-1.1l2l0" target="_blank">博客园里也有各种MS Chart在MVC中的用法</a>，都不是很满意，要么要加handler，要么在视图中用，感觉各种不好。琢磨了下，也google了一些资料，最后决定用下面这种方式来实现。</p>
<p>示例是一个雷达图，绘图是在控制器里实现，<span style="color: #ff0000;">页面调用只要直接用&lt;img src=”{controller}”&gt;就可以，web.config也不需要再增加任何配置</span>。</p>
<p>主要步骤如下</p>
<p>1、引用2个类库，<span style="color: #ff0000;">System.Web.DataVisualization</span>和<span style="color: #ff0000;">System.Web.DataVisualization.Design</span></p>
<p>2、控制器实现代码：</p>
<pre class="brush:csharp">using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Web.Mvc;
using System.Web.UI.DataVisualization.Charting;

namespace MvcChart.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            double[] baseValues = { 65.62, 75.54, 60.45, 34.73, 85.42 };
            double[] userValues = { 80.62, 45.54, 80.45, 44.73, 80.42 };
            string[] moduleNames = { "常识判断", "言语理解表达", "判断推理", "数量关系", "资料分析" };
            Chart radar = new Chart();
            radar.Series.Add("baseSeries");
            radar.Series["baseSeries"].Points.DataBindXY(moduleNames, baseValues);
            radar.Series["baseSeries"].ChartType = SeriesChartType.Radar;
            radar.Series["baseSeries"]["RadarDrawingStyle"] = "Area";
            radar.Series["baseSeries"]["AreaDrawingStyle"] = "Polygon";
            radar.Series["baseSeries"]["CircularLabelsStyle"] = "Horizontal";
            radar.Series.Add("userSeries");
            radar.Series["userSeries"].Points.DataBindXY(moduleNames, userValues);
            radar.Series["userSeries"].ChartType = SeriesChartType.Radar;
            radar.Series["userSeries"]["RadarDrawingStyle"] = "Area";
            radar.Series["userSeries"]["AreaDrawingStyle"] = "Polygon";
            radar.Series["userSeries"]["CircularLabelsStyle"] = "Horizontal";
            radar.ChartAreas.Add("chartArea");
            radar.ChartAreas["chartArea"].Area3DStyle.Enable3D = true;
            MemoryStream stream = new MemoryStream();
            radar.SaveImage(stream, ChartImageFormat.Jpeg);
            return new ImageResult(Image.FromStream(stream), ImageFormat.Jpeg);
        }
    }
}</pre>
<p>3、注意这里的返回类型，使用的是ImageResult，这是我们自己实现的ActionResult。</p>
<pre class="brush:csharp">using System.Drawing;
using System.Drawing.Imaging;

namespace System.Web.Mvc
{
    public class ImageResult : ActionResult
    {
        public ImageResult()
        { }

        public ImageResult(Image image)
        {
            Image = image;
        }

        public ImageResult(Image image, ImageFormat format)
        {
            Image = image;
            ImageFormat = format;
        }

        /// &lt;summary&gt;
        ///
        /// &lt;/summary&gt;
        public Image Image { get; set; }

        /// &lt;summary&gt;
        /// 指定图像的文件格式
        /// &lt;/summary&gt;
        public ImageFormat ImageFormat { get; set; }

        public override void ExecuteResult(ControllerContext context)
        {
            if (Image == null)
                throw new ArgumentNullException("Image");

            if (ImageFormat == null)
                throw new ArgumentNullException("ImageFormat");

            context.HttpContext.Response.Clear();

            if (ImageFormat.Equals(ImageFormat.Gif))
                context.HttpContext.Response.ContentType = "image/gif";
            else if (ImageFormat.Equals(ImageFormat.Jpeg))
                context.HttpContext.Response.ContentType = "image/jpeg";
            else if (ImageFormat.Equals(ImageFormat.Png))
                context.HttpContext.Response.ContentType = "image/png";
            else if (ImageFormat.Equals(ImageFormat.Bmp))
                context.HttpContext.Response.ContentType = "image/bmp";
            else if (ImageFormat.Equals(ImageFormat.Tiff))
                context.HttpContext.Response.ContentType = "image/tiff";
            else if (ImageFormat.Equals(ImageFormat.Icon))
                context.HttpContext.Response.ContentType = "image/vnd.microsoft.icon";
            else if (ImageFormat.Equals(ImageFormat.Wmf))
                context.HttpContext.Response.ContentType = "image/wmf";

            Image.Save(context.HttpContext.Response.OutputStream, ImageFormat);
        }
    }
}</pre>
<p>非常简单，不需要其它的配置。</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.joyup.me/archives/using-ms-chart-in-mvc-3.html/feed</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>多线程并发测试类库</title>
		<link>http://blog.joyup.me/archives/multi-threaded-concurrent-test-library.html</link>
		<comments>http://blog.joyup.me/archives/multi-threaded-concurrent-test-library.html#comments</comments>
		<pubDate>Sat, 16 Apr 2011 12:08:14 +0000</pubDate>
		<dc:creator>jianphu</dc:creator>
				<category><![CDATA[C#技术]]></category>
		<category><![CDATA[开发日志]]></category>
		<category><![CDATA[ManualResetEvent]]></category>
		<category><![CDATA[Thread]]></category>
		<category><![CDATA[多线程编程]]></category>
		<category><![CDATA[并发测试]]></category>

		<guid isPermaLink="false">http://blog.moozi.net/?p=114</guid>
		<description><![CDATA[<p>WEB项目中除了单元测试，还经常需要多线程测试一个方法是否存在并发问题，或者是否有性能问题。每次都要写测试代码总是一件很累的事情。于是写了这一个多线程测试的类库，用来进行快速的多线程并发测试。</p>
<p>多线程并发测试由以下步骤完成：</p>
<ul>
<li>创建并发测试的线程数，先创建的线程等待最后一个线程创建完成。</li>
<li>所有线程执行待测试的方法，返回测试的结果。</li>
<li>等所有线程执行完成后，进入思考时间等待。</li>
<li>继续进行循环测试。</li></ul>]]></description>
			<content:encoded><![CDATA[<p>WEB项目中除了单元测试，还经常需要多线程测试一个方法是否存在并发问题，或者是否有性能问题。每次都要写测试代码总是一件很累的事情。于是写了这一个多线程测试的类库，用来进行快速的多线程并发测试。</p>
<p>多线程并发测试时，需要等所有线程测试结束后通知主线程，主线程才能进行下一步动作，这里主要用到了<span style="color: #ff0000;">ManualResetEvent</span>。ManualResetEvent 类表示一个本地等待处理事件，在已发事件信号后必须手动重置该事件。通常，此通信涉及一个线程在其他线程进行之前必须完成的任务。当一个线程开始一个活动（此活动必须完成后，其他线程才能开始）时，它调用 Reset 以将 ManualResetEvent 置于非终止状态。此线程可被视为控制 ManualResetEvent。调用 ManualResetEvent 上的 WaitOne 的线程将阻止，并等待信号。当控制线程完成活动时，它调用 Set 以发出等待线程可以继续进行的信号。并释放所有等待线程。一旦它被终止，ManualResetEvent 将保持终止状态，直到它被手动重置。即对 WaitOne 的调用将立即返回。可以通过将布尔值传递给构造函数来控制 ManualResetEvent 的初始状态，如果初始状态处于终止状态，为 true；否则为 false。</p>
<p>多线程并发测试由以下步骤完成：</p>
<ol>
<li>创建并发测试的线程数，先创建的线程等待最后一个线程创建完成。</li>
<li>所有线程执行待测试的方法，返回测试的结果。</li>
<li>等所有线程执行完成后，进入思考时间等待。</li>
<li>继续进行循环测试。</li>
</ol>
<p>我们来看这个多线程并发测试的代码。</p>
<pre class="brush:csharp">    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Threading;
    /// &lt;summary&gt;
    /// 并发测试
    /// &lt;/summary&gt;
    public class ConcurrentTest : IDisposable
    {
        #region 私有方法
        /// &lt;summary&gt;
        /// 测试方法所在的接口
        /// &lt;/summary&gt;
        private Func&lt;bool&gt; func;
        /// &lt;summary&gt;
        /// 主线程控制信号
        /// &lt;/summary&gt;
        private ManualResetEvent manualResetEvent;
        /// &lt;summary&gt;
        /// 测试线程控制信号
        /// &lt;/summary&gt;
        private ManualResetEvent threadResetEvent;
        /// &lt;summary&gt;
        /// 待执行的线程数
        /// &lt;/summary&gt;
        private List&lt;int&gt; threads;
        /// &lt;summary&gt;
        /// 测试结果
        /// &lt;/summary&gt;
        private List&lt;ConcurrentTestResult&gt; results;
        /// &lt;summary&gt;
        /// 执行测试的成功数
        /// &lt;/summary&gt;
        private int successCount;
        /// &lt;summary&gt;
        /// 执行测试的失败数
        /// &lt;/summary&gt;
        private int failureCount;
        /// &lt;summary&gt;
        /// 测试耗时
        /// &lt;/summary&gt;
        private long elapsedMilliseconds;
        /// &lt;summary&gt;
        /// 当前线程
        /// &lt;/summary&gt;
        private int currentIndex;
        /// &lt;summary&gt;
        /// 当前测试的总线程数
        /// &lt;/summary&gt;
        private int currentCount;
        /// &lt;summary&gt;
        /// 思考时间
        /// &lt;/summary&gt;
        private int thinkTime;
        /// &lt;summary&gt;
        /// 重复次数
        /// &lt;/summary&gt;
        private int repeatCount;
        /// &lt;summary&gt;
        /// 测试计时器
        /// &lt;/summary&gt;
        private Stopwatch stopwatch;
        #endregion

        #region 构造函数
        /// &lt;summary&gt;
        /// 构造函数
        /// &lt;/summary&gt;
        public ConcurrentTest()
        {
            manualResetEvent = new ManualResetEvent(true);
            threadResetEvent = new ManualResetEvent(true);
            stopwatch = new Stopwatch();
        }
        #endregion

        #region 执行测试
        /// &lt;summary&gt;
        /// 执行多线程测试
        /// &lt;/summary&gt;
        /// &lt;param name="threadCount"&gt;需要测试的线程数&lt;/param&gt;
        /// &lt;param name="func"&gt;待执行方法&lt;/param&gt;
        /// &lt;returns&gt;&lt;/returns&gt;
        public List&lt;ConcurrentTestResult&gt; Execute(int threadCount, Func&lt;bool&gt; func)
        {
            return Execute(threadCount, 1, func);
        }
        /// &lt;summary&gt;
        /// 执行多线程测试
        /// &lt;/summary&gt;
        /// &lt;param name="threadCount"&gt;需要测试的线程数&lt;/param&gt;
        /// &lt;param name="repeatCount"&gt;重复次数&lt;/param&gt;
        /// &lt;param name="func"&gt;待执行方法&lt;/param&gt;
        /// &lt;returns&gt;&lt;/returns&gt;
        public List&lt;ConcurrentTestResult&gt; Execute(int threadCount, int repeatCount, Func&lt;bool&gt; func)
        {
            return Execute(threadCount, 0, repeatCount, func);
        }
        /// &lt;summary&gt;
        /// 执行多线程测试
        /// &lt;/summary&gt;
        /// &lt;param name="threadCount"&gt;需要测试的线程数&lt;/param&gt;
        /// &lt;param name="thinkTime"&gt;思考时间，单位毫秒&lt;/param&gt;
        /// &lt;param name="repeatCount"&gt;重复次数&lt;/param&gt;
        /// &lt;param name="func"&gt;待执行方法&lt;/param&gt;
        /// &lt;returns&gt;&lt;/returns&gt;
        public List&lt;ConcurrentTestResult&gt; Execute(int threadCount, int thinkTime, int repeatCount, Func&lt;bool&gt; func)
        {
            return Execute(new List&lt;int&gt;() { threadCount }, thinkTime, repeatCount, func);
        }
        /// &lt;summary&gt;
        /// 执行多线程测试
        /// &lt;/summary&gt;
        /// &lt;param name="threads"&gt;分别需要测试的线程数&lt;/param&gt;
        /// &lt;param name="thinkTime"&gt;思考时间，单位毫秒&lt;/param&gt;
        /// &lt;param name="repeatCount"&gt;重复次数&lt;/param&gt;
        /// &lt;param name="func"&gt;待执行方法&lt;/param&gt;
        /// &lt;returns&gt;&lt;/returns&gt;
        public List&lt;ConcurrentTestResult&gt; Execute(List&lt;int&gt; threads, int thinkTime, int repeatCount, Func&lt;bool&gt; func)
        {
            this.func = func;
            this.threads = threads;
            this.thinkTime = thinkTime;
            this.repeatCount = repeatCount;
            CheckParameters();
            CreateMultiThread();
            return this.results;
        }
        #endregion

        #region 验证参数
        /// &lt;summary&gt;
        /// 验证参数
        /// &lt;/summary&gt;
        private void CheckParameters()
        {
            if (func == null) throw new ArgumentNullException("func不能为空");
            if (threads == null || threads.Count == 0) throw new ArgumentNullException("threads不能为空或者长度不能为0");
            if (thinkTime &lt; 0) throw new Exception("thinkTime不能小于0");
            if (repeatCount &lt;= 0) throw new Exception("repeatCount不能小于等于0");
        }
        #endregion

        #region 创建多线程并执行测试
        /// &lt;summary&gt;
        /// 创建多线程进行测试
        /// &lt;/summary&gt;
        private void CreateMultiThread()
        {
            results = new List&lt;ConcurrentTestResult&gt;(threads.Count);
            foreach (int threadCount in threads)
            {
                for (int repeat = 0; repeat &lt; repeatCount; repeat++)
                {
                    //主线程进入阻止状态
                    manualResetEvent.Reset();
                    //测试线程进入阻止状态
                    threadResetEvent.Reset();
                    stopwatch.Reset();
                    currentCount = threadCount;
                    currentIndex = 0;
                    successCount = 0;
                    failureCount = 0;
                    elapsedMilliseconds = 0;
                    for (int i = 0; i &lt; currentCount; i++)
                    {
                        Thread t = new Thread(new ThreadStart(DoWork));
                        t.Start();
                    }
                    //阻止主线程，等待测试线程完成测试
                    manualResetEvent.WaitOne();
                    results.Add(new ConcurrentTestResult()
                    {
                        FailureCount = failureCount,
                        SuccessCount = successCount,
                        ElapsedMilliseconds = elapsedMilliseconds
                    });
                    Thread.Sleep(thinkTime);
                }
            }
        }
        /// &lt;summary&gt;
        /// 执行测试方法
        /// &lt;/summary&gt;
        private void DoWork()
        {
            bool executeResult;
            Interlocked.Increment(ref currentIndex);
            if (currentIndex &lt; currentCount)
            {
                //等待所有线程创建完毕后同时执行测试
                threadResetEvent.WaitOne();
            }
            else
            {
                //最后一个线程创建完成，通知所有线程，开始执行测试
                threadResetEvent.Set();
                //开始计时
                stopwatch.Start();
            }
            //执行测试
            executeResult = func();
            Interlocked.Decrement(ref currentIndex);
            if (currentIndex == 0)
            {
                //最后一个线程执行的测试结束，结束计时
                stopwatch.Stop();
                elapsedMilliseconds = stopwatch.ElapsedMilliseconds;
                //保存测试结果
                if (executeResult)
                    Interlocked.Increment(ref successCount);
                else
                    Interlocked.Increment(ref failureCount);
                //通知主线程继续
                manualResetEvent.Set();
            }
            else
            {
                //保存测试结果
                if (executeResult)
                    Interlocked.Increment(ref successCount);
                else
                    Interlocked.Increment(ref failureCount);
            }
        }
        #endregion

        #region 释放资源
        /// &lt;summary&gt;
        /// 释放资源
        /// &lt;/summary&gt;
        public void Dispose()
        {
            manualResetEvent.Close();
            threadResetEvent.Close();
        }
        #endregion
    }
    /// &lt;summary&gt;
    /// 并发测试结果
    /// &lt;/summary&gt;
    public class ConcurrentTestResult
    {
        /// &lt;summary&gt;
        /// 当前执行线程总数
        /// &lt;/summary&gt;
        public int ThreadCount
        {
            get { return SuccessCount + FailureCount; }
        }
        /// &lt;summary&gt;
        /// 测试成功数
        /// &lt;/summary&gt;
        public int SuccessCount { get; set; }
        /// &lt;summary&gt;
        /// 测试失败数
        /// &lt;/summary&gt;
        public int FailureCount { get; set; }
        /// &lt;summary&gt;
        /// 总耗时
        /// &lt;/summary&gt;
        public long ElapsedMilliseconds { get; set; }
    }</pre>
<p>使用起来就非常简单了，我们看测试代码：</p>
<pre class="brush:csharp">    class Program
    {
        static void Main(string[] args)
        {
            using (ConcurrentTest concurrentTest = new ConcurrentTest())
            {
                var result = concurrentTest.Execute(5, -1, 10, new TestClass().Execute);
                foreach (var item in result)
                {
                    Console.WriteLine("线程数：{0}\t成功：{1}\t失败：{2}\t耗时：{3}",
                        item.ThreadCount, item.SuccessCount, item.FailureCount, item.ElapsedMilliseconds);

                }
            }
            Console.ReadKey(true);
        }
    }

    public class TestClass
    {
        public bool Execute()
        {
            int tempValue = GetRandom();
            System.Threading.Thread.Sleep(tempValue);
            return tempValue % 2 == 0;
        }
        private int GetRandom()
        {
            return new Random().Next(990, 1000);
        }
    }</pre>
<p>测试类库提供了4个Execute方法的重载，一般情况下能满足我们的多线程并发测试场景了。</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.joyup.me/archives/multi-threaded-concurrent-test-library.html/feed</wfw:commentRss>
		<slash:comments>6</slash:comments>
		</item>
		<item>
		<title>在生产环境下，MONO与.NET Framework的性能差别。</title>
		<link>http://blog.joyup.me/archives/the-performance-difference-of-mono-and-net-framework-the-in-production-environment.html</link>
		<comments>http://blog.joyup.me/archives/the-performance-difference-of-mono-and-net-framework-the-in-production-environment.html#comments</comments>
		<pubDate>Wed, 02 Mar 2011 13:04:14 +0000</pubDate>
		<dc:creator>jianphu</dc:creator>
				<category><![CDATA[C#技术]]></category>
		<category><![CDATA[开发日志]]></category>
		<category><![CDATA[apache]]></category>
		<category><![CDATA[mod_mono]]></category>
		<category><![CDATA[mono]]></category>
		<category><![CDATA[MONO性能]]></category>

		<guid isPermaLink="false">http://blog.moozi.net/?p=109</guid>
		<description><![CDATA[<p>上个月公司项目需要，在腾讯社区开放平台部署了基于mono的腾讯校友应用“公务员考试”。经过一周的观察，发现问题非常大。</p>
<p>2月18日到2月24日的平均值在70.91%。而2月23日-3月1日平均值在76.61%。占用的CPU非常高。在访问用户数不高的情况下，单是MONO的守护进程就占用了75.0%的CPU。</p>
<p>生产环境上是用Apache+mod_mono来做WEB站的，总的来看，对于WEB负载，MONO的情况并不理想。</p>]]></description>
			<content:encoded><![CDATA[<p>      上个月公司项目需要，在腾讯社区开放平台部署了基于mono的腾讯校友应用“<a title="公务员考试" href="http://gongwuyuan591up.qzoneapp.com/" target="_blank">公务员考试</a>”。经过一周的观察，发现问题非常大。</p>
<p>      我们来看mono在腾讯服务器上的CPU报表。</p>
<p><img class="aligncenter size-full wp-image-110" title="mono-cpu" src="http://blog.moozi.net/wp-content/uploads/2011/03/mono-cpu.jpg" alt="" width="786" height="259" /></p>
<p>      2月18日到2月24日的平均值在70.91%。而2月23日-3月1日平均值在76.61%。占用的CPU非常高。</p>
<p>      另一份基本相同的代码部署在Windows服务器上，我们看这台服务器的CPU使用率。</p>
<p><img class="aligncenter size-full wp-image-111" title="windows-cpu" src="http://blog.moozi.net/wp-content/uploads/2011/03/windows-cpu.jpg" alt="" width="697" height="443" /></p>
<p>      这台服务器除了这个项目外，还负载了其它的Web项目。从报表反映的情况来看，运行状态非常健康。</p>
<p>      这两台服务器的配置分别是：</p>
<p>       Tencent：CPU-Xeon E5506*2(2.13GHZ)，内存-7G，OS-Linux 2.6(suse)，MONO2.8.2</p>
<p>       Windows：CPU-Xeon E5420*4(2.50GHZ)，内存-4G，OS-Windows Server 2003，.NET Framework。</p>
<p>      在服务器的配置上没有办法做直接的对比，附上Windows服务器，只是想从一个侧面来说明项目代码并不复杂，系统需要处理的计划量并不多。<img class="aligncenter size-full wp-image-112" title="mono-taskmanager" src="http://blog.moozi.net/wp-content/uploads/2011/03/mono-taskmanager.jpg" alt="" width="564" height="33" /></p>
<p>      在访问用户数不高的情况下，单是MONO的守护进程就占用了75.0%的CPU。</p>
<p><img class="aligncenter size-full wp-image-113" title="tencent-online-user" src="http://blog.moozi.net/wp-content/uploads/2011/03/tencent-online-user.jpg" alt="" width="1004" height="254" /></p>
<p>      生产环境上是用Apache+mod_mono来做WEB站的，总的来看，对于WEB负载，MONO的情况并不理想。</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.joyup.me/archives/the-performance-difference-of-mono-and-net-framework-the-in-production-environment.html/feed</wfw:commentRss>
		<slash:comments>12</slash:comments>
		</item>
		<item>
		<title>Web性能优化实践——应用层性能优化</title>
		<link>http://blog.joyup.me/archives/web-performance-optimization-practice-application-optimization.html</link>
		<comments>http://blog.joyup.me/archives/web-performance-optimization-practice-application-optimization.html#comments</comments>
		<pubDate>Tue, 01 Mar 2011 02:19:27 +0000</pubDate>
		<dc:creator>jianphu</dc:creator>
				<category><![CDATA[Asp.Net]]></category>
		<category><![CDATA[开发日志]]></category>
		<category><![CDATA[LVS]]></category>
		<category><![CDATA[WPO]]></category>
		<category><![CDATA[应用层优化]]></category>
		<category><![CDATA[数据库优化]]></category>

		<guid isPermaLink="false">http://blog.moozi.net/?p=105</guid>
		<description><![CDATA[<p>随着公司项目的进一步推广，用户数量的增加，已经面临着单台服务器不能负载的问题。这次的优化由于时间关系主要分两步走，首先优化应用层代码以提高单台服务器的负载和吞吐率。之后再进行分表，引入队列、MemCached等分布式应用。</p>
<p>项目背景：这是一个在线竞赛的项目(http://race.gwy.591up.com)，在竞赛的时间段内数据库的写入压力很大。</p>
<p>当前问题：1、服务器带宽压力。2、数据库压力。</p>
<p>任何一个优化都要针对已经存在的问题，从服务器监控的报表可以看到我们这个网站应用服务器带宽压力、数据库服务器带宽压力都很大，应用服务器的CPU使用率不高，因此，主要的优化是对应用服务器带宽和数据库服务器的写入压力做的优化，因为目的很明确，效果也是比较明显的。</p>]]></description>
			<content:encoded><![CDATA[<p>随着公司项目的进一步推广，用户数量的增加，已经面临着单台服务器不能负载的问题。</p>
<p>这次的优化由于时间关系主要分两步走，首先优化应用层代码以提高单台服务器的负载和吞吐率。之后再进行分表，引入队列、MemCached等分布式应用。</p>
<p>项目背景：这是一个在线竞赛的项目(<a title="591UP公务员考试应战平台" href="http://race.gwy.591up.com" target="_blank">http://race.gwy.591up.com</a>)，在竞赛的时间段内数据库的写入压力很大。</p>
<p>当前问题：1、服务器带宽压力。2、数据库压力。</p>
<p>下图是Web服务器CPU使用率报表。</p>
<p><img class="aligncenter size-full wp-image-106" title="CPU使用率报表" src="http://blog.moozi.net/wp-content/uploads/2011/03/CPU.jpg" alt="" width="698" height="425" /></p>
<p>总体上应用层服务器的CPU使用率不高。</p>
<p>下图是Web服务器带宽报表。</p>
<p><img class="aligncenter size-full wp-image-107" title="Web服务器流量报表" src="http://blog.moozi.net/wp-content/uploads/2011/03/流量图.jpg" alt="" width="602" height="271" /> 从这个报表可以看到，每块竞赛带宽的占用都会出现一个很高的峰值。</p>
<p>我们再看数据库服务器的带宽报表。</p>
<p><img class="aligncenter size-full wp-image-108" title="数据库服务器带宽报表" src="http://blog.moozi.net/wp-content/uploads/2011/03/Database.jpg" alt="" width="596" height="247" /> 同样的，数据库服务器同样的在竞赛的时间点流量超大，很明显这种情况是不正常的，查询肯定是有问题的。</p>
<p>面对这样的问题，确定了第一期主要做以下的优化。</p>
<p>1、用flash storage做用户做题断点记录。（做题断点：类似程序断点，用户做到第N题时退出做答，下次进入时依然定位到第N题。）这里原来是用数据库存储的，但用户每做一题都会执行一条UPDATE语句，而数据库是MySQL的MyISAM引擎，更新操作经常被堵塞。</p>
<p>2、更改系统交卷行为。原来系统在用户做完提交竞赛后，会执行一条UPDATE语句更新用户提交试卷的时间点。同样的这个UPDATE也是在同一时间段内执行，和产品经理沟通后，确认在最后一分钟的时候可以不用再执行这个更新，允许用户的作答时间有1分钟的误差。</p>
<p>3、调整数据库的更新语句为插入语句，这个优化点因为时间问题，推迟到第二个优化阶段再处理。</p>
<p>4、调整应用服务器以支持LVS集群。对当前系统进行分析后，暂时可以不用调整代码直接部署集群，问题是在多台服务器内都会存在相同的进程内缓存，这种情况暂时是可以接受的，后期需要改到MemCached集中管理缓存。</p>
<p>5、等待成绩页面同一时间跳转的压力问题。在线竞赛的提交时间点很集中，用户做答完题目后，会统一跳转到一个页面等待答案（这时后台的Windows 服务在进行竞赛统计），这里服务器的并发、带宽压力都非常大。因此，优化这里不进行跳转，而是在当前的页面等待，并且会自动给不同的用户分配不同的等待时间，以避免占满服务器的带宽。</p>
<p>6、每场完整的公务员考试试卷，题目资源有150K-200K，因为作答和查看解析是在不同的页面，之前的实现会造成题目的多次加载，严重的浪费了带宽资源。于是这里优化成Handler输入静态资源加载，从服务器加载一次之后，后面所有的地方调用到题目都可以从浏览器的本地缓存中加载带省服务器带宽。同时，服务器上只对静态资源服务器开启了GZip压缩，对动态文件进行压缩会浪费服务器的CPU资源，而只对Handler输出的题目进行GZip压缩，一方面节省了CPU，另一方面把150K-200K的题目资源压缩到了50K左右。</p>
<p>7、数据库性能优化。调整了代码中查询的各个条件的位置，使查询语句能够更多的使用到索引。同时把原来每次一条的插入操作修改为一次插入多条等一些数据库查询优化。</p>
<p>任何一个优化都要针对已经存在的问题，从服务器监控的报表可以看到我们这个网站应用服务器带宽压力、数据库服务器带宽压力都很大，应用服务器的CPU使用率不高，因此，主要的优化是对应用服务器带宽和数据库服务器的写入压力做的优化，因为目的很明确，效果也是比较明显的。</p>
<p>文中提到了用Handler来输出静态资源让浏览器缓存，附上这个代码，其它的优化针对性很高，就不再啰嗦了，主要的还是记录下这次优化的工作方式和工作方法。</p>
<p>Handler输出的静态资源使用了.NET流压缩，于是我们声明一个压缩器接口。</p>
<pre class="brush:csharp">using System.IO;

namespace ND.Race.Compressor
{
    /// &lt;summary&gt;
    /// 压缩器接口
    /// &lt;/summary&gt;
    public interface ICompressor
    {
        string EncodingName { get; }
        bool CanHandle(string acceptEncoding);
        void Compress(string content, Stream targetStream);
    }
}</pre>
<p>GZip压缩器实现这个接口。</p>
<pre class="brush:csharp">using System.IO;
using System.IO.Compression;
using System.Text;

namespace ND.Race.Compressor
{
    public sealed class GZipCompressor : ICompressor
    {
        public string EncodingName
        {
            get { return "gzip"; }
        }

        public bool CanHandle(string acceptEncoding)
        {
            return !string.IsNullOrEmpty(acceptEncoding) &amp;&amp;
                   acceptEncoding.Contains("gzip");
        }

        public void Compress(string content, Stream targetStream)
        {
            using (var writer = new GZipStream(targetStream, CompressionMode.Compress))
            {
                var bytes = Encoding.UTF8.GetBytes(content);
                writer.Write(bytes, 0, bytes.Length);
            }
        }
    }
}</pre>
<p>同样的Deflate压缩器也实现这个接口。</p>
<pre class="brush:csharp">using System.IO;
using System.IO.Compression;
using System.Text;

namespace ND.Race.Compressor
{
    public sealed class DeflateCompressor : ICompressor
    {
        public string EncodingName
        {
            get { return "deflate"; }
        }

        public bool CanHandle(string acceptEncoding)
        {
            return !string.IsNullOrEmpty(acceptEncoding) &amp;&amp;
                   acceptEncoding.Contains("deflate");
        }

        public void Compress(string content, Stream targetStream)
        {
            using (var writer = new DeflateStream(targetStream, CompressionMode.Compress))
            {
                var bytes = Encoding.UTF8.GetBytes(content);
                writer.Write(bytes, 0, bytes.Length);
            }
        }
    }
}</pre>
<p>如果浏览器不支持流压缩，那我们只能直接输出内容了，因此我们还需要一个不进行压缩的处理类。</p>
<pre class="brush:csharp">using System.IO;
using System.Text;

namespace ND.Race.Compressor
{
    public sealed class NullCompressor : ICompressor
    {
        public string EncodingName
        {
            get { return "utf-8"; }
        }

        public bool CanHandle(string acceptEncoding)
        {
            return true;
        }

        public void Compress(string content, Stream targetStream)
        {
            using (targetStream)
            {
                var bytes = Encoding.UTF8.GetBytes(content);
                targetStream.Write(bytes, 0, bytes.Length);
            }
        }
    }
}</pre>
<p>现在我们就可以开始编码我们的Handler了。</p>
<pre class="brush:csharp">public class QuestionCacheHandler : IHttpHandler
{
    #region 静态变量
    /// &lt;summary&gt;
    /// 资源过期时间
    /// &lt;/summary&gt;
    private static readonly int durationInDays = 30;
    /// &lt;summary&gt;
    /// 流压缩接口
    /// &lt;/summary&gt;
    private static readonly ICompressor[] Compressors = {
                                                                new GZipCompressor(),
                                                                new DeflateCompressor(),
                                                                new NullCompressor()
                                                            };
    #endregion

    #region 私有变量
    /// &lt;summary&gt;
    /// 内存流压缩类
    /// &lt;/summary&gt;
    private ICompressor compressor;
    /// &lt;summary&gt;
    /// ETAG
    /// &lt;/summary&gt;
    private string eTagCacheKey;
    /// &lt;summary&gt;
    /// 竞赛场次Id
    /// &lt;/summary&gt;
    private long raceId;
    #endregion

    public void ProcessRequest(HttpContext context)
    {
        if (context == null) return;

        long.TryParse(context.Request.QueryString["raceId"], out raceId);
        if (raceId == 0) return;        

        var acceptEncoding = context.Request.Headers["Accept-Encoding"];
        compressor = Compressors.First(o =&gt; o.CanHandle(acceptEncoding));
        eTagCacheKey = string.Concat(raceId, "/etag");
        if (IsInBrowserCache(context, eTagCacheKey)) return;
        SendOutputToClient(context, true, eTagCacheKey);
    }

    /// &lt;summary&gt;
    /// 发送内容到客户端
    /// &lt;/summary&gt;
    /// &lt;param name="context"&gt;&lt;/param&gt;
    /// &lt;param name="insertCacheHeaders"&gt;&lt;/param&gt;
    /// &lt;param name="etag"&gt;&lt;/param&gt;
    private void SendOutputToClient(HttpContext context, bool insertCacheHeaders, string etag)
    {
        string content = "";
        MemoryStream memoryStream = new MemoryStream();
        compressor.Compress(content, memoryStream);
        byte[] bytes = memoryStream.ToArray();
        HttpResponse response = context.Response;
        if (insertCacheHeaders)
        {
            HttpCachePolicy cache = context.Response.Cache;
            cache.SetETag(etag);
            cache.SetOmitVaryStar(true);
            cache.SetMaxAge(TimeSpan.FromDays(durationInDays));
            cache.SetLastModified(DateTime.Now);
            cache.SetExpires(DateTime.Now.AddDays(durationInDays)); // HTTP 1.0 的浏览器使用过期时间
            cache.SetValidUntilExpires(true);
            cache.SetCacheability(HttpCacheability.Public);
            cache.SetRevalidation(HttpCacheRevalidation.AllCaches);
            cache.VaryByHeaders["Accept-Encoding"] = true;
        }
        response.AppendHeader("Content-Length", bytes.Length.ToString(System.Globalization.CultureInfo.InvariantCulture));
        response.ContentType = "text/plain";
        response.ContentType = "application/x-javascript";
        response.AppendHeader("Content-Encoding", compressor.EncodingName);
        if (bytes.Length &gt; 0)
            response.OutputStream.Write(bytes, 0, bytes.Length);
        if (response.IsClientConnected)
            response.Flush();
    }

    /// &lt;summary&gt;
    /// 是否浏览器已经缓存
    /// &lt;/summary&gt;
    /// &lt;param name="context"&gt;&lt;/param&gt;
    /// &lt;param name="etag"&gt;&lt;/param&gt;
    /// &lt;returns&gt;&lt;/returns&gt;
    private bool IsInBrowserCache(HttpContext context, string etag)
    {
        string incomingEtag = context.Request.Headers["If-None-Match"];
        if (String.Equals(incomingEtag, etag, StringComparison.Ordinal))
        {
            context.Response.Cache.SetETag(etag);
            context.Response.AppendHeader("Content-Length", "0");
            context.Response.StatusCode = (int)System.Net.HttpStatusCode.NotModified;
            context.Response.End();
            return true;
        }
        return false;
    }

    public bool IsReusable
    {
        get
        {
            return false;
        }
    }
}</pre>
<p>服务器端代码通过Http请求Header的Accept-Encoding来判断是否支持流压缩，再通过Header的etag来判断浏览器中是否已经有缓存副本。</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.joyup.me/archives/web-performance-optimization-practice-application-optimization.html/feed</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>《SaaS架构设计》读书笔记——可伸缩的应用架构</title>
		<link>http://blog.joyup.me/archives/saas-architecture-scalable-application-architecture.html</link>
		<comments>http://blog.joyup.me/archives/saas-architecture-scalable-application-architecture.html#comments</comments>
		<pubDate>Mon, 21 Feb 2011 16:58:32 +0000</pubDate>
		<dc:creator>jianphu</dc:creator>
				<category><![CDATA[读书笔记]]></category>
		<category><![CDATA[Saas]]></category>
		<category><![CDATA[应用层优化]]></category>
		<category><![CDATA[数据库优化]]></category>
		<category><![CDATA[数据库分表]]></category>

		<guid isPermaLink="false">http://blog.moozi.net/?p=101</guid>
		<description><![CDATA[<p>对于SaaS应用的可伸缩，最理想的情况：随着用户数的增大，系统架构不用做调整，而仅需要增加/增强相应的硬件设备（应用服务器、数据库服务器）即可。而通常强调的应用架构具有可伸缩性，一般指的是可以实现"Scale out"，即水平扩展或者向外扩展。而"Scale up"通常为垂直扩展或者向上扩展，也就是增强硬件设备，这种方式几乎是任何应用架构普遍适用的，但是通常都会面临高成本的问题。</p>
<p>1、应用服务器层的水平扩展。实现应用服务器层的负载均衡，是实现应用服务器水平扩展的最主要手段，具体实现负载均衡的策略有以下两种：a.基于硬件负载均衡设备实现负载均衡，如F5设备。b.基于软件的方式实现负载均衡，例如通过配置Apache Http Server。</p>
<p>2、数据库层的水平扩展。相对于应用服务器层的水平扩展，数据库层的水平扩展更难实现。实现数据库的水平扩展也有多种方式。</p>]]></description>
			<content:encoded><![CDATA[<p>      对于SaaS应用的可伸缩，最理想的情况：随着用户数的增大，系统架构不用做调整，而仅需要增加/增强相应的硬件设备（应用服务器、数据库服务器）即可。而通常强调的应用架构具有<span style="color: #ff0000;">可伸缩性，一般指的是可以实现”Scale out”，即水平扩展或者向外扩展。而”Scale up”通常为垂直扩展或者向上扩展，也就是增强硬件设备，这种方式几乎是任何应用架构普遍适用的，但是通常都会面临高成本的问题。</span></p>
<p>      <span style="color: #ff0000;"><strong>1、应用服务器层的水平扩展。</strong></span>实现应用服务器层的负载均衡，是实现应用服务器水平扩展的最主要手段，具体实现负载均衡的策略有以下两种：<br />
      a.基于硬件负载均衡设备实现负载均衡，如F5设备。<br />
      b.基于软件的方式实现负载均衡，例如通过配置Apache Http Server。</p>
<p>      Apache可以实现负载均衡，根据服务器的压力情况，将每个用户请求分布到不同的应用服务器上。但大部分应用的用户请教是有状态的（一般使用Session记录用户状态）。这种情况下，如何能够在多台应用服务器之间保持用户状态，将是实现应用服务器层水平扩展的关键。<br />
      <strong>a.Session复制。</strong>Session复制的技术实现非常复杂，在大规模集群中实用性并不强，服务器之间大量的Session复制会严重影响这些服务器的性能。而随着服务器数量的增加，这种性能影响会显得更加突出，甚至不可接受。在大部分互联网应用中，Session复制技术应该是很少采用的。<br />
      <strong>b.Session Sticky。</strong>为了避免Session复制所带来的性能影响，更简单、也是更高效的一种做法是Session Sticky。这种方式将同一用户的请求转发到特定的JBoss服务器上，避免了集群中Session的复制。Session Sticky的实现非常简单，但是这种方式不能满足fail-over的需求。即当一台应用服务器down的时候，这台服务器上正在访问的所有用户的Session都失效了，所有用户不得不再次重新登录。而且这种方式还容易导致负载不够均衡。<br />
      <strong>c.基于Cache的集中式Session。</strong>这种方案通常使用集中式的Cache来代替本地Session。集中式的Session服务器采用的是MemCached，其本身也具备水平扩展能力。当Session数量大到一台Cache服务器都不能承受的程度时，我们也仅需要增加相应的Cache服务器即可。</p>
<p>      三种水平扩展方式的比较：</p>
<table style="width: 540px; border: #e6e6e6 1px solid;" cellspacing="1" cellpadding="0">
<thead>
<tr style="text-align: center; background: #f6f6f6; height: 24px; font-size: 14px; vertical-align: middle; font-weight: bold;">
<td style="width: 120px;">实现方式</td>
<td style="width: 200px;">优势</td>
<td style="width: 220px;">劣势</td>
</tr>
</thead>
<tbody style="font-size: 12px; padding: 3px 5px;">
<tr>
<td style="font-weight: bold;">Session复制</td>
<td>服务器负载可以得到较好的均衡，也可以确保fail-over的支持</td>
<td>Session复制会对服务器网络环境带来巨大的压力，尤其在应用服务器数量较大的时候，基本不适用于大型互联网，而且需要相应的应用服务器支持</td>
</tr>
<tr>
<td style="font-weight: bold;">Session Sticky</td>
<td>实现比较简单，在Load Balance层做相应的配置即可，不会带来Session复制引起的网络环境压力</td>
<td>不能实现完全的负载均衡，部分情况下负载会极端失衡，无法实现fail-over</td>
</tr>
<tr>
<td style="font-weight: bold;">基于Cache的集中式Session</td>
<td>应用服务器层无状态，可以实现完全的负载均衡，不会带来Session复制引起的网络环境压力</td>
<td>实现相对复杂一些，Cache本身可靠性不能绝对保证，可能会造成部分Session的丢失</td>
</tr>
</tbody>
</table>
<p>      <span style="color: #ff0000;"><strong>2、数据库层的水平扩展。</strong></span>相对于应用服务器层的水平扩展，数据库层的水平扩展更难实现。实现数据库的水平扩展也有多种方式：<br />
      <strong>a.数据库的垂直切分</strong>：将不同的功能模块所涉及的表划分到不同的物理数据库中，从而将对这些表的访问压力分担到多个不同的物理数据库中。<br />
      <strong>b.数据库的读/写分离</strong>：同一个数据库在多个物理服务器上具有多份Copy，彼此同步。然后将对于数据库的写操作都统一到一个主服务器上，而读操作则分摊到多台从服务器上。通过读/写分离，实现数据库访问压力的分担。<br />
      <strong>c.数据库的水平切分</strong>：将原来存储在一个数据表中的数据，按照一定的规则，切分到多个不同的物理数据库中。每个数据库的数据结构完全相同，但是数据各不相同。最终对于业务数据的访问，会根据其数据所在的数据库，定位到某一个数据库中查询。</p>
<p>     <span style="color: #ff0000;"><strong> 2.1、数据库的垂直切分。</strong></span>尽管数据库的垂直切分是最容易想到的，但对于大部分应用而言，除非模块间的关联很少，否则要实现垂直切分也不容易：<br />
      a.原本可能存在的表连接，需要想办法去除。<br />
      b.原本同一个数据库的事务操作，可能会变成跨库事务。可见数据库的垂直切分是一个可以适当采用，但很难广泛采用的数据库层扩展技术。</p>
<p>     <strong> 2.2、数据库的读/写分离技术。</strong>对于读多写少的互联网应用，会广泛采用读/写分离技术。尽管Slave Database Server数量是可以线性扩展的，但是基于以下两个原因，Slave Database Server也不是越多越好。<br />
      a.如果应用读/写比例不是很悬殊，单纯增加Slave Database Server对于应用性能提升并且没有特别的作用，通常情况下，Slave Server的数量与读/写比例对应。<br />
      b.Slave Database Server过多可能造成Master-Slave之间的同步性能降低。</p>
<p>     <strong> 2.3、数据库的水平切分。</strong>无论数据库的垂直切分还是读/写分离，对于实现数据库层的水平扩展，适用范围都比较狭窄。数据库的水平切分，通常有两种处理方式：<br />
      a.一种是采用Hash算法。采用Hash算法实现更为简单，性能也高，但是扩展性略差。因为Hash算法一开始就确定，如果后面变更的话，会涉及数据的迁移。<br />
      b.另一种是将对应到哪个物理数据库也作为关系表存储在集中式的租户数据库中。这种方式更为常见。在用户登录时，通过查询相应的关系表，即可以确定其对应租房的业务数据存储在具体的哪个物理数据库中。</p>
<p>      三种数据库层的水平扩展方案对比：</p>
<table style="width: 540px; border: #e6e6e6 1px solid;" cellspacing="1" cellpadding="0">
<thead>
<tr style="text-align: center; background: #f6f6f6; height: 24px; font-size: 14px; vertical-align: middle; font-weight: bold;">
<td style="width: 120px;">实现方式</td>
<td style="width: 200px;">优点</td>
<td style="width: 220px;">不足</td>
</tr>
</thead>
<tbody style="font-size: 12px; padding: 3px 5px;">
<tr>
<td style="font-weight: bold;">垂直切分</td>
<td>实现简单</td>
<td>扩展能力有限，强关联的应用不容易垂直切分</td>
</tr>
<tr>
<td style="font-weight: bold;">读/写分离</td>
<td>可有效分担读的压力，主要在数据库层扩展，应用修改较小</td>
<td>对于读的比例不高的应用，扩展能力有限。依赖于数据库本身的同步能力</td>
</tr>
<tr>
<td style="font-weight: bold;">水平切分</td>
<td>SaaS应用中普遍适用，扩展性强，基本无限扩展</td>
<td>实现比较复杂，应用一般需要做较大改造。需要预先做好负载规划，后期数据迁移比较困难</td>
</tr>
</tbody>
</table>
]]></content:encoded>
			<wfw:commentRss>http://blog.joyup.me/archives/saas-architecture-scalable-application-architecture.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>《SaaS架构设计》读书笔记——高性能的Multi-Tenant最佳实践</title>
		<link>http://blog.joyup.me/archives/saas-high-performance-multi-tenant.html</link>
		<comments>http://blog.joyup.me/archives/saas-high-performance-multi-tenant.html#comments</comments>
		<pubDate>Sun, 20 Feb 2011 16:59:58 +0000</pubDate>
		<dc:creator>jianphu</dc:creator>
				<category><![CDATA[读书笔记]]></category>
		<category><![CDATA[Saas]]></category>
		<category><![CDATA[WPO]]></category>
		<category><![CDATA[应用层优化]]></category>
		<category><![CDATA[数据库优化]]></category>

		<guid isPermaLink="false">http://blog.moozi.net/?p=99</guid>
		<description><![CDATA[<p>Multi-Tenant（多租户）的SaaS可以从数据库优化、应用层优化、Web层优化这几个方面来提高SaaS的性能。</p>
<p>1、数据库层性能优化</p>
<p>2、应用层性能优化</p>
<p>3、Web层性能优化</p>]]></description>
			<content:encoded><![CDATA[<p>      Multi-Tenant（多租户）的SaaS可以从数据库优化、应用层优化、Web层优化这几个方面来提高SaaS的性能。</p>
<p>      <span style="color: #ff0000;"><strong>1、数据库层性能优化</strong></span></p>
<p>      <strong>1.1、建立合适的索引。</strong>建立合适的索引对基于数据库的应用都非常重要。建立合适的索引要注意以下几个问题：<br />
      a.索引应该创建在条件(WHERE)，排序(ORDER BY)，分组(GROUP BY)等操作所涉及的列上。<br />
      b.索引应该有较强的选择性，即应尽可能建立在重复数据少的数据列中。<br />
      c.如果多个条件经常需要组合起来查询，应合理使用联合索引。<br />
      d.一次查询中只能使用一个索引，可使用相应的分析工具分析索引效果。<br />
      e.索引不是越多越好，过多的索引可能导致CUD(新增、修改、删除)的性能降低，并且占用更多的空间。<br />
      另外一个，在互联网应用中不应该使用包含LIKE的模糊查询，要实现模糊查询，更适合的方式是搜索引擎。</p>
<p>      <strong>1.2、消除大数据表连接。</strong>消除表连接有以下几种解决方案。</p>
<table style="width: 540px; border: #e6e6e6 1px solid;" cellspacing="1" cellpadding="0">
<thead>
<tr style="text-align: center; background: #f6f6f6; height: 24px; font-size: 14px; vertical-align: middle; font-weight: bold;">
<td style="width: 120px;">解决方案</td>
<td style="width: 200px;">适用场景</td>
<td style="width: 220px;">示例场景</td>
</tr>
</thead>
<tbody style="font-size: 12px; padding: 3px 5px;">
<tr>
<td style="font-weight: bold;">冗余存储关联字段</td>
<td>业务需求上可以接受冗余导致的不一致，或者冗余数据可以很容易被同步更新</td>
<td>订单列表查询时，希望查看到订单的客户名称，原本订单上只记录了客户ID，通过关联客户表查询客户名称</td>
</tr>
<tr>
<td style="font-weight: bold;">Cache缓存</td>
<td>变动概率不高，但是对于数据一致性要求较高</td>
<td>用户名(UserName)被很多业务所关联查询，但是也不适用于冗余方案（业务上不允许不一致，并且要保持所有冗余存储的地方同步更新很困难）</td>
</tr>
<tr>
<td style="font-weight: bold;">直接删除关联字段</td>
<td>不是必须包含的被关联表的字段，可以直接从列表查询中去除</td>
<td>订单列表中的产品型号等非关键字，其实并不一定要包含在订单列表中</td>
</tr>
<tr>
<td style="font-weight: bold;">拆分成多次查询</td>
<td>对于单个数据的查询，如果涉及多张关联表，有时多分次查询会比一次复杂的关联查询更为合适</td>
<td>订单表单中需要查询到关联产品的编码、型号等很多字段</td>
</tr>
</tbody>
</table>
<p>      <strong>1.3、避免复杂SQL。</strong>避免复杂的SQL可以参考这样一个原则：一般需要考虑把复杂的SQL分解成多个简单的SQL执行，将复杂的运算尽量留在应用服务器层。因为一般情况下，应用服务器层的水平扩展更容易实现，而数据库层的扩展是比较困难的。</p>
<p>      <span style="color: #ff0000;"><strong>2、应用层性能优化。</strong></span>应用层不是首先提及的性能优化对象，原因是在大部分情况下，应用层更容易通过简单的水平扩展来增强系统处理能力。应用层常见的优化策略有这些：</p>
<p>      <strong>2.1、Cache。</strong>其实很难说Cache就是应用层的优化策略。互联网应用属于“读多写少”，这种情况下做Cache，其实也是对数据库层的一种优化，因为避免了对于数据库的频繁访问。总的说来，Cache应用具备如下特征：<br />
      a.需要Cache的数据应该是读多写少的。<br />
      b.由于Cache数据是易失的，因为为了保证可靠性，一般情况下应用中还需要增加一层逻辑：当Cache中访问不到时，需要访问数据库，并重新将其载入Cache。</p>
<p>      <strong>2.2、统计和报表。</strong>为了缓解实时计算给数据库带来的巨大压力，统计和报表运算有如下的一些优化策略，不过具体的优化策略还是跟应用场景相关。<br />
      a.对于统计结果实时性不高的，并且历史数据是不允许修改的情况下，通常可以采用后台任务定时统计的策略。<br />
      b.如果历史数据是可以修改的，则相应的后台任务定时统计方案会略有些复杂：对于被修改的历史数据，历史统计结果和报表需要重新运算。<br />
      c.对于部分统计结果实时性要求很高的功能，则可以在后台任务定时统计的基础上，再加上增量数据实时统计的结果。</p>
<p>      <strong>2.3、使用全文索引进行搜索。</strong></p>
<p>      <strong>2.4、异步操作。</strong>异步操作并没有从本质上提升系统性能或者数据吞吐量。但是异步操作使得用户体验得到了极大的提升，同时也能降低系统的并发负载。因此，将同步操作变成异步操作也是提升性能的一种重要手段。</p>
<p>      <span style="color: #ff0000;"><strong>3、Web层性能优化。</strong></span>关于Web层性能优化主要有Web开发的性能优化和Http服务器的性能优化。Web开发的性能优化可以查阅我的《<a title="页面加载时间优化笔记" href="http://blog.moozi.net/archives/ie-ff-pipelining-max-connections.html">页面加载时间优化笔记</a>》。</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.joyup.me/archives/saas-high-performance-multi-tenant.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>一次艰难的mono环境部署</title>
		<link>http://blog.joyup.me/archives/apache-mod_mono-mono-2-8-2-in-sles-10.html</link>
		<comments>http://blog.joyup.me/archives/apache-mod_mono-mono-2-8-2-in-sles-10.html#comments</comments>
		<pubDate>Wed, 26 Jan 2011 11:50:33 +0000</pubDate>
		<dc:creator>jianphu</dc:creator>
				<category><![CDATA[DotNet]]></category>
		<category><![CDATA[apache]]></category>
		<category><![CDATA[mod_mono]]></category>
		<category><![CDATA[mono]]></category>
		<category><![CDATA[SLES]]></category>
		<category><![CDATA[SUSE]]></category>
		<category><![CDATA[xsp]]></category>
		<category><![CDATA[腾讯社区开放平台]]></category>

		<guid isPermaLink="false">http://blog.moozi.net/?p=94</guid>
		<description><![CDATA[<p>继上篇文章《Mono的兼容性问题》之后，现在总结一下我在suse 10中部署mono环境遇到的一些问题及其处理办法。</p>
<p>腾讯社区开放平台提供的体验区的服务器环境是这样的：Linux version 2.6.16.60 (gcc version 4.1.2 20070115 (SUSE Linux))</p>
<p>这个Linux内核的版本号应该是SLES（SUSE Linux Enterprise Server 10 SP2）的操作系统。mono原来的版本号是1.2.2，而我们需要2.8.2的mono。腾讯的服务器是没有配置dns解析功能的，所以不能直接用yast2来通过更新源来升级mono；对于可以用yast2安装的，可以参考我的《在openSUSE中部署基于apache的mono环境》。而用rpm手工安装，太多的程序依赖处理起来很麻烦，只好用通过编译mono源码的方式来安装了。</p>]]></description>
			<content:encoded><![CDATA[<p>继上篇文章《<a title="在MONO 2.8.2中遇到的一些兼容性问题" href="http://blog.moozi.net/archives/web-services-compatible-in-mono-2-8-2.html">Mono的兼容性问题</a>》之后，现在总结一下我在suse 10中部署mono环境遇到的一些问题及其处理办法。</p>
<p>腾讯社区开放平台提供的体验区的服务器环境是这样的：</p>
<p>操作系统：<span style="color: #ff0000;">Linux version 2.6.16.60 (gcc version 4.1.2 20070115 (SUSE Linux))</span></p>
<p>这个Linux内核的版本号应该是SLES（SUSE Linux Enterprise Server 10 SP2）的操作系统。mono原来的版本号是1.2.2，而我们需要2.8.2的mono。所以，第一步的工作就是升级mono。</p>
<p>通过腾讯的跳板机登录到服务器后，需要跟腾讯的运维人员申请root权限，这个就不用多说了。</p>
<p>部署mono环境的具体步骤如下：</p>
<p>1、下载mono并上传到腾讯的服务器上。</p>
<p>腾讯的服务器是没有配置dns解析功能的，所以不能直接用yast2来通过更新源来升级mono；对于可以用yast2安装的，可以参考我的《<a title="在openSUSE中部署基于apache的mono环境" href="http://blog.moozi.net/archives/apache-mod_mono-in-opensuse-linux.html">在openSUSE中部署基于apache的mono环境</a>》。而用rpm手工安装，太多的程序依赖处理起来很麻烦，只好用通过编译mono源码的方式来安装了。</p>
<p><a href="http://ftp.novell.com/pub/mono/archive/2.8/sources/" target="_blank">在这里可以下载到mono的源码</a>，我们只需要<span style="color: #ff0000;">mono-2.8.tar.bz2/xsp-2.8.tar.bz2/mod_mono-2.8.tar.bz2</span>。mono-2.8.tar.bz2当然是mono的核心了，xsp-2.8.tar.bz2是一个轻量级的web服务器，而mod_mono-2.8.tar.bz2是apache的模块，使用这个来解析.net开发的网站。</p>
<p>上传文件到腾讯的服务器需要分2部走，首先用rz -be把文件上传到腾讯的跳板机，然后再用scp命令把文件传到目标服务器。</p>
<p>2、安装mono-2.8.tar.bz2。</p>
<p>切换到mono-2.8.bar.bz2的目录，执行</p>
<pre class="brush:shell">tar xf mono-2.8.tar.bz2</pre>
<p>解压mono源码包。之后再</p>
<pre class="brush:shell">cd mono-2.8</pre>
<p>进入解压出来的文件夹后，执行配置命令，这里都使用默认配置，如果使用&#8211;prefix指定目录的话，需要修改很多配置，操作起来非常麻烦。</p>
<pre class="brush:shell">./configure</pre>
<p>之后就是最常规的</p>
<pre class="brush:shell">make</pre>
<p>进入漫长的编译过程。中间过程如果出错，根据它的提示安装相当的文件。然后直接进行安装。</p>
<pre class="brush:shell">make install</pre>
<p>到这里，mono就安装完成了。这步非常简单。我走的弯路是腾讯要求要把软件都安装到/usr/local/services/目录，而mono指定安装目录后，需要设置系统变量及在后面的软件安装过程都要显式的指定mono目录，一直都没有折腾好，最后只好使用默认安装，结果一次就好了。</p>
<p>3、安装xsp-2.8.tar.bz2。</p>
<p>一样解压文件并执行configure，</p>
<pre class="brush:shell">tar xf xsp-2.8.tar.bz2
cd xsp-2.8
./configure</pre>
<p>到这里一直都是正确的，但是执行</p>
<pre class="brush:shell">make</pre>
<p>的时候，就不能进行下去了。会有这样一个提示：</p>
<blockquote><p>Your sqlite3 version is old &#8211; please upgrade to at least v3.5.0!</p>
<p>Unhandled Exception:System.EntryPointNotFoundException: sqlite3_next_stmt</p>
<p>&#8230;&#8230;.</p></blockquote>
<p>提示这个错误是因为linux本身的sqlite3版本号不够，默认的是3.1.3。你要么升级sqlite要么修改xsp的文件。查看xsp的安装文件发现是在编译xsp的demo文件时产生的错误。在<span style="color: #ff0000;">$XSP_PATH/test/1.1/webcontrols</span>目录下有<span style="color: #ff0000;">dbpage1.aspx/dbpage1.sqlite/dbpage2.aspx/dbpage2.sqlite/dbpage_test_setup.cs</span>这几个文件，错误就是在编译这些文件的时候产生的。于是我修改了这个目录下的<span style="color: #ff0000;"><strong>Makefile/Makefile.am/Makefile.in</strong></span>这三个文件，把和sqlite有关的都删除掉了。</p>
<p>分别编辑这三个文件，找到：</p>
<pre class="brush:shell">samples_Data =</pre>
<p>把这个值的</p>
<pre class="brush:shell">dbpage1.aspx \
dbpage2.aspx \</pre>
<p>把这两个删掉，还有：</p>
<pre class="brush:shell">sqlitedir = $(pkglibdir)/test/1.1/webcontrols
sqlite_DATA = dbpage.sqlite \
                       dbpage2.sqlite

samplesbindir = ....
samplesbin_SCRIPTS = ...
dbpage_test_setup_build = ...
EXTRA_Dist = $(sqlite_DATA) ...
all: all-am</pre>
<p>这些都删掉。在页面的底部还有：</p>
<pre class="brush:shell">dbpage_test_setup.exe :$(dbpage_test_setup_build)....
# ...... 一直到 ......
dbpage2.sqlite: dbpage_test_setup.exe
    $(RUNTIME) dbpage_test_setup.exe</pre>
<p>全部都需要删掉。三个页面都执行这样的删除操作。删除完后，再make，就能成功编译了。完了再</p>
<pre class="brush:shell">make install</pre>
<p>安装mono最麻烦的问题就是这里编译出错。网上查这个编译错误，貌似很多人都有遇到，但是又都没有说怎么解决。期间我还尝试升级sqlite3，可是没有升级成功。最后一个文件一个文件打开来看，才发现Makefile文件的秘密。主要是第一次折腾linux系统，所以多走了很多弯路。</p>
<p>4、安装mod_mono-2.8.tar.bz2。</p>
<p>安装这个也没有遇到什么问题，主要是如果你的apache文件不是安装在默认目录的，需要在configure命令时指定apache的bin目录。</p>
<p>5、都安装好后，剩下的就是配置apache了。</p>
<p>编辑<span style="color: #ff0000;">$APACHE_PATH/conf/httpd.conf</span>文件，增加一个配置：</p>
<pre class="brush:shell">#Mono module
Include conf/mod_mono.conf</pre>
<p>安装mod_mono之后，这个mod_mono.cnf是自动生成的，和apache的httpd.conf在相同的目录。</p>
<p>对于网站的配置，Mono的官方网站有一个<a href="http://go-mono.com/config-mod-mono/" target="_blank">mod_mono的配置生成工具</a>，建议使用里面的Virtual Host的方式来生成配置文件。它能帮我们生成这样的配置文件：</p>
<pre class="brush:shell">&lt;VirtualHost *:80&gt;
  ServerName mono.moozi.net
  ServerAdmin moozi@moozi.net
  DocumentRoot /srv/www/mono.moozi.net
  # MonoServerPath can be changed to specify which version of ASP.NET is hosted
  # mod-mono-server1 = ASP.NET 1.1 / mod-mono-server2 = ASP.NET 2.0
  # For SUSE Linux Enterprise Mono Extension, uncomment the line below:
  # MonoServerPath mono.moozi.net "/opt/novell/mono/bin/mod-mono-server2"
  # For Mono on openSUSE, uncomment the line below instead:
  MonoServerPath mono.moozi.net "/usr/bin/mod-mono-server2"

  # To obtain line numbers in stack traces you need to do two things:
  # 1) Enable Debug code generation in your page by using the Debug="true"
  #    page directive, or by setting &lt;compilation debug="true" /&gt; in the
  #    application's Web.config
  # 2) Uncomment the MonoDebug true directive below to enable mod_mono debugging
  MonoDebug mono.moozi.net true

  # The MONO_IOMAP environment variable can be configured to provide platform abstraction
  # for file access in Linux.  Valid values for MONO_IOMAP are:
  #    case
  #    drive
  #    all
  # Uncomment the line below to alter file access behavior for the configured application
  MonoSetEnv mono.moozi.net MONO_IOMAP=all
  #
  # Additional environtment variables can be set for this server instance using
  # the MonoSetEnv directive.  MonoSetEnv takes a string of 'name=value' pairs
  # separated by semicolons.  For instance, to enable platform abstraction *and*
  # use Mono's old regular expression interpreter (which is slower, but has a
  # shorter setup time), uncomment the line below instead:
  # MonoSetEnv mono.moozi.net MONO_IOMAP=all;MONO_OLD_RX=1

  MonoApplications mono.moozi.net "/:/srv/www/mono.moozi.net"
  &lt;Location "/"&gt;
    Allow from all
    Order allow,deny
    MonoSetServerAlias mono.moozi.net
    SetHandler mono
  &lt;/Location&gt;
&lt;/VirtualHost&gt;</pre>
<p>为了提高apache的处理性能，我们需要对这个配置再做一些修改，最后的配置如下：</p>
<pre class="brush:shell">&lt;VirtualHost *:80&gt;
  ServerName mono.moozi.net
  ServerAdmin mono@moozi.net
  DocumentRoot /srv/www/mono.moozi.net/

  MonoServerPath mono.moozi.net "/usr/local/bin/mod-mono-server4" #这里我用.net 4.0
  MonoDebug mono.moozi.net true
  MonoSetEnv mono.moozi.net MONO_IOMAP=all  

  MonoApplications mono.moozi.net  "/:/srv/www/mono.moozi.net/"
  &lt;Location "/"&gt;
    Allow from all
    Order allow,deny
    MonoSetServerAlias mono.moozi.net
    SetHandler mono
  &lt;/Location&gt;
  &lt;Location ~ "^/(styles|scripts|images)/"&gt;
    SetHandler default-handler
  &lt;/Location&gt;
  &lt;Location ~ "^/[^/]+\.(txt|html|ico|js|css|jpg|jpeg|gif|png)"&gt;
    SetHandler default-handler
  &lt;/Location&gt;
&lt;/VirtualHost&gt;</pre>
<p>这里我们<strong>增加了两个&lt;Location /&gt;配置节，让静态资源文件使用apache的默认handler处理</strong>。</p>
<p>最后，重启apache就好了。</p>
<p>经过了近一周时间的折腾，终于成功地在腾讯社区开放平台部署好MONO环境。</p>
<p>我们的应用是腾讯社区开放平台的首个.NET应用，测试了一下午，程序运行状态良好。从感官上来看，响应比IIS还快&#8230;当然，事实还有待于验证。</p>
<p>杯具的是腾讯体验区的web服务器今天封网了，要年后初九才能上线到体验区&#8230;</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.joyup.me/archives/apache-mod_mono-mono-2-8-2-in-sles-10.html/feed</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>在MONO 2.8.2中遇到的一些兼容性问题</title>
		<link>http://blog.joyup.me/archives/web-services-compatible-in-mono-2-8-2.html</link>
		<comments>http://blog.joyup.me/archives/web-services-compatible-in-mono-2-8-2.html#comments</comments>
		<pubDate>Sun, 23 Jan 2011 16:21:34 +0000</pubDate>
		<dc:creator>jianphu</dc:creator>
				<category><![CDATA[DotNet]]></category>
		<category><![CDATA[MoMA]]></category>
		<category><![CDATA[mono]]></category>
		<category><![CDATA[xsp]]></category>

		<guid isPermaLink="false">http://blog.moozi.net/?p=93</guid>
		<description><![CDATA[<p>最近公司的一个项目因为要接入腾讯开放平台，需要转移到mono环境中，在做mono兼容性调整的时候遇到了一些问题，记录下来跟大家分享一下。</p>
<p>总的说来，mono整体的兼容性是非常不错的，在linux中跑起来很流畅，mono 2.8.2已经不再是玩具了。关于mono，园子里的geffzhang有很深入的研究，有兴趣的同学可以去看看他的博客。</p>]]></description>
			<content:encoded><![CDATA[<p>最近公司的一个项目因为要接入腾讯开放平台，需要转移到mono环境中，在做mono兼容性调整的时候遇到了一些问题，记录下来跟大家分享一下。</p>
<p>首先，要把.NET Framework的应用迁移到mono平台，一定要借助这个软件：<a href="http://www.mono-project.com/MoMA" target="_blank">MoMA</a>。Mono Migration Analyzer (Moma)是一个用于开发者使用的MS .net下开发的应用程序迁移到Mono平台的不兼容性检测工具。工具通过分析.dll或者.exe程序集的代码是否符合ECMA CLI，大家都知道符合ECMA CLI标准的.net 程序可以运行于MS .net和Mono平台。这个工具可以帮助我们生成一个很详细的mono兼容性文档，可以帮助我们调整程序的兼容性。</p>
<p>不过这个工具的分析结果也不完全正确，一些实现方式MoMA提示不兼容，但在mono中是可以正常使用的。</p>
<p>遇到的一些不兼容的问题：</p>
<p>1、项目是使用JS来调用Web Services来做的，用xsp做WEB服务器端的时候发现如果Web Services接口中有使用可空参数的时候，不能正常使用，即使回传的值不是null，接口依然只能接收到null值。</p>
<p>2、如果使用nginx通过调用mono的fastcgi来做WEB服务器，这种方式将不支持Web Services。Apache的mod_mono暂未做测试。</p>
<p>3、在mono环境中，TimeZoneInfo.Local无法使用，这是mono的一个BUG。</p>
<p>4、MySQL Connector的mono版本中有2个类库，一个是.net 2.0，一个是.net 4.0。如果把2.0的类库也用MoMA分析，会提示很多不支持的方法；如果你是用nginx的fastcgi解析.net程序，需要使用4.0的MySQL类库；而在xsp中，使用2.0的类库就好了。</p>
<p>5、如果你的操作系统是SLES，建议获取mono的源码编译完装，会让你省去很多事情。</p>
<p>6、在SLES编译XSP源码时，你系统的sqlite3的版本号要大于3.5.0，否则编译不过。</p>
<p>7、把项目转到mono平台，风险还是非常大的，非常多第三方类库不能直接在mono平台中使用，如Combres/log4net/Quartz.NET等等，都需要修改源代码。</p>
<p>暂时先整理这些问题，后面会补上在SLES中部署mono环境的详细过程。</p>
<p>总的说来，mono整体的兼容性是非常不错的，在linux中跑起来很流畅，mono 2.8.2已经不再是玩具了。关于mono，园子里的<a href="http://www.cnblogs.com/shanyou/" target="_blank">geffzhang</a>有很深入的研究，有兴趣的同学可以去看看他的博客。</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.joyup.me/archives/web-services-compatible-in-mono-2-8-2.html/feed</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
	</channel>
</rss>

