Quantcast
Channel: IT瘾博客推荐
Viewing all 532 articles
Browse latest View live

MySQL双主(主主)架构方案 - ygqygq2 - 博客园

$
0
0

在企业中,数据库高可用一直是企业的重中之重,中小企业很多都是使用mysql主从方案,一主多从,读写分离等,但是单主存在单点故障,从库切换成主库需要作改动。因此,如果是双主或者多主,就会增加mysql入口,增加高可用。不过多主需要考虑自增长ID问题,这个需要特别设置配置文件,比如双主,可以使用奇偶,总之,主之间设置自增长ID相互不冲突就能完美解决自增长ID冲突问题。

主从同步复制原理

在开始之前,我们先来了解主从同步复制原理。

复制分成三步:

1. master将改变记录到二进制日志(binary log)中(这些记录叫做二进制日志事件,binary log events);

2. slave将master的binary log events拷贝到它的中继日志(relay log);

3. slave重做中继日志中的事件,将改变反映它自己的数据。

下图描述了这一过程:

Image

该过程的第一部分就是master记录二进制日志。在每个事务更新数据完成之前,master在二日志记录这些改变。MySQL将事务串行的写入二进制日志,即使事务中的语句都是交叉执行的。在事件写入二进制日志完成后,master通知存储引擎提交事务。

下一步就是slave将master的binary log拷贝到它自己的中继日志。首先,slave开始一个工作线程——I/O线程。I/O线程在master上打开一个普通的连接,然后开始binlog dump process。Binlog dump process从master的二进制日志中读取事件,如果已经跟上master,它会睡眠并等待master产生新的事件。I/O线程将这些事件写入中继日志。

SQL slave thread处理该过程的最后一步。SQL线程从中继日志读取事件,更新slave的数据,使其与master中的数据一致。只要该线程与I/O线程保持一致,中继日志通常会位于OS的缓存中,所以中继日志的开销很小。

此外,在master中也有一个工作线程:和其它MySQL的连接一样,slave在master中打开一个连接也会使得master开始一个线程。

MySQL5.6以前的版本复制过程有一个很重要的限制——复制在slave上是串行化的,也就是说master上的并行更新操作不能在slave上并行操作。 MySQL5.6版本参数slave-parallel-workers=1 表示启用多线程功能。

MySQL5.6开始,增加了一个新特性,是加入了全局事务 ID (GTID) 来强化数据库的主备一致性,故障恢复,以及容错能力。

官方文档: http://dev.mysql.com/doc/refman/5.6/en/replication-gtids.html

MySQL双主(主主)架构方案思路是:

1.两台mysql都可读写,互为主备,默认只使用一台(masterA)负责数据的写入,另一台(masterB)备用;

2.masterA是masterB的主库,masterB又是masterA的主库,它们互为主从;

3.两台主库之间做高可用,可以采用keepalived等方案(使用VIP对外提供服务);

4.所有提供服务的从服务器与masterB进行主从同步(双主多从);

5.建议采用高可用策略的时候,masterA或masterB均不因宕机恢复后而抢占VIP(非抢占模式);

这样做可以在一定程度上保证主库的高可用,在一台主库down掉之后,可以在极短的时间内切换到另一台主库上(尽可能减少主库宕机对业务造成的影响),减少了主从同步给线上主库带来的压力;

但是也有几个不足的地方:

1.masterB可能会一直处于空闲状态(可以用它当从库,负责部分查询);

2.主库后面提供服务的从库要等masterB先同步完了数据后才能去masterB上去同步数据,这样可能会造成一定程度的同步延时;

架构的简易图如下:

MySQL主主同步方案

主主环境(这里只介绍2台主的配置方案):

1.CentOS 6.8 64位 2台:masterA(192.168.10.11),masterB(192.168.10.12)

2.官方Mysql5.6版本

搭建过程:

1.安装MySQL服务(建议源码安装)

1.1 yum安装依赖包

yum-yinstallmakegccgcc-c++  ncurses-devel bison openssl-devel

1.2 添加MySQL所需要的用户和组

groupadd -g27mysql
adduser-u27-g mysql -s /sbin/nologin mysql

1.3 下载MySQL源码包

mkdir-p /data/packages/src
cd/data/packages/wgethttp://distfiles.macports.org/cmake/cmake-3.2.3.tar.gzwgethttp://dev.mysql.com/get/Downloads/MySQL-5.6/mysql-5.6.34.tar.gz

1.4 创建mysql数据目录

mkdir-p /usr/local/mysql/data

1.5 解压编译安装cmake、MySQL

cd /data/packages/srctar-zxvf ../cmake-3.2.3.tar.gz
cd cmake-3.2.3/./bootstrap
gmakemakeinstall
cd ../tarxf  mysql-5.6.34.tar.gz
cd mysql-5.6.34cmake .-DCMAKE_INSTALL_PREFIX=/usr/local/mysql -DSYSCONFDIR=/etc \-DWITH_SSL=bundled -DDEFAULT_CHARSET=utf8 -DDEFAULT_COLLATION=utf8_general_ci \-DWITH_INNOBASE_STORAGE_ENGINE=1-DWITH_MYISAM_STORAGE_ENGINE=1\-DMYSQL_TCP_PORT=3306-DMYSQL_UNIX_ADDR=/tmp/mysql.sock \-DMYSQL_DATADIR=/usr/local/mysql/datamake&&makeinstall

1.6 添加开机启动脚本

cpsupport-files/mysql.server /etc/rc.d/init.d/mysqld

1.7 添加masterA配置文件/etc/my.cnf

[client]
port=3306socket= /tmp/mysql.sock

[mysqld]
basedir= /usr/local/mysql
port=3306socket= /tmp/mysql.sock
datadir= /usr/local/mysql/data
pid-file= /usr/local/mysql/data/mysql.pid
log-error = /usr/local/mysql/data/mysql.err

server-id=1auto_increment_offset=1auto_increment_increment=2#奇数ID

log-bin = mysql-bin                                                     #打开二进制功能,MASTER主服务器必须打开此项
binlog-format=ROW
binlog-row-p_w_picpath=minimal
log-slave-updates=truegtid-mode=on
enforce-gtid-consistency=truemaster-info-repository=TABLE
relay-log-info-repository=TABLEsync-master-info=1slave-parallel-workers=0sync_binlog=0binlog-checksum=CRC32
master-verify-checksum=1slave-sql-verify-checksum=1binlog-rows-query-log_events=1#expire_logs_days=5max_binlog_size=1024M                                                   #binlog单文件最大值

replicate-ignore-db =mysql                                             #忽略不同步主从的数据库
replicate-ignore-db =information_schema
replicate-ignore-db =performance_schema
replicate-ignore-db =test
replicate-ignore-db =zabbix

max_connections=3000max_connect_errors=30skip-character-set-client-handshake                                     #忽略应用程序想要设置的其他字符集
init-connect='SET NAMES utf8'#连接时执行的SQL
character-set-server=utf8                                               #服务端默认字符集
wait_timeout=1800#请求的最大连接时间
interactive_timeout=1800#和上一参数同时修改才会生效
sql_mode=NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES                     #sql模式
max_allowed_packet=10M
bulk_insert_buffer_size=8M
query_cache_type=1query_cache_size=128M
query_cache_limit=4M
key_buffer_size=256M
read_buffer_size=16K

skip-name-resolve
slow_query_log=1long_query_time=6slow_query_log_file=slow-query.log
innodb_flush_log_at_trx_commit=2innodb_log_buffer_size=16M

[mysql]
no-auto-rehash

[myisamchk]
key_buffer_size=20M
sort_buffer_size=20M
read_buffer=2M
write_buffer=2M

[mysqlhotcopy]
interactive-timeout

[mysqldump]
quick
max_allowed_packet=16M

[mysqld_safe]

1.8 特别参数说明

log-slave-updates =true#将复制事件写入binlog,一台服务器既做主库又做从库此选项必须要开启
#masterA自增长ID
auto_increment_offset=1auto_increment_increment=2#奇数ID
#masterB自增加ID
auto_increment_offset=2auto_increment_increment=2#偶数ID

1.9 添加masterB配置文件/etc/my.cnf

[client]
port=3306socket= /tmp/mysql.sock

[mysqld]
basedir= /usr/local/mysql
port=3306socket= /tmp/mysql.sock
datadir= /usr/local/mysql/data
pid-file= /usr/local/mysql/data/mysql.pid
log-error = /usr/local/mysql/data/mysql.err

server-id=2auto_increment_offset=2auto_increment_increment=2#偶数ID

log-bin = mysql-bin                                                     #打开二进制功能,MASTER主服务器必须打开此项
binlog-format=ROW
binlog-row-p_w_picpath=minimal
log-slave-updates=truegtid-mode=on
enforce-gtid-consistency=truemaster-info-repository=TABLE
relay-log-info-repository=TABLEsync-master-info=1slave-parallel-workers=0sync_binlog=0binlog-checksum=CRC32
master-verify-checksum=1slave-sql-verify-checksum=1binlog-rows-query-log_events=1#expire_logs_days=5max_binlog_size=1024M                                                   #binlog单文件最大值

replicate-ignore-db =mysql                                             #忽略不同步主从的数据库
replicate-ignore-db =information_schema
replicate-ignore-db =performance_schema
replicate-ignore-db =test
replicate-ignore-db =zabbix

max_connections=3000max_connect_errors=30skip-character-set-client-handshake                                     #忽略应用程序想要设置的其他字符集
init-connect='SET NAMES utf8'#连接时执行的SQL
character-set-server=utf8                                               #服务端默认字符集
wait_timeout=1800#请求的最大连接时间
interactive_timeout=1800#和上一参数同时修改才会生效
sql_mode=NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES                     #sql模式
max_allowed_packet=10M
bulk_insert_buffer_size=8M
query_cache_type=1query_cache_size=128M
query_cache_limit=4M
key_buffer_size=256M
read_buffer_size=16K

skip-name-resolve
slow_query_log=1long_query_time=6slow_query_log_file=slow-query.log
innodb_flush_log_at_trx_commit=2innodb_log_buffer_size=16M

[mysql]
no-auto-rehash

[myisamchk]
key_buffer_size=20M
sort_buffer_size=20M
read_buffer=2M
write_buffer=2M

[mysqlhotcopy]
interactive-timeout

[mysqldump]
quick
max_allowed_packet=16M

[mysqld_safe]

1.10 初始化MySQL

cd /usr/local/mysql
scripts/mysql_install_db --user=mysql

1.11 为启动脚本赋予可执行权限并启动MySQL

chmod+x /etc/rc.d/init.d/mysqld/etc/init.d/mysqld start

2. 配置主从同步

2.1 添加主从同步账户

masterA上:

mysql>grantreplicationslaveon*.*to'repl'@'192.168.10.12'identifiedby'123456';
mysql>flushprivileges;

masterB上:

mysql>grantreplicationslaveon*.*to'repl'@'192.168.10.11'identifiedby'123456';
mysql>flushprivileges;

2.2 查看主库的状态

masterA上:

mysql>show master status;+------------------+----------+--------------+------------------+-------------------+|File|Position|Binlog_Do_DB|Binlog_Ignore_DB|Executed_Gtid_Set|+------------------+----------+--------------+------------------+-------------------+|mysql-bin.000003|120||||+------------------+----------+--------------+------------------+-------------------+1rowinset(0.00sec)

masterB上

mysql>show master status;+------------------+----------+--------------+------------------+-------------------+|File|Position|Binlog_Do_DB|Binlog_Ignore_DB|Executed_Gtid_Set|+------------------+----------+--------------+------------------+-------------------+|mysql-bin.000003|437||||+------------------+----------+--------------+------------------+-------------------+1rowinset(0.00sec)

2.3 配置同步信息:

masterA上:

mysql>change mastertomaster_host='192.168.10.12',master_port=3306,master_user='repl',master_password='123456',master_log_file='mysql-bin.000003',master_log_pos=437;

mysql>start slave;

mysql>show slave status\G;

 

显示有如下状态则正常:

Slave_IO_Running: Yes

Slave_SQL_Running: Yes

masterB上:

#本人是测试环境,可以保证没数据写入,否则需要的步骤是:先masterA锁表-->masterA备份数据-->masterA解锁表 -->masterB导入数据-->masterB设置主从-->查看主从

mysql>change mastertomaster_host='192.168.10.11',master_port=3306,master_user='repl',master_password='123456',master_log_file='mysql-bin.000003',master_log_pos=120;

start slave;

mysql>show slave status\G;

显示有如下状态则正常:

Slave_IO_Running: Yes

Slave_SQL_Running: Yes

3.测试主从同步

3.1 在masterA上创建一个数据库测试同步效果

mysql>show databases;+--------------------+|Database|+--------------------+|information_schema||mysql||performance_schema||test|+--------------------+4rowsinset(0.00sec)

mysql>createdatabasetest01;

Query OK,1row affected (0.00sec)

mysql>show databases;+--------------------+|Database|+--------------------+|information_schema||mysql||performance_schema||test||test01|+--------------------+5rowsinset(0.00sec)

mysql>quit

Bye[root@masterA data]#

3.2 到masterB查看是否已经同步创建数据库

mysql>show databases;+--------------------+|Database|+--------------------+|information_schema||mysql||performance_schema||test||test01|+--------------------+5rowsinset(0.00sec)

mysql>quit

Bye[root@masterB data]#

4. 开启MySQL5.6的GTID功能

masterA和masterB分别执行如下命令:

mysql>stop slave;

Query OK,0rows affected (0.00sec)

mysql>change mastertoMASTER_AUTO_POSITION=1;

Query OK,0rows affected (0.01sec)

mysql>start slave;

Query OK,0rows affected (0.00sec)

5. 遇到的问题

一种主从报错折腾了我半天:

Last_IO_Errno: 1236

Last_IO_Error: Got fatal error 1236 from master when reading data from binary log: 'Could not open log file'

后面修改主从同步相关 参数,确认原因是my.cnf增加了如下参数:

log-bin = mysql-bin

relay-log = mysql-bin

Image

从正常主主同步时的二进制日志文件显示,有2套二进制日志。因此推断上面2个参数导致不能产生2套二进制文件,故导致二进制文件错乱和丢失。


Centos 更改MySQL5.7数据库目录位置 - CSDN博客

$
0
0

Centos7.3 安装Mysql5.7并修改初始密码
基于 CentOS Mysql 安装与主从同步配置详解

Centos 通过 yum安装(RPM分发进行安装)MySQL的几个人默认目录如下:

目录目录内容
/usr/bin客户端程序和脚本
/usr/sbinmysqld服务器
/var/lib/mysql日志文件,数据库文件
/usr/share/mysql错误消息和字符集文件
/etc/my.cnf配置文件

假如要把目录移到/home/data下需要进行下面几步:

1、home目录下建立data目录

mkdir-p /home/data& cd /home/data/

2、把MySQL服务进程停掉

[root@localhostdata]# mysqladmin -u root -p shutdownEnterpassword:

3、把 /var/lib/mysql整个目录移到 /home/data

mv/var/lib/mysql /home/data/

或者

cp-R/var/lib/mysql /home/data/

这样就把MySQL的数据文件移动到了/home/data/mysql下

4、设置 /home/data/下mysql文件夹的属主和权限

chown-Rmysql:mysql /home/data/mysql
[root@localhostdata]# lsmysql[root@localhostdata]# lldrwxr-x--x. 5 root root 4096 10月 31 04:03 mysql[root@localhostdata]# chown -Rmysql:mysql /home/data/mysql[root@localhostdata]# lldrwxr-x--x. 5 mysql mysql 4096 10月 31 04:03 mysql

5、修改配置文件 /etc/my.cnf

为保证MySQL能够正常工作,需要指明 mysql.sock文件的产生位置。修改 socket=/var/lib/mysql/mysql.sock一行中等号右边的值为: /home/data/mysql/mysql.sock以及修改 datadir/home/data/mysql操作如下:

#datadir=/var/lib/mysqldatadir=/home/data/mysql#socket=/var/lib/mysql/mysql.socksocket=/home/data/mysql/mysql.sock

6、重新启动MySQL服务

service mysqldstart

往往坑总是一个接着一个。

启动异常

以上截图看不到任何问题,我们来查看一下日志

tail-n1000/var/log/mysqld.log-f

详细的日志信息

2017-10-31T08:48:06.533321Z0[Warning]Can'tcreate test file /home/data/mysql/localhost.lower-test2017-10-31T08:48:06.533401Z0[Note] /usr/sbin/mysqld (mysqld5.7.20) startingasprocess25325...2017-10-31T08:48:06.536585Z0[Warning]Can'tcreate test file /home/data/mysql/localhost.lower-test2017-10-31T08:48:06.536617Z0[Warning]Can'tcreate test file /home/data/mysql/localhost.lower-test

通过关键词搜索找到此篇
http://jejoker.iteye.com/blog/1882028

设置一个SELinux即可

setenforce 0

Apache Phoenix的Join操作和优化 - CSDN博客

$
0
0

估计Phoenix中支持Joins,对很多使用HBase的朋友来说,还是比较好的。下面我们就来演示一下。

首先看一下几张表的数据:

Orders表:

OrderID

CustomerID

ItemID

Quantity

Date

1630781

C004

I001

650

09-01-2013

1630782

C003

I006

2500

09-02-2013

1630783

C002

I002

340

09-03-2013

1630784

C004

I006

1260

09-04-2013

1630785

C005

I003

1500

09-05-2013

数据保存到Orders.csv,内容格式为:

1630781,C004,I001,650,09-01-2013

1630782,C003,I006,2500,09-02-2013

1630783,C002,I002,340,09-03-2013

1630784,C004,I006,1260,09-04-2013

1630785,C005,I003,1500,09-05-2013

 

Customers表:

CustomerID

CustomerName

Country

C001

Telefunken

Germany

C002

Logica

Belgium

C003

Salora Oy

Finland

C004

Alps Nordic AB

Sweden

C005

Deister Electronics

Germany

C006

Thales Nederland

Netherlands

数据保存到Customers.csv,内容格式为:

C001,Telefunken,Germany

C002,Logica,Belgium

C003,Salora Oy,Finland

C004,Alps Nordic AB,Sweden

C005,Deister Electronics,Germany

C006,Thales Nederland,Netherlands

 

Items表:

ItemID

ItemName

Price

I001

BX016

15.96

I002

MU947

20.35

I003

MU3508

9.6

I004

XC7732

55.24

I005

XT0019

12.65

I006

XT2217

12.35

数据保存到Items.csv,内容格式为:

I001,BX016,15.96

I002,MU947,20.35

I003,MU3508,9.6

I004,XC7732,55.24

I005,XT0019,12.65

I006,XT2217,12.35

 

创建表的语句为:

Orders.sql文件内容为:

create table IF NOT EXISTS Orders (

   OrderID Integer,

   CustomerID Char(4),

   ItemID Char(4),

   Quantity Integer,

   Date Char(10)

   constraint pk Primary key(OrderID)

);

 

Customers.sql文件内容为:

create table IF NOT EXISTS Customers (

   CustomerID Char(4),

   CustomerName Varchar(50),

   Country Varchar(50)

   constraint pk Primary key(CustomerID)

);

 

Items.sql文件内容为:

create table IF NOT EXISTS Items (

   ItemID Char(4),

   ItemName Char(10),

   Price Decimal(25,2)

   constraint pk Primary key(ItemID)

);

 

我们将上面的数据导入到创建的表中:

bin/psql.py gpmaster:2181:/hbaseforkylin Orders.sql Orders.csv

bin/psql.py gpmaster:2181:/hbaseforkylin Customers.sql Customers.csv

bin/psql.py gpmaster:2181:/hbaseforkylin Items.sql Items.sql

 

查看数据是否已经导入:

可以看到数据都全部导入了。

 

下面来执行一些关于Join的相关操作:

SELECT O.OrderID,C.CustomerName,C.Country, O.Date

FROM Orders AS O

INNER JOIN Customers AS C

ON O.CustomerID = C.CustomerID;

 

执行过程以及结果如下:

 

基于索引的Join操作

当执行join查询操作时,二级索引能够自动地被利用。如果我们在Orders和Items上分别创建索引,如下:

CREATE INDEX iOrders ON Orders (ItemID) INCLUDE (CustomerID, Quantity);

CREATE INDEX i2Orders ON Orders(CustomerID) INCLUDE (ItemID, Quantity);

CREATE INDEX iItems ON Items (ItemName) INCLUDE (Price);

如果你创建可变的二级索引,出现如下的错误:

Error: ERROR 1029 (42Y88): Mutable secondary indexes must have the hbase.regionserver.wal.codec property set to org.apache.hadoop.hbase.regionserver.wal.IndexedWALEditCodec in thehbase-sites.xml of every region server. tableName=IORDERS(state=42Y88,code=1029)

那么需要在RegionServer每个节点的hbase-site.xml中配置参数:

<property>

   <name>hbase.regionserver.wal.codec</name>

   <value>org.apache.hadoop.hbase.regionserver.wal.IndexedWALEditCodec</value>

</property>

然后重启HBase集群。

 

执行查询:

SELECT ItemName,sum(Price * Quantity) AS OrderValue

FROM Items

JOIN Orders

ON Items.ItemID = Orders.ItemID

WHERE Orders.CustomerID > 'C002'

GROUP BY ItemName;

 

查询结果为:

通过explain查询执行计划:

 在这个案例中,可以看到索引iItems和索引i2Orders都有使用。

GroupedJoins and Derived Tables

