[转载]中兴 X876使用技巧

mikel阅读(1116)

X876使用小技巧

第一次使用安卓系统,于是把自己遇到并解决的问题一个个写出来,都是在876上实践的。

1.关于WIFI黑屏就掉线的解决方法:
进入“设置”-“无线和网络”-“WLAN设置”-“MENU(手机的菜单键)”-“高级”-“WLAN休眠策略”-“永不休眠”-“MENU”-“保存”
有很多人说无效,我也不知道什么原因,之前我是没问题的,后来也不行了~郁闷。

2.蓝牙接发问题:
发送:点住文件不放,弹出对话框,选蓝牙发送。
接收:收到消息时,(注意看手机最左上角的消息通知),然后试试按住信息栏不放往下托拉。(信息栏就是最上面那栏,有信号阿,电池阿这里)

3.图标的显示、隐藏和换屏:
显示:有的人安装了软件后,软件不会在主屏上显示,而是直接到了程序库里(自己暂时这么叫了),调出来的方法是进入程序库,找到对应软件的图标点住不放直到可移动后直接移到主屏上。
隐藏:在主屏上对着一个图标点住不放直到可移动后(有震动提示),把它扔到垃圾桶。
换屏:在主屏上对着一个图标点住不放直到可移动后,移到主屏的边缘不动,手机自动会换屏。
程序库进入方法:激活屏幕后,MENU对上的那个点阵块。

4.SD装载与卸载:
手机用数据线连上电脑后,手机方会有提示“装载”或“不装载”,装载就是连上SD卡,这时可以拷贝文件到手机SD卡上。
如果要断开SD与电脑的连接,直接按住信息栏不放往下托拉,点击“关闭USB存储设备”,再点“关”闭即可。
如果一开始点了不装载,之后又想装载。直接按住信息栏不放往下托拉,点击“USB已连接”再点“装载”即可
注:以上“4”的操作从没断开过数据线。(这个好阿,手机屁屁可以多插几年了)

5.输入法的切换:
很多人不喜欢手机自带的输入法,包括我也一样。所以会自行装其他输入法(我最喜欢百度输入法),那么要如何在输入的时候换你安装好的输入法呢?
方法:点住输入框不放。然后看到了吧?

6.google搜索框的调用:
其实手机带的google搜索很有用,很方便。输入一个字符,手机有关的就会显示出来,无关的也可以调出来。不过它占在主屏上很不好看也浪费位置。我们可以用上面“3”的方法把它去掉。那么要如何调出来呢?
激活屏幕,然后对着“MENU”长按不放,看到什么了没?一个键盘! 直接输入就行了。比如输入“U” 那么”UC浏览器”是不是就可以用了呢?  再比如输入“www.baidu.com”是不是直接就可以用UC上网呢?(前提是UC是默认浏览器)就不用特意去开UC,开了UC后又输入了。方便吧?

7.后台程序的查看:
方法跟“6”一样,不过是对着房子键长按。(MENU左边那个)。不过好像不能结束程序。
下面8楼的兄弟”cby200311″指的就是这个。加分鼓励。

8.长按屏幕左下或右下的“…”键,能够调出五屏来。

9.壁纸不能全屏?
解决方法:别用240X400的壁纸(虽然像素是这个),876的壁纸是跟着屏幕动的,876有五个分屏!经过试验。  请使用像素为:600X500的壁纸。 我已经验证可行,相当不错。

10.铃音、信息音和闹钟音的自由调用:
铃音:找到声音文件,长按不放,弹出的对话框中选“用作铃声
信息音:进入信息-“MENU”-“设置”-“选择铃声” 确定即可。
闹钟音:进入“闹钟时钟”–“MENU”-“添加闹钟”(或对着已有闹钟长按-编辑)-“铃声” 选择确定即可。

自由铃音:
在SD卡的根目录下增加media文件夹,在该文件夹里创建audio文件夹,在该文件夹里在创建以下文件夹:
ringtones      文件夹—来电铃声,里面放入自选的铃声音乐
notifications  文件夹—短信铃声,里面放入自选的铃声音乐;
alarms          文件夹—闹铃铃声,里面放入自选的铃声音乐;
设置好后,用上面的方法进行调用。

[转载]深入理解Android消息处理系统——Looper、Handler、Thread

mikel阅读(854)

[转载]深入理解Android消息处理系统——Looper、Handler、Thread – Greenwood – 博客园.

(自) Activity,Service属于主线程,在主线程中才能更新UI,如toast等。其他线程中不能直接使用,这时可以使用Handler来处理,Handler可以在Activity和Service中。

熟悉Windows编程的朋友可能知道Windows程序是消息驱动的,并且有全局的消息循环系统。而Android应用程序也是消息驱动的,按道 理来说也应该提供消息循环机制。实际上谷歌参考了Windows的消息循环机制,也在Android系统中实现了消息循环机制。Android通过 Looper、Handler来实现消息循环机制,Android消息循环是针对线程的(每个线程都可以有自己的消息队列和消息循环)。本文深入介绍一下 Android消息处理系统原理。

Android系统中Looper负责管理线程的消息队列和消息循环,具体实现请参考Looper的源码。 可以通过Loop.myLooper()得到当前线程的Looper对象,通过Loop.getMainLooper()可以获得当前进程的主线程的 Looper对象。

前面提到Android系统的消息队列和消息循环都是针对具体线程的,一个线程可以存在(当然也可以不存在)一个消息队列和一个消息循环 (Looper),特定线程的消息只能分发给本线程,不能进行跨线程,跨进程通讯。但是创建的工作线程默认是没有消息循环和消息队列的,如果想让该线程具 有消息队列和消息循环,需要在线程中首先调用Looper.prepare()来创建消息队列,然后调用Looper.loop()进入消息循环。如下例 所示:

class LooperThread extends Thread {
public Handler mHandler;

public void run() {
Looper.prepare();

mHandler = new Handler() {
public void handleMessage(Message msg) {
// process incoming messages here
}
};

Looper.loop();
}
}这样你的线程就具有了消息处理机制了,在Handler中进行消息处理。

Activity是一个UI线程,运行于主线程中,Android系统在启动的时候会为Activity创建一个消息队列和消息循环(Looper)。详细实现请参考ActivityThread.java文件。

Handler的作用是把消息加入特定的(Looper)消息队列中,并分发和处理该消息队列中的消息。构造Handler的时候可以指定一个Looper对象,如果不指定则利用当前线程的Looper创建。详细实现请参考Looper的源码。

Activity、Looper、Handler的关系如下图所示:

message

一个Activity中可以创建多个工作线程或者其他的组件,如果这些线程或者组件把他们的消息放入Activity的主线程消息队列,那么该消息就会在 主线程中处理了。因为主线程一般负责界面的更新操作,并且Android系统中的weget不是线程安全的,所以这种方式可以很好的实现Android界 面更新。在Android系统中这种方式有着广泛的运用。

那么另外一个线程怎样把消息放入主线程的消息队列呢?答案是通过Handle对象,只要Handler对象以主线程的Looper创建,那么调用 Handler的sendMessage等接口,将会把消息放入队列都将是放入主线程的消息队列。并且将会在Handler主线程中调用该handler 的handleMessage接口来处理消息。

这里面涉及到线程同步问题,请先参考如下例子来理解Handler对象的线程模型:

1、首先创建MyHandler工程。

2、在MyHandler.java中加入如下的代码:

package com.simon;

import android.app.Activity;
import android.os.Bundle;
import android.os.Message;
import android.util.Log;
import android.os.Handler;

public class MyHandler extends Activity {
static final String TAG = “Handler”;
Handler h = new Handler(){
public void handleMessage (Message msg)
{
switch(msg.what)
{
case HANDLER_TEST:
Log.d(TAG, “The handler thread id = ” + Thread.currentThread().getId() + “\n”);
break;
}
}
};

static final int HANDLER_TEST = 1;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, “The main thread id = ” + Thread.currentThread().getId() + “\n”);

new myThread().start();
setContentView(R.layout.main);
}

class myThread extends Thread
{
public void run()
{
Message msg = new Message();
msg.what = HANDLER_TEST;
h.sendMessage(msg);
Log.d(TAG, “The worker thread id = ” + Thread.currentThread().getId() + “\n”);
}
}
}在这个例子中我们主要是打印,这种处理机制各个模块的所处的线程情况。如下是我的机器运行结果:

09-10 23:40:51.478: Debug/Handler(302): The main thread id = 1
09-10 23:40:51.569: Debug/Handler(302): The worker thread id = 8
09-10 23:40:52.128: Debug/Handler(302): The handler thread id = 1我们可以看出消息处理是在主线程中处理的,在消息处理函数中可以安全的调用主线程中的任何资源,包括刷新界面。工作线程和主线程运行在不同的线程中,所 以必须要注意这两个线程间的竞争关系。

上例中,你可能注意到在工作线程中访问了主线程handler对象,并在调用handler的对象向消息队列加入了一个消息。这个过程中会不会出现消息队 列数据不一致问题呢?答案是handler对象不会出问题,因为handler对象管理的Looper对象是线程安全的,不管是加入消息到消息队列和从队 列读出消息都是有同步对象保护的,具体请参考Looper.java文件。上例中没有修改handler对象,所以handler对象不可能会出现数据不 一致的问题。

通过上面的分析,我们可以得出如下结论:

1、如果通过工作线程刷新界面,推荐使用handler对象来实现。

2、注意工作线程和主线程之间的竞争关系。推荐handler对象在主线程中构造完成(并且启动工作线程之后不要再修改之,否则会出现数据不一致),然后在工作线程中可以放心的调用发送消息SendMessage等接口。

