C# 线程池示例

以下三个代码示例演示 QueueUserWorkItem 和 RegisterWaitForSingleObject 方法。

第一个示例使用 QueueUserWorkItem 方法将一个由 ThreadProc 方法表示的非常简单的任务排入队列。

using System;
using System.Threading;
public class Example {
    public static void Main() {
        // 将任务排队
        ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadProc));
       
        Console.WriteLine("Main thread does some work, then sleeps.");
        // 如果你注释掉这个Sleep, 而线程池使用后台线程,
         // 那么主线程将会停止运行,并在线程池任务运行之前退出
         // (这是一个简单的线程池运行环境例子)
        Thread.Sleep(1000);

        Console.WriteLine("Main thread exits.");
    }

    // 这是执行任务的线程子程序
    static void ThreadProc(Object stateInfo) {
        // 没有state对象被传递到QueueUserWorkItem, 所以
        // stateInfo是null.
        Console.WriteLine("Hello from the thread pool.");
    }
}

为 QueueUserWorkItem 提供任务数据
下面的代码示例使用 QueueUserWorkItem 方法将一个任务排队并为该任务提供数据。

using System;
using System.Threading;

// TaskInfo 类 为任务提供将在线程池线程中执行的 state 信息
public class TaskInfo {
    // 任务所需的State信息,这些类成员可以是任务需要的确定的只读属性,
    // 可读写属性,或者其它。
    public string Boilerplate;
    public int Value;

    // Public 构造函数提供一个非常简单途径用来接收任务所需要的信息
    public TaskInfo(string text, int number) {
        Boilerplate = text;
        Value = number;
    }
}

public class Example {
    public static void Main() {
        // 创建一个包含任务所需要信息的对象。
        TaskInfo ti = new TaskInfo("This report displays the number {0}.", 42);

        // 将任务排队并传入所需信息
        if (ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadProc), ti)) {   
            Console.WriteLine("Main thread does some work, then sleeps.");

            // 如果你注释掉这个Sleep, 而线程池使用后台线程,
              // 那么主线程将会停止运行,并在线程池任务有机会运行之前退出
              // (这是一个简单的线程池运行环境例子)
            Thread.Sleep(1000);

            Console.WriteLine("Main thread exits.");
        }
        else {
            Console.WriteLine("Unable to queue ThreadPool request.");
        }
    }

    // 这个线程子程序执行独立的任务,在这里转换并打印一个非常简单的报告
    static void ThreadProc(Object stateInfo) {
        TaskInfo ti = (TaskInfo) stateInfo;
        Console.WriteLine(ti.Boilerplate, ti.Value);
    }
}

RegisterWaitForSingleObject
下面的示例演示几种线程处理功能。

· 使用 RegisterWaitForSingleObject 方法将任务排队,以由 ThreadPool 线程执行。

· 使用 AutoResetEvent 发出信号,通知执行任务。请参见 EventWaitHandle、AutoResetEvent 和 ManualResetEvent。

· 使用 WaitOrTimerCallback 委托处理超时和信号。

· 使用 RegisteredWaitHandle 取消排入队列的任务。

 

using System;
using System.Threading;

// TaskInfo 包含将要被回调(callback)的数据
public class TaskInfo {
    public RegisteredWaitHandle Handle = null;
    public string OtherInfo = "default";
}

public class Example {
    public static void Main(string[] args) {
        // 主线程使用 AutoResetEvent 通知执行回调方法的 wait 句柄(handle)
        AutoResetEvent ev = new AutoResetEvent(false);

        TaskInfo ti = new TaskInfo();
        ti.OtherInfo = "First task";
        // TaskInfo对象包含了为任务返回RegisterWaitForSingleObject的注册等待句柄(handle)
        // 这允许当对象一旦被通知就终止等待操作。(见 WaitProc).
        ti.Handle = ThreadPool.RegisterWaitForSingleObject(
            ev,
            new WaitOrTimerCallback(WaitProc),
            ti,
            1000,
            false
        );

        // 主线程等待三秒钟,用以示范排队线程超时的情况,然后通知它。
        Thread.Sleep(3100);
        Console.WriteLine("Main thread signals.");
        ev.Set();

        // 主线程休眠(sleeps),应该能给回调(callback)方法足够的时候去执行它。
        // 如果你注释掉这行,程序通常会在线程池线程被执行前退出。
        Thread.Sleep(1000);
        // 如果你开始一个自己的线程,你可以调用 Thread.Join() 来等待它完成。
        // 这个选项在线程池线程里不可用。
    }
  
    // 当注册等待超时,或者当等待句柄(WaitHandle——在这个例子里是AutoResetEvent)被通知时,
    // 将会执行这个回调(callback)方法,
    // WaitProc 反注册等待句柄(WaitHandle) the first time the event is
    // signaled.
    public static void WaitProc(object state, bool timedOut) {
        // state 对象必须传入正确的类型,因为 WaitOrTimerCallback 委托(delegate)
        // 明确指出的对象的类型。
        TaskInfo ti = (TaskInfo) state;

        string cause = "TIMED OUT";
        if (!timedOut) {
            cause = "SIGNALED";
            // 如果这个回调(callback)方法被执行是因为等待句柄(WaitHandle)通知,
            // 停止执行接下来的回调(callback )方法, 并反注册等待句柄(WaitHandle)
            if (ti.Handle != null)
                ti.Handle.Unregister(null);
        }

        Console.WriteLine("WaitProc( {0} ) executes on thread {1}; cause = {2}.",
            ti.OtherInfo,
            Thread.CurrentThread.GetHashCode().ToString(),
            cause
        );
    }
}

 

---------------------------------------------------------------------------------------------------

c#.net多线程编程教学(3):线程同步

随着对多线程学习的深入,你可能觉得需要了解一些有关线程共享资源的问题. .NET framework提供了很多的类和数据类型来控制对共享资源的访问。

  考虑一种我们经常遇到的情况:有一些全局变量和共享的类变量,我们需要从不同的线程来更新它们,可以通过使用System.Threading.Interlocked类完成这样的任务,它提供了原子的,非模块化的整数更新操作。

  还有你可以使用System.Threading.Monitor类锁定对象的方法的一段代码,使其暂时不能被别的线程访问。

  System.Threading.WaitHandle类的实例可以用来封装等待对共享资源的独占访问权的操作系统特定的对象。尤其对于非受管代码的互操作问题。

  System.Threading.Mutex用于对多个复杂的线程同步的问题,它也允许单线程的访问。

  像ManualResetEvent和AutoResetEvent这样的同步事件类支持一个类通知其他事件的线程。

  不讨论线程的同步问题,等于对多线程编程知之甚少,但是我们要十分谨慎的使用多线程的同步。在使用线程同步时,我们事先就要要能够正确的确定是那个对象和方法有可能造成死锁(死锁就是所有的线程都停止了相应,都在等者对方释放资源)。还有赃数据的问题(指的是同一时间多个线程对数据作了操作而造成的不一致),这个不容易理解,这么说吧,有X和Y两个线程,线程X从文件读取数据并且写数据到数据结构,线程Y从这个数据结构读数据并将数据送到其他的计算机。假设在Y读数据的同时,X写入数据,那么显然Y读取的数据与实际存储的数据是不一致的。这种情况显然是我们应该避免发生的。少量的线程将使得刚才的问题发生的几率要少的多,对共享资源的访问也更好的同步。

  .NET Framework的CLR提供了三种方法来完成对共享资源 ,诸如全局变量域,特定的代码段,静态的和实例化的方法和域。

  (1) 代码域同步:使用Monitor类可以同步静态/实例化的方法的全部代码或者部分代码段。不支持静态域的同步。在实例化的方法中,this指针用于同步;而在静态的方法中,类用于同步,这在后面会讲到。

  (2) 手工同步:使用不同的同步类(诸如WaitHandle, Mutex, ReaderWriterLock, ManualResetEvent, AutoResetEvent 和Interlocked等)创建自己的同步机制。这种同步方式要求你自己手动的为不同的域和方法同步,这种同步方式也可以用于进程间的同步和对共享资源的等待而造成的死锁解除。

  (3) 上下文同步:使用SynchronizationAttribute为ContextBoundObject对象创建简单的,自动的同步。这种同步方式仅用于实例化的方法和域的同步。所有在同一个上下文域的对象共享同一个锁。
Monitor Class

  在给定的时间和指定的代码段只能被一个线程访问,Monitor 类非常适合于这种情况的线程同步。这个类中的方法都是静态的,所以不需要实例化这个类。下面一些静态的方法提供了一种机制用来同步对象的访问从而避免死锁和维护数据的一致性。

  Monitor.Enter 方法:在指定对象上获取排他锁。

  Monitor.TryEnter 方法:试图获取指定对象的排他锁。

  Monitor.Exit 方法:释放指定对象上的排他锁。

  Monitor.Wait 方法:释放对象上的锁并阻塞当前线程,直到它重新获取该锁。

  Monitor.Pulse 方法:通知等待队列中的线程锁定对象状态的更改。

  Monitor.PulseAll 方法:通知所有的等待线程对象状态的更改。

  通过对指定对象的加锁和解锁可以同步代码段的访问。Monitor.Enter, Monitor.TryEnter 和 Monitor.Exit用来对指定对象的加锁和解锁。一旦获取(调用了Monitor.Enter)指定对象(代码段)的锁,其他的线程都不能获取该锁。举个例子来说吧,线程X获得了一个对象锁,这个对象锁可以释放的(调用Monitor.Exit(object) or Monitor.Wait)。当这个对象锁被释放后,Monitor.Pulse方法和 Monitor.PulseAll方法通知就绪队列的下一个线程进行和其他所有就绪队列的线程将有机会获取排他锁。线程X释放了锁而线程Y获得了锁,同时调用Monitor.Wait的线程X进入等待队列。当从当前锁定对象的线程(线程Y)受到了Pulse或PulseAll,等待队列的线程就进入就绪队列。线程X重新得到对象锁时,Monitor.Wait才返回。如果拥有锁的线程(线程Y)不调用Pulse或PulseAll,方法可能被不确定的锁定。Pulse, PulseAll and Wait必须是被同步的代码段鄂被调用。对每一个同步的对象,你需要有当前拥有锁的线程的指针,就绪队列和等待队列(包含需要被通知锁定对象的状态变化的线程)的指针。

  你也许会问,当两个线程同时调用Monitor.Enter会发生什么事情?无论这两个线程地调用Monitor.Enter是多么地接近,实际上肯定有一个在前,一个在后,因此永远只会有一个获得对象锁。既然Monitor.Enter是原子操作,那么CPU是不可能偏好一个线程而不喜欢另外一个线程的。为了获取更好的性能,你应该延迟后一个线程的获取锁调用和立即释放前一个线程的对象锁。对于private和internal的对象,加锁是可行的,但是对于external对象有可能导致死锁,因为不相关的代码可能因为不同的目的而对同一个对象加锁。

  如果你要对一段代码加锁,最好的是在try语句里面加入设置锁的语句,而将Monitor.Exit放在finally语句里面。对于整个代码段的加锁,你可以使用MethodImplAttribute(在System.Runtime.CompilerServices命名空间)类在其构造器中设置同步值。这是一种可以替代的方法,当加锁的方法返回时,锁也就被释放了。如果需要要很快释放锁,你可以使用Monitor类和C# lock的声明代替上述的方法。

  让我们来看一段使用Monitor类的代码:
 

public void some_method()
{

int a=100;

int b=0;

Monitor.Enter(this);

//say we do something here.

int c=a/b;

Monitor.Exit(this);

}

  上面的代码运行会产生问题。当代码运行到int c=a/b; 的时候,会抛出一个异常,Monitor.Exit将不会返回。因此这段程序将挂起,其他的线程也将得不到锁。有两种方法可以解决上面的问题。第一个方法是:将代码放入try…finally内,在finally调用Monitor.Exit,这样的话最后一定会释放锁。第二种方法是:利用C#的lock()方法。调用这个方法和调用Monitoy.Enter的作用效果是一样的。但是这种方法一旦代码执行超出范围,释放锁将不会自动的发生。见下面的代码:
 

public void some_method()
{

int a=100;

int b=0;

lock(this);

//say we do something here.

int c=a/b;

}

  C# lock申明提供了与Monitoy.Enter和Monitoy.Exit同样的功能,这种方法用在你的代码段不能被其他独立的线程中断的情况。
WaitHandle Class

  WaitHandle类作为基类来使用的,它允许多个等待操作。这个类封装了win32的同步处理方法。WaitHandle对象通知其他的线程它需要对资源排他性的访问,其他的线程必须等待,直到WaitHandle不再使用资源和等待句柄没有被使用。下面是从它继承来的几个类:

  Mutex 类:同步基元也可用于进程间同步。

  AutoResetEvent:通知一个或多个正在等待的线程已发生事件。无法继承此类。

  ManualResetEvent:当通知一个或多个正在等待的线程事件已发生时出现。无法继承此类。

  这些类定义了一些信号机制使得对资源排他性访问的占有和释放。他们有两种状态:signaled 和 nonsignaled。Signaled状态的等待句柄不属于任何线程,除非是nonsignaled状态。拥有等待句柄的线程不再使用等待句柄时用set方法,其他的线程可以调用Reset方法来改变状态或者任意一个WaitHandle方法要求拥有等待句柄,这些方法见下面:

  WaitAll:等待指定数组中的所有元素收到信号。

  WaitAny:等待指定数组中的任一元素收到信号。

  WaitOne:当在派生类中重写时,阻塞当前线程,直到当前的 WaitHandle 收到信号。

  这些wait方法阻塞线程直到一个或者更多的同步对象收到信号。

  WaitHandle对象封装等待对共享资源的独占访问权的操作系统特定的对象无论是收管代码还是非受管代码都可以使用。但是它没有Monitor使用轻便,Monitor是完全的受管代码而且对操作系统资源的使用非常有效率。

Mutex Class

  Mutex是另外一种完成线程间和跨进程同步的方法,它同时也提供进程间的同步。它允许一个线程独占共享资源的同时阻止其他线程和进程的访问。Mutex的名字就很好的说明了它的所有者对资源的排他性的占有。一旦一个线程拥有了Mutex,想得到Mutex的其他线程都将挂起直到占有线程释放它。Mutex.ReleaseMutex方法用于释放Mutex,一个线程可以多次调用wait方法来请求同一个Mutex,但是在释放Mutex的时候必须调用同样次数的Mutex.ReleaseMutex。如果没有线程占有Mutex,那么Mutex的状态就变为signaled,否则为nosignaled。一旦Mutex的状态变为signaled,等待队列的下一个线程将会得到Mutex。Mutex类对应与win32的CreateMutex,创建Mutex对象的方法非常简单,常用的有下面几种方法:

  一个线程可以通过调用WaitHandle.WaitOne 或 WaitHandle.WaitAny 或 WaitHandle.WaitAll得到Mutex的拥有权。如果Mutex不属于任何线程,上述调用将使得线程拥有Mutex,而且WaitOne会立即返回。但是如果有其他的线程拥有Mutex,WaitOne将陷入无限期的等待直到获取Mutex。你可以在WaitOne方法中指定参数即等待的时间而避免无限期的等待Mutex。调用Close作用于Mutex将释放拥有。一旦Mutex被创建,你可以通过GetHandle方法获得Mutex的句柄而给WaitHandle.WaitAny 或 WaitHandle.WaitAll 方法使用。

  下面是一个示例:
 

public void some_method()
{

int a=100;

int b=20;

Mutex firstMutex = new Mutex(false);

FirstMutex.WaitOne();

//some kind of processing can be done here.

Int x=a/b;

FirstMutex.Close();

}

  在上面的例子中,线程创建了Mutex,但是开始并没有申明拥有它,通过调用WaitOne方法拥有Mutex。
Synchronization Events

  同步时间是一些等待句柄用来通知其他的线程发生了什么事情和资源是可用的。他们有两个状态:signaled and nonsignaled。AutoResetEvent 和 ManualResetEvent就是这种同步事件。

AutoResetEvent Class

  这个类可以通知一个或多个线程发生事件。当一个等待线程得到释放时,它将状态转换为signaled。用set方法使它的实例状态变为signaled。但是一旦等待的线程被通知时间变为signaled,它的转台将自动的变为nonsignaled。如果没有线程侦听事件,转台将保持为signaled。此类不能被继承。

ManualResetEvent Class

  这个类也用来通知一个或多个线程事件发生了。它的状态可以手动的被设置和重置。手动重置时间将保持signaled状态直到ManualResetEvent.Reset设置其状态为nonsignaled,或保持状态为nonsignaled直到ManualResetEvent.Set设置其状态为signaled。这个类不能被继承。

Interlocked Class

  它提供了在线程之间共享的变量访问的同步,它的操作时原子操作,且被线程共享.你可以通过Interlocked.Increment 或 Interlocked.Decrement来增加或减少共享变量.它的有点在于是原子操作,也就是说这些方法可以代一个整型的参数增量并且返回新的值,所有的操作就是一步.你也可以使用它来指定变量的值或者检查两个变量是否相等,如果相等,将用指定的值代替其中一个变量的值.

ReaderWriterLock class

  它定义了一种锁,提供唯一写/多读的机制,使得读写的同步.任意数目的线程都可以读数据,数据锁在有线程更新数据时将是需要的.读的线程可以获取锁,当且仅当这里没有写的线程.当没有读线程和其他的写线程时,写线程可以得到锁.因此,一旦writer-lock被请求,所有的读线程将不能读取数据直到写线程访问完毕.它支持暂停而避免死锁.它也支持嵌套的读/写锁.支持嵌套的读锁的方法是ReaderWriterLock.AcquireReaderLock,如果一个线程有写锁则该线程将暂停;

  支持嵌套的写锁的方法是ReaderWriterLock.AcquireWriterLock,如果一个线程有读锁则该线程暂停.如果有读锁将容易倒是死锁.安全的办法是使用ReaderWriterLock.UpgradeToWriterLock方法,这将使读者升级到写者.你可以用ReaderWriterLock.DowngradeFromWriterLock方法使写者降级为读者.调用ReaderWriterLock.ReleaseLock将释放锁, ReaderWriterLock.RestoreLock将重新装载锁的状态到调用ReaderWriterLock.ReleaseLock以前.

结论:

  这部分讲述了.NET平台上的线程同步的问题.造接下来的系列文章中我将给出一些例子来更进一步的说明这些使用的方法和技巧.虽然线程同步的使用会给我们的程序带来很大的价值,但是我们最好能够小心使用这些方法.否则带来的不是受益,而将倒是性能下降甚至程序崩溃.只有大量的联系和体会才能使你驾驭这些技巧.尽量少使用那些在同步代码块完成不了或者不确定的阻塞的东西,尤其是I/O操作;尽可能的使用局部变量来代替全局变量;同步用在那些部分代码被多个线程和进程访问和状态被不同的进程共享的地方;安排你的代码使得每一个数据在一个线程里得到精确的控制;不是共享在线程之间的代码是安全的;在下一篇文章中我们将学习线程池有关的知识.

 

----------------------------------------------------------------------------------------------------------

C# 多线程控制控件实例(例程简单,注释详细)

该实例功能为“多线程控制UI控件”,线程函数实现自动加1。界面如下:

 

 

 

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Threading;

namespace treadTest
{   
    //定义委托
    public delegate void ListBoxDelegate();
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
        //委托处理方法(关联与ListBoxDelegate)
        private void ListBox()
        {
            if (!listBox1.InvokeRequired)//如果在UI主线程操作ListBox,
            {
                listBox1.Items.Add(++CommonData.num);//则直接进行控件操作,“与UI主线程相关联”
                listBox1.SelectedItem = listBox1.Items[listBox1.Items.Count - 1];
            }
            else//如果是在另一线程操作ListBox,则启用委托
                listBox1.Invoke(new ListBoxDelegate(listShow));
        }
       
        //定义对UI主线程控件的操作,“与AddAuto相关联”。
        private void listShow()
        {
            listBox1.Items.Add(CommonData.num);
            listBox1.SelectedItem = listBox1.Items[listBox1.Items.Count - 1];
        }
        //定义线程函数
        private void AddAuto()
        {
            while (CommonData.Flag == 0)
            {
                CommonData.num++;
                Thread.Sleep(1000);
                ListBox();//不能直接控制UI上的控件,所以用该方法选择使用委托
            }
        }
        //在click事件中启动多线程
        private void btnStart_Click(object sender, EventArgs e)
        {
            //线程标志置0,表示开启线程
            CommonData.Flag = 0;
            //定义 ThreadStart的委托类型的参数,并使该委托指向线程函数
            ListBoxDelegate mycn = new ListBoxDelegate(AddAuto);
            //实例化线程
            Thread insertTxt = new Thread(new ThreadStart(mycn));
            //启动线程
            insertTxt.Start();     
        }

        private void btnAbort_Click(object sender, EventArgs e)
        {
            CommonData.Flag = 1;
        }
        private void btnCtrlMain_Click(object sender, EventArgs e)
        {
            ListBox();
        }
        private void btnReset_Click(object sender, EventArgs e)
        {
            CommonData.num = 0;
        }
        private void btnClear_Click(object sender, EventArgs e)
        {
            listBox1.Items.Clear();
        }
        private void btnQuit_Click(object sender, EventArgs e)
        {
            Application.Exit();
        }

    }
   
    //全局变量解决方案
    public class CommonData
    {
        private static int _Flag = 0;
        private static int _num = 0;
        public static int Flag
        {
            get { return _Flag; }
            set { _Flag = value; }
        }
        public static int num
        {
            get { return _num; }
            set { _num = value; }
        }
    }
}

 

总结:

        要使用多线程控制UI控件,必须用委托实现。调用控件的Invoke方法(Invoke方法的参数是一个委托类型的参数)。

实现步骤:

         1.声明委托。

          2.声明委托处理函数(判断是主线程控制UI控件,还是Invoke(多线程)控制UI控件)。

         3.声明一个线程实例,将线程函数的委托传入ThreadStart()。

         4.开启该线程。

         5.定义该线程函数,欲控制UI控件,则调用第2步的委托处理函数,他将自己判断选择用Invoke。

         6.定义Invoke需要调用的函数(如本例的listShow函数)

//*********************************************************************************************************************************

      在上例中,只是完成了多线程控制主线程控件的功能,如果能手动和自动同时访问全局变量时,就有可能出现线程不同步的问题。以下主要利用lock线程锁来修改解决方案,使线程同步,注意代码带动的地方。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Threading;

