Skip to content

Zebra读写分离接入

Lucky Xin edited this page Jun 1, 2021 · 9 revisions

1.介绍

GroupDataSource是Zebra提供的读写分离数据源,主要提供主从分离, 就近路由,端到端监控等功能。 如果你还不理解读写分离的概念,请参考Zebra读写分离介绍

2.准备

2.1远程配置中心接入(zookeeper)

(1)zookeeper配置文件
需要在项目中的src/main/resource目录下面添加zookeeper.properties文件,指定zookeeper的地址

zkserver=localhost:2181

(2)应用名
需要在项目中的src/main/resource目录下面添加app.properties文件,指定应用名

app_name=zebra_ut

(3)就近读取功能的机房配置 在zookeeper中路径 /zebra/zebra-router/region/config下添加center配置(一般一个城市作为一个region, 多个机房组成1个idc),eg.

{
  "regions" : [
    {"region" : "region1", "centers" : [
      {"center":"center1","priority":2, "idcs":[
        {"idc" : "idc1", "desc" : "机房1"},
        {"idc" : "idc2", "desc" : "机房2"},
        {"idc" : "idc3", "desc" : "机房3"}
      ]},
      {"center":"center2","priority":1,"idcs":[
        {"idc" : "idc4", "desc" : "机房4"}
      ]}
    ]},
    {"region" : "region2", "centers" : [
      {"center":"center3","priority":1,"idcs":[
        {"idc" : "idc5", "desc" : "机房5"},
        {"idc" : "idc6", "desc" : "机房6"}
      ]}
    ]}
  ]
}

在zookeeper中路径 /zebra/zebra-router/idc/config 下添加idc(机房一般包含多个网段)的配置, eg.

{
	"regions" : [
		{"region" : "region1", "idcs" : [
			{"idc" : "idc1", "net" : ["192.1", "192.2"], "desc" : "机房1"},
			{"idc" : "idc2", "net" : ["192.3", "192.4"], "desc" : "机房2"},
			{"idc" : "idc3", "net" : ["192.5"], "desc" : "机房3"},
			{"idc" : "idc4", "net" : ["192.6"], "desc" : "机房4"}
		]},
		{"region" : "region2", "idcs" : [
			{"idc" : "idc5", "net" : ["192.10", "192.11"], "desc" : "机房5"},
			{"idc" : "idc6", "net" : ["192.20", "192.21"], "desc" : "机房6"}
		]}
	]
}

(4)以上信息添加完成后,使用zebra的管理平台进行读写分离配置的注册,得到一个jdbcref,参考zebra管理平台使用

(5)配置完成后请跳至下文 3.使用说明 进行后续接入

2.2本地配置接入

(1)应用名
同样需要在项目中的src/main/resource目录下面添加app.properties文件,指定应用名

app_name=zebra_ut

(2) 机房信息 机房信息配置在本地文件中, 在src/main/resource目录下创建region目录,其中包含2个文件:center.json和idc.json

center.json

{
  "regions" : [
    {"region" : "region1", "centers" : [
      {"center":"center1","priority":2, "idcs":[
        {"idc" : "idc1", "desc" : "机房1"},
        {"idc" : "idc2", "desc" : "机房2"},
        {"idc" : "idc3", "desc" : "机房3"}
      ]},
      {"center":"center2","priority":1,"idcs":[
        {"idc" : "idc4", "desc" : "机房4"}
      ]}
    ]},
    {"region" : "region2", "centers" : [
      {"center":"center3","priority":1,"idcs":[
        {"idc" : "idc5", "desc" : "机房5"},
        {"idc" : "idc6", "desc" : "机房6"}
      ]}
    ]}
  ]
}

idc.json

{
	"regions" : [
		{"region" : "region1", "idcs" : [
			{"idc" : "idc1", "net" : ["192.1", "192.2"], "desc" : "机房1"},
			{"idc" : "idc2", "net" : ["192.3", "192.4"], "desc" : "机房2"},
			{"idc" : "idc3", "net" : ["192.5"], "desc" : "机房3"},
			{"idc" : "idc4", "net" : ["192.6"], "desc" : "机房4"}
		]},
		{"region" : "region2", "idcs" : [
			{"idc" : "idc5", "net" : ["192.10", "192.11"], "desc" : "机房5"},
			{"idc" : "idc6", "net" : ["192.20", "192.21"], "desc" : "机房6"}
		]}
	]
}

