一. SPI
SPI
全称为
(Service Provider Interface)
,是
JDK
内置的一种服务提供发现机制。
作用:做服务的扩展
,使用
SPI
机制的优势是实现解耦,
使得第三方服务模块的装配控制逻辑与调用者的业务代码分离
简介:
破坏双亲委派模型之一, 在JNDI标准服务中,它存在的目的是对资源进行查找和集中管理,它需要(SPI)代码, 但是类加载器不可能认识它们?
即Java团队, 设计了一个线程上下文类加载器【通过Thread类中setContext-ClassLoader()设置】,默认应用程序类加载器。 JNDI服务使用这个线程上下文加载器, 实现了加载所需的SPI服务代码, 这试一种父类加载器加载子类加载器完成类加载的行为, 打通了双亲委派机制的层次结构来逆向加载。 JAVA中涉及SPI的加载有列如:JDBC,JNDI... SPI服务提供多与一个时, 代码就根据提供者类型来硬编码, 即提供了ServiceLoader类, 以META-INF/services中配置信息,辅以责任链模式。
Dubbo, 采用SPI机制实现了大量的扩展点,比如:过滤器,负载均衡,线程池....
Dubbo的SPI实现:
// 注意事项
// 1. 在需要使用SPI接口层,声明SPI注解
// 2. 依赖与上篇文章的Provider创建META-INF/dubbo目录
// 3. 配置文件名:com.ly.service.HelloService内容:hello=com.ly.service.impl.HelloServiceImpl
// 实现
public class SpiDubboMain {
public static void main(String[] args) {
// 获取扩展加载器
ExtensionLoader<HelloService> loader =
ExtensionLoader.getExtensionLoader(HelloService.class);
// 获取支持的拓展点\META-INF\dubbo
Set<String> extensions = loader.getSupportedExtensions();
for(String ex:extensions) {
String spi = loader.getExtension(ex).sayHellow("spi");
System.out.println(spi);
}
}
}
二. 利用SPI实现过滤器, 【时间检测】
// 注意事项
// 1. 可以自己指定在Consumer 还是 Provider
// 2. 新建工程 spi-filter
// 3. Filter接口全限定名: org.apache.dubbo.rpc.Filter
// 4. 创建META-INF/dubbo目录
// 5. 文件名:org.apache.dubbo.rpc.Filter
// 6. 内容: timefilter=com.leiyu.filter.DubboInvokeFilter
// 7. 指定了Consumer 即在Consumer工程加入spi-filter依赖
@Activate(group = {CommonConstants.CONSUMER})
public class DubboInvokeFilter implements Filter{
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
long startTime = 0 ;
try {
startTime = System.currentTimeMillis();
return invoker.invoke(invocation) ;
} finally {
System.out.println("invoke time : " +( System.currentTimeMillis() - startTime) + " 毫秒");
}
}
}
三. 配置负载均衡
// 注意事项
// 1. 可以配置在Consumer 也可以配置在Provider, 采用dubbo自带的【看官网文档】
//在服务消费者一方配置负载均衡策略
@Reference(check = false,loadbalance = "random")
//在服务提供者一方配置负载均衡
@Service(loadbalance = "random")
public class HelloServiceImpl implements HelloService {
public String sayHello(String name) {
return "hello " + name;
}
}
// 2. 可以自定义负载均衡, 需要实现LoadBalance接口
// 配置负载均衡器 利用SPI机制, 创建META-INF/dubbo目录
// 文件名:org.apache.dubbo.rpc.cluster.LoadBalance
// 内容: onlyFirst=包名.负载均衡器
四. 异步调用
Dubbo
不只提供了堵塞式的的同步调用,同时提供了异步调用的方式。
利用
Future
模式来异步等
待和获取结果即可。这种方式可以大大的提升消费者端的利用率。 目这种方式可以通过
XML
的方式进
行引入。
XML配置开启异步:
<dubbo:reference id="helloService"
interface="com.ly.service.HelloService">
<dubbo:method name="sayHello" async="true" />
</dubbo:reference>
获取结果:RpcContext.getContext().getFuture() 来进行获取Future对象来进行后续的结果等待操作
五. 线程池
dubbo中与java对应的线程池:
fix:
表示创建固定大小的线程池。也是
Dubbo
默认的使用方式,默认创建的执行线程数为
200
,并
且是没有任何等待队列的。所以再极端的情况下可能会存在问题,比如某个操作大量执行时,可能
存在堵塞的情况。后面也会讲相关的处理办法。
cache:
创建非固定大小的线程池,当线程不足时,会自动创建新的线程。但是使用这种的时候需
要注意,如果突然有高
TPS
的请求过来,方法没有及时完成,则会造成大量的线程创建,对系统的
CPU
和负载都是压力,执行越多反而会拖慢整个系统。
自定义线程池:实现dubbo固定线程池使用情况的监测
1. 创建dubbo-spi-threadpool工程 包名: com.ly.threadpool.watchingThreadPool
public class WachingThreadPool extends FixedThreadPool implements Runnable {
private final static Logger LOGGER = LoggerFactory.getLogger(WachingThreadPool.class) ;
// 定义线程池使用的阈值
private final static double ALANG_PERCENT = 0.90 ;
// 存储真正做线程池的对象
private final Map<URL, ThreadPoolExecutor> THREAD_POOLS = new ConcurrentHashMap<>() ;
// 每隔三秒报告线程池使用情况
public WachingThreadPool() {
Executors.newSingleThreadScheduledExecutor()
.scheduleWithFixedDelay(this,1, 3, TimeUnit.SECONDS) ;
}
// 通过父类方式创建线程池
@Override
public Executor getExecutor(URL url) {
final Executor executor = super.getExecutor(url);
if(executor instanceof ThreadPoolExecutor) {
THREAD_POOLS.put(url, (ThreadPoolExecutor) executor) ;
}
return executor ;
}
@Override
public void run() {
// 遍历线程池
for(Map.Entry<URL, ThreadPoolExecutor> entry:THREAD_POOLS.entrySet()) {
final URL url = entry.getKey();
final ThreadPoolExecutor executor = entry.getValue();
// 计算相关指标
// 拿到活动线程数
final int activeCount = executor.getActiveCount() ;
// 拿到总共线程数
final int size = executor.getCorePoolSize();
double useredPrecent = activeCount / (size * 1.0) ;
LOGGER.info("线程池执行状态: [{}/{}: {}%]", activeCount, size, useredPrecent*100);
if(useredPrecent > ALANG_PERCENT) {
LOGGER.error("超出警戒线! host: {} 当前使用率是: {}, URL: {}",
url.getIp(), useredPrecent, url);
}
}
}
}
2. 创建META-INF/dubbo目录
3. 创建文件: org.apache.dubbo.common.threadpool.ThreadPool 内容:
watching=com.ly.threadpool.WatchingThreadPool
4. 在Provider的xml配置文件中添加:
<dubbo:provider threadpool="watching" ></dubbo:provider>
5. 在提供方加入dubbo-spi-threadpool工程依赖
6. 修改consumer施压:
public static void main(String[] args) throws IOException, InterruptedException {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(ConsumerConfiguration.class);
context.start();
// 获取消费者组件
ConsumerComponent service = context.getBean(ConsumerComponent.class);
while(true) {
for (int i = 0; i < 1000; i++) {
Thread.sleep(6);
new Thread(new Runnable() {
@Override
public void run() {
String msg = service.say("watching", 0) ;
System.out.println(msg);
}
}).start();
}
}
}
六. 路由规则
1. 根据zookeeper获取注册中心配置
2. 可以实现动态配置
public class DubboRouterMain {
public static void main(String[] args) {
RegistryFactory
registryFactory = ExtensionLoader.getExtensionLoader(RegistryFactory.class).getAdaptiveExtension();
Registry registry = registryFactory.getRegistry(URL.valueOf("zookeeper://127.0.0.1:2181"));
registry.register(URL.valueOf("condition://0.0.0.0/com.ly.service.HelloService?category=routers&force=true&dynamic=true&rule="
+ URL.encode("=> host != 192.168.23.1")));
}
}
route:// 表示示路由规则的类型,支持条件路由规则和脚本路由规则,可拓展
0.0.0.0
表示对所有
IP
地址生效,如果只想对某个
IP
的生效,请填入具体
IP
com.ly.service.HelloService
表示只对指定服务生效
category=routers
表示该数据为动态配置类型
dynamic
:
是否为持久数据,当指定服务重启时是否继续生效
runtime
:
是否在设置规则时自动缓存规则,如果设置为
true
则会影响部分性能
rule
:
是整个路由最关键的配置,用于配置路由规则
... => ...
在这里
=>
前面的就是表示消费者方的匹配规则,可以不填
(
代表全部
)
。
=>
后方则必
须填写,表示当请求过来时,如果选择提供者的配置
七. 服务降级
服务降级,当服务器压力剧增的情况下,根据当前业务情况及流量对一些服务有策略的降低服务级别, 以释放服务器资源,保证核心任务的正常运行。
防止分布式系统雪崩:【蝴蝶效应】 指当一个请求超时,一直等待响应, 而高并发下,多个请求等待响应,导致系统资源耗尽宕机,而宕机后,导致其他分布式服务调用该宕机服务,也会出现资源耗尽而宕机。最后造成 整个分布式系统瘫痪, 即雪崩。
第一种 在 dubbo 管理控制台配置服务降级
1. mock=force:return+null
2.
mock=fail:return+null
第二种 指定返回简单值或者null
<dubbo:reference id="xxService" check="false"
interface="com.xx.XxService" timeout="3000" mock="return null" />
<dubbo:reference id="xxService2" check="false"
interface="com.xx.XxService2" timeout="3000" mock="return 1234" />
如果是标注 则使用
@Reference(mock="return null") @Reference(mock="return
简单值
")
也支持
@Reference(mock="force:return null")
第三种 根据注册中心动态配置
registry.register(URL.valueOf("override://0.0.0.0/com.foo.BarService?
category=configurators&dynamic=false
&application=foo&mock=force:return+null"));
第四种 整合整合 hystrix