Spring Cloud Config Server 的 RSocket 实现方式

640?wx_fmt=jpeg

✏️ Pic by Alibaba Tech on Facebook


技术实践的作用在于:除了用于构建业务,也是为了验证某项技术或框架是否值得大规模推广。


这是《RSocket 从入门到落地》系列文章的第二篇,来一起实践一下如何基于RSocket来实现Spring Config Server的功能。该系列文章作者是阿里巴巴资深技术专家雷卷,第一篇回顾「传送门」。第二篇


阅读本系列文章,需要大家对Java有了解,其中可能会涉及到Kotlin,有少部分C++和Python(不做要求),如果了解Spring Boot则最好。 


?


配置推送一直都是应用部署的必要环节,不同的环境都会有不同的配置信息,如本地开发连接到本地127的数据库,线上环境则需要不一样的配置,这里我们不一一列举不同配置项,一句话,软件运行需要加载一定的配置项。


对Spring Boot应用来说,使用Config Server非常简单,如Spring Cloud Config Server(基于git),Spring Cloud Consul等都支持配置推送,所以整合也非常简单。 今天我们看看如何基于RSocket来实现Spring Config Server的功能。声明一下,接下来的是Demo,只是实现基础功能,不是说其他的产品都没有技术难度,只是实现通讯这一块,配置的一致性,版本控制等功能,还是要花功夫的。



配置推送的核心



配置推送的功能主要是两个:


  • 主动获取配置: request/response,这个是非常有必要的,当有一些错误,需要去主动刷新配置。

  • 配置更新推送:当配置有变化时,将新的配置推送下来,也就是通常所说的 request/stream


RSocket 支持大部分通讯模型的,所以我们写一个最基本的RSocket Handler就可以了。出于接下来讲解的需要,我将50行不到的实现代码都贴出来(我应该使用Kotlin的,这样可能就只有25行啦 :) ),后面我们再进行分析一下:


public class RSocketConfigHandlerImpl extends AbstractRSocket implements RSocketConfigHandler {    private static String CONFIG_TYPE = "text/x-java-properties";    private Map> configProcessorStore = new ConcurrentHashMap<>();    private Map configSnapshot = new ConcurrentHashMap<>();    public RSocketConfigHandlerImpl() {        refresh("app1", "name=leijuan");    }    public Mono requestResponse(Payload payload) {        String appName = "app1"; //payload.getDataUtf8();        if (!configSnapshot.containsKey(appName)) {            initApp(appName);        }        return Mono.just(DefaultPayload.create(configSnapshot.get(appName), CONFIG_TYPE));    }    public Flux requestStream(Payload payload) {        String appName = "app1"; //payload.getDataUtf8();        return Flux.create(sink -> {            configProcessorStore.get(appName).subscribe(config -> {                sink.next(DefaultPayload.create(config, CONFIG_TYPE));            });        });    }    public String getLastConfig(String appName) {        return configSnapshot.get(appName);    }    public void refresh(String appName, String config) {        if (!configSnapshot.containsKey(appName)) {            initApp(appName);        }        configSnapshot.put(appName, config);        configProcessorStore.get(appName).onNext(config);    }    private void initApp(String appName) {        synchronized (this) {            configSnapshot.put(appName, "");            configProcessorStore.put(appName, ReplayProcessor.cacheLast());        }    }}


上述的代码非常简单,我们稍微解释一下:


  • 配置项基于properties文本文件格式,也是spring boot通用的;

  • requestResponse返回该应用最新的配置项值,从configSnapshot这个Map数据结构获取;

  • requestStream的处理稍微有一点技巧,就是要选择Reactor的ReplayProcessor,然后使用ReplayProcessor.cacheLast()创建该ReplayProcessor,表示缓存配置流中的最新的一个值,这样应用启动后,接收到的配置更新就是最新的。一个应用名称对应一个ReplayProcessor就可以了;

