
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010046908/article/details/85260717 上一篇我们学习了一下consul在SpringCloud中的使用。今天要给大家介绍的阿里巴巴中间件团队出品的Nacos来作为新一代的服务管理中间件。 首先学习Nacos之前,我们应该看看Nacos的官网,对它有一个初步的认识。 1. Nacos 官网 (https://nacos.io) 2 Nacos简介 Nacos 致力于帮助您发现、配置和管理微服务。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据及流量管理。 Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。 Nacos 是构建以“服务”为中心的现代应用架构 (例如微服务范式、云原生范式) 的服务基础设施。 3.Nocos基本架构及概念 服务 (Service) 服务是指一个或一组软件功能(例如特定信息的检索或一组操作的执行),其目的是不同的客户端可以为不同的目的重用(例如通过跨进程的网络调用)。Nacos 支持主流的服务生态,如 Kubernetes Service、gRPC|Dubbo RPC Service 或者 Spring Cloud RESTful Service. 服务注册中心 (Service Registry) 服务注册中心,它是服务,其实例及元数据的数据库。服务实例在启动时注册到服务注册表,并在关闭时注销。服务和路由器的客户端查询服务注册表以查找服务的可用实例。服务注册中心可能会调用服务实例的健康检查 API 来验证它是否能够处理请求。 服务元数据 (Service Metadata) 服务元数据是指包括服务端点(endpoints)、服务标签、服务版本号、服务实例权重、路由规则、安全策略等描述服务的数据 服务提供方 (Service Provider) 是指提供可复用和可调用服务的应用方 服务消费方 (Service Consumer) 是指会发起对某个服务调用的应用方 配置 (Configuration) 在系统开发过程中通常会将一些需要变更的参数、变量等从代码中分离出来独立管理,以独立的配置文件的形式存在。目的是让静态的系统工件或者交付物(如 WAR,JAR 包等)更好地和实际的物理运行环境进行适配。配置管理一般包含在系统部署的过程中,由系统管理员或者运维人员完成这个步骤。配置变更是调整系统运行时的行为的有效手段之一。 配置管理 (Configuration Management) 在数据中心中,系统中所有配置的编辑、存储、分发、变更管理、历史版本管理、变更审计等所有与配置相关的活动统称为配置管理。 名字服务 (Naming Service) 提供分布式系统中所有对象(Object)、实体(Entity)的“名字”到关联的元数据之间的映射管理服务,例如 ServiceName -> Endpoints Info, Distributed Lock Name -> Lock Owner/Status Info, DNS Domain Name -> IP List, 服务发现和 DNS 就是名字服务的2大场景。 配置服务 (Configuration Service) 在服务或者应用运行过程中,提供动态配置或者元数据以及配置管理的服务提供者 4. Nocos 安装与启动 4.1.预备环境准备 Nacos 依赖 Java 环境来运行。如果您是从代码开始构建并运行Nacos,还需要为此配置 Maven环境,请确保是在以下版本环境中安装使用: 64 bit OS,支持 Linux/Unix/Mac/Windows,推荐选用 Linux/Unix/Mac。 64 bit JDK 1.8+;下载 & 配置。 Maven 3.2.x+;下载 & 配置。 4.2.下载源码或者安装包 你可以通过源码和发行包两种方式来获取 Nacos。 从 Github 上下载源码方式 git clone https://github.com/alibaba/nacos.git cd nacos/ mvn -Prelease-nacos clean install -U ls -al distribution/target/ // change the $version to your actual path cd distribution/target/nacos-server-$version/nacos/bin 下载编译后压缩包方式 您可以从 最新稳定版本 下载 nacos-server-$version.zip 包。 unzip nacos-server-$version.zip 或者 tar -xvf nacos-server-$version.tar.gz cd nacos/bin 4.3.启动服务器 Linux/Unix/Mac 启动命令(standalone代表着单机模式运行,非集群模式): sh startup.sh -m standalone Windows 启动命令: cmd startup.cmd 或者双击startup.cmd运行文件。 启动成功之后,在浏览器打开 http://localhost:8848/nacos/ 这样的一个控制台就启动成功了。 注意:我们今天主要是要学习nacos作为服务注册中心的案例。 5. 服务注册与发现 5.1服务注册 curl -X PUT 'http://127.0.0.1:8848/nacos/v1/ns/instance?serviceName=nacos.naming.serviceName&ip=20.18.7.10&port=8080' 5.2服务发现 curl -X GET 'http://127.0.0.1:8848/nacos/v1/ns/instances?serviceName=nacos.naming.serviceName' 6. Nacos 服务提供者 6.1创建一个项目:spring-cloud-nacos-provider 引入依赖 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.1.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.lidong</groupId> <artifactId>spring-cloud-nacos-producer</artifactId> <version>1.0.0</version> <name>spring-cloud-nacos-producer</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> <spring-cloud.version>Greenwich.RC2</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> <version>0.2.1.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> <repositories> <repository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/milestone</url> </repository> </repositories> </project> 对配置文件做一个简单的介绍,我们使用的是最新版的springboot2.1.1,springcloud.Greenwich.RC2版本。 其中: spring-boot-starter-actuator 健康检查依赖于此包。 spring-cloud-starter-alibaba-nacos-discovery Spring Cloud nacos 的服务发现支持。 6.2 提供者添加配置(application.yml) server: port: 9005 #提供者的端口 spring: application: name: spring-cloud-nacos-producer cloud: nacos: discovery: server-addr: 127.0.0.1:8848 management: endpoints: web: exposure: include: '*' nacos的地址和端口号默认是127.0.0.1:8848 ,如果没有配置hosts,默认的地址localhost,nacos服务会占用8848端口 server.port :9005 服务的提供者的端口spring.application.name 是指注册到 nacos 的服务名称,后期客户端会根据这个名称来进行服务调用。 spring.application.cloud.nacos.discovery.server-addr: 127.0.0.1:8848 6.3 修改启动类 添加 @EnableDiscoveryClient 注解,开启服务发现支持。 package com.lidong.provider; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; /** * 开启服务发现 */ @EnableDiscoveryClient @SpringBootApplication public class SpringCloudLidongProviderApplication { public static void main(String[] args) { SpringApplication.run(SpringCloudLidongProviderApplication.class, args); } } 6.4新建服务 新建 NacosProducerController,提供 sayHello 接口, 返回一个hello—>字符串。 package com.lidong.provider.service; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * 创建服务 */ @RestController public class NacosProducerController { @Value("${server.port}") private Integer port; /** * 服务接口 * @param name * @return */ @RequestMapping("/hello") public String sayHello(@RequestParam("name")String name) { return "hello ---> "+name+" port -->"+port; } } 启动项目: Wed Dec 26 11:55:40 CST 2018 sun.misc.Launcher$AppClassLoader@18b4aac2 JM.Log:INFO Init JM logger with Slf4jLoggerFactory success, sun.misc.Launcher$AppClassLoader@18b4aac2 Wed Dec 26 11:55:40 CST 2018 sun.misc.Launcher$AppClassLoader@18b4aac2 JM.Log:INFO Log root path: C:\Users\vip\logs\ Wed Dec 26 11:55:40 CST 2018 sun.misc.Launcher$AppClassLoader@18b4aac2 JM.Log:INFO Set nacos log path: C:\Users\vip\logs\nacos 2018-12-26 11:55:40.842 INFO 22252 --- [ main] o.s.c.a.n.registry.NacosServiceRegistry : nacos registry, spring-cloud-nacos-producer 192.168.10.116:9005 register finished 服务提供者发布成功。 这时候,我们在控制台会发现服务列表中有一个名字为spring-cloud-nacos-producer的服务 点击详情会发现服务的详细信息 7. Nacos 服务消费者 7.1创建一个项目:spring-cloud-nacos-consumer 引入依赖 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.1.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.lidong</groupId> <artifactId>spring-cloud-nacos--consumer</artifactId> <version>0.0.1-SNAPSHOT</version> <name>spring-cloud-nacos--consumer</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> <spring-cloud.version>Greenwich.RC2</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> <version>0.2.1.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-ribbon</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> <repositories> <repository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/milestone</url> </repository> </repositories> </project> 7.2 消费者添加配置(application.yml) server: port: 9006 #提供者的端口 spring: application: name: spring-cloud-nacos-consumer cloud: nacos: discovery: server-addr: 127.0.0.1:8848 management: endpoints: web: exposure: include: '*' nacos的地址和端口号默认是 127.0.0.1:8848 ,如果没有配置hosts,默认的地址localhost,nacos服务会占用8848接口 server.port :9006 服务的消费者的端口 spring.application.cloud.nacos.discovery.server-addr: 127.0.0.1:8848 7.3配置启动类 package com.lidong.consumer; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.context.annotation.Bean; import org.springframework.web.client.RestTemplate; @SpringBootApplication @EnableDiscoveryClient public class SpringCloudNacosConsumerApplication { @Autowired private RestTemplateBuilder builder; @Bean @LoadBalanced // 添加负载均衡支持,很简单,只需要在RestTemplate上添加@LoadBalanced注解,那么RestTemplate即具有负载均衡的功能,如果不加@LoadBalanced注解的话,会报java.net.UnknownHostException:springboot-h2异常,此时无法通过注册到Nacos Server上的服务名来调用服务,因为RestTemplate是无法从服务名映射到ip:port的,映射的功能是由LoadBalancerClient来实现的。 public RestTemplate restTemplate() { return builder.build(); } public static void main(String[] args) { SpringApplication.run(SpringCloudNacosConsumerApplication.class, args); } } 7.4创建消费服务 package com.lidong.consumer.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.cloud.client.loadbalancer.LoadBalancerClient; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; /** * 创建服务的消费者 */ @RestController public class ConsumerController { private static final String SERVICE_NAME = "spring-cloud-nacos-producer"; @Autowired private DiscoveryClient discoveryClient; /** * 获取所有服务 */ @RequestMapping("/services") public Object services() { return discoveryClient.getInstances(SERVICE_NAME); } /** * 消费服务 */ @RequestMapping("/callSayHello") public String services(@RequestParam("name") String name) { ServiceInstance serviceInstance = (ServiceInstance) discoveryClient.getInstances(SERVICE_NAME); String callServiceResult = new RestTemplate().getForObject(serviceInstance.getUri().toString() + "/hello", String.class); System.out.println(callServiceResult); return callServiceResult; } } http://localhost:9006/services 获取服务列表的结果 [{"serviceId":"spring-cloud-nacos-producer","host":"192.168.10.116","port":9005,"secure":false,"metadata":{"cluster":"DEFAULT","instanceId":"192.168.10.116#9005#DEFAULT#spring-cloud-nacos-producer","healthy":"true","weight":"1.0"},"uri":"http://192.168.10.116:9005","scheme":null,"instanceId":null}] 测试请求的urlhttp://localhost:9006/callSayHello?name=9006 消费的结果 hello ---> 9006 port -->9005 备注:spring.cloud.nacos配置说明 配置项 key 默认值 说明 服务端地址 spring.cloud.nacos.discovery.server-addr 服务名 spring.cloud.nacos.discovery.service spring.application.name 权重 spring.cloud.nacos.discovery.weight 1 取值范围 1 到 100,数值越大,权重越大 网卡名 spring.cloud.nacos.discovery.network-interface 当IP未配置时,注册的IP为此网卡所对应的IP地址,如果此项也未配置,则默认取第一块网卡的地址 注册的IP地址 spring.cloud.nacos.discovery.ip 优先级最高 注册的端口 spring.cloud.nacos.discovery.port -1 默认情况下不用配置,会自动探测 命名空间 spring.cloud.nacos.discovery.namespace 常用场景之一是不同环境的注册的区分隔离,例如开发测试环境和生产环境的资源(如配置、服务)隔离等。 AccessKey spring.cloud.nacos.discovery.access-key SecretKey spring.cloud.nacos.discovery.secret-key Metadata spring.cloud.nacos.discovery.metadata 使用Map格式配置 日志文件名 spring.cloud.nacos.discovery.log-name 接入点 spring.cloud.nacos.discovery.endpoint UTF-8 地域的某个服务的入口域名,通过此域名可以动态地拿到服务端地址 是否集成Ribbon ribbon.nacos.enabled true 到这里Nacos作为注册中心来注册服务和发现服务的流程就已经完成。这只是个简单入门,深入学习请关照nacos官方信息。 源码地址https://download.csdn.net/download/u010046908/10877868
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010046908/article/details/85260629 今天我们要学习的是consul在soringcloud中的使用。首先学习consul之前,我们应该看看consul的官网,对它有一个初步的认识。 1. consul 官网 (https://www.consul.io) 2. consul 简介 consul是google开源的一个使用go语言开发的服务发现、配置管理中心服务。内置了服务注册与发现框 架、分布一致性协议实现、健康检查、Key/Value存储、多数据中心方案,不再需要依赖其他工具(比如ZooKeeper等)。服务部署简单,只有一个可运行的二进制的包。每个节点都需要运行agent,他有两种运行模式server和client。每个数据中心官方建议需要3或5个server节点以保证数据安全,同时保证server-leader的选举能够正确的进行。 3.consul基本概念 client CLIENT表示consul的client模式,就是客户端模式。是consul节点的一种模式,这种模式下,所有注册到当前节点的服务会被转发到SERVER,本身是不持久化这些信息。 server SERVER表示consul的server模式,表明这个consul是个server,这种模式下,功能和CLIENT都一样,唯一不同的是,它会把所有的信息持久化的本地,这样遇到故障,信息是可以被保留的。 server-leader 中间那个SERVER下面有LEADER的字眼,表明这个SERVER是它们的老大,它和其它SERVER不一样的一点是,它需要负责同步注册的信息给其它的SERVER,同时也要负责各个节点的健康监测。 raft server节点之间的数据一致性保证,一致性协议使用的是raft,而zookeeper用的paxos,etcd采用的也是taft。 服务发现协议 consul采用http和dns协议,etcd只支持http 服务注册 consul支持两种方式实现服务注册,一种是通过consul的服务注册http API,由服务自己调用API实现注册,另一种方式是通过json个是的配置文件实现注册,将需要注册的服务以json格式的配置文件给出。consul官方建议使用第二种方式。 服务发现 consul支持两种方式实现服务发现,一种是通过http API来查询有哪些服务,另外一种是通过consul agent 自带的DNS(8600端口),域名是以NAME.service.consul的形式给出,NAME即在定义的服务配置文件中,服务的名称。DNS方式可以通过check的方式检查服务。 服务间的通信协议 Consul使用gossip协议管理成员关系、广播消息到整个集群,他有两个gossip pool(LAN pool和WAN pool),LAN pool是同一个数据中心内部通信的,WAN pool是多个数据中心通信的,LAN pool有多个,WAN pool只有一个。 4.consul架构图 5.Consul常用命令 5.1 agent 运行一个consul agent consul agent -dev 5.2 join 将agent加入到consul集群 consul join IP 5.3 members 列出consul cluster的members consul members 5.4 leave 将节点移除所在集群 consul leave 6.consul安装和启动 点击“download”下载:使用命令启动 consul agent -dev 启动成功之后在地址:http://localhost:8500 7.consul服务的发现与注册 7.1 注册服务 使用HTTP API 注册个服务,使用[接口API](https://www.consul.io/api/agent/service.html API)调用 调用 http://localhost:8500/v1/agent/service/register PUT 注册一个服务。request body: { "ID": "userServiceId", //服务id "Name": "userService", //服务名 "Tags": [ //服务的tag,自定义,可以根据这个tag来区分同一个服务名的服务 "primary", "v1" ], "Address": "127.0.0.1",//服务注册到consul的IP,服务发现,发现的就是这个IP "Port": 9000, //服务注册consul的PORT,发现的就是这个PORT "EnableTagOverride": false, "Check": { //健康检查部分 "DeregisterCriticalServiceAfter": "90m", "HTTP": "http://www.baidu.com", //指定健康检查的URL,调用后只要返回20X,consul都认为是健康的 "Interval": "10s" //健康检查间隔时间,每隔10s,调用一次上面的URL } } 使用curl调用 curl http://127.0.0.1:8500/v1/agent/service/register -X PUT -i -H "Content-Type:application/json" -d '{ "ID": "userServiceId", "Name": "userService", "Tags": [ "primary", "v1" ], "Address": "127.0.0.1", "Port": 8000, "EnableTagOverride": false, "Check": { "DeregisterCriticalServiceAfter": "90m", "HTTP": "http://www.baidu.com", "Interval": "10s" } }' 结果 % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 288 0 0 100 288 0 18000 --:--:-- --:--:-- --:--:-- 18000HTTP/1.1 200 OK Vary: Accept-Encoding Date: Wed, 26 Dec 2018 05:11:32 GMT Content-Length: 0 7.2 发现个服务 刚刚注册了名为userService的服务,我们现在发现(查询)下这个服务 curl http://127.0.0.1:8500/v1/catalog/service/userService 返回的响应: curl http://127.0.0.1:8500/v1/catalog/service/userService % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 891 100 891 0 0 28741 0 --:--:-- --:--:-- --:--:-- 28741[ { "ID": "9b831a00-ae68-d575-5e51-df193897b834", "Node": "vip-PC", "Address": "127.0.0.1", "Datacenter": "dc1", "TaggedAddresses": { "lan": "127.0.0.1", "wan": "127.0.0.1" }, "NodeMeta": { "consul-network-segment": "" }, "ServiceKind": "", "ServiceID": "userServiceId", "ServiceName": "userService", "ServiceTags": [ "primary", "v1" ], "ServiceAddress": "127.0.0.1", "ServiceWeights": { "Passing": 1, "Warning": 1 }, "ServiceMeta": {}, "ServicePort": 8000, "ServiceEnableTagOverride": false, "ServiceProxyDestination": "", "ServiceProxy": {}, "ServiceConnect": {}, "CreateIndex": 88, "ModifyIndex": 88 } ] 基本的服务发现和注册我们已经弄清楚了。接下来我来看看Spring Cloud 整合consul的使用。 8. consul服务提供者 8.1创建一个项目:spring-cloud-consul-provider 引入依赖 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.1.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.lidong</groupId> <artifactId>spring-cloud-consul-producer</artifactId> <version>1.0.0</version> <name>spring-cloud-consul-producer</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> <spring-cloud.version>Greenwich.RC2</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-consul-discovery</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> <repositories> <repository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/milestone</url> </repository> </repositories> </project> 对配置文件做一个简单的介绍,我们使用的是最新版的springboot2.1.1,springcloud.Greenwich.RC2版本。 其中: spring-boot-starter-actuator 健康检查依赖于此包。 spring-cloud-starter-consul-discovery Spring Cloud consul的服务发现支持。 8.2 提供者添加配置(application.yml) server: port: 9001 #提供者的端口 spring: application: name: spring-cloud-consul-producer cloud: consul: host: localhost port: 8500 discovery: tags: dev serviceName: spring-cloud-consul-producer # 注册到consul的服务名称 healthCheckPath: /actuator/health healthCheckInterval: 15s healthCheckUrl: http://127.0.0.1:9001/actuator/health register: true prefer-ip-address: false consul的地址和端口号默认是127.0.0.1:8500,如果没有配置hosts,默认的地址localhost,consul服务会占用8500端口 server.port :9001 服务的提供者的端口spring.application.name 是指注册到 consul的服务名称,后期客户端会根据这个名称来进行服务调用。 spring.application.cloud.discovery.discovery.host: localhost spring.application.cloud.discovery.discovery. port:8500 8.3 修改启动类 添加 @EnableDiscoveryClient 注解,开启服务发现支持。 package com.lidong.provider; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; /** * 开启服务发现 */ @EnableDiscoveryClient @SpringBootApplication public class SpringCloudLidongProviderApplication { public static void main(String[] args) { SpringApplication.run(SpringCloudLidongProviderApplication.class, args); } } 8.4新建服务 新建 ConsulProducerController,提供 sayHello 接口, 返回一个hello—>字符串。 package com.lidong.provider.service; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * 创建服务 */ @RestController public class ConsulProducerController { @Value("${server.port}") private Integer port; /** * 服务接口 * @param name * @return */ @RequestMapping("/hello") public String sayHello(@RequestParam("name")String name) { return "hello ---> "+name+" port -->"+port; } } 启动项目: 2018-12-26 13:21:42.984 INFO 20248 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 9001 (http) with context path '' 2018-12-26 13:21:42.994 INFO 20248 --- [ main] o.s.c.c.s.ConsulServiceRegistry : Registering service with consul: NewService{id='spring-cloud-consul-producer-9001', name='spring-cloud-consul-producer', tags=[dev, secure=false], address='vip-PC', meta=null, port=9001, enableTagOverride=null, check=Check{script='null', interval='15s', ttl='null', http='http://127.0.0.1:9001/actuator/health', method='null', header={}, tcp='null', timeout='null', deregisterCriticalServiceAfter='null', tlsSkipVerify=null, status='null'}, checks=null} 2018-12-26 13:21:43.008 INFO 20248 --- [ main] l.p.SpringCloudLidongProviderApplication : Started SpringCloudLidongProviderApplication in 4.09 seconds (JVM running for 4.755) 服务提供者发布成功。 这时候,我们在控制台会发现服务列表中有一个名字为spring-cloud-consul-producer的服务 点击详情会发现服务的详细信息 9. Consul服务消费者 9.1创建一个项目:spring-cloud-consul-consumer 引入依赖 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.1.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.lidong</groupId> <artifactId>spring-cloud-consul-consumer</artifactId> <version>0.0.1-SNAPSHOT</version> <name>spring-cloud-consul-consumer</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> <spring-cloud.version>Greenwich.RC2</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-consul-discovery</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-ribbon</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> <repositories> <repository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/milestone</url> </repository> </repositories> </project> 9.2 消费者添加配置(application.yml) server: port: 9002 #服务消费者的端口 spring: application: name: spring-cloud-consul-consumer cloud: consul: host: localhost port: 8500 discovery: tags: dev register: false #设置不需要注册到 consul 中 healthCheckPath: /actuator/health healthCheckInterval: 15s healthCheckUrl: http://127.0.0.1:9002/actuator/health consul的地址和端口号默认是 127.0.0.1:8500,如果没有配置hosts,默认的地址localhost,consul服务会占用8500接口 server.port :9006 服务的消费者的端口 spring.application.cloud.consul.discovery.register: false 9.3配置启动类 package com.lidong.consumer; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.context.annotation.Bean; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.client.RestTemplate; @SpringBootApplication @EnableDiscoveryClient public class SpringCloudConsulConsumerApplication { @Autowired private RestTemplateBuilder builder; @LoadBalanced @Bean// 添加负载均衡支持,很简单,只需要在RestTemplate上添加@LoadBalanced注解,那么RestTemplate即具有负载均衡的功能,如果不加@LoadBalanced注解的话,会报java.net.UnknownHostException:springboot-h2异常,此时无法通过注册到Eureka Server上的服务名来调用服务,因为RestTemplate是无法从服务名映射到ip:port的,映射的功能是由LoadBalancerClient来实现的。 public RestTemplate restTemplate() { return builder.build(); } public static void main(String[] args) { SpringApplication.run(SpringCloudConsulConsumerApplication.class, args); } } 9.4创建消费服务 package com.lidong.consumer.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.cloud.client.loadbalancer.LoadBalancerClient; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; /** * 创建服务的消费者 */ @RestController public class ConsumerController { private static final String SERVICE_NAME = "spring-cloud-consul-producer"; @Autowired private DiscoveryClient discoveryClient; /** * 获取所有服务 */ @RequestMapping("/services") public Object services() { return discoveryClient.getInstances(SERVICE_NAME); } /** * 消费服务 */ @RequestMapping("/callSayHello") public String services(@RequestParam("name") String name) { ServiceInstance serviceInstance = (ServiceInstance) discoveryClient.getInstances(SERVICE_NAME); String callServiceResult = new RestTemplate().getForObject(serviceInstance.getUri().toString() + "/hello", String.class); System.out.println(callServiceResult); return callServiceResult; } } http://localhost:9002/services 获取服务列表的结果 [{"instanceId":"spring-cloud-consul-producer-9001","serviceId":"spring-cloud-consul-producer","host":"vip-PC","port":9001,"secure":false,"metadata":{"dev":"dev","secure":"false"},"uri":"http://vip-PC:9001","scheme":null}] 测试请求的urlhttp://localhost:9002/callSayHello?name=9002 消费的结果 hello ---> 9002 port -->9001 源码地址https://download.csdn.net/download/u010046908/10877868
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010046908/article/details/84879858 需求描述 要为数据库里的所有的表统一加上十六个备用字段,前提是备用字段名取表名前三位,拼接上备用1-16 , 16个字段中,其中8个varchar, 4个int, 4个datetime 1.创建一个简单的表 DROP TABLE customer_detail_info; CREATE TABLE `customer_detail_info` ( `cus_id` int(11) NOT NULL, `name` varchar(255) DEFAULT NULL, PRIMARY KEY (`cus_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -- 查看表结构 DESC customer_detail_info; 表结构截图: 2.创建一个存储过程 DROP PROCEDURE IF EXISTS `procedure_update_table`; DELIMITER ;; CREATE PROCEDURE `procedure_update_table`(IN table_name VARCHAR(50)) BEGIN declare i int; DECLARE sq VARCHAR(8000); SET i = 1 ; WHILE i < 17 DO -- 取表名前三位 set @qz = LEFT(table_name,3); -- 拼接字段名 set @pp = CONCAT(@qz,i); -- 前八个字符类型 IF i < 9 THEN SET sq = CONCAT('ALTER table ',table_name,' add column ',@pp, ' VARCHAR(8) '); -- 中间四个int ELSEIF (i > 8 && i < 13) THEN SET sq = CONCAT('ALTER table ',table_name,' add column ',@pp, ' INT(11)'); -- 其他的时间类型 ELSE SET sq = CONCAT('ALTER table ',table_name,' add column ',@pp, ' DATETIME '); end IF; SET i = i + 1 ; SET @_SQL = sq ; -- 预定义sql PREPARE stmt FROM @_SQL ; -- 执行sql EXECUTE stmt ; -- 释放连接 DEALLOCATE PREPARE stmt; -- 结束While 循环 END WHILE; END; 3.整个库中全部表获取的存储过程 -- 已存在的存储删除 DROP PROCEDURE IF EXISTS procedure_get_all_tables; DELIMITER ;; CREATE PROCEDURE procedure_get_all_tables() BEGIN -- 用于判断是否结束循环 DECLARE done int DEFAULT 0; -- 存储表名称的变量 DECLARE cur VARCHAR(200); DECLARE tbs_list CURSOR FOR SELECT TABLE_NAME FROM information_schema.`TABLES` WHERE TABLE_Schema = 'hqh_log'; -- 定义 设置循环结束标识done值怎么改变 的逻辑 declare continue handler for not FOUND set done = 1; OPEN tbs_list; -- 循环开始 REPEAT FETCH tbs_list INTO cur; if not done THEN CALL procedure_update_table(cur); end if; until done end repeat; CLOSE tbs_list; END; 4.执行存储过程 -- 调用存储过程 CALL procedure_get_all_tables(); -- 查看表结构 DESC customer_detail_info; 给大家在提供一个mybatis调用存储过程的例子 Mybatis调用MySQL存储过程
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010046908/article/details/79611701 MySQL order by case 解决方法字母顺序应该是A-Z包含# 正序排列,#号排到了前面的问题 1 使用order by直接排序出现问题 SELECT * FROM customer_info ORDER BY cus_name_index ASC; 2 使用使用order by case 解决问题 SELECT * FROM customer_info ORDER BY CASE WHEN cus_name_index = '#' then cus_name_index END ASC, CASE WHEN cus_name_index != '#' then cus_name_index END ASC; 问题完美解决。
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010046908/article/details/79553293 1、安装maven wget http://mirror.bit.edu.cn/apache/maven/maven-3/3.5.0/binaries/apache-maven-3.5.0-bin.tar.gz tar -zxvf apache-maven-3.5.0-bin.tar.gz vi ~/.bashrc export MAVEN_HOME=/usr/local/apache-maven-3.5.0 export PATH=$PATH:$MAVEN_HOME/bin source ~/.bashrc 2、查看maven版本 mvn -version Apache Maven 3.5.0 (bb52d8502b132ec0a5a3f4c09453c07478323dc5; 2015-11-11T00:41:47+08:00) Maven home: /usr/local/apache-maven-3.5.0 Java version: 1.8.0_161, vendor: Oracle Corporation Java home: /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.161-0.b14.el7_4.x86_64/jre Default locale: zh_CN, platform encoding: UTF-8 OS name: "linux", version: "3.10.0-514.el7.x86_64", arch: "amd64", family: "unix" 3.修改maven的进行 cd /usr/local/apache-maven-3.5.0/conf vi settings.xml <mirror> <id>nexus-aliyun</id> <mirrorOf>*</mirrorOf> <name>Nexus aliyun</name> <url>http://maven.aliyun.com/nexus/content/groups/public</url> </mirror> 4、安装git 4.1 安装git yum install -y git 4.2查看版本 git --version
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010046908/article/details/79553227 Docker 要求 CentOS 系统的内核版本高于 3.10 ,查看本页面的前提条件来验证你的CentOS 版本是否支持 Docker 。通过 uname -r 命令查看你当前的内核版本 uname -r centos版本 3.10.0-514.el7.x86_64 1、初步安装和启动docker yum update -y yum -y install docker systemctl start docker 2、设置镜像 vi /etc/docker/daemon.json { "registry-mirrors": ["https://aj2rgad5.mirror.aliyuncs.com"] } 3、开放管理端口映射 vi /lib/systemd/system/docker.service 将第11行的ExecStart=/usr/bin/dockerd,替换为: ExecStart=/usr/bin/dockerd -H tcp://0.0.0.0:2375 -H unix:///var/run/docker.sock -H tcp://0.0.0.0:7654 2375是管理端口,7654是备用端口 在~/.bashrc中写入docker管理端口 export DOCKER_HOST=tcp://0.0.0.0:2375 source ~/.bashrc 4、重启docker systemctl daemon-reload systemctl restart docker.service 5、测试docker是否正常安装和运行 docker run hello-world 6、查看结果 Hello from Docker! This message shows that your installation appears to be working correctly.
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010046908/article/details/77369205 好久没有更新博客文章了,今天给大家带来的是logback+ELK+SpringMVC 日志收集服务器搭建。接下来我会介绍ELK是什么?logback是什么?以及搭建的流程。 1.ELK是什么? ELK是由Elasticsearch、Logstash、Kibana这3个软件的缩写。 Elasticsearch是一个分布式搜索分析引擎,稳定、可水平扩展、易于管理是它的主要设计初衷 Logstash是一个灵活的数据收集、加工和传输的管道软件 Kibana是一个数据可视化平台,可以通过将数据转化为酷炫而强大的图像而实现与数据的交互将三者的收集加工,存储分析和可视转化整合在一起就形成了 ELK 。 2.ELK流程 ELK的流程应该是这样的:Logback->Logstash->(Elasticsearch<->Kibana) 应用程序产生出日志,由logback日志框架进行处理。 将日志数据输出到Logstash中 Logstash再将数据输出到Elasticsearch中 Elasticsearch再与Kibana相结合展示给用户 3.ELK官网 https://www.elastic.co/guide/index.html 4. 环境配置 4.1 基础环境 jdk 1.8 Centos 7.0 X86-64 注意:ELK服务不能在root用户开启。需要重新创建用户。 下载ELK相关服务压缩包 创建ELK用户和目录并赋予权限,方便统一管理。 [root@localhost /]# mkdir elsearch [root@localhost /]# groupadd elsearch [root@localhost /]# useradd -g elsearch elsearch [root@localhost /]# chown -R elsearch:elsearch /elsearch [root@localhost /]# su elsearch [elk@localhost /]$ cd elsearch 4.2 下载,然你也可以去官网找最新版的 wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-5.2.2.tar.gz wget https://artifacts.elastic.co/downloads/logstash/logstash-5.2.2.tar.gz wget https://artifacts.elastic.co/downloads/kibana/kibana-5.2.2-linux-x86_64.tar.gz 我这里是以5.2.2为例来实现。 4.3 配置Elasticsearch Elasticsearch是可以搭建集群,我这边只是解压后直接修改配置文件 elasticsearch.yml cluster.name: es_cluster_1 node.name: node-1 path.data: /usr/local/services/elasticsearch-5.2.2/data path.logs:/usr/local/services/elasticsearch-5.2.2/logs network.host: 192.168.10.200 http.port: 9200 启动ElasticSearch,访问http://192.168.10.200:9200/ 看到如上的界面就代表启动成功。 注意:安装过程中出现一些问题。在这篇文章中已经都给我们解决了。 http://www.cnblogs.com/sloveling/p/elasticsearch.html 4.4 配置logstash 解压 tar -zxvf /usr/local/services/logstash-5.2.2.tar.gz 测试配置,只是测试服务是否启动。还有这个文件是没有的,启动时加上这个路径就是以这个配置启动 vi /usr/local/services/logstash-5.2.2/config/logstash.conf input { stdin { } } output { stdout { codec => rubydebug {} } } logstash以配置文件方式启动有两种: 列表内容 logstash -f logstash-test.conf //普通方式启动 logstash agent -f logstash-test.conf –debug//开启debug模式 ./bin/logstash -f config/logstash.conf --debug 启动成功会看到如下的结果: 这是时候,我们在控制台随便输入都可以被收集 n"=>"1", "host"=>"localhost", "message"=>"我们都是好好"}} { "@timestamp" => 2017-08-18T05:45:25.340Z, "@version" => "1", "host" => "localhost", "message" => "我们都是好好" } [2017-08-18T13:45:26,933][DEBUG][logstash.pipeline ] Pushing flush onto pipeline [2017-08-18T13:45:31,934][DEBUG][logstash.pipeline ] Pushing flush onto pipeline 4.5 配置logstash 配置kibana + 解压 [elsearch@localhost root]$ tar -zxvf /usr/local/services/kibana-5.2.2-linux-x86_64.tar.gz 打开配置 [elsearch@localhost root]$ vim /usr/local/services/kibana-5.2.2-linux-x86_64/config/kibana.yml 修改配置,最后最加 server.port: 8888 server.host: "192.168.10.200" elasticsearch.url: "http://192.168.10.200:9200" 启动 [elsearch@localhost root]$ /usr/local/services/kibana-5.2.2-linux-x86_64/bin/kibana & 访问地址 http://192.168.10.200:8888 基本ELK的环境的搭建就ok了,我们接下来学习logback-ELK整合来收集JavaEE中的日志。 4.6 logback-ELK整合 4.6.1 本案列采用maven管理 pom.xml <dependency> <groupId>net.logstash.logback</groupId> <artifactId>logstash-logback-encoder</artifactId> <version>4.11</version> </dependency> <!--实现slf4j接口并整合--> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.3</version> </dependency> <dependency> <groupId>net.logstash.log4j</groupId> <artifactId>jsonevent-layout</artifactId> <version>1.7</version> </dependency> 4.6.2配置logaback的配置文件 <?xml version="1.0" encoding="UTF-8"?> <configuration debug="false"> <!--定义日志文件的存储地址 勿在 LogBack 的配置中使用相对路径--> <property name="LOG_HOME" value="E:/logs" /> <!-- 控制台输出 --> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符--> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern> </encoder> </appender> <!-- 按照每天生成日志文件 --> <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <!--日志文件输出的文件名--> <FileNamePattern>${LOG_HOME}/TestWeb.log_%d{yyyy-MM-dd}.log</FileNamePattern> <!--日志文件保留天数--> <MaxHistory>30</MaxHistory> </rollingPolicy> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符--> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern> </encoder> <!--日志文件最大的大小--> <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy"> <MaxFileSize>10MB</MaxFileSize> </triggeringPolicy> </appender> <!-- show parameters for hibernate sql 专为 Hibernate 定制 --> <logger name="org.hibernate.type.descriptor.sql.BasicBinder" level="TRACE" /> <logger name="org.hibernate.type.descriptor.sql.BasicExtractor" level="DEBUG" /> <logger name="org.hibernate.SQL" level="DEBUG" /> <logger name="org.hibernate.engine.QueryParameters" level="DEBUG" /> <logger name="org.hibernate.engine.query.HQLQueryPlan" level="DEBUG" /> <!--myibatis log configure--> <logger name="com.apache.ibatis" level="TRACE"/> <logger name="java.sql.Connection" level="DEBUG"/> <logger name="java.sql.Statement" level="DEBUG"/> <logger name="java.sql.PreparedStatement" level="DEBUG"/> <appender name="stash" class="net.logstash.logback.appender.LogstashTcpSocketAppender"> <destination>192.168.10.200:8082</destination> <!-- encoder is required --> <encoder charset="UTF-8" class="net.logstash.logback.encoder.LogstashEncoder" /> </appender> <!-- 日志输出级别 --> <root level="INFO"> <!-- 只有添加stash关联才会被收集--> <appender-ref ref="stash" /> <appender-ref ref="STDOUT" /> <appender-ref ref="FILE" /> </root> </configuration> 注意:logstash接收日志的地址 192.168.10.200:8082 4.6.3配置logstash-test.conf vi logstash-test.conf input { tcp { host => "192.168.10.200" port => 8082 mode => "server" ssl_enable => false codec => json { charset => "UTF-8" } } } output { elasticsearch { hosts => "192.168.10.200:9200" index => "logstash-test" } stdout { codec => rubydebug {} } } 启动收集 ./bin/logstash -f config/logstash-test.conf –debug 4.6.4配置Controller添加日志输出 package com.example.demo; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; @RestController public class TestEndpoints { private final static Logger logger = LoggerFactory.getLogger(TestEndpoints.class); @GetMapping("/product/{id}") public String getProduct(@PathVariable String id) { String data = "{\"name\":\"李东\"}"; logger.info(data); return "product id : " + id; } @GetMapping("/order/{id}") public String getOrder(@PathVariable String id) { return "order id : " + id; } } 请求调用之后控制台的log 之后Kibana中就可以收集到log { "_index": "logstash-test", "_type": "logs", "_id": "AV3zu4jiJKLF9tWSjmZj", "_score": null, "_source": { "@timestamp": "2017-08-18T05:04:51.698Z", "level": "INFO", "port": 56525, "thread_name": "http-nio-8081-exec-10", "level_value": 20000, "@version": 1, "host": "192.168.10.165", "logger_name": "com.example.demo.TestEndpoints", "message": "{\"name\":\"李东\"}" }, "fields": { "@timestamp": [ 1503032691698 ] }, "sort": [ 1503032691698 ] } 基本上就这些步骤,希望看完之后,动手实践一下,谢谢阅读。
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010046908/article/details/72383841 安装环境:CentOS7 64位 MINI版,安装MySQL5.7 1、配置YUM源 在MySQL官网中下载YUM源rpm安装包:http://dev.mysql.com/downloads/repo/yum/ 1.1下载mysql源安装包 shell> wget http://dev.mysql.com/get/mysql57-community-release-el7-8.noarch.rpm 1.2安装mysql源 shell> yum localinstall mysql57-community-release-el7-8.noarch.rpm 检查mysql源是否安装成功 shell> yum repolist enabled | grep "mysql.*-community.*" 看到上图所示表示安装成功 2、安装MySQL shell> yum install mysql-community-server 3、启动MySQL服务 shell> systemctl start mysqld 查看MySQL的启动状态 shell> systemctl status mysqld ● mysqld.service - MySQL Server Loaded: loaded (/usr/lib/systemd/system/mysqld.service; disabled; vendor preset: disabled) Active: active (running) since 五 2016-06-24 04:37:37 CST; 35min ago Main PID: 2888 (mysqld) CGroup: /system.slice/mysqld.service └─2888 /usr/sbin/mysqld --daemonize --pid-file=/var/run/mysqld/mysqld.pid 6月 24 04:37:36 localhost.localdomain systemd[1]: Starting MySQL Server... 6月 24 04:37:37 localhost.localdomain systemd[1]: Started MySQL Server. 4、开机启动 shell> systemctl enable mysqld shell> systemctl daemon-reload 5、修改root默认密码 mysql安装完成之后,在/var/log/mysqld.log文件中给root生成了一个默认密码。通过下面的方式找到root默认密码,然后登录mysql进行修改: shell> grep 'temporary password' /var/log/mysqld.log shell> mysql -uroot -p mysql> ALTER USER 'root'@'localhost' IDENTIFIED BY 'MyNewPass4!'; 或者 mysql> set password for 'root'@'localhost'=password('MyNewPass4!'); 注意:mysql5.7默认安装了密码安全检查插件(validate_password),默认密码检查策略要求密码必须包含:大小写字母、数字和特殊符号,并且长度不能少于8位。否则会提示ERROR 1819 (HY000): Your password does not satisfy the current policy requirements错误,如下图所示: 通过msyql环境变量可以查看密码策略的相关信息: mysql> show variables like '%password%'; validate_password_policy:密码策略,默认为MEDIUM策略 validate_password_dictionary_file:密码策略文件,策略为STRONG才需要 validate_password_length:密码最少长度 validate_password_mixed_case_count:大小写字符长度,至少1个 validate_password_number_count :数字至少1个 validate_password_special_char_count:特殊字符至少1个 上述参数是默认策略MEDIUM的密码检查规则。 共有以下几种密码策略: 策略 检查规则 0 or LOW Length 1 or MEDIUM Length; numeric, lowercase/uppercase, and special characters 2 or STRONG Length; numeric, lowercase/uppercase, and special characters; dictionary file MySQL官网密码策略详细说明:http://dev.mysql.com/doc/refman/5.7/en/validate-password-options-variables.html#sysvar_validate_password_policy 修改密码策略 在/etc/my.cnf文件添加validate_password_policy配置,指定密码策略 选择0(LOW),1(MEDIUM),2(STRONG)其中一种,选择2需要提供密码字典文件 validate_password_policy=0 如果不需要密码策略,添加my.cnf文件中添加如下配置禁用即可: validate_password = off 重新启动mysql服务使配置生效: systemctl restart mysqld 6、添加远程登录用户 默认只允许root帐户在本地登录,如果要在其它机器上连接mysql,必须修改root允许远程连接,或者添加一个允许远程连接的帐户,为了安全起见,我添加一个新的帐户: mysql> GRANT ALL PRIVILEGES ON . TO ‘yangxin’@’%’ IDENTIFIED BY ‘Yangxin0917!’ WITH GRANT OPTION; 7、配置默认编码为utf8 修改/etc/my.cnf配置文件,在[mysqld]下添加编码配置,如下所示: [mysqld] character_set_server=utf8 init_connect=’SET NAMES utf8’ 重新启动mysql服务,查看数据库默认编码如下所示: 默认配置文件路径: 配置文件:/etc/my.cnf 日志文件:/var/log//var/log/mysqld.log 服务启动脚本:/usr/lib/systemd/system/mysqld.service socket文件:/var/run/mysqld/mysqld.pid
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010046908/article/details/68924339 在前面已经写了两篇关于Android蓝牙和ios 蓝牙开发的文章,今天带来的是微信小程序蓝牙实现。 Android蓝牙 ios蓝牙(Swift) 有一段时间没有。没有写关于小程序的文章了。3月28日,微信的api又一次新的更新。期待已久的蓝牙api更新。就开始撸一番。 源码地址 1.简述 蓝牙适配器接口是基础库版本 1.1.0 开始支持。 iOS 微信客户端 6.5.6 版本开始支持,Android 客户端暂不支持 蓝牙总共增加了18个api接口。 2.Api分类 搜索类 连接类 通信类 3.API的具体使用 详细见官网: https://mp.weixin.qq.com/debug/wxadoc/dev/api/bluetooth.html#wxgetconnectedbluethoothdevicesobject 4. 案例实现 4.1 搜索蓝牙设备 /** * 搜索设备界面 */ Page({ data: { logs: [], list:[], }, onLoad: function () { console.log('onLoad') var that = this; // const SDKVersion = wx.getSystemInfoSync().SDKVersion || '1.0.0' // const [MAJOR, MINOR, PATCH] = SDKVersion.split('.').map(Number) // console.log(SDKVersion); // console.log(MAJOR); // console.log(MINOR); // console.log(PATCH); // const canIUse = apiName => { // if (apiName === 'showModal.cancel') { // return MAJOR >= 1 && MINOR >= 1 // } // return true // } // wx.showModal({ // success: function(res) { // if (canIUse('showModal.cancel')) { // console.log(res.cancel) // } // } // }) //获取适配器 wx.openBluetoothAdapter({ success: function(res){ // success console.log("-----success----------"); console.log(res); //开始搜索 wx.startBluetoothDevicesDiscovery({ services: [], success: function(res){ // success console.log("-----startBluetoothDevicesDiscovery--success----------"); console.log(res); }, fail: function(res) { // fail console.log(res); }, complete: function(res) { // complete console.log(res); } }) }, fail: function(res) { console.log("-----fail----------"); // fail console.log(res); }, complete: function(res) { // complete console.log("-----complete----------"); console.log(res); } }) wx.getBluetoothDevices({ success: function(res){ // success //{devices: Array[11], errMsg: "getBluetoothDevices:ok"} console.log("getBluetoothDevices"); console.log(res); that.setData({ list:res.devices }); console.log(that.data.list); }, fail: function(res) { // fail }, complete: function(res) { // complete } }) }, onShow:function(){ }, //点击事件处理 bindViewTap: function(e) { console.log(e.currentTarget.dataset.title); console.log(e.currentTarget.dataset.name); console.log(e.currentTarget.dataset.advertisData); var title = e.currentTarget.dataset.title; var name = e.currentTarget.dataset.name; wx.redirectTo({ url: '../conn/conn?deviceId='+title+'&name='+name, success: function(res){ // success }, fail: function(res) { // fail }, complete: function(res) { // complete } }) }, }) 4.2连接 获取数据 /** * 连接设备。获取数据 */ Page({ data: { motto: 'Hello World', userInfo: {}, deviceId: '', name: '', serviceId: '', services: [], cd20: '', cd01: '', cd02: '', cd03: '', cd04: '', characteristics20: null, characteristics01: null, characteristics02: null, characteristics03: null, characteristics04: null, result, }, onLoad: function (opt) { var that = this; console.log("onLoad"); console.log('deviceId=' + opt.deviceId); console.log('name=' + opt.name); that.setData({ deviceId: opt.deviceId }); /** * 监听设备的连接状态 */ wx.onBLEConnectionStateChanged(function (res) { console.log(`device ${res.deviceId} state has changed, connected: ${res.connected}`) }) /** * 连接设备 */ wx.createBLEConnection({ deviceId: that.data.deviceId, success: function (res) { // success console.log(res); /** * 连接成功,后开始获取设备的服务列表 */ wx.getBLEDeviceServices({ // 这里的 deviceId 需要在上面的 getBluetoothDevices中获取 deviceId: that.data.deviceId, success: function (res) { console.log('device services:', res.services) that.setData({ services: res.services }); console.log('device services:', that.data.services[1].uuid); that.setData({ serviceId: that.data.services[1].uuid }); console.log('--------------------------------------'); console.log('device设备的id:', that.data.deviceId); console.log('device设备的服务id:', that.data.serviceId); /** * 延迟3秒,根据服务获取特征 */ setTimeout(function () { wx.getBLEDeviceCharacteristics({ // 这里的 deviceId 需要在上面的 getBluetoothDevices deviceId: that.data.deviceId, // 这里的 serviceId 需要在上面的 getBLEDeviceServices 接口中获取 serviceId: that.data.serviceId, success: function (res) { console.log('000000000000' + that.data.serviceId); console.log('device getBLEDeviceCharacteristics:', res.characteristics) for (var i = 0; i < 5; i++) { if (res.characteristics[i].uuid.indexOf("cd20") != -1) { that.setData({ cd20: res.characteristics[i].uuid, characteristics20: res.characteristics[i] }); } if (res.characteristics[i].uuid.indexOf("cd01") != -1) { that.setData({ cd01: res.characteristics[i].uuid, characteristics01: res.characteristics[i] }); } if (res.characteristics[i].uuid.indexOf("cd02") != -1) { that.setData({ cd02: res.characteristics[i].uuid, characteristics02: res.characteristics[i] }); } if (res.characteristics[i].uuid.indexOf("cd03") != -1) { that.setData({ cd03: res.characteristics[i].uuid, characteristics03: res.characteristics[i] }); } if (res.characteristics[i].uuid.indexOf("cd04") != -1) { that.setData({ cd04: res.characteristics[i].uuid, characteristics04: res.characteristics[i] }); } } console.log('cd01= ' + that.data.cd01 + 'cd02= ' + that.data.cd02 + 'cd03= ' + that.data.cd03 + 'cd04= ' + that.data.cd04 + 'cd20= ' + that.data.cd20); /** * 回调获取 设备发过来的数据 */ wx.onBLECharacteristicValueChange(function (characteristic) { console.log('characteristic value comed:', characteristic.value) //{value: ArrayBuffer, deviceId: "D8:00:D2:4F:24:17", serviceId: "ba11f08c-5f14-0b0d-1080-007cbe238851-0x600000460240", characteristicId: "0000cd04-0000-1000-8000-00805f9b34fb-0x60800069fb80"} /** * 监听cd04cd04中的结果 */ if (characteristic.characteristicId.indexOf("cd01") != -1) { const result = characteristic.value; const hex = that.buf2hex(result); console.log(hex); } if (characteristic.characteristicId.indexOf("cd04") != -1) { const result = characteristic.value; const hex = that.buf2hex(result); console.log(hex); that.setData({ result: hex }); } }) /** * 顺序开发设备特征notifiy */ wx.notifyBLECharacteristicValueChanged({ deviceId: that.data.deviceId, serviceId: that.data.serviceId, characteristicId: that.data.cd01, state: true, success: function (res) { // success console.log('notifyBLECharacteristicValueChanged success', res); }, fail: function (res) { // fail }, complete: function (res) { // complete } }) wx.notifyBLECharacteristicValueChanged({ deviceId: that.data.deviceId, serviceId: that.data.serviceId, characteristicId: that.data.cd02, state: true, success: function (res) { // success console.log('notifyBLECharacteristicValueChanged success', res); }, fail: function (res) { // fail }, complete: function (res) { // complete } }) wx.notifyBLECharacteristicValueChanged({ deviceId: that.data.deviceId, serviceId: that.data.serviceId, characteristicId: that.data.cd03, state: true, success: function (res) { // success console.log('notifyBLECharacteristicValueChanged success', res); }, fail: function (res) { // fail }, complete: function (res) { // complete } }) wx.notifyBLECharacteristicValueChanged({ // 启用 notify 功能 // 这里的 deviceId 需要在上面的 getBluetoothDevices 或 onBluetoothDeviceFound 接口中获取 deviceId: that.data.deviceId, serviceId: that.data.serviceId, characteristicId: that.data.cd04, state: true, success: function (res) { console.log('notifyBLECharacteristicValueChanged success', res) } }) }, fail: function (res) { console.log(res); } }) } , 1500); } }) }, fail: function (res) { // fail }, complete: function (res) { // complete } }) }, /** * 发送 数据到设备中 */ bindViewTap: function () { var that = this; var hex = 'AA5504B10000B5' var typedArray = new Uint8Array(hex.match(/[\da-f]{2}/gi).map(function (h) { return parseInt(h, 16) })) console.log(typedArray) console.log([0xAA, 0x55, 0x04, 0xB1, 0x00, 0x00, 0xB5]) var buffer1 = typedArray.buffer console.log(buffer1) wx.writeBLECharacteristicValue({ deviceId: that.data.deviceId, serviceId: that.data.serviceId, characteristicId: that.data.cd20, value: buffer1, success: function (res) { // success console.log("success 指令发送成功"); console.log(res); }, fail: function (res) { // fail console.log(res); }, complete: function (res) { // complete } }) }, /** * ArrayBuffer 转换为 Hex */ buf2hex: function (buffer) { // buffer is an ArrayBuffer return Array.prototype.map.call(new Uint8Array(buffer), x => ('00' + x.toString(16)).slice(-2)).join(''); } }) 5.效果展示 发送校验指令。获取结果
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010046908/article/details/62229015 1.kafka安装 brew install kafka 安装会依赖zookeeper。 看到如下结果,就可以说是安装成功了。 lidongdeMacBook-Pro:~ lidong$ brew install kafka Updating Homebrew... ==> Auto-updated Homebrew! Updated 2 taps (homebrew/core, pivotal/tap). ==> New Formulae bluepill gobuster prest buildifier grunt sha1dc crowdin gtef shellshare dhall-json libcds spigot dmtx-utils librealsense teleport docker-credential-helper libtensorflow uniutils dvd-vr monitoring-plugins urh gandi.cli mysql-utilities woboq_codebrowser go@1.7 pqiv ==> Updated Formulae kops wakatime-cli kotlin wesnoth kubernetes-cli winetricks kubernetes-helm wireguard-tools ldc wireshark leveldb x265 lftp xmake libarchive xonsh libav yarn libbluray yle-dl libchamplain youtube-dl libcouchbase zimg libdvbpsi znc libepoxy zsh-history-substring-search libfabric ==> Renamed Formulae bash-completion2 -> bash-completion@2 bazel02 -> bazel@0.2 bigdata -> blazegraph bison27 -> bison@2.7 boost-python159 -> boost-python@1.59 cassandra21 -> cassandra@2.1 cassandra22 -> cassandra@2.2 clang-format38 -> clang-format@3.8 docker111 -> docker@1.11 docker171 -> docker@1.71 erlang-r18 -> erlang@18 ffmpeg28 -> ffmpeg@2.8 freetds091 -> freetds@0.91 giflib5 -> giflib@5 glfw2 -> glfw@2 gnupg21 -> gnupg@2.1 gnuplot4 -> gnuplot@4 go14 -> go@1.4 go15 -> go@1.5 go16 -> go@1.6 gradle214 -> gradle@2.14 grails25 -> grails@2.5 gsl1 -> gsl@1 gst-ffmpeg010 -> gst-ffmpeg@0.10 gst-plugins-bad010 -> gst-plugins-bad@0.10 gst-plugins-base010 -> gst-plugins-base@0.10 gst-plugins-good010 -> gst-plugins-good@0.10 gst-plugins-ugly010 -> gst-plugins-ugly@0.10 gstreamer010 -> gstreamer@0.10 mariadb100 -> mariadb@10.0 postgresql94 -> postgresql@9.4 postgresql95 -> postgresql@9.5 rebar3 -> rebar@3 recipes -> gnome-recipes redis26 -> redis@2.6 redis28 -> redis@2.8 ruby187 -> ruby@1.8 ruby193 -> ruby@1.9 ruby20 -> ruby@2.0 ruby21 -> ruby@2.1 ruby22 -> ruby@2.2 ruby23 -> ruby@2.3 ==> Deleted Formulae ctorrent libgroove s3sync silc-client ee node@5 scsh xstow ==> Installing dependencies for kafka: zookeeper ==> Installing kafka dependency: zookeeper ==> Downloading https://homebrew.bintray.com/bottles/zookeeper-3.4.9.sierra.bott ######################################################################## 100.0% ==> Pouring zookeeper-3.4.9.sierra.bottle.tar.gz ==> Caveats To have launchd start zookeeper now and restart at login: brew services start zookeeper Or, if you don't want/need a background service you can just run: zkServer start ==> Summary �� /usr/local/Cellar/zookeeper/3.4.9: 238 files, 18.2MB ==> Installing kafka ==> Downloading https://homebrew.bintray.com/bottles/kafka-0.10.2.0.sierra.bottl ######################################################################## 100.0% ==> Pouring kafka-0.10.2.0.sierra.bottle.tar.gz ==> Caveats To have launchd start kafka now and restart at login: brew services start kafka Or, if you don't want/need a background service you can just run: zookeeper-server-start /usr/local/etc/kafka/zookeeper.properties & kafka-server-start /usr/local/etc/kafka/server.properties ==> Summary �� /usr/local/Cellar/kafka/0.10.2.0: 132 files, 37.2MB lidongdeMacBook-Pro:~ lidong$ 注意:安装目录:/usr/local/Cellar/kafka/0.10.2.0 2.安装的配置文件位置 /usr/local/etc/kafka/server.properties /usr/local/etc/kafka/zookeeper.properties 3. 启动zookeeper lidongdeMacBook-Pro:0.10.2.0 lidong$ ./bin/zookeeper-server-start /usr/local/etc/kafka/zookeeper.properties & [1] 4436 lidongdeMacBook-Pro:0.10.2.0 lidong$ [2017-03-15 14:11:38,682] INFO Reading configuration from: /usr/local/etc/kafka/zookeeper.properties (org.apache.zookeeper.server.quorum.QuorumPeerConfig) [2017-03-15 14:11:38,685] INFO autopurge.snapRetainCount set to 3 (org.apache.zookeeper.server.DatadirCleanupManager) [2017-03-15 14:11:38,685] INFO autopurge.purgeInterval set to 0 (org.apache.zookeeper.server.DatadirCleanupManager) [2017-03-15 14:11:38,685] INFO Purge task is not scheduled. (org.apache.zookeeper.server.DatadirCleanupManager) [2017-03-15 14:11:38,685] WARN Either no config or no quorum defined in config, running in standalone mode (org.apache.zookeeper.server.quorum.QuorumPeerMain) [2017-03-15 14:11:38,708] INFO Reading configuration from: /usr/local/etc/kafka/zookeeper.properties (org.apache.zookeeper.server.quorum.QuorumPeerConfig) [2017-03-15 14:11:38,708] INFO Starting server (org.apache.zookeeper.server.ZooKeeperServerMain) [2017-03-15 14:11:38,728] INFO Server environment:zookeeper.version=3.4.9-1757313, built on 08/23/2016 06:50 GMT (org.apache.zookeeper.server.ZooKeeperServer) [2017-03-15 14:11:38,728] INFO Server environment:host.name=192.168.1.130 (org.apache.zookeeper.server.ZooKeeperServer) [2017-03-15 14:11:38,728] INFO Server environment:java.version=1.8.0_92 (org.apache.zookeeper.server.ZooKeeperServer) [2017-03-15 14:11:38,728] INFO Server environment:java.vendor=Oracle Corporation (org.apache.zookeeper.server.ZooKeeperServer) [2017-03-15 14:11:38,728] INFO Server environment:java.home=/Library/Java/JavaVirtualMachines/jdk1.8.0_92.jdk/Contents/Home/jre (org.apache.zookeeper.server.ZooKeeperServer) [2017-03-15 14:11:38,728] INFO Server environment:java.class.path=:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/aopalliance-repackaged-2.5.0-b05.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/argparse4j-0.7.0.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/connect-api-0.10.2.0.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/connect-file-0.10.2.0.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/connect-json-0.10.2.0.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/connect-runtime-0.10.2.0.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/connect-transforms-0.10.2.0.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/guava-18.0.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/hk2-api-2.5.0-b05.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/hk2-locator-2.5.0-b05.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/hk2-utils-2.5.0-b05.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/jackson-annotations-2.8.0.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/jackson-annotations-2.8.5.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/jackson-core-2.8.5.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/jackson-databind-2.8.5.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/jackson-jaxrs-base-2.8.5.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/jackson-jaxrs-json-provider-2.8.5.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/jackson-module-jaxb-annotations-2.8.5.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/javassist-3.20.0-GA.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/javax.annotation-api-1.2.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/javax.inject-1.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/javax.inject-2.5.0-b05.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/javax.servlet-api-3.1.0.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/javax.ws.rs-api-2.0.1.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/jersey-client-2.24.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/jersey-common-2.24.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/jersey-container-servlet-2.24.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/jersey-container-servlet-core-2.24.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/jersey-guava-2.24.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/jersey-media-jaxb-2.24.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/jersey-server-2.24.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/jetty-continuation-9.2.15.v20160210.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/jetty-http-9.2.15.v20160210.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/jetty-io-9.2.15.v20160210.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/jetty-security-9.2.15.v20160210.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/jetty-server-9.2.15.v20160210.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/jetty-servlet-9.2.15.v20160210.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/jetty-servlets-9.2.15.v20160210.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/jetty-util-9.2.15.v20160210.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/jopt-simple-5.0.3.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/kafka-clients-0.10.2.0.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/kafka-log4j-appender-0.10.2.0.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/kafka-streams-0.10.2.0.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/kafka-streams-examples-0.10.2.0.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/kafka-tools-0.10.2.0.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/kafka_2.11-0.10.2.0-sources.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/kafka_2.11-0.10.2.0-test-sources.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/kafka_2.11-0.10.2.0.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/log4j-1.2.17.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/lz4-1.3.0.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/metrics-core-2.2.0.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/osgi-resource-locator-1.0.1.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/reflections-0.9.10.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/rocksdbjni-5.0.1.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/scala-library-2.11.8.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/scala-parser-combinators_2.11-1.0.4.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/slf4j-api-1.7.21.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/slf4j-log4j12-1.7.21.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/snappy-java-1.1.2.6.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/validation-api-1.1.0.Final.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/zkclient-0.10.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/zookeeper-3.4.9.jar (org.apache.zookeeper.server.ZooKeeperServer) [2017-03-15 14:11:38,729] INFO Server environment:java.library.path=/Users/lidong/Library/Java/Extensions:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java:. (org.apache.zookeeper.server.ZooKeeperServer) [2017-03-15 14:11:38,729] INFO Server environment:java.io.tmpdir=/var/folders/vl/zhy0vf8141vb8y5f6yx0dgrr0000gn/T/ (org.apache.zookeeper.server.ZooKeeperServer) [2017-03-15 14:11:38,729] INFO Server environment:java.compiler=<NA> (org.apache.zookeeper.server.ZooKeeperServer) [2017-03-15 14:11:38,729] INFO Server environment:os.name=Mac OS X (org.apache.zookeeper.server.ZooKeeperServer) [2017-03-15 14:11:38,729] INFO Server environment:os.arch=x86_64 (org.apache.zookeeper.server.ZooKeeperServer) [2017-03-15 14:11:38,729] INFO Server environment:os.version=10.12.3 (org.apache.zookeeper.server.ZooKeeperServer) [2017-03-15 14:11:38,729] INFO Server environment:user.name=lidong (org.apache.zookeeper.server.ZooKeeperServer) [2017-03-15 14:11:38,729] INFO Server environment:user.home=/Users/lidong (org.apache.zookeeper.server.ZooKeeperServer) [2017-03-15 14:11:38,729] INFO Server environment:user.dir=/usr/local/Cellar/kafka/0.10.2.0 (org.apache.zookeeper.server.ZooKeeperServer) [2017-03-15 14:11:38,743] INFO tickTime set to 3000 (org.apache.zookeeper.server.ZooKeeperServer) [2017-03-15 14:11:38,743] INFO minSessionTimeout set to -1 (org.apache.zookeeper.server.ZooKeeperServer) [2017-03-15 14:11:38,743] INFO maxSessionTimeout set to -1 (org.apache.zookeeper.server.ZooKeeperServer) [2017-03-15 14:11:38,769] INFO binding to port 0.0.0.0/0.0.0.0:2181 (org.apache.zookeeper.server.NIOServerCnxnFactory) 4.启动 kafka 服务 lidongdeMacBook-Pro:~ lidong$ cd /usr/local/Cellar/kafka/0.10.2.0 lidongdeMacBook-Pro:0.10.2.0 lidong$ ./bin/kafka-server-start /usr/local/etc/kafka/server.properties & [1] 4751 lidongdeMacBook-Pro:0.10.2.0 lidong$ [2017-03-15 14:13:36,327] INFO KafkaConfig values: advertised.host.name = null advertised.listeners = null advertised.port = null authorizer.class.name = auto.create.topics.enable = true auto.leader.rebalance.enable = true background.threads = 10 broker.id = 0 broker.id.generation.enable = true broker.rack = null compression.type = producer connections.max.idle.ms = 600000 controlled.shutdown.enable = true controlled.shutdown.max.retries = 3 controlled.shutdown.retry.backoff.ms = 5000 controller.socket.timeout.ms = 30000 create.topic.policy.class.name = null default.replication.factor = 1 delete.topic.enable = false fetch.purgatory.purge.interval.requests = 1000 group.max.session.timeout.ms = 300000 group.min.session.timeout.ms = 6000 host.name = inter.broker.listener.name = null inter.broker.protocol.version = 0.10.2-IV0 leader.imbalance.check.interval.seconds = 300 leader.imbalance.per.broker.percentage = 10 listener.security.protocol.map = SSL:SSL,SASL_PLAINTEXT:SASL_PLAINTEXT,TRACE:TRACE,SASL_SSL:SASL_SSL,PLAINTEXT:PLAINTEXT listeners = null log.cleaner.backoff.ms = 15000 log.cleaner.dedupe.buffer.size = 134217728 log.cleaner.delete.retention.ms = 86400000 log.cleaner.enable = true log.cleaner.io.buffer.load.factor = 0.9 log.cleaner.io.buffer.size = 524288 log.cleaner.io.max.bytes.per.second = 1.7976931348623157E308 log.cleaner.min.cleanable.ratio = 0.5 log.cleaner.min.compaction.lag.ms = 0 log.cleaner.threads = 1 log.cleanup.policy = [delete] log.dir = /tmp/kafka-logs log.dirs = /usr/local/var/lib/kafka-logs log.flush.interval.messages = 9223372036854775807 log.flush.interval.ms = null log.flush.offset.checkpoint.interval.ms = 60000 log.flush.scheduler.interval.ms = 9223372036854775807 log.index.interval.bytes = 4096 log.index.size.max.bytes = 10485760 log.message.format.version = 0.10.2-IV0 log.message.timestamp.difference.max.ms = 9223372036854775807 log.message.timestamp.type = CreateTime log.preallocate = false log.retention.bytes = -1 log.retention.check.interval.ms = 300000 log.retention.hours = 168 log.retention.minutes = null log.retention.ms = null log.roll.hours = 168 log.roll.jitter.hours = 0 log.roll.jitter.ms = null log.roll.ms = null log.segment.bytes = 1073741824 log.segment.delete.delay.ms = 60000 max.connections.per.ip = 2147483647 max.connections.per.ip.overrides = message.max.bytes = 1000012 metric.reporters = [] metrics.num.samples = 2 metrics.recording.level = INFO metrics.sample.window.ms = 30000 min.insync.replicas = 1 num.io.threads = 8 num.network.threads = 3 num.partitions = 1 num.recovery.threads.per.data.dir = 1 num.replica.fetchers = 1 offset.metadata.max.bytes = 4096 offsets.commit.required.acks = -1 offsets.commit.timeout.ms = 5000 offsets.load.buffer.size = 5242880 offsets.retention.check.interval.ms = 600000 offsets.retention.minutes = 1440 offsets.topic.compression.codec = 0 offsets.topic.num.partitions = 50 offsets.topic.replication.factor = 3 offsets.topic.segment.bytes = 104857600 port = 9092 principal.builder.class = class org.apache.kafka.common.security.auth.DefaultPrincipalBuilder producer.purgatory.purge.interval.requests = 1000 queued.max.requests = 500 quota.consumer.default = 9223372036854775807 quota.producer.default = 9223372036854775807 quota.window.num = 11 quota.window.size.seconds = 1 replica.fetch.backoff.ms = 1000 replica.fetch.max.bytes = 1048576 replica.fetch.min.bytes = 1 replica.fetch.response.max.bytes = 10485760 replica.fetch.wait.max.ms = 500 replica.high.watermark.checkpoint.interval.ms = 5000 replica.lag.time.max.ms = 10000 replica.socket.receive.buffer.bytes = 65536 replica.socket.timeout.ms = 30000 replication.quota.window.num = 11 replication.quota.window.size.seconds = 1 request.timeout.ms = 30000 reserved.broker.max.id = 1000 sasl.enabled.mechanisms = [GSSAPI] sasl.kerberos.kinit.cmd = /usr/bin/kinit sasl.kerberos.min.time.before.relogin = 60000 sasl.kerberos.principal.to.local.rules = [DEFAULT] sasl.kerberos.service.name = null sasl.kerberos.ticket.renew.jitter = 0.05 sasl.kerberos.ticket.renew.window.factor = 0.8 sasl.mechanism.inter.broker.protocol = GSSAPI security.inter.broker.protocol = PLAINTEXT socket.receive.buffer.bytes = 102400 socket.request.max.bytes = 104857600 socket.send.buffer.bytes = 102400 ssl.cipher.suites = null ssl.client.auth = none ssl.enabled.protocols = [TLSv1.2, TLSv1.1, TLSv1] ssl.endpoint.identification.algorithm = null ssl.key.password = null ssl.keymanager.algorithm = SunX509 ssl.keystore.location = null ssl.keystore.password = null ssl.keystore.type = JKS ssl.protocol = TLS ssl.provider = null ssl.secure.random.implementation = null ssl.trustmanager.algorithm = PKIX ssl.truststore.location = null ssl.truststore.password = null ssl.truststore.type = JKS unclean.leader.election.enable = true zookeeper.connect = localhost:2181 zookeeper.connection.timeout.ms = 6000 zookeeper.session.timeout.ms = 6000 zookeeper.set.acl = false zookeeper.sync.time.ms = 2000 (kafka.server.KafkaConfig) [2017-03-15 14:13:36,390] INFO starting (kafka.server.KafkaServer) [2017-03-15 14:13:36,393] INFO Connecting to zookeeper on localhost:2181 (kafka.server.KafkaServer) [2017-03-15 14:13:36,409] INFO Starting ZkClient event thread. (org.I0Itec.zkclient.ZkEventThread) [2017-03-15 14:13:36,417] INFO Client environment:zookeeper.version=3.4.9-1757313, built on 08/23/2016 06:50 GMT (org.apache.zookeeper.ZooKeeper) [2017-03-15 14:13:36,418] INFO Client environment:host.name=192.168.1.130 (org.apache.zookeeper.ZooKeeper) [2017-03-15 14:13:36,418] INFO Client environment:java.version=1.8.0_92 (org.apache.zookeeper.ZooKeeper) [2017-03-15 14:13:36,418] INFO Client environment:java.vendor=Oracle Corporation (org.apache.zookeeper.ZooKeeper) [2017-03-15 14:13:36,418] INFO Client environment:java.home=/Library/Java/JavaVirtualMachines/jdk1.8.0_92.jdk/Contents/Home/jre (org.apache.zookeeper.ZooKeeper) [2017-03-15 14:13:36,418] INFO Client environment:java.class.path=:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/aopalliance-repackaged-2.5.0-b05.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/argparse4j-0.7.0.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/connect-api-0.10.2.0.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/connect-file-0.10.2.0.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/connect-json-0.10.2.0.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/connect-runtime-0.10.2.0.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/connect-transforms-0.10.2.0.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/guava-18.0.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/hk2-api-2.5.0-b05.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/hk2-locator-2.5.0-b05.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/hk2-utils-2.5.0-b05.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/jackson-annotations-2.8.0.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/jackson-annotations-2.8.5.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/jackson-core-2.8.5.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/jackson-databind-2.8.5.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/jackson-jaxrs-base-2.8.5.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/jackson-jaxrs-json-provider-2.8.5.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/jackson-module-jaxb-annotations-2.8.5.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/javassist-3.20.0-GA.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/javax.annotation-api-1.2.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/javax.inject-1.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/javax.inject-2.5.0-b05.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/javax.servlet-api-3.1.0.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/javax.ws.rs-api-2.0.1.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/jersey-client-2.24.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/jersey-common-2.24.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/jersey-container-servlet-2.24.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/jersey-container-servlet-core-2.24.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/jersey-guava-2.24.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/jersey-media-jaxb-2.24.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/jersey-server-2.24.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/jetty-continuation-9.2.15.v20160210.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/jetty-http-9.2.15.v20160210.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/jetty-io-9.2.15.v20160210.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/jetty-security-9.2.15.v20160210.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/jetty-server-9.2.15.v20160210.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/jetty-servlet-9.2.15.v20160210.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/jetty-servlets-9.2.15.v20160210.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/jetty-util-9.2.15.v20160210.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/jopt-simple-5.0.3.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/kafka-clients-0.10.2.0.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/kafka-log4j-appender-0.10.2.0.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/kafka-streams-0.10.2.0.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/kafka-streams-examples-0.10.2.0.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/kafka-tools-0.10.2.0.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/kafka_2.11-0.10.2.0-sources.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/kafka_2.11-0.10.2.0-test-sources.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/kafka_2.11-0.10.2.0.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/log4j-1.2.17.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/lz4-1.3.0.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/metrics-core-2.2.0.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/osgi-resource-locator-1.0.1.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/reflections-0.9.10.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/rocksdbjni-5.0.1.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/scala-library-2.11.8.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/scala-parser-combinators_2.11-1.0.4.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/slf4j-api-1.7.21.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/slf4j-log4j12-1.7.21.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/snappy-java-1.1.2.6.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/validation-api-1.1.0.Final.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/zkclient-0.10.jar:/usr/local/Cellar/kafka/0.10.2.0/libexec/bin/../libs/zookeeper-3.4.9.jar (org.apache.zookeeper.ZooKeeper) [2017-03-15 14:13:36,418] INFO Client environment:java.library.path=/Users/lidong/Library/Java/Extensions:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java:. (org.apache.zookeeper.ZooKeeper) [2017-03-15 14:13:36,418] INFO Client environment:java.io.tmpdir=/var/folders/vl/zhy0vf8141vb8y5f6yx0dgrr0000gn/T/ (org.apache.zookeeper.ZooKeeper) [2017-03-15 14:13:36,418] INFO Client environment:java.compiler=<NA> (org.apache.zookeeper.ZooKeeper) [2017-03-15 14:13:36,418] INFO Client environment:os.name=Mac OS X (org.apache.zookeeper.ZooKeeper) [2017-03-15 14:13:36,418] INFO Client environment:os.arch=x86_64 (org.apache.zookeeper.ZooKeeper) [2017-03-15 14:13:36,418] INFO Client environment:os.version=10.12.3 (org.apache.zookeeper.ZooKeeper) [2017-03-15 14:13:36,418] INFO Client environment:user.name=lidong (org.apache.zookeeper.ZooKeeper) [2017-03-15 14:13:36,418] INFO Client environment:user.home=/Users/lidong (org.apache.zookeeper.ZooKeeper) [2017-03-15 14:13:36,418] INFO Client environment:user.dir=/usr/local/Cellar/kafka/0.10.2.0 (org.apache.zookeeper.ZooKeeper) [2017-03-15 14:13:36,419] INFO Initiating client connection, connectString=localhost:2181 sessionTimeout=6000 watcher=org.I0Itec.zkclient.ZkClient@1139b2f3 (org.apache.zookeeper.ZooKeeper) [2017-03-15 14:13:36,437] INFO Waiting for keeper state SyncConnected (org.I0Itec.zkclient.ZkClient) [2017-03-15 14:13:36,441] INFO Opening socket connection to server localhost/0:0:0:0:0:0:0:1:2181. Will not attempt to authenticate using SASL (unknown error) (org.apache.zookeeper.ClientCnxn) [2017-03-15 14:13:36,506] INFO Socket connection established to localhost/0:0:0:0:0:0:0:1:2181, initiating session (org.apache.zookeeper.ClientCnxn) [2017-03-15 14:13:36,593] INFO Session establishment complete on server localhost/0:0:0:0:0:0:0:1:2181, sessionid = 0x15ad097e0a90000, negotiated timeout = 6000 (org.apache.zookeeper.ClientCnxn) [2017-03-15 14:13:36,595] INFO zookeeper state changed (SyncConnected) (org.I0Itec.zkclient.ZkClient) [2017-03-15 14:13:36,702] INFO Cluster ID = hgqzqaTdSI-KIydvLD0TQw (kafka.server.KafkaServer) [2017-03-15 14:13:36,705] WARN No meta.properties file under dir /usr/local/var/lib/kafka-logs/meta.properties (kafka.server.BrokerMetadataCheckpoint) [2017-03-15 14:13:36,747] INFO [ThrottledRequestReaper-Fetch], Starting (kafka.server.ClientQuotaManager$ThrottledRequestReaper) [2017-03-15 14:13:36,748] INFO [ThrottledRequestReaper-Produce], Starting (kafka.server.ClientQuotaManager$ThrottledRequestReaper) [2017-03-15 14:13:36,781] INFO Log directory '/usr/local/var/lib/kafka-logs' not found, creating it. (kafka.log.LogManager) [2017-03-15 14:13:36,796] INFO Loading logs. (kafka.log.LogManager) [2017-03-15 14:13:36,802] INFO Logs loading complete in 6 ms. (kafka.log.LogManager) [2017-03-15 14:13:36,915] INFO Starting log cleanup with a period of 300000 ms. (kafka.log.LogManager) [2017-03-15 14:13:36,917] INFO Starting log flusher with a default period of 9223372036854775807 ms. (kafka.log.LogManager) [2017-03-15 14:13:36,965] INFO Awaiting socket connections on 0.0.0.0:9092. (kafka.network.Acceptor) [2017-03-15 14:13:36,968] INFO [Socket Server on Broker 0], Started 1 acceptor threads (kafka.network.SocketServer) [2017-03-15 14:13:36,990] INFO [ExpirationReaper-0], Starting (kafka.server.DelayedOperationPurgatory$ExpiredOperationReaper) [2017-03-15 14:13:36,991] INFO [ExpirationReaper-0], Starting (kafka.server.DelayedOperationPurgatory$ExpiredOperationReaper) [2017-03-15 14:13:37,034] INFO Creating /controller (is it secure? false) (kafka.utils.ZKCheckedEphemeral) [2017-03-15 14:13:37,040] INFO Result of znode creation is: OK (kafka.utils.ZKCheckedEphemeral) [2017-03-15 14:13:37,040] INFO 0 successfully elected as leader (kafka.server.ZookeeperLeaderElector) [2017-03-15 14:13:37,139] INFO [ExpirationReaper-0], Starting (kafka.server.DelayedOperationPurgatory$ExpiredOperationReaper) [2017-03-15 14:13:37,143] INFO [ExpirationReaper-0], Starting (kafka.server.DelayedOperationPurgatory$ExpiredOperationReaper) [2017-03-15 14:13:37,145] INFO [ExpirationReaper-0], Starting (kafka.server.DelayedOperationPurgatory$ExpiredOperationReaper) [2017-03-15 14:13:37,158] INFO [GroupCoordinator 0]: Starting up. (kafka.coordinator.GroupCoordinator) [2017-03-15 14:13:37,159] INFO [GroupCoordinator 0]: Startup complete. (kafka.coordinator.GroupCoordinator) [2017-03-15 14:13:37,161] INFO [Group Metadata Manager on Broker 0]: Removed 0 expired offsets in 2 milliseconds. (kafka.coordinator.GroupMetadataManager) [2017-03-15 14:13:37,187] INFO Will not load MX4J, mx4j-tools.jar is not in the classpath (kafka.utils.Mx4jLoader$) [2017-03-15 14:13:37,218] INFO Creating /brokers/ids/0 (is it secure? false) (kafka.utils.ZKCheckedEphemeral) [2017-03-15 14:13:37,225] INFO Result of znode creation is: OK (kafka.utils.ZKCheckedEphemeral) [2017-03-15 14:13:37,227] INFO Registered broker 0 at path /brokers/ids/0 with addresses: EndPoint(192.168.1.130,9092,ListenerName(PLAINTEXT),PLAINTEXT) (kafka.utils.ZkUtils) [2017-03-15 14:13:37,229] WARN No meta.properties file under dir /usr/local/var/lib/kafka-logs/meta.properties (kafka.server.BrokerMetadataCheckpoint) [2017-03-15 14:13:37,236] INFO New leader is 0 (kafka.server.ZookeeperLeaderElector$LeaderChangeListener) [2017-03-15 14:13:37,259] INFO Kafka version : 0.10.2.0 (org.apache.kafka.common.utils.AppInfoParser) [2017-03-15 14:13:37,259] INFO Kafka commitId : 576d93a8dc0cf421 (org.apache.kafka.common.utils.AppInfoParser) [2017-03-15 14:13:37,260] INFO [Kafka Server 0], started (kafka.server.KafkaServer) 5.创建topic 让我们使用单个分区和只有一个副本创建一个名为“test”的主题 ./bin/kafka-topics --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic test Created topic "test". 6.查看创建的topic 我们现在可以看到该主题,如果我们运行list topic命令: ./bin/kafka-topics --list --zookeeper localhost:2181 test 7.发送一些消息 Kafka提供了一个命令行客户端,它将从文件或标准输入接收输入,并将其作为消息发送到Kafka集群。默认情况下,每行都将作为单独的消息发送。 运行生产者,然后在控制台中键入一些消息发送到服务器。 > bin / kafka-console-producer.sh --broker-list localhost:9092 --topic test 第一条消息 第二条消息 8.消费消息 Kafka还有一个命令行消费者,将消息转储到标准输出。 ./bin/kafka-console-consumer --bootstrap-server localhost:9092 --topic test --from-beginning 第一条消息 第二条消息 如果你有上面的每个命令运行在不同的终端,那么你现在应该能够输入消息到生产者终端,看到他们出现在消费者终端。 所有命令行工具都有其他选项; 运行没有参数的命令将显示详细记录它们的使用信息。
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010046908/article/details/62217578 1.Lambda 表达式 Lambda 表达式,也可称为闭包,它是推动 Java 8 发布的最重要新特性。 Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中)。 使用 Lambda 表达式可以使代码变的更加简洁紧凑。 2.语法 lambda 表达式的语法格式如下: (parameters) -> expression 或 (parameters) ->{ statements; } 3.lambda表达式的重要特征: 列表内容可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。 可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。 可选的大括号:如果主体包含了一个语句,就不需要使用大括号。 可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值。 public class Java8Tester { public static void main(String args[]){ Java8Tester tester = new Java8Tester(); // 类型声明 MathOperation addition = (int a, int b) -> a + b; // 不用类型声明 MathOperation subtraction = (a, b) -> a - b; // 大括号中的返回语句 MathOperation multiplication = (int a, int b) -> { return a * b; }; // 没有大括号及返回语句 MathOperation division = (int a, int b) -> a / b; System.out.println("10 + 5 = " + tester.operate(10, 5, addition)); System.out.println("10 - 5 = " + tester.operate(10, 5, subtraction)); System.out.println("10 x 5 = " + tester.operate(10, 5, multiplication)); System.out.println("10 / 5 = " + tester.operate(10, 5, division)); // 不用括号 GreetingService greetService1 = message -> System.out.println("Hello " + message); // 用括号 GreetingService greetService2 = (message) -> System.out.println("Hello " + message); greetService1.sayMessage("W3CSchool"); greetService2.sayMessage("Google"); } interface MathOperation { int operation(int a, int b); } interface GreetingService { void sayMessage(String message); } private int operate(int a, int b, MathOperation mathOperation){ return mathOperation.operation(a, b); } } 执行以上脚本,输出结果为: 10 + 5 = 15 10 - 5 = 5 10 x 5 = 50 10 / 5 = 2 Hello W3CSchool Hello Google 使用 Lambda 表达式需要注意以下两点: Lambda 表达式主要用来定义行内执行的方法类型接口,例如,一个简单方法接口。在上面例子中,我们使用各种类型的Lambda表达式来定义MathOperation接口的方法。然后我们定义了sayMessage的执行。 Lambda 表达式免去了使用匿名方法的麻烦,并且给予Java简单但是强大的函数化的编程能力。 5.变量作用域 lambda 表达式只能引用 final 或 final 局部变量,这就是说不能在 lambda 内部修改定义在域外的变量,否则会编译错误。 在 Java8Tester.java 文件输入以下代码: public class Java8Tester { final static String salutation = "Hello! "; public static void main(String args[]){ GreetingService greetService1 = message -> System.out.println(salutation + message); greetService1.sayMessage("W3CSchool"); } interface GreetingService { void sayMessage(String message); } } 执行以上脚本,输出结果为: Hello! W3CSchool
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010046908/article/details/62216945 1. Optional类的简介 Optional 类是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。 Optional 是个容器:它可以保存类型T的值,或者仅仅保存null。Optional提供很多有用的方法,这样我们就不用显式进行空值检测。 Optional 类的引入很好的解决空指针异常。 2.类声明 以下是一个 java.util.Optional 类的声明: public final class Optional<T> extends Object 3.类方法 static Optional empty() 返回空的 Optional 实例。 boolean equals(Object obj) 判断其他对象是否等于 Optional。 Optional filter(Predicate< ? super predicate如果值存在,并且这个值匹配给定的 predicate,返回一个Optional用以描述这个值,否则返回一个空的Optional。 Optional flatMap(Function< ? super T,Optional> mapper) 如果值存在,返回基于Optional包含的映射方法的值,否则返回一个空的Optional T get() 如果在这个Optional中包含这个值,返回值,否则抛出异常:NoSuchElementException int hashCode() 返回存在值的哈希码,如果值不存在 返回 0。 void ifPresent(Consumer< ? super T> consumer) 如果值存在则使用该值调用 consumer , 否则不做任何事情。 boolean isPresent() 如果值存在则方法会返回true,否则返回 false。 Optional map(Function< ? super T, ? extends U> mapper) 如果存在该值,提供的映射方法,如果返回非null,返回一个Optional描述结果。 static Optional of(T value) 返回一个指定非null值的Optional。 static Optional ofNullable(T value) 如果为非空,返回 Optional 描述的指定值,否则返回空的 Optional。 T orElse(T other) 如果存在该值,返回值, 否则返回 other。 T orElseGet(Supplier< ? extends T> other) 如果存在该值,返回值, 否则触发 other,并返回 other 调用的结果。 T orElseThrow(Supplier< ? extends X> exceptionSupplier)如果存在该值,返回包含的值,否则抛出由 Supplier 继承的异常 String toString() 返回一个Optional的非空字符串,用来调试。 3.Optional 实例 我们可以通过以下实例来更好的了解 Optional 类的使用: import java.util.Optional; public class Java8Tester { public static void main(String args[]){ Java8Tester java8Tester = new Java8Tester(); Integer value1 = null; Integer value2 = new Integer(10); // Optional.ofNullable - 允许传递为 null 参数 Optional<Integer> a = Optional.ofNullable(value1); // Optional.of - 如果传递的参数是 null,抛出异常 NullPointerException Optional<Integer> b = Optional.of(value2); System.out.println(java8Tester.sum(a,b)); } public Integer sum(Optional<Integer> a, Optional<Integer> b){ // Optional.isPresent - 判断值是否存在 System.out.println("第一个参数值存在: " + a.isPresent()); System.out.println("第二个参数值存在: " + b.isPresent()); // Optional.orElse - 如果值存在,返回它,否则返回默认值 Integer value1 = a.orElse(new Integer(0)); //Optional.get - 获取值,值需要存在 Integer value2 = b.get(); return value1 + value2; } } 结果: 第一个参数值存在: false 第二个参数值存在: true 10
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010046908/article/details/61916389 1 Consul简介 Consul 是 HashiCorp 公司推出的开源工具,用于实现分布式系统的服务发现与配置。与其他分布式服务注册与发现的方案,Consul的方案更“一站式”,内置了服务注册与发现框 架、分布一致性协议实现、健康检查、Key/Value存储、多数据中心方案,不再需要依赖其他工具(比如ZooKeeper等)。使用起来也较 为简单。Consul使用Go语言编写,因此具有天然可移植性(支持Linux、windows和Mac OS X);安装包仅包含一个可执行文件,方便部署,与Docker等轻量级容器可无缝配合 。 2 Consul安装 安装环境: mac:64bit(查看mac位数:打开终端–>”uname -a”) consul_0.6.4_darwin_amd64.zip和consul_0.6.4_web_ui.zip,从consul官网https://www.consul.io/downloads.html进行下载就好(选择好OS和位数) 1、解压consul_0.6.4_darwin_amd64.zip 2、将解压后的二进制文件consul(上边画红框的部分拷贝到/usr/local/bin下) sudo scp consul /usr/local/bin/ 说明:使用sudo是因为权限问题。 3、查看是否安装成功, 直接在家目录下执行consul命令即可。出现如下结果,表示安装成功。 lidongdeMacBook-Pro:bin lidong$ consul usage: consul [--version] [--help] <command> [<args>] Available commands are: agent Runs a Consul agent configtest Validate config file event Fire a new event exec Executes a command on Consul nodes force-leave Forces a member of the cluster to enter the "left" state info Provides debugging information for operators join Tell Consul agent to join cluster keygen Generates a new encryption key keyring Manages gossip layer encryption keys kv Interact with the key-value store leave Gracefully leaves the Consul cluster and shuts down lock Execute a command holding a lock maint Controls node or service maintenance mode members Lists the members of a Consul cluster monitor Stream logs from a Consul agent operator Provides cluster-level tools for Consul operators reload Triggers the agent to reload configuration files rtt Estimates network round trip time between nodes snapshot Saves, restores and inspects snapshots of Consul server state version Prints the Consul version watch Watch for changes in Consul 3 Consul 启动 1、执行命令 ./consul agent -dev # -dev表示开发模式运行,另外还有-server表示服务模式运行 查看显示结果: lidongdeMacBook-Pro:bin lidong$ consul agent -dev ==> Starting Consul agent... ==> Starting Consul agent RPC... ==> Consul agent running! Version: 'v0.7.5' Node ID: 'c67a8d03-deac-35b8-8f68-06ff7b687215' Node name: 'lidongdeMacBook-Pro.local' Datacenter: 'dc1' Server: true (bootstrap: false) Client Addr: 127.0.0.1 (HTTP: 8500, HTTPS: -1, DNS: 8600, RPC: 8400) Cluster Addr: 127.0.0.1 (LAN: 8301, WAN: 8302) Gossip encrypt: false, RPC-TLS: false, TLS-Incoming: false Atlas: <disabled> ==> Log data will now stream in as it occurs: 2017/03/13 12:44:31 [DEBUG] Using unique ID "c67a8d03-deac-35b8-8f68-06ff7b687215" from host as node ID 2017/03/13 12:44:31 [INFO] raft: Initial configuration (index=1): [{Suffrage:Voter ID:127.0.0.1:8300 Address:127.0.0.1:8300}] 2017/03/13 12:44:31 [INFO] raft: Node at 127.0.0.1:8300 [Follower] entering Follower state (Leader: "") 2017/03/13 12:44:31 [INFO] serf: EventMemberJoin: lidongdeMacBook-Pro.local 127.0.0.1 2017/03/13 12:44:31 [INFO] consul: Adding LAN server lidongdeMacBook-Pro.local (Addr: tcp/127.0.0.1:8300) (DC: dc1) 2017/03/13 12:44:31 [INFO] serf: EventMemberJoin: lidongdeMacBook-Pro.local.dc1 127.0.0.1 2017/03/13 12:44:31 [INFO] consul: Adding WAN server lidongdeMacBook-Pro.local.dc1 (Addr: tcp/127.0.0.1:8300) (DC: dc1) 2017/03/13 12:44:36 [WARN] raft: Heartbeat timeout from "" reached, starting election 2017/03/13 12:44:36 [INFO] raft: Node at 127.0.0.1:8300 [Candidate] entering Candidate state in term 2 2017/03/13 12:44:36 [DEBUG] raft: Votes needed: 1 2017/03/13 12:44:36 [DEBUG] raft: Vote granted from 127.0.0.1:8300 in term 2. Tally: 1 2017/03/13 12:44:36 [INFO] raft: Election won. Tally: 1 2017/03/13 12:44:36 [INFO] raft: Node at 127.0.0.1:8300 [Leader] entering Leader state 2017/03/13 12:44:36 [INFO] consul: cluster leadership acquired 2017/03/13 12:44:36 [INFO] consul: New leader elected: lidongdeMacBook-Pro.local 2017/03/13 12:44:36 [DEBUG] consul: reset tombstone GC to index 3 2017/03/13 12:44:36 [INFO] consul: member 'lidongdeMacBook-Pro.local' joined, marking health alive 2017/03/13 12:44:37 [INFO] agent: Synced service 'consul' 2017/03/13 12:44:37 [DEBUG] agent: Node info in sync 2017/03/13 12:44:39 [INFO] agent.rpc: Accepted client: 127.0.0.1:54095 2017/03/13 12:44:50 [DEBUG] http: Request GET /v1/catalog/datacenters (580.862µs) from=127.0.0.1:54114 2017/03/13 12:44:50 [DEBUG] http: Request GET /v1/catalog/datacenters (34.955µs) from=127.0.0.1:54114 2017/03/13 12:44:50 [DEBUG] http: Request GET /v1/internal/ui/nodes?dc=dc1&token=<hidden> (1.024476ms) from=127.0.0.1:54112 2017/03/13 12:44:50 [DEBUG] http: Request GET /v1/coordinate/nodes?dc=dc1&token=<hidden> (361.25µs) from=127.0.0.1:54111 2017/03/13 12:44:50 [DEBUG] http: Request GET /v1/internal/ui/services?dc=dc1&token=<hidden> (315.405µs) from=127.0.0.1:54111 2017/03/13 12:44:53 [DEBUG] http: Request GET /v1/internal/ui/nodes?dc=dc1&token=<hidden> (140.095µs) from=127.0.0.1:54111 2017/03/13 12:44:54 [DEBUG] http: Request GET /v1/kv/?keys&seperator=/&dc=dc1&token=<hidden> (1.368486ms) from=127.0.0.1:54111 2017/03/13 12:44:55 [DEBUG] http: Request GET /v1/acl/list?dc=dc1&token=<hidden> (9.253µs) from=127.0.0.1:54111 2017/03/13 12:44:57 [DEBUG] http: Request GET /v1/internal/ui/services?dc=dc1&token=<hidden> (109.196µs) from=127.0.0.1:54111 2017/03/13 12:44:59 [DEBUG] http: Request GET /v1/internal/ui/services?dc=dc1&token=<hidden> (98.48µs) from=127.0.0.1:54111 2017/03/13 12:45:58 [DEBUG] agent: Service 'consul' in sync 2017/03/13 12:45:58 [DEBUG] agent: Node info in sync 2017/03/13 12:47:55 [DEBUG] agent: Service 'consul' in sync 2017/03/13 12:47:55 [DEBUG] agent: Node info in sync 说明: -dev(该节点的启动不能用于生产环境,因为该模式下不会持久化任何状态),该启动模式仅仅是为了快速便捷的启动单节点consul 该节点处于server模式 该节点是leader 该节点是一个健康节点 2、查看consul cluster中的每一个consul节点的信息 lidongdeMacBook-Pro:~ lidong$ consul members Node Address Status Type Build Protocol DC lidongdeMacBook-Pro.local 127.0.0.1:8301 alive server 0.7.5 2 dc1 说明: Address:节点地址 Status:alive表示节点健康 Type:server运行状态是server状态 DC:dc1表示该节点属于DataCenter1 注意: members命令的输出是基于gossip协议的,并且是最终一致的(也就是说,某一个时刻你去运用该命令查到的consul节点的状态信息可能是有误的) 输入http://127.0.0.1:8500/ui/ 访问Consul,可查看到如下界面: 4 停止服务(优雅退出) 命令:CTRL+C ^C==> Caught signal: interrupt 2017/03/13 12:50:42 [DEBUG] http: Shutting down http server (127.0.0.1:8500) 2017/03/13 12:50:42 [INFO] agent: requesting shutdown 2017/03/13 12:50:42 [INFO] consul: shutting down server 2017/03/13 12:50:42 [WARN] serf: Shutdown without a Leave 2017/03/13 12:50:42 [WARN] serf: Shutdown without a Leave 2017/03/13 12:50:42 [ERR] dns: error starting tcp server: accept tcp 127.0.0.1:8600: use of closed network connection 2017/03/13 12:50:42 [INFO] agent: shutdown complete 说明: 该节点离开后,会通知cluster中的其他节点 注意: 安装部分参考自:https://www.consul.io/intro/getting-started/install.html 启动和停止服务部分参考自:https://www.consul.io/intro/getting-started/agent.html 5 Consul常用命令 命令 解释 示例 agent 运行一个consul agent consul agent -dev join 将agent加入到consul集群 consul join IP members 列出consul cluster集群中的members consul members leave 将节点移除所在集群 consul leave consul agent 命令详解 输入consul agent --help ,可以看到consul agent 的选项,如下: -advertise=addr Sets the advertise address to use -advertise-wan=addr Sets address to advertise on wan instead of advertise addr -atlas=org/name Sets the Atlas infrastructure name, enables SCADA. -atlas-join Enables auto-joining the Atlas cluster -atlas-token=token Provides the Atlas API token -atlas-endpoint=1.2.3.4 The address of the endpoint for Atlas integration. -bootstrap Sets server to bootstrap mode -bind=0.0.0.0 Sets the bind address for cluster communication -http-port=8500 Sets the HTTP API port to listen on -bootstrap-expect=0 Sets server to expect bootstrap mode. -client=127.0.0.1 Sets the address to bind for client access. This includes RPC, DNS, HTTP and HTTPS (if configured) -config-file=foo Path to a JSON file to read configuration from. This can be specified multiple times. -config-dir=foo Path to a directory to read configuration files from. This will read every file ending in ".json" as configuration in this directory in alphabetical order. This can be specified multiple times. -data-dir=path Path to a data directory to store agent state -dev Starts the agent in development mode. -recursor=1.2.3.4 Address of an upstream DNS server. Can be specified multiple times. -dc=east-aws Datacenter of the agent (deprecated: use 'datacenter' instead). -datacenter=east-aws Datacenter of the agent. -encrypt=key Provides the gossip encryption key -join=1.2.3.4 Address of an agent to join at start time. Can be specified multiple times. -join-wan=1.2.3.4 Address of an agent to join -wan at start time. Can be specified multiple times. -retry-join=1.2.3.4 Address of an agent to join at start time with retries enabled. Can be specified multiple times. -retry-interval=30s Time to wait between join attempts. -retry-max=0 Maximum number of join attempts. Defaults to 0, which will retry indefinitely. -retry-join-wan=1.2.3.4 Address of an agent to join -wan at start time with retries enabled. Can be specified multiple times. -retry-interval-wan=30s Time to wait between join -wan attempts. -retry-max-wan=0 Maximum number of join -wan attempts. Defaults to 0, which will retry indefinitely. -log-level=info Log level of the agent. -node=hostname Name of this node. Must be unique in the cluster -protocol=N Sets the protocol version. Defaults to latest. -rejoin Ignores a previous leave and attempts to rejoin the cluster. -server Switches agent to server mode. -syslog Enables logging to syslog -ui Enables the built-in static web UI server -ui-dir=path Path to directory containing the Web UI resources -pid-file=path Path to file to store agent PID consul agent 命令的常用选项,如下: -data-dir 作用:指定agent储存状态的数据目录 这是所有agent都必须的 对于server尤其重要,因为他们必须持久化集群的状态 -config-dir 作用:指定service的配置文件和检查定义所在的位置 通常会指定为”某一个路径/consul.d”(通常情况下,.d表示一系列配置文件存放的目录) -config-file 作用:指定一个要装载的配置文件 该选项可以配置多次,进而配置多个配置文件(后边的会合并前边的,相同的值覆盖) -dev 作用:创建一个开发环境下的server节点 该参数配置下,不会有任何持久化操作,即不会有任何数据写入到磁盘 这种模式不能用于生产环境(因为第二条) -bootstrap-expect 作用:该命令通知consul server我们现在准备加入的server节点个数,该参数是为了延迟日志复制的启动直到我们指定数量的server节点成功的加入后启动。 -node 作用:指定节点在集群中的名称 该名称在集群中必须是唯一的(默认采用机器的host) 推荐:直接采用机器的IP -bind 作用:指明节点的IP地址 有时候不指定绑定IP,会报Failed to get advertise address: Multiple private IPs found. Please configure one. 的异常 -server 作用:指定节点为server 每个数据中心(DC)的server数推荐至少为1,至多为5 所有的server都采用raft一致性算法来确保事务的一致性和线性化,事务修改了集群的状态,且集群的状态保存在每一台server上保证可用性 server也是与其他DC交互的门面(gateway) -client 作用:指定节点为client,指定客户端接口的绑定地址,包括:HTTP、DNS、RPC 默认是127.0.0.1,只允许回环接口访问 若不指定为-server,其实就是-client -join 作用:将节点加入到集群 -datacenter(老版本叫-dc,-dc已经失效) 作用:指定机器加入到哪一个数据中心中 使用-client 参数可指定允许客户端使用什么ip去访问,例如-client 192.168.11.143 表示可以使用http://192.168.11.143:8500/ui 去访问。 我们尝试一下: consul agent -dev -client 192.168.11.143 发现果然可以使用http://192.168.11.143:8500/ui 访问了。 6 Consul 的高可用 Consul Cluster集群架构图如下: 这边准备了三台CentOS 7的虚拟机,主机规划如下,供参考: 主机名称 IP 作用 是否允许远程访问 node0 192.168.11.143 consul server 是 node1 192.168.11.144 consul client 否 node2 192.168.11.145 consul client 是 6.1 搭建步骤: 启动node0机器上的Consul(node0机器上执行): consul agent -data-dir /tmp/node0 -node=node0 -bind=192.168.11.143 -datacenter=dc1 -ui -client=192.168.11.143 -server -bootstrap-expect 1 启动node1机器上的Consul(node1机器上执行): consul agent -data-dir /tmp/node1 -node=node1 -bind=192.168.11.144 -datacenter=dc1 -ui 启动node2机器上的Consul(node2机器上执行): consul agent -data-dir /tmp/node2 -node=node2 -bind=192.168.11.145 -datacenter=dc1 -ui -client=192.168.11.145 将node1节点加入到node0上(node1机器上执行): consul join 192.168.11.143 将node2节点加入到node0上(node2机器上执行): consul join -rpc-addr=192.168.11.145:8400 192.168.11.143 这样一个简单的Consul集群就搭建完成了,在node1上查看当前集群节点: consul members -rpc-addr=192.168.11.143:8400 结果如下: Node Address Status Type Build Protocol DC node0 192.168.11.143:8301 alive server 0.7.0 2 dc1 node1 192.168.11.144:8301 alive client 0.7.0 2 dc1 node2 192.168.11.145:8301 alive client 0.7.0 2 dc1 说明集群已经搭建成功了。 我们分析一下,为什么第5步和第6步需要加-rpc-addr 选项,而第4步不需要加任何选项呢?原因是-client 指定了客户端接口的绑定地址,包括:HTTP、DNS、RPC,而consul join 、consul members 都是通过RPC与Consul交互的。 6.2 访问集群 如上,我们三个节点都加了-ui 参数启动了内建的界面。我们可以通过:http://192.168.11.143:8500/ui/ 或者http://192.168.11.145:8500/ui/进行访问,也可以在node1机器上通过http://127.0.0.1:8500/ui/ 进行访问,原因是node1没有开启远程访问 ,三种访问方式结果是一致的,如下: 7 参考文档: Consul官方文档:https://www.consul.io/intro/getting-started/install.html Consul 系列博文:http://www.cnblogs.com/java-zhao/archive/2016/04/13/5387105.html 使用consul实现分布式服务注册和发现:http://www.tuicool.com/articles/M3QFven
文章来源:https://segmentfault.com/a/1190000006785852 今天给大家带来的是 数人云工程师文权在高效运维线上群的分享实录。从传统单体应用架构到微服务架构,安全问题一直是人们关注的重点,文权与大家分享了关于微服务访问安全设计方案的探索与实践。 我们首先从传统单体应用架构下的访问安全设计说起,然后分析现代微服务架构下,访问安全涉及的原则,接着讨论目前常用的几种微服务架构下的访问安全设计方案。最后,详析Spring Cloud微服务架构下如何解决访问安全的问题。 1.传统单体应用的访问安全设计 上面的示意图展示了单体应用的访问逻辑。用户通过客户端发出http或者https请求,经过负载均衡后,单体应用收到请求。接着经过auth层,进行身份验证和权限批准,这里,一般会有跟后端数据库的交互。通过后,将请求分发到对应的功能逻辑层中去。完成相关操作后,返回结果给客户端。 1.1传统单体应用的访问安全设计——原则 从以上分析可以看到,传统单体应用的访问安全设计原则为: 第一,每次的用户请求都需要验证是否安全,这里可以分两种情况: 一种是没有session的请求,需要经过几个步骤完成session化。一般为验证当前用户的credential,获取当前用户的identity,这两步都需要访问数据库等持久化对象来完成,最后一步是为当前可用创建session,返回给客户端后,启用该session。 另一种是有session的请求,只需验证请求中当前session的有效性,即可继续请求。 第二,用户的操作请求都在后端单个进程中执行完成,完全依赖后端调用方法的可靠性。一旦出错,应用是无法再次重复请求。 1.2 传统单体应用的访问安全设计——优势和注意点 传统单体应用由于设计相对简单单一,暴露给外界的入口相对较少,从而具有被攻击并造成危害性的可能小的优势。 也正是由于单体应用简单单一的特点,需要注意相关问题: 应用后端保存了所有的credential等敏感信息 一旦入侵了对这个应用的请求,就有可能拿到所有的保存在后端的信息 应用的每次操作一般都需要和数据库进行交互,造成数据库负载变高 2.微服务架构下,访问安全设计原则 来看下这张典型的微服务设计架构图,如图所示,有以下几点特征: 每个服务只有权限去操作自己负责的那部分功能。 用户请求的身份验证和权限批准都由独立的gateway服务来保障 对外服务的LB层无法直接与提供业务服务的应用层进行访问 从上面的特征分析来看,想要给出一份访问安全设计的原则说明,就要看看微服务架构下,访问安全有哪些痛点,以下罗列了几点: 单点登录,即在微服务这种多独立服务的架构下,实现用户只需要登录一次就能访问所有相互信任的应用系统 微服务架构下的应用一般都是无状态的,导致用户的请求每次都需要鉴权,可能引发Auth服务的性能瓶颈 微服务架构下,每个组件都管理着各自的功能权限,这种细粒度的鉴权机制需要事先良好的规划 微服务架构下,需要考虑到那些非浏览器端的客户请求,是否具有良好的可操作性 根据实际情况,还有一些其他痛点,这里不再一一赘述,而这些痛点,就形成了我们在为微服务架构设计访问安全的原则。 3.微服务架构下,常用的访问安全设计方案 HTTP Basic Authentication + Independent Auth DB HTTP Basic Authentication + Central Auth DB API Tokens SAML 这里列出4种,首先简单介绍下,然后一一叙述。 第一种,使用HTTP Basic Auth协议,加上独立的Auth数据库。 第二种,也是使用HTTP Basic Auth协议,跟第一种不同的是,使用集中式的Auth数据库 第三种,API Tokens协议,这种大家应该比较熟悉,很多公有服务(比如Github、Twitter等)的API都是用这种方式。 第四种,SAML,即Security Assertion Markup Language,翻译过来,是『安全声明标记语言』,它是基于XML的一种协议,企业内使用得较多。 下面一一做介绍。 3.1 微服务常用访问安全设计方案——Basic Auth + Independent Auth DB 一种,如上示意图所示,使用Basic Auth协议,配合每个服务自己都拥有存储Credential敏感数据的数据库(或者其他持久化仓库)。 简单介绍下Basic Auth协议,它是在用户的请求中添加一个Authorization消息头,这个消息头的值是一个固定格式:Basic base64encode(username+“:”+password) 完整的消息头列子为:Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ== Basic Auth协议基本上被所有流行的网页浏览器都支持。 这种方案的特点: 每个提供功能的服务都拥有自己独立的鉴权和授权机制 每个提供功能的服务都拥有自己独立的数据库,来保存敏感信息 每次用户请求都需要携带用户的credential来完成操作 小结下使用这种方案的好处: 微服务的应用可以实现100%无状态化 基于Basic Auth开发简单 同时,小结下使用这种方案需要注意的地方: 由于每个服务都有自己存储credential的机制,需要事先为每个服务设计好如何存储和查找用户的Credential由于每次用户请求都会携带用户的Credential,需要事先设计好如何管理鉴权机制。 3.2微服务常用访问安全设计方案——Basic Auth + Central Auth DB 二种,如上示意图所示,使用Basic Auth协议,与第一种方案相比,每个服务共用有同一个Auth DB。 第二种方案的特点和第一种很相似: 每个提供功能的服务都拥有自己独立的鉴权和授权机制 每个提供功能的服务共用同一个DB,来保存Credential等敏感信息 每次用户请求都需要携带用户的credential来完成操作 小结下使用第二种方案的好处: 除了拥有第一种方案相似的好处外,由于共用了同一个持久化仓库来管理用户信息,简化了原来独立管理的机制 同时,小结下使用这种方案需要注意的地方: 中心化Auth DB会被每次用户请求来访问连接,可能引发AuthDB性能瓶颈 需要在每个服务中实现对共有Auth DB查找用户信息的逻辑 3.3微服务常用访问安全设计方案——API Tokens 第三种,如上示意图所示,使用Token Based协议来对用户请求进行操作鉴权。 简单介绍下最基本的Token Based的交互方式: 用户使用包含用户名和密码的credential从客户端发起资源请求 后端接受请求,通过授权中心,生产有效token字符串,返回给客户端 客户端获得token后,再次发出资源请求 后端接受带token的请求,通过授权中心,获取相关资源,返回给客户端 业界常用的OAuth就是基于Token Based这套逻辑,实现的互联网级的鉴权机制。 第三种方案的特点明显: 使用token来进行鉴权,替换用户本身的用户名和密码,提高了交互安全性每次用户请求需要携带有效token,与Auth服务进行交互验证 小结下使用第三种方案的好处: 由于使用了token来鉴权,业务服务不会看到用户的敏感信息 同时,小结下使用这种方案需要注意的地方: Auth服务可能需要处理大量的生产token的操作 3.4微服务常用访问安全设计方案——SAML 第四种,如上示意图所示,使用SAML协议来对用户请求进行操作鉴权。它是一个基于XML的标准,用于在不同的安全域(security domain)之间交换认证和授权数据。在SAML标准定义了身份提供者(identity provider)和服务提供者(service provider),这两者构成了前面所说的不同的安全域。 以上图Google提供的Apps SSO的机制,简单介绍下SAML鉴权的交互方式: 用户请求访问自建的google application 当前application 生成一个 SAML 身份验证请求。SAML 请求将进行编码并嵌入到SSO 服务的网址中。 当前application将重定向发送到用户的浏览器。重定向网址包含应向SSO 服务提交的编码 SAML 身份验证请求。 SSO(统一认证中心或叫Identity Provider)解码 SAML 请求,并提取当前application的 ACS(声明客户服务)网址以及用户的目标网址(RelayState 参数)。然后,统一认证中心对用户进行身份验证。 统一认证中心生成一个 SAML 响应,其中包含经过验证的用户的用户名。按照 SAML 2.0 规范,此响应将使用统一认证中心的 DSA/RSA 公钥和私钥进行数字签名。 统一认证中心对 SAML 响应和 RelayState 参数进行编码,并将该信息返回到用户的浏览器。统一认证中心提供了一种机制,以便浏览器可以将该信息转发到当前application ACS。 当前application使用统一认证中心的公钥验证 SAML 响应。如果成功验证该响应,ACS 则会将用户重定向到目标网址。 用户将重定向到目标网址并登录到当前application。 目前SAML在业界也有相当的使用度,包括IBM Weblogic等产品。 第四种方案的特点有: 由Identity Provider提供可信的签名声明服务的访问安全由可信的Identity Provider提供 小结下使用第四种方案的好处:标准的可信访问模型 同时,小结下使用这种方案需要注意的地方: 基于XML协议,传输相对复杂对非浏览器客户端适配不方便。 4.Spring Cloud Security解决方案 Spring Cloud Security特点有: 基于OAuth2 和OpenID协议的可配置的SSO登录机制 基于tokens保障资源访问安全 引入UAA鉴权服务,UAA是一个Web服务,用于管理账户、Oauth2客户端和用户用于鉴权的问题令牌(Issue Token)。UAA实现了Oauth2授权框架和基于JWT(JSON web tokens)的问题令牌。 下面简单介绍下UAA,事实上,它是由CloudFoundry发起的,也是CloudFoundry平台的身份管理服务(https://docs.cloudfoundry.org…)。 主要功能是基于OAuth2,当用户访问客户端应用时,生成并发放token给目标客户端。 UAA认证服务包含如下几个方面的内容: 认证对象。如用户、客户端以及目标资源服务器 认证类型。主要有授权码模式、密码模式以及客户端模式 认证范围,即认证权限,并作为一个命名的参数附加到AccessToken上。 接下来,结合实例,一起来看下UAA在Spring Cloud中的实践。 如图所示,这是一个简单的基于Spring Cloud微服务架构的例子,它的主要组件有: Eureka组件提供服务发现功能 独立的Config组件提供类似配置中心的服务,持久化层可以是文件系统,也可是git repository Auth组件提供基于UAA的鉴权服务 Account组件保存用户的业务信息 其他组件不一一介绍了 这里主要讲Auth组件和Account组件是如何基于UAA服务进行认证和授权。 一为Auth组件业务代码中定义了不同客户端的认证类型和认证范围,其中: 浏览器端的认证类型是password,认证范围是uiaccount组件端的认证类型是client_credentials,认证范围是server 图二为config组件(配置中心)定义的请求路由的规则,其中: 使用/uaa/**来转发基于uaa的认证请求至auth组件 使用/accouts/**来转发请求至account组件,并标记serviceId为account-service,与图一中的withClient对应。 一为浏览器打开应用入口后,输入用户名和密码后,发出的认证请求: 认证url为/uaa/oauth/token,这是uaa模式下标准的请求获取token的url表单中包含了字段scope(认证范围)和字段password(认证类型) 图二为图一发出认证请求的返回结果: Access_token为有效认证token,将来被其他请求使用 图三为发出获取当前用户的信息的请求: 在请求里的Authorization的值为图二中获得的access_token 图一为Account组件在Config组件(配置中心)定义的OAuth2协议下获取token的方式,这里定义了: clientID和clientSecret accessTokenUrl,这里指定了auth组件的uaa获取token的url grant-type,即认证类型,这里指定为client_credentials scope,这里指定了server,说明是这个认证请求只适用在各微服务之间的访问。 图二为Accout组件业务代码中定义了需要使用Auth组件进行事先鉴权的方法: 使用@PreAuthorize annotation中可以指定认证范围的具体条件,这里是限定了server或者是demo账户,才有权限发起认证。 最后小结下Spring Cloud Security的特点: 基于UAA,使用OAuth2协议。不会暴露用户的敏感信息 基于认证类型和认证范围,实现细粒度的鉴权机制 非浏览器客户端下良好的操作性 5.Q&A 问题:Basic Auth + Central Auth DB这种方式中,每个服务有自己的鉴权DB,这块只是一个缓冲吗?如果中途通过别的方式修改了中心DB的数据,而缓冲又没过期,这个时候有什么解决方案吗?答:不是缓冲,这里的Central Auth DB是指各个微服务共用一个数据库。 问题:微服务架构需要服务路由和服务注册么?跟esb的区别在哪里?答:服务路由组件和服务注册组件和是相对必要的,他们保证了用户请求能发到正确的微服务中去。ESB企业服务总线是相对比较重的组件,而不是像微服务组件一样只负责单个业务。 问题:在微服务中,对于数据权限的粒度,是可以集中在在gateway中进行还是由每个微服务自己独立配置?答:推荐由那个专门负载权限的微服务组件来配置。 问题:您好,辛苦了,请问现在有类似SAML协议,但是不基于XML,而是基于JSON或者其他简化格式的协议吗?答:目前据我所知没有基于JSON的SAML协议。有个叫JWT(JSON web token)的协议,它是完全基于JSON的,Spring Cloud架构中也使用了JWT。 问题:对于这个架构,服务划分的粒度有没有什么好的建议?另外登录凭证保存在客户端如何解决报文被拦截的安全漏洞?答:服务划分需要按具体业务来说,一般来说,一个业务实体作为一个微服务。使用https可以一定程度上提高安全性。 问题:spring cloud security可以解决token重放攻击么?答:token重放攻击不是特别了解,可能是数据弱一致性导致的,建议设计尽可能短的过期时间。 问题:我们公司现在在设计DMP,从行业的状况来看,采用了微服务,但是有一点,首先对于应用本身暴露出来的服务,是和应用一起部署的,也就是并非单独的部署,那么业务组件接口暴露部署是否合理呢?答:业务组件的接口一般可以通过统一网关来管理。也可以对业务接口像spring cloud中设置访问scope限制。 问题:所有暴露的微服务是否需要一个统一的服务管控和治理平台?答:是的,一般有服务网关和服务发现组件来管理用户请求。 问题:微服务的gateway需要实现底层多个细粒度的API组合的场景,我们现在一部分使用异步,但是遇到了没办法全面的解藕。我想问问,对于此,使用响应式?还是异步回调式?它们的区别点会有哪些呢?答:使用哪种API方案,其实要看业务。如果后端业务需要强数据一致性,建议使用响应式的。反之,可以使用异步回调或者消息队列。 问题:uaa和netflix zull集成 可行吗?是否做过这方面的尝试? 答:可以。Zuul组件提供网关服务,uaa是基于OAuth2协议,提供授权服务的。微服务架构下,他们是独立的,是可以自由组合的。举个例子,可以在zuul组件的配置文件中,为授权服务(auth-service)组件的指定路由表。 可以参考:https://gist.github.com/nevermosby/875b9f7b1799a6207832010d6eafcfc3
1.官网文档: http://projects.spring.io/spring-security-oauth/docs/oauth2.html 2. Spring OAuth2.0 提供者实现原理 Spring OAuth2.0提供者实际上分为: 授权服务 Authorization Service. 资源服务 Resource Service. 注意:虽然这两个提供者有时候可能存在同一个应用程序中,但在Spring Security OAuth中你可以把他它们各自放在不同的应用上,而且你可以有多个资源服务,它们共享同一个中央授权服务。 所有获取令牌的请求都将会在Spring MVC controller endpoints中进行处理,并且访问受保护的资源服务的处理流程将会放在标准的Spring Security请求过滤器中(filters)。 2.1 配置一个授权服务必须要实现的endpoints: AuthorizationEndpoint:用来作为请求者获得授权的服务,默认的URL是/oauth/authorize. TokenEndpoint:用来作为请求者获得令牌(Token)的服务,默认的URL是/oauth/token. 2.2 配置一个资源服务必须要实现的过滤器: OAuth2AuthenticationProcessingFilter:用来作为认证令牌(Token)的一个处理流程过滤器。只有当过滤器通过之后,请求者才能获得受保护的资源。 配置提供者(授权、资源)都可以通过简单的Java注解@Configuration来进行适配或者也可以使用基于XML的声明式语法来进行配置,如果使用XML配置的的话,那么请使用http://www.springframework.org/schema/security/spring-security-oauth2.xsd来作为XML的schema(即XML概要定义)以及使用http://www.springframework.org/schema/security/oauth2来作为命名空间。 3. 授权服务配置 配置一个授权服务,你需要考虑几种授权类型(Grant Type),不同的授权类型为客户端(Client)提供了不同的获取令牌(Token)方式,为了实现并确定这几种授权,需要配置使用 ClientDetailsService 和 TokenService 来开启或者禁用这几种授权机制。到这里就请注意了,不管你使用什么样的授权类型(Grant Type),每一个客户端(Client)都能够通过明确的配置以及权限来实现不同的授权访问机制。这也就是说,假如你提供了一个支持”client_credentials”的授权方式,并不意味着客户端就需要使用这种方式来获得授权。下面是几种授权类型的列表,具体授权机制的含义可以参见RFC6749(中文版本): authorization_code:授权码类型。 implicit:隐式授权类型。 password:资源所有者(即用户)密码类型。 client_credentials:客户端凭据(客户端ID以及Key)类型。 refresh_token:通过以上授权获得的刷新令牌来获取新的令牌。 可以用 @EnableAuthorizationServer 注解来配置OAuth2.0 授权服务机制,通过使用@Bean注解的几个方法一起来配置这个授权服务。下面咱们介绍几个配置类,这几个配置是由Spring创建的独立的配置对象,它们会被Spring传入AuthorizationServerConfigurer中: ClientDetailsServiceConfigurer:用来配置客户端详情服务(ClientDetailsService),客户端详情信息在这里进行初始化,你能够把客户端详情信息写死在这里或者是通过数据库来存储调取详情信息。 AuthorizationServerSecurityConfigurer:用来配置令牌端点(Token Endpoint)的安全约束. AuthorizationServerEndpointsConfigurer:用来配置授权(authorization)以及令牌(token)的访问端点和令牌服务(token services)。 注意:以上的配置可以选择继承AuthorizationServerConfigurerAdapter并且覆写其中的三个configure方法来进行配置。) 配置授权服务一个比较重要的方面就是提供一个授权码给一个OAuth客户端(通过 authorization_code 授权类型),一个授权码的获取是OAuth客户端跳转到一个授权页面,然后通过验证授权之后服务器重定向到OAuth客户端,并且在重定向连接中附带返回一个授权码。 如果你是通过XML来进行配置的话,那么可以使用 <authorization-server/> 标签来进行配置。 3.1 配置客户端详情信息(Client Details): ClientDetailsServiceConfigurer (AuthorizationServerConfigurer 的一个回调配置项) 能够使用内存或者JDBC来实现客户端详情服务(ClientDetailsService),有几个重要的属性如下列表: clientId:(必须的)用来标识客户的Id。 secret:(需要值得信任的客户端)客户端安全码,如果有的话。 scope:用来限制客户端的访问范围,如果为空(默认)的话,那么客户端拥有全部的访问范围。 authorizedGrantTypes:此客户端可以使用的授权类型,默认为空。 authorities:此客户端可以使用的权限(基于Spring Security authorities)。 客户端详情(Client Details)能够在应用程序运行的时候进行更新,可以通过访问底层的存储服务(例如将客户端详情存储在一个关系数据库的表中,就可以使用 JdbcClientDetailsService)或者通过 ClientDetailsService 接口(同时你也可以实现 ClientDetailsService 接口)来进行管理。 3.2 管理令牌(Managing Token): AuthorizationServerTokenServices 接口定义了一些操作使得你可以对令牌进行一些必要的管理,在使用这些操作的时候请注意以下几点: 当一个令牌被创建了,你必须对其进行保存,这样当一个客户端使用这个令牌对资源服务进行请求的时候才能够引用这个令牌。 当一个令牌是有效的时候,它可以被用来加载身份信息,里面包含了这个令牌的相关权限。 当你自己创建 AuthorizationServerTokenServices 这个接口的实现时,你可能需要考虑一下使用 DefaultTokenServices 这个类,里面包含了一些有用实现,你可以使用它来修改令牌的格式和令牌的存储。默认的,当它尝试创建一个令牌的时候,是使用随机值来进行填充的,除了持久化令牌是委托一个 TokenStore 接口来实现以外,这个类几乎帮你做了所有的事情。并且 TokenStore 这个接口有一个默认的实现,它就是 InMemoryTokenStore ,如其命名,所有的令牌是被保存在了内存中。除了使用这个类以外,你还可以使用一些其他的预定义实现,下面有几个版本,它们都实现了TokenStore接口: InMemoryTokenStore:这个版本的实现是被默认采用的,它可以完美的工作在单服务器上(即访问并发量压力不大的情况下,并且它在失败的时候不会进行备份),大多数的项目都可以使用这个版本的实现来进行尝试,你可以在开发的时候使用它来进行管理,因为不会被保存到磁盘中,所以更易于调试。 JdbcTokenStore:这是一个基于JDBC的实现版本,令牌会被保存进关系型数据库。使用这个版本的实现时,你可以在不同的服务器之间共享令牌信息,使用这个版本的时候请注意把”spring-jdbc”这个依赖加入到你的classpath当中。 JwtTokenStore:这个版本的全称是 JSON Web Token(JWT),它可以把令牌相关的数据进行编码(因此对于后端服务来说,它不需要进行存储,这将是一个重大优势),但是它有一个缺点,那就是撤销一个已经授权令牌将会非常困难,所以它通常用来处理一个生命周期较短的令牌以及撤销刷新令牌(refresh_token)。另外一个缺点就是这个令牌占用的空间会比较大,如果你加入了比较多用户凭证信息。JwtTokenStore 不会保存任何数据,但是它在转换令牌值以及授权信息方面与 DefaultTokenServices 所扮演的角色是一样的。 3.3 JWT令牌(JWT Tokens) 使用JWT令牌你需要在授权服务中使用 JwtTokenStore,资源服务器也需要一个解码的Token令牌的类 JwtAccessTokenConverter,JwtTokenStore依赖这个类来进行编码以及解码,因此你的授权服务以及资源服务都需要使用这个转换类。 Token令牌默认是有签名的,并且资源服务需要验证这个签名,因此呢,你需要使用一个对称的Key值,用来参与签名计算,这个Key值存在于授权服务以及资源服务之中。或者你可以使用非对称加密算法来对Token进行签名,Public Key公布在/oauth/token_key这个URL连接中,默认的访问安全规则是”denyAll()”,即在默认的情况下它是关闭的,你可以注入一个标准的 SpEL 表达式到 AuthorizationServerSecurityConfigurer 这个配置中来将它开启(例如使用”permitAll()”来开启可能比较合适,因为它是一个公共密钥)。 如果你要使用 JwtTokenStore,请务必把”spring-security-jwt”这个依赖加入到你的classpath中。 3.4 配置授权类型(Grant Types): 授权是使用 AuthorizationEndpoint 这个端点来进行控制的,你能够使用 AuthorizationServerEndpointsConfigurer 这个对象的实例来进行配置(AuthorizationServerConfigurer 的一个回调配置项,见上的概述) ,如果你不进行设置的话,默认是除了资源所有者密码(password)授权类型以外,支持其余所有标准授权类型的(RFC6749),我们来看一下这个配置对象有哪些属性可以设置吧,如下列表: authenticationManager:认证管理器,当你选择了资源所有者密码(password)授权类型的时候,请设置这个属性注入一个 AuthenticationManager 对象。 userDetailsService:如果啊,你设置了这个属性的话,那说明你有一个自己的 UserDetailsService 接口的实现,或者你可以把这个东西设置到全局域上面去(例如 GlobalAuthenticationManagerConfigurer 这个配置对象),当你设置了这个之后,那么 “refresh_token” 即刷新令牌授权类型模式的流程中就会包含一个检查,用来确保这个账号是否仍然有效,假如说你禁用了这个账户的话。 authorizationCodeServices:这个属性是用来设置授权码服务的(即 AuthorizationCodeServices 的实例对象),主要用于 “authorization_code” 授权码类型模式。 implicitGrantService:这个属性用于设置隐式授权模式,用来管理隐式授权模式的状态。 tokenGranter:这个属性就很牛B了,当你设置了这个东西(即 TokenGranter 接口实现),那么授权将会交由你来完全掌控,并且会忽略掉上面的这几个属性,这个属性一般是用作拓展用途的,即标准的四种授权模式已经满足不了你的需求的时候,才会考虑使用这个。 在XML配置中呢,你可以使用 “authorization-server” 这个标签元素来进行设置。 3.5 配置授权端点的URL(Endpoint URLs): AuthorizationServerEndpointsConfigurer 这个配置对象(AuthorizationServerConfigurer 的一个回调配置项,见上的概述) 有一个叫做 pathMapping() 的方法用来配置端点URL链接,它有两个参数: 第一个参数:String 类型的,这个端点URL的默认链接。 第二个参数:String 类型的,你要进行替代的URL链接。 以上的参数都将以 “/” 字符为开始的字符串,框架的默认URL链接如下列表,可以作为这个 pathMapping() 方法的第一个参数: /oauth/authorize:授权端点。 /oauth/token:令牌端点。 /oauth/confirm_access:用户确认授权提交端点。 /oauth/error:授权服务错误信息端点。 /oauth/check_token:用于资源服务访问的令牌解析端点。 /oauth/token_key:提供公有密匙的端点,如果你使用JWT令牌的话。 需要注意的是授权端点这个URL应该被Spring Security保护起来只供授权用户访问,我们来看看在标准的Spring Security中 WebSecurityConfigurer 是怎么用的。 @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests().antMatchers("/login").permitAll().and() // default protection for all resources (including /oauth/authorize) .authorizeRequests() .anyRequest().hasRole("USER") // ... more configuration, e.g. for form login } 注意:如果你的应用程序中既包含授权服务又包含资源服务的话,那么这里实际上是另一个的低优先级的过滤器来控制资源接口的,这些接口是被保护在了一个访问令牌(access token)中,所以请挑选一个URL链接来确保你的资源接口中有一个不需要被保护的链接用来取得授权,就如上面示例中的 /login 链接,你需要在 WebSecurityConfigurer 配置对象中进行设置。 令牌端点默认也是受保护的,不过这里使用的是基于 HTTP Basic Authentication 标准的验证方式来验证客户端的,这在XML配置中是无法进行设置的(所以它应该被明确的保护)。 在XML配置中可以使用 <authorization-server/> 元素标签来改变默认的端点URLs,注意在配置 /check_token 这个链接端点的时候,使用 check-token-enabled 属性标记启用。 3.6 强制使用SSL(Enforcing SSL): 使用简单的HTTP请求来进行测试是可以的,但是如果你要部署到产品环境上的时候,你应该永远都使用SSL来保护授权服务器在与客户端进行通讯的时候进行加密。你可以把授权服务应用程序放到一个安全的运行容器中,或者你可以使用一个代理,如果你设置正确了的话它们应该工作的很好(这样的话你就不需要设置任何东西了)。 但是也许你可能希望使用 Spring Security 的 requiresChannel() 约束来保证安全,对于授权端点来说(还记得上面的列表吗,就是那个 /authorize 端点),它应该成为应用程序安全连接的一部分,而对于 /token 令牌端点来说的话,它应该有一个标记被配置在 AuthorizationServerEndpointsConfigurer 配置对象中,你可以使用 sslOnly() 方法来进行设置。当然了,这两个设置是可选的,不过在以上两种情况中,会导致Spring Security 会把不安全的请求通道重定向到一个安全通道中。(注:即将HTTP请求重定向到HTTPS请求上)。 3.7自定义错误处理(Error Handling): 端点实际上就是一个特殊的Controller,它用于返回一些对象数据。 授权服务的错误信息是使用标准的Spring MVC来进行处理的,也就是 @ExceptionHandler 注解的端点方法,你也可以提供一个 WebResponseExceptionTranslator 对象。最好的方式是改变响应的内容而不是直接进行渲染。 假如说在呈现令牌端点的时候发生了异常,那么异常委托了 HttpMessageConverters 对象(它能够被添加到MVC配置中)来进行输出。假如说在呈现授权端点的时候未通过验证,则会被重定向到 /oauth/error 即错误信息端点中。whitelabel error (即Spring框架提供的一个默认错误页面)错误端点提供了HTML的响应,但是你大概可能需要实现一个自定义错误页面(例如只是简单的增加一个 @Controller 映射到请求路径上 @RequestMapping(“/oauth/error”))。 3.8 映射用户角色到权限范围(Mapping User Roles to Scopes): 有时候限制令牌的权限范围是很有用的,这不仅仅是针对于客户端,你还可以根据用户的权限来进行限制。如果你使用 DefaultOAuth2RequestFactory 来配置 AuthorizationEndpoint 的话你可以设置一个flag即 checkUserScopes=true来限制权限范围,不过这只能匹配到用户的角色。你也可以注入一个 OAuth2RequestFactory 到 TokenEnpoint 中,不过这只能工作在 password 授权模式下。如果你安装一个 TokenEndpointAuthenticationFilter 的话,你只需要增加一个过滤器到 HTTP BasicAuthenticationFilter 后面即可。当然了,你也可以实现你自己的权限规则到 scopes 范围的映射和安装一个你自己版本的 OAuth2RequestFactory。AuthorizationServerEndpointConfigurer 配置对象允许你注入一个你自定义的 OAuth2RequestFactory,因此你可以使用这个特性来设置这个工厂对象,前提是你使用 @EnableAuthorizationServer 注解来进行配置 4. 资源服务配置 一个资源服务(可以和授权服务在同一个应用中,当然也可以分离开成为两个不同的应用程序)提供一些受token令牌保护的资源,Spring OAuth提供者是通过Spring Security authentication filter 即验证过滤器来实现的保护,你可以通过 @EnableResourceServer 注解到一个 @Configuration 配置类上,并且必须使用 ResourceServerConfigurer 这个配置对象来进行配置(可以选择继承自 ResourceServerConfigurerAdapter 然后覆写其中的方法,参数就是这个对象的实例),下面是一些可以配置的属性: tokenServices:ResourceServerTokenServices 类的实例,用来实现令牌服务。 resourceId:这个资源服务的ID,这个属性是可选的,但是推荐设置并在授权服务中进行验证。 其他的拓展属性例如 tokenExtractor 令牌提取器用来提取请求中的令牌。 请求匹配器,用来设置需要进行保护的资源路径,默认的情况下是受保护资源服务的全部路径。 受保护资源的访问规则,默认的规则是简单的身份验证(plain authenticated)。 其他的自定义权限保护规则通过 HttpSecurity 来进行配置。 @EnableResourceServer 注解自动增加了一个类型为 OAuth2AuthenticationProcessingFilter 的过滤器链, 在XML配置中,使用 <resource-server /> 标签元素并指定id为一个servlet过滤器就能够手动增加一个标准的过滤器链。 ResourceServerTokenServices 是组成授权服务的另一半,如果你的授权服务和资源服务在同一个应用程序上的话,你可以使用 DefaultTokenServices ,这样的话,你就不用考虑关于实现所有必要的接口的一致性问题,这通常是很困难的。如果你的资源服务器是分离开的,那么你就必须要确保能够有匹配授权服务提供的 ResourceServerTokenServices,它知道如何对令牌进行解码。 在授权服务器上,你通常可以使用 DefaultTokenServices 并且选择一些主要的表达式通过 TokenStore(后端存储或者本地编码)。 RemoteTokenServices 可以作为一个替代,它将允许资源服务器通过HTTP请求来解码令牌(也就是授权服务的 /oauth/check_token 端点)。如果你的资源服务没有太大的访问量的话,那么使用RemoteTokenServices 将会很方便(所有受保护的资源请求都将请求一次授权服务用以检验token值),或者你可以通过缓存来保存每一个token验证的结果。 使用授权服务的 /oauth/check_token 端点你需要将这个端点暴露出去,以便资源服务可以进行访问,这在咱们授权服务配置中已经提到了,下面是一个例子: @Override public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception { oauthServer.tokenKeyAccess("isAnonymous() || hasAuthority('ROLE_TRUSTED_CLIENT')") .checkTokenAccess("hasAuthority('ROLE_TRUSTED_CLIENT')"); } 在这个例子中,我们配置了 /oauth/check_token 和 /oauth/token_key 这两个端点(受信任的资源服务能够获取到公有密匙,这是为了验证JWT令牌)。这两个端点使用了HTTP Basic Authentication 即HTTP基本身份验证,使用 client_credentials 授权模式可以做到这一点。 4.1 配置OAuth-Aware表达式处理器(OAuth-Aware Expression Handler): 你也许希望使用 Spring Security’s expression-based access control 来获得一些优势,一个表达式处理器会被注册到默认的 @EnableResourceServer 配置中,这个表达式包含了 #oauth2.clientHasRole,#oauth2.clientHasAnyRole 以及 #oauth2.denyClient 所提供的方法来帮助你使用权限角色相关的功能(在 OAuth2SecurityExpressionMethods 中有完整的列表)。 在XML配置中你可以注册一个 OAuth-Aware 表达式处理器即 <expression-handler /> 元素标签到 常规的 <http /> 安全配置上。 资料来源:http://www.cnblogs.com/xingxueliao/p/5911292.html
转载 作者:纯洁的微笑 出处:http://www.ityouknow.com/ 在江湖中要练就绝世武功必须内外兼备,精妙的招式和深厚的内功,武功的基础是内功。对于武功低(就像江南七怪)的人,招式更重要,因为他们不能靠内功直接去伤人,只能靠招式,利刃上优势来取胜了,但是练到高手之后,内功就更主要了。一个内功低的人招式在奇妙也打不过一个内功高的人。比如,你剑法再厉害,一剑刺过来,别人一掌打断你的剑,你还怎么使剑法,你一掌打到一个武功高的人身上,那人没什么事,却把你震伤了,你还怎么打。同样两者也是相辅相成的,内功深厚之后,原来普通的一招一式威力也会倍增。 对于搞开发的我们其实也是一样,现在流行的框架越来越多,封装的也越来越完善,各种框架可以搞定一切,几乎不用关注底层的实现,初级程序员只要熟悉基本的使用方法,便可以快速的开发上线;但对于高级程序员来讲,内功的修炼却越发的重要,比如算法、设计模式、底层原理等,只有把这些基础熟练之后,才能在开发过程中知其然知其所以然,出现问题时能快速定位到问题的本质。 对于Java程序员来讲,spring全家桶几乎可以搞定一切,spring全家桶便是精妙的招式,jvm就是内功心法很重要的一块,线上出现性能问题,jvm调优更是不可回避的问题。因此JVM基础知识对于高级程序员的重要性不必言语,我司在面试高级开发的时候,jvm相关知识也必定是考核的标准之一。本篇文章会根据之前写的jvm系列文章梳理出jvm需要关注的所有考察点。 jvm 总体梳理 jvm体系总体分四大块: 类的加载机制 jvm内存结构 GC算法 垃圾回收 GC分析 命令调优 当然这些知识点在之前的文章中都有详细的介绍,这里只做主干的梳理 这里画了一个思维导图,将所有的知识点进行了陈列,因为图比较大可以点击右键下载了放大查看。 {:.center} 类的加载机制 主要关注点: 什么是类的加载 类的生命周期 类加载器 双亲委派模型 什么是类的加载 类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构。类的加载的最终产品是位于堆区中的Class对象,Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口。 类的生命周期 类的生命周期包括这几个部分,加载、连接、初始化、使用和卸载,其中前三部是类的加载的过程,如下图; {:.center} 加载,查找并加载类的二进制数据,在Java堆中也创建一个java.lang.Class类的对象 连接,连接又包含三块内容:验证、准备、初始化。1)验证,文件格式、元数据、字节码、符号引用验证;2)准备,为类的静态变量分配内存,并将其初始化为默认值;3)解析,把类中的符号引用转换为直接引用 初始化,为类的静态变量赋予正确的初始值 使用,new出对象程序中使用 卸载,执行垃圾回收 几个小问题? 1、JVM初始化步骤 ? 2、类初始化时机 ?3、哪几种情况下,Java虚拟机将结束生命周期? 答案参考这篇文章jvm系列(一):java类的加载机制 类加载器 {:.center} 启动类加载器:Bootstrap ClassLoader,负责加载存放在JDK\jre\lib(JDK代表JDK的安装目录,下同)下,或被-Xbootclasspath参数指定的路径中的,并且能被虚拟机识别的类库 扩展类加载器:Extension ClassLoader,该加载器由sun.misc.Launcher$ExtClassLoader实现,它负责加载DK\jre\lib\ext目录中,或者由java.ext.dirs系统变量指定的路径中的所有类库(如javax.*开头的类),开发者可以直接使用扩展类加载器。 应用程序类加载器:Application ClassLoader,该类加载器由sun.misc.Launcher$AppClassLoader来实现,它负责加载用户类路径(ClassPath)所指定的类,开发者可以直接使用该类加载器 类加载机制 全盘负责,当一个类加载器负责加载某个Class时,该Class所依赖的和引用的其他Class也将由该类加载器负责载入,除非显示使用另外一个类加载器来载入 父类委托,先让父类加载器试图加载该类,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类 缓存机制,缓存机制将会保证所有加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存区寻找该Class,只有缓存区不存在,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓存区。这就是为什么修改了Class后,必须重启JVM,程序的修改才会生效 jvm内存结构 主要关注点: jvm内存结构都是什么 对象分配规则 jvm内存结构 {:.center} 方法区和对是所有线程共享的内存区域;而java栈、本地方法栈和程序员计数器是运行是线程私有的内存区域。 Java堆(Heap),是Java虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。 方法区(Method Area),方法区(Method Area)与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。 程序计数器(Program Counter Register),程序计数器(Program Counter Register)是一块较小的内存空间,它的作用可以看做是当前线程所执行的字节码的行号指示器。 JVM栈(JVM Stacks),与程序计数器一样,Java虚拟机栈(Java Virtual Machine Stacks)也是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。 本地方法栈(Native Method Stacks),本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native方法服务。 对象分配规则 对象优先分配在Eden区,如果Eden区没有足够的空间时,虚拟机执行一次Minor GC。 大对象直接进入老年代(大对象是指需要大量连续内存空间的对象)。这样做的目的是避免在Eden区和两个Survivor区之间发生大量的内存拷贝(新生代采用复制算法收集内存)。 长期存活的对象进入老年代。虚拟机为每个对象定义了一个年龄计数器,如果对象经过了1次Minor GC那么对象会进入Survivor区,之后每经过一次Minor GC那么对象的年龄加1,知道达到阀值对象进入老年区。 动态判断对象的年龄。如果Survivor区中相同年龄的所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象可以直接进入老年代。 空间分配担保。每次进行Minor GC时,JVM会计算Survivor区移至老年区的对象的平均大小,如果这个值大于老年区的剩余值大小则进行一次Full GC,如果小于检查HandlePromotionFailure设置,如果true则只进行Monitor GC,如果false则进行Full GC。 如何通过参数来控制个各个内存区域 参考此文章:jvm系列(二):JVM内存结构 GC算法 垃圾回收 主要关注点: 对象存活判断 GC算法 垃圾回收器 对象存活判断 判断对象是否存活一般有两种方式: 引用计数:每个对象有一个引用计数属性,新增一个引用时计数加1,引用释放时计数减1,计数为0时可以回收。此方法简单,无法解决对象相互循环引用的问题。 可达性分析(Reachability Analysis):从GC Roots开始向下搜索,搜索所走过的路径称为引用链。当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的,不可达对象。 GC算法 GC最基础的算法有三种:标记 -清除算法、复制算法、标记-压缩算法,我们常用的垃圾回收器一般都采用分代收集算法。 标记 -清除算法,“标记-清除”(Mark-Sweep)算法,如它的名字一样,算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象。 复制算法,“复制”(Copying)的收集算法,它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。 标记-压缩算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存 分代收集算法,“分代收集”(Generational Collection)算法,把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。 垃圾回收器 Serial收集器,串行收集器是最古老,最稳定以及效率高的收集器,可能会产生较长的停顿,只使用一个线程去回收。 ParNew收集器,ParNew收集器其实就是Serial收集器的多线程版本。 Parallel收集器,Parallel Scavenge收集器类似ParNew收集器,Parallel收集器更关注系统的吞吐量。 Parallel Old 收集器,Parallel Old是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法 CMS收集器,CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。 G1收集器,G1 (Garbage-First)是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器. 以极高概率满足GC停顿时间要求的同时,还具备高吞吐量性能特征 GC算法和垃圾回收器算法图解以及更详细内容参考 jvm系列(三):GC算法 垃圾收集器 GC分析 命令调优 主要关注点: GC日志分析 调优命令 调优工具 GC日志分析 摘录GC日志一部分(前部分为年轻代gc回收;后部分为full gc回收): 2016-07-05T10:43:18.093+0800: 25.395: [GC [PSYoungGen: 274931K->10738K(274944K)] 371093K->147186K(450048K), 0.0668480 secs] [Times: user=0.17 sys=0.08, real=0.07 secs] 2016-07-05T10:43:18.160+0800: 25.462: [Full GC [PSYoungGen: 10738K->0K(274944K)] [ParOldGen: 136447K->140379K(302592K)] 147186K->140379K(577536K) [PSPermGen: 85411K->85376K(171008K)], 0.6763541 secs] [Times: user=1.75 sys=0.02, real=0.68 secs] 通过上面日志分析得出,PSYoungGen、ParOldGen、PSPermGen属于Parallel收集器。其中PSYoungGen表示gc回收前后年轻代的内存变化;ParOldGen表示gc回收前后老年代的内存变化;PSPermGen表示gc回收前后永久区的内存变化。young gc 主要是针对年轻代进行内存回收比较频繁,耗时短;full gc 会对整个堆内存进行回城,耗时长,因此一般尽量减少full gc的次数 young gc 日志: {:.center} Full GC日志: {:.center} 调优命令 Sun JDK监控和故障处理命令有jps jstat jmap jhat jstack jinfo jps,JVM Process Status Tool,显示指定系统内所有的HotSpot虚拟机进程。 jstat,JVM statistics Monitoring是用于监视虚拟机运行时状态信息的命令,它可以显示出虚拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据。 jmap,JVM Memory Map命令用于生成heap dump文件 jhat,JVM Heap Analysis Tool命令是与jmap搭配使用,用来分析jmap生成的dump,jhat内置了一个微型的HTTP/HTML服务器,生成dump的分析结果后,可以在浏览器中查看 jstack,用于生成java虚拟机当前时刻的线程快照。 jinfo,JVM Configuration info 这个命令作用是实时查看和调整虚拟机运行参数。 详细的命令使用参考这里jvm系列(四):jvm调优-命令篇 调优工具 常用调优工具分为两类,jdk自带监控工具:jconsole和jvisualvm,第三方有:MAT(Memory Analyzer Tool)、GChisto。 jconsole,Java Monitoring and Management Console是从java5开始,在JDK中自带的java监控和管理控制台,用于对JVM中内存,线程和类等的监控 jvisualvm,jdk自带全能工具,可以分析内存快照、线程快照;监控内存变化、GC变化等。 MAT,Memory Analyzer Tool,一个基于Eclipse的内存分析工具,是一个快速、功能丰富的Java heap分析工具,它可以帮助我们查找内存泄漏和减少内存消耗 GChisto,一款专业分析gc日志的工具
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010046908/article/details/57079566 在前面写过一篇dubbo2.5-spring4-mybastis3.2-springmvc4-mongodb3.4-redis3.2整合(七)RabbitMQ工作原理和Spring的集成 ,今天在进一步使用一下RabbitMQ的延迟队列的实现。 1. 简介 RabbitMQ如何实现延迟队列:延迟队列存储的对象肯定是对应的延迟消息,所谓”延迟消息”是指当消息被发送以后,并不想让消费者立即拿到消息,而是等待指定时间后,消费者才拿到这个消息进行消费。 2. RabbitMQ的延迟队列使用场景 场景一:在订单系统中,一个用户下单之后通常有30分钟的时间进行支付,如果30分钟之内没有支付成功,那么这个订单将进行一场处理。这是就可以使用延迟队列将订单信息发送到延迟队列。 场景二:用户希望通过手机远程遥控家里的智能设备在指定的时间进行工作。这时候就可以将用户指令发送到延迟队列,当指令设定的时间到了再将指令推送到只能设备。 3.RabbitMQ实现延迟队列 AMQP协议,以及RabbitMQ本身没有直接支持延迟队列的功能,但是可以通过TTL和DLX模拟出延迟队列的功能。 3.1 TTL(Time To Live) RabbitMQ可以针对Queue和Message设置 x-message-tt,来控制消息的生存时间,如果超时,则消息变为dead letter RabbitMQ针对队列中的消息过期时间有两种方法可以设置。 通过队列属性设置,队列中所有消息都有相同的过期时间。 对消息进行单独设置,每条消息TTL可以不同。 如果同时使用,则消息的过期时间以两者之间TTL较小的那个数值为准。消息在队列的生存时间一旦超过设置的TTL值,就成为dead letter 详细可以参考:RabbitMQ之TTL(Time-To-Live 过期时间) 3.2 DLX (Dead-Letter-Exchange) RabbitMQ的Queue可以配置x-dead-letter-exchange 和x-dead-letter-routing-key(可选)两个参数,如果队列内出现了dead letter,则按照这两个参数重新路由。 x-dead-letter-exchange:出现dead letter之后将dead letter重新发送到指定exchange x-dead-letter-routing-key:指定routing-key发送队列出现dead letter的情况有:消息或者队列的TTL过期 队列达到最大长度 消息被消费端拒绝(basic.reject or basic.nack)并且requeue=false,利DLX,当消息在一个队列中变成死信后,它能被重新publish到另一个Exchange。这时候消息就可以重新被消费。 4.案例的实现 4.1 rabbit.properties rabbit_username=lidong1665 rabbit_password=123456 rabbit_host=192.168.0.107 rabbit_port=5672 4.2 spring-rabbitmq.xml <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:rabbit="http://www.springframework.org/schema/rabbit" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/rabbit http://www.springframework.org/schema/rabbit/spring-rabbit-1.7.xsd"> <!--配置connection-factory,指定连接rabbit server参数 --> <rabbit:connection-factory id="rabbitConnectionFactory" username="${rabbit_username}" password="${rabbit_password}" host="${rabbit_host}" port="${rabbit_port}"/> <!--通过指定下面的admin信息,当前producer中的exchange和queue会在rabbitmq服务器上自动生成 --> <rabbit:admin id="connectAdmin" connection-factory="rabbitConnectionFactory" /> <bean id="taskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor"> <property name="corePoolSize" value="3"/> <property name="maxPoolSize" value="5"/> <property name="queueCapacity" value="15"/> </bean> <bean id="jsonMessageConverter" class="org.springframework.amqp.support.converter.Jackson2JsonMessageConverter" /> <rabbit:topic-exchange name="delayChangeTest" declared-by="connectAdmin" delayed="true"> <rabbit:bindings> <rabbit:binding queue="delay_queue" pattern="order.delay.notify" /> </rabbit:bindings> </rabbit:topic-exchange> <!--定义queue 配置延迟队列的信息--> <rabbit:queue name="delay_queue" durable="true" auto-declare="true" auto-delete="false" declared-by="connectAdmin"> </rabbit:queue> <rabbit:template id="rabbitTemplate2" connection-factory="rabbitConnectionFactory" exchange="delayChangeTest"/> <bean id="orderConsumer" class="com.lidong.dubbo.core.util.customer.OrderConsumer"></bean> <rabbit:listener-container connection-factory="rabbitConnectionFactory" acknowledge="manual" channel-transacted="false" message-converter="jsonMessageConverter"> <rabbit:listener queues="queueTest" ref="messageReceiver" method="onMessage"/> </rabbit:listener-container> </beans> 4.3 创建生产者 package com.lidong.dubbo.core.spittle.service; import com.lidong.dubbo.api.spittle.service.IMessageProducer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.amqp.AmqpException; import org.springframework.amqp.core.*; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.text.SimpleDateFormat; import java.util.Date; /** * @项目名称:lidong-dubbo * @类名:MessageProducerImp * @类的描述: * @作者:lidong * @创建时间:2017/2/4 上午10:01 * @公司:chni * @QQ:1561281670 * @邮箱:lidong1665@163.com */ @Service public class MessageProducerServiceImp implements IMessageProducer { private Logger logger = LoggerFactory.getLogger(MessageProducerServiceImp.class); @Resource private RabbitTemplate rabbitTemplate2; @Override public void sendMessage(Object message) { logger.info("to send message:{}",message); final int xdelay= 300*1000; SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); //发送延迟消息 rabbitTemplate2.convertAndSend("order.delay.notify", message, new MessagePostProcessor() { @Override public Message postProcessMessage(Message message) throws AmqpException { //设置消息持久化 message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT); //设置延迟时间(5分钟后执行) message.getMessageProperties().setDelay(xdelay); logger.info("----"+sf.format(new Date()) + " Delay sent."); return message; } }); } } 4.4 创建消费者 package com.lidong.dubbo.core.util.customer; import com.rabbitmq.client.Channel; import org.activiti.engine.impl.util.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.amqp.core.Message; import org.springframework.amqp.rabbit.core.ChannelAwareMessageListener; /** * @项目名称:lidong-dubbo * @类名:OrderConsumer * @类的描述: * @作者:lidong * @创建时间:2017/2/25 下午12:59 * @公司:chni * @QQ:1561281670 * @邮箱:lidong1665@163.com */ public class OrderConsumer implements ChannelAwareMessageListener { private Logger logger = LoggerFactory.getLogger(OrderConsumer.class); @Override public void onMessage(Message message, Channel channel) throws Exception { logger.info("[延时消息]" + message.getMessageProperties()); if (message != null) { long deliveryTag = message.getMessageProperties().getDeliveryTag(); logger.debug("deliveryTag= "+deliveryTag); //手动确认 channel.basicAck(deliveryTag,false); } } } 发送消息之后。消费5分钟之后接受到消息,开始处理。 代码地址
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010046908/article/details/56289187 1、简介 在Java中创建线程的2种方式,一种是直接继承Thread,另外一种就是实现Runnable接口。这2种方式都有一个缺陷就是:在执行完任务之后无法获取执行结果。 2、需求 在Java中,如果需要获取执行结果,就必须通过共享变量或者使用线程通信的方式来达到效果,这样使用起来就比较麻烦。而自从Java 1.5开始,就提供了Callable和Future,通过它们可以在任务执行完毕之后得到任务执行结果。 3、Callable、FutureTask简介 在学习Callable和FutureTask之前,我们先看一下java.lang.Runnable吧,它是一个接口,在它里面只声明了一个run()方法: @FunctionalInterface public interface Runnable { /** * When an object implementing interface <code>Runnable</code> is used * to create a thread, starting the thread causes the object's * <code>run</code> method to be called in that separately executing * thread. * <p> * The general contract of the method <code>run</code> is that it may * take any action whatsoever. * * @see java.lang.Thread#run() */ public abstract void run(); } 由于run()方法返回值为void类型,所以在执行完任务之后无法返回任何结果。 Callable位于java.util.concurrent包下,它也是一个接口,在它里面也只声明了一个方法,只不过这个方法叫做call(): package java.util.concurrent; /** * A task that returns a result and may throw an exception. * Implementors define a single method with no arguments called * {@code call}. * * <p>The {@code Callable} interface is similar to {@link * java.lang.Runnable}, in that both are designed for classes whose * instances are potentially executed by another thread. A * {@code Runnable}, however, does not return a result and cannot * throw a checked exception. * * <p>The {@link Executors} class contains utility methods to * convert from other common forms to {@code Callable} classes. * * @see Executor * @since 1.5 * @author Doug Lea * @param <V> the result type of method {@code call} */ @FunctionalInterface public interface Callable<V> { /** * Computes a result, or throws an exception if unable to do so. * * @return computed result * @throws Exception if unable to compute a result */ V call() throws Exception; } 可以看到,这是一个泛型接口,call()函数返回的类型就是传递进来的V类型。 那么怎么使用Callable呢?一般情况下是配合ExecutorService来使用的,在ExecutorService接口中声明了若干个submit方法的重载版本: <T> Future<T> submit(Callable<T> task); <T> Future<T> submit(Runnable task, T result); Future<?> submit(Runnable task); 第一个submit方法里面的参数类型就是Callable。 Callable一般是和ExecutorService配合来使用的,具体的使用方法讲在后面讲述。 一般情况下我们使用第一个submit方法和第三个submit方法,第二个submit方法很少使用。 Future就是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果。必要时可以通过get方法获取执行结果,该方法会阻塞直到任务返回结果。 Future类位于java.util.concurrent包下,它是一个接口: public interface Future<V> { boolean cancel( boolean mayInterruptIfRunning); boolean isCancelled(); boolean isDone(); V get() throws InterruptedException, ExecutionException; V get( long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException; } 在Future接口中声明了5个方法,下面依次解释每个方法的作用: cancel方法用来取消任务,如果取消任务成功则返回true,如果取消任务失败则返回false。参数mayInterruptIfRunning表示是否允许取消正在执行却没有执行完毕的任务,如果设置true,则表示可以取消正在执行过程中的任务。如果任务已经完成,则无论mayInterruptIfRunning为true还是false,此方法肯定返回false,即如果取消已经完成的任务会返回false;如果任务正在执行,若mayInterruptIfRunning设置为true,则返回true,若mayInterruptIfRunning设置为false,则返回false;如果任务还没有执行,则无论mayInterruptIfRunning为true还是false,肯定返回true。 isCancelled方法表示任务是否被取消成功,如果在任务正常完成前被取消成功,则返回 true。 isDone方法表示任务是否已经完成,若任务完成,则返回true; get()方法用来获取执行结果,这个方法会产生阻塞,会一直等到任务执行完毕才返回; get(long timeout, TimeUnit unit)用来获取执行结果,如果在指定时间内,还没获取到结果,就直接返回null。 Future提供了三种功能: 判断任务是否完成; 能够中断任务; 能够获取任务执行结果。 因为Future只是一个接口,所以是无法直接用来创建对象使用的,因此就有了下面的FutureTask。 4、Callable、FutureTask使用 4.1简单使用 CallableTask.java public class CallableTask implements Callable<Integer> { @Override public Integer call() throws Exception { int hours=5; int amount = 0; while(hours>0){ System.out.println("我正在工作,剩余时间 "+hours+"小时"); amount++; hours--; Thread.sleep(1000); } return amount; } } TestDemo.java public class TestDemo { public static void main(String args[]) throws ExecutionException { CallableTask worker = new CallableTask(); FutureTask<Integer> jiangong = new FutureTask<Integer>(worker); new Thread(jiangong).start(); while(!jiangong.isDone()){ try { System.out.println("获取结果"+jiangong.get()); System.out.println("任务是否取消"+jiangong.isCancelled()); System.out.println("任务是否执行"+jiangong.isDone()); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } int amount; try { amount = jiangong.get(); System.out.println("工作做完了,上交了"+amount); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } } 运行的结果: 4.2并发的实现 package com.lidong.demo; import java.util.concurrent.Callable; /** * @项目名称:lidong-dubbo * @类名:Task * @类的描述: * @作者:lidong * @创建时间:2017/2/21 下午3:42 * @公司:chni * @QQ:1561281670 * @邮箱:lidong1665@163.com */ public class Task implements Callable<Integer> { @Override public Integer call() throws Exception { System.out.println(Thread.currentThread().getName()+" 子线程在进行计算开始"); Thread.sleep(3000); int sum = 0; for(int i=0;i<100;i++) sum += i; System.out.println(Thread.currentThread().getName()+" 子线程在进行计算结束"); return sum; } } Test.java package com.lidong.demo; import java.util.concurrent.*; public class Test { public static void main(String[] args) { ExecutorService executor = Executors.newCachedThreadPool(); Task task = new Task(); FutureTask<Integer> futureTask = new FutureTask<Integer>(task); executor.submit(futureTask); executor.shutdown(); System.out.println(Thread.currentThread().getName()+" 主线程在执行任务"); try { System.out.println(Thread.currentThread().getName()+ " task运行结果"+futureTask.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+ " 所有任务执行完毕"); } } 运行结果:
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010046908/article/details/56015388 前面写过一篇关于dubbo2.5-spring4-mybastis3.2-springmvc4-mongodb3.4-redis3.2整合(九)之 spring中定时器quartz的整合。今天主要来继续总结quartz在集群中的使用。 1、说明 quartz可以通过jdbc直连连接到MYSQL数据库,读取配置在数据库里的job初始化信息,并且把job通过java序列化到数据库里,这样就使得每个job信息得到了持久化,即使在jvm或者容器挂掉的情况下,也能通过数据库感知到其他job的状态和信息。 quartz集群各节点之间是通过同一个数据库实例(准确的说是同一个数据库实例的同一套表)来感知彼此的。 2、数据库表的创建 创建quartz要用的数据库表,此sql文件在:quartz-2.2.3\docs\dbTables。此文件夹下有各个数据库的sql文件,mysql选择tables_mysql.sql。总共有11张表。 QRTZ_CALENDARS 以 Blob 类型存储 Quartz 的 Calendar 信息 QRTZ_CRON_TRIGGERS 存储 Cron Trigger,包括 Cron表达式和时区信息 QRTZ_FIRED_TRIGGERS 存储与已触发的 Trigger 相关的状态信息,以及相联 Job的执行信息 QRTZ_PAUSED_TRIGGER_GRPS 存储已暂停的 Trigger 组的信息 QRTZ_SCHEDULER_STATE 存储少量的有关 Scheduler 的状态信息,和别的 Scheduler实例(假如是用于一个集群中) QRTZ_LOCKS 存储程序的悲观锁的信息(假如使用了悲观锁) QRTZ_JOB_DETAILS 存储每一个已配置的 Job 的详细信息 QRTZ_JOB_LISTENERS 存储有关已配置的 JobListener 的信息 QRTZ_SIMPLE_TRIGGERS 存储简单的Trigger,包括重复次数,间隔,以及已触的次数 QRTZ_BLOG_TRIGGERS Trigger 作为 Blob 类型存储(用于 Quartz 用户用 JDBC创建他们自己定制的 Trigger 类型,JobStore 并不知道如何存储实例的时候) QRTZ_TRIGGER_LISTENERS 存储已配置的 TriggerListener 的信息 QRTZ_TRIGGERS 存储已配置的 Trigger 的信息 3、 quartz.properties的配置 #调度标识名 集群中每一个实例都必须使用相同的名称 org.quartz.scheduler.instanceName = MyScheduler #线程数量 org.quartz.threadPool.threadCount = 10 #线程优先级 org.quartz.threadPool.threadPriority = 5 #数据保存方式为持久化 org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX #数据库平台 org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate #表的前缀 org.quartz.jobStore.tablePrefix = QRTZ_ #库的别名 org.quartz.jobStore.dataSource = myDS # Cluster开启集群 org.quartz.jobStore.isClustered = true #ID设置为自动获取 每一个必须不同 org.quartz.scheduler.instanceId = AUTO org.quartz.dataSource.myDS.driver = com.mysql.jdbc.Driver org.quartz.dataSource.myDS.URL = jdbc:mysql://localhost:3306/db_my?characterEncoding=utf-8 org.quartz.dataSource.myDS.user = root org.quartz.dataSource.myDS.password = 123456 org.quartz.dataSource.myDS.maxConnections = 5 4.创建任务 package com.lidong.dubbo.core.util.quartz; import java.io.Serializable; import java.text.SimpleDateFormat; import java.util.Date; import org.quartz.DisallowConcurrentExecution; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.quartz.PersistJobDataAfterExecution; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean; import org.springframework.scheduling.quartz.QuartzJobBean; /** * @项目名称:lidong-dubbo * @类名:SpringQtz * @类的描述: 作业类的调度 * @作者:lidong * @创建时间:2017/2/8 下午5:41 * @公司:chni * @QQ:1561281670 * @邮箱:lidong1665@163.com */ public class SpringQtz extends QuartzJobBean{ static Logger logger = LoggerFactory.getLogger(SpringQtz.class); private static int counter = 0; @Override protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException { long ms = System.currentTimeMillis(); logger.error(" SpringQtz start 执行"); logger.info("-------"+new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date(ms))+" "+"(" + counter++ + ")"); } } 5.spring-quartz.xml的配置 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 定义调用对象和调用对象的方法 --> <bean id="SpringQtzJobDetail" class="org.springframework.scheduling.quartz.JobDetailFactoryBean"> <property name="jobClass" value="com.lidong.dubbo.core.util.quartz.SpringQtz"/> <property name="durability" value="true"/> <property name="group" value="job_work"/> <property name="name" value="job_work_name"/> </bean> <bean id="SpringQtzJobDetail1" class="org.springframework.scheduling.quartz.JobDetailFactoryBean"> <property name="jobClass" value="com.lidong.dubbo.core.util.quartz.SpringQtzDemo"/> <property name="durability" value="true"/> <property name="group" value="job_work1"/> <property name="name" value="job_work_name1"/> </bean> <!-- ======================== 调度触发器 ======================== --> <bean id="CronTriggerBean" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean"> <property name="jobDetail" ref="SpringQtzJobDetail"></property> <!-- cron表达式 --> <property name="cronExpression" value="0/20 * * * * ?"></property> </bean> <bean id="CronTriggerBean1" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean"> <property name="jobDetail" ref="SpringQtzJobDetail1"></property> <!-- cron表达式 --> <property name="cronExpression" value="0/30 * * * * ?"></property> </bean> <!-- ======================== 调度工厂 ======================== --> <bean id="SpringJobSchedulerFactoryBean" class="org.springframework.scheduling.quartz.SchedulerFactoryBean"> <property name="dataSource"> <ref bean="dataSource"/> </property> <property name="applicationContextSchedulerContextKey" value="applicationContext" /> <property name="configLocation" value="classpath:config/quartz.properties"/> <!--启动时更新己存在的Job,这样就不用每次修改targetObject后删除qrtz_job_details表对应记录了--> <property name="overwriteExistingJobs" value="true"/> <property name="triggers"> <list> <ref bean="CronTriggerBean" /> <ref bean="CronTriggerBean1" /> </list> </property> <property name="jobDetails"> <list> <ref bean="SpringQtzJobDetail" /> <ref bean="SpringQtzJobDetail1" /> </list> </property> </bean> </beans> 6. 分别部署到两台Tomcat服务器查看效果 第1个tomcat的结果: 第2个omcat的结果: 可以看到。任务不会重复执行。 代码地址
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010046908/article/details/56008445 1. Docker 简介 Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以实现虚拟化。容器是完全使用沙箱机制,相互之间不会有任何接口。Docker image 是用于运行容器化进程的方案,在本文中,我们将构建一个简单的 Spring Boot 应用程序。 2.环境搭建 JDK 1.8+ Maven 3.0+ Docker 最新版。 3.用 Maven 构建项目 3.1 创建目录结构 mkdir -p src/main/java/com/lidong/demo 在linux或者mac系统中。 3.2 创建 pom.xml 文件 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.lidong.demo</groupId> <artifactId>lidong-spring-boot-demo</artifactId> <version>1.0-SNAPSHOT</version> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.1.RELEASE</version> <relativePath/> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> <docker.image.prefix>springio</docker.image.prefix> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> <plugin> <groupId>com.spotify</groupId> <artifactId>docker-maven-plugin</artifactId> <version>0.4.13</version> <configuration> <imageName>${docker.image.prefix}/${project.artifactId}</imageName> <dockerDirectory>src/main/docker</dockerDirectory> <resources> <resource> <targetPath>/</targetPath> <directory>${project.build.directory}</directory> <include>${project.build.finalName}.jar</include> </resource> </resources> </configuration> </plugin> </plugins> </build> </project> 注意: Spring Boot Maven plugin 提供了很多方便的功能: 1)它收集的类路径上所有 jar 文件,并构建成一个单一的、可运行的jar,这使得它更方便地执行和传输服务。 2)它搜索的 public static void main() 方法来标记为可运行的类。 3)它提供了一个内置的依赖解析器,用于设置版本号以匹配 Spring Boot 的依赖。您可以覆盖任何你想要的版本,但它会默认选择的 Boot 的版本集。 Spotify 的 docker-maven-plugin 插件是用于构建 Maven 的 Docker Image 1)imageName指定了镜像的名字,本例为 springio/lidong-spring-boot-demo 2)dockerDirectory指定 Dockerfile 的位置 3)resources是指那些需要和 Dockerfile 放在一起,在构建镜像时使用的文件,一般应用 jar 包需要纳入。 4.编写 第一个Spring Boot 应用 编写一个简单的 Spring Boot 应用 : src/main/java/com/lidong/demo/SampleController.java: package com.lidong.demo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; /** * @项目名称:lidong-dubbo * @类名:SampleController * @类的描述: * @作者:lidong * @创建时间:2017/2/19 上午9:34 * @公司:chni * @QQ:1561281670 * @邮箱:lidong1665@163.com */ @Controller @SpringBootApplication public class SampleController { @ResponseBody @RequestMapping(value = "/") String home(){ return "Hello Docker World"; } public static void main(String[] args) { SpringApplication.run(SampleController.class,"--server.port=8081"); } } 类用 @SpringBootApplication @RestController 标识,可用 Spring MVC 来处理 Web 请求。 @RequestMapping 将 / 映射到 home() ,并将”Hello Docker World” 文本作为响应。 main() 方法使用 Spring Boot 的 SpringApplication.run() 方法来启动应用。 5.运行程序 5.1使用Maven命令 mvn package 运行: java -jar target/lidong-spring-boot-demo-1.0-SNAPSHOT.jar 访问项目 如果程序正确运行,浏览器访问 http://localhost:8081/,可以看到页面 “Hello Docker World.” 字样。 5.2 使用IDEA 插件 6.将项目容器化 Docker 使用 Dockerfile 文件格式来指定 image 层, 创建文件 src/main/docker/Dockerfile: FROM frolvlad/alpine-oraclejdk8:slim VOLUME /tmp ADD lidong-spring-boot-demo-1.0-SNAPSHOT.jar app.jar RUN sh -c 'touch /app.jar' ENV JAVA_OPTS="" ENTRYPOINT [ "sh", "-c", "java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /app.jar" ] 解释下这个配置文件: VOLUME 指定了临时文件目录为/tmp。其效果是在主机 /var/lib/docker 目录下创建了一个临时文件,并链接到容器的/tmp。改步骤是可选的,如果涉及到文件系统的应用就很有必要了。/tmp目录用来持久化到 Docker 数据文件夹,因为 Spring Boot 使用的内嵌 Tomcat 容器默认使用/tmp作为工作目录 项目的 jar 文件作为 “app.jar” 添加到容器的 ENTRYPOINT 执行项目 app.jar。为了缩短 Tomcat 启动时间,添加一个系统属性指向 “/dev/urandom” 作为 Entropy Source 构建 Docker Image 执行构建成为 docker image: mvn package docker:build 运行 运行 Docker Image docker run -p 8081:8081 -t springio/lidong-spring-boot-demo 看到这个Spring的图标。就以为这我们在docker 上发布Spring boot 程序已经完成。 接下来去访问在浏览器访问 http://localhost:8081/,可以看到页面 “Hello Docker World.” 字样。 参考资料: http://spring.io/guides/gs/spring-boot-docker/ 在这里是一个简单的spring boot的入门。后面会详细介绍Spring-boot 构建微服务一些具体的知识。
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010046908/article/details/55050050 今天新建android studio项目时候。突然遇到如下问题: 很是操蛋。最后发现时候用于Setting->compiler - Command-line Optons:-Xmx2048m导致的。 解决办法是将Command-line Optons的参数删除,设置为空就ok了
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010046908/article/details/55047193 1、Swagger2是什么? Swagger 是一款RESTFUL接口的文档在线自动生成+功能测试功能软件。 Swagger 是一个规范和完整的框架,用于生成、描述、调用和可视化 RESTful 风格的 Web 服务。总体目标是使客户端和文件系统作为服务器以同样的速度来更新。文件的方法,参数和模型紧密集成到服务器端的代码,允许API来始终保持同步。Swagger 让部署管理和使用功能强大的API从未如此简单。 2、Swagger2官网 官网地址 3.Swagger2的入门教程 3.1Swagger2的maven依赖 <!-- 构建Restful API --> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.4.0</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.4.0</version> </dependency> 3.2RestApiConfig的配置 package com.lidong.dubbo.web.util; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport; import springfox.documentation.builders.ApiInfoBuilder; import springfox.documentation.builders.PathSelectors; import springfox.documentation.builders.RequestHandlerSelectors; import springfox.documentation.service.ApiInfo; import springfox.documentation.service.Contact; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spring.web.plugins.Docket; import springfox.documentation.swagger2.annotations.EnableSwagger2; /** * @项目名称:lidong-dubbo * @类名:RestApiConfig * @类的描述: Restapi的基本配置 * @作者:lidong * @创建时间:2017/2/11 上午10:01 * @公司:chni * @QQ:1561281670 * @邮箱:lidong1665@163.com * @使用方法:Restful API 访问路径: http://localhost:8080/lidong-dubbo-web/swagger-ui.html */ @EnableWebMvc @EnableSwagger2 @Configuration @ComponentScan(basePackages ="com.lidong.dubbo") public class RestApiConfig extends WebMvcConfigurationSupport { @Bean public Docket createRestApi() { return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) .select() .apis(RequestHandlerSelectors.basePackage("com.lidong.dubbo.web")) .paths(PathSelectors.any()) .build(); } private ApiInfo apiInfo() { return new ApiInfoBuilder() .title("SpringMVC中使用Swagger2构建RESTful APIs") .termsOfServiceUrl("https://github.com/lidong1665") .contact(new Contact("请叫我小东子","http://blog.csdn.net/u010046908","lidong1665@163.com")) .version("1.0.0") .build(); } } 3.3在Controller中添加 @ApiOperation(value = "获取用户详细信息", notes = "根据url的id来获取用户详细信息") @ApiImplicitParam(name = "id", value = "用户ID", required = true, dataType = "Long") @RequestMapping(value = "getUserForid/{id}", method = RequestMethod.GET) @ResponseBody public String getUser(@PathVariable int id) { try { return JsonUtil.bean2json(userService.getUserById(id)); } catch (Exception e) { e.printStackTrace(); } return null; } 3.4结果 代码地址
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010046908/article/details/54930240 今天来总结一下。Spring中quartz的使用。 1、Quartz是什么? Quartz 是一个开源的作业调度框架,它完全由 Java 写成,并设计用于 J2SE 和 J2EE 应用中。它提供了巨大的灵活性而不牺牲简单性。你能够用它来为执行一个作业而创建简单的或复杂的调度。 2、Quartz的分类 2.1、按照作业类的继承方式来分,可以分为两类 作业类需要继承自特定的作业类基类,如Quartz中需要继承自org.springframework.scheduling.quartz.QuartzJobBean;java.util.Timer中需要继承自java.util.TimerTask。 作业类即普通的java类,不需要继承自任何基类。 注意:推荐使用第二种方式,因为这样所以的类都是普通类,不需要事先区别对待。 2.2、按照任务调度的触发时机来分,这里主要是针对作业使用的触发器,主要有以下两种: 每隔指定时间则触发一次,在Quartz中对应的触发器为:org.springframework.scheduling.quartz.SimpleTriggerBean 每到指定时间则触发一次,在Quartz中对应的调度器为:org.springframework.scheduling.quartz.CronTriggerBean 注意:并非每种任务都可以使用这两种触发器,如java.util.TimerTask任务就只能使用第一种。Quartz和spring task都可以支持这两种触发条件。 3、添加Quartz依赖 <!-- 定时器的使用 --> <dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz</artifactId> <version>2.2.3</version> </dependency> 4、Spring中quartz的使用(作业类即普通的java类的使用) 4.1、定义作业类 package com.lidong.dubbo.core.util.quartz; import java.text.SimpleDateFormat; import java.util.Date; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * @项目名称:lidong-dubbo * @类名:SpringQtz * @类的描述: 作业类的调度 * @作者:lidong * @创建时间:2017/2/8 下午5:41 * @公司:chni * @QQ:1561281670 * @邮箱:lidong1665@163.com */ public class SpringQtz { static Logger logger = LoggerFactory.getLogger(SpringQtz.class); private static int counter = 0; /** * 调度的方法 */ public void execute() { long ms = System.currentTimeMillis(); logger.info(new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date(ms))+" "+"(" + counter++ + ")"); } } 4.2、在Spring中配置作业类 <!-- 要调用的工作类 --> <bean id="SpringQtzJob" class="com.lidong.dubbo.core.util.quartz.SpringQtz" /> 4.3 、定义调用对象和方法 <!-- 定义调用对象和调用对象的方法 --> <bean id="SpringQtzJobMethod" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean"> <!-- 调用的类 --> <property name="targetObject"> <ref bean="SpringQtzJob" /> </property> <!-- 要执行的方法名称 --> <property name="targetMethod"> <value>execute</value> </property> </bean> 4.4、配置调度触发器 <!-- ======================== 调度触发器 ======================== --> <bean id="CronTriggerBean" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean"> <!--作业详情 --> <property name="jobDetail" ref="SpringQtzJobMethod"></property> <!-- cron表达式 --> <property name="cronExpression" value="0/10 * * * * ?"></property> </bean> 4.5、配置调度工厂 <!-- ======================== 调度工厂 ======================== --> <bean id="SpringJobSchedulerFactoryBean" class="org.springframework.scheduling.quartz.SchedulerFactoryBean"> <property name="triggers"> <list> <ref bean="CronTriggerBean" /> <ref bean="CronTriggerBean1" /> </list> </property> </bean> 5、quartz的cron 表达式 一个cron表达式至少有6个,是按照空格来分割的时间元素。 按照顺序为: 秒 0-59 分钟 0-59 小时 0-23 天 (月)1-31 月 0-31 天 (星期)1-7 年份 1970- 递增 给几个例子: "30 * * * * ?" 每半分钟触发任务 "30 10 * * * ?" 每小时的10分30秒触发任务 "30 10 1 * * ?" 每天1点10分30秒触发任务 "30 10 1 20 * ?" 每月20号1点10分30秒触发任务 "30 10 1 20 10 ? *" 每年10月20号1点10分30秒触发任务 "30 10 1 20 10 ? 2011" 2011年10月20号1点10分30秒触发任务 "30 10 1 ? 10 * 2011" 2011年10月每天1点10分30秒触发任务 "30 10 1 ? 10 SUN 2011" 2011年10月每周日1点10分30秒触发任务 "15,30,45 * * * * ?" 每15秒,30秒,45秒时触发任务 "15-45 * * * * ?" 15到45秒内,每秒都触发任务 "15/5 * * * * ?" 每分钟的每15秒开始触发,每隔5秒触发一次 "15-30/5 * * * * ?" 每分钟的15秒到30秒之间开始触发,每隔5秒触发一次 "0 0/3 * * * ?" 每小时的第0分0秒开始,每三分钟触发一次 "0 15 10 ? * MON-FRI" 星期一到星期五的10点15分0秒触发任务 "0 15 10 L * ?" 每个月最后一天的10点15分0秒触发任务 "0 15 10 LW * ?" 每个月最后一个工作日的10点15分0秒触发任务 "0 15 10 ? * 5L" 每个月最后一个星期四的10点15分0秒触发任务 "0 15 10 ? * 5#3" 每个月第三周的星期四的10点15分0秒触发任务 基本上就这么多了。 代码地址:
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010046908/article/details/54906751 目前项目中需要存储一些文件、视频等。于是乎,查找了一些关于文件服务器资料。其中有Lustre、HDFS、Gluster、Alluxio、Ceph 、FastDFS。下面简单介绍一下: Lustre 是一个大规模的、安全可靠的、具备高可用性的集群文件系统,它是由SUN公司开发和维护的。该项目主要的目的就是开发下一代的集群文件系统,目前可以支持超过10000个节点,数以PB的数据存储量。 HDFS Hadoop Distributed File System,简称HDFS,是一个分布式文件系统。HDFS是一个高度容错性的系统,适合部署在廉价的机器上。HDFS能提供高吞吐量的数据访问,非常适合大规模数据集上的应用。 GlusterFS 是一个集群的文件系统,支持PB级的数据量。GlusterFS 通过RDMA和TCP/IP方式将分布到不同服务器上的存储空间汇集成一个大的网络化并行文件系统。 Alluxio 前身是Tachyon,是以内存为中心的分布式文件系统,拥有高性能和容错能力,能够为集群框架(如Spark、MapReduce)提供可靠的内存级速度的文件共享服务。 Ceph 是新一代开源分布式文件系统,主要目标是设计成基于POSIX的没有单点故障的分布式文件系统,提高数据的容错性并实现无缝的复制。 FastDFS是一个开源的轻量级分布式文件系统,它对文件进行管理,功能包括:文件存储、文件同步、文件访问(文件上传、文件下载)等,解决了大容量存储和负载均衡的问题。特别适合以文件为载体的在线服务,如相册网站、视频网站等等。FastDFS为互联网量身定制,充分考虑了冗余备份、负载均衡、线性扩容等机制,并注重高可用、高性能等指标,使用FastDFS很容易搭建一套高性能的文件服务器集群提供文件上传、下载等服务。 通过以上6中文件的服务器的介绍,我们业务非常适合选择用FastDFS,所以就了解学习了一番,感觉确实颇为强大,在此再次感谢淘宝资深架构师余庆大神开源了如此优秀的轻量级分布式文件系统,本篇文章就记录一下FastDFS的最新版本5.0.9在CentOS7中的安装与配置。 1.Fastdfs的简介 了解一下基础概念,FastDFS是一个开源的轻量级分布式文件系统,由跟踪服务器(tracker server)、存储服务器(storage server)和客户端(client)三个部分组成,主要解决了海量数据存储问题,特别适合以中小文件(建议范围:4KB < file_size <500MB)为载体的在线服务。 FastDFS系统结构如下图所示: 跟踪器和存储节点都可以由一台多台服务器构成。跟踪器和存储节点中的服务器均可以随时增加或下线而不会影响线上服务。其中跟踪器中的所有服务器都是对等的,可以根据服务器的压力情况随时增加或减少。 为了支持大容量,存储节点(服务器)采用了分卷(或分组)的组织方式。存储系统由一个或多个卷组成,卷与卷之间的文件是相互独立的,所有卷 的文件容量累加就是整个存储系统中的文件容量。一个卷可以由一台或多台存储服务器组成,一个卷下的存储服务器中的文件都是相同的,卷中的多台存储服务器起 到了冗余备份和负载均衡的作用。 在卷中增加服务器时,同步已有的文件由系统自动完成,同步完成后,系统自动将新增服务器切换到线上提供服务。 当存储空间不足或即将耗尽时,可以动态添加卷。只需要增加一台或多台服务器,并将它们配置为一个新的卷,这样就扩大了存储系统的容量。 2.FastDFS的下载 Fastdfs的稳定版下载地址 3.FastDFS的 安装 详细见 CentOS 7 安装配置分布式文件系统 FastDFS 5.0.5 4.SpringMVC上传文件到FastDFS 4.1 fast_client.cnf配置 connect_timeout = 2 #网络超时时间 network_timeout = 30 #字符集 charset = UTF-8 #跟踪服务器的端口 http.tracker_http_port = 9099 http.anti_steal_token = no http.secret_key = FastDFS1234567890 #跟踪服务器地址 。跟踪服务器主要是起到负载均衡的作用 tracker_server = 192.168.0.116:22122 4.2 fastdfs文件上传的流程 上传文件交互过程: client询问tracker上传到的storage,不需要附加参数; tracker返回一台可用的storage; client直接和storage通讯完成文件上传。 4.3 FastDFS文件下载的流程 下载文件交互过程: client询问tracker下载文件的storage,参数为文件标识(卷名和文件名); tracker返回一台可用的storage; client直接和storage通讯完成文件下载。 需要说明的是,client为使用FastDFS服务的调用方,client也应该是一台服务器,它对tracker和storage的调用均为服务器间的调用。 4.4 FastDFSUtil 的封装 package com.lidong.dubbo.util; import org.csource.common.MyException; import org.csource.common.NameValuePair; import org.csource.fastdfs.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.io.ClassPathResource; import org.springframework.web.multipart.MultipartFile; import javax.servlet.http.HttpServletResponse; import java.io.*; import java.util.HashMap; import java.util.Map; /** * @项目名称:lidong-dubbo * @类名:FastDFSUtil * @类的描述: FastDFS 上传文件到文件服务器 * @作者:lidong * @创建时间:2017/2/6 下午5:23 * @公司:chni * @QQ:1561281670 * @邮箱:lidong1665@163.com */ public class FastDFSUtil { private final static Logger logger = LoggerFactory.getLogger(FastDFSUtil.class); /** *上传服务器本地文件-通过Linux客户端,调用客户端命令上传 * @param filePath 文件绝对路径 * @return Map<String,Object> code-返回代码, group-文件组, msg-文件路径/错误信息 */ public static Map<String, Object> uploadLocalFile(String filePath) { Map<String, Object> retMap = new HashMap<String, Object>(); /** * 1.上传文件的命令 */ String command = "fdfs_upload_file /etc/fdfs/client.conf " + filePath; /** * 2.定义文件的返回信息 */ String fileId = ""; InputStreamReader inputStreamReader = null; BufferedReader bufferedReader = null; try { /** * 3.通过调用api, 执行linux命令上传文件 */ Process process = Runtime.getRuntime().exec(command); /** * 4.读取上传后返回的信息 */ inputStreamReader = new InputStreamReader(process.getInputStream()); bufferedReader = new BufferedReader(inputStreamReader); String line; if ((line = bufferedReader.readLine()) != null) { fileId = line; } /** * 5.如果fileId包含M00,说明文件已经上传成功。否则文件上传失败 */ if (fileId.contains("M00")) { retMap.put("code", "0000"); retMap.put("group", fileId.substring(0, 6)); retMap.put("msg", fileId.substring(7, fileId.length())); } else { retMap.put("code", "0001"); //上传错误 retMap.put("msg", fileId); //返回信息 } } catch (Exception e) { logger.error("IOException:" + e.getMessage()); retMap.put("code", "0002"); retMap.put("msg", e.getMessage()); }finally { if (inputStreamReader!=null){ try { inputStreamReader.close(); } catch (IOException e) { e.printStackTrace(); } } if (bufferedReader != null) { try { bufferedReader.close(); } catch (IOException e) { e.printStackTrace(); } } } return retMap; } /** * Description: 直接通过fdfs java客户端上传到服务器-读取本地文件上传 * * @param filePath 本地文件绝对路径 * @return Map<String,Object> code-返回代码, group-文件组, msg-文件路径/错误信息 */ public static Map<String, Object> upload(String filePath) { Map<String, Object> retMap = new HashMap<String, Object>(); File file = new File(filePath); TrackerServer trackerServer = null; StorageServer storageServer = null; if (file.isFile()) { try { String tempFileName = file.getName(); byte[] fileBuff = FileUtil.getBytesFromFile(file); String fileId = ""; //截取后缀 String fileExtName = tempFileName.substring(tempFileName.lastIndexOf(".") + 1); ConfigAndConnectionServer configAndConnectionServer = new ConfigAndConnectionServer().invoke(1); StorageClient1 storageClient1 = configAndConnectionServer.getStorageClient1(); storageServer = configAndConnectionServer.getStorageServer(); trackerServer = configAndConnectionServer.getTrackerServer(); /** * 4.设置文件的相关属性。调用客户端的upload_file1的方法上传文件 */ NameValuePair[] metaList = new NameValuePair[3]; //原始文件名称 metaList[0] = new NameValuePair("fileName", tempFileName); //文件后缀 metaList[1] = new NameValuePair("fileExtName", fileExtName); //文件大小 metaList[2] = new NameValuePair("fileLength", String.valueOf(file.length())); //开始上传文件 fileId = storageClient1.upload_file1(fileBuff, fileExtName, metaList); retMap = handleResult(retMap, fileId); } catch (Exception e) { e.printStackTrace(); retMap.put("code", "0002"); retMap.put("msg", e.getMessage()); }finally { /** * 5.关闭跟踪服务器的连接 */ colse(storageServer, trackerServer); } } else { retMap.put("code", "0001"); retMap.put("msg", "error:本地文件不存在!"); } return retMap; } /** * Description:远程选择上传文件-通过MultipartFile * * @param file 文件流 * @return Map<String,Object> code-返回代码, group-文件组, msg-文件路径/错误信息 */ public static Map<String, Object> upload(MultipartFile file) { Map<String, Object> retMap = new HashMap<String, Object>(); TrackerServer trackerServer = null; StorageServer storageServer = null; try { if (file.isEmpty()) { retMap.put("code", "0001"); retMap.put("msg", "error:文件为空!"); } else { ConfigAndConnectionServer configAndConnectionServer = new ConfigAndConnectionServer().invoke(1); StorageClient1 storageClient1 = configAndConnectionServer.getStorageClient1(); storageServer = configAndConnectionServer.getStorageServer(); trackerServer = configAndConnectionServer.getTrackerServer(); String tempFileName = file.getOriginalFilename(); //设置元信息 NameValuePair[] metaList = new NameValuePair[3]; //原始文件名称 metaList[0] = new NameValuePair("fileName", tempFileName); //文件后缀 byte[] fileBuff = file.getBytes(); String fileId = ""; //截取后缀 String fileExtName = tempFileName.substring(tempFileName.lastIndexOf(".") + 1); metaList[1] = new NameValuePair("fileExtName", fileExtName); //文件大小 metaList[2] = new NameValuePair("fileLength", String.valueOf(file.getSize())); /** * 4.调用客户端呢的upload_file1的方法开始上传文件 */ fileId = storageClient1.upload_file1(fileBuff, fileExtName, metaList); retMap = handleResult(retMap, fileId); } } catch (Exception e) { retMap.put("code", "0002"); retMap.put("msg", "error:文件上传失败!"); }finally { /** * 5.关闭跟踪服务器的连接 */ colse(storageServer, trackerServer); } return retMap; } /** * 下载文件 * * @param response * @param filepath 数据库存的文件路径 * @param downname 下载后的名称 * filepath M00/开头的文件路径 * group 文件所在的组 如:group0 * @throws IOException */ public static void download(HttpServletResponse response, String group, String filepath, String downname) { StorageServer storageServer = null; TrackerServer trackerServer = null; try { ConfigAndConnectionServer configAndConnectionServer = new ConfigAndConnectionServer().invoke(0); StorageClient storageClient = configAndConnectionServer.getStorageClient(); storageServer = configAndConnectionServer.getStorageServer(); trackerServer = configAndConnectionServer.getTrackerServer(); /** *4.调用客户端的下载download_file的方法 */ byte[] b = storageClient.download_file(group, filepath); if (b == null) { logger.error("Error1 : file not Found!"); response.getWriter().write("Error1 : file not Found!"); } else { logger.info("下载文件.."); downname = new String(downname.getBytes("utf-8"), "ISO8859-1"); response.setHeader("Content-Disposition", "attachment;fileName=" + downname); OutputStream out = response.getOutputStream(); out.write(b); out.close(); } } catch (Exception e) { e.printStackTrace(); try { response.getWriter().write("Error1 : file not Found!"); } catch (IOException e1) { e1.printStackTrace(); } }finally { /** * 5.关闭跟踪服务器的连接 */ colse(storageServer, trackerServer); } } /** * 删除文件 * * @param group 文件分组, filepath 已M00/ 开头的文件路径 * @return Map<String,Object> code-返回代码, msg-错误信息 */ public static Map<String, Object> delete(String group, String filepath) { Map<String, Object> retMap = new HashMap<String, Object>(); StorageServer storageServer = null; TrackerServer trackerServer = null; try { ConfigAndConnectionServer configAndConnectionServer = new ConfigAndConnectionServer().invoke(0); StorageClient storageClient = configAndConnectionServer.getStorageClient(); storageServer = configAndConnectionServer.getStorageServer(); trackerServer = configAndConnectionServer.getTrackerServer(); /** * 4.调用客户端的delete_file方法删除文件 */ int i = storageClient.delete_file(group, filepath); if (i == 0) { retMap.put("code", "0000"); retMap.put("msg", "删除成功!"); } else { retMap.put("code", "0001"); retMap.put("msg", "文件不存在!"); } } catch (Exception e) { e.printStackTrace(); retMap.put("code", "0002"); retMap.put("msg", "删除失败!"); } finally { /** * 5.关闭跟踪服务器的连接 */ colse(storageServer, trackerServer); } return retMap; } /** * 关闭服务器 * * @param storageServer * @param trackerServer */ private static void colse(StorageServer storageServer, TrackerServer trackerServer) { if (storageServer != null && trackerServer != null) { try { storageServer.close(); trackerServer.close(); } catch (IOException e) { e.printStackTrace(); } } } /** * 处理上传到文件服务器之后,返回来的结果 * * @param retMap * @param fileId * @return */ private static Map<String, Object> handleResult(Map<String, Object> retMap, String fileId) { if (!fileId.equals("") && fileId != null) { retMap.put("code", "0000"); retMap.put("group", fileId.substring(0, 6)); retMap.put("msg", fileId.substring(7, fileId.length())); } else { retMap.put("code", "0003"); retMap.put("msg", "error:上传失败!"); } return retMap; } /** * @项目名称:lidong-dubbo * @类名:FastDFSUtil * @类的描述: ConfigAndConnectionServer * @作者:lidong * @创建时间:2017/2/7 上午8:47 * @公司:chni * @QQ:1561281670 * @邮箱:lidong1665@163.com */ private static class ConfigAndConnectionServer { private TrackerServer trackerServer; private StorageServer storageServer; private StorageClient storageClient; private StorageClient1 storageClient1; public TrackerServer getTrackerServer() { return trackerServer; } public StorageServer getStorageServer() { return storageServer; } public StorageClient getStorageClient() { return storageClient; } public StorageClient1 getStorageClient1() { return storageClient1; } public ConfigAndConnectionServer invoke(int flag) throws IOException, MyException { /** * 1.读取fastDFS客户端配置文件 */ ClassPathResource cpr = new ClassPathResource("fdfs_client.conf"); /** * 2.配置文件的初始化信息 */ ClientGlobal.init(cpr.getClassLoader().getResource("fdfs_client.conf").getPath()); TrackerClient tracker = new TrackerClient(); /** * 3.建立连接 */ trackerServer = tracker.getConnection(); storageServer = null; /** * 如果flag=0时候,构造StorageClient对象否则构造StorageClient1 */ if (flag == 0) { storageClient = new StorageClient(trackerServer, storageServer); } else { storageClient1 = new StorageClient1(trackerServer, storageServer); } return this; } } } 4.5 SpringMVC 上传文件到Fastdfs文件服务器 @RequestMapping("/upload") public String addUser(@RequestParam("file") CommonsMultipartFile[] files, HttpServletRequest request){ for(int i = 0;i<files.length;i++){ logger.info("fileName-->" + files[i].getOriginalFilename()+" file-size--->"+files[i].getSize()); Map<String, Object> retMap = FastDFSUtil.upload(files[i]); String code = (String) retMap.get("code"); String group = (String) retMap.get("group"); String msg = (String) retMap.get("msg"); if ("0000".equals(code)){ logger.info("文件上传成功"); //TODO:将上传文件的路径保存到mysql数据库 }else { logger.info("文件上传失败"); } } return "/success"; } 基本上就这么多。大家在学习的过程中如果遇到问题。可以直接在下面评论、吐槽。 代码地址
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010046908/article/details/54773323 一般在mac上安装软件大家都是比较喜欢用brew来安装,今天就用brew来安装RabbitMQ。详细信息可以查看官网http://www.rabbitmq.com/install-standalone-mac.html 1.使用brew来安装 RabbitMQ brew install rabbitmq 看到如下的代码表示RabbitMQ安装成功 localhost:~ lidong$ brew install rabbitmq Updating Homebrew... ==> Installing dependencies for rabbitmq: jpeg, libpng, libtiff, wxmac, erlang ==> Installing rabbitmq dependency: jpeg ==> Downloading https://homebrew.bintray.com/bottles/jpeg-8d.sierra.bottle.2.tar ######################################################################## 100.0% ==> Pouring jpeg-8d.sierra.bottle.2.tar.gz �� /usr/local/Cellar/jpeg/8d: 19 files, 708.3K ==> Installing rabbitmq dependency: libpng ==> Downloading https://homebrew.bintray.com/bottles/libpng-1.6.28.sierra.bottle ######################################################################## 100.0% ==> Pouring libpng-1.6.28.sierra.bottle.tar.gz �� /usr/local/Cellar/libpng/1.6.28: 26 files, 1.2M ==> Installing rabbitmq dependency: libtiff ==> Downloading https://homebrew.bintray.com/bottles/libtiff-4.0.7_1.sierra.bott ######################################################################## 100.0% ==> Pouring libtiff-4.0.7_1.sierra.bottle.tar.gz �� /usr/local/Cellar/libtiff/4.0.7_1: 248 files, 3.4M ==> Installing rabbitmq dependency: wxmac ==> Downloading https://homebrew.bintray.com/bottles/wxmac-3.0.2_4.sierra.bottle ######################################################################## 100.0% ==> Pouring wxmac-3.0.2_4.sierra.bottle.tar.gz �� /usr/local/Cellar/wxmac/3.0.2_4: 810 files, 24.6M ==> Installing rabbitmq dependency: erlang ==> Downloading https://homebrew.bintray.com/bottles/erlang-19.2.sierra.bottle.t ######################################################################## 100.0% ==> Pouring erlang-19.2.sierra.bottle.tar.gz ==> Caveats Man pages can be found in: /usr/local/opt/erlang/lib/erlang/man Access them with `erl -man`, or add this directory to MANPATH. ==> Summary �� /usr/local/Cellar/erlang/19.2: 7,310 files, 280.9M ==> Installing rabbitmq ==> Using the sandbox ==> Downloading https://www.rabbitmq.com/releases/rabbitmq-server/v3.6.6/rabbitm ######################################################################## 100.0% ==> /usr/bin/unzip -qq -j /usr/local/Cellar/rabbitmq/3.6.6/plugins/rabbitmq_mana ==> Caveats Management Plugin enabled by default at http://localhost:15672 Bash completion has been installed to: /usr/local/etc/bash_completion.d To have launchd start rabbitmq now and restart at login: brew services start rabbitmq Or, if you don't want/need a background service you can just run: rabbitmq-server ==> Summary �� /usr/local/Cellar/rabbitmq/3.6.6: 188 files, 5.8M, built in 9 minutes 12 seconds 注意: rabbitmq的安装目录: /usr/local/Cellar/rabbitmq/3.6.6 3.RabbitMQ 的启动 进入到 /usr/local/Cellar/rabbitmq/3.6.6,执行 localhost:3.6.6 lidong$ sbin/rabbitmq-server 4.RabbitMQ 启动插件 待RabbitMQ 的启动完毕之后,另起终端进入cd /Users/lidong/javaEE/rabbitmq_server-3.6.6/sbin 。启动插件: sudo ./rabbitmq-plugins enable rabbitmq_management(执行一次以后不用再次执行) 5.登陆管理界面 浏览器输入:http://localhost:15672/ 账号密码初始默认都为guest
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010046908/article/details/54728375 一般在mac上安装软件大家都是比较喜欢用brew来安装,今天就用brew来安装ActiveMQ。 1.使用brew来安装 ActiveMQ brew install activemq 看到如下的结果时候,可以很庆幸的告诉你,你已经成功安装了activemq。 localhost:~ lidong$ brew install activemq Updating Homebrew... ==> Auto-updated Homebrew! Updated 1 tap (homebrew/core). ==> New Formulae bit gobby molecule statik carrot2 gsmartcontrol opencoarrays tarsnap-gui cnats gtk-chtheme orc-tools terminator easy-tag imagemagick@6 source-to-image todoman geeqie klavaro speexdsp watchexec gifcap mingw-w64-binutils sqlparse ==> Updated Formulae abcde gradle opus adns grafana orientdb afl-fuzz groonga osc amazon-ecs-cli grsync osquery ammonite-repl gsoap packer ansible gtk+ packetbeat ansible-cmdb gtk+3 pandoc ant h2o parallel antigen hana pazpar2 apktool haproxy pbzip2 app-engine-go-64 harfbuzz pcsc-lite arangodb heroku pdf2htmlex aria2 hivemind pdfcrack armor htmlcleaner pdftoedn arping httrack pdns asio hunspell pev assh hyperscan pgformatter aubio icarus-verilog pgrouting autotrace icoutils pius aws-elasticbeanstalk ievms pkcs11-helper aws-sdk-cpp imagemagick plantuml awscli influxdb pngcrush b2-tools innotop pod2man beansdb intercal poppler berkeley-db ios-webkit-debug-proxy postgrest bfg iperf3 pre-commit bib-tool irssi prips bibutils iso-codes prometheus bind jack pstoedit bitlbee javarepl pulseaudio bitrise jdnssec-tools purescript blockhash jenkins pushpin bogofilter jid pwntools buku jigdo pyenv cabal-install joe pyqt5 caddy jruby qbs cadubi kapacitor qcachegrind caf kawa qemu carina khal qjackctl cattle kibana qscintilla2 cdk kobalt quantlib certbot kotlin rabbitmq clasp kubernetes-cli rancher-cli cloc kubernetes-helm rancher-compose cmake languagetool rancid coffeescript lastpass-cli ranger collectd lean-cli ripgrep commandbox leptonica rocksdb conan lft rpm consul-template libass rswift coturn libcec rtags cromwell libcouchbase rtv crystal-lang libdap ruby-build curlpp libev rust dar libfabric sbcl darcs libgcrypt sdb darkice libgit2 serd datetime-fortran libgit2-glib sfk datomic libgosu shadowsocks-libev dbhash libgphoto2 shmcat dbt libgtop sip dbxml liblas snap7 dcmtk libmikmod snort deis libmill soci deisctl libming sops dependency-check libmwaw sord diff-pdf libosmium sourcekitten diffoscope libphonenumber speedtest_cli dirt libpng sphinx-doc dmd libproxy sqldiff dnscrypt-proxy libsass sqlite docker libslax sqlite-analyzer docker-compose libspectre sshguard docker-machine libsvm sstp-client docker-machine-nfs libtasn1 stern docker-machine-parallels libtiff stoken docker-swarm libupnp stormpath-cli dockward libusb svtplay-dl doitlive libvirt swaks dpkg libvpx swift dub libxc swiftformat duplicity libxml2 swiftgen dwarfutils lighttpd swiftlint ecl link-grammar swig eiffelstudio linkerd syncthing ejabberd liquigraph synfig eject lmdb syntaxerl elasticsearch logentries tbox elasticsearch@2.4 logstash tcpkali elixir logtalk telegraf elixirscript lrdf terraform emscripten lsyncd terragrunt etcd ltc-tools thefuck euca2ools lz4 thrift extract_url macvim tile38 fabio makeself tin fdk-aac mariadb tintin fftw mcabber tippecanoe filebeat mediaconch tomcat flatbuffers memcached transcrypt flow memcacheq tty-clock fluent-bit mercurial ttyd fontforge metaproxy tvnamer fonttools metricbeat twarc fossil micropython typescript fping mikutter u-boot-tools fq minizip udunits freeswitch mktorrent unittest-cpp freetds mkvtoolnix unrar fwup moc unshield fzf mongo-c-driver vapoursynth gammu mongodb vdirsyncer gcal mongoose vice gdb mono vim geckodriver mpd wavpack geoipupdate mpv webalizer ghc msgpack weechat ghostscript mypy whatmp3 ginac neofetch wireguard-tools git-cola nexus xapian git-lfs nghttp2 xmlrpc-c git-subrepo nim xonsh git-test no-more-secrets xqilla git-tracker node xrootd giter8 node-build xxhash gitlab-ci-multi-runner node@0.12 xz gitup node@4 yadm gitversion node@6 yank gmime nodeenv yarn gnu-cobol notmuch yash gnu-sed nss yaws gnupg-pkcs11-scd nvc yaz gnupg2 nvi yle-dl gnuradio open-cobol you-get gnutls open-jtalk youtube-dl go open-mesh zabbix godep open-ocd zbar gofabric8 openconnect zeromq google-java-format opencore-amr zimg gosu openshift-cli zplug ==> Renamed Formulae eigen32 -> eigen@3.2 scala210 -> scala@2.10 scala211 -> scala@2.11 ==> Deleted Formulae cpp-netlib dmtx-utils dynamodb-local gcc@6 qtplay ==> Using the sandbox ==> Downloading https://www.apache.org/dyn/closer.cgi?path=/activemq/5.14.3/apac ==> Best Mirror http://mirrors.cnnic.cn/apache/activemq/5.14.3/apache-activemq-5 ######################################################################## 100.0% ==> Caveats To have launchd start activemq now and restart at login: brew services start activemq Or, if you don't want/need a background service you can just run: activemq start ==> Summary �� /usr/local/Cellar/activemq/5.14.3: 555 files, 59.7M, built in 1 minute 39 seconds localhost:~ lidong$ 2.使用activemq –version来查看安装的版本 localhost:~ lidong$ activemq --version INFO: Loading '/usr/local/Cellar/activemq/5.14.3/libexec//bin/env' INFO: Using java '/Library/Java/JavaVirtualMachines/jdk1.8.0_92.jdk/Contents/Home/bin/java' Java Runtime: Oracle Corporation 1.8.0_92 /Library/Java/JavaVirtualMachines/jdk1.8.0_92.jdk/Contents/Home/jre Heap sizes: current=62976k free=61648k max=932352k JVM args: -Xms64M -Xmx1G -Djava.util.logging.config.file=logging.properties -Djava.security.auth.login.config=/usr/local/Cellar/activemq/5.14.3/libexec//conf/login.config -Dactivemq.classpath=/usr/local/Cellar/activemq/5.14.3/libexec//conf:/usr/local/Cellar/activemq/5.14.3/libexec//../lib/: -Dactivemq.home=/usr/local/Cellar/activemq/5.14.3/libexec/ -Dactivemq.base=/usr/local/Cellar/activemq/5.14.3/libexec/ -Dactivemq.conf=/usr/local/Cellar/activemq/5.14.3/libexec//conf -Dactivemq.data=/usr/local/Cellar/activemq/5.14.3/libexec//data Extensions classpath: [/usr/local/Cellar/activemq/5.14.3/libexec/lib,/usr/local/Cellar/activemq/5.14.3/libexec/lib/camel,/usr/local/Cellar/activemq/5.14.3/libexec/lib/optional,/usr/local/Cellar/activemq/5.14.3/libexec/lib/web,/usr/local/Cellar/activemq/5.14.3/libexec/lib/extra] ACTIVEMQ_HOME: /usr/local/Cellar/activemq/5.14.3/libexec ACTIVEMQ_BASE: /usr/local/Cellar/activemq/5.14.3/libexec ACTIVEMQ_CONF: /usr/local/Cellar/activemq/5.14.3/libexec/conf ACTIVEMQ_DATA: /usr/local/Cellar/activemq/5.14.3/libexec/data ActiveMQ 5.14.3 For help or more information please see: http://activemq.apache.org 3.activemq常用的命令 Tasks: browse - Display selected messages in a specified destination. bstat - Performs a predefined query that displays useful statistics regarding the specified broker consumer - Receives messages from the broker create - Creates a runnable broker instance in the specified path. decrypt - Decrypts given text dstat - Performs a predefined query that displays useful tabular statistics regarding the specified destination type encrypt - Encrypts given text export - Exports a stopped brokers data files to an archive file list - Lists all available brokers in the specified JMX context producer - Sends messages to the broker purge - Delete selected destination's messages that matches the message selector query - Display selected broker component's attributes and statistics. start - Creates and starts a broker using a configuration file, or a broker URI. stop - Stops a running broker specified by the broker name. Task Options (Options specific to each task): --extdir <dir> - Add the jar files in the directory to the classpath. --version - Display the version information. -h,-?,--help - Display this help information. To display task specific help, use Main [task] -h,-?,--help 4.启动activeMQ服务 activemq start 看到如下信息,就表示已经安装成功 localhost:~ lidong$ activemq start INFO: Loading '/usr/local/Cellar/activemq/5.14.3/libexec//bin/env' INFO: Using java '/Library/Java/JavaVirtualMachines/jdk1.8.0_92.jdk/Contents/Home/bin/java' INFO: Starting - inspect logfiles specified in logging.properties and log4j.properties to get details INFO: pidfile created : '/usr/local/Cellar/ActiveMQ/5.14.3/libexec//data/activemq.pid' (pid '2402') 然后就可以访问管理web console。在浏览器中输入url: http://localhost:8161/ 点击 Manager ActiveMQ boker 输入用户名:admin 密码admin 看到这个页面,就可以到ActiveMQ 启动成功了。
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010046908/article/details/54174622 前面已经写了四篇关于dubbo2.5-spring4-mybastis3.2-springmvc4-mongodb3.4-redis3.2整合的文章: dubbo2.5-spring4-mybastis3.2-springmvc4-mongodb3.4-redis3.2整合(一)Dubbo的使用 dubbo2.5-spring4-mybastis3.2-springmvc4-mongodb3.4-redis3.2整合(二)之 JDBC连接池、监控组件 Druid dubbo2.5-spring4-mybastis3.2-springmvc4-mongodb3.4-redis3.2整合(三)使用Spring AOP实现mysql的读写分离 dubbo2.5-spring4-mybastis3.2-springmvc4-mongodb3.4-redis3.2整合(四)Spring AOP中使用log4j实现http请求日志入mongodb dubbo2.5-spring4-mybastis3.2-springmvc4-mongodb3.4-redis3.2整合(五)Spring中spring-data-redis的使用 接着上一篇文章,今天来写一篇关于Spring-data-redis在Spring中的缓存的使用 缓存(Caching)可以存储经常会用到的信息,这样每次需要的时候,这些信息都可以立即可用的。尽管Spring自身并没有实现缓存的解决方案,但是他对缓存功能提供了声明式的支持,能够与各种流行的缓存进行集成。几天主要介绍的是redis的缓存的支持。 1. Spring中启用对缓存管理的支持 Spring中对缓存管理器的支持有两种方式: 注解驱动的缓存 XML声明的缓存 1.1 注解驱动的缓存 使用Spring的缓存抽象时,最为通用的方式就是在方法上添加@Cacheable和@CacheEvict注解。 在往bean添加缓存注解之前,必须要启用Spring对注解驱动的支持。如果我们使用java配置的话,那么可以在其中一个配置上添加@EnableCaching,这样的话就能启动注解驱动的缓存。 package com.lidong.util; import java.lang.reflect.Method; import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.CachingConfigurerSupport; import org.springframework.cache.annotation.EnableCaching; import org.springframework.cache.interceptor.KeyGenerator; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.cache.RedisCacheManager; import org.springframework.data.redis.core.RedisTemplate; @Configuration @EnableCaching public class CachingConfig extends CachingConfigurerSupport{ @Bean public KeyGenerator wiselyKeyGenerator(){ return new KeyGenerator() { @Override public Object generate(Object target, Method method, Object... params) { StringBuilder sb = new StringBuilder(); sb.append(target.getClass().getName()); sb.append(method.getName()); for (Object obj : params) { sb.append(obj.toString()); } return sb.toString(); } }; } /** * 声明缓存管理器 * @param redisTemplate * @return */ @Bean public CacheManager cacheManager(RedisTemplate<String, Object> redisTemplate) { return new RedisCacheManager(redisTemplate); } } 1.2 XML声明的缓存 如果以XML的方式配置应用的话,那么可以是使用Spring cache命名空间的 <cache:annotation-driven/> 元素来启动注解驱动的缓存。 <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/beans" xmlns:cache="http://www.springframework.org/schema/cache" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache-4.0.xsd"> <!--启用缓存--> <cache:annotation-driven/> <!--声明缓存管理器--> <bean id="org.springframework.data.redis.cache.RedisCacheManager"/> </beans> 2. Spring中配置缓存管理器 Spring 3.1内置了五个缓存管理器 SimpleCacheManager NoOpCacheManager ConcrrentMapCacheManager CompositeCacheManager EhCacheCacheManager Spring 3.2引入了另外的一个缓存管理器,这个缓存管理器可以在基于JCache(JSR-107)的缓存提供商之中。除了核心的Spring框架,Spring data又提供了两个缓存管理器。 - RedisCacheManager 来自于Spring-data-redis项目 - GemfireCacheManager 来自于Spring-data-GemFire项目 所以我们选择缓存管理器时候,取决于使用底层缓存供应商。 3. Spring中配置Redis缓存管理器 缓存的条目不过就是一个键值对(key-vlaue),其中key描述了产生value的操作和参数。因此你,Redis作为key-value存储,非常适合于做缓存。 Redis可以用来为spring缓存抽象机制存储缓存条目,Spring-data-redis提供了RedisCacheManager,这是CacheManager的一个实现。RedisCacheManager 会与一个Redis服务器协作,并且通过RedisTemplate将缓存条目存储到Redis中。 @Configuration @EnableCaching public class RedisCachingConfig extends CachingConfigurerSupport{ @Bean public KeyGenerator wiselyKeyGenerator(){ return new KeyGenerator() { @Override public Object generate(Object target, Method method, Object... params) { StringBuilder sb = new StringBuilder(); sb.append(target.getClass().getName()); sb.append(method.getName()); for (Object obj : params) { sb.append(obj.toString()); } return sb.toString(); } }; } /** * 声明缓存管理器RedisCacheManager * @param redisTemplate * @return */ @Bean public CacheManager cacheManager(RedisTemplate<String, Object> redisTemplate) { return new RedisCacheManager(redisTemplate); } } 4. 为方法添加注解以支持缓存 Spring 的缓存抽象在很大程度上是围绕切面的构建的。在Spring中启用缓存时,会创建一个切面,它触发一个或更多的Spring的缓存注解。 Spring中提供了四个注解来声明缓存规则 @Cacheable 声明Spring在调用方法之前,首先应该在缓存中查找方法的返回值。如果这个值能够找到,就会返回存储的值,否则的话,这个方法就会被调用,返回值会放在缓存之中。 @CachePut 表明Spring应该将方法的返回值放到缓存中,在方法的调用前并不会检查缓存,方法始终都会被调用 @CacheEvict表明Spring应该在缓存中清除一个或多个条目 @Caching 这是一个分组的注解,能够同时应用多个其他的缓存注解 在Service层缓存数据的数据 package com.lidong.core.user.service; import java.util.List; import javax.annotation.Resource; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; import com.lidong.api.service.user.IUserService; import com.lidong.core.user.dao.IUserDao; import com.lidong.model.user.User; @Service("userService") public class UserServiceImp implements IUserService { @Resource IUserDao mIUserDao; @Override public String sayHello(String name) { return "Hello " + name; } @Cacheable(value={"getUserById"}) @Override public User getUserById(int userId) { return mIUserDao.selectByPrimaryKey(userId); } @Override public User getUserByUsername(String username) { return mIUserDao.selectByPrimaryUsername(username); } @CacheEvict(value={"getAllUser"},allEntries=true) @Override public void addUser(User user) { mIUserDao.insert(user); } @Cacheable(value={"getAllUser"}) @Override public List<User> getAllUser() { return mIUserDao.selectAllUsers(); } @CacheEvict(value={"getAllUser","getUserById"},allEntries=true) @Override public int delUserById(Integer userId) { return mIUserDao.deleteByPrimaryKey(userId); } @CacheEvict(value={"getAllUser","getUserById"},allEntries=true) @Override public int updateUser(User user) { return mIUserDao.updateByPrimaryKey(user); } } 数据缓存成功 再次获取用户列表的时候;直接从缓存中读取用户的列表。 注意:缓存java对象时必须实现Serilaizable接口,因为Spring会将对象先序列化之后再存入到Redis中。 代码地址
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010046908/article/details/54170891 前面已经写了四篇关于dubbo2.5-spring4-mybastis3.2-springmvc4-mongodb3.4-redis3.2整合的文章: dubbo2.5-spring4-mybastis3.2-springmvc4-mongodb3.4-redis3.2整合(一)Dubbo的使用 dubbo2.5-spring4-mybastis3.2-springmvc4-mongodb3.4-redis3.2整合(二)之 JDBC连接池、监控组件 Druid dubbo2.5-spring4-mybastis3.2-springmvc4-mongodb3.4-redis3.2整合(三)使用Spring AOP实现mysql的读写分离 dubbo2.5-spring4-mybastis3.2-springmvc4-mongodb3.4-redis3.2整合(四)Spring AOP中使用log4j实现http请求日志入mongodb 今天继续写一篇关于Spring中spring-data-redis的使用。 Redis是一种特殊的类型的数据库,它被称为一种key-value存储。key-value存储保存的是键值对。实际上,key-value存储于哈希Map有很大的相似。 spring data是一种面向模板的数据访问,能够在使用Redis的时候,为我们提供了帮助。于是就有了spring-data-redis。 1. spring-data-redis的简介 spring-data-redis包含了多个模板实现,用来完成Redis数据库的存取功能。创建spring-data-redis模板之前,我们首先需要一个Redis连接工厂,spring-data-redis提供了四个连接工厂供我们选择。 2.spring-data-redis所需要依赖 <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.8.0</version> </dependency> <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-redis</artifactId> <version>1.6.2.RELEASE</version> </dependency> 3. spring-data-redis的使用 3.1 连接到Redis Redis连接工厂会生成到Redis数据库服务器的连接。spring-data-redis为四种Redis客户端实现了连接工厂: JedisConnectionFactory JredisConnectionFactory LettuceConnectionFactory SrpConnectionFactory 具体选择哪一种取决于自己。 (1)创建redis.properties: maxTotal=8 #最大空闲时间 maxIdle=8 #最短空闲时间 minIdle=0 #最大的等待时间 maxWaitMillis=6000 #Redis的连接地址 hostR=127.0.0.1 #端口 portR=6379 (2)创建spring-redis.xml <?xml version="1.0" encoding="UTF-8"?> <beans xmlns:rabbit="http://www.springframework.org/schema/rabbit" xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/rabbit http://www.springframework.org/schema/rabbit/spring-rabbit-1.2.xsd"> <!-- 引入redis.properties配置文件--> <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="location" value="classpath:redis.properties" /> </bean> <!-- redis连接池的配置 --> <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig"> <property name="maxTotal" value="${maxTotal}" /> <property name="maxIdle" value="${maxIdle}" /> <property name="minIdle" value="${minIdle}" /> <property name="maxWaitMillis" value="10000" /> <property name="minEvictableIdleTimeMillis" value="300000"></property> <property name="numTestsPerEvictionRun" value="3"></property> <property name="timeBetweenEvictionRunsMillis" value="60000"></property> </bean> <!-- 工厂类配置 --> <bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"> <property name="hostName" value="${hostR}" /> <property name="port" value="${portR}" /> <property name="poolConfig" ref="jedisPoolConfig" /> <property name="timeout" value="15000"></property> <property name="usePool" value="true"></property> </bean> <!-- redisTemplate配置 --> <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate"> <property name="connectionFactory" ref="jedisConnectionFactory" /> <property name="keySerializer"> <bean class="org.springframework.data.redis.serializer.StringRedisSerializer" /> </property> <property name="valueSerializer"> <bean class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer" /> </property> <property name="enableTransactionSupport" value="true" /> </bean> </beans> 以上是我配置的jedisPoolConfig,jedisConnectionFactory,redisTemplate。 3.2 使用RedisTemplate Redis连接工厂会生成到Redis key-value存储的连接(以RedisConnection的形式。)借助RedisConnection,可以存储和读取数据。 spring-redis-data以模板的形式提供了较好等级的数据访问方案。实际上,spring-data-redis提供了两个模板: RedisTemplate StringRedisTemplate 其中RedisTemplate使用两个类型进行了参数。第一个参数是key的类型,第二个参数是value的类型,而StringRedisTemplate是RedisTemplate的扩展,只关注String类型,也就是key和vlaue都是String类型。 3.2.1 RedisTemplate使用简单值 假设我们想通过RedisTemplate public class UserRedisDaoImp extends AbstractBaseRedisTemplete<User> implements IUserRedisDao { @Override public User findById(String key) { return (User) redisTemplate.opsForValue().get(key); } @Override public void saveUser(String key,User user) { redisTemplate.opsForValue().set(key, user); } } 3.2.2 RedisTemplate使用List值 使用List类型的value与之类似,只需要使用opForList()方法, package com.lidong.core.user.dao; import java.util.List; import com.lidong.model.user.User; import com.lidong.util.AbstractBaseRedisTemplete; public class UserRedisDaoImp extends AbstractBaseRedisTemplete<User> implements IUserRedisDao { @Override public List<User> getUserList(String key,long start,long end) { return redisTemplate.opsForList().range(key, 0, end); } @Override public Long addUserToUserList(String key, User user) { return redisTemplate.opsForList().leftPush(key, user); } } 3.2.3 RedisTemplate使用Set值 除了使用List类型和value类型,我们还可以使用opForSet()的方法操作Set,最为常用的的就是向Set中添加一个元素: @Override public void saveUser(String key,User user) { redisTemplate.opsForSet().add(key, user); } 在我们有多个Set,并对这些Set集合进行差、交、并的操作。 Set<User> difference = redisTemplate.opsForSet().difference("users1", "users2"); Set<User> union = redisTemplate.opsForSet().union("users1", "users2"); Set<User> intersect = redisTemplate.opsForSet().intersect("users1", "users2"); //我还可以移除Set中的元素 Long remove = redisTemplate.opsForSet().remove("user1", user); 3.2.4 RedisTemplete绑定到某个key上 我们可以将Value、List、Set等可以绑定到指定的key上。这些用个的不太多,但是也简单。这里就不具体写了。 3.2.5 构造AbstractBaseRedisTemplete package com.lidong.util; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.data.redis.core.RedisTemplate; /** * 基础的RedisTemplete * @author lidong * @param <T> * @date 2017-1-5 */ public abstract class AbstractBaseRedisTemplete<T> implements ApplicationContextAware { protected RedisTemplate<String,T> redisTemplate; /** * @Description RedisTemplate * @param redisTemplate */ public void setRedisTemplate(RedisTemplate<String,T> redisTemplate) { this.redisTemplate = redisTemplate; } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { @SuppressWarnings("unchecked") RedisTemplate<String,T> redisTemplate = applicationContext.getBean( "redisTemplate", RedisTemplate.class); setRedisTemplate(redisTemplate); } } Spring-Data-Redis的使用基本最常用 的就是这三种类型value类型、List类型、Set类型。 代码地址
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010046908/article/details/54021946 1.Macaca简介 Macaca是阿里巴巴集团开发的一套完整的自动化测试解决方案。 2.Macaca特性: 支持移动端和PC端 支持Native, Hybrid, H5 等多种应用类型 提供客户端工具和持续集成服务 3.macaca-cli客户端的安装: 3.1安装 Node.js 请安装 Node.js v4.0 或者更高版本,装好 Node.js 后命令行里就已经集成了 npm 工具,为了提高安装模块的速度,请使用国内的 cnpm。 3.2 iOS 环境安装 请安装 Xcode8 或者更高版本 需要安装 usbmuxd 以便于通过 USB 通道测试 iOS 真机,不需要测试真机则不用安装 $ brew install usbmuxd 应用中如含有 WebView,请安装 ios-webkit-debug-proxy $ brew install ios-webkit-debug-proxy 备注:使用brew命令需要安装Homebrew(一款常用的 MacOS 的包管理器),请按照官网提示安装。 准备 App 包:如需要测试 iOS 应用,请使用 Scheme 设置为 debug 的 .app 包。 3.3 Android环境安装 3.3.1 安装 JDK 配置 JAVA_HOME,根据你所使用的 shell 工具修改不同的文件,比如 ~/.bashrc, ~/.bash_profile, ~/.zshrc shell export JAVA_HOME=path/to/your/Java/Home 3.3.2安装安卓 SDK 运行 brew install android-sdk,然后安装18-24版本中的任一 SDK shell 环境设置 ANDROID_HOME 根据你所使用的Terminal修改不同的 文件,比如~/.bashrc, ~/.bash_profile, ~/.zshrc # 如果是通过homebrew安装的android-sdk,则路径如下 export ANDROID_HOME = /usr/local/opt/android-sdk # 如果通过其他方式安装的sdk,路径设置为对应的android sdk的路径 export ANDROID_HOME = path/to/your/Android/sdk 注意:准备 App 包:如需要测试 Android 应用,请使用 .apk 格式的包。 3.4 全局安装macaca $ npm i -g macaca-cli 如果看到如下可爱的小猴子,那恭喜你安装成功啦!重新安装则会覆盖更新。 3.5安装驱动 3.6 环境检查 通过 macaca doctor 可以检查环境是否配置成功 $ macaca doctor 如上图所示则表示环境均配置正常,如果有错误,会出现红色的提示。 4 运行官方示例 将官方示例(mobile-app-sample-nodejs)克隆到本地,更多的示例请访问macaca-sample。 $ git clone https://github.com/macaca-sample/mobile-app-sample-nodejs.git --depth=1 $ cd mobile-app-sample-nodejs $ npm i # 更多运行方式见Makefile $ macaca run --verbose 4.1 Android 的自动化测试 先在mobile-app-sample-nodejs/macaca-test/mobile-app-sample.test.js脚本文件中如果是ios该为Android。 var platform = process.env.platform || 'Android'; platform = platform.toLowerCase(); 在mobile-app-sample-nodejs目录下执行 macaca run 测试的过程 lidongdeMacBook-Pro:mobile-app-sample-nodejs lidong$ macaca run ./macaca-test/macaca-mobile-sample.test.js >> webdriver sdk launched >> >> >> macaca mobile sample >> get /Users/lidong/.macaca-temp/android_app_bootstrap-debug.apk from cache >> sha:e2ca601f9ee1ec101326d12377a2e8d4 INSTRUMENTATION_STATUS: numtests=1 INSTRUMENTATION_STATUS: stream= com.android.uiautomator.client.Initialize: INSTRUMENTATION_STATUS: id=UiAutomatorTestRunner INSTRUMENTATION_STATUS: test=testStartServer INSTRUMENTATION_STATUS: class=com.android.uiautomator.client.Initialize INSTRUMENTATION_STATUS: current=1 INSTRUMENTATION_STATUS_CODE: 1 uiautomator start socket server. >> socket server ready >> socket client ready recive: {"cmd":"wake","args":{}} return: {"success":true,"data":{"status":0,"value":true}} recive: {"cmd":"getWindowSize","args":{}} return: {"success":true,"data":{"status":0,"value":"{\"width\":1080,\"height\":1794}"}} >> current window size {"width":1080,"height":1794} recive: {"cmd":"find","args":{"strategy":"class name","selector":"android.widget.EditText","multiple":true}} return: {"success":true,"data":{"status":0,"value":[{"ELEMENT":"1"},{"ELEMENT":"2"}]}} recive: {"cmd":"clearText","args":{"elementId":"1"}} return: {"success":true,"data":{"status":0,"value":true}} recive: {"cmd":"setText","args":{"elementId":"1","text":"中文+Test+12345678"}} return: {"success":true,"data":{"status":0,"value":true}} recive: {"cmd":"find","args":{"strategy":"class name","selector":"android.widget.EditText","multiple":true}} return: {"success":true,"data":{"status":0,"value":[{"ELEMENT":"3"},{"ELEMENT":"4"}]}} recive: {"cmd":"clearText","args":{"elementId":"4"}} return: {"success":true,"data":{"status":0,"value":true}} recive: {"cmd":"setText","args":{"elementId":"4","text":"111111"}} return: {"success":true,"data":{"status":0,"value":true}} recive: {"cmd":"find","args":{"strategy":"name","selector":"Login","multiple":true}} return: {"success":true,"data":{"status":0,"value":[{"ELEMENT":"5"}]}} recive: {"cmd":"click","args":{"elementId":"5"}} return: {"success":true,"data":{"status":0,"value":true}} >> >> #1 should login success (19613ms) recive: {"cmd":"getSource","args":{}} return: {"success":true,"data":{"status":0,"value":true}} >> { hierarchy: { rotation: '0', node: { index: '0', class: 'android.widget.FrameLayout', package: 'com.github.android_app_bootstrap', checkable: 'false', checked: 'false', clickable: 'false', enabled: 'true', focusable: 'false', focused: 'false', scrollable: 'false', 'long-clickable': 'false', password: 'false', selected: 'false', bounds: '[0,0][1080,1794]', node: [Object] } } } >> >> #2 should display home (911ms) recive: {"cmd":"find","args":{"strategy":"name","selector":"list","multiple":false}} 等等 4.2 IOS 的自动化测试 先在mobile-app-sample-nodejs/macaca-test/mobile-app-sample.test.js脚本文件中如果是Android改为ios。 var platform = process.env.platform || 'ios'; platform = platform.toLowerCase(); 在mobile-app-sample-nodejs目录下执行 macaca run 测试过程 lidongdeMacBook-Pro:mobile-app-sample-nodejs lidong$ macaca run ./macaca-test/macaca-mobile-sample.test.js >> webdriver sdk launched >> >> >> macaca mobile sample >> get /Users/lidong/.macaca-temp/android_app_bootstrap-debug.apk from cache >> sha:e2ca601f9ee1ec101326d12377a2e8d4 INSTRUMENTATION_STATUS: numtests=1 INSTRUMENTATION_STATUS: stream= com.android.uiautomator.client.Initialize: INSTRUMENTATION_STATUS: id=UiAutomatorTestRunner INSTRUMENTATION_STATUS: test=testStartServer INSTRUMENTATION_STATUS: class=com.android.uiautomator.client.Initialize INSTRUMENTATION_STATUS: current=1 INSTRUMENTATION_STATUS_CODE: 1 uiautomator start socket server. >> socket server ready >> socket client ready recive: {"cmd":"wake","args":{}} return: {"success":true,"data":{"status":0,"value":true}} recive: {"cmd":"getWindowSize","args":{}} return: {"success":true,"data":{"status":0,"value":"{\"width\":1080,\"height\":1794}"}} >> current window size {"width":1080,"height":1794} recive: {"cmd":"find","args":{"strategy":"class name","selector":"android.widget.EditText","multiple":true}} return: {"success":true,"data":{"status":0,"value":[{"ELEMENT":"1"},{"ELEMENT":"2"}]}} recive: {"cmd":"clearText","args":{"elementId":"1"}} return: {"success":true,"data":{"status":0,"value":true}} 5.脚本初始化参数 5.1 常见的参数 platformName String 当前用例运行的平台 { iOS / Android / Desktop } browserName String 当前测试的浏览器名称 { iOS: Safari } { Android: Chrome } { Desktop: Chrome / Electron } 5.2 App 相关参数 deviceName String 模拟器的名称,例如 ‘iPhone 6’ 或者 ‘Nexus 5x’。 app Stirng .ipa,.app 或者 .apk 文件的绝对地址或者远程地址,或者是包含上述文件格式的 Zip 文件。 udid String 测试设备的唯一设备 ID。 5.3 Android 的参数介绍 reuse Number 0: 启动并安装 app。{1 (默认): 卸载并重装 app。 2: 仅重装 app。3: 在测试结束后保持 app 状态。} package String Android app 的 package name。 activity String 启动时的 Activity name。 5.4 iOS 的参数介绍 reuse Number 0: 清楚数据并重装 app。 1: (默认) 卸载并重装 app。 2: 仅重装 app。 3: 在测试结束后保持 app 状态。 bundleId String 应用的 Bundle ID,例如 com.apple.Maps。 autoAcceptAlerts Boolean 自动接受所有的系统弹窗信息。默认是 false。 autoDismissAlerts Boolean 自动拒绝所有的系统弹窗信息。默认是 false。 5.5 基本用法 'use strict'; require('should'); var xml2map = require('xml2map'); var platform = process.env.platform || 'ios'; platform = platform.toLowerCase(); var pkg = require('../package'); /** * download app form npm * * or use online resource: https://npmcdn.com/ios-app-bootstrap@latest/build/ios-app-bootstrap.zip * * npm i ios-app-bootstrap --save-dev * * var opts = { * app: path.join(__dirname, '..', 'node_modules', 'ios-app-bootstrap', 'build', 'ios-app-bootstrap.zip'); * }; */ // see: https://macacajs.github.io/desired-caps var iOSOpts = { deviceName: 'iPhone 5s', platformName: 'iOS', autoAcceptAlerts: false, //reuse: 3, //udid: '', //bundleId: 'xudafeng.ios-app-bootstrap', app: 'http://localhost:8087/ios-app-bootstrap.zip' }; var androidOpts = { platformName: 'Android', autoAcceptAlerts: false, // reuse: 3, // udid: '', // package: 'com.github.android_app_bootstrap', // activity: 'com.github.android_app_bootstrap.activity.WelcomeActivity', app: 'http://localhost:8087/android_app_bootstrap-debug.apk' }; const isIOS = platform === 'ios'; const infoBoardId = isIOS ? 'info' : 'com.github.android_app_bootstrap:id/info'; const wd = require('macaca-wd'); // override custom wd require('./wd-extend')(wd, isIOS); describe('macaca mobile sample', function() { this.timeout(5 * 60 * 1000); const driver = wd.promiseChainRemote({ host: 'localhost', port: 3456 }); driver.configureHttp({ timeout: 600 * 1000 }); before(function() { return driver .init(isIOS ? iOSOpts : androidOpts); }); after(function() { return driver .sleep(1000) .quit(); }); it('#1 should login success', function() { return driver .getWindowSize() .then(size => { console.log(`current window size ${JSON.stringify(size)}`); }) .appLogin('中文+Test+12345678', '111111') .sleep(1000); }); Macaca自动化测试Android和IOS应用,基本上说到这里就要结束。后面我们还是学习如何自己写测试脚本。
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010046908/article/details/53982620 1. 为什么要进行(主从复制)读写分离 分布式环境下数据库的读写分离策略是解决数据库读写性能瓶颈的一个关键解决方案,更是最大限度了提高了应用中读取 (Read)数据的速度和并发量。 在进行数据库读写分离的时候,我们首先要进行数据库的主从配置,最简单的是一台Master和一台Slave(大型网站系统的话,当然会很复杂,这里只是分析了最简单的情况)。通过主从配置主从数据库保持了相同的数据,我们在进行读操作的时候访问从数据库Slave,在进行写操作的时候访问主数据库Master。这样的话就减轻了一台服务器的压力。 在进行读写分离案例分析的时候。首先,配置数据库的主从复制,使用mysqlreplicate命令快速搭建 Mysql 主从复制。 2.MySQL主从复制的原理 2.1MySQL主从复制的原理 2.2MySQL主从复制的基本过程 MySQL主从复制的两种情况:同步复制和异步复制,实际复制架构中大部分为异步复制。复制的基本过程如下: Slave上面的IO进程连接上Master,并请求从指定日志文件的指定位置(或者从最开始的日志)之后的日志内容。 Master接收到来自Slave的IO进程的请求后,负责复制的IO进程会根据请求信息读取日志指定位置之后的日志信息,返回给Slave的IO进程。返回信息中除了日志所包含的信息之外,还包括本次返回的信息已经到Master端的bin-log文件的名称以及bin-log的位置。 Slave的IO进程接收到信息后,将接收到的日志内容依次添加到Slave端的relay-log文件的最末端,并将读取到的Master端的 bin-log的文件名和位置记录到master-info文件中,以便在下一次读取的时候能够清楚的告诉Master“我需要从某个bin-log的哪个位置开始往后的日志内容,请发给我”。 Slave的Sql进程检测到relay-log中新增加了内容后,会马上解析relay-log的内容成为在Master端真实执行时候的那些可执行的内容,并在自身执行。 3.开始MySQL5.7.12的主从复制教程: 3.1 MySQL5.6开始主从复制有两种方式:基于日志(binlog);基于GTID(全局事务标示符)。 需要注意的是:GTID方式不支持临时表!所以如果你的业务系统要用到临时表的话就不要考虑这种方式了,至少目前最新版本MySQL5.6.12的GTID复制还是不支持临时表的。 所以此篇教程主要是告诉大家如何通过日志(binlog)方式做主从复制! 3.2 MySQL官方提供的MySQL Replication教程: http://dev.mysql.com/doc/refman/5.6/en/replication.html 这个官方教程强烈建议大家阅读。 3.3、准备工作: 配置MySQL主从复制(读写分离)之前,需要在主从两台服务器先安装好MySQL5.7。 目前最新的MySQL5.7 GA版本是MySQL5.7.12(点此下载MySQL5.7.12源码包)。 需要注意如下两点: (1)如果你需要用于生产环境,安教程安装MySQL时不要急着做mysql启动操作。建议把mysql初始化生成的/usr/local/mysql/mysql.cnf删除,然后把你优化好的mysql配置文件my.cnf放到/etc下。 (2)建议主备两台服务器在同一局域网,主备两台数据库网络需要互通。 4. 我的所使用的环境: 主数据库IP:192.168.0.104 从数据库IP:192.168.0.105 3.4 修改主数据库的的配置文件: 1 [mysqld] 2 server-id=1 3 log-bin=mysqlmaster-bin.log 4 sync_binlog=1 5 #注意:下面这个参数需要修改为服务器内存的70%左右 6 innodb_buffer_pool_size = 512M 7 innodb_flush_log_at_trx_commit=1 8 sql_mode=STRICT_TRANS_TABLES,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION,NO_AUTO_VALUE_ON_ZERO 9 lower_case_table_names=1 10 log_bin_trust_function_creators=1 修改之后要重启mysql服务: # /etc/init.d/mysql restart 3.5、修改从数据库的的配置文件: 注意:(server-id配置为大于主数据库的server-id即可) 1 [mysqld] 2 server-id=2 3 log-bin=mysqlslave-bin.log 4 sync_binlog=1 5 #注意:下面这个参数需要修改为服务器内存的70%左右 6 innodb_buffer_pool_size = 512M 7 innodb_flush_log_at_trx_commit=1 8 sql_mode=STRICT_TRANS_TABLES,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION,NO_AUTO_VALUE_ON_ZERO 9 lower_case_table_names=1 10 log_bin_trust_function_creators=1 修改之后要重启mysql: # /etc/init.d/mysql restart 附一个我已优化过的从数据库配置文件:点此下载 3.6、SSH登录到主数据库: 3.6.1. 在主数据库上创建用于主从复制的账户(192.168.0.104换成你的从数据库IP): 1 # mysql -uroot -p 2 mysql> GRANT REPLICATION SLAVE ON *.* TO 'repl'@'192.168.0.104' IDENTIFIED BY 'repl'; 3.6.2. 主数据库锁表(禁止再插入数据以获取主数据库的的二进制日志坐标): mysql> FLUSH TABLES WITH READ LOCK; 3.6.3. 然后克隆一个SSH会话窗口,在这个窗口打开MySQL命令行: 在这个例子中,二进制日志文件是mysqlmaster-bin.000001,位置是332,记录下这两个值,稍后要用到。 3.6.4 在主数据库上使用mysqldump命令创建一个数据快照: mysqldump -uroot -p -h127.0.0.1 -P3306 –all-databases –triggers –routines –events >all.sql 注意:接下来会提示你输入mysql数据库的root密码,输入完成后,如果当前数据库不大,很快就能导出完成。 3.6.5 解锁第2.6.2步主数据的锁表操作: mysql> UNLOCK TABLES; 3.7、SSH登录到从数据库: (1)通过FTP、SFTP或其他方式,将上一步备份的主数据库快照all.sql上传到从数据库某个路径,例如我放在了/home/lidong/目录下; (2)从导入主的快照: # cd /home/lidong # mysql -uroot -p -h127.0.0.1 -P3306 < all.sql 注意:接下来会提示你输入mysql数据库的root密码,输入完成后,如果当前数据库不大,很快就能导入完成。 (3)给从数据库设置复制的主数据库信息(注意修改MASTER_LOG_FILE和MASTER_LOG_POS的值): # mysql -uroot -p mysql> CHANGE MASTER TO MASTER_HOST='192.168.100.2',MASTER_USER='repl',MASTER_PASSWORD='repl',MASTER_LOG_FILE='mysqlmaster-bin.000001',MASTER_LOG_POS=332; # 然后启动从数据库的复制线程: mysql> START slave; # 接着查询数据库的slave状态: mysql> SHOW slave STATUS \G # 如果下面两个参数都是Yes,则说明主从配置成功! Slave_IO_Running: Yes Slave_SQL_Running: Yes (4)接下来我们可以在主数据库上创建数据库、表、插入数据,然后看从数据库是否同步了这些操作。 一主一从的主从复制就实现了。 4、实现读写分离的两种方法 第一种方式是我们最常用的方式,就是定义2个数据库连接,一个是MasterDataSource,另一个是SlaveDataSource。更新数据时我们读取MasterDataSource,查询数据时我们读取SlaveDataSource。这种方式很简单。 第二种方式动态数据源切换,就是在程序运行时,把数据源动态织入到程序中,从而选择读取主库还是从库。主要使用的技术是:Annotation,spring AOP ,反射。 5.Spring AOP 实现MySQL读写分离的具体实现 5.1创建用于配置动态分配的读写的数据源ChooseDataSource.java package com.lidong.util.aspect; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.commons.lang3.StringUtils; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; /** * 获取数据源,用于动态切换数据源 */ public class ChooseDataSource extends AbstractRoutingDataSource { public static Map<String, List<String>> METHOD_TYPE_MAP = new HashMap<String, List<String>>(); /** * 实现父类中的抽象方法,获取数据源名称 * @return */ protected Object determineCurrentLookupKey() { return DataSourceHandler.getDataSource(); } // 设置方法名前缀对应的数据源 public void setMethodType(Map<String, String> map) { for (String key : map.keySet()) { List<String> v = new ArrayList<String>(); String[] types = map.get(key).split(","); for (String type : types) { if (StringUtils.isNotBlank(type)) { v.add(type); } } METHOD_TYPE_MAP.put(key, v); } } } 5.2 DataSourceAspect进行具体方法的AOP拦截 package com.lidong.util.aspect; import org.apache.commons.lang3.StringUtils; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.annotation.EnableAspectJAutoProxy; import org.springframework.stereotype.Component; /** * 切换数据源(不同方法调用不同数据源) */ @Aspect @Component @EnableAspectJAutoProxy(proxyTargetClass = true) public class DataSourceAspect { protected Logger logger = LoggerFactory.getLogger(this.getClass()); @Pointcut("execution(* com.lidong.core.*.dao.*.*(..))") public void aspect() { } /** * 配置前置通知,使用在方法aspect()上注册的切入点 */ @Before("aspect()") public void before(JoinPoint point) { String className = point.getTarget().getClass().getName(); String method = point.getSignature().getName(); logger.info(className + "." + method + "(" + StringUtils.join(point.getArgs(), ",") + ")"); try { for (String key : ChooseDataSource.METHOD_TYPE_MAP.keySet()) { for (String type : ChooseDataSource.METHOD_TYPE_MAP.get(key)) { if (method.startsWith(type)) { DataSourceHandler.putDataSource(key); } } } } catch (Exception e) { e.printStackTrace(); } } } 5.3 数据源的Handler类 DataSourceHandler.java package com.lidong.util.aspect; /** * 数据源的Handler类 * @author lidong * */ public class DataSourceHandler { // 数据源名称线程池 public static final ThreadLocal<String> holder = new ThreadLocal<String>(); /** * 在项目启动的时候将配置的读、写数据源加到holder中 */ public static void putDataSource(String datasource) { holder.set(datasource); } /** * 从holer中获取数据源字符串 */ public static String getDataSource() { return holder.get(); } } 5.4 spring-db.xml读写数据源配置 driver=com.mysql.jdbc.Driver url=jdbc:mysql://127.0.0.1:3306/hms url1=jdbc:mysql://192.168.0.105:3306/hms username=root password=123456 password1= initialSize=0 maxActive=20 maxIdle=20 minIdle=1 maxWait=60000 timeBetweenEvictionRunsMillis=3000 minEvictableIdleTimeMillis=300000 maxPoolPreparedStatementPerConnectionSize=20 druid.filters= <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd"> <!-- 引入配置文件 <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="location" value="classpath:jdbc.properties" /> </bean> --> <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="locations"> <list> <value>classpath:jdbc.properties</value> <value>classpath:mongo.properties</value> <value>classpath:redis.properties</value> </list> </property> </bean> <bean id="statFilter" class="com.alibaba.druid.filter.stat.StatFilter" lazy-init="true"> <property name="logSlowSql" value="true" /> <property name="mergeSql" value="true" /> </bean> <bean id="readDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> <property name="url" value="${url}" /> <property name="username" value="${username}" /> <property name="password" value="${password}" /> <property name="filters" value="stat" /> <property name="maxActive" value="20" /> <property name="initialSize" value="1" /> <property name="maxWait" value="60000" /> <property name="minIdle" value="1" /> <property name="timeBetweenEvictionRunsMillis" value="3000" /> <property name="minEvictableIdleTimeMillis" value="300000" /> <property name="validationQuery" value="SELECT 'x'" /> <property name="testWhileIdle" value="true" /> <property name="testOnBorrow" value="false" /> <property name="testOnReturn" value="false" /> <property name="poolPreparedStatements" value="true" /> <property name="maxPoolPreparedStatementPerConnectionSize" value="20" /> </bean> <bean id="writeDataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close" init-method="init" lazy-init="true"> <property name="driverClassName" value="${driver}" /> <property name="url" value="${url1}" /> <property name="username" value="${username}" /> <property name="password" value="${password1}" /> <property name="initialSize" value="${initialSize}" /> <property name="maxActive" value="${maxActive}" /> <property name="minIdle" value="${minIdle}" /> <property name="maxWait" value="${maxWait}" /> <property name="proxyFilters"> <list> <ref bean="statFilter" /> </list> </property> <property name="filters" value="${druid.filters}" /> <property name="connectionProperties" value="password=${password1}" /> <property name="testWhileIdle" value="true" /> <property name="testOnBorrow" value="false" /> <property name="testOnReturn" value="false" /> <property name="validationQuery" value="SELECT 'x'" /> <property name="timeBetweenLogStatsMillis" value="60000" /> <property name="minEvictableIdleTimeMillis" value="${minEvictableIdleTimeMillis}" /> <property name="timeBetweenEvictionRunsMillis" value="${timeBetweenEvictionRunsMillis}" /> </bean> <!-- 配置动态分配的读写 数据源 --> <bean id="dataSource" class="com.lidong.util.aspect.ChooseDataSource" lazy-init="true"> <property name="targetDataSources"> <map key-type="java.lang.String" value-type="javax.sql.DataSource"> <!-- write --> <entry key="write" value-ref="writeDataSource" /> <!-- read --> <entry key="read" value-ref="readDataSource" /> </map> </property> <property name="defaultTargetDataSource" ref="writeDataSource" /> <property name="methodType"> <map key-type="java.lang.String"> <!-- read --> <entry key="read" value=",get,select,count,list,query" /> <!-- write --> <entry key="write" value=",add,create,update,delete,remove," /> </map> </property> </bean> </beans> 配置了readDataSource和writeDataSource两个数据源,但是交给 SqlSessionFactoryBean进行管理的只有dataSource,使用了com.lidong.util.aspect.ChooseDataSource来进行数据源的选择,默认的数据源是writeDataSource。methodType是定义了方法的关键字,那些是选择读库,那个是写库。 5.5 在Dao层通过切面选择数据源 package com.lidong.core.user.service; import java.util.List; import javax.annotation.Resource; import org.springframework.stereotype.Service; import com.lidong.api.service.user.IUserService; import com.lidong.core.user.dao.IUserDao; import com.lidong.model.user.User; @Service("userService") public class UserServiceImp implements IUserService { @Resource IUserDao mIUserDao; @Override public User getUserById(int userId) { return mIUserDao.selectByPrimaryKey(userId); } @Override public User getUserByUsername(String username) { return mIUserDao.selectByPrimaryUsername(username); } @Override public void addUser(User user) { mIUserDao.insert(user); } @Override public List<User> getAllUser() { return mIUserDao.selectAllUsers(); } @Override public int delUserById(Integer userId) { return mIUserDao.deleteByPrimaryKey(userId); } @Override public int updateUser(User user) { return mIUserDao.updateByPrimaryKey(user); } } 注意:通过看com.alibaba.druid.pool.DruidDataSourceStatLoggerImpl 是一分钟发一次心跳,监听写数据源有没有宕机。如果宕机会进行重连。 [代码地址]
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010046908/article/details/53978250 1、面向切面编程的一些术语 切面(Aspect):切面用于组织多个Advice,Advice放在切面中的定义。 连接点(Jionpoint):程序执行过程中的明确的点,如方法的调用,或异常的抛出。在SpringAOP中,连接点总是方法的调用。 增强的处理(Advice):AOP框架在特定的切入点执行的增强处理。处理有around、brefore和after等类型。 切入点(Pointcut):可以插入增强处理的连接点。简而言之,当某个连接点满足指定的要求时,该连接点将添加增强处理,该连接点也就变成了切入点。 pointcut xxxPointcut():execution(void H*.say*()) 引入:将方法或字段添加到被代理的类中,Spring允许将新的接口引入到任何被调用的处理对象中。例如:你可以使用一个引入,使任何对象实现IsModified接口,以此来简化缓存。 目标对象:被AOP框架进行增强处理的对象,也被称为被增强对象,如果AOP框架采用的是动态的AOP实现 ,那该对象就是一个被代理对象。 AOP代理:AOP框架创建的对象,简单说,代理就是对目标对象的加强,Spring中的AOP代理可以是JDK的动态代理,也可以是cglib的动态代理,前者是实现接口 的目标对象的代理,后者为不实现接口的目标对象的代理。 织入(Weaving):将增强处理添加到目标对象中,并创一个被增强对象(AOP代理)的过程就是织入。织入有l两种实现方式—–编译时增强(AspectJ)和运行时增强(如Spring AOP)。Spring和其他纯Java AOP框架一样,在运行时织入。 2、AOP的基本概念 AOP是从程序运行角度考虑程序的流程,提取业务处理的过程的切面,AOP面向的是程序运行中各个步骤,希望以更好的方式来组合业务处理的各个步骤。 AOP框架并不会与特定的代码耦合,AOP框架能处理程序执行中特定的切入点(PointCut),而不是某个具体类的耦合。 3、AOP框架具有如下的两个特征。 各个步骤之间良好的隔离。 源代码无关。 注:Spring的AOP支持允许将一些通用任务如安全、事务、日志等进行集中式处理。从而提高了更多的复用。 4.如何在Spring中引入AOP功能 <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>${srping.version}</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.7.2</version> </dependency> 5、实现Web层的日志切面 实现AOP的切面主要有以下几个要素: 使用 @Aspect 注解将一个java类定义为切面类。 使用 @Pointcut 定义一个切入点,可以是一个规则表达式,比如下例中某个package下的所有函数,也可以是一个注解等。 根据需要在切入点不同位置的切入内容 使用 @Before 在切入点开始处切入内容 使用 @After 在切入点结尾处切入内容 使用 @AfterReturning 在切入点return内容之后切入内容(可以用来对处理返回值做一些加工处理) 使用 @Around 在切入点前后切入内容,并自己控制何时执行切入点自身的内容 使用 @AfterThrowing 用来处理当切入内容部分抛出异常之后的处理逻辑 package com.lidong.util; import java.util.Arrays; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; import javax.servlet.http.HttpServletRequest; import org.apache.log4j.Logger; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import com.mongodb.BasicDBObject; /** 1. 保存请求数据到mongodb 2. @author lidong 3. @createTime 2016年12月24日 */ @Aspect @Order(1) @Component public class WebLogAspect { private Logger logger = Logger.getLogger("mongodb"); ThreadLocal<Long> startTime = new ThreadLocal<>(); HttpServletRequest request; JoinPoint mJoinPoint; @Pointcut("execution (* com.lidong.*.controller.*.*(..))") public void webLog(){} @Before("webLog()") public void doBefore(JoinPoint joinPoint) throws Throwable { startTime.set(System.currentTimeMillis()); ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); request = attributes.getRequest(); mJoinPoint = joinPoint; } private BasicDBObject getBasicDBObject(HttpServletRequest request, JoinPoint joinPoint) { BasicDBObject r = new BasicDBObject(); r.append("requestURL", request.getRequestURL().toString()); r.append("requestURI", request.getRequestURI()); r.append("queryString", request.getQueryString()); r.append("remoteAddr", request.getRemoteAddr()); r.append("remoteHost", request.getRemoteHost()); r.append("remotePort", request.getRemotePort()); r.append("localAddr", request.getLocalAddr()); r.append("localName", request.getLocalName()); r.append("method", request.getMethod()); r.append("headers", getHeadersInfo(request)); r.append("parameters", request.getParameterMap()); r.append("classMethod", joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName()); r.append("args", Arrays.toString(joinPoint.getArgs())); return r; } /** * 获取请求头部的信息 * @param request * @return */ private Map<String, String> getHeadersInfo(HttpServletRequest request) { Map<String, String> map = new HashMap<>(); Enumeration headerNames = request.getHeaderNames(); while (headerNames.hasMoreElements()) { String key = (String) headerNames.nextElement(); String value = request.getHeader(key); map.put(key, value); } return map; } @AfterReturning(returning = "ret", pointcut = "webLog()") public void doAfterReturning(Object ret) throws Throwable { BasicDBObject logInfo = getBasicDBObject(request, mJoinPoint); // 处理完请求,返回内容 logInfo.append("response",ret.toString()); logInfo.append("spend_time", (System.currentTimeMillis() - startTime.get())); logger.info(logInfo); } } 注意:在WebLogAspect切面中,分别通过doBefore和doAfterReturning两个独立函数实现了切点头部和切点返回后执行的内容,若我们想统计请求的处理时间,就需要在doBefore处记录时间,并在doAfterReturning处通过当前时间与开始处记录的时间计算得到请求处理的消耗时间。 6、实现Web层的日志的日志保存到mongodb数据库中 6.1.通过自定义appender实现 思路:log4j提供的输出器实现自Appender接口,要自定义appender输出到MongoDB,只需要继承AppenderSkeleton类,并实现几个方法即可完成。 引入mongodb的驱动,在pom.xml中引入下面依赖 <dependency> <groupId>org.mongodb</groupId> <artifactId>mongodb-driver</artifactId> <version>3.2.2</version> </dependency> 实现MongoAppender 编写MongoAppender类继承AppenderSkeleton,实现如下: package com.lidong.util; import org.apache.log4j.AppenderSkeleton; import org.apache.log4j.spi.LoggingEvent; import com.mongodb.BasicDBObject; import com.mongodb.MongoClient; import com.mongodb.MongoClientURI; import com.mongodb.client.MongoCollection; import com.mongodb.client.MongoDatabase; /** * 自定义appender实现输出日志MongoDB * @author lidong * */ public class MongoAppender extends AppenderSkeleton { private MongoClient mongoClient; private MongoDatabase mongoDatabase; private MongoCollection<BasicDBObject> logsCollection; /** * mongodb连接的url */ private String connectionUrl; /** * 数据库的名称 */ private String databaseName; /** * 集合的名称 */ private String collectionName; @Override protected void append(LoggingEvent loggingEvent) { if(mongoDatabase == null) { MongoClientURI connectionString = new MongoClientURI(connectionUrl); mongoClient = new MongoClient(connectionString); mongoDatabase = mongoClient.getDatabase(databaseName); logsCollection = mongoDatabase.getCollection(collectionName, BasicDBObject.class); } //将日志插入到集合 logsCollection.insertOne((BasicDBObject) loggingEvent.getMessage()); } @Override public void close() { if(mongoClient != null) { mongoClient.close(); } } @Override public boolean requiresLayout() { return false; } public MongoClient getMongoClient() { return mongoClient; } public void setMongoClient(MongoClient mongoClient) { this.mongoClient = mongoClient; } public MongoDatabase getMongoDatabase() { return mongoDatabase; } public void setMongoDatabase(MongoDatabase mongoDatabase) { this.mongoDatabase = mongoDatabase; } public MongoCollection<BasicDBObject> getLogsCollection() { return logsCollection; } public void setLogsCollection(MongoCollection<BasicDBObject> logsCollection) { this.logsCollection = logsCollection; } public String getConnectionUrl() { return connectionUrl; } public void setConnectionUrl(String connectionUrl) { this.connectionUrl = connectionUrl; } public String getDatabaseName() { return databaseName; } public void setDatabaseName(String databaseName) { this.databaseName = databaseName; } public String getCollectionName() { return collectionName; } public void setCollectionName(String collectionName) { this.collectionName = collectionName; } } 6.3 在log4j.prperties中配置 # mongodb输出 log4j.logger.mongodb=INFO, mongodb log4j.appender.mongodb=com.lidong.util.MongoAppender log4j.appender.mongodb.connectionUrl=mongodb://127.0.0.1:27017 log4j.appender.mongodb.databaseName=logs log4j.appender.mongodb.collectionName=logs_request 6.4在spring-mvc.xml中开启统一处理请求日志 <!-- web统一管理 --> <aop:aspectj-autoproxy proxy-target-class="true"/> <bean class="com.lidong.util.WebLogAspect" /> 6.5在数据库中查看保存的日志 代码地址
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010046908/article/details/53927157 之前写过一篇Android打开本地pdf文件的文章,最后总结的时候说,后面一定要拓展库,让其也能打开网络的的pdf文件。今天终于可以兑现承诺了。frok一份代码https://github.com/JoanZapata/android-pdfview,开始改造一番。 1.基本思路: 打开网络pdf 思路整体还是来源与图片的加载。 android中加载网络图片的框架有很多个。如image-laoder, fresco、glide等,首先都是从内存中找图片,如果内存中没有,接着从本地找,本地没有在从网络下载。 android中加载pdf也是类似,首先从本地找pdf文件,如果本地存在该pdf文件,直接打开,如果本地不存在,将该pdf文件下载到本地在打开。 下载文件用到了retrofit2的库,已经封装到android_pdf中了。 2.依赖android_pdf库方法 2.1 在项目的gradle中增加如下代码: compile 'com.lidong.pdf:android_pdf:1.0.1' 2.2 一句代码就可以加载网络pdf。 pdfView.fileFromLocalStorage(this,this,this,fileUrl,fileName); //设置pdf文件地址 2.3对fileFromLocalStorage(this,this,this,fileUrl,fileName)的解析 /** * 加载pdf文件 * @param onPageChangeListener * @param onLoadCompleteListener * @param onDrawListener * @param fileUrl * @param fileName */ public void fileFromLocalStorage( final OnPageChangeListener onPageChangeListener, final OnLoadCompleteListener onLoadCompleteListener, final OnDrawListener onDrawListener, String fileUrl, final String fileName) OnPageChangeListener onPageChangeListener :翻页回调 OnLoadCompleteListener onLoadCompleteListener:加载完成的回调 OnDrawListener:页面绘制的回调 String fileUrl : 文件的网络地址 String fileName 文件名称 3.使用android_pdf库方法 3.1写一个布局文件 <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/activity_main" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.lidong.pdf.androidpdf.MainActivity"> <com.lidong.pdf.PDFView android:id="@+id/pdfView" android:layout_width="match_parent" android:layout_height="match_parent" /> </RelativeLayout> 3.2在MainActivity中加载 import android.graphics.Canvas; import android.os.Environment; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; import android.widget.Toast; import com.lidong.pdf.PDFView; import com.lidong.pdf.api.ApiManager; import com.lidong.pdf.listener.OnDrawListener; import com.lidong.pdf.listener.OnLoadCompleteListener; import com.lidong.pdf.listener.OnPageChangeListener; import com.lidong.pdf.util.FileUtils; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import okhttp3.ResponseBody; import rx.android.schedulers.AndroidSchedulers; import rx.functions.Action1; import rx.schedulers.Schedulers; public class MainActivity extends AppCompatActivity implements OnPageChangeListener ,OnLoadCompleteListener, OnDrawListener { private PDFView pdfView ; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); pdfView = (PDFView) findViewById( R.id.pdfView ); displayFromFile1("填写所要打开的pdf地址"); } /** * 获取打开网络的pdf文件 * @param fileUrl * @param fileName */ private void displayFromFile1( String fileUrl ,String fileName) { showProgress(); pdfView.fileFromLocalStorage(this,this,this,fileUrl,fileName); //设置pdf文件地址 } /** * 翻页回调 * @param page * @param pageCount */ @Override public void onPageChanged(int page, int pageCount) { Toast.makeText( MainActivity.this , "page= " + page + " pageCount= " + pageCount , Toast.LENGTH_SHORT).show(); } /** * 加载完成回调 * @param nbPages 总共的页数 */ @Override public void loadComplete(int nbPages) { Toast.makeText( MainActivity.this , "加载完成" + nbPages , Toast.LENGTH_SHORT).show(); hideProgress(); } @Override public void onLayerDrawn(Canvas canvas, float pageWidth, float pageHeight, int displayedPage) { // Toast.makeText( MainActivity.this , "pageWidth= " + pageWidth + " // pageHeight= " + pageHeight + " displayedPage=" + displayedPage , Toast.LENGTH_SHORT).show(); } /** * 显示对话框 */ private void showProgress(){ LoadingUIHelper.showDialogForLoading(this,"报告加载中,请等待。。。",false); } /** * 关闭等待框 */ private void hideProgress(){ LoadingUIHelper.hideDialogForLoading(); } 代码地址 效果实现: 代码已经奉上,请大家伙给点建议。一起交流(1561281670)
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010046908/article/details/53884325 1. Redis的简介 Redis是一个开源的,先进的 key-value 存储可用于构建高性能,可扩展的 Web 应用程序的解决方案。 Redis官方网网站是:http://www.redis.io/,如下: Redis 有三个主要使其有别于其它很多竞争对手的特点: Redis是完全在内存中保存数据的数据库,使用磁盘只是为了持久性目的; Redis相比许多键值数据存储系统有相对丰富的数据类型; Redis可以将数据复制到任意数量的从服务器中; 2. Redis优点 异常快速 : Redis是非常快的,每秒可以执行大约110000设置操作,81000个/每秒的读取操作。 支持丰富的数据类型 : Redis支持最大多数开发人员已经知道如列表,集合,可排序集合,哈希等数据类型。这使得在应用中很容易解决的各种问题,因为我们知道哪些问题处理使用哪种数据类型更好解决。 操作都是原子的 : 所有 Redis 的操作都是原子,从而确保当两个客户同时访问 Redis 服务器得到的是更新后的值(最新值)。 MultiUtility工具:Redis是一个多功能实用工具,可以在很多如:缓存,消息传递队列中使用(Redis原生支持发布/订阅),在应用程序中,如:Web应用程序session,网站页面点击数等任何短暂的数据,最新的100条评论等; 3.Redis环境 要在 Ubuntu 上安装 Redis,打开终端,然后输入以下命令: $sudo apt-get update $sudo apt-get install redis-server 这将在您的计算机上安装Redis 启动 Redis $redis-server 查看 redis 是否还在运行 $redis-cli 这将打开一个 Redis 提示符,如下图所示: redis 127.0.0.1:6379> 在上面的提示信息中:127.0.0.1 是本机的IP地址,6379是 Redis 服务器运行的端口。现在输入 PING 命令,如下图所示: redis 127.0.0.1:6379> ping PONG 这说明现在你已经成功地在计算机上安装了 Redis。 在Ubuntu上安装Redis桌面管理器 要在Ubuntu 上安装 Redis桌面管理,可以从 http://redisdesktop.com/download 下载包并安装它。 Redis 桌面管理器会给你用户界面来管理 Redis 键和数据。 4. Redis的主从配置(以window为例) 在window中安装redis服务器,然后将文件复制一份,如下图: 修改文件夹名称分别为主服务器为Redis-master,从服务器为Redis - slave, 时候为修改从服务器redis.windows.conf文件中的端口prot为6380,在6380的redis.windows.conf中指定 是6379的slave,如: #配置6379主服务器配置一个从服务器6380 slaveof 127.0.0.1 6379 这样Redis的一主一从就配置好了。接下来启动两个主从服务器: 4.1. 启动Redis-master: redis-server.exe redis.windows.conf 4.2. 启动Redis - slave: redis-server.exe redis.windows.conf 4.3 连接主Redis-master服务并保存数据: C:\Program Files\Redis-master>redis-cli.exe 127.0.0.1:6379> set test1 text_1 OK 127.0.0.1:6379> get test1 "text_1" 127.0.0.1:6379> 4.4.连接主Redis - slave服务查看数据: C:\Program Files\Redis - slave>redis-cli.exe 127.0.0.1:6379> get test1 "text_1" 127.0.0.1:6379> 4.5 用Redis Desktop Manager查看数据 总结:Redis简单使用就这么多,谢谢的的阅读。
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010046908/article/details/53873673 1. Zookeeper 简介 ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,是Google的Chubby一个开源的实现,是Hadoop和Hbase的重要组件。它是一个为分布式应用提供一致性服务的软件,提供的功能包括:配置维护、域名服务、分布式同步、组服务等。 ZooKeeper的目标就是封装好复杂易出错的关键服务,将简单易用的接口和性能高效、功能稳定的系统提供给用户。 ZooKeeper包含一个简单的原语集, 提供Java和C的接口。 ZooKeeper代码版本中,提供了分布式独享锁、选举、队列的接口,代码在zookeeper-3.4.8\src\recipes。其中分布锁和队列有Java和C两个版本,选举只有Java版本。 2. Zookeeper原理 ZooKeeper是以Fast Paxos算法为基础的,Paxos 算法存在活锁的问题,即当有多个proposer交错提交时,有可能互相排斥导致没有一个proposer能提交成功,而Fast Paxos作了一些优化,通过选举产生一个leader (领导者),只有leader才能提交proposer,具体算法可见Fast Paxos。因此,要想弄懂ZooKeeper首先得对Fast Paxos有所了解。 ZooKeeper的基本运转流程: 1、选举Leader。 2、同步数据。 3、选举Leader过程中算法有很多,但要达到的选举标准是一致的。 4、Leader要具有最高的zxid。 5、集群中大多数的机器得到响应并follow选出的Leader。[3] 3. Zookeeper特点 在Zookeeper中,znode是一个跟Unix文件系统路径相似的节点,可以往这个节点存储或获取数据。如果在创建znode时Flag设置为EPHEMERAL,那么当创建这个znode的节点和Zookeeper失去连接后,这个znode将不再存在在Zookeeper里,Zookeeper使用Watcher察觉事件信息。当客户端接收到事件信息,比如连接超时、节点数据改变、子节点改变,可以调用相应的行为来处理数据。Zookeeper的Wiki页面展示了如何使用Zookeeper来处理事件通知,队列,优先队列,锁,共享锁,可撤销的共享锁,两阶段提交。 那么Zookeeper能做什么事情呢,简单的例子:假设我们有20个搜索引擎的服务器(每个负责总索引中的一部分的搜索任务)和一个总服务器(负责向这20个搜索引擎的服务器发出搜索请求并合并结果集),一个备用的总服务器(负责当总服务器宕机时替换总服务器),一个web的cgi(向总服务器发出搜索请求)。搜索引擎的服务器中的15个服务器提供搜索服务,5个服务器正在生成索引。这20个搜索引擎的服务器经常要让正在提供搜索服务的服务器停止提供服务开始生成索引,或生成索引的服务器已经把索引生成完成可以提供搜索服务了。使用Zookeeper可以保证总服务器自动感知有多少提供搜索引擎的服务器并向这些服务器发出搜索请求,当总服务器宕机时自动启用备用的总服务器。 4. Zookeeper的下载 可以从 https://zookeeper.apache.org/releases.html 下载ZooKeeper,目前最新的稳定版本为 3.4.8 版本,用户可以自行选择一个速度较快的镜像来下载即可。 5. Zookeeper的目录结构 下载并解压ZooKeeper软件压缩包后,可以看到zk包含以下的文件和目录: ZooKeeper软件的文件和目录 bin目录 zk的可执行脚本目录,包括zk服务进程,zk客户端,等脚本。其中,.sh是Linux环境下的脚本,.cmd是Windows环境下的脚本。 lib目录 zk依赖的包。 libexec目录 一些用于操作zk的工具包。 5.单机模式 ZooKeeper的安装包括单机模式安装,以及集群模式安装。 单机模式较简单,是指只部署一个zk进程,客户端直接与该zk进程进行通信。 在开发测试环境下,通过来说没有较多的物理资源,因此我们常使用单机模式。当然在单台物理机上也可以部署集群模式,但这会增加单台物理机的资源消耗。故在开发环境中,我们一般使用单机模式。 5.1 运行配置 在etc/zookeeper/目录下提供了zoo.cfg,打开zoo.cfg,可以看到默认的一些配置。 tickTime 时长单位为毫秒,为zk使用的基本时间度量单位。例如,1 * tickTime是客户端与zk服务端的心跳时间,2 * tickTime是客户端会话的超时时间。 tickTime的默认值为2000毫秒,更低的tickTime值可以更快地发现超时问题,但也会导致更高的网络流量(心跳消息)和更高的CPU使用率(会话的跟踪处理)。 tickTime=2000 clientPort zk服务进程监听的TCP端口,默认情况下,服务端会监听2181端口。 clientPort=2181 dataDir 无默认配置,必须配置,用于配置存储快照文件的目录。如果没有配置dataLogDir,那么事务日志也会存储在此目录。 dataDir=/usr/local/var/run/zookeeper/data dataLogDir=/usr/local/var/run/zookeeper/logs 5.2 启动 在mac环境下,将zookeeper的bin目录配置到环境变量中,直接执行命令 zkServer start 这个命令使得zk服务进程在后台进行。 如果想在前台中运行以便查看服务器进程的输出日志,可以通过以下命令运行: zkServer start-foreground 执行此命令,可以看到大量详细信息的输出,以便允许查看服务器发生了什么。 5.3 连接 如果是连接同一台主机上的zk进程,那么直接运行bin/目录下的zkCli.cmd(Windows环境下)或者zkCli.sh(mac环境下),即可连接上zk。 直接执行zkCli.cmd或者zkCli.sh命令默认以主机号 127.0.0.1,端口号 2181 来连接zk,如果要连接不同机器上的zk,可以使用 -server 参数,例如: zkCli -server 192.168.0.1:2181 6.集群模式 单机模式的zk进程虽然便于开发与测试,但并不适合在生产环境使用。在生产环境下,我们需要使用集群模式来对zk进行部署。 注意 在集群模式下,建议至少部署3个zk进程,或者部署奇数个zk进程。如果只部署2个zk进程,当其中一个zk进程挂掉后,剩下的一个进程并不能构成一个quorum的大多数。因此,部署2个进程甚至比单机模式更不可靠,因为2个进程其中一个不可用的可能性比一个进程不可用的可能性还大。 6. 1 运行配置 在集群模式下,所有的zk进程可以使用相同的配置文件(是指各个zk进程部署在不同的机器上面),例如如下配置: tickTime=2000 dataDir=/home/lidong/zookeeper clientPort=2181 initLimit=5 syncLimit=2 server.1=192.168.0.105:2888:3888 server.2=192.168.0.108:2888:3888 initLimit ZooKeeper集群模式下包含多个zk进程,其中一个进程为leader,余下的进程为follower。 当follower最初与leader建立连接时,它们之间会传输相当多的数据,尤其是follower的数据落后leader很多。initLimit配置follower与leader之间建立连接后进行同步的最长时间。 syncLimit 配置follower和leader之间发送消息,请求和应答的最大时间长度。 tickTime tickTime则是上述两个超时配置的基本单位,例如对于initLimit,其配置值为5,说明其超时时间为 2000ms * 5 = 10秒。 server.id=host:port1:port2 其中id为一个数字,表示zk进程的id,这个id也是dataDir目录下myid文件的内容。 host是该zk进程所在的IP地址,port1表示follower和leader交换消息所使用的端口,port2表示选举leader所使用的端口。 dataDir 其配置的含义跟单机模式下的含义类似,不同的是集群模式下还有一个myid文件。myid文件的内容只有一行,且内容只能为1 - 255之间的数字,这个数字亦即上面介绍server.id中的id,表示zk进程的id。 注意 如果仅为了测试部署集群模式而在同一台机器上部署zk进程,server.id=host:port1:port2配置中的port参数必须不同。但是,为了减少机器宕机的风险,强烈建议在部署集群模式时,将zk进程部署不同的物理机器上面。 6. 2启动 假如我们打算在两台不同的机器 192.168.0.105(windows),192.168.0.108(mac os)上各部署一个zk进程,以构成一个zk集群。 两个zk进程均使用相同的 zoo.cfg 配置: 192.168.0.105(windows) tickTime=2000 dataDir=D:/zookeeper clientPort=2181 initLimit=5 syncLimit=2 server.1=192.168.0.105:2888:3888 server.2=192.168.0.108:2888:3888 192.168.0.100(mac os) tickTime=2000 dataDir=/home/lidong/zookeeper clientPort=2181 initLimit=5 syncLimit=2 server.1=192.168.0.105:2888:3888 server.2=192.168.0.108:2888:3888 在window主机器dataDir目录( D:/zookeeper目录)下,分别生成一个myid文件,其内容分别为1,2。 在Mac os主机器dataDir目录( /home/lidong/zookeeper)下,分别生成一个myid文件,其内容分别为1,2。 然后分别在这两台机器上启动zk进程,这样我们便将zk集群启动了起来。 6. 3连接 可以使用以下命令来连接一个zk集群: zkCli -server 192.168.0.105:2181,192.168.0.108:2181 成功连接后,可以看到如下输出: Connecting to 192.168.0.105:2181,192.168.0.108:2181 Welcome to ZooKeeper! JLine support is enabled WATCHER:: WatchedEvent state:SyncConnected type:None path:null [zk: 192.168.0.105:2181,192.168.0.108:2181(CONNECTED) 0] 从日志输出可以看到,客户端连接的是192.168.0.108:2181进程,客户端已成功连接上zk集群。 注意:连接到那个集群是随机的。
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010046908/article/details/53870144 1 Druid简介 Druid是阿里巴巴开源平台上的一个项目,整个项目由数据库连接池、插件框架和SQL解析器组成。该项目主要是为了扩展JDBC的一些限制,可以让程序员实现一些特殊的需求,比如向密钥服务请求凭证、统计SQL信息、SQL性能收集、SQL注入检查、SQL翻译等,程序员可以通过定制来实现自己需要的功能。 Druid是目前最好的数据库连接池,在功能、性能、扩展性方面,都超过其他数据库连接池,包括DBCP、C3P0、BoneCP、Proxool、JBoss DataSource。Druid已经在阿里巴巴部署了超过600个应用,经过一年多生产环境大规模部署的严苛考验。 2.Druid的功能 1、替换DBCP和C3P0。Druid提供了一个高效、功能强大、可扩展性好的数据库连接池。 2、可以监控数据库访问性能,Druid内置提供了一个功能强大的StatFilter插件,能够详细统计SQL的执行性能,这对于线上分析数据库访问性能有帮助。 3、数据库密码加密。直接把数据库密码写在配置文件中,这是不好的行为,容易导致安全问题。DruidDruiver和DruidDataSource都支持PasswordCallback。 4、SQL执行日志,Druid提供了不同的LogFilter,能够支持Common-Logging、Log4j和JdkLog,你可以按需要选择相应的LogFilter,监控你应用的数据库访问情况。 5、扩展JDBC,如果你要对JDBC层有编程的需求,可以通过Druid提供的Filter机制,很方便编写JDBC层的扩展插件。 3.Druid怎么配置maven <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>${druid-version}</version> </dependency> 4.Druid支持的数据库 理论上说,支持所有有jdbc驱动的数据库。实际测试过的有 mysql 支持,大规模使用 oracle 支持,大规模使用 sqlserver 支持 postgres 支持 db2 支持 h2 支持 derby 支持 sqlite 支持 sybase 支持 5.配置DruidDataSource <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> <!-- 基本属性 url、user、password --> <property name="url" value="${jdbc_url}" /> <property name="username" value="${jdbc_user}" /> <property name="password" value="${jdbc_password}" /> <!-- 配置初始化大小、最小、最大 --> <property name="initialSize" value="1" /> <property name="minIdle" value="1" /> <property name="maxActive" value="20" /> <!-- 配置获取连接等待超时的时间 --> <property name="maxWait" value="60000" /> <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 --> <property name="timeBetweenEvictionRunsMillis" value="60000" /> <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 --> <property name="minEvictableIdleTimeMillis" value="300000" /> <property name="validationQuery" value="SELECT 'x'" /> <property name="testWhileIdle" value="true" /> <property name="testOnBorrow" value="false" /> <property name="testOnReturn" value="false" /> <!-- 打开PSCache,并且指定每个连接上PSCache的大小 --> <property name="poolPreparedStatements" value="true" /> <property name="maxPoolPreparedStatementPerConnectionSize" value="20" /> <!-- 配置监控统计拦截的filters --> <property name="filters" value="stat" /> </bean> 通常来说,只需要修改initialSize、minIdle、maxActive。 如果用Oracle,则把poolPreparedStatements配置为true,mysql可以配置为false。分库分表较多的数据库,建议配置为false。 6.配置_StatFilter <servlet> <servlet-name>DruidStatView</servlet-name> <servlet-class>com.alibaba.druid.support.http.StatViewServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>DruidStatView</servlet-name> <url-pattern>/druid/*</url-pattern> </servlet-mapping> <filter> <filter-name>DruidWebStatFilter</filter-name> <filter-class>com.alibaba.druid.support.http.WebStatFilter</filter-class> <init-param> <param-name>exclusions</param-name> <param-value>*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*</param-value> </init-param> <init-param> <param-name>profileEnable</param-name> <param-value>true</param-value> </init-param> <init-param> <param-name>principalCookieName</param-name> <param-value>USER_COOKIE</param-value> </init-param> <init-param> <param-name>principalSessionName</param-name> <param-value>USER_SESSION</param-value> </init-param> </filter> <filter-mapping> <filter-name>DruidWebStatFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> 7.结果展示 7.1.首页 7.2 SQL监控 代码地址
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010046908/article/details/53868659 1.Dubbo简介 DUBBO是一个分布式服务框架,致力于提供高性能和透明化的RPC远程服务调用方案,是阿里巴巴SOA服务化治理方案的核心框架,每天为2,000+个服务提供3,000,000,000+次访问量支持,并被广泛应用于阿里巴巴集团的各成员站点。 随着互联网的发展,网站应用的规模不断扩大,常规的垂直应用架构已无法应对,分布式服务架构以及流动计算架构势在必行,亟需一个治理系统确保架构有条不紊的演进。 单一应用架构 当网站流量很小时,只需一个应用,将所有功能都部署在一起,以减少部署节点和成本。 此时,用于简化增删改查工作量的 数据访问框架(ORM) 是关键。 垂直应用架构 当访问量逐渐增大,单一应用增加机器带来的加速度越来越小,将应用拆成互不相干的几个应用,以提升效率。 此时,用于加速前端页面开发的 Web框架(MVC) 是关键。 分布式服务架构 当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。 此时,用于提高业务复用及整合的 分布式服务框架(RPC) 是关键。 流动计算架构 当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。 此时,用于提高机器利用率的 资源调度和治理中心(SOA) 是关键。 2.需求 在大规模服务化之前,应用可能只是通过RMI或Hessian等工具,简单的暴露和引用远程服务,通过配置服务的URL地址进行调用,通过F5等硬件进行负载均衡。 (1) 当服务越来越多时,服务URL配置管理变得非常困难,F5硬件负载均衡器的单点压力也越来越大。 此时需要一个服务注册中心,动态的注册和发现服务,使服务的位置透明。 并通过在消费方获取服务提供方地址列表,实现软负载均衡和Failover,降低对F5硬件负载均衡器的依赖,也能减少部分成本。 (2) 当进一步发展,服务间依赖关系变得错踪复杂,甚至分不清哪个应用要在哪个应用之前启动,架构师都不能完整的描述应用的架构关系。 这时,需要自动画出应用间的依赖关系图,以帮助架构师理清理关系。 (3) 接着,服务的调用量越来越大,服务的容量问题就暴露出来,这个服务需要多少机器支撑?什么时候该加机器? 为了解决这些问题,第一步,要将服务现在每天的调用量,响应时间,都统计出来,作为容量规划的参考指标。 其次,要可以动态调整权重,在线上,将某台机器的权重一直加大,并在加大的过程中记录响应时间的变化,直到响应时间到达阀值,记录此时的访问量,再以此访问量乘以机器数反推总容量。 以上是Dubbo最基本的几个需求,更多服务治理问题参见: http://code.alibabatech.com/blog/experience_1402/service-governance-process.html 3.架构 dubbo运行架构如下图示: 节点角色说明: Provider: 暴露服务的服务提供方。 Consumer: 调用远程服务的服务消费方。 Registry: 服务注册与发现的注册中心。 Monitor: 统计服务的调用次调和调用时间的监控中心。 Container: 服务运行容器。 调用关系说明: 服务容器负责启动,加载,运行服务提供者。 服务提供者在启动时,向注册中心注册自己提供的服务。 服务消费者在启动时,向注册中心订阅自己所需的服务。 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。 1.连通性: 注册中心负责服务地址的注册与查找,相当于目录服务,服务提供者和消费者只在启动时与注册中心交互,注册中心不转发请求,压力较小 监控中心负责统计各服务调用次数,调用时间等,统计先在内存汇总后每分钟一次发送到监控中心服务器,并以报表展示 服务提供者向注册中心注册其提供的服务,并汇报调用时间到监控中心,此时间不包含网络开销 服务消费者向注册中心获取服务提供者地址列表,并根据负载算法直接调用提供者,同时汇报调用时间到监控中心,此时间包含网络开销 注册中心,服务提供者,服务消费者三者之间均为长连接,监控中心除外 注册中心通过长连接感知服务提供者的存在,服务提供者宕机,注册中心将立即推送事件通知消费者 注册中心和监控中心全部宕机,不影响已运行的提供者和消费者,消费者在本地缓存了提供者列表 注册中心和监控中心都是可选的,服务消费者可以直连服务提供者 2.健状性: 监控中心宕掉不影响使用,只是丢失部分采样数据 数据库宕掉后,注册中心仍能通过缓存提供服务列表查询,但不能注册新服务 注册中心对等集群,任意一台宕掉后,将自动切换到另一台 注册中心全部宕掉后,服务提供者和服务消费者仍能通过本地缓存通讯 服务提供者无状态,任意一台宕掉后,不影响使用 服务提供者全部宕掉后,服务消费者应用将无法使用,并无限次重连等待服务提供者恢复 3.伸缩性: 注册中心为对等集群,可动态增加机器部署实例,所有客户端将自动发现新的注册中心 服务提供者无状态,可动态增加机器部署实例,注册中心将推送新的服务提供者信息给消费者 4.升级性: 当服务集群规模进一步扩大,带动IT治理结构进一步升级,需要实现动态部署,进行流动计算,现有分布式服务架构不会带来阻力: Deployer: 自动部署服务的本地代理。 Repository: 仓库用于存储服务应用发布包。 Scheduler: 调度中心基于访问压力自动增减服务提供者。 Admin: 统一管理控制台。 4.dubbo的调用方式 异步调用 基于NIO的非阻塞实现并行调用,客户端不需要启动多线程即可完成并行调用多个远程服务,相对多线程开销较小。 本地调用 本地调用,使用了Injvm协议,是一个伪协议,它不开启端口,不发起远程调用,只在JVM内直接关联,但执行Dubbo的Filter链。 5.duubo的用法 本地服务:(Spring配置) local.xml <bean id=“xxxService” class=“com.xxx.XxxServiceImpl” /> <bean id=“xxxAction” class=“com.xxx.XxxAction”> <property name=“xxxService” ref=“xxxService” /> </bean> 远程服务:(Spring配置) 在本地服务的基础上,只需做简单配置,即可完成远程化: 将上面的local.xml配置拆分成两份,将服务定义部分放在服务提供方remote-provider.xml,将服务引用部分放在服务消费方remote-consumer.xml。 并在提供方增加暴露服务配置,在消费方增加引用服务配置。 如下: remote-provider.xml <bean id=“xxxService” class=“com.xxx.XxxServiceImpl” /> <!-- 和本地服务一样实现远程服务 --> <dubbo:service interface=“com.xxx.XxxService” ref=“xxxService” /> <!-- 增加暴露远程服务配置 --> remote-consumer.xml <dubbo:reference id=“xxxService” interface=“com.xxx.XxxService” /> <!-- 增加引用远程服务配置 --> <bean id=“xxxAction” class=“com.xxx.XxxAction”> <!-- 和本地服务一样使用远程服务 --> <property name=“xxxService” ref=“xxxService” /> </bean> 6.dubbo统一管理控制台 今天就对dubbo做简单的说明。后面开始介绍如何整合dubbo2.5-spring4-mybastis3.2-springmvc4-mongodb3.4-redis3.2整合。 代码地址
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010046908/article/details/53170130 以前写过一篇关于微信小程序上拉加载,上拉刷新的文章,今天写的是关于小程序网络请求的封装。 在这里首先声明一个小程序文档的bug,导致大伙们在请求的时候,服务器收到不到参数的问题 示例代码: wx.request({ url: 'test.php', //仅为示例,并非真实的接口地址 data: { x: '' , y: '' }, header: { 'Content-Type': 'application/json' }, success: function(res) { console.log(res.data) } }) 其中header 中的Content-Type,应该用小写content-type才能让服务器收到参数。让我折腾的好久,改了服务器仍然不行,原来是这个问题。参数在request payload中,服务器不能收到,使用如下转换之后 function json2Form(json) { var str = []; for(var p in json){ str.push(encodeURIComponent(p) + "=" + encodeURIComponent(json[p])); } return str.join("&"); } 最终还是认为是content-type的问题。最后改小写就ok,觉得微信这么牛逼的团队,犯了一个很低级 的错误,把我开发者折腾的爬了。不说,上代码吧。 1 、Http请求的类 import util from 'util.js'; /** * url 请求地址 * success 成功的回调 * fail 失败的回调 */ function _get( url, success, fail ) { console.log( "------start---_get----" ); wx.request( { url: url, header: { // 'Content-Type': 'application/json' }, success: function( res ) { success( res ); }, fail: function( res ) { fail( res ); } }); console.log( "----end-----_get----" ); } /** * url 请求地址 * success 成功的回调 * fail 失败的回调 */ function _post_from(url,data, success, fail ) { console.log( "----_post--start-------" ); wx.request( { url: url, header: { 'content-type': 'application/x-www-form-urlencoded', }, method:'POST', data:{data: data}, success: function( res ) { success( res ); }, fail: function( res ) { fail( res ); } }); console.log( "----end-----_get----" ); } /** * url 请求地址 * success 成功的回调 * fail 失败的回调 */ function _post_json(url,data, success, fail ) { console.log( "----_post--start-------" ); wx.request( { url: url, header: { 'content-type': 'application/json', }, method:'POST', data:data, success: function( res ) { success( res ); }, fail: function( res ) { fail( res ); } }); console.log( "----end----_post-----" ); } module.exports = { _get: _get, _post:_post, _post_json:_post_json } 2、测试用例 2.1 get请求 //GET方式 let map = new Map(); map.set( 'receiveId', '0010000022464' ); let d = json_util.mapToJson( util.tokenAndKo( map ) ); console.log( d ); var url1 = api.getBaseUrl() + 'SearchTaskByReceiveId?data='+d; network_util._get( url1,d, function( res ) { console.log( res ); that.setData({ taskEntrys:res.data.taskEntrys }); }, function( res ) { console.log( res ); }); 2.2 POST请求 //Post方式 let map = new Map(); map.set( 'receiveId', '0010000022464' ); let d = json_util.mapToJson( util.tokenAndKo( map ) ); console.log( d ); var url1 = api.getBaseUrl() + 'SearchTaskByReceiveId'; network_util._post( url1,d, function( res ) { console.log( res ); that.setData({ taskEntrys:res.data.taskEntrys }); }, function( res ) { console.log( res ); }); 效果
此文章来源于APP架构师这个公众号 今天的主题是Android开发中的内存泄漏,之所以说这个是因为前几天做了项目中的内存泄漏排查与解决,在这里总结一下,被提供一种快速定位解决Android内存泄漏的方案,希望大家看完有所收获。 1 奠基之石——内存泄漏概述 在介绍内存泄漏之前很有必要提及一下Android系统的垃圾回收机制。Java GC(Garbage Collection,垃圾收集,垃圾回收)机制,是Java与C++/C的主要区别之一,作为Java开发者,一般不需要专门编写内存回收和垃圾清理代码,对内存泄露和溢出的问题,也不需要像C程序员那样战战兢兢。这是因为在Java虚拟机中,存在自动内存管理和垃圾清扫机制。概括地说,该机制对虚拟机中的内存进行标记,并确定哪些内存需要回收,根据一定的回收策略,自动的回收内存,永不停息(Nerver Stop)的保证虚拟机中的内存空间,防止出现内存泄露和溢出问题。Android系统的垃圾回收是基于可达性分析算法(根搜索算法)的。如下图所示,从GC Roots(每种具体实现对GC Roots有不同的定义)作为起点,向下搜索它们引用的对象,可以生成一棵引用树,树的节点视为可达对象,反之视为不可达。 不可达的对象(如下图中的object5,6,7)会在系统GC的时候被回收,从而释放内存空间。 如果所有的对象都可以被顺利回收就没有本文的诞生了,举个简单的例子,我们在开发中经常使用单例模式,单例的静态特性导致其生命周期同应用一样长。有时创建单例时如果我们需要Context对象,如果传入的是Application的Context那么不会有问题。如果传入的是Activity的Context对象,那么当Activity生命周期结束时,该Activity的引用依然被单例持有,所以不会被回收,而单例的生命周期又是跟应用一样长,这个情况就叫做内存泄露(Memory Leak)。它指的是当你不再需要某个实例后,但是这个对象却仍然被引用,防止被垃圾回收(Prevent from being bargage collected)。 public class Util { private Context mContext; private static Util sInstance; private Util(Context context) { this.mContext = context; } public static Util getInstance(Context context) { if (sInstance == null) { sInstance = new Util(context); } return sInstance; } } 本杰明 富兰克林曾说:A small leak will sink a great ship.意即:小漏不补沉大船。基于Android系统的设备一般来说内存就不大,特别是早期的Android设备,内存泄漏是很致命的,内存泄漏积攒到一定程度会引发内存溢出(OOM),如果处理不当直接导致程序崩溃退出。 2.了然于胸——常见的内存泄漏 一般来说在开发中我们经常会犯下下面几个错误,导致内存泄漏。这几个都是前人踩坑总结出来的,非常有参考价值,至少我在排查解决内存泄漏的时候是这样的。 一、单例造成的内存泄漏 Android的单例模式非常受开发者的喜爱,不过使用的不恰当的话也会造成内存泄漏。因为单例的静态特性使得单例的生命周期和应用的生命周期一样长,这就说明了如果一个对象已经不需要使用了,而单例对象还持有该对象的引用,那么这个对象将不能被正常回收,这就导致了内存泄漏。例子见上面那段代码。 二、非静态内部类创建静态实例造成的内存泄漏 有的时候我们可能会在启动频繁的Activity中,为了避免重复创建相同的数据资源,在Activity内部创建了一个非静态内部类的单例,每次启动Activity时都会使用该单例的数据,这样虽然避免了资源的重复创建,不过这种写法却会造成内存泄漏,因为非静态内部类默认会持有外部类的引用,而又使用了该非静态内部类创建了一个静态的实例,该实例的生命周期和应用的一样长,这就导致了该静态实例一直会持有该Activity的引用,导致Activity的内存资源不能正常回收。例子如下 public class MainActivity extends AppCompatActivity { private static TestResource mResource = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); if (mManager == null) { mManager = new TestResource(); } //... } class TestResource { //... } } 三、Handler造成的内存泄漏 Handler的使用造成的内存泄漏问题应该说最为常见了,平时在处理网络任务或者封装一些请求回调等api都应该会借助Handler来处理,我们经常在Activity里面这样定义一个私有的Handler对象并初始化,这种创建Handler的方式会造成内存泄漏,由于mHandler是Handler的非静态匿名内部类的实例,所以它持有外部类Activity的引用,我们知道消息队列是在一个Looper线程中不断轮询处理消息,那么当这个Activity退出时消息队列中还有未处理的消息或者正在处理消息,而消息队列中的Message持有mHandler实例的引用,mHandler又持有Activity的引用,所以导致该Activity的内存资源无法及时回收,引发内存泄漏。 private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { //... } }; 四、资源未关闭造成的内存泄漏 对于使用了BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,造成内存泄漏。 3 神兵利器——检测内存泄漏的常见工具 见到这个标题有经验的开发者可能要吐槽我是标题党了,特别是从Eclipse时代走过来的开发者,以为我一要开始贴那张像**一样的MAT内存模型图或者AndroidStudio中Monitors下的实时内存占用图,又要开始分析那一条条剪不断理还乱的内存引用链,然后费尽九牛二虎之力去查找项目中无数的内存泄漏中的一个。但是,我要告诉你,你错了。其实,以前我看到内存泄漏分析文章的时候也是这样的想法,看着恐怖的MAT内存模型图,觉得内存泄漏的排查和解决简直是Android开发中登峰造极的技能。直到我遇到了她——LeakCanary,我才直到原来内存泄漏的排查和解决可以那么的优雅。LeakCanary是Square开源了一个内存泄露自动探测神器 。这是项目的github仓库地址:https://github.com/square/leakcanary 。使用非常简单,在build.gradle中引入包依赖: debugCompile 'com.squareup.leakcanary:leakcanary- android:1.5' releaseCompile 'com.squareup.leakcanary:leakcanary- android-no-op:1.5' testCompile 'com.squareup.leakcanary:leakcanary- android-no-op:1.5' 在Application中的onCreate方法中增加初始化代码: if (LeakCanary.isInAnalyzerProcess(this)) { // This process is dedicated to LeakCanary for // heap analysis. // You should not init your app in this process. return; } LeakCanary.install(this); 集成后什么都不用做,按照正常测试,当有内存泄漏发生后,应用会通过系统通知栏发出通知,点击通知就可以进入查看内存泄漏的具体信息。在这里举个实践中的例子。把LeakCanary集成到项目中后,等App启动后一会,系统通知到了,点击通知,跳转到泄漏的详情页面进行查看: 很明显,WebSiteQueryActivity泄露了。首先,static 的MaskHeadView.fLayout变量引用了FrameLayout.mContext对象,这个对象的引用就是指向了WebSiteQueryActivity的实例,导致了它的泄漏,在第二节中我们说过static对象是内部的static对象是比较容易造成内存泄漏的,检查代码发现,MaskHeadView直接在WebSiteQueryActivity的xml文件中使用了,因此持有WebSiteQueryActivity的实例,因为fLayout对象是静态的,因此它的生命周期与Application同样长,因此WebSiteQueryActivity退出后,它的实例引用依然被fLayout持有,导致它无法被回收从而内存泄露了。仔细检查代码,发现fLayout并没有被外部使用到,应该是之前的开发者手抖加了个static字段上去或者是现在不用了,但是没有去掉,在这里我直接去掉了这个修饰符,在此build代码,这个内存泄漏的现象消失了。 //去掉static修饰符,避免static对象引起的内存泄漏 private static FrameLayout fLayout; public MaskHeadView(Context context, AttributeSet attrs) { super(context, attrs); this.context=context; initView(context); } private void initView(Context context2) { view = LayoutInflater.from(context).inflate(R.layout. mask_head_view, this); fLayout=(FrameLayout) view.findViewById(R.id. mask_container); } 这只是个极简单的例子,但方法是一样的。顺便提一句,其实无论是MAT工具的内存分析,还是AndroidStudio中自带的分析工具亦或是LeakCanary,原理都是一样的,都是dump java heap出来进行分析,找到泄漏的问题,只是LeakCanary帮我们把分析的工作做了。 曾几何时,你以为内存泄漏分析都是这样的 但是现在你会发现其实也可以是酱紫的:
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010046908/article/details/52873529 大家可以能会修改Toolbar中menu的文字颜色,直接使用style是没有用的,使用theme就可以让你完成想要的效果 <!-- ToolBar --> <style name="ToolBarStyle" parent="@style/ThemeOverlay.AppCompat.ActionBar"> <item name="android:textColorPrimary">@android:color/white</item> <item name="android:textColorSecondary">@android:color/white</item> <item name="android:actionMenuTextColor">@android:color/white</item> <item name="actionMenuTextColor">@android:color/white</item> <item name="android:textColor">@color/white</item> <item name="android:textSize">18sp</item> <!-- 修改字体大小--> </style> app:theme=”@style/ToolBarStyle” <android.support.v7.widget.Toolbar android:id="@+id/id_tool_bar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:background="?attr/colorPrimary" app:navigationIcon="?attr/homeAsUpIndicator" app:theme="@style/ToolBarStyle">
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010046908/article/details/52742357 1.Node.js 搭建http服务器 1.1创建server.js var http = require('http'); var querystring = require('querystring'); http.createServer(function (request, response) { console.log('receive request'); response.writeHead(200, { 'Content-Type': 'text-plain' }); response.end('Hello World\n'); }).listen(8124); console.log("node server start ok port "+8124); 1.2执行 server.js node server.js 1.3服务器启动 如图: 通过浏览器地址请求:http://localhost:8124 2.发送GET请求 2.1发送get请求通过聚合服务器获取微信新闻数据 var http = require('http'); var querystring = require('querystring'); http.get('http://v.juhe.cn/weixin/query?key=f16af393a63364b729fd81ed9fdd4b7d&pno=1&ps=10', function (response) { var body = []; console.log(response.statusCode); console.log(response.headers); console.log(response); response.on('data', function (chunk) { body.push(chunk); }); response.on('end', function () { body = Buffer.concat(body); console.log(body.toString()); }); }); 2.2 执行 get_demo.js node get_demo.js 2.3获取的结果打印 3.发送POST请求 3.1发送post请求通过聚合服务器获取微信闻数据 var http = require('http'); var querystring = require('querystring'); var postData = querystring.stringify({ 'key' : 'f16af393a63364b729fd81ed9fdd4b7d', 'pno':'1', 'ps':10 }); var options = { hostname: 'v.juhe.cn', path: '/weixin/query', method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Content-Length': Buffer.byteLength(postData) } }; var req = http.request(options, (res) => { console.log(`STATUS: ${res.statusCode}`); console.log(`HEADERS: ${JSON.stringify(res.headers)}`); res.setEncoding('utf8'); res.on('data', (chunk) => { console.log(`BODY: ${chunk}`); }); res.on('end', () => { console.log('No more data in response.') }) }); req.on('error', (e) => { console.log(`problem with request: ${e.message}`); }); // write data to request body req.write(postData); req.end(); 3.2 执行 post_demo.js node post_demo.js 3.3获取的结果打印
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010046908/article/details/52739543 1.初始化数据 启动MongoDB服务,在test数据库中插入一条实例数据: > use part_0; switched to db part_0 > db.user.insert({"username":"lidong"}); WriteResult({ "nInserted" : 1 }) > db.user.insert({"username":"lizirui","sex":"1"}); WriteResult({ "nInserted" : 1 }) > db.user.find(); { "_id" : ObjectId("57f4898418cde5e4b9fe7a92"), "username" : "lidong" } { "_id" : ObjectId("57f4899918cde5e4b9fe7a93"), "username" : "lizirui", "sex" : "1" } > db.user.insert({"username":"liziqi","sex":"1"}); WriteResult({ "nInserted" : 1 }) > db.user.insert({"username":"lizihan","sex":"1"}); WriteResult({ "nInserted" : 1 }) > db.user.find(); { "_id" : ObjectId("57f4898418cde5e4b9fe7a92"), "username" : "lidong" } { "_id" : ObjectId("57f4899918cde5e4b9fe7a93"), "username" : "lizirui", "sex" : "1" } 2.在Node.js中引入MongoDB模块 npm install mongodb 3.编写test.js测试连接 var mongo = require('mongodb'); var host = "localhost"; var port = 27017; //创建MongoDB数据库所在服务器的Server对象 var server = new mongo.Server(host, port, {auto_reconnect:true}); //创建MongoDB数据库 var db = new mongo.Db('part_0', server, {saft:true}); //数据库连接操作 db.open(function(err, db){ if(err) { console.log('连接数据库发生错误'); throw err; } else{ console.log("成功建立数据库连接"); db.collection('user',{safe:true}, function(err, collection){ if(err){ console.log(err); }else{ console.log('-----------'); collection.find(function(error,cursor){ cursor.each(function(error,doc){ if(doc){ console.log("name:"+doc.username+" sex:"+doc.sex); } }); }); } }); db.close(); } }); db.on('close',function(err,db){ if (err) {throw err;} else{ console.log("成功关闭数据库"); } }); 4.运行结果
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010046908/article/details/52733305 微信小程序可谓是9月21号之后最火的一个名词了,一经出现真是轰炸了整个开发人员,当然很多App开发人员有了一个担心,微信小程序的到来会不会让移动端App颠覆,让移动端的程序员失业,身为一个Android开发者我是不相信的,即使有,那也是需要个一两年的过度和打磨才能实现的吧。 不管微信小程序是否能颠覆当今的移动开发格局,我们都要积极向上的心态去接收,去学习。不排斥新技术,所以,心动不如行动,赶紧先搭建一个微信小程序开发工具。那么接下来就让我们来开始学习列表的上拉加载和下拉刷新的实现吧(通过聚合数据平台获取微信新闻)。 1.介绍几个组件 1.1 scroll-view 组件 注意:使用竖向滚动时,需要给一个固定高度,通过 WXSS 设置 height。 1.2 image组件 注意:mode有12种模式,其中3种是缩放模式,9种是裁剪模式。 1.3 Icon组件 iconType: [ ‘success’, ‘info’, ‘warn’, ‘waiting’, ‘safe_success’, ‘safe_warn’, ‘success_circle’, ‘success_no_circle’, ‘waiting_circle’, ‘circle’, ‘download’, ‘info_circle’, ‘cancel’, ‘search’, ‘clear’ ] 2.列表的上拉加载和下拉刷新的实现 2.1先来张效果图 !这里写图片描述 2.2逻辑很简单,直接上代码 2.2.1 detail.wxml 布局文件 <loading hidden="{{hidden}}" bindchange="loadingChange"> 加载中... </loading> <scroll-view scroll-y="true" style="height: 100%;" bindscrolltolower="loadMore" bindscrolltoupper="refesh"> <view wx:if="{{hasRefesh}}" style="display: flex;flex-direction: row;align-items: center;align-self: center;justify-content: center;"> <icon type="waiting" size="45"/><text>刷新中...</text></view> <view wx:else style="display:none" ><text></text></view> <view class="lll" wx:for="{{list}}" wx:for-item="item" bindtap="bindViewTap" data-title="{{item.title}}" > <image style=" width: 50px;height: 50px;margin: 20rpx;" src="{{item.firstImg}}" ></image> <view class="eee" > <view style="margin:5px;font-size:8px"> 标题:{{item.title}}</view> <view style="margin:5px;color:red;font-size:6px"> 来源:{{item.source}}</view> </view> </view> <view class="tips1"> <view wx:if="{{hasMore}}" style="display: flex;flex-direction: row;align-items: center;align-self: center;justify-content: center;"> <icon type="waiting" size="45"/><text>玩命的加载中...</text></view> <view wx:else><text>没有更多内容了</text></view> </view> </scroll-view> 2.2.1 detail.js逻辑代码文件 var network_util = require('../../utils/network_util.js'); var json_util = require('../../utils/json_util.js'); Page({ data:{ // text:"这是一个页面" list:[], dd:'', hidden:false, page: 1, size: 20, hasMore:true, hasRefesh:false }, onLoad:function(options){ var that = this; var url = 'http://v.juhe.cn/weixin/query?key=f16af393a63364b729fd81ed9fdd4b7d&pno=1&ps=10'; network_util._get(url, function(res){ that.setData({ list:res.data.result.list, hidden: true, }); },function(res){ console.log(res); }); }, onReady:function(){ // 页面渲染完成 }, onShow:function(){ // 页面显示 }, onHide:function(){ // 页面隐藏 }, onUnload:function(){ // 页面关闭 }, //点击事件处理 bindViewTap: function(e) { console.log(e.currentTarget.dataset.title); }, //加载更多 loadMore: function(e) { var that = this; that.setData({ hasRefesh:true,}); if (!this.data.hasMore) return var url = 'http://v.juhe.cn/weixin/query?key=f16af393a63364b729fd81ed9fdd4b7d&pno='+(++that.data.page)+'&ps=10'; network_util._get(url, function(res){ that.setData({ list: that.data.list.concat(res.data.result.list), hidden: true, hasRefesh:false, }); },function(res){ console.log(res); }) }, //刷新处理 refesh: function(e) { var that = this; that.setData({ hasRefesh:true, }); var url = 'http://v.juhe.cn/weixin/query?key=f16af393a63364b729fd81ed9fdd4b7d&pno=1&ps=10'; network_util._get(url, function(res){ that.setData({ list:res.data.result.list, hidden: true, page:1, hasRefesh:false, }); },function(res){ console.log(res); }) }, }) 最后的效果: 关于新闻的详情实现,后面在实现,由于还不知道怎么加载h5页面。谢谢你学习,有问题直接QQ(1561281670)交流。 代码地址:https://github.com/lidong1665/WeiXinProject
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010046908/article/details/52453794 1、问题提出 React-Native中navigator.pop()后如何更新前一个页面,这是一个最为常见得问题。 2、问题的描述 比如说,我们开发应用的时候,上传头像是一个最为常见的功能,我点击选择打开图库的按钮之后,push到图库的页面,我们在上传成功后,需要pop回到当前页面,并把图片路径传到当前页面。 3、React-Native中的解决办法 这个问题对于一个有Android和ios开发经验的程序员首先想到的就是回调函数或者广播机制等。其实在React-Native中同样也可用回调函数来解决这个问题。本来想以android来举例实现,最后还是算了直接上React-Native吧。 在A页面中实现一个声明一个函数refresView() 在A页面push参数中增加一个回掉函数callBack(msg) 在B页面pop时候调用callBack将值传回,更新界面 4.代码的实现 4.A页面的实现 /** * Sample React Native App * https://github.com/facebook/react-native * @flow */ import React, { Component } from 'react'; import { AppRegistry, StyleSheet, Text, Navigator, ToastAndroid, View } from 'react-native'; import Button from './Button'; import Test from './test'; var _navigator; var d; class Hello2 extends Component { constructor(props){ super(props); d = this; this.state = {city:''} // this.refeshView = this.refeshView.bind(this); } configureScene(route){ return Navigator.SceneConfigs.FadeAndroid; } refeshView(msg){ console.log('刷新'); d.setState({'city':msg}); console.log('end'); } renderScene(router, navigator){ console.log(d); _navigator = navigator; if(router.id === 'main'){ return <View style={styles.container}> <Button onPress={() => { console.log('start'); _navigator.push({title:'MainPage',id:'page',callBack:(msg)=>{ console.log(d); d.refeshView(msg); console.log('end');}}); }} text={'打开'} style={styles.instructions} disabled = {false}/> <Text style={styles.welcome}> {d.state.city} </Text> <Text style={styles.instructions}> </Text> <Text style={styles.instructions}> Press Cmd+R to reload,{'\n'} Cmd+D or shake for dev menu </Text> </View> }else if(router.id === 'page'){ return ( <Test navigator={navigator} router={router}/> ); } } render() { return ( <Navigator initialRoute={{ title: 'Main', id:'main'}} configureScene={this.configureScene} renderScene={this.renderScene} /> ); } } const styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#F5FCFF', }, welcome: { fontSize: 20, textAlign: 'center', margin: 10, }, instructions: { textAlign: 'center', color: '#333333', marginBottom: 5, }, }); AppRegistry.registerComponent('Hello2', () => Hello2); 4.2、B页面的实现 import React, { Component } from 'react'; import { AppRegistry, StyleSheet, Text, View } from 'react-native'; var _navigator; import Button from './Button'; class Test extends Component { render() { return ( <View style={{flex:1}}> <Button onPress={() => { console.log(this.props); this.props.router.callBack('I am a Student'); this.props.navigator.pop(); }} text={'返回'} style={styles.instructions} disabled = {false}/> </View> ); } } const styles = StyleSheet.create({ instructions: { textAlign: 'center', color: '#126798', marginTop: 50, } }); module.exports = Test; 代码非常的简单,谢谢大家学习。 5、效果展示
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010046908/article/details/52368551 Android应用开发中三种常见的图片压缩方法,分别是:质量压缩法、比例压缩法(根据路径获取图片并压缩)和比例压缩法(根据Bitmap图片压缩)。 一、质量压缩法 private Bitmap compressImage(Bitmap image) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); image.compress(Bitmap.CompressFormat.JPEG, 100, baos);//质量压缩方法,这里100表示不压缩,把压缩后的数据存放到baos中 int options = 100; while ( baos.toByteArray().length / 1024>100) { //循环判断如果压缩后图片是否大于100kb,大于继续压缩 baos.reset();//重置baos即清空baos image.compress(Bitmap.CompressFormat.JPEG, options, baos);//这里压缩options%,把压缩后的数据存放到baos中 options -= 10;//每次都减少10 } ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray());//把压缩后的数据baos存放到ByteArrayInputStream中 Bitmap bitmap = BitmapFactory.decodeStream(isBm, null, null);//把ByteArrayInputStream数据生成图片 return bitmap; } 二、图片按比例大小压缩方法(根据路径获取图片并压缩) private Bitmap getimage(String srcPath) { BitmapFactory.Options newOpts = new BitmapFactory.Options(); //开始读入图片,此时把options.inJustDecodeBounds 设回true了 newOpts.inJustDecodeBounds = true; Bitmap bitmap = BitmapFactory.decodeFile(srcPath,newOpts);//此时返回bm为空 newOpts.inJustDecodeBounds = false; int w = newOpts.outWidth; int h = newOpts.outHeight; //现在主流手机比较多是800*480分辨率,所以高和宽我们设置为 float hh = 800f;//这里设置高度为800f float ww = 480f;//这里设置宽度为480f //缩放比。由于是固定比例缩放,只用高或者宽其中一个数据进行计算即可 int be = 1;//be=1表示不缩放 if (w > h && w > ww) {//如果宽度大的话根据宽度固定大小缩放 be = (int) (newOpts.outWidth / ww); } else if (w < h && h > hh) {//如果高度高的话根据宽度固定大小缩放 be = (int) (newOpts.outHeight / hh); } if (be <= 0) be = 1; newOpts.inSampleSize = be;//设置缩放比例 //重新读入图片,注意此时已经把options.inJustDecodeBounds 设回false了 bitmap = BitmapFactory.decodeFile(srcPath, newOpts); return compressImage(bitmap);//压缩好比例大小后再进行质量压缩 } 三、图片按比例大小压缩方法(根据Bitmap图片压缩) private Bitmap comp(Bitmap image) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); image.compress(Bitmap.CompressFormat.JPEG, 100, baos); if( baos.toByteArray().length / 1024>1024) {//判断如果图片大于1M,进行压缩避免在生成图片(BitmapFactory.decodeStream)时溢出 baos.reset();//重置baos即清空baos image.compress(Bitmap.CompressFormat.JPEG, 50, baos);//这里压缩50%,把压缩后的数据存放到baos中 } ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray()); BitmapFactory.Options newOpts = new BitmapFactory.Options(); //开始读入图片,此时把options.inJustDecodeBounds 设回true了 newOpts.inJustDecodeBounds = true; Bitmap bitmap = BitmapFactory.decodeStream(isBm, null, newOpts); newOpts.inJustDecodeBounds = false; int w = newOpts.outWidth; int h = newOpts.outHeight; //现在主流手机比较多是800*480分辨率,所以高和宽我们设置为 float hh = 800f;//这里设置高度为800f float ww = 480f;//这里设置宽度为480f //缩放比。由于是固定比例缩放,只用高或者宽其中一个数据进行计算即可 int be = 1;//be=1表示不缩放 if (w > h && w > ww) {//如果宽度大的话根据宽度固定大小缩放 be = (int) (newOpts.outWidth / ww); } else if (w < h && h > hh) {//如果高度高的话根据宽度固定大小缩放 be = (int) (newOpts.outHeight / hh); } if (be <= 0) be = 1; newOpts.inSampleSize = be;//设置缩放比例 //重新读入图片,注意此时已经把options.inJustDecodeBounds 设回false了 isBm = new ByteArrayInputStream(baos.toByteArray()); bitmap = BitmapFactory.decodeStream(isBm, null, newOpts); return compressImage(bitmap);//压缩好比例大小后再进行质量压缩 }
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010046908/article/details/51820378 前面总结了几篇关于Swift的使用,今天要讲的是关于使用Swift开发IOS中蓝牙4.0的开发流程,我以前只是会搞android的蓝牙开发,最近开始了Swift的学习,作为一个swift爱好者,想把蓝牙4.0的这个装逼神器在swift中使用一下。 使用Swift开发IOS中蓝牙4.0的开发流程有如下的几个步骤: 建立桥接文件 案例的实现 1. 建立桥接文件 1.1在用Swift使用OC中得类文件的时候,需要进行桥接,首先建一个.h的头文件。 注意:桥接文件的命名规则:项目名-Bridging-Header.Swift // // Swfit-BLE-Bridging-Header.h // Swift-BLE // // Created by lidong on 16/7/3. // Copyright © 2016年 李东. All rights reserved. // #import <CoreBluetooth/CoreBluetooth.h> 1.2 在Build-settings -> Swift Complier - Code Generaton —>Objective C Briding Herder中添加自己的桥接文件。 如下图: 2.案例的实现 首先,CoreBluetooth库文件为我们提供两个类CBCentralManagerDelegate,CBPeripheralDelegate ,是蓝牙操作的核心类。 CBCentralManagerDelegate 中心管理器的代理类 CBPeripheralDelegate 外围设备的代理类 2.1创建CBCentralManager,设置代理 var myCentralManager:CBCentralManager! myCentralManager = CBCentralManager() myCentralManager.delegate = self 2.2启动扫描发现设备 print("扫描设备。。。。 ") myCentralManager.scanForPeripheralsWithServices(nil, options: nil) //蓝牙的状态 func centralManager(central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: NSError?) { switch (central.state) { case CBCentralManagerState.PoweredOn: print("蓝牙已打开, 请扫描外设!"); break; case CBCentralManagerState.PoweredOff: print("蓝牙关闭,请先打开蓝牙"); default: break; } } //发现设备 func centralManager(central: CBCentralManager, didDiscoverPeripheral peripheral: CBPeripheral, advertisementData: [String : AnyObject], RSSI: NSNumber) { print("--didDiscoverPeripheral-") if peripheral.name == DEVICENAME{ self.myPeripheral = peripheral; self.myCentralManager = central; central.connectPeripheral(self.myPeripheral, options: nil) print(self.myPeripheral); } } 2.3 发现设备后,连接设备,连接成功,关闭中心啊管理者的扫描,发现设备的服务,设置外围设备的代理 //设备已经接成功 func centralManager(central: CBCentralManager, didConnectPeripheral peripheral: CBPeripheral) { print("---------didConnectPeripheral-") print(central) print(peripheral) //关闭扫描 self.myCentralManager.stopScan() self.myPeripheral.delegate = self self.myPeripheral.discoverServices(nil) print("扫描服务..."); } 2.4 根据服务发现特征 /** 发现服务调用次方法 - parameter peripheral: <#peripheral description#> - parameter error: <#error description#> */ func peripheral(peripheral: CBPeripheral, didDiscoverServices error: NSError?) { print("---发现服务调用次方法-") for s in peripheral.services!{ peripheral.discoverCharacteristics(nil, forService: s) print(s.UUID.UUIDString) } } /** 根据服务找特征 - parameter peripheral: <#peripheral description#> - parameter service: <#service description#> - parameter error: <#error description#> */ func peripheral(peripheral: CBPeripheral, didDiscoverCharacteristicsForService service: CBService, error: NSError?) { print("----发现特征------") for c in service.characteristics! { if c.UUID.UUIDString == "2AF0"{ print(c.UUID.UUIDString) peripheral.setNotifyValue(true, forCharacteristic: c) } if c.UUID.UUIDString == "2AF1"{ print(c.UUID.UUIDString) self.writeCharacteristic = c } } } 2.5向设备发送指令,获取数据 /** 写入后的回掉方法 - parameter peripheral: <#peripheral description#> - parameter characteristic: <#characteristic description#> - parameter error: <#error description#> */ func peripheral(peripheral: CBPeripheral, didWriteValueForCharacteristic characteristic: CBCharacteristic, error: NSError?) { print("didWriteValueForCharacteristic") } /** <#设置特征为正在监听,读取数据#> - parameter peripheral: <#peripheral description#> - parameter characteristic: <#characteristic description#> - parameter error: <#error description#> */ func peripheral(peripheral: CBPeripheral, didUpdateNotificationStateForCharacteristic characteristic: CBCharacteristic, error: NSError?) { print("-----didUpdateNotificationStateForCharacteristic-----") if (error != nil) { print(error?.code); } //Notification has started if(characteristic.isNotifying){ peripheral.readValueForCharacteristic(characteristic); print(characteristic.UUID.UUIDString); } } /** 获取外设的数据 - parameter peripheral: <#peripheral description#> - parameter characteristic: <#characteristic description#> - parameter error: <#error description#> */ func peripheral(peripheral: CBPeripheral, didUpdateValueForCharacteristic characteristic: CBCharacteristic, error: NSError?) { print("----didUpdateValueForCharacteristic---") if characteristic.UUID.UUIDString == "2AF0" { let data:NSData = characteristic.value! print(data) let d = Array(UnsafeBufferPointer(start: UnsafePointer<UInt8>(data.bytes), count: data.length)) print(d) let s:String = HexUtil.encodeToString(d) if s != "00" { result += s print(result ) print(result.characters.count ) } if result.characters.count == 38 { lable.text = result } } } 2.6 发送指令的方法 /** 发送指令到设备 */ func writeToPeripheral(bytes:[UInt8]) { if writeCharacteristic != nil { let data1:NSData = dataWithHexstring(bytes) self.myPeripheral.writeValue(data1, forCharacteristic: writeCharacteristic, type: CBCharacteristicWriteType.WithResponse) } else{ } } /** 将[UInt8]数组转换为NSData - parameter bytes: <#bytes description#> - returns: <#return value description#> */ func dataWithHexstring(bytes:[UInt8]) -> NSData { let data = NSData(bytes: bytes, length: bytes.count) return data } 总结:使用Swift开发IOS中蓝牙4.0的开发流程基本就是如上两大步骤,六小步骤,如果有不明白,可以联系我。 注意:蓝牙调试代码只能真机,模拟器是没效果的。 代码地址https://github.com/lidong1665/Swift-BLE
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010046908/article/details/51809558 接着上篇Android中WebView加载本地Html,与JavaScript与Android方法相互传值,今天来一篇续集,为什么要来续集呢,感觉自己还有没有交代完的部分,于是在这里再次交代一下,在Android中我们需要将给复杂的数据传到Html页面,我们该怎么做? 大家作为一直搞android或IOS的小伙伴们,有好多对Html都了解不是太多,于是思考了一番,无非就是向Html页面传值吗?我就只会传字符串等基本的数据类型,遇到像List或Map中多层嵌套的复杂数据我该怎么办?我只会字符串,我只会字符串,我只会字符串,于是我是想到了JSON,JSON作为数据交换的一种方式,我是该选择JSON字符串来帮我完成复杂的数据交换,是因为Android或JavaScript中都JSON转换的方法,能够方便的数据转换。 上面我已经把问题抛了出来问题,解决的方法也已经给出。下面来一个简单的例子来说明一下实现方法。 案例说明,我要将一个List传到页面做一个表格,就这么简单。 1.创建一个Person对象 package com.lidong.androiddemo; /** * Created by lidong on 16/7/2 */ public class Person { public String name; public String age; public String uint; public Person(String name, String age, String uint) { this.name = name; this.age = age; this.uint = uint; } } 2.创建MyObeject package com.lidong.androiddemo; import android.content.Context; import android.util.Log; import android.webkit.JavascriptInterface; import com.google.gson.Gson; import java.util.ArrayList; import java.util.List; /** * Created by lidong on 16/6/29. */ public class MyObject { public static final String TAG = MyObject.class.getSimpleName() ; private Context mContext; private String data; public MyObject(Context c,String data){ this.data = data; mContext = c; } /** * 获取person字符串传Html * @return */ @JavascriptInterface public String getData(){ List<Person> mlist = new ArrayList<>(); for (int i = 0; i <10 ; i++) { mlist.add(new Person("Li"+i,i+"","com"+i)); } Gson gson = new Gson(); String d = gson.toJson(mlist); Log.d(TAG, "getData: dddd"+d); return d; } } 3.MainActivity的实现 public class MainActivity extends AppCompatActivity { private static final String TAG = MainActivity.class.getSimpleName(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); WebView webView = (WebView) findViewById(R.id.webView); WebSettings settings = webView.getSettings(); //调用WebView关联的WebSettings中setJavaScriptEnable(true)方法。 settings.setJavaScriptEnabled(true); webView.loadUrl("file:///android_asset/index.html"); //调用WebView关联的WebSettings中addJavaScriptInterface webView.addJavascriptInterface(new MyObject(this,"dd"),"my"); } } 4.index.html的实现 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>测试Android Json传值</title> <script src="http://cdn.hcharts.cn/jquery/jquery-1.8.3.min.js"></script> <script src="http://cdn.hcharts.cn/highcharts/highcharts.js"></script> <script type="text/javascript"> $(function(){ //通过暴露的my对象来获取数据 var data = my.getData(); //将json字符串转换为数组 var f = eval(data); //向表格填充数据 for(var i = 0;i<f.length;i++){ var en = f[i]; $("table").append("<tr><td>"+en.age+"</td><td>"+en.name+"</td><td>"+en.uint+"</td></tr>"); } }); </script> </head> <body> 人员表 <div > <table id="table" border="1" bgcolor="#ffddff"></table> </div> </body> </html> 这个案例基本上就四个步骤,通过这篇文章的学习妈妈在也不会担心android向html传复杂的数据了,谢谢学习
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010046908/article/details/51759125 最近在做项目中,要使用HightChart来实现心电图,于是,使用WebView加载本地html页面,但是数据是通过蓝牙设备采集的数据,用Java代码获取的数据,需要将数据传到JavaScript中去,使用来绘制心电图。以前都加载服务器端返回的url地址,使用WebView加载,这次不同了,要自己实现心电图的绘制。于是细细的学习了JavaScript与Java代码相互传值,最后总结在这里。 为了让WebView中的JavaScript脚本调用Android方法,WebView提供了一套WebSettings工具类,该工具了提供了大量的方法来管理WebView的选项设置,其中setJavaScriptEnable(true),是让WebView中的JavaScript脚本来调用Android方法。 还有一个最重要的方法addJavaScriptInterface(Object obj,String name)方法,该方法负责把object对象暴漏成JavaScript中的name对象。 1.在WebView调用Android只需要三步 调用WebView关联的WebSettings中setJavaScriptEnable(true)方法。 调用WebView关联的WebSettings中addJavaScriptInterface(Object obj,String name)。 在JavaScript中通过暴露出来的name调用Android中的方法。 1.1首先创建一个Java类 public class MyObject { private Context mContext; private String data; private String time; public MyObject(Context c,String data,String time){ this.data = data; this.time = time; mContext = c; } /** * 获取心电数据 * @return */ @JavascriptInterface public String getData(){ String[] dd = new String[0]; try { dd = data.substring(data.indexOf("Ъ")+2, data.length()-1).split(","); } catch (Exception e) { e.printStackTrace(); } return Arrays.toString(dd).toString(); } /** * 获取测量时间 * @return */ @JavascriptInterface public String getTime(){ return time; } } 1.2创建在Activity中加载HTML页面 WebSettings wSet =wb .getSettings(); //调用WebView关联的WebSettings中setJavaScriptEnable(true)方法。 wSet.setJavaScriptEnabled(true); //加载本地HTML页面 wb.loadUrl("file:///android_asset/xd.html"); if (mList != null && mList.size()>0) { 调用WebView关联的WebSettings中addJavaScriptInterface(Object obj,String name)。 wb.addJavascriptInterface(new MyObject(getActivity(), mList.get(0).getResult(),mList.get(0).getDateTime()),"myObj");//这里的myObj是javaScript对象,直接调用getTime()方法,即 myObj.getTime(); } 1.3创建xd.hmtl文件 <!DOCTYPE HTML> <html> <head> <base href="<%=basePath%>"> <title>心电</title> <meta http-equiv="pragma" content="no-cache"> <meta http-equiv="cache-control" content="no-cache"> <meta http-equiv="expires" content="0"> <meta http-equiv="keywords" content="keyword1,keyword2,keyword3"> <meta http-equiv="description" content="This is my page"> <!-- <link rel="stylesheet" type="text/css" href="styles.css"> --> <!--<script src="http://cdn.hcharts.cn/jquery/jquery-1.8.3.min.js"></script>--> <!--<script src="http://cdn.hcharts.cn/highcharts/highcharts.js"></script>--> <script type="text/javascript" src="file:///android_asset/js/jquery-1.8.3.min.js"></script> <script type="text/javascript" src="file:///android_asset/js/highcharts.js"></script> <script type="text/javascript"> $(function(){ var data = myObj.getData(); var time = myObj.getTime(); dataHighchartXdDt(data,time); }) //心电 function dataHighchartXdDt(dtxd,t){ //以下是绘制心电的逻辑 //省略 }); } </script> <style> </style> </head> <body> <div class="middlecenter-left-data-top" id="container"></div> </body> </html> 注意: var data = myObj.getData(); var time = myObj.getTime(); 以上第一个方法就是获取心电数据,第二个方法就是获取测量时间。 2.在Android调用JavaScript方法 2.1webView调用js的基本格式为 webView.loadUrl(“javascript:methodName(parameterValues)”) 2.2调用js无参无返回值函数 String call = "javascript:sayHello()"; webView.loadUrl(call); 2.3调用js有参无返回值函数 注意对于字符串作为参数值需要进行转义双引号。 String call = "javascript:alertMessage("" + "content" + "")"; webView.loadUrl(call); 2.4调用js有参数有返回值的函数 Android在4.4之前并没有提供直接调用js函数并获取值的方法,所以在此之前,常用的思路是 java调用js方法,js方法执行完毕,再次调用java代码将值返回。 2.4.1.Java调用js代码 String call = "javascript:sumToJava(1,2)"; webView.loadUrl(call); 2.4.2 js函数处理,并将结果通过调用java方法返回 function sumToJava(number1, number2){ window.control.onSumResult(number1 + number2) } 2.4.3.Java在回调方法中获取js函数返回值 @JavascriptInterface public void onSumResult(int result) { Log.i(LOGTAG, "onSumResult result=" + result); } 2.5 java代码时用evaluateJavascript方法调用 function getGreetings() { return 1; } private void testevaluateJavascript(WebView webView) { webView.evaluateJavascript("getGreetings()", new ValueCallback<String>() { @Override public void onReceiveValue(String value) { Log.i(LOGTAG, "onReceiveValue value=" + value); }}); } 输出结果 I/MainActivity( 1432): onReceiveValue value=1 注意 上面限定了结果返回结果为String,对于简单的类型会尝试转换成字符串返回,对于复杂的数据类型,建议以字符串形式的json返回。 evaluateJavascript方法必须在UI线程(主线程)调用,因此onReceiveValue也执行在主线程。 总结,JavaScript与Android方法相互传值,基本上就这么多了。
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010046908/article/details/51636392 今天给大家带来的是堪称是一个可以替代SQLite,Core Data 的以及ORMlibraries的轻量级数据库—Realm移动端数据库。 相比SQLite,Realm更快并且具有很多现代数据库的特性,比如支持JSON,流式api,数据变更通知,以及加密支持,这些都为IOS开发者带来了方便。 Ream提供了五种编程方式的实现。分别是Java,Objective C,Swift,React-Native,tamarin。在前面我着重介绍在Android中的使用。现在来介绍在Swift中得使用。 1 .关于Realm的几个特点: (1)使用简单,大部分常用的功能(比如插入、查询等)都可以用一行简单的代码轻松完成,学习成本低。 (2)Realm不是基于Core Data,也不是基于SQLite封装构建的。它有自己的数据库存储引擎。 (3)Realm具有良好的跨平台特性,可以在iOS和Android平台上共同使用。代码可以使用 Swift 、 Objective-C 以及 Java 语言来编写。 (4)Realm 还提供了一个轻量级的数据库查看工具(Realm Browser)。你也可以用它进行一些简单的编辑操作(比如插入和删除操作) 2.支持的类型 Realm支持以下的属性类型:Bool、Int8、Int16、Int32、Int64、Double、Float、String、NSDate(精度到秒)以及NSData. 也可以使用List 和Object来建立诸如一对多、一对一之类的关系模型,此外Object的子类也支持此功能。 3.Realm的安装配置 Realm的官网去下载最新框架:http://static.realm.io/downloads/swift/latest 拖拽RealmSwift.framework和Realm.framework文件到”Embedded Binaries”选项中。选中Copy items if needed并点击Finish 4.开始获取Realm // // RealmUtil.swift // HelloSwfit // // Created by lidong on 16/6/11. // Copyright © 2016年 lidong. All rights reserved. // import Foundation import RealmSwift /// RealmUtil工具类 class RealmUtil { private static let instance = RealmUtil() // 单例 全局的数据访问接口 class var sharedInstance: RealmUtil { struct Static { static var onceToken : dispatch_once_t = 0 static var instance : RealmUtil? = nil } dispatch_once(&Static.onceToken) { Static.instance = RealmUtil() } return Static.instance! } /** 得到Realm - returns: <#return value description#> */ func getRealm() -> Realm{ var config = Realm.Configuration() // Use the default directory, but replace the filename with the username config.fileURL = config.fileURL!.URLByDeletingLastPathComponent? .URLByAppendingPathComponent("realm_demo.realm") // 创建一个有配置的realm let realm = try! Realm(configuration: config) return realm } } 5.创建RealmSwift.Object // // Dog.swift // HelloSwfit // // Created by lidong on 16/6/11. // Copyright © 2016年 lidong. All rights reserved. // import UIKit import RealmSwift class Dog: Object { dynamic var name = "" dynamic var age = 0 } class Person: Object { dynamic var name = "" dynamic var picture: NSData? = nil // optionals supported var dogs = List<Dog>() } 6.开始对数据库进行简单的增删改查的操作 // // HandleData.swift // HelloSwfit // // Created by lidong on 16/6/11. // Copyright © 2016年 lidong. All rights reserved. // import Foundation import RealmSwift /// 处理数据 class HandleData { /** 添加一个狗 - parameter dog: <#dog description#> */ func addDog(dog:Dog){ let realm = RealmUtil.sharedInstance.getRealm() try! realm.write { realm.add(dog) } } /** 修改一个狗的信息 - parameter dog: <#dog description#> */ func updateDog(dog:Dog){ let realm = RealmUtil.sharedInstance.getRealm() try! realm.write { realm.add(dog,update:true) } } /** 删除一个狗的信息 - parameter dog: <#dog description#> */ func deleteDog(dog:Dog){ let realm = RealmUtil.sharedInstance.getRealm() try! realm.write { realm.delete(dog) } } /** 查询所有狗的信息 - returns: <#return value description#> */ func findAll(code:String) -> RealmSwift.Results<Dog> { let realm = RealmUtil.sharedInstance.getRealm() let results = realm.objects(Dog.self).filter("age = \(code)") return results } /** 添加一个人 - parameter p: */ func addPerson(p:Person){ let realm = RealmUtil.sharedInstance.getRealm() try! realm.write { realm.add(p) } } /** 查找所有的用户信息 - returns: <#return value description#> */ func findAll() -> RealmSwift.Results<Person>{ let realm = RealmUtil.sharedInstance.getRealm() let results = realm.objects(Person.self) return results } } 7.在RealmViewController中简单调用 // // RealmDemo.swift // HelloSwfit // // Created by lidong on 16/6/11. // Copyright © 2016年 lidong. All rights reserved. // import UIKit import RealmSwift class RealmDemo:UIViewController { override func viewDidLoad() { super.viewDidLoad() self.view.backgroundColor = UIColor.whiteColor() let h = HandleData() // let dog = Dog() // dog.age = 2 // dog.name = "gggg" // h.addDog(dog) // let dogs1 = List<Dog>() // dogs1.append(dog) // // let person = Person() // person.name = "lidong" // person.dogs = dogs1 // // h.addPerson(person) let result = h.findAll() print(result) } } 代码地址:
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010046908/article/details/51629262 好长时间没有写关于Android方面的学习文章了,今天给大家带来的是堪称是一个可以替代SQLite以及ORMlibraries的轻量级数据库—Realm移动端数据库。 相比SQLite,Realm更快并且具有很多现代数据库的特性,比如支持JSON,流式api,数据变更通知,以及加密支持,这些都为安卓开发者带来了方便。 Ream提供了五种编程方式的实现。分别是Java,Objective C,Swift,React-Native,tamarin。在这里我着重介绍在Android中的使用。后面也会介绍在Swift中得使用。 1.先介绍一下打开数据Realm数据库的工具:Realm Browser可视化工具 Realm资源包中包含了一个很有用的实用工具,可以帮助我们更好地管理Realm数据库,那就是Realm Browser。Realm Browser可以让您轻松地读写Realm数据库(以.realm结尾),因此我们无需头疼如何去查看Realm专有数据库的逻辑结构以及其中的数据,可视化的操作就如同SQLite的其他数据库查看工具一样,十分简单、易用(虽然Realm Browser的功能还十分简陋,真的只能读写而已)。 2.Realm支持的数据类型 支持基本数据结构:boolean, byte, short, ìnt, long, float, double, String, Dateand byte[] 支持JSON等复杂的数据类型 3.Realm的官方名词 Realm:Realm是框架的核心所在,是我们构建数据库的访问点,使用建造者模式构建对象。 RealmObject:这是我们自定义的realm数据模型。创建数据模型的行为将会影响到数据库的结构。要创建一个数据模型,我们只需要继承RealmObject,然后设计我们想要存储的属性即可。 RealmQuery(查询):要在数据库中检索信息,我们需要用到“检索”操作。如果需要检索更复杂的数据,那么还可以使用复合查询以及结果排序等等操作。 RealmResults:这个类是执行任何查询请求后所返回的类,其中包含了一系列的Object对象。和List类似,我们可以用下标语法来对其进行访问,并且还可以决定它们之间的关系。不仅如此,它还拥有许多更强大的功能,包括排序、查找等等操作。 4、Realm在Android中的使用 4.1在项目的build.grade中配置下载库文件 在本案例中我使用的最新版本的1.0.0版本。 apply plugin: 'realm-android' buildscript { repositories { jcenter() maven { url 'https://jitpack.io' } } dependencies { classpath "io.realm:realm-gradle-plugin:1.0.0" } } 4.2创建数据库,获取去Realm package com.lidong.demo.realm; import android.content.Context; import io.realm.Realm; import io.realm.RealmConfiguration; /** * *@className:RealmUtil *@desc:RealmUtil工具类 *@author:lidong *@datetime:16/6/10 下午9:55 */ public class RealmUtil { private static RealmUtil sIntance; public final Context mContext; private String realmName = "realm_demo.realm"; public RealmUtil(Context mContext) { this.mContext = mContext; } /** * 双检索单例 * @param context * @return */ public static RealmUtil getIntance(Context context){ if (sIntance == null) { synchronized (RealmUtil.class) { if (sIntance == null) { sIntance = new RealmUtil(context); } } } return sIntance; } /** * 获取realm对象 * @return */ public Realm getRealm(){ Realm realm =Realm.getInstancenew RealmConfiguration.Builder(mContext) .name(realmName) .build()); return realm; } } 4.3创建一个RealmObject 只要继承了RealmObject类,任意JavaBean都能存储在Realm中。必须有一个默认构造器,成员变量有相应的getter/setter方法 package com.lidong.demo.realm; import io.realm.RealmObject; import io.realm.annotations.PrimaryKey; /** * Person */ public class Person extends RealmObject { @PrimaryKey private String code;//编号 private String name;//姓名 private int age;//年龄 public Person() { } public Person(int age, String code, String name) { this.age = age; this.code = code; this.name = name; } public String getCode() { return code; } public void setCode(String code) { this.code = code; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "Person{" + "code='" + code + '\'' + ", name='" + name + '\'' + ", age=" + age + '}'; } } 4.4对数据Person进行增删改查操作 PersonDao.java package com.lidong.demo.realm; import java.util.List; /** * Created by lidong on 16/6/9. */ public interface PersonDao { /** * 插入Person * @param person * @throws Exception */ void insert(Person person)throws Exception; /** * 获取所有的用户 * @return * @throws Exception */ List<Person> getAllPerson()throws Exception; /** * 更新用户 * @throws Exception */ Person updatePerson(Person person)throws Exception; /** * 删除用户 * @param code * @throws Exception */ void deletePerson(String code)throws Exception; /** * 异步插入Person * @param person * @throws Exception */ void insertPersonAsync(Person person)throws Exception; } PersonDaoImp.java package com.lidong.demo.realm; import android.content.Context; import java.util.List; import io.realm.Realm; /** * *@className:PersonDaoImpl *@desc: *@author:lidong *@datetime:16/6/10 下午10:01 */ public class PersonDaoImpl implements PersonDao { private Context context; private Realm mRealm; public PersonDaoImpl(Context context){ mRealm = RealmUtil.getIntance(context).getRealm(); } /** * @同步插入用户 * @param person * @throws Exception */ @Override public void insert(Person person) throws Exception { mRealm.beginTransaction(); Person person1 = mRealm.copyToRealm(person); mRealm.commitTransaction(); mRealm.close(); } /** * 获取所有的用户 * * @return * @throws Exception */ @Override public List<Person> getAllPerson() throws Exception { List<Person> mlist = null; mlist = mRealm.where(Person.class).findAll(); mRealm.close(); return mlist; } /** * @param person * @throws Exception */ @Override public Person updatePerson(Person person) throws Exception { mRealm.beginTransaction(); Person person1 = mRealm.copyToRealmOrUpdate(person); mRealm.commitTransaction(); mRealm.close(); return person1; } @Override public void deletePerson(String code) throws Exception { Person person = mRealm.where(Person.class).equalTo("code",code).findFirst(); mRealm.beginTransaction(); person.deleteFromRealm(); mRealm.commitTransaction(); } /** * 异步插入Person * * @param person * @throws Exception */ @Override public void insertPersonAsync(final Person person) throws Exception { //一个Realm只能在同一个线程中访问,在子线程中进行数据库操作必须重新获取Realm对象: mRealm.executeTransaction(new Realm.Transaction() { @Override public void execute(Realm realm) { realm.beginTransaction(); Person person1 = realm.copyToRealm(person); realm.commitTransaction(); realm.close();//并且要记得在离开线程时要关闭 realm.close(); } }); mRealm.close();//关闭Realm对象 } } 4.5在Activity中简单调用 package com.lidong.demo.realm; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.util.Log; import com.lidong.demo.R; import java.util.List; import io.realm.Realm; public class DemoRealmActivity extends AppCompatActivity { static final String TAG = DemoRealmActivity.class.getSimpleName(); Realm mRealm; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_demo_realm); // mRealm= RealmUtil.getIntance(this).getRealm(); // // // mRealm.executeTransaction(new Realm.Transaction() { // @Override // public void execute(Realm realm) { // Person person = realm.createObject(Person.class); // person.setName("李东"); // person.setAge(24); // person.setCode(UUID.randomUUID().toString()); // } // }); Person person = new Person(); person.setName("李东1"); person.setAge(28); person.setCode("6e56d3aa-7119-429e-8c59-7ad8241e838d"); PersonDao dao = new PersonDaoImpl(this); // try { // dao.insert(person); // } catch (Exception e) { // e.printStackTrace(); // } try { dao.deletePerson("6e56d3aa-7119-429e-8c59-7ad8241e838d"); } catch (Exception e) { e.printStackTrace(); } try { List<Person> persons = dao.getAllPerson(); Log.d(TAG, "onCreate: "+persons); } catch (Exception e) { e.printStackTrace(); } } @Override protected void onDestroy() { super.onDestroy(); } } 总结:Android中使用Realm数据库基本上就这几点步骤,这是个入门,更加复杂的操作,我在后面会慢慢的深入。 代码地址
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010046908/article/details/51592303 下面是我的新建的Swift学习交流群,欢迎大家一起来共同学习Swift。 不管是IOS还是Android,就三种常用模式,MVC,MVP,MVVM网上的资料非常之多,对于MVVM大家估计都有所了解,我在这里就简单的以图示的形式给大家展示。 ViewModel层,就是View和Model层的粘合剂 View层就是ViewController Model层就是用于处理数据的层 这样简单的描述了一下,大家应该是可以明白的。 1.项目的目录结构 2.案例的实现: 2.1 View层的实现 2.1.1 ZGJMView的实现 // // ZGJMView.swift // HelloSwfit // // Created by lidong on 16/6/5. // Copyright © 2016年 lidong. All rights reserved. // import UIKit /** * 周公解梦的view层 */ protocol ZGJMView { /** 显示等待进度框 */ func showProgress() /** 影藏等待进度框 */ func hideProgress() /** 获取服务器返回的数据 - parameter items: <#items description#> */ func getZGJM(items:Array<String>) } 2.1.2 ZGJMDemoMVVM的实现 // // ZGJMDemoMVVM.swift // HelloSwfit // // Created by lidong on 16/6/5. // Copyright © 2016年 lidong. All rights reserved. // import UIKit class ZGJMDemoMVVM: UITableViewController,ZGJMView { var items:Array = [String]() override func viewDidLoad() { super.viewDidLoad() self.tableView.delegate = self self.tableView.dataSource = self self.title = "GET请求" let vm = ZGJMViewModel(view: self) vm.getZGJMData() } func showProgress() { MBProgressHUD.showHUDAddedTo(self.view, animated: true) } func hideProgress() { MBProgressHUD.hideHUDForView(self.view, animated: true) } func getZGJM(items: Array<String>) { self.items = items self.tableView.reloadData() } override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return items.count } override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { let cell = UITableViewCell() cell.textLabel?.text = items[indexPath.row] return cell; } override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { Util.showToast(self, message: items[indexPath.row]) } } 2.2Model层代码的实现: // // NetWokingModel.swift // HelloSwfit // // Created by lidong on 16/6/5. // Copyright © 2016年 lidong. All rights reserved. // import Foundation import SwiftyJSON /** * 定义获取数据的代理 */ protocol ZGJMModelDelegate { /** 获取数据成功的回调 - parameter error: <#error description#> */ func getDataError(error:String) /** 获取数据失败的回调 - parameter items: <#items description#> */ func getDataSuccess(items:Array<String>) } /// model层用来获取数据 class ZGJMModel:ResponseResultDelegate { var delegate : ZGJMModelDelegate! init(delegate : ZGJMModelDelegate){ self.delegate = delegate } /** 获取周公解梦的数据 */ func getZGJMData() { let aFUtil:AFNetWorkingUtil = AFNetWorkingUtil.sharedInstance aFUtil.delegate = self let action:String = "/dream/category" let dic = ["key":"c73b082b0c150b3bcba2cea1b96a8922"] aFUtil.get(action, params: dic) } /** <#Description#> - parameter responseObj: <#responseObj description#> */ func responseSuccess(responseObj: AnyObject?) { var items:Array = [String]() let json = JSON(responseObj!) Util.log("responseSuccess", message: json["error_code"].intValue) Util.log("responseSuccess", message: json["reason"].string!) Util.log("responseSuccess", message: json["result"].array!.count) let d = json["result"].array!.count if d > 0 { let list: Array<JSON> = json["result"].array! for item in list { items.append(item["name"].string!) } } delegate.getDataSuccess(items) } /** <#Description#> - parameter responseObj: <#responseObj description#> */ func responseError(responseObj: AnyObject?) { delegate.getDataError("服务器异常!") } } 2.3 ViewModel层的实现 // // NetWorkingViewModel.swift // HelloSwfit // // Created by lidong on 16/6/5. // Copyright © 2016年 lidong. All rights reserved. // /// 周公解梦ViewModel层 class ZGJMViewModel: ZGJMModelDelegate { /// model var model:ZGJMModel! /// view var view:ZGJMView /** 构造方法 - parameter view: <#view description#> - returns: <#return value description#> */ init(view:ZGJMView){ self.view = view self.model = ZGJMModel(delegate: self) } /** 获取周公解梦的数据 */ func getZGJMData() { self.view.showProgress() self.model.getZGJMData() } /** 错误信息的回调 - parameter error: <#error description#> */ func getDataError(error: String) { self.view.hideProgress() } /** 返回成功的回调 - parameter items: <#items description#> */ func getDataSuccess(items: Array<String>) { self.view.hideProgress() self.view.getZGJM(items) } } 2.4AFNetworkingUtil的实现 // // AFNetWorkingUtil.swift // HelloSwfit // // Created by lidong on 16/5/17. // Copyright © 2016年 lidong. All rights reserved. // import UIKit /// 对AFNetworking的封装 /** * 网络请求响应结果的回调 */ protocol ResponseResultDelegate { /** 响应成功的回调 - parameter response: 成功的信息 */ func responseSuccess(responseObj:AnyObject?) /** 响应失败的回调 - parameter responseError: 失败的信息 */ func responseError(responseObj:AnyObject?) } class AFNetWorkingUtil { /// 基础URL let BASE_URL = "http://v.juhe.cn" /// AFHTTPSessionManager let _sessionManager = AFHTTPSessionManager() /// 定义一个响应结果的传递代理 var delegate: ResponseResultDelegate? // 单例 全局的的网络工具 class var sharedInstance: AFNetWorkingUtil { struct Static { static var onceToken : dispatch_once_t = 0 static var instance : AFNetWorkingUtil? = nil } dispatch_once(&Static.onceToken) { Static.instance = AFNetWorkingUtil() } return Static.instance! } /** 获取baseUrl - parameter baseUrl: 基础的url - returns: URL */ func getBaseUrl(baseUrl:String) ->String{ return BASE_URL } /** post请求 - parameter action: 请求的action - parameter params: 请求参数 - parameter v: <#v description#> */ func post(action:String,params:Dictionary<String,String>){ _sessionManager.POST(getBaseUrl(BASE_URL)+action, parameters: params, success: { (operation:NSURLSessionDataTask?, responseObj:AnyObject?) in print(responseObj) self.delegate?.responseSuccess(responseObj) }) { (operation:NSURLSessionDataTask? ,error:NSError) in print(error) self.delegate?.responseError(error) } } /** get请求 - parameter action: 请求的action - parameter params: 请求参数 */ func get(action:String,params:Dictionary<String,String>){ _sessionManager.GET(getBaseUrl(BASE_URL)+action, parameters: params, success: { (operation:NSURLSessionDataTask?, responseObj:AnyObject?) in print(responseObj) self.delegate?.responseSuccess(responseObj) }) { (operation:NSURLSessionDataTask? ,error:NSError) in print(error) self.delegate?.responseError(error) } } } 总结:最后总结一下,通过使用MVVM对代码进行分层之后,模块化更加的清楚,假如要换网络框架,需要修改model层和api层的代码,其他的层的代码都不需要再动,这样各层之间保持单一职责。代码的易读性变得更强了。 代码地址: 运行的效果: