00 总体脑图
第一章、基础篇
01 计算机基础 ## 1.1 操作系统
略
目录 | 文件 | 内容 | 搜索 | 系统类 | 网络 | 权限 | |
---|---|---|---|---|---|---|---|
cd | cp/scp | vi/vim | find: find . -name xx | kill | netstat -an | chmod | |
ls | mv | cat | grep: ps -ef | grep xx | lsof: lsof-i:port | ||
mkdir | rm | tail | whereis | top | |||
pwd | tar/gzip/unzip | which | su | ||||
ll | uname | ||||||
df |
02 计算机网络
编号 | 浏览器 | 长度(字节) |
---|---|---|
1 | IE | 2048 |
2 | 360 | 2118 |
3 | Firfox | 65536 |
4 | Safari | 80000 |
5 | Opera | 190000 |
6 | Chrome | 8182 |
请求方法 | 说明 | 数据库操作 |
---|---|---|
post | 新增 | C |
get | 查询 | R |
put | 更新 | U |
delete | 删除 | D |
code | 描述 |
---|---|
以1开头(1xx) | 服务器处收到请求的过程中 |
以2开头(2xx) | 请求成功 |
以3开头(3xx) | 重定向 |
以4开头(4xx) | 客户端状态码(由于客户端这边的问题,服务器无法处理。如客户端这边的数据格式不对、给的资源路径 不对、权限不足等) |
以5开头(5xx) | 服务端状态码(服务器处理出错,服务端内部业务异常或系统异常) |
几个概念
1、文中的使用非对称加密对称密钥就是数字信封技术
2、https通讯全过程
TCP三次握手->建立SSL连接(验证双方证书身份、获取对称密钥)->建立https连接(使用对称密钥加密通讯)
3、https单向身份认证一般来说就是客户端验证服务器https证书是否是合法有效的。https双方身份认证就是客户端验证服务器身份,服务器也验证客户端的身份,一般在安全要求 比较高的双方接口对接时,验证双向的身份。
1)客户端向服务器发送https请求,基本包括SSL证书版本等信息给服务器端建立连接。
2)服务器端确认SSL证书版本、服务器公钥证书返回给客户端。
3)客户端校验服务器端证书是否合法(通过CA证书来验证服务器身份)。验证通过就继续下一步,验证不通过警告提示(如浏览器的警告提示)。一般CA证书的验证包括:证书信任链验证、证书有效期验证、证书是否吊销等。
4)客户端发送可支持的对称加密方案到服务器端供其选择。
5)服务器端将选择好的加密方案(算法)以明文方式发给客户端。
6)客户端收到采用的加密算法后,产生随机码作为对称加密的密钥。
7)客户端把对称密钥使用服务器端的公钥(非对称加密)加密后发送到服务器端。
8)服务器端使用私钥(非对称加密)解密后获得对称加密的密钥。
9)双方通过对称加密密钥(随机数)进行加密通讯。
以下是浏览器通过https访问百度服务器的具体实例:
1)客户端通过发送Client Hello报文开始SSL通信。报文中包含客户端支持的SSL的指定版本、加密组件列表、所使用的加密算法及密钥长度、随机数Random-A等。
2)服务器可进行SSL通信时,会以Server Hello报文作为应答。和客户端一样,在报文中包含SSL版本以及加密组件(客户端支持中筛选)、服务器生成的随机数Random-B、服务器公钥证书PublicKey-B。
3)客户端校验服务器端证书PublicKey-B是否合法(通过CA证书来验证服务器身份)。验证通过就继续下一步,验证不通过警告提示(如浏览器的警告提示)。一般CA证书的验证包括:证书信任链验证、证书有效期验证、证书是否吊销等。
4)客户端将自己的公钥证书PublicKey-A发送给服务器端,供服务器验证客户端身份。客户端事先也需申请相应的SSL证书。
5)服务器校验客户端证书PublicKey-A是否合法(通过CA证书来验证服务器身份)。验证通过就继续下一步,验证不通过警告提示(如浏览器的警告提示)。一般CA证书的验证包括:证书信任链验证、证书有效期验证、证书是否吊销等。
6)客户端发送可支持的对称加密方案到服务器端供其选择。
7)服务器端将选择好的加密方案(算法)发送给客户端,报文使用客户端公钥PublicKey-A进行非对称加密。
8)客户端使用自己的私钥PrivateKey-A解密报文,并通过Random-A、Random-B推算出Random-C,也就是对称加密的密钥。
9)客户端使用服务器公钥PublicKey-B非对称加密对称密钥Random-C,发送给服务器。
10)服务器使用自己的私钥PrivateKey-B解密报文,解决出对称密钥Random-C。
11)双方完成身份验证,并使用对称密钥Random-C进行加密通讯。
【问题1】为什么需要三次握手?
相对UDP,TCP是可靠的通讯协议,是全双工通信。TCP三次握手的关键在于,序列号seq的交换确认,因为对于客户端和服务端来说,双方序列号的确认是可靠传输的关键。1、2步握手只能确定发送方发和收正常,并不能确定接收方也是发和收正常,增加了第3次握手,才能保证接收方也是发和收都正常。
【问题2】为什么连接的时候是三次握手,关闭的时候却是四次挥手?
由于服务器端回复已经响应完毕,此时客户端并不是立刻就收完了,所以服务器处于半关闭状态,等服务器完全处理完,客户端收到通知后才完全关闭,固为4次。
【问题3】怎么简单描述三次握手?
就像是小明与小红发微信一样
小明:我要给你发数据了
小红:好的,我准备好了,你发吧
小明:好的,收到
【问题4】为什么是四次挥手
小红:我的数据发完了。
小明:好的,收到,我看看收完了没。
小明:好的,已经收完了,你关闭吧。
小红:好的,我关闭了。
一般客户端向服务器发送请求后服务器会回应响应。但服务器不会主动向客户端发送请求。响应式的方式可以解决此类问题。当然传统的方式也可以达到相同的效果比如:轮询、http长连接。
方式 | 实现 | 说明 |
---|---|---|
轮询 | js+ajax定时轮询 | 每次轮询都是一对request+response,消耗资源、不实时 |
http长连接 | keep-alived | http每次请求均是一对request+response,keep-alived类似把多个request放在同一个连接发送,当然每个request都会有自己对应的response,实际上也是多次的请求与响应,且keep-alived本身是不可控的 |
comet | comet http长连接的另一种方式,类似hack的方式,但实际上还是发送一个request连接,服务器不是立即返回,等到服务器有相应的结果再返回。这个连接不能被重复使用 | |
websocket | websocket协议 | websocket类似tcp是全双工通信,在协议本身上(请求头、二进制帧)网络开销更小、速度更快、更及时。 |
03 JAVA基础
JDK1.7 | JDK1.8 | |
---|---|---|
存储 | 数组+链表 | 数组+链表+红黑树 |
位置算法 | h & (length-1) | h & (length-1) |
链表超过8 | 链表 | 红黑对(链表超过8且数组长度超64) |
节点结构 | Entry |
Node |
插法 | 头插法(扩容环化造成死循环) | 尾插法 |
JDK1.7
使用一个Entry数组来存储数据,用key的hashcode取模来决定key会被放到数组里的位置,如果hashcode取模后的结果相同,那么这些key会被定位到Entry数组的同一个格子里,这些key会形成一个链表;这样数据遍历时间就过长。1.7中hashmap链表插入的方式是使用头插法。
JDK1.8
使用一个Node数组来存储数据,但是这个Node可能是链表结构,也可能是红黑树结构;如果插入的元素key的hashcode值相同,那么这些key也会被定位到Node数组的同一个格子里,如果不超过8个使用链表存储,超过8个且Node数组长度超过64,会将链表转换为红黑树。1.8中hashmap链表插入的方式是使用尾插法。
【相关问题】
问题一:为什么jdk1.8后改为尾插法?
主要是因为头插法在多线程扩容情况下会引起链表环。那什么是链表环呢?
引用一张图片(来源blog.csdn.net/weixin_3142…):
主要是支持安全的多线程的读写
JDK1.7 | JDK1.8 | |
---|---|---|
实现 | segment+hashentry | Node+CAS+Synchronized |
锁 | Segment继承ReetrantLock | Synchronized |
存储 | 数组+链表 | 数组+链表+红黑树 |
链表超过8 | 数组 | 红黑树 |
插法 | 头插法 | 尾插法 |
equals源代码中可以看到:
JDK自带的equals有两种,针对Object对象及String对象
1、String中的equals
==判断是否相等,相等直接返回true->再判断是否为string类型,否直接返回false->是则继续判断对象length->循环判断char是否相等(jdk8使用的是char,高版本的jdk已使用效率更高的byte)
2、Object中的equals
直接return (this == obj),一般业务对象比较要改造equals方法
p.s.自定义对象需要重写equals
**
String 中的对象是不可变的,也就可以理解为常量,线程安全。AbstractStringBuilder 是 StringBuilder 与 StringBuffer 的公共父类,定义了一些字符串的基本操作,如 expandCapacity、append、insert、indexOf 等公共方法。StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的。
操作少量的数据: 适用 String
单线程操作字符串缓冲区下操作大量数据: 适用 StringBuilder
多线程操作字符串缓冲区下操作大量数据: 适用 StringBuffer
上面的结论没有完全理解的话也没关系,我们来看一个具体的案例!
浅拷贝
浅拷贝的示例代码如下,我们这里实现了 Cloneable 接口,并重写了 clone() 方法。
clone() 方法的实现很简单,直接调用的是父类 Object 的 clone() 方法。
public class Address implements Cloneable {
private String name; // 省略构造函数、Getter&Setter方法
@Override
public Address clone() {
try {
return (Address) super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
}
public class Person implements Cloneable {
private Address address;
// 省略构造函数、Getter&Setter方法
@Override
public Person clone() {
try {
Person person = (Person) super.clone();
return person;
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
}
复制代码
测试 :
Person person1=new Person(new Address("武汉"));
Person person1Copy=person1.clone(); // true
System.out.println(person1.getAddress()==person1Copy.getAddress());
复制代码
从输出结构就可以看出, person1 的克隆对象和 person1 使用的仍然是同一个 Address 对象。
深拷贝
这里我们简单对 Person 类的 clone() 方法进行修改,连带着要把 Person 对象内部的 Address 对象一起复制。
@Override
public Person clone(){
try{
Person person=(Person)super.clone();
person.setAddress(person.getAddress().clone());
return person;
}catch(CloneNotSupportedException e){
throw new AssertionError();
}
}
复制代码
测试 :
Person person1 = new Person(new Address("武汉"));
Person person1Copy = person1.clone(); // false
System.out.println(person1.getAddress() == person1Copy.getAddress());
复制代码
从输出结构就可以看出,虽然 person1 的克隆对象和 person1 包含的 Address 对象已经是不同的了。
启动spring容器(创建beanfactory)->加载配置(注解、xml)->实例化bean(执行构造方法)->注入属性(设置bean属性)->初始化bean(设置属性值)->使用->销毁
CAS(Compare and swap 比较并交换) 一种CPU指令级的操作,保证数据一致性。A B线程更新变量值,A B线程先获取变量值,然后生成新值,变量值放在内存V中、预期原值为A(备份值)、新值为B。如果V=A 更新新值。在多线程情况下保证值的一致性。主要是保证线程当时取的值,在要更新的时候内存还是那个值没有被其它线程改变了。
方法 一般使用动词
效率方面
StringBulider > StringBuffer > String
@SpringbootApplication @SpringBootConfiguration @Configuration @AutoWired
@Controller @RestController @Component @RequestMapping @GetMapping
@Service @Resource
04 JAVA进阶 # 1 JVM
为了屏蔽各种硬件和操作系统对内存访问的差异,Java定义了JVM内存模型
主内存->工作内存->线程
java内存模型和java运行时数据区的关系:主内存对应着java堆和方法区,工作内存对应着java栈。
1)判断是否是垃圾
可以通过引用计数算法和可达性分析算法不判断,由于引用计数算法无法解决循环引用的问题,所以目前使用的都是可达性分析算法
2)遍历并回收垃圾
可以通过垃圾收集器(Serial/Parallel/CMS/G1)来回收垃圾,垃圾收集器使用的算法标记清除算法、标记整理算法、复制回收算法和分代回收算法。
Minor | [maɪnə] | Eden |
---|---|---|
Young | Eden+S0+S1 | |
Major | [ˈmeɪdʒə(r)] | Old |
Full | Eden+S0+S1+Old |
标记清除:先标记,标记完毕之后再清除,效率不高,会产生碎片
标记整理:标记完毕之后,让所有存活的对象向一端移动
复制算法:Eden区S0、S1 区比例为8:1:1 ,就是上面谈到的 YGC使用的就是复制回收算法。
1.3.4.1 收集器
1)Serial收集器
一个单线程的收集器,在进行垃圾收集时候,必须暂停其他所有的工作线程直到它收集结束。
特点:CPU利用率最高,停顿时间即用户等待时间比较长。
适用场景:小型应用
通过JVM参数-XX:+UseSerialGC可以使用串行垃圾回收器。
2)Parallel收集器(jdk8默认收集器)
采用多线程来通过扫描并压缩堆
特点:停顿时间短,回收效率高,对吞吐量要求高。
适用场景:大型应用,科学计算,大规模数据采集等。
通过JVM参数 XX:+USeParNewGC 打开并发标记扫描垃圾回收器。
3)CMS收集器
采用“标记-清除”算法实现,使用多线程的算法去扫描堆,对发现未使用的对象进行回收。
(1)初始标记
(2)并发标记
(3)并发预处理
(4)重新标记
(5)并发清除
(6)并发重置
特点:响应时间优先,减少垃圾收集停顿时间
适应场景:服务器、电信领域等。
通过JVM参数 -XX:+UseConcMarkSweepGC设置
4)G1收集器(jdk17默认收集器)
在G1中,堆被划分成 许多个连续的区域(region)。采用G1算法进行回收,吸收了CMS收集器特点。
特点:支持很大的堆,高吞吐量
--支持多CPU和垃圾回收线程
--在主线程暂停的情况下,使用并行收集
--在主线程运行的情况下,使用并发收集
实时目标:可配置在N毫秒内最多只占用M毫秒的时间进行垃圾回收
通过JVM参数 –XX:+UseG1GC 使用G1垃圾回收器
1.3.4.2 收集器对比
收集器 | CMS | G1 |
---|---|---|
回收算法 | 标记清除 | 标记整理 |
回收区域 | 老年代 | 新生代+老年代 |
内存布局 | 传统 | 将新生代、老年代切一起分成一个个Region |
内存碎片 | 产生碎片空间 | 碎片空间小 |
并发 | 并发 | 并发 |
JDK使用 | JDK8默认(Parallel) | JDK9默认 |
停顿时间 | 最短停顿时间 | 可预测停顿时间 |
名称 | 说明 | 备注 |
---|---|---|
GC | Garbage Collection 垃圾回收 | |
GC种类 | MinorGC、MajorGC、YongGC、OldGC | |
GC收集算法 | 标记清除算法、标记整理算法、复制回收算法 | |
GC收集器 | Serial、Parallel、CMS、G1、ZGC | |
存活判断算法 | 引用计数算法、可达性分析算法 |
在Java虚拟机中GC是一个重要的部分,我们可以看到在GC中有不少概念GC种类、GC收集算法、GC收集器、引用计数算法、可达性分析算法。原本就比较难懂这概念一多就有点乱了。下面一个图从总体上梳理了一下它们之间的相互关系。
1)整个垃圾回收可以分成两步:1、先判断对象是否存活;2、再回对象。
2)在整个垃圾回收各概念之间的关系
GC收集器核心是GC收集算法
GC收集算法一般先要判断对象是否存活就会用到引用计数算法或可达性分析算法
引用计数算法解决不了循环引用的情况,所以目前使用的都是可达性分析算法
GC分为4个种类,作用在内存的不同区域(新生代Eden/S0/S1、老年代)。这时GC收集器会相互组合完成不同种类的GC,从而达到JVM GC的功能
1、全局变量与局部变量在内存中的区别?
1、jdk1.7方法区(习惯上把永久代叫方法区)
2、jdk1.8方法区(由元数据区+堆组成),其中字符串常量池被放在堆中
jdk1.7的永久代在jdk1.8中去掉并换成元数据区
1、多线程如何共享数据
/**
* 卖票处理
* @author yang
*/
public class SellTicket {
//卖票系统,多个窗口的处理逻辑是相同的
public static void main(String[] args) {
Ticket t = new Ticket();
new Thread(t).start();
new Thread(t).start();
}
}
/**
* 将属性和处理逻辑,封装在一个类中
* @author yang
*/
class Ticket implements Runnable{
private int ticket = 10;
public synchronized void run() {
while(ticket>0){
ticket--;
System.out.println("当前票数为:"+ticket);
}
}
}
作者:那时年少轻狂
链接:https://www.imooc.com/article/17112?block_id=tuijian_wz
来源:慕课网
本文原创发布于慕课网 ,转载请注明出处,谢谢合作
复制代码
public class MultiThreadShareData {
public static void main(String[] args) {
ShareData data = new ShareData();
new Thread(new MyRunnable1(data)).start();
new Thread(new MyRunnable2(data)).start();
}
}
class MyRunnable1 implements Runnable {
private ShareData data;
public MyRunnable1(ShareData data) {
this.data = data;
}
public void run() {
data.decrement();
}
}
class MyRunnable2 implements Runnable {
private ShareData data;
public MyRunnable2(ShareData data) {
this.data = data;
}
public void run() {
data.increment();
}
}
class ShareData {
private int j = 10;
public synchronized void increment() {
j++;
System.out.println("线程:" + Thread.currentThread().getName() + "加操作之后,j = " + j);
}
public synchronized void decrement() {
j--;
System.out.println("线程:" + Thread.currentThread().getName() + "加操作之后,j = " + j);
}
}
作者:那时年少轻狂
链接:https://www.imooc.com/article/17112?block_id=tuijian_wz
来源:慕课网
本文原创发布于慕课网 ,转载请注明出处,谢谢合作
复制代码
2、父子线程如果共享数据
通过 InteritableThreadLocal实现共享
类别 | synchronized | Lock |
---|---|---|
形态 | java关键字、jvm层次 | 类 |
释放 | 1、自动释放2、执行完同步代码或异常 | 1、手动释放unlock()2、在finally里必须释放,不然会死锁 |
类型 | 悲观锁/非公平锁 | 悲观锁/公平锁、非公平锁 |
状态 | 无法判断 | 可判断tryLock() |
性能 | 少量同步 | 大量同步 |
总结:建议使用synchronized,在jdk1.5之前Lock优于synchronized,但在jdk1.5之后对synchronized进行了优化,后面在性能方面基本与Lock一样且使用简单(有作者说"synchronized是亲生的,jdk还是会一直优化他不会让Lock优于它")。
参数 | 说明 |
---|---|
corePoolSize | 核心线程数量,线程池维护线程的最少数量 |
maximumPoolSize | 线程池维护线程的最大数量 |
keepAliveTime | 线程池除核心线程外的其他线程的最长空闲时间,超过该时间的空闲线程会被销毁 |
unit | keepAliveTime的单位,TimeUnit中的几个静态属性:NANOSECONDS、MICROSECONDS、MILLISECONDS、SECONDS |
workQueue | 线程池所使用的任务缓冲队列 |
threadFactory | 线程工厂,用于创建线程,一般用默认的即可 |
handler | 线程池对拒绝任务的处理策略 |
当线程池任务处理不过来的时候(什么时候认为处理不过来后面描述),可以通过handler指定的策略进行处理,ThreadPoolExecutor提供了四种策略:
可以通过实现RejectedExecutionHandler接口自定义处理方式。
一个线程提交到线程池的处理流程如下图
总结即:处理任务判断的优先级为 核心线程corePoolSize、任务队列workQueue、最大线程maximumPoolSize,如果三者都满了,使用handler处理被拒绝的任务。
注意:
队列操作:
方法 | 说明 |
---|---|
add | 增加一个元索; 如果队列已满,则抛出一个异常 |
remove | 移除并返回队列头部的元素; 如果队列为空,则抛出一个异常 |
offer | 添加一个元素并返回true; 如果队列已满,则返回false |
poll | 移除并返回队列头部的元素; 如果队列为空,则返回null |
put | 添加一个元素; 如果队列满,则阻塞 |
take | 移除并返回队列头部的元素; 如果队列为空,则阻塞 |
element | 返回队列头部的元素; 如果队列为空,则抛出一个异常 |
peek | 返回队列头部的元素; 如果队列为空,则返回null |
【附】阿里巴巴Java开发手册中对线程池的使用规范
1.【强制】创建线程或线程池时请指定有意义的线程名称,方便出错时回溯。
正例:
public class TimerTaskThread extends Thread {
public TimerTaskThread(){
super.setName("TimerTaskThread");
...
}
}
ThreadPoolExecutor通过几个核心参数来定义不同类型的线程池,适用于不同的使用场景;其中在任务提交时,会依次判断corePoolSize, workQueque, 及maximumPoolSize,不同的状态不同的处理。
1)主线程判断所有子线程是否执行完成
jdk8增加的类,可以代替CountDownLatch
可以发现,设计模式好像都是类似的。越看越感觉都着不多。其实都是类似面向接口编程的一种体现,只不过侧重点不一样或者说要体现的结果不一样。
问题一:应对可能变化的对象实现
方案:间接创****建
模式:工厂模式
问题二:为请求指定相应的操作(类似请假审批,不同时长对应不同职位的审批人)
方案:程序根据请求动态选择操作
模式:责任链模式
一个行为型模式,包含多个行为或职责的业务,通过策略模式简化
public class StrategyContext {
Strategy strategy;
public StrategyContext(Strategy strategy) {
this.strategy = strategy;
}
/**
*
*/
public int context(int a, int b) {
return strategy.operator(a,b);
}
}
复制代码
策略模式的核心为StrategyContext上下文类,持有strategy对象,在context完成操作。
如何使用策略模式解决大量使用if else 或大量switch问题,策略模式+反射。
策略模式后好像使用都还是要用if else来决定调用哪个类,所以在引入策略模式后,在上下文类还要增加反射。
public class StrategyContext {
Strategy strategy;
public StrategyContext(String type) throws Exception {
Class clazz = Class.forName(type);
this.strategy = (Strategy) clazz.newInstance();
}
/**
*
*/
public int context(int a, int b) {
return strategy.operator(a,b);
}
复制代码
当然这里的type可以用个枚举来解决。感觉代价非常大是不是没必要,不过代码的可读性还是增强了。
p.s. 在框架里策略模式中的Context一般不会直接出现,类似spring中直接在使用时就通过注解给设置了
描述:原接口Shape不变,方法数量不变,在方法实现中增加修饰
场景:
场景一:一个类功能简单,满足不了我们的需求
场景二:给原方法增加日志功能,不改变原方法,新的实现类去实现此功能,带入的对象为接口对象
特点
public class ColorShapeDecorator extends ShapeDecorator {
public ColorShapeDecorator(Shape shape) {
super(shape);
}
@Override
public void draw() {
setColor();
shape.draw();
}
private void setColor() {
//设置画图颜色
}
}
复制代码
设置一个中间代理来控制访问原目标对象,达到增强原对象的功能和简化访问方式的目的
场景:
场景一:不改变原方法,对原方法增加耗时的计算
场景二:rpc远程调用,client端进行动态代理类似耗时计算一样,用户不用关心client的具体实现
分类
说明
/**
* 与适配器模式的区别,适配器模式主要改变所考虑对象的接口,
* 而代理模式不能改变所代理类的接口。与装饰器模式的区别,
* 装饰器模式是为了增强功能,代理模式是为了加以控制
*/
public class ProxySigntureService implements SigntureService {
private SigntureService signatureService;
/**
* Default constructor
*/
public ProxySigntureService(SigntureService signatureService) {
this.signatureService = signatureService;
}
public void sign() {
//控制对这个对象的访问
// 实现电子签名
}
}
复制代码
public class DynamicProxySignatureService implements InvocationHandler {
private Object obj;
public DynamicProxySignatureService(Object obj) {
this.obj = obj;
}
@Override
public Object invoke(Object proxyObj, Method method, Object[] objects)
throws Throwable {
return method.invoke(obj,objects);
}
}
复制代码
参考文章:blog.csdn.net/liujiahan62…
描述: 原接口不变,增加方法数量
场景:
场景一:原接口不变,在基础上增加新的方法。
场景二:接口的抽象方法很多,不想一一实现,使用适配器模式继承原实现类,再实现此接口
新接口
public interface Targetable {
/**
*
*/
public void method01();
/**
*
*/
public void method02();
}
复制代码
原接口实现类
public class Source {
public void method01() {
// TODO implement here
}
}
复制代码
适配器类,用于实现新接口。继承原实现类,同时实现新接口。
public class Adapter extends Source implements Targetable {
/**
*
*/
public void method02() {
// TODO implement here
}
}
复制代码
测试类
public class AdapterTest {
public static void main(String[] args) {
Targetable targetable = new Adapter();
targetable.method01();
targetable.method02();
}
}
复制代码
保证被创建一次,节省系统开销。
1)单例实现方式
2)实现代码
package com.hanko.designpattern.singleton;
/**
* 饿汉式 (饿怕了,担心没有吃,所以在使用之前就new出来)
*优点:实现简单,安全可靠
*缺点:在不需要时,就已实例化了
* @author hanko
* @version 1.0
* @date 2020/9/14 18:50
*/
public class HungrySingleton {
//特点一 静态私有变量 直接初始化
private static HungrySingleton instance = new HungrySingleton();
//特点二 构造函数私有
private HungrySingleton(){
}
public static HungrySingleton getInstance(){
return instance;
}
public void doSomething(){
//具体需要实现的功能
}
}
复制代码
package com.hanko.designpattern.singleton;
/**
* 懒汉式(非常懒,所以在要使用时再去new)
*优点:简单
*缺点:存在线程安全问题
* @author hanko
* @version 1.0
* @date 2020/9/14 18:50
*/
public class SluggardSingleton {
//特点一 静态私有变量,先不初始化
private static SluggardSingleton instance;
//特点二 构造函数私有
private SluggardSingleton(){
}
//特点三 null判断,没有实例化就new
public static SluggardSingleton getInstance(){
if(instance == null){
instance = new SluggardSingleton();
}
return instance;
}
public void doSomething(){
//具体需要实现的功能
}
}
复制代码
package com.hanko.designpattern.singleton;
/**
* 懒汉式(非常懒,所以在要使用时再去new)
*优点:简单
*缺点:存在线程安全问题
* @author hanko
* @version 1.0
* @date 2020/9/14 18:50
*/
public class SluggardSingleton {
//特点一 静态私有变量,先不初始化
private static SluggardSingleton instance;
//特点二 构造函数私有
private SluggardSingleton(){
}
//特点三 null判断,没有实例化就new
public static synchronized SluggardSingleton getInstance(){
if(instance == null){
instance = new SluggardSingleton();
}
return instance;
}
public void doSomething(){
//具体需要实现的功能
}
}
复制代码
package com.hanko.designpattern.singleton;
/**
* 双重校验
*对懒汉式单例模式做了线程安全处理增加锁机制
* volatile变量级
* synchronized 类级
* @author hanko
* @version 1.0
* @date 2020/9/15 9:53
*/
public class DoubleCheckSingleton {
//特点一 静态私有变量,增加volatile变量级锁
private static volatile DoubleCheckSingleton instance;
//特点二 构造函数私有
private DoubleCheckSingleton(){
}
//特点三 双重null判断 synchronized类级锁
public static DoubleCheckSingleton getInstance(){
if (instance == null){
synchronized(DoubleCheckSingleton.class){
if (instance == null){
instance = new DoubleCheckSingleton();
}
}
}
return instance;
}
}
复制代码
package com.hanko.designpattern.singleton;
/**
* 内部静态类方式
*优点:静态内部类不会在InnerStaticSingleton类加载时加载,
* 而在调用getInstance()方法时才加载
*缺点:存在反射攻击或者反序列化攻击
* @author hanko
* @version 1.0
* @date 2020/9/15 10:03
*/
public class InnerStaticSingleton {
//特点一:构造函数私有
private InnerStaticSingleton(){
}
//特点二:静态内部类
private static class InnerSingleton{
private static InnerSingleton instance = new InnerSingleton();
}
public InnerSingleton getInstance(){
return InnerSingleton.instance;
}
public void doSomething(){
//do Something
}
}
复制代码
package com.hanko.designpattern.singleton;
/**
* 枚举实现单例简单安全
*
* @author hanko
* @version 1.0
* @date 2020/9/14 19:01
*/
public enum EnumSingleton {
INS;
private Singleton singleton;
EnumSingleton() {
singleton = new Singleton();
}
public void doSomething(){
singleton...
//具体需要实现的功能
}
}
EnumSingleton.INS.doSomething();
复制代码
(简单工厂、抽象工厂):解耦代码。
简单工厂:用来生产同一等级结构中的任意产品,对于增加新的产品,无能为力。
工厂方法:用来生产同一等级结构中的固定产品,支持增加任意产品。
抽象工厂:用来生产不同产品族的全部产品,对于增加新的产品,无能为力;支持增加产品族。
参考文章:zhuanlan.zhihu.com/p/248497545
定义了对象之间的一对多的依赖,这样一来,当一个对象改变时,它的所有的依赖者都会收到通知并自动更新。
提供一个统一的接口,用来访问子系统中的一群接口,外观定义了一个高层的接口,让子系统更容易使用。
允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。与策略模式类似,策略模式侧重点在一个事的不同实现方式抽离出来,而状态模式是一个事的不同状态抽离出来(开始、进行中、结束),每次状态完成自己的业务逻辑。
Java8
Java17
sealed class 密封类允许描述哪个类或接口可以扩展或实现这个类或接口。简而言之,我们可以限制谁可以使用这个类或接口。假设我们有一个学生抽象类,如果我们将其设为一个密封类,并且只允许ScienceStudent和CommerceSudent扩展该类,那么只有这些类才能扩展该Student,而其他类如果试图扩展该类,则会出错。
public abstract sealed class Student permits ScienceStudent , CommerceStudent { ... }
这提供了一种比访问修饰符更具声明性的方法来限制超类的使用。
Java19
类似 Go 语言的协程。它是轻量级线程,可显著减少编写、维护和观察高吞吐量并发应用程序的工作量。其目标包括以简单的请求线程样式编写的服务器应用程序,使得能够接近最佳的硬件利用率,现有代码能够以最小的更改采用虚拟线程,并启用故障排除、调试和使用现有 JDK 工具分析虚拟线程。
第二章、存储篇
使用LinkedHashMap实现LRU
1)LinkedHashMap被get过的元素会自动放在尾项
2)LinkedHashMap链表被删除不用补位
1、缓存穿透 (缓存、DB均无,穿透)
缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中时被动写的,并且出于容错考虑,如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义。在流量大时,可能DB就挂掉了,要是有人利用不存在的key频繁攻击我们的应用,这就是漏洞。
有很多种方法可以有效地解决缓存穿透问题,最常见的则是采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。另外也有一个更为简单粗暴的方法(我们采用的就是这种),如果一个查询返回的数据为空(不管是数据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。
布隆过滤器缺点就是只能存不能直接删除,最近出来一个布谷鸟过滤器。
2、缓存击穿(缓存无、DB有,击穿)
对于一些设置了过期时间的key,如果这些key可能会在某些时间点被超高并发地访问,是一种非常“热点”的数据。这个时候,需要考虑一个问题:缓存被“击穿”的问题,这个和缓存雪崩的区别在于这里针对某一key缓存,前者则是很多key。
缓存在某个时间点过期的时候,恰好在这个时间点对这个Key有大量的并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。
1)使用mutex key(互斥锁)
业界比较常用的做法,是使用mutex。简单地来说,就是在缓存失效的时候,不是立即去load db,而是先使用缓存工具的某些带成功操作返回值的操作(比如Redis的SETNX)去set一个mutex key(对缓存的逻辑加锁),当操作返回成功时,再进行load db的操作并回设缓存。
2) "提前"使用互斥锁(mutex key):
在value内部设置1个超时值(timeout1), timeout1比实际的memcache timeout(timeout2)小。当从cache读取到timeout1发现它已经过期时候,马上延长timeout1并重新设置到cache。然后再从数据库加载数据并设置到cache中。伪代码如下:
3) "永远不过期":
这里的“永远不过期”包含两层意思:
(1) 从redis上看,确实没有设置过期时间,这就保证了,不会出现热点key过期问题,也就是“物理”不过期。(2) 从功能上看,如果不过期,那不就成静态的了吗?所以我们把过期时间存在key对应的value里,如果发现要过期了,通过一个后台的异步线程进行缓存的构建,也就是“逻辑”过期
从实战看,这种方法对于性能非常友好,唯一不足的就是构建缓存时候,其余线程(非构建缓存的线程)可能访问的是老数据,但是对于一般的互联网功能来说这个还是可以忍受。
3、缓存雪崩(一批过期的key)
缓存雪崩是指在我们设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到DB,DB瞬时压力过重雪崩。
缓存失效时的雪崩效应对底层系统的冲击非常可怕。大多数系统设计者考虑用加锁或者队列的方式保证缓存的单线程(进程)写,从而避免失效时大量的并发请求落到底层存储系统上。一个简单方案就是将缓存失效时间分散开(过期时间错开) ,比如我们可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。
1、数据类型
String Hash List Set ZSet
2、分布式锁
1)Redis实现分布式锁面临的主要问题
就是能否保证分布式锁的原子性,主要体现在以下两点:
2)详细说明
a、未释放
b、误释放
3)实现方案
p.s. 手动实现方案需程序解决以上两问题,使用第三方的客户端或组件一般自带解决以上问题
3、常见问题
1)单线程的redis为什么快(目前redis已出多线程版本)
2)持久化
AOF记录的是操作命令,并非数据。恢复时需要执行全是命令
rdb记录的是数据。恢复时只需恢复对应的数据。
先引用一张图片
目录图
1、SQL语句优化
vim /etc/my.cnf
[mysqld]
slow-query-log = on # 开启慢查询功能
slow_query_log_file = /data/slow-query.log # 慢查询日志存放路径与名称
long_query_time = 5 # 查询时间超过5s的查询语句
log-queries-not-using-indexes = on # 列出没有使用索引的查询语句
复制代码
1)查看所有日志状态: show variables like '%quer%';
2)查看慢查询状态:show variables like 'show%'
MySql内部函数explain(查询sql的执行计划),explain返回各列的含义
table:显示这一行的数据是关于哪张表的
type:这是重要的列,显示连接使用了何种类型。
从最好到最差的连接类型为const、eq_reg、ref、range、index 和ALL
possible_keys:显示可能应用在这张表中的索引。如果为空,没有可能的索引。
key:实际使用的索引。如果为NULL,则没有使用索引。
keyjen:使用的索引的长度。在不损失精确性的情况下,长度越短越好
ref:显示索引的哪一列被使用了,如果可能的话,是一个常数
rows: MYSQL认为必须检查的用来返回请求数据的行数
复制代码
子查询尽量不用或改成join
group by 尽量使用索引字段
给查询语句增加limit
2、索引优化
3、数据库结构优化
4、配置优化
1)缓存池大小
2)打开文件限制
1)innodb缓冲池内存占用大小
2)innodb_buffer_pool_instances 缓冲池个数
3)innodb_log_buffer_size 缓冲的大小
4)innodb IO配置
5、服务器硬件优化
1、InnoDB myisam区别
2、怎么处理MySQL的慢查询?
开启慢查询->explain分析SQL->横纵向分表
3、Myisam和Innodb的区别?
全文索引 | 事务 | 锁 | |
---|---|---|---|
Innodb | 无 | 有 | 行 |
Myisam | 有 | 无 | 表 |
4、Mysql中索引类型有哪些?
普通索引:允许被索引的数据列包含重复的值
唯一索引:可以保证数据记录的唯一性
主键索引:是一种特殊的唯一索引,在一张表中只能定义一个主键索引,主键用于唯一标识一条记录,使用关键字primary key来创建
全文索引:通过建立倒排索引,可以极大的提升检索效率,解决判断字段是否包含的问题,是目前搜索引擎使用的一种关键技术索引可以极大地提高数据的查询速度
5、为什么用自增主键
数据库使用的是B+Tree,非自增会出现要插入到节点与节点中间,会发生节点分裂。
6、为什么用联合索引
减少开销。建一个联合索引(col1,col2,col3),实际相当于建了(col1),(col1,col2),(col1,col2,col3)三个索引。
第三章、性能篇
top查内存->thread查cpu/死锁->dashboard内存
heapdump 查dump****
trace跟踪代码->watch查函数参数与返回值
tt -i 1001 //上面指令发现异常后,查看异常
tt -i 1001 -p //重新调用,重现异常
ognl '@com.Arthas@hashSet' //查看
ognl '@[email protected]("test")' //更新
trace org.springframework.web.servlet.DispatcherServlet *
复制代码
watch org.springframework.web.method.support.InvocableHandlerMethod doInvoke "{params,returnObj}"
复制代码
tt -t com.wdbyte.arthas.service.UserServiceImpl mysql
复制代码
tt -i 1001 -p
复制代码
直接在Idea plugins里搜索xrebel
安装后重启,在Idea上可以看到多出一些工具按钮
点击用xreble启动项目,控制台日志显示xrebel地址,打开后试用或破解
然后随便访问项目的URL,发现
把每个被调用或执行的类的方法耗时都显示出来,同时不同颜色标明耗时情况。
CPU占用过高排查流程 (进程->线程列表->线程堆栈)
相关命令:
#1、查询占用CPU情况
top shift+p(cpu排序) shift+m(内存排序)
#2、通过进程pid,查询对应的线程列表
top -Hp pid
#3、线程id转为十六进制
printf '%x\n' id
#4、jstack查出线程堆栈,现用第3步搜索对应线程进行分析
jstack -l pid
#5.1、jstack具体分析-业务线程(堆栈可定位到类的代码行)
#线程死锁
#线程阻塞
#线程死循环
#5.2、jstack具体分析-GC线程
#jstat -gcutil pid 1000查询是否是频繁gc占用内存
复制代码
内存占用过高排查流程 (进行->jvm内存)
相关命令:
#1、查询占用内存情况
top shift+p(cpu排序) shift+m(内存排序)
#2、查询GC情况
jstat -gcutil pid 1000 #分析年轻代、老年代占用内存情况
#3、jmap查询堆内存使用情况
jmap -histo pid #实例数与内存占用
jmap -dump #与histo类似
jmap -heap #jvm内存占用详情
复制代码
源代码地址:
gitee.com/hankzhousan…
第四章、安全篇
安全API接口认证方案 | ProcessOn免费在线作图,在线流程图,在线思维导图 |
1)官方配置Token验证
2)开发一个Token验证接口
3)通过appid secret获取access_token
4)所有业务URL直接拼接access_token
5)针对报文安全可以设置加密模式,使用在平台配置的AESkey进行加密
private function checkSignature()
{
$signature = $_GET["signature"];
$timestamp = $_GET["timestamp"];
$nonce = $_GET["nonce"];
$token = TOKEN;
$tmpArr = array($token, $timestamp, $nonce);
sort($tmpArr, SORT_STRING);
$tmpStr = implode( $tmpArr );
$tmpStr = sha1( $tmpStr );
if( $tmpStr == $signature ){
return true;
}else{
return false;
}
}
复制代码
微信公众平台开发概述 | 微信开放文档
安全核心在于:
1、定时的Token验证
2、全部接口在https基础下请求
3、access_token具有时效性
4、AES增加安全系数
微信公众平台开发概述 | 微信开放文档
1)在微信官方配置并获取
a、appid mchid(商户id)
b、api key(API v3密钥)即AES-256-GCM 对称加密密钥。
c、商户API证书
d、微信支付平台证书即平台的公钥证书用于加密业务接口的敏感报文。
2)生成签名值
a、签名结构体
HTTP请求方法\n
URL\n
请求时间戳\n
请求随机串\n
请求报文主体\n
复制代码
b、使用商户API私钥(merchantPrivateKey)对以上数据进行SHA256 with RSA然后生成
Base64编码字符串。
3)生成HTTP头中的Authorization数据,Authorization由认证类型和签名信息两个部分组成
a、认证类型,目前为WECHATPAY2-SHA256-RSA2048
b、签名信息
4)使用带Authorization的HTTP请求,调用业务接口
import okhttp3.HttpUrl;
import java.security.Signature;
import java.util.Base64;
String schema = "WECHATPAY2-SHA256-RSA2048";
HttpUrl httpurl = HttpUrl.parse(url);
String getToken(String method, HttpUrl url, String body) {
String nonceStr = "your nonce string";
long timestamp = System.currentTimeMillis() / 1000;
String message = buildMessage(method, url, timestamp, nonceStr, body);
String signature = sign(message.getBytes("utf-8"));
return "mchid="" + yourMerchantId + "","
+ "nonce_str="" + nonceStr + "","
+ "timestamp="" + timestamp + "","
+ "serial_no="" + yourCertificateSerialNo + "","
+ "signature="" + signature + """;
}
String sign(byte[] message) {
Signature sign = Signature.getInstance("SHA256withRSA");
sign.initSign(yourPrivateKey);
sign.update(message);
return Base64.getEncoder().encodeToString(sign.sign());
}
String buildMessage(String method, HttpUrl url, long timestamp, String nonceStr, String body) {
String canonicalUrl = url.encodedPath();
if (url.encodedQuery() != null) {
canonicalUrl += "?" + url.encodedQuery();
}
return method + "\n"
+ canonicalUrl + "\n"
+ timestamp + "\n"
+ nonceStr + "\n"
+ body + "\n";
}
复制代码
wechatpay-api.gitbook.io/wechatpay-a…
略
1、客户端发送clienthello+随机数A
2、服务器发送serverhello+随机数B
3、服务器发送服务器证书B
4、客户端验证服务器证书B
5、客户端利用AB生成新的随机数C,同时使用服务器公钥加密
6、服务器收到密文后,使用服务器私钥解密取得随机数C即对称加密的密钥
zhuanlan.zhihu.com/p/579964062
SpringSecurity+Oauth2
第五章、架构篇
service mesh
未来的微服务架构与技术栈
网关 zuul/gateway
服务注册发现 eureka/nacos/consul
配置中心 configserver/nacos/apollo
熔断限流降级 hystrix/sentinel
监控 springbootadmin
链路器 sleuth+zipkin
www.yuque.com/hanko-yzlxl…
http dubbo rmi hessian[ˈhesiən] webservice
p.s.Consumer Provider定时向Monitor发送信息。
zookeeper
xml 注解 properties API
hessian dubbo fastjson java自带序列化
正式版本:
大白话版本:
第六章、分布式篇
从系统的部署纬度上讲,把应用分布到不同的节点通过网络连接。与集中式对比。
两阶段提交协议用于保证分布式事务的原子性,即所有的参与节点或者全部都执行或者全部不执行,其执行过程主要分为两个阶段:
第一阶段,准备阶段;第二阶段,提交阶段。
1)准备阶段
协调者为每个参与者都发送prepare消息,每个参与者进行表决,返回同意或取消。预执行本地事务,资源阻塞,但不提交事务。
2)提交阶段
协调者基于每个参与者准备阶段的表决,当且仅当所有参与者同意提交,协调者才通知所有的参与者提交事务,否则协调者将通知所有的参与者取消事务。
三阶段提交协议在协调者和参与者中都引入超时机制,并且把两阶段提交协议的第一个阶段拆分成了两步:询问,然后再锁资源,最后真正提交。
(1)三个阶段的执行
1.CanCommit阶段
协调者向参与者发送CanCommit请求,参与者如果可以提交就返回Yes响应,否则返回No响应。
2.PreCommit阶段
协调者根据参与者的反应情况来决定是否可以继续事务的PreCommit操作。
3.DoCommit阶段
协调者基于每个参与者PreCommit阶段的反馈结果,决定真正提交事务,还是中断事务。
2PC | 3PC | |
---|---|---|
超时 | 协调者可设置超时 | 协调者、参与者均可设置超时 |
阻塞 | 进入询问后阻塞所有节点 | 第一阶段缓冲,通过后第二阶段才阻塞 |
一致性 | 节点异常会发生不一致性 | 节点异常会发生不一致性 |
核心思想是:
针对每个操作,都要注册一个与其对应的确认和补偿(撤销)操作。分为三个阶段:
Try 阶段:主要是对业务系统做检测(一致性)及资源预留(准隔离性)
Confirm 阶段:主要是对业务系统做确认提交,Try阶段执行成功并开始执行 Confirm阶段时,默认Confirm阶段是不会出错的。即:只要Try成功,Confirm一定成功。(Confirm 操作满足幂等性。要求具备幂等设计,Confirm 失败后需要进行重试。)
Cancel 阶段主要是在业务执行错误,需要回滚的状态下执行的业务取消,预留资源释放。(Cancel 操作满足幂等性)
相关问题:
如何发现某个服务的Cancel或者Confirm一直没成功,会不停的重试调用他的Cancel或者Confirm逻辑,务必要他成功!
seata处理事务的原理是二阶段提交,二阶段强依赖数据库XA,seata在基础上做了改造,封装了TM的database。
一般索引: 数据由数据索引、数据内容组成,正常情况是通过数据索引来定位内容就像sql查询一样。
倒排索引: 倒排索引是先通过分散的内容片段定位到相视度高的内容,再由内容定位到这条记录,所以称为倒排索引,带有倒排索引的文件称为倒排文件。通俗的讲倒排索引就好比书的目录,通过目录咱们可以准确的找到相应的数据。下面对lucene倒排索引的结构与算法进行介绍。
倒排索引服务于es查询操作,对数据的聚合,排序则需要使用正排索引,下面我们介绍正排索引。
Elasticsearch
方案一、基于ElasticSearch方式
方案二、基于Spark方式
方案三、基于Python方式
复制代码
方案一、基于ElasticSearch方式
详见文章,里面列举了各种ElasticSearch的实现样例。主要是通过ES的fielddata做聚合或调取termvector两种方式实现,当然调用方式比较多http requset、highclient、springboot data repository、elasticsearch resttemplate等调用方式
zhuanlan.zhihu.com/p/315888125
方案二、基于Spark方式
Spark是基于内存的分布式计算组件,Spark官方提供了JavaWordCount的demo,详见以下文章
zhuanlan.zhihu.com/p/329967589
方案三、基于Python方式
如果你只能实现简单的数据词频统计,python是最的方式,几行代码就可以搞定
text = "http requset highclient springboot"
data = text.lower().split()
words = {}
for word in data:
if word not in words:
words[word] = 1
else:
words[word] = words[word] + 1
result = sorted(words.items(), reverse=True)
print(result)
复制代码
参考文章 :zhuanlan.zhihu.com/p/333358727
消息队列如何保证不丢失,考虑的几个方面
单个queue有序
RocketMq在topic基础上可以增加tag进一步筛选
同一个Queue中有序,原理就是使用MessageQueueSelector(两种实现随机与哈希)
生产者
rocketMQTemplate.syncSendOrderly
设置相同的hashKey使消息发送至同一个queue中,保证消息有序
消费者
consumeMode设置成ConsumeMode.ORDERLY
@Component
@RocketMQMessageListener(topic = "topic-lcf1", selectorExpression = "tag1",
consumerGroup = "luchunfeng1",consumeMode = ConsumeMode.ORDERLY)
public class Consumer implements RocketMQListener {
private static final Logger logger = LoggerFactory.getLogger(Consumer.class);
@Override
public void onMessage(String s) {
logger.info(s);
}
}
复制代码
//CONSUME_SUCCESS 消费成功
//RECONSUME_LATER 消费失败,需要稍后重新消费
ConsumeConcurrentlyStatus.RECONSUME_LATER
ACL是access control list的简称,俗称访问控制列表。访问控制,基本上会涉及到用户、资源、权限、角色等
单个partition有序,生产者发送消息指定partition
生产者到kafka服务器
消费者手动提交
consumer.commitSync()
transactional.id
,通过 initTransactions()
初始化事务状态信息,再通过 beginTransaction()
标识一个事务的开始,然后通过 commitTransaction()
或 abortTransaction()
对事务进行 commit 或 abortbroker服务器节点
producer->broker->topic->partition->consumer
从三个方向考虑
分析原因,正常情况下应该是不会积压,积压要不就是发送端突然变大了,要不就是消费端出问题了
消费幂等,方法有以下几种
1、filebeat logstash区别
2、ELK增加kafka
缓冲(filebeat->kafka->logstash->elasticsearch->kibana)
第七章、大数据篇
第八章、运维篇
第九章、热点问题篇
1、传统架构改造成微服务架构
问题说明:微服务拆分后,对应的数据库也会进行拆分。也就是原本很简单功能一个查询就解决,拆分后就得跨多个微服务查询。如果其中一张表数据较少可以代码循环的方式解决,如果每个微服务的数据都比较多就比较麻烦。
方案一:使用视图,通过视图把两个库的中表进行强关联。当然这样也就违避微服务的解耦
方案二:临时表或缓存,通过临时表或缓存构建临时关联数据,使用完就清理
方案三:数据冗余,牺牲空间换时间。类似索引,牺牲索引空间换查询速度。
建议使用方案三(尽量不要使用共享数据库),数据同步的问题可以借助MQ来实现
微服务粒度问题,多次讨论拆分架构。拆分是一个迭代的过程,别试着一步到位拆成很细,一开始千万别拆太细。一般微服务划分越细,那么模块之前的集成就越复杂
技术问题:itextpdf本身不支持SM2算法及数据结构
解决方案:通过分析源代码,通过继承或实现的方式改造itext
技术问题:adobereader不支持自定义签名验证
解决方案:adober官方文档分析
项目中2018年使用springboot1.5.3版本,SpringBoot上传使用的是MultiPartFile,原理是上传文件先存放在tomcat容器层临时目录,再传给springboot的servlet层面,再给到MultiPartFile对象。tomcat临时目录被Linux删除、无法上传文件。(1.5.20就已经修复此bug,当临时目录不存在什么自动生成)。
mysql cpu 内存都飚的比较高,后面发现是mysql开启了慢查询
idea阿里插件(p3c)+sonarqube
微服务后大家只管自己的服务,当需要增加或调整对方业务时,整个业务协调方面困难,研发组长变业务专家(共性技术下沉,转向关注业务)
项目经理(兼架构师)
反面:
正面:
1. 管理方面
2. 技术方面
"不做code review基本上就是背着定时炸弹前进:什么时候炸了都不知道"
第十章、项目实战
互联网用户数据过大,排行榜性能较差
redis zset 数据结构为 id、score、member。下面是具体实现的步骤
1)插入数据
zadd game 90 tom
zadd game 92 sam
zadd game 95 hanko
复制代码
2)获取范围内的排名
zrange game 0 -1 withscores //开始到集合结束的从小到大列表,-1代表结束
zrevrange game 0 -1 withscores //开始到集合结束的从大到小列表
3)获取某用户的排名
zrank game hanko
zrevrank game hanko
1、策略
限流: 鉴于只有少部分用户能够秒杀成功,所以要限制大部分流量,只允许少部分流量进入服务后端。
削峰: 对于秒杀系统瞬时会有大量用户涌入,所以在抢购一开始会有很高的瞬间峰值。高峰值流量是压垮系统很重要的原因,所以如何把瞬间的高流量变成一段时间平稳的流量也是设计秒杀系统很重要的思路。实现削峰的常用的方法有利用缓存和消息中间件等技术。
p.s. 削峰主要是把并行请求变为串行请求
2、具体方案
1)削峰
异步处理:秒杀系统是一个高并发系统,采用异步处理模式可以极大地提高系统并发量,其实异步处理就是削峰的一种实现方式。
2)限流
网关限流
3)缓存
秒杀系统最大的瓶颈一般都是数据库读写,由于数据库读写属于磁盘IO,性能很低,如果能够把部分数据或业务逻辑转移到内存缓存,效率会有极大地提升。
前端
后台技术选型
第十一章、管理篇
目标(目标清晰)、计划( 凡事预则立,不预则废 )、职责(职责明确)、制度(赏罚分明)
注重团队中的薄弱环节
一只沿口不齐的木桶,它盛水的多少,不在于木桶上那块最长的木板,而在于木桶上最短的那块木板。要使木桶多盛水(提高水桶的整体效应),需要的不是去增加最长的那块木板长度,而是下工夫依次补齐木桶上最短的那些木板,这就是管理上有名的“木桶”法则。企业管理也是如此,要提高企业的效益,就必须狠抓薄弱环节,否则单位的整体工作就会受到影响。人们常说“取长补短”,即取长的目的是为了补短,只取长而不补短,就很难提高工作的整体效应。项目管理者联盟
提升自己的判断力,不盲目跟风
羊群效应是指人们经常会收到多数人的影响,而跟从大众的思想或行为,也被称为“从众效应”。羊群是一种很散乱的组织,平时在一起也是盲目地左冲右撞,一旦有一头羊动起来,其他的养也会不假思索地一哄而上,全然不顾前面可能有狼或者不远处有更好的草。因此,就是比喻人都有一种从众心理,从众心理很容易导致盲从,而盲从往往会使人陷入骗局或遭到失败。
规章制度面前人人平等
“ 热炉”法则不仅形象地阐述了规章制度的权威性,而且活灵活现地描述了惩处所需掌握的原则:(1)热炉火红,不用手去摸也知道炉子是热的,是会灼伤人的,这就是惩处的警告性原则。领导者要经常对下属进行规章制度教育,警告或劝戒不要触犯规章制度,否则会受到惩处。(2)每当碰到热炉,肯定会被火灼伤,这就是规章制度的权威性。也就是说只要触犯单位的规章制度,就一定会受到惩处。(3)当你碰到热炉时,立即就被灼伤,这就是惩处的即时性原则。惩处必须在错误行为发生后立即进行,决不拖泥带水,决不能有时间差,以达到及时改正错误行为的目的。(4)不管是谁碰到热炉,都会被灼伤,这就是规章制度的公平性原则。
增加管理的透明度
金鱼缸是玻璃做的,透明度很高,不论从哪个角度观察,里面的情况都一清二楚,这就是管理上的“金鱼缸”法则。“金鱼缸”法则运用到管理中,就是要求领导者必须增加规章制度和各项工作的透明度。各项规章制度和工作有了透明度,领导者的行为就会置于员工的监督之下,就会有效地防止领导者滥用权力,从而强化领导者的自我约束机制。同时,员工在履行监督义务的同时,自身的主人翁意识和责任感得到极大的提升,而敬业、爱岗和创新的精神也必将得到升华。
真诚温暖员工
也称“温暖”法则,源于法国作家拉封丹写过的一则寓言:北风和南风比威力,看谁能把行人身上的大衣脱掉。北风首先吹得人寒冷刺骨,结果行人为了抵御北风的侵袭,便把大衣裹得紧紧的。南风则徐徐吹动,顿时风和日丽,行人觉得温暖如春,随之开始解开纽扣,继而脱掉大衣,最终南风获得了胜利。这则寓言形象地说明一个道理:温暖胜于严寒、柔性胜于刚性。领导者在管理中运用“南风”法则,就是要尊重和关心员工,以员工为本,多点“人情味”,少点官架子,尽力解决员工日常生活中的实际困难,使员工真正感觉到领导者给予的温暖,从而激发他们工作的积极性。
保持适当的距离更有利于管理
“ 刺猬”法则讲的是:两只困倦的刺猬,由于寒冷而拥在一起。可因为各自身上都长着刺,刺得对方怎么也睡不舒服。于是它们离开了一段距离,但又冷得受不了,于是凑到一起。几经折腾,两只刺猬终于找到了一个合适的距离,既能互相获得对方的温暖又不致于被扎。“刺猬”法则就是管理和人际交往中的“心理距离效应”。心理学研究认为:领导者要搞好工作,就应该与员工保持亲密关系,这样做可以获得他们的尊重。与员工保持一定的心理距离,不仅可以避免员工之间的嫉妒和紧张,而且可以减少他们的恭维、奉承、行贿等行为,防止与员工称兄道弟、吃喝不分,并在工作中丧失原则。事实上,雾里看花,水中望月,给人的是“距离美”的感觉,管理上也是如此。一个原本很受员工敬佩的领导者,往往由于与员工“亲密无间”,就会使自己的缺点显露无遗,结果在不知不觉中丧失了严肃性,不利于对其更进一步的管理。另外,“刺猬”法则还启示我们,彼此间的亲密协作是必不可少的,员工之间、管理者与员工之间、管理者之间,尽管每个人都有其特点和个性,但各自为战在工作中却是不可取的,“独木难成林”、众人划桨开大船就是这个道理。线务局的工作千头万绪,各位局领导、中层干部、管理人员,各区域局、各部室都要各司其职、各负其责、立足本岗、发挥作用,同时也要注意分工不分家、补台不包办、到位不越位,切实形成合力、发挥团队作用。
时刻保持危机意识
关于“问题管理”有个著名的“青蛙原理”,说的是如果把一只青蛙扔进沸水中,青蛙肯定会马上跳出来。但是如果把一只青蛙放入冷水中逐渐加温,青蛙则会在不知不觉中丧失跳出去的能力,直至被热水烫死。这个原理是用来形容企业中存在的两种性质的问题,即显性问题和隐性问题。人们对显性问题的反应就如同青蛙对沸水的反应一样,会马上采取相应的措施,及时地将其扼杀在萌芽状态;而隐性问题由于自身的隐匿性,不易被发现,往往是等到发现时,已经对企业酿成了严重的损失。这就启示我们,很多线路障碍都是一些不起眼的小问题日积月累的结果,有客观的,但是也有主观的,跟我们的部分线务员在巡回或随工配合中的麻痹大意有关,听任一些小问题长期自由发展,最终酿成了影响线路通畅的大祸。“冰冻三尺,非一日之寒”,因此我们要时刻关注潜在的问题,而不是等小问题变大了、危机降临了再临时抱佛脚。
竞争是提高效率的法宝
“ 鲶鱼效应”来自一个古老的传说:一个小渔村的渔民靠到深海捕捉沙丁鱼(一种比较懒的鱼)为生。但由于捕鱼点距离陆地比较远,渔民捕的鱼运回渔村时,往往死掉大半,很难卖出好价钱。只有一个渔翁,他运回陆地的鱼,都是活的,总能卖出好价钱,但是他从来不让人看他的鱼舱。直到他死后,好奇的村民才发现,原来他的鱼舱里总是放着一条鲶鱼。由于鲶鱼是以捕食沙丁鱼为生,所以鲶鱼在鱼舱里会不停地追逐沙丁鱼,结果一些老弱的沙丁鱼被吃掉,但其他的沙丁鱼由于总在不停游动,所以都活着到岸。而其他渔船所捕的沙丁鱼静止不动,结果一大半都会死掉。这个传说告诉我们一个浅显的道理:“生于忧患、死于安乐”,如果一个企业缺少活力与竞争意识,没有生存的压力,就如同“沙丁鱼”一样,在“鱼舱”里混吃混喝,必然会被日益残酷的市场竞争所淘汰。一个员工也是如此,长期安于现状、不思进取,必然会成为时代的弃儿。
领导者要成为“鲶鱼”, 有句俗话叫“兵熊熊一个,将熊熊一窝”。一家公司或一个部门,如果领导缺乏激情,要想手下的人有激情,那是白日做梦。最常见的情况是,领导工作不在状态,员工必然上行下效,人浮于事,缺乏创新和主动性,日复一日,年复一年,必然成了一潭死水。反之则是“强将手下无弱兵”。如果领导者本身是一条充满活力的“鲶鱼”,那么,通过整顿纪律,规范制度,改造流程,合理配置岗位和人、财、物,就能将那些无能的“沙丁鱼”吃掉、赶走,使有能耐的“沙丁鱼”得到正面的激励,从而使整个机构呈现欣欣向荣的景象。因此,作为领导者,如果自己的公司没有激情,首先不要怪员工,而是要去反思自己是不是有激情。只有自己先成为“鲶鱼”,才能影响员工,才能使整个组织的活力都被调动起来,从而使集体的力量更加强大,克敌制胜。
这种管理方式属于最典型的柔性管理,目的很明确,就是要求企业的管理层要经常深入到基层和员工群众中去,体察民意、了解实情,与员工打成一片,从而增强领导层的亲和力和企业的凝聚力,激发员工的自豪感、自信心,起到上下一心、团结一致、共同进步的理想效果。“走动式”管理启示我们:一个整天忙忙碌碌、足不出户的领导决不是好领导,而事无巨细、事必躬亲的领导也不是好领导,只有削掉“椅子背儿”,从办公室中解放出来、深入基层与员工群众中去,才能取得事半功倍的效果。
及时矫正和补救正在发生的问题
一个房子如果窗户破了,没有人去修补,隔不久,其它的窗户也会莫名其妙的被人打破 ; 一面墙,如果出现一些涂鸦没有清洗掉,很快的,墙上就布满了乱七八糟,不堪入目的东西。一个很干净的地方,人会不好意思丢垃圾,但是一旦地上有垃圾出现之后,人就会毫不犹疑的抛,丝毫不觉羞愧 。这真是很奇怪的现象
“目标清晰(目标量化+计划),职责明确,赏罚分明(制度),超越伯乐。”
第十二章、前端篇
第十三章、测试篇
第十四章、其它
1、微服务:SpringCloud/Dubbo、Gateway、Eureka/Nacos/Consul、Config/Apollo、Hystrix/Sentinel
2、分布式:SLB、LVS、KeepAlived、LCN/Seata、ELK/ES等;
3、缓存:Java本地缓存、Memcached、Redis、MongoDB;
4、消息:RabbitMQ、RocketMQ、Kafka等
5、存储:Minio/Fastdfs、 MySQL(MybatisPlus)、Oracle、DB2、SQL Server
6、测试:Jmeter、Loadrunner、JUnit、Mockjs
7、监控:Arthas、XRebel、Jconsole、Jvisualvm、MAT
8、自动化:Jenkins、Nexus、GIT、Sonaqube、Maven、Gradle;
9、项目管理:TAPD(敏捷)、Redmine、禅道、Jira、Worktile(OKR)等;
10、设计:常用设计模式、UML、PowerDesinger
11、前端:JavaScript、Jquery、Ajax、Nodejs、Vue 等;
12、后台:SpringSecurity/Shiro、Oauth2、okHttp、Swagger/Knife4j
13、大数据:熟悉 Spark、Hive、Hadoop(hdfs、hbase);
14、容器:Docker/Podman;
15、其它:Python、Activiti、区块链技术、对称/非对称加密(RSA、SM2)
可用性指系统在给定时间内可以正常工作的概率,通常用SLA(服务等级协议,service level agreement)指标来表示。
这是这段时间的总体的可用性指标。
通俗叫法 | 可用性级别 | 年度宕机时间 | 周宕机时间 | 每天宕机时间 |
---|---|---|---|---|
1个9 | 90% | 36.5天 | 16.8小时 | 2.4小时 |
2个9 | 99% | 87.6小时 | 1.68小时 | 14分钟 |
3个9 | 99.9% | 8.76小时 | 10.1分钟 | 86秒 |
4个9 | 99.99% | 52.6分钟 | 1.01分钟 | 8.6秒 |
5个9 | 99.999% | 5.26分钟,315.36秒 | 6.05秒 | 0.86秒 |
可靠性相关的几个指标如下:
即平均无故障时间,是指从新的产品在规定的工作环境条件下开始工作到出现第一个故障的时间的平均值。
MTBF越长表示可靠性越高,正确工作能力越强 。
即平均修复时间,是指可修复产品的平均修复时间,就是从出现故障到修复中间的这段时间。
MTTR越短表示易恢复性越好。
即平均失效时间。系统平均能够正常运行多长时间,才发生一次故障。
系统的可靠性越高,平均无故障时间越长。