深圳嘉华_Net中高级开发工程师面试题(二)

1、Redis 和 MemCache 的区别

  1. 性能:
    redis 只能使用单核,而 Memcache 可以使用多核,所以在比较上,平均每一个核上Redis在存储小数据时比Memcached性能更高。而在100k以上的数据中,Memcache性能要高于Redis,虽然Redis最近也在存储大数据的性能上进行优化,但是比起Memcache,还是稍有逊色。说了这么多,结论是,无论你使用哪一个,每秒处理请求的次数都不会成为瓶颈。(比如瓶颈可能会在网卡)

  2. 内存利用率:
    如果要说内存使用效率,使用简单的key-value存储的话,Memcache的内存利用率更高,而如果Redis采用hash结构来做key-value存储,由于其组合式的压缩,其内存利用率会高于Memcache。当然,这和你的应用场景和数据特性有关。

  3. 数据持久化和数据同步
    如果你对数据持久化和数据同步有所要求,那么推荐你选择Redis,因为这两个特性Memcache都不具备。即使你只是希望在升级或者重启系统后缓存数据不会丢失,选择Redis也是明智的。

  4. 具体应用需求
    当然,最后还得说到你的具体应用需求。Redis相比Memcache来说,拥有更多的数据结构和并支持更丰富的数据操作,通常在Memcached里,你需要将数据拿到客户端来进行类似的修改再set回去。这大大增加了网络IO的次数和数据体积。在Redis中,这些复杂的操作通常和一般的GET/SET一样高效。所以,如果你需要缓存能够支持更复杂的结构和操作,那么Redis会是不错的选择。

2、Net中Cache和Redis的区别

redis是分布式缓存,是将数据随机分配到不同服务器的,catch属于单机缓存,只能本机访问
cache 比较适合小数据量的缓存使用,因为缓存是直接使用的应用程序部署的服务器的内存。
如果是不需要缓存大量的数据,就不用部署redis这种服务器集群的缓存方案。

3、JWT和Outh2.0关系

3.1、OAuth

3.1.1、定义

OAuth: An open protocol to allow secure authorization in a simple and standard method from web, mobile and desktop applications. 也就是说OAuth是一个开放标准,提供了一种简单和标准的安全授权方法,允许用户无需将某个网站的用户名密码提供给第三方应用就可以让该第三方应用访问该用户在某网站上的某些特定信息(如简单的个人信息)。

3.1.2、 OAuth 2.0 协议处理流程

     +--------+                               +---------------+
     |        |--(A)- Authorization Request ->|   Resource    |
     |        |                               |     Owner     |
     |        |<-(B)-- Authorization Grant ---|               |
     |        |                               +---------------+
     |        |
     |        |                               +---------------+
     |        |--(C)-- Authorization Grant -->| Authorization |
     | Client |                               |     Server    |
     |        |<-(D)----- Access Token -------|               |
     |        |                               +---------------+
     |        |
     |        |                               +---------------+
     |        |--(E)----- Access Token ------>|    Resource   |
     |        |                               |     Server    |
     |        |<-(F)--- Protected Resource ---|               |
     +--------+                               +---------------+

3.2、Token

Token就是获取信息的凭证,如上述的Access Token,关于Token的具体使用有相应的RFC文件指导: The OAuth 2.0 Authorization Framework: Bearer Token Usage

3.2.1、 Access Token

 bearer
GET /resource/1 HTTP/1.1
Host: example.com
Authorization: Bearer mF_9.B5f-4.1JqM

3.2.2、 认证请求方式

使用Token的认证请求的方式有三种,客户端可以选择一种来实现,但是不能同时使用多种:

3.2.2.1、 放在请求头

放在Header的Authorization中,并使用Bearer开头:

GET /resource HTTP/1.1
Host: server.example.com
Authorization: Bearer mF_9.B5f-4.1JqM

3.2.2.2、 放在请求体

放在body中的access_token参数中,并且满足以下条件:

HTTP请求头的Content-Type设置成application/x-www-form-urlencoded.
Body参数是single-part.
HTTP请求方法应该是推荐可以携带Body参数的方法,比如POST,不推荐GET.
示例:

POST /resource HTTP/1.1
Host: server.example.com
Content-Type: application/x-www-form-urlencoded

access_token=mF_9.B5f-4.1JqM

3.2.2.3、 放在URI

放在uri中的access_token参数中

GET /resource?access_token=mF_9.B5f-4.1JqM
Host: server.example.com

3.3、JWT

JWT: JSON Web Tokens, 这是一个开放的标准,规定了一种Token实现方式,以JSON为格式,相应的RFC文件为: JSON Web Token (JWT)

JWT的结构分为三个部分:

  • Header: 存放Token类型和加密的方法
  • Payload: 包含一些用户身份信息.
  • Signature: 签名是将前面的Header,Payload信息以及一个密钥组合起来并使用Header中的算法进行加密
    最终生成的是一个有两个.号连接的字符串,前两个部分是Header和Payload的Base64编码,最后一个是签名,如下:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.MejVLl-m7KMfaay0nXxDWGEVPWsQ2f6SZnTvq4fXaLI
详细内容参考: Introduction to JSON Web Tokens

3.4、问题解答

3.4.1、 什么是OAuth?

也就是说OAuth是一个开放标准,提供了一种简单和标准的安全授权方法,允许用户无需将某个网站的用户名密码提供给第三方应用就可以让该第三方应用访问该用户在某网站上的某些特定信息(如简单的个人信息),现在一般用的是OAuth 2.0(不兼容1.0).

3.4.2、 什么是Token?

Token就是获取信息的凭证,如上述的Access Token,让客户端无需用户密码即可获取用户授权的某些资源.

3.4.3、 什么又是JWT?

JSON Web Tokens, 这是一个开放的标准,规定了一种Token实现方式,以JSON为格式.

3.4.4、 三者之间又是什么关系?

这三个相互连接且是由大到小的一种关系,OAuth规定授权流程,Token为其中一环的一个信息载体,具体的一种实现方式由JWT规定.

4、分布式与集群的区别

单机结构
我想大家最最最熟悉的就是单机结构,一个系统业务量很小的时候所有的代码都放在一个项目中就好了,然后这个项目部署在一台服务器上就好了。整个项目所有的服务都由这台服务器提供。这就是单机结构。那么,单机结构有啥缺点呢?我想缺点是显而易见的,单机的处理能力毕竟是有限的,当你的业务增长到一定程度的时候,单机的硬件资源将无法满足你的业务需求。此时便出现了集群模式,往下接着看。
分布式结构
单机处理到达瓶颈的时候,你就把单机复制几份,这样就构成了一个“集群”。集群中每台服务器就叫做这个集群的一个“节点”,所有节点构成了一个集群。每个节点都提供相同的服务,那么这样系统的处理能力就相当于提升了好几倍(有几个节点就相当于提升了这么多倍)。但问题是用户的请求究竟由哪个节点来处理呢?最好能够让此时此刻负载较小的节点来处理,这样使得每个节点的压力都比较平均。要实现这个功能,就需要在所有节点之前增加一个“调度者”的角色,用户的所有请求都先交给它,然后它根据当前所有节点的负载情况,决定将这个请求交给哪个节点处理。这个“调度者”有个牛逼了名字——负载均衡服务器。集群结构的好处就是系统扩展非常容易。如果随着你们系统业务的发展,当前的系统又支撑不住了,那么给这个集群再增加节点就行了。但是,当你的业务发展到一定程度的时候,你会发现一个问题——无论怎么增加节点,貌似整个集群性能的提升效果并不明显了。这时候,你就需要使用微服务结构了。

分布式结构
从单机结构到集群结构,你的代码基本无需要作任何修改,你要做的仅仅是多部署几台服务器,每台服务器上运行相同的代码就行了。但是,当你要从集群结构演进到微服务结构的时候,之前的那套代码就需要发生较大的改动了。所以对于新系统我们建议,系统设计之初就采用微服务架构,这样后期运维的成本更低。但如果一套老系统需要升级成微服务结构的话,那就得对代码大动干戈了。所以,对于老系统而言,究竟是继续保持集群模式,还是升级成微服务架构,这需要你们的架构师深思熟虑、权衡投入产出比。

