文章目录

  • 1. Dubbo与RPC的关系
  • 2. Dubbo的基本使用
    • 2.1 Dubbo是什么?
    • 2.2 负载均衡
    • 2.3 服务超时
    • 2.4 集群容错
    • 2.5 服务降级
    • 2.6 本地存根
    • 2.7 参数回调
    • 2.8 异步调用
    • 2.9 泛化调用、泛化服务
  • 3. dubbo的REST协议
  • 4. dubbo的控制台
  • 5. dubbo的服务路由

1. Dubbo与RPC的关系

1.1 什么是RPC?

维基百科这样解释:

远程过程调用(英语:Remote Procedure Call,缩写为 RPC)是一个计算机通信协议。该协议允许运行于一台计算机的程序调用另一个地址空间(通常为一个开放网络的一台计算机)的子程序,而程序员就像调用本地程序一样,无需额外地为这个交互作用编程(无需关注细节)。RPC是一种服务器-客户端(Client/Server)模式,经典实现是一个通过发送请求-接受回应进行信息交互的系统。

如果涉及的软件采用面向对象编程,那么远程过程调用亦可称作远程调用或远程方法调用,所以,对于Java程序员而言,RPC就是远程方法调用

如何理解RPC是一个计算机通信协议呢?我们已经知道RPC是专注于远程方法调用,如果实现远程方法调用,基本的就是通过网络,通过传输数据来进行调用。如下图所示

        可以看到远程方法A 想要调用远程方法B,需要定义 数据类型 和 传输协议。而这些需要定义的东西作为一个协议存在于调用方和接收方,后续所有调用都遵守这个已制定的协议,这就是RPC通信协议。所以,我们其实可以看到RPC的自定义性是很高的,各个公司内部都可以实现自己的一套RPC框架,而Dubbo就是阿里所开源出来的一套RPC框架

RPC和 HTTP、TCP的关系就是:RPC是基于HTTP、TCP协议来传输数据的,对于所传输的数据,可以交由RPC的双方来协商定义,但基本都会包括:

  1. 调用的是哪个类或接口
  2. 调用的是哪个方法,方法名和方法参数类型(考虑方法重载)
  3. 调用方法的入参

1.2 Dubbo与RPC的关系

上面说到实现RPC框架需要定义 数据类型 和 传输协议。而Dubbo作为阿里开源出来的RPC框架,已经制定好了对应的 传输数据类型 和传输协议,使用Dubbo必须遵循Dubbo制定好的规则。

Dubbo的传输协议见下文!

3. 自定义RPC框架思路

服务端:

  1. 注册服务到zk或redis。以map的形式保存起来,key = 服务名,value = List<服务器地址>。客户端请求可以负载到value的某个地址上。
    注意:如果只把服务放在本地缓存中,那么其他的服务将调用失败,因为不同的服务属于不同的jvm,其他服务将无法感知另一个服务中的本地缓存。
  2. 把服务和服务的实现类注册到本地缓存。以map的形式保存起来,key = 服务名,value = 服务的实现类。目的是:当服务端接受到客户端请求,可以根据客户端传来的接口名,从本地缓存中拿到其实现类,然后通过反射调用客户端想要调用的方法
  3. 根据不同的协议启动不同的服务器。如果是Http协议则启动Tomcat,如果是Dubbo协议则启动Netty。

客户端:

  1. 指定传输的数据类型,包括接口(服务)名、方法名、参数类型、参数名,并封装成一个类Invocation。

  2. 当客户端调用某个接口时,采用jdk动态代理的方式,调用invoke代理方法,在代理方法中做增强逻辑。逻辑如下:
    2.1:填充数据类型Invocation
    2.2:从zk或redis中根据服务名拉取服务器地址,并负载均衡到某一个服务器地址下
    2.3:获取客户端协议(dubbo 或 http),并根据协议向服务端发送数据Invocation

  3. 客户端DispartchServlet拦截到客户端发过来的请求。通过JSON序列化二进制数据为Invocation 对象。根据对象中的接口名,从本地缓存中拿到对应的实现类,利用反射调用客户端客户端想要调用的方法,并输出。完成了远程服务调用!

2. Dubbo的基本使用

首先附上dubbo官方使用文档:https://dubbo.apache.org/zh/docs/v2.7/user/examples/loadbalance/

2.1 Dubbo是什么?

Apache Dubbo 是一款高性能、轻量级的开源 Java 服务框架,提供了六大核心能力:面向接口代理的高性能RPC调用,智能容错和负载均衡,服务自动注册和发现,高度可扩展能力,运行期流量调度,可视化的服务治理与运维。

