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

COMMIT和数据一致性

$
0
0

[align=justify; direction: ltr; unicode-bidi: embed; vertical-align: baseline;]2.在执行一条update语句后一直未提交,数据会写到数据文件中吗?

一致性查询及一致性读原理

Select * from test where object_id = 2;
如果8点钟可以查询出两条记录,假设一下,如果此查询很慢,从8点开
始查,9点才能结束。在此期间不巧被删了一条数据,请问最终返回的结果是一条数据还是两条数据?

原理:
两个前提:
•1. 了解数据库的SCN(System change Number),是数据库内部的时钟,可以与时间相互转换,是一个只会增加不会减少的数字,存在于Oracle的最小单位块里,当某块改变时SCN就会递增。
•2. 数据库的回滚记录事务槽,如果你更新了某块,事务就被写进事务槽里。如果未提交或回滚,改块就存在活动事务,数据库读到此块可以识别到这种情况的存在。



 


 



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


ITeye推荐




oracle各类型SQL的操作流程

$
0
0

SELECT
•Select * from test  where  object_id=200在体系中是如何运转的。
•1. 在PGA中把此条SQL语句hash成一个值;
•2. 接下来根据此hash值到SGA的共享池中去匹配,如果没有,首先查询自己的语句语法是否正确,语义是否正确,是否有权限。如果都通过则通过CBO解析生成执行计划(如走索引还是全表)。
•3. 如果是走索引,到数据缓存区找到object_id=200的索引,根据索引rowid找到记录,如果数据缓存区找不到,则到数据文件中找到,并展示给用户。

UPDATE
•Update test set object_id = 100 where object_id = 200; 在体系中是如何运转的。
•更新object_id=200的记录首先要查到object_id=200的记录,检查object_id=200是否在数据缓存中,不在则从磁盘中读取到数据缓存中。
•在回滚表空间的相应回滚段事务表上分配事务槽,从而在回滚表空间分配到空间。该动作需要记录redo日志。
•在数据缓存区中创建object_id=200的前镜像,前镜像数据也会写进磁盘的数据文件(undo表空间的数据文件),从缓存区写进磁盘有CKPT决定,当然这些动作都要记录redo日志。
•前面的步骤做完了,才允许object_id=200改为object_id=100,记录redo日志。
•此时用户如果执行了提交,日志缓存区立即记录这个提交信息,然后将回滚段事务标记为非激活INACTIVE状态,表示允许重写。
如果执行了回滚,Oracle需要从回滚段中将前镜像object_id=200的数据读出来,修改数据缓存区,完成回滚,记录redo日志。

delete运作流程?

 



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


ITeye推荐



oracle 数据库体系结构

$
0
0

       任何硬件平台或操作系统下的ORACLE体系结构都是相同的,包括如下四个方面:
物理结构
        数据文件,日志文件,控制文件,参数文件。
逻辑结构
        表空间、段、区间、数据块。
内存结构
        共享池,数据缓冲区,日志缓冲区,PGA。
进程
        用户进程、服务器进程、后台进程。



 

       SGA是共享内存区,PGA是私有内存区,用户对数据库发起的无论查询还是更新的任何操作,都是PGA预先处理,然后接下来才进入实例区域,
由SGA和系列后台进程共同完成用户发起的请求。
PGA的作用主要是三点
保存用户的连接信息,如会话属性,绑定变量等;
保存用户权限等重要信息;
做部分排序操作,如果放不下,就到临时表中完成,就是在磁盘中完成排序。

SGA
library cache

       最主要的功能就是存放用户提交的SQL语句及相关的解析树(解析树也就是对SQL语句中所涉及的所有对象的展现)、执行计划、用户提交的PL/SQL程序块(包括匿名程序块、存储过程、包、函数等)以及它们转换后能够被Oracle执行的代码等。

       也存放了很多的数据库对象的信息,包括表、索引等。有关这些数据库对象的信息都是从dictionary cache中获得的。如果用户对library cache中的对象信息进行了修改,比如为表添加了一个列等,则这些修改会返回到dictionary cache中。

 

软解析实验
	alter system flush shared_pool;   --- 禁止在公司测试环境使用
	select owner,name,type,kept,sharable_mem,pins,locks,LOADS from v$db_object_cache where name like '%dba_data_files%'

SQL> select * from dba_data_files t where t.file_id=2;

已用时间:  00: 00: 00.04

执行计划
----------------------------------------------------------
Plan hash value: 1869944940

------------------------------------------------------------------------------------------------------
| Id  | Operation                         | Name             | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                  |                  |     2 |   812 |     4   (0)| 00:00:01 |
|   1 |  VIEW                             | DBA_DATA_FILES   |     2 |   812 |     4   (0)| 00:00:01 |
|   2 |   UNION-ALL                       |                  |       |       |            |          |
|   3 |    NESTED LOOPS                   |                  |     1 |   395 |     2   (0)| 00:00:01 |
|   4 |     MERGE JOIN CARTESIAN          |                  |     1 |   380 |     1   (0)| 00:00:01 |
|   5 |      NESTED LOOPS                 |                  |     1 |    70 |     1   (0)| 00:00:01 |
|*  6 |       TABLE ACCESS BY INDEX ROWID | FILE$            |     1 |    31 |     1   (0)| 00:00:01 |
|*  7 |        INDEX UNIQUE SCAN          | I_FILE1          |     1 |       |     0   (0)| 00:00:01 |
|*  8 |       FIXED TABLE FIXED INDEX     | X$KCCFE (ind:1)  |     1 |    39 |     0   (0)| 00:00:01 |
|   9 |      BUFFER SORT                  |                  |     1 |   310 |     1   (0)| 00:00:01 |
|* 10 |       FIXED TABLE FULL            | X$KCCFN          |     1 |   310 |     0   (0)| 00:00:01 |
|  11 |     TABLE ACCESS CLUSTER          | TS$              |     1 |    15 |     1   (0)| 00:00:01 |
|* 12 |      INDEX UNIQUE SCAN            | I_TS#            |     1 |       |     0   (0)| 00:00:01 |
|  13 |    NESTED LOOPS                   |                  |     1 |   471 |     2   (0)| 00:00:01 |
|  14 |     NESTED LOOPS                  |                  |     1 |   456 |     1   (0)| 00:00:01 |
|  15 |      MERGE JOIN CARTESIAN         |                  |     1 |   365 |     1   (0)| 00:00:01 |
|  16 |       NESTED LOOPS                |                  |     1 |    55 |     1   (0)| 00:00:01 |
|* 17 |        TABLE ACCESS BY INDEX ROWID| FILE$            |     1 |    16 |     1   (0)| 00:00:01 |
|* 18 |         INDEX UNIQUE SCAN         | I_FILE1          |     1 |       |     0   (0)| 00:00:01 |
|* 19 |        FIXED TABLE FIXED INDEX    | X$KCCFE (ind:1)  |     1 |    39 |     0   (0)| 00:00:01 |
|  20 |       BUFFER SORT                 |                  |     1 |   310 |     1   (0)| 00:00:01 |
|* 21 |        FIXED TABLE FULL           | X$KCCFN          |     1 |   310 |     0   (0)| 00:00:01 |
|* 22 |      FIXED TABLE FIXED INDEX      | X$KTFBHC (ind:1) |     1 |    91 |     0   (0)| 00:00:01 |
|  23 |     TABLE ACCESS CLUSTER          | TS$              |     1 |    15 |     1   (0)| 00:00:01 |
|* 24 |      INDEX UNIQUE SCAN            | I_TS#            |     1 |       |     0   (0)| 00:00:01 |
------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   6 - filter("F"."SPARE1" IS NULL)
   7 - access("F"."FILE#"=2)
   8 - filter("FE"."FENUM"=2)
  10 - filter("FNNAM" IS NOT NULL AND "FNFNO"=2 AND "FNTYP"=4 AND"INST_ID"=USERENV('INSTANCE') AND BITAND("FNFLG",4)<>4)
  12 - access("F"."TS#"="TS"."TS#")
  17 - filter("F"."SPARE1" IS NOT NULL)
  18 - access("F"."FILE#"=2)
  19 - filter("FE"."FENUM"=2)
  21 - filter("FNNAM" IS NOT NULL AND "FNFNO"=2 AND "FNTYP"=4 AND"INST_ID"=USERENV('INSTANCE') AND BITAND("FNFLG",4)<>4)
  22 - filter("HC"."KTFBHCAFNO"=2)
  24 - access("HC"."KTFBHCTSN"="TS"."TS#")


统计信息
----------------------------------------------------------
        151  recursive calls
          1  db block gets
         32  consistent gets
          0  physical reads
          0  redo size
       1179  bytes sent via SQL*Net to client
        338  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
          1  rows processed

SQL> /

已用时间:  00: 00: 00.00

执行计划
----------------------------------------------------------
Plan hash value: 1869944940

------------------------------------------------------------------------------------------------------
| Id  | Operation                         | Name             | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                  |                  |     2 |   812 |     4   (0)| 00:00:01 |
|   1 |  VIEW                             | DBA_DATA_FILES   |     2 |   812 |     4   (0)| 00:00:01 |
|   2 |   UNION-ALL                       |                  |       |       |            |          |
|   3 |    NESTED LOOPS                   |                  |     1 |   395 |     2   (0)| 00:00:01 |
|   4 |     MERGE JOIN CARTESIAN          |                  |     1 |   380 |     1   (0)| 00:00:01 |
|   5 |      NESTED LOOPS                 |                  |     1 |    70 |     1   (0)| 00:00:01 |
|*  6 |       TABLE ACCESS BY INDEX ROWID | FILE$            |     1 |    31 |     1   (0)| 00:00:01 |
|*  7 |        INDEX UNIQUE SCAN          | I_FILE1          |     1 |       |     0   (0)| 00:00:01 |
|*  8 |       FIXED TABLE FIXED INDEX     | X$KCCFE (ind:1)  |     1 |    39 |     0   (0)| 00:00:01 |
|   9 |      BUFFER SORT                  |                  |     1 |   310 |     1   (0)| 00:00:01 |
|* 10 |       FIXED TABLE FULL            | X$KCCFN          |     1 |   310 |     0   (0)| 00:00:01 |
|  11 |     TABLE ACCESS CLUSTER          | TS$              |     1 |    15 |     1   (0)| 00:00:01 |
|* 12 |      INDEX UNIQUE SCAN            | I_TS#            |     1 |       |     0   (0)| 00:00:01 |
|  13 |    NESTED LOOPS                   |                  |     1 |   471 |     2   (0)| 00:00:01 |
|  14 |     NESTED LOOPS                  |                  |     1 |   456 |     1   (0)| 00:00:01 |
|  15 |      MERGE JOIN CARTESIAN         |                  |     1 |   365 |     1   (0)| 00:00:01 |
|  16 |       NESTED LOOPS                |                  |     1 |    55 |     1   (0)| 00:00:01 |
|* 17 |        TABLE ACCESS BY INDEX ROWID| FILE$            |     1 |    16 |     1   (0)| 00:00:01 |
|* 18 |         INDEX UNIQUE SCAN         | I_FILE1          |     1 |       |     0   (0)| 00:00:01 |
|* 19 |        FIXED TABLE FIXED INDEX    | X$KCCFE (ind:1)  |     1 |    39 |     0   (0)| 00:00:01 |
|  20 |       BUFFER SORT                 |                  |     1 |   310 |     1   (0)| 00:00:01 |
|* 21 |        FIXED TABLE FULL           | X$KCCFN          |     1 |   310 |     0   (0)| 00:00:01 |
|* 22 |      FIXED TABLE FIXED INDEX      | X$KTFBHC (ind:1) |     1 |    91 |     0   (0)| 00:00:01 |
|  23 |     TABLE ACCESS CLUSTER          | TS$              |     1 |    15 |     1   (0)| 00:00:01 |
|* 24 |      INDEX UNIQUE SCAN            | I_TS#            |     1 |       |     0   (0)| 00:00:01 |
------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   6 - filter("F"."SPARE1" IS NULL)
   7 - access("F"."FILE#"=2)
   8 - filter("FE"."FENUM"=2)
  10 - filter("FNNAM" IS NOT NULL AND "FNFNO"=2 AND "FNTYP"=4 AND"INST_ID"=USERENV('INSTANCE') AND BITAND("FNFLG",4)<>4)
  12 - access("F"."TS#"="TS"."TS#")
  17 - filter("F"."SPARE1" IS NOT NULL)
  18 - access("F"."FILE#"=2)
  19 - filter("FE"."FENUM"=2)
  21 - filter("FNNAM" IS NOT NULL AND "FNFNO"=2 AND "FNTYP"=4 AND"INST_ID"=USERENV('INSTANCE') AND BITAND("FNFLG",4)<>4)
  22 - filter("HC"."KTFBHCAFNO"=2)
  24 - access("HC"."KTFBHCTSN"="TS"."TS#")


统计信息
----------------------------------------------------------
          0  recursive calls
          1  db block gets
          7  consistent gets
          0  physical reads
          0  redo size
       1179  bytes sent via SQL*Net to client
        338  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
          1  rows processed

 
DICTIONARY CACHE

       在内存中存放ORACLE数据库中常用的数据字典的信息,若此区域太小,当ORACLE需要某些数据字典信息,如对某用户的权限设置等信息时, 如果该信息不能在DICTIONARY CACHE中找到,则必须先通过物理读从ORACLE数据库的数据文件中得到该信息,然后再将该内存区域的部分信息替换出去
DB Buffer Cache

       存放Oracle系统最近使用过的数据块。让他们能够在内存中进行操作。在这个级别里没有系统文件,用户数据文件,临时数据文件,回滚段文件之分。也就是任何文件的数据块都有可能被缓冲。数据库的任何修改都在该缓冲里完成,并由DBWR进程将修改后的数据写入磁盘。
    刷新DB Buffer Cache实验

SQL> create table test_buffer as select * from dba_objects;

表已创建。

SQL> exec dbms_stats.gather_table_stats(user,'test_buffer');

表已分析。

SQL> select blocks,empty_blocks from dba_tables where table_name='TEST_BUFFER' and owner='SYS';

    BLOCKS EMPTY_BLOCKS
---------- ------------
       688           79
SQL> select count(*) from x$bh;

  COUNT(*)
----------
      8835

SQL> select count(*) from x$bh where state = 0;

  COUNT(*)
----------
        29

SQL> alter system set events = 'immediate trace name flush_cache';

系统已更改。

SQL> select count(*) from x$bh where state = 0;

  COUNT(*)
----------
      8832

SQL> set autotrace traceonly
SQL> select count(*) from test_buffer;


执行计划
----------------------------------------------------------
Plan hash value: 2550671572

--------------------------------------------------------------------------
| Id  | Operation          | Name        | Rows  | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |             |     1 |   154   (2)| 00:00:02 |
|   1 |  SORT AGGREGATE    |             |     1 |            |          |
|   2 |   TABLE ACCESS FULL| TEST_BUFFER | 50081 |   154   (2)| 00:00:02 |
--------------------------------------------------------------------------


统计信息
----------------------------------------------------------
          1  recursive calls
          0  db block gets
        693  consistent gets
        689  physical reads
          0  redo size
        410  bytes sent via SQL*Net to client
        385  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed

SQL> /


执行计划
----------------------------------------------------------
Plan hash value: 2550671572

--------------------------------------------------------------------------
| Id  | Operation          | Name        | Rows  | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |             |     1 |   154   (2)| 00:00:02 |
|   1 |  SORT AGGREGATE    |             |     1 |            |          |
|   2 |   TABLE ACCESS FULL| TEST_BUFFER | 50081 |   154   (2)| 00:00:02 |
--------------------------------------------------------------------------


统计信息
----------------------------------------------------------
          0  recursive calls
          0  db block gets
        693  consistent gets
          0  physical reads
          0  redo size
        410  bytes sent via SQL*Net to client
        385  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed

Redo Log Buffer
         重做日志文件的缓冲区,对数据库的任何修改都按顺序被记录在该缓冲,然后由LGWR进程将它写入Redo log files。这些修改信息包含DML语句以及DDL语句。 重做日志缓冲区的存在是因为内存到内存的操作比较内存到硬盘的速度快很多,所以重作日志缓冲区可以加快数据库的操作速度,但是考虑的数据库的一致性与可恢复性,数据在重做日志缓冲区中的滞留时间不会很长。
