谷粒商城学习笔记

262 阅读25分钟

分布式基础(全栈开发篇)

1分布式基础概念

1.1微服务

微服务架构风格,将一个单独的应用程序开发为一套小服务,每个小服务运行灾自己的进程中,并使用轻量级机制通信,通常是HTTP API。这些服务围绕业务能力来构建,并通过完全自动化部署机制来独立部署。这些服务使用不同的编程语言来书写,以及不同数据存储技术,并保持最低限度的集中式管理。

简而言之:拒绝大型单体应用,基于业务边界进行服务微化拆分,各个服务独立部署运行。

1.2集群&分布式&节点

集群是个物理形态,分布式是个工作方式。

只要是一推机器,就可以叫集群,他们是不是一起协作着干活。这个谁也不知道;

《分布式系统原理与范型》定义:

“分布式系统是若干独立计算机的集合,这些计算机对于用户来说就像单个相关系统”

分布式系统(distributed system)是建立灾网络之上的软件系统。

分布式是指将不同的业务在不同的地方

集群指的是将几台服务器集中在一起,实现同一个业务。

例如:京东是一个分布式系统,众多业务运行在不同的机器,所有业务构成一个大型的业务集群,每一个小的业务,比如用户系统,访问压力大的时候一台服务器是不够的。我们就应该将用户系统部署到多个服务器,也就是每个业务系统也可以做集群;

分布式中的每一个节点,都可以做集群。而集群并不一定就是分布式的。

节点:集群中的一个服务器。

1.3远程调用

在分布式系统中,各个服务可能处于不同主机,但是服务之间不可避免的需要相互调用,我们称为远程调用。

SpringCloud中使用的HTTP+JSON的方式完成远程调用

1.4负载均衡

分布式系统中,A服务需要调用B服务,B服务在多个机器中都存在,A调用任意一个服务器均可完成功能。

为了使每一个服务器都不要太忙或者太闲,我们可以负载均衡的调用每一个服务器,提升网站的健壮性。

常见负载均衡算法:

  • 轮询:为第一个请求选择健康池中的第一个后端服务器,然后按顺序往后依次选择,直到最后一个,然后循环
  • 最小连接:优先选择连接数最小的,也就是压力最小的后端服务器,在会话较长的情况下可以采取这种方式。
  • 散列:根据请求源的IP的散列(hash)来选择要转发的服务器。这种方式可以一定程度上保证特定用户能连接到相同的服务器。如果你的应用需要处理状态而要求用户能连接到和之前相同的服务器,可以考虑采取这种方式。

1.5服务注册/发现&注册中心

A服务调用B服务,A服务并不知道B服务当前在哪几台服务器有,哪些正常的,哪些服务已经下线。解决这个问题可以引入注册中心;

如果某些服务下线,我们其他人可以实时的感知到其他服务的状态,从而避免调用不可用的服务

1.6配置中心

每个服务最终都有大量的配置,并且每个服务都可能部署在多台机器上。我们经常需要变更配置,我们可以让每个服务在配置中心获取自己的位置。

配置中心用来集中管理微服务的配置信息

1.7服务的熔断和降级

在微服务架构中,微服务之间通过网络通信,存在相互依赖,当其中一个服务不可用时,有可能造成雪崩效应。要防止这样的情况,必须要有容错机制来保护服务。

  1. 服务熔断

    设置服务的超时,当调用的服务经常失败到达某个阈值,我们可以开启断路保护机制,后来的请求不再去调用这个服务。本地直接返回默认的数据

  2. 服务降级

    在运维期间,当系统处于高峰期,系统资源紧张,我们可以让非核心业务降级运行。降级:某些服务不处理,或者简单处理【抛异常、放回null、调用Mock数据。调用Fallback处理逻辑】

1.8API网关

在微服务架构中,API Gateway作为整体架构的重要组件,它抽象了微服务中都需要的公共功能,同时提供了客户端负载均衡,服务自动熔断,灰度发布,统一认证,限流流控,日志统计等丰富的功能,帮助我们解决很多API管理难题。

2项目架构图

2.1项目微服务架构图

谷粒商城-微服务架构图.jpg

2.2微服务划分图

image-20220727211651711.png

3软件

3.1Vagrant+VirtualBox

快速安装linux虚拟机

3.2Docker

虚拟化运行环境技术。Docker基于镜像,可以秒级启动各种容器。每种容器都是一个完整的运行环境,容器之间相互隔离。

mysql

docker run -p 3306:3306 --name mysql -v /mydata/mysql/log:/var/log/mysql \
-v /mydata/mysql/data:/var/lib/mysql \
-v /mydata/mysql/conf:/var/etc/mysql \
-e MYSQL_ROOT_PASSWORD=123456 \
-d mysql:5.7

redis

docker run -p 6379:6379 --name redis  \
-v /mydata/redis/data:/data \
-v /mydata/redis/conf/redis.conf:/etc/redis/redis.conf \
-d redis:6.2.7 redis-server /etc/redis/redis.conf

4分布式组件

4.1 SpringCloud Alibaba

github.com/alibaba/spr…

SpringCloud Nacos 作为注册中心

引入依赖

下载nacos-server服务器

将微服务注册到注册中心,在application配置注册中心地址

使用@EnableDiscoveryClient注解开启服务注册与发现功能的客户端

SpringCloud Nacos 作为配置中心

作为配置中统一进行配置管理

启动nacos :

1.引入依赖

2.创建一个bootstrap.properties(优先于application.properties加载),springboot的规定

​ 配置当前应用的名字和配置中心服务器的地址;

3.需要个配置中心默认添加一个 数据集(Data Id)名字为:当前应用名.properties

4.给 应用名.propertise 添加任何配置

5.动态获取配置

​ @RefreshScope //动态获取并刷新配置

​ @value("${配置项的名}")//获取某个配置的值

​ 如果配置中心和当前应用的配置文件中都配置相同的项,优先使用配置中心的配置。

6.细节

​ 命名空间:配置隔离;

​ 默认:public(保留空间);默认新增的所有配置都在public空间

​ a.开发、测试、生产,环境不一样,配置不一样;利用命名空间来作环境隔离,可以新建多个命名空间,通过在bootstrap.properties修改不同的命名空间 ;

spring.cloud.nacos.config.namespace=fd66894b-5120-4906-86be-21e6e911152d

​ b.每一个微服务之间相互隔离配置,每一个微服务都创建自己的命名空间,这样只加载自己命名空间下的所有配置

​ 配置集:所用的配置的集合;

​ 配置集ID:类似配置文件名=Data ID :配置文件名

​ 配置分组:

​ 默认所用的配置集都属于:DEFAULT_GROUP;

​ 11/11,618,12/12;不同的时候可以使用不同的组,通过在bootstrap.properties修改不同的group;

谷粒商城项目:每个微服务创建自己的命名空间,使用配置分组来区分环境,dev、test、prod;

7.同时加载多个配置集

随着业务的壮大,可能会有很多的配置,不会将所用的配置写在一个配置文件中,不好维护和管理,一般做法是差分配置文件;

​ 跟数据源有关的配置文件写在一个配置文件中,datasource.properties;

​ 跟框架有关的配置文件写在一个配置文件中,mybatis.properties;

​ ...

​ 通过在bootstrap.properties 进行修改引用;

​ 微服务任何配置信息,任何配置文件都可以放在配置中心中,只需要在bootstrap.properties 说明加载配置中心哪些配置文件即可;

以前SpringBoot任何方式(@Value、@ConfigurationProperties)从配置问价获取值,都能使用。

配置中心有的优先配置中心的

4.2 SpringCloud

Feign声明式远程调用

Feign是一个声明式的HTTP客户端,它的目的就是让远程调用更加好用。

会员服务(member)调用优惠券服务(coupon),只需在会员服务引入openfeign,引入openfeign就用了调用别的服务的能力

使用@EnableFeignClients开启feign的远程调用功能,@EnableFeignClients(basePackages = "com.atguigu.gulimall.member.feign")

//Feign调用流程
 1、构造请求数据,将对象转为json;
                 RequestTemplate template = buildTemplateFromArgs.create(argv);
            2、发送请求进行执行(执行成功会解码响应数据):
                 executeAndDecode(template);
            3、执行请求会有重试机制
                 while(true){
                     try{
                       executeAndDecode(template);
                     }catch(){
                         try{retryer.continueOrPropagate(e);}catch(){throw ex;}
                         continue;
                     }
            
                 }
            

feign的使用

@FeignClient("gulimall-coupon")
public interface CouponFeignService {


    /**
     * 1、CouponFeignService.saveSpuBounds(spuBoundTo);
     *      1)、@RequestBody将这个对象转为json。
     *      2)、找到gulimall-coupon服务,给/coupon/spubounds/save发送请求。
     *          将上一步转的json放在请求体位置,发送请求;
     *      3)、对方服务收到请求。请求体里有json数据。
     *          (@RequestBody SpuBoundsEntity spuBounds);将请求体的json转为SpuBoundsEntity;
     * 只要json数据模型是兼容的。双方服务无需使用同一个to
     * @param spuBoundTo
     * @return
     */
    @PostMapping("/coupon/spubounds/save")
    R saveSpuBounds(@RequestBody SpuBoundTo spuBoundTo);


    @PostMapping("/coupon/skufullreduction/saveinfo")
    R saveSkuReduction(@RequestBody SkuReductionTo skuReductionTo);
}

API网关

问题

