Quantcast
Channel: IT社区推荐资讯 - ITIndex.net
Viewing all 15843 articles
Browse latest View live

mysql读写分离

$
0
0

转载他人

 

 

使用mysql主从复制的好处有:  

1、采用主从服务器这种架构,稳定性得以提升。如果主服务器发生故障,我们可以使用从服务器来提供服务。

2、在主从服务器上分开处理用户的请求,可以提升数据处理效率。

3、将主服务器上的数据复制到从服务器上,保护数据免受意外的损失。

环境描述:

新企业要搭建架构为主从复制的mysql数据库。

主服务器(mysql-master):IP地址:192.168.48.128,mysql已安装,没有用户数据。

从服务器(mysql-slave):IP地址:192.168.48.130,mysql已安装,没有用户数据。

主从服务器均可正常提供服务。

 

配置主服务器(master)

1、编辑数据库配置文件my.cnf或my.ini(windows),一般在/etc/目录下。

在[mysqld]的下面加入下面代码:

log-bin=mysql-bin

server-id=1

innodb_flush_log_at_trx_commit=1

sync_binlog=1

binlog-do-db=wordpress

binlog_ignore_db=mysql

 

server-id=1中的1可以任定义,只要是唯一的就行。

binlog-do-db=wordpress是表示只备份wordpress。

binlog_ignore_db=mysql表示忽略备份mysql。

不加binlog-do-db和binlog_ignore_db,那就表示备份全部数据库。

 

2、然后重启MySQL:#servicemysqldrestart

 

3、登录mysql,在mysql中添加一个backup的账号,并授权给从服务器。

[root@localhost~]#mysql-uroot–p123456登录mysqlmysql>grantreplicationslaveon*.*to'backup'@'192.168.48.130'identifiedby'backup';

创建backup用户,并授权给192.168.48.130使用。

4、查询主数据库状态,并记下FILE及Position的值,这个在后面配置从服务器的时候要用到。

mysql>showmasterstatus;请记下显示的信息,配置从服务器会用到。+——————+———-+————–+——————+|File|Position|Binlog_Do_DB|Binlog_Ignore_DB|+——————+———-+————–+——————+|mysql-bin.000001|253|dbispconfig|mysql|+——————+———-+————–+——————+

1rowinset(0.00sec)

 

在从服务器上操作:

1)、确保/etc/my.cnf中有log-bin=mysql-bin和server-id=1参数,并把server-id=1修改为server-id=10。修改之后如下所示:

[mysqld]

log-bin=mysql-bin启动二进制文件

server-id=10服务器ID

 

2)、重启mysql服务。

[root@localhost~]#mysqladmin-p123456shutdown

[root@localhost~]#mysqld_safe--user=mysql&

 

3)、登录mysql,执行如下语句

[root@localhost~]#mysql-uroot–p123456

mysql>changemastertomaster_host='192.168.48.128',master_user='backup',master_password='backup',master_log_file='mysql-bin.000003',master_log_pos=401;

 

4)、启动slave同步。

mysql>startslave;

 

5)、检查主从同步,如果您看到Slave_IO_Running和Slave_SQL_Running均为Yes,则主从复制连接正常。mysql>showslavestatus\G

 

验证配置是否正常,mysql主从能否正常复制。

在主数据库上新建一个库,并且在库中写一个表和一些数据。

[root@localhost~]#mysql-uroot–p123456

mysql>createdatabasemysqltest;

mysql>usemysqltest;

mysql>createtableuser(idint(5),namechar(10));

mysql>insertintouservalues(00001,'zhangsan');

 

在从数据库中验证一下,是否正常复制到数据。

[root@localhost~]#mysql-uroot–p123456

mysql>showdatabases;

mysql>select*frommysqltest.user;

 

 

 

MySQL 数据同步 一主多从

 

 

Master 主服务器的ip:192.168.1.99

Slave1 从服务器的ip:192.168.1.113

Slave2 从服务器的ip:192.168.1.111

 

 

一、master主服务器上设置:

1.权限设置

允许用户user从ip为192.168.1.111、192.168.1.113的主机连接到mysql服务器(master),并使用password作为密码

下面涉及到,从服务器的ip、登陆用户名、登陆密码。

下面用户名是repl,密码是repl
mysql>GRANT ALL PRIVILEGES ON *.* TO 'repl'@'192.168.1.111'IDENTIFIED BY 'repl';

mysql>GRANT ALL PRIVILEGES ON *.* TO 'repl'@'192.168.1.113'IDENTIFIED BY 'repl';

 

 

2.文件配置

修改my.ini配置文件

 [mysqld]

# The TCP/IP Port the MySQL Server will listen on

port=3306

加入下面两行,设置log文件、服务id

log-bin = mysql-bin.log

server-id = 1

重启mysql服务。

 

 

3.得到主服务器上当前的二进制日志名和偏移量

mysql> show master status;

+------------------+----------+--------------+------------------+

| File            | Position | Binlog_Do_DB | Binlog_Ignore_DB |

+------------------+----------+--------------+------------------+

| mysql-bin.000005 |     106 |             |                 |

+------------------+----------+--------------+------------------+

1 row in set

 

二、slave1从服务器上设置:

1.文件配置

修改my.ini配置文件

 [mysqld]

# The TCP/IP Port the MySQL Server will listen on

port=3306

加入下面两行,设置服务id、log文件

server-id = 2

log-bin = mysql-bin.log

重启mysql服务。

 

 

2.在从服务器上,关闭slave线程

 Mysql>stop slave;

 

 3.在从服务器做相应设置

指定复制使用的用户,主数据库服务器的ip、端口、以及开始执行复制的日志文件和位置等。

Mysql >Change master to

master_host='192.168.1.99',

master_port=3306,

master_user='repl',

master_password='repl',

master_log_file='mysql-bin.000005',

master_log_pos=106;

 

 

      4.在从服务器上,启动slave线程

Mysql >start slave;

 

 

5.显示slave的状态信息

Mysql >show slave status;

 

 

6.显示从服务器上的进程

Mysql >show processlist;

 

三、slave2从服务器上设置:

1.文件配置

修改my.ini配置文件

 [mysqld]

# The TCP/IP Port the MySQL Server will listen on

port=3306

加入下面两行,设置服务id、log文件

server-id = 3

log-bin = mysql-bin.log

重启mysql服务。

 

 

2.3.4.5.6步的设置,同slave1一样

 

 

三、测试查看效果

1、保持master主服务器和slave1、slave2开启;

在master服务器上操作,数据库和表,能看到,slave服务器上的数据库和表,跟随着,做相应变动。

 

 

备注:这里我们默认,要同步的数据库,他们已经具有相同的初始信息,包括schema和具体的表。如果,初始信息不相同,则需将master主服务器中的信息,先备份,然后再导入到从服务器中。

一个完整的mysql读写分离环境包括以下几个部分:

  • 应用程序client
  • database proxy
  • database集群

在本次实战中,应用程序client基于c3p0连接后端的database proxy。database proxy负责管理client实际访问database的路由策略,采用开源框架amoeba。database集群采用mysql的master-slave的replication方案。整个环境的结构图如下所示:

实战步骤与详解

一.搭建mysql的master-slave环境

1)分别在host1(10.20.147.110)和host2(10.20.147.111)上安装mysql(5.0.45),具体安装方法可见官方文档

2)配置master

首先编辑/etc/my.cnf,添加以下配置:

log-bin=mysql-bin #slave会基于此log-bin来做replication
server-id=1 #master的标示
binlog-do-db = amoeba_study #用于master-slave的具体数据库

然后添加专门用于replication的用户:

mysql> GRANT REPLICATION SLAVE ON *.* TO repl@10.20.147.111 IDENTIFIED BY '111111';

重启mysql,使得配置生效:

/etc/init.d/mysqld restart

最后查看master状态:

3)配置slave

首先编辑/etc/my.cnf,添加以下配置:

server-id=2 #slave的标示

配置生效后,配置与master的连接:

mysql> CHANGE MASTER TO
    -> MASTER_HOST='10.20.147.110',
    -> MASTER_USER='repl',
    -> MASTER_PASSWORD='111111',
    -> MASTER_LOG_FILE='mysql-bin.000003',
    -> MASTER_LOG_POS=161261;

其中MASTER_HOST是master机的ip,MASTER_USER和MASTER_PASSWORD就是我们刚才在master上添加的用户,MASTER_LOG_FILE和MASTER_LOG_POS对应与master status里的信息

最后启动slave:

mysql> start slave;

4)验证master-slave搭建生效

通过查看slave机的log(/var/log/mysqld.log):

100703 10:51:42 [Note] Slave I/O thread: connected to master 'repl@10.20.147.110:3306',  replication started in log 'mysql-bin.000003' at position 161261

如看到以上信息则证明搭建成功,如果有问题也可通过此log找原因

二.搭建database proxy

此次实战中database proxy采用 amoeba ,它的相关信息可以查阅官方文档,不在此详述

1)安装amoeba

下载amoeba(1.2.0-GA)后解压到本地(D:\openSource\amoeba-mysql-1.2.0-GA),即完成安装

2)配置amoeba

先配置proxy连接和与各后端mysql服务器连接信息(D:\openSource\amoeba-mysql-1.2.0-GA\conf\amoeba.xml):

 

  1. <server>  
  2.     <!-- proxy server绑定的端口 -->  
  3.      <property name="port">8066 </property>  
  4.        
  5.     <!-- proxy server绑定的IP -->  
  6.     <!--   
  7.     <property name="ipAddress">127.0.0.1</property>  
  8.      -->  
  9.     <!-- proxy server net IO Read thread size -->  
  10.      <property name="readThreadPoolSize">20 </property>  
  11.        
  12.     <!-- proxy server client process thread size -->  
  13.      <property name="clientSideThreadPoolSize">30 </property>  
  14.        
  15.     <!-- mysql server data packet process thread size -->  
  16.      <property name="serverSideThreadPoolSize">30 </property>  
  17.        
  18.     <!-- socket Send and receive BufferSize(unit:K)  -->  
  19.      <property name="netBufferSize">128 </property>  
  20.        
  21.     <!-- Enable/disable TCP_NODELAY (disable/enable Nagle's algorithm). -->  
  22.      <property name="tcpNoDelay">true </property>  
  23.        
  24.     <!-- 对外验证的用户名 -->  
  25.      <property name="user">root </property>  
  26.        
  27.     <!-- 对外验证的密码 -->  
  28.      <property name="password">root </property>  
  29. </server>  

 

Xhtml代码 
  1. <server>  
  2.     <!-- proxy server绑定的端口 -->  
  3.     <property name="port">8066</property>  
  4.       
  5.     <!-- proxy server绑定的IP -->  
  6.     <!--  
  7.     <property name="ipAddress">127.0.0.1</property> 
  8.      -->  
  9.     <!-- proxy server net IO Read thread size -->  
  10.     <property name="readThreadPoolSize">20</property>  
  11.       
  12.     <!-- proxy server client process thread size -->  
  13.     <property name="clientSideThreadPoolSize">30</property>  
  14.       
  15.     <!-- mysql server data packet process thread size -->  
  16.     <property name="serverSideThreadPoolSize">30</property>  
  17.       
  18.     <!-- socket Send and receive BufferSize(unit:K)  -->  
  19.     <property name="netBufferSize">128</property>  
  20.       
  21.     <!-- Enable/disable TCP_NODELAY (disable/enable Nagle's algorithm). -->  
  22.     <property name="tcpNoDelay">true</property>  
  23.       
  24.     <!-- 对外验证的用户名 -->  
  25.     <property name="user">root</property>  
  26.       
  27.     <!-- 对外验证的密码 -->  
  28.     <property name="password">root</property>  
  29. </server>  

 

 

以上是proxy提供给client的连接配置

 

  1. <dbServerList>  
  2.      <dbServer name="server1">            
  3.         <!-- PoolableObjectFactory实现类 -->  
  4.          <factoryConfig class="com.meidusa.amoeba.mysql.net.MysqlServerConnectionFactory">  
  5.              <property name="manager">defaultManager </property>  
  6.                
  7.             <!-- 真实mysql数据库端口 -->  
  8.              <property name="port">3306 </property>  
  9.                
  10.             <!-- 真实mysql数据库IP -->  
  11.              <property name="ipAddress">10.20.147.110 </property>  
  12.              <property name="schema">amoeba_study </property>  
  13.                
  14.             <!-- 用于登陆mysql的用户名 -->  
  15.              <property name="user">root </property>  
  16.                
  17.             <!-- 用于登陆mysql的密码 -->  
  18.              <property name="password" ></property>  
  19.                
  20.          </factoryConfig>  
  21.            
  22.         <!-- ObjectPool实现类 -->  
  23.          <poolConfig class="com.meidusa.amoeba.net.poolable.PoolableObjectPool">  
  24.              <property name="maxActive">200 </property>  
  25.              <property name="maxIdle">200 </property>  
  26.              <property name="minIdle">10 </property>  
  27.              <property name="minEvictableIdleTimeMillis">600000 </property>  
  28.              <property name="timeBetweenEvictionRunsMillis">600000 </property>  
  29.              <property name="testOnBorrow">true </property>  
  30.              <property name="testWhileIdle">true </property>  
  31.          </poolConfig>  
  32.      </dbServer>  
  33.      <dbServer name="server2">  
  34.            
  35.         <!-- PoolableObjectFactory实现类 -->  
  36.          <factoryConfig class="com.meidusa.amoeba.mysql.net.MysqlServerConnectionFactory">  
  37.              <property name="manager">defaultManager </property>  
  38.                
  39.             <!-- 真实mysql数据库端口 -->  
  40.              <property name="port">3306 </property>  
  41.                
  42.             <!-- 真实mysql数据库IP -->  
  43.              <property name="ipAddress">10.20.147.111 </property>  
  44.              <property name="schema">amoeba_study </property>  
  45.                
  46.             <!-- 用于登陆mysql的用户名 -->  
  47.              <property name="user">root </property>  
  48.                
  49.             <!-- 用于登陆mysql的密码 -->  
  50.              <property name="password" ></property>  
  51.                
  52.          </factoryConfig>  
  53.            
  54.         <!-- ObjectPool实现类 -->  
  55.          <poolConfig class="com.meidusa.amoeba.net.poolable.PoolableObjectPool">  
  56.              <property name="maxActive">200 </property>  
  57.              <property name="maxIdle">200 </property>  
  58.              <property name="minIdle">10 </property>  
  59.              <property name="minEvictableIdleTimeMillis">600000 </property>  
  60.              <property name="timeBetweenEvictionRunsMillis">600000 </property>  
  61.              <property name="testOnBorrow">true </property>  
  62.              <property name="testWhileIdle">true </property>  
  63.          </poolConfig>  
  64.      </dbServer>          
  65. </dbServerList>  

 

Xhtml代码 
  1. <dbServerList>  
  2.     <dbServer name="server1">           
  3.         <!-- PoolableObjectFactory实现类 -->  
  4.         <factoryConfig class="com.meidusa.amoeba.mysql.net.MysqlServerConnectionFactory">  
  5.             <property name="manager">defaultManager</property>  
  6.               
  7.             <!-- 真实mysql数据库端口 -->  
  8.             <property name="port">3306</property>  
  9.               
  10.             <!-- 真实mysql数据库IP -->  
  11.             <property name="ipAddress">10.20.147.110</property>  
  12.             <property name="schema">amoeba_study</property>  
  13.               
  14.             <!-- 用于登陆mysql的用户名 -->  
  15.             <property name="user">root</property>  
  16.               
  17.             <!-- 用于登陆mysql的密码 -->  
  18.             <property name="password"></property>  
  19.               
  20.         </factoryConfig>  
  21.           
  22.         <!-- ObjectPool实现类 -->  
  23.         <poolConfig class="com.meidusa.amoeba.net.poolable.PoolableObjectPool">  
  24.             <property name="maxActive">200</property>  
  25.             <property name="maxIdle">200</property>  
  26.             <property name="minIdle">10</property>  
  27.             <property name="minEvictableIdleTimeMillis">600000</property>  
  28.             <property name="timeBetweenEvictionRunsMillis">600000</property>  
  29.             <property name="testOnBorrow">true</property>  
  30.             <property name="testWhileIdle">true</property>  
  31.         </poolConfig>  
  32.     </dbServer>  
  33.     <dbServer name="server2">  
  34.           
  35.         <!-- PoolableObjectFactory实现类 -->  
  36.         <factoryConfig class="com.meidusa.amoeba.mysql.net.MysqlServerConnectionFactory">  
  37.             <property name="manager">defaultManager</property>  
  38.               
  39.             <!-- 真实mysql数据库端口 -->  
  40.             <property name="port">3306</property>  
  41.               
  42.             <!-- 真实mysql数据库IP -->  
  43.             <property name="ipAddress">10.20.147.111</property>  
  44.             <property name="schema">amoeba_study</property>  
  45.               
  46.             <!-- 用于登陆mysql的用户名 -->  
  47.             <property name="user">root</property>  
  48.               
  49.             <!-- 用于登陆mysql的密码 -->  
  50.             <property name="password"></property>  
  51.               
  52.         </factoryConfig>  
  53.           
  54.         <!-- ObjectPool实现类 -->  
  55.         <poolConfig class="com.meidusa.amoeba.net.poolable.PoolableObjectPool">  
  56.             <property name="maxActive">200</property>  
  57.             <property name="maxIdle">200</property>  
  58.             <property name="minIdle">10</property>  
  59.             <property name="minEvictableIdleTimeMillis">600000</property>  
  60.             <property name="timeBetweenEvictionRunsMillis">600000</property>  
  61.             <property name="testOnBorrow">true</property>  
  62.             <property name="testWhileIdle">true</property>  
  63.         </poolConfig>  
  64.     </dbServer>         
  65. </dbServerList>  

 

 

以上是proxy与后端各mysql数据库服务器配置信息,具体配置见注释很明白了

最后配置读写分离策略:

 

  1. <queryRouter class="com.meidusa.amoeba.mysql.parser.MysqlQueryRouter" >  
  2.      <property name="LRUMapSize">1500 </property>  
  3.      <property name="defaultPool">server1 </property>  
  4.      <property name="writePool">server1 </property>  
  5.      <property name="readPool">server2 </property>  
  6.      <property name="needParse">true </property>  
  7. </queryRouter>  

 

Xhtml代码 
  1. <queryRouter class="com.meidusa.amoeba.mysql.parser.MysqlQueryRouter">  
  2.     <property name="LRUMapSize">1500</property>  
  3.     <property name="defaultPool">server1</property>  
  4.     <property name="writePool">server1</property>  
  5.     <property name="readPool">server2</property>  
  6.     <property name="needParse">true</property>  
  7. </queryRouter>  

 

 

从以上配置不然发现,写操作路由到server1(master),读操作路由到server2(slave)

3)启动amoeba

在命令行里运行D:\openSource\amoeba-mysql-1.2.0-GA\amoeba.bat即可:

log4j:WARN log4j config load completed from file:D:\openSource\amoeba-mysql-1.2.0-GA\conf\log4j.xml
log4j:WARN ip access config load completed from file:D:\openSource\amoeba-mysql-1.2.0-GA/conf/access_list.conf
2010-07-03 09:55:33,821 INFO  net.ServerableConnectionManager - Server listening on 0.0.0.0/0.0.0.0:8066.
三.client端调用与测试

1)编写client调用程序