  • 接下来我们做一个接口,叫做refresh,负责刷新配置;

通过RSocket和Reactor的配合,就是这么简单,接下来就是启动RSocket Server,这在第一篇文章中介绍过,这里贴一下代码,主要就是和Spring Boot整合的。


@Configurationpublic class RSocketAutoConfiguration {    private Logger log = LoggerFactory.getLogger(RSocketAutoConfiguration.class);    @Bean    public RSocketConfigHandlerImpl rsocketHandler() {        return new RSocketConfigHandlerImpl();    }    @Bean(destroyMethod = "dispose")    public Disposable rsocketResponder(RSocketConfigHandlerImpl rsocketHandler) {        Disposable responder = RSocketFactory.receive()                .acceptor((setup, sendingSocket) -> Mono.just(rsocketHandler))                .transport(TcpServerTransport.create("0.0.0.0", 42252))                .start()                .subscribe();        log.info("RSocket Config Server started on 42252.");        return responder;    }}


就是创建RSocket Handler Bean,然后将该bean传给acceptor函数即可。



Spring Boot 应用如何继承配置推送



这个也是非常简单,spring-cloud-context已经支持自定义配置项加载,所以我们只需要实现一个基于RSocket的PropertySourceLocator就可以啦,代码如下:


public class RSocketConfigPropertySourceLocator implements PropertySourceLocator {    private static Logger log = LoggerFactory.getLogger(RSocketConfigPropertySourceLocator.class);    public static RSocket CONFIG_RSOCKET;    public static Properties CONFIG_PROPERTIES = new Properties();    public static String LAST_CONFIG = "";    @Override    public PropertySource locate(Environment environment) {        String applicationName = environment.getProperty("spring.application.name");        if (applicationName != null) {            String cloudConfigUri = environment.getProperty("spring.cloud.config.rsocket.uri");            if (cloudConfigUri == null) {                cloudConfigUri = "tcp://127.0.0.1:42252";            }            if (CONFIG_RSOCKET == null) {                initRsocket(cloudConfigUri);            }            CONFIG_RSOCKET.requestResponse(DefaultPayload.create(applicationName))                    .subscribe(configPayload -> {                        refresh(configPayload.getDataUtf8());                    });        } else {            log.error("Please setup spring.application.name in application.properties");        }        return new PropertiesPropertySource("rsocket", CONFIG_PROPERTIES);    }    public void initRsocket(String cloudConfigUri) {        CONFIG_RSOCKET =                RSocketFactory.connect()                        .transport(UriTransportRegistry.clientForUri(cloudConfigUri))                        .start()                        .timeout(Duration.ofSeconds(3))                        .block();    }    public static void refresh(String config) {        try {            if (!LAST_CONFIG.equals(config)) {                CONFIG_PROPERTIES.load(new ByteArrayInputStream(config.getBytes()));                LAST_CONFIG = config;            }        } catch (Exception e) {            log.error("Failed to refresh config", e);        }    }


这个类的核心就是从environment拿到RSocket Config Server的地址和应用名,然后再调用一次requestResponse拿到最新的配置项。


接下来,我们会再创建一个config listener,负责监听配置项变化,说监听是便于理解,其实就是对Flux的subscribe。代码如下:


public class RSocketConfigListener {    private Logger log = LoggerFactory.getLogger(RSocketConfigListener.class);    private ContextRefresher contextRefresher;    private String applicationName;    public RSocketConfigListener(ContextRefresher contextRefresher, String applicationName) {        this.contextRefresher = contextRefresher;        this.applicationName = applicationName;    }    @PostConstruct    public void init() {        CONFIG_RSOCKET.requestStream(DefaultPayload.create(applicationName))                .subscribe(payload -> {                    String config = payload.getDataUtf8();                    log.info("Config refresh: " + config);                    refresh(payload.getDataUtf8());                    contextRefresher.refresh();                });    }}


调用requestStream获取配置流,然后进行subscribe,收到新的配置项后,调用一下ContextRefresher bean的refresh()方法,就完成了。


应用集成配置刷新,这个非常简单,只需要@RefreshScope 即可。



服务注册和服务发现



服务注册和发现是另外一个非常重要的特性,但是在上述的config server代码中,你会发现如果应用在连接到配置服务器的时候,能够带上一些信息,如应用的原信息,提供的服务列表信息,加上还是长连接(包括心跳检测),那么实现这样的一个服务注册就非常简单了,代码如下:


CONFIG_RSOCKET =                RSocketFactory.connect()                        .setupPayload(DefaultPayload.create("service=accout.svc.alibaba.net,ip=192.168.0.22,port=8080","application/json"))                        .transport(UriTransportRegistry.clientForUri(cloudConfigUri))                        .start()                        .timeout(Duration.ofSeconds(3))                        .block();


通过setupPayload()方法,我们向config server发送应用的信息,在server的acceptor()方法中可以将这些信息进行保存。 如果在创建连接时不能获取服务列表,可以在容器所有的ready后通过调用fireAndForget来通知注册中。 注册中心的实现中,当连接被关闭或者非法断开后,从服务注册中心进行删除。


这样一个服务注册就实现了,样例代码如下:


 Disposable responder = RSocketFactory.receive()                .acceptor((setupPayload, peerRSocket) -> {                    //进行 setup payload解析,将应用的信息注册                    peerRSocket.onClose().subscribe(t -> {                        //连接关闭后的处理逻辑                     });                    return Mono.just(rsocketHandler);                })                .transport(TcpServerTransport.create("0.0.0.0", 42252))                .start()                .subscribe();


当然服务注册还有其他非常多的逻辑,但是我们解决了核心的注册、健康度检查和下线处理。其他的优雅上下线,可能还会涉及一些细节,以及要实现fireAndForgot()进行事件通知。



服务注册和服务发现



通过此文,我们介绍了通过RSocket的两个通讯模型来实现配置推送这个场景,并利用同一个连接和RSocket的connection setup特性,将服务注册也进行了简单实现。有兴趣的同学可以自己看一下,代码量非常小的。 


本文作者:雷卷,GitHub ID linux-china,Java程序员,阿里巴巴资深技术专家。



/ 推荐另一篇值得细品的「云原生经验分享」 /


Spring Cloud Config Server 的 RSocket 实现方式_第1张图片

Photo by David Heslop on Unsplash


/ 推荐一个值得参与的开发者活动 /



640?wx_fmt=jpeg


©每周一推

第一时间获得下期分享



640?wx_fmt=jpeg

Tips:

# 点下“好看”❤️

# 然后,公众号对话框内发送“雨伞”,试试手气??

# 本期奖品由采购不设限的「淘宝企业服务」赞助 ?

你可能感兴趣的:(Spring Cloud Config Server 的 RSocket 实现方式)