惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

S
Schneier on Security
The Register - Security
The Register - Security
月光博客
月光博客
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
The GitHub Blog
The GitHub Blog
博客园 - 司徒正美
罗磊的独立博客
U
Unit 42
S
SegmentFault 最新的问题
Y
Y Combinator Blog
博客园_首页
Hugging Face - Blog
Hugging Face - Blog
J
Java Code Geeks
Schneier on Security
Schneier on Security
Know Your Adversary
Know Your Adversary
C
Check Point Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Simon Willison's Weblog
Simon Willison's Weblog
V
Vulnerabilities – Threatpost
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
阮一峰的网络日志
阮一峰的网络日志
The Hacker News
The Hacker News
博客园 - 叶小钗
C
Cybersecurity and Infrastructure Security Agency CISA
Spread Privacy
Spread Privacy
L
LINUX DO - 热门话题
T
The Exploit Database - CXSecurity.com
P
Palo Alto Networks Blog
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Latest news
Latest news
L
Lohrmann on Cybersecurity
A
About on SuperTechFans
L
LangChain Blog
Stack Overflow Blog
Stack Overflow Blog
S
Securelist
A
Arctic Wolf
D
Darknet – Hacking Tools, Hacker News & Cyber Security
T
Threatpost
Scott Helme
Scott Helme
博客园 - 聂微东
博客园 - 【当耐特】
T
Tenable Blog
I
Intezer
D
DataBreaches.Net
B
Blog RSS Feed
Security Latest
Security Latest
C
Cisco Blogs
T
Tor Project blog
N
Netflix TechBlog - Medium

博客园 - min.jiang

业务网关-分流插件 业务网关-操作手册 业务网关-配置 dubbo+zipkin调用链监控(二) Kafka Manager Spring Cache扩展:注解失效时间+主动刷新缓存(二) 从头开始搭建一个Spring boot+ActiveMQ高可用分布式环境 简易RPC框架-熔断降级机制 转:一篇讲线上优化查 CPU的脚本 简易RPC框架-上下文 简易RPC框架-客户端限流配置 简易RPC框架-过滤器机制 简易RPC框架-心跳与重连机制 简易RPC框架-私有协议栈 自动化测试:behave dubbo源码分析(二):超时原理以及应用场景 简单RPC框架-业务线程池 简单RPC框架-基于Consul的服务注册与发现 简易RPC框架-学习使用
简易RPC框架-SPI
min.jiang · 2018-05-20 · via 博客园 - min.jiang

案例

我们所熟悉的jbdc是一种用于执行SQL语句的Java API,可以为多种关系数据库提供统一访问,提供了一种基准,据此可以构建更高级的工具和接口。

如上图所示,任意的一个数据库厂商只要去实现jdbc的接口,就可以轻松的对接jbdc从而为应用开发人员所服务。

SPI

上面的jdbc的设计理念叫SPI,它的全名是Service Provider Interface。它的理念是对某类功能进行抽象,确保应用程序依赖抽象而不是具体的某种实现,通过配置服务实现者的方式来达到面向接口编程以及扩展的目的。比如我们项目中需要用到日志组件,有的项目喜欢logback,有的喜欢log4j,有的喜欢common-log等等,如果在项目中直接依赖这些日志接口,那么后续如果需要对日志组件重新选型,对现有项目的影响会非常大,所有后来就有了slfj,它抽象了日志接口但不包含任何的实现,具体实现全部依赖于不同的厂商。

传统的java spi一般做法是在resources/META-INF/services/目录下面创建一个以服务接口命名的文件,该文件内容就是实现该服务接口的具体实现类的完全限定名。当程序加载的时候,就能通过resources/META-INF/services/里的配置文件找到具体的实现类名,并加载实例化。 通过这个机制就能找到服务接口的实现类,而不需要再代码里写死。

java.util.ServiceLoader这个就是java spi中用来加载服务实现类的工具,本文不对它的具体用法做过多介绍。