(3)机房信息创建完成后,开始进行ds的配置
在src/main/resources目录下创建数据库的配置文件(zebra抽象为jdbcRef,并根据jdbcref找到数据库相关配置,访问数据库),文件名必须以jdbcRef.properties格式命名,例如jdbcRef="sample",那么创建sample.properties配置文件

zebra.group.sample=<groupConfig>\
  <singleConfig>\
  <name>sample-n1</name>\
  <writeWeight>0</writeWeight>\
  <readWeight>-1</readWeight>\
  </singleConfig>\
  <singleConfig>\
  <name>sample-n2</name>\
  <writeWeight>-1</writeWeight>\
  <readWeight>1</readWeight>\
  </singleConfig>\
  </groupConfig>

zebra.ds.sample-n1=<dsConfig>\
  <url>jdbc:mysql://127.0.0.1:3306/sample?characterEncoding=UTF8&amp;socketTimeout=60000</url>\
  <driverClass>com.mysql.jdbc.Driver</driverClass>\
  <active>true</active>\
  <username>root</username>\
  <properties>idleConnectionTestPeriod=80&amp;acquireRetryAttempts=50&amp;acquireRetryDelay=300&amp;maxStatements=1</properties>\
  <password>123456</password>\
  </dsConfig>

zebra.ds.sample-n2=<dsConfig>\
  <url>jdbc:mysql://127.0.0.1:3306/sample?characterEncoding=UTF8&amp;socketTimeout=60000</url>\
  <driverClass>com.mysql.jdbc.Driver</driverClass>\
  <active>true</active>\
  <username>root</username>\
  <properties>idleConnectionTestPeriod=80&amp;acquireRetryAttempts=50&amp;acquireRetryDelay=300&amp;maxStatements=1</properties>\
  <password>123456</password>\
  </dsConfig>

(4)以上配置完成后,进入下文使用说明
特别注意: 使用本地的配置时,GroupDataSource的configManagerType配置需要设置为local

3.使用说明

3.1 第一步:添加依赖

版本选择请参考Zebra版本更新记录,并配合数据监控组件zebra-cat-client一起使用

<dependency>
<!--核心依赖:数据源-->
	<groupId>com.dianping.zebra</groupId>
	<artifactId>zebra-client</artifactId>
	<version>${version}</version>
</dependency>

<dependency>
<!--可选依赖:cat监控-->
	<groupId>com.dianping.zebra</groupId>
	<artifactId>zebra-cat-client</artifactId>
	<version>${version}</version>
</dependency>

3.2 第二步:添加连接池配置

zebra目前支持的数据源有c3p0,tomcat-jdbc,druid,hikaricp, dbcp和dbcp2。zebra使用的配置是基于c3p0的,zebra会自动把c3p0的连接池配置转化成其他类型的连接池配置,对于不能转的配置,zebra会自动给默认值。

基本参数

<bean id="dataSource" class="com.dianping.zebra.group.jdbc.GroupDataSource" init-method="init" destroy-method="close">
    <!-- 必配。指定唯一确定数据库的key-->
    <property name="jdbcRef" value="sample" /> 
    <!-- 选配。指定底层使用的连接池类型,支持"c3p0","tomcat-jdbc","druid","hikaricp","dbcp2"和"dbcp",推荐使用"druid"或者"dbcp2" -->
    <property name="configManagerType" value="zookeeper" />
    <property name="poolType" value="hikaricp" /> 
    <!-- 选配。指定连接池的最小连接数,默认值是5。 -->
    <property name="minPoolSize" value="5" />
    <!-- 选配。指定连接池的最大连接数,默认值是20。 -->
    <property name="maxPoolSize" value="20" />
    <!-- 选配。指定连接池的初始化连接数,默认值是5。 -->
    <property name="initialPoolSize" value="5" />
    <!-- 选配。指定连接池的获取连接的超时时间,默认值是1000。 -->
    <property name="checkoutTimeout" value="1000" />
