Java全栈知识点问题汇总(下)-(二)

81 阅读1小时+

13 分布式

分布式相关。

13.1 一致性算法

什么是分布式系统的副本一致性?有哪些?

分布式系统通过副本控制协议,使得从系统外部读取系统内部各个副本的数据在一定的约束条件下相同,称之为副本一致性(consistency)。副本一致性是针对分布式系统而言的,不是针对某一个副本而言。

强一致性(strong consistency) :任何时刻任何用户或节点都可以读到最近一次成功更新的副本数据。强一致性是程度最高的一致性要求,也是实践中最难以实现的一致性。

单调一致性(monotonic consistency) :任何时刻,任何用户一旦读到某个数据在某次更新后的值,这个用户不会再读到比这个值更旧的值。单调一致性是弱于强一致性却非常实用的一种一致性级别。因为通常来说,用户只关心从己方视角观察到的一致性,而不会关注其他用户的一致性情况。

会话一致性(session consistency) :任何用户在某一次会话内一旦读到某个数据在某次更新后的值,这个用户在这次会话过程中不会再读到比这个值更旧的值。会话一致性通过引入会话的概念,在单调一致性的基础上进一步放松约束,会话一致性只保证单个用户单次会话内数据的单调修改,对于不同用户间的一致性和同一用户不同会话间的一致性没有保障。实践中有许多机制正好对应会话的概念,例如php 中的session 概念。

最终一致性(eventual consistency) :最终一致性要求一旦更新成功,各个副本上的数据最终将达 到完全一致的状态,但达到完全一致状态所需要的时间不能保障。对于最终一致性系统而言,一个用户只要始终读取某一个副本的数据,则可以实现类似单调一致性的效果,但一旦用户更换读取的副本,则无法保障任何一致性。

弱一致性(week consistency) :一旦某个更新成功,用户无法在一个确定时间内读到这次更新的值,且即使在某个副本上读到了新的值,也不能保证在其他副本上可以读到新的值。弱一致性系统一般很难在实际中使用,使用弱一致性系统需要应用方做更多的工作从而使得系统可用。

在分布式系统中有哪些常见的一致性算法?

分布式算法 - 一致性Hash算法

  • 一致性Hash算法是个经典算法,Hash环的引入是为解决单调性(Monotonicity)的问题;虚拟节点的引入是为了解决平衡性(Balance)问题

分布式算法 - Paxos算法

  • Paxos算法是Lamport宗师提出的一种基于消息传递的分布式一致性算法,使其获得2013年图灵奖。自Paxos问世以来就持续垄断了分布式一致性算法,Paxos这个名词几乎等同于分布式一致性, 很多分布式一致性算法都由Paxos演变而来

分布式算法 - Raft算法

  • Paxos是出了名的难懂,而Raft正是为了探索一种更易于理解的一致性算法而产生的。它的首要设计目的就是易于理解,所以在选主的冲突处理等方式上它都选择了非常简单明了的解决方案

分布式算法 - ZAB算法

  • ZAB 协议全称:Zookeeper Atomic Broadcast(Zookeeper 原子广播协议), 它应该是所有一致性协议中生产环境中应用最多的了。为什么呢?因为他是为 Zookeeper 设计的分布式一致性协议!

谈谈你对一致性hash算法的理解?

判定哈希算法好坏的四个定义:

  • 平衡性(Balance): 平衡性是指哈希的结果能够尽可能分布到所有的缓冲中去,这样可以使得所有的缓冲空间都得到利用。很多哈希算法都能够满足这一条件。
  • 单调性(Monotonicity): 单调性是指如果已经有一些内容通过哈希分派到了相应的缓冲中,又有新的缓冲加入到系统中。哈希的结果应能够保证原有已分配的内容可以被映射到原有的或者新的缓冲中去,而不会被映射到旧的缓冲集合中的其他缓冲区。
  • 分散性(Spread): 在分布式环境中,终端有可能看不到所有的缓冲,而是只能看到其中的一部分。当终端希望通过哈希过程将内容映射到缓冲上时,由于不同终端所见的缓冲范围有可能不同,从而导致哈希的结果不一致,最终的结果是相同的内容被不同的终端映射到不同的缓冲区中。这种情况显然是应该避免的,因为它导致相同内容被存储到不同缓冲中去,降低了系统存储的效率。分散性的定义就是上述情况发生的严重程度。好的哈希算法应能够尽量避免不一致的情况发生,也就是尽量降低分散性。
  • 负载(Load): 负载问题实际上是从另一个角度看待分散性问题。既然不同的终端可能将相同的内容映射到不同的缓冲区中,那么对于一个特定的缓冲区而言,也可能被不同的用户映射为不同 的内容。与分散性一样,这种情况也是应当避免的,因此好的哈希算法应能够尽量降低缓冲的负荷。

什么是Paxos算法?如何实现的?

Paxos算法是Lamport宗师提出的一种基于消息传递的分布式一致性算法,使其获得2013年图灵奖。

  • 三个角色? 可以理解为人大代表(Proposer)在人大向其它代表(Acceptors)提案,通过后让老百姓(Learner)落实

Paxos将系统中的角色分为提议者 (Proposer)决策者 (Acceptor),和最终决策学习者 (Learner):

  1. Proposer: 提出提案 (Proposal)。Proposal信息包括提案编号 (Proposal ID) 和提议的值 (Value)。
  2. Acceptor: 参与决策,回应Proposers的提案。收到Proposal后可以接受提案,若Proposal获得多数Acceptors的接受,则称该Proposal被批准。
  3. Learner: 不参与决策,从Proposers/Acceptors学习最新达成一致的提案(Value)。

在多副本状态机中,每个副本同时具有Proposer、Acceptor、Learner三种角色。

  • 基于消息传递的3个阶段

  1. 第一阶段: Prepare阶段;Proposer向Acceptors发出Prepare请求,Acceptors针对收到的Prepare请求进行Promise承诺。

    1. Prepare: Proposer生成全局唯一且递增的Proposal ID (可使用时间戳加Server ID),向所有Acceptors发送Prepare请求,这里无需携带提案内容,只携带Proposal ID即可。

    2. Promise: Acceptors收到Prepare请求后,做出“两个承诺,一个应答”。

      1. 承诺1: 不再接受Proposal ID小于等于(注意: 这里是<= )当前请求的Prepare请求;
      2. 承诺2: 不再接受Proposal ID小于(注意: 这里是< )当前请求的Propose请求;
      3. 应答: 不违背以前作出的承诺下,回复已经Accept过的提案中Proposal ID最大的那个提案的Value和Proposal ID,没有则返回空值。
  2. 第二阶段: Accept阶段; Proposer收到多数Acceptors承诺的Promise后,向Acceptors发出Propose请求,Acceptors针对收到的Propose请求进行Accept处理。

    1. Propose: Proposer 收到多数Acceptors的Promise应答后,从应答中选择Proposal ID最大的提案的Value,作为本次要发起的提案。如果所有应答的提案Value均为空值,则可以自己随意决定提案Value。然后携带当前Proposal ID,向所有Acceptors发送Propose请求。
    2. Accept: Acceptor收到Propose请求后,在不违背自己之前作出的承诺下,接受并持久化当前Proposal ID和提案Value。
  3. 第三阶段: Learn阶段; Proposer在收到多数Acceptors的Accept之后,标志着本次Accept成功,决议形成,将形成的决议发送给所有Learners。

什么是Raft算法?

不同于Paxos算法直接从分布式一致性问题出发推导出来,Raft算法则是从多副本状态机的角度提出。Raft实现了和Paxos相同的功能,它将一致性分解为多个子问题: Leader选举(Leader election)、日志同步(Log replication)、安全性(Safety)、日志压缩(Log compaction)、成员变更(Membership change)等。同时,Raft算法使用了更强的假设来减少了需要考虑的状态,使之变的易于理解和实现

  • 三个角色

Raft将系统中的角色分为领导者(Leader)跟从者(Follower)候选人(Candidate):

  1. Leader: 接受客户端请求,并向Follower同步请求日志,当日志同步到大多数节点上后告诉Follower提交日志。
  2. Follower: 接受并持久化Leader同步的日志,在Leader告之日志可以提交之后,提交日志。
  3. Candidate: Leader选举过程中的临时角色。

Raft要求系统在任意时刻最多只有一个Leader,正常工作期间只有Leader和Followers。

  • 以子问题Leader选举为例?

Raft 使用心跳(heartbeat)触发Leader选举。当服务器启动时,初始化为Follower。Leader向所有Followers周期性发送heartbeat。如果Follower在选举超时时间内没有收到Leader的heartbeat,就会等待一段随机的时间后发起一次Leader选举。

Follower将其当前term加一然后转换为Candidate。它首先给自己投票并且给集群中的其他服务器发送 RequestVote RPC (RPC细节参见八、Raft算法总结)。结果有以下三种情况:

  • 赢得了多数的选票,成功选举为Leader;
  • 收到了Leader的消息,表示有其它服务器已经抢先当选了Leader;
  • 没有服务器赢得多数的选票,Leader选举失败,等待选举时间超时后发起下一次选举。

选举出Leader后,Leader通过定期向所有Followers发送心跳信息维持其统治。若Follower一段时间未收到Leader的心跳则认为Leader可能已经挂了,再次发起Leader选举过程。

13.2 全局唯一ID

全局唯一ID有哪些实现方案?

常见的分布式ID生成方式,大致分类的话可以分为两类:

  1. 一种是类DB型的,根据设置不同起始值和步长来实现趋势递增,需要考虑服务的容错性和可用性;
  2. 另一种是类snowflake型,这种就是将64位划分为不同的段,每段代表不同的涵义,基本就是时间戳、机器ID和序列数。这种方案就是需要考虑时钟回拨的问题以及做一些 buffer的缓冲设计提高性能。

数据库方式实现方案?有什么缺陷?

  • MySQL为例

我们将分布式系统中数据库的同一个业务表的自增ID设计成不一样的起始值,然后设置固定的步长,步长的值即为分库的数量或分表的数量。

以MySQL举例,利用给字段设置auto_increment_incrementauto_increment_offset来保证ID自增。

  1. auto_increment_offset:表示自增长字段从那个数开始,他的取值范围是1 .. 65535。
  2. auto_increment_increment:表示自增长字段每次递增的量,其默认值是1,取值范围是1 .. 65535。

缺点也很明显,首先它强依赖DB,当DB异常时整个系统不可用。虽然配置主从复制可以尽可能的增加可用性,但是数据一致性在特殊情况下难以保证。主从切换时的不一致可能会导致重复发号。还有就是ID发号性能瓶颈限制在单台MySQL的读写性能

  • 使用redis实现

Redis实现分布式唯一ID主要是通过提供像 INCRINCRBY 这样的自增原子命令,由于Redis自身的单线程的特点所以能保证生成的 ID 肯定是唯一有序的。

但是单机存在性能瓶颈,无法满足高并发的业务需求,所以可以采用集群的方式来实现。集群的方式又会涉及到和数据库集群同样的问题,所以也需要设置分段和步长来实现。

为了避免长期自增后数字过大可以通过与当前时间戳组合起来使用,另外为了保证并发和业务多线程的问题可以采用 Redis + Lua的方式进行编码,保证安全。

Redis 实现分布式全局唯一ID,它的性能比较高,生成的数据是有序的,对排序业务有利,但是同样它依赖于redis,需要系统引进redis组件,增加了系统的配置复杂性

当然现在Redis的使用性很普遍,所以如果其他业务已经引进了Redis集群,则可以资源利用考虑使用Redis来实现。

雪花算法如何实现的?

Snowflake,雪花算法是由Twitter开源的分布式ID生成算法,以划分命名空间的方式将 64-bit位分割成多个部分,每个部分代表不同的含义。而 Java中64bit的整数是Long类型,所以在 Java 中 SnowFlake 算法生成的 ID 就是 long 来存储的。

  • 第1位占用1bit,其值始终是0,可看做是符号位不使用。
  • 第2位开始的41位是时间戳,41-bit位可表示2^41个数,每个数代表毫秒,那么雪花算法可用的时间年限是(1L<<41)/(1000L360024*365)=69 年的时间。
  • 中间的10-bit位可表示机器数,即2^10 = 1024台机器,但是一般情况下我们不会部署这么台机器。如果我们对IDC(互联网数据中心)有需求,还可以将 10-bit 分 5-bit 给 IDC,分5-bit给工作机器。这样就可以表示32个IDC,每个IDC下可以有32台机器,具体的划分可以根据自身需求定义。
  • 最后12-bit位是自增序列,可表示2^12 = 4096个数。

这样的划分之后相当于在一毫秒一个数据中心的一台机器上可产生4096个有序的不重复的ID。但是我们 IDC 和机器数肯定不止一个,所以毫秒内能生成的有序ID数是翻倍的。

雪花算法有什么问题?有哪些解决思路?

  • 有哪些问题
  1. 时钟回拨问题;
  2. 趋势递增,而不是绝对递增;
  3. 不能在一台服务器上部署多个分布式ID服务;
  • 如何解决时钟回拨

以百度的UidGenerator为例,CachedUidGenerator方式主要通过采取如下一些措施和方案规避了时钟回拨问题和增强唯一性:

  1. 自增列:UidGenerator的workerId在实例每次重启时初始化,且就是数据库的自增ID,从而完美的实现每个实例获取到的workerId不会有任何冲突。
  2. RingBuffer:UidGenerator不再在每次取ID时都实时计算分布式ID,而是利用RingBuffer数据结构预先生成若干个分布式ID并保存。
  3. 时间递增:传统的雪花算法实现都是通过System.currentTimeMillis()来获取时间并与上一次时间进行比较,这样的实现严重依赖服务器的时间。而UidGenerator的时间类型是AtomicLong,且通过incrementAndGet()方法获取下一次的时间,从而脱离了对服务器时间的依赖,也就不会有时钟回拨的问题

(这种做法也有一个小问题,即分布式ID中的时间信息可能并不是这个ID真正产生的时间点,例如:获取的某分布式ID的值为3200169789968523265,它的反解析结果为{"timestamp":"2019-05-02 23:26:39","workerId":"21","sequence":"1"},但是这个ID可能并不是在"2019-05-02 23:26:39"这个时间产生的)。

13.3 分布式锁

有哪些方案实现分布式锁?

综合讲讲方案:

  • 使用场景

    • 需要保证一个方法在同一时间内只能被同一个线程执行
  • 实现方式:

    • 加锁和解锁
  • 方案,考虑因素(性能,稳定,实现难度,死锁)

    • 基于数据库做分布式锁--乐观锁(基于版本号)和悲观锁(基于排它锁)
    • 基于 redis 做分布式锁:setnx(key,当前时间+过期时间)和Redlock机制
    • 基于 zookeeper 做分布式锁:临时有序节点来实现的分布式锁,Curator
    • 基于 Consul 做分布式锁

基于数据库如何实现分布式锁?有什么缺陷?

  • 基于数据库表(锁表,很少使用)

最简单的方式可能就是直接创建一张锁表,然后通过操作该表中的数据来实现了。当我们想要获得锁的时候,就可以在该表中增加一条记录,想要释放锁的时候就删除这条记录。

为了更好的演示,我们先创建一张数据库表,参考如下:

CREATE TABLE database_lock (
    `id` BIGINT NOT NULL AUTO_INCREMENT,
    `resource` int NOT NULL COMMENT '锁定的资源',
    `description` varchar(1024) NOT NULL DEFAULT "" COMMENT '描述',
    PRIMARY KEY (id),
    UNIQUE KEY uiq_idx_resource (resource)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='数据库分布式锁表';

当我们想要获得锁时,可以插入一条数据:

INSERT INTO database_lock(resource, description) VALUES (1, 'lock');

当需要释放锁的时,可以删除这条数据:

DELETE FROM database_lock WHERE resource=1;
  • 基于悲观锁

悲观锁实现思路

  1. 在对任意记录进行修改前,先尝试为该记录加上排他锁(exclusive locking)。
  2. 如果加锁失败,说明该记录正在被修改,那么当前查询可能要等待或者抛出异常。 具体响应方式由开发者根据实际需要决定。
  3. 如果成功加锁,那么就可以对记录做修改,事务完成后就会解锁了。
  4. 其间如果有其他对该记录做修改或加排他锁的操作,都会等待我们解锁或直接抛出异常。

以MySQL InnoDB中使用悲观锁为例

要使用悲观锁,我们必须关闭mysql数据库的自动提交属性,因为MySQL默认使用autocommit模式,也就是说,当你执行一个更新操作后,MySQL会立刻将结果进行提交。set autocommit=0;

//0.开始事务
begin;/begin work;/start transaction; (三者选一就可以)
//1.查询出商品信息
select status from t_goods where id=1 for update;
//2.根据商品信息生成订单
insert into t_orders (id,goods_id) values (null,1);
//3.修改商品status为2
update t_goods set status=2;
//4.提交事务
commit;/commit work;

上面的查询语句中,我们使用了select…for update的方式,这样就通过开启排他锁的方式实现了悲观锁。此时在t_goods表中,id为1的 那条数据就被我们锁定了,其它的事务必须等本次事务提交之后才能执行。这样我们可以保证当前的数据不会被其它事务修改。

上面我们提到,使用select…for update会把数据给锁住,不过我们需要注意一些锁的级别,MySQL InnoDB默认行级锁。行级锁都是基于索引的,如果一条SQL语句用不到索引是不会使用行级锁的,会使用表级锁把整张表锁住,这点需要注意。

  • 基于乐观锁

乐观并发控制(又名“乐观锁”,Optimistic Concurrency Control,缩写“OCC”)是一种并发控制的方法。它假设多用户并发的事务在处理时不会彼此互相影响,各事务能够在不产生锁的情况下处理各自影响的那部分数据。在提交数据更新之前,每个事务会先检查在该事务读取数据后,有没有其他事务又修改了该数据。如果其他事务有更新的话,正在提交的事务会进行回滚。

以使用版本号实现乐观锁为例?

使用版本号时,可以在数据初始化时指定一个版本号,每次对数据的更新操作都对版本号执行+1操作。并判断当前版本号是不是该数据的最新的版本号。

1.查询出商品信息
select (status,status,version) from t_goods where id=#{id}
2.根据商品信息生成订单
3.修改商品status为2
update t_goods 
set status=2,version=version+1
where id=#{id} and version=#{version};

需要注意的是,乐观锁机制往往基于系统中数据存储逻辑,因此也具备一定的局限性。由于乐观锁机制是在我们的系统中实现的,对于来自外部系统的用户数据更新操作不受我们系统的控制,因此可能会造成脏数据被更新到数据库中。在系统设计阶段,我们应该充分考虑到这些情况,并进行相应的调整(如将乐观锁策略在数据库存储过程中实现,对外只开放基于此存储过程的数据更新途径,而不是将数据库表直接对外公开)。

  • 缺陷

对数据库依赖,开销问题,行锁变表锁问题,无法解决数据库单点和可重入的问题。

基于redis如何实现分布式锁?有什么缺陷?

  • 最基本的Jedis方案

加锁: set NX PX + 重试 + 重试间隔

向Redis发起如下命令: SET productId:lock 0xx9p03001 NX PX 30000 其中,"productId"由自己定义,可以是与本次业务有关的id,"0xx9p03001"是一串随机值,必须保证全局唯一(原因在后文中会提到),“NX"指的是当且仅当key(也就是案例中的"productId:lock”)在Redis中不存在时,返回执行成功,否则执行失败。"PX 30000"指的是在30秒后,key将被自动删除。执行命令后返回成功,表明服务成功的获得了锁。

