ElasticSearch
商品发布代码
es索引的设计
(1)方便检索{
skuId:1
spuId:1
skuTitle:华为xx
price:9988
saleCount:99
attrs:[
{尺寸:5寸}
{CPU:高通945}
]}
冗余:100万*20 = 1000000 * 2kb = 2G
(2)
sku索引{
skuId:1
spuId:1
skuTitle:华为xx
price:9988
saleCount:99
}
attr索引{
spuId:1
attrs:[
{尺寸:5寸}
{CPU:高通945}
]}}
es 的数据都是存储在内存中的
搜索:小米
10000个,4000spu
分步,4000个spu对应的所有属性;
esClient:spuId:[4000个spuId] 4000 * 8 = 32000byte = 32kb
来100万个并发
32kb * 1000000 = 32 GB
问题:网络传输的数据量太大,相对于空间的冗余我们的网络IO更珍贵,所以选用第一种。
“index”: false, 不能作为检索字段,就是 query 的时候不能使用这个
“doc_values”: false 插入的时候该字段不进行倒排索引维护,就是不进行分词处理
PUT product
{
"mappings": {
"properties": {
"skuId": {
"type": "long"
},
"spuId": {
"type": "keyword"
},
"skuTitle": {
"type": "text",
"analyzer": "ik_smart"
},
"skuPrice": {
"type": "keyword"
},
"skuImg": {
"type": "keyword",
"index": false,
"doc_values": false
},
"saleCount": {
"type": "long"
},
"hasStock": {
"type": "boolean"
},
"hotScore": {
"type": "long"
},
"brandId": {
"type": "long"
},
"catalogId": {
"type": "long"
},
"brandName": {
"type": "keyword",
"index": false,
"doc_values": false
},
"brandImg": {
"type": "keyword",
"index": false,
"doc_values": false
},
"catalogName": {
"type": "keyword",
"index": false,
"doc_values": false
},
"attrs": {
"type": "nested",
"properties": {
"attrId": {
"type": "long"
},
"attrName": {
"type": "keyword",
"index": false,
"doc_values": false
},
"attrValue": {
"type": "keyword"
}
}
}
}
}
}
feign 源码分析
SynchronousMethodHandler
/*
1. 构造请求数据,将对象转为json
RequestTemplate template = buildTemplateFromArgs.create(argv);
2. 发送请求进行执行(执行成功会解码响应数据)
executeAndDecode(template, options);
3. 执行请求会有重试机制
while(true){
try{
executeAndDecode(template, options);
}catch(){
retryer.continueOrPropagate(e);
}
}
*/
public Object invoke(Object[] argv) throws Throwable {
// 构建一个RestTemplate
RequestTemplate template = buildTemplateFromArgs.create(argv);
Options options = findOptions(argv);
Retryer retryer = this.retryer.clone();
// 循环调用
while (true) {
try {
return executeAndDecode(template, options);
} catch (RetryableException e) {
try {
// 出现异常就会执行重试机制
retryer.continueOrPropagate(e);
} catch (RetryableException th) {
Throwable cause = th.getCause();
if (propagationPolicy == UNWRAP && cause != null) {
throw cause;
} else {
throw th;
}
}
if (logLevel != Logger.Level.NONE) {
logger.logRetry(metadata.configKey(), logLevel);
}
continue;
}
}
}
前端商城系统的架构
服务端渲染式开发
用户访问nginx ,nginx反向代理到网关,网关在转发请求给我们的微服务。在网关进行统一的鉴权认证、限流、日志收集等工作。nginx还存储静态资源(比如css、js),动态资源放在微服务里面(就是html模板),这就是实现了动静分离。静态资源放在nginx,所有要经过服务器处理的页面放在对应的微服务中。动静分离的好处减轻微服务的压力。
Nginx搭建域名访问环境
域名映射效果
- nginx直接代理给网关,网关判断
- 如果/api/,转交给对应的服务器
- 如果是 满足域名,转交给对应的服务
访问流程:用户访问页面(gulimall.com)-> 请求来到nginx,nginx根据server 的配置转发请求 -> nginx将请求转发给gateway -> gateway 转发请求给具体的微服务 -> 微服务被调用
正向代理和反向代理
所谓正向代理就是顺着请求的方向进行的代理,即代理服务器他是由你配置为你服务,去请求目标服务器地址。
所谓反向代理正好与正向代理相反,**代理服务器是为目标服务器服务的。
Nginx
nginx 配置文件结构
nginx 动静分离
访问 http://gulimall.com/static/index/js/hello.js 回去找 /usr/share/nginx/html/static/index/hello.js,就是将端口号后面的路径拼接上我们配置的root 路径来寻找静态资源。
注意点
- Nginx 代理时会丢失请求的host信息等
压力测试 & 性能监控
缓存与分布式锁
商品检索
kibana测试检索语句
需求:模糊匹配(skuTitle),过滤(按照分类,品牌(多值匹配),价格区间,库存,属性(按照属性id和属性值匹配)),排序(按照价格排序),分页,高亮(skuTitle),聚合分析(有几个分类、几个品牌、几个属性、对应的值和名字
相关api:查询结果字段高亮,nested类型字段检索和聚合api
注意:
-
使用filter 的目的是不计算相关性得分
- term 是精确匹配,
- terms也是精确匹配,但是匹配的对象可以有多个值(比如匹配数组、nested类型的字段)
细节点:
- 我们可以通过子聚合拿到按照品牌聚合之后,品牌的名字
- 聚合的数据是在我们query查询的结果进行的。子聚合是在父聚合的结果进行的聚合。
GET /gulimall_product/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"skuTitle": "华为"
}
}
],
"filter": [
{
"term": {
"catalogId": "225"
}
},
{
"terms": {
"brandId": [
"9",
"1",
"2"
]
}
},
{
"range": {
"skuPrice": {
"gte": 6000,
"lte": 9000
}
}
},
{
"term": {
"hasStock": "false"
}
},
{
"nested": {
"path": "attrs",
"query": {
"bool": {
"must": [
{
"term": {
"attrs.attrId": {
"value": "15"
}
}
},
{
"terms": {
"attrs.attrValue": [
"海思(Hisilicon)",
"以官网信息为准"
]
}
}
]
}
}
}
}
]
}
},
"sort": [
{
"skuPrice": {
"order": "desc"
}
}
],
"from": 0,
"size": 10,
"highlight": {
"fields": {
"skuTitle": {
}
},
"pre_tags": "<b style='color:red'>",
"post_tags": "</b>"
},
"aggs": {
"cata_log_agg": {
"terms": {
"field": "catalogId",
"size": 10
},
"aggs": {
"cata_name_agg": {
"terms": {
"field": "catalogName",
"size": 10
}
}
}
},
"brand_id_agg":{
"terms": {
"field": "brandId",
"size": 10
},"aggs": {
"brand_name_agg": {
"terms": {
"field": "brandName",
"size": 10
}
},
"brand_img_agg": {
"terms": {
"field": "brandImg",
"size": 10
}
}
}
},
"attrs_agg":{
"nested": {
"path": "attrs"
},"aggs": {
"attr_id_agg": {
"terms": {
"field": "attrs.attrId",
"size": 10
},"aggs": {
"attr_name_agg": {
"terms": {
"field": "attrs.attrName",
"size": 10
}
},
"attr_value_agg": {
"terms": {
"field": "attrs.attrValue",
"size": 10
}
}
}
}
}
}
}
}
ESCliennt 的使用
导入依赖
<properties>
<!-- 注意:SpringBoot 默认就写好了es的版本,我们需要改成我们自己的 -->
<elasticsearch.version>7.6.2</elasticsearch.version>
</properties>
<!-- 导入 es的rest-high-level-client -->
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>7.6.2</version>
</dependency>
配置类
@Configuration
public class GulimallElasticSearchConfig {
// 因为发送请求的时候需要这个类型的参数
public static final RequestOptions COMMON_OPTIONS;
static {
RequestOptions.Builder builder = RequestOptions.DEFAULT.toBuilder();
// builder.addHeader("Authorization", "Bearer " + TOKEN);
// builder.setHttpAsyncResponseConsumerFactory(
// new HttpAsyncResponseConsumerFactory
// .HeapBufferedResponseConsumerFactory(30 * 1024 * 1024 * 1024));
COMMON_OPTIONS = builder.build();
}
@Bean
public RestHighLevelClient restHighLevelClient() {
RestHighLevelClient client = new RestHighLevelClient(
RestClient.builder(
new HttpHost("192.168.1.10", 9200, "http")));
return client;
}
}
使用步骤
-
创建SearchRequest,里面可以存放我们编写的DSL
-
发送请求给es,restHighLevelClient.search(searchRequest, GulimallElasticSearchConfig.COMMON_OPTIONS)
-
数据的序列化和反序列化使用json
// 创建检索请求
SearchRequest searchRequest = new SearchRequest().indices("bank");
// 构造检索条件
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
....
searchRequest.source(searchSourceBuilder);
// 发送检索请求
SearchResponse searchResponse = restHighLevelClient.search(searchRequest, GulimallElasticSearchConfig.COMMON_OPTIONS);
....
// 发序列化结果为对象
Account account = JSON.parseObject(sourceAsString, Account.class);