分布式结构就是将一个完整的系统,按照业务功能,拆分成一个个独立的子系统,在分布式结构中,每个子系统就被称为“服务”。这些子系统能够独立运行在web容器中,它们之间通过RPC方式通信。举个例子,假设需要开发一个在线商城。按照微服务的思想,我们需要按照功能模块拆分成多个独立的服务,如:用户服务、产品服务、订单服务、后台管理服务、数据分析服务等等。这一个个服务都是一个个独立的项目,可以独立运行。如果服务之间有依赖关系,那么通过RPC方式调用。这样的好处有很多:

  • 系统之间的耦合度大大降低,可以独立开发、独立部署、独立测试,系统与系统之间的边界非常明确,排错也变得相当容易,开发效率大大提升。
  • 系统之间的耦合度降低,从而系统更易于扩展。我们可以针对性地扩展某些服务。假设这个商城要搞一次大促,下单量可能会大大提升,因此我们可以针对性地提升订单系统、产品系统的节点数量,而对于后台管理系统、数据分析系统而言,节点数量维持原有水平即可。
  • 服务的复用性更高。比如,当我们将用户系统作为单独的服务后,该公司所有的产品都可以使用该系统作为用户系统,无需重复开发。

5、EF的三种模式

  • DataBaseFirst传统的表驱动方式创建EDM,然后通过EDM生成模型和数据层代码。除生成实体模型和自跟踪实现模型,还支持生成轻型DbContext。
  • ModelFirst先创建EDM模型,再生成DDL数据库脚本和模型和数据层代码。除生成实体模型和自跟踪实现模型,支持生成轻型DbContext。
  • CodeFirst手动创建POCO模型,数据层DbContext及映射关系,通过Database.SetInitializer生成数据库,这种方式较灵活,但是代码工作较多。
    虽然Code First灵活,但是我们不可能手工去写大量的POCO类和映射关系。所以我们此次开发选择Model First.

6、什么是线程同步

实际应用中多个线程往往需要共享数据,因此必须使用同步技术,确保一次只有一个线程访问和改变共享数据。同步又分为进程内部线程的同步以及进程之间线程的同步。

进程内部线程同步

  1. lock : 使用比较简单 lock(obj){ Synchronize part }; 只能传递对象,无法设置等待超时;
  2. InterLocked: 原子操作,提供了以线程安全的方式递增,递减,交换和读取值的方法;
  3. Monitor: lock语句等同于Monitor.Enter() ,同样只能传递对象,无法设置等待超时:
Monitor.Enter(obj){
                //Synchronized part
}finally{
    Monitor.Exit(obj);
}

另外使用Monitor.TryEnter(),可以传递等待超时,若获取锁,则布尔参考变量设为true,执行同步操作;若超时未获取锁,则布尔参考变量设为false,执行其他操作; 如下:

bool lockTaken=false;
Monitor.TryEnter(obj, 500, ref lockTaken);
if(lockTaken){
    try
    {
        //Synchronized part
    }
    finally
    {
        Monitor.Exit(obj);
    }
}else{
    //don't aquire the lock, excute other parts
}

进程之间线程同步
1、WaitHandle: 一个抽象基类,用于等待一个信号的设置。 常用方法如下:
WaitOne(): 等待一个信号的出现,可设置超时;
WaitAll(): 等待多个信号的出现,可设置超时;
WaitAny(): 等待任意一个信号的出现,可设置超时;
2、Mutex类(Mutual Exclusion 互斥),EventWaitHandle类,Semaphore类 均派生自WaitHandle类。
Mutex: 与Monitor 类似,只有一个线程能够获取锁定。利用WaitOne() 获取锁定,利用ReleaseMutex() 解除锁定。构造函数使用如下:

bool isNew = false;
mutex = new Mutex(false, "Mutex1", out isNew);

参数1:锁创建后是否由主调线程拥有。 如果设为true,相当于调用了WaitOne(),需要释放,否则其他线程无法获取锁;
参数2:锁名称,可通过OpenExist()或TryOpenExist() 打开已有锁,因为操作系统识别有名称的互锁,所以可由不同的进程共享。若锁名称为空,就是未命名的互锁,不能在多个进程之间共享;
参数3: 是否为新创建的互锁;
下面的例子演示Mutex 在进程之间的使用:

class Program
{
    private static Mutex mutex = null;  
    static void Main(string[] args)
    {
        bool isNew = false;
        mutex = new Mutex(false, "Mutex1", out isNew);
        Console.WriteLine("Main Start....");
        mutex.WaitOne();
        Console.WriteLine("Aquire Lock and Running....");
        Thread.Sleep(10000);
        mutex.ReleaseMutex();
        Console.WriteLine("Release Lock....");
        Console.WriteLine("Main end....");
        Console.ReadLine();
    }
}

3、Semaphore: 信号量的作用于互斥锁类似,但它可以定义一定数量的线程同时使用。下面是构造函数:

bool isNew = false;
semaphore = new Semaphore(3, 3, "semaphore1", out isNew);

参数1:创建后,最初释放的锁的数量,如参数1设为2,参数2设为3,则创建后只有2个锁可用,另1个已经锁定;
参数2:定义可用锁的数量;
参数3: 信号量的名称,与Mutex类似;
参数4:是否为新创建的互锁;
以下例子创建了信号量“semaphore1”,利用Parallel.For() 同步运行Func1() ,在Func1() 中,当线程获取信号量锁,释放锁或等待超时,都会在控制台里输出,

class Program
{
	private static Semaphore semaphore = null;
	static void Main(string[] args)
	{
	
	    Console.WriteLine("Main Start....");
	    bool isNew = false;
	    semaphore = new Semaphore(3, 3, "semaphore1", out isNew);
	    Parallel.For(0, 6, Func1);
	    Console.WriteLine("Main end....");
	    Console.ReadLine();
	}

	static void Func1(int index)
	{
	    Console.WriteLine("Task {0} Start....",Task.CurrentId);
	    bool isComplete = false;
	    while (!isComplete)
	    {
	        if (semaphore.WaitOne(1000))    
	        {
	            try
	            {
	                Console.WriteLine("Task {0} aquire lock....", Task.CurrentId);
	                Thread.Sleep(5000);
	            }
	            finally
	            {
	                semaphore.Release();
	                Console.WriteLine("Task {0} release lock....", Task.CurrentId);
	                isComplete = true;
	            }
	        }
	        else
	        {
	            Console.WriteLine("Task {0} timeout....", Task.CurrentId);
	        }
	    }
	}
}

运行结果,线程1,2,3首先获取信号量锁,线程4,5,6在等待,直到1,2,3释放,

4、AutoResetEvent 类:可以使用事件通知其他任务,构造函数为 public AutoResetEvent(bool initialState)。

当initialState=true,处于signaled 模式(终止状态),调用waitone() 也不会阻塞任务,等待信号,调用Reset()方法,可以设置为non-signaled 模式;
当initialState=fasle,处于non-signaled 模式(非终止状态),调用waitone() 会等待信号阻塞当前线程(可以在多个线程中调用,同时阻塞多个线程),直到调用set()发送信号释放线程(调用一次,只能释放一个线程),一般使用这种方式;

以下例子创建5个任务,分别调用waitone()阻塞线程,接着每隔2s 调用set(),

private static AutoResetEvent autoReset = new AutoResetEvent(false);
static void Main(string[] args)
{
    Console.WriteLine("Main Start....");
    for (int i = 0; i < 5; i++)
    {
        Task.Factory.StartNew(() =>
        {
            Console.WriteLine("{0} Start....", Task.CurrentId);
            autoReset.WaitOne();
            Console.WriteLine("{0} Continue....", Task.CurrentId);
        });
    }
    for (int i = 0; i < 5;i++ )
    {
        Thread.Sleep(2000);
        autoReset.Set();
    }
    Console.WriteLine("Main end....");
    Console.ReadLine();
}

运行结果每次顺序略有不同,释放是随机的:

5、ManualResetEvent 类:功能基本上和AutoSetEvent类似,但又一个不同点:

使用AutoSetEvent,每次调用set(),切换到终止模式,只能释放一个waitone(),便会自动切换到非终止模式;但ManualResetEvent,调用set(),切换到终止模式,可以释放当前所有的waitone(),需要手动调用reset()才能切换到非终止模式。