具体程序细节就不详述了,只是一个最普通的基于mysql driver的jdbc的数据库操作程序

2)配置数据库连接

本client基于c3p0,具体数据源配置如下:

 

  1. <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"  
  2.     destroy-method="close">  
  3.      <property name="driverClass" value="com.mysql.jdbc.Driver" />  
  4.      <property name="jdbcUrl" value="jdbc:mysql://localhost:8066/amoeba_study" />  
  5.      <property name="user" value="root" />  
  6.      <property name="password" value="root" />  
  7.      <property name="minPoolSize" value="1" />  
  8.      <property name="maxPoolSize" value="1" />  
  9.      <property name="maxIdleTime" value="1800" />  
  10.      <property name="acquireIncrement" value="1" />  
  11.      <property name="maxStatements" value="0" />  
  12.      <property name="initialPoolSize" value="1" />  
  13.      <property name="idleConnectionTestPeriod" value="1800" />  
  14.      <property name="acquireRetryAttempts" value="6" />  
  15.      <property name="acquireRetryDelay" value="1000" />  
  16.      <property name="breakAfterAcquireFailure" value="false" />  
  17.      <property name="testConnectionOnCheckout" value="true" />  
  18.      <property name="testConnectionOnCheckin" value="false" />  
  19. </bean>  

 

Xhtml代码 
  1. <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"  
  2.     destroy-method="close">  
  3.     <property name="driverClass" value="com.mysql.jdbc.Driver" />  
  4.     <property name="jdbcUrl" value="jdbc:mysql://localhost:8066/amoeba_study" />  
  5.     <property name="user" value="root" />  
  6.     <property name="password" value="root" />  
  7.     <property name="minPoolSize" value="1" />  
  8.     <property name="maxPoolSize" value="1" />  
  9.     <property name="maxIdleTime" value="1800" />  
  10.     <property name="acquireIncrement" value="1" />  
  11.     <property name="maxStatements" value="0" />  
  12.     <property name="initialPoolSize" value="1" />  
  13.     <property name="idleConnectionTestPeriod" value="1800" />  
  14.     <property name="acquireRetryAttempts" value="6" />  
  15.     <property name="acquireRetryDelay" value="1000" />  
  16.     <property name="breakAfterAcquireFailure" value="false" />  
  17.     <property name="testConnectionOnCheckout" value="true" />  
  18.     <property name="testConnectionOnCheckin" value="false" />  
  19. </bean>  

 

 

值得注意是,client端只需连到proxy,与实际的数据库没有任何关系,因此jdbcUrl、user、password配置都对应于amoeba暴露出来的配置信息

3)调用与测试

首先插入一条数据:insert into zone_by_id(id,name) values(20003,'name_20003')

通过查看master机上的日志/var/lib/mysql/mysql_log.log:

100703 11:58:42       1 Query       set names latin1
                      1 Query       SET NAMES latin1
                      1 Query       SET character_set_results = NULL
                      1 Query       SHOW VARIABLES
                      1 Query       SHOW COLLATION
                      1 Query       SET autocommit=1
                      1 Query       SET sql_mode='STRICT_TRANS_TABLES'
                      1 Query       SHOW VARIABLES LIKE 'tx_isolation'
                      1 Query       SHOW FULL TABLES FROM `amoeba_study` LIKE 'PROBABLYNOT'
                      1 Prepare     [1] insert into zone_by_id(id,name) values(?,?)
                      1 Prepare     [2] insert into zone_by_id(id,name) values(?,?)           
                      1 Execute     [2] insert into zone_by_id(id,name) values(20003,'name_20003')

得知写操作发生在master机上

通过查看slave机上的日志/var/lib/mysql/mysql_log.log:

100703 11:58:42       2 Query       insert into zone_by_id(id,name) values(20003,'name_20003')

得知slave同步执行了这条语句

然后查一条数据:select t.name from zone_by_id t where t.id = 20003

通过查看slave机上的日志/var/lib/mysql/mysql_log.log:

100703 12:02:00      33 Query       set names latin1
                     33 Prepare     [1] select t.name from zone_by_id t where t.id = ?
                     33 Prepare     [2] select t.name from zone_by_id t where t.id = ?    
                     33 Execute     [2] select t.name from zone_by_id t where t.id = 20003 

得知读操作发生在slave机上

并且通过查看slave机上的日志/var/lib/mysql/mysql_log.log发现这条语句没在master上执行

通过以上验证得知简单的master-slave搭建和实战得以生效



已有 0人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐




Akka Cluster 2.3.1 bug分析和相关实现原理

$
0
0

       Akka这样一个scala世界里的明星,给我们提供了各种各样吸引人的功能和特性,尤其在分布式、高并发领域。但就像任何其他优秀的框架,它的实现也必然会有其复杂性,在Roland Kuhn(Akka Tech Lead)的带领下,Akka的实现原理吸收了各个领域内成熟、领先的理论。尤其是Akka里cluster的实现,更是体现了非常多的优秀理论和实战经验。

        但由于它目前还处在实验阶段,在使用过程中还是会有可能碰到这样或那样的问题,下面就以Akka 2.3.1为例,详细分析我们碰到的一个bug。

1)场景描述

     集群里有两台机器SeedNode1(10.10.10.110) 和 SeedNode2(10.10.10.220),Akka的配置文件application.conf里相关配置如下:

   seed-nodes = [

        "akka.tcp://ClusterSystem@10.10.10.110:2551",

        "akka.tcp://ClusterSystem@10.10.10.220:2552"]

       我们先启动SeedNode1,等一会启动SeedNode2,发现SeedNode2和SeedNode1的TCP链路是连上了,但就是无法正常进行工作。但如果先让SeedNode2先启动,然后再启动SeedNode1,则

没有问题,集群可正常启动。

 

2)分析

        为了更好方便大家理解,下面先介绍一下cluster和remote的相关实现细节,这样才能前后串起来。

2.1)cluster的启动

        要使用一个cluster首先要启动它,所以我们先从启动这个步骤的实现开始进行分析。Akka集群的启动首先就是要启动一种叫做种子节点(SeedNode)的节点们。只有种子节点启动成功,其他节点才能选择任意一个种子节点加入集群。

        种子节点默认可配置多个,它们之间没有任何区别,种子节点的启动分以下几种情况:

1.某种子节点启动,它首先判断自己的ip是否在种子节点配置列表中,如果在并且是第一个,则它在一个规定时间内(默认是5秒),向其他种子节点发送‘InitJoin’消息,如果有确认消息返回,则加入第一个返回确认的种子节点所在的cluster中,否则,它自己将创建一个新的cluster。(这些任务由FirstSeedNodeProcess这个Actor完成,任务完成后它就销毁自己)

 

2.某种子节点启动,它首先判断自己的ip是否在种子节点配置中,但不是第一个,则它向其他种子节点发送消息,如果在一个规定时间内(默认是5秒)没有收到任何确认消息,则它将不断重试,直到有一个种子节点返回正确的确认消息,然后就加入这个种子节点所在的cluster中。(这里注意以下,它不会自己创建一个新cluster)。(这些任务由JoinSeedNodeProcess这个Actor完成,任务完成后它就销毁自己)

       从上面的分析,我们可以得出下面的一些结论:

#.一个集群第一次启动成功,那一定是种子节点配置列表中排在第一位的节点,由它来创建出集群。但是随着时间的推移,排在第一的种子节点有可能重启了,那这个时候,它将首选加入到其他种子节点去。

#一个种子节点可以加入任何一个其他节点,不用非得都加到排第一位的节点上。

 

下面我们举例说明,有种子节点1、2、3:

* 1. seed2启动, 但是没有收到seed1 或seed3的确认。

* 2. seed3启动,没有收到seed1 的确认消息(seed2处在’inactive’状态)。

* 3. seed1 启动,创建cluster并加入到自己中。

* 4. seed2 重试加入过程,收到seed1的确认, 加入到seed1。

* 5. seed3重试加入过程,先收到seed2的确认, 加入到seed2。

 

2.2)remote通讯链路的上行、下行实现  

2.2.1)上行路径(listen启动的全过程)

由于上行路径较复杂,所以画了几张图辅助说明:



 
                                         (
建立listen的步骤 )

 

                                              (接收一个新链路请求 )



                                                (接收一个新链路处于等待握手状态 )

 

1###可以把Remoting这个非常重要的类作为通讯模块的入口,它在启动的时候(start方法里)会向           EndpointManager这个Actor发送Listen消息,启动底层通讯NettyTransport的listen操作。

 

2###由AkkaProtocolTransport类来包一层NettyTransport,所以,先调用的是AkkaProtocolTransport的listen方法,这个方法里产生一个upstreamListenerPromise,这个promise最后会被成赋值为ActorAssociationEventListener(EndpointManager的实例),而这个promise的作用是为了设置AkkaProtocolManager的associationListener属性为EndpointManager的实例。

 

3###NettyTransport在linsten过程中,会返回一个associationListenerPromise,这个promise会通过调用interceptListen方法而被赋值ActorAssociationEventListener(AkkaProtocolManager的实例)。

而这个promise有两个作用:

***把建立起来的通讯Channel(监听端口的)置为可读状态(setReadable),以便接收后续进入的消息。

***作为TcpServerHandler的构造参数传入(_associationListenerFuture),TcpServerHandler实例(它其实是

netty里SimpleChannelUpstreamHandler的一个扩展)里最重要的方法是onConnect这个回调方法。当有外部链接建立成功,

onConnect方法会被调用,紧接着会调用initInbound方法,然后在该promise处等待,直到promise被成功赋值。

 

4###当上面initInbound方法里的promise被成功唤醒,它就会调用init方法。

 

5###init方法里首先会创建一个TcpAssociationHandle实例(包含一个readHandlerPromise),这个Promise在这里等待被唤醒(它被后面7处的操作唤醒而设置channel(新链接的)置为可读状态(setReadable),同时在netty中注册该channel的listen为ProtocolStateActor实例),然后会向AkkaProtocolManager实例发送InboundAssociation消息(这个消息里包含一个TcpAssociationHandle实例)。

 

6###AkkaProtocolManager实例收到InboundAssociation消息,创建一个ProtocolStateActor实例(调用inboundProps构造方法),这个实例的构造函数里包含两个重要的参数TcpAssociationHandle实例、EndpointManager的实例;

 

7###ProtocolStateActor实例的这种构造方法会把TcpAssociationHandle实例里的readHandlerPromise设置值而唤醒它。

 

8###ProtocolStateActor实例初始化后会等待在接受握手的状态中(WaitHandshake),这个时候如果接收到网络报文,decode后发现是Associate消息,则调用notifyInboundHandler方法。在这个方法中会向EndpointManager实例发送InboundAssociation(new AkkaProtocolHandle(...))消息,notifyInboundHandler方法也创建了一个readHandlerPromise,它作为参数放在发往EndpointManager实例的消息里,然后等待被赋值。

 

9###EndpointManager实例收到InboundAssociation消息后,根据addressToWritable(EndpointPolicy规则的集合)进行一些必要的判断,如果符合要求则调用createAndRegisterEndpoint方法,这个方法最主要是创建EndpointWriter实例并注册这个实例。不符合则进行相关动作,如保存这个InboundAssociation消息,等待后续条件合适再处理。

 

10###在创建EndpointWriter实例的preStart方法里,判断是否已经存在AkkaProtocolHandle实例,如果已经存在则创建一个EndpointReader实例,并把它作为值设置给步骤7里的readHandlerPromise,使readHandlerPromise这个Promise的future被唤醒。

 

11###ProtocolStateActor实例的readHandlerPromise被唤醒后,会向自己发送一条HandleListenerRegistered(EndpointReader实例)的消息,接收到这个消息后,它会修改自己状态机里的状态数据为ListenerReady。后续所有接受的网络数据包就会被正常的decode和分发了。

       

2.2.2)下行路径

作为发送端(client),当seed节点A向seed节点B发送InitJoin消息时,调用链如下:

1###向处在accepting状态中的EndpointManager实例发送'Send(message, senderOption, recipientRef, _)'

 

2###EndpointManager实例调用createAndRegisterWritingEndpoint方法,创建一个ReliableDeliverySupervisor实例(在EndpointWriter实例之上封了一层,以加强可靠性)。

并且向addressToWritable这个HashMap里添加一条记录。

 

3###ReliableDeliverySupervisor实例会创建一个EndpointWriter实例,在其preStart方法里,由于传入的AkkaProtocolHandle为None,所以会调用transport.associate(remoteAddress, ...),同时EndpointWriter实例进入Initializing状态。

 

4###上面的transport是AkkaProtocolTransport实例,它会向AkkaProtocolManager实例的发送一个AssociateUnderlyingRefuseUid消息

 

5###AkkaProtocolManager实例收到AssociateUnderlyingRefuseUid消息后,调用createOutboundStateActor方法,该方法调用ProtocolStateActor.outboundProps的构造方法。

 

6###ProtocolStateActor实例的outboundProps构造方法,会调用NettyTransport实例的associate方法,它会调用NettyFutureBridge(bootstrap.connect(socketAddress)进行真正的网络连接。

 

7.1###如果无法成功建立连接,则向外发送异常,这个异常会最终被EndpointManager实例捕获。

 

8###EndpointManager实例捕获异常后,根据异常情况进行处理,如果是链接失败异常则调用markAsFailed修改addressToWritable相关配置。

 

7.2###如果成功建立连接,则InitJoin消息会发送对对方机器。

 

3)bug具体原因分析

      通过上面的cluster集群启动过程的分析和remoting的实现过程,可以用来具体分析一下我们的问题场景。 我们是先启动SeedNode1,它启动后会调用remoting的下行路径向SeedNode2发送 ’InitJoin‘消息,它在发送几次后,还没收到响应则自己创建了集群。等我们再启动SeedNode2的时候,SeedNode2会向SeedNode1发起链接,走的是SeedNode1的上行路径,于是bug发生了。

        它具体原因就在下行链路的处理环节8###中没有捕获ConnectException异常,也就没有对addressToWritable相关配置进行调整。这就使得上行链路的处理环节9###EndpointManager,无法正常往下进行。

      该bug在今年4月份被修复,2.3.2及其之后的版本都没有问题,具体修复请查看 https://github.com/akka/akka/commit/672e7f947c9d4e3499bb3667a7230685546b7f7b

      虽然就是新增了一个对ConnectException异常的捕获,但分析这个bug的原因过程,还是有收获的,应该能对使用Akka的remoting、cluster模块的相关朋友有帮助。



已有 0人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐



HTML5中新型input类型

$
0
0

你可能已经听说过,HTML5里引入了几种新的input类型。在HTML5之前,大家熟知的input类型包括:text(输入框),hidden(隐藏域),submit(提交按钮)等。而HTML5到来之后,新增的input类型包括:number(数字),date(日期),color(颜色),range(范围)等等。网上之所以还没有大量的出现对这些新型的input类型的使用,是因为还有很多人在使用古老的IE6/IE8,只有当使用这些古老浏览器的人所占的比例可以忽略不计时,那就是HTML5主导天下之日,那天也是我们Web开发人员的新纪元的开始。

下面是这几种新型input类型的实例演示,可能在不同的浏览器上它们的样式会稍微有些变化,但基本的功能都是一样的。

html5中的新型input类型

数字型 type=”number”

<input type="number">

效果:

日期型 type=”date”

<input type="date">

效果:

颜色选择器 type=”color”

<input type="color">

效果:

范围 type=”range”

<input type="range">

效果:

需要注意的是,如果你使用的是谷歌浏览器或Opera浏览器,当你点击日期类型的输入框时,会弹出日历,让你选择日期,但如果你使用的是火狐浏览器,很遗憾,火狐浏览器还没有实现弹出日历的功能,因为HTML5规范里没有规定实现日历的方法,所以各浏览器自己决定如何实现,相信不久之后火狐浏览器/IE浏览器也会有自己的弹出式日历框。

谷歌浏览器中date类型效果图:

谷歌浏览器中date类型效果图

谷歌浏览器中date类型效果图

如何打造一个适宜生活的房间(一)

$
0
0

查看原文: http://zaizhichang.com/?p=724
10128906868986d422

之前写了《 如果难以进入工作状态,先从收拾房间开始吧!》,在里面讲了通过整理房间可以迅速的调整自己的状态,但是怎么整理房间,对于一个男生来讲其实是一个跨世纪的课题,很多人都解决不好这个问题,当然处女座和整理癖的男生除外。作为一个既不是处女座也不是整理癖的男生,在收拾房间上基本上是一种零状态,不过好的是在女朋友的多次教育和培养下,终于下定决心把整个房间的布置进行了重新的布置和划分,目前看来效果还算可以,过程中搜集了很多资料再加上我自己的勤于思考,也总结出来了一些门道。

首先对于混乱的房间而言,主要造成的原因是因为在处理很多问题上无法把共通的决策和选择系统化。很多人喜欢把一些东西随机找一个地方放下来,这样时间稍稍一长就会出现混乱的迹象,分析原因主要有两个:一个是不知道该如何去放置这些物品,另外一个是因为没有固定的物品放置地点。所以,物品整理和打扫的最关键步骤就是建立一个物品何去何从的自动决策体系。

因为面对每一个物品都必须为这个物品做一个决策,判定这个物品该何去何从,在每一次的放置过程中都会产生一些焦虑感,随着物品的不断增多之后就会出现压力越来越大,最后这压得人喘不过气来,杂物就开始堆积了。我仔细观察过我们家妹子的生活习惯,发现她在面对所有物品的时候需要最终做的决策很少很少,只需要按照之前既定的位置把物品放回去即可,下次要使用的时候轻车熟路的去取。对于没条理的人而言要比有条理的人浪费的时间要多得多,因为很多时候都花费在了无谓的找东西和每次都不一样的整理上。

那么究竟该怎样去整理一个混乱的房间呢?自从借鉴了GTD理论之后,我发现整理房间对于我而言就变得简单了许多,总结起来三个步骤,第一步就是要学会组织,第二步就是学会分类,第三部才是所谓的整理和打扫。

当我在面对一个杂乱不堪的房间的时候,第一步要做的事情不是动手去打扫、也不是去搬各种乱七八糟的东西,第一不要做的事情是分析,分析你在这个房间里面生活的时间和生活的情景,然后把你在这个房间里所做的每种不同类型的事情列成清单,根据这个清单再列出一张你在这个房间里面所需的功能区的清单。

在列清单的过程中最好是按照情景或者是生活场景来进行,比如我在家里面主要的生活有做饭吃饭、学习阅读、写作上网,那么我就可以按照这个模式来对我的生活区间进行划分,然后列举出每一个所需要的功能的清单。最理想的状态就是能够列举出一张清楚的各种事项的处理清单,各个清单之间相互不重复,彼此无遗漏。

当列举玩清单之后,就要为为每种情景的内容配上所需要的设备、材料等。比如我在写作上网的时候需要的就有一台电脑、一个放电脑的书桌、一把舒适的椅子,电脑周边的设备,同时在这个过程中我可能还需要一些让我生活的东西,比如水杯、比如台灯等。通过这样的梳理之后就可以知道自己到底需要的是哪些东西,同时就可以把自己不需要的东西清理掉。

