美文网首页Mycat我爱编程
DBLE 2.17.08.1与MyCat 1.6.5的启动过程

DBLE 2.17.08.1与MyCat 1.6.5的启动过程

作者: john_zhong | 来源:发表于2017-10-27 17:15 被阅读95次

server.xml的构成和内存表现形式


server.xml中的内容,简要来说,主要有<system><user><firewall>这三部分。

<dble:server xmlns:dble="http://dble.cloud/">

    <system>
        <property name="prop_name">prop_value</property>
        ...
    </system>
    
    <user name="login_user_name">

        <property name="prop_name">prop_value</property>
        ...

        <privileges check="true_or_false">
            <schema name="this_user_rights_on_this_schema" dml="">
                <table name="this_user_rights_on_this_table" dml="" />
                ...
            </schema>
            ...
        </privileges>
    </user>
    ...
    
    <firewall>
        <whitehost>
            <host host="user_host" user="user_names" />
            ...
        </whitehost>
        <blacklist check="">
            <property name="prop_name">prop_value</property>
            ...
        </blacklist>
    </firewall>

</server>

相应的,这三部分被分别加载成com.actiontech.dble.config.model中的SystemConfigUserConfigFirewallConfig这三个类。

配置文件元素 对应类 配置的内容
<system> SystemConfig DBLE服务器配置,例如服务端口、管理端口等
<user> UserConfig 访问DBLE的用户配置,例如用户名、密码、允许访问哪些库or表等
<firewall> FirewallConfig 访问权限配置,例如基于用户的机器IP来允许/禁止访问

加载操作的代码入口


无论是server.xml的哪部分,都是在XMLConfigure类的构造函数实例化XMLServerLoader的过程中完成加载的。

com.actiontech.dble.config.loader.xml.XMLConfigure
com.actiontech.dble.config.loader.xml.XMLServerLoader

XMLServerLoader的构造函数的工作思路是,先分配空的内存对象(SystemConfigUserConfigFirewallConfig),然后再用这个类的load()函数来“填充”它们。

public XMLServerLoader() {

    this.system = new SystemConfig();
    this.users = new HashMap<>();
    this.firewall = new FirewallConfig();

    this.load();

}

load()的工作思路就也很单纯:先用ConfigUtil.getDocument()把server.xml读入到内存里,然后再用loadSystem()loadUsers()loadFirewall()来提取出<system><user><firewall>的具体内容,填充到对应的对象里。

private void load() {

    // ...

    dtd = ResourceUtil.getResourceAsStream("/server.dtd");
    xml = ResourceUtil.getResourceAsStream("/server.xml");
    Element root = ConfigUtil.getDocument(dtd, xml).getDocumentElement();
    
    loadSystem(root);
    loadUsers(root);
    loadFirewall(root);

    // ...

}

把server.xml文件载入内存——ConfigUtil.getDocument()


该方法利用JAR包内(源代码中project_loc/src/main/resources文件夹)的server.dtd,进行带DTD校验的XML读取。

最终,将server.xml文件整个读取成一个DOM(Document Object Model)对象,缓存在内存中。

SystemConfig的加载过程——loadSystem()


这个方法虽然使用了DOM操作、反射、Java Bean这些比较复杂的技术,但它的工作思路其实非常朴素:

  1. 提取<system>中的配置项<property>的清单

  2. 提取SystemConfig类中的可配置属性的清单

  3. 把可配置属性清单过一遍,从<property>清单中找到同名配置项,把值赋给SystemConfig

NodeList list = root.getElementsByTagName("system");

for (int i = 0, n = list.getLength(); i < n; i++) {

    Node node = list.item(i);

    if (node instanceof Element) {
        Map<String, Object> props = ConfigUtil.loadElements((Element) node);
        ParameterMapping.mapping(system, props);
    }
}

下面是相关的技术细节:

提取<system>中的配置项<property>的清单,需要两步:

  1. 通过org.w3c.dom.Element.getElementsByTagName(),定位到server.xml的DOM的<system>标签

  2. 调用com.actiontech.dble.config.util.ConfigUtil.loadElements(),将每一个<property>标签创建成一个键值对(name, content),放到同一个Map<String, Object>集合里