namespace treadTest
{   
    //定义委托
    public delegate void ListBoxDelegate();
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
        //委托处理方法(关联与ListBoxDelegate)
        private void ListBox()
        {
            if (!listBox1.InvokeRequired)//如果在UI主线程操作ListBox,
            {
                listBox1.Items.Add(CommonData.plus());//则直接进行控件操作,“与UI主线程相关联”
                listBox1.SelectedItem = listBox1.Items[listBox1.Items.Count - 1];
            }
            else//如果是在另一线程操作ListBox,则启用委托
                listBox1.Invoke(new ListBoxDelegate(listShow));
        }
       
        //定义对UI主线程控件的操作,“与AddAuto相关联”。
        private void listShow()
        {
            listBox1.Items.Add(CommonData.plus());
            listBox1.SelectedItem = listBox1.Items[listBox1.Items.Count - 1];
        }
        //定义线程函数
        private void AddAuto()
        {
            while (CommonData.Flag == 0)
            {
                Thread.Sleep(1000);
                ListBox();//不能直接控制UI上的控件,所以用该方法选择使用委托
            }
        }
        //在click事件中启动多线程
        private void btnStart_Click(object sender, EventArgs e)
        {
            //线程标志置0,表示开启线程
            CommonData.Flag = 0;
            //定义 ThreadStart的委托类型的参数,并使该委托指向线程函数
            ListBoxDelegate mycn = new ListBoxDelegate(AddAuto);
            //实例化线程
            Thread insertTxt = new Thread(new ThreadStart(mycn));
            //启动线程
            insertTxt.Start();     
        }

        private void btnAbort_Click(object sender, EventArgs e)
        {
            CommonData.Flag = 1;
        }
        private void btnCtrlMain_Click(object sender, EventArgs e)
        {
            ListBox();
        }
        private void btnReset_Click(object sender, EventArgs e)
        {
            CommonData.num = 0;
        }
        private void btnClear_Click(object sender, EventArgs e)
        {
            listBox1.Items.Clear();
        }
        private void btnQuit_Click(object sender, EventArgs e)
        {
            Application.Exit();
        }
    }
   
    //全局变量解决方案
    public class CommonData
    {
        private static int _Flag = 0;
        private static int _num = 0;
        public static int plus()
        {
            lock (new object())
            {
                return _num++;
            }
        }
        public static int Flag
        {
            get { return _Flag; }
            set { _Flag = value; }
        }
        public static int num
        {
            get { return _num; }
            set { _num = value; }
        }
    }
}

点击查看原图
发表在 article | 标签为 , | C# 线程池示例已关闭评论

.Net线程问题解答

目录

 

 

基础篇

怎样创建一个线程
受托管的线程与 Windows线程
前台线程与后台线程
名为BeginXXX和EndXXX的方法是做什么用的
异步和多线程有什么关联
WinForm多线程编程篇

我的多线程WinForm程序老是抛出InvalidOperationException ,怎么解决?
Invoke,BeginInvoke干什么用的,内部是怎么实现的
每个线程都有消息队列吗?
为什么Winform不允许跨线程修改UI线程控件的值
有没有什么办法可以简化WinForm多线程的开发
线程池

线程池的作用是什么?
所有进程使用一个共享的线程池,还是每个进程使用独立的线程池?
为什么不要手动线程池设置最大值?
.Net线程池有什么不足?
同步

CLR怎样实现lock(obj)锁定?
WaitHandle是什么,他和他的派生类怎么使用
什么是用双锁实现Singleton,为什么要这样做,为什么有人说双锁检验是不安全的
互斥对象(Mutex)、事件(Event)对象与lock语句的比较
什么时候需要锁定

只有共享资源才需要锁定
把锁定交给数据库
了解你的程序是怎么运行的
业务逻辑对事务和线程安全的要求
计算一下冲突的可能性
请多使用lock,少用Mutex
Web和IIS

应用程序池,WebApplication,和线程池之间有什么关系
Web页面怎么调用异步WebService
 

基础篇

 怎样创建一个线程

我只简单列举几种常用的方法,详细可参考.Net多线程总结(一)

一)使用Thread类

ThreadStart threadStart=new ThreadStart(Calculate);//通过ThreadStart委托告诉子线程讲执行什么方法,这里执行一个计算圆周长的方法
Thread thread=new Thread(threadStart);
thread.Start(); //启动新线程

public void Calculate(){
double Diameter=0.5;
Console.Write("The perimeter Of Circle with a Diameter of {0} is {1}"Diameter,Diameter*Math.PI);
}

二)使用Delegate.BeginInvoke

delegate double CalculateMethod(double Diameter); //申明一个委托,表明需要在子线程上执行的方法的函数签名
static CalculateMethod calcMethod = new CalculateMethod(Calculate);//把委托和具体的方法关联起来
static void Main(string[] args)
{
//此处开始异步执行,并且可以给出一个回调函数(如果不需要执行什么后续操作也可以不使用回调)
calcMethod.BeginInvoke(5, new AsyncCallback(TaskFinished), null);
Console.ReadLine();
}

//线程调用的函数,给出直径作为参数,计算周长
public static double Calculate(double Diameter)
{
    return Diameter * Math.PI;
}

//线程完成之后回调的函数
public static void TaskFinished(IAsyncResult result)
{
    double re = 0;
    re = calcMethod.EndInvoke(result);
    Console.WriteLine(re);
}

三)使用ThreadPool.QueueworkItem

WaitCallback w = new WaitCallback(Calculate);
//下面启动四个线程,计算四个直径下的圆周长
ThreadPool.QueueUserWorkItem(w, 1.0);
ThreadPool.QueueUserWorkItem(w, 2.0);
ThreadPool.QueueUserWorkItem(w, 3.0);
ThreadPool.QueueUserWorkItem(w, 4.0);
public static void Calculate(double Diameter)
{
return Diameter * Math.PI;
}

下面两条来自于http://www.cnblogs.com/tonyman/archive/2007/09/13/891912.html

  受托管的线程与 Windows线程

必须要了解,执行.NET应用的线程实际上仍然是Windows线程。但是,当某个线程被CLR所知时,我们将它称为受托管的线程。具体来说,由受托管的代码创建出来的线程就是受托管的线程。如果一个线程由非托管的代码所创建,那么它就是非托管的线程。不过,一旦该线程执行了受托管的代码它就变成了受托管的线程。

一个受托管的线程和非托管的线程的区别在于,CLR将创建一个System.Threading.Thread类的实例来代表并操作前者。在内部实现中,CLR将一个包含了所有受托管线程的列表保存在一个叫做ThreadStore地方。

CLR确保每一个受托管的线程在任意时刻都在一个AppDomain中执行,但是这并不代表一个线程将永远处在一个AppDomain中,它可以随着时间的推移转到其他的AppDomain中。

从安全的角度来看,一个受托管的线程的主用户与底层的非托管线程中的Windows主用户是无关的。
 

  前台线程与后台线程

启动了多个线程的程序在关闭的时候却出现了问题,如果程序退出的时候不关闭线程,那么线程就会一直的存在,但是大多启动的线程都是局部变量,不能一一的关闭,如果调用Thread.CurrentThread.Abort()方法关闭主线程的话,就会出现ThreadAbortException 异常,因此这样不行。
后来找到了这个办法: Thread.IsBackground 设置线程为后台线程。
 
msdn对前台线程和后台线程的解释:托管线程或者是后台线程,或者是前台线程。后台线程不会使托管执行环境处于活动状态,除此之外,后台线程与前台线程是一样的。一旦所有前台线程在托管进程(其中 .exe 文件是托管程序集)中被停止,系统将停止所有后台线程并关闭。通过设置 Thread.IsBackground 属性,可以将一个线程指定为后台线程或前台线程。例如,通过将 Thread.IsBackground 设置为 true,就可以将线程指定为后台线程。同样,通过将 IsBackground 设置为 false,就可以将线程指定为前台线程。从非托管代码进入托管执行环境的所有线程都被标记为后台线程。通过创建并启动新的 Thread 对象而生成的所有线程都是前台线程。如果要创建希望用来侦听某些活动(如套接字连接)的前台线程,则应将 Thread.IsBackground 设置为 true,以便进程可以终止。
所以解决办法就是在主线程初始化的时候,设置:Thread.CurrentThread.IsBackground = true;

这样,主线程就是后台线程,在关闭主程序的时候就会关闭主线程,从而关闭所有线程。但是这样的话,就会强制关闭所有正在执行的线程,所以在关闭的时候要对线程工作的结果保存。

 

经常看到名为BeginXXX和EndXXX的方法,他们是做什么用的

这是.net的一个异步方法名称规范
.Net在设计的时候为异步编程设计了一个异步编程模型(APM),这个模型不仅是使用.NET的开发人员使用,.Net内部也频繁用到,比如所有的Stream就有BeginRead,EndRead,Socket,WebRequet,SqlCommand都运用到了这个模式,一般来讲,调用BegionXXX的时候,一般会启动一个异步过程去执行一个操作,EndEnvoke可以接收这个异步操作的返回,当然如果异步操作在EndEnvoke调用的时候还没有执行完成,EndInvoke会一直等待异步操作完成或者超时

.Net的异步编程模型(APM)一般包含BeginXXX,EndXXX,IAsyncResult这三个元素,BeginXXX方法都要返回一个IAsyncResult,而EndXXX都需要接收一个IAsyncResult作为参数,他们的函数签名模式如下

IAsyncResult BeginXXX(...);

<返回类型> EndXXX(IAsyncResult ar);

BeginXXX和EndXXX中的XXX,一般都对应一个同步的方法,比如FileStream的Read方法是一个同步方法,相应的BeginRead(),EndRead()就是他的异步版本,HttpRequest有GetResponse来同步接收一个响应,也提供了BeginGetResponse和EndGetResponse这个异步版本,而IAsynResult是二者联系的纽带,只有把BeginXXX所返回的IAsyncResult传给对应的EndXXX,EndXXX才知道需要去接收哪个BeginXXX发起的异步操作的返回值。

这个模式在实际使用时稍显繁琐,虽然原则上我们可以随时调用EndInvoke来获得返回值,并且可以同步多个线程,但是大多数情况下当我们不需要同步很多线程的时候使用回调是更好的选择,在这种情况下三个元素中的IAsynResult就显得多余,我们一不需要用其中的线程完结标志来判断线程是否成功完成(回调的时候线程应该已经完成了),二不需要他来传递数据,因为数据可以写在任何变量里,并且回调时应该已经填充,所以可以看到微软在新的.Net Framework中已经加强了对回调事件的支持,这总模型下,典型的回调程序应该这样写

a.DoWork+=new SomeEventHandler(Caculate);
a.CallBack+=new SomeEventHandler(callback);
a.Run();

(注:我上面讲的是普遍的用法,然而BeginXXX,EndXXX仅仅是一种模式,而对这个模式的实现完全取决于使用他的开发人员,具体实现的时候你可以使用另外一个线程来实现异步,也可能使用硬件的支持来实现异步,甚至可能根本和异步没有关系(尽管几乎没有人会这样做)-----比如直接在Beginxxx里直接输出一个"Helloworld",如果是这种极端的情况,那么上面说的一切都是废话,所以上面的探讨并不涉及内部实现,只是告诉大家微软的模式,和框架中对这个模式的经典实现)

 

 

异步和多线程有什么关联

有一句话总结的很好:多线程是实现异步的一种手段和工具

我们通常把多线程和异步等同起来,实际是一种误解,在实际实现的时候,异步有许多种实现方法,我们可以用进程来做异步,或者使用纤程,或者硬件的一些特性,比如在实现异步IO的时候,可以有下面两个方案:

1)可以通过初始化一个子线程,然后在子线程里进行IO,而让主线程顺利往下执行,当子线程执行完毕就回调

2)也可以根本不使用新线程,而使用硬件的支持(现在许多硬件都有自己的处理器),来实现完全的异步,这是我们只需要将IO请求告知硬件驱动程序,然后迅速返回,然后等着硬件IO就绪通知我们就可以了

实际上DotNet Framework里面就有这样的例子,当我们使用文件流的时候,如果制定文件流属性为同步,则使用BeginRead进行读取时,就是用一个子线程来调用同步的Read方法,而如果指定其为异步,则同样操作时就使用了需要硬件和操作系统支持的所谓IOCP的机制

 

 

WinForm多线程编程篇

 

我的多线程WinForm程序老是抛出InvalidOperationException ,怎么解决?

在WinForm中使用多线程时,常常遇到一个问题,当在子线程(非UI线程)中修改一个控件的值:比如修改进度条进度,时会抛出如下错误

Cross-thread operation not valid: Control 'XXX' accessed from a thread other than the thread it was created on.

在VS2005或者更高版本中,只要不是在控件的创建线程(一般就是指UI主线程)上访问控件的属性就会抛出这个错误,解决方法就是利用控件提供的Invoke和BeginInvoke把调用封送回UI线程,也就是让控件属性修改在UI线程上执行,下面列出会报错的代码和他的修改版本

ThreadStart threadStart=new ThreadStart(Calculate);//通过ThreadStart委托告诉子线程讲执行什么方法
Thread thread=new Thread(threadStart);
thread.Start();
public void Calculate(){
    double Diameter=0.5;
    double result=Diameter*Math.PI;
    CalcFinished(result);//计算完成需要在一个文本框里显示
}
public void CalcFinished(double result){
    this.TextBox1.Text=result.ToString();//会抛出错误
}
上面加粗的地方在debug的时候会报错,最直接的修改方法是修改Calculate这个方法如下

delegate void changeText(double result);

public void Calculate(){
    double Diameter=0.5;
    double result=Diameter*Math.PI;
    this.BeginInvoke(new changeText(CalcFinished),t.Result);//计算完成需要在一个文本框里显示
}

这样就ok了,但是最漂亮的方法是不去修改Calculate,而去修改CalcFinished这个方法,因为程序里调用这个方法的地方可能很多,由于加了是否需要封送的判断,这样修改还能提高非跨线程调用时的性能

delegate void changeText(double result);

public void CalcFinished(double result){
    if(this.InvokeRequired){
        this.BeginInvoke(new changeText(CalcFinished),t.Result);
    }
    else{
        this.TextBox1.Text=result.ToString();
    }
}
上面的做法用到了Control的一个属性InvokeRequired(这个属性是可以在其他线程里访问的),这个属性表明调用是否来自另非UI线程,如果是,则使用BeginInvoke来调用这个函数,否则就直接调用,省去线程封送的过程

 

Invoke,BeginInvoke干什么用的,内部是怎么实现的?
这两个方法主要是让给出的方法在控件创建的线程上执行

Invoke使用了Win32API的SendMessage,

UnsafeNativeMethods.PostMessage(new HandleRef(this, this.Handle), threadCallbackMessage, IntPtr.Zero, IntPtr.Zero);

BeginInvoke使用了Win32API的PostMessage

UnsafeNativeMethods.PostMessage(new HandleRef(this, this.Handle), threadCallbackMessage, IntPtr.Zero, IntPtr.Zero);

这两个方法向UI线程的消息队列中放入一个消息,当UI线程处理这个消息时,就会在自己的上下文中执行传入的方法,换句话说凡是使用BeginInvoke和Invoke调用的线程都是在UI主线程中执行的,所以如果这些方法里涉及一些静态变量,不用考虑加锁的问题

 

每个线程都有消息队列吗?
不是,只有创建了窗体对象的线程才会有消息队列(下面给出<Windows 核心编程>关于这一段的描述)

当一个线程第一次被建立时,系统假定线程不会被用于任何与用户相关的任务。这样可以减少线程对系统资源的要求。但是,一旦这个线程调用一个与图形用户界面有关的函数(例如检查它的消息队列或建立一个窗口),系统就会为该线程分配一些另外的资源,以便它能够执行与用户界面有关的任务。特别是,系统分配一个T H R E A D I N F O结构,并将这个数据结构与线程联系起来。

这个T H R E A D I N F O结构包含一组成员变量,利用这组成员,线程可以认为它是在自己独占的环境中运行。T H R E A D I N F O是一个内部的、未公开的数据结构,用来指定线程的登记消息队列(posted-message queue)、发送消息队列( send-message queue)、应答消息队列( r e p l y -message queue)、虚拟输入队列(virtualized-input queue)、唤醒标志(wake flag)、以及用来描述线程局部输入状态的若干变量。图2 6 - 1描述了T H R E A D I N F O结构和与之相联系的三个线程。

 

 

为什么Winform不允许跨线程修改UI线程控件的值
在vs2003下,使用子线程调用ui线程创建的控件的属性是不会有问题的,但是编译的时候会出现警告,但是vs2005及以上版本就会有这样的问题,下面是msdn上的描述

"当您在 Visual Studio 调试器中运行代码时,如果您从一个线程访问某个 UI 元素,而该线程不是创建该 UI 元素时所在的线程,则会引发 InvalidOperationException。调试器引发该异常以警告您存在危险的编程操作。UI 元素不是线程安全的,所以只应在创建它们的线程上进行访问"

从上面可以看出,这个异常实际是debugger耍的花招,也就是说,如果你直接运行程序的exe文件,或者利用运行而不调试(Ctrl+F5)来运行你的程序,是不会抛出这样的异常的.大概ms发现v2003的警告对广大开发者不起作用,所以用了一个比较狠一点的方法.

不过问题依然存在:既然这样设计的原因主要是因为控件的值非线程安全,那么DotNet framework中非线程安全的类千千万万,为什么偏偏跨线程修改Control的属性会有这样严格的限制策略呢?

这个问题我还回答不好,希望博友们能够予以补充

 

有没有什么办法可以简化WinForm多线程的开发
使用backgroundworker,使用这个组建可以避免回调时的Invoke和BeginInvoke,并且提供了许多丰富的方法和事件

参见.Net多线程总结(二)-BackgroundWorker,我在这里不再赘诉

 

线程池

线程池的作用是什么

作用是减小线程创建和销毁的开销

创建线程涉及用户模式和内核模式的切换,内存分配,dll通知等一系列过程,线程销毁的步骤也是开销很大的,所以如果应用程序使用了完一个线程,我们能把线程暂时存放起来,以备下次使用,就可以减小这些开销

所有进程使用一个共享的线程池,还是每个进程使用独立的线程池?

每个进程都有一个线程池,一个Process中只能有一个实例,它在各个应用程序域(AppDomain)是共享的,.Net2.0 中默认线程池的大小为工作线程25个,IO线程1000个,有一个比较普遍的误解是线程池中会有1000个线程等着你去取,其实不然, ThreadPool仅仅保留相当少的线程,保留的线程可以用SetMinThread这个方法来设置,当程序的某个地方需要创建一个线程来完成工作时,而线程池中又没有空闲线程时,线程池就会负责创建这个线程,并且在调用完毕后,不会立刻销毁,而是把他放在池子里,预备下次使用,同时如果线程超过一定时间没有被使用,线程池将会回收线程,所以线程池里存在的线程数实际是个动态的过程

为什么不要手动线程池设置最大值?

当我首次看到线程池的时候,脑袋里的第一个念头就是给他设定一个最大值,然而当我们查看ThreadPool的SetMaxThreads文档时往往会看到一条警告:不要手动更改线程池的大小,这是为什么呢?

其实无论FileStream的异步读写,异步发送接受Web请求,甚至使用delegate的beginInvoke都会默认调用 ThreadPool,也就是说不仅你的代码可能使用到线程池,框架内部也可能使用到,更改的后果影响就非常大,特别在iis中,一个应用程序池中的所有 WebApplication会共享一个线程池,对最大值的设定会带来很多意想不到的麻烦

线程池的线程为何要分类?

线程池有一个方法可以让我们看到线程池中可用的线程数量:GetAvaliableThread(out workerThreadCount,out iocompletedThreadCount),对于我来说,第一次看到这个函数的参数时十分困惑,因为我期望这个函数直接返回一个整形,表明还剩多少线程,这个函数居然一次返回了两个变量.

原来线程池里的线程按照公用被分成了两大类:工作线程和IO线程,或者IO完成线程,前者用于执行普通的操作,后者专用于异步IO,比如文件和网络请求,注意,分类并不说明两种线程本身有差别,线程就是线程,是一种执行单元,从本质上来讲都是一样的,线程池这样分类,举例来说,就好像某施工工地现在有1000把铁锹,规定其中25把给后勤部门用,其他都给施工部门,施工部门需要大量使用铁锹来挖地基(例子土了点,不过说明问题还是有效的),后勤部门用铁锹也就是铲铲雪,铲铲垃圾,给工人师傅修修临时住房,所以用量不大,显然两个部门的铁锹本身没有区别,但是这样的划分就为管理两个部门的铁锹提供了方便

线程池中两种线程分别在什么情况下被使用,二者工作原理有什么不同?

下面这个例子直接说明了二者的区别,我们用一个流读出一个很大的文件(大一点操作的时间长,便于观察),然后用另一个输出流把所读出的文件的一部分写到磁盘上

我们用两种方法创建输出流,分别是

创建了一个异步的流(注意构造函数最后那个true)

FileStream outputfs=new FileStream(writepath, FileMode.Create, FileAccess.Write, FileShare.None,256,true);

创建了一个同步的流

FileStream outputfs = File.OpenWrite(writepath);

 然后在写文件期间查看线程池的状况

string readpath = "e:\\RHEL4-U4-i386-AS-disc1.iso";
string writepath = "e:\\kakakak.iso";
byte[] buffer = new byte[90000000];

//FileStream outputfs=new FileStream(writepath, FileMode.Create, FileAccess.Write, FileShare.None,256,true);
//Console.WriteLine("异步流");
//创建了一个同步的流

FileStream outputfs = File.OpenWrite(writepath);
Console.WriteLine("同步流");

 //然后在写文件期间查看线程池的状况

ShowThreadDetail("初始状态");

FileStream fs = File.OpenRead(readpath);

fs.BeginRead(buffer, 0, 90000000, delegate(IAsyncResult o)
{

    outputfs.BeginWrite(buffer, 0, buffer.Length,

    delegate(IAsyncResult o1)
    {

        Thread.Sleep(1000);

        ShowThreadDetail("BeginWrite的回调线程");

    }, null);

    Thread.Sleep(500);//this is important cause without this, this Thread and the one used for BeginRead May seem to be same one
},

null);

Console.ReadLine();

public static void ShowThreadDetail(string caller)
{
    int IO;
    int Worker;
    ThreadPool.GetAvailableThreads(out Worker, out IO);
    Console.WriteLine("Worker: {0}; IO: {1}", Worker, IO);
}

输出结果
异步流
Worker: 500; IO: 1000
Worker: 500; IO: 999
同步流
Worker: 500; IO: 1000
Worker: 499; IO: 1000
 