3、除了2所述的hanlder对象之外的任何主线程的成员变量如果在工作线程中调用,仔细考虑线程同步问题。如果有必要需要加入同步对象保护该变量。

4、handler对象的handleMessage接口将会在主线程中调用。在这个函数可以放心的调用主线程中任何变量和函数,进而完成更新UI的任务。

5、Android很多API也利用Handler这种线程特性,作为一种回调函数的变种,来通知调用者。这样Android框架就可以在其线程中将消息发送到调用者的线程消息队列之中,不用担心线程同步的问题。

深入理解Android消息处理机制对于应用程序开发非常重要,也可以让你对线程同步有更加深刻的认识。以上是最近Simon学习Android消息处理机制的一点儿总结,如有错误之处请不吝指教。

转自:http://blog.csdn.net/dywe_ddm/archive/2010/10/10/5930948.aspx

[转载]Flash游戏编程指南

mikel阅读(1103)

[转载]Flash游戏编程指南-RIABook.cn.


这本是天地会译林军翻译的完整版,700多页真是辛苦了,除了每页上边的培训广告有点煞风景其他都好.
英文原书是这本The Essential Guide to Flash Games: Building Interactive Entertainment with ActionScript

目录

第一部分  基本游戏框架
第一章 第二游戏说          (译者:aserrewin)
第二章 创建一个AS3游戏框架     (译者:aserrewin)
第三章 创建超级点击         (译者:kenjor)

第二部分  游戏实例
第四章 御空加农炮的基础架构     (译者:yyluo-阿树)
第五章 构建御空加农炮游戏循环    (译者:yyluo-阿树)
第六章 预备!坦克大战        (译者:sun11086-0025)
第七章 构建坦克大战游戏       (译者:sun11086-0025,peichao01)
第八章 休闲智力游戏-魔法色块    (译者:cosmos53076)
第九章 骰子游戏王          (译者:yangjh415)
第十章 滚屏游戏世界         (译者:Pizzaman)
第十一章 制作绝佳的反应力游戏    (译者:享受生活)
第十二章 制作一个Viral Game: 隧道惊魂 (译者:心月不皈,Mr.Star )

下载:

From 115网盘
From 微盘
From 天地会

[转载]android开发我的新浪微博客户端-阅读微博功能篇(6.2)

mikel阅读(866)

[转载]android开发我的新浪微博客户端-阅读微博功能篇(6.2) – 遇见未知的自己 – 博客园.

注:最近由于OAuth上传图片碰到了难题,一直在做这方面的研究导致博客很久没有更新。

在上面一篇中已经实现了预读微博的UI界面,效果如上图,接下来完成功能部分的代码,当用户在上一个列表界面的列表中点击某一条微博的时候显示这个阅读微博的界面,在这个界面中根据传来的微博ID,然后根据这个ID通过api获取微博的具体内容进行显示。

在ViewActivity.class的onCreate方法中添加如下代码:

private UserInfo user;
private String key=“”;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.view);

。。。。。

//获取上一个页面传递过来的key,key为某一条微博的id
Intent i=this.getIntent();
if(!i.equals(null)){
Bundle b
=i.getExtras();
if(b!=null){
if(b.containsKey(key)){
key
= b.getString(key);
view(key);
}
}
}

}

接下来就是view方法具体获取微博内容的方法,在这个方法中如果获取的本条微博如果包含图片那么就用前面AsyncImageLoader的方法异步载入图片并且进行显示,同时在这个方法中还要获取本条微博被转发的次数以及评论的次数,具体代码如下:

private void view(String id){
user
=ConfigHelper.nowUser;
OAuth auth
=new OAuth();
String url
= http://api.t.sina.com.cn/statuses/show/:id.json;
List params
=new ArrayList();
params.add(
new BasicNameValuePair(source, auth.consumerKey));
params.add(
new BasicNameValuePair(id, id));
HttpResponse response
=auth.SignRequest(user.getToken(), user.getTokenSecret(), url, params);
if (200 == response.getStatusLine().getStatusCode()){
try {
InputStream is
= response.getEntity().getContent();
Reader reader
= new BufferedReader(new InputStreamReader(is), 4000);
StringBuilder buffer
= new StringBuilder((int) response.getEntity().getContentLength());
try {
char[] tmp = new char[1024];
int l;
while ((l = reader.read(tmp)) != 1) {
buffer.append(tmp,
0, l);
}
}
finally {
reader.close();
}
String string
= buffer.toString();
//Log.e(“json”, “rs:” + string);
response.getEntity().consumeContent();
JSONObject data
=new JSONObject(string);
if(data!=null){
JSONObject u
=data.getJSONObject(user);
String userName
=u.getString(screen_name);
String userIcon
=u.getString(profile_image_url);
Log.e(
userIcon, userIcon);
String time
=data.getString(created_at);
String text
=data.getString(text);

TextView utv=(TextView)findViewById(R.id.user_name);
utv.setText(userName);
TextView ttv
=(TextView)findViewById(R.id.text);
ttv.setText(text);

ImageView iv=(ImageView)findViewById(R.id.user_icon);
AsyncImageLoader asyncImageLoader
= new AsyncImageLoader();
Drawable cachedImage
= asyncImageLoader.loadDrawable(userIcon,iv, new ImageCallback(){
@Override
public void imageLoaded(Drawable imageDrawable,ImageView imageView, String imageUrl) {

imageView.setImageDrawable(imageDrawable);
}
});
if (cachedImage == null)
{
iv.setImageResource(R.drawable.usericon);
}
else
{
iv.setImageDrawable(cachedImage);
}
if(data.has(bmiddle_pic)){
String picurl
=data.getString(bmiddle_pic);
String picurl2
=data.getString(original_pic);

ImageView pic=(ImageView)findViewById(R.id.pic);
pic.setTag(picurl2);
pic.setOnClickListener(
new OnClickListener() {
@Override
public void onClick(View v) {
Object obj
=v.getTag();
Intent intent
= new Intent(ViewActivity.this,ImageActivity.class);
Bundle b
=new Bundle();
b.putString(
url, obj.toString());
intent.putExtras(b);
startActivity(intent);
}
});
Drawable cachedImage2
= asyncImageLoader.loadDrawable(picurl,pic, new ImageCallback(){
@Override
public void imageLoaded(Drawable imageDrawable,ImageView imageView, String imageUrl) {
showImg(imageView,imageDrawable);
}
});
if (cachedImage2 == null)
{
//pic.setImageResource(R.drawable.usericon);
}
else
{
showImg(pic,cachedImage2);
}
}
}
}
catch (IllegalStateException e) {
e.printStackTrace();
}
catch (IOException e) {
e.printStackTrace();
}
catch (JSONException e) {
e.printStackTrace();
}
}
url
= http://api.t.sina.com.cn/statuses/counts.json;
params
=new ArrayList();
params.add(
new BasicNameValuePair(source, auth.consumerKey));
params.add(
new BasicNameValuePair(ids, id));
response
=auth.SignRequest(user.getToken(), user.getTokenSecret(), url, params);
if (200 == response.getStatusLine().getStatusCode()){
try {
InputStream is
= response.getEntity().getContent();
Reader reader
= new BufferedReader(new InputStreamReader(is), 4000);
StringBuilder buffer
= new StringBuilder((int) response.getEntity().getContentLength());
try {
char[] tmp = new char[1024];
int l;
while ((l = reader.read(tmp)) != 1) {
buffer.append(tmp,
0, l);
}
}
finally {
reader.close();
}
String string
= buffer.toString();
response.getEntity().consumeContent();
JSONArray data
=new JSONArray(string);
if(data!=null){
if(data.length()>0){
JSONObject d
=data.getJSONObject(0);
String comments
=d.getString(comments);
String rt
=d.getString(rt);
Button btn_gz
=(Button)findViewById(R.id.btn_gz);
btn_gz.setText(
转发(+rt+));
Button btn_pl
=(Button)findViewById(R.id.btn_pl);
btn_pl.setText(
评论(+comments+));
}
}
}
catch (IllegalStateException e) {
e.printStackTrace();
}
catch (IOException e) {
e.printStackTrace();
}
catch (JSONException e) {
e.printStackTrace();
}
}

}

在上面的方法中对于微博中包含的图片显示尺寸进行了特别的处理,如果直接把获取的图片显示在ImageView中,因为当图片宽高超过手机屏幕的时候,系 统会自动按照手机的屏幕按比例缩放图片进行显示,但是我发现一个现象图片的高虽然是按照比例缩小了,但是图片占据的高仍旧是原来图片的高度照成真实图片和 文字内容之间多了很高的一块空白,这个现象非常的奇怪,所以我写了如下方法进行处理:

private void showImg(ImageView view,Drawable img){
int w=img.getIntrinsicWidth();
int h=img.getIntrinsicHeight();
Log.e(
w, w+/+h);
if(w>300)
{
int hh=300*h/w;
Log.e(
hh, hh+“”);
LayoutParams para
=view.getLayoutParams();
para.width
=300;
para.height
=hh;
view.setLayoutParams(para);
}
view.setImageDrawable(img);

}

本篇到这里就结束了,请继续关注下一篇。

[转载]ASP.NET MVC 3.0学习系列文章—NuGet and ASP.NET MVC 3.0

mikel阅读(1121)

[转载]ASP.NET MVC 3.0学习系列文章—NuGet and ASP.NET MVC 3.0 – 爱因斯坦的小脑 – 博客园.