</bean>

全部参数列表

很多时候,zebra提供的默认连接池参数对业务已经足够使用了,但如果不能够覆盖业务的特殊场景,也能够支持业务按照需要定制底层连接池参数,具体请参考:Zebra读写分离属性列表

代码中直接使用Zebra

如果业务不是在Spring环境中使用zebra的,也可以通过以下代码进行初始化

GroupDataSource dataSource = new GroupDataSource("sample");
//Set other datasource properties if you want

dataSource.init();

//Now dataSource is ready for use
Connection conn = dataSource.getConnection();
 
//Must destory datasource if not use 
dataSource.close();

SpringBoot接入Zebra

本案例介绍:springboot 1.5.9、zebra-client 2.9.1、zebra-dao 0.2.1如何进行整合。

注意:业界ORM框架mybatis提供了mybatis-spring-boot-starter,以便于mybatis与springboot的整合。由于zebra-dao对mybatis进行了封装,这个starter已经无法起作用,因此这里并不使用。

项目目录结构如下所示:

pom.xml核心依赖

 <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>1.2</version>
        </dependency>
        <dependency>
            <groupId>com.dianping.zebra</groupId>
            <artifactId>zebra-client</artifactId>
            <version>2.9.1</version>
        </dependency>
       <dependency>
            <groupId>com.dianping.zebra</groupId>
            <artifactId>zebra-cat-client</artifactId>
            <version>2.9.1</version>
        </dependency>
        <dependency>
            <groupId>com.dianping.zebra</groupId>
            <artifactId>zebra-dao</artifactId>
            <version>0.2.1</version>
        </dependency>
    </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <!--Import dependency management from SpringBoot-->
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>1.5.9.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

DataSourceConfig.java

@Configuration
public class DataSourceConfig {

  private String jdbcref="your_jdbcref_from_dba";
  
	@Bean
	public DataSource zebraDataSource() {
		GroupDataSource groupDataSource = new GroupDataSource(jdbcref);
		groupDataSource.init();
    
		return groupDataSource;
	}

	@Bean(name="sqlSessionFactory")
	public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource) throws IOException {
		SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean();
		ssfb.setDataSource(dataSource);
    //如果你是将sql写在映射xml文件中,那么需要通过以下方式指定xml文件路径;本案例是直接通过注解来编写sql,因此不需要使用以下配置。
		//PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
		//ssfb.setMapperLocations(resolver.getResources("classpath*:config/sqlmap/**/*.xml"));
		return ssfb;
	}

	@Bean
	public ZebraMapperScannerConfigurer mapperScannerConfigurer() throws IOException {
		ZebraMapperScannerConfigurer configurer = new ZebraMapperScannerConfigurer();
		configurer.setSqlSessionFactoryBeanName("sqlSessionFactory");
    //mapper接口所在包名
		configurer.setBasePackage("com.dianping.zebra.springboot.mapper");
    
		return configurer;
	}
}

UserMapper.java

public interface UserMapper {
	@Select("SELECT * FROM `User` where id = #{userId}")
	public UserEntity findUserById(@Param("userId") int userId);
}

UserEntity.java

public class UserEntity {

	private int id;

	private String name;

	private String tel;

	private String email;
	
	private String alias;

	private int role;

	private Date updateTime;
  
  //setters and getters
}

Application.java

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class,args);
    }
}

SpringBootTest.java

@RunWith(SpringJUnit4ClassRunner. class)
@SpringBootTest(classes = Application.class)
public class ZebraSpringbootTest {
    @Autowired
    private UserMapper userMapper;

    @Test
    public void test(){
        UserEntity user = userMapper.findUserById(1);
        System.out.println(user);
    }
}

运行程序之后,控制台输出user内容,则表示成功

注意事项

1.jdbcurl参数

如果需要额外的参数比如zeroDateTimeBehavior=convertToNull或者其他参数,可以通过以下方式修改"extraJdbcUrlParams"参数进行设置或者甚至是覆盖;

如果有大事务或者大查询会超过60s的,请也覆盖掉socketTimeout=60000这个参数。

