Java架构专家

时间:2024-10-27 06:59:36

单体服务

大型网站架构演进

01
02
03
04
05
06
读写分离注意数据同步
07
分库分表(数据接近千万级,使用分布式主键),注意数据同步
08
09
10
11

所需要具备的技术栈与能力

微服务 + 搜索 + 日志处理 + 分库分表 + 优化

把控团队
系统分解与模块拆分
指导与培训
沟通与协调能力
抽象,举例,画图
软技能(项目管理/谈判)

单体架构设计与项目开发


功能开发
个人中心
上线部署

前后端分离
分层架构

技术选型
后端
springboot
前端
jquery/vue

考虑因素
切合业务
社区活跃度
团队技术水平
版本更新迭代周期
试错精神
安全性
成功案例
开源精神

前后端分离开发模式

早期传统 web 开发
12

前后端单页面交互, mvvm 开发模式
13

项目分层设计原则
项目拆分与聚合
maven 聚合项目

父子模块相互依赖后需要进行安装(install)

数据库建模工具

数据库外键
性能影响
热更新
降低耦合度
数据库分库分表

数据库配置
hikariCP
mybatis

mybatis 逆向生成工具

restflu web service
通信方式
信息传递
无状态
独立性
get -> /getOrder?id=1001
post -> /saveOrder
put -> /modifyOrder
delete -> deleteOrder?id=1001

可修改:
Editor -> code style -> inspections -> incorrect injection point autowiring in Spring bean compoments
Editor -> code style -> inspections -> Non recommended ‘field’ injections

service 注意事务

- 事务传播 - Propagation
- REQUIRED: 使用当前的事务,如果当前没有事务,则自己新建一个事务,子方法是必须运行在一个事务中的;
- 如果当前存在事务,则加入这个事务,成为一个整体。
- 举例:领导没饭吃,我有钱,我会自己买了自己吃;领导有的吃,会分给你一起吃。
- SUPPORTS: 如果当前有事务,则使用事务;如果当前没有事务,则不使用事务。
- 举例:领导没饭吃,我也没饭吃;领导有饭吃,我也有饭吃。
- MANDATORY: 该传播属性强制必须存在一个事务,如果不存在,则抛出异常
- 举例:领导必须管饭,不管饭没饭吃,我就不乐意了,就不干了(抛出异常)
- REQUIRES_NEW: 如果当前有事务,则挂起该事务,并且自己创建一个新的事务给自己使用;
- 如果当前没有事务,则同 REQUIRED
- 举例:领导有饭吃,我偏不要,我自己买了自己吃
- NOT_SUPPORTED: 如果当前有事务,则把事务挂起,自己不使用事务去运行数据库操作
- 举例:领导有饭吃,分一点给你,我太忙了,放一边,我不吃
- NEVER: 如果当前有事务存在,则抛出异常
- 举例:领导有饭给你吃,我不想吃,我热爱工作,我抛出异常
- NESTED: 如果当前有事务,则开启子事务(嵌套事务),嵌套事务是独立提交或者回滚;
- 如果当前没有事务,则同 REQUIRED。
- 但是如果主事务提交,则会携带子事务一起提交。
- 如果主事务回滚,则子事务会一起回滚。相反,子事务异常,则父事务可以回滚或不回滚。
- 举例:领导决策不对,老板怪罪,领导带着小弟一同受罪。小弟出了差错,领导可以推卸责任。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

springboot 自动装配可以不在启动类加 @EnableTransactionManagement

电商项目核心功能

用户注册与登录
整合 swagger2
controller
Bo

cros

账号密码
邮箱验证
手机验证

cookie 与 session
cookie
以键值对的形式存储信息在浏览器
cookie 不能跨域,当前及父级域名可以取值
cookie 可以设置有效期
cookie 可以设置 path
session
基于服务器内存的缓存(非持久化), 可保存请求会话
每个 session 通过 sessionid 来区分不同请求
session 可设置过期时间
session 也是以键值对形式存在的

设置完 session,请求回返回 sessionid 给前端

整合 log4j
排除 springboot 自带的日志
手动加依赖

aop 切面拦截统计 service 调用时间

打印 sql 语句

简历
自我介绍+工作经历+项目经历+学历背景
自我介绍中要增加核心优势,放 github/博客链接
工作经历中重点的是项目内容,要参考技术挑战和项目对业务的影响程度

首页商品推荐

商品详情与评论渲染

分页插件
PageHelper

数据脱敏工具

商品的搜索与分页

分类设计与实现

购物车与订单
刷新购物车的相关数据
收货地址功能
订单实现流程
聚合支付中心(企业)

微信与支付宝支付

微信支付
用户 -> 微信客户端 -> 商户后台系统 -> 微信支付系统
调统一下单 api,返回预支付 code_url(2 个小时有效)
将链接生成二维码
异步通知商户结果(提供接口),告知支付通知接收情况(反馈)

前端 headers 携带信息

netapp

前端 js 轮询查询支付结果

支付宝支付

定时任务扫描关闭过期订单

映射本地静态资源,实现 webmvcconfigurer 的 addResourceHandlers 方法

文件上传大小限制

spring:
  servlet:
    multipart:
      max-file-size: 512000     # 文件上传大小限制为500kb
      max-request-size: 512000  # 请求大小限制为500kb
  • 1
  • 2
  • 3
  • 4
  • 5

手动更新订单状态
http://localhost:8088/myorders/deliver?orderId=190827F2R9A6ZT2W

用户中心
订单管理
评价管理

云服务器部署上线

云服务器购买
安装 jdk
安装 tomcat
安装 mariaDB
打包 springboot

部署 tomcat 发布前端

内网互通(云服务器同地域)
4 核 8g/16g
8 核 32g
16 核 64g

java 环境检查安装
查询
rpm -qa | grep java -i
删除
rpm -e --nodeps xxx

下载解压 java

添加环境变量
vi /etc/profile

export JAVA_HOME=/usr/java/jdk1.9.0_191
export CLASSPATH=.:%JAVA_HOME%/lib/:%JAVA_HOME%/lib/
export PATH= P A T H : PATH: PATH:JAVA_HOME/bin

source /etc/profile

域名/二级域名

下载安装 mariadb (官方文档)

grant all privileges on *.* to 'root'@'%' identified by 'xxx';
flush privileges;
  • 1
  • 2

修改项目配置
单体项目打包发布

cookie 异常问题

vi

<CookieProcessor
  className=""
/>
  • 1
  • 2
  • 3

单体架构总结
1,mvc 框架(springboot 多模块)
2,文件上传处理
3,事务管理
4,配置梳理
5,war 包发布

nginx

安装

download

安装依赖环境

1,安装 gcc 环境
yum install gcc-c++
2,安装 PCRE 库,用于解析正则表达式
yum install -y pcre pcre-devel
3,zlib 压缩和解压缩依赖
yum install -y zlib zlib-devel
4,ssl 安全的加密套接字协议层,用于 http 安全传输,也就是 https
yum install -y openssl openssl-devel

tar -zxvf nginx-1.16.
创建临时目录
mkdir /var/temp/ngin -p
进入根目录,创建 makefile 文件

./configure \
--prefix=/usr/local/nginx \
--pid-path=/var/run/nginx/ \
--lock-path=/var/lock/ \
--error-log-path=/var/log/nginx/ \
--http-log-path=/var/log/nginx/ \
--with-http_gzip_static_module \
--http-client-body-temp-path=/var/temp/nginx/client \
--http-proxy-temp-path=/var/temp/nginx/proxy \
--http-fastcgi-temp-path=/var/temp/nginx/fastcgi \
--http-uwsgi-temp-path=/var/temp/nginx/uwsgi \
--http-scgi-temp-path=/var/temp/nginx/scgi
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

进入根目录
make
make install

进入/usr/local/nginx/sbin,启动
./nginx

停止
./nginx -s stop
重新加载
./nginx -s reload

显示默认首页过程解析

配置文件讲解

nginx 的进程模型

master 进程: 主进程
worker 进程: 工作进程

可以修改 worker_processes 数量添加 worker 进程
可以设置为内核数-1

进入/usr/local/nginx/sbin,检查 nginx 配置
./nginx -t

nginx 处理 web 请求机制

异步非阻塞处理请求

