赞
踩
这里做个讲解,我的mac系统更新到12.3
,查看PID:控制中心占用了7000端口,故自此把gulimall-coupon 服务的端口改为 7001
谷粒商城-分布式高级篇-ElasticSearch
ES在内存中,所以在检索中优于mysql。ES也支持集群,数据分片存储。
需求:
分析sku在es中如何存储
商品mapping
分析:商品上架在es中是存sku还是spu?
1)、检索的时候输入名字,是需要按照sku的title进行全文检索的
2)、检素使用商品规格,规格是spu的公共属性,每个spu是一样的
3)、按照分类id进去的都是直接列出spu的,还可以切换。
4〕、我们如果将sku的全量信息保存到es中(包括spu属性〕就太多字段了
方案1:
{
skuId:1
spuId:11
skyTitile:华为xx
price:999
saleCount:99
attr:[
{尺寸:5},
{CPU:高通945},
{分辨率:全高清}
]
缺点:如果每个sku都存储规格参数(如尺寸),会有冗余存储,因为每个spu对应的sku的规格参数都一样
方案2:
sku索引
{
spuId:1
skuId:11
}
attr索引
{
skuId:11
attr:[
{尺寸:5},
{CPU:高通945},
{分辨率:全高清}
]
}
先找到4000个符合要求的spu,再根据4000个spu查询对应的属性,封装了4000个id,long 8B*4000=32000B=32KB
1K个人检索,就是32MB
结论:如果将规格参数单独建立索引,会出现检索时出现大量数据传输的问题,会引起网络网络
因此选用方案1,以空间换时间
最终选用的数据模型:
- { “type”: “keyword” }, # 保持数据精度问题,可以检索,但不分词
- “analyzer”: “ik_smart” # 中文分词器
- “index”: false, # 不可被检索,不生成index
- “doc_values”: false # 默认为true,不可被聚合,es就不会维护一些聚合的信息
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": { # 商品的库存 true:有库存 false:无库存
"type": "boolean"
},
"hotScore": { # 热度评分
"type": "long"
},
"brandId": { # 品牌id
"type": "long"
},
"catalogId": { # 分类id
"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": { # 属性的id
"type": "long"
},
"attrName": { # 属性的名字
"type": "keyword",
"index": false,
"doc_values": false
},
"attrValue": { # 属性的值
"type": "keyword"
}
}
}
}
}
}
冗余存储的字段:不用来检索,也不用来分析,节省空间
属性是"type": “nested”,因为是内部的属性进行检索
数组类型的对象会被扁平化处理(对象的每个属性会分别存储到一起)
user.name=["aaa","bbb"]
user.addr=["ccc","ddd"]
这种存储方式,可能会发生如下错误:
错误检索到{aaa,ddd},这个组合是不存在的
数组的扁平化处理会使检索能检索到本身不存在的,为了解决这个问题,就采用了嵌入式属性,数组里是对象时用嵌入式属性(不是对象无需用嵌入式属性)
创建模块: gulimall-search
选择依赖web,但不要在里面选择es、openfeign。 并做降版本处理
将 gulimall-search 服务加入注册中心nacos中去
<dependency>
<groupId>com.hgw.gulimall</groupId>
<artifactId>gulimall-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
application.properties:
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
spring.application.name=gulimall-search
第一步、导入依赖 这里的版本要和所按照的ELK版本匹配
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>7.4.2</version>
</dependency>
在spring-boot-dependencies中所依赖的ES版本位6.8.4,要改掉
<properties>
<java.version>1.8</java.version>
<elasticsearch.version>7.4.2</elasticsearch.version>
</properties>
编写配置,给容器中注入一个 RestHighLevelClient
在 com.hgw.gulimall.search.config
包下创建一个ES的配置类
请求测试项,比如es添加了安全访问规则,访问es需要添加一个安全头,就可以通过requestOptions设置
官方建议把requestOptions创建成单实例
@Configuration
public class GulimallElasticSearchConfig {
public static final RequestOptions COMMON_OPTIONS;
static {
RequestOptions.Builder builder = RequestOptions.DEFAULT.toBuilder();
COMMON_OPTIONS = builder.build();
}
@Bean
public RestHighLevelClient esRestClient() {
RestClientBuilder builder = null;
// 可以指定多个es
builder = RestClient.builder(new HttpHost("124.222.223.222",9200,"http"));
RestHighLevelClient client = new RestHighLevelClient(builder);
return client;
}
}
此外还有多种方法
POST: /product/spuinfo/{spuId}/up
按skuId上架
第一步、在 SpuInfoController 类中编写商品上架Controller层的实现方法
@RestController
@RequestMapping("product/spuinfo")
public class SpuInfoController {
@Autowired
private SpuInfoService spuInfoService;
/**
* 商品上架
* /product/spuinfo/{spuId}/up
* @param spuId
* @return
*/
@PostMapping("/{spuId}/up")
public R spuUp(@PathVariable("spuId") Long spuId){
spuInfoService.up(spuId);
return R.ok();
}
// ......
}
第二步、编写上架实体类
product里组装好,search里上架
商品上架需要在es中保存spu信息并更新spu的状态信息,由于SpuInfoEntity
与索引的数据模型并不对应,所以我们要建立专门的vo进行数据传输。
gulimall-common 服务下的
@Data
public class SkuEsModel {
private Long skuId;
private Long spuId;
private String skuTitle;
private BigDecimal skuPrice;
private String skuImg;
private Long saleCount; // 商品的销量
private boolean hasStock; // 商品的库存
private Long hotScore; // 热度评分
private Long brandId; // 品牌ID
private Long catalogId; // 分类ID
private String brandName; // 品牌名
private String brandImg; // 品牌图片
private String catalogName; //分类的名字
private List<Attr> attrs;
/**
* 商品规格
*/
@Data
public static class Attr{
private Long attrId; // 属性id
private String attrName; // 属性名
private String attrValue; // 属性值
}
}
第三步、编写 查处当前spuId对应的所有sku信息 的实现方法
SkuInfoServiceImpl 实现类下 :
/**
* 根据spuID查询spuId对应的所有sku信息
* @param spuId
* @return
*/
@Override
public List<SkuInfoEntity> getSkusBySpuId(Long spuId) {
List<SkuInfoEntity> list = this.list(new QueryWrapper<SkuInfoEntity>().eq("spu_id", spuId));
return list;
}
第四步、构造sku检索属性
//TODO 4、查询当前sku的所有可以被检索的规格属性
List<ProductAttrValueEntity> baseAttrs = attrValueService.baseAttrListForSpu(spuId);
List<Long> attrIds = baseAttrs.stream().map(attr -> {
return attr.getAttrId();
}).collect(Collectors.toList());
// 过滤掉可以被检索检索的规格属性id
List<Long> searchAttrIds = attrValueService.selectSearchAttrsIds(attrIds);
Set<Long> idSet = new HashSet<>(searchAttrIds);
List<SkuEsModel.Attr> attrsList = baseAttrs.stream().filter(item -> {
return idSet.contains(item.getAttrId());
}).map(item -> {
SkuEsModel.Attr attr = new SkuEsModel.Attr();
BeanUtils.copyProperties(item, attr);
return attr;
}).collect(Collectors.toList());
updateSpuAttr类中
/**
* 在指定的所有属性集合里面,挑出检索属性
* SELECT attr_id FROM pms_attr WHERE attr_id IN(?) AND search_type = 1;
* @param attrIds
* @return
*/
@Override
public List<Long> selectSearchAttrsIds(List<Long> attrIds) {
return this.baseMapper.selectSearchAttrsIds(attrIds);
}
src/main/resources/mapper/product/
ProductAttrValueDao.xml
<select id="selectSearchAttrsIds" resultType="java.lang.Long">
SELECT attr_id FROM pms_attr WHERE attr_id IN
<foreach collection="attrIds" item="id" separator="," open="(" close=")">
#{id}
</foreach>
AND search_type = 1;
</select>
上架要确保还有库存
在ware微服务里添加"查询sku是否有库存"的controller
@RestController
@RequestMapping("ware/waresku")
public class WareSkuController {
@Autowired
private WareSkuService wareSkuService;
// 查询sku是否有库存
@PostMapping("/hasstock")
public R getSkusHasStock(@RequestBody List<Long> skuIds){
// sku_id,stock
List<SkuHasStockVo> vos = wareSkuService.getSkusHasStock(skuIds);
return R.ok().setData(vos);
}
@Override
public List<SkuHasStockVo> getSkusHasStock(List<Long> skuIds) {
List<SkuHasStockVo> collect = skuIds.stream().map(skuId -> {
SkuHasStockVo vo = new SkuHasStockVo();
// 查询当前sku的总库存量
// SELECT SUM(stock-stock_locked) FROM wms_ware_sku WHERE sku_id=1
Long count = baseMapper.getSkuStock(skuId);
vo.setSkuId(skuId);
vo.setHasStock(count>0?true:false);
return vo;
}).collect(Collectors.toList());
return collect;
}
@Mapper
public interface WareSkuDao extends BaseMapper<WareSkuEntity> {
void addStock(@Param("skuId") Long skuId, @Param("wareId") Long wareId, @Param("skuNum") Integer skuNum);
Long getSkuStock(@Param("skuId") Long skuId);
}
WareSkuDao.xml
<select id="getSkuStock" resultType="java.lang.Long">
SELECT SUM(stock-stock_locked) FROM wms_ware_sku WHERE sku_id=#{skuId}
</select>
setData()
、getData()
方法public class R extends HashMap<String, Object> {
private static final long serialVersionUID = 1L;
public R setData(Object data){
put("data", data);
return this;
}
/**
* 利用fastjson进行逆转
* @param typeReference
* @param <T>
* @return
*/
public <T> T getData(TypeReference<T> typeReference){
Object data = get("data"); // 默认是map
String s = JSON.toJSONString(data);
T t = JSON.parseObject(s, typeReference);
return t;
}
@FeignClient("gulimall-ware")
public interface WareFeignService {
/**
* 1、R设计的时候可以加上泛型
* 2、直接返回我们想要的结果
* 3、自己封装解析结果
* 查询sku是否有库存
* @param skuIds
* @return
*/
@PostMapping("/ware/waresku/hasstock")
R<List<SkuHasStockVo>> getSkusHasStock(@RequestBody List<Long> skuIds);
}
Map<Long, Boolean> stockMap = null;
try{
R<List<SkuHasStockVo>> skusHasStock = wareFeignService.getSkusHasStock(skuIdList);
stockMap = skusHasStock.getData().stream().collect(Collectors.toMap(SkuHasStockVo::getSkuId, item -> item.getHasStock()));
} catch (Exception e){
log.error("库存服务查询异常,原因:{}",e);
}
Map<Long, Boolean> finalStockMap = stockMap;
自此商品上架方法的数据都封装好了
/**
* 商品上架
* @param spuId
*/
@Override
public void up(Long spuId) {
// 1、查找当前spuId对应的所有sku信息
List<SkuInfoEntity> skus = skuInfoService.getSkusBySpuId(spuId);
List<Long> skuIdList = skus.stream().map(SkuInfoEntity::getSkuId).collect(Collectors.toList());
//TODO 4、查询当前sku的所有可以被检索的规格属性
List<ProductAttrValueEntity> baseAttrs = attrValueService.baseAttrlistforspu(spuId);
List<Long> attrIds =baseAttrs.stream().map(attr->{
return attr.getAttrId();
}).collect(Collectors.toList());
// 过滤掉可以被检索检索的规格属性id
List<Long> searchAttrIds = attrService.selectSearchAttrIds(attrIds);
Set<Long> idSet = new HashSet<>(searchAttrIds);
// 将所有的规格属性 通过 被检索的规格属性id 过滤出来我们想要的 sku的所有可以被检索的规格属性
List<SkuEsModel.Attr> attrsList = baseAttrs.stream().filter(item -> {
return idSet.contains(item.getAttrId());
}).map(item -> {
SkuEsModel.Attr attr = new SkuEsModel.Attr();
BeanUtils.copyProperties(item, attr);
return attr;
}).collect(Collectors.toList());
//TODO 1、发送远程调用,库存系统查询是否有库存
Map<Long, Boolean> stockMap = null;
try{
R r = wareFeignService.getSkusHasStock(skuIdList);
// 封装成map,下面只需要通过skuId查询是否有库存
TypeReference<List<SkuHasStockVo>> typeReference = new TypeReference<List<SkuHasStockVo>>() {
};
stockMap = r.getData(typeReference).stream().collect(Collectors.toMap(SkuHasStockVo::getSkuId, item -> item.getHasStock()));
}catch (Exception e) {
log.error("库存服务查询异常:原因{}",e);
}
Map<Long, Boolean> finalStockMap = stockMap;
// 2、封装每个sku的信息
List<SkuEsModel> upProducts = skus.stream().map(sku -> {
// 组装需要的数据
SkuEsModel esModel = new SkuEsModel();
BeanUtils.copyProperties(sku, esModel);
esModel.setSkuPrice(sku.getPrice());
esModel.setSkuImg(sku.getSkuDefaultImg());
// 1、发送远程调用,库存系统查询是否有库存
if (finalStockMap == null) {
esModel.setHasStock(true);
} else {
esModel.setHasStock(finalStockMap.get(sku.getSkuId()));
}
//TODO 2、热度评分。刚上架:0
esModel.setHotScore(0L);
//TODO 3、品牌和分类的名字信息,品牌的logo
BrandEntity brand = brandService.getById(sku.getBrandId());
esModel.setBrandName(brand.getName());
esModel.setBrandImg(brand.getLogo());
CategoryEntity category = categoryService.getById(sku.getCatalogId());
esModel.setCatalogName(category.getName());
// 4、查询当前sku的所有可以被检索的规格属性(方法见TODO4)
esModel.setAttrs(attrsList);
return esModel;
}).collect(Collectors.toList());
//TODO 5、将数据发送给es进行保存 gulimall-search
}
@Slf4j
@RequestMapping("/search/save")
@RestController
public class ElasticSaveController {
@Autowired
ProductSaveService productSaveService;
/**
* 上架商品
* @param skuEsModels
* @return
*/
@PostMapping("/product")
public R productStatsUp(@RequestBody List<SkuEsModel> skuEsModels) {
boolean b = false;
try{
b = productSaveService.productStatsUp(skuEsModels);
}catch (Exception e){
log.error("ElasticSaveController商品上架错误: {}",e);
return R.error(BizCodeEnume.PRODUCT_UP_EXCEPTION.getCode(),BizCodeEnume.PRODUCT_UP_EXCEPTION.getMsg());
}
if (!b) {
return R.ok();
} else {
return R.error(BizCodeEnume.PRODUCT_UP_EXCEPTION.getCode(),BizCodeEnume.PRODUCT_UP_EXCEPTION.getMsg());
}
}
}
@Slf4j
@Service
public class ProductSaveServiceImpl implements ProductSaveService {
@Autowired
RestHighLevelClient restHighLevelClient;
@Override
public boolean productStatsUp(List<SkuEsModel> skuEsModels) throws IOException {
// 保存到es
// 1、给es中建立索引并建立好映射关系(我们通过Kibana完成)
// 2、给es中保存这些数据
// BulkRequest bulkRequest, RequestOptions options
BulkRequest bulkRequest = new BulkRequest();
for (SkuEsModel model : skuEsModels) {
// 构造保存请求
IndexRequest indexRequest = new IndexRequest(EsConstant.PRODUCT_INDEX);
indexRequest.id(model.getSkuId().toString());
String modelJson = JSON.toJSONString(model);
indexRequest.source(modelJson, XContentType.JSON);
bulkRequest.add(indexRequest);
}
// 发起请求
BulkResponse bulk = restHighLevelClient.bulk(bulkRequest, GulimallElasticSearchConfig.COMMON_OPTIONS);
//TODO 如果批量错误
boolean b = bulk.hasFailures(); // true:有错误 false:无错误
List<String> collect = Arrays.stream(bulk.getItems()).map(item -> {
return item.getId();
}).collect(Collectors.toList());
log.info("商品上架完成:{},返回数据:{}",collect,bulk.toString());
return b;
}
}
@FeignClient("gulimall-search")
public interface SearchFeignService {
// 上架商品
@PostMapping("/search/save/product")
R productStatusUp(@RequestBody List<SkuEsModel> skuEsModels);
}
//TODO 5、将数据发送给es进行保存 gulimall-search
R r = searchFeignService.productStatusUp(upProducts);
if (r.getCode() == 0){
// 远程调用成功
// TODO 6、远程调用成功 修改当前SPU的状态为上架
baseMapper.updateSpuStatus(spuId, ProductConstant.StatusEnum.SPU_UP.getCode());
} else {
// 远程调用失败
// TODO 7、重复调用?接口幂等性;重试机制?
log.error("商品远程es保存失败");
}
@Mapper
public interface SpuInfoDao extends BaseMapper<SpuInfoEntity> {
void updateSpuStatus(@Param("spuId") Long spuId, @Param("code") int code);
}
SpuInfoDao.xml:
<update id="updateSpuStatus">
UPDATE pms_spu_info SET publish_status=#{code},update_time=NOW() WHERE id=#{spuId}
</update>
此时查看ES中也存入了数据 !
自此写到哪个功能时,再导入相应的服务前端代码
这里对 gulimall-product 服务目录进行了调整:
controller
包名改成了 app
,后台管理的操作web
包,用来写前台的Controller第一步、导入依赖
在 gulimall-product 服务中导入:
devtools
依赖<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!--页面修改时不重启服务器实时更新,热部署-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
第二步、将前端页面复制进项目
将资料中的html>首页资源下的内容复制到 gulimall-product 服务中
index
文件夹复制到 src/main/resources/static
路径下 (静态资源都放在 static 文件下就可以按照路径直接访问)index.html
文件复制到 src/main/resources/templates
路径下第三步、配置文件关闭缓存
spring:
thymeleaf:
cache: false # 关闭缓存
需求:访问 http://127.0.0.1:10000/index.html 来到首页,来到首页时并渲染一集分类
web
目录下创建并编写 IndexController 类@Controller
public class IndexController {
@Autowired
CategoryService categoryService;
@GetMapping({"/","/index.html"})
public String indexPage(Model model){
// TODO 1、查找所有的一级分类
List<CategoryEntity> categoryEntities = categoryService.getLevel1Categorys();
// 视图解析器进行拼串
// 前缀:"classpath:/templates/" 后缀:".html"
model.addAttribute("categorys", categoryEntities);
return "index";
}
}
@Override
public List<CategoryEntity> getLevel1Categorys() {
List<CategoryEntity> categoryEntities = baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", 0));
return categoryEntities;
}
<html lang="en" xmlns:th="http://www.thymeleaf.org">
header_main
>header_banner
>header_main_left
盒子下的ul内容,通过Thymeleaf语法进行渲染<ul>
<li th:each="category : ${categorys}">
<a href="#" class="header_main_left_a" th:attr="ctg-data=${category.catId}">
<b th:text="${category.name}"></b>
</a>
</li>
</ul>
效果展示:
需求:当鼠标滑到一级菜单的时候显示二级、三级分类数据
com/atguigu/gulimall/product/vo
目录下:
@NoArgsConstructor
@AllArgsConstructor
@Data
public class Catelog2Vo {
private String catalog1Id; // 父分类的id(一级分类)
private List<Catelog3Vo> catalog3List; // 三级子分类
private String id;
private String name;
/**
* 三级分类 Vo
*/
@NoArgsConstructor
@AllArgsConstructor
@Data
public static class Catelog3Vo{
private String catalog2Id; // 父分类的id(二级分类)
private String id;
private String name;
}
}
@Controller
public class IndexController {
@Autowired
CategoryService categoryService;
/**
* 渲染二级、三级分类
* @return
*/
@ResponseBody
@GetMapping({"/index/catalog.json"})
public Map<String, List<Catelog2Vo>> getCatalogJson(){
Map<String, List<Catelog2Vo>> catalogJson = categoryService.getCatalogJson();
return catalogJson;
}
}
@Override
public Map<String, List<Catelog2Vo>> getCatalogJson() {
// 1、查出所有分类
List<CategoryEntity> level1Categorys = this.getLevel1Categorys();
// 2、封装数据
Map<String, List<Catelog2Vo>> parent_cid = level1Categorys.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {
// 2.1、每一个一集分类,查到这个一集分类的所有二级分类
List<CategoryEntity> categoryEntities = baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", v.getCatId()));
// 2.2、封装上面的结果
List<Catelog2Vo> catelog2Vos = null;
if (categoryEntities != null) {
catelog2Vos = categoryEntities.stream().map(l2 -> {
Catelog2Vo catelog2Vo = new Catelog2Vo(v.getCatId().toString(), null, l2.getCatId().toString(), l2.getName());
// 2.3、找当前二级分类的三级分类封装成vo
List<CategoryEntity> level3Catelog = baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", l2.getCatId()));
if (level3Catelog!=null) {
List<Catelog2Vo.Catelog3Vo> collect = level3Catelog.stream().map(l3 -> {
// 封装成指定格式
Catelog2Vo.Catelog3Vo catelog3Vo = new Catelog2Vo.Catelog3Vo(l2.getCatId().toString(), l3.getCatId().toString(), l3.getName());
return catelog3Vo;
}).collect(Collectors.toList());
catelog2Vo.setCatalog3List(collect);
}
return catelog2Vo;
}).collect(Collectors.toList());
}
return catelog2Vos;
}));
return parent_cid;
}
修改静态资源:src/main/resources/static/index/js
路径下的 catalogLoader.js 文件的请求路径
$(function(){
$.getJSON("index/catalog.json",function (data) {
var ctgall=data;
测试成功:
域名映射效果:
gulimall.com
gulimall.com
nginx 直接代理给网关,网关进行判断
/api/***
,转交给对应的服务器由于本人使用的是腾讯云服务器,Nginx跑在云服务器上,这里通过使用花生壳内网穿透 + 腾讯云 Nginx 来实现:
/etc/hosts
# h
124.222.223.222 gulimall.com
复制 /mydata/nginx/conf/conf.d
路径下的 default.conf 文件,并重命名为 gulimall.conf
修改 /mydata/nginx/conf/conf.d
路径下的 gulimall.conf
**
server {
listen 80;
server_name gulimall.com;
#charset koi8-r;
#access_log /var/log/nginx/log/host.access.log main;
location / {
proxy_pass http://m374k82881.qicp.vip:80;
}
# ...
[root@hgwtencent conf.d]# docker restart nginx
修改 /mydata/nginx/conf
路径下的 nginx.conf 配置文件
upstream gulimall{
server m374k82881.qicp.vip:16059;
}
诊断域名:m374k82881.qicp.vip 域名IP地址指向:103.46.128.53 转发服务器IP:103.46.128.53 域名已激活内网穿透功能,并与转发服务器IP指向一致 连接转发服务器成功 映射:m374k82881.qicp.vip:16059 局域网服务器:127.0.0.1:88 本机内网IP:192.168.3.222 局域网服务器连接成功
修改 /mydata/nginx/conf/conf.d
路径下的 gulimall.conf 配置文件
server {
listen 80;
server_name gulimall.com;
#charset koi8-r;
#access_log /var/log/nginx/log/host.access.log main;
location / {
proxy_set_header Host $host;
proxy_pass http://gulimall;
}
修改完配置文件进行重启nginx容器服务:
docker restart nginx
配置 gulimall-gateway 服务下的 application.yml 配置文件,加上以下网关配置
- id: gulimall_host_route
uri: lb://gulimall-product
predicates:
- Host=**.gulimall.com
测试成功
本人在最后还是选择了 在本地小鲸鱼中跑了个Nginx来模拟域名访问。
影响性能考虑包括:
首先考虑自己的应用属于CPU密集型还是IO密集型
jdk 的两个小工具 jconsole、jvisualvm(升级版的 jconsole);通过命令启动,可监控本地和远程应用。远程应用需要配置
- jconsole
- jvisualvm
运行状态:
要监控GC,安装插件:工具-插件。可用插件-检查最新版本 报错的时候百度“插件中心”,改个JVM对应的插件中心url.xml.z
安装visual GC
根据视频做的测试:
压测内容 | 压测线程数 | 吞吐量/s | 90%响应时间 | 99%响应时间 |
---|---|---|---|---|
Nginx | 50 | 638 | 27 | 746 |
Gateway | 50 | 43,381 | 2 | 11 |
简单服务 | 50 | 37,674 | 2 | 4 |
首页一级菜单渲染(关闭缓存) | 50 | 130(db,thymeleaf) | 426 | 721 |
首页一级菜单渲染(开启缓存、优化数据库、关日志) | 50 | 98 | 322 | 346 |
三级分类数据获取 | 50 | 45/min(db) | 94,428 | 94,641 |
首页全量数据获取 | 50 | 1.6(静态资源) | 30012 | 30023 |
Nginx+Gateway | 50 | |||
Gateway+简单服务 | 50 | 14,493 | 5 | 35 |
全链路 | 50 | 554 | 74 | 100 |
/static/**
所有请求都由Nginx直接返回第一步、给静态资源搬家
/mydata/nginx/html
路径下创建一个 static 文件夹,用来存放我们的静态资源src/main/resources/static
目录下的 index文件夹下所有内容 剪切到 服务器的 /mydata/nginx/html/static
目录下**第二步、**替换本地的index.html中静态资源的请求路径全部加上
/static/
href="
替换 href="/static/
<script src="
替换 <script src="/static/
<img src="
替换 <img src="/static/
第三步、配置Nginx
location /static/ {
root /usr/share/nginx/html;
}
测试,进去感觉明显快了
-Xmx1024m -Xms1024m -Xmn512m
<img src="/static/index/img/5a154759N5385d5d6.jpg" />
修改实现类 CategoryServiceImpl 的方法,将数据库的多次查询变为一次
/**
* 封装一级、二级、三级分类
* @return
*/
@Override
public Map<String, List<Catelog2Vo>> getCatalogJson() {
/**
* 1、将数据库的多次查询变为一次
*/
List<CategoryEntity> selectList = baseMapper.selectList(null);
// 1、查出所有分类
List<CategoryEntity> level1Categorys = getParent_cid(selectList,0L);
// 2、封装数据
Map<String, List<Catelog2Vo>> parent_cid = level1Categorys.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {
// 2.1、每一个一集分类,查到这个一集分类的所有二级分类
List<CategoryEntity> categoryEntities = getParent_cid(selectList,v.getCatId());
// 2.2、封装上面的结果
List<Catelog2Vo> catelog2Vos = null;
if (categoryEntities != null) {
catelog2Vos = categoryEntities.stream().map(l2 -> {
Catelog2Vo catelog2Vo = new Catelog2Vo(v.getCatId().toString(), null, l2.getCatId().toString(), l2.getName());
// 2.3、找当前二级分类的三级分类封装成vo
List<CategoryEntity> level3Catelog = getParent_cid(selectList,l2.getCatId());
if (level3Catelog!=null) {
List<Catelog2Vo.Catelog3Vo> collect = level3Catelog.stream().map(l3 -> {
// 封装成指定格式
Catelog2Vo.Catelog3Vo catelog3Vo = new Catelog2Vo.Catelog3Vo(l2.getCatId().toString(), l3.getCatId().toString(), l3.getName());
return catelog3Vo;
}).collect(Collectors.toList());
catelog2Vo.setCatalog3List(collect);
}
return catelog2Vo;
}).collect(Collectors.toList());
}
return catelog2Vos;
}));
return parent_cid;
}
private List<CategoryEntity> getParent_cid(List<CategoryEntity> selectList, Long parent_cid) {
List<CategoryEntity> collect = selectList.stream().filter(item -> item.getParentCid() == parent_cid).collect(Collectors.toList());
return collect;
}
业务笔记写到了别处:谷粒商城-分布式高级篇-分布式锁与缓存
Redis 笔记:Redis
此处笔记写到别处: Sentinel服务流控、熔断和降级
微服务架构是一个分布式架构,它按业务划分服务单元,一个分布式系统往往有很多介服务单元。由于服务单元数量众多,业务的复杂性,如果出现了错误和异常,很难去定位。主要体现在,一个请求可能需要调用很多个服务,而内部服务的调用复杂性,决定了问题难以定位。所以微服务架构中,必须实现分布式链路追踪,去跟进一个请求到底有 哪些服务参与,参与的顺序又是怎样的,从而达到每个请求的步骤清哳可见,出了问題,很快定位。
链路追踪組件有 Google 的 Dapper, Twitter 的Zpkin,以及阿里的 Eagleeve(鹰眼)等,它们都是非常优秀的链路追踪开源组件。
第一步、在gulimall-common服务中导入依赖
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.SR3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<!--链路追踪-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
第二步、Docker安装 zipkin 服务器
docker run -d -p 9411:9411 openzipkin/zipkin
第三步、引入zipkin依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
因为 zipkin 同时包含了sleuth,所以将上面sleuth的依赖删掉
第四步、为每个服务都配置依赖
# zipkin服务器的地址
spring.zipkin.base-url=http://127.0.0.1:9411/
# 关闭服务器发现,否则SpringCloud会把 zipkin 的 url当作服务器名称
spring.zipkin.discovery-client-enabled=false
# 设置使用 http 的方式传输数据
spring.zipkin.sender.type=web
# 设置抽样采集率为100%,默认为0.1%,即10%
spring.sleuth.sampler.probability=1
进行一次购物活动,查看 http://localhost:9411/
好啦,就陪大家到这里了!刷题背题去咯~
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。