​ 后台管理系统需要经常给商品服务等各个服务发送请求,现在想来做一个商品的增删改查,发请求就需要知道服务所在的地址,假设我们写了一号机器的10000端口,一号机器掉钱了以后,难道要在前端系统里面,改成二号机器的10000端口吗?商品服务有可能是十几台机器同时上线,某一个用不了还要动态切换到能用的状态,不可能天天在后台管理系统的代码里面来改各个服务所在的端口,这样太麻烦了。

我们需要后台管理系统给任何服务发送的请求,都先经过经过网关,网关帮我动态的路由到各个服务,网关也能从注册中心中感知某一个服务上线还是下线,总是能帮我们把请求路由到指定的位置。

每一个请求过来,后期需要给它权限(鉴权)、监控等等,如果将这种功能写到各个服务上,造成很多重复开发。客户端发送的请求先经过api网关,由网关代转给各个服务,网关对鉴权、限流、日志输出进行统一的处理。

/**   1)、让所有请求过网关;
*          1、@FeignClient("gulimall-gateway"):给gulimall-gateway所在的机器发请求
*          2、/api/product/skuinfo/info/{skuId}
*   2)、直接让后台指定服务处理
*          1、@FeignClient("gulimall-gateway")
*          2、/product/skuinfo/info/{skuId}
*/
SpringCloud-Gateway

SpringCloud-Gateway 网关核心概念及原理

​ SpringCloud gateway 作为springcloud 官方推出第二代网关框架,取代了Zuul网关。

​ Route:路由

​ Predicate:断言

​ Filter:过滤器

当请求到达网关,网关先利用断言(Predicate)来判断是不是符合某个路由规则,如果符合了就按路由规则路由(Route)到指定地方,但先路由到指定地方必须先经过一系列的过滤器(Filter)进行过滤

SpringCloud-Gateway 创建&测试API网关

1、开启服务注册发现

(配置nacos的注册中心地址)

2、编写网关配置文件

5商品服务API-三级分类

查询-递归树形结构数据获取

首先将category表的sql导入

controller

/**
     * 查出所有分类以及子分类,以树形结构组装起来
     */
@RequestMapping("/list/tree")
public R list(){

    List<CategoryEntity> entities = categoryService.listWithTree();


    return R.ok().put("data", entities);
}

在categoryService中生成接口

List<CategoryEntity> listWithTree();

在categoryServiceImpl中写实现方法

原始写法

@Autowired
CategoryDao categoryDao;

快速写法

public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService
{

} 

CategoryServiceImpl 继承 ServiceImpl<CategoryDao, CategoryEntity>

ServiceImpl 中有baseMapper属性,baseMapper就是泛型指定的Mapper

public class ServiceImpl<M extends BaseMapper<T>, T> implements IService<T> {
    protected Log log = LogFactory.getLog(this.getClass());
    @Autowired
    protected M baseMapper;//CategoryDao
    }

所以baseMapper就是CategoryDao,就不需要使用注入@Autowired CategoryDao categoryDao;了

@Override
public List<CategoryEntity> listWithTree() {
    //1、查出所有分类
    List<CategoryEntity> entities = baseMapper.selectList(null);

    //2、组装成父子的树形结构
    //2.1)、找到所有的一级分类
    List<CategoryEntity> level1Menus = entities.stream().filter(categoryEntity ->
            categoryEntity.getParentCid() == 0
    ).map((menu) -> {
        menu.setChildren(getChildrens(menu, entities));
        return menu;
    }).sorted((menu1, menu2) -> {
        return (menu1.getSort() == null ? 0 : menu1.getSort()) - (menu2.getSort() == null ? 0 : menu2.getSort());
    }).collect(Collectors.toList());
    return level1Menus;
}

给CategoryEntity添加一个属性而这个属性不是数据表的字段,添加@TableField(exist=false)

@TableField(exist=false)
private List<CategoryEntity> children;//将当前菜单的所有子分类保存到children

配置网关路由与路径重写

CORS 跨域

跨域:指的是浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对javascript施加的安全限制。

同源策略:是指协议、域名、端口都要相同,其中一个不同就会产生跨域。

带src属性的资源不受跨域限制

跨域只出现在前后端分离的ajax请求或axios请求,表单和超链接的请求不会跨域的,如果前后端不分离,ajax和axios请求也不会跨域。

浏览器为了安全限制起见,默认拒绝跨区请求

No 'Access-Control-Allow-Origin' header is present on the requested resource

跨域流程

非简单请求 (put、delete)等,需要先发送预检请求

image-20220904212829761.png 解决跨域

1.使用nginx部署为同一域

image-20220904213107197.png

在gulimall-gateway中定义GulimallCorsConfiguration类,在GulimallCorsConfiguration中添加请求规则,然后再application.yam中配置gateway的路由规则

@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);//跨域请求默认不包含cookie,设置我true可以包含cookie
        //Max-Age 表示响应的有效时间为多少秒
        Source.registerCorsConfiguration("/**",corsConfiguration);//
        return new CorsWebFilter(Source);
    }
}

