乐优商城

为了让SpringBoot帮我们完成各种自动配置,我们必须引入SpringBoot提供的自动配置依赖,我们称为启动器

java配置主要靠java类和一些注解,比较常用的注解有:
  @Configuration:声明一个类作为配置类,代替xml文件
  @Bean:声明在方法上,将方法的返回值加入Bean容器,代替标签
  @value:属性注入
  @PropertySource:指定外部属性文件,

@SpringBootApplication注解有3个:
  @SpringBootConfiguration
  @EnableAutoConfiguration
  @ComponentScan

一般启动类会放在一个比较前的包目录中。

SpringBoot为我们提供了默认配置,而默认配置生效的条件一般有两个:
  你引入了相关依赖
  你自己没有配置Bean

1)启动器
  所以,我们如果不想配置,只需要引入依赖即可,而依赖版本我们也不用操心,因为只要引入了SpringBoot提供的stater(启动器),就会自动管理依赖及版本了。
  因此,玩SpringBoot的第一件事情,就是找启动器,SpringBoot提供了大量的默认启动器,参考课前资料中提供的《SpringBoot启动器.txt》
2)全局配置
  另外,SpringBoot的默认配置,都会读取默认属性,而这些属性可以通过自定义application.properties文件来进行覆盖。这样虽然使用的还是默认配置,但是配置中的值改成了我们自定义的。
  因此,玩SpringBoot的第二件事情,就是通过application.properties来覆盖默认属性值,形成自定义配置。我们需要知道SpringBoot的默认属性key,非常多

默认的静态资源路径为:
  classpath:/META-INF/resources/
  classpath:/resources/
  classpath:/static/
  classpath:/public

  你会发现日志中什么都没有,因为我们记录的log级别是debug,默认是显示info以上,我们需要进行配置。
  SpringBoot通过logging.level.=debug来配置日志级别,填写包名

SpringBoot整合SpringMVC
SpringBoot整合jdbc和事务
SpringBoot整合连接池
SpringBoot整合mybatis

系统架构的演变
集中式架构
垂直拆分
分布式服务
服务治理(SOA)
微服务
SOA :面向服务的架构 Service Oriented Architecture

前面说的SOA,英文翻译过来是面向服务。微服务,似乎也是服务,都是对系统进行拆分。因此两者非常容易混淆,但其实缺有一些差别

微服务的特点:
  单一职责:微服务中每一个服务都对应唯一的业务能力,做到单一职责
  微:微服务的服务拆分粒度很小,例如一个用户管理就可以作为一个服务。每个服务虽小,但“五脏俱全”。
  面向服务:面向服务是说每个服务都要对外暴露Rest风格服务接口API。并不关心服务的技术实现,做到与平台和语言无关,也不限定用什么技术实现,只要提供Rest的接口即可。
  自治:自治是说服务间互相独立,互不干扰

远程调用方式
  无论是微服务还是SOA,都面临着服务间的远程调用。那么服务间的远程调用方式有哪些呢?
常见的远程调用方式有以下几种:
  RPC:Remote Produce Call远程过程调用,类似的还有RMI。自定义数据格式,基于原生TCP通信,速度快,效率高。早期的webservice,现在热门的dubbo,都是RPC的典型
  Http:http其实是一种网络传输协议,基于TCP,规定了数据传输的格式。现在客户端浏览器与服务端通信基本都是采用Http协议。也可以用来进行远程服务调用。缺点是消息封装臃肿。

现在热门的Rest风格,就可以通过http协议来实现

  RPC,即 Remote Procedure Call(远程过程调用),是一个计算机通信协议。 该协议允许运行于一台计算机的程序调用另一台计算机的子程序,而程序员无需额外地为这个交互作用编程。说得通俗一点就是:A计算机提供一个服务,B计算机可以像调用本地服务那样调用A计算机的服务

RPC调用流程图
自己动手实现RPC

Http客户端工具

* HttpClient
* OKHttp
* URLConnection

JSON转换工具
  HttpClient请求数据后是json字符串,需要我们自己把Json字符串反序列化为对象,我们会使用JacksonJson工具来实现。
  JacksonJson是SpringMVC内置的json处理工具,其中有一个ObjectMapper类,可以方便的实现对json的处理

json转集合