PMON(processes Mointor)

       是进程监视器。如果执行某些更新语句,未提交时进程崩溃,这时PMON会自动回滚该操作,无需人工执行rollback命令。除此之外还可以干预后台进程,比如RECO异常失败了,此时PMON会重启RECO进程,如果遇到LGWR进程失败这种严重的问题,PMON会做出中止实例这个激烈的动作,用于防止数据错乱。

SMON(System Monitor)

      系统监视器,与PMON不同的是,SMON关注的是系统级的操作而非单个进程,重点工作在于实例恢复,除此以外还有清理临时表空间、清理回滚段空间、合并空闲空间等。

CKPT(Checkpoint Process)

       检查点进程。由Oracle的fast_start_mttr_target参数控制,用于触发DBWR从数据缓冲中写出数据到磁盘。CKPT执行越频繁,DBWR写出最频繁,性能越低,但数据库异常恢复的时候会越快。

RECO(Distributed Database Recovery)

       用于分布式数据库恢复

DBWRn(Database Block Writer)

       数据库块写入器是Oracle最核心的进程之一,负责把数据从数据缓存区写到磁盘,改进程和CKPT相辅相成,因为是CKPT促成DBWR去写的。不过DBWR也和LGWR密切相关,因为DBWR要想把数据缓存区数据写到磁盘时,必须通知LGWR先完成日志缓存区写到磁盘的动作后,方可开工。

LGWR(Log Writer)
       日志写入器,就是将日志缓存区的数据从内存写到磁盘的redo文件中。Redo的记录可以用来做数据库的异常恢复,只要保护好这些redo文件和后续对于的归档文件,从理论上来讲,即使数据文件都被删光了,可以根据这些日志将曾经发生的事情全部重做一遍,从而保证数据库的安全。LGWR必须记录下所有从数据缓存区写到数据文件的动作,工作任务相当繁重。由于要顺序记录情况下保留的日志才有意义,多进程难以保证顺序,因此只能采用单线程。为了适应工作高强度的日志记录工作,LGWR制定了5条规则:
      –每隔三秒钟,LGWR运行一次;
      –任何commit触发LGWR运行一次;
      –DBWR要把数据从数据缓存写到磁盘,触发LGWR运行一次;
      –日志缓存区满三分之一或记录慢1MB,触发LGWR运行一次;
      –联机日志文件切换也将触发LGWR。
ARCn(Archive Process)
        归档进程,它的作用是在LGWR写日志写到需要覆盖重写的时候,触发ARCH进程去转移日志文件,复制出去形成归档日志文件。
总结:

        1. PGA是用来排序的,当PGA空间不够时只有用磁盘排序,如果一个大排序不仅非常耗CPU,而且会影响其他的排序,就是影响其他的功能慢。想想我们系统中的排序,排序在设计或开发阶段就很随意,大的排序也不避讳。

        2. DBWR写磁盘的前提条件是保证对应的redo已经写到磁盘,我们可以把最繁忙的进程LGWR写redo log放到最快的磁盘上,同时也可以提高commit的速度。

        3. 避免循环commit提交。LGWR是单线程的顺序写,如果有大量的循环提交,那log buffer基本没有用处,大量commit排队提交,commit慢了造成锁释放慢,在系统大并发下,性能是不是有问题。

        4. 如有一个很大的数据库,数据量庞大,访问量非常高,而共享池很小,会产生很多SQL硬解析,因为解析的SQL很快就被挤出共享池。

        5. 如果你诊断一个数据库共享池总不够用,进一步发现硬解析很高,那就要用变量。

        6. 上班时间导入数据和大量操作数据有什么影响?产生大量的redo,会影响其他功能慢。导出也会影响性能,以后再讲。

        7. 在用as of timestap恢复数据的时候,发生快照失效,原因是什么,undo中没有改记录的改动了。如何解决,可以增大undo_retention,也可以增大undo表空间大小。

 



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


ITeye推荐



高效开源的网络扫描框架NINJA-PingU

$
0
0

写在前面:

这个扫描框架有着一个很萌的名字(企鹅忍者),同时它还有其他优秀的特性,开源,支持插件,高效。

正文:

NINJA-PingU作为一个扫描框架,可不仅仅只有ping这样简单的功能,他是一个专门为大型网络扫描所设计的框架,兼顾效率的同时,支持插件的开发。

NINJA-PingU本身也自带了很多插件,这些插件可以很好的分析网络环境中的服务和识别嵌入式设备。更多关于NINJA-PingU的信息可以看他的官方网站  http://owasp.github.io/NINJA-PingU

环境需求

- gcc
- Linux box. Will not work neither on *BSD or Windows OS.
- Root Privileges

安装指令

 $ cd /tmp; wget https://github.com/OWASP/NINJA-PingU/archive/v1.0.tar.gz; tar -xvf v1.0.tar.gz; cd NINJA-PingU-1.0/; ./npingu.sh

参数说明

 # sudo ./bin/npingu [OPTIONS] targets
  -t    Number of sender threads.
  -p    Port scan range. For instance, 80 or 20-80.
  -d    Delay between packages sent (in usecs).
  -s    No service identification (less bandwith load, more hosts/time).
  -m    Module to run. For instance, Service.
  -h    Show this help.
  [targets] Ip address seed. For instance, 192.168.1. or 1.1.1.1-255.0.0.0

NINJA Pingu使用脚本编译,使用如下语句可以运行它。

$ ./npingu.sh

用例

扫描OVH服务器

 # ./bin/npingu -t 3 -p 20-80 188.1.1.1-188.255.1.1 -d 1 -m Service
  -Targeted Hosts [188.165.83.148-188.255.83.148]
  -Targeted Port Range [20-80]
  -Threads [3]
  -Delay 1 usec
  -Use the Service identification Module

扫描google web服务器

  # ./bin/npingu -t 5 -p 80 -s 74.125.0.0-74.125.255.255
  -Targeted Hosts [74.125.0.0-74.125.255.255]
  -Targeted Port [80]
  -Threads [5]
  -s synOnly scan

扫描 32764/TCP后门

 # ./bin/npingu -t 2 1.1.1.1-255.1.1.1 -m Backdoor32764 -p 32764
  -Targeted Hosts [1.1.1.1-255.1.1.1]
  -Targeted Port [32764]
  -Threads [2]
  -Use the 32764/TCP Backdoor Module

联系我们

如果你发现了bug或者有任何建议请联系我的邮箱 guifre.ruiz@owasp.org

DIV+CSS解决IE6,IE7,IE8,FF兼容问题

$
0
0

1.ie8下兼容问题,这个最好处理,转化成ie7兼容就可以。在头部加如下一段代码,然后只要在IE7下兼容了,IE8下面也就兼容了
<meta http-equiv="x-ua-compatible" content="ie=7" />

2.flaot浮动造成IE6下面双倍边距问题,这个最常见,也最好处理,!important解决,比如
margin-left:10px !important;/*IE7,IE8,FF下是10PX*/;
margin-left:5px;/*IE6下属性写的是5PX,但在显示出来的是10px

3.清除块display,这个可以解决浮动造成的块,造成块后,当DIV背景填色或填图片的时候,会出现背景 断开或差一小块。这种兼容出现的不太多,我做到现在,只遇到过两次,方法是在出现兼容的DIV的CSS中写一个display:block,或其它属性, 中文什么意思我不知道,我英语差,但能达到想要的效果,6 e" Z+ e% |8 G# |
4.很多朋友DIV+CSS的时候,会出现,在IE的几个浏览器下都好了,但是在FF出问题了,用!important又会把IE7做的不兼容,很头疼,在想,有没有什么方法只对FF下进行操做,我用过这 个方法,感觉得是百试不爽,就是在属性前面加符号如:*、&,¥,#,@,—,+,加过符号的属性只有IE的浏览器才识别,而FF不识别,方法如 下(注意有符号的属性和没符号的属性的顺序)
height:100px;/*FF下显示100的高*/
+height:120px;/*IE678下显示120高*/

5.有时候,会在布局的时候,发现,有一个DIV浮动了,接下来的一个DIV本来是要在下面显示的,结果跑上面去了,这种情况一般在FF下面会出 现,解决的办法就是清除一下浮动,在设置过浮动的那个DIV下面加一个DIV,CSS面写个clear:both;如下<div style="float:left;height:100px; width:500px;">
<div style="clear:both;">
< div style="height:100px; width=300px">

6. 再就是 居中问题,这个问题在新手身上很多,主要原因是对盒子模型不够理解,没熟记盒子模型,如果发现你的 页面没有局中,我现在知道的,有这几个原因:1. 一个是没盒子,就是BODY后的一个大DIV把所有DIV装起来的那个,你没写。2.就是你写了,但是宽度没用绝对宽度:而是用一个相对的宽度,想局中, 必须用绝对宽度。-

7.扩展:如果我想在设计的时候,实现IE6,IE7,FF下出现三种不同的效果,比如IE6下背景红色,IE7下蓝色FF下绿色,这里,我自己试过,可以, 用兼容的方法(注意顺序,可以好好理解一下)。7 L& t- o7 k- a1 I
background:red;/*FF里显示的红色*/
+background:blue !important;/*IE7下面显示的蓝色*/
+background:green;/*IE6下面显示的绿色*/

在这里,我想说一下,虽然兼容给你带来很多郁闷,让人心烦,但同时,在你做多了后,你会发现,兼容有时候会满足你很多不好达到的效果,就像最后一 个,要做那种效果,不用兼容的方法,那你就JS去吧,JS还得想想FF和IE下的不同,当然,JS的兼容,我也不会,我没去研究过。以后的事,先把CSS+DIV学熟再说。

多做,做练,始终把盒子模型放在心中,才会熟练,才会运用自如,才会在做的时候,自然而然就知道哪里会有兼容问题,直接在测试前就解决掉那些最常见的兼容问题。



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


ITeye推荐



少走弯路,看看会导致创业失败的7大原因

$
0
0

  如果你正准备创业,失败是一条必经的道路。但如果你无法弄明白失败的原因,成功永远不会眷顾到你。以下是可能导致创业失败的七个主要原因:

  一:把收入看得比客户还重

  把收入看得太重是创业失败的一个重要原因。刚开始创业的时候,千万不要把收入看的太重,更不能忽视客户。当然,笔者并非是“教唆”创业者不重视收入,但是相对于客户资源而言,赚钱更简单些,因为只要有客户,公司总有机会实现盈利。

  创业时,你要抓住自己的核心价值,然后去获取客户。你不仅要去验证自己的想法能否带来销量,还要验证自己的产品能否真正改变人们的生活。一旦客户对你的产品有需求,他们就会愿意掏钱购买。如果说客户根本不愿意花钱购买你的产品,那商业计划做的再好也没用。

  还有一点需要注意,不要疯狂打广告。因为对任何科技产品来说,客户更看重的是产品体验,而不是天花乱坠的广告效果。如果你拥有优秀的产品体验,客户就愿意花钱买单。

  二:考虑的范围太小

  如果你的产品只能解决几百人的问题,那么根本无法发展壮大。如果你的用户无法达到数万级别,那最好不要尝试构建一家公司。你的产品不应该只为自己、邻居、朋友圈服务,考虑范围应该扩大。如果你足够有实力,甚至可以放眼全球。

  无论是创意还是产品,都需要更大的格局,因此在一开始创业时,不妨将思考的范围扩大。

  三:招聘人才不谨慎

  产品或服务越好,说明员工素质越高。一家初创公司需要许多关键员工,比如产品开发人员、市场营销人员、销售人员等等。如果核心团队的素质不高,那么提供的产品和服务自然也好不到哪儿去。

  招聘的时候一定要谨慎,只有对公司未来充满激情的人,才能吸引更多优秀的人才将自己的职业生涯投入到你的公司之中。有句话要牢记,招聘要慢,解雇要快。如果发现员工不合要求,不要犹豫,赶紧解雇。你没有那么多时间和资金给他人再次证明自己的机会,特别是在创业时期。有时你需要表现的无情一些。

  四:产品发布太慢

  世界上没有完美的产品和服务,不要追求完美的产品,因为它根本不存在。产品必须尽快上市,然后根据用户反馈不断改进。你可以先开发一款原型产品、beta产品,或是最低可行产品(MVP),并尽快交付到用户手上,然后让客户去决定这款产品是否有价值。

  五:无法及时做出改变

  绝大多数创业者之所以失败,是因为适应能力太差。他们无法根据客户要求、市场现状及时做出改变。有两点非常重要:一是适应变化,二是速度要快。

  身为一名创业者,你需要在必要的时候,灵活改变,及时作出决策。创业不能过于依赖最初的规划,多数初创公司在发展中期都会彻底改变自己刚成立时的规划。实际上,多数情况下公司的变化都是由客户决定的,你要根据客户的需要,及时做出改变。

  改变并不意味着重头开始。因此,你的公司必须要有一个基本的业务框架,这样可以保证每次改变不会影响到框架,并且保持住公司的发展主线。

  六:不会优化资源

  除非你的公司已经获得了大量投资,否则在刚创业的时候资源是很有限的。而且,大部分初创公司都会遭到资金短缺问题。所以,你必须了解公司的重点业务,并相应地分配资源。例如,如果你是一家产品型的公司,那就应该去打造一款核心产品,并发展用户。客户满意便会购买你的产品,你也会因此赚到钱,继而开发更好的产品。

  一家初创公司不要寄希望于打一场广告闪电战。你需要构建一个用户社区,这对企业长期发展非常有利,也符合成本效益。

  七:市场营销

  市场营销的重要性不言而喻,虽然产品本身很重要,但如果产品无法接触到终端用户,再好的产品也没有用。如果你深谙营销之道,那会给公司带来巨大的成功。如今有许多渠道能帮助公司营销,只是不少创业者并不知道如何利用技术来吸引眼球。

  社交媒体就是一个非常好的营销渠道,它不仅能够营造用户社区,还可以提升产品影响力,你可以在产品推出之前在社交媒体上造势。好的营销不需要太多钱,但需要你花时间去做。如果你希望自己的产品能够接触到更多的人,那么就需要花大量时间为他们服务。

  一旦获得较大规模的用户群,就可以利用他们营造雪球效应。1000名客户是一个非常重要的指标,而且也只有拥有超过1000名客户后,你才能把自己的团队称之为真正意义上的“公司”。

  创业成功是每位创业者的梦想,但成功毕竟只属于少数人。如果你刚刚踏上创业之途,千万别有太大压力。你应该专注在自己的客户身上,做正确的事,至少确保自己的产品和公司可以活下去。

  Via TNW

VisualVM 使用实例

$
0
0

VisualVM概述

VisualVM 是一款免费的性能分析工具。监控程序运行的实时数据,从而进行动态的性能分析。同时,它能自动选择更快更轻量级的技术尽量减少性能分析对应用程序造成的影响,提高性能分析的精度。

 

知识点:

转储:性能分析工具从内存中获得当前状态数据并存储到文件用于静态的性能分析。

1     系统转储:JVM 生成的本地系统的转储,又称作核心转储。一般的,系统转储数据量大,需要平台相关的工具去分析,如 Windows 上的 windbg 和 Linux 上的 gdb。

2     Java 转储:JVM 内部生成的格式化后的数据,包括线程信息,类的加载信息以及堆的统计数据。通常也用于检测死锁。

3     堆转储:JVM 将所有对象的堆内容存储到文件。

 

快照:应用程序启动后,性能分析工具开始收集各种运行时数据,其中一些数据直接显示在监视视图中,而另外大部分数据被保存在内部,直到用户要求获取快照,基于这些保存的数据的统计信息才被显示出来。快照包含了应用程序在一段时间内的执行信息

1     CPU 快照:主要包含了应用程序中函数的调用关系及运行时间,这些信息通常可以在 CPU 快照视图中进行查看。

2     内存快照:主要包含了内存的分配和使用情况、载入的所有类、存在的对象信息及对象间的引用关系等。这些信息通常可以在内存快照视图中进行查看。

 

性能分析:性能分析是通过收集程序运行时的执行数据来帮助开发人员定位程序需要被优化的部分,从而提高程序的运行速度或是内存使用效率

