《极客时间·每日一课》笔记

秒杀服务的限流策略

  1. 合法性限流
    鉴定非法请求:
    1. 验证码(剔除机器人,使用户的请求时间随机分布)
    2. 非法IP限制
    3. 隐藏秒杀按钮入口
  2. 负载限流
    1. 负载均衡分发请求到每个服务器
    2. 多级(级联)负载,第二层MAC负载,第三层IP负载,第四层端口号负载,第七层nginx负载
    3. 级联复杂均衡,级联数的权衡
    4. 软件、硬件负载均衡
  3. 服务限流
    1. servlet服务器限流,配置连接数
    2. 算法限流(令牌桶)
    3. 队列限流,MQ中间件
    4. 缓存限流,多级缓存,缓存级数的权衡,缓存一致性问题,多级缓存的请求耗时
    5. 监控,服务降级

跨语言的RPC调用

WS的两个主要缺点:1.传输格式XML冗余浪费带宽编码解码复杂效率低;2.传输协议基于应用层HTTP,路径长效率低。
Pb的问题:只能转换数据,不能进行数据传输。
引入netty,两者结合诞生gRPC,基于HTTP2。

Spring Boot使用HTTP/2

server.http2.enable=true + SSL证书
JDK8 + Tomcat 9 + libtcnative
JDK9 + Tomcat 9,自动支持HTTP/2
Undertow 1.4.0 支持HTTP/2
Jetty 9.4.8 + org.eclipse.jetty:jetty-alpn-conscrypt-server + org.eclipse.jetty.http2:http2-server
keytool + curl
定制Tomcat connector
底层http库可配置,如okhttp3

高可用配置中心

基于数据库的配置中心
缓存 + MySQL的实现方式:服务请求获取配置,先从JVM本地缓存查找,没有找到则去配置中心查找配置,即实际上去MySQL查找配置。
存储于MySQL的配置信息如何同步到缓存的三种方式:

  1. 缓存过期
  2. 定时轮询
  3. MQ通知,mysql binlog

问题:一致性,数据库压力,延迟性,

基于zookeeper的配置中心
watcher机制,

避免资损问题

  1. 网络异常问题
  2. 查询和
  3. 幂等性问题
  4. 通知问题
  5. 状态同步问题

Spring Data JPA动态复杂查询

复杂查询: 涉及多表,join,子查询等
动态查询:查询条件未知
Spring Data JPA能够减少代码量,适用于事务相关的OLTP场景
Mybatis支持手写SQL,可以跟进查询条件动态拼接参数,适用于数据分析的OLAP场景。

JPA支持动态复杂查询的三种方式:

  1. @Query注解,手写复杂SQL,需要遍历各种不同组合情况的查询条件去实现动态查询;
  2. @OneToOne, @OneToMany, @ManyToMany, @JoinColumn, @JoinTable等注解,需要学习其属性如cascadeType, FetchType, MappedBy,外键和级联的性能差。
  3. JPA + querydsl-jpa
    引入依赖
    QueryDSL: predicate, projection, QEntity
    定义maven profile: qentity, all.

Spring Data JPA动态部分更新

Spring Data JPA更新Entity时,对数据表中除主键外所有字段进行全量更新
repository.save(),在更新时,先查询
NULL值问题
@DynamicUpdate + BeanUtils.copyNullProperties()

高并发场景下锁的使用技巧

乐观锁的实现方式

  1. 体现在数据库层面,就是版本号
  2. 体现在Java编码,就是CAS

高并发场景下扣库存的解决方案:

  1. synchronize,同步排它锁,重量级,问题:线程串行导致的性能问题、无法解决分布式情况下跨进程/跨JVM的问题
  2. 数据库行锁,悲观锁,select for update,可以解决跨进程问题,但是:
    1. 性能问题,select for update会一直阻塞直到事务提交,串行执行
    2. 需要设置数据库事务隔离级别read committed,否则其他事务不能看到提交的数据
    3. 如果事务中有第三方超时接口,会导致这个事务的连接一直阻塞,打满数据库连接
    4. 在多个业务的加锁顺序控制不好的情况下,发生交叉死锁。
  3. 分布式锁(Redis)的问题:
    1. 设置锁和超时时间的原子性;Redis 2.6.12版本之前,setnx不支持expire,需要setnx + expire两条命令,非原子操作
    2. 不设置超时时间的问题:代码耗时过长,超过超时时间,锁失效?错误地将其他线程同一个key的锁删除??
    3. 服务宕机或者线程超时阻塞的问题:
    4. 超时时间设置不合理的问题:业务经验值,续命锁,守护线程,实现复杂
  4. 数据库乐观锁(版本号)
  5. 人工补偿

ZAB v.s. Paxos