这两个构造函数创建的流都可以使用BeginWrite来异步写数据,但是二者行为不同,当使用同步的流进行异步写时,通过回调的输出我们可以看到,他使用的是工作线程,而非IO线程,而异步流使用了IO线程而非工作线程

其实当没有制定异步属性的时候,.Net实现异步IO是用一个子线程调用fs的同步Write方法来实现的,这时这个子线程会一直阻塞直到调用完成.这个子线程其实就是线程池的一个工作线程,所以我们可以看到,同步流的异步写回调中输出的工作线程数少了一,而使用异步流,在进行异步写时,采用了 IOCP方法,简单说来,就是当BeginWrite执行时,把信息传给硬件驱动程序,然后立即往下执行(注意这里没有额外的线程),而当硬件准备就绪, 就会通知线程池,使用一个IO线程来读取

.Net线程池有什么不足
没有提供方法控制加入线程池的线程:一旦加入线程池,我们没有办法挂起,终止这些线程,唯一可以做的就是等他自己执行

1)不能为线程设置优先级
2)一个Process中只能有一个实例,它在各个AppDomain是共享的。ThreadPool只提供了静态方法,不仅我们自己添加进去的WorkItem使用这个Pool,而且.net framework中那些BeginXXX、EndXXX之类的方法都会使用此Pool。
3)所支持的Callback不能有返回值。WaitCallback只能带一个object类型的参数,没有任何返回值。
4)不适合用在长期执行某任务的场合。我们常常需要做一个Service来提供不间断的服务(除非服务器down掉),但是使用ThreadPool并不合适。

下面是另外一个网友总结的什么不需要使用线程池,我觉得挺好,引用下来
如果您需要使一个任务具有特定的优先级。
如果您具有可能会长时间运行(并因此阻塞其他任务)的任务。
如果您需要将线程放置到单线程单元中(所有 ThreadPool 线程均处于多线程单元中)。
如果您需要与该线程关联的稳定标识。例如,您应使用一个专用线程来中止该线程、将其挂起或按名称发现它。

 

 

锁定与同步

CLR怎样实现lock(obj)锁定?

从原理上讲,lock和Syncronized Attribute都是用Moniter.Enter实现的,比如如下代码

 

object lockobj=new object();
lock(obj){ 
//do things
}

在编译时,会被编译为类似

try{
  Moniter.Enter(obj){
   //do things
  }
}
catch{}
finally{
  Moniter.Exit(obj);
}

而[MethodImpl(MethodImplOptions.Synchronized)]标记为同步的方法会在编译时被lock(this)语句所环绕
所以我们只简单探讨Moniter.Enter的实现

(注:DotNet并非使用Win32API的CriticalSection来实现Moniter.Enter,不过他为托管对象提供了一个类似的结构叫做Syncblk)

每个对象实例头部都有一个指针,这个指针指向的结构,包含了对象的锁定信息,当第一次使用Moniter.Enter(obj)时,这个obj对象的锁定结构就会被初时化,第二次调用Moniter.Enter时,会检验这个object的锁定结构,如果锁没有被释放,则调用会阻塞

 

 

WaitHandle是什么,他和他的派生类怎么使用

  WaitHandle是Mutex,Semaphore,EventWaitHandler,AutoResetEvent,ManualResetEvent共同的祖先,他们包装了用于同步的内核对象,也就是说是这些内核对象的托管版本。

  Mutex:类似于一个接力棒,拿到接力棒的线程才可以开始跑,当然接力棒一次只属于一个线程(Thread Affinity),如果这个线程不释放接力棒(Mutex.ReleaseMutex),那么没办法,其他所有需要接力棒运行的线程都知道能等着看热闹

Semaphore:类似于一个小桶,里面装了几个小球,凡是拿到小球就可以跑,比如指定小桶里最初有四个小球,那么开始的四个线程就可以直接拿着自己的小球开跑,但是第五个线程一看,小球被拿光了,就只好乖乖的等着有谁放一个小球到小桶里(Semophore.Release),他才能跑,但是这里的游戏规则比较特殊,我们可以随意向小桶里放入小球,也就是说我可以拿走一个小球,放回去俩,甚至一个都不拿,放回去5个,这样就有五个线程可以拿着这些小球运行了.我们可以规定小桶里有开始有几个小球(构造函数的第一个参数),也可以规定最多不能超过多少小球(构造函数的第二个参数)

  ManualResetEvent,AutoResetEvent可以参考http://www.cnblogs.com/uubox/archive/2007/12/18/1003953.html

什么是用双锁实现Singleton,为什么要这样做,双锁检验是不安全的吗?

使用双锁检验技巧来实现单件,来自于Java社区

public static MySingleton Instance{
get{
    if(_instance!=null)}{
        lock(_instance){
            if(s_value==null){
                _instance= new MySingleton();
            }
        }
    }
}
}

 