@Override
public boolean lock(String key, long expire, int retryTimes, long retryDuration) {
    // use JedisCommands instead of setIfAbsense
    boolean result = setRedis(key, expire);
​
    // retry if needed
    while ((!result) && retryTimes-- > 0) {
        try {
            log.debug("lock failed, retrying..." + retryTimes);
            Thread.sleep(retryDuration);
        } catch (Exception e) {
            return false;
        }
​
        // use JedisCommands instead of setIfAbsense
        result = setRedis(key, expire);
    }
    return result;
}
​
private boolean setRedis(String key, long expire) {
    try {
        RedisCallback<String> redisCallback = connection -> {
            JedisCommands commands = (JedisCommands) connection.getNativeConnection();
            String uuid = SnowIDUtil.uniqueStr();
            lockFlag.set(uuid);
            return commands.set(key, uuid, NX, PX, expire); // 看这里
        };
        String result = redisTemplate.execute(redisCallback);
        return !StringUtil.isEmpty(result);
    } catch (Exception e) {
        log.error("set redis occurred an exception", e);
    }
    return false;
}

解锁: 采用lua脚本: 在删除key之前,一定要判断服务A持有的value与Redis内存储的value是否一致。如果贸然使用服务A持有的key来删除锁,则会误将服务B的锁释放掉。

if redis.call("get", KEYS[1])==ARGV[1] then
    return redis.call("del", KEYS[1])
else
    return 0
end
  • 基于RedLock实现分布式锁

假设有两个服务A、B都希望获得锁,有一个包含了5个redis master的Redis Cluster,执行过程大致如下:

  1. 客户端获取当前时间戳,单位: 毫秒
  2. 服务A轮寻每个master节点,尝试创建锁。(这里锁的过期时间比较短,一般就几十毫秒) RedLock算法会尝试在大多数节点上分别创建锁,假如节点总数为n,那么大多数节点指的是n/2+1。
  3. 客户端计算成功建立完锁的时间,如果建锁时间小于超时时间,就可以判定锁创建成功。如果锁创建失败,则依次(遍历master节点)删除锁。
  4. 只要有其它服务创建过分布式锁,那么当前服务就必须轮寻尝试获取锁。
  • 基于Redisson实现分布式锁

过程

  1. 线程去获取锁,获取成功: 执行lua脚本,保存数据到redis数据库。
  2. 线程去获取锁,获取失败: 订阅了解锁消息,然后再尝试获取锁,获取成功后,执行lua脚本,保存数据到redis数据库。

互斥

如果这个时候客户端B来尝试加锁,执行了同样的一段lua脚本。第一个if判断会执行“exists myLock”,发现myLock这个锁key已经存在。接着第二个if判断,判断myLock锁key的hash数据结构中,是否包含客户端B的ID,但明显没有,那么客户端B会获取到pttl myLock返回的一个数字,代表myLock这个锁key的剩余生存时间。此时客户端B会进入一个while循环,不听的尝试加锁。

watch dog自动延时机制

客户端A加锁的锁key默认生存时间只有30秒,如果超过了30秒,客户端A还想一直持有这把锁,怎么办?其实只要客户端A一旦加锁成功,就会启动一个watch dog看门狗,它是一个后台线程,会每隔10秒检查一下,如果客户端A还持有锁key,那么就会不断的延长锁key的生存时间。

可重入

每次lock会调用incrby,每次unlock会减一。

  • 方案比较
  1. 借助Redis实现分布式锁时,有一个共同的缺陷: 当获取锁被决绝后,需要不断的循环,重新发送获取锁(创建key)的请求,直到请求成功。这就造成空转,浪费宝贵的CPU资源。
  2. RedLock算法本身有争议,并不能保证健壮性。
  3. Redisson实现分布式锁时,除了将key新增到某个指定的master节点外,还需要由master自动异步的将key和value等数据同步至绑定的slave节点上。那么问题来了,如果master没来得及同步数据,突然发生宕机,那么通过故障转移和主备切换,slave节点被迅速升级为master节点,新的客户端加锁成功,旧的客户端的watch dog发现key存在,误以为旧客户端仍然持有这把锁,这就导致同时存在多个客户端持有同名锁的问题了。

基于zookeeper如何实现分布式锁?

说几个核心点:

  • 顺序节点

创建一个用于发号的节点“/test/lock”,然后以它为父亲节点的前缀为“/test/lock/seq-”依次发号:

  • 获得最小号得锁

由于序号的递增性,可以规定排号最小的那个获得锁。所以,每个线程在尝试占用锁之前,首先判断自己是排号是不是当前最小,如果是,则获取锁。

  • 节点监听机制

每个线程抢占锁之前,先抢号创建自己的ZNode。同样,释放锁的时候,就需要删除抢号的Znode。抢号成功后,如果不是排号最小的节点,就处于等待通知的状态。等谁的通知呢?不需要其他人,只需要等前一个Znode 的通知就可以了。当前一个Znode 删除的时候,就是轮到了自己占有锁的时候。第一个通知第二个、第二个通知第三个,击鼓传花似的依次向后。

13.4 分布式事务

什么是ACID?

一个事务有四个基本特性,也就是我们常说的(ACID):

  1. Atomicity(原子性) :事务是一个不可分割的整体,事务内所有操作要么全做成功,要么全失败。
  2. Consistency(一致性) :事务执行前后,数据从一个状态到另一个状态必须是一致的(A向B转账,不能出现A扣了钱,B却没收到)。
  3. Isolation(隔离性) : 多个并发事务之间相互隔离,不能互相干扰。
  4. Durability(持久性) :事务完成后,对数据库的更改是永久保存的,不能回滚。

分布式事务有哪些解决方案?

什么是分布式的XA协议?

XA协议是一个基于数据库分布式事务协议,其分为两部分:事务管理器本地资源管理器。事务管理器作为一个全局的调度者,负责对各个本地资源管理器统一号令提交或者回滚。二阶提交协议(2PC)三阶提交协议(3PC)就是根据此协议衍生出来而来。主流的诸如Oracle、MySQL等数据库均已实现了XA接口。

XA接口是双向的系统接口,在事务管理器(Transaction Manager)以及一个或多个资源管理器(Resource Manager)之间形成通信桥梁。也就是说,在基于XA的一个事务中,我们可以针对多个资源进行事务管理,例如一个系统访问多个数据库,或即访问数据库、又访问像消息中间件这样的资源。这样我们就能够实现在多个数据库和消息中间件直接实现全部提交、或全部取消的事务。XA规范不是java的规范,而是一种通用的规范

什么是2PC?

两段提交,顾名思义就是要进行两个阶段的提交:

  • 第一阶段,准备阶段(投票阶段);
  • 第二阶段,提交阶段(执行阶段)。

下面还拿下单扣库存举例子,简单描述一下两段提交(2PC)的原理:

之前说过业务服务化(SOA)以后,一个下单流程就会用到多个服务,各个服务都无法保证调用的其他服务的成功与否,这个时候就需要一个全局的角色(协调者)对各个服务(参与者)进行协调。

一个下单请求过来通过协调者,给每一个参与者发送Prepare消息,执行本地数据脚本但不提交事务。

如果协调者收到了参与者的失败消息或者超时,直接给每个参与者发送回滚(Rollback)消息;否则,发送提交(Commit)消息;参与者根据协调者的指令执行提交或者回滚操作,释放所有事务处理过程中被占用的资源,显然2PC做到了所有操作要么全部成功、要么全部失败。

两段提交(2PC)的缺点

二阶段提交看似能够提供原子性的操作,但它存在着严重的缺陷:

  • 网络抖动导致的数据不一致:第二阶段中协调者向参与者发送commit命令之后,一旦此时发生网络抖动,导致一部分参与者接收到了commit请求并执行,可其他未接到commit请求的参与者无法执行事务提交。进而导致整个分布式系统出现了数据不一致。
  • 超时导致的同步阻塞问题:2PC中的所有的参与者节点都为事务阻塞型,当某一个参与者节点出现通信超时,其余参与者都会被动阻塞占用资源不能释放。
  • 单点故障的风险:由于严重的依赖协调者,一旦协调者发生故障,而此时参与者还都处于锁定资源的状态,无法完成事务commit操作。虽然协调者出现故障后,会重新选举一个协调者,可无法解决因前一个协调者宕机导致的参与者处于阻塞状态的问题。

什么是3PC?

三段提交(3PC)是对两段提交(2PC)的一种升级优化,3PC在2PC的第一阶段和第二阶段中插入一个准备阶段。保证了在最后提交阶段之前,各参与者节点的状态都一致。同时在协调者和参与者中都引入超时机制,当参与者各种原因未收到协调者的commit请求后,会对本地事务进行commit,不会一直阻塞等待,解决了2PC的单点故障问题,但3PC还是没能从根本上解决数据一致性的问题。

3PC的三个阶段分别是CanCommit、PreCommit、DoCommit

  • CanCommit:协调者向所有参与者发送CanCommit命令,询问是否可以执行事务提交操作。如果全部响应YES则进入下一个阶段。
  • PreCommit:协调者向所有参与者发送PreCommit命令,询问是否可以进行事务的预提交操作,参与者接收到PreCommit请求后,如参与者成功的执行了事务操作,则返回Yes响应,进入最终commit阶段。一旦参与者中有向协调者发送了No响应,或因网络造成超时,协调者没有接到参与者的响应,协调者向所有参与者发送abort请求,参与者接受abort命令执行事务的中断。
  • DoCommit:在前两个阶段中所有参与者的响应反馈均是YES后,协调者向参与者发送DoCommit命令正式提交事务,如协调者没有接收到参与者发送的ACK响应,会向所有参与者发送abort请求命令,执行事务的中断。

什么是TCC?

TCC(Try-Confirm-Cancel)又被称补偿事务,TCC与2PC的思想很相似,事务处理流程也很相似,但2PC是应用于在DB层面,TCC则可以理解为在应用层面的2PC,是需要我们编写业务逻辑来实现

TCC它的核心思想是:"针对每个操作都要注册一个与其对应的确认(Try)和补偿(Cancel)"。

还拿下单扣库存解释下它的三个操作:

  • Try阶段:下单时通过Try操作去扣除库存预留资源。
  • Confirm阶段:确认执行业务操作,在只预留的资源基础上,发起购买请求。
  • Cancel阶段:只要涉及到的相关业务中,有一个业务方预留资源未成功,则取消所有业务资源的预留请求。

TCC的缺点

  • 应用侵入性强:TCC由于基于在业务层面,至使每个操作都需要有try、confirm、cancel三个接口。
  • 开发难度大:代码开发量很大,要保证数据一致性confirm和cancel接口还必须实现幂等性。

什么是SAGA方案?

13.5 分布式缓存

分布式系统中常用的缓存方案有哪些?

  • 客户端缓存:页面和浏览器缓存,APP缓存,H5缓存,localStorage和sessionStorage

  • CDN缓存:

    • 内存存储:数据的缓存
    • 内容分发:负载均衡
  • nginx缓存:本地缓存,外部缓存

  • 数据库缓存:持久层缓存(mybatis,hibernate多级缓存),Mysql查询缓存

  • 操作系统缓存:Page Cache,Buffer Cache

分布式系统缓存的更新模式?

  • Cache Aside模式
  1. 读取失效:cache数据没有命中,查询DB,成功后把数据写入缓存
  2. 读取命中:读取cache数据
  3. 更新:把数据更新到DB,失效缓存

// Read
data = cache.get(id);
if (data == null) {
    data = db.get(id);
    cache.put(id, data);
}
​
// Write
db.save(data);
cache.invalid(data.id);
  • Read/Write Through模式

缓存代理了DB读取、写入的逻辑,可以把缓存看成唯一的存储。

  • Write Back模式

这种模式下所有的操作都走缓存,缓存里的数据再通过异步的方式同步到数据库里面。所以系统的写性能能够大大提升了。

分布式系统缓存淘汰策略

缓存淘汰,又称为缓存逐出(cache replacement algorithms或者cache replacement policies),是指在存储空间不足的情况下,缓存系统主动释放一些缓存对象获取更多的存储空间。一般LRU用的比较多,可以重点了解一下。

  • FIFO 先进先出(First In First Out)是一种简单的淘汰策略,缓存对象以队列的形式存在,如果空间不足,就释放队列头部的(先缓存)对象。一般用链表实现。
  • LRU 最近最久未使用(Least Recently Used),这种策略是根据访问的时间先后来进行淘汰的,如果空间不足,会释放最久没有访问的对象(上次访问时间最早的对象)。比较常见的是通过优先队列来实现。
  • LFU 最近最少使用(Least Frequently Used),这种策略根据最近访问的频率来进行淘汰,如果空间不足,会释放最近访问频率最低的对象。这个算法也是用优先队列实现的比较常见。

更进一步的谈谈Redis缓存淘汰的8个模式,可以参考上文Redis问答部分。

13.6 分布式任务

Java中定时任务有哪些?如何演化的?

这里主要讲讲Java的定时任务是如何一步步发展而来的:

  • Timer
new Timer("testTimer").schedule(new TimerTask() {
    @Override
    public void run() {
        System.out.println("TimerTask");
    }
}, 1000,2000);

解释:1000ms是延迟启动时间,2000ms是定时任务周期,每2s执行一次

  • ScheduledExecutorService
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);
scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
    @Override
    public void run() {
        System.out.println("ScheduledTask");
    }
}, 1, 1, TimeUnit.SECONDS);

解释:延迟1s启动,每隔1s执行一次,是前一个任务开始时就开始计算时间间隔,但是会等上一个任务结束在开始下一个

  • SpringTask
@Service
public class SpringTask {
    private static final Logger log = LoggerFactory.getLogger(SpringTask.class);
​
    @Scheduled(cron = "1/5 * * * * *")
    public void task1(){
        log.info("springtask 定时任务!");
    }
    
    @Scheduled(initialDelay = 1000,fixedRate = 1*1000)
    public void task2(){
        log.info("springtask 定时任务!");
    }
}

解释:

  1. task1是每隔5s执行一次,{秒} {分} {时} {日期}
  2. task2是延迟1s,每隔1S执行一次
  • Quartz

quartz 是一个开源的分布式调度库,它基于java实现。

  1. Job 表示一个任务,要执行的具体内容。
  2. JobDetail 表示一个具体的可执行的调度程序,Job 是这个可执行程调度程序所要执行的内容,另外 JobDetail 还包含了这个任务调度的方案和策略。
  3. Trigger 代表一个调度参数的配置,什么时候去调。
  4. Scheduler 代表一个调度容器,一个调度容器中可以注册多个 JobDetail 和 Trigger。当 Trigger 与 JobDetail 组合,就可以被 Scheduler 容器调度了。
//创建调度器Schedule
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = schedulerFactory.getScheduler();
//创建JobDetail实例,并与HelloWordlJob类绑定
JobDetail jobDetail = JobBuilder.newJob(HelloWorldJob.class).withIdentity("job1", "jobGroup1")
        .build();
//创建触发器Trigger实例(立即执行,每隔1S执行一次)
Trigger trigger = TriggerBuilder.newTrigger()
        .withIdentity("trigger1", "triggerGroup1")
        .startNow()
        .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(1).repeatForever())
        .build();
//开始执行
scheduler.scheduleJob(jobDetail, trigger);
scheduler.start();

常见的JOB实现方案?

基于上面Java任务演化出分布式Job方案:

  • quartz

JDBCJobStore 支持集群所有触发器和job都存储在数据库中无论服务器停止和重启都可以恢复任务同时支持事务处理。

  • elastic-job

elastic-job 是由当当网基于quartz 二次开发之后的分布式调度解决方案 , 由两个相对独立的子项目Elastic-Job-Lite和Elastic-Job-Cloud组成 。

Elastic-Job-Lite定位为轻量级无中心化解决方案,使用jar包的形式提供分布式任务的协调服务。

Elastic-Job-Cloud使用Mesos + Docker(TBD)的解决方案,额外提供资源治理、应用分发以及进程隔离等服务

亮点:

  1. 基于quartz 定时任务框架为基础的,因此具备quartz的大部分功能
  2. 使用zookeeper做协调,调度中心,更加轻量级
  3. 支持任务的分片
  4. 支持弹性扩容 , 可以水平扩展 , 当任务再次运行时,会检查当前的服务器数量,重新分片,分片结束之后才会继续执行任务
  5. 失效转移,容错处理,当一台调度服务器宕机或者跟zookeeper断开连接之后,会立即停止作业,然后再去寻找其他空闲的调度服务器,来运行剩余的任务
  6. 提供运维界面,可以管理作业和注册中心。
  • xxl-job

个轻量级分布式任务调度框架 ,主要分为 调度中心和执行器两部分 , 调度中心在启动初始化的时候,会默认生成执行器的RPC代理

对象(http协议调用), 执行器项目启动之后, 调度中心在触发定时器之后通过jobHandle 来调用执行器项目里面的代码,核心功能和elastic-job差不多

13.7 分布式会话

Cookie和Session有什么区别?