这两周忙着换工作,也没时间更新这个系列的文章。。。。。。。 Smile 在在博客园搜了NuGet这个关键字,结果只有两页,还有几篇文章是我的,所以我觉得还是介绍下NuGet,以及NuGet.org,好让一些初学者能够快速的入门。

ASP.NET MVC 3.0学习系列文章—序

ASP.NET MVC 3.0学习系列文章–Razor and ASP.NET MVC 3.0

ASP.NET MVC 3.0学习系列文章—Controllers in ASP.NET MVC 3.0

ASP.NET MVC 3.0学习系列文章—Model in ASP.NET MVC 3.0

ASP.NET MVC 3.0学习系列文章–Dependency Resolution in ASP.NET MVC 3.0

NuGet-Stickers-550x365

1.Introduction

NuGet is a Visual Studio 2010 extension that makes it easy to add, remove, and update libraries and tools in Visual Studio projects that use the .NET Framework. This topic lists documentation that will help you use NuGet packages and create your own.

NuGet是一个开源的项目,项目的发起人是微软的几个人员。如果你关注NuGet建议关注这两个人:

a.David Ebbo http://blog.davidebbo.com/ Twitter: @davidebbo

b. Phil Haack http://haacked.com/

2.Why NuGet

NuGet的使用可以缩短我们下载和添加dll的时间。不适用nuget的时候,我们添加一个dll需要先下载,解压,添加引用等等。而使用nuGet只需要一步操作。

image

可以看出来,使用NuGet后,dll的配置可以自动添加,而且当有新的版本出现时可以自动更新dll。

使用NuGet另外一个优点是,你添加一个dll后,它会自动把相关联的dll给添加到引用中来。

image

3.安装NuGet

你可以去codeplex上下载NuGet,或者使用VS2010的Extension Manager来安装NuGet:

image

关于NuGet的使用,我这里就给大家说明下如何使用帮助命令:

image

4.NuGet的打包和Feed:

1. 如何打包一个NuGet包: http://nuget.codeplex.com/wikipage?title=Creating%20a%20Package

2.Hosting Your Own Local and Remote NuGet Feeds

http://haacked.com/archive/2010/10/21/hosting-your-own-local-and-remote-nupack-feeds.aspx

5.NuGet.org

image

你只需要注册一个账号就可以上传Package了。 如果你需要和别人分享一个模块或者项目,可以试试使用nuget来分享。。

Nick

[转载]memcached源码分析之slabs

mikel阅读(1099)

[转载]memcache源码分析之slabs – 先贝夜话 – 博客园.

slab是memcache用来管理item的内容存储部分。

分配内存时,memcache把我们通过参数m设置的内存大小分配到每个slab中

1、slab默认最多为200个,但是由于item的最大为1MB,而且每个slab里面存储的item的尺寸是根据factor来确定的,所以能够分配的slab的个数小于200。

2、关于增长因子factor参数(配置时参数名为f),默认为1.25,即每个slab所能存储的item的大小是根据factor的大小来变化的。

3、每个slab中含有一个或多个trunk,trunk中存储的就是item,item的最大为1M,所以trunk最大为1M

4、每个slab中会有一个item空闲列表,当新的item需要存储时,首先会考虑空闲列表,从中取出一个位置用来存储。当空闲列表满时,系统会去自动扩充。

5、每个slab中有二个参数为end_page_ptr、end_page_free,前者指向当前空闲的trunk指针,后者当前 trunk指向空闲处,当4中的空闲列表为空时,如果end_page_ptr和end_page_free不为空,则会在此trunk中存储item。 如果没有多余的trunk可用,系统会自动扩充trunk。

采用这种方式管理内存的好处是最大程度的减少了内存碎片的产生,提高了存储和读取效率。

下面是一些源码注释

slabs.h

1 /* stats */
2 void stats_prefix_init(void);
3 void stats_prefix_clear(void);
4 void stats_prefix_record_get(const char *key, const size_t nkey, const bool is_hit);
5 void stats_prefix_record_delete(const char *key, const size_t nkey);
6 void stats_prefix_record_set(const char *key, const size_t nkey);
7 /*@null@*/
8 char *stats_prefix_dump(int *length);

slabs.c