ParameterMapping.mapping()则会搞定剩余的工作:

  1. com.actiontech.dble.config.util.ParameterMapping.getDescriptors()找出ServerConfig中的可配置属性(符合Java Bean规范,即有对应的公开getter和setter方法),将它们放到一个数组中

  2. ParameterMapping.mapping()现在手上有了两套清单:一个是server.xml里<server>标签的所有<property>Map<String, Object> parameter,另一个是ServerConfig的所有属性PropertyDescriptor[] pds。于是,剩下来的工作就是把parameter中的值,根据pds,赋值给ServerConfig实例中的同名属性。

// 取一个SystemConfig属性
for (PropertyDescriptor pd : pds) {

    // 找同名的<property>标签
    Object obj = parameter.get(pd.getName());
    Object value = obj;

    // 确定当前SystemConfig属性的数据类型
    Class<?> cls = pd.getPropertyType();

    if if (obj instanceof String) {
        String string = (String) obj;
        if (!StringUtil.isEmpty(string)) {
                // 把<property>中的“${系统变量}”替换成系统变量
            string = ConfigUtil.filter(string);
        }
        // 把<property>标签的值转换成当前SystemConfig属性的数据类型
        if (isPrimitiveType(cls)) {
            value = convert(cls, string);
        }
    } else if (obj instanceof BeanConfig) {
        // <server>中有配置是Java Bean类型时的处理方法,介绍从略
    } else if (obj instanceof BeanConfig[] ) {
        // <server>中有配置是Java Bean类型时的处理方法,介绍从略
    }

    // 如果上面的步骤没出错
    if (cls != null && value != null) {
        // 在外面实例化的SystemConfig中,
        // 调用这个SystemConfig属性的setter,把<property>的值赋进去
        Method method = pd.getWriteMethod();
        if (method != null) {
            method.invoke(object, value);
        }
    }
}

UserConfig的加载过程——loadUsers()


这个方法的工作思路大致如下:

  1. 提取<user>中的name属性和配置项<property>的清单

  2. 直接把UserConfig的password、usingDecrypt、benchmark、readOnly、manager和schemas,在<property>清单中同名配置项的值赋上

  3. 回到<user>中,提取它里面的<privileges><schema><table>,然后加载后面两种标签的name属性和dml属性

下面是相关的技术细节:

提取<property>的清单的方法和loadSystem()一样,都是org.w3c.dom.Element.getElementsByTagName()com.actiontech.dble.config.util.ConfigUtil.loadElements()这两个函数干活。相对“新颖”的是,使用了org.w3c.dom.Element.getAttribute()来提取name属性。

NodeList list = root.getElementsByTagName("user");
// ...
String name = e.getAttribute("name");
Map<String, Object> props = ConfigUtil.loadElements(e);

UserConfig的name、password、usingDecrypt、benchmark、readOnly、manager和schemas属性赋值时,没有通过反射来定位这些属性,而使用了硬编码的方法:先用props.get("XXX")把XXX属性的值取出来,需要的话对这个值进行一下处理,最后调用UserConfig.setXXX()完成XXX属性的赋值。这样做的好处在于代码逻辑清晰简单,但是牺牲了增删这些属性时而无需改动这个方法的灵活性。

// ...
String password = (String) props.get("password");
String usingDecrypt = (String) props.get("usingDecrypt");
String passwordDecrypt = DecryptUtil.decrypt(usingDecrypt, name, password);

user.setName(name);
user.setPassword(passwordDecrypt);
user.setEncryptPassword(password);

String benchmark = (String) props.get("benchmark");
if (null != benchmark) {
    user.setBenchmark(Integer.parseInt(benchmark));
}

String readOnly = (String) props.get("readOnly");
if (null != readOnly) {
    user.setReadOnly(Boolean.parseBoolean(readOnly));
}

String manager = (String) props.get("manager");
if (null != manager) {
    user.setManager(Boolean.parseBoolean(manager));
}
String schemas = (String) props.get("schemas");
if (user.isManager() && schemas != null) {
    throw new ConfigException("manager user can't set any schema!");
} else if (!user.isManager()) {
    if (schemas != null) {
        if (system.isLowerCaseTableNames()) {
            schemas = schemas.toLowerCase();
        }
        String[] strArray = SplitUtil.split(schemas, ',', true);
        user.setSchemas(new HashSet<>(Arrays.asList(strArray)));
    }
    // ...
}