Phoenix也支持复杂的Join语法,比如 grouped joins(或子查询),以及derived-tables(派生表)的join操作。

对于 grouped joins来说,如下:

SELECT O.OrderID,C.CustomerID, I.ItemID

FROM Customers AS C

INNER JOIN

    (Items AS I

     INNER JOIN OrdersAS O

     ON I.ItemID = O.ItemID)

ON C.CustomerID = O.CustomerID;

 

通过使用一个子查询(derived table)替换 sub join,得到相等的查询:

SELECT S.OrderID,C.CustomerID, S.ItemID

FROM Customers AS C

INNER JOIN

    ( select O.CustomerID,

                  O.OrderID,

                   I.ItemID

       from Items AS I

     INNER JOIN Orders AS O

     ON I.ItemID = O.ItemID) S

ON C.CustomerID = S.CustomerID;

 

上面两个查询结果都为:

 

HashJoinvs. Sort-Merge Join

基本的Hash Join通常比其他类型的join算法更好,但是它也有一些限制,其中最典型的一个特性是关系中的一张表要小到能够加载到内存中。Phoenix同时支持Hash Join和Sort-Merge Join去实现快速的join操作以及两张大表之间的join操作。

Phoenix目前尽可能地使用Hash Join算法,因为通常更快。但是我们可以使用“USE_SORT_MERGE_JOIN”的hint在查询中使用Sort-Merge Join。对于这两种join的算法的选择将来会根据表的统计信息自动选择。

Foreign Key to Primary Key Join Optimization

通常情况下,一个join发生在一个child 表到一个parent 表,通过child表的外键映射到一个parent表的主键。因此代替对parent表的全表扫描,Phoenix将基于child表的外键值对parent表进行skip-scan或range-scan扫描。

 

下面我们举个例子,parent表为“Employee”,child表为“Patent”。

CREATE TABLE Employee (

   Region VARCHAR NOT NULL,

   LocalID VARCHAR NOT NULL,

   Name VARCHAR,

   StartDate DATE,

CONSTRAINT pk PRIMARY KEY (Region, LocalID));

 

CREATE TABLE Patent (

   PatentID VARCHAR NOT NULL,

   Region VARCHAR,

   LocalID VARCHAR,

   Title VARCHAR,

   Category VARCHAR,

   FileDate DATE,

CONSTRAINT pk PRIMARY KEY (PatentID));

 

 

SELECT E.Name, E.Region, P.PCount

FROM Employee AS E

JOIN

   (SELECT Region, LocalID, count(*) AS PCount

    FROM Patent

    WHERE FileDate >= to_date('2000-01-01')

    GROUP BY Region, LocalID) AS P

ON E.Region = P.Region AND E.LocalID =P.LocalID;

上面的查询语句通过Region和LocalID两个join的key对表“Employee”使用skip-scan扫描。下面是查询使用和不使用这个优化的执行时间(“Employee”大概 5000000 行,“Patent”大约 1000 行记录):

W/O(没有)Optimization

W/(有)Optimization

8.1s

0.4s

 

然而,当考虑到child外键的值完全在parent表的主键空间中,因此使用skip-scan只会慢不会快的。你可以总是通过指定“NO_CHILD_PARENT_OPTIMIZATION”的hint关闭这个优化。将来,通过表的统计信息智能地选择这两种模式。

 

Configuration

前面我们提到,使用Hash Join时,前提条件是将关联中的一张表加载到内存中,以便广播到所有的服务器,因此需要考虑RegionServer服务器的堆内存足够大以便容纳较小的表,我们也需要关注一下使用Hash Join的几个关键性的配置参数。

服务器端缓存用于存放哈希表,缓存的大小和生存时间由下面的几个参数控制。

1.   phoenix.query.maxServerCacheBytes

一个relation在被压缩和发送到RegionServer前的最大原始数据大小(bytes)。

如果尝试去序列化一个relation原始数据大小超过这个值的话,会导致MaxServerCacheSizeExceededException错误。

默认值为: 104857600

2.   phoenix.query.maxGlobalMemoryPercentage

所有threads使用的堆内存百分比(Runtime.getRuntime().maxMemory())。

默认值为: 15

3.   phoenix.coprocessor.maxServerCacheTimeToLiveMs

服务器端缓存的最大生存时间。

当在服务器端出现IO异常(“ Could not find hash cache for joinId”),就考虑调整这个参数了。

如果获取到“ Earlier hash cache(s) might have expired on servers”告警日志时,可以提升这个参数的值。

默认值为: 30000(30s)

 

尽管有时可以通过修改参数来解决上面提到的一些异常问题,但是强烈建议首先要考虑优化join的查询,大家可以学习下面的优化部分内容。

 

OptimizingYour Query

下面是默认join的顺序(没有表统计信息的存在),查询的一边作为“smaller”relation并且将被加载到服务器的内存中:

1.    lhs INNER JOIN  rhs

rhs将在服务器的内存中建立hash表

2.    lhs LEFT OUTER JOIN  rhs

rhs将在服务器的内存中建立hash表

3.    lhs RIGHT OUTER JOIN  rhs

lhs将在服务器的内存中建立hash表

对于多个join查询来说,join的顺序是比较复杂的,你可以使用explain来查询真正的执行计划。对于multiple-inner-join查询来说,Phoenix默认应用star-join优化,意味着join所有右手边的表的同时,leading的表(即左手边的表)将仅仅被扫描一次。当所有右手边的表的大小超过内存限制时,你可以通过使用“NO_STAR_JOIN”的hint来关闭这个优化。

下面我们来看一下之前的查询示例:

SELECT O.OrderID, C.CustomerName, I.ItemName, I.Price,O.Quantity

FROM Orders AS O

INNER JOIN Customers AS C

ON O.CustomerID = C.CustomerID

INNER JOIN Items AS I

ON O.ItemID = I.ItemID;

默认的Join顺序为(使用star-join优化):

1. SCAN Customers --> BUILD HASH[0]
   SCAN Items --> BUILD HASH[1]
2. SCAN Orders JOIN HASH[0], HASH[1] --> Final Resultset

 

另外,如果我们使用“NO_STAR_JOIN”的hint,如下:

SELECT /*+ NO_STAR_JOIN*/ O.OrderID, C.CustomerName, I.ItemName, I.Price, O.Quantity
FROM Orders AS O
INNER JOIN Customers AS C
ON O.CustomerID = C.CustomerID
INNER JOIN Items AS I
ON O.ItemID = I.ItemID;

这次的Join顺序为:

1. SCAN Customers --> BUILD HASH[0]
2. SCAN Orders JOIN HASH[0]; CLOSE HASH[0] --> BUILD HASH[1]
3. SCAN Items JOIN HASH[1] --> Final Resultset

 

这里需要说明的是,并不是表的整个数据集都计算到内存占用的,而是只有查询使用的列数据并且过滤后的记录才会在服务器端建立Hash表。

 

Phoenix二级索引(Secondary Indexing)的使用 - MOBIN - 博客园

$
0
0
摘要
HBase只提供了一个基于字典排序的主键索引,在查询中你只能通过行键查询或扫描全表来获取数据,使用Phoenix提供的二级索引,可以避免在查询数据时全表扫描,提高查过性能,提升查询效率
 
测试环境:
数据约370万
数据格式:(数据来自 搜狗实验室)
三节点集群(一主两从,hadoop和HBase属同一集群)
 
目录
  • Covered Indexes(覆盖索引)
  • Functional indexes(函数索引)
  • Global indexes(全局索引)
  • Local indexes(本地索引)
 
索引类型
Covered Indexes(覆盖索引)
覆盖索引:只需要通过索引就能返回所要查询的数据,所以索引的列必须包含所需查询的列(SELECT的列和WHRER的列)
 
不带索引的查询:
查询USERID= 9bb8b2af925864bb275b840c578df3c3的KEYWORD和URL
EXPLAIN(语句的执行逻辑及计划):
(由图看知先进行了全表扫描再通过过滤器来筛选出目标数据,显示这种查询方式效率是很低的)
 
查询时间:(平均在38s~41s)
 
 
带索引:
(创建基于USERID的覆盖索引并绑定KEYWORD列上的数据)
CREATE INDEX COVERINDEX ON CSVTANLES(USERID) INCLUDE(KEYWORD)
当你要通过UERID来查询KEYWORD时就直接可以从索引上取回数据而无需先得到索引再去数据表中查询数据
查询语句:
SECECT KEYWORD FROM CSVTABLES WHERE USERID='9bb8b2af925864bb275b840c578df3c3'
 
EXPLAIN:
(使用了COVERINDEX索引使用SCAN在索引区间内查询)
 
查询用时(平均在49ms~70ms):
注意:SELECT所带的字段必须包含在覆盖索引内
 
Functional indexes(函数索引)
从Phoeinx4.3以上就支持函数索引,其索引不局限于列,可以合适任意的表达式来创建索引,当在查询时用到了这些表达式时就直接返回表达式结果
例2:使用UPPER函数创建函数索引使查询出的USERID和URL里字母都是大写的
创建函数索引
CREATE INDEX UPPERINDEX ON CSVTABLES (UPPER(USERID || '  ' || URL))
查询:
 
Global indexes(全局索引)
全局索引适用于多读少写的场景,在写操作上会给性能带来极大的开销,因为所有的更新和写操作(DELETE,UPSERT VALUES和UPSERT SELECT)都会引起索引的更新,在读数据时,Phoenix将通过索引表来达到快速查询的目的。
在用使用全局索引之前需要在每个RegionServer上的hbase-site.xml添加如下属性:
<property><name>hbase.regionserver.wal.codec</name><value>org.apache.hadoop.hbase.regionserver.wal.IndexedWALEditCodec</value></property>
在USERID字段上创建索引
CREATE INDEX USERIDINDEX ON CSVTABLES(USERID);
 
以下查询会用到索引
SELECT USERID FROM CSVTABLES WHERE USERID='9bb8b2af925864bb275b840c578df3c3';
 
SELECT USERID,ROWKEY CSVTABLES WHERE USERID='9bb8b2af925864bb275b840c578df3c3';
 
以下查询不会用到索引
查询语句1.
SELECT USERID,KEYWORD FROM CSVTABLES WHERE USERID='9bb8b2af925864bb275b840c578df3c3'
(虽然USERID是索引字段,但KEYWORD不是索引字段,所以不会使用到索引)
 
查询语句2.
SELECT KEYWORD FROM CSVTABLES WHERE USERID='9bb8b2af925864bb275b840c578df3c3'
(同理,KEYWORD不是索引字段)
 
使用以下三种方式,执行查询语句2时也将用到索引.
1.创建包含字段KEYWORD的覆盖索引
CREATE INDEX MYINDEX ON CSVTABLE(USERID) INCLUDE(KEYWORD);
 
2.强制使用索引
SELECT /*+ INDEX(CSVTABLES,MYINDEX) */ KEYWORD FROM CSVTABLES WHERE USERID='9bb8b2af925864bb275b840c578df3c3';
如果KEYWORD是索引字段,那么就会直接从索引表中查询
如果KEYWORD不是索引字段,那么将会进行全表扫描,所以当用户明确知道表中数据较少且符合检索条件时才适用,此时的性能才是最佳的。
 
3.使用本地索引
CREATE LOCAL INDEX MYINDEX ON CSVTABLES(KEYWORD);
 
Local indexes(本地索引)
本地索引适用于写多读少,空间有限的场景,和全局索引一样,Phoneix在查询时会自动选择是否使用本地索引,使用本地索引,为避免进行写操作所带来的网络开销,索引数据和表数据都存放在相同的服务器中,当查询的字段不完全是索引字段时本地索引也会被使用,与全局索引不同的是,所有的本地索引都单独存储在同一张共享表中,由于无法预先确定region的位置,所以在读取数据时会检查每个region上的数据因而带来一定性能开销。
在使用本地索引需要在Master的hbase-site.xml添加以下属性
复制代码
<property><name>hbase.master.loadbalancer.class</name><value>org.apache.phoenix.hbase.index.balancer.IndexLoadBalancer</value></property><property><name>hbase.coprocessor.master.classes</name><value>org.apache.phoenix.hbase.index.master.IndexMasterObserver</value></property>
复制代码
Phoeinx4.3以上为支持在数据region合并时本地索引region也能进行合并需要在每个region servers中添加以下属性
<property><name>hbase.coprocessor.regionserver.classes</name><value>org.apache.hadoop.hbase.regionserver.LocalIndexMerger</value></property>
创建本地索引
CREATE LOCAL INDEX MYINDEX ON CSVTABLES(USERID);
查询
CREATE LOCAL INDEX MYINDEX ON CSVTABLES(USERID);
整个查询只花了0.19s
 
 
删除索引
CREATE LOCAL INDEX MYINDEX ON CSVTABLES(KEYWORD);
如果表中的一个索引列被删除,则索引也将被自动删除,如果删除的是
覆盖索引上的列,则此列将从覆盖索引中被自动删除。
 
索引的优化
以下属性都必须在各节点上的hbase-site.xml中设置为true才能起效,
1.index.builder.threads.max:(默认值:10)
    根据主表的更新来确定更新索引表的线程数
 
2.index.builder.threads.keepalivetime:(默认值:60)
    builder线程池中线程的存活时间
 
3.index.write.threads.max:(默认值:10)
    更新索引表时所能使用的线程数(即同时能更新多少张索引表),其数量最好与索引表的数量一致
 
4.index.write.threads.keepalivetime(默认值:60)
     更新索引表的线程所能存活的时间
  
5.hbase.htable.threads.max(默认值:2147483647)
     每张索引表所能使用的线程(即在一张索引表中同时可以有多少线程对其进行写入更新),增加此值可以提高更新索引的并发量
 
6.hbase.htable.threads.keepalivetime(默认值:60)
     索引表上更新索引的线程的存活时间
 
7.index.tablefactoy.cache.size(默认值:10)
     允许缓存的索引表的数量
     增加此值,可以在更新索引表时不用每次都去重复的创建htable,由于是缓存在内存中,所以其值越大,其需要的内存越多

Oracle 移动数据文件的操作方法 - CSDN博客

$
0
0

将表空间和数据文件从一个位置移动到另一个位置的操作方法

一. OFFLINE

OFFLINE 分为ALTER DATABASE 与 ALTER TABLESPACE OFFLINE,

他们的区别参看blog: http://www.cndba.cn/Dave/article/1226

按数据文件来:

1.先将相应的数据文件 offline  

ALTER DATABASE DATAFILE 'D:/ORACLE/ORADATA/DBA/TEST01.DBF' OFFLINE;
2.把数据文件 copy 到新位置
3. alter database rename file 'D:/ORACLE/ORADATA/DBA/TEST01.DBF' to 'D:/TEST01.DBF';

4. 介质恢复(offline 数据文件必须要介质恢复)

recover datafile 'D:/TEST01.DBF'
5. 将相应的数据文件 online 

SQL>ALTER DATABASE DATAFILE 'D:/TEST01.DBF' ONLINE;

按表空间来:

1.先将相应的表空间 offline  

SQL>alter tablespace test offline;  

2.把数据文件 copy 到新位置
3. alter tablespace TEST  rename datafile 'D:/TEST01.DBF' to 'D:/ORACLE/ORADATA/DBA/TEST01.DBF'

4. 将表空间 online 

SQL>alter tablespace test online;   

 

二. Shutdown 数据库

1. 关闭数据库 

C:>set ORACLE_SID=DBA

C:>sqlplus /nolog

SQL*Plus: Release 10.2.0.1.0 - Production on 星期日 11月 29 11:14:02 2009

Copyright (c) 1982, 2005, Oracle.  All rights reserved.

SQL> conn sys/admin as sysdba

已连接。

SQL> shutdown immediate

数据库已经关闭。

已经卸载数据库。

ORACLE 例程已经关闭。

SQL>


2. 把数据文件 copy 到新位置

3. rename datafile

SQL> startup mount

ORACLE 例程已经启动。

Total System Global Area  289406976 bytes

Fixed Size                  1248576 bytes

Variable Size              71303872 bytes

Database Buffers          209715200 bytes

Redo Buffers                7139328 bytes

数据库装载完毕。

SQL> alter database rename file 'D:/ORACLE/ORADATA/DBA/TEST01.DBF' to 'D:/TEST01

.DBF';

数据库已更改。

SQL> alter database open;

数据库已更改。

SQL> select file#,name,status from v$datafile;

 FILE# NAME                                STATUS

-------- -------------------------------------------------------  ---------------

   1  D:/ORACLE/ORADATA/DBA/SYSTEM01  SYSTEM

   2  D:/ORACLE/ORADATA/DBA/UNDOTBS0  ONLINE

   3  D:/ORACLE/ORADATA/DBA/SYSAUX01  ONLINE

   4  D:/ORACLE/ORADATA/DBA/USERS01.   ONLINE

   5  D:/TEST01.DBF                              ONLINE

搭建 springboot 2.0 mybatis 读写分离 配置区分不同环境 - 黄青石 - 博客园

$
0
0

最近公司打算使用springboot2.0, springboot支持HTTP/2,所以提前先搭建一下环境。网上很多都在springboot1.5实现的,所以还是有些差异的。接下来咱们一块看一下。

  文章的主要思路:

  1.工程的结构。

  2.重要代码说明。

  3.运行结果。

  4.总结。

  

  1) 我用的开发工具是Idea。工程的结构如下:

  

工程结构的每个部分的说明: 

  config:  用于配置动态数据源的配置,同时使用切面实现数据库读写分离。同时使用ThreadLocal去维护当前线程该用读锁还是写锁。

  controller: 用于拦截请求,我在示例里边使用的是rest的请求拦截。

  entity: 与数据库对应的实体类。

  mapper: 与数据库数据库方法的对应

  service: 用于实现控制层到数据层的一个衔接,提供服务。

  还有一个启动入口类。  

  resources下面的mapper文件夹是数据库映射的xml文件。

  app-config.xml用于添加外部的bean等提前放置的xml文件。需要使用该类时需要加上注解:@ImportResource("classpath:app-config.xml")。

  application.yml是应用的配置文件。

  logback.xml用于日志输出配置。

  mybatis-config.xml用于mybatis的一些配置内容。

  Testinit.sql用于创建表使用的。

   POM文件的包引入:

复制代码
<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.hqs.demo</groupId><artifactId>springboot-mybatis-rw</artifactId><version>0.0.1-SNAPSHOT</version><packaging>jar</packaging><name>springboot2.0.1.RELEASE-mybatis-rw</name><description>Demo project for Spring Boot</description><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.0.1.RELEASE</version><relativePath/> <!-- lookup parent from repository --></parent><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding><java.version>1.8</java.version><logback.version>1.1.7</logback.version></properties><dependencies><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.1.6</version></dependency><dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId><version>3.4</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>1.3.2</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><!--热部署使用 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><optional>true</optional></dependency><dependency><groupId>com.jayway.jsonpath</groupId><artifactId>json-path</artifactId><scope>test</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-configuration-processor</artifactId><optional>true</optional></dependency><dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><version>${logback.version}</version></dependency><dependency><groupId>ch.qos.logback</groupId><artifactId>logback-core</artifactId><version>${logback.version}</version></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>
复制代码

  2)  这块内容比较复杂的点应该就是读写库的切换:

    通过在DataSourceConfig.java里边读取主库从库:

   

   使用Spring切面编程来拦截需要更改数据库的方法:

   

  根据配置的方法放到配置文件,可以将需要读或写的方法放到application.yml文件中:

read: get,select,count,list,query,find    
write: add,create,update,delete,remove,insert

  3)运行结果:

  读数据:

  

  数据库采用read库

  

  写数据:

  

  使用写库:

  

  4)总结:

   1. 通过搭建springboot简单了解到其原理,编写很容易,运行也方便。

    2. 在搭建的过程中也参考了很多资料,非常感谢,牛人还是很多的。

    3. 本来打算用最新的druid-spring-boot-starter 1.1.9,但是没找到太多的资料,所以没有用。

    4. 放上git地址供参考  https://github.com/stonehqs/springboot2.0.1.RELEASE-mybatis-rw

如果有不对的地方,还希望同学们给出意见和建议。

Spring Cloud 微服务的那点事 - CSDN博客

$
0
0

什么是微服务

微服务的概念源于2014年3月Martin Fowler所写的一篇文章“Microservices”。

微服务架构是一种架构模式,它提倡将单一应用程序划分成一组小的服务,服务之间互相协调、互相配合,为用户提供最终价值。每个服务运行在其独立的进程中,服务与服务间采用轻量级的通信机制互相沟通(通常是基于HTTP的RESTful API)。每个服务都围绕着具体业务进行构建,并且能够被独立地部署到生产环境、类生产环境等。另外,应尽量避免统一的、集中式的服务管理机制,对具体的一个服务而言,应根据业务上下文,选择合适的语言、工具对其进行构建。

微服务是一种架构风格,一个大型复杂软件应用由一个或多个微服务组成。系统中的各个微服务可被独立部署,各个微服务之间是松耦合的。每个微服务仅关注于完成一件任务并很好地完成该任务。在所有情况下,每个任务代表着一个小的业务能力。

微服务架构优势

复杂度可控:在将应用分解的同时,规避了原本复杂度无止境的积累。每一个微服务专注于单一功能,并通过定义良好的接口清晰表述服务边界。由于体积小、复杂度低,每个微服务可由一个小规模开发团队完全掌控,易于保持高可维护性和开发效率。