以下例子说明了这个不同的:

 private static ManualResetEvent manualReset = new ManualResetEvent(false);
 static void Main(string[] args)
 {
     Console.WriteLine("Main Start....");
     for (int i = 0; i < 5; i++)
     {
         Task.Factory.StartNew(() =>
         {
             Console.WriteLine("{0} Start....", Task.CurrentId);
             manualReset.WaitOne();
             Console.WriteLine("{0} Continue....", Task.CurrentId);
         });
     }
     Thread.Sleep(2000);
     manualReset.Set();
     manualReset.WaitOne();
     Console.WriteLine("it doesn't work now, Main continue....");
     manualReset.Reset();
     manualReset.WaitOne();
     Console.WriteLine("Main end....");
     Console.ReadLine();
 }

7、事务的四大特性

事务具有原子性、一致性、隔离性、持续性四个属性,缩写字母为ACID。
(1)原子性:事务是一个工作单元,事务中的所有修改要么提交、要么撤销,在事务完成之前如果系统出现故障,重新启动时SQL Server会撤销所做的修改。
(2)一致性:一致性是指数据的状态,RDMS提供了以并发事务修改和查询数据的能力。
(3)隔离性:隔离是用于控制访问数据的机制,确保事务所访问数据是在其期望的一致性级别中的数据,SQL Server支持两种不同的模式来处理隔离:基于锁的传统模式和基于行版本控制的新模式,在企业内部部署的SQL Server中,默认是基于锁的模式。
(4)持续性:数据修改写入到数据库磁盘上的数据部分之前,总是先写入到数据库的事务日志磁盘,在提交之后,指令记录在事务日志的磁盘上,在尚未修改磁盘上的数据部分之前,事务被认为是持续的,在系统正常或是出现故障启动时,SQL Server将检查每个数据库的事务日志并执行具有两个阶段的恢复过程-重做和撤销。

8、事务的锁

排他锁:当试图修改数据时,事务会请求数据资源的一个排他锁,而不管其隔离级别,如果授予了锁,那么排他锁知道事务结束才会被解除,对于单语句事务意味着直到语句完成锁定才会被解除,对于多语句事务意味着直到完成所有语句并通过COMMIT TRAN或ROLLBACK TRAN命令结束才会解除锁定。排他锁之所以被称为排他,是因为如果一个事务正在修改行,直到事务完成,其他事务都不能修改相同的行,这是默认的修改行为。然而,另外一个事物能不能读取相同的行,取决于它的隔离级别。

共享锁:当试图读取数据时,事务默认请求数据资源的一个共享锁,并且一旦语句完成资源读取,会立即释放资源的共享锁。共享锁之所以被称为共享,是因为多个事务可以同时持有相同资源的共享锁。虽然在修改数据时,不能修改锁的模式和所需的持续时间,但是通过改变其隔离级别,可以在读取数据时控制锁定的处理方式。

更新锁
更新 (U) 锁可以防止通常形式的死锁。一般更新模式由一个事务组成,此事务读取记录,获取资源(页或行)的共享 (S) 锁,然后修改行,此操作要求锁转换为排它 (X) 锁。如果两个事务获得了资源上的共享模式锁,然后试图同时更新数据,则一个事务尝试将锁转换为排它 (X) 锁。共享模式到排它锁的转换必须等待一段时间,因为一个事务的排它锁与其它事务的共享模式锁不兼容;发生锁等待。第二个事务试图获取排它 (X) 锁以进行更新。由于两个事务都要转换为排它 (X) 锁,并且每个事务都等待另一个事务释放共享模式锁,因此发生死锁。

若要避免这种潜在的死锁问题,请使用更新 (U) 锁。一次只有一个事务可以获得资源的更新 (U) 锁。如果事务修改资源,则更新 (U) 锁转换为排它 (X) 锁。否则,锁转换为共享锁。

9、事务并发过程中可能出现的四种问题