- id: product_route
    uri: lb://gulimall-product
    predicates:
    - Path=/api/product/**
    filters:
    - RewritePath=/api/(?<segment>/?.*),/$\{segment}
    
- id: admin_route #id是一个标识
  uri: lb://renren-fast #lb(load banlace 负载均衡) 到指定的服务
  predicates:#断言,在那种情况下才路由给uri
    - Path=/api/** #指定路径:api下的请求路由给renren-fast服务,前端项目发送请求都带了api前缀
  filters: #过滤器
    - RewritePath=/api/(?<segment>/?.*),/renren-fast/$\{segment} #路径重写:将/api转为/renren-fast 
    #不加路径重写的效果是http://localhost:88/api/captcha.jpg => http://renren-fast:8080/api/captcha.jpg 
    #加了之后就到了真实的路径http://renren-fast:8080/captcha.jpg

删除节点

/**
   * 删除
   * @RequestBody:获取请求体,必须发送post请求
   * SpringMVC自动将请求体的数据(json),转为对应的对象
   */
  @RequestMapping("/delete")
  //@RequiresPermissions("product:category:delete")
  public R delete(@RequestBody Long[] catIds){//将请求体的内容转换为 Long[] catIds这个数组
	categoryService.removeMenuByIds(Arrays.asList(catIds));

      return R.ok();
  }

mybatis逻辑删除

/**
* 是否显示[0-不显示,1显示]
*/
@TableLogic(value = "1",delval = "0")
private Integer showStatus;

SPU:Standard Product Unit(标准化产品单元)

是商品信息聚合的是最小单元,是一组可复用、易检索的标准化信息的集合,该集合描述了一个产品的特性。共享规格参数

SKU:Stock Keeping Unit(库存量单位)

即库存进出计量的基本单元,可以是以件、盒,托盘等为单位。SKU这是对于 大型连锁超市DC(配送中心)物流管理的一个必要的方法。现在已经被引申为产品统一编号的简称,每种产品均对应有唯一的SKU。销售属性

增删改查

修改不仅需要将主表修改,还需要将关联表冗余信息更改,需要进行判断。需要开启事务@Transactional。

VO(value object) 值对象 Vo包中实体类的扩展,实体类中需要但数据库表中没有的属性?

通常用于业务层之间的数据传递,和 PO 一样也是仅仅包含数据而已。但应是抽象出 的业务对象 , 可以和表对应 , 也可以不 , 这根据业务

的需要 。用 new 关键字创建,由 GC 回收的。

View object:视图对象;

接受页面传递来的数据,封装对象

将业务处理完成的对象,封装成页面要用的数据

mybatis自定义结果集

<!--    resultType 返回集合里面元素的类型,只要有嵌套属性就要封装自定义结果-->
<resultMap id="spuItemAttrGroupVo" type="com.atguigu.gulimall.product.vo.SpuItemAttrGroupVo">
    <!--        spu_id  attr_group_name  attr_group_id  attr_id  attr_name             attr_value  -->
    <result property="groupName" column="attr_group_name"></result>
    <collection property="attrs" ofType="com.atguigu.gulimall.product.vo.Attr">
        <result column="attr_name" property="attrName"></result>
        <result column="attr_value" property="attrValue"></result>
    </collection>
</resultMap>

<select id="getAttrGroupWithAttrsBySpuId" resultMap="spuItemAttrGroupVo">
    SELECT
        pav.`spu_id`,
        ag.`attr_group_name`,
        ag.`attr_group_id`,
        aar.`attr_id`,
        attr.`attr_name`,
        pav.`attr_value`
    FROM `pms_attr_group` ag
             LEFT JOIN `pms_attr_attrgroup_relation` aar ON aar.`attr_group_id` = ag.`attr_group_id`
             LEFT JOIN `pms_attr` attr ON attr.`attr_id` = aar.`attr_id`
             LEFT JOIN `pms_product_attr_value` pav ON pav.`attr_id` = attr.`attr_id`
    WHERE ag.catelog_id=#{catalogId} AND pav.`spu_id`=#{spuId}
</select>

TO 两个微服务之间需要传数据,将对象转成json,再将 json解析

分布式高级(微服务架构篇)

JSR303校验

/** 3、JSR303
*   1)、给Bean添加校验注解:javax.validation.constraints,并定义自己的message提示
*   2)、开启校验功能@Valid
*      效果:校验错误以后会有默认的响应;
*   3)、给校验的bean后紧跟一个BindingResult,就可以获取到校验的结果
*   4)、分组校验(多场景的复杂校验)
*         1)、  @NotBlank(message = "品牌名必须提交",groups = {AddGroup.class,UpdateGroup.class})
*          给校验注解标注什么情况需要进行校验
*         2)、@Validated({AddGroup.class})
*         3)、默认没有指定分组的校验注解@NotBlank,在分组校验情况@Validated({AddGroup.class})下不生效,只会在@Validated生效;
*   5)、自定义校验
*       1)、编写一个自定义的校验注解
*       2)、编写一个自定义的校验器 ConstraintValidator
*       3)、关联自定义的校验器和自定义的校验注解
*          *      @Documented
*          * @Constraint(validatedBy = { ListValueConstraintValidator.class【可以指定多个不同的校验器,适配不同类型的校验】 })
*          * @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
*          * @Retention(RUNTIME)
*          * public @interface ListValue {
*/

