Fork me on GitHub

2014年8月

Unable to start debugging on the web server 错误

今天在用Visual Studio调试Web程序时,突然出现下面这个错误:

Unable to start debugging on the web server. The Microsoft Visual Studio Remote Debugging Moniter (MSVSMON.EXE) does not appear to be running on the remote computer...

如下图所示:

msvsmon_err

起先以为是VS的问题,重启了好几次,但是点击调试按钮的时候仍然是这个提示信息。百思不得其解,因为这个项目下午的时候还可以调试,一直没有人碰过电脑,没改过代码也没装过软件,到晚上的时候就报错了。
不得己Google之,发现下面的解决方案:修改注册表中的DisableLoopbackCheck项,修改方法见参考链接。 重启电脑后问题依旧,最后努力思索从下午到晚上这段时间都做过哪些操作可能会导致影响。看错误信息,感觉是服务端口被占用之类的问题,但是这段时间确实没有安装或启动过什么服务。然后想到可能是配置文件的错误导致,继而突然意识到晚上在写代码之前,用SVN Update了下代码,查看SVN日志,发现有人提交了*.csproj.user文件,查看这个文件的变更。
老的配置文件如下:

<UseIIS>True</UseIIS>
<UseCustomServer>False</UseCustomServer>
<CustomServerUrl></CustomServerUrl>

修改后的代码如下:

<UseIIS>False</UseIIS> 
<UseCustomServer>True</UseCustomServer>
<CustomServerUrl>http://www.test.com/</CustomServerUrl>

原来是因为团队内有人不喜欢VS自带的http://localhost:4738/这个URL,便使用修改hosts文件的方式,将www.test.com指向127.0.0.1,并在VS的Web配置中选择了"Use Custom Web Server",这样URL确实好看了不少。 但是问题是:这个文件的提交直接导致了我浪费了一晚上的时间。
最后在VS的Web配置中改回默认的"Use IIS Express",问题解决(其实参考链接中也有人回答了该解决方法,只是我一开始没有注意到...),如下图所示:

web_server_config

教训总结:

  1. 任何一点小小的操作都可能导致意想不到的问题,要留心自己的每一个操作,并注意检查每个操作可能带来的影响;
  2. 绝对不要提交私人的配置文件到SVN服务器,譬如这里的*.csproj.user文件,应该加到ignore列表中。

参考

VS2010 error: Unable to start debugging on the web server

扫描二维码,在手机上阅读!

学习PHP的错误处理

在日常的PHP开发中,我们经常会遇到各种各样的PHP错误,有常见的语法错误,数据库错误,也有各种很难调试的奇怪问题。

一、PHP错误等级

在PHP中预定义了一些PHP错误常量,在Core.php文件中可以查看定义。这里是一份完整的列表:Predefined Constants

这些变量不仅可以在PHP文件中使用,也可以用在php.ini配置文件中。譬如在PHP中打开错误提示,可以使用下面的代码:

ini_set('display_errors','on'); 
error_reporting(E_ALL & ~E_NOTICE); 

这两句PHP代码对应的php.ini配置是:

display_errors = On 
error_reporting = E_ALL & ~E_NOTICE 

一般在生产环境不应该将错误信息打开,这样容易暴露服务器上的一些敏感信息,如网站路径,php文件函数,数据库连接等等。可以参考CodeIgniter的做法,在CI的入口文件index.php中有这样的错误处理代码:

/*
 *---------------------------------------------------------------
 * ERROR REPORTING
 *---------------------------------------------------------------
 *
 * Different environments will require different levels of error reporting.
 * By default development will show errors but testing and live will hide them.
 */

if (defined('ENVIRONMENT'))
{
    switch (ENVIRONMENT)
    {
        case 'development':
            error_reporting(E_ALL);
        break;
    
        case 'testing':
        case 'production':
            error_reporting(0);
        break;

        default:
            exit('The application environment is not set correctly.');
    }
}

在开发环境,需要打开错误提示,可以在这段代码的上面 define('ENVIRONMENT', 'development'),或者测试和生产环境定义testingproduction就可以了。

二、PHP异常处理

PHP也和其他编程语言一样,支持异常的处理,基本的语法如下:

try
{
    //可能出现错误或异常的代码
} 
catch(Exception $e)
{
    //对异常进行处理
    die( 'Exception: ' .$e->getMessage() );
}

