2.SOA架构下的序列化、RPC、服务注册与均衡、服务网关
2018-02-07 19:15
435 查看
1.序列化
无论是什么类型的数据想要在网络上面传输都需要转换成二进制流。在面向对象程序语言开发中,对象与二进制流数据之间的转换就是序列化和反序列化。JAVA有众多序列化类库以及开源产品。各种序列化方式性能对比:
JDK自带序列化方式:
//序列化 //字节输出流 ByteArrayOutputStream os = new ByteArrayOutputStream(); //对象输出流 ObjectOutputStream out = new ObjectOutputStream(os); //对象序列化进字节数据中 out.writeObject(new Object()); byte [] bytes = os.toByteArray(); //反序列化 //字节输入流 ByteArrayInputStream in = new ByteArrayInputStream(bytes); //从流中读取对象 ObjectInputStream inputStream = new ObjectInputStream(in); Object object = (Object)inputStream.readObject();
2.基于TCP/IP的RPC
介绍:这种RPC方式就是消费者将需要调用的服务接口的方法名称、方法参数通过套接字传递到服务提供者,由服务提供者远程调用之后将结果回传。利用反射实现的。
缺点:
HTTP(应用层)、TCP(传输层)、IP(网络层)、网络接口层。TCP/IP处于下层协议,开发起来需要更多关注底层细节、实现代价高、且由于协议自身局限性难以跨平台。对于不同平台的终端(android、ios、等)需要重新开发不同的工具包来发送和解析请求。
并且在高并发系统下,任意一个小错误都可能因为多线程、锁、IO等问题无限放大
优点:
由于TCP/IP属于下层协议,能够更加灵活的对协议字段进行定制、减少网络无用数据的传输、降低网络开销
消费者:
public class ConsumerDemo { public static void main(String[] args) throws NoSuchMethodException, IOException, ClassNotFoundException { //获取服务提供者的接口名,一般RPC框架都是暴露服务提供者的接口定义 String providerInterface = ProviderDemo.class.getName(); //需要远程执行的方法,其实就是消费者调用生产者的方法 Method method = ProviderDemo.class.getMethod("printMsg", java.lang.String.class); //需要传递的参数 Object[] rpcArgs = {"Hello RPC!"}; Socket consumer = new Socket("127.0.0.1", 8899); //将方法名称和参数 传递给服务生产者 ObjectOutputStream output = new ObjectOutputStream(consumer.getOutputStream()); output.writeUTF(providerInterface); output.writeUTF(method.getName()); output.writeObject(method.getParameterTypes()); output.writeObject(rpcArgs); //从生产者读取返回的结果 ObjectInputStream input = new ObjectInputStream(consumer.getInputStream()); Object result = input.readObject(); System.out.println(result.toString()); } }
服务提供者:
public interface ProviderDemo { /** * 服务提供者打印Msg方法 */ public String printMsg(String msg); }
public class ProviderDemoImpl implements ProviderDemo { public String printMsg(String msg) { System.out.println("----" + msg + "----"); return "Ni Hao " + msg; } }
public class ProviderServer { public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException { //用于存放生产者服务接口的Map,实际的框架中会有专门保存服务提供者的 Map<String, Object> serviceMap = new HashedMap(); serviceMap.put(ProviderDemo.class.getName(), new ProviderDemoImpl()); //服务器 ServerSocket server = new ServerSocket(8899); while (true) { Socket socket = server.accept(); ObjectInputStream input = new ObjectInputStream(socket.getInputStream()); String interfaceName = input.readUTF(); //获取服务消费者需要消费服务的接口名 String methodName = input.readUTF(); ////获取服务消费者需要消费服务的方法名 //参数的类型 Class<?>[] parameterTypes = (Class<?>[]) input.readObject(); //参数的对象 Object[] rpcArgs = (Object[]) input.readObject(); //执行调用过程 Class providerInteface = Class.forName(interfaceName); //得到接口Class Object provider = serviceMap.get(interfaceName);//取得服务实现的对象 //获取需要执行的方法 Method method = providerInteface.getMethod(methodName, parameterTypes); //通过反射进行调用 Object result = method.invoke(provider, rpcArgs); //返回给客户端即服务消费者数据 ObjectOutputStream output = new ObjectOutputStream(socket.getOutputStream()); output.writeObject(result); } } }
3.基于HTTP的RPC
介绍:基于http的远程调用使用httpclient模拟发送http请求
各层协议:
HTTP(应用层)、TCP(传输层)、IP(网络层)、网络接口层
HTTP请求响应全过程:
浏览器根据所使用的的http协议解析出url对应域名
dns解析域名找到ip地址
通过url解析出端口号
浏览器向该机器(ip+port)发起并建立连接
浏览器发送请求,应用服务器响应请求
浏览器渲染网页,关闭连接
使用HttpClient发送HTTP请求:
由于http这种公有协议的广泛应用,我们可以使用除浏览器之外的工具发送http请求并解析响应请求。但是如果使用socketAPI实现会造成大量工作量且重复无价值劳动。httpClinet给出了一个成熟的结局方案
1第一种:普通的Get/Post请求,Get请求可以带参数(无需登陆) public static String GetHtttpLink(String path) throws Exception { HttpClient client = new HttpClient();// 打开新窗口 GetMethod get = new GetMethod(path);//path是请求地址,可以带参数 int st1 = client.executeMethod(get); logger.info("执行状态:" + st1); String result = get.getResponseBodyAsString();//服务端返回的Response get.releaseConnection();//释放链接 return result; } public static String PostHtttpLink(String path) throws Exception { HttpClient client = new HttpClient();// 打开新窗口 PostMethod po = new PostMethod(path); int st1 = client.executeMethod(po); logger.info("执行状态:" + st1); String result = po.getResponseBodyAsString();//服务端返回的Response po.releaseConnection(); return result; } 第二种:带多个参数的Post请求 (无需登陆) public static String PostWithParas() { HttpClient client = new HttpClient(); PostMethod postMethod = new PostMethod("http://127.0.0.1:8080/userrz"); NameValuePair param1 = new NameValuePair("id", "XXX");//参数1 NameValuePair param2 = new NameValuePair("time", "XXXXXX");//参数2 NameValuePair param3 = new NameValuePair("url", "XXXXXX");//参数3 NameValuePair param4 = new NameValuePair("msg", "XXX");参数4 postMethod.setRequestBody(new NameValuePair[]{param1, param2, param3, param4 });//设置参数 String result=""; try { client.executeMethod(postMethod);//执行请求 result = postMethod.getResponseBodyAsString();//获取返回的Response } catch (IOException e) { e.printStackTrace(); } postMethod.releaseConnection();//释放链接 } 第三种:带Cookies请求认证,访问网站内部某个页面(需要登陆,用户名密码认证) public static String GetLink(String Corpcode, String Code) throws Exception { HttpClient client = new HttpClient();// 打开新窗口 client.getHostConfiguration().setHost("localhost", 8080);// 配置 // 模拟手动登录页面,login是处理登陆的action PostMethod post = new PostMethod("/login"); NameValuePair username = new NameValuePair("username", "admin");//用户名 NameValuePair pass = new NameValuePair("password", "admin123");//密码 post.setRequestBody(new NameValuePair[] { username, pass }); int status = client.executeMethod(post); System.out.println("执行状态:" + status + ",返回状态:" + post.getStatusLine(); post.releaseConnection(); // 查看 cookie,并传递Cookies到服务器认证 CookieSpec cookiespec = CookiePolicy.getDefaultSpec(); Cookie[] cookies = cookiespec.match("localhost", 8080, "/", false, client.getState().getCookies()); if (cookies.length == 0) { System.out.println("没有Cookies"); } else { for (int i = 0; i < cookies.length; i++) { System.out.println("Cookies:" + cookies[i].toString()); } } // 因为前面已经登陆了,也加入了Cookies,所以才可以直接访问这个地址 GetMethod get = new GetMethod("/user");//这个才是需要访问的真正action int stats = client.executeMethod(get); String result = get.getResponseBodyAsString(); System.out.println("Get请求:" + result); get.releaseConnection(); return result; }
优点:
可以使用诸如json、xml等通用格式的数据。并且在高并发情况下诸如多线程、锁、IO等问题已经在众多成熟开源web容器中得到解决(tomcat、jboss、apache等)
缺点:
由于http属于上层协议,发送相同内容传输的数据字节数肯定更多,传输效率是低一点点。当然这个问题可以在代码优化、gzip数据压缩等方式下得到优化。同时http请求可以携带的数据有限,如果海量数据会性能不稳定。
客户端:
<dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.2</version> </dependency>
public void testHttpService() throws UnsupportedEncodingException { System.out.println("测试http请求开始~"); //封装请求参数 Map map = new HashMap<String, String>(); map.put("reqData","Hello World,世界你好~"); //http://localhost/testHttpService 请求的服务器地址URL String resp = HttpUtil.post("http://localhost/testHttpService", map); System.out.println("http服务返回结果为:"+ com.alibaba.fastjson.JSON.toJSON(resp)); }
服务端:
@RequestMapping("/testHttpService") public void testHttpService(HttpServletRequest request,HttpServletResponse response) throws IOException { logger.info("测试HTTP请求 服务端开始~"); String reqData=request.getParameter("reqData"); //模拟 相关业务逻辑处理 logger.info("处理相关业务~reqData="+reqData); //模拟 返回业务结果 logger.info("业务处理完成,返回结果~"); Map mapResult=new HashMap(); mapResult.put("success",true); mapResult.put("code","0000"); mapResult.put("msg","http请求测试成功~"); //防止Http请求中文乱码 response.setHeader("Content-Type", "text/html;charset=utf-8"); PrintWriter printWriter=response.getWriter(); printWriter.write(com.alibaba.fastjson.JSON.toJSONString(mapResult)); printWriter.flush(); logger.info("测试HTTP请求 服务端结束~"); }
4.服务的路由和负载均衡算法
负载均衡:同一个服务接口集群部署是最常用的横向扩展方式来提高并发能力。简单的负载均衡其实就是如何根据集群中结点的工作状态判断将下一次请求到底发送给集群中哪一个结点,通常使用服务协调框架zk来维护一张服务器地址表,根据不同的负载均衡算法从地址表上面选一个结点,然后将下一次请求发送给它。
服务路由:
zk使用zab协议保证数据一致性以及选举的特性正好用来做这样一件事情(维护结点地址表)。首先最基本的,服务在启动后正常注册以及服务宕机之后自动删除这样的模式就解决了其他调度方式(例如f5硬负载、lvs、nginx软负载)的单节点问题。
负载均衡算法:
轮询:集群中结点挨个服务、不关心结点实际负荷和连接数
随机:概率上当调用次数无限大后随机的结果近似于轮询
一致性hash:将调用者ip地址做hash函数计算出一个数值,该数值对地址表长度做取模运算,得到的结果就是被选中的那个结点。这样可以保证同一个客户端发起的请求都会被发送到同一个服务器结点上
加权轮询:给配置好、负载低的机器赋高权重(处理更多请求)。配置低、负载高的机器赋低权重(降低它的负载防止宕机)
加权随机:给配置好、负载低的机器赋高权重(处理更多请求)。配置低、负载高的机器赋低权重(降低它的负载防止宕机)
最小连接数:根据每个节点负荷,将本次请求发送给当前网络连接、负荷最低的机器
动态的负载均衡算法:
由于单个类型的负载均衡算法不能应对复杂多变的实际工作环境,可以代码实现多种负载均衡算法在实际工作环境中动态切换。
在zk中可以使用watcher机制来实现
5.HTTP服务网关
终端多样:随着移动互联网的发展,相同的应用程序可能会开发不同终端的版本,但是相同的功能、相同的数据在不同的平台下没有必要重复开发,由于不同终端都需要使用公共网络来发起客户端请求,考虑到http请求的未加密明文(包括参数、返回值、cookie、head等)使得类似请求可以被截获以及伪造。
网关:
由于上面的问题,需要建立一个强大的安全体系来保障相关数据和接口的安全。网关是所有请求进入的一个关口,负责对各种终端发送的请求的权限和安全校验,然后网关让请求进入集群内。网关会获取集群发
a6cb
回的响应,网关将响应回传给不同的应用终端。
基于网关的安全架构:
由于网关的存在,在消费者和提供者之间不再有直接的调用关系,中间的调用关系由网关来负责。因此服务提供者也不是直接向外界提供服务了,下图:
相关文章推荐
- 【架构】SpringCloud 注册中心、负载均衡、熔断器、调用监控、API网关示例
- 微服务架构之RPC-client序列化细节
- 【架构】SpringCloud 注册中心、负载均衡、熔断器、调用监控、API网关示例
- 微服务架构之RPC-client序列化细节
- 微服务SOA架构与RPC远程过程调用
- 模拟dubbo 框架RPC调用及dubbo的服务动态注册,服务路由,负载均衡功能的思考
- 大型分布式网站架构设计与实践 第一章《面向服务的体系架构(SOA)》1.1基于TCP协议的RPC
- 【58沈剑架构系列】微服务架构之RPC-client序列化细节
- 搞有中国特色的SOA(面向服务架构)——1
- OSGi服务:非常适合SOA的架构
- SOA架构,微服务,技术实践汇总
- Spring Cloud构建微服务架构(六)高可用服务注册中心
- SOA (面向服务的架构)
- SOA四层架构中的服务层设计
- 【微服务架构】SpringCloud之Eureka(服务注册和服务发现基础篇)(二)
- SOA——面向服务的体系架构
- SOA和微服务架构的区别?
- SOA(面向服务架构)——踩坑后反思:这样值得吗?
- SOA 与微服务架构
- Spring Cloud构建微服务架构(一)服务注册与发现