001 /* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
002 /*
003 * Slabs memory allocation, based on powers-of-N. Slabs are up to 1MB in size
004 * and are divided into chunks. The chunk sizes start off at the size of the
005 * "item" structure plus space for a small key and value. They increase by
006 * a multiplier factor from there, up to half the maximum slab size. The last
007 * slab size is always 1MB, since that's the maximum item size allowed by the
008 * memcached protocol.
009 */
010 #include "memcached.h"
011 #include <sys/stat.h>
012 #include <sys/socket.h>
013 #include <sys/signal.h>
014 #include <sys/resource.h>
015 #include <fcntl.h>
016 #include <netinet/in.h>
017 #include <errno.h>
018 #include <stdlib.h>
019 #include <stdio.h>
020 #include <string.h>
021 #include <assert.h>
022 #include <pthread.h>
023
024 /* powers-of-N allocation structures */
025
026 typedef struct {
027 unsigned int size; /* item的大小 */
028 unsigned int perslab; /* 每个trunk有多少item */
029
030 void **slots; //空闲item列表
031 unsigned int sl_total; //空闲总量
032 unsigned int sl_curr; //当前空闲处
033
034 void *end_page_ptr; //当前trunk空闲处
035 unsigned int end_page_free; //当前trunk空闲个数
036
037 unsigned int slabs; //已分配chunk数目
038
039 void **slab_list; //trunk指针
040 unsigned int list_size; //trunk数目
041
042 unsigned int killing; /* index+1 of dying slab, or zero if none */
043 size_t requested; //已分配总内存大小
044 } slabclass_t;
045
046 static slabclass_t slabclass[MAX_NUMBER_OF_SLAB_CLASSES];
047 static size_t mem_limit = 0;//内存限制大小
048 static size_t mem_malloced = 0;//已分配大小
049 static int power_largest;
050
051 static void *mem_base = NULL;
052 static void *mem_current = NULL;//内存使用当前地址
053 static size_t mem_avail = 0;//剩余内存
054
055 /**
056 * slab 线程锁
057 */
058 static pthread_mutex_t slabs_lock = PTHREAD_MUTEX_INITIALIZER;
059
060 /*
061 * Forward Declarations
062 */
063 static int do_slabs_newslab(const unsigned int id);
064 static void *memory_allocate(size_t size);
065
066 #ifndef DONT_PREALLOC_SLABS
067 /* Preallocate as many slab pages as possible (called from slabs_init)
068 on start-up, so users don't get confused out-of-memory errors when
069 they do have free (in-slab) space, but no space to make new slabs.
070 if maxslabs is 18 (POWER_LARGEST - POWER_SMALLEST + 1), then all
071 slab types can be made.  if max memory is less than 18 MB, only the
072 smaller ones will be made.  */
073 static void slabs_preallocate (const unsigned int maxslabs);
074 #endif
075
076
077 //寻找适合给定大小的item存储的slab
078 unsigned int slabs_clsid(const size_t size) {
079 int res = POWER_SMALLEST;
080
081 if (size == 0)
082 return 0;
083 while (size > slabclass[res].size)//找到第一个比item size大的slab
084 if (res++ == power_largest)
085 return 0;
086 return res;
087 }
088
089
090 /* slab初始化*/
091 /* limit:内存大小(字节);factor:增长因子;prealloc:是否一次性分配内存*/
092 void slabs_init(const size_t limit, const double factor, const bool prealloc) {
093 int i = POWER_SMALLEST - 1;//0
094 unsigned int size = sizeof(item) + settings.chunk_size;//chunk_size 最小分配空间
095
096 mem_limit = limit;
097
098 if (prealloc) {//一次分配所有设置的内存
099 /* Allocate everything in a big chunk with malloc */
100 mem_base = malloc(mem_limit);
101 if (mem_base != NULL) {
102 mem_current = mem_base;
103 mem_avail = mem_limit;
104 } else {
105 fprintf(stderr, "Warning: Failed to allocate requested memory in one large chunk.\nWill allocate in smaller chunks\n");
106 }
107 }
108
109 memset(slabclass, 0, sizeof(slabclass));
110
111 while (++i < POWER_LARGEST && size <= settings.item_size_max / factor) {
112 /* Make sure items are always n-byte aligned */
113 if (size % CHUNK_ALIGN_BYTES)//字节数为8的倍数
114 size += CHUNK_ALIGN_BYTES - (size % CHUNK_ALIGN_BYTES);
115
116 slabclass[i].size = size;//item大小
117 slabclass[i].perslab = settings.item_size_max / slabclass[i].size;//item数目
118 size *= factor;//乘以增长因子
119 if (settings.verbose > 1) {
120 fprintf(stderr, "slab class %3d: chunk size %9u perslab %7u\n",i, slabclass[i].size, slabclass[i].perslab);
121 }
122 }
123
124 power_largest = i;
125 slabclass[power_largest].size = settings.item_size_max;
126 slabclass[power_largest].perslab = 1;//最大的只能存储一个item
127 if (settings.verbose > 1) {
128 fprintf(stderr, "slab class %3d: chunk size %9u perslab %7u\n",i, slabclass[i].size, slabclass[i].perslab);
129 }
130
131 /* for the test suite:  faking of how much we've already malloc'd */
132 {
133 char *t_initial_malloc = getenv("T_MEMD_INITIAL_MALLOC");
134 if (t_initial_malloc) {
135 mem_malloced = (size_t)atol(t_initial_malloc);
136 }
137
138 }
139
140 #ifndef DONT_PREALLOC_SLABS
141 {
142 char *pre_alloc = getenv("T_MEMD_SLABS_ALLOC");
143
144 if (pre_alloc == NULL || atoi(pre_alloc) != 0) {
145 slabs_preallocate(power_largest);
146 }
147 }
148 #endif
149 }
150
151
152 //新分配trunk
153 #ifndef DONT_PREALLOC_SLABS
154 static void slabs_preallocate (const unsigned int maxslabs) {
155 int i;
156 unsigned int prealloc = 0;
157
158 /* pre-allocate a 1MB slab in every size class so people don't get
159 confused by non-intuitive "SERVER_ERROR out of memory"
160 messages.  this is the most common question on the mailing
161 list.  if you really don't want this, you can rebuild without
162 these three lines.  */
163
164 for (i = POWER_SMALLEST; i <= POWER_LARGEST; i++) {
165 if (++prealloc > maxslabs)
166 return;
167 do_slabs_newslab(i);
168 }
169
170 }
171 #endif
172
173
174 //扩充trunk数目
175 static int grow_slab_list (const unsigned int id) {
176 slabclass_t *p = &slabclass[id];
177 if (p->slabs == p->list_size) {
178 size_t new_size =  (p->list_size != 0) ? p->list_size * 2 : 16;
179 void *new_list = realloc(p->slab_list, new_size * sizeof(void *));
180 if (new_list == 0) return 0;
181 p->list_size = new_size;
182 p->slab_list = new_list;
183 }
184 return 1;
185 }
186
187
188
189 //分配trunk
190 static int do_slabs_newslab(const unsigned int id) {
191 slabclass_t *p = &slabclass[id];
192 int len = p->size * p->perslab;//1MB
193 char *ptr;
194
195 if ((mem_limit && mem_malloced + len > mem_limit && p->slabs > 0) || (grow_slab_list(id) == 0) || ((ptr = memory_allocate((size_t)len)) == 0)) {
196 MEMCACHED_SLABS_SLABCLASS_ALLOCATE_FAILED(id);
197 return 0;
198 }
199
200 memset(ptr, 0, (size_t)len);
201 p->end_page_ptr = ptr;
202 p->end_page_free = p->perslab;
203
204 p->slab_list[p->slabs++] = ptr;
205 mem_malloced += len;
206 MEMCACHED_SLABS_SLABCLASS_ALLOCATE(id);
207
208 return 1;
209 }
210
211
212
213 /*@存储item@*/
214 static void *do_slabs_alloc(const size_t size, unsigned int id) {
215 slabclass_t *p;
216 void *ret = NULL;
217
218 if (id < POWER_SMALLEST || id > power_largest) {
219 MEMCACHED_SLABS_ALLOCATE_FAILED(size, 0);
220 return NULL;
221 }
222
223 p = &slabclass[id];
224 assert(p->sl_curr == 0 || ((item *)p->slots[p->sl_curr - 1])->slabs_clsid == 0);
225
226 #ifdef USE_SYSTEM_MALLOC
227 if (mem_limit && mem_malloced + size > mem_limit) {
228 MEMCACHED_SLABS_ALLOCATE_FAILED(size, id);
229 return 0;
230 }
231 mem_malloced += size;
232 ret = malloc(size);
233 MEMCACHED_SLABS_ALLOCATE(size, id, 0, ret);
234 return ret;
235 #endif
236
237 /* fail unless we have space at the end of a recently allocated page,
238 we have something on our freelist, or we could allocate a new page */
239 if (! (p->end_page_ptr != 0 || p->sl_curr != 0 || do_slabs_newslab(id) != 0)) {//没有空闲 也不能扩展
240 ret = NULL;
241 } else if (p->sl_curr != 0) {
242 /* return off our freelist */
243 ret = p->slots[--p->sl_curr];
244 } else {
245 /* if we recently allocated a whole page, return from that */
246 assert(p->end_page_ptr != NULL);
247 ret = p->end_page_ptr;
248 if (--p->end_page_free != 0) {
249 p->end_page_ptr = ((caddr_t)p->end_page_ptr) + p->size;
250 } else {
251 p->end_page_ptr = 0;
252 }
253 }
254
255 if (ret) {
256 p->requested += size;
257 MEMCACHED_SLABS_ALLOCATE(size, id, p->size, ret);
258 } else {
259 MEMCACHED_SLABS_ALLOCATE_FAILED(size, id);
260 }
261
262 return ret;
263 }
264
265
266 //释放item内存
267 static void do_slabs_free(void *ptr, const size_t size, unsigned int id) {
268 slabclass_t *p;
269
270 assert(((item *)ptr)->slabs_clsid == 0);
271 assert(id >= POWER_SMALLEST && id <= power_largest);
272 if (id < POWER_SMALLEST || id > power_largest)
273 return;
274
275 MEMCACHED_SLABS_FREE(size, id, ptr);
276 p = &slabclass[id];
277
278 #ifdef USE_SYSTEM_MALLOC
279 mem_malloced -= size;
280 free(ptr);
281 return;
282 #endif
283
284 if (p->sl_curr == p->sl_total) { //需要扩充空闲列表
285 int new_size = (p->sl_total != 0) ? p->sl_total * 2 : 16; /* 16 is arbitrary */
286 void **new_slots = realloc(p->slots, new_size * sizeof(void *));
287 if (new_slots == 0)
288 return;
289 p->slots = new_slots;
290 p->sl_total = new_size;
291 }
292 p->slots[p->sl_curr++] = ptr;
293 p->requested -= size;
294 return;
295 }
296
297
298 static int nz_strcmp(int nzlength, const char *nz, const char *z) {
299 int zlength=strlen(z);
300 return (zlength == nzlength) && (strncmp(nz, z, zlength) == 0) ? 0 : -1;
301 }
302
303
304 //获取状态
305 bool get_stats(const char *stat_type, int nkey, ADD_STAT add_stats, void *c) {
306 bool ret = true;
307
308 if (add_stats != NULL) {
309 if (!stat_type) {
310 /* prepare general statistics for the engine */
311 STATS_LOCK();
312 APPEND_STAT("bytes", "%llu", (unsigned long long)stats.curr_bytes);
313 APPEND_STAT("curr_items", "%u", stats.curr_items);
314 APPEND_STAT("total_items", "%u", stats.total_items);
315 APPEND_STAT("evictions", "%llu",(unsigned long long)stats.evictions);
316 APPEND_STAT("reclaimed", "%llu",(unsigned long long)stats.reclaimed);
317 STATS_UNLOCK();
318 } else if (nz_strcmp(nkey, stat_type, "items") == 0) {
319 item_stats(add_stats, c);
320 } else if (nz_strcmp(nkey, stat_type, "slabs") == 0) {
321 slabs_stats(add_stats, c);
322 } else if (nz_strcmp(nkey, stat_type, "sizes") == 0) {
323 item_stats_sizes(add_stats, c);
324 } else {
325 ret = false;
326 }
327 } else {
328 ret = false;
329 }
330
331 return ret;
332 }
333
334
335 /*状态*/
336 static void do_slabs_stats(ADD_STAT add_stats, void *c) {
337 int i, total;
338 /* Get the per-thread stats which contain some interesting aggregates */
339 struct thread_stats thread_stats;
340 threadlocal_stats_aggregate(&thread_stats);
341
342 total = 0;
343 for(i = POWER_SMALLEST; i <= power_largest; i++) {
344 slabclass_t *p = &slabclass[i];
345 if (p->slabs != 0) {
346 uint32_t perslab, slabs;
347 slabs = p->slabs;
348 perslab = p->perslab;
349
350 char key_str[STAT_KEY_LEN];
351 char val_str[STAT_VAL_LEN];
352 int klen = 0, vlen = 0;
353
354 APPEND_NUM_STAT(i, "chunk_size", "%u", p->size);
355 APPEND_NUM_STAT(i, "chunks_per_page", "%u", perslab);
356 APPEND_NUM_STAT(i, "total_pages", "%u", slabs);
357 APPEND_NUM_STAT(i, "total_chunks", "%u", slabs * perslab);
358 APPEND_NUM_STAT(i, "used_chunks", "%u",slabs*perslab - p->sl_curr - p->end_page_free);
359 APPEND_NUM_STAT(i, "free_chunks", "%u", p->sl_curr);
360 APPEND_NUM_STAT(i, "free_chunks_end", "%u", p->end_page_free);
361 APPEND_NUM_STAT(i, "mem_requested", "%llu",(unsigned long long)p->requested);
362 APPEND_NUM_STAT(i, "get_hits", "%llu",(unsigned long long)thread_stats.slab_stats[i].get_hits);
363 APPEND_NUM_STAT(i, "cmd_set", "%llu",(unsigned long long)thread_stats.slab_stats[i].set_cmds);
364 APPEND_NUM_STAT(i, "delete_hits", "%llu",(unsigned long long)thread_stats.slab_stats[i].delete_hits);
365 APPEND_NUM_STAT(i, "incr_hits", "%llu",(unsigned long long)thread_stats.slab_stats[i].incr_hits);
366 APPEND_NUM_STAT(i, "decr_hits", "%llu",(unsigned long long)thread_stats.slab_stats[i].decr_hits);
367 APPEND_NUM_STAT(i, "cas_hits", "%llu",(unsigned long long)thread_stats.slab_stats[i].cas_hits);
368 APPEND_NUM_STAT(i, "cas_badval", "%llu",(unsigned long long)thread_stats.slab_stats[i].cas_badval);
369
370 total++;
371 }
372 }
373
374 /* add overall slab stats and append terminator */
375
376 APPEND_STAT("active_slabs", "%d", total);
377 APPEND_STAT("total_malloced", "%llu", (unsigned long long)mem_malloced);
378 add_stats(NULL, 0, NULL, 0, c);
379 }
380
381
382 //为item分配内存
383 static void *memory_allocate(size_t size) {
384 void *ret;
385
386 if (mem_base == NULL) {
387 /* We are not using a preallocated large memory chunk */
388 ret = malloc(size);
389 } else {
390 ret = mem_current;
391
392 if (size > mem_avail) {
393 return NULL;
394 }
395
396 /* mem_current pointer _must_ be aligned!!! */
397 if (size % CHUNK_ALIGN_BYTES) {
398 size += CHUNK_ALIGN_BYTES - (size % CHUNK_ALIGN_BYTES);
399 }
400
401 mem_current = ((char*)mem_current) + size;
402 if (size < mem_avail) {
403 mem_avail -= size;
404 } else {
405 mem_avail = 0;
406 }
407 }
408
409 return ret;
410 }
411
412
413 //存储
414 void *slabs_alloc(size_t size, unsigned int id) {
415 void *ret;
416
417 pthread_mutex_lock(&slabs_lock);
418 ret = do_slabs_alloc(size, id);
419 pthread_mutex_unlock(&slabs_lock);
420 return ret;
421 }
422
423
424 //释放
425 void slabs_free(void *ptr, size_t size, unsigned int id) {
426 pthread_mutex_lock(&slabs_lock);
427 do_slabs_free(ptr, size, id);
428 pthread_mutex_unlock(&slabs_lock);
429 }
430
431
432 //状态
433 void slabs_stats(ADD_STAT add_stats, void *c) {
434 pthread_mutex_lock(&slabs_lock);
435 do_slabs_stats(add_stats, c);
436 pthread_mutex_unlock(&slabs_lock);
437 }