cookie和session的方案虽然分别属于客户端和服务端,但是服务端的session的实现对客户端的cookie有依赖关系的,服务端执行session机制时候会生成session的id值,这个id值会发送给客户端,客户端每次请求都会把这个id值放到http请求的头部发送给服务端,而这个id值在客户端会保存下来,保存的容器就是cookie,因此当我们完全禁掉浏览器的cookie的时候,服务端的session也会不能正常使用。

谈谈会话技术的发展?

单机 - Session + Cookie

多机器

  • 在负载均衡侧 - Session 粘滞
  • Session数据同步

多机器,集群 - session集中管理,比如redis;目前方案上用的最多的是SpringSession,早前也有用tomcat集成方式的。

无状态token,比如JWT

分布式会话有哪些解决方案?

  • Session Stick
  • Session Replication
  • Session 数据集中存储
  • Cookie Based
  • JWT

什么是Session Stick?

方案即将客户端的每次请求都转发至同一台服务器,这就需要负载均衡器能够根据每次请求的会话标识(SessionId)来进行请求转发,如下图所示。

这种方案实现比较简单,对于Web服务器来说和单机的情况一样。但是可能会带来如下问题:

  • 如果有一台服务器宕机或者重启,那么这台机器上的会话数据会全部丢失。
  • 会话标识是应用层信息,那么负载均衡要将同一个会话的请求都保存到同一个Web服务器上的话,就需要进行应用层(第7层)的解析,这个开销比第4层大。
  • 负载均衡器将变成一个有状态的节点,要将会话保存到具体Web服务器的映射。和无状态节点相比,内存消耗更大,容灾方面也会更麻烦。

PS:为什么这种方案到目前还有很多项目使用呢?因为不需要在项目代码侧改动,而是只需要在负载均衡侧改动。

什么是Session Replication?

Session Replication 的方案则不对负载均衡器做更改,而是在Web服务器之间增加了会话数据同步的功能,各个服务器之间通过同步保证不同Web服务器之间的Session数据的一致性,如下图所示。

Session Replication 方案对负载均衡器不再有要求,但是同样会带来以下问题:

  • 同步Session数据会造成额外的网络带宽的开销,只要Session数据有变化,就需要将新产生的Session数据同步到其他服务器上,服务器数量越多,同步带来的网络带宽开销也就越大。
  • 每台Web服务器都需要保存全部的Session数据,如果整个集群的Session数量太多的话,则对于每台机器用于保存Session数据的占用会很严重。

什么是Session 数据集中存储?

Session 数据集中存储方案则是将集群中的所有Session集中存储起来,Web服务器本身则并不存储Session数据,不同的Web服务器从同样的地方来获取Session,如下图所示。

相对于Session Replication方案,此方案的Session数据将不保存在本机,并且Web服务器之间也没有了Session数据的复制,但是该方案存在的问题在于:

  • 读写Session数据引入了网络操作,这相对于本机的数据读取来说,问题就在于存在时延和不稳定性,但是通信发生在内网,则问题不大。
  • 如果集中存储Session的机器或集群出现问题,则会影响应用。

什么是Cookie Based Session?

Cookie Based 方案是将Session数据放在Cookie里,访问Web服务器的时候,再由Web服务器生成对应的Session数据,如下图所示。

但是Cookie Based 方案依然存在不足:

  • Cookie长度的限制。这会导致Session长度的限制。
  • 安全性。Seesion数据本来是服务端数据,却被保存在了客户端,即使可以加密,但是依然存在不安全性。
  • 带宽消耗。这里不是指内部Web服务器之间的宽带消耗,而是数据中心的整体外部带宽的消耗。
  • 性能影响。每次HTTP请求和响应都带有Seesion数据,对Web服务器来说,在同样的处理情况下,响应的结果输出越少,支持的并发就会越高。

什么是JWT?使用JWT的流程?对比传统的会话有啥区别?

JSON Web Token,一般用它来替换掉Session实现数据共享。

使用基于 Token 的身份验证方法,在服务端不需要存储用户的登录记录。大概的流程是这样的:

  • 1、客户端通过用户名和密码登录服务器;
  • 2、服务端对客户端身份进行验证;
  • 3、服务端对该用户生成Token,返回给客户端;
  • 4、客户端将Token保存到本地浏览器,一般保存到cookie中;
  • 5、客户端发起请求,需要携带该Token;
  • 6、服务端收到请求后,首先验证Token,之后返回数据。

如上图为Token实现方式,浏览器第一次访问服务器,根据传过来的唯一标识userId,服务端会通过一些算法,如常用的HMAC-SHA256算法,然后加一个密钥,生成一个token,然后通过BASE64编码一下之后将这个token发送给客户端;客户端将token保存起来,下次请求时,带着token,服务器收到请求后,然后会用相同的算法和密钥去验证token,如果通过,执行业务操作,不通过,返回不通过信息。

可以对比下图session实现方式,流程大致一致。

优点

  • 无状态、可扩展 :在客户端存储的Token是无状态的,并且能够被扩展。基于这种无状态和不存储Session信息,负载均衡器能够将用户信息从一个服务传到其他服务器上。
  • 安全:请求中发送token而不再是发送cookie能够防止CSRF(跨站请求伪造)。
  • 可提供接口给第三方服务:使用token时,可以提供可选的权限给第三方应用程序。
  • 多平台跨域

对应用程序和服务进行扩展的时候,需要介入各种各种的设备和应用程序。 假如我们的后端api服务器a.com只提供数据,而静态资源则存放在cdn 服务器b.com上。当我们从a.com请求b.com下面的资源时,由于触发浏览器的同源策略限制而被阻止。

我们通过CORS(跨域资源共享)标准和token来解决资源共享和安全问题

举个例子,我们可以设置b.com的响应首部字段为:

// 第一行指定了允许访问该资源的外域 URI。
Access-Control-Allow-Origin: http://a.com// 第二行指明了实际请求中允许携带的首部字段,这里加入了Authorization,用来存放token。
Access-Control-Allow-Headers: Authorization, X-Requested-With, Content-Type, Accept
​
// 第三行用于预检请求的响应。其指明了实际请求所允许使用的 HTTP 方法。
Access-Control-Allow-Methods: GET, POST, PUT,DELETE
​
// 然后用户从a.com携带有一个通过了验证的token访问B域名,数据和资源就能够在任何域上被请求到。

13.8 常见系统设计

如何设计一个秒杀系统?

  • 秒杀特点及思路?

短时间内,大量用户涌入,集中读和写有限的库存。

  1. 尽量将请求拦截在系统上游(越上游越好);
  2. 读多写少的多使用缓存(缓存抗读压力);
  • 从分层角度理解?

层层拦截,将请求尽量拦截在系统上游,避免将锁冲落到数据库上。

  • 第一层:客户端优化

产品层面,用户点击“查询”或者“购票”后,按钮置灰,禁止用户重复提交请求; JS层面,限制用户在x秒之内只能提交一次请求,比如微信摇一摇抢红包。 基本可以拦截80%的请求。

  • 第二层:站点层面的请求拦截(nginx层,写流控模块)

怎么防止程序员写for循环调用,有去重依据么? IP? cookie-id? …想复杂了,这类业务都需要登录,用uid即可。在站点层面,对uid进行请求计数和去重,甚至不需要统一存储计数,直接站点层内存存储(这样计数会不准,但最简单,比如guava本地缓存)。一个uid,5秒只准透过1个请求,这样又能拦住99%的for循环请求。 对于5s内的无效请求,统一返回错误提示或错误页面。

这个方式拦住了写for循环发HTTP请求的程序员,有些高端程序员(黑客)控制了10w个肉鸡,手里有10w个uid,同时发请求(先不考虑实名制的问题,小米抢手机不需要实名制),这下怎么办,站点层按照uid限流拦不住了。

  • 第三层:服务层拦截

方案一:写请求放到队列中,每次只透有限的写请求到数据层,如果成功了再放下一批,直到库存不够,队列里的写请求全部返回“已售完”。

方案二:或采用漏斗机制,只放一倍的流量进来,多余的返回“已售完”,把写压力转换成读压力。 读请求,用cache,redis单机可以抗10W QPS,用异步线程定时更新缓存里的库存值。

还有提示“模糊化”,比如火车余票查询,票剩了58张,还是26张,你真的关注么,其实我们只关心有票和无票。

  • 第四层:数据库层

浏览器拦截了80%,站点层拦截了99.9%并做了页面缓存,服务层又做了写请求队列与数据缓存,每次透到数据库层的请求都是可控的。 db基本就没什么压力了,通过自身锁机制来控制,避免出现超卖。

  • 从架构角度理解?
  1. 高性能

    1. 动静分离

      秒杀过程中你是不需要刷新整个页面的,只有时间在不停跳动。这是因为一般都会对大流量的秒杀系统做系统的静态化改造,即数据意义上的动静分离。动静分离三步走:

      1. 数据拆分;
      2. 静态缓存;
      3. 数据整合。
    2. 热点优化 数据的热点优化与动静分离是不一样的,热点优化是基于二八原则对数据进行了纵向拆分,以便进行针对性地处理。热点识别和隔离不仅对“秒杀”这个场景有意义,对其他的高性能分布式系统也非常有参考价值。

    3. 系统优化

      1. 减少序列化:减少 Java 中的序列化操作可以很好的提升系统性能。序列化大部分是在 RPC 阶段发生,因此应该尽量减少 RPC 调用,一种可行的方案是将多个关联性较强的应用进行 “合并部署”,从而减少不同应用之间的 RPC 调用(微服务设计规范)
      2. 直接输出流数据:只要涉及字符串的I/O操作,无论是磁盘 I/O 还是网络 I/O,都比较耗费 CPU 资源,因为字符需要转换成字节,而这个转换又必须查表编码。所以对于常用数据,比如静态字符串,推荐提前编码成字节并缓存,具体到代码层面就是通过 OutputStream() 类函数从而减少数据的编码转换;另外,热点方法toString()不要直接调用ReflectionToString实现,推荐直接硬编码,并且只打印DO的基础要素和核心要素
      3. 裁剪日志异常堆栈:无论是外部系统异常还是应用本身异常,都会有堆栈打出,超大流量下,频繁的输出完整堆栈,只会加剧系统当前负载。可以通过日志配置文件控制异常堆栈输出的深度
      4. 去组件框架:极致优化要求下,可以去掉一些组件框架,比如去掉传统的 MVC 框架,直接使用 Servlet 处理请求。这样可以绕过一大堆复杂且用处不大的处理逻辑,节省毫秒级的时间,当然,需要合理评估你对框架的依赖程度
  2. 高可用

    1. 流量削峰

      1. 答题:答题目前已经使用的非常普遍了,本质是通过在入口层削减流量,从而让系统更好地支撑瞬时峰值。
      2. MQ: 最为常见的削峰方案是使用消息队列,通过把同步的直接调用转换成异步的间接推送缓冲瞬时流量。
      3. 过滤
    2. Plan B: 为了保证系统的高可用,必须设计一个 Plan B 方案来进行兜底。

接口设计要考虑哪些方面?

讲讲几个要点:

  • 接口版本化
  • 命名规范
  • 请求参数的规范性及处理的统一性
  • 返回数据类型、返回码及信息提示的规范性
  • 接口安全验证及权限的控制
  • 请求接口日志的记录
  • 良好的接口说明文档和测试程序

什么是接口幂等?如何保证接口的幂等性?

接口的幂等性实际上就是接口可重复调用,在调用方多次调用的情况下,接口最终得到的结果是一致的。有些接口可以天然的实现幂等性,比如查询接口,对于查询来说,你查询一次和两次,对于系统来说,没有任何影响,查出的结果也是一样。

除了查询功能具有天然的幂等性之外,增加、更新、删除都要保证幂等性。那么如何来保证幂等性呢?

  • 全局唯一ID

如果使用全局唯一ID,就是根据业务的操作和内容生成一个全局ID,在执行操作前先根据这个全局唯一ID是否存在,来判断这个操作是否已经执行。如果不存在则把全局ID,存储到存储系统中,比如数据库、redis等。如果存在则表示该方法已经执行。

从工程的角度来说,使用全局ID做幂等可以作为一个业务的基础的微服务存在,在很多的微服务中都会用到这样的服务,在每个微服务中都完成这样的功能,会存在工作量重复。另外打造一个高可靠的幂等服务还需要考虑很多问题,比如一台机器虽然把全局ID先写入了存储,但是在写入之后挂了,这就需要引入全局ID的超时机制。

使用全局唯一ID是一个通用方案,可以支持插入、更新、删除业务操作。但是这个方案看起来很美但是实现起来比较麻烦,下面的方案适用于特定的场景,但是实现起来比较简单。

  • 去重表

这种方法适用于在业务中有唯一标的插入场景中,比如在以上的支付场景中,如果一个订单只会支付一次,所以订单ID可以作为唯一标识。这时,我们就可以建一张去重表,并且把唯一标识作为唯一索引,在我们实现时,把创建支付单据和写入去去重表,放在一个事务中,如果重复创建,数据库会抛出唯一约束异常,操作就会回滚。

  • 插入或更新

这种方法插入并且有唯一索引的情况,比如我们要关联商品品类,其中商品的ID和品类的ID可以构成唯一索引,并且在数据表中也增加了唯一索引。这时就可以使用InsertOrUpdate操作。在mysql数据库中如下:

insert into goods_category (goods_id,category_id,create_time,update_time) 
       values(#{goodsId},#{categoryId},now(),now()) 
       on DUPLICATE KEY UPDATE
       update_time=now()
  • 多版本控制

这种方法适合在更新的场景中,比如我们要更新商品的名字,这时我们就可以在更新的接口中增加一个版本号,来做幂等

boolean updateGoodsName(int id,String newName,int version);

在实现时可以如下

update goods set name=#{newName},version=#{version} where id=#{id} and version<${version}
  • 状态机控制

这种方法适合在有状态机流转的情况下,比如就会订单的创建和付款,订单的付款肯定是在之前,这时我们可以通过在设计状态字段时,使用int类型,并且通过值类型的大小来做幂等,比如订单的创建为0,付款成功为100。付款失败为99

在做状态机更新时,我们就这可以这样控制

update `order` set status=#{status} where id=#{id} and status<#{status}

14 微服务

14.1 Spring Cloud

什么是微服务?谈谈你对微服务的理解?

  • 微服务

以前所有的代码都放在同一个工程中、部署在同一个服务器、同一项目的不同模块不同功能互相抢占资源,微服务就是将工程根据不同的业务规则拆分成微服务,部署在不同的服务器上,服务之间相互调用,java中有的微服务有dubbo(只能用来做微服务)、springcloud( 提供了服务的发现、断路器等)。

  • 微服务的特点
  1. 按业务划分为一个独立运行的程序,即服务单元
  2. 服务之间通过HTTP协议相互通信
  3. 自动化部署
  4. 可以用不同的编程语言
  5. 可以用不同的存储技术
  6. 服务集中化管理
  7. 微服务是一个分布式系统
  • 微服务的优势
  1. 将一个复杂的业务拆分为若干小的业务,将复杂的业务简单化,新人只需要了解他所接管的服务的代码,减少了新人的学习成本。
  2. 由于微服务是分布式服务,服务于服务之间没有任何耦合。微服务系统的微服务单元具有很强的横向拓展能力。
  3. 服务于服务之间采用HTTP网络通信协议来通信,单个服务内部高度耦合,服务与服务之间完全独立,无耦合。这使得微服务可以采用任何的开发语言和技术来实现,提高开发效率、降低开发成本。
  4. 微服务是按照业务进行拆分的,并有坚实的服务边界,若要重写某一业务代码,不需了解所有业务,重写简单。
  5. 微服务的每个服务单元是独立部署的,即独立运行在某个进程中,微服务的修改和部署对其他服务没有影响。
  6. 微服务在CAP理论中采用的AP架构,具有高可用分区容错特点。高可用主要体现在系统7x24不间断服务,他要求系统有大量的服务器集群,从而提高系统的负载能力。分区容错也使得系统更加健壮。
  • 微服务的不足
  1. 微服务的复杂度:构建一个微服务比较复杂,服务与服务之间通过HTTP协议或其他消息传递机制通信,开发者要选出最佳的通信机制,并解决网络服务差时带来的风险。 2.分布式事物:将事物分成多阶段提交,如果一阶段某一节点失败仍会导致数据不正确。如果事物涉及的节点很多,某一节点的网络出现异常会导致整个事务处于阻塞状态,大大降低数据库的性能。
  2. 服务划分:将一个完整的系统拆分成很多个服务,是一件非常困难的事,因为这涉及了具体的业务场景
  3. 服务部署:最佳部署容器Docker
  • 微服务和SOA的关系

微服务相对于和ESB联系在一起的SOA轻便敏捷的多,微服务将复杂的业务组件化,也是一种面向服务思想的体现。对于微服务来说,它是SOA的一种体现,但是它比ESB实现的SOA更加轻便、敏捷和简单。

什么是Spring Cloud?

Spring Cloud是一系列框架的有序集合。它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、智能路由、消息总线、负载均衡、断路器、数据监控等,都可以用Spring Boot的开发风格做到一键启动和部署。

Spring Cloud并没有重复制造轮子,它只是将各家公司开发的比较成熟、经得起实际考验的服务框架组合起来,通过Spring Boot风格进行再封装屏蔽掉了复杂的配置和实现原理,最终给开发者留出了一套简单易懂、易部署和易维护的分布式系统开发工具包。

  • SpringCloud的优点
  1. 耦合度比较低。不会影响其他模块的开发。
  2. 减轻团队的成本,可以并行开发,不用关注其他人怎么开发,先关注自己的开发。
  3. 配置比较简单,基本用注解就能实现,不用使用过多的配置文件。
  4. 微服务跨平台的,可以用任何一种语言开发。
  5. 每个微服务可以有自己的独立的数据库也有用公共的数据库。
  6. 直接写后端的代码,不用关注前端怎么开发,直接写自己的后端代码即可,然后暴露接口,通过组件进行服务通信。
  • SpringCloud的缺点
  1. 部署比较麻烦,给运维工程师带来一定的麻烦。
  2. 针对数据的管理比麻烦,因为微服务可以每个微服务使用一个数据库。
  3. 系统集成测试比较麻烦
  4. 性能的监控比较麻烦。

SpringCloud中的组件有哪些?

说出主要的组件:

  • Spring Cloud Eureka,服务注册中心,特性有失效剔除、服务保护
  • Spring Cloud Zuul,API服务网关,功能有路由分发和过滤
  • Spring Cloud Config,分布式配置中心,支持本地仓库、SVN、Git、Jar包内配置等模式
  • Spring Cloud Ribbon,客户端负载均衡,特性有区域亲和,重试机制
  • Spring Cloud Hystrix,客户端容错保护,特性有服务降级、服务熔断、请求缓存、请求合并、依赖隔离
  • Spring Cloud Feign,声明式服务调用本质上就是Ribbon+Hystrix
  • Spring Cloud Stream,消息驱动,有Sink、Source、Processor三种通道,特性有订阅发布、消费组、消息分区
  • Spring Cloud Bus,消息总线,配合Config仓库修改的一种Stream实现,
  • Spring Cloud Sleuth,分布式服务追踪,需要搞清楚TraceID和SpanID以及抽样,如何与ELK整合

具体说说SpringCloud主要项目?

Spring Cloud的子项目,大致可分成两类,一类是对现有成熟框架"Spring Boot化"的封装和抽象,也是数量最多的项目;第二类是开发了一部分分布式系统的基础设施的实现,如Spring Cloud Stream扮演的就是kafka, ActiveMQ这样的角色。

  • Spring Cloud Config Config能够管理所有微服务的配置文件

集中配置管理工具,分布式系统中统一的外部配置管理,默认使用Git来存储配置,可以支持客户端配置的刷新及加密、解密操作。

  • Spring Cloud Netflix

Netflix OSS 开源组件集成,包括Eureka、Hystrix、Ribbon、Feign、Zuul等核心组件。

  1. Eureka:服务治理组件,包括服务端的注册中心和客户端的服务发现机制;
  2. Ribbon:负载均衡的服务调用组件,具有多种负载均衡调用策略;
  3. Hystrix:服务容错组件,实现了断路器模式,为依赖服务的出错和延迟提供了容错能力;
  4. Feign:基于Ribbon和Hystrix的声明式服务调用组件;
  5. Zuul:API网关组件,对请求提供路由及过滤功能。
  • Spring Cloud Bus
  1. 用于传播集群状态变化的消息总线,使用轻量级消息代理链接分布式系统中的节点,可以用来动态刷新集群中的服务配置信息。
  2. 简单来说就是修改了配置文件,发送一次请求,所有客户端便会重新读取配置文件(需要利用中间插件MQ)。
  • Spring Cloud Consul

Consul 是 HashiCorp 公司推出的开源工具,用于实现分布式系统的服务发现与配置。与其它分布式服务注册与发现的方案,Consul 的方案更“一站式”,内置了服务注册与发现框架、分布一致性协议实现、健康检查、Key/Value 存储、多数据中心方案,不再需要依赖其它工具(比如 ZooKeeper 等)。使用起来也较为简单。Consul 使用 Go 语言编写,因此具有天然可移植性(支持Linux、windows和Mac OS X);安装包仅包含一个可执行文件,方便部署,与 Docker 等轻量级容器可无缝配合。

  • Spring Cloud Security

Spring Cloud Security提供了一组原语,用于构建安全的应用程序和服务,而且操作简便。可以在外部(或集中)进行大量配置的声明性模型有助于实现大型协作的远程组件系统,通常具有中央身份管理服务。它也非常易于在Cloud Foundry等服务平台中使用。在Spring Boot和Spring Security OAuth2的基础上,可以快速创建实现常见模式的系统,如单点登录,令牌中继和令牌交换。

  • Spring Cloud Sleuth

在微服务中,通常根据业务模块分服务,项目中前端发起一个请求,后端可能跨几个服务调用才能完成这个请求(如下图)。如果系统越来越庞大,服务之间的调用与被调用关系就会变得很复杂,假如一个请求中需要跨几个服务调用,其中一个服务由于网络延迟等原因挂掉了,那么这时候我们需要分析具体哪一个服务出问题了就会显得很困难。Spring Cloud Sleuth服务链路跟踪功能就可以帮助我们快速的发现错误根源以及监控分析每条请求链路上的性能等等。

  • Spring Cloud Stream

轻量级事件驱动微服务框架,可以使用简单的声明式模型来发送及接收消息,主要实现为Apache Kafka及RabbitMQ。

  • Spring Cloud Task

Spring Cloud Task的目标是为Spring Boot应用程序提供创建短运行期微服务的功能。在Spring Cloud Task中,我们可以灵活地动态运行任何任务,按需分配资源并在任务完成后检索结果。Tasks是Spring Cloud Data Flow中的一个基础项目,允许用户将几乎任何Spring Boot应用程序作为一个短期任务执行。

  • Spring Cloud Zookeeper
  1. SpringCloud支持三种注册方式Eureka, Consul(go语言编写),zookeeper
  2. Spring Cloud Zookeeper是基于Apache Zookeeper的服务治理组件。
  • Spring Cloud Gateway

Spring cloud gateway是spring官方基于Spring 5.0、Spring Boot2.0和Project Reactor等技术开发的网关,Spring Cloud Gateway旨在为微服务架构提供简单、有效和统一的API路由管理方式,Spring Cloud Gateway作为Spring Cloud生态系统中的网关,目标是替代Netflix Zuul,其不仅提供统一的路由方式,并且还基于Filer链的方式提供了网关基本的功能,例如:安全、监控/埋点、限流等。

  • Spring Cloud OpenFeign

Feign是一个声明性的Web服务客户端。它使编写Web服务客户端变得更容易。要使用Feign,我们可以将调用的服务方法定义成抽象方法保存在本地添加一点点注解就可以了,不需要自己构建Http请求了,直接调用接口就行了,不过要注意,调用方法要和本地抽象方法的签名完全一致。

Spring Cloud项目部署架构?

Spring Cloud和Dubbo区别?

服务调用方式:dubbo是RPC springcloud Rest Api

注册中心:dubbo 是zookeeper springcloud是eureka,也可以是zookeeper

服务网关,dubbo本身没有实现,只能通过其他第三方技术整合,springcloud有Zuul路由网关,作为路由服务器,进行消费者的请求分发,springcloud支持断路器,与git完美集成配置文件支持版本控制,事物总线实现配置文件的更新与服务自动装配等等一系列的微服务架构要素。

服务注册和发现是什么意思?Spring Cloud如何实现?

当我们开始一个项目时,我们通常在属性文件中进行所有的配置。随着越来越多的服务开发和部署,添加和修改这些属性变得更加复杂。有些服务可能会下降,而某些位置可能会发生变化。手动更改属性可能会产生问题。 Eureka 服务注册和发现可以在这种情况下提供帮助。由于所有服务都在 Eureka 服务器上注册并通过调用 Eureka 服务器完成查找,因此无需处理服务地点的任何更改和处理。

什么是Eureka?

Eureka作为SpringCloud的服务注册功能服务器,他是服务注册中心,系统中的其他服务使用Eureka的客户端将其连接到Eureka Service中,并且保持心跳,这样工作人员可以通过Eureka Service来监控各个微服务是否运行正常。

Eureka怎么实现高可用?

集群吧,注册多台Eureka,然后把SpringCloud服务互相注册,客户端从Eureka获取信息时,按照Eureka的顺序来访问。

什么是Eureka的自我保护模式?

默认情况下,如果Eureka Service在一定时间内没有接收到某个微服务的心跳,Eureka Service会进入自我保护模式,在该模式下Eureka Service会保护服务注册表中的信息,不再删除注册表中的数据,当网络故障恢复后,Eureka Servic 节点会自动退出自我保护模式

DiscoveryClient的作用?

可以从注册中心中根据服务别名获取注册的服务器信息。

Eureka和Zookeeper都可以提供服务注册与发现的功能,请说说两个的区别?

  1. ZooKeeper中的节点服务挂了就要选举,在选举期间注册服务瘫痪,虽然服务最终会恢复,但是选举期间不可用的,选举就是改微服务做了集群,必须有一台主其他的都是从
  2. Eureka各个节点是平等关系,服务器挂了没关系,只要有一台Eureka就可以保证服务可用,数据都是最新的。如果查询到的数据并不是最新的,就是因为Eureka的自我保护模式导致的
  3. Eureka本质上是一个工程,而ZooKeeper只是一个进程
  4. Eureka可以很好的应对因网络故障导致部分节点失去联系的情况,而不会像ZooKeeper 一样使得整个注册系统瘫痪
  5. ZooKeeper保证的是CP,Eureka保证的是AP

什么是网关?

网关相当于一个网络服务架构的入口,所有网络请求必须通过网关转发到具体的服务。

网关的作用是什么?

统一管理微服务请求,权限控制、负载均衡、路由转发、监控、安全控制黑名单和白名单等

什么是Spring Cloud Zuul(服务网关)?

Zuul是对SpringCloud提供的成熟对的路由方案,他会根据请求的路径不同,网关会定位到指定的微服务,并代理请求到不同的微服务接口,他对外隐蔽了微服务的真正接口地址。

  • 三个重要概念:动态路由表,路由定位,反向代理:

    • 动态路由表:Zuul支持Eureka路由,手动配置路由,这俩种都支持自动更新
    • 路由定位:根据请求路径,Zuul有自己的一套定位服务规则以及路由表达式匹配
    • 反向代理:客户端请求到路由网关,网关受理之后,在对目标发送请求,拿到响应之后在 给客户端
  • 它可以和Eureka,Ribbon,Hystrix等组件配合使用,

  • Zuul的应用场景:

    • 对外暴露,权限校验,服务聚合,日志审计等

网关与过滤器有什么区别?

网关是对所有服务的请求进行分析过滤,过滤器是对单个服务而言。

常用网关框架有哪些?

Nginx、Zuul、Gateway

Zuul与Nginx有什么区别?

Zuul是java语言实现的,主要为java服务提供网关服务,尤其在微服务架构中可以更加灵活的对网关进行操作。Nginx是使用C语言实现,性能高于Zuul,但是实现自定义操作需要熟悉lua语言,对程序员要求较高,可以使用Nginx做Zuul集群。

既然Nginx可以实现网关?为什么还需要使用Zuul框架?

Zuul是SpringCloud集成的网关,使用Java语言编写,可以对SpringCloud架构提供更灵活的服务。

ZuulFilter常用有哪些方法?

  • Run():过滤器的具体业务逻辑
  • shouldFilter():判断过滤器是否有效
  • filterOrder():过滤器执行顺序
  • filterType():过滤器拦截位置

如何实现动态Zuul网关路由转发?

通过path配置拦截请求,通过ServiceId到配置中心获取转发的服务列表,Zuul内部使用Ribbon实现本地负载均衡和转发。

Zuul网关如何搭建集群?

使用Nginx的upstream设置Zuul服务集群,通过location拦截请求并转发到upstream,默认使用轮询机制对Zuul集群发送请求。

Ribbon是什么?

Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法

Ribbon客户端组件提供一系列完善的配置项,如连接超时,重试等。简单的说,就是在配置文件中列出后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器。我们也很容易使用Ribbon实现自定义的负载均衡算法。(有点类似Nginx)

Nginx与Ribbon的区别?

Nginx是反向代理同时可以实现负载均衡,nginx拦截客户端请求采用负载均衡策略根据upstream配置进行转发,相当于请求通过nginx服务器进行转发。Ribbon是客户端负载均衡,从注册中心读取目标服务器信息,然后客户端采用轮询策略对服务直接访问,全程在客户端操作。

Ribbon底层实现原理?

Ribbon使用discoveryClient从注册中心读取目标服务信息,对同一接口请求进行计数,使用%取余算法获取目标服务集群索引,返回获取到的目标服务信息

@LoadBalanced注解的作用?

开启客户端负载均衡。

什么是断路器

当一个服务调用另一个服务由于网络原因或自身原因出现问题,调用者就会等待被调用者的响应 当更多的服务请求到这些资源导致更多的请求等待,发生连锁效应(雪崩效应)

断路器有三种状态

  • 打开状态:一段时间内 达到一定的次数无法调用 并且多次监测没有恢复的迹象 断路器完全打开 那么下次请求就不会请求到该服务
  • 半开状态:短时间内 有恢复迹象 断路器会将部分请求发给该服务,正常调用时 断路器关闭
  • 关闭状态:当服务一直处于正常状态 能正常调用

什么是Hystrix?

在分布式系统,我们一定会依赖各种服务,那么这些个服务一定会出现失败的情况,就会导致雪崩,Hystrix就是这样的一个工具,防雪崩利器,它具有服务降级,服务熔断,服务隔离,监控等一些防止雪崩的技术。

Hystrix有四种防雪崩方式:

  • 服务降级:接口调用失败就调用本地的方法返回一个空
  • 服务熔断:接口调用失败就会进入调用接口提前定义好的一个熔断的方法,返回错误信息
  • 服务隔离:隔离服务之间相互影响
  • 服务监控:在服务发生调用时,会将每秒请求数、成功请求数等运行指标记录下来。

什么是Feign?

Feign 是一个声明web服务客户端,这使得编写web服务客户端更容易

他将我们需要调用的服务方法定义成抽象方法保存在本地就可以了,不需要自己构建Http请求了,直接调用接口就行了,不过要注意,调用方法要和本地抽象方法的签名完全一致。

Spring Cloud有几种调用接口方式?

  • Feign
  • RestTemplate

Ribbon和Feign调用服务的区别?

调用方式同:Ribbon需要我们自己构建Http请求,模拟Http请求然后通过RestTemplate发给其他服务,步骤相当繁琐

而Feign则是在Ribbon的基础上进行了一次改进,采用接口的形式,将我们需要调用的服务方法定义成抽象方法保存在本地就可以了,不需要自己构建Http请求了,直接调用接口就行了,不过要注意,调用方法要和本地抽象方法的签名完全一致。

什么是 Spring Cloud Bus?

  • Spring Cloud Bus就像一个分布式执行器,用于扩展的Spring Boot应用程序的配置文件,但也可以用作应用程序之间的通信通道。
  • Spring Cloud Bus 不能单独完成通信,需要配合MQ支持
  • Spring Cloud Bus一般是配合Spring Cloud Config做配置中心的
  • Springcloud config实时刷新也必须采用SpringCloud Bus消息总线

什么是Spring Cloud Config?

Spring Cloud Config为分布式系统中的外部配置提供服务器和客户端支持,可以方便的对微服务各个环境下的配置进行集中式管理。Spring Cloud Config分为Config Server和Config Client两部分。Config Server负责读取配置文件,并且暴露Http API接口,Config Client通过调用Config Server的接口来读取配置文件。

分布式配置中心有哪些框架?

Apollo、zookeeper、springcloud config。

分布式配置中心的作用?

动态变更项目配置信息而不必重新部署项目。

Spring Cloud Config可以实现实时刷新吗?

springcloud config实时刷新采用SpringCloud Bus消息总线。

什么是Spring Cloud Gateway?

Spring Cloud Gateway是Spring Cloud官方推出的第二代网关框架,取代Zuul网关。网关作为流量的,在微服务系统中有着非常作用,网关常见的功能有路由转发、权限校验、限流控制等作用。

使用了一个RouteLocatorBuilder的bean去创建路由,除了创建路由RouteLocatorBuilder可以让你添加各种predicates和filters,predicates断言的意思,顾名思义就是根据具体的请求的规则,由具体的route去处理,filters是各种过滤器,用来对请求做各种判断和修改。

14.2 Kubernetes

什么是Kubernetes?Kubernetes与Docker有什么关系?

  • 是什么

Kubernetes是一个开源容器管理工具,负责容器部署,容器扩缩容以及负载平衡。作为Google的创意之作,它提供了出色的社区,并与所有云提供商合作。因此,我们可以说Kubernetes不是一个容器化平台,而是一个多容器管理解决方案。

众所周知,Docker提供容器的生命周期管理,Docker镜像构建运行时容器。但是,由于这些单独的容器必须通信,因此使用Kubernetes。因此,我们说Docker构建容器,这些容器通过Kubernetes相互通信。因此,可以使用Kubernetes手动关联和编排在多个主机上运行的容器。

  • 有哪些特性
  1. 自我修复: 在节点故障时可以删除失效容器,重新创建新的容器,替换和重新部署,保证预期的副本数量,kill掉健康检查失败的容器,并且在容器未准备好之前不会处理客户端情况,确保线上服务不会中断
  2. 弹性伸缩: 使用命令、UI或者k8s基于cpu使用情况自动快速扩容和缩容应用程序实例,保证应用业务高峰并发时的高可用性,业务低峰时回收资源,以最小成本运行服务
  3. 自动部署和回滚: k8s采用滚动更新策略更新应用,一次更新一个pod,而不是同时删除所有pod,如果更新过程中出现问题,将回滚恢复,确保升级不影响业务
  4. 服务发现和负载均衡: k8s为多个容器提供一个统一访问入口(内部IP地址和一个dns名称)并且负载均衡关联的所有容器,使得用户无需考虑容器IP问题
  5. 机密和配置管理: 管理机密数据和应用程序配置,而不需要把敏感数据暴露在径向力,提高敏感数据安全性,并可以将一些常用的配置存储在k8s中,方便应用程序调用
  6. 存储编排: 挂载外部存储系统,无论时来自本地存储、公有云(aws)、还是网络存储(nfs、GFS、ceph),都作为集群资源的一部分使用,极大提高存储使用灵活性
  7. 批处理: 提供一次性任务,定时任务:满足批量数据处理和分析的场景

Kubernetes的整体架构?

Kubernetes主要由以下几个核心组件组成:

  1. etcd:提供数据库服务保存了整个集群的状态
  2. kube-apiserver:提供了资源操作的唯一入口,并提供认证、授权、访问控制、API注册和发现等机制
  3. kube-controller-manager:负责维护集群的状态,比如故障检测、自动扩展、滚动更新等
  4. cloud-controller-manager:是与底层云计算服务商交互的控制器
  5. kub-scheduler:负责资源的调度,按照预定的调度策略将Pod调度到相应的机器上
  6. kubelet:负责维护容器的生命周期,同时也负责Volume(CVI)和网络(CNI)的管理;
  7. kube-proxy:负责为Service提供内部的服务发现和负载均衡,并维护网络规则
  8. container-runtime:是负责管理运行容器的软件,比如docker

除了核心组件,还有一些推荐的Add-ons:

  1. kube-dns负责为整个集群提供DNS服务
  2. Ingress Controller为服务提供外网入口
  3. Heapster提供资源监控
  4. Dashboard提供GUI
  5. Federation提供跨可用区的集群
  6. Fluentd-elasticsearch提供集群日志采集、存储与查询

Kubernetes中有哪些核心概念?

  • Cluster、Master、Node
  1. Cluster

    1. Cluster(集群) 是计算、存储和网络资源的集合,Kubernetes 利用这些资源运行各种基于容器的应用。最简单的 Cluster 可以只有一台主机(它既是 Mater 也是 Node)
  2. Master

    1. Master 是 Cluster 的大脑,它的主要职责是调度,即决定将应用放在哪里运行。
    2. Master 运行 Linux 操作系统,可以是物理机或者虚拟机。
    3. 为了实现高可用,可以运行多个 Master。
  3. Node

    1. Node 的职责是运行容器应用。
    2. Node 由 Master 管理,Node 负责监控并汇报容器的状态,并根据 Master 的要求管理容器的生命周期。
    3. Node 运行在 Linux 操作系统,可以是物理机或者是虚拟机。
  • Pod
  1. 基本概念

    1. Pod 是 Kubernetes 的最小工作单元。
    2. 每个 Pod 包含一个或多个容器。Pod 中的容器会作为一个整体被 Master 调度到一个 Node 上运行。
  2. 引入 Pod 的目的

    1. 可管理性: 有些容器天生就是需要紧密联系,一起工作。Pod 提供了比容器更高层次的抽象,将它们封装到一个部署单元中。Kubernetes 以 Pod 为最小单位进行调度、扩展、共享资源、管理生命周期。
    2. 通信和资源共享: Pod 中的所有容器使用同一个网络 namespace,即相同的 IP 地址和 Port 空间。它们可以直接用 localhost 通信。同样的,这些容器可以共享存储,当 Kubernetes 挂载 volume 到 Pod,本质上是将 volume 挂载到 Pod 中的每一个容器。
  3. Pod 的使用方式

    1. 运行单一容器: one-container-per-Pod 是 Kubernetes 最常见的模型,这种情况下,只是将单个容器简单封装成 Pod。即便是只有一个容器,Kubernetes 管理的也是 Pod 而不是直接管理容器。
    2. 运行多个容器: 对于那些联系非常紧密,而且需要直接共享资源的容器,应该放在一个 Pod 中。比如下面这个 Pod 包含两个容器:一个 File Puller,一个是 Web Server。File Puller 会定期从外部的 Content Manager 中拉取最新的文件,将其存放在共享的 volume 中。Web Server 从 volume 读取文件,响应 Consumer 的请求。这两个容器是紧密协作的,它们一起为 Consumer 提供最新的数据;同时它们也通过 volume 共享数据。所以放到一个 Pod 是合适的。
  • Controller
  1. 基本概念

    1. Kubernetes 通常不会直接创建 Pod,而是通过 Controller 来管理 Pod 的。Controller 中定义了 Pod 的部署特性,比如有几个副本,在什么样的 Node 上运行等。为了满足不同的业务场景,Kubernetes 提供了多种 Controller,包括 Deployment、ReplicaSet、DaemonSet、StatefuleSet、Job 等。
  2. 各个 Controller

    1. Deployment: Deployment 是最常用的 Controller,比如我们可以通过创建 Deployment 来部署应用的。Deployment 可以管理 Pod 的多个副本,并确保 Pod 按照期望的状态运行。
    2. ReplicaSet: ReplicaSet 实现了 Pod 的多副本管理。使用 Deployment 时会自动创建 ReplicaSet,也就是说 Deployment 是通过 ReplicaSet 来管理 Pod 的多个副本,我们通常不需要直接使用 ReplicaSet。
    3. DaemonSet: DaemonSet 用于每个 Node 最多只运行一个 Pod 副本的场景。正如其名称所揭示的,DaemonSet 通常用于运行 daemon。
    4. StatefuleSet: StatefuleSet 能够保证 Pod 的每个副本在整个生命周期中名称是不变的。而其他 Controller 不提供这个功能,当某个 Pod 发生故障需要删除并重新启动时,Pod 的名称会发生变化。同时 StatefuleSet 会保证副本按照固定的顺序启动、更新或者删除。
    5. Job: Job 用于运行结束就删除的应用。而其他 Controller 中的 Pod 通常是长期持续运行。
  • Service、Namespace
  1. Service

    1. Deployment 可以部署多个副本,每个 Pod 都有自己的 IP。而 Pod 很可能会被频繁地销毁和重启,它们的 IP 会发生变化,用 IP 来访问 Deployment 副本不太现实。
    2. Service 定义了外界访问一组特定 Pod 的方式。Service 有自己的 IP 和端口,Service 为 Pod 提供了负载均衡。
  2. Namespace

    1. Namespace 可以将一个物理的 Cluster 逻辑上划分成多个虚拟 Cluster,每个 Cluster 就是一个 Namespace。不同 Namespace 里的资源是完全隔离的。

    2. Kubernetes 默认创建了两个 Namespace:

      1. default:创建资源时如果不指定,将被放到这个 Namespace 中。
      2. kube-system:Kubernetes 自己创建的系统资源将放到这个 Namespace 中。

什么是Heapster?

Heapster是由每个节点上运行的Kubelet提供的集群范围的数据聚合器。此容器管理工具在Kubernetes集群上本机支持,并作为pod运行,就像集群中的任何其他pod一样。因此,它基本上发现集群中的所有节点,并通过机上Kubernetes代理查询集群中Kubernetes节点的使用信息。

什么是Minikube?

Minikube是一种工具,可以在本地轻松运行Kubernetes。这将在虚拟机中运行单节点Kubernetes群集。

什么是Kubectl?

Kubectl是一个平台,您可以使用该平台将命令传递给集群。因此,它基本上为CLI提供了针对Kubernetes集群运行命令的方法,以及创建和管理Kubernetes组件的各种方法。

kube-apiserver和kube-scheduler的作用是什么?

kube -apiserver遵循横向扩展架构,是主节点控制面板的前端。这将公开Kubernetes主节点组件的所有API,并负责在Kubernetes节点和Kubernetes主组件之间建立通信。

kube-scheduler负责工作节点上工作负载的分配和管理。因此,它根据资源需求选择最合适的节点来运行未调度的pod,并跟踪资源利用率。它确保不在已满的节点上调度工作负载。

请你说一下kubenetes针对pod资源对象的健康监测机制?

K8s中对于pod资源对象的健康状态检测,提供了三类probe(探针)来执行对pod的健康监测:

  • livenessProbe探针

可以根据用户自定义规则来判定pod是否健康,如果livenessProbe探针探测到容器不健康,则kubelet会根据其重启策略来决定是否重启,如果一个容器不包含livenessProbe探针,则kubelet会认为容器的livenessProbe探针的返回值永远成功。

  • ReadinessProbe探针 同样是可以根据用户自定义规则来判断pod是否健康,如果探测失败,控制器会将此pod从对应service的endpoint列表中移除,从此不再将任何请求调度到此Pod上,直到下次探测成功。
  • startupProbe探针 启动检查机制,应用一些启动缓慢的业务,避免业务长时间启动而被上面两类探针kill掉,这个问题也可以换另一种方式解决,就是定义上面两类探针机制时,初始化时间定义的长一些即可。

K8s中镜像的下载策略是什么?

可通过命令kubectl explain pod.spec.containers来查看imagePullPolicy这行的解释。

K8s的镜像下载策略有三种:Always、Never、IFNotPresent;

  • Always:镜像标签为latest时,总是从指定的仓库中获取镜像;
  • Never:禁止从仓库中下载镜像,也就是说只能使用本地镜像;
  • IfNotPresent:仅当本地没有对应镜像时,才从目标仓库中下载。

默认的镜像下载策略是:当镜像标签是latest时,默认策略是Always;当镜像标签是自定义时(也就是标签不是latest),那么默认策略是IfNotPresent。

image的状态有哪些?

Running:Pod所需的容器已经被成功调度到某个节点,且已经成功运行,

Pending:APIserver创建了pod资源对象,并且已经存入etcd中,但它尚未被调度完成或者仍然处于仓库中下载镜像的过程

Unknown:APIserver无法正常获取到pod对象的状态,通常是其无法与所在工作节点的kubelet通信所致。

如何控制滚动更新过程?

可以通过下面的命令查看到更新时可以控制的参数:

[root@master yaml]# kubectl explain deploy.spec.strategy.rollingUpdate
  • maxSurge :此参数控制滚动更新过程,副本总数超过预期pod数量的上限。可以是百分比,也可以是具体的值。默认为1。 (上述参数的作用就是在更新过程中,值若为3,那么不管三七二一,先运行三个pod,用于替换旧的pod,以此类推)
  • maxUnavailable:此参数控制滚动更新过程中,不可用的Pod的数量。 (这个值和上面的值没有任何关系,举个例子:我有十个pod,但是在更新的过程中,我允许这十个pod中最多有三个不可用,那么就将这个参数的值设置为3,在更新的过程中,只要不可用的pod数量小于或等于3,那么更新过程就不会停止)。

DaemonSet资源对象的特性?

DaemonSet这种资源对象会在每个k8s集群中的节点上运行,并且每个节点只能运行一个pod,这是它和deployment资源对象的最大也是唯一的区别。所以,在其yaml文件中,不支持定义replicas,除此之外,与Deployment、RS等资源对象的写法相同。

它的一般使用场景如下:

  1. 在去做每个节点的日志收集工作;
  2. 监控每个节点的的运行状态;

说说你对Job这种资源对象的了解?

Job与其他服务类容器不同,Job是一种工作类容器(一般用于做一次性任务)。使用常见不多,可以忽略这个问题。

#提高Job执行效率的方法:
spec:
  parallelism: 2           #一次运行2个
  completions: 8           #最多运行8个
  template:
metadata:

pod的重启策略是什么?

可以通过命令kubectl explain pod.spec查看pod的重启策略。(restartPolicy字段)

  • Always:但凡pod对象终止就重启,此为默认策略。
  • OnFailure:仅在pod对象出现错误时才重启

描述一下pod的生命周期有哪些状态?

Pending:表示pod已经被同意创建,正在等待kube-scheduler选择合适的节点创建,一般是在准备镜像;

Running:表示pod中所有的容器已经被创建,并且至少有一个容器正在运行或者是正在启动或者是正在重启;

Succeeded:表示所有容器已经成功终止,并且不会再启动;

Failed:表示pod中所有容器都是非0(不正常)状态退出;

Unknown:表示无法读取Pod状态,通常是kube-controller-manager无法与Pod通信。

创建一个pod的流程是什么?

1) 客户端提交Pod的配置信息(可以是yaml文件定义好的信息)到kube-apiserver;

