-
Notifications
You must be signed in to change notification settings - Fork 3
manongfanshen_reading_notes
自旋锁: 保证共享变量的互斥访问
-
test_and_set(lock)这是一个不可分割的原子操作 -
存在不可重入性:使用计数器记录重入的次数,防止线程抢不到锁,无限循环空耗 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):定义连接数据库的接口
分布式事务的解决方案:
-
两阶段提交:使用一个事务管理器,阶段一保证所有数据库准备好,阶段二迅速提交
-
通过消息队列接耦,只保证最终一致性而非强一致性。引入事件表,定时程序读取事件表向消息队列写消息(要保证幂等性)
动态性:在运行时修改一个类;用声明的方式编程
- 先来创建一个接口(必须得有接口才能动态地创建新类):
public interface IHelloWorld {
void sayHello();
}- 再创建除了非业务功能的类(实现上面的接口):
public class HelloWorld implements IHelloWorld {
@Override
public void sayHello() {
System.out.println("Hello World");
}
}- 再写一个类告诉它具体把 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;
}
}- 使用一下 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是线性无关的
目标:分离与日志、安全、事务、性能统计相关的代码和真正的业务代码
解决:
-
模版方法:在父类中把非功能性代码写好,只留一个抽象方法让子类去实现。子类只关注业务逻辑就可以;缺陷是:父类会定义一切:要执行那些非功能性代码,以什么顺序执行等,子类只能无条件接受
-
装饰者模式:解决上述问题;
不足:处理日志/安全/事务/性能统计的非功能性代码和业务代码不正交
-
面向切面编程(AOP)
把业务功能看成一层层面包,那么日志/安全/事务/性能统计就像一个个“切面”(Aspect)。让切面和业务独立,并且能够灵活地“织入”业务方法中,使业务代码清爽
1)修改现有类:在编译的时候,根据 AOP 的配置信息,悄悄地把“切面”代码和业务类编译到一起。需要增强编译器,并且业务类会被改变
2)在运行期做手脚,在业务类加载以后,为该业务类动态地生成一个代理类,让代理类去调用执行“切面”代码,增强现有的业务类,业务类不用进行任何改变。客户直接使用的是代理类对象,而不是原有的业务类对象
1)使用 Java 动态代理技术,要求业务类必须有接口才能工作
2)使用 GGlib,生成一个业务类的子类来作为代理类
Inversion of Contorol(IoC):最早是自己去创建自己依赖的对象,有了 spring 容器的介入,所有的依赖关系都有容器负责,发生了控制的翻转。也称依赖注入(Dependency Injection,DI)
前端需要共享 cookie(有跨域问题),后端需要共享 session(有异构系统、跨主机问题)
统一二级域名?使用redis共享 session?
使用 JWT:解决不了异构系统有单独的账号体系的问题
CAS,central authentication service:一个认证中心的 cookie,加上多个子系统的 cookie
OAuth 三种方式
-
Resource Owner Password Credentials Grant(资源所有者密码凭据许可)
-
Implicit Grant(隐式许可)
-
Authorization Code Grant(授权码许可)
在OAuth中,还有几个术语:资源所有者;资源服务器;客户端;授权服务器
- 加缓存
- web服务器(Nginx)、应用服务器(Tomcat)、数据库、缓存服务器(Redis,Java 使用 Jedis 与之交互)分成多台 host
-
Jedis 使用余数算法:计算出 key 的一个整数 hash,用这个 hash 对服务器数目求余数。缺点:添加机器,会导致大量缓存失效,加大数据库压力
-
一致性 Hash 算法:把失效的缓存控制在特定的区间。当增删服务器时,只会影响相邻节点的缓存数据,其他部分的数据与原来保持一致
-
Hash 槽(Hash Slot):需要 redis cluster 互相通信
一主多从
使用 Keepalived,
Nginx 保证负载均衡,做失效转移,通过 session sticky 保证同一个客户端的请求一直被转发到同一台 Tomcat 上;使用 Redis集群保存 session
引入中间层 MySQL Proxy 来区分读写
这本书不算很系统,但是能帮助我建立一些感性的认识,比如说 Java 为什么要发展出动态代理,为什么要发展出 Spring,常规的后端架构是怎么从单体扩展到分布式的。
Java 技术的一大发展方向就是正交性,追求非功能性代码和业务代码分离。
接口就是一个实现正交性的很好的方式,这就为什么组合要比继承好。想起来 go 确实比较实用主义,一下子从语言设计层面抛弃完整的 OOP 实现,只提倡 interface 的组合,就是奔着正交性去的。
追求非功能性代码和业务代码分离,会导致声明式编程的流行,也就是写配置文件,XML、YAML、Dockerfile(注解也算是一种声明),基础架构(Spring、CI/CD、Docker、k8s、Istio……)会根据配置文件帮程序员生成大量非功能性代码、接管通用的基础服务,程序员只要 focus 在业务上面。避免重新造轮子,毕竟只有业务才是真正带来利润的,工具都是可以复用的。