events {
  # 默认使用epoll
  use epoll;
  # 每个worker允许连接的客户端最大连接数
  worker_connections 10240;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

nginx 配置结构与指令语法

main 全局配置
event 配置工作模式以及连接数
http http模块相关配置
  server 虚拟主机配置,可以有多个
    localtion 路由规则,表达式
    upstream 集群,内网服务器
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

nginx 核心配置文件

可以设置 worker 的用户权限
user root;

日志级别
debug/info/notice/warn/error/crit

http 模块
  include 包含模块
  defaule_type application/octet-stream 默认的类型
  日志格式和路径
  #log_format main '$remote_addr - ';
  #access_log logs/ main
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

sendfile 默认打开,发送文件传输
#tcp_nopush 数据包累积到一定大小之后再发送

keepalive_timeout 65; 存活超时时间

gzip 压缩后传输,消耗性能

打开失败以及失败的解决方案

./nginx -h

问题一: “/var/run/nginx/” failed (2:No such file or directory)
mkdir /var/run/nginx
问题二: invalid PID number “” in “/var/run/nginx/”
./nginx -c /usr/local/nginx/conf/

nginx 常用的命令

./nginx -s stop 暴力停止
./nginx -s quit 优雅停机
./nginx -V 查看版本和配置路径信息

nginx 日志切割

通常以天作为单位切割
在/usr/local/nginx/sbin 下创建 cut_my_log.sh

下面以分为单位

#!/bin/bash
LOG_PATH="/var/log/nginx"
RECORD_TIME=$(date -d "yesterday" +%Y-%m-%d+%H:%M)
PID=/var/run/nginx/
mv ${LOG_PATH}/ ${LOG_PATG}/access.${RECORD_TIME}.log
mv ${LOG_PATH}/ ${LOG_PATH}/error.${RECORD_TIME}.log
#向nginx主进程发送信号,用于重新打开日志文件
kill -USR1 `cat $PID`
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

赋予权限
chmod +x cut_my_log.sh

定时切割
yum install crontabs
crontab -l
crontab -e

每一分钟
分/时/日/月/星期/年(可选)

*/1 * * * * /usr/local/nginx/sbin/cut_my_log.sh
  • 1

每天晚上 23:59

59 23 * * *
  • 1

每日凌晨 1 点

0 1 * * *
  • 1

重启定时任务
service crond restart
其他
service crond start
service crond stop
service crond reload

使用 nginx 为静态资源提供服务

前端打包后,ngxin 指定 index 页面入口

server {
  listen 90;
  server_name lcoalhost;
  location / {
    root /home/foodie-shop;
    index ;
  }

  location /xxx {
    root /home;
  }

  location /static {
    alias /home/xxx;
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

使用 gzip 压缩来提升请求效率

开启 gzip 压缩功能,提高传输效率,节约带宽
gzip on;

限制最小压缩,小于 1 字节文件不会压缩
gzip_min_length 1;

定义压缩的级别(压缩比,文件越大,压缩越多,但是 cpu 使用会越多)
gzip_comp_level 3;

定义压缩文件的类型
gzip_types xxx;
类型有下面这些
text/plain application/javascript application/x-javascript text/css
application/xml text/javascript application/x-httpd-php image/jpeg
image/gif image/png application/json

location 的匹配规则解析

精确匹配

location = / {
}
  • 1
  • 2

正则表达式

location ~* \.(GIF|png|bmp|jpg|jpeg) {
  root /home;
}
  • 1
  • 2
  • 3

以某个字符路径开头请求

location ^~ /xxx/img {
  root /home;
}
  • 1
  • 2
  • 3

dns 域名解析

充当反向代理服务器

使用 SwitchHosts 模拟本地域名解析访问

127.0.0.1 localhot # 虚拟主机名
::1 localhost # 虚拟主机名

nginx 的跨域

浏览器拒绝跨站点访问

jsonp/springboot cors/nginx

server {
  #允许跨域请求的域, *代表所有
  add_header 'Access-Control-Allow-Orgin' *;
  #允许带上cookie请求
  add_header 'Access-Control-Allow-Credentials' 'true';
  #允许请求的方法,比如GET/POST/PUT/DELETE
  add_header 'Access-Control-Allow-Methods' *;
  #允许请求的header
  add_header 'Access-Control-Allow-Headers' *;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

Nginx 中配置静态资源防盗链

server {
  #对源站点验证
  valid_referer *.;
  #非法引入会进入下面的判断
  if($invalid_referer) {
    return 404;
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

nginx 的模块化体系


  http
    event module
    phase handler
    output filter
    upstream
    load balancer
    extent module
  mail
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

nginx 负载均衡

四层负载均衡
ip+端口 进行转发请求
F5 硬件负载均衡
LVS
haproxy
nginx

七层负载均衡
基于 http 的 url 或者 ip 转发请求
nginx
haproxy
apache

dns 地域负载均衡

nginx 集群搭建

nginx
tomcat1
tomcat2
tomcat3

upstram tomcats {
    server 192.168.1.173:8080;
    server 192.168.1.174:8080;
    server 192.168.1.175:8080;
}

server {
    listen 80;
    server_name ;
    location / {
        proxy_pass http://tomcats;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

使用 jmater 测试单节点与集群的并发异常

下载地址
/

windows 双击
mac 双击 jmeter
可以修改语言 选项 -> 选择语言

1,添加线程组 -> 线程组添加取样器(http 请求)
填写协议/服务器地址/端口/方法
添加聚合报告/查看结果树/用表格查看结果

异常率控制在 20%

负载均衡(反向代理服务器)

轮询(默认)
加权轮询(数值越小权重越小)

upstram tomcats {
    server 192.168.1.173:8080 weight=1;
    server 192.168.1.174:8080 weight=2;
    server 192.168.1.175:8080 weight=5;
}
  • 1
  • 2
  • 3
  • 4
  • 5

upstream 指令参数

max_conns 最大并发连接数

upstram tomcats {
    server 192.168.1.173:8080 max_conns=2;
    server 192.168.1.174:8080 max_conns=2;
    server 192.168.1.175:8080 max_conns=2;
}
  • 1
  • 2
  • 3
  • 4
  • 5

slow_start
只支持权重,只支持商业版
server 192.168.1.173:8080 weight=6 slow_start=60s;

down
配置之后表示不可用

backup
表示备用机器

max_fails
最大失败数,达到了就认为下线了
fail_timeout
达到最大失败数后,多少时间后尝试去请求失败的机器
server 192.168.1.173:8080 max_fails=2 fail_timeout=1s;

keepalive
吞吐量,设置长连接

upstram tomcats {
    server 192.168.1.173:8080 max_conns=2;
    server 192.168.1.174:8080 max_conns=2;
    server 192.168.1.175:8080 max_conns=2;

    keepalive 32;
}
server {
    location / {
      proxy_http_version 1.1;
      proxy_set_header Connection "";
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

ip_hash
服务器发生异常不能直接移除配置,可以标记为 down
内网不变动,原因是 hash(192.) ip 只有前三位参与运算
问题: 节点变动,会话丢失,缓存请求不到

upstram tomcats {
    ip_hash;
    server 192.168.1.173:8080;
    server 192.168.1.174:8080;
    server 192.168.1.175:8080;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

一致性哈希算法
顺时针就近原则,减少用户影响

url_hash
url 变化后 hash 值不一样,请求的服务器就不一样
hash(url) % node_counts

upstram tomcats {
    hash $request_uri;
    server 192.168.1.173:8080;
    server 192.168.1.174:8080;
    server 192.168.1.175:8080;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

least_conn
请求最小连接数的服务器

upstram tomcats {
    least_conn;
    server 192.168.1.173:8080;
    server 192.168.1.174:8080;
    server 192.168.1.175:8080;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

nginx 控制浏览器缓存

304 状态 被 nginx 缓存

expires

  location /static {
    alias /home/xxx;
    # expires 10s;
    # expires @22h30m;
    # expires -1; # 提前过期
    # expires epoch; # 不设置过期
    # expires off; # nginx不设置不显示,浏览器使用默认缓存机制
    # expires max; # 最大值
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

proxy_cache_path 设置缓存保存的目录
keys_zone 设置共享内存以及占用的空间大小
max_size 设置缓存大小
inactive 超过此时间,则缓存自动清理
use_temp_path 关闭临时目录
proxy_cache_path /usr/local/nginx/upstream_cache keys_zone=mycache:5m max_size=1g inactive=30s use_temp_path=off;

server {
    # 开启并且使用缓存
    proxy_cache mycache;
    # 缓存校验控制
    proxy_cache_valid 200 304 24h;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

ssl 证书
config 目录添加 nginx crt/key
增加安装配置
–with-http_ssl_module

server {
    listen 443;
    server_name localhost;
    添加官网配置
    ssl open;
    ssl_certificate ;
    ssl_certificate_key ;
    ssl_session_cache share:SSL:1m;
    ssl_session_timeout 5m;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;
    ssl_prefer_server_ciphers on;
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

动静分离
前端项目部署到 nginx

部署到云服务器
域名映射到服务器
前端修改接口服务地址(去掉端口号)

支付中心接口修改

nginx 高可用 ha
nginx 主备(硬件配置需要一样)
keepalived 基于 VRRP 协议(虚拟路由冗余协议)

keepalived 安装
虚拟 ip
master ip
backup ip

下载 keepalived-2.0. 后解压

./configure --prefix=/usr/local/keepalived --sysconf=/etc
make && make install

/etc/keepalived/

如果报错 libnl/libnl-3 dev
yum -y install libnl libnl-devel

配置 keepalived
/etc/keepalived

global_defs {
  #路由id: 当前安装keepalived节点主机的标识符,全局唯一
  router_id keep_171
}

vrrp_instance VI_1 {
  # 当前节点状态,MASTER/BACKUP
  state MASTER
  # 当前实例绑定的网卡
  interface eth0
  # 保证主备节点一致
  virtual_router_id 51
  # 选举优先级,备机设置成80
  priority 100
  # 主备之间同步检查的时间间隔,默认1s
  advert_int 1
  # 认证授权的密码,防止非法节点的进入,节点都一样
  authentication {
    auth_type PASS
    auth_pass 111
  }
  # 追踪nginx脚本
  track_script {
    check_nginx_alive
  }
  # 虚拟ip
  virtual_ipaddress {
      192.168.1.161
  }
  # 下面的删除
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

192.168.1.171
192.168.1.172
192.168.1.161 (绑定虚拟 ip)

启动
进入/usr/local/keepalived/sbin/
./keepalived

注册为系统服务
cp /keepalived /etc//
cp sysconfig/keepalived /etc/sysconfig/
systemctl daemon-reload
systemctl start

keepalived 配置 nginx 自动重启
cd /etc/keepalived/
vi check_nginx_alive_or_not.sh

#!/bin/bash
A='ps -C nginx --no-header |wc -l'
#判断nginx是否宕机,如果宕机了,尝试重启
if [$A -eq 0];then
  /usr/local/nginx/sbin/nginx
  sleep 3
  if[$A -eq 0];then
    killall keepalived
  fi
fi
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

chmod +x check_nginx_alive_or_not.sh

配置文件添加

vrrp_script check_nginx_alive {
  script "/etc/keepalived/check_nginx_alive_or_not.sh"
  interval 2 # 每隔两秒
  weight 10 # 如果脚本运行失败,则权重+10
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

keepalived 双主热备
dns 轮询两个虚拟 ip

LVS(linux virtual system)

ipvs
负载均衡调度器

工作模式
nat(类似 nginx)
tun(ip 隧道, 相应通过服务直接返回(暴露到公网),不经过 lvs 返回)
dr(经过路由(vip)返回)

服务器与 ip 约定
lvs:
dip: 192.168.1.151
vip: 192.168.1.150

nginx1
rip: 192.168.1.171
vip: 192.168.1.150
nginx2
rip: 192.168.1.172
vip: 192.168.1.150

lvs 服务器
虚拟机需要关闭网络管理器
systemctl stop NetworkManager
systemctl disable NetworkManager

cd /etc/sysconfig/network-scripts/
cp ifcfg-ens33 ifcfg-ens33:1
vi ifcfg-ens33:1
BOOTPROTO=“static”
DEVICE=“ens33:1”
ONBOOT=“yes”
IPADDR=192.168.1.150
NETMASK=255.255.255.0

service network restart

安装 ipvsadm
yum install ipvsadm
ipvsadm -Ln

nginx1/nginx2 服务器
cd /etc/sysconfig/network-scripts/
cp ifcfg-lo ifcfg-lo:1
vi ifcfg-lo:1
DEVICE=“lo:1”
IPADDR=192.168.1.150
NETMASK=255.255.255.255
NETWORK=127.0.0.0
BROADCAST=127.255.255.255
ONBOOT=“yes”
NAME=loopback

ifup lo

设置 arp
请求: 0
响应: 2

nginx1/nginx2 服务器
vi /etc/
net..arp_ignore = 1
net..arp_ignore = 1
net..arp_ignore = 1
net..arp_announce = 2
net..arp_announce = 2
net..arp_announce = 2

sysctl -p

route add -host 192.168.1.150 dev lo:1
route -n

echo “route add -host 192.168.1.150 dev lo:1” >> /etc/

使用 ipvsadm 配置集群规则
lvs 服务器
ipvsadm -A -t 192.168.1.150:80 -s rr
ipvsadm -Ln

ipvsadm -a -t 192.168.1.150:80 -r 192.168.1.171:80 -g
ipvsadm -a -t 192.168.1.150:80 -r 192.168.1.172:80 -g

ipvsadm -E -t 192.168.1.150:80 -s rr -p 5
ipvsadm --set 1 1 1

搭建 keepalived+lvs+nginx 高可用集群负载
lvs 主备

global_defs {
  router_id LVS_151
}

vrrp_instance VI_1 {
  state MASTER
  interface ens33
  virtual_router_id 41
  priority 100
  advert_int 1
  authentication {
      auth_type PASS
      auth_pass 1111
  }
  virtual_ipaddress {
    192.168.1.150
  }
}
# 配置集群地址访问的IP+端口, 端口和nginx保持一致
virtual_server 192.168.1.150 80 {
    # 健康检查时间,单位 秒
    delay_loop 6
    # 配置负载均衡的算法,默认是轮询
    lb_kind DR
    # 设置会话持久化的事件
    persistence_timeout 5
    # 协议 -t
    protocol TCP
    # 负载均衡的真实服务器,也就是nginx节点的具体的真实ip地址
    real_server 192.168.1.171 80 {
        # 轮询的默认权重配比设置为1
        weight 1
        # 设置健康检查
        connect_port 80
        # 超时时间 2s
        connect_timeout 2
        # 重试次数
        nb_get_retry 2
        # 间隔时间 3s
        delay_before_retry 3
    }
    real_server 192.168.1.172 80 {
        # 轮询的默认权重配比设置为1
        weight 1
        # 设置健康检查
        connect_port 80
        # 超时时间 2s
        connect_timeout 2
        # 重试次数
        nb_get_retry 2
        # 间隔时间 3s
        delay_before_retry 3
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
global_defs {
  router_id LVS_152
}

vrrp_instance VI_1 {
  state BACKUP
  interface ens33
  virtual_router_id 41
  priority 50
  advert_int 1
  authentication {
      auth_type PASS
      auth_pass 1111
  }
  virtual_ipaddress {
    192.168.1.150
  }
}
# 配置集群地址访问的IP+端口, 端口和nginx保持一致
virtual_server 192.168.1.150 80 {
    # 健康检查时间,单位 秒
    delay_loop 6
    # 配置负载均衡的算法,默认是轮询
    lb_kind DR
    # 设置会话持久化的事件
    persistence_timeout 5
    # 协议 -t
    protocol TCP
    # 负载均衡的真实服务器,也就是nginx节点的具体的真实ip地址
    real_server 192.168.1.171 80 {
        # 轮询的默认权重配比设置为1
        weight 1
        # 设置健康检查
        connect_port 80
        # 超时时间 2s
        connect_timeout 2
        # 重试次数
        nb_get_retry 2
        # 间隔时间 3s
        delay_before_retry 3
    }
    real_server 192.168.1.172 80 {
        # 轮询的默认权重配比设置为1
        weight 1
        # 设置健康检查
        connect_port 80
        # 超时时间 2s
        connect_timeout 2
        # 重试次数
        nb_get_retry 2
        # 间隔时间 3s
        delay_before_retry 3
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54

主从复制高可用 Redis 集群

分布式架构优点
业务解耦
系统模块化,可重用化
提升系统并发量
优化运维部署效率

分布式架构缺点
架构复杂
部署多个子系统复杂
系统之间通信耗时

设计原则
异步解耦
幂等一致性
拆分原则
融合分布式中间件
容错高可用

nosql 常见分类
键值数据库 redis
列存储数据库 hbase
文档型数据库 mongodb
图形数据库 neo4j

redis 安装
解压
tar -zxvf redis-5.0.
yum install gcc-c++
make
make install

cd utils
cp redis_init_script /etc/
cp /usr/local/redis
修改 daemonize yes
修改 dir /usr/local/redis/workspace
新增文件夹/usr/local/redis/workspace
修改 bind 0.0.0.0
修改 requirepass xxx

修改/etc//redis_init_script
CONF=“/usr/local/redis/”

chmod 777 redis_init_script
./redis_init_script start

跟随系统自启动

#chkconfig: 22345 10 90
#description: Start and Stop redis
  • 1
  • 2

chkconfig redis_init_script on

命令行使用
redis-cli
auth xxx

redis-cli -a xxx ping

redis_init_script stop 命令加上 -a “xxx”

springboot 整合 redis 实战

redis 的线程模型

redis 整合 springboot

<dependency>
    <groupId></groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
  • 1
  • 2
  • 3
  • 4
spring:
  redis:
    database: 0
    host: 127.0.0.1
    port: 6379
    password: xxx
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

redis 进阶提升与主从复制
redis 持久化
RDB 快照
AOF 写操作记日志
编辑 开启 aof
appendonly yes

主从架构
三台 redis(一主二从)

从节点
replicaof master_ip port
masterauth password
replica-read-only yes

无磁盘复制
repl-diskless-sync no
repl-diskless-sync-delay 5

redis 缓存过期机制
(主动)定期删除
hz 10
(被动)惰性删除
MAXMEMORY POLICY
noeviction
LRU
LFU

redis 哨兵机制与实现
sentinel.conf
protected-mode no
port 26379
daemonize yes
pidfile /var/run/
logfile /usr/local/redis/sentinel/
dir /usr/local/redis/sentinel

quorum 设置为 2(有两个哨兵认为主节点挂了才算挂)
sentinel monitor mymaster 127.0.0.1 6379 2
sentinel auth-pass mymaster xxx
sentinel down-after-milliseconds mymaster 10000
sentinel parallel-syncs mymaster 1
sentinel failover-timeout mymaster 180000

启动
redis-sentinel
redis-cli -p 36379
哨兵节点要有至少三个或者奇数个节点
哨兵分部式部署在不同的计算机节点
一组哨兵只监听一组主从

集群搭建
三主三从
所有节点修改配置如下
cluster-enabled yes
cluster-config-file
cluster-node-timeout 5000
appendonly yes
删除 和

redis-cli -a xxx --cluster create 192.168.1.201 192.168.1.202 192.168.1.203 192.168.1.204 192.168.1.205 192.168.1.206 --cluster-replicas 1

redis-cli -a xxx --cluster check 192.168.1.201:6379
redis-cli -c -a xxx -h 192.168.1.202 -p 6379
cluster info
cluster nodes

springboot 集成 redis 集群

redis 缓存穿透
1,缓存不存在的值
2,使用布隆过滤器
可以判断一定不存在,不能判断一定存在
不能够删除
redis 缓存雪崩
key 同一时间失效
方案
永不过期
过期时间错开
多缓存结合
采购第三方 redis

批量获取
().multiGet(keys)
管道
(){};

分布式架构

分布式会话
redis
springsession

<dependency>
    <groupId></groupId>
    <artifactId>spring-session-data-redis</artifactId>
</dependency>
<dependency>
    <groupId></groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

拦截器

单点登录
sso + thymeleaf

分布式搜索

下载解压 elstsearch
修改

: xxx
: es-node1
: /path/data
: /path/logs
: 0.0.0.0
cluster.initial_master_nodes: ["es-node1"]

: true // 开启本地跨域
-origin: "*" //允许所有请求类型跨域
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

修改
-Xms1g
-Xmx1g

创建新的用户去启动 es
useradd esuser
chown -R esuser:esuser /usr/local/elasticsearch-7.4.2
su esuser

su root
修改/etc/security/,后面添加

* soft nofile 65535
* hard nofile 131072
* soft nproc 2048
* hard nproc 4096
  • 1
  • 2
  • 3
  • 4

修改/etc/,后面添加
vm.max_map_count=262145
sysctl -p

su esuer
./elasticsearch

访问
192.168.:9200

安装 head 插件(mobz/elasticsearch-head)

head 与 postman 基于索引的基本操作

mappings(数据类型)
type: “text” // 模糊搜索
type: “keyword” // 精确索引

主要数据类型
text,keyword
long,integer,short,byte
double,float
boolean
date
object
数组不能混,类型需要一致

新版本乐观锁
_version
if_seq_no 和 if_primary

分词和内置分词器
_analyze

standard // 单词会被拆分,大写转换成小写
simple // 能按非字母分词,大写转换成小写
whitespace // 按照空格分词,忽略大小写
stop // 去除停用词
keyword // 不做分词

建立 IK 中文分词器
modcl/elasticsearch-analysis-ik
需要指定对应的版本
解压后放到 plugins 目录下
重启 es

自定义中文词库
config/IKAnalyzer

<entry key="ext_dict"></entry>
  • 1

同级目录下创建

DSL 搜索

特定领域查询语言

{
  "query": {
    "match": {
      "desc": "慕课网"
    }
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

分页
from + size

term
不分词,精确匹配

terms
不分词,精确匹配,可以传一个数组

match_phrase
匹配连续词组
slop
中间允许跳过的词

query
operator: or / and
minimum_should_match: “60%”
minimum_should_match: 4 // 最小应该匹配

通过 ids 查询

ids: {
  "type": "_doc",
  "values": ["1001","1003","10101"]
}
  • 1
  • 2
  • 3
  • 4

match / multi_match
fields:[“desc”,“nickname^10”] // 提升权重(boost)(排名上升)

布尔查询(可以组合查询)
bool
must/should/must_not

过滤器

只会对结果进行筛选
post_filter: {
  range:{
    money: {
      "gt": 60,
      "lt": 1000
    }
  }
}
post_filter: {
  term:{
    birthday: "1997-12-24"
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

排序

{
  "sort": [
    {
      "age": "asc"
    },
    {
      "money": "desc"
    }
  ]
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

字符串(文本)排序可以增加 keyword 之后进行排序

http://192.168.1.1:9200/shop/_mapping
{
  "properties":{
    "id":{
      "type":"long"
    },
    "nickname": {
      "type":"text",
      "analyzer":"ik_max_word",
      "fields": {
        "keyword":{
          "type":"keyword"
        }
      }
    }
  }
}

http://192.168.1.1:9200/shop/_search
{
  "sort": [
    {
      "": "asc"
    }
  ]
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

高亮

{
  "query": {
    "match": {
      "desc": ""
    }
  },
  "highlight": {
    "pre_tags": ["<span>"],
    "post_tags": ["</span>"],
    "fields": {
      "desc": {}
    }
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

深度分页
默认限制 10000
使用页码控制

http://192.168.1.1:9200/shop/_settings //PUT
{
  "index.max_result_window": 100000
}
  • 1
  • 2
  • 3
  • 4

游标查询

http://192.168.1.1:9200/shop/_search?scroll=1m // 一分钟
{
  "query": {
    "match_all": {}
  },
  "sort": ["_doc"],
  "size": 5
}
http://192.168.1.1:9200/shop/_search?scroll
{
  "scroll": 1m,
  "scroll_id": "上一次查询返回的scroll_id"
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

批量查询

http://192.168.1.1:9200/shop/_mget
{
  "ids":["1001","1003","10015"]
}
  • 1
  • 2
  • 3
  • 4

批量操作 bulk(注意要换行)

http://192.168.1.1:9200/shop/_bulk
{"create": {"_index": "shop", "_type":"_doc","_id":"2004"}}
{"id":"2004","nickname":"name-2004"}

http://192.168.1.1:9200/shop/_doc/_bulk
{"create": {"_id":"2004"}}
{"id":"2004","nickname":"name-2004"}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

bulk 不存在新增存在更新 index

http://192.168.1.1:9200/shop/_doc/_bulk
{"index": {"_id":"2004"}}
{"id":"2004","nickname":"index-2004"}
  • 1
  • 2
  • 3

bulk 批量修改属性

http://192.168.1.1:9200/shop/_doc/_bulk
{"update":{"_id":"2004"}}
{"doc":{"id":"3304"}}
  • 1
  • 2
  • 3

bulk 批量删除

http://192.168.1.1:9200/shop/_doc/_bulk
{"delete":{"_id":"2004"}}
  • 1
  • 2

ES 集群
192.168.1.184
192.168.1.185
192.168.1.186
所有的节点删除 /usr/local/elasticsearch-7.4.2/data 下的 nodes
所有的 修改 : xxx-es-cluster
不同节点修改不同 node 节点名称
=node1
修改(主节点(可以被选举)和数据节点)
: true
: true
discovery.seed_hosts: [“192.168.1.184”, “192.168.1.185”, “192.168.1.186”]
// 查看非注释内容
more | grep [#]

集群分片测试
集群宕机测试
集群脑裂(新版本已处理(N/2)+1)
集群的文档读写原理
1,协调节点
2,主分片或者副本分配轮询读操作
3,返回给协调节点
4,响应给客户端

springboot 整合 elasticsearch

<dependencies>
    <dependency>
        <groupId></groupId>
        <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
        <version>2.2.</version>
    </dependency>
    <dependency>
        <groupId></groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <version>test</version>
    </dependency>
</dependencies>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
application.yml
spring:
  data:
    elasticsearch:
      cluster-nodes: 192.168.1.187:9300
      cluster-name: es6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

elasticsearch6.4.3 配置文件
: es6
: node0
: /usr/local/elasticserch-6.4.3/data
: /usr/local/elasticserch-6.4.3/logs
: 0.0.0.0

需要切换到 root 用户下去修改配置
vim /etc/security/

* soft nofile 65535
* hard nofile 131073
* soft nproc 2048
* hard nproc 4096
  • 1
  • 2
  • 3
  • 4

ik 的版本要对应

es 整合 springboot

不建议使用 ElasticsearchTemplate 对索引进行管理(创建索引,更新映射,删除索引)
索引就像数据库或者数据库中的表
只会针对数据做 crud 的操作
属性类型不灵活主分片和副分片无法设置

logstash 数据同步
使用 update_time 作为同步边界
logstash-input-jdbc 插件(版本要和 es 相同)
解压后
cd logstash-6.4.3
mkdir sync
复制数据库驱动到当前目录(sync)
cp /mysql-connector-java-5.1. .
vi

input {
    jdbc {
        # 设置 MySql/MariaDB 数据库url以及数据库名称
        jdbc_connection_string => "jdbc:mysql://192.168.1.6:3306/foodie-shop-dev?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true"
        # 用户名和密码
        jdbc_user => "root"
        jdbc_password => "root"
        # 数据库驱动所在位置,可以是绝对路径或者相对路径
        jdbc_driver_library => "/usr/local/logstash-6.4.3/sync/mysql-connector-java-5.1."
        # 驱动类名
        jdbc_driver_class => ""
        # 开启分页
        jdbc_paging_enabled => "true"
        # 分页每页数量,可以自定义
        jdbc_page_size => "1000"
        # 执行的sql文件路径
        statement_filepath => "/usr/local/logstash-6.4.3/sync/"
        # 设置定时任务间隔  含义:分、时、天、月、年,全部为*默认含义为每分钟跑一次任务
        schedule => "* * * * *"
        # 索引类型
        type => "_doc"
        # 是否开启记录上次追踪的结果,也就是上次更新的时间,这个会记录到 last_run_metadata_path 的文件
        use_column_value => true
        # 记录上一次追踪的结果值
        last_run_metadata_path => "/usr/local/logstash-6.4.3/sync/track_time"
        # 如果 use_column_value 为true, 配置本参数,追踪的 column 名,可以是自增id或者时间
        tracking_column => "updated_time"
        # tracking_column 对应字段的类型
        tracking_column_type => "timestamp"
        # 是否清除 last_run_metadata_path 的记录,true则每次都从头开始查询所有的数据库记录
        clean_run => false
        # 数据库字段名称大写转小写
        lowercase_column_names => false
    }
}
output {
    elasticsearch {
        # es地址
        hosts => ["192.168.1.187:9200"]
        # 同步的索引名
        index => "foodie-items"
        # 设置_docID和数据相同
        # document_id => "%{id}"
        document_id => "%{itemId}"
    }

    # 自定义模板名称
    template_name => "myik"
    # 模板所在位置
    template => "/usr/local/logstash-6.4.3/sync/"
    # 重写模板
    template_overwrite => true
    # 默认为true, fase关闭logstash自动管理模板功能,如果自定义模板,则设置为false
    manage_template => false

    # 日志输出
    stdout {
        codec => json_lines
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60

SELECT
       AS itemId,
      i.item_name AS itemName,
      i.sell_counts AS sellCounts,
       AS imageUrl,
      tempSpec.price_discount AS price,
      i.update_time as update_time
  FROM
      items i
          LEFT JOIN items_img ii ON  = ii.item_id
          LEFT JOIN ( SELECT item_id, MIN( price_discount ) AS price_discount FROM items_spec GROUP BY item_id ) tempSpec ON  = tempSpec.item_id
  WHERE
      ii.is_main = 1
      and
      i.update_time >= :sql_last_value
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

@timestemp 的时间首次安装后应该是 1970 年,数据的 update_time 要比这个时间大才会采集到

自定义模板配置中文分词
https://192.168.187:9200/_template/logstash 查询返回后的模板进行修改

{
  "order": 0,
  "version": 1,
  "index_patterns": ["*"],
  "settings": {
    "index": {
      "refresh_interval": "5s"
    }
  },
  "mappings": {
    "_default_": {
      "dynamic_templates": [
        {
          "message_field": {
            "path_match": "message",
            "match_mapping_type": "string",
            "mapping": {
              "type": "text",
              "norms": false
            }
          }
        },
        {
          "string_fields": {
            "match": "*",
            "match_mapping_type": "string",
            "mapping": {
              "type": "text",
              "norms": false,
              "analyzer": "ik_max_word",
              "fields": {
                "keyword": {
                  "type": "keyword",
                  "ignore_above": 256
                }
              }
            }
          }
        }
      ],
      "properties": {
        "@timestamp": {
          "type": "date"
        },
        "@version": {
          "type": "keyword"
        },
        "geoip": {
          "dynamic": true,
          "properties": {
            "ip": {
              "type": "ip"
            },
            "location": {
              "type": "geo_point"
            },
            "latitude": {
              "type": "half_float"
            },
            "longitude": {
              "type": "half_float"
            }
          }
        }
      }
    }
  },
  "aliases": {}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69

FastDFS
/happyfish100/fastdfs/wiki

tracker / storage 这台服务器下载解压 FastDFS
安装环境

yum install -y gcc gcc-c++
yum -y install libevent
tar -zxvf libfastcommon-1.0.
./
./ install

tar -zxvf fastdfs-6.
./
./ install
cp fastdfs-6.04/conf/* /etc/fdfs
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

通过加载 或 来区分是 tracker 还是 storage 服务
修改
base_path=/usr/local/fastdfs/tracker
启动 tracker 服务(tracker 要先启动再启动 storage)
/usr/bin/fdfs_trackerd /etc/fdfs/
修改
group_name=xxx
base_path=/usr/local/fastdfs/stroage
store_path0=/usr/local/fastdfs/stroage
tracker_server=192.168.1.155:22122
启动 stroage 服务
/usr/bin/fdfs_stroaged /etc/fdfs/
修改 文件
base_path=/usr/local/fastdfs/client
tracker_server=192.168.1.155:22122

测试图片上传
/usr/bin/fdfs_test /etc/fdfs/ upload

nginx 要和 stroage 安装在同一个服务
解压 fastdfs-nginx-module-1.
进入 fastdfs-nginx-module-1.22
修改 src/config

ngx_module_incs="/usr/local/include" 改成 ngx_module_incs="/usr/include"
CORE_INCS="$CORE_INCS /usr/local/include" 改成 CORE_INCS="$CORE_INCS /usr/include"
  • 1
  • 2

cp mod_fastdfs.conf /etc/fdfs/
yum install -y pcre pcre-devel
yum install -y zlib zlib-devel
yum install -y openssl openssl-devel

tar -zxvf nginx-1.16.
mkdir /var/temp/nginx -p

cd /nginx-1.16.1
./configure
–prefix=/usr/local/nginx
–pid-path=/var/run/nginx/
–lock-path=/var/lock/
–error-log-path=/var/log/nginx/
–http-log-path=/var/log/nginx/
–with-http_gzip_static_module
–http-client-body-temp-path=/var/temp/nginx/client
–http-proxy-temp-path=/var/temp/nginx/proxy
–http-fastcgi-temp-path=/var/temp/nginx/fastcgi
–http-uwsgi-temp-path=/var/temp/nginx/uwsgi
–http-scgi-temp-path=/var/temp/nginx/scgi
–add-module=/home/software/FastDFS/fastdfs-nginx-module-1.22/src

make
make install

修改 mod_fastdfs.conf
base_path=/usr/local/fastdfs/tmp
tracker_server=192.168.1.155:22122
group_name=xxx
url_have_group_name=true
store_path0=/usr/local/fastdfs/stroage

修改

server {
    listen 8888; //需要和的配置文件里面http.server_port的一致
    server_name localhost;
    localtion /xxx/M00 {
      ngx_fastdfs_module;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

消息队列
分布式消息队列认知提升
应用场景: 服务解耦/削峰/异步
应用思考点: 生产端可靠性投递/消费端幂等
高可用/低延时/可靠性/扩展性/堆积能力

集群架构模式: 分布式,可扩展,高可用,可维护

rabbitMQ 四种集群架构
主备模式
远程模式
镜像模式(高可用)
多活模型(异地容灾),使用 federation 插件

kafka 特点
分布式/跨平台/实时性/伸缩性
kafka 高性能的原因是什么
顺序写/page cache/高性能高吞吐/后台异步,主动 flush/预读策略/io 调度

rabbitMQ 实战
AMQP 核心概念
server
connection
channel
message(properties/body)
virtual host
exchange
binding
routing key queue

安装与入门

erlang-18.3-1..x86_64.rpm
rabbitmq-server-3.6.
socat-1.7.3.2-1.1.e17.x86_64.rpm

三个节点允许
yum install build-essential openssl openssl-devel unixODBC unixODBC-devel make gcc gcc-c++ kernel-devel m4 ncurses-devel tk tc xz

配置 etc/hostname 和/etc/hosts
下载 RabbitMQ 所需软件包(在这里使用的是 RabbitMQ3.6.5 稳定版本)
wget /releases/erlang/erlang-18.3-1..x86_64.rpm
wget /CentOS/7/x86_64/socat-1.7.3.2-1.1..x86_64.rpm
wget /releases/rabbitmq-server/v3.6.5/rabbitmq-server-3.6.

安装服务命令
rpm -ivh erlang-18.3-1..x86_64.rpm
rpm -ivh socat-1.7.3.2-1.1.el7.x86_64.rpm
rpm -ivh rabbitmq-server-3.6.

修改用户登录与连接心跳检测,注意修改
vim /usr/lib/rabbitmq/lib/rabbitmq_server-3.6.5/ebin/
修改点 1:loopback_users 中的 <<“guest”>>,只保留 guest (用于用户登录)
修改点 2:heartbeat 为 10(用于心跳连接)

安装管理插件
首先启动服务(后面 | 包含了停止、查看状态以及重启的命令)
/etc//rabbitmq-server start | stop | status | restart

查看服务有没有启动: lsof -i:5672 (5672 是 Rabbit 的默认端口)
rabbitmq-plugins enable rabbitmq_management

可查看管理端口有没有启动:
lsof -i:15672 或者 netstat -tnlp | grep 15672

访问地址,输入用户名密码均为 guest
http://localhost:15672/

api
交换机属性
name: 交换机名称
type: direct,topic,fanout,headers
durability: 是否需要持久化,true 为持久化
auto delete: 当最后一个绑定到 exchange 上的队列删除后,自动删除该 exchange
internal: 当前 exchange 是否用于 rabbitMQ 内部使用,默认是 false
arguments: 扩展参数,用于扩展 amqp 协议自制定化使用

direct exchange
所有发送到 direct exchange 的消息被转发到 routeKey 中指定的 queue
注意: direct 模式可以使用 rabbitmq 自带的 exchange: default exchange,
所以不需要将 exchange 进行任何绑定操作,
消息传递时,routeKey 必须完全匹配才会被队列接收没否则消息会被抛弃
直连模式指定字符串作为 key

topic exchange
exchange 将 routeKey 和某个 topic 进行模糊匹配,此时队列需要绑定一个 topic

"#" 匹配一个或多个词
"*" 匹配不多不少一个词
如:
"log.#" 能够匹配到""
"log.*" 只会匹配到""
  • 1
  • 2
  • 3
  • 4
  • 5

fanout exchange
不处理路由键,只需要简单的将队列绑定到交换机上
发送到交换机的消息都会被转发到与该交换机绑定的所有队列上
转发消息是最快的

binding
exchange 和 exchange,queue 之间的连接关系

queue
durability: 是否持久化(durable/transient)
auto delete: yes (代表当最后一个监听被移除之后,该 queue 会被自动删除)

message
content_type, content_encoding, priority
correlation_id, reply_to, expiration, message_id
timestamp, type, user_id, app_id, cluster_id
correlation_id 用于指定唯一规则
同一个 virtual host 里面不能有相同名称的 exchange 和 queue

rabbitmq 高级特性
消息如何保障 100%的投递成功
消息信心落库,对消息状态进行打标

幂等性概念详解
版本号

在海量订单产生的业务高峰期,如何避免消息的重复消费问题
消费端实现幂等性
业务唯一 id 或指纹码,利用数据库主键去重

消息的 ack 与重回队列

confirm 确认消息(ack)
生产者投递消息后,broker 收到消息后异步给生产者一个应答
()
addConfirmListener

return 返回消息
return Listener 用于处理一些不可路由的消息
监听队列,进行消费处理操作
Mandatory: true

消息的限流
单个客户端无法同时处理这么多数据
qos 功能,即在非自动确认消息的前提下,如果一定数目的消息未被确认前,不进行新的消息
(0,1,false);
prefetchSize:0
prefetchCount: 不要同时给一个消费者推送多于 n 个消息,即一旦有 n 个消息还没有 ack,则该 consumer 将 block 掉,直到有消息 ack
global: true/false 是否将上面设置应用于 channel
消费端 ack 与重回队列
手工 ack 和 nack
重回队列设置成 false

ttl 队列/消息
支持队列的过期时间,从消息入队列开始计算,只要超过了队列的超时时间设置,那么消息会自动的清除

死信队列
几种情况
消息被拒绝(/)并且 requeue=false
消息 ttl 过期
队列达到最大长度
设置后会被路由到指定的队列
(“x-dead-letter-exchange”, “dlx-exchange”)
(queueName, exchangeName, routingKey)
(queueName, false, false, false, arguments)
(“dlx,exchange”, exchangeType, true,false,false,null)
(“”, false, false, false, null)
(“”, “”, “#”)

集群架构-镜像队列集群环境搭建实操
参考搭建文档

与 springboot 整合
/xingyuezhiyun/rabbitmqDemo

基础组件封装与实战
迅速消息发送
确认消息发送
延迟消息发送

rabbitMQ 可靠性投递基础组件封装
迅速消息 不保证确认
确认消息 确认写库
可靠消息 最终一致性(定时任务方案(rabbitMq 消息补偿机制))
延迟消息(使用插件: rabbitmq_delayed_message_exchange-0.0.)

broker_message.broker_message
CREATE TABLE IF NOT EXISTS `broker_message` (
  `message_id` varchar(128) NOT NULL,
  `message` varchar(4000),
  `try_count` int(4) DEFAULT 0,
  `status` varchar(10) DEFAULT '',
  `next_retry` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
  `create_time` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
  `update_time` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
  PRIMARY KEY (`message_id`)
) ENGINE=InnoDB DEFAULE CHARSET=utf8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

Kafka

kafka 核心概念详解

mq 实战应用场景

异步化,服务解耦,削峰填谷
海量日志收集
数据同步应用
实时计算分析

kafka 基本概念

kafka 基本配置参数详解

topic/partition(isr(/HW/LEO)/osr)/replica/offset

  zookeeper环境搭建
    1,修改三台服务器节点的hostname
    2,关闭防火墙
        启动防火墙: systemctl start firewalld
        关闭防火墙: systemctl stop firewalld
        重启防火墙: systemctl restart firewalld
        查看防火墙状态: systemctl status firewalld
        开机禁用防火墙: systemctl disable firewalld
    3,上传zk到三台服务器节点
        进行解压: tar -zxvf zookeeper-3.4. -C /usr/local/
        修改环境变量: vim /etc/profile
            export ZOOKEEPER_HOME=/usr/local/zookeeper-3.4.6
            export PATH=.:$ZOOKEEPER_HOME/bin
        刷新环境变量: source /etc/profile
        到zookeeper-3.4.6目录下修改配置文件
            /usr/local/zookeeper/conf
            mv zoo_sample.cfg 
            修改
                dataDir=/usr/local/zookeeper-3.4.6/data
                修改(增加)集群地址
                server.0=bhz221:2888:3888
                server.1=bhz222:2888:3888
                server.2=bhz223:2888:3888
        创建文件夹
            mkdir /usr/local/zookeeper-3.4.6/data
        创建文件myid 路径应该创建在/usr/local/zookeeper-3.4.6/data下面
            vim /usr/local/zookeeper-3.4.6/data/myid
              // 每一台服务器的myid文件内容不同,分别修改里面的值为0,1,2
              // 与之前的配置文件里面的server.0,server.1,server.2顺序相对应
        启动路径: 进入zookeeper-3.4.6/bin/
         start
         status
         stop
    4,开机启动
        cd /etc//
        touch zookeeper
        chmod 777 zookeeper
        vim zookeeper
          开机启动zookeeper脚本:
              ```
              #!/bin/bash

              #chkconfig: 2345 20 90
              #description: zookeeper
              #processname: zookeeper
              export JAVA_HOME=/usr/local/jdk1.8
              export PATH=$JAVA_HOME/bin:%PATH
              case $1 in
                      start) /usr/local/zookeeper-3.4.6/bin/ start;;
                      stop) /usr/local/zookeeper-3.4.6/bin/ stop;;
                      status) /usr/local/zookeeper-3.4.6/bin/ status;;
                      restart) /usr/local/zookeeper-3.4.6/bin/ restart;;
                      *)  echo "require start|stop|status|restart" ;;
              esac
              ```
        开机启动配置: chkconfig zookeeper on
        验证:
            chkconfig --add zookeeper
            chkconfig --list zookeeper
    5,连接工具
        zooInspetor
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63

kafka 环境搭建

kafka版本: kafka_2.12
    /dist/kafka/2.1.0/kafka_2.12-2.1.
    tar -zxvf kafka_2.12-2.1. -C /usr/local
    mv kafka_2.12-2.1.0 kafka_2.12
    vim /usr/localkafka_2.12/config/
        =0
        port=9092
        =192.168.11.221
        =192.168.11.221
        =5
        =/usr/local/kafka_2.12/kafka-logs
        =192.168.11.221:2181,192.168.11.222:2181,192.168.11.223:2181
    mkdir /usr/local/kafka_2.12/kafka-logs
    启动kafka: /usr/localkafka_2.12/bin/ /usr/localkafka_2.12/config/
管控台: kafkaManger 2.0.0.2
    vim /usr/local/kafka-manager-2.0.0.2/conf/
    ="192.168.11.221:2181,192.168.11.222:2181,192.168.11.223:2181"
    启动: /usr/local/kafka-manager-2.0.0.2/bin/kafka-manager &
    访问: http://192.168.11.221:9000
    add cluster
    集群验证
        add topic
        发送测试: ./ --broker-list 192.168.221:9092 --topic test
        接收测试: ./ --bootstrap-server 192.168.221:9092 --topic test
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

kafka 急速入门

配置消费者参数属性和构造消费者对象
订阅主题
拉去消息并进行消费处理
提交消费偏移量,关闭消费者

参数文件配置参数
    
    listeners
    
    和
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

kafka 生产者

,和,
kafkaProducer是线程安全的,consumer不是线程安全的
一条消息必须通过key去计算出实际的partition,按照partition去存储,刚开始创建ProducerRecord时key为空,kafka也会通过算法计算生成一个key和partition

kafka 生产者重要参数详解
    acks: 指定发送消息后,broker至少有多少个副本接收到该消息客户端才能够收到服务端的成功响应,默认为1
    : 用来限制生产者客户端能发送的消息的最大值
    retries和: 重试次数和重试间隔,默认100
    : 这个参数用来指定消息的压缩方式,默认值是none,可选gzip,snappy,lz4
    : 指定在多久之后关闭限制的连接,默认是540000(ms)
    : 这个参数用来指定生产者发送producerBatch之前等待更多消息加入produceBatch的时间,默认是0
    batch size: 累计多少条消息,则一次进行批量发送
    : 缓存提升性能参数,默认为32M
    : 这个参数用来设置socket接收消息缓冲区(SO_RECBUF)的大小,默认值32KB
    : 这个参数用来设置socket发送消息缓冲区(SO_SNDBUF)的大小,默认值为128KB
    : 这个参数用来配置producer等待请求响应的 最长时间,默认值为30000(ms)

kafka 拦截器
    生产者实现
    // 添加配置
    (ProducerConfig.INTERCEPTOR_CLASSES_CONFIG, ())
    消费者实现
    // 添加配置
    (ConsumerConfig.INTERCEPTOR_CLASSES_CONFIG, ())

kafka 序列化反序列化
    XxxSerializer实现Serializer<Xxx>
    // 添加配置
    (ProducerConfig.VALUE_SERIALIZER_CLASSES_CONFIG, ())
    XxxDeSerializer实现DeSerializer<Xxx>
    // 添加配置
    (ConsumerConfig.VALUE_SERIALIZER_CLASSES_CONFIG, ())

kafka 分区器使用
    实现Partitioner
    // 添加配置
    (ProducerConfig.PARTITIONER_CLASSES_CONFIG, ())
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

kafka 消费者

  消费者与消费者组
      通过消费者组的设置来区分发布订阅(不同组)和点对点消费(相同组)
  消费者必要参数方法
      
      和
      groupid: 消费者所属消费组
      subscribe: 消息主题订阅,支持集合/标准正则表达式
      assign: 只订阅主题的某个分区
  消费者提交位移
      ,默认值为true(要改为false)
      ,默认值为5秒
  消费者手工提交
      手工提交: 
      提交方式: commitSync / commitAsync
      同步方式: 整体提交 / 分区提交
  消费者在均衡
      不同组(发布订阅)
      ((Const.TOPIC_REBALANCE), new ConsumerRebalanceListener)
  消费者多线程
      kafka是线程安全的,但是kafka consumer是线程非安全的
      1,一个线程一个consumer (同一个消费者组)
      2,consumer里分发任务完成后提交(join)
  消费者重要参数
      : 一次拉取最小数据量,默认为1B
      : 一次拉取最大数据量,默认为50M
      : 一次fetch请求,从一个partition中取得的records最大值,默认1M
      : Fetch 请求发给broker后,在broker中可能会被阻塞的时长,默认为500
      : Consumer每次调用poll()时取到的records的最大数,默认为500条

kafka 高级应用整合springboot
    <dependency>
        <groupId></groupId>
        <artifactId>spring-kafka</artifactId>
    </dependency>
    生产者: maven / 配置 / KfkaTemplate<String, Object>
        -path=/producer
        =8001
        -servers=
        =0
        -size=16384
        -memory=33554432
        -serializer=
        -serializer=
        =1
             0, 生产者在成功写入消息之前不会等待任何来自服务器的响应
             1, 只要集群的首领节点收到消息,生产者就会收到一个来自服务器成功响应
             -1, 分区leader必须等待消息被成功写入到所有的isr副本才认为producer请求成功,消息持久性可以保证,但是吞吐率是最差的
    消费者: maven / 配置 / @KafkaListener(groupId = "group02", topics = "topic02")
        -path=/consumer
        =8002
        -servers=
        -auto-commit=false
        -mode=manual
        -offset-reset=earliest
            (偏移量的分区或者偏移量无效的情况下该作如何处理, latest: 最新的记录开始读; earliest: 从起始位置开始读取分区的记录)
        -serializer=
        -serializer=
        =5
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58

海量日志系统收集

filebeat + kafka + logstash + es / kibana
maven 排除 spring-boot-starter-log4j2
MDC 变量替换输出
filebeat-6.6.0-linux-x86_64.
logstash-6.6.

es
kibana
watcher(es 告警插件)

数据同步应用

什么是数据同步
分表分库后同步表数据

应用场景
数据落地后才开始同步

canal 基于日志增量订阅和消费的业务包括:
数据库镜像,数据库实时备份
索引构建和实时维护(拆分异构索引,倒排索引)
业务 cache 刷新,带业务逻辑的增量数据处理

优点: 实时性好,分布式,ack 机制
缺点: 只支持增量同步,不支持全量同步
mysql -> es, rdb
一个 instance 只能有一个消费端消费
单点压力过大

-1.1.
-1.1.
-1.1.
-1.1.

mysql -> canal -> mq(kafka) -> es

实时计算分析

架构思考: 分布式日志,跟踪告警,分析平台

分布式锁设计

使用锁解决电商中的超卖

行锁(扣减后变成负数)
synchronized 方法锁(手动控制事务)
synchronized 块锁(手动多个事务进行控制)
reentrantLock(手动控制事务)
  • 1
  • 2
  • 3
  • 4

单体应用锁的局限性

分布式服务使用ReentrantLock没有办法锁住
基于数据库实现分布式锁的步骤
  select ... for update
  优点: 简单方便,易于理解,易于操作
  缺点: 并发量大师,对数据库压力较大
  建议: 作为锁的数据库与业务数据库分开
redis分布式锁原理
  SET resource_name my_random_value NX PX 30000
  my_random_value: 随机值
  NX: key不存在时设置成功,key存在时则设置不成功
  PX: 自动失效时间,出现异常情况,锁可以过期失效
redis分布式锁实现
  实现AutoCloseable可以自动调用关闭方法(需要关闭的资源申请要写到try括号里面)
  redisson(推荐)
zookeeper分布式锁实现
  curator
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

数据库读写分离&分库分表

数据切分

先垂直后水平
垂直切分
    不同业务之间,禁止跨库join联查
    跨库事务难以处理
水平切分
    将一张表的数据按照某种规则分到不同的数据库中
    拆分规则很难抽象
    分片事务一致性难以解决
    二次扩展时,数据迁移,维护难度大
两种模式
    中间层代理
    客户端模式
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

环境搭建

mycat(服务端代理)
  centos7
  yum安装mysql数据库
  下载安装mycat,修改配置文件
  
  
      rule属性: 定义分片表的分片规则,必须与中的tableRule对应
mysql主从同步配置
  主配置log-bin,指定文件的名字
  主配置server-id,默认为1
  从配置server-id,与主不能重复
  主创建备份账户并授权 REPLICATION SLAVE
      create user 'repl' identified by '123456';
      grant replication slave on *.* to 'repl'@%;
      flush privileges;
  主进行锁表 FLUSH TABLES WITH READ LOCK;
  主找到log-bin的位置 SHOW MASTER STATUS;
  主备份数据(新开窗口): mysqldump --all-databases --master-data >  -uroot -p
  主进行解锁 UNLOCK TABLES;
  从导入dump数据(scp传输)
      mysql <  -uroot -p
  在从上配置主的配置
    CHANGE MASTER TO
    MASTER_HOST='master_host_name',
    MASTER_USER='replication_user_name',
    MASTER_PASSWORD='replication_password',
    MASTER_LOG_FILE='recorded_log_file_name',
    MASTER_LOG_POS='recorded_log_position';
  START SLAVE;
全局表(type=global): 冗余信息
子表: childTable/name/joinKey
mycat-haproxy
  yum -y install haproxy.x86_64
  /etc/haproxy/
      defaluts -> mode -> tcp
      backend app -> server app1 / app2
  启动: haproxy -f /etc/haproxy/
mycat-keepalived
  # vrrp_strict
  real_server 设置成haproxy的ip和端口
  添加 vrrp_script chk_haproxy {
      script "killall -0 haproxy"
      interval 2
  }
  vrrp_instance_VI_1 {
    virtual_ipaddress {
      定义相同网段ip
    }
    track_script {
      chk_haproxy
    }
  }
  启动: keepalived -f /etc/keepalived/
商品订单表根据userId进行分片
  注意点: 父子表要先写入父表,分片的列不能更新

sharding-jdbc(客户端代理模式)
  sharding-jdbc支持springboot和xml配置两种模式
  mycat不支持同一库内的水平切分,sharding-jdbc支持
  绑定表(子表),主表的id要和子表的一致(源码中的广播表(主表)和绑定规则表判断有问题)
  可以配置读写分离,读不能从写库里面读
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61

分布式全局 id

问题: 两个分片表中存在相同的order_id, 导致业务混乱
UUID(32位): mycat不支持,sharding-jdbc支持,有可能重复
统一id序列:
  优点: id集中管理,避免重复
  缺点: 并发量大时,id生成器压力较大
  mycat进行配置
雪花算法:
  基本保持全局唯一,毫秒内并发最大4096个id
  时间回调,可能引起id重复
  mycat和sharding-jdbc都支持雪花算法
  sharding-jdbc可设置最大容忍回调时间
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

分布式事务

xa协议的两阶段提交
  mysql5.7以上/mysql connector/J 5.0 以上支持xa协议
  java系统中,数据源采用的时Atomikos
  mycat(可配置handleDistributedTransations)和sharding-jdbc都直接支持xa协议
事务补偿机制(不推荐)
  优点: 逻辑清晰,流程简单
  缺点: 数据一致性差,可能出错的点比较多,属于应用层补充,需要编写大量代码
基于本地消息表的最终一致方案
  消息表+定时任务
  适用于公司外部系统(第三方对接)
基于MQ消息队列的最终一致方案
  不依赖定时任务,基于mq更高效可靠
  适用于公司内的系统
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

分布式接口幂等性

Delete操作的幂等性
  根据唯一业务号去删除(天然幂等),可以先查询出来判断后再删除
  删除操作没有唯一业务号,看具体的业务需求
update操作的幂等性
  后台使用版本号作为更新条件(乐观锁+行锁)
  页面查询出version字段传递到后端
insert操作的幂等性
  有唯一业务号的insert操作(分布式锁)
  没有唯一业务号的insert操作(token机制)
  混合操作(token机制)
前后端分离的项目使用sessionid+redis+RLock
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

分布式限流

guava rate-limit(单机版,不操作数据库,cpu计算场景)
  非阻塞式(tryAcquire)
  阻塞式(acquire)
基于nginx的分分布式限流
  ip限制(修改配置)
  连接数限制
redis + lua
  idea安装emmylua
  redis预加载lua脚本
  限流组件(编写annotation)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

微服务

业务拆分思考维度
    流量压力维度
    业务维度
指导方针
    隔离业务场景
    剥离高频接口
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

eureka

分布式系统 cap 定理
eureka server/client/consumer服务
eureka 注册发现源码阅读
eureka 心跳和服务续约
eureka 主从(相互指定,客户端注册主从地址)
  • 1
  • 2
  • 3
  • 4
  • 5

ribbon/feign

hystrix

hystrix实现timeout降级(配置+代码)
    @GetMapping("/xxx")
    @HystrixCommand(
        fallbackMethod = "timeoutFallback", // 超时降级的方法(函数)
        commandProperties = {
          @HystrixProperty(name="", value="3000")
        }
    )
hystrix实现requestCache降级
    @CacheResult
    @CacheKey
    @HystrixCommand(commandKey="cacheKey") // 配置文件 =2000
    =true
hystrix多降级方案
    @HystrixCommand(fallbackMethod="fallback2")
turbine
spring-cloud-starter-netflix-hystrix-dashboard
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

config

静态配置
  环境配置
    数据库连接
    注册中心配置
    消息队列配置
  安全配置
    连接密码
    公钥私钥
    http连接cert
动态配置
  功能控制
    功能开关
    人工熔断开关
    蓝绿发布
    数据源切换
  业务规则
    当日外汇利率
    规则引擎参数
  应用参数
    网关黑白名单
    缓存过期时间
    日志mdc设置
其他用法
  环境隔离
  业务开关+定向推送
  修改业务逻辑
    网站黑名单
    费率: 规则引擎
    熔断阈值
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

bus/gateway

bus: 批量更新配置中心配置
getway: GatewayFilter / GlobalFilter
    创建网关和路由规则
    配置网关层redis
    添加网关层跨域
    基于jwt实现用户鉴权
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

sleuth/stream

链路追踪的基本功能
    分布式环境下链路追踪
    timing信息(时间)
    信息收集和展示
    定位链路
sleuth 集成 elk
    logback 添加 appender
stream (消息驱动)
    跨系统异步通信
    应用解耦
    流量削峰
    场景(stream批量强制用户退出 / 延时队列处理超时订单)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

sentinel

apollo 第三方授权
apolloOpenAPI
apollo + sentinel 集成
  • 1
  • 2
  • 3

dubbo

生产者,消费者,zookeeper
  • 1

容器与容器编排技术

微服务落地需求
  环境需求差异大: CPU 业务型, GPU计算型, 高吞吐I/O型
  服务敏捷要求高: 成百上千, 快速启动,优雅停止
  组织架构变化: 产品导向, devOps文化,团队微小化

安装
  curl -fsSL  -o 
  sh  --mirror Aliyun
DockerFile 编写和打包
CloudFoundry 开源PaaS云平台
  /products/pcfdev
  /cloudfoundry-incubator/cfdev
mesos + marathon
  环境快速搭建: /mesosphere/playa-mesos
  制作镜像,页面管理镜像启动容器
kubernetes
  环境搭建
    /docs/setup/production-environment/tools/kubeadm/install-kubeadm/
  滚动升级
    更新镜像+kubectl apply
    查询: kubectl rollout history deployment
    回滚: kubectl roll undo deployment
  pod 访问方式
    clusterIp service(内部)
    nodeport service(由内而外)
    loadbalance service(外部)
    ingress(外部)
  故障排除思路:
    pod 状态异常:
      pod status: init:CrashLoopBackOff
        kubectl get pod
      关闭SELinux
        /etc/selinux/config: SELINUX=disable 或者 setenforce 0
    pod 状态异常, 但无法解析DNS:
      iptables -P FORWARD ACCEPT
      kubectl get svc kube-dns --namespace=kube-system
      kubectl get eq kube-dns -- namespace=kube-system
    DNS正常,服务不可达:
      kubectl get endpoints <service-name>
      pod和service的label不一致
      service的容器端口错误
    kubernetes api 不可达:
      kubectl get service kubernetes
      kubectl get endpoints kubernetes
      rbac
    volume 卷
      emptyDir (临时目录)
      hostPath (物理服务器路径)
      sotrage provider (云服务提供商提供)
      pv / pvc (持久层卷)
    configmap & secret
      yaml
    认证 + 授权
      普通用户
      服务账号 service account
      客户端证书 / 静态密码文件 basic auth / token (推荐)
      cat /etc/kubernetes/manifests/
    集群监控和管理
      helm
        curl /kubernetes/helm/master/scripts/get | bash
        helm init
        kubectl create namespace monitoring
        helm install --name prometheus-operator --set rbacEnable=true --namespace=monitoring stable/prometheus-operator
      kubernetes + fluentd + elasticsearch + kibana
        /kubernetes/kubernetes/tree/master/cluster/addons/fluentd-elasticsearch
      集群规模变大
        命令超时: apiserver + etcd的锅
        响应变慢: dns解析变慢,容器启动变慢
        解决办法:
          修改硬件配置,采用ssd固态硬盘
          修改etcd软件分片设置,--etcd-servers-overrides
          检查资源分配,kube-dns跨node均衡,网络协议栈选型
      高可用
        独立的etcd集群(推荐)
        多master
        apiserver无状态
        外部负载均衡(推荐)
        内部进行负载均衡或反向代理
        kube-dns (replica / anti-affinity)
      cicd
        jenkins
          kubernetes plugin
      微服务扩容
        网络引流
        系统资源
        基础设施
      有状态到无状态
        数据库服务
        减少文件系统依赖
        集群管理
        资源预配置
      cloud foundry 资源触发
        app autoscaler服务
        cpu / 内存 扩容
        定制化metric: prometheus, metric register
        简单策略
          定义伸缩上下限
          定义伸缩规则
          定制时间规则
      marathon autoscale controller
        /mesosphere/marathon-lb-autoscale
        mesosphere/marathon-lb-autoscale
        关联marathon和haproxy
        制定目标rps(每个实例每秒请求数)规则
        curl -i -H 'Content-type: application/json' 10.141.141.10:8080/v2/apps -d @
      kubernetes 弹性扩缩容
        HPA / VPA / cronHPA / cluster-autoscaler
        Istio / knative
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108

高性能网络通信-Netty

应用场景下选择rpc, http, mq, netty
rpc: 系统间即使访问,同步服务调用
http: 外部接口api提供,非高并发场景,非大数据报文传输
mq: 微服务之间解耦,流量削峰
netty: 底层基础通信,数据传输,数据同步

netty tcp拆包粘包问题的处理
  产生原因:
    应用程序write写入的字节大小大于套接口发送缓冲区的大小
    进行mss大小的tcp分段,以太网帧的payload大于mtu进行ip分片等
  解决方案
    消息定长,例如每个报文的大小固定为200个字节,如果不够,空位补空格
    在包尾部增加特殊字符进行分割,如回车
    消息分为消息头和消息体,在消息头中包含表示消息总长度的字段,然后进行业务处理
netty 序列化实战marshalling
netty 序列化实战protobuf
netty 自定义协议栈实战
netty最佳实战
  项目整体业务与技术实现
    数据通信要求实时性高且性能高,异构系统
    需要保障不同的业务对应不同的实现
    支持负载均衡策略,故障切换
    需要可靠性保障的支持,数据不允许丢失
    netty + springboot + zookeeper
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

应用监控与调优(工具篇)

性能调优目标概述与四板斧
    借助监控预防问题,发现问题
    借助工具定位问题
    定期复盘,防止同类问题再现
    定好规范,一定程度上规避问题

应用性能调优
    skywalking
        /apache/skywalking/tree/v6.6.0/docs
        /books/skywalking
        java agent 插件 (加载plugin目录下的插件)
            内置插件
            引导插件
            可选插件
        扩展插件(/SkyAPM/java-plugin-extensions)
        开源协议
            Apache
            GPL
            LGPL
            BSD
            MIT
        插件实战
            监控springbean
                apm-spring-annotation-plugin
            监控任意代码
                apm-customize-enhance-plugin
    springboot监控神器
        spring boot actuator
        spring boot admin
            /codecentric/spring-boot-admin
            /spring-boot-admin/2.1.6
            集成client模式
            服务发现模式
    prometheus+grafana
    javamelody
        /javamelody/javamelody
        /javamelody/javamelody/wiki/SpringBootStarter
        /javamelody/javamelody/wiki/UserGuide#2-webxml-file
        /itmuch/platform
    tomcatmanager(只支持传统war应用)
        /project/tomcat/
    PSIProbe
        /psi-probe/psi-probe
        /psi-probe/psi-probe/wiki
        /psi-probe
        /qlqwjy/p/
    技巧与实战篇
        对象池: 复用对象
          commons-pool2
              ObjectPool: GenericObjectPool / GenericKeyedObjectPool
              Factory: 创建和管理(PooledObject),一般要自己扩展
              PooledObject: 包装原有的对象,从而让对象池管理,一般用DefaultPooledObject
        线程池: 复用线程
            /hit100410628/article/details/72934353
            new ThreadPoolExecutor(
              corePoolSize 5,
              maximumPoolSize 10,
              keepAliveTime,
              ,
              new LinkedBlockingQueue<>(100),
              (),
              new ()
            );
            BlockingQueue详解,选择与调优
                ArrayBlockingQueue
                LinkedTransferQueue
                DelayQueue
                LinkedBlockingQueue
                PriorityBlockingQueue
                SynchronousQueue
                合理设置corePoolSize,maximumPoolSize,workQueue的容量
            ScheduledThreadPoolExecutor
                scheduleAtFixedRate
                scheduleWithFixedDelay
                /diaorenxiang/article/details/38827409
            FockJoinPool
                异常需要自己抛出
            Executors
                创建线程池的工厂以及工具
                new CachedThreadPool()
                    缓存型线程池,会先查看池中是否有以前建立的线程,有就复用,没有就新建
                    适用于生存周期很短的异步任务
                new FixedThreadPool()
                    固定线程池,任意时间最多只有固定数目的活动线程存在
                    适用于线程数比较稳定的并发线程场景
                new SingleThreadExecutor()
                    任意时间池中只有一个线程,保证任务按照指定顺序执行
                    适用于需要严格控制执行顺序的场景
                new ScheduledThreadPool()
                    创建一个有调度能力的线程池
                    适用于定时任务,延时任务
                new WorkStealingPool()
                    创建一个ForkJoinPool
                    适用于分而治之,递归计算的CPU密集场景
            线程池调优
                线程数调优
                    cpu密集型: n + 1
                    io密集型: 2n
                    估算的经验公式: n * u * (1+wt/st)
                      n: cpu核心数
                      u: 目标cpu利用率
                      wt: 线程等待时间
                      st: 线程运行时间
                blockingQueue调优
                  /2012/03/
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
/**
 * A class that calculates the optimal thread pool boundaries. It takes the desired target utilization and the desired
 * work queue memory consumption as input and retuns thread count and work queue capacity.
 *
 * @author Niklas Schlimm
 *
 */
public abstract class PoolSizeCalculator {
  /**
   * The sample queue size to calculate the size of a single {@link Runnable} element.
   */
  private final int SAMPLE_QUEUE_SIZE = 1000;

  /**
   * Accuracy of test run. It must finish within 20ms of the testTime otherwise we retry the test. This could be
   * configurable.
   */
  private final int EPSYLON = 20;

  /**
   * Control variable for the CPU time investigation.
   */
  private volatile boolean expired;

  /**
   * Time (millis) of the test run in the CPU time calculation.
   */
  private final long testtime = 3000;

  /**
   * Calculates the boundaries of a thread pool for a given {@link Runnable}.
   *
   * @param targetUtilization
   *            the desired utilization of the CPUs (0 <= targetUtilization <= 1)
   * @param targetQueueSizeBytes
   *            the desired maximum work queue size of the thread pool (bytes)
   */
  protected void calculateBoundaries(
    BigDecimal targetUtilization,
    BigDecimal targetQueueSizeBytes
  ) {
    calculateOptimalCapacity(targetQueueSizeBytes);
    Runnable task = creatTask();
    start(task);
    start(task); // warm up phase
    long cputime = getCurrentThreadCPUTime();
    start(task); // test intervall
    cputime = getCurrentThreadCPUTime() - cputime;
    long waittime = (testtime * 1000000) - cputime;
    calculateOptimalThreadCount(cputime, waittime, targetUtilization);
  }

  private void calculateOptimalCapacity(BigDecimal targetQueueSizeBytes) {
    long mem = calculateMemoryUsage();
    BigDecimal queueCapacity = targetQueueSizeBytes.divide(
      new BigDecimal(mem),
      RoundingMode.HALF_UP
    );
    System.out.println(
      "Target queue memory usage (bytes): " + targetQueueSizeBytes
    );
    System.out.println(
      "createTask() produced " +
      creatTask().getClass().getName() +
      " which took " +
      mem +
      " bytes in a queue"
    );
    System.out.println("Formula: " + targetQueueSizeBytes + " / " + mem);
    System.out.println(
      "* Recommended queue capacity (bytes): " + queueCapacity
    );
  }

  /**
   * Brian Goetz' optimal thread count formula, see 'Java Concurrency in Practice' (chapter 8.2)
   *
   * @param cpu
   *            cpu time consumed by considered task
   * @param wait
   *            wait time of considered task
   * @param targetUtilization
   *            target utilization of the system
   */
  private void calculateOptimalThreadCount(
    long cpu,
    long wait,
    BigDecimal targetUtilization
  ) {
    BigDecimal waitTime = new BigDecimal(wait);
    BigDecimal computeTime = new BigDecimal(cpu);
    BigDecimal numberOfCPU = new BigDecimal(
      Runtime.getRuntime().availableProcessors()
    );
    BigDecimal optimalthreadcount = numberOfCPU
      .multiply(targetUtilization)
      .multiply(
        new BigDecimal(1)
        .add(waitTime.divide(computeTime, RoundingMode.HALF_UP))
      );
    System.out.println("Number of CPU: " + numberOfCPU);
    System.out.println("Target utilization: " + targetUtilization);
    System.out.println("Elapsed time (nanos): " + (testtime * 1000000));
    System.out.println("Compute time (nanos): " + cpu);
    System.out.println("Wait time (nanos): " + wait);
    System.out.println(
      "Formula: " +
      numberOfCPU +
      " * " +
      targetUtilization +
      " * (1 + " +
      waitTime +
      " / " +
      computeTime +
      ")"
    );
    System.out.println("* Optimal thread count: " + optimalthreadcount);
  }

  /**
   * Runs the {@link Runnable} over a period defined in {@link #testtime}. Based on Heinz Kabbutz' ideas
   * (/archive/).
   *
   * @param task
   *            the runnable under investigation
   */
  public void start(Runnable task) {
    long start = 0;
    int runs = 0;
    do {
      if (++runs > 5) {
        throw new IllegalStateException("Test not accurate");
      }
      expired = false;
      start = System.currentTimeMillis();
      Timer timer = new Timer();
      timer.schedule(
        new TimerTask() {

          public void run() {
            expired = true;
          }
        },
        testtime
      );
      while (!expired) {
        task.run();
      }
      start = System.currentTimeMillis() - start;
      timer.cancel();
    } while (Math.abs(start - testtime) > EPSYLON);
    collectGarbage(3);
  }

  private void collectGarbage(int times) {
    for (int i = 0; i < times; i++) {
      System.gc();
      try {
        Thread.sleep(10);
      } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
        break;
      }
    }
  }

  /**
   * Calculates the memory usage of a single element in a work queue. Based on Heinz Kabbutz' ideas
   * (/archive/).
   *
   * @return memory usage of a single {@link Runnable} element in the thread pools work queue
   */
  public long calculateMemoryUsage() {
    BlockingQueue<Runnable> queue = createWorkQueue();
    for (int i = 0; i < SAMPLE_QUEUE_SIZE; i++) {
      queue.add(creatTask());
    }
    long mem0 =
      Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
    long mem1 =
      Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
    queue = null;
    collectGarbage(15);
    mem0 =
      Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
    queue = createWorkQueue();
    for (int i = 0; i < SAMPLE_QUEUE_SIZE; i++) {
      queue.add(creatTask());
    }
    collectGarbage(15);
    mem1 =
      Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
    return (mem1 - mem0) / SAMPLE_QUEUE_SIZE;
  }

  /**
   * Create your runnable task here.
   *
   * @return an instance of your runnable task under investigation
   */
  protected abstract Runnable creatTask();

  /**
   * Return an instance of the queue used in the thread pool.
   *
   * @return queue instance
   */
  protected abstract BlockingQueue<Runnable> createWorkQueue();

  /**
   * Calculate current cpu time. Various frameworks may be used here, depending on the operating system in use. (.
   * /products/sigar). The more accurate the CPU time measurement, the more accurate the results
   * for thread count boundaries.
   *
   * @return current cpu time of current thread
   */
  protected abstract long getCurrentThreadCPUTime();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
连接池: 复用连接
    数据库连接池
        Hikari
        Tomcat
        DBCP2
        Alibaba Druid
    redis连接池
        jedis
        lettuce
        redisson
    http连接池
        apache httpclient
        okhttp
    连接池调优
        连接数 = 2n + 可用磁盘数
        分离: 2个连接池(分别处理实时和慢sql)
异步化
    本地异步化
        创建线程
        线程池
        @Async
            @Async注解标注的方法必须返回void或Future
            建议将@Async标注的方法放到独立的类中去
            建议自定义BlockingQueue的大小
        生产者消费者
    远程异步化
        AsyncRestTemplate
        WebClient
            /spring-framework/reference/web/
锁优化
    synchronized
        修饰实例方法: 给当前实例加锁,进入同步代码前要获得当前实例的锁
        修饰静态方法: 给当前类加锁,进入同步代码前要获得指定类对象的锁
        修饰代码块: 给指定对象加锁,进入同步代码前要获得指定对象的锁
    synchronized原理
        对象存储对象头
            Mark word (无锁/偏向锁/轻量级锁/重量级锁/GC标记)
            类型指针
            数组长度
        锁的适用场景
            偏向锁:
            轻量级锁:
            重量级锁:
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

jvm 调优

理论篇
    内存结构
    堆
    虚拟机栈
    栈帧
    局部变量表
    操作数栈
        指向运行时常量池引用
        方法返回地址
        动态链接
        本地方法栈(native c 语言实现)
        程序计数器
    方法区
      类信息
      运行时常量池
    类加载机制
        javac
        javap
        -Xverify:none

    编译器优化
    解释执行
        优势在于没有编译的等待时间
        性能相对差一些
    编译执行
        运行效率会高很多,一般认为比解释执行快一个数量级
        带来了额外的开销

    模式切换
        java -version
        -Xint 设置 jvm 的执行模式为解释模式
        -Xcomp JVM 优先以编译模式运行,不能编译的,以解释模式运行
        -Xmixed 混合模式运行(默认模式)
    C1
        是一个简单快速的编译器
        主要关注局部性的优化
        适用于执行时间较短或对启动性能有要求的程序.例如,gui 应用对界面启动速度就有一定要求
        称为 client compiler
        只想开启 c1: -XX:+TieredCompilation -XX:TieredStopAtLevel=1
    C2
        是为长期运行的服务器端应用程序做性能调优的编译器
        适用于执行时间较长或对峰值性能有要求的程序
        称为 server compiler (springboot)
        只想开启 c2: -XX:-TieredCompilation
    分层编译
        级别越高,应用启动越慢,优化的开销越高,峰值性能也越高
    热点代码
        基于采样的热点探测
        基于计数器的热点探测
            -XX:CompileThreshold=X 指定方法调用计数器阈值(关闭分层编译时才有效)
            -XX:OnStackReplacePercetage=X 指定回边计数器阈值(关闭分层编译时才有效)
            -XX:-UseCounterDecay 关闭方法调用计数器热度衰减
            -XX:CounterHalfLifeTime 指定方法调用计数器半衰周期(秒)
    方法内联
        -XX:+PrintInlining 打印内联详情,该参数需和-XX:+UnlockDiagnosticVMOptions 配合适用
        -XX:+UnlockDiagnosticVMOptions 打印 JVM 诊断相关的信息
        -XX:MaxInlineSize=n 默认 35 如果非热点方法的字节码超过该值,则无法内联,单位字节
        -XX:FreqInlineSize=n 默认 325 如果热点方法的字节码超过该值,则无法内联,单位字节
        -XX:InlineSmallCode=n 默认 1000 目标编译后生成的机器码代销大于该值则无法内联,单位字节
        -XX:MaxInlineLevel=n 默认 9 内联方法的最大调用帧数(嵌套调用的最大内联深度)
        -XX:MaxTrivialSize=n 默认 6 如果方法的字节码少于该值,则直接内联,单位字节
        -XX:MinInliningThreshold=n 默认 250 如果目标方法的调用次数低于该值,则不去内联
        -XX:LiveNodeCountInliningCutoff=n 默认 40000 编译过程中最大活动节点数的上限,仅对 c2 编译器有效
        -XX:InlineFrequencyCount=n 默认 100 如果方法的调用点的执行次数超过该值,则触发内联
        -XX:MaxRecursiveInlineLevel=n 默认 1 递归调用大于 n 就不内联
        -XX:+InlineSynchronizedMethods 默认开启 是否允许内联同步方法
    逃逸分析,标量替换,栈上分配
        作用域逃逸
        赋值给类变量或可以在其他线程中访问的实例变量
        标量替换
        栈上分配
            -XX:+DoEscapeAnalysis jdk8 默认开启 是否开启逃逸分析
            -XX:+DliminateAllocations jdk8 默认开启 是否开启标量替换
            -XX:+EliminateLocks jdk8 默认开启 是否开启锁消除
    垃圾收集算法
        什么场景下该使用什么垃圾回收策略
            内存
            cpu
    垃圾回收发生在哪些区域
        堆
        方法区
    对象在什么时候能够被回收
        程序计数器
        可达性分析
        根对象
            虚拟机栈中引用的对象
            方法区中类静态属性引用的对象
            方法区中常量引用的对象
            本地方法栈中 jni 引用的对象
    引用
        强引用
        弱引用
        虚引用
    基础垃圾回收算法
        标记-清除,标记-整理,复制
    综合垃圾回收算法
        分代收集算法
            新对象不一定分配到 eden
            对象大于-XX:PretenureSizeThreshold,就会直接分配到老年代
            新生代空间不够
            触发新生代(minirGC)垃圾回收条件
                eden 空间不足
            触发老年代(fullGC)垃圾回收的条件
                老年代空间不足
                元空间不足
                要晋升到老年代的对象所占用的空间大于老年代的剩余空间
                显示调用 ()
            分代收集算法调优原则
                合理设置 survivor 区域的大小,避免内存浪费
                让 gc 尽量发生在新生代,尽量减少 fullgc 的发生
                相关参数
                    -XX:NewRatio=n 老年代:新生代内存大小比值
                    -XX:SurivorRatio=n suvivor 区内存大小比值
                    -XX:PretenureSizeThreshold=n 对象大于改值就在老年代分配,0 表示不限制
                    -Xms 最小堆内存
                    -Xmx 最大堆内存
                    -Xmn 新生代太小
                    -XX:+DisableExplicitGC 忽略 ()的调用
                    -XX:NewSize=n 新生代初始内存大小
                    -XX:MaxNewSize=n 新生代最大内存
          增量算法
      垃圾收集器
          Serial
              单线程,简单高效,需要 stop the world
              命令行执行或嵌入式场景
          Serial Old
              采用标记-整理算法
          Parnew
              serial 收集器的多线程版
              可使用-XX:ParallelGCThreads 设置垃圾收集的线程数
          parallel Scavenge
              可以达到一个可控制的吞吐量
              使用-XX:+UseAdptiveSizePolicy 打开自适应 GC 策略
          parallel Old
              采用标记-整理算法
          CMS(jdk9 开始不使用)
              采用标记-整理算法
              并发收集器
              系统停顿时间短,响应速度快的场景,比较适合 web 服务
          G1
              面向服务器端应用的垃圾收集器
              占用内存较大的应用(6G 以上)
              /hotspot_options_jdk8.html
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143

工具篇

监控类工具
    jps(查看 jvm 进程状态 -q/-m/-l)
    jstat(监控 jvm 的各种运行状态)

故障排查工具
    jinfo(查看与调整 jvm 参数)
    jmap(用来展示对象内存映射或堆内存详细信息)
    使用-XX:+HeapDumpOnOutOfMemoryError, 让虚拟机在 OOM 异常出现后自动生成堆 Dump 文件
    对于 springboot 应用,也可以使用 springboot actuator 提供的/actuator/heapdump 实现堆 Dump
    jstack(线程长时间等待, 打印当前虚拟机的线程快照)
    jcmd(将诊断命令请求发送到正在运行的虚拟机)
    jhat(用来分析 jmap 生成的堆 Dump, jdk11 废弃)
    jhsdb(jdk9 才有,jdk8 需要启动 )
可视化工具
    jhsdb
    jhsdb hsdb --pid xxx
    jconsole
    VisualVM(生产环境不适应 profiler)
    Java Mission Control
    < jdk11 添加如下参数
        -XX:+UnlockCommercialFeatures -XX:+FlightRecorder
远程连接工具
    jstatd
        创建安全策略文件 
        grant codebase "file:\${}/../lib/" {
        permission ;
        }
        开放防火墙 jstatd 的端口
    jmx
        匿名访问
            java -= \
            -=1232 \
            -=1240 \
            -=false \
            -=false \
            -jar 
        开启认证
            
            admin readwrite
            
            admin admin123
            上面文件权限设置成 600
            java -= \
            -=1232 \
            -=1240 \
            -=true \
            -=./ \
            -=./ \
            -=false \
            -jar 
        开启 ssl
        生成 keystore
            keytool -genkeypair \
            -alias visualvm \
            -keyalg RSA \
            -validity 365 \
            -storetype pkcs12 \
            -keystore  \
            -storepass visualvm-key \
            -keypass visualvm-key \
            -dname "CN-damu, OU=itmuch, O=, L=NanJing, S=JiangSu, C=CN"
        导出 visualvm 的 cert
            keytool -exportcert \
            -alias visualvm \
            -storetype pkcs12 \
            -keystore  \
            -file  \
            -storepass visualvm-key \
        把 visualvm 的 cert 导入到 java-app 的 truststore 里,生成一个 turestore
            keytool -importcert \
            -alias visualvm \
            -file  \
            -keystore  \
            -storepass java-app-trust \
            -norompt
        生产 java-app 的 keystore
            keytool -genkeypair \
            -alias java-app \
            -keyalg RSA \
            -validity 365 \
            -storetype pkcs12 \
            -keystore  \
            -storepass java-app-key \
            -keypass java-app-key \
            -dname "CN-damu, OU=itmuch, O=, L=NanJing, S=JiangSu, C=CN"
        导出 java-app 的 cert
            keytool -exportcert \
            -alias java-app \
            -storetype pkcs12 \
            -keystore  \
            -file  \
            -storepass java-app-key \
        把 java-app 的 cert 导入到 visualvm 的 truststore 里,生成一个 turestore
            keytool -importcert \
            -alias java-app \
            -file  \
            -keystore  \
            -storepass visualvm-trust \
            -norompt
        启动
            java -= \
            -=1232 \
            -=1240 \
            -=true \
            -=./ \
            -=./ \
            -=/root/test/ \
            -=java-app-key \
            -=/root/test/ \
            -=java-app-trust \
            -jar 
        本地获取  和  后启动 visualvm
            ssh
                java -= \
                -=1232 \
                -=1240 \
                -=false \
                -=false \
                -jar 
                ssh -v -D 9696 root@
                启动 visualvm
                    network -> proxySettings -> SOCKS Proxy
                    file -> add JMX connection
第三方工具
    Memory Analyzer(MAT)
    主要功能
        找出内存泄露的原因
        找出重复引用的类和 jar
        分析集合的使用
        分析类加载器
    JITWatch
    HSDIS
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132

实战篇

jvm 日志
    java8 垃圾收集日志打印参数
          -Xms50m -Xmx50m -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps -XX:+PrintGCCause
    java8 运行时日志打印参数
        -XX:+TraceClassLoading -XX:+PrintGCTraceBiasedLocking
    java11
        -Xlog 统一设置
            -Xms50m -Xmx50m -Xlog:gc*=trace:file=/Users/xxx/
            -Xlog:class+load=debug,biasedlocking=debug:file=/Users/xxx/
cpu 过高问题定位
    top + jstack
    top
    top -Hp 36032 查看线程
    printf %x 36044
    8ccc
    jstack 36032 >  | grep -A 30 8ccc
可能导致 cpu 占用率过高的场景与解决方案
    频繁创建对象
    合理使用单例
    序列化和反序列化
    使用合适的类库
    正则表达式
        减少字符匹配期间执行的回溯
        /ityouknow/article/details/80851338
    频繁的线程上下文切换
    降低切换的频率
堆内存溢出问题
    堆内存溢出的场景与解决方案
        内存泄露
            -Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
            mat
            visual vm
非内存泄露
    存储结构问题
    参数校验(分页 pageSize 加判断@Max(100))
栈内存溢出问题
    系统支持的最大线程数,表示物理内存决定的理论系统进程数上限
        cat /proc/sys/kernel/threads-max
        修改 sysctl -w -max=7726
    查看系统限制某用户下最多可以运行多少进程或线程
        cat /proc/sys/kernel/threads-max
        修改 sysctl -w kernel.pid_max=65535
    限制一个进程可以拥有虚拟内存域的数量
        修改 sysctl -w vm.max_map_count=262144
    查看用户最多可启动的进程数目
        ulimit -u
        修改 ulimit -u 65535
    如何运行更多线程
        减少 Xss 配置
        增加栈能分配的内存
        杀死其他程序
        加大操作系统对线程数目的限制
方法区溢出
    不同 jdk 版本,方法区存放结构不同,相同的代码报错也可能不同
    常量池里对象太大
    加载的类的种类太多
    动态代理的操作库生成了大量的动态代理类
    Metaspace 泄露排查
    /article/603830
    如何避免
        根据 jdk 版本,为常量池保留足够空间
        防止类加载过多导致的溢出
            jdk<=7 设大 PermSize, MaxPermSize
            jdk>=8 留空元空间相关的配置,或设置合理大小的元空间
直接内存溢出
    直接内存可以使用 Unsafe 或 ByteBuffer 分配
    用-XX:MaxDirectMemorySize 控制,默认是 0,表示不限制
    有很大的数据需要存储,生命周期长
    频繁的 IO 操作,比如并发网络通信
    堆 Dump 文件看不出问题或者比较小,可考虑直接内存溢出问题
    溢出时报 , -XX:MaxDirectMemorySize 不起作用
    溢出报 : Direct buffer memory, -XX:MaxDirectMemorySize 起作用
代码缓存区
    -XX:ReservedCodeCacheSize=3000k
    设置合理的代码缓存区大小
    太小可能会突然出现性能下降,但是业务没有问题
分析 GC 日志
    jdk8
        年轻代日志
        时间戳
        垃圾收集器显示标志
        年轻代回收前后(总)
        堆回收前后(总)
        full 日志
        G1
    jdk11
        GC 日志可视化分析工具
        GCeasy
        GCViewer
定位并解决项目越来越慢的问题
    stop the world 过长
    项目依赖的资源导致过慢
    数据库
    网络
    code cache 满了
    线程争抢
    服务器问题
    操作系统问题
    其他进程争抢资源
    
    
    
    tlab(线程私有分配缓存区)
    加速对象分配,占用 eden 空间
jvm 参数与相关工具整理
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106

数据库调优

慢查询发现与分析
    数据库调优的维度
        调优金字塔
            业务需求>架构调优>数据库>系统和硬件
    /datacharmer/test_db
    慢查询日志
        修改 
            slow_query_log=ON
            log_output 日志输出到哪里,默认是 file,设置成 table,则将日志记录到 mysql.show_log 中
            long_query_time 执行时间超过多久才记录到慢查询日志,一般设置成 500ms
            log_queries_not_using_indexes 记录没有索引的查询,开发环境打开,生产环境关闭
            service mysqld restart
        分析慢查询日志文件
            mysqldumpslow
            mysqldumpslow -s t -t 10 -a 
            pt-query-digest
        explian
            id 相同依次执行,id 不同大的先执行
            explain format = tree / json xxx; show warning; (终端)
            type
                system > const > eq_ref > ref > all
            possible_keys 可能得索引选择
            key 实际选择的索引
            key_len 索引的长度
            ref 索引的那一列被引用了
            rows 估计要扫描的行
            filtered 符合查询条件的数据百分比
            extra 展示有关本次查询的附加信息
            using filesort
            using temporary
        估计查询性能
            log(row_count) / log(index_block_length / 3 * 2 / (index*length + data_pointer_length)) + 1
            比如: log(500000) / log(1024 / 3 * 2 / (3 key 字节长度 + 4)) + 1
        sql 性能分析
            show profile
                select @@have*profiling;
                select @@profiling;
                set @@profiling = 1;
                set profiling_history_size = 100;
                show profiles; 得到 query_id
                show profile for query query_id;
                set profiling = 0
            information_schema
                set @@profiling = 1;
                show profiles; 得到 query_id
                select state,format(duration,6) as duration from information_schema.profiling where query_id = query_id order by seq
            performance_schema
                select \* from performance_schema.setup_actors

                update performance_schema.setup_actors set enabled = 'NO', history = 'NO' where host = '%' and user = '%'

                insert into performance_schema.setup_actors(HOST,USER,ROLE,ENABLED,HISTORY) values('localhost','test_user','%','YES','YES')

                update performance_schema.setup_instruments set enabled = 'YES', timed = 'YES' where name like '%statement/%';
                update performance_schema.setup_instruments set enabled = 'YES', timed = 'YES' where name like '%stage/%';
                update performance_schema.setup_instruments set enabled = 'YES', where name like '%events_statements_/%';
                update performance_schema.setup_instruments set enabled = 'YES', where name like '%events_stages_/%';
                select * from  where emp_no = 1001;
                select event_id,truncate(timer_wait/1000000000000, 6) as duration, sql_text from performance_schema.events_statements_history_long where sql_text like '%10001%';
                select event_name as stage,truncate(timer_wait/100000000000, 6) as duration from performance_schema.events_stages_history_long where nesting_event_id = 31;
            optimizer_trace
              set optimizer_trace='enabled=on', end_markers_in_json=on;
              set optimizer_trace_offset=-30,optimizer_trace_limit=30;
              执行查询语句
              select * from information_schema.optimizer_trace limit 30 \G;
              查看对应的trace
            mysql数据库诊断命令
              show [full] processlist;
              show status;
              show variables;
              show index;
              show engine;
              show master status;
              show slave status;

数据库调优理论讲解
  索引数据结构
    innodb
      b+tree
      主键索引: 叶子节点存储主键及数据
      非主键索引: 叶子节点存储索引以及主键
    myisam
      b+tree
      主键/非主键索引的叶子节点存储指向数据块的指针
    hash索引
      innodb引擎支持自适应hash索引
        show variables like innodb_adaptive_hash_index
    全文索引
      ngram
  b-tree(b+tree)和hash索引特性与限制
    特性
      完全匹配
      范围匹配
      前缀匹配
    最左原则 index(name,age,sex)
      查询条件不包括最左列,无法使用索引
      跳过了索引中的列,则无法完全使用索引
      查询中有某个列的范围(模糊查询),则右边所有列都无法使用索引
    hash索引
      hash索引并不是按照索引值排序,所以没法使用排序
      不支持部分索引列匹配查找
      只支持等值查询
  创建索引的原则
    哪些场景建议创建索引
      select语句,频繁作为where条件的字段
      update/delete语句的where条件
      需要分组,排序的字段
      distinct所使用的字段
      字段的值有唯一性约束
      对于多表查询,连接字段应创建索引,且类型务必一致
    哪些场景不建议创建索引
      where子句里用不到的字段
      表的记录非常少
      有大量重复数据,选择性低
        索引的选择性越高,查询效率越好,因为在查找时可以过滤更多行
      频繁更新的字段,要考虑维护开销
  索引失效与解决方案
    可能导致索引失效的场景
      索引列不独立
      使用了左模糊
      使用OR查询的部分字段没有索引
      字符串条件未使用''引起来
      不符合最左前缀原则的查询
      索引字段建议添加not null约束
      隐式转换导致索引失效
  索引调优技巧
    长字段的索引调优
      hash函数
        字段的值应该具备的要求(完整值)
          字段的长度应该比较小,sha1/md5是不合适的
          应当尽量避免hash冲突,流行使用crc32()或者fnv64()
      前缀索引
        增加前缀索引
          alter table employees add key (first_name(7))
        完整列的选择性
          select count(distinct first_name) / count(*) from employees
        尝试的计算
          select count(district left(first_name, 5)) / count(*) from employees
      后缀索引
    使用单列索引和组合索引的技巧
      如果出现性能问题
        组合索引 > 单例索引
    覆盖索引
      select的字段只需从索引就能获得就叫覆盖索引
    冗余,重复索引的优化
      重复索引(应删除)
        在相同的列上按照相同的顺序创建的索引
      如果已经存在索引index(A,B),又创建了index(A),那么index(A)就是index(A,B)的冗余索引
      在排序阶段注意最左原则判断是否需要添加冗余索引
特定语句的原理与调优
  join
    left join
    straight_join 强制指定join的顺序
    right join
    inner join
    full outer join
    cross join 笛卡尔积
    cross join on 相当于inner join
    算法(explain 的 extra 查看)
      nlj(nested loop join)
      bnlj(block nested loop join)
        show variables like 'join_buffer_size'
        set join_buffer_size = 1024*1024*50
      bkaj(batched key access join)
      bka
        mrr
          set optimizer_switch = 'mrr_cost_based=off'
      hash join 替代bnlj (mysql 8.0.20)
    调优原则
      小表驱动大表,如果有where条件,应当要能够使用索引,并尽可能地减少外层循环
      join的字段尽量创建索引
      尽量减少扫描的行数
      参与join的表不要太多,建议不超过3张
      如果被驱动表的join字段用不了索引,且内存较为充足,可以考虑把join buffer设置的大一些
  limit 语句优化
    分页查询
      覆盖索引
        select emp_no from employees limit 300000, 10;
      覆盖索引+join
        select * from employees e inner join (select emp_no from employees limit 300000, 10) t on e.emp_no=t.emp_no;
      覆盖索引+子查询
        select * from employees where emp_no >= (select emp_no from employees limit 300000, 1) limit 10;
      范围查询+limit语句 (需要拿到上一页的主键最大值)
        select * from employees where emp_no > xxx limit 10;
      起始主键+结束主键
      禁止传入过大页码
  count语句优化
    没有非主键索引,会使用主键索引
    如果存在非主键索引的话,会使用非主键索引
    如果存在多个非主键索引,会使用一个最小的非主键索引
    count(字段)只会针对该字段统计,会排除该字段值为null的行
    count(*)不会排除
    count(*)和count(1)没有区别,innodb处理他们是用一样的操作方法
    没有特殊需求,尽量使用count(*)
    innodb mysql版本8.0.18 > 8.0.13, 可以针对无条件的count语句去优化
    换思路,总行数减去小行数
  order by语句优化
    最好利用索引避免排序
    排序字段存在组合索引可以避免排序
    排序字段存在多个索引中无法避免排序
    sort buffer 满后写到临时文件
      快速排序算法
      归并算法
    排序模式
      rowid排序
        缓存区read_rnd_buffer_size
      全字段排序
        直接取出sql中需要的所有字段,放到sort buffer
          sort_buffer_size指定sort buffer的大小
      打包字段排序
        全字段模式的优化,将字段紧密地排列在一起
      max_length_for_sort_data
        当order by sql中出现字段的总长度小于该值,使用全字段排序,否则使用rowid排序
      filesort_summary解读
        memory_available 可用内存,sort_buffer_size的值
        num_rows_found 有多少条数据参与排序,越小越好
        num_initial_chunks_spilled_to_disk 产生了几个临时文件,0表示完全基于内存排序
        sorr_mode
          <sort_key,rowid>: 使用了rowid排序模式
          <sort_key,additional_fields>: 使用了全字段排序
          <sort_key,packed_additional_fields>: 使用了打包字段排序
      如何调优filesort
        调大sort_buffer_size,减少临时文件和归并操作
          optimizer trace中num_initial_chunks_spilled_to_disk的值
          sort_merge_passes变量的值
          调大read_rnd_buffer_size,让顺序io返回的结果更多
          调小max_sort_length
  group by语句优化
    松散索引扫描
      explain的extra展示using index for group-by
      查询作用在单张表
      指定的所有字段要符合最左前缀原则
      只支持min()/max()且必须作用在同一个字段和在索引中,且紧跟group by所指定的字段
        index(c1,c2,c3) -> select c1,c2,min(c3),max(c3) from t1 group by c1,c2
      特定聚合函数用法
        avg() / sum() / count()
        查询中必须不存在group by或distinct
    紧凑索引扫描
      explain extra 没有明显标记(using index)
    临时表(避免出现)
      explain extra using temporary
    distinct
  表结构设计优化
    字段少而精
      把常用的字段放到一起
      把不常用的字段独立出去
      大字段(text/blob/clob)独立出去
    尽量用小型字段
      用数字替代字符串
      避免使用允许为NULL的字段
    合理平衡范式与冗余
    数据量非常大可以考虑分库分表
percona toolkit
  pt-query-digest
    pt-query-digest 
  pt-index-usage (非生产使用)
  pt-variable-advisor
    分析mysql变量使用是否合理
  pt-online-schema-change
sql 调优实战
  常规调优方案
    优化子查询
      分组使用松散索引扫描
    添加索引
    添加组合索引(排序字段)
    小于mysql8版本注意索引排序(出现backward index)
    调大sort_buffer_size
      set sort_buffer_size = 4 * 1024 * 1024; // 4m
  激进的调优方案
    like如果可以尽量使用右模糊,避免全模糊
    彻底使用冗余优化sql(数据冗余到其他表)
    考虑使用非关系型数据库
    业务妥协
架构调优
操作系统调优
    linux调优相关命令
        top
        ps
          ps -ef 全格式展示所有进程
          ps -au 显示较详细的信息,比如进程占用的cpu,内存等
          ps -aux 显示所有包含其他使用者的行程
        jobs
        pgrep 根据特定条件查询进程pid信息
        meminfo cat /proc/meminfo
        free
        swap
        vmstat
        df
        du
        netstat (netstat -a / netstat -t / netstat -u / netstat -antp)
        route 显示和操作路由表
        lsof (lsof -i:8080)
        linux命令大全: /s/1fcQdQLOo2fGkP5V0FaQT2Q?pwd=x98p
        系统
            /etc/os-release
            /etc/system-release
            /etc/redhat-release
            /etc/centos-release
            /etc/issue
            lsb_release -a
            uname 显示系统信息
            uptime
        用户
            id 展示用户的id以及所属群组的id
            w 展示当前登陆系统的用户信息
            who (who -a / who -H / whi -q)
            whoami 显示自身用户名称
            last
            cpuinfo (cat /proc/cpuinfo)
        其他
          sysstat / iostat / mpstat / pidstat
              /Linux/2019-08/
          htop / iotop / iftop
              /xuanbjut/p/
        zabbix
        prometheus
          下载:prometheus,alertmanager,node_exporter,mysqld_exporter
          /s/1GWQ0Y2GJAA_zbngmkAEeAw?pwd=084s
架构调优与架构设计
    结合工具,发现性能瓶颈,并去做针对性的调优
        应用层面的调优
        数据库层面的调优
        操作系统层面
        技术方案的调优
        "有损"的调优
    通过架构上的演进去解决性能瓶颈
        单体应用:CPU密集型,IO密集型
    微服务
      水平扩容
      无底洞问题
          让集群保持小而美
    中心化
        公共依赖集群化
    去中心化(优先考虑)
        每个应用携带
        容错性
        伸缩性
        控制力相对较弱
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272
  • 273
  • 274
  • 275
  • 276
  • 277
  • 278
  • 279
  • 280
  • 281
  • 282
  • 283
  • 284
  • 285
  • 286
  • 287
  • 288
  • 289
  • 290
  • 291
  • 292
  • 293
  • 294
  • 295
  • 296
  • 297
  • 298
  • 299
  • 300
  • 301
  • 302
  • 303
  • 304
  • 305
  • 306
  • 307
  • 308
  • 309
  • 310
  • 311
  • 312
  • 313
  • 314
  • 315
  • 316
  • 317
  • 318
  • 319
  • 320
  • 321
  • 322
  • 323
  • 324
  • 325
  • 326
  • 327
  • 328
  • 329
  • 330
  • 331
  • 332
  • 333
  • 334
  • 335
  • 336
  • 337
  • 338