zoukankan      html  css  js  c++  java
  • RabbitMQ(消息队列)私人学习笔记

    俗话说“好记性不如烂笔头”,编程的海洋如此的浩大,养成做笔记的习惯是成功的一步!


    此笔记主要是rabbitMQ3.6.6版本的笔记,并且笔记都是博主自己一字一字编写和记录,有错误的地方欢迎大家指正。




    一、基础知识:
    1、rabbitMQ是使用Erlang语言编写的高效率的开源消息队列框架,能持久化消息队列,保证消息队列的不丢失,
      同时支持集群功能。官方网址为:http://www.rabbitmq.com/。因为Erlang语言并发能力非常强,对分布式也有
      非常好的支持,故也使得rabbitMQ框架的并发性能非常的出色,分布式功能齐全。
      
      
      
      提示:
    MQ全称 message queue 消息队列,在java中称为JMS(java message service),其主要使用场景为 需要做一些耗时的操作,
    而服务端又无须将操作结果返回给客户端的情形,通过MQ来异步处理这些耗时操作,从而节省服务器的请求响应时间。
    例如:邮件发送,数据统计等操作。



    2、rabbitMQ的一些特性:
    (1)rabbitMQ支持将消息队列的数据写入文件来保存,而持久化数据,保证消息的不丢失。

    (2)rabbitMQ是支持事务的,但是会影响性能,使得服务端与客户端同步。

    (3)rabbitMQ是基于AMQP(Advanced Message Queuing Protocol)高级消息队列协议实现MQ功能的。

    (4)rabbitMQ提供了Confirm确认机制,来异步的确定消息的发送是否成功,相当于异步的事务实现机制。

    (5)rabbitMQ不支持延时消息的功能,需要自己定制,可使用rabbitMQ的DLX过期机制来实现或者使用
      rabbitmq_delayed_message_exchange插件。必要时可以改用阿里的rocketMQ,此MQ本身就支持延时消息的功能。

    (6)rabbitMQ的exchange交换类型有几种常用的,direct (point-to-point), topic (publish-subscribe) 、
    fanout (multicast)和headers(publish-subscribe)模式。



    3、rabbitMQ的exchange和routing key的关系需要理清楚,exchange是一种交换机制,表示相当于一种交换策略,比如需要单点发送,
      还是群体广播。而routing key则是确定消息最终投递到哪个队列的判断依据,例如需要投递给 A 路由标识的队列,A就是路由的判断
      依据。

    注意:不同exchange之间是无法进行相互消费和发布的,即使exchange模式相同也不允许,必须要求是同一个exchange
     交换机制(通过名称来做标识),即exchange相同则视为同一个exchange。



    4、exchange模式一定需要注意:
    direct    消息只会投递一次,即使有多个符合routing key的消费者,也只会给其中一个消费者消费。
    fanout  消息会投递多次,只要是同一个exchange都会投递。
    topic  消息会投递多次,只要有符合routing key的,都会投递。
    headers  消息会投递多次,只要是符合headers头的key,都会投递。x-match有all、any两种。



    二、Linux(CentOS7)安装方式:
    1、rabbitMQ依赖于erlang语言,需要先安装和编译erlang语言。
    (1)安装erlang语言依赖的环境,执行命令:
    yum -y install make gcc gcc-c++ kernel-devel m4 ncurses-devel openssl-devel unixODBC-devel



    (2)通过wget来下载erlang安装包,执行命令:
    wget http://erlang.org/download/otp_src_19.2.tar.gz -P /user/software

    -P /user/software 是表示指定下载目录,如果不指定,默认下载目录为当前路径。




    (3)进入到解压后的otp_src_19.2目录文件夹,配置编译,执行命令:
    ./configure --prefix=/usr/local/erlang --with-ssl -enable-threads -enable-smmp-support -enable-kernel-poll --enable-hipe --without-javac



    (4)配置完编译后,则开始编译和安装(编译时间可能会比较长),执行命令:
    make && make install



    (5)测试erlang是否安装成功,进入编译后的erlang目录 /usr/local/erlang,执行命令: ./bin/erl

    进入到erl语言环境后,输入
    1+1.
    会有正确的计算结果,输入halt(). 可以退出erl语言环境。




    (6)配置erlang的环境变量,修改profile文件,执行命令: vi /etc/profile
    然后增加下面的配置内容:
    #erlang env config
    export ERLANG_HOME=/usr/local/erlang
    export PATH=$PATH:$ERLANG_HOME/bin

    修改完配置后,让系统重新读取profile的配置,执行命令: source /etc/profile







    2、安装rabbitMQ Server。
    (1)上传二进制的rabbitmq-server-generic-unix-3.6.6.tar.xz包到linux系统下。

    也可以通过wget在线下载,执行命令:
    wget http://www.rabbitmq.com/releases/rabbitmq-server/v3.6.5/rabbitmq-server-generic-unix-3.6.6.tar.xz


    (2)解压和拆包:
    xz -d rabbitmq-server-generic-unix-3.6.6.tar.xz
    tar -xvf rabbitmq-server-generic-unix-3.6.6.tar


    (3)进入解压后的目录/usr/user/rabbitmq_server-3.6.6 ,然后进入到sbin目录下,执行基本的操作,
    后台方式启动rabbitMQ服务:./rabbitmq-server -detached   提示:-detached就是表示后台形式运行。
    查看rabbitMQ的状态:./rabbitmqctl status
    停止rabbitMQ的服务:./rabbitmqctl stop
    列出所有的队列名称: ./rabbitmqctl list_queues



    (4)rabbitmq-server-generic-unix-3.6.6是通用的linux版本,配置文件是没有创建的,可以在指定目录下手动创建。
    默认的配置文件在   ${SYS_PREFIX}/etc/rabbitmq/rabbitmq
    默认的日志文件在   ${SYS_PREFIX}/var/log/rabbitmq
    默认的mnesia数据库的路径${SYS_PREFIX}/var/lib/rabbitmq/mnesia
    默认插件路径 ${SYS_PREFIX}/etc/rabbitmq/enabled_plugins
     
    ${SYS_PREFIX}表示当前rabbitMQ的所属目录,具体可以查看rabbitmq-defaults脚本。








    三、使用笔记:
    1、rabbitMQ有提供web网页形式的管理插件。首先必须保证rabbitMQ的服务已启动,然后进入到rabbitMQ安装目录/sbin目录下,
    执行命令:  ./rabbitmq-plugins enable rabbitmq_management

    插件启动成功后,默认端口为15672,即可通过浏览器访问:http://localhost:15672  

    打开网页后需要用户名和密码登录,rabbitMQ默认创建了guest用户(密码也为guest),并且只运行本地访问,
    如果需要远程访问,可以通过新建用户,具体看(2)创建用户的方式。

    如果需要修改插件的默认端口,可以在rabbitmq.config中添加如下配置:
    {rabbitmq_management ,[{listener,[{port,15673}]}]}




    2、新建rabbitMQ用户,并运行远程访问。
    (1)创建admin用户,密码为123456:sh rabbitmqctl add_user admin 123456

    (2)添加权限(后面分别是配置、写、读权限):sh rabbitmqctl set_permissions -p "/" admin ".*" ".*" ".*"

    (3)更改用户的角色(必须为administrator角色,否则无法远程访问):sh rabbitmqctl set_user_tags admin administrator




    3、rabbitMQ配置, 默认的配置文件在${SYS_PREFIX}/etc/rabbitmq/rabbitmq/rabbitmq.config,可能不存在该配置文件,可以自己
    手动创建。rabbitMQ的配置是使用erlang的规范来配置的,与普通的配置形式不同,必须严格按照erlang规范来配置。此目录下
    有rabbitmq.config文件,可以参照下大概的配置语法。

    rabbitMQ环境配置, 默认的配置文件在${SYS_PREFIX}/etc/rabbitmq/rabbitmq/rabbitmq-env.conf,可能不存在改配置文件,
    可以自己手动创建。rabbitmq-env.conf的配置是普通格式,一个配置命令一行即可。



    4、rabbitMQ的默认使用端口是5672,可以在rabbitmq.config配置文件中修改tcp端口,或者直接在rabbitmq-env.conf环境配置文件中
    修改默认端口。如果rabbitmq.config和rabbitmq-env.conf配置文件中都有修改端口,则以rabbitmq-env.conf为准。


    5、rabbitmqctl常用的参数组合:
      add_user <UserName> <Password>        增加用户
      delete_user <UserName>删除用户
      change_password <UserName> <NewPassword>更改密码
      list_users 列出所有用户
      add_vhost <VHostPath>增加虚拟主机
      delete_vhost <VHostPath>删除虚拟主机
      list_vhostsset_permissions [-p <VHostPath>] <UserName> <Regexp> <Regexp> <Regexp>列出虚拟主机的权限
      clear_permissions [-p <VHostPath>] <UserName>清除权限
      list_permissions [-p <VHostPath>]列出权限
      list_user_permissions <UserName>列出用户权限
      list_queues [-p <VHostPath>] [<QueueInfoItem> ...]列出所有的队列名称
      list_exchanges [-p <VHostPath>] [<ExchangeInfoItem> ...]列出所有可用的exchange
      list_bindings [-p <VHostPath>]列出队列与exchange绑定情况
    list_connections [<ConnectionInfoItem> ...]列出所有的客户端连接
    cluster_status查看集群的状态。
    list_policies [-p <vhost>] 列出所有的策略(策略是用于队列镜像使用的)


    6、rabbitMQ的服务端默认提供了一些exchange机制,客户端可以直接使用,也可以自己额外声明。
    交换名称 交换模式
    amq.rabbitmq.trace      topic
    amq.direct      direct
    amq.topic       topic
    amq.headers    headers
    amq.fanout     fanout
    amq.match     headers
    direct //此类型没有名称,客户端可用""来调用此模式。
    amq.rabbitmq.log       topic



    7、使用topic模式时,有两个特殊符号可以使用,其中 * 表示一个任意单词  #表示0个或多个任意单词,
      每个单词之间是采用.来分割的。


    8、当使用direct交换模式时,如果此时有多个消费者是同时监听同一个队列,那么服务端会按顺序轮流投递给不同的消费者,
    例如第一条消息给A消费者,那么第二条消息将给B消费者,依次循环投递。
      




    四、java使用rabbitMQ的笔记:
    1、rabbitMQ的java client有两个版本,一个是rabbitMQ java client官方按照自己规范编写的,另外一个是rabbitMQ JMS client按照
      java的jms规范编写的,相应的jar包都在jar_lib目录下。
      
      
    2、此处记录的默认都是基于rabbitMQ java client的使用,使用此客户端,需要引入jar_lib/java_client目录下的包和depend_jar依赖
      包。
      
      

    3、java client的基本核心代码如下:
    ConnectionFactory factory = new ConnectionFactory();

    //可以直接通过uri地址来设置主机和密码那些
    factory.setUri("amqp://admin:123456@10.17.2.82:5672");

    //设置每个connection的channel数量。注意,channel不能过大,因为channel在传输数据时,也是独占connection连接的。
    factory.setRequestedChannelMax(10);

    Connection connection = factory.newConnection();
    Channel channel = connection.createChannel();

     
    4、注意rabbitMQ的连接是基于channel通道的,channel是基于socket的connection连接上细分出来的,使用channel是为了重复使用
    connection连接,同一个connection连接下通过数字来标识来区分不同的channel,需要注意的是channel在传输数据时,也是独
    占connection连接的,故在同一个connection上不能创建大量的channel通道。目前官方提供的jar包没有连接池的使用概念,
    需要自己去定制connection和channel的连接池。
      
      
    5、java client支持传统的socket连接和非阻塞的nio socket连接,默认使用传统的socket连接。
      代码中通过ConnectionFactory factory = new ConnectionFactory(); factory.useNio();方法来改为使用nio连接。
      
      
    6、java client的连接如果不主动关闭,是会通过心跳heartbeat来保持一直保持长连接的,默认是60秒发送一次心跳,可以在通过
       代码设置心跳时间 factory.setRequestedHeartbeat(60);   对于消息提供者,发送完数据后需要手动释放资源,让tcp连接断开,
    而不是一直占用资源。



    7、java client的连接是长连接,并且会通过AMQConnection.MainLoop内部类通过新开线程一直循环监听读取消息,消费端的使用就不
      进行关闭,rabbitMQ的队列一有消息会马上推送给客户端,客户端马上进行消费处理。





    8、在java client的客户端,可以重复的声明queue和exchange,在服务端如果本身已存在queue或exchange则进行更新,否则会新增。
      如果是更新,服务端只是对新参数值与原参数值做比较,如果发现不一致会抛异常。故需要注意重复声明queue和exchange的情况,
      不能随意改动参数值。



    9、如果客户度设置ack为非自动回复,假如hellow消息给A客户端消费后,一直不回复ack给服务端,只要A客户端一直与服务端保持连接,
      那么服务端就不会将hellow消息投递给其他消费端的,除非A客户端断开连接,才重新将hellow消息投递给其他消费者。
      



    五、rabbitMQ的集群:
    1、使用集群后,rabbitMQ的客户端(例如java客户端)可以连接集群中的任意一个节点,本身客户端也不支持同时配置多个IP地址,即同
    一时刻只能是连接单个IP。rabbitMQ客户端可以监听关闭信息(例如java客户端可以实现ShutdownListener接口),当客户端发现关闭
    后,可以重新连接到其他的节点上。

    温馨提示:
    官方是不推荐使用上面的方案的,因为集群的rabbitMQ服务器有改动的话,需要客户端的代码重新进行修改来适应集群服务器的
    变化。官方推荐使用其他第三方技术来解决集群变动而不影响客户端的问题,例如配合使用普通的TCP负载均衡器HAProxy技术。




    2、官方网站的集群有为两种形式:
    形式一普通集群(默认形式):队列结构和交换模式在所有节点中都同步,但是队列里的消息数据不会复制到其他节点,仅仅存在原
     节点中,假设msg1消息存在节点A中,客户端通过节点B来访问msg1消息,那么B节点做为消费者向节点
     A中拉取 msg1消息,然后节点B再将msg1消息推送给客户端。如果节点A宕机了,那么就会导致msg1消
     息无法获取。特别提醒,队列由哪个节点首次声明,那么队列里的消息就存在那个节点下。假如A节点
     声明了queueA,现在要将msg1消息存入queueA队列,即使客户端通过节点B来存放,消息最终还是保持
     到节点A中,因为节点A是声明queueA队列的节点者。
     
     
     
    形式二镜像集群(官方的HA方案):不仅队列结构和交换模式在所有节点中都同步,连队列里的消息数据都会拷贝到其他节点上,这
     样就即使原消息节点宕机了,也不影响消息的消费,可以直接从其他节点中读取到此信息,实现
     了高可用性。
     
     

    提示:普通集群的优点是节省了磁盘空间和宽度,性能比较好,但是存在单点故障,消息服务点出口固定导致会出现瓶颈。镜像集群
     的优点是实现了高可用性,保证消息的不丢失和避免单点故障,但是会耗费大量的空间和宽带。
     
     普通集群和镜像集群各有优势,可以混合使用,根据队列名称来做选择策略,消息量大的队列可以使用普通集群的模式,消息
     量小并且要求高可靠性的队列,可以使用镜像模式。


          
    3、rabbitMQ的集群可以动态构成,即开始运行的时候所有节点都是以单机的形式运行,随后可以加入集群中,加入集群后也可以脱离集
     群再次成为单机。动态构建集群是通过 rabbitmqctl 命令来实现的。
     
     


    4、rabbitMQ的集群官方推荐是基于LAN局域网来实现的,因为集群后节点通信非常频繁,故不推荐使用WAN广域网的形式。如果有需求
       需要使用到WAN广域网的形式,可以借助shovel或者Federation插件来更好的实现。




    5、单台linux机的集群步骤:
    (1)复制多份rabbitmq_server-3.6.6文件,用于同时启动多个服务节点。

    (2)修改rabbitmq_server-3.6.6/etc/rabbitmq/rabbitmq-env.conf环境文件,必须修改节点名称和默认端口号。
    例如名称可以改为rabbit1、rabbit2、rabbit3,端口号为5672,5673,5674。如果有开启rabbitMQ的插件,
    还需要注意插件的端口要修改为不同,例如web管理插件{rabbitmq_management ,[{listener,[{port,15674}]}]}

    (3)将节点rabbit1、rabbit2、rabbit3组成一个集群,操作rabbit2、rabbit3加入到rabbit1中。

    操作rabbit2节点:
    sh rabbitmqctl stop_app
    sh rabbitmqctl join_cluster rabbit1@$HOSTNAME
    sh rabbitmqctl start_app

    操作rabbit3节点(声明为内存节点):
    sh rabbitmqctl stop_app
    sh rabbitmqctl join_cluster --ram rabbit1@$HOSTNAME
    sh rabbitmqctl start_app


    注意@$HOSTNAME是表示当前系统的主机名称,根据不同名称自行进行修改替换。
    加入集群后,可以通过任意节点的sh rabbitmqctl cluster_status来查看集群状态。


    (4)通过策略配置镜像队列(可根据需要自行修改)。

    设置hellow开头的队列名称,所有都进行镜像:
    sh rabbitmqctl set_policy hello-all "^hello." '{"ha-mode":"all","ha-sync-mode":"manual"}'


    设置当节点数量小于等于2时,将two.开头的队列名称镜像到所有存活的节点上。
    sh rabbitmqctl set_policy ha-two "^two." '{"ha-mode":"exactly","ha-params":2,"ha-sync-mode":"automatic"}'


    设置nodes.开通的队列名称,镜像到rabbit@nodeA和rabbit@nodeB节点上。
    rabbitmqctl set_policy ha-nodes "^nodes."  '{"ha-mode":"nodes","ha-params":["rabbit@nodeA", "rabbit@nodeB"]}'


    (5)在有些情况下,队列已经存在,并且已有消息,而策略后面在添加的,并且同步模式为manual的情况下,是不会镜像已经存在的
      队列消息的,可以通过手动同步队列消息,执行命令 rabbitmqctl sync_queue <que_name>
      
      



    6、rabbitMQ在linux下集群的注意事项:
    (1)当全部的节点都关闭后,启动节点时,建议最后关闭的那个节点先启动,否则可能出现其他节点会出现启动失败的情况。

    (2)集群时如果不是在同一台机器,必要要注意所有集群的linux机器的erlang的cookie必须要一致,默认在
      $HOME/.erlang.cookie目录下,可以直接将此cookie文件拷贝到其他机器来确保cookie的相同。
      
    (3)集群时必须要注意每个节点的名称,要求必须整个集群唯一,并且每台机器要设置好所有节点名称与ip的映射关系,
    在/etc/hosts下进行添加映射关系。

    (4)如果是排他队列(exclusive为true),即俗称临时队列,如果不会镜像到其他节点的,即使他是可持久话并且有符合的策略。
    持久化功能在排他队列中无效的,连接一断开就会马上被清除。



    7、rabbitMQ集群是不具备负载均衡的,可以借助第三方负载均衡器来实现此功能,使得使用的客户端只需要写入单个代理IP即可,
      不仅达到了负载均衡的目的,也使得rabbitMQ集群节点的修改而无需变动客户端代码,只需修改服务端的配置即可。比较常用
      的是HAProxy代理作为rabbitMQ的负载均衡器。具体的使用方式:

    (1)必须先禁止selinux
    vi /etc/selinux/config


    #SELINUX=enforcing #注释掉
    #SELINUXTYPE=targeted #注释掉
    SELINUX=disabled #增加


    :wq!  #保存退出
    setenforce 0 #使配置立即生效


    (2)如果有开启防火墙,则需要添加配置防火墙。
    vi /etc/sysconfig/iptables  #编辑


    -A RH-Firewall-1-INPUT -d 224.0.0.18 -j ACCEPT  #允许组播地址通信
    -A RH-Firewall-1-INPUT -p    vrrp    -j ACCEPT  #允许VRRP(虚拟路由器冗余协议)通信
    -A RH-Firewall-1-INPUT -m state --state NEW -m tcp -p tcp --dport 80 -j ACCEPT  #允许80端口通过防火墙
    -A INPUT -p tcp -m state --state NEW -m tcp --dport 1080 -j ACCEPT #运行1080口,用于haproxy的web管理界面接口。

    :wq! #保存退出
    /etc/init.d/iptables restart #重启防火墙使配置生效


    (3)安装HAProxy软件,执行在线安装命令  yum install haproxy


    (4)安装完毕后,可以执行命令  haproxy -v 来查看版本号确定是否安装成功。
      
      
    (5)配置/etc/haproxy/haproxy.cfg 文件(具体可以参考本文件下的haproxy.cfg文件)
    listen mystats#用于开启haproxy的web管理界面,浏览器访问地址为http://ip:1080/haproxy,绿色栏表示可以,红色栏表示不可用。
    bind 0.0.0.0:1080           #监听端口 
    stats refresh 30s           #统计页面自动刷新时间
    stats uri /haproxy          #管理页面的url
    stats realm Haproxy Manager #统计页面密码框上提示文本
    stats auth admin:admin      #统计页面用户名和密码设置
    #stats hide-version         #隐藏统计页面上HAProxy的版本信息


    listen rabbitmq_cluster #添加rabbitMQ的集群代理
    #设置监听的端口为5671,注意如果rabbitMQ和haproxy同一台机器,那么5672被rabbitMQ使用的话则不可以再给haproxy监听。
    bind 0.0.0.0:5671
    #配置tcp
    option tcplog
    mode tcp
    #配置超时时间 单位为毫秒
    timeout client  3000
    timeout server  3000
    option          clitcpka
    #负载均衡规则 使用轮询方式
    balance roundrobin 
    #配置rabbitMQ的节点 check inter 5000 是检测心跳频率5秒,rise 2是2次正确认为服务器可用,fall 2是3次失败认为服务器不可用
    server rabbit1 127.0.0.1:5672 check inter 5000 rise 2 fall 2
    server rabbit2 127.0.0.1:5673 check inter 5000 rise 2 fall 2
    server rabbit3 127.0.0.1:5674 check inter 5000 rise 2 fall 2


    (5)修改完配置后,重新启动haproxy的服务。常用的命令如下:
    service haproxy start启动服务
    service haproxy restart重新启动服务
    service haproxy stop关闭服务
    service haproxy status查看服务状态



    (6)最后在客户端直接返回haproxy的服务器地址和端口即可,例如访问rabbitMQ,直接访问 
    amqp://admin:123456@ip:5671即可,haproxy会自动分发请求给rabbitMQ节点。





    注意:使用haproxy代理做rabbitMQ的负载均衡器后,rabbitMQ不准使用临时队列(即排他队列exclusive为true)的队列,否则会出现
     消息丢失的问题。原因是,临时队列在当前声明此队列的连接断开后,就会马上删除,而haproxy是通过算法随机转发请求到
     rabbitMQ的,会出现haproxy对rabbitMQ频繁连接与断开的情形,导致临时队列被删除了。




    /**************************************************附加******************************************************/
    附加一、AMQP协议的基本概念:
    Broker: 接收和分发消息的应用,RabbitMQ Server就是Message Broker。

    Virtual host: 出于多租户和安全因素设计的,把AMQP的基本组件划分到一个虚拟的分组中,类似于网络中的namespace概念。
    当多个不同的用户使用同一个RabbitMQ server提供的服务时,可以划分出多个vhost,每个用户在自己的vhost
    创建exchange/queue等。

    Connection: publisher/consumer和broker之间的TCP连接。断开连接的操作只会在client端进行,Broker不会断开连接,
    除非出现网络故障或broker服务出现问题。

    Channel: 如果每一次访问RabbitMQ都建立一个Connection,在消息量大的时候建立TCP Connection的开销将是巨大的,
    效率也较低。Channel是在connection内部建立的逻辑连接,如果应用程序支持多线程,通常每个thread
    创建单独的channel进行通讯,AMQP method包含了channel id帮助客户端和message broker识别channel,
    所以channel之间是完全隔离的。Channel作为轻量级的Connection极大减少了操作系统建立TCP connection的开销。

    Exchange: message到达broker的第一站,根据分发规则,匹配查询表中的routing key,分发消息到queue中去。
     常用的类型有:direct (point-to-point), topic (publish-subscribe) and fanout (multicast)。

    Queue: 消息最终被送到这里等待consumer取走。一个message可以被同时拷贝到多个queue中。

    Binding: exchange和queue之间的虚拟连接,binding中可以包含routing key。Binding信息被保存到exchange中的查询表中,
    用于message的分发依据。









  • 相关阅读:
    严重: Parse error in application web.xml file at jndi:/localhost/ipws/WEBINF/web.xml java.lang.NoSuchMethodException: org.apache.catalina.deploy.WebXml
    Failed to install .apk on device 'emulator5554': timeout解决方法
    java.lang.NoClassDefFoundError:org.jsoup.Jsoup
    Conversion to Dalvik format failed: Unable to execute dex:解决方法
    apache Digest: generating secret for digest authentication ...
    Description Resource Path Location Type Project has no default.properties file! Edit the project properties to set one.
    android service随机自启动
    MVC3 安装部署
    EF 4.3 CodeBased 数据迁移演练
    SQL Server 2008开启sa账户
  • 原文地址:https://www.cnblogs.com/catgwj/p/7492825.html
Copyright © 2011-2022 走看看