[转载]当网站遭遇DDOS攻击的解决方案及展望

mikel阅读(746)

[转载]当网站遭遇DDOS攻击的解决方案及展望 – 李晨光 – 博客园.

一、事件发生
春节长假刚过完,WEB就出现故障,下午1点吃完回来,立即将桌面解锁并习惯性的检查了Web服务器。通过Web服务器性能监视软件图像显示的向下滑行的红色曲线看到WEB出现问题了。
根据上述的问题,我马上开始核查Web服务器的日志,试 试是否能检测到问题究竟什么时候开始,或者发现一些关于引起中断的线索。正当查询线索过程中。公司首席运营官(CIO)告诉我,他已经接到客户的投诉电 话,报告说无法访问他们的网站。于是从台式机中敲入网站地址,试着从台式电脑访问他们的网站,但是看到的只是无法显示此页面的消息。
回想前几天也未对Web服务器做了任何改变也未对Web 服务器做过任何改变,服务器曾经出现过的性能问题。在Web服务器的日志文件中没有发现任何可疑之处,因此接下来我去仔细查看防火墙日志,和路由器日志。 仔细查看了防火墙日志,打印出了那台服务器出问题时的记录。并过滤掉正常的流量并保留下可疑的记录。表中显示了打印出来的结果。
源IP地址
目的IP地址
源端口号
目的端口号
协议
172.16.45.2
192.168.0.175
7843
7
17
10.166.166.166
192.168.0.175
19
7
17
10.168.45.3
192.168.0.175
34511
7
17
10.166.166.166
192.168.0.175
19
7
17
192.168.89.111
192.168.0.175
1783
7
17
10.166.166.166
192.168.0.175
19
7
17
10.231.76.8
192.168.0.175
29589
7
17
192.168.15.12
192.168.0.175
17330
7
17
10.166.166.166
192.168.0.175
19
7
17
172.16.43.131
192.168.0.175
8935
7
17
10.23.67.9
192.168.0.175
22387
7
17
10.166.166.166
192.768.0.75
19
7
17
192.168.57.2
192.168.0.175
6588
7
17
172.16.87.11
192.768.0.75
21453
7
17
10.166.166.166
192.168.0.175
19
7
17
10.34.67.89
192.168.0.175
45987
7
17
10.65.34.54
192.168.0.175
65212
7
17
192.168.25.6
192.168.0.175
52967
7
17
172.16.56.15
192.168.0.175
8745
7
17
10.166.166.166
192.168.0.175
19
7
17
表一 防火墙日志
之后在路由器日志上做了同样的工作并打印出了看上去异常的记录。
攻击期间的路由器日志
图一
解释:
IP packet sizedistribution 这个标题下的两行显示了数据包按大小范围分布的百分率。这里显示的内容表明:98.4%的数据包的大小在33字节到64字节之间(注意红色标记)。
参数解释:
IP packet sizedistribution 这个标题下的两行显示了数据包按大小范围分布的百分率。这里显示的内容表明:98.4%的数据包的大小在33字节到64字节之间。
Protocol 协议名称
Total Flows 自从最后一次清除统计信息后,这种协议的信息流的个数。
Flows/Sec 每秒钟时间内出现这种协议的信息流的平均个数,它等于总信息流数/综合时间的秒数。
Packets/Flow 遵守这种协议的信息流中平均的数据包数。等于这种协议的数据包数,或者在这段综合时间内,这种协议的信息流数。
Bytes/Pkt 遵守这种协议的数据包的平均字节数(等于这种协议总字节数,或者在这段综合时间内,这种协议的数据包数)。B/Pkt ,这一信息流中每个数据包的平均字节数
Packets/Sec 每秒钟时间内这种协议的平均数据包数(它等于这种协议的总数据包),或者这段综合时间的总秒数。
Active(Sec)/Flow 从第一个数据包到终止信息流的最后一个数据包的总时间(以秒为单位,比如TCP FIN,终止时间量等等),或者这段综合时间内这种协议总的信息流数。
Idle(Sec)/Flow 从这种协议的各个非终止信息流的最后一个数据包起,直到输入这一命令时止的时间总和(以秒为单位),或者这段综合时间内信息流的总时间长度。
正常路由日志
图二
IP packet sizedistribution 这个标题下的两行显示了数据包按大小范围分布的百分率。这里显示的内容表明:2%的数据包的大小在33字节到64字节之间。
注意网站的访问量直线下降。很明显,在这段时间没人能访问他的Web服务器。我开始研究到底发生了什么,以及该如何尽快地修复。
二、事件分析
我的Web服务器发生了什么?很有可能攻击,那么受到什 么样的攻击呢?从这一攻击是对回显端口看,即是端口7,不断发送小的UDP数据包来实现。攻击看似发自两个策源地,可能是两个攻击者同时使用不同的工具。 在任何情况下,超负荷的数据流都会拖垮Web服务器。然而攻击地址源不确定,不知道是攻击源本身是分布的,还是同一个地址伪装出许多不同的IP地址,这个 问题比较难判断。假如源地址不是伪装的,是真实地址,则可以咨询ARIN I美国Internet号码注册处,从它的“whois”数据库查出这个入侵1P地址属于哪个网络。接下来只需联系那个网络的管理员就可以得到进一步的信 息。
那么假如源地址是伪装的,追踪这个攻击者就麻烦得多。若 使用的是Cisco路由器,则还需查询NetFlow高速缓存。NetFlow是Cisco快速转发(CEF)交换框架的特性之一。为了追踪这个伪装的地 址,必须查询每个路由器上的NetFlow缓存,才能确定流量进入了哪个接口,然后通过这些路由器一次一个接口地往回一路追踪,直至找到那个IP地址源。 然而这样做是非常难的,因为在Web Server和攻击者的发起pc之间可能经由许多路由器,而且属于不同的组织。另外,必须在攻击正在进行时做这些分析。
经过分析之后,将防火墙日志和路由器日志里的信息关联起 来,发现了一些有趣的相似性,如表黑色标记处。攻击的目标显然是Web服务器192.68.0.175,端口为UDP 7,即回显端口。这看起来很像拒绝服务攻击(但还不能确定,因为攻击的分布很随意)。地址看起来多多少少是随意而分散的,只有一个源地址是固定不变的,其 源端口号也没变。这很有趣。接着又将注意力集中到路由器日志上。
立刻发现,攻击发生时路由器日志上有大量的64字节的数据包,而此时Web服务器日志上没有任何问题。他还发现,案发时路由器日志里还有大量的“UDP-other”数据包,而Web服务器日志也一切正常。这种现象与基于UDP的拒绝服务攻击的假设还是很相符的。
攻击者正是用许多小的UDP数据包对Web服务器的回显 (echo 7)端口进行洪泛式攻击,因此他们的下一步任务就是阻止这一行为。首先,我们在路由器上堵截攻击。快速地为路由器设置了一个过滤规则。因为源地址的来源很 随机,他们认为很难用限制某个地址或某一块范围的地址来阻止攻击,因此决定禁止所有发给192.168.0.175的UDP包。这种做法会使服务器丧失某 些功能,如DNS,但至少能让Web服务器正常工作。
路由器最初的临时DOS访问控制链表(ACL)
access-list 121 remark Temporary block DoS attack on web server 192.168.0.175
access-list 105 deny udp any host 192.168.0.175
access-list 105 permit ip any any
这样的做法为Web服务器减轻了负担,但攻击仍能到达web,在一定程度上降低了网络性能。 那么下一步工作是联系上游带宽提供商,想请他们暂时限制所有在他的网站端口7上的UDP入流量。这样做会显著降低网络上到服务器的流量。
三、针对DOS预防措施
对于预防及缓解这种带宽相关的DoS攻击并没有什么灵丹妙药。本质上,这是一种“粗管子打败细管子”的攻击。攻击者能“指使”更多带宽,有时甚至是巨大的带宽,就能击溃带宽不够的网络。在这种情况下,预防和缓解应相辅相成。
有许多方法可以使攻击更难发生,或者在攻击发生时减小其影响,具体如下:
Ø 网络入口过滤 网络服务提供商应在他的下游网络上设置入口过滤,以防止假信息包进入网络(而把它们留在Internet上)。这将防止攻击者伪装IP地址,从而易于追踪。
Ø 网络流量过滤 过滤掉网络不需要的流量总是不会错的。这还能防止DoS攻击,但为了达到效果,这些过滤器应尽量设置在网络上游。
Ø 网络流量速率限制 一些路由器有流量速率的最高限制。这些限制条款将加强带宽策略,并允许一个给定类型的网络流量匹配有限的带宽。这一措施也能预先缓解正在进行的攻击,同时,这些过滤器应尽量设置在网络上游(尽可能靠近攻击者);
Ø 入侵检测系统和主机监听工具 IDS能警告网络管理员攻击的发生时间,以及攻击者使用的攻击工具,这将能协助阻止攻击。主机监听工具能警告管理员系统中是否出现DoS工具
Ø 单点传送RPF 这是CEF用于检查在接口收到的数据包的另一特性。如果源IP地址CEF表上不具有与指向接收数据包时的接口一致的路由的话,路由器就会丢掉这个数据包。丢弃RPF的妙处在于,它阻止了所有伪装源IP地址的攻击。
针对DDOS预防措施
看了上面的实际案例我们也了解到,许多DDoS攻击都很 难应对,因为搞破坏的主机所发出的请求都是完全合法、符合标准的,只是数量太大。借助恰当的ACL,我们可以阻断ICMP echo请求。但是,如果有自己的自治系统,就应该允许从因特网上ping你。不能ping通会使ISP或技术支持团队(如果有的话)丧失某些故障排解能 力。也可能碰到具有Cisco TCP截获功能的SYN洪流:
Router(config)#ip tcp intercept list 101
Router(config)#ip tcp intercept max-incomplete high 3500
Router(config)#ip tcp intercept max-incomplete low  3000
Router(config)#ip tcp intercept one-minute high 2500
Router(config)#ip tcp intercept one-minute low 2000
Router(config)#access-list 101 permit any any
如果能采用基于上下文的访问控制(Context Based Access Control,CBAC),则可以用其超时和阈值设置应对SYN洪流和UDP垃圾洪流。例如:
Router(config)# ip inspect tcp synwait-time 20
Router(config)# ip inspect tcp idle-time 60
Router(config)# ip inspect udp idle-time 20
Router(config)# ip inspect max-incomplete high 400
Router(config)# ip inspect max-incomplete low  300
Router(config)# ip inspect one-minute high  600
Router(config)# ip inspect one-minute low 500
Router(config)# ip inspect tcp max-incomplete host 300 block-time 0
警告:建议不要同时使用TCP截获和CBAC防御功能,因为这可能导致路由器过载。
打开Cisco快速转发(Cisco Express Forwarding,CEF)功能可帮助路由器防御数据包为随机源地址的洪流。可以对调度程序做些设置,避免在洪流的冲击下路由器的CPU完全过载:
Router(config)#scheduler allocate 3000 1000
在做了这样的配置之后,IOS会用3s的时间处理网络接口中断请求,之后用1s执行其他任务。对于较早的系统,可能必须使用命令scheduler interval<milliseconds>。
四、总结
无论是出于报复、敲诈勒索、发起更大规模攻击,DoS或 DDoS攻击都是一种不容轻视的威胁。非同一般的DoS攻击通常是某种不完整的漏洞利用—使系统服务崩溃,而不是将控制权交给攻击者。防范这种攻击的办法 是及时打上来自厂商的补丁,或者对于Cisco系统,及时将操作系统升级到更新版本。同时,要关闭有漏洞的服务,或者至少要用访问控制列表限制访问。常规 的DoS攻击,特别是DDoS攻击,经常不是那么有章法,也更难防范。如果整个带宽都被蹩脚的ping洪流所耗尽,我们所能做的就很有限了。最后,必须与 ISP和权力部门协作,尽可能从源头上阻止攻击。要用不同供应商、不同AS路径并支持负载均衡功能的不止一条到因特网的连接,但这与应对消耗高带宽的常规 DoS/DDoS洪流的要求还相差很远。我们总是可以用CAR或NBAR来抛弃数据包或限制发动进攻的网络流速度,减轻路由器CPU的负担,减少对缓冲区 和路由器之后的主机的占用。

