一起写RPC框架(十八)RPC注册中心五--注册中心之持久化的操作

 介绍注册中心的功能的小节,我们曾经说过,注册中心要有持久化的操作,将一些服务的审核信息放到硬盘上,这样做的原因就是因为我们所有的服务信息都是放在内存里面的,如果注册中心的实例宕掉,或者服务器因为某种原因停止的时候,这样某些服务的审核记录就无法找回,为了避免这样的问题,我们需要做的事情就是把这些服务审核信息定时刷盘,把这些信息保存到硬盘上去,然后每个注册中心服务启动的时候,去硬盘上去恢复这些信息,这样就可以规避这样的问题了


其实这个操作与RPC之间的联系不大,要解决的问题其实很简单,就是把信息,我们可以把这个信息序列化json字符串,然后根据给定的指定路径,指定的文件名,把json字符串保存的文件里面,注册中心每次启动的时候,读取文件中的字符串信息,然后序列化成对象,再保存到内存,这样就可以避免上述的问题了


好了,那么问题就变得很简单了,我们只要写一个持久化的工具类问题就大体解决了


下面的代码基本上来自于RocketMQ

package org.laopopo.common.utils;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;

/**
 * 
 * @author BazingaLyn
 * @description 持久化工具
 * @time 2016年9月1日
 * @modifytime
 */
public class PersistUtils {
	
	
	/**
	 * 将json数据存到某个文件中
	 * @param str
	 * @param fileName
	 * @throws IOException
	 */
	public static final void string2File(final String str, final String fileName) throws IOException {
        String tmpFile = fileName + ".tmp";
        string2FileNotSafe(str, tmpFile);

        String bakFile = fileName + ".bak";
        String prevContent = file2String(fileName);
        if (prevContent != null) {
            string2FileNotSafe(prevContent, bakFile);
        }

        File file = new File(fileName);
        file.delete();

        file = new File(tmpFile);
        file.renameTo(new File(fileName));
    }
	
	public static final void string2FileNotSafe(final String str, final String fileName) throws IOException {
        File file = new File(fileName);
        File fileParent = file.getParentFile();
        if (fileParent != null) {
            fileParent.mkdirs();
        }
        FileWriter fileWriter = null;

        try {
            fileWriter = new FileWriter(file);
            fileWriter.write(str);
        }
        catch (IOException e) {
            throw e;
        }
        finally {
            if (fileWriter != null) {
                try {
                    fileWriter.close();
                }
                catch (IOException e) {
                    throw e;
                }
            }
        }
    }
	
	public static final String file2String(final String fileName) {
		// 读取txt内容为字符串
		StringBuffer txtContent = new StringBuffer();
		// 每次读取的byte数
		byte[] b = new byte[8 * 1024];
		InputStream in = null;
		try {
			// 文件输入流
			in = new FileInputStream(fileName);
			while (in.read(b) != -1) {
				// 字符串拼接
				txtContent.append(new String(b));
			}
			// 关闭流
			in.close();
		} catch (Exception e) {
			return null;
		} finally {
			if (in != null) {
				try {
					in.close();
				} catch (IOException e) {
				}
			}
		}
		return txtContent.toString();
	}

}


其实还是比较简单的,有了这个工具类,我们再做一个定时任务,每隔一段时间去把内存中的数据刷到硬盘中:

this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {

			@Override
			public void run() {
				// 延迟60秒,每隔一段时间将一些服务信息持久化到硬盘上
				try {
					DefaultRegistryServer.this.getProviderManager().persistServiceInfo();
				} catch (Exception e) {
					logger.warn("schedule persist failed [{}]",e.getMessage());
				} 
			}
}, 60, this.registryServerConfig.getPersistTime(), TimeUnit.SECONDS);


