手写RPC框架02-路由模块设计与实现

本文围绕RPC框架的路由模块展开。阐述了因服务多部署需路由模块及jdk Random随机函数缺点,介绍了路由抽象和多种负载均衡算法,如随机、权重、轮询等,还提及其他路由算法及路由策略配置化,最终完成了路由层设计与部分算法实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

源码地址:https://github.com/lhj502819/IRpc/tree/v3

系列文章:

为什么需要路由模块?

在当今互联网日益发展的情况下,我们一个服务一般都会部署多个,一方面可以均摊压力,另一方面也可以增加容错性,提高我们系统的稳定性。
但这种情况无疑会提升系统的复杂性,这里我们只讨论在进行RPC远程调用的时候我们需要考虑的事情。如果只有一个服务提供者Provider的情况下,直接根据ip + port请求即可,如果有多个Provider的话,那么就需要一套合适的负载均衡算法去选择一个合适的Provider。
如果没有路由模块的话,我们也可以很简单的实现,比如在上一版本中我们通过jdk自带的Random函数进行的随机选择。
在这里插入图片描述

但这样做有以下几个弊端:

  • 假设目标机器的性能不一致,如何对机器进行权重分配?
  • 每次都要执行Random函数,在高并发情况下对CPU的消耗较高;
  • 如何基于路由策略做ABTest?

因此我们单独抽象出一个模块来做这些工作,也就是路由模块。

jdk Random随机函数的缺点

通过查看Random函数的源码我们就能知道,由于Random函数底层会调用System.nanTome(),此函数会发起一次系统调用,而系统调用就涉及到CPU的状态切换,对性能的消耗是极大的。因此我们如果需要用到随机算法的话,最好自己实现一套。
在这里插入图片描述

路由抽象

public interface IRouter {

    /**
     * 刷新路由数组
     * @param selector
     */
    void refreshRouterArr(Selector selector);

    /**
     * 获取对应provider的连接通道
     * @param selector
     * @return
     */
    ChannelFutureWrapper select(Selector selector);

    /**
     * 更新权重值
     */
    void updateWeight(URL url);

}

负载均衡算法

随机算法

对应源代码中的cn.onenine.irpc.framework.core.router.RandomRouterImpl
实现思想:提前将所有的连接打乱顺序,随机放到数组中,也能达到随机访问的效果,但访问的顺序是不变的。当Client连接完成后,则调用此方法打乱顺序。

public void refreshRouterArr(Selector selector) {
    List<ChannelFutureWrapper> channelFutureWrappers = CONNECT_MAP.get(selector.getProviderServiceName());
    ChannelFutureWrapper[] arr = new ChannelFutureWrapper[channelFutureWrappers.size()];
    //提权生成调用先后顺序的随机数组
    int[] result = createRandomIndex(arr.length);
    //按照随机数组中的数字顺序,将所有的provider channel放入新的Channel数组中
    for (int i = 0; i < result.length; i++) {
        arr[i] = channelFutureWrappers.get(result[i]);
    }
    SERVICE_ROUTER_MAP.put(selector.getProviderServiceName(), arr);
}

/**
 * 创建随机乱序数组
 */