ElasticSearch

基本概念

1.数据关系型数据库对比

  • Index(索引)相对于数据库中的数据库。动词,相当于 MySQL 中的 insert; 名词,相当于 MySQL 中的 Database
  • type(类型)在 Index(索引)中,可以定义一个或多个类型。 类似于 MySQL 中的 Table;每一种类型的数据放在一起;6.0后已移除
  • Document(文档)类似于MySQL中的table中的内容。保存在某个索引(Index)下,某种类型(Type)的一个数据(Document),文档是 JSON 格 式的,Document 就像是 MySQL 中的某个 Table 里面的内容;

2.倒排索引

image-20221206172031829.png

先分词,将整句拆分为单词;相关性得分。

缓存

redis、redisson、Spring-cache

//使用方法,可以看这两个类
IndexController
CategoryServiceImpl

修改缓存:双写模式和失效模式

redis缓存

//分布式锁方法使用p158,Redis Setnx 命令 - 只有在 key 不存在时设置 key 的值。
getCatalogJsonFromDbWithRedisLock()//redis锁的使用,但不推荐,可以使用redisson

Spring-Cache

异步&线程池

com.atguigu.gulimall.search.thread.ThreadTest

异步编排 CompletableFuture

登入校验使用拦截器

LoginUserInterceptor

总结

1.Reactive和WebFlux

2.接口幂等性

分布式开发需要关注的接口幂等性,无论是A—>B请求的互调,还是发请求的调用,无论怎么调用,都需要服务里面的所有接口都应该是幂等的,调用一次和调用无数次,结果都是一样,这是整个分布式系统中,业务功能的保证,可以通过加锁、数据的乐观锁和悲观锁字段、加事务都可以实现接口幂等性,在系统中利用最多的保证接口幂等性,使用的是令牌机制(类似于验证码),我给你发一个令牌,你下次带上来,用过一次就销毁。

3.本地事务和分布式事务

在整个分布式系统里面,除了每个系统要解决自身的事务外。分布式调用需要有分布式事务的存在,可以用SpringCloud Alibaba-Seata 组件,这个组件最好用的常见是后台管理系统,比如在后台添加一个商品,保存的时候需要将它的优惠、库存等等各种信息,在各个系统全部都要保存,后台管理系统并发要求不高的可以使用seata在做分布式事务。如果并发性能超高的,高并发的下单等等,我们要保证最终一致性,所以最终解决方案是使用的RabbitMQ,用RabbitMQ发一个消息,首先可靠消息的保证,可靠消息就是需要发送端确认是接受端确认机制,最终系统只需要监听消息,然后根据消息改变系统的相关的属性,不能达到强一致,但可以达到最终一致。

4.性能与压力测试

高并发系统的压力测试,使用apache-jmeter来进行压力测试,监控jvm的相关性能,jconsolejvisualvm来监控性能。

5.缓存和分布式锁

在分布式系统中我们要保证吞吐量,保证性能往上提升,引用缓存是必须的,用缓存可以极大的加速系统。但是使用缓存期间也会有问题,缓存的击穿(单点问题),缓存的雪崩(大面积问题),缓存的穿透(缓存的null值一直来查数据库的问题),来解决这些问题的方法可以使用分布式锁,当发并量请求进来全部要来查数据库的时候,先查缓存,使用分布式锁来锁住这些请求,只有一个请求会进来,当查不到缓存的时候,再来查询数据库,最终只会个数据放一条查询,而其他人得到锁以后,只会下次继续来看一下缓存就行了,不用来查数据库了。分布式锁除了缓存在使用外,接口幂等性也在使用,比如并发的几个请求,都来调用了A接口,这个接口只能调用一次,这一次的数据是什么样就是什么样,释放锁之后再来调用,我们就来判断一下,这个数据状态,如果已经被处理就不处理了。在定时任务中也有用到,我们定时启动了3台机器,这3台机器同时启动了一个任务,比如上架秒杀商品,只要用了分布式锁,上架过了就不再上架了,用分布式锁来锁住所有机器,来一个机器来执行这个任务

6.ElasticSearch

商品的检索,不能放在sql里面,使用sql语句来查询,性能太差了。所用的检索,无论是按照分类、名字、属性检索,所有的检索数据都保存在ElasticSearch中。

7.异步和线程池