当一个异常被抛出时,其后的代码将不会继续执行,PHP 会尝试查找匹配的 catch 代码块。如果一个异常没有被捕获,而且又没用使用set_exception_handler()作相应的处理的话,那么 PHP 将会产生一个严重的错误,并且输出未能捕获异常 "Uncaught Exception ..." 的提示信息。

三、顶层异常处理器 (Top Level Exception Handler)

使用set_error_handler可以自定义PHP的错误处理函数。下面是一个简单的例子:

<?php
//error handler function
function customError($errno, $errstr, $errfile, $errline)
{
    echo "<b>Custom error:</b> [$errno] $errstr<br />";
    echo " Error on line $errline in $errfile<br />";
    die();
}

//set error handler
set_error_handler("customError");

//trigger error
trigger_error("A custom error has been triggered");
?>

要注意的是,set_error_handler例程只能处理用户级错误,如变量未定义或通过trigger_error引发的错误等。如果需要处理其他错误譬如E_ERROR, E_PARSE, E_CORE_ERROR,一种解决方法是使用register_shutdown_function函数,这个函数可以让用户自定义PHP程序执行完成后执行的函数。如果PHP程序是出现错误导致的退出,可以通过error_get_last函数获取最后一次发生的错误。在CodeIgniter.php文件中可以看到使用set_error_handler自定义了错误提示信息,当CodeIgniter出错时页面上可以看到自定义的显示,该代码位于Common.php中的_exception_handlerExceptions.php中的show_php_error。我们修改CodeIgniter的代码,让其支持register_shutdown_function
首先在CodeIgniter.php中添加如下代码:

set_error_handler('_exception_handler');
register_shutdown_function('_shutdown_handler');

然后在Common.php中添加_shutdown_handler

/**
 * Shutdown Handler
 *
 * The following error types cannot be handled with a user defined function: 
 *         E_ERROR, E_PARSE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR, E_COMPILE_WARNING, 
 *         and most of E_STRICT raised in the file where set_error_handler() is called.
 * So, we use shutdown function to work around this situation.
 *
 * @access    private
 * @return    void
 */
if ( ! function_exists('_shutdown_handler'))
{
    function _shutdown_handler()
    {
        $lasterror = error_get_last();
        switch ($lasterror['type'])
        {
            case E_ERROR:
            case E_CORE_ERROR:
            case E_COMPILE_ERROR:
            case E_USER_ERROR:
            case E_RECOVERABLE_ERROR:
            case E_CORE_WARNING:
            case E_COMPILE_WARNING:
            case E_PARSE:
                $severity = $lasterror['type'];
                $message = $lasterror['message'];
                $filepath = $lasterror['file'];
                $line = $lasterror['line'];
                //header('Location: /ci/index.php/error');
        }
        
        $_error =& load_class('Exceptions', 'core');

        // Should we display the error? We'll get the current error_reporting
        // level and add its bits with the severity bits to find out.
        
        if (($severity & error_reporting()) == $severity)
        {
            $_error->show_php_error($severity, $message, $filepath, $line);
        }
        
        // Should we log the error?  No?  We're done...
        if (config_item('log_threshold') == 0)
        {
            return;
        }
        
        $_error->log_exception();
    }
}

注意:shutdown_handler可以捕获到E_PARSE,但是只能捕获到include的PHP文件里的语法错误。

四、PHP错误实例

熟悉常见的错误代码和返回信息可以快速诊断出代码中出现的问题。下面列出一些常见的PHP错误,及提示信息。

1. E_ERROR = 1

$b = undefined_func();  // 函数未定义 Fatal error: Call to undefined function func()
$a = new C(); // 类未定义 Fatal error: Class 'C' not found in
$a->undefined_func(); // 类的成员函数未定义 Fatal error: Call to undefined method C::undefined_func()
$a = 0;
for (;;) {  // 死循环 Fatal error: Maximum execution time of 300 seconds exceeded
    $a ++;
}

2. E_WARNING = 2

$a = 1;
$b = 0;
$c = $a / $b;  // 被零除 Warning: Division by zero

3. E_PARSE = 4