<bean id="dataSource" class="com.dianping.zebra.group.jdbc.GroupDataSource" init-method="init" destroy-method="close">
    <!--添加额外的jdbc url的参数,已param1=value1&m2=value2的方式注入-->
	<property name="extraJdbcUrlParams" value="param1=value1&amp;param2=value2" /> 
</bean>

配置方法参见: zebra读写分离属性列表 中搜索 “extraJdbcUrlParams” 部分内容。

2.utf8mb4支持

A1: 检查连接mysql的版本 如果是mysql5.7或mysqlserver的collation_server为utf8_general_ci, 将mysql-connector-java升级到5.1.33及以上版本后可以直接支持。

A2: 一般情况下,用户在使用这个参数时,都是设置编码方式:"set names utf8mb4"。

要求mysql-connector-java要在5.1.20以上。poolType不能是c3p0,其他的类型都能支持。 推荐使用最新版本。

其他功能说明

1.强制走主从库

Zebra的GroupDataSource是一个读写分离的数据源,根据业务场景有时会有sql强制走(主/从)库的需求。

Zebra提供了不同粒度的强制走主库及数据源粒度的强制走从库逻辑。

强制走主库逻辑优先级: 整个数据源走主库 > 事务 = SQL Hint > api方式

  • 事务:在事务中的所有操作均会路由到主库进行操作。
  • 单个SQL走主库:使用SQL Hint的方式,SQL前加入Zebra的强制走主Hint /+zebra:w/, 使用后该SQL会强制路由到主库
/*+zebra:w*/select * from test

或者

/*master*/select * from test
  • 单次请求所有SQL走主库:使用Zebra提供的一套API 从调用API到清理标记过程中的所有SQL均会走主库
/**   
* 使用本地的context来设置走主库,该方式只会影响本应用内本次请求的所有sql走主库,不会影响到pigeon后端服务。  
* 调用过该方法后,一定要在请求的末尾调用clearLocalContext进行清理操作。   
* 优先级比forceMasterInPiegonContext低。   
*/
ZebraForceMasterHelper.forceMasterInLocalContext();
xxxDao.insert();
xxxDao.select();
/**   
* 配合forceMasterInLocalContext进行使用,在请求的末尾调用该方法,对LocalContext进行清理。  
*/
ZebraForceMasterHelper.clearLocalContext();
  • 整个数据源走主库:一般情况下,除非对主从延迟要求很高,一般应用不建议使用该配置
<bean id="writeDs" class="com.dianping.zebra.group.jdbc.GroupDataSource" init-method="init">
    <property name="jdbcRef" value="testjdbcref" /> 
    <property name="routerType" value="master-only" />
</bean>
  • 整个数据源走从库:
<bean id="writeDs" class="com.dianping.zebra.group.jdbc.GroupDataSource" init-method="init">
    <property name="jdbcRef" value="testjdbcref" /> 
    <property name="routerType" value="slave-only" />
</bean>

2.路由策略

为了满足高可用的需求,mysql集群和业务服务器集群一般需要进行多机房/多中心部署,为此会产生跨机房/中心访问的问题。 为了平衡跨机房/中心访问带来的延迟问题,需要应用机器能够优先访问部署在同一机房/中心的DB。

示意图如下 这是一个同中心就近读取的例子:

zebra目前提供3种粒度的就行读取策略

  • **同机房优先:**应用访问DB时会优先选择同一个机房内的DB,适合同机房内部署DB并且对网络延时高的应用。
  • **同中心优先:**应用访问DB时会优先选择同一个中心内的DB,适合同中心内部署DB并且对网络延时较高的应用。(中心由1个机房或多个机房组成)
  • **同地域优先:**应用访问DB时会优先选择同一个地域内的DB

这里需要解释一下,这里的读库并不一定是真的读库,而是数据库配置中配置有读流量的库,这个流量也可能在主库

3.3 第三步:添加监控配置

CAT需要的配置有:在Java项目的src/main/resources/META-INF目录下,新建一个文件app.properties,该文件的内容就只有一行:app.name=${appkey}。
接入了CAT监控后,就可以查看SQL的性能等指标。

4.接入zebra-dao(可选)

zebra-dao接入指南

Clone this wiki locally