1     CPU 性能分析:CPU 性能分析的主要目的是统计函数的调用情况及执行时间,或者更简单的情况就是统计应用程序的 CPU 使用情况。通常有 CPU 监视和 CPU 快照两种方式来显示 CPU 性能分析结果。

2     内存性能分析:内存性能分析的主要目的是通过统计内存使用情况检测可能存在的内存泄露问题及确定优化内存使用的方向。通常有内存监视和内存快照两种方式来显示内存性能分析结果。

3     线程性能分析:线程性能分析主要用于在多线程应用程序中确定内存的问题所在。一般包括线程的状态变化情况,死锁情况和某个线程在线程生命期内状态的分布情况等

VisualVM部署

下载VisualVM安装程序,解压到本地目录,到bin目录中执行jvisualVM.exe

PS:

启动JvisualVM提示"无法检测到本地java应用程序"解决方式:

将tmp目录中的hsperfdata_userName文件夹设置到ntfs文件格式的分区下即可

查看方式:%TMP%然后设置环境变量到新的目录即可

VisualVM监控界面

VisualVM内存、CPU、线程监控

内存分析

VisualVM 通过检测 JVM 中加载的类和对象信息等帮助我们分析内存使用情况,我们可以通过 VisualVM 的监视标签和 Profiler 标签对应用程序进行内存分析



 

Java堆内存溢出

java.lang.OutOfMemoryError: Java heap space

    at java.util.Arrays.copyOf(Arrays.java:2760)

    at java.util.Arrays.copyOf(Arrays.java:2734)

    at java.util.ArrayList.ensureCapacity(ArrayList.java:167)

    at java.util.ArrayList.add(ArrayList.java:351)

    at com.hongdian.demo.oome.OOMObject.createOOME(OOMObject.java:12)

    at com.hongdian.demo.oome.OOMObject.main(OOMObject.java:21)

 

Java栈和本地方法栈溢出

Exception in thread "main" java.lang.StackOverflowError

at com.hongdian.demo.oome.StackOverflowError.stackLeak

(StackOverflowError.java:8)

at com.hongdian.demo.oome.StackOverflowError.stackLeak

(StackOverflowError.java:8)

 

 

 

CPU分析

VisualVM 能够监控应用程序在一段时间的 CPU 的使用情况,显示 CPU 的使用率、方法的执行效率和频率等相关数据帮助我们发现应用程序的性能瓶颈。



 

线程死锁

Java 语言能够很好的实现多线程应用程序。当我们对一个多线程应用程序进行调试或者开发后期做性能调优的时候,往往需要了解当前程序中所有线程的运行状态,是否有死锁从而分析系统可能存在的问题



 

 

线程死锁信息:DeadLock.java(网络转载)

Found one Java-level deadlock:

=============================

"Thread-1":

  waiting to lock monitor 0x02abed0c (object 0x22eda760, a baor.test.thread.Resource),

  which is held by "Thread-0"

"Thread-0":

  waiting to lock monitor 0x02abeca4 (object 0x22eda770, a baor.test.thread.Resource),

  which is held by "Thread-1"

 

Java stack information for the threads listed above:

===================================================

"Thread-1":

       at baor.test.thread.MyThread2.run(MyThread2.java:17)

       - waiting to lock <0x22eda760> (a baor.test.thread.Resource)

       - locked <0x22eda770> (a baor.test.thread.Resource)

"Thread-0":

       at baor.test.thread.MyThread1.run(MyThread1.java:17)

       - waiting to lock <0x22eda770> (a baor.test.thread.Resource)

       - locked <0x22eda760> (a baor.test.thread.Resource)

 

Found 1 deadlock.

 

 

 

 

 



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


ITeye推荐



使用 VisualVM 进行性能分析及调优

$
0
0

 

使用 VisualVM 进行性能分析及调优

VisualVM 是一款免费的\集成了多个 JDK 命令行工具的可视化工具,它能为您提供强大的分析能力,对 Java 应用程序做性能分析和调优。这些功能包括生成和分析海量数据、跟踪内存泄漏、监控垃圾回收器、执行内存和 CPU 分析,同时它还支持在 MBeans 上进行浏览和操作。本文主要介绍如何使用 VisualVM 进行性能分析及调优。

  • 概述

开发大型 Java 应用程序的过程中难免遇到内存泄露、性能瓶颈等问题,比如文件、网络、数据库的连接未释放,未优化的算法等。随着应用程序的持续运行,可能会造成整个系统运行效率下降,严重的则会造成系统崩溃。为了找出程序中隐藏的这些问题,在项目开发后期往往会使用性能分析工具来对应用程序的性能进行分析和优化。

VisualVM 是一款免费的性能分析工具。它通过 jvmstat、JMX、SA(Serviceability Agent)以及 Attach API 等多种方式从程序运行时获得实时数据,从而进行动态的性能分析。同时,它能自动选择更快更轻量级的技术尽量减少性能分析对应用程序造成的影响,提高性能分析的精度。

本文将对 VisualVM 的主要功能逐一介绍并探讨如何利用获得的数据进行性能分析及调优。

 

性能分析的主要方式

  • 监视:监视是一种用来查看应用程序运行时行为的一般方法。通常会有多个视图(View)分别实时地显示 CPU 使用情况、内存使用情况、线程状态以及其他一些有用的信息,以便用户能很快地发现问题的关键所在。
  • 转储:性能分析工具从内存中获得当前状态数据并存储到文件用于静态的性能分析。Java 程序是通过在启动 Java 程序时添加适当的条件参数来触发转储操作的。它包括以下三种:
    • 系统转储:JVM 生成的本地系统的转储,又称作核心转储。一般的,系统转储数据量大,需要平台相关的工具去分析,如 Windows 上的 windbg 和 Linux 上的 gdb。
    • Java 转储:JVM 内部生成的格式化后的数据,包括线程信息,类的加载信息以及堆的统计数据。通常也用于检测死锁。
    • 堆转储:JVM 将所有对象的堆内容存储到文件。
  • 快照:应用程序启动后,性能分析工具开始收集各种运行时数据,其中一些数据直接显示在监视视图中,而另外大部分数据被保存在内部,直到用户要求获取快照,基于这些保存的数据的统计信息才被显示出来。快照包含了应用程序在一段时间内的执行信息,通常有 CPU 快照和内存快照两种类型。
    • CPU 快照:主要包含了应用程序中函数的调用关系及运行时间,这些信息通常可以在 CPU 快照视图中进行查看。
    • 内存快照:主要包含了内存的分配和使用情况、载入的所有类、存在的对象信息及对象间的引用关系等。这些信息通常可以在内存快照视图中进行查看。
  • 性能分析:性能分析是通过收集程序运行时的执行数据来帮助开发人员定位程序需要被优化的部分,从而提高程序的运行速度或是内存使用效率,主要有以下三个方面:
    • CPU 性能分析:CPU 性能分析的主要目的是统计函数的调用情况及执行时间,或者更简单的情况就是统计应用程序的 CPU 使用情况。通常有 CPU 监视和 CPU 快照两种方式来显示 CPU 性能分析结果。
    • 内存性能分析:内存性能分析的主要目的是通过统计内存使用情况检测可能存在的内存泄露问题及确定优化内存使用的方向。通常有内存监视和内存快照两种方式来显示内存性能分析结果。
    • 线程性能分析:线程性能分析主要用于在多线程应用程序中确定内存的问题所在。一般包括线程的状态变化情况,死锁情况和某个线程在线程生命期内状态的分布情况等

VisualVM 安装

VisualVM 是一个性能分析工具,自从 JDK 6 Update 7 以后已经作为 Oracle JDK 的一部分,位于 JDK 根目录的 bin 文件夹下。VisualVM 自身要在 JDK6 以上的版本上运行,但是它能够监控 JDK1.4 以上版本的应用程序。下面主要介绍如何安装 VisualVM 以及各种 VisualVM 上的插件。

安装 VisualVM

VisualVM 项目的官方网站目前提供英文版本和多语言支持版本下载。多语言版本主要支持英语、日语以及中文三种语言。如果下载安装多语言版本的 VisualVM,安装程序会依据操作系统的当前语言环境去安装相应 VisualVM 的语言版本。最新 VisualVM 版本主要支持的操作系统包括:Microsoft Windows (7, Vista, XP, Server)、Linux、Sun Solaris、Mac OS X、HP-UX 11i。本文以 Microsoft Windows XP 为安装环境并支持中文。

  • VisualVM项目的官方网站上下载 VisualVM 安装程序。
  • 将 VisualVM 安装程序解压缩到本地系统。
  • 导航至 VisualVM 安装目录的 bin 目录,然后启动 jvisualvm.exe。

安装 VisualVM 上的插件

VisualVM插件中心提供很多插件以供安装向 VisualVM 添加功能。可以通过 VisualVM 应用程序安装,或者从 VisualVM插件中心手动下载插件,然后离线安装。另外,用户还可以通过下载插件分发文件 (.nbm 文件 ) 安装第三方插件为 VisualVM 添加功能。

从 VisualVM 插件中心安装插件安装步骤 :

  • 从主菜单中选择“工具”>“插件”。
  • 在“可用插件”标签中,选中该插件的“安装”复选框。单击“安装”。
  • 逐步完成插件安装程序。
图 1. VisualVM 插件管理器
图 1. VisualVM 插件管理器

根据 .nbm 文件安装第三方插件安装步骤 :

  • 从主菜单中选择“工具”>“插件”。
  • 在“已下载”标签中,点击"添加插件"按钮,选择已下载的插件分发文件 (.nbm) 并打开。
  • 选中打开的插件分发文件,并单击"安装"按钮,逐步完成插件安装程序。
图 2. 通过 .nbm 文件安装 VisualVM 插件
图 2. 通过 .nbm 文件安装 VisualVM 插件

功能介绍

下面我们将介绍性能分析的几种常见方式以及如何使用 VisualVM 性能分析工具进行分析。

内存分析

VisualVM 通过检测 JVM 中加载的类和对象信息等帮助我们分析内存使用情况,我们可以通过 VisualVM 的监视标签和 Profiler 标签对应用程序进行内存分析。

在监视标签内,我们可以看到实时的应用程序内存堆以及永久保留区域的使用情况。

图 3. 内存堆使用情况
图 3. 内存堆使用情况
图 4. 永久保留区域使用情况
图 4. 永久保留区域使用情况

此外,我们也可以通过 Applications 窗口右击应用程序节点来启用“在出现 OOME 时生成堆 Dump”功能,当应用程序出现 OutOfMemory 例外时,VisualVM 将自动生成一个堆转储。

图 5. 开启“在出现 OOME 时生成堆”功能
图 5. 开启“在出现 OOME 时生成堆”功能

在 Profiler 标签,点击“内存”按钮将启动一个内存分析会话,等 VisualVM 收集和统计完相关性能数据信息,将会显示在性能分析结果。通过内存性能分析结果,我们可以查看哪些对象占用了较多的内存,存活的时间比较长等,以便做进一步的优化。

此外,我们可以通过性能分析结果下方的类名过滤器对分析结果进行过滤。

图 6. 内存分析结果
图 6. 内存分析结果

CPU 分析

VisualVM 能够监控应用程序在一段时间的 CPU 的使用情况,显示 CPU 的使用率、方法的执行效率和频率等相关数据帮助我们发现应用程序的性能瓶颈。我们可以通过 VisualVM 的监视标签和 Profiler 标签对应用程序进行 CPU 性能分析。

在监视标签内,我们可以查看 CPU 的使用率以及垃圾回收活动对性能的影响。过高的 CPU 使用率可能是由于我们的项目中存在低效的代码,可以通过 Profiler 标签的 CPU 性能分析功能进行详细的分析。如果垃圾回收活动过于频繁,占用了较高的 CPU 资源,可能是由内存不足或者是新生代和旧生代分配不合理导致的等。

图 7. CPU 使用情况
图 7. CPU 使用情况

在 Profiler 标签,点击“CPU”按钮启动一个 CPU 性能分析会话 ,VisualVM 会检测应用程序所有的被调用的方法。当进入一个方法时,线程会发出一个“method entry”的事件,当退出方法时同样会发出一个“method exit”的事件,这些事件都包含了时间戳。然后 VisualVM 会把每个被调用方法的总的执行时间和调用的次数按照运行时长展示出来。

此外,我们也可以通过性能分析结果下方的方法名过滤器对分析结果进行过滤。

图 8. CPU 性能分析结果
图 8. CPU 性能分析结果

线程分析

Java 语言能够很好的实现多线程应用程序。当我们对一个多线程应用程序进行调试或者开发后期做性能调优的时候,往往需要了解当前程序中所有线程的运行状态,是否有死锁、热锁等情况的发生,从而分析系统可能存在的问题。

在 VisualVM 的监视标签内,我们可以查看当前应用程序中所有活动线程和守护线程的数量等实时信息。

图 9. 活跃线程情况
图 9. 活跃线程情况

VisualVM 的线程标签提供了三种视图,默认会以时间线的方式展现。另外两种视图分别是表视图和详细信息视图。

时间线视图上方的工具栏提供了缩小,放大和自适应三个按钮,以及一个下拉框,我们可以选择将所有线程、活动线程或者完成的线程显示在视图中。

图 10. 线程时间线视图
图 10. 线程时间线视图
图 11. 线程表视图
图 11. 线程表视图

我们在详细信息视图中不但可以查看所有线程、活动线程和结束的线程的详细数据,而且也可以查看某个线程的详细情况。

图 12. 线程详细视图
图 12. 线程详细视图

快照功能

我们可以使用 VisualVM 的快照功能生成任意个性能分析快照并保存到本地来辅助我们进行性能分析。快照为捕获应用程序性能分析数据提供了一个很便捷的方式因为快照一旦生成可以在任何时候离线打开和查看,也可以相互传阅。

VisualVM 提供了两种类型的快照:

  • Profiler 快照:当有一个性能分析会话(内存或者 CPU)正在进行时,我们可以通过性能分析结果工具栏的“快照”按钮生成 Profiler 快照捕获当时的性能分析数据。
图 13. Profiler 快照
图 13. Profiler 快照
  • 应用程序快照:我们可以右键点击左侧 Applications 窗口中应用程序节点,选择“应用程序快照”为生成一个应用程序快照。应用程序快照会收集某一时刻的堆转储,线程转储和 Profiler 快照,同时也会捕获 JVM 的一些基本信息。
图 14. 应用程序快照
图 14. 应用程序快照

转储功能

线程转储的生成与分析

VisualVM 能够对正在运行的本地应用程序生成线程转储,把活动线程的堆栈踪迹打印出来,帮助我们有效了解线程运行的情况,诊断死锁、应用程序瘫痪等问题。

图 15. 线程标签及线程转储功能
图 15. 线程标签及线程转储功能

当 VisualVM 统计完应用程序内线程的相关数据,会把这些信息显示新的线程转储标签。

图 16. 线程转储结果
图 16. 线程转储结果

堆转储的生成与分析

VisualVM 能够生成堆转储,统计某一特定时刻 JVM 中的对象信息,帮助我们分析对象的引用关系、是否有内存泄漏情况的发生等。

图 17. 监视标签及堆转储功能
图 17. 监视标签及堆转储功能

当 VisualVM 统计完堆内对象数据后,会把堆转储信息显示在新的堆转储标签内,我们可以看到摘要、类、实例数等信息以及通过 OQL 控制台执行查询语句功能。

堆转储的摘要包括转储的文件大小、路径等基本信息,运行的系统环境信息,也可以显示所有的线程信息。

图 18. 堆转储的摘要视图
图 18. 堆转储的摘要视图

从类视图可以获得各个类的实例数和占用堆大小数,分析出内存空间的使用情况,找出内存的瓶颈,避免内存的过度使用。

图 19. 堆转储的类视图
图 19. 堆转储的类视图

通过实例数视图可以获得每个实例内部各成员变量的值以及该实例被引用的位置。首先需要在类视图选择需要查看实例的类。

图 20. 选择查询实例数的类
图 20. 选择查询实例数的类
图 21. 实例数视图
图 21. 实例数视图

此外,还能对两个堆转储文件进行比较。通过比较我们能够分析出两个时间点哪些对象被大量创建或销毁。

图 22. 堆转储的比较
图 22. 堆转储的比较
图 23. 堆转储的比较结果
图 23. 堆转储的比较结果