ZK,observer不参与Leader的选举过程和过半写成功策略
事务操作,改造zk状态的操作,如znode的创建、删除、内容更新
zxid,全局唯一的id,二阶段提交协议
其他算法的区别

算法 关键字
Paxos 过半选举,数据副本一致性
Raft 相对Paxos协议进行简化
ZAB 适用于离线的海量数据处理
hash路由 es请求路由
一致性hash 负载均衡
Cassandra 最终一致性

中台服务化编排

服务间的协作通过流程编排自动执行,实现一个完整的业务流程

  1. Zeebe

    1. 基于BPMN2.0规范,
    2. 任务通知模型采用消息驱动和发布-订阅两种模式
    3. Raft算法实现无中心化选举
    4. activiti6.0以下版本,基于数据库记录状态来驱动工作流进行,适合传统行业;
    5. Zeebe,消息机制驱动
    6. 适用于传统工作流升级为分布式工作流
  2. Nexflix Conductor

    1. JSON DSL定义服务执行流程
    2. 带服务管理监控界面
    3. 异步消息
  3. Apache Camel

    1. 基于组件,扩展性强
    2. 路由引擎控制器
    3. 5种DSL:Java DSL、Spring XML、Blueprint XML、REST DSL、Annotation DSL

服务编排的6个考虑:
编排框架选型、编排流程DSL、可视化编排设计系统、编排业务隔离、编排引擎的高可用、监控

数据库优化场景

  1. 分页limit优化,偏移量,返回结果集
    1. 增加where条件解决
    2. 业务层记录索引定位符,有数据删除或者插入的情况
    3. 子查询 + 索引定位:select id from (select id from info_table where id < 4900000 order by id desc limit 20 ) as temp_info order by id limit 1
  2. 链/连表查询,join查询拆分成单表查询,避免使用临时表
  3. 子查询过重,需要优化

反射和泛型编程

多看看Spring Data JPA源码

高性能、高可用的MySQL架构

高性能:
MySQL 主从同步,bin log、relay log
读写分离, 80-20原则,
分库分表, 水平+垂直
MyCat
慢查询日志, mysqldumpslow
数据库优化:字段类型选取,数据库缓存,关闭无用服务,数据库参数调优,选择合适的存储引擎,SQL语句编写,索引处理

高可用:
去中心化集群,2个MyCat集群,vip
海量请求层层处理:限流,削峰填谷,缓存

自动化测试

客户端(GUI)+服务端(API),后者对业务逻辑层+数据层
工具:
postman+newman,不能测试RPC接口?
JMeter+插件,可以测试RPC接口;
JMeter+Jenkins:命令行或performance
rest-assured+TestNG+Jenkins

Postman做接口自动化测试

pre-request script(预处理脚本)+ 测试脚本
自动生成测试脚本;脚本分类:变量设置、响应处理相关、断言
postman+jenkins

binlog数据恢复

数据恢复依赖于备份方式. 主流备份方案:

  1. 全量备份,备份快,适合于数据量较小
  2. 全量+增量,节约磁盘空间,备份慢,适合于数据量大,

binlog三种模式:

  1. statement level, 老版本默认模式, 只存储SQL,不能进行数据恢复
  2. row level, 新版本默认模式
  3. mixed level,默认是statement,切换到row level模式

解析工具:mysqlbinlog

数据恢复工具:

  1. binlog-rollback, perl脚本
  2. MyFlash,美团开源

一种全新的思路:
在MySQL集群中引入延迟从库,可以设置24小时延迟同步。每日监控,若发现误操作,将延迟从库的数据同步到其他节点。

通过软引用和弱引用提升JVM内存使用效率

对于非核心数据,使用软引用HashMap<String, SoftReference<Content>>
弱引用类似。

从Java线程内存模型角度分析线程是否安全

主内存和线程内存,存的是副本;过程:读取,加载,操作,回写

线程安全 不安全的结构
StringBuffer StringBuilder

volitile,保证立即度和立即写,提升性能,不能保证原子性,不能保证线程安全

分布式定时任务系统

调度器,执行器,管理界面;注解扫描;任务拆分到执行器;
高可用,调度器(master)选举,zk;

Git分支工作流

分支的定义;MR, merge request;
约定大于一切;版本号;git cherry-pick;git subtree

操作系统&Java代码运行

时序存储引擎

涉猎太少,听不太懂。
开源产品:RRD,Graphite,OpenTSDB,InfluxDB,prometheus

Facebook内存数据库Gorilla论文
作者开源的:https://github.com/lindb/lindb
特性:

  1. 数据是追加操作,Append only
  2. 时间强关联性
  3. 冷热数据明显,一般查询近期数据,写多读少
  4. 有很多维度可以对数据进行过滤

组成部分
metric name,tags(k-v对,可以有多个kv对),timestamp,filed value

