开发者学堂课程【高校精品课-上海交通大学-企业级应用体系架构:Arch. Components 2】学习笔记,与课程紧密联系,让用户快速学习知识。
课程地址:https://developer.aliyun.com/learning/course/75/detail/15820
Arch. Components 2(二)
内容介绍:
一、有状态、无状态服务
二、scope 的取值
二、scope 的 取值
singleton 的含义就是单个的,只有一个实例的应用,它是默认值,所以在 controler 里将其注销掉就是默认值,在整个spring 的 ioc 容器中,只有单个对象,无论怎么调用都不变;
prototype 的意思是有多少个请求就创建多少个对象,
session 在整个 Http Session 的生命周期里,在一个浏览器里,发送三次请求, 第一次请求之后,就创建 Http Session 对象,而且没有超时因为连续点击三次,这3次被当作一个 Http Session 对象,在3个动作产生的一个 Http Session 对象的生命周期里,只创建了一个对象;
Request、Application、websocket 这三种不常用。
当使用 singleton 的时候,整个系统是无状态的。client 可能有 A、B、C,发送请求过来的时候,server 刚启动的时候什么都没有,发现要由 usercontroler 来处理,就创建 usercontroler 的对象,usercontroler 的对象实际是在spring容器里运行,它的默认值是 singleton,所以它只有一个。
请求 B、C 发送过来的时候,同样调用到这个对象上。好处是在整个运行过程中,只有一个对象,节省内存;缺点是假设在 usercontroler 里定义一个变量 count,存在的问题是 count 变成 A、B、C 公用的,假设在 usercontroler 类里面还有一个方法,叫做 inkerment,它的逻辑是把 count 做++操作,比如count初始值是0,请求 A 执行 inkement 方法,得到 count 值为1;B访问的时候,同样执行上面的操作,得到count值为2;这时A访问也会看到是2,原因是在内存中,只有一个对象,它所有的属性变成大家公用的。所谓无状态,不是说没有状态,没有属性,而是不为单个的用户维护他们各自的状态,和前面讲到的 Http Session 是不一样的。Http Session 是 A、B、C 各有一个,现在是共用一个,所有不会专门记录 A 只进入一次。那会不会乱呢?我们要使用它的优点,回避它的缺点,只要不带这样的属性就不会出乱,就是在做一些处理。
在 Usercontroler 中,application 和 context 只注入一次,除此之外都是方法,没有任何自己的状态,比如 int、count,所以就不用为不同的用户维护他们各自的状态。所以只需要一个实例就足够了。注销掉之后,可以看到并没有出错。所以无状态不是没有状态,而是不为单独的客户端维护他们各自的状态。如一个科学计算的 controler,就执行一些计算任务,给定数据,输出结果,不保存任何东西,就不会有任何问题。
无状态服务是我们追求的,因为它在客户端发送请求后,共用一个对象,只要有一个对象,就可以处理所有请求,能够节省资源。所有客户端全部共享资源。
Prototype 是有状态,当有一个新的实例产生的时候,引用的对象的 scope 是 Prototype,就会创建一个新的对象出来。
还是刚才的例子,当服务器端有一个对象是有状态,即 section 是 Prototype 时,也就是没有把 scope Prototype注销掉,A 请求发送过来之后,发现要由 usercontroler 来处理,就创建 usercontroler,它的类型是 Prototype,就和用户关联起来;当 B 发送请求时,又创建一个新的 usercontroler 专门为它服务,C 也是一样。这时占用的内存就会比较多,服务器有它自己的逻辑,不会一遇到 Prototype 就创建对象,如果这样,就会启动拒绝服务攻击,DBOS,服务攻击来攻击网站,马上就要崩溃。显然它不能这样做。服务器有它自己的逻辑,但服务器的逻辑只能保证不死机,可能在用户端会话特别多时,维护会话状态而导致性能特别慢。服务器端可以用这样的逻辑,当用户 A 访问的时候,就创建一个对象为它服务,当用户 B 访问的时候,同样创建一个对象为它服务,但是服务器端会有一个配置文件,会配置一个叫实例池的东西,对所有类型的类,可以创建对象放在实例池。约定对于 usercontroler,实例池的最大尺寸是2,也就是最多创建2个对象,这样就可以防止太多的请求而导致的太多对象的问题。但是只有2个对象,是如何对大量的用户服务的呢?
C 用户发送请求的时候,现在已经有2个对象,达到了实例池的上限,没有办法创建新的对象,查看 A 和B这2个对象哪一个距离上次被访问时间比较远,也就是最近最少使用的原则。假设顺序是 A、B、C 的访问,显然 usercontroler A是最少访问的对象,于是把 A 的状态写在服务器端的内存里,写到硬盘上去,写成 txt 文件或者什么都可以,这涉及到 Java 序列化的问题,把状态写出去。
如果已经定义 int、count,在文档里面写 count=1,count=1作为文件的内容写出去,这样就把状态保护起来了。写出去的目的就是空出位置来给 C 用,C 请求来了之后,就在这个对象上使用,count 重新变成0,为 C 服务。如果继续按照 A、B、C 的顺序循环,会看到A对应的对象不再关联,内存里没有它的状态,于是把状态读回来,前提是先空出一个位置来,仍然是最近最少使用策略,在两个对象里找,把 B 写出来,把 A 的文件读回对象里。但是他是从写出的状态都回来的,所以状态是得到维护的。现在在内存里的是 A 和 C,B 在外面……
以此类推,频繁的在内存和硬盘之间做换进换出,状态实例化到硬盘上,或者把实例化好的状态读回来,但无论如何,内存里只有两个对象。也就是说,牺牲掉的是性能,但节约的是内存里的数量。靠硬盘的换进换出保证只有两个对象就可以输入很多客户端,这就是服务器在处理有状态的对象时要考虑的问题。
如果一个服务是有状态的,首先它要占很多内存,不管有多大,总是可能不能满足客户端用户的需求,比用户端的数量要少很多,所以要频繁的换进换出。是以硬盘的时间和空间换来了内存不至于被充满。所谓有状态就是为每一个用户维护他们各自的状态,都是这个原因导致的。所以在涉及的时候,应该尽量保证有状态的尽量少,无状态的尽量多。在系统里面,不能让所有的东西都是有状态的,有状态的应该尽量集中在某一层,让其他层都是无状态。
刚才给出的例子,Controller 是无状态的,有状态的可以一直到 service 层,再到 DAO 层,再到 repostory层,又可以是无状态的。所有的状态都维护在 service 层。
通过上面的了解,可以知道有状态的东西是比较奢侈的,应该尽量避免,但无法完全剔除,因为应用里不可能一点状态都没有,所以要规划好。