线程转储和堆转储均可以另存成文件,以便进行离线分析。

图 24. 转储文件的导出
图 24. 转储文件的导出

 

总结

本文首先简要列举了一些性能分析相关的背景知识。然后介绍了 VisualVM 的下载和安装。最后从内存性能、CPU 性能、快照功能以及转储功能四个方面展开,进一步说明了如何使用 VisualVM 进行性能分析。通过本文的介绍,相信读者对性能分析会有一定的了解,并可以利用 VisualVM 进行性能分析。



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


ITeye推荐




使用Java VisualVM监控远程JVM

$
0
0
我们经常需要对我们的开发的软件做各种测试, 软件对系统资源的使用情况更是不可少, 目前有多个监控工具, 相比JProfiler对系统资源尤其是内存的消耗是非常庞大,JDK1.6开始自带的VisualVM就是不错的监控工具.
这个工具就在JAVA_HOME\bin\目录下的jvisualvm.exe, 双击这个文件就能看到一个比较直观的界面
使用Java VisualVM监控远程JVM - liuyb_94242 - 我的空间我做主

从左边Applications树中可以知道,不光可以监控本地JVM运行情况, 还可以监控远程机器上的JVM运行情况.
本地监控:只要打开某个JAVA程序就会自动的加入到本地监控中.

因为本地监控无需配置, 所以这里主要介绍监控远程JVM
要进行远程监控, 本机的VisualVM就必须和远程的JVM要进行通信, Visualvm目前支持两种remote connection方式.
分别是jstatd和JMX方式: 这里我主要介绍的是通过JMX方式.

通过JMX连接远程机器, 需要经过下面的配置:
1. 修改远程机器JDK配置文件 (我这里远程机器是linux).
   a.进入JAVA_HOME\jre\lib\management\目录
   b.拷贝jmxremote.password.template这个文件到当前目录, 并改名为jmxremote.password
     c.打开jmxremote.password文件,去掉 # monitorRole  QED 和 # controlRole  R&D 这两行前面的注释符号

2. 修改远程机器上需要被监控的程序的配置文件 (我这里是监控Tomcat容器内部署的应用).
   a.进入TOMCAT_HOME\bin目录
     b.打开catalina.sh文件,加入如下信息:
        JAVA_OPTS="$JAVA_OPTS -Djava.rmi.server.hostname=192.168.0.237
                                                     -Dcom.sun.management.jmxremote.port=18999
                                                     -Dcom.sun.management.jmxremote.ssl=false
                                                     -Dcom.sun.management.jmxremote.authenticate=false"
   c.重启Tomcat服务.

3. 客户端VisualVM配置 (我客户端用的是WinXP).
     a.直接反键点击Remote,选择Add Remote Host...
     b.在弹出的界面中输入远程机器的IP地址(192.168.0.237),这个IP地址会加入到Remote节点下.
     c.反键点击这个IP地址,选择Add JMX Connection, 在弹出的界面中输入刚配置的端口号(18999), 这个连接会加入到该IP节点下.
     d.反键点击这个连接,选择Open.

此时就可以看到监控的界面, 从界面上我们可以看到CPU信息, 内存信息, 统计加载类数量,线程信息.

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


ITeye推荐



交换机路由器转发包的过程

$
0
0

当交换机收到一个数据包时;
交换机会将这个数据包包头的目的MAC信息提取出来,与自身的MAC地址表比较;
情况一:如果找到对应项,则按MAC表进行转发;
情况二:如果没找到对应项, 则在除了接收到数据包以外的所有端口进行转发(广播);

解释:MAC地址表 是MAC地址与端口对应的关系表;即某个MAC地址连接在某个端口;
问题:当情况二发生时,需要在其他所有端口进行广播(这样做其实很不好),这个广播会到什么地方结束呢?答:——路由器。

为什么到路由器就会结束了呢?
路由器接收到一个数据包时,工作过程如下:
首先提取数据包头的目的MAC信息,与自身MAC表比较:
情况一:如果找到对应项,则按MAC表进行转发(与交换机一样);
情况二(亮点来了):如果没找到则提取数据包头的目的IP信息,则与自身的路由表进行比较(又分两种情况)
       情况二。1找到了对应的路由表,则按着路由表转发(与查到MAC表很像啊)。
       情况二。2没找到对应路由表,则按着 缺省路由转发(发现了吗自始自终没有出现过广播)

解释:路由表和MAC的区别在于,路由表存放的是目的IP下一步要去的地方的IP。

广播在路由器能够结束,原因就是路由器会在查找不到对应MAC表时,根据目的IP进行路由。路由过程的两种情况都不存在广播。

 

文章转载自: http://blog.csdn.net/liuaibing/article/details/7263106

 

 

网络设备之间通信经过以下几个步骤:

1.发送端的数据向外发送一个数据包;

2.系统判断这个数据包的目标地址是否在同一个网段;

3.若与发送机属于同一网段,系统直接将数据包封装成帧,通过二层设备发送到本网段内的目标地址;

4.若不在同一网段,系统将数据包转发到网关,重新封装;

5.网关查看数据包送达的目标ip地址;

6.系统根据目标ip地址查找路由表,决定转发端口;

7.重新封装转发到下一个路由器;

8.网关发现目标地址属于本网段,查找MAC表(ip与mac对应关系),封装成帧发送到目标机器网卡;

9.目标主机验证后传送给上层应用。

 

 

IP数据包到路由器之后,它首先要读去IP包头的目标IP地址,然后查看路由表,根据路由协议算法,确定一条最佳的路径,为什么要这样做呢,那是因为要确定这个数据包应该从路由器上的那个接口上转发出去,很明显路由的每一条可用的路由都和路由嚣上的接口是对应的,就这样一个数据包就离开了这个路由器。事情就是这样。看见楼上有一个不太正确的说法,IP数据包头里的源IP和目标IP都不会被改变,如果改变数据包不可能到达或者回来,但只有一种情况,源和目标IP是被改变的,那就是NAT.

路由器转发数据包不会对它的IP源地址和目标地址做修改,只会修改MAC.

具体路由器转发规则如下:

当主机A发向主机B的数据流在网络层封装成IP数据包,IP数据包的首部包含了源地址和目标地址。主机A会用本机配置的24位IP网络掩码255.255.255.0与目标地址进行与运算,得出目标网络地址与本机的网络地址是不是在同一个网段中。如果不是将IP数据包转发到网关。

在发往网关前主机A还会通过ARP的请求获得默认网关的MAC地址。在主机A数据链路层IP数据包封装成以太网数据帧,然后才发住到网关……也就是路由器上的一个端口。

当网关路由器接收到以太网数据帧时,发现数据帧中的目标MAC地址是自己的某一个端口的物理地址,这时路由器会把以太网数据帧的封装去掉。路由器认为这个IP数据包是要通过自己进行转发,接着它就在匹配路由表。匹配到路由项后,它就将包发往下一条地址。

路由器转发数据包就是这样,所以它始终是不会改IP地址的。只会改MAC.

当有数据包传到路由器时,路由器首先将其的目的地址与路由表进行对比,如果是本地网络,将不会进行转发到外网络,而是直接转发给本地网内的目的主机,改变的只是数据包的源地址,(原源地址MAC变为了路由器的MAC);当然其中的过程关系到相关的协议的使用,像什么ARP,IP等。但是如果目的地址经路由表对比,发现不是在本网中,有nat就将改变源地址的IP(原源地址的Ip地址改为了路由器的IP地址),路由器将熟数据包转发到相应的端口,进行通信。

如:A访问B,首先对比是否同一子网,如果是,检查ARP表,有B的MAC就直接发送,没有就发送ARP请求.如果否,发送到默认网关C,源IP为A,源MAC为A,目的IP为B,目的MAC地址为C,C接收到这个包,检查路由表,发送到下一跳D,源IP为A,源MAC为C,目的IP为B,目的MAC为D.....如此循环,直到发送到B.NAT为特殊应用,会修改源IP为网关自己外网IP。



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


ITeye推荐



提供信源链接,让中文互联网真正“互联”起来

$
0
0

为什么各种假托CNN、路透社名义的谣言易于在中文互联网世界流传?一个重要原因是,大家没有养成提供原始信源链接的习惯。实际上,网络媒体提供信源链接是一种基本的传播伦理,也是一种简单却能造福公众的行为。

马航MH370事件让等待真相的人们真切地体会到了“信源”的重要性。尤其是在最初的24小时内,社交媒体上呈现了“ 一场不可思议的流言盛宴”, 各种打着“CNN说”、“美联社报道”、“路透社消息”,乃至“美国CIA披露”名头的消息上泛滥不休。直到有心人亲自登陆CNN网站查询,才发现大部分不过是好事者假托权威信源伪造的不实消息。

谣言的流行,自然与社交媒体的特性有关,也与国人的信息判断力尚待提升有关,但还有一个被人们忽略的关键因素为谣言传播提供了温床,那就是:在中文互联网世界里,人们没有养成提供信源原始出处链接的习惯,读者也未充分意识到信源出处链接的重要性。

相较于习惯提供大量链接的英美等国,中国的互联网其实在很大程度上还没有真正“互联”起来。这既影响了网络信息的质量,更阻碍了用户获得更广泛的信息。

1、提供信源链接已是西方媒体的标准动作

如果你有通过网络阅读西方主流媒体的习惯,那么你应该对网页文字内提供的大量链接并不陌生,因为这已经是西方媒体的标准动作。

让我们随便选取一个例子——《华盛顿邮报》对MH370的一篇报道《 中国、澳大利亚在搜寻中发现新的疑似残骸》。简单数一数就可以发现,这篇并不算太长的报道中,一共提供了6个链接,这些链接的作用都非常关键——

第一个链接指向的是CCTV英语新闻频道在Twitter发布的一则消息,其中包含了卫星图片。

第二个链接指向的是澳大利亚海事安全局发布的媒体通稿原文。

第三个链接指向的是澳大利亚海事安全局提供的地图和数据文件。

第四个链接指向的是《华盛顿邮报》此前的另一篇报道,介绍的是搜救海域的天气状况,为读者理解文中内容提供背景信息。

第五个链接指向的是该报制作的信息图,同样是提供背景信息。

第六个链接指向的是《悉尼晨锋报》的一则报道。

不难看出,这些链接指向的网站虽然各不相同,但都肩负着同样的使命:为报道中提到的信息提供原始出处,既佐证了信息的权威性,又让感兴趣的读者可以轻易了解到更多相关信息。

这只是我随机选取的一个简单例子。西方主流媒体网站(更不用说博客这种诞生于互联网上的内容产品)的几乎每一篇报道都是这样做的,他们之间真正连成了密集的网络。

2、中国网络媒体没有提供信源链接的习惯

相较之下,中国的网站则像是一个个缺乏联系的孤岛。感兴趣的读者可以随机打开几个网站的几篇文章,数数文中提供了多少链接,相信平均下来每篇不会超过一个,因为大部分文章完全不提供链接。

这样做的缺点是什么?简单而言有两方面。其一,感兴趣的读者无法通过一篇文章顺藤摸瓜找到更多的相关信息。其二,为虚假信息的流传提供了便利,因为各类信息都不必提供原始出处作为佐证。

给出信源原始链接,不仅适用于传统网站,也适用于社交媒体——从一定程度上说,社交媒体甚至更需要原始链接。试想,如果提供信源出处链接已经成为中文互联网媒体的通行准则,那些假托CNN之名的谣言也就很难出现和生存了。

3、提供链接是一项基本的传播伦理

2012年底,哥伦比亚新闻学院Tow数字新闻中心曾经推出一份研究报告《 后工业时代的新闻业》,报告中记录了传媒业的诸多变化,并给处于危机中的媒体给出了许多中肯的建议。建议之一即是:永远链接到原始出处。

报告中说:

链接是网络的基本技术特征,它将互联网与其他出版形式区分开来,因为它告诉用户:“如果你想了解更多关于这个话题的内容,你可以在这里找到更多相关资料。”这也显示了对用户的一种尊重——尊重其兴趣,尊重其自己跟踪寻找内容的能力。

在新闻操作中,最基本的链接形式就是链接到材料的原始出处。对一桩诉讼的讨论应该链接到起诉书原文,对一篇科学论文的讨论应该链接到原文,对一则搞笑视频的介绍应该链接到这则视频(如果能嵌入就更好了)。

这份报告甚至将提供链接称为“核心的传播伦理之一”。它还分析了一些网络媒体不愿这样做的原因,并一一批驳:编辑记者不习惯这种互联网的新形式?拜托,网络都出现这么多年了,编辑记者哪个不是天天泡在微博微信上?这样做会不会让我的用户点着点着就跑去其他网站了?这种商业考虑或许对于广告部门来说有道理,但新闻媒体是一项事关服务公众的工作,如果仅仅因此就拒绝给出链接,那将是非常令人震惊和不安的——事实上,一个真正互联互通的网络增加的是所有网站的曝光率,也很有可能增加每一个用户的阅读频率和阅读时间,人人都能从中受益。

结语

正如哥伦比亚大学新闻学院的报告中所言,“链接到原始出处的公共价值是非常明显的,而且做到这一点非常容易。那些拒绝这样做的媒体完全是在藐视受众,藐视公共传播的道德规范。”

换句话说,不给出处链接就是耍流氓。在此真诚向中文网络媒体呼吁:向西方同行看齐,在你们的文章中尽量增加信源链接吧,让我们共同构建一个真正互联互通的网络。

(本文首发于 腾讯反思媒体专栏。遗憾的是,发表时,腾讯将文中的所有链接都去掉了。)

您可能也喜欢:

互联网审查问题的经济解法

中国互联网法治之惑(二)

互联网时代的人:更强大还是更脆弱?

中国互联网法治之惑(三)
无觅

智能电视变成监听工具

$
0
0
NCC Groupp的安全专家演示了如何将智能电视变成监听工具。智能电视内置了扬声器和存储器,可以被恶意程序利用记录会话。间谍程序可通过物理接触或恶意应用下载安装到电视机上。NCC Group是通过物理接触方式安装间谍软件,该公司的安全专家认为恶意应用可伪装成合法应用通过设备制造商的应用商店安装到电视机上,智能电视支持自动更新,因此恶意应用可释出恶意更新将合法应用变成间谍软件。NCC Group演示用内部存储器记录30秒钟的会话,会话记录时间可以更长,30秒钟只是用于演示目的。更先进的恶意攻击可以在本地储存长时间的会话记录,然后再定时上传数据,或者也直接将监听的数据流上传到一台服务器,绕过本地储存。前NSA合同工Edward Snowden在香港期间见律师时都要求将手机放到冰箱里以防止监听。新的研究暗示他可能还需要担心附近的电视。






有趣的机器学习:最简明入门指南

$
0
0

在听到人们谈论机器学习的时候,你是不是对它的涵义只有几个模糊的认识呢?你是不是已经厌倦了在和同事交谈时只能一直点头?让我们改变一下吧!

本指南的读者对象是所有对机器学习有求知欲但却不知道如何开头的朋友。我猜很多人已经读过了“机器学习”的 维基百科词条,倍感挫折,以为没人能给出一个高层次的解释。本文就是你们想要的东西。

本文目标在于平易近人,这意味着文中有大量的概括。但是谁在乎这些呢?只要能让读者对于ML更感兴趣,任务也就完成了。

何为机器学习?

机器学习这个概念认为,对于待解问题,你无需编写任何专门的程序代码,泛化算法(generic algorithms)能够在数据集上为你得出有趣的答案。对于泛化算法,不用编码,而是将数据输入,它将在数据之上建立起它自己的逻辑。

举个例子,有一类算法称为分类算法,它可以将数据划分为不同的组别。一个用来识别手写数字的分类算法,不用修改一行代码,就可以用来将电子邮件分为垃圾邮件和普通邮件。算法没变,但是输入的训练数据变了,因此它得出了不同的分类逻辑。

机器学习算法是个黑盒,可以重用来解决很多不同的分类问题。

“机器学习”是一个涵盖性术语,覆盖了大量类似的泛化算法。

 

两类机器学习算法

你可以认为机器学习算法分为两大类: 监督学习监督学习。两者区别很简单,但却非常重要。

监督学习

假设你是一名房产经纪,生意越做越大,因此你雇了一批实习生来帮你。但是问题来了——你可以看一眼房子就知道它到底值多少钱,实习生没有经验,不知道如何估价。

