不优化前,tomcat最大线程数阻碍了系统的最大并发
1. 使用spring Boot内置的Tomcat 在 application.yml中添加
* server.tomcat.accpt-count :等待队列长度,默认100
* server.tomcat.max-connections:最大可连接数,默认10000
* server.tomcat.max-threads:最大工作线程数,默认200
* server.tomcat.min-spare-threads: 最小工作线程数,默认10
- 默认配置下,连接超过10000后会出现拒绝连接情况
- 默认配置下,触发的请求超过200 + 100后会拒绝处理
2. 使用外挂Tomcat
keepAlive 是在http 1.1 中 使用长连接,可能会被黑客利用
keepAliveTimeOut:多少秒后不响应的断开keepalive
maxKeepAliveRequests:多少次请求后keepalive断开失效
使用WebServerFactoryCustomizer < ConfigurableServletWebServerFactory > 定制化内嵌tomcat配置
import org.apache.catalina.connector.Connector;
import org.apache.coyote.http11.Http11NioProtocol;
import org.springframework.boot.web.embedded.tomcat.TomcatConnectorCustomizer;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.server.ConfigurableWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.stereotype.Component;
//当spring 容器中没有tomcatEmaeddedServletContainerFactory 这个bean时,会把此bean加载进spring
@Component
public class WebServerConfiguration implements WebServerFactoryCustomizer<ConfigurableWebServerFactory> {
@Override
public void customize(ConfigurableWebServerFactory factory) {
//使用对应工厂类提供给我们的接口定制化我们的Tomcat connector
( (TomcatServletWebServerFactory)factory ).addConnectorCustomizers(new TomcatConnectorCustomizer() {
@Override
public void customize(Connector connector) {
Http11NioProtocol protocol = (Http11NioProtocol) connector.getProtocolHandler();
// 定制化keepalivetimeout 这是30秒内没有请求则服务器自动断开keepalive链接
protocol.setKeepAliveTimeout(3000);
// 当客户端发送超过10000个请求 ,则自动断开keepalive
protocol.setMaxKeepAliveRequests(1000);
}
});
}
}
线程数量 : 4核Cpu 8G内存单进程调度数800-1000以上即花费巨大的时间在cpu调度上
等待队列长度 : 队列做缓冲池用,但也不能无限长,消耗内存,出对,入队也耗cpu
1. 单机容量问题 ,水平扩展
表象:单机cpu使用率增高,memory占用增加,网络带宽使用增加
CPU us :用户空间的cpu使用情况(用户层代码)
cpu sy :内核空间的cpu使用情况 系统调用
load avarage :1,5,15 分钟load平均值,跟着核数系数,0代表通常 ,代表打满 1+代表等待阻塞
memory : free 空闲内存 used 使用内存
2. nginx web服务器
location 节点path : 指定url映射key
location 节点内容: root 指定location path后对应的跟路径,index 指定默认的访问页
sbin/nginx -c conf/nginx.conf启动
修改配置后直接sbin/nginx -s reload无缝重启
3. nginx 做反向代理服务器
设置upstream server
设置动态请求location为proxy pass 路径
开启tomcat access log 验证
设置 nginx 与tomcat keepAlive为长连接 :
* keepalive_timeout 60 ;
* proxy_http_version 1.1;
* proxy_set_header Connection "";
4 . nginx 高性能原因
1. epoll 多路复用
* java bio 模型,阻塞进程式
* linux select模型,变更触发轮训查找,有1024数量上限
- java server监听客户端链接,如果某个连接有变化,遍历所有的链接,找到发送变化的一个或者多个,然后执行read操作
* epoll模型,变更触发回调直接读取,理论上无上限
- java server 监听客户端链接,是否有变化,设置回调函数,若有变化则直接唤醒自己并执行回调函数
2. master worker 进程模式
3. 协程机制
* 依附于线程的内存模型,切换开销小
* 遇阻塞及归还执行权,代码同步
* 无需加锁
基于cookie 传输sessionid :java tomcat容器session实现迁移到redis
基于token传输类似sessionid :java 代码ssession 实现迁移到redis
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
import org.springframework.stereotype.Component;
@Component
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 3600)
public class RedisConfig {
@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate redisTemplate = new RedisTemplate();
redisTemplate.setConnectionFactory(redisConnectionFactory);
// 修改redis序列化方式
// 首先解决key 的序列化方式
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
redisTemplate.setKeySerializer(stringRedisSerializer);
//解决value的序列化方式
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
return redisTemplate;
}
}
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.imooc.miaoshaproject.service.CachService;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import java.util.concurrent.TimeUnit;
@Service
public class CachServiceImpl implements CachService {
private Cache<String, Object> commonCache = null;
@PostConstruct
public void init() {
// 设置缓存容器的初始容量为10
commonCache = CacheBuilder.newBuilder()
.initialCapacity(10)
.maximumSize(100)//设置缓存中最大可以存储100个key,超过100个之后会按照LRU的策略移除缓存项目
//设置写缓存后多少秒过期
.expireAfterWrite(60, TimeUnit.SECONDS)
.build();
}
@Override
public void serCommonCache(String key, Object value) {
commonCache.put(key,value);
}
@Override
public Object getFromCommonCache(String key) {
return commonCache.getIfPresent(key);
}
}
nginx反向代理前置
依靠文件系统存索引级的文件
依靠内存缓存文件地址
申明一个cache缓存节点的内容
proxy_cache_path /usr/local/openresty/nginx/tem_cache levels=1:2 keys_zone=tmp_cache:100m inactive=7d max_size=10g;
proxy_cache tem_cache;
proxy_cache_key $uri;
proxy_cache_valid 200 206 304 7d;
数据是从本地磁盘中读取,不如内存中快,并不推荐
6. nginx lua缓存
lua协程机制
function foo(a)
print("foo 函数输出", a)
return coroutine.yield(2 * a) -- 返回2*a的值
end
co = coroutine.create(function(a, b)
print("第一次协同程序输出", a, b)
local r = foo(a + 1)
print("第二次协同程序输出", r)
local r, s = coroutine.yield(a + b, a - b) --a ,b 的值为第一次调用协同程序是传入
print("第三次协同程序输出", r, s) -- b的值为第二次调用协同程序时传入
return b, "结束协同程序"
end)
print("main",coroutine.resume(co,1,10))
print("------------------")
print("main",coroutine.resume(co,"r"))
print("------------------")
print("main",coroutine.resume(co,"x","y"))
print("------------------")
print("main",coroutine.resume(co,"x","y"))
nginx协程机制
1) ngx_http_post_read_phase:
接收到完整的http头部后处理的阶段,它位于uri重写之前,实际上很少有模块会注册在该阶段,默认的情况下,该阶段被跳过。
2)ngx_http_server_rewrite_phase:
uri与location匹配前,修改uri的阶段,用于重定向,也就是该阶段执行处于server块内,location块外的重写指令,在读取请求头的过程中nginx会根据host及端口找到对应的虚拟主机配置。
3)ngx_http_find_config_phase:
根据uri寻找匹配的location块配置项阶段,该阶段使用重写之后的uri来查找对应的location,值得注意的是该阶段可能会被执行多次,因为也可能有location级别的重写指令。
4)ngx_http_rewrite_phase:
上一阶段找到location块后再修改uri,location级别的uri重写阶段,该阶段执行location基本的重写指令,也可能会被执行多次。
5)ngx_http_post_rewrite_phase:
防止重写url后导致的死循环,location级别重写的后一阶段,用来检查上阶段是否有uri重写,并根据结果跳转到合适的阶段。
6)ngx_http_preaccess_phase:
下一阶段之前的准备,访问权限控制的前一阶段,该阶段在权限控制阶段之前,一般也用于访问控制,比如限制访问频率,链接数等。
7)ngx_http_access_phase:
让http模块判断是否允许这个请求进入nginx服务器,访问权限控制阶段,比如基于ip黑白名单的权限控制,基于用户名密码的权限控制等。
8)ngx_http_post_access_phase:
访问权限控制的后一阶段,该阶段根据权限控制阶段的执行结果进行相应处理,向用户发送拒绝服务的错误码,用来响应上一阶段的拒绝。
9)ngx_http_try_files_phase:
为访问静态文件资源而设置,try_files指令的处理阶段,如果没有配置try_files指令,则该阶段被跳过。
10)ngx_http_content_phase:
处理http请求内容的阶段,大部分http模块介入这个阶段,内容生成阶段,该阶段产生响应,并发送到客户端。
11)ngx_http_log_phase:
处理完请求后的日志记录阶段,该阶段记录访问日志。
http无法介入的阶段有4个:
3)ngx_http_find_config_phase
5)ngx_http_post_rewrite_phase
8)ngx_http_post_access_phase
9)ngx_http_try_files_phase
剩余的7个阶段,http模块均能介入,每个阶段可介入模块的个数也是没有限制的,多个http模块可同时介入同一阶段并作用于同一请求。
init_by_lua: 系统启动时调用
init_worker_by_lua: worker 进程启动时调用
set_by_lua : nginx变量用复杂lua return
rewrite_by_lua: 重写url规则
access_by_lua: 权限验证阶段
content_by_lua: 内容输出节点
使用lua脚本返回数据
location /miaosha/get{
default_type “text/html”
content_by_lua_file …/lua/staticitem.lua;
}
staticitem.lua
ngx.say("hello static item lua")
function get_from_cache(key)
local cache_ngx = ngx.shared.my_cache
local value = cache_ngx:get(key)
return value
end
function set_to_cache(key, value, exptime)
if not exptimed then
exptime = 0
end
local cache_ngx = ngx.shared.my_cache
local succ, err, forcible = cache_ngx:set(key, value, exptime)
return succ
end
local args = ngx.req.get_uri_args();
local id = args["id"]
local item_model = get_from_cache("item_" .. id)
if item_model == nil then
local resp = ngx.location.capture("/item/get?id="..id)
item_model = resp.body
set_to_cache("item_"..id,item_model,1*60)
end
ngx.say(item_model)
使用openresty中的redis模块连接redis
local args = ngx.req.get_uri_args()
local id = args["id"]
local redis = require "resty.redis"
local cache = redis:new()
local ok, err = cache:connect("11.11.11.11", 6379)
local item_model = cache:get("item" .. id)
if item_model == ngx.null or item_model == nil then
local resp = ngx.location.capture("/item/get?id=" .. id)
item_model = resp.body
end
ngx.say(item_model)
DNS 用CNAME解析到源站
回源缓存设置
强推失效
定义:在服务端完成html,css,甚至js的load渲染成纯html 文件后直接以静态资源的方式部署到cdn上
phantomjs
扣减库存缓存化
方案:
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import javax.imageio.ImageIO;
public class CodeUtil {
private static int width = 90;// 定义图片的width
private static int height = 20;// 定义图片的height
private static int codeCount = 4;// 定义图片上显示验证码的个数
private static int xx = 15;
private static int fontHeight = 18;
private static int codeY = 16;
private static char[] codeSequence = {
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R',
'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' };
/**
* 生成一个map集合
* code为生成的验证码
* codePic为生成的验证码BufferedImage对象
* @return
*/
public static Map<String,Object> generateCodeAndPic() {
// 定义图像buffer
BufferedImage buffImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
// Graphics2D gd = buffImg.createGraphics();
// Graphics2D gd = (Graphics2D) buffImg.getGraphics();
Graphics gd = buffImg.getGraphics();
// 创建一个随机数生成器类
Random random = new Random();
// 将图像填充为白色
gd.setColor(Color.WHITE);
gd.fillRect(0, 0, width, height);
// 创建字体,字体的大小应该根据图片的高度来定。
Font font = new Font("Fixedsys", Font.BOLD, fontHeight);
// 设置字体。
gd.setFont(font);
// 画边框。
gd.setColor(Color.BLACK);
gd.drawRect(0, 0, width - 1, height - 1);
// 随机产生40条干扰线,使图象中的认证码不易被其它程序探测到。
gd.setColor(Color.BLACK);
for (int i = 0; i < 30; i++) {
int x = random.nextInt(width);
int y = random.nextInt(height);
int xl = random.nextInt(12);
int yl = random.nextInt(12);
gd.drawLine(x, y, x + xl, y + yl);
}
// randomCode用于保存随机产生的验证码,以便用户登录后进行验证。
StringBuffer randomCode = new StringBuffer();
int red = 0, green = 0, blue = 0;
// 随机产生codeCount数字的验证码。
for (int i = 0; i < codeCount; i++) {
// 得到随机产生的验证码数字。
String code = String.valueOf(codeSequence[random.nextInt(36)]);
// 产生随机的颜色分量来构造颜色值,这样输出的每位数字的颜色值都将不同。
red = random.nextInt(255);
green = random.nextInt(255);
blue = random.nextInt(255);
// 用随机产生的颜色将验证码绘制到图像中。
gd.setColor(new Color(red, green, blue));
gd.drawString(code, (i + 1) * xx, codeY);
// 将产生的四个随机数组合在一起。
randomCode.append(code);
}
Map<String,Object> map =new HashMap<String,Object>();
//存放验证码
map.put("code", randomCode);
//存放生成的验证码BufferedImage对象
map.put("codePic", buffImg);
return map;
}
public static void main(String[] args) throws Exception {
//创建文件输出流对象
OutputStream out = new FileOutputStream("/Users/hzllb/Desktop/javaworkspace/miaoshaStable/"+System.currentTimeMillis()+".jpg");
Map<String,Object> map = CodeUtil.generateCodeAndPic();
ImageIO.write((RenderedImage) map.get("codePic"), "jpeg", out);
System.out.println("验证码的值为:"+map.get("code"));
}
}
private ExecutorService executorService;
private RateLimiter orderCreateRateLimiter;
@PostConstruct
public void init(){
executorService = Executors.newFixedThreadPool(20);
orderCreateRateLimiter = RateLimiter.create(300);
}
if(orderCreateRateLimiter.acquire() <= 0){
throw new BusinessException(EmBusinessError.RATELIMIT);
}