(metric name,tags)一般是索引。
索引存储的数据结构:
posting list
位图bitmap
roaring bitmap
SSTable
Offset Block
Index Block
Footer

如何快速开发数据平台

Grafana,插件

斐波那契数列

解决:

  1. 递归,n到40的执行时间过大,O(n^2)
  2. 动态规划,迭代思想,O(n)
  3. 通项公式,有幂运算,O(log(n))
  4. 线性代数,矩阵运算,O(log(n))
// 解法1:递归
long fib1(int n) {
	return (2 > n) ? (long) n : Fib(n -1) + Fib(n - 2);
}

// 解法2:线性复杂度迭代,常数空间复杂度
public static int fib2(int n) {
	// 初始化:Fib(0) = 0,Fib(1) = 1
    int prev = 0;
    int next = 1;
    while(n-- > 1) {
        next = prev + next;
        prev = next - prev;
    }
    return next;
}

// 解决2的迭代版本
public static int fib(int n, int first, int second) {
    if (n < 2) {
        return n;
    }
    if (n == 2) {
        return first + second;
    } else {
        return Fib(n - 1, second, first + second);
    }
}

private static long matrixFib(int num) {
    if (num <= 1) {
        return num;
    }
    // 二维矩阵
    Matrix first = new Matrix(2, 2);
    first.setElement(1, 1, 1);
    first.setElement(1, 2, 1);
    first.setElement(2, 1, 1);
    first.setElement(2, 2, 0);

    Matrix result = new Matrix(2, 1);
    result.setElement(1, 1, 1);
    result.setElement(2, 1, 0);
    // 根据递推式求第num项,只需求first矩阵的num - 1次方
    int n = num - 1;
    while (n > 0) {
        if (n % 2 != 0) {
            result = first.multiMatrix(result);
        }
        if ((n /= 2) > 0) {
            first = first.multiMatrix(first);
        }
    }
    return result.getElement(1, 1);
}
    
/**
 * 一个当作矩阵的二维数组
 */
class Matrix {
    /**
     * 当前矩阵的行数
     */
    private int row;
    /**
     * 当前矩阵的列数
     */
    private int col;
    /**
     * 二维数组用于保存矩阵
     */
    private ArrayList<ArrayList<Integer>> matrix;

    /**
     * 传入行数和列数构造一个零矩阵
     */
    Matrix(int row, int col) {
        this.row = row;
        this.col = col;
        matrix = new ArrayList<>(row);
        for (int i = 0; i < row; i++) {
            ArrayList<Integer> list = new ArrayList<>(col);
            for (int j = 0; j < col; j++) {
                list.add(0);
            }
            matrix.add(list);
        }
    }

    private int getRow() {
        return row;
    }

    private int getCol() {
        return col;
    }

    int getElement(int row, int col) {
        return matrix.get(row - 1).get(col - 1);
    }

    void setElement(int row, int col, int value) {
        matrix.get(row - 1).set(col - 1, value);
    }

    /**
     * 获取某一行向量的值
     */
    private ArrayList<Integer> getRow(int row) {
        return matrix.get(row - 1);
    }

    /**
     * 获取某一列向量的值
     */
    private ArrayList<Integer> getCol(int col) {
        ArrayList<Integer> arrCol = new ArrayList<>();
        for (int i = 0; i < row; i++) {
            arrCol.add(matrix.get(i).get(col - 1));
        }
        return arrCol;
    }

    /**
     * 向量点乘
     */
    private int multiVec(ArrayList<Integer> v1, ArrayList<Integer> v2) {
        if (v1.size() != v2.size()) {
            return -1;
        }
        int result = 0;
        for (int i = 0; i < v1.size(); i++) {
            result += (v1.get(i)) * (v2.get(i));
        }
        return result;
    }

    /**
     * 矩阵乘法,只有第一个矩阵的列数等于第二个矩阵的行数才能相乘
     */
    Matrix multiMatrix(Matrix matrix1) {
        if (getCol() != matrix1.getRow()) {
            return null;
        }
        Matrix matrix2 = new Matrix(getRow(), matrix1.getCol());
        for (int i = 1; i <= getRow(); i++) {
            for (int j = 1; j <= matrix1.getCol(); j++) {
                matrix2.setElement(i, j, multiVec(getRow(i), matrix1.getCol(j)));
            }
        }
        return matrix2;
    }
}

当小内存遇上大数据

  1. 压缩:
    无论是哪种方式,都需要保证计算结果的正确性
    1. 无损压缩
    2. 有损压缩
  2. 分块:
    每次只加载部分数据
  3. 索引:
    索引存在RAM,数据存在硬盘
原创文章 131 获赞 175 访问量 32万+

猜你喜欢

转载自blog.csdn.net/lonelymanontheway/article/details/105025621