配置文件 @RefreshScope实现配置自动更新
项目中的使用:每个微服务创建自己的命名空间,使用配置分组区分环境,dev、test、pro、
application.properties对应命名空间下,所属分组、应用名.properties。
图片表示application.properties。对应nacos中coupon命名空间,prod分组,Data Id名称为应用名gulimall-coupon.properties文件。yml同理
配置集 同时加载多个配置集
注释配置文件
使用配置集
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 spring.cloud.nacos.config.namespace =29176167-82d9-46b0-9a00-687acca63fd4 spring.cloud.nacos.config.ext-config[0].data-id =datasource.yml spring.cloud.nacos.config.ext-config[0].group =dev spring.cloud.nacos.config.ext-config[0].refresh =true spring.cloud.nacos.config.ext-config[1].data-id =mybatis.yml spring.cloud.nacos.config.ext-config[1].group =dev spring.cloud.nacos.config.ext-config[1].refresh =true spring.cloud.nacos.config.ext-config[2].data-id =other.yml spring.cloud.nacos.config.ext-config[2].group =dev spring.cloud.nacos.config.ext-config[2].refresh =true 
此时访问localhost:7000/coupon/coupon/test
值是们项目中配置的值,并没有使用到相对应的配置文件
这是因为
No active profile set, falling back to default profiles: default 我们没有设置使用配置文件的分组,默认使用的是DEFAULT_GROUP分组。当我们的配置集中没有程序中使用的配置则会使用默认分组的配置文件,但是coupon命名空间下没有这个默认分组的配置文件,则使用原程序的配置application.properties
当取消注释设置使用prod分组后
Gatewa网关 
引入common模块中引入了跟数据库有关的相关依赖,而网关项目又没配置数据库信息,从而启动报错,主启动程序排除解决报错。
1 2 3 4 5 6 7 8 9 10 @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}) @EnableDiscoveryClient public  class  GulimallGatewayApplication      public  static  void  main (String[] args)           SpringApplication.run(GulimallGatewayApplication.class, args);     } } 
前端发送请求到网关存在跨域问题
协议名、域名、端口都要相同,其中一个不同都会产生跨越问题。 
网关统一解决跨域问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Configuration public  class  GuliMallCorsConfiguration      @Bean      public  CorsWebFilter corsWebFilter ()           UrlBasedCorsConfigurationSource source = new  UrlBasedCorsConfigurationSource();         CorsConfiguration corsConfiguration = new  CorsConfiguration();                  corsConfiguration.addAllowedHeader("*" );         corsConfiguration.addAllowedMethod("*" );         corsConfiguration.addAllowedOrigin("*" );         corsConfiguration.setAllowCredentials(true );         source.registerCorsConfiguration("/**" , corsConfiguration);         return  new  CorsWebFilter(source);     } } 
只希望有一个解决跨域,不希望有多个。将另一个注释,这里是renren-fast解决了。(网关解决跨域,人人就不要解决了,否则重复!没配网关的时候不会出现跨域问题,就是人人后台解决了)
人人快速开发 
product/category转换成路由为product-category
根据规则页面则应该创建在views目录下product包下的category.vue文件中。
前端项目发送请求到网关中
网关重写路径RewritePath
https://docs.spring.io/spring-cloud-gateway/docs/2.2.9.RELEASE/reference/html/#global-filters 
uri: lb://renren-fast: lb负载均衡://应用名
返回的数据Object中data属性中的data才是我们想要的。所用我们可以使用ES6的解构赋值语法。
只拿出返回数据的data属性值,data.data中则包含我们所要的数据。
前端Vue 
框中内容回显,应该使用请求查询当前数据库,得到数据再回显到页面上。避免当多人在页面上操作时一个人修改了,会导致当前数据滞后。
以上图片ES6新语法。
拖拽修改菜单功能 draggable开启拖拽功能
allow-drop 拖拽时判定目标节点能否被放置`Function(draggingNode, dropNode, type)``
``type` 参数有三种情况:’prev’、’inner’ 和 ‘next’,分别表示放置在目标节点前、插入至目标节点和放置在目标节点后
node-drop 拖拽成功完成时触发的事件。共四个参数,依次为:被拖拽节点对应的 Node、结束拖拽时最后进入的节点、被拖拽节点的放置位置(before、after、inner)、event
批量删除 
使用组件自带的方法ref="xxx"
this.$refs.menuTree.getCheckedNodes();调用树形组件的getCheckedNodes 方法
OOS云存储 阿里云OSS云存储首先创建一个Bucket。
Bucket 
公共读代表读不需要账户密码,写操作则需要。
可以点击上传文件进行上传操作,通过url访问。
SDK https://help.aliyun.com/document_detail/32009.html?spm=a2c4g.11186623.6.923.4c6513c03fK1WG 
首先安装SDK
1 2 3 4 5 <dependency >     <groupId > com.aliyun.oss</groupId >      <artifactId > aliyun-sdk-oss</artifactId >      <version > 3.10.2</version >  </dependency > 
上传文件 简单上传文件
Endpoint地域节点,在Bucekt页面概览中可以找到。
在测试中将上面的代码进行测试。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 String endpoint = "yourEndpoint" ; String accessKeyId = "yourAccessKeyId" ; String accessKeySecret = "yourAccessKeySecret" ; OSS ossClient = new  OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret); PutObjectRequest putObjectRequest = new  PutObjectRequest("examplebucket" , "exampledir/exampleobject.txt" , new  File("D:\\localpath\\examplefile.txt" )); ossClient.putObject(putObjectRequest); ossClient.shutdown();             
上传成功后,就能在我们给定的路径看到我们上传的文件。
Alibaba Cloud OSS https://github.com/alibaba/spring-cloud-alibaba/blob/master/README-zh.md 
https://github.com/alibaba/aliyun-spring-boot/tree/master/aliyun-spring-boot-samples/aliyun-oss-spring-boot-sample 
1 2 3 4 5 <dependency >     <groupId > com.alibaba.cloud</groupId >      <artifactId > aliyun-oss-spring-boot-starter</artifactId >      <version > 1.0.0</version >  </dependency > 
将我们之前导入的SDK注释掉。这里要指定一下版本,可以区阿里云的Maven仓库查看依赖https://maven.aliyun.com/mvn/search 
坑 
配置文件配置OSS
1 2 3 4 5 6 7 alibaba:   cloud:      access-key:      secret-key:      oss:        endpoint:  
可行 1 2 3 4 <dependency >     <groupId > com.alibaba.cloud</groupId >      <artifactId > spring-cloud-starter-alicloud-oss</artifactId >  </dependency > 
1 2 3 4 5 6 7 spring:   cloud:      alicloud:        access-key:  xxxxxxxxxxxxxx        secret-key:  xxxxxxxxxxxxxxxxxxxxxx        oss:          endpoint:  oss-cn-shenzhen.aliyuncs.com  
我们可以通过OSSClient对象进行相关操作,省略了创建OSSClient的步骤。
1 2 3 4 5 6 7 8 9 10 11 12 @SpringBootTest class  GulimallProductApplicationTests      @Autowired(required = false)      OSSClient ossClient;     @Test      public  void  testOSSUpload ()           ossClient.putObject("mall-kylin" , "exampledir/ossClient.txt" , new  File("C:\\Users\\kylin\\Desktop\\OSS.txt" ));                  ossClient.shutdown();     } } 
目前尚未知晓原因。
服务端签名后直传 https://help.aliyun.com/document_detail/31926.html?spm=a2c4g.11186623.6.1749.5a3c43d33sLRZe 
选择代码示例Java。
应用服务器核心代码解析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 protected  void  doGet (HttpServletRequest request, HttpServletResponse response)             throws  ServletException, IOException  {        String accessId = "<yourAccessKeyId>" ;          String accessKey = "<yourAccessKeySecret>" ;          String endpoint = "oss-cn-hangzhou.aliyuncs.com" ;          String bucket = "bucket-name" ;          String host = "https://"  + bucket + "."  + endpoint;                   String callbackUrl = "http://88.88.88.88:8888" ;         String dir = "user-dir-prefix/" ;                   OSS ossClient = new  OSSClientBuilder().build(endpoint, accessId, accessKey);         try  {             long  expireTime = 30 ;             long  expireEndTime = System.currentTimeMillis() + expireTime * 1000 ;             Date expiration = new  Date(expireEndTime);                          PolicyConditions policyConds = new  PolicyConditions();             policyConds.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0 , 1048576000 );             policyConds.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, dir);             String postPolicy = ossClient.generatePostPolicy(expiration, policyConds);             byte [] binaryData = postPolicy.getBytes("utf-8" );             String encodedPolicy = BinaryUtil.toBase64String(binaryData);             String postSignature = ossClient.calculatePostSignature(postPolicy);             Map<String, String> respMap = new  LinkedHashMap<String, String>();             respMap.put("accessid" , accessId);             respMap.put("policy" , encodedPolicy);             respMap.put("signature" , postSignature);             respMap.put("dir" , dir);             respMap.put("host" , host);             respMap.put("expire" , String.valueOf(expireEndTime / 1000 ));                          JSONObject jasonCallback = new  JSONObject();             jasonCallback.put("callbackUrl" , callbackUrl);             jasonCallback.put("callbackBody" ,                     "filename=${object}&size=${size}&mimeType=${mimeType}&height=${imageInfo.height}&width=${imageInfo.width}" );             jasonCallback.put("callbackBodyType" , "application/x-www-form-urlencoded" );             String base64CallbackBody = BinaryUtil.toBase64String(jasonCallback.toString().getBytes());             respMap.put("callback" , base64CallbackBody);             JSONObject ja1 = JSONObject.fromObject(respMap);                          response.setHeader("Access-Control-Allow-Origin" , "*" );             response.setHeader("Access-Control-Allow-Methods" , "GET, POST" );             response(request, response, ja1.toString());         } catch  (Exception e) {                          System.out.println(e.getMessage());         } finally  {              ossClient.shutdown();         }     } 
这一段代码是返回Json解决跨域,跨域我们前面解决了。Json则使用的是@RestController也不用了。所以删掉这段代码直接返回respMap。
由于导入了spring-cloud-starter-alicloud-oss所以创建OssClient步骤可以省略,文件前缀按日期格式来划分。回调服务器的URL暂时用不到注释掉。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 @RestController public  class  OssController      @Resource      OSSClient ossClient;     @Value("${spring.cloud.alicloud.oss.endpoint}")      private  String endpoint;     @Value("${spring.cloud.alicloud.oss.bucket}")      private  String bucket;     @Value("${spring.cloud.alicloud.access-key}")      private  String accessId;     @GetMapping("/oss/policy")      public  Map<String, String> policy ()           String host = "https://"  + bucket + "."  + endpoint;                            String format = new  SimpleDateFormat("yyyy-MM-dd" ).format(new  Date());         String dir = format + "/" ;          try  {             long  expireTime = 30 ;             long  expireEndTime = System.currentTimeMillis() + expireTime * 1000 ;             Date expiration = new  Date(expireEndTime);                          PolicyConditions policyConds = new  PolicyConditions();             policyConds.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0 , 1048576000 );             policyConds.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, dir);             String postPolicy = ossClient.generatePostPolicy(expiration, policyConds);             byte [] binaryData = postPolicy.getBytes("utf-8" );             String encodedPolicy = BinaryUtil.toBase64String(binaryData);             String postSignature = ossClient.calculatePostSignature(postPolicy);             Map<String, String> respMap = new  LinkedHashMap<String, String>();             respMap.put("accessid" , accessId);             respMap.put("policy" , encodedPolicy);             respMap.put("signature" , postSignature);             respMap.put("dir" , dir);             respMap.put("host" , host);             respMap.put("expire" , String.valueOf(expireEndTime / 1000 ));                          JSONObject jasonCallback = new  JSONObject();                          jasonCallback.put("callbackBody" ,                     "filename=${object}&size=${size}&mimeType=${mimeType}&height=${imageInfo.height}&width=${imageInfo.width}" );             jasonCallback.put("callbackBodyType" , "application/x-www-form-urlencoded" );             String base64CallbackBody = BinaryUtil.toBase64String(jasonCallback.toString().getBytes());             respMap.put("callback" , base64CallbackBody);             return  respMap;         } catch  (Exception e) {                          System.out.println(e.getMessage());         } finally  {             ossClient.shutdown();         }         return  null ;     } } 
同时配置网关
http://localhost:88/api/thirdparty/oss/policy 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 spring:   cloud:      gateway:               routes:                   -  id:  test_route            uri:  https://www.baidu.com            predicates:              -  Query=url,baidu                   -  id:  qq_route            uri:  https://www.qq.com            predicates:              -  Query=url,qq          -  id:  product_route            uri:  lb://gulimall-product            predicates:              -  Path=/api/product/**            filters:              -  RewritePath=/api/(?<segment>.*),/$\{segment}          -  id:  third_party_route            uri:  lb://gulimall-third-party            predicates:              -  Path=/api/thirdparty/**            filters:              -  RewritePath=/api/thirdparty/(?<segment>.*),/$\{segment}          -  id:  admin_route            uri:  lb://renren-fast            predicates:              -  Path=/api/**            filters:              -  RewritePath=/api/(?<segment>.*),/renren-fast/$\{segment}  
前端Upload组件 拿到外网访问的Bucket域名
修改成action的地址
向阿里云OSS地发送请求的时候,出现了跨域问题。
配置跨域设置。
此时就解决了跨域问题。
表格自定义显示logo图片https://element.eleme.io/#/zh-CN/component/table 
表单校验 
https://element.eleme.io/#/zh-CN/component/form 
Form 组件提供了表单验证的功能,只需要通过 rules 属性传入约定的验证规则,并将 Form-Item 的 prop 属性设置为需校验的字段名即可。校验规则参见 async-validator 
JSR303校验 
javax.validation.constraints包下注解进行校验。
控制器参数使用@Valid注解标注,迭代注解@Validated,获取校验结果BindingResult
统一异常处理 使用@ControllerAdvice注解这是一个增强的 Controller。使用这个 Controller ,可以实现三个方面的功能:
全局异常处理 
全局数据绑定 
全局数据预处理 
 
@ExceptionHandler异常处理方法,处理指定的异常类型。
@RestControllerAdvice=@RequestBody+@ControllerAdvice
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 @Slf4j @RestControllerAdvice(basePackages = "com.kylin.gulimall.product.controller") public  class  MallExceptionControllerAdvice           @ExceptionHandler(value = MethodArgumentNotValidException.class)      public  R handlerValidException (MethodArgumentNotValidException e)           log.error("数据校验出现问题{},异常类型{}" , e.getMessage(), e.getClass());         BindingResult bindingResult = e.getBindingResult();         Map<String, String> map = new  HashMap<>(16 );         bindingResult.getFieldErrors().forEach(fieldError -> {             map.put(fieldError.getField(), fieldError.getDefaultMessage());         });         return  R.error(BizCodeEnum.VALID_EXCEPTION.getCode(), BizCodeEnum.VALID_EXCEPTION.getMsg()).put("data" , map);     }          @ExceptionHandler(value = Throwable.class)      public  R handleException (Throwable throwable)           return  R.error(BizCodeEnum.UNKNOWN_EXCEPTION.getCode(), BizCodeEnum.UNKNOWN_EXCEPTION.getMsg());     } } 
定义错误代码枚举类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 public  enum  BizCodeEnum      UNKNOWN_EXCEPTION(10000 , "系统未知异常" ),     VALID_EXCEPTION(10001 , "参数格式校验失败" ),     SMS_CODE_EXCEPTION(10002 , "验证码获取频率太高,稍后再试" ),     TO_MANY_REQUEST(10003 , "请求流量过大" ),     SMS_SEND_CODE_EXCEPTION(10403 , "短信发送失败" ),     USER_EXIST_EXCEPTION(15001 , "用户已经存在" ),     PHONE_EXIST_EXCEPTION(15002 , "手机号已经存在" ),     LOGINACTT_PASSWORD_ERROR(15003 , "账号或密码错误" ),     SOCIALUSER_LOGIN_ERROR(15004 , "社交账号登录失败" ),     NOT_STOCK_EXCEPTION(21000 , "商品库存不足" ),     PRODUCT_UP_EXCEPTION(11000 , "商品上架异常" );     private  int  code;     private  String msg;     BizCodeEnum(int  code, String msg) {         this .code = code;         this .msg = msg;     }     public  int  getCode ()           return  code;     }     public  String getMsg ()           return  msg;     } } 
分组校验 
首先定义两个分组
校验注解中的group属性指定所属分组
参数中使用@Validated注解给value赋值,表明该参数所属分组。
group属性没指定分组,个人感觉所属空分组。每个参数都指定不同分组的话,非常冗余。
自定义校验注解 
可以参考别人写好的注解
直接将其复制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE}) @Retention(RUNTIME) @Documented @Constraint(validatedBy = {}) public  @interface  ListValue {    String message ()  default  " {com.kylin.common.valid.ListValue.message}";      Class<?>[] groups() default {};     Class<? extends Payload>[] payload() default {};     int[] value() default {}; } 
导入依赖
1 2 3 4 5 <dependency >     <groupId > jakarta.validation</groupId >      <artifactId > jakarta.validation-api</artifactId >      <version > 2.0.1</version >  </dependency > 
自定义注解的默认message消息会从ValidationMessages.properties中读取message名称属性的值。也就是这里配置的com.kylin.common.valid.ListValue.message
接着编写自定义校验器实现ConstraintValidator接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 /**  * 自定义参数校验器  *  * @author kylin  */ public class ListValueConstraintValidator implements ConstraintValidator<ListValue, Integer> {     private Set<Integer> set = new HashSet<>();     /**      * 初始化方法      *      * @param constraintAnnotation 注解      */     @Override     public void initialize(ListValue constraintAnnotation) {         int[] value = constraintAnnotation.value();         for (int i : value) {             set.add(i);         }     }     /**      * 判断是否校验成功      *      * @param value   需要校验的值      * @param context      * @return      */     @Override     public boolean isValid(Integer value, ConstraintValidatorContext context) {         return set.contains(value);     } } 
在自定义校验注解中@Constraint(validatedBy = {})指定校验器。@Constraint(validatedBy = {ListValueConstraintValidator.class})
Elasticsearch 安装 docker安装elasticsearch
docker pull elasticsearch:7.4.2
创建目录,挂载文件。授权 
配置elasticsearch,允许所有的ip访问 
docker允许命令 容器名为elasticsearch 开发9200、9300端口。当前以当节点允许,配置Java内存,-v挂载,-d运行images 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 mkdir -p /mydata/elasticsearch/config mkdir -p /mydata/elasticsearch/data chmod -R 777 /mydata/elasticsearch/ echo  "http.host: 0.0.0.0" >>/mydata/elasticsearch/config/elasticsearch.ymldocker run --name elasticsearch -p 9200:9200 -p 9300:9300 --privileged=true  \ -e "discovery.type=single-node"  \ -e ES_JAVA_OPTS="-Xms64m -Xmx128m"  \ -v /mydata/elasticsearch/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml \ -v /mydata/elasticsearch/data:/usr/share/elasticsearch/data \ -v /mydata/elasticsearch/plugins:/usr/share/elasticsearch/plugins \ -d elasticsearch:7.4.2 
free -m查看内存命令
docker安装kibana
docker pull kibana:7.4.2
1 docker run --name kibana -e ELASTICSEARCH_HOSTS=http://192.168.56.10:9200 -p 5601:5601 -d kibana:7.4.2 
入门操作 新增 
PUT和POST都可以
POST新增。如果不指定id,会自动生成id。指定id就会修改这个数据,并新增版本号。 
PUT可以新增可以修改。PUT必须指定id。由于PUT需要指定id,我们一般用来做修改操作,不指定id会报错。 
 
