写点什么

架构师训练营 - 系统安全与高可用

用户头像
Pontus
关注
发布于: 2020 年 08 月 26 日



软件组织原则

软件的复杂度和它的规模成指数关系

一个复杂度为100的软件系统,如果能拆分成两个互不相关、同等规模的子系统,那么每个子系统的复杂度应该是25,而不是50。如果让你来设计一个淘宝,你会一头雾水,但是如果让你来做一个登录页面,你就会非常清晰了。



组件内聚原则

组件内聚原则主要讨论哪些类应该聚合在同一个组件中,以便组件既能提供相对完整的功能,又不至于太过庞大。(复用发布等同原则、共同封闭原则、共同复用原则)



复用发布等同原则

软件复用的最小粒度应该等同于其发布的最小粒度。也就是说,如果你希望别人以怎样的粒度复用你的软件,你就应该以怎样的粒度发布你的软件。比如,Spring早期的版本把所有功能整合在一个jar包里面,使用者必须完全依赖整个jar包,而后面的版本进行了细粒度的拆分,以便用户可以更细粒度地进行依赖。



版本号约定建议

版本号格式:主版本号.次版本号.修订号。比如:1.2.3。

主版本号升级,表示组件发生了不向前兼容的重大修订。

次版本号升级,表示组件进行了重要功能修订或者bug修复,但是组件是向前兼容的。

修订号升级,表示组件进行了不重要的功能修订或bug修复。



共同封闭原则

我们应该将那些会同时修改,并且为了相同目的而修改的类放到同一组件中。而将不会同时修改,并且不会为了相同目的而修改的类放到不同的组件中。比如:某个组件修改了,而我的组件和被修改组件不相干,不应该受到牵连。



共同复用原则

共同复用原则是说,不要强迫一个组件的用户依赖他们不需要的东西。也就是说,我们应该将互相依赖,共同复用的类放在一个组件中。共同复用和共同封闭原则是相互冲突的。



组件耦合原则

组件内聚原则讨论的是组件应该包含哪些功能和类,而组件耦合原则讨论组件之间的耦合关系应该如何设计。(无循环依赖原则、稳定依赖原则、稳定抽象原则)



无循环依赖原则

无循环依赖原则说,组件依赖关系中不应该出现环。如果组件A依赖组件B,组件B依赖组件C,组件C又依赖组件A,就形成了循环依赖。



稳定依赖原则

组件的依赖关系必须指向更稳定的方向。较少变更的组件是稳定的,也就是说,经常变更的组件是不稳定的。根据稳定依赖原则,不稳定的组件应该依赖稳定组件,而不是反过来。



稳定抽象原则

一个组件的抽象化程度应该与其稳定性程度一致。也就是说,一个稳定的组件应该是抽象的,而不稳定的组件应该是具体的。



系统安全

常见攻击



XSS攻击



防御手段:消毒,过滤恶意字符。



SQL注入攻击



防御手段:消毒,过滤非法字符是一种简单粗暴的手段。预编译。

黑客获取表结构的手段:错误回显、盲注、开源



CSRF攻击



防御手段:表单token,Referer检查,验证码(识别出来到底是人还是机器人)



防御手段



Web应用防火墙

开源实现ModSecurity,最宝贵的就是识别攻击的正则表达式。



网站安全漏洞扫描

和电脑安全漏洞扫描一样,网站也需要安全漏洞扫描。



加密



单向散列加密



密文不可逆的,相当于一个指纹。常用在用户密码的保护。我们并不需要知道原始密码到底是什么,我们只要知道密码对不对就行了。



任何一个系统都不能做到取回密码的功能,只能做到重置密码。能做到取回密码的功能的网站,说明系统本身就是脆弱的了。



对称加密



场景:电商支持信用卡支付,电商网站需要跟银行进行交互,此时需要通过对称加密保存信用卡号、手机号和安全码。



非对称加密



场景:https握手、数字签名、区块链



