标签:随机数生成 ids 死锁 xtend 相等 远程机器 思想 辅助 elb
总结
这五种 I/O 模型中,前四种属于同步 I/O,因为其中真正的 I/O 操作(recvfrom)将阻塞进程/线程,只有异步 I/O 模型才与 POSIX 定义的异步 I/O 相匹配
传统阻塞 I/O 服务模型
Reactor 模式
Reactor是非阻塞同步网络模型,通过一个或多个输入同时传递给服务处理器的服务请求的事件驱动处理模式, 基本设计思想就是I/O 复用模型结合线程池,Reactor 模式也叫 Dispatcher 模式
Proactor 模型
Proactor 模型是异步网络模型 ,把 I/O 操作改为异步,即交给操作系统来完成就能进一步提升性能
######################## 文本操作 ########################
# 升序排列
ls -lrt
# 目录dos2unix转换格式
find . -type f -exec dos2unix {} \;
# 变量要保留其原来的换行符要加双引号,建议所有变量引用都用双引号加大括号圈上
echo "${var}"
# 变量长度
echo "${#var}"
# 接受所有参数
$@
# 查看数组中所有元素
${list[@]}
# 查看数组长度
${#list[@]}
# 去掉全路径的文件名,只保留目录
${path%/*}
# 定义数组
list=("1" "2" "3")
# 定义map
declare -A map=(["1"]="name" ["2"]="age")
# 删除空白行和注释行
cat <file> | grep -v ^# | grep .
cat <file> | grep -Ev '^$|^#'
# 去掉行尾巴空格
echo ${var} | sed 's/[ \t]*$//g'
# 去掉单引号
echo ${var} | sed $'s/\'//g'
# 例如查看状态是UNCONN,Recv-Q是0的端口信息
ss -ln | awk '($2=="UNCONN" && $3=="0") {print $0}'
# 统计状态是UNCONN,Recv-Q是0的端口的netid和出现的次数
ss -ln | awk '($2=="UNCONN" && $3=="0") {netids[$1]++}END{for(i in netids)print i "\t" netids[i]}'
# 大写转小写
echo ${var} | tr 'A-Z' 'a-z'
# 字符串转ASCLL码
echo "${var}" | tr -d "\n" | od -An -t dC
# 根据名字找
find <dir> -name <org>
# 根据用户找
find <dir> -user <org>
# 根据组找
find <dir> -group <org>
# 根据权限找
find <dir> -perm <org>
# 根据大小找
find <dir> -size <org>
# 根据更改找
find <dir> -mmin <org>
# 根据类型找
find <dir> -type <l|b|f>
######################## 系统命令 ########################
# 查看cpu
lscpu
# 查看pci
lspci
# 查看后台job
jobs
# 后台运行
( cmd ) &
# 唤醒
fg %<job_num>
# 暂停放入后台
ctrl z
# 唤醒stop的job
bg %<job_num>
# 发送信号,优先15SIGTERM,不行再9SIGKILL
kill -SIGTERM <PID>
# 杀用户所有进程
pkill -SIGTERM -u <user_name>
# 杀父进程
pkill -P <PID>
# 杀终端
pkill -SIGTERM -u <tty_name>
# 查看cpu信息
cat /proc/cpuinfo
# 查看服务单元
systemctl
systemctl --type service
systemctl list-units
# 判断状态
systemctl <is-active|is-enabled|is-failed|isolate|is-system-running> <unit_name>
# 看错误信息
systemctl --failed
systemctl status <unit_name> -l
# 看enable disable static的单元
systemctl list-unit-files
# 查看systemd日志
journalctl
# 指定级别
journalctl -p <err|debug|info|warning...>
# 持续打印
journalctl -f
# 指定单元
journalctl -u
# 当前时钟时区
timedatectl
# 设置
timedatectl <set-ntp|set-time|set-timezone|set-local-rtc>
# 查看hostname信息
hostnamectl status
# 本地域名解析位置
cat /etc/hosts
cat /etc/resolv.conf
# 检查网络设备
ip addr show <eno>
# 查看网络性能
ip -s link <eno>
# 跟踪请求路径
tracepath
tracepath6
# 查看网络连接
nmcli con show
# 查看网络设备信息
nmcli dev show <eno>
# 修改网络接口
nmcli con add
nmcli con mod
# 激活/取消连接
nmcli con up "<id>"
nmcli con down "<id>"
# 网络配置文件位置
ls /etc/sysconfig/network-scripts/
# 查看网卡提供商
ls -l /sys/class/net/ens1f0/device # 看输出的最后一列<id>
lspci | grep <id>
yum <repolist|list|search|install|remove|update>
# 查找rpm包
rpm -qa | grep <name>
# 查看rpm信息
rpm -qi <name>
# 解压rpm包
rpm2cpio <rpm> | cpio -id
# 用户信息
cat /etc/passwd
# 组信息
cat /etc/group
# 更改用户或组
chown -R <user> <dir>
chown -R :<group> <dir>
chown -R <user>:<group> <dir>
# 改权限
chmod -R 750 <file>
# 显示当前登录信息
w -f
# 公钥存放位置
cat ~/.ssh/known_hosts
# 私钥位置
ls /etc/ssh/ssh_host_*
# 创建私钥公钥对
ssh-keygen
# 将公钥复制到远程机器实现互信
ssh-copy-id <user>@<host>
# 检测文件挂载点
df -h
# 检测目录使用空间信息
du -h <dir>
# 文件系统挂在
mount <dir> <dir>
# 查看目录中所有打开的文件和正在运行的进程
lsof <dir>
# 取消挂载
umount <dir>
# 创建硬连接
ln <exist_path> <path>
# 创建软连接
ln -s <exist_path> <path>
######################## 其他软件 ########################
# 查看证书过期时间
openssl x509 -noout -enddate -in <crt_path>
# 获取端口证书过期时间
echo 'Q' | timeout 5 openssl s_client -connect <host:port> 2>/dev/null | openssl x509 -noout -enddate
# 自签根证书
openssl genrsa -aes256 -out <ca私钥位置> 2048
openssl req -new -key <ca私钥位置> -out <ca签发流程位置> -subj "/C=/ST=/L=/O=/OU=/CN=/emailAddress="
openssl x509 -req -sha256 -days <过期天数> -in <ca签发流程位置> -out <ca证书位置> -signkey <ca私钥位置> -CAcreateserial
# 根证书签发子证书
openssl genrsa -aes256 -out <私钥位置> 2048
openssl req -new -key <私钥位置> -out <签发流程位置> -subj "/C=/ST=/L=/O=/OU=/CN=/emailAddress="
openssl x509 -req -sha256 -days <过期天数> -in <签发流程位置> -out <证书位置> -signkey <私钥位置> -CAkey <ca私钥> -CA <ca证书位置> -CAcreateserial
openssl pkcs12 -export -clcerts -in <证书位置> -inkey <私钥位置> -out <p12证书位置> -name <别名>
# 查看keystore
${JAVA_HOME}/bin/keytool -v -list -storepass <password> -keystore <keystore_path>
# 导入trust keystore
${JAVA_HOME}/bin/keytool -import -trustcacerts -noprompt -alias <别名> -file <证书位置> -keystore <Keystore位置>
# 导入keystore
${JAVA_HOME}/bin/keytool -importkeystore -trustcacerts -noprompt -alias <别名> -deststoretype pkcs12 -srcstoretype pkcs12 -srckeystore <p12证书位置> -destkeystore <Keystore位置>
# 删除tag和name为none的坏掉的image
docker rmi $(docker images -f "dangling=true" -q)
# 删掉所有容器
docker stop $(docker ps -qa)
docker kill $(docker ps -qa)
docker rm $(docker ps -qa)
# 删除所有镜像
docker rmi --force $(docker images -q)
粘包拆包问题是处于网络比较底层的问题,在数据链路层、网络层以及传输层都有可能发生。我们日常的网络应用开发大都在传输层进行,由于UDP有消息保护边界,不会发生粘包拆包问题,因此粘包拆包问题只发生在TCP协议中
什么是粘包、拆包
例如客户端给服务端发了两个包,包1和包2,服务端收到了一个包(包1和包2首尾连在一起)就叫做粘包,如果服务端正常收到两个包,但是第一个包是包1的前半部分,第二个包是包1的后半部分加上包2,就叫做发生了粘包和拆包
为什么会发生粘包、拆包
粘包、拆包解决办法
实现比较丑陋,勿喷啊
冒泡排序:从前向后比较相邻的元素。如果前一个比后一个大,就交换他们两个,每一轮把一个最大的数运到数组最后面。
public static int[] sort(int[] arr) {
int len = arr.length;
// 冒泡总次数
for (int i = 1; i < len; i++) {
boolean flag = true;
// 每次冒泡过程
for (int j = 0; j < len - i; j++) {
if (arr[j] > arr[j + 1]) {
MyUtils.swap(arr, j, j + 1);
flag = false;
}
}
if (flag) {
// 如果一个冒泡过程没改变,退出返回已经有序
break;
}
}
return arr;
}
选择排序:每次从未排序数组中找一个最小的元素,放到以有序数组后面
public static int[] sort(int[] arr) {
int len = arr.length;
// 选择次数
for (int i = 0; i < len - 1; i++) {
int min = i;
// 每次选择过程
for (int j = i + 1; j < len; j++) {
if (arr[j] < arr[min]) {
min = j;
}
}
if (min != i) {
MyUtils.swap(arr, i, min);
}
}
return arr;
}
插入排序:每次把未排序的第一个数,插入到已排序数组的适当位置(如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面)
public static int[] sort(int[] arr) {
int len = arr.length;
// 插入次数,left为未有序的左边
for (int left = 1; left < len; left++) {
int temp = arr[left];
int right = left - 1;
// right为有序部分的右边
while (right >= 0 && temp < arr[right]) {
arr[right + 1] = arr[right];
right--;
}
// 判断是否需要插入
if (right != left - 1) {
arr[right + 1] = temp;
}
}
return arr;
}
归并排序:将数组分成很多小份,然后依次合并
public static int[] sort(int[] arr) {
sort(arr, 0, arr.length - 1);
return arr;
}
private static void sort(int[] arr, int left, int right) {
if (left == right) {
return;
}
// 等同于(right + left)/2
int mid = left + ((right - left) >> 1);
sort(arr, left, mid);
sort(arr, mid + 1, right);
// 已经分成了许多小份,开始合并
merge(arr, left, mid, right);
}
private static void merge(int[] arr, int left, int mid, int right) {
int[] help = new int[right - left + 1];
int i = 0;
int p1 = left;
int p2 = mid + 1;
// 左边右边通过辅助数组合并
while (p1 <= mid && p2 <= right) {
help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
}
// 左边没空加到后面
while (p1 <= mid) {
help[i++] = arr[p1++];
}
// 右边没空加到后面
while (p2 <= right) {
help[i++] = arr[p2++];
}
for (int j = 0; j < help.length; j++) {
arr[left + j] = help[j];
}
}
荷兰国旗问题:给定一个整数数组,给定一个值K,这个值在原数组中一定存在,要求把数组中小于K的元素放到数组的左边,大于K的元素放到数组的右边,等于K的元素放到数组的中间,最终返回一个整数数组,其中只有两个值,分别是等于K的数组部分的左右两个下标值
public static int[] sort(int[] arr) {
partiton(arr, 0, arr.length - 1);
return arr;
}
public static int[] partiton(int[] arr, int left, int right) {
int less = left - 1;
int more = right + 1;
int pNum = arr[right];
while (left < more) {
if (arr[left] < pNum) {
MyUtils.swap(arr, ++less, left++);
} else if (arr[left] > pNum) {
MyUtils.swap(arr, --more, left);
} else {
left++;
}
}
return new int[]{less, more};
}
快速排序:重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作,递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序
// 基于荷兰国旗问题的快排
public static int[] sort(int[] arr) {
sort(arr, 0, arr.length - 1);
return arr;
}
public static void sort(int[] arr, int left, int right) {
if (left < right) {
int[] pIndexs = DutchFlag.partiton(arr, left, right);
sort(arr, left, pIndexs[0]);
sort(arr, pIndexs[1], right);
}
}
堆排序:先建立大根堆,然后不停做heapify,也就是把未有序的最后一位和堆首互换,然后调整堆结构
public static int[] sort(int[] arr) {
int len = arr.length;
buildBigHeap(arr, len);
while (len > 0) {
MyUtils.swap(arr, 0, --len);
heapify(arr, 0, len);
}
return arr;
}
// 建立大根堆
public static void buildBigHeap(int[] arr, int len) {
for (int index = 0; index < arr.length; index++) {
while (arr[index] > arr[(index - 1) / 2]) {
MyUtils.swap(arr, index, (index - 1) / 2);
index = (index - 1) / 2;
}
}
}
// 调整堆
private static void heapify(int[] arr, int currRoot, int len) {
int left = currRoot * 2 + 1;
int right = currRoot * 2 + 2;
while (left < len) {
int largest = right < len && arr[left] < arr[right] ? right : left;
largest = arr[largest] > arr[currRoot] ? largest : currRoot;
if (largest == currRoot) {
break;
}
MyUtils.swap(arr, currRoot, largest);
currRoot = largest;
left = currRoot * 2 + 1;
right = currRoot * 2 + 2;
}
}
前序 中序 后续 层级遍历
public static void pre(TreeNode root) {
if (root != null) {
Stack<TreeNode> stack = new Stack<>();
stack.push(root);
// 先进右再进左
while (!stack.isEmpty()) {
root = stack.pop();
System.out.print(root.val + " -> ");
if (root.right != null) {
stack.push(root.right);
}
if (root.left != null) {
stack.push(root.left);
}
}
}
System.out.println();
}
public static void preReur(TreeNode root) {
if (root == null) {
return;
}
System.out.print(root.val + " -> ");
preReur(root.left);
preReur(root.right);
}
public static void mid(TreeNode root) {
Stack<TreeNode> stack = new Stack<>();
// 左走到头了开始弹,然后去右
while (root != null || !stack.isEmpty()) {
if (root != null) {
stack.push(root);
root = root.left;
} else {
root = stack.pop();
System.out.print(root.val + " -> ");
root = root.right;
}
}
System.out.println();
}
public static void midReur(TreeNode root) {
if (root == null) {
return;
}
midReur(root.left);
System.out.print(root.val + " -> ");
midReur(root.right);
}
public static void post(TreeNode root) {
// 把线序遍历反过来,得到前右左,然后再反过来变成左右前
if (root != null) {
Stack<TreeNode> stackStack = new Stack<>();
Stack<TreeNode> stack = new Stack<>();
stack.push(root);
while (!stack.isEmpty()) {
root = stack.pop();
stackStack.push(root);
if (root.left != null) {
stack.push(root.left);
}
if (root.right != null) {
stack.push(root.right);
}
}
while (!stackStack.isEmpty()) {
System.out.print(stackStack.pop().val + " -> ");
}
}
System.out.println();
}
public static void postReur(TreeNode root) {
if (root == null) {
return;
}
postReur(root.left);
postReur(root.right);
System.out.print(root.val + " -> ");
}
public static void level(TreeNode root) {
if (root == null) {
return;
}
LinkedList<TreeNode> queue = new LinkedList<>();
queue.add(root);
TreeNode curr = null;
while (!queue.isEmpty()) {
curr = queue.pop();
System.out.print(curr.val + " -> ");
if (curr.left != null) {
queue.add(curr.left);
}
if (curr.right != null) {
queue.add(curr.right);
}
}
}
特点
解决问题:判断一个元素是否在一个集合中,优势是只需要占用很小的内存空间以及有着高效的查询效率
原理:保存了很长的二进制向量,同时结合 Hash 函数实现
特点
计算误差
先根据样本大小n,可以接受的误差p,计算需要申请多大内存m
再由m,n得到hash function的个数k
再计算实际的误差p
计算桶中位置
求出 对象的hashcode之后模一个数组长度就可以映射到具体的桶中
index = hashCode % Length
取余操作的性能是不如与运算的,当长度为2的幂次方时,就可以等价成下面的与运算
index = hashCode & (Length - 1)
jdk1.8继续做了优化
index = hashCode ^ (hashCode >>> 16) & (Length - 1)
扩容方面
ArrayBlockingQueue
数据结构
线程安全性
实现
可重入锁,Condition
final ReentrantLock lock;
private final Condition notEmpty;
private final Condition notFull;
插入或读取操作都需要拿锁
特点
参数
LinkedBlockingQueue
数据结构
线程安全性
可重入锁,Condition
// take, poll, peek 等读操作的方法需要获取到这个锁
private final ReentrantLock takeLock = new ReentrantLock();
// 如果读操作的时候队列是空的,那么等待 notEmpty 条件
private final Condition notEmpty = takeLock.newCondition();
// put, offer 等写操作的方法需要获取到这个锁
private final ReentrantLock putLock = new ReentrantLock();
// 如果写操作的时候队列是满的,那么等待 notFull 条件
private final Condition notFull = putLock.newCondition();
PriorityBlockingQueue
SynchronousQueue
生产者用extends,消费者用super
public class NewStack<T>{
public void pushAll(Iterable <? extends T> src) {
for (T t : src) {
push(t);
}
}
public void popAll(Collection <? super T> dst){
while (!isEmpty()){
dst.add(pop());
}
}
}
特征
实现
过程
结构图
直接通过new ThreadPoolExecutor()创建(推荐,可以定制化,控制细节)
通过Executors工具类创建内置常用的线程池方案
newFixedThreadPool 用于负载比较重的服务器,为了资源的合理利用,需要限制当前线程数量
源码:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>();
}
核心线程数和最大线程数一样,稳定高负荷工作,因此没用超出核心线程数回收的情况keepAliveTime 设置为0,等待队列用LinkedBlockingQueue无界队列
newSingleThreadExecutor 用于串行执行任务的场景,每个任务必须按顺序执行,不需要并发执行
源码:
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
解释:
类似于newFixedThreadPool,只是顾名思义池中只有一个线程干活,相当于串行
newCachedThreadPool 用于并发执行大量短期的小任务,或者是负载较轻的服务器
源码:
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
解释:
没用核心线程,最大线程数无限,线程60秒没任务干就停止 ,等待队列用直接握手队列SynchronousQueue,任务直接交给线程执行不会保存
newScheduledThreadPool 用于需要多个后台线程执行周期任务,同时需要限制线程数量的场景
源码:
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE,
DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
new DelayedWorkQueue());
}
private static final long DEFAULT_KEEPALIVE_MILLIS = 10L;
解释:
ScheduledThreadPoolExecutor继承自ThreadPoolExecutor,使用优先级队列DelayedWorkQueue,运行时间短的任务先执行,否则先等待的先执行
newSingleThreadScheduledExecutor 用于需要后台单线程执行周期任务
解释:
单线程执行版的newScheduledThreadPool ,保证任务串行执行,保证串行返回
作用:
使用方式
实现方式
JVM中锁升级流程
线程的通信是指线程之间以何种机制来交换信息,在编程中,线程之间的通信机制有两种,共享内存和消息传递
当调用wait方法后,线程会被放到对象内部的等待池中,在等待池中的线程不会去竞争CPU,只有调用Notify或者NotifyAll才会从等待池中,放入锁池中,等待对象锁的释放从而竞争CPU以执行。
# 查运行中的java应用
root@64b47b31317a:/# jps -ml
1 /app.jar --spring.profiles.active=pro
116 sun.tools.jps.Jps -ml
# 根据第一列的PID找出 load比较高的应用PID
top -p <pids>
root@64b47b31317a:/# top -p 1
# dump线程堆栈信息
jstack <pid>
root@64b47b31317a:/# jstack 1
"lettuce-eventExecutorLoop-1-3" #33 daemon prio=5 os_prio=0 tid=0x00007fe394367000 nid=0x24 waiting on condition [0x00007fe376bd3000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
省略若干
# 查看java.lang.Thread.State状态
程序到达安全点,安全点是对象引用关系不会变化的点,例如方法调用,循环跳转,异常跳转等
没有被任何其他对象引用的对象被视为垃圾。
问1:JavaGC中如何判断对象是否被引用
答1:GC中主要有两种引用判断方法
问2:可达性分析中,哪些对象可作为GC root
答2:
作用
String
对象会直接存储在常量池中。String
对象,可以使用String
提供的intern
方法同步到常量池中。intern 方法会从字符串常量池中查询当前字符串是否存在,若不存在就会将当前字符串放入常量池StringTable中,StringTable默认大小1009,可以通过参数修改 -XX:StringTableSize=123456...区别
案例
String s = new String("1");
s.intern();
String s2 = "1";
System.out.println(s == s2);
String s3 = new String("1") + new String("1");
s3.intern();
String s4 = "11";
System.out.println(s3 == s4);
// 打印结果是
// jdk6 下false false
// jdk7 下false true
保证单线程执行的内存可见性,多线程不能保证
在执行程序时,为了提高性能,编译器和处理器常常会对指令做重排序
对象创建之后状态不能修改
对象的所有的域都是final类型
对象是正确创建的(对象创建过程中,this引用没用逃逸)
在静态初始化函数中初始化一个对象引用
将对象的引用保存到正确的构造对象的final类型域中
将对象的引用保存到一个由锁保护的域中
将对象的引用用volatile关键字修饰,或者保存到AtomicReference对象中
对象发布主要有三步 1.分配内存空间 2初始化对象 3引用指向分配的内存
由于指令重排的存在,可能出现132的顺序,多线程环境下,可能出现 instance != null 但是初始化工作还没完成的情况在占有锁的线程没有完成初始化时,另一个线程认为以及初始化完毕了去使用对象的时候便会有问题
加上 volatile 关键字就可以解决指令重排的问题
类似于栈,内存的管理权不属于JVM而属于栈本身,所有被pop掉的index上还存在着过期的引用Stack.pop()的源码中手动清除了过期引用
elementData[elementCount] = null; /* to let gc do its work
将对象引用放入了缓存,可以用WeakHashMap作为引用外键
监听器和其他回调,可以用WeakHashMap作为引用外键
BeanFactory和ApplicationContext是Spring的两大核心接口,都可以当做Spring的容器,ApplicationContext是BeanFactory的子接口
通知(Advice)
通知定义了在切入点代码执行时间点附近需要做的工作。
连接点(Joinpoint)
程序能够应用通知的一个“时机”,这些“时机”就是连接点,例如方法调用时、异常抛出时、方法返回后等等
切入点(Pointcut)
通知定义了切面要发生的“故事”,连接点定义了“故事”发生的时机,那么切入点就定义了“故事”发生的地点,例如某个类或方法的名称,Spring中允许我们方便的用正则表达式来指定。
切面(Aspect)
通知、连接点、切入点共同组成了切面:时间、地点和要发生的“故事”
引入(Introduction)
引入允许我们向现有的类添加新的方法和属性(Spring提供了一个方法注入的功能)
目标(Target)
即被通知的对象,如果没有AOP,那么通知的逻辑就要写在目标对象中,有了AOP之后它可以只关注自己要做的事,解耦合
代理(proxy)
应用通知的对象,详细内容参见设计模式里面的动态代理模式
织入(Weaving)
把切面应用到目标对象来创建新的代理对象的过程,aop织入的三种方式:
编译时:当一个类文件被编译时进行织入,这需要特殊的编译器才可以做的到,例如AspectJ的织入编译器;
类加载时:使用特殊的ClassLoader在目标类被加载到程序之前增强类的字节代码;
运行时:切面在运行的某个时刻被织入,SpringAOP就是以这种方式织入切面的,Spring默认使用了JDK的动态代理技术,如果没有实现接口则转为使用Cglib
Spring事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring是无法提供事务功能的。真正的数据库层的事务提交和回滚是通过binlog或者redo log实现的
MySQL 主要分为 Server 层和存储引擎层
是否支持行级锁 : MyISAM 只有表级锁(table-level locking),而InnoDB 支持行级锁(row-level locking)和表级锁,默认为行级锁。
是否支持事务和崩溃后的安全恢复: MyISAM 强调的是性能,每次查询具有原子性,其执行速度比InnoDB类型更快,但是不提供事务支持。但是InnoDB 提供事务支持事务,外部键等高级数据库功能。 具有事务(commit)、回滚(rollback)和崩溃修复能力(crash recovery capabilities)的事务安全(transaction-safe (ACID compliant))型表。
是否支持外键: MyISAM不支持,而InnoDB支持。
是否支持MVCC :仅 InnoDB 支持。应对高并发事务, MVCC比单纯的加锁更高效;MVCC只在 READ COMMITTED 和 REPEATABLE READ 两个隔离级别下工作;MVCC可以使用 乐观(optimistic)锁 和 悲观(pessimistic)锁来实现;各数据库中MVCC实现并不统一
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
未提交读(Read uncommitted) | 可能 | 可能 | 可能 |
已提交读(Read committed) | 不可能 | 可能 | 可能 |
可重复读(Repeatable read) | 不可能 | 不可能 | 可能 |
可串行化(Serializable) | 不可能 | 不可能 | 不可能 |
MyISAM适用频繁执行全表count,查询频率高,增删改频率不高
InnoDB增删改查都频繁,对可靠性要求高,要求支持事务
SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE
。SELECT * FROM table_name WHERE ... FOR UPDATE
undo log
)实现的,所有事务进行的修改都会先记录到这个回滚日志中,然后在对数据库中的对应行进行写入binlog
,Master 把它的二进制日志传递给 slaves
并回放来达到 master-slave
数据一致的目的不使用索引如和查询
而我们使用索引时Innodb默认使用B+树实现如下效果,大大提高了效率
(>、<、between、like
左匹配)等就不能进一步匹配了,后面的条件退化为线性查找查询限定数据范围
读写分离
垂直拆分
水平拆分
BETWEEN
不用IN
:SELECT id FROM t WHERE num BETWEEN 1 AND 5
# 查配置
show variables like '%';
# 放开用户的远程操作权限
GRANT ALL PRIVILEGES ON *.* TO '<user>'@'%' IDENTIFIED BY '<password>' WITH GRANT OPTION;
# 刷新权限规则生效
flush privileges;
# 在线改配置
set <global|session>
# 查看隔离级别
select @@tx_isolation;
# 设置隔离级别
set session transaction isolation level read UNCOMMITTED;
# 开启事务
start transaction;
# 事务回滚
rollback;
# 事务提交
commit;
# 关闭事务自动提交
set autocommit = 0
# 加共享锁
<command> lock in share mode
# 索引创建
ALTER TABLE table_name ADD INDEX index_name (column_list)
ALTER TABLE table_name ADD UNIQUE (column_list)
ALTER TABLE table_name ADD PRIMARY KEY (column_list)
CREATE INDEX index_name ON table_name (column_list)
CREATE UNIQUE INDEX index_name ON table_name (column_list)
# 删除索引
DROP INDEX index_name ON talbe_name
ALTER TABLE table_name DROP INDEX index_name
ALTER TABLE table_name DROP PRIMARY KEY
# 查看索引
show index from table_name;
show keys from table_name;
单一责任原则
就一个类而言,应该只有一个引起它变化的原因。
开放封闭原则
软件实体应该是可以扩展的,但是不可修改。
依赖倒转原则
1.高层模块不应该依赖底层模块,两个都应该抽象依赖。
2.抽象不应该依赖细节。细节应该依赖抽象。
迪米特法则
如果两个类不必彼此直接通信,那么这两个类就不应该发生直接的相互作用。如果其中一个类需要调用另一个类的摸一个方法的话,应该同通过第三方转发这个调用
组合聚合复用原则
尽量使用组合聚合,尽量不要使用类继承。
简单工厂模式
定义超类,子类通过继承重写方法实现多态,创建一个Factory工厂类,调用统一的超类接口,实例化相应的子类对象,实现解耦。
策略模式
封装实现同一功能的不同算法,通过Context上下文方式加载不同策略,可以和简单工厂模式结合使用。
装饰模式
动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更加灵活。
代理模式
为其他对象提供一种代理以控制对这个对象的访问。
工厂方法模式
定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到了其子类。
原型模式
用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
模板方法模式
定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定的步骤。
外观模式(门面模式)
为了系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得这以子系统更加容易使用。
建造者模式
将一个复杂对象的构建与他的表示分离,使得同样的构建过程可以创建不同的表示。
观察者模式(发布订阅)
一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己。
抽象工厂方法
提供一个创建一系列相关或相互依赖对象的接口,而无需具体指定他们的实现。
状态模式
当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类。(状态模式主要解决的是控制一个对象状态转换的条件表达式过于复杂时的情况)
适配器模式
将一个类的接口转换成客户希望的另一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
备忘录模式
在不破坏封装性的前提下,捕获一个对象的内部状态,并在对象之外保存这个状态。这样以后就可将对象恢复到原先保存的状态。
组合模式
将对象组合成树形结构以表示 “部分-整体” 的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。
迭代器模式
提供一种方法顺序访问一个聚合对象中各个元素,而又不暴露该对象的内部表示。
单例模式
保证一个类仅有一个实例,并提供一个访问它的全局访问点。
桥接模式
将抽象部分与它的实现部分分离,使它们都可以独立地变化。
命令模式
将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作。
责任链模式
使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这个对象连成链条,并沿着这条链传递该请求,直到有一个对象处理它为止。
中介者模式
用 一个中介对象封装一系列对象交互。中介者使得各对象不需要显示地互相引用,从而使得其耦合松散,而且可以独立地改变它们之间的交互。
享元模式
运用共享技术有效地支持大量细粒度的对象。
解释器模式
给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。
访问者模式
表示一个作用于某种对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
类:当存在多对一的依赖关系的时候,我们会用到观察者模式,其中Subject用于注册,移除,发生改变时通知Observer,Observer收到通知之后进行update操作。Observer储存在Vector容器中
接口:定义了update(Observable o, Object arg)方法,当调用Observable的notifyObservers时,会触发update。观察者需要实现这个接口,重写update方法实现特定功能
普通的线程安全单例
public class Singleton{
private static final Singleton INSTANCE = new Singleton();
private Singleton(){}
public static Singleton getSingleton(){
return INSTANCE;
}
}
双检查线程安全单例
public class SafePublish {
private volatile static SafePublish instance = null;
public static SafePublish getInstance() {
if (instance == null) {
synchronized (SafePublish.class) {
if (instance == null) {
instance = new SafePublish();
}
}
}
return instance;
}
private SafePublish() {}
}
枚举线程安全单利
public enum Singleton {
INSTANCE;
}
主要用于解决分布式系统中负载均衡的问题。
一般情况:
问题:服务器数量不多,容易出现数据倾斜问题(服务器分布不均匀,缓存数据集中在部分服务器上)
解决方案:可以增加虚拟节点,例如在主机ip后加编号取模映射到环的不同位置。然后数据遇到虚拟节点之后再映射回真实节点。
单节点分布式锁
Redlock
安全特性:互斥访问,即永远只有一个 client 能拿到锁
免死锁:最终 client 都可能拿到锁,不会出现死锁的情况,即使原本锁住某资源的 client crash 了或者出现了网络分区
容错性:只要大部分 Redis 节点存活就可以正常提供服务
实现
原理
假如起了 5 个 master 节点,分布在不同的机房尽量保证可用性。为了获得锁,client 会进行如下操作:
失败重试
如果一个 client 申请锁失败了,那么它需要稍等一会在重试避免多个 client 同时申请锁的情况,最好的情况是一个 client 需要几乎同时向 5 个 master 发起锁申请。另外就是如果 client 申请锁失败了它需要尽快在它曾经申请到锁的 master 上执行 unlock 操作,便于其他 client 获得这把锁,避免这些锁过期造成的时间浪费,当然如果这时候网络分区使得 client 无法联系上这些 master,那么这种浪费就是不得不付出的代价了。
放锁
放锁操作很简单,就是依次释放所有节点上的锁就行了
性能、崩溃恢复和 fsync
如果我们的节点没有持久化机制,client 从 5 个 master 中的 3 个处获得了锁,然后其中一个重启了,这是注意 整个环境中又出现了 3 个 master 可供另一个 client 申请同一把锁! 违反了互斥性。如果我们开启了 AOF 持久化那么情况会稍微好转一些,因为 Redis 的过期机制是语义层面实现的,所以在 server 挂了的时候时间依旧在流逝,重启之后锁状态不会受到污染。但是考虑断电之后呢,AOF部分命令没来得及刷回磁盘直接丢失了,除非我们配置刷回策略为 fsnyc = always,但这会损伤性能。解决这个问题的方法是,当一个节点重启之后,我们规定在 max TTL 期间它是不可用的,这样它就不会干扰原本已经申请到的锁,等到它 crash 前的那部分锁都过期了,环境不存在历史锁了,那么再把这个节点加进来正常工作。
哈希槽 (hash slot)
的方式来分配的。redis cluster 默认分配了 16384 个slot,当我们set一个key 时,会用CRC16
算法来取模得到所属的slot
,然后将这个key 分到哈希槽区间的节点上,具体算法就是:CRC16(key) % 16384
方案 | 描述 | 优点 | 缺点 |
---|---|---|---|
rdb | 在配置文件中定义了rdb备份的触发条件,条件达到就开始备份redis内存快照。 | 恢复数据很快,磁盘io少 | 没有达到触发条件期间发生故障,未备份数据丢失 |
aof | 将每次操作都记录到一个日志中,通过日志恢复数据。 | 丢失数据风险小 | 还原数据速度慢,磁盘io频繁。 |
问: 在dump rdb过程中,aof如果停止同步,会不会丢失?
答: 不会,所有的操作缓存在内存的队列里, dump完成后,统一操作.
问: aof重写是指什么?
答: aof重写是指把内存中的数据,逆化成命令,写入到.aof日志里.
以解决 aof日志过大的问题.
问: 如果rdb文件,和aof文件都存在,优先用谁来恢复数据?
答: aof
问: 2种是否可以同时用?
答: 可以,而且推荐这么做
问: 恢复时rdb和aof哪个恢复的快
答: rdb快,因为其是数据的内存映射,直接载入到内存,而aof是命令,需要逐条执行
标签:随机数生成 ids 死锁 xtend 相等 远程机器 思想 辅助 elb
原文地址:https://www.cnblogs.com/freshchen/p/11740978.html