本章目录:
分布式Java应用图:
分布式Java应用:大型系统会被拆分成多个子系统来实现,对于Java来说,这些子系统可能部署在同一台机器上不同的JVM,或者部署在不同机器上,但是这些子系统之间要进行相互通信来共同实现业务功能。
分布式应用架构的演变
分布式应用架构面临的首要问题,便是如何实现应用之间的远程调用(RPC)。有两种方式:一种是基于HTTP的RPC,一种是基于TCP的RPC。
1.1 基于TCP的RPC
1.1.1 RPC名词解释
RPC也就是远程调用,RPC的实现包括客户端和服务端,也就是服务提供方和服务调用方,服务调用方发送RPC请求到服务提供方,服务提供方根据服务调用方的参数执行请求方法,将执行结果返回给调用方,这就是一次RPC请求。
1.1.2 对象的序列化
要注意 无论是什么类型的数据,最终都是要转成二进制在网络上传输。
对象的序列化:就是将对象转成二进制流的过程。
对象的反序列化:就是将二进制流转成对象的过程。
Java中的序列化代码:
//定义一个字节数组的输出流
ByteArrayOutputStream os=new ByteArrayOutputStream();
//对象的输出流
ObjectOutputStream out=new ObjectOutputStream(os);
//将对象写入字节数组输出,进行序列化
out.writeObject(zhangsan);
byte[] zhangsanByte=os.toByteArray();
反序列化代码:
ByteArrayInputStream is=new ByteArrayInputStream(zhangsanByte);
//执行反序列化 从流中读取对象
ObjectInputStream in=new ObjectInputStream(is);
Person person=(Person) in.readObject();
1.1.3 基于TCP协议实现RPC
基于Java的socket API 实现一个简单的RPC,这里面包括了服务接口,接口的远端实现,服务的消费者和远端的提供方。
1.2 基于HTTP协议的RPC
1.2.1 HTTP协议栈
1.2.2 HTTP请求与响应
1.2.3 通过HTTPClient发送HTTP请求
package com.openapi.TestPojo;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.apache.http.Consts;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
/**
* 利用HttpClient发送HTTP请求
* @author Administrator
*
*/
public class MyHttpClient {
public static void main(String[] args) {
String url="http://www.baidu.com";
CloseableHttpClient client = HttpClients.createDefault();
String result="";
try {
result=doPost(client, url);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(result);
}
/**
* get请求
* @param client
* @param url
* @return
* @throws ClientProtocolException
* @throws IOException
*/
public static String doGet(CloseableHttpClient client,String url) throws ClientProtocolException, IOException{
//注意get如果想添加参数可以直接在url后面拼接
HttpGet httpGet=new HttpGet(url);
HttpResponse response=client.execute(httpGet);
HttpEntity entity=response.getEntity();
byte [] bytes=EntityUtils.toByteArray(entity);
String result=new String(bytes,"utf8");
return result;
}
/**
* post请求
* @param client
* @param url
* @return
* @throws ClientProtocolException
* @throws IOException
*/
public static String doPost(CloseableHttpClient client,String url) throws ClientProtocolException, IOException{
//post请求的参数
List<NameValuePair> nvps = new ArrayList<NameValuePair>();
nvps.add(new BasicNameValuePair("name", "value"));
HttpPost httpPost=new HttpPost(url);
//设置参数
httpPost.setEntity(new UrlEncodedFormEntity(nvps, Consts.UTF_8));
HttpResponse response=client.execute(httpPost);
HttpEntity entity=response.getEntity();
byte [] bytes=EntityUtils.toByteArray(entity);
String result=new String(bytes,"utf8");
return result;
}
}
1.2.4 使用HTTP协议的优势
1.2.5 JSON和XML
JSON和对象的转化
案例1
将java对象JSON序列化
需要的jar包
Person person=new Person();
person.setName("wuk");
person.setPassword("123");
JSONObject json= JSONObject.fromObject(person);
System.out.println(json);
JSON串转成对象
String jsonStr="{\"password\":\"123\",\"name\":\"wuk\"}";
JSONObject jo=JSONObject.fromObject(jsonStr);
Person person=(Person) JSONObject.toBean(jo,Person.class);
System.out.println(person);
xml和对象的转换
需要的jar包
xstream-1.4.10.jar
案例1
Java对象转成XML
package com.wuk.xml;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.DomDriver;
public class XMlTest {
public static void main(String[] args) {
Person person = new Person();
person.setName("wuk");
person.setPassword("123");
//将对象序列化成xml
XStream stream=new XStream(new DomDriver());
//设置person类的别名
stream.alias("person", Person.class);
String personXml = stream.toXML(person);
System.out.println(personXml);
}
}
案例2
xml转成Java对象
package com.wuk.xml;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.DomDriver;
public class XMlTest2 {
public static void main(String[] args) {
//将对象序列化成xml
XStream stream=new XStream(new DomDriver());
//XStream对象设置默认安全防护,同时设置允许的类
//否则就会报警告 Security framework of XStream not initialized, XStream is probably vulnerable.
XStream.setupDefaultSecurity(stream);
stream.allowTypes(new Class[]{Person.class});
//设置person类的别名
stream.alias("person", Person.class);
String xml="<person><name>wuk</name><password>123</password></person>";
Person person=(Person) stream.fromXML(xml);
System.out.println(person);
}
}
1.2.6 RESTful和RPC
目前有两种主流的URL链接风格:一种是RPC,一种是REST。
RPC就是平时常见的链接请求。
RESTful风格的思想就是通过HTTP的不同请求方式POST,GET,PUT,DELETE完成对应的CRUD操作。
采用springmvc来实现RESTful风格的url操作。
1.2.7 基于HTTP协议的RPC的实现
1.3 服务的路由和负载均衡
1.3.1 服务化的演变
分布式应用架构体系对于业务逻辑复用的需求十分强烈,上层业务都想借助已有的底层服务,快速搭建更丰富的应用。
公共业务被拆分出来,形成可共用的服务,最大程度保障代码和逻辑的复用。这种设计被称为SOA。
在SOA架构中,服务消费者通过服务名称,在众多服务中找出要调用的服务的地址列表,称为服务的路由。
对于负载较高的服务来说,往往对应着由多台服务器组成的集群,当请求到来时候,为了将请求均衡地分配到后端服务器,负载均衡程序将从服务对应的地址列表中,通过相应的负载均衡算法和规则,选取一台服务器进行访问,这个过程称之为服务的负载均衡。
当服务规模小的时候,可采用硬编码方式将服务地址和配置写在代码中,解决服务路由和负载均衡问题。也可通过LVS或者Nginx解决。
当服务和规模越来越大,对应的机器数量越多,依靠单一的硬件负载均衡和使用LVS,Nginx进行路由和负载均衡,容易出现单点故障问题(单个点发生故障的时候会波及到整个系统或者网络,从而导致整个系统或者网络的瘫痪),一旦服务路由或者负载均衡服务器宕机,依赖他的所有服务均会失效。
这时需要一个能够动态注册和获取服务信息的地方来统一管理服务名称和对应的服务器列表信息,称为服务配置中心。
服务提供者将服务名称和地址注册到服务配置中心,服务消费者通过服务配置中心来获取要调用的机器列表,然后通过负载均衡算法,选取其中一台进行调用。
当服务器宕机或下线时,相应的机器能够在配置中心中动态移除,并通知消费者。注意消费者只需要在第一次调用时候需要查询服务配置中心,然后将查询信息缓存在本地,然后根据对一台服务器发起RPC调用,后面只需要调用本地信息,直到配置中心发生上述变更。
我们可以使用Zookeeper搭建服务配置中心,如下:
1.3.2 负载均衡算法
前面说到消费者从配置中心获取到服务的地址列表,然后选取一台服务器发起RC调用,在如何选择服务器的问题上采用了负载均衡算法,但是对于不同场景负载均衡的算法不同,有:轮询法,随机法,源地址哈希法,加权轮询法,加权随机法,最小连接法。
1 轮询法
将请求按照顺序轮流分配到后端服务器,他均衡地对待后台的每一台服务器,不关心服务器实际的连接数和当前的系统负载。
使用的目的在于希望请求转移的绝对均衡,但是付出的是性能代价。
2 随机法
通过系统随机函数,根据后台服务器列表的值得大小,随机选择一台访问。但是随着调用量的增大,实际效果接近于轮询法。
3 源地址哈希法
思想是获取客户端访问的IP,通过哈希计算得到一个值,用该值对服务器列表的大小进行取模运算,得到的结果便是要选择的服务器的序。注意同一个IP客户端,当后台服务器列表不变,每次都会被映射到指定的服务器,这样就可以在服务消费者和提供者之间建立有状态的session会话。
4 加权轮询法
不同的后端服务器配置可能不一样,抗压能力不同,这样我们可以给配置高,负载低的更高权重,让其处理更多的请求。这样就可以将请求顺序地且按照权重分配到服务器。
5 加权随机法
同理上面,但是他是去按照权重来随机选取服务器,而非顺序。
6 最小连接法
这个比较灵活,他是根据后台服务器当前的连接情况,动态选择当前积压连接数最少的一个服务器处理当前请求。
1.3.3 动态配置规则
固定的策略无法满足需求,一方面要支持用户的特殊需求,一方面又要尽可能复用代码,所以将特殊的需求剥离出来,采用动态配置规则方式处理。
由于Groovy脚本语言能直接编译成Java的class字节码,很好的和Java进行交互,因此我们通过Groovy语言,利用其动态特性,实现负载均衡的动态配置。
1.3.4 ZooKeeper介绍和环境搭建
ZooKeeper是一个针对大型分布式系统的可靠的协调系统,提供的功能包括配置维护,名字服务,分布式同步,组服务等。ZooKeeper是可以集群复制的,集群间通过Zab协议保持数据的一致性。
该协议包括leader election和Atomic broadcas阶段,集群中将选出一个leader,其他的机器称为follower,所有的写操作都要被传到leader,并通过broadcas将所有的更新告诉follower。
当leader崩溃就会重新选出一个leader,让系统恢复正常。当leader被选举出来,且大多数服务器和leader完成数据同步后,leader election阶段就结束,进入Atomic broadcas阶段,主要是同步leader和follower之间的信息。
1.3.5 ZooKeeper API使用简介
这里会详细写一篇博客。