秘钥安全管理与加解密服务系统架构



秘钥管理,比如写在配置文件或者代码里面,是非常不安全的,对于有源码权限的人来说,加密算法、秘钥都是可见的,那就没有秘密可言了。主要是为了防自己人,真的出了问题了,也能自证清白。



反垃圾邮件



先批量通过人工标记的方式标注一批邮件,再经过分类算法进行训练,得到分类模型。分类算法加载最新分类模型,对待处理邮件进行处理,就可以得出邮件是正常邮件或垃圾邮件的概率。主要是利用贝叶斯分类算法进行分类,如根据“茶叶”这个词分别出现在垃圾邮件和非垃圾邮件的概率,可以推断出一封邮件包含了“茶叶”关键字,这封邮件是垃圾邮件的概率。分类算法训练,就是要计算出这些:P(A|B)、P(A)、P(B)。



布隆过滤器黑名单



电子商务风险控制

反欺诈、反黄牛、反洗钱、反黑客盗号、反竞争对手恶意下单占用库存等等。风控的手段包括机器自动识别和人工识别。机器自动识别的方式主要有规则引擎和机器学习。



规则引擎

当交易某些指标满足一定条件的时候,就会被认为具有高风险的欺诈可能性。比如:

  • 用户来自欺诈高发地区

  • 交易金额超过某个数值

  • 和上次登录地址距离差距很大

  • 用户登录地与收货地不符

  • 用户第一次交易

大型网站在运营过程中,结合业界最新发现,会总结出数以千计此类高风险交易的规则。如果来业务逻辑中进行实现,会充沛大量的if-else,并且要经常修改发布。



机器学习

规则引擎技术虽然简单,但是随着规则逐渐增加,出现规则冲突,难以维护的情况,而且随着规则数量的增多,性能越来越差。大型互联网应用更倾向于使用机器学习模型进行风控。



系统高可用



可用性指标

网站年度可用性指标=(1-网站不可用时间/年度总时间)X 100%

网站不可用时间(故障时间)= 故障修复时间点 - 故障发现(报告)时间点



故障分管理



故障处理流程及考核



引起故障的原因

  • 硬件故障

  • 软件bug

  • 系统发布

  • 并发压力

  • 网络攻击

  • 外部灾难



高可用系统架构思路

解耦

  • 高内聚、低耦合的组件设计原则

  • 面向对象基本设计原则

  • 面向对象设计模式

  • 领域驱动设计建模

隔离

  • 业务与子系统隔离

  • 微服务与中台架构

  • 生产者与消费者隔离

  • 虚拟机与容器隔离

异步

  • 多线程编程

  • 反应式编程

  • 异步通信网络编程

  • 事件驱动异步架构

重试

备份(冗余)

  • 集群设计

  • 数据库复制(CAP原理)

Failover(失效转移)

  • 数据库主主失效转移

  • 负载均衡失效转移

失效转移的情况,需要关注的点是幂等

事务补偿

  • 传统事务的ACID

  • 分布式事务BASE

事务补偿:通过执行业务逻辑逆操作,使得事务回滚到事务前的状态

熔断

断路器三状态:关闭(服务正常调用)、打开(服务直接返回失败)、半开(部分正常调用)。



限流

限流是指对进入系统的用户请求进行流量限制,如果访问量超过了系统的最大处理能力,就会丢弃一部分的用户请求,保证整个系统可用,保证大部分用户是可以访问系统的。

限流的几种算法:

  • 计数器(固定窗口、滑动窗口)

  • 令牌桶算法

  • 漏桶算法

计数器(固定窗口)算法:



计数器(滑动窗口)算法:



令牌桶算法:



漏桶算法:



请求进来先放到漏桶里面,有个另外的线程以固定速度从漏桶取出请求进行处理。

自适应限流



没有提前人工评估,实时自动评估QPS。业务流量的不确定性与技术方案的自适应性天生一对。

降级

