迭代速度太快,对于我们这种互联网从业人员来说,过去四十年的互联网公司典型架构真是沧海桑田。
洪荒时代
上个世纪九十年代。
最典型的架构是
apache http serve + CGI(C++等语言代码)。用户访问80端口。服务器反向代理访问后台嵌入式程序(类似与今天的appache tomcat)
史前的一些尝试
CORBA
1991年OMG(对象管理组织)提出了CORBA1.1,定义了IDL接口定义语言,开发出对象请求代理ORB中间件,在客户机/服务器结构中,ORB通过一定的应用程序接口(API),实现对象之间的交互;
首先,你要定义一个IDL文件
module CreatorModule{
interface Creator{
boolean login(in string name,in string password);
boolean register(in string name,in string password);
};
};
然后使用idlj此文件转化为一些JAVA代码。然后实现对应的接口。必须启动orbd服务进程。
典型的客户端代码如下
System.out.println("Client init config starts....");
String[] args = {};
Properties properties = new Properties();
properties.put("org.omg.CORBA.ORBInitialHost", "127.0.0.1"); //指定ORB的ip地址
properties.put("org.omg.CORBA.ORBInitialPort", "8080"); //指定ORB的端口
//创建一个ORB实例
orb = ORB.init(args, properties);
//获取根名称上下文
try {
objRef = orb.resolve_initial_references("NameService");
} catch (InvalidName e) {
e.printStackTrace();
}
ncRef = NamingContextExtHelper.narrow(objRef);
String name = "Creator";
try {
//通过ORB拿到server实例化好的Creator类
creator = CreatorHelper.narrow(ncRef.resolve_str(name));
} catch (NotFound e) {
e.printStackTrace();
} catch (CannotProceed e) {
e.printStackTrace();
} catch (org.omg.CosNaming.NamingContextPackage.InvalidName e) {
e.printStackTrace();
}
典型的服务端代码如下
String[] args = {};
Properties properties = new Properties();
properties.put("org.omg.CORBA.ORBInitialHost", "127.0.0.1"); //指定ORB的ip地址
properties.put("org.omg.CORBA.ORBInitialPort", "8080"); //指定ORB的端口
//创建一个ORB实例
orb = ORB.init(args, properties);
//拿到根POA的引用,并激活POAManager,相当于启动了server
obj = orb.resolve_initial_references("RootPOA");
rootPOA = POAHelper.narrow(obj);
rootPOA.the_POAManager().activate();
//创建一个CreatorImpl实例
creatorImpl = new CreatorImpl();
creatorImpl.setToDoListServer(this);
//从服务中得到对象的引用,并注册到服务中
ref = rootPOA.servant_to_reference(creatorImpl);
creatorhref = CreatorHelper.narrow(ref);
//得到一个根命名的上下文
objRef = orb.resolve_initial_references("NameService");
ncRef = NamingContextExtHelper.narrow(objRef);
//在命名上下文中绑定这个对象
String name = "Creator";
NameComponent path[] = ncRef.to_name(name);
ncRef.rebind(path, creatorhref);
System.out.println("server.ToDoListServer is ready and waiting....");
//启动线程服务,等待客户端调用
orb.run();
COM
是在Windows 3.1中最初为支持复合文档而使用OLE技术上发展而来,经历了OLE 2/COM、ActiveX、DCOM和COM+等几个阶段。
Windows,呵呵。
JAVA的一家独大
1997年2月,RPC的一种java实现诞生(RMI)。特点是可以像调用本地对象一样调用远程对象。RMI被寄予厚望并被整合进入JDK中。
一个使用RMI调用远程对象的典型代码如下所示
try {
UserHandler handler = (UserHandler) Naming.lookup("user");
int count = handler.getUserCount();
String name = handler.getUserName(1);
System.out.println("name: " + name);
System.out.println("count: " + count);
System.out.println("user: " + handler.getUserByName("lmy86263"));
} catch (NotBoundException e) {
e.printStackTrace();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (RemoteException e) {
e.printStackTrace();
}
服务端代码如下
UserHandler userHandler = null;
try {
userHandler = new UserHandlerImpl();
Naming.rebind("user", userHandler);
System.out.println(" rmi server is ready ...");
} catch (Exception e) {
e.printStackTrace();
}
...
//启动必要的rmiregistry进程(一个监听指定端口的服务)
java.rmi.registry.LocateRegistry.createRegistry(iPort);
随后,诞生了整个庞大J2EE标准和生态。JSP/servelet标准,tomcat/weblogic,EJB等等…
JAVA EE时代
JEE 包括JSP/Servlet,JMS, EJB,JAX-WS,JAX-RS,CDI等等。其中JSP/Servlet 容器的常见实现就是tomcat,他被成为web应用服务器。就整个JEE体系而言,主要的玩家是Glassfish(开源), Geronimo(开源), WebLogic(商业), WebSphere(商业)。因为都符合规范,所以你可以自由切换。这些被称为应用服务器。因为它们比tomcat而言,多支持了EJB规范。
EJB 2.0的规范由于过于复杂,后续受到了Spring为主的所谓轻量级架构的冲击。为了反击,推出了新的EJB3.0规范。
EJB有以下三种类型
- Session Bean(保存单个用户的对应数据)
- Entity Bean(持久性的数据存储,可以通过其读写数据库)
- Message Driven Bean(从外部获取并消耗JMS消息,执行相应指令)
EJB是官方JCP来制定的,Spring是开源社区规定的。Spring在战斗中目前取得了压倒性的优势,这是各方都有预料到的。也许,我们的官方真的已经走的太远太远了。他们试图用java构建一个围城,但是,这真的可能吗?也许再过十年,我们就没必要在提这EJB的历史了。
OSGI的平行发展
osgi(Open Service Gateway Initiative)是1999年发起的。WebLogic, WebSphere,tomcat都使用OSGI的架构思想。可以说osgi是复杂【软件】的基础。对于互联网公司而言,一个热插拔,动态插件化,在不重启的情况下更新代码有意义吗?好像没什么意义。但是,OSGI能很好的隔离中间件和应用代码的依赖版本问题。所以,至今仍被个互联网企业使用。
osgi没有特别好的翻译,但是,我觉得意译为【java模块化系统】并没有问题。他和所有java后台模式都不冲突,但是可以更好实现代码的模块化。
服务化时代(WebService)
如果不进行服务化,我们大概会遇到这些问题
- 代码拷贝耦合(代码层面的耦合是最恐怖的,新手程序员拷贝代码的习惯总是令人绝望)
- 基础库耦合(程序包越大越大)
- 数据库耦合(大家都要访问数据库,sql质量越来越差,同一个数据库的业务互相影响)
服务化是必须的,就像编程早期的模块化一样。我们不能像编写photoshop这种软件的方式一样写互联网。互联网的代码膨胀和失控速度远远超过传统软件行业。就这样,服务化变成了大家的共识。
J2EE和RMI的缺点很明显,那就是大家都要玩JAVA。微软等很多人都不会开心。COM和COBRA也是一样,凭什么大家都要遵循如此复杂的RPC规范?真本来就是一个江湖,江湖上谁也不服谁。
1999年12月,微软推出SOAP(Simple Object Access Protocol)、WSDL(WebServicesDescriptionLanguage)、UDDI(UniversalDescriptionDiscovery andIntegration)。SOAP=RPC+HTTP+XML。
毫无疑问,没有人理他。
微服务时代
折腾一番之后,大家终于意识到,一个简单易用而且开放的的规范才是我们真正需要的东西。
SpringCloud,服务注册发现链路跟踪监控等一些列架构。
典型的通讯是直接HTTP协议的轻量级Restful API。当然,也并非没有例外。所以,又诞生了基于TCP的Thrift。用于解决性能问题。依旧是IDL语言来定义RPC
一个典型的服务如下
System.out.println("服务端开启....");
TProcessor tprocessor = new Hello.Processor<Hello.Iface>(new HelloServiceImpl());
// 简单的单线程服务模型
TServerSocket serverTransport = new TServerSocket(9898);
TServer.Args tArgs = new TServer.Args(serverTransport);
tArgs.processor(tprocessor);
tArgs.protocolFactory(new TBinaryProtocol.Factory());
TServer server = new TSimpleServer(tArgs);
server.serve();
}catch (TTransportException e) {
e.printStackTrace();
一个典型的调用者如下
transport = new TSocket("localhost", 9898, 30000);
// 协议要和服务端一致
TProtocol protocol = new TBinaryProtocol(transport);
Hello.Client client = new Hello.Client(protocol);
transport.open();
String result = client.helloString("哈哈");
System.out.println(result);
左看右看,似乎和CORBA没啥区别。至少少了一个独立的服务进程,而是变成了在代码中的整体中。这毫无疑问是因为服务注册服务发现的发展,开发者需要约定一个统一端口进行通信的时代已经过去了。
微服务的一个核心问题就是,什么样的粒度才算合适。统一的API暴露接口是大家的共识,问题是接下来怎么办。垂直拆分和水平拆分之后,业务和服务的连接关系也就复杂了。多出来的服务需要大量的管理。也会有网络IO问题。
业界比较流行的分割粒度是按照子业务。
单元化的尝试
这是阿里巴巴内部常见的一种架构。
服务化在上层应用访问下层应用时,是随机选择节点的
而单元化打破了这一点
每一个单元,都是一个缩小版的网站。如果这样的架构极其方便的进行水平扩容和冗灾。最核心的问题是,如何保证各个单元的持久化数据是同步的。比如不同单元的买家抢购同一种有限额的商品。
阿里巴巴所采用的就是一套自称为X-DB的系统。每个单元的数据库可以实时同步到中央数据库的内容。
关于单元化的细节问题,强烈建议访问
https://blog.csdn.net/qq_27384769/article/details/80331540
容器时代
SpringBoot + Docker + k8s。也就是我们现在正处于的时代。
Docker公司和谷歌公司也着实狠狠的撕了一波,但是基本大势已去。
https://blog.csdn.net/define_us/article/details/83824681
可见的未来
sidecar + servicemesh。
第一代 service mesh 以 Linkerd 和 Envoy 为代表。Envoy是一个网络层和传输层的网络代理。号称能战胜ngnix。Linkerd也是个代理。只做代理显然不能满足野心。他们没有野心,所以很快先驱变先烈。lstio又成为了新时代的主流。这个本身是一个开源大型微服务系统管理工具。谷歌和IBM,Lyft联手做的。主要面向k8s的环境进行部署。谷歌显然不会另起炉灶将自己的k8s上积累的优势破坏掉。
一个庞大的CNCF基金会的组织似乎让我们看到了互联网行业走向开放和团结的第一步。口号是 坚持和整合开源技术来编排容器作为微服务架构的一部分。说白了,微服务是共识,容器和开源是希望大家坚守的。
历史都是经历某种轮回的,2018年,Thrift相对传统CORBA的唯一进步(取消独立的服务进程)又用sidecar的名字死灰复燃了。用一个独立的进程完成对本地服务的代理?说好的谁也不服谁呢?sidecar又能在激烈的市场中引领潮流最终形成大家都能接受的规范和标准吗?虚拟机技术真的会在容器技术面前一败涂地彻底退出了历史舞台吗?我们拭目以待。
总结
现在,一般大型互联网公司的后台可以分为三波人,三件事,三个平台
- IAAS 运维平台 提供可伸缩的基础平台,网络,存储,数据库
- PAAS 技术平台 提供必要的中间件,简化开发
- SAAS 业务平台 快速迭代的业务开发
具体的划分代表性的就是阿里的大中台战略
采取技术可以争论,但是功能不能少。时代再变迁。但是估计在我们的有生之年,后台服务架构都会将以下功能作为刚需:
开发简单
- 低耦合
- 低冗余
- 底层原理不扩散(避免初级程序员付出过高的学习成本)
依赖清晰/链路可追踪/全过程可记录/日志可收集
服务发现/负载均衡/高可用
https://blog.csdn.net/define_us/article/details/86619813
协议适配/跨语言/兼容
优雅降级/平稳变更上线
性能/高并发/低延迟
可伸缩/扩展性
权限/安全
自由灵活的测试环境
应该支持线上实时流量/线上历史流量/自造流量
支持大数据体系/数据库
智能运维/自动化代码审查/完善的开发工具和清晰简单的工作流/快速报警和响应/
清晰的故障追踪和回溯办法
没有一种技术会说自己不如别人。他们只是慢慢的落后时代,被逐步抛弃罢了。与其说这种技术不如别人,倒不如说是这种技术的支持者不如别人。市场永远在追求足够简单,或者“让别人觉得”足够简单。在功能的底线下,简单就是第一竞争力。那么性能呢?性能总是有改进和优化的办法。那为什么人类会反复寻找不到一个正确的答案呢?换个问题,为什么人类几万年也追求不到一个正义呢?因为大家对正义的定义不同,大家对简单的定义也不同。我们有些时候的简单是自己自由扩展体会一切皆有肯能,有些时候追求的简单则是被包养的幸福。我们永远可能永远无法就何为简单这个话题,和别人,和曾经的自己,和未来的自己达成一致。
唯一欣慰的是,在我们追求简单的过程中,互联网编程的架构功能日益完善。我不相信有人能告诉我们未来的互联网架构会是如何的,每个人,每个公司,每个技术支持者,都有他们梦中的明天。但我能告诉你,现在我们能做到的功能,在以后的任意时代的互联网编程工程师手里,都能够做到。