(Yes it’s a long title, since people kept asking me to write about this and that too :) I do when it has a point.) While SQL databases are insanely useful tools, their monopoly in the last decades is coming to an end. And it’s just time: I can’t even count the things that were forced into relational databases, but never really fitted them. (That being said, relational databases will always be the best for the stuff that has relations.) But, the differences between NoSQL databases are much bigger […]
View Details最近在写一些东西的时候,有一些数据是使用XML来保存的,而其中有一些数据是一段HTML文本,一开始觉得没什么问题,当把这些HTML放到XML的一个结点的时候,才发现数据已经变样了。 原来在XML中有5个预定义的实体引用: < < 小于 > > 大于 & & 和号 ' ' 省略号 " " 引号 参考:http://www.w3school.com.cn/xml/xml_cdata.asp HTML和XML差不多,是由一些预定义的标签组成的,所以包含了大量的"<"、">"符号。 所以有两个方法可以将HTML插入到XML中: 1、将HTML进行一次转换,将所有的"<"、">"等预定义符号转换成相应的实体引用。 2、将HTML包在CDATA中。 很明显,第一种方法比较烦琐,序列化数据的时候和把序列化的时候都需要进行转换。 第二种方法则很简单了。 写了一个方法:
1 2 3 4 5 6 7 |
public class XMLUtil { public static function CDATA(data:String):XML { return new XML( " <![CDATA[ " + data + " ]]\> " ); } } |
可以这样使用:
1 |
var node:XML = < node > {XMLUtil.CDATA( " 字符串 " )} </ node > ; |
参考: http://cookbooks.adobe.com/post_Create_CDATA_tags_between_XML_nodes_using_AS-6142.html 转载于:https://www.cnblogs.com/yili16438/archive/2011/04/13/2015369.html from:https://blog.csdn.net/weixin_30556161/article/details/97479499
View Details缘由 我们在使用spring boot开发的服务中,一般会选择打包成单体的fatjar来发布服务,这在传统的部署方式下是非常方便的,但是当我们选择使用docker这种容器化的方式来部署应用的时候,却有一点点的不便之处,因为这个单体的jar一般都比较大,每次镜像push到仓库和从仓库拉取都需要比较长的时间。 原因是什么了? docker的一大特色就是镜像的存储是分层的,参考下面这张官图 我们在Dockerfile中的每一个指令会对应到镜像的每一层,docker在更新镜像时,只会推送变更过的层,当它计算出来这一层的摘要和之前的版本一致时,会复用上一次打包镜像时的缓存,会极大的提高打包镜像以及镜像push/pull操作的速度。 那么问题来了,当我们springboot打包出来的单体jar的时候,每次编译这个jar都会发生变化,对应的存储层也会发生变化,push和pull操作时都需要重新推送,而且这个jar一般都不小,一个典型的应用会在100M左右,对应用部署和发布的速度会有比较大的影响。 稍作思考,很容易就能发现这个肥大的jar文件里面,大部分其实都是固定不变的各种依赖库,我们真正每次编译会变化的业务代码部分其实很小很小,可能也就只有几百KB,只要能将这两部分分离,变成docker镜像中的两层,一定能极大的提升镜像发布的速度 牛刀小试 首先拿来动手尝试的是一个springboot admin的项目,项目的结构是这样的: 使用最常见的打包方式:
1 2 3 4 |
<plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> |
会生成一个32M的jar文件,优化之前的Dockerfile非常简单:
1 2 3 4 5 6 7 8 |
FROM openjdk:11.0.5-stretch as builder RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime RUN echo 'Asia/Shanghai' >/etc/timezone VOLUME /tmp COPY target/*.jar ./app.jar CMD ["/bin/bash", "-c", "java -jar -server app.jar"] EXPOSE 8080 |
可以看到这种方式在构建v2版本的镜像时,会重新copy整个完整的jar 如果要拆开这个单体的jar,有两种方式,一是修改mvn打包的配置,将lib包放在独立的文件夹下,在这里我们考虑到项目众多,尽量减少修改,选择了在Docker打包镜像时,解压打包出来的jar包,将其中的内容分开来copy,修改后的Dockerfile如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
#采用docker的分阶段构建方式,第一阶段负责解压jar包 FROM openjdk:11.0.5-stretch as builder RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime RUN echo 'Asia/Shanghai' >/etc/timezone VOLUME /tmp WORKDIR /target ADD target/*.jar app.jar RUN jar xf app.jar #这里分别copy解压后的内容 FROM openjdk:11.0.5-stretch VOLUME /tmp WORKDIR /app COPY --from=builder target/BOOT-INF/lib ./lib COPY --from=builder target/org/ ./org COPY --from=builder target/META-INF/ ./META-INF COPY --from=builder target/BOOT-INF/classes ./classes CMD ["/bin/bash", "-c", "java -cp .:./classes/:./lib/* -server org.springframework.boot.loader.JarLauncher"] EXPOSE 8080 |
看看修改后的效果: 在copy lib目录时,是直接using cache的。来看看push的时候效果对比 首先是优化前的push: 可以看到在push v2的时候还是会push一个33MB的层,虽然其实我们一行代码没有修改。 然后是优化后的: 可以看到这一次仅仅只推送了一个13KB的层,推送的速度快了非常多,同理也可以想象的到,我们在拉取镜像更新版本时速度会快很多。 路遇荆棘 在针对springboot-admin这个最简单的项目的优化取得很好的效果之后,就开始准备照搬到其他的项目中,没想到同样的方式怎么折腾都无效,分离之后的lib目录依然会每次需要全量重新push。出问题的项目结构大概是这样的: 一个常见的多模块mvn项目,有common,domain,rest-client,rest-server 这4个子模块,其中rest-server会依赖common和domain这两个子模块,打包出来的jar是在rest-server这个模块中。 究竟是什么鬼了? 终得正果 苦苦思索一番之后,lib目录既然不能复用上一次的cache,那一定是因为里面的内容有变化,遂将jar包解压,进到lib目录,真凶果然在此: 项目自身的3个子模块在每次编译的时候也会做为jar包放到lib目录下,这3个jar包每次编译都会有变化,所以导致这一层的cache失效。 找到问题之后,解决的思路就很简单了,将这种jar单独copy到一个目录下即可,修改后的Dockerfile如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
FROM openjdk:11.0.5-stretch as builder RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime RUN echo 'Asia/Shanghai' >/etc/timezone VOLUME /tmp WORKDIR /target ADD target/*.jar app.jar RUN jar xf app.jar #创建一个snapshot目录,把snapshot的jar复制过去 RUN mkdir BOOT-INF/snapshot RUN mv BOOT-INF/lib/*SNAPSHOT.jar BOOT-INF/snapshot/ FROM openjdk:11.0.5-stretch VOLUME /tmp WORKDIR /app COPY --from=builder target/BOOT-INF/lib ./lib COPY --from=builder target/org/ ./org COPY --from=builder target/META-INF/ ./META-INF #单独复制snapshot这一层 COPY --from=builder target/BOOT-INF/snapshot ./snapshot COPY --from=builder target/BOOT-INF/classes ./classes CMD ["/bin/bash", "-c", "java -cp .:./classes/:./lib/*:./snapshot/* -server org.springframework.boot.loader.JarLauncher"] EXPOSE 8089 |
这样修改之后效果就和上面单模块的项目一样了,至此,基本完成了springboot项目的docker镜像优化,在jenkins的流水线上可以将原来镜像push的时间从1分钟以上优化到10s左右 未来之路 在整个优化的过程中,发现springboot2.3 M1版本已经有针对性的优化方案,增加了LAYERED_JAR的打包格式,未来可期。 具体可参考下文: www.jdon.com/53738 注: 本文中举例的两个项目案例,可在github上找到:github.com/yishh/sprin… 作者:thor_lee 链接:https://juejin.cn/post/6844904119338008583 来源:掘金 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
View Details遇到的问题: 公司java项目按老的方式打包出来一个fat jar,100MB, 推送到腾讯云镜像仓库很慢,8分钟。。。走的是公网,专线暂时还没配置好 以前是内网harbor,速度还不明显。 归根究底,一次推送100MB是个不合理的事情 思路: 了解spring boot打包,期望将依赖的libs 和 业务代码拆分 优化dockerfile,充分利用缓存 解决问题: 先修改spring-boot-maven-plugin,只打包业务代码。网上有些是配置exclude,我试了,恶心到了。。那么多包挨个找
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <!--<mainClass>cn.qg.clotho.Bootstrap</mainClass>--> <layout>ZIP</layout> <!--<executable>true</executable>--> <includes> <include> <groupId>${project.groupId}</groupId> <artifactId>${project.artifactId}</artifactId> </include> </includes> </configuration> </plugin> |
新增maven-dependency-plugin,将依赖移到libs目录下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-dependency-plugin</artifactId> <executions> <execution> <id>copy</id> <phase>package</phase> <goals> <goal>copy-dependencies</goal> </goals> <configuration> <type>jar</type> <includeTypes>jar</includeTypes> <includeScope>runtime</includeScope> <outputDirectory>${project.build.directory}/libs</outputDirectory> </configuration> </execution> </executions> </plugin> |
优化dockerfile 参考 https://medium.com/@nieldw/caching-maven-dependencies-in-a-docker-build-dca6ca7ad612 ,牛逼
1 2 3 4 5 6 7 8 9 10 11 12 13 |
FROM mvn3.5 as builder WORKDIR /build COPY pom.xml . RUN mvn dependency:go-offline COPY src/ /build/src/ RUN mvn package FROM jdk1.8 EXPOSE 80 CMD exec java -Dloader.path="/home/libs/" -jar /home/app.jar COPY --from=builder /build/target/*.jar /home/app.jar COPY --from=builder /build/target/libs /home/libs/ |
搞定。最终代码变化每次推送也就1MB多 启动命令 java -Dloader.path="libs/" -jar app.jar 作者:小猋_a8f1 链接:https://www.jianshu.com/p/32456eea0488 来源:简书 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
View Details在这容器化的世界里,我们已经很少直接通过文件发布来运行asp.net core程序了。现在大多数情况下,我们都会使用docker来运行程序。在使用docker之前,我们往往需要打包我们的应用程序。asp.net core程序的镜像打包,网上有很多教程,其中大多数是使用sdk这个镜像来直接打包。打出来的包有好几百MB,3.1 SDK打出来的包甚至超过了1GB。那么有什么办法来缩小我们打出来的镜像吗?最小能缩小到多少呢?这篇文章就来介绍下如何缩小asp.net core 打包出来镜像的大小。 新建asp.net core 程序 新建一个asp.net core应用程序,用来演示打包。首先我们演示下如果使用dotnet sdk5.0来打包 docker 镜像。 sdk:5.0
1 2 3 4 5 6 7 8 |
FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build WORKDIR /app COPY /. /app RUN dotnet restore -s https://nuget.cdn.azure.cn/v3/index.json WORKDIR /app/CoreDockerImageSizeTest RUN dotnet publish -o ./out -c Release EXPOSE 5000 ENTRYPOINT ["dotnet", "out/CoreDockerImageSizeTest.dll"] |
在项目根目录下新建一个Dockerfile文件,文件内容如上。这个Dockerfile比较简单,使用dotnet sdk:5.0最为底层包来构建,这也是最傻瓜的打包方式。那么看看这个镜像打出来有多大吧。
1 |
docker build . -t coredockerimagesizetest_0.1 |
使用docker build命令进行打包。
1 2 |
REPOSITORY TAG IMAGE ID CREATED SIZE coredockerimagesizetest_0.1 latest 14aea8e0c1d5 5 seconds ago 643MB |
使用docker images命令来查看镜像列表,我们发现我们打出来的镜像居然有643MB,真的很大。如果是内网还好一点,如果在镜像存在docker hub等第三方仓库,这得下半天。显然这个镜像太大了,接下来看我们如何进行优化。 sdk:5.0-buster-slim 最新的VisualStudio内置了docker工具,可以自动为我们生成Dockerfile文件。我们来看看它生成的镜像文件有多大。 右键解决方案=>添加=>Docker支持=>Linux 。 选择完成后VS会为我们自动添加一个Dockerfile在根目录。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
FROM mcr.microsoft.com/dotnet/aspnet:5.0-buster-slim AS base WORKDIR /app EXPOSE 5000 FROM mcr.microsoft.com/dotnet/sdk:5.0-buster-slim AS build WORKDIR /src COPY ["CoreDockerImageSizeTest/CoreDockerImageSizeTest.csproj", "CoreDockerImageSizeTest/"] RUN dotnet restore "CoreDockerImageSizeTest/CoreDockerImageSizeTest.csproj" COPY . . WORKDIR "/src/CoreDockerImageSizeTest" RUN dotnet build "CoreDockerImageSizeTest.csproj" -c Release -o /app/build FROM build AS publish RUN dotnet publish "CoreDockerImageSizeTest.csproj" -c Release -o /app/publish FROM base AS final WORKDIR /app COPY --from=publish /app/publish . ENTRYPOINT ["dotnet", "CoreDockerImageSizeTest.dll"] |
这个自动生成的Dockerfile使用了sdk:5.0-buster-slim这个镜像进行build跟publish,使用aspnet:5.0-buster-slim这个runtime级别的镜像做为final底包。从名字来看,很明显slim代表着轻量。让我们试试这个Dockerfile打出来的包有多大。
1 2 |
REPOSITORY TAG IMAGE ID CREATED SIZE coredockerimagesizetest_0.2 latest 0a24618f6ece 11 seconds ago 210MB |
使用docker build命令进行打包。使用docker images命令查看镜像的大小,这个镜像的大小为210MB。果然比上面的镜像小了很多。那么是否还能继续缩小镜像的大小呢?继续往下看。 5.0-alpine 除了使用buster-slim镜像,我们还可以选择更加小巧的alpine镜像来打包。alpine镜像是继续alpine linux创建的镜像,所以它更加轻量级更加小巧。 关于alpine linux可以查看这篇:Alpine Linux 与 CentOS 有什么区别? 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
FROM mcr.microsoft.com/dotnet/aspnet:5.0-alpine AS base WORKDIR /app EXPOSE 5000 FROM mcr.microsoft.com/dotnet/sdk:5.0-alpine AS build WORKDIR /src COPY ["CoreDockerImageSizeTest/CoreDockerImageSizeTest.csproj", "CoreDockerImageSizeTest/"] RUN dotnet restore "CoreDockerImageSizeTest/CoreDockerImageSizeTest.csproj" COPY . . WORKDIR "/src/CoreDockerImageSizeTest" RUN dotnet build "CoreDockerImageSizeTest.csproj" -c Release -o /app/build FROM build AS publish RUN dotnet publish "CoreDockerImageSizeTest.csproj" -c Release -o /app/publish FROM base AS final WORKDIR /app COPY --from=publish /app/publish . ENTRYPOINT ["dotnet", "CoreDockerImageSizeTest.dll"] |
修改Dockerfile使用aspnet:5.0-alpine及sdk:5.0-alpine来构建这个镜像。
1 2 |
REPOSITORY TAG IMAGE ID CREATED SIZE coredockerimagesizetest_0.3 latest db34d613e21a 12 seconds ago 108MB |
使用docker build命令进行打包。使用docker images命令查看镜像的大小,这个镜像的大小为108MB。现在这个镜像已经比我们第一次打包减少了500多MB了。那么还能更小吗?请往下看。 runtime-deps:5.0-alpine 最新的.net core程序支持自宿主及单文件发布。如果采用以上发布形式,那么我们可以选择使用runtime-deps:5.0-alpine做为最终底包来打包我们的镜像。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
FROM mcr.microsoft.com/dotnet/aspnet:5.0-alpine AS base WORKDIR /app EXPOSE 5000 FROM mcr.microsoft.com/dotnet/sdk:5.0-alpine AS build WORKDIR /src COPY ["CoreDockerImageSizeTest/CoreDockerImageSizeTest.csproj", "CoreDockerImageSizeTest/"] RUN dotnet restore "CoreDockerImageSizeTest/CoreDockerImageSizeTest.csproj" COPY . . WORKDIR "/src/CoreDockerImageSizeTest" RUN dotnet build "CoreDockerImageSizeTest.csproj" -c Release -o /app/build FROM build AS publish RUN dotnet publish "CoreDockerImageSizeTest.csproj" -c Release -o /app/publish \ --runtime alpine-x64 \ --self-contained true \ /p:PublishTrimmed=true \ /p:PublishSingleFile=true FROM mcr.microsoft.com/dotnet/runtime-deps:5.0-alpine AS final WORKDIR /app COPY --from=publish /app/publish . ENTRYPOINT ["./CoreDockerImageSizeTest"] |
修改Dockerfile,使用/runtime-deps:5.0-alpine做为final镜像。
1 2 |
REPOSITORY TAG IMAGE ID CREATED SIZE coredockerimagesizetest_0.5 latest dab1289626f9 6 seconds ago 54.6MB |
使用docker build命令进行打包。使用docker images命令查看镜像的大小,这个镜像的大小为54.6MB。 总结 通过以上演示,我们的镜像大小从一开始的600多MB缩小到了54MB。一般生产我主要选择buster-slim这个镜像来打包。如果选择runtime-deps打包,打出来的包是最小的,虽然演示项目是可以运行的,但是本人没有在生产使用过,还请谨慎使用。 代码在这:CoreDockerImageSizeTest from:https://www.cnblogs.com/kklldog/p/netcore-docker-image-size.html
View Detailsconsul consul用于微服务下的服务治理,主要特点有:服务发现、服务配置、健康检查、键值存储、安全服务通信、多数据中心等。 什么叫服务治理发现?起初我们的服务比较单一,各服务之间通过接口就能访问。后面服务越来越复杂出现了分布式,为了不引起单点问题,必然是多服务部署,如果还用原来的方式直接连接,那么在某个服务挂掉或者修改了信息,就会导致连接失败。如果连接端能够不去关心具体的服务配置,他只要连接到那个服务,后续的工作由其它服务保证,包括负载均衡、健康检查等,保证总有可用的连接那就行了,consul就是做这个的,当然,它的功能远不止这些,这里只是以服务发现为例。 与它同类的东西,还有Eureka、zooKeeper、etcd等也能做这些事,说不上谁好谁坏,看场景挑合适的吧,不过Eureka现在已经闭源了,这个还是建议不要去用这个了。 consul下载 consule的安装超级简单,去官方下载地址Download ,找到自已对应系统的压缩包,解压后里面就一个文件consul, 将这个文件放到你的PATH中,就能直接用了。 验证下安装成功没有, 看到下面的提示就行了。
1 2 3 |
$ consul version Consul v1.7.2 Protocol 2 spoken by default, understands 2 to 3 (agent will automatically use protocol >2 when speaking to compatible agents) |
consul的CLI操作 consul提供了cli的命令操作,如启动代理、键值存储、注册/注销服务、加入集群等,这个consul提供的HTTP的API操作也是一样的,只是这里都是shell的操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
$ consul Usage: consul [--version] [--help] <command> [<args>] Available commands are: acl Interact with Consul's ACLs agent Runs a Consul agent catalog Interact with the catalog config Interact with Consul's Centralized Configurations connect Interact with Consul Connect debug Records a debugging archive for operators 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. intention Interact with Connect service intentions 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 login Login to Consul using an auth method logout Destroy a Consul token created with login 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 services Interact with services snapshot Saves, restores and inspects snapshots of Consul server state tls Builtin helpers for creating CAs and certificates validate Validate config files/directories version Prints the Consul version watch Watch for changes in Consul |
Agent启动 consul是通过Agent来运行的,Agent又分为Server Agent和Client Agent两种类型,这两类型基本上是没区别的,Server Agent会将服务的消息存储起来,至少要启动一个Server Agent,为了防止单点,集群环境中推荐3-5个。 Client Agent主要用于注销服务、健康检查及转发Server Agent的查询等,它相当于一个代理,所以他必须在集群的每台主机上都要运行。 先看下Agent的常用配置
1 |
$ consul agent --help |
--server 定义运行server agent --data-dir 配置consul数据存储路径 --bootstrap-expect :期望的server节点数目,consul一直等到指定sever数目的时候才会引导整个集群 --bind:该地址用来在集群内部的通讯,集群内的所有节点到地址都必须是可达的,默认是0.0.0.0 --node:节点在集群中的名称,在一个集群中必须是唯一的,默认是该节点的主机名 --ui: web的管理ui,查看服务和节点 --config-dir:配置文件目录,所有以.json结尾的文件都会被加载,可以是服务或consul自身的配置 --client:提供HTTP、DNS、RPC等服务,默认是127.0.0.1,不对外提供服务,如果需要则改成0.0.0.0 我本地没有虚拟机,也没用Docker操作,所以,如果要同时启动Server和Client的话,我就用的改端口的方式,当然生产环境肯定就没有这个了,它都是一台机器启动一个Agent, 这里只是测试用的。 启动一个Server Agent
1 |
$ consul agent --server=true --ui=true --data-dir=/tmp/consul --node=server1 --dev |
上面的 --server表示以server方式,--ui 会开启一个web ui管理界面, --dev 表示开发者模式,不需要ACL验证。不然那个web ui的打不开会报没有权限。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
==> Starting Consul agent... Version: 'v1.7.2' Node ID: 'f6272369-098a-1412-ed86-6e2076c1e5e4' Node name: 'server1' Datacenter: 'dc1' (Segment: '<all>') Server: true (Bootstrap: false) Client Addr: [127.0.0.1] (HTTP: 8500, HTTPS: -1, gRPC: 8502, DNS: 8600) Cluster Addr: 127.0.0.1 (LAN: 8301, WAN: 8302) Encrypt: Gossip: false, TLS-Outgoing: false, TLS-Incoming: false, Auto-Encrypt-TLS: false ==> Log data will now stream in as it occurs: 2020-04-01T14:28:52.579+0800 [DEBUG] agent.tlsutil: Update: version=1 2020-04-01T14:28:52.580+0800 [DEBUG] agent.tlsutil: OutgoingRPCWrapper: version=1 2020-04-01T14:28:52.580+0800 [INFO] agent.server.raft: initial configuration: index=1 servers="[{Suffrage:Voter ID:f6272369-098a-1412-ed86-6e2076c1e5e4 Address:127.0.0.1:8300}]" 2020-04-01T14:28:52.580+0800 [INFO] agent.server.raft: entering follower state: follower="Node at 127.0.0.1:8300 [Follower]" leader= 2020-04-01T14:28:52.581+0800 [INFO] agent.server.serf.wan: serf: EventMemberJoin: server1.dc1 127.0.0.1 2020-04-01T14:28:52.581+0800 [INFO] agent.server.serf.lan: serf: EventMemberJoin: server1 127.0.0.1 2020-04-01T14:28:52.581+0800 [INFO] agent.server: Adding LAN server: server="server1 (Addr: tcp/127.0.0.1:8300) (DC: dc1)" 2020-04-01T14:28:52.581+0800 [INFO] agent.server: Handled event for server in area: event=member-join server=server1.dc1 area=wan 2020-04-01T14:28:52.581+0800 [INFO] agent: Started DNS server: address=127.0.0.1:8600 network=tcp 2020-04-01T14:28:52.582+0800 [INFO] agent: Started DNS server: address=127.0.0.1:8600 network=udp 2020-04-01T14:28:52.582+0800 [INFO] agent: Started HTTP server: address=127.0.0.1:8500 network=tcp 2020-04-01T14:28:52.582+0800 [INFO] agent: Started gRPC server: address=127.0.0.1:8502 network=tcp 2020-04-01T14:28:52.582+0800 [INFO] agent: started state syncer ==> Consul agent running! |
启动Client Agent
1 |
$ consul agent --data-dir=/tmp/consul_client --join=127.0.0.1:8301 --serf-lan-port=8303 --serf-wan-port=8305 --dns-port=8601 --server-port=8304 --http-port=8503 --server=false --config-dir=./consul.d --enable-script-checks --node=client1 |
上面有一个--join表示加入到集群中,写server agent的地址就行。 config-dir的配置目录下面一个服务的配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
{ "service":{ "name":"web", "tags":[ "local" ], "port":80, "check":{ "name":"ping", "args":["/usr/bin/curl","-s", "http://localhost/"], "interval":"10s" } } } |
上面的命令运行后,会启动一个名字为“web"的服务,并提供健康检查。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
==> Starting Consul agent... Version: 'v1.7.2' Node ID: 'e1c8f283-f5c4-27e7-c29c-64666df1c52b' Node name: 'client1' Datacenter: 'dc1' (Segment: '') Server: false (Bootstrap: false) Client Addr: [127.0.0.1] (HTTP: 8503, HTTPS: -1, gRPC: -1, DNS: 8601) Cluster Addr: 192.168.0.103 (LAN: 8303, WAN: 8305) Encrypt: Gossip: false, TLS-Outgoing: false, TLS-Incoming: false, Auto-Encrypt-TLS: false ==> Log data will now stream in as it occurs: 2020-04-01T00:00:49.227+0800 [INFO] agent.client.serf.lan: serf: EventMemberJoin: client1 192.168.0.103 2020-04-01T00:00:49.231+0800 [INFO] agent: Started DNS server: address=127.0.0.1:8601 network=udp 2020-04-01T00:00:49.232+0800 [INFO] agent: Started DNS server: address=127.0.0.1:8601 network=tcp 2020-04-01T00:00:49.232+0800 [INFO] agent: Started HTTP server: address=127.0.0.1:8503 network=tcp ==> Joining cluster... 2020-04-01T00:00:49.232+0800 [INFO] agent: (LAN) joining: lan_addresses=[127.0.0.1:8301] 2020-04-01T00:00:49.233+0800 [WARN] agent.client.memberlist.lan: memberlist: Refuting a dead message (from: client1) 2020-04-01T00:00:49.233+0800 [INFO] agent.client.serf.lan: serf: EventMemberJoin: server1 127.0.0.1 2020-04-01T00:00:49.233+0800 [INFO] agent.client: adding server: server="server1 (Addr: tcp/127.0.0.1:8300) (DC: dc1)" 2020-04-01T00:00:49.233+0800 [INFO] agent: (LAN) joined: number_of_nodes=1 2020-04-01T00:00:49.233+0800 [INFO] agent: Join completed. Initial agents synced with: agent_count=1 2020-04-01T00:00:49.233+0800 [INFO] agent: started state syncer ==> Consul agent running! 2020-04-01T00:00:49.235+0800 [INFO] agent: Synced node info 2020-04-01T00:00:49.235+0800 [INFO] agent: Synced service: service=web 2020-04-01T00:00:53.316+0800 [INFO] agent: Synced check: check=service:web |
查看下,启动的Agent
1 2 3 4 |
$ ./consul members Node Address Status Type Build Protocol DC Segment server1 127.0.0.1:8301 alive server 1.7.2 2 dc1 <all> client1 127.0.0.1:8303 alive client 1.7.2 2 dc1 <default> |
web管理界面 可以查看服务的定义和节点,访问[web管理界面](http://127.0.0.1:8500/ui) HTTP API consul除了提供DNS外,还提供Http的操作,如注册服务、查看节点、查看服务信息等,一般都是通过API来操作的。 如我上面定义的 "web"服务,通过下面的API查询,就能得到具体的IP地址和端口,这样就能直接连到那台服务了。 其它更详情API操作,可以自已去参照官方的文档 API
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
$ curl http://127.0.0.1:8500/v1/catalog/service/web [ { "ID": "e1c8f283-f5c4-27e7-c29c-64666df1c52b", "Node": "client1", "Address": "127.0.0.1", "Datacenter": "dc1", "TaggedAddresses": { "lan": "127.0.0.1", "lan_ipv4": "127.0.0.1", "wan": "127.0.0.1", "wan_ipv4": "127.0.0.1" }, "NodeMeta": { "consul-network-segment": "" }, "ServiceKind": "", "ServiceID": "web", "ServiceName": "web", "ServiceTags": [ "local" ], "ServiceAddress": "", "ServiceWeights": { "Passing": 1, "Warning": 1 }, "ServiceMeta": {}, "ServicePort": 80, "ServiceEnableTagOverride": false, "ServiceProxy": { "MeshGateway": {}, "Expose": {} }, "ServiceConnect": {}, "CreateIndex": 15, "ModifyIndex": 15 } ] |
from:https://zhuanlan.zhihu.com/p/122340918
View Details可以直接使用命令 git reset HEAD 这个是整体回到上次一次操作 绿字变红字(撤销add) 如果是某个文件回滚到上一次操作: git reset HEAD 文件名 红字变无 (撤销没add修改) git checkout — 文件 from:https://www.cnblogs.com/jpfss/p/11888157.html
View Details装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。 这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。 我们通过下面的实例来演示装饰器模式的用法。其中,我们将把一个形状装饰上不同的颜色,同时又不改变形状类。 介绍 意图:动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活。 主要解决:一般的,我们为了扩展一个类经常使用继承方式实现,由于继承为类引入静态特征,并且随着扩展功能的增多,子类会很膨胀。 何时使用:在不想增加很多子类的情况下扩展类。 如何解决:将具体功能职责划分,同时继承装饰者模式。 关键代码: 1、Component 类充当抽象角色,不应该具体实现。 2、修饰类引用和继承 Component 类,具体扩展类重写父类方法。 应用实例: 1、孙悟空有 72 变,当他变成"庙宇"后,他的根本还是一只猴子,但是他又有了庙宇的功能。 2、不论一幅画有没有画框都可以挂在墙上,但是通常都是有画框的,并且实际上是画框被挂在墙上。在挂在墙上之前,画可以被蒙上玻璃,装到框子里;这时画、玻璃和画框形成了一个物体。 优点:装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。 缺点:多层装饰比较复杂。 使用场景: 1、扩展一个类的功能。 2、动态增加功能,动态撤销。 注意事项:可代替继承。 实现 我们将创建一个 Shape 接口和实现了 Shape 接口的实体类。然后我们创建一个实现了 Shape 接口的抽象装饰类 ShapeDecorator,并把 Shape 对象作为它的实例变量。 RedShapeDecorator 是实现了 ShapeDecorator 的实体类。 DecoratorPatternDemo 类使用 RedShapeDecorator 来装饰 Shape 对象。 步骤 1 创建一个接口: Shape.java
1 2 3 |
public interface Shape { void draw(); } |
步骤 2 创建实现接口的实体类。 Rectangle.java
1 2 3 4 5 6 7 |
public class Rectangle implements Shape { @Override public void draw() { System.out.println("Shape: Rectangle"); } } |
Circle.java
1 2 3 4 5 6 7 |
public class Circle implements Shape { @Override public void draw() { System.out.println("Shape: Circle"); } } |
步骤 3 创建实现了 Shape 接口的抽象装饰类。 ShapeDecorator.java
1 2 3 4 5 6 7 8 9 10 |
public abstract class ShapeDecorator implements Shape { protected Shape decoratedShape;public ShapeDecorator(Shape decoratedShape){ this.decoratedShape = decoratedShape; } public void draw(){ decoratedShape.draw(); } } |
步骤 4 创建扩展了 ShapeDecorator 类的实体装饰类。 RedShapeDecorator.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public class RedShapeDecorator extends ShapeDecorator { public RedShapeDecorator(Shape decoratedShape) { super(decoratedShape); } @Override public void draw() { decoratedShape.draw(); setRedBorder(decoratedShape); } private void setRedBorder(Shape decoratedShape){ System.out.println("Border Color: Red"); } } |
步骤 5 使用 RedShapeDecorator 来装饰 Shape 对象。 […]
View DetailsGuava工程包含了若干被Google的 Java项目广泛依赖 的核心库,例如:集合 [collections] 、缓存 [caching] 、原生类型支持 [primitives support] 、并发库 [concurrency libraries] 、通用注解 [common annotations] 、字符串处理 [string processing] 、I/O 等等。 Guava 是Java的工具集,提供了一些常用的便利的操作工具类,减少因为 空指针、异步操作等引起的问题BUG,提高开发效率。 本文主要介绍了Guava常用的工具方法,快速入门Guava。 1、基本工具(Base utils) 1. Optional null 值出现在代码中,有如下缺点: 语义模糊,引起歧义。例如,Map.get(key)返回Null时,可能表示map中的值是null,亦或map中没有key对应的值。 在应用层面可能造成混乱,出现令人意外的错误。 为了尽量避免程序中的null值,guava提供了Optional对数据进行封装。如果值为空则立即抛出异常,并且提供了Absent和Present两个子类分别表示值缺失和值存在的情形,来增强null的语义。 常用方法如下: isPresent():如果Optional包含非null的引用(引用存在),返回true get() :如果Optional为NULL将触发异常
1 2 3 4 5 6 7 8 |
public static void test(){ Optional<Integer> possible = Optional.fromNullable(5); //创建允许null值的Optional if(possible.isPresent()){//包含的引用非null的(引用存在),返回true System.out.println(possible.get()); }else{ System.out.println("possible is null"); } } |
or(defaultvalue) :包含的引用缺失(null),返回默认的值,否则返回本身 orNull():包含的引用缺失,返回null asSet():如果引用存在,返回只有单一元素的集合;若为NULl返回空集合 2. 先决条件 Preconditions Preconditions 提供了判断条件是否合法的静态方法,如果不符合要求会抛出异常。类似断言。 方法声明(不包括额外参数) 描述 检查失败时抛出的异常 checkArgument(boolean) 检查boolean是否为true,用来检查传递给方法的参数 IllegalArgumentException checkNotNull(T) 检查value是否为null,该方法直接返回value,因此可以内嵌使用checkNotNull NullPointerException checkState(boolean) 用来检查对象的某些状态。 IllegalStateException checkElementIndex(int index, int size) 检查index作为索引值对某个列表、字符串或数组是否有效。index>=0 && index<size IndexOutOfBoundsException checkPositionIndex(int index, int size) 检查index作为位置值对某个列表、字符串或数组是否有效。index>=0 && index<=size IndexOutOfBoundsException checkPositionIndexes(int start, int end, int size) 检查[start, end]表示的位置范围对某个列表、字符串或数组是否有效 IndexOutOfBoundsException 每个判断方法都有三个多态方法: 没有额外参数:抛出的异常中没有错误消息; 有一个Object对象作为额外参数:抛出的异常使用Object.toString() 作为错误消息; 有一个String对象作为额外参数,并且有一组任意数量的附加Object对象:这个变种处理异常消息的方式有点类似printf,但考虑GWT的兼容性和效率,只支持%s指示符。例如:
1 2 3 |
checkArgument(i >= 0); checkArgument(i >= 0, "Argument was expected nonnegative"); checkArgument(i < j, "Expected i < j, but %s > %s", i, j); |
3. […]
View DetailsGuava是谷歌提供的一个核心Java类库,其中包括新的集合类型、不可变集合、图库,以及用于并发、I/O、Hash、缓存、字符串等的 实用工具。它在谷歌中的大多数Java项目中被广泛使用,也被许多其他公司广泛使用,熟练掌握这些工具类能帮助我们快速的处理日常开发中的一些问题,比如,不可变集合、集合的转换、字符串处理、本地缓存等 最近一段时间,我把Guava中常用到的工具类学了一遍,感觉有些工具类还是挺有用的,至少它帮你封装了很多功能,让你在处理一些逻辑的时候,不用太关注细节,把Guava的工具类直接拿来用就好了。下面我就介绍一下Guava中最常用的一些工具: 1、Guava不可变集合 不可变集合就是集合创建之后元素是不可改变的,主要用途如下: 不可变对象提供给别人使用时是安全的,因为不可变,所有人都无法进行修改,只能读 支持多个线程调用,不存在竞争的问题,天然支持多线程 不可变集合节省内存空间,因为不可变,集合空间在创建时就已经确定好了,不用考虑扩容等问题,内存利用率高 不可变集合可用于常量 Guava针对常用的集合类型List、Set、Map等都提供了不可变类型的集合 详细使用方法,可查看另一篇博客《Guava系列之不可变集合》 2、Guava新的集合类型 Guava提供了几种新的集合类型,补充了JDK中的集合类型 比如我们要统计List中某个元素出现的次数,如果使用JDK中的list就需要使用循环遍历进行统计,但使用了Guava的Multiset就可以直接统计出来元素出现的次数 再比如,我们要通过Map中的key查找value,通过value来查找值,也就是需要一个双向Map,如果使用JDK中的Map,我们需要维护两个Map,一个从key映射到value,另外一个从value映射到key,而且不管是新增还是修改Map中的元素,都要保持两个Map同步修改,维护成本太高了,使用Guava的BiMap可以通过一个Map轻松解决这个问题 更多新集合类型请查看《Guava系列之新的集合类型》 3、Guava超实用的集合工具类 JDK中集合的操作已经提供了很多工具类,比如基本的集合交集、并集、差集这些常用的操作,Guava中提供的工具类是对JDK的补充,在Guava中提供了静态的创建集合的方法,还有集合的很多操作,比如笛卡尔集、list反转、排列组合、Set转Map、Map的各种过滤等 新集合工具类的详细使用,请查看《Guava系列之超实用的集合工具类》 4、Guava本地缓存Cache Guava中的缓存是本地缓存的实现,与ConcurrentMap相似,但不完全一样。最基本的区别就是,ConcurrentMap会一直保存添加进去的元素,除非你主动remove掉。而Guava Cache为了限制内存的使用,通常都会设置自动回收 Guava Cache的使用场景: 以空间换取时间,就是你愿意用内存的消耗来换取读取性能的提升 你已经预测到某些数据会被频繁的查询 缓存中存放的数据不会超过内存空间 Guava Cache的详细使用方法,可查看《Guava系列之Cache》 5、Guava强大的String工具类 String是我们平时开发工作当中使用最频繁的类型, Guava提供了字符串的连接、分隔等操作,特别是字符串的匹配,那是相当强大,比如提取出字符串中的字母、数字、特殊字符等,可以从指定字符串中提取、删除、替换等操作 举个例子,提取“er 3j6o 3k ,)$ wt@ wr4576je ow3453535345irjew jwfel ” 字符串的字母,直接可以调用现成的方法 再比如,你需要将上述字符串中的数字全部移除或替换成其他字符,都有现成的方法,使用起来非常方便,只要你使用好了这些工具类, 可以大大提升你对字符串的处理效率 具体详细用法,请查看《Guava系列之强大的String工具类》 6、Guava限流RateLimiter 在互联网高并发场景下,限流是用来保证系统稳定性的一种手段,当系统遭遇瞬时流量激增时,可能会由于系统资源耗尽导致宕机。而限流可以把一小部分流量拒绝掉,保证大部分流量可以正常访问,从而保证系统只接收承受范围以内的请求,多余的请求给拒绝掉 我们常用的限流算法有:漏桶算法、令牌桶算法 Guava中的限流使用的是令牌桶算法,RateLimiter提供了两种限流实现 平滑突发限流(SmoothBursty) 平滑预热限流(SmoothWarmingUp) Guava RateLimiter的详细用法,请查看《Guava系列之限流RateLimiter》 7、Guava发布/订阅EventBus EventBus是Guava中实现的用于发布/订阅模式的事件处理组件,它是设计模式中观察者模式的优雅实现 EventBus是消息总线,它会根据消息的类型发送到指定的消息订阅者,当有消息没有订阅者接收时,会将消息发送给DeadEvent 关于EventBus的详细用法,请查看《Guava系列之EventBus》 以上是对最近学习Guava类库的一个总结,它包括了我们平常开发中最常用的一些组件工具类,熟练掌握这些工具类的使用方法,必然会让你的工作如虎添翼~ from:https://www.pianshen.com/article/57281487560/
View Details