专栏首页一杯82年的JAVA从0.5到1写个rpc框架 - 5:服务监控和管理(actuator)

从0.5到1写个rpc框架 - 5:服务监控和管理(actuator)

微服务一般需要监控实例状态和对其做一些干预,通过spring的endpoint可以实现这个功能。

springboot项目中只要引入spring-boot-starter-actuator就可以得到一些管理服务的接口,比如停止服务,获取服务信息等。他用的并不是controller,而是Endpoint,不过主要功能差不多。

借住上节实现的filter机制,可以在不改动框架核心代码的基础上实现这个功能。作为实践写两个功能:获取服务端的统计数据、服务状态控制

新建模块 acuprpc-spring-boot-starter-actuator。

为了统一管理这个框架的endpoint,定义一个父类。所有子类的id默认加上“rpc”前缀

public abstract class AbstractRpcEndpoint<T> extends AbstractEndpoint<T> {
    private static final String PREFIX = "rpc";

    public AbstractRpcEndpoint(String id) {
        super(PREFIX + id);
    }
    public AbstractRpcEndpoint(String id, boolean sensitive) {
        super(PREFIX + id, sensitive);
    }
    public AbstractRpcEndpoint(String id, boolean sensitive, boolean enabled) {
        super(PREFIX + id, sensitive, enabled);
    }
}

数据统计

MonitorFilter

使用filter拦截请求,统计处理请求的数量。

@Getter
public class MonitorFilter implements RpcFilter {

    private Map<String, RequestCount> requestCountMap = new ConcurrentHashMap<>();

    @Override
    public void doFilter(RpcRequest request, RpcResponse response, RpcFilterChain filterChain) {
        RequestCount count = requestCountMap.computeIfAbsent(request.getKey(), RequestCount::new);
        count.received.increment();
        count.invoking.increment();
        try {
            filterChain.doFilter(request, response);
            count.success.increment();
        } catch (Exception e) {
            count.failed.increment();
            throw e;
        } finally {
            count.invoking.decrement();
        }
    }

    @Getter
    public static class RequestCount {
        private String key;
        private LongAdder received = new LongAdder();//已接收
        private LongAdder invoking = new LongAdder();//执行中
        private LongAdder success = new LongAdder();//处理成功
        private LongAdder failed = new LongAdder();//处理失败

        public RequestCount(String key) {
            this.key = key;
        }
    }
}

RpcStatEndpoint

提供http接口,通过 /rpcstat 即可获取invoke()的返回值。

public class RpcStatEndpoint extends AbstractRpcEndpoint<Map<String, Object>> {
    private MonitorFilter filter;

    public RpcStatEndpoint(MonitorFilter filter) {
        super("stat");
        this.filter = filter;
    }

    @Override
    public Map<String, Object> invoke() {
        Map<String, Object> result = new HashMap<>();
        Collection<MonitorFilter.RequestCount> counts = filter.getRequestCountMap().values();
        result.put("counts", counts);
        result.put("serving", counts.stream().anyMatch(t -> t.getInvoking().sum() > 0L));
        return result;
    }
}

服务管理

RejectFilter

使用filter拦截请求,并在filter中维护一个下线状态,如果下线了则拒绝所有请求(针对这种返回值,客户端可以重新发现其他节点)。

@Data
public class RejectFilter implements RpcFilter {
    private boolean reject = false;
    //拒绝请求的处理逻辑也可以自定义
    private BiConsumer<RpcRequest, RpcResponse> rejectFunction = (rpcRequest, response) -> response.reject();

    @Override
    public void doFilter(RpcRequest request, RpcResponse response, RpcFilterChain filterChain) {
        if (reject) {
            rejectFunction.accept(request, response);
            return;
        }
        filterChain.doFilter(request, response);
    }
}

EndpointMvcAdapter

Endpoint使用很方便,但是相对controller不是那么灵活,比如我要让接口支持参数,就需要一些其他操作,将Endpoint使用EndpointMvcAdapter包装一次。为了复用,我写了个通用的EndpointMvcAdapter,通过反射去调用参数指定的方法。

@Slf4j
public class ReflectEndpointMvcAdapter extends EndpointMvcAdapter implements RpcCode {
    private Map<String, Method> methodMap = new HashMap<>();
    private Set<String> ipWhiteList = new HashSet<>();

    public ReflectEndpointMvcAdapter(Endpoint<?> delegate, String ipWhiteList) {
        super(delegate);
        Method[] methods = delegate.getClass().getMethods();
        //...
    }