[转载]Javascript中各种trim的实现

mikel阅读(980)

[转载]Javascript中各种trim的实现 – snandy – 博客园.

這是lgzx公司的一道面試題,要求給js的String添加一個方法,去除字符串兩邊的空白字符(包括空格、製錶符、換頁符等)。

1 String.prototype.trim = function() {
2 //return this.replace(/[(^\s+)(\s+$)]/g,"");//會把字符串中間的空白符也去掉
3 //return this.replace(/^\s+|\s+$/g,""); //
4 return this.replace(/^\s+/g,"").replace(/\s+$/g,"");
5 }

JQuery1.4.2,Mootools 使用

1 function trim1(str){
2 return str.replace(/^(\s|\xA0)+|(\s|\xA0)+$/g, '');
3 }

JQuery1.4.3,Prototype 使用,该方式去掉g以稍稍提高性能 在小规模的处理字符串时性能较好

1 function trim2(str){
2 return str.replace(/^(\s|\u00A0)+/,'').replace(/(\s|\u00A0)+$/,'');
3 }

Steven Levithan 在进行性能测试后提出了在JS中执行速度最快的裁剪字符串方式,在处理长字符串时性能较好

01 function trim3(str){
02 str = str.replace(/^(\s|\u00A0)+/,'');
03 for(var i=str.length-1; i>=0; i--){
04 if(/\S/.test(str.charAt(i))){
05 str = str.substring(0, i+1);
06 break;
07 }
08 }
09 return str;
10 }

最后需要提到的是 ECMA-262(V5) 中给String添加了原生的trim方法(15.5.4.20)。此外Molliza Gecko 1.9.1引擎中还给String添加了trimLefttrimRight 方法。

[转载]SQL Server性能调优:资源管理之内存管理篇(下)

mikel阅读(958)

[转载]SQL Server性能调优:资源管理之内存管理篇(下) – hjq19851202 – 博客园.

在上篇文章SQL Server性能调优:资源管理之内存管理篇(上),介绍了SQL Server的内存管理的一些理论知识,这篇利用这些知识来解决现实中常见的一些问题。

一、数据页缓存压力的调优

前篇我们说过,如果用户访问的数据页面都缓存在内存里,这样的相应速度是最快的。 但是现实中,数据库的大小都是大于物理内存的,SQL Server不可能将用户需要的所有数据都缓存在内存中,当用户需要的数据不在内存中,将会发生Paging动作从硬盘中读取需要的数据,偶尔的 Paging不会从整体上影响SQL Server的性能,但如果Paging动作经常发生将会严重影响SQL Server整体性能。

当我们进行数据页缓存的调优时,第一步先是确定是否有数据页缓存的压力,第二步是确定数据页缓存页的压力是由哪里引起的,主要可以分成外部压力和内部压力。

1、是否有数据页缓存压力

确定是否有数据页缓存压力,主要可以从下面的一些内存性能计数器和sys.sysProcesses来确认。

SQL Server:Buffer Manager-Lazy Writes/Sec的值经常发生。

SQL Server:Buffer Manager-Page Life Expectancy的经常反复变化,始终升不上去。

SQL Server:Buffer Manager-Page Reads/Sec的值经常不为0。

从sys.sysprocesses这一系统视图的wait_type中能看到 ASYNC_IO_COMPLETION值,这一值代表的意思是“等待I/O操作的完成”,这通常代表内存不足发生了硬盘读写,也可能有人会说这是硬盘的 速度太慢导致的,只要换上速度快的硬盘就能解决这个问题了。确实换上速度快的硬盘能使SQL Server的响应速度提高一些,但是如果上面那三个计数器的值经常,那硬盘的问题就不是主要问题,它只是内存不够(因)导致的硬盘读写(果),根本原因 还是在内存上。

从上面的分析中,可以确认系统中存在数据页缓存压力,现在就来分析这一压力的来源,是外部压力还是内部压力。

2、压力的来源

1)外部压力

SQL Server:Buffer Manager-Total Server Memory的值是否变小了。如果变小了那就说明是,SQL Server的能使用的内存被系统或者外部程序给压缩了。这就是外部压力。

2)内部压力

SQL Server:Buffer Manager-Total Server Memory的值没什么变化,但是和SQL Server:Buffer Manager-Target Server Memory的大小基本相等。这就是SQL Server的数据页的内存需求已经等于了系统能提供的内存大小了。说明是数据库内部压力。

3、解决办法

1)外部压力

发生外部压力的大多数情形都是由于系统中还运行了其他的服务器软件,在它需要内存 的时候抢掉了SQL Server的内存。因此解决方案也就是将SQL Server运行在专门的服务器上。还有一种情形会导致外部压力的发生,那就是操作系统在占用大量内存的操作(比如备份),解决方案就是将这些操作方到 SQL Server运行压力小的时候(比如凌晨1、2点的时候)。

2)内部压力

a、找出读取数据页面最多的语句,对它进行调优。找出这些语句可以通过sys.dm_exec_query_status动态视图和sys.dm_exec_sql_text动态函数的关联查询。