为了帮助你的实习生(也许是为了解放你自己去度个假),你决定写个小软件,可以根据房屋大小、地段以及类似房屋的成交价等因素来评估你所在地区房屋的价值。

你把3个月来城里每笔房屋交易都写了下来,每一单你都记录了一长串的细节——卧室数量、房屋大小、地段等等。但最重要的是,你写下了最终的成交价:

这是我们的“训练数据”。

我们要利用这些训练数据来编写一个程序来估算该地区其他房屋的价值:

这就称为 监督学习。你已经知道每一栋房屋的售价,换句话说,你知道问题的答案,并可以反向找出解题的逻辑。

为了编写软件,你将包含每一套房产的训练数据输入你的机器学习算法。算法尝试找出应该使用何种运算来得出价格数字。

这就像是算术练习题,算式中的运算符号都被擦去了: 

天哪!一个阴险的学生将老师答案上的算术符号全擦去了。

看了这些题,你能明白这些测验里面是什么样的数学问题吗?你知道,你应该对算式左边的数字“做些什么”以得出算式右边的答案。

在监督学习中,你是让计算机为你算出数字间的关系。而一旦你知道了解决这类特定问题所需要的数学方法后,你就可以解答同类的其它问题了。

无监督学习

 

让我们回到开头那个房地产经纪的例子。要是你不知道每栋房子的售价怎么办?即使你所知道的只是房屋的大小、位置等信息,你也可以搞出很酷的花样。这就是所谓的 无监督学习

 
即使你不是想去预测未知的数据(如价格),你也可以运用机器学习完成一些有意思的事。

这就有点像有人给你一张纸,上面列出了很多数字,然后对你说:“我不知道这些数字有什么意义,也许你能从中找出规律或是能将它们分类,或是其它什么-祝你好运!”

你该怎么处理这些数据呢?首先,你可以用个算法自动地从数据中划分出不同的细分市场。也许你会发现大学附近的买房者喜欢户型小但卧室多的房子,而郊区的买房者偏好三卧室的大户型。这些信息可以直接帮助你的营销。

你还可以作件很酷的事,自动找出房价的离群数据,即与其它数据迥异的值。这些鹤立鸡群的房产也许是高楼大厦,而你可以将最优秀的推销员集中在这些地区,因为他们的佣金更高。

本文余下部分我们主要讨论监督学习,但这并不是因为无监督学习用处不大或是索然无味。实际上,随着算法改良,不用将数据和正确答案联系在一起,因此无监督学习正变得越来越重要。

老学究请看:还有很多其它种类的机器学习算法。但初学时这样理解不错了。

 

太酷了,但是评估房价真能被看作“学习”吗?

作为人类的一员,你的大脑可以应付绝大多数情况,并且没有任何明确指令也能够学习如何处理这些情况。如果你做房产经纪时间很长,你对于房产的合适定价、它的最佳营销方式以及哪些客户会感兴趣等等都会有一种本能般的“感觉”。强人工智能(Strong AI)研究的目标就是要能够用计算机复制这种能力。

但是目前的机器学习算法还没有那么好——它们只能专注于非常特定的、有限的问题。也许在这种情况下,“学习”更贴切的定义是“在少量范例数据的基础上找出一个等式来解决特定的问题”。

不幸的是,“机器在少量范例数据的基础上找出一个等式来解决特定的问题”这个名字太烂了。所以最后我们用“机器学习”取而代之。

当然,要是你是在50年之后来读这篇文章,那时我们已经得出了强人工智能算法,而本文看起来就像个老古董。未来的人类,你还是别读了,叫你的机器仆人给你做份三明治吧。

让我们写代码吧!

前面例子中评估房价的程序,你打算怎么写呢?往下看之前,先思考一下吧。

如果你对机器学习一无所知,很有可能你会尝试写出一些基本规则来评估房价,如下:

def estimate_house_sales_price(num_of_bedrooms, sqft, neighborhood):
  price = 0

  # In my area, the average house costs $200 per sqft
  price_per_sqft = 200

  if neighborhood == "hipsterton":
    # but some areas cost a bit more
    price_per_sqft = 400

  elif neighborhood == "skid row":
    # and some areas cost less
    price_per_sqft = 100

  # start with a base price estimate based on how big the place is
  price = price_per_sqft * sqft

  # now adjust our estimate based on the number of bedrooms
  if num_of_bedrooms == 0:
    # Studio apartments are cheap
    price = price — 20000
  else:
    # places with more bedrooms are usually
    # more valuable
    price = price + (num_of_bedrooms * 1000)

 return price

假如你像这样瞎忙几个小时,也许会取得一点成效,但是你的程序永不会完美,而且当价格变化时很难维护。

如果能让计算机找出实现上述函数功能的办法,这样岂不更好?只要返回的房价数字正确,谁会在乎函数具体干了些什么呢?

def estimate_house_sales_price(num_of_bedrooms, sqft, neighborhood):
  price = <computer, plz do some math for me>

  return price

考虑这个问题的一种角度是将房价看做一碗美味的汤,而汤中成分就是卧室数、面积和地段。如果你能算出每种成分对最终的价格有多大影响,也许就能得到各种成分混合起来形成最终价格的具体比例。

这样可以将你最初的程序(全是疯狂的if else语句)简化成类似如下的样子:

def estimate_house_sales_price(num_of_bedrooms, sqft, neighborhood):
 price = 0

 # a little pinch of this
 price += num_of_bedrooms * .841231951398213

 # and a big pinch of that
 price += sqft * 1231.1231231

 # maybe a handful of this
 price += neighborhood * 2.3242341421

 # and finally, just a little extra salt for good measure
 price += 201.23432095

 return price

请注意那些用粗体标注的神奇数字—— .841231951398213,  1231.1231231, 2.3242341421,  201.23432095。它们称为 权重。如果我们能找出对每栋房子都适用的完美权重,我们的函数就能预测所有的房价!

找出最佳权重的一种笨办法如下所示:

步骤1:

首先,将每个权重都设为1.0:

def estimate_house_sales_price(num_of_bedrooms, sqft, neighborhood):
  price = 0

  # a little pinch of this
  price += num_of_bedrooms * 1.0

  # and a big pinch of that
  price += sqft * 1.0

  # maybe a handful of this
  price += neighborhood * 1.0

  # and finally, just a little extra salt for good measure
  price += 1.0

  return price

步骤2:

将每栋房产带入你的函数运算,检验估算值与正确价格的偏离程度:

运用你的程序预测房屋价格。

 

例如:上表中第一套房产实际成交价为25万美元,你的函数估价为17.8万,这一套房产你就差了7.2万。

再将你的数据集中的每套房产估价偏离值平方后求和。假设数据集中有500套房产交易,估价偏离值平方求和总计为86,123,373美元。这就反映了你的函数现在的“正确”程度。

现在,将总计值除以500,得到每套房产的估价偏离平均值。将这个平均误差值称为你函数的 代价

如果你能调整权重使得这个代价变为0,你的函数就完美了。它意味着,根据输入的数据,你的程序对每一笔房产交易的估价都是分毫不差。而这就是我们的目标——尝试不同的权重值以使代价尽可能的低。

步骤3:

不断重复步骤2,尝试 所有可能的权重值组合。哪一个组合使得代价最接近于0,它就是你要使用的,你只要找到了这样的组合,问题就得到了解决!

思想扰动时间

这太简单了,对吧?想一想刚才你做了些什么。你取得了一些数据,将它们输入至三个通用的简单步骤中,最后你得到了一个可以对你所在区域的房屋进行估价的函数。房价网,要当心咯!
但是下面的事实可能会扰乱你的思想:

1.过去40年来,很多领域(如语言学/翻译学)的研究表明,这种通用的“搅动数据汤”(我编造的词)式的学习算法已经胜过了需要利用真人明确规则的方法。机器学习的“笨”办法最终打败了人类专家。

2.你最后写出的函数真是笨,它甚至不知道什么是“面积”和“卧室数”。它知道的只是搅动,改变数字来得到正确的答案。

3.很可能你都不知道为何一组特殊的权重值能起效。所以你只是写出了一个你实际上并不理解却能证明的函数。
4.试想一下,你的程序里没有类似“面积”和“卧室数”这样的参数,而是接受了一组数字。假设每个数字代表了你车顶安装的摄像头捕捉的画面中的一个像素,再将预测的输出不称为“价格”而是叫做“方向盘转动度数”, 这样你就得到了一个程序可以自动操纵你的汽车了!

太疯狂了,对吧?

 

步骤3中的“尝试每个数字”怎么回事?

好吧,当然你不可能尝试所有可能的权重值来找到效果最好的组合。那可真要花很长时间,因为要尝试的数字可能无穷无尽。
为避免这种情况,数学家们找到了很多 聪明的办法来快速找到优秀的权重值,而不需要尝试过多。下面是其中一种:
首先,写出一个简单的等式表示前述步骤2:

这是你的 代价函数

接着,让我们将这同一个等式用机器学习的数学术语(现在你可以忽略它们)进行重写:

  θ表示当前的权重值。 J(θ) 意为“当前权重值对应的代价”。

这个等式表示我们的估价程序在当前权重值下偏离程度的大小。
如果将所有赋给卧室数和面积的可能权重值以图形形式显示,我们会得到类似下图的图表:

代价函数的图形像一支碗。纵轴表示代价。

图中蓝色的最低点就是代价最低的地方——即我们的程序偏离最小。最高点意味着偏离最大。所以,如果我们能找到一组权重值带领我们到达图中的最低点,我们就找到了答案!

因此,我们只需要调整权重值使我们在图上能向着最低点“走下坡路”。如果对于权重的细小调节能一直使我们保持向最低点移动,那么最终我们不用尝试太多权重值就能到达那里。

如果你还记得一点微积分的话,你也许记得如果你对一个函数求导,结果会告诉你函数在任一点的斜率。换句话说,对于图上给定一点,它告诉我们那条路是下坡路。我们可以利用这一点朝底部进发。

所以,如果我们对代价函数关于每一个权重求偏导,那么我们就可以从每一个权重中减去该值。这样可以让我们更加接近山底。一直这样做,最终我们将到达底部,得到权重的最优值。(读不懂?不用担心,接着往下读)。

这种找出最佳权重的办法被称为 批量梯度下降,上面是对它的高度概括。如果想搞懂细节,不要害怕,继续 深入下去吧。

当你使用机器学习算法库来解决实际问题,所有这些都已经为你准备好了。但明白一些具体细节总是有用的。

 

还有什么你随便就略过了?

上面我描述的三步算法被称为 多元线性回归。你估算等式是在求一条能够拟合所有房价数据点的直线。然后,你再根据房价在你的直线上可能出现的位置用这个等式来估算从未见过的房屋的价格。这个想法威力强大,可以用它来解决“实际”问题。

但是,我为你展示的这种方法可能在简单的情况下有效,它不会在所有情况下都有用。原因之一是因为房价不会一直那么简单地跟随一条连续直线。

但是,幸运的是,有很多办法来处理这种情况。对于非线性数据,很多其他类型的机器学习算法可以处理(如神经网络或有核向量机)。还有很多方法运用线性回归更灵活,想到了用更复杂的线条来拟合。在所有的情况中,寻找最优权重值这一基本思路依然适用。

还有,我忽略了 过拟合的概念。很容易碰上这样一组权重值,它们对于你原始数据集中的房价都能完美预测,但对于原始数据集之外的任何新房屋都预测不准。这种情况的解决之道也有不少(如正则化以及使用交叉验证数据集)。学会如何处理这一问题对于顺利应用机器学习至关重要。

换言之,基本概念非常简单,要想运用机器学习得到有用的结果还需要一些技巧和经验。但是,这是每个开发者都能学会的技巧。

 

机器学习法力无边吗?

一旦你开始明白机器学习技术很容易应用于解决貌似很困难的问题(如手写识别),你心中会有一种感觉,只要有足够的数据,你就能够用机器学习解决任何问题。只需要将数据输入进去,就能看到计算机变戏法一样找出拟合数据的等式。

但是很重要的一点你要记住,机器学习只能对用你占有的数据实际可解的问题才适用。

例如,如果你建立了一个模型来根据每套房屋内盆栽数量来预测房价,它就永远不会成功。房屋内盆栽数量和房价之间没有任何的关系。所以,无论它怎么去尝试,计算机也推导不出两者之间的关系。

你只能对实际存在的关系建模。

怎样深入学习机器学习

我认为,当前机器学习的最大问题是它主要活跃于学术界和商业研究组织中。对于圈外想要有个大体了解而不是想成为专家的人们,简单易懂的学习资料不多。但是这一情况每一天都在改善。

吴恩达教授(Andrew Ng)在 Coursera上的机器学习免费课程非常不错。我强烈建议由此入门。任何拥有计算机科学学位、还能记住一点点数学的人应该都能理解。

另外,你还可以下载安装 SciKit-Learn,用它来试验成千上万的机器学习算法。它是一个python框架,对于所有的标准算法都有“黑盒”版本。

有趣的机器学习:最简明入门指南,首发于 博客 - 伯乐在线

原生AJAX

$
0
0

1、XMLHttpRequest

对象是ajax的基础,几乎所有的浏览器都支持他,只是创建方式不同,如IE5,IE6

var xmlhttp;
if (window.XMLHttpRequest)
  {// code for IE7+, Firefox, Chrome, Opera, Safari
  xmlhttp=new XMLHttpRequest();
  }
else
  {// code for IE6, IE5
  xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
  }

2、AJAX - 向服务器发送请求请求

get请求

xmlhttp.open("GET","demo_get2.html?fname=Henry&lname=Ford",true);
xmlhttp.send();

 

POST 请求

xmlhttp.open("POST","ajax_test.html",true);
xmlhttp.setRequestHeader("Content-type","application/x-www-form-urlencoded");
xmlhttp.send("fname=Henry&lname=Ford");

区别

GET 还是 POST?

与 POST 相比,GET 更简单也更快,并且在大部分情况下都能用。

然而,在以下情况中,请使用 POST 请求:

  • 无法使用缓存文件(更新服务器上的文件或数据库)
  • 向服务器发送大量数据(POST 没有数据量限制)
  • 发送包含未知字符的用户输入时,POST 比 GET 更稳定也更可靠

3、响应

属性 描述
responseText获得字符串形式的响应数据。
responseXML获得 XML 形式的响应数据。

 

1)document.getElementById("myDiv").innerHTML=xmlhttp.responseText;

2)xmlDoc=xmlhttp.responseXML;

txt="";
x=xmlDoc.getElementsByTagName("ARTIST");
for (i=0;i<x.length;i++)
  {
  txt=txt + x[i].childNodes[0].nodeValue + "<br>";
  }
document.getElementById("myDiv").innerHTML=txt;

4、状态

属性 描述
onreadystatechange存储函数(或函数名),每当 readyState 属性改变时,就会调用该函数。
readyState存有 XMLHttpRequest 的状态。从 0 到 4 发生变化。
  • 0: 请求未初始化
  • 1: 服务器连接已建立
  • 2: 请求已接收
  • 3: 请求处理中
  • 4: 请求已完成,且响应已就绪
status200: "OK"
404: 未找到页面

xmlhttp.onreadystatechange=function()
  {
  if (xmlhttp.readyState==4 && xmlhttp.status==200)
    {
    document.getElementById("myDiv").innerHTML=xmlhttp.responseText;
    }
  }



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


ITeye推荐



从注册流程 分析如何安全退出多个Activity 多种方式(附DEMO)

$
0
0

前言


由于一个同学问到我如何按照一个流程走好之后回到首页,我以前看到过4个解决方案,后来发现有做个记录和总结的必要,就写了这篇博文。(之前看小强也写过一篇,这里通过自身的分析完整的总结一下以下6种方案,并加上一个DEMO便于大家了解大体流程)


在android的用户交互中,按钮触发的意图(Intent)跳转会为你重新打开新的一个界面活动(Activity),对于之前的界面根据需求进行摧毁(Finish())或则保留。


如果一个交互流程中,是从A开始,按照A - B - C - D - A这样的顺序进行的话,那么B,C,D这3个活动界面会根据你D中最后的操作来进行保留或是摧毁,例如