其中有以下几个关键点

  • 注册与发现:Dubbo使用zookeeper做服务的注册中心,就是服务的提供者以临时节点的形式将服务Server信息注册保存到Zookeeper的dubbo目录下的provider的节点下,供消费者发现调用。

  • 负载均衡: Dubbo支持负载均衡策略,就是同一个Dubbo服务被多台服务器启用后,会在在Zookeeper提供者节点下显示多个相同接口名称节点。消费者在调用Dubbo负载均衡服务时,采用权重的算法策略选择具体某个服务器上的服务,权重策略以*2倍数设置。

  • 容错机制:Dubbo的提供者在Zookeeper上使用的是临时节点,一旦提供者所在服务挂掉,该节点的客服端连接将会关闭,故节点自动消失。所以消费者调用接口时将不会轮询到已经挂掉的接口上(延迟例外)。

  • Dubbo容器:Dubbo在java jvm中有自己的容器,和Spring IOC的bean一样,将服务对象保存到自己的容器中。

  • 监控中心:监控中心主要是用来服务监控和服务治理。服务治理包含:负载均衡策略、服务状态、容错、路由规则限定、服务降级等。具体可以下载Dubbo监控中心客户端查看与设置。

  • Dubbo的协议:点击链接获取更多协议的详细信息

①:dubbo协议: Dubbo默认协议是dubbo协议,采用单一长连接和 NIO 异步通讯,基于hessian作为序列化协议,适合于数据量小但并发高的服务调用,以及服务消费者机器数远大于服务提供者机器数的情况。

②:hessian协议: Hessian底层采用Http通讯(同步),走hessian序列化协议。适用于提供者数量比消费者数量还多,适用于文件的传输,一般较少用

③:http协议: 走json序列化,适用于浏览器查看,同时给应用程序和浏览器JS使用的服务。

④:rmi协议:走java二进制序列化,多个短连接,适合消费者和提供者数量差不多,适用于文件的传输,一般较少用

⑤:webservice协议:采用SOAP文本序列化,适用HTTP传输,常用于系统集成,跨语言调用

⑥:redis协议:基于 Redis实现的 RPC 协议。

⑦:rest协议:基于标准的Java REST API实现的REST调用支持

2.2 负载均衡

生产者在为某个接口暴露服务时,可以根据协议、ip、端口号、服务、group、version等六要素暴露多个接口实例,达到类似于集群的形式。如下所示:任意修改某个要素就算是这个接口已暴露的实例!在代码中可以通过修改@Service注解的值来暴露不同的服务实例@Service(interfaceName = "com.tuling.DemoService", version = "generic") 这样就会暴露http://ip:port/DemoService + generic服务,消费时要根据生产者暴露的规则来进行消费。

        如果在application.properties配置文件中,配置了多个协议,Dubbo会默认会根据配置暴露多个服务实例,如果做下面的配置,那么上面的DemoService接口在zookeeper上就会有两个服务实例,一个Http的,一个Dubbo的!

# 配置多协议# dubbo协议
dubbo.protocols.p1.id=dubbo1
dubbo.protocols.p1.name=dubbo
dubbo.protocols.p1.port=20881
dubbo.protocols.p1.host=0.0.0.0
# http协议
dubbo.protocols.p2.id=dubbo2
dubbo.protocols.p2.name=http
dubbo.protocols.p2.port=20882
dubbo.protocols.p2.host=0.0.0.0

那么面对多个服务实例,消费端调用时是如何进行选择的呢?Dubbo为我们提供了四种负载均衡策略,可以通过负载均衡策略来选择一个服务实例进行调用!默认的负载策略为 random 随机调用。四种策略如下:

  • Random 随机:按权重设置随机概率,可通过配置权重修改概率
  • RoundRobin 轮询:存在慢的提供者累积请求的问题,比如:第二台机器很慢,但没挂,当请求调到第二台时就卡在那,久而久之,所有请求都卡在调到第二台上。
  • LeastActive 最少活跃数:活跃数是指调用前后的计数差,服务调用越快,活跃数越小。提供者越慢,接收的请求就越少,因为越慢的提供者的调用前后计数差会越大,活跃数也会变大
  • ConsistentHash 一致性Hash:相同参数的请求总是发到同一提供者。