独立部署:由于微服务具备独立的运行进程,所以每个微服务也可以独立部署。当某个微服务发生变更时无需编译、部署整个应用。由微服务组成的应用相当于具备一系列可并行的发布流程,使得发布更加高效,同时降低对生产环境所造成的风险,最终缩短应用交付周期。

技术选型灵活:微服务架构下,技术选型是去中心化的。每个团队可以根据自身服务的需求和行业发展的现状,自由选择最适合的技术栈。由于每个微服务相对简单,故需要对技术栈进行升级时所面临的风险就较低,甚至完全重构一个微服务也是可行的。

容错:当某一组建发生故障时,在单一进程的传统架构下,故障很有可能在进程内扩散,形成应用全局性的不可用。在微服务架构下,故障会被隔离在单个服务中。若设计良好,其他服务可通过重试、平稳退化等机制实现应用层面的容错。

扩展:单块架构应用也可以实现横向扩展,就是将整个应用完整的复制到不同的节点。当应用的不同组件在扩展需求上存在差异时,微服务架构便体现出其灵活性,因为每个服务可以根据实际需求独立进行扩展。

什么是Spring Boot

Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置。用我的话来理解,就是Spring Boot其实不是什么新的框架,它默认配置了很多框架的使用方式,就像maven整合了所有的jar包,Spring Boot整合了所有的框架(不知道这样比喻是否合适)。

Spring Boot简化了基于Spring的应用开发,通过少量的代码就能创建一个独立的、产品级别的Spring应用。 Spring Boot为Spring平台及第三方库提供开箱即用的设置,这样你就可以有条不紊地开始。Spring Boot的核心思想就是约定大于配置,多数Spring Boot应用只需要很少的Spring配置。采用Spring Boot可以大大的简化你的开发模式,所有你想集成的常用框架,它都有对应的组件支持。

Spring Cloud都做了哪些事

Spring Cloud是一系列框架的有序集合。它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用Spring Boot的开发风格做到一键启动和部署。Spring并没有重复制造轮子,它只是将目前各家公司开发的比较成熟、经得起实际考验的服务框架组合起来,通过Spring Boot风格进行再封装屏蔽掉了复杂的配置和实现原理,最终给开发者留出了一套简单易懂、易部署和易维护的分布式系统开发工具包

以下为Spring Cloud的核心功能:

  • 分布式/版本化配置

  • 服务注册和发现

  • 路由

  • 服务和服务之间的调用

  • 负载均衡

  • 断路器

  • 分布式消息传递

我们再来看一张图:


通过这张图,我们来了解一下各组件配置使用运行流程:

  • 1、请求统一通过API网关(Zuul)来访问内部服务.

  • 2、网关接收到请求后,从注册中心(Eureka)获取可用服务

  • 3、由Ribbon进行均衡负载后,分发到后端具体实例

  • 4、微服务之间通过Feign进行通信处理业务

  • 5、Hystrix负责处理服务超时熔断

  • 6、Turbine监控服务间的调用和熔断相关指标

Spring Cloud体系介绍

上图只是Spring Cloud体系的一部分,Spring Cloud共集成了19个子项目,里面都包含一个或者多个第三方的组件或者框架!

Spring Cloud 工具框架

1、Spring Cloud Config 配置中心,利用git集中管理程序的配置。 

2、Spring Cloud Netflix 集成众多Netflix的开源软件
3、Spring Cloud Bus 消息总线,利用分布式消息将服务和服务实例连接在一起,用于在一个集群中传播状态的变化 
4、Spring Cloud for Cloud Foundry 利用Pivotal Cloudfoundry集成你的应用程序
5、Spring Cloud Cloud Foundry Service Broker 为建立管理云托管服务的服务代理提供了一个起点。
6、Spring Cloud Cluster 基于Zookeeper, Redis, Hazelcast, Consul实现的领导选举和平民状态模式的抽象和实现。
7、Spring Cloud Consul 基于Hashicorp Consul实现的服务发现和配置管理。
8、Spring Cloud Security 在Zuul代理中为OAuth2 rest客户端和认证头转发提供负载均衡
9、Spring Cloud Sleuth SpringCloud应用的分布式追踪系统,和Zipkin,HTrace,ELK兼容。
10、Spring Cloud Data Flow 一个云本地程序和操作模型,组成数据微服务在一个结构化的平台上。
11、Spring Cloud Stream 基于Redis,Rabbit,Kafka实现的消息微服务,简单声明模型用以在Spring Cloud应用中收发消息。
12、Spring Cloud Stream App Starters 基于Spring Boot为外部系统提供spring的集成
13、Spring Cloud Task 短生命周期的微服务,为SpringBooot应用简单声明添加功能和非功能特性。
14、Spring Cloud Task App Starters
15、Spring Cloud Zookeeper 服务发现和配置管理基于Apache Zookeeper。
16、Spring Cloud for Amazon Web Services 快速和亚马逊网络服务集成。
17、Spring Cloud Connectors 便于PaaS应用在各种平台上连接到后端像数据库和消息经纪服务。
18、Spring Cloud Starters (项目已经终止并且在Angel.SR2后的版本和其他项目合并)
19、Spring Cloud CLI 插件用Groovy快速的创建Spring Cloud组件应用。

当然这个数量还在一直增加...

三者之间的关系

微服务是一种架构的理念,提出了微服务的设计原则,从理论为具体的技术落地提供了指导思想。Spring Boot是一套快速配置脚手架,可以基于Spring Boot快速开发单个微服务;Spring Cloud是一个基于Spring Boot实现的服务治理工具包;Spring Boot专注于快速、方便集成的单个微服务个体,Spring Cloud关注全局的服务治理框架。

Spring Boot/Cloud是微服务实践的最佳落地方案。

实战经历

遇到问题,寻找方案

2015年初的时候,因为公司业务的大量发展,我们开始对原有的业务进行拆分,新上的业务线也全部使用独立的项目来开发,项目和项目之间通过http接口进行访问。15年的业务发展非常迅速,项目数量也就相应急剧扩大,到了15底的时候项目达60多个,当项目数达到30几个的时候,其实我们就遇到了问题,经常某个项目因为扩展增加了新的IP地址,我们就需要被动的更新好几个相关的项目。服务越来越多,服务之间的调用关系也越来越复杂,有时候想画一张图来表示项目和项目之间的依赖关系,线条密密麻麻无法看清。网上有一张图可以表达我们的心情。


这个时候我们就想找一种方案,可以将我们这么多分布式的服务给管理起来,到网上进行了技术调研。我们发现有两款开源软件比较适合我们,一个是Dubbo,一个是Spring Cloud。

其实刚开始我们是走了一些弯路的。这两款框架我们当时都不熟悉,当时国内使用Spring Cloud进行开发的企业非常的少,我在网上也几乎没找到太多应用的案例。但是Dubbo当时在国内的使用还是挺普遍的,相关的资料各方面都比较完善。因此在公司扩展新业务线众筹平台的时候,技术选型就先定了Dubbo,因为也是全新的业务没有什么负担,这个项目我们大概开发了六个月投产,上线之初也遇到了一些问题,但最终还比较顺利。

在新业务线选型使用Dubbo的同时,我们也没有完全放弃Spring Cloud,我们抽出了一两名开发人员学习Spring Boot我也参与其中,为了验证Spring Boot是否可以到达实战的标准,我们在业余的时间使用Spring Boot开发了一款开源软件云收藏,经过这个项目的实战验证我们对Spring Boot就有了信心。最重要的是大家体会到使用Spring Boot的各种便利之后,就再也不想使用传统的方式来进行开发了。

但是还有一个问题,在选择了Spring Boot进行新业务开发的同时,并没有解决我们上面的那个问题,服务于服务直接调用仍然比较复杂和传统,这时候我们就开始研究Spring Cloud。因为大家在前期对Spring Boot有了足够的了解,因此学习Sprig Cloud就显得顺风顺水了。所以在使用Dubbo半年之后,我们又全面开始拥抱Spring Cloud。

为什么选择使用Spring Cloud而放弃了Dubbo

可能大家会问,为什么选择了使用Dubbo之后,而又选择全面使用Spring Cloud呢?其中有几个原因:

1)从两个公司的背景来谈:Dubbo,是阿里巴巴服务化治理的核心框架,并被广泛应用于中国各互联网公司;Spring Cloud是大名鼎鼎的Spring家族的产品。阿里巴巴是一个商业公司,虽然也开源了很多的顶级的项目,但从整体战略上来讲,仍然是服务于自身的业务为主。Spring专注于企业级开源框架的研发,不论是在中国还是在世界上使用都非常广泛,开发出通用、开源、稳健的开源框架就是他们的主业。

2)从社区活跃度这个角度来对比,Dubbo虽然也是一个非常优秀的服务治理框架,并且在服务治理、灰度发布、流量分发这方面做的比Spring Cloud还好,除过当当网在基础上增加了rest支持外,已有两年多的时间几乎都没有任何更新了。在使用过程中出现问题,提交到github的Issue也少有回复。

相反Spring Cloud自从发展到现在,仍然在不断的高速发展,从github上提交代码的频度和发布版本的时间间隔就可以看出,现在Spring Cloud即将发布2.0版本,到了后期会更加完善和稳定。

3) 从整个大的平台架构来讲,dubbo框架只是专注于服务之间的治理,如果我们需要使用配置中心、分布式跟踪这些内容都需要自己去集成,这样无形中使用dubbo的难度就会增加。Spring Cloud几乎考虑了服务治理的方方面面,更有Spring Boot这个大将的支持,开发起来非常的便利和简单。

4)从技术发展的角度来讲,Dubbo刚出来的那会技术理念还是非常先进,解决了各大互联网公司服务治理的问题,中国的各中小公司也从中受益不少。经过了这么多年的发展,互联网行业也是涌现了更多先进的技术和理念,Dubbo一直停滞不前,自然有些掉队,有时候我个人也会感到有点可惜,如果Dubbo一直沿着当初的那个路线发展,并且延伸到周边,今天可能又是另一番景象了。

Spring 推出Spring Boot/Cloud也是因为自身的很多原因。Spring最初推崇的轻量级框架,随着不断的发展也越来越庞大,随着集成项目越来越多,配置文件也越来越混乱,慢慢的背离最初的理念。随着这么多年的发展,微服务、分布式链路跟踪等更多新的技术理念的出现,Spring急需一款框架来改善以前的开发模式,因此才会出现Spring Boot/Cloud项目,我们现在访问Spring官网,会发现Spring Boot和Spring Cloud已经放到首页最重点突出的三个项目中的前两个,可见Spring对这两个框架的重视程度。

总结一下,dubbo曾经确实很牛逼,但是Spring Cloud是站在近些年技术发展之上进行开发,因此更具技术代表性。

如何进行微服务架构演进

当我们将所有的新业务都使用Spring Cloud这套架构之后,就会出现这样一个现象,公司的系统被分成了两部分,一部分是传统架构的项目,一部分是微服务架构的项目,如何让这两套配合起来使用就成为了关键,这时候Spring Cloud里面的一个关键组件解决了我们的问题,就是Zuul。在Spring Cloud架构体系内的所有微服务都通过Zuul来对外提供统一的访问入口,所有需要和微服务架构内部服务进行通讯的请求都走统一网关。如下图:


从上图可以看出我们对服务进行了分类,有四种:基础服务、业务服务、组合服务、前置服务。不同服务迁移的优先级不同

  • 基础服务,是一些基础组件,与具体的业务无关。比如:短信服务、邮件服务。这里的服务最容易摘出来做微服务,也是我们第一优先级分离出来的服务。

  • 业务服务,是一些垂直的业务系统,只处理单一的业务类型,比如:风控系统、积分系统、合同系统。这类服务职责比较单一,根据业务情况来选择是否迁移,比如:如果突然有需求对积分系统进行大优化,我们就趁机将积分系统进行改造,是我们的第二优先级分离出来的服务。

  • 前置服务,前置服务一般为服务的接入或者输出服务,比如网站的前端服务、app的服务接口这类,这是我们第三优先级分离出来的服务。

  • 组合服务,组合服务就是涉及到了具体的业务,比如买标过程,需要调用很多垂直的业务服务,这类的服务我们一般放到最后再进行微服务化架构来改造,因为这类服务最为复杂,除非涉及到大的业务逻辑变更,我们是不会轻易进行迁移。

在这四类服务之外,新上线的业务全部使用Sprng Boot/Cloud这套技术栈。就这样,我们从开源项目云收藏开始,上线几个Spring Boot项目,到现在公司绝大部分的项目都是在Spring Cloud这个架构体系中。

经验和教训

架构演化的步骤

  • 在确定使用Spring Boot/Cloud这套技术栈进行微服务改造之前,先梳理平台的服务,对不同的服务进行分类,以确认演化的节奏。

  • 先让团队熟悉Spring Boot技术,并且优先在基础服务上进行技术改造,推动改动后的项目投产上线

  • 当团队熟悉Spring Boot之后,再推进使用Spring Cloud对原有的项目进行改造。

  • 在进行微服务改造过程中,优先应用于新业务系统,前期可以只是少量的项目进行了微服务化改造,随着大家对技术的熟悉度增加,可以加快加大微服务改造的范围

  • 传统项目和微服务项目共存是一个很常见的情况,除非公司业务有大的变化,不建议直接迁移核心项目。

服务拆分原则

服务拆分有以下几个原则和大家分享

横向拆分。按照不同的业务域进行拆分,例如订单、营销、风控、积分资源等。形成独立的业务领域微服务集群。

纵向拆分。把一个业务功能里的不同模块或者组件进行拆分。例如把公共组件拆分成独立的原子服务,下沉到底层,形成相对独立的原子服务层。这样一纵一横,就可以实现业务的服务化拆分。

要做好微服务的分层:梳理和抽取核心应用、公共应用,作为独立的服务下沉到核心和公共能力层,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求

服务拆分是越小越好吗?微服务的大与小是相对的。比如在初期,我们把交易拆分为一个微服务,但是随着业务量的增大,可能一个交易系统已经慢慢变得很大,并且并发流量也不小,为了支撑更多的交易量,我会把交易系统,拆分为订单服务、投标服务、转让服务等。因此微服务的拆分力度需与具体业务相结合,总的原则是服务内部高内聚,服务之间低耦合。

微服务vs传统开发

使用微服务有一段时间了,这种开发模式和传统的开发模式对比,有很大的不同。

  • 分工不同,以前我们可能是一个一个模块,现在可能是一人一个系统。

  • 架构不同,服务的拆分是一个技术含量很高的问题,拆分是否合理对以后发展影响巨大。

  • 部署方式不同,如果还像以前一样部署估计累死了,自动化运维不可不上。

  • 容灾不同,好的微服务可以隔离故障避免服务整体down掉,坏的微服务设计仍然可以因为一个子服务出现问题导致连锁反应。

给数据库带来的挑战

每个微服务都有自己独立的数据库,那么后台管理的联合查询怎么处理?这应该是大家会普遍遇到的一个问题,有三种处理方案。

1)严格按照微服务的划分来做,微服务相互独立,各微服务数据库也独立,后台需要展示数据时,调用各微服务的接口来获取对应的数据,再进行数据处理后展示出来,这是标准的用法,也是最麻烦的用法。

2) 将业务高度相关的表放到一个库中,将业务关系不是很紧密的表严格按照微服务模式来拆分,这样既可以使用微服务,也避免了数据库分散导致后台系统统计功能难以实现,是一个折中的方案。

3)数据库严格按照微服务的要求来切分,以满足业务高并发,实时或者准实时将各微服务数据库数据同步到NoSQL数据库中,在同步的过程中进行数据清洗,用来满足后台业务系统的使用,推荐使用MongoDB、HBase等。

三种方案在不同的公司我都使用过,第一种方案适合业务较为简单的小公司;第二种方案,适合在原有系统之上,慢慢演化为微服务架构的公司;第三种适合大型高并发的互联网公司。

微服务的经验和建议

1、建议尽量不要使用Jsp,页面开发推荐使用Thymeleaf。Web项目建议独立部署Tomcat,不要使用内嵌的Tomcat,内嵌Tomcat部署Jsp项目会偶现龟速访问的情况。

2、服务编排是个好东西,主要的作用是减少项目中的相互依赖。比如现在有项目a调用项目b,项目b调用项目c...一直到h,是一个调用链,那么项目上线的时候需要先更新最底层的h再更新g...更新c更新b最后是更新项目a。这只是这一个调用链,在复杂的业务中有非常多的调用,如果要记住每一个调用链对开发运维人员来说就是灾难。

有这样一个好办法可以尽量的减少项目的相互依赖,就是服务编排,一个核心的业务处理项目,负责和各个微服务打交道。比如之前是a调用b,b掉用c,c调用d,现在统一在一个核心项目W中来处理,W服务使用a的时候去调用b,使用b的时候W去调用c,举个例子:在第三方支付业务中,有一个核心支付项目是服务编排,负责处理支付的业务逻辑,W项目使用商户信息的时候就去调用“商户系统”,需要校验设备的时候就去调用“终端系统”,需要风控的时候就调用“风控系统”,各个项目需要的依赖参数都由W来做主控。以后项目部署的时候,只需要最后启动服务编排项目即可。

3、不要为了追求技术而追求技术,确定进行微服务架构改造之前,需要考虑以下几方面的因素:
1)团队的技术人员是否已经具备相关技术基础。
2)公司业务是否适合进行微服务化改造,并不是所有的平台都适合进行微服务化改造,比如:传统行业有很多复杂垂直的业务系统。
3)Spring Cloud生态的技术有很多,并不是每一种技术方案都需要用上,适合自己的才是最好的。

Community Detection 社群发现算法 - CSDN博客

$
0
0

        社区发现(Community Detection)算法用来发现网络中的社区结构,也可以视为一种广义的 聚类算法。以下是我的一个 PPT 报告,分享给大家。



        从上述定义可以看出:社区是一个比较含糊的概念,只给出了一个定性的刻画。

另外需要注意的是,社区是一个子图,包含顶点和边。





        下面我们以新浪微博用户对应的网络图为例,来介绍相应的社区发现算法



        这里在相互关注的用户之间建立连接关系,主要是为了简化模型,此时对应的图为无向图。

当然,我们也可以采用单向关注来建边,此时将对应有向图。




        这个定义看起来很拗口,但通过层层推导,可以得到如下 (4.2)的数学表达式。定义中的随机网络也称为 Null Model,其构造方法为:

        the null model used has so far been a random graph with the same number of nodes, the same number of edges and the same degree distribution as in the original graph, but with links among nodes randomly placed.



       注意,(4.2) 是针对无向图的,因此这里的 m 表示无向边的条数,即若节点 i 和节点 j 有边相连,则节点 (i, j) 对 m 只贡献一条边




        标签传播算法(LPA)的做法比较简单:

第一步: 为所有节点指定一个唯一的标签;

第二步: 逐轮刷新所有节点的标签,直到达到收敛要求为止。对于每一轮刷新,节点标签刷新的规则如下:

        对于某一个节点,考察其所有邻居节点的标签,并进行统计,将出现个数最多的那个标签赋给当前节点。当个数最多的标签不唯一时,随机选一个。


注:算法中的记号 N_n^k 表示节点 n 的邻居中标签为 k 的所有节点构成的集合。



 


       SLPA 中引入了  Listener 和  Speaker 两个比较形象的概念,你可以这么来理解:在刷新节点标签的过程中,任意选取一个节点作为 listener,则其所有邻居节点就是它的 speaker 了,speaker 通常不止一个,一大群 speaker 在七嘴八舌时,listener 到底该听谁的呢?这时我们就需要制定一个规则。

        在 LPA 中,我们以出现次数最多的标签来做决断,其实这就是一种规则。只不过在 SLPA 框架里,规则的选取比较多罢了(可以由用户指定)。

        当然,与 LPA 相比,SLPA 最大的特点在于:它会记录每一个节点在刷新迭代过程中的 历史标签序列(例如迭代 T 次,则每个节点将保存一个长度为 T 的序列,如上图所示),当迭代停止后,对每一个节点历史标签序列中各(互异)标签出现的频率做统计,按照某一给定的阀值过滤掉那些出现频率小的标签,剩下的即为该节点的标签(通常有多个)。


SLPA 后来被作者改名为 GANXiS,且软件包仍在不断更新中......



        这里对上面的图做个简单介绍:带问号的节点是待确定标签的节点,黑色实心点为其邻居节点,它们的标签是已知的,注意标签均是由二元数对的序列构成的,序列中每一个元素的第一个分量表示其标签,第二个分量表示该节点属于该标签对应社区的可能性(或者说概率,叫做 belonging coefficent),因此对于每个节点,其概率之和等于 1。


        我们按照以下步骤来确定带问号节点的标签:


1. 获取邻居节点中所有的互异(distinct) 标签列表,并累加相应的 belonging coefficent 值。

2. 对 belonging coefficent 值列表做归一化,即将列表中每个标签的 belonging coefficent 值除以 C1 (C1 为列表中 belonging coefficent 值的最大值)。

3. 过滤。若列表中归一化后的 belonging coefficent 值(已经介于 0,1 之间)小于某一阀值 p (事先指定的参数),则将对应的二元组从列表中删除。

4. 再一次做归一化。由于过滤后,剩余列表中的各 belonging coefficent 值之和不一定等于 1,因此,需要将每个 belonging coefficent 值除以 C2 (C2 表示各 belonging coefficent 值之和)。


        经过上述四步,列表中的标签即确定为带问号节点的标签。




        这里,我们对 Fast Unfolding 算法做一个简要介绍,它分为以下两个阶段:


第一个阶段:首先将每个节点指定到唯一的一个社区,然后按顺序将节点在这些社区间进行移动。怎么移动呢?以上图中的节点 i 为例,它有三个邻居节点 j1, j2, j3,我们分别尝试将节点 i 移动到 j1, j2, j3 所在的社区,并计算相应的 modularity 变化值,哪个变化值最大就将节点 i 移动到相应的社区中去(当然,这里我们要求最大的 modularity 变化值要为正,如果变化值均为负,则节点 i 保持不动)。按照这个方法反复迭代,直到网络中任何节点的移动都不能再改善总的 modularity 值为止。


第二个阶段:将第一个阶段得到的社区视为新的“节点”(一个社区对应一个),重新构造子图,两个新“节点”之间边的权值为相应两个社区之间各边的权值的总和。


我们将上述两个阶段合起来称为一个 pass,显然,这个 pass  可以继续下去。


        从上述描述我们可以看出,这种算法包含了一种 hierarchy 结构,正如对一个学校的所有初中生进行聚合一样,首先我们可以将他们按照班级来聚合,进一步还可以在此基础上按照年级来聚合,两次聚合都可以看做是一个社区发现结果,就看你想要聚合到什么层次与程度。




        DCLP 算法是 LPA 的一个变种,它引入了一个参数来限制每一个标签的 传播范围,这样可有效控制 Monster (非常大的 community,远大于其他 community)的产生。



        


        最后,我们给出一些 实验结果

        对比上述两个表格可知:SDCLP 算法得到的 top 5 社区更为均匀。



       最近有很多朋友向我索要本文的 PPT,为方便大家参考,我把 本文的完整 PPTSPLA / GANXiS 软件包的源码以及 几篇相关算法的代表 Paper 打包了,有需要的读者请点击 社区发现算法资料自行下载。 


作者: peghoty 

出处:  http://blog.csdn.net/peghoty/article/details/9286905 

欢迎转载/分享, 但请务必声明文章出处.




logistic回归详解(二):损失函数(cost function)详解 - CSDN博客

$
0
0

有监督学习

机器学习分为有监督学习,无监督学习,半监督学习,强化学习。对于逻辑回归来说,就是一种典型的有监督学习。
既然是有监督学习,训练集自然可以用如下方式表述:

{(x1,y1),(x2,y2),⋯,(xm,ym)}

对于这m个训练样本,每个样本本身有n维特征。再加上一个偏置项x0, 则每个样本包含n+1维特征:

x=[x0,x1,x2,⋯,xn]T

其中x∈Rn+1,x0=1,y∈{0,1}

李航博士在统计学习方法一书中给分类问题做了如下定义:
分类是监督学习的一个核心问题,在监督学习中,当输出变量Y取有限个离散值时,预测问题便成为分类问题。这时,输入变量X可以是离散的,也可以是连续的。监督学习从数据中学习一个分类模型或分类决策函数,称为分类器(classifier)。分类器对新的输入进行输出的预测(prediction),称为分类(classification).

在logistic回归详解一( http://blog.csdn.net/bitcarmanlee/article/details/51154481)中,我们花了一整篇篇幅阐述了为什么要使用logistic函数:

hθ(x)=g(θTx)=11+e−θTx

其中一个重要的原因,就是要将Hypothesis(NG课程里的说法)的输出映射到0与1之间,既:
0≤hθ(x)≤1

同样是李航博士统计学习方法一书中,有以下描述:
统计学习方法都是由模型,策略,和算法构成的,即统计学习方法由三要素构成,可以简单表示为:

方法=模型+策略+算法

对于logistic回归来说,模型自然就是logistic回归,策略最常用的方法是用一个损失函数(loss function)或代价函数(cost function)来度量预测错误程度,算法则是求解过程,后期会详细描述相关的优化算法。

logistic函数求导

g′(z)=ddz11+e−z=1(1+e−z)2(e−z)=1(1+e−z)⋅(1−1(1+e−z))=g(z)(1−g(z))

此求导公式在后续推导中会使用到

常见的损失函数

机器学习或者统计机器学习常见的损失函数如下:

1.0-1损失函数 (0-1 loss function)

L(Y,f(X))={1,0,Y ≠ f(X)Y = f(X)

2.平方损失函数(quadratic loss function)

L(Y,f(X))=(Y−f(x))2

3.绝对值损失函数(absolute loss function)

L(Y,f(x))=|Y−f(X)|

4.对数损失函数(logarithmic loss function) 或对数似然损失函数(log-likehood loss function)

L(Y,P(Y|X))=−logP(Y|X)

逻辑回归中,采用的则是对数损失函数。如果损失函数越小,表示模型越好。

说说对数损失函数与平方损失函数

在逻辑回归的推导中国,我们假设样本是服从伯努利分布(0-1分布)的,然后求得满足该分布的似然函数,最终求该似然函数的极大值。整体的思想就是求极大似然函数的思想。而取对数,只是为了方便我们的在求MLE(Maximum Likelihood Estimation)过程中采取的一种数学手段而已。

损失函数详解

根据上面的内容,我们可以得到逻辑回归的对数似然损失函数cost function:

cost(hθ(x),y)={−log(hθ(x))−log(1−hθ(x))if y=1if y=0

稍微解释下这个损失函数,或者说解释下对数似然损失函数:
当y=1时,假定这个样本为正类。如果此时hθ(x)=1,则单对这个样本而言的cost=0,表示这个样本的预测完全准确。那如果所有样本都预测准确,总的cost=0
但是如果此时预测的概率hθ(x)=0,那么cost→∞。直观解释的话,由于此时样本为一个正样本,但是预测的结果P(y=1|x;θ)=0, 也就是说预测 y=1的概率为0,那么此时就要对损失函数加一个很大的惩罚项。
当y=0时,推理过程跟上述完全一致,不再累赘。

将以上两个表达式合并为一个,则单个样本的损失函数可以描述为:

cost(hθ(x),y)=−yilog(hθ(x))−(1−yi)log(1−hθ(x))

因为yi只有两种取值情况,1或0,分别令y=1或y=0,即可得到原来的分段表示式。

全体样本的损失函数可以表示为:

cost(hθ(x),y)=∑i=1m−yilog(hθ(x))−(1−yi)log(1−hθ(x))

这就是逻辑回归最终的损失函数表达式

CTR预估中GBDT与LR融合方案 - CSDN博客

$
0
0

1、 背景

      CTR预估(Click-Through Rate Prediction)是互联网计算广告中的关键环节,预估准确性直接影响公司广告收入。CTR预估中用的最多的模型是LR(Logistic Regression)[1],LR是广义线性模型,与传统线性模型相比,LR使用了Logit变换将函数值映射到0~1区间[2],映射后的函数值就是CTR的预估值。LR这种线性模型很容易并行化,处理上亿条训练样本不是问题,但线性模型学习能力有限,需要大量特征工程预先分析出有效的特征、特征组合,从而去间接增强LR的非线性学习能力。

      LR模型中的特征组合很关键, 但又无法直接通过特征笛卡尔积解决,只能依靠人工经验,耗时耗力同时并不一定会带来效果提升。如何自动发现有效的特征、特征组合,弥补人工经验不足,缩短LR特征实验周期,是亟需解决的问题。Facebook 2014年的文章介绍了通过GBDT(Gradient Boost Decision Tree)解决LR的特征组合问题[3],随后Kaggle竞赛也有实践此思路[4][5],GBDT与LR融合开始引起了业界关注。

      GBDT(Gradient Boost Decision Tree)是一种常用的非线性模型[6][7][8][9],它基于集成学习中的boosting思想[10],每次迭代都在减少残差的梯度方向新建立一颗决策树,迭代多少次就会生成多少颗决策树。GBDT的思想使其具有天然优势可以发现多种有区分性的特征以及特征组合,决策树的路径可以直接作为LR输入特征使用,省去了人工寻找特征、特征组合的步骤。这种通过GBDT生成LR特征的方式(GBDT+LR),业界已有实践(Facebook,Kaggle-2014),且效果不错,是非常值得尝试的思路。下图1为使用GBDT+LR前后的特征实验示意图,融合前人工寻找有区分性特征(raw feature)、特征组合(cross feature),融合后直接通过黑盒子(Tree模型GBDT)进行特征、特种组合的自动发现。

图1

2、 GBDT与LR融合现状

      GBDT与LR的融合方式,Facebook的paper有个例子如下图2所示,图中Tree1、Tree2为通过GBDT模型学出来的两颗树,x为一条输入样本,遍历两棵树后,x样本分别落到两颗树的叶子节点上,每个叶子节点对应LR一维特征,那么通过遍历树,就得到了该样本对应的所有LR特征。由于树的每条路径,是通过最小化均方差等方法最终分割出来的有区分性路径,根据该路径得到的特征、特征组合都相对有区分性,效果理论上不会亚于人工经验的处理方式。

图2

      GBDT模型的特点,非常适合用来挖掘有效的特征、特征组合。业界不仅GBDT+LR融合有实践,GBDT+FM也有实践,2014 Kaggle CTR竞赛冠军就是使用GBDT+FM,可见,使用GBDT融合其它模型是非常值得尝试的思路[11]。

      笔者调研了Facebook、Kaggle竞赛关于GBDT建树的细节,发现两个关键点:采用ensemble决策树而非单颗树;建树采用GBDT而非RF(Random Forests)。解读如下:

      1)为什么建树采用ensemble决策树?

      一棵树的表达能力很弱,不足以表达多个有区分性的特征组合,多棵树的表达能力更强一些。GBDT每棵树都在学习前面棵树尚存的不足,迭代多少次就会生成多少颗树。按paper以及Kaggle竞赛中的GBDT+LR融合方式,多棵树正好满足LR每条训练样本可以通过GBDT映射成多个特征的需求。

      2)为什么建树采用GBDT而非RF?

      RF也是多棵树,但从效果上有实践证明不如GBDT。且GBDT前面的树,特征分裂主要体现对多数样本有区分度的特征;后面的树,主要体现的是经过前N颗树,残差仍然较大的少数样本。优先选用在整体上有区分度的特征,再选用针对少数样本有区分度的特征,思路更加合理,这应该也是用GBDT的原因。

      然而,Facebook和Kaggle竞赛的思路是否能直接满足现在CTR预估场景呢?

      按照Facebook、Kaggle竞赛的思路,不加入广告侧的ADID特征?但是现CTR预估中,AD ID类特征是很重要的特征,故建树时需要考虑AD ID。直接将AD ID加入到建树的feature中?但是AD ID过多,直接将AD ID作为feature进行建树不可行。下面第三部分将介绍针对现有CTR预估场景GBDT+LR的融合方案。

3、 GBDT与LR融合方案

      AD ID类特征在CTR预估中是非常重要的特征,直接将AD ID作为feature进行建树不可行,顾考虑为每个AD ID建GBDT树。但互联网时代长尾数据现象非常显著,广告也存在长尾现象,为了提升广告整体投放效果,不得不考虑长尾广告[12]。在GBDT建树方案中,对于曝光充分训练样本充足的广告,可以单独建树,发掘对单个广告有区分度的特征,但对于曝光不充分样本不充足的长尾广告,无法单独建树,需要一种方案来解决长尾广告的问题。

      综合考虑方案如下,使用GBDT建两类树,非ID建一类树,ID建一类树。1)非ID类树:不以细粒度的ID建树,此类树作为base,即便曝光少的广告、广告主,仍可以通过此类树得到有区分性的特征、特征组合。2)ID类树:以细粒度的ID建一类树,用于发现曝光充分的ID对应有区分性的特征、特征组合。

      如何根据GBDT建的两类树,对原始特征进行映射?以如下图3为例,当一条样本x进来之后,遍历两类树到叶子节点,得到的特征作为LR的输入。当AD曝光不充分不足以训练树时,其它树恰好作为补充。

图3

      通过GBDT 映射得到的特征空间维度如何?GBDT树有多少个叶子节点,通过GBDT得到的特征空间就有多大。如下图4一颗树,一个叶子节点对应一种有区分性的特征、特征组合,对应LR的一维特征。这颗树有8个叶子节点,即对应LR 的8维特征。估算一下,通过GBDT转换得到的特征空间较低,Base树、ID树各N颗,特征空间维度最高为(N+N*广告数+N*广告主数+ N*广告类目数)*叶子节点个数。其中广告数、广告主数、广告类目数都是有限的,同时参考Kaggle竞赛中树的数目N最多为30,叶子节点个数小于10,则估算通过GBDT 映射得到的特征空间维度并不高,且并不是每个ID训练样本都足以训练多颗树,实际上通过GBDT 映射得到的特征空间维度更低。


图4

      如何使用GBDT 映射得到的特征?

      通过GBDT生成的特征,可直接作为LR的特征使用,省去人工处理分析特征的环节,LR的输入特征完全依赖于通过GBDT得到的特征。此思路已尝试,通过实验发现GBDT+LR在曝光充分的广告上确实有效果,但整体效果需要权衡优化各类树的使用。同时,也可考虑将GBDT生成特征与LR原有特征结合起来使用,待尝试。

4、 总结与展望

      点击率预估模型涉及的训练样本一般是上亿级别,样本量大,模型常采用速度较快的LR。但LR是线性模型,学习能力有限,此时特征工程尤其重要。现有的特征工程实验,主要集中在寻找到有区分度的特征、特征组合,折腾一圈未必会带来效果提升。GBDT算法的特点正好可以用来发掘有区分度的特征、特征组合,减少特征工程中人力成本,且业界现在已有实践,GBDT+LR、GBDT+FM等都是值得尝试的思路。不同场景,GBDT融合LR/FM的思路可能会略有不同,可以多种角度尝试。

5、 参考文献

[1].Chapelle O, Manavoglu E, Rosales R. Simple and scalable responseprediction for display advertising[J]. ACM

[2]. http://blog.csdn.net/lilyth_lilyth/article/details/10032993

[3].He X, Pan J, Jin O, et al. Practical lessons from predicting clicks on adsat facebook[C]. Proceedings of 20th ACM SIGKDD Conference on KnowledgeDiscovery and Data Mining. ACM, 2014: 1-9.

[4]. http://www.csie.ntu.edu.tw/~r01922136/Kaggle-2014-criteo.pdf

[5]. https://github.com/guestwalk/Kaggle-2014-criteo

[6]. http://www.cnblogs.com/leftnoteasy/archive/2011/03/07/random-forest-and-gbdt.html

[7]. https://github.com/dmlc/xgboost

[8]. http://cos.name/2015/03/xgboost/?replytocom=6610

[9]. http://vdisk.weibo.com/s/vlQWp3erG2yo/1431658679

[10].Ensemble Methods: Foundations and Algorithms (Chapman& Hall/Crc Machine Learnig & Pattern Recognition): Zhi-Hua Zhou:9781439830031

 [11]. http://blog.csdn.net/hero_fantao/article/details/42747281

[12]. Richardson M, Dominowska E, Ragno R. Predicting clicks: estimatingthe click-through rate for new ads[C]. Proceedings of the 16th internationalconference on World Wide Web. ACM, 2007: 521-530.

 

 

 

 

 

【实践】CTR中xgboost/gbdt +lr - CSDN博客

$
0
0

自学习 CTR预估中GBDT与LR融合方案 ,有意用简单暴利的python实现一版GBDT/XGboost做特征选择,融合LR进行CTR的代码demo。

1. GBDT + LR

python3.5.3 + scikit-learn0.18.1

from scipy.sparse.construct import hstack
from sklearn.model_selection import train_test_split
from sklearn.datasets.svmlight_format import load_svmlight_file
from sklearn.ensemble.gradient_boosting import GradientBoostingClassifier
from sklearn.linear_model.logistic import LogisticRegression
from sklearn.metrics.ranking import roc_auc_score
from sklearn.preprocessing.data import OneHotEncoder
import numpy as np

def gbdt_lr_train(libsvmFileName):

    # load样本数据
    X_all, y_all = load_svmlight_file(libsvmFileName)

    # 训练/测试数据分割
    X_train, X_test, y_train, y_test = train_test_split(X_all, y_all, test_size = 0.3, random_state = 42)

    # 定义GBDT模型
    gbdt = GradientBoostingClassifier(n_estimators=40, max_depth=3, verbose=0,max_features=0.5)

    # 训练学习
    gbdt.fit(X_train, y_train)

    # 预测及AUC评测
    y_pred_gbdt = gbdt.predict_proba(X_test.toarray())[:, 1]
    gbdt_auc = roc_auc_score(y_test, y_pred_gbdt)
    print('gbdt auc: %.5f' % gbdt_auc)

    # lr对原始特征样本模型训练
    lr = LogisticRegression()
    lr.fit(X_train, y_train)    # 预测及AUC评测
    y_pred_test = lr.predict_proba(X_test)[:, 1]
    lr_test_auc = roc_auc_score(y_test, y_pred_test)
    print('基于原有特征的LR AUC: %.5f' % lr_test_auc)

    # GBDT编码原有特征
    X_train_leaves = gbdt.apply(X_train)[:,:,0]
    X_test_leaves = gbdt.apply(X_test)[:,:,0]

    # 对所有特征进行ont-hot编码
    (train_rows, cols) = X_train_leaves.shape

    gbdtenc = OneHotEncoder()
    X_trans = gbdtenc.fit_transform(np.concatenate((X_train_leaves, X_test_leaves), axis=0))

    # 定义LR模型
    lr = LogisticRegression()
    # lr对gbdt特征编码后的样本模型训练
    lr.fit(X_trans[:train_rows, :], y_train)
    # 预测及AUC评测
    y_pred_gbdtlr1 = lr.predict_proba(X_trans[train_rows:, :])[:, 1]
    gbdt_lr_auc1 = roc_auc_score(y_test, y_pred_gbdtlr1)
    print('基于GBDT特征编码后的LR AUC: %.5f' % gbdt_lr_auc1)

    # 定义LR模型
    lr = LogisticRegression(n_jobs=-1)
    # 组合特征
    X_train_ext = hstack([X_trans[:train_rows, :], X_train])
    X_test_ext = hstack([X_trans[train_rows:, :], X_test])

    print(X_train_ext.shape)
    # lr对组合特征的样本模型训练
    lr.fit(X_train_ext, y_train)

    # 预测及AUC评测
    y_pred_gbdtlr2 = lr.predict_proba(X_test_ext)[:, 1]
    gbdt_lr_auc2 = roc_auc_score(y_test, y_pred_gbdtlr2)
    print('基于组合特征的LR AUC: %.5f' % gbdt_lr_auc2)


if __name__ == '__main__':
    gbdt_lr_train('data/sample_libsvm_data.txt')
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72

2. XGboost + LR

python3.5.3 + scikit-learn0.18.1 + xgboost0.6

import xgboost as xgb
from sklearn.datasets import load_svmlight_file
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import roc_curve, auc, roc_auc_score
from sklearn.externals import joblib
import numpy as np
from scipy.sparse import hstack
from sklearn.preprocessing.data import OneHotEncoder


def xgboost_lr_train(libsvmFileNameInitial):

    # load样本数据
    X_all, y_all = load_svmlight_file(libsvmFileNameInitial)

    # 训练/测试数据分割
    X_train, X_test, y_train, y_test = train_test_split(X_all, y_all, test_size = 0.3, random_state = 42)

    # 定义xgb模型
    xgboost = xgb.XGBClassifier(nthread=4, learning_rate=0.08,
                            n_estimators=50, max_depth=5, gamma=0, subsample=0.9, colsample_bytree=0.5)
    # 训练xgb学习
    xgboost.fit(X_train, y_train)

    # 预测xgb及AUC评测
    y_pred_test = xgboost.predict_proba(X_test)[:, 1]
    xgb_test_auc = roc_auc_score(y_test, y_pred_test)
    print('xgboost test auc: %.5f' % xgb_test_auc)

    # xgboost编码原有特征
    X_train_leaves = xgboost.apply(X_train)
    X_test_leaves = xgboost.apply(X_test)


    # 合并编码后的训练数据和测试数据
    All_leaves = np.concatenate((X_train_leaves, X_test_leaves), axis=0)
    All_leaves = All_leaves.astype(np.int32)

    # 对所有特征进行ont-hot编码
    xgbenc = OneHotEncoder()
    X_trans = xgbenc.fit_transform(All_leaves)

    (train_rows, cols) = X_train_leaves.shape

    # 定义LR模型
    lr = LogisticRegression()
    # lr对xgboost特征编码后的样本模型训练
    lr.fit(X_trans[:train_rows, :], y_train)
    # 预测及AUC评测
    y_pred_xgblr1 = lr.predict_proba(X_trans[train_rows:, :])[:, 1]
    xgb_lr_auc1 = roc_auc_score(y_test, y_pred_xgblr1)
    print('基于Xgb特征编码后的LR AUC: %.5f' % xgb_lr_auc1)

    # 定义LR模型
    lr = LogisticRegression(n_jobs=-1)
    # 组合特征
    X_train_ext = hstack([X_trans[:train_rows, :], X_train])
    X_test_ext = hstack([X_trans[train_rows:, :], X_test])

    # lr对组合特征的样本模型训练
    lr.fit(X_train_ext, y_train)

    # 预测及AUC评测
    y_pred_xgblr2 = lr.predict_proba(X_test_ext)[:, 1]
    xgb_lr_auc2 = roc_auc_score(y_test, y_pred_xgblr2)
    print('基于组合特征的LR AUC: %.5f' % xgb_lr_auc2)

