
搭建环境:redis-4.0.10.tar.gz安装包、Red Hat Enterprise Linux 7(64位) 1、将redis安装包上传至服务器并解压缩至/usr/local路径下; 2、顺序执行下面命令: mkdir /usr/local/redis-cluster(创建运行路径) cd redis-cluster/ mkdir -p 9001/data 9002/data 9003/data 9004/data 9005/data 9006/data(服务端口对应数据持久化存储路径) 3、顺序执行下面命令: mkdir redis-cluster/bin(创建集中执行文件路径) cd /usr/local/redis-4.0.10/src(进入执行wen文件的路径) cp mkreleasehdr.sh redis-benchmark redis-check-aof redis-cli redis-server redis-trib.rb /usr/local/redis-cluster/bin/.(把将用到的各个执行文件集中复制到执行路径下) 4、执行cp /usr/local/redis-4.0.10/* /usr/local/redis-cluster/9001,将redis整体复制到9001实例下; 5、编辑redis.conf文件如下: port 9001(每个节点的端口号) daemonize yes(设置成后台守护模式) bind 172.23.129.222(绑定当前机器 IP) dir /usr/local/redis-cluster/9001/data/(数据文件存放位置) pidfile /var/run/redis_9001.pid(pid 9001和port要对应) cluster-enabled yes(启动集群模式) cluster-config-file nodes9001.conf(9001和port要对应) cluster-node-timeout 15000 6、执行复制操作: cp -rf /usr/local/redis-cluster/9001/* /usr/local/redis-cluster/9002 cp -rf /usr/local/redis-cluster/9001/* /usr/local/redis-cluster/9003 cp -rf /usr/local/redis-cluster/9001/* /usr/local/redis-cluster/9004 cp -rf /usr/local/redis-cluster/9001/* /usr/local/redis-cluster/9005 cp -rf /usr/local/redis-cluster/9001/* /usr/local/redis-cluster/9006 7、顺序编辑900X下的每个redis.conf文件,将其中的9001替换成各自对应的900X; 8、启动各个节点: /usr/local/redis-cluster/bin/redis-server /usr/local/redis-cluster/9001/redis.conf /usr/local/redis-cluster/bin/redis-server /usr/local/redis-cluster/9002/redis.conf /usr/local/redis-cluster/bin/redis-server /usr/local/redis-cluster/9003/redis.conf /usr/local/redis-cluster/bin/redis-server /usr/local/redis-cluster/9004/redis.conf /usr/local/redis-cluster/bin/redis-server /usr/local/redis-cluster/9005/redis.conf /usr/local/redis-cluster/bin/redis-server /usr/local/redis-cluster/9006/redis.conf 9、查看启动状态: 10、查看ruby的版本,ruby --version,如果低于2.2执行下面的升级安装操作: 11、安装rvm: 12、执行source /usr/local/rvm/scripts/rvm使更新生效; 13、执行rvm list known命令后显示ruby的版本: # MRI Rubies [ruby-]1.8.6[-p420] [ruby-]1.8.7[-head] # security released on head [ruby-]1.9.1[-p431] [ruby-]1.9.2[-p330] [ruby-]1.9.3[-p551] [ruby-]2.0.0[-p648] [ruby-]2.1[.10] [ruby-]2.2[.10] [ruby-]2.3[.7] [ruby-]2.4[.4] [ruby-]2.5[.1] [ruby-]2.6[.0-preview2] ruby-head # for forks use: rvm install ruby-head-<name> --url https://github.com/github/ruby.git --branch 2.2 # JRuby jruby-1.6[.8] jruby-1.7[.27] jruby-9.1[.17.0] jruby[-9.2.0.0] jruby-head # Rubinius rbx-1[.4.3] rbx-2.3[.0] rbx-2.4[.1] rbx-2[.5.8] rbx-3[.100] rbx-head # TruffleRuby truffleruby[-1.0.0-rc2] # Opal opal # Minimalistic ruby implementation - ISO 30170:2012 mruby-1.0.0 mruby-1.1.0 mruby-1.2.0 mruby-1.3.0 mruby-1[.4.0] mruby[-head] # Ruby Enterprise Edition ree-1.8.6 ree[-1.8.7][-2012.02] # Topaz topaz # MagLev maglev-1.0.0 maglev-1.1[RC1] maglev[-1.2Alpha4] maglev-head # Mac OS X Snow Leopard Or Newer macruby-0.10 macruby-0.11 macruby[-0.12] macruby-nightly macruby-head # IronRuby ironruby[-1.1.3] ironruby-head 14、执行rvm install 2.5.1开始安装; 15、如果ruby版本没有问题的话就可以继续执行命令gem install redis创建redis的集群; 16、构建redis集群: /usr/local/redis-cluster/bin/redis-trib.rb create --replicas 1 172.23.129.222:9001 172.23.129.222:9002 172.23.129.222:9003 172.23.129.222:9004 172.23.129.222:9005 172.23.129.222:9006 replicas 1表示一个副本; 17、执行./redis-cli -c -h 172.23.129.222 -p 9001登录redis集群; 18、执行cluster info和cluster nodes查看集群的基本信息;
首先rpm -qa rpm-build看下是否已经安装了rpmbuild软件包,没有的话执行命令yum -y install rpm-build,然后mkdir -p ~/rpmbuild/{BUILD,RPMS,SOURCES,SPECS,SRPMS}创建工作路径。BUILD存放源代码并在该路径下完成编译工作;RPMS存放最终打包完成的二进制rpm包;SOURCES存放压缩的代码包由系统自动执行解压缩操作;SPECS存放spec扩展名的打包操作配置清单;SRPMS保存最终打包完成的源代码rpm包。 从https://gitee.com/gonglibin/GlbLib-1.0.0下载源代码包(Makefile需要剔除install相关操作),执行tar -czf GtLib-1.0.0.tar.gz GtLib-1.0.0/打成tar.gz格式包扔到SOURCES路径下。在SPECS目录下新建GtLib.spec文件,写入配置信息保存退出,执行rpmbuild -bb GtLib.spec开始打包操作。如果打包成功在RPMS目下会生成x86_64路径,x86_64是架构名称,不同的机器名称可能会不同。为了验证打包的正确性,可以执行yum install GtLib-1.0.0-1.x86_64.rpm命令对rpm包进行安装,执行yum remove GtLib-1.0.0-1.x86_64命令进行卸载,执行rsync -av GtLib-1.0.0-1.x86_64.rpm rpm@192.168.100.100::mylibrary命令进行发布。 GtLib.spec文件信息 ############################################################## # http://www.rpm.org/max-rpm/ch-rpm-inside.html # ############################################################## Name: GtLib # 软件包名称 Version: 1.4.0 # 关键包版本 Release: 1 # 发布序列号 Summary: Global library # 软件包概要 Group: gonglibin # 软件包分组 License: gonglibin # 软件包授权 URL: https://gitee.com/gonglibin/GlbLib-1.0.0 # 软件包主页 Source0: GtLib-1.4.0.tar.gz # 代码压缩包 %description -l zh_CN # 软件包描述 个人通用库纯属自娱自乐! %prep # 预处理脚本 %setup # 软件包解压 %build # 源代码编译 make clean make all %install # 构建安装路径 mkdir -p $PWD%{_prefix}/lib/GtLib mkdir -p $PWD%{_prefix}/include/GtInc cp -rf lib/* $PWD%{_prefix}/lib/GtLib/. cp -rf inc/* $PWD%{_prefix}/include/GtInc/. cp -rf usr %{buildroot}/. %post # 善后处理脚本 %files # 安装路径及文件 %{_prefix}/lib/GtLib %{_prefix}/include/GtInc %attr(755,root,root) %defattr(755,root,root) %changelog # 安装变更日志
STL中提供了若干容器,在gdb调试的时候,因为其内部结构和元素数据类型方面因素,查看可是费了劲,把下面这个脚本保存为用户根目录下.gdbinit,当gdb启动的时候自动去加载,打印容器变量的时候自动识别识别调用很方便。 未加载脚本: (gdb) p one_rg $5 = {gid = {static npos = 18446744073709551615, _M_dataplus = {<std::allocator<char>> = {<__gnu_cxx::new_allocator<char>> = {<No data fields>}, <No data fields>}, _M_p = 0x7c08c8 "rec"}}, smooth_enable = false, alloc_enable = false, mg_mc = {<std::_List_base<MGAndMCStruct, std::allocator<MGAndMCStruct> >> = { _M_impl = {<std::allocator<std::_List_node<MGAndMCStruct> >> = {<__gnu_cxx::new_allocator<std::_List_node<MGAndMCStruct> >> = {<No data fields>}, <No data fields>}, _M_node = {_M_next = 0x7c43c0, _M_prev = 0x7c4660}}}, <No data fields>}, d_mg = {static npos = 18446744073709551615, _M_dataplus = {<std::allocator<char>> = {<__gnu_cxx::new_allocator<char>> = {<No data fields>}, <No data fields>}, _M_p = 0x7c6708 "rs_action"}}, d_cc = { static npos = 18446744073709551615, _M_dataplus = {<std::allocator<char>> = {<__gnu_cxx::new_allocator<char>> = {<No data fields>}, <No data fields>}, _M_p = 0x7c6838 "ee_thompson"}}} 加载脚本以后: (gdb) p one_rg $1 = { gid = { static npos = 18446744073709551615, _M_dataplus = { <std::allocator<char>> = { <__gnu_cxx::new_allocator<char>> = {<No data fields>}, <No data fields>}, members of std::basic_string<char, std::char_traits<char>, std::allocator<char> >::_Alloc_hider: _M_p = 0x7bf108 "rec" } }, smooth_enable = false, alloc_enable = false, mg_mc = { <std::_List_base<MGAndMCStruct, std::allocator<MGAndMCStruct> >> = { _M_impl = { <std::allocator<std::_List_node<MGAndMCStruct> >> = { <__gnu_cxx::new_allocator<std::_List_node<MGAndMCStruct> >> = {<No data fields>}, <No data fields>}, members of std::_List_base<MGAndMCStruct, std::allocator<MGAndMCStruct> >::_List_impl: _M_node = { _M_next = 0x7c43c0, _M_prev = 0x7c4540 } } }, <No data fields>}, d_mg = { static npos = 18446744073709551615, _M_dataplus = { <std::allocator<char>> = { <__gnu_cxx::new_allocator<char>> = {<No data fields>}, <No data fields>}, members of std::basic_string<char, std::char_traits<char>, std::allocator<char> >::_Alloc_hider: _M_p = 0x7c6738 "rs_action" } }, d_cc = { static npos = 18446744073709551615, _M_dataplus = { <std::allocator<char>> = { <__gnu_cxx::new_allocator<char>> = {<No data fields>}, <No data fields>}, members of std::basic_string<char, std::char_traits<char>, std::allocator<char> >::_Alloc_hider: _M_p = 0x7c6868 "ee_thompson" } } } 脚本如下:# # STL GDB evaluators/views/utilities - 1.03 # # The new GDB commands: # are entirely non instrumental # do not depend on any "inline"(s) - e.g. size(), [], etc # are extremely tolerant to debugger settings # # This file should be "included" in .gdbinit as following: # source stl-views.gdb or just paste it into your .gdbinit file # # The following STL containers are currently supported: # # std::vector<T> -- via pvector command # std::list<T> -- via plist or plist_member command # std::map<T,T> -- via pmap or pmap_member command # std::multimap<T,T> -- via pmap or pmap_member command # std::set<T> -- via pset command # std::multiset<T> -- via pset command # std::deque<T> -- via pdequeue command # std::stack<T> -- via pstack command # std::queue<T> -- via pqueue command # std::priority_queue<T> -- via ppqueue command # std::bitset<n> -- via pbitset command # std::string -- via pstring command # std::widestring -- via pwstring command # # The end of this file contains (optional) C++ beautifiers # Make sure your debugger supports $argc # # Simple GDB Macros writen by Dan Marinescu (H-PhD) - License GPL # Inspired by intial work of Tom Malnar, # Tony Novac (PhD) / Cornell / Stanford, # Gilad Mishne (PhD) and Many Many Others. # Contact: dan_c_marinescu@yahoo.com (Subject: STL) # # Modified to work with g++ 4.3 by Anders Elton # Also added _member functions, that instead of printing the entire class in map, prints a member. # # std::vector<> # define pvector if $argc == 0 help pvector else set $size = $arg0._M_impl._M_finish - $arg0._M_impl._M_start set $capacity = $arg0._M_impl._M_end_of_storage - $arg0._M_impl._M_start set $size_max = $size - 1 end if $argc == 1 set $i = 0 while $i < $size printf "elem[%u]: ", $i p *($arg0._M_impl._M_start + $i) set $i++ end end if $argc == 2 set $idx = $arg1 if $idx < 0 || $idx > $size_max printf "idx1, idx2 are not in acceptable range: [0..%u].\n", $size_max else printf "elem[%u]: ", $idx p *($arg0._M_impl._M_start + $idx) end end if $argc == 3 set $start_idx = $arg1 set $stop_idx = $arg2 if $start_idx > $stop_idx set $tmp_idx = $start_idx set $start_idx = $stop_idx set $stop_idx = $tmp_idx end if $start_idx < 0 || $stop_idx < 0 || $start_idx > $size_max || $stop_idx > $size_max printf "idx1, idx2 are not in acceptable range: [0..%u].\n", $size_max else set $i = $start_idx while $i <= $stop_idx printf "elem[%u]: ", $i p *($arg0._M_impl._M_start + $i) set $i++ end end end if $argc > 0 printf "Vector size = %u\n", $size printf "Vector capacity = %u\n", $capacity printf "Element " whatis $arg0._M_impl._M_start end end document pvector Prints std::vector<T> information. Syntax: pvector <vector> <idx1> <idx2> Note: idx, idx1 and idx2 must be in acceptable range [0..<vector>.size()-1]. Examples: pvector v - Prints vector content, size, capacity and T typedef pvector v 0 - Prints element[idx] from vector pvector v 1 2 - Prints elements in range [idx1..idx2] from vector end # # std::list<> # define plist if $argc == 0 help plist else set $head = &$arg0._M_impl._M_node set $current = $arg0._M_impl._M_node._M_next set $size = 0 while $current != $head if $argc == 2 printf "elem[%u]: ", $size p *($arg1*)($current + 1) end if $argc == 3 if $size == $arg2 printf "elem[%u]: ", $size p *($arg1*)($current + 1) end end set $current = $current._M_next set $size++ end printf "List size = %u \n", $size if $argc == 1 printf "List " whatis $arg0 printf "Use plist <variable_name> <element_type> to see the elements in the list.\n" end end end document plist Prints std::list<T> information. Syntax: plist <list> <T> <idx>: Prints list size, if T defined all elements or just element at idx Examples: plist l - prints list size and definition plist l int - prints all elements and list size plist l int 2 - prints the third element in the list (if exists) and list size end define plist_member if $argc == 0 help plist_member else set $head = &$arg0._M_impl._M_node set $current = $arg0._M_impl._M_node._M_next set $size = 0 while $current != $head if $argc == 3 printf "elem[%u]: ", $size p (*($arg1*)($current + 1)).$arg2 end if $argc == 4 if $size == $arg3 printf "elem[%u]: ", $size p (*($arg1*)($current + 1)).$arg2 end end set $current = $current._M_next set $size++ end printf "List size = %u \n", $size if $argc == 1 printf "List " whatis $arg0 printf "Use plist_member <variable_name> <element_type> <member> to see the elements in the list.\n" end end end document plist_member Prints std::list<T> information. Syntax: plist <list> <T> <idx>: Prints list size, if T defined all elements or just element at idx Examples: plist_member l int member - prints all elements and list size plist_member l int member 2 - prints the third element in the list (if exists) and list size end # # std::map and std::multimap # define pmap if $argc == 0 help pmap else set $tree = $arg0 set $i = 0 set $node = $tree._M_t._M_impl._M_header._M_left set $end = $tree._M_t._M_impl._M_header set $tree_size = $tree._M_t._M_impl._M_node_count if $argc == 1 printf "Map " whatis $tree printf "Use pmap <variable_name> <left_element_type> <right_element_type> to see the elements in the map.\n" end if $argc == 3 while $i < $tree_size set $value = (void *)($node + 1) printf "elem[%u].left: ", $i p *($arg1*)$value set $value = $value + sizeof($arg1) printf "elem[%u].right: ", $i p *($arg2*)$value if $node._M_right != 0 set $node = $node._M_right while $node._M_left != 0 set $node = $node._M_left end else set $tmp_node = $node._M_parent while $node == $tmp_node._M_right set $node = $tmp_node set $tmp_node = $tmp_node._M_parent end if $node._M_right != $tmp_node set $node = $tmp_node end end set $i++ end end if $argc == 4 set $idx = $arg3 set $ElementsFound = 0 while $i < $tree_size set $value = (void *)($node + 1) if *($arg1*)$value == $idx printf "elem[%u].left: ", $i p *($arg1*)$value set $value = $value + sizeof($arg1) printf "elem[%u].right: ", $i p *($arg2*)$value set $ElementsFound++ end if $node._M_right != 0 set $node = $node._M_right while $node._M_left != 0 set $node = $node._M_left end else set $tmp_node = $node._M_parent while $node == $tmp_node._M_right set $node = $tmp_node set $tmp_node = $tmp_node._M_parent end if $node._M_right != $tmp_node set $node = $tmp_node end end set $i++ end printf "Number of elements found = %u\n", $ElementsFound end if $argc == 5 set $idx1 = $arg3 set $idx2 = $arg4 set $ElementsFound = 0 while $i < $tree_size set $value = (void *)($node + 1) set $valueLeft = *($arg1*)$value set $valueRight = *($arg2*)($value + sizeof($arg1)) if $valueLeft == $idx1 && $valueRight == $idx2 printf "elem[%u].left: ", $i p $valueLeft printf "elem[%u].right: ", $i p $valueRight set $ElementsFound++ end if $node._M_right != 0 set $node = $node._M_right while $node._M_left != 0 set $node = $node._M_left end else set $tmp_node = $node._M_parent while $node == $tmp_node._M_right set $node = $tmp_node set $tmp_node = $tmp_node._M_parent end if $node._M_right != $tmp_node set $node = $tmp_node end end set $i++ end printf "Number of elements found = %u\n", $ElementsFound end printf "Map size = %u\n", $tree_size end end document pmap Prints std::map<TLeft and TRight> or std::multimap<TLeft and TRight> information. Works for std::multimap as well. Syntax: pmap <map> <TtypeLeft> <TypeRight> <valLeft> <valRight>: Prints map size, if T defined all elements or just element(s) with val(s) Examples: pmap m - prints map size and definition pmap m int int - prints all elements and map size pmap m int int 20 - prints the element(s) with left-value = 20 (if any) and map size pmap m int int 20 200 - prints the element(s) with left-value = 20 and right-value = 200 (if any) and map size end define pmap_member if $argc == 0 help pmap_member else set $tree = $arg0 set $i = 0 set $node = $tree._M_t._M_impl._M_header._M_left set $end = $tree._M_t._M_impl._M_header set $tree_size = $tree._M_t._M_impl._M_node_count if $argc == 1 printf "Map " whatis $tree printf "Use pmap <variable_name> <left_element_type> <right_element_type> to see the elements in the map.\n" end if $argc == 5 while $i < $tree_size set $value = (void *)($node + 1) printf "elem[%u].left: ", $i p (*($arg1*)$value).$arg2 set $value = $value + sizeof($arg1) printf "elem[%u].right: ", $i p (*($arg3*)$value).$arg4 if $node._M_right != 0 set $node = $node._M_right while $node._M_left != 0 set $node = $node._M_left end else set $tmp_node = $node._M_parent while $node == $tmp_node._M_right set $node = $tmp_node set $tmp_node = $tmp_node._M_parent end if $node._M_right != $tmp_node set $node = $tmp_node end end set $i++ end end if $argc == 6 set $idx = $arg5 set $ElementsFound = 0 while $i < $tree_size set $value = (void *)($node + 1) if *($arg1*)$value == $idx printf "elem[%u].left: ", $i p (*($arg1*)$value).$arg2 set $value = $value + sizeof($arg1) printf "elem[%u].right: ", $i p (*($arg3*)$value).$arg4 set $ElementsFound++ end if $node._M_right != 0 set $node = $node._M_right while $node._M_left != 0 set $node = $node._M_left end else set $tmp_node = $node._M_parent while $node == $tmp_node._M_right set $node = $tmp_node set $tmp_node = $tmp_node._M_parent end if $node._M_right != $tmp_node set $node = $tmp_node end end set $i++ end printf "Number of elements found = %u\n", $ElementsFound end printf "Map size = %u\n", $tree_size end end document pmap_member Prints std::map<TLeft and TRight> or std::multimap<TLeft and TRight> information. Works for std::multimap as well. Syntax: pmap <map> <TtypeLeft> <TypeRight> <valLeft> <valRight>: Prints map size, if T defined all elements or just element(s) with val(s) Examples: pmap_member m class1 member1 class2 member2 - prints class1.member1 : class2.member2 pmap_member m class1 member1 class2 member2 lvalue - prints class1.member1 : class2.member2 where class1 == lvalue end # # std::set and std::multiset # define pset if $argc == 0 help pset else set $tree = $arg0 set $i = 0 set $node = $tree._M_t._M_impl._M_header._M_left set $end = $tree._M_t._M_impl._M_header set $tree_size = $tree._M_t._M_impl._M_node_count if $argc == 1 printf "Set " whatis $tree printf "Use pset <variable_name> <element_type> to see the elements in the set.\n" end if $argc == 2 while $i < $tree_size set $value = (void *)($node + 1) printf "elem[%u]: ", $i p *($arg1*)$value if $node._M_right != 0 set $node = $node._M_right while $node._M_left != 0 set $node = $node._M_left end else set $tmp_node = $node._M_parent while $node == $tmp_node._M_right set $node = $tmp_node set $tmp_node = $tmp_node._M_parent end if $node._M_right != $tmp_node set $node = $tmp_node end end set $i++ end end if $argc == 3 set $idx = $arg2 set $ElementsFound = 0 while $i < $tree_size set $value = (void *)($node + 1) if *($arg1*)$value == $idx printf "elem[%u]: ", $i p *($arg1*)$value set $ElementsFound++ end if $node._M_right != 0 set $node = $node._M_right while $node._M_left != 0 set $node = $node._M_left end else set $tmp_node = $node._M_parent while $node == $tmp_node._M_right set $node = $tmp_node set $tmp_node = $tmp_node._M_parent end if $node._M_right != $tmp_node set $node = $tmp_node end end set $i++ end printf "Number of elements found = %u\n", $ElementsFound end printf "Set size = %u\n", $tree_size end end document pset Prints std::set<T> or std::multiset<T> information. Works for std::multiset as well. Syntax: pset <set> <T> <val>: Prints set size, if T defined all elements or just element(s) having val Examples: pset s - prints set size and definition pset s int - prints all elements and the size of s pset s int 20 - prints the element(s) with value = 20 (if any) and the size of s end # # std::dequeue # define pdequeue if $argc == 0 help pdequeue else set $size = 0 set $start_cur = $arg0._M_impl._M_start._M_cur set $start_last = $arg0._M_impl._M_start._M_last set $start_stop = $start_last while $start_cur != $start_stop p *$start_cur set $start_cur++ set $size++ end set $finish_first = $arg0._M_impl._M_finish._M_first set $finish_cur = $arg0._M_impl._M_finish._M_cur set $finish_last = $arg0._M_impl._M_finish._M_last if $finish_cur < $finish_last set $finish_stop = $finish_cur else set $finish_stop = $finish_last end while $finish_first != $finish_stop p *$finish_first set $finish_first++ set $size++ end printf "Dequeue size = %u\n", $size end end document pdequeue Prints std::dequeue<T> information. Syntax: pdequeue <dequeue>: Prints dequeue size, if T defined all elements Deque elements are listed "left to right" (left-most stands for front and right-most stands for back) Example: pdequeue d - prints all elements and size of d end # # std::stack # define pstack if $argc == 0 help pstack else set $start_cur = $arg0.c._M_impl._M_start._M_cur set $finish_cur = $arg0.c._M_impl._M_finish._M_cur set $size = $finish_cur - $start_cur set $i = $size - 1 while $i >= 0 p *($start_cur + $i) set $i-- end printf "Stack size = %u\n", $size end end document pstack Prints std::stack<T> information. Syntax: pstack <stack>: Prints all elements and size of the stack Stack elements are listed "top to buttom" (top-most element is the first to come on pop) Example: pstack s - prints all elements and the size of s end # # std::queue # define pqueue if $argc == 0 help pqueue else set $start_cur = $arg0.c._M_impl._M_start._M_cur set $finish_cur = $arg0.c._M_impl._M_finish._M_cur set $size = $finish_cur - $start_cur set $i = 0 while $i < $size p *($start_cur + $i) set $i++ end printf "Queue size = %u\n", $size end end document pqueue Prints std::queue<T> information. Syntax: pqueue <queue>: Prints all elements and the size of the queue Queue elements are listed "top to bottom" (top-most element is the first to come on pop) Example: pqueue q - prints all elements and the size of q end # # std::priority_queue # define ppqueue if $argc == 0 help ppqueue else set $size = $arg0.c._M_impl._M_finish - $arg0.c._M_impl._M_start set $capacity = $arg0.c._M_impl._M_end_of_storage - $arg0.c._M_impl._M_start set $i = $size - 1 while $i >= 0 p *($arg0.c._M_impl._M_start + $i) set $i-- end printf "Priority queue size = %u\n", $size printf "Priority queue capacity = %u\n", $capacity end end document ppqueue Prints std::priority_queue<T> information. Syntax: ppqueue <priority_queue>: Prints all elements, size and capacity of the priority_queue Priority_queue elements are listed "top to buttom" (top-most element is the first to come on pop) Example: ppqueue pq - prints all elements, size and capacity of pq end # # std::bitset # define pbitset if $argc == 0 help pbitset else p /t $arg0._M_w end end document pbitset Prints std::bitset<n> information. Syntax: pbitset <bitset>: Prints all bits in bitset Example: pbitset b - prints all bits in b end # # std::string # define pstring if $argc == 0 help pstring else printf "String \t\t\t= \"%s\"\n", $arg0._M_data() printf "String size/length \t= %u\n", $arg0._M_rep()._M_length printf "String capacity \t= %u\n", $arg0._M_rep()._M_capacity printf "String ref-count \t= %d\n", $arg0._M_rep()._M_refcount end end document pstring Prints std::string information. Syntax: pstring <string> Example: pstring s - Prints content, size/length, capacity and ref-count of string s end # # std::wstring # define pwstring if $argc == 0 help pwstring else call printf("WString \t\t= \"%ls\"\n", $arg0._M_data()) printf "WString size/length \t= %u\n", $arg0._M_rep()._M_length printf "WString capacity \t= %u\n", $arg0._M_rep()._M_capacity printf "WString ref-count \t= %d\n", $arg0._M_rep()._M_refcount end end document pwstring Prints std::wstring information. Syntax: pwstring <wstring> Example: pwstring s - Prints content, size/length, capacity and ref-count of wstring s end # # C++ related beautifiers (optional) # set print pretty on set print object on set print static-members on set print vtbl on set print demangle on set demangle-style gnu-v3 set print sevenbit-strings off
IntelliJ IDEA是java语言的开发集成环境,在业界被公认为最好的java开发工具,没有之一,MyEclipse在它面前绝对是弱爆了,谁用谁知道,唯一的缺点是太占资源,8G内存是标配。衡量一个java工程师的技术水平只要问问他用什么开发工具就知道了,反正哥自己用的是MyEclipse 2015。 1、跳过测试编译:在窗体右侧Maven Projects窗口中点击Execute Maver Goal对话框中输入install或install -DskipTests忽略测试。 2、发布包并上传仓库:在pom.xml文件中修改工程版本号,执行deploy命令参数同上将发布的jar包自动上传至仓库。 3、位scala工程打包:点击菜单file选择Project Structure在选择Artifacts点击“+”符号进行增加操作,在输出方式中选择JAR -> From modules with dependencies ...方式,在Main Class中选择主类,点击OK确定,在菜单中Build中点击Build Artifacts在Action中选择具体的操作行为。 4、设置界面背景色:在setting中选择Appearance&Behavior中的Appearance,在Theme下拉列表中选择背景色。 5、设置快捷键风格:在setting中选择keymap,并在下拉列表中选择习惯的快捷键如MyEclipse风格的快捷键。 6、设置字体及字号:在setting中选择Editor,在Colors&Fonts中的Font右侧修改字体和字号。 7、显示代码行号:在setting中Editor中选择General,在下面Appearance右栏中找到Show line numbers,选中点击OK。 8、安装第三方插件:在setting中选择plugins,在右栏中查找需要安装的插件名称,执行安装操作。 9、kafka生产者写入数据提示:Failed to send messages after 3,打开server.properties配置文件,找到host.name配置,将localhost修改为本机IP地址,重启kafka服务。 10、Redis存储key为utf8时启动命令为:./redis-cli --raw 11、mongo数据库导入导出./mongodump -h 127.0.0.1:27017 -d 库名称 -o输出路径;./mongorestore -h 127.0.0.1:27017 备份路径 12、JAVA操作mysql中文乱码问题 a、/etc/my.conf文件[mysql]块中添加default-character-set=utf8保存后重启服务; b、通过jdbc方式连接库时加参数?useUnicode=true&characterEncoding=utf8; c、建库建表设置字符集为utf8,Coltation选择utf8_unicode_ci; 13、mysql远程登录本地命令行中文乱码问题,a、show variables like 'character_set_%'来查看当前字符集;b、set character_set_results='utf8'来设置字符集; 14、SSH隧道加代理访问问题 a、在xshell中SSH中Tunneling对话框中Type选择Dynamic的Sock4/5方式,默认端口及加入备注信息; b、在浏览器设置代理,选择“连接”标签,点击“局域网设置”,在代理服务器中点高级,在套接字中输入localhost端口1080后保存;
1、C++调用kafka的C语言动态库报错undefined reference:extern "C" {#include <rdkafka.h>}方式进行声明。 2、javac编译报错类can not be resolved to a type:在/etc/environment文件中添加CLASSPATH=.表示在当前目录下查找类文件。 3、javah生成jni头文件:javah 类名(不加class)声明头文件结果例如JNIEXPORT jint JNICALL Java_com_xxx_data_GtTool_JniGtFunction(int, jstring); 4、postgres数据库远程pgAdmin配置: a、编辑/var/lib/pgsql/data/pg_hda.conf文件在IPV4下添加host all all 0.0.0.0 0.0.0.0 md5 b、编辑同路径下postgresql.conf文件将tcpip_socket=off改为on c、创建用户su postgre后createuser -P用户名,设置密码后重启服务 5、磁盘阵列卸载与挂载: a、df -lh查看当前挂载信息 b、使用umount挂载路径指令完成卸载操作,如果提示忙致卸载失败,lsof挂载路径查看当前进程占用信息杀进程或fuser -k 挂载路径结束占用 c、使用service nfs restart重启服务 d、使用mount磁盘 挂载目录完成挂载操作 6、avro序列化C动态库安装: a、进入安装目录mkdir build创建目录后进入 b、cmake ..默认以上级路径进行编译操作 c、make执行编译,make test对测试用例进行编译 d、make install将结果安装到默认路径下 7、librdkafka动态C库安装: a、编辑Makefile文件删除16行-Werror参数 b、删除子目录examples的Makefile文件第四行-Werror参数 c、make;make install执行编译操作并将结果复制到默认路径下 8、libkafka动态C++库安装: a、执行./configurea--disable-gtest命令 b、打开./lib/src/Packet.cc文件275行第四个参数添加强制类型装换(size_t*) c、make;make install执行编译并将结果复制到默认路径下 9、vim编辑器增加插件: a、下载omnicppcomplete并在用户家路径下创建.vim隐藏目录并解压缩 b、在用户家路径下创建.ctags隐藏文件并输入配置信息 --c++-kinds=+p --fields=+iaS --extra=+q c、通过ctags -R 路径生成tags文件 d、在/etc/vimrc文件中增加相关配置 set nocp "omnicppcomplete filetype plugin on "omnicppcomplete set tags+=路径 "文件名 e、set completeopt=menuone,menu,longest用于控制是否在当前窗口上面显示相关信息 f、au CurSorMovedI,InsertLeave * if pumvisible()==o|silent! pclose|endif用于控制在输入完毕以后是否自动关闭窗口上方显示的相关信息 10、golang实现protobuf编译:protoc --go_out=. 文件名.proto,生成文件名.pb.go目标文件 11、mongodb数据库C语言开发包安装: a、执行./autogen.sh --with-libbson=bundled生成configure文件进行编译 b、报错m4_esyscmd_s等需要首先安装m4-1.4.17.tar.gz包,以及autoconf-2.69.tar.gz包 c、libbson和libmongoc文件默认保存在/usr/local/lib路径下 12、mongodb启动参数说明: a、--storegeEngine mmapvi指定存储引擎类型,系统默认wiredTiger值 b、--logpath参数指定日志存储路径 c、--fork参数指定是否将进程放置在后台以daemon方式运行 13、mongodb报错套接字错误:删除/data/db/mongod.lock文件 14、git服务器搭建: a、yum install git下载并安装若缺少组件可以git-all b、用户各自创建密钥在用户.ssh目录下存在id_rsa和id_rsa.pub文件,将pub公钥复制到/home/git/.ssh/authoried_keys文件中一个用户一行 c、进入/srv路径下创建空仓git init --bare 项目名.git d、chown -R git.git 项目.git修改权限 e、远程执行git clone git@192.168.0.100:/srv/项目.git拉取 15、gitweb服务搭建: a、yum install gitweb b、vim /etc/httpd/conf.d/git.conf第一行Alias /git /var/www/git中git改为gitweb c、vim /etc/gitweb/conf文件找到#our $projectroot行接触屏蔽并改值为/srv d、页面显示'\r',打开/var/www/git/gitweb.css文件加入块信息.cntrl{display:none;} e、重启httpd服务,无法访问尝试关闭防火墙 16、mysql服务搭建: a、执行rpm -qa | grep -i mysql查看安装情况 b、执行rpm -ivh mysql-community-release-el6-5.noarch.rpm c、执行yum -y install mysql-server后启动服务即可 17、elk5.1.1服务搭建: a、es启动报错max file descriptors[4096] ... 及max number of threads[1024] ...打开/etc/security/limits.conf文件添加soft nofiile 65536\n hard nofile 131072\n softnproc 2048和hard nproc 4096,打开/etc/security/limits.d/q0-nproc.conf文件soft nproc 1024改为soft nproc 2048,打开/etc/sysctl.conf文件添加vim.max_map_count=655360 b、打开es配置文件设置node.name=名称network.host本地地址http.port端口 c、logstash配置文件设置input和output块 d、kibana设置server.port、server.host和elasticsearch.url值 补充说明: e、以redis作为消息队列组件格式见logstash配置文件定义地址端口键数据类编码和标签等 f、es启动不能使用root用户,./bin/elasticsearch -d将进程放入后台,使用curl -X GET https://192.168.0.100:9200进行测试 g、在logstash配置文件中设置好input和output后./bin/logstash -f config/logstash.conf & h、编辑kibana配置文件后执行./bin/kibana -c config/kibana.yml &在5601端口接受服务 18、git给工程打版本tag: a、在含有git信息的工程代码路径下执行git tag 标签名称 b、执行git push origin --tags输入密码后提交 19、ssh-keygen命令:第一步确认公钥存放路径、第二步输入密码、第三步再次输入密码,公钥生成完毕 20、linux系统时间修改: a、date -s 2017-06-01 b、date -s 09:00:00 c、date -s "2017-06-01 09:00:00" 21、jsoncpp库安装: a、下载SCONS工具解压缩export MYSCONS=解压缩路径,设置export SCONS_LIB_DIR=$MYSCONS/engine b、下载jsoncpp包解压缩进入包目录,执行python $MYSCONS/script/scons platform=linux-gcc c、libs目录下/linux-gcc-4.4.7下包含静态库和动态库文件各一个,将文件复制到/usr/lib下,将include下文件复制到/usr/local/include下
1、C、C++挂载libxml.so动态库参数:`xml2-config --cflags --libs` 2、linux终端设置内核参数:sysctl -w kernel.shmmax=xxx; sysctl -w kernel.shmall=xxx; 3、sort排序: sort file1 > file2; 排重排序:sort -u file1 > file2; 提取重复留存一份:sort file1 | uniq -d > file2; 丢弃全部重复数据:sort file1 | uniq -u > file2; 4、linux终端设置coredump:ulimit -c unlimited,(进程中务必关闭信号处理) 5、java本地化:gcj --main=含有main方法类名称 file1.java file2.java filen.java 库文件 a、gcj -C file.java b、gcj -c file1.class 库.jar -o 目标文件.o c、gcj --main=类 -o 执行文件 目标文件.o 6、linux管理员账户密码修改:GRUB引导界面按e键选择一项,再按e键进入编辑状态后在行尾输入“/ single”,按b键引导成功后passwd root修改密码。 7、crontab编辑:crontab -e进入编辑状态,定时服务运行失败多是动态库路径加载失败导致,可在脚本中添加export LD_LIBRARY_PATH=/usr/local/lib之类语言。 8、查看进程动态库加载状态:ldd 执行文件,Not found表示库路径加载失败需要手动配置。 9、mysql提示host is not allowed to connect to this mysql server错误:对mysql进行授权操作GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY '123456' WITH GRANT OPTION; 10、linux设置字符集:locale -a,在.bash_profile文件中加入export LANG=en_US 11、linux修改消息队列内核参数: a、/proc/sys/kernel目录下msgmax定义单个消息最大值默认8192,msgmnb定义消息队列最大保存至默认16384,msgmni定义可创建消息队列总数。 b、echo 新值 > /proc/sys/kernel/文件名修改 c、编辑/etc/sysctl.conf文件添加kernel.msgmax=新值;kernel.msgmnb=新值重启系统 12、g++报错undefined reference to '-Unwind Resume':编译时添加参数-WL, -Bdynamic -lgcc_s 13、linux服务器代理设置: a、编辑/etc/squid/squid.conf文件http_port 3128为http_port 本机地址:3128 transparent b、找到visible_hostname行下增加visibel_hostname 本机地址 c、检查DNS设置/etc/resolv.conf文件nameserver DNS服务器地址 d、service squid start启动代理服务 14、gdb调试锁定线程:set scheduler-locking off|on|step 15、添加动态库路径: a、打开/etc/ld.so.conf文件或在/etc/ld.so.conf.d路径下创建conf扩展文件 b、写入动态库绝对路径如/usr/hadoop/c++/linux-i386 ... c、sudo /sbin/ldconfig -v 16、samba安装 a、yum install samba b、useradd 用户名 c、smbpasswd 密码或smbpasswd -a 用户名 d、service samba start 配置或关闭SElinux:setenforce 0 17、dmesg和addr2line定位异常: a、dmsg打印错误数据如GtDemo[48997] trap divide error ip:41cdf1 sp:7f0a80da4410 error:0 in GtDemo[4000000+2900] b、使用addr2line -e GtDemo 41cdf1打印报错文件行号 c、使用readelf -w GtDemo打印DWARF格式数据如special opcode 146:advance Address by 10 to 0x4004fe and line by 1 to 5 18、抓取VLAN报文分析:tcpdump -i eth0 vlan and dst port 80 -w 文件名,其中vlan可以是vlan100或vlan200等参数 19、vim编辑器golang语法高亮: a、确认go/misc路径下存在vim路径且存在go.vim文件 b、配置GOROOT环境变量并按照《go语言编程》第八章开发工具第三小节188页创建脚本 20、go语言编译方法: a、go run 文件名.go(直接运行) b、go build 文件名.go(生成可执行文件非main包不生成) c、go install 文件名.go(main包生成执行文件其余在pkg下生成静态文件) 21、gdb调试golang: a、go build -gcflags "-N -l "文件名.go进行编译关闭内联优化 b、gdb 可执行文件,开始调试gdb需7.1及以上版本,list需l main.main方式调用 22、mongodb启动:/usr/local/mongodb-3.2.8/bin/mongod --storageEngine mmapv1 --logpath /usr/local/mongodb-3.2.8/db.log --fork 23、hive建表与数据导入: a、hive --service cli与远程hiveserver连接 b、show databases查看数据库,show tables查看表 c、create table 表名 (字段名 类型, ...... )row format delimited fields terminated by '\t'; d、load data local input '/home/data.log' overwrite into table 表名; 24、myeclipse远程调试: a、远程服务启动附加参数-Xdebug -Xrunjdwp:transport=dt_socket,server=y,address=9999 b、参数后附加-cp 路径/工程名.jar等信息 c、开发环境中点击debug选择debug configurations选择myeclipse externally launched server左上角new launch configuration新建远程调试配置 d、connection properties中填入host和port连接信息 e、在source标签中add添加java project工程勾选后开始调试 25、kafka报错Failed to send messages after 3:打开server.properties文件,找到host.name配置将localhost修改为本机地址并重启kafka服务。 26、redis乱码:./redis-cli --raw 27、mongodb数据库备份: a、./mongodump -h 127.0.0.1:27017 -d库名称 -o输出路径 b、./mongorestore -好27.0.0.1:27017 -d库名称 备份路径 28、java操作mysql中文乱码: a、/etc/my.conf文件[mysqld]块中添加default-character-set=utf8保存重启 b、jdbc连接附加参数?useUnicode=true&characterEncoding=utf8 c、建库建表设置字符集为utf8,colltation选择utf8_unicode_c 29、mongodb创建索引: a、db.表名.ensureIndex({"字段名":1}),1代表升序,-1代表降序 b、db.表名.ensureIndex({"字段名":1},{unique:true}),不能插入唯一索引项上已经存在的记录 c、db.表名.ensureIndex({"字段名":1},{unique:true,dropDups:true}) d、db.表名.dropIndex({"字段名":1})删除已经创建的索引 30、mysql本地登录中文乱码: a、show variables like 'character_set_%';查看字符集 b、set character_set_results='utf8';设置字符集
Aerospike是一个以分布式为核心,T级别大数据高并发的……,参见《Aerospike入坑导读》https://yq.aliyun.com/articles/622455?spm=a2c4e.11155435.0.0.36e43312Nq4iTX,针对常用的一般性操作给出九个用例,代码采用GO语言实现,JAVA、C\C++以及其他语言的实现都差不多,同样具有参考意义,完整的代码请从https://gitee.com/gonglibin/codes/0hqjzto12argkmbsyv97n32下载。 func asInformation() { url := fmt.Sprintf("%s:%d", AS_HOST, AS_PORT) if con, err := as.NewConnection(url, time.Second * 10); err == nil { defer con.Close() if ifs, err := as.RequestInfo(con, ""); nil == err { for k, v := range ifs { /** * features: peers;cdt-list;cdt-map;cluster-stable;pipelining;geo ... * build: 4.2.0.5 * version: Aerospike Community Edition build 4.2.0.5 * build_os: el7 * statistics: cluster_size=1;cluster_key=E50AB9AFD60C;cluster_in ... * services-alumni: * services: * partition-generation: 0 * node: BB9822D9A270008 * edition: Aerospike Community Edition * build_time: Mon Jul 16 19:02:48 UTC 2018 */ fmt.Printf("%s: %s\n", k, v) } } } else { panic(err.Error()) } } 用例一:asInformation获取aerospike的基础信息,第64行调用RequestInfo函数,函数的第一个参数为aerospike的连接句柄,第二个参数为信息名称,空则返回全部信息,结果保存在map容器中。 func asMapToRecord(cli *as.Client) { arr := []byte{0x11, 0x21, 0x31} lst := []interface{}{111, "STRING", arr} mmp := map[interface{}] interface{} { "key1": 1, "key2": "A", "key3": arr, "key4": lst, } mybin := "mybin1" bin := as.NewBin(mybin, mmp) cli.PutBins(AS_WPLC, AS_NKEY, bin) if rst, err := cli.Get(AS_PLCY, AS_NKEY, bin.Name); nil == err { val := rst.Bins[bin.Name].(map[interface{}] interface{}) fmt.Printf("Intege: %d, String: %s, []Byte: %v, Map: %v\n", val["key1"], val["key2"], val["key3"], val["key4"]) } } 用例二:asMapToRecord以map数据结构作为bin存储的数据类型,93、94、95行构造了一个map实例,且每个节点的数据类型都不一样,分别为整形、字符串、字符数组和链表(其中链表中包含三个元素,分别为整形、字符串和字符数组),可见GO语言的map对数据类型的支持是多么的肆意妄为。查询的结果以键值对的方式返回,指定key从而读取value。 func asListToRecord(cli *as.Client) { mybin := "mybin2" bin := as.NewBin(mybin, []interface{}{111, "STRING_111", []byte{0x11, 0x21, 0x31}}) cli.PutBins(AS_WPLC, AS_NKEY, bin) if rst, err := cli.Get(AS_PLCY, AS_NKEY, bin.Name); nil == err { val := rst.Bins[bin.Name].([]interface{}) fmt.Printf("Intege: %d, String: %s, []Byte: %v\n", val[0], val[1], val[2]) } } 用例三:asListToRecord以链表数据结构作为bin存储的数据类型,第119行构造了一个链表实例,并附加bin名称定义了一个bin对象,链表结构共三个节点,分别存储整形、字符串和字符数组。查询的结果集以链表方式返回,可以直接通过下标得到数据项。 func asBatchToRecord(cli *as.Client) { bn := "mybin3" bk := []string{"b_key1", "b_key2", "b_key3", "b_key4"} bv := []string{"b_val1", "b_val2", "b_val3", "b_val4"} // 写入操作 for i, k := range bk { bin := as.NewBin(bn, bv[i]) key, _ := as.NewKey(AS_NMSP, AS_MYST, k) cli.PutBins(AS_WPLC, key, bin) } // 存在判断 ks := make([]*as.Key, len(bk) * 2) for i, k := range bk { ks[i], _ = as.NewKey(AS_NMSP, AS_MYST, k) } ks[4], _ = as.NewKey(AS_NMSP, AS_MYST, "b_key5") ks[5], _ = as.NewKey(AS_NMSP, AS_MYST, "b_key6") ks[6], _ = as.NewKey(AS_NMSP, AS_MYST, "b_key7") ks[7], _ = as.NewKey(AS_NMSP, AS_MYST, "b_key8") if ext, err := cli.BatchExists(nil, ks); nil == err { for i, e := range ext { fmt.Printf("ns = %s, set = %s, key = %s, exists = %t\n", ks[i].Namespace(), ks[i].SetName(), ks[i].Value(), e) } } else { panic(err.Error()) } // 批量获取 if rst, err := cli.BatchGet(nil, ks, bn); nil == err { for i, r := range rst { if nil != r { fmt.Printf("ns = %s, set = %s, key = %s, bin = %s, val = %s\n", ks[i].Namespace(), ks[i].SetName(), ks[i].Value(), bn, r.Bins[bn]) } else { fmt.Printf("ns = %s, set = %s, key = %s, bin = %s, val = %s\n", ks[i].Namespace(), ks[i].SetName(), ks[i].Value(), bn, "nil") } } } } 用例四:asBatchToRecord批量操作,140 - 144行写入四条记录,147行故意定义其两倍查询主键数组,其中前四个为刚才写入记录的主键,151 - 154行构造了四个不存在的主键,155行提请批量判断记录是否存在,156 - 158行打印结果至标准输出。164 - 172行采用两倍主键数组批量获取结果集,存在的结果打印,不存在的显示nil。 func asAppendToRecord(cli *as.Client) { mybin := "mybin4" bin := as.NewBin(mybin, "aero") cli.AppendBins(AS_WPLC, AS_NKEY, bin) asGetBinsAndPrint("AppendBins第一次调用", cli, AS_NKEY, bin) bin = as.NewBin(mybin, "spike") cli.AppendBins(AS_WPLC, AS_NKEY, bin) asGetBinsAndPrint("AppendBins第二次调用", cli, AS_NKEY, bin) } 用例五:asAppendToRecord从右侧向bin中追加数据,183 - 185行写入aero,187 - 189行写入spike,分两次写入完整aerospike数据。 func asAddBinsToRecord(cli *as.Client) { val := 8 mybin := "mybin5" bm := make(as.BinMap) bm[mybin] = val cli.Add(AS_WPLC, AS_NKEY, bm) asGetBinsAndPrint("BinMap 传入调用", cli, AS_NKEY, as.NewBin(mybin, val)) // NewBin 调用累加 bin := as.NewBin(mybin, val << 1) cli.AddBins(AS_WPLC, AS_NKEY, bin) asGetBinsAndPrint("NewBin 调用累加", cli, AS_NKEY, bin) // Operate调用累加 bin = as.NewBin(mybin, val << 2) cli.Operate(AS_WPLC, AS_NKEY, as.AddOp(bin), as.GetOp()) asGetBinsAndPrint("Operate调用累加", cli, AS_NKEY, bin) //上面一行与下面几行等价,Operate调用累加的同时返回结果。 //if rst, err := cli.Operate(AS_WPLC, AS_NKEY, as.AddOp(bin), as.GetOp()); nil == err { // fmt.Printf( // "Operate调用累加 ---> ns = %s, set = %s, AS_NKEY = %s, bin = %s, val = %d\n", // AS_NKEY.Namespace(), AS_NKEY.SetName(), AS_NKEY.Value(), bin.Name, rst.Bins[bin.Name]) //} } 用例六:asAddBinsToRecord对bin值进行累加,201 - 204行构造一个map,调用as的Add函数累加,207 - 209行调用as的AddBins函数累加,212 - 214行调用as的Operate函数累加,三种方式导致的结果一致,不同的只是入口参数不同。 func asPrependToRecord(cli *as.Client) { mybin := "mybin6" wds := []string{"工程师", "研发", "软件", "是", "我"} for i, w := range wds { bin := as.NewBin(mybin, w) cli.PrependBins(AS_WPLC, AS_NKEY, bin) asGetBinsAndPrint(strconv.Itoa(i + 1), cli, AS_NKEY, bin) } } 用例七:asPrependToRecord从左侧向bin中追加数据,为了演示效果特将一句中文分词后从右向左逐一写入bin,每写一次打印输出一次。 func asReplaceToRecord(cli *as.Client) { bn := []string{"b_name_1", "b_name_2", "b_name_3"} bv := []string{"b_value_1", "b_value_2", "b_value_3"} for i, b := range bv { bin := as.NewBin(bn[i], b) cli.PutBins(AS_WPLC, AS_NKEY, bin) } fmt.Printf("覆盖前 --- > ") if rst, err := cli.Get(AS_PLCY, AS_NKEY, bn[0], bn[1], bn[2]); nil == err { for k, v := range rst.Bins { fmt.Printf("{bin = %s, val = %s}, ", k, v) } fmt.Println() } wp := as.NewWritePolicy(0, 0) wp.RecordExistsAction = as.REPLACE nb := as.NewBin("b_name_replace", "b_value_replace") cli.PutBins(wp, AS_NKEY, nb) asGetBinsAndPrint("覆盖后", cli, AS_NKEY, nb) } 用例八:asReplaceToRecord对原有记录进行覆盖操作,需要特别注意的一点是PutBins时务必指定所有的bin,已存在的但未传入的bin系统将不会自动保存。248 - 259行写入三个bin并打印至标准输出,261 - 266行采用一个新的bin执行覆盖操作成功后,之前写入的三个bin被删除。 func asGetHeaderFromKey(cli *as.Client) { if rst, _ := cli.GetHeader(AS_PLCY, AS_NKEY); nil != rst { fmt.Printf("Key.generation = %d, Key.expiration = %d (%s)\n", rst.Generation, rst.Expiration, time.Duration(rst.Expiration) * time.Second) } else { fmt.Printf("Failed to GetHeader: namespace = %s, set = %s, key = %s\n", AS_NKEY.Namespace(), AS_NKEY.SetName(), AS_NKEY.Value()) } } 用例九:asGetHeaderFromKey查询一个已存在主键的被修改次数和生存期,一个key在没有被修改的情况下,系统默认生存期为720个小时(30天) Redis和Aerospike经常被拿来比较,至于用哪里好更多的时候还是要看需求,从总体上看,Aerospike比Redis对结构化数据支持的更好些,数据有效期、集群化管理、容灾容错方面也是各有所长,一定要找一个彼此不可替代的场景还真不好找。现在做个项目工程哪个不需要存点临时的中间数据给各个子系统读读写写的,多个备选方案总不是坏事。
腾讯广告实时交易平台在向竞价胜出一方返回成交价的时候,先对价格进行TEA加密,再对密文进行BASE64编码,接收方先对BASE64解码,再对密文解密,双方事先约定密钥。鹅厂官网提供了C#、C++、JAVA和PHP的解密代码包,无奈原有平台都是基于GO语言的,虽然可以调C++的静态库(libdecrypt.a),但开发工具是JetBrains GoLand,跑在Windows 7下后期难以调试,在虚拟机下跑linux版严重影响开发效率,不得不尝试改写为GO语言直接调用来的酣畅淋漓,主要原因还是太穷买不起MacBook。 原本觉得是个小活儿分分钟就可以搞定,没想到是个花了十二个工时的脏活累活,压根不是抄抄写写那么简单,一个坑接着一个坑。libdecrypt.a静态库在编译的时候没有加入调试信息完全无法跳入,jar倒是可以反编译看到源码,但编译器对部分中间变量做了优化处理,尤其对部分逻辑还原的带有强烈的个人感情色彩,IDEA和Java Decompiler两个工具自说自话,连被优化掉的变量命名都那么令人忍俊不禁。 // IntelliJ IDEA if(nInBufLen % 8 == 0 && nInBufLen >= 16 && pKey.length == 16) { // 省略 } // Java Decompiler if ((nInBufLen % 8 != 0) || (nInBufLen < 16) || (pKey.length != 16)) { return null; } // IntelliJ IDEA dest_buf[j] ^= pInBuf[var18 + j]; // Java Decompiler int tmp250_249 = j; byte[] tmp250_247 = dest_buf; tmp250_247[tmp250_249] = ((byte)(tmp250_247[tmp250_249] ^ pInBuf[(nBufPos + j)])); 调试的时候问题百出,只能左手跑JetBrains GoLand,右手跑IntelliJ IDEA,两边同时Step Out | Into跟踪,检查输入输出发现问题再解决问题,数次打算放弃名正言顺直接调用C++库,毕竟项目进度摆在那里男人何苦为难自己,不过坚持不懈是我唯一拿得出手值得炫耀的品格了,况且没准这个问题或许就是最后一个问题了呢。 罗里吧嗦说说遇到的坑吧! 坑一:GO语言做<<操作的时候高位溢出部分舍弃,C\C++和JAVA补1,需要对0xffffffff取反以后再做位置或操作; 坑二:GO语言“+”优先级高于“^”,在改写C\C++和JAVA表达式的时候需要加括号提升优先级,如:z -= int64(y << 4) + int64(c) ^ (y + sum ^ ((y >> 5) + int64(d))); 坑三:已知a := []int{1, 2, 3, 4, 5, 6}; b := a[:],二者指向同一内存空间,没有达到b = (int[])a.clone() 的目的; 坑四:GO语言没有“>>>”无符号右移运算符; 坑五:GO语言[]byte取值范围0-255,与JAVA的byte[]对应的是[]int8; 代码下载地址:https://gitee.com/gonglibin/codes/67lj5sv43bdegrm2ah81x21
Aerospike是一个以分布式为核心,T级别大数据高并发的结构化数据存储解决方案,读写操作达微妙级,索引内存化数据固态化,自动感知集群状态,节点间数据强一致性,平滑扩展以及丰富的开发语言支持,和redis相比aql介入的更有亲和力,对RDBMS的支持更好,熟悉sql的小哥哥小姐姐表示毫无压力,不过稳定性和普及率还有相当的差距,据说许多互联网广告相关业务都用它存储中间数据,难道我这些年混的都是假的互联网广告公司吗,有用的请举个爪儿。 Aerospike MySQL namespace db set table bin column key primary key record row 下载地址:https://www.aerospike.com/artifacts/aerospike-server-community/,解压执行asinstall,service aerospike [start|stop|status]管理服务,/etc/aerospike/aerospike.conf服务端配置文件中network块中heartbeat部分配置了集群的组播地址与端口,namespace块定义自己的业务命名空间;/etc/aerospike/astools.conf为aql端配置文件,其中aql块中timeout项被屏蔽默认1000毫秒,实操经验显示太短,建议改为2000以上; (解压缩) [root@localhost ~]# tar xvf aerospike-server-community-4.2.0.5-el7.tgz aerospike-server-community-4.2.0.5-el7/ aerospike-server-community-4.2.0.5-el7/SHA256SUMS aerospike-server-community-4.2.0.5-el7/aerospike-tools-3.15.3.8-1.el7.x86_64.rpm aerospike-server-community-4.2.0.5-el7/LICENSE aerospike-server-community-4.2.0.5-el7/asinstall aerospike-server-community-4.2.0.5-el7/aerospike-server-community-4.2.0.5-1.el7.x86_64.rpm (进入安装目录) [root@localhost ~]# cd aerospike-server-community-4.2.0.5-el7/ (安装) [root@localhost aerospike-server-community-4.2.0.5-el7]# ./asinstall Installing tools rpm -Uvh aerospike-tools-3.15.3.8-1.el7.x86_64.rpm 准备中... ################################# [100%] 软件包 aerospike-tools-3.15.3.8-1.el7.x86_64 已经安装 Installing server rpm -Uvh aerospike-server-community-4.2.0.5-1.el7.x86_64.rpm 准备中... ################################# [100%] 软件包 aerospike-server-community-4.2.0.5-1.el7.x86_64 已经安装 (启动服务) [root@localhost aerospike-server-community-4.2.0.5-el7]# service aerospike start /bin/mountpoint: /usr/local/lib/libuuid.so.1: no version information available (required by /lib64/libblkid.so.1) /bin/mountpoint: /usr/local/lib/libuuid.so.1: no version information available (required by /lib64/libblkid.so.1) Redirecting to /bin/systemctl start aerospike.service (查看服务状态) [root@localhost aerospike-server-community-4.2.0.5-el7]# service aerospike status /bin/mountpoint: /usr/local/lib/libuuid.so.1: no version information available (required by /lib64/libblkid.so.1) /bin/mountpoint: /usr/local/lib/libuuid.so.1: no version information available (required by /lib64/libblkid.so.1) Redirecting to /bin/systemctl status aerospike.service ● aerospike.service - Aerospike Server Loaded: loaded (/usr/lib/systemd/system/aerospike.service; disabled; vendor preset: disabled) Drop-In: /etc/systemd/system/aerospike.service.d └─aerospike.conf Active: active (running) since 一 2018-07-23 16:25:21 CST; 17s ago Process: 7452 ExecStartPre=/bin/systemctl start aerospike_telemetry (code=exited, status=0/SUCCESS) Process: 7444 ExecStartPre=/usr/bin/asd-systemd-helper (code=exited, status=0/SUCCESS) Main PID: 7455 (asd) CGroup: /system.slice/aerospike.service └─7455 /usr/bin/asd --config-file /etc/aerospike/aerospike.conf --fgdaemon 7月 23 16:25:31 localhost.localdomain asd[7455]: Jul 23 2018 08:25:31 GMT: INFO (info): (ticker.c:291) in-progress: tsvc-q 0 info-q 0 nsup-delete-q 0 rw-hash 0 proxy-hash 0 tree-gc-q 0 7月 23 16:25:31 localhost.localdomain asd[7455]: Jul 23 2018 08:25:31 GMT: INFO (info): (ticker.c:313) fds: proto (0,3,3) heartbeat (0,0,0) fabric (0,0,0) 7月 23 16:25:31 localhost.localdomain asd[7455]: Jul 23 2018 08:25:31 GMT: INFO (info): (ticker.c:322) heartbeat-received: self 67 foreign 0 7月 23 16:25:31 localhost.localdomain asd[7455]: Jul 23 2018 08:25:31 GMT: INFO (info): (ticker.c:353) fabric-bytes-per-second: bulk (0,0) ctrl (0,0) meta (0,0) rw (0,0) 7月 23 16:25:31 localhost.localdomain asd[7455]: Jul 23 2018 08:25:31 GMT: INFO (info): (ticker.c:408) {test} objects: all 0 master 0 prole 0 non-replica 0 7月 23 16:25:31 localhost.localdomain asd[7455]: Jul 23 2018 08:25:31 GMT: INFO (info): (ticker.c:469) {test} migrations: complete 7月 23 16:25:31 localhost.localdomain asd[7455]: Jul 23 2018 08:25:31 GMT: INFO (info): (ticker.c:488) {test} memory-usage: total-bytes 0 index-bytes 0 sindex-bytes 0 data-bytes 0 used-pct 0.00 7月 23 16:25:31 localhost.localdomain asd[7455]: Jul 23 2018 08:25:31 GMT: INFO (info): (ticker.c:408) {3gu} objects: all 0 master 0 prole 0 non-replica 0 7月 23 16:25:31 localhost.localdomain asd[7455]: Jul 23 2018 08:25:31 GMT: INFO (info): (ticker.c:469) {3gu} migrations: complete 7月 23 16:25:31 localhost.localdomain asd[7455]: Jul 23 2018 08:25:31 GMT: INFO (info): (ticker.c:488) {3gu} memory-usage: total-bytes 0 index-bytes 0 sindex-bytes 0 data-bytes 0 used-pct 0.00 (客户端接入) [root@localhost aerospike-server-community-4.2.0.5-el7]# aql Seed: 127.0.0.1 User: None Config File: /etc/aerospike/astools.conf /root/.aerospike/astools.conf Aerospike Query Client Version 3.15.3.8 C Client Version 4.3.12 Copyright 2012-2017 Aerospike. All rights reserved. aql> show namespaces +------------+ | namespaces | +------------+ | "test" | | "myns" | +------------+ [127.0.0.1:3000] 2 rows in set (0.003 secs) OK (查看接入配置) aql> get all ECHO = false VERBOSE = false OUTPUT = TABLE OUTPUT_TYPES = true TIMEOUT = 3000 LUA_USERPATH = /opt/aerospike/usr/udf/lua LUA_SYSPATH = /opt/aerospike/sys/udf/lua USE_SMD = false RECORD_TTL = 0 RECORD_PRINT_METADATA = false REPLICA_ANY = false KEY_SEND = false DURABLE_DELETE = false FAIL_ON_CLUSTER_CHANGE = true SCAN_PRIORITY = AUTO NO_BINS = false LINEARIZE_READ = false (执行插入操作) aql> insert into myns(pk,id,name,host,port,info) values('key1','001','AAA','192.168.0.1','10001','设备一') OK, 1 record affected. (执行查询操作) aql> select * from myns +-------+-------+---------------+---------+-------------+ | id | name | host | port | info | +-------+-------+---------------+---------+-------------+ | "001" | "AAA" | "192.168.0.1" | "10001" | "设备一" | +-------+-------+---------------+---------+-------------+ 1 row in set (1.311 secs) (执行删除操作) aql> delete from myns where pk='key1' OK, 1 record affected. (执行查询操作) aql> select * from myns 0 rows in set (1.269 secs) OK Aerospike从安装到启动到操作上面都逐一演示了一遍,下一篇文章打算选择一个开发语言来实现远程连接和执行上述操作,为使用Aerospike的研发小伙伴抛砖引玉。 前几天失联了,回来发现被预防针的新闻刷屏了,这群瘪犊子们该好好管管了,别让老百姓日子过得太糟心,哎~~~
一、总体描述 网站数据的抓取分析是当今众多互联网业务中非常重要的组成部分,舆情分析、网络搜索、定向广告等都涉及到大量的数据采集分析。一套优秀的系统应该满足高效爬取和精准分析的要求,而在海量的网站数据中,页面的布局和展示存在着巨大的差异。分别为每个网站制作一个页面解析模板不仅耗时耗力,且当网站页面改版以后之前的工作都将失去意义。在本文档中,力图从几个方向的叠加应用上寻找一个中间性的设计思路,用来寻找一种低成本消耗,而效果也可接受的解决方案。 二、网页布局 上图是某网站对2018年俄罗斯世界杯首场揭幕战的报道,从页面的布局来看,左侧红框部分为报道正文,黄框部分为广告位和导航栏,绿框部分为HTML代码对应的正文阴影区域。作为定向抓取的解决方案通行的做法是为这个布局风格定制一套样式模板,提取并保存文档标题与正文,其余部分(根据业务要求)则丢弃。 通过随机抽取若干有代表性的固网与移动端的主流媒体来看,大多数的页面布局均具备一定特征可循,正文在网页中通常以两种方式来展现,一种以标签的开闭区间静态值的方式来描述,另一种则是通过AJAX多次请求的方式懒加载。提取操作时可以通过一种或几种算法的叠加应用来获取绝大多数网页的正文信息,从业务应用的角度上看,错误率在可接受方位内,不会对产品和业务产生实质性影响。 三、分析方案 1、标签定位 标签是浏览器解析页面原始文件的重要依据,对于文章标题,正文,图片及表格都有着非常好的指示作用。比如采用title、h1、h2定位文章标题,采用p定位文章段落,采用a定位超链接,采用img定位引用图片等。标签定位的算法简单粗暴有效,但误判率比较高,在实际调用的时候应该辅助其他算法以提高准确性。 2、标签分布 标签分布是对标签定位单一算法的补充,标签定位虽然可以准确地找到资源项,但却不能判定资源的价值属性,而通过统计标签的分布密度可以一定程度的增加预测的准确性。相对于一篇规范性的文章,应该具备标题、正文、开头、描写和结尾等要素,标签的分布应具备集中密集的特征,因此同一类型的文本标签应在一定区间范围内连续出现或有规律性的间隔出现。但是,该算法对于超短正文的情况显然是无能为力,在实际使用阶段还应增加其他辅助手段。 3、文本判别 从标签开闭区间提取出来的文本串应首先判断其长度,无论是广告、资讯还是导航等信息,文字描述都是言简意赅,大段长篇的表述通常不会出现在这些区域位置中。其次再对文本串的结尾符进行判断,一篇规范的段落或语句应以标点符号结尾,而菜单、导航、分类、超链等信息均不会出现这种情况。显然单一的文本判别算法也存在极端情况的不确定性,必要时应辅助其他算法提升识别的准确率。 4、DOM规范 许多第三方开源的HTML解析库大多以HTML原始文件作为入参,以DOM树结构作为中间值,用户在定位资源的时候通过快速查询来找到想要的数据项。从DOM树状结构来看,各个节点之间存在父子或兄弟的对应关系,一个庞大的原始页面文件一旦找到标题与正文(红框部分)的节点,其余的噪音信息就非常容易去除了,某种程度上极大的缩小了文本处理范围,降低了计算压力和负载。 5、关联计算 上图是某网站科技频道对苹果手机的点评文章,左侧图文为文章的正文部分,右侧为推荐、广告与导航,从内容的关联性角度来看二者毫不相关。通过词向量技术计算文本之间的余弦相似度,可以剔除语义上完全无关的语言群落,仅保留近似语义文本,达到正文提取的目的。 6、信息摘除 当前绝大多数的网站都已经加入到网盟广告或将自己的剩余流量售卖给广告交易平台用于盈利和变现。无论是网盟还是ADX,对于互联网广告的曝光位置和约束都是开放的,掌握了这些标准和规范(广告交易平台的域名、广告位ID、广告位尺寸、广告创意URL及信息、曝光点击及监测地址等),在大多数长尾流量的页面中都可以轻松识别出来,从而摘除与文章正文无关的信息。 7、视觉定位 视觉定位技术是站在网页浏览者的角度全局的审视网页的页面布局,通过判断各个内容项占据背景的位置、颜色、尺寸、乃至于字体字号等细节,分析预测页面的核心描述信息。该算法相比前面提到的方法技术门槛较高,研发难度较大。 8、反向模板 反向模板技术是指经过上述一种或几种算法计算提取正文再得到验证以后,反向推导出该媒体下网页的标题与正文HTML的标签体系,并自动生成对照模板,以便未来根据模板解析页面,毕竟模板套取方式相比上述算法来的更加简单直接与高效。
互联网已经成为人们日常获取信息与沟通交流的重要方式,伴随用户规模的不断攀升每日的传播数据呈现爆发式增加。在这些海量数据中包括文本、图片、声音及视频多种格式,既有积极的也有消极的,甚至包括有悖伦理道德及违反法律法规的不良信息。为了创建一个良性的网络环境,有必要建立一套有效的机制,从海量信息中快速准确的识别出不良信息,切断其传播渠道,从而达到净化网络环境的目的。 敏感信息识别系统的设计应采用以机器为主导的,人工干预为辅助的处理机制,并随着算法的不断优化与数据模型的不断完善,逐步降低人工干预比例;系统设计应满足对“存量数据”和“增量数据”两种不同规模下,“全量扫描”与“抽样扫描”二种方式的支持;文本数据能够识别语言和字符集,图片、声音及视频等数据能够识别存储格式及编码类型;视频数据能够导出帧,通过逐帧或跳帧进行识别;系统应采用并行处理的计算机制,日处理能力不低于5TB;系统的识别算法应设计为立体多维度,降低因不合理的单一维度识别命中率而导致的偏差情况的发生,且算法模型应具备自我追加自我完善的动态机制。 敏感信息识别系统由建模层、识别层和存储层组成,实现从构建到应用再到持久化的完整流程。建模型的主要功能是提供建模所需要的各项基础要素,以机器自动化为主人工干预为辅,要素的质量直接关系到模型的品质与后期识别的正确率;识别层的任务是将模型放到业务中进行匹配操作,各个维度的加权计算可以有效纠正单一算法中加权因子缺失所导致的结果失真情况的发生,而模型自身是一个不断追加不算优化的过程,在业务应用服务中应采用预加载和空间换时间的思想来满足每日海量数据的计算需求;存储层存储元数据,敏感信息识别系统本身不应对存储层进行结构化修改,采用标注法对信息的敏感类型、敏感等级进行操作,对外提供人工复审接口,方便后期维护和效果检测。 功能描述: 1、建模层 建模层由机采(机器采集)和人采(人工采集)两部分构成,系统在交付早期运营阶段,可由人工分拣样本数据到训练库,并对样本进行敏感类型及等级标注,在模型初具规模以后可逐步降低人工干预强度,进入良性学习阶段,人工不定期抽检即可。建模算法可采用基于单层神经网络的机器学习建模开源项目,文本数据分词后附带敏感类型作为入参,图片(视频)数据以文件集合附带分类标签作为入参,(语音数据待考),训练结果以二进制文件形式作为模型保存至业务端,以备识别层启动加载。 人采模块应提供到识别层模块地址的操作接口,由人工提供批量关键字导入,其他模型的样本规模每个应不少于100条信息。由于人工操作的主观性比较强,同一样本在不同的人看来可能敏感类型和等级均有偏差,为了避免这种情况的发生,一条敏感信息应至少由二名以上人员进行标注并取均值,重大网监时期可启动审核操作,最大程度杜绝敏感信息给公司业务带来的不利影响。 算法模块覆盖上述机采和人采功能,机采算法需密切关注行业在神经网络和人工智能方面技术前沿企业开源的算法专利与第三方库,人采算法是对机采算法进行业务相关性的修正,对敏感分类和评级打分体系进行平滑过渡处理,可适当引入相似度计算、海明距离等扩展性算法,增加近似度和词法联想的自然语言处理,但有必要关注扩展性算法对精准度的不利因素,应在充分测试的基础上逐步推进模型的优化工作。此外在算法模块中还应定期更新物料库的IDF文本模型,这有利于根据社会热点准实时调整热词基准率,对关键词提取有着重要意义。 2、识别层 识别层以建模层的输出作为输入,在业务具体的识别阶段,对模型进行初始加载操作,模型包括“功能模块示意图”中的模型项,但不仅限于此,可根据业务需要动态增减模型项,并支持热插拔操作。针对每个敏感模型返回的命中评分系统应具备一个汇总算法,即每个分类自身权重乘以其匹配度(百分比)累加值取对数,其结果是一个介于零和一之间的浮点数,来作为敏感最终评估计算的修正值。 文本处理首先判断字符集和语言,并根据需要转换为内部存储所对应的字符集,采用分词系统对元数据进行分词,删除停用词以后提取当前文本的关键词(取tfidf前五名保存),当热点事件在网络中迅速传播的时候通过屏蔽策略可大幅度降低传播热度。 图片格式化操作用来对不同格式的图片文件进行转换,以便统一入参格式。图片敏感信息的识别可考虑图片文件名称和图像内容两方面因素,其中文件名是图像内容的补充,在识别率较低的时候对名称进行追加识别,其目的在于宁肯轻微错判也绝不漏判。 视频格式化操作对视频流文件导出为帧集合,可以采用逐一帧识别,更可采用一定比例的跳帧随机抽取样本帧,复用图像识别操作判断敏感类型和等级,对全部帧或部分帧的识别结果进行汇总算法操作,作为该视频的最终敏感分类和等级的评定终值。 音频格式化操作可将音频内容转换成文本以后复用文本处理流程来实现敏感分类和等级的评定。 3、存储层 存储层是所有元数据的持久化解决方案,确保数据的安全性、完整性和一致性,逻辑上分为物理存储和读写接口。基于网络数据特性可采用半结构化的分布式存储解决方案来保存高扩展性的网页内容,消息队列即可满足先进先出的特性又能实现消息的自由订阅。接口层可以定期读取增量数据到消息队列,并推送到识别层的各个业务节点,节点可根据自己的设备负载情况自由消费队列数据,并在处理完毕后立即回写到消息队列的返回“主题”中,即便接口层或物理存储层发生异常,数据也可保留一定时间,避免丢失造成的损失。 4、支撑层 支撑层包括业务监控、人机交互、服务托管、日志跟踪等诸多辅助工具,用于确保整个系统高效平稳地运行,并未后续的算法优化和采集重心提供策略和依据。 5、序列化 敏感信息识别系统是一个独立且相对封闭的集成环境,模块间数据传输可能存在跨设备跨网络的情况,为了保证数据的安全性和完整性,需要在生产端序列化,并在消费端反序列化,ProtoBuf和aveo均是理想的高效序列化工具可供采用。序列化信息应包含版本号、信息类型、操作类型、(加密标识和密钥)、数据长度、数据信息、识别结果等字段。 流程说明: 1、数据采集 机器采集列表保存在关系型数据库中,前端通过页面开放给系统运营人员定制,爬取操作启动时加载。若没有原始页面应用需求可在页面解析完毕以后将有效数据放入系统总线中供后续业务模块消费。人工采集可将页面中的有效数据复制到人机交互页面作为敏感信息样本,同时标注分类和等级,前后端通过用户权限认证接口打通直接完成底层操作,同时预留外部接口供其他系统调用。 2、识别操作 系统根据配置项加载部分或全部识别模型,线程池从消息队列逐一读取记录并执行反序列化操作,根据数据类型执行不同的处理流程,模型匹配完成后进行汇总计算,序列化后回写系统总线消息队列主题,并对当前执行过程记录日志用于离线的效果分析。 3、总线队列 系统总线启动时可创建生产工作线程和消费工作线程,生产工作线程定时跟踪底层存储增量数据的变化情况,当有数据到达的时候,将待消费数据从存储中提取出来放入消费主题;消费工作线程在入口挂起等待,有新消息的时候自动触发回写操作,更新底层的原址数据。 4、日志分析 日志分析系统以小时为单位(实时性要求较高的系统另议),采用批处理方式对日志数据进行分析并生成报表,统计得到数据规模、敏感信息比例、敏感信息强度、传播频次热度以及识别准确率等。 5、支撑系统 业务监控和服务托管可以采用众多成熟的第三方解决方案,方便运维人员实时关注服务器压力负载和资源占用情况,避免硬件和网络故障导致的服务异常情况。 6、对外接口 支撑系统预留对外操作接口为第三方提供敏感信息识别分析和统计服务,为将来业务拓展打下基础。
SSM(Spring + SpringMVC + MyBatis)三个开源框架的简称,是WEB项目开发的不二之选,是码农进阶全栈工程师心路历程上的驿站。大公司开源框架和算法制定标准规范引领行业趋势,小公司拿来主义面向业务敏捷开发快速迭代在夹缝中艰难生存。SSM框架完美的符合了当下的这种行业现状,对于底层是如何实现的开发人员完全不必关注,踏踏实实梳理好业务逻辑,做好质量把控年底KPI基本问题就不大了。 Spring是一个轻量级IoC及AOP容器框架,配置又多又细,SpringBoot简化了大量通用且不常用的配置项,使构建一个微服务变得超简单;SpringMVC是目前最优秀的MVC框架,注解用得好事半功倍;MyBatis用于数据持久化,MyBatisPlus增强版内置分页功能简化开发流程自动化代码生成,唯一要做的事情就剩下写写SQL了。 无私的分享从这里开始: git clone git@gitee.com:gonglibin/kirin.git 文件夹 PATH 列表 卷序列号为 0009-68A2 D:\WORKSPACES\KIRIN │ kirin.iml │ pom.xml │ ├─.idea │ │ .name │ │ compiler.xml │ │ encodings.xml │ │ misc.xml │ │ modules.xml │ │ uiDesigner.xml │ │ workspace.xml │ │ │ ├─copyright │ │ profiles_settings.xml │ │ │ ├─inspectionProfiles │ │ profiles_settings.xml │ │ Project_Default.xml │ │ │ └─libraries │ (略) │ └─src ├─main │ ├─java │ │ └─com │ │ └─kirin │ │ ├─api │ │ │ ├─controller │ │ │ │ KrnStatisticAdownerController.java │ │ │ │ │ │ │ ├─request │ │ │ └─response │ │ │ KrnResponse.java │ │ │ │ │ ├─dao │ │ │ ├─entity │ │ │ │ StatisticAdowner.java │ │ │ │ │ │ │ ├─impl │ │ │ │ KrnStatisticAdownerServiceImpl.java │ │ │ │ │ │ │ ├─mapper │ │ │ │ KrnStatisticAdownerMapper.java │ │ │ │ │ │ │ └─service │ │ │ KrnStatisticAdownerService.java │ │ │ │ │ └─web │ │ ├─server │ │ │ KrnApplication.java │ │ │ KrnInterceptor.java │ │ │ KrnMvcConfig.java │ │ │ │ │ └─tools │ │ KrnAutoMysql.java │ │ │ └─resources │ │ application.properties │ │ kirin.properties │ │ │ ├─mybatis │ │ mybatis-config.xml │ │ spring-jdbc.xml │ │ spring-mybatis.xml │ │ │ ├─spring │ │ spring-web-entry.xml │ │ │ └─xml │ StatisticAdowner.xml │ └─test └─java └─com └─kirin kirin/src/main/java/com/kirin/web/server/KrnApplication.java @SpringBootApplication @ImportResource("classpath:spring/spring-web-entry.xml") @EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class}) public class KrnApplication { private static Logger logger = LoggerFactory.getLogger(KrnApplication.class); public static void main(String[] args) { SpringApplication.run(KrnApplication.class, args); } } 1行@SpringBootApplication表示该对象为当前应用的启动类; 2行@ImportResource表示引入配置文件资源读取解析及加载; 8行启动,就这么简单~~~ kirin/src/main/java/com/kirin/web/server/ KrnMvcConfig.java @Configuration public class KrnMvcConfig extends WebMvcConfigurerAdapter { private static Logger logger = LoggerFactory.getLogger(KrnMvcConfig.class); @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { super.addResourceHandlers(registry); } @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new KrnInterceptor()).addPathPatterns("/**"); } } KrnMvcConfig类继承自WebMvcConfigurerAdapter抽象类(该类实现了WebMvcConfigurer接口方法为空并交给子类去实现); 1行@Configuration表示希望Spring将该类作为配置项资源; 6行实现静态资源处理; 11行向资源中添加拦截器; kirin/src/main/java/com/kirin/web/server/ KrnInterceptor.java public class KrnInterceptor implements HandlerInterceptor { /** * 该方法将在请求处理之前被调用,只有该方法返回true,才会继续 * 执行后续的Interceptor和Controller,当返回值为true时就会 * 继续调用下一个Interceptor的preHandle方法,如果已经是最后 * 一个Interceptor的时候就会是调用当前请求的Controller方法。 */ @Override public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception { boolean rst = true; String err = "{\"code\":500,\"message\":\"操作错误\",\"data\":null}"; if (true != httpServletRequest.getMethod().equals("GET")) { httpServletResponse.setCharacterEncoding("UTF-8"); httpServletResponse.setContentType("text/html; charset=utf-8"); try { PrintWriter writer = httpServletResponse.getWriter(); writer.print(err); writer.close(); } catch (Exception e) { e.printStackTrace(); } rst = false; } return rst; } /** * 该方法将在请求处理之后,DispatcherServlet进行视图 * 返回渲染之前进行调用,可以在这个方法中对Controller * 处理之后的ModelAndView 对象进行操作。 */ @Override public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception { } /** * 该方法也是需要当前对应的Interceptor的preHandle方法的返回值为true * 时才会执行,该方法将在整个请求结束之后,也就是在DispatcherServlet * 渲染了对应的视图之后执行。用于进行资源清理。 */ @Override public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception { } } KrnInterceptor类继承自HandlerInterceptor接口,接口定义了三个方法,分别对应在不同的阶段启动拦截事件,详情见注释,preHandle方法为演示,当HTTP请求不为GET时返回报错信息。 kirin/src/main/java/com/kirin/api/controller/KrnStatisticAdownerController.java @RestController @RequestMapping("/statisticadowner") public class KrnStatisticAdownerController { @Autowired KrnStatisticAdownerService krnStatisticAdownerService; @ResponseBody @RequestMapping("display") public Object getStatisticAdownerInfo() { return new KrnResponse<StatisticAdowner>(200, "操作成功", krnStatisticAdownerService.getStatisticAdownerInfo()); } } 1行@RestController (@ResponseBody + @Controller),表示将返回结果按照response的type直接写到HTTP的response body中去,且该类是一个控制器; 2行@RequestMapping表示将请求路径映射到的类上; 5行注入KrnStatisticAdownerService资源; 8行@RequestMapping表示将多级路径映射到该类的具体方法上; kirin/src/main/java/com/kirin/dao/entity/StatisticAdowner.java @TableName("t_statistic_adowner") public class StatisticAdowner implements Serializable { private static final long serialVersionUID = 1L; @TableId(value = "id", type = IdType.AUTO) private Integer id; /** * 展示量 */ @TableField("sad_viewAccount") private Integer sadViewaccount; // (省略) } 1行表示该类映射自哪张表,并将表结构映射成对象的成员变量; 6行@TableId为主键; 11行@TableField("sad_viewAccount")为表字段名; 需要说明的一点是数据表到类声明的自动化生成最好遵循一定的命名规则否则后果很严重; kirin/src/main/java/com/kirin/dao/service/KrnStatisticAdownerService.java 自定义KrnStatisticAdownerService接口继承自IService接口,其中定义了大量常用的增删改查的方法,基本涵盖了常用的数据库操作,自定义接口中的方法在控制器中被当作资源注入并调用。 kirin/src/main/java/com/kirin/dao/ impl/KrnStatisticAdownerServiceImpl.java @Service public class KrnStatisticAdownerServiceImpl extends ServiceImpl<KrnStatisticAdownerMapper, StatisticAdowner> implements KrnStatisticAdownerService { @Autowired KrnStatisticAdownerMapper krnStatisticAdownerMapper; @Override public StatisticAdowner getStatisticAdownerInfo() { return krnStatisticAdownerMapper.getStatisticAdownerInfo(); } } KrnStatisticAdownerServiceImpl类是KrnStatisticAdownerService接口的实现对象,它同时继承自ServiceImpl类,该类是IService接口中方法的具体实现。 4行注入KrnStatisticAdownerMapper资源; 7行覆盖KrnStatisticAdownerService接口中的方法; kirin/src/main/java/com/kirin/dao/ mapper/KrnStatisticAdownerMapper.java public interface KrnStatisticAdownerMapper extends BaseMapper<StatisticAdowner> { StatisticAdowner getStatisticAdownerInfo(); } 2行调用具体实现,建议在XML配置中书写SQL,好维护好管理逻辑清晰方便编辑修改; kirin/src/main/resources/xml/StatisticAdowner.xml <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.kirin.dao.mapper.KrnStatisticAdownerMapper"> <!-- 通用查询映射结果 --> <resultMap id="BaseResultMap" type="com.kirin.dao.entity.StatisticAdowner"> <id column="id" property="id" /> <result column="sad_viewAccount" property="sadViewaccount" /> <result column="sad_clickAccount" property="sadClickaccount" /> <result column="sad_costAmount" property="sadCostamount" /> <result column="sad_adOwnerId" property="sadAdownerid" /> <result column="sad_createTime" property="sadCreatetime" /> </resultMap> <!-- 通用查询结果列 --> <sql id="Base_Column_List"> id, sad_viewAccount AS sadViewaccount, sad_clickAccount AS sadClickaccount, sad_costAmount AS sadCostamount, sad_adOwnerId AS sadAdownerid, sad_createTime AS sadCreatetime </sql> <select id="getStatisticAdownerInfo" resultType="com.kirin.dao.entity.StatisticAdowner"> SELECT <include refid="Base_Column_List" /> FROM db_charm_app.t_statistic_adowner WHERE id = 1000123 </select> </mapper> select块部分是需要开发者实现的,把调试好的SQL复制到这里就OK了。 SSM框架设计的异常出色,使用起来异常便利,不过坑很多,踩一个少一个,都踩遍了工作起来就会变得很轻松,但是也会很乏味,剩下的事情全是面对不断的需求变更、业务调整和功能扩展,在日复一日的重复中慢慢老去~~~
从第一台计算机诞生到现在七十多年,计算机一直遵循着冯哥提出的以二进制为计算基础,以运算控制存储及输入输出五大组件构成的体系架构,硬件的发展从晶体管到中小规模集成电路再到大规模及超大规模集成电路,体积越来越小、能耗越来越低、功能越来越强、价格越来越便宜,便携化网络化和智能化将是未来的发展趋势。 个人对硬件的体验还停留在二十年前奔腾166跳线超频到200风冷不开盖不发烫不死机的美好回忆中(可惜主板不支持否则一定挑战一把233水冷)。和几十年不变的硬件体系相比软件工程思想却发生了巨大的变化,经历了面向过程(POP)到面向结构(SOP)到面向对象(OOP)到面向接口(IOP)到面向服务(SOA)再到面向切面(AOP)的演变,不过后者的出现并不是消灭和取代前者,江湖门派总爱争论哪种思想先进哪种语言优秀,其实每种编程思想和语言各有其适用的应用场景,各有其擅长的解决之道。 面向过程(POP):面向过程的编程思想是伴随着计算机诞生最早出现的,一句话总结就是先干啥后干啥然后干啥最后干啥,程序看起来就是一个数据加算法的集合,数据以变量的形式在算法之间被调度流转和加工,这种模式与人类的日常活动非常相似,所以很容易理解。 面向结构(SOP):面向结构的编程思想本质还是面向过程,只是更加注重逻辑的独立性,追求自上而下的模块化设计,部分地解决了标准化和规范化的问题。一个项目包含若干程序,程序包含若干文件,文件包含若干函数,函数包含若干语句,语句转换成若干条机器指令按顺序执行。当对数据复杂性和安全性有较高要求的时候,面向结构的编程思想就显得力不从心了。 面向对象(OOP):面向对象的编程思想是将面向过程编程思想中以开发者为主导地位变成以世界万物为主导地位,将事物的内部属性与外部环境隔离开,强调了事物之间的联系并赋予他们从属关系,界定对象的职责与能力范围。总结起来就是,你有我也有,你没有我还有 – 继承;花多少钱办多大事儿 – 重载;你行我比你还行 – 覆盖;谁能谁干 – 多态。面向对象编程思想如同侏罗纪时代的恐龙出现以后就迅速统治了地球,在下一次小行星撞上地球之前都不会消失。 面向接口(IOP):面向接口的编程思想和面向结构思想类似都是主流编程思想的一个分支体系,它的本质是只要开发者严格遵循接口的定义(规范与约束)就一定可以得到想要的一致性结果,它弱化了对象的属性和方法的概念,将对象理解成一个抽象体,根据需要调用或扩展接口功能来满足实际业务。 面向服务(SOA):面向服务的编程思想是在面向接口的编程思想上更进一步,两者粒度不同,前者是将一个整体功能封装起来,后者封装的则是一个对象。这种思想更多的还是体现在架构层面而非编码层面,多被当做中间件应用于系统间而非系统内调用。 面向切面编程(AOP),第一次听到切面这个名词感觉好奇怪,听说过挂面拉面刀削面,做法有炒的焖的打卤的炸酱的,难道软件行业都发展到必须边吃面边写代码的新高度了吗?后来一调查发现意思满拧,切面是剖面、创面和断面的意思,和吃没有半毛钱关系,记得小时候经常被老师夸奖:这孩子记吃不记打,一直引以为傲到现在。 其实切面这个词表达的还是挺生动形象的,操作手法与植物嫁接类似,在枝干上人工打开一个深度创面,让受体和配体之间紧密贴合,利用细胞增生的原理使二者创口慢慢愈合,达到投之以桃报之以李的目的。 面向切面的编程思想追求的最高境界是以最小的侵入代价换取最大的自主功能实现,为了达到这个目标,引入了几个全新的概念:反射、拦截、责任链、动态代理和控制反转,讲清楚每个概念都够写本书了,理解是一回事用好是另一回事,这里面坑多了,填一个少一个,早日填平好迎接新技术的到来。打算过几天结合一个基础框架分享一下面向切面的编程思想,也算给自己一点点激励。 曾经有许多种编程思想摆在我的面前,但是我都不太明白,等到面试的时候才后悔莫及,尘世间最痛苦的事情莫过于此。如果可以给我一个机会再来一次的话,我会说我要努力学习,如果有人问我未来的编程思想是什么,我希望是面向钞票!
C#与JAVA二者是对标下的产物,无论设计思想还是语法格式都非常相似,相恨相杀一路走来各自拥有一群拥趸,思想理念都传承自面向对象的C++,不过要说血缘关系还是C#更近一些,保留了比较多的C++的影子让人多了几分熟悉的味道,IDE上VS也比MyEclipse用户体验好很多,不用idea去比明显偏心眼VS,好吧你说对了。 C#和C++都提供了良好完备的线程间同步机制,C#保留了更多的C++烙印,JAVA则干练的多让开发者省心不少。过去在面向过程的编程思路上,锁的操作基本都是基于语句的,锁的范围从加锁开始到解锁终止,代码编写的过程中各种小心锁区间的逻辑处理,一个异常就可能导致万劫不复的死锁。C#和JAVA把更多面向底层的锁操作封装起来,通过赋予对象实例一个修饰词的方法,极大的简化了步骤,当然也可以对语句块加锁,省略了显式的繁琐操作。 C# JAVA C++ 获取 Monitor.Enter(object); Monitor.TryEnter(object); 休眠 Monitor.Wait(object); final void wait(); pthread_cond_wait(_cond, _mutex); pthread_cond_timewait(_cond, _mutex, _abstime); 唤醒 Monitor.Pulse(object); Monitor.PulseAll(object); final void notify(); final void notifyAll(); pthread_cond_signal(_cond); pthread_cond_broadcast (_cond); 释放 Monitor.Exit(object); 上表显示了C#和JAVA语言基于监视器的同步方法的函数,C#比JAVA多了对监视器的获取和释放操作,二者都提供休眠等待时间,唤醒操作都提供单播和多播两种方式,在明确被唤醒对象且预知其执行方法及结果的前提下建议单播方式唤醒,否则多播唤醒后再逐个投入睡眠导致的惊群上下文切换会造成比较大的系统开销。需要特别注意的地方是按照甲骨文的推荐把wait()方法放到一个循环中可以有效避免假唤醒(spurious wakeup)情况的发生,虽然Oralce强调这种情况发生的概率极小,其实意思是说:我可告诉你,不听是你的事情,出了问题别找我。表中用C++做陪衬说明是为了更好的比较这种机制的适用场景,那就是并不是简单的粗暴加锁解锁,而是有条件有尺度的以最小开销获取独占操作。 public void CSharpFunc(int v) { Monitor.Enter(this); if (flag) { try { Monitor.Wait(this); } catch(SynchironizationLockException e) { Console.WriteLine(e); } } Console.Write("Value {0}", v); flag = true; Monitor.Pulse(this); Monitor.Exit(this); } synchronized void JavaFunc(int v) { while (!flag) try { wait(); } catch(InterruptedException e) { System.out.println("InterruptedException caught"); } System.out.println("Valuse: " + v); flag = false; notify(); } 前几天出门体验了一次7号共享电单车,以为无桩的可以为所欲为骑行,结果冲了押金又充值,扫码开锁竟然要指定目的地,因为目的地周边没有停车场,只好骑到地铁站再换乘,这叫啥事呀,账上趴着充值剩余的金额,提不出来也不知道下次啥时候消费,有种上当受骗的感觉,有桩的共享出行交通工具都是反人类设计,改改吧。
C面向过程、golang半对象化、C#、C++和java都是纯纯的面向对象的计算机开发语言,纯到main都静态在类里,纯到没有全局变量的概念,纯到方法不能脱离类独立存在,面向对象的编程思想是计算机发展历史上的一次革命与突破,与面向过程的编程思想相比无所谓孰优孰劣,每种语言都有自己的亮点和槽点,结合几种主流语言聊聊它们和C#的一些逗比差异。 1、判断表达式:在if、while、do ... while、for语句中C认为0为false其他为true,C++为了向下兼容作了隐式转换,C#和java中一定要用布尔表达式,把非零值当做真有时候可能只是一厢情愿。 2、分支语句:C#要求每个分支必须break或者goto,其他语言中那种自动向下遍历分支的功能在C#中被终结,使用中需要特别注意。 3、foreach语言:C完全不支持,C# 用法foreach(类型 变量名 in 表达式),java用法foreach(类型 变量名 : 表达式),go用法for _, v := range x {fmt.Println(v)},C#和java的区别在于in和冒号,go的循环语句最强大,匿名变量自动指向下标。 4、函数返回值,go语言支持多个返回值见下面用例,C、C++、C#、java均不支持,golang轻松完胜! type GoDate struct { year int mon T.Month day int } func (d *GoDate) GoDateNow() { d.year, d.mon, d.day = T.Now().UTC().Date() } 5、ref、out和params:这几个关键字是C#独有的,以ref声明的变量以地址方式传递,作用在之上的操作结果都会直接反映到调用一方,以out声明的变量明确表示需要从中看到返回结果,根本目的还是解决单一返回值的限制,params本质是一个object数组类型的list,各种语言都有各自变通的解决方式。 6、unsafe关键字:C#独有用于指定一条或者一块非安全的上下文,超赞C#的这点设计,使得对指针的应用可以得以延续,在做java开发的时候时常让人怀念指针真是个好东西,golang中保留了指针概念甚是令人欣慰。 7、fixed关键字:C#独有用在非安全上下文中,功能类似于const类型常指针但更强大,保持指向不变的前提下防CLR回收。 8、类型修饰符:只要是面向对象必然躲不开修饰符,修饰符即包括修饰成员变量和成员函数,还包括修饰继承关系。公有、私有和保护的就不说了,说说C#中几个有特点的,internal表示当前项目内部可访问,readonly表示成员变量为只读,sealed等同于C++和java中的final防继承。 9、继承关系:除了C++以外越来越多面向对象的开发语言选择单一继承(接口允许多重继承),在继承的同时忽略继承修饰符,java用法:public class B extends A,C#用法:public class B : A,C++用法class B : public A,其中public也可以是私有和保护类型,和多重继承一样这些复杂的概念广被吐槽,这就好比臭豆腐喜欢的人甘之若饴讨厌的人避之不及。 10、变量类型:比较喜欢强类型的开发语言,在使用变量前显式声明变量类型,许多脚本语言为了方便开发者var了事,golang也用var自动推导变量类型,但本质还是强类型。在基本数据类型中C、C++、C#和golang都提供8位16位32位64位有无符号类型变量,而java不区分符号,这样做真的好吗? 11、拆装箱操作:面向对象的编程语言基本都提供装箱(boxing)和拆箱(unboxing)操作,有的地方称为加框操作和消框操作好有喜感的翻译,把基本数据类型封装到对应的数据对象中,从而通过调用对象方法实现对基本数据类型的操作。 12、属性概念:不清楚在主流开发语言中这个概念算不算是C#独有的,感觉这个设计思想夹杂着浓重的视窗气息(一张视窗对象的继承关系图谱可以让人静静的看上一整年),C#对象中的属性包含对象的成员变量但不仅仅限于此,声明和用法也极有特色,属性在对象中并不占用空间,属性这个名词完美的解释了它的作用,写属性set自动隐含value参数,害的第一次接触属性概念的人满世界找value变量从哪里蹦出来的,调用的时候格式更像公有的成员变量而不是成员方法,不加括号显得光秃秃的有没有。 13、静态修饰符:在C和C++中static全局变量表示该变量的作用域在当前文件内,在函数中表示函数返回不释放变量存储空间并保存其当前值,对于纯面向对象的开发语言中因为没有全局的概念,静态修饰符则用来将变量放在静态数据区,表示该变量在项目中的单一有效性。 14、索引器:indexer算是C#中特有的概念吧,声明与属性类似,名称叫this并用中括号做定界符,定义完成以后就可以像操作数组一样操作对象了,真霸气! 15、字符集:C#和java都把unicode作为默认的字符集,对于涉及中文的开发确实方便太多了,用C处理ANSI以外的编码时各种乱码确实让人头疼。 16、数组的定义和使用:C#对数组的声明有两种方式,以二维数组为例int[][]和int[,],前者是一个n*m但m不定长的二维交错数组,后者是n*m的定长二维数组,这和其他主流开发语言不太一样,虽然各有各的变通方式,但都不如C#看上去清新靓丽。 17、结构体:C#保留了C和C++语言中的结构体数据类型,给人多了一份亲切感,而对于一个含有n个元素的数组,存储结构体远比存储对象要经济实惠的多,尤其是C#把结构体当做值类型看待分配的是栈空间,而对象被当做引用类型看待分配的是堆空间,唯一的缺点是传参值复制开销略微大点,个人建议java可以借鉴一下下啦,嘻嘻~ 18、同步机制:无论是C#还是java都把同步机制的处理极大的简化了相比C和C++而言,在面向过程的编程实现中,同步机制都是作用于函数的,对锁的操作小心了再小心,一不留神处理不当死锁是稀疏平常的事情,面向对象的编程实现上对锁的处理最明显的变化就是加在对象上,锁的粒度和边界不像面向过程时那么费心了,更多的精力可以放在逻辑组织和算法实现上了。 19、异常处理:程序错误有编译错误、运行错误和逻辑错误,一个工程中很大一部分的代码都是用于对运行时错误和异常进行处理,try-catch的好处是可以批量捕获异常并根据异常类型实现不同的操作,也可以throw给上层处理,还可以finally一些释放资源的操作,书写看起来美观多了,也不用担心异常终止资源占用的问题了,这方面C有点老迈需要年轻化。 20、对象继承指代:在当前类中,C++和java用super指代父类用this指代自己,C#用base指代父类用this指代自己,C和golang只想做好自己。 粗略大致列举了二十条吧,对比了C、C++、java和golang几种语言从设计到语法上的异同,不是想表达哪个语言好哪个语言烂,更不是为了证明某个语言开发者高大上某个语言开发者穷矮矬,不必对号入座。有人的地方就有江湖,有江湖的地方就有门派,有门派的地方就有一招一式,每个人可以表达好恶,但标准不一定是唯一的,各路江湖豪杰华山论剑切磋技艺相互学习相互汲取取长补短,其实各种编程语言的设计思想大致都是相同的,不同之处在于语法、书写和侧重解决的问题,励斌出品必是精品!
C#和JAVA有许多相似的地方,设计思想差不多,语法及其相像,均传承自面向对象设计思想,灵感来自C++并取其精华去其“糟粕(二字持保留意见)”,中间语言、解释执行、一次编译、到处执行,出身豪门算得上是表兄弟关系,各自拥有庞大的拥趸,两种语言在发展的道路上你追我赶相互借鉴相互学习相互渗透,至于谁的IDE更强大对于一个用了二十年vim编辑器的人来说实在无从评判,强大到让人内牛满面~ C#语言中关于事件(event)结合代理(delegate)实现对象状态变更时的通知机制,总感觉这种处理有点过于复杂化了,但既然人家这么设计必定有人家的道理,相信并向人家学习而不急于批判和否定才能让自己进步的更快,个人觉得这种处理大概是来自于视窗系统独有的对各个控件事件集中快速响应的机制吧,这可能也是从事前后端开发关注点的差异,前端重人机交互当然交互的核心就是不确定时间属性和状态的事件,后端重触发每个事件的发生基本都是预定义且流程化构建好的,所以接下来尝试理解和解读一下C#的事件。 第一步:声明一个代理,这个代理可以是系统的也可以是自定义的。 public delegate void MyDelegate(); // 声明无参无返回值代理 public delegate bool MyDelegate(int k, int v); // 声明有参有返回值代理 第二步:创建一个包含该代理事件的对象,对象中调用代理实现事件的处理。 public class MyArrayList : ArrayList { public event MyDelegate MyChanged; // 声明代理事件 public override void Add(object o) // 覆盖父类方法 { base.Add(o); // 调用父类方法 OnChanged(); // 调用事件函数 } protected virtual void OnChanged() { if (null != MyChanged) MyChanged(); // 代理触发事件 } } 第三步:创建一个类,将事件和代理绑定到一起,a、类构造时以包含代理事件对象作为入参,b、“+=”运算符实现绑定,c、在代理中传入类成员函数。 public class MyEvent { private MyArrayList list; public MyEvent(MyArrayList l) { list = l; list.MyChanged += new MyDelegate(ListChanged); // 绑定事件 } private void ListChanged() // 被绑定事件 { System.Console.WriteLine("ListChanged ..."); } } 第四步:创建含有事件的类的实例,创建含有方法的类的实例。 public class MyTest { public static void Main() { MyEvent me = new MyEvent(new MyArrarList()); me.Add("object_1"); } } 仔细研究发现,代理相当于C\C++中的函数指针,但功能更强大,使用更安全,代理实例在创建的时候,代理会把传给它的参数传给绑定的方法,而且代理可以通过“+=”运算符搭载更多的方法,下面是对比C\C++函数指针的用法。 char* (*pFun)(char*) = NULL; pFun = GtCodeUtf8ToGB2312; char* pszData = (*pFun)("计算机"); char* GtCodeUtf8ToGB2312(char* pszUtf8) { char* pszGB2312 = NULL; ...... return pszGB2312; } 再举两个C语言中典型的函数指针的例子: 例一:int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);其中第三个参数是一个函数地址,指向被创建线程的核心处理函数。 例二:void *bsearch(const void *key, const void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *));其中最后一个参数也是一个函数地址,指向两个元素对比计算函数。 关于“事件+代理”设计概念的理解和用法大致是这些,其实无论哪种开发语言共通的是设计思想差异的是语法格式,场景不同适用的开发语言可能不同,每个语言从诞生到发展到消失都是随着计算机技术革命而不断繁衍和进化,C#语言还有许多挺有意思的地方,留在以后慢慢聊吧。
喜欢java主要是喜欢它的简单,一次调用的背后是许多开发者默默的付出,讨厌java主要是讨厌它的简单,一次调用的背后完全不知道系统私下里都干了些啥。计算机技术经历几十年发展,底层的东西日趋完善,后来人在前辈的基础上快速构建,把产品推向市场,反馈迭代再反馈再迭代,变现盈利才是王道。当下有些强业务型互联网企业重融资重逼格,满嘴设计模式数据结构算法大全,闭口不谈运营模式产品形态用户体验~!@#$%^&*()...... 爬是爬虫,网页爬取,爬网页是技术活有难度有挑战,有些站点不喜欢被爬,爬烦了容易被拉黑,有些页面不是静态的,来来回回多步交互才能完成爬取,有些站点需要验证登录,各种形式验证码考验人脑眼手配合更别提机器识别了。 <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>fluent-hc</artifactId> <version>4.5.3</version> </dependency> String html = Executor.newInstance() .execute(Request.Get("http://www.url.com") // 目标地址 .userAgent("Mozilla/5.0 Chrome/85.0.252.101 Safari/536.86") // 浏览器UA .viaProxy("127.0.0.1:2017") // 代理服务 .connectTimeout(5000) // 连接超时 .socketTimeout(10000)) // 套接字超时 .returnContent() // 返回CT对象 .asString(Charset.forName("UTF-8")); // 设置字符集 扒是扒取HTML网页标签提取有价值内容,虽然每个站点都遵循html规范,但标签的定义差别还是非常大的,如果是有针对性的爬取某几家站点,可以通过配置的方式定义标签从而在元素集合中快速定位到信息。 getElementById(String id) // <h1 id="title" cid="560" docid="8168976">标题</h1> getElementsByTag(String tag) // <a target="_blank" href="new.htm" title="财经"></a> getElementsByClass(String class) // <div class="post"><a href="http://aaa.com"</a></div> getElementsByAttribute(String key) // <img src="captchay?w=240&h=50" w="240" h="50"/> getElementsByAttributeValue(String key, String value) // <span class="txt"><a target="_blank" ... <dependency> <groupId>org.jsoup</groupId> <artifactId>jsoup</artifactId> <version>1.10.2</version> </dependency> Document doc = Jsoup.parse(htm); Elements div = doc.getElementsByAttributeValue("class", "txt"); for(Element d : div) { for(Element l : d.getElementsByTag("a")) { System.out.print(l.attr("href") + " : "); System.out.print(l.attr("title") + " : "); System.out.println(l.text()); } } 切是切分对中文文章或语句进行分词,每个单词由偏移、词性和词三部分构成一个item对象,所有对象构成一个集合通过迭代器可以遍历,用户可以添加自己的字典,也可以指定切分过滤策略,扩展性很不错,调用仅一行代码,返回一个容器。 <dependency> <groupId>org.ansj</groupId> <artifactId>ansj_seg</artifactId> <version>5.0.0</version> </dependency> // 未指定策略切分 Result rst = ToAnalysis.parse(article); // 初始化用户字典 UserDefineLibrary.insertWord("网络", "n", 1000); // 指定词性过滤分词 FilterRecognition flt = new FilterRecognition(); flt.insertStopNatures("w"); Result rst = ToAnalysis.parse(article).recognition(flt); 存是指将数据保存起来,mongodb作为类结构化存储解决方案高效稳定分布式相当好用,对数据类型和长度没有传统关系型数据库定义时那么费劲上心,很容易就能满足入门级建库建表的要求,一般复杂性的操作都能满足,用过绝对爱不释手。 <dependency> <groupId>org.mongodb</groupId> <artifactId>mongo-java-driver</artifactId> <version>3.0.2</version> </dependency> GtMongo tm = new GtMongo(); Gt.TuMongoOpen("127.0.0.1",12345); Gt.TuMongoGetDatabase("my_mongodb"); Gt.TuMongoGetCollection("my_collecttion"); Gt.TuMongoInsert( new Document("title, "一带一路高峰时刻") .append("url", "http://www.yidaiyilu.com") .append("text", "一带一路”国际合作高峰论坛举行") ...... .append("keys","一带一路") ); 构是构建索引,依托elk(Elasticsearch、Logstash和Kibana)日志分析系统,特点是分布式,零配置,自动发现,索引自动分片,索引副本机制,restful风格接口,多数据源,自动搜索负载,开源的伸缩性好,玩的好不好全看自己水平高低了。 <dependency> <groupId>org.elasticsearch.client</groupId> <artifactId>transport</artifactId> <version>5.1.1</version> </dependency> try { String now = new SimpleDateFormat("yyyy-MM-dd").format(new Date()) + "T" + new SimpleDateFormat("HH:mm:ss.SS+0800").format(new Date()); TransportClient tc = new PreBuiltTransportClient(Settings.EMPTY).addTransportAddress(new InetSocketTransportAddress(new InetSocketAddress(TE_HOST, TE_PORT))); String jsn[] = { "{\"@time\":\"" + now + "\",\"data\":{\"area\":\"area1\",\"msg\":\"msg1\",\"uid\":\"111\",\"keys\":\"key1\"}}", "{\"@time\":\"" + now + "\",\"data\":{\"area\":\"area2\",\"msg\":\"msg2\",\"uid\":\"222\",\"keys\":\"key2\"}}", "{\"@time\":\"" + now + "\",\"data\":{\"area\":\"area3\",\"msg\":\"msg3\",\"uid\":\"333\",\"keys\":\"key3\"}}" }; for (int i = 0; i < jsn.length; i ++) { IndexResponse ir = tc.prepareIndex(TE_NAME, TE_TYPE).setSource(jsn[i]).get(); System.out.println(ir.getId() + ", " + ir.getType() + ", " + ir.getIndex()); } tc.close(); } catch (Exception e) { e.printStackTrace(); } 查是查询,上面说了,自动发现自动创建,逻辑处理不复杂的话上手十分简单,但对中文的处理和复杂逻辑搜索还是需要花点功夫研究下的。 try { Settings st = Settings.builder().put("cluster.name", "elasticsearch").put("node.name", "kafka").build(); TransportClient tc = new PreBuiltTransportClient(st).addTransportAddress(new InetSocketTransportAddress(new InetSocketAddress(TE_HOST, TE_PORT))); // SearchResponse sr = tc.prepareSearch("logstash-*").get(); // 全量数据 // SearchResponse sr = tc.prepareSearch("logstash-*").setQuery(QueryBuilders.matchQuery("uid", "111")).get(); // 指定字段 // SearchResponse sr = tc.prepareSearch("logstash-*").setQuery(QueryBuilders.regexpQuery("area", ".*1")).get(); // 正则表达 // SearchResponse sr = tc.prepareSearch("logstash-*").setQuery(QueryBuilders.wildcardQuery("area", "*e*")).get(); // 通配符号 QueryBuilder qb = QueryBuilders.boolQuery() .must(QueryBuilders.termQuery("area", "area1")) .must(QueryBuilders.regexpQuery("keys", "k.*")) .mustNot(QueryBuilders.termQuery("uid", "xyz")); SearchResponse sr = tc.prepareSearch("logstash-*").setQuery(qb).get(); SearchHits sh = sr.getHits(); System.out.println("共搜到: " + sh.getTotalHits() + "条结果!"); for(SearchHit ht : sh){ System.out.println(ht.getSourceAsString()); } tc.close(); } catch (Exception e) { System.out.println("共搜到: 0条结果!"); e.printStackTrace(); } 关于“爬!扒@切#存$构%查”的话题就这么多,抛砖引玉蜻蜓点水,总之入门十分简单,深钻还是有一定难度的。每个模块花不了几行就能实现最初级的功能,java能成为最广泛的应用开发语言确实是有它显著优势的,虽说有些开发语言随着某个领域的突破会快速发展壮大,但java和c、c++还将长期存在,保持强大的生命力。
前段时间伤自尊了,被人三言两语给问蒙圈了,蔫头耷脑好半天愣没缓不过来,俗话说得好呀,刀不磨要生锈人不学要落后,人过三十不学艺人过四十天过午,老老实实做人踏踏实实做学问,证明自己实在不是干技术的料也好早点转行去卖鸡蛋灌饼。 开始今天话题前先澄清一个事,小蓝单车发布麒麟计划,共享单车即将进入精准广告、实时导航、运动数据和快乐骑行时代,联系二个月前《细思极恐的共享单车》的博文,朋友圈及各路粉丝一再追问我和蓝去去的关系,今天正式澄清:本人不是小蓝单车的产品经理,也没有从事相关方面工作,文章内容如有雷同纯属巧合。 回到正题,今天说说超时,做服务一定绕不开大并发、高效率、可靠稳定、快速响应这些关键词,从技术的角度上讲,每个环节每个步骤都有值得大书特书一番的地方,不过个人觉得最最重要的地方还是超时管理,这方面做好就成功一大半了。首先介绍一下应用场景,作为一个服务,对外受理接入请求,请求报文到达以后先解析,理解用户想要干什么,对于当下大多数的互联网业务,往往需要一系列逻辑处理,这边取点标签那边取点参数,汇总起来算一算得到最终结果再给用户返回回去。随着互联网的发展数据量越来越大,数据本地化早已不能满足业务需要,在逻辑处理过程中数次调用远程通信已是再平常不过的事情了,这就产生了上下游多个节点间的数据通信与传输的问题,那么多连接有tcp的有udp的,有长连接的有短连接的,有先发后回的,有后发先回的,有发了不回的,有不要还回的,各种情况都要考虑到,一个字忒乱。 怎样把超时管理起来呢,epoll非常棒但终究是事件触发的,把超时转化为事件是想要达到的目的。成千上万个连接的超时设置的有长有短,最讨厌的是这些连接还是剧烈频繁变动的,对于有序容器管理这些连接成本真的有点高,要是再加把锁实现进(线)程同步,效率有点让人焦虑,既然不能全局有序可不可以局部有序呢,只关心最值而不是顺序本身,根堆的偏序特性是个相当不错的选择,二叉树每次插值调姿开销确实有点大。根堆分大小,取决于比较函数,不是全局有序因此迭代器意义不大,元素追加弹出基本够用了,在用例中比较函数计算(入队事件 + 超时事件 - 当前事件)最小值当做堆顶。 001 while (true) { 002 if (sg_CPQueue.GtPQueueTop(stNode) > 0) { 003 /* 小根堆最短超时 */ 004 iTotal = epoll_wait(sg_CEpoll.GtEpollGetEpoll(), stEvent, GT_EVENT, GtAdpDeadline(stNode.m_stTimer)); 005 } 006 else { 007 /* 监听套接字事件 */ 008 iTotal = epoll_wait(sg_CEpoll.GtEpollGetEpoll(), stEvent, GT_EVENT, -1); 009 } 010 011 if (iTotal > 0) { 012 /* 套接字事件处理 */ 013 for (iCount = 0; iCount < iTotal; iCount ++) { 014 if (stEvent[iCount].data.fd == (sg_CEpoll.GtEpollGetNet())->GtNetGetSocket()) { 015 /* 新接入客户事件 */ 016 while ((iSocket = accept((sg_CEpoll.GtEpollGetNet())->GtNetGetSocket(), (struct sockaddr*)&stClient, &iClient)) > 0) { 017 GtAdpEpollAdd(iSocket); 018 } 019 } 020 else { 021 /* 读写事件派发中 */ 022 sg_CPool.GtPoolDistribute((void*)stEvent[iCount].data.fd); 023 } 024 } 025 } 026 else { 027 /* 小根堆超时清理 */ 028 if (stNode.m_iLower > 0) { 029 GtAdpErrors(stNode.m_iUpper); 030 GtAdpEpollDel(stNode.m_iLower); 031 GtAdpPQueueDel(stNode.m_iLower); 032 } 033 } 034 } 第2行从小根堆中获取一个元素,获取成功就用套接字超时作为epoll_wait超时,否则阻塞直到有事件发生为止。 第11行判断返回值,如果大于零表明有待处理事件发生,等于零表明有超时发生。 第13行至24行对事件处理,是新接入子套接字则加入epoll监听,否则派发任务到工作线程。 第28行至32行清除epoll事件并从根堆中剔除元素,同时返回上游错误信息。 001 void* GtAdpPoolCore(void* pPara) 002 { 003 GtNet::CGtNet CNet; 004 UCHAR uszBuf[GT_PACKET] = {0}; 005 006 CNet.GtNetSetSocket((int)pPara); 007 // UCHAR(0x04) | int(socket) | data ... 008 if (read(CNet.GtNetGetSocket(), uszBuf, GT_PACKET) > 0) { 009 if (GT_ADP_PKTTAG != uszBuf[0]) { 010 /* 上游请求受理 */ 011 GtAdpPoolUpper(CNet, uszBuf); 012 } 013 else { 014 /* 下游业务操作 */ 015 GtAdpPoolLower(CNet, uszBuf); 016 } 017 } 018 else { 019 /* 上游连接关闭 */ 020 GtAdpEpollDel(CNet.GtNetGetSocket()); 021 } 022 023 return NULL; 024 } 第8行读取消息(既可能是上游消息也可能是下游消息),大于零启动业务处理,否则清除epoll事件并关闭连接。 第9行识别报文格式,用例采用0x04标识示意,具体业务侧应采用更加严格的定义。 第11行调用上半场处理函数。 第15行调用下半场处理函数。 001 void GtAdpPoolUpper(GtNet::CGtNet& rCUpper, UCHAR* puszPacket) 002 { 003 int iSocket = 0; 004 UCHAR uszBuf[GT_PACKET] = {0}; 005 char szUrl[GT_BYTE64] = "192.168.0.221:12345"; 006 007 #ifdef _GT_TCP_ 008 GtNet::CGtTcp CLower; 009 #else 010 GtNet::CGtUdp CLower; 011 #endif 012 013 // 业务处理开始 014 uszBuf[0] = GT_ADP_PKTTAG; 015 iSocket = rCUpper.GtNetGetSocket(); 016 memcpy(uszBuf + sizeof(UCHAR), &iSocket, sizeof(int)); 017 sprintf((char*)uszBuf + sizeof(UCHAR) + sizeof(int), "GtAdapter: %s", (char*)puszPacket); 018 // 业务处理结束 019 020 #ifdef _GT_TCP_ 021 if (GT_SUCCESS == CLower.GtTcpConnect(szUrl)) { 022 #else 023 if (GT_SUCCESS == CLower.GtUdpConnect(szUrl)) { 024 #endif 025 CLower.GtNetSend(uszBuf, sizeof(UCHAR) + sizeof(int) + strlen((char*)uszBuf + sizeof(UCHAR) + sizeof(int))); 026 027 /* 下游启动关注 */ 028 GtAdpEpollAdd(CLower.GtNetGetSocket()); 029 GtAdpPQueueAdd(iSocket, CLower.GtNetGetSocket()); 030 } 031 else { 032 GtAdpErrors(rCUpper.GtNetGetSocket()); 033 } 034 035 return; 036 } 第13行至18行业务实现过程。 第21行或23行为初始化下游连接操作(tcp和udp是个人封装过的),需要注意的是,udp需要采用connect方式初始化,即将对端地址关联到套接字上,以方便read读取。 第28行和29行在发送完毕数据以后将套接字再次注册事件和追加到超时根堆中去。 第32行若tcp连接失败则向上游返回失败信息,udp只能通过超时判断。 001 void GtAdpPoolLower(GtNet::CGtNet& rCLower, UCHAR* puszPacket) 002 { 003 int iResult = GT_SUCCESS; 004 UCHAR uszBuf[GT_PACKET] = {0}; 005 int iSocket = *(int*)(puszPacket + sizeof(UCHAR)); 006 007 /* 下游解除关注 */ 008 GtAdpEpollDel(rCLower.GtNetGetSocket()); 009 GtAdpPQueueDel(rCLower.GtNetGetSocket()); 010 011 // 业务处理开始 012 iResult = (NULL != strcpy((char*)uszBuf, (char*)puszPacket + sizeof(UCHAR) + sizeof(int))) ? GT_SUCCESS : GT_FAILURE; 013 // 业务处理结束 014 015 if (GT_SUCCESS == iResult) { 016 GtAdpResponse(iSocket, uszBuf, strlen((char*)uszBuf)); 017 } 018 else { 019 GtAdpErrors(iSocket); 020 } 021 022 return; 023 } 第8行至9行清除epoll事件并从根堆中删除元素。 第11行至13行业务实现过程。 第15行至20行根据业务实现结果向上游返回不同的消息。 001 long GtAdpDeadline(struct timeval& rstTimer) 002 { 003 long lTimer = 0; 004 struct timeval stNow, stTimer; 005 006 gettimeofday(&stNow, NULL); 007 (rstTimer.tv_usec += GT_ADP_TIMOUT) > GT_ADP_ONESEC ? rstTimer.tv_sec ++, rstTimer.tv_usec -= GT_ADP_ONESEC : 0; 008 009 stTimer.tv_sec = rstTimer.tv_sec - stNow.tv_sec; 010 stTimer.tv_usec = rstTimer.tv_usec - stNow.tv_usec; 011 stTimer.tv_usec < 0 ? stTimer.tv_sec -= 1, stTimer.tv_usec += GT_ADP_ONESEC : 0; 012 013 lTimer = stTimer.tv_sec * GT_ADP_TMBASE + stTimer.tv_usec / GT_ADP_TMBASE; 014 015 return lTimer > 0 ? lTimer : 0; 016 } 第6行获取当前的系统时间。 第7行入队时间加上超时时间。 第9行至11行计算当前时间与超时时间的差值。 第13行将差值转换成epoll超时的时间单位。 完整的代码片段http://git.oschina.net/gonglibin/codes/rc87dt0iagk4exqm3psuw66,作为论证和用例代码仅供参考,调试测试通过,根堆封装成对象的时候已经加入锁,epoll对象早年封装的没有考虑同步的问题,代码中通过注释已经提请留意了。 外面下雨了,前些日子的暑热一扫而光,Google I/O 2017开发者大会518开幕了,谷歌宣布kotlin作为一级编程语言,消息一出有小伙伴慌了,其实木必要,kotlin好比加了语法糖的java,跟java原本就有着血浓于水的感情,况且谷歌与oracle豪门之间的恩怨不至于革小码农们的命,编程思想比实现语言更重要,人工智能与VR是好东西,未来有前途,抽空研究研究。
天舟一号发射国产航母下水C919成功首飞好事一桩接一桩,中国人认定要干的事情没有干不成的实在可喜可贺。这拨沙尘过去了,天真蓝又能大口喘气了真舒服,半夜里骑小蓝车去麦当劳买那么大鸡排,店里竟然坐着一大半客人在用餐,望京国贸CBD金融街上地西二旗写字楼的灯火通明时刻提醒人们这里是繁华的国际大都市,任何不努力都只是借口,什刹海三里屯也灯火通明,算了吧那地少去,喝的不是酒全是寂寞。 做软件开发的总是躲不过同步机制这个话题,并发处理免不了对公共数据进行操作,不认真对待它总找你麻烦,一段代码写的漫天飞舞天花乱坠结果折在同步上是一件很憋屈的事情,一个木桶能盛多少水是由最短的那块木板决定的,没错同步机制处理不好这就是瓶颈。同步机制这个话题有点大,三言两语说不清,需要耐下心慢慢研究,吐血推荐《UNIX网络编程(第2版)》第1卷以及第2卷之进程间通信,作者史蒂文博士,全宇宙知名网络专家教育家,治学严谨而且文章通俗易懂,可惜天妒英才令人扼腕叹息,上帝身边缺个程序猿。没读过史蒂文博士著作的攻城狮不是好码畜,必须精读,必要时动手操练一下受益匪浅绝对一生受用。 同步机制不能不说posix和system v这两种标准,posix比较新方便移植也更标准化一些,在没有竞争条件的情况下,保持用户态调用也更轻量化一些,system v相对老些,每次调用都会进出内核,性能方面略处下风,不追求极致的话哪个都行全凭个人习惯和喜好。主要的同步机制包括锁(自旋锁、互斥锁、条件锁、读写锁)、信号灯(信号量)、记录锁(文件锁),接下来谈谈个人的认识和理解。 先撇清一个问题,有童鞋说原子操作也能实现并发处理对公共数据的排他性操作呀,这话没毛病,原子操作是利用CPU单一指令执行中不可中断的特性实现的,即必须确保对数据的操作在一个指令中完成,显然对一个变量的“读改写”在一条CPU指令里完成是不现实的,因此需要一种机制来保障,由于同步机制考量的是对一系列计算过程或一块代码区间执行的独占性操作,所以这个话题就不在这里讨论了。 自旋锁的意义在于对超短时间内的操作进行轻量级锁定,它会一遍一遍尝试获取锁定,容易造成CPU高占用,并且锁定区间存在阻塞操作的话还将导致潜在的死锁风险,因此对于大块逻辑计算完全不适用,自旋久了容易晕,一晕就容易吐,一吐就容易虚,一虚就容易亏,一亏就需要补~ 互斥锁好东西效率真棒没的说,上锁的干活等锁的挂起完全不占用任何计算资源,有两个属性超级有用,一个是PTHREAD_PROCESS_SHARED,把含有该属性的互斥锁放在共享内存中供多个进程分别映射到各自地址空间中,竟然没有比线程共享在效率上有明显衰减,真是了不起;另一个是PTHREAD_MUTEX_RECURSIVE,同一线程下多次锁定不会导致阻塞,在团队开发工作中有时无法彻底杜绝嵌套调用,互斥量就显示出它的价值了,但互斥量必须成对出现,而且也是有系统开销的喔,另外上述两个优惠政策不能同时享受,最终解释权归操作系统。 条件锁跟互斥锁基本一回事只是增加了条件判断,满足才触发下面的流程,否则挂起等待,节省了大量轮询的资源消耗。唤醒方式分单播与广播,在明确条件等待者身份唯一性的情况下单播已经够用了,否则推荐更可靠的广播方式发送,假如对唤醒还是不放心的话还可以调用定时等待,条件满足或超时立即返回,这样够放心了吧。 读写锁是在互斥锁基础上对特定应用场景的优化,写写之间互斥读写之间互斥读读之间共享,总结起来就是数据变动就独占不变就共享,效率比互斥锁差一些也是完全可以理解和接受的。 信号灯(信号量或信号量集合)网上有不少讨论,有认为是一回事只是叫法不同而已,也有认为是二回事,个人支持第二种,因为我们的语文真的是语文老师教的,回到这个严肃的话题上说说区别。灯有两种状态开和关或是亮和灭,非开即关不是亮就是灭,半开半关半亮半灭的灯从来没见过;量代表一个值,可以是十可以是百可以是千,总之不是非此即彼的关系,假设量是一定的,借一个少一个,借光了只能等着,有人还回来了才能再往出借,所以说信号灯是信号量初始为一时的特例,信号量是对信号灯的衍生和扩展,能够解决更加复杂的资源分配问题,长这么大还是第一次被自己清晰的条理和超强的概括能力震撼到。信号灯分两种有名的和无名的,无名比有名快,posix比system v快,无名的用在线程间,放共享内存被映射后进程间可用,有名的原本就为进程间设计的。另外system v还分带不带undo标识的,不带的更快点,带的如果进程over了可以自动释放。两套标准分别提供各自的API,并包含非阻塞的调用方式。 记录锁(文件锁)都是对文件进行操作,区别是粒度不同,记录锁是锁文件区间,文件锁是把对文件的操作当成锁操作并进而转化成独占操作而已,因此锁效率较差用的也越来越少了,但对文件中的部分区间上锁进行读写操作还是有存在和使用的价值的。 下面两张图是尊敬的史蒂文博士对上述各种同步机制在Solaris上测试的结论数据,鉴于上世纪末计算机的计算速度和处理能力,各项数值在当下已经没有实际参考意义,但数值间的差值变化与发展趋势还是能够真实反映各自效率的,具有重大的参考价值。 总结了若干条在使用同步机制时需要注意的事项。 第一:上锁和解锁务必成对出现且尽量放置在相同的调用层级关系中; 第二:坚持谁上锁谁解锁原则,适用在进程、线程、协程乃至于函数和方法; 第三:原则上锁定区间越小越好时间越短越好,兼顾锁定频率适当优化代码; 第四:锁定区间强烈反对任务阻塞操作即便自认为可控也绝不是明智的做法; 第五:进程间同步务必注册回调函数以保证进程或正常或异常终止时能释放锁; 第六:业务流程侧实现锁操作而非在功能算法侧以减少嵌套调用规避死锁风险; 第七:不同应用场景选择适合的同步机制对并发处理效率的提升效果十分明显; 第八:妥善维护好锁属性与状态以及远离非法操作是保证并发处理的重要基础; 总结一下中心思想,对于如何提升并发处理效率最好的思路不是选择具体哪种同步机制,也不是怎么组织优化代码,更不是简单增加进(线)程数,而是压根就不用任何同步机制。之前《高性能服务优化》一文中承诺单聊下同步机制,这篇作业就算是兑现了,文中所涉及的代码实现在http://git.oschina.net/gonglibin/GlbLib-1.0.0中都可以找到,包含封装与测试,需要的自己扒吧。 坚持梦想不忘初心送给今天的自己和各位小伙伴儿,2017年5月8号。
做服务无非就那几步,启动端口监听、接收请求数据、分配资源受理,反馈应答数据,关闭释放资源。一个优秀的服务端实现要考虑并发处理和对共享资源控制还是要费点心思的。Linux下提供了一个基于xinetd服务的低成本小制作解决方案,用起来超方便,优缺点放最后总结欢迎点评。 0001: /****************************************/ 0002: /* Author: gong_libin */ 0003: /* Date: 2000_01_01 */ 0004: /* File: GDeamon.c */ 0005: /****************************************/ 0006: 0007: /** 0008: * /etc/xinetd.d/GDeamon 0009: * service GDeamon 0010: * { 0011: * flags = REUSE 0012: * socket_type = stream 0013: * wait = no 0014: * user = root 0015: * server = /usr/bin/GDeamon 0016: * log_on_failure += USERID 0017: * disable = no 0018: * } 0019: * 0020: * /etc/services 0021: * GDeamon 321/tcp # The GDeamon Protocol 0022: */ 0023: 0024: #include <stdio.h> 0025: #include <stdlib.h> 0026: #include <string.h> 0027: #include <unistd.h> 0028: #include <fcntl.h> 0029: #include <time.h> 0030: 0031: #define G_STDIN stdin 0032: #define G_STDOUT stdout 0033: #define G_STDERR stderr 0034: 0035: #define G_TIMOUT 10 0036: #define G_PACKET 1024 0037: #define G_RESULT G_PACKET * G_PACKET 0038: 0039: int main(int argc, char* argv[]) 0040: { 0041: FILE* pstPipe = NULL; 0042: time_t ulTime = time(NULL); 0043: char szPacket[G_PACKET] = {0}; 0044: char szResult[G_RESULT] = {0}; 0045: int iCount = 0, iTotal = 0, iLength = 0; 0046: 0047: close(fileno(G_STDERR)); 0048: fcntl(fileno(G_STDIN), F_SETFL, O_NONBLOCK); 0049: 0050: if (fread(&iLength, sizeof(int), 1, G_STDIN) > 0) { 0051: while (iTotal < iLength) { 0052: if (time(NULL) - ulTime > G_TIMOUT) { 0053: printf("数据接收超时"); 0054: break; 0055: } 0056: if ((iCount = fread(&szPacket[iTotal], sizeof(char), G_PACKET - iTotal, G_STDIN)) > 0) { 0057: iTotal += iCount; 0058: } 0059: } 0060: if (iLength == strlen(szPacket)) { 0061: if (NULL != (pstPipe = popen(szPacket, "r"))) { 0062: if (NULL != fgets(szResult, G_PACKET, pstPipe)) { 0063: printf("%s", szResult); 0064: } 0065: pclose(pstPipe); 0066: } 0067: else { 0068: printf("命令执行失败"); 0069: } 0070: } 0071: else { 0072: printf("数据长度错误"); 0073: } 0074: } 0075: 0076: return 0; 0077: } 009-018定义配置文件/etc/xinetd.d/Gdeamon,通知系统允许端口复用、传输层是TCP协议、不等待进程终止、进程归属root用户、执行程序存放路径等信息。 021行追加到/etc/services文件中,通知系统传输层协议类型和监听端口及服务名称。 047行关闭错误输出免得添乱,48行设置标准输入为非阻塞模式。 050行从标准输入读取一个整形,在这个用例中这个整形用于表示后面数据的长度。 051-059行根据上面整形值循环读取数据直到读取完毕或超时为止。 060行判断读取数据的完整性。 061-069把用户发送过来的指令提交系统执行并通过管道获取执行结果。 063行把执行结果通过标准输出返回。 完事了就这么简单省心省力满足并发处理资源回收完全不操心,监听端口监听到连接的时候会fork子进程,同时把子连接的套接字传下去,并把读写操作重定向到标准输入输出上,真欣赏这种简单粗暴而又行之有效的做法。用例仅供参考,至于通信协议和异常机制没做过多处理,需要自己加吧。 优点:开发便利快捷,重点可放在业务流的上,处理并发量不大逻辑不复杂的接口半天时间连调带测足够;不仅支持tcp协议也支持udp协议,可以节省大量服务框架代码的开发工作量; 缺点:并发量一大力不从心,响应不够快速高效,在线调测不够方便; 今晚有雨出门带伞~
凡是努力过的人都有一个共同点那就是懂得天赋的重要性,没有天赋的人努力做的再好还不如人家随便搞搞,虽说努力不一定会成功,但不努力真的很舒服,有时候不逼自己一下都不相信自己还能把辣么简单的事情搞砸,聊聊高性能服务优化这个重大课题就是为了证明一下自己的技术实力,其实知道的真的就这么一点点。 这是一个严肃认真的课题,一个复杂深奥的课题,一个滋滋冒油的课题,营养而又美味,值得花费精力深入探索追求极致与完美。要说这年头硬件是越来越便宜了,服务器处理器随随便便几十个核,内存32G是最低消费,硬盘1T的起码好几块,网卡想驱几个驱几个,一加电呼呼响那动静跟飞机起飞似的。服务器集群化应用和管理越来越便利了,计算存储资源都动态调剂了,有必要为了提升几个点的性能费这么大劲吗?没错这话问的没毛病,确实没必要费那么大力气去整这玩意,瞎耽误工夫还不如读读《钢铁是怎么炼成的》呢,下面就聊聊一般可以从哪些方面入手来优化高性能服务。 1、修改系统默认单一进程可打开文件描述符参数以便允许进程创建更多的接入; 2、修改系统参数在不影响现有服务的前提下优化各种关闭状态实现尽快回收资源; 3、服务端初始监听套接字允许端口复用通过多进程多线程方式增加并行处理能力; 4、采用非阻塞的通信方式,事件驱动的集中管理方式,边缘触发的内核通知模式; 5、采用异步方式处理业务逻辑,尽量找事干处理器一刻别闲着,哪里有活立马干; 6、开发语言支持协程尽量以协程实现业务逻辑,协程是极轻量化线程性能提升明显; 7、优化套接的字属性并扩充读写缓冲区的空间,增加单位时间里数据的有效吞吐量; 8、采用零拷贝实现对套接字底层读写,减少数据在内核空间和用户空间的复制次数; 9、进(线)程绑定处理器内核,提高任务调度命中率,降低上下文切换带来的损耗; 10、谨慎选用同步机制以避免进(线)程间因争夺共享资源成为系统瓶颈,以后单聊; 11、根据操作的特性来合理选择容器类型用于存储各类资源,具体情况还需具体分析; 12、优化任务调度算法,优化进(线)程属性参数,适度并合理调配系统的计算资源; 13、欢迎补充。 上面提到的部分优化措施所对应的具体代码实现可以从http://git.oschina.net/gonglibin中“项目”或“代码片段”中找到,欢迎咎由自取。 前些日子有天下班在立水桥换乘,看见一个白白胖胖的小哥倒在站台上,上身T恤下身牛仔裤脚穿旅游鞋,红十字双肩包扔在旁边,躺在地上喘着粗气,一看就是那种天天久坐披星戴月早出晚归晒不着太阳的同行,后来在站务员的关照下慢慢缓过来了,所以说男人对自己下手也别太狠。 网上说长斑的香蕉对贫血高血压便秘和心绞痛效果更好,特地把刚剥开的香蕉又默默包了起来,着急吃的小伙伴们可以在表皮上画上黑斑效果应该是差不多的,天舟一号顺利升空了,国产航母也顺利下水了,上海上港给首尔FC瓷瓷实实上了一课,膀大腰圆的胡尔克壮的像头牛,五一国际劳动节要到了,假期还是一如既往工作和学习,老话说的好,世上无难事只要肯放弃,有时候人不努力一下都不知道绝望的滋味。
序列化是啥啥是序列化为啥要序列化把啥序列化不序列化行不行?问题真多,馒头要一口一口吃问题要一个一个回答。所谓序列化就是格式化,感觉哪不对劲,是约定的格式化,及格了,能够满足正向操作和逆向操作的约定格式化,良好了,能高效稳定满足正向操作和逆向操作的约定格式化,满分了,能提供轻松扩展并高效稳定满足正向操作和逆向操作的约定格式化,耶~恭喜你都会抢答啦。能够支持PHP语言的......,歇菜没你事别瞎掺和。 格式化好理解,标题正中段落开头空两个字段落结束换行这就是格式化,修饰词约定的意思是说你看见这个格式就知道哪个是标题哪个是正文,满足正向操作和逆向操作意思是说能转换过来也必须能丝毫不差原样还原回去,高效好理解一次操作就占一半以上系统开销,数据中一大半是冗余信息估计没人爱用,稳定可靠不能动不动就掉链子,一会儿异常了一会儿解析失败了一会儿又丢数据了。序列化是啥啥是序列化我解释完了,觉得不对的下面就不用看了。 为啥要序列化,一提这茬儿内心满满的委屈一把辛酸泪呀。早年电脑不联网的时候数据都在本地格式化着用,后来联网以后因为硬件和网络等差异,高低字节序主机字节序网络字节序各种问题扑面而来,接着就是各种协议呀标准呀,国际组织特地制定一大套RFC规范,虽然有些规范允许用户通过夹带数据的形式提供扩展,但用起来还是有种种不便。这些年随着云技术发展,对海量数据传输、存储和安全就变的越来越重要,RFC解决的是公有问题,序列化解决的就是私有问题。多年前项目实施中需要跨设备数据传输,Y哥打算把结构体强制类型转换以后扔过来,为这事跟他PK,后来呢没有后来了。其实吧,当时用的是刀片组服务器,无盘的从刀去mount主刀,其实这个方案在当时的确没问题,但以后呢,谁能保证采购的设备都用刀片,谁能保证服务器都是一个品牌一个型号的呢,矮油说出来真是舒服多了。 把啥序列化,废话当然是数据了,还能是鸡蛋灌饼吗。其实数据跟数据还是有差别的,数据类型有n多种编码n多种长短不定必选的单选的多选的,把这些特性要一一恰当地表达出来。 不序列化行不行,行呀,你开心就好!法律规定纳税是公民应尽的义务,又没规定数据必须序列化。 谈谈评判一个序列化工具优劣的标准吧。 首先是效率,包括计算效率和载荷效率两个方面。假设完成一次处理需要耗时一秒钟,序列化和反序列化时间越短则效率越高,不能把时间都耽误在这方面,这些操作对用户来说是不可见的,用户是上帝浪费他们的宝贵时间就是图财害命。网络是按流量计费的,多用多花少用少花不用不花,报文中没用的东西占一半,有效载荷仅仅50%,再压缩也瞎掰,传输花钱存储花钱,过日子要精打细算,吃不穷穿不穷算计不到就受穷,有用的留下没用的统统丢掉。 其次是扩展,约定好的不能老变,今天添点这个明天删点那个简单类型改对象了元素改数组了天天追着屁股后面收拾能不累嘛,开始前把这些潜在的问题都想好,省掉多少麻烦呀,扩展性好不好全仰仗工具本身了,一个优秀的序列化解决方案可以让大家开开心心合作很久,否则就等着被喷吧,内部合作就算了大家碍于情面,外部合作除非你是甲方。某年某月某运营商的某负责人指着当今全球最大的通信设备制造商某产品负责人的鼻子大骂你们还想不想干了,这个场景在十年后的今天依旧记忆犹新,听说最近要“混改”,固网用户大量流失移动用户增长乏力,一手好牌打的这么烂也只能呵呵哒了,不好意思跑题了~ protobuf avro thrift bson json xml ...... 计算效率 贼快 超快 特快 够快 挺快 还算快 别提了 载荷效率 贼高 贼高 也贼高 高拉特 一般般 真一般 别瞎比 数据类型 啥都有 全都有 真都有 够用 有缺失 不全面 都有啥 开发周期 花雕 状元红 女儿红 加饭 一口闷 对瓶吹 吐一身 研发成本 不差钱 咱有钱 哥趁钱 这小钱 哥没钱 真没钱 别提钱 技术门槛 高智商 智商高 智高商 有智商 无痴呆 向左看 向右看 综合点评 实时业务吐血推荐 大数据业务推荐 定制业务推荐 系统内推荐 Java体系小快灵推荐 前端或配置推荐 确定你比人家更出色 光说不练假把式光练不说傻把势,上干货。 Protobuf(java版本):http://git.oschina.net/gonglibin/codes/w0ulc5noe2yzi87sx3tbv65 Protobuf(C++版本):http://git.oschina.net/gonglibin/codes/0m3f4vt6yheci57ro2dg196 Avro(C++版本):http://git.oschina.net/gonglibin/codes/qknvpiuzxrdjs582mel4g31 Bson(C++版本):http://git.oschina.net/gonglibin/GlbBson Json(java版本):http://git.oschina.net/gonglibin/codes/xq3m08arci1d5ksoenyv774 Json(C++版本):http://git.oschina.net/gonglibin/codes/3bk2e5z9w0t8spugi6ayq69 Xml(C++版本):http://git.oschina.net/gonglibin/codes/h5cowfr2tmue0yszv841l29 最后给出一个个人的序列化反序列化实例,该实例是基于KLV可扩展跨平台可加密可压缩支持宽窄字符,受GTP协议启发供企业内部平台应用的解决方案,站在巨人的肩膀上前各位前辈致以崇高的敬意。 http://git.oschina.net/gonglibin/GlbLib-1.0.0/blob/master/src/GlbNet/GlbPro.cpp?dir=0&filepath=src%2FGlbNet%2FGlbPro.cpp&oid=8daa3e63431d5ca30267d44b59fb77561e77dbf1&sha=2a17296ac1a35a38dca9c1b996a3e39a47d1df23 最近共享经济火呀,共享与分享一字之差,站在第一人称的角度,共享的可以是属于自己的也可以是属于别人的,分享却只能是属于自己的才会拿出来分给其他人,分享与分赃也是一字之差,分享是高尚的行为,分赃却是肮脏丑陋的罪恶。博客属于自媒体,技术博客不同于八卦博客,是自媒体中的阳春白雪,它可以陶冶人的情操,舒缓人的情绪,助消化助睡眠,缓解女性妊娠期孕吐反应。 要想成为一名优秀的程序猿,靠的是三分技术与七分勤奋,笔耕不辍手脚勤快是这种品质的重要保证,不经历风雨怎能见彩虹没有人能随随便便成功该出手时就出手风风火火闯九州他说风雨中这点痛算什么擦干泪不要问为什么,艾玛差点忘说了,另外还有那九十分靠的是人品。
搜索真是个好东西,没有前想找点东西忒费劲,也不知道去哪找,现在赶上好时候了,敲敲键盘点点鼠标问问度娘啥都知道了。虽然好些人吐槽度娘太任性,可有时候没她真不行,搜索结果和谷歌差不是一星半点儿,尤其技术贴,真怀念当年www.g.cn,但有的选吗,既然不能改变只能含着泪往前走。 开源的资源最牛逼的要说是lucene了,特别这些年不断完善,太多公司的产品用它了。另外一个是ELK,其实底层也是依托lucene做索引的核心层,从数据采集、创建索引到检索服务一条龙解决方案,支撑企业级应用绰绰有余,更大的数据规模也不在话下,分布式特性完美的顺应了时代需要(不云不开森),连喷子们都着急干跺脚找不到槽点完全生无可恋。 哥不信那套,非要挤挤这个粉刺,who说没槽点,有呀,ELK每次升级都面目全非,一点传承性都没有,完全颠覆码畜们的认知,太虐心了,本来学点东西就吃力,悟性差智商低岁数大野路子半路出家,不利因素集于一身,ELK这是借升级清理门户吗?其实ELK确实很不错,非常值得研究和学习,等有时间了一定大书特书一番。开春儿了,压箱底儿的存货发霉了,拿出来晒晒太阳曝曝光,聊聊自己的私有搜索引擎吧。 汉语分词,老祖宗流传下来的语言太美妙了,发音悦耳书写优美唯一的缺点就是疏忽了单词间距,西洋语言发音蹩脚辅音结尾声调不分全特么是缺点,但就一点好单词间空格分隔分词太省力了。吐血推荐汉语分词利器ANSJ,精度要求不高的话拿去用妥妥的,JAVA语言ToAnalysis.parse(sentence).recognition(filter)一句话搞定,管你啥字符集呢,还能自定义用户字典,自定义停用词库、属性过滤等等,真是小微企业的福音呀,感谢开源。Java分词用例可以参考,http://git.oschina.net/gonglibin/codes/rnplksfcy07ezivm3q4th53,另一个是基于字典库自己实现的C分词,http://git.oschina.net/gonglibin/codes/2570vrhlbutonmjxqc1az97。 停用词表,汉语词汇可分为两个大类,实词和虚词,以及十五个小类。其中,名词、动词、形容词、数词、量词、代词、副词、区别词、状态词共九小类为实词;介词、连词、助词、语气词等四小类为虚词。加之两类特殊的词,拟声词和叹词,共同组成汉语词汇类型。就分析系统本身而言,实词中的数词、量词、代词、区别词、状态词,以及虚词四小类、特殊词类的拟声词和叹词均收录在停用词表中。看看吧,为啥说汉语精彩呀,光词性分类就相当疯狂了,当然具体表定义还要具体分析,看行业看业务看需求,应该是各有各的不同。 正排索引,正向索引是在完成分词后,对词组进行特征标注的过程,包括展现频率、出现位置、词组格式等属性。正向索引在内存中以键值对方式暂存,将文档ID作为主键,词组集合作为值,在导入至倒排索引后删除。可以选择的容器很多,不考虑效率哪种都中。 倒排索引,倒排索引以字或词组为关键字创建索引,关键字对应的记录项保存了出现这个字或词组的文档集合,记录着该文档的ID和各个字符在该文档中出现的位置信息及描述。由于每个字或词组对应的文档数量是动态变化的,所以倒排表的建立和维护较为复杂,哥觉得复杂不代表别人也觉得复杂噢。由于查询的时候可以一次性得到查询关键字所对应的全部文档ID的集合,所以计算效率较高。在检索过程中,响应速度是一个较为关键的性能指标,而索引的建立是在后台进行,虽然效率相对差一些,但并不会影响到整个搜索引擎的总体效率。 索引创建,数据应用是对结果数据进行一系列的预处理,按照定义的格式生成索引数据保存起来,等待搜索引擎加载模块的初始化操作,假设索引数据包括用户兴趣、用户地域、用户类型等维度。数据加载模块读取存储器中的中间数据并写入到内存数据库中,一个或多个搜索引擎的实例将内存映射到本进程中,保证数据样本一致性和并发访问。 搜索引擎通过解析URL的入口参数,对检索集进行整合排序操作后,返回给调用用户。用户根据不同的业务类型向数据引擎发起资源定位请求,从而获取搜索结果。一次完整的检索请求由两个步骤组成,索引查询和数据传输。第一步将关键字对应的DOC数据的ID集合返回,应用层再对ID集合遍历,逐一获取对应的DOC快照片段或全量文本。 1、解析URL参数,获取索引项、关键字、逻辑关系及结果区间; 2、根据各个关键字获取文档ID集合,仅留存符合的索引字段项; 3、根据索引字段项逻辑关系归并多个关键字对应的文档ID集合; 4、根据关键字展现频率、出现位置、词组格式等属性来计算权重; 5、根据权重值按照降序执行排序,按用户请求集合结果区间返回; 检索流程是一个高度实时性同步通信服务系统,用户搜索操作的请求与应答需要控制在毫秒级时间段内,在搜索特性上同时具备BFS(广度优先)与DFS(深度优先)的特性,并且通过tf-idf向量模型来评估一个字或一个词组在语料中的重要程度。 摆糊了这么多说说咋存的吧,正排用单链放内存里,管他呢内存有的是不用白不用,倒排放codis里分布式全权托管,定义好数据结构想怎么用怎么用,持久化啦读写异常啦内存管理啦基本不用过问,连接池里取出一个用,想着还就行了,搜索优化略微费点心,无论如何省不掉的,工作量的大头,水很深,拼技术拼体力看颜值看人品的活儿。DOC集合扔mongodb里,半结构化加分布式个人觉得稳定性效率并发各方面还是挺合适的。最后补充说明下,搜索技术是一个非常庞大的全面性系统性的复杂工程,值得学习的地方非常多,对个人技能的提升很有帮助,今天这篇low文就算是一个好的开始吧。 北京去年冬天不太冷,今年春天来的又早,柳絮没往年多,高速出京一如既往的堵,天真蓝云真白难得好天气,吉野家的什锦蘑菇饭浓重的荤油味冒充假素食,培训班的孩子们匆匆忙忙扒拉几口饭又赶下一场去了,不容易可也没办法,苦点累点总比被淘汰好,除了那些高尚的、纯粹的、脱离了低级趣味的人们。两年一届的上海车展即将拉开帷幕,劳斯莱斯法拉利兰博基尼早早被预定光了,今年下手又晚了,午饭鸡蛋灌饼里一定多加两片生菜,安慰一下那颗受伤的心。
中国互联网发展到今天基本形成了一个定式那就是免费,想让用户付费别做梦了,连操作系统都不花钱,吃你俩烂西瓜又算得了什么呢。虽说免费吧,但中国互联网这些年也发展的有声有色,还革了许多其他行业的命,走别人的路让别人无路可走。总体来看,互联网赚钱无非广告、游戏和电商三种模式,游戏和电商直接收费、广告间接收费。试想在百度每搜索一次付一分钱,微信QQ每用一天收一毛钱,用不了多久估计就挂了,毕竟愿意付这点小钱的用户都太少太少了。有人说国人不重视知识产权,也对,不过这个话题与人群估值一般性算法无关,自动关闭。虽然大多数用户享受互联网服务的时候不愿意付费,但在不影响用户总体体验的前提下找人替用户付费还是相当靠谱的,这样用户在贡献个人行为的同时就可以享受免费服务了。又有人说了国人的用户隐私得不到保障,烦不烦,要啥隐私要啥自行车,不是说了和人群估值无关的话题自动关闭了嘛。 人群属性 自然属性 性别、婚姻、生育、行业、职位、收入、教育、健康、资质、年龄段、所在地 ...... 兴趣属性 关注、爱好、兴趣、关联兴趣 ...... 消费属性 基础偏好、短期偏好、长期偏好、关联偏好、消费次数、消费金额、消费频率 ...... 设备属性 终端设备、操作系统、接入方式、传输带宽、地理位置 ...... 社交属性 社交应用、群组特征、关联账户、活跃程度 ...... 根据用户的操作借用互联网广告行业的概念可以分为展示(impression)、点击(click)和行为(action)三种类型,展示指一般性浏览,点击为用户主动触发,行为指用户有明确意图的由一系列步骤构成的操作集合,如注册下载等动作。一个用户在互联网上的行为由多个操作组成,一个人群的定义又由多个用户的各个属性维度组成,因此人群 + 行为 = 估值,对数据建模和逻辑处理的过程叫算法,将采集、存储、计算和应用集成到一起称为系统架构,高效稳定的跑起来叫做人群估值体系,把这一整套玩意玩转的人叫牛牪犇,也不知道这么定义有没有毛病。 公式1:UV(UserValue) = P(I)V(I) + P(C)V(C) + P(A)V(A) 公式2:UV(UserValue) = wimpP(I)V(I) + wclkP(C)V(C) + wactP(A)V(A) 公式一用户估值由用户浏览、点击和行为三种价值相加后转化而来的,按照用户意图的强度可表示为P(A) > P(C) > P(I),因此对用户价值的评估有必要附加权重系数以充分表现其价值的实际意义,由此得到公式二即获取用户的基准价值。不妨以电商为例,注册、搜索、筛选、加车、购买,新客或回头客可以分别赋予不同的系数q,那么该用户对某商品的喜好就可以表示为UL = L(c, u) * (1 + q),进而该用户的行为价值f(uap) = UV * UL。 再说说用户价值与目标人群价值的相关性吧,假设将每个用户的人群属性分别映射为一个单独的向量集合Vx = {A1, A2, A3 ... An},那么N个维度的集合记做Un = {V1, V2, V3 ... Vn}, 恰好目标人群样本经过离线学习以后同样获得N个维度的集合记做Bn = {V1, V2, V3 ... Vn},那么该用户与目标人群保持潜在一致性的概率分布可以通过公式三获得并位于[-1, 1]区间。 其实说了这么些东西,连自己都觉得空洞实际应用的价值相当有限,比如前几天在某宝某东上买了些商品,购买行为早都结束了,短期内也不会再有类似消费行为了,浏览个网页却偏偏还是满屏的刷,一边喊着提升转化率,一边喊着消耗量,反正不花自己兜里的钱,一二线的互联网公司尚且如此,更何况那些技术底蕴薄弱的创业公司和小微企业了,数据没错、算法没错、媒体没错、广告主更没错,是我错了,我不该不清cookie,最后顺便感谢下大姨妈的马老师和苏老师。 前几天中央创立了雄安经济特区,伴随结构调整和深化改革,国家又将迎来翻天覆地大发展的机遇,听说好多有眼光有胆识的精英们都第一时间冲向了遍地黄金的南三县,自己也想去,可惜盘缠不够,写博客不容易,请大家自觉养成给博主打赏的好习惯,别每次都提醒了,切实转变思想作风,撸起袖子加油干吧! 励斌出品,必是精品,喊的不齐重来,励斌出品,必是精品。
epoll是个好东西好多地方都在直接或间接的用,nginx用event库用nio用你用我用大家用。LT模式省心ET模式牛掰,处理得当效率那真是杠杠的,C、C++的用法可以参考“项目”通用库https://git.oschina.net/gonglibin/GlbLib-1.0.0中的封装,也可以参考“代码片段”中的用例https://git.oschina.net/gonglibin/codes/kev675liw8unz3j4psft134。由于一个潜在的项目迁移计划导致未来存在基于java技术路线中涉及线程池大并发长(短)连接的业务,所以一个框架就显得特别重要,兵马未动粮草先行。其实不同的业务包含各自不同的应用场景,就通信而言其实差别有时候还是挺大的,同样TCP连接,有的长有的短,有的求多应少,有的求少应多,还有一问一答的,更有一言不合就shutdown的,对底层资源的管理不上心有时候真不行,大半夜没起过夜绝对不算老司机。 说说nio踩过的坑吧,下面列举的仅仅是让发动机托底的大坑,其中崴脚的小坑更是不计其数,首先打开地址https://git.oschina.net/gonglibin/codes/2jagu36pq7dwsc1eylkr916。首先注意一点的是nio的许多操作不是线程安全的,用不对各种坑有的踩了。大致思路首先创建TCP服务,构造里建线程池,主进程里给selector对象画个圈圈,这个对象的所有操作限制在listen方法里,把对业务的处理全部浸泡在读事件中,为啥读完不注册写信号让写事件处理呢,一会后面说吧,要是忘了千万别提醒我。 坑一,cli.register(sel, SelectionKey.OP_READ, ByteBuffer.allocate(TU_SERSIZE)),在注册新连接的时候顺带把allocate的buffer赋值给SelectionKey对象的attachment,接下来read并判断读取长度,不大于零和对端一起关闭,否则把SelectionKey对象往线程池里一派发就完事了,流程自己继续跑它自己的路,代码看起来简洁潇洒大气木毛病,正嘚瑟着问题来了,对端一关闭傻眼了,新请求直接把attachment擦除了,所以还是别顺带了,老老实实给每个任务开个buffer吧。 坑二,这些年搬砖一直追求精益求精对资源少占少用,C、C++一个套接字底层对应两块buffer,一读一写井水不犯河水相安无事,ByteBuffer对象是环形buffer,通过flip方法实现读写缓存的逆转,把position游标值和limit提交值一变轻松搞定,好是好但在线程池里维护一个连接上的各个buffer就够费劲的这还非要把读写合并一堆儿,真考验人呀,加锁呗,行是行就是显得low。 坑三,把SelectionKey对象的实例作为线程入口参数派发,表面看没毛病,但在完成各个事件处理以后重新注册监听事件的时候线程间同步又成了问题,其实这个坑已经解答了上面的疑问,读写分离按需注册固然完美,但每个线程的执行时间是不同的,CPU时间片调度也只能相对均衡,保不齐谁早点谁晚点,一个close操作秒完,那边任务才跑一半,咔嚓一下就把注册的监听事件给改了,结果这边的注册失效了,傻了吧唧还等呢,左等不来右等不来结果下班了。 坑四,SocketChannel对象关闭以后把SelectionKey实例的valid记做false,打标记倒是无所谓啦,不过无效的对象是不是就别再放在监听集合里处理是不是好点呀,免得每次还要判断,不判断就抛出一个异常,你看人家C库epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)设置EPOLL_CTL_DEL清理的干干净净,或许这么玩有人家自己的道理吧,相信别人这么做是有道理的。 把接收缓存和频道对象作为入参传给线程池进行任务处理,保证每次请求都能被唯一的指派,强调服务的一对一特性,处理完流程内部直接返回了,能做成读写异步自然好呀,不过写这个框架是有私心的,业务指向十分明确,其他的需求还是另起炉灶或许更好一些。最后还是要强调一下,不同的业务对通信方式的要求不尽相同,所以一个框架解决不了所有问题,还是应该具体问题具体分析。 难得一本正经的写个东西浅显地剖析一个不是问题的问题,口号喊起来:励斌出品,必是精品,欧耶!
摘 要:所谓机器翻译就是利用电子计算机把一种自然语言表达转换为另一种自然语言表达。其处理策略主要有三种。一,基于规则转换的机器翻译系统;二,基于例子的经验系统;三,基于词转换的机器翻译系统。其中,主要以第一种系统在我国的产品化机器翻译软件中比较多见。从二十世纪五十年代开始,机器翻译已经经历了五十多年的开发与研究,耗费了大量的人力和财力,翻译效果却依旧不能令人满意。本文希望通过对机器翻译发展瓶颈的论述,开阔研发人员的思路,促进技术革新,提高机器翻译水平的质量。 关键词:机器翻译 辅助翻译 一、绪论 随着信息时代的到来,全球经济一体化进程的推进和网络技术的发展,人类迫切需要跨越信息沟通与交流中的语言障碍,显然人工翻译已经无法满足当前信息时代的需求,机器翻译就成为当前迫切需要解决的问题。信息时代的到来带来了信息爆炸和信息革命,网络和通信技术的腾飞为我们搭建了一个开放的平台。人类可以跨越文化的差异却无法逾越自然语言的鸿沟,机器翻译就是在这样一个背景下开拓其漫长的研究开发的道路,而这条道路远比我们想象的要艰难。 从机器翻译发展的历史来看,全球许多国家和研究机构投入了大量的时间、人力和财力,经历了希望,失望,再到希望的反复过程。上个世纪四五十年代,伴随计算机的诞生,机器翻译迈出了稚嫩的脚步,由于当时计算机技术方面的原因,起步不久便陷入了停滞的状态,六十年代中期开始进入了对自然语言的语法、语义和语用等基本问题的研究阶段。七八十年代以后,国际计算机语言学界出现了一批新的语法理论,机器翻译开始走向繁荣。九十年代以来,计算机硬件的速度、容量提高促进了机器翻译技术的进步,其主要理论可以分为两大类:理性主义(Empiricist)方法和经验主义(Rationalist)方法。前者又被称为基于规则(Rule-Based)的方法。该理论在机器翻译界一直占据主导地位。随着计算机硬件和机器翻译理论的不断提升,机器翻译应用终于也有了可喜的进展,一部分成果转化成了产品,被推向市场。近年来,随着统计理论、信息论、计算机语言学,语料库语言学等学科的深入,促进了机器翻译相关技术和基础资源的研发。 当前国内主要机器翻译产品如下: 系统名称 语言对应 翻译处理策略 应用国家和地区 研制开发单位 ICENT 汉英 基于规则,扩充上下文无关文法 中国和英语国家 湖南长沙国防科技大学 华建翻译IAT英汉个人版 汉英、英汉 基于规则,与类比推理和模糊匹配技术有机结合 中国大陆 华建机器翻译有限公司 华建网译中心 汉英、英汉、 俄汉、汉日、 日汉、德汉 基于规则,与类比推理和模糊匹配技术有机结合 中国大陆、香港 台湾、东南亚、 澳大利亚等 华建机器翻译有限公司 华建译通 英汉双向超智能版 汉英、英汉 (简繁体) 基于规则,与类比推理和模糊匹配技术有机结合 中国大陆 华建机器翻译有限公司 东方快车 汉英、英汉 用户词库优先,专业库,逻辑规则库,句法和词法规则库 内地、香港、台湾、新加坡 交大铭泰公司 雅信CATS 汉英、英汉 日、德、俄、西互译 预测术语提取,翻译记忆,词典提示,模糊匹配 中、英、台湾、香港、及各大语系国家 交大铭泰辅助翻译部 金山快译2002 汉英、英汉 (简繁体) 日汉(简繁体) 字典查找,辅以文法、句法分析 中国大陆 金山软件股份有限公司 迈创多语通英汉双向机器翻译系统 汉英、英汉双向自动翻译 总结自然语言一般表达规律实现多语互译 使用英语和汉语的国家与地区 发明人:孙建军 侯敏 日汉翻译引擎 日汉 基于规则 中国、日本 南京大学 厦门大学多语机器翻译系统XMMMT 英汉、汉英、 多引擎混合策略 中国 厦门大学语言技术中心 译星英汉机器翻译系统 汉英、英汉 语法规则转换,以有定子句文法为基础,结合功能合一文法,词汇功能文法 中国大陆、港台地区、美国、新加坡、澳大利亚 中软网络技术股份有限公司 智达汉英双向机器翻译系统 汉英、英汉 以语料库为基础,采用统计、规则方法以及基于实例的翻译技术 哈尔滨工业大学机器翻译研究室 二、正文 现阶段机器翻译代表性理论主要有经验主义(Rationalist)方法和理性主义(Empiricist)方法。以下就这两种方法分别论述。 1.经验主义方法 经验主义方法又称基于语料库(Corpus-Based)的方法,包括基于统计(Statistics-Based)基于实例(Example-Based)两种方法。其思路是通过对大量的自然语言进行概率运算,依据语言各要素间相似度来构造语言模型,从而达到翻译的目的。在实际翻译过程中,系统对源语料进行分析,之后在数据库中快速搜索,与模版进行模糊匹配,查找最大相似度结构,通过局部修饰成分的调整,实现自然语言的翻译转换。 经验主义方法的优点:第一,当源语料和数据库中的模版达到一定相似度的时候,翻译的正确率较高,比较贴近自然语言的表达习惯和语法规则。第二,由于计算量相对较小,在单位时间里,系统处理的数据量大,可有效提高翻译的速度,同时对计算机硬件的运算要求也不高。第三,用户通过操作数据库,可针对自身实际情况,自我学习,创建个性化策略,大幅度提升翻译效果。 经验主义方法的缺点:第一,当源语料和数据库中的模版无法实现匹配的时候,翻译的正确率将完全没有保证,系统的适应性较差。第二,当数据发生错误匹配的时候,翻译结果将缺乏可读性。第三,系统在各种自然语言之间的移植性较差,由于源数据和目标数据是一对一的关系,因此在开发其它语言系统的时候,参照性及借鉴性不突出。 综合上述特点,由于口语系统和专业系统通常语言结构简单,有一定规律可循,因此,经验主义方法对其比较有针对性。 2.理性主义方法 理性主义方法通常又称为基于规则(Rule-Based)的方法,可分为基于转换(Transfer-Based)和基于中间语言(Interlingua-Based)两种方法。此理论在机器翻译界一直占主导地位。其思路是由人工或机器辅助先构造供翻译使用的词语信息和句法语义规则库,通过知识表示,知识推理,经由分析、生成等步骤来进行机器翻译。 理性主义方法的优点:第一,系统的适应性强,当数据库空缺相关数据的时候,系统基于规则可自行生成,译文的可读性强。第二,系统紧凑,数据冗余度小,避免重复数据占用资源。第三,系统的移植性强,便于在一个相对成熟的系统上进行其它语言系统的二次开发,节约研究资源,降低开发成本。 理性主义方法的缺点:第一,由于此方法需要大量的分析和运算,翻译速度相比经验主义慢,因此对计算机硬件的要求较高,系统资源的开销也比较大。第二,系统扩展需要专业人员进一步开发,用户需要不断升级数据库,才能提高翻译质量。 综合上述特点,篇章和网络语言结构修饰成分多,变化复杂,理性主义方法比较适合这种通用型自然语言翻译系统。 结合以上两种方法的优缺点,为了能够有效提高机器翻译的质量,把二者集成在一起,形成优势互补,合理配置系统资源,在当前的实际应用中,的确可以显著提高专业翻译人员的工作效率。经过评测专家对“雅信CATS专业翻译平台”进行为期三个月以上的跟踪调查评估,人均单位成本下降2/3,知识库最高稳定增长率为112%。 无论是经验主义方法还是理性主义方法,在进行机器翻译的时候,都需要对源语言进行分析,分析的过程包括三个部分:词法分析、句法分析、语义分析。在此过程中存在着难以解决和暂时无法突破的瓶颈。下面就分析过程中的一些常见歧义进行简单论述。 首先是语法结构歧义。比较常见的有动宾型歧义结构,其语法结构为“动词+名词(1)+的+名词(2)”。此结构有三种生成,一种是“动词+名词(1)”组成动宾短语作为“名词(2)”的修饰成分,例如:阅览书籍的学生。另一种是“动词+名词(1)”组成偏正短语作为“名词(2)”的修饰成分,例如:出差人员的名单。再一种是“名词(1)+的+名词(2)”组成的短语做“动词”的宾语,例如:敲打电脑的键盘。还有一种比较常见的是介宾型歧义结构,其语法结构为“介词+名词(1)+的+名词(2)”。此结构有二种生成,一种是“介词+名词(1)”组成介词短语作为“名词(2)”的修饰成分,例如:对于事物的理解。另一种是“名词(1)+名词(2)”组成短语作为“介词”的宾语,例如:对于明天的会议。以上两种歧义结构在日常的语言现象中出现概率非常大,也是机器翻译消歧工作的一个难点。 其次是语义歧义。比较常见的有词义辨析,早期的词义消歧通常靠人手工编制规则,费时费力。九十年代以后,汉语词义消歧工作得到越来越多的关注。LAM(1997)利用《现汉》中的释义文本和《同义词词林》的词义类,对实词多义词进行词义消歧,平均正确率为45.5%。李涓子(1999)利用《同义词词林》和《人民日报》语料库进行多义词消歧,正确率为52.13%。如果根据词和词之间的组合关系来分化多义词,便能够有效解决这个问题。但对于计算机来说,如何进行词义搭配的描述就成了需要解决的问题。另外,专用名词的识别问题也是影响翻译质量的一个原因。在自然语言中,语句中经常出现人名、地名和企业组织名称,如果系统无法识别的话,即便语句的其它成分都正确地生成,整个译文依旧不具备可读性,甚至于让人不知所云。要是把这些专用名词均制作成词组,倒是可以解决此问题,但请试想一下,若把国内的、国外的、姓名、地址、组织机构名称一一列举,其数据的庞大足以使个人PC系统瘫痪。而要是要求所有的源语料对专用名词进行标注(比如加“下划线”),显然又不太现实。因此,一个优秀的机器翻译系统能正确识别专用名词,那么它的翻译质量便有了一定的保障。再有,语言外部知识对机器翻译的结果同样有着巨大的影响。例如:中阿两国在此基础上达成相互谅解。源语料的“中”是特指中国,而“阿”有可能是阿富汗、阿拉伯、阿尔及利亚、阿曼、阿尔巴尼亚、阿拉伯联合酋长国、阿根廷等多种可能。所以,为了能够保证译文质量,系统还应该具备一定的常识,这就要求研发人员必须将机器翻译自身和外部知识库进行无缝连接,消除译文的背景歧义。还有一个制约翻译效果的因素就是源语料的不规范性,这种情况在网络中表现格外突出。比如形容词名用,名词动用,违规缩略语,造词,造语以及其他各种网络色彩浓重的语言。这些都给机器翻译系统的工作带来极大的麻烦。 三、结论 当前的机器翻译主要还是以受限辅助翻译系统为主,相对比较成熟的产品均是经验主义方法和理性主义方法的有机结合。即人译机助或机译人助,整个翻译过程是通过人机交互的方式进行,机器翻译还将长期作为专业翻译工作者的得力助手,其前景充满着光明,并带给我们巨大的经济效益和社会效益。因此,有必要一方面做好系统的提升与完善,一方面探索机器翻译与各领域技术的融合。在未来的一段时期内,机器翻译依旧会基本保持现状,并在此基础上翻译质量会有一定幅度提升,但恐怕很难有质的飞跃。机器翻译的未来将寄希望于硬件和软件的技术革新和更多外围学科在计算机机器翻译领域的应用。相信有一天,机器翻译的课题一定会被彻底突破,随着此技术和语音识别、语音合成技术的捆绑集成,人类的沟通与交流会变得更加便利与快捷。 四、结束语 通过近一个月的调查与研究,我深刻感受到科技发展带给人类社会的便利,以及新学科、新技术所蕴藏着的无限潜力,只要勇于探索,勤奋追求,知识必将造福于人类。 在此篇论文的写作过程中,得到了中国科学院计算机语言信息工程研究中心黄静副研究员和吴世锋副研究员的大力帮助,在这里深表感谢! 参考文献 [1]刘倬.机器翻译的发展和突破.机器翻译研究进展 [2]何站涛,韩兆强,闫栗丽.机器翻译质量的研究与探讨.机器翻译研究进展 [3]赵红梅,陈肇雄,黄河燕.”X的NP”类歧义句式及基于SC文法的消歧处理.智能型机器翻译研究论文集