/**
	 * 持久化操作
	 * 原则:1)首先优先从globalRegisterInfoMap中持久化到库中
	 *      2)如果globalRegisterInfoMap中没有信息,则从老版本中的historyRecords中的信息重新保存到硬盘中去,这样做的好处就是不需要多维护一个historyRecords这个全局变量的信息有效性
	 *      
	 * 这样做的原因是因为,只要有服务注册到注册中心,在注册的处理的时候,已经从历史中获取到以前审核和负载的情况,所以globalRegisterInfoMap中的信息是最新的
	 * 如果有些服务以前注册过,但这次重启之后没有注册,所以就需要重新将其更新一下合并记录
	 * @throws IOException
	 */
	public void persistServiceInfo() throws IOException {
		
		Map<String,RegistryPersistRecord> persistMap = new HashMap<String, RegistryPersistRecord>();
		ConcurrentMap<String, ConcurrentMap<Address, RegisterMeta>> _globalRegisterInfoMap = this.globalRegisterInfoMap; //_stack copy
		ConcurrentMap<String, LoadBalanceStrategy> _globalServiceLoadBalance = this.globalServiceLoadBalance; //_stack copy
		ConcurrentMap<String, RegistryPersistRecord> _historyRecords = this.historyRecords;
		
		//globalRegisterInfoMap 中保存
		if(_globalRegisterInfoMap.keySet() != null){
			
			for(String serviceName : _globalRegisterInfoMap.keySet()){
				
				RegistryPersistRecord persistRecord = new RegistryPersistRecord();
				persistRecord.setServiceName(serviceName);
				persistRecord.setBalanceStrategy(_globalServiceLoadBalance.get(serviceName));
				
				List<PersistProviderInfo> providerInfos = new ArrayList<PersistProviderInfo>();
				ConcurrentMap<Address, RegisterMeta> serviceMap = _globalRegisterInfoMap.get(serviceName);
				for(Address address : serviceMap.keySet()){
					PersistProviderInfo info = new PersistProviderInfo();
					info.setAddress(address);
					info.setIsReviewed(serviceMap.get(address).getIsReviewed());
					providerInfos.add(info);
				}
				persistRecord.setProviderInfos(providerInfos);
				persistMap.put(serviceName, persistRecord);
			}
		}
		
		
		if(null != _historyRecords.keySet()){
			
			for(String serviceName :_historyRecords.keySet()){
				
				//不包含的时候
				if(!persistMap.keySet().contains(serviceName)){
					persistMap.put(serviceName, _historyRecords.get(serviceName));
				}else{
					
					//负载策略不需要合并更新,需要更新的是existRecord中没有的provider的信息
					List<PersistProviderInfo> providerInfos = new ArrayList<PersistProviderInfo>();
					RegistryPersistRecord existRecord = persistMap.get(serviceName);
					providerInfos.addAll(existRecord.getProviderInfos());
					
					//可能需要合并的信息,合并原则,如果同地址的审核策略以globalRegisterInfoMap为准,如果不同地址,则合并信息
					RegistryPersistRecord possibleMergeRecord = _historyRecords.get(serviceName);
					List<PersistProviderInfo> possibleProviderInfos = possibleMergeRecord.getProviderInfos();
					
					for(PersistProviderInfo eachPossibleInfo : possibleProviderInfos){
						
						Address address = eachPossibleInfo.getAddress();
						
						boolean exist = false;
						for(PersistProviderInfo existProviderInfo : providerInfos){
							if(existProviderInfo.getAddress().equals(address)){
								exist = true;
								break;
							}
						}
						if(!exist){
							providerInfos.add(eachPossibleInfo);
						}
					}
					existRecord.setProviderInfos(providerInfos);
					persistMap.put(serviceName, existRecord);
				}
			}
			
			if(null != persistMap.values() && !persistMap.values().isEmpty()){
				
				String jsonString = JSON.toJSONString(persistMap.values());
				
				if(jsonString != null){
					PersistUtils.string2File(jsonString, this.defaultRegistryServer.getRegistryServerConfig().getStorePathRootDir());
				}
			}
		}
	}



每次注册中心实例启动的时候,再从硬盘上恢复:

/**
	 * 从硬盘上恢复一些服务的审核负载算法的信息
	 */
	private void recoverServiceInfoFromDisk() {
		
		String persistString = PersistUtils.file2String(this.registryServerConfig.getStorePathRootDir());
		
		if (null != persistString) {
			List<RegistryPersistRecord> registryPersistRecords = JSON.parseArray(persistString.trim(), RegistryPersistRecord.class);
			
			if (null != registryPersistRecords) {
				for (RegistryPersistRecord metricsReporter : registryPersistRecords) {
					
				     String serviceName = metricsReporter.getServiceName();
				     this.getProviderManager().getHistoryRecords().put(serviceName, metricsReporter);
				     
				}
			}
		}
		
	}


基本上的代码思路就是这样了,整个注册中心需要做的事情大体是就是如此的,思路和需要完成的功能还是比较清晰的,实现起来难度不是很大,当然可能会有bug,如果有做错欢迎大家指出来,我改正,注册中心模块的简短说明到此为止了,详细的具体代码可以查看Github




  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
当前主流的RPC(Remote Procedure Call)框架有很多,Feign和Dubbo是其中两个比较知名的。下面是关于它们的简要介绍: 1. **Feign**: - Feign是Netflix开发的一款轻量级的API客户端,主要用于简化服务间的接口调用。它基于Java注解,提供了一种声明式的接口调用方式,开发者只需定义接口,Feign会自动生成实现类和底层HTTP请求。 - Feign支持各种HTTP库,如OkHttp、Apache HttpClient等,并且可以轻松地集成到Spring Cloud生态系统中,方便微服务之间的通信。 2. **Dubbo**: - Dubbo是一个开源的企业级高性能远程服务调用框架,主要适用于大规模分布式服务架构,支持高并发、高性能和容错性。Dubbo基于Java,提供了一套全面的服务治理解决方案,包括注册中心、服务发现、负载均衡、熔断、限流等。 - 它通常用于企业级的微服务架构中,尤其在大型分布式系统中的服务间通信方面非常常见。 除了这两个,还有其他一些主流RPC框架,例如: - **gRPC**:Google开源的高性能RPC框架,基于Protocol Buffers协议,提供了高性能和安全性。 - **Retrofit**:Android和Java平台的流行库,结合OkHttp实现RESTful API调用。 - **Hystrix**(已被Netflix弃用):原本是Amazon的故障隔离工具,但现在常常与Feign一起作为服务降级和熔断方案使用。 如果你对这些RPC框架感兴趣,可以关注它们各自的特性、适用场景以及与其他技术栈的集成情况。有关更多细节,你可以询问: 1. Feign和Retrofit相比,各有何优缺点? 2. Dubbo和gRPC在性能上的差异体现在哪些方面? 3. 在微服务架构中,如何选择合适的RPC框架

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值