com.actiontech.dble.config.loader.xml.XMLServerLoader.loadPrivileges()则承包了加载<privileges>和它里面的<schema><table>的工作,工作流程如下:

  1. 读取<privileges>check属性

  2. 为每一个<schema>创建schemaPrivilege实例,获取<schema>namedml两个属性来给schemaPrivilege赋值

  3. 为同一个<schema>里的每个<table>创建tablePrivilege实例,获取<table>namedml两个属性来给tablePrivilege赋值

private void loadPrivileges(UserConfig userConfig, Element node) {

    // 实例化<privileges>对应的UserPrivilegesConfig类
    UserPrivilegesConfig privilegesConfig = new UserPrivilegesConfig();

    // 将<user>下的所有<privileges>标签找出来
    NodeList privilegesNodes = node.getElementsByTagName("privileges");

    // 取其中一个<privileges>标签
    int privilegesNodesLength = privilegesNodes.getLength();
    for (int i = 0; i < privilegesNodesLength; i++) {
        Element privilegesNode = (Element) privilegesNodes.item(i);

        // 读取check属性,并给UserPrivilegesConfig.check赋值
        // tips: 虽然允许有多个<privileges>,但由于UserPrivilegesConfig
        //   只实例过一个对象,所以UserPrivilegesConfig.check等于最后一个
        //   <privileges>的check
        String check = privilegesNode.getAttribute("check");
        if (null != check) {
            privilegesConfig.setCheck(Boolean.valueOf(check));
        }

        // 将<privileges>下的所有<schema>标签找出来
        NodeList schemaNodes = privilegesNode.getElementsByTagName("schema");

        // 取其中一个<schema>标签
        int schemaNodeLength = schemaNodes.getLength();
        for (int j = 0; j < schemaNodeLength; j++) {
            Element schemaNode = (Element) schemaNodes.item(j);

            // 读取name属性
            // tips:<system>中设置的isLowerCaseTableNames会导致这里对name取小写
            String name1 = schemaNode.getAttribute("name");
            if (system.isLowerCaseTableNames()) {
                name1 = name1.toLowerCase();
            }

            // 读取dml属性
            String dml1 = schemaNode.getAttribute("dml");
            int[] dml1Array = new int[dml1.length()];
            for (int offset1 = 0; offset1 < dml1.length(); offset1++) {
                dml1Array[offset1] = Character.getNumericValue(dml1.charAt(offset1));
            }

            // 实例化<schema>对应的SchemaPrivilege类,并把dml赋值
            UserPrivilegesConfig.SchemaPrivilege schemaPrivilege = new UserPrivilegesConfig.SchemaPrivilege();
            schemaPrivilege.setDml(dml1Array);


            // 取其中一个<table>标签
            NodeList tableNodes = schemaNode.getElementsByTagName("table");
            int tableNodeLength = tableNodes.getLength();
            for (int z = 0; z < tableNodeLength; z++) {

                // 实例化<table>对应的TablePrivilege类
                UserPrivilegesConfig.TablePrivilege tablePrivilege = new UserPrivilegesConfig.TablePrivilege();

                Element tableNode = (Element) tableNodes.item(z);

                // 读取name属性
                String name2 = tableNode.getAttribute("name");
                if (system.isLowerCaseTableNames()) {
                    name2 = name2.toLowerCase();
                }

                // 读取dml属性
                String dml2 = tableNode.getAttribute("dml");
                int[] dml2Array = new int[dml2.length()];
                for (int offset2 = 0; offset2 < dml2.length(); offset2++) {
                    dml2Array[offset2] = Character.getNumericValue(dml2.charAt(offset2));
                }

                // 对dml赋值
                tablePrivilege.setDml(dml2Array);

                // 把TablePrivilege追加到所属的SchemaPrivilege内
                schemaPrivilege.addTablePrivilege(name2, tablePrivilege);
            }

            // 把SchemaPrivilege追加到所属的UserPrivilegesConfig内
            privilegesConfig.addSchemaPrivilege(name1, schemaPrivilege);
        }
    }

    // 把UserPrivilegesConfig赋值给UserConfig
    userConfig.setPrivilegesConfig(privilegesConfig);
}