注意:比较难理解的是LeastActive 最少活跃数,理论上最少活跃数应该是在服务提供者端进行统计的,服务提供者统计有多少个请求正在执行中。但是Dubbo却选择在消费端进行统计最少活跃数,为什么能在消费端进行统计?逻辑如下:

  1. 消费者会缓存所调用服务的所有提供者,比如记为p1、p2、p3三个服务提供者,每个提供者内都有一个属性记为active,默认位0
  2. 消费者在调用次服务时,如果负载均衡策略是leastactive
  3. 消费者端会判断缓存的所有服务提供者的active,选择最小的,如果都相同,则随机选出某一个服务提供者后,假设位p2,Dubbo就会对p2.active+1
  4. 然后真正发出请求调用该服务
  5. 消费端收到响应结果后,对p2.active-1
  6. 这样就完成了对某个服务提供者当前活跃调用数进行了统计,并且并不影响服务调用的性能,下次调用会再次判断最小的active,这就解释了为什么服务提供者越慢,接收的请求就越少!因为它的active值大!

配置方式

  • Provider端配置:生产者通过在暴露服务的@Servic注解上进行配置:@Service(loadbalance = "roundrobin"),配置时需要注意负载均衡方式均为小写!
  • Consumer端配置:消费端通过@Reference(loadbalance = "leastactive ")

如果Provider和Consumer都配置,则以Consumer端配置的为准!

2.3 服务超时

在服务提供者(服务端)和服务消费者上都可以配置服务超时时间,这两者是不一样的。

@Service(version = "timeout", timeout = 4000)   //服务提供者端超时时间
@Reference(version = "timeout", timeout = 3000,retries = 1)   //服务消费者端超时时间

消费者调用一个服务,分为三步:

  1. 消费者发送请求(网络传输)
  2. 服务端执行服务
  3. 服务端返回响应(网络传输)

如果在服务端和消费端只在其中一方配置了timeout

那么没有歧义,表示消费端调用服务的超时时间,消费端如果超过时间还没有收到响应结果,则消费端会抛超时异常,但,服务端不会抛异常,服务端在执行服务后,会检查执行该服务的时间,如果超过timeout,则会打印一个超时日志。服务会正常的执行完。

如果在服务端和消费端各配了一个timeout

那情况就比较复杂了,假设

  • 服务执行为5s
  • 消费端timeout=3s
  • 服务端timeout=6s

那么消费端调用服务时,消费端会收到超时异常(因为消费端超时了),服务端一切正常(服务端没有超时)。如果配置服务端timeout=4s,那么由于服务执行为5s,所以服务端也会打印警告,标识服务端也超时了!

2.4 集群容错

一个服务提供多个实例(集群),集群容错是指:集群容错表示:服务消费者在调用某个服务时,这个服务有多个服务提供者,在经过负载均衡后选出其中一个服务提供者之后进行调用,但调用报错后,Dubbo所采取的后续处理策略。如图:如果服务实例1调用失败,则会尝试调用服务实例2或者3,默认重试2次。

集群容错可以在@Service 和 @Reference注解上进行配置:如果两者都配置,以消费端为主!

@Service( cluster = "failfast")  //服务端超集群容错
@Reference(cluster = "failfast") //消费端集群容错

Dubbo提供了六种集群容错方案:

  1. Failover Cluster:失败自动切换
    当出现失败,重试其它服务器。通常用于读操作,但重试会带来更长延迟。可通过 retries=“2” 来设置重试次数(不含第一次)。
  2. Failfast Cluster:快速失败
    只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增记录
  3. Failsafe Cluster:失败安全
    出现异常时,不抛异常,直接忽略。通常用于写入审计日志等操作
  4. Failback Cluster:失败自动恢复
    后台记录失败请求,定时重发。通常用于消息通知操作
  5. Forking Cluster:并行调用多个服务器
    只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可通过 forks=“2” 来设置最大并行数
  6. Broadcast Cluster:广播调用所有提供者
    逐个调用,任意一台报错则报错。通常用于通知所有提供者更新缓存或日志等本地资源信息

2.5 服务降级

服务降级表示:服务消费者在调用某个服务提供者时,如果该服务提供者报错了,所采取的措施。可以通过服务降级功能临时屏蔽某个出错的非关键服务,并定义降级后的返回策略。集群容错和服务降级的区别在于:

  1. 集群容错是整个集群范围内的容错
  2. 服务降级是单个服务提供者的自身容错