2) Apiserver收到指令后,通知给controller-manager创建一个资源对象;

3) Controller-manager通过api-server将pod的配置信息存储到ETCD数据中心中;

4) Kube-scheduler检测到pod信息会开始调度预选,会先过滤掉不符合Pod资源配置要求的节点,然后开始调度调优,主要是挑选出更适合运行pod的节点,然后将pod的资源配置单发送到node节点上的kubelet组件上。

5) Kubelet根据scheduler发来的资源配置单运行pod,运行成功后,将pod的运行信息返回给scheduler,scheduler将返回的pod运行状况的信息存储到etcd数据中心。

删除一个pod会发生什么事情?

Kube-apiserver会接受到用户的删除指令,默认有30秒时间等待优雅退出,超过30秒会被标记为死亡状态,此时Pod的状态Terminating,kubelet看到pod标记为Terminating就开始了关闭Pod的工作;

关闭流程如下:

  1. pod从service的endpoint列表中被移除;
  2. 如果该pod定义了一个停止前的钩子,其会在pod内部被调用,停止钩子一般定义了如何优雅的结束进程;
  3. 进程被发送TERM信号(kill -14)
  4. 当超过优雅退出的时间后,Pod中的所有进程都会被发送SIGKILL信号(kill -9)。

K8s的Service是什么?

Pod每次重启或者重新部署,其IP地址都会产生变化,这使得pod间通信和pod与外部通信变得困难,这时候,就需要Service为pod提供一个固定的入口。

Service的Endpoint列表通常绑定了一组相同配置的pod,通过负载均衡的方式把外界请求分配到多个pod上

K8s是怎么进行服务注册的?

Pod启动后会加载当前环境所有Service信息,以便不同Pod根据Service名进行通信。

K8s集群外流量怎么访问Pod?

可以通过Service的NodePort方式访问,会在所有节点监听同一个端口,比如:30000,访问节点的流量会被重定向到对应的Service上面。

K8s数据持久化的方式有哪些?

  • EmptyDir(空目录)

没有指定要挂载宿主机上的某个目录,直接由Pod内保部映射到宿主机上。类似于docker中的manager volume。

主要使用场景:

  1. 只需要临时将数据保存在磁盘上,比如在合并/排序算法中;
  2. 作为两个容器的共享存储,使得第一个内容管理的容器可以将生成的数据存入其中,同时由同一个webserver容器对外提供这些页面。

emptyDir的特性: 同个pod里面的不同容器,共享同一个持久化目录,当pod节点删除时,volume的数据也会被删除。如果仅仅是容器被销毁,pod还在,则不会影响volume中的数据。 总结来说:emptyDir的数据持久化的生命周期和使用的pod一致。一般是作为临时存储使用。

  • Hostpath

将宿主机上已存在的目录或文件挂载到容器内部。类似于docker中的bind mount挂载方式。

这种数据持久化方式,运用场景不多,因为它增加了pod与节点之间的耦合。

一般对于k8s集群本身的数据持久化和docker本身的数据持久化会使用这种方式,可以自行参考apiService的yaml文件,位于:/etc/kubernetes/main…目录下。

  • PersistentVolume(简称PV):

基于NFS服务的PV,也可以基于GFS的PV。它的作用是统一数据持久化目录,方便管理。

在一个PV的yaml文件中,可以对其配置PV的大小,

指定PV的访问模式:

  1. ReadWriteOnce:只能以读写的方式挂载到单个节点;
  2. ReadOnlyMany:能以只读的方式挂载到多个节点;
  3. ReadWriteMany:能以读写的方式挂载到多个节点。,

以及指定pv的回收策略(这里的回收策略指的是在PV被删除后,在这个PV下所存储的源文件是否删除):

  1. recycle:清除PV的数据,然后自动回收;
  2. Retain:需要手动回收;
  3. delete:删除云存储资源,云存储专用;

若需使用PV,那么还有一个重要的概念:PVC,PVC是向PV申请应用所需的容量大小,K8s集群中可能会有多个PV,PVC和PV若要关联,其定义的访问模式必须一致。定义的storageClassName也必须一致,若群集中存在相同的(名字、访问模式都一致)两个PV,那么PVC会选择向它所需容量接近的PV去申请,或者随机申请。

Replica Set 和 Replication Controller 之间有什么区别?

Replica Set 和 Replication Controller 几乎完全相同。它们都确保在任何给定时间运行指定数量的 Pod 副本。不同之处在于复制 Pod 使用的选择器。Replica Set 使用基于集合的选择器,而 Replication Controller 使用基于权限的选择器。

Equity-Based 选择器:这种类型的选择器允许按标签键和值进行过滤。因此,在外行术语中,基于 Equity 的选择器将仅查找与标签具有完全相同短语的 Pod。示例:假设您的标签键表示 app = nginx,那么使用此选择器,您只能查找标签应用程序等于 nginx 的那些 Pod。

Selector-Based 选择器:此类型的选择器允许根据一组值过滤键。因此,换句话说,基于 Selector 的选择器将查找已在集合中提及其标签的 Pod。示例:假设您的标签键在(nginx、NPS、Apache)中显示应用程序。然后,使用此选择器,如果您的应用程序等于任何 nginx、NPS或 Apache,则选择器将其视为真实结果。

其他

基础篇 基础篇主要面向的初级、中级开发工程师职位,主要考察对k8s本身的理解。

kubernetes包含几个组件。各个组件的功能是什么。组件之间是如何交互的。 k8s的pause容器有什么用。是否可以去掉。 k8s中的pod内几个容器之间的关系是什么。 一个经典pod的完整生命周期。 k8s的service和ep是如何关联和相互影响的。 详述kube-proxy原理,一个请求是如何经过层层转发落到某个pod上的整个过程。请求可能来自pod也可能来自外部。 rc/rs功能是怎么实现的。详述从API接收到一个创建rc/rs的请求,到最终在节点上创建pod的全过程,尽可能详细。另外,当一个pod失效时,kubernetes是如何发现并重启另一个pod的? deployment/rs有什么区别。其使用方式、使用条件和原理是什么。 cgroup中的cpu有哪几种限制方式。k8s是如何使用实现request和limit的。

拓展实践篇 拓展实践篇主要面向的高级开发工程师、架构师职位,主要考察实践经验和技术视野。

设想一个一千台物理机,上万规模的容器的kubernetes集群,请详述使用kubernetes时需要注意哪些问题?应该怎样解决?(提示可以从高可用,高性能等方向,覆盖到从镜像中心到kubernetes各个组件等) 设想kubernetes集群管理从一千台节点到五千台节点,可能会遇到什么样的瓶颈。应该如何解决。 kubernetes的运营中有哪些注意的要点。 集群发生雪崩的条件,以及预防手段。 设计一种可以替代kube-proxy的实现 sidecar的设计模式如何在k8s中进行应用。有什么意义。 灰度发布是什么。如何使用k8s现有的资源实现灰度发布。 介绍k8s实践中踩过的比较大的一个坑和解决方式。

14.3 Service Mesh

什么是Service Mesh(服务网络)?

Service Mesh是专用的基础设施层,轻量级高性能网络代理。提供安全的、快速的、可靠地服务间通讯,与实际应用部署一起,但对应用透明。

为了帮助理解, 下图展示了服务网格的典型边车部署方式:

www.jianshu.com/p/cc5b54ad8…

什么是Istio?

Istio的架构?

15 DevOps

15.1 Linux

什么是Linux?

Linux是一种基于UNIX的操作系统,最初是由Linus Torvalds引入的。它基于Linux内核,可以运行在由Intel,MIPS,HP,IBM,SPARC和Motorola制造的不同硬件平台上。Linux中另一个受欢迎的元素是它的吉祥物,一个名叫Tux的企鹅形象。

UNIX和LINUX有什么区别?

