2023届秋招整理(项目面试题)

376 阅读4分钟

自学项目

一、电商中的高并发秒杀系统设计

1、说说你的缓存设计的怎么设计的?

答:系统中的缓存设计为 Redis 分布式缓存和 JVM 本地缓存,主要存储的是秒杀商品的信息,在秒杀的时候,首先从本地缓存中获取商品信息,如果商品信息不存在,则需要从分布式缓存中获取商品信息,如果分布式缓存中没有商品信息则需要从数据库获取商品信息,当然为了防止缓存击穿问题,更新分布式缓存时候需要首先获得分布式锁,只允许一个线程进行更新。这里存在一个问题就是 JVM 本地缓存的一致性问题,采用的是基于版本号的更新策略。这个版本号,使用更新缓存时候的时间戳。请求传入的版本号大于本地缓存的版本号,这说明已经有其他的机器更新过该缓存,意味着本地缓存的数据滞后,需要从分布式缓存中重新获取

2、你是怎么用 RocketMQ 做异步消息队列下单的?

答:RocketMQ 实现原理如下:

  1. 生产者先发送一条半事务消息到MQ
  2. MQ收到消息后返回ack确认
  3. 生产者开始执行本地事务
  4. 如果事务执行成功发送commit到MQ,失败发送rollback
  5. 如果MQ长时间未收到生产者的二次确认commit或者rollback,MQ对生产者发起消息回查
  6. 生产者查询事务执行最终状态
  7. 根据查询事务状态再次提交二次确认
  8. 最终,如果MQ收到二次确认commit,就可以把消息投递给消费者,反之如果是rollback,消息会保存下来并且在3天后被删除。

3、你是如何保证没有超卖的?

答:不用的秒杀活动,采用不同的秒杀方案,小型秒杀采用的就是 Redis 缓存预扣,然后直接同步更新数据库的库存,这两种操作是放在了一个事务中,一旦出现问题直接回滚,此时的并发量受限于数据库。第二个就是异步秒杀方案,Redis 缓存预扣,然后发送一条事务型消息给 RocketMQ,然后异步的进行下订单,扣减库存,使用 RocketMQ 就保证了本地事务与发送消息的原子性。

4、我看见你上面有部署进行压测,能说一下你是怎么压测的吗?做了哪些优化?

答:

5、你是怎么生成订单的全局 Id 的呢?

二、仿 Dubbo 的远程调用 RPC 框架

1、HTTP 和你这个 RPC 协议有啥区别?

答:

2、能说说 项目中如何使用 SPI 机制的吗?

JDK内置提供的ServiceLoader会自动帮助我们去加载/META-INF/services/目录下边的文件,并且将其转换为具体实现类。JDK内部的ServiceLoader加载流程大致为:调用load函数->再调用到reload方法,并且在reload方法里面触发一个叫做LazyIterator的类,这个类实现了迭代器的Iterator接口。

3、你是如何与 SpringBoot 框架进行整合的?

  • 在服务端定义一个配置类,实现 InitializingBean 接口和 ApplicationContextAware,并重写里面的 afterPropertiesSet() 方法,拿到 Map<String, Object> beanMap = applicationContext.getBeansWithAnnotation(IRpcService.class); 带有注解的所有对象,然后将对象注入到注册中心,并本地保留一份。

image.png

image.png

public class IRpcServerAutoConfiguration implements InitializingBean, ApplicationContextAware {

    private static final Logger LOGGER = LoggerFactory.getLogger(IRpcServerAutoConfiguration.class);

    private ApplicationContext applicationContext;

