• 【原创】SSM框架(5):使用一致性hash算法实现动态设置数据源 和 主从读写分离


     一、【转载】一致性哈希算法的原理

      一致性哈希算法,是分布式系统中常用的算法,比如有N台缓存服务器,你需要将数据缓存到这N台服务器上。一致性哈希算法可以将数据尽可能平均的存储到N台缓存服务器上,提高系统的负载均衡,并且当有缓存服务器加入或退出集群时,尽可能少的影响现有缓存服务器的命中率,减少数据对后台服务的大量冲击。 

       一致性哈希算法的基本原理,把数据通过hash函数映射到一个很大的环形空间里,如下图所示:

      A、B、C、D 4台缓存服务器通过hash函数映射环形空间上,数据的存储时,先得到一个hash值,对应到这个环中的每个位置,如缓存数据:K1对应到了图中所示的位置,然后沿顺时针找到一个机器节点A,将K1存储到A节点上,K2存储到A节点上,K3、K4存储到B节点上。

       如果B节点宕机了,则B上的数据就会落到C节点上,如下图所示:

      这样,只会影响C节点,对其他的节点A,D的数据不会造成影响。然而,这又会造成一个“雪崩”的情况,即C节点由于承担了B节点的数据,所以C节点的负载会变高,C节点很容易也宕机,这样依次下去,这样造成整个集群都挂了。

       为此,引入了“虚拟节点”的概念:即把想象在这个环上有很多“虚拟节点”,数据的存储是沿着环的顺时针方向找一个虚拟节点,每个虚拟节点都会关联到一个真实节点,如下图所使用:

      引入“虚拟节点”后,映射关系就从 {对象 ->节点 }转换到了 {对象 ->虚拟节点 }。图中的A1、A2、B1、B2、C1、C2、D1、D2都是虚拟节点,机器A负载存储A1、A2的数据,机器B负载存储B1、B2的数据,机器C负载存储C1、C2的数据。由于这些虚拟节点数量很多,均匀分布,提高了平衡性,因此不会造成“雪崩”现象。

                一致性哈希算法的Java实现(以上内容转载自本链接资源)

                漫画:什么是一致性哈希?

                详解一致性hash算法、hash环

    二、项目实战

    1、故事描述:

      数据库集群中,有三台主库master1、master2、master3和三台从库slave1、slave2、slave3(主从备份关系)。

      现有一业务需求,要求将客户信息Customr(idcardNo:身份证号,name:姓名)保存到数据库中,由于客户数据量特别大,所以要采用分库进行存储。分库的逻辑是:使用客户的身份证号,进行一致性hash运算后,分别存储在master1/2/3中的其中一台;同时要求,为提高服务器查询性能,要进行主从读写分离处理。

    2、实现步骤: 

    1. 在本地创建六个主从数据库,采用触发器模拟实现主从数据同步功能
    2. 修改数据库配置文件,增加以上六个数据源节点,增加动态数据源路由配置
    3. 实现一致性hash算法工具类:HashFunction、ConsistentHash<T>
    4. 重写DynamicDataSourceHolder、DynamicDataSource两个类,使用一致性hash算法,动态计算要查询的目标数据库,实现动态数据源切换:
    5. 编写Service层的业务代码
    6. 编写Controller和客户端代码
    7. 运行代码,测试结果

    3、实现详情:

    步骤一:在本地创建六个主从数据库,采用触发器模拟实现主从数据同步功能

      (1)新建数据库:nb_master_01、nb_master_02、nb_master_03、nb_slave_01、nb_slave_02、nb_slave_03

      (2)在六个数据库中,新建客户信息表customer

    # 人员信息表
    DROP TABLE IF EXISTS customer;
    CREATE TABLE customer(
       id varchar(25) not null comment '主键id',
        idcard_no VARCHAR(18) not null comment '身份证号',
        name VARCHAR(50)  comment '姓名',
        sex CHAR(2) comment '性别',
       remark varchar(50) default '主库-master-01' comment '信息备注',
    );

      (3)在三台主库中,分别创建“insert、update、delete”操作对应的触发器

        触发器增加成功后,当主库通过insert/update/delete语句进行操作时,操作结果会同步更新到从库之中,从而实现从库自动同步主库数据的功能。

    ################################ 增加数据同步触发器
    #######注意根据主从对应关系,修改sql中的主库和从库名称
    use nb_master_01;
    create TRIGGER tr_insert_customer after INSERT on customer for EACH ROW BEGIN -- 向从库nb_slave_01中的customer表,插入一条数据 insert into nb_slave_01.customer (id,idcard_no,name,sex) values (new.id,new.idcard_no,new.name,new.sex); END; use nb_master_01; create TRIGGER tr_update_customer after UPDATE on customer for EACH ROW BEGIN -- 向从库中同步数据 update nb_slave_01.customer set id=new.id,idcard_no=new.idcard_no,name=new.name,sex=new.sex where id=new.id; END; use nb_master_01; create TRIGGER tr_delete_customer before DELETE on customer for EACH ROW BEGIN -- 向从库中同步数据 delete from nb_slave_01.customer where id=old.id; END;

    步骤二:修改数据库配置文件,增加以上六个数据库节点

      (1)修改db.properties,增加数据库的连接地址

    #配置主从数据库的链接地址
    #主库1
    master1.jdbc.url=jdbc:mysql://localhost:3306/nb_master_01?useUnicode=true&characterEncoding=utf8
    #主库
    master2.jdbc.url=jdbc:mysql://localhost:3306/nb_master_02?useUnicode=true&characterEncoding=utf8
    #主库
    master3.jdbc.url=jdbc:mysql://localhost:3306/nb_master_03?useUnicode=true&characterEncoding=utf8
    
    #从库1
    slave1.jdbc.url=jdbc:mysql://localhost:3306/nb_slave_01?useUnicode=true&characterEncoding=utf8
    #从库2
    slave2.jdbc.url=jdbc:mysql://localhost:3306/nb_slave_02?useUnicode=true&characterEncoding=utf8
    #从库
    slave3.jdbc.url=jdbc:mysql://localhost:3306/nb_slave_03?useUnicode=true&characterEncoding=utf8

        (2)在spring-mybatis.xml配置文件中,增加以上六个数据库的bean

        <!--读取静态配置文件,获取相关数据库连接参数 -->
        <context:property-placeholder location="classpath:db.properties"/>
        <!-- 配置数据源 -->
        <bean id="abstractDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
              abstract="true" destroy-method="close">
            <property name="driverClass" value="${jdbc.driver}"/>
            <property name="user" value="${jdbc.username}"/>
            <property name="password" value="${jdbc.password}"/>
            <property name="maxPoolSize" value="${c3p0.maxPoolSize}"/>
            <property name="minPoolSize" value="${c3p0.minPoolSize}"/>
            <property name="autoCommitOnClose" value="${c3p0.autoCommitOnClose}"/>
            <property name="checkoutTimeout" value="${c3p0.checkoutTimeout}"/>
            <property name="acquireRetryAttempts" value="${c3p0.acquireRetryAttempts}"/>
        </bean>
       <!-- 配置数据源:主库 --> <bean id="master1" parent="abstractDataSource"> <property name="jdbcUrl" value="${master1.jdbc.url}"/> </bean> <!-- 配置数据源:主库 --> <bean id="master2" parent="abstractDataSource"> <property name="jdbcUrl" value="${master2.jdbc.url}"/> </bean> <!-- 配置数据源:主库 --> <bean id="master3" parent="abstractDataSource"> <property name="jdbcUrl" value="${master3.jdbc.url}"/> </bean> <!-- 配置数据源:从库1 --> <bean id="slave1" parent="abstractDataSource"> <property name="jdbcUrl" value="${slave1.jdbc.url}"/> </bean> <!-- 配置数据源:从库2 --> <bean id="slave2" parent="abstractDataSource"> <property name="jdbcUrl" value="${slave2.jdbc.url}"/> </bean> <!-- 配置数据源:从库3 --> <bean id="slave3" parent="abstractDataSource"> <property name="jdbcUrl" value="${slave3.jdbc.url}"/> </bean>
    <!-- 动态数据源配置,这个class要完成实例化 --> <bean id="dynamicDataSource" class="com.newbie.util.dbMultipleRouting.DynamicDataSource"> <property name="targetDataSources"> <!-- 指定lookupKey和与之对应的数据源,切换时使用的为key --> <map key-type="java.lang.String"> <entry key="master1" value-ref="master1"/> <entry key="master2" value-ref="master2"/> <entry key="master3" value-ref="master3"/> <entry key="slave1" value-ref="slave1"/> <entry key="slave2" value-ref="slave2"/> <entry key="slave3" value-ref="slave3"/> </map> </property> <!-- 这里可以指定默认的数据源 --> <property name="defaultTargetDataSource" ref="master1"/> </bean> <!-- 配置MyBatis创建数据库连接的工厂类 --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <!--数据源指定为:动态数据源DynamicDataSource --> <property name="dataSource" ref="dynamicDataSource"/> <!-- mapper配置文件 --> <property name="mapperLocations" value="classpath:com/newbie/dao/*.xml"/> </bean> <!-- 配置自动扫描DAO接口包,动态实现DAO接口实例,注入到Spring容器中进行管理 --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <!-- 注入SqlSession工厂对象:SqlSessionFactoryBean --> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/> <!-- 指定要扫描的DAO接口所在包 --> <property name="basePackage" value="com.newbie.dao"/> </bean>

    步骤三:实现一致性hash算法工具类:HashFunction、ConsistentHash<T>

      (1)新建工具类HashFunction,使用MurMurHash算法,实现key的hash值计算。

        一致性hash算法要计算数据库节点的hash值、客户身份证号的hash值,统一使用 HashFunction.hash(key)方法,来计算hash值。

               Murmurhash介绍与实现

               一致性哈希算法的Java实现(推荐阅读)  

    package com.newbie.util.consistentHash;
    
    import java.nio.ByteBuffer;
    import java.nio.ByteOrder;
    /**
     * 使用MurMurHash算法,实现key的hash值计算
     * MurmurHash是一种非加密型哈希函数,性能很高,碰撞率低,适用于一般的哈希检索操作。
     */
    public class HashFunction {
        public Long hash(String key) {
            ByteBuffer buf = ByteBuffer.wrap(key.getBytes());
            int seed = 0x1234ABCD;
    
            ByteOrder byteOrder = buf.order();
            buf.order(ByteOrder.LITTLE_ENDIAN);
    
            long m = 0xc6a4a7935bd1e995L;
            int r = 47;
    
            long h = seed ^ (buf.remaining() * m);
    
            long k;
            while (buf.remaining() >= 8) {
                k = buf.getLong();
    
                k *= m;
                k ^= k >>> r;
                k *= m;
    
                h ^= k;
                h *= m;
            }
    
            if (buf.remaining() > 0) {
                ByteBuffer finish = ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN);
                finish.put(buf).rewind();
                h ^= finish.getLong();
                h *= m;
            }
    
            h ^= h >>> r;
            h *= m;
            h ^= h >>> r;
    
            buf.order(byteOrder);
            return h;
        }
    }

      (2)新建一致性hash算法工具类:ConsistentHash

         a. 定义构造方法,接收数据库节点对象,然后根据节点对象计算生成数据存储的节点;

         b. 定义“public T get(String key)”方法,接收客户的身份证号,计算得出该客户信息存储的目标数据节点。

    package com.newbie.util.consistentHash;
    
    import java.util.Collection;
    import java.util.SortedMap;
    import java.util.TreeMap;
    /**
    * 实现一致性hash计算:实现保存数据库节点方法、实现根据身份证号key来查找存储的节点方法
    *
    @param <T> : 数据库节点对象(此处使用数据源对应的key:master1、master2、master3) */ public class ConsistentHash<T> { //hash函数接口,调用该接口的hash(key)方法,计算hash值 private final HashFunction hashFunction; //每个机器节点,关联的虚拟节点个数 private final int numberOfReplicas; //环形虚拟节点 private final SortedMap<Long, T> circle = new TreeMap<Long, T>(); /** * @param hashFunction:hash 函数接口 * @param numberOfReplicas;每个机器节点关联的虚拟节点个数 * @param nodes: 真实机器节点 */ public ConsistentHash(HashFunction hashFunction, int numberOfReplicas, Collection<T> nodes) { this.hashFunction = hashFunction; this.numberOfReplicas = numberOfReplicas; //遍历真实节点,生成对应的虚拟节点 for (T node : nodes) { add(node); } } /** * 增加真实机器节点 * 由真实节点,计算生成虚拟节点 * @param node */ public void add(T node) { for (int i = 0; i < this.numberOfReplicas; i++) { long hashcode = this.hashFunction.hash(node.toString() + "-" + i); circle.put(hashcode, node); } } /** * 删除真实机器节点 * * @param node */ public void remove(T node) { for (int i = 0; i < this.numberOfReplicas; i++) { long hashcode = this.hashFunction.hash(node.toString() + "-" + i); circle.remove(hashcode); } } /** * * 根据数据的key,计算hash值,然后从虚拟节点中,查询取得真实机器节点对象 * @param key * @return */ public T get(String key) { if (circle.isEmpty()) { return null; } long hash = hashFunction.hash(key); if (!circle.containsKey(hash)) { SortedMap<Long, T> tailMap = circle.tailMap(hash);// 沿环的顺时针找到一个虚拟节点 hash = tailMap.isEmpty() ? circle.firstKey() : tailMap.firstKey(); } return circle.get(hash); // 返回该虚拟节点对应的真实机器节点的信息 } }

    步骤四:使用一致性hash算法,动态计算要查询的目标数据库,实现动态数据源切换

    (1)重写DynamicDataSourceHolder类的实现,实现以下功能:

    • 属性 -- ThreadLocal<String>  DATA_SOURCE_KEY :保存当前主从数据源标识-master/slave,由Service层的方法设置该属性,指定要操作的是主库,还是从库。
    • 属性 -- ThreadLocal<String> CONSISTENT_HASH_KEY :保存要增加(查询)的客户身份证号,用于计算hash值,由Service层的方法设置该属性。
    • 属性 -- ArrayList<String> dataSouceKeyList : 保存所有主库数据源对应的key值集合[master1、master2、master3],用于一致性hash算法中计算生成主机存储节点。在Spring容器实例化DynamicDataSource对象时,通过重写的afterPropertiesSet()方法,主动给本类的dataSouceKeyList、consistentHashDB进行赋值和实例化。
    • 属性 -- ConsistentHash<String> consistentHashDB :一致性hash算法工具对象,用于根据身份证号,计算获取数据的存储节点,获取目标数据源的key。由DynamicDataSource对象的afterPropertiesSet()方法,进行实例化。
    • 方法 -- public static String getDataSourceFinalKey():根据客户的身份证号【CONSISTENT_HASH_KEY】,计算数据存储的主库数据源key;再根据【DATA_SOURCE_KEY】属性的值,判断要操作的是主库还是从库,是否应该修改为从库的key,进行主从读写分离,最终实现数据源的动态切换。
    package com.newbie.util.dbMultipleRouting;
    
    import com.newbie.util.consistentHash.ConsistentHash;
    import com.newbie.util.consistentHash.HashFunction;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.concurrent.atomic.AtomicInteger;
    
    /**
     * 自定义类:用来保存数据源标识,从而在实例化DynamicDataSource时来指定要使用的数据源实例
     */
    public class DynamicDataSourceHolder {
        /**
         * 注意:数据源标识保存在线程变量中,避免多线程操作数据源时互相干扰
         */
        private static final ThreadLocal<String> DATA_SOURCE_KEY = new ThreadLocal<String>();
        /**
         * 分库方案中,用于计算hash码的基值
         */
        private static final ThreadLocal<String> CONSISTENT_HASH_KEY = new ThreadLocal<String>();
        /**
         * 所有主库对应的key值集合
         * 在一致性hash算法中,用于生成主机虚拟节点
         */
        private static List<String> dataSouceKeyList = new ArrayList<String>();
    
        /**
         * 一致性hash算法工具
         */
        private static ConsistentHash<String> consistentHashDB = null;
    
    
        public static String getDataSourceKey(){
            return DATA_SOURCE_KEY.get();
        }
    
        public static void setDataSourceKey(String dataSourceKey){
            DATA_SOURCE_KEY.set(dataSourceKey);
        }
    
        public static void clearDataSourceKey(){
            DATA_SOURCE_KEY.remove();
        }
    
        public static String geConsistentHashKey(){
            return CONSISTENT_HASH_KEY.get();
        }
    
        public static void setConsistentHashKey(String consistentHashKey){
            CONSISTENT_HASH_KEY.set(consistentHashKey);
        }
    
        public static void clearConsistentHashKey(){
            CONSISTENT_HASH_KEY.remove();
        }
    
    
        public static List<String> getDataSouceKeyList() {
            return dataSouceKeyList;
        }
    
        public static void setDataSouceKeyList(List<String> dataSouceKeyList) {
            DynamicDataSourceHolder.dataSouceKeyList = dataSouceKeyList;
        }
    
        /**
         * 向主库集合中,增加一个主库节点
         * @param key
         */
        public static void addDataSourceKey(String key){
            dataSouceKeyList.add(key);
        }
        /**
         * 使用主库集合,实现一致性hash算法
         */
        public static void initConsistentHash(){
            HashFunction hashFunction = new HashFunction();
            consistentHashDB = new ConsistentHash<String>(hashFunction,100,dataSouceKeyList);
        }
    
        /**
         * 按照一致性hash分表方案,计算真实存储的数据源节点,返回对应的数据源的key
         * @return
         */
        public static String getDataSourceFinalKey(){
            //计算真实节点
            String originalKey = consistentHashDB.get(CONSISTENT_HASH_KEY.get());
            //判断要操作的是主库,还是从库
            if(DATA_SOURCE_KEY.get() == null || DATA_SOURCE_KEY.get().startsWith("master")){
                return originalKey;
            }else{
                return originalKey.replace("master","slave");
            }
        }
    
    }

    (2)修改DynamicDataSource类的实现,重写父类的两个方法:

    • public void afterPropertiesSet() :在Spring容器实例化本类对象时,利用反射机制,从父类中拿到数据源对应的key值集合。然后调用DynamicDataSourceHolder类的方法,给属性dataSouceKeyList进行赋值。
    • protected Object determineCurrentLookupKey() :调用DynamicDataSourceHolder类的getDataSourceFinalKey()方法,动态设置数据源标识。

    package com.newbie.util.dbMultipleRouting;
    
    import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
    import org.springframework.util.ReflectionUtils;
    import javax.sql.DataSource;
    import java.lang.reflect.Field;
    import java.util.*;
    import java.util.concurrent.atomic.AtomicInteger;
    
    /**
     * DynamicDataSource由自己实现,实现AbstractRoutingDataSource,数据源由自己指定。
     */
    public class DynamicDataSource extends AbstractRoutingDataSource {
        /**
         * 重写父类的方法
         * 利用反射机制,从父类中拿到数据源的集合resolvedDataSources,以此设置主库的节点(设置 DynamicDataSourceHolder.dataSouceKeyList)
         */
        @Override
        public void afterPropertiesSet() {
            super.afterPropertiesSet();
            //由于父类的resolvedDataSources属性是私有的子类获取不到,需要使用反射获取
            Field field = ReflectionUtils.findField(AbstractRoutingDataSource.class, "resolvedDataSources");
            field.setAccessible(true);
            Map<String, DataSource> resolvedDataSources = null;
            try {
                resolvedDataSources = (Map<String, DataSource>) field.get(this);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
            //根据数据源集合resolvedDataSources,来处理主库的集合
            if (resolvedDataSources != null) {
                Set<Map.Entry<String, DataSource>> entrySet = resolvedDataSources.entrySet();
                Iterator<Map.Entry<String, DataSource>> iterator = entrySet.iterator();
                while (iterator.hasNext()) {
                    //判断key是否以master开头,如果是则认为是主库,并将该key值保存到:ynamicDataSourceHolder.dataSouceKeyList
                    String key = iterator.next().getKey();
                    if (key != null && key.startsWith("master")) {
                        //将主库的key,加入到主库集合列表中
                        DynamicDataSourceHolder.addDataSourceKey(key);
                    }
                }
                //根据主库集合列表,生成一致性hash算法节点
                DynamicDataSourceHolder.initConsistentHash();
            }
        }
    
        /**
         * 动态设置数据源标识
         * @return
         */
        @Override
        protected Object determineCurrentLookupKey() {
            return DynamicDataSourceHolder.getDataSourceFinalKey();
        }
    
    }

    步骤五:编写Service层的业务代码

    package com.newbie.service.impl;
    
    import com.newbie.dao.CustomerMapper;
    import com.newbie.domain.Customer;
    import com.newbie.domain.CustomerExample;
    import com.newbie.domain.UserAccount;
    import com.newbie.service.IConsistentHashService;
    import com.newbie.util.dbMultipleRouting.DynamicDataSourceHolder;
    import com.newbie.util.dbMultipleRouting.annotation.AnnotationDBSourceKey;
    import org.springframework.stereotype.Service;
    import javax.annotation.Resource;
    import java.util.ArrayList;
    import java.util.Iterator;
    import java.util.List;
    import java.util.Random;
    
    /**
     * 使用一致性hash的方式,实现分库插入和查询
     */
    @Service
    public class ConsistentHashService implements IConsistentHashService {
        @Resource
        private CustomerMapper customerMapper;
        /**
         * 用于记录每次操作的客户信息,以方便查询和插入操作
         */
        private List<Customer> customerList = new ArrayList<Customer>();
        private  int countNumber = 10;      //生成10个客户
    
        /**
         * 批量生成一写用户,保存到List集合,用于插入数据库 和 查询数据库
         * @return
         */
        public void initCustomerList(String name) {
            for (int i = 0; i < countNumber; i++) {
                Customer customer = new Customer();
                String idcardNo = getIdno();    //生成身份证号
                customer.setId(idcardNo);
                customer.setIdcardNo(idcardNo);
                customer.setName(name + "-" + (countNumber - i));
                customerList.add(customer);
            }
        }
    
        /**
         * 从客户集合中,随机抽取10个客户,用来查询数据库
         */
        public List<Customer> getTenCustomer(){
            List<Customer> customers = new ArrayList<Customer>();
            //设置要随机访问的客户总数
            int tmpCoustomerNum = customerList.size() < countNumber ? customerList.size() : countNumber;
            for (int i = 0; i < tmpCoustomerNum; i++) {
                int randmoIndex = new Random().nextInt(customerList.size());
                Customer customer = customerList.get(randmoIndex);
                customers.add(customer);
            }
            return customers;
        }
    
        /**
         * 使用随机数,生成身份证号ID
         * @return
         */
        public String getIdno() {
            StringBuilder sb = new StringBuilder("");
            for (int i = 0; i < 18; i++) {
                int random = new Random().nextInt(10);
                if (random == 0) {
                    random = 4;
                }
                sb.append(random);
            }
            return sb.toString();
        }
    
        /**
         * 向主库中,增加客户信息,插入十条数据,默认姓名为"简小六"
         * @return
         */
        @AnnotationDBSourceKey("master")
        public String addConstomer() {
            String message = "<p>插入结束</p>";
            message += "<p>插入信息如下:客户姓名 , 身份证号 , 原始数据源 , 最终数据源</p>";
            //向属性customerlist中,添加客户信息
            String name = "简小七";
            initCustomerList(name);
            int customerListSize = customerList.size();
            //向数据库中插入数据
            Iterator<Customer> iterator = customerList.iterator();
            while (iterator.hasNext()){
                Customer customer = iterator.next();
                DynamicDataSourceHolder.setConsistentHashKey(customer.getIdcardNo());   //设置用于一致性hash计算的key,以此来动态修改操作的数据源
                customerMapper.insertSelective(customer);       //执行数据库插入操作
                message += "<p>"+customer.getName()+" , "+customer.getIdcardNo()+" , "+DynamicDataSourceHolder.getDataSourceKey()+" , "+DynamicDataSourceHolder.getDataSourceFinalKey();
            }
            return message;
        }
    
        /**
         * 从主库库中查询客户信息
         */
        @AnnotationDBSourceKey("master")
        public String queryCustomerFromMater() {
            return queryData();
        }
    
        /**
         * 从slave库中查询客户信息
         */
        @AnnotationDBSourceKey("slave")
        public String queryCustomer() {
           return queryData();
        }
    /** * 将查询数据的业务抽离成公共方法 */ public String queryData(){ String message = "<p>查询结束:</p>"; message += "<p>查询信息如下:客户姓名 , 身份证号 , 原始数据源 , 最终数据源</p>"; //从数据库中查询数据 Iterator<Customer> iterator = getTenCustomer().iterator(); while (iterator.hasNext()){ Customer customer = iterator.next(); String idcardNo = customer.getIdcardNo(); CustomerExample example = new CustomerExample(); example.createCriteria().andIdcardNoEqualTo(idcardNo); DynamicDataSourceHolder.setConsistentHashKey(idcardNo); //设置用于一致性hash计算的key,以此来动态修改操作的数据源 customerMapper.selectByExample(example); //执行数据库插入操作 message += "<p>"+customer.getName()+" , "+customer.getIdcardNo()+" , "+DynamicDataSourceHolder.getDataSourceKey()+" , "+DynamicDataSourceHolder.getDataSourceFinalKey(); } return message; } }

    步骤六:编写Controller和客户端代码

    package com.newbie.controller;
    
    import com.newbie.domain.Customer;
    import com.newbie.service.IConsistentHashService;
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.web.bind.annotation.RequestMapping;
    import javax.annotation.Resource;
    
    @Controller
    public class ConsistentHashController {
        @Resource
        IConsistentHashService consistentHashService;
        /**
         * 插入十条数据,默认姓名为"简小六"
         */
        @RequestMapping("/addCustomer")
        public String addCustomer(Model model) {
            String message = consistentHashService.addConstomer();
            model.addAttribute("message", message);
            return "showQuery";
        }
        /**
         * 从主库库中查询客户信息
         */
        @RequestMapping("queryCustomerFromMater")
        public String queryCustomerFromMater(Model model){
            String message = consistentHashService.queryCustomerFromMater();
            model.addAttribute("message", message);
            return "showQuery";
        }
        /**
         * 从slave库中查询客户数据
         */
        @RequestMapping("queryCustomer")
        public String queryCustomer(Model model){
            String message = consistentHashService.queryCustomer();
            model.addAttribute("message", message);
            return "showQuery";
        }
    }
    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
        <title>测试:分库分表逻辑</title>
    </head>
    <body>
    <h2>练习二:一致性hash算法分库方案</h2>
    <h3> </h3>
    <a href="addCustomer"> 插入十条数据,姓名为"简小六" </a><br/><br/>
    <a href="queryCustomerFromMater">从master库中查询客户数据</a><br/><br/>
    <a href="queryCustomer">从slave库中查询客户数据</a>
    <br/><hr/><hr><hr/><br/>
    </body>
    </html>

    步骤七:运行代码,测试结果

    (1)插入五条数据,姓名为“简小六”,结果如下图所示:

     

    (2)从master库中随机查询五条数据,结果如下图所示:

    (3)从slave库中随机查询五条数据,结果如下图所示:

  • 相关阅读:
    bzoj 3122 [Sdoi2013]随机数生成器(逆元,BSGS)
    归并排序
    MS-coco数据集下载及使用(转)
    转-深度学习视觉领域常用数据集汇总
    opencv-Mat数据类型及位数总结
    opencv-图像类型、深度、通道
    opencv-VS2010配置opencv2.4.8
    opencv-访问Mat中每个像素的值
    VS2010文件包含
    转载: 8个月从CS菜鸟到拿到Google Offer的经历+内推
  • 原文地址:https://www.cnblogs.com/newbie27/p/10851923.html
Copyright © 2020-2023  润新知