(1)注册流程中,在A界面点击注册,通过B,C,D界面完成注册后,B,C,D就随之摧毁,而如果D中注册不成功没跳转会A的话,那么B,C,D就不能摧毁,之前所填的内容也必须保留。


(2)客户端交互中,返回首页按钮,由于在频繁的点击打开过多的界面(如微信查看朋友圈),返回首页就必须一个一个back回去,所有有的客户端为了优化用户体验,便会加入一个按钮返回首页(之前打开的全部关闭)。


以上几个例子都涉及到了   ---    如何安全退出多个ACTIVITY    这个问题。


其实,这个问题的解决方案有好多种,并且各有各的优缺点,下面就罗列出多个方案以及各个方案的优缺点所在,以便用户根据需求选择。


知识结构


首先,通过大致的思维导图罗列出了以下几个知识点,来帮助你去分析学习:


1. Activity的启动模式                        

2. intent:  Flags属性,以及其显、隐式        

3.Application : 全局的使用

4.Activity:  onActivityResult(int requestCode, int resultCode, Intent data)方法

5. 栈的概念:Last-In/First-Out(LIFO)   ---  后进先出的原则 

6.BroadcastReceiver 广播

7. 栈的引申的知识点:(1)ArrayList和LinkedList的区别  (2)android 栈和队列


以上的 (1)Activity的启动模式  (2)intent:  Flags属性  (3)栈的概念         

我通过一篇文章写明了他们3者的联系可以点击以下链接查看

Activity启动模式 及 Intent Flags 与 栈 的关联分析



具体方案


方案1

方法:采用FLAG_ACTIVITY_CLEAR_TOP退出整个程序(多activity)

思路:通过Intent的Flags来控制堆栈去解决

android中,每打开一个Activity,便会在栈中加入一个Activity,当该Activity被摧毁后,栈中便移除了它,并且栈中的Activity是按照开打的先后顺序依次排排列的。

Android的窗口类提供了历史栈,我们可以通过stack的原理来巧妙的实现,这里我们在A窗口打开B窗口时在Intent中直接加入标 志 Intent.FLAG_ACTIVITY_CLEAR_TOP,这样开启B时将会清除该进程空间的所有Activity。

代码:

在注册流程最后的FourthStep.class中,点击完成注册点击事件

		btn_finish.setOnClickListener(new OnClickListener() {

			@Override
			public void onClick(View v) {
				// TODO Auto-generated method stub
				Intent intent = new Intent(INTENT_METHOD_FIRST_SINGUP);
				intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
				startActivity(intent);
			}
		});
其中的 INTENT_METHOD_FIRST_SINGUP 是登录界面的Intent隐式Action。

优缺点:

优:使用对栈的巧妙利用,不会赞成内存无故占用等问题,个人认为这个方法是首选。


方案2

方法:通过堆栈管理器去管理

思路:通过堆栈管理器,对Stack进的存储Activity进行操作(推入,推出,弹出)

代码:

public class StackManager {
	/**
	 * Stack 中对应的Activity列表  (也可以写做 Stack<Activity>)
	 */
	private static Stack mActivityStack;
	private static StackManager mInstance;

	/**
	 * @描述 获取栈管理工具
	 * @return ActivityManager
	 */
	public static StackManager getStackManager() {
		if (mInstance == null) {
			mInstance = new StackManager();
		}
		return mInstance;
	}

	/**
	 * 推出栈顶Activity
	 */
	public void popActivity(Activity activity) {
		if (activity != null) {
			activity.finish();
			mActivityStack.remove(activity);
			activity = null;
		}
	}

	/**
	 * 获得当前栈顶Activity
	 */
	public Activity currentActivity() {
		//lastElement()获取最后个子元素,这里是栈顶的Activity
		if(mActivityStack == null || mActivityStack.size() ==0){
			return null;
		}
		Activity activity = (Activity) mActivityStack.lastElement();
		return activity;
	}

	/**
	 * 将当前Activity推入栈中
	 */
	public void pushActivity(Activity activity) {
		if (mActivityStack == null) {
			mActivityStack = new Stack();
		}
		mActivityStack.add(activity);
	}

	/**
	 * 弹出指定的clsss所在栈顶部的中所有Activity
	 * @clsss : 指定的类 
	 */
	public void popTopActivitys(Class clsss) {
		while (true) {
			Activity activity = currentActivity();
			if (activity == null) {
				break;
			}
			if (activity.getClass().equals(clsss)) {
				break;
			}
			popActivity(activity);
		}
	}
	
	/**
	 * 弹出栈中所有Activity
	 */
	public void popAllActivitys() {
		while (true) {
			Activity activity = currentActivity();
			if (activity == null) {
				break;
			}
			popActivity(activity);
		}
	}
}
之后在注册流程中的对应步骤的Activity的onCreate()中把当前 Activity推入栈列表,完成注册流程后,弹出 栈列表中流程所涉及的 Activity。
优缺点:

缺:如果处理不当,容易造成不在当前界面的Activity被全局引用而摧毁不掉,内存得不到释放,从而无故占用不必要的内存。


方案3:

方法:全局记录打开的Activity或通过一个自定义的类去管理打开的Activity

思路:通过在Application中用一个列表来记录当前所打开的Activity,根据需求去遍历finish()。

描述和方案2有点类似。

代码:

public class AppApplication extends Application {
	private static AppApplication mAppApplication;
	/** 当前打开的activity列表 */
	public ArrayList<Activity> activityList;

	@Override
	public void onCreate() {
		// TODO Auto-generated method stub
		super.onCreate();
		mAppApplication = this;
	}

	/** 获取Application */
	public static AppApplication getApp() {
		if (mAppApplication == null) {
			mAppApplication = new AppApplication();
		}
		return mAppApplication;
	}

	/** 添加当前Activity 到列表中 */
	public void addActivity(Activity acitivity) {
		if(activityList == null){
			activityList = new ArrayList<Activity>();
		}
		activityList.add(acitivity);
	}
	/** 清空列表,取消引用*/
	public void clearActivity(){
		activityList.clear();
	}

	/** 遍历退出所有Activity */
	public void exit() {
		for (Activity activity : activityList) {
			activity.finish();
		}
		clearActivity();//千万记得清空取消引用。
		System.exit(0);
	}
使用流程和方法2类似。

优缺点:

缺:如果处理不当,容易造成不在当前界面的Activity被全局引用而摧毁不掉,内存得不到释放,从而无故占用不必要的内存。


方案4

方法:使用广播机制解决

思路:通过Activity创建的时候,设置监听广播,在注册流程最后步完成注册时候,发送广播进行遍历finish().

描述:这里我把这些广播的初始化都写在了基类BaseActivity里面,便于维护。

代码:

	/**
	 * 初始化退出广播
	 */
	public void initFinishReceiver() {
		IntentFilter filter = new IntentFilter();
		filter.addAction(INIENT_FINISH);
		registerReceiver(mFinishReceiver, filter);
	}
	
	/**
	 * 监听是否退出的广播
	 */
	public BroadcastReceiver mFinishReceiver = new BroadcastReceiver() {

		@Override
		public void onReceive(Context context, Intent intent) {
			if (INIENT_FINISH.equals(intent.getAction())) {
				finish();
			}
		}
	};
在流程中的每步Activity中,初始化广播,之后在点击完成注册时候,发送广播
		btn_finish.setOnClickListener(new OnClickListener() {

			@Override
			public void onClick(View v) {
				// TODO Auto-generated method stub
				getApplicationContext().sendBroadcast(new Intent(INIENT_FINISH));
			}
		});
优缺点:

缺:开启过多的广播监听,觉得会浪费资源。


方案5:

方法:通过Activity跳转中传递requestCode的之后根据onActivityResult(int requestCode, int resultCode, Intent data)中返回的resultCode遍历关闭Activity

思路:使用startActivityForResult(intent, requestCode)方法跳转,并且通过

描述:这里我把这些广播的初始化都写在了基类BaseActivity里面便于查看。

代码:

/** 关闭时候的requestCode请求码 */
	public final static int FINISH_REQUESTCODE = 1;
	/** 关闭时候的resultCode请求码 */
	public final static int FINISH_RESULTCODE = 1;
	/**
	 * 方法5通过回调关闭的时候用到
	 */
	@Override
	protected void onActivityResult(int requestCode, int resultCode, Intent data) {
		// TODO Auto-generated method stub
		if(requestCode == FINISH_REQUESTCODE ){
			if(resultCode == FINISH_RESULTCODE){
				setResult(FINISH_RESULTCODE);
				finish();
			}
		}
		super.onActivityResult(requestCode, resultCode, data);
	}
之后在流程的Activity中调用带请求码的Intent跳转意图。

startActivityForResult(new Intent(getApplicationContext(), SecondStep.class),FINISH_REQUESTCODE);
在最后完成注册流程的时候通过以下方式返回:
	btn_finish.setOnClickListener(new OnClickListener() {

		@Override
		public void onClick(View v) {
			// TODO Auto-generated method stub
			setResult(FINISH_RESULTCODE);
			finish();
		}
	});

优缺点:

方案6(不推荐)

方法:方法有人说可以使用抛出异常来退出,可是这样会影响到用户体验,所以不推荐



总结

以上便是我从注册流程分析如何安全退出多个ACTIVITY 的汇总总结,综上所述,博主觉得方案1是最可行的方法,如有什么错误之处,望大家提出,马上改正。


源码DEMO



最后附上源码: 下载地址   

(以上方式中都已经讲到了对应的方法和代码,源码可以更好的帮助你去体验下这几种方式的使用流程)

作者:vipzjyno1 发表于2014-5-12 8:00:09 原文链接
阅读:7 评论:0 查看评论

HTML5如何用window.postMessage在网页间传递数据

$
0
0

估计很少人知道 HTML5 APIS里有一个window.postMessage API。 window.postMessage的功能是允许程序员跨域在两个窗口/frames间发送数据信息。基本上,它就像是跨域的AJAX,但不是浏览器跟服务器之间交互,而是在两个客户端之间通信。让我们来看一下 window.postMessage是如何工作的。除了IE6、IE7之外的所有浏览器都支持这个功能。

数据发送端

首先我们要做的是创建通信发起端,也就是数据源”source”。作为发起端,我们可以open一个新窗口,或创建一个iframe,往新窗口里发送数据,简单起见,我们每6秒钟发送一次,然后创建消息监听器,从目标窗口监听它反馈的信息。

//弹出一个新窗口
var domain = 'http://scriptandstyle.com';
var myPopup = window.open(domain 
            + '/windowPostMessageListener.html','myWindow');

//周期性的发送消息
setInterval(function(){
	var message = 'Hello!  The time is: ' + (new Date().getTime());
	console.log('blog.local:  sending message:  ' + message);
        //send the message and target URI
	myPopup.postMessage(message,domain);
},6000);

//监听消息反馈
window.addEventListener('message',function(event) {
	if(event.origin !== 'http://scriptandstyle.com') return;
	console.log('received response:  ',event.data);
},false);

这里我使用了 window.addEventListener,但在IE里这样是不行的,因为IE使用 window.attachEvent。如果你不想判断浏览器的类型,可以使用一些工具库,比如jQuery或Dojo。

假设你的窗口正常的弹出来了,我们发送一条消息——需要指定URI(必要的话需要指定协议、主机、端口号等),消息接收方必须在这个指定的URI上。如果目标窗口被替换了,消息将不会发出。

我们同时创建了一个事件监听器来接收反馈信息。有一点极其重要,你一定要验证消息的来源的URI!只有在目标方合法的情况才你才能处理它发来的消息。

如果是使用iframe,代码应该这样写:

//捕获iframe
var domain = 'http://scriptandstyle.com';
var iframe = document.getElementById('myIFrame').contentWindow;

//发送消息
setInterval(function(){
	var message = 'Hello!  The time is: ' + (new Date().getTime());
	console.log('blog.local:  sending message:  ' + message);
        //send the message and target URI
	iframe.postMessage(message,domain); 
},6000);

确保你使用的是iframe的 contentWindow属性,而不是节点对象。

数据接收端

下面我们要开发的是数据接收端的页面。接收方窗口里有一个事件监听器,监听“message”事件,一样,你也需要验证消息来源方的地址。消息可以来自任何地址,要确保处理的消息是来自一个可信的地址。

//响应事件
window.addEventListener('message',function(event) {
	if(event.origin !== 'http://davidwalsh.name') return;
	console.log('message received:  ' + event.data,event);
	event.source.postMessage('holla back youngin!',event.origin);
},false);

上面的代码片段是往消息源反馈信息,确认消息已经收到。下面是几个比较重要的事件属性:

  • source – 消息源,消息的发送窗口/iframe。
  • origin – 消息源的URI(可能包含协议、域名和端口),用来验证数据源。
  • data – 发送方发送给接收方的数据。

这三个属性是消息传输中必须用到的数据。

使用window.postMessage

跟其他很web技术一样,如果你不校验数据源的合法性,那使用这种技术将会变得很危险;你的应用的安全需要你对它负责。 window.postMessage就像是PHP相对于JavaScript技术。 window.postMessage很酷,不是吗?

原文来自: webhek

[Java][activiti]同步或者重构activiti identify用户数据的方法

$
0
0

同步或者重构Activiti Identify用户数据的多种方案比较

相信每个涉及到用户的系统都有一套用户权限管理平台或者模块,用来维护用户以及在系统内的功能、数据权限,我们使用的Activiti工作流引擎配套设计了包括 User、Group的Identify模块,怎么和业务数据同步呢,这个问题是每个新人必问的问题之一,下面介绍几种同步方案,最后总结比较。

如果你在考虑直接使用Activiti引擎的Identify模块作为系统的用户数据管理模块,您真是奇才~开个玩笑

方案一:调用IdentifyService接口完成同步

参考IdentifyService接口Javadoc: http://www.activiti.org/javadocs/org/activiti/engine/IdentityService.html

接口定义:

importjava.util.List;
importcom.foo.arch.entity.id.User;
importcom.foo.arch.service.ServiceException;
/**
 * 维护用户、角色、权限接口
 *
 * @author HenryYan
 *
 */
publicinterface AccountService {
 
    /**
     * 添加用户并[同步其他数据库]
     * <ul>
     * <li>step 1: 保存系统用户,同时设置和部门的关系</li>
     * <li>step 2: 同步用户信息到activiti的identity.User,同时设置角色</li>
     * </ul>
     *
     * @param user              用户对象
     * @param orgId             部门ID
     * @param roleIds           角色ID集合
     * @param synToActiviti     是否同步到Activiti数据库,通过配置文件方式设置,使用属性:account.user.add.syntoactiviti
     * @throws OrganizationNotFoundException    关联用户和部门的时候从数据库查询不到哦啊部门对象
     * @throws  Exception                       其他未知异常
     */
    publicvoid save(User user, Long orgId, List<long> roleIds, booleansynToActiviti)
            throws OrganizationNotFoundException, ServiceException, Exception;
    /**
     * 删除用户
     * @param userId        用户ID
     * @param synToActiviti     是否同步到Activiti数据库,通过配置文件方式设置,使用属性:account.user.add.syntoactiviti
     * @throws Exception
     */
    publicvoid delete(Long userId, booleansynToActiviti) throwsServiceException, Exception;
 
    /**
     * 同步用户、角色数据到工作流
     * @throws Exception
     */
    publicvoid synAllUserAndRoleToActiviti() throwsException;
 
    /**
     * 删除工作流引擎Activiti的用户、角色以及关系
     * @throws Exception
     */
    publicvoid deleteAllActivitiIdentifyData() throwsException;
}


同步单个接口实现片段:

@Service
@Transactional
publicclass AccountServiceImpl implementsAccountService { 
    /**
     * 保存用户信息,并且同步用户信息到activiti的identity.User和identify.Group
     * @param user              用户对象{@link User}
     * @param roleIds           用户拥有的角色ID集合
     * @param synToActiviti     是否同步数据到Activiti
     * @see Role
     */
    publicvoid saveUser(User user, List<long> roleIds, booleansynToActiviti) {
        String userId = ObjectUtils.toString(user.getId());
        // 保存系统用户
        accountManager.saveEntity(user);
 
        // 同步数据到Activiti Identify模块
        if(synToActiviti) {
            UserQuery userQuery = identityService.createUserQuery();
            List<org.activiti.engine.identity.user> activitiUsers = userQuery.userId(userId).list();
            if(activitiUsers.size() == 1) {
                updateActivitiData(user, roleIds, activitiUsers.get(0));
            }elseif (activitiUsers.size() > 1) {
                String errorMsg = "发现重复用户:id="+ userId;
                logger.error(errorMsg);
                thrownew RuntimeException(errorMsg);
            }else{
                newActivitiUser(user, roleIds);
            }
        }
    }
    /**
     * 添加工作流用户以及角色
     * @param user      用户对象{@link User}
     * @param roleIds   用户拥有的角色ID集合
     */
    privatevoid newActivitiUser(User user, List<long> roleIds) {
        String userId = user.getId().toString();
        // 添加用户
        saveActivitiUser(user);
 
        // 添加membership
        addMembershipToIdentify(roleIds, userId);
    }
 
    /**
     * 添加一个用户到Activiti {@link org.activiti.engine.identity.User}
     * @param user  用户对象, {@link User}
     */
    privatevoid saveActivitiUser(User user) {
        String userId = user.getId().toString();
        org.activiti.engine.identity.User activitiUser = identityService.newUser(userId);
        cloneAndSaveActivitiUser(user, activitiUser);
        logger.debug("add activiti user: {}", ToStringBuilder.reflectionToString(activitiUser));
    }
    /**
     * 添加Activiti Identify的用户于组关系
     * @param roleIds   角色ID集合
     * @param userId    用户ID
     */
    privatevoid addMembershipToIdentify(List<long> roleIds, String userId) {
        for(Long roleId : roleIds) {
            Role role = roleManager.getEntity(roleId);
            logger.debug("add role to activit: {}", role);
            identityService.createMembership(userId, role.getEnName());
        }
    }
    /**
     * 更新工作流用户以及角色
     * @param user          用户对象{@link User}
     * @param roleIds       用户拥有的角色ID集合
     * @param activitiUser  Activiti引擎的用户对象,{@link org.activiti.engine.identity.User}
     */
    privatevoid updateActivitiData(User user, List<long> roleIds, org.activiti.engine.identity.User activitiUser) {
        String userId = user.getId().toString();
        // 更新用户主体信息
        cloneAndSaveActivitiUser(user, activitiUser);
 
        // 删除用户的membership
        List<group> activitiGroups = identityService.createGroupQuery().groupMember(userId).list();
        for(Group group : activitiGroups) {
            logger.debug("delete group from activit: {}", ToStringBuilder.reflectionToString(group));
            identityService.deleteMembership(userId, group.getId());
        }
        // 添加membership
        addMembershipToIdentify(roleIds, userId);
    }
 
    /**
     * 使用系统用户对象属性设置到Activiti User对象中
     * @param user          系统用户对象
     * @param activitiUser  Activiti User
     */
    privatevoid cloneAndSaveActivitiUser(User user, org.activiti.engine.identity.User activitiUser) {
        activitiUser.setFirstName(user.getName());
        activitiUser.setLastName(StringUtils.EMPTY);
        activitiUser.setPassword(StringUtils.EMPTY);
        activitiUser.setEmail(user.getEmail());
        identityService.saveUser(activitiUser);
    }
 
    @Override
    publicvoid delete(Long userId, booleansynToActiviti, booleansynToChecking) throwsServiceException, Exception {
        // 查询需要删除的用户对象
        User user = accountManager.getEntity(userId);
        if(user == null) {
            thrownew ServiceException("删除用户时,找不到ID为"+ userId + "的用户");
        }
        /**
         * 同步删除Activiti User Group
         */
        if(synToActiviti) {
            // 同步删除Activiti User
            List<role> roleList = user.getRoleList();
            for(Role role : roleList) {
                identityService.deleteMembership(userId.toString(), role.getEnName());
            }
            // 同步删除Activiti User
            identityService.deleteUser(userId.toString());
        }
 
        // 删除本系统用户
        accountManager.deleteUser(userId);
 
        // 删除考勤机用户
        if(synToChecking) {
            checkingAccountManager.deleteEntity(userId);
        }
    }
}

同步全部数据接口实现片段:

同步全部数据步骤:

  • 删除Activiti的User、Group、Membership数据

  • 复制Role对象数据到Group

  • 复制用户数据以及Membership数据

ActivitiIdentifyCommonDao.java
publicclass ActivitiIdentifyCommonDao {
    protectedLogger logger = LoggerFactory.getLogger(getClass());
    @Autowired
    privateJdbcTemplate jdbcTemplate;
    /**
     * 删除用户和组的关系
     */
    publicvoid deleteAllUser() {
        String sql = "delete from ACT_ID_USER";
        jdbcTemplate.execute(sql);
        logger.debug("deleted from activiti user.");
    }
    /**
     * 删除用户和组的关系
     */
    publicvoid deleteAllRole() {
        String sql = "delete from ACT_ID_GROUP";
        jdbcTemplate.execute(sql);
        logger.debug("deleted from activiti group.");
    }
    /**
     * 删除用户和组的关系
     */
    publicvoid deleteAllMemerShip() {
        String sql = "delete from ACT_ID_MEMBERSHIP";
        jdbcTemplate.execute(sql);
        logger.debug("deleted from activiti membership.");
    }
}


ActivitiIdentifyService.java
publicclass ActivitiIdentifyService extendsAbstractBaseService {
    @Autowired
    protectedActivitiIdentifyCommonDao activitiIdentifyCommonDao;
    /**
     * 删除用户和组的关系
     */
    publicvoid deleteAllUser() {
        activitiIdentifyCommonDao.deleteAllUser();
    }
     
    /**
     * 删除用户和组的关系
     */
    publicvoid deleteAllRole() {
        activitiIdentifyCommonDao.deleteAllRole();
    }
     
    /**
     * 删除用户和组的关系
     */
    publicvoid deleteAllMemerShip() {
        activitiIdentifyCommonDao.deleteAllMemerShip();
    }
}


AccountServiceImpl.java
publicclass AccountServiceImpl implementsAccountService {
@Override
    publicvoid synAllUserAndRoleToActiviti() throwsException {
        // 清空工作流用户、角色以及关系
        deleteAllActivitiIdentifyData();
 
        // 复制角色数据
        synRoleToActiviti();
 
        // 复制用户以及关系数据
        synUserWithRoleToActiviti();
    }
 
    /**
     * 复制用户以及关系数据
     */
    privatevoid synUserWithRoleToActiviti() {
        List<user> allUser = accountManager.getAll();
        for(User user : allUser) {
            String userId = user.getId().toString();
            // 添加一个用户到Activiti
            saveActivitiUser(user);
 
            // 角色和用户的关系
            List<role> roleList = user.getRoleList();
            for(Role role : roleList) {
                identityService.createMembership(userId, role.getEnName());
                logger.debug("add membership {user: {}, role: {}}", userId, role.getEnName());
            }
        }
    }
    /**
     * 同步所有角色数据到{@link Group}
     */
    privatevoid synRoleToActiviti() {
        List<role> allRole = roleManager.getAll();
        for(Role role : allRole) {
            String groupId = role.getEnName().toString();
            Group group = identityService.newGroup(groupId);
            group.setName(role.getName());
            group.setType(role.getType());
            identityService.saveGroup(group);
        }
    }
    @Override
    publicvoid deleteAllActivitiIdentifyData() throwsException {
        activitiIdentifyService.deleteAllMemerShip();
        activitiIdentifyService.deleteAllRole();
        activitiIdentifyService.deleteAllUser();
    }
}


方案二:覆盖IdentifyService接口的实现

此方法覆盖 IdentifyService接口的默认实现类: org.activiti.engine.impl.IdentityServiceImpl

读者可以根据现有的用户管理接口实现覆盖 IdentityServiceImpl的每个方法的默认实现,这样就等于放弃使用系列表:ACT_ID_。

此方法不再提供代码,请读者自行根据现有接口逐一实现接口定义的功能。

方案三:用视图覆盖同名的ACT_ID_系列表

此方案和第二种类似,放弃使用系列表:ACT_ID_,创建同名的视图。

1.删除已创建的ACT_ID_*表

创建视图必须删除引擎自动创建的ACT_ID_*表,否则不能创建视图。

2.创建视图:

  • ACT_ID_GROUP
  • ACT_ID_INFO
  • ACT_ID_MEMBERSHIP
  • ACT_ID_USER

创建的视图要保证数据类型一致,例如用户的ACT_ID_MEMBERSHIP表的两个字段都是字符型,一般系统中都是用NUMBER作为用户、角色的主键类型,所以创建视图的时候要把数字类型转换为字符型。

3.修改引擎默认配置

在引擎配置中设置属性 dbIdentityUsedfalse即可。

<beanid="processEngineConfiguration"class="org.activiti.spring.SpringProcessEngineConfiguration">
    ...<propertyname="dbIdentityUsed"ref="false">
    ...</property></bean>

总结
  • 方案 :不破坏、不修改源码, 面向接口编程推荐

  • 方案 :放弃原有的Identify模块,使用自定义的实现,特殊情况可以使用此方式;

  • 方案 :不需要编写Java代码,只需要创建同名视图即可, 对于现有系统的集成、强烈推荐

作者:szwangdf 发表于2014-5-12 17:31:22 原文链接
阅读:97 评论:0 查看评论

Oracle PL/SQL 编程基础 实例 2

$
0
0
if  循环  控制语句 


if--then        endif


if----then ----else   endif


if-----then --elsif then ----else     endif


--编写一个过程,可以 输入一个雇员名,如果该雇员的工资低于2000就给他增加10%
    create   or replace procedure  sp_pro6(spName varchar2) is
       v_sal  emp.sal %type;
       begin  
          select sal into v_sal from emp where  ename =spName;
          --判断
          if v_sal<2000 then  
              update emp set sal=sal*1.1 where ename =spName;
           end if;
       end;
--======####案例s33 编写一个过程,可以 输入一个雇员名,如果该雇员的补助不是0就在原基础上增加100,如果是0就加200
    create   or replace procedure  sp_pro7(spName varchar2) is
       v_comm  emp.comm %type;
       begin  
          select comm into v_comm  from emp where  ename =spName;
          --判断
          if v_comm<>0 then  
              update emp set comm=comm+100 where ename =spName;
          else 
            update emp set comm=comm+200 where ename =spName;
           end if;
       end;








----========循环   loop    end loop
       -----案例  编写一个过程 可输入用户名 并添加10个用户到users表中  用户编号从1来时增加
       --建个表 
        create table users1(uid1 number,uname varchar2(40));
       
         create or replace procedure  sp_pro8(spName varchar2)  is
         --定义变量
           v_num number :=1;
           begin
             loop 
                 insert  into users1 values(v_num,spName);
                 --判断是否退出循环
                   exit  when  v_num =10;
                  --自增
                   v_num:=v_num+1;
             end loop; 
           end;
           
           
  ----------------===while   ...loop    end   loop 
  
  
      ----===案例   从11 开始 添加10个用户
      create or replace procedure  sp_pro8(spName varchar2)  is
         --定义变量
           v_num number :=11;
           begin
            while  v_num<=20
              loop 
                 --执行
                   insert  into users1 values(v_num,spName);
                  --自增
                   v_num:=v_num+1;
                end loop; 
           end;
 ---------------------for      
           begin  for i in  reverse 1.. 10 loop
             insert  into users1 values(v_num,spName);
             end loop;
             end;
            
 
 -----------------顺序控制语句 goto  null   
       goto  label 
       
       <<label>>  
        
      
-----=-----------返回结果集的过程----=======
 ---1.----创建 一个 包 在该包中 定义一个 类型 test_cursor  是个游标
      Create or replace package  testpackage as
        type  test_cursor is ref cursor;
       end  testpackage;
-----2.创建过程
       create  or  replace  procedure  sp_pro9 (spNO in number,p_cursor  out testpackage.test_cursor)
        is
         begin 
           open  p_cursor for select * from  emp where  deptno=spNO;
          end;
    
       
 
 -----3.如何在java中调用 
    ---1.创建 Callablestatement cs =ct.prepareCall([call sp_pro9(?,?)]);
    ---- cs.setInt(1,10);
    ----cs.registerOutParameter(2,oracle.jdbc.OracleTypes.CURSOR);
    --执行--cs.execute();
     --得到结果集    
         /*ResultSet rs=(ResultSet)cs.getObject(2);    
       
            while(rs.next()){
            ....
            }*/




---------------------例外处理---------
        case_not_found 
        data_not_found 
        cursor_already_open 
        dup_val_on_index  唯一索引重复
        invaild_cursor 在不合法的游标上执行操作 比如 从没有打开的游标取值 或关闭没有打开的游标
        invalid_number  
        too_many_rows  select  into 的时候 返回超过一行
        zero_divide   2/0
        value_error  变量长度不足以容纳实际长度 
        
          -----自定义例外
            create  or replace procedure ex_test(spNO number)
              is 
              --定义一个例外
              myex exception;
           begin 
              update  emp set sal=sal+1000 where empno=spNO;
              --sql%notfound  表示没有update
              --raise myex  触发myex
              if sql%notfound then
                raise myex;
              end if;
              exception 
                 when myex then 
               dbms_output.put_line('没有更新任何用户');
                 
           end;     
    
-----------------------------视图---------------
   --视图不能添加索引
   create view myview as select * from  emp where sal<1000;     
    select * from myview;  

作者:fangchao3652 发表于2014-5-12 17:28:26 原文链接
阅读:115 评论:0 查看评论

Oracle PL/SQL 编程基础 实例

$
0
0
create table mytest(name varchar(20),password varchar(30));


create or replace procedure sp_pro2 is 
begin 
insert into mytest values('fc','123');
end;




查看错误信息


show error


如何调用该过程:


1, exec 过程名 (参数,。。)
2.  call 过程名 (参数  )




set server output on


begin 
dbms_output.put_line('helloe');
end;


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




declare 
  v_ename varchar(20);
begin 
    select ename into v_ename from emp where empno=&aa;
  dbms_output.put_line('姓名:'||v_ename);
  end;






  -----------
  create procedure sp_pro3 (spName varchar2,newSal number) is
  begin  
                          --执行部分
     update emp set sal=newSal where ename=spName;
     end;
     --dioyong 
     call sp_pro3('scott','8000');
 --------------函数 -------
 
    CREATE or replace  function sp_fun1(spName varchar2) return 
    number is yearsal number(7,2);
     begin select sal*12+nvl(comm,0) into yearsal from emp 
       where ename=spName;
       return yearsal;
       end;
    
    --diaoyong
    var abc number;
    call sp_fun1('SCOTT') into : abc;
    ---------------------------
    
   -------包------------由包规范和包体组成的
   --创建一个包sap_pack1 
   --声明该包有一个过程和一个函数
   create or replace package sp_pack1 is
     procedure update_sal(spName varchar2,newsal number);
     function  annual_income(spName varchar2) return number;
     end;
     
     --创建包体---
         create or replace package body sp_pack1 is
          
         procedure update_sal(spName varchar2,newsal number) is
     begin  
     update emp set sal=newsal where ename=spName;
     end;
     
      function   annual_income(spName varchar2) return number
           is yearsal number(7,2);
     begin   select sal*12+nvl(comm,0) into yearsal from emp 
       where ename=spName;
       return yearsal;
    end;
    end;


