学习笔记(2)基本配置以及高性能高可用数据库设计
基本配置
微服务设计
gmall-api:抽取所有的bean、模型类、异常、通用类等
gmall-common:抽取工具类,通用类等
gmall_pms:商品管理系统
gmall_ums:用户管理系统
gmall_sms:营销管理系统
gmall_admin_web: 后台管理系统对接前端的web项目
gmall_api: 不包含mapper,impl,mapper应该是每个具体实现里的,impl应该是服务类的真正实现。api保留了接口,javabean。
<!--所有工具类,公共依赖-->
<artifactId>gmall-common</artifactId>
<!--所有的公共api、bean、异常、模型等-->
<artifactId>gmall-api</artifactId>
使用MyBatisPlus逆向生成生成 Mapper 、 Model 、 Service 、 Controller 层代码。下面是其配置步骤
配置整合MyBatisPlus
//在启动类加如下注解
@MapperScan(basePackages = "com.atguigu.gmall.pms.mapper")
//gmall-mbg 逆向工程代码生成器
public class CodeGenerator {
public static void main(String[] args) {
String moduleName = "pms";
// 1、创建代码生成器
AutoGenerator mpg = new AutoGenerator();
// 2、全局配置
GlobalConfig gc = new GlobalConfig();
String projectPath = System.getProperty("user.dir");
gc.setOutputDir("E:\\ideaworkshop\\gmall-parent\\gmall-mbg" + "/src/main/java");
gc.setAuthor("Lfy");
gc.setOpen(false); //生成后是否打开资源管理器
gc.setFileOverride(false); //重新生成时文件是否覆盖
gc.setServiceName("%sService"); //去掉Service接口的首字母I
gc.setIdType(IdType.AUTO); //主键策略
gc.setDateType(DateType.ONLY_DATE);//定义生成的实体类中日期类型
gc.setSwagger2(true);//开启Swagger2模式
gc.setBaseColumnList(true);
gc.setBaseResultMap(true);//生成每个xml的baseResultMap
mpg.setGlobalConfig(gc);
// 3、数据源配置
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl("jdbc:mysql://192.168.2.137:3307/gmall_"+moduleName+"?useUnicode=true&useSSL=false&characterEncoding=utf8");
dsc.setDriverName("com.mysql.jdbc.Driver");
dsc.setUsername("root");
dsc.setPassword("root");
dsc.setDbType(DbType.MYSQL);
mpg.setDataSource(dsc);
// 4、包配置
PackageConfig pc = new PackageConfig();
pc.setModuleName(moduleName); //模块名
pc.setParent("com.atguigu.gmall");
pc.setController("controller");
pc.setEntity("entity");
pc.setService("service");
pc.setMapper("mapper");
mpg.setPackageInfo(pc);
// 5、策略配置
StrategyConfig strategy = new StrategyConfig();
strategy.setInclude(moduleName + "_\\w*");//设置要映射的表名
strategy.setNaming(NamingStrategy.underline_to_camel);//数据库表映射到实体的命名策略
strategy.setTablePrefix(pc.getModuleName() + "_");//设置表前缀不生成
strategy.setEntityTableFieldAnnotationEnable(true);//是否生成实体时,生成字段注解
strategy.setColumnNaming(NamingStrategy.underline_to_camel);//数据库表字段映射到实体的命名策略
strategy.setEntityLombokModel(true); // lombok 模型 @Accessors(chain = true) setter链式操作
//strategy.setLogicDeleteFieldName("is_deleted");//逻辑删除字段名
//strategy.setEntityBooleanColumnRemoveIsPrefix(true);//去掉布尔值的is_前缀
//自动填充
//TableFill gmtCreate = new TableFill("gmt_create", FieldFill.INSERT);
//TableFill gmtModified = new TableFill("gmt_modified", FieldFill.INSERT_UPDATE);
//ArrayList<TableFill> tableFills = new ArrayList<>();
//tableFills.add(gmtCreate);
//tableFills.add(gmtModified);
//strategy.setTableFillList(tableFills);
//strategy.setVersionFieldName("version");//乐观锁列
strategy.setRestControllerStyle(true); //restful api风格控制器
strategy.setControllerMappingHyphenStyle(true); //url中驼峰转连字符
mpg.setStrategy(strategy);
mpg.setTemplateEngine(new FreemarkerTemplateEngine());
// 6、执行
mpg.execute();
}
}
Springboot默认带了一个数据源,比阿里的效率跟高
如上一篇笔记所写,各服务需要远程调用,下面介绍dubbo的配置整合
配置整合dubbo
//启动类加如下注解
@EnableDubbo
//配置文件
dubbo.application.name=gmall-pms
dubbo.registry.address=zookeeper://192.168.2.134:2181
dubbo.protocol.name=dubbo
dubbo.protocol.port=20880
dubbo.consumer.check=false
dubbo控制台:java -jar dubbo-admin-0.0.1-SNAPSHOT.jar
暴露dubbo服务
import com.alibaba.dubbo.config.annotation.Service;
@Slf4j
@Service
@Component
public class BrandServiceImpl extends ServiceImpl<BrandMapper, Brand> implements BrandService {}
@Slf4j打印日志lombok
就不用System.out.println("保存成功...."+byId.getName());了
日志太多,不方便查看,用es检索
将日志导入logstash
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration>
<configuration>
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
<include resource="org/springframework/boot/logging/logback/console-appender.xml"/>
<!--应用名称-->
<property name="APP_NAME" value="gmall-pms"/>
<!--日志文件保存路径-->
<property name="LOG_FILE_PATH" value="${LOG_FILE:-${LOG_PATH:-${LOG_TEMP:-${java.io.tmpdir:-/tmp}}}/logs}"/>
<contextName>${APP_NAME}</contextName>
<!--每天记录日志到文件appender-->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_FILE_PATH}/${APP_NAME}-%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${FILE_LOG_PATTERN}</pattern>
</encoder>
</appender>
<!--输出到logstash的appender-->
<appender name="LOGSTASH" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
<destination>192.168.2.137:4560</destination>
<encoder charset="UTF-8" class="net.logstash.logback.encoder.LogstashEncoder"/>
</appender>
<!--日志级别 DEBUG-INFO-WARN-ERROR -->
<root level="DEBUG">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE"/>
<appender-ref ref="LOGSTASH"/>
</root>
</configuration>
<!--引入logback整合logstash的第三方jar包-->
<!-- https://mvnrepository.com/artifact/net.logstash.logback/logstash-logback-encoder -->
<dependency>
<groupId>net.logstash.logback</groupId>
<artifactId>logstash-logback-encoder</artifactId>
<version>5.3</version>
</dependency>
高性能高可用数据库设计
数据库是整个系统最重要的一环,也是前面限流、缓存、熔断和降级等守卫的最终目标。
主从同步
最容易想到 “防止数据丢失” 的手段就是备份。备份有多种方式,如可以由运维人员手工备份、编写定时备份的脚本,或者通过触发器实现远程的实时备份等,这里我们主要介绍使用 “主从同步” 来实现数据库的备份。
目前主流的关系型数据库都支持主从同步。如图所示,当向主数据库 Master 中写入数据时, Master 会自动将数据备份到两个从数据库 Slaver1 和 Slaver2 中。
主从同步的原理实际和 “数据库灾难恢复” 的原理是一致的:都是通过日志,将记录的所有的 DML 重新执行了一次。以 MySQL 为例,当对 MySQL 进行 DML 操作时,相应的增删改语句就会被记录在一个名为 binlog 的日志文件中。之后,主数据库通过网络将 binlog 日志文件的内容发送给从数据库,从数据库在收到后就会解析 binlog 中记录的 DML ,并且将解析后的 DML 写入自己的日志文件中(称为 “relay log”),最后从数据库会把 relay log 中全部的 DML 重新执行一遍,从而实现对主数据库的备份作用,整个过程如图所示。
主从配置简要步骤:
1)、主从数据库在自己配置文件中声明需要同步哪个数据库,忽略哪个数据库等信息。并且server-id不能一样
2)、主库授权某个账号密码来同步自己的数据
3)、从库使用这个账号密码连接主库来同步数据
读写分离
有了 “主从同步” 的结构后,还可以进一步实现 “读写分离”。一般而言,我们会选择将数据写入 “主数据库”,而从 “从数据库” 中读取数据。读写分离看似简单,但会大幅度提高数据库的性能。因为对数据库的访问总是 “读多写少”,而数据的并发冲突仅仅发生在 “写” 上。读写分离可以让大量的读请求与较少的写请求相互隔离,因此就不用再考虑读请求的并发冲突问题,因此可以大大降低并发冲突的基数。
MyCat是重量级框架。sharding-jdbc为增强版的数据库驱动,是轻量级框架。
我们使用sharding-jdbc完后读写分离。Sharding-JDBC 采用在 JDBC 协议层扩展分库分表,是一个以 jar 形式提供服务的轻量级组件,其核心思路是小而美地完成最核心的事情。
下面使用sharding-jdbc配置多个数据源
//配置文件
dataSources:
db_master: !!com.zaxxer.hikari.HikariDataSource
driverClassName: com.mysql.jdbc.Driver
jdbcUrl: jdbc:mysql://192.168.2.137:3307/gmall_pms?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: root
db_slave: !!com.zaxxer.hikari.HikariDataSource
driverClassName: com.mysql.jdbc.Driver
jdbcUrl: jdbc:mysql://192.168.2.137:3316/gmall_pms?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: root
masterSlaveRule:
SpringBoot的牛逼之处:SpringBoot引入某个场景,这个场景的组件就会自动配置好。
比如数据源自动配置:
分库分表
主从同步是为了备份,而读写分离可以降低并发冲突,从而提高访问效率。 除此以外,我们还要思考如何存储电商平台的大量数据。单个关系型数据库的容量有限,无法存放全部的商品数据,因此就需要 “分而治之”:将商品数据根据一定的规则,拆分并存储到多个数据库中。
分库分表与读写分离都可以在应用层通过拦截器实现。例如,我们可以先根据 hash 算法设计数据与多个数据库之间的映射关系。当接收到 SQL 命令时,就使用拦截器分析这条 SQL 的语义,然后在稍加改造后发送到相应的数据库中。例如,当接收到 "insert into goods (gid , gname , gprice) values ( 1001 , ‘iphone10’ , 9999 ) “时,拦截器就会分析这条 SQL 语句的作用是 “插入”,并且能够得知插入的表名是"goods"以及数据的 id 是"1001”。之后,拦截器就可以根据表名和 id 值的 Hash 映射,寻找到对应的具体数据库,进行插入操作。
很多数据访问的 “增强” 功能都是通过拦截器实现的。例如,MyBatis Plus、PageHelper 等数据访问层框架,以及 MyCat 等中间件的底层也都采用的是” 拦截器 “技术。
在实现方式上,对于分库分表和读写分离等数据操作而言,既可以在代码层直接实现,也可以借用 MyCat 等数据库中间件实现。一般而言,对于简单的逻辑非常建议通过编码直接实现,这也是效率最高的做法。如果逻辑较为复杂,或者对数据管理有较高要求,就可以使用 MyCat 等中间件。中间件能够让我们像操作单数据库那样实现分库分表以及读写分离,大大的降低了开发成本,但既然是 “中间件” 就必定会给系统额外增加一层组件,会造成一定程度的性能损耗和网络延迟。
通常可以将主从同步、分库分表和读写分离结合使用。如图所示,当应用程序发来请求时,首先判断是读请求还是写请求。
- 如果是写请求,可以使用 “分库分表” 技术将数据写入 Master1 或 Master2 中。之后, Master1 或 Master2 再根据 “主从同步” 将写入的数据备份到自己的两个 Slaver 中;
- 如果发来的是读请求,就可以直接从 Slaver 中读取。但要注意,因为采用了 “分库分表”,所以在读取时,需要同时读取 Master1 和 Master2 对应的 Slaver 数据。
数据库高可用
我们可以使用 “主从同步” 实现某个 Master 数据库的高可用,但如何保证整个数据库架构的高可用?举个例子,如果使用了 MyCat 来实现读写分离和分库,那么一旦 MyCat 挂掉,就会导致整个数据库也无法使用,如图所示。
在分布式或集群架构中,经常会遇到这种 “单点问题”,典型的解决方案就是 “去中心化”。“去中心化” 的核心是以下两点:
-
与搭建集群类似,通过配置多个服务来防止单点故障;
-
多个服务之间通过 “心跳机制” 保持连接;
在使用 “去中心化” 时,通常会根据我们自己配置的优先级,先从多个服务中选取一个作为主节点。当这个主节点挂点后会通过 “心跳机制” 告知其他节点,其他节点再根据选举策略选择一个新的主节点,然后由这个新节点作为新的入口。如下图所示,可以先配置两个 MyCat ,并首先选择使用 MyCat-A 。当 MyCat-A 挂掉之后,“心跳机制” 会将 MyCat-A 的故障告知 MyCat-B 、 MyCat-C ,随后 MyCat-B 和 MyCat-C 根据选举策略选出一个新的入口(例如 MyCat-B ),之后 MyCat-B 就会承担起分库分表和读写分离的入口。
提示:在实际开发时,通常可以使用 HaProxy + Keepalived 结合实现多个服务的 “去中心化”。
以 MySQL 为例,介绍了主从同步、读写分离、分库分表,这些都是使用关系型数据库应对海量数据的常见解决方案,不仅适用于秒杀系统,也适用于大部分项目的存储需求。