Skip to content

manongfanshen_reading_notes

gobomb edited this page Apr 15, 2019 · 3 revisions

码农翻身笔记

自旋锁: 保证共享变量的互斥访问

  1. test_and_set(lock) 这是一个不可分割的原子操作

  2. 存在不可重入性:使用计数器记录重入的次数,防止线程抢不到锁,无限循环空耗 CPU

信号量:解决线程同步的问题

悲观锁:synchronized;线程容易阻塞

乐观锁:

compareAndSwap(CAS):一个硬件指令,操作系统和硬件保证原子执行

public int next(){
    while(true){
        int A = 读取内存的值;
        int B = A + 1;
        if(comapareAndSwap(内存的值,A,B){
            return B;
        }
    }
}

如果数据结构复杂,使用CAS需要频繁读/写内存数据和进行比较,开销比较大。

AtomicReference:不比较数据,只比较两个对象的引用。

ABA问题:

假设有两个线程, 线程1 读到内存的数值为 A , 然后时间片到期,撤出CPU。 线程2运行,线程2 也读到了A , 把它改成了B, 然后又把B改成原来的值A , 简单点说,修改的次序是 A -> B ->A 。 然后线程1开始运行, 它发现内存的值还是A , 完全不知道内存中已经被操作过。

AtomicStampedReference:加入版本号解决ABA问题

数据库

JDBC(Java DataBase Connectivity):定义连接数据库的接口

分布式事务的解决方案:

  1. 两阶段提交:使用一个事务管理器,阶段一保证所有数据库准备好,阶段二迅速提交

  2. 通过消息队列接耦,只保证最终一致性而非强一致性。引入事件表,定时程序读取事件表向消息队列写消息(要保证幂等性)

Java 动态代理

动态性:在运行时修改一个类;用声明的方式编程

  1. 先来创建一个接口(必须得有接口才能动态地创建新类):
public interface IHelloWorld {
    void sayHello();
}
  1. 再创建除了非业务功能的类(实现上面的接口):
public class HelloWorld implements IHelloWorld {
    @Override
    public void sayHello() {
        System.out.println("Hello World");
    }
}
  1. 再写一个类告诉它具体把 Logger 的代码加到什么地方。这个类需要实现 InvocationHandler 接口,该接口中有个叫做 invoke 的方法就是写扩展代码的地方:
public class LoggerHandler implements InvocationHandler {
    private Object target;
    
    public LoggerHandler(Object target) { this.target = target; }
    
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Logger logger = Logger.getLogger("Log");
        logger.log(Level.INFO, "start log");
        Object result = method.invoke(target, args);
        logger.log(Level.INFO, "end log");
        return result;
    }
}
  1. 使用一下 LoggerHandler 这个类
public class HelloWorld implements IHelloWorld {
    @Override
    public void sayHello() {
        System.out.println("Hello World");
    }
    public static void main(String[] args) {
        IHelloWorld hw = new HelloWorld();
        LoggerHandler handler = new LoggerHandler(hw);
        Object proxy = Proxy.newProxyInstance(
            Thread.currentThread().getContextClassLoader(),
            hw.getClass().getInterfaces(), handler);
        ((IHelloWorld)proxy).sayHello();    // 强制类型转换,因为只有实现了接口才能操纵这个方法
    }
}

正交性

每个功能可以独立变化、拓展而互不影响

拓展A功能的时候,不会影响到B功能。A功能的增减和B是线性无关的

Spring

目标:分离与日志、安全、事务、性能统计相关的代码和真正的业务代码

解决:

  1. 模版方法:在父类中把非功能性代码写好,只留一个抽象方法让子类去实现。子类只关注业务逻辑就可以;缺陷是:父类会定义一切:要执行那些非功能性代码,以什么顺序执行等,子类只能无条件接受

  2. 装饰者模式:解决上述问题;

    不足:处理日志/安全/事务/性能统计的非功能性代码和业务代码不正交

  3. 面向切面编程(AOP)

    把业务功能看成一层层面包,那么日志/安全/事务/性能统计就像一个个“切面”(Aspect)。让切面和业务独立,并且能够灵活地“织入”业务方法中,使业务代码清爽