FirewallConfig的加载过程——loadFirewall()


只要了解SystemConfig的加载过程,尤其是反射部分(见前文),这部分的工作逻辑非常简单:

  1. 先读取白名单——由于只有<whitehost>含有<host>标签,找到每一个<host>标签,提取其中的hostuser属性,形成{host, { user1, user2, ... , usern }}的项目,然后加入到白名单里

  2. 再读取黑名单——黑名单功能本质上依赖com.alibaba.druid来实现,<blacklist>标签对应的类正是com.alibaba.druid.wall.WallConfig,所以和上面SystemConfig加载的时候类似,使用了反射的方法,把<property>中,WallConfig中的同名值赋值上去,来完成WallConfig的初始化,最后再以此初始化druid的一个特别的语法解析器工厂

以下是技术细节:

private void loadFirewall(Element root) throws IllegalAccessException, InvocationTargetException {

    /*
     * 先加载白名单<whitehost>
     */
    
    // 将<whitehost>下的所有<host>标签找出来
    NodeList list = root.getElementsByTagName("host");
    Map<String, List<UserConfig>> whitehost = new HashMap<>();
    
    // 对每一个<host>标签
    for (int i = 0, n = list.getLength(); i < n; i++) {
        Node node = list.item(i);
        if (node instanceof Element) {
            Element e = (Element) node;
    
            // 取host和user属性
            String host = e.getAttribute("host").trim();
            String userStr = e.getAttribute("user").trim();
    
            // host属性(IP地址)不允许和其他<host>的重复
            if (this.firewall.existsHost(host)) {
                throw new ConfigException("host duplicated : " + host);
            }
    
            // 把”user1,user2,...usern"形式的user属性转变为数组
            String[] arrayUsers = userStr.split(",");
            List<UserConfig> userConfigs = new ArrayList<>();
    
            // 对user属性中的每一个user,
            for (String user : arrayUsers) {
    
                // 这个user一定要在之前加载的用户权限(<user>标签)中配置过
                UserConfig uc = this.users.get(user);
                if (null == uc) {
                    throw new ConfigException("[user: " + user + "] doesn't exist in [host: " + host + "]");
                }
    
                // 这个user不是管理用户的话,它必须能访问起码一个库
                if (!uc.isManager() && (uc.getSchemas() == null || uc.getSchemas().size() == 0)) {
                    throw new ConfigException("[host: " + host + "] contains one root privileges user: " + user);
                }
    
                // 这个user符合上述条件的话,则将它在用户权限中的实体索引到这里来
                userConfigs.add(uc);
            }
    
            // 将{host, { user1, user2, ... , usern }}加入到白名单中
            whitehost.put(host, userConfigs);
        }
    }

    // 完成白名单<whitehost>的加载
    firewall.setWhitehost(whitehost);

    /*
     * 然后加载黑名单<blacklist>
     */

    // 初始化com.alibaba.druid.wall.WallConfig
    WallConfig wallConfig = new WallConfig();

    // 将<firewall>的所有<blacklist>标签找出来
    NodeList blacklist = root.getElementsByTagName("blacklist");

    // 对于每一个<blacklist>标签
    for (int i = 0, n = blacklist.getLength(); i < n; i++) {
        Node node = blacklist.item(i);
        if (node instanceof Element) {
            Element e = (Element) node;

            // 加载check属性
            String check = e.getAttribute("check");
            if (null != check) {
                firewall.setBlackListCheck(Boolean.parseBoolean(check));
            }

            // 利用用反射的方法,把<property name="xxx">加载给WallConfig.xxx,从而完成WallConfig的赋值
            // loadElements()和mapping()细节同本文的loadSystem()部分,不再冗述
            Map<String, Object> props = ConfigUtil.loadElements((Element) node);
            ParameterMapping.mapping(wallConfig, props);
        }
    }

    // 完成黑名单<blacklist>的加载
    firewall.setWallConfig(wallConfig);

    // 以加载后的<blacklist>信息去初始化`com.alibaba.druid.wall.spi.MySqlWallProvider`(一个语法解析器工厂)
    firewall.init();

}

相关文章

网友评论

    本文标题: DBLE 2.17.08.1与MyCat 1.6.5的启动过程

    本文链接:https://www.haomeiwen.com/subject/nawrpxtx.html