当把这些物品从功能和情景上划分成了不同的区域之后,接下来就可以为每个区域分配空间了。再分配空间的过程中尽量把一些具有相同的功能模块或者是在同一情景下完成的合并到一起,比如说写作和阅读,可能都需要一张舒适的椅子和一张舒适的书桌。在这个地方可以多花一些时间,让自己找到能最佳配合你的功能需要的家庭用品布置方案,让自己以最舒适最自然的状态去使用这些物品。

在这个部分的时候,最关键的一点事发挥自己的想象,找到自己理想的生活空间的样子,抛开那些已有的家具和设备,抛开各种的限制和预算发挥自己的想象力,找到自己理想中的生活环境的布置样式。然后把这些写下来或者是可以在设计软件里面描摹出来,形成成型的草图,然后在每块空间标出要分配的物品和相应的功能区。

未完待续---------

跑步日记:今天跑了五公里,重新注册咕咚账号到今天终于达到了100公里了,获得了百公里的勋章。


  青春就应该这样绽放   游戏测试:三国时期谁是你最好的兄弟!!   你不得不信的星座秘密

Simhash的巧妙

$
0
0

Simhash是locality sensitive hash(局部敏感哈希)的一种,最早由Moses Charikar在《similarity estimation techniques from rounding algorithms》一文中提出。Google就是基于此算法实现网页文件查重的《Detecting near-duplicates for web crawling》。

算法思路比较简单,这里不再赘述,其中比较容易被人忽视的但是又比较重要的两点是:

1、 与余弦相似度的关系:simhash源于随机超平面算法,可以推导出与余弦相似度的关系。

2、 索引:google在把simhash用来做分布去重的时候,海明距离小于4的即为相似的网页。simhash为64位,那么根据鸽巢原理,把hash平均分为4份的话,至少有一份是完全相同的。那么对于一个网页,你就可以建4个KV结构的map或索引,平均分成4份,每一份作为一个key。

假设是存在数据库的话,新来一个网页,计算了simhash, 前16位为key将数据库中的已有网页取出来,进行两两计算海明距离即可,就不需要进行全库比较了,大大大提高了计算效率。

可以很轻松的实现到mapreduce框架中。

其中,第2点对于系统是极为重要的。

您可能也喜欢:

漫话距离(By Dahua Lin@MIT)

马氏距离(Mahalanobis distance)和欧氏距离(Euclidean distance )

距离度量-L1距离和L2距离的概念

亡灵序曲配姚明

麦蒂:我没和阿帅吵架,我这周复出
无觅

如何快速分析一家企业

$
0
0

以前写过 如何一周读懂一个行业,是为了让咨询师快速进入一个行业开始工作做准备的方法。这次讲的是如何对一家企业做比较深入的分析,找出问题并提出组织变革的建议。每个企业的复杂度不同,有个套路只能让分析尽可能快一点,没办法保证多长时间可以完成分析。

第一步是分析目标企业的内部结构。《 组织理论与设计》第3章介绍了多个分析组织结构的维度。另外,《 卓有成效的组织》把组织里的各种角色分解成5个组成部分,然后总结了5种基本结构型,可以帮助我们从一个较高的层面理解一个企业的基本特征。

第二步是分析目标企业所处的竞争环境。Duncan的 环境不确定性框架虽然很老,还是可以帮助我们快速定位一个行业的不确定性水平。Porter的 竞争五力是基本的分析模型,从供应者、购买者、潜在进入者、替代者、竞争者五种力量的角度来分析企业的竞争环境和定价能力。 Grundy的文章指出竞争五力模型有过度简化之嫌,并指出五力之间可能的互相影响、以及其他外部因素(例如 PEST)可能对竞争环境造成的影响。另外, 国家文化也会对企业造成影响。

第三步是要找出差距。 Nadler和Tushman的文章提出了三个问题:(1)外部商业环境在发生怎样的变化?(2)外界环境的变化对企业提出了怎样的要求?(3)达成这些要求的挑战是什么?基本上,当下最大的环境变化莫过于 互联网的影响,基于这个大背景又提出了6条对企业的战略要求。就对着这6条挨个看哪儿有短板吧。

第四步是要提出变革方案。“变什么”是每个企业每个问题特定的,没办法一概而论;“怎么变”是可以有套路的。之前写过一篇 事关变革其中讲过怎么做一个组织变革管理的方案,不重复了。

所以就是这么几步……先看这个企业的固有特征,再看它所处的环境,看环境给它提出什么挑战,找到痛点,提出变革方案。齐活。

有女儿的男人

$
0
0

6月15日,父亲节,回望人生,他是站在你身后最大的依靠。

文/ 晚睡姐姐

自从韩寒在微博上第一次公开了女儿小野的照片,这个梳着波波头,长着一双水汪汪的大眼睛的萌小孩就被网友热烈追捧,风头压过了她爹韩寒。韩寒在微博上委屈地说:“以前看月亮的时候,人家都叫我韩少,现在新人胜旧人,在片场大家都叫我岳父。我的青春也太短暂了…”

明着抱怨,实际心里还不怎么偷着乐呢。谁家有个长相如此可爱,如此Q,还鬼精鬼灵的女儿——“两岁多时她要摘一朵花,我说,小野,不可以,花朵也是有爸爸妈妈的,所以你要对这朵小花说什么呢?她若有所思,上前一步说道:乖,和爸爸妈妈说再见……她太犀利了”——谁能不爱呢?

做了父亲的韩寒,油然而生一种责任感,“我要一辈子供养我女儿,让她衣食无忧,想玩什么就玩什么,什么出人头地、理想抱负,都是空的。她想要什么,我给她什么。”如果自己没钱了,他也绝不改初衷,“我就是给李彦宏开车也要让她过得好,让我的子女不再为他们父辈感到惭愧。”

知名作家、80后精神领袖谈起教育也没了那份睿智,基本和所有初为人父,内心被小女儿的诞生弄得脆弱无比的男人一样愚昧。这种想法近乎于天真,很难变成现实,你韩寒再厉害,也不能代替女儿去走她的人生,或许她会衣食无忧,可生活路上的顺风与逆流依然不可避免的要由她自己去承担。不说别的,就说感情,别看现在网友们呼唤韩寒岳父的声音一浪高过一浪,那都是打哈哈凑趣,有一天,她真爱上了的某人,可不见得就能和她顺顺利利的走下去。和所有女孩一样,总会在某个人身上,体会到人生最初的心碎。

但即便天真,这也是一个做父亲最热烈真挚的爱。因为爱,就不够理性;因为爱,就愿意当她生命里的英雄。

女儿融化了韩寒的心,也让他无形中变成了一个双重标准的人。谈到女儿的青春期,他说:“我肯定希望她的青春当中有初恋,她的男朋友一定要靠谱一些。当她确认她有男朋友的时候,我保证在72个小时之内,男朋友会被我查个底朝天。”查是为了什么,不就是要代替女儿把关,若是做人不靠谱,或是脚踩两条船玩弄女性之类的,当爸爸的好出面干预嘛。不过他好像忘了自己以前曾经说过,“我也不觉得共享有什么败坏道德的,婚姻应该是开放的,也就是说,在获得了前配偶的理解和许可的情况下,你应当是可以叠加重复婚姻的,男女都应当是这样。”

韩少的风流倜傥放浪不羁江湖屡有传闻——他说过,“我和我太太的感情非常坚固,但也许和其他姑娘也早已如同亲人。我甚至希望她们之间能够友好互助和平共处,就是这样。其他人会爱上我,我也许也会中意其他人,但没有人能改变我和我太太的感情”——可把同样的生活理论,换到自己女儿身上,他就不同意了。

换句话说,他可以和别的姑娘们在婚姻之内当“亲人”,可不允许别的男人因为不忠诚让女儿伤心。他能接受开放的婚姻,但若是未来有人要和小野搞开放的婚姻,他没准会把那个男人打得满地找牙。

中国文人里,男性沙文主义者较别的圈子更多。他们可以小情小调的歌颂女性的宽容、伟大,可从内心深处,他们多半都不怎么瞧得起女人,所谓爱情,也不平等,基本上都是男人占便宜,女人在吃亏。自己是这样的男人,自然知道男人在这里面窝藏的小伎俩和猫腻,所以当父亲的一定要火眼金睛仔细分辨哪种男人像自己,保证自家姑娘不吃亏。

所谓针不扎到肉上不知道痛,就是这个意思。总要当了父亲,才能更真实的明白一个女人的喜怒哀乐,渴望与恐惧。

王朔在小说《一点正经没有》中写过:“……扣子伸出小手去弄花。阳光照在花园里,使人和景物都显得明媚动人。扣子几乎被阳光照透明了……天真无邪,无忧无虑浑然不知人事——令人不忍久视。”主人公方言对女儿扣子说:“……扣子,你爸学坏可全为了你,让你以爸为镜长大到社会上是坏人一眼就能认出来——可怜天下父母心。”

王朔家也是女儿,那也是他自己的真心话。后来他还写了《祝女儿书》,狠狠地对女儿剖析了一下自己内心的阴暗面和真实心路历程,堪称王朔前半生的“忏悔录和思痛书”。 专业人士评论:“文字极其优美和细密。王朔在这个私人情感领域里倾身投入,袒露挚爱亲情,这在他以前的创作中从未有过。”他如此真诚,那是他自己生命的沉淀,更重要的,是女儿在他生命中的出现引发了他跳出男性特有的狭窄视野,有了最深刻的自我批判和反省。

现在好像说将心比心是一句特别老土的话了,按照王朔的语言风格应该是“过把瘾就死”、“爱谁谁”。可人总不会永远站在食物链的顶端,当跌落下来,体会到原来不曾体会到的感觉之后,才能让怜悯和同情复苏,知道自己施加给别人的那些有意无意的伤害,都是什么样的感觉。

韩寒现在越来越像一位慈父了,陪伴小野成了他生活中一项很重要的事情。“我一直觉得所谓爱,就是陪伴,没太多别的。”他说。这个有了女儿的男人,有一种内在的改变,一位纯真的小姑娘唤起他不同以往的柔情。可见情场上的浪子们都应该生女儿,生了女儿,有了顾忌,呵护着她一颦一笑,或许才会对所有的女性都多一点慈悲心,变得没有那么低级。

韩小野

附: 韩寒:这里会长出一朵花 

小野在她很小的时候从她奶奶这里学会了一套评判标准,那就是害虫和益虫。有天我正吃饭,她突然从旁边飞身而出,口中大喊一句,害虫,打死。然后一只飞蛾就被她拍死了。
我大吃一惊说:我去,小野,这是不对的。
这句话的结果就是小野又学会了一句“我去”。
她说:我去,是奶奶说的。
这是我一直想和她探讨的一个观点,但我想了很久也没找到合适的措辞。为此我和我的母亲还争辩过:对于那些虫族,所谓的有害与有益都是相对于人类而言,但你让小孩子有了这种二元对立非黑即白贴上标签即可捕杀的三五想法,并不利于她的身心。我母亲反驳道:那蚊子咬她怎么办,难道还要养起来?害虫就是害虫,小孩子不能好坏不分,《农夫与蛇》的故事你听过没有?
毫无疑问,这事一直争不出个结果。但小野飞身杀虫让我很生气。我站了起来,以前所未有的严厉再次责问她:你可以么?你可以这样做么?
她从未见我如此,退了一步,有点畏怯道:它是坏的小动物,它是苍蝇。{那时候她吧一切在空中飞的昆虫都叫苍蝇}
我突然思路开朗,构建出关于此事完整的哲学体系:什么叫坏的,什么叫好的?你害你的小动物就是坏的,不伤害你的小动物就是好的。这个飞飞的小动物伤害你了么?你把它打死了,它的家人就找不到它了,会很难过知道么?你可以不伤害它们,如果它们没有伤害你,知道了么?你这样做,它会很痛苦,所以你错了,你要做那些让它很快乐的事情,你知道么?你想想,如果你找不到家人,你会难过么?
也许是我语气太严肃,小野突然一句不说,两眼通红,凝滞几秒,瞬间大哭了起来。
我没有即刻安慰她,继续追问:你说,你做错了么?
小野已经哭得没法说一句完整的句子,但抽泣之中,她还是断断续续说,我错了。
我上前抚了抚她的脑袋,语气缓和道:那你现在要做什么呢?
小野哭着走到那只飞蛾那里,蹲下身子说:对不起,你很痛苦。
看着她好几滴泪都落到地板上,我心疼不已,更怕她为此反而留下更大的心理创伤,便心生一计,说:别哭了,我们一起帮助它好么??
小野噙着泪水,道,好。
我把飞蛾捡起,带上小铲子,牵上小野到了一片土地。我挖了一个小坑。让小野把飞蛾扔了进去,顺便告诉她,这是飞蛾,不是苍蝇。我教小野把土盖上以后说,这只飞蛾以前是个动物,现在它死了,我们把她埋了起来它就会变成一朵花,变成另外一种生命,就不会再痛苦了。小野你快去拿你的水壶来,我们要浇水了。
小野飞奔入屋。
我瞬间起身,跑到十几米外摘了一朵花(罪过罪过),折返回去,把花插在刚才埋飞蛾的地方。完成这个动作,小野正好提着水壶从屋里出来。她走到那朵花前,惊讶得说不出话。我说:你看,就在刚才,它变成了一朵花长了出来,说明它已经原谅你了。
小野破涕为笑,依偎到我的怀里,说:它这么快就有了花。
我亲了她一口,说:是啊,我们又是它的好朋友了。它很快长了出来说明它很快乐。
小野开心地笑了。
我说,别难过了小野,那只飞蛾变成了花,现在像我们一样快乐。
夕阳洒下,我抱起她,走向远方。我想所谓教育也许就是这样,爱与耐心,加上孩子能明白的方式。这世界不是那么好也不是那么坏,但这世界上的很多东西不能只用好或者坏来形容。初秋,已经开始吹起凉风,但此情此景能温暖一切。
她轻轻贴到我耳边,说:嗯,爸爸,那我们再去打一个飞蛾吧。


说明:
1. 左岸读书_blog by 左岸 © From 2008 致力于美好的读书体验。
2. 手机访问: http://wap.zreading.cn
3. 微博: http://weibo.com/zreading
4. 左岸.思文.群号: 188145065
5. 还有: 人生旅图冷知识
6. 微信:zreading
7. Site Meter
您可能也喜欢:
男人的致命弱点
男人怕女人唠叨的心理密码
一个男人关心的东西决定他的层次
男人,共勉之!
男人为何容易自命不凡?
无觅

全麦、白面包都滚蛋:5个不该吃面包的理由

$
0
0

全麦、白面包都滚蛋:5个不该吃面包的理由
我(原文作者,下同)有个朋友最近瘦了14磅,如你所想,她真的很自豪。我开玩笑的问她:“你的秘密是什么?”

她说,这很简单,我不再吃面包了。

这通常会让人感到吃惊,因为在我们的社会中,面包被认为是很正经的食物,甚至与许多遗产和信仰密切相关。然而面包并不是最好的营养来源,这在科学界已经被公认一段时间了。

把面包放在食物金字塔的底部实际上是错误的,尽管许多受人尊敬的专业医疗人士声称面包和其他谷物对人体来说是不必要的,而且它们还可能具有潜在危害,但面包依旧出现在我们的学校中(这里指的是西方学校)。下面将为大家介绍一些有关面包的惊人事实。

1.吃全麦面包比吃一个士力架巧克力的血糖水平还高

全麦、白面包都滚蛋:5个不该吃面包的理由

全麦面包实际上并不全是谷物。在制作过程中,还是会形成面粉,谷物会被分解成类似面粉的粉末。正因为谷物的粉末形式,身体可以迅速消化面包,之后让其以葡萄糖的形式进入血液中。这会增加产生脂肪的激素,比如胰岛素。全麦面包甚至比大多数糖果(比如士力架)拥有更高的G.I(血糖指数)。

当血糖水平急速上升时,我们会以轰然倒塌的速度感到极度饥饿。这导致了不断进食的循环,饿了,然后继续吃。这是高碳水化合物饮食存在的缺点,但却是许多被误导的权威人士试图向我们兜售的。

关于这个循环的解决办法,一些科学家认为,如果一个人患有糖尿病或者超重,那他就要采用限制碳水化合物的饮食了。

结论:因为谷物会在面包中被分解成细小的颗粒,能被人体迅速吸收且造成血糖水平升高。这就是为什么面包会有如此高的血糖指数以及为什么它的血糖指数比很多糖果都高。

2.面包中含有大量的麸质

全麦、白面包都滚蛋:5个不该吃面包的理由

面包中的主要成分是小麦(尽管会有些面包的主要成分发生变化)。小麦中含有一种叫做麸质的蛋白质。你可能听说过有的人会有“乳糜泻”或者“麸质不耐症 ”。有证据表明,很大一部分人对麸质很敏感。

麸质胶水般的特质让它很具有弹性,当你在烘焙或者揉捏面包的时候它会让原材料伸展开来(比如制作完美的纸杯蛋糕!)。

虽然烤制麸质很好,但是研究表明其还是存在较多副作用,包括腹胀、大便不成形、肠壁遭到破坏以及疲劳。

更令人担忧的是,麸质与精神分裂有关,可能会像某些麻醉剂一样让人上瘾。

结论:大多数面包含有麸质,会对你的健康造成损害。为了知道麸质是否对你造成了影响,把它从你的饮食中撤除30天,之后再恢复。你的心情应该会有所不同。根据许多人的经验,他们表示,懈怠的情绪会有所好转。

3.面包含有其他有害物质

如果你看了面包包装背面的成分列表,你可能会想要一个化学学位来了解上面写的都是些什么。大多数面包含有糖或者高果糖玉米糖浆。

当你吞下它们的时候,糖、谷物会让你的胰腺超负荷工作。这些化学物质大多数是防腐剂,会在你的胃里躺很多年。

结论:大多数面包含糖,这些糖会使你的血糖水平上升。当你的血糖水平上升,你的身体必须用额外的方式储存能量来加以补偿,比如脂肪。

4.面包的必需营养素很低

全麦、白面包都滚蛋:5个不该吃面包的理由

即便我们对面包以及注重营养的问题视而不见,我们剩下的也只有失望。面包的营养还不如其它的食物来源,例如新鲜蔬菜、水果、鱼、鸡蛋和肉类。

即使是全麦面包在营养方面也让人失望。它不仅自身营养价值低,而且会减少人体对其它真正有营养食物的吸收。原因有两个。首先是全麦块矿物中的植酸 ,比如铁、锌、钙的吸收。其次是因为麸质会损害肠道粘膜,减少营养物质的吸收。

谷物甚至没有所有的必需氨基酸,而人类需要用它们来对肌肉进行塑造和保持(不像香蕉是极好的氨基酸来源)。

结论:面包是传统的穷人食物,并不像看起来那么有营养。事实上,面包中的谷物阻碍体内营养素的吸收。相比于将面包变为你的主食,吃其它的食物会更有利于营养的吸收。

5.全麦会增加有害胆固醇

在很多研究中,人们发现超过12周的周期里,全麦面包会让体内小而密的低密度脂蛋白胆固醇增加60%。(小而密的低密度脂蛋白与心脏疾病有关)。

因为周边有关脂肪的流言,胆固醇是一个具有争议的话题。我们被引导着去相信脂肪导致了有害胆固醇,但实际上罪魁祸首可能是小麦。

结论:小麦已经被发现会增加低密度脂蛋白胆固醇,而且大多数面包中含有小麦。

最低限度:

面包是西方世界被消耗最多的产品。它随处可见,汉堡、三明治、圆面包等。我们经常被告知全麦面包是一天食物中最健康的,但随着真相浮出水面我们便无法进一步相信。面包相比于新鲜蔬菜、水果、肉类和鱼只是可怜的营养来源。

关于面包的主要问题是,事实上小麦在过去的50年中经历了如此的变化,我们的身体对它已经不适应了。虽然面包控制了整个购物车通道,但我们有很多好的理由不把它们捡起来。

[ 千里之外 via gnet]


如何兼职创业并避免风险

$
0
0

大多数人开始创建第一家公司时,仍然有一份正常工作。

这是有道理的:你不需要贷款,并且你并不需要资金。如果你“失败”了,你失去仅仅是时间,但考虑到你得到的乐趣,经历,以及一切你已经学会的东西,那几乎不是个失败。

如果你真的抓住了“创始人”的这个漏洞,那你只需要放弃所有的空闲时间,这对你来说是完美的。

但你也把自己置于一个危险的,法律上可能模棱两可的情况。如果管理不当,你将面对不必要的诉讼或者更坏的情况。

我就曾经处于这种情况:当我工作的时候我开始了我的创业,我雇的人都是非常有能力可以额外拥有自己的公司。但是因为它,我已经知道他们会被起诉,这是不是一种共赢的情况。

这里是我如何成功的秘诀:

我不是律师,但是……

哼哼,套头交易是如此的单调乏味。但我不得不说这:我不是律师。这一切都不是法律意见。如果你做或不做任何事情在这里列出的任何事情和有任何不好的事情发生,这不是我的错。

如果有什么好事发生,我很高兴也很荣幸接受你优厚的捐款。

挑一个对你约束不多但可以蓬勃发展的行业

您的公司有制约因素,而一个“正常”的公司没有: 

  1. 在正常营业时间内,您不能接听电话。 
  2. 在正常营业时间内,您不能回复电子邮件。 
  3. 您不能负担聘请三位开发人员增添功能和错误。 
  4. 您不得随时随地工作。 

您倾向于打破这些限制,但这是错误的做法。例如,你明白如何避免社交媒体的巨大风险,然后你可以为客户提供这方面优质的服务,你把这些都列到了你网站上。但是上面没有你的电话,客户上午 10 点发给您的邮件直到那天晚上还没有得到回复,(这意味着他们到第二天也看不到你的回复了)。 

这就是所谓的“错过的期望。”这也叫浪费机会 — 这一点就可以反驳你了。

正确的方法是,你不仅要在种种限制条件下工作,而且还要把它们转化为优势! 

例如,你的固定收入能负担使你被低估的服务。所以,如果有人抱怨没有电话号码,你可以指出,你负担不起每个员工 5 美元的手机。 

举另一个例子,选择一个产品,产品简单并有很少的功能是一个优势。 “我们只做一件事,我们做的比任何人都好”是一个伟大的营销口号。你没有时间做一个 Microsoft Excel 的类似版本,所以不要太复杂!

拥抱缓慢增长

我们每个人都有在最上层生存的梦想,无论是在你的博客或在 Hack News 上,你都希望得到 267 票和无数的 RSS 订阅,或者希望 1.1 版的问世就另你的博客出名。

如果你做到了,那么你干得不错,但这不是可以“计划”的,尤其它仅仅是在你的业余时间经营的生意,事实上,任何创业的公司应该瞄准缓慢,持续的增长,而不是爆发式的增长。

这不是一件坏事!缓慢生长使你有最大机会获得成功。慢意味着你的成功并不依赖于一些不太可能的,大规模的,完全在你的控制之外的事件。慢意味着你可以在早期彻底改变而不是放弃这个公司。你有一份工作,所以你不需要爆发式增长所获得的成功。

请记住,您的直接目标不是数百万美元, 而是建立一个足够牢固,牢固到让你可以放弃你的日常工作的企业。它不影响你的工作,参加一个“在线 MBA 课程”,并思考如何增加你的业务收入。它真正的含义是你的利润已经足够满足你省吃俭用的生活。或者,意味着你有的客户多以可以“证明”你有一个可行的商业模式,因此你可以以合理条件来筹集资金。

不要在工作中说谎

避免诉讼的最好方法就是要让 你的雇主知道你有一个副业。

我知道,你真的不相信我说的,你想秘密地进行。你害怕告诉他们,因为他们可能会解雇你,不信任你,或者在午饭时取笑你。

是的,这可能会发生。但什么是更糟的呢?比如说你被告了?如果你不认为他们会起诉你,那么你不应该害怕告诉他们!他们反正迟早会知道的。你需要根据你自己的情况在合适的时间告诉他们。

我总是告诉我的雇主,并且它总是最有效的方法。

以下是这些故事。

如何去告诉他们?写一个简单的文件,解释你在做什么。这里是我的模板:

致敬启者:
我有一个跟我的工作不冲突的爱好。我写这封信以确保你对我和我的副业没有任何误解。
我的爱好是....
我的爱好只是在家里和我自己的时间内进行;这和我的员工协议并没有任何冲突。我拥有它; [公司]对它没有所有权或权利。像任何的爱好一样,它可以产生少量收入。
虽然我不期待它,我明白,如果我的爱好与我的工作发生冲突,那么立即通知您这是我的责任。
谢谢您。

然后你得到这封信的回复就有法律效力。“授权 ”意味着回复你的人可以合法地代表公司。当然,这将取决于公司,但典型地,CXO 们或本公司的法律咨询是一个不错的选择。

得到老板的支持签名还是不够好。他有时侯也不能代表公司。

请检查您的就业协议 

确定在您关于副业的模板信中没有同您的就业协议冲突的语言。是真的吗?它最好是。 

就业协议通常偏向于用人单位,有时蛮横的条款说,你做什么,甚至在家里和自己的时间做任何无关的工作,会自动由公司拥有,并且还你负责通知报告你做的这些事情的,如果你没有做上述的事情,它可能会对你造成的无法估量的损害。

法院抛出一些令人震惊的合同,这是极端情况,你不能依赖于这一点。毕竟你不想去法院的。再说了,你已经签了字。 

阅读你的协议,并确保有副业这件事它是合法的。如果不是,你必须编写类似上面的一封信,明确地指出这是一个对你的就业协议来说没有冲突的例外。

不要使用公司的资产或网络,真的!

在大多数就业协议里的另一个条款是,任何你在工作中做的体力活,或者在公司的软件和设备里(如笔记本电脑,客户名单时,Photoshop)都自动归为公司的财产。

这一条款是公平的。如果你的项目真的就在旁边,你没有权利在工作时间做自己的生意。如果你的项目仅仅只是你的,它就不能被一个公司的笔记本电脑,公司的软件,或公司的互联网连接所协助。

当涉及到公司的财产,偏执一点。假设老大在监视。假设每一个笔记本电脑有一个秘密监视软件,记录所有击键,鼠标点击,屏幕截图,网站和你阅读或书写的电子邮件。假设你在互联网上做的一切都被记录,编目,标记,并被以某些人持续的监测。

现在我知道你是非常聪明的。你想要在白天处理一些技术支持的电子邮件,所以你使用匿名化的公司加 GMail-over-SSL 来绕过网络管理员。你只是想证明你在做一些合法的事情..

即使这样也还不够。如果他们决定起诉,他们得到看穿你的电子邮件记录(这就是所谓的“发现”)。然后,他们有你在工作时间内发送的 100 封邮件的记录。你输了。

知道了吗?别这样做。

时间管理是关键 

我在个人时间管理方面使非常糟糕的。我拖延,我是杂乱无章的,并且如果我不小心,我会用 2 个小时,失败在博客上,什么也没有表现出来只是捧腹大笑,但笑的眼泪在我的键盘搅炼。这也许也不错,就像我在网上赌场玩二十一点赢了一堆钱。那么至少我有钱来炫耀它。

一次创业已经产生了无限的工作量。这需要所有的时间,这对你来说是 40 小时/周,比它应该是少。你已经浪费不起时间,是否意味着不良的生活习惯或在错误的任务中工作。

这没有锦囊妙计 - 工作流程是个人的事情 - 但这里有一些技巧,让你很好地开始:

  • 经常检查电子邮件。
  • 收件箱清零 - 处理你的电子邮件直到什么也没有留下,这对身心都有好处。
  • GTD(把事情做完) - 这种技巧彻底改变了我的生活。少数人能够做到,但每个人都有一个或两个窍门,使他们更具效率。
  • 在工作中冲刺 - 短,活动的集中爆发消除来自工作内容切换的和中断的大量浪费。
  • 如果你没有睡够,您的工作效率直线下降。让一个小时的睡眠来换一个小时的编码永远不会有效。
  • 可以使用类似的 Rescue Time(免费!)工具来判断你的时间都花在哪里了;你知道在哪浪费了时间。 P.S.托尼·赖特,RescueTime 的创始人,创办了那家公司。
  • 首先优化最慢的任务 - 找到和优化最慢的任务,你的整体工作效率会得到惊人的提高。

你有什么秘诀?欢迎发表评论。

本文链接

不做7件事,提高生产力

$
0
0

编者注:此文是 CamMi Pham 的文章,原文发表在 Medium上。作者通过自身经历和一些科学证据说明,有时候努力工作未必是通往成功的正确路径。成功的关键不是努力而是巧干。因为时间是稀缺商品,有时候少干反而收效更佳。

忙未必就说明你有生产力。要想有生产力,更多的是要管理好你的精力而不是时间。要经营好你的生活。我们需要学会花费最小的精力得到最大的收益。不干以下 7 件事,我把每周的工作时间从 80 小时缩短到了 40 小时,但是完成的工作却多了很多。

1、不加班效率高

每周 5 天,每天 8 小时的工作制是福特在 1926 年的发现。实验表明,把每天工作时间从 10 小时降至 8 小时,每周工作时间从 6 天降至 5 天后,生产力反而提升了。

1980 年由商业圆桌会议发布的《建筑项目的加班效应》指出,无论从短期还是长期来看,工作越多,效率和生产力都会下降:

若每周工作时间超过 60 小时的情况持续超过两个月,生产力下降的累积效应将导致完工日期推迟,而同样的人数每周只工作 40 小时甚至还会更早完工。

美国军方研究表明,“每天少睡 1 小时持续一周会导致相当于 0.1 血醇水平的认知退化”。

《睡眠的秘密世界》指出:

熬夜之后无论白天你干得有多好,情绪不会太高。更重要的是前瞻性思考和行动的意愿、对冲动的抑制、乐观程度、同理心、情商等也会下降。

所以保证充足的睡眠对于维持高水平的生产力十分重要。

很多名人的经验是白天多小睡。达芬奇、拿破仑、爱迪生、罗斯福夫人、吉恩·奥特里、肯尼迪、洛克菲勒、丘吉尔、林顿·约翰逊、里根等都有小睡的习惯。

2、不要老说“好的”

根据 20/80 原理(帕累托原理),20% 的努力创造出 80% 的结果;但反过来 20% 的结果消耗了 80% 的努力。因此我们应该把精力集中在能产出 80% 结果的事情上,然后放弃其他的事情。如此就能把更多的时间集中在最重要的任务上。我们应该对低产出甚至无结果的任务停止说“yes”。

成功人士和非常成功人士的区别在于后者几乎对所有事情说“不”

——巴菲特

这就引出一个问题:我们该对哪些事情说“yes”,对哪些事情说“no”呢?如果想不出哪些事情值得花时间,不妨来个简单的分离测试。跟踪自己所做的一切事情然后尽可能优化。

我们中的大多数往往都说了太多的“yes”,因为这比拒绝要容易得多。没人想当坏人。

2012 年的消费者杂志发表了一项研究,研究人员把 120 人分成了 2 组。一组人训练成说“我不能(I can’t)”,另一组则说“我不(I don’t)”。结果很有趣:

告诉自己“我不能吃 X”的学生在 61% 的时间内选择了吃巧克力糖,而告诉自己“我不吃
X”的只有 36%。在说法上作这么简单的一个变化就能显著改善健康食品的选择。

所以,下次需要避免说 yes 的时候,直接说“我不”。

另一个避免不必要活动的技巧是 20 秒规则:对于不应该做的事情多给自己 20 秒的时间考虑。

3、不要事必躬亲

我曾经管理过一个非常大的社区,试图一个人做完所有事情,最后发现我干不了。我筋疲力尽,最后社区接管了自我管理的工作。令人惊奇的是大家做得比我还好。我因而领悟到了社区的力量。

我们必须意识到,在需要的时候可以去寻求帮助,这一点很重要。让做得更好的人接管你的一些工作对你来说更好。这可以让你花更多的时间在自己最重要的任务上。不要把时间浪费在自己解决问题上,让专家帮助你。

很多时候,哪怕朋友不能帮你,他们的陪伴也能让你更有生产力。

4、不要完美主义

达尔豪斯大学的心理学教授 Simon Sherry 的完美主义与生产力研究发现,完美主义是生产力的绊脚石:

完美主义者完成任务需要花费更多的时间
完美主义者因此等待完美时刻而耽搁。就商业而言,如果你等到了完美时刻时间已经太迟。
完美主义往往因为一叶障目而不见泰山,因为过于关注小事情而错失了大场面。

最好的完美时刻就是现在。

5、不要做重复工

根据 Tethys Solution 的一项研究,一支 5 人团队,如果把 3%、20%、25%、30% 和 70% 时间花在重复性工作上,经过 2 个月的生产力强化(引入自动化软件)后,时间相应降至 3%、10%、15%、15% 和 10%。

现在只要我需要重复做一件事情 5 次以上,我就会要求自己找个程序来替我做。不会编程没关系,如果不懂开发,那就去买程序。

人们往往不记得时间就是金钱。他们手工做的原因通常是因为这么做比较容易,不需要研究。处理 30 张图片似乎没有问题,但是如果是 30000 张呢?你只能靠软件去处理了。记住,你需要花钱去挣钱,而时间是你最有价值的商品。

6、决策不靠猜测

如果网站可以 SEO,那么生活也能。不要靠胡乱猜测做决定,要用数据支持你的决策。

不同领域有许多研究可供参考。比方说,你是否知道下午 4 点的时候人最容易分心?这是宾州大学助理教授 Robert Matchock 领导的一项研究。哪怕你找不到需要的数据,进行分离测试也不需要话太多的时间。

要不断问问自己,你打算如何去衡量和优化自己所做的一切事情 ?

7、不要老是工作

大多数人没有意识到,如果我们过于专注在某件事情上,就会画地为牢,跳不出框框。时不时从工作中脱身出来独处一下非常重要。按照《孤独的力量》的说法,独处的时间对于大脑和精神很有益处:

哈佛研究表明,如果某人相信自己正在独自体验某件事情时,其记忆会更持久更精确。另一项研究表明,一定时间的独处可令人更具同理心。尽管没人会质疑早期过多的孤僻生活是不健康的,但一定时间的独处可令青少年改善情绪提高成绩。

给反省留点时间很重要。当我们不去寻找解决方案时,后者却往往不期而至。

当然,我们不能一夜之间就能变得更有生产力。跟生活中的每一件事情一样,这也需要努力。守株待兔是等不到变化的。我们都需要多了解自己的身体,找到精力分配的最佳方式,这样才能拥有更成功更幸福的生活。

本文链接

NoSQL 在腾讯应用实践

$
0
0
转载自:http://djt.qq.com/article/view/234

  吴悦, http://t.qq.com/iwuyue  腾讯大讲堂特约讲师,腾讯T4技术专家。先后参与腾讯分布式文件系统(TFS),K-V存储,SQL集群,接入网关(TGW)的设计与研发。见证了腾讯Nosql 从07年诞生,08、09批量应用,10年至今应用于腾讯开放平台让更多的第三方开发者使用;关注于构建具有低成本、高性能、高可用,可扩展,易运营特点的互联网海量后台服务。目前任腾讯架构平台部平台开发中心技术总监。

 

  此文已发表在《程序员》杂志。


一、前言
  NoSQL的历史很长,最早可以追朔到Berkeley DB等嵌入式数据库的年代。互联网行业的高速发展对大数据的需求,为NoSQL的发展起到了推波助澜的作用。互联网时代的NoSQL,源起于Google为解决大数据的存储与计算而提出的GFS + Bigtable + Map Reduce。随后Hadoop  (Hdfs+Hbase+MapReduce)、 Hypertable、Memcached,Tokyo cabinet,Redis, Dynamo,Cassandra等等NoSQL 产品雨后春笋般的推出,使得Nosql技术广泛应用于互联网各个领域。
  在腾讯过去的几年中,互联网社交平台取得令人瞩目的发展。包括平台用户基数、在线、应用数都取得突飞猛进的增长;另外随着开放的加剧,还有越来越多的第三方选择社交平台开发应用。这些外部条件的变化对技术平台的而言,也带来了新挑战:除需要为用户提供更强的海量服务外,同时还需要提供开放的软件基础架构来帮助第三方开发海量服务。
  在解决这些问题的实践中,总结了很多经验。其中关键一点则是通过NoSQL技术来构建海量服务的数据层,并通过分析和总结出不同的业务场景和技术特点,为各种场景提供更合适的数据层解决方案。具体而言:
 相册、日志等UGC类应用,主要是自我产生数据,他人以浏览为主,其技术特点是读取量巨大,修改量低于读取量一个量级,数据量从几百T至P级不等。提供SAS、SATA级的TDB、TFS解决方案。
 农牧场等Social Game类应用,核心数据是用户背包数据,互动性很强,其技术特点是巨大读取量与修改量,数据量在百G级别。提供MEM级的TMEM解决方案。
 信息中心的Feeds类应用,其技术特点是巨大的修改量与读取量,数据量也在几十T到几百T不等。提供SSD级的TSSD解决方案。


二、【2006~2008】因QQ相册而研发TFS、TDB
  回顾NoSQL在腾讯的发展历程,需要从2006年腾讯分布式文件系统TFS      的研发开始谈起,TFS目的是在公司内部构建统一的存储平台,为各个BU提供文件系统服务。第一期的重点是要能够支持到QQ相册的快速发展。当时QQ相册使用传统企业级存储硬件+标准linux文件系统的老架构,在数百亿的图片数,每天近10亿长尾下载的规模下已难以为继。通过分析,老架构主要有下面三个问题:
 采用FC-SAN等高端企业级存储硬件,这些硬件主要是针对电信、银行等高ARPU值的行业客户而生,价格通常比较贵,对盛行免费的互联网企业来说,成本压力大。
 使用通用的linux文件系统,对相册海量小文件的场景,空间利用和IO性能都不能很好的满足要求。
 元数据与对象数据耦合,扩展性和可维护性较差,单机故障以及扩容都是异常繁琐的运维操作。
  TFS采用廉价的存储设备,在软件层面使用类似软raid的技术来满足系统基于不可靠硬件的可靠性要求。将对象数据与元数据分离:对象数据存储采用自研的CHUNK文件系统,inode节点更小,空间分配采用了基于append + delete更为紧凑的管理方式,使得单机最大可以支持数10亿的图片文件;元数据使用MYSQL存储。系统架构如下:


  上面的架构很好的满足了数百亿级别规模下的QQ相册业务发展。大致在07的时候,QQ相册采用TFS的新架构趋于稳定,同时业务发展需要,对用户上传也放开了限制,用户上传浏览的活跃度上升的一个新的量级,用户目录、文件索引等元数据规模突破千亿。在使用MYSQL应对如此大规模的元数据的场景下,暴露出一些问题:
 索引低效:在QQ相册的场景,上千亿的记录,使用MYSQL的B树索引索引的存储量消耗都在数TB到数十TB。 海量索引在无法全内存的情况会带来IO的多次访问,一方面增加了单次访问的时延,另一方面降低了磁盘的IO利用率。
 数据搬迁:每天数亿的图片上传导致系统扩容,IDC分布策略,导致数据搬迁是常态。使用MYSQL,使用select逐条记录方式搬迁,不同的记录分散在不同的磁盘偏移,一方面搬迁速度较慢,另一方面由迁移导致的磁盘随机IO与业务正常访问相互交织在一起,从而影响到在线业务访问。
 系统控制:MYSQL更多针对是各种数据通用场景所做的设计与开发,实现较复杂。在使用中遇到性能问题,异常故障时难以定位原因,对业务系统来说,已经是无法打破的天花板。
