赞
踩
读写分离注意数据同步
分库分表(数据接近千万级,使用分布式主键),注意数据同步
微服务 + 搜索 + 日志处理 + 分库分表 + 优化
把控团队
系统分解与模块拆分
指导与培训
沟通与协调能力
抽象,举例,画图
软技能(项目管理/谈判)
springboot2.x
功能开发
个人中心
上线部署
前后端分离
分层架构
技术选型
后端
springboot
前端
jquery/vue
考虑因素
切合业务
社区活跃度
团队技术水平
版本更新迭代周期
试错精神
安全性
成功案例
开源精神
前后端分离开发模式
早期传统 web 开发
前后端单页面交互, mvvm 开发模式
项目分层设计原则
项目拆分与聚合
maven 聚合项目
父子模块相互依赖后需要进行安装(install)
数据库建模工具
www.pdman.cn
数据库外键
性能影响
热更新
降低耦合度
数据库分库分表
数据库配置
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。 - 但是如果主事务提交,则会携带子事务一起提交。 - 如果主事务回滚,则子事务会一起回滚。相反,子事务异常,则父事务可以回滚或不回滚。 - 举例:领导决策不对,老板怪罪,领导带着小弟一同受罪。小弟出了差错,领导可以推卸责任。
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
手动更新订单状态
http://localhost:8088/myorders/deliver?orderId=190827F2R9A6ZT2W
用户中心
订单管理
评价管理
云服务器购买
安装 jdk
安装 tomcat
安装 mariaDB
打包 springboot
部署 tomcat 发布前端
内网互通(云服务器同地域)
4 核 8g/16g
8 核 32g
16 核 64g
centos7.x
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/dt.jar:%JAVA_HOME%/lib/tools.jar
export PATH=
P
A
T
H
:
PATH:
PATH:JAVA_HOME/bin
source /etc/profile
域名/二级域名
下载安装 mariadb (官方文档)
https://mariadb.org
grant all privileges on *.* to 'root'@'%' identified by 'xxx';
flush privileges;
修改项目配置
单体项目打包发布
cookie 异常问题
vi context.xml
<CookieProcessor className="org.apache.tomcat.util.http.LegacyCookieProcessor"/>
单体架构总结
1,mvc 框架(springboot 多模块)
2,文件上传处理
3,事务管理
4,配置梳理
5,war 包发布
安装
nginx.org
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.1.tar.gz
创建临时目录
mkdir /var/temp/ngin -p
进入根目录,创建 makefile 文件
./configure \
--prefix=/usr/local/nginx \
--pid-path=/var/run/nginx/nginx.pid \
--lock-path=/var/lock/nginx.lock \
--error-log-path=/var/log/nginx/error.log \
--http-log-path=/var/log/nginx/access.log \
--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
进入根目录
make
make install
进入/usr/local/nginx/sbin,启动
./nginx
停止
./nginx -s stop
重新加载
./nginx -s reload
nginx.conf 配置文件讲解
master 进程: 主进程
worker 进程: 工作进程
可以修改 worker_processes 数量添加 worker 进程
可以设置为内核数-1
进入/usr/local/nginx/sbin,检查 nginx 配置
./nginx -t
异步非阻塞处理请求
events {
# 默认使用epoll
use epoll;
# 每个worker允许连接的客户端最大连接数
worker_connections 10240;
}
main 全局配置
event 配置工作模式以及连接数
http http模块相关配置
server 虚拟主机配置,可以有多个
localtion 路由规则,表达式
upstream 集群,内网服务器
可以设置 worker 的用户权限
user root;
日志级别
debug/info/notice/warn/error/crit
http 模块
include 包含模块
defaule_type application/octet-stream 默认的类型
日志格式和路径
#log_format main '$remote_addr - ';
#access_log logs/access.log main
sendfile 默认打开,发送文件传输
#tcp_nopush 数据包累积到一定大小之后再发送
keepalive_timeout 65; 存活超时时间
gzip 压缩后传输,消耗性能
./nginx -h
问题一: “/var/run/nginx/nginx.pid” failed (2:No such file or directory)
mkdir /var/run/nginx
问题二: invalid PID number “” in “/var/run/nginx/nginx.pid”
./nginx -c /usr/local/nginx/conf/nginx.conf
./nginx -s stop 暴力停止
./nginx -s quit 优雅停机
./nginx -V 查看版本和配置路径信息
通常以天作为单位切割
在/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/nginx.pid
mv ${LOG_PATH}/acess.log ${LOG_PATG}/access.${RECORD_TIME}.log
mv ${LOG_PATH}/error.log ${LOG_PATH}/error.${RECORD_TIME}.log
#向nginx主进程发送信号,用于重新打开日志文件
kill -USR1 `cat $PID`
赋予权限
chmod +x cut_my_log.sh
定时切割
yum install crontabs
crontab -l
crontab -e
每一分钟
分/时/日/月/星期/年(可选)
*/1 * * * * /usr/local/nginx/sbin/cut_my_log.sh
每天晚上 23:59
59 23 * * *
每日凌晨 1 点
0 1 * * *
重启定时任务
service crond restart
其他
service crond start
service crond stop
service crond reload
前端打包后,ngxin 指定 index 页面入口
server { listen 90; server_name lcoalhost; location / { root /home/foodie-shop; index index.html; } location /xxx { root /home; } location /static { alias /home/xxx; } }
开启 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 ~* \.(GIF|png|bmp|jpg|jpeg) {
root /home;
}
以某个字符路径开头请求
location ^~ /xxx/img {
root /home;
}
充当反向代理服务器
127.0.0.1 localhot promote.cache-dns.local # 虚拟主机名
::1 localhost promote.cache-dns.local # 虚拟主机名
浏览器拒绝跨站点访问
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' *;
}
server {
#对源站点验证
valid_referer *.xxx.com;
#非法引入会进入下面的判断
if($invalid_referer) {
return 404;
}
}
nginx.core
http
event module
phase handler
output filter
upstream
load balancer
extent module
mail
四层负载均衡
ip+端口 进行转发请求
F5 硬件负载均衡
LVS
haproxy
nginx
七层负载均衡
基于 http 的 url 或者 ip 转发请求
nginx
haproxy
apache
dns 地域负载均衡
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 www.tomcats.com;
location / {
proxy_pass http://tomcats;
}
}
下载地址
https://jmeter.apache.org/
windows 双击 jmeter.bat
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;
}
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;
}
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 "";
}
}
ip_hash
服务器发生异常不能直接移除配置,可以标记为 down
内网不变动,原因是 hash(192.168.x) ip 只有前三位参与运算
问题: 节点变动,会话丢失,缓存请求不到
upstram tomcats {
ip_hash;
server 192.168.1.173:8080;
server 192.168.1.174:8080;
server 192.168.1.175:8080;
}
一致性哈希算法
顺时针就近原则,减少用户影响
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;
}
least_conn
请求最小连接数的服务器
upstram tomcats {
least_conn;
server 192.168.1.173:8080;
server 192.168.1.174:8080;
server 192.168.1.175:8080;
}
304 状态 被 nginx 缓存
expires
location /static {
alias /home/xxx;
# expires 10s;
# expires @22h30m;
# expires -1; # 提前过期
# expires epoch; # 不设置过期
# expires off; # nginx不设置不显示,浏览器使用默认缓存机制
# expires max; # 最大值
}
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;
}
ssl 证书
config 目录添加 nginx crt/key
增加安装配置
–with-http_ssl_module
server {
listen 443;
server_name localhost;
添加官网配置
ssl open;
ssl_certificate xxx.crt;
ssl_certificate_key xxx.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;
}
动静分离
前端项目部署到 nginx
部署到云服务器
域名映射到服务器
前端修改接口服务地址(去掉端口号)
支付中心接口修改
nginx 高可用 ha
nginx 主备(硬件配置需要一样)
keepalived 基于 VRRP 协议(虚拟路由冗余协议)
keepalived 安装
虚拟 ip
master ip
backup ip
下载 keepalived-2.0.18.tar.gz 后解压
./configure --prefix=/usr/local/keepalived --sysconf=/etc
make && make install
/etc/keepalived/keepalived.conf
如果报错 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 } # 下面的删除 }
192.168.1.171 www.171.com
192.168.1.172 www.172.com
192.168.1.161 www.ha.com (绑定虚拟 ip)
启动
进入/usr/local/keepalived/sbin/
./keepalived
注册为系统服务
cp init.d/keepalived /etc/init.d/
cp sysconfig/keepalived /etc/sysconfig/
systemctl daemon-reload
systemctl start keepalived.service
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
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
}
keepalived 双主热备
dns 轮询两个虚拟 ip
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/sysctl.conf
net.upv4.conf.all.arp_ignore = 1
net.ipv4.conf.default.arp_ignore = 1
net.ipv4.conf.lo.arp_ignore = 1
net.ipv4.conf.all.arp_announce = 2
net.ipv4.conf.default.arp_announce = 2
net.ipv4.conf.lo.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/rc.local
使用 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 } }
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 } }
分布式架构优点
业务解耦
系统模块化,可重用化
提升系统并发量
优化运维部署效率
分布式架构缺点
架构复杂
部署多个子系统复杂
系统之间通信耗时
设计原则
异步解耦
幂等一致性
拆分原则
融合分布式中间件
容错高可用
nosql 常见分类
键值数据库 redis
列存储数据库 hbase
文档型数据库 mongodb
图形数据库 neo4j
redis 安装
解压
tar -zxvf redis-5.0.5.tar.gz
yum install gcc-c++
make
make install
cd utils
cp redis_init_script /etc/init.d
cp redis.conf /usr/local/redis
修改 daemonize yes
修改 dir /usr/local/redis/workspace
新增文件夹/usr/local/redis/workspace
修改 bind 0.0.0.0
修改 requirepass xxx
修改/etc/init.d/redis_init_script
CONF=“/usr/local/redis/redis.conf”
chmod 777 redis_init_script
./redis_init_script start
跟随系统自启动
#chkconfig: 22345 10 90
#description: Start and Stop redis
chkconfig redis_init_script on
命令行使用
redis-cli
auth xxx
redis-cli -a xxx ping
redis_init_script stop 命令加上 -a “xxx”
redis 的线程模型
redis 整合 springboot
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
spring:
redis:
database: 0
host: 127.0.0.1
port: 6379
password: xxx
redis 进阶提升与主从复制
redis 持久化
RDB 快照
AOF 写操作记日志
编辑 redis.conf 开启 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/redis-sentinel.pid
logfile /usr/local/redis/sentinel/redis-sentinel.log
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 sentinel.conf
redis-cli -p 36379
哨兵节点要有至少三个或者奇数个节点
哨兵分部式部署在不同的计算机节点
一组哨兵只监听一组主从
集群搭建
三主三从
所有节点修改配置如下
cluster-enabled yes
cluster-config-file nodes-6379.conf
cluster-node-timeout 5000
appendonly yes
删除 appendonly.aof 和 dump.rdb
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
批量获取
redisTemplate.opsForValue().multiGet(keys)
管道
redisTemplate.executePipelined(){};
分布式会话
redis
springsession
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
拦截器
单点登录
sso + thymeleaf
下载解压 elstsearch
修改 elstsearch.yml
cluster.name: xxx
node.name: es-node1
path.data: /path/data
path.logs: /path/logs
network.host: 0.0.0.0
cluster.initial_master_nodes: ["es-node1"]
http.cors.enabled: true // 开启本地跨域
http.cors.allow-origin: "*" //允许所有请求类型跨域
修改 jvm.options
-Xms1g
-Xmx1g
创建新的用户去启动 es
useradd esuser
chown -R esuser:esuser /usr/local/elasticsearch-7.4.2
su esuser
su root
修改/etc/security/limits.conf,后面添加
* soft nofile 65535
* hard nofile 131072
* soft nproc 2048
* hard nproc 4096
修改/etc/sysctl.conf,后面添加
vm.max_map_count=262145
sysctl -p
su esuer
./elasticsearch
访问
192.168.1.xxx: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">custom.dic</entry>
同级目录下创建 custom.dic
特定领域查询语言
{
"query": {
"match": {
"desc": "慕课网"
}
}
}
分页
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"]
}
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"
}
}
排序
{
"sort": [
{
"age":"asc"
},
{
"money":"desc"
}
]
}
字符串(文本)排序可以增加 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": [ { "nickname.keyword": "asc" } ] }
高亮
{
"query": {
"match": {
"desc": ""
}
},
"highlight":{
"pre_tags": ["<span>"],
"post_tags": ["</span>"],
"fields":{
"desc":{}
}
}
}
深度分页
默认限制 10000
使用页码控制
http://192.168.1.1:9200/shop/_settings //PUT
{
"index.max_result_window": 100000
}
游标查询
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"
}
批量查询
http://192.168.1.1:9200/shop/_mget
{
"ids":["1001","1003","10015"]
}
批量操作 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"}
bulk 不存在新增存在更新 index
http://192.168.1.1:9200/shop/_doc/_bulk
{"index": {"\_id":"2004"}}
{"id":"2004","nickname":"index-2004"}
bulk 批量修改属性
http://192.168.1.1:9200/shop/_doc/_bulk
{"update":{"_id":"2004"}}
{"doc":{"id":"3304"}}
bulk 批量删除
http://192.168.1.1:9200/shop/_doc/_bulk
{"delete":{"_id":"2004"}}
ES 集群
192.168.1.184
192.168.1.185
192.168.1.186
所有的节点删除 /usr/local/elasticsearch-7.4.2/data 下的 nodes
所有的 elasticsearch.yml 修改 cluster.name: xxx-es-cluster
不同节点修改不同 node 节点名称
node.name=node1
修改(主节点(可以被选举)和数据节点)
node.master: true
node.data: true
discovery.seed_hosts: [“192.168.1.184”, “192.168.1.185”, “192.168.1.186”]
// 查看非注释内容
more elasticsearch.yaml | grep [#]
集群分片测试
集群宕机测试
集群脑裂(新版本已处理(N/2)+1)
集群的文档读写原理
1,协调节点
2,主分片或者副本分配轮询读操作
3,返回给协调节点
4,响应给客户端
springboot 整合 elasticsearch
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
<version>2.2.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>test</version>
</dependency>
</dependencies>
application.yml
spring:
data:
elasticsearch:
cluster-nodes: 192.168.1.187:9300
cluster-name: es6
elasticsearch6.4.3 配置文件
cluster.name: es6
node.name: node0
path.data: /usr/local/elasticserch-6.4.3/data
path.logs: /usr/local/elasticserch-6.4.3/logs
network.host: 0.0.0.0
需要切换到 root 用户下去修改配置
vim /etc/security/limits.conf
* soft nofile 65535
* hard nofile 131073
* soft nproc 2048
* hard nproc 4096
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.41.jar .
vi logstash-db-sync.conf
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.41.jar" # 驱动类名 jdbc_driver_class => "com.mysql.jdbc.Driver" # 开启分页 jdbc_paging_enabled => "true" # 分页每页数量,可以自定义 jdbc_page_size => "1000" # 执行的sql文件路径 statement_filepath => "/usr/local/logstash-6.4.3/sync/foodie-items.sql" # 设置定时任务间隔 含义:分、时、天、月、年,全部为*默认含义为每分钟跑一次任务 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/logstash-ik.json" # 重写模板 template_overwrite => true # 默认为true, fase关闭logstash自动管理模板功能,如果自定义模板,则设置为false manage_template => false # 日志输出 stdout { codec => json_lines } }
foodie-items.sql
SELECT
i.id AS itemId,
i.item_name AS itemName,
i.sell_counts AS sellCounts,
ii.url AS imageUrl,
tempSpec.price_discount AS price,
i.update_time as update_time
FROM
items i
LEFT JOIN items_img ii ON i.id = ii.item_id
LEFT JOIN ( SELECT item_id, MIN( price_discount ) AS price_discount FROM items_spec GROUP BY item_id ) tempSpec ON i.id = tempSpec.item_id
WHERE
ii.is_main = 1
and
i.update_time >= :sql_last_value
@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": {} }
FastDFS
https://github.com/happyfish100/fastdfs/wiki
tracker / storage 这台服务器下载解压 FastDFS
安装环境
yum install -y gcc gcc-c++
yum -y install libevent
tar -zxvf libfastcommon-1.0.42.tar.gz
./make.sh
./make.sh install
tar -zxvf fastdfs-6.04.tar.gz
./make.sh
./make.sh install
cp fastdfs-6.04/conf/* /etc/fdfs
通过加载 tracker.conf 或 storage.conf 来区分是 tracker 还是 storage 服务
修改 tracker.conf
base_path=/usr/local/fastdfs/tracker
启动 tracker 服务(tracker 要先启动再启动 storage)
/usr/bin/fdfs_trackerd /etc/fdfs/tracker.conf
修改 storage.conf
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/stroage.conf
修改 client.conf 文件
base_path=/usr/local/fastdfs/client
tracker_server=192.168.1.155:22122
测试图片上传
/usr/bin/fdfs_test /etc/fdfs/client.conf upload xxx.jpg
nginx 要和 stroage 安装在同一个服务
解压 fastdfs-nginx-module-1.22.tar.gz
进入 fastdfs-nginx-module-1.22
修改 src/config
ngx_module_incs=“/usr/local/include” 改成 ngx_module_incs=“/usr/include”
CORE_INCS=“
C
O
R
E
I
N
C
S
/
u
s
r
/
l
o
c
a
l
/
i
n
c
l
u
d
e
"
改成
C
O
R
E
I
N
C
S
=
"
CORE_INCS /usr/local/include" 改成 CORE_INCS="
COREINCS/usr/local/include"改成COREINCS="CORE_INCS /usr/include”
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.1.tar.gz
mkdir /var/temp/nginx -p
cd /nginx-1.16.1
./configure
–prefix=/usr/local/nginx
–pid-path=/var/run/nginx/nginx.pid
–lock-path=/var/lock/nginx.lock
–error-log-path=/var/log/nginx/error.log
–http-log-path=/var/log/nginx/access.log
–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
修改 nginx.conf
server {
listen 8888; //需要和stroage.conf的配置文件里面http.server_port的一致
server_name localhost;
localtion /xxx/M00 {
ngx_fastdfs_module;
}
}
消息队列
分布式消息队列认知提升
应用场景: 服务解耦/削峰/异步
应用思考点: 生产端可靠性投递/消费端幂等
高可用/低延时/可靠性/扩展性/堆积能力
集群架构模式: 分布式,可扩展,高可用,可维护
rabbitMQ 四种集群架构
主备模式
远程模式
镜像模式(高可用)
多活模型(异地容灾),使用 federation 插件
kafka 特点
分布式/跨平台/实时性/伸缩性
kafka 高性能的原因是什么
顺序写/page cache/高性能高吞吐/后台异步,主动 flush/预读策略/io 调度
rabbitMQ 实战
AMQP 核心概念
server
connection
channel
message(properties/body)
virtual host
exchange
binding
routing key queue
安装与入门
http://www.rabbitmq.com
erlang-18.3-1.e17.centos.x86_64.rpm
rabbitmq-server-3.6.5-1.noarch.rpm
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 www.rabbitmq.com/releases/erlang/erlang-18.3-1.el7.centos.x86_64.rpm
wget http://repo.iotti.biz/CentOS/7/x86_64/socat-1.7.3.2-1.1.el7.lux.x86_64.rpm
wget www.rabbitmq.com/releases/rabbitmq-server/v3.6.5/rabbitmq-server-3.6.5-1.noarch.rpm
安装服务命令
rpm -ivh erlang-18.3-1.el7.centos.x86_64.rpm
rpm -ivh socat-1.7.3.2-1.1.el7.x86_64.rpm
rpm -ivh rabbitmq-server-3.6.5-1.noarch.rpm
修改用户登录与连接心跳检测,注意修改
vim /usr/lib/rabbitmq/lib/rabbitmq_server-3.6.5/ebin/rabbit.app
修改点 1:loopback_users 中的 <<“guest”>>,只保留 guest (用于用户登录)
修改点 2:heartbeat 为 10(用于心跳连接)
安装管理插件
首先启动服务(后面 | 包含了停止、查看状态以及重启的命令)
/etc/init.d/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.info.oa"
"log.*" 只会匹配到"log.erro"
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 收到消息后异步给生产者一个应答
channel.confirmSelect()
addConfirmListener
return 返回消息
return Listener 用于处理一些不可路由的消息
监听队列,进行消费处理操作
Mandatory: true
消息的限流
单个客户端无法同时处理这么多数据
qos 功能,即在非自动确认消息的前提下,如果一定数目的消息未被确认前,不进行新的消息
channel.basicQos(0,1,false);
prefetchSize:0
prefetchCount: 不要同时给一个消费者推送多于 n 个消息,即一旦有 n 个消息还没有 ack,则该 consumer 将 block 掉,直到有消息 ack
global: true/false 是否将上面设置应用于 channel
消费端 ack 与重回队列
手工 ack 和 nack
重回队列设置成 false
ttl 队列/消息
支持队列的过期时间,从消息入队列开始计算,只要超过了队列的超时时间设置,那么消息会自动的清除
死信队列
几种情况
消息被拒绝(basic.reject/basic.nack)并且 requeue=false
消息 ttl 过期
队列达到最大长度
设置后会被路由到指定的队列
arguments.put(“x-dead-letter-exchange”, “dlx-exchange”)
channel.queueBind(queueName, exchangeName, routingKey)
channel.queueDeclare(queueName, false, false, false, arguments)
channel.exchangeDeclare(“dlx,exchange”, exchangeType, true,false,false,null)
channel.queueDeclare(“dlx.queue”, false, false, false, null)
channel.queueBind(“dlx.queue”, “dlx.exchange”, “#”)
集群架构-镜像队列集群环境搭建实操
参考搭建文档
与 springboot 整合
https://github.com/xingyuezhiyun/rabbitmqDemo
基础组件封装与实战
迅速消息发送
确认消息发送
延迟消息发送
rabbitMQ 可靠性投递基础组件封装
迅速消息 不保证确认
确认消息 确认写库
可靠消息 最终一致性(定时任务方案(rabbitMq 消息补偿机制))
延迟消息(使用插件: rabbitmq_delayed_message_exchange-0.0.1.ez)
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
异步化,服务解耦,削峰填谷
海量日志收集
数据同步应用
实时计算分析
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.6.tar.gz -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 zoo.cfg 修改zoo.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 // 与之前的zoo.cfg配置文件里面的server.0,server.1,server.2顺序相对应 启动路径: 进入zookeeper-3.4.6/bin/ zkServer.sh start zkServer.sh status zkServer.sh stop 4,开机启动 cd /etc/rc.d/init.d 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/zkServer.sh start;; stop) /usr/local/zookeeper-3.4.6/bin/zkServer.sh stop;; status) /usr/local/zookeeper-3.4.6/bin/zkServer.sh status;; restart) /usr/local/zookeeper-3.4.6/bin/zkServer.sh restart;; *) echo "require start|stop|status|restart" ;; esac ``` 开机启动配置: chkconfig zookeeper on 验证: chkconfig --add zookeeper chkconfig --list zookeeper 5,连接工具 zooInspetor
kafka版本: kafka_2.12 https://archive.apache.org/dist/kafka/2.1.0/kafka_2.12-2.1.0.tgz tar -zxvf kafka_2.12-2.1.0.tgz -C /usr/local mv kafka_2.12-2.1.0 kafka_2.12 vim /usr/localkafka_2.12/config/server.properties broker.id=0 port=9092 host.name=192.168.11.221 dvertised.host.name=192.168.11.221 num.partitions=5 log.dirs=/usr/local/kafka_2.12/kafka-logs zookeeper.connect=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/kafka-server-start.sh /usr/localkafka_2.12/config/server.properties 管控台: kafkaManger 2.0.0.2 vim /usr/local/kafka-manager-2.0.0.2/conf/application.conf kafka-manager.zkhost="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 发送测试: ./kafka-console-producer.sh --broker-list 192.168.221:9092 --topic test 接收测试: ./kafka-console-consumer.sh --bootstrap-server 192.168.221:9092 --topic test
配置消费者参数属性和构造消费者对象
订阅主题
拉去消息并进行消费处理
提交消费偏移量,关闭消费者
参数文件配置参数
zookeeper.connect
listeners
broker.id
log.dir和log.dirs
message.max.bytes
bootstrap.servers,key.serializer和value.serializer,client.id kafkaProducer是线程安全的,consumer不是线程安全的 一条消息必须通过key去计算出实际的partition,按照partition去存储,刚开始创建ProducerRecord时key为空,kafka也会通过算法计算生成一个key和partition kafka 生产者重要参数详解 acks: 指定发送消息后,broker至少有多少个副本接收到该消息客户端才能够收到服务端的成功响应,默认为1 max.request.size: 用来限制生产者客户端能发送的消息的最大值 retries和retry.backoff.msretries: 重试次数和重试间隔,默认100 compression.type: 这个参数用来指定消息的压缩方式,默认值是none,可选gzip,snappy,lz4 connections.max.idle.ms: 指定在多久之后关闭限制的连接,默认是540000(ms) linger.ms: 这个参数用来指定生产者发送producerBatch之前等待更多消息加入produceBatch的时间,默认是0 batch size: 累计多少条消息,则一次进行批量发送 buffer.memory: 缓存提升性能参数,默认为32M receive.buffer.bytes: 这个参数用来设置socket接收消息缓冲区(SO_RECBUF)的大小,默认值32KB send.buffer.bytes: 这个参数用来设置socket发送消息缓冲区(SO_SNDBUF)的大小,默认值为128KB request.timeout.ms: 这个参数用来配置producer等待请求响应的 最长时间,默认值为30000(ms) kafka 拦截器 生产者实现org.apache.kafka.clients.producer.ProducerInterceptor // 添加配置 properties.put(ProducerConfig.INTERCEPTOR_CLASSES_CONFIG, CustomProducerInterceptor.class.getName()) 消费者实现org.apache.kafka.clients.consumer.ConsumerInterceptor // 添加配置 properties.put(ConsumerConfig.INTERCEPTOR_CLASSES_CONFIG, CustomConsumerInterceptor.class.getName()) kafka 序列化反序列化 XxxSerializer实现Serializer<Xxx> // 添加配置 properties.put(ProducerConfig.VALUE_SERIALIZER_CLASSES_CONFIG, XxxSerializer.class.getName()) XxxDeSerializer实现DeSerializer<Xxx> // 添加配置 properties.put(ConsumerConfig.VALUE_SERIALIZER_CLASSES_CONFIG, XxxDeSerializer.class.getName()) kafka 分区器使用 实现Partitioner // 添加配置 properties.put(ProducerConfig.PARTITIONER_CLASSES_CONFIG, CustomConsumerInterceptor.class.getName())
消费者与消费者组 通过消费者组的设置来区分发布订阅(不同组)和点对点消费(相同组) 消费者必要参数方法 bootstrap.servers key.deserializer和value.deserializer groupid: 消费者所属消费组 subscribe: 消息主题订阅,支持集合/标准正则表达式 assign: 只订阅主题的某个分区 消费者提交位移 enable.auto.commit,默认值为true(要改为false) auto.commit.interval.ms,默认值为5秒 消费者手工提交 手工提交: enable.auto.commit 提交方式: commitSync / commitAsync 同步方式: 整体提交 / 分区提交 消费者在均衡 不同组(发布订阅) comsumer.subscribe(Collections.singletonList(Const.TOPIC_REBALANCE), new ConsumerRebalanceListener) 消费者多线程 kafka是线程安全的,但是kafka consumer是线程非安全的 1,一个线程一个consumer (同一个消费者组) 2,consumer里分发任务完成后提交(join) 消费者重要参数 fetch.min.bytes: 一次拉取最小数据量,默认为1B fetch.max.bytes: 一次拉取最大数据量,默认为50M max.partition.fetch.bytes: 一次fetch请求,从一个partition中取得的records最大值,默认1M fetch.max.wait.ms: Fetch 请求发给broker后,在broker中可能会被阻塞的时长,默认为500 max.poll.records: Consumer每次调用poll()时取到的records的最大数,默认为500条 kafka 高级应用整合springboot <dependency> <groupId>org.springframework.kafka</groupId> <artifactId>spring-kafka</artifactId> </dependency> 生产者: maven / 配置 / KfkaTemplate<String, Object> server.servlet.context-path=/producer server.port=8001 spring.kafka.bootstrap-servers= spring.kafka.producer.retries=0 spring.kafka.producer.batch-size=16384 spring.kafka.producer.buffer-memory=33554432 spring.kafka.producer.key-serializer=org.apache.kafka.common.serialization.StringSerializer spring.kafka.producer.value-serializer=org.apache.kafka.common.serialization.StringSerializer spring.kafka.producer.acks=1 0, 生产者在成功写入消息之前不会等待任何来自服务器的响应 1, 只要集群的首领节点收到消息,生产者就会收到一个来自服务器成功响应 -1, 分区leader必须等待消息被成功写入到所有的isr副本才认为producer请求成功,消息持久性可以保证,但是吞吐率是最差的 消费者: maven / 配置 / @KafkaListener(groupId = "group02", topics = "topic02") server.servlet.context-path=/consumer server.port=8002 spring.kafka.bootstrap-servers= spring.kafka.consumer.enable-auto-commit=false spring.kafka.listener.ack-mode=manual spring.kafka.consumer.auto-offset-reset=earliest (偏移量的分区或者偏移量无效的情况下该作如何处理, latest: 最新的记录开始读; earliest: 从起始位置开始读取分区的记录) spring.kafka.producer.key-serializer=org.apache.kafka.common.serialization.StringSerializer spring.kafka.producer.value-serializer=org.apache.kafka.common.serialization.StringSerializer spring.kafka.listener.concurrency=5
filebeat + kafka + logstash + es / kibana
maven 排除 spring-boot-starter-log4j2
MDC 变量替换输出
filebeat-6.6.0-linux-x86_64.tar.gz
logstash-6.6.0.tar.gz
jvm.options
logstash-script.conf
es
kibana
watcher(es 告警插件)
什么是数据同步
分表分库后同步表数据
应用场景
数据落地后才开始同步
canal 基于日志增量订阅和消费的业务包括:
数据库镜像,数据库实时备份
索引构建和实时维护(拆分异构索引,倒排索引)
业务 cache 刷新,带业务逻辑的增量数据处理
优点: 实时性好,分布式,ack 机制
缺点: 只支持增量同步,不支持全量同步
mysql -> es, rdb
一个 instance 只能有一个消费端消费
单点压力过大
canal.adapter-1.1.4.tar.gz
canal.admin-1.1.4.tar.gz
canal.deployer-1.1.4.tar.gz
canal.example-1.1.4.tar.gz
mysql -> canal -> mq(kafka) -> es
架构思考: 分布式日志,跟踪告警,分析平台
行锁(扣减后变成负数)
synchronized 方法锁(手动控制事务)
synchronized 块锁(手动多个事务进行控制)
reentrantLock(手动控制事务)
分布式服务使用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
先垂直后水平
垂直切分
不同业务之间,禁止跨库join联查
跨库事务难以处理
水平切分
将一张表的数据按照某种规则分到不同的数据库中
拆分规则很难抽象
分片事务一致性难以解决
二次扩展时,数据迁移,维护难度大
两种模式
中间层代理
客户端模式
mycat(服务端代理) centos7 yum安装mysql数据库 下载安装mycat,修改配置文件 server.xml schema.xml rule属性: 定义分片表的分片规则,必须与rule.xml中的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 > dbdump.db -uroot -p 主进行解锁 UNLOCK TABLES; 从导入dump数据(scp传输) mysql < dbdump.db -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/haproxy.cfg defaluts -> mode -> tcp backend app -> server app1 / app2 启动: haproxy -f /etc/haproxy/haproxy.cfg 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/keepalived.conf 商品订单表根据userId进行分片 注意点: 父子表要先写入父表,分片的列不能更新 sharding-jdbc(客户端代理模式) sharding-jdbc支持springboot和xml配置两种模式 mycat不支持同一库内的水平切分,sharding-jdbc支持 绑定表(子表),主表的id要和子表的一致(源码中的广播表(主表)和绑定规则表判断有问题) 可以配置读写分离,读不能从写库里面读
问题: 两个分片表中存在相同的order_id, 导致业务混乱
UUID(32位): mycat不支持,sharding-jdbc支持,有可能重复
统一id序列:
优点: id集中管理,避免重复
缺点: 并发量大时,id生成器压力较大
mycat进行配置
雪花算法:
基本保持全局唯一,毫秒内并发最大4096个id
时间回调,可能引起id重复
mycat和sharding-jdbc都支持雪花算法
sharding-jdbc可设置最大容忍回调时间
xa协议的两阶段提交
mysql5.7以上/mysql connector/J 5.0 以上支持xa协议
java系统中,数据源采用的时Atomikos
mycat(可配置handleDistributedTransations)和sharding-jdbc都直接支持xa协议
事务补偿机制(不推荐)
优点: 逻辑清晰,流程简单
缺点: 数据一致性差,可能出错的点比较多,属于应用层补充,需要编写大量代码
基于本地消息表的最终一致方案
消息表+定时任务
适用于公司外部系统(第三方对接)
基于MQ消息队列的最终一致方案
不依赖定时任务,基于mq更高效可靠
适用于公司内的系统
Delete操作的幂等性
根据唯一业务号去删除(天然幂等),可以先查询出来判断后再删除
删除操作没有唯一业务号,看具体的业务需求
update操作的幂等性
后台使用版本号作为更新条件(乐观锁+行锁)
页面查询出version字段传递到后端
insert操作的幂等性
有唯一业务号的insert操作(分布式锁)
没有唯一业务号的insert操作(token机制)
混合操作(token机制)
前后端分离的项目使用sessionid+redis+RLock
guava rate-limit(单机版,不操作数据库,cpu计算场景)
非阻塞式(tryAcquire)
阻塞式(acquire)
基于nginx的分分布式限流
ip限制(修改配置)
连接数限制
redis + lua
idea安装emmylua
redis预加载lua脚本
限流组件(编写annotation)
业务拆分思考维度
流量压力维度
业务维度
指导方针
隔离业务场景
剥离高频接口
分布式系统 cap 定理
eureka server/client/consumer服务
eureka 注册发现源码阅读
eureka 心跳和服务续约
eureka 主从(相互指定,客户端注册主从地址)
hystrix实现timeout降级(配置+代码) @GetMapping("/xxx") @HystrixCommand( fallbackMethod = "timeoutFallback", // 超时降级的方法(函数) commandProperties = { @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds", value="3000") } ) hystrix实现requestCache降级 @CacheResult @CacheKey @HystrixCommand(commandKey="cacheKey") // 配置文件 hystrix.command.cacheKey.execution.isolation.thread.timeoutInMilliseconds=2000 hystrix.cammand.default.requestCache.enabled=true hystrix多降级方案 @HystrixCommand(fallbackMethod="fallback2") turbine spring-cloud-starter-netflix-hystrix-dashboard
静态配置 环境配置 数据库连接 注册中心配置 消息队列配置 安全配置 连接密码 公钥私钥 http连接cert 动态配置 功能控制 功能开关 人工熔断开关 蓝绿发布 数据源切换 业务规则 当日外汇利率 规则引擎参数 应用参数 网关黑白名单 缓存过期时间 日志mdc设置 其他用法 环境隔离 业务开关+定向推送 修改业务逻辑 网站黑名单 费率: 规则引擎 熔断阈值
bus: 批量更新配置中心配置
getway: GatewayFilter / GlobalFilter
创建网关和路由规则
配置网关层redis
添加网关层跨域
基于jwt实现用户鉴权
链路追踪的基本功能
分布式环境下链路追踪
timing信息(时间)
信息收集和展示
定位链路
sleuth 集成 elk
logback 添加 appender
stream (消息驱动)
跨系统异步通信
应用解耦
流量削峰
场景(stream批量强制用户退出 / 延时队列处理超时订单)
apollo 第三方授权
apolloOpenAPI
apollo + sentinel 集成
生产者,消费者,zookeeper
微服务落地需求 环境需求差异大: CPU 业务型, GPU计算型, 高吞吐I/O型 服务敏捷要求高: 成百上千, 快速启动,优雅停止 组织架构变化: 产品导向, devOps文化,团队微小化 安装 curl -fsSL get.docker.com -o get-docker.sh sh get-docker.sh --mirror Aliyun DockerFile 编写和打包 CloudFoundry 开源PaaS云平台 http://network.pivotal.io/products/pcfdev https://github.com/cloudfoundry-incubator/cfdev mesos + marathon 环境快速搭建: github.com/mesosphere/playa-mesos 制作镜像,页面管理镜像启动容器 kubernetes 环境搭建 kubernetes.io/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/kube-apiserver.yaml 集群监控和管理 helm curl https://raw.githubusercontent.com/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 https://github.com/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 http://github.com/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 @autoscale.json kubernetes 弹性扩缩容 HPA / VPA / cronHPA / cluster-autoscaler Istio / knative
应用场景下选择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
性能调优目标概述与四板斧 借助监控预防问题,发现问题 借助工具定位问题 定期复盘,防止同类问题再现 定好规范,一定程度上规避问题 应用性能调优 skywalking https://github.com/apache/skywalking/tree/v6.6.0/docs http://www.itmuch.com/books/skywalking java agent 插件 (加载plugin目录下的插件) 内置插件 引导插件 可选插件 扩展插件(github.com/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 https://github.com/codecentric/spring-boot-admin http://codecentric.github.io/spring-boot-admin/2.1.6 集成client模式 服务发现模式 prometheus+grafana javamelody https://github.com/javamelody/javamelody https://github.com/javamelody/javamelody/wiki/SpringBootStarter https://github.com/javamelody/javamelody/wiki/UserGuide#2-webxml-file https://gitee.com/itmuch/platform tomcatmanager(只支持传统war应用) https://wiki.jikexueyuan.com/project/tomcat/manager.html PSIProbe https://github.com/psi-probe/psi-probe https://github.com/psi-probe/psi-probe/wiki https://psi-probe.githube.io/psi-probe https://www.cnblogs.com/qlqwjy/p/9492813.html 技巧与实战篇 对象池: 复用对象 commons-pool2 ObjectPool: GenericObjectPool / GenericKeyedObjectPool Factory: 创建和管理(PooledObject),一般要自己扩展 PooledObject: 包装原有的对象,从而让对象池管理,一般用DefaultPooledObject 线程池: 复用线程 http://blog.csdn.net/hit100410628/article/details/72934353 new ThreadPoolExecutor( corePoolSize 5, maximumPoolSize 10, keepAliveTime, TimeUnit.SECONS, new LinkedBlockingQueue<>(100), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy() ); BlockingQueue详解,选择与调优 ArrayBlockingQueue LinkedTransferQueue DelayQueue LinkedBlockingQueue PriorityBlockingQueue SynchronousQueue 合理设置corePoolSize,maximumPoolSize,workQueue的容量 ScheduledThreadPoolExecutor scheduleAtFixedRate scheduleWithFixedDelay http://blog.csdn.net/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调优 https://www.javacodegeeks.com/2012/03/threading-stories-about-robust-thread.html
/** * 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 * (http://www.javaspecialists.eu/archive/Issue124.html). * * @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 * (http://www.javaspecialists.eu/archive/Issue029.html). * * @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. (e.g. * http://www.hyperic.com/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(); }
连接池: 复用连接 数据库连接池 Hikari Tomcat DBCP2 Alibaba Druid redis连接池 jedis lettuce redisson http连接池 apache httpclient okhttp 连接池调优 连接数 = 2n + 可用磁盘数 分离: 2个连接池(分别处理实时和慢sql) 异步化 本地异步化 创建线程 线程池 @Async @Async注解标注的方法必须返回void或Future 建议将@Async标注的方法放到独立的类中去 建议自定义BlockingQueue的大小 生产者消费者 远程异步化 AsyncRestTemplate WebClient https://docs.spring.io/spring-framework/reference/web/webflux-webclient.html 锁优化 synchronized 修饰实例方法: 给当前实例加锁,进入同步代码前要获得当前实例的锁 修饰静态方法: 给当前类加锁,进入同步代码前要获得指定类对象的锁 修饰代码块: 给指定对象加锁,进入同步代码前要获得指定对象的锁 synchronized原理 对象存储对象头 Mark word (无锁/偏向锁/轻量级锁/重量级锁/GC标记) 类型指针 数组长度 锁的适用场景 偏向锁: 轻量级锁: 重量级锁:
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 默认开启 是否允许内联同步方法
逃逸分析,标量替换,栈上分配
垃圾收集算法 垃圾收集器 数据库调优 架构调优 操作系统调优 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命令大全: https://pan.baidu.com/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 https://www.linuxidc.com/Linux/2019-08/160082.html htop / iotop / iftop https://www.cnblogs.com/xuanbjut/p/11531394.html zabbix prometheus 下载:prometheus,alertmanager,node_exporter,mysqld_exporter https://pan.baidu.com/s/1GWQ0Y2GJAA_zbngmkAEeAw?pwd=084s 架构调优与架构设计 结合工具,发现性能瓶颈,并去做针对性的调优 应用层面的调优 数据库层面的调优 操作系统层面 技术方案的调优 "有损"的调优 通过架构上的演进去解决性能瓶颈 单体应用:CPU密集型,IO密集型 微服务 水平扩容 无底洞问题 让集群保持小而美 中心化 公共依赖集群化 去中心化(优先考虑) 每个应用携带 容错性 伸缩性 控制力相对较弱
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。