• 亿级流量场景下,大型缓存架构设计实现【2】


     正文前先来一波福利推荐:

    福利一:

    百万年薪架构师视频,该视频可以学到很多东西,是本人花钱买的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;  
        }  
    	
    }
    

      

  • 相关阅读:
    接口测试--apipost中cookie管理器的使用
    python解释器换了路径,导致pip安装失败解决方法
    Jmeter之Bean shell使用(二)
    Jmeter之Bean shell使用(一)
    BeanShell生成随机数
    Jmeter之Json 提取器
    Jmeter全面信息学习笔记
    python模块之codecs
    open()和with open()的区别
    【图像处理】第二次实验:二维快速傅里叶变换与离散余弦变换
  • 原文地址:https://www.cnblogs.com/gxyandwmm/p/11665923.html
Copyright © 2020-2023  润新知