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

秒杀服务的限流策略

  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线程内存模型角度分析线程是否安全

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

线程安全不安全的结构
StringBufferStringBuilder

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,数据存在硬盘
posted @ 2022-10-04 23:12  johnny233  阅读(40)  评论(0编辑  收藏  举报  来源