业界曾比较流行的有:
LAMP架构:Linux+Apache+PHP(前后端分离)+MySQL(读写分离)
MVC架构:Spring+Struts+iBatis/Hibernate+Tomcat
在高并发、大流量的应用场景中,需要做集群,通常的组网方案是前端通过F5等负载均衡器做七层负载均衡(或者使用SLB等软负载),后端做对等集群部署。
随着业务的不断发展,应用规模日趋庞大,传统垂直架构开发模式的弊端变得越来越突出。这就需要将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使用前端应用能更快速地响应多变的市场市场需求。
同时将公用能力API抽取出来,作为独立的公共服务供其他调用者消费,以实现服务的共享和重用,降低开发和运维成本。应用拆分之后会按照模块独立部署,接口调用由本地API演进成跨进程的远程方法调用,此时RPC框架应运而生。
RPC(Remote Procedure Call),它是一种进程间通信方式。允许像调用本地服务一样调用远程服务,它的具体实现方式可以不同,如Sprign的HTTP Invoker,Facebook的Thrift二进制私有协议通信。
RPC由20世纪80年代Bruce Jay Nelson提出。
RPC框架的目标就是让远程过程(服务)调用更加简单、透明,RPC框架负责屏蔽底层的传输方式(TCP或者UDP)、序列化方式(XML/JSON/二进制)和通信细节。框架使和者只需要了解谁在什么位置提供了什么样的远程服务接口即可,开发者不需要关心底层通信细节和调用过程。
RPC框架实现的几个核心技术点总结如下:
1. 远程服务提供者需要以某种形式提供服务调用相关的信息,包括但不限于服务接口定义、数据结构,或者中间太空的服务定义文件,如:Thrift的IDL文件、WS-RPC的WSDL文件定义,甚至也可以是服务端的接口说明文档;服务调用者需要通过一定的途径获取远程服务调用相关信息,例如服务端接口定义Jar包导入,获取服务端的IDL文件等
2. 远程代理对象:服务调用者调用的服务实际是远程服务的本地代理,对于Java语言,它的实现就是JDK的动态代理,通过动态代理的拦截机制,将本地调用封装成远程服务调用
3. 通信:RPC框架与具体的协议无关,如Spring的远程调用支持HTTP Invoke、RMI Invoke,MessagePack使用的是私有的二进制压缩协议
4. 序列化:远程通信,需要将对象转换成二进制码流进行传输,不同的序列化框架,支持的数据类型、数据包大小、异常类型及性能等都不同。不同的RPC框架应用场景不同,因此技术选择也会存在很大差异。一些做得比较好的RPC框架,可以支持多种序列化方式,有的甚至支持用户自定义序列化框架(hadoop avro)
下面通过Java原生的序列化、Socket通信、动态代理和反射机制,实现最简单 RPC 框架。它由三部分组成:
1. 服务提供者:运行在服务端,负责提供服务接口定义和服务实现类
2. 服务发布者:运行在RPC服务端,负责将本地服务发布成远程服务,供其他消费者调用
3. 本地服务代理:运行在RPC客户央,通过代理调用远程服务提供者,然后将结果进行封装返回给本地消费者
源代码:
EchoService
public interface EchoService {
String echo(String ping);
}
EchoServiceImpl
public class EchoServiceImpl implements EchoService{
@Override
public String echo(String ping) {
return ping != null ? ping + " --> I am ok.":"I am ok.";
}
}
RpcExporter
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Method;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
/**
* RPC 服务端服务发布者
* @author hs26661
*
*/
public class RpcExporter {
static Executor executor = Executors.newFixedThreadPool(Runtime
.getRuntime().availableProcessors());
public static void exporter(String hostName, int port) throws Exception {
ServerSocket server = new ServerSocket();
server.bind(new InetSocketAddress(hostName, port)); //监听 客户端的TCP连接
try {
while (true) {
executor.execute(new ExporterTask(server.accept()));
}
} finally {
server.close();
}
}
private static class ExporterTask implements Runnable {
Socket client = null;
public ExporterTask(Socket client) {
this.client = client;
}
@Override
public void run() {
ObjectInputStream input = null;
ObjectOutputStream output = null;
try {
input = new ObjectInputStream(client.getInputStream());
String interfaceName = input.readUTF(); //客户端发送的码流
Class> service = Class.forName(interfaceName); //反序列化为对象
String methodName = input.readUTF();
Class>[] parameterTypes = (Class>[]) input.readObject();
Object[] arguments = (Object[]) input.readObject();
Method method = service.getMethod(methodName, parameterTypes);
Object result = method.invoke(service.newInstance(), arguments); //调用服务端方法
output = new ObjectOutputStream(client.getOutputStream());
output.writeObject(result);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (output != null)
try {
output.close(); //调用完成,释放对象
} catch (IOException e) {
e.printStackTrace();
}
if (input != null)
try {
input.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (client != null)
try {
client.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
RPCImporter
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.InetSocketAddress;
import java.net.Socket;
public class RpcImporter<S> {
@SuppressWarnings("unchecked")
public S importer(final Class> serviceClass,final InetSocketAddress addr){
return (S) Proxy.newProxyInstance(serviceClass.getClassLoader(), new Class>[]{serviceClass.getInterfaces()[0]},
new InvocationHandler(){
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Socket socket=null; //创建socket 客户端,根据指定地址连接远程服务提供者
ObjectOutputStream output = null;
ObjectInputStream input=null;
try
{
socket = new Socket();
socket.connect(addr);
output = new ObjectOutputStream(socket.getOutputStream());
output.writeUTF(serviceClass.getName());
output.writeUTF(method.getName());
output.writeObject(method.getParameterTypes());
output.writeObject(args); //把远程服务调用者需要的接口类、方法名、参数列表等编码后发送给服务提供者
input=new ObjectInputStream(socket.getInputStream()); //同步阻塞等待服务端返回应答,获取应答之后返回
return input.readObject();
}
finally
{
if(socket!=null)socket.close();
if(output!=null)output.close();
if(input!=null)input.close();
}
}
});
}
}
import java.net.InetSocketAddress;
public class RpcClient {
public static void main(String[] args) {
new Thread(new Runnable(){
@Override
public void run() {
try{
RpcExporter.exporter("localhost",8088);
}
catch(Exception e){
e.printStackTrace();
}
}
}).start();
RpcImporter importer=new RpcImporter();
EchoService echo = importer.importer(EchoServiceImpl.class, new InetSocketAddress("localhost",8088));
System.out.println(echo.echo("Are you ok?"));
}
}
当服务越来越多时,服务URL配置管理变得非常困难,F5等硬件负载均衡器的单点压力也越来越大。此时需要一个服务注册中心,动态地注册和发现服务,使服务的位置透明。消费者在本地缓存服务提供者列表,实现软负载均衡,这可以降低对F5等硬件负载均衡器的依赖,也能降低硬件成本。
随着业务的发展,服务间依赖关系变得错综复杂,甚至分不清哪个应用要在哪个应用之前启动,架构师不能完整地描述应用之间的调用关系。需要一个分布式消息跟踪系统可视化展示服务调用链,用于依赖分析、业务调用路径梳理等,帮助架构师清理不合理的服务依赖,防止业务服务架构腐化。
服务的调用量越来越大,服务的容量问题就暴露出来,某个服务需要多少机器支撑、什么时候该加机器?为了解决容量规划问题,需要采集服务调用KPI数据,进行汇总和分析,通过计算得出服务部署实例数和服务器的配置规格。
服务上线前的审批、下线通知,需要统一的服务生命周期管理流程进行管控。不同的服务安全权限不同,需要保证敏感服务不被误调用,制定服务的安全策略。
服务化之后,随之而来的就是服务治理问题,单凭RPC框架无法解决服务治理问题。
SOA是一种粗粒度、松耦合的以服务为中心的架构,接口之间通过定义明确的协议和接口进行通信。SOA帮助工程师站在一个新的高度理解企业级架构中各种组件的开发和部署形式,可以帮助企业系统架构以更迅速、可靠和可重用的形式规划整个业务系统。相比传统的非服务化架构,SOA能够更加从容地应对复杂企业系统集成和需要的快速变化。
主要特征如下:
- 原子服务
- 高密度部署
- 敏捷交付
- 微自治
本系统文章来自《分布式服务框架 原理与实践》中国工信出版集团 电子工业出版社 李林锋/著