https://www.cnblogs.com/yepei/p/4716112.html
https://www.cnblogs.com/kaleidoscope/p/9467192.html
事务管理方式
spring支持编程式事务管理和声明式事务管理两种方式。
编程式事务管理使用TransactionTemplate或者直接使用底层的PlatformTransactionManager。对于编程式事务管理,spring推荐使用TransactionTemplate。
声明式事务管理建立在AOP之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。声明式事务最大的优点就是不需要通过编程的方式管理事务,这样就不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明(或通过基于@Transactional注解的方式),便可以将事务规则应用到业务逻辑中。
显然声明式事务管理要优于编程式事务管理,这正是spring倡导的非侵入式的开发方式。声明式事务管理使业务代码不受污染,一个普通的POJO对象,只要加上注解就可以获得完全的事务支持。和编程式事务相比,声明式事务唯一不足地方是,后者的最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别。但是即便有这样的需求,也存在很多变通的方法,比如,可以将需要进行事务管理的代码块独立为方法等等。
事务隔离级别
隔离级别是指若干个并发的事务之间的隔离程度。TransactionDefinition 接口中定义了五个表示隔离级别的常量:
- TransactionDefinition.ISOLATION_DEFAULT:这是默认值,表示使用底层数据库的默认隔离级别。对大部分数据库而言,通常这值就是TransactionDefinition.ISOLATION_READ_COMMITTED。
- TransactionDefinition.ISOLATION_READ_UNCOMMITTED:该隔离级别表示一个事务可以读取另一个事务修改但还没有提交的数据。该级别不能防止脏读,不可重复读和幻读,因此很少使用该隔离级别。比如PostgreSQL实际上并没有此级别。
- TransactionDefinition.ISOLATION_READ_COMMITTED:该隔离级别表示一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读,这也是大多数情况下的推荐值。
- TransactionDefinition.ISOLATION_REPEATABLE_READ:该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相同。该级别可以防止脏读和不可重复读。
- TransactionDefinition.ISOLATION_SERIALIZABLE:所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。
@Transactional 注解应该只被应用到 public 方法上,这是由 Spring AOP 的本质决定的。如果你在 protected、private 或者默认可见性的方法上使用 @Transactional 注解,这将被忽略,也不会抛出任何异常。
https://www.jianshu.com/p/ca1b0d4107c5
promotion failed
该问题是在进行Minor GC时,Survivor Space放不下,对象只能放入老年代,而此时老年代也放不下造成的。
解决方法:
让CMS在进行一定次数的Full GC(标记清除)的时候进行一次标记整理算法;从而可以控制老年代的碎片在一定的数量以内。
或者控制朝生夕灭的大对象直接被分配到老年代,让朝生夕灭的大对象分配到新生代,下次minorGC掉
concurrent mode failure
该问题是在执行CMS GC的过程中同时业务线程将对象放入老年代,而此时老年代空间不足(在promotion failed的前提下,老年代恰好还正在full gc),或者在做Minor GC的时候,新生代Survivor空间放不下,需要放入老年代,而老年代也放不下而产生的。
解决方法:
解决这个问题的通用方法是调低触发CMS GC执行的阀值,CMS GC触发主要由CMSInitiatingOccupancyFraction值决定,默认情况是当旧生代已用空间为68%时,即触发CMS GC,在出现concurrent mode failure的情况下,可考虑调小这个值,提前CMS GC的触发,以保证旧生代有足够的空间。



