# lagou_3_3_dubbo **Repository Path**: java-quickstart/lagou_3_3_dubbo ## Basic Information - **Project Name**: lagou_3_3_dubbo - **Description**: 第三阶段第三模块 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2020-06-16 - **Last Updated**: 2020-12-19 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 李志勇的作业 #### 阶段三模块三作业 编程题一:将 Web 请求 IP 透传到 Dubbo 服务中 通过扩展 Dubbo 的 Filter(TransportIPFilter),完成 Web 请求的真实 IP 透传到 Dubbo 服务当中,并在 Dubbo 服务中打印请求的 IP 题目要求: 1. 构建一个 Web 项目(A),提供一个 HTTP 接口;构建 2 个 Dubbo 服务(B 和 C),各提供一个 Dubbo 接口,被 Web 项目调用(如下图所示) 2. 从 Web 项目获取请求的 IP,通过 TransportIPFilter 完成把 IP 设置到 RpcContext 中 3. 在两个 Dubbo 项目(B 和 C)中,从 RpcContext 获取 IP 并打印到控制台,B 和 C 都应该正确打印 IP 4. 注意:不可在业务方法调用 Dubbo 接口前采用硬编码的方式设置 IP **实现思路**: - 首先构建两个 Dubbo 服务(A 和 B),各提供一个 Dubbo 接口,注册到 zookeeper://127.0.0.1:2181 - 构建一个 Web 消费者,增加拦截器获取请求 IP 设置到 ThreadLocal 中,从注册中心发现 Dubbo 服务,在 controller 中调用 Dubbo 服务 ```java public class RequestContext { private static final ThreadLocal REMOTE_ADDR = new ThreadLocal<>(); public static void setRemoteAddr(String remoteAddr) { REMOTE_ADDR.set(remoteAddr); } public static String getRemoteAddr() { return REMOTE_ADDR.get(); } public static void removeRemoteAddr() { REMOTE_ADDR.remove(); } } public class IPInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { RequestContext.setRemoteAddr(request.getRemoteAddr()); return true; } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { RequestContext.removeRemoteAddr(); } } ``` - 实现 Filter(作用于 consumer 端),从 ThreadLocal 中获取上一步设置的请求 IP,再将获取到的 IP 设置到 RpcContext 中 ```java @Activate(group = CommonConstants.CONSUMER) public class TransportIPFilter implements Filter { @Override public Result invoke(Invoker invoker, Invocation invocation) throws RpcException { RpcContext rpcContext = RpcContext.getContext(); rpcContext.setAttachment("remoteAddr", RequestContext.getRemoteAddr()); return invoker.invoke(invocation); } } ``` - 两个 Dubbo 服务从 RpcContext 中获取 IP,输出到控制台 编程题二:简易版 Dubbo 方法级性能监控 在真实业务场景中,经常需要对各个业务接口的响应性能进行监控(常用指标为:TP90、TP99) 下面通过扩展 Dubbo 的 Filter(TPMonitorFilter),完成简易版本 Dubbo 接口方法级性能监控,记录下 TP90、TP99 请求的耗时情况 题目要求: 1. 编写一个 Dubbo 服务,提供 3 个方法(methodA、methodB、methodC),每方法都实现了随机休眠 0-100ms 2. 编写一个消费端程序,不断调用 Dubbo 服务的 3 个方法(建议利用线程池进行并行调用,确保在 1 分钟内可以被调用 2000 次以上) 3. 利用 TPMonitorFilter 在消费端记录每个方法的请求耗时时间(异步调用不进行记录) 4. 每隔 5s 打印一次最近 1 分钟内每个方法的 TP90、TP99 的耗时情况 **实现思路**: - 首先构建一个 Dubbo 服务,提供 3 个方法,每个方法中随机休眠 0~100ms,注册到 zookeeper://127.0.0.1:2181 - 构建一个 Web 消费者,从注册中心发现 Dubbo 服务,在 controller 中通过线程池并发调用 Dubbo 服务 ```java private AtomicBoolean started = new AtomicBoolean(true); @DubboReference private HelloService helloService; @GetMapping("/start") public String start(Integer sleep) throws InterruptedException { started.set(true); while (started.get()) { executorService.submit(() -> helloService.aHello("aaa")); executorService.submit(() -> helloService.bHello("bbb")); executorService.submit(() -> helloService.cHello("ccc")); TimeUnit.MILLISECONDS.sleep(Optional.ofNullable(sleep).orElse(500)); } return "started success"; } ``` - 实现一个 Filter(作用于 Consumer 端),在 invoke 中封装请求调用信息(开始时间、结束时间、方法名称)并保存到全局的 Map 中 - 通过 ScheduledExecutorService 线程池每 5s 启动工作线程,从全局 Map 中过滤出一分钟内的调用信息,按照调用耗时排序,在通过 TP90 或者 TP99 计算出相应的索引位置,获取该位置的请求调用信息即可 ```java @Override public void run() { String currentDate = LocalDateTime.now() .format(new DateTimeFormatterBuilder() .appendPattern("yyyy-MM-dd HH:mm:ss").toFormatter()); for (Map.Entry> entry : methodMonitorsMap.entrySet()) { List monitors = entry.getValue(); System.out.println(String.format(">>>[%s] %s TP%d: %dms, TP%d: %dms", currentDate, entry.getKey(), 90, getTP(monitors, 0.9), 99, getTP(monitors, 0.99)) ); } } private long getTP(List monitors, double rate) { long endTime = System.currentTimeMillis(); long startTime = endTime - 60000; List lastMinuteMonitors = monitors.stream() .filter(o -> (o.getResponseTime() >= startTime && o.getResponseTime() <= endTime)) .sorted() .collect(Collectors.toList()); int index = (int) (lastMinuteMonitors.size() * rate); return lastMinuteMonitors.get(index).getCostTime(); } ``` - 通过 ScheduledExecutorService 线程池每 60s 启动工作线程,将全局 Map 中 1 分钟之前的调用信息删除 ```java Executors.newSingleThreadScheduledExecutor() .scheduleWithFixedDelay(() -> { long startTime = System.currentTimeMillis() - 60000; for (Map.Entry> entry : methodMonitorsMap.entrySet()) { entry.getValue().removeIf(o -> o.getResponseTime() < startTime); } }, 60, 60, TimeUnit.SECONDS); ``` #### 视频演示 [视频讲解](./视频演示.mov)