— 物理读取页面最多的100条语句
SELECT TOP 100
qs.total_physical_reads,qs.execution_count,
qs.total_physical_reads /qs.execution_count as avg_io,
qt.text, db_name(qt.dbid) as dbname, qt.objectid
FROM sys.dm_exec_query_stats qs
cross apply sys.dm_exec_sql_text(qs.sql_handle) as qt
ORDER BY qs.total_physical_reads desc
GO

— 逻辑读取页面最多的100条语句
SELECT TOP 100
qs.total_logical_reads,qs.execution_count,
qs.total_logical_reads /qs.execution_count as avg_io,
qt.text, db_name(qt.dbid) as dbname
FROM sys.dm_exec_query_stats qs
cross apply sys.dm_exec_sql_text(qs.sql_handle) as qt
ORDER BY qs.total_logical_reads desc
GO

找出这些语句然后经可以用语句调优的方式来进行调优了。

b、如果你认为语句已经没有调优的空间了,或者像快速的提高服务器性能就只能增加物理内存了。

二、Buffer Pool中的Stolen Memory的压力调优

1、通过Memory Clerk的分析

由于Buffer Pool里的Stolen内存都是SQL Server自己申请的,所以在Memory Clerk的动态管理视图里可以查看。通过分析各Clerk的大小,基本就能判断Stolen内存压力的来源。常见的使用Stolen的内存较多的 Memory Clerk。

a)CACHESTORE_SQLCP:缓存动态TSQL语句的执行计划的地方。这通常和程序员的代码有关,如果程序员习惯使用动态TSQL语句,这部分的内存中缓存的执行计划就会非常大。解决方法就是使用存储过程或者参数话的TSQL。

b)OBJECTSTORE_LOCK_MANAGER:SQL Server里锁结构使用的内存。如果SQL Server中的阻塞严重的话,这部分内存的内存使用量会很大。解决方案就是解决阻塞问题了。

2、通过sys.sysprocesses里面的waittype字段进行分析

1)CMEMTHREAD(0X00B9)

当多个用户向同一缓存区中申请内存或者释放内存,在某一时刻只会有一个连接的操作 可以成功,其他的连接必须等待。这种情况比较少,主要是发生在哪些并发度非常高的系统中,而且通常都是在编译动态的TSQL语句。解决方案就是使用存储过 程或者参数化的TSQL语句,提高执行计划的重用。

2)RESOURCE_SEMAPHORE_QUERY_COMPLIE(0X011A)

当用户传送过的语句或者调用的存储过程过分复杂,SQL Server编译它所需要的内存会非常大。SQL Server为了防止过多的内存被用来做编译动作,所以设置了编译内存的上限。当有太多复杂的语句同时在编译,编译所需要的内存可能达到这个上限,这将有 部分语句将处于等待内存进行编译的状态,也就该waittype。

解决方法有:尽量多的使用存储过程或参数化的TSQL语句,简化每次需编译的语句复杂度,分成几个存储过程,实在不行的话可以考虑定期运行DBCC FREEPROCCACHE语句来手工清除缓存中的执行计划,保证stolen中内存量。

三、Multi-Page Memory压力调优

由于32位的SQL Server会在启动的时候分配好Multi-Page的大小而且比较小,默认是384MB,因此对于32位的SQL Server比较容易发生Multi-Page Memory的压力。该部分的压力主要可能由下面三种情形导致。

1、程序连接数据库时的Network Packet Size大小,如果设置成8KB或者更高的时候,而且连接又非常大时。对于32位的SQL Server该部分的内存使用量会很快达到上限。解决方法就是将程序中设置的Network Packet Size改成默认的4KB,或者升级到64位SQL Server,这样Multi-Page的大小就没有限制了。

2、程序员使用了很多复杂的TSQL语句或者存储过程,它的执行计划超过了 8KB,这将占用Multi-Page的空间。由于32位的SQL Server中该部分的大小比较小,它将很快被填满,而由于Buffer Pool很大没有压力,它将不会触发Lazy Writer,Mullti-Page中的执行计划将不会被清理。而这时如果用户需要申请Multi-Page Memory就必须等待。这会体现在sys.sysprocessed的waittype字段上,该值等于 SOS_RESERVEDMEMBLOCKLIST。解决方案:语句进行调整,将它的执行计划控制在8KB以内,如果不行的话可以考虑定期运行DBCC FREEPROCCACHE语句来手工清理执行计划,或者升级到64位SQL Server。

这篇写得很乱,大家凑合看吧。。。

[转载]SQL Server性能调优:资源管理之内存管理篇(上)

mikel阅读(972)

[转载]SQL Server性能调优:资源管理之内存管理篇(上) – hjq19851202 – 博客园.

SQL Server来说,最重要的资源是内存、Disk和CPU,其中内存又是重中之重,因为SQL Server为了性能要求,会将它所要访问的数据全部(只要内存足够)放到缓存中。这篇就来介绍SQL Server的内存管理体系。

SQL Server作为Windows上运行的应用程序,必须接受Windows的资源管理,利用Windows的API来申请和调度各类资源。但是,由于 Windows的资源管理体系,是为了满足大多数的应用程序所设计的,这对于SQL Server这种定位于企业级、支持多用户和高并发性的数据库应用程序来说不是很适合,为此SQL Server开发了自己的一套资源管理体系——SQLOS(SQL操作系统)。也就是说SQL Server的资源管理分两层,第一层是在Windows上,通过Windows的API来申请资源。第二层是在SQL Server上,利用SQLOS来决定如何使用从Windows那里申请来的资源。

一、操作系统层面的SQL Server内存管理

由于SQL server的内存是通过Windows的API来申 请的,如果Windows自己本身就缺少内存,SQL Server由于申请不到内存,性能自然受影响。因此做SQL Server的内存检测,第一步就是查看系统层面的内存,以确保系统本身不缺内存,这一步简单但是必不可少。这里先介绍Windows的一些内存管理理 念,然后介绍如何检查系统的内存情况。

1、Windows的一些内存术语

Virtual Address Space(虚拟地址空间):应用程序能够申请访问的最大地址空间。对于32位的服务器,地址寻址空间为2的32次方,也就是4GB,但是这4GB并不是 都给SQL Server使用的,默认情况下是用户态2GB,核心态2GB,所以说对于32位的系统SQL Server只有2GB的内存可供使用。不过可以通过设置/3GB boot.int参数,来调整系统的配置,使用户态为3GB,核心态为1GB。或者开启AWE(地址空间扩展),将寻址空间扩展为64GB,不过该设置有 缺陷,下面会分析。

Physical Memory(物理内存):也就是通常所说的电脑的内存大小。

Reserved Memory(保留地址):应用程序访问内存的方式之一,先保留(Reserve)一块内存地址空间,留着将来使用(SQL Server中的数据页面使用的内存就是通过这个方式申请  的)。被保留的地址空间,不能被其他程序访问,不然会出现访问越界的报错提示。

Committed Memory(提交内存):将保留(Reserve)的内存页面正式提交(Commit)使用。

Shared Memory(共享内存):对一个以上进程可见的内存。

Private Bytes(私有内存):某进程提交的地址空间中,非共享的部分。

Working Set:进程的地址空间中存放在物理内存中的部分。

Page Fault(页面访问错误):访问在虚拟地址空间,但不存在于Working Set中会发生Page Fault。这个又分两种情况,第一种是目标页面在硬盘上,这钟访问会带来硬盘读写,这种称为Hard Fault。另外一种是目标页面在物理内存中,但是不是该进程的Working Set下,Windows只需要重新定向一下,成为Soft Fault。由于Soft Hard不带来硬盘读写,对系统的性能影响很小,因此管理员关心的是Hard Fault。

System Working Set:Windows系统的Working Set。

2、Windows的内存检测

可以通过Windows的性能监视器来检测Windows的内存使用情况,如何使用性能监视器,可以看这篇文章《使用“性能监视器”监视系统性能/运行情况》 。在检测内存上,比较重要的计数器有下面一些:

分析Windows系统的内存总体使用情况的计数器

Memory:Available MBytes:系统中空闲的物理内存数。

Memory:Pages/Sec:由于Hard Page的发生,每秒钟从硬盘中读取或者写入的页面数。该计数器等于Memory:Pages Input/Sec与Memory:Pages Output/Sec之和。

分析Windows系统自身的内存使用情况的计数器:

Memory:Cache Bytes:系统的Working Set,也就是Windows系统使用的物理内存数。

对于每个进程的内存使用情况的计数器:

Process:Private Bytes:进程提交的地址空间中非共享的部分。

Process:Working Set:进程的地址空间中存放在物理内存中的那部分。

从这些计数器中,我们可以看到系统中是否还有空闲内存,哪个进程使用的内存最多,在发生问题的时候是否有内存使用量突变等情况。这为接下来分析SQL Server的使用提供一个前提条件。

二、SQL Server内部的内存管理

1、内存使用分类

按用途分类

1)Database cache(数据页面)。SQL Server中的页面都是以8KB为一个页面存储的。当SQL Server需要用到某个页面时,它会将该页面读到内存中,使用完后会缓存在内存中。在内存没有压力的情况下,SQL Server不会将页面从内存中删除。如果SQL Server感觉到内存的压力时,会将最长时间没有使用的页面从内存中删除来空出内存。

2)各类Consumer(功能组件)

Connection的连接信息

General:一组大杂烩。语句的编译、范式化、每个锁数据结构、事务上下文、表格和索引的元数据等

Query Plan:语句和存储过程的执行计划。和Database cache类似,SQL Server也会将执行计划缓存以供将来使用,减少编译时间。

