前端web方向面试题

基础题

  • HTML5有什么新特性、新标签?CSS3(CSS4)有什么新特性?

  • CSS中的选择器有哪些?它们权重(优先级)的顺序是什么?

  • 如何理解CSS中的盒子模型?Flex如何布局?

  • less、sass、stylus三者的区别?

  • ECMAScript2015~2019有什么新特性?

  • Javascript的基本数据类型有哪些?Set、Map的区别是什么?

  • 为什么要组件化(模块化)开发?如何实现组件化(模块化)开发?

  • 详细介绍Javascript、Typescript、React、Vue、微信、Flutter、IOS、Android开发?(选其中2项详细介绍)

  • HTTP常见的状态码、请求头有哪些?RESTful常用方法有哪些?

  • Git常用命令有哪些?webpack如何配置?

  • 一个常见的前后端交互发生了什么?比如访问:https://qq.com,越详细越好。

  • CSS、HTML、Javascript如何性能优化?

  • React、Vue如何实现SSR(服务端渲染)?

  • 是否了解nodejs、Java开发?熟悉哪些方面?

  • 如何实现分页、菜单级联如何实现?后端同学如何提供数据?(详见视频)

加分题

  • 平时喜欢读哪些技术方面的书籍?前端方面、其他技术栈或是其他?

Java方向面试题

基础题

  1. Java 9及以上版本有什么新特性?

  2. 是否熟悉前端ES6(ECMAScript)、Typescript、React、Vue、微信、Flutter、IOS、Android开发?(选其中2项详细介绍)

  3. 面向对象的特征有哪些?抽象类和接口有什么区别?

  4. Java如何进行异常处理?关键字:throws、throw、try、catch、finally分别如何使用?

  5. Java有哪些方式实现网络通信?

  6. 主键、唯一索引、索引、联合索引的区别是什么?左连接、右连接、内连接、外连接的区别是什么?工作中是如何优化SQL查询的?

  7. Mybtais中#$有什么区别?Mybatis元素(标签)如何实现1..1(一对一)1..N(一对多)映射查询?

  8. Git常用命令有哪些?为了减少应用的大小,Maven如何排除多级依赖中不使用的Jar包?

  9. 如何理解Spring的依赖注入、控制反转?Spring Framework包含哪些内容?

  10. Spring MVC注解@RequestBoby、@ResponseBody有什么区别?什么注解可以校验用户输入?如何接收用户文件上传?

  11. HTTP常见的状态码、请求头有哪些?RESTful API方法有哪些?Spring MVC注解如何实现RESTful API?

  12. Spring Cloud是通过哪些组件治理微服务的?工作中如何使用Spring Cloud?

  13. 描述一下Redis有哪些数据结构?工作中是如何使用的?

  14. 熟悉哪些设计模式?选择3个,介绍一下他们适合哪些场景?

  15. Kafka(或其他消息中间件)可以做什么?工作中是如何使用的?

  16. 是否熟悉Elastic Stack?简单介绍一下它们的作用?

加分题

  1. 平时喜欢读哪些技术方面的书籍?Java方面、其他技术栈、区块链、人工智能或是其他?

  2. 是否熟悉Kubernetes、Istio?Kubernetes、Istio包含什么组件,组件之间是如何工作的?

Spring-Boot-with-Docker

本文介绍如何构建Spring Boot应用的Docker镜像。Docker是一个具有“社交”特性的linux容器管理的工具箱,允许用户发布和使用他人发布的容器镜像,一个Docker镜像就是一个容器化进程,本文介绍如何构建一个Spring Boot应用镜像。

基本的Dockerfile

一个Spring Boot应用很容易制作一个可执行的JAR文件,比如Maven可以使用mvn install,Gradle可以使用gradle build构建,制作可执行JAR的一个基本的Dockerfile类似这样,文件放在项目的顶级目录:

1
2
3
4
5
FROM openjdk:8-jdk-alpine
VOLUME /tmp
ARG JAR_FILE
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar","/app.jar"]

JAR_FILE可以作为docker命令的一部分作为参数传入(对于Maven、Gradle是不同的),比如对于Maven:

$ docker build --build-arg=target/*.jar -t myorg/myapp .

对于Gradle:

$ docker build --build-arg=build/libs/*.jar -t myorg/myapp .

当然,一旦确定了构建系统,你可以不需要ARG,直接硬编码。比如对于Maven:

1
2
3
4
FROM openjdk:8-jdk-alpine
VOLUME /tmp
COPY target/*.jar app.jar
ENTRYPOINT ["java","-jar","/app.jar"]

然后可以简化构建命令:

$ docker build -t myorg/myapp .

运行镜像:

1
2
3
4
5
6
7
8
9
10
11
$ docker -p 8080:8080 myorg/myapp
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.1.6.RELEASE)

2019-11-18 03:52:01.912 INFO 1 --- [ main] hello.Application : Starting Application v0.1.0 on 12cf47053074 with PID 1 (/app.jar started by root in /)
...

如果你想看看镜像的内部结构,可以执行:

1
2
3
4
5
docker run -ti --entrypoint /bin/sh myorg/myapp
/ # ls
app.jar dev home media opt root sbin sys usr
bin etc lib mnt proc run srv tmp var
/ #

Entry Point

Dockerfile的执行方式使用ENTRYPOINT而没有使用shell打包java进程,这样做的好处是java进程可以响应KILL信号指令,比如在本例中使用CTRL-C中止进程。如果ENTRYPOINT的命令比较长,可以单独制作一个shell脚本,并将脚本拷贝到镜像:

1
2
3
4
5
FROM openjdk:8-jdk-alpine
VOLUME /tmp
COPY run.sh .
COPY target/*.jar app.jar
ENTRYPOINT ["run.sh"]

run.sh

1
2
#!/bin/sh
exec java -jar /app.jar

ENTRYPOINT还可以注入环境变量,比如加入运行时java命令行参数:

1
2
3
4
5
FROM openjdk:8-jdk-alpine
VOLUME /tmp
ARG JAR_FILE
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["sh", "-c", "java ${JAVA_OPTS} -jar /app.jar"]
$ docker build --build-arg JAR_FILE=./target/gs-spring-boot-docker-0.1.0.jar  -t ljun51/docker .
$ docker run -p 8080:8080 -e "JAVA_OPTS=-Ddebug -Xmx128m" ljun51/docker

上面的示例以Spring Boot的-Ddebug参数输出DEBUG日志。

上面的示例使用ENTRYPOINT并带有明确的shell可以传递环境变量的参数给java command,但是不能传递命令行参数给Spring Boot应用。下面这样修改端口不会生效:

$ docker run -p 9000:9000 ljun51/docker --server.port=9000

不生效的原因是docker命令的--server.port=9000部分传给了ENTRYPOINT(sh),而没有传给它启动的java进程。要修复这个问题可以通过添加CMD

1
2
3
4
5
FROM openjdk:8-jdk-alpine
VOLUME /tmp
ARG JAR_FILE
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["sh", "-c", "java ${JAVA_OPTS} -jar /app.jar ${0} ${@}"]
$ docker build --build-arg JAR_FILE=./target/gs-spring-boot-docker-0.1.0.jar  -t ljun51/docker .
$ docker run -p 9000:9000 ljun51/docker --server.port=9000

${0}表示“command”(第一个参数),${@}表示“command arguments”(命令行其他参数)。如果使用shell脚本,则不需要${0}.run.sh:

1
2
#!/bin/sh
exec java ${JAVA_OPTS} -jar /app.jar ${@}

到目前为止,docker配置都比较简单,生成的镜像也不是非常高效。docker镜像在JAR中打包了一个单独的文件系统层,它的大小在10MB以上,对于某些应用甚至50MB以上,我们可以通过分离成多层来改进。

使用工具构建镜像

编写一个基本的Spring Boot应用

创建一个简单的应用,src/main/java/hello/Application.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package hello;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
@RestController
public class Application {

@RequestMapping("/")
public String home() {
return "Hello Docker World";
}

public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}

}

现在可以运行这个应用,而不必有Docker容器,使用Gradle:

./gradlew build && java -jar build/libs/gs-spring-boot-docker-0.1.0.jar

或是用Maven:

./mvnw package && java -jar target/gs-spring-boot-docker-0.1.0.jar

访问localhost:8080会返回”Hello Docker World”。

容器化应用

Docker使用Dockerfile文件格式指定镜像的“layers”,在Spring Boot工程的顶级目录下创建一个Dockerfile文件,文件名就叫Dockerfile

1
2
3
4
5
FROM openjdk:8-jdk-alpine
VOLUME /tmp
ARG JAR_FILE
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]

这个Dockerfile非常简单但是包含了运行Spring Boot应用需要的内容:Java和JAR文件。项目JAR文件被COPY到容器中,并叫”app.jar”,然后执行ENTRYPOINT,没有shell包裹java进程。

文件中添加了一个指向”/tmp”的VOLUME,是因为默认情况下Spring Boot应用在该目录中创建工作目录。实际结果是在主机上的“/var/lib/docker”下创建一个临时文件,并将其链接到“/tmp”下的容器。对于我们在此处编写的简单应用程序,此步骤是可选的,但对于其他Spring Boot应用程序,如果它们需要实际在文件系统中进行写操作,则可能是必需的。

为减少Tomcat启动时间,添加了一个系统属性指向了"/dev/urandom"作为熵的来源,如果使用的是较新的Spring Boot或Tomcat的标准版本,这不是必须的。

为了利用Spring Boot胖JAR文件中的依赖项和应用程序资源之间的明确分割,我们将只要稍微不同的Dockerfile实现:

1
2
3
4
5
6
7
FROM openjdk:8-jdk-alpine
VOLUME /tmp
ARG DEPENDENCY=target/dependency
COPY ${DEPENDENCY}/BOOT-INF/lib /app/lib
COPY ${DEPENDENCY}/META-INF /app/META-INF
COPY ${DEPENDENCY}/BOOT-INF/classes /app
ENTRYPOINT ["java","-cp","app:app/lib/*","hello.Application"]

使用Maven构建Docker镜像

在Maven的pom.xml新增插件信息,更多信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<properties>
<docker.image.prefix>ljun51</docker.image.prefix>
</properties>
<build>
<plugins>
<plugin>
<groupId>com.spotify</groupId>
<artifactId>dockerfile-maven-plugin</artifactId>
<version>1.4.9</version>
<configuration>
<repository>${docker.image.prefix}/${project.artifactId}</repository>
</configuration>
</plugin>
</plugins>
</build>

该配置指定一项强制性的内容:有镜像名的仓库,镜像以ljun51/gs-spring-boot-docker命名。

其他可选属性:

  • 解压的fat jar的目录名,作为构建docker镜像的参数可以通过<buildArgs/>插件配置指定。
  • 镜像标签,如果未指定默认使用”latest”,可以通过<tag/>元素设置。

为了确保docker镜像创建之前JAR包被解压,添加下面的插件依赖配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>unpack</id>
<phase>package</phase>
<goals>
<goal>unpack</goal>
</goals>
<configuration>
<artifactItems>
<artifactItem>
<groupId>${project.groupId}</groupId>
<artifactId>${project.artifactId}</artifactId>
<version>${project.version}</version>
</artifactItem>
</artifactItems>
</configuration>
</execution>
</executions>
</plugin>

使用命令行构建docker镜像:

$ ./mvnw install dockerfile:build

推送镜像到dockhub,./mvnw dockerfile:push。Maven运行install时自动推送镜像的配置:

1
2
3
4
5
6
7
8
9
10
<executions>
<execution>
<id>default</id>
<phase>install</phase>
<goals>
<goal>build</goal>
<goal>push</goal>
</goals>
</execution>
</executions>

使用Gradle构建Docker镜像

如果使用Gradle需要这样添加插件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
buildscript {
...
dependencies {
...
classpath('gradle.plugin.com.palantir.gradle.docker:gradle-docker:0.13.0')
}
}

group = 'ljun51'

...
apply plugin: 'com.palantir.docker'

task unpack(type: Copy) {
dependsOn bootJar
from(zipTree(tasks.bootJar.outputs.files.singleFile))
into("build/dependency")
}
docker {
name "${project.group}/${bootJar.baseName}"
copySpec.from(tasks.unpack.outputs).into("dependency")
buildArgs(['DEPENDENCY': "dependency"])
}

这个配置说明4个事情:

  • 解压fat jar文件
  • 创建的镜像名为ljun51/gs-spring-boot-docker
  • 解压jar file的位置,可以使用硬编码
  • 指向jar file的构建参数

使用Gradle构建docker镜像并推送到dockerhub:

$ ./gradlew build docker

如果没有dockerhub的账号,推送应该会报错;推送的的步骤不是必须的,即使没有推送也是可以使用docker运行的:

1
2
3
4
$ docker run -p 8080:8080 -t ljun51/gs-spring-boot-docker
....
2015-03-31 13:25:48.035 INFO 1 --- [ main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http)
2015-03-31 13:25:48.037 INFO 1 --- [ main] hello.Application : Started Application in 5.613 seconds (JVM running for 7.293)

查看正在运行的docker容器:

1
2
3
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
01cae1671836 ljun51/gs-spring-boot-docker "java -cp app:app/li…" 22 seconds ago Up 21 seconds 0.0.0.0:8080->8080/tcp elated_cori

通过上面的容器ID停止运行:

1
2
$ docker stop 01cae1671836
01cae1671836

使用Spring Profiles

使用Spring配置文件运行刚创建的Docker镜像和将环境变量传递给Docker run命令一样比较容易:

$ docker run -e "SPRING_PROFILES_ACTIVE=prod" -p 8080:8080 -t ljun51/gs-spring-boot-docker

$ docker run -e "SPRING_PROFILES_ACTIVE=dev" -p 8080:8080 -t ljun51/gs-spring-boot-docker

在Docker容器中调试应用

可以使用JPDA Transport像调试远程服务一样。使用JAVA_OPTS环境变量传递java agent设置启用这个功能,映射agent端口到本机。使用Docker for Mac会有一些限制,可以通过一些黑魔法解决。

$ docker run -e "JAVA_OPTS=-agentlib:jdwp=transport=dt_socket,address=5005,server=y,suspend=n" -p 8080:8080 -p 5005:5005 -t ljun51/gs-spring-boot-docker

参考:

  1. Spring Boot with Docker
  2. Spring Boot Docker

kubernetes

kube-apiserver

kube-apiserver是Kubernetes最重要的核心组件之一。

  • 提供集群管理的REST API接口,包括认证授权、数据校验及集群状态变更等。
  • 提供与其他组件之间的数据交换(只有API Server才能直接操作etcd)

OpenAPI和Swagger定义

kube-apiserver使用OpenAPI记录了完整的API详细信息。从Kubernetes 1.10开始使用/openapi/v2定义资源接口。

1
2
kubectl proxy --port=8080
curl http://127.0.0.1:8080/openapi/v2 | python -m json.tool

直接访问kubernetes REST API

kube-apiserver支持https(默认6443端口)和http(监听127.0.0.1的8080端口),两个接口提供的REST API格式相同,
参考Kubernetes API Reference查看所有API的调用格式。

kubernetes api结构
(图片来自 OpenShift Blog)

使用kubectl访问

1
2
3
4
5
6
7
kubectl get --raw /api/

{
"versions": [
"v1"
]
}
1
2
kubectl get --raw /apis/batch/v1
kubectl get --raw /apis/node.k8s.io/v1beta1

使用kubectl proxy访问

下面的命令以反向代理的模式运行kubectl,它负责定位apiserver并进行身份验证:

1
kubectl proxy --port=8080
1
2
3
4
5
6
7
curl http://localhost:8080/api/

{
"versions": [
"v1"
]
}
1
curl http://localhost:8080/api/v1

编写资源文件

Kubernetes可以使用资源文件管理资源。但是我们定义资源配置清单时,apiVersion、kind、metadata尽管有章可循,但spec字段对于不同的资源来说却是千差万别,因此用户需要参考Kubernetes API的参考文档来了解各种可用属性字段。好在Kubernetes内建系统提供了部分文档可以参考,可以使用kubectl explain获取相关帮助,它给出相关对象的下一级文档。比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
$ kubectl explain pods
KIND: Pod
VERSION: v1

DESCRIPTION:
Pod is a collection of containers that can run on a host. This resource is
created by clients and scheduled onto hosts.

FIELDS:
apiVersion <string>
APIVersion defines the versioned schema of this representation of an
object. Servers should convert recognized schemas to the latest internal
value, and may reject unrecognized values. More info:
https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources

kind <string>
Kind is a string value representing the REST resource this object
represents. Servers may infer this from the endpoint the client submits
requests to. Cannot be updated. In CamelCase. More info:
https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds

metadata <Object>
Standard object's metadata. More info:
https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata

spec <Object>
Specification of the desired behavior of the pod. More info:
https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status

status <Object>
Most recently observed status of the pod. This data may not be up to date.
Populated by the system. Read-only. More info:
https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status

要了解二级对象或三级对象的资源时:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ kubectl explain pods.spec
KIND: Pod
VERSION: v1

RESOURCE: spec <Object>

DESCRIPTION:
Specification of the desired behavior of the pod. More info:
https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status

PodSpec is a description of a pod.

FIELDS:
...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ kubectl explain pods.spec.containers
KIND: Pod
VERSION: v1

RESOURCE: containers <[]Object>

DESCRIPTION:
List of containers belonging to the pod. Containers cannot currently be
added or removed. There must be at least one container in a Pod. Cannot be
updated.

A single application container that you want to run within a pod.

FIELDS:
...

还有一种方式可以快速定义资源文件,即参考现有的资源配置。比如:

1
kubectl get pods open-user-74b8b67bc9-kgkxx -o yaml --export > pods-demo.yaml

Kubernetes认证授权

使用kubeadm部署的Kubernetes集群默认提供了拥有集群管理权限的kubeconfig配置文件/etc/kubernetes/admin.conf,它可以复制到任何拥有kubectl的主机上管理集群。管理员可以创建其他账号授予非管理员管理部分资源,其配置过程可以分为两部分:

  1. 为用户创建专用私钥及证书

  2. 将步骤1的配置置于kubeconfig文件中
    下面给出创建过程,创建了一个kubernetes-ljun51用户,(需要以root身份):

  3. 为目标用户kubernetes-ljun51创建专用私钥及证书,保存在/etc/kubernetes/pki目录

    1. 生成私钥文件

      1
      2
      # cd /etc/kubernetes/kpi
      # (umask 077; openssl genrsa -out kubernetes-ljun51.key 2048)
    2. 创建证书签署请求,-subj选项中的CN的值将作为用户名,O的值作为用户组

      1
      2
      # openssl req -new -key kubernetes-ljun51.key -out kubernetes-ljun51.csr \
      -subj "/CN=kubernetes-ljun51/O=kubernetes-Users"
    3. 基于kubeadm安装坤儿呢特色集群时生成的CA签署证书,有效时长3650天

      1
      2
      # openssl x509 -req -in kubernetes-ljun51.csr -CA ca.crt -CAkey ca.key \
      -CAcreateserial -out kubernetes-ljun51.crt -days 3650
    4. 验证证书信息

      1
      # openssl x509 -in kubernetes-ljun51.crt -text -noout
  4. 以默认管理员kubenetes-admin@kubernetes为新建的kubernetes-ljun51设定kube-config配置文件,配置结果将默认保存于当前系统用户的./kube/config文件中,也可以使用kubectl –kubeconfig选项指定自定义的专用文件路径。

    1. 配置集群信息,包括集群名称、API Server URL和CA证书。若集群已经存在可省略此步,另外,提供的新配置不能与现有配置中的集群名称相同,否则会覆盖:

      1
      2
      3
      $ k config set-cluster abc12366 --embed-certs=true \
      --certificate-authority=/etc/kubernetes/pki/ca.crt \
      --server="https://118.118.116.142:6443"
    2. 配置客户端证书及密钥,用户名信息会通过命令从证书Subject的CN值中自动提取,例如前面创建csr时使用的”CN=kubernetes-ljun51”,而组名则来自于”O=kubernetes-Users”的定义:

      1
      2
      3
      $ sudo kubectl config set-credentials kubernetes-ljun51 --embed-certs=true \
      --client-certificate=/etc/kubernetes/pki/kubernetes-ljun51.crt \
      --client-key=/etc/kubernetes/pki/kubernetes-ljun51.key
    3. 配置上下文,用来组合cluster和credentials,即访问的集群上下文。如果为管理了多个集群而设置了多个环境,则可以使用use-context切换:

      1
      $ k config set-context kubernetes-ljun51@abc12366 --cluster=abc12366 --user=kubernetes-ljun51
    4. 指定要使用的上下问,切换为以kubernetes-ljun51访问集群:

      1
      $ k config use-context kubernetes-ljun51@abc12366
    5. 测试访问集群资源,不过在启用RBAC的集群上没有访问权限:

      1
      $ k get pods

      若需要切换至管理账号,可使用kubectl config use-context kubernetes-admin@kubernetes命令,临时使用可使用kubectl --context=kubernetes-ljun51@abc12366 get pods命令.