1、脏读:读取未提交的数据。一个进程更新了数据但在另一个进程读取相同数据之前未提交该更新。第二个进程所读取的数据处于一种不一致状态。
2、丢失修改:一个进程读取了数据,并对数据执行了一些计算,然后根据这些计算更新数据。如果两个进程都是先读取数据,然后再根据他们所读取数据更新,其中一个进程可能会覆盖另一个进程的更新。
3、不可重复读:在同一事物的两次读取中,进程读取相同的资源得到不同的值。当第二个进程在第一个进程的两次读取之间更新数据,就会发生这种情况。
4、幻读:当一个进程对一定范围内的行执行操作,而另外一个进程对该范围内的行执行不兼容的操作,这时会发生幻读。例如,一个进程删除的符合某筛选器的所有行,在删除事务期间,另一个进程插入符合该筛选器的新行。则新行被认为是幻影行。

10、事务的隔离级别

READ_UNCOMMITTED:读取者不取锁,会出现脏读现象(读取者读到了写入者还没有提交的数据)。

READ_COMMITTED:它是企业内部部署的SQL Server默认隔离级别。读取者获得共享锁,但是读取完成后便不再持有锁。解决了脏读,但会出现丢失更新(丢失更新主要发生在两个事务读取一个值时,同时基于读取的值进行更新,由于在该隔离级别中读取后不会再该资源上持有锁,两个事务都可以更新其值,并且最后更新该值的事务将会覆盖另外一个事务的更新。产生的主要原因还是读取者在读取完成后便不再持有共享锁导致的)和不可重复读(在读取间隙,读取者不再持有共享锁,那么写入者便可以更改数据造成前后读取不一致的现象)的问题。

Repeatable Read:读取者直到事务结束一直持有共享锁。这意味着直到读取者事务结束都没有写入者能够修改资源。解决了丢失更新和不可重复读的问题,但是容易引发死锁(多个读取者第一次都获得了共享锁但是在接下来谁都无法获得排他锁的现象。原因就是锁的持续时间到了事务结束之后,然而事务既可以读取也可以写入导致的问题)虽然这种模式能够保证读取者前后读取的一致性,但是会出现幻读(共享锁还是互斥锁都 保留到了事务结束,但是无法阻止其他人运行新增操作,导致第一次查询时没有数据,第二次查询时却有了数据。被称为“幻读”)。

Serializable
为了防止幻影读取,需要将隔离级别提升为SERIALIZABLE,最重要的部分是SERIALIZABLE隔离级别的行为类似于REPEATABLE READ即它要求读取者获取一个共享锁来进行读取,并持有锁到事务结束,但是SERIALIZABLE隔离级别添加了另外一个方面-在逻辑上,该隔离级别要求读取者锁定查询筛选所限定的键的整个范围。这意味着读取者锁定的不仅是查询筛选限定的现有行,也包括将来行,或者准确地说,它会阻止其他事务尝试添加读取者查询筛选限定的行。

11、AJAX的底层实现原理

Ajax核心—-----XMLHttpRequest
上面我们大概感受了一下Ajax的过程,我们发现发送异步请求才是核心,事实上它就是XMLHttpRequest,整个Ajax之所以能完成异步请求完全是因为这个对应可以发送异步请求的缘故。而且我们又发现上面那个事件就是整个处理过程的核心,可以根据不同状态执行不同操作,其实它就是XMLHttpRequest的方法onreadystatechange,它在每次状态发生改变时都会触发。那么是谁取得的返回信息呢?它就是XMLHttpRequest的另一个方法responseText(当然还有responseXML)。(⊙o⊙)哦,我们还没有说发送给谁以及执行发送操作,这两个就是XMLHttpRequest的open和send方法。Y(o)Y,其他的当然还有了,我们直接列出来吧:

XMLHttpRequest对象属性
readyState:请求状态,开始请求时值为0直到请求完成这个值增长到4
responseText:目前为止接收到的响应体,readyState<3此属性为空字符串,=3为当前响应体,=4则为完整响应体
responseXML:服务器端相应,解析为xml并作为Document对象返回
status:服务器端返回的状态码,=200成功,=404表示“Not Found”
statusText:用名称表示的服务器端返回状态,对于“OK”为200,“Not Found”为400

方法
setRequestHeader():向一个打开但是未发生的请求设置头信息
open():初始化请求参数但是不发送
send():发送Http请求
abort():取消当前相应
getAllResponseHeaders():把http相应头作为未解析的字符串返回
getResponseHeader():返回http相应头的值