$b = 1  // 语法错误,少个逗号 Parse error: syntax error, unexpected $end
if (true)
{
    if (true)
    {
        if (false)
        {  // 语法错误,缺右括号
    }
}

4. E_NOTICE = 8

$b = $undefined_var;  // 变量未定义 Notice:Undefined variable: undefined_var

5. E_USER_ERROR/E_USER_WARNING/E_USER_NOTICE

trigger_error("This is an error.", E_USER_ERROR);  // User error

参考

  1. PHP异常处理详解
  2. PHP Error Handling
  3. PHP set_error_handler() 函数
  4. register_shutdown_function
  5. register_shutdown_function 函数详解
扫描二维码,在手机上阅读!

使用SVN的8个技巧

1. 使用SVN钩子强制提交注释

一个好的SVN实践是文件提交时要求必须填写注释,并注明相关修改信息,如bug号、任务描述等,内容按照约定编写。这样在后期的代码审核和回溯过程中会非常方便,可以更快的定位到具体代码的修改记录。

所谓SVN钩子就是一些与版本库事件发生时触发的程序,例如新修订版本的创建,或者是未版本化属性的修改。目前subversion提供了如下几种钩子:start-commitpre-commitpost-commitpre-unlockpost-unlockpre-lockpost-lockpre-revprop-changepost-revprop-change,其中我们修改post-commit脚本即可实现强制提交注释的功能。

如下是一个实现强制提交注释的post-commit脚本例子:

REPOS="$1"
TXN="$2"

# Make sure that the log message contains some text.
SVNLOOK=/usr/bin/svnlook
LOGMSG=`$SVNLOOK log -t "$TXN" "$REPOS" | wc -m`

if [ "$LOGMSG" -lt 4 ]
then
    echo "拜托,写点注释吧!" 1>&2
    exit 1
fi

exit 0

2. 创建本地仓库

有时候我们在没有网络的情况下无法连接外网上的SVN仓库,或者没有条件搭建SVN服务,这时我们同样可以使用SVN管理我们自己的代码。我们可以使用TortoiseSVN在本地创建代码仓库,并进行代码的版本管理。具体步骤如下:

  1. 新建一个空文件夹,用于存放本地的代码仓库;
  2. 在这个空文件夹上点击右键 -> TortoiseSVN -> Create Respository here,创建仓库;
  3. 在另一个目录Checkout,本地SVN路径格式类似于:file:///C:Repo

3. SVN命令行操作

在Windows系统中TortoiseSVN是进行SVN代码管理的最佳利器,操作也非常简单方便。但是在一些特殊环境下,熟悉SVN命令行操作也是必须的,譬如想在一些自动化脚本程序中使用SVN的功能。 这里总结一些常用命令如下,更多命令请访问后面的参考链接:

(1) 从版本库获取信息

  • svn help
  • svn info $url
  • svn list
  • svn log
  • svn diff

(2) 从版本库到本地

  • svn [co|checkout] $url $local
  • svn export $url $local
  • svn [up|update]

(3) 从本地到版本库

  • svn import $local $url -m "some comments..."
  • svn add $file
  • svn delete $file
  • svn mv $oldfile $newfile
  • svn rm $url
  • svn [ci|commit]
  • svn revert $file 和 svn revert -R $dir

4. 更换版本比较工具

在进行SVN提交时需要非常谨慎,每次提交之前应先做SVN更新或与资源库同步,要特别注意SVN关于冲突和错误的提示信息。对每个提交的文件进行多次检查,确认它们是不是你真正想要提交的。

在提交的文件上选择Compare with base可以将本地代码和版本库中的代码进行比较,确保修改的内容无误。TortoiseSVN默认使用自带的TortoiseMerge工具进行代码比较,也可以更换其他的代码比较工具:在TortoiseSVN -> Settings -> Diff Viewer选项中找到Configure the program used for comparing different revisions of files,选择External,然后选择你喜欢的比较工具,如:Beyond Compare。

5. SVN服务器迁移

这条其实并不算SVN技巧,但是在我们日常工作中确实经常会遇到这样的情况,想将一台SVN服务器上的仓库迁移到另一台服务器。如果SVN服务器是一台Windows服务器,可以直接将SVN仓库目录复制到新服务器上,然后在新服务器上重启SVN服务即可。如果SVN服务器是linux服务器,可以通过下面的命令操作,将一台服务器上的目录拷贝到另一台服务器。

$ scp -r root@x.x.x.x:/home/svn /home/svn
$ svnserve -d -r /home/svn

在TortoiseSVN客户端,需要更新SVN地址:右键 -> TortoiseSVN -> Relocate...

6. svn:ignore

我们在用SVN提交代码时,常常有一些文件未版本化并且也不想提交,所以在提交时根本不想看到这些文件,譬如类似于Visual Studio工程的bin obj目录。

可以使用 svn propset svn:ignore 命令来将某个文件或目录添加到忽略列表中。可以在下面的链接中找到一些常见的ignore文件:Best general SVN Ignore Pattern?

7. SVN目录结构

Subversion有一个标准的目录结构,如下所示:

svn://projects/
|
+- trunck
+- branches
+- tags

其中,trunk为主开发目录,branches为分支开发目录,tags为tag存档目录(不允许修改)。这几个目录具体怎么使用,svn并没有明确规范,一般有两种方式:trunk作为主开发目录或者trunk作为发布目录。

8. SVN使用原则

  • 代码变更及时提交,避免本地修改后无法修复;
  • 提交前确认代码可编译通过,保证新增的文件同时被提交;
  • 不要将格式修正和代码修改混合提交。修正格式包括增加缩进、减少空格等,如果把这些和代码修改一起提交,很难从日志信息中发现代码修正记录;
  • 每次提交尽量是一个最小粒度的修改,如果一次提交涉及到两个完全不同的功能,那么分两次提交,并在注释中写清楚提交内容;
  • 所有代码文件使用UTF-8格式;
  • 提交的文件必须是开发者公用的程序文件,不要提交私人测试程序、程序缓存、图片缓存、自动生成的文件等。

参考

  1. SVN之使用原则
  2. svn强制写日志和可以修改历史日志(svn钩子的应用)
  3. SVN搭建本地文件版本管理
  4. SVN 命令参考
  5. 关于SVN 目录结构
扫描二维码,在手机上阅读!

如何在SQL SERVER 2008中修改主键Id的值

今天遇到一个客户的奇葩需求,需要修改主键Id的值。由于之前测试时有些Id较小的数据被删除掉了,所以数据的编号不是连续的,如下(例子做了简化),客户需要将T3、T4的Id修改为3、4:

    Id    Name
    1     T1
    2     T2
    ...   //测试数据被删
    101   T3
    102   T4

由于Id字段设置了主键自增标识,无法直接UPDATE,所以首先想到的是怎么把Id字段的自增标识删除掉,更新完了以后再重新设置回来。在网上搜索获知,可以通过更新系统表syscolumnscolstat值来达到这个目的。但是要想修改syscolumns这个系统表,必须将数据库设置成允许修改系统表。此操作可以在SQL Server Enterprise Manager里面选择数据库服务器,按右键,选择“属性”,在“服务器设置”页面中将“允许对系统目录直接修改”一项选中。也可以使用如下语句来实现:

use master
go
sp_configure 'allow updates', 1
go 
reconfigure with override
go 

根据这个提示很快写出下面的更新代码:

sp_configure 'allow updates', 1
go
reconfigure with override
go
update syscolumns set colstat = 0 where id = OBJECT_ID('Test') and colstat = 1
go
update dbo.Test set Id = 3 where Id = 101
update dbo.Test set Id = 4 where Id = 102
go
update syscolumns set colstat = 1 where id = OBJECT_ID('Test') and name = 'Id'
go
dbcc checkident ('Test', RESEED, 4)
go
sp_configure 'allow updates', 0
go
reconfigure with override
go

事情总不是那么顺利,在数据库中执行后提示错误信息: Ad hoc updates to system catalogs are not allowed. 中文错误信息是: 不允许对系统目录进行即席更新

继续研究发现,原来这个方法已经是很古老的技巧了。从SQL SERVER 2005和2008开始已经不再支持直接对系统表进行修改了。针对这种情况,使用笨方法,既然不能直接更新,那就只能删掉然后新增了。新增记录时,SQL SERVER刚好可以通过打开identity_insert插入指定的Id值。 修改后的SQL语句如下:

dbcc checkident ('Test', RESEED, 0)
GO
set identity_insert dbo.Test on
GO
insert into dbo.Test(Id, Name) values(3, N'T3')
insert into dbo.Test(Id, Name) values(4, N'T4')
delete from dbo.Test where Id = 101
delete from dbo.Test where Id = 102
GO
set identity_insert dbo.Test off
GO
dbcc checkident ('Test', RESEED, 4)
GO

这样,我们就变相的实现了更新Id字段的目的。 注意:这种方法只限于要修改的记录和其他表没有任何关联,如果涉及到外键约束,还需要更新所有的关联表,这就比较棘手了。不知道还有没有其他的解决方法,还望不吝赐教!

参考:

  1. 数据库置疑问题解决
  2. SQL2008如何解决"不允许对系统目录进行即席更新"?
扫描二维码,在手机上阅读!

Windows+IIS+ASP.NET+SQL SERVER 出现50x错误的解决思路

最近一段时间服务器老是出现500、502或者503错误,由于数据量大了,访问用户增多,这类错误越来越影响正常业务。花了无数的时间排查问题,现做简单总结如下。如果你有新的建议,我会后续补上。 首先需要明确的是出现这类问题肯定是服务器错误,有可能是程序问题,也有可能是系统问题,要根据实际情况进行排查。在我遇到的情形中,我是按下列步骤进行排查的:

1. 首先打开任务管理器,查看内存占用

如果内存占用始终在95%上下,有可能是服务器上某个程序太耗内存导致内存不足。内存问题的排查情况比较多,可能是网站程序有内存泄露,也有可能是某个操作非常吃内存。
在我的服务器上,出现的问题是任务管理器里所有进程占用内存都不多,但是显示内存占用100%,在SQL SERVER中执行下面的SQL,确定是数据库缓存的问题。重启数据库,并为数据库设置最大占用内存可以解决这个问题。

select counter_name, ltrim(cntr_value*1.0/1024/1024)+'G' as memoryGB
from master.sys.dm_os_performance_counters
where counter_name like '%target%server%memory%'
    or counter_name like '%total%memory%'

-- Target Server Memory (KB) 服务器能够使用的动态内存总量。
-- Total Server Memory (KB) 从缓冲池提交的内存 (KB)。注意:这不是 SQL Server 使用的总内存。

2. 查看CPU占用

CPU占用100%同样可能会导致服务器出现50x错误,CPU问题的排查和内存问题一样,需要根据具体进程进行排查。在我的一次排查中,发现sqlservr.exe进程一直占用95%以上的CPU。进入SQL SERVER执行sp_who可以看到连接数很多,而且大多数状态都是suspended。使用下面的SQL语句可以查看当前正在执行的具体SQL。

SELECT
    [Spid] = session_Id,
    ecid,
    [Database] = DB_NAME(sp.dbid),
    [User] = nt_username,
    [Status] = er.status,
    [Wait] = wait_type,
    [Individual Query] = SUBSTRING(qt.text, er.statement_start_offset / 2,
        (CASE WHEN er.statement_end_offset = - 1
              THEN LEN(CONVERT(NVARCHAR(MAX), qt.text)) * 2
              ELSE er.statement_end_offset
         END - er.statement_start_offset) / 2),
    [Parent Query] = qt.text,
    Program = program_name,
    Hostname,
    nt_domain,
    start_time
FROM sys.dm_exec_requests er
INNER JOIN  sys.sysprocesses sp ON er.session_id = sp.spid
CROSS APPLY sys.dm_exec_sql_text(er.sql_handle) AS qt
WHERE session_Id > 50 /* Ignore system spids.*/ AND session_Id NOT IN (@@SPID)

结果显示正在执行很多相同的SQL,而且都是suspended状态。定位到具体的SQL语句,然后就是优化工作了,为该SQL添加索引后问题解决。

3. 检查IIS应用程序池是否正常运行

有时候IIS的应用程序池会异常退出,也会导致50x错误。应用程序池的退出很大一部分原因都是由于上面两个原因导致,要么是内存爆掉了,要么是CPU爆掉了,从而导致应用程序池崩溃这样的连锁反应。进入IIS->应用程序池,启动相应的程序池问题解决。

4. 检查数据库死锁

通过下面的SQL可以查看设置的死锁超时时间,如果LOCK_TIMEOUT值是-1,意味着如果遇到死锁则进程一直等待资源释放,这样就可能导致50x问题。可以将死锁的超时时间设置为3秒,则可以解决这类问题。具体的死锁原因还需要进一步分析。

SELECT @@LOCK_TIMEOUT
SET LOCK_TIMEOUT 3000

注意:LOCK_TIMEOUT值是程序级的设置(application-level setting),只对当前连接有效。在连接打开后,可以使用SET LOCK_TIMEOUT设置死锁超时时间。不能设置成全局的,如果想设置全局,可以使用CommandTimeout值代替。

参考链接:

1. 查询sqlserver 正在执行的sql语句的详细信息
2.SQL Server 性能调优(cpu)
3. 快速搞懂 SQL Server 的锁定和阻塞
4. SQL Server性能调优:资源管理之内存管理篇(上)

扫描二维码,在手机上阅读!