服务降级可以在消费端的 @Reference注解上使用mock来指定降级方案

  • mock=force:return+null 表示消费方对该服务的方法调用都直接返回 null 值,不发起远程调用。用来屏蔽不重要服务不可用时对调用方的影响。
  • mock=fail:return+null 表示消费方对该服务的方法调用在失败后,再返回 null 值,不抛异常。用来容忍不重要服务不稳定时对调用方的影响。
 //服务降级:如果调用失败返回123@Reference(version = "timeout", timeout = 1000, mock = "fail: return 123")

更多服务降级方案可参考本地伪装:https://dubbo.apache.org/zh/docs/v2.7/user/examples/local-mock/

本地伪装其实也是对Mock的应用,便于服务端在客户端执行容错逻辑

2.6 本地存根

消费端通过Dubbo远程调用服务端,其业务实现基本都在服务端。但有些时候想在消费端也执行部分逻辑,比如:做 ThreadLocal 缓存(这个用处最大),提前验证参数,调用失败后伪造容错数据等等,此时就需要在@Reference中带上 Stub,消费端生成服务的代理 Proxy 实例,会把 Proxy 通过构造函数传给 Stub,然后把 Stub 暴露给用户,Stub 可以决定要不要去调 Proxy。

//本地存根,开启stub
@Reference(stub = "true")
或者
@Reference(stub = "com.foo.DemoServiceStub") //指定stub对象

还需要自定义一个类实现DemoService接口,表示为DemoService做的本地存根,这个类是放在消费端的

public class DemoServiceStub implements DemoService {private final DemoService demoService;// 构造函数传入真正的远程代理对象public DemoServiceStub(DemoService demoService){this.demoService = demoService;}@Overridepublic String sayHello(String name) {// 此代码在客户端执行, 你可以在客户端做ThreadLocal本地缓存,或预先验证参数是否合法,等等try {return demoService.sayHello(name); // safe  null} catch (Exception e) {// 你可以容错,可以做任何AOP拦截事项return "容错数据";}}
}

注意:实现类中必须有一个传入远程 DemoService 实例的构造函数

使用上述存根代码执行后,如果调用失败,则会执行DemoServiceStub中的容错方案,控制台打印”容错数据“!

2.7 参数回调

参数回调是指:当消费端调用服务成功后,希望服务端能够回调一下消费端的逻辑

既然是服务端回调消费端的逻辑,那么这个逻辑一定是存在消费端的!以DemoService服务为例

消费端调用

    @Reference(version = "callback")private DemoService demoService;//调用服务demoService.sayHello("aaa", "d1", new DemoServiceListenerImpl())

上述代码new DemoServiceListenerImpl()中要包含着具体的回调逻辑

// 回调逻辑接口
public interface DemoServiceListener {void changed(String msg);
}
// 回调逻辑实现类
public class DemoServiceListenerImpl implements DemoServiceListener {@Overridepublic void changed(String msg) {System.out.println("被回调了:"+msg);}
}

服务端回调

// DemoService接口
public interface DemoService {// 回调方法default String sayHello(String name, String key, DemoServiceListener listener) {return null;};
}
// @Method注明了是sayHello()中索引为2的参数参与了回调,以及最大同时支持3个回调,上述代码只有一个,如果写4个就报错
@Service(version = "callback", methods = {@Method(name = "sayHello", arguments = {@Argument(index = 2, callback = true)})}, callbacks = 3)
public class CallBackDemoService implements DemoService {@Overridepublic String sayHello(String name, String key, DemoServiceListener callback) {callback.changed(); //代理对象直接回调消费端的changed方法return "";  // 正常访问}
}

在服务端回调时,需要注意:

  • sayHello()方法中的DemoServiceListener为代理对象,并不是消费端传过来的DemoServiceListenerImpl对象
  • 需要在@Service中使用@Method注明是哪个方法中哪个参数参与了回调,以及最大同时支持几个回调

结果:
在消费端打印的是changed的内容,但这个方法是在服务端被执行的

2.8 异步调用

上文所讲的内容都是依托于同步调用的,Dubbo也提供了异步调用方式,异步调用与同步调用的求别在于:

  • 服务端需要使用CompletableFuture.supplyAsync开启一个线程执行任务
  • 客户端需要使用CompletableFuture.whenComplete监听异步线程执行完毕

消费端代码示例:

    @Reference(version = "async")private DemoService demoService;public static void main(String[] args) throws IOException {ConfigurableApplicationContext context = SpringApplication.run(AsyncDubboConsumerDemo.class);DemoService demoService = context.getBean(DemoService.class);// 调用直接返回CompletableFutureCompletableFuture<String> future = demoService.sayHelloAsync("异步调用");  // 5//这个方法只有等异步线程执行结束才会调用future.whenComplete((v, t) -> {if (t != null) {t.printStackTrace();} else {System.out.println("Response: " + v);}});System.out.println("结束了");}

服务端代码示例

public interface DemoService {// 同步调用方法String sayHello(String name);// 异步调用方法default CompletableFuture<String> sayHelloAsync(String name) {return null;};}
@Service(version = "async")
public class AsyncDemoService implements DemoService {//同步调用@Overridepublic String sayHello(String name) {System.out.println("sayhello方法 " + name);return name;}// 主要关注这个异步调用@Overridepublic CompletableFuture<String> sayHelloAsync(String name) {System.out.println("执行了异步服务" + name);//相当于在异步线程里执行sayHello方法!return CompletableFuture.supplyAsync(() -> {return sayHello(name);});}
}

执行结果如下:
消费端:

服务端:

可以看到他们之间打印的顺序也是异步的体现!

2.9 泛化调用、泛化服务

泛化调用: 在Dubbo中,如果某个服务想要支持泛化调用,就可以将该服务的generic属性设置为true,那对于服务消费者来说,就可以不用依赖该服务的接口,直接利用GenericService接口来进行服务调用。泛化调用可以用来做服务测试。

@EnableAutoConfiguration
public class GenericDubboConsumerDemo {//调用DemoService服务,并不需要注入DemoService,也不需要引入依赖@Reference(id = "demoService", version = "default", interfaceName = "com.tuling.DemoService", generic = true)private GenericService genericService;public static void main(String[] args) throws IOException {ConfigurableApplicationContext context = SpringApplication.run(GenericDubboConsumerDemo.class);GenericService genericService = (GenericService) context.getBean("demoService");Object result = genericService.$invoke("sayHello", new String[]{"java.lang.String"}, new Object[]{"周瑜"});System.out.println(result);}
}

泛化服务: 可以不实现具体的某个接口,而是实现GenericService接口,并在@Service上标明接口名即可,在调用直接注入DemoService就可以使用!

@Service(interfaceName = "com.tuling.DemoService", version = "generic")
public class GenericDemoService implements GenericService {@Overridepublic Object $invoke(String s, String[] strings, Object[] objects) throws GenericException {System.out.println("执行了generic服务");return "执行的方法是" + s;}
}

3. dubbo的REST协议

dubbo支持多种远程调用方式,例如dubbo RPC(二进制序列化 + tcp协议)、http invoker(二进制序列化 + http协议)、hessian(二进制序列化 + http协议)、WebServices (文本序列化 + http协议)、REST(文本序列化 + http协议)等等的支持。

当我们用Dubbo提供了一个服务后,如果消费者没有使用Dubbo也想调用服务,那么这个时候就可以让我们的服务支持REST协议,这样消费者就可以通过REST形式调用我们的服务了。更多REST协议内容点击查看官网!

①:服务端配置文件修改协议为rest

dubbo.protocol.name=rest

②:服务端实现:使用@Path指定Rest风格的访问路径(注意:所有暴露的服务都必须加@Path)

@Service(version = "rest")
@Path("demo")
public class RestDemoService implements DemoService {@GET@Path("say")@Produces({ContentType.APPLICATION_JSON_UTF_8, ContentType.TEXT_XML_UTF_8})@Overridepublic String sayHello(@QueryParam("name") String name) {System.out.println("执行了rest服务" + name);URL url = RpcContext.getContext().getUrl();return String.format("%s: %s, Hello, %s", url.getProtocol(), url.getPort(), name);  // 正常访问}}

这样就可以通过浏览器访问这个服务了,其他消费端也可以直接通过HttpCliet等非dubbo的形式去调用服务!

4. dubbo的控制台

5. dubbo的服务路由

经过服务路由可以配置黑名单、白名单、读写分离、隔离不同机房网段等等,这点在官网有很详细的解释 点击查看官网详情!!!

dubbo提供的标签路由还可以用来发布版本,什么是蓝绿发布、灰度发布?

Dubbo的负载均衡、集群容错、服务降级等机制详解相关推荐

  1. 融云发送自定义消息_数据源管理 | Kafka集群环境搭建,消息存储机制详解

    一.Kafka集群环境 1.环境版本 版本:kafka2.11,zookeeper3.4 注意:这里zookeeper3.4也是基于集群模式部署. 2.解压重命名 tar -zxvf kafka_2. ...

  2. 数据源管理 | Kafka集群环境搭建,消息存储机制详解

    本文源码:GitHub·点这里 || GitEE·点这里 一.Kafka集群环境 1.环境版本 版本:kafka2.11,zookeeper3.4 注意:这里zookeeper3.4也是基于集群模式部 ...

  3. LVS负载均衡集群服务搭建详解(一)

    LVS概述 1.LVS:Linux Virtual Server 四层交换(路由):根据请求报文的目标IP和目标PORT将其转发至后端主机集群中的某台服务器(根据调度算法): 不能够实现应用层的负载均 ...

  4. LVS负载均衡集群服务搭建详解

    一.LVS概述  1.LVS:Linux Virtual Server 四层交换(路由):根据请求报文的目标IP和目标PORT将其转发至后端主机集群中的某台服务器(根据调度算法): 不能够实现应用层的 ...

  5. Dubbo 源码分析 - 集群容错之 LoadBalance

    1.简介 LoadBalance 中文意思为负载均衡,它的职责是将网络请求,或者其他形式的负载"均摊"到不同的机器上.避免集群中部分服务器压力过大,而另一些服务器比较空闲的情况.通 ...

  6. Dubbo 源码分析 - 集群容错之 Cluster

    1.简介 为了避免单点故障,现在的应用至少会部署在两台服务器上.对于一些负载比较高的服务,会部署更多台服务器.这样,同一环境下的服务提供者数量会大于1.对于服务消费者来说,同一环境下出现了多个服务提供 ...

  7. 使用lvs搭建负载均衡集群

    有时候,单台服务器的性能可能无法应付大规模的服务请求,且其一旦出现故障,就会造成用户在一段时间内无法访问.通过集群技术,可以在付出较低成本的情况下获得在性能.可靠性.灵活性方面的相对较高的收益. 集群 ...

  8. 集群(一)——LVS负载均衡集群

    集群(一)--LVS负载均衡集群 一.企业群集应用 1.群集的含义 2.问题出现 3.解决办法 4.根据群集所针对的目标差异进行分类 ①.负载均衡群集 ②.高可用群集 ③.高性能运算群集 二.负载均衡 ...

  9. 超详细!一文带你了解 LVS 负载均衡集群!

    作者 | JackTian 来源 | 杰哥的IT之旅(ID:Jake_Internet) 前言 如今,在各种互联网应用中,随着站点对硬件性能.响应速度.服务稳定性.数据可靠性等要求也越来越高,单台服务 ...

最新文章

  1. ORB-SLAM3在windows下的编译使用
  2. Commons BeanUtils包学习2
  3. dispatchTouchEvent onInterceptTouchEvent onTouchEvent区分
  4. 网站搜索功能怎么实现_电商网站上的搜索功能是如何实现的?
  5. miniui 查询_JQueryMiniUI按照时间进行查询的实现方法
  6. Solr部分更新MultiValued的Date日期字段时报错及解决方案:Invalid Date String:‘Mon Sep 14 01:48:38 CST 2015‘
  7. python3 datatime,python3处理时间和日期:datetime模块 – Python3教程
  8. PHP笔记(CSS篇)
  9. hudson--插件管理
  10. Stata进行logistic回归绘制列线图并做内部验证
  11. 冒烟测试 SMOKE Test
  12. 474922-22-0,DSPE-PEG2000-Mal,DSPE-PEG2k-Maleimide,
  13. 计算机网络题库——第3章数据链路层
  14. 世界之最VS谁是世界上最无聊的人
  15. 2017华为实习生招聘机考模拟题——0交换排序
  16. FPGA Altera Remote Update笔记
  17. 计算机毕业设计Java毕业论文答辩管理系统(源码+系统+mysql数据库+lw文档)
  18. Spring的下载及目录结构
  19. 为大数据定个小目标:从改变惯例开始
  20. (附源码)springboot中学成绩管理 毕业设计100854

热门文章

  1. ICE学习之C# Java之间通讯
  2. Java读写Excel之POI超入门(转)
  3. Elixir:可能成为下一代Web开发语言
  4. MVVM架构~knockoutjs系列之验证信息自定义输出~续
  5. 苏格拉底与失恋者的对话————最经典的失恋哲理 ...
  6. 服务器托管常见问题纠纷与解决方法
  7. Spring源码系列:依赖注入(二)createBean
  8. 《SolidWorks 2017中文版机械设计从入门到精通)》——1.6 参考基准轴
  9. Dom4J读写xml
  10. 交换机VLAN、 TRUNK 、VTP 配置