if __name__ == '__main__':
    xgboost_lr_train("data/sample_libsvm_data.txt")

word精华编号篇之一自动编号-只谈office-51CTO博客

$
0
0

      我也曾经有过为编号抓狂的日子,曾经熬夜写某项目方案,又花了整整半天的时间修改格式和编号,痛定思痛发誓再也不能这么过,最理想的就是设置一次编号格式后,点标题按钮编号自动出现。Anything is possible,只要您按照下面的步骤去做。
   再次强调,样式、编号、目录、页眉页脚功能从遥远的word早期版本就已经提供给我们了,还是那句话,只是我们缺少发现。从2007版开始两个编号按钮直接放在段落选项卡中方便取用。
 
为了说明方便,我把左边的按钮起名叫小编号按钮,右边的按钮叫大编号按钮。

          

小编号按钮                                      大编号按钮

 
我们在文章中给各级标题编号及形式修改,使用大编号按钮;给正文中某部分文字编号及形式修改,使用小编号按钮;所有的编号数值修改,都使用小编号按钮下的“设置编号值”命令。

 

二. 各级标题编号的生成与快速修改
1.最快方法:编号--样式—文字
  新建文档,不需要新建任何文字与标题,直接点大编号按钮,选择列表库里第二行第三种样式,现在的样式面板中的各标题立刻与编号建立了内部关联,显示如下
 

   而且文档中甚至立刻出现了第一个标题“1”编号,等待您输入标题文字,现在的编号格式就是以下形式
1 背景
1.1    现状
1.1.1 问题
  之所以选择这个多级编号样式,因为这是最常用的编号形式而且是最方便修改为中文编号格式的。
  下面将标题1快速修改为我们最常使用的中文一、二、三编号,点大编号按钮中的定义新的多级列表,出现下面对话框,您需要再点击一次更多按钮,就和我的显示一样,点击左上角的级别1,在下面“此级别的编号样式”中选择“一、二、三”样式,一级标题就更换成功了,但是悲剧似乎出现,其他标题的第一位数字也变成了“一”,

最简单的解决方法:分别点2、3、4等各级别,勾选右面的正规形式编号,各标题编号恢复正常。更多的编号样式修改我们将在下一次介绍。
 

 

   接着您再也不用操心编号的事了,踏踏实实按照我们前面文章里介绍的样式修改方法,修改各级标题的字体字号等以后,专心构思您的方案或报告。
 
2.一般方法:文字—样式—编号
   先写文字,second修改标题样式,then 光标放在任意一个标题文字中都可以(不需要选中),再点大编号按钮,还是按照上述步骤设置。
 
  通过以上任意一种方法设置后,我们再新建一行,直接点某标题样式,自动出现带有对应标题样式的 顺序编号,点标题1出现二”,点标题2出现“2.1”,而且当我们在导航视图或者回到大纲视图中对标题顺序进行调整时,相应的编号都会自动更新。
 
三. 正文内容使用编号
使用自动编号
文字部分编号,使用小编号按钮,选择一种编号样式,按下回车后自动出现下一个编号,直接写内容,回车后出现下一个编号,例如
1)         背景
2)         规划
3)         方案
  当我们在下一部分中想重新开始编号时,会继续上面的编号,需要更改编号值从1重新开始
4)         介绍
5)         内容
6)         总结
  点击小编号按钮中的设置编号值,选择开始新列表,值设置调整回1就可以了

  

  如果要上下调整编号内容顺序,可以光标放在某编号中按 shift+alt+上下箭头,不需要剪切粘贴,直接对换内容,而且编号顺序不乱。
   这样做的好处是调整顺序方便,但是每次开始一个新编号时都要去重新设置编号值。如果我们取消自动编号使用手动输入编号,内容随你定,但是当调整顺序的时候,编号不会自动更改,两种方法各有利弊,根据自己的习惯选择。

Apache JMeter录制HTTPS的方法及测试中常见问题解决 - CSDN博客

$
0
0

HTTPS工作原理

在进入正题之前,我想还是有必要再尽量简洁地唠叨一遍一些有关HTTPS工作原理方面的知识,了解这些技术细节有利于你明白为什么JMeter在录制时要求你进行一系列的准备和配置工作,以及它是如何完成这项测试任务的。而且,在不了解原理的情况下开展相关技术领域的测试工作,可能是一件非常“危险”的事情,如果你已经了解了HTTPS的工作原理,你可以跳过此章节。

加密算法

众所周知,加密算法主要分为对称和非对称加密,对称加密就是大家使用相同的密钥完成加解密,这也是人类历史上长期运用的消息安全传递方法,这个方法有一个明显的缺点就是密钥的唯一性所带来的可能出现多个异地保存的副本,比如一份密码本对于消息的加解密人员必须人手一份才能正常完成工作,一旦有一份密码本泄露出去到了第三方的手中,加密体系的保密性也就不复存在了。 
随着数学和计算机技术的发展,人们终于发明了非对称加密算法以解决密钥的唯一性问题,这时产生了一对密钥,这对密钥可以实现相互加解密。你可以将一份密钥(私钥)掌握在自己手中,将另一份密钥(公钥)放出去,其他人得到公钥,用公钥对信息进行加密后发送给你,你就可以用手中的私钥对消息进行解密,你也可以反向用自己的私钥加密信息发给其他掌握了公钥的人,他们用公钥来解密你的加密信息。在非对称加密体系中,你只需要照顾好自己手中的私钥不要遗失,不用担心公钥的泄露会造成安全隐患。另外,非对称加密算法的好处远远超过了只是消息加密的范畴,当消息发送方手中牢牢掌握了自己的私钥的时候,消息接收方有理由相信,当他使用发送方给他的公钥成功完成消息解密的同时,发送方的身份也就得到的验证。但更加安全的代价就是非对称加密算法的性能相对于对称加密算法要慢得多。

HTTPS工作流程

基本概念

有人会说既然有了非对称加密算法,那么直接利用它就可以实现浏览器与Web服务器间通信消息的加解密了。但你要明白一切保障信息安全的手段都是在牺牲性能和易用性的代价下展开的,确实可以在消息传递中直接利用非对称加密算法来实现消息的加解密,但在浏览器与Web服务器之间HTTP协议如此频繁和体积巨大的消息通信场景下,非对称加密算法的性能问题就凸显出来,如果直接采用,将极大影响协议的整体性能。于是,人们就采用变通的方法,同时利用非对称加密算法和对称加密算法实现针对HTTP消息传递的加解密,其中非对称加密算法只参与到对对称加密算法密钥(对话密钥)的加密过程中,而大块消息的加解密则由浏览器和Web服务器间协商好的对称加密算法来完成。 
另外,任何Web服务器都有可能向浏览器提供建立HTTPS连接所需要的公钥,这里就排除不了一些假冒的服务器提供一些自己伪造的公钥,为了确保Web服务器是值得我浏览器信任的,于是引入了数字证书的概念。为了证明自己是值得信赖的,Web服务器需要向第三方CA机构申请一个数字证书,证书中包含了证书基本信息和服务器的公钥信息,证书的最后面是一个由CA机构的私钥对证书上述信息计算摘要后加密的消息签名信息。浏览器内置了一些权威的根证书,这些根证书中带有对应其发证CA机构的公钥,一旦得到Web服务器的数字证书,浏览器会在第一时间来根据证书信息查找是否存在对应的根证书,如果存在,利用根证书中的公钥解密Web服务器数字证书中的消息签名信息,并与自己计算的证书摘要信息进行比对,从而验证其真实性和完整性,只要通过验证,浏览器会自动信任由这些权威机构颁发的证书。当然在某些特定场景下Web服务器也需要浏览器提供数字证书以证明其可信度。

工作流程

HTTPS采用SSL/TLS协议实现在通信安全上的保障,主要分为握手阶段和对话阶段。 
其中握手阶段的大致的工作流程如下: 
(1)首先,浏览器会向服务器发起HTTPS请求,请求消息中包含了自己当前所采用的SSL/TLS协议的版本信息,一个随机数(用于生成在对话阶段的对称加密密钥)支持的加密算法。另外,还将向服务器索要数字证书; 
(2)服务器回应浏览器的请求,响应消息中包含确认使用与浏览器相同版本的SSL/TLS协议的回应,一个随机数(用于生成在对话阶段的对称加密密钥),确认将使用浏览器支持的加密算法完成加解密。并将数字证书发给浏览器; 
(3)浏览器接到服务器的数字证书后,判断其是否值得信任,如果信任,获取服务器的公钥,将生成一个随机数(用于生成在对话阶段的对称加密密钥)并使用服务器提供的公钥进行加密,向服务器发送请求消息,请求消息中包含了加密后的随机数,编码改变的通知(表示随后的信息都将用双方商定的加密方法和密钥发送),浏览器握手阶段结束的通知(包含一个之前所有信息的摘要值以校验是否握手阶段通信信息完整)。 
(4)服务器确认通知,通过自己的私钥解密被加密随机数,并回应浏览器的请求,响应消息为编码改变的通知(表示随后的信息都将用双方商定的加密方法和密钥发送),服务器握手阶段结束的通知(包含一个之前所有信息的摘要值以校验是否握手阶段通信信息完整)。

对话阶段就将采用由三次产生随机数所生成的对称加密密钥对HTTP消息进行加解密的传输。

测试环境搭建

Keytool工具使用

Keytool 是一个Java数字证书的管理工具,为我们提供了便捷的方法来制作、管理数字证书。主要的使用方法如下: 
(1)制作证书

keytool -genkey -alias xreztento -sigalg MD5withRSA -keyalg RSA -keysize 1024 -validity 10950  -keystore ./test.keystore 
  • 1

可以使用-sigalg指定消息签名算法,比如MD2withRSA、MD5withRSA、SHA1withRSA等。可以使用-keyalg指定非对称加密算法,比如RSA、DSA等。 
如果是初次创建keystore,将要求你设置密码信息,之后填写证书信息后完成证书的生产:

这里写图片描述

(2)查看基本信息

keytool -list -v -keystore ./test.keystore
  • 1

我们就可以看到在keystore中全部证书的基本信息情况,比如消息签名算法,证书指纹等:

这里写图片描述

(3)查看公钥信息

keytool -list -rfc -keystore ./test.keystore
  • 1

我们就可以看到在keystore中全部证书的公钥:

这里写图片描述

(4)导出数字证书

keytool -export -file xreztento.crt -alias xreztento -keystore ./test.keystore
  • 1

我们导出指定的数字证书:

这里写图片描述

(5)导出私钥

You can extract a private key from a keystore with Java6 and OpenSSL. This all depends on the fact that both Java and OpenSSL support PKCS#12-formatted keystores.

Next, use OpenSSL to do the extraction to PEM.

我们可以先将证书仓库转换为PKCS12格式,然后利用OpenSSL自带的工具进行私钥的导出:

keytool -importkeystore -srckeystore test.keystore -destkeystore test.p12 -deststoretype PKCS12
  • 1

导出结果如下:

这里写图片描述

openssl pkcs12 -in test.p12 -out extracted.pem -nodes
  • 1

从而导出私钥:

这里写图片描述

Apache Tomcat的配置

下面,我们就通过Keytool生产的数字证书,来搭建一个基于Apache Tomcat的测试环境,主要的配置非常简单,就是在server.xml文件下增加一个HTTPS的连接器配置项,如下:

<Connector port="8443" protocol="HTTP/1.1" SSLEnabled="true"  
                  maxThreads="150" scheme="https" secure="true"  
                  clientAuth="false" sslProtocol="TLS"   
           keystoreFile="D:\keystore\test.keystore"  
           keystorePass="123456"  
           ciphers="xreztento"/>  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

这样我们就完成了配置工作,现在启动Tomcat,使用8443端口访问:

这里写图片描述

可以看到我们生产的证书由于浏览器没有找到对应的内置根证书,所以警告用户这个HTTPS连接是不可信的,我们通过“高级”下的继续访问强行通过:

这里写图片描述

至此,测试环境准备完成。

JMeter录制HTTPS协议与测试

录制方法

For versions of JMeter from 2.10, JMeter will generate its own certificate(s). These are generated with a validity period defined by the property proxy.cert.validity , default 7 days, and random passwords. If JMeter detects that it is running under Java 7 or later, it will generate certificates for each target server as necessary (dynamic mode) unless the following property is defined: proxy.cert.dynamic_keys=false . When using dynamic mode, the certificate will be for the correct host name, and will be signed by a JMeter-generated CA certificate.

JMeter2.10版本+Java7以上版本很好的支持了HTTPS的录制,我们知道JMeter录制是通过浏览器访问其代理服务器,由代理服务器根据访问解析后生成HTTPSampler的方式完成录制的。代理服务器对于浏览器来说是充当了Web服务器的角色,对于Web服务器来说充当了浏览器的角色,于是当建立代理服务器连接后,浏览器会直接将代理服务器当做目标Web服务器,还记得我们普及的HTTPS原理知识吗,它就会向代理服务器索要数字证书,于是,JMeter通过生成一个本地证书的方式来完成这个与浏览器建立HTTPS握手阶段的过程。 
当启动代理服务器时,JMeter会生产一个证书:

这里写图片描述

所生产的证书在bin目录下:

这里写图片描述

当我们设置完浏览器代理后,开始浏览录制时,会出现以下页面: 
这里写图片描述

显然,我们的JMeter数字证书也是不被信任的,接下来我们就需要将该证书导入到浏览器的根证书列表中以达到人工配置信任的目的:

这里写图片描述

完成导入后,重新启动浏览器,我们再重新录制脚本:

这里写图片描述

于是,我们录制成功,回放一下:

这里写图片描述

这样,我们就完成了基于HTTPS的录制,以上基于对HTTPS工作原理的理解所完成的操作可以完美的实现录制工作,当然,在某些浏览器环境下,你也可以不选择导入证书的环节,而是直接将站点域名作为信任站点加入到信任列表中。

测试中常见问题解决

Certificates does not conform to algorithm constraints

录制完脚本,回放就一定可以成功吗?在大多数情况下,我们都可以成功的完成回放及后续的测试工作,但事事不绝对,你可能会遇到一些奇异的问题,比如,当你制作数字证书时,使用了以下命令,我们将消息签名算法修改为MD2withRSA:

keytool -genkey -alias xreztento -sigalg MD2withRSA -keyalg RSA -keysize 1024 -validity 10950  -keystore ./test.keystore 
  • 1

再次将我们刚才成功回放的脚本运行一次,可能会得到以下错误: 
这里写图片描述

什么原因造成的问题呢?

javax.net.ssl.SSLHandshakeException: java.security.cert.CertificateException: Certificates does not conform to algorithm constraints 
Cause: 
JDK7 has changed the default Java security settings to disable MD2 algorithm to sign SSL certificates. 
Resolving the problem: 
The default Java security settings can be re-enabled by editing JDK_HOME/jre/lib/security/java.security and commenting out the following line: 
\jdk.certpath.disabledAlgorithms=MD2

原因是JDK7以上版本的默认安全策略有所加强,凡是签名算法为MD2的都不符合其默认要求,因此,为你提供了两种途径来修复该问题: 
(1)修改java.security文件的安全策略 
(2)直接将JRE版本调整为Java6

利用人工智能检测色情图片 - CSDN博客

$
0
0

色情内容在中国一直处于严格的监管,即使这样,互联网上还是很容易就能访问到色情内容。还记得曾经的“绿坝-花季护航”软件么?由于其识别效果差、软件不稳定,最后不了了之,浪费了大量的人力和金钱。

随着计算机视觉和深度学习的发展,算法已经成熟,利用人工智能,我们能够更加精确的识别色情内容。现在有很多云服务商提供鉴黄服务,通过集成鉴黄API到产品中,就可以给产品增加色情过滤功能。这种模式对于大多数互联网产品非常有效,可以极大的降低运营风险(对于博客、图床、直播等服务提供商而言,过滤色情、暴力等内容是重中之重)。但对于一部分产品而言,这种模式存在一些不足:

  1. 鉴别图片、视频内容等必须通过网络服务进行,响应速度难以保证;
  2. 通常鉴黄服务按次或者按照流量计费,对于个人开发者而言,有成本负担;

open_nsfw

现在有个好消息,雅虎开源了其深度学习色情图片检测模型open_nsfw,项目地址: http://github.com/yahoo/open_nsfw,这里的NSFW代表Not Suitable for Work。项目提供了基于caffe的深度神经网络模型和一个python脚本,可以供测试:

python ./classify_nsfw.py \
--model_def nsfw_model/deploy.prototxt \--pretrained_model nsfw_model/resnet_50_1by2_nsfw.caffemodel \test_image.jpg

定义NSFW内容是非常主观的,在某个国家或地区会引起反感的内容可能在另一个地方完全没问题。所以,open_nsfw模型只关注一种类型的NSFW内容:色情图片。但需要注意的是,该模型不能解决草图、漫画、文本等内容的识别。

色情图片的判别也是非常主观的,所以该模型并不会直接给出某个图片是否色情的结果,而是给出一个概率(0-1之间的分数)。一般而言,得分小于0.2表示图像很可能是安全的,评分大于0.8则基本可判定图片属于色情图片。如果得分介于这两个值之间,则需要程序员根据需求来设定一个阀值。如果需要比较严格的过滤,设一个比较低的阀值,反之设一个比较高的阀值。

集成open_nsfw到C++程序

open_nsfw提供了一个python脚本,google了一圈,也没有找到有人将open_nsfw集成到C++代码中。好在classify_nsfw.py这个脚本比较简单,而caffe提供了C++的例子cpp_classification,结合这两部分的代码,我编写了一个C++的鉴黄程序,源码参考: https://gitee.com/mogoweb/dpexamples

当然看似简单的代码翻译工作,遇到的坑也不少,下面就总结一下C++代码中需要注意的地方。

图片预处理

在classify_nsfw.py中,编写了一个resize_image函数来处理图片缩放,没有采用caffe内置的图片缩放程序。代码注释中解释是因为训练时使用了这个缩放算法,所以为了达到最好的效果,测试中也需要采用该缩放算法。随后又对图片进行了一次裁剪,因为调用resize_image缩放为256x256大小,而模型接受的size为224x224。

img_data_rs = resize_image(pimg, sz=(256,256))image= caffe.io.load_image(StringIO(img_data_rs))

H, W, _ =image.shape
_, _, h, w = caffe_net.blobs['data'].data.shape
h_off =max((H - h) /2,0)
w_off =max((W - w) /2,0)
crop =image[h_off:h_off + h, w_off:w_off + w, :]

在C++代码中,我使用了caffe中提供的opencv方法处理这个步骤:

cv::Mat img = ReadImageToCVMat(file,256,256);
...// crop imagecv::Sizesize= sample.size();intH =size.height;intW =size.width;inth = input_geometry_.height;intw = input_geometry_.width;inth_off = std::max((H - h) /2,0);intw_off = std::max((W - w) /2,0);
sample(cv::Rect(w_off, h_off, w, h)).copyTo(sample_resized);

数据预处理

在classify_nsfw.py中,我们可以看到这样一段代码:

caffe_transformer= caffe.io.Transformer({'data': nsfw_net.blobs['data'].data.shape})# move image channels to outermostcaffe_transformer.set_transpose('data',(2, 0, 1))# subtract the dataset-mean value in each channelcaffe_transformer.set_mean('data', np.array([104, 117, 123]))# rescale from [0, 1] to [0, 255]caffe_transformer.set_raw_scale('data', 255)# swap channels from RGB to BGRcaffe_transformer.set_channel_swap('data',(2, 1, 0))

这段代码其实是对数据做某种变换,将读入的数据转换为caffe模型能够接受的格式。

caffe_transformer.set_transpose('data',(2, 0, 1))

该语句困扰了我好长时间,不知道在C++程序中该如何处理。后查网络资料,才了解到因为caffe.io读取的图像为HWC(H:高,W:宽,C:颜色)矩阵,而caffe模型需要(CHW)格式,所以需要对矩阵做一个变换,将颜色值维度提前到最前面。(2, 0, 1)的含义就是将原来数据的第2, 0, 1列按照新的顺序重新排列。

caffe_transformer.set_raw_scale('data', 255)

这个处理是因为使用caffe.io读取的颜色值归一化为0~1之间的数,而caffe模型接受的是一个字节的整数,范围0~255,所以需要进行一个放大。

在C++代码中,由于采用了opencv进行图像处理,不需要上面两步的变换处理。

caffe_transformer.set_mean('data', np.array([104, 117, 123]))

在很多示例中,均值通常从均值文件中加载,这里直接给了一个固定值。所谓均值,可以理解为数据的算术平均值。通常输入数据减去均值,是为了减少奇异值(异常的值,比平均值大很多或小很多的值)的影响。这里是一个三元组,分别代表RGB通道上的均值。

对应的C++代码如下:

cv::Matsample_normalized;cv::subtract(sample_float, mean_, sample_normalized);

接下来的代码是RGB转BGR,这个比较容易理解。

caffe_transformer.set_channel_swap('data',(2, 1, 0))

查看了一些caffe的C++例子,均没有这个步骤,可能cv::Mat中已经能够正确判断出RGB和BGR,所以代码中没有增加这一步骤。

对比测试

这个程序是否能够如愿工作呢?让我们找一些图片测试一下。考虑到内容审查,这里进行测试的图片均不是严格意义上的色情图片,只是裸露程度不同。下面使用C++程序和open_nsfw python脚本测试的结果进行对比。

image

C++ : 0.6122

python: 0.875480949879

image

