自学项目
一、电商中的高并发秒杀系统设计
1、说说你的缓存设计的怎么设计的?
答:系统中的缓存设计为 Redis 分布式缓存和 JVM 本地缓存,主要存储的是秒杀商品的信息,在秒杀的时候,首先从本地缓存中获取商品信息,如果商品信息不存在,则需要从分布式缓存中获取商品信息,如果分布式缓存中没有商品信息则需要从数据库获取商品信息,当然为了防止缓存击穿问题,更新分布式缓存时候需要首先获得分布式锁,只允许一个线程进行更新。这里存在一个问题就是 JVM 本地缓存的一致性问题,采用的是基于版本号的更新策略。这个版本号,使用更新缓存时候的时间戳。请求传入的版本号大于本地缓存的版本号,这说明已经有其他的机器更新过该缓存,意味着本地缓存的数据滞后,需要从分布式缓存中重新获取。
2、你是怎么用 RocketMQ 做异步消息队列下单的?
答:RocketMQ 实现原理如下:
- 生产者先发送一条半事务消息到MQ
- MQ收到消息后返回ack确认
- 生产者开始执行本地事务
- 如果事务执行成功发送commit到MQ,失败发送rollback
- 如果MQ长时间未收到生产者的二次确认commit或者rollback,MQ对生产者发起消息回查
- 生产者查询事务执行最终状态
- 根据查询事务状态再次提交二次确认
- 最终,如果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);带有注解的所有对象,然后将对象注入到注册中心,并本地保留一份。
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;
}