{
"app":"group",
"layerId":2760,
"expKey":"ab_group_lunbo",
"frontStrategyName":"lunbo",
"status":true,
"strategyMap":{
"noshow":false,
"show":true
}
}
循环依赖
在每个volatile写操作的前面插入一个StoreStore屏障。
在每个volatile写操作的后面插入一个StoreLoad屏障。
在每个volatile读操作的后面插入一个LoadLoad屏障。
在每个volatile读操作的后面插入一个LoadStore屏障。
https://bbs.huaweicloud.com/blogs/detail/186752
方法区
该区域是线程共享的区域,用来存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。在JDK1.7以前是以永久代方式实现的,存在于堆中,可以通过-XX:PermSize(初始值)、-XX:MaxPermSize(最大值)参数设置大小;而1.8以后以元空间方式实现,使用的是直接内存(但运行时常量池和静态变量仍放在堆中),可以通过-XX:MetaspaceSize(初始值)、-XX:MaxMetaspaceSize(最大值)控制大小,如果不设置则只受限于本地内存大小。
在方法区中还存在常量池(1.7后放入堆中),而常量池也分了几种,常常让初学者比较困惑,比如静态常量池、运行时常量池、字符串常量池。静态常量池就是指存在于我们的class文件中的常量池,通过javap -v ClassDemo.class反编译上面的代码可以看到该常量池:
静态常量池中就是存储了类和方法的信息、符号引用以及字面量等东西,当类加载到内存中后,JVM就会将这些内容存放到运行时常量池中,同时会将符号引用(可以理解为对象方法的定位描述符)会被解析为直接引用(即对象的内存地址)存入到运行时常量池中(因为在类加载之前并不知道符号引用所对应的对象内存地址是多少,需要用符号替代)。而字符串常量池网上争议比较多,我个人理解它也是运行时常量池的一部分,专门用于存储字符串常量,这里先简单提一下,稍后会详细分析字符串常量池。
使用String类的intern方法动态添加字符串常量到运行时常量池中(intern方法在1.6和1.7及以后的实现不相同,1.6字符串常量池放于永久代中,intern会把首次遇到的字符串实例复制永久代中并返回永久代中的引用,而1.7及以后常量池也放入到了堆中,intern也不会再复制实例,只是在常量池中记录首次出现的实例引用)。
new String("abc")会创建两个对象(在此之前没有定义“abc”字面量,就会在字符串常量池创建对象,然后堆中创建String对象并引用该常量,否则只会创建堆中的String对象)
thread-localmap怎么实现的?内存泄漏和应用场景
分布式事务:最大努力通知 和 可靠消息最终一致性
https://zhuanlan.zhihu.com/p/183753774
https://xiaomi-info.github.io/2020/01/02/distributed-transaction/
redis脑裂
集群中出现多个主节点,导致客户端不知从哪个主节点写入数据,多主写数据严重导致数据丢失。
脑裂排查:
1.是不是数据同步发生问题
主从集群发生数据丢失,比较从节点和主节点数据,若主节点多,从节点少,及数据偏移量在主节点中,则表示数据丢失是由数据同步未完成导致的。
发生数据丢失原因:哨兵认为主库已死(假死),选新主,客户端仍和原主库进行数据交互,导致此部分数据丢失。
2.客户端操作日志排查
当发现客户端仍和原主库进行交互,未和新主库通信,则初步判定发生脑裂
解决脑裂:
min-slaves-to-write:这个配置项设置了主库能进行数据同步的最少从库数量
min-slaves-max-lag:这个配置项设置了主从库间进行数据复制时,从库给主库发送 ACK 消息的最大延迟(以秒为单位)
假设为 N 和 T。这两个配置项组合后的要求是,主库连接的从库中至少有 N 个从库,和主库进行数据复制时的 ACK 消息延迟不能超过 T 秒,否则,主库就不会再接收客户端的请求了。
https://blog.csdn.net/yzpbright/article/details/113482105
ISN:初始化序列号(initial sequence number),是在建立tcp三次握手的时候,存储在TCP头部的序列号位置中的数字的代称。也就是说,告诉对方我将要开始发送的初始化序列号是多少,两边都要发这个ISN,即tcp三次握手中第一次握手的SYN包和第二次握手的SYN+ACK包中都有这个数值。
序列号不固定:要是固定的话 容易才出后续的号,被攻击,
线程共享的环境包括:
1.进程代码段
2.进程的公有数据及内存空间(利用这些共享的数据,线程很容易的实现相互之间的通讯)
3.进程打开的文件描述符、信号的处理器、进程的当前目录和进程用户ID与进程组ID。
线程独立的资源包括:
1.线程ID
2.寄存器组的值
3.线程的堆栈
4.错误返回码
5.线程的信号屏蔽码
6.线程的优先级
fork之文件描述符的继承
通过fork()创建子进程时,子进程继承父进程环境和上下文的大部分内容的拷贝,其中就包括文件描述符表。
(1)对于父进程在fork()之前打开的文件来说,子进程都会继承,与父进程共享相同的文件偏移量。
2)相反,如果父进程先进程fork,再打开my.dat,这时父子进程关于my.dat 的文件描述符表指向不同的系统文件表条目,不继承。
僵尸进程&孤儿进程
孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。
危害:孤儿进程并没有什么危害。
僵尸进程:一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵死进程。ps查看状态为Z。
危害:如果大量的产生僵死进程,将因为没有可用的进程号而导致系统不能产生新的进程. 此即为僵尸进程的危害,应当避免。
解决方法:
(1)通过信号机制:子进程退出时向父进程发送SIGCHILD信号,父进程处理SIGCHILD信号。在信号处理函数中调用wait进行处理僵尸进程。
(2)让僵尸进程变成孤儿进程,由init回收,就是让父亲先死kill
(3)让僵尸进程的父进程来回收,父进程每隔一段时间来查询子进程是否结束并回收,调用wait()或者waitpid(),通知内核释放僵尸进程
说明:任何一个子进程(init除外)在exit()之后,并非马上就消失掉,而是留下一个称为僵尸进程(Zombie)的数据结构,等待父进程处理。若父进程未及时获取子进程状态,则导致进程描述符等资源无法释放,变成僵尸进程。若父进程退出了则交由init进程管理。
线程上下文指的是什么?
某一时间点CPU寄存器和程序计数器的内容,被称为上下文。
寄存器是CPU内部的少量速度很快的闪存。
程序计数器是一个专用的寄存器,被用来表示指令序列中CPU正在执行的位置。
什么情况导致切换?
- 当前线程任务正常完成,CPU调度下一个任务
- 硬件中断
- 当前任务被挂起,比如用了sleep方法,wait方法,yeild方法等,让出了CPU
- 当前任务执行I/O等会引起阻塞的操作,调度器会挂起这个任务,然后执行下一个任务
- 多个任务抢占锁资源,当前任务没抢到,就会被调度器挂起,然后调度器会执行下一个任务
线程间同步方式
https://blog.csdn.net/weichi7549/article/details/119804487
Thrift通信原理
https://www.cnblogs.com/jpfss/p/10881220.html
物理地址和虚拟地址
早起计算机,程序是直接运行到物理内存(可以理解为内存条上的内存)上的,但程序一多计算机如何把有限的内存分配给那么多任务使用,虚拟内存地址出现。
人们想到了一种变通的方法,就是增加一个中间层,利用一种间接的地址访问方法访问物理内存。按照这种方法,程序中访问的内存地址不再是实际的物理内存地址,而是一个虚拟地址,然后由操作系统将这个虚拟地址映射到适当的物理内存地址上。
虚拟地址转换物理地址的过程
1.简单方式:每个进程只存储Base地址和Bound地址,直接定位
2.以段Segment为基础的内存:
现根据段号去Segment Table表中寻找内存基地址记录,再根据提供的偏移地址Offset定位物理地址
3.以页为基础内存
先将虚拟地址分别拆分成:页目录索引, 页表项索引 , 偏移
页目录索引*页大小+ 页表项索引*页表项大小+偏移=物理地址
快表(https://blog.csdn.net/DSIslander/article/details/8226784)
快表TLB是单独的寄存器,又称页表缓存,页表是存在于主存。里面存放的是一些页表文件(虚拟地址到物理地址的转换表);当外部有数据传过来时(即虚拟地址),CPU先去快表查询有没有存在所需的页表,若存在则命中,直接虚拟地址转换为物理地址,若未命中则去页面慢慢查找,查找到则把当前页表添加到块表中,便于后续快速查找。
延迟ack-Delay-ACK(https://blog.csdn.net/wdscq1234/article/details/52430382)
简单的说,Delay Ack就是延时发送ACK,在收到数据包的时候,会检查是否需要发送ACK,如果需要的话,进行快速ACK还是延时ACK,在无法使用快速确认的条件下,就会使用Delay Ack。
既要对之前数据ack又要回发数据,则只需将本次数据+上次数据ack一块发送过去即可,减少网络包,提高法搜高效率;
场景:当收到数据,立刻有回发的数据,可将ack+数据一块发,若暂时没有,ack暂时等待一段时间(内核自定义倒计时)一定时间没有回发数据,则发送delay-ACK
IPV4和IPV6的区别
https://cn.fs.com/blog/23489.html
IPv6把IP地址由32位增加到128位
报头简化,地址是4倍但报头只有它2倍大,去除冗余域或被丢弃或被列为扩展报头
IPv6中加入了关于身份验证、数据一致性和保密性的内容
IPv6加强了对移动设备的支持,允许移动终端在切换接入点时保留相同的IP地址。
支持无状态自动地址配置,简化了地址配置过程。无需DNS服务器也可完成地址的配置,路由广播地址前缀,各主机根据自己MAC地址和收到的地址前缀生成可聚合全球单播地址。这也方便了某一区域内的主机同时更换IP地址前缀
秒杀优化方案
https://cloud.tencent.com/developer/article/1744075
- 限流策略。比如在压力测试中我们测到系统QPS达到了极限,那么超过的部分直接返回已经抢完,通过Nginx的lua脚本可以查redis看到QPS数据从而可以动态调节
- 异步削峰。对Redis中的红包预减数量,立即返回抢红包成功请用户等待,然后把发送消息发给消息队列,进行流量的第二次削峰,让后台服务慢慢处理
(1)页面端优化,如:
- 按钮置灰:禁止用户重复提交请求
- 通过JS控制:在一定时间内只能提交一次请求;设置验证码等
- 对于前端优先级较低的页面可以使用静态的、或者加载缓存来减少这方面请求资源消耗
(2)web server层优化,如:
- 动静分离:如将几乎不变的静态页面直接通过NG或CDN来路由访问,只有动态变换的页面可以请求到web server端
- 页面缓存化
- Nginx反向代理实现web server端的水平扩展
(3)后端service服务层优化
- 使用缓存(Redis、Memchched):将读多写少的业务数据放入缓存,如秒杀业务中可以将更新频繁的商品库存信息放入Redis缓存处理
注:库存信息放入Redis缓存的时候最好分为多份放入不同key的缓存中,如库存为10万可以分为10份分别放入不同key的缓存中,这样将数据分散操作可以达到更高的读写性能。
- 使用队列处理:将请求放入队列排队处理,以可控的速度来访问底层DB
- 异步处理:如将秒杀成功的订单通知信息通过消息队列(RabbitMQ、Kafka)来异步处理
(4)DB层优化
- 读写分离
- 分表分库
- 数据库集群
用户态内核态
一个任务(进程)执行系统调用而陷入内核代码中执行时,说它处于内核运行态,会利用当前进程的内核栈,操作特权指令,内核态能够执行所有指令。内核态随意进入用户态
用户态,进程执行用户代码时,CPU只能执行非特权指令。用户态需要中断、异常、系统调用等进入内核态。
Mybatis防止SQL注入
mybatis对sql进行预编译,先把#字段用?代替,在SQL执行前,会先将上面的SQL发送给数据库进行编译。
JDBC中的PreparedStatement类在起作用,(原因是SQL已编译好,再次执行时无需再编译)它的对象包含了编译好的SQL语句,
mybatis并不能全部避免sql注入
在MyBatis中,“${xxx}”这样格式的参数会直接参与SQL编译,从而不能避免注入攻击。但涉及到动态表名和列名时,只能使用“${xxx}”这样的参数格式。所以,这样的参数需要我们在代码中手工进行处理来防止注入。
unsafe类
主要提供一些用于执行低级别、不安全操作的方法,如直接访问系统内存资源、自主管理内存资源等,合理运用提高性能,底层资源操作能力
Unsafe提供的API大致可分为内存操作、CAS、Class相关、对象操作、线程调度、系统信息获取、内存屏障、数组操作等几类。
内存操作:分配,拷贝,扩充,释放对外内存,设置地址值等
系统相关:返回页大小,系统指针大小等
数组:返回数组大小及偏移量
cas等
Unsafe只提供了3种CAS方法:compareAndSwapObject、compareAndSwapInt和compareAndSwapLong。都是native方法。
获取实例方式:
1.java命令行:java -Xbootclasspath/a: ${path} // 其中path为调用Unsafe相关方法的类所在jar包路径
2.通过反射获取单例对象theUnsafe。
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
return (Unsafe) field.get(null);
cas应用:
AtomicInteger的实现中,静态字段valueOffset即为字段value的内存偏移地址,valueOffset的值在AtomicInteger初始化时,在静态代码块中通过Unsafe的objectFieldOffset方法获取。在AtomicInteger中提供的线程安全方法中,通过字段valueOffset的值可以定位到AtomicInteger对象中value的内存地址,从而可以根据CAS实现对value字段的原子操作。
线程应用:
//取消阻塞线程
public native void unpark(Object thread);
//阻塞线程
public native void park(boolean isAbsolute, long time);
//获得对象锁(可重入锁)
@Deprecated
public native void monitorEnter(Object o);
//释放对象锁
@Deprecated
public native void monitorExit(Object o);
//尝试获取对象锁
@Deprecated
public native boolean tryMonitorEnter(Object o);
AtomicInteger
底层用的是volatile的变量和CAS来进行更改数据的。
- volatile保证线程的可见性,多线程并发时,一个线程修改数据,可以保证其它线程立马看到修改后的值
- CAS 保证数据更新的原子性。

707

被折叠的 条评论
为什么被折叠?



