正文前先来一波福利推荐:
福利一:
百万年薪架构师视频,该视频可以学到很多东西,是本人花钱买的VIP课程,学习消化了一年,为了支持一下女朋友公众号也方便大家学习,共享给大家。
福利二:
毕业答辩以及工作上各种答辩,平时积累了不少精品PPT,现在共享给大家,大大小小加起来有几千套,总有适合你的一款,很多是网上是下载不到。
获取方式:
微信关注 精品3分钟 ,id为 jingpin3mins,关注后回复 百万年薪架构师 ,精品收藏PPT 获取云盘链接,谢谢大家支持!
------------------------正文开始---------------------------
1、 分发层+应用层 的双层nginx架构,提升缓存命中率
缓存架构图:
架构图介绍:
1、缓存命中率低
缓存数据生产服务那一层已经搞定了,相当于三层缓存架构中的本地堆缓存+redis分布式缓存都搞定了
就要来做三级缓存中的nginx那一层的缓存了
如果一般来说,你默认会部署多个nginx,在里面都会放一些缓存,就默认情况下,此时缓存命中率是比较低的
2、如何提升缓存命中率
分发层+应用层,双层nginx
分发层nginx,负责流量分发的逻辑和策略,这个里面它可以根据你自己定义的一些规则,比如根据productId去进行hash,然后对后端的nginx数量取模
将某一个商品的访问的请求,就固定路由到一个nginx后端服务器上去,保证说只会从redis中获取一次缓存数据,后面全都是走nginx本地缓存了
后端的nginx服务器,就称之为应用服务器; 最前端的nginx服务器,被称之为分发服务器
看似很简单,其实很有用,在实际的生产环境中,可以大幅度提升你的nginx本地缓存这一层的命中率,大幅度减少redis后端的压力,提升性能
openresty的设计【源码示例】:
local uri_args = ngx.req.get_uri_args() local productId = uri_args["productId"] local hosts = {"192.168.31.187", "192.168.31.19"} local hash = ngx.crc32_long(productId) local index = (hash % 2) + 1 backend = "http://"..hosts[index] local requestPath = uri_args["requestPath"] requestPath = "/"..requestPath.."?productId="..productId local http = require("resty.http") local httpc = http.new() local resp, err = httpc:request_uri(backend,{ method = "GET", path = requestPath }) if not resp then ngx.say("request error: ", err) return end ngx.say(resp.body) httpc:close()
2、热点问题:超级热数据导致系统崩溃的场景:
3、热点问题:超级热数据导致系统崩溃的场景 --- 解决方案:
openresty中热数据处理切换处理:
storm中的逻辑处理:
private class HotProductFindThread implements Runnable { public void run() { List<Map.Entry<Long, Long>> productCountList = new ArrayList<Map.Entry<Long, Long>>(); List<Long> hotProductIdList = new ArrayList<Long>(); while(true) { // 1、将LRUMap中的数据按照访问次数,进行全局的排序 // 2、计算95%的商品的访问次数的平均值 // 3、遍历排序后的商品访问次数,从最大的开始 // 4、如果某个商品比如它的访问量是平均值的10倍,就认为是缓存的热点 try { productCountList.clear(); hotProductIdList.clear(); if(productCountMap.size() == 0) { Utils.sleep(100); continue; } LOGGER.info("【HotProductFindThread打印productCountMap的长度】size=" + productCountMap.size()); // 1、先做全局的排序 for(Map.Entry<Long, Long> productCountEntry : productCountMap.entrySet()) { if(productCountList.size() == 0) { productCountList.add(productCountEntry); } else { boolean bigger = false; for(int i = 0; i < productCountList.size(); i++){ Map.Entry<Long, Long> topnProductCountEntry = productCountList.get(i); if(productCountEntry.getValue() > topnProductCountEntry.getValue()) { int lastIndex = productCountList.size() < productCountMap.size() ? productCountList.size() - 1 : productCountMap.size() - 2; for(int j = lastIndex; j >= i; j--) { if(j + 1 == productCountList.size()) { productCountList.add(null); } productCountList.set(j + 1, productCountList.get(j)); } productCountList.set(i, productCountEntry); bigger = true; break; } } if(!bigger) { if(productCountList.size() < productCountMap.size()) { productCountList.add(productCountEntry); } } } } // 2、计算出95%的商品的访问次数的平均值 int calculateCount = (int)Math.floor(productCountList.size() * 0.95); Long totalCount = 0L; for(int i = productCountList.size() - 1; i >= productCountList.size() - calculateCount; i--) { totalCount += productCountList.get(i).getValue(); } Long avgCount = totalCount / calculateCount; // 3、从第一个元素开始遍历,判断是否是平均值得10倍 for(Map.Entry<Long, Long> productCountEntry : productCountList) { if(productCountEntry.getValue() > 10 * avgCount) { hotProductIdList.add(productCountEntry.getKey()); // 将缓存热点反向推送到流量分发的nginx中 String distributeNginxURL = "http://192.168.31.227/hot?productId=" + productCountEntry.getKey(); HttpClientUtils.sendGetRequest(distributeNginxURL); // 将缓存热点,那个商品对应的完整的缓存数据,发送请求到缓存服务去获取,反向推送到所有的后端应用nginx服务器上去 String cacheServiceURL = "http://192.168.31.179:8080/getProductInfo?productId=" + productCountEntry.getKey(); String response = HttpClientUtils.sendGetRequest(cacheServiceURL); String[] appNginxURLs = new String[]{ "http://192.168.31.187/hot?productId=" + productCountEntry.getKey() + "&productInfo=" + response, "http://192.168.31.19/hot?productId=" + productCountEntry.getKey() + "&productInfo=" + response }; for(String appNginxURL : appNginxURLs) { HttpClientUtils.sendGetRequest(appNginxURL); } } } Utils.sleep(5000); } catch (Exception e) { e.printStackTrace(); } } } }
使用的http工具类
package com.roncoo.eshop.storm.http; import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.NameValuePair; import org.apache.http.client.HttpClient; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.message.BasicNameValuePair; import org.apache.http.util.EntityUtils; /** * HttpClient工具类 * @author lixuerui * */ @SuppressWarnings("deprecation") public class HttpClientUtils { /** * 发送GET请求 * @param url 请求URL * @return 响应结果 */ @SuppressWarnings("resource") public static String sendGetRequest(String url) { String httpResponse = null; HttpClient httpclient = null; InputStream is = null; BufferedReader br = null; try { // 发送GET请求 httpclient = new DefaultHttpClient(); HttpGet httpget = new HttpGet(url); HttpResponse response = httpclient.execute(httpget); // 处理响应 HttpEntity entity = response.getEntity(); if (entity != null) { is = entity.getContent(); br = new BufferedReader(new InputStreamReader(is)); StringBuffer buffer = new StringBuffer(""); String line = null; while ((line = br.readLine()) != null) { buffer.append(line + " "); } httpResponse = buffer.toString(); } } catch (Exception e) { e.printStackTrace(); } finally { try { if(br != null) { br.close(); } if(is != null) { is.close(); } } catch (Exception e2) { e2.printStackTrace(); } } return httpResponse; } /** * 发送post请求 * @param url URL * @param map 参数Map * @return */ @SuppressWarnings({ "rawtypes", "unchecked", "resource" }) public static String sendPostRequest(String url, Map<String,String> map){ HttpClient httpClient = null; HttpPost httpPost = null; String result = null; try{ httpClient = new DefaultHttpClient(); httpPost = new HttpPost(url); //设置参数 List<NameValuePair> list = new ArrayList<NameValuePair>(); Iterator iterator = map.entrySet().iterator(); while(iterator.hasNext()){ Entry<String,String> elem = (Entry<String, String>) iterator.next(); list.add(new BasicNameValuePair(elem.getKey(), elem.getValue())); } if(list.size() > 0){ UrlEncodedFormEntity entity = new UrlEncodedFormEntity(list, "utf-8"); httpPost.setEntity(entity); } HttpResponse response = httpClient.execute(httpPost); if(response != null){ HttpEntity resEntity = response.getEntity(); if(resEntity != null){ result = EntityUtils.toString(resEntity, "utf-8"); } } } catch(Exception ex){ ex.printStackTrace(); } finally { } return result; } }