前方预警:纯粹个人理解demo,可能有错仅供参考。 拆解搭积木方式实现一个微服务,欢迎大家在评论区留言!
前言
要实现一个简单的微服务模型需要包含:实现服务的注册、发现和调用。先简单的画个包含每个模块的流程图,然后下面我们会以堆积木的方式进行拆解。
先说下注册中心
说注册中心的话这里有个CAP定理,指的是一致性(Consistency)、可用性(Availability)、分区容错性(Partition tolerance)。但是现实中只有CP/或者AP,三个特性CAP不会同时存在的。
当然实现这些特性对我来说遥不可及,但是我们可以找一些替代品,平时我们都用过redis(CP)或者zookeeper(AP)。
那么我们是否可以选用一个组件作为我们注册中心的存储容器
- 我们需要提供一个服务中心的客户端能够让服务注册进来,让客户端可以查找服务的信息。(一个简单的注册中心不就出来了)
- 我们只需要设置好我们的数据结构方便客户端能够在容器中准确的得到服务端信息。(这里算不算服务发现)。
- 我们把服务的信息根据设置好的数据结构存储在容器中。(这里算不算服务注册)。
- 由于服务端的不确定性我们是否可以提供一些心跳定期的检查下服务端的健康情况。
- 然后我们可以在发现提供服务发现的时候提供一些负载方法减轻下服务的压力。
这里简单的说下数据交互的基本设计
首先这里声明一点,这里说的服务提供主要是通过代理和反射实现的(当然也可以完全基于http)。下面是基于上面的声明说的。
- 先不谈论代理,反射的话我们的需要知道的就是1.明确是哪个类;2.哪个方法;3.参数;然后我们可以根据反射执行业务逻辑拿到结果了。那么这里我们就完成了一个基本的服务调用;
- 完成了服务的调用,然后我们考虑的问题是什么就是数据传输了!数据传输我们要考虑的问题是序列化如果把我们想要的东西进行序列化传输和对数据进行反序列化。(常用的序列化协议:XML、JSON、HESSION、PROTOSTUF、对象的序列化)
- 序列化也搞定了,我们这里考虑代理的问题,为什么要引入代理?最主要是因为代理把服务的调用和数据的传输、解析这部分处理变成无感和解耦了。
- 最后我们要做的就是服务端在项目启动的时候把服务信息发布到注册中心;然后客户端在调用服务端的时候先从注册中心拿到调用的基本信息;接着调用服务端拿到我们想要的结果;一个简单的流程就结束了。
根据上面交互这里简单的实现一个微服务(数据传输用netty)
服务提供用个简单的注解声明下
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface RpcService {
String version() default "";
}
服务请求需要的数据对象
private String id;
private String version;
private String className;// 类名
private String methodName;// 函数名称
private Class<?>[] parameterTypes;// 参数类型
private Object[] parameters;// 参数列表
private String ip;//IP
private int port;//端口
服务相应简单的对象
private String requestId;
private int code;//-1失败 0成功
private String errorMsg;
private Object data;
服务注册简单的处理,服务端启动的时候扫描注解
类全量类似:com.soap.TestRPC(也可以别名)={127.0.0.1:8080(服务端可以有多个)}
客户端代理简单处理
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return threadPool.submit(new Callable<Object>() {
@Override
public Object call() throws Exception {
try {
if(null==client) {
client = new NettyClient();
}
//获取IP地址开始
logger.info("【通过zookeeper获取服务端地址开始】");
SeverRegistry sr=SeverRegistry.getServerRegistry();
List<String> ipList=sr.getServerPath(method.getDeclaringClass().getName());
for(String str:ipList) {
logger.info("服务名称{},地址{}",method.getDeclaringClass().getName(),str);
ip=str.split(":")[0];
port=Integer.valueOf(str.split(":")[1]);
}
logger.info("【通过zookeeper获取服务端地址结束】");
String requestID = UUID.randomUUID().toString().replace("-", "");
RequestBean request = new RequestBean();
request.setClassName(method.getDeclaringClass().getName());
request.setMethodName(method.getName());
request.setParameters(args);
request.setParameterTypes(method.getParameterTypes());
request.setId(requestID);
request.setIp(ip);
request.setPort(port);
Object result = client.send(request);
Class<?> returnType = method.getReturnType();
ResponseBean response = (ResponseBean) result;
if(-1==response.getCode()) {
throw new Exception(response.getErrorMsg());
}
return response.getData();
} catch (Exception e) {
logger.error("调用失败:" + e.getMessage(), e);
throw new Exception(e.getMessage());
} finally {
//关闭channel 客户端还可以复用
/*
* if(null!=client) { client.destroy(); client = null; }
*/
}
}
}).get();
服务端处理
/**
* 通过反射,执行本地方法
* @param request
* @return
* @throws Throwable
*/
private Object handler(RequestBean request) throws Throwable{
String className = request.getClassName();
Object serviceBean = serviceMap.get(className);
if (serviceBean!=null){
Class<?> serviceClass = serviceBean.getClass();
String methodName = request.getMethodName();
Class<?>[] parameterTypes = request.getParameterTypes();
Object[] parameters = request.getParameters();
Method method = serviceClass.getMethod(methodName, parameterTypes);
method.setAccessible(true);
return method.invoke(serviceBean, parameters);
//return method.invoke(serviceBean, getParameters(parameterTypes,parameters));
}else{
throw new Exception("未找到服务接口,请检查配置!:"+className+"#"+request.getMethodName());
}
}
通信采用netty进行
public void channelRead(ChannelHandlerContext ctx, Object msg) {
threadPool.execute(new Runnable() {
@Override
public void run() {
RequestBean request=null;
if(RequestBean.class.isInstance(msg)) {
request=(RequestBean)msg;
}else {
logger.info("非法请求...");
return;
}
if ("heartBeat".equals(request.getMethodName())) {
logger.info("客户端心跳信息..."+ctx.channel().remoteAddress());
}else{
logger.info("RPC客户端请求接口:"+request.getClassName()+" 方法名:"+request.getMethodName());
ResponseBean response = new ResponseBean();
response.setRequestId(request.getId());
try {
Object result = handler(request);
response.setCode(0);
response.setData(result);
} catch (Throwable e) {
e.printStackTrace();
response.setCode(-1);
response.setErrorMsg(e.toString());
logger.error("RPC Server handle request error",e);
}
ctx.writeAndFlush(response);
}
}
});
}