继续上篇内容,本篇讲解注册订阅相关内容。
本例中的RPC注册服务采用应用级服务,一个应用只向注册中心注册一个实例。相比方法级的注册,减少大量的注册信息,便于服务总量能进行估算等。
一. 选择注册中心
这里注册中心选用Nacos,为啥选用nacos呢?
- nacos 有良好的中文文档
- 可以当作配置中心和注册中心
- 较好的后台可视化界面
客户端选择了2.x版本, 引入依赖,
<properties>
<nacos-client.version>2.0.4</nacos-client.version>
</properties>
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
<version>${nacos-client.version}</version>
</dependency>
二. 注册服务
在上篇末尾提到了ProviderContextStart ,该类实现SmartLifecycle接口,容器启动会执行start方法,停止时会执行stop方法。
整体流程如下:
- 获取应用地址
- 获取配置信息
- 启动一个Netty服务
- 获取容器中所有的ProviderBean对象,放入提供者上下文中
- 向nacos注册服务
public class ProviderContextStart implements SmartLifecycle, ApplicationContextAware {
private final Logger log = LoggerFactory.getLogger(getClass());
private volatile boolean RUNNING = false;
private ApplicationContext applicationContext;
private NamingService namingService;
private RainRpcConfig rpcConfig;
@Override
public void start() {
log.info("准备注册服务");
try {
// 获取绑定地址
String ip = InetAddress.getLocalHost().getHostAddress();
rpcConfig = applicationContext.getBean(RainRpcConfig.class);
if (StringUtils.isEmpty(rpcConfig.getApplicationName())) {
log.info("rpc未配置应用名");
return;
}
// 启动服务类
new Thread(new NettyServerTask(ip, rpcConfig.getPort())).start();
// 初始化提供者
Map<String, ProviderBean> beansOfType = applicationContext.getBeansOfType(ProviderBean.class);
ProviderContext.PROVIDER_MAP.putAll(beansOfType);
// 注册应用
namingService = NamingFactory.createNamingService(rpcConfig.getRegisterAddress());
namingService.registerInstance(rpcConfig.getApplicationName(), ip, rpcConfig.getPort(), rpcConfig.getCluster());
RUNNING = true;
} catch (UnknownHostException | NacosException e) {
log.error("注册服务异常", e);
}
log.info("注册服务成功");
}
@Override
public void stop() {
log.info("准备注销服务");
if (namingService != null) {
try {
String ip = InetAddress.getLocalHost().getHostAddress();
namingService.deregisterInstance(rpcConfig.getApplicationName(), ip, rpcConfig.getPort(), rpcConfig.getCluster());
RUNNING = false;
} catch (UnknownHostException | NacosException e) {
log.error("注销服务异常", e);
}
}
log.info("注销服务成功");
}
@Override
public boolean isRunning() {
return RUNNING;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
ProviderBean 是设置了对接口实现类的引用
public class ProviderBean <T>{
private Class<T> interfaceClass;
private T ref;
private Method[] methodList;
/**
* 获取调用方法
* @author wu
* @param simpleMethodName
* @return {@link Method}
*/
public Method getMethod(String simpleMethodName) {
for (Method method : methodList) {
if (simpleMethodName.equals(method.getName())) {
return method;
}
}
return null;
}
// ....省略get、set等方法
}
三. 引用服务
ConsumerBean 是在上篇自动扫描@RainConsumer时注入容器的。
ConsumerBean 是一个FactoryBean对象,注入容器,会调用getObject()方法,最终调用initRef()。
public class ConsumerBean<T> implements FactoryBean<T>, ApplicationContextAware {
private final Log logger = LogFactory.getLog(getClass());
/**
* 服务接口
*/
private Class<T> interfaceClass;
private RainConsumer annotation;
private ApplicationContext applicationContext;
/**
* 注册中心地址
*/
@Value("${rpc.register.address}")
private String registerAddress;
private T ref;
public T initRef() {
Enhancer enhancer = new Enhancer();
// 设置enhancer对象的父类
enhancer.setSuperclass(interfaceClass);
// 设置enhancer的回调对象
enhancer.setCallback(new ConsumerMethodInterceptor(annotation,applicationContext));
// 创建代理对象
ref = (T) enhancer.create();
try {
connect();
} catch (NacosException e) {
logger.error("获取服务异常", e);
}
return ref;
}
/**
* 连接注册中心获取服务
*
* @return
* @author wu
*/
public void connect() throws NacosException {
logger.info("注册中心地址:" + registerAddress);
// 获取注册中心
NamingService naming = NamingFactory.createNamingService(registerAddress);
// 查询健康的服务实例
List<Instance> allInstances = naming.selectInstances(annotation.name(), Collections.singletonList(annotation.cluster()), true);
// 服务路径包裹
ProviderDirectory directory = new ProviderDirectory(annotation.name());
logger.info("已获取到服务提供者实例" + JSON.toJSONString(allInstances));
for (Instance instance : allInstances) {
// 开启新服务
startNewService(instance, directory);
}
// 放入消费者上下文
ConsumerContext.CLIENT_MAP.putIfAbsent(annotation.name(), directory);
// 添加监听
naming.subscribe(annotation.name(), Collections.singletonList(annotation.cluster()), this::subscribe);
}
/**
* 订阅回调事件
*
* @param event
*/
public void subscribe(Event event) {
logger.info("接收到回调事件:" + JSON.toJSONString(event));
if (event instanceof NamingEvent) {
NamingEvent namingEvent = (NamingEvent) event;
ProviderDirectory providerDirectory = ConsumerContext.CLIENT_MAP.get(namingEvent.getServiceName());
if (providerDirectory == null) {
logger.error(namingEvent.getServiceName() + "对应的服务目录不存在");
return;
}
// 对应服务全部下线
if (CollectionUtils.isEmpty(namingEvent.getInstances())) {
providerDirectory.clearService();
}
// 当前的服务key集合
Set<String> currentKeyList = providerDirectory.getProviderList()
.stream()
.map(ClientHolder::getKey)
.collect(Collectors.toSet());
// 注册中心服务key集合
Map<String, Instance> newInstanceMap = namingEvent.getInstances()
.stream()
.collect(Collectors.toMap(item -> ProviderDirectory.generateKey(item.getIp(), item.getPort()), v -> v));
Set<String> newKeyList = newInstanceMap.keySet();
List<String> commonKeyList = CollectionUtil.getCommon(currentKeyList, newKeyList);
// 获取移除的服务
List<String> removeKeyList = CollectionUtil.getDiff(currentKeyList, commonKeyList);
// 获取新增的服务
List<String> saveKeyList = CollectionUtil.getDiff(newKeyList, commonKeyList);
// 移除服务
providerDirectory.removeService(removeKeyList);
// 开启新服务
startNewService(newInstanceMap, saveKeyList, providerDirectory);
}
}
/**
* 开启新服务
*
* @param newInstanceMap
* @param saveKeyList
* @param providerDirectory
* @return
*/
private void startNewService(Map<String, Instance> newInstanceMap, List<String> saveKeyList, ProviderDirectory providerDirectory) {
if (CollectionUtils.isEmpty(saveKeyList)) {
return;
}
for (String key : saveKeyList) {
Instance instance = newInstanceMap.get(key);
startNewService(instance, providerDirectory);
}
}
/**
* 开启新服务
*
* @param instance
* @param providerDirectory
*/
private void startNewService(Instance instance, ProviderDirectory providerDirectory) {
NettyClientTask client = new NettyClientTask(instance.getIp(), instance.getPort());
// 启动服务
new Thread(client).start();
// 添加服务
String serviceKey = ProviderDirectory.generateKey(instance.getIp(), instance.getPort());
providerDirectory.addService(serviceKey, client);
}
@Override
public T getObject() throws Exception {
if (ref != null) {
return ref;
}
return initRef();
}
// ....省略get、set等方法
}
ProviderDirectory对象持有所有同类型应用的所有实例
public class ProviderDirectory {
private String applicationName;
private List<ClientHolder> providerList;
private final AtomicInteger seq=new AtomicInteger(0);
public ProviderDirectory(String applicationName) {
this.applicationName = applicationName;
this.providerList = new ArrayList<>();
}
/**
* 生成服务key
* @param ip
* @param port
* @return
*/
public static String generateKey(String ip,int port) {
return ip + ":" + port;
}
/**
* 获取客户端实例
* @return
*/
public NettyClientTask getClient() {
if (providerList.isEmpty()) {
return null;
}
List<ClientHolder> temp = this.providerList;
int index = seq.getAndIncrement();
return temp.get(index % temp.size()).getClient();
}
/**
* 添加服务实例
* @param serviceKey
* @param client
*/
public void addService(String serviceKey,NettyClientTask client) {
ClientHolder clientHolder = new ClientHolder(serviceKey, client);
providerList.add(clientHolder);
}
/**
* 清除所有服务
*/
public void clearService() {
providerList.clear();
}
/**
* 删除指定服务
* @param keysList
*/
public void removeService(List<String> keysList) {
providerList.removeIf(holder -> keysList.contains(holder.getKey()));
}
}
public class ClientHolder {
private String key;
private NettyClientTask client;
public ClientHolder(String key,NettyClientTask client) {
this.key = key;
this.client = client;
}
// 省略get/set
}
ConsumerBean 代理对象调用方法时,会执行ConsumerMethodInterceptor 的intercept方法,此方法用于构造请求对象,发送请求。
public class ConsumerMethodInterceptor implements MethodInterceptor {
private RainConsumer annotation;
private Object degradationService;
private ApplicationContext applicationContext;
public ConsumerMethodInterceptor(RainConsumer annotation,ApplicationContext applicationContext) {
this.annotation = annotation;
this.applicationContext = applicationContext;
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
Request request = Request.buildRequest(method.getDeclaringClass().getName(), method.getName(), objects);
return ConsumerContext.invoke(request, annotation.name(),annotation.timeout());
}
}
四. 上下文对象
ProviderContext 作为服务端上下文,存放了已加载的服务类。
Netty处理完数据流之后,调用exec方法,根据对应的类执行对应的方法。
public class ProviderContext {
/**
* beanName和 bean的对应关系,beanName 为全路径类名
*/
public static final Map<String, ProviderBean> PROVIDER_MAP = new ConcurrentHashMap<>();
public static Object exec(String className, String methodName, Object params) throws InvocationTargetException, IllegalAccessException {
ProviderBean providerBean = PROVIDER_MAP.get(className);
if (providerBean == null) {
throw new RuntimeException("服务提供者不存在,调用类:" + className + ",调用方法" + methodName);
}
Method method = providerBean.getMethod(methodName);
// 无参数
if (method.getParameterTypes().length == 0) {
return method.invoke(providerBean.getRef());
}
// 单个参数
if (method.getParameterTypes().length == 1) {
return method.invoke(providerBean.getRef(), params);
}
Object[] paramsArr = (Object[]) params;
// 多个参数
return method.invoke(providerBean.getRef(), paramsArr);
}
}
ConsumerContext 是客户端上下文,此处用于发起以及接收rpc的调用结果。
public class ConsumerContext {
public static final Map<Long, RainFuture> RESULT_MAP = new ConcurrentHashMap<>();
public static final Map<String, ProviderDirectory> CLIENT_MAP = new ConcurrentHashMap<>();
public static Object invoke(Request request, String applicationName,Long requestTimeout) throws Exception {
RainFuture rainFuture = new RainFuture();
RESULT_MAP.put(request.getId(), rainFuture);
getChannelFuture(applicationName).channel().writeAndFlush(request);
Response response = rainFuture.get(requestTimeout, TimeUnit.SECONDS);
RESULT_MAP.remove(request.getId());
// 如果出现异常
if (ResultEnum.ERROR.getCode() == response.getCode()) {
throw response.getCause();
}
return response.getData();
}
public static ChannelFuture getChannelFuture(String applicationName) {
ProviderDirectory providerDirectory = CLIENT_MAP.get(applicationName);
NettyClientTask client = providerDirectory.getClient();
if (client == null) {
throw new RuntimeException("client 客户端不存在");
}
while (true) {
//获取future,线程有等待处理时间
if (null == client.channelFuture) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
continue;
}
return client.channelFuture;
}
}
}