Session,有没有必要使用它?
今天来说说 Session 。这个东西嘛,我想每个Asp.net开发人员都知道它,尤其是初学Asp.net时,肯定也用过它,因为用它保存会话数据确实非常简单。与前二篇博客不同,这次我不打算细说它的使用,而是打算说说它的缺点,同时我还会举个实际的例子,来看看它到底有什么不好的影响。当然了,光批评是没有意义,事情也得解决,没有会话也不行,所以,本文将也给出一个自认为能替代Session的解决方案。
Session的来龙去脉
当我们新建一个网站时,VS20XX 生成的网站模板代码中,Session就是打开。是的,如果你没有关闭它,Session其实是一直在工作着。 您只需要在Page中用一行代码就能判断您的网站是否在使用Session,
Session["key1"] = DateTime.Now;
很简单,就是写一下Session,如果代码能运行,不出现异常,就表示您的网站是支持Session的。我们可以去web.config从全局关闭它,
<sessionState mode="Off"></sessionState>
再运行上面的代码,就能看到黄页了。换句话说:当您访问Session时发生以下异常即表示您的网站(或者当前页面)是不支持Session的。
这里要说明一下:如果您在某个页面中访问Session时,出现以上黄页,也有可能是页面级别关闭了Session 。在每个aspx页的Page指令行,只要我们设置一下EnableSessionState即可,这个属性有3个可选项。我创建了三个页面,分别接受IDE给的默认名称。
// Default.aspx
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" EnableSessionState="True" Inherits="_Default" %> // Default2.aspx
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default2.aspx.cs" EnableSessionState="ReadOnly" Inherits="Default2" %> // Default3.aspx
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default3.aspx.cs" EnableSessionState="False" Inherits="Default3" %>
对于Default.aspx来说,EnableSessionState这个设置可以不用显式指定,因为它就是默认值。 页面的这个参数的默认值也可以在web.config中设置,如:<pages enableSessionState="ReadOnly">
以上三个设置就分别设置了三个不同的Session使用方法。下面我们再来看一下,这个设置对于Session来说,是如何起作用的。
如果您的web.config中有如下设置:
<compilation debug="true">
那么,可以在x:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\Temporary ASP.NET Files\websiteName\xxxxxx\xxxxxxxx中找到这么三个aspx页面的【编译前版本】:
说明:Asp.net的编译临时目录也可以在web.config中指定,如:<compilation debug="true" tempDirectory="D:\Temp">
// Default.aspx
public partial class _Default : System.Web.SessionState.IRequiresSessionState { // Default2.aspx
public partial class Default2 : System.Web.SessionState.IRequiresSessionState, System.Web.SessionState.IReadOnlySessionState { // Default3.aspx
public partial class Default3 {
或者您也可以编译整个网站,从生成的程序集去看这些类的定义,也能看到以上结果。
也就是说:Page指令中的设置被编译器转成一些接口【标记】,那么,您或许有点好奇,为什么搞这么几个接口,它们在哪里被使用?下面我们来看看这个问题,当然了,也只能反编译.net framework的代码找线索了。最终发现在Application的PostMapRequestHandler事件中
internal class MapHandlerExecutionStep : HttpApplication.IExecutionStep
{
void HttpApplication.IExecutionStep.Execute()
{
HttpContext context = this._application.Context;
HttpRequest request = context.Request; // .................... 注意下面这个调用
context.Handler = this._application.MapHttpHandler(
context, request.RequestType, request.FilePathObject, request.PhysicalPathInternal, false);
// ....................
}
}
接着找HttpContext的Handler属性
public IHttpHandler Handler
{
set
{
this._handler = value;
// ...........................
if( this._handler != null ) {
if( this._handler is IRequiresSessionState ) {
this.RequiresSessionState = true;
}
if( this._handler is IReadOnlySessionState ) {
this.ReadOnlySessionState = true;
}
// ...........................
}
}
}
至此,应该大致搞清楚了,原来这二个接口也只是一个标记。我们可以看一下它们的定义:
public interface IRequiresSessionState
{
}
public interface IReadOnlySessionState : IRequiresSessionState
{
}
完全就是个空接口,仅仅只是为了区分使用Session的方式而已。可能您会想HttpContext的这二个属性RequiresSessionState, ReadOnlySessionState又是在哪里被使用的。答案就是在SessionStateModule中。 SessionStateModule就是实现Session的HttpModule ,它会检查了所有请求,根据HttpContext的这二个属性分别采用不同的处理方式。大致是如下方法:
bool requiresSessionState = this._rqContext.RequiresSessionState;
// 后面会有一些针对requiresSessionState的判断 if( !requiresSessionState ) {
// .......................
} this._rqReadonly = this._rqContext.ReadOnlySessionState;
// 后面会有一些针对this._rqReadonly的判断 if( this._rqReadonly ) {
this._rqItem = this._store.GetItem(this._rqContext, this._rqId, out flag2, out span,
out this._rqLockId, out this._rqActionFlags);
}
else {
this._rqItem = this._store.GetItemExclusive(this._rqContext, this._rqId, out flag2, out span,
out this._rqLockId, out this._rqActionFlags);
// ..........................
}
这块的代码比较散,为了对这二个参数有个权威的说明,我将直接引用MSDN中的原文。
会话状态由 SessionStateModule 类进行管理,在请求过程中的不同时间,该类调用会话状态存储提供程序在数据存储区中读写会话数据。请求开始时,SessionStateModule 实例通过调用 GetItemExclusive 方法或 GetItem 方法(如果 EnableSessionState 页属性已设置为 ReadOnly)从数据源检索数据。请求结束时,如果修改了会话状态值,则 SessionStateModule 实例调用 SessionStateStoreProviderBase.SetAndReleaseItemExclusive 方法将更新的值写入会话状态存储区。
上面的说法提到了锁定,既然有锁定,就会影响并发。我们再看看MSDN中关于并发的解释。
对 ASP.NET 会话状态的访问专属于每个会话,这意味着如果两个不同的用户同时发送请求,则会同时授予对每个单独会话的访问。但是,如果这两个并发请求是针对同一会话的(通过使用相同的 SessionID 值),则第一个请求将获得对会话信息的独占访问权。第二个请求将只在第一个请求完成之后执行。(如果由于第一个请求超过了锁定超时时间而导致对会话信息的独占锁定被释放,则第二个会话也可获得访问权。)如果将 @ Page 指令中的 EnableSessionState 值设置为 ReadOnly,则对只读会话信息的请求不会导致对会话数据的独占锁定。但是,对会话数据的只读请求可能仍需等到解除由会话数据的读写请求设置的锁定。
ASP.NET 应用程序是多线程的,因此可支持对多个并发请求的响应。多个并发请求可能会试图访问同一会话信息。假设有这样一种情况,框架集中的多个框架全部引用同一应用程序中的 ASP.NET 网页。框架集中每个框架的独立请求可以在 Web 服务器的不同线程上并发执行。如果每个框架的 ASP.NET 页都访问会话状态变量,则可能会有多个线程并发访问会话存储区。为避免会话存储区中的数据冲突和意外的会话状态行为, SessionStateModule 和 SessionStateStoreProviderBase 类提供了一种功能,能在执行 ASP.NET 页期间以独占方式锁定特定会话的会话存储项。 请注意,如果 EnableSessionState 属性标记为 ReadOnly,则不会对会话存储项设置锁定。但是,同一应用程序中的其他 ASP.NET 页也许可以写入会话存储区,因此对存储区中只读会话数据的请求可能仍然必须等待锁定数据被释放。
在对 GetItemExclusive 方法的调用中,请求开始时即对会话存储数据设置锁定。请求完成后,在调用 SetAndReleaseItemExclusive 方法期间释放锁定。
如果 SessionStateModule 实例在调用 GetItemExclusive 或 GetItem 方法过程中遇到锁定的会话数据,则该实例每隔半秒重新请求一次该会话数据,直到锁定被释放或 ExecutionTimeout 属性中指定的时间已经过去。如果请求超时,SessionStateModule 将调用 ReleaseItemExclusive 方法来释放会话存储数据,然后立即请求该会话存储数据。
为当前响应调用 SetAndReleaseItemExclusive 方法之前,锁定的会话存储数据可能已经在单独的线程上由对 ReleaseItemExclusive 方法的调用释放。这可能导致 SessionStateModule 实例设置和释放已经由其他会话释放和修改的会话状态存储数据。为避免这种情况,SessionStateModule 为每个请求都提供一个锁定标识符,以便修改锁定的会话存储数据。仅当数据存储区中的锁定标识符与 SessionStateModule 提供的锁定标识符匹配时,会话存储数据才能修改。
在权威文字面前,我再解释就显得是多余的。不过,通过我上面的代码分析及MSDN解释,我们可以明白三点:
1. 它说明了,为什么在Application的一系列事件中,PostMapRequestHandler事件要早于AcquireRequestState事件的原因。因为SessionStateModule要访问HttpContext.RequiresSessionState,但是这个属性又要等到给HttpContext.Handler赋值后才能获取到,而HttpContext.Handler的赋值操作是在PostMapRequestHandler事件中完成的,有意思吧。
2. 如果你没有关闭Session,SessionStateModule就一直在工作中,尤其是全采用默认设置时,会对每个请求执行一系列的调用。
3. 使用Session时,尤其是采用默认设置时,会影响并发访问。
Session对并发访问的影响
如果您觉得前面的文字可能不是太好理解,没关系,我特意做了几个实验页面,请继续往下看。
第一个页面,主要HTML部分:
<div>
<b>This is Default1.aspx</b>
</div>
第一个页面,后台代码部分:
protected void Page_Load(object sender, EventArgs e)
{
// 这里故意停5秒。
System.Threading.Thread.Sleep(5000);
}
第二个页面,主要HTML部分(无后台代码):
<div>
<b>This is Default2.aspx</b>
</div>
第三个页面,主要HTML部分(无后台代码):
<div>
<b>This is Default3.aspx</b>
</div>
现在轮到主框架页面上场了,主要HTML部分
<iframe src="Default1.aspx" width="150px"></iframe>
<iframe src="Default2.aspx" width="150px"></iframe>
<iframe src="Default3.aspx" width="150px"></iframe> <h1>
<asp:Literal ID="labResult" runat="server"></asp:Literal>
</h1>
主框架页面,后台代码部分:
public partial class _Default : System.Web.UI.Page
{
private static int count = 0; protected void Page_Load(object sender, EventArgs e)
{
// 因为前面的页面都没有使用Session,所以就在这里简单地使用一下了。
Session["Key1"] = System.Threading.Interlocked.Increment(ref count);
} protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e); this.labResult.Text = Session["Key1"].ToString();
}
}
以上代码实在太简单,我也不多说了。现在来看一下页面显示效果吧。首先看到的是这个样子:
5秒后,所有子框架的页面才会全部加载完成。
上面的示例代码写得很清楚,只有default1.aspx才会执行5秒,后面2个页面没有任何延迟,应该会直接显示的。但从结果可以看出:第一个页面请求阻塞了后面的所有页面请求!!
其实同样的场景还会发生在Ajax比较密集的网站中,这类网站中,一个页面也有可能发出多个请求,而且是在【上一个请求还没完成前】就发出了下一个请求,此时的请求过程其实与上面的子框架是一样的。有人可能想问:我的网站就没关Session,Ajax的使用也很多,为什么就没有这种感觉呢?其实,前面也说了:这里的并发影响只限于同一个用户的多次请求,而且如果服务器响应比较快时,我们通常也是不能察觉的,但它却实也是会阻塞后面的请求。
我们感觉不到Session的阻塞,是因为阻塞的时间不够长,而我的测试用例故意则让这种现象更明显了。 不管你们信不信,反正我是信了。
对于并发问题,我想谈谈我的想法:微软在Session中,使用了锁定的设计,虽然会影响并发,但是,设计本身是安全的、周密的。因为确实有可能存在一个用户的多个请求中会有修改与读取的冲突操作。微软是做平台的,他们不得不考虑这个问题。但现实中,这种冲突的可能性应该是很小的,或者是我们能控制的,在此情况下,会显得这个问题是不可接受的。
Session的缺点总结
任何事情都有二面性,优缺点都是兼有的。在评价一个事物时,我们应该要全面地分析它的优缺点,否则评价也就失去了意义。今天我们还是在批评Session的缺点前,先看看它的优点:只需要一行代码就可以方便的维持用户的会话数据。这其实是个伟大的实现!
但是,现在为什么还是有人会不使用它呢?比如我就不用它,除非做点小演示,否则我肯定不会使用它。为什么?
我个人认为这个伟大的实现,还是有些局限制性,或者说是一些缺点吧。现在我们再来看看Session的缺点:
1. 当mode="InProc"时,也就是默认设置时,容易丢失数据,为什么?因为网站会因为各种原因重启。
2. 当mode="InProc"时,Session保存的东西越多,就越占用服务器内存,对于用户在线人数较多的网站,服务器的内存压力会比较大。
3. 当mode="InProc"时,程序的扩展性会受到影响,原因很简单:服务器的内存不能在多台服务器间共享。
4. 虽然Session可以支持扩展性,也就是设置mode="SQLServer"或者mode="StateServer",但这种方式下,还是有缺点:在每次请求时,也不管你用不用会话数据,都为你准备好,这其实是浪费资源的。
5. 如果你没有关闭Session,SessionStateModule就一直在工作中,尤其是全采用默认设置时,会对每个请求执行一系列的调用。浪费资源。
6. 并发问题,前面有解释,也有示例。
7. 当你使用无 Cookie 会话时,为了安全,Session默认会使用 重新生成已过期的会话标识符 的策略,此时,如果通过使用 HTTP POST 方法发起已使用已过期会话 ID 发起的请求,将丢失发送的所有数据。这是因为 ASP.NET 会执行重定向,以确保浏览器在 URL 中具有新的会话标识符。
不可否认的是,或许有些人认为这些缺点是可以接受的,他们更看中Session的简单、易使用的优点,那么,Session仍然是完美的。
不使用Session的替代方法
对于前面我列出的Session的一些缺点,如果您认为你有些是不能接受的,那么,可以参考一下我提出的替代解决方法。
1. 如果需要在一个页面的前后调用过程中维持一些简单的数据,可以使用<input type="hidden" />元素来保存这些数据。
2. 您希望在整个网站都能共享一些会话数据,就像mode="InProc"那样。此时,我们可以使用Cookie与Cache相结合做法,自行控制会话数据的保存与加载。具体做法也简单:为请求分配置一个Key(有就忽略),然后用这个Key去访问Cache,以完成保存与加载的逻辑。如果要使用的会话数据数量不止一个,可以自定义一个类型或者使用一个诸如Dictionary, HashTable 这样的集合来保存它们。很简单吧,基本上这种方式就是与mode="InProc"差不多了。只是没有锁定问题,因此也就没有并发问题。
3. 如果您想实现mode="StateServer"类似的效果,那么可以考虑使用memcached这类技术,或者自己写个简单的服务,在内部使用一个或者多个Dictionary, HashTable来保存数据即可。这样我们可以更精确的控制读写时机。这种方法也需要使用Cookie保存会话ID。
4. 如果您想实现mode="SQLServer"类似的效果,那么可以考虑使用mongodb这类技术,同样我们可以更精确的控制读写时机。这种方法也需要使用Cookie保存会话ID。如果您没用使用过mongodb,可以参考我的博客: MongoDB实战开发 【零基础学习,附完整Asp.net示例】
从前面三种替代方法来看,如果不使用Session,那么Cookie就是必需的。其实Cookie本身就是设计用来维持会话状态的。只是它不适合保存过大的数据而已,因此,用它保存会话ID这样的数据,可以说是很恰当的。事实上,Session就是这样做的。
推荐方法:为了保持网站程序有较好的扩展性,且不需要保存过大的会话数据,那么,直接使用Cookie将是最好的选择。
由于Cookie保存在浏览器,且不安全,所以建议只保存诸如:id, key 之类的简单数据,需要其它的会话数据时再根据这些id, Key去获取。
到这里,我想我可以回答标题中的问题了:Session,其实是没有必要使用的,不用它,也能容易地实现会话数据的保存。
Asp.net MVC 中的Session
我们再来看一下Asp.net MVC中是如何使用Session的。Asp.net平台作为底层的框架,它提供了HttpContext.Session这个成员属性让我们可以方便地使用Session,但是在MVC中,Controller抽象类为也提供了这样一个属性,我们只要访问它就可以了(支持更好的测试性)。
回想一下,前面我们看到SessionStateModule是根据当前HttpHandler来决定是不是启用Session。但是现在Controller和Page是分开的, Controller又是如何使用Session的呢?要回答这个问题就要扯到路由了,简单地说:现在在MVC处理请求的时候,当前HttpHandler是 MvcHandler类的实例,它有如下定义:
public class MvcHandler : IHttpAsyncHandler, IHttpHandler, IRequiresSessionState {
因此,在Controller.Session中,它是访问的HttpContext.Session,而MvcHandler实现了IRequiresSessionState接口,所以,访问HttpContext.Session就可以获取到Session 。注意哦,我上面的代码取自MVC 2.0,从类型实现的接口可以看出,Session将一直有效,不能关闭,而且属于影响并发的那种模式。所以,此时你只能从web.config中全局关闭它。
说明,在MVC 3.0 和Asp.net 4.0中,才可以支持Controller订制Session的访问性。
在这种使用方式下,如果您不想继续使用Session,可以使用上面我列出的替代方法。
在MVC中,还有一个地方也在使用Session,那就是Controller.TempData这个成员属性。通常我们可能会这样使用它:
TempData["mydata"] = "aaaaaaaaaa"; // or other object
return RedirectToAction("Index");
在这种地方,这些保存到TempData的数据其实也是存放在Session中的。你可以从web.config中关闭Session,你就能看到异常了。对于这种使用方法,你仍然可以前面的替代方法,但是,还有另一种方法也能做为替代Session的方法。我们看一下Controller的一段代码:
protected virtual ITempDataProvider CreateTempDataProvider() {
return new SessionStateTempDataProvider();
}
TempData就是通过这种Provider的方式来支持其它的保存途径。而且在MvcFutures中,还有一个CookieTempDataProvider类可供使用。使用也很简单,获取MVC源码,编译项目MvcFutures,然后引用它,重写以上虚方法就可以了:
protected override ITempDataProvider CreateTempDataProvider()
{
return new Microsoft.Web.Mvc.CookieTempDataProvider(this.HttpContext);
}
注意哦,这里有2个陷阱:MVC 2的MvcFutures的CookieTempDataProvider并不能正常工作。至于我在尝试时,发现它是这样写的(注释部分是我加的):
public static IDictionary<string, object> DeserializeTempData(string base64EncodedSerializedTempData)
{
byte[] bytes = Convert.FromBase64String(base64EncodedSerializedTempData);
var memStream = new MemoryStream(bytes);
var binFormatter = new BinaryFormatter();
return binFormatter.Deserialize(memStream, null) as TempDataDictionary; // 这里会导致一直返回null
//return binFormatter.Deserialize(memStream, null) as IDictionary<string, object>; // 这样写才对嘛。
}
就算能运行,这样做会导致生成的Cookie的长度较大,因此容易导致浏览器不支持。最终我重写了以上代码(以及另一个序列化的代码):
public static IDictionary<string, object> DeserializeTempData(string base64EncodedSerializedTempData)
{
try {
return (new JavaScriptSerializer()).Deserialize<IDictionary<string, object>>(
HttpUtility.UrlDecode(base64EncodedSerializedTempData));
}
catch {
return null;
}
} public static string SerializeToBase64EncodedString(IDictionary<string, object> values)
{
if( values == null || values.Count == 0 )
return null; return HttpUtility.UrlEncode(
(new JavaScriptSerializer()).Serialize(values));
}
上面的方法虽然解决了序列化结果过长的问题,但它也引入了新的问题:由于使用IDictionary<string, object>类型,造成复杂类型在序列化时就丢失了它们的类型信息,因此,在反序列化时,就不能还原正原的类型。也正是因为此原因,这种方法将只适合保存简单基元类型数据。
现有的代码怎么办?
本来,这篇博客到这里就没有了。是啊,批也批过了,解决办法也给了,还有什么好说的,不过,突然想到一个很现实的问题,要是有人问我:Fish,我的代码很多的地方在使用Session,如果按你前面的方法,虽可行,但是要改动的代码比较多,而且需要测试,还要重新部署,这个工作量太大了,有没有更好的办法?
是啊,这个还真是个现实的问题。怎么办呢?
针对这个问题,我也认真的思考过,也回忆过曾经如何使用Session,以及用Session都做过些什么。一般说来,用Session基本上也就是保存一些与用户相关的临时信息,而且不同的页面使用的Session冲突的可能性也是极小的,使用方式以 mode="InProc" 为主。其实也就是Cache,只是方便了与“当前用户”的关联而已。
于是针对这个前提,继续想:现在要克服的最大障碍是并发的锁定问题。至于这个问题嘛,我们可以参考一下前面MSND中的说明,就是因为GetItemExclusive这些方法搞出来的嘛。想到这里,似乎办法也就有了:我也来实现一个使用Cache的Provider,并且在具体实现时,故意不搞锁定,不就行了嘛。
最终,我提供二个Provider,它们都是去掉了锁定相关的操作,试了一下,并发问题不存了。但有个问题需要说明一下,ProcCacheSessionStateStore采用Cache保存Session的内容,与 mode="InProc" 类似, CookieSessionStateStore则采用Cookie保存Session对象,但它有个限制,只适合保存简单基元类型数据(且不包含敏感信息),原因与CookieTempDataProvider一样。所以,请根据您的使用场景来选择合适的Provider
以下是使用方法:很简单,只要在web.config中加一段以下配置就好了:
<sessionState mode="Custom" customProvider="CookieSessionStateStore">
<providers>
<add name="ProcCacheSessionStateStore" type="Fish.SampleCode.ProcCacheSessionStateStore"/>
<add name="CookieSessionStateStore" type="Fish.SampleCode.CookieSessionStateStore"/>
</providers>
</sessionState>
好了,这次不用改代码了,在部署环境中,也只需要修改了一下配置就完事了。
警告:我提供的这二个Provider只是做了简单的测试,并没经过实际的项目检验,如果您需要使用,请自行测试它的可用性。
Session,有没有必要使用它?的更多相关文章
- session实现购物车
为实现简单的购物功能(购物车添加.账户查看.购物车商品删除.实时的购物商品数量及价格的计算显示.购物车商品数量可手动输入等),用session实现了一简单的以php语言为基础.连接MySQL数据库的购 ...
- Asp.net Core中使用Session
前言 2017年就这么悄无声息的开始了,2017年对我来说又是特别重要的一年. 元旦放假在家写了个Asp.net Core验证码登录, 做demo的过程中遇到两个小问题,第一是在Asp.net Cor ...
- 懒加载session 无法打开 no session or session was closed 解决办法(完美解决)
首先说明一下,hibernate的延迟加载特性(lazy).所谓的延迟加载就是当真正需要查询数据时才执行数据加载操作.因为hibernate当中支持实体对象,外键会与实体对象关联起来.如 ...
- 探索ASP.NET MVC5系列之~~~6.Session篇(进程外Session)
其实任何资料里面的任何知识点都无所谓,都是不重要的,重要的是学习方法,自行摸索的过程(不妥之处欢迎指正) 汇总:http://www.cnblogs.com/dunitian/p/4822808.ht ...
- Nhibernate的Session管理
参考:http://www.cnblogs.com/renrenqq/archive/2006/08/04/467688.html 但这个方法还不能解决Session缓存问题,由于创建Session需 ...
- nginx+iis+redis+Task.MainForm构建分布式架构 之 (redis存储分布式共享的session及共享session运作流程)
本次要分享的是利用windows+nginx+iis+redis+Task.MainForm组建分布式架构,上一篇分享文章制作是在windows上使用的nginx,一般正式发布的时候是在linux来配 ...
- zookeeper源码分析之六session机制
zookeeper中session意味着一个物理连接,客户端连接服务器成功之后,会发送一个连接型请求,此时就会有session 产生. session由sessionTracker产生的,sessio ...
- [转载]Cookie/Session的机制与安全
Cookie和Session是为了在无状态的HTTP协议之上维护会话状态,使得服务器可以知道当前是和哪个客户在打交道.本文来详细讨论Cookie和Session的实现机制,以及其中涉及的安全问题. 因 ...
- 修改session垃圾回收几率
<?php //修改session垃圾回收几率 ini_set('session.gc_probability','1'); ini_set('session.gc_divisor','2'); ...
- Nginx反向代理,负载均衡,redis session共享,keepalived高可用
相关知识自行搜索,直接上干货... 使用的资源: nginx主服务器一台,nginx备服务器一台,使用keepalived进行宕机切换. tomcat服务器两台,由nginx进行反向代理和负载均衡,此 ...
随机推荐
- 用Spring提高java观察者模式灵活性
在上篇博客 用java观察者模式解耦经典三层架构 的最后,用了一个Client类把Listener的实现类注冊到了LoginEventSource类中,假设须要加入�新的逻辑,加入�新的listene ...
- codeforces C. Cd and pwd commands 执行命令行
执行命令来改变路径 cd 并显示路径命令 pwd 一个节目的 抽样: input 7 pwd cd /home/vasya pwd cd .. pwd cd vasya/../petya pwd ou ...
- Android调试系列—使用android studio调试smali代码
1.工具介绍 使用工具 android killer:用于反编译apk包,得到smali代码 android studio:调试smali代码工具,或者使用idea,android studio就是在 ...
- C#基础:关键字和数据类型
[关键字] #region 和 #endregion 关键字可以折叠代码 checked 用于整型算术运算时控制当前环境中的溢出检查 unchecked 操作符用于整型算术运算时控制当前环境中的 ...
- C# 中文转拼音类
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace SU { ...
- java NumberForMate的使用
有时候我们需要将数字转化为字符串,并且转化后的长度要一定.比如00012这种类型.这时候我们就可以使用NumberForMate这个方法: NumberFormat numberFormat = Nu ...
- 百度地图HTML接口
<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content ...
- Sql 函数大全 (更新中...由难到简
1.字符处理类: 1.1 指定指定字符输出的次数 ) 结果:1a1a1a1a1a (5个1a)
- shell中的循环语句
for语法格式 for var in list;do commands done 其中list可以包含: 1) 直接写 for alpha in a b c d;do echo $alpha done ...
- python yield 理解
在别人的代码中看到yield这个关键字,看了几篇资料,说一下个人理解. 包含yield 关键字的函数成为一个迭代器,yield跟return的位置一样,只不过每次返回结果后,并没有退出,而是等待下一次 ...