写点什么

架构师训练营」第 11 周作业

用户头像
edd
关注
发布于: 2020 年 08 月 24 日
架构师训练营」第 11 周作业

导致系统不可用的原因有哪些?保障系统稳定高可用的方案有哪些?请分别列举并简述。

导致系统不可用的原因:

硬件层面

  • 硬件故障

  • 内存、硬盘、网卡等

软件层面

  • 操作系统安全、性能漏洞

  • 基础组件安全、性能漏洞

  • 如 Tomcat, Nginx, Jenkins(几乎每周都会有新的漏洞)

软件 bug

  • 系统崩溃。个别崩溃可可从服务集群中摘除。大范围崩溃可能是程序bug或负载过高,需通过模拟复现,问题并定位解决

  • 应用cpu或内存使用率过高。可通过profile工具定位繁忙进程/线程

  • 网络可用性降低,包括延迟、丢包等。网络质量检测,及时修复或启用备用线路。

软件高并发导致的不可用

  • 系统负载超过其极限。系统限流+及时扩容

网络黑客攻击

  • XSS/SQL Injection/CRSF/Error code echo

外界环境层面

  • 地震、火灾

  • 电子、电磁干扰

社会宗教层面

  • 游行、示威

  • 某国外大型网站就在黑人游行疫情期间,大量服务器被抢,导致网站服务不稳定。

  • 恐怖组织袭击,大量服务器被炸坏,导致网站服务不稳定。



保障系统稳定高可用的方案:

通用设计方法

  • Scale-out(横向扩展):组合多台性能机器组成一个分布式集群来共同抵御并发流量的冲击(1台4核4G->2台4核4G)

  • Scale-up(纵向扩展):追逐摩尔定律不断提升CPU性能(4核4G->8核8G)

  • 缓存:磁盘寻道花费时间在毫秒级、CPU指令和内存的寻址在纳秒级、千兆网络读取数据时间在微秒级,不同的存储介质的性能不通

  • 异步

架构分层

  • 什么是分层架构

  • 将整体系统拆分成N个层次

  • 例如:MVC、OSI网络模型、TCP/IP协议、Linux文件系统

  • 分层有什么好处

  • 简化系统,让不同的人专注做某一层次的事情

  • 分层可以提升代码的复用

  • 分层更容易做横向的扩展

  • 如何做系统分层

  • 定义清楚每一层次的边界是什么

  • 层次之间一定是相邻层相互依赖,数据的流转也只能在相邻的两层之间流转。

  • 举例:表现层和业务层都可以直接操作数据库,当数据库地址发生变更,就需要在多个层次做更改,失去分层的意义、后面维护和重构是灾难性的

  • 分层架构的不足

  • 增加了代码的复杂度

  • 如果每个层次独立部署,层次通过网络来交互,多层的架构在性能上会有损耗



系统怎样做到高可用?

  • 高可用性

  • 它指的是系统具备较高的无故障运行的能力

  • 可用性的度量

  • MTBF(Mean Time Between Failure)是平均故障间隔的意思,代表两次故障的间隔时间,也就是系统正常运转的平均时间。这个时间越长,系统稳定性越高。

  • MTTR(Mean Time To Repair)表示故障的平均恢复时间,也可以理解为平均故障时间。这个值越小,故障对于用户的影响越小。

  • 可用性与 MTBF 和 MTTR 的值息息相关Availability = MTBF / (MTBF + MTTR)

  • 一般来说,我们的核心业务系统的可用性,需要达到四个九,非核心系统的可用性最多容忍到三个九。

  • 高可用系统设计思路

  • 系统设计failover(故障转移)

  • 超时控制

  • 降级

  • 限流

  • 系统运维

  • 灰度发布

  • 灰度发布指的是系统的变更不是一次性地推到线上的,而是按照一定比例逐步推进的。

  • 一般情况下,灰度发布是以机器维度进行的。比方说,我们先在 10% 的机器上进行变更,同时观察 Dashboard 上的系统性能指标以及错误日志。如果运行了一段时间之后系统指标比较平稳并且没有出现大量的错误日志,那么再推动全量变更。

  • 故障演练

  • 故障演练指的是对系统进行一些破坏性的手段,观察在出现局部故障时,整体的系统表现是怎样的,从而发现系统中存在的,潜在的可用性问题

  • 举例:Chaos Monkey

  • 不要过渡追求高可用

  • 提高系统的可用性有时候是以牺牲用户体验或者是牺牲系统性能为前提的,也需要大量人力来建设相应的系统,完善机制。

  • 特殊业务不追求性能,只追求极致的可用性

  • 比如配置下发的系统

如何让系统易于扩展

  • 为什么提升扩展性很复杂

  • 在单机系统中通过增加处理核心的方式,来增加系统的并行处理能力当并行的任务数较多时,系统会因为争抢资源而达到性能上的拐点,系统处理能力不升反降。

  • 集群系统中,不同的系统分层上可能存在一些“瓶颈点”,这些瓶颈点制约着系统的横线扩展能力。如数据库瓶颈、千兆带宽的限制

  • 无状态的服务和组件更易于扩展,而像 MySQL 这种存储服务是有状态的,就比较难以扩展。因为向存储集群中增加或者减少机器时,会涉及大量数据的迁移,而一般传统的关系型数据库都不支持。这就是为什么提升系统扩展性会很复杂的主要原因

  • 我们需要站在整体架构的角度,而不仅仅是业务服务器的角度来考虑系统的扩展性 。所以说,数据库、缓存、依赖的第三方、负载均衡、交换机带宽等等都是系统扩展时需要考虑的因素。

  • 高可用扩展性的设计思路

