2008年9月22日

原文地址:http://www.cnblogs.com/yizhu2000/archive/2007/12/04/982142.html

本人作为一位web工程师,着眼最多之处莫过于 性能与架构,本次幸得参与sd2.0大会,得以与同行广泛交流,于此二方面,有些心得,不敢独享,与众博友分享,本文是这次参会与众同撩交流的心得,有兴趣者可以查看视频

架构设计的几个心得:


一,不要过设计:never over design

这是一个常常被提及的话题,但是只要想想你的架构里有多少功能是根本没有用到,或者最后废弃的,就能明白其重要性了,初涉架构设计,往往倾向于设计大而化一的架构,希望设计出具有无比扩展性,能适应一切需求的增加架构,web开发领域是个非常动态的过程,我们很难预测下个星期的变化,而又需要对变化做出最快最有效的响应。。

ebay的工程师说过,他们的架构设计从来都不能满足系统的增长,所以他们的系统永远都在推翻重做。请注意,不是ebay架构师的能力有问题,他们设计的架构总是建立旧版本的瓶颈上,希望通过新的架构带来突破,然而新架构带来的突破总是在很短的时间内就被新增需求淹没,于是他们不得不又使用新的架构
web开发,是个非常敏捷的过程,变化随时都在产生,用户需求千变万化,许多方面偶然性非常高,较之软件开发,希望用一个架构规划以后的所有设计,是不现实的

二,web架构生命周期:web architecture‘s life cycle


既然要杜绝过设计,又要保证一定的前瞻性,那么怎么才能找到其中的平衡呢?希望下面的web架构生命周期能够帮到你

architecture_life_cycle

所设计的架构需要在1-10倍的增长下,通过简单的增加硬件容量就能够胜任,而在5-10倍的增长期间,请着手下一个版本的架构设计,使之能承受下一个10倍间的增长

google之所以能够称霸,不完全是因为搜索技术和排序技术有多先进,其实包括baidu和yahoo,所使用的技术现在也已经大同小异,然而,google能在一个月内通过增加上万台服务器来达到足够系统容量的能力确是很难被复制的


三,缓存:Cache


空间换取时间,缓存永远计算机设计的重中之重,从cpu到io,到处都可以看到缓存的身影,web架构设计重,缓存设计必不可少,关于怎样设计合理的缓存,jbosscache的创始人,淘宝的创始人是这样说的:其实设计web缓存和企业级缓存是非常不同的,企业级缓存偏重于逻辑,而web缓存,简单快速为好。。

缓存带来的问题是什么?是程序的复杂度上升,因为数据散布在多个进程,所以同步就是一个麻烦的问题,加上集群,复杂度会进一步提高,在实际运用中,采用怎样的同步策略常常需要和业务绑定

老钱为搜狐设计的帖子设计了链表缓存,这样既可以满足灵活插入的需要,又能够快速阅读,而其他一些大型社区也经常采用类此的结构来优化帖子列表,memcache也是一个常常用到的工具

钱宏武谈架构设计视频 http://211.100.26.82/CSDN_Live/140/qhw.flv

Cache的常用的策略是:让数据在内存中,而不是在比较耗时的磁盘上。从这个角度讲,mysql提供的heap引擎(存储方式)也是一个值得思考的方法,这种存储方法可以把数据存储在内存中,并且保留sql强大的查询能力,是不是一举两得呢?

我们这里只说到了读缓存,其实还有一种写缓存,在以内容为主的社区里比较少用到,因为这样的社区最主要需要解决的问题是读问题,但是在处理能力低于请求能力时,或者单个希望请求先被缓存形成块,然后批量处理时,写缓存就出现了,在交互性很强的社区设计里我们很容易找到这样的缓存

四,核心模块一定要自己开发:DIY your core module


这点我们是深有体会,钱宏武和云风也都有谈到,我们经常倾向于使用一些开源模块,如果不涉及核心模块,确实是可以的,如果涉及,那么就要小心了,因为当访问量达到一定的程度,这些模块往往都有这样那样的问题,当然我们可以把问题归结为对开源的模块不熟悉,但是不管怎样,核心出现问题的时候,不能完全掌握其代码是非常可怕的


五,合理选择数据存储方式:reasonable data storage


我们一定要使用数据库吗,不一定,雷鸣告诉我们搜索不一定需要数据库,云风告诉我们,游戏不一定需要数据库,那么什么时候我们才需要数据库呢,为什么不干脆用文件来代替他呢?
首先我们需要先承认,数据库也是对文件进行操作。我们需要数据库,主要是使用下面这几个功能,一个是数据存储,一个是数据检索,在关系数据库中,我们其实非常在乎数据库的复杂搜索的能力,看看一个统计用的tsql就知道了(不用仔细读,扫一眼就可以了)