在高并发系统里面,异步是必要的,一定是要会编写的,和以前不一样的方式,以前是new Thread(),然后start,创建一个线程。在高并发系统里面,每一个请求进来,都new Thread() ,start,我们的资源很快就会耗尽,所以我们为了控制我们整个系统的资源,所有的异步任务,都要提交个线程池,这样线程池里面有一个最大量,比如核心是20个线程,最大是200个线程,排队可以排500个、800个、1000个,这样就使用线程池控制住了资源,峰值也就是200个正在运行的和1000个在队列里面排满的,再不能占用更多的资源了。所以有这种资源控制方案以后我们就不担心系统因为资源耗尽而崩溃。即使用了异步任务和线程池,但异步任务之间可能有顺序,我们可以使用异步编排,CompletableFuture。

8.单点登入与社交登入

使用微博来进行社交登入。单点登入,在不同域名系统下如何完成登入。在相同域名下,登入一次,处处都要实现单点登入效果,整合了Spring-Session,让一处的登入,处处都可用,相当于将所用微服务的session进行了同步,只要登入成功以后去哪个微服务,都可以获取到session数据。使用Spring-Session解决分布式系统Session不一致的问题。

9.商城业务

商品上架(后台管理系统)、商品检索、商品的详情(缓冲技术、除了将数据缓存到redis,还整合了Spring-Cache来更方便的使用缓存,全系统都应该使用这种方式进行缓存,缓存不一致问题改怎么解决,缓存的清空,缓存的更新,都可以使Spring-Cache很方便的解决这些问题)、购物车、订单、秒杀

10.RabbitMQ

来做分布式事务,解决最终一致性的时候,RabbitMQ是一个很好的工具,我们只需要A服务给B服务来发一个消息,不需要关心最终怎么做,在订单的场景下,使用RabbitMQ来做分布式事务。在秒杀的场景下,加入RabbitMQ队列来进行削峰处理,将所有的流量排队放到队列里面,由后台慢慢来进行消费,如果引入消息队列,我们的A、B服务也不用关心接口调用了,A都不用调用B,相当于应用之间来进行解耦。在订单中,我们进行关单的时候,使用RabbitMQ的死信队列,保证需要关单的数据都能被关掉。

11.支付

整合了支付宝的沙箱来进行支付。

12.定时任务与分布式调度

秒杀系统的所有商品上架都需要定时任务来做。

13.ShardingSphere

分库分表,高可用做mysql集群的时候

mycat做读写分离,写操作分到主库,主从复制到从库,进行数据库同步

14.SpringCloud组件

一定要掌握的

SpringCloud Alibaba

1、SpringCloud Alibaba简介

2、SpringCloud Alibaba-Nacos[作为注册中心]

3、SpringCloud Alibaba-Nacos[作为配置中心] 所有服务的配置都放给配置中心,这样通过nacos的控制台动态的修改配置,修改完之后微服务就能动态的不下线的情况下,应用到最新的配置。

4、SpringCloud Alibaba-Sentinel 为了保护我们的整个系统,引入了Sentinel,作为流量哨兵,从流量入口开始保护我们任意想保护的资源,结合Sentinel最好加上Sleuth+Zipkin服务链路追踪,对我们整个系统的运行状况,服务的链路追踪,都能了如执掌,这样可用在里面跳出一些异常问题用哨兵进行限流,或者降级。

5、SpringCloud Alibaba-Seata

6、SpringCloud Alibaba-oss

二、SpringCloud

1、Feign声明式远程调用

2、Gateway

3、Sleuth+Zipkin服务链路追踪

缓存:加快速度

异步:加快速度、控制资源

消息队列:流量削峰、最终一致,都能通过消息队列来降低某一个服务的压力。所有消息存到队列里面,闲的时候在慢慢消费。

高并发有三宝:缓存、异步、对排好

高可用集群(架构师提升篇)

1 分布式:一个业务分拆多个子业务,部署在不同的服务器上 2 集群:同一个业务,部署在多个服务器上

主从、分片、选领导

集群

集群(cluster)技术是一种较新的技术,通过集群技术,可以在付出较低成本的情况下获得在性能、可靠性、灵活性方面的相对较高的收益,其任务调度则是集群系统中的核心技术

集群是一组相互独立的、通过高速网络互联的计算机,它们构成了一个组,并以单一系统的模式加以管理。一个客户与集群相互作用时,集群像是一个独立的服务器。集群配置是用于提高可用性和可缩放性。

1 提高性能

一些计算密集型应用,如:天气预报、核试验模拟等,需要计算机要有很强的运算处理能力,现有的技术,即使普通的大型机器计算也很难胜任。这时,一般都使用计算机集群技术,集中几十台甚至上百台计算机的运算能力来满足要求。提高处理性能一直是集群技术研究的一个重要目标之一。

2 降低成本