拆分:将复杂的问题简单化。庞杂的系统拆分成一个个独立、单一职责的模块

  • 存储层的扩展性

  • 存储拆分首先考虑的维度是业务维度

  • 然后根据数据维度拆分:按照某些算法来分库分表,此时尽量不要使用事务

  • 业务层的扩展性

  • 业务维度

  • 重要性维度

  • 请求来源维度



请用你熟悉的编程语言写一个用户密码验证函数,Boolean checkPW(String 用户 ID,String 密码明文,String 密码密文)返回密码是否正确 boolean 值,密码加密算法使用你认为合适的加密算法。



因为MD5算法是不可逆的,所以被很多网站广泛使用,

普遍使用的三种加密方式

方式一:使用位运算符,将加密后的数据转换成16进制

方式二:使用格式化方式,将加密后的数据转换成16进制(推荐)

方式三:使用算法,将加密后的数据转换成16进制



用户对象

//实体
public class User {
String userID;
String userName;
String passwordEncryptedText;
/**
* 构造函数
* @param userID
* @param userName
* @param passwordEncryptedText
*/
User(String userID, String userName, String passwordEncryptedText) {
this.userID = userID;
this.userName = userName;
this.passwordEncryptedText = passwordEncryptedText;
}
}



工具类

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
/**
* 密码工具类
*/
public class PwdCheckerUtils {
/**
* 由于MD5 与SHA-1均是从MD4 发展而来,它们的结构和强度等特性有很多相似之处
* SHA-1与MD5 的最大区别在于其摘要比MD5 摘要长 32 比特(1byte=8bit,相当于长4byte,转换16进制后比MD5多8个字符)。
* 对于强行攻击,:MD5 是2128 数量级的操作,SHA-1 是2160数量级的操作。
* 对于相同摘要的两个报文的难度:MD5是 264 是数量级的操作,SHA-1 是280 数量级的操作。
* 因而,SHA-1 对强行攻击的强度更大。 但由于SHA-1 的循环步骤比MD5 多(80:64)且要处理的缓存大(160 比特:128 比特),SHA-1 的运行速度比MD5 慢。
*
* @param source 需要加密的字符串
* @param hashType 加密类型 (MD5 和 SHA)
* @return
*/
public static String getHash(String source, String hashType) {
// 用来将字节转换成 16 进制表示的字符
char hexDigits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
try {
MessageDigest md = MessageDigest.getInstance(hashType);
md.update(source.getBytes()); // 通过使用 update 方法处理数据,使指定的 byte数组更新摘要 (为什么需要先使用update方法 有的md5方法中怎么不使用?)
byte[] encryptStr = md.digest(); // 获得密文完成哈希计算,产生128 位的长整数
char str[] = new char[16 * 2]; // 每个字节用 16 进制表示的话,使用两个字符
int k = 0; // 表示转换结果中对应的字符位置
for (int i = 0; i < 16; i++) { // 从第一个字节开始,对每一个字节,转换成 16 进制字符的转换
byte byte0 = encryptStr[i]; // 取第 i 个字节
str[k++] = hexDigits[byte0 >>> 4 & 0xf]; // 取字节中高 4 位的数字转换, >>> 为逻辑右移,将符号位一起右移
str[k++] = hexDigits[byte0 & 0xf]; // 取字节中低 4 位的数字转换
}
return new String(str); // 换后的结果转换为字符串
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return null;
}
public static User queryUserByID(String userID) {
if (userID != null) {
return new PwdCheckerUtils().new User(userID, "", "");
}
return null;
}
public static boolean checkPW(String userID, String pwdPlainText, String pwdEncryptedText) {
if (queryUserByID(userID) == null) {
return false;
}
String salt = userID;
return checkPW(pwdPlainText + salt, pwdEncryptedText);
}
public static boolean checkPW(String pwdPlainText, String pwdEncryptedText) {
String hash = getHash(pwdPlainText, "MD5");
return (hash != null && hash.equals(pwdEncryptedText));
}
//测试
public static void main(final String[] args) {
//根据用户ID加密
String userID = "admin";
String pwdPlainText = "123456";
// String pwdEncryptedText = "e10adc3949ba59abbe56e057f20f883e";
String pwdEncryptedTextWithSalt = "b9d11b3be25f5a1a7dc8ca04cd310b28";
boolean passed = PwdCheckerUtils.checkPW(userID, pwdPlainText, pwdEncryptedTextWithSalt);
if (passed) {
System.out.println("用户密码验证通过");
} else {
System.out.println("用户密码错误");
}
}
}



用户头像

edd

关注

还未添加个人签名 2018.01.18 加入

还未添加个人简介

评论 (1 条评论)

发布
用户头像
你大量引用我的作业请注明以下出处!谢谢!
2020 年 08 月 24 日 09:20
回复
没有更多了
架构师训练营」第 11 周作业