先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7
深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新软件测试全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上软件测试知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
如果你需要这些资料,可以添加V获取:vip1024b (备注软件测试)
正文
if (url != null &&url.length() > 0) { //指定URL的情况下,不做本地引用
isJvmRefer = false;
} else if(InjvmProtocol.getInjvmProtocol().isInjvmRefer(tmpUrl)) {
//默认情况下如果本地有服务暴露,则引用本地服务.
isJvmRefer = true;
} else {
isJvmRefer = false;
}
} else {
isJvmRefer =isInjvm().booleanValue();
}
if (isJvmRefer) {
URL url = new URL(Constants.LOCAL_PROTOCOL,NetUtils.LOCALHOST, 0, interfaceClass.getName()).addParameters(map);
invoker =refprotocol.refer(interfaceClass, url);
if (logger.isInfoEnabled()) {
logger.info("Using injvmservice " + interfaceClass.getName());
}
} else {
if (url != null &&url.length() > 0) { // 用户指定URL,指定的URL可能是对点对直连地址,也可能是注册中心URL
String[] us =Constants.SEMICOLON_SPLIT_PATTERN.split(url);
if (us != null &&us.length > 0) {
for (String u : us) {
URL url =URL.valueOf(u);
if (url.getPath() ==null || url.getPath().length() == 0) {
url =url.setPath(interfaceName);
}
if(Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
urls.add(url.addParameterAndEncoded(Constants.REFER_KEY,StringUtils.toQueryString(map)));
} else {
urls.add(ClusterUtils.mergeUrl(url, map));
}
}
}
} else { // 通过注册中心配置拼装URL
List us =loadRegistries(false);
if (us != null &&us.size() > 0) {
for (URL u : us) {
URL monitorUrl =loadMonitor(u);
if (monitorUrl != null){
map.put(Constants.MONITOR_KEY, URL.encode(monitorUrl.toFullString()));
}
urls.add(u.addParameterAndEncoded(Constants.REFER_KEY,StringUtils.toQueryString(map)));
}
}
if (urls == null || urls.size()== 0) {
throw newIllegalStateException(“No such any registry to reference " +interfaceName + " on the consumer " + NetUtils.getLocalHost() +” use dubbo version " + Version.getVersion() + “, please config<dubbo:registry address=”…" /> to your springconfig.");
}
}
if (urls.size() == 1) {
invoker =refprotocol.refer(interfaceClass, urls.get(0));
} else {
List<Invoker<?>>invokers = new ArrayList
URL registryURL =null;
for (URL url : urls) {
invokers.add(refprotocol.refer(interfaceClass, url));
if(Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
registryURL = url; // 用了最后一个registryurl
}
}
if (registryURL != null) { // 有 注册中心协议的URL
// 对有注册中心的Cluster只用 AvailableCluster
URL u = registryURL.addParameter(Constants.CLUSTER_KEY,AvailableCluster.NAME);
invoker = cluster.join(new StaticDirectory(u, invokers));
} else { // 不是 注册中心的URL
invoker = cluster.join(new StaticDirectory(invokers));
}
}
}
Boolean c = check;
if (c == null && consumer !=null) {
c = consumer.isCheck();
}
if (c == null) {
c = true; // default true
}
if (c && !invoker.isAvailable()){
throw new IllegalStateException("Failed to check the status of the service " +interfaceName + “. No provider available for the service " + (group== null ? “” : group + “/”) + interfaceName + (version ==null ? “” : “:” + version) + " from the url " +invoker.getUrl() + " to the consumer " + NetUtils.getLocalHost() +” use dubbo version " + Version.getVersion());
}
if (logger.isInfoEnabled()) {
logger.info("Refer dubboservice " + interfaceClass.getName() + " from url " +invoker.getUrl());
}
// 创建服务代理
return (T)proxyFactory.getProxy(invoker);
}
一开始调用isInjvm()方法判断目标接口是否在本地就有,如果本地就有,直接调用本地的接口。
如果本地没有,就在配置中找有没有用户指定的url,如果指定了就使用用户指定的url提供的接口。
如果没有指定url,则从注册中心中获得目标url列表。
如果urls.size()==1,则直接用这个url获得invoker,这个invoker就是最后用来创建动态代理用的。
当urls.size()>1时,有registryURL属性,如果配置了注册中心协议Protocol,则只用AvailableCluster得到invoker。
cluster.join()方法是用来获得invoker的,cluster属性的定义:
private transient volatile Invoker<?> invoker;
Invoker是个接口,根据配置的不同会使用不同的实现类,比如上面的AvailableCluster,他的join()方法是这样的:
public Invoker join(Directory directory)throws RpcException {
return new AbstractClusterInvoker(directory) {
public Result doInvoke(Invocationinvocation, List<Invoker> invokers, LoadBalance loadbalance)throws RpcException {
for (Invoker invoker :invokers) {
if (invoker.isAvailable()){
returninvoker.invoke(invocation);
}
}
throw new RpcException("No provider available in " + invokers);
}
};
}
实际上这个join()方法返回了一个AbstractClusterInvoker对象,并重写了他的doInvoke()方法,这个方法在动态代理实际被调用时会用到。
现在回到createProxy()方法,最后用得到的invoker通过proxyFactory创建动态代理,至此动态代理就创建完了。
消费端动态代理部分
当我们在代码中配置好的SequenceService进行远程调用时,实际上调用的是对应Invoker的invoke()方法,invoker是一个接口,对于这个接口的实现大概是这样的:
Invoker
----AbstractInvoker
----AbstractClusterInvoker
----AbstractProxyInvoker
----DelegateInvoker
----MockClusterInvoker
----MergeableClusterInvoker
----InvokerListenerAdapter
----InvokerListenerAdapter
……
还有很多
AbstractInvoker就是用来远程通信的Invoker
AbstractClusterInvoker是provider是集群时使用Invoker,比AbstractInvoker多了负载均衡,选择provider的过程,最终确定了调用的provider之后还是会调用AbstractInvoker中的invoke()方法。
我们先看AbstractClusterInvoker的invoke()方法:
public Result invoke(final Invocation invocation) throws RpcException {
checkWhetherDestroyed();
LoadBalanceloadbalance;
List<Invoker> invokers = list(invocation);
if (invokers !=null && invokers.size() > 0) {
loadbalance =ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(invokers.get(0).getUrl()
.getMethodParameter(invocation.getMethodName(),Constants.LOADBALANCE_KEY, Constants.DEFAULT_LOADBALANCE));
} else {
loadbalance=ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(Constants.DEFAULT_LOADBALANCE);
}
RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation);
return doInvoke(invocation, invokers, loadbalance);
}
首先判断当前consumer是否已经destory了,然后用list(invocation)方法获得所有的provider信息,获得负载均衡算法LoadBalance,设置同步属性,最后调用doInvoke方法。
AbstractClusterInvoker的doInvoke()方法是个抽象方法:
protected abstract Result doInvoke(Invocation invocation,List<Invoker> invokers,
LoadBalance loadbalance) throws RpcException;
他的子类有很多,比如:
AvailableClusterInvoker 选择第一个可用的provider。
FailBackClusterInvoker失败自动恢复,后台记录失败请求,定时重发,通常用于消息通知操作。
FailfastClusterInvoker快速失败,只发起一次调用,失败立即报错,通常用于非幂等性的写操作。
FailoverClusterInvoker失败转移,当出现失败,重试其它服务器,通常用于读操作,但重试会带来更长延迟。
FailsafeClusterInvoker失败安全,出现异常时,直接忽略,通常用于写入审计日志等操作。
ForkingClusterInvoker并行调用,只要一个成功即返回,通常用于实时性要求较高的操作,但需要浪费更多服务资源。
具体使用哪个得看配置
我们以之前提到的AvailableClusterInvoker为例,看一下doInvoke()方法:
public Result doInvoke(Invocation invocation, List<Invoker> invokers,LoadBalance loadbalance) throws RpcException {
for(Invoker invoker : invokers) {
if(invoker.isAvailable()) {
return invoker.invoke(invocation);
}
}
throw new RpcException("No provider available in " + invokers);
}
就是判断invoker是否可用,可用就直接调用invoker的invoke()方法,实际上调用的还是AbstractInvoker的invoke()方法,如果不是集群就直接调这个方法了,该方法代码如下:
public Result invoke(Invocation inv) throws RpcException {
if(destroyed.get()) {
throw new RpcException(“Rpc invoker for service " + this + " on consumer” + NetUtils.getLocalHost()
+" use dubbo version " + Version.getVersion()
+" is DESTROYED, can not be invoked any more!");
}
RpcInvocation invocation = (RpcInvocation) inv;
invocation.setInvoker(this);
if (attachment!= null && attachment.size() > 0) {
invocation.addAttachmentsIfAbsent(attachment);
}
Map<String,String> context = RpcContext.getContext().getAttachments();
if (context !=null) {
invocation.addAttachmentsIfAbsent(context);
}
if(getUrl().getMethodParameter(invocation.getMethodName(), Constants.ASYNC_KEY,false)) {
invocation.setAttachment(Constants.ASYNC_KEY, Boolean.TRUE.toString());
}
RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation);
try {
return doInvoke(invocation);
} catch(InvocationTargetException e) { // biz exception
Throwablete = e.getTargetException();
if (te ==null) {
return new RpcResult(e);
} else {
if (te instanceof RpcException) {
((RpcException) te).setCode(RpcException.BIZ_EXCEPTION);
}
return new RpcResult(te);
}
} catch(RpcException e) {
if(e.isBiz()) {
return new RpcResult(e);
} else {
throw e;
}
} catch(Throwable e) {
return new RpcResult(e);
}
}
还是先判断consumer是否是destory的,其实destroyed是destory的过去分词,不是人家拼错了。
然后经历一堆和AbstractClusterInvoker的invoke一样的参数设置,最后调用doInvoke()方法,而且这个方法在这个Invoker里面也是抽象的。
AbstractInvoker的doInvoke()方法在DubboInvoker类里面有具体实现,这个DubboInvoker是AbstractInvoker的子类,doInvoke()方法如下:
@Override
protected Result doInvoke(final Invocation invocation) throws Throwable {
RpcInvocation inv = (RpcInvocation) invocation;
final String methodName = RpcUtils.getMethodName(invocation);
inv.setAttachment(Constants.PATH_KEY, getUrl().getPath());
inv.setAttachment(Constants.VERSION_KEY, version);
ExchangeClient currentClient;
if(clients.length == 1) {
currentClient = clients[0];
} else {
currentClient = clients[index.getAndIncrement() % clients.length];
}
try {
boolean isAsync = RpcUtils.isAsync(getUrl(), invocation);
boolean isOneway = RpcUtils.isOneway(getUrl(), invocation);
int timeout= getUrl().getMethodParameter(methodName, Constants.TIMEOUT_KEY,Constants.DEFAULT_TIMEOUT);
if(isOneway) {
boolean isSent = getUrl().getMethodParameter(methodName, Constants.SENT_KEY, false);
currentClient.send(inv, isSent);
RpcContext.getContext().setFuture(null);
return new RpcResult();
} else if(isAsync) {
ResponseFuture future = currentClient.request(inv, timeout);
RpcContext.getContext().setFuture(newFutureAdapter(future));
return new RpcResult();
} else {
RpcContext.getContext().setFuture(null);
return(Result) currentClient.request(inv, timeout).get();
}
} catch(TimeoutException e) {
throw new RpcException(RpcException.TIMEOUT_EXCEPTION, "Invoke remote method timeout. method: " + invocation.getMethodName() + ", provider: "+ getUrl() + ", cause: " + e.getMessage(), e);
} catch(RemotingException e) {
throw new RpcException(RpcException.NETWORK_EXCEPTION, "Failed to invoke remote method: " + invocation.getMethodName() + ", provider: " +getUrl() + ", cause: " + e.getMessage(), e);
}
}
在经过一堆的设置参数(地址、版本)之后,dubbo获得了两个参数,isAsync和isOneway,isAsync为true时代表异步调用,isOneway为true时代表没有返回值。
当isOneway为true时,调用send()方法然后返回一个空的RpcResult,ExchangeClient的send()方法就是用来把消息发给provider的,send()方法的返回值类型是void。
而当isAsync为true时,设置了一个ResponseFuture之后返回一个空的RpcResult
最后的else就是普通的同步调用,不需要设置Future,一直等到provider端返回处理结果,currentClient.request方法负责把请求发出。
ExchangeClient是个接口,request()方法的实现类在HeaderExchangeClient类中,HeaderExchangeClient的request()方法只有一行,直接调用了HeaderExchangeChannel的request方法,这个request方法如下:
public ResponseFuture request(Object request, int timeout) throws RemotingException {
if (closed) {
throw new RemotingException(this.getLocalAddress(), null, “Failed to send request” + request + “, cause: The channel " + this + " isclosed!”);
}
// create request.
Request req =new Request();
req.setVersion(“2.0.0”);
req.setTwoWay(true);
req.setData(request);
DefaultFuture future = new DefaultFuture(channel, req, timeout);
try {
channel.send(req);
} catch(RemotingException e) {
future.cancel();
throw e;
}
return future;
}
其中的channel就是dubbo集成的Netty的Channel类,负责服务器间消息传输,这个类在dubbo中和netty中都能找到,这里调用了他的send()方法。
Channel的send()方法来自EndPoint接口
Channel接口实现了EndPoint接口
AbstractChannel抽象类实现了Channel接口,然而他的send()方法的功能只是判断当前channel是否已关闭
public void send(Object message, boolean sent) throws RemotingException {
if (isClosed()){
throw new RemotingException(this, "Failed to send message "
+(message == null ? “” : message.getClass().getName()) + “:”+ message
+", cause: Channel closed. channel: " + getLocalAddress() + "-> " + getRemoteAddress());
}
}
最后NettyChannel类继承了AbstractChannel类,重写了父类的send()方法,代码如下:
public void send(Object message, boolean sent) throws RemotingException {
super.send(message, sent);
boolean success= true;
int timeout =0;
try {
ChannelFuture future = channel.write(message);
if (sent) {
timeout= getUrl().getPositiveParameter(Constants.TIMEOUT_KEY,Constants.DEFAULT_TIMEOUT);
success= future.await(timeout);
}
Throwable cause = future.getCause();
if (cause!= null) {
throw cause;
}
} catch(Throwable e) {
throw new RemotingException(this, "Failed to send message " + message + "to " + getRemoteAddress() + ", cause: " + e.getMessage(), e);
}
if (!success) {
throw new RemotingException(this, "Failed to send message " + message + "to " + getRemoteAddress()
+“in timeout(” + timeout + “ms) limit”);
}
}
一开始调用了父类的send()方法,判断是否关闭
channel.write()方法就是Channel负责发送消息的方法,至此,消息只要再通过一些事件处理器(主要是编码),就可以发到provider端了。
消费端事件处理器部分
NettyClient在初始化时添加了三个事件处理器用来处理发送消息和接收消息的事件,分别是NettyCodecAdapter.DeCoder,NettyCodecAdapter.Encoder,NettyHandler,代码在NettyClient类的doOpen()方法里:
@Override
protected void doOpen() throws Throwable {
NettyHelper.setNettyLoggerFactory();
bootstrap = new ClientBootstrap(channelFactory);
// config
// @see org.jboss.netty.channel.socket.SocketChannelConfig
bootstrap.setOption(“keepAlive”,true);
bootstrap.setOption(“tcpNoDelay”, true);
bootstrap.setOption(“connectTimeoutMillis”, getTimeout());
final NettyHandler nettyHandler = new NettyHandler(getUrl(), this);
bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
public ChannelPipeline getPipeline() {
NettyCodecAdapter adapter = new NettyCodecAdapter(getCodec(), getUrl(),NettyClient.this);
ChannelPipeline pipeline = Channels.pipeline();
pipeline.addLast(“decoder”, adapter.getDecoder());
pipeline.addLast(“encoder”, adapter.getEncoder());
pipeline.addLast(“handler”, nettyHandler);
returnpipeline;
}
});
}
几种事件处理器的在添加时顺序是DeCoder,Encoder,NettyHandler。
当线程给对方发送信息时,叫做下行事件,下行事件会先经过NettyHandler再经过Encoder。
当线程接收对方发来的信息时,叫做上行事件,上行事件会先经过DeCoder再经过NettyHandler。
在调用Channel.write()时,会调用事件处理器中的NettyHandler和Encoder,反过来当provider给consumer返回信息时调用的是DeCoder和NettyHandler。
第一步,NettyHandler的writeRequested方法会首先被调用:
@Override
public void writeRequested(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
super.writeRequested(ctx, e);
NettyChannel channel = NettyChannel.getOrAddChannel(ctx.getChannel(), url, handler);
try {
handler.sent(channel, e.getMessage());
} finally {
NettyChannel.removeChannelIfDisconnected(ctx.getChannel());
}
}
这里是进行一些dubbo的回调功能。
第二步是调用NettyCodecAdapter.Encoder,encoder的定义和实现类就在NettyCodecAdapter类中:
private final ChannelHandler encoder = new InternalEncoder();
private final Codec2codec;
@Sharable
private class InternalEncoder extends OneToOneEncoder {
@Override
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注软件测试)
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
Event e) throws Exception {
super.writeRequested(ctx, e);
NettyChannel channel = NettyChannel.getOrAddChannel(ctx.getChannel(), url, handler);
try {
handler.sent(channel, e.getMessage());
} finally {
NettyChannel.removeChannelIfDisconnected(ctx.getChannel());
}
}
这里是进行一些dubbo的回调功能。
第二步是调用NettyCodecAdapter.Encoder,encoder的定义和实现类就在NettyCodecAdapter类中:
private final ChannelHandler encoder = new InternalEncoder();
private final Codec2codec;
@Sharable
private class InternalEncoder extends OneToOneEncoder {
@Override
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注软件测试)
[外链图片转存中…(img-11JQGS5j-1713299332793)]
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!