简介
之前提到的用sharding-jdbc对mysql进行读写分离可以很好的处理在读远远大于写的情况下的并发问题,但是无法突破写入大量数据造成性能下降的问题,在数据量达到千万甚至亿级别时,内存可能已经无法缓存全部索引,就要从硬盘中读取索引,这时即便再怎么优化也难免造成性能下降的问题,这时我们需要对数据进行分库分表,mysql中提供了分区功能,但是无法突破单机性能瓶颈,这里我们使用sharding-jdbc来实现分库分表功能。
在读数据远远大于写数据时,且写数据连在千万级别,为了扩展数据库的可读性可以采用数据库的读写分离结构来减轻读数据的压力。其实现方案就是利用数据库的主备,将读请求分发到各个从库中,来减轻读压力,实现这部分方案往往依赖中间件去屏蔽数据库访问信息(开发者像使用单数据源一样进行开发),而这类中间件又可以分为两部分,一部分为植入应用程序,通常为jar包,如淘宝的 TDDL,在访问数据源时由植入的中间件完成对数据请求的分发,将读请求分发到不同的数据库中,采用这部分中间的优点是简单易用,缺点是不能跨语言去使用。还有一部分中间件为独立部署到服务器中,应用程序去访问这部分服务,然后由服务去完成请求的处理和转发。sharding-jdbc(现在托管给Apache改名为ShardingSphere)兼备了这两种功能,这里我们使用ShardingSphere的sharding-jdbc模块来植入应用程序来实现一个读写分离的应用。
数据库连接池是为了规避每次查询时都向数据库建立连接而采用的技术,而向数据库创建连接往往比一次简单的查询所耗费的时间多好几倍,采用连接池在项目中可以有效的提升数据库查询的响应速度,而在SpringBoot中的spring-boot-starter-jdbc模块中又默认集成了HikariCP连接池,使我们很方便的在Springboot中使用数据库连接池.注意一下,由于本人使用的是mybatis持久化框架,HikariCP的依赖包含在spring-boot-starter-jdbc模块中,而mybatis-spring-boot-starter又包含了spring-boot-starter-jdbc,所以只需要引入mybatis-spring-boot-starter即可
Executors 是JDK 1.5中提供的一个创建线程池的建议类,其是对ThreadPoolExecutor进行了一次封装,屏蔽了ThreadPoolExecutor复杂的参数,简单使用且暴力。Executors提供了3种线程池分别为newCachedThreadPool(无固定大小线程池,如果线程空闲时间超过60s则回收),固定线程池newFixedThreadPool,和单一线程池newSingleThreadExecutor(只有一个线程),相比使用ThreadPoolExecutor,Executors虽然也是使用ThreadPoolExecutor去创建线程池,但是其给ThreadPoolExecutor提供了默认参数,这就使我们在使用Executors时比ThreadPoolExecutor简单的多得多,往往只有传入一个创建线程的个数参数即可,但就是由于其屏蔽了ThreadPoolExecutor的细节导致在使用Executors在处理大量任务时存在OOM的隐患,所以在阿里巴巴编程规范中明确禁止使用Executors去创建线程池,我们可以通过查看api和查看
Executors代码可以得知为什么会这样.
在<使用agent和javassist实现基于jvm的aop日志打印系统>一文中,我使用了javaagent完成了一个基于JVM的aop,在main方法运行前动态的修改class文件的字节码,从而达到aop的效果,而java应用代码的编写者却感觉不到我们修改了代码,但是使用javaagent我们在启动java的main方时启动premain方法(虽然inst.addTransformer启动的ClassFileTransformer是永久存在的,也就是说后续用户自定义类加载器,并放弃双亲委派时(因为使用双亲委派模式每次加载的都是同一个class对象)也会调用ClassFileTransformer里的方法)(具体实践可查看使用agent和javassist实现基于jvm的aop日志打印系统 中的更进一步一节),但是这要求我们每次在主代码启动时必须加上javaagent参数去启动主代码包,如果我们转换器ClassFileTransformer需要变动的话那还是要停掉jvm,那么有无一种可以在运行时可以动态修改字节码的方法呢?答案就是JDK1.6提供的agentmain。
之前主要使用oracle来实现存储过程,今天在写mysql存储过程时发现mysql并没有类似oracle的for循环语法,因此通过游标方式来实现类似功能,本过程通过正则切割地区id将统计局的全国到村和街道的数据装换成供数据仓库使用的宽表结构 (因为统计局提供的地区层次是通过id来区分的,code字段共有12个字符,第1、2位标识省份,第3、4位标识地市、第5、6位标识县区,第7-9位标识乡镇、第10-12位标识村。其数据来自http://www.stats.gov.cn/tjsj/tjbz/tjyqhdmhcxhfdm/), 转化后的表结构如下 注意这里多了一列当前正在使用的id,这是为了应对后续存在的地区名字或结构变化,这里本人采用的是维度变化的类型6的变种,如果id和正在使用的id相同则代表当前地区是最新结构,否则通过子查询去寻找当前的正在使用的地区id,因为这张表中目前有70多万条数据,如果重新生成较为耗时,且这是一个通用维度,历史数据还是关联就的地区状态,所以需要保留地区维度表的历史信息,且地区信息是拉平的(数据仓库维度表中数据都是拉平的,以此表来说如果有些数据是关联在福建省下则,需要创建,福建省,福建市,福建县,福建镇,福建乡),因为如果维度不拉平则在统计分析时会漏掉直接关联在福建省下的数据.