通常一套较好的集群配置,其软硬件开销要超过100000美元。但与价值上百万美元的专用超级计算机相比已属相当便宜。在达到同样性能的条件下,采用计算机集群比采用同等运算能力的大型计算机具有更高的性价比。

3 提高可扩展性

用户若想扩展系统能力,不得不购买更高性能的服务器,才能获得额外所需的CPU 和存储器。如果采用集群技术,则只需要将新的服务器加入集群中即可,对于客户来看,服务无论从连续性还是性能上都几乎没有变化,好像系统在不知不觉中完成了升级。

4 增强可靠性

集群技术使系统在故障发生时仍可以继续工作,将系统停运时间减到最小。集群系统在提高系统的可靠性的同时,也大大减小了故障损失。

分布式事务

seata AT 分布式事务 效率不高

@GlobalTransactional
//为了保证高并发。库存服务自己回滚。可以发消息给库存服务;
//库存服务本身也可以使用自动解锁模式  消息

注解

@ResponseBody
@GetMapping(value = "/payOrder",produces = "text/html")//produces指定返回值类型
@RestController
@Controller

实体类中

@Data
@TableName("pms_category")
@TableId

//使用vo下面两个是不规范的
@TableField(exist = false)//数据库不存在的字段
@JsonInclude(JsonInclude.Include.NON_EMPTY)//不为空才返回

mybatis

void updateCategory(@Param("catId") Long catId,@Param("name") String name);

<update id="updateCategory">
        UPDATE `pms_category_brand_relation` SET catelog_name=#{name} WHERE catelog_id=#{catId}
</update>

controller

@GetMapping("/{attrType}/list/{catelogId}")
public R baseAttrList(@RequestParam Map<String, Object> params,
                          @PathVariable("catelogId") Long catelogId,
                          @PathVariable("attrType")String type)
@PostMapping
public R update(@RequestBody AttrVo attr){//post请求携带json数据,要封装成我们自定义的对象加@RequestBody
        attrService.updateAttr(attr);

        return R.ok();
    }

@RestController 是@controller和@ResponseBody 的结合

@Controller 将当前修饰的类注入SpringBoot IOC容器,使得从该类所在的项目跑起来的过程中,这个类就被实例化。 @ResponseBody 它的作用简短截说就是指该类中所有的API接口返回的数据,甭管你对应的方法返回Map或是其他Object,它会以Json字符串的形式返回给客户端

日志

@Slf4j//开启,有一个log变量
 
log.info("完整路径:{}", Arrays.asList(catelogPath));

service

@Transactional //事务
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class) //去除springboot数据原有关的操作
Mybatis foreach遍历
collection 集合参数
item 遍历出来的每一个元素,这里用id来表示
separator 分隔符
open 开始符号
close 结束符号
SELECT attr_id FROM `pms_attr` WHERE attr_id IN(?,?,?) AND search_type = 1

<select id="selectSearchAttrIds" 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>
@ConfigurationProperties(prefix = "gulimall.thread")//可以将属性到配置文件中以gulimall.thread开头
@Component
@Data
public class ThreadPoolConfigProperties {
    private Integer coreSize;//核心线程大小
    private Integer maxSize;//最大大小
    private Integer keepAliveTime;//休眠时常
}

//application.properties
gulimall.thread.core-size=20
gulimall.thread.max-size=200
gulimall.thread.keep-alive-time=10

事务

@GlobalTransactional  //seata分布式全局事务,高并发不适用,需要用MQ
@Transactional //普通事务

远程调用全用@PostMapping,请求体用@RequestBody

@RequestBody是一个注解,用于将请求体中的JSON字符串绑定到控制器方法的参数上。它主要用于接收POST请求中的数据,而GET请求没有请求体,所以不能用@RequestBody接收数据。它有一些特点和用法:

  • @RequestBody最多只能有一个,而@RequestParam可以有多个。
  • @RequestBody可以与@RequestParam同时使用,但是@RequestParam接收的是URL中的参数,而@RequestBody接收的是请求体中的参数。
  • @RequestBody可以接收简单类型或复杂类型的参数,但是要求前端传递的JSON字符串中的键要与后端参数对象的属性名一致,否则无法绑定。
  • @RequestBody会根据请求头中的Content-Type来选择合适的HttpMessageConverter来解析请求体中的数据。最常用的是application/json类型,对应的解析器是Jackson。
  • @RequestBody在解析请求体中的数据时,会根据JSON字符串中的值来判断后端参数对象的属性值。如果值为null或"",则属性值为null。如果值为有效的JSON值,则属性值为对应的类型。如果没有指定某个键,则不会调用对应属性的setter方法。
@PostMapping("/listWithItem")
public R listWithItem(@RequestBody Map<String, Object> params)

@RequestParam是一个注解,用于将请求参数绑定到控制器方法的参数上。它有三个属性:

  • value:请求参数的名称,必须指定。
  • required:是否必须包含该参数,默认为true,如果请求中没有该参数,会抛出异常。
  • defaultValue:参数的默认值,如果设置了该值,required会自动设为false。