针对上面的问题结合业务需求,07年底研发出TDB用以替代MYSQL,TDB是一个典型的K-V存储系统。其特点是:接口简单,性能高效,具备优秀的扩展能力。


  数据层面,索引设计使用HASH,通过KEY可直接定位磁盘物理偏移,避免B树设计导致的二次定位磁盘性能开销,解决索引低效的问题。同时采用16MB大磁盘块的设计,使得TDB的数据迁移速度可达网卡性能上限,解决迁移性能问题。另外系统可控性更强,一方面因为是专用场景,所以可以简化设计,方便定位问题与优化更新;另一方面打通了存储系统到磁盘IO的控制路径,避免MYSQL的系统天花板。
  接入层面,为业务提供透明的访问代理,从而实现无缝的水平扩展。由于接口简单,并且是PAAS,业务使用非常方便,从07年底开始,在Qzone、朋友、群空间等社区应用中逐步取得了广泛应用。
三、【2009】Social Game催生的TMEM
  09年有一款叫农场的游戏大家应该不会陌生,农场的火爆带动了一批Social Game应用的兴起。其典型特点(1)好友间互动性很强,用户背包数据会被频繁的修改与读取;(2)交叉访问,无明显热点数据;举个例子来说,对于传统的应用来说,用户间交互相对较弱,活跃用户数据就是热点数据;而对于Social Game而言,用户交互性强,通过交叉访问,活跃用户也会频繁访问与修改非活跃用户数据。(3)放大效果明显;比如用户每次登陆,通常会遍历好友的农场,会遍历菜地偷菜,捉虫,一次偷菜、捉虫会导致多个用户的多个背包数据修改。这些行为一方面导致整个系统中无明显热点数据,用户传统的读缓存+写落地的方式则难以很好的满足这些业务的需求;另一方面庞大的用户基数之上的火爆应用,往往单款应用就会有数百万次每秒的访问量,这种海量访问不光对存储层面,同时对网络通讯层面提出更高的性能要求。
  针对以上新兴面临的问题,很明显TDB并不能很好的解决,所以09年围绕着网络通信,内存持久化两个方面,做了大量的设计与论证。09年底左右推出新的服务TMEM。


核心有两点:
 提供内核级KCCD网络通讯组件,提升网络通信性能,在网络包量吞吐方面相对于应用层提升了接近1倍的性能。
 通过将操作流水加数据镜像落地TFS,解决了内存持久化的问题。
  TMEM的出现很好的满足了业务海量访问的需求。不过毕竟内存介质的成本比较高,所以TMEM在小数据量的场景下,性价比比较高。但针对于中大数据规模的海量访问的场景,使用TMEM的成本偏高,而使用SAS介质的TDB,则IO性能又不能很好的满足。比如社区中各类应用产生的Feeds,数据量数十TB至数百TB,每天访问量数十亿至数百亿,应对该场景传统的做法必须得是前端内存缓存加上后端落地存储。分级存储导致的内存数据保护,各级间数据一致性,另外最为关键的一点,与Social Game类似,Feeds也是典型交叉访问,热点不是非常明显,等等这些都是数据层亟需解决的难题。
四、【2010】顺SSD之势的TSSD
  在10年的时候,引入了SSD存储介质,开始构建TSSD K-V存储系统。SSD的特点:有着很好的随机读取性能,往往单盘可达数万IOPS,远高于SAS、SATA的数百随机IOPS。容量方面也接近SAS盘的容量,可达数百GB。但SSD也有弊端:(1)寿命有限,随机写入的寿命相对于顺序写入为1/10左右;(2)随机写入场景,性能易受干扰,毛刺率较高;具体而言:受限于物理机制,SSD的存储单元只能先擦除才能写入,并且擦除次数有限,针对NAND芯片,在3000~5000次左右。其中擦除单元是512KB,写入单元是4KB。随机写入的场景,会带来写入放大。
  因此应用SSD存储介质,必须优化随机写入性能。TSSD通过构建地址映射,增加随机写入内存缓冲区,实现随机转顺序的写入;通过定期的垃圾回收机制,回收垃圾数据。


  TSSD系统中,单机可以支持容量数TB,性能随机数万次IOPS。这样基于TSSD使用简单的架构,更少的机器便可支持到容量数十至数百TB,性能数十万IOPS的Feeds类应用。
五、NoSQL小结
  至此,业已构建出基于内存、SSD、SAS、SATA的各类存储介质的存储系统,在上面也已提到各类存储系统所对应的使用场景。实际应用中,各种业务场景千变万化,有没有统一的方法来判别和选择合适的存储系统呢?大致在 1987 年,Jim Gray发表了这个"五分钟法则"的观点,简而言之,如果一条记录频繁被访问,就应该放到内存里,否则的话就应该待在硬盘上按需要再访问。这个临界点就是五分钟。这个看似经验公式,隐含的是硬件性能和成本两个方面的因素。大约在97的时候,Jim Gray再次回顾了该法则,并引入了SSD,验证了该法则依然正确。这里不在赘述该法则。
  很多情况下需要一种直接根据业务的访问模型,因此使用IO访问密度,即每GB的存储的IO访问次数,会更为直观。那看看目前常用的几种存储介质:


SATA:希捷2TB/7200转/SATA(ST32000644NS)
SAS:希捷300GB/15000转/SAS(ST3300657SS)
SSD :Intel 160GB X25-M G2 34nm
DRAM:三星8GB DDR3 1333 REG ECC
(中关村在线报价,人民币美元汇率:6.3157,2012/4/16)
  根据业务IO访问密度,选择合适的存储介质,就是根据存储介质的IO访问密度特性与价格来选择性价比最高的存储介质,即找到每种存储介质之间IO访问密度的临界点。
  临界点G(X,Y).IO per sec per GB = X.IO per sec per GB * Y.price per GB / X. price per GB,X与Y分别表示对比的两种存储介质,且Y.IO per sec per GB 大于 X.IO per sec per GB。根据上面的公式,可以得到表格如下:


  怎么理解上面的公式呢?举计算SATA与SAS之间的临界点为例,G(SATA,SAS).IO per sec per GB = SATA.IO per sec per GB* SAS.price per GB/SATA.price per GB = 1/20 * 0.71/0.23 = 0.15 per sec per GB。假设现在有1GB的数据,访问密度是X,X小于2/3,那么使用SAS介质则需要0.71$,如果选择SATA介质,则需要X/(1/20) * 0.23 $。当X为0.15时,选择SATA与SAS的成本是一样的,当大于0.15时,则使用SATA的成本相对于SAS则高;否则则低。SAS比SSD,SSD比DRAM也是类似。
  实际上对于一款存储介质而言,IO访问特性与单位每GB的成本是决定了其存在与生命力的关键因素。通常来讲,其IO访问特性一般来讲不会有革命性的变化,而单位每GB的成本却是可以控制的,所以厂商总是会围绕容量来不断的深入优化。
六、开放的挑战
  10年底开始,随着开放的加剧,越来越多的第三方选择社交平台开发应用。这些外部条件的变化对技术平台的而言,也带来了新挑战:除需要为用户提供更强的海量服务外,同时还需要提供开放的软件基础架构来帮助第三方开发海量服务。现有已成熟的NoSQL的架构能否被外部第三方接受,其关键的一点是接口的友好性和兼容性。采用的标准化的接口,会大大降低外部开发者的使用门槛。因此,在11年推出了CMEM 的NoSQL云存储服务,开发者可直接使用memcached接口,并即将支持redis接口。同时也提供了SQL云存储服务,开发者可用MYSQL客户端直接访问。后续推出CFS文件系统云存储服务,开发者可使用posix接口访问到TFS。


  回顾TFS、TDB、TMEM、TSSD、CMEM、CDB、CFS等一系列存储系统过去近6个年头的演进发展。有两点经验可供分享:
  1、 建议NoSQL开发者尽量选择构建PAAS服务。一方面对业务开发者而言,不需关心日常运维,使用更为方便,易于接受;另一方面更容易形成用户需求提出、实现与发布的闭环,从而方便小步快跑,快速迭代出完善的服务。
  2、 建议中小业务开发者尽量使用云服务。通常一个NoSQL服务所面临的挑战有两个方面:一方面是大家所直观感受的产品本身;另一方面是服务背后的运营体系。类似与铁路系统、消防系统,用户所直观感受到的火车、铁路,消防栓、消防车只是整个服务其中一个部分,是冰山海平面之上的部分;用户所感受不到的铁路规划、调度系统、消防规划、消防演练,报警系统等后台运营体系通常是冰山海平面以下的部分,往往需要有大量的人力和财力投入,可能是中小公司难以投入的。而在没有稳固的后台运营体系支撑下,类似动车事故、火灾等生产系统故障难以避免,并最终为业务带来不可估量的损失。
  从过去几年来看,硬件在变,存储介质的性能与容量在不断提升,并不断会有新存储介质的产生;业务在变,不断有新兴的产品和新的业务体验,并对后台系统提出新的挑战和需求;唯一不变的就是需要拥抱变化,为业务提供更贴切与更优化的存储服务。想了解更多关于腾讯TFS、TDB、TMEM、TSSD、CMEM、CDB、CFS等一系列存储系统过去近6个年头的演进发展,大家可以到腾讯大讲堂网站(djt.qq.com)翻阅架构平台部的分享文章,谢谢。



杨粼波 2014-06-15 11:39 发表评论

Android权限

$
0
0

Android权限说明
开发android程序的时候常常会设计到各种权限,程序执行需要读取到安全敏感项必需在androidmanifest.xml中声明相关权限请求,各种权限说明如下:
android.permission.ACCESS_CHECKIN_PROPERTIES
允许读写访问”properties”表在checkin数据库中,改值可以修改上传( Allows read/write accessto the “properties” table in the checkin database, to change valuesthat get uploaded)

android.permission.ACCESS_COARSE_LOCATION
允许一个程序访问CellID或WiFi热点来获取粗略的位置(Allows an application to accesscoarse (e.g., Cell-ID, WiFi) location)
android.permission.ACCESS_FINE_LOCATION
允许一个程序访问精良位置(如GPS) (Allows an application to access fine (e.g.,GPS) location)

android.permission.ACCESS_LOCATION_EXTRA_COMMANDS
允许应用程序访问额外的位置提供命令(Allowsan application to access extra location provider commands)

android.permission.ACCESS_MOCK_LOCATION
允许程序创建模拟位置提供用于测试(Allows an application to create mock locationproviders for testing)
android.permission.ACCESS_NETWORK_STATE
允许程序访问有关GSM网络信息(Allowsapplications to access information about networks)

android.permission.ACCESS_SURFACE_FLINGER
允许程序使用SurfaceFlinger底层特性(Allows an application to useSurfaceFlinger’s low level features)

android.permission.ACCESS_WIFI_STATE
允许程序访问Wi-Fi网络状态信息(Allows applications to access information aboutWi-Fi networks)

android.permission.ADD_SYSTEM_SERVICE
允许程序发布系统级服务(Allows an application to publish system-levelservices).

android.permission.BATTERY_STATS
允许程序更新手机电池统计信息(Allows an application to update the collectedbattery statistics)

android.permission.BLUETOOTH
允许程序连接到已配对的蓝牙设备(Allowsapplications to connect to paired bluetooth devices)

android.permission.BLUETOOTH_ADMIN
允许程序发现和配对蓝牙设备(Allows applications to discover and pair bluetoothdevices)

android.permission.BRICK
请求能够禁用设备(非常危险)(Required to be able to disable the device (very*erous!).)

android.permission.BROADCAST_PACKAGE_REMOVED
允许程序广播一个提示消息在一个应用程序包已经移除后(Allows an application to broadcast anotification that an application package has been removed)

android.permission.BROADCAST_STICKY
允许一个程序广播常用intents(Allows an application to broadcast stickyintents)

android.permission.CALL_PHONE
允许一个程序初始化一个电话拨号不需通过拨号用户界面需要用户确认(Allows an application to initiate aphone call without going through the Dialer user interface for theuser to confirm the call being placed.)

android.permission.CALL_PRIVILEGED
允许一个程序拨打任何号码,包含紧急号码无需通过拨号用户界面需要用户确认(Allows an application to callany phone number, including emergency numbers, without goingthrough the Dialer user interface for the user to confirm the callbeing placed)

android.permission.CAMERA
请求访问使用照相设备(Required to be able to access the camera device. )

android.permission.CHANGE_COMPONENT_ENABLED_STATE
允许一个程序是否改变一个组件或其他的启用或禁用(Allows an application to change whether anapplication component (other than its own) is enabled or not. )

android.permission.CHANGE_CONFIGURATION
允许一个程序修改当前设置,如本地化(Allows an application to modify the currentconfiguration, such as locale. )

android.permission.CHANGE_NETWORK_STATE
允许程序改变网络连接状态(Allows applications to change network connectivitystate)

android.permission.CHANGE_WIFI_STATE
允许程序改变Wi-Fi连接状态(Allows applications to change Wi-Fi connectivitystate)

android.permission.CLEAR_APP_CACHE
允许一个程序清楚缓存从所有安装的程序在设备中(Allows an application to clear the caches ofall installed applications on the device. )

android.permission.CLEAR_APP_USER_DATA
允许一个程序清除用户设置(Allows an application to clear user data)

android.permission.CONTROL_LOCATION_UPDATES
允许启用禁止位置更新提示从无线模块(Allows enabling/disabling location updatenotifications from the radio. )

android.permission.DELETE_CACHE_FILES
允许程序删除缓存文件(Allows an application to delete cache files)

android.permission.DELETE_PACKAGES
允许一个程序删除包(Allows an application to delete packages)

android.permission.DEVICE_POWER
允许访问底层电源管理(Allows low-level access to power management)

android.permission.DIAGNOSTIC
允许程序RW诊断资源(Allows applications to RW to diagnostic resources. )

android.permission.DISABLE_KEYGUARD
允许程序禁用键盘锁(Allows applications to disable the keyguard )

android.permission.DUMP
允许程序返回状态抓取信息从系统服务(Allows an application to retrieve state dumpinformation from system services.)

android.permission.EXPAND_STATUS_BAR
允许一个程序扩展收缩在状态栏,android开发网提示应该是一个类似windowsmobile中的托盘程序(Allows an application to expand or collapse thestatus bar. )

android.permission.FACTORY_TEST
作为一个工厂测试程序,运行在root用户(Run as a manufacturer test application,running as the root user. )

android.permission.FLASHLIGHT
访问闪光灯,android开发网提示htcDream不包含闪光灯(Allows access to the flashlight )

android.permission.FORCE_BACK
允许程序强行一个后退操作是否在顶层activities(Allows an application to force a BACKoperation on whatever is the top activity. )

android.permission.FOTA_UPDATE
暂时不了解这是做什么使用的,android开发网分析可能是一个预留权限.

android.permission.GET_ACCOUNTS
访问一个帐户列表在Accounts Service中(Allows access to the list of accounts inthe Accounts Service)

android.permission.GET_PACKAGE_SIZE
允许一个程序获取任何package占用空间容量(Allows an application to find out the spaceused by any package. )

android.permission.GET_TASKS
允许一个程序获取信息有关当前或最近运行的任务,一个缩略的任务状态,是否活动等等(Allows an application toget information about the currently or recently running tasks: athumbnail representation of the tasks, what activities are runningin it, etc.)

android.permission.HARDWARE_TEST
允许访问硬件(Allows access to hardware peripherals. )

android.permission.INJECT_EVENTS
允许一个程序截获用户事件如按键、触摸、轨迹球等等到一个时间流,android 开发网提醒算是hook技术吧(Allows anapplication to inject user events (keys, touch, trackball) into theevent stream and deliver them to ANY window.)

android.permission.INSTALL_PACKAGES
允许一个程序安装packages(Allows an application to install packages. )

android.permission.INTERNAL_SYSTEM_WINDOW
允许打开窗口使用系统用户界面(Allows an application to open windows that are foruse by parts of the system user interface. )

android.permission.INTERNET
允许程序打开网络套接字(Allows applications to open network sockets)

android.permission.MANAGE_APP_TOKENS
允许程序管理(创建、催后、 z- order默认向z轴推移)程序引用在窗口管理器中(Allows an application tomanage (create, destroy, Z-order) application tokens in the windowmanager. )

android.permission.MASTER_CLEAR
目前还没有明确的解释,android开发网分析可能是清除一切数据,类似硬格机

android.permission.MODIFY_AUDIO_SETTINGS
允许程序修改全局音频设置(Allows an application to modify global audiosettings)