select   c.Class_name,d.Class_name_2,a.Creativity_Title,b.User_name,(select   count(Id)   from   review   where   Reviewid=a.Id)   as   countNum   from   Creativity   as   a,User_info   as   b,class   as   c,class2   as   d   where   a.user_id=b.id   and   a.Creativity_Class=c.Id   and   a.Creativity_Class_2=d.Id
select   a.Id,max(c.Class_name),(max(d.Class_name_2),max(a.Creativity_Title),max(b.User_name),count(e.Id)   as   countNum   from   Creativity   as   a,User_info   as   b,class   as   c,class2   as   d,review   as   e   where   a.user_id=b.id   and   a.Creativity_Class=c.Id   and   a.Creativity_Class_2=d.Id   and   a.Id=e.Reviewid   group   by   a.Id ..............................................

我们可以看出需要数据库关联,排序的能力,这个能力在某些情况下非常重要,但是如果你的网站的常规操作,全是这样复杂的逻辑,那效率一定是非常低的,所以我们常常在数据库里加入许多冗余字段,来减小简单查询时关联等操作带来的压力,我们看看下面这张图,可以看到数据库的设计重心,和网站(指内容型社区)需要面对的问题实际是有一些偏差的

database

同样其他一些软件产品也遇到同样的问题所以具我了解,有许多特殊的运用都有自己设计的特殊数据存储结构与方法,比如有的大型服务程序采取树形数据存储结构,lucene使用文件来存储索引和文件

从另外一个角度上看,使用数据库,意味着数据和表现是完全分离的(这当然是经典的设计思路),也就是说当需要展示数据时,不得不需要一个转换的过程,也可以说是绑定的过程,当网站具备一定规模的时候,数据库往往成为效率的瓶颈,所以许多网站也采用直接书写静态文件的方法来避免读取操作时的绑定

这并不是说我们从今天起就可以把我们亲爱的数据库打入冷宫,而是我们在设计数据的持久化时,需要根据实际情况来选择存储方式,而数据库不过是其中一个选项


六,搞清楚谁是最重要的人:who's the most important guy


在用例需求分析的时候常常讲到涉众,就是和你的设计息息相关的人,在web中我们一定以为最重要的涉众莫过于用户了。,在一个传统的互动社区开发中,最重要的东西是内容,用户产生内容,所以用户就是上帝,至于内容挑选工具,不就是给坐我后面三排的妹妹们用的吗?凑或行了,实在有问题我就在数据里手动帮你加得了。。这大概是眼下许多小型甚至中型网站技术人员的普遍想法。钱宏武在他的讲座里谈到了这个问题:实际上网站每天产生的内容非常的多,普通人是不可能看完的,而编辑负责把精华的内容推荐到首页上,所以很多用户读到的内容其实都依赖于编辑的推荐,所以设计让编辑工作方便的工具也是非常重要,有时甚至是最重要的。


七,不要执着于文档:don't be crazy about document


web开发的文档重要吗?什么文档最重要?我的看法是web开发中交流>文档,

现在大的软件公司比较流行的做法是:
注重产品设计文档,在这种方法里,产品文档非常详尽,并且没有歧义,开发人员基于设计文档开发,测试人员基于设计文档制定测试方案,任何新人都可以通过阅读产品设计文档来了解项目的概况

而web项目从概念到实现的时间是非常短的,而且越短越好,并且由于变化迅速,要想写出完整的产品和需求文档是几乎不可能的,大多数情况是等你写出完备的文档,项目早就是另外一个样子,但是没有文档的问题是,如果团队发生变化,添加新成员怎样才能了解软件的结构和概念呢,一种是每个人都了解软件的整个结构,除非你的团队整体消失,否则任何一个人都能够担当培养新人的责任,这种face2face交流比文档有效率很多。

于是就有了前office开发者,现任yahoo中国某产品开发负责人的刘振飞所感觉到的落差,他说,我们的项目是吵出来的,我听完会心一笑


八,团队:team


不要专家团队,而要外科手术式的团队,你的团队里一定要有清道夫,需要有弓箭手,让他们和项目一起成长,才是项目负责人的最大成就

 

总结:

0)架构是一种权衡

architecture

1)web开发的特点是是:没有太复杂的技术难点,一切在于迅速的把握需求,其实这正式敏捷开发的要旨所在,一切都可以非常快速的建立,非常快速的重构,我们的开发工具,底层库和框架,包括搜索引擎和web文档提供的帮助,都提我们供给了敏捷的能力。

2)此外,相应的,最有效率的交流方式必须留给web开发,那就是face2face(面对面),不要太担心你的设计不能被完备的文档所保留下来,他们会以交流,代码和小卡片的方式保存下来