例如,如果你想获取请求中的name参数,你可以这样写:

@GetMapping("/hello")
public String hello(@RequestParam("name") String name) {
  return "Hello, " + name;
}

如果你想让name参数可选,你可以这样写:

@GetMapping("/hello")
public String hello(@RequestParam(value = "name", required = false) String name) {
  return "Hello, " + (name == null ? "world" : name);
}

如果你想给name参数一个默认值,你可以这样写:

@GetMapping("/hello")
public String hello(@RequestParam(value = "name", defaultValue = "world") String name) {
  return "Hello, " + name;
}

实体字段校验 @NotNull、@NotEmpty、@NotBlank

1.@NotNull

不能为 null,但可以为 empty,一般用在 Integer 类型的基本数据类型的非空校验上,而且被其标注的字段可以使用 @size、@Max、@Min 对字段数值进行大小的控制

2.@NotEmpty

不能为 null,且长度必须大于 0,一般用在集合类上或者数组上

3.@NotBlank

只能作用在接收的 String 类型上,注意是只能,不能为 null,而且调用 trim() 后,长度必须大于 0即:必须有实际字符

@Valid是一个注解,用于校验JavaBean中的属性值是否符合规范。它可以配合一些其他的注解,如@NotBlank, @Max, @Min等,来指定不同的校验规则。它可以用在方法参数、方法返回值、方法参数中的属性、方法返回值中的属性等位置上。它需要和一个BindingResult对象一起使用,来接收校验的结果和错误信息。

例如,如果你想校验一个用户对象的姓名和年龄是否为空,你可以这样写:

public class User {
  @NotBlank(message = "姓名不能为空")
  private String name;
  @NotNull(message = "年龄不能为空")
  private Integer age;
  //省略getter和setter
}

@RestController
public class UserController {
  @PostMapping("/user")
  public String addUser(@Valid @RequestBody User user, BindingResult bindingResult) {
    if (bindingResult.hasErrors()) {
      //返回错误信息
      return bindingResult.getFieldError().getDefaultMessage();
    } else {
      //保存用户
      return "添加用户成功";
    }
  }
}

@Validated是一个注解,用于分组校验或嵌套校验。它是Spring提供的对@Valid的扩展,可以指定一个或多个分组,只校验属于该分组的属性。它也可以用在类上,表示该类需要进行校验。它需要和@Valid一起使用,来开启嵌套校验。

例如,如果你想根据不同的场景,对用户对象的不同属性进行校验,你可以这样写:

//定义两个分组接口
public interface Add {}
public interface Update {}

public class User {
  @NotBlank(message = "姓名不能为空", groups = {Add.class, Update.class})
  private String name;
  @NotNull(message = "年龄不能为空", groups = {Add.class})
  private Integer age;
  //省略getter和setter
}

@RestController
@Validated
public class UserController {
  @PostMapping("/user")
  public String addUser(@Validated(Add.class) @RequestBody User user) {
    //保存用户
    return "添加用户成功";
  }

  @PutMapping("/user")
  public String updateUser(@Validated(Update.class) @RequestBody User user) {
    //更新用户
    return "更新用户成功";
  }
}

Lombok @Builder

详解Lombok中的@Builder用法 - 简书 (jianshu.com)

备注/疑问?

BeanUtils性能差?推荐使用MapStructs?
BeanUtils.copyProperties(attr,attrEntity);//复制对象,只要内容一样就行
小数用BigDecimal不用Double

工具类使用

//json转为我们指定的对象。
Map<String, List<Catelog2Vo>> result = JSON.parseObject(catalogJSON, new TypeReference<Map<String, List<Catelog2Vo>>>() {
});
//对象转Json字符串
String s = JSON.toJSONString(vehicleStatusVos);

多看一下配置,config

VO,DTO,DO,PO都是一些常见的Java对象的缩写,它们分别表示:

  • VO(Value Object)值对象,用于展示层,它的作用是把某个指定页面(或组件)的所有数据封装起来。通常是由一些基本类型和字符串类型组成的。
  • DTO(Data Transfer Object)数据传输对象,用于在不同层之间传递数据,比如在展示层和服务层之间。通常是由一些基本类型和PO或者VO组成的。
  • DO(Domain Object)领域对象,用于封装业务逻辑和数据的对象。通常是由一些基本类型和PO或者其他DO组成的,并且包含一些业务方法。
  • PO(Persistant Object)持久对象,用于和数据库表进行映封的对象。通常是由一些基本类型和字符串类型组成的,并且和表的字段一一对应。

更多内容连接

谷粒商城篇章1:blog.csdn.net/unique_perf…

谷粒商城篇章2:blog.csdn.net/unique_perf…

谷粒商城篇章3:blog.csdn.net/unique_perf…