Unix最初是作为Bell Laboratories的专有操作系统开始的,后来产生了不同的商业版本。另一方面,Linux是免费的,开源的,旨在为大众提供非适当的操作系统。

什么是BASH?

BASH是Bourne Again SHell的缩写。它由Steve Bourne编写,作为原始Bourne Shell(由/ bin / sh表示)的替代品。它结合了原始版本的Bourne Shell的所有功能,以及其他功能,使其更容易使用。从那以后,它已被改编为运行Linux的大多数系统的默认shell。

什么是Linux内核?

Linux内核是一种低级系统软件,其主要作用是为用户管理硬件资源。它还用于为用户级交互提供界面。

什么是LILO?

LILO是Linux的引导加载程序。它主要用于将Linux操作系统加载到主内存中,以便它可以开始运行。

什么是交换空间?

交换空间是Linux使用的一定空间,用于临时保存一些并发运行的程序。当RAM没有足够的内存来容纳正在执行的所有程序时,就会发生这种情况。

Linux的基本组件是什么?

就像任何其他典型的操作系统一样,Linux拥有所有这些组件:内核,shell和GUI,系统实用程序和应用程序。Linux比其他操作系统更具优势的是每个方面都附带其他功能,所有代码都可以免费下载。

Linux系统安装多个桌面环境有帮助吗?

通常,一个桌面环境,如KDE或Gnome,足以在没有问题的情况下运行。尽管系统允许从一个环境切换到另一个环境,但这对用户来说都是优先考虑的问题。有些程序在一个环境中工作而在另一个环境中无法工作,因此它也可以被视为选择使用哪个环境的一个因素。

BASH和DOS之间的基本区别是什么?

BASH和DOS控制台之间的主要区别在于3个方面:

BASH命令区分大小写,而DOS命令则不区分; 在BASH下,/ character是目录分隔符,\作为转义字符。在DOS下,/用作命令参数分隔符,\是目录分隔符 DOS遵循命名文件中的约定,即8个字符的文件名后跟一个点,扩展名为3个字符。BASH没有遵循这样的惯例。

GNU项目的重要性是什么?

这种所谓的自由软件运动具有多种优势,例如可以自由地运行程序以及根据你的需要自由学习和修改程序。它还允许你将软件副本重新分发给其他人,以及自由改进软件并将其发布给公众。

描述root账户?

root帐户就像一个系统管理员帐户,允许你完全控制系统。你可以在此处创建和维护用户帐户,为每个帐户分配不同的权限。每次安装Linux时都是默认帐户。

如何在发出命令时打开命令提示符?

要打开默认shell(可以找到命令提示符的位置),请按Ctrl-Alt-F1。这将提供命令行界面(CLI),你可以根据需要从中运行命令。

如何知道Linux使用了多少内存?

在命令shell中,使用“concatenate”命令:cat / proc / meminfo获取内存使用信息。你应该看到一行开始像Mem:64655360等。这是Linux认为它可以使用的总内存。

你也可以使用命令

free - m
vmstat
top
htop

找到当前的内存使用情况

Linux系统下交换分区的典型大小是多少?

交换分区的首选大小是系统上可用物理内存量的两倍。如果无法做到这一点,则最小大小应与安装的内存量相同。

什么是符号链接?

符号链接的行为类似于Windows中的快捷方式。这些链接指向程序,文件或目录。它还允许你即时访问它,而无需直接转到整个路径名。

Ctrl + Alt + Del组合键是否适用于Linux?

是的,它确实。就像Windows一样,你可以使用此组合键来执行系统重启。一个区别是你不会收到任何确认消息,因此,立即重启。

如何引用连接打印机等设备的并行端口?

在Windows下,你将并行端口称为LPT端口,而在Linux下,你将其称为/ dev / lp。因此,LPT1,LPT2和LPT3在Linux下称为/ dev / lp0,/ dev / lp1或/ dev / lp2。

硬盘驱动器和软盘驱动器等驱动器是否用驱动器号表示?

在Linux中,每个驱动器和设备都有不同的名称。例如,软盘驱动器称为/ dev / fd0和/ dev / fd1。IDE / EIDE硬盘驱动器称为/ dev / hda,/ dev / hdb,/ dev / hdc等。

如何在Linux下更改权限?

假设你是系统管理员或文件或目录的所有者,则可以使用chmod命令授予权限。使用+符号添加权限或 - 符号拒绝权限,以及以下任何字母:u(用户),g(组),o(其他),a(所有),r(读取),w(写入)和x(执行)。例如,命令chmod go + rw FILE1.TXT授予对文件FILE1.TXT的读写访问权限,该文件分配给组和其他组。

在Linux中,为不同的串口分配了哪些名称?

串行端口标识为/ dev / ttyS0到/ dev / ttyS7。这些是Windows中COM1到COM8的等效名称。

如何在Linux下访问分区?

Linux在驱动器标识符的末尾分配数字。例如,如果第一个IDE硬盘驱动器有三个主分区,则它们将命名/编号,/ dev / hda1,/ dev / hda2和/ dev / hda3。

什么是硬链接?

硬链接直接指向磁盘上的物理文件,而不指向路径名。这意味着如果重命名或移动原始文件,链接将不会中断,因为链接是针对文件本身的,而不是文件所在的路径。

Linux下文件名的最大长度是多少?

任何文件名最多可包含255个字符。此限制不包括路径名,因此整个路径名和文件名可能会超过255个字符。

什么是以点开头的文件名?

通常,以点开头的文件名是隐藏文件。这些文件可以是包含重要数据或设置信息的配置文件。将这些文件设置为隐藏会使其不太可能被意外删除。

解释虚拟桌面?

这可以作为最小化和最大化当前桌面上不同窗口的替代方案。当你可以打开一个或多个程序时,使用虚拟桌面可以清除桌面。你可以简单地在虚拟桌面之间进行随机播放,而不是在每个程序中保持完整的程序,而不是最小化/恢复所有这些程序。

如何在Linux下跨不同的虚拟桌面共享程序?

要在不同的虚拟桌面之间共享程序,请在程序窗口的左上角查找看起来像图钉的图标。按此按钮将“固定”该应用程序到位,使其显示在所有虚拟桌面上,位于屏幕上的相同位置。

无名(空)目录代表什么?

此空目录名称用作Linux文件系统的无名基础。这用作所有其他目录,文件,驱动器和设备的附件。

什么是pwd命令?

pwd命令是print working directory命令的缩写。

PWD
/home/guru99/myDir

什么是守护进程?

守护进程是提供基本操作系统下可能无法使用的多种功能的服务。其主要任务是监听服务请求,同时对这些请求采取行动。服务完成后,它将断开连接并等待进一步的请求。

如何从一个桌面环境切换到另一个桌面环境,例如从KDE切换到Gnome?

假设你已安装这两个环境,只需从图形界面注销即可。然后在登录屏幕上,键入你的登录ID和密码,并选择要加载的会话类型。在你将其更改为其他选项之前,此选项将保持默认状态。

Linux下的权限有哪些?

Linux下有3种权限:

  • 读取:用户可以读取文件或列出目录
  • 写入:用户可以写入新文件到目录的文件
  • 执行:用户可以运行文件或查找特定文件一个目录

区分大小写如何影响命令的使用方式?

当我们讨论区分大小写时,只有当每个字符按原样编码时,命令才被认为是相同的,包括小写和大写字母。这意味着CD,CD和Cd是三个不同的命令。使用大写字母输入命令,它应该是小写的,将产生不同的输出。

是否可以使用快捷方式获取长路径名?

就在这里。称为文件名扩展的功能允许你使用TAB键执行此操作。例如,如果你有一个名为/ home / iceman / assignments目录的路径,则键入如下:/ ho [tab] / ice [tab] / assi [tab]。但是,这假设路径是唯一的,并且你正在使用的shell支持此功能。

什么是重定向?

重定向是将数据从一个输出定向到另一个输出的过程。它还可以用于将输出作为输入定向到另一个进程。

什么是grep命令?

grep使用基于模式的搜索的搜索命令。它使用与命令行一起指定的选项和参数,并在搜索所需的文件输出时应用此模式。

当发出的命令与上次使用时产生的结果不同时,会出现什么问题?

从看似相同的命令获得不同结果的一个非常可能的原因与区分大小写问题有关。由于Linux区分大小写,因此先前使用的命令可能以与当前格式不同的格式输入。例如,要列出目录中的所有文件,应键入命令ls,而不是LS。如果没有存在该确切名称的程序,则键入LS将导致错误消息,或者如果存在名为LS的程序执行另一个功能,则可能产生不同的输出。

/ usr / local的内容是什么?

它包含本地安装的文件。此目录在文件存储在网络上的环境中很重要。具体来说,本地安装的文件将转至/ usr / local / bin,/ usr / local / lib等。此目录的另一个应用是它用于从源安装的软件包,或未正式随分发一起提供的软件。

你如何终止正在进行的流程?

系统中的每个进程都由唯一的进程ID或pid标识。使用kill命令后跟pid来终止该进程。

要立即终止所有进程,请使用kill 0。

如何在命令行提示符中插入注释?

通过在实际注释文本之前键入#符号来创建注释。这告诉shell完全忽略后面的内容。例如“#这只是shell将忽略的注释。”

什么是命令分组以及它是如何工作的?

你可以使用括号对命令进行分组。例如,如果要将当前日期和时间以及名为OUTPUT的文件的内容发送到名为MYDATES的第二个文件,可以按如下方式应用命令分组:(date cat OUTPUT)> MYDATES

如何从单个命令行条目执行多个命令或程序?

你可以通过使用分号符号分隔每个命令或程序来组合多个命令。例如,你可以在单个条目中发出这样一系列命令:

ls –l cd .. ls –a MYWORK which is equivalent to 3 commands: ls -l cd.. ls -a MYWORK
**请注意,这将按指定的顺序依次执行。

编写一个命令,查找扩展名为“c”的文件,并在其中出现字符串“apple”?

find ./ -name "*.c" | xargs grep –i "apple"

编写一个显示所有.txt文件的命令,包括其个人权限。

ls -al * .txt

解释如何为Git控制台着色?

要为Git控制台着色,可以使用命令git config-global color.ui auto。在命令中,color.ui变量设置变量的默认值,例如color.diff和color.grep。

如何在Linux中将一个文件附加到另一个文件?

要在Linux中将一个文件附加到另一个文件,你可以使用命令cat file2 >> file 1. operator >>附加指定文件的输出或创建文件(如果未创建)。而另一个命令cat文件1文件2>文件3将两个或多个文件附加到一个文件。

解释如何使用终端找到文件?

要查找文件,你必须使用命令,查找。-name“process.txt”。它将查找名为process.txt的文件的当前目录。

解释如何使用终端创建文件夹?

要创建文件夹,你必须使用命令mkdir。它将是这样的:〜$ mkdir Guru99

解释如何使用终端查看文本文件?

要查看文本文件,请使用命令cd转到文本文件所在的特定文件夹,然后键入less filename.txt。

解释如何在Ubuntu LAMP堆栈上启用curl?

要在Ubuntu上启用curl,首先安装libcurl,完成后使用以下命令sudo /etc/init .d / apache2 restart或sudo service apache2 restart。

解释如何在Ubuntu中启用root日志记录?

启用root日志记录的命令是

#sudo sh-c'echo“greater-show-manual-login = true”>> / etc / lightdm / lightdm.conf'

如何在启动Linux服务器的同时在后台运行Linux程序?

通过使用nohup。它将停止接收NOHUP信号的进程,从而终止它,你注销了调用的程序。并在后台运行该过程。

解释如何在Linux中卸载库?

要在Linux中卸载库,可以使用命令

sudo apt-get remove library_name

15.2 Docker

什么是虚拟化技术?

在计算机技术中,虚拟化(Virtualization)是一种资源管理技术。它是将计算机的各种实体资源,如:服务器、网络、内存及存储等,予以抽象、转换后呈现出来,打破实体结构间的不可切割的障碍,使用户可以用更好的方式来利用这些资源。

虚拟化的目的是为了在同一个主机上运行多个系统或应用,从而提高系统资源的利用率,并带来降低成本、方便管理和容错容灾等好处。

  • 硬件虚拟化

硬件虚拟化就是硬件物理平台本身提供了对特殊指令的截获和重定向的支持。支持虚拟化的硬件,也是一些基于硬件实现软件虚拟化技术的关键。在基于硬件实现软件虚拟化的技术中,在硬件是实现虚拟化的基础,硬件(主要是CPU)会为虚拟化软件提供支持,从而实现硬件资源的虚拟化。

  • 软件虚拟化

软件虚拟化就是利用软件技术,在现有的物理平台上实现对物理平台访问的截获和模拟。在软件虚拟化技术中,有些技术不需要硬件支持,如:QEMU;而有些软件虚拟化技术,则依赖硬件支持,如:VMware、KVM。

什么是Docker?

Docker是一个开源的应用容器引擎,它让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到安装了任何 Linux 发行版本的机器上。Docker基于LXC来实现类似VM的功能,可以在更有限的硬件资源上提供给用户更多的计算资源。与同VM等虚拟化的方式不同,LXC不属于全虚拟化、部分虚拟化或半虚拟化中的任何一个分类,而是一个操作系统级虚拟化。

Docker是直接运行在宿主操作系统之上的一个容器,使用沙箱机制完全虚拟出一个完整的操作,容器之间不会有任何接口,从而让容器与宿主机之间、容器与容器之间隔离的更加彻底。每个容器会有自己的权限管理,独立的网络与存储栈,及自己的资源管理能,使同一台宿主机上可以友好的共存多个容器。

Docker借助Linux的内核特性,如:控制组(Control Group)、命名空间(Namespace)等,并直接调用操作系统的系统调用接口。从而降低每个容器的系统开销,并实现降低容器复杂度、启动快、资源占用小等特征。

Docker和虚拟机的区别?

虚拟机Virtual Machine与容器化技术(代表Docker)都是虚拟化技术,两者的区别在于虚拟化的程度不同。

  • 举个例子
  1. 服务器:比作一个大型的仓管基地,包含场地与零散的货物——相当于各种服务器资源。
  2. 虚拟机技术:比作仓库,拥有独立的空间堆放各种货物或集装箱,仓库之间完全独立——仓库相当于各种系统,独立的应用系统和操作系统。
  3. Docker:比作集装箱,操作各种货物的打包——将各种应用程序和他们所依赖的运行环境打包成标准的容器,容器之间隔离。
  • 基于一个图解释

  1. 虚拟机管理系统(Hypervisor)。利用Hypervisor,可以在主操作系统之上运行多个不同的从操作系统。类型1的Hypervisor有支持MacOS的HyperKit,支持Windows的Hyper-V以及支持Linux的KVM。类型2的Hypervisor有VirtualBox和VMWare。
  2. Docker守护进程(Docker Daemon)。Docker守护进程取代了Hypervisor,它是运行在操作系统之上的后台进程,负责管理Docker容器。
  3. vm多了一层guest OS,虚拟机的Hypervisor会对硬件资源也进行虚拟化,而容器Docker会直接使用宿主机的硬件资源
  • 基于虚拟化角度
  1. 隔离性 由于vm对操作系统也进行了虚拟化,隔离的更加彻底。而Docker共享宿主机的操作系统,隔离性较差。
  2. 运行效率 由于vm的隔离操作,导致生成虚拟机的速率大大低于容器Docker生成的速度,因为Docker直接利用宿主机的系统内核。因为虚拟机增加了一层虚拟硬件层,运行在虚拟机上的应用程序在进行数值计算时是运行在Hypervisor虚拟的CPU上的;另外一方面是由于计算程序本身的特性导致的差异。虚拟机虚拟的cpu架构不同于实际cpu架构,数值计算程序一般针对特定的cpu架构有一定的优化措施,虚拟化使这些措施作废,甚至起到反效果。
  3. 资源利用率 在资源利用率上虚拟机由于隔离更彻底,因此利用率也会相对较低。

Docker的架构?

Docker 使用客户端-服务器 (C/S) 架构模式,使用远程API来管理和创建Docker容器。

  • Docker 客户端(Client) : Docker 客户端通过命令行或者其他工具使用 Docker SDK (docs.docker.com/develop/sdk…) 与 Docker 的守护进程通信。
  • Docker 主机(Host) :一个物理或者虚拟的机器用于执行 Docker 守护进程和容器。

Docker 包括三个基本概念:

  • 镜像(Image) :Docker 镜像(Image),就相当于是一个 root 文件系统。比如官方镜像 ubuntu:16.04 就包含了完整的一套 Ubuntu16.04 最小系统的 root 文件系统。
  • 容器(Container) :镜像(Image)和容器(Container)的关系,就像是面向对象程序设计中的类和实例一样,镜像是静态的定义,容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等。
  • 仓库(Repository) :仓库可看着一个代码控制中心,用来保存镜像。

Docker镜像相关操作有哪些?

# 查找镜像
docker search mysql
​
# 拉取镜像
docker pull mysql
​
# 删除镜像
docker rmi hello-world
​
# 更新镜像
docker commit -m="update test" -a="pdai" 0a1556ca3c27  pdai/ubuntu:v1.0.1
​
# 生成镜像
docker build -t pdai/ubuntu:v2.0.1 .
​
# 镜像标签
docker tag a733d5a264b5 pdai/ubuntu:v3.0.1
​
# 镜像导出
docker save > pdai-ubuntu-v2.0.2.tar 57544a04cd1a
​
# 镜像导入
docker load < pdai-ubuntu-v2.0.2.tar

Docker容器相关操作有哪些?

# 容器查看
docker ps -a
​
# 容器启动
docker run -it pdai/ubuntu:v2.0.1 /bin/bash
​
# 容器停止
docker stop f5332ebce695
​
# 容器再启动
docker start f5332ebce695
​
# 容器重启
docker restart f5332ebce695
​
# 容器导出
docker export f5332ebce695 > ubuntu-pdai-v2.tar
​
# 容器导入
docker import ubuntu-pdai-v2.tar pdai/ubuntu:v2.0.2
​
# 容器强制停止并删除
docker rm -f f5332ebce695
​
# 容器清理
docker container prune
​
# 容器别名操作
docker run -itd --name pdai-ubuntu-202 pdai/ubuntu:v2.0.2 /bin/bash

如何查看Docker容器的日志?