@Test
public void testJson3() throws IOException {
    User user = new User();
    user.setId(8L);
    user.setAge(21);
    user.setName("柳岩");
    user.setUserName("liuyan");

    // 序列化,得到对象集合的json字符串
    String json = objectMapper.writeValueAsString(Arrays.asList(user, user));

    // 反序列化,接收两个参数:json数据,反序列化的目标类字节码
    List<User> users = objectMapper.readValue(json, objectMapper.getTypeFactory().constructCollectionType(List.class, User.class));
    for (User u : users) {
        System.out.println("u = " + u);
    }
}

json转任意复杂类型

public void testJson4() throws IOException {
    User user = new User();
    user.setId(8L);
    user.setAge(21);
    user.setName("柳岩");
    user.setUserName("liuyan");

    // 序列化,得到对象集合的json字符串
    String json = objectMapper.writeValueAsString(Arrays.asList(user, user));
    List<User> list = objectMapper.readValue(json, new TypeReference<List<User>>() {});
    for (User user1 : list) {
        System.out.println("user1 = " + user1);
    }
}

  当对象泛型关系复杂时,类型工厂也不好使了。这个时候Jackson提供了TypeReference来接收类型泛型,然后底层通过反射来获取泛型上的具体类型。实现数据转换。

Spring的RestTemplate
  Spring提供了一个RestTemplate模板工具类,对基于Http的客户端进行了封装,并且实现了对象与json的序列化和反序列化,非常方便。RestTemplate并没有限定Http的客户端类型,而是进行了抽象,目前常用的3种都有支持:

* HttpClient
* OkHttp
* JDK原生的URLConnection(默认的)

  通过RestTemplate的getForObject()方法,传递url地址及实体类的字节码,RestTemplate会自动发起请求,接收响应,并且帮我们对响应结果进行反序列化。

分布式事务
  跨数据源或者跨服务的事务,这就是分布式事务。

在分布式系统中 就不得不面对CAP理论
  CAP定理是由加州大学伯克利分校Eric Brewer教授提出来的,他指出WEB服务无法同时满足一下3个属性:
  一致性(Consistency) : 客户端知道一系列的操作都会同时发生(生效)
  可用性(Availability) : 每个操作都必须以可预期的响应结束
  分区容错性(Partition tolerance) : 即使出现单个组件无法可用,操作依然可以完成

  具体地讲在分布式系统中,在任何数据库设计中,一个Web应用至多只能同时支持上面的两个属性。显然,任何横向扩展策略都要依赖于数据分区。因此,设计人员必须在一致性与可用性之间做出选择。
AP –> 牺牲C (最终一致性)

  而面对高并发的互联网行业,高可用显然会比一致性要重要的多,但是一致性也是不可抛弃的,如何解决这一矛盾?这个时候,大神们有总结出了BASE理论:

* Basically Available(基本可用)
* Soft state(软状态)
* Eventually consistent(最终一致性)

  BASE理论是对CAP中的一致性和可用性进行一个权衡的结果,理论的核心思想就是:我们无法做到强一致,但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性(Eventual consistency)。

柔性事务和刚性事务
  刚性事务:严格遵循ACID原则的事务, 例如单机环境下的数据库事务。
  柔性事务:指遵循BASE理论(基本可用,最终一致)的事务,通常用在分布式事务中。

因为分布式事务的特点,只能采用柔性事务,常用的柔性事务解决方案有:

* 两阶段提交(Two Phase Commit, 2PC)
    * 有成熟的解决框架,实现简单。
    * 资源锁定周期长,执行效率低,不适合高并发场景
    * 故障恢复困难

* TCC补偿型事务(Try-Confirm-Cancle)
    * 实现很复杂,开发成本高
    * 资源锁定粒度小,执行效率高
    * 强隔离性,严格的数据一致性
    * 事务执行周期短

* 异步确保,基于可靠MQ服务
    * 实现较为复杂,成本略高
    * 对MQ的可靠性要求高
    * 事务执行周期长

  TCC是基于业务层面的事务定义。锁粒度完全由业务自己控制。它本质是一种补偿的思路。它把事务运行过程分成 Try、Confirm / Cancel 两个阶段。在每个阶段的逻辑由业务代码控制。这样就事务的锁粒度可以完全自由控制。业务可以在牺牲隔离性的情况下,获取更高的性能。

* Try 阶段
    * Try :尝试执行业务
    * 完成所有业务检查( 一致性 )
    * 预留必须业务资源( 准隔离性 )

