开发者学堂课程【高校精品课-上海交通大学-企业级应用体系架构:Arch. Components 2】学习笔记,与课程紧密联系,让用户快速学习知识。
课程地址:https://developer.aliyun.com/learning/course/75/detail/15820
Arch. Components 2(一)
内容介绍:
一、有状态、无状态服务
二、scope 的取值
一、有状态、无状态服务
HTTP 是一个无状态的协议,是靠在服务器端创建会话状态,每次访问带入 Cookie,在 Cookie 里写入会话对象的ID,以这种方式维护状态。
也就是说,所有的用户状态都在服务器端维护,客户端只有对应会话状态对象的一个 ID,ID 随 HTTP 请求发到服务器端的时候,都会随 Cookie 发送过来,所以即便 HTTP 是一个无状态的协议,也可以通过这种方式把用户的状态维护起来。
但是HTTP对象是在服务器端的,所以要注意两件事情,第一:当服务客户端非常多的时候,Http Session 的对象就会非常多,会消耗资源。而服务器对象里面,服务器存在 Http 之后,另外一个问题,假设说有一个集群,除了服务器 A,还有服务器 B。由于有会话状态的问题,用户的会话状态在 A 服务器上,在当前会话领域,要保证每次发过来的请求都要在 A 机器上,不能在 B 上,称为会话的粘滞性。一旦创建会话,会自动粘到相应的服务器。Http Session的大小决定处理客户数的多少,要尽量节省使用。
在 Session 上加 timeout,因为 HTTP 协议无状态,不能判断用户正在浏览还是已经关闭网页。Http Session 在给定时间timeout 内没有更新或读取过,就认为客户端已经不在了,随即被取消。一旦取消掉,用户状态就丢了。
这就是有时访问网站时,明明已经登录过,但长时间不动,它会判断出来需要重新登陆,原因是对应的 Session对象已经没有了。一方面,要节约使用,对象尽量回收,加 timeout,用户在网站点击注销,收到请求可以明确的把它销毁掉,以这种方式控制它的生命周期;另一方面,存储的内容本身也要节约,不能存放很大的东西,占用资源很多,会迅速耗单。
Http Session 的存储过程,A 用户和 B 用户的会话对象就是一些 key,value,应该放的是服务器端对象的地址,而不是对象本身,所以 Http 协议用 Session 时一定要节约。
Http Session 是明确知道用户发送请求之后,在当前会话里建立一个 Session 对象来访问、记录用户的状态,比如说他的购物车对象,购物车对象本身是 key,value 的结构,会记录购买了哪些东西,买的实物本身就是一个集合,集合引用实际的对象。要处理这些,所以一个用户会有一个对话状态,除了 Http Session 是一个会话中创建一个对象,使用spring框架写应用,spring 框架里有 controler,按照分层架构,controler 还会调用 DAO 或者后台的 service,无论是controler,DAO 还是 service,他们也是对象。当一个应用服务器里有大量用户访问时,是一个服务所有对象,还是每一个用户都会有一个新对象,和 Http Session 道理是相同的吗?当对象数量很多的时候,服务器端资源会容易被耗干,这些应该如何控制呢?
看一个例子,在 convars 里面一个叫 stateful 的例子,对之前学过的分层结构进行简单的处理。在数据库里,将user这张表放在 bookstore 库里,表里放进两个人,结构如下图所示,有 user_id、nickname、tel 和 address,Tom和Jerry 两个人。
底层是实体类,user 类会映射 bookstore,spring的gpa,所以应该加这一行,否则会出错。
映射刚才这张表,字段和刚才是对应的;userReposirty 帮着创建,所以不需要写任何东西;再向上是服务层,就会暴露一些向上的查询方法或者增删改查的方法,我们只暴露了 findUserByid 这一个方法;接口层有一个实线,通过注入的 userDao 来访问返回值,就是按照id找到的值。userDao 要暴露一个具体的方法,刚才的 findone 的实现,通过注入,获取 GPA 生成的 userReposirty。通过 userReposirty 是 gpa 帮我们生成的getline的方法。逻辑是这样,一直到前面有一个 controler,controler 响应 finduser 参数的请求。就会调用注入的applicationcontext,获取上层的userservice对象,然后调用 finduserby。也就是说,controler 调 service,service 调 Dao,Dao 调 Reposirty,Reposirty 访问 user 的结构。
将程序运行一下,在浏览器输入 id 为1的,就是刚才看到的 Tom,id 为2的是 Jerry;换一个浏览器,访问同样的内容。两个浏览器,在服务器看来,二者是两个不同的会话,问题是在刚才的例子里,创建几个 usercontroler 的实例,usercontroler 用到了 userservice,userservice 的实现类又创建了几个实例呢?为了搞清楚这件事情,在usercontroler 里输出了 userservice 对象,直接 println 这个对象的话,就会输出对象对应的 userID,就是在控制台上看到的 userID。同时打印一下自身的浏览,也就是说,usercontroler有几个,引用的 userservice 有几个。刚才在两个浏览器里各发了几个请求,输出了这样的内容,在这里面,userservice 就是服务类,他的id有4e9c516、6732de07这样的输出,就是说他只有两个对象,而在 usercontroler 里面,三个 id 各不相同。多运行几次,输出结果与上述相同。
在整个过程中,只创建了两个 userservice 对象,usercontroler 每个都不一样,也就是说,每次发送请求的时候,都创建 usercontroler 的对象,而 usercontroler 对象后面用到的 userservice 对象却始终只有两个,因为在两个浏览器里面,现在会话就只有两个,每次发送请求不同,唯一差异在 usercontroler 类里面,scope 里的内容不相同,usercontroler 的 scope 是 Prototype;userservice 的 scope 里面是 session。这就是他们带来的差异。
但显然这并不是我们想要的,controler 并没有什么数据成员,所以创建太多 controler 是没有意义的。实际上只要有一个 controler 就可以了。
将这一行注释掉,重新运行,看一下效果。在两个浏览器分别访问3次,前三次是 suffer 发出的,是同一个对象,后三次也是同一个对象,这6次只有一个 controler,很显然功能没有什么变化。
但在服务器端的对象创建就产生了很大的变化,scope 是 Prototype 时是每产生一个请求就会创建一个 controler对象,被注解的类就会创建一个新的对象,在 session 这边没有改变过,在一个浏览器里创建一个对象,而把 controler上的scope 注解掉之后,controler 就变成了全局唯一的。这个差异叫做服务的对象有没有状态。