    @Override
    public void afterPropertiesSet() throws Exception {
        Server server = null;
        Map<String, Object> beanMap = applicationContext.getBeansWithAnnotation(IRpcService.class);
        if (beanMap.size() == 0) {
            //说明当前应用内部不需要对外暴露服务
            return;
        }
        printBanner();
        long begin = System.currentTimeMillis();
        server = new Server();
        server.initServerConfig();
        IRpcListenerLoader iRpcListenerLoader = new IRpcListenerLoader();
        iRpcListenerLoader.init();
        for (String beanName : beanMap.keySet()) {
            Object bean = beanMap.get(beanName);
            IRpcService iRpcService = bean.getClass().getAnnotation(IRpcService.class);
            ServiceWrapper dataServiceServiceWrapper = new ServiceWrapper(bean, iRpcService.group());
            dataServiceServiceWrapper.setServiceToken(iRpcService.serviceToken());
            dataServiceServiceWrapper.setLimit(iRpcService.limit());
            server.exportService(dataServiceServiceWrapper);
            LOGGER.info(">>>>>>>>>>>>>>> [irpc] {} export success! >>>>>>>>>>>>>>> ",beanName);
        }
        long end = System.currentTimeMillis();
        ApplicationShutdownHook.registryShutdownHook();
        server.startApplication();
        LOGGER.info(" ================== [{}] started success in {}s ================== ",server.getServerConfig().getApplicationName(),((double)end-(double)begin)/1000);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    private void printBanner(){
        System.out.println();
        System.out.println("==============================================");
        System.out.println("|||---------- IRpc Starting Now! ----------|||");
        System.out.println("==============================================");
        System.out.println("源代码地址: https://gitee.com/IdeaHome_admin/irpc-framework");
        System.out.println("version: 1.0.0");
        System.out.println();
    }
}
  • 客户端自动装配对象 IRpcClientAutoConfiguration 类。负责在每个bean启动的时候,对bean里面凡是携带有IRpcReference注解的字段都修改其引用,使其指向对应的代理对象。
  • 获取 bean 对象的成员变量字段,判断是否具有注解 IRpcReference.class; 有注解的话通过反射工厂创建代理对象,进行一个替换。
public class IRpcClientAutoConfiguration implements BeanPostProcessor, ApplicationListener<ApplicationReadyEvent> {

    private static RpcReference rpcReference = null;
    private static Client client = null;
    private volatile boolean needInitClient = false;
    private volatile boolean hasInitClientConfig = false;

    private static final Logger LOGGER = LoggerFactory.getLogger(IRpcClientAutoConfiguration.class);

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        Field[] fields = bean.getClass().getDeclaredFields();
        for (Field field : fields) {
            if (field.isAnnotationPresent(IRpcReference.class)) {
                if (!hasInitClientConfig) {
                    client = new Client();
                    try {
                        rpcReference = client.initClientApplication();
                    } catch (Exception e) {
                        LOGGER.error("[IRpcClientAutoConfiguration] postProcessAfterInitialization has error ",e);
                        throw new RuntimeException(e);
                    }
                    hasInitClientConfig = true;
                }
                needInitClient = true;
                IRpcReference iRpcReference = field.getAnnotation(IRpcReference.class);
                try {
                    field.setAccessible(true);
                    Object refObj = field.get(bean);
                    RpcReferenceWrapper rpcReferenceWrapper = new RpcReferenceWrapper();
                    rpcReferenceWrapper.setAimClass(field.getType());
                    rpcReferenceWrapper.setGroup(iRpcReference.group());
                    rpcReferenceWrapper.setServiceToken(iRpcReference.serviceToken());
                    rpcReferenceWrapper.setUrl(iRpcReference.url());
                    rpcReferenceWrapper.setTimeOut(iRpcReference.timeOut());
                    //失败重试次数
                    rpcReferenceWrapper.setRetry(iRpcReference.retry());
                    rpcReferenceWrapper.setAsync(iRpcReference.async());
                    refObj = rpcReference.get(rpcReferenceWrapper);
                    field.set(bean, refObj);
                    client.doSubscribeService(field.getType());
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (Throwable throwable) {
                    throwable.printStackTrace();
                }
            }
        }
        return bean;
    }