事件句柄
onreadystatechange:每次readyState改变时调用该事件句柄,但是当readyState=3有可能调用多次
代码实现
ajax.js文件代码,也是我们说的主要内容,是一个javascript文件,所有的Ajax操作都在这里:
//与服务器通信

function talktoServer(url) {
	var req = newXMLHTTPRequst();
	var callbackHandler = getReadyStateHandler(req);
	req.onreadystatechange = callbackHandler;
	req.open("POST", url, true);
	req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
	var testmsg = document.getElementById("testmsg");
	var msg_value = testmsg.value;
	req.send("msg="+msg_value);
}

//创建XMLHTTP对象

function newXMLHTTPRequst() {
	var xmlreq = false;
	if (window.XMLHttpRequest) {
		xmlreq = new XMLHttpRequest();
	} 
	else if (window.ActiveXobject) {
		try{
			xmlreq=new ActiveXObject("Msxm12.XMLHTTP");
		} 
		catch (e1) {
			try {
				xmlreq = new ActiveXObject("Microsoft.XMLHTTP");
			} 
			catch (e2) {
			}
		}
	}
	return xmlreq;
}

//服务器回调函数

function getReadyStateHandler(req) {
	return function() {
		if (req.readyState == 4) {
			if (req.status == 200) {
			var msg_display = document.getElementById("msg_display");
			msg_display.innerHTML = req.responseText;
			} else {
				var hellomsg = document.getElementById("hellomsg");
				hellomsg.innerHTML = "ERROR" + req.status;
			}
		}
	}
}

12、MVC的过滤器有哪些

MVC过滤器一共分为四个:ActionFilter(方法过滤器),ResultFilter(结果过滤器,感觉不是很好听,就这样叫吧),AuthorizationFilter(授权过滤器),ExceptionFilter(异常处理过滤器)

在这里插入图片描述

13、抽象类和接口的异同

在这里插入图片描述

14、垂直拆分和水平拆分

垂直(纵向)拆分:是指按功能模块拆分,比如分为订单库、商品库、用户库…这种方式多个数据库之间的表结构不同。
优点:

  • 拆分后业务清晰,拆分规则明确。
  • 系统之间整合或扩展容易。
  • 数据维护简单。

缺点:

  • 部分业务表无法join,只能通过接口方式解决,提高了系统复杂度。
  • 受每种业务不同的限制存在单库性能瓶颈,不易数据扩展跟性能提高。
  • 事务处理复杂。

水平(横向)拆分:将同一个表的数据进行分块保存到不同的数据库中,这些数据库中的表结构完全相同。
优点:

  • 不存在单库大数据,高并发的性能瓶颈。
  • 对应用透明,应用端改造较少。
  • 按照合理拆分规则拆分,join操作基本避免跨库。
  • 提高了系统的稳定性跟负载能力。

缺点:

  • 拆分规则难以抽象。
  • 分片事务一致性难以解决。
  • 数据多次扩展难度跟维护量极大。
  • 跨库join性能较差。

15、 聚集索引和非聚集索引区别

区别:

  • 聚集索引一个表只能有一个,而非聚集索引一个表可以存在多个
  • 聚集索引存储记录是物理上连续存在,而非聚集索引是逻辑上的连续,物理存储并不连续
  • 聚集索引:物理存储按照索引排序;聚集索引是一种索引组织形式,索引的键值逻辑顺序决定了表数据行的物理存储顺序。
  • 非聚集索引:物理存储不按照索引排序;非聚集索引则就是普通索引了,仅仅只是对数据列创建相应的索引,不影响整个表的物理存储顺序。
  • 索引是通过二叉树的数据结构来描述的,我们可以这么理解聚簇索引:索引的叶节点就是数据节点。而非聚簇索引的叶节点仍然是索引节点,只不过有一个指针指向对应的数据块。

优势与缺点:
聚集索引插入数据时速度要慢(时间花费在“物理存储的排序”上,也就是首先要找到位置然后插入),查询数据比非聚集数据的速度快。

发布了43 篇原创文章 · 获赞 3 · 访问量 7513

猜你喜欢

转载自blog.csdn.net/huan13479195089/article/details/105400063