在資訊暴炸的時代,為了在專案中提高資料載入效率,快取技術是必不可以少的,快取技術存在於應用場景的方方面面。從瀏覽器請求,到反向代理伺服器,從程式內快取到分散式快取。其中快取策略,演算法也是層出不窮,下面要說的就是一套如何實現一套可以對後端伺服器形成最小壓力的架構。
一、快取的解析
借一下上一篇文章中的圖,其實現在問很多人怎麼提高資料的訪問速度和效率,很多人都能回答出做快取處理,這句話可以說對也可以說不對,對是因為沒錯,快取是可以提高效率,但並不是做了快取處理他的併發量就一定能提升的上來,就以上圖為例,如果說前端請求很高,達到了2000K,如果所有的資料載入在後端redis中做了快取,如果這時所有使用者請求全部從前端傳到後端,伺服器照樣承受不了這麼高併發,所以說只在後端提升了載入速度是沒有用的,正確的做法是把快取在到Nginx中而不是存到後臺伺服器,原因就是Nginx能承受的併發比tomcat要高。
有了這個思路後,架構就要換一下了,如上圖,當第一次請求過來時走後臺去後臺Redis中查詢,並存在Nginx下面的Redis,之後的所有請求就不用走後臺查詢,這樣效能就提升上來了,這時還要考慮一個問題,那就是如果Nginx下的Redis本身沒有快取資料又怎麼搞,那就要去Nginx快取中去找,因為Nginx本身是帶有快取功能的,如果Nginx中存在要的快取就直接從Nginx中返回給使用者,這樣一設計後可以幾十萬個請求就只有幾十個落在了後臺伺服器上了。這種設計除了可以有效提高載入速度、降低後端服務負載之外,還可以防止快取雪崩,這也就是電商中用的多級快取架構體系。
二、應用
我們在看購物平臺時有很多商品優先推薦展示,這些其實都是推廣商品,並非真正意義上的熱門商品,首頁展示這些商品資料需要載入效率極高,並且商城首頁訪問頻率也是極高,所以需要對首頁資料做快取處理,首先想到的就是Redis快取。
接下來要作的東西就是設計推廣商品的表結構及邏輯設計,其實這推廣產品不一定只在首頁出現,可能在別的地方也有,所以可以設計一張表存放這玩意,表結構如下
DROP TABLE IF EXISTS `ad_items`; CREATE TABLE `ad_items` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(100) DEFAULT NULL, `type` int(3) DEFAULT NULL COMMENT '分類,1首頁推廣,2列表頁推廣', `sku_id` varchar(60) DEFAULT NULL COMMENT '展示的產品', `sort` int(11) DEFAULT NULL COMMENT '排序', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=123 DEFAULT CHARSET=utf8;
建立實體類:
@Data @AllArgsConstructor @NoArgsConstructor //MyBatisPlus表對映註解 @TableName(value = "ad_items") public class AdItems implements Serializable{ @TableId(type = IdType.AUTO) private Integer id; private String name; private Integer type; private String skuId; private Integer sort; }
public interface AdItemsMapper extends BaseMapper<AdItems> { }
public interface SkuService extends IService<Sku> { /** * 根據推廣產品分類ID查詢Sku列表 * @param id * @return */ List<Sku> typeSkuItems(Integer id); }
@Service public class SKuServiceImpl extends ServiceImpl<SkuMapper, Sku> implements SkuService { @Resource private AdItemsMapper adItemsMapper; @Resource private SkuMapper skuMapper; @Override public List<Sku> typeSkuItems(Integer id) { //1.查詢當前分類下的所有列表資訊 QueryWrapper<AdItems> adItemsQueryWrapper = new QueryWrapper<AdItems>(); adItemsQueryWrapper.eq("type",id); List<AdItems> adItems = adItemsMapper.selectList(adItemsQueryWrapper); //2.根據推廣列表查詢產品列表資訊 List<String> skuids = adItems.stream().map(adItem->adItem.getSkuId()).collect(Collectors.toList()); return skuids==null || skuids.size()<=0? null : skuMapper.selectBatchIds(skuids); } }
@RestController @RequestMapping(value = "/sku") public class SkuController { @Autowired private SkuService skuService; /**** * 根據推廣分類查詢推廣產品列表 * */ @GetMappingpublic List<Sku> typeItems(@RequestParam(value = "id")Integer id){ //查詢 List<Sku> skus = skuService.typeSkuItems(id); return skus; } }
經過上面的程式碼就可以實現熱門商品推廣操作,但是有個問題,因為前面說過熱點資料訪問量大,是要放在快取裡面進行快取處理的,下面就來完成這最後一步
三、快取處理
在改程式碼前先要說明幾個常用的註解:
#Redis配置 redis: host: 192.168.32.135 port: 6379
然後在啟動類上開啟快取
然後修改SKuServiceImpl類,加上紅框內內容
完成這一步快取就做好了,但是有個問題,前面說過熱點商品可能在多個地方都存被呼叫的情況,針對這個情況就要寫feigin介面,在寫feigin介面同時我也順便把修改和刪除操作給寫了,後面就懶著改這個類了
在spring-cloud-api的pom檔案中先引用以下包
<!--工具包--> <dependency> <groupId>com.ghy</groupId> <artifactId>spring-cloud-common</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency> <!--openfeign--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> <version>2.2.5.RELEASE</version> </dependency>
然後在spring-cloud-goods-api伺服器寫feigin介面
@FeignClient(value = "spring-cloud-goods-service")
public interface SkuFeign {
/****
* 根據推廣分類查詢推廣產品列表
*/
@GetMapping(value = "/sku")
List<Sku> typeItems(@RequestParam(value = "id")Integer id);
/****
* 根據推廣分類刪除推廣產品列表
*
*/
@DeleteMapping(value = "/sku")
RespResult delTypeItems(@RequestParam(value = "id")Integer id);
/****
* 根據推廣分類修改推廣產品列表
*/
@PutMapping(value = "/sku")
RespResult updateTypeItems(@RequestParam(value = "id")Integer id);
}
四、Lua+Redis實現多級快取
前面說過Lua和多級快取,接下來就把這兩個東西結合起來實現;前面說過前端請求過來後第一次會通過Nginx會分發到tomact進行查詢,然後將查詢的資料放到nginx中去;現在要做的操作就是通過Lua指令碼完善後面的操作,操作流程是這樣,當請求過來
nginx會先執行lua指令碼資料先從redis中去查詢先看下redis有沒有對應的資料,如果有則直接把資料響應給使用者,沒有再經過tomact查詢;這時有的人會問,如果一不小心把Redis伺服器中的資料全清空了怎麼辦,其實問題也不大,前面也提過Nginx本身也帶有快取,當Redis沒查詢資料時,他會去Nginx自身查下,看能不能找到資料;
下面先寫一個lua指令碼;
進入/usr/local/openresty/nginx/lua,在裡面建立一個aditem.lua指令碼
--資料響應型別JSON ngx.header.content_type="application/json;charset=utf8" --Redis庫依賴 local redis = require("resty.redis"); local cjson = require("cjson"); --獲取id引數(type) local id = ngx.req.get_uri_args()["id"]; --key組裝 local key = "ad-items-skus::"..id --建立連結物件 local red = redis:new() --設定超時時間 red:set_timeout(2000) --設定伺服器連結資訊 red:connect("192.168.32.135", 6379) --查詢指定key的資料 local result=red:get(key); --關閉Redis連結 red:close() if result==nil or result==null or result==ngx.null then return true else --輸出資料 ngx.say(result) end
#推廣產品查詢 location /sku{ content_by_lua_file /usr/local/openresty/nginx/lua/aditem.lua; }
五、nginx代理快取
前面有提過,當Redis快取沒有資料時,請求會去nginx快取找資料;在這時就要提到一個東西,那就是proxy_cache ;proxy_cache 是用於 proxy 模式的快取功能,proxy_cache 在 Nginx 配置的 http 段、server 段中分別寫入不同的配置。http 段中的配置用於定義 proxy_cache 空間,server 段中的配置用於呼叫 http 段中的定義,啟用對server 的快取功能。要想使用它就要完成兩個步驟:第一個步驟是定義快取空間;第二個步驟是在指定地方使用定義的快取 。
1、開啟Proxy_Cache快取,這個需要在nginx.conf中配置才能開啟快取:
proxy_cache_path /usr/local/openresty/nginx/cache levels=1:2 keys_zone=proxy_cache:10m max_size=1g inactive=60m use_temp_path=off;
#先找Nginx快取 rewrite_by_lua_file /usr/local/openresty/nginx/lua/aditem.lua; #啟用快取openresty_cache proxy_cache proxy_cache; #針對指定請求快取 #proxy_cache_methods GET; #設定指定請求會快取 proxy_cache_valid 200 304 60s; #最少請求1次才會快取 proxy_cache_min_uses 1; #如果併發請求,只有第1個請求會去伺服器獲取資料 #proxy_cache_lock on; #唯一的key proxy_cache_key $host$uri$is_args$args; #動態代理,這是在快取中都沒查到的情況
proxy_pass http://192.168.32.32:8081;
1:先查詢Redis快取 2:Redis快取沒資料,直接找Nginx快取 3:Nginx快取沒資料,則找真實伺服器
這時還可以發現在cache目錄下多了目錄個檔案,這個東西就是Nginx快取;這裡補充下,如果你的linux是布在雲上而你的專案是內外,那麼動態代理配置的就是專案的公網地址;
六、Cache_Purge代理快取清理
#清理快取
location ~ /purge(/.*) {
#清理快取
proxy_cache_purge proxy_cache $host$1$is_args$args;
}