当前位置:   article > 正文

java架构专家

java架构专家

单体服务

大型网站架构演进

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

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

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

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

单体架构设计与项目开发

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

前后端分离
分层架构

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

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

前后端分离开发模式

早期传统 web 开发
12

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

项目分层设计原则
项目拆分与聚合
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。
- 但是如果主事务提交,则会携带子事务一起提交。
- 如果主事务回滚,则子事务会一起回滚。相反,子事务异常,则父事务可以回滚或不回滚。
- 举例:领导决策不对,老板怪罪,领导带着小弟一同受罪。小弟出了差错,领导可以推卸责任。
  • 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

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;
  • 1
  • 2

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

cookie 异常问题

vi context.xml

<CookieProcessor className="org.apache.tomcat.util.http.LegacyCookieProcessor"/>
  • 1

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

nginx

安装
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
  • 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.conf 配置文件讲解

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/access.log main
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

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

keepalive_timeout 65; 存活超时时间

gzip 压缩后传输,消耗性能

nginx.pid 打开失败以及失败的解决方案

./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 常用的命令

./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/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`
  • 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 index.html;
  }

  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 promote.cache-dns.local # 虚拟主机名
::1 localhost promote.cache-dns.local # 虚拟主机名

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 *.xxx.com;
  #非法引入会进入下面的判断
  if($invalid_referer) {
    return 404;
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

nginx 的模块化体系

nginx.core
  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 www.tomcats.com;
    location / {
        proxy_pass http://tomcats;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

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

下载地址
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;
}
  • 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.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;
}
  • 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 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;
}

  • 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.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
  }
  # 下面的删除
}
  • 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 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
  • 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/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
    }
}
  • 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.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
  • 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>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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

拦截器

单点登录
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: "*" //允许所有请求类型跨域
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

修改 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
  • 1
  • 2
  • 3
  • 4

修改/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>
  • 1

同级目录下创建 custom.dic

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": [
    {
      "nickname.keyword": "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
  • 27
  • 28

高亮

{
  "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
所有的 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>
  • 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 配置文件
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
  • 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.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
    }
}
  • 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

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
  • 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
  • 70

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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

通过加载 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;
    }
}
  • 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

安装与入门
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"
  • 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 收到消息后异步给生产者一个应答
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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

Kafka

kafka 核心概念详解

mq 实战应用场景

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

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.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
  • 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

kafka 环境搭建

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
  • 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 急速入门

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

参数文件配置参数
    zookeeper.connect
    listeners
    broker.id
    log.dir和log.dirs
    message.max.bytes
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

kafka 生产者

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())
  • 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 消费者

  消费者与消费者组
      通过消费者组的设置来区分发布订阅(不同组)和点对点消费(相同组)
  消费者必要参数方法
      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
  • 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.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(手动控制事务)
  • 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,修改配置文件
  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要和子表的一致(源码中的广播表(主表)和绑定规则表判断有问题)
  可以配置读写分离,读不能从写库里面读
  • 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="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
  • 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 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
  • 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
        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
  • 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
  * (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();

}
  • 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
        连接池: 复用连接
            数据库连接池
                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标记)
                    类型指针
                    数组长度
                锁的适用场景
                    偏向锁: 
                    轻量级锁: 
                    重量级锁:     
  • 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 默认开启 是否允许内联同步方法
逃逸分析,标量替换,栈上分配

        垃圾收集算法
        垃圾收集器

数据库调优
架构调优
操作系统调优
    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密集型
    微服务
      水平扩容
      无底洞问题
          让集群保持小而美
    中心化
        公共依赖集群化
    去中心化(优先考虑)
        每个应用携带
        容错性
        伸缩性
        控制力相对较弱
  • 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
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/blog/article/detail/45974
推荐阅读
相关标签
  

闽ICP备14008679号