C++ : 0.2536

python: 0.0773676931858

image

C++ : 0.6319

python: 0.782215833664

image

C++ : 0.0914

python: 0.0774072110653

image

C++ : 0.0073

python: 3.04092318402e-05

从结果可以看出,使用C++程序进行测试,结果基本符合预期,但是和python版本还是有一些差距,猜测问题可能在于对图片进行缩放采用的算法不同,如果要获得好的结果,训练和测试阶段对数据的预处理需要一致。另外一个可能是没有RGB到BGR的转换,这个还需要再验证。

如果你有兴趣,可以找一些尺度更大的图片测试,看看是不是能够正确的识别出来。


Spark-Streaming获取kafka数据的两种方式-Receiver与Direct的方式 - CSDN博客

$
0
0

Spark-Streaming获取kafka数据的两种方式-Receiver与Direct的方式,可以从代码中简单理解成Receiver方式是通过zookeeper来连接kafka队列,Direct方式是直接连接到kafka的节点上获取数据了。


一、基于Receiver的方式
这种方式使用Receiver来获取数据。Receiver是使用Kafka的高层次Consumer API来实现的。receiver从Kafka中获取的数据都是存储在Spark Executor的内存中的,然后Spark Streaming启动的job会去处理那些数据。

然而,在默认的配置下,这种方式可能会因为底层的失败而丢失数据。如果要启用高可靠机制,让数据零丢失,就必须启用Spark Streaming的预写日志机制(Write Ahead Log,WAL)。该机制会同步地将接收到的Kafka数据写入分布式文件系统(比如HDFS)上的预写日志中。所以,即使底层节点出现了失败,也可以使用预写日志中的数据进行恢复。


如何进行Kafka数据源连接
1、在maven添加依赖

<dependency><groupId>org.apache.spark</groupId><artifactId>spark-streaming-kafka_2.10</artifactId><version>1.4.1</version></dependency>

2、scala代码

val kafkaStream = {
  val sparkStreamingConsumerGroup = "spark-streaming-consumer-group"
  val kafkaParams = Map("zookeeper.connect" -> "zookeeper1:2181","group.id" -> "spark-streaming-test","zookeeper.connection.timeout.ms" -> "1000")
  val inputTopic = "input-topic"
  val numPartitionsOfInputTopic = 5
  val streams = (1 to numPartitionsOfInputTopic) map { _ =>
    KafkaUtils.createStream(ssc, kafkaParams, Map(inputTopic -> 1), StorageLevel.MEMORY_ONLY_SER).map(_._2)
  }
  val unifiedStream = ssc.union(streams)
  val sparkProcessingParallelism = 1 // You'd probably pick a higher value than 1 in production.
  unifiedStream.repartition(sparkProcessingParallelism)
}

需要注意的要点
1、Kafka中的topic的partition,与Spark中的RDD的partition是没有关系的。所以,在KafkaUtils.createStream()中,提高partition的数量,只会增加一个Receiver中,读取partition的线程的数量。不会增加Spark处理数据的并行度。
2、可以创建多个Kafka输入DStream,使用不同的consumer group和topic,来通过多个receiver并行接收数据。
3、如果基于容错的文件系统,比如HDFS,启用了预写日志机制,接收到的数据都会被复制一份到预写日志中。因此,在KafkaUtils.createStream()中,设置的持久化级别是StorageLevel.MEMORY_AND_DISK_SER。


二、基于Direct的方式
这种新的不基于Receiver的直接方式,是在Spark 1.3中引入的,从而能够确保更加健壮的机制。替代掉使用Receiver来接收数据后,这种方式会周期性地查询Kafka,来获得每个topic+partition的最新的offset,从而定义每个batch的offset的范围。当处理数据的job启动时,就会使用Kafka的简单consumer api来获取Kafka指定offset范围的数据。


这种方式有如下优点:
1、简化并行读取:如果要读取多个partition,不需要创建多个输入DStream然后对它们进行union操作。Spark会创建跟Kafka partition一样多的RDD partition,并且会并行从Kafka中读取数据。所以在Kafka partition和RDD partition之间,有一个一对一的映射关系。

2、高性能:如果要保证零数据丢失,在基于receiver的方式中,需要开启WAL机制。这种方式其实效率低下,因为数据实际上被复制了两份,Kafka自己本身就有高可靠的机制,会对数据复制一份,而这里又会复制一份到WAL中。而基于direct的方式,不依赖Receiver,不需要开启WAL机制,只要Kafka中作了数据的复制,那么就可以通过Kafka的副本进行恢复。

3、一次且仅一次的事务机制:
基于receiver的方式,是使用Kafka的高阶API来在ZooKeeper中保存消费过的offset的。这是消费Kafka数据的传统方式。这种方式配合着WAL机制可以保证数据零丢失的高可靠性,但是却无法保证数据被处理一次且仅一次,可能会处理两次。因为Spark和ZooKeeper之间可能是不同步的。
基于direct的方式,使用kafka的简单api,Spark Streaming自己就负责追踪消费的offset,并保存在checkpoint中。Spark自己一定是同步的,因此可以保证数据是消费一次且仅消费一次。


scala连接代码

val topics = Set("teststreaming")  
val brokers = "bdc46.hexun.com:9092,bdc53.hexun.com:9092,bdc54.hexun.com:9092"  
val kafkaParams = Map[String, String]("metadata.broker.list" -> brokers, "serializer.class" -> "kafka.serializer.StringEncoder")  
// Create a direct stream  
val kafkaStream = KafkaUtils.createDirectStream[String, String, StringDecoder, StringDecoder](ssc, kafkaParams, topics)   
val events = kafkaStream.flatMap(line => {  
Some(line.toString())  
})



三、总结:两种方式在生产中都有广泛的应用,新api的Direct应该是以后的首选方式。

参考之前的文章,Direct连接kafka的实例:

http://blog.csdn.net/kwu_ganymede/article/details/50160793


Spark2.x学习笔记:11、RDD依赖关系与stage划分 - CSDN博客

$
0
0

11、 RDD依赖关系与stage划分

Spark中RDD的高效与DAG图有着莫大的关系,在DAG调度中需要对计算过程划分stage,而划分依据就是RDD之间的依赖关系。

11.1 窄依赖与宽依赖

针对不同的转换函数,RDD之间的依赖关系分类窄依赖(narrow dependency)和宽依赖(wide dependency, 也称 shuffle dependency)。

(1)窄依赖
窄依赖是指 1个父RDD分区对应1个子RDD的分区。换句话说,一个父RDD的分区对应于一个子RDD的分区,或者多个父RDD的分区对应于一个子RDD的分区。
所以窄依赖又可以分为两种情况,

  • 1个子RDD的分区对应于1个父RDD的分区,比如map、filter、union等算子
  • 1个子RDD的分区对应于N个父RDD的分区,比如co-paritioned join

(2)宽依赖
宽依赖是指 1个父RDD分区对应多个子RDD分区。宽依赖又分为两种情况

  • 1个父RDD对应非全部多个子RDD分区,比如groupByKey、reduceByKey、sortByKey
  • 1个父RDD对应所以子RDD分区,比如未经协同划分的join

这里写图片描述

总结: 如果父RDD分区对应1个子RDD的分区就是窄依赖,否则就是宽依赖。

11.2 为什么Spark将依赖分为窄依赖和宽依赖?

(1) 窄依赖(narrow dependencies)可以支持在同一个集群Executor上,以pipeline管道形式顺序执行多条命令,例如在执行了map后,紧接着执行filter。分区内的计算收敛,不需要依赖所有分区的数据,可以并行地在不同节点进行计算。所以它的失败恢复也更有效,因为它只需要重新计算丢失的parent partition即可

(2)宽依赖(shuffle dependencies) 则需要所有的父分区都是可用的,必须等RDD的parent partition数据全部ready之后才能开始计算,可能还需要调用类似MapReduce之类的操作进行跨节点传递。从失败恢复的角度看,shuffle dependencies 牵涉RDD各级的多个parent partition。

11.3 DAG

RDD之间的依赖关系就形成了DAG(有向无环图)
在Spark作业调度系统中,调度的前提是判断多个作业任务的依赖关系,这些作业任务之间可能存在因果的依赖关系,也就是说有些任务必须先获得执行,然后相关的依赖任务才能执行,但是任务之间显然不应出现任何直接或间接的循环依赖关系,所以本质上这种关系适合用DAG表示

11.4 stage划分

由于shuffle依赖必须等RDD的父RDD分区数据全部可读之后才能开始计算,因此spark的设计是让父 RDD将结果写在本地,完全写完之后,通知后面的RDD。后面的RDD则首先去读之前RDD的本地数据作为输入,然后进行运算。
由于上述特性,将shuffle依赖就必须分为两个阶段(stage)去做:

  • (1)第1个阶段(stage)需要把结果shuffle到本地,例如reduceByKey,首先要聚合某个key的所有记录,才能进行下一步的reduce计算,这个汇聚的过程就是shuffle。
  • (2)第2个阶段(stage)则读入数据进行处理。

为什么要写在本地?
后面的RDD多个分区都要去读这个信息,如果放到内存,如果出现数据丢失,后面的所有步骤全部不能进行,违背了之前所说的需要父RDD分区数据全部ready的原则。

同一个stage里面的task是可以并发执行的,下一个stage要等前一个stage ready(和mapreduce的reduce需要等map过程ready 一脉相承)。

Spark 将任务以 shuffle 依赖(宽依赖)为边界打散,划分多个 Stage. 最后的结果阶段叫做 ResultStage, 其它阶段叫 ShuffleMapStage, 从后往前推导,依将计算。

这里写图片描述
1.从后往前推理,遇到宽依赖就断开,遇到窄依赖就把当前RDD加入到该Stage
2.每个Stage里面Task的数量是由该Stage中最后一个RDD的Partition的数量所决定的。
3.最后一个Stage里面的任务类型是ResultTask,前面其他所有的Stage的任务类型是ShuffleMapTask。
4.代表当前Stage的算子一定是该Stage的最后一个计算步骤

表面上看是数据在流动,实质上是算子在流动
(1)数据不动代码动
(2)在一个Stage内部算子为何会流动(Pipeline)?首先是算子合并,也就是所谓的函数式编程的执行的时候最终进行函数的展开从而把一个Stage内部的多个算子合并成为一个大算子(其内部包含了当前Stage中所有算子对数据的计算逻辑);其次,是由于Transformation操作的Lazy特性!在具体算子交给集群的Executor计算之前首先会通过Spark Framework(DAGScheduler)进行算子的优化(基于数据本地性的Pipeline)。

11.5 Spark计算引擎原理

  • 通过RDD,创建DAG(逻辑计划)
  • 为DAG生成物理查询计划
  • 调度并执行Task
  • 分布式执行Task

这里写图片描述

Spring Boot 使用Spring security 集成CAS - CSDN博客

$
0
0

1.创建工程

      创建Maven工程:springboot-security-cas

2.加入依赖

      创建工程后,打开pom.xml,在pom.xml中加入以下内容:
[html]  view plain  copy
  1. <parent>  
  2.         <groupId>org.springframework.boot</groupId>  
  3.         <artifactId>spring-boot-starter-parent</artifactId>  
  4.         <version>1.4.3.RELEASE</version>  
  5.     </parent>  
  6.     <properties>  
  7.         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>  
  8.         <java.version>1.8</java.version>  
  9.     </properties>  
  10.     <dependencies>  
  11.         <dependency>  
  12.             <groupId>org.springframework.boot</groupId>  
  13.             <artifactId>spring-boot-starter</artifactId>  
  14.         </dependency>  
  15.         <dependency>  
  16.             <groupId>org.springframework.boot</groupId>  
  17.             <artifactId>spring-boot-starter-web</artifactId>  
  18.         </dependency>  
  19.         <!-- security starter Poms -->  
  20.         <dependency>  
  21.             <groupId>org.springframework.boot</groupId>  
  22.             <artifactId>spring-boot-starter-security</artifactId>  
  23.         </dependency>  
  24.         <!-- security 对CAS支持 -->  
  25.         <dependency>  
  26.             <groupId>org.springframework.security</groupId>  
  27.             <artifactId>spring-security-cas</artifactId>  
  28.         </dependency>  
  29.         <!-- security taglibs -->  
  30.         <dependency>  
  31.             <groupId>org.springframework.security</groupId>  
  32.             <artifactId>spring-security-taglibs</artifactId>  
  33.         </dependency>  
  34.         <!-- 热加载 -->  
  35.         <dependency>  
  36.             <groupId>org.springframework.boot</groupId>  
  37.             <artifactId>spring-boot-devtools</artifactId>  
  38.             <optional>true</optional>  
  39.         </dependency>  
  40.         <dependency>  
  41.             <groupId>org.springframework.boot</groupId>  
  42.             <artifactId>spring-boot-configuration-processor</artifactId>  
  43.             <optional>true</optional>  
  44.         </dependency>  
  45.     </dependencies>  
  46.     <build>  
  47.         <plugins>  
  48.             <plugin>  
  49.                 <groupId>org.springframework.boot</groupId>  
  50.                 <artifactId>spring-boot-maven-plugin</artifactId>  
  51.             </plugin>  
  52.         </plugins>  
  53.     </build>  

3.创建application.properties

      创建application.properties文件,加入以下内容:
[plain]  view plain  copy
  1. #CAS服务地址  
  2. cas.server.host.url=http://localhost:8081/cas  
  3. #CAS服务登录地址  
  4. cas.server.host.login_url=${cas.server.host.url}/login  
  5. #CAS服务登出地址  
  6. cas.server.host.logout_url=${cas.server.host.url}/logout?service=${app.server.host.url}  
  7. #应用访问地址  
  8. app.server.host.url=http://localhost:8080  
  9. #应用登录地址  
  10. app.login.url=/login  
  11. #应用登出地址  
  12. app.logout.url=/logout  

4.创建入口启动类(MainConfig)

      创建入口启动类MainConfig,完整代码如下:
[java]  view plain  copy
  1. package com.chengli.springboot;  
  2.   
  3. import org.springframework.boot.SpringApplication;  
  4. import org.springframework.boot.autoconfigure.SpringBootApplication;  
  5. import org.springframework.security.access.prepost.PreAuthorize;  
  6. import org.springframework.web.bind.annotation.RequestMapping;  
  7. import org.springframework.web.bind.annotation.RestController;  
  8.   
  9. @RestController  
  10. @SpringBootApplication  
  11. public class MainConfig {  
  12.     public static void main(String[] args) {  
  13.         SpringApplication.run(MainConfig.class, args);  
  14.     }  
  15.   
  16.     @RequestMapping("/")  
  17.     public String index() {  
  18.         return "访问了首页哦";  
  19.     }  
  20.   
  21.     @RequestMapping("/hello")  
  22.     public String hello() {  
  23.         return "不验证哦";  
  24.     }  
  25.   
  26.     @PreAuthorize("hasAuthority('TEST')")//有TEST权限的才能访问  
  27.     @RequestMapping("/security")  
  28.     public String security() {  
  29.         return "hello world security";  
  30.     }  
  31.   
  32.     @PreAuthorize("hasAuthority('ADMIN')")//必须要有ADMIN权限的才能访问  
  33.     @RequestMapping("/authorize")  
  34.     public String authorize() {  
  35.         return "有权限访问";  
  36.     }  
  37.       
  38.     /**这里注意的是,TEST与ADMIN只是权限编码,可以自己定义一套规则,根据实际情况即可*/  
  39. }  

5.创建Security配置类(SecurityConfig)

      创建Security配置类SecurityConfig,完整代码如下:
[java]  view plain  copy
  1. package com.chengli.springboot.security;  
  2.   
  3. import org.jasig.cas.client.session.SingleSignOutFilter;  
  4. import org.jasig.cas.client.validation.Cas20ServiceTicketValidator;  
  5. import org.springframework.beans.factory.annotation.Autowired;  
  6. import org.springframework.context.annotation.Bean;  
  7. import org.springframework.context.annotation.Configuration;  
  8. import org.springframework.security.cas.ServiceProperties;  
  9. import org.springframework.security.cas.authentication.CasAssertionAuthenticationToken;  
  10. import org.springframework.security.cas.authentication.CasAuthenticationProvider;  
  11. import org.springframework.security.cas.web.CasAuthenticationEntryPoint;  
  12. import org.springframework.security.cas.web.CasAuthenticationFilter;  
  13. import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;  
  14. import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;  
  15. import org.springframework.security.config.annotation.web.builders.HttpSecurity;  
  16. import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;  
  17. import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;  
  18. import org.springframework.security.core.userdetails.AuthenticationUserDetailsService;  
  19. import org.springframework.security.web.authentication.logout.LogoutFilter;  
  20. import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;  
  21.   
  22. import com.chengli.springboot.custom.CustomUserDetailsService;  
  23. import com.chengli.springboot.properties.CasProperties;  
  24.   
  25. @Configuration  
  26. @EnableWebSecurity //启用web权限  
  27. @EnableGlobalMethodSecurity(prePostEnabled = true) //启用方法验证  
  28. public class SecurityConfig extends WebSecurityConfigurerAdapter {  
  29.     @Autowired  
  30.     private CasProperties casProperties;  
  31.       
  32.     /**定义认证用户信息获取来源,密码校验规则等*/  
  33.     @Override  
  34.     protected void configure(AuthenticationManagerBuilder auth) throws Exception {  
  35.         super.configure(auth);  
  36.         auth.authenticationProvider(casAuthenticationProvider());  
  37.         //inMemoryAuthentication 从内存中获取  
  38.         //auth.inMemoryAuthentication().withUser("chengli").password("123456").roles("USER")  
  39.         //.and().withUser("admin").password("123456").roles("ADMIN");  
  40.           
  41.         //jdbcAuthentication从数据库中获取,但是默认是以security提供的表结构  
  42.         //usersByUsernameQuery 指定查询用户SQL  
  43.         //authoritiesByUsernameQuery 指定查询权限SQL  
  44.         //auth.jdbcAuthentication().dataSource(dataSource).usersByUsernameQuery(query).authoritiesByUsernameQuery(query);  
  45.           
  46.         //注入userDetailsService,需要实现userDetailsService接口  
  47.         //auth.userDetailsService(userDetailsService);  
  48.     }  
  49.       
  50.     /**定义安全策略*/  
  51.     @Override  
  52.     protected void configure(HttpSecurity http) throws Exception {  
  53.         http.authorizeRequests()//配置安全策略  
  54.             //.antMatchers("/","/hello").permitAll()//定义/请求不需要验证  
  55.             .anyRequest().authenticated()//其余的所有请求都需要验证  
  56.             .and()  
  57.         .logout()  
  58.             .permitAll()//定义logout不需要验证  
  59.             .and()  
  60.         .formLogin();//使用form表单登录  
  61.           
  62.         http.exceptionHandling().authenticationEntryPoint(casAuthenticationEntryPoint())  
  63.             .and()  
  64.             .addFilter(casAuthenticationFilter())  
  65.             .addFilterBefore(casLogoutFilter(), LogoutFilter.class)  
  66.             .addFilterBefore(singleSignOutFilter(), CasAuthenticationFilter.class);  
  67.           
  68.         //http.csrf().disable(); //禁用CSRF  
  69.     }  
  70.       
  71.     /**认证的入口*/  
  72.     @Bean  
  73.     public CasAuthenticationEntryPoint casAuthenticationEntryPoint() {  
  74.         CasAuthenticationEntryPoint casAuthenticationEntryPoint = new CasAuthenticationEntryPoint();  
  75.         casAuthenticationEntryPoint.setLoginUrl(casProperties.getCasServerLoginUrl());  
  76.         casAuthenticationEntryPoint.setServiceProperties(serviceProperties());  
  77.         return casAuthenticationEntryPoint;  
  78.     }  
  79.       
  80.     /**指定service相关信息*/  
  81.     @Bean  
  82.     public ServiceProperties serviceProperties() {  
  83.         ServiceProperties serviceProperties = new ServiceProperties();  
  84.         serviceProperties.setService(casProperties.getAppServerUrl() + casProperties.getAppLoginUrl());  
  85.         serviceProperties.setAuthenticateAllArtifacts(true);  
  86.         return serviceProperties;  
  87.     }  
  88.       
  89.     /**CAS认证过滤器*/  
  90.     @Bean  
  91.     public CasAuthenticationFilter casAuthenticationFilter() throws Exception {  
  92.         CasAuthenticationFilter casAuthenticationFilter = new CasAuthenticationFilter();  
  93.         casAuthenticationFilter.setAuthenticationManager(authenticationManager());  
  94.         casAuthenticationFilter.setFilterProcessesUrl(casProperties.getAppLoginUrl());  
  95.         return casAuthenticationFilter;  
  96.     }  
  97.       
  98.     /**cas 认证 Provider*/  
  99.     @Bean  
  100.     public CasAuthenticationProvider casAuthenticationProvider() {  
  101.         CasAuthenticationProvider casAuthenticationProvider = new CasAuthenticationProvider();  
  102.         casAuthenticationProvider.setAuthenticationUserDetailsService(customUserDetailsService());  
  103.         //casAuthenticationProvider.setUserDetailsService(customUserDetailsService()); //这里只是接口类型,实现的接口不一样,都可以的。  
  104.         casAuthenticationProvider.setServiceProperties(serviceProperties());  
  105.         casAuthenticationProvider.setTicketValidator(cas20ServiceTicketValidator());  
  106.         casAuthenticationProvider.setKey("casAuthenticationProviderKey");  
  107.         return casAuthenticationProvider;  
  108.     }  
  109.       
  110.     /*@Bean 
  111.     public UserDetailsService customUserDetailsService(){ 
  112.         return new CustomUserDetailsService(); 
  113.     }*/  
  114.       
  115.     /**用户自定义的AuthenticationUserDetailsService*/  
  116.     @Bean  
  117.     public AuthenticationUserDetailsService<CasAssertionAuthenticationToken> customUserDetailsService(){  
  118.         return new CustomUserDetailsService();  
  119.     }  
  120.       
  121.     @Bean  
  122.     public Cas20ServiceTicketValidator cas20ServiceTicketValidator() {  
  123.         return new Cas20ServiceTicketValidator(casProperties.getCasServerUrl());  
  124.     }  
  125.       
  126.     /**单点登出过滤器*/  
  127.     @Bean  
  128.     public SingleSignOutFilter singleSignOutFilter() {  
  129.         SingleSignOutFilter singleSignOutFilter = new SingleSignOutFilter();  
  130.         singleSignOutFilter.setCasServerUrlPrefix(casProperties.getCasServerUrl());  
  131.         singleSignOutFilter.setIgnoreInitConfiguration(true);  
  132.         return singleSignOutFilter;  
  133.     }  
  134.       
  135.     /**请求单点退出过滤器*/  
  136.     @Bean  
  137.     public LogoutFilter casLogoutFilter() {  
  138.         LogoutFilter logoutFilter = new LogoutFilter(casProperties.getCasServerLogoutUrl(), new SecurityContextLogoutHandler());  
  139.         logoutFilter.setFilterProcessesUrl(casProperties.getAppLogoutUrl());  
  140.         return logoutFilter;  
  141.     }  
  142. }  