查询文档 
_seq_no乐观锁?if_seq_no=0&if_primary_term=1
更新文档 
删除文档或索引 
批量API 
进阶操作 
检索信息 
Query DSL https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl.html 
IK分词器 安装 vagrant 使用root账号连接开启过程
安装wget
yum install wget
1 2 3 4 5 6 7 wget https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.4.2/elasticsearch-analysis-ik-7.4.2.zip unzip elasticsearch-analysis-ik-7.4.2.zip rm -rf *.zip mv ./* ik 
可以进入到容器里面
docker exec -it  elasticsearch /bin/bash
进入到了cd /bin/
运行elasticsearch-plugin list
安装成功。退出容器,重启docker restart elasticserach
词库 
可视化插件 https://blog.csdn.net/ljcgit/article/details/103856634 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 ls 结果如下: LICENSE.txt  README.textile  config  lib   modules NOTICE.txt   bin             data    logs  plugins   cd  config  ls 结果如下: elasticsearch.keystore  ingest-geoip  log4j2.properties  roles.yml  users_roles elasticsearch.yml       jvm.options   role_mapping.yml   users   vi elasticsearch.yml   http.cors.enabled: true  http.cors.allow-origin: "*"  
1 2 3 docker pull mobz/elasticsearch-head:5 docker run -d --name es_admin -p 9100:9100 mobz/elasticsearch-head:5 
Nginx 
include /etc/nginx/conf.d/*.conf;
这里会将这个目录下的配置文件一并包括生效,所以要注意这里配置文件所配置的信息。 
负载均衡到网关 首先配置本机的host输入gulimall.com访问虚拟机IP地址80端口
虚拟机的80端口运行了Nginx,docker中的/etc/nginx/conf.d/gulimall.conf中配置了server_name gulimall.com当访问的时候会携带host参数,代理地址为http://gulimall
而Nginx的配置文件nginx.conf中配置了gulimall的负载均衡转发到了我们的网关。
我们的网关则把请求转发到了我们的gulimall-product服务中。
JMeter 
nginx测试 向http://192.168.56.10/发送测试
使用docker stats实时查看容器运行情况
当我点击运行测试的时候,我们能看见
所以nginx是CPU密集型
这里出现的异常是我们线程组给stop导致的,从而请求失败。
吞吐量/s:8086.8 
90%响应时间:7 
99%相应时间:18 
 
网关测试 向http://localhost:88/发送测试
吞吐量/s:26274.7 
90%响应时间:3 
99%相应时间:9 
 
简单服务测试 访问http://localhost:10000/hello
吞吐量/s:45000 
90%响应时间:2 
99%相应时间:4 
 
网关+简单服务测试 
访问测试http://localhost:88/hello
吞吐量/s:6409.0 
90%响应时间:3 
99%相应时间:13 
 
两个一加起来吞吐量大量减少。
nginx+网关+简单服务测试 访问http://gulimall.com/hello
吞吐量/s:2279.2 
90%响应时间:34 
99%相应时间:52 
 
中间件越多,性能损失越多,大多损失在网络交互了。
首页一级菜单渲染 访问测试http://localhost:10000/
tymeleaf开启缓存 
吞吐量/s:2632.7 
90%响应时间:27 
99%相应时间:50 
 
日志打印级别提高为erro 
吞吐量/s:3125.5 
90%响应时间:23 
99%相应时间:42 
 
字段设置为索引 
首页三级分类数据获取 访问测试http://localhost:10000/index/catalog.json
吞吐量/s:323.2(db) 
90%响应时间:176 
99%相应时间:266 
 
首页全量数据获取 访问测试http://localhost:10000/
高级设置中勾选从HTML文件获取所有内容的资源
吞吐量/s:62(静态资源) 
90%响应时间:1122 
99%相应时间:1510 
 
异常是因为有些文件报404
Nginx动静分离优化 
将静态目录复制到虚拟机/mydata/nginx/html/static的目录下
进入docker容器,编写配置/etc/nginx/conf.d/gulimall.conf文件
1 location /static/ {	root /usr/share/nginx/html;} 
其中容器内目录/usr/share/nginx/html目录挂载到了/mydata/nginx/html/也就是我们存放静态资源的地方。
访问http://gulimall.com进行测试
缓存 
redis 1 2 3 4 <dependency >     <groupId > org.springframework.boot</groupId >      <artifactId > spring-boot-starter-data-redis</artifactId >  </dependency > 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public  class  StringRedisTemplate  extends  RedisTemplate <String , String > 	 	public  StringRedisTemplate ()   		setKeySerializer(RedisSerializer.string()); 		setValueSerializer(RedisSerializer.string()); 		setHashKeySerializer(RedisSerializer.string()); 		setHashValueSerializer(RedisSerializer.string()); 	} 	 	public  StringRedisTemplate (RedisConnectionFactory connectionFactory)   		this (); 		setConnectionFactory(connectionFactory); 		afterPropertiesSet(); 	} 	protected  RedisConnection preProcessConnection (RedisConnection connection, boolean  existingConnection)   		return  new  DefaultStringRedisConnection(connection); 	} } 
StringRedisTemplate是专门操作string类型数据。 
使用redis将数据存入到缓存中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Override public  Map<String, List<Catelog2Vo>> getCatelogJson() {         String catalogJson = redisTemplate.opsForValue().get("catalogJson" );     if  (StringUtils.isEmpty(catalogJson)) {                  Map<String, List<Catelog2Vo>> catalogJsonFromDB = getCatalogJsonFromDB();                  String s = JSON.toJSONString(catalogJsonFromDB);         redisTemplate.opsForValue().set("catalogJson" , s);         return  catalogJsonFromDB;     }          Map<String, List<Catelog2Vo>> result = JSON.parseObject(catalogJson, new  TypeReference<Map<String, List<Catelog2Vo>>>() {     });     return  result; } 
此时压测访问首页三级分类数据获取http://localhost:10000/index/catalog.json(日志关闭,thymeleaf缓存开启,索引创建)
吞吐量/s:1351 
90%响应时间:44 
99%相应时间:99 
 
出现堆外内存溢出:OutOfDirectMemoryError(lettuce5.2.1暂时没出现,5.1.8出现问题)
springboot2.0以后默认使用lettuce作为操作redis的客户端。它使用netty进行网络通信。 
lettuce的bug导致netty堆外内存溢出 
 
缓存穿透 
缓存雪崩 
缓存击穿 
分布式锁 
结果放入缓存这步操作应该放在锁内,否则会因为网络通信延迟导致又查询了一次数据库。 
Redisson 1 2 3 4 <dependency >     <groupId > org.springframework.boot</groupId >      <artifactId > spring-boot-starter-data-redis</artifactId >  </dependency > 
https://github.com/redisson/redisson/wiki/Table-of-Content 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Bean(destroyMethod = "shutdown") public  RedissonClient redisson ()           Config config = new  Config();     config.useSingleServer().setAddress("redis://192.168.56.10:6379" );          RedissonClient redissonClient = Redisson.create(config);     return  redissonClient; } 
缓存数据一致性 
SpringCache 
引入依赖
1 2 3 4 <dependency >     <groupId > org.springframework.boot</groupId >      <artifactId > spring-boot-starter-cache</artifactId >  </dependency > 
1 2 3 4 5 6 7 8 9 10 @Cacheable(value = {"category"}, key = "#root.method.name", sync = true) @Override public  List<CategoryEntity> getLevel1Categories ()      System.out.println("get Level 1 Categories........" );     long  l = System.currentTimeMillis();     List<CategoryEntity> categoryEntities = this .baseMapper.selectList(             new  QueryWrapper<CategoryEntity>().eq("parent_cid" , 0 ));     System.out.println("消耗时间:"  + (System.currentTimeMillis() - l));     return  categoryEntities; } 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 @EnableConfigurationProperties(CacheProperties.class) @Configuration @EnableCaching public  class  MyCacheConfig           @Bean      public  RedisCacheConfiguration redisCacheConfiguration (CacheProperties cacheProperties)           RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();                  config = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new  StringRedisSerializer()));         config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new  GenericJackson2JsonRedisSerializer()));         CacheProperties.Redis redisProperties = cacheProperties.getRedis();                  if  (redisProperties.getTimeToLive() != null ) {             config = config.entryTtl(redisProperties.getTimeToLive());         }         if  (redisProperties.getKeyPrefix() != null ) {             config = config.prefixKeysWith(redisProperties.getKeyPrefix());         }         if  (!redisProperties.isCacheNullValues()) {             config = config.disableCachingNullValues();         }         if  (!redisProperties.isUseKeyPrefix()) {             config = config.disableKeyPrefix();         }         return  config;     } } 
相关配置文件
1 2 3 4 5 6 7 8 9 10 spring.cache.type =redis spring.cache.redis.time-to-live =3600000 spring.cache.redis.key-prefix =CACHE_ spring.cache.redis.use-key-prefix =true spring.cache.redis.cache-null-values =true 
异步 
线程池 JUC之线程池 | Kylin (codekylin.cn) 
CompletableFuture CompletableFuture类似前端的promise