• ASP.NET-自定义HttpModule与HttpHandler


        在之前的ASP.NET是如何在IIS下工作的这篇文章中介绍了ASP.NET与IIS配合工作的机制,在http请求经过一系列处理后,最后到达ASP.NET管道中,这时,就是Http Modules和HttpHandler出场的时候了。

      再来摆出管道工作时序图来一看:

    管道

       

    HttpModule

    HttpModule是类似于过滤器的作用,可以没有,也可以有任意个,每一个都可以订阅管道事件中的任意个事件,在每个订阅的事件中可自定义功能实现。

    HttpModule是实现IHttpModule接口的类。接口如下:

    public interface IHttpModule
        {
            // 摘要: 
            //     处置由实现 System.Web.IHttpModule 的模块使用的资源(内存除外)。
            void Dispose();
            //
            // 摘要: 
            //     初始化模块,并使其为处理请求做好准备。
            //
            // 参数: 
            //  context:
            //  一个 System.Web.HttpApplication,它提供对 ASP.NET 应用程序内所有应用程序对象的公用的方法、属性和事件的访问
            void Init(HttpApplication context);
        }

    下面实现一个HttpModule,并订阅管道中的一系列事件,订阅事件就是在Init方法中绑定EventHandler的过程:

    代码有点长,因为我把每一个事件都订阅了,这样一来可以清楚的看出哪些事件执行了,这些事件执行的先后顺序是什么。代码如下:

    public class MyModule : IHttpModule
        {
            #region IHttpModule Members
    
            public void Dispose()
            {
                //此处放置清除代码。
            }
    
            public void Init(HttpApplication context)
            {
                // 下面是如何处理 LogRequest 事件并为其 
                // 提供自定义日志记录实现的示例
                context.LogRequest += new EventHandler(OnLogRequest);
                context.BeginRequest += new EventHandler(context_BeginRequest);
                context.AuthenticateRequest += new EventHandler(context_AuthenticateRequest);
                context.AcquireRequestState += new EventHandler(context_AcquireRequestState);
                context.AuthorizeRequest += new EventHandler(context_AuthorizeRequest);
                context.Disposed += new EventHandler(context_Disposed);
                context.Error += new EventHandler(context_Error);
                context.EndRequest += new EventHandler(context_EndRequest);
                context.MapRequestHandler += new EventHandler(context_MapRequestHandler);
                context.PostAcquireRequestState += new EventHandler(context_PostAcquireRequestState);
                context.PostAuthenticateRequest += new EventHandler(context_PostAuthenticateRequest);
                context.PostAuthorizeRequest += new EventHandler(context_PostAuthorizeRequest);
                context.PostLogRequest += new EventHandler(context_PostLogRequest);
                context.PostReleaseRequestState += new EventHandler(context_PostReleaseRequestState);
                context.PostRequestHandlerExecute += new EventHandler(context_PostRequestHandlerExecute);
                context.PostResolveRequestCache += new EventHandler(context_PostResolveRequestCache);
                context.PostUpdateRequestCache += new EventHandler(context_PostUpdateRequestCache);
                context.ReleaseRequestState += new EventHandler(context_ReleaseRequestState);
                context.RequestCompleted += new EventHandler(context_RequestCompleted);
                context.ResolveRequestCache += new EventHandler(context_ResolveRequestCache);
                context.UpdateRequestCache += new EventHandler(context_UpdateRequestCache);
                context.PreRequestHandlerExecute += new EventHandler(context_PreRequestHandlerExecute);
                context.PreSendRequestContent += new EventHandler(context_PreSendRequestContent);
                context.PreSendRequestHeaders += new EventHandler(context_PreSendRequestHeaders);
                context.PostMapRequestHandler += new EventHandler(context_PostMapRequestHandler);
    
    
            }
    
            void context_Error(object sender, EventArgs e)
            {
                WriteLog("Error");
                //HttpContext.Current.Response.Write("Error<br />");
            }
    
            void context_UpdateRequestCache(object sender, EventArgs e)
            {
                WriteLog("UpdateRequestCache");
                //HttpContext.Current.Response.Write("UpdateRequestCache<br />");
            }
    
            void context_ResolveRequestCache(object sender, EventArgs e)
            {
                WriteLog("ResolveRequestCache");
                // HttpContext.Current.Response.Write("ResolveRequestCache<br />");
            }
    
            void context_RequestCompleted(object sender, EventArgs e)
            {
                WriteLog("RequestCompleted");
                // HttpContext.Current.Response.Write("RequestCompleted<br />");
            }
    
            void context_ReleaseRequestState(object sender, EventArgs e)
            {
                WriteLog("ReleaseRequestState");
                //HttpContext.Current.Response.Write("ReleaseRequestState<br />");
            }
    
            void context_PostUpdateRequestCache(object sender, EventArgs e)
            {
                WriteLog("PostUpdateRequestCache");
                //HttpContext.Current.Response.Write("PostUpdateRequestCache<br />");
            }
    
            void context_PostResolveRequestCache(object sender, EventArgs e)
            {
                WriteLog("PostResolveRequestCache");
                //HttpContext.Current.Response.Write("PostResolveRequestCache<br />");
            }
    
            void context_PostRequestHandlerExecute(object sender, EventArgs e)
            {
                WriteLog("PostRequestHandlerExecute");
                //HttpContext.Current.Response.Write("PostRequestHandlerExecute<br />");
            }
    
            void context_PostReleaseRequestState(object sender, EventArgs e)
            {
                WriteLog("PostReleaseRequestState");
                //HttpContext.Current.Response.Write("PostReleaseRequestState<br />");
            }
    
            void context_PostLogRequest(object sender, EventArgs e)
            {
                WriteLog("PostLogRequest");
                //HttpContext.Current.Response.Write("PostLogRequest<br />");
            }
    
            void context_PostAuthorizeRequest(object sender, EventArgs e)
            {
                WriteLog("PostAuthorizeRequest");
                //HttpContext.Current.Response.Write("PostAuthorizeRequest<br />");
            }
    
            void context_PostAuthenticateRequest(object sender, EventArgs e)
            {
                WriteLog("PostAuthenticateRequest");
                //HttpContext.Current.Response.Write("PostAuthenticateRequest<br />");
            }
    
            void context_PostAcquireRequestState(object sender, EventArgs e)
            {
                WriteLog("PostAcquireRequestState");
                //HttpContext.Current.Response.Write("PostAcquireRequestState<br />");
            }
    
            void context_MapRequestHandler(object sender, EventArgs e)
            {
                WriteLog("MapRequestHandler");
                //HttpContext.Current.Response.Write("MapRequestHandler<br />");
            }
    
            void context_Disposed(object sender, EventArgs e)
            {
                WriteLog("Disposed");
                //HttpContext.Current.Response.Write("Disposed<br />");
            }
    
            void context_AuthorizeRequest(object sender, EventArgs e)
            {
                WriteLog("AuthorizeRequest");
                //HttpContext.Current.Response.Write("AuthorizeRequest<br />");
            }
    
            void context_AcquireRequestState(object sender, EventArgs e)
            {
                WriteLog("AcquireRequestState");
                //HttpContext.Current.Response.Write("AcquireRequestState<br />");
            }
    
    
            void context_PreSendRequestHeaders(object sender, EventArgs e)
            {
                WriteLog("PreSendRequestHeaders");
                //HttpContext.Current.Response.Write("PreSendRequestHeaders<br />");
            }
    
            void context_PreSendRequestContent(object sender, EventArgs e)
            {
                WriteLog("PreSendRequestContent");
                //HttpContext.Current.Response.Write("PreSendRequestContent<br />");
            }
    
            void context_PreRequestHandlerExecute(object sender, EventArgs e)
            {
                WriteLog("PreRequestHandlerExecute");
                //HttpContext.Current.Response.Write("PreRequestHandlerExecute<br />");
            }
    
            void context_EndRequest(object sender, EventArgs e)
            {
                WriteLog("EndRequest");
                //HttpContext.Current.Response.Write("EndRequest<br />");
            }
    
            void context_BeginRequest(object sender, EventArgs e)
            {
                WriteLog("*******************************************************************************");
                HttpApplication app = sender as HttpApplication;
                WriteLog(app.Context.Request.Path);
                WriteLog("BeginRequest");
                //HttpContext.Current.Response.Write("BeginRequest<br />");
            }
    
            void context_AuthenticateRequest(object sender, EventArgs e)
            {
                WriteLog("AuthenticateRequest");
                //HttpContext.Current.Response.Write("AuthenticateRequest<br />");
            }
            #endregion
    
            public void OnLogRequest(Object source, EventArgs e)
            {
                //可以在此处放置自定义日志记录逻辑
                WriteLog("OnLogRequest");
                //HttpContext.Current.Response.Write("OnLogRequest<br />");
            }
    
            public void context_PostMapRequestHandler(object sender, EventArgs e)
            {
                WriteLog("PostMapRequestHandler");
                //HttpContext.Current.Response.Write("PostMapRequestHandler<br />");
            }
    
            public void WriteLog(string message)
            {
                string path = @"d:aspnethttpmodule.txt";
                StreamWriter writer = null;
                if (!File.Exists(path))
                {
                    writer = File.CreateText(path);
                }
                else
                {
                    FileInfo info = new FileInfo(path);
                    writer = info.AppendText();
    
                }
                writer.WriteLine(message);
    
                writer.Flush();
                writer.Close();
            }
        }

    订阅的事件实现中,将事件名称保存到我本地D盘的一个文本文件中。

    代码实现完毕了,下一步就是要代码起作用了,很简单,只需要在web.config中简单配置就可以了。配置中注意IIS7集成模式和IIS7经典模式(包括IIS6)的区别,配置如下:

    <!--IIS6或者IIS7经典模式-->
    <system.web>
        <httpModules>
          <add name="mycustommodule" type="fengzheng.MyModule,handler_modules"/>
        </httpModules>
      </system.web>
    <!--IIS7集成模式-->
    <system.webServer>
        <modules>
          <add name="mycustommodule" type="fengzheng.MyModule,handler_modules"/>
        </modules>
    </system.webServer>

    如此一来,一个HttpModule及其配置工作就完成了,接下来,发布网站到IIS或者直接在VS中运行,随便访问项目中的一个文件(任何文件类型都可以),我的项目中有一个WebForm2.aspx的页面,我在浏览器中访问这个页面,发现页面是空白的,因为页面中我什么都没写,上面的Module实现中,我把输出全部放到本地D盘的一个文本文件中了,ok,打开那个文本文件。如图:

    image

    我们看到输出内容,第2行是访问的页面地址,下面依次为订阅的事件输出,我们清楚的看到了事件的执行顺序。

    BeginRequest          		#发出信号表示创建任何给定的新请求。 此事件始终被引发,并且始终是请求处理期间发生的第一个事件
    AuthenticateRequest         #发出信号表示配置的身份验证机制已对当前请求进行了身份验证。 订阅 AuthenticateRequest 事件可确保在处理附加模块或事件处理程序之前对请求进行身份验证
    PostAuthenticateRequest     #预订 PostAuthenticateRequest 事件的功能可以访问由 PostAuthenticateRequest 处理的任何数据
    AuthorizeRequest			#发出信号表示 ASP.NET 已对当前请求进行了授权。 订阅 AuthorizeRequest 事件可确保在处理附加的模块或事件处理程序之前对请求进行身份验证和授权
    PostAuthorizeRequest		#发出信号表示 ASP.NET 已对当前请求进行了授权。 订阅 PostAuthorizeRequest 事件可确保在处理附加的模块或处理程序之前对请求进行身份验证和授权
    ResolveRequestCache			#引发这个事件来决定是否可以使用从输出缓冲返回的内容来结束请求。这依赖于Web应用程序的输出缓冲时怎样设置的
    PostResolveRequestCache		#在 ASP.NET 跳过当前事件处理程序的执行并允许缓存模块满足来自缓存的请求时发生
    MapRequestHandler			#ASP.NET 基础结构使用 MapRequestHandler 事件来确定用于当前请求的请求处理程序
    PostMapRequestHandler		#在 ASP.NET 已将当前请求映射到相应的事件处理程序时发生
    AcquireRequestState			#当 ASP.NET 获取与当前请求关联的当前状态(如会话状态)时发生
    PostAcquireRequestState		#预订 AcquireRequestState 事件的功能可以访问由 PostAcquireRequestState 处理的任何数据
    PreRequestHandlerExecute	#在ASP.NET开始执行HTTP请求的处理程序之前引发这个事件。在这个事件之后,ASP.NET 把该请求转发给适当的HTTP处理程序
    PostRequestHandlerExecute   #在 ASP.NET 事件处理程序(例如,某页或某个 XML Web service)执行完毕时发生
    ReleaseRequestState			#在 ASP.NET 执行完所有请求事件处理程序后发生。 该事件将使状态模块保存当前状态数据
    PostReleaseRequestState		#在 ASP.NET 已完成所有请求事件处理程序的执行并且请求状态数据已存储时发生
    UpdateRequestCache			#当 ASP.NET 执行完事件处理程序以使缓存模块存储将用于从缓存为后续请求提供服务的响应时发生
    PostUpdateRequestCache		#在 ASP.NET 完成缓存模块的更新并存储了用于从缓存中为后续请求提供服务的响应后,发生此事件
    OnLogRequest				#恰好在 ASP.NET 为当前请求执行任何记录之前发生,即使发生错误,也会引发 LogRequest 事件
    PostLogRequest				#在 ASP.NET 处理完 LogRequest 事件的所有事件处理程序后发生
    EndRequest					#在 ASP.NET 响应请求时作为 HTTP 执行管线链中的最后一个事件发生
    PreSendRequestContent		#恰好在 ASP.NET 向客户端发送内容之前发生,可能发生多次
    PreSendRequestHeaders		#恰好在 ASP.NET 向客户端发送 HTTP 标头之前发生
    RequestCompleted			#在任何托管模块和处理程序执行后,它使模块清理资源

    访问一个页面的过程中,依次触发了23个事件,而HttpModule可订阅的事件个数为25个,观察发现,Error和Disposed这两个事件没有触发。Error事件在发生错误的情况下执行,而Disposed事件,当我们关闭刚才打开的页面,再到文本文件里查看,发现Disposed事件出现了,所以Disposed在会话结束后触发。

    由于HttpModule的个数可以有多个,我们可以按照上面的方式定义HttpModule实现类,然后再web.config中增加配置项,就可以实现多个HttpModule同时订阅管道事件了。

     

    介绍完HttpModule,那么HttpHandler又是什么呢,它又在什么什么时候执行呢?接下来看一下HttpHandler。

    HttpHandler

    HttpHandler是HTTP请求的处理中心,真正地对客户端请求的服务器页面做出编译和执行,并将处理过后的信息附加在HTTP请求信息流中再次返回到HttpModule中。 
    HttpHandler与HttpModule不同,一旦定义了自己的HttpHandler类,那么它对系统的HttpHandler的关系将是“覆盖”关系。

    HttpHandler是实IHttpHandler接口的类,IHttpHandler接口定义如下:

        public interface IHttpHandler
        {
            // 摘要: 
            //     获取一个值,该值指示其他请求是否可以使用 System.Web.IHttpHandler 实例。
            //
            // 返回结果: 
            //     如果 System.Web.IHttpHandler 实例可再次使用,则为 true;否则为 false。
            bool IsReusable { get; }
    
            // 摘要: 
            //     通过实现 System.Web.IHttpHandler 接口的自定义 HttpHandler 启用 HTTP Web 请求的处理。
            //
            // 参数: 
            //   context:
            //     System.Web.HttpContext 对象,它提供对用于为 HTTP 请求提供服务的内部服务器对象(如 Request、Response、Session
            //     和 Server)的引用。
            void ProcessRequest(HttpContext context);
        }

    接口中只有一个属性和一个方法,所以实现一个HttpHandler也很简单,下面实现一个简单的HttpHandler,代码如下:

    public class MyIISHandler : IHttpHandler
        {
            /// <summary>
            /// 您将需要在网站的 Web.config 文件中配置此处理程序 
            /// 并向 IIS 注册它,然后才能使用它。有关详细信息,
            /// 请参见下面的链接: http://go.microsoft.com/?linkid=8101007
            /// </summary>
            #region IHttpHandler Members
    
            public bool IsReusable
            {
                // 如果无法为其他请求重用托管处理程序,则返回 false。
                // 如果按请求保留某些状态信息,则通常这将为 false。
                get { return true; }
            }
    
            public void ProcessRequest(HttpContext context)
            {
                //在此处写入您的处理程序实现。
                WriteLog("请求一个asox页面");
            }
    
            #endregion
        }

    上面我实现了一个很简单的HttpHandler类,在ProcessRequest方法中,调用上面的HttpModule类中写文本文件的方法,在文本文件中写入“请求一个asox页面”,没错,是一个asox页面,我自己定义的文件格式,下面我会在web.config中添加配置项:

    <!--IIS6或者IIS7经典模式-->
      <system.web>
        <httpHandlers>
          <add name="mycustomhandler" path="*.asox" verb="*" type="fengzheng.MyIISHandler,handler_modules"/>
        </httpHandlers>
      </system.web>
     
    <!--IIS7集成模式-->
      <system.webServer>
        <handlers>
           <add name="mycustomhandler" path="*.asox" verb="*" type="fengzheng.MyIISHandler,handler_modules"/>
        </handlers>
      </system.webServer>

    配置项属性的解释:

    path:指定了需要调用处理程序的路径和文件名(可以包含通配符)。“*”、“*.aspx”、“booklist.aspx”、“test1.aspx,test2.aspx”、“*.asox”、“*.txt”。

    verb:指定了处理程序支持的HTTP动作。*-支持所有的HTTP动作;“GET”-支持Get操作;“POST”-支持Post操作;“GET, POST”-支持两种操作。

    type:用名字空间、类名称和程序集名称的组合形式指定处理程序或处理程序工厂的实际类型。ASP.NET运行时首先搜索bin目录中的DLL,接着在GAC中搜索。

    接着,发布站点到IIS。打开IIS,找到当前站点的“处理程序映射”,会发现多了刚刚配置的HttpHandler,如图:

    2014-04-15_181203

    没错,关于对*.asox这种类型的文件,就可以映射到上面创建的HttpHandler来进行处理,观察其它条目发现,像*.aspx、*.ashx的处理程序是System.Web.UI.PageHandlerFactory和System.Web.UI.SimpleHandlerFactory这样的工厂类型。没错,可以指定处理程序为一个HttpHandler,也可以指定为一个抽象工厂类型。先不说工厂类型的事儿,访问一下网站中的asox页面,看一下文本文件的记录情况。

    image

    起作用了,在HttpModule输出的一堆信息中,夹杂着HttpHandler的输出,当然这仅限于访问asox类型的页面,因为我只对路径为*.asox的文件格式做了设置,修改下配置文件,例如将path=”*.asox”改为path=”*.aspx”,那么ASP.NET对*.aspx页面原有的解析机制将被我们设置的处理程序所覆盖。

     

    回到上面的输出内容,我们观察HttpHandler输出内容所在的位置,位于PreRequestHandlerExecute和PostRequestHandlerExecute这两个事件之间,这与HttpApplication类中的管道事件的创建过程有关。

     

    前面说到了,处理处理程序可以指定为一个工厂类型,下面,我就创建一个工厂类型的处理程序。

    这里所说的工厂类型的处理程序,就是实现了IHttpHandlerFactory接口的类,IHttpHandlerFactory接口定义如下:

    public interface IHttpHandlerFactory
        {
            // 摘要: 
            //     返回实现 System.Web.IHttpHandler 接口的类的实例。
            //
            // 参数: 
            //   context:
            //     System.Web.HttpContext 类的实例,它提供对用于为 HTTP 请求提供服务的内部服务器对象(如 Request、Response、Session
            //     和 Server)的引用。
            //
            //   requestType:
            //     客户端使用的 HTTP 数据传输方法(GET 或 POST)。
            //
            //   url:
            //     所请求资源的 System.Web.HttpRequest.RawUrl。
            //
            //   pathTranslated:
            //     所请求资源的 System.Web.HttpRequest.PhysicalApplicationPath。
            //
            // 返回结果: 
            //     处理请求的新的 System.Web.IHttpHandler 对象。
            IHttpHandler GetHandler(HttpContext context, string requestType, string url, string pathTranslated);
            //
            // 摘要: 
            //     使工厂可以重用现有的处理程序实例。
            //
            // 参数: 
            //   handler:
            //     要重用的 System.Web.IHttpHandler 对象。
            void ReleaseHandler(IHttpHandler handler);
        }

    同样很简单,也是只有两个接口方法,下面是实现这个接口的工厂,代码如下:

    public class MyHttpHandlerFactory:IHttpHandlerFactory
    {
            public IHttpHandler GetHandler(HttpContext context, string requestType, string url, string pathTranslated)
            {
    			//写日志
                WriteLog(string.Format("requestType:{0}|url:{1}|pathTranslated:{2}", requestType, url, pathTranslated));
                
    			//生成一个IHttpHandler
    			Type t = typeof(MyIISHandler);
                IHttpHandler handler =  Activator.CreateInstance(t) as IHttpHandler;
                return handler;
            }
    
            public void ReleaseHandler(IHttpHandler handler)
            {
            }
    }

    方法中,返回了前面创建的那个HttpHander类,依然调用记录文本文件的方法输出内容,方便观察执行的实际和具体内容。配置文件改改为这个工厂类。

    <add name="mycustomFactoryHandler" path="*.asox" verb="*" type="fengzheng.MyHttpHandlerFactory,handler_modules"/>

    配置完毕后,访问网站中的asox页面,打开文本文件,内容如下:

    image

    我们发现,工厂类中构造IHttpHandler接口的方法发生在HttpModule的MapRequestHandler之后,这同样与HttpApplication类中构造管道事件有关。

     

    说了这么多,那么,HttpModule和HttpHandler究竟能干什么呢?

    HttpModule很常用的一个作用就是Url重写,URLRewriter就是基于HttpModule实现的。

    另外,有通过HttpHandler对图片加水印,防止盗链的。

    具体的可以参考这篇文章

    部署网站注意事项:

    网站采用.net 4.0集成模式部署,集成模式是一种统一的请求处理管道,它将ASP.NET请求管道与IIS核心管道组合在一起,这种模式能够提供更好的性能,能够实现配置和治理的模块化,而且增加了使用托管代码模块扩展IIS时的灵活性。IIS经典模式与集成模式的区别

    集成模式和经典模式的配置文件稍有不同,部署时需要注意针对不同的部署模式,修改配置文件。在vs2013中新建的web应用程序,默认的web.config内容如下:

    <?xml version="1.0" encoding="utf-8"?>
    <!--
      有关如何配置 ASP.NET 应用程序的详细信息,请访问
      http://go.microsoft.com/fwlink/?LinkId=169433
      -->
    <configuration>
      <system.web>
        <compilation debug="true" targetFramework="4.5" />
        <httpRuntime targetFramework="4.5" />
      </system.web>
    </configuration>
    • 按照经典模式部署,配置文件应该如下:
    <?xml version="1.0" encoding="utf-8"?>
    <!--
      有关如何配置 ASP.NET 应用程序的详细信息,请访问
      http://go.microsoft.com/fwlink/?LinkId=169433
      -->
    <configuration>
      <system.web>
        <compilation debug="true" targetFramework="4.5" />
        <httpRuntime targetFramework="4.5" />
        <httpModules>
          <add name="mycustommodule" type="fengzheng.MyModule,handler_modules"/>
        </httpModules>
        <httpHandlers>
          <add name="mycustomhandler" path="*.asox" verb="*" type="fengzheng.MyIISHandler,handler_modules"/>
        </httpHandlers>
      </system.web>
    </configuration>

    经典模式经测试总是出现如下错误,500.21 - 模块无法识别:

    HTTP 错误 500.21 - Internal Server Error
    处理程序“PageHandlerFactory-ISAPI-4.0_64bit”在其模块列表中有一个错误模块“IsapiModule”

    至于错误原因:目前还不是很清楚。

    • 按照集成模式部署,配置文件应该如下,将配置信息放到system.webServer节点之下
    <?xml version="1.0" encoding="utf-8"?>
    <!--
      有关如何配置 ASP.NET 应用程序的详细信息,请访问
      http://go.microsoft.com/fwlink/?LinkId=169433
      -->
    <configuration>
      <system.web>
        <compilation debug="true" targetFramework="4.5" />
        <httpRuntime targetFramework="4.5" />
      </system.web>
      <system.webServer>
        <modules>
          <add name="mycustommodule" type="fengzheng.MyModule,handler_modules"/>
        </modules>
        <handlers>
          <add name="mycustomhandler" path="*" verb="*" type="fengzheng.MyIISHandler,handler_modules"/>
        </handlers>
      </system.webServer>
    </configuration>
  • 相关阅读:
    MySQL创建数据库简单命令
    工作的本质是解决问题
    使用消息中间件时,如何保证消息仅仅被消费一次?
    缓存穿透了怎么办?
    MySQL 数据库的提速器-写缓存(Change Buffer)
    删库了,我们一定要跑路吗?
    做好一件事的三要素
    一分钟简单了解 JSON Web Token
    聊一聊 MySQL 中的数据编辑过程中涉及的两阶段提交
    聊一聊 MySQL 数据库中的那些锁
  • 原文地址:https://www.cnblogs.com/fengzheng/p/3666774.html
Copyright © 2020-2023  润新知