互联网公司的系统有成千上万个大大小小的服务组成,服务各自部署在不同的机器上,服务间的调用需要用到网络通信,服务消费方每调用一个服务都要写一坨网络通信相关的代码,不仅复杂而且极易出错。还要考虑新服务依赖老服务时如何调用老服务,别的服务依赖新服务的时候新服务如何发布方便他人调用。如何解决这个问题呢?业界一般采用RPC远程调用的方式来实现。
RPC:
Remote Procedure Call Protocol 既 远程过程调用,一种能让我们像调用本地服务一样调用远程服务,可以让调用者对网络通信这些细节无感知,比如服务消费方在执行 helloWorldService.sayHello("sowhat") 时,实质上调用的是远端的服务。这种方式其实就是RPC,RPC思想在各大互联网公司中被广泛使用,如阿里巴巴的dubbo、当当的Dubbox 、Facebook 的 thrift、Google 的grpc、Twitter的finagle等。
说了那么多,还是实现一个简易版的RPC demo吧。
public interface SoWhatService {
String sayHello(String name);
}
接口类实现
public class SoWhatServiceImpl implements SoWhatService
{
@Override
public String sayHello(String name)
{
return "你好啊 " + name;
}
}
服务注册对外提供者
/**
* 服务注册对外提供者
*/
public class ServiceFramework
{
public static void export(Object service, int port) throws Exception
{
ServerSocket server = new ServerSocket(port);
while (true)
{
Socket socket = server.accept();
new Thread(() ->
{
try
{
//反序列化
ObjectInputStream input = new ObjectInputStream(socket.getInputStream());
//读取方法名
String methodName =(String) input.readObject();
//参数类型
Class<?>[] parameterTypes = (Class<?>[]) input.readObject();
//参数
Object[] arguments = (Object[]) input.readObject();
//找到方法
Method method = service.getClass().getMethod(methodName, parameterTypes);
//调用方法
Object result = method.invoke(service, arguments);
// 返回结果
ObjectOutputStream output = new ObjectOutputStream(socket.getOutputStream());
output.writeObject(result);
} catch (Exception e)
{
e.printStackTrace();
}
}).start();
}
}
}
服务运行
public class ServerMain
{
public static void main(String[] args)
{
//服务提供者 暴露出接口
SoWhatService service = new SoWhatServiceImpl();
try
{
ServiceFramework.export(service, 1412);
} catch (Exception e)
{
e.printStackTrace();
}
}
}
动态代理调用远程服务
/**
* @author sowhat
* 动态代理调用远程服务
*/
public class RpcFunction
{
public static <T> T refer(Class<T> interfaceClass, String host, int port) throws Exception
{
return (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class<?>[]{interfaceClass},
new InvocationHandler()
{
@Override
public Object invoke(Object proxy, Method method, Object[] arguments) throws Throwable
{
//指定 provider 的 ip 和端口
Socket socket = new Socket(host, port);
ObjectOutputStream output = new ObjectOutputStream(socket.getOutputStream());
//传方法名
output.writeObject(method.getName());
//传参数类型
output.writeObject(method.getParameterTypes());
//传参数值
output.writeObject(arguments);
ObjectInputStream input = new ObjectInputStream(socket.getInputStream());
//读取结果
Object result = input.readObject();
return result;
}
});
}
}
调用方法
public class RunMain
{
public static void main(String[] args)
{
try
{
//服务调用者 需要设置依赖
SoWhatService service = RpcFunction.refer(SoWhatService.class, "127.0.0.1", 1412);
System.out.println(service.sayHello(" sowhat1412"));
} catch (Exception e)
{
e.printStackTrace();
}
}
}
Dubbo 是阿里巴巴研发开源工具,主要分为2.6.x 跟 2.7.x 版本。是一款分布式、高性能、透明化的 RPC 服务框架,提供服务自动注册、自动发现等高效服务治理方案,可以和Spring 框架无缝集成,它提供了6大核心能力:
1. 面向接口代理的高性能RPC调用
2. 智能容错和负载均衡
3. 服务自动注册和发现
4. 高度可扩展能力
5. 运行期流量调度
6. 可视化的服务治理与运维
调用过程:
- 服务提供者 Provider 启动然后向 Registry 注册自己所能提供的服务。
- 服务消费者 Consumer 向Registry订阅所需服务,Consumer 解析Registry提供的元信息,从服务中通过负载均衡选择 Provider调用。
- 服务提供方 Provider 元数据变更的话Registry会把变更推送给Consumer,以此保证Consumer获得最新可用信息。
注意点:
- Provider 跟 Consumer 在内存中记录调用次数跟时间,定时发送统计数据到Monitor,发送的时候是短连接。
- Monitor 跟 Registry 是可选的,可直接在配置文件中写好,Provider 跟 Consumer进行直连。
- Monitor 跟 Registry 挂了也没事, Consumer 本地缓存了 Provider 信息。
- Consumer 直接调用 Provider 不会经过 Registry。Provider、Consumer这俩到 Registry之间是长连接。
如上图,总的而言 Dubbo 分为三层。
- Busines层:由用户自己来提供接口和实现还有一些配置信息。
- RPC层:真正的RPC调用的核心层,封装整个RPC的调用过程、负载均衡、集群容错、代理。
- Remoting层:对网络传输协议和数据转换的封装。
如果每一层再细分下去,一共有十层。
他们之间的调用关系直接看下面官网图即可。
Dubbo 采用 微内核设计 + SPI 扩展技术来搭好核心框架,同时满足用户定制化需求。这里重点说下SPI。
操作系统层面的微内核跟宏内核:
- 微内核Microkernel:是一种内核的设计架构,由尽可能精简的程序所组成,以实现一个操作系统所需要的最基本功能,包括了底层的寻址空间管理、线程管理、与进程间通信。成功案例是QNX系统,比如黑莓手机跟车用市场。
- 宏内核Monolithic :把 进程管理、内存管理、文件系统、进程通信等功能全部作为内核来实现,而微内核则仅保留最基础的功能,Linux 就是宏内核架构设计。
Dubbo中的广义微内核:
- 思想是 核心系统 + 插件,说白了就是把不变的功能抽象出来称为核心,把变动的功能作为插件来扩展,符合开闭原则,更容易扩展、维护。比如小霸王游戏机中机体本身作为核心系统,游戏片就是插件。vscode、Idea、chrome等都是微内核的产物。
- 微内核架构其实是一直架构思想,可以是框架层面也可以是某个模块设计,它的本质就是将变化的部分抽象成插件,使得可以快速简便地满足各种需求又不影响整体的稳定性。
主流的数据库有MySQL、Oracle、DB2等,这些数据库是不同公司开发的,它们的底层协议不大一样,那怎么约束呢?一般就是定制统一接口,具体实现不管,反正面向相同的接口编程即可。等到真正使用的时候用具体的实现类就好,问题是哪里找用那个实现类呢?这时候就采用约定好的法则将实现类写到指定位置即可。
SPI 全称为 Service Provider Interface,是一种服务发现机制。它约定在ClassPath路径下的META-INF/services文件夹查找文件,自动加载文件里所定义的类。
接口:
package com.example.demo.spi;
public interface SPIService {
void execute();
}
实现类1:
public class SpiImpl1 implements SPIService{
@Override
public void execute() {
System.out.println("SpiImpl1.execute()");
}
}
实现类2:
public class SpiImpl2 implements SPIService{
@Override
public void execute() {
System.out.println("SpiImpl2.execute()");
}
}
配置路径
调用加载类
package com.example.demo.spi;
import sun.misc.Service;
import java.util.Iterator;
import java.util.ServiceLoader;
public class Test {
public static void main(String[] args) {
Iterator<SPIService> providers = Service.providers(SPIService.class);
ServiceLoader<SPIService> load = ServiceLoader.load(SPIService.class);
while(providers.hasNext()) {
SPIService ser = providers.next();
ser.execute();
}
System.out.println("--------------------------------");
Iterator<SPIService> iterator = load.iterator();
while(iterator.hasNext()) {
SPIService ser = iterator.next();
ser.execute();
}
}
}
ServiceLoader.load(SPIService.class) 底层调用大致逻辑如下: iterator.hasNext() 跟 iterator.next()底层调用大致如下:
JDK自带的不好用Dubbo 就自己实现了一个 SPI,该SPI 可以通过名字实例化指定的实现类,并且实现了 IOC 、AOP 与 自适应扩展 SPI 。
key = com.sowhat.value
Dubbo 对配置文件目录的约定,不同于 Java SPI ,Dubbo 分为了三类目录。
- META-INF/services/ :该目录下 SPI 配置文件是为了用来兼容 Java SPI 。
- META-INF/dubbo/ :该目录存放用户自定义的 SPI 配置文件。
- META-INF/dubbo/internal/ :该目录存 Dubbo 内部使用的 SPI 配置文件。
使用的话很简单 引入依赖,然后百度教程即可。
@Test
void sowhat()
{
ExtensionLoader<SPIService> spiService = ExtensionLoader.getExtensionLoader(SPIService.class); //按需获取实现类对象
SPIService demo1 = spiService.getExtension("SpiImpl1");
demo1.execute();
}
ExtensionLoader.getExtension 方法的整个思路是 查找缓存是否存在,不存在则读取SPI文件,通过反射创建类,然后设置依赖注入这些东西,有包装类就包装下,执行流程如下图所示:
说下重要的四个部分:
1 . injectExtension IOC
查找 set 方法,根据参数找到依赖对象则注入。
2 . WrapperClass AOP
包装类,Dubbo 帮你自动包装,只需要某个扩展类的构造函数只有一个参数,并且是扩展接口类型,就会被判定为包装类。
3 . Activate
Active 有三个属性,group 表示修饰在哪个端,是 provider 还是 consumer,value 表示在 URL参数中出现才会被激活,order 表示实现类的顺序。
需求:根据配置来进行 SPI 扩展的加载后不想在启动的时候让扩展被加载,想根据请求时候的参数来动态选择对应的扩展。实现:Dubbo用代理机制实现了自适应扩展,为用户想扩展的接口 通过JDK 或者 Javassist 编译生成一个代理类,然后通过反射创建实例。实例会根据本来方法的请求参数得知需要的扩展类,然后通过 ExtensionLoader.getExtensionLoader(type.class).getExtension(name)来获取真正的实例来调用,看个官网样例。
public interface WheelMaker {
Wheel makeWheel(URL url);
}
// WheelMaker 接口的自适应实现类
public class AdaptiveWheelMaker implements WheelMaker {
public Wheel makeWheel(URL url) {
if (url == null) {
throw new IllegalArgumentException("url == null");
}
// 1. 调用 url 的 getXXX 方法获取参数值
String wheelMakerName = url.getParameter("Wheel.maker");
if (wheelMakerName == null) {
throw new IllegalArgumentException("wheelMakerName == null");
}
// 2. 调用 ExtensionLoader 的 getExtensionLoader 获取加载器
// 3. 调用 ExtensionLoader 的 getExtension 根据从url获取的参数作为类名称加载实现类
WheelMaker wheelMaker = ExtensionLoader.getExtensionLoader(WheelMaker.class).getExtension(wheelMakerName);
// 4. 调用实现类的具体方法实现调用。
return wheelMaker.makeWheel(URL url);
}
}
查看Adaptive注解源码可知该注解可用在类或方法上,Adaptive 注解在类上或者方法上有不同的实现逻辑。
Adaptive 注解在类上时,Dubbo 不会为该类生成代理类,Adaptive 注解在类上的情况很少,在 Dubbo 中,仅有两个类被 Adaptive 注解了,分别是 AdaptiveCompiler 和 AdaptiveExtensionFactory,表示拓展的加载逻辑由人工编码完成,这不是我们关注的重点。
Adaptive 注解在方法上时,Dubbo 则会为该方法生成代理逻辑,表示拓展的加载逻辑需由框架自动生成,大致的实现机制如下:
@SPI("apple")
public interface FruitGranter {
Fruit grant();
@Adaptive
String watering(URL url);
}
---
// 苹果种植者
public class AppleGranter implements FruitGranter {
@Override
public Fruit grant() {
return new Apple();
}
@Override
public String watering(URL url) {
System.out.println("watering apple");
return "watering finished";
}
}
---
// 香蕉种植者
public class BananaGranter implements FruitGranter {
@Override
public Fruit grant() {
return new Banana();
}
@Override
public String watering(URL url) {
System.out.println("watering banana");
return "watering success";
}
}
调用方法实现:
public class ExtensionLoaderTest {
@Test
public void testGetExtensionLoader() {
// 首先创建一个模拟用的URL对象
URL url = URL.valueOf("dubbo://192.168.0.1:1412?fruit.granter=apple");
// 通过ExtensionLoader获取一个FruitGranter对象
FruitGranter granter = ExtensionLoader.getExtensionLoader(FruitGranter.class)
.getAdaptiveExtension();
// 使用该FruitGranter调用其"自适应标注的"方法,获取调用结果
String result = granter.watering(url);
System.out.println(result);
}
}
通过如上方式生成一个内部类。大致调用流程如下:
Dubbo框架是以URL为总线的模式,运行过程中所有的状态数据信息都可以通过URL来获取,比如当前系统采用什么序列化,采用什么通信,采用什么负载均衡等信息,都是通过URL的参数来呈现的,所以在框架运行过程中,运行到某个阶段需要相应的数据,都可以通过对应的Key从URL的参数列表中获取。URL 具体的参数如下:
protocol:指的是 dubbo 中的各种协议,如:dubbo thrift http username/password:用户名/密码 host/port:主机/端口 path:接口的名称 parameters:参数键值对
protocol://username:password@host:port/path?k=v
服务暴露从代码流程看分为三部分:
- 检查配置,最终组装成 URL。
- 暴露服务到到本地服务跟远程服务。
- 服务注册至注册中心。
服务暴露从对象构建转换看分为两步:
- 将服务封装成Invoker。
- 将Invoker通过协议转换为Exporter。
Dubbo中一个可执行体就是一个invoker,所以 provider 跟 consumer 都要向 invoker 靠拢。通过上面demo可知为了无感调用远程接口,底层需要有个代理类包装 invoker。
服务的引入时机有两种:
1 . 饿汉式:
通过实现 Spring 的 InitializingBean 接口中的 afterPropertiesSet 方法,容器通过调用 ReferenceBean的 afterPropertiesSet 方法时引入服务。
2 . 懒汉式(默认):
懒汉式是只有当服务被注入到其他类中时启动引入流程。
服务引用的三种方式:
- 本地引入:服务暴露时本地暴露,避免网络调用开销。
- 直接连接引入远程服务:不启动注册中心,直接写死远程Provider地址 进行直连。
- 通过注册中心引入远程服务:通过注册中心抉择如何进行负载均衡调用远程服务。
服务引用流程:
- 检查配置构建map ,map 构建 URL ,通过URL上的协议利用自适应扩展机制调用对应的 protocol.refer 得到相应的 invoker ,此处
- 想注册中心注册自己,然后订阅注册中心相关信息,得到provider的 ip 等信息,再通过共享的netty客户端进行连接。
- 当有多个 URL 时,先遍历构建出 invoker 然后再由 StaticDirectory 封装一下,然后通过 cluster 进行合并,只暴露出一个 invoker 。
- 然后再构建代理,封装 invoker 返回服务引用,之后 Comsumer 调用的就是这个代理类。
调用方式:
调用之前你可能需要考虑这些事:
调用大致流程:
调用方式:
Dubbo 引入了Cluster、Directory、Router、LoadBalance、Invoker模块来保证Dubbo系统的稳健性,它们的关系如下图:
集群容错是在消费者端通过Cluster子类实现的,Cluster接口有10个实现类,每个Cluster实现类都会创建一个对应的ClusterInvoker对象。核心思想是让用户选择性调用这个Cluster中间层,屏蔽后面具体实现细节。
Cluster | Cluster Invoker | 作用 |
---|---|---|
FailoverCluster | FailoverClusterInvoker | 失败自动切换功能,默认 |
FailfastCluster | FailfastClusterInvoker | 一次调用,失败异常 |
FailsafeCluster | FailsafeClusterInvoker | 调用出错则日志记录 |
FailbackCluster | FailbackClusterInvoker | 失败返空,定时重试2次 |
ForkingCluster | ForkingClusterInvoker | 一个任务并发调用,一个OK则OK |
BroadcastCluster | BroadcastClusterInvoker | 逐个调用invoker,全可用才可用 |
AvailableCluster | AvailableClusterInvoker | 哪个能用就用那个 |
MergeableCluster | MergeableClusterInvoker | 按组合并返回结果 |
Dubbo中一般有4种负载均衡策略。
关于 服务目录Directory 你可以理解为是相同服务Invoker的集合,核心是RegistryDirectory类。具有三个功能。
- 从注册中心获得invoker列表。
- 监控着注册中心invoker的变化,invoker的上下线。
- 刷新invokers列表到服务目录。
服务路由其实就是路由规则,它规定了服务消费者可以调用哪些服务提供者。条件路由规则由两个条件组成,分别用于对服务消费者和提供者进行匹配。比如有这样一条规则:
host = 10.20.153.14 => host = 10.20.153.12
该条规则表示 IP 为 10.20.153.14 的服务消费者只可调用 IP 为 10.20.153.12 机器上的服务,不可调用其他机器上的服务。条件路由规则的格式如下:
[服务消费者匹配条件] => [服务提供者匹配条件]
如果服务消费者匹配条件为空,表示不对服务消费者进行限制。如果服务提供者匹配条件为空,表示对某些服务消费者禁用服务。
通读下Dubbo的大致实现方式后其实就可以依葫芦画瓢了,一个RPC框架大致需要下面这些东西:
PS :
感觉没啥特别好写的,因为人Dubbo官方文档啥都有,你说你英文看不懂,那中文总该看得懂了吧。
Dubbo面试题:https://sowhat.blog.csdn.net/article/details/71191035 Adaptive讲解:https://blog.csdn.net/weixin_33967071/article/details/92608993 Dubbo视频:https://b23.tv/KVk0xo Dubbo demo:https://mp.weixin.qq.com/s/FPbu8rFOHyTGROIV8XJeTA doExportUrlsFor1Protocol详解:https://www.cnblogs.com/hzhuxin/p/7993860.html
本文由哈喽比特于3年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/f8XfF0Y6w-IWYKeg8raTiw
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。