#例:实时查看docker容器名为user-uat的最后10行日志
docker logs -f -t --tail 10 user-uat
​
#例:查看指定时间后的日志,只显示最后100行:
docker logs -f -t --since="2018-02-08" --tail=100 user-uat
​
#例:查看最近30分钟的日志:
docker logs --since 30m user-uat
​
#例:查看某时间之后的日志:
docker logs -t --since="2018-02-08T13:23:37" user-uat
​
#例:查看某时间段日志:
docker logs -t --since="2018-02-08T13:23:37" --until "2018-02-09T12:23:37" user-uat
​
#例:将错误日志写入文件:
docker logs -f -t --since="2018-02-18" user-uat | grep error >> logs_error.txt

如何启动Docker容器?参数含义?

[root@pdai docker-test]# docker run -itd pdai/ubuntu:v2.0.1 /bin/bash
  • -it 可以连写的,表示 -i -t
  • -t: 在新容器内指定一个伪终端或终端。
  • -i: 允许你对容器内的标准输入 (STDIN) 进行交互
  • -d: 后台模式

如何进入Docker后台模式?有什么区别?

  • 第一种:docker attach
[root@pdai ~]# docker ps
CONTAINER ID        IMAGE                COMMAND             CREATED             STATUS              PORTS               NAMES
f5332ebce695        pdai/ubuntu:v2.0.1   "/bin/bash"         38 minutes ago      Up 2 seconds        22/tcp, 80/tcp      jolly_kepler
[root@pdai ~]# docker attach f5332ebce695
root@f5332ebce695:/# echo 'pdai'
pdai
root@f5332ebce695:/# exit
exit
[root@pdai ~]# docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

看到没,使用docker attach进入后,exit便容器也停止了。

  • 第二种:docker exec
[root@pdai ~]# docker exec -it f5332ebce695 /bin/bash
Error response from daemon: Container f5332ebce69520fba353f035ccddd4bd42055fbd1e595f916ba7233e26476464 is not running
[root@pdai ~]# docker restart f5332ebce695
f5332ebce695
[root@pdai ~]# docker exec -it f5332ebce695 /bin/bash
root@f5332ebce695:/# exit
exit
[root@pdai ~]# docker ps
CONTAINER ID        IMAGE                COMMAND             CREATED             STATUS              PORTS               NAMES
f5332ebce695        pdai/ubuntu:v2.0.1   "/bin/bash"         42 minutes ago      Up 8 seconds        22/tcp, 80/tcp      jolly_kepler

注意:

  • 我特意在容器停止状态下执行了docker exec,是让你看到docker exec是在容器启动状态下用的,且注意下错误信息;
  • 推荐大家使用 docker exec 命令,因为此退出容器终端,不会导致容器的停止。

15.3 CI/CD

什么是CI?

CI的英文名称是Continuous Integration,中文翻译为:持续集成。

CI中,开发人员将会频繁地向主干提交代码,这些新提交的代码在最终合并到主干前,需要经过编译和自动化测试流进行验证。 持续集成(CI)是在源代码变更后自动检测、拉取、构建和(在大多数情况下)进行单元测试的过程。持续集成的目标是快速确保开发人员新提交的变更是好的,并且适合在代码库中进一步使用。CI的流程执行和理论实践让我们可以确定新代码和原有代码能否正确地集成在一起。

通俗点讲就是:通过持续集成, 开发人员能够在任何时候多次向仓库提交作品,而不是独立地开发每个功能模块并在开发周期结束时一一提交。这里的一个重要思想就是让开发人员更快更、频繁地做到这一点,从而降低集成的开销。 实际情况中,开发人员在集成时经常会发现新代码和已有代码存在冲突。 如果集成较早并更加频繁,那么冲突将更容易解决且执行成本更低。当然,这里也有一些权衡,这个流程不提供额外的质量保障。 事实上,许多组织发现这样的集成方式开销更大,因为它们依赖人工确保新代码不会引起新的 bug 或者破坏现有代码。 为了减少集成期间的摩擦,持续集成依赖于测试套件和自动化测试。 然而,要认识到自动化测试和持续测试是完全不同的这一点很重要。

CI 的目标是将集成简化成一个简单、易于重复的日常开发任务, 这样有助于降低总体的构建成本并在开发周期的早期发现缺陷。 要想有效地使用 CI 必须转变开发团队的习惯,要鼓励频繁迭代构建, 并且在发现 bug 的早期积极解决。

什么是CD?

这里的CD可对应多个英文名称,持续交付Continuous Delivery持续部署Continuous Deployment。下面我们分别来看看上面是持续交付和持续部署。

  • 持续交付

持续交付(CD)实际上是 CI 的扩展,其中软件交付流程进一步自动化,以便随时轻松地部署到生成环境中。 成熟的持续交付方案也展示了一个始终可部署的代码库。使用 CD 后,软件发布将成为一个没有任何紧张感的例行事件。 开发团队可以在日常开发的任何时间进行产品级的发布,而不需要详细的发布方案或者特殊的后期测试。

完成 CI 中构建及单元测试和集成测试的自动化流程后,持续交付可自动将已验证的代码发布到存储库。为了实现高效的持续交付流程,务必要确保 CI 已内置于开发管道。持续交付的目标是拥有一个可随时部署到生产环境的代码库。

在持续交付中,每个阶段(从代码更改的合并,到生产就绪型构建版本的交付)都涉及测试自动化和代码发布自动化。在流程结束时,运维团队可以快速、轻松地将应用部署到生产环境中或发布给最终使用的用户。

CD 集中依赖于部署流水线,团队通过流水线自动化测试和部署过程。此流水线是一个自动化系统, 可以针对构建执行一组渐进的测试套件。CD 具有高度的自动化,并且在一些云计算环境中也易于配置。在流水线的每个阶段,如果构建无法通过关键测试会向团队发出警报。否则,将继续进入下一个测试, 并在连续通过测试后自动进入下一个阶段。流水线的最后一个部分会将构建部署到和生产环境等效的环境中。 这是一个整体的过程,因为构建、部署和环境都是一起执行和测试的,它能让构建在实际的生产环境可部署和可验证。

  • 持续部署

持续部署扩展了持续交付,以便软件构建在通过所有测试时自动部署。在这样的流程中, 不需要人为决定何时及如何投入生产环境。CI/CD 系统的最后一步将在构建后的组件/包退出流水线时自动部署。 此类自动部署可以配置为快速向客户分发组件、功能模块或修复补丁,并准确说明当前提供的内容。采用持续部署的组织可以将新功能快速传递给用户,得到用户对于新版本的快速反馈,并且可以迅速处理任何明显的缺陷。 用户对无用或者误解需求的功能的快速反馈有助于团队规划投入,避免将精力集中于不容易产生回报的地方。

随着 DevOps 的发展,新的用来实现 CI/CD 流水线的自动化工具也在不断涌现。这些工具通常能与各种开发工具配合, 包括像 GitHub 这样的代码仓库和 Jira 这样的 bug 跟踪工具。此外,随着 SaaS 这种交付方式变得更受欢迎, 许多工具都可以在现代开发人员运行应用程序的云环境中运行,例如 GCP 和 AWS。但是对于一个成熟的CI/CD管道(Pipeline)来说,最后的阶段是持续部署。作为持续交付——自动将生产就绪型构建版本发布到代码存储库——的延伸,持续部署可以自动将应用发布到生产环境。

什么是CI/CD的管道?

CI / CD管道是与自动化工具和改进的工作流程集成的部署管道。 如果执行得当,它将最大程度地减少人为错误,并增强整个SDLC的反馈循环,使团队可以在更短的时间内交付较小的发行版。

典型的CI / CD管道必须包括以下阶段:

  • 构建阶段
  • 测试阶段
  • 部署阶段
  • 自动化测试阶段
  • 部署到生产

如何理解DevOPS?

DevOps是Development和Operations的组合,是一种方法论,是一组过程、方法与系统的统称,用于促进应用开发、应用运维和质量保障(QA)部门之间的沟通、协作与整合。以期打破传统开发和运营之间的壁垒和鸿沟。

CI、CD和DevOps之间的关系 :

在完全部署到所有用户之前,有哪些方法可以测试部署?

由于必须回滚/撤消对所有用户的部署可能是一种代价高昂的情况(无论是技术上还是用户的感知),已经有许多技术允许“尝试”部署新功能并在发现问题时轻松“撤消”它们。这些包括:

  • 蓝/绿测试/部署

在这种部署软件的方法中,维护了两个相同的主机环境 —— 一个“蓝色” 和一个“绿色”。(颜色并不重要,仅作为标识。)对应来说,其中一个是“生产环境”,另一个是“预发布环境”。

在这些实例的前面是调度系统,它们充当产品或应用程序的客户“网关”。通过将调度系统指向蓝色或绿色实例,可以将客户流量引流到期望的部署环境。通过这种方式,切换指向哪个部署实例(蓝色或绿色)对用户来说是快速,简单和透明的。

当新版本准备好进行测试时,可以将其部署到非生产环境中。在经过测试和批准后,可以更改调度系统设置以将传入的线上流量指向它(因此它将成为新的生产站点)。现在,曾作为生产环境实例可供下一次候选发布使用。

同理,如果在最新部署中发现问题并且之前的生产实例仍然可用,则简单的更改可以将客户流量引流回到之前的生产实例 —— 有效地将问题实例“下线”并且回滚到以前的版本。然后有问题的新实例可以在其它区域中修复。

  • 金丝雀测试/部署

在某些情况下,通过蓝/绿发布切换整个部署可能不可行或不是期望的那样。另一种方法是为金丝雀测试/部署。在这种模型中,一部分客户流量被重新引流到新的版本部署中。例如,新版本的搜索服务可以与当前服务的生产版本一起部署。然后,可以将 10% 的搜索查询引流到新版本,以在生产环境中对其进行测试。

如果服务那些流量的新版本没问题,那么可能会有更多的流量会被逐渐引流过去。如果仍然没有问题出现,那么随着时间的推移,可以对新版本增量部署,直到 100% 的流量都调度到新版本。这有效地“更替”了以前版本的服务,并让新版本对所有客户生效。

  • 功能开关

对于可能需要轻松关掉的新功能(如果发现问题),开发人员可以添加功能开关。这是代码中的 if-then 软件功能开关,仅在设置数据值时才激活新代码。此数据值可以是全局可访问的位置,部署的应用程序将检查该位置是否应执行新代码。如果设置了数据值,则执行代码;如果没有,则不执行。

这为开发人员提供了一个远程“终止开关”,以便在部署到生产环境后发现问题时关闭新功能。

  • 暗箱发布

在暗箱发布中,代码被逐步测试/部署到生产环境中,但是用户不会看到更改(因此名称中有暗箱一词)。例如,在生产版本中,网页查询的某些部分可能会重定向到查询新数据源的服务。开发人员可收集此信息进行分析,而不会将有关接口,事务或结果的任何信息暴露给用户。

这个想法是想获取候选版本在生产环境负载下如何执行的真实信息,而不会影响用户或改变他们的经验。随着时间的推移,可以调度更多负载,直到遇到问题或认为新功能已准备好供所有人使用。实际上功能开关标志可用于这种暗箱发布机制。

什么是持续测试?

持续测试是一个过程,它将自动化测试作为软件交付通道中内嵌的一部分,以尽快获得软件发布后业务风险的反馈。

持续测试与自动化测试的侧重点

  • 自动化测试旨在生成一组与用户故事或应用程序要求相关的通过/失败的数据点。
  • 持续测试侧重于业务风险,并提供有关软件是否可以发布的判断。要实现这一转变,我们需要停止询问“我们是否已完成测试?”而是集中精力在“发布版本是否具有可接受的业务风险级别?”

为什么我们需要持续测试

今天,整个行业的变化要求测试更多,同时使自动化测试更难实现(至少使用传统工具和方法):

  • 应用程序体系结构越来越分散和复杂,包含云,API,微服务等,并在单个业务事务中创建几乎无限的不同协议和技术组合。
  • 由于Agile,DevOps和持续交付,许多应用程序现在每两周发布一次,每天发布数千次。因此,可用于测试设计,维护和特别是执行的时间大大减少。

既然软件是业务的主要接口,那么应用程序故障就是业务失败, 如果它影响用户体验,即使是看似微不足道的小故障也会产生严重后果。因此,与应用相关的风险已成为即使是非技术性商业领袖的主要关注点。

如何做版本管理?

  • Master 分支 主分支,这个分支最近发布到生产环境的代码,最近发布的Release, 这个分支只能从其他分支合并,不能在这个分支直接修改
  • Develop 分支 这个分支是我们是我们的主开发分支,包含所有要发布到下一个Release的代码,这个主要合并与其他分支,比如Feature分支
  • Feature 分支 这个分支主要是用来开发一个新的功能,一旦开发完成,我们合并回Develop分支进入下一个Release
  • Release 分支 当你需要发布一个新Release的时候,我们基于Develop分支创建一个Release分支,完成Release后,我们合并到Master和Develop分支
  • Hotfix 分支 当我们在Production发现新的Bug时候,我们需要创建一个Hotfix, 完成Hotfix后,我们合并回Master和Develop分支,所以Hotfix的改动会进入下一个Release

15.4 监控体系

为什么要有监控系统? 谈谈你对监控的理解?

监控的目标

  • 发现问题:当系统发生故障报警,我们会收到故障报警的信息。
  • 定位问题:故障邮件一般都会写某某主机故障、具体故障的内容,我们需要对报警内容进行分析。比如一台服务器连不上,我们就需要考虑是网络问题、还是负载太高导致长时间无法连接,又或者某开发触发了防火墙禁止的相关策略等,我们就需要去分析故障具体原因。
  • 解决问题:当然我们了解到故障的原因后,就需要通过故障解决的优先级去解决该故障。
  • 总结问题:当我们解决完重大故障后,需要对故障原因以及防范进行总结归纳,避免以后重复出现。

具体而言

  • 对系统不间断的实时监控:实际上是对系统不间断的实时监控(这就是监控);
  • 实时反馈系统当前状态:我们监控某个硬件、或者某个系统,都是需要能实时看到当前系统的状态,是正常、异常、或者故障。
  • 保证服务可靠性安全性:我们监控的目的就是要保证系统、服务、业务正常运行
  • 保证业务持续稳定运行:如果我们的监控做得很完善,即使出现故障,能第一时间接收到故障报警,在第一时间处理解决,从而保证业务持续性的稳定运行。

监控体系监控哪些内容?

1、硬件监控 通过SNMP来进行路由器交换机的监控(这些可以跟一些厂商沟通来了解如何做)、服务器的温度以及其它,可以通过IPMI来实现。当然如果没有硬件全都是云,直接跳过这一步骤。

2、系统监控 如CPU的负载,上下文切换、内存使用率、磁盘读写、磁盘使用率、磁盘inode使用率。当然这些都是需要配置触发器,因为默认太低会频繁报警。

3、服务监控 比如公司用的LNMP架构,Nginx自带Status模块、PHP也有相关的Status、MySQL的话可以通过Percona官方工具来进行监控。Redis这些通过自身的info获取信息进行过滤等。方法都类似。要么服务自带。要么通过脚本来实现想监控的内容,以及报警和图形功能。

4、网络监控 如果是云主机又不是跨机房,那么可以选择不监控网络。当然你说我们是跨机房以及如何如何,推荐使用smokeping来做网络相关的监控,或者直接交给你们的网络工程师来做,因为术业有专攻。

5、安全监控 如果是云主机可以考虑使用自带的安全防护。当然也可以使用iptables。如果是硬件,那么推荐使用硬件防火墙。使用云可以购买防DDOS,避免出现故障导致down机一天。如果是系统,那么权限、密码、备份、恢复等基础方案要做好。Web同时也可以使用Nginx+Lua来实现一个Web层面的防火墙。当然也可以使用集成好的OpenResty。

6、Web监控 Web监控的话题其实还是很多。比如可以使用自带的Web监控来监控页面相关的延迟、js响应时间、下载时间、等等。这里我推荐使用专业的商业软件监控宝或听云来实现。毕竟人家全国各地都有机房(如果本身是多机房那就另说了)。

7、日志监控 如果是Web的话可以使用监控Nginx的50x、40x的错误日志,PHP的ERROR日志。其实这些需求无非是,收集、存储、查询、展示,我们其实可以使用开源的ELKStack来实现。Logstash(收集)、Elasticsearch(存储+搜索)、Kibana(展示)。

8、业务监控 上面做了那么多,其实最终还是保证业务的运行。这样我们做的监控才有意义。所以业务层面这块的监控需要和开发以及总监开会讨论,监控比较重要的业务指标,(需要开会确认)然后通过简单的脚本就可以实现,最后设置触发器即可 。

9、流量分析 平时我们分析日志都是拿awk sed xxx一堆工具来实现。这样对我们统计IP、PV、UV不是很方便。那么可以使用百度统计、Google统计、商业,让开发嵌入代码即可。为了避免隐私也可以使用Piwik来做相关的流量分析。

10、可视化 通过Screen以及引入一些第三方的库来美化界面,同时我们也需要知道,订单量突然增加、突然减少。或者说突然来了一大波流量,这流量从哪儿来,是不是推广了,还是被攻击了。可以结合监控平来梳理各个系统之间的业务关系。

11、自动化监控 如上我们做了那么多的工作,当然不能是一台一台的来加key实现。可以通过Zabbix的主动模式以及被动模式来实现。当然最好还是通过API来实现。

监控一般采用什么样的流程?

  • 采集 通过SNMP、Agent、ICMP、SSH、IPMI等对系统进行数据采集
  • 存储 各类数据库服务,MySQL、PostgreSQL, 时序库等
  • 分析 提供图形及时间线情况信息,方便我们定位故障所在
  • 展示 指标信息、指标趋势展示
  • 报警 电话、邮件、微信、短信、报警升级机制
  • 处理 故障级别判定,找响应人员进行快速处理

16 其他

16.1 设计模式

16.2 开源协议

说说常见的开源协议?

最流行的六种:MIT、Apache、BSD、GPL和LGPL、Mozilla。

GPL协议、LGPL协议与BSD协议的法律区别?