Optimizer:生成执行计划的过程中消耗的内存。

Utilities:像BCP、Log Manager、Backup等比较特殊的操作消耗的内存。

3)线程内存:存放进程内每个线程的数据结构和相关信息消耗的内存,每个线程需0.5MB的内存。

4)第三方代码消耗的内存:SQL Server的进程里,会运行一些非SQL Server自身的代码。例如:用户定义的CLR或Extended Stored Procedure代码

按申请方式分类

1)预先Reserve一块大的内存,然后在使用的时候一块一块的Commit。Database Page是按这种方式申请的。

2)直接用Commit方式申请的内存,成为Stolen方式。除了Database Page之外其他内存基本都是按这种方式申请的。

按申请内存的大小分类

1)申请小于等于8KB为一个单位的内存,这些内存称为Buffer Pool

2)申请大于8KB为一个单位的内存,这些内存称为Multi-Page(或MemToLeave)

SQL Server对于Database Page都是采用先Reserved后Commit的方式申请的,而数据页都是以8KB为单位进行申请的。

对于Consumer中的内存申请,一般都是按Stolen方式申请的,且大多数的执行计划的大小都是小于8KB的,少数特别复杂的存储过程的执行计划会超过8KB,默认的连接的数据包是4KB,除非客户端特别设置了超过8KB(不建议)

第三方代码的内存申请一般是按Stolen方式申请的,个别比如CLR中可能会用Reserved/Commit的方式申请。

线程的内存每个都以0.5MB的方式申请,自然是放在MemToLeave中。

之所以花了这么大篇幅来讲SQL Server的内存分类,是因为SQL Server尤其是32位的SQL Server对不同种类的内存的申请大小是不一样的,对Commit、Stolen和MemTOLeave等类型的内存是有限制的。因此会出现系统中还有空闲内存,但是SQL Server不会申请使用的现象。

2、各部分内存的大小限制

1)32位的Windows

在SQL Server启动时,会预先分配好MemToLeave区域的大小。默认大小为256MB+256(SQL Server配置的允许最大线程数)* 0.5MB=384MB,因此Buffer Pool中的最大值为2GB-384MB=1.664G。如果使用了AWE技术,可以将系统的扩展地址空间达到64GB,但由于AWE扩展出来的地址只能用Reserved/Commit方式申请,为此MemToLeave的内存还是384MB,Buffer Pool中的Stolen的最大内存为1.664G,剩余的内存都可以为Database Page页面使用。

2)64位的Windows

32位的SQL Server。由于64位的操作系统,核心态不再占用32位进程的虚拟地址空间,因此MemToLeave的大小还是为384MB,Buffer Pool可以达到3.664G。如果还开启了AWE,这3.664GB可以全部用于Buffer Pool中的Stolen,剩余的内存都可以给Database Page页面使用。不过这种情况很少见,哪里用64位操作系统的机器装32位的哦-_- 。

64位的SQL Server。所有的内存都无限申请的,有需要就申请。

3、SQL Server内存使用情况的分析

一般来说有两种方式,第一种就是用来分析系统内存情况时使用的用性能计数器来分析,第二种是使用动态管理视图(DMV,只适用于SQL Server2005和2008)

1)SQL Server性能计数器

SQLServer:Memory Manager:Total Server Memory(KB):SQL Server缓冲区提交的内存。不是SQL Server总的使用内存,只是Buffer Pool中的大小。

SQLServer:Memory Manager:Target Server Memory(KB):服务器可供SQL Server使用的内存量。一般是由SQL Server能访问到的内存量和SQL Server的sp_Configure配置中的Max Server Memory值中的较小值算得。

SQLServer:Memory Manger:Memory Grants Pending:等待内存授权的进程总数。如果该值不为0,说明当前有用户的内存申请由于内存压力被延迟,这意味着比较严重的内存瓶颈。

SQLServer:Buffer Manager:Buffer Cache Hit Ratio:数据从缓冲区中找到而不需要从硬盘中去取的百分比。SQL Server在运行一段时间后,该比率的变化应该很小,而且都应该在98%以上,如果在95%以下,说明有内存不足的问题。

SQLServer:Buffer Manager:Lazy Writes/Sec:每秒钟被惰性编辑器(Lazy writer)写入的缓冲数。当SQL Server感觉到内存压力的时候,会将最久没有使用的数据页面和执行计划从缓冲池中清理掉,做这个动作的就是Lazy Writer。

Page Life Expectancy:页面不被引用后,在缓冲池中停留的秒数。在内存没有压力的情况下,页面会一直待在缓冲池中,Page Life Expectancy会维持在一个比较高的值,如果有内存压力时,Page Life Expectancy会下降。所以如果Page Life Expectancy不能维持在一个值上,就代表SQLServer有内存瓶颈。

SQLServer:Buffer Manager:Database Pages :就是Database Cache的大小。

SQLServer:Buffer Manager:Free Pages:SQL Server中空闲可用的大小。

SQLServer:Buffer Manager:Stolen Pages:Buffer Pool中Stolen的大小。

SQLServer:Buffer Manager:Total Pages:Buffer Pool的总大小(等于Database Pages+Free Pages+Stolen Pages)。该值乘以8KB,应该等于Memory Manager:Total Server Memory的值。

从上面这些计数器中我们就能了解SQL Server的内存使用情况,结合前面说的系统层的计数器大概能看出是否存在内存瓶颈。

2)内存动态管理视图

在SQL Server 2005以后,SQL Server的内存管理是使用Memory Clerk的方式统一管理。所有的SQL Server的内存的申请或释放,都需要通过它们的Clerk,SQL Server也通过这些Clerk的协调来满足不同需求。通过查询这些DMV,可以得到比用性能计数器更加详细的内存使用情况。

我们可以通过下面的查询语句来检测SQL Server的Clerk的内存使用情况。

使用sys.dm_os_memory_clerks查看内存使用情况

SELECT type, Clerk的类型
sum(virtual_memory_reserved_kb) as vm_Reserved_kb, 保留的内存
sum(virtual_memory_committed_kb) as vm_Committed_kb, 提交的内存
sum(awe_allocated_kb) as awe_Allocated_kb, 开启AWE后使用的内存
sum(shared_memory_reserved_kb) as sm_Reserved_kb, 共享的保留内存
sum(shared_memory_committed_kb) as sm_Committed_kb, 共享的提交内存
sum(single_pages_kb) as SinlgePage_kb, Buffer Pool中的Stolen的内存
sum(multi_pages_kb) as MultiPage_kb MemToLeave的内存
FROM sys.dm_os_memory_clerks
GROUP BY type
ORDER BY type

从上面的查询语句,我们可以算出前面提到的内存大小

Reserved/Commit = sum(virtual_memory_reserved_kb) / sum(virtual_memory_committed_kb)

Stolen = sum(single_pages_kb) + sum(multi_pages_kb)

Buffer Pool = sum(virtual_memory_committed_kb) + sum(single_pages_kb)

MemToLeave = sum(multi_pages_kb)

通过上面的介绍我们可以知道SQL Server总体和各部分内存的使用情况,如果我想知道数据页的缓存中到底缓存了哪些数据,这些数据是属于哪个数据库的哪个表中的呢?执行计划又是缓存了哪些语句的执行计划呢?这也可以通过DMV查看的到。

查看内存中的数据页面缓存的是哪个数据库的哪个表格的数据

declare @name nvarchar(100)
declare @cmd nvarchar(1000)
declare dbnames cursor for
select name from master.dbo.sysdatabases
open dbnames
fetch next from dbnames into @name
while @@fetch_status = 0
begin
set @cmd = select b.database_id, db=db_name(b.database_id),p.object_id,p.index_id,buffer_count=count(*) from
这里的object_id代表是SQL Server中的对象号,index_id代表是索引号,buffer_count代表的是页面数
+ @name + .sys.allocation_units a,
+ @name + .sys.dm_os_buffer_descriptors b, + @name + .sys.partitions p
where a.allocation_unit_id = b.allocation_unit_id
and a.container_id = p.hobt_id
and b.database_id = db_id(”’ + @name + ”’)
group by b.database_id,p.object_id, p.index_id
order by b.database_id, buffer_count desc
exec (@cmd)
fetch next from dbnames into @name
end
close dbnames
deallocate dbnames
go

根据上面取出来的@object_id找出是哪个数据库的哪个表
SELECT s.name AS table_schema, o.name as table_name 使用的就是table_schema.table_name表
FROM sys.sysobjects AS o INNER JOIN
sys.schemas AS s ON o.uid = s.schema_id
WHERE (o.id = @object_id)
根据上面取出来的@object_id和@index_id找出索引的名称
SELECT id, indid, name as index_name index_name就是索引的名称
FROM sys.sysindexes
WHERE (id = @object_id) AND (indid = @index_id)
根据上面取出来的表名table_schema.table_name和索引的名称index_name,还可以找出该索引是建立在哪些字段上的
EXEC sp_helpindex table_schema.table_name

查看内存中缓存的执行计划,以及执行计划对应的语句:

输出可能较大,请小心使用
SELECT usecounts, refcounts, size_in_bytes, cacheobjtype, objtype, text
FROM sys.dm_exec_cached_plans cp CROSS APPLY sys.dm_exec_sql_text(plan_handle)
ORDER BY objtype DESC

写了这么多竟然发现大多数讲的还是数据收集的这一部分,相应的解决办法还没有讲到。。。由于文章太长,具体的解决方法将在下一篇讲解,下一篇将从Database Page、Stolen和Multi-Page三部分的具体瓶颈来讲解。