 ---调用 ----
        call sp_pack1.update_sal('SCOTT',9000);










----------定义并使用变量---================================
     --  标量类型 scalar 
      v_ename varchar(10);
      v_sal  number(6,2):=5.4
      v_data date;
      v_valid  boolean not null default false;
                       ---输入员工号,显示 name sal tax
                       declare 
                         v_tax_rate number (3,2):=0.03;
                       -- v_ename varchar2(5);
                         v_ename  emp.ename%type;
                        -- v_sal  number(7,2);
                         v_sal  emp.sal%type;
                          v_tax_sal number (7,2);
                       begin  
                           select ename,sal into v_ename,v_sal from emp where empno=&no;
                           --计算所得税 
                             v_tax_sal:=v_sal*v_tax_rate;
                           --输出
                            dbms_output.put_line('name:'||v_ename||'工资'||v_sal  ||'交税:'||v_tax_sal);
                           end;


--复合类型  composite
      ----pl/sql 记录  类似于高级语言的结构体 
              --定义一个plsql 记录类型  emp_record_type
                  declare
                type   emp_record_type is record (aname emp.ename%type,salary emp.sal%type,title emp.job%type);
                --定义了一个 变量 sp_record  这个变量的类型是emp_record_type                                                                           
                  sp_record emp_record_type;
               begin 
                 select ename,sal,job into  sp_record from  emp where empno=7788;
               dbms_output.put_line('员工名:'||sp_record.aname);
               end;
      --------pl/sql  表  相当于该机语言里的数组 
            --定义一个plsql 表类型  emp_table_type   
                  declare 
                  type emp_table_type is table  of emp.ename%type index by  binary_integer;
                    sp_table emp_table_type;
                  begin 
                     select ename into  sp_table(0)  from emp where empno=7788;
                     dbms_output.put_line('员工名--》'||sp_table(0)); 
                  end;
--参照类型
        ---1游标变量
               --实例一  输入部门号,显示所有的员工姓名和他的工资
                       declare
                             --定义游标类型
                             type sp_emp_cursor is ref cursor;
                              --定义游标变量
                             test_cursor sp_emp_cursor; 
                             --定义变量
                             v_ename emp.ename%type;
                             v_sal emp.sal%type;
                         begin 
                              --执行
                              --把test_cursor 和select 结合
                            open test_cursor for select ename,sal  from emp where deptno=&depno;
                            --循环取出
                            loop
                              fetch test_cursor into v_ename,v_sal;
                              --判断是否test——cursor 为空
                               exit when  test_cursor%notfound;
                              dbms_output.put_line('员工名->'||v_ename||'工资->'||v_sal); 
                            end loop;
                         end;
作者:fangchao3652 发表于2014-5-12 17:27:25 原文链接
阅读:109 评论:0 查看评论

开源jms服务ActiveMQ的负载均衡+高可用部署方案探索

$
0
0

最近公司做项目需要用到jms消息服务,最终选择了apache的activemq这个开源消息总线,但是在activemq的官网没能找到既满足高可用又满足集群部署的方案,所以探索了其集群+高可用部署方案,经试用验证ok,这里和大家分享下。

一、架构和技术介绍

1、简介

ActiveMQ 是Apache出品,最流行的,能力强劲的开源消息总线。完全支持JMS1.1和J2EE 1.4规范的 JMS Provider实现

2、activemq的特性

1. 多种语言和协议编写客户端。语言: Java, C, C++, C#, Ruby, Perl, Python, PHP。应用协议: OpenWire,Stomp REST,WS Notification,XMPP,AMQP

2. 完全支持JMS1.1和J2EE 1.4规范 (持久化,XA消息,事务)

3. 对Spring的支持,ActiveMQ可以很容易内嵌到使用Spring的系统里面去,而且也支持Spring2.0的特性

4. 通过了常见J2EE服务器(如 Geronimo,JBoss 4, GlassFish,WebLogic)的测试,其中通过JCA 1.5 resourceadaptors的配置,可以让ActiveMQ可以自动的部署到任何兼容J2EE1.4商业服务器上

5. 支持多种传送协议:in-VM,TCP,SSL,NIO,UDP,JGroups,JXTA

6. 支持通过JDBC和journal提供高速的消息持久化

7. 从设计上保证了高性能的集群,客户端-服务器,点对点

8. 支持Ajax

9. 支持与Axis的整合

10. 可以很容易得调用内嵌JMS provider,进行测试

3、下载和安装ActiveMQ

1、下载

ActiveMQ的最新版本是5.10.0,但由于我们内网下载存在问题,所以目前通过内网只能下载到5.9.0,下载地址: http://activemq.apache.org/activemq-590-release.html

2、安装

如果是在windows系统中运行,可以直接解压apache-activemq-5.9.0-bin.zip,并运行bin目录下的activemq.bat文件,此时使用的是默认的服务端口:61616和默认的console端口:8161。

如果是在linux或unix下运行,在bin目录下执行命令:./activemq setup

3、修改ActiveMQ的服务端口和console端口

A、修改服务端口:打开conf/activemq.xml文件,修改以下红色字体部分

<transportConnectors>

<transportConnector name="openwire" uri="tcp://10.42.220.72:61618"discoveryUri="multicast://default"/>

</transportConnectors>

 

B、修改console的地址和端口:打开conf/jetty.xml文件,修改以下红色字体部分

<bean id="jettyPort"class="org.apache.activemq.web.WebConsolePort"init-method="start">

<property name="port" value="8162"/>

</bean>

4、通过客户端代码试用ActiveMQ

需要提前将activemq解压包中的lib目录下的相关包引入到工程中,再进行如下编码:

1、发送端的代码:

importjavax.jms.Connection;

importjavax.jms.ConnectionFactory;

importjavax.jms.DeliveryMode;

importjavax.jms.Destination;

importjavax.jms.MessageProducer;

importjavax.jms.Session;

importjavax.jms.TextMessage;

importorg.apache.activemq.ActiveMQConnection;

importorg.apache.activemq.ActiveMQConnectionFactory;

publicclass Sender {

privatestaticfinalintSEND_NUMBER = 5;

 

publicstaticvoid main(String[] args) {

// ConnectionFactory:连接工厂,JMS用它创建连接

ConnectionFactory connectionFactory;

// Connection:JMS客户端到JMS Provider的连接

Connection connection = null;

// Session:一个发送或接收消息的线程

Session session;

// Destination:消息的目的地;消息发送给谁.

Destination destination;

// MessageProducer:消息发送者

MessageProducer producer;

// TextMessage message;

//构造ConnectionFactory实例对象,此处采用ActiveMq的实现jar

connectionFactory = new ActiveMQConnectionFactory(

ActiveMQConnection. DEFAULT_USER,

ActiveMQConnection. DEFAULT_PASSWORD,

"failover:(tcp://10.42.220.72:61617,tcp://10.42.220.72:61618)");

try {

//构造从工厂得到连接对象

connection =connectionFactory.createConnection();

//启动

connection.start();

//获取操作连接

session = connection.createSession( true, Session. AUTO_ACKNOWLEDGE);

//获取session

destination = session.createQueue("FirstQueue");

//得到消息生成者【发送者】

producer =session.createProducer(destination);

//设置不持久化,此处学习,实际根据项目决定

producer.setDeliveryMode(DeliveryMode. NON_PERSISTENT);

//构造消息,此处写死,项目就是参数,或者方法获取

sendMessage(session, producer);

session.commit();

} catch (Exception e) {

e.printStackTrace();

} finally {

try {

if ( null != connection)

connection.close();

} catch (Throwable ignore) {

}

}

}

 

publicstaticvoid sendMessage(Session session,MessageProducer producer)

throws Exception {

for ( int i = 1; i <= SEND_NUMBER; i++) {

TextMessage message = session

.createTextMessage("ActiveMq发送的消息" + i);

//发送消息到目的地方

System. out.println("发送消息:" + "ActiveMq 发送的消息" + i);

producer.send(message);

}

}

}

 

2、接收端代码:

importjavax.jms.Connection;

importjavax.jms.ConnectionFactory;

importjavax.jms.Destination;

importjavax.jms.MessageConsumer;

importjavax.jms.Session;

importjavax.jms.TextMessage;

importorg.apache.activemq.ActiveMQConnection;

importorg.apache.activemq.ActiveMQConnectionFactory;

 

publicclass Receive {

publicstaticvoid main(String[] args) {

// ConnectionFactory:连接工厂,JMS用它创建连接

ConnectionFactory connectionFactory;

// Connection:JMS客户端到JMS Provider的连接

Connection connection = null;

// Session:一个发送或接收消息的线程

Session session;

// Destination:消息的目的地;消息发送给谁.

Destination destination;

//消费者,消息接收者

MessageConsumer consumer;

connectionFactory = new ActiveMQConnectionFactory(

ActiveMQConnection. DEFAULT_USER,

ActiveMQConnection. DEFAULT_PASSWORD,

"failover:(tcp://10.42.220.72:61617,tcp://10.42.220.72:61618)");

try {

//构造从工厂得到连接对象

connection =connectionFactory.createConnection();

//启动

connection.start();

//获取操作连接

session = connection.createSession( false,

Session. AUTO_ACKNOWLEDGE);

//获取session

destination = session.createQueue("FirstQueue");

consumer =session.createConsumer(destination);

while ( true) {

//设置接收者接收消息的时间,为了便于测试,这里谁定为100s

TextMessage message =(TextMessage) consumer.receive(100000);

if ( null != message) {

System. out.println("收到消息" + message.getText());

} else {

break;

}

}

} catch (Exception e) {

e.printStackTrace();

} finally {

try {

if ( null != connection)

connection.close();

} catch (Throwable ignore) {

}

}

}

}

3、通过监控查看消息堆栈的记录:

登陆 http://localhost:8162/admin/queues.jsp,默认的用户名和密码:admin/admin

二、ActiveMQ的多种部署方式

单点的ActiveMQ作为企业应用无法满足高可用和集群的需求,所以ActiveMQ提供了master-slave、broker cluster等多种部署方式,但通过分析多种部署方式之后我认为需要将两种部署方式相结合才能满足我们公司分布式和高可用的需求,所以后面就重点将解如何将两种部署方式相结合。

1、Master-Slave部署方式
1)shared filesystem Master-Slave部署方式

主要是通过共享存储目录来实现master和slave的热备,所有的ActiveMQ应用都在不断地获取共享目录的控制权,哪个应用抢到了控制权,它就成为master。

多个共享存储目录的应用,谁先启动,谁就可以最早取得共享目录的控制权成为master,其他的应用就只能作为slave。

2)shared database Master-Slave方式

与shared filesystem方式类似,只是共享的存储介质由文件系统改成了数据库而已。

 

3)Replicated LevelDB Store方式

这种主备方式是ActiveMQ5.9以后才新增的特性,使用ZooKeeper协调选择一个node作为master。被选择的master broker node开启并接受客户端连接。

其他node转入slave模式,连接master并同步他们的存储状态。slave不接受客户端连接。所有的存储操作都将被复制到连接至Master的slaves。

如果master死了,得到了最新更新的slave被允许成为master。fialed node能够重新加入到网络中并连接master进入slave mode。所有需要同步的disk的消息操作都将等待存储状态被复制到其他法定节点的操作完成才能完成。所以,如果你配置了replicas=3,那么法定大小是(3/2)+1=2. Master将会存储并更新然后等待 (2-1)=1个slave存储和更新完成,才汇报success。至于为什么是2-1,熟悉Zookeeper的应该知道,有一个node要作为观擦者存在。

单一个新的master被选中,你需要至少保障一个法定node在线以能够找到拥有最新状态的node。这个node将会成为新的master。因此,推荐运行至少3个replica nodes,以防止一个node失败了,服务中断。

2、Broker-Cluster部署方式

前面的Master-Slave的方式虽然能解决多服务热备的高可用问题,但无法解决负载均衡和分布式的问题。Broker-Cluster的部署方式就可以解决负载均衡的问题。

Broker-Cluster部署方式中,各个broker通过网络互相连接,并共享queue。当broker-A上面指定的queue-A中接收到一个message处于pending状态,而此时没有consumer连接broker-A时。如果cluster中的broker-B上面由一个consumer在消费queue-A的消息,那么broker-B会先通过内部网络获取到broker-A上面的message,并通知自己的consumer来消费。

1)static Broker-Cluster部署

在activemq.xml文件中静态指定Broker需要建立桥连接的其他Broker:

1、 首先在Broker-A节点中添加networkConnector节点:

<networkConnectors>

<networkConnector uri="static:(tcp:// 0.0.0.0:61617)"duplex="false"/>

</networkConnectors>

2、 修改Broker-A节点中的服务提供端口为61616:

<transportConnectors>

<transportConnectorname="openwire"uri="tcp://0.0.0.0:61616?maximumConnections=1000&amp;wireFormat.maxFrameSize=104857600"/>

</transportConnectors>

3、 在Broker-B节点中添加networkConnector节点:

<networkConnectors>

<networkConnector uri="static:(tcp:// 0.0.0.0:61616)"duplex="false"/>

</networkConnectors>

4、 修改Broker-A节点中的服务提供端口为61617:

<transportConnectors>

<transportConnectorname="openwire"uri="tcp://0.0.0.0:61617?maximumConnections=1000&amp;wireFormat.maxFrameSize=104857600"/>

</transportConnectors>

5、分别启动Broker-A和Broker-B。

2)Dynamic Broker-Cluster部署

在activemq.xml文件中不直接指定Broker需要建立桥连接的其他Broker,由activemq在启动后动态查找:

1、 首先在Broker-A节点中添加networkConnector节点:

<networkConnectors>

<networkConnectoruri="multicast://default"

dynamicOnly="true"

networkTTL="3"

prefetchSize="1"

decreaseNetworkConsumerPriority="true" />

</networkConnectors>

2、修改Broker-A节点中的服务提供端口为61616:

<transportConnectors>

<transportConnectorname="openwire"uri="tcp://0.0.0.0:61616? " discoveryUri="multicast://default"/>

</transportConnectors>

3、在Broker-B节点中添加networkConnector节点:

<networkConnectors>

<networkConnectoruri="multicast://default"

dynamicOnly="true"

networkTTL="3"

prefetchSize="1"

decreaseNetworkConsumerPriority="true" />

</networkConnectors>

4、修改Broker-B节点中的服务提供端口为61617:

<transportConnectors>

<transportConnectorname="openwire"uri="tcp://0.0.0.0:61617" discoveryUri="multicast://default"/>

</transportConnectors>

5、启动Broker-A和Broker-B

2、Master-Slave与Broker-Cluster相结合的部署方式

可以看到Master-Slave的部署方式虽然解决了高可用的问题,但不支持负载均衡,Broker-Cluster解决了负载均衡,但当其中一个Broker突然宕掉的话,那么存在于该Broker上处于Pending状态的message将会丢失,无法达到高可用的目的。

由于目前ActiveMQ官网上并没有一个明确的将两种部署方式相结合的部署方案,所以我尝试者把两者结合起来部署:

1、部署的配置修改

这里以Broker-A + Broker-B建立cluster,Broker-C作为Broker-B的slave为例:

1)首先在Broker-A节点中添加networkConnector节点:

<networkConnectors>

<networkConnector uri="masterslave:(tcp://0.0.0.0:61617,tcp:// 0.0.0.0:61618)" duplex="false"/>

</networkConnectors>

2)修改Broker-A节点中的服务提供端口为61616:

<transportConnectors>

<transportConnectorname="openwire"uri="tcp://0.0.0.0:61616?maximumConnections=1000&amp;wireFormat.maxFrameSize=104857600"/>

</transportConnectors>

3)在Broker-B节点中添加networkConnector节点:

<networkConnectors>

<networkConnector uri="static:(tcp:// 0.0.0.0:61616)"duplex="false"/>

</networkConnectors>

4)修改Broker-B节点中的服务提供端口为61617:

<transportConnectors>

<transportConnectorname="openwire"uri="tcp://0.0.0.0:61617?maximumConnections=1000&amp;wireFormat.maxFrameSize=104857600"/>

</transportConnectors>

5)修改Broker-B节点中的持久化方式:

<persistenceAdapter>

<kahaDB directory="/localhost/kahadb"/>

</persistenceAdapter>

6)在Broker-C节点中添加networkConnector节点:

<networkConnectors>

<networkConnector uri="static:(tcp:// 0.0.0.0:61616)"duplex="false"/>

</networkConnectors>

7)修改Broker-C节点中的服务提供端口为61618:

<transportConnectors>

<transportConnectorname="openwire"uri="tcp://0.0.0.0:61618?maximumConnections=1000&amp;wireFormat.maxFrameSize=104857600"/>

</transportConnectors>

8)修改Broker-B节点中的持久化方式:

<persistenceAdapter>

<kahaDB directory="/localhost/kahadb"/>

</persistenceAdapter>

9)分别启动broker-A、broker-B、broker-C,因为是broker-B先启动,所以“/localhost/kahadb”目录被lock住,broker-C将一直处于挂起状态,当人为停掉broker-B之后,broker-C将获取目录“/localhost/kahadb”的控制权,重新与broker-A组成cluster提供服务。



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


ITeye推荐



Viewing all 15843 articles
Browse latest View live


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