主题:限流策略如何扩展

本文要讨论的问题是,rpc框架中的限流过滤器扩展问题(可参考之前的文章 :简易RPC框架-客户端限流配置),之前介绍的限流实现是采用了guava提供的RateLimit,当时客户端限流的实现是在框架中写好的不允许修改,不同项目如果需要不同的限流策略那么就需要针对原有方案进行扩展,如果扩展呢?

Spring-boot 中的SPI

我在spring-boot项目中按传统的spi方式配置后,发现ServiceLoader加载指定接口找不到具体的实现类,后来发现spring-boot有自己的spi实现。它是在resources/META-INF/spring.factories中配置相关的接口,而且这个类的配置方式与传统的spi也有所不同,它采用了key=value方式,这点有点类似dubbo的spi机制。文件目录如下:

下面给出我调整之后的方案:

客户端限流接口

定义一个限流的接口,因为限流会有些参数控制,所以就增加RpcInvocation来协助完成。

public interface AccessLimitService {

    void acquire(RpcInvocation invocation);
}

客户端限流接口实现

本文只是为了简单实现,所以直接将原有写在rpc框架中的限流方式抽取出来,并没有重新采用一种新的限流策略。

public class AccessLimitServiceImpl implements AccessLimitService {

    @Override
    public void acquire(RpcInvocation invocation) {
        AccessLimitManager.acquire(invocation);
    }


    static class AccessLimitManager{

        private final static Object lock=new Object();

        private final static Map<String,RateLimiter> rateLimiterMap= Maps.newHashMap();

        public static void acquire(RpcInvocation invocation){
            if(!rateLimiterMap.containsKey(invocation.getClassName())) {
                synchronized (lock) {
                    if(!rateLimiterMap.containsKey(invocation.getClassName())) {
                        final RateLimiter rateLimiter = RateLimiter.create(invocation.getMaxExecutesCount());
                        rateLimiterMap.put(invocation.getClassName(), rateLimiter);
                    }
                }
            }
            else {
                RateLimiter rateLimiter=rateLimiterMap.get(invocation.getClassName());
                rateLimiter.acquire();
            }
        }
    }
}

客户端限流过滤器调整

既然限流的实现抽取成了接口,所以此处的具体实现调整为从服务提供者中找对应的实现。

@Override
public Object invoke(RpcInvoker invoker, RpcInvocation invocation) {
    logger.info("before acquire,"+new Date());
    List<AccessLimitService> accessLimitServiceLoader = SpringFactoriesLoader.loadFactories(AccessLimitService.class, null);
    if(!CollectionUtils.isEmpty(accessLimitServiceLoader)){
        AccessLimitService accessLimitService=accessLimitServiceLoader.get(0);
        accessLimitService.acquire(invocation);
    }

    Object rpcResponse=invoker.invoke(invocation);
    logger.info("after acquire,"+new Date());
    return rpcResponse;
}
    

目前还不支持同一个项目中多种限流策略,目前版本只允许存在一种,如果配置了多种实现,也只会选择第一个。如果需要支持也是可以的,通过配置一个名称来指定即可,但感觉价值并不大。

SpringFactoriesLoader就是spring-boot实现的类似java.util.ServiceLoader的一种服务加载工具,它负责从resources/META-INF/spring.factories中读取相应的配置,并对其加载实例化。总共包含两个核心方法:

  • loadFactoryNames

    这个方法就是加载某个接口的所有指定实现类名,它可以服务于下面的loadFactories方法。

  • loadFactories 首先通过loadFactoryNames方法从配置文件中获取接口与实现类的关系,然后一个一个实例化服务实现类。

经过以上几步的调整,就基本实现了一个简单的基于SPI思想的组件扩展机制。客户端可以扩展任意的限流机制去替换。

本文源码

文中代码是依赖上述项目的,如果有不明白的可下载源码