android.permission.MODIFY_PHONE_STATE
允许修改话机状态,如电源,人机接口等(Allows modification of the telephony state –power on, mmi, etc.

android.permission.MOUNT_UNMOUNT_FILESYSTEMS
允许挂载和反挂载文件系统可移动存储(Allows mounting and unmounting file systems forremovable storage. )

android.permission.PERSISTENT_ACTIVITY
允许一个程序设置他的activities显示(Allow an application to make its activitiespersistent. )

android.permission.PROCESS_OUTGOING_CALLS
允许程序监视、修改有关播出电话(Allows an application to monitor, modify, or abortoutgoing calls)

android.permission.READ_CALENDAR
允许程序读取用户日历数据(Allows an application to read the user’s calendardata.)

android.permission.READ_CONTACTS
允许程序读取用户联系人数据(Allows an application to read the user’s contactsdata.)

android.permission.READ_FRAME_BUFFER
允许程序屏幕波或和更多常规的访问帧缓冲数据(Allows an application to take screen shotsand more generally get access to the frame buffer data)

android.permission.READ_INPUT_STATE
允许程序返回当前按键状态(Allows an application to retrieve the current state ofkeys and switches. )

android.permission.READ_LOGS
允许程序读取底层系统日志文件(Allows an application to read the low-level systemlog files. )

android.permission.READ_OWNER_DATA
允许程序读取所有者数据(Allows an application to read the owner’s data)

android.permission.READ_SMS
允许程序读取短信息(Allows an application to read SMS messages.)

android.permission.READ_SYNC_SETTINGS
允许程序读取同步设置(Allows applications to read the sync settings)

android.permission.READ_SYNC_STATS
允许程序读取同步状态(Allows applications to read the sync stats)

android.permission.REBOOT
请求能够重新启动设备(Required to be able to reboot the device. )

android.permission.RECEIVE_BOOT_COMPLETED
允许一个程序接收到 ACTION_BOOT_COMPLETED广播在系统完成启动(Allows an application toreceive the ACTION_BOOT_COMPLETED that is broadcast after thesystem finishes booting. )

android.permission.RECEIVE_MMS
允许一个程序监控将收到MMS彩信,记录或处理(Allows an application to monitor incomingMMS messages, to record or perform processing on them. )

android.permission.RECEIVE_SMS
允许程序监控一个将收到短信息,记录或处理(Allows an application to monitor incoming SMSmessages, to record or perform processing on them.)

android.permission.RECEIVE_WAP_PUSH
允许程序监控将收到WAPPUSH信息(Allows an application to monitor incoming WAP pushmessages. )

android.permission.RECORD_AUDIO
允许程序录制音频(Allows an application to record audio)

android.permission.REORDER_TASKS
允许程序改变Z轴排列任务(Allows an application to change the Z-order oftasks)

android.permission.RESTART_PACKAGES
允许程序重新启动其他程序(Allows an application to restart otherapplications)

android.permission.SEND_SMS
允许程序发送SMS短信(Allows an application to send SMS messages)

android.permission.SET_ACTIVITY_WATCHER
允许程序监控或控制activities已经启动全局系统中Allows an application to watch andcontrol how activities are started globally in the system.

android.permission.SET_ALWAYS_FINISH
允许程序控制是否活动间接完成在处于后台时Allows an application to control whetheractivities are immediately finished when put in the background.

android.permission.SET_ANIMATION_SCALE
修改全局信息比例(Modify the global animation scaling factor.)

android.permission.SET_DEBUG_APP
配置一个程序用于调试(Configure an application for debugging.)

android.permission.SET_ORIENTATION
允许底层访问设置屏幕方向和实际旋转(Allows low-level access to setting theorientation (actually rotation) of the screen.)

android.permission.SET_PREFERRED_APPLICATIONS
允许一个程序修改列表参数PackageManager.addPackageToPreferred()和PackageManager.removePackageFromPreferred()方法(Allows anapplication to modify the list of preferred applications with thePackageManager.addPackageToPreferred() 
andPackageManager.removePackageFromPreferred() methods.)

android.permission.SET_PROCESS_FOREGROUND
允许程序当前运行程序强行到前台(Allows an application to force any currentlyrunning process to be in the foreground.)

android.permission.SET_PROCESS_LIMIT
允许设置最大的运行进程数量(Allows an application to set the maximum number of(not needed) application processes that can be running. )

android.permission.SET_TIME_ZONE
允许程序设置时间区域(Allows applications to set the system time zone)

android.permission.SET_WALLPAPER
允许程序设置壁纸(Allows applications to set the wallpaper )

android.permission.SET_WALLPAPER_HINTS
允许程序设置壁纸hits(Allows applications to set the wallpaper hints)

android.permission.SIGNAL_PERSISTENT_PROCESSES
允许程序请求发送信号到所有显示的进程中(Allow an application to request that a signalbe sent to all persistent processes)

android.permission.STATUS_BAR
允许程序打开、关闭或禁用状态栏及图标Allows an application to open, close, or disablethe status bar and its icons.

android.permission.SUBSCRIBED_FEEDS_READ
允许一个程序访问订阅RSS Feed内容提供(Allows an application to allow access thesubscribed feeds ContentProvider. )

android.permission.SUBSCRIBED_FEEDS_WRITE
系统暂时保留改设置,android开发网认为未来版本会加入该功能。

android.permission.SYSTEM_ALERT_WINDOW
允许一个程序打开窗口使用 TYPE_SYSTEM_ALERT,显示在其他所有程序的顶层(Allows an applicationto open windows using the type TYPE_SYSTEM_ALERT, shown on top ofall other applications. )

android.permission.VIBRATE
允许访问振动设备(Allows access to the vibrator)

android.permission.WAKE_LOCK
允许使用PowerManager的 WakeLocks保持进程在休眠时从屏幕消失( Allows using PowerManagerWakeLocks to keep processor from sleeping or screen fromdimming)

android.permission.WRITE_APN_SETTINGS
允许程序写入API设置(Allows applications to write the apn settings)

android.permission.WRITE_CALENDAR
允许一个程序写入但不读取用户日历数据(Allows an application to write (but not read)the user’s calendar data. )

android.permission.WRITE_CONTACTS
允许程序写入但不读取用户联系人数据(Allows an application to write (but not read) theuser’s contacts data. )

android.permission.WRITE_GSERVICES
允许程序修改Google服务地图(Allows an application to modify the Google servicemap. )

android.permission.WRITE_OWNER_DATA
允许一个程序写入但不读取所有者数据(Allows an application to write (but not read) theowner’s data.)

android.permission.WRITE_SETTINGS
允许程序读取或写入系统设置(Allows an application to read or write the systemsettings. )

android.permission.WRITE_SMS
允许程序写短信(Allows an application to write SMS messages)

android.permission.WRITE_SYNC_SETTINGS
允许程序写入同步设置(Allows applications to write the sync settings)


已有 0人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐



存储系统的那些事

$
0
0

  存储系统从其与生俱来的使命来说,就难以摆脱复杂系统的魔咒。无论是从单机时代的文件系统,还是后来C/S或B/S结构下数据库这样的存储中间件兴起,还是如今炙手可热的云存储服务来说,存储都很复杂,而且是越来越复杂。

  存储为什么会复杂,要从什么是存储谈起。存储这个词非常平凡,存储 + 计算(操作)就构成了一个朴素的计算机模型。简单来说,存储就是负责维持计算系统的状态的单元。从维持状态的角度,我们会有最朴素的可靠性要求。比如单机时代的文件系统,机器断电、程序故障、系统重启等常规的异常,文件系统必须可以正确地应对,甚至对于磁盘扇区损坏,文件系统也需要考虑尽量将损失降到最低。对于大部分的业务程序而言,你只需要重点关注业务的正常分支流程就行,对于出乎意料的情况,通常只需抛出一个错误,告诉用户你不该这么玩。但是对于存储系统,你需要花费绝大部分精力在各种异常情况的处理上,甚至你应该认为,这些庞杂的、多样的错误分支处理,才是存储系统的“正常业务逻辑”。

  到了互联网时代,有了C/S或B/S结构,存储系统又有了新指标:可用性。为了保证服务质量,那些用户看不见的服务器程序必须时时保持在线,最好做到逻辑上是不宕机的(可用性100%)。服务器程序怎么才能做到高可用性?答案是存储中间件。没有存储中间件,意味着所有的业务程序,都必须考虑每做一步就对状态进行持久化,以便自己挂掉后另一台服务器(或者自己重启后),知道之前工作到哪里了,接下去应该做些什么。但是对状态进行持久化(也就是存储)会非常繁琐,如果每个业务都自己实现,负担无疑非常沉重。但如果有了高可用的存储中间件,服务器端的业务程序就只需操作存储中间件来更新状态,通过同时启动多份业务程序的实例做互备和负载均衡,很容易实现业务逻辑上不宕机。

  所以,数据库这样的存储中间件出现基本上是历史必然。尽管数据库很通用,但它决不会是唯一的存储中间件。比如业务中用到的富媒体(图片、音视频、Office文档等),我们很少会去存储到数据库中,更多的时候我们会把它们放在文件系统里。但是单机时代诞生的文件系统,真的是最适合存储这些富媒体数据的么?不,文件系统需要改变,因为:

  1. 伸缩性。单机文件系统的第一个问题是单机容量有限,在存储规模超过一台机器可管理的时候,应该怎么办。
  2. 性能瓶颈。通常,单机文件系统在文件数目达到临界点后,性能会快速下降。在4TB的大容量磁盘越来越普及的今天,这个临界点相当容易到达。
  3. 可靠性要求。单机文件系统通常只是单副本的方案,但是今天单副本的存储早已无法满足业务的可靠性要求。数据需要有冗余(比较经典的做法是3副本),并且在磁盘损坏时及早修复丢失的数据,以避免所有的副本损坏造成数据丢失。
  4. 可用性要求。单机文件系统通常只是单副本的方案,在该机器宕机后,数据就不可读取,也不可写入。

  在分布式存储系统出现前,有一些基于单机文件系统的改良版本被一些应用采纳。比如在单机文件系统上加 RAID5 做数据冗余,来解决单机文件系统的可靠性问题。假设 RAID5 的数据修复时间是1天(实际上往往做不到,尤其是业务系统本身压力比较大的情况下,留给 RAID 修复用的磁盘读写带宽很有限),这种方案单机的可靠性大概是100年丢失一次数据(即可靠性是2个9)。看起来尚可?但是你得小心两种情况。一种是你的集群规模变大,你仍然沿用这个土方法,比如你现在有 100 台这样的机器,那么就会变成1年就丢失一次数据。另一种情况是如果实际数据修复时间是 3 天,那么单机的可靠性就直降至4年丢失一次数据,100台就会是15天丢失一次数据。这个数字显然无法让人接受。

  Google GFS 是很多人阅读的第一份分布式存储的论文,这篇论文奠定了 3 副本在分布式存储系统里的地位。随后 Hadoop 参考此论文实现了开源版的 GFS —— HDFS。但关于 Hadoop 的 HDFS 实际上业界有不少误区。GFS 的设计有很强的业务背景特征,本身是用来做搜索引擎的。HDFS 更适合做日志存储和日志分析(数据挖掘),而不是存储海量的富媒体文件。因为:

  1. HDFS 的 block 大小为 64M,如果文件不足 64M 也会占用 64M。而富媒体文件大部分仍然很小,比如图片常规尺寸在 100K 左右。有人可能会说我可以调小 block 的尺寸来适应,但这是不正确的做法,HDFS 的架构是为大文件而设计的,不可能简单通过调整 block 大小就可以满足海量小文件存储的需求。
  2. HDFS 是单 Master 结构,这决定了它能够存储的元数据条目数有限,伸缩性存在问题。当然作为大文件日志型存储,这个瓶颈会非常晚才遇到;但是如果作为海量小文件的存储,这个瓶颈很快就会碰上。
  3. HDFS 仍然沿用文件系统的 API 形式,比如它有目录这样的概念。在分布式系统中维护文件系统的目录树结构,会遭遇诸多难题。所以 HDFS 想把 Master 扩展为分布式的元数据集群并不容易。

  分布式存储最容易处理的问题域还是单键值的存储,也就是所谓的 Key-Value 存储。只有一个 Key,就意味着我们可以通过对 Key 做 Hash,或者对 Key 做分区,都能够让请求快速定位到特定某一台存储机器上,从而转化为单机问题。这也是为什么在数据库之后,会冒出来那么多 NoSQL 数据库。因为数据库和文件系统一样,最早都是单机的,在伸缩性、性能瓶颈(在单机数据量太大时)、可靠性、可用性上遇到了相同的麻烦。NoSQL 数据库的名字其实并不恰当,他们更多的不是去 SQL,而是去关系(我们知道数据库更完整的称呼是关系型数据库)。有关系意味着有多个索引,也就是有多个 Key,而这对数据库转为分布式存储系统来说非常不利。

  七牛云存储的设计目标是针对海量小文件的存储,所以它对文件系统的第一个改变也是去关系,也就是去目录结构(有目录意味着有父子关系)。所以七牛云存储不是文件系统(File System),而是键值存储(Key-Value Storage),用时髦点的话说是对象存储(Object Storage)。不过七牛自己喜欢把它叫做资源存储(Resource Storage),因为它是用来存储静态资源文件的。蛮多七牛云存储的新手会问,为什么我在七牛的 API 中找不到创建目录这样的 API,根本原因还是受文件系统这个经典存储系统的影响。

  七牛云存储的第一个实现版本,从技术上来说是经典的 3 副本的键值存储。它由元数据集群和数据块集群组成。每个文件被切成了 4M 为单位的一个个数据块,各个数据块按 3 副本做冗余。但是作为云存储,它并不仅仅是一个分布式存储集群,它需要额外考虑:

  1. 网络问题,也就是文件的上传下载问题。文件上传方面,我们得考虑在相对比较差的网络条件下(比如2G/3G网络)如何确保文件能够上传成功,大文件(七牛云存储的单文件大小理论极限是1TB)如何能够上传成功,如何能够更快上传。文件下载加速方面,考虑到 CDN 已经发展了 10 多年的历史,非常成熟,我们决定基于 CDN 来做下载加速。
  2. 数据处理。当用户文件托管到了七牛,那么针对文件内容的数据处理需求也会自然衍生。比如我们第一个客户就给我们提了图片缩略图相关的需求。在音视频内容越来越多的时候,自然就有了音视频转码的需求。可以预见在Office文档多了后,也就会有 Office 文档转换的需求。

  所以从技术上来说,七牛云存储是这样的:

  七牛云存储 = 分布式存储集群 + 上传加速网络(下载外包给CDN) + 数据处理集群

  网络问题并不是七牛要解决的核心问题,只是我们要面对的现实困难。所以在这个问题上如果能够有足够专业的供应商,能够外包我们会尽可能外包。而分布式存储集群的演进和优化,才是我们最核心的事情。早在 2012 年 2 月,我们就启动了新一代基于纠删码算术冗余的存储系统的研发。新存储系统的关注焦点在:

  1. 成本。经典的 3 副本存储系统虽然经典,但是代价也是高昂的,需要我们投入 3 倍的存储成本。那么有没有保证高可靠和高可用的前提下把成本做下来?
  2. 可靠性。如何进一步提升存储系统的可靠性?答案是更高的容错能力(从允许同时损坏2块盘到允许同时损坏4块盘),更快的修复速度(从原先3小时修复一块坏盘到30分钟修复一块坏盘)。
  3. 伸缩性。如何从系统设计容量、IO吞吐能力、网络拓扑结构等角度,让系统能够支持EB级别的数据存储规模?关于伸缩性这个话题,涉及的点是全方位的,本文不展开讨论,后面我们另外独立探讨这个话题(让我们把焦点放在成本和可靠性上)。

  在经过了四个大的版本迭代,七牛新一代云存储(v2)终于上线。新存储的第一大亮点是引入了纠删码(EC)这样的算术冗余方案,而不再是经典的 3 副本冗余方案。我们的 EC 采用的是 28 + 4,也就是把文件切分为 28 份,然后再根据这 28 份数据计算出 4 份冗余数据,最后把这 32 份数据存储在 32 台不同的机器上。这样做的好处是既便宜,又提升了可靠性和可用性。从成本角度,同样是要存储 1PB 的数据,要买的存储服务器只需 3 副本存储的 36.5%,经济效益相当好。从可靠性方面,以前 3 副本只能允许同时损坏2块盘,现在能够允许同时损坏4块盘,直观来说这大大改善了可靠性(后面讨论可靠性的时候我们给出具体的数据)。从可用性角度,以前能够接受 2 台服务器下线,现在能够同时允许 4 台服务器下线。

  新存储的第二大亮点是修复速度,我们把单盘修复时间从 3 小时提升到了 30 分钟以内。修复时间同样对提升可靠性有着重要意义(后面讨论可靠性的时候我们给出具体的数据)。这个原因是比较容易理解的。假设我们的存储允许同时坏 M 块盘而不丢失数据,那么集群可靠性,就是看在单位修复时间内,同时损坏 M+1 块盘的概率。例如,假设我们修复时间是 3 小时,那么 3 副本集群的可靠性就是看 3 小时内同时损坏 3 块盘的概率(也就是丢数据的概率)。

  让我们回到存储系统最核心的指标 —— 可靠性。首先,可靠性和集群规模是相关的。假设我们有 1000 块磁盘的集群,对于 3 副本存储系统来说,这 1000 块盘同时坏 3 块就会发生数据丢失,这个概率显然比 3 块盘同时坏 3 块要高很多。基于这一点,有些人会想这样的土方法:那我要不把集群分为 3 块磁盘一组互为镜像,1000 块盘就是 333 组(不好意思多了1块,我们忽略这个细节),是不是可以提升可靠性?这些同学忽略了这样一些关键点:

  1. 3 块盘同时坏 3 块盘(从而丢失数据)的概率为 p,那么 333 组这样的集群,丢失数据的概率是 1-(1-p)^333 ≈ p * 333,而不是 p。
  2. 互为镜像的麻烦之处是修复速度存在瓶颈。坏一块盘后你需要找一个新盘进行数据对拷,而一块大容量磁盘数据对拷的典型时间是 15 小时(我们后面将给出 15 小时同时坏 3 块盘的概率)。要想提升这个修复速度,第一步我们就需要打破镜像带来的束缚。

  如果一个存储系统的修复时间是恒定的,那么这个存储集群在规模扩大的时候,必然伴随着可靠性的降低。所以最理想的情况是集群越大,修复速度越快。这样才能抵消因集群增大导致坏盘概率增加带来负面影响。计算表明,如果我们修复速度和集群规模成正比(线性关系),那么集群随着规模增大,可靠性会越来越高。

  下表列出了1000块硬盘的存储集群在不同存储方案、不同修复时间下的可靠性计算结果:

副本存储方案

容错度(M)

修复时间

数据丢失概率(P)

可靠性

3副本方案

2

30分钟

1. 00E-08

8个9

3小时

1. 00E-05

5个9

15小时

1. 00E-02

2个9

28+4算术冗余方案

4

30分钟

1. 00E-16

16个9

3小时

1. 00E-11

11个9

15小时

1. 00E-07

7个9

  对于数据丢失概率具体的计算公式和计算方法,由于篇幅所限,本文中不做展开,我会另找机会讨论。

  对我个人而言,七牛新一代云存储(v2)的完成,了了我多年的夙愿。但七牛不会就此停止脚步。我们在存储系统上又有了一些好玩的想法。从长远来说,单位存储的成本会越来越廉价(硬件和软件系统都会推动这个发展趋势)。而存储系统肯定会越来越复杂。例如,有赖于超高的容错能力,七牛对单块磁盘的可靠性要求降低了很多,这就为未来我们采用桌面硬盘而不是企业硬盘作为存储介质打下基础。但是单块磁盘可靠性的降低,则会进一步推动存储系统往复杂的方向发展。基于这个推理,我认为存储必然需要转为云服务,成为水电煤一样的基础设施。存储系统越来越复杂,越来越专业,这就导致自建存储的难度和成本越来越高,自建存储的必要性也越来越低。必然有那么一天,你会发现云存储的成本远低于自建存储的成本,到时自建存储就会是纯投入而无产出,也就没有多少人会去热衷于干这样的事情了。

JAVA实现对BMP图片的读取

$
0
0

BMP图片格式,是windows自带的一个图片格式,(*bmp),在windows的系统下都支持这种格式,bmp格式与设备无关的位图(DIB)格式,BMP简称位图,BMP的原始数据是没有经过压缩处理的  占用的空间比其它格式的图片要大

 

   BMP由四部分组成 ,位图文件头 , 位图信息头 ,  颜色 , 图像数据区

 

BMP图片是三个字节为一个颜色保存,将字节拼接为int需要使用位移来做

 

位图文件头 (12个字节):

 0000-0001:文件标识,为字母ASCII码“BM”,42 4D。

0002-0005:整个文件大小,单位字节。
0006-0009:这个位置的数字是被微软保留的
000A-000D:记录图像数据区的起始位置。从文件开始到位图数据(bitmap data)之间的偏移量。 
位图信息头()
000E-0011:图像描述信息块的大小
0012-0015:图像宽度。以像素为单位。
0016-0019:图像高度。以像素为单位。
001A-001B:图像的plane总数(恒为1)。
001C-001D:记录像素,也就是颜色。1 - Monochrome bitmap,4 - 16 color bitmap,8 - 256 color bitmap,F - 16位位图,18 - 24bit (true color) bitmap,20 - 32位位图。

001E-0021:数据压缩方式(数值位0:不压缩;1:8位压缩;2:4位压缩;3:Bitfields压缩)。
0022-0025:图像区数据的大小。单位字节,该数必须是4的倍数。
0026-0029:水平每米有多少像素,在设备无关位图(.DIB)中,每字节以00H填写。
002A-002D:垂直每米有多少像素,在设备无关位图(.DIB)中,每字节以00H填写。
002E-0031:此图像所用的颜色数。

0032-0035:指定重要的颜色数。当该域的值等于颜色数时(或者等于0时),表示所有颜色都一样重要。



 

颜色
每一个字节表示一个颜色 r 0~255 g  0~255  b 0~255  保留一位  
 
图像数据区
从下到上 从左往右的顺序扫描
 
 
JAVA写一个读取BMP图片
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;

import java.awt.Graphics;
import java.io.BufferedInputStream;
import java.io.FileInputStream;

import javax.swing.JFrame;
import javax.swing.JPanel;

/**
 * 图片查看器的窗口
 * 
 * @author Administrator
 * 
 */
public class priUI extends JFrame {
	public static void main(String[] args) {
		priUI ui = new priUI();
		ui.initUI();
	}

	public void initUI() {

		this.setSize(600, 500);
		this.setTitle("图片查看器");

		// 设置布局
		FlowLayout layout = new FlowLayout();
		this.setLayout(layout);

		JPanel center = new myPanel();
		center.setPreferredSize(new Dimension(400, 300));
		center.setBackground(Color.WHITE);
		this.add(center);

		this.setDefaultCloseOperation(3);
		this.setVisible(true);
	}

	/**
	 * 读取BMP文件的方法(BMP24位)
	 */

	public int[][] readFile(String path) {

		try {
			// 创建读取文件的字节流
			FileInputStream fis = new FileInputStream(path);
			BufferedInputStream bis = new BufferedInputStream(fis);
			// 读取时丢掉前面的18位,
			// 读取图片的18~21的宽度
			bis.skip(18);
			byte[] b = new byte[4];
			bis.read(b);
			// 读取图片的高度22~25
			byte[] b2 = new byte[4];
			bis.read(b2);

			// 得到图片的高度和宽度
			int width = byte2Int(b);
			int heigth = byte2Int(b2);
			// 使用数组保存得图片的高度和宽度
			int[][] date = new int[heigth][width];

			int skipnum = 0;
			if (width * 3 / 4 != 0) {
				skipnum = 4 - width * 3 % 4;
			}
			// 读取位图中的数据,位图中数据时从54位开始的,在读取数据前要丢掉前面的数据
			bis.skip(28);
			for (int i = 0; i < date.length; i++) {
				for (int j = 0; j < date[i].length; j++) {
					// bmp的图片在window里面世3个byte为一个像素
					int blue = bis.read();
					int green = bis.read();
					int red = bis.read();
					// 创建一个Color对象,将rgb作为参数传入其中
					Color c = new Color(red, green, blue);
					// Color c = new Color(blue,green,red);
					// 将得到的像素保存到date数组中
					date[i][j] = c.getRGB();
				}
				// 如果补0的个数不为0,则需要跳过这些补上的0
				if (skipnum != 0) {
					bis.skip(skipnum);
				}
			}
			return date;
		} catch (Exception e) {
			e.printStackTrace();

		}
		return null;

	}

	// 将四个byte拼接成一个int
	public int byte2Int(byte[] by) {
		int t1 = by[3] & 0xff;
		int t2 = by[2] & 0xff;
		int t3 = by[1] & 0xff;
		int t4 = by[0] & 0xff;
		int num = t1 << 24 | t2 << 16 | t3 << 8 | t4;
		return num;

	}

	class myPanel extends JPanel {
		public void paint(Graphics g) {
			super.paint(g);
			// 读取数据
			int[][] date = readFile("C:\\Users\\Administrator\\Desktop\\meinv.bmp");
			// 判断是否存在
			if (date != null) {
				// this.setPreferredSize(new
				// Dimension(date[0].length,date.length));
				this.setPreferredSize(new Dimension(date[0].length, date.length));
				// 遍历
				for (int i = 0; i < date.length; i++) {
					for (int j = 0; j < date[i].length; j++) {
						Color c = new Color(date[i][j]);
						g.setColor(c);
						g.drawLine(j, date.length - i, j, date.length - i);
					}
				}
			}
		}
	}
}
 


 

 


已有 0人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐



部分华为手机不显示logcat问题原因是手机底层开关没打开

$
0
0

部分华为手机不显示logcat问题原因是手机底层开关没打开

 

部分华为手机不显示log问题原因是手机底层开关没打开。

 

http://hi.baidu.com/windgoing/item/507be1a9ac067d2d8919d3ea

 

转自这个连接。

 

1)进入工程模式

 

   有两种方式可以进入工程模式:

 

     a. 在拨号界面输入“*#*#2846579#*#*”

 

     b. 若是小米4.0系统(MIUI),进入“设置-->全部设置-->原厂设置-->工程模式”

 

 

 

 