突破 Java 无法修改编译好的类的限制

1)修改现有类:在编译的时候,根据 AOP 的配置信息,悄悄地把“切面”代码和业务类编译到一起。需要增强编译器,并且业务类会被改变

2)在运行期做手脚,在业务类加载以后,为该业务类动态地生成一个代理类,让代理类去调用执行“切面”代码,增强现有的业务类,业务类不用进行任何改变。客户直接使用的是代理类对象,而不是原有的业务类对象

动态生成代理类的方法

1)使用 Java 动态代理技术,要求业务类必须有接口才能工作

2)使用 GGlib,生成一个业务类的子类来作为代理类

使用容器创建对象

Inversion of Contorol(IoC):最早是自己去创建自己依赖的对象,有了 spring 容器的介入,所有的依赖关系都有容器负责,发生了控制的翻转。也称依赖注入(Dependency Injection,DI)

Web 单点登录(Single Sign On,SSO)

前端需要共享 cookie(有跨域问题),后端需要共享 session(有异构系统、跨主机问题)

统一二级域名?使用redis共享 session?

使用 JWT:解决不了异构系统有单独的账号体系的问题

CAS,central authentication service:一个认证中心的 cookie,加上多个子系统的 cookie

授权

OAuth 三种方式

  1. Resource Owner Password Credentials Grant(资源所有者密码凭据许可)

  2. Implicit Grant(隐式许可)

  3. Authorization Code Grant(授权码许可)

在OAuth中,还有几个术语:资源所有者;资源服务器;客户端;授权服务器

后端

数据库的可用性

  1. 加缓存
  2. web服务器(Nginx)、应用服务器(Tomcat)、数据库、缓存服务器(Redis,Java 使用 Jedis 与之交互)分成多台 host

多台 Redis 保证数据均匀

  1. Jedis 使用余数算法:计算出 key 的一个整数 hash,用这个 hash 对服务器数目求余数。缺点:添加机器,会导致大量缓存失效,加大数据库压力

  2. 一致性 Hash 算法:把失效的缓存控制在特定的区间。当增删服务器时,只会影响相邻节点的缓存数据,其他部分的数据与原来保持一致

  3. Hash 槽(Hash Slot):需要 redis cluster 互相通信

故障转移

一主多从

Nginx 的高可用

使用 Keepalived,

Tomcat 的高可用

Nginx 保证负载均衡,做失效转移,通过 session sticky 保证同一个客户端的请求一直被转发到同一台 Tomcat 上;使用 Redis集群保存 session

MySQL 读写分离

引入中间层 MySQL Proxy 来区分读写

读后感

这本书不算很系统,但是能帮助我建立一些感性的认识,比如说 Java 为什么要发展出动态代理,为什么要发展出 Spring,常规的后端架构是怎么从单体扩展到分布式的。

Java 技术的一大发展方向就是正交性,追求非功能性代码和业务代码分离。

接口就是一个实现正交性的很好的方式,这就为什么组合要比继承好。想起来 go 确实比较实用主义,一下子从语言设计层面抛弃完整的 OOP 实现,只提倡 interface 的组合,就是奔着正交性去的。

追求非功能性代码和业务代码分离,会导致声明式编程的流行,也就是写配置文件,XML、YAML、Dockerfile(注解也算是一种声明),基础架构(Spring、CI/CD、Docker、k8s、Istio……)会根据配置文件帮程序员生成大量非功能性代码、接管通用的基础服务,程序员只要 focus 在业务上面。避免重新造轮子,毕竟只有业务才是真正带来利润的,工具都是可以复用的。

Clone this wiki locally