有一些系统功能是非核心的,但是它却给系统产生了非常大的压力,比如电商里面的确认收货这个功能,即便我们不去确认收货,系统也会超时自动确认收货。但实际上确认收货这个操作逻辑非常重,因为它会给数据库造成非常大的压力,它要修改订单状态,完成支付确认,并进行评价等一系列操作。比如淘宝双11的时候,就可以将确认收货、评价这些非核心的功能暂时关闭。将宝贵的机器资源留给正在购物的人。资源有限的情况下,尽量把机器资源留给那些高价值的操作(比如:下单购物)去。

异地多活

将数据中心分布在多个不同地点的机房里,这些机房都可以对外提供服务,用户连接任何一个机房都能得到正常的服务。异地多活的难点是数据一致性。



高可用系统的运维策略

发布高可用



自动化测试



在系统演化的早期,功能不确定,系统也不稳定,自动化测试的各种准备也不足,此时手工测试比较合适。但是随着时间推移,系统不断演化变得稳定了,此时采用自动化测试成本就比较低了。

自动化部署



持续部署三步走:

1)持续集成,向公共分支提交代码并触发自动化测试。

2)持续交付,除了跑单元测试及打包,持续交付还会将软件部署到测试环境。

3)持续部署,代码在没有人工干预的情况下,被测试、构建、部署并推送生产环境。

持续部署的流程:



预发布验证

即使经过非常严格的测试,软件部署到线上还是不可避免地会出现各种问题,甚至服务启动失败。



自动化发布



对于单体系统来说比较重要,对于微服务而言,一般来说各发布各的,比较灵活。

灰度发布



对于大规模的服务集群比较有用。

网站运行监控

绝对不允许没有监控的系统裸奔上线。(监控数据采集、用户行为日志采集、服务器性能监控、业务运行数据报告)当指标异常时,可以通过告警和自动控制的方式进行处理。

监控系统架构



高可用价值观

保持简单,使问题易于发现,快速解决。

目标明确,解决特定环境下的具体问题。

价值回归,成本收益要合理。



作业

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

  • 原因和方案见上文中系统高可用部分

  • 导致系统不可用原因

  • 硬件故障

  • 软件bug

  • 系统发布

  • 并发压力

  • 网络攻击

  • 外部灾难

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



public interface IHasher {
String hash(String source);
}

import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.Objects;
import org.apache.commons.codec.binary.Hex;
/**
* 基于md5的hash
*/
public class MD5Hasher implements IHasher {
@Override
public String hash(String source) {
source = Objects.requireNonNull(source);
try {
MessageDigest md5 = MessageDigest.getInstance("md5");
return Hex.encodeHexString(md5.digest(source.getBytes(StandardCharsets.UTF_8)));
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
}

public interface IPasswordChecker {
boolean checkPW(String userId, String password, String encryptPassword);
}

/**
* 简单的密码检查
*/
public class SimplePasswordChecker implements IPasswordChecker {
private final IHasher hasher;
public SimplePasswordChecker(IHasher hasher) {
this.hasher = hasher;
}
@Override
public boolean checkPW(String userId, String password, String encryptPassword) {
String hash = hasher.hash("simple_" + userId + "_" + password + "_" + getUserSalt(userId));
// System.out.println(hash);
return hash.equals(encryptPassword);
}
/**
* 根据用户id返回特定的salt,这里为了简化,直接返回某个固定的值
* @param userId
* @return
*/
protected String getUserSalt(String userId) {
return "TODO";
}
}

public class Main {
public static void main(String[] args) {
IPasswordChecker checker = new SimplePasswordChecker(new MD5Hasher());
System.out.println(checker.checkPW("user", "password", "a16c6d10c839e8e0e3141f654e008aa4"));
}
}



用户头像

Pontus

关注

还未添加个人签名 2018.04.21 加入

还未添加个人简介

评论

发布
暂无评论
架构师训练营 - 系统安全与高可用