* Confirm / Cancel 阶段:
    * Confirm :确认执行业务
        * 真正执行业务
        * 不做任务业务检查
        * Confirm 操作满足幂等性
    * Cancel :取消执行业务
        * 释放 Try 阶段预留的业务资源
        * Cancel 操作满足幂等性
        * Confirm 与 Cancel 互斥

TCC实例
  在电商网站中,一个经典的案例是这样的:用户下单,基于订单系统实现,操作订单表;同时下单需要对商品进行减库存操作,在库存系统完成;同时还要对用户进行积分奖励,在用户中心完成。这样就出现了分布式事务。
我们用这个业务场景来解释TCC的过程:

* try:尝试执行业务
    * 完成业务检查、预留必须业务资源
    * 在本例中:判断库存是否充足,如果充足,锁定库存数据。
* confirm/cancel:
    * Confirm :
        * 确认执行业务,利用try阶段预留的资源进行操作,如果失败还要重试,因此要保证接口的幂等性
        * 在本例中,我们在库存系统减库存,同时在用户中心给用户增加积分。
    * Cancel :
        * 如果confirm阶段出现异常、超时。则调用cancel取消执行业务,进行事务补偿(例如逆向操作)。释放try阶段锁定的资源。如果在事务补偿过程中出现异常,也必须进行重试。如果超过重试次数后,依然无法成功,则记录日志,以便后续人工介入进行事务补偿操作。
        * 在本例中,我们尝试恢复库存。同时取消用户积分。

TCC的优缺点
优点:
  TCC的try、confirm等操作全部有用户定义,因此锁定粒度由用户自由控制。各个资源独立锁定,分别提交、释放,无需等待对方。失败后是执行cancel中的补偿型操作即可。
  事务执行效率高,时间短
  能够保证严格的数据一致性。

缺点:
  正是因为需要用户编写所有的try、confirm、cancel操作,另外还有重试、幂等的要求,日志的记录等,实现复杂度较高,开发成本增加。

目前有一些开源的TCC框架:
TCC-transaction: https://github.com/changmingxie/tcc-transaction
spring-cloud-rest-tcc: https://github.com/prontera/spring-cloud-rest-tcc
happylifeplat-transaction: https://github.com/yu199195/happylifeplat-transaction

TCC使用场景
  对一致性要求较高,事务时效性比较敏感的业务。

day16-注册及阿里大于资料中的关于分布式事务的讲解:

付款减库存还是下单减库存?
一般采用下单减库存,用户体验好。
下单减库存,就必须设置付款有效时间。

减库存是否有线程安全问题?如何解决?
只有满足 共享变量 多线程并发 多步骤 就可能会发生线程安全问题
  1)查询库存余量
  2)判断库存是否充足
  3)减库存 下单成功
并发访问情况下,因为是多线程操作,而且查询和修改是分开的,因此就会出现超卖现象。
为了避免超卖,解决方案:
a:加锁
  1)悲观锁:认为线程安全问题一定会发生。在每次查询都锁表。select….for update
  2)乐观锁:认为线程安全问题不会发生。查询时不锁表,在修改数据时做校验,校验数据是否有被修改过
  update tb_item as num=num-1 where id=1 and num=10;
刚才的乐观锁频率过高,导致数据库压力过大,很有可能就崩溃了。
b:变同步写为异步写
  思路:不再直接操作数据库,而是把库存量读取到内存中,在内存中完成减库存操作。再把生成订单和减库存的任务通过MQ交给数据库操作。
lpush 放入10条数据进入队列
lpop

  如何在内存中判断库存是否有剩余?
用redis就可以实现。
  方案1:用简单的key和value结构。利用redis事务监控该字段(利用redis的乐观锁操作 watch)
  
  

                            set num 10
                            watch
                            get num
                            if(num>0){
                                set num =9
                            }

  方案2:用list结构(队列)。初始化向队列中填等于库存量的数据。每次请求到达调用lpop取,根据返回结果是否为null 判断是否失败。
  解决数据库的并发写问题。
解决如何避免用户不断刷新页面,nginx压力过大问题
  静态化技术:把动态jsp页面转化为静态的HTML。然后所有的请求不再到达tomcat,而是直接由nginx处理

猜你喜欢

转载自blog.csdn.net/huming1250446609/article/details/82432600