    @RequestMapping(value = "/{name:.*}", method = RequestMethod.GET, produces = {
            ActuatorMediaTypes.APPLICATION_ACTUATOR_V1_JSON_VALUE,
            MediaType.APPLICATION_JSON_VALUE
    })
    @ResponseBody
    @HypermediaDisabled
    public Object invoke(HttpServletRequest request, HttpServletResponse response, @PathVariable String name) {
        if (!checkIp(request)) {
            //...
        }
        Method method = methodMap.get(name);
        //...
        try {
            return method.invoke(getDelegate());
        } catch (Exception e) {
            //...
        }
    }

    private boolean checkIp(HttpServletRequest request) {
       //...
    }

    private String getIp(HttpServletRequest request) {
        //...
    }
}

RpcEndpoint

因为要用ReflectEndpointMvcAdapter,invoke方法暂时没想到用什么( /rpc 时调用),就返回null。

public class RpcEndpoint extends AbstractRpcEndpoint<Object> implements RpcCode {
    private RejectFilter filter;

    public RpcEndpoint(RejectFilter filter) {
        super("");
        this.filter = filter;
    }

    @Override
    public Object invoke() {
        return null;
    }

    public void online() {
        filter.setReject(false);
    }

    public void offline() {
        filter.setReject(true);
    }

    public int status() {
        if (filter.isReject()) {
            throw new HttpStatusException(NOT_AVAILABLE);
        }
        return 0;
    }

}

定义bean时包装

@Bean
    public ReflectEndpointMvcAdapter rpcEndpoint(RejectFilter rejectFilter) {
        return new ReflectEndpointMvcAdapter(process(new RpcEndpoint(rejectFilter)), ipWhiteList);
    }

    private <T extends AbstractRpcEndpoint<?>> T process(T endpoint) {
        endpoint.setSensitive(sensitive);
        return endpoint;
    }

现在只要引入acuprpc-spring-boot-starter-actuator就能得到这几个http接口了,借助这几个接口服务可以优雅地重发。

本文分享自微信公众号 - 一杯82年的JAVA(acupjava),作者:acupt

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-07-14

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 从0.5到1写个rpc框架 - 4:request filter

    在写mvc项目时,经常会用到filter,可以给一个请求做前置或者后置处理。如下:

    acupt
  • 探索JAVA并发 - 如何优雅地取消线程任务

    一种常用的方法是在任务代码中加入一个“是否取消”的标志,任务定期去查看这个标志是否改变,如果被改变了就取消剩下的任务,此时如果想取消这个任务只需要修改它的标志,...

    acupt
  • JAVA中有趣的位运算

    当我们看一些源码的时候,经常会看到诸如 &、|、^、~ 的符号,这些就是位运算符。

    acupt
  • Java设计模式(一)工厂模式

    一、场景描述 仪器数据文件的格式包含Pdf、Word、Excel等多种,不同种格式的文件其数据的采集方式不同,因此定义仪器数据采集接口,并定义PDF、Excel...

    用户1637609
  • 补习系列-springboot restful实战

    摘自百科的定义:REST即表述性状态转移(英文:Representational State Transfer,简称REST) 是Roy Fielding博士(...

    美码师
  • (58) 文本文件和字符流 / 计算机程序的思维逻辑

    上节我们介绍了如何以字节流的方式处理文件,我们提到,对于文本文件,字节流没有编码的概念,不能按行处理,使用不太方便,更适合的是使用字符流,本节就来介绍字符流。 ...

    swiftma
  • JDK8新特性总结

    Lambda表达式是一个新的语言特性,已经在JDK8中加入。它是一个可以传递的代码块,你也可以把它们当做方法参数。Lambda表达式允许您更紧凑地创建单虚方法接...

    掌上编程
  • 程序猿的日常——Java基础之抽象类与接口、枚举、泛型

    再次回顾这些基础内容,发现自己理解的又多了一点。对于一些之前很模糊的概念,渐渐的清晰起来。 抽象类与接口 抽象类通常是描述一些对象的通用方法和属性,并且默...

    用户1154259
  • Jquery ajax传递复杂参数给WebService

    http://www.cnblogs.com/kingge/archive/2011/08/04/2127642.html

    跟着阿笨一起玩NET
  • 反射类的字段

    @Test public void test6() throws Exception { Person person = new...

    MonroeCode

扫码关注云+社区

领取腾讯云代金券