3)人的因素会更加重要,无论是对用户的需求,还是开发人员的素质。

 

另:有关web效率,有著名的14条规则,由yahoo性能效率小组所总结,并广为流传。业已出现相关插件(YSlow),针对具体网页按彼规则评分,这次该小组负责人Tenni Theurer也受邀来到此次大会,我把Tenni小姐(之前真的没有想到她是个女孩,并且如此年轻)和她的团队的14 rules列在下面

  • Make Fewer HTTP Requests
  • Use a Content Delivery Network
  • Add an Expires Header
  • Gzip Components
  • Put CSS at the Top
  • Move Scripts to the Bottom
  • Avoid CSS Expressions
  • Make JavaScript and CSS External
  • Reduce DNS Lookups
  • Minify JavaScript
  • Avoid Redirects
  • Remove Duplicate Scripts
  • Configure ETags
  • Make Ajax Cacheable

     

    通过安装firebugYSlow这两个firefox插件(请注意要先安装firebug再安装yslow,下载后拖动到firefox里即可)我们可以看到你的网页根据下面的规则的评分,这是我在博客园博客首页的评分截图,上面D表示总分,下面是单项评分,A最好F最差,不知道还有没有G :)

    YSlow

     

    相关连接

    yahoo性能团队:http://developer.yahoo.com/performance/

     

  • posted @ 2008-09-22 10:59 小No 阅读(21) | 评论 (0)编辑

    2008年9月4日

    转发这篇文章主要是原作者BLOG的主题的字体太小,看得不太舒服~~

     

    对于一个真正的企业级的应用来说,Caching肯定是一个不得不考虑的因素,合理、有效地利用Caching对于 增强应用的Performance(减少对基于Persistent storage的IO操作)、Scalability(将数据进行缓存,减轻了对Database等资源的压力)和Availability(将数据进行 缓存,可以应对一定时间内的网络问题、Web Service不可访问问题、Database的崩溃问题等等)。Enterprise Library的Caching Application Block为我们提供了一个易用的、可扩展的实现Caching的框架。借助于Caching Application Block,Administrator和Developer很容易实现基于Caching的管理和编程。由于Caching的本质在于将相对稳定的数据 常驻内存,以避免对Persistent storage的IO操作的IO操作,所以有两个棘手的问题:Load Balance问题;Persistent storage和内存中数据同步的问题。本篇文章提供了一个解决方案通过SqlDependency实现SQL Server中的数据和Cache同步的问题。

    一、Cache Item的过期策略

    在 默认的情况下,通过CAB(以下对Caching Application Block的简称,注意不是Composite UI Application Block )的CacheManager加入的cache item是永不过期的;但是CacheManager允许你在添加cache item的时候通过一个ICacheItemExpiration对象应用不同的过期策略。CAB定了一个以下一些class实现了 ICacheItemExpiration,以提供不同的过期方式:

    • AbsoluteTime:为cache item设置一个cache item的绝对过期时间。
    • ExtendedFormatTime:通过一个表达式实现这样的过期策略:每分钟过期(*****:5个*分别代表minute、hour、date、month、year);每个小时的第5分钟过期(5****);每个月的2号零点零分过期(0 0 2 * *)。
    • FileDependency:将cache item和一个file进行绑定,通过检测file的最后更新时间确定file自cache item被添加以来是否进行过更新。如果file已经更新则cache item过期。
    • NeverExpired:永不过期。
    • SlidingTime:一个滑动的时间,cache item的每次获取都将生命周期延长到设定的时间端,当cache item最后一次获取的时间算起,超出设定的时间,则cache item过期。

    对 于过期的cache item,会及时地被清理。所以要实现我们开篇提出的要求:实现Sql Server中的数据和Cache中的数据实现同步,我们可以通过创建基于Sql Server数据变化的cache item的过期策略。换句话说,和FileDependency,当Persistent storage(Database)的数据变化本检测到之后,对于得cache自动过期。但是,对于文件的修改和删除,我们和容易通过文件的最后更新日期 或者是否存在来确定。对于Database中Table数据的变化的探测就不是那么简单了。不过SQL Server提供了一个SqlDependency的组建帮助我们很容易地实现了这样的功能。

    二、创建基于SqlDependency的ICacheItemExpiration

    SqlDependency 是建立在SQL Server 2005的Service Broker之上。SqlDependency向SQL Server订阅一个Query Notification。当SQL Server检测到基于该Query的数据发生变化,向SqlDependency发送一个Notification,并触发SqlDependency 的Changed事件,我们就可以通过改事件判断对应的cache item是否应该过期。

    我们现在就来创建这样的一个ICacheItemExpiration。我们先看看ICacheItemExpiration的的定义:

    public interface ICacheItemExpiration
    {
        // Methods
        bool HasExpired();
        void Initialize(CacheItem owningCacheItem);
        void Notify();
    }

    而 判断过期的依据就是根据HasExpired方法,我们自定义的CacheItemExpiration就是实现了该方法,根据 SqlDependency判断cache item是否过期。下面是SqlDependencyExpiration的定义(注:SqlDependencyExpiration的实现通过 Enterprise Library DAAB实现DA操作):

    namespace Artech.SqlDependencyCaching
    {
        public class SqlDependencyExpiration : ICacheItemExpiration
        {
            private static readonly CommandType DefaultComamndType = CommandType.StoredProcedure;

            public event EventHandler Expired;

            public bool HasChanged
            { get; set; }

            public string ConnectionName
            { get; set; }

            public SqlDependencyExpiration(string commandText, IDictionary<string, object> parameters) :
                this(commandText, DefaultComamndType, string.Empty, parameters)
            { }

            public SqlDependencyExpiration(string commandText, string connectionStringName, IDictionary<string, object> parameters) :
                this(commandText, DefaultComamndType, connectionStringName, parameters)
            { }

            public SqlDependencyExpiration(string commandText, CommandType commandType, IDictionary<string, object> parameters) :
                this(commandText, commandType, string.Empty, parameters)
            { }

            public SqlDependencyExpiration(string commandText, CommandType commandType, string connectionStringName, IDictionary<string, object> parameters)
            {
                if (string.IsNullOrEmpty(connectionStringName))
                {
                    this.ConnectionName = DatabaseSettings.GetDatabaseSettings(ConfigurationSourceFactory.Create()).DefaultDatabase;
                }
                else
                {
                    this.ConnectionName = connectionStringName;
                }

                SqlDependency.Start(ConfigurationManager.ConnectionStrings[this.ConnectionName].ConnectionString);
                using (SqlConnection sqlConnection = DatabaseFactory.CreateDatabase(this.ConnectionName).CreateConnection() as SqlConnection)
                {
                    SqlCommand command = new SqlCommand(commandText, sqlConnection);
                    command.CommandType = commandType;
                    if (parameters != null)
                    {
                        this.AddParameters(command, parameters);
                    }
                 SqlDependency dependency = new SqlDependency(command);
                    dependency.OnChange += delegate
                    {
                        this.HasChanged = true;
                        if (this.Expired != null)
                        {
                            this.Expired(this, new EventArgs());
                        }
                    };
                    if (sqlConnection.State != ConnectionState.Open)
                    {
                        sqlConnection.Open();
                    }
                    command.ExecuteNonQuery();
                }
            }

            private void AddParameters(SqlCommand command, IDictionary<string, object> parameters)
            {
                command.Parameters.Clear();
                foreach (var parameter in parameters)
                {
                    string parameterName = parameter.Key;
                    if (!parameter.Key.StartsWith("@"))
                    {
                        parameterName = "@" + parameterName;
                    }

                    command.Parameters.Add(new SqlParameter(parameterName, parameter.Value));
                }
            }

            #region ICacheItemExpiration Members

            public bool HasExpired()
            {
                bool indicator = this.HasChanged;
                this.HasChanged = false;
                return indicator;
            }

            public void Initialize(CacheItem owningCacheItem)
            {         }

            public void Notify()
            {         }

            #endregion
        }
    }

    我们来简单分析一下实现过程,先看看Property定义:

    private static readonly CommandType DefaultComamndType = CommandType.StoredProcedure;

    public event EventHandler Expired;

    public bool HasChanged
    { get; set; }

    public string ConnectionName
    { get; set; }

    通 过DefaultComamndType 定义了默认的CommandType,在这了我默认使用Stored Procedure;Expired event将在cache item过期时触发;HasChanged代表Database的数据是否被更新,将作为cache过期的依据;ConnectionName代表的是 Connection string的名称。

    为了使用上的方便,我定义了4个重载的构造函 数,最后的实现定义在public SqlDependencyExpiration(string commandText, CommandType commandType, string connectionStringName, IDictionary<string, object> parameters)。parameters代表commandText的参数列表,key为参数名称,value为参数的值。首先获得真正的 connection string name(如果参数connectionStringName为空,就使用DAAB默认的connection string)

    if (string.IsNullOrEmpty(connectionStringName))
    {
         this.ConnectionName = DatabaseSettings.GetDatabaseSettings(ConfigurationSourceFactory.Create()).DefaultDatabase;
    }
    else
    {
         this.ConnectionName = connectionStringName;
    }

    然 后通过调用SqlDependency.Start()方法,并传入connection string作为参数。该方法将创建一个Listener用于监听connection string代表的database instance发送过来的query notifucation。

    SqlDependency.Start(ConfigurationManager.ConnectionStrings[this.ConnectionName].ConnectionString);

    然 后创建SqlConnection,并根据CommandText和CommandType参数创建SqlCommand对象,并将参数加入到 command的参数列表中。最后将这个SqlCommand对象作为参数创建SqlDependency 对象,并注册该对象的OnChange 事件(对HasChanged 赋值;并触发Expired事件)。这样当我们执行该Cmmand之后,当基于commandtext的select sql语句获取的数据在database中发生变化(添加、更新和删除),SqlDependency 的OnChange 将会触发

    SqlDependency dependency = new SqlDependency(command);
    dependency.OnChange += delegate
    {
          this.HasChanged = true;
           if (this.Expired != null)
           {
                  this.Expired(this, new EventArgs());

           }
            
    };
    这样在HasExpired方法中,就可以根据HasChanged 属性判断cache item是否应该过期了。

    public bool HasExpired()
    {
         bool indicator = this.HasChanged;
         this.HasChanged = false;
         return indicator;
    }

    三、如何应用SqlDependencyExpiration

    我 们现在创建一个简单的Windows Application来模拟使用我们创建的SqlDependencyExpiration。我们模拟一个简单的场景:假设我们有一个功能需要向系统所 有的user发送通知,而且不同的user,通知是不一样的,由于通知的更新的频率不是很高,我们需要讲某个User的通知进行缓存。

    这是我们的表结构:Messages

    image

    我们通过下面的SP来获取基于某个User 的Message:

    ALTER PROCEDURE [dbo].[Message_Select_By_User]
    (@UserID    VarChar(50))
    AS
    BEGIN   
        Select ID, UserID, [Message] From dbo.Messages Where UserID = @UserID
    END

    注:如何写成Select * From dbo.Messages Where UserID = @UserID, SqlDependency 将不能正常运行;同

    时Table的schema(dbo)也是必须的。

    我 们设计如下的界面来模拟:通过Add按钮,可以为选择的User创建新的Message,而下面的List将显示基于某个User(Foo)的 Message List。该列表的获取方式基于Lazy Loading的方式,如果在Cache中,则直接从Cache中获取,否则从Db中获取,并将获取的数据加入cache。

    image

    我们先定义了3个常量,分别表示:缓存message针对的User,获取Message list的stored procedure名称和Cache item的key。

    private const string UserName = "Foo";
    private const string MessageCachingProcedure = "Message_Select_By_User";
    private const string CacheKey = "__MessageOfFoo";

    我们通过一个Property来创建或获取我们的上面定义的SqlDependencyExpiration 对象

    private SqlDependencyExpiration CacheItemExpiration
    {
        get
        {
            IDictionary<string, object> parameters = new Dictionary<string, object>();
            parameters.Add("UserID", UserName);
            SqlDependencyExpiration expiration= new SqlDependencyExpiration(MessageCachingProcedure, parameters);
            expiration.Expired += delegate
            {
                MessageBox.Show("Cache has expired!");
            };

            return expiration;
        }
    }

    通过GetMessageByUser从数据库中获取基于某个User的Message List(使用了DAAB):

    private List<string> GetMessageByUser(string userName)
    {
        List<string> messageList = new List<string>();
        Database db = DatabaseFactory.CreateDatabase();
        DbCommand command = db.GetStoredProcCommand(MessageCachingProcedure);
        db.AddInParameter(command, "UserID", DbType.String, userName);
        IDataReader reader = db.ExecuteReader(command);
        while (reader.Read())
        {
            messageList.Add(reader.GetString(2));
        }

        return messageList;
    }

    通 过GetMessages获取User(Foo)的Message List:首先通过CacheManager检测message list是否存在于Cache,如何不存在,调用上面的GetMessageByUser方法从database中获取Foo的message list。并将其加入Cache中,需要注意的是这里使用到了我们的SqlDependencyExpiration 对象。

    private List<string> GetMessages()
    {
        ICacheManager manager = CacheFactory.GetCacheManager();
        if (manager.GetData(CacheKey) == null)
        {
            manager.Add(CacheKey, GetMessageByUser(UserName), CacheItemPriority.Normal, null, this.CacheItemExpiration);
        }

        return manager.GetData(CacheKey) as List<string>;
    }

    由于在我们的例子中需要对DB进行数据操作,来检测数据的变换是否应用Cache的过期,我们需要想数据库中添加Message。我们通过下面的方式现在message的添加。

    private void CreateMessageEntry(string userName, string message)
    {
        Database db = DatabaseFactory.CreateDatabase();
        string insertSql = "INSERT INTO [dbo].[Messages]([UserID],[Message])VALUES(@userID, @message)";
        DbCommand command = db.GetSqlStringCommand(insertSql);
        db.AddInParameter(command, "userID", DbType.String, userName);
        db.AddInParameter(command, "message", DbType.String, message);
        db.ExecuteNonQuery(command);
    }

    我 们的Add按钮的实现如下:基于我们选择的Username和输入的message的内容向DB中添加Message,然后调用 GetMessages()方法获取基于用户Foo的Message列表。之所以要在两者之间将线程休眠1s,是为了上SqlDependency有足够 的时间结果从Database传过来的Query Notification,并触发OnChanged事件并执行相应的Event Handler,这样调用GetMessages时检测Cache才能检测到cache item已经过期了。

    private void buttonAdd_Click(object sender, EventArgs e)
    {
        this.CreateMessageEntry(this.comboBoxUserName.SelectedValue.ToString(), this.textBoxMessage.Text.Trim());
        Thread.Sleep(1000);
        this.listBoxMessage.DataSource = this.GetMessages();
    }

    由 于我们缓存了用户Foo的Message list,所以当我们为Foo创建Message的时候,下面的ListBox的列表能够及时更新,这表明我们的cache item已经过期了。而我们为其他的用户(Bar,Baz)创建Message的时候,cache item将不会过期,这一点我们可以通过弹出的MessageBox探测掉(expiration.Expired += delegate           MessageBox.Show("Cache has expired!");}; ),只有前者才会弹出下面的MessageBox:

    image

    注:由于SqlDependency建立在Service Broker之上的,所以我们必须将service Broker开关打开(默认使关闭的)。否则我们将出现下面的错误:

    image

    打开service Broker可以通过如下的T-SQL:ALTER DATABASE MyDb SET ENABLE_BROKER ;

     

    posted @ 2008-09-04 21:15 小No 阅读(86) | 评论 (0)编辑

    2008年6月22日

    前两个礼拜去买了一台爱可视605wifi(4G)的MP4,虽然1700的价钱挺贵的,不过605绝对对得起这个价格,尤其是那个屏幕,真是美到不行,而且分辨率720 * 576 以下的RMVB都能完美的播放,还有最好一点就是可以用wifi上网,直接看youtobe或优酷之类视频网站上的视频,省去下载的功夫。

    不过美中不足的是它支持浏览PDF文件,不支持直接浏览txt文件,
    这让喜欢看电子书的很是苦恼。虽然605对PDF的支持很好,但是翻页实在太慢。

    后来上了一些论坛看到别人说只要把txt文件的后缀名改成html就可以用浏览器直接查看了,
    于是就去试了一下,果然是可以查看了,但是效果很差:
    1. 文字不会换行
    2. 字体过小
    3. 文件多的话,一个个修改太耗时

    居于以上原因,就有自己开发了小工具把文件文件批量转换成html文件的想法,于是就花了一个上午的时间把这个工具写出来,功能挺简单的,不过够用就可以了
    目前这个工具支持txt文件,可以修改字体大小,字体颜色以及背景颜色。

    自己用605测试过,浏览效果还不错,跟在电脑上直接看txt文件差不多

    HtmlBuilder 1.0 下载地址:
    http://www.cnblogs.com/Files/NickYao/HtmlBuilder_v1.0.rar

    这个工具需要.net framework 2.0 的支持, vista的用户可以不用下载.net framework 2.0
    其他系统的用户可以到以下地址下载.net framework 2.0:

    http://www.microsoft.com/downloads/details.aspx?FamilyID=0856EACB-4362-4B0D-8EDD-AAB15C5E04F5&displaylang=zh-cn

    附加 一个转换好的电子书 黄易-翻云覆雨:

    http://www.cnblogs.com/Files/NickYao/黄易-翻云覆雨.rar

     

     



    posted @ 2008-06-22 18:03 小No 阅读(650) | 评论 (3)编辑

    2008年5月4日

         摘要: 本文主要介绍自己在使用Unity时碰到的一个问题,及解决方案。由于本人不擅长写作,所以文字都很简单,各位还是主要看代码及DEMO吧。首先先看一下以下代码:publicinterfaceILogger{voidWrite();}publicclassFlatFileLogger:ILogger{privateMessage_message;publicFlatFileLogger(Messageme... 阅读全文
    posted @ 2008-05-04 14:52 小No 阅读(1604) | 评论 (9)编辑

    2008年4月15日

            在使用Asp.net Mvc MVCContrib中的Castle有时会出现No component for key HomeController was found这样的错误

            在看了园子里的《Asp.net Mvc中MVCContrib中无法使用Castle的发解决方案 》这篇文章之后,按文章的方法修改了代码,修改方法如下:

            下载MvcContrib源代码,更改MvcContrib.Castle的WindsorControllerFactory.cs中的34行CreateController方法为:

                public IController CreateController(RequestContext context, string controllerName)
                { 
                        controllerName = controllerName +
    "Controller"; //更改了这里

                        IWindsorContainer container = GetContainer(context);
                       
    return (IController)container.Resolve(controllerName);
                }

            这个更改方法可能仅限于MVCContrib 0.0.1.91

            这样修改使得MvcContrib.Samples.NVelocityViewFactory这个例子可以正常运行,但另一个例子MvcContrib.Samples.WindsorControllerFactory却出现类似的问题。

            因此在MVCContrib的Google论坛里提出了疑问,最后得到了解释:

            由于组件名在Windsor是区分大小写的,而ASP.NET MVC里的Controller却是不区分的。如果按上面的代码修改,mysite.com/Home/index 这个地址会正常工作,而mysite.com/home/index将会报错 ,因为home是小写。

            因此把组件名强制注册为小写是目前的解决方案,就像修改之前的代码那样:   

                public IController CreateController(RequestContext context, string controllerName)
                { 
                        controllerName = controllerName.ToLower() +
    "controller"; //更改了这里

                        IWindsorContainer container = GetContainer(context);
                       
    return (IController)container.Resolve(controllerName);
                }

            不过既然不用修改源代码,那MvcContrib.Samples.NVelocityViewFactory这个例子还是会出错啊,没错,这个例子是存在BUG,不过修改起来很简单。只需修改Global.asax.cs文件的一行代码:

    protected virtual void InitializeWindsor()
            
    {
                
    if (_container == null)
                
    {
                    _container 
    = new WindsorContainer();

                    
    // Add our singleton NVelocityViewFactory
                    _container.AddComponent("ViewFactory"typeof(IViewEngine), typeof(Castle.NVelocityViewFactory));

                    Type[] assemblyTypes 
    = Assembly.GetExecutingAssembly().GetTypes();
                    
                    
    foreach (Type type in assemblyTypes)
                    
    {
                        
    if (typeof(IController).IsAssignableFrom(type))
                        
    {
                            
    //_container.AddComponentWithLifestyle(type.Name, type, LifestyleType.Transient); //修改前
                            _container.AddComponentWithLifestyle(type.Name.ToLower(), type, LifestyleType.Transient); //修改后
                            ControllerBuilder.Current.SetControllerFactory( typeof(WindsorControllerFactory));
                        }

                    }

                }

            }


         
         

    posted @ 2008-04-15 15:30 小No 阅读(37) | 评论 (0)编辑

    2008年1月19日

    优点:

    1. 方便(只要打开MSN就可以了)
    2. 快速(由于国内访问MSN空间的速度比较慢,因此使用机器人直接提交数据到MSN空间会比直接登录MSN空间写日志要快。当然前提条件是机器人服务器的网速不能跟用户的网速相差太远。如果是手机用户的话,那机器人的优势就更大了,ADSL怎样也比GPRS强吧,除非手机使用的是WIFI或者3G网络。)
    3. 隐蔽(不过这个优点也只能是针对那些上班时只能开MSN,不能浏览网页的朋友)

    局限:

    1. 由于MSN一条消息只能发400个字符,所以使用i-Writer来写日志,也只能写一些400字以内的MINI日志。(后续版本可能会使用拼接消息的方式来发布日志,以便支持篇幅比较长的日志)
    2. 不支持所见即所得的编辑方式。(这个只能是暂时先用其他编辑器先写好日志,再粘贴到MSN里来代替)
    3. 不支持图片上传


    posted @ 2008-01-19 13:16 小No 阅读(23) | 评论 (0)编辑

    2008年1月18日

            MSN机器人i-Writer的目标就是方便快速的往Windows Live Spaces里写文章。

            i-Writer 已经参加了Windows Live Messgener 2007 机器人大赛,请大家帮忙投一下票,谢谢
            投票地址:http://contest.xiaoi.com/listRobot.do?action=showDetail&id=82

            经过几天紧张的代码编写,i-Writer今天终于上线了,该机器人的MSN账号是iWriter@live.cn,欢迎大家加为好友。
            Web版机器人: http://sp.incesoft.com:8100/engine/SP050761/webbot.htm
            i-Writer的在线服务时间是:9:30-18:00                                     点此使用机器人

            虽然到目前为止,功能还很简陋,但基本的功能都已经实现了,以下i-Writer的功能列表:

    1. 文章的增、改、删操作
    2. 获取最近发布的文章列表(最多20篇)
    3. 获取指定文章的详细信息
    4. 获取分类列表
    5. 获取个人信息
    6. 获取个人空间信息

    i-Writer的使用帮助:

    第1步)要想使用i-Writer成功往Windows Live Spaces里写文章,首先必须启用Windows Live Spaces的电子邮件发布。

    启用电邮邮件发布的步骤:

    • 登陆到你的Windows Live Spaces
    • 顺序选择 选项-->电子邮件发布

              image

    • 勾选启用电子邮件发布复选框、填写发件人电子邮件(任意一个e-mail地址)和机密字(机密字后面在使用i-Writer时,是用来当作密码的)

              image

    • 最后点击“保存”按钮,就完成了电子邮件发布的启用。

    第2步)  把iWriter@live.cn加为msn好友
    第3步)  使用i-Writer指令发布文章,以下是i-Writer的指令列表:

    • setting 用户名|机密字 -- 设置您的共享空间的发布帐户.

              其中需要注意的是, 用户名不是你的msn账号,而是你的Spaces主页地址的二级域名。

              例如我的Spaces地址是:http://kokomusic.spaces.live.com/,那么用户名就是kokomusic

              机密字也不是你的MSN账号的密码,而是在启用电子邮件发布时所填写的机密字

    • recent N -- 获取最近的N篇文章, N最大值为20.
    • newpost 标题|内容 -- 发布新文章.
    • newpost 标题|内容|分类 -- 发布新文章.
    • editpost 文章ID|标题|内容 -- 更新文章.
    • editpost 文章ID|标题|内容|分类 -- 更新文章.
    • delpost 文章ID -- 删除文章.
    • categories -- 获取分类列表.
    • myblog -- 获取空间信息.
    • myprofile -- 获取个人信息.
    • help -- 打开帮助,获取命令列表.
    • ? -- 打开帮助,获取命令列表.

                    一个发布一篇新文章的例子:

                   如果是第一次使用,首先先设置发布帐户:

                   输入命令:setting kokomusic/mypassword 

                   这时i-Writer会返回一条消息:设置帐户成功

                   接着输入命令: newpost 测试/测试

                   这时i-Writer会返回一条消息:文章发布成功

                   最后转到您的空间就可以看到刚刚发布的那篇文章了

      PS:   如果只想测试i-Writer的功能
              输入指令:setting iwriter1980|iwriter
              就可以使用i-Writer的所有功能了。


        这个机器人我是使用赢思的SDK来开发的,该SDK的接口比较简单,开发起来比较容易。
        源码下载

    posted @ 2008-01-18 23:00 小No 阅读(1699) | 评论 (8)编辑

    2008年1月15日

         摘要: 今天在博客园里看到一篇文章《在你的页面上添加Silverlight 应用 Step by Step 》,自己动手跟着一步步做,最后试验成功,小兴奋了一阵。但回过头来看一看,发现还是有点麻烦,以下介绍的就是一个比较快捷方便方法。如果没看过以上那篇文章的朋友,建议在看本篇文章之前先去看一看,因为该文章介绍过的东西我在这里就不再重复了。   阅读全文
    posted @ 2008-01-15 23:45 小No 阅读(1646) | 评论 (5)编辑

    2007年12月29日

        以前我们在更新应用程序,导至asp.net应用程序重启,应用程序的用户通常会显示不友好的错误信息,或者IE一直显示加载状态。

    ASP.net 2.0
    支持您在应用程序根目录下放置一个app_offline.htm文件,用户请求时,系统会检查是否有这个文件存在,如果有,系统会将app_offline.htm文件的内容直接返回给用户。
    app_offline.htm
    的内容可以更改成任何您需要的内容,但是要注意,文件不能太小,因为大部分用户的IE设置了显示友好的Http错误信息,如果app_offline.htm小于512字节,此设置将会失效,将会显示404错误。

        另外注意一下地址栏,offline信息显示时,地址栏依然显示的时请求的地址

    posted @ 2007-12-29 11:48 小No 阅读(31) | 评论 (0)编辑

    2007年12月28日

        平常在编写代码时,我们都知道用Shift + 方向键来选择一行或多行代码。
        但如果只要选择某几列代码呢?这可能就很多不多人不知道了。
        其实也很简单,只要同时按住Shift + Alt + 方向键或者单按Alt + 鼠标控制就可以做垂直选择了。
    posted @ 2007-12-28 16:07 小No 阅读(44) | 评论 (0)编辑

    导航

    <2008年10月>
    2829301234
    567891011
    12131415161718
    19202122232425
    2627282930311
    2345678

    统计

    与我联系

    搜索

     

    常用链接

    留言簿(1)

    我参加的小组

    我参与的团队

    我的标签

    随笔分类

    随笔档案

    文章分类

    相册

    Blogs

    积分与排名

    最新评论

    阅读排行榜

    评论排行榜