bt365体育投注.主頁欢迎您!!

    <acronym id="zvmrr"></acronym>
    <td id="zvmrr"></td>
  • <tr id="zvmrr"><label id="zvmrr"></label></tr>
  • <acronym id="zvmrr"></acronym>
  • 刘忠旭

    刘忠旭 查看完整档案

    填写现居城市  |  填写毕业院校  |  填写所在公司/组织填写个人主网站
    编辑

    致力于最实用的技术

    个人动态

    刘忠旭 发布了文章 · 10月30日

    Spring Boot - 多模块多环境配置,大厂必备技能

    小伙伴们在初入职场(尤其大厂)看到别人的工程,是否曾有如下困扰:

    • 在一个工程里面如何实现多模块,分模块开发怎么搞?
    • 公司那么多环境,互相隔离,配置各不相同,如何实现多环境?

    反观自己创建的工程,代码都堆在一个模块里面,或许还不清楚模块的概念;更别提多环境了;

    今天我们一文搞定,让我们离大厂更近一步。

    多模块

    模块

    模块化编程中,开发者将程序分解成离散功能块(discrete chunks of functionality),并称之为模块。

    多模块的优点

    每个模块具高内聚的特性,使得校验、调试、测试轻而易举。 精心编写的模块提供了可靠的抽象和封装界限,使得每个模块都具有条理清楚的设计和明确的目的。

    实现多模块

    1. 创建maven工程
    2. 配置多模块
    3. 添加模块依赖
    创建maven工程

    image
    image
    image

    配置多模块

    在pom中,增加modules节点,模块名<module>任意名称</module>,可以配置多个;

    <modules>
     <module>seckill-api</module>
     <module>seckill-biz</module>
    </modules>
    

    image

    通常到这里,多模块就配置完毕了。但现实中,我们的模块间是需要相互依赖的,同时每个模块还要依赖第三方模块;

    添加模块依赖

    seckill-api(api层)要依赖seckill-biz(业务层),在api模块的pom文件中,增加如下配置

    <dependencies>
     <dependency> 
         <groupId>com.sifou.courses</groupId>
         <artifactId>seckill-biz</artifactId>
         <version>1.0-SNAPSHOT</version>
     </dependency>
    </dependencies>
     

    假定,api和biz模块都依赖lombok,validation-api这两个第三方模块(包),如何实现?

    • 方案1:在每个(biz & api)模块中,增加依赖;
    • 方案2:在父模块增加依赖;

    相信大家都会选择方案2;在root工程中的pom文件,增加如下配置;

        <properties>
            <lombok.version>1.18.8</lombok.version>
            <javax.validation>2.0.1.Final</javax.validation>
        </properties>
    
        <dependencies>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>${lombok.version}</version>
            </dependency>
            <dependency>
                <groupId>javax.validation</groupId>
                <artifactId>validation-api</artifactId>
                <version>${javax.validation}</version>
            </dependency>
        </dependencies>
    

    到这里,配置完成;(是不是很清晰,请投币,点赞)
    image
    还可以用mvn dependency:tree命令,来查看依赖关系(必备核心技能,解决包冲突,解决包版本失效)

    [INFO] Scanning for projects...
    [INFO] ------------------------------------------------------------------------
    [INFO] Reactor Build Order:
    [INFO] 
    [INFO] com.sifou.courses.seckill
    [INFO] seckill-biz
    [INFO] seckill-api
    [INFO]                                                                         
    [INFO] ------------------------------------------------------------------------
    [INFO] Building com.sifou.courses.seckill 1.0-SNAPSHOT
    [INFO] ------------------------------------------------------------------------
    [INFO] 
    [INFO] --- maven-dependency-plugin:3.1.2:tree (default-cli) @ com.sifou.courses.seckill ---
    [INFO] com.sifou.courses:com.sifou.courses.seckill:pom:1.0-SNAPSHOT
    [INFO] +- org.projectlombok:lombok:jar:1.18.8:compile
    [INFO] \- javax.validation:validation-api:jar:2.0.1.Final:compile
    [INFO]                                                                         
    [INFO] ------------------------------------------------------------------------
    [INFO] Building seckill-biz 1.0-SNAPSHOT
    [INFO] ------------------------------------------------------------------------
    [INFO] 
    [INFO] --- maven-dependency-plugin:3.1.2:tree (default-cli) @ seckill-biz ---
    [INFO] com.sifou.courses:seckill-biz:jar:1.0-SNAPSHOT
    [INFO] +- org.projectlombok:lombok:jar:1.18.8:compile
    [INFO] \- javax.validation:validation-api:jar:2.0.1.Final:compile
    [INFO]                                                                         
    [INFO] ------------------------------------------------------------------------
    [INFO] Building seckill-api 1.0-SNAPSHOT
    [INFO] ------------------------------------------------------------------------
    [INFO] 
    [INFO] --- maven-dependency-plugin:3.1.2:tree (default-cli) @ seckill-api ---
    [INFO] com.sifou.courses:seckill-api:jar:1.0-SNAPSHOT
    [INFO] +- com.sifou.courses:seckill-biz:jar:1.0-SNAPSHOT:compile
    [INFO] +- org.projectlombok:lombok:jar:1.18.8:compile
    [INFO] \- javax.validation:validation-api:jar:2.0.1.Final:compile
    [INFO] ------------------------------------------------------------------------
    [INFO] Reactor Summary:
    [INFO] 
    [INFO] com.sifou.courses.seckill .......................... SUCCESS [  1.007 s]
    [INFO] seckill-biz ........................................ SUCCESS [  0.040 s]
    [INFO] seckill-api ........................................ SUCCESS [  0.036 s]
    [INFO] ------------------------------------------------------------------------
    [INFO] BUILD SUCCESS
    [INFO] ------------------------------------------------------------------------
    [INFO] Total time: 1.862 s
    [INFO] Finished at: 2020-10-29T23:18:09+08:00
    [INFO] Final Memory: 27M/230M
    [INFO] ------------------------------------------------------------------------
    
    Process finished with exit code 0
    

    多环境

    在工作中,我们面临开发、测试、生产等等多个环境,要完美实现多环境,总共可以分文两个大的步骤;

    • 在工程中支持多环境配置;
    • 在真实环境中实现多环境启动;

    支持多环境配置

    1. 创建properties文件
    2. 指定环境参数
    创建properties文件

    在resources文件夹下创建三个以properties为后缀的文件
    例如:
    application-dev.properties:开发环境
    application-test.properties:测试环境
    application-prod.properties:生产环境

    指定环境参数

    spring.profiles.active=test

    到这里,多环境配置完成;
    在Spring Boot中多环境配置文件名必须满足:application-{profile}.properties的固定格式,其中{profile}对应你的环境标识;

    例如:
    application-dev.properties:开发环境
    application-test.properties:测试环境
    application-prod.properties:生产环境

    application.properyies通过spring.profiles.active来具体激活一个或者多个配置文件,如果没有指定任何profile的配置文件的话,spring boot默认会启动application-default.properties;而哪个配置文件运行:

    spring.profiles.active=test

    就会加载application-test.properties配置文件内容

    多环境启动

    刚刚讲了在工程中如何配置,那么在真正的环境中如何启动?莫非,改配置吗???当然不是,正解如下。

    -Dspring.profiles.active=${PROFILE}
    

    在启动脚本中,增加上面这个,按环境来指定要加载的配置文件;

    以上,欢迎大家多交流。

    最后

    我在思否开了一门《大厂电商 Java 秒杀系统架构实战》课程,本课程从秒杀场景出发,分为基础篇、进阶篇、架构篇3部分。

    • 基础篇重点在于实现功能(导购、交易)
    • 进阶篇重点突破超卖、突发流量给系统带来的挑战,高并发银弹,缓存如何使用;
    • 架构篇重点讲解如何保障系统高可用(方法论+最佳实践)

    通过秒杀这个典型的高并发场景,带你彻底搞明白高并发系统该如何设计。

    查看原文

    赞 10 收藏 8 评论 0

    刘忠旭 发布了文章 · 2019-11-20

    移动开发者的后端开发入门体验

    大多数公司移动开发的现状

    目前大多数公司移动开发过程中都会多多少少遇到下面的这几种场景:

    1. 场景A(格式)

      • 移动端:老哥,要开发了,需要把接口给我。
      • 后端:这个之前有给PC的接口,你直接调Dubbo接口吧,你用那个字段就取哪个字段好了
      • 移动端:???这么多字段哪个是我要的,为什么成功的时候这个字段返回的是个json对象、失败的时候返回了个字符串。
    2. 场景B (效率)

      • 移动端:各位大佬,App这边这次项目中有个功能,需要用到订单、商品和物流的信息,这个接口我应该找谁要?
      • 订单大佬:我这边只能给你订单的,其他的业务不该我去处理 商品大佬:我也是跟订单一样的。
      • 移动端:这接口没人接了。。。
      • 产品:。。。我去跟各个TL确认下,这接口归属哪边来开发
    3. 场景C (在线问题解决)

      • 客满同学:线上App报错了,弹出了个错误提示
      • 若干分钟后。。。
      • 移动端:经过排查是xxx接口返回了错误信息,所以弹出了对应提示,功能不可用,接口责任人在订单同学那边,需要订单同学查下。
      • 若干分钟后。。。
      • 订单同学: 这个接口是个聚合接口,不只有订单的信息,看了下是调用xxx接口报错了,需要sss同学帮忙看下。
      • 若干分钟后。。。
      • sss同学:排查完毕,是今天发布导致的,已经回滚了,目前线上问题已修复 此时已经过去了好几个若干分钟。
    相信每个移动端开发的小伙伴都或多或少的遇到过以上几种场景。
    
    

    为什么移动端要对后端开发有所了解

    1. 作为移动端开发的小伙伴们平时要对接的开发侧的小伙伴,大多数时间段内都是后端开发的同学,了解后端开发的相关知识,可以有利于双方之间的沟通,大大减少双方沟通过程中“大眼瞪小眼”的尴尬场景,这也就是上面所说的场景A
    2. 虽然现在Android官方推荐使用Kotlin语言作为Android的开发语言,但是绝大多数Android的开发者都是使用java开发Android入的坑,学习后端开发的时候开发语言上不需要重新学习一遍
    3. App端作为直接面向用户的端,接口里的内容往往不是仅仅由一个提供方提供的,例如订单列表的借口中既会有订单相关的信息,也会有购买方的用户信息,还会有所购买的商品的信息,而在实际场景中,用户、订单、商品往往是由不同的部门负责的,这样的话给app端提供的接口的维护归属就有了分歧,任何一个改动都需要通知各方协调(场景B),当有线上问题出现时查询问题原因也需要大家共同去查,会浪费很多人力(场景C),如果各方只提供自己相关的数据,由app端自己去组合数据提供给app端,那么责任划分和查询问题就会轻松得多。
    4. 移动端开发也会需要自己的一些基础设施,如自动化构建的维护、跨平台内容的下发、热修复的管理这些都需要有自己的平台去完成,而开发这些平台最适合的莫过于我们这些移动开发人员,因为我们是使用方,更了解这些平台需要替我们完成什么样的功能

    如何入门

    首先是选择学习的方向,选择一个合适的框架

    ????对于java后端的开发而言,不论是各种书籍还是网上的博客,基本都是在SSH和SSM两种组合开发框架中做选择。

    • SSH:Struts2 做控制器(controller) + Spring 管理组件 + Hibernate 负责数据库。
    • SSM: SpringMVC 做控制器(controller) + Spring 管理组件 + MyBatis 负责数据库。

    ????SSH是比较早的项目使用的框架,在各类书籍里经常会见到,目前大多数公司使用的是SSM,Struts2之前多次爆出了漏洞而且迭代速度并没有SpringMVC 那么快,对于 Hibernate与Mybatis 的话各有优劣,打个比方的话, Hibernate 更像一个可以拎包入住的装修好的房子,功能完善,但是难以进行优化和扩展,MyBatis 像是毛胚房,需要开发人员自己装修(写sql和维护映射),但是得益于此扩展和优化相对自由度较高,建议选择SSM好了,毕竟用的人多,遇到问题也容易找到解决方案。如果你对Spring繁琐的配置过程感到很头疼的话,建议使用SpringBoot来进行开发,因为它集齐了各类常用的开发框架,同时降低了 Spring 开发的门槛,更是简化了各种配置过程。

    简单的后端开发用到了哪些知识

    相信每个最初接触后端开发的小伙伴都会跟我一样对后端复杂技术生态一脸懵逼,因为一上来就会接触到很多陌生的词汇,像Spring 的IOC和DI、负责数据库操作的Mybatis、缓存方面的Redis等,甚至连该怎么创建都不清楚。这些。。。。。都是正常的,毕竟没什么人样样精通。对于后端项目开发的学习,以我自身的经历来讲可以这样进行:

    1. 首先是搭建后端项目的框架,当然每个公司都会有自己的模板工程,部署完之后就可以跑起来,如果没有模版工程的小伙伴可以到https://start.spring.io/或者使用idea自动创建一个工程,
      altstart.spring.io自动生成工程只需要在这个页面上选择对应的条件就好了,之后把生成的工程导入Idea就可以开发了。
    2. 工程可以跑起来之后就可以写提供给客户端的接口了,在这部分你需要学习创建Controller、Service、log日志的输出以及maven的依赖管理,学习这些看官方文档应该是最快的方案,https://docs.spring.io/spring-boot/docs/2.1.5.RELEASE/reference/htmlsingle/官方文档写的很详细,所以没必要单独为spring买一本书去学习,因为书肯定不如官网的文档更新的快,如果觉得英文不太方便的话,国内也是有中文翻译的文档版本的,可以参照https://love2.io/@funkkiid/doc/Spring-Boot-Reference-Guide/README.md来学习。
    3. 相信经过上面的两部份你已经知道如何给客户端提供接口了,但是光是这些是不够的,因为给客户端需要的数据往往来自各个服务提供方,例如上面举的订单的例子。这里就需要我们从其他后端应用的服务获取数据,目前国内大多数公司使用阿里开源的Dubbo框架来对外提供服务,所以在这里我们还需要学习Dubbo的使用,由于Dubbo框架是中国公司开源的,所以[文档]https://dubbo.apache.org/zh-cn/有中文版,学习起来会比较方便
    4. 如果单纯是调用别人的服务来整合数据的话,上面三部分学习知识已经可以满足你的要求了。如果有涉及到App独立的数据需要持久化存储的话,还需要学习Mysql相关的知识以及Mybatis的使用。
    5. 随着后端工程业务逐渐复杂,你可能还需要学习更多的内容来完成自己的需求:
    6. 缓存方面:redis
    7. 权限方面:shiro、spring security
    8. 代理服务:Nginx
    9. 消息:ActiveMQ
    10. 数据库连接池:druid
    11. 定时任务:Quartz
    12. 爬虫:WebMagic

    其实这些都是可选的,用到的时候再去学习就ok了

    有赞移动后端基础设施所做的工作

    对于有赞移动端的基础设施的工作我们主要做了以下的内容:

    1. App网关,虽然项目名称是网关,但是跟后端通常所指的网关不相同,这是一个专门给App这边提供接口的应用,开始所说的三种场景的问题,这里都能够很好的解决,因为后端同学大多说是不了解app开发的,更多的小伙伴给App提供接口的时候会按照给前端网页提供接口的方法给接口,这种场景下App这边调用起来就会比较麻烦,在App网关中App端的小伙伴自己给app端提供接口,由App端自行维护这个应用,这样的话中台的小伙伴只需要提供自己那部分基础服务就好了,完全不需要考虑提供出去接口的格式和接口归属的划分,同时出现线上问题App端的小伙伴也可以直接定位到是哪个服务出现了问题,可以减少线上故障的时间。App网关结构如下图所示:alt。在服务化的过程中前段同学也使用node做过类似的处理,感兴趣的同学可以移步Node 在有赞的实践,只不过不同点在于:App网关里有一部分app端自有的业务逻,没有页面渲染的功能。
    2. MBD,由于业务线比较多,大家都在自己机器上打包的话比较耗时间,也没办法对安装包的构建过程做统一的管理,所以开发了MBD来进行正式包、测试包和热修复以及二方库构建的管理。
      Apub,用于app应用新版本和热修复的下发和灰度,同时与MBD打通,可以实现从构建到发布、热修复、交付一系列流程的打通。
    3. Weex构建平台 ,类似于MBD的功能,对应的场景非原生发版,而是weex发版,使得开发人员可以更关注于业务本身,便捷的在不同环境全量、灰度发布weex页面。
    4. 配置中心,随着App功能的迭代,App端的配置日益增多:各种功能的开关、参数的配置、网页url的地址……
      对配置动态化的期望值也越来越高:配置修改后实时生效,灰度发布,分环境,同时对于运营人员而言,也不希望每次修改信息都由开发人员帮忙修改代码发布。
      在这样的场景,传统的通过移动端或者后端代码中hardcode发版、修改数据库等方式已经越来越无法满足开发人员和运营人员对配置管理的需求。于是我们开发了移动配置中心来满足这些需求。
      配置中心中可以对不同的功能划分不同的组件,给运营人员和开发人员发布新配置的功能,新的配置可以通过有赞IM的长连接和App前后台切换去主动拉取配置,达到实时生效,经过数个版本的迭代,还接入了移动基础保障发布权限的审批、Apub的灰度发布功能。
    5. 移动基础保障平台,用于移动端管理平台上的权限管理和审批、app端应用日志的捞取和用户设备信息的查询、以及app内应用反馈的处理,配置中心基础功能见下图:alt

    感受&收获

    1. 客户端的开发更加注重的是用户的体验,是用户操作时的具体感受,面向的是单个用户,而服务端开发更注重的是功能和数据,是线上功能的可用性及数据的准确性。
    2. 虽然客户端和后端都会考虑应用的性能,但是两者侧重点不同,客户端更看重用户使用的感知,如动画效果及滑动流畅性,而后端更看重的是运行效率和并发能力。
    3. 客户端在开发的过程中并发的场景比较少,切换线程一类的操作比较多,而服务端刚好相反,服务端场景下用户并发的场景会很多,线程切换类的操作倒是不如客户端那么频繁了,这与系统的限制及面对的场景相关,客户端只面对一个用户和服务端,而服务端需要面对多个用户。

    讲座【特别推荐】

    Spring Boot 系列文章如若不解,可以通过讲座进行进一步学习。

    手把手的跟你们一起Coding,你快来。

    1. Spring Boot 1.5 快速入门教程
    2. Spring Boot 1.5 进阶
    3. Spring Boot 系列教程(入门+进阶) 【SF编辑推荐】
    查看原文

    赞 2 收藏 1 评论 0

    刘忠旭 赞了文章 · 2019-05-18

    SegmentFault 技术周刊 Vol.36 - 一篇入门 Spring Boot

    clipboard.png

    一篇带你入门 Spring Boot。

    Spring Boot 初识

    SpringBoot前世今生

    本文主要讲述spring boot的由来,即其它诞生的背景,初衷,现状,及对未来的展望。

    Spring Boot参考指南中文版--Chapter1.Spring Boot中文文档

    本节提供一个Spring Boot参考文档的简明概述。你可以把它作为文档其余部分的导航。你可以从头到尾依次阅读该参考指南,或跳过你不感兴趣的章节。

    Spring Boot 学习资料收集

    Spring Boot QuickStart

    Spring Boot QuickStart (1)

    Spring Boot 简化了基于 Spring 的应用开发,你只需要 "run" 就能创建一个独立的,产品级别的 Spring 应用。

    Spring 平台及第三方库提供开箱即用的设置,这样你就可以有条不紊地开始。多数 Spring Boot 应用只需要很少的Spring 配置。你可以使用 Spring Boot 创建 Java 应用,并使用 java -jar 启动它或采用传统的 war 部署方式。

    • 系统要求
    • 安装
    • 使用
    • 配置

    Spring Boot QuickStart (2) - 基础

    基于 Spring Boot 创建一个命令行应用,先来个最基本的体验,体验一下:

    • 配置管理(配置文件加载,多环境配置文件)
    • 日志
    • 单元测试

    Spring Boot QuickStart (3) - Web & Restful

    基于 Spring Boot 可以快速创建一个Web & Restful 应用。

    • 注解
    • 路由,方法
    • 请求参数
    • Cookie
    • Session
    • 模板引擎
    • 常用配置

    Spring Boot QuickStart (4) - Database

    到了操作数据库的环节,以 MySQL 为基准,体验一下数据库的相关操作,JPA、MyBatis 将是学习重点。体验的基线:

    • 单表。增、删、改、查(多条件组合查询、分页,排序等)
    • 多表关联。一对一,一对多,多对多

    Spring Boot QuickStart (5) - Spring Data JPA

    Java Persistence API,可以理解就是 Java 一个持久化标准或规范,Spring Data JPA 是对它的实现。并且提供多个 JPA 厂商适配,如 Hibernate、Apache 的 OpenJpa、Eclipse的EclipseLink等。

    spring-boot-starter-data-jpa 默认使用的是 Hibernate 实现。

    在 SpringBoot + Spring Data Jpa 中,不需要额外的配置什么,只需要编写实体类(Entity)与数据访问接口(Repository)就能开箱即用,Spring Data JPA 能基于接口中的方法规范命名自动的帮你生成实现(根据方法命名生成实现,是不是很牛逼?)

    Spring Boot 入门实用教程

    Spring Boot - 整合Jsp/FreeMarker

    本文讲述了(json,jsp,freemarker)配置及整合方法,并针对web开发常用的注解的概念及功能进行了介绍,留下了一个疑问:为什么整合jsp后必须通过spring-boot:run方式启动?欢迎大家留言讨论。

    Spring Boot - Servlet、过滤器、监听器、拦截器

    本文讲解了注册Servlet/Filter/Listener的两种?方式(Servlet/Filter/Listener的概念大家自行查阅资料了解),及拦截器基本原理,并通过注解实现http拦截器,另外本文还有一个疑问:为什么Spring中实现的Http拦截器,无法对我们自定义的servlet请求进行拦截?欢迎大家留言讨论。

    Spring Boot - 静态资源处理、启动加载、日志处理

    1. 静态资源处理

      1. 默认资源映射(/** - > /resources/static) ,重点是默认目录的优先级
      2. ?定义资源映射 (继承WebMvcConfigurerAdapter 并重写方法addResourceHandlers)
    2. 启动加载

      1. CommandLineRunner (实现接?,多个类加载的优先级)
    3. 日志处理

      1. logback(配置,控制台输出,文件输出)

    Spring Boot - 整合JdbcTemplate、MyBatis

    • 本文讲解Spring Boot整合JdbcTemplate、整合mybatis,介绍并重点讲解了事务处理和配置。
    • 本文未提及关于分?查询和数据库连接池,我认为分页查询的重点是分页算法如何封装,并不是Spring Boot关注的重点,大家可以自己实现。
    • 另外现在常用的数据库连接池有c3p0/dbcp/tomcat-jdbc/HikariCP。
    • 顺便提一下,在Spring Boot中更改数据源,只需要在application.properties配置文件中增加spring.datasource.type配置即可。

    Spring Boot - 部署Deploy

    服务发布Tomcat:

    • 修改启动类,继承 SpringBootServletInitializer 并重写 configure 方法
    • 修改pom文件中jar 为 war
    • 修改pom,排除tomcat插件
    • 打包部署到容器

    Spring Boot 属性配置

    SpringBoot配置属性之MVC

    SpringBoot配置属性之Server

    SpringBoot配置属性之DataSource

    SpringBoot配置属性之NOSQL

    SpringBoot配置属性之MQ

    SpringBoot配置属性之Security

    SpringBoot配置属性之Migration

    SpringBoot配置属性之其他

    SpringBoot配置文件日期属性转换实例

    Spring Boot 进阶

    Spring Boot - 自定义启动banner

    实现的方式非常简单,我们只需要在Spring Boot工程的/src/main/resources目录下创建一个banner.txt文件,然后将ASCII字符画复制进去,就能替换默认的banner了。

    spring boot validated的使用

    spring-boot中可以用@validated来校验数据,如果数据异常则会统一抛出异常,方便异常中心统一处理。

    比如,我们判断一个输入参数是否合法,可以用如下方式

    spring-boot启动初探

    Spring Boot充分利用了JavaConfig的配置模式以及“约定优于配置”的理念,能够极大的简化基于Spring MVC的Web应用和REST服务开发。

    使用spring boot开发web应用,决定项目是否可以直接启动的是spring-boot-starter-tomcat模块,我们可以直接引入spring-boot-starter-web。

    Maven管理SpringBoot Profile

    完成了上面的五步,即可使项目根据你的构建参数的不同,打包出不同环境下运行的包。

    1. 第1步去掉了SpringBoot内嵌的tomcat和tomcat-jdbc。使得我们可以决定在什么情况下使用何种容器运行我们的项目。
    2. 第2步配置了Maven构建Porfile,使得构建可根据我们的指令分发不同的包。
    3. 第3步配置了Maven资源过滤,不仅使得不同Profile下的资源文件互不可见,且替换了资源文件中以“@xx@”表示的属性值。
    4. 第4步使Spring的Profile由Maven决策,这样,我们就不用每次打包都修改Spring的Profile配置了。
    5. 第5步展示了如何执行不同Profile下的构建命令,并且使用了一个Shell脚本方便我们执行构建和跳过测试(多数时候我们在构建项目时先测试,并不需要在构建时测试,测试和构建的解耦使得我们更专注。但同时,如果你忘记了前置测试,也可能会引发未察觉的测试问题)。

    SpringBoot四大神器之Actuator

    Spring Boot有四大神器,分别是auto-configuration、starters、cli、actuator,本文主要讲actuator。actuator是spring boot提供的对应用系统的自省和监控的集成功能,可以对应用系统进行配置查看、相关功能统计等。

    SpringBoot四大神器之Starter

    SpringBoot的starter主要用来简化依赖用的。本文主要分两部分,一部分是列出一些starter的依赖,另一部分是教你自己写一个starter。

    SpringBoot RESTful 应用中的异常处理小结

    • @ControllerAdvice 和 @ExceptionHandler 的区别
    • 处理 Controller 中的异常
    • 处理 404 错误

    Spring Boot整合jsp后必须通过spring-boot:run方式启动?

    简单总结一下,本文阐述的问题并不是日常开发中的主要问题(可能连主要问题都算不上,谁会用main去调试??),但是遇到了就花时间来研究一下,还是有所收获的。

    • 分析问题思路
    • Spring Boot 初始化的部分流程
    • 请求转发和重定向的区别

    另外大家注意如果pom文件中<scope>去掉,再正常部署到tomcat容器中,会有jar冲突,建议大家试验过后,修改回去。

    Spring-boot 启动时碰到的错误

    通过springBoot构建一个简单的Restful webService

    springboot定制404错误信息

    SpringBoot-vue 基于Java的微服务全栈快速开发实践

    Spring Boot 讲堂

    Java 微服务实践 - Spring Boot 系列

    Java 微服务实践 - Spring Boot 为系列讲座,二十节专题直播,时长高达50个小时,包括目前最流行技术,深入源码分析,授人以渔的方式,帮助初学者深入浅出地掌握,为高阶从业人员抛砖引玉。

    系列讲座列表:

    1. Java 微服务实践 - Spring Boot 系列(一)初体验

    2. Java 微服务实践 - Spring Boot 系列(二) Web篇(上)

    3. Java 微服务实践 - Spring Boot 系列(三)Web篇(中)

    4. Java 微服务实践 - Spring Boot 系列(四)Web篇(下)

    5. Java 微服务实践 - Spring Boot 系列(五)嵌入式Web容器

    6. Java 微服务实践 - Spring Boot 系列(六)数据库 JDBC

    7. Java 微服务实践 - Spring Boot 系列(七)MyBatis

    8. Java 微服务实践 - Spring Boot 系列(八)JPA

    9. Java 微服务实践 - Spring Boot 系列(九)NoSQL

    10. Java 微服务实践 - Spring Boot 系列(十)缓存

    11. Java 微服务实践 - Spring Boot 系列(十一)消息

    12. Java 微服务实践 - Spring Boot 系列(十二)验证

    13. Java 微服务实践 - Spring Boot 系列(十三)WebSocket

    14. Java 微服务实践- Spring Boot 系列(十四)WebService

    15. Java 微服务实践 - Spring Boot 系列(十五)安全

    16. Java 微服务实践 - Spring Boot 系列(十六)日志

    17. Java 微服务实践 - Spring Boot 系列(十七)监管

    18. Java 微服务实践 - Spring Boot 系列(十八)配置

    19. Java 微服务实践 - Spring Boot 系列(十九)测试

    20. Java 微服务实践 - Spring Boot 系列(二十)自定义启动器

    插播一则消息:11.11 - 11.13,SF 讲堂将开启优惠模式,大部分讲座均有不同限量的优惠,等你来抢! >>> 电梯直达活动页

    本期完
    :)


    欢迎关注 SegmentFault 微信服务号,获取最新讲堂及优惠信息。

    图片描述

    查看原文

    赞 23 收藏 122 评论 3

    刘忠旭 发布了文章 · 2019-05-15

    开放平台核心功能--授权

    系统交互流程(OAuth2.0)

    clipboard.png

    授权码生成

    加密解密算法(AES)

    
    import org.apache.commons.codec.binary.Base64;
    
    import javax.crypto.Cipher;
    import javax.crypto.SecretKey;
    import javax.crypto.spec.IvParameterSpec;
    import javax.crypto.spec.SecretKeySpec;
    import java.security.InvalidKeyException;
    import java.security.Key;
    import java.security.NoSuchAlgorithmException;
    import java.security.spec.InvalidKeySpecException;
    
    /**
     * @author liuzhongxu
     * @date 2019/4/16
     */
    public class AESUtil {
        /**
         * 算法名称
         */
        public static final String KEY_ALGORITHM = "AES";
        /**
         *算法名称/加密模式/填充方式
         */
        public static final String CIPHER_ALGORITHM = "AES/CBC/PKCS5Padding";
        /**
         * 编码
         */
        public static final String CHARSET_NAME = "UTF-8";
    
        /**
         *
         * 生成密钥key对象
         * @param keyStr 密钥字符串
         * @return 密钥对象
         * @throws InvalidKeyException
         * @throws NoSuchAlgorithmException
         * @throws InvalidKeySpecException
         * @throws Exception
         */
        private static SecretKey keyGenerator(String keyStr) throws Exception {
            SecretKeySpec secretKey = new SecretKeySpec(keyStr.getBytes(CHARSET_NAME), KEY_ALGORITHM);
            return secretKey;
        }
        /**
         * 加密数据
         * @param data 待加密数据
         * @param key 密钥
         * @return 加密后的数据
         */
        public static String encrypt(String data, String key) throws Exception {
            Key secretKey = keyGenerator(key);
            IvParameterSpec iv = new IvParameterSpec(key.getBytes(CHARSET_NAME));
            // 实例化Cipher对象,它用于完成实际的加密操作
            Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
            // 初始化Cipher对象,设置为加密模式
            cipher.init(Cipher.ENCRYPT_MODE, secretKey, iv);
            byte[] results = cipher.doFinal(data.getBytes(CHARSET_NAME));
            // 执行加密操作。加密后的结果通常都会用Base64编码进行传输
            return Base64.encodeBase64String(results);
        }
        /**
         * 解密数据
         * @param data 待解密数据
         * @param key 密钥
         * @return 解密后的数据
         */
        public static String decrypt(String data, String key) throws Exception {
            Key secretKey = keyGenerator(key);
            IvParameterSpec iv = new IvParameterSpec(key.getBytes(CHARSET_NAME));
            Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
            //初始化Cipher对象,设置为解密模式
            cipher.init(Cipher.DECRYPT_MODE, secretKey, iv);
            // 执行解密操作
            return new String(cipher.doFinal(Base64.decodeBase64(data)));
        }
        public static void main(String[] args) throws Exception {
            String source = "工作模式-->>ECB:电子密码本模式、CBC:加密分组链接模式、CFB:加密反馈模式、OFB:输出反馈模式";
            System.out.println("原文: " + source);
            String key = "A1B2C3D4A1B2C3D4";
            String encryptData = encrypt(source, key);
            System.out.println("加密后: " + encryptData);
            String decryptData = decrypt(encryptData, key);
            System.out.println("解密后: " + decryptData);
        }
    }

    计算签名

        /**
         * 计算签名
         */
        private static String calculateSign(String source, String accessSecret) throws InvalidKeyException {
            try {
                //HmacSHA256加密
                Mac mac = Mac.getInstance("HmacSHA256");
                mac.init(new SecretKeySpec(accessSecret.getBytes("UTF-8"), "HmacSHA256"));
                byte[] signData = mac.doFinal(source.getBytes("UTF-8"));
                //bas64加密
                return Base64.getEncoder().encodeToString(signData);
            } catch (NoSuchAlgorithmException e) {
                throw new RuntimeException("HMAC-SHA1 not supported.");
            } catch (UnsupportedEncodingException e) {
                throw new RuntimeException("UTF-8 not supported.");
            }
     
        }
    查看原文

    赞 0 收藏 0 评论 0

    刘忠旭 发布了文章 · 2019-04-09

    Redis Lua脚本的执行原理

    Redis提供了非常丰富的指令集,但是用户依然不满足,希望可以自定义扩充若干指令来完成一些特定领域的问题。Redis 为这样的用户场景提供了 lua脚本支持,用户可以向服务器发送 lua 脚本来执行自定义动作,获取脚本的响应数据。Redis 服务器会单线程原子性执行 lua 脚本,保证 lua 脚本在处理的过程中不会被任意其它请求打断。

    wzy.jpg

    图片

    比如在《Redis 深度历险》分布式锁小节,我们提到了 del_if_equals 伪指令,它可以将匹配 key 和删除 key 合并在一起原子性执行,Redis原生没有提供这样功能的指令,它可以使用 lua脚本来完成。

    wzy.webp_.jpg

    那上面这个脚本如何执行呢?使用 EVAL 指令

    wzy.webp_1.jpg

    EVAL 指令的第一个参数是脚本内容字符串,上面的例子我们将 lua 脚本压缩成一行以单引号围起来是为了方便命令行执行。然后是 key 的数量以及每个 key 串,最后是一系列附加参数字符串。附加参数的数量不需要和key保持一致,可以完全没有附加参数。

    7564501-5234a0760fd7d1c7.webp_.jpg

    上面的例子中只有 1 个 key,它就是 foo,紧接着 bar 是唯一的附加参数。在 lua 脚本中,数组下标是从 1 开始,所以通过 KEYS[1] 就可以得到 第一个 key,通过 ARGV[1] 就可以得到第一个附加参数。redis.call 函数可以让我们调用 Redis 的原生指令,上面的代码分别调用了 get 指令和 del 指令。return 返回的结果将会返回给客户端。

    SCRIPT LOAD 和 EVALSHA 指令
    在上面的例子中,脚本的内容很短。如果脚本的内容很长,而且客户端需要频繁执行,那么每次都需要传递冗长的脚本内容势必比较浪费网络流量。所以 Redis 还提供了 SCRIPT LOAD 和 EVALSHA 指令来解决这个问题。

    wzy.webp_2.jpg

    SCRIPT LOAD 指令用于将客户端提供的 lua 脚本传递到服务器而不执行,但是会得到脚本的唯一 ID,这个唯一 ID 是用来唯一标识服务器缓存的这段 lua 脚本,它是由 Redis 使用 sha1 算法揉捏脚本内容而得到的一个很长的字符串。有了这个唯一 ID,后面客户端就可以通过 EVALSHA 指令反复执行这个脚本了。

    我们知道 Redis 有 incrby 指令可以完成整数的自增操作,但是没有提供自乘这样的指令。

    wzy2.webp_.jpg

    下面我们使用 SCRIPT LOAD 和 EVALSHA 指令来完成自乘运算。

    wzy.webp_3.jpg

    先将上面的脚本单行化,语句之间使用分号隔开

    wzy.webp_4.jpg

    加载脚本

    wzy.webp_5.jpg

    命令行输出了很长的字符串,它就是脚本的唯一标识,下面我们使用这个唯一标识来执行指令

    wzy.webp_6.jpg

    错误处理
    上面的脚本参数要求传入的附加参数必须是整数,如果没有传递整数会怎样呢?

    wzy.webp_7.jpg

    可以看到客户端输出了服务器返回的通用错误消息,注意这是一个动态抛出的异常,Redis 会保护主线程不会因为脚本的错误而导致服务器崩溃,近似于在脚本的外围有一个很大的 try catch语句包裹。在 lua 脚本执行的过程中遇到了错误,同 redis 的事务一样,那些通过 redis.call 函数已经执行过的指令对服务器状态产生影响是无法撤销的,在编写 lua 代码时一定要小心,避免没有考虑到的判断条件导致脚本没有完全执行。

    wzy.webp_8.jpg

    如果读者对 lua 语言有所了解就知道 lua 原生没有提供 try catch 语句,那上面提到的异常包裹语句究竟是用什么来实现的呢?lua 的替代方案是内置了 pcall(f) 函数调用。pcall 的意思是 protected call,它会让 f 函数运行在保护模式下,f 如果出现了错误,pcall 调用会返回 false 和错误信息。而普通的 call(f) 调用在遇到错误时只会向上抛出异常。在 Redis 的源码中可以看到 lua 脚本的执行被包裹在 pcall 函数调用中。

    wzy.webp_9.jpg

    Redis 在 lua 脚本中除了提供了 redis.call 函数外,同样也提供了redis.pcall函数。前者遇到错误向上抛出异常,后者会返回错误信息。使用时一定要注意 call 函数出错时会中断脚本的执行,为了保证脚本的原子性,要谨慎使用。

    错误传递
    redis.call 函数调用会产生错误,脚本遇到这种错误会返回怎样的信息呢?我们再看个例子

    wzy.webp_10.jpg

    客户端输出的依然是一个通用的错误消息,而不是 incr 调用本应该返回的 WRONGTYPE 类型的错误消息。Redis 内部在处理 redis.call 遇到错误时是向上抛出异常,外围的用户看不见的 pcall调用捕获到脚本异常时会向客户端回复通用的错误信息。如果我们将上面的 call 改成 pcall,结果就会不一样,它可以将内部指令返回的特定错误向上传递。

    wzy.webp_11.jpg

    脚本死循环怎么办?
    Redis 的指令执行是个单线程,这个单线程还要执行来自客户端的 lua 脚本。如果 lua 脚本中来一个死循环,是不是 Redis 就完蛋了?Redis 为了解决这个问题,它提供了 script kill 指令用于动态杀死一个执行时间超时的 lua 脚本。不过 script kill 的执行有一个重要的前提,那就是当前正在执行的脚本没有对 Redis 的内部数据状态进行修改,因为 Redis 不允许 script kill 破坏脚本执行的原子性。比如脚本内部使用了 redis.call("set", key, value) 修改了内部的数据,那么 script kill 执行时服务器会返回错误。下面我们来尝试以下 script kill指令。

    wzy.webp_12.jpg

    eval指令执行后,可以明显看出来 redis 卡死了,死活没有任何响应,如果去观察 Redis 服务器日志可以看到日志在疯狂输出 hello 字符串。这时候就必须重新开启一个 redis-cli 来执行 script kill 指令。

    再回过头看 eval 指令的输出

    wzy.webp_13.jpg

    看到这里细心的同学会注意到两个疑点,第一个是 script kill 指令为什么执行了 2.58 秒,第二个是脚本都卡死了,Redis 哪里来的闲功夫接受 script kill 指令。如果你自己尝试了在第二个窗口执行 redis-cli 去连接服务器,你还会发现第三个疑点,redis-cli 建立连接有点慢,大约顿了有 1 秒左右。

    Script Kill 的原理
    下面我就要开始揭秘kill的原理了,lua 脚本引擎功能太强大了,它提供了各式各样的钩子函数,它允许在内部虚拟机执行指令时运行钩子代码。比如每执行 N 条指令执行一次某个钩子函数,Redis 正是使用了这个钩子函数。

    wzy.webp_14.jpg

    wzy.webp_15.jpg

    Redis 在钩子函数里会忙里偷闲去处理客户端的请求,并且只有在发现 lua 脚本执行超时之后才会去处理请求,这个超时时间默认是 5 秒。于是上面提出的三个疑点也就烟消云散了。

    思考题

    在延时队列小节,我们使用 zrangebyscore 和 zdel 两条指令来争抢延时队列中的任务,通过 zdel 的返回值来决定是哪个客户端抢到了任务,这意味着那些没有抢到任务的客户端会有这样一种感受 —— 到了嘴边的肉(任务)最后还被别人抢走了,会很不爽。如果可以使用 lua 脚本来实现争抢逻辑,将 zrangebyscore 和zdel指令原子性执行就不会存在这种问题,读者可以尝试一下

    查看原文

    赞 1 收藏 1 评论 0

    刘忠旭 赞了文章 · 2018-06-20

    十问 TiDB :关于架构设计的一些思考

    作者:黄东旭

    “我希望能够把 TiDB 的设计的一些理念能够更好的传达给大家,相信大家理解了背后原因后,就能够把 TiDB 用的更好。”

    做 TiDB 的缘起是从思考一个问题开始的:为什么在数据库领域有这么多永远也躲不开的坑?从 2015 年我们写下第一行代码,3 年以来我们迎面遇到无数个问题,一边思考一边做,尽量用最小的代价来快速奔跑。

    作为一个开源项目,TiDB 是我们基础架构工程师和社区一起努力的结果,TiDB 已经发版到 2.0,有了一个比较稳定的形态,大量在生产环境使用的伙伴们。可以负责任的说,我们做的任何决定都经过了非常慎重的思考和实践,是经过内部和社区一起论证产生的结果。它未必是最好的,但是在这个阶段应该是最适合我们的,而且大家也可以看到 TiDB 在快速迭代进化。

    这篇文章是关于 TiDB 代表性“为什么”的 TOP 10,希望大家在了解了我们这些背后的选择之后,能更加纯熟的使用 TiDB,让它在适合的环境里更好的发挥价值。

    这个世界有很多人,感觉大于思想,疑问多于答案。感恩大家保持疑问,我们承诺回馈我们的思考过程,毕竟有时候很多思考也很有意思。

    一、为什么分布式系统并不是银弹

    其实并没有什么技术是完美和包治百病的,在存储领域更是如此,如果你的数据能够在一个 MySQL 装下并且服务器的压力不大,或者对复杂查询性能要求不高,其实分布式数据库并不是一个特别好的选择。 选用分布式的架构就意味着引入额外的维护成本,而且这个成本对于特别小的业务来说是不太划算的,即使你说需要高可用的能力,那 MySQL 的主从复制 + GTID 的方案可能也基本够用,这不够的话,还有最近引入的 Group Replication。而且 MySQL 的社区足够庞大,你能 Google 找到几乎一切常见问题的答案。

    我们做 TiDB 的初衷并不是想要在小数据量下取代 MySQL,而是尝试去解决基于单机数据库解决不了的一些本质的问题。

    有很多朋友问我选择分布式数据库的一个比较合适的时机是什么?我觉得对于每个公司或者每个业务都不太一样,我并不希望一刀切的给个普适的标准(也可能这个标准并不存在),但是有一些事件开始出现的时候:比如是当你发现你的数据库已经到了你每天开始绞尽脑汁思考数据备份迁移扩容,开始隔三差五的想着优化存储空间和复杂的慢查询,或者你开始不自觉的调研数据库中间件方案时,或者人肉在代码里面做 sharding 的时候,这时给自己提个醒,看看 TiDB 是否能够帮助你,我相信大多数时候应该是可以的。

    而且另一方面,选择 TiDB 和选择 MySQL 并不是一刀切的有你没他的过程,我们为了能让 MySQL 的用户尽可能减小迁移和改造成本,做了大量的工具能让整个数据迁移和灰度上线变得平滑,甚至从 TiDB 无缝的迁移回来,而且有些小数据量的业务你仍然可以继续使用 MySQL。所以一开始如果你的业务和数据量还小,大胆放心的用 MySQL 吧,MySQL still rocks,TiDB 在未来等你。

    二、为什么是 MySQL

    和上面提到的一样,并不是 MySQL 不好我们要取代他,而是选择兼容 MySQL 的生态对我们来说是最贴近用户实际场景的选择:

    1. MySQL 的社区足够大,有着特别良好的群众基础,作为一个新的数据库来说,如果需要用户去学习一套新的语法,同时伴随很重的业务迁移的话,是很不利于新项目冷启动的。
    2. MySQL 那么长时间积累下来大量的测试用例和各种依赖 MySQL 的第三方框架和工具的测试用例是我们一个很重要的测试资源,特别是在早期,你如何证明你的数据库是对的,MySQL 的测试就是我们的一把尺子。
    3. 已经有大量的已有业务正在使用 MySQL,同时也遇到了扩展性的问题,如果放弃这部分有直接痛点的场景和用户,也是不明智的。

    另一方面来看,MySQL 自从被 Oracle 收购后,不管是性能还是稳定性这几年都在稳步的提升,甚至在某些场景下,已经开始有替换 Oracle 的能力,从大的发展趋势上来说,是非常健康的,所以跟随着这个健康的社区一起成长,对我们来说也是一个商业上的策略。

    三、为什么 TiDB 的设计中 SQL 层和存储层是分开的

    一个显而易见的原因是对运维的友好性。很多人觉得这个地方稍微有点反直觉,多一个组件不就会增加部署的复杂度吗?

    其实在实际生产环境中,运维并不仅仅包含部署。举个例子,如果在 SQL 层发现了一个 BUG 需要紧急的更新,如果所有部件都是耦合在一起的话,你面临的就是一次整个集群的滚动更新,如果分层得当的话,你可能需要的只是更新无状态的 SQL 层,反之亦然。

    另外一个更深层次的原因是成本。存储和 SQL 所依赖的计算资源是不一样的,存储会依赖 IO,而计算对 CPU 以及内存的要求会更高,无需配置 PCIe/NVMe/Optane 等磁盘,而且这两者是不一定对等的,如果全部耦合在一起的话,对于资源调度是不友好的。 对于 TiDB 来说,目标定位是支持 HTAP,即 OLTP 和 OLAP 需要在同一个系统内部完成。显然,不同的 workload 即使对于 SQL 层的物理资源需求也是不一样的,OLAP 请求更多的是吞吐偏好型以及长 query,部分请求会占用大量内存,而 OLTP 面向的是短平快的请求,优化的是延迟和 OPS (operation per second),在 TiDB 中 SQL 层是无状态的,所以你可以将不同的 workload 定向到不同的物理资源上做到隔离。还是那句话,对调度器友好,同时调度期的升级也不需要把整个集群全部升级一遍。

    另一方面,底层存储使用 KV 对数据进行抽象,是一个更加灵活的选择。

    一个好处是简单。对于 Scale-out 的需求,对 KV 键值对进行分片的难度远小于对带有复杂的表结构的结构化数据,另外,存储层抽象出来后也可以给计算带来新的选择,比如可以对接其他的计算引擎,和 TiDB SQL 层同时平行使用,TiSpark 就是一个很好的例子。

    从开发角度来说,这个拆分带来的灵活度还体现在可以选择不同的编程语言来开发。对于无状态的计算层来说,我们选择了 Go 这样开发效率极高的语言,而对于存储层项目 TiKV 来说,是更贴近系统底层,对于性能更加敏感,所以我们选择了 Rust,如果所有组件都耦合在一起很难进行这样的按需多语言的开发,对于开发团队而言,也可以实现专业的人干专业的事情,存储引擎的开发者和 SQL 优化器的开发者能够并行的开发。 另外对于分布式系统来说,所有的通信几乎都是 RPC,所以更明确的分层是一个很自然的而且代价不大的选择。

    四、为什么不复用 MySQL 的 SQL 层,而是选择自己重写

    这点是我们和很多友商非常不一样的地方。 目前已有的很多方案,例如 Aurora 之类的,都是直接基于 MySQL 的源码,保留 SQL 层,下面替换存储引擎的方式实现扩展,这个方案有几个好处:一是 SQL 层代码直接复用,确实减轻了一开始的开发负担,二是面向用户这端确实能做到 100% 兼容 MySQL 应用。

    但是缺点也很明显,MySQL 已经是一个 20 多年的老项目,设计之初也没考虑分布式的场景,整个 SQL 层并不能很好的利用数据分布的特性生成更优的查询计划,虽然替换底层存储的方案使得存储层看上去能 Scale,但是计算层并没有,在一些比较复杂的 Query 上就能看出来。另外,如果需要真正能够大范围水平扩展的分布式事务,依靠 MySQL 原生的事务机制还是不够的。

    自己重写整个 SQL 层一开始看上去很困难,但其实只要想清楚,有很多在现代的应用里使用频度很小的语法,例如存储过程什么的,不去支持就好了,至少从 Parser 这层,工作量并不会很大。 同时优化器这边自己写的好处就是能够更好的与底层的存储配合,另外重写可以选择一些更现代的编程语言和工具,使得开发效率也更高,从长远来看,是个更加省事的选择。

    五、为什么用 RocksDB 和 Etcd Raft

    很多工程师都有着一颗造轮子(玩具)的心,我们也是,但是做一个工业级的产品就完全不一样了,目前的环境下,做一个新的数据库,底层的存储数据结构能选的大概就两种:1. B+Tree, 2. LSM-Tree。

    但是对于 B+Tree 来说每个写入,都至少要写两次磁盘: 1. 在日志里; 2. 刷新脏页的时候,即使你的写可能就只改动了一个 Byte,这个 Byte 也会放大成一个页的写 (在 MySQL 里默认 InnoDB 的 Page size 是 16K),虽然说 LSM-Tree 也有写放大的问题,但是好处是 LSM-tree 将所有的随机写变成了顺序写(对应的 B+tree 在回刷脏页的时候可能页和页之间并不是连续的)。 另一方面,LSMTree 对压缩更友好,数据存储的格式相比 B+Tree 紧凑得多,Facebook 发表了一些关于 MyRocks 的文章对比在他们的 MySQL 从 InnoDB 切换成 MyRocks (Facebook 基于 RocksDB 的 MySQL 存储引擎)节省了很多的空间。所以 LSM-Tree 是我们的选择。

    选择 RocksDB 的出发点是 RocksDB 身后有个庞大且活跃的社区,同时 RocksDB 在 Facebook 已经有了大规模的应用,而且 RocksDB 的接口足够通用,并且相比原始的 LevelDB 暴露了很多参数可以进行针对性的调优。随着对于 RocksDB 理解和使用的不断深入,我们也已经成为 RocksDB 社区最大的使用者和贡献者之一,另外随着 RocksDB 的用户越来越多,这个项目也会变得越来越好,越来越稳定,可以看到在学术界很多基于 LSM-Tree 的改进都是基于 RocksDB 开发的,另外一些硬件厂商,特别是存储设备厂商很多会针对特定存储引擎进行优化,RocksDB 也是他们的首选之一。

    反过来,自己开发存储引擎的好处和问题同样明显,一是从开发到产品的周期会很长,而且要保证工业级的稳定性和质量不是一个简单的事情,需要投入大量的人力物力。好处是可以针对自己的 workload 进行定制的设计和优化,由于分布式系统天然的横向扩展性,单机有限的性能提升对比整个集群吞吐其实意义不大,把有限的精力投入到高可用和扩展性上是一个更加经济的选择。 另一方面,RocksDB 作为 LSM-Tree 其实现比工业级的 B+Tree 简单很多(参考对比 InnoDB),从易于掌握和维护方面来说,也是一个更好的选择。 当然,随着我们对存储的理解越来越深刻,发现很多专门针对数据库的优化在 RocksDB 上实现比较困难,这个时候就需要重新设计新的专门的引擎,就像 CPU 也能做图像处理,但远不如 GPU,而 GPU 做机器学习又不如专用的 TPU。

    选择 Etcd Raft 的理由也类似。先说说为什么是 Raft,在 TiDB 项目启动的时候,我们其实有过在 MultiPaxos 和 Raft 之间的纠结,后来结论是选择了 Raft。Raft 的算法整体实现起来更加工程化,从论文就能看出来,论文中甚至连 RPC 的结构都描述了出来,是一个对工业实现很友好的算法,而且当时工业界已经有一个经过大量用户考验的开源实现,就是 Etcd。而且 Etcd 更加吸引我们的地方是它对测试的态度,Etcd 将状态机的各个接口都抽象得很好,基本上可以做到与操作系统的 API 分离,极大降低了写单元测试的难度,同时设计了很多 hook 点能够做诸如错误注入等操作,看得出来设计者对于测试的重视程度。

    与其自己重新实现一个 Raft,不如借力社区,互相成长。现在我们也是 Etcd 社区的一个活跃的贡献者,一些重大的 Features 例如 Learner 等新特性,都是由我们设计和贡献给 Etcd 的,同时我们还在不断的为 Etcd 修复 Bug。

    没有完全复用 Etcd 的主要的原因是我们存储引擎的开发语言使用了 Rust,Etcd 是用 Go 写的,我们需要做的一个工作是将他们的 Raft 用 Rust 语言重写,为了完成这个事情,我们第一步是将 Etcd 的单元测试和集成测试先移植过来了(没错,这个也是选择 Etcd 的一个很重要的原因,有一个测试集作为参照),以免移植过程破坏了正确性。另外一方面,就如同前面所说,和 Etcd 不一样,TiKV 的 Raft 使用的是 Multi-Raft 的模型,同一个集群内会存在海量的互相独立 Raft 组,真正复杂的地方在如何安全和动态的分裂,移动及合并多个 Raft 组,我在我的 这篇文章 里面描述了这个过程。

    六、为什么有这样的硬件配置要求

    我们其实对生产环境硬件的要求还是蛮高的,对于存储节点来说,SSD 或者 NVMe 或者 Optane 是刚需,另外对 CPU 及内存的使用要求也很高,同时对大规模的集群,网络也会有一些要求 (详见我们的官方文档推荐配置的 相关章节),其中一个很重要的原因是我们底层的选择了 RocksDB 的实现,对于 LSM Tree 来说因为存在写放大的天然特性,对磁盘吞吐需求会相应的更高,尤其是 RocksDB 还有类似并行 Compaction 等特性。 而且大多数机械磁盘的机器配置倾向于一台机器放更大容量的磁盘(相比 SSD),但是相应的内存却一般来说不会更大,例如 24T 的机械磁盘 + 64G 内存,磁盘存储的数据量看起来更大,但是大量的随机读会转化为磁盘的读,这时候,机械磁盘很容易出现 IO 瓶颈,另一方面,对于灾难恢复和数据迁移来说,也是不太友好的。

    另外,TiDB 的各个组件目前使用 gRPC 作为 RPC 框架,gPRC 是依赖 HTTP2 作为底层协议,相比很多朴素的 RPC 实现,会有一些额外的 CPU 开销。TiKV 内部使用 RocksDB 的方式会伴随大量的 Prefix Scan,这意味着大量的二分查找和字符串比较,这也是和很多传统的离线数据仓库很不一样的 Pattern,这个会是一个 CPU 密集型的操作。在 TiDB 的 SQL 层这端,SQL 是计算密集型的应用这个自然不用说,另外对内存也有一定的需求。由于 TiDB 的 SQL 是一个完整的 SQL 实现,表达力和众多中间件根本不是一个量级,有些算子,比如 Hashjoin,就是会在内存里开辟一块大内存来执行 Join,所以如果你的查询逻辑比较复杂,或者 Join 的一张子表比较大的情况下(偏 OLAP 实时分析业务),对内存的需求也是比较高的,我们并没有像单机数据库的优化器一样,比如 Order by 内存放不下,就退化到磁盘上,我们的哲学是尽可能的使用内存。 如果硬件资源不足,及时的通过拒绝执行和失败通知用户,因为有时候半死不活的系统反而更加可怕。

    另外一方面,还有很多用户使用 TiDB 的目的是用于替换线上 OLTP 业务,这类业务对于性能要求是比较高的。 一开始我们并没有在安装阶段严格检查用户的机器配置,结果很多用户在硬件明显没有匹配业务压力的情况下上线,可能一开始没什么问题,但是峰值压力一上来,可能就会造成故障,尽管 TiDB 和 TiKV 对这种情况做了层层的内部限流,但是很多情况也无济于事。 所以我们决定将配置检查作为部署脚本的强制检查,一是减少了很多沟通成本,二是可以让用户在上线时尽可能的减少后顾之忧。

    七、为什么用 Range-based 的分片策略,而不是 Hash-based

    Hash-based 的问题是实现有序的 Scan API 会比较困难,我们的目标是实现一个标准的关系型数据库,所以会有大量的顺序扫描的操作,比如 Table Scan,Index Scan 等。用 Hash 分片策略的一个问题就是,可能同一个表的数据是不连续的,一个顺序扫描即使几行都可能会跨越不同的机器,所以这个问题上没得选,只能是 Range 分片。 但是 Range 分片可能会造成一些问题,比如频繁读写小表问题以及单点顺序写入的问题。 在这里首先澄清一下,静态分片在我们这样的系统里面是不存在的,例如传统中间件方案那样简单的将数据分片和物理机一一对应的分片策略会造成:

    • 动态添加节点后,需要考虑数据重新分布,这里必然需要做动态的数据迁移;
    • 静态分片对于根据 workload 实时调度是不友好的,例如如果数据存在访问热点,系统需要能够快速进行数据迁移以便于将热点分散在不同的物理服务商上。

    回到刚才提到的基于 Range 分片的问题,刚才我说过,对于顺序写入热点的问题确实存在,但也不是不可解。对于大压力的顺序写入的场景大多数是日志或者类似的场景,这类场景的典型特点是读写比悬殊(读 << 写),几乎没有 Update 和随机删除,针对这种场景,写入压力其实可以通过 Partition Table 解决,这个已经在 TiDB 的开发路线图里面,今年之内会和大家见面。

    另外还有一个频繁读写小表造成的热点问题。这个原因是,在底层,TiDB 的数据调度的最小单位是 Region,也就是一段段按字节序排序的键值 Key-Value Pairs (默认大小 96M),假设如果一个小表,总大小连 96M 都不到,访问还特别频繁,按照目前的机制,如果不强制的手动 Split,调度系统无论将这块数据调度到什么位置,新的位置都会出现热点,所以这个问题本质上是无解的。因此建议如果有类似的访问 pattern,尽可能的将通用的小表放到 Redis 之类的内存缓存中,或者甚至直接放在业务服务的内存里面(反正小)。

    八、为什么性能(延迟)不是唯一的评价标准

    很多朋友问过我,TiDB 能替换 Redis 吗?大家对 Redis 和 TiDB 的喜爱之情我也很能理解,但是很遗憾,TiDB 并不是一个缓存服务,它支持跨行强一致事务,在非易失设备上实现持久化存储,而这些都是有代价的。

    简单来说,写磁盘的 IO 开销 (WAL,持久化),多副本高可用和保证分布式事务必然会牺牲延迟,更不用说做跨数据中心的同步了,在这点上,我认为如果需要很低延迟的响应速度(亚毫秒级)就需要在业务端做缓存了。TiDB 的定位是给业务提供一个可扩展的 The Source of Truth (真相之源),即使业务层的缓存失效,也有一个地方能够提供强一致的数据,而且业务不用关心容量问题。另一方面,衡量一个分布式系统更有意义的指标是吞吐,这个观点我在很多文章里已经提到过,提高并发度,如果系统的吞吐能够随着集群机器数量线性提升,而且延迟是稳定的才有意义,而且这样才能有无限的提升空间。在实际的环境中,单个 TiDB 集群已经有一些用户使用到了百万级别的 QPS,这个在单机架构上是几乎不可能实现的。另外,这几年硬件的进步速度非常快,特别是 IO 相关的创新,比如 NVMe SSD 的普及,还有刚刚商用的持久化内存等新的存储介质。很多时候我们在软件层面上绞尽脑汁甚至牺牲代码的优雅换来一点点性能提升,很可能换块磁盘就能带来成倍的提升。

    我们公司内部有一句话:Make it right before making it fast。正确性和可靠性的位置是在性能之前的,毕竟在一个不稳定的系统上谈性能是没有意义的。

    九、为什么弹性伸缩能力如此重要

    在业务初期,数据量不大,业务流量和压力不大的时候,基本随便什么数据库都能够搞定,但很多时候业务的爆发性增长可能是没有办法预期的,特别是一些 ToC 端的应用。早期的 Twitter 用户一定对时不时的大鲸鱼(服务不可用)深恶痛绝,近一点还有前两年有一段时间爆红的足记 App,很短的时间之内业务和数据量爆发性增长,数据库几乎是所有这些案例中的核心瓶颈。 很多互联网的 DBA 和年轻的架构师会低估重构业务代码带来的隐形成本,在业务早期快速搞定功能和需求是最重要的。想象一下,业务快速增长,服务器每天都因为数据库过载停止服务的时候,DBA 告诉你没办法,先让你重新去把你的业务全改写成分库分表的形式,在代码里到处加 Sharding key,牺牲一切非 Sharding key 的多维关联查询和相关的跨 Shard 的强一致事务,然后数据复制好多份……这种时候是真正的时间等于金钱,决定这个公司生死存亡的时候不是去写业务和功能代码,而是因为基础设施的限制被迫重构,其实是非常不好的。 如果这个时候,有一个方案,能够让你几乎无成本的,不修改业务代码的时候对数据库吞吐进行线性扩展(无脑加机器其实是最便宜的),最关键的是为了业务的进化争取了时间,我相信这个选择其实一点都不难做。

    其实做 TiDB 的初心正是如此,我们过去见到了太多类似的血和泪,实在不能忍了,分库分表用各种中间件什么的炫技是很简单,但是我们想的是真正解决很多开发者和 DBA 眼前的燃眉之急。

    最近这段时间,有两个用户的例子让我印象很深,也很自豪,一个是 Mobike,一个是转转,前者是 TiDB 的早期用户,他们自己也在数据增长很快的时候就开始使用 TiDB,在快速的发展过程中没有因为数据库的问题掉链子;后者是典型的快速发展的互联网公司,一个 All-in TiDB 的公司,这个早期的技术选型极大的解放了业务开发的生产力,让业务能够更放开手脚去写业务代码,而不是陷入无休止的选择 Sharding key,做读写分离等等和数据库较劲的事情。

    为业务开发提供更加灵活便捷和低成本的智能基础存储服务,是我们做 TiDB 的出发点和落脚点,分布式/高可用/方便灵活的编程接口/智能省心,这些大的方向上也符合未来主流的技术发展趋势。对于CEO 、 CTO 和架构师这类的管理者而言,在解决眼前问题的同时,跟随大的技术方向,不给未来多变的业务埋坑,公司尽可能快速发展,这个才是核心要去思考的问题。

    十、如何根据自己的实际情况参考业内的使用案例

    TiDB 是一个通用的数据库,甚至希望比一般的数据库更加通用,TiDB 是很早就尝试融合 OLTP 和 OLAP 的边界的数据库产品,我们是最早将 HTAP 这个概念从实验室和论文里带到现实的产品之一。这类通用基础软件面临的一个问题就是我们在早期其实很难去指导垂直行业的用户把 TiDB 用好,毕竟各自领域都有各自的使用场景和特点,TiDB 的开发团队的背景大部分是互联网行业,所以天然的会对互联网领域的架构和场景更熟悉,但是比如在金融,游戏,电商,传统制造业这些行业里其实我们不是专家,不过现在都已经有很多的行业专家和开发者已经能将 TiDB 在各自领域用得很好。

    我们的 Blog,公众号,官网等平台会作为一个案例分享的中心,欢迎各位正在使用 TiDB 的用户,将你们的思考和使用经验分享给我们,就像现在已有案例背后的许多公司一样,我们会将你们的经验变成一篇篇的用户案例,通过我们的平台分享给其他的正在选型的公司。

    查看原文

    赞 3 收藏 2 评论 0

    刘忠旭 发布了文章 · 2018-01-09

    Java新兵2017年精华文章

    2017精华文章汇总

    2017年精选内容汇总,点击标题阅读。

    Spring Boot 系列文章

    1. Spring Boot - 整合Jsp/FreeMarker 2.3k 次浏览
    2. Spring Boot - Servlet、过滤器、监听器、拦截器 1.9k 次浏览
    3. 为什么整合jsp后必须通过spring-boot:run方式启动? 2.3k 次浏览
    4. Spring Boot - 静态资源处理、启动加载、日志处理 3.3k 次浏览
    5. Spring Boot - 整合JdbcTemplate、MyBatis 3.1k 次浏览

    若有所获,帮忙点赞

    高可用架构系列

    1. 金币(积分)商城架构漫谈 1.1k 次浏览

    系统架构设计将是2018年重点内容,大家敬请期待

    讲座【特别推荐】

    Spring Boot 系列文章如若不解,可以通过讲座进行进一步学习。

    手把手的跟你们一起Coding,你快来。

    1. Spring Boot 1.5 快速入门教程
    2. Spring Boot 1.5 进阶
    3. Spring Boot 系列教程(入门+进阶) 【SF编辑推荐】
    查看原文

    赞 5 收藏 6 评论 0

    刘忠旭 关注了用户 · 2017-12-22

    PingCAP @pingcap

    PingCAP 是国内开源的新型分布式数据库公司,秉承开源是基础软件的未来这一理念,PingCAP 持续扩大社区影响力,致力于前沿技术领域的创新实现。其研发的分布式关系型数据库 TiDB 项目,具备「分布式强一致性事务、在线弹性水平扩展、故障自恢复的高可用、跨数据中心多活」等核心特性,是大数据时代理想的数据库集群和云数据库解决方案。目前准生产测试用户 1000 余家,并已被 200 余家不同行业的领先企业应用在实际生产环境,涉及互联网、游戏、金融、政府、电信、制造业等多个领域。

    关注 853

    认证与成就

    • SegmentFault 讲师
    • 获得 62 次点赞
    • 获得 8 枚徽章 获得 0 枚金徽章, 获得 2 枚银徽章, 获得 6 枚铜徽章

    擅长技能
    编辑

    (??? )
    暂时没有

    开源项目 & 著作
    编辑

    (??? )
    暂时没有

    注册于 2017-05-13
    个人主页被 2.7k 人浏览

    bt365体育投注