参考文章:http://www.julianbrowne.com/article/brewers-cap-theorem
学习就应该从最基础学起,不要让你的知识有间隔,万丈高楼平地起.
我目前所知道的服务注册中心总共有: zk,eureka,nacos。 他们都是服务注册中心,zk主要的大家应该非常熟悉,他经常和dubbo一起使用,eureka是springcloud全家桶产品,在2.0的时候 就已经不维护了,Nacos 是阿里巴巴推出来的一个新开源项目。
zk,eureka,nacos 都离不开基础原理:RPC ,RMI ,在实现上框架上应该保障CAP理论知识
RPC全英名字:Remote Procedure Call 英文缩写,中文名字为远程过程调用,作用是对数据进行处理后显示或打印,最开始的时候我以为RPC属于JavaEE13个规范里的内容,其实不是搞混了,13个规范里指的是RMI,RPC来源于C语言,它是一个相对较为旧的协议,继承C语言的规范。使用RPC
可以像调用本地方法一样调用远程的方法方法
RMI是JavaEE里的13个规范,RMI是面向对象的范式,也就是用户需要知道他调用的对象和对象中的方法,RPC不是面向对象也不能处理对象,而是调用具体的子程序,并且RMI是基于JavaEE规范的,也就是说他只能用户Java语言,RMI则可以跨语言
如果想知道RMI调用机制什么,那么必须先知道他是什么,学习就是先从是什么入手,看缩写字母一定要去查,当你查出全拼的时候就恍然大悟~ Java Remote method protocol
/**
* @创建人 wxf
* @创建时间 2020/1/6
* @描述
*/
public class JudyEntry implements Serializable {
private String name;
private Integer age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
**
* @创建人 wxf
* @创建时间 2020/1/6
* @描述
*/
public interface JudyInterface extends Remote {
public String getJudyName() throws RemoteException;
}
/**
* @创建人 wxf
* @创建时间 2020/1/6
* @描述
*/
public class JudyServerImpl extends UnicastRemoteObject implements JudyInterface {
protected JudyServerImpl() throws RemoteException {
super();
}
@Override
public String getJudyName() throws RemoteException {
JudyEntry judyEntry = new JudyEntry();
judyEntry.setAge(18);
judyEntry.setName("judy");
return judyEntry.getName();
}
}
/**
* @创建人 wxf
* @创建时间 2020/1/6
* @描述
*/
public class ProgramClient {
public static void main(String[] args) {
try {
JudyInterface judyInterface = (JudyInterface) Naming.lookup("rmi://127.0.0.1:6600/JudyInterface");
String judyName = judyInterface.getJudyName();
System.out.println(judyName);
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* @创建人 wxf
* @创建时间 2020/1/6
* @描述
*/
public class ProgramServer {
public static void main(String[] args) {
try {
JudyInterface judyInterface = new JudyServerImpl();
//创建注册表,让服务注册到这上面。
LocateRegistry.createRegistry(6600);
Naming.rebind("rmi://127.0.0.1:6600/JudyInterface",judyInterface);
System.out.println("begin");
Thread.sleep(600000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
我们换一种思路想,快递再给我们寄出物品的时候,首先他会存储在一个地方,然后写上我们的地方,然后我们去固定的地点取物品。其实跟远程调用一样,有注册存储,有唯一标识。
C的全英是Consisency 一致性: 在分布式系统中所有节点的数据都应该保持一致,当然当数据节点越多的时候数据同步也是越耗时的
A的全英是Availability 可用性:服务一直可用,这是必须要达到的一点
p的全英是Partition tolerance : 分区容错性 。高可用的提现也就是在某个服务单个节点挂点之后,不会影响整个服务崩溃。
假如目前我们有两个节点N1和N2,A表示程序 V表示数据库存储数据。
在正常情况下N1 发送->V1 消息 -> 给 V0 存储, V0 存储收到V1消息 之后通过M --> 发送给B中V0,这个时候N1和N2 节点的数据是一致的。
在非正常情况下,论文阐述的是在网络异常的情况下,将会发生什么? 从图中我们可以看出A中的V1到B中V0 由于网络原因无法发送消息V0,那么这个时候N1节点应该高可用还是服务一致性,所以这也就是CAP原理中分区容错性和高可用的二选一原则。(根据不同业务场景选择CP或者AP)
解决CAP引发的问题。例如:服务降级,分区容错,最终一致性, 所以无论是nacos ,eureka,zk都有这些特性的相关实现。Eureka是AP,zk是CP,Nacos是AP
对于Nacos的注册与发现实现过程我就不再粘贴代码,我把github地址附上,大家有需要可以自己拿取~
https://github.com/JudyWang88/nacos-config-discovery-gateway.git如果你感觉不错,那就来波关注点赞,嘻嘻~ ,这里面Nacos是服务注册,使用Dubbo服务发现
根据源码我们来看一下是如何实现的,我们从图中指的这个类入手
第一步获取属性
Properties properties = new Properties();
properties.setProperty("serverAddr","127.0.0.1");
properties.setProperty("namespace", "judy");
第二步创建注册表
NamingService naming = NamingFactory.createNamingService(properties);
第三步注册
naming.registerInstance("nacos.test.3", "11.11.11.11", 8888, "TEST1");
细节:注册表如何创建
public static NamingService createNamingService(Properties properties) throws NacosException {
try {
//3 利用反射,跟config使用的是一个套路
Class<?> driverImplClass = Class.forName("com.alibaba.nacos.client.naming.NacosNamingService");
Constructor constructor = driverImplClass.getConstructor(Properties.class);
NamingService vendorImpl = (NamingService)constructor.newInstance(properties);
return vendorImpl;
} catch (Throwable e) {
throw new NacosException(NacosException.CLIENT_INVALID_PARAM, e);
}
}
//4 走构造函数
public NacosNamingService(Properties properties) {
//初始化方法,也就是把该准备的都准备好
init(properties);
}
接下来看看EventDispatcher,NamingProxy,BeatReactor,HostReactor
private void init(Properties properties) {
namespace = InitUtils.initNamespaceForNaming(properties);
initServerAddr(properties);
InitUtils.initWebRootContext();
initCacheDir();
initLogName(properties);
eventDispatcher = new EventDispatcher();
serverProxy = new NamingProxy(namespace, endpoint, serverList);
serverProxy.setProperties(properties);
beatReactor = new BeatReactor(serverProxy, initClientBeatThreadCount(properties));
hostReactor = new HostReactor(eventDispatcher, serverProxy, cacheDir, isLoadCacheAtStart(properties),
initPollingThreadCount(properties));
}
//创建一个线程吧监听的服务获取到所有元数据
executor.execute(new Notifier());
public NamingEvent(String serviceName, String groupName, String clusters, List<Instance> instances) {
this.serviceName = serviceName;
this.groupName = groupName;
this.clusters = clusters;
this.instances = instances;
}
// 注册domain放到list中
serverProxy = new NamingProxy(namespace, endpoint, serverList);
public NamingProxy(String namespaceId, String endpoint, String serverList) {
this.namespaceId = namespaceId;
this.endpoint = endpoint;
if (StringUtils.isNotEmpty(serverList)) {
this.serverList = Arrays.asList(serverList.split(","));
if (this.serverList.size() == 1) {
this.nacosDomain = serverList;
}
}
initRefreshSrvIfNeed();
}
private void initRefreshSrvIfNeed() {
if (StringUtils.isEmpty(endpoint)) {
return;
}
ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(1, new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setName("com.alibaba.nacos.client.naming.serverlist.updater");
t.setDaemon(true);
return t;
}
});
// 这块是根据serversFromEndpoint来判断是否需要有值和获取serverlist,如果没有值则定时循环获取
executorService.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
refreshSrvIfNeed();
}
}, 0, vipSrvRefInterMillis, TimeUnit.MILLISECONDS);
refreshSrvIfNeed();
}
serversFromEndpoint 是什么? 从nacos远程获取服务list
public List<String> getServerListFromEndpoint() {
try {
String urlString = "http://" + endpoint + "/nacos/serverlist";
List<String> headers = builderHeaders();
//重点
HttpClient.HttpResult result = HttpClient.httpGet(urlString, headers, null, UtilAndComs.ENCODING);
if (HttpURLConnection.HTTP_OK != result.code) {
throw new IOException("Error while requesting: " + urlString + "'. Server returned: "
+ result.code);
}
}
BeatReactor
public BeatReactor(NamingProxy serverProxy, int threadCount) {
this.serverProxy = serverProxy;
//executorService定时器
executorService = new ScheduledThreadPoolExecutor(threadCount, new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setDaemon(true);
thread.setName("com.alibaba.nacos.naming.beat.sender");
return thread;
}
});
}
注册
naming.registerInstance("nacos.test.3", "11.11.11.11", 8888, "TEST1");
@Override
public void registerInstance(String serviceName, String groupName, String ip, int port, String clusterName) throws NacosException {
Instance instance = new Instance();
instance.setIp(ip);
instance.setPort(port);
instance.setWeight(1.0);
instance.setClusterName(clusterName);
registerInstance(serviceName, groupName, instance);
}
@Override
public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException {
if (instance.isEphemeral()) {
BeatInfo beatInfo = new BeatInfo();
beatInfo.setServiceName(NamingUtils.getGroupedName(serviceName, groupName));
beatInfo.setIp(instance.getIp());
beatInfo.setPort(instance.getPort());
beatInfo.setCluster(instance.getClusterName());
beatInfo.setWeight(instance.getWeight());
beatInfo.setMetadata(instance.getMetadata());
beatInfo.setScheduled(false);
beatInfo.setPeriod(instance.getInstanceHeartBeatInterval());
beatReactor.addBeatInfo(NamingUtils.getGroupedName(serviceName, groupName), beatInfo);
}
serverProxy.registerService(NamingUtils.getGroupedName(serviceName, groupName), groupName, instance);
}
public final Map<String, BeatInfo> dom2Beat = new ConcurrentHashMap<String, BeatInfo>();
public void addBeatInfo(String serviceName, BeatInfo beatInfo) {
NAMING_LOGGER.info("[BEAT] adding beat: {} to beat map.", beatInfo);
String key = buildKey(serviceName, beatInfo.getIp(), beatInfo.getPort());
BeatInfo existBeat = null;
//fix #1733
if ((existBeat = dom2Beat.remove(key)) != null) {
existBeat.setStopped(true);
}
dom2Beat.put(key, beatInfo);
executorService.schedule(new BeatTask(beatInfo), beatInfo.getPeriod(), TimeUnit.MILLISECONDS);
MetricsMonitor.getDom2BeatSizeMonitor().set(dom2Beat.size());
}
总结
Nacos总结了两篇文章,目前先总结到这吧~ 下两篇文章分享分布式事务解决方案 ~ 期待下周相约吧~