这样做其实是为了提高效率,比起
public static MySingleton Instance{

get{

lock(_instance){

if(s_value==null){

_instance= new MySingleton();

}

}

前一种方法在instance创建的时候不需要用lock同步,从而增进了效率

在java中这种技巧被证明是不安全的详细见http://www.cs.umd.edu/~pugh/java/memoryModel/

但是在.Net下,这样的技巧是成立的,因为.Net使用了改进的内存模型

并且在.Net下,我们可以使用LazyInit来实现单件

private static readonly _instance=new MySingleton()

public static MySingleton Instance{

get{return _instance}

}

当第一此使用_instance时,CLR会生成这个对象,以后再访问这个字段,将会直接返回

互斥对象(Mutex),信号量(Semaphore),事件(Event)对象与lock语句的比较

首先这里所谓的事件对象不是System.Event,而是一种用于同步的内核机制

互斥对象和事件对象属于内核对象,利用内核对象进行线程同步,线程必须要在用户模式和内核模式间切换,所以一般效率很低,但利用互斥对象和事件对象这样的内核对象,可以在多个进程中的各个线程间进行同步。

lock或者Moniter是.net用一个特殊结构实现的,不涉及模式切换,也就是说工作在用户方式下,同步速度较快,但是不能跨进程同步

 

什么时候需要锁定?
刚刚接触锁定的程序员往往觉得这个世界非常的危险,每个静态变量似乎都有可能产生竞争

首先锁定是解决竞争条件的,也就是多个线程同时访问某个资源,造成意想不到的结果,比如,最简单的情况,一个计数器,如果两个线程同时加一,后果就是损失了一个计数,但是频繁的锁定又可能带来性能上的消耗,还有最可怕的情况,死锁

到底什么情况下我们需要使用锁,什么情况下不用呢?

只有共享资源才需要锁定
首先,只有可以被多线程访问的共享资源才需要考虑锁定,比如静态变量,再比如某些缓存中的值,属于线程内部的变量不需要锁定

把锁定交给数据库
数据库除了存储数据之外,还有一个重要的用途就是同步,数据库本身用了一套复杂的机制来保证数据的可靠和一致性,这就为我们节省了很多的精力.保证了数据源头上的同步,我们多数的精力就可以集中在缓存等其他一些资源的同步访问上了

了解你的程序是怎么运行的
实际上在web开发中大多数逻辑都是在单个线程中展开的,无论asp.net还是php,一个请求都会在一个单独的线程中处理,其中的大部分变量都是属于这个线程的,根本没有必要考虑锁定,当然对于asp.net中的application对象中的数据,我们就要小心一些了

WinForm中凡是使用BeginInvoke和Invoke调用的方法也都不需要考虑同步,因为这用这两个方法调用的方法会在UI线程中执行,因此实际是同步的,所以如果调用的方法中存在某些静态变量,不需要考虑锁定

业务逻辑对事务和线程安全的要求
这条是最根本的东西,开发完全线程安全的程序是件很费时费力的事情,在电子商务等涉及金融系统的案例中,许多逻辑都必须严格的线程安全,所以我们不得不牺牲一些性能,和很多的开发时间来做这方面的工作,而一般的应用中,许多情况下虽然程序有竞争的危险,我们还是可以不使用锁定,比如有的时候计数器少一多一,对结果无伤大雅的情况下,我们就可以不用去管他

计算一下冲突的可能性
我以前曾经谈到过,架构不要过设计,其实在这里也一样,假如你的全局缓存里的某个值每天只有几百或者几千个访问,并且访问时间很短,并且分布均匀(实际上这是大多数的情况),那么冲突的可能性就非常的少,也许每500天才会出现一次或者更长,从7*24小时安全服务的角度来看,也完全符合要求,那么你还会为这样万分之一的可能性花80%的精力去设计吗?

请多使用lock,少用Mutex
如果你一定要使用锁定,请尽量不要使用内核模块的锁定机制,比如.net的Mutex,Semaphore,AutoResetEvent,ManuResetEvent,使用这样的机制涉及到了系统在用户模式和内核模式间的切换,所以性能差很多,但是他们的优点是可以跨进程同步线程,所以应该清楚的了解到他们的不同和适用范围

 

 

Web和IIS应用程序池,WebApplication,和线程池之间有什么关系
一个应用程序池是一个独立的进程,拥有一个线程池,应用程序池中可以有多个WebApplication,每个运行在一个单独的AppDomain中,这些WebApplication公用一个线程池

不同的AppDomain保证了每个WebApplication的静态变量不会互相干扰,不同的应用程序池保证了一个网站瘫痪,其他不同进程中的站点还能正常运行

 下图说明了他们的关系

 

Web页面怎么调用异步WebService
把Page的Async属性设置为true,就可以调用异步的方法,但是这样调用的效果可能并不如我们的相像,请参考Web中使用多线程来增强用户体验

 

 

推荐文章

http://www.cnblogs.com/uubox/archive/2007/12/18/1003953.html
(内核对象同步,讲的很通俗易懂ManuResetEvent,AutoResetEvent) 

http://alang79.blogdriver.com/alang79/456761.html

A low-level Look at the ASP.NET Architecture

参考资料

<Windows 核心编程>这本书里对内核对象的描述比较详尽
<.Net框架程序设计>和上面一本一样也是大牛Jeffery Richard的作品
 

发表在 article | 标签为 | 32条评论

12 个月份的英语单词的来历(帮你记住12个英语单词)

公历一年有12个月,但不少人并不知道12 个月的英语名称的来历。公历起源于古罗马历法。罗马的英语原来只有10 个月,古罗马皇帝决定增加两个月放在年尾,
后来朱里斯*凯撒大帝把这两个月移到年初,成为1月.2月,原来的1月.2月便成了3月.4月,依次类推。这就是今天世界沿用的公历。

January——1月

在罗马传说中,有一位名叫雅努斯的守护神,生有先后两副脸,一副回顾过去,一副要眺望未来。人们认为选择他的名字作为除旧迎新的第一个月月名,很有意
义。英语January,便是由这位守护神的拉丁文名字January演变而来的。

February——2月
每年2 月初,罗马人民都要杀牲饮酒 ,欢庆菲勃卢姆节。这一天,人们常用一种牛、草制成的名叫Februa的鞭子,抽打不育的妇女,以求怀孕生子。 这一天,人们还要忏悔自己过去一年的罪过,洗刷自己的灵魂,求得神明的饶恕,使自己成为一个贞洁的人。英语2月Feb ruary,便是由拉丁文Februar-ius(即菲勃卢姆
节)演变而来。

March-----3月

3月,原是罗马旧历法的1 月,新年的开始。凯撒大帝改革历法后,原来的1月变成3月,但罗马人仍然把3 月看做是一年的开始。另外,按照传统习惯,3月是每
年出征远战的季节。为了纪念战神玛尔斯,人们便把这位战神的拉丁名字作为3月的月名。英语3月 March,便是由这位战神的名字演变而来的。

April——4月

罗马的4月,正是大地回春.鲜花初绽的美好季节。英文4月April便由拉丁文April(即开花的日子)演变而来。

May——5月

罗马神话中的女神玛雅,专门司管春天和生命。为了纪念这位女神,罗马人便用她的名字——拉丁文Maius命名5月,英文5月May便由这位女神的名字演变而来。

June——6月

罗马神话中的裘诺,是众神之王,又是司管生育和保护妇女的神。古罗马对她十分崇敬,便把6月奉献给她,以她的名字——拉丁文Junius来命名6 月。英语6
月June便由这位女神的名字演变而来。也有学者认为,Junius可能是个代拉丁家族中一个显赫贵族的姓氏。

July——7月

罗马统治者朱里斯*凯撒大帝被刺死后,著名的罗马将军马克*按东尼建议将凯撒大帝诞生的7月,用凯撒的名字——拉丁文Julius(即朱里斯)命名之。这一建
议得到了元老院的通过。英语7月July由此演变而来。

August——8月

朱里斯*凯撒死后,由他的甥孙屋大维续任罗马皇帝。为了和凯撒齐名,他也想用自己的名字来命名一个月份。他的生日在9月,但他选定8月。因为他登基后,
罗马元老院在8 月授予他Augustus(奥古斯都)的尊号。于是,他决定用这个尊号来命名8月。原来8月比7月少一天,为了和凯撒平起平坐,他又决定从2月中
抽出一天加在8月上。从此,2月便少了一天。英语8月August便由这位皇帝的拉丁语尊号演变而来。

September——9月

老历法的7月,正是凯撒大帝改革历法后的9月,拉丁文Septem是“7”月的意思。虽然历法改革了,但人们仍袭用旧名称来称呼9月。英语9月September,便由
此演变而来。

October——10月

英语10月,来自拉丁文Octo,即“8”的意思。它和上面讲的9月一样,历法改了,称呼仍然沿用未变。

November——11月

罗马皇帝奥古斯都和凯撒都有了自己名字命名的月份,罗马市民和元老院要求当时的罗马皇帝梯比里乌斯用其名命名11月。但梯比里乌斯没有同意,他明智地
对大家说,如果罗马每个皇帝都用自己的名字来命名月份,那么出现了第13个皇帝怎么办?于是,11月仍然保留着旧称Novem,即拉丁文“9”的意思。英语11月
November便由此演变而来。

December——12月罗马皇帝琉西乌斯要把一年中最后一个月用他情妇的Amagonius的名字来命名,但遭但元老院的反对。于是,12月仍然沿用旧名Decem,即拉丁
文“10”的意思。英语12月December,便由此演变而来。

星期的单词
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
Sunday
 

发表在 english | 标签为 | 12 个月份的英语单词的来历(帮你记住12个英语单词)已关闭评论

nginx和squid配合搭建的web服务器前端系统

点击查看原图

 

前端的lvs和squid,按照安装方法,把epoll打开,配置文件照搬,基本上问题不多。

这个架构和app_squid架构的区别,也是关键点就是:加入了一级中层代理,中层代理的好处实在太多了:

1、gzip压缩

压缩可以通过nginx做,这样,后台应用服务器不管是apache、resin、lighttpd甚至iis或其他古怪服务器,都不用考虑压缩的功能问题。

2、负载均衡和故障屏蔽

nginx可以作为负载均衡代理使用,并有故障屏蔽功能,这样,根据目录甚至一个正则表达式来制定负载均衡策略变成了小case。

3、方便的运维管理,在各种情况下可以灵活制订方案。

例如,如果有人用轻量级的ddos穿透squid进行攻击,可以在中层代理想办法处理掉;访问量和后台负载突变时,可以随时把一个域名或一个目录的请求扔入二级cache服务器;可以很容易地控制no-cache和expires等header。等等功能。。。

4、权限清晰

这台机器就是不写程序的维护人员负责,程序员一般不需要管理这台机器,这样假如出现故障,很容易能找到正确的人。

对于应用服务器和数据库服务器,最好是从维护人员的视线中消失,我的目标是,这些服务只要能跑得起来就可以了,其它的事情全部可以在外部处理掉。

发表在 web server | 标签为 , | nginx和squid配合搭建的web服务器前端系统已关闭评论

nginx proxy_cache

从nginx-0.7.44版开始,nginx支持了类似squid较为正规的cache功能,这个缓存是把链接用md5编码hash后保存,所以它可以支持任意链接,同时也支持404/301/302这样的非200状态。

 

开启缓存

使用 proxy_cache_path 配置缓存空间(必须放在 http 上下文的顶层位置),然后在目标上下文中使用 proxy_cache 指令。

proxy_cache_path /path/to/cache levels=1:2 keys_zone=NAME:10m inactive=5m max_size=10g clean_time=1m;

1、levels指定该缓存空间有两层hash目录,第一层目录是1个字母,第二层为2个字母,保存的文件名就会类似/path/to/cache/c/29/b7f54b2df7773722d382f4809d65029c;

2、keys_zone 设置一个共享内存区,该内存区用于存储缓存键和元数据,有些类似计时器的用途。将键的拷贝放入内存可以使NGINX在不检索磁盘的情况下快速决定一个请求是HIT还是MISS,这样大大提高了检索速度。一个1MB的内存空间可以存储大约8000个key,那么上面配置的10MB内存空间可以存储差不多80000个key。

3、inactive 指定项目在不被访问的情况下能够在内存中保持的时间。上述例子,如果一个文件在5分钟之内没有被请求,则缓存管理将会自动将其在内存中删除,不管该文件是否过期。该参数默认值为10分钟(10m)。注意,非活动内容有别于过期内容。NGINX不会自动删除由缓存控制头部指定的过期内容(例Cache-Control:max-age=120)。过期内容只有在inactive指定时间内没有被访问的情况下才会被删除。如果过期内容被访问了,那么NGINX就会将其从原服务器上刷新,并更新对应的inactive计时器。

4、max_size设置了缓存的上限(在上面的例子中是10G);这是一个可选项,如果不指定就允许缓存不断增长,占用所有可用的磁盘空间。当缓存达到这个上限,处理器便调用cache manager来移除最近最少被使用的文件,这样把缓存的空间降低至这个限制之下。

5、clean_time指定一分钟清理一次缓存。

6、NGINX最初会将注定写入缓存的文件先放入一个临时存储区域, use_temp_path=off命令指示NGINX将在缓存这些文件时将它们写入同一个目录下。强烈建议将参数设置为off来避免在文件系统中不必要的数据拷贝。use_temp_path在NGINX1.7版本中有所介绍

 

访问过期缓存

NGINX内容缓存的一个非常强大的特性是:当无法从原始服务器获取最新的内容时,NGINX可以分发缓存中的陈旧(stale,编者注:即过期内容)内容。这种情况一般发生在关联缓存内容的原始服务器宕机或者繁忙时。比起对客户端传达错误信息,NGINX可发送在其内存中的陈旧的文件。NGINX的这种代理方式,为服务器提供额外级别的容错能力,并确保了在服务器故障或流量峰值的情况下的正常运行。为了开启该功能,只需要添加proxy_cache_use_stale命令即可:

location / {

...

proxy_cache_use_stale error timeout http_500 http_502 http_503 http_504;

}

按照上面例子中的配置,当NGINX收到服务器返回的error,timeout或者其他指定的5xx错误,并且在其缓存中有请求文件的陈旧版本,则会将这些陈旧版本的文件而不是错误信息发送给客户端。

 

配置后端服务器组:

upstream my_upstream {

server 192.168.61.1:9080max_fails=10 fail_timeout=10s weight=5;

}

 

配置例子:

NGINX提供了丰富的可选项配置用于缓存性能的微调。

proxy_cache_path /path/to/cache levels=1:2 keys_zone=my_cache:10m max_size=10g inactive=60m
use_temp_path=off;
server {
...
location / {
proxy_cache my_cache;
proxy_cache_revalidate on;
proxy_cache_min_uses 3;
proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
proxy_cache_lock on;
proxy_pass http://my_upstream;
}
}

1、proxy_cache_revalidate指示NGINX在刷新来自服务器的内容时使用GET请求。如果客户端的请求项已经被缓存过了,但是在缓存控制头部中定义为过期,那么NGINX就会在GET请求中包含If-Modified-Since字段,发送至服务器端。这项配置可以节约带宽,因为对于NGINX已经缓存过的文件,服务器只会在该文件请求头中Last-Modified记录的时间内被修改时才将全部文件一起发送。

2、proxy_cache_min_uses设置了在NGINX缓存前,客户端请求一个条目的最短时间。当缓存不断被填满时,这项设置便十分有用,因为这确保了只有那些被经常访问的内容才会被添加到缓存中。该项默认值为1。

3、proxy_cache_use_stale中的updating参数告知NGINX在客户端请求的项目的更新正在原服务器中下载时发送旧内容,而不是向服务器转发重复的请求。第一个请求陈旧文件的用户不得不等待文件在原服务器中更新完毕。陈旧的文件会返回给随后的请求直到更新后的文件被全部下载。

4、当proxy_cache_lock被启用时,当多个客户端请求一个缓存中不存在的文件(或称之为一个MISS),只有这些请求中的第一个被允许发送至服务器。其他请求在第一个请求得到满意结果之后在缓存中得到文件。如果不启用proxy_cache_lock,则所有在缓存中找不到文件的请求都会直接与服务器通信。

 

指定哪些方法的请求被缓存

例如 proxy_cache_methods GET HEAD POST;

 

缓存跳过

例如 proxy_cache_bypass $cookie_nocache $arg_nocache$arg_comment;

如果任何一个参数值不为空,或者不等于0,nginx就不会查找缓存,直接进行代理转发

例子:

location / {
proxy_pass http://www.sudone.com/;

proxy_cache NAME;#使用NAME这个keys_zone

proxy_cache_valid 200 302 1h;#200和302状态码保存1小时
proxy_cache_valid 301 1d;#301状态码保存一天
proxy_cache_valid any 1m;#其它的保存一分钟
}

 

应用案例:

server {
listen 80;
server_name _;
server_tokens off;
location / {
proxy_pass http://127.0.0.1:8080/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_cache my-cache;
proxy_cache_valid 3s;
proxy_no_cache $cookie_PHPSESSID;
proxy_cache_bypass $cookie_PHPSESSID;
proxy_cache_key "schemehost$request_uri";
add_header X-Cache $upstream_cache_status;
}
}

server {
listen 8080;
server_name _;
root /var/www/your_document_root/;
index index.php index.html index.htm;
server_tokens off;
location ~ \.php$ {
try_files $uri /index.php;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME documentroot fastcgi_script_name;
include /etc/nginx/fastcgi_params;
}
location ~ /\.ht {
deny all;
}
}

 

配置汇总:

1.add_header cache-status $upstream_cache_status在响应头中添加缓存命中的状态。

HIT:缓存命中,直接返回缓存中内容,不回源到后端。

MISS:缓存未命中,回源到后端获取最新的内容。

EXPIRED:缓存命中但过期了,回源到后端获取最新的内容。

UPDATING:缓存已过期但正在被别的Nginx Worker进程更新,配置了proxy_cache_use_stale updating指令时会存在该状态。

STALE:缓存已过期,但因后端服务出现了问题(比如后端服务挂了)返回过期的响应,配置了如proxy_cache_use_stale error timeout指令后会出现该状态。

REVALIDATED:启用proxy_cache_revalidate指令后,当缓存内容过期时,Nginx通过一次if-modified-since的请求头去验证缓存内容是否过期,此时会返回该状态。

BYPASS:proxy_cache_bypass指令有效时,强制回源到后端获取内容,即使已经缓存了。

4.proxy_cache_min_uses

用于控制请求多少次后响应才被缓存。默认“proxy_cache_min_uses1;”,如果缓存热点比较集中、存储有限,则可以通过修改该参数来来减少缓存数量和写磁盘次数。

5.proxy_no_cache

用于控制什么情况下响应不被缓存。比如配置“proxy_no_cache$args_nocache”,如果带的nocache参数值至少有一个不为空或者为0,则响应将不被缓存。

6.proxy_cache_bypass

类似于proxy_no_cache,但是,其控制什么情况不使用缓存的内容,而是直接到后端获取最新的内容。如果命中,则$upstream_cache_status为BYPASS。

7.proxy_cache_use_stale

当对缓存内容的过期时间不敏感,或者后端服务出问题时,即使缓存的内容不新鲜也总比返回错误给用户强(类似于托底),此时可以配置该参数,如“proxy_cache_use_stale error timeout http_500 http_502 http_503 http_504”,即如果出现超时、后端连接出错、500、502、503等错误时,则即使缓存内容已过期也先返回给用户,此时$upstream_cache_status为STALE。还有一个updating表示缓存已过期但正在被别的Nginx Worker进程更新,但先返回了过期内容,此时$upstream_cache_status为UPDATING。

8.proxy_cache_revalidate

当缓存过期后,如果开启了proxy_cache_revalidate,则会发出一次if-modified-since或if-none-match条件请求,如果后端返回304,则此时$upstream_cache_status为REVALIDATED,我们将得到两个好处,节省带宽和减少写磁盘的次数。

9.proxy_cache_lock

当多个客户端同时请求同一份内容时,如果开启proxy_cache_lock(默认off),则只有一个请求被发送至后端。其他请求将等待该请求的返回。当第一个请求返回后,其他相同请求将从缓存中获取内容返回。当第一个请求超过了proxy_cache_lock_timeout超时时间(默认为5s),则其他请求将同时请求到后端来获取响应,且响应不会被缓存(在1.7.8版本之前是被缓存的)。启用proxy_cache_lock可以应对Dog-pile effect(当某个缓存失效时,同时有大量相同的请求没命中缓存,而同时请求到后端,从而导致后端压力太大,此时限制一个请求去拿即可)。

proxy_cache_lock_age是1.7.8新添加的,如果在proxy_cache_lock_age指定的时间内(默认为5s),最后一个发送到后端进行新缓存构建的请求还没有完成,则下一个请求将被发送到后端来构建缓存(因为1.7.8版本之后,proxy_cache_lock_timeout超时之后返回的内容是不缓存的,需要下一次请求来构建响应缓存)。

 

缓存清理

有时缓存的内容是错误的,需要手工清理,Nginx企业版提供了purger功能,对于社区版Nginx可以考虑使用ngx_cache_purge(https://github.com/FRiCKLE/ngx_cache_purge)模块进行清理缓存。

location ~ /purge(/.*) {

allow 127.0.0.1;

deny all;

proxy_cache_purge cache$1$is_args$args;

}

 

NGINX SQUID 分流:

bbs运行在缓存上,用户每发布一张帖子,都需要使用purge指令清除该帖子的缓存,如果是squid在最前端,那么每次发布一张帖子,都需要在所有的squid中调用purge指令,这样在机器比较多的时候,purge将成为一个巨大的压力。

所以在这里将Nginx squid架构放在最前端并使用手工url_hash的方式分流,将经常需要purge的帖子页面和列表页面按一个url对应一台squid的策略,分布到各台squid上,并提供了一台或一组backup的squid,个别squid出现异常时将自动使用backup的机器继续提供一段时间的服务直到其正常。在这样的架构下,purge就不再是关键问题,因为一个url只会对应到一台机器上,所以purge的时候,后端app_server找到对应的机器就可以了。

可以看到在前端中还有一台Nginx(purge)的机器,这台机器是专用于purge的,只要发送purge指令和需要清除的url到这台机器,就可以找到相应的服务器并清除缓存了。另外,purge时还需要清理backup机器上的缓存,所以无论前端机器增加到多少,purge指令只会在2台机器上执行,如果backup机器使用到2-3台,purge指令就会在3-4台机器上执行,仍然在可接受范围之内。

Nginx squid架构作为前端,另有的好处:

1/使用Nginx的日志统计点击量非常方便

 

 

 

聊聊:

 

一个很悲剧的事实
对动态网页使用CDN,无论squid还是varnish都不能直接用,都需定制代码。
例如varnish会判断response的header,如果发现里面有set-cookie项,它就认为这个页面不应该被缓存。对于规模庞大/OOP封装严密的网站,普通程序员根本意识不到调用哪一个fucntion会输出set-cookie,这个会导致CDN命中率急剧降低。但你也无力去对每行代码做code review,没有办法,只能去修改varnish代码了,这又引入一个新的维护成本. Squid也有这个问题

purge效率
purge就是CDN删除缓存项的接口,国内的UGC网站,因为严厉的内容检查制度和泛滥的垃圾广告,删帖子删图片特别频繁,某些网站可能高达40%(发100个贴,有40个帖子可能被删除或者修改),所以对purge的效率有要求。
squid和varnish的purge效率都达不到国内这种强度要求,nginx+memcache purge性能要好很多。
在当前的中国,遇到突发事件后,要是不及时删除指定的链接或内容,后果可能会很严重(小到个人被炒,大到公司被关都有可能)
某门户网站曾经发生过,某个链接怎么也删不掉,一慌张把CDN所有缓存都删了重启,导致内网流量瞬间暴涨,各业务线的服务器全线报警

 

..

发表在 web server | 标签为 | nginx proxy_cache已关闭评论

监测Squid日志的五种方法

Squid是Unix、Linux环境下一款优秀的代理服务器软件,本文叙述了Squid代理日志的五种监测方法,五种方法各有重点,可根据需要进行选择使用。

1 使用命令行

访问时间 持续时间 客户IP 采用协议 传输字节 访问方式 服务IP

如果我们仅仅需要查看某一个字段,可以用awk命令,它把一个记录行分割成多个字段,我们使用参数传回需要的字段。命令如下:

# tail -f /var/log/squid/access.log | awk '{print$3 " " $8 " " $7}'

这里选择的是客户IP及取回内容字段,显示如下:

192.168.1.146 - http://jump.qq.com/clienturl_simp_80

192.168.1.149 - http://mm.china.com/zh_cn/images/tit_liangzhuang.gif

192.168.1.161 - http://ly.zzip.com.cn/movie/list.aspx ?

这种方式的优点是实时性强,显示的是当前正在访问的记录的情况。

2 用SARG进行监测

SARG的全称是Squid Analysis Report Generator,即Squid报告分析产生器。利用SARG,可以窗口的方式显示上网用户的浏览记录,包括用户的访问时间,访问站点,传输字节等。SARG可从站点http://sourceforge.net/projects/sarg/ 下载,最新版本是2.0,目前还没有中文版本,这里下载的是源代码包。

1.安装SARG

把下载后的sarg-2.0.1.tar.gz拷贝到/usr/local/目录,进行解压缩并安装。执行如下指令:

#cp sarg-2.0.1.tar.gz /usr/local/

#cd /usr/local/

#tar -zfxv sarg-2.0.1.tar.gz

解压后在当前目录下产生sarg-2.0.1目录,进入到这个目录,执行以下安装命令:

# ./configure

# make

# make install

2.配置SARG

软件安装在/usr/local/sarg/目录下,目录下的配置文件sarg.conf主要包括如下参数:“language English”定义报告显示语言;“title "Squid User Access Reports"”定义报告文件的标题;“output_dir /var/www/html/squid-reports”定义报告文件的输出目录;“user_ip yes”以用户IP为基准进行显示,前提是进行了很好的用户IP控制;“access_log /var/log/squid/access.log”指定Squid日志文件的存放位置;“report_type topsites sites_users users_sites date_time denied auth_failures”显示产生的报告文件里包括什么类型的内容(如表所示)。

3.使用SARG

配置完成之后,在终端窗口用命令sarg生成报告文件,然后就可以在客户端使用浏览器通过访问http://Squid 日志服务器地址/squid-reports/来查看squid日志了。图1所示是查看日志的一个界面。

图1 使用SARG查看日志界面

3 用Webalizer进行监测

Webalizer工作方式不同于SARG,这个软件主要侧重于信息的汇总,如带宽、输入输出量,用于比较在不同的时间段网络的使用情况。

1.安装Webalizer

从站点http://www.mrunix.net/webalizer/download.html 下载,当前稳定版本是 2.01-10,提供RPM包和tar包格式的下载,笔者在这里下载的是webalizer-2.01-10-src.tgz。

进入到下载目录,执行如下命令:

#tar-xvzf webalizer-2.01-10-src.tgz

#cd web webalizer-2.01-10

#./configure

#make

#make install

2.配置Webalizer

Webalizer的配置文件是webalizer.conf,它可以放在安装目录下,也可以放在/etc/目录下,安装后产生可执行命令webalizer,当命令执行时,寻找webalizer.conf配置文件,产生相应的输出。webalizer.conf的配置比较简单,主要是指定squid日志文件的目录位置及产生报告的输出目录,主要参数如下:“LogFile /var/log/squid/access.log”表示squid日志文件目录;“LogType squid”表示Webalizer报告文件的输出类型;“OutputDir /home/webalizer/ ”表示报告文件的输出目录。

3.使用Webalizer

当执行webalizer命令后,在输出目录后输出报告文件,文件是基于时间段的,每一个时间段的报告产生的非常详细,查看文件仍可使用客户端浏览器方式。

4 用Calamaris进行监测

Calamaris是一个用perl语言写成的程序,如果要使用的话,首先系统里要安装perl解释器。它产生Squid日志的详细报告,包括按高峰时间时的使用情况、流进网内流量、流出流量、进出UDP包、进出TCP包、请求的二级或三级域名产生的报告文档。除用于Squid日志产生的日志分析外,它还可用于其他形式的代理服务软件产生的日志,如NetCache、Inktomi Traffic Server、Oops! proxy server、Novell InterNet Caching System等。

安装Calamaris也很简单,首先从站点http://cord.de/tools/squid/calamaris/Welcome.html.en 下载Calamaris V2.59到/usr/local/目录。执行以下命令:

#cd /usr/local/

#tar xvzf calamaris-2.59.tar.gz

# cd calamaris-2.59

解压后的目录包含可执行文件calamaris,无须安装,直接使用即可,比如用以下命令使Calamaris处理squid日志文件,产生html格式的文件,并输出到/var/www/html/calamaris/index.html。然后执行如下命令就可以查看输出报告了。

#/usr/local/calamaris/calamaris -a -F html /var/log/squid/access.log>/var/www/html/calamaris/index.html

对一般的应用来讲,上面的命令产生了最详细的输出报告,我们用命令选项-a表示产生所有类型的报告,用选项-F html表示产生html格式的文档。/var/log/squid/access.log表示squid日志文件的存放位置,/var/www/html/calamaris/index.html表示输出的文件名。当产生报告文件后,在客户端用浏览器可进行浏览。

5 用Squid-Graph进行监测

Squid-Graph同Calamaris一样,也是用Perl写成的,但正如它的名字一样,它用图形化的方法产生squid代理的使用情况,它产生一些综合信息。

可以从站点http://squid-graph.securlogic.com/files/stable/squid-graph-3.1.tar.gz 下载这个软件,这个软件是目前最后一个可获得的版本。这个程序的执行需要使用Perl GD模块,名称一般为GD.pm,是perl的绘图模块,其功能类似于市面上的许多图形程序。利用GD,我们展示如何创建几何图形,以及如何进行图像处理,这个模块可在软件发行版本的CD上找到,也可以从站点http://stein.cshl.org/WWW/software/GD/ 下载。

把下载的软件放到/usr/local/目录,进行解压。执行如下命令:

#cd /usr/local/

# tar xvzf squid-graph-3.1.tar.gz

# mv squid-graph-3.1 squid-graph

# cd squid-graph

# chmod +x /usr/local/squid-graph/bin/*

软件不须安装,直接使用,比如我们用以下的命令可产生TCP访问的累计图形:

#/usr/local/squid-graph/bin/squid-graph -c -n -o=/var/www/html/squid-graph/ --title="Squid server usage of proxy" < /var/log/squid/access.log

上面的这个命令用-c选项产生累计图形,用-n选项指定在命令执行过程中不向屏幕上输出信息,-o选项指定输出文件的目录,-title选项指定自定义的输出文档标题,图2是一个输出示例。

图2 Squid-Graph的显示

其实,squid-graph命令配合Linux下的其他命令如grep可以在squid日志中搜索需要的字符串,然后针对这一匹配项产生需要的图形。如下面的命令就产生了在所有日志行中有字符串“192.168.6.99”的客户端机器的使用squid代理的图形。

#cat /var/log/squid/access.log|grep"192.168.6.99"| /usr/local/squid-graph/bin/squid-graph -c -n / -o=/var/www/html/squid-graph/ --title="192.168.6.99’s usage"

利用这种方式,还可以产生统计访问某一个站点,访问特殊类型的文件如“.MP3”的统计图形

发表在 article | 标签为 | 35条评论

PIF病毒解决方案

最近局域网里面感染了PIF的病毒,十几台电脑无一幸免。它的症状大约是这样的,

1、路由器出现声音报警,提示有ARP攻击;

2、两个局域网中的一个局域网出现掉线的情况;

3、出现掉线的局域网中的大多数电脑系统时间都被改成了2004年

4、360、卡巴等软件全部被关闭。


5、电脑出现360、杀毒软件、注册表编辑器等无法打开,一双击运行就出现上“文件正在使用中”的提示。

6、安全模式无法进入,出现蓝屏重启现象。

7、无法查看隐藏文件,文件选项中“隐藏文件和文件夹”下两个选项同时被选中的情况。

8、各个分区、包括接入的U盘等都出现autorun.inf情况

9、进程里很多3.pif,h.pif,5.pif,无法结束进程

 

网上有提供一些解决方案,方案一:

360安全卫士最新版安装程序,“windows修复专家”,“SREng2”,“冰刃 ICEWORD”

将上述软件存放到光盘或U盘中,待修复系统时使用。

1、修复IFEO映像挟持:

  对付IFEO映像挟持最好的方法就是修改可执行文件的文件名,尝试修改360安全卫士的文件名,双击运行360发现360已经可以打开,但是360打开不多久就被关闭。利用“冰刃”查看进程,发现“冰刀”同样被IFEO映像挟持。修改““冰刃”的可执行文件,“冰刃”可正常运行。在结束“wuauclt.exe”进程后,360等软件已经不会被关闭,利用360的扫描“恶评插件”功能并清除后,即可恢复安全模式的正常运行,也可以清理所有的“IFEO映像挟持”。

修改系统日期为当前正确日期。

二、处理“wuauclt.exe”防止死灰复燃

  “wuauclt.exe”本来是Windows的系统更新程序,但是这次发现的木马程序已经将这个文件变成了
自己。所以这一步要干掉它。

  进入安全模式,利用其它电脑正常的“wuauclt.exe”文件换掉c:\windows\system32以及 c:\windows\system32\dllcache\中的文件,实在找不到正常的文件,
可以直接删除这两个文件,由于目前系统无法查看系统隐藏文件,所以要在命令行的模式下进行清除

  在运行中运行“CMD”进入命令行模式,依次运行下列命令

attrib -s -h -r c:\windows\system32\wuau*.exe

attrib -s -h -r c:\windows\system32\dllcache\wuau*.exe

del c:\windows\system32\wuau*.exe

del c:\windows\system32\dllcache\wuau*.exe

打开SREng,删除蓝色的异常的启动项目。

然后将先前修改的360的可执行文件改成原来的,如果忘记可以在这个时候重装360,并且在保护功能中打开ARP防火墙功能。

重启计算机进入正常模式,然后安装“windows修复专家”,用它对系统进行扫描,清理一切发现的可清除项目,当提示“进行驱动级清理”后,在提示重启计算机时,选择重启。




重启后,“windows修复专家”,会在桌面启动前再次运行并自动扫描,当扫描完成后关闭“windows修复专家”,这时桌面
出现。利用360对IE进行全面修复。

查找并删除硬盘中一切可删除的PIF文件。

到此清理基本成功。

 

 

因为网上提供的方法需要的工具太多了,我根本无法联网,上网下载软件就会被结束浏览器的进程,还不停跳出黄色网站。我只好拔开网线,手动删毒。

我先重装了系统,然后启动系统盘的PE系统,在PE系统里面查找*.pif文件,把这些文件都删除,查找autorun.inf文件,都删除它。重启电脑,装上360再扫描一遍,问题就解决了。

如果真的不想重装系统那也可以用USBcleaner6.0 修复系统安全模式,保护系统时间,进入安全模式再装杀毒软件,杀毒完了,再进PE系统手动删除所有PIF文件,所有恶意的autorun文件。

发表在 article | 标签为 | 66条评论

squid集群配置

在学习CDN方面资料时,所搭建的squid集群环境,供以后学习!
[基本架构]
squid1
                squid      web
squid2
squid1和squid2是姐妹关系,squid1,squid2和squid是父子关系,squid与web也是父子关系.
[IP分配信息]
squid    192.168.5.163
squid1   192.168.5.161
squid2   192.168.5.145
web      192.168.5.162
[内容]将其配置文件Copy下
squid1配置文件
acl all src 0.0.0.0/0.0.0.0
acl manager proto cache_object
acl localhost src 127.0.0.1/255.255.255.255
acl to_localhost dst 127.0.0.0/8
acl SSL_ports port 443
acl CONNECT method CONNECT
acl purge method PURGE
#acl gsrc src 192.168.5.161 192.168.5.162 192.168.5.163
#acl gdst dst 192.168.5.161 192.168.5.162 192.168.5.163
acl Safe_ports port 80
acl Safe_ports port 3130

http_access allow manager localhost
http_access deny manager
http_access allow purge localhost
http_access deny purge
#http_access allow gsrc
#http_access allow gdst
http_access deny !Safe_ports
http_access deny CONNECT !SSL_ports
#http_access deny all
icp_access allow all
#http_port 80 accel vhost vport
hierarchy_stoplist cgi-bin ?
acl QUERY urlpath_regex cgi-bin \?
cache deny QUERY
visible_hostname slibing1.squid.com
cache_mgr zhengjun.zhu@tiancity.com
access_log /usr/local/squid/var/logs/access.log squid
#refresh_pattern ^ftp:          1440    20%     10080
#refresh_pattern ^gopher:       1440    0%      1440
refresh_pattern -i .html$       10      50%     20
acl apache rep_header Server ^Apache
broken_vary_encoding allow apache
coredump_dir /usr/local/squid/var/cache
cache_mem 64 MB
#negative_ttl 1 second
###################################################
http_port 80 accel vhost vport
icp_port 3130
cache_peer 192.168.5.163 parent 80 0 no-query originserver no-digest name=cache0
cache_peer 192.168.5.161 sibling 80 3130 name=cache1
cache_peer 192.168.5.163 sibling 80 3130 name=cache2
cache_peer 192.168.5.165 sibling 80 3130 name=cache3
cache_peer_domain cache0 [url]www.squid.com[/url]

http_access deny !Safe_ports
negative_ttl 1 second
icp_query_timeout 2000
digest_generation on
log_icp_queries on
icp_hit_stale   on
squid2配置文件
acl all src 0.0.0.0/0.0.0.0
acl manager proto cache_object
acl localhost src 127.0.0.1/255.255.255.255
acl to_localhost dst 127.0.0.0/8
acl SSL_ports port 443
acl CONNECT method CONNECT
acl purge method PURGE
#acl gsrc src 192.168.5.161 192.168.5.162 192.168.5.163
#acl gdst dst 192.168.5.161 192.168.5.162 192.168.5.163
http_access allow manager localhost
http_access deny manager
http_access allow purge localhost
http_access deny purge
#http_access allow gsrc
#http_access allow gdst
#http_access deny !Safe_ports
http_access deny CONNECT !SSL_ports
#http_access deny all
icp_access allow all
#http_port 80 accel vhost vport
hierarchy_stoplist cgi-bin ?
acl QUERY urlpath_regex cgi-bin \?
cache deny QUERY
visible_hostname slibing2.squid.com
cache_mgr zhengjun.zhu@tiancity.com
access_log /usr/local/squid/var/logs/access.log squid
#refresh_pattern ^ftp:          1440    20%     10080
#refresh_pattern ^gopher:       1440    0%      1440
refresh_pattern -i .html$       10      50%     20
acl apache rep_header Server ^Apache
broken_vary_encoding allow apache
coredump_dir /usr/local/squid/var/cache
cache_mem 64 MB
#negative_ttl 1 second
###################################################
http_port 80 accel vhost vport
icp_port 3130
cache_peer 192.168.5.163 parent 80 0 no-query originserver no-digest name=cache0
cache_peer 192.168.5.161 sibling 80 3130 name=cache1
cache_peer 192.168.5.163 sibling 80 3130 name=cache2
cache_peer 192.168.5.165 sibling 80 3130 name=cache3
cache_peer_domain cache0 [url]www.squid.com[/url]

acl Safe_ports port 80
acl Safe_ports port 3130
http_access deny !Safe_ports
negative_ttl 1 second
icp_query_timeout 2000
digest_generation on
log_icp_queries on
icp_hit_stale   on
squid配置文件
acl all src 0.0.0.0/0.0.0.0
acl manager proto cache_object
acl localhost src 127.0.0.1/255.255.255.255
acl to_localhost dst 127.0.0.0/8
acl SSL_ports port 443
acl CONNECT method CONNECT
acl purge method PURGE
acl CactiServer src 127.0.0.1 192.168.5.163
acl CactiServer src 192.168.5.161/255.255.255.255
acl SNMP snmp_community passwd
#acl gsrc src 192.168.5.161 192.168.5.162 192.168.5.163
#acl gdst dst 192.168.5.161 192.168.5.162 192.168.5.163
http_access allow manager localhost
http_access deny manager
http_access allow purge localhost
http_access deny purge
#http_access allow gsrc
#http_access allow gdst
#http_access deny !Safe_ports
http_access deny CONNECT !SSL_ports
#http_access deny all
snmp_port 3401
snmp_access allow SNMP CactiServer
snmp_access deny all
icp_access allow all
hierarchy_stoplist cgi-bin ?
acl QUERY urlpath_regex cgi-bin \?
cache deny QUERY
visible_hostname parent.squid.com
cache_mgr zhengjun.zhu@tiancity.com
access_log /usr/local/squid/var/logs/access.log squid
#refresh_pattern ^ftp:          1440    20%     10080
#refresh_pattern ^gopher:       1440    0%      1440
refresh_pattern -i .html$       10      50%     20
acl apache rep_header Server ^Apache
broken_vary_encoding allow apache
coredump_dir /usr/local/squid/var/cache
cache_mem 64 MB
#negative_ttl 1 second
###################################################
http_port 80 accel vhost vport
icp_port 3130
cache_peer 192.168.5.162 parent 80 0 no-query originserver no-digest name=cache0
cache_peer 192.168.5.162 sibling 80 3130 name=cache1
cache_peer 192.168.5.163 sibling 80 3130 name=cache2
cache_peer 192.168.5.165 sibling 80 3130 name=cache3
cache_peer_domain cache0 [url]www.squid.com[/url]

acl Safe_ports port 80
acl Safe_ports port 3130
http_access deny !Safe_ports
negative_ttl 1 second
icp_query_timeout 2000
icp_access allow all
digest_generation on
log_icp_queries on
icp_hit_stale   on
web配置
在其加了一个mod_expires模块
ExpiresActive On
ExpiresDefault A600
发表在 article | 标签为 | squid集群配置已关闭评论

.NET 中多线程间资源共享与访问

翻译

vinodramakrishnan著Managing shared resource access in .NET multi-threading

简介

本文详细地描述一个定制的.NET类ThreadLockHelper(该类能在多线程环境下有效地共享资源),同时提供了.NET下多线程中资源共享及同步技术的概述。

文中给出的helper类,将会帮助并简化.NET框架下,高级程序员们对多线程的使用。本文也讲述如何在多线程之间同步资源的访问。

本文将引导你:
使用.NET线程threading模型在访问共享资源时,设计一个更好的加锁机制。设计并实现复杂多线程的解决方案。
我们假设你比较属性.NET开发组件,和基本的线程机制。

内容

概述在多线程中访问共享资源。
设计和实现ThreadLockHelper类。
使用ThreadLockHelper类的示例程序。

总结

在多线程中访问共享资源概述

使用多线程技术,可以使一个.NET程序同时执行多个任务。多线程允许你同时开启多个线程,分别执行不同任务;还能够提高程序的性能和响应时间。

因为多线程能够同时访问资源,所以最好在多线程间进行同步。当一个程序运行在多线程环境下的时候,它需要确保当一个线程挂起的时候,不应该还占用着对象(资源)。线程安全的基本含义是:当多线程同时访问时,对象的成员总是管理着一个有效状态,确保它们不会冲突。

.NET提供了不同的同步机制,以管理多线程的线程安全。

lock

lock是一个关键字,它通过给一个对象加锁,执行语句,解锁,把一段语句标志为临界区。

 

示例代码

lock(obj) { // code to be locked will go here }

Monitor

Monitor:Monitor类是用来同步实例中的方法或静态的方法。这个方法要依赖于一个object,也就是说,它不是在如int或string之类的值上加锁。该临界区通过调用Monitor.Enter()建立,并通过Monitor.Exit()释放。

示例代码:

try
 
{
    Monitor.Enter(obj);
{ 
    // code to be locked will go here

}
finally
{
    Monitor.Exit(obj);
}

Mutex

当位于进程之内或之间的线程需要访问操作系统的资源的时候,需要一个控制机制来限制资源访问的冲突。 System.Threading.Mutex是一个继承于WaitHandle的类,它必须实现一个信号量机制表明排他地占用或释放资源。同一时间,只能有一个线程占用Mutex。在访问资源之前,每个线程都通过发信号,以获得Mutex的控制权。此后,线程还必须等待资源的控制权。当线程完成操作时,通过ReleaseMutex()发出完成信号( lock和Monitor对于unmanaged 资源是不起作用的)。

示例代码:

Mutex objMutex = new
 Mutex(false
, "ThreadLock"
 );
objMutex.WaitOne();
// code to be locked will go here

objMutex.ReleaseMutex();

 

ThreadLockHelper类的设计和实现

ThreadLockHelper类

ThreadLockHelper class is a singleton implementation and only one instance will be taking care of locking threads for a process to be executed.

ThreadLockHelper类需要的命名空间

using
 System;
using
 System.Threading;
/// <summary>

/// 一个静态的用于对managed/unmanaged资源进行加锁的类

/// </summary>

public
 class
 ThreadLockHelper 
{
    static
 ThreadLockHelper mInstance = null;
    Mutex mMutex = null; 
    
    private
 ThreadLockHelper ()
    {
    }
    
    public
 static
 ThreadLockHelper GetInstance()
    {
      if
( mInstance == null )
      {
        mInstance = new
 ThreadLockHelper (); 
        mInstance.mMutex = new
 Mutex(false
, "ThreadLock"
 );
      }
      return
( mInstance );
    }
    
    
    public
 bool
 CreateLock()
    {
      if
 ( mMutex == null )
      {
        mMutex = new
 Mutex(false
, "ThreadLock"
); 
      }
      return
( mMutex.WaitOne() );
    }
    
    public
 void
 ReleaseLock()
    {
        mMutex.ReleaseMutex();
    } 
}

调用示例程序
在进程执行前创建一个ThreadLockHelper锁,在执行后,释放。

public
 class
 Activity
{
    public
 void
 InvokeTask()
    {
        Task objTask = new
 Task(); 
        ThreadLockHelper.GetInstance().CreateLock();
        objTask.DoTask();
        ThreadLockHelper.GetInstance().ReleaseLock();
    }
}

上面的程序中,objTask.DoTask()操作用于访问一个共享的资源(例如:调用一个web服务完成某些功能)

如果你在不同的线程中调用了上面的InvokeTask()方法,示例如下:

Activity objActivity = null;
Thread thdInvokeTask ;
for
(int
 i=1; i < 100 ; i++)
{
    objActivity = new
 Activity(); 
    thdInvokeTask = new
 Thread(new
 ThreadStart(objClsThread.InvokeTask));
    thdInvokeTask.Start(); 
}

在上面的场景中,如果你不采用加锁机制,应用程序就会因线程退出异常(thread abort exception)失败。

你可以在objTask.DoTask()方法中加入Web服务调用,来测试上面的场景。

总结

本文给你一些有效的线程同步管理方法。还提出如何实现一个有效管理共享资源的定制类(ThreadLockHelper)。

程序中的同步锁不应该使用太多,否则可能会影响性能。在需要加的地方加锁才是正确的。

关于原文作者vinodramakrishnan

(MCSD) working as an Architect in a leading software organization having more than 8 years core IT experience in Microsoft Technologies.
Click here(http://www.codeproject.com/script/profile/whos_who.asp?vt=arts&id=1801947
) to view vinodramakrishnan's online profile.
 

发表在 article | 标签为 | .NET 中多线程间资源共享与访问已关闭评论

Nginx 常见应用技术指南[Nginx Tips] 第二版

原文:http://www.linuxtone.org/html/85/t-1685.html

http://nginx.net/

作者:NetSeek  http://www.linuxtone.org
(IT运维专家网|集群架构|性能调优)
欢迎转载,转载时请务必以超链接形式标明文章原始出处和作者信息及本声明.
首发时间: 2008-11-25     更新时间:2009-1-14

目 录
一、        Nginx 基础知识
二、        Nginx 安装及调试
三、        Nginx Rewrite
四、        Nginx Redirect
五、        Nginx 目录自动加斜线:
六、        Nginx Location
七、        Nginx expires
八、        Nginx 防盗链
九、        Nginx 访问控制
十、        Nginx日志处理
十一、     Nginx Cache
十二、     Nginx负载均衡
十三、       Nginx简单优化      
十四、        如何构建高性能的LEMP环境
十五、        Nginx服务监控
十六、        常见问题与错误处理.
十七、        相关资源下载

【前言】:
编写此技术指南在于推广普及NGINX在国内的使用,更方便的帮助大家了解和掌握NGINX的一些使用技巧。本指南很多技巧来自于网络和工作中或网络上朋友们问我的问题.在此对网络上愿意分享的朋友们表示感谢和致意!欢迎大家和我一起丰富本技术指南提出更好的建议!请朋友们关注: http://www.linuxtone.org
技术分享社区! 互想学习共同进步!

一、 Nginx 基础知识
1、简介
   Nginx ("engine x") 是一个高性能的 HTTP 和 反向代理 服务器,也是一个 IMAP/POP3/SMTP 代理服务器。 Nginx 是由 Igor Sysoev 为俄罗斯访问量第二的 Rambler.ru 站点开发的,它已经在该站点运行超过两年半了。Igor 将源代码以类BSD许可证的形式发布。尽管还是测试版,但是,Nginx 已经因为它的稳定性、丰富的功能集、示例配置文件和低系统资源的消耗而闻名了。
更多的请见官方wiki: http://wiki.codemongers.com/

2、 Nginx的优点
nginx做为HTTP服务器,有以下几项基本特性:
1)        处理静态文件,索引文件以及自动索引;打开文件描述符缓冲.
2)        无缓存的反向代理加速,简单的负载均衡和容错.
3)        FastCGI,简单的负载均衡和容错.
4)        模块化的结构。包括gzipping, byte ranges, chunked responses, 以及 SSI-filter等filter。如果由FastCGI或其它代理服务器处理单页中存在的多个SSI,则这项处理可以并行运行,而不需要相互等待。
5)        支持SSL 和 TLS SNI.