6.用户自定义类

      (1)定义CasProperties,用于将properties文件指定的内容注入以方便使用,这里不注入也是可以的,可以获取Spring 当前的环境,代码如下:
[java]  view plain  copy
  1. package com.chengli.springboot.properties;  
  2.   
  3. import org.springframework.beans.factory.annotation.Value;  
  4. import org.springframework.stereotype.Component;  
  5.   
  6. /** 
  7.  * CAS的配置参数 
  8.  * @author ChengLi 
  9.  */  
  10. @Component  
  11. public class CasProperties {  
  12.     @Value("${cas.server.host.url}")  
  13.     private String casServerUrl;  
  14.   
  15.     @Value("${cas.server.host.login_url}")  
  16.     private String casServerLoginUrl;  
  17.   
  18.     @Value("${cas.server.host.logout_url}")  
  19.     private String casServerLogoutUrl;  
  20.   
  21.     @Value("${app.server.host.url}")  
  22.     private String appServerUrl;  
  23.   
  24.     @Value("${app.login.url}")  
  25.     private String appLoginUrl;  
  26.   
  27.     @Value("${app.logout.url}")  
  28.     private String appLogoutUrl;  
  29. ......省略 getters setters 方法  
  30. }  

      (2)定义CustomUserDetailsService类,代码如下:
[java]  view plain  copy
  1. package com.chengli.springboot.custom;  
  2.   
  3. import java.util.HashSet;  
  4. import java.util.Set;  
  5.   
  6. import org.springframework.security.cas.authentication.CasAssertionAuthenticationToken;  
  7. import org.springframework.security.core.userdetails.AuthenticationUserDetailsService;  
  8. import org.springframework.security.core.userdetails.UserDetails;  
  9. import org.springframework.security.core.userdetails.UsernameNotFoundException;  
  10.   
  11. /** 
  12.  * 用于加载用户信息 实现UserDetailsService接口,或者实现AuthenticationUserDetailsService接口 
  13.  * @author ChengLi 
  14.  * 
  15.  */  
  16. public class CustomUserDetailsService /* 
  17.     //实现UserDetailsService接口,实现loadUserByUsername方法 
  18.     implements UserDetailsService { 
  19.     @Override 
  20.     public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { 
  21.         System.out.println("当前的用户名是:"+username); 
  22.         //这里我为了方便,就直接返回一个用户信息,实际当中这里修改为查询数据库或者调用服务什么的来获取用户信息 
  23.         UserInfo userInfo = new UserInfo(); 
  24.         userInfo.setUsername("admin"); 
  25.         userInfo.setName("admin"); 
  26.         Set<AuthorityInfo> authorities = new HashSet<AuthorityInfo>(); 
  27.         AuthorityInfo authorityInfo = new AuthorityInfo("TEST"); 
  28.         authorities.add(authorityInfo); 
  29.         userInfo.setAuthorities(authorities); 
  30.         return userInfo; 
  31.     }*/  
  32.       
  33.       
  34.     //实现AuthenticationUserDetailsService,实现loadUserDetails方法  
  35.     implements AuthenticationUserDetailsService<CasAssertionAuthenticationToken> {  
  36.   
  37.     @Override  
  38.     public UserDetails loadUserDetails(CasAssertionAuthenticationToken token) throws UsernameNotFoundException {  
  39.         System.out.println("当前的用户名是:"+token.getName());  
  40.         /*这里我为了方便,就直接返回一个用户信息,实际当中这里修改为查询数据库或者调用服务什么的来获取用户信息*/  
  41.         UserInfo userInfo = new UserInfo();  
  42.         userInfo.setUsername("admin");  
  43.         userInfo.setName("admin");  
  44.         Set<AuthorityInfo> authorities = new HashSet<AuthorityInfo>();  
  45.         AuthorityInfo authorityInfo = new AuthorityInfo("TEST");  
  46.         authorities.add(authorityInfo);  
  47.         userInfo.setAuthorities(authorities);  
  48.         return userInfo;  
  49.     }  
  50.   
  51. }  

      (3)定义AuthorityInfo类,用于加载当前登录用户的权限信息,实现GrantedAuthority接口,代码如下:
[java]  view plain  copy
  1. package com.chengli.springboot.custom;  
  2.   
  3. import org.springframework.security.core.GrantedAuthority;  
  4.   
  5. /** 
  6.  * 权限信息 
  7.  *  
  8.  * @author ChengLi 
  9.  * 
  10.  */  
  11. public class AuthorityInfo implements GrantedAuthority {  
  12.     private static final long serialVersionUID = -175781100474818800L;  
  13.   
  14.     /** 
  15.      * 权限CODE 
  16.      */  
  17.     private String authority;  
  18.   
  19.     public AuthorityInfo(String authority) {  
  20.         this.authority = authority;  
  21.     }  
  22.   
  23.     @Override  
  24.     public String getAuthority() {  
  25.         return authority;  
  26.     }  
  27.   
  28.     public void setAuthority(String authority) {  
  29.         this.authority = authority;  
  30.     }  
  31.   
  32. }  

      (4)定义UserInfo类,用于加载当前用户信息,实现UserDetails接口,代码如下:
[java]  view plain  copy
  1. package com.chengli.springboot.custom;  
  2.   
  3. import java.util.Collection;  
  4. import java.util.HashSet;  
  5. import java.util.Set;  
  6.   
  7. import org.springframework.security.core.GrantedAuthority;  
  8. import org.springframework.security.core.userdetails.UserDetails;  
  9.   
  10. /** 
  11.  * 用户信息 
  12.  * @、这里我写了几个较为常用的字段,id,name,username,password,可以根据实际的情况自己增加 
  13.  * @author ChengLi 
  14.  * 
  15.  */  
  16. public class UserInfo implements UserDetails {  
  17.     private static final long serialVersionUID = -1041327031937199938L;  
  18.   
  19.     /** 
  20.      * 用户ID 
  21.      */  
  22.     private Long id;  
  23.   
  24.     /** 
  25.      * 用户名称 
  26.      */  
  27.     private String name;  
  28.   
  29.     /** 
  30.      * 登录名称 
  31.      */  
  32.     private String username;  
  33.   
  34.     /** 
  35.      * 登录密码 
  36.      */  
  37.     private String password;  
  38.   
  39.     private boolean isAccountNonExpired = true;  
  40.   
  41.     private boolean isAccountNonLocked = true;  
  42.   
  43.     private boolean isCredentialsNonExpired = true;  
  44.   
  45.     private boolean isEnabled = true;  
  46.   
  47.     private Set<AuthorityInfo> authorities = new HashSet<AuthorityInfo>();  
  48. ....省略getters setters 方法  
  49. }  

到这里基本就已经完成了,运行CAS Server ,将以上的application.properties文件中的地址修改为实际的地址即可运行。

参考:
https://github.com/xuanbo/spring-boot-cas-client

数据仓库数据质量管理【转】 - CSDN博客

$
0
0

 一个完善的数据仓库必须含有一个完整的 数据质量管理系统元数据管理系统,但是目前国内的数据仓库对数据质量管理这块都不是那么重视,我个人觉得这是一个很大的误区,一个数据仓库如果连数据质量都无法保证,还如何基于做出有效的分析来给决策者做决策的依据?
       从个人理解的角度看,数据质量管理系统应该包含 数据质量检测、脏数据的处理与修正这两块。对于数据质量检测这块,又分为 物理数据监控与逻辑数据监控。我个人理解的物理数据监控是指纯粹从技术上保证数据的有效性,与业务无关,而逻辑数据监控则是从业务逻辑上,对原始指标以及最终产出的业务指标之间的逻辑平衡性监控。通过这些监控,能让底层ETL技术人员第一时间发现数据问题并且解决问题,同时也能根据这些监控提前知道可能产生的结果,为后续产生的业务分析报告作出进一步的修正,从而保证数据仓库的数据的是有效的是能真正反应事实的。 对于脏数据的处理与修正这块则是与流程机制有关系,比如产生了某种类型的脏数据第一时间应该如何处理,是reject还是保存起来发给相应的业务人员来手工check然后再回写到数据仓库。
       在数据质量检测这块,对于 物理数据监测,通过包含有如下所述的几个方面。其一,数据格式的合法性。比如日期型数据在原系统是字符类型的,进入dw后就需要做日期格式的验证,验证是否是有效的日期格式;其二,数据值域的有效性。比如一些维度代码是否有出现维表以外的代码值;其三,空值或者空格的合理性。比如一些重要的不可为空的字段对应映射的源数据字段是否有空值或者空格;其四,主键的有效性。比如对源表业务上已经明确了是主键的字段是否真的唯一;其五,乱码检测。比如数据中是否含有乱码;其六,对于reject的记录的统计。比如是否有超长的数据、是否有不合乎格式的数据出现等;其七,记录数的平衡。比如从源系统抽取了n条记录,最终入库的是否也是n条,进入dw之后是否也是n条;
       对于逻辑数据的监控则与业务含义直接挂钩了,主要体现在 不同表按照不同的力度描述了相同的指标最终这些指标之间的均衡性的检测,比如网站中的session表含有每个session对应的pv,而path表则含有所有session在每一步的明细,那么逻辑上session表的pv的总和就应该与path表的总记录数是相等的,这就是一种业务指标上的均衡
       上述说了那么多,其实还有许多可以补充的地方,目前我只想到这些,后续再继续增加,总之,一个完备的数据质量管理系统是数据仓库必不可少的部分, 只有数据质量管理系统做好了,才能提高数据仓库数据的可用性,同时也提高数据仓库的数据产生的价值.

有些问题还请指点一二:
关于“记录数的平衡”,如果做了去重,关联,过滤这些操作,怎么保证进入仓库的记录数是否正确?

记录数的平衡,我们在项目中是这样做的,对于因为质量问题被丢弃的记录,在后台设置专门的的文件或者数据库表进行存放,在前台提供检索界面, 如果是来源业务系统的问题,反馈,促使来源业务系统进行修正,我们还据此发现业务系统的业务处理逻辑上有小BUG。 其实后线分析系统与业务系统应该是一个双向交互的关系,不仅仅反映在数据质量上,在信息上也是,这样的分析系统才是强大的。

 

源地址: http://bbs.dwway.com/thread-29340-1-1.html

MySQL多数据源笔记2-Spring多数据源一主多从读写分离(手写) - 狂小白 - 博客园

$
0
0

一.为什么要进行读写分离呢?

  因为数据库的“写操作”操作是比较耗时的(写上万条条数据到Mysql可能要1分钟分钟)。但是数据库的“读操作”却比“写操作”耗时要少的多(从Mysql读几万条数据条数据可能只要十秒钟)。

所以读写分离解决的是,数据库的“写操作”影响了查询的效率问题。

如下图所示:

 

 

读写分离: 大多数站点的数据库读操作比写操作更加密集,而且查询条件相对复杂,数据库的大部分性能消耗在查询操作上了。为保证数据库数据的一致性,我们要求所有对于数据库的更新操作都是针对主数据库的,但是读操作是可以针对从数据库来进行。

如下图所示:

 

 

以下进行一个代码层面的自动切换数据源进行读写分离的例子。

 第一。首先搭建一个SSM框架的web工程。省略。

jdb.properties配置如下:

复制代码
#主数据库连接
jdbc_url_m=jdbc:mysql://localhost:3306/mama-bike?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull
#两个从数据库连接
jdbc_url_s_1=jdbc:mysql://localhost:3307/mama-bike?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull
jdbc_url_s_2=jdbc:mysql://localhost:3308/mama-bike?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull
jdbc_username=root
jdbc_password=root
复制代码

 

web.xml配置省略

 

 

第二。spring-cfg.xml文件中配置一个主数据源,两个从数据源,具体配置如下:

复制代码
<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context-4.0.xsd
    http://www.springframework.org/schema/tx
    http://www.springframework.org/schema/tx/spring-tx-4.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"><!--扫描注解生成bean--><context:annotation-config/><!--包扫描--><context:component-scan base-package="com.coder520"/><context:property-placeholder location="classpath:jdbc.properties"/><bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"><property name="dataSource" ref="dataSource"/><property name="mapperLocations" value="classpath:com/coder520/**/**.xml"/></bean><bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"><property name="basePackage" value="com.coder520.*.dao"/><property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/></bean><!--声明事务管理 采用注解方式--><tx:annotation-driven transaction-manager="transactionManager"/><bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource" ref="dataSource"/></bean><!--开启切面代理--><aop:aspectj-autoproxy/><!--切换数据源切面--><bean id="switchDataSourceAspect" class="com.coder520.common.DataSourceAspect"/><!--切面配置--><aop:config><aop:aspect ref="switchDataSourceAspect"><aop:pointcut id="tx" expression="execution(* com.coder520.*.service.*.*(..))"/><aop:before method="before" pointcut-ref="tx"/></aop:aspect></aop:config><!--主数据库设置--><bean id="masterdataSource" class="com.alibaba.druid.pool.DruidDataSource"
          destroy-method="close" init-method="init"><property name="url" value="${jdbc_url_m}"/><property name="username" value="${jdbc_username}"/><property name="password" value="${jdbc_password}"/></bean><!--从数据库设置--><bean id="slavedataSource_1" class="com.alibaba.druid.pool.DruidDataSource"
          destroy-method="close" init-method="init"><property name="url" value="${jdbc_url_s_1}"/><property name="username" value="${jdbc_username}"/><property name="password" value="${jdbc_password}"/></bean><!--从数据库设置--><bean id="slavedataSource_2" class="com.alibaba.druid.pool.DruidDataSource"
          destroy-method="close" init-method="init"><property name="url" value="${jdbc_url_s_2}"/><property name="username" value="${jdbc_username}"/><property name="password" value="${jdbc_password}"/></bean><bean id="dataSource" class="com.coder520.common.DynamicDataSource"><property name="targetDataSources"><map><entry key="master" value-ref="masterdataSource"/><entry key="slave_1" value-ref="slavedataSource_1"/><entry key="slave_2" value-ref="slavedataSource_2"/></map></property><!--默认数据源为主数据库--><property name="defaultTargetDataSource" ref="masterdataSource"/></bean></beans>
复制代码

 

spring-mvc.xml配置如下:

 

  

复制代码
<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
      http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
      http://www.springframework.org/schema/context
      http://www.springframework.org/schema/context/spring-context.xsd
      http://www.springframework.org/schema/aop
      http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
      http://www.springframework.org/schema/mvc
      http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd"><!--开启切面编程自动代理--><mvc:annotation-driven><mvc:message-converters><bean class="org.springframework.http.converter.StringHttpMessageConverter"/><bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter"><property name="supportedMediaTypes"><list><value>text/html;charset=UTF-8</value><value>application/json;charset=UTF-8</value></list></property></bean></mvc:message-converters></mvc:annotation-driven><!--包扫描--><context:component-scan base-package="com.coder520.*.controller"></context:component-scan><!--开启注解扫描--><mvc:annotation-driven/><!--处理静态资源--><mvc:default-servlet-handler/><bean id="velocityConfigurer" class="org.springframework.web.servlet.view.velocity.VelocityConfigurer"><property name="resourceLoaderPath" value="/WEB-INF/views"/><property name="velocityProperties"><props><prop key="input.encoding">utf-8</prop><prop key="output.encoding">utf-8</prop><prop key="file.resource.loader.cache">false</prop><prop key="file.resource.loader.modificationCheckInterval">1</prop><prop key="velocimacro.library.autoreload">false</prop></props></property></bean><bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver"><property name="exceptionMappings"><props><prop key="org.apache.shiro.authz.UnauthorizedException">403</prop></props></property></bean><bean class="org.springframework.web.servlet.view.velocity.VelocityViewResolver"><property name="suffix" value=".vm"/><property name="contentType" value="text/html;charset=utf-8"/><property name="dateToolAttribute" value="date"/><!--日期函数名称--></bean></beans>
复制代码

 

 Spring提供了一个AbstractRoutingDataSource这个类来帮我们切换数据源。故名思意,Routing,是路由的意思,可以帮我们切换到我们想切换到的数据库。因此我们需要自己创建一个类来继承它。

我们再进入看一下AbstractRoutingDataSource源码是如何实现。

里面的方法到底干嘛用的,都在源码里面写明注释,并且标记执行顺序。如下://

复制代码
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.springframework.jdbc.datasource.lookup;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import javax.sql.DataSource;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.jdbc.datasource.AbstractDataSource;
import org.springframework.util.Assert;

public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {   
  //装载spring-cfg.xml中配置的那三个数据源。 private Map<Object, Object> targetDataSources;
  //默认数据源 private Object defaultTargetDataSource;
  //出错回滚 private boolean lenientFallback = true;
  //Map中各个数据源对应的key private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
  //装载Map<Object,Object> targetDataSources,即一个MAP装载一个旧MAP private Map<Object, DataSource> resolvedDataSources;
  //这属性是为了得到defaultTargetDataSource, private DataSource resolvedDefaultDataSource; public AbstractRoutingDataSource() { }
   //1.装载spring-cfg.xml中配置的那三个数据源 public void setTargetDataSources(Map<Object, Object> targetDataSources) { this.targetDataSources = targetDataSources; }    //1.设置默认数据源 public void setDefaultTargetDataSource(Object defaultTargetDataSource) { this.defaultTargetDataSource = defaultTargetDataSource; }   
public void setLenientFallback(boolean lenientFallback) { this.lenientFallback = lenientFallback; }
public void setDataSourceLookup(DataSourceLookup dataSourceLookup) { this.dataSourceLookup = (DataSourceLookup)(dataSourceLookup != null?dataSourceLookup:new JndiDataSourceLookup()); }
  // 2.根据spring-cfg.xml中配置targetDataSources可以在afterPropertiesSet方法中对targetDataSources进行解析,获取真正的datasources public void afterPropertiesSet() { if(this.targetDataSources == null) { throw new IllegalArgumentException("Property 'targetDataSources' is required"); } else {
       //新建一个跟MAP targetDataSource一样的MAP this.resolvedDataSources = new HashMap(this.targetDataSources.size());
        //遍历MAP Iterator var1 = this.targetDataSources.entrySet().iterator();         //判断MAP中是否还有数据源 while(var1.hasNext()) {
          //获取数据源Entry Entry<Object, Object> entry = (Entry)var1.next();
          //设置每一个数据源Entry对应的key Object lookupKey = this.resolveSpecifiedLookupKey(entry.getKey());
          //设置数据源Entry对应的value,即数据源 DataSource dataSource = this.resolveSpecifiedDataSource(entry.getValue());
          //放入到新建的MAP中 this.resolvedDataSources.put(lookupKey, dataSource); }         //设置默认数据源 if(this.defaultTargetDataSource != null) { this.resolvedDefaultDataSource = this.resolveSpecifiedDataSource(this.defaultTargetDataSource); } } } protected Object resolveSpecifiedLookupKey(Object lookupKey) { return lookupKey; } protected DataSource resolveSpecifiedDataSource(Object dataSource) throws IllegalArgumentException { if(dataSource instanceof DataSource) { return (DataSource)dataSource; } else if(dataSource instanceof String) { return this.dataSourceLookup.getDataSource((String)dataSource); } else { throw new IllegalArgumentException("Illegal data source value - only [javax.sql.DataSource] and String supported: " + dataSource); } } public Connection getConnection() throws SQLException { return this.determineTargetDataSource().getConnection(); } public Connection getConnection(String username, String password) throws SQLException { return this.determineTargetDataSource().getConnection(username, password); } public <T> T unwrap(Class<T> iface) throws SQLException { return iface.isInstance(this)?this:this.determineTargetDataSource().unwrap(iface); } public boolean isWrapperFor(Class<?> iface) throws SQLException { return iface.isInstance(this) || this.determineTargetDataSource().isWrapperFor(iface); }
  //3.最关键的一个方法。此方法决定选择哪一个数据源 protected DataSource determineTargetDataSource() { Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
    //决定选择数据源的key,即传进来的那个数据源 Object lookupKey = this.determineCurrentLookupKey();
    //获取相应的数据源 DataSource dataSource = (DataSource)this.resolvedDataSources.get(lookupKey);
    //如果为空,就用默认的那个数据源 if(dataSource == null && (this.lenientFallback || lookupKey == null)) { dataSource = this.resolvedDefaultDataSource; }     //如果默认数据源还是为空,证明没配置默认数据源,就会抛异常 if(dataSource == null) { throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]"); } else { return dataSource; } }   //这是最重要的方法,要我们实现改方法的。 protected abstract Object determineCurrentLookupKey(); }
复制代码

 

因此实现该determineCurrentLookupKey()方法:首先自己创建的类要继承AbstractRoutingDataSource


如下代码
复制代码
package com.coder520.common;   

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

/**
* Created by cong on 2018/3/14.
*/
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceHolder.getDataSource();
}
}
复制代码
DynamicDataSourceHolder.getDataSource()是获取数据源。但是呢,spring中的数据源是唯一,每一个用户过来都是共用这个数据源的。我们知道高并发的情况下,多个用户共享一个资源,这是有线程问题的,这样获取数据源是不安全的。

