当前位置: 首页 > 科技观察

小白科普:“无状态”的东西

时间:2023-03-17 16:18:50 科技观察

软件师傅正在闭着眼睛练习,最小的弟子缓缓走进来。弟子:师父,有一件事我不明白,我很苦恼。师父:你来听,我给你讲。弟子:经常听到师兄争论“无状态”,说“无状态”在软件编程中是个好东西,但究竟什么是状态?什么是无国籍?大师睁开眼睛,写下一行:y=f(x),然后又闭上了眼睛。学生:(奇怪地问)这不就是一个函数吗?初中的时候学过,给定一个x,可以计算一个函数(比如平方)得到一个y。师父:对,这是一个纯函数。对于相同的输入,总是得到相同的输出,并且不依赖于外界的状态。学生:这没什么!大师:大家想一想,如果在一个CPU上多个线程并发调用这个函数,会不会有什么问题?学生:没有。师父:如果有多个线程在多个CPU上并行执行这个函数,会不会有问题?弟子:没有。师父:为什么?弟子:因为每次调用都不会在这个函数中保留数据,调用完就结束了,每次调用都是全新的调用,没有第一次和第一百次的关系。师父:因为那个函数不保存状态,所以并发和并行都没有问题。学生:是的,我明白了。大师:想想你常用的HTTP。每次访问一个静态的HTML页面,是不是相当于给服务器调用了一个函数?函数输入:一个URL路径,函数输出:HTML页面。弟子:也就是说,这个服务器不会记录每次请求的是谁,它只需要执行这个‘函数调用’即可。师父:告诉我,这样的HTTP协议有什么好处?弟子:由于没有状态,如果一个服务器的访问量太大,我可以很容易地添加一个新的服务器来处理请求。师父:“小朋友可以教,这就是所谓的scale-out。弟子:横向扩展?还有纵向扩展(scale-up)吗?师父:对,纵向扩展就是增加CPU、内存、硬盘等。.提高单台服务器的处理能力由于单机总是有上限的,要应对大量用户的访问,提高可用性,就必须依靠水平扩展,现在你已经体会到了好处无状态?学生:懂了,师父,服务器端无状态确实是一个美好的世界,但现实是残酷的,没有状态是不够的,一个人登录,我们要记住他是谁,他把商品加入购物车,我们也要记下来。师父:那你怎么记的?弟子:一定要用Session来保存状态!师傅:服务器一旦引入state,横向扩展就不好做了!学生:是的,我该怎么办?师父:这里有很多方法,比如让'state'在服务器之间复制,但是最常用的是将状态转移到另一个地方,并尝试将服务器恢复到无状态'y=f(x)'。(注:现实中在图中的服务器之前还有负责负载均衡的服务器)弟子:哦,这样又可以横向扩展了!对了,师父,我刚才听师兄们提过‘statelessobjects’。他们说一个对象没有实例变量,或者实例变量是final的。那么对吗?师父:嗯,在这种情况下,说'无状态对象'有点不准确,更准确的说法是'不可变对象'(ImmutableObject),例如:publicfinalclassComplex{privatefinalinta;privatefinalintb;publicComplex(inta,intb){this.a=a;this.b=b;}publicComplexadd(Complexother){returnnewComplex(a+other.a,b+other.b);}}弟子:哦,这个类的对象一旦创建,就不能是的,我看到那个add方法,它不是对现有对象的修改,它返回一个全新的对象。师父:这样,多个线程调用add对象时,都是线程安全的。我这里有一张图,是LISP大师给我的。它生动地展示了可变与不可变。可以拿下:弟子:价格有点高,而且每次都创建新的对象!我们用的是Spring,Controller和Service是大量并发调用的,所以肯定不能用这个方法。Master:对,你用的Controller和Service默认都是单例的,运行时只有一个实例。它们的方法应该是像y=f(x)这样的无状态方法,而且很容易不把共享的实例变量放在里面。否则,多线程并发操作可能会出现问题。弟子:但是我们的Controller一般需要放一个Service的实例变量,比如LoginController中的userService。如果多个线程同时访问这个共享的userService,那岂不是出问题了?@ControllerpublicclassLoginController{@AutowiredprivateUserServiceuserService;...userService的使用略...}师父:你误入歧途了,你把无状态和非共享实例变量画上了等号。想一想,如果LoginController调用的userService的方法类似于y=f(x),会不会有线程安全问题?学生:嗯……好像没什么问题。Controller和Service都是纯函数调用。但是,如果您确实需要共享变量怎么办?师父:很简单,使用ThreadLocal,在每个线程中存储这个变量,让它们互不干扰,是线程安全的。【本文为专栏作家“刘欣”原创稿件,转载请通过作者微信获取授权公众号coderising】点此查看该作者更多好文