Dubbo进阶

一. 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 loader =
                ExtensionLoader.getExtensionLoader(HelloService.class);
        // 获取支持的拓展点\META-INF\dubbo
        Set 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配置开启异步:
 
     

 获取结果: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 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 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配置文件中添加:

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
 

如果是标注 则使用 @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

你可能感兴趣的:(java,开发语言)