2) 打开Log

 

    1. 依次进入“后台设置-->2.LOG设置-->LOG开关”,选择“LOG打开”;返回上一个界面,点击“LOG级别设置”,选择“VREBOSE”

 

    2. 返回到图1所示二面,选择“6. Dump & Log”,打开开关“打开Dump & Log”

 


 

3) 重启手机

记得重启,自己在做的时候没重启还浪费了几分钟时间。



已有 1人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐




OpenTSDB设计解读

$
0
0
OpenTSDB是基于HBase存储时间序列数据的一个开源数据库,确切地说,它只是一个HBase的应用而已,其对于时间序列数据的处理可以供其他系统参考和借鉴。本文会针对它在数据库的设计方面展开一些探索和讨论。本文原文链接: http://blog.csdn.net/bluishglc/article/details/31052749,转载请注明出处!

本文基于的是OpenTSDB最早的一个稳定版本1.0.0进行讲解的,下载部署完成之后,我们首先需要了解的是它的数据库Schema, 它主要有两个表:tsdb-uid和tsdb. 前者描述指标(metrics)相关的元数据,后者存储时间序列数据。首先我们来了解一下“指标”(metrics)的概念,简单讲一个指标就是一个需要收集的数据项,但是只有指标是不能全面地描述出一条数据产生的相关背景信息的,比如:如果我们要统计cpu的使用率,我们可以建立一下名为 proc.stat.cpu的metrics,如果我们从不同的机器和用户下收集了大量的cpu信息,如果没有对一条信息进行一定地标识,我们是无法区分出哪些数据来自哪台机器的哪个用户,所以我们需要建立一些“标签”(Tag)来标识一条数据,而这些“标签”是从属于一个指标的。所以如果是关系建模的话,“指标”(metrics)、“标签”(Tag)和“记录”(Record)三者之间的关系应该是: (下载EA)

但是到了OpenTSDB的表设计上,就把“指标”(metrics)和“标签”(Tag)统一放在了tsdb-uid表中存储,而两者之间的关联关系则直接按JOIN的形式做为一条纪录存储了,格式为:
RowKey(自增ID,3字节数组):name:metrics,name:tagk,name:tagv
同时也对它们之间的 反向关联关系也作了展开存储。

这里要特别强调的是, 这里设计是非常具有典型性的,实际上这也是HBase表设计上的一种常见的“Pattern”把表间关联关系展开,以JOIN的结果为RowKey存储数据!包括正向关联和反向关联两类数据!(请仔细参考图1理解)

下面让我们插入2个metrics: proc.stat.cpuproc.stat.mem,以及一条记录:  proc.stat.cpu 1297574486 54.2 host=foo type=user来观察一下数据表结构

首先是 tsdb-uid表:



从表中的记录可知:

1. 第一条记录:rowkey为\x00,含3个字段:metrics,tagk,tagv, 其值分别是已经添加的所有指标、标签名和标签值的数量。这一条数据是系统生成和维护的。这里有两个metrics:cpu和mem,两个key:host和type,两个value:foo和user,所以 rowkey为\x00的三个数据的value都是2

2. 一个UID是针对一种指标+一种标签名+一种标签值的组合分配的,即: 一种指标+一种标签名+一种标签值 = 一个UID,也就是说:一个UID对应的一种指标+一种标签名+一种标签值的组合才是可以单独抽取出来时行统计的最小单位!

然后我们看 tsdb表:

我们看重点看一下纪录表的rowkey:

指标UID(指标+标签的某个组合)+ 数据生成时间(取整点时间)+标签1-Key的UID+标签1-Vlaue的UID+...+标签N-Key的UID+标签N-Vlaue的UID

让我们以图纪录为例,重点看一下时间的处理:

1297574486 = 2011-02-13 13:21:26    

MWeP = 01001101 01010111 01100101 01010000 = 1297573200 = 2011-02-13 13:00:00 (截取整点小时位)

PK 0101000001101011 1286 (从整点小时到记录时间的秒偏差,1286秒正是21分钟26秒)

1297573200+1286=1297574486

PK,即小时内秒数被当作了Column

一些设计技巧:

1. 针对Hot Spot的应对策略

OpenTSDB处理的是典型的时间序列化数据,必然面临“热点”问题,关于它对热点问题的处理,HBase的官方文档  http://hbase.apache.org/book/rowkey.design.html 中专门提到过:

 However, the difference is that the timestamp is not in the  lead position of the key, and the design assumption is that there are dozens or hundreds (or more) of different metric types. Thus, even with a continual stream of input data with a mix of metric types, the Puts are distributed across various points of regions in the table.    

一般来说,如果使用时间做rowkey,那么前面就必须加“哈希”字段(也就是salted处理)。但是OpenTSDB并没有特别的哈希字段,它的处理比较聪明:首先,时间字段不会放在rowkey的开始位置,其次,rowkey开始位置挑选了自身的一个理想的业务字段“metrics"来替代了“哈希”字段。

从OpenTSDB的处理上我们可以总结出一点:在处理时间序列数据时,如果系统中存在“理想的”“天然的”起哈希作用的字段应该优先考虑其作为rowkey的起始组成部分,后接时间字段,但如果找不到这样的字段再设置人工的哈希字段


2. rowkey的设计思想

一.为了能够检索特定的metrics,tag name,tag name的data point, 将 metrics,tag name,tag name编入rowkey是显然的事情,但是直接使用它们来组成rowkey有两个明显的问题:
1. 会占用大量的存储空间(因为这些值会大量重复地出现在很多的rowkey中)
2. 由于每一个metrics,tag key,tag value的长度都是不固定的,这不利于通过字节偏移量来直接定位它们.(否则需要使用特定的分隔符,而且为了避免输入信息中可能存在特定的分隔符导致解析出错,还要对所有输入信息的分割符进行转义处理)

围绕一个性能指标,会有多种附加"属性"(或者说"标签")对其进行说明与描述, 那么对指标的查询也自然是以这些标签或标签值展开的,因此一条指标记录的rowkey必然要包含这些标签和标签值.但是由于标签和标签值是不定长的,这为rowkey的设计带来麻烦,所以需要为这些标签和标签值分配一个定长的ID,在rowkey中使用它们的ID来指代它们,这样rowkey就可以规范化,方便从rowkey中直接通过偏移截取需要的"部分".
二.Tall-Narrow和Wide-Flat两种表设计风格相结合


作者:bluishglc 发表于2014-6-15 22:03:08 原文链接
阅读:73 评论:0 查看评论

XSS 前端防火墙 —— 天衣无缝的防护

$
0
0
作者:zjcqoo

上一篇讲解了钩子程序的攻防实战,并实现了一套对框架页的监控方案,将防护作用到所有子页面。

到目前为止,我们防护的深度已经差不多,但广度还有所欠缺。

例如,我们的属性钩子只考虑了 setAttribute,却忽视还有类似的 setAttributeNode。尽管从来不用这方法,但并不意味人家不能使用。

例如,创建元素通常都是 createElement,事实上 createElementNS 同样也可以。甚至还可以利用现成的元素 cloneNode,也能达到目的。因此,这些都是边缘方法都是值得考虑的。

下面我们对之前讨论过的监控点,进行逐一审核。

内联事件执行 eval

在第一篇文章结尾谈到,在执行回调的时候,最好能监控 eval,setTimeout('...') 这些能够解析代码的函数,以防止执行储存在其他地方的 XSS 代码。

先来列举下这类函数:

  • eval

  • setTimeout(String) / setInterval(String)

  • Function

  • execScript / setImmediate(String)

事实上,利用上一篇的钩子技术,完全可以把它们都监控起来。但现实并没有我们想象的那样简单。

eval 重写有问题吗

eval 不就是个函数,为什么不可以重写?

var raw_fn = window.eval;

window.eval = function(exp) {
    alert('执行eval: ' + exp);
    return raw_fn.apply(this, arguments);
};

console.log(eval('1+1'));

完全没问题啊。那是因为代码太简单了,下面这个 Demo 就可以看出山寨版 eval 的缺陷:

(function() {
    eval('var a=1');
})();

alert(typeof a);

Run

按理说应该 undefined 才对,结果却是 number。局部变量都跑到全局上来了。这是什么情况?事实上,eval 并不是真正意义的函数,而是一个关键字!想了解详情 请戳这里

Function 重写有意义吗

Function 是一个全局变量,重写 window.Function 理论上完全可行吧。

var raw_fn = window.Function;

window.Function = function() {
    alert('调用Function');
    return raw_fn.apply(this, arguments);
};

var add = Function('a', 'b', 'return a+b');
console.log( add(1, 2) );

重写确实可行。但现实却是不堪一击的:因为所有函数都是 Function 类的实例,所以访问任何一个函数的 constructor 即可得到原始的 Function。

例如 alert.constructor,就可以绕过我们的钩子。甚至可以用匿名函数:

(function(){}).constructor

所以,Function 是永远钩不住的。

额外的执行方法

就算不用这类函数,仍有相当多的办法执行字符串,例如:

  • 创建脚本,innerHTML = 代码

  • 创建脚本,路径 = data:代码

  • 创建框架,路径 = javascript:代码

  • ......

看来,想完全把类似 eval 的行为监控起来,是不现实的。不过作为预警,我们只监控 eval,setTimeout/Interval 也就足够了。

可疑模块拦截

第二篇谈了站外模块的拦截。之所以称之『模块』而不是『脚本』,并非只有脚本元素才具备执行能力。框架页、插件都是可以运行代码的。

可执行元素

我们列举下,能执行远程模块的元素:

  • 脚本
<script src="..." />
  • 框架
<iframe src="..."><frame src="...">
  • 插件(Flash)
<embed src="..."><object data="..."><object><param name="moive|src" value="..."></object>
  • 插件(其他)
<applet codebase="...">

这些元素的路径属性,都应该作为排查的对象。

不过,有这么个元素的存在,可能导致我们的路径检测失效,它就是:

<base href="...">

它能重定义页面的相对路径,显然是不容忽视的。

事实上,除了使用元素来执行站外模块,还可以使用网络通信,获得站外的脚本代码,然后再调用 eval 执行:

AJAX

目前主流浏览器都支持跨域请求,只要服务端允许就可以。因此,我们需监控 XMLHttpRequest::open 方法。如果请求的是站外地址,就得做策略匹配。不通过则放弃向上调用,或者抛出一个异常,或者给 XHR 产生一个 400 状态。

WebSocket

WebSocket 和 XHR 类似,也能通过钩子的方法进行监控。

不过,值得注意的是,WebSocket 并非是个函数,而是一个类。因此,在返回实例的时候, 别忘了将 constructor 改成自己的钩子,否则就会泄露原始接口

var raw_class = window.WebSocket;

window.WebSocket = function WebSocket(url, arg) {
    alert('WebSocket 请求:' + url);

    var ins = new raw_class(url, arg);
    // 切记
    ins.constructor = WebSocket;
    return ins;
};

var ws = new WebSocket('ws://127.0.0.1:1000');

另外,因为它是一个类,所以不要忽略了静态方法或属性:

  • WebSocket.CONNECTING

  • WebSocket.OPEN

  • ...

因此,还需将它们拷贝到钩子上。

框架页消息

HTML5 赋予了框架页跨域通信的能力。如果没有为框架元素建立白名单的话,攻击者可以嵌入自己的框架页面,然后将 XSS 代码 postMessage 给主页面,通过 eval 执行。

不过为了安全考虑,HTML5 在消息事件里保存了来源地址,以识别消息是哪个页面发出的。

因为是个事件,我们可以使用第一篇文章里提到的方法,对其进行捕获。每当有消息收到时,可以根据策略,决定是否阻止该事件的传递。

// 我们的防御系统
(function() {
    window.addEventListener('message', function(e) {
        if (confirm('发现来自[' + e.origin + ']的消息:\n\n' + e.data + '\n\n是否拦截?')) {
            e.stopImmediatePropagation();
        }
    }, true);
})();


window.addEventListener('message', function(e) {
    alert('收到:' + e.data)
})
postMessage('hello', '*');

Run

当然,如果配置了框架页的白名单,就能完全避免这回事了。所以这项防御可以选择性的开启。

事件源

HTML5 新增了一个叫 EventSource 的接口。不过其用法与 WebSocket 非常相似,因此可以使用类似的钩子进行防御。

到此,我们列举了各种能执行远程模块的方式。事实上,对其防御并不难,难的是收集这些监控点,做到滴水不漏。

API 钩子

对于动态创建的可执行模块,我们通过属性钩子,来监控其远程路径。

创建元素的方法

  • createElement / createElementNS 无中生有

  • cloneNode 克隆现有

  • innerHTML / outerHTML 工厂创建

前两种,通过钩子程序很容易实现。

第三种,因为 inner/outerHTML 是元素的 property,而非 attribute。由于 Chrome 是无法获取原生访问器的,所以使用钩子会导致无法调用上级接口。

再者,inner/outerHTML 传进来的是字符串。标签和属性鱼龙混杂,解析字符串肯定是不靠谱的。所以还得先调用原生 innerHTML 批量构建出节点,然后再扫描其中的元素。而这个过程中,节点挂载事件已经触发了。

所以,无需考虑第三种情况。

你可能会有疑问,既然用节点挂载事件都能搞定,为什么还要前面的钩子?其实,在 第二篇文章里已经详细讨论了,动态创建的脚本没法被事件拦截,所以才用钩子。

而通过 innerHTML 产生的脚本,是不会执行的!这个大家都听说过吧。

新页面环境

除了使用最简单的框架,其实还有其他可以获得新页面的途径。

弹窗

通过弹窗也能获得新页面环境,大家都知道。但是窗口关闭,也随之销毁了,难道还能使用吗?不妨测试一下:

<style> .aa { color: red }</style><button id="btn">POPUP</button><script>
    btn.onclick = function() {
        var win = window.open();
        var raw_fn = win.Element.prototype.setAttribute;

        win.close();

        setTimeout(function() {
            console.log(raw_fn);
            raw_fn.call(btn, 'class', 'aa');
        }, 1000);
    };</script>

Run

尽管会有瞬间的闪动,但从新窗口里获取的变量确实被保留下来了,并且依然起作用。因为我们引用着它,所以即使窗口关闭,仍然不会对其内存回收的。

