华为外派伊拉克,一天补助6000元

沉默王二

共 14818字,需浏览 30分钟

 · 2024-04-17

大家好,我是二哥呀。

有个同学发来一张截图,问我是真的假的:“华为外派到伊拉克,一天补助有6000元”,这是真的离谱啊,别说补助 6000 了,5000 我都辞职去伊拉克了(dog)。

大致调查了一下,比较正经的说法是,华为对艰苦地区的补助一天最高是 100 美元,并且食宿全免。我没有去核实,所以大家仅作参考!

有懂的同学可以评论区给核对一下哈~据说华为外派除了基本工资外,提供以下福利:

  • 离家补助
  • 艰苦补助(据郭嘉情况而定)
  • 伙食、住宿免费
  • 每年三套探亲机票,包家属伙食+住宿补贴
  • 优先升职加薪

对异乡他国不是很排斥的同学,真的可以尝试一下。当然了,华为的风评有好有坏,我们这里不做评断,我个人是觉得,如果你是对口院校,对华为有情节,去华为确实是个不错的选择。

这次我们就以《Java 面试指南-华为面经》中同学 6 的 Java 通用软件开发一二面为例,来看看华为面试官都喜欢问哪些问题。

可以看得出,仍然是围绕着二哥一直给大家强调的 Java 后端四大件展开,所以大家在准备的时候一定要有的放矢,内容较长,建议大家先收藏起来,面试的时候大概率会碰到,我会尽量用通俗易懂+手绘图的方式,让天下所有的面渣都能逆袭 😁

华为面经(详细)

说说分库分表的准则

分库分表是为了解决单库单表数据量过大导致数据库性能下降的一种解决方案。

分库的策略有两种:

①、垂直分库:按照业务模块将不同的表拆分到不同的库中,例如,用户表、订单表、商品表等分到不同的库中。

三分恶面渣逆袭:垂直分库

②、水平分库:按照一定的策略将一个表中的数据拆分到多个库中,例如,按照用户 id 的 hash 值将用户表拆分到不同的库中。

三分恶面渣逆袭:水平分库

当单表数据增量过快,业界流传的说法是超过 500 万的数据量就要考虑分表了。

在技术派实战项目中,我们将文章表和文章详情表做了分表处理,因为文章的详情数据量会比较大,而文章的基本信息数据量会比较小。

垂直拆分可以减轻只查询文章基本数据,不需要附带文章详情时的查询压力。

当然了,当表的数据量过大时,仍然要考虑水平分表,将一个表的数据分散到多个表中,以减轻单表的查询压力。

三分恶面渣逆袭:表拆分

怎么看走没走索引,如何分析SQL

explain 是 MySQL 提供的一个用于查看查询执行计划的工具,可以帮助我们分析查询语句的性能瓶颈,找出慢 SQL 的原因。

使用方式也非常简单,在 select 语句前加上 explain 关键字就可以了。

explain select * from students where id =9

接下来,我们需要理解 explain 输出结果中各个字段的含义。

三分恶面渣逆袭:EXPLAIN

①、id 列:查询的标识符。

②、select_type 列:查询的类型。常见的类型有:

  • SIMPLE:简单查询,不包含子查询或者 UNION 查询。
  • PRIMARY:查询中如果包含子查询,则最外层查询被标记为 PRIMARY。
  • SUBQUERY:子查询。
  • DERIVED:派生表的 SELECT,FROM 子句的子查询。

③、table 列:查的哪个表。

④、type 列:表示 MySQL 在表中找到所需行的方式,性能从最优到最差分别为:system > const > eq_ref > ref > range > index > ALL。

  • system,表只有一行,一般是系统表,往往不需要进行磁盘 IO,速度非常快
  • const、eq_ref、ref:这些类型表示 MySQL 可以使用索引来查找单个行,其中 const 是最优的,表示查询最多返回一行。
  • range:只检索给定范围的行,使用索引来检索。在where语句中使用 bettween...and<><=in 等条件查询 type 都是 range
  • index:遍历索引树读取。
  • ALL:全表扫描,效率最低。

⑤、possible_keys 列:可能会用到的索引,但并不一定实际被使用。

⑥、key 列:实际使用的索引。如果为 NULL,则没有使用索引。

⑦、key_len 列:MySQL 决定使用的索引长度(以字节为单位)。当表有多个索引可用时,key_len 字段可以帮助识别哪个索引最有效。通常情况下,更短的 key_len 意味着数据库在比较键值时需要处理更少的数据。