public static Integer[] createRandomArr(Integer[] arr) {
    int total = arr.length;
    Random ra = new Random();
    for (int i = 0; i < total; i++) {
        int j = ra.nextInt(total);
        if (i == j) {
            continue;
        }
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
    return arr;
}

权重算法

每个Provider在向注册中心注册的时候,都会设置自身的权重值为100,Client会在与Provider建立连接之后开启一个NodeData Watcher,当监听到Provider节点数据发生变化时,则会发起一个自定义的事件IRpcNodeChangeEvent,通知我们的路由策略进行权重刷新(updateWeight)。
在这里插入图片描述

如下为核心实现逻辑:

@Override
public void updateWeight(URL url) {
    List<ChannelFutureWrapper> channelFutureWrappers = CONNECT_MAP.get(url.getServiceName());
    //创建根据权重值创建对应的数组,权重大的其index在数组中占比大
	//比如channelFutureWrappers的第3个weight占比为50%,其他的4个总共占比50%
	//那么weightArr中则大概长这样:3,3,3,3,0,1,2,4
    Integer[] weightArr = createWeightArr(channelFutureWrappers);
    Integer[] randomArr = createRandomArr(weightArr);
    ChannelFutureWrapper[] finalChannelFutureWrappers = new ChannelFutureWrapper[randomArr.length];
    for (int i = 0; i < randomArr.length; i++) {
        finalChannelFutureWrappers[i] = channelFutureWrappers.get(randomArr[i]);
    }
    SERVICE_ROUTER_MAP.put(url.getServiceName(),finalChannelFutureWrappers);
}
public static Integer[] createWeightArr(List<ChannelFutureWrapper> channelFutureWrappers) {
    List<Integer> weightArr = new ArrayList<>();
    for (int k = 0; k < channelFutureWrappers.size(); k++) {
        Integer weight = channelFutureWrappers.get(k).getWeight();
        int c = weight / 100;
        for (int i = 0; i < c; i++) {
            weightArr.add(k);
        }
    }
    Integer[] arr = new Integer[weightArr.size()];
    return weightArr.toArray(arr);
}
/**
 * 创建随机乱序数组
 */
public static Integer[] createRandomArr(Integer[] arr) {
    int total = arr.length;
    Random ra = new Random();
    for (int i = 0; i < total; i++) {
        int j = ra.nextInt(total);
        if (i == j) {
            continue;
        }
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
    return arr;
}

轮询算法

通过自增计数,对数组长度取余的方式进行轮询访问。

public class ChannelFuturePollingRef {

    private AtomicLong referenceTimes = new AtomicLong(0);

    /**
     * 对Providers实现轮询访问
     */
    public ChannelFutureWrapper getChannelFutureWrapper(String serviceName) {
        ChannelFutureWrapper[] wrappers = SERVICE_ROUTER_MAP.get(serviceName);
        //自增取余,顺序访问
        //0 % 10 = 0; 1 % 10 = 1; 2 % 10 = 2 ;....;11 % 10 = 1
        long i = referenceTimes.getAndIncrement();
        int index = (int) (i % wrappers.length);
        return wrappers[index];
    }

}

其他路由算法

  • 最小连接数

需要记录每个应用服务器正在处理的连接数,然后将新来的请求转发到最少的那台上。

  • 分布式哈希一致性算法

分布式哈希一致性算法在实际使用时可能会出现“哈希倾斜”问题,为了解决这类问题,通常在算法的内部会设计一些虚拟节点,从而平衡请求的均匀性。

  • ip的hash算法

通过将源地址通过hash计算,定位到具体的一台机器上,但是如果一旦某台机器崩溃的话,该IP的请求就会直接失败,容错性不强。

路由策略配置化

将具体的路由策略通过配置的方式,使用起来更加灵活。在Client初始化的时候,会根据不同的配置选择对应的路由策略实现。

private void initConfig() {
    //初始化路由策略
    String routeStrategy = clientConfig.getRouteStrategy();
    if (RANDOM_ROUTER_TYPE.equals(routeStrategy)) {
        IROUTER = new RandomRouterImpl();
    } else if (ROTATE_ROUTER_TYPE.equals(routeStrategy)) {
        IROUTER = new RotateRouterImpl();
    }
}

总结

本次我们完成了RPC框架中路由层的设计与实现,并实现了随机路由算法、根据权重进行访问和轮询算法。

<think>好的,我现在要帮用户解答Java后端面试的复习攻略。首先,我得想想用户的需求是什么。用户可能是准备找工作的应届生或者有一定经验的开发者,想要系统性地复习Java后端相关的知识,应对面试。用户可能希望得到一个全面的复习路线,涵盖重点知识点和常见面试题。 首先,我需要确定Java后端面试通常考察哪些内容。一般来说,包括Java基础、数据结构算法、数据库、框架(如Spring)、系统设计、分布式系统、计算机网络、操作系统、项目经验、行为面试题等。此外,代码能力和实战经验也很重要。 接下来要考虑用户的背景。如果是应届生,可能更侧重基础知识和算法;如果有经验,可能更关注系统设计、分布式和高并发。但用户没有说明,所以应该覆盖全面,同时突出重点。 然后,需要组织内容的结构。分模块来讲解,每个模块列出重点知识点和常见问题,这样用户复习起来有条理。比如分Java基础、JVM、并发编程、数据库、框架、系统设计等模块。 每个模块里需要具体的关键点。例如Java基础中的集合类、多线程、IO/NIO等;JVM的内存模型、垃圾回收;并发编程的锁机制、线程池;数据库的索引、事务隔离级别;Spring的IoC、AOP、事务管理等。 还要注意结合实际面试题,比如HashMap的实现原理,ConcurrentHashMap的线程安全机制,MySQL的索引优化,Redis的持久化方式,分布式事务解决方案等。这些是高频问题,需要详细解释。 系统设计部分可能包括如何设计一个秒杀系统,分布式锁的实现,CAP理论等。这部分需要用户有一定的实战经验,或者了解常见的设计模式。 另外,代码能力部分要提醒用户多刷LeetCode,特别是数组、链表、二叉树、动态规划等题目。面试中可能会有手写代码的环节,所以熟练度很重要。 项目经验方面,用户需要准备自己参过的项目,能够清晰描述项目架构、遇到的挑战和解决方案。最好有技术难点和优化经验,这能展示实际能力。 行为面试题如职业规划、优缺点、团队协作等问题也不能忽视,虽然技术为主,但综合能力同样重要。 最后,需要提醒用户复习时注意理解原理,而不仅仅是记忆,面试官喜欢深入追问。同时建议模拟面试,查漏补缺,保持良好心态。 可能用户没有提到的深层需求是希望高效复习,抓住重点,避免浪费时间在不重要的内容上。因此,攻略需要简洁明了,突出重点,提供明确的复习方向和学习资源推荐。 总结下来,回答的结构应该是分模块列出重点,每个模块给出关键知识点和常见问题,最后补充复习建议。这样用户可以根据自己的情况有针对性地准备,提高面试成功率。</think>以下是Java后端面试的系统复习攻略(按优先级排序),附高频考点和复习建议: --- ### 一、Java核心基础(必须吃透) 1. **集合框架** - HashMap底层原理(数组+链表/红黑树)、ConcurrentHashMap分段锁/CAS机制 - ArrayList vs LinkedList时间复杂度对比 - 快速失败(fail-fast)和安全失败(fail-safe)区别 *高频题:HashMap扩容机制?为什么线程不安全?* 2. **多线程并发** - synchronized底层原理(对象头、锁升级过程) - volatile关键字内存可见性 - ThreadLocal原理及内存泄漏问题 - 线程池参数(corePoolSize、workQueue等)执行流程 *高频题:如何用ReentrantLock实现公平锁?* 3. **JVM** - 内存模型(堆、栈、方法区) - 垃圾回收算法(CMS/G1/ZGC对比) - 类加载机制双亲委派 *高频题:Full GC触发条件?如何排查OOM问题?* --- ### 二、数据库(MySQL+Redis重点) 1. **MySQL** - 索引原理(B+树 vs Hash) - 事务隔离级别MVCC实现 - 慢查询优化(EXPLAIN执行计划) *高频题:为什么用自增主键?如何解决幻读?* 2. **Redis** - 持久化机制(RDB/AOF混合持久化) - 缓存穿透/雪崩/击穿解决方案 - 分布式锁实现(Redlock算法) *高频题:Redis为什么快?集群数据如何分片?* --- ### 三、框架体系(Spring全家桶) 1. **Spring Framework** - IOC容器启动流程(Bean生命周期) - AOP实现原理(动态代理/CGLIB) - 声明式事务传播机制 *高频题:循环依赖如何解决?* 2. **Spring Boot** - 自动配置原理(@EnableAutoConfiguration) - Starter组件设计思想 - 监控端点(Actuator)使用 3. **Spring Cloud** - 服务注册发现(Eureka/Nacos) - 熔断降级(Hystrix/Sentinel) - 网关路由(Zuul/Gateway) --- ### 四、系统设计(社招重点) 1. **分布式系统** - CAP定理BASE理论 - 分布式事务解决方案(Seata/TCC) - 分库分表策略(ShardingSphere) 2. **高并发场景** - 秒杀系统设计(库存扣减方案) - 限流算法(令牌桶/漏桶) - 消息队列应用(Kafka顺序消费) --- ### 五、编码能力(必须手写) 1. **LeetCode高频题** - 链表:反转链表、环形链表检测 - 二叉树:层序遍历、最近公共祖先 - DP:最长递增子序列、背包问题 *建议:每天保持3道中等难度练习* --- ### 六、项目复盘(必考) 1. **STAR法则描述项目** - 突出技术难点(如:如何解决分布式锁失效问题) - 性能优化指标(QPS从1000提升到5000) - 体现架构设计能力(微服务拆分策略) --- ### 复习建议 1. **优先级策略**:基础 > 数据库 > 框架 > 系统设计 2. **实战演练**:本地搭建Redis集群/实现简易RPC框架 3. **模拟面试**:使用《Java面试突击手册》自测 4. **避坑指南**:避免死记硬背,重点理解设计思想 *附资源推荐:* - 书籍:《Java并发编程实战》《凤凰架构》 - 视频:尚硅谷JUC/黑马Spring源码 - 网站:JavaGuide、小林coding图解系统
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

壹氿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值