简而言之,GPL协议就是一个开放源代码协议,软件的初始开发者使用了GPL协议并公开软件的源程序后,后续使用该软件源程序开发软件者亦应当根据GPL协议把自己编写的源程序进行公开。GPL协议要求的关键在于开放源程序,但并不排斥软件作者向用户收费。虽然如此,很多大公司对GPL协议还是又爱又恨,爱的是这个协议项下的软件历经众多程序员千锤百炼的修改,已经非常成熟完善,恨的是必须开放自己后续的源程序,导致竞争对手也可以根据自己修改的源程序开发竞争产品。

正因大公司对GPL协议在商业上存在顾虑,因此,另两种协议被采用的更多,第一种是LGPL(亦称GPL V2)协议,可以翻译为更宽松的GPL协议。与GPL协议的区别为,后者如果只是对LGPL软件的程序库的程序进行调用而不是包含其源代码时,相关的源程序无需开源。调用和包含的区别类似在互联网网网页上对他人网页内容的引用: 如果把他人的内容全部或部分复制到自己的网页上,就类似包含,如果只是贴一个他人网页的网址链接而不引用内容,就类似调用。有了这个协议,很多大公司就可以把很多自己后续开发内容的源程序隐藏起来。

第二种是BSD协议(类似的还有MIT协议)。BSD协议鼓励软件的作者公开自己后续开发的源代码,但不强求。在BSD协议项下开发的软件,原始的源程序是开放源代码的,但使用者修改以后,可以自行选择发布源程序或者二进制程序(即目标程序),当然,使用者有义务把自己原来使用的源程序与BSD协议在软件对外发布时一并发布。因为比较灵活,所以BSD深受大公司的欢迎。

MongoDB修改开源协议?

2018年10月,MongoDB宣布其开源许可证将从GNU AGPLv3,切换到SSPL,新许可证将适用于新版本的MongoDB Community Server以及打过补丁的旧版本。

根据 MongoDB 之前的 GNU AGPLv3 协议,想要将 MongoDB 作为公共服务运行的公司必须将他们的软件开源,或需要从 MongoDB 获得商业许可,”该公司解释说,“然而,MongoDB 的普及使一些组织在违反 GNU AGPLv3 协议的边缘疯狂试探,甚至直接违反了协议。”

尽管 SSPL 与 GNU AGPLv3 没有什么不同,但 SSPL 会明确要求托管 MongoDB 实例的云计算公司要么从 MongoDB 获取商业许可证,要么向社区开源其服务代码。

随后Red Hat宣布,将不会在Red Hat Enterprise Linux或Fedora中使用MongoDB。事实上,MongoDB修改开源协议之后,Red Hat并不是首家弃用的Linux社区。2018年12月5日,Linux发行版Debian在邮件列表中讨论并决定不使用SSPL协议下的软件。2019年1月,Fedora Legal也对SSPL v1协议做出了相关决定,Fedora已确定服务器端公共许可证v1(SSPL)不是自由软件许可证。

16.3 软件理论

什么是CAP理论?

CAP原理指的是,在分布式系统中这三个要素最多只能同时实现两点,不可能三者兼顾。因此在进行分布式架构设计时,必须做出取舍。而对于分布式数据系统,分区容忍性是基本要求,否则就失去了价值。因此设计分布式数据系统,就是在一致性和可用性之间取一个平衡。对于大多数Web应用,其实并不需要强一致性,因此牺牲一致性而换取高可用性,是目前多数分布式数据库产品的方向。

  1. 一致性(Consistency):数据在多个副本之间是否能够保持一致的特性。(当一个系统在一致状态下更新后,应保持系统中所有数据仍处于一致的状态)
  2. 可用性(Availability):系统提供的服务必须一直处于可用状态,对每一个操作的请求必须在有限时间内返回结果。
  3. 分区容错性(Tolerance of network Partition):分布式系统在遇到网络分区故障时,仍然需要保证对外提供一致性和可用性的服务,除非整个网络都发生故障。

为什么只能同时满足两个

例如,服务器中原本存储的value=0,当客户端A修改value=1时,为了保证数据的一致性,要写到3个服务器中,当服务器C故障时,数据无法写入服务器C,则导致了此时服务器A、B和C的value是不一致的。这时候要保证分区容错性,即当服务器C故障时,仍然能保持良好的一致性和可用性服务,则Consistency和Availability不能同时满足。为什么呢?

如果满足了一致性,则客户端A的写操作value=1不能成功,这时服务器中所有value=0。 如果满足可用性,即所有客户端都可以提交操作并得到返回的结果,则此时允许客户端A写入服务器A和B,客户端C将得到未修改之前的value=0结果。

什么是BASE理论?

  1. Basically Available(基本可用)分布式系统在出现不可预知故障的时候,允许损失部分可用性
  2. Soft state(软状态)软状态也称为弱状态,和硬状态相对,是指允许系统中的数据存在中间状态,并认为该中间状态的存在不会影响系统的整体可用性,即允许系统在不同节点的数据副本之间进行数据同步的过程存在延时。
  3. Eventually consistent(最终一致性)最终一致性强调的是系统中所有的数据副本,在经过一段时间的同步后,最终能够达到一个一致的状态。因此,最终一致性的本质是需要系统保证最终数据能够达到一致,而不需要实时保证系统数据的强一致性

CAP 与 BASE 关系

BASE是对CAP中一致性和可用性权衡的结果,其来源于对大规模互联网系统分布式实践的结论,是基于CAP定理逐步演化而来的,其核心思想是即使无法做到强一致性(Strong consistency),更具体地说,是对 CAP 中 AP 方案的一个补充。其基本思路就是:通过业务,牺牲强一致性而获得可用性,并允许数据在一段时间内是不一致的,但是最终达到一致性状态。

CAP 与 ACID 关系

ACID 是传统数据库常用的设计理念,追求强一致性模型。BASE 支持的是大型分布式系统,提出通过牺牲强一致性获得高可用性。

ACID 和 BASE 代表了两种截然相反的设计哲学,在分布式系统设计的场景中,系统组件对一致性要求是不同的,因此 ACID 和 BASE 又会结合使用。

什么是SOLID原则?

  • S单一职责SRP Single-Responsibility Principle

一个类,最好只做一件事,只有一个引起它的变化。单一职责原则可以看做是低耦合,高内聚在面向对象原则的引申,将职责定义为引起变化的原因,以提高内聚性减少引起变化的原因。

比如: SpringMVC 中Entity,DAO,Service,Controller, Util等的分离。

  • O开放封闭原则OCP Open - Closed Principle

对扩展开放,对修改关闭(设计模式的核心原则)

比如: 设计模式中模板方法模式和观察者模式都是开闭原则的极好体现

  • L里氏替换原则LSP Liskov Substitution Principle

任何基类可以出现的地方,子类也可以出现;这一思想表现为对继承机制的约束规范,只有子类能够替换其基类时,才能够保证系统在运行期内识别子类,这是保证继承复用的基础。

比如:正方形是长方形是理解里氏代换原则的经典例子。(讲的是基类和子类的关系,只有这种关系存在时,里氏代换原则才存在)

  • I接口隔离法则ISL Interface Segregation Principle

客户端不应该依赖那些它不需要的接口。(接口隔离原则是指使用多个专门的接口,而不使用单一的总接口; 这个法则与迪米特法则是相通的)

  • D依赖倒置原则DIP Dependency-Inversion Principle

要依赖抽象,而不要依赖具体的实现, 具体而言就是高层模块不依赖于底层模块,二者共同依赖于抽象。抽象不依赖于具体, 具体依赖于抽象。

什么是合成/聚合复用原则?

Composite/Aggregate ReusePrinciple ,CARP: 要尽量使用对象组合,而不是继承关系达到软件复用的目的。

组合/聚合可以使系统更加灵活,类与类之间的耦合度降低,一个类的变化对其他类造成的影响相对较少,因此一般首选使用组合/聚合来实现复用;其次才考虑继承,在使用继承时,需要严格遵循里氏代换原则,有效使用继承会有助于对问题的理解,降低复杂度,而滥用继承反而会增加系统构建和维护的难度以及系统的复杂度,因此需要慎重使用继承复用。

此原则和里氏代换原则氏相辅相成的,两者都是具体实现"开-闭"原则的规范。违反这一原则,就无法实现"开-闭"原则。

什么是迪米特法则?

Law of Demeter,LoD: 系统中的类,尽量不要与其他类互相作用,减少类之间的耦合度.

又叫最少知识原则(Least Knowledge Principle或简写为LKP).

  • 不要和“陌生人”说话。英文定义为: Don't talk to strangers.
  • 只与你的直接朋友通信。英文定义为: Talk only to your immediate friends.

比如:外观模式Facade(结构型)

什么是康威定律?

康威在一篇文章中描述: 设计系统的组织,其产生的设计等同于组织之内、组织之间的沟通结构。

  • 定律一: 组织沟通方式会通过系统设计表达出来,就是说架构的布局和组织结构会有相似。
  • 定律二: 时间再多一件事情也不可能做的完美,但总有时间做完一件事情。一口气吃不成胖子,先搞定能搞定的。
  • 定律三: 线型系统和线型组织架构间有潜在的异质同态特性。种瓜得瓜,做独立自治的子系统减少沟通成本。
  • 定律四: 大的系统组织总是比小系统更倾向于分解。合久必分,分而治之。

16.4 软件成熟度模型

什么是CMM?

由美国卡内基梅隆大学的软件工程研究所(SEI)创立的CMM(Capability Maturity Model 软件能力成熟度模型)认证评估,在过去的十几年中,对全球的软件产业产生了非常深远的影响。CMM共有五个等级,分别标志着软件企业能力成熟度的五个层次。从低到高,软件开发生产计划精度逐级升高,单位工程生产周期逐级缩短,单位工程成本逐级降低。据SEI统计,通过评估的软件公司对项目的估计与控制能力约提升40%到50%;生产率提高10%到20%,软件产品出错率下降超过1/3。

对一个软件企业来说,达到CMM2就基本上进入了规模开发,基本具备了一个现代化软件企业的基本架构和方法,具备了承接外包项目的能力。CMM3评估则需要对大软件集成的把握,包括整体架构的整合。一般来说,通过CMM认证的级别越高,其越容易获得用户的信任,在国内、国际市场上的竞争力也就越强。因此,是否能够通过CMM认证也成为国际上衡量软件企业工程开发能力的一个重要标志。

CMM是目前世界公认的软件产品进入国际市场的通行证,它不仅仅是对产品质量的认证,更是一种软件过程改善的途径。参与CMM评估的博科负责人表示,通过CMM的评估认证不是目标,它只是推动软件企业在产品的研发、生产、服务和管理上不断成熟和进步的手段,是一种持续提升和完善企业自身能力的过程。此次由美国PIA咨询公司负责评估并最终通过CMM3认证,标志着博科在质量管理的能力已经上升到一个新的高度。

什么是CMMI5 呢?

CMMI全称是Capability Maturity Model Integration, 即软件能力成熟度模型集成模型,是由美国国防部与卡内基-梅隆大学和美国国防工业协会共同开发和研制的。CMMI是一套融合多学科的、可扩充的产品集合, 其研制的初步动机是为了利用两个或多个单一学科的模型实现一个组织的集成化过程改进

CMMI分为五个等级,二十五个过程区域(PA)。

1. 初始级 软件过程是无序的,有时甚至是混乱的,对过程几乎没有定义,成功取决于个人努力。管理是反应式的。

2. 已管理级 建立了基本的项目管理过程来跟踪费用、进度和功能特性。制定了必要的过程纪律,能重复早先类似应用项目取得的成功经验。

3. 已定义级 已将软件管理和工程两方面的过程文档化、标准化,并综合成该组织的标准软件过程。所有项目均使用经批准、剪裁的标准软件过程来开发和维护软件,软件产品的生产在整个软件过程是可见的。

4. 量化管理级 分析对软件过程和产品质量的详细度量数据,对软件过程和产品都有定量的理解与控制。管理有一个作出结论的客观依据,管理能够在定量的范围内预测性能。

5. 优化管理级 过程的量化反馈和先进的新思想、新技术促使过程持续不断改进。

每个等级都被分解为过程域,特殊目标和特殊实践,通用目标、通用实践和共同特性:

每个等级都有几个过程区域组成,这几个过程域共同形成一种软件过程能力。每个过程域,都有一些特殊目标和通用目标,通过相应的特殊实践和通用实践来实现这些目标。当一个过程域的所有特殊实践和通用实践都按要求得到实施,就能实现该过程域的目标。

CMMI与CMM的区别呢?

CMM是指“能力成熟度模型”,其英文全称为Capability Maturity Model for Software;

CMMI 是指“能力成熟度模型集成”,全称为:Capability Maturity Model Integration;

CMMI是系统工程和软件工程的集成成熟度模型,CMMI更适合于信息系统集成企业。CMMI是在CMM基础上发展起来的,它继承并发扬了CMM的优良特性,借鉴了其他模型的优点,融入了新的理论和实际研究成果。它不仅能够应用在软件工程领域,而且可以用于系统工程及其他工程领域。

CMM与ISO9000的主要区别?

1.CMM是专门针对软件产品开发和服务的,而ISO9000涉及的范围则相当宽。

2.CMM强调软件开发过程的成熟度,即过程的不断改进和提高。而ISO9000则强调可接收的质量体系的最低标准。

16.5 等级保护

为什么是做等级保护?

  1. 法律法规要求

《网络安全法》明确规定信息系统运营、使用单位应当按照网络安全等级保护制度要求,履行安全保护义务,如果拒不履行,将会受到相应处罚。

第二十一条:国家实行网络安全等级保护制度。网络运营者应当按照网络安全等级保护制度的要求,履行下列安全保护义务,保障网络免受干扰、破坏或者未经授权的访问,防止网络数据泄露或者被窃取、篡改。

  1. 行业要求

在金融、电力、广电、医疗、教育等行业,主管单位明确要求从业机构的信息系统(APP)要开展等级保护工作。

  1. 企业系统安全的需求

信息系统运营、使用单位通过开展等级保护工作可以发现系统内部的安全隐患与不足之处,可通过安全整改提升系统的安全防护能力,降低被攻击的风险。

简单来说,《网络安全法》一直对网站、信息系统、APP有等级保护要求,中小型企业通常是行业要求才意识到问题。

等级保护分为哪些等级?

  • 第一级 自主保护级

(无需备案,对测评周期无要求)此类信息系统受到破坏后,会对公民、法人和其他组织的合法权益造成一般损害,不损害国家安全、社会秩序和公共利益。

  • 第二级 指导保护级:

(公安部门备案,建议两年测评一次)此类信息系统受到破坏后,会对公民、法人和其他组织的合法权益造成严重损害。会对社会秩序、公共利益造成一般损害,不损害国家安全。

  • 第三级 监督保护级

(公安部门备案,要求每年测评一次)此类信息系统受到破坏后,会对国家安全、社会秩序造成损害,对公共利益造成严重损害,对公民、法人和其他组织的合法权益造成特别严重的损害。

  • 第四级 强制保护级

(公安部门备案,要求半年一次)此类信息系统受到破坏后,会对国家安全造成严重损害,对社会秩序、公共利益造成特别严重损害。

  • 第五级 专控保护级

(公安部门备案,依据特殊安全需求进行)此类信息系统受到破坏后会对国家安全造成特别严重损害。

怎么做等级保护?

  • 等级保护通常需要5个步骤
  1. 定级(企业自主定级-专家评审-主管部门审核-公安机关审核)
  2. 备案(企业提交备案材料-公安机关审核-发放备案证明)
  3. 测评(等级测评-三级每年测评一次)
  4. 建设整改(安全建设-安全整改)
  5. 监督检查(公安机关每年监督检查)
  • 企业自己如何做等级保护
  1. 在定级备案的步骤,一级不需要备案仅需企业自主定级。二级、三级是大部分普通企业的信息系统定级。四级、五级普通企业不会涉及,通常是与国家相关(如等保四级-涉及民生的,如铁路、能源、电力等)的重要系统。根据地区不同备案文件修改递交通常需要1个月左右的时间。
  2. 定级备案后,寻找本地区测评机构进行等级测评。
  3. 根据测评评分(GBT22239-2019信息安全技术网络安全等级保护基本要求。具体分数需要测评后才能给出)对信息系统(APP)进行安全整改,如果企业没有专业的安全团队,需要寻找安全公司进行不同项目的整改。等级保护2.0三级有211项内容,通常企业需要根据自身情况采购安全产品完成整改。
  4. 进行安全建设整改后,通过测评。当地公安机关会进行监督检查包含定级备案测评、测评后抽查。

整个流程企业自行做等级保护,顺利的话3-4个月完成,如果不熟悉需要半年甚至更久。

等保三的基本要求?

说说等级保护三级的技术要求,主要包含五个部门

  • 物理安全

保证物理的安全,比如物理位置,机房的访问安全;涉及访问控制,防火防盗防雷防电磁,保备用电等

  • 网络安全

保证网络层面安全,比如访问控制,安全审计,入侵防范,恶意代码防范,设备防范等。

  • 主机安全

比如,身份鉴别,访问控制,安全审计,剩余信息保护(比如退出时清理信息),安全审计,入侵防范等。

  • 应用安全

比如,数据完整性,数据保密性(加密),数据备份和回复。

16.6 ISO27001

什么是ISO27001?

信息安全管理体系标准(ISO27001)可有效保护信息资源,保护信息化进程健康、有序、可持续发展。ISO27001是信息安全领域的管理体系标准,类似于质量管理体系认证的 ISO9000标准。当您的组织通过了ISO27001的认证,就相当于通过ISO9000的质量认证一般,表示您的组织信息安全管理已建立了一套科学有效的管理体系作为保障。

ISO27001认证流程?

第一阶段:现状调研

从日常运维、管理机制、系统配置等方面对贵公司信息安全管理安全现状进行调研,通过培训使贵公司相关人员全面了解信息安全管理的基本知识。

第二阶段:风险评估

对贵公司信息资产进行资产价值、威胁因素、脆弱性分析,从而评估贵公司信息安全风险,选择适当的措施、方法实现管理风险的目的。

第三阶段:管理策划

根据贵公司对信息安全风险的策略,制定相应信息安全整体规划、管理规划、技术规划等,形成完整的信息安全管理系统。

第四阶段:体系实施

ISMS建立起来(体系文件正式发布实施)之后,要通过一定时间的试运行来检验其有效性和稳定性。

第五阶段:认证审核

经过一定时间运行,ISMS达到一个稳定的状态,各项文档和记录已经建立完备,此时,可以提请进行认证。