因此我们要用到并发编程问题呢,我们要用到并发编程里面的一个类ThreadLocal这个类,这个类用来ThreadLocal类用来提供线程内部的局部变量。这种变量在多线程环境下访问(通过get或set方法访问)时能保证各个线程里的变量相对独立于其他线程内的变量。
ThreadLocal实例通常来说都是private static类型的,用于关联线程和线程的上下文。

那么我们在两个从库中进行读操作如何公平的分配来读操作呢?我们自然想到要有轮询的思维。通过一个计时器来自增求模运算。这个计时器的只从-1开始,这样得到的结果就只有0和1了,根据0 和 1来分配两个从库进行读操作。
注意这个计时器如果用Inter类型的话,必然会出现线程安全问题的,因为这是共享的数据类型。因此我们可以用并发编程里面的AtomicInterger原子属性的类。解决线程安全问题。我们知道Integer是有范围的,我们不能让
这个计数器一直自增,这样下去会去问题的。因此还需要来一个计数器重置。

DynamicDataSourceHolder类代码如下:
复制代码
package com.coder520.common;

import java.util.concurrent.atomic.AtomicInteger;

/**
 * Created by cong on 2018/3/14.
 */
public class DynamicDataSourceHolder {

    //绑定本地线程
    public static final ThreadLocal<String> holder = new ThreadLocal<>();

    //计数器
    private static AtomicInteger counter = new AtomicInteger(-1);


    //写库对应的数据源Key
    private static final String MASTER = "master";

    //从库对应的数据源key
    private static final String SLAVE_1 = "slave_1";
    private static final String SLAVE_2 = "slave_2";

    //设置数据源,判断传进来的主库还是从库的类型
    public static void setDataSource(DataSourceType dataSourceType){
        if (dataSourceType == DataSourceType.MASTER){
            System.out.println("-----MASTER------");
            holder.set(MASTER);
        }else if (dataSourceType == DataSourceType.SLAVE){
            holder.set(roundRobinSlaveKey());
        }
    }
    //获取数据源
    public static String getDataSource(){
        return holder.get();
    }

    
    //轮询选择哪一个从数据库去读操作
    private static String roundRobinSlaveKey() {
        //计数器模运算
        Integer index = counter.getAndIncrement() % 2;
        //计数器重置
        if (counter.get()>9999){
            counter.set(-1);
        }
        //轮询判断
        if (index == 0){
            System.out.println("----SLAVE_1-----");
            return SLAVE_1;
        }else {
            System.out.println("----SLAVE_2-----");
            return SLAVE_2;
        }

    }

}
复制代码

 

DataSourceType是一个枚举类型,这些这样写是让代码美观一些。

DataSourceType枚举类型代码如下:

复制代码
package com.coder520.common;

/**
 */
public enum DataSourceType {

    MASTER,SLAVE;

}
复制代码

 

到这里已经万事具备了,到了关键一步了,那么我们什么时候切换数据源呢?我怎么切换数据源呢?

我们要切换数据源的时候我们手动去控制它,我们希望在业务层打一个注解,比如现在我们需要读库了,业务层的方法都是读库了,我们只要打一个注解就把它搞定,例如@DataSource(DataSourceType.SLAVE),

然后让DynamicDataSourceHolder这个类自动帮我们切换一下,用它setDataSource(DataSourceType dataSourceType)方法将数据源设置成SLAVE.这样读操作就走读库了。

那么问题来了,我们想达到这个效果,那改怎么办呢?那么首先我们要定义一个注解。

那么又有疑问了,为什么我们不在每一个查询的方法里面调用DynamicDataSourceHolder.setDataSource(DataSourceType dataSourceType)方法设置一下不就行了吗?

这样做也可以,但是这样做岂不是很蛋疼?因为这样做代码就不够优雅了,要重复写很多代码。每一个查询方法里面都这样写,岂不是烦死?

 

因此我们自定义一个注解,代码如下:

复制代码
package com.coder520.common;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Created by cong on 2018/3/14.
 */   

//运行时影响程序注解 @Retention(RetentionPolicy.RUNTIME)
//这个注解作用于所有方法 @Target({ElementType.METHOD}) public @interface DataSource { //打了这个注解,如果没设置值,我们就默认用MASTER主库 DataSourceType value() default DataSourceType.MASTER; }
复制代码

那么我们到这里就OK了吗?并不是的,我们只是打了个注解,还没进行数据源的切换呢。然后做呢?

这时我们就要用切面编程AOP方法来执行所有的切面,我们切哪个方法呢?我们切所有的业务层,service层的方法,然后获取到它的注解,看一下注解标记的是MASTER,还是SLAVE

然后调用DynamicDataSourceHolder.setDataSource(DataSourceType dataSourceType)方法设置一下就行了。这是正是切面编程大显身手的时候,切面编程让我们一段代码让我们给每一个方法执行一段业务逻辑,

减少我们的代码量。

 

我们都是AOP有前置通知,后置通知,环绕通知,我们在这里一定要用前置通知,因为进入方法前就一定先要切换数据源,方法执行完了,再切换数据源还有个屁用。

 

DataSourceAspect切面类的代码如下:

复制代码
package com.coder520.common;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.reflect.MethodSignature;

import java.lang.reflect.Method;

/***/
public class DataSourceAspect {

    public void before(JoinPoint point) throws NoSuchMethodException {
        //获取切点
        Object target = point.getTarget();
        //获取方法的名字
        String method = point.getSignature().getName();
        //获取字节码对象
        Class classz = target.getClass();
        //获取方法上的参数
        Class<?>[] parameterTypes = ((MethodSignature)point.getSignature()).getMethod().getParameterTypes();
        //获取方法
        Method m = classz.getMethod(method,parameterTypes);
        //判断方法是否存在,并且判断是否有DataSource这个注释。
        if (m != null && m.isAnnotationPresent(DataSource.class)){
            //获取注解
            DataSource dataSource = m.getAnnotation(DataSource.class);
            //设置数据源
            DynamicDataSourceHolder.setDataSource(dataSource.value());
        }
    }

}
复制代码

注意:必须在spirng-cfg.xml中声明切面这个BEAN,并指定切哪里。
如下:
复制代码
<!--开启切面代理--><aop:aspectj-autoproxy/><!--切换数据源切面Bean--><bean id="switchDataSourceAspect" class="com.coder520.common.DataSourceAspect"/><!--切面配置--><aop:config><aop:aspect ref="switchDataSourceAspect"><aop:pointcut id="tx" expression="execution(* com.coder520.*.service.*.*(..))"/><aop:before method="before" pointcut-ref="tx"/></aop:aspect></aop:config>
复制代码

 

到这里就完成了,接着就是测试了。
我们简单的进行CURD来测试一下:
如下 业务层代码:
复制代码
package com.coder520.user.service;

import com.coder520.common.DataSource;
import com.coder520.common.DataSourceType;
import com.coder520.common.DynamicDataSourceHolder;
import com.coder520.user.dao.UserMapper;
import com.coder520.user.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.UnsupportedEncodingException;
import java.security.NoSuchAlgorithmException;


@Service("userServiceImpl")
public class UserServiceImpl implements UserService{

    @Autowired
    private UserMapper userMapper;


    /**
     *@Description 根据用户名查询用户
     */
    @DataSource(DataSourceType.SLAVE)
    @Override
    public User findUserByUserId(long id) {
        User user=null;
        try {
             user =userMapper.selectByPrimaryKey(id);
        }catch (Exception e){
            e.printStackTrace();
            throw e;
        }
        return user;
    }

    @Override
    @Transactional
    public int insertUser() {
        User user = new User();
        user.setMobile("1234567");
        user.setNickname("laowang");
        User user1 = new User();
        user1.setId(2L);
        user1.setMobile("11111111");
        user1.setNickname("laowang2");
         userMapper.insertSelective(user);
         userMapper.insertSelective(user1);
         return 0;
    }


    @Override
    public void createUser(User user) {
        userMapper.insertSelective(user);
    }
}
复制代码

 

Controller层代码:

复制代码
package com.coder520.user.controller;

import com.coder520.user.entity.User;
import com.coder520.user.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpSession;

/***/
@Controller
@RequestMapping("user")
public class UserController {


    @Autowired
    private UserService userService;



    /**
     *@Description  获取用户信息
     */
    @RequestMapping("/getuser")
    @ResponseBody
    public User getUser(){
       return userService.findUserByUserId(1);
    }

    @RequestMapping("/setuser")
    @ResponseBody
    public int setUser(){
       return userService.insertUser();
    }



}
复制代码

 

mybatis那部分的代码省略。

 

运行结果如下:

 

 

 
   

 

 可以看到两个SLVE是轮询切换的。

 

接着自己可以测试一下插入,修改数据源,是否切换到主库中。查看3个数据库是否同步了,这里就不演示了。

就算中途出错,事务会回滚的。这里不演示了,自己可以去试一下。

 

 

主从复制数据是异步完成的,这就导致主从数据库中的数据有一定的延迟,在读写分离的设计中必须要考虑这一点。

以博客为例,用户登录后发表了一篇文章,他需要马上看到自己的文章,但是对于其它用户来讲可以允许延迟一段时间(1分钟/5分钟/30分钟),不会造成什么问题。

这时对于当前用户就需要读主数据库,对于其他访问量更大的外部用户就可以读从数据库。

 

解决办法:

       适当放弃一致性:在一些实时性要求不高的场合,我们适当放弃一致性要求。这样就可以充分利用多种手段来提高系统吞吐量,例如页面缓存(cookie,session)、分布式数据缓存(redis)、数据库读写分离、查询数据搜索索引化。

 

总结:

  我的想法是要使用读写分离来实现系统吞吐量的提升就要从业务上想办法降低一致性的要求。

  对必须要有一致性的功能是无法进行读写分离的,可以采用多库不区分读写以及redis缓存等技术来实现。

   所以主从分离后,去从数据库读的话,可能还没同步过来。

MySQL多数据源笔记3-分库分表理论和各种中间件 - 狂小白 - 博客园

$
0
0

一.使用中间件的好处

  使用中间件对于主读写分离新增一个从数据库节点来说,可以不用修改代码,达到新增节点数据库而不影响到代码的修改。因为如果不用中间件,那么在代码中自己是先读写分离,如果新增节点,

你进行写操作时,你的轮询求模的数据量就要修改。但是中间件的维护也很麻烦的。

 

二.各种中间件

  1.MYSQL官方的mysqlProxy,它可以实现读写分离,但是它使用率很低,搞笑的是MySQL官方都不推荐使用。

  2.Amoeba:这是阿里巴巴工程司写的,是开源的。使用也很少。

  3.阿里开源的cobar,缺点查询回来的数据没有排序,和分页,这些都要自己处理,用的少。

  5.mycat:是根据cobar改造的。用的还算比较多

  6.ShardingJDBC:这是当当网的。这近几年比较流行,比较牛逼。

 

注意:如果我们单纯的做读写分离,一般都会选择用SpringAOP去做。并不是一定使用中间件就一定好。

 

读写分离做完,我们数据库的压力就解决了吗?

  并没有,做完读写分离,我们都知道知识一个或几个主库,多个从库,每个数据库里面的数据都是一样的。只是一份复制。

  这样做的好处就是:

      1。数据库可以备份。

      2.减轻数据库的压力。 

数据库的压力问题就缓解了吗?并没有缓解的特别厉害,当你这真正的高并发,大数据量来的时候,你做读写分离也不够用的。MySQL单表能承受多少数据量呢?

实践来看的话,大概也就7000W-1亿,这根据字段量,和字段里面存的东西,这数据是根据业务走的,不一定那么精确。

当达到这个数量级的时候你的多表关联查询什么的,即便优化到位,我说的优化,第一是索引一定要设置合理,第二SQL优化,但是SQL优化做的很有限。

到这个数据量你去关联那么两三个表,基本都是在5S,10S甚至更长的时间。

 

这时候就要考虑别的办法了。走到这一步,那该怎么办呢?

  那么就要进行分库分表:

      1.垂直拆分:

        基于领域模型做数据的垂直切分是一种最佳实践。如将订单、用户、支付等领域模型划分到不同的DB库里面。前提必须各领域数据之间join展示场景较少,在这种情况下分库能获得很高的价值,同时各个系统之间的扩展性得到很大程度的提高。

        缺点:如果在拆分之前,系统中很多列表和详情页所需的数据是可以通过sql join来完成的。而拆分后,数据库可能是分布式在不同实例和不同的主机上,join将变得非常麻烦。而且基于架构规范,性能,安全性等方面考虑,一般是禁止跨库join的。

        那该怎么办呢?首先要考虑下垂直分库的设计问题,如果可以调整,那就优先调整。

          解决办法:

             1.建立全局表:系统中可能出现所有模块都可能会依赖到的一些表。比较类似我们理解的“数据字典”。为了避免跨库join查询,我们可以将这类表在其他每个数据库中均保存一份。这种做法叫做建立全局表

              同时,这类数据通常也很少发生修改(几乎不会),一般不用太担心“一致性”问题。

             2.进行数据同步:比如,用户库中的tab_a表和订单库中tbl_b有关联,可以定时将指定的表做同步。当然,同步本来会对数据库带来一定的影响,需要性能影响和数据时效性中取得一个平衡。这样来避免复杂的跨库查询。

             3.系统层面组装:调用不同模块的组件或者服务,获取到数据并进行字段拼装。不要想着这很容易,实践起来可真没有那么容易,尤其是数据库设计上存在问题但又无法轻易调整的时候。具体情况通常会比较复杂。

              组装的时候要避免循环调用服务,循环RPC,循环查询数据库,最好一次性返回所有信息,在代码里做组装。

             4.适当的进行字段冗余:比如“订单表”中保存“卖家Id”的同时,将卖家的“Name”字段也冗余,这样查询订单详情的时候就不需要再去查询“卖家用户表”。

        如下图:

            拆分前:

            

            拆分后:

            

        

             这样的话能够缓解一下压力,我们还可以对每一个独立的DB在进行读写分离。

             当然拆分也会出现问题的。比如说分布式事务。比如支付系统支付失败了回滚,那么你订单系统也要回滚的。但是你们都不在同一个库里面,这是就需要分布式事务了。

 

              如果走到这一步,你的系统还是缓解不了压力,那么说明你这系统比较厉害了。说明你公司在你的行业里面能叫的上名字的了。

              那么我们就要进行水平拆分了。 

        

      2.水平拆分:垂直切分缓解了原来单集群(所有的数据库都存在一张表里,)的压力,但是在抢购时依然捉襟见肘。原有的订单模型已经无法满足业务需求,于是就要进行水平分表也称为横向分表,

            比较容易理解,就是将表中不同的数据行按照一定规律分布到不同的数据库表中(这些表保存在同一个数据库中),这样来降低单表数据量,优化查询性能。

            最常见的方式就是通过 主键或者 时间等字段进行 Hash取模后拆分。

            如下图:

            

            如果数据压力还是比较大怎么呢?,还能怎么办,继续进行水平拆分咯,比如,user(1)user(2)user(3)继续惊醒水平拆分。

 

    

   水平拆分所带来的问题:

      1. 常见分片规则问题

        1。一致性hash: 依据Sharding Key对5取模,根据余数将数据散落到目标表中。比如将数据均匀的分布在5张user表中,如下图:

        

           2.范围切分:比如按照时间区间或ID区间来切分。

            优点:单表大小可控,天然水平扩展。
            缺点:无法解决集中写入瓶颈的问题

            如下图:

            

          3.将ID和库的Mapping关系记录在一个单独的库中。

            优点:ID和库的Mapping算法可以随意更改。
            缺点:引入额外的单点。

            如下图:

        

 

      

  数据迁移问题

  很少有项目会在初期就开始考虑分片设计的,一般都是在业务高速发展面临性能和存储的瓶颈时才会提前准备。因此,不可避免的就需要考虑历史数据迁移的问题。

  一般做法就是通过程序先读出历史数据,这些数据量非常大,不涉及事务,实时性要求低,这是你就要考虑一下非关系数据库了,比如MongoDB,Hbase,这些数据库

  扩展性非常好,这些NoSQL天然就是分片的。弹性非常好。然后按照指定的分片规则再将数据写入到各个分片节点中。

     这里还要知道一下什么是OLTP,OLAP:

    

           OLTP(on-line transaction processing)主要是执行基本日常的事务处理,比如数据库记录的增删查改。比如在银行的一笔交易记录,就是一个典型的事务。 
           OLTP的特点一般有: 
                  1.实时性要求高。我记得之前上大学的时候,银行异地汇款,要隔天才能到账,而现在是分分钟到账的节奏,说明现在银行的实时处理能力大大增强。 
                  2.数据量不是很大,生产库上的数据量一般不会太大,而且会及时做相应的数据处理与转移。 
                  3.交易一般是确定的,比如银行存取款的金额肯定是确定的,所以OLTP是对确定性的数据进行存取 
                  4.高并发,并且要求满足ACID原则。比如两人同时操作一个银行卡账户,比如大型的购物网站秒杀活动时上万的QPS请求。

    

           联机分析处理OLAP(On-Line Analytical Processing)是数据仓库系统的主要应用,支持复杂的分析操作,侧重决策支持,并且提供直观易懂的查询结果。典型的应用就是复杂的动态的报表系统。

           OLAP的特点一般有: 
                  1.实时性要求不是很高,比如最常见的应用就是天级更新数据,然后出对应的数据报表。 
                  2.数据量大,因为OLAP支持的是动态查询,所以用户也许要通过将很多数据的统计后才能得到想要知道的信息,例如时间序列分析等等,所以处理的数据量很大; 
                  3.OLAP系统的重点是通过数据提供决策支持,所以查询一般都是动态,自定义的。所以在OLAP中,维度的概念特别重要。一般会将用户所有关心的维度数据,存入对应数据平台

 

  跨分片的排序分页 

  在分库分表的情况下,为了快速(分页)查询数据,分表策略的选择就显得非常重要了,需要尽最大限度将需要跨范围查询的数据尽量集中,多数情况下在我们做了最大限度的努力之后,数据仍然可能是分布式的。

  为了进一步提高查询的性能,维持查询的中间变量信息是我们在分库分表模式下提高分页查询速度的另一个手段:我们每次翻页查询时,通过中间信息的分析,就可以直接定位到目标表的目标位置,通过这种方式提供了近似于在单表模式下的分页查询能力。

  但另一方面也需要在业务上做出一定的牺牲:限制查询区段,提高检索速度。

  如下图:

  

 

 

  跨分片的函数处理

  在使用Max、Min、Sum、Count之类的函数进行统计和计算的时候,需要先在每个分片数据源上执行相应的函数处理,然后再将各个结果集进行二次处理,最终再将处理结果返回。

 

  跨分片join

  Join是关系型数据库中最常用的特性,但是在分片集群中,join也变得非常复杂。应该尽量避免跨分片的join查询(这种场景,比上面的跨分片分页更加复杂,而且对性能的影响很大)。

  通常有以下 几种方式来避免

    1.全局表

      全局表的概念之前在“垂直分库”时提过。基本思想一致,就是把一些类似数据字典又可能会产生join查询的表信息放到各分片中,从而避免跨分片的join。

    2.ER分片

    在关系型数据库中,表之间往往存在一些关联的关系。如果我们可以先确定好关联关系,并将那些存在关联关系的表记录存放在同一个分片上,那么就能很好的避免跨分片join问题。在一对多关系的情况下,我们通常会选择按照数据较多的那一方进行拆分。

    3.内存计算

    随着spark内存计算的兴起,理论上来讲,很多跨数据源的操作问题看起来似乎都能够得到解决。可以将数据丢给spark集群进行内存计算,最后将计算结果返回。

 

总结

并非所有表都需要水平拆分,要看增长的类型和速度,水平拆分是大招,拆分后会增加开发的复杂度,不到万不得已不使用。

在大规模并发的业务上,尽量做到在线查询和离线查询隔离,交易查询和运营/客服查询隔离。

拆分维度的选择很重要,要尽可能在解决拆分前问题的基础上,便于开发。

数据库没你想象的那么坚强,需要保护,尽量使用简单的、良好索引的查询,这样数据库整体可控,也易于长期容量规划以及水平扩展。

Viewing all 532 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>