⑧、ref 列:用于与索引列比较的值来源。

  • const:表示常量,这个值是在查询中被固定的。例如在 WHERE column = 'value'中。
  • 一个或多个列的名称,通常在 JOIN 操作中,表示 JOIN 条件依赖的字段。
  • NULL,表示没有使用索引,或者查询使用的是全表扫描。

⑨、rows 列:估算查到结果集需要扫描的数据行数,原则上 rows 越少越好。

⑩、Extra 列:附加信息。

  • Using index:表示只利用了索引。
  • Using where:表示使用了 WHERE 过滤。
  • Using temporary :表示使用了临时表来存储中间结果。

示例:

二哥的 Java 进阶之路

说说 SQL 该如何优化

我在进行慢 SQL 优化的时候,主要通过以下几个方面进行优化:

沉默王二:SQL 优化

如何避免不必要的列?

比如说尽量避免使用 select *,只查询需要的列,减少数据传输量。

SELECT * FROM employees WHERE department_id = 5;

改成:

SELECT employee_id, first_name, last_name FROM employees WHERE department_id = 5;

如何进行分页优化?

当数据量巨大时,传统的LIMITOFFSET可能会导致性能问题,因为数据库需要扫描OFFSET + LIMIT数量的行。

延迟关联(Late Row Lookups)和书签(Seek Method)是两种优化分页查询的有效方法。

①、延迟关联

延迟关联适用于需要从多个表中获取数据且主表行数较多的情况。它首先从索引表中检索出需要的行 ID,然后再根据这些 ID 去关联其他的表获取详细信息。

SELECT e.id, e.name, d.details
FROM employees e
JOIN department d ON e.department_id = d.id
ORDER BY e.id
LIMIT 100020;

延迟关联后:

SELECT e.id, e.name, d.details
FROM (
    SELECT id
    FROM employees
    ORDER BY id
    LIMIT 100020
AS sub
JOIN employees e ON sub.id = e.id
JOIN department d ON e.department_id = d.id;

首先对employees表进行分页查询,仅获取需要的行的 ID,然后再根据这些 ID 关联获取其他信息,减少了不必要的 JOIN 操作。

②、书签(Seek Method)

书签方法通过记住上一次查询返回的最后一行的某个值,然后下一次查询从这个值开始,避免了扫描大量不需要的行。

假设需要对用户表进行分页,根据用户 ID 升序排列。

SELECT idname
FROM users
ORDER BY id
LIMIT 100020;

书签方式:

SELECT idname
FROM users
WHERE id > last_max_id  -- 假设last_max_id是上一页最后一行的ID
ORDER BY id
LIMIT 20;

优化后的查询不再使用OFFSET,而是直接从上一页最后一个用户的 ID 开始查询。这里的last_max_id是上一次查询返回的最后一行的用户 ID。这种方法有效避免了不必要的数据扫描,提高了分页查询的效率。

如何进行索引优化?

正确地使用索引可以显著减少 SQL 的查询时间,通常可以从索引覆盖、避免使用 != 或者 <> 操作符、适当使用前缀索引、避免列上函数运算、正确使用联合索引等方面进行优化。

①、利用覆盖索引

使用非主键索引查询数据时需要回表,但如果索引的叶节点中已经包含要查询的字段,那就不会再回表查询了,这就叫覆盖索引。

举个例子,现在要从 test 表中查询 city 为上海的 name 字段。

select name from test where city='上海'

如果仅在 city 字段上添加索引,那么这条查询语句会先通过索引找到 city 为上海的行,然后再回表查询 name 字段,这就是回表查询。

为了避免回表查询,可以在 city 和 name 字段上建立联合索引,这样查询结果就可以直接从索引中获取。

alter table test add index index1(city,name);

②、避免使用 != 或者 <> 操作符

!= 或者 <> 操作符会导致 MySQL 无法使用索引,从而导致全表扫描。

例如,可以把column<>'aaa',改成column>'aaa' or column<'aaa',就可以使用索引了。

优化策略就是尽可能使用 =><BETWEEN等操作符,它们能够更好地利用索引。

③、适当使用前缀索引

适当使用前缀索引可以降低索引的空间占用,提高索引的查询效率。

比如,邮箱的后缀一般都是固定的@xxx.com,那么类似这种后面几位为固定值的字段就非常适合定义为前缀索引:

alter table test add index index2(email(6));

需要注意的是,MySQL 无法利用前缀索引做 order by 和 group by 操作。

④、避免列上使用函数

在 where 子句中直接对列使用函数会导致索引失效,因为数据库需要对每行的列应用函数后再进行比较,无法直接利用索引。

select name from test where date_format(create_time,'%Y-%m-%d')='2021-01-01';

可以改成:

select name from test where create_time>='2021-01-01 00:00:00' and create_time<'2021-01-02 00:00:00';

通过日期的范围查询,而不是在列上使用函数,可以利用 create_time 上的索引。

⑤、正确使用联合索引

正确地使用联合索引可以极大地提高查询性能,联合索引的创建应遵循最左前缀原则,即索引的顺序应根据列在查询中的使用频率和重要性来安排。

select * from messages where sender_id=1 and receiver_id=2 and is_read=0;

那就可以为 sender_id、receiver_id 和 is_read 这三个字段创建联合索引,但是要注意索引的顺序,应该按照查询中的字段顺序来创建索引。

alter table messages add index index3(sender_id,receiver_id,is_read);

如何进行 JOIN 优化?

对于 JOIN 操作,可以通过优化子查询、小表驱动大表、适当增加冗余字段、避免 join 太多表等方式来进行优化。

①、优化子查询

子查询,特别是在 select 列表和 where 子句中的子查询,往往会导致性能问题,因为它们可能会为每一行外层查询执行一次子查询。

使用子查询:

select name from A where id in (select id from B);

使用 JOIN 代替子查询:

select A.name from A join B on A.id=B.id;

②、小表驱动大表

在执行 JOIN 操作时,应尽量让行数较少的表(小表)驱动行数较多的表(大表),这样可以减少查询过程中需要处理的数据量。

比如 left join,左表是驱动表,所以 A 表应小于 B 表,这样建立连接的次数就少,查询速度就快了。

select name from A left join B;

③、适当增加冗余字段

在某些情况下,通过在表中适当增加冗余字段来避免 JOIN 操作,可以提高查询效率,尤其是在高频查询的场景下。

比如,我们有一个订单表和一个商品表,查询订单时需要显示商品名称,如果每次都通过 JOIN 操作查询商品表,会降低查询效率。这时可以在订单表中增加一个冗余字段,存储商品名称,这样就可以避免 JOIN 操作。

select order_id,product_name from orders;

④、避免使用 JOIN 关联太多的表

《阿里巴巴 Java 开发手册》上就规定,不要使用 join 关联太多的表,最多不要超过 3 张表。

因为 join 太多表会降低查询的速度,返回的数据量也会变得非常大,不利于后续的处理。

如果业务逻辑允许,可以考虑将复杂的 JOIN 查询分解成多个简单查询,然后在应用层组合这些查询的结果。

如何进行排序优化?

MySQL 生成有序结果的方式有两种:一种是对结果集进行排序操作,另外一种是按照索引顺序扫描得出的自然有序结果。

因此在设计索引的时候要充分考虑到排序的需求。

select idname from users order by name;

如果 name 字段上有索引,那么 MySQL 可以直接利用索引的有序性,避免排序操作。

如何进行 UNION 优化?

UNION 操作用于合并两个或者多个 SELECT 语句的结果集。

①、条件下推

条件下推是指将 where、limit 等子句下推到 union 的各个子查询中,以便优化器可以充分利用这些条件进行优化。

假设我们有两个查询分支,需要合并结果并过滤:

SELECT * FROM (
    SELECT * FROM A
    UNION
    SELECT * FROM B
AS sub
WHERE sub.id = 1;

可以改写成:

SELECT * FROM A WHERE id = 1
UNION
SELECT * FROM B WHERE id = 1;

通过将查询条件下推到 UNION 的每个分支中,每个分支查询都只处理满足条件的数据,减少了不必要的数据合并和过滤。

说说你对JVM调优的了解

JVM 调优是一个复杂的过程,主要包括对堆内存、垃圾收集器、JVM 参数等进行调整和优化。

①、JVM 的堆内存主要用于存储对象实例,如果堆内存设置过小,可能会导致频繁的垃圾回收。所以,技术派实战项目是在启动 JVM 的时候就调整了一下 -Xms 和-Xmx 参数,让堆内存最大可用内存为 2G。

②、在项目运行期间,我会使用 JVisualVM 定期观察和分析 GC 日志,如果发现频繁的 Full GC,就需要特别关注老年代的使用情况。

接着,通过分析 Heap dump 寻找内存泄漏的源头,看看是否有未关闭的资源,长生命周期的大对象等。

之后,就要进行代码优化了,比如说减少大对象的创建、优化数据结构的使用方式、减少不必要的对象持有等。

说说进程间的通信方式

进程间通信(IPC,Inter-Process Communication)的方式有管道、信号、消息队列、共享内存、信号量和套接字。

编程十万问:进程间通信

简单说说管道:

管道可以理解成不同进程之间的传话筒,一方发声,一方接收,声音的介质可以是空气或者电缆。

进程间的管道就是内核中的一串缓存,从管道的一端写入数据,另一端读取。数据只能单向流动,遵循先进先出(FIFO)的原则。

编程十万问:管道

①、匿名管道:允许具有亲缘关系的进程(如父子进程)进行通信。

三分恶面渣逆袭:“奉先我儿”

②、命名管道:允许无亲缘关系的进程通信,通过在文件系统中创建一个特殊类型的文件来实现。

缺点:管道的效率低,不适合进程间频繁地交换数据。

简单说说信号:

信号可以理解成以前的 BB 机,用于通知接收进程某件事情发生了,是一种较为简单的通信方式,主要用于处理异步事件。

比如kill -9 1050就表示给 PID 为 1050 的进程发送SIGKIL信号。

这里顺带普及一下 Linux 中常用的信号:

  • SIGHUP:当我们退出终端(Terminal)时,由该终端启动的所有进程都会接收到这个信号,默认动作为终止进程。
  • SIGINT:程序终止(interrupt)信号。按 Ctrl+C 时发出,大家应该在操作终端时有过这种操作。
  • SIGQUIT:和 SIGINT 类似,按 Ctrl+\ 键将发出该信号。它会产生核心转储文件,将内存映像和程序运行时的状态记录下来。
  • SIGKILL:强制杀死进程,本信号不能被阻塞和忽略。
  • SIGTERM:与 SIGKILL 不同的是该信号可以被阻塞和处理。通常用来要求程序自己正常退出。

简单说说消息队列:

消息队列是保存在内核中的消息链表,按照消息的类型进行消息传递,具有较高的可靠性和稳定性。

编程十万问:消息队列

缺点:消息体有一个最大长度的限制,不适合比较大的数据传输;存在用户态与内核态之间的数据拷贝开销。

编程十万问:消息队列

简单说说共享内存:

允许两个或多个进程共享一个给定的内存区,一个进程写⼊的东西,其他进程⻢上就能看到。

共享内存是最快的进程间通信方式,它是针对其他进程间通信方式运行效率低而专门设计的。

三分恶面渣逆袭:共享内存

缺点:当多进程竞争同一个共享资源时,会造成数据错乱的问题。

简单说说信号量:

信号量可以理解成红绿灯,红灯停(信号量为零),绿灯行(信号量非零)。它本质上是一个计数器,用来控制对共享资源的访问数量。

三分恶面渣逆袭:信号量

它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。Java 中的 java.util.concurrent.Semaphore 类就实现了类似的功能。

控制信号量的⽅式有两种原⼦操作:

  • ⼀个是 P 操作(wait,减操作),当进程希望获取资源时,它会执行 P 操作。如果信号量的值大于 0,表示有资源可用,信号量的值减 1,进程继续执行。如果信号量的值为 0,表示没有可用资源,进程进入等待状态,直到信号量的值变为大于 0。
  • 另⼀个是 V 操作(signal,加操作),当进程释放资源时,它会执行 V 操作,信号量的值加 1。如果有其他进程因为等待该资源而被阻塞,这时会唤醒其中一个进程。
编程十万问:信号量

简单说说套接字 Socket:

这个和 Java 中的 Socket 很相似,提供网络通信的端点,可以让不同机器上运行的进程之间进行双向通信。

说说 DNS 的解析过程

DNS 的全称是 Domain Name System,也就是域名解析系统,它可以将域名映射到对应的 IP 地址上,比如说我们访问 www.javabetter.cn,实际上访问的是我在阿里云上一台丐版服务器,它的 IP 地址是 xxx.xxx.xxx.xxx。

当然了,也可以通过 IP 地址直接访问服务器,但不方便记忆,所以就有了域名系统。一个好的域名可以卖好多好多钱,像 javabetter.cn 这个域名,一年需要 39 块钱。

域名到 IP 之间的映射,就需要 DNS 来完成。

我来说说 DNS 的解析过程吧:

三分析面渣逆袭:DNS 解析流程

假设我们在浏览器地址栏里键入了 paicoding.com:

浏览器会首先检查自己的缓存中是否有这个域名对应的 IP 地址,如果有,直接返回;如果没有,进入下一步。

检查本地 DNS 缓存是否有该域名的记录。如果没有,向根域名服务器发送请求,根域名服务器将请求指向更具体的服务,如 com 顶级域名服务器。

顶级域名服务器再将请求指向权限域名服务器,通常由域名注册机构直接管理,paicoding.com是在阿里云上注册的,所以阿里云会提供对应的 DNS 解析服务,将域名和阿里云服务器绑定起来。

最终,浏览器使用获得的 IP 地址发起一个 HTTP 请求到目标服务器,然后该服务器返回所请求的网页内容。

HTTP 2.0和3.0 的区别

HTTP/2.0 基于 TCP 协议,而 HTTP/3.0 则基于 QUIC 协议,Quick UDP Connections,直译为快速 UDP 网络连接。

三分恶面渣逆袭:HTTP 协议变迁

基于 TCP 的 HTTP/2.0,尽管从逻辑上来说,不同的流之间相互独立,不会相互影响,但在实际传输的过程中,数据还是要一帧一帧的发送和接收,一旦某一个流的数据有丢包,仍然会阻塞在它之后传输的流数据。

而基于 UDP 的 QUIC 协议可以更彻底地解决这样的问题,让不同的流之间真正的实现相互独立传输,互不干扰。

同时,QUIC 协议在传输的过程中就完成了 TLS 加密握手,更直接了。

说说Spring的Bean实例化方式

Spring 提供了 4 种不同的方式来实例化 Bean,以满足不同场景下的需求。

说说构造方法的方式

在类上使用@Component(或@Service、@Repository 等特定于场景的注解)标注类,然后通过构造方法注入依赖。

@Component
public class ExampleBean {
    private DependencyBean dependency;

    @Autowired
    public ExampleBean(DependencyBean dependency) {
        this.dependency = dependency;
    }
}

说说静态工厂的方式

在这种方式中,Bean 是由一个静态方法创建的,而不是直接通过构造方法。

public class ClientService {
    private static ClientService clientService = new ClientService();

    private ClientService() {}

    public static ClientService createInstance() {
        return clientService;
    }
}

说说实例工厂方法实例化的方式

与静态工厂方法相比,实例工厂方法依赖于某个类的实例来创建 Bean。这通常用在需要通过工厂对象的非静态方法来创建 Bean 的场景。

public class ServiceLocator {
    public ClientService createClientServiceInstance() {
        return new ClientService();
    }
}

说说 FactoryBean 接口实例化方式

FactoryBean 是一个特殊的 Bean 类型,可以在 Spring 容器中返回其他对象的实例。通过实现 FactoryBean 接口,可以自定义实例化逻辑,这对于构建复杂的初始化逻辑非常有用。

public class ToolFactoryBean implements FactoryBean<Tool{
    private int factoryId;
    private int toolId;

    @Override
    public Tool getObject() throws Exception {
        return new Tool(toolId);
    }

    @Override
    public Class<?> getObjectType() {
        return Tool.class;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }

    // setter and getter methods for factoryId and toolId
}

参考链接

  • 三分恶的面渣逆袭:https://javabetter.cn/sidebar/sanfene/nixi.html
  • 二哥的 Java 进阶之路:https://javabetter.cn

ending

一个人可以走得很快,但一群人才能走得更远。二哥的编程星球已经有 5000 多名球友加入了,如果你也需要一个良好的学习环境,戳链接 🔗 加入我们吧。这是一个编程学习指南 + Java 项目实战 + LeetCode 刷题的私密圈子,你可以阅读星球专栏、向二哥提问、帮你制定学习计划、和球友一起打卡成长。

两个置顶帖「球友必看」和「知识图谱」里已经沉淀了非常多优质的学习资源,相信能帮助你走的更快、更稳、更远

欢迎点击左下角阅读原文了解二哥的编程星球,这可能是你学习求职路上最有含金量的一次点击。

最后,把二哥的座右铭送给大家:没有什么使我停留——除了目的,纵然岸旁有玫瑰、有绿荫、有宁静的港湾,我是不系之舟。共勉 💪。

浏览 121
10点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
10点赞
评论
收藏
分享

手机扫一扫分享

举报