现实中,可以把点击事件绑在 document 上,这样用户随便点哪里都能触发,以此获得纯净的环境。

因此,我们还得把弹窗函数,也通过钩子保护起来。

除了最常用的 window.open,其实还有:

  • showModalDialog

  • showModelessDialog

opener

如果当前网页是从其他页面点击打开的,无论是弹窗还是超链接,window.opener 都记录着来源页的环境。

如果是来源页和自己又是同源站点,甚至还能访问到来源页里面的变量。

这种情况相当常见。例如从帖子列表页,点开一个帖子详情页,那么详情页是完全可以操控列表页的。

要解决这个问题也不难,直接给 window.opener 注入防护程序不就可以了,就像对待新出现的框架页那样。

但是,window.opener 可能也有自己的 opener,一层层递归上去或许有很多。每个页面也许又有自己的框架页,因此防护 window.opener 可能会执行非常多的代码。如果在初始化时就进行,或许会有性能问题。

事实上,这个冷门的属性几乎不怎么用到。所以不如做个延时策略:只有第一次访问 opener 的时候,才对其进行防护。

我们将 window.opener 进行重写,把它变成一个 getter 访问器:

var raw_opener = window.opener;
var scanned;

window.__defineGetter__('opener', function() {
    if (!scanned) {
        installHook(raw_opener);
        scanned = true;
    }
    return raw_opener;
});

这样,只要不访问 opener,就不会触发对它的防护,做到真正按需执行。

后记

关于防护监控点,也没有一个完整的答案,能想到多少算多少,以后可以慢慢补充。

但是,装了那么多的钩子及事件,对页面的性能影响有多大呢?

所以,我们还得开发一个测试控制台,来跟踪这套系统。看看监控全开时,会对页面产生多大影响。

HTML5 Web socket和socket.io - wishyouhappy

$
0
0

what is websockets


 

Two-way communication over ont TCP socket, a type of PUSH technology

   HTML5的新特性,用于双向推送消息(例如网页聊天,手机推送消息等)

 

  原理:

  1. client利用regular http请求webpage
  2. 请求的webpage 执行javascript脚本,open a connection to server.
  3. 有新的信息时服务器和客户端可以相互发送信息(Real-time traffic from the server to the client  and from the client to the server

   HTML5 WebSockets

 

客户端


 

  说明:

  readyState:

CONNECTING (0):表示还没建立连接;
OPEN (1): 已经建立连接,可以进行通讯;
CLOSING (2):通过关闭握手,正在关闭连接;
CLOSED (3):连接已经关闭或无法打开;

  

  url: WebSocket 服务器的网络地址,协议通常是”ws”或“wss(加密通信)”,

  事件:

  • send:向服务器端发送数据
  • close 方法就是关闭连接;
  • onopen连接建立,即握手成功触发的事件;
  • onmessage收到服务器消息时触发的事件;
  • onerror异常触发的事件;

 

  使用例子:

// 创建一个Socket实例
var socket = new WebSocket('ws://localhost:8080');

// 打开Socket
socket.onopen = function(event) {

// 发送一个初始化消息
socket.send('I am the client and I\'m listening!');

// 监听消息
socket.onmessage = function(event) {
console.log('Client received a message',event);
};

// 监听Socket的关闭
socket.onclose = function(event) {
console.log('Client notified socket has closed',event);
};

// 关闭Socket....
//socket.close()
};

 

服务器端


 

  jWebSocket (Java)
  web-socket-ruby (ruby)
  Socket IO-node (node.js)

 

 下面以socket.io说明,环境说明:(node.js安装见  http://www.cnblogs.com/wishyouhappy/p/3647037.html

 1. 安装socket.io

 npm install -g socket.io

  2.使用require引入http和socket.io

var http = require("http");
var io= require('socket.io');

  3.创建server

var server = http.createServer(function(request, response){
response.writeHead(200,{"Content-Type":"text/html"});
response.write("WebSocket Start~~~~~~~~~~~~");
response.end("");
}).listen(8000);

 4.监听

var socket= io.listen(server);

 5.例子:

var http = require("http");
var io= require('socket.io');
var server = http.createServer(function(request, response){
response.writeHead(200,{"Content-Type":"text/html"});
response.write("WebSocket Start~~~~~~~~~~~~");
response.end("");
}).listen(8000);

var socket= io.listen(server);

// 添加一个连接监听器
socket.on('connection', function(client){

client.on('message',function(event){
console.log('Received message from client!',event);
});
client.on('disconnect',function(){
clearInterval(interval);
console.log('Server has disconnected');
});
});

 

数据发送两种方式send和emit


 

使用send和emit都可以发送数据,但是emit可以自定义事件,如下:

emit:

//服务器

socket.on('connection', function(client){
client.on('message',function(event){
client.emit('emitMessage', { hello: 'messgge received, wish you happy'+new Date().toString() });
});

});


//客户端
socket.on('emitMessage',function(data) {
document.getElementById("result").innerHTML+=data.hello + "<br />";

});

send:

//服务器

socket.on('connection', function(client){
client.send('hello, I am the server');
});


//客户端
socket.on('message',function(data) {
document.getElementById("result").innerHTML+=data + "<br />";

});

 

实例:(socket.io)


 

客户端:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<style>
div{
border-radius: 10px;
border: 2px solid pink;
width:800px;
}
</style>
</head>
<body>
<h1></h1>
<div id="result"></div>
<script src="http://localhost:8000/socket.io/socket.io.js"></script>
<script>
//创建Socket.IO实例,建立连接

var socket = io.connect('http://localhost:8000');

// 添加一个连接监听器
socket.on('connect',function() {
console.log('Client has connected to the server!');
});

// 添加一个连接监听器
socket.on('message',function(data) {
document.getElementById("result").innerHTML+=data + "<br />";

});
socket.on('emitMessage',function(data) {
document.getElementById("result").innerHTML+=data.hello + "<br />";

});

// 添加一个关闭连接的监听器
socket.on('disconnect',function() {
console.log('The client has disconnected!');
});

// 通过Socket发送一条消息到服务器
function sendMessageToServer(message) {
socket.send(message);
}
var date = new Date();
var ms="Time: "+date.toString()+"Today is a nice day, wish you happy";
setInterval("sendMessageToServer(ms)",1000);
</script>

</body>
</html>

服务器:

var http = require("http");
var io= require('socket.io');
var server = http.createServer(function(request, response){
response.writeHead(200,{"Content-Type":"text/html"});
response.write("WebSocket Start~~~~~~~~~~~~");
response.end("");
}).listen(8000);

var socket= io.listen(server);

// 添加一个连接监听器
socket.on('connection', function(client){

client.on('message',function(event){
console.log('Received message from client!',event);
client.emit('emitMessage', { hello: 'messgge received, wish you happy'+new Date().toString() });
});
client.on('disconnect',function(){
// clearInterval(interval);
console.log('Server has disconnected');
});
client.send('hello, I am the server');
});

结果:

客户端:

服务器端:


本文链接: HTML5 Web socket和socket.io,转载请注明。

Socket.IO进阶

$
0
0

转载自:  http://raytaylorlin.com/Tech/web/Node.js/socket-io-advanced

 

Socket.IO进阶

2013-12-10

在上一篇博文 Socket.IO中,我简要介绍了Socket.IO的基本使用方法并创建了一个简单的聊天室DEMO。本篇在入门篇的基础上,继续探讨Socket.IO的进阶用法。本篇将从配置、房间、事件等方面入手,介绍一些Socket.IO中实用的API和注意事项。

1. 配置

Socket.IO提供了4个配置的API:io.configure, io.set, io.enable, io.disable。其中io.set对单项进行设置,io.enable和io.disable用于单项设置布尔型的配置。io.configure可以让你对不同的生产环境(如devlopment,test等等)配置不同的参数。以下定义了development和release两种环境下Socket.IO的不同配置:

var io =require('socket.io').listen(80);

io.configure('development',function(){
    io.enable('browser client etag');
    io.set('log level',1);});

io.configure('release',function(){
    io.set('transports',['websocket']);});

下面列举一些常用的配置项,具体配置参数参见 官方WIKI

  • transports(默认 ['websocket', 'htmlfile', 'xhr-polling', 'jsonp-polling']):一个包含通信方法类型的数组。Socket.IO支持多种实现在线即时通信的方式,如websocket、polling等等,该配置能让你自行选择备用的通信方式。
  • log level(默认 3):日志输出的最低级别,0为error,1为warn,2为info,3为debug,默认即输出所有类型的日志。
  • heartbeat interval(默认 25秒):心跳包发送间隔,客户端需要在此时间段之内向服务器发送一个心跳包才能保持通信。

2. 房间

房间是Socket.IO提供的一个非常好用的功能。房间相当于为指定的一些客户端提供了一个命名空间,所有在房间里的广播和通信都不会影响到房间以外的客户端。

在入门篇中,我们知道 socket.join('room name')可用于客户端进入房间, socket.leave('room name')用于离开房间。当客户端进入一个房间之后,可以通过以下两种方式在房间里广播消息:

//1. 向my room广播一个事件,提交者会被排除在外(即不会收到消息)
io.sockets.on('connection',function(socket){//注意:和下面对比,这里是从客户端的角度来提交事件
    socket.broadcast.to('my room').emit('event_name', data);}//2. 向another room广播一个事件,在此房间所有客户端都会收到消息//注意:和上面对比,这里是从服务器的角度来提交事件
io.sockets.in('another room').emit('event_name', data);//向所有客户端广播
io.sockets.emit('event_name', data);

除了向房间广播消息之外,还可以通过以下API来获取房间的信息。

//获取所有房间的信息//key为房间名,value为房间名对应的socket ID数组
io.sockets.manager.rooms

//获取particular room中的客户端,返回所有在此房间的socket实例
io.sockets.clients('particular room')//通过socket.id来获取此socket进入的房间信息
io.sockets.manager.roomClients[socket.id]

3. 事件

Socket.IO内置了一些默认事件,我们在设计事件的时候应该避开默认的事件名称,并灵活运用这些默认事件。

服务器端事件:

  • io.sockets.on('connection', function(socket) {}):socket连接成功之后触发,用于初始化
  • socket.on('message', function(message, callback) {}):客户端通过 socket.send来传送消息时触发此事件,message为传输的消息,callback是收到消息后要执行的回调
  • socket.on('anything', function(data) {}):收到任何事件时触发
  • socket.on('disconnect', function() {}):socket失去连接时触发(包括关闭浏览器,主动断开,掉线等任何断开连接的情况)

客户端事件:

  • connect:连接成功
  • connecting:正在连接
  • disconnect:断开连接
  • connect_failed:连接失败
  • error:错误发生,并且无法被其他事件类型所处理
  • message:同服务器端message事件
  • anything:同服务器端anything事件
  • reconnect_failed:重连失败
  • reconnect:成功重连
  • reconnecting:正在重连

在这里要提下客户端socket发起连接时的顺序。当第一次连接时,事件触发顺序为:connecting->connect;当失去连接时,事件触发顺序为:disconnect->reconnecting(可能进行多次)->connecting->reconnect->connect。

4. 授权

  • 向所有客户端广播: socket.broadcast.emit('broadcast message');

  • 进入一个房间( 非常好用!相当于一个命名空间,可以对一个特定的房间广播而不影响在其他房间或不在房间的客户端): socket.join('your room name');

  • 向一个房间广播消息(发送者 收不到消息): socket.broadcast.to('your room name').emit('broadcast room message');

  • 向一个房间广播消息( 包括发送者都能收到消息)( 这个API属于io.sockets): io.sockets.in('another room name').emit('broadcast room message');

  • 强制使用WebSocket通信:(客户端) socket.send('hi'),(服务器)用 socket.on('message', function(data){})来接收。

Socket.IO的进阶用法介绍基本就到这里。个人感觉在日常使用的时候这些基本API已经够用了,这也体现了Socket.IO极其简洁易用的设计哲学。本文只是抛砖引玉,当在实际运用中遇到解决不了的问题时,再去查看官方详细的WIKI会比较好。

参考文献: Socket.IO官方WIKI

所属目录:  Tech ->  web ->  Node.js

 

/



已有 0人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐



Socket.IO入门

$
0
0

       最近用node+socket.io+mqtt+php实现了基于PC和android的聊天工具。一直也没故上总结,今天从网上看到一篇整理的特别好的文章,故转载过来,再次感觉原文作者。

 

       转载自:http://raytaylorlin.com/Tech/web/Node.js/socket-io-tutorial/

 

Socket.IO入门

2013-11-29

暑假的时候Heatmap项目组需要对在线即时通信和协作进行技术探索,于是我开始研究Web在线聊天的实现方式。在充分对Comet技术进行了研究之后(详见我之前写的一篇 Comet简介的博文),在丁基友的提示之下决定尝试使用Socket.IO。一个是考虑以后HTML5做网络通信需要用到WebSocket现在可以提前接触一下,另外一个是这个东西的服务器端要用到Node.js,之前就对node很有兴趣正好借此提升下功力。

1. 简介

首先是Socket.IO的官方网站: http://socket.io

官网非常简洁,甚至没有API文档,只有一个简单的“How to use”可以参考。因为Socket.IO就跟官网一样简洁好用易上手。

那么Socket.IO到底是什么呢?Socket.IO是一个WebSocket库,包括了客户端的js和服务器端的nodejs,它的目标是构建可以在不同浏览器和移动设备上使用的实时应用。它会自动根据浏览器从WebSocket、AJAX长轮询、Iframe流等等各种方式中选择最佳的方式来实现网络实时应用,非常方便和人性化,而且支持的浏览器最低达IE5.5,应该可以满足绝大部分需求了。

2. 安装部署

2.1 安装

首先安装非常简单,在node.js环境下只要一句:

npm install socket.io

2.2 结合express来构建服务器

express是一个小巧的Node.js的Web应用框架,在构建HTTP服务器时经常使用到,所以直接以Socket.IO和express为例子来讲解。

var express =require('express'), app = express(), server =require('http').createServer(app), io =require('socket.io').listen(server);
server.listen(3001);

若不使用express,请参考socket.io/#how-to-use

3. 基本使用方法

主要分为服务器端和客户端两段代码,都非常简单。

Server(app.js):

//接上面的代码
app.get('/',function(req, res){
    res.sendfile(__dirname +'/index.html');});

io.sockets.on('connection',function(socket){
    socket.emit('news',{ hello:'world'});
    socket.on('other event',function(data){
        console.log(data);});});

首先io.sockets.on函数接受字符串"connection"作为客户端发起连接的事件,当连接成功后,调用带有socket参数的回调函数。我们在使用socket.IO的时候,基本上都在这个回调函数里面处理用户的请求。

socket最关键的是emit和on两个函数,前者提交(发出)一个事件(事件名称用字符串表示),事件名称可以自定义,也有一些默认的事件名称,紧接着是一个对象,表示向该socket发送的内容;后者接收一个事件(事件名称用字符串表示),紧接着是收到事件调用的回调函数,其中data是收到的数据。

在上面的例子中,我们发送了news事件和收到了other event事件,那么客户端应该会有对应的接收和发送事件。没错,客户端代码和服务器正好相反,而且非常相似。

Client(client.js)

<scriptsrc="/socket.io/socket.io.js"></script><script>var socket = io.connect('http://localhost');
    socket.on('news',function(data){
        console.log(data);
        socket.emit('other event',{ my:'data'});});</script>

有两点要注意的: socket.io.js路径要写对,这个js文件实际放在了服务器端的node_modules文件夹中,在请求这个文件时会重定向,因此不要诧异服务器端不存在这个文件但为什么还能正常工作。 当然,你可以把服务器端的socket.io.js这个文件拷贝到本地,使它成为客户端的js文件,这样就不用每次都向Node服务器请求这个js文件,增强稳定性。第二点是要用 var socket = io.connect('网站地址或ip');来获取socket对象,接着就可以使用socket来收发事件。关于事件处理,上面的代码表示收到“news”事件后,打印收到的数据,并向服务器发送“other event”事件。

注:内置默认的事件名例如“disconnect”表示客户端连接断开,“message”表示收到消息等等。自定义的事件名称,尽量不要跟Socket.IO中内置的默认事件名重名,以免造成不必要的麻烦。

4. 其他常用API

  • 向所有客户端广播: socket.broadcast.emit('broadcast message');

  • 进入一个房间( 非常好用!相当于一个命名空间,可以对一个特定的房间广播而不影响在其他房间或不在房间的客户端): socket.join('your room name');

  • 向一个房间广播消息(发送者 收不到消息): socket.broadcast.to('your room name').emit('broadcast room message');

  • 向一个房间广播消息( 包括发送者都能收到消息)( 这个API属于io.sockets): io.sockets.in('another room name').emit('broadcast room message');

  • 强制使用WebSocket通信:(客户端) socket.send('hi'),(服务器)用 socket.on('message', function(data){})来接收。

5. 使用Socket.IO构建一个聊天室

最后,我们通过一个简单的实例来结束本篇。用Socket.IO构建一个聊天室就是50行左右的代码的事情,实时聊天效果也非常好。以下贴出关键代码:

Server(socketChat.js)

//一个客户端连接的字典,当一个客户端连接到服务器时,//会产生一个唯一的socketId,该字典保存socketId到用户信息(昵称等)的映射var connectionList ={};

exports.startChat =function(io){
    io.sockets.on('connection',function(socket){//客户端连接时,保存socketId和用户名var socketId = socket.id;
        connectionList[socketId]={
            socket: socket
        };//用户进入聊天室事件,向其他在线用户广播其用户名
        socket.on('join',function(data){
            socket.broadcast.emit('broadcast_join', data);
            connectionList[socketId].username = data.username;});//用户离开聊天室事件,向其他在线用户广播其离开
        socket.on('disconnect',function(){if(connectionList[socketId].username){
                socket.broadcast.emit('broadcast_quit',{
                    username: connectionList[socketId].username
                });}delete connectionList[socketId];});//用户发言事件,向其他在线用户广播其发言内容
        socket.on('say',function(data){
            socket.broadcast.emit('broadcast_say',{
                username: connectionList[socketId].username,
                text: data.text
            });});})};

Client(socketChatClient.js)

var socket = io.connect('http://localhost');//连接服务器完毕后,马上提交一个“加入”事件,把自己的用户名告诉别人
socket.emit('join',{
    username:'Username hehe'});//收到加入聊天室广播后,显示消息
socket.on('broadcast_join',function(data){
    console.log(data.username +'加入了聊天室');});//收到离开聊天室广播后,显示消息
socket.on('broadcast_quit',function(data){
    console.log(data.username +'离开了聊天室');});//收到别人发送的消息后,显示消息
socket.on('broadcast_say',function(data){
    console.log(data.username +'说: '+ data.text);});//这里我们假设有一个文本框textarea和一个发送按钮.btn-send//使用jQuery绑定事件
$('.btn-send').click(function(e){//获取文本框的文本var text = $('textarea').val();//提交一个say事件,服务器收到就会广播
    socket.emit('say',{
        username:'Username hehe'
        text: text
    });});

这就是一个简单的聊天室DEMO,你可以根据你的需要随意扩展。Socket.IO基本上就是各种事件的提交和接收处理,思想非常简单。

 

 



已有 0人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐



Viewing all 15843 articles
Browse latest View live


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