Nginx专为性能优化而开发,性能是其最重要的考量, 实现上非常注重效率 。它支持内核Poll模型,能经受高负载的考验, 有报告表明能支持高达 50,000 个并发连接数。
Nginx具有很高的稳定性。其它HTTP服务器,当遇到访问的峰值,或者有人恶意发起慢速连接时,也很可能会导致服务器物理内存耗尽频繁交换,失去响应,只能重启服务器。例如当前apache一旦上到200个以上进程,web响应速度就明显非常缓慢了。而Nginx采取了分阶段资源分配技术,使得它的CPU与内存占用率非常低。nginx官方表示保持10,000个没有活动的连接,它只占2.5M内存,所以类似DOS这样的攻击对nginx来说基本上是毫无用处的。就稳定性而言, nginx比lighthttpd更胜一筹。
Nginx支持热部署。它的启动特别容易, 并且几乎可以做到7*24不间断运行,即使运行数个月也不需要重新启动。你还能够在不间断服务的情况下,对软件版本进行进行升级。
Nginx采用master-slave模型, 能够充分利用SMP的优势,且能够减少工作进程在磁盘I/O的阻塞延迟。当采用select()/poll()调用时,还可以限制每个进程的连接数。
Nginx代码质量非常高,代码很规范, 手法成熟, 模块扩展也很容易。特别值得一提的是强大的Upstream与Filter链。 Upstream为诸如reverse proxy, 与其他服务器通信模块的编写奠定了很好的基础。而Filter链最酷的部分就是各个filter不必等待前一个filter执行完毕。它可以把前一个filter的输出做为当前filter的输入,这有点像Unix的管线。这意味着,一个模块可以开始压缩从后端服务器发送过来的请求,且可以在模块接收完后端服务器的整个请求之前把压缩流转向客户端。
Nginx采用了一些os提供的最新特性如对sendfile (Linux 2.2+),accept-filter (FreeBSD 4.1+),TCP_DEFER_ACCEPT (Linux 2.4+) 的支持,从而大大提高了性能

二、 Nginx 安装及调试
1、Pcre 安装

CODE:

./configure
  make && make install
  cd ../

2.        nginx 编译安装

CODE:

./configure --user=www --group=www --prefix=/usr/local/nginx/ --with-http_stub_status_module --with-openssl=/usr/local/openssl
make && make install

更详细的模块定制与安装请参照官方wiki.

3、Nginx 配置文件测试:

CODE:

# /usr/local/nginx/sbin/nginx -t  //Debug 配置文件的关键命令需要重点撑握.

2008/12/16 09:08:35 [info] 28412#0: the configuration file /usr/local/nginx/conf/nginx.conf syntax is ok
2008/12/16 09:08:35 [info] 28412#0: the configuration file /usr/local/nginx/conf/nginx.conf was tested successfully

3、Nginx 启动:

CODE:

# /usr/local/nginx/sbin/nginx
4、Nginx 配置文件修改重新加载:

CODE:

# kill -HUP `cat /usr/local/nginx/logs/nginx.pid
`
三、Nginx Rewrite

1.  Nginx Rewrite 基本标记(flags)
last - 基本上都用这个Flag。
※相当于Apache里的[L]标记,表示完成rewrite,不再匹配后面的规则
break - 中止Rewirte,不再继续匹配
redirect - 返回临时重定向的HTTP状态302
permanent - 返回永久重定向的HTTP状态301
      ※原有的url支持正则  重写的url不支持正则

2.  正则表达式匹配,其中:
    * ~         为区分大小写匹配
    * ~*       为不区分大小写匹配
    * !~和!~*   分别为区分大小写不匹配及不区分大小写不匹配

3. 文件及目录匹配,其中:
   * -f和!-f用来判断是否存在文件
    * -d和!-d用来判断是否存在目录
    * -e和!-e用来判断是否存在文件或目录
    * -x和!-x用来判断文件是否可执行

3.  Nginx 的一些可用的全局变量,可用做条件判断:

CODE:

$args
$content_length
$content_type
$document_root
$document_uri
$host
$http_user_agent
$http_cookie
$limit_rate
$request_body_file
$request_method
$remote_addr
$remote_port
$remote_user
$request_filename
$request_uri
$query_string
$scheme
$server_protocol
$server_addr
$server_name
$server_port
$uri

四、 Nginx Redirect
将所有linuxtone.org与netseek.linuxtone.org域名全部自跳转到http://www.linuxtone.org

CODE:

server
{
listen 80;
server_name linuxtone.org netseek.linuxtone.org;
index index.html index.php;
root /data/www/wwwroot;
if ($host !~ "^www\.linxtone\.org$") {
rewrite ^(.*) http://www.linuxtone.org$1 redirect;
}
........................
}

五、 Nginx 目录自动加斜线:

CODE:

if (-d $request_filename){
           rewrite ^/(.*)([^/])$ http://$host/$1$2/ permanent;
     }

六  Nginx Location

1.基本语法:[和上面rewrite正则匹配语法基本一致]
location [=|~|~*|^~] /uri/ { … }
    * ~  为区分大小写匹配
    * ~* 为不区分大小写匹配
    * !~和!~*分别为区分大小写不匹配及不区分大小写不匹配

示例1:
location = / {
# matches the query / only.
# 只匹配 / 查询。
}
匹配任何查询,因为所有请求都已 / 开头。但是正则表达式规则和长的块规则将被优先和查询匹配

示例2:
location ^~ /images/ {
# matches any query beginning with /images/ and halts searching,
# so regular expressions will not be checked.
# 匹配任何已 /images/ 开头的任何查询并且停止搜索。任何正则表达式将不会被测试。

示例3:
location ~* \.(gif|jpg|jpeg)$ {
# matches any request ending in gif, jpg, or jpeg. However, all
# requests to the /images/ directory will be handled by
}
# 匹配任何已 gif、jpg 或 jpeg 结尾的请求。

七、 Nginx expires

1.根据文件类型expires

CODE:

# Add expires header for static content
location ~* \.(js|css|jpg|jpeg|gif|png|swf)$ {
    if (-f $request_filename) {
       root /data/www/wwwroot/bbs;
       expires      1d;
       break;
    }
}

2、根据判断某个目录

CODE:

# serve static files
location ~ ^/(images|javascript|js|css|flash|media|static)/  {
root    /data/www/wwwroot/down;
        expires 30d;
  }

八、  Nginx 防盗链

1.        针对不同的文件类型

CODE:

#Preventing hot linking of images and other file types
location ~* ^.+\.(gif|jpg|png|swf|flv|rar|zip)$ {
        valid_referers none blocked server_names *.linuxtone.org linuxtone.org http://localhost baidu.com;
if ($invalid_referer) {
      rewrite   ^/   ;
     # return   403;
      }
}

2.        针对不同的目录

CODE:

location /img/ {
    root /data/www/wwwroot/bbs/img/;
    valid_referers none blocked server_names *.linuxtone.org http://localhost baidu.com;
    if ($invalid_referer) {
                   rewrite  ^/  ;
                   #return   403;
    }
}

3.        同实现防盗链和expires的方法

CODE:

#Preventing hot linking of images and other file types
location ~* ^.+\.(gif|jpg|png|swf|flv|rar|zip)$ {
        valid_referers none blocked server_names *.linuxtone.org linuxtone.org http://localhost ;
if ($invalid_referer) {
      rewrite   ^/   ;
                     }
     access_log off;
     root /data/www/wwwroot/bbs;
expires 1d;
     break;
}

九、 Nginx 访问控制

1.        Nginx 身份证验证

CODE:

#cd /usr/local/nginx/conf
#mkdir htpasswd
/usr/local/apache2/bin/htpasswd -c /usr/local/nginx/conf/htpasswd/tongji linuxtone
#添加用户名为linuxtone
New password:   (此处输入你的密码)
Re-type new password:   (再次输入你的密码)
Adding password for user
http://count.linuxtone.org/tongji/data/index.html(目录存在/data/www/wwwroot/tongji/data/目录下)
将下段配置放到虚拟主机目录,当访问http://count.linuxtone/tongji/即提示要密验证:
location ~ ^/(tongji)/  {
                root    /data/www/wwwroot/count;
                        auth_basic              "LT-COUNT-TongJi";
                        auth_basic_user_file  /usr/local/nginx/conf/htpasswd/tongji;
                }

2.        Nginx 禁止访问某类型的文件.
如,Nginx下禁止访问*.txt文件,配置方法如下.

CODE:

location ~* \.(txt|doc)$ {
   if (-f $request_filename) {
   root /data/www/wwwroot/linuxtone/test;
   #rewrite …..可以重定向到某个URL
   break;
   }
}

方法2:

CODE:

location ~* \.(txt|doc)${
        root /data/www/wwwroot/linuxtone/test;
        deny all;
}

实例:
禁止访问某个目录

CODE:

location ~ ^/(WEB-INF)/ {
            deny all;
}  

3.        使用ngx_http_access_module限制ip访问

CODE:

location / {
    deny    192.168.1.1;
    allow   192.168.1.0/24;
    allow   10.1.1.0/16;
    deny    all;
}

详细参见wiki: http://wiki.codemongers.com/NginxHttpAccessModule#allow

4.        Nginx 下载限制并发和速率

CODE:

limit_zone   linuxtone  $binary_remote_addr  10m;
server
       {
               listen       80;
               server_name  down.linuxotne.org;
               index index.html index.htm index.php;
               root   /data/www/wwwroot/down;
               #Zone limit
               location / {
                   limit_conn   linuxtone  1;
                   limit_rate  20k;
               }
..........
       }

只允许客房端一个线程,每个线程20k.
【注】limit_zone   linuxtone  $binary_remote_addr  10m; 这个可以定义在主的

5.        Nginx 实现Apache一样目录列表

CODE:

location  /  {
    autoindex  on;
}

6.        上文件大小限制
主配置文件里加入如下,具体大小根据你自己的业务做调整。
client_max_body_size 10m;                                                         

十、        Nginx 日志处理

1.Nginx 日志切割
#contab -e
59 23 * * * /usr/local/sbin/logcron.sh /dev/null 2>&1
[root@count ~]# cat /usr/local/sbin/logcron.sh

CODE:

#!/bin/bash
log_dir="/data/logs"
time=`date +%Y%m%d`  
/bin/mv  ${log_dir}/access_linuxtone.org.log ${log_dir}/access_count.linuxtone.org.$time.log
kill -USR1 `cat  /var/run/nginx.pid`

更多的日志分析与处理就关注(同时欢迎你参加讨论):http://bbs.linuxtone.org/forum-8-1.html

2.利用AWSTATS分析NGINX日志
  设置好Nginx日志格式,仍后利用awstats进行分析.
请参考: http://bbs.linuxtone.org/thread-56-1-1.html

3.        Nginx 如何不记录部分日志
日志太多,每天好几个G,少记录一些,下面的配置写到server{}段中就可以了
location ~ .*\.(js|jpg|JPG|jpeg|JPEG|css|bmp|gif|GIF)$
{
     access_log off;
}

十一、Nginx Cache服务配置

如果需要将文件缓存到本地,则需要增加如下几个子参数:

CODE:

proxy_store on;
proxy_store_access user:rw group:rw all:rw;
proxy_temp_path 缓存目录;

其中,
proxy_store on用来启用缓存到本地的功能,
proxy_temp_path用来指定缓存在哪个目录下,如:proxy_temp_path html;

在经过上一步配置之后,虽然文件被缓存到了本地磁盘上,但每次请求仍会向远端拉取文件,为了避免去远端拉取文件,必须修改

CODE:

proxy_pass:
if ( !-e $request_filename) {
    proxy_pass  http://mysvr;
}

即改成有条件地去执行proxy_pass,这个条件就是当请求的文件在本地的proxy_temp_path指定的目录下不存在时,再向后端拉取。

   
更多更高级的应用可以研究ncache,详细请参照http://bbs.linuxtone.org
里ncache相关的贴子.

十二、Nginx 负载均衡
1. Nginx 负载均衡基础知识
nginx的upstream目前支持4种方式的分配
1)、轮询(默认)
每个请求按时间顺序逐一分配到不同的后端服务器,如果后端服务器down掉,能自动剔除。
2)、weight
指定轮询几率,weight和访问比率成正比,用于后端服务器性能不均的情况。
2)、ip_hash
每个请求按访问ip的hash结果分配,这样每个访客固定访问一个后端服务器,可以解决session的问题。
3)、fair(第三方)
按后端服务器的响应时间来分配请求,响应时间短的优先分配。
4)、url_hash(第三方)

2.        Nginx 负载均衡实例1

CODE:

upstream bbs.linuxtone.org {#定义负载均衡设备的Ip及设备状态
    server 127.0.0.1:9090 down;
    server 127.0.0.1:8080 weight=2;
    server 127.0.0.1:6060;
    server 127.0.0.1:7070 backup;
}

在需要使用负载均衡的server中增加
proxy_pass http://bbs.linuxtone.org/
;

每个设备的状态设置为:
a)        down 表示单前的server暂时不参与负载
b)        weight 默认为1.weight越大,负载的权重就越大。
c)        max_fails :允许请求失败的次数默认为1.当超过最大次数时,返回proxy_next_upstream 模块定义的错误
d)        fail_timeout:max_fails次失败后,暂停的时间。
e)        backup: 其它所有的非backup机器down或者忙的时候,请求backup机器。所以这台机器压力会最轻。

nginx支持同时设置多组的负载均衡,用来给不用的server来使用。

client_body_in_file_only 设置为On 可以讲client post过来的数据记录到文件中用来做debug
client_body_temp_path 设置记录文件的目录 可以设置最多3层目录
location 对URL进行匹配.可以进行重定向或者进行新的代理 负载均衡

3.        Nginx 负载均衡实例 2
按访问url的hash结果来分配请求,使每个url定向到同一个后端服务器,后端服务器为缓存时比较有效,也可以用作提高Squid缓存命中率.

简单的负载均等实例:
#vi nginx.conf  //nginx主配置文件核心配置

CODE:

……….
#loadblance my.linuxtone.org
       upstream  my.linuxtone.org  {
       ip_hash;
       server   127.0.0.1:8080;
       server   192.168.169.136:8080;
       server   219.101.75.138:8080;
       server   192.168.169.117;
       server   192.168.169.118;
       server   192.168.169.119;
     }
…………..
include          vhosts/linuxtone_lb.conf;
………
# vi proxy.conf
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
client_max_body_size 50m;
client_body_buffer_size 256k;
proxy_connect_timeout 30;
proxy_send_timeout 30;
proxy_read_timeout 60;

proxy_buffer_size 4k;
proxy_buffers 4 32k;
proxy_busy_buffers_size 64k;
proxy_temp_file_write_size 64k;
proxy_next_upstream error timeout invalid_header http_500 http_503 http_404;
proxy_max_temp_file_size 128m;
proxy_store on;
proxy_store_access   user:rw  group:rw  all:r;
#nginx cache               
#client_body_temp_path  /data/nginx_cache/client_body 1 2;
proxy_temp_path /data/nginx_cache/proxy_temp 1 2;

#vi  linuxtone_lb.conf

CODE:

server
    {
        listen  80;
        server_name my.linuxtone.org;
        index index.php;
        root /data/www/wwwroot/mylinuxtone;
        if (-f $request_filename) {
            break;
           }
        if (-f $request_filename/index.php) {
          rewrite (.*) $1/index.php break;
        }

        error_page 403 http://my.linuxtone.org/member.php?m=user&a=login;
        location / {
           if ( !-e $request_filename) {
               proxy_pass http://my.linuxtone.org;
               break;
           }
           include /usr/local/nginx/conf/proxy.conf;
        }
}

十三、Nginx简单优化

1.        减小nginx编译后的文件大小 (Reduce file size of nginx)
默认的nginx编译选项里居然是用debug模式(-g)的(debug模式会插入很多跟踪和ASSERT之类),编译以后一个nginx有好几兆。去掉nginx的debug模式编译,编译以后只有几百K
在 auto/cc/gcc,最后几行有:
# debug

CODE:

CFLAGS=”$CFLAGS -g”
注释掉或删掉这几行,重新编译即可。

2.        修改Nginx的header伪装服务器
1)        修改nginx.h

CODE:

#vi nginx-0.7.30/src/core/nginx.h
#define NGINX_VERSION      "1.8"
#define NGINX_VER          "LTWS/" NGINX_VERSION

#define NGINX_VAR          "NGINX"
#define NGX_OLDPID_EXT     ".oldbin"

2) 修改nginx_http_header_filter_module
#vi nginx-0.7.30/src/http/ngx_http_header_filter_module.c
将如下

CODE:

static char ngx_http_server_string[] = "Server: nginx" CRLF;
修改为

CODE:

static char ngx_http_server_string[] = "Server: LTWS" CRLF;
a)        修改nginx_http_header_filter_module
#vi nginx-0.7.30/src/http/ngx_http_special_response.c
将如下:

CODE:

static u_char ngx_http_error_full_tail[] =
"<hr><center>" NGINX_VER "</center>" CRLF
"</body>" CRLF
"</html>" CRLF
;

CODE:

static u_char ngx_http_error_tail[] =
"<hr><center>nginx</center>" CRLF
"</body>" CRLF
"</html>" CRLF
;

修改为:

CODE:

static u_char ngx_http_error_full_tail[] =
"<center> "NGINX_VER" </center>" CRLF
"<hr><center>http://www.linuxtone.org</center>" CRLF
"</body>" CRLF
"</html>" CRLF
;

static u_char ngx_http_error_tail[] =
"<hr><center>LTWS</center>" CRLF
"</body>" CRLF
"</html>" CRLF
;

修改后重新编译一下环境,
404错误的时候显示效果图(如果没有指定错误页的话):
 


404.png

利用curl命令查看服务器header
 


curl.png

3.为特定的CPU指定CPU类型编译优化.
默认nginx使用的GCC编译参数是-O
需要更加优化可以使用以下两个参数
--with-cc-opt='-O3' \
--with-cpu-opt=opteron \
使得编译针对特定CPU以及增加GCC的优化.
此方法仅对性能有所改善并不会有很大的性能提升,供朋友们参考.
CPUD类型确定: # cat /proc/cpuinfo | grep "model name"
编译优化参数参考:http://en.gentoo-wiki.com/wiki/Safe_Cflags

4.Tcmalloc优化Nginx 性能

CODE:

# wget http://download.savannah.gnu.org/releases/libunwind/libunwind-0.99-alpha.tar.gz
# tar zxvf libunwind-0.99-alpha.tar.gz
# cd libunwind-0.99-alpha/
# CFLAGS=-fPIC ./configure
# make CFLAGS=-fPIC
# make CFLAGS=-fPIC install
# wget http://google-perftools.googlecode.com/files/google-perftools-0.98.tar.gz
# tar zxvf google-perftools-0.98.tar.gz
# cd google-perftools-0.98/
# ./configure
# make && make install
# echo "/usr/local/lib" > /etc/ld.so.conf.d/usr_local_lib.conf
# ldconfig
# lsof -n | grep tcmalloc

编译nginx 加载google_perftools_module:
./configure --with-google_perftools_module
在主配置文件加入nginx.conf 添加:
google_perftools_profiles /path/to/profile;

5.内核参数优化
# vi /etc/sysctl.conf   #在末尾增加以下内容:

CODE:

net.ipv4.tcp_fin_timeout = 30
net.ipv4.tcp_keepalive_time = 300
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 1
net.ipv4.ip_local_port_range = 5000 65000

#使配置立即生效
/sbin/sysctl -p

十四、如何构建高性的LEMP
请参见: http://www.linuxtone.org/lemp/lemp.pdf

1、提供完整的配置脚本下载:http://www.linuxtone.org/lemp/scripts.tar.gz

2、提供NGINX常见配置范例含(虚拟主机,防盗链,Rewrite,访问控制,负载均衡
Discuz相关程序静态化及等等),你只要稍稍修改即可线上应用。 3、将原版的xcache替换成EA,并提供相关简单调优脚本及配置文件。
更多的及更新资料请关注: http://www.linuxtone.org

十五、Nginx监控
1、        RRDTOOL+Perl脚本画图监控
先安装好rrdtool ,关于rrdtool本文不作介绍,具体安装请参照linuxtone监控版块.
#cd /usr/local/sbnin
#wget http://blog.kovyrin.net/files/mrtg/rrd_nginx.pl.txt

#mv rrd_nginx.pl.txt rrd_nginx.pl
#chmod a+x rrd_nginx.pl

#vi rrd_nginx.pl   //配置脚本文件设置好路径
#!/usr/bin/perl
use RRDs;
use LWP::UserAgent;

# define location of rrdtool databases
my $rrd = '/data/www/wwwroot/nginx/rrd';
# define location of images
my $img = '/data/www/wwwroot/nginx/html';
# define your nginx stats URL
my $URL = "http://219.232.244.13/nginx_status";
…………
【注】根据自己具体的状况修改相应的路径.
#crontab –e //加入如下
* * * * * /usr/local/sbin/rrd_nginx.pl
重启crond后,通过配置nginx虚拟主机指到/data/www/wwwroot/nginx/html目录,通过crond自动执行perl脚本会生成很多图片.
http://xxx/connections-day.png即可看到服务器状态图。

2、        官方Nginx-rrd 监控服务(多虚拟主机)(推荐)
网址:http://www.nginx.eu/nginx-rrd.html

此解决方案其实是基于上述监控方案的一个改进和增强,同样先安装好rrdtool这个画图工具和相应的perl模块再做如下操作:
# yum install perl-HTML*
先建立好生成的库存和图片存放录

CODE:

#mkdir -p /data/www/wwwroot/nginx/{rrd,html}

#cd /usr/local/sbin
#wget http://www.nginx.eu/nginx-rrd/nginx-rrd-0.1.4.tgz
#tar zxvf nginx-rrd-0.1.4.tgz
#cd nginx-rrd-0.1.4
#cd etc/
#cp nginx-rrd.conf /etc
#cd etc/cron.d
#cp nginx-rrd.cron /etc/cron.d

#cd /usr/local/src/nginx-rrd-0.1.4/html
# cp index.php /data/www/wwwroot/nginx/html/

#cd /usr/local/src/nginx-rrd-0.1.4/usr/sbin
#cp * /usr/sbin/

#vi /etc/nginx-rrd.conf

CODE:

#####################################################
#
# dir where rrd databases are stored
RRD_DIR="/data/www/wwwroot/nginx/rrd";
# dir where png images are presented
WWW_DIR="/data/www/wwwroot/nginx/html";
# process nice level
NICE_LEVEL="-19";
# bin dir
BIN_DIR="/usr/sbin";
# servers to test
# server_utl;server_name
SERVERS_URL="http://219.32.205.13/nginx_status;219.32.205.13  http://www.linuxtone.org/nginx_status;www.linuxtone.org""

//根据你的具体情况做调整.
SEVERS_URL 格式 http://domain1/nginx_status;domain1 http://domain2/nginx_status;domain2
这种格式监控多虚拟主机连接状态:
重点启crond服务,仍后通过http://219.32.205.13/nginx/html/
即可访问。配置过程很简单!

3、        CACTI模板监控Nginx
利用Nginx_status状态来画图实现CACTI监控
nginx编译时允许http_stub_status_module

# vi /usr/local/nginx/conf/nginx.conf

CODE:

location /nginx_status {
stub_status on;
access_log off;
allow 192.168.1.37;
deny all;
}

CODE:

# kill -HUP `cat /usr/local/nginx/logs/nginx.pid`

# wget http://forums.cacti.net/download.php?id=12676
# tar xvfz cacti-nginx.tar.gz
# cp cacti-nginx/get_nginx_socket_status.pl /data/cacti/scripts/
# cp cacti-nginx/get_nginx_clients_status.pl /data/cacti/scripts/
# chmod 755 /data/cacti/scripts/get_nginx*

检测插件

CODE:

# /data/cacti/scripts/get_nginx_clients_status.pl http://192.168.1.37/nginx_status
在cacti管理面板导入
cacti_graph_template_nginx_clients_stat.xml
cacti_graph_template_nginx_sockets_stat.xml

十六、常见问题与错误处理
1、400 bad request错误的原因和解决办法
配置nginx.conf相关设置如下.
client_header_buffer_size 16k;
large_client_header_buffers 4 64k;
根据具体情况调整,一般适当调整值就可以。

2、Nginx 502 Bad Gateway错误
proxy_next_upstream error timeout invalid_header http_500 http_503;
或者尝试设置:
large_client_header_buffers 4 32k;

3、Nginx出现的413 Request Entity Too Large错误
这个错误一般在上传文件的时候会出现,
编辑Nginx主配置文件Nginx.conf,找到http{}段,添加
client_max_body_size 10m; //设置多大根据自己的需求作调整.
如果运行php的话这个大小client_max_body_size要和php.ini中的如下值的最大值一致或者稍大,这样就不会因为提交数据大小不一致出现的错误。
post_max_size = 10M
upload_max_filesize = 2M

4、解决504 Gateway Time-out(nginx)
遇到这个问题是在升级discuz论坛的时候遇到的
一般看来, 这种情况可能是由于nginx默认的fastcgi进程响应的缓冲区太小造成的, 这将导致fastcgi进程被挂起, 如果你的fastcgi服务对这个挂起处理的不好, 那么最后就极有可能导致504 Gateway Time-out
现在的网站, 尤其某些论坛有大量的回复和很多内容的, 一个页面甚至有几百K。
默认的fastcgi进程响应的缓冲区是8K, 我们可以设置大点
在nginx.conf里, 加入: fastcgi_buffers 8 128k
这表示设置fastcgi缓冲区为8×128k
当然如果您在进行某一项即时的操作, 可能需要nginx的超时参数调大点,例如设置成60秒:send_timeout 60;
只是调整了这两个参数, 结果就是没有再显示那个超时, 可以说效果不错, 但是也可能是由于其他的原因, 目前关于nginx的资料不是很多, 很多事情都需要长期的经验累计才有结果, 期待您的发现哈!

5、如何使用Nginx Proxy
朋友一台服务器运行tomcat 为8080端口,IP:192.168.1.2:8080,另一台机器IP:192.168.1.8. 朋友想通过访问http://192.168.1.8
即可访问tomcat服务.配置如下:
在192.168.1.8的nginx.conf上配置如下:

CODE:

server {
listen 80;
server_name java.linuxtone.org
location / {
proxy_pass http://192.168.1.2:8080;
include /usr/local/nginx/conf/proxy.conf;
}
}

6、如何关闭Nginx的LOG
access_log /dev/null; error_log /dev/null;

十七、相关资源下载

1.nginx配置示例及脚本下载:
# wget http://www.linuxtone.org/lemp/scripts.tar.gz
#此脚本范例定期更新.
--------------------------------------------------------------------------------------------------------------

Nginx作为一个后起之秀,他的迷人之处已经让很多人都投入了他的怀抱。配置简单,实现原理简单。做一个负载平衡的再好不过了。

其原理:

简单介绍一下他的安装及配置过程

官方网站
http://wiki.codemongers.com/Main

一、依赖的程序

1. gzip module requires zlib library
2. rewrite module requires pcre library
3. ssl support requires openssl library

二、安装
./configure
make
make install

默认安装的路径是/usr/local/nginx

更多的安装配置
./configure --prefix=/usr/local/nginx
--with-openssl=/usr/include (启用ssl)
--with-pcre=/usr/include/pcre/ (启用正规表达式)
--with-http_stub_status_module (安装可以查看nginx状态的程序)
--with-http_memcached_module (启用memcache缓存)
--with-http_rewrite_module (启用支持url重写)

三、启动及重启
启动:nginx
重启:kill -HUP `cat /usr/local/nginx/logs/nginx.pid`
测试配置文件:nginx -t

简单吧,安装,启动都比较方便。

四、配置文件
http://wiki.codemongers.com/NginxFullExample

#运行用户

user  nobody nobody;

#启动进程

worker_processes  5;

#全局错误日志及PID文件


error_log  logs/error.log notice;

pid        logs/nginx.pid;

#工作模式及连接数上限

events {

  #工作模式有:select(标准模式),poll
(标准模式),
kqueue(高效模式,适用FreeBSD 4.1+, OpenBSD 2.9+, NetBSD 2.0 and MacOS X),

  #epoll(高效模式,本例用的。适用Linux 2.6+,SuSE 8.2,),/dev/poll(高效模式,适用Solaris 7 11/99+, HP/UX 11.22+ (eventport), IRIX 6.5.15+ 和 Tru64 UNIX 5.1A+)

  use epoll;

  worker_connections      1024;

}

#设定http服务器,利用它的反向代理功能提供负载均衡支持

http {

  #设定mime类型

  include      conf/mime.types;

  default_type  application/octet-stream;

  #设定日志格式

  log_format main        '$remote_addr - $remote_user [$time_local] '

                         '"$request" $status $bytes_sent '

                         '"$http_referer" "$http_user_agent" '

                         '"$gzip_ratio"';
  log_format download    '$remote_addr - $remote_user [$time_local] '

                         '"$request" $status $bytes_sent '

                         '"$http_referer" "$http_user_agent" '

                         '"$http_range" "$sent_http_content_range"';

  #设定请求缓冲

  client_header_buffer_size    10k;

  large_client_header_buffers  4 4k;

  

  #开启gzip模块,要求安装gzip 在运行./config时要指定

  gzip on;

  gzip_min_length  1100;

  gzip_buffers    4 8k;

  gzip_types      text/plain;

  output_buffers  1 32k;

  postpone_output  1460;

  

  #设定访问日志

  access_log  logs/access.log  main;

  client_header_timeout  3m;

  client_body_timeout    3m;

  send_timeout          3m;

  sendfile                on;

  tcp_nopush              on;

  tcp_nodelay            on;

  keepalive_timeout  65;

  

  #设定负载均衡的服务器列表

  upstream backserver {

  #weigth参数表示权值,权值越高被分配到的几率越大

  #本例是指在同一台服务器,多台服务器改变ip即可

  server 127.0.0.1:8081 weight=5;

  server 127.0.0.1:8082;

  server 127.0.0.1:8083;

  }
  #设定虚拟主机,默认为监听80端口,改成其他端口会出现问题

  server {

    listen         80;

    server_name    test.com

 www.test.com

;

    charset utf8;

    #设定本虚拟主机的访问日志

    access_log  logs/test.com.log  main;

    #如果访问 /images/*, /js/*, /css/* 资源,则直接取本地文件,不用转发。但如果文件较多效果不是太好。

    location ~ ^/(images|js|css)/  {

        root    /usr/local/testweb;

        expires 30m;

    }

    

    #对 "/" 启用负载均衡

    location / {

       proxy_pass      http://backserver

;

       proxy_redirect          off;

       proxy_set_header        Host $host;

       proxy_set_header        X-Real-IP $remote_addr;

       proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;

       client_max_body_size    10m;

       client_body_buffer_size 128k;

       proxy_connect_timeout  90;

       proxy_send_timeout      90;

       proxy_read_timeout      90;

       proxy_buffer_size      4k;

       proxy_buffers          4 32k;

       proxy_busy_buffers_size 64k;

       proxy_temp_file_write_size 64k;

    }

    #设定查看Nginx状态的地址,在运行./config 要指定,默认是不安装的。

    location /NginxStatus {

       stub_status            on;

       access_log              on;

       auth_basic              "NginxStatus";

       #是否要通过用户名和密码访问,测试时可以不加上。conf/htpasswd 文件的内容用 apache
 提供的 htpasswd 工具来产生即可       

       #auth_basic_user_file  conf/htpasswd;

    }

}

 

发表在 web server | 标签为 | 30条评论

MyISAM InnoDB 存储引擎的比较和事务的概念

MyISAM InnoDB 存储引擎的比较和事务的概念

一、两种引擎的应用方式和区别:
MyISAM:这个是默认类型,它是基于传统的ISAM类型,ISAM是Indexed Sequential Access Method (有索引的 顺序访问方法) 的缩写,它是存储记录和文件的标准方法.与其他存储引擎比较,MyISAM具有检查和修复表格的大多数工具. MyISAM表格可以被压缩,而且它们支持全文搜索.但它不是事务安全的,而且也不支持外键。如果事物回滚将造成不完全回滚,不具有原子性。如果执行大量的SELECT,MyISAM是更好的选择。

InnoDB:这种类型是事务安全的.它与BDB类型具有相同的特性,它还支持外键.InnoDB表格速度很快.具有比BDB还丰富的特性,因此如果需要一个事务安全的存储引擎,建议使用它.如果你的数据执行大量的INSERT或UPDATE,出于性能方面的考虑,应该使用InnoDB表,对于支持事务的InnoDB类型的表,影响速度的主要原因是AUTOCOMMIT默认设置是打开的,而且程序没有显式调用BEGIN 开始事务,导致每插入一条都自动Commit,严重影响了速度。可以在执行sql前调用begin,多条sql形成一个事物(即使autocommit打开也可以),将大大提高性能。

二、事务的概念:

  事务(Transaction)是访问并可能更新数据库中各种数据项的一个程序执行单元。事务通常由高级数据库操纵语言或编程语言书写的用户程序的执行所引起,并用形如begin transaction和end transaction语句来界定。事务由事务开始和事务结束之间执行的全体操作组成。

三、事务的四大特性:

(1) 原子性
  事务的原子性指的是,事务中包含的程序作为数据库的逻辑工作单位,它所做的对数据修改操作要么全部执行,要么完全不执行。这种特性称为原子性。 

(2) 一致性
    事务的一致性指的是在一个事务执行之前和执行之后数据库都必须处于一致性状态。这种特性称为事务的一致性。假如数据库的状态满足所有的完整性约束,就说该数据库是一致的。一致性和事务的原子性是密不可分的。

(3) 分离性
  分离性指并发的事务是相互隔离的。即一个事务内部的操作及正在操作的数据必须封锁起来,不被其它企图进行修改的事务看到。

(4)持久性
  持久性意味着当系统或介质发生故障时,确保已提交事务的更新不能丢失。即一旦一个事务提交,DBMS保证它对数据库中数据的改变应该是永久性的,耐得住任何系统故障。持久性通过数据库备份和恢复来保证。

四、为什么要使用事务:

我们用一个简单的例子来说明这个问题,比如我到银行去存钱,于是有这么几个步骤:
1、把钱交给工作人员;2、工作人员填单;3、将单子给我签字;4、工作人员确认并输入电脑。
  在这个事务进行的过程中,要是我把钱交给工作人员之后,进行到步骤3我签玩字,银行工作人员突发心脏病挂掉了,那,我的钱还没有被输入电脑,但我却交了钱又签字确认了,这时候我岂不是要亏死了?我肯定会要求银行回退这次事务,把钱还给我,要么由银行其他人员帮我输入电脑,完成本次事务。

    当然,这个是在有人工操作的时候进行的,我们可以要求银行做到交易的完整性。但是如果我们用自动存款机存钱的时候,机器在处理到我确认存款但还没有将记录真正提交到数据库的时候,突然因为断电或其他未知故障而突然停止,这时我该怎么办?

   于是,在数据库里产生了这么一个术语:事务(Transaction),也就是要么成功,要么失败,并恢复原状。

五、在MySQL中体验事务的过程:

1. 4.0以上mysqld都支持事务,包括非max版本。3.23的需要max版本mysqld才能支持事务。可以用show engines 来查看mysql支持的引擎:

mysql> show engines;
+------------+----------+----------------------------------------------------------------+--------------+-----+------------+
| Engine | Support | Comment | Transactions | XA | Savepoints |
+------------+----------+----------------------------------------------------------------+--------------+-----+------------+
| ndbcluster | DISABLED | Clustered, fault-tolerant tables | YES | NO | NO |
| MRG_MYISAM | YES | Collection of identical MyISAM tables | NO | NO | NO |
| BLACKHOLE | YES | /dev/null storage engine (anything you write to it disappears) | NO | NO | NO |
| CSV | YES | CSV storage engine | NO | NO | NO |
| MEMORY | YES | Hash based, stored in memory, useful for temporary tables | NO | NO | NO |
| FEDERATED | YES | Federated MySQL storage engine | YES | NO | NO |
| ARCHIVE | YES | Archive storage engine | NO | NO | NO |
| InnoDB | YES | Supports transactions, row-level locking, and foreign keys | YES | YES | YES |
| MyISAM | DEFAULT | Default engine as of MySQL 3.23 with great performance | NO | NO | NO |
+------------+----------+----------------------------------------------------------------+--------------+-----+------------+
 

2. 创建表时如果不指定type则默认为myisam,不支持事务。
存储引擎是针对表对象来说的,可以用 show create table tablename 命令看表的类型。

mysql> show create table aa;
+-------+-------------------------------------------------------------------------------------------+
| Table | Create Table |
+-------+-------------------------------------------------------------------------------------------+
| aa | CREATE TABLE `aa` (
  `a` varchar(10) DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1 |
+-------+-------------------------------------------------------------------------------------------+
 

3、 对不支持事务的表做start/commit操作没有任何效果,在执行commit前已经提交,但在支持事务的表里则必须 commit后才能生效:

use test;
drop table if exists aa;
create table aa (a varchar(10)) type=myisam;
drop table if exists bb;
create table bb (a varchar(10)) type=innodb;

insert into aa values('a');
insert into bb values('a');
select * from aa;
select * from bb;

验证:虽然还没有进行commit操作,但是这时两个表都能看到一条记录,根据事务的定义必须使用类似于 BEGAN END 的语句块来执行一个事务,如果程序没有显式调用BEGIN 开始事务,在Innodb中每插入一条记录都会自动Commit。

use test;
begin;
insert into aa values('aaaa');
insert into bb values('aaaa');
select * from aa;
select * from bb;

验证:在begin的语句块中执行事务操作,虽然还没有进行commit操作,但是aa表已经能看到增加了一条新的记录,但bb表还没有.

commit;

验证:在begin执行的当前窗口后执行commit命令后,再次查看bb表,新的记录也添加进去了。

 

 

注意:commit必须和begin对应起来,中间封装一个事务,如果关掉执行了begin但尚未执行commit的窗口,在其他窗口执行commit操作,这时数据库认为这个事务在执行的某个过程中失败,所以整个事务都会失败!

 

 

六、Mysql表引擎的切换:

1. 可以执行以下命令来切换非事务表到事务(数据不会丢失),innodb表比myisam表更安全:
   alter table tablename type=innodb;

2. innodb表不能用repair table命令和myisamchk -r table_name
但可以用check table,以及mysqlcheck [OPTIONS] database [tables]

3. 启动mysql数据库的命令行中添加了以下参数可以使新发布的mysql数据表都默认为使用事务(只影响到create语句。)
--default-table-type=InnoDB

4. 临时改变默认表类型可以用:
set table_type=InnoDB;
show variables like 'table_type';
show engines;

------------------------------------------------------------------------------------------------------------------------------------------------

Mysql的transaction实现 收藏
 transaction在数据库编程中是一个重要的概念,这样做可以控制对数据库操作的事务提交。
但是要想在程序中实现事务,要求数据库本身支持事务。
现在的关系型数据库,我们日常使用的mysql,oracle等等都支持事务,有的是安装后直接就支持,有的需要做一些设置。
这篇文章是针对mysql的,讲述从数据库安装,设置,一直到sql语句,甚至到java程序中,如何实现transaction。
1.安装
要想在mysql的表中支持transaction,必须要求是innodb表。普通表使用的autocommit模式,会自动提交每一条sql语句,不能算是transaction吧。
安装时要指定mysql支持innodb,./configure --with-innodb。

2.配置
安装后,可以对innodb做一些配置,在my.cnf或my.ini中的[mysqld]段。
#存储目录,如果不指定默认为安装的data目录,为空时以innodb_data_file_path指定路径为准
innodb_data_home_dir =
#数据文件名及大小,默认为ibdata1,10m大小。autoextend可以自增,max:2000M文件最大2g,因为有的硬盘有2g文件大小限制。
innodb_data_file_path = ibdata1:2000M;ibdata2:2000M:autoextend:max:2000M
# 设置缓冲池大小
set-variable = innodb_buffer_pool_size=70M
set-variable = innodb_additional_mem_pool_size=10M
#设置日志文件路径,默认在date目录下,名称为ib_logfile...
innodb_log_group_home_dir =
#设置日志文件数目,默认为3
set-variable = innodb_log_files_in_group=3
# 设置日志文件大小
set-variable = innodb_log_file_size=10M
# 设置日志缓冲大小
set-variable = innodb_log_buffer_size=8M
# 任何事务提交前写入日志,方便故障诊断,请设为1。如果丢失最近的几个事务影响不大的话,设置为0(默认值)。
innodb_flush_log_at_trx_commit=1
#设置超时时间
set-variable = innodb_lock_wait_timeout=50

注意:innodb不会自动生成目录,上面所有指定目录要手工生成。默认不用。

完整的配置参数如下表(下表引自http://man.chinaunix.net/database/mysql/inonodb_zh/2.htm#InnoDB_start
):

  innodb_data_home_dir
 这是InnoDB表的目录共用设置。如果没有在 my.cnf 进行设置,InnoDB 将使用MySQL的 datadir 目录为缺省目录。如果设定一个空字串,可以在 innodb_data_file_path 中设定绝对路径。
 
innodb_data_file_path
 单独指定数据文件的路径与大小。数据文件的完整路径由 innodb_data_home_dir 与这里所设定值的组合。 文件大小以 MB 单位指定。因此在文件大小指定后必有“M”。 InnoDB 也支持缩写“G”, 1G = 1024M。从 3.23.44 开始,在那些支持大文件的操作系统上可以设置数据文件大小大于 4 GB。而在另一些操作系统上数据文件必须小于 2 GB。数据文件大小总和至少要达到 10 MB。在 MySQL-3.23 中这个参数必须在 my.cnf 中明确指定。在 MySQL-4.0.2 以及更新版本中则不需如此,系统会默认在 MySQL 的 datadir 目录下创建一个 16 MB 自扩充(auto-extending)的数据文件 ibdata1。你同样可以使用一个 原生磁盘分区(RAW raw disk partitions(raw devices)) 作为数据文件, 如何在 my.cnf 中详细指定它们请查看第 12.1 节。
 
innodb_mirrored_log_groups
 为了保护数据而设置的日志文件组的拷贝数目,默认设置为 1。在 my.cnf 中以数字格式设置。
 
innodb_log_group_home_dir
 InnoDB 日志文件的路径。必须与 innodb_log_arch_dir 设置相同值。 如果没有明确指定将默认在 MySQL 的 datadir 目录下建立两个 5 MB 大小的 ib_logfile... 文件。
 
innodb_log_files_in_group
 日志组中的日志文件数目。InnoDB 以环型方式(circular fashion)写入文件。数值 3 被推荐使用。在 my.cnf 中以数字格式设置。
 
innodb_log_file_size
 日志组中的每个日志文件的大小(单位 MB)。如果 n 是日志组中日志文件的数目,那么理想的数值为 1M 至下面设置的缓冲池(buffer pool)大小的 1/n。较大的值,可以减少刷新缓冲池的次数,从而减少磁盘 I/O。但是大的日志文件意味着在崩溃时需要更长的时间来恢复数据。 日志文件总和必须小于 2 GB,3.23.55 和 4.0.9 以上为小于 4 GB。在 my.cnf 中以数字格式设置。
 
innodb_log_buffer_size
 InnoDB 将日志写入日志磁盘文件前的缓冲大小。理想值为 1M 至 8M。大的日志缓冲允许事务运行时不需要将日志保存入磁盘而只到事务被提交(commit)。 因此,如果有大的事务处理,设置大的日志缓冲可以减少磁盘I/O。 在 my.cnf 中以数字格式设置。
 
innodb_flush_log_at_trx_commit
 通常设置为 1,意味着在事务提交前日志已被写入磁盘, 事务可以运行更长以及服务崩溃后的修复能力。如果你愿意减弱这个安全,或你运行的是比较小的事务处理,可以将它设置为 0 ,以减少写日志文件的磁盘 I/O。这个选项默认设置为 0。
 
innodb_log_arch_dir
 The directory where fully written log files would be archived if we used log archiving. 这里设置的参数必须与 innodb_log_group_home_dir 相同。 从 4.0.6 开始,可以忽略这个参数。
 
innodb_log_archive
 这个值通常设为 0。 既然从备份中恢复(recovery)适合于 MySQL 使用它自己的 log files,因而通常不再需要 archive InnoDB log files。这个选项默认设置为 0。
 
innodb_buffer_pool_size
 InnoDB 用来高速缓冲数据和索引内存缓冲大小。 更大的设置可以使访问数据时减少磁盘 I/O。在一个专用的数据库服务器上可以将它设置为物理内存的 80 %。 不要将它设置太大,因为物理内存的使用竞争可能会影响操作系统的页面调用。在 my.cnf 中以数字格式设置。
 
innodb_additional_mem_pool_size
 InnoDB 用来存储数据字典(data dictionary)信息和其它内部数据结构(internal data structures)的存储器组合(memory pool)大小。理想的值为 2M,如果有更多的表你就需要在这里重新分配。如果 InnoDB 用尽这个池中的所有内存,它将从操作系统中分配内存,并将错误信息写入 MySQL 的错误日志中。在 my.cnf 中以数字格式设置。
 
innodb_file_io_threads
 InnoDB 中的文件 I/O 线程。 通常设置为 4,但是在 Windows 下可以设定一个更大的值以提高磁盘 I/O。在 my.cnf 中以数字格式设置。
 
innodb_lock_wait_timeout
 在回滚(rooled back)之前,InnoDB 事务将等待超时的时间(单位 秒)。InnoDB 会自动检查自身在锁定表与事务回滚时的事务死锁。如果使用 LOCK TABLES 命令,或在同一个事务中使用其它事务安全型表处理器(transaction safe table handlers than InnoDB),那么可能会发生一个 InnoDB 无法注意到的死锁。在这种情况下超时将用来解决这个问题。这个参数的默认值为 50 秒。在 my.cnf 中以数字格式设置。
 
innodb_flush_method
 这个参数仅仅与 Unix 相关。这个参数默认值为 fdatasync。 另一个设置项为 O_DSYNC。这仅仅影响日志文件的转储,在 Unix 下以 fsync 转储数据。InnoDB 版本从 3.23.40b 开始,在 Unix 下指定 fdatasync 为使用 fsync 方式、指定 O_DSYNC 为使用 O_SYNC 方式。由于这在某些 Unix 环境下还有些问题所以在 'data' versions 并没有被使用。
 
innodb_force_recovery
 警告:此参数只能在你希望从一个被损坏的数据库中转储(dump)数据的紧急情况下使用! 可能设置的值范围为 1 - 6。查看下面的章节 'Forcing recovery' 以了解这个参数的具体含义。参数设置大于 0 的值代表着 InnoDB 防止用户修改数据的安全度。从 3.23.44 开始,这个参数可用。在 my.cnf 中以数字格式设置。
 
innodb_fast_shutdown
 InnoDB 缺少在关闭之前清空插入缓冲。这个操作可能需要几分钟,在极端的情况下可以需要几个小时。如果这个参数据设置为 1 ,InnoDB 将跳过这个过程而直接关闭。从 3.23.44 和 4.0.1 开始,此参数可用。从 3.23.50 开始,此参数的默认值为 1。
 
innodb_thread_concurrency
 InnoDB 会试图将 InnoDB 服务的使用的操作系统进程小于或等于这里所设定的数值。此参数默认值为 8。如果计算机系统性能较低或 innodb_monitor 显示有很多线程等侍信号,应该将这个值设小一点。如果你的计算机系统有很我的处理器与磁盘系统,则可以将这个值设高一点以充分利用你的系统资源。建议设值为处理器数目+ 磁盘数目。 从 3.23.44 和 4.0.1 开始,此参数可用。在 my.cnf 中以数字格式设置。
 

innodb还需要使用二进制日志文件:

log-bin指定二进制文件名称,不指定默认生成。
log-bin-index 可以指定索引文件。
使用 binlog-do-db可以指定记录的数据库。
使用 binlog-ignore-db可以指定不记录的数据库。
注意的是: binlog-do-db 和binlog-ignore-db 一次只指定一个数据库,指定多个数据库需要多个语句。而且,MySQL会将所有的数据库名称改成小写, 在指定数据库时必须全部使用小写名字,否则不会起作用。

3.添加表
CREATE TABLE user (id INT NOT NULL AUTO_INCREMENT,PRIMARY KEY,fname VARCHAR(15),sname VARCHAR(20),sex VARCHAR(6),age VARCHAR(3)) TYPE=INNODB;
记得后面的TYPE=INNODB。

4.sql语句的transaction实现
两种方式:
如果SET AUTOCOMMIT=0;也就是关闭了自动提交,那么任何commit或rallback语句都可以触发事务提交。
比如:
 mysql> SET AUTOCOMMIT=0;
 Query OK, 0 rows affected (0.00 sec)
 
 mysql> INSERT INTO user(fname,sname) VALUES ('Max','Ma');
 Query OK, 1 row affected (0.00 sec)

 mysql> INSERT INTO user(fname,sname) VALUES ('Sky','Sun');
 Query OK, 1 row affected (0.00 sec)
 
 mysql> COMMIT;
 Query OK, 0 rows affected (0.00 sec)
这样事务就算提交了。
如果SET AUTOCOMMIT=1;也就是开启了自动提交(默认值),那么必须要以begin或者START TRANSACTION声明事务的开始,然后再以commit或rallback语句都可以触发事务提交。
比如:
 mysql> SET AUTOCOMMIT=1;
 Query OK, 0 rows affected (0.00 sec)

 mysql> BEGIN;
 Query OK, 0 rows affected (0.00 sec)
 
 mysql> INSERT INTO user(fname,sname) VALUES ('Max','Ma');
 Query OK, 1 row affected (0.00 sec)

 mysql> INSERT INTO user(fname,sname) VALUES ('Sky','Sun');
 Query OK, 1 row affected (0.00 sec)
 
 mysql> COMMIT;
 Query OK, 0 rows affected (0.00 sec)

像其他关系型数据库一样,也可以使用存储过程(procedure)来封装事务。

5.java程序开发中的实现。
涉及到程序开发实现方法就多了。
一.自己写方法把mysql的底层transaction命令封装。我感觉程序开发中应该尽量避免和底层数据库的过多交互,我没有实现它。
有人实现了,下面是他实现的一个例子网址:http://dlog.cn/html/diary/showlog.vm?sid=7&log_id=2516

二.java的jdbc开发包包含了操作transaction的方法,在java.sql.connection接口里。
使用他的好处是可以和多种类型数据库交互。
三.hibernate等ORM框架工具。
hibernate中也封状了对transaction的操作,在org.hibernate.Session类中,使用beginTransaction()方法开启transaction;使用getTransaction().commit()提交transaction;使用getTransaction().rollback()方法回滚transaciton。
四.这是我常用的一种方法,把hibernate的session的方法封装或者实现jdbc的connection的接口。

 

发表在 article | 标签为 , | MyISAM InnoDB 存储引擎的比较和事务的概念已关闭评论

dotnet 自定义应用程序配置文件

配置文件概述:

  应用程序配置文件是标准的 XML 文件,XML 标记和属性是区分大小写的。它是可以按需要更改的,开发人员可以使用配置文件来更改设置,而不必重编译应用程序。配置文件的根节点是configuration。我们经常访问的是appSettings,它是由.Net预定义配置节。我们经常使用的配置文件的架构是象下面的形式。先大概有个印象,通过后面的实例会有一个比较清楚的认识。下面的“配置节”可以理解为进行配置一个XML的节点。

  常见配置文件模式:
 

<
configuration
>


<
configSections
>
    
//
配置节声明区域,包含配置节和命名空间声明


                
<
section
>
              
//
配置节声明


             
<
sectionGroup
>
       
//
定义配置节组


                  
<
section
>
       
//
配置节组中的配置节声明


        
<
appSettings
>
 
//
预定义配置节


        
<
Custom element 
for
 configuration section
>
  
//
配置节设置区域

  只有appSettings节的配置文件及访问方法

  下面是一个最常见的应用程序配置文件的例子,只有appSettings节。
 

<?
xml version="1.0" encoding="utf-8"
?>


<
configuration
>


<
appSettings
>


<
add 
key
="connectionstring"
 value
="User ID=sa;Data Source=.;Password=;Initial Catalog=test;Provider=SQLOLEDB.1;"
 
/>


<
add 
key
="TemplatePATH"
 value
="Template"
 
/>


</
appSettings
>


</
configuration
>

  下面来看看这样的配置文件如何方法。

  string _connectionString=ConfigurationSettings.AppSettings["connectionstring"];

  使用ConfigurationSettings类的静态属性AppSettings就可以直接方法配置文件中的配置信息。这个属性的类型是NameValueCollection。
  自定义配置文件

  自定义配置节

  一个用户自定义的配置节,在配置文件中分为两部分:一是在<configSections></ configSections>配置节中声明配置节(上面配置文件模式中的“<section>”),另外是在<configSections></ configSections >之后设置配置节(上面配置文件模式中的“<Custom element for configuration section>”),有点类似一个变量先声明,后使用一样。声明一个配置文件的语句如下:
 

<
section 
name
=" "
 type
=" "
/>

  <section>:声明新配置节,即可创建新配置节。

  name:自定义配置节的名称。

  type:自定义配置节的类型,主要包括System.Configuration.SingleTagSectionHandler、   System.Configuration.DictionarySectionHandler、 System.Configuration.NameValueSectionHandler。

  不同的type不但设置配置节的方式不一样,最后访问配置文件的操作上也有差异。下面我们就举一个配置文件的例子,让它包含这三个不同的type。
 

<?
xml version="1.0" encoding="utf-8" 
?>


<
configuration
>


<
configSections
>


<
section 
name
="Test1"
 type
="System.Configuration.SingleTagSectionHandler"
/>


<
section 
name
="Test2"
 type
="System.Configuration.DictionarySectionHandler"
/>


<
section 
name
="Test3"
 type
="System.Configuration.NameValueSectionHandler"
 
/>


</
configSections
>


<
Test1 
setting1
="Hello"
 setting2
="World"
/>


<
Test2
>


<
add 
key
="Hello"
 value
="World"
 
/>


</
Test2
>


<
Test3
>


<
add 
key
="Hello"
 value
="World"
 
/>


</
Test3
>


</
configuration
>

  我们对上面的自定义配置节进行说明。在声明部分使用<section name="Test1" type="System.Configuration.SingleTagSectionHandler"/>声明了一个配置节它的名字叫Test1,类型为SingleTagSectionHandler。在设置配置节部分使用 <Test1 setting1="Hello" setting2="World"/>设置了一个配置节,它的第一个设置的值是Hello,第二个值是World,当然还可以有更多。其它的两个配置节和这个类似。

  下面我们看在程序中如何访问这些自定义的配置节。我们用过ConfigurationSettings类的静态方法GetConfig来获取自定义配置节的信息。

  public static object GetConfig(string sectionName);

  下面是访问这三个配置节的代码:
 

 
//
访问配置节Test1


            IDictionary IDTest1 
=
 (IDictionary)ConfigurationSettings.GetConfig(
"
Test1
"
);

string
 str 
=
 (
string
)IDTest1[
"
setting1
"
] 
+
"
 
"
+
(
string
)IDTest1[
"
setting2
"
];
MessageBox.Show(str);        
//
输出Hello World

//
访问配置节Test1的方法2


            
string
[] values1
=
new
 
string
[IDTest1.Count];
IDTest1.Values.CopyTo(values1,
0
);
MessageBox.Show(values1[
0
]
+
"
 
"
+
values1[
1
]);    
//
输出Hello World

//
访问配置节Test2


            IDictionary IDTest2 
=
 (IDictionary)ConfigurationSettings.GetConfig(
"
Test2
"
);

string
[] keys
=
new
 
string
[IDTest2.Keys.Count];

string
[] values
=
new
 
string
[IDTest2.Keys.Count];
IDTest2.Keys.CopyTo(keys,
0
);
IDTest2.Values.CopyTo(values,
0
);
MessageBox.Show(keys[
0
]
+
"
 
"
+
values[
0
]);

//
访问配置节Test3


            NameValueCollection nc
=
(NameValueCollection)ConfigurationSettings.GetConfig(
"
Test3
"
);
MessageBox.Show(nc.AllKeys[
0
].ToString()
+
"
 
"
+
nc[
"
Hello
"
]);    
//
输出Hello World

  自定义配置节组

  配置节组是使用<sectionGroup>元素,将类似的配置节分到同一个组中。配置节组声明部分将创建配置节的包含元素,在<configSections>元素中声明配置节组,并将属于该组的节置于<sectionGroup>元素中。下面是一个包含配置节组的配置文件的例子:
 

<?
xml version="1.0" encoding="utf-8" 
?>


<
configuration
>


<
configSections
>


<
sectionGroup 
name
="TestGroup"
>


<
section 
name
="Test"
 type
="System.Configuration.NameValueSectionHandler"
/>


</
sectionGroup
>


</
configSections
>


<
TestGroup
>


<
Test
>


<
add 
key
="Hello"
 value
="World"
/>


</
Test
>


</
TestGroup
>


</
configuration
>

 

发表在 article | 标签为 | dotnet 自定义应用程序配置文件已关闭评论

Mysql的distinct语句和group by,order by

最近,在做一个项目的时候,发现得出的数据于预料的相差很多,仔细的研究了一下,发现问题出在 distinct语句和groupy by,order by
首先,distinct语句,获得非重复的(唯一)行记.
grouy by是分组,order by 是排序。

直接看我的例子。

假定我有一个表f_job,有字段:

select job_id, com_id,job_time from f_job order by job_time desc limit 10; T e:webwebphpfhrtee.txt
+--------+--------+---------------------+
| job_id | com_id | job_time |
+--------+--------+---------------------+
| 5060 | 2205 | 2006-09-29 16:30:11 |
| 4707 | 19084 | 2006-09-29 16:27:55 |
| 4708 | 19084 | 2006-09-29 16:27:55 |
| 4709 | 19084 | 2006-09-29 16:27:55 |
| 4710 | 19084 | 2006-09-29 16:27:55 |
| 4711 | 19084 | 2006-09-29 16:27:55 |
| 4859 | 19084 | 2006-09-29 16:27:55 |
| 4918 | 19084 | 2006-09-29 16:27:55 |
| 5059 | 2205 | 2006-09-29 16:27:22 |
| 4078 | 2715 | 2006-09-29 16:18:36 |
+--------+--------+---------------------+
10 rows in set (0.03 sec)

还有其他字段,不可能影响结果.此处不列出。

job_id是primary key。 com_id是外键,我需要按照时间来排序。所以必须使用order by!

你看到了com_id在得出的结果中不唯一,对,我需要的结果就是提取com_id唯一的最近10条com_id的记录,而已。

我就以以前的MSSQL的经验写如下的语句执行:
mysql> select distinct( com_id) from f_job order by job_time desc limit 10; T e:webwebphpfhrtee.txt
+--------+
| com_id |
+--------+
| 19084 |
| 2197 |
| 19917 |
| 19580 |
| 19520 |
| 19664 |
| 19397 |
| 19900 |
| 1176 |
| 19449 |
+--------+

对,这次得出的结果是唯一,但是,这不对呀,很明显,把com_id记录号码是2005的记录给没了,这结果肯定错。马上分析一下所有的的结果,发现忽略的com_id并没有消失,只是被排到后面去了。

我按照时间排序,应该出现的结果第一个就是2205呀,为什么能排到后面?从两条可疑记录看出了结果:

原记录:

| 5058 | 19580 | 2006-09-29 15:23:58 |
| 5057 | 19917 | 2006-09-29 15:14:16 |
| 4973 | 19580 | 2006-09-29 15:13:49 |
| 5011 | 19580 | 2006-09-29 15:13:49 |

distinct后的次序:

| 19917 |
| 19580 |

这说明,对于不是在一起的隔行记录,如果恰巧隔一行,还可以被order by 比较出来,否则比较出来的不再真实,这是因为,被缓存的上次的order by 的临时值在客观上不再有用!还有一种情况,如果记录连着,也可以被比较出来。

先是明白了MySql的弱智了。

马上又执行了一下操作,结果如下:
mysql> select distinct(com_id),job_time from f_job order by job_time desc limit 10;
+--------+---------------------+
| com_id | job_time |
+--------+---------------------+
| 2205 | 2006-09-29 16:30:11 |
| 19084 | 2006-09-29 16:27:55 |
| 2205 | 2006-09-29 16:27:22 |
| 2715 | 2006-09-29 16:18:36 |
| 2197 | 2006-09-29 16:03:16 |
| 19580 | 2006-09-29 15:23:58 |
| 19917 | 2006-09-29 15:14:16 |
| 19580 | 2006-09-29 15:13:49 |
| 19520 | 2006-09-29 10:29:41 |
| 19900 | 2006-09-29 10:16:48 |
+--------+---------------------+
10 rows in set (0.10 sec)

先和这个比较一下:
mysql> select com_id,job_time from f_job order by job_time desc limit 10; T e:webwebphpfhrtee.txt
+--------+---------------------+
| com_id | job_time |
+--------+---------------------+
| 2205 | 2006-09-29 16:30:11 |
| 19084 | 2006-09-29 16:27:55 |
| 19084 | 2006-09-29 16:27:55 |
| 19084 | 2006-09-29 16:27:55 |
| 19084 | 2006-09-29 16:27:55 |
| 19084 | 2006-09-29 16:27:55 |
| 19084 | 2006-09-29 16:27:55 |
| 19084 | 2006-09-29 16:27:55 |
| 2205 | 2006-09-29 16:27:22 |
| 2715 | 2006-09-29 16:18:36 |
+--------+---------------------+
10 rows in set (0.06 sec)

发现,distinct恰巧和前面第一次测试的结果相反处理我们的信息,他把隔行的看作不同的结果输出,这就导致了2205的记录又出现了。

然后我就用了group by语句,应该可以吧,结果也让我。。。:
mysql> select com_id from f_job group by com_id order by job_time desc limit 10;
+--------+
| com_id |
+--------+
| 19084 |
| 2197 |
| 19917 |
| 19580 |
| 19520 |
| 19664 |
| 19397 |
| 19900 |
| 1176 |
| 19449 |
+--------+
10 rows in set (0.03 sec)

这和distinct的结果一样,这倒不出乎意料,然后我又加入分组job_time,看看:
mysql> select com_id from f_job group by com_id,job_time order by job_time desc limit 10;
+--------+
| com_id |
+--------+
| 2205 |
| 19084 |
| 2205 |
| 2715 |
| 2197 |
| 19580 |
| 19917 |
| 19580 |
| 19520 |
| 19900 |
+--------+
10 rows in set (0.03 sec)

这结果差一点点了,然后我再distinct(com_id)一下,应该可以了吧!看看:
mysql> select distinct(com_id) from f_job group by com_id,job_time order by job_time desc limit 10; T e:webwebphpfhrtee.txt
+--------+
| com_id |
+--------+
| 19084 |
| 2197 |
| 19917 |
| 19580 |
| 19520 |
| 19664 |
| 19397 |
| 19900 |
| 1176 |
| 19449 |
+--------+
10 rows in set (0.04 sec)

汗,这和没加group by 的一点区别都没,怎么这样弱呀!没办法,这怎么办,犹豫中。。。

上面的问题彻底的说明了这样一个事实:distinct只能返回它的目标字段,而无法返回其它字段。。。

汗,这和没加group by 的一点区别都没,怎么这样弱呀!没办法,这怎么办,犹豫中。。。

------------------------------------
想起了一个很能骚的一个人--phzzy,这jh,qq说了句话,果然在,他玩php,马上求助,经过1个多小时的艰苦YY,终于这鸟人先大爷我一步给出语句:
select (`com_id`),max(`job_time`) from `f_job` GROUP BY `com_id` order by max(`job_time`) limit 10;

mdgb,终于明白了,刚拿到这语句就明白了。

我tmd知道这是2次排序,md,group by是一次,然后无论怎么样,都不可能2次排序,因为第二次必须借助内部的集聚函数。。。。。。我怎么没想到max,气死我了

发表在 article | 标签为 | Mysql的distinct语句和group by,order by已关闭评论

squid之重定向redirect使用/启用Linux的内存盘/squid重启建目

在squid.conf中添加
url_rewrite_program /etc/squid/redirect.pl ##重定向perl程序
redirect_rewrites_host_header off ##禁止squid做host更新
redirect_children 20 ##启用20个进程

再看这个redirect.pl
#!/usr/bin/perl
$|=1;
        while (<>) {
                @X = split;
                $url = $X[0];
                if ($url =~ /^http:\/\/www\.baidu\.com/) {
                        print “302:https:\/\/www\.google\.com\n”;
                }
                elsif ($url =~ /^http:\/\/opvps\.com/) {
                        print “302:http:\/\/www\.opvps\.com\n”;
                }
                elsif ($url =~ /^http:\/\/gmail\.com/) {
                        print “302:https:\/\/gmail\.google\.com\n”;
                }
                else {
                        print “$url\n”;
                }
            }

程序实现如下功能:
将http://www.baidu.com
转向http://www.google.com

http://opvps.com
及www变成http://www.opvps.com

http://gmail.com
变成https://gmail.google.com

重定向acl匹配:

正常情况下,squid将每个请求发送往重定向器。可以使用redirector_access规则来有选择的发送某些请求。语法与http_access相同:
redirector_access allow|deny [!]ACLname
例如:
acl opvps src 192.168.1.5
acl All src 0/0

redirector_access deny opvps
redirector_access allow All
在该情形里,对匹配opvps(来源于192.168.0.5的主机)的请求,Squid跳过重定向器。

 

 

-----------------------------------------------------------------------

启用Linux的内存盘

大家都知道,读写放在内存里面文件的速度远远快于放在硬盘,放在内存中基本上不存在IO读写等待的问题。当然放在内存上,机器一当重启,内存中的文件就会丢失了,适合于存放一些Cache 如squid的 cache目录

大多数的Linux发行版本中,内存盘默认使用的是/dev/shm 大小一般等同于内存的大小

当然我也可以重设这个盘的大小,使用
mount -o remount,size=3G /dev/shm
这样/dev/shm就成为3G了

其实我们可以直接把文件放到/dev/shm下使用的

下面,我将squid 的cache 目录挂在内存盘中使用
mount -t tmpfs -o size=3G,mode=0755 tmpfs /usr/local/squid/var/cache
在/etc/fstab中加入一行,实行每次启动自动挂载内存盘
tmpfs /usr/local/squid/var/cache  tmpfs size=3G,mode=0755 0 0
当然,squid的cache的权限需要更改,每次启动需要更改目录权限及重建cache目录,需要执行如下指令
chown -R nobody:nobody /usr/local/squid/var/cache #这里的squid是使用nobody用户的
/usr/local/squid/sbin/squid -z ##重建cache

这样linux的内存盘就用启来了

-----------------------------------------------------

新到服务器Dell1900,2G内存,正好用来做squid代理服务器,用内存盘做ramdisk,以减少I/O瓶颈,FreeBSD 6.2的OS,先vi /etc/fstab,加入'md /ramdisk mfs rw,noatime,-s512M 0 0',但是这样的话,每次机器重启由于没有cache目录结构了,导致squid没法启动,需要手动squid -z,于是修改/usr/local/etc/rc.d/squid启动脚本如下,注意蓝色部分是添加的自动创建cache目录。

squid_checkrunning() {
        ${command} ${squid_flags} -k check 2>/dev/null
}

squid_stop() {
        echo "Stopping ${name}."
        ${command} ${squid_flags} -k shutdown
        run_rc_command poll
}

. /etc/rc.subr

name=squid
rcvar=${name}_enable

command=/usr/local/sbin/squid
extra_commands=reload
reload_cmd="${command} ${squid_flags} -k reconfigure"
stop_precmd="squid_checkrunning"
stop_cmd="squid_stop"

load_rc_config ${name}

squid_chdir=${squid_chdir:-/usr/local/squid/logs}
squid_enable=${squid_enable:-"NO"}
squid_flags=${squid_flags-"-D"}
squid_user=${squid_user:-squid}
default_config=/usr/local/etc/squid/squid.conf

required_dirs=${squid_chdir}

# squid(8) will not start if ${default_config} is not present so try
# to catch that beforehand via ${required_files} rather than make
# squid(8) crash.
# If you remove the default configuration file make sure to add
# '-f /path/to/your/squid.conf' to squid_flags

if [ -z "${squid_flags}" ]; then
        required_files=${default_config}
fi

cache_dir="/ramdisk/00"
if [ ! -d $cache_dir ]; then
  /usr/local/sbin/squid -z
fi

run_rc_command "$1"
发表在 article | 标签为 | squid之重定向redirect使用/启用Linux的内存盘/squid重启建目已关闭评论

isp.apnic.list/获取电信,网通,铁通APNIC权威IP数据集

从apnic获得ISP IP列表的脚本

朋友让我看一个abel在CU上提出从apnic获得ISP IP列表的脚本
。仔细看了一遍发现abel的方法很复杂很强大,好些命令看不明白,所以想自己按照abel的思想写一个更简单易懂shell脚本。正好前些天学习了sed,练习机会来了,花了两个多小时完成这个任务。脚本如下:

#!/bin/sh
FILE=ip_apnic
ISPIP=ISP.ip
rm -f $FILE
rm -f *.ip
wget http://ftp.apnic.net/apnic/stats/apnic/delegated-apnic-latest
-O $FILE
#remove all useless lines
sed -i -e '/\(apnic\)|\(CN\)|\(ipv4\)|\(.\+\)|\(.\+\)|\(.\+\)|\(.\+\)/!d;' $FILE
#get ip networks and the number of hosts in this network from field 4 and field 5
sed -i -e 's/\(apnic\)|\(CN\)|\(ipv4\)|\(.\+\)|\(.\+\)|\(.\+\)|\(.\+\)/\4 \5/g;' $FILE

while read ip cnt
do
        pow=32
        #cnt2=$cnt
        #get netmask from the number of hosts in the network
        while [ $cnt -gt 1 ]; do
                pow=$(($pow-1))
                cnt=$(($cnt/2))
        done
        #get whois information, remove all useless lines, extract the netname from the rest line.
        NETNAME=`whois -h whois.apnic.net $ip | sed -e '/netname/!d;/netname/q' | sed -e 's/netname:[[:blank:]]\+\(.*\)/\1/g'`
        echo $ip/$pow $NETNAME >> $ISPIP
        echo $ip/$pow $NETNAME
done < $FILE

#all networks are categorized by their netname like these "sed ..."
sed -n -e '/CNC\|wangtong/p' $ISPIP >> CNCGROUP.ip
sed -n -e '/CHINANET\|CHINATELECOM/p' $ISPIP >> CHINANET.ip
done

abel写好几个case语句匹配NETNAME,由于ISP的NETNAME实在太多,我就不写case了,直接在最后的sed命令中硬编码NETNAME。如果要做的更好,可以先定义一个ISP NETNAME的数组,然后做一个for循环。与abel的脚本相比,上面的脚本使用的命令更少,程序结构更简单,基本上是靠sed命令解决问题。如果说abel使用乘法计算2*5=10的话,那我就是用比较笨的加法计算2*5=5 + 5 = 10。

 

---------------------------------------------------------------------------------------------------------------------------------------------

程序代码

<?php
         $iplist=file_get_contents("ftp://ftp.apnic.net/apnic/stats/apnic/delegated-apnic-latest
");
         $ipsplit=split("[\n]+",$iplist);
         foreach($ipsplit as $val){
                 if(preg_match("/apnic\|CN\|ipv4\|/",$val)){
                 list($nic,$CN,$ver,$ip,$mask)=preg_split("/\|/",$val);
                 $iparray['set'][]=array("ip"=>$ip,"mask"=>$mask);
                 }
         }
        
         foreach($iparray['set'] as $val){
                 echo $val['ip']."\t".(32-log($val['mask'],2))."\n";
                 $flag=GetWhois($val['ip']);
                 switch($flag){
                         case "CHINANET":
                                 //$fpc=fopen("CHINANET.lst","a+");
                                 //fwrite($fpc,$val['ip']."/".(32-log($val['mask'],2))."\n");
                                 $CHINANET[]=$val['ip']."/".(32-log($val['mask'],2));
                                 break;
                         case "CNC":
                                 //$fpc=fopen("CNC.lst","a+");
                                 //fwrite($fpc,$val['ip']."/".(32-log($val['mask'],2))."\n");
                                 $CNC[]=$val['ip']."/".(32-log($val['mask'],2));
                                 break;
                                 break;
                         case "CRTC":
                                 //$fpc=fopen("CRTC.lst","a+");
                                 //fwrite($fpc,$val['ip']."/".(32-log($val['mask'],2))."\n");
                                 $CRTC[]=$val['ip']."/".(32-log($val['mask'],2));
                                 break;

                         default:        
                                 //$fpc=fopen("OTHERS.lst","a+");
                                 //fwrite($fpc,$val['ip']."/".(32-log($val['mask'],2))."\n");
                                 $OTHERS[]=$val['ip']."/".(32-log($val['mask'],2));
                                 break;
                 }
                
                
                        
         }
        
         //去掉重复数据
                 $CHINANET = array_unique($CHINANET);
                 $CNC = array_unique($CNC);
                 $CRTC = array_unique($CRTC);
                 $OTHERS = array_unique($OTHERS);
                
                 //生成IP集表 CHINANET
                 $MaxCount = count($CHINANET);
                 $fpc_lst=fopen("CHINANET.lst","a+");
                 $fpc_routes_rsc=fopen("CHINANET_routes.rsc","a+");
                 $fpc_address_rsc=fopen("CHINANET_address.rsc","a+");
                 fwrite($fpc_routes_rsc,"/ ip route rule\n");
                 fwrite($fpc_address_rsc,"/ ip firewall address-list\n");
                
                 for ($iCount=0;$iCount< $MaxCount;$iCount++){
                         fwrite($fpc_lst,$CHINANET[$iCount]."\n");
                         fwrite($fpc_routes_rsc,"add   dst-address=".$CHINANET[$iCount]." action=lookup table=Route_ChinaNet disabled=no\n");
                         fwrite($fpc_address_rsc,"add list=ChinaNet address=".$CHINANET[$iCount]." disabled=no\n");
                 }
                
                 fclose($fpc_address_rsc);
                 fclose($fpc_routes_rsc);
                 fclose($fpc_lst);
                
                 //生成IP集表 CNC
                 $MaxCount = count($CNC);
                 $fpc_lst=fopen("CNC.lst","a+");
                 $fpc_routes_rsc=fopen("CNC_routes.rsc","a+");
                 $fpc_address_rsc=fopen("CNC_address.rsc","a+");
                 fwrite($fpc_routes_rsc,"/ ip route rule\n");
                 fwrite($fpc_address_rsc,"/ ip firewall address-list\n");
                
                 for ($iCount=0;$iCount< $MaxCount;$iCount++){
                         fwrite($fpc_lst,$CNC[$iCount]."\n");
                         fwrite($fpc_routes_rsc,"add   dst-address=".$CNC[$iCount]." action=lookup table=Route_CNC disabled=no\n");
                         fwrite($fpc_address_rsc,"add list=CNC address=".$CNC[$iCount]." disabled=no\n");
                 }
                
                 fclose($fpc_address_rsc);
                 fclose($fpc_routes_rsc);
                 fclose($fpc_lst);
                
                
                 //生成IP集表 CRTC
                 $MaxCount = count($CRTC);
                 $fpc_lst=fopen("CRTC.lst","a+");
                 $fpc_routes_rsc=fopen("CRTC_routes.rsc","a+");
                 $fpc_address_rsc=fopen("CRTC_address.rsc","a+");
                 fwrite($fpc_routes_rsc,"/ ip route rule\n");
                 fwrite($fpc_address_rsc,"/ ip firewall address-list\n");
                
                 for ($iCount=0;$iCount< $MaxCount;$iCount++){
                         fwrite($fpc_lst,$CRTC[$iCount]."\n");
                         fwrite($fpc_routes_rsc,"add   dst-address=".$CRTC[$iCount]." action=lookup table=Route_CRTC disabled=no\n");
                         fwrite($fpc_address_rsc,"add list=CRTC address=".$CRTC[$iCount]." disabled=no\n");
                 }
                
                 fclose($fpc_address_rsc);
                 fclose($fpc_routes_rsc);
                 fclose($fpc_lst);
                
                 //生成IP集表 OTHERS
                 $MaxCount = count($OTHERS);
                 $fpc_lst=fopen("OTHERS.lst","a+");
                 $fpc_routes_rsc=fopen("OTHERS_routes.rsc","a+");
                 $fpc_address_rsc=fopen("OTHERS_address.rsc","a+");
                 fwrite($fpc_routes_rsc,"/ ip route rule\n");
                 fwrite($fpc_address_rsc,"/ ip firewall address-list\n");
                
                 for ($iCount=0;$iCount< $MaxCount;$iCount++){
                         fwrite($fpc_lst,$OTHERS[$iCount]."\n");
                         fwrite($fpc_routes_rsc,"add   dst-address=".$OTHERS[$iCount]." action=lookup table=Route_OTHERS disabled=no\n");
                         fwrite($fpc_address_rsc,"add list=ChinaNet address=".$OTHERS[$iCount]." disabled=no\n");
                 }
                
                 fclose($fpc_address_rsc);
                 fclose($fpc_routes_rsc);
                 fclose($fpc_lst);        

         function GetWhois($IP){
                 $rootwhois = 'whois.apnic.net';
                 $buffer1 = ReadSocket($rootwhois,$IP);
                 $flag="";
                 $buffer_result=nl2br($buffer1);
                 if(preg_match("/CHINANET/",$buffer_result)){
                         $flag="CHINANET";
                         return $flag;
                 }
                 if(preg_match("/CNC/",$buffer_result)){
                         $flag="CNC";
                         return $flag;
                 }
                 if(preg_match("/CRTC/",$buffer_result)){
                         $flag="CRTC";
                         return $flag;
                 }
                 if(!preg_match("/CHINANET|CRTC|CNC/",$buffer_result)){
                         $flag="OTHERS";
                         return $flag;
                 }
         }
function ReadSocket($whois,$ip)
{
   $buffer = '';
   if (!$sock = fsockopen( $whois, 43, $errNum, $errStr, 20))
{
         echo "Sorry,Can't fsockopen it";
   }
   else
   {
     fputs($sock,"$ip\n");
     //$buffer = fread($sock, 8192);
     while(!feof($sock))         $buffer.=fgets($sock, 8192);
     fclose($sock);
   }
   return $buffer;
}
?>

 
--------------------------------------------------------------------------------
附:获取IP地址范围方法:
1、 利用shell程序获取IP地址段

#!/bin/sh

FILE=/root/study/apnic/ip_apnic

rm -f $FILE

wget http://ftp.apnic.net/apnic/stats/apnic/delegated-apnic-latest
-O $FILE

grep 'apnic|CN|ipv4|' $FILE | cut -f 4,5 -d'|'|sed -e 's/|/ /g' | while read ip cnt

do

echo $ip:$cnt

mask=$(cat << EOF | bc | tail -1

pow=32;

define log2(x) {

if (x<=1) return (pow);

pow--;

return(log2(x/2));

}

log2($cnt)

EOF)

echo $ip/$mask>> cn.net

NETNAME=`whois $ip@whois.apnic.net | sed -e '/./{H;$!d;}' -e 'x;/netnum/!d' |grep ^netname | sed -e 's/.*: \(.*\)/\1/g' | sed -e 's/-.*//g'`

case $NETNAME in

CNC)

echo $ip/$mask >> CNCGROUP

;;

CHINANET|CNCGROUP)

echo $ip/$mask >> $NETNAME

;;

CHINANET|CNCGROUP)

echo $ip/$mask >> $NETNAME

;;

CHINATELECOM)

echo $ip/$mask >> CHINANET

;;

*)

echo $ip/$mask >> OTHER

;;

esac

done
 

发表在 article | 标签为 | isp.apnic.list/获取电信,网通,铁通APNIC权威IP数据集已关闭评论