🚀💥最新对线面试官,线下试一试,干!!!
运动确实会带来运气和机会
昨天晚上:晚上出去跑了跑步,突然手机一顿颤抖。接通后发现,原来有人事发现了我,邀请我去面试,询问我什么时间方便线下。面对着寒境下为数不多的面试机会,我当即决定明天下午就去。。。
今天:提前半个小时来到了大楼下,转了好几圈,终于找到了入口。
公司内:看着公司装修还不错,进入面试间等待,半个小时后,一个眼看着就颇具实力的面试官(事后发现是前某度的)进来了,我笑脸相迎,开始了愉快的对线。。。
现场面试提问记录
- static关键字?
- 单例模式懒加载?会有什么问题?为什么要static修饰成员变量和方法?如何控制并发?为什么要二次判断是否创建成功了?锁定的粒度?锁是重量级操作,使用锁合理吗?
- concurrentHashMap?
- 线程CountDownLatch,信号量?
- volatile?跟cpu有关系吗?跟一级缓存、二级缓存有关系吗?
- spring依赖注入,有哪些方案?
- 每个注解的出现是解决了什么问题?比如,这样的类的关系如何实现:A里面引用B,A‘继承了A,B’是B的子类,现在要实现A‘调用B’的实现方法?
- redis-cluster模式下,如果需要同时原子的执行三条指令才可以完成业务,如何做?如果用lua包裹,针对修改的数据不在一个分片时,如何处理?
- 你们的系统高Qps方案,会如何设计?
思考复盘
spring依赖注入
Spring 依赖注入的多种实现方式
在 Spring 框架中,依赖注入(Dependency Injection, DI)是一个核心概念,旨在通过外部注入的方式来管理对象之间的依赖关系,从而降低耦合性并提高代码的可测试性。本文将介绍各种依赖注入的方式,并实现一个具体的类关系:A
里面引用 B
,A'
继承了 A
,B'
是 B
的子类,现在要实现 A'
调用 B'
的实现方法。
类定义和关系
假设我们有以下类关系:
A
引用了B
A'
继承了A
B'
是B
的子类
并且我们希望在 A'
中调用 B'
的方法。
示例类
package com.kabai;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.stereotype.Component;
// 定义类 B
@Component
public class B {
public void doSomething() {
System.out.println("B is doing something");
}
}
// 定义类 BPrime 继承 B
@Component
public class BPrime extends B {
@Override
public void doSomething() {
System.out.println("BPrime is doing something");
}
}
// 定义类 A
@Component
public class A {
protected B b;
@Autowired
public void setB(B b) {
this.b = b;
}
public void execute() {
b.doSomething();
}
}
// 定义类 APrime 继承 A
@Component
public class APrime extends A {
private BPrime bPrime;
@Autowired
@Qualifier("bPrime")
public void setBPrime(BPrime bPrime) {
this.bPrime = bPrime;
}
@Override
public void execute() {
bPrime.doSomething();
}
}
1. 使用注解
1.1 @Autowired
@Autowired
注解用于自动注入 Spring 容器中的 bean。它可以应用于构造函数、方法、属性或参数上,通过类型匹配的方式自动注入依赖。
@Component
public class A {
@Autowired
protected B b;
public void execute() {
b.doSomething();
}
}
@Component
public class APrime extends A {
@Autowired
@Qualifier("bPrime")
private BPrime bPrime;
@Override
public void execute() {
bPrime.doSomething();
}
}
1.2 @Resource
@Resource
注解是 Java EE 提供的注解,可以按名称或类型注入 bean。
@Component
public class A {
@Resource(name = "b")
protected B b;
public void execute() {
b.doSomething();
}
}
@Component
public class APrime extends A {
@Resource(name = "bPrime")
private BPrime bPrime;
@Override
public void execute() {
bPrime.doSomething();
}
}
2. 使用 XML 配置
XML 配置是一种较为传统的方式,通过 XML 文件来配置 bean 以及它们之间的依赖关系。
<!-- applicationContext.xml -->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="b" class="com.kabai.B"/>
<bean id="bPrime" class="com.kabai.BPrime"/>
<bean id="a" class="com.kabai.A">
<property name="b" ref="b"/>
</bean>
<bean id="aPrime" class="com.kabai.APrime">
<property name="b" ref="bPrime"/>
</bean>
</beans>
3. 使用 Java 配置类
使用 Java 配置类,可以通过 @Configuration
和 @Bean
注解来定义和配置 bean。
@Configuration
@ComponentScan(basePackages = "com.kabai")
public class AppConfig {
@Bean
public B b() {
return new B();
}
@Bean
public BPrime bPrime() {
return new BPrime();
}
@Bean
public A a() {
A a = new A();
a.setB(b());
return a;
}
@Bean
public APrime aPrime() {
APrime aPrime = new APrime();
aPrime.setB(bPrime());
return aPrime;
}
}
4. 构造函数注入
构造函数注入通过类的构造函数来注入依赖,这种方式在实例化时强制依赖的存在。
@Component
public class A {
protected B b;
@Autowired
public A(B b) {
this.b = b;
}
public void execute() {
b.doSomething();
}
}
@Component
public class APrime extends A {
private BPrime bPrime;
@Autowired
public APrime(BPrime bPrime) {
super(bPrime);
this.bPrime = bPrime;
}
@Override
public void execute() {
bPrime.doSomething();
}
}
5. Setter 方法注入
Setter 方法注入通过类的 Setter 方法来注入依赖。
@Component
public class A {
protected B b;
@Autowired
public void setB(B b) {
this.b = b;
}
public void execute() {
b.doSomething();
}
}
@Component
public class APrime extends A {
private BPrime bPrime;
@Autowired
@Override
public void setB(B b) {
if (b instanceof BPrime) {
this.bPrime = (BPrime) b;
}
super.setB(b);
}
@Override
public void execute() {
bPrime.doSomething();
}
}
总结
Spring 提供了多种依赖注入的方式,包括注解、XML 配置、Java 配置类、构造函数注入和 Setter 方法注入。
redis-cluster模式下,如果需要同时原子的执行三条指令
在 Redis 集群模式下,同时原子地执行三条指令是一项挑战,特别是当涉及到多个分片时。Lua 脚本可以用来确保在单个节点上的原子性操作,但在跨节点时则需要额外的处理。以下是一些方法和考虑:
单节点原子操作
如果所有涉及的键都位于同一个分片(同一个 Redis 节点上),那么可以使用 Lua 脚本来确保操作的原子性:
redis.call('SET', 'key1', 'value1')
redis.call('SET', 'key2', 'value2')
redis.call('SET', 'key3', 'value3')
跨节点操作
在 Redis 集群模式下,涉及多个分片的情况,需要一些复杂的机制来确保操作的原子性。可以考虑以下方法:
-
在应用层实现分布式锁:
- 在每个涉及的分片上加锁,以确保这些操作可以顺序执行。可以使用
SETNX
和PEXPIRE
来实现分布式锁。
- 在每个涉及的分片上加锁,以确保这些操作可以顺序执行。可以使用
-
使用事务机制:
- 如果 Redis 集群节点支持事务,可以考虑在应用层将操作拆分成多个事务,并通过幂等性和重试机制来确保操作的一致性。
-
分布式事务协调器:
- 引入一个分布式事务协调器,如 XA 或 TCC(Try-Confirm-Cancel)模式。这种方法适用于对事务一致性要求极高的场景,但实现起来较为复杂。
示例:使用 Lua 脚本和分布式锁
-
加锁:
为每个涉及的键加锁:
-- Lua script for acquiring locks local lock1 = redis.call('SETNX', 'lock:key1', 'lock_value') local lock2 = redis.call('SETNX', 'lock:key2', 'lock_value') local lock3 = redis.call('SETNX', 'lock:key3', 'lock_value') if lock1 == 1 and lock2 == 1 and lock3 == 1 then -- All locks acquired redis.call('PEXPIRE', 'lock:key1', 10000) redis.call('PEXPIRE', 'lock:key2', 10000) redis.call('PEXPIRE', 'lock:key3', 10000) return true else -- Release any acquired locks if lock1 == 1 then redis.call('DEL', 'lock:key1') end if lock2 == 1 then redis.call('DEL', 'lock:key2') end if lock3 == 1 then redis.call('DEL', 'lock:key3') end return false end
-
执行操作:
在应用层检查锁定成功后执行操作,然后释放锁:
if acquire_locks() then redis.call('SET', 'key1', 'value1') redis.call('SET', 'key2', 'value2') redis.call('SET', 'key3', 'value3') release_locks() else -- Retry or handle failure end
-
释放锁:
-- Lua script for releasing locks redis.call('DEL', 'lock:key1') redis.call('DEL', 'lock:key2') redis.call('DEL', 'lock:key3')
总结
通过使用 Lua 脚本、分布式锁和事务协调机制,可以在 Redis 集群模式下实现跨分片的原子性操作。然而,由于实现的复杂性,通常需要根据具体业务需求和场景权衡性能和一致性。
转载自:https://juejin.cn/post/7387303384082677787