深入浅出Docker技术- 快速运行Mysql容器

环境准备:

    docker/centos7

实例机器:
     192.168.1.102 
本地虚拟机

资料准备:

    https://gitee.com/gz-tony/viemall-dubbo/tree/master/viemall-docekr/compose/mysql

 docker run -d -p 3306:3306 --name=viemall-mysql --restart=always  -e TZ="Asia/Shanghai" \
 -e MYSQL_ROOT_PASSWORD=123456 \
 -v /opt/docker/mysql/data:/var/lib/mysql \
 -v /opt/docker/mysql/conf/utf8.cnf:/etc/mysql/conf.d/utf8.cnf  mysql:5.7.22 \
 --character-set-server=utf8

资料参考:   https://www.cnblogs.com/mmry/p/8812599.html

深入浅出Docker技术- 快速构建Nginx Web负载均衡

image.png

环境准备:

    docker/centos7

实例机器:
     192.168.1.102
本地虚拟机

资料准备:

    https://gitee.com/gz-tony/viemall-dubbo/tree/master/viemall-docekr/compose/nginx-tomcat

官网资料:

     https://hub.docker.com/_/nginx/

概括:

  使用三个容器技术,一个容器部署Nginx, 80端口,一个部署WebA,一个部署WebB, Nginx 这项技术不是很懂的可以看我之前的系列文章;这里不细讲!!!!!


方式一:

   采用传统的docker命令,一个一个的容器部署,dockerfile 这里我就不编写,采用官网的docker文件;

    因为docker run 的默认网络是bridge模式, bridge模式是docker的默认网络模式,容器与容器之间是不互通的,那么我们需要容器共享一个 Network Namespace,所以创建的时候使用自定义网络;

 1.新建docker网络

    docker network create viemall

    docker network ls

 2.启动TomcatATomcatB服务:

docker run  -d  -p 8080:8080 --net=viemall --name=viemall-tomcat-a --restart=always  -e TZ="Asia/Shanghai" \
 -v /opt/docker/tomcat/web-a/ROOT:/usr/local/tomcat/webapps/ROOT \
 -v /opt/docker/tomcat/web-a/logs:/usr/local/tomcat/logs \
 tomcat:8.0.51-jre8-slim
 docker run  -d  -p 8081:8080 --net=viemall --name=viemall-tomcat-b --restart=always  -e TZ="Asia/Shanghai" \
 -v /opt/docker/tomcat/web-b/ROOT:/usr/local/tomcat/webapps/ROOT \
 -v /opt/docker/tomcat/web-b/logs:/usr/local/tomcat/logs \
 tomcat:8.0.51-jre8-slim

3.启动Nginx

docker run  -d  -p 80:80 --net=viemall --name=viemall-nginx --restart=always  -e TZ="Asia/Shanghai" \
  -v /opt/docker/nginx/conf/nginx.conf:/etc/nginx/nginx.conf \
  -v /opt/docker/nginx/logs:/var/log/nginx \
  -v /opt/docker/nginx/html:/usr/share/nginx/html \
 nginx:1.14-alpine

测试访问:

  http://192.168.1.102/


  image.pngimage.png


方式二: docker compose

   docker composedocker编排服务的一部分,可以让用户在其他平台上快速的安装docker,一键就部署成功了;

  1.安装docker compose

       参考文章:   http://www.dczou.com/viemall/808.html

  2.docker compose官方文档:

        https://docs.docker.com/compose/compose-file/

 3. docker compose文件编写:

version: '3'
services:
  nginx:
    image: nginx:1.14-alpine 
    ports:
      - "80:80"
    container_name: viemall-nginx
    restart: always
    volumes:
      - /opt/docker/nginx/conf/nginx.conf:/etc/nginx/nginx.conf
      - /opt/docker/nginx/html:/usr/share/nginx/html
      - /opt/docker/nginx/logs:/var/log/nginx
    environment:
      TZ: Asia/Shanghai
    depends_on:
      - viemall-tomcat-A
      - viemall-tomcat-B
    networks:
      - "viemall-net"
  viemall-tomcat-A:
    image: tomcat:8.0.51-jre8-slim
    container_name: viemall-tomcat-a
    ports:
      - "8080:8080"
    restart: always
    volumes:
      - /opt/docker/tomcat/web-a/ROOT:/usr/local/tomcat/webapps/ROOT
      - /opt/docker/tomcat/web-a/logs:/usr/local/tomcat/logs
    environment:
      TZ: Asia/Shanghai
      JAVA_OPTS: -Djava.security.egd=file:/dev/./urandom
    networks:
      - "viemall-net"
  viemall-tomcat-B:
    image: tomcat:8.0.51-jre8-slim
    container_name: viemall-tomcat-b
    ports:
      - "8081:8080"
    restart: always
    volumes:
      - /opt/docker/tomcat/web-b/ROOT:/usr/local/tomcat/webapps/ROOT
      - /opt/docker/tomcat/web-b/logs:/usr/local/tomcat/logs
    environment:
      TZ: Asia/Shanghai
      JAVA_OPTS: -Djava.security.egd=file:/dev/./urandom
    networks:
      - "viemall-net"
networks:
  viemall-net:
    driver: bridge

 4. docker compose 服务启动;

     docker-compose up -d


   查看compose启动的各个容器的状态:

docker-compose ps

   进入某个容器:

docker-compose exec viemall-tomcat-a bash

   退出某个容器

exit

   停止 docker compose启动的容器: 停止运行的容器而不删除它们,它们可以使用命令docker-compose start重新启动起来

docker-compose stop

 卸载docker compose启动的容器:

docker-compose down

深入浅出Docker技术- docker-compose的两种方式



这里简单介绍下两种安装docker-compose的方式:
第一种方式相对简单,但是由于网络问题,常常安装不上,并且经常会断开,
第二种方式略微麻烦,但是安装过程比较稳定
方法一:
#  sudo curl -L https://github.com/docker/compose/releases/download/1.21.0/docker-compose-$(uname -s)-$(uname -m) -o /usr/local/bin/docker-compose
# chmod +x /usr/local/bin/docker-compose  
#docker-compose --version

    但是此方法会经常因为网络的原因而无法安装

  方法二: pip安装方式:

     wget https://files.pythonhosted.org/packages/ae/e8/2340d46ecadb1692a1e455f13f75e596d4eab3d11a57446f08259dee8f02/pip-10.0.1.tar.gz
    tar -xvf pip-10.0.1.tar.gz
    cd pip-10.0.1/
    sudo python setup.py install
    sudo pip install docker-compose
    ln -s /usr/bin/docker-compose /usr/local/bin/docker-compose

深入浅出Docker技术- Docker 构建Tomcat DockerFile 镜像;

     前文已经讲述了基于公共的Tomcat 镜像来部署WEB应用,并且也简单的讲述了DockerFile 的语法应用;但是公共的镜像都是官方通用的或者是其他公司或者个人PUSH上去的,在项目开发过程中也未必符合,比如官方的JDK镜像是有时区问题,因为是外国佬的镜像基础;

   <<Docker下的web开发和Tomcat部署>>

  <<深入浅出Docker技术-Dockerfile详解>>

 下面将讲述基于DockerFile 来构建基于自己的Tomcat 镜像

FROM docker.io/java:8u111-jdk-alpine
 
MAINTAINER Tony <1174616612@qq.com>

ENV TOMCAT_VERSION=8.0.52 \
	TZ=Asia/Shanghai #解决时差问题

#安装tomcat	
COPY apache-tomcat-${TOMCAT_VERSION}.tar.gz /usr/local/
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone  \
	&& cd /usr/local  \	
    && tar xzf apache-tomcat-${TOMCAT_VERSION}.tar.gz  \
	&& rm apache-tomcat-${TOMCAT_VERSION}.tar.gz  \
	&& mv apache-tomcat-${TOMCAT_VERSION} tomcat  

WORKDIR /usr/local/tomcat
EXPOSE 8080 
CMD ["./bin/catalina.sh", "run"]

  image.png

   构建命令:

    docker build tomcat:v1 .

   构建完毕后,就可以形成公司公用镜像,统一公司内部使用了;


深入浅出Docker技术-基于harbor搭建企业docker 镜像仓库

    上章讲述了《docker私有库搭建过程(Registry)》 ,开源Docker Registry的有很多的不足,所以采用现在比较主流的 harbor作为Docker 镜像仓库搭建;

1. 背景

      docker中要使用镜像,一般会从本地、docker Hup公共仓库和其它第三方公共仓库中下载镜像,一般出于安全和外网(墙)资源下载速率的原因考虑企业级上不会轻易使用。那么有没有一种办法可以存储自己的镜像又有安全认证的仓库呢? 

         —-> 企业级环境中基于Harbor搭建自己的安全认证仓库。

        Harbor是VMware公司最近开源的企业级Docker Registry项目, 其目标是帮助用户迅速搭建一个企业级的Docker registry服务。

2.安装: Harbor

     harbor需要安装docker和docker-compose才能使用,安装docker步骤省略,

    

    docker-dompose安装步骤如下: 

    下载最新版的docker-compose文件 

      curl -L https://github.com/docker/compose/releases/download/1.21.0/docker-compose-$(uname -s)-$(uname -m) -o /usr/local/bin/docker-compose 

    添加可执行权限 

        chmod +x /usr/local/bin/docker-compose

     查看 docker-compose版本

         docker-compose –version 

   


 下载harbor离线包

      wget https://storage.googleapis.com/harbor-releases/release-1.4.0/harbor-offline-installer-v1.4.0.tgz

  安装步骤:

[root@reg ~]# tar xvf harbor-offline-installer-v1.4.0.tgz

   1.  [root@reg ~]# cd /opt/harbor/ 

   2.  [root@reg harbor]# ls 

        common  docker-compose.yml  harbor.0.5.0.tgz  harbor.cfg  install.sh  LICENSE  NOTICE  prepare 

   3.配置harbor.cfg文件:

1.  hostname= reg.docker.tb     #本机外网IP或域名,该地址供用户通过UI进行访问,不要使用127.0.0.1 
2.  ui_url_protocal = http     #配置访问协议,默认为http;如果要启动SSl认证为:https 
3.  email_server = smtp.qq.com #配置邮件服务器地址 
4.  email_server_port =25      #邮件服务器端口 
5.  email_username= xxxxx      #用户 
6.  email_password = xxxx      #密码 
7.  email_from = admin@simpletour.com   #发件人地址 
8.  email_ssl = false          #是否进行ssl加密  
9.  harbor_admin_password = Harbor12345     #harbor admin的初始密码 
10. auth_mode = db_auth   #harbor认证模式,默认为db_auth,本地mysql,也可以配置ldap认证 
11. #ldap认证方式 
12. ldap_url= ldaps://ldap.mydomain.com 
13. ldap_basedn = ou=people,dc=mydomain,dc=com 
14. ldap_uid = uid 
15. ldap_scope = 3 
16.  
17. #数据库密码 
18. db_password = root123  #默认不需要修改
19. self_registration = on 
20. use_compressed_js = on 
21. max_job_workers = 3         #最大工作进 
22. token_expiration = 30       #token过期时间,默认为30分钟 
23. verify_remote_cert = on 
24. customize_crt = on 
25.  
26. #显示的认证及组织信息 
27. crt_country = CN 
28. crt_state = State 
29. crt_location = CN 
30. crt_organization = organization 
31. crt_organizationalunit = organizational unit 
32. crt_commonname = example.com 
33. crt_email = example@example.com  
34.  
35. #可选的https证书配置地址 
36. ssl_cert = /root/cert/reg.docker.tb.crt 
37. ssl_cert_key = /root/cert/reg.docker.tb.key

    #上面启动了SSL认证,所以要创建一个CA证书[实验环境自己创建,线上环境购买就好]

    如果不使用HTTPS 直接修改hostname 和harbor_admin_password就行了; 如果详细修改,可以在安装完毕后,控制台修改配置;

执行./prepare命令

执行./install.sh 命令;

image.png

   注:默认执行上面安装后会自动启动服务;

    安装出现的小问题: 80端口被暂用;

image.png

    因为:harbor使用的是80端口,

   在公网上,一般情况下都不暴露默认端口,避免被攻击!

   以下修改harbor的默认80端口为其他端口!

   这里有一篇文章: 不详细讲述;

       https://www.cnblogs.com/huangjc/p/6420355.html

image.png

image.png

安装成功;;  访问你设置的域名地址;

image.png

如果想重启:harbor

  cd  /opt/program/tools/harbor

docker-compose stop
./install.sh

客户端使用测试 [ docker 机 ] 

  * 通过 admin 账户登陆创建 test 用户

  * 退出 admin 账户登陆 test 用户

1. 新建项目 
     我们新建一个名称为
test的项目,设置不公开。注意:当项目设为公开后,任何人都有此项目下镜像的读权限。命令行用户不需要“docker login”就可以拉取此项目下的镜像。

image.png

  新建项目完毕后,我们就可以用test账户提交本地镜像到Harbor仓库了。例如我们提交本地nginx镜像:

2. 客户端测试:

1、admin登录

    $ docker login xx.xx.63.76

     Username: admin

      Password:

      Login Succeeded

image.png

报这个错误的都是如下2个原因:

   1、是端口错了!

   2、未在docker启动文件中添加–insecure-registry信任关系!

大多数这个错误是第2个原因,因为你没有添加信任关系的话,docker默认使用的是https协议,所以端口不对(443),会报连接拒绝这个错误;

或者提示你 "服务器给HTTPS端的是HTTP响应" 这个错误,因为你没添加端口信任,服务器认为这是默认的https访问,返回的却是http数据! 

       十分抱歉,因为我文章演示是http 方式的验证模式,SO…..;

 

解决方法:

正确的添加信任关系包括端口号:

–insecure-registry=xx.xxx.103.99

一定要把主机与端口同时添加进去!

 记住,这是harbor镜像仓库,而不是单纯的registry容器仓库!

1、在你的机器上修改文件 /etc/sysconfig/docker ,添加(或修改)字段:

    vim /usr/lib/systemd/system/docker.service

 ExecStart=/usr/bin/dockerd --insecure-registry=192.168.x.x

  修改完毕重启 docker

   service docker restart   或者  systemctl restart docker.service  

3. push 镜像到仓库

  如下:

   给镜像打tag

   docker tag your-docker-image 172.20.30.35:10080/your-repository-name/your-docker-image:tag  

   注意,your-repository-name 不能少,否则会报错:权限验证失败


  从本地PUSH 镜像到仓库

    docker push  172.20.30.35:10080/your-repository-name/your-docker-image:tag


  从仓库中 pull(拉) 镜像

  docker pull your-repository-name/your-docker-image:tag  

  或者

   docker pull 172.20.30.35:10080/your-repository-name/your-docker-image:tag  

docker私有库搭建过程(Registry)

      我们知道可以使用hub.docker.com作为我们公共或者私有的registry。但由于服务器在国外的原因,网速会非常的慢。
所以我们在利用docker开发构建容器服务时,我们希望能够建立自己的私有registry,上传镜像值我们的私有
registry中心,然后在其他物理机上部署的时候,可以快速的pull,然后实现大规模的分发以及部署,提高效率。

1、安装并运行registry

安装:

  [root@iZm5e61wb7816uew9egnr2Z ~]  docker pull  docker.io/registry

运行:

   docker run -d -p 5000:5000 -v /opt/program/data/registry:/var/lib/registry docker.io/registry

-d后台运行

-p指定端口

-v把registry的镜像路径/var/lib/registry映射到本机的/opt/program/data/registry

检查5000端口

      netstat -an | grep 5000

[root@docker01 ~]# netstat -an | grep 5000

image.png

      telnet 127.0.0.1 5000成功。

image.png

2、添加tag标记

       docker tag : 标记本地镜像,将其归入某一仓库。

docker tag [OPTIONS] IMAGE[:TAG] [REGISTRYHOST/][USERNAME/]NAME[:TAG]

 如: 将镜像ubuntu:15.10标记为 runoob/ubuntu:v3 镜像。

     root@runoob:~# docker tag ubuntu:15.10 runoob/ubuntu:v3

image.png

      docker tag csphere/wordpress:4.2 172.31.228.145:5000/csphere/wordpress:0.0.1

image.png

3、上传镜像到本地私有仓库中:

          docker push 10.100.50.120:5000/busybox


开源Docker Registry的有很多的不足:

   用户与鉴权 : 可以基于htpasswd文件进行简单的用户管理,但是维护不便,也没有对外的API可供集成。

   缺少日志与审记 : 没有日志收集能力,也缺少审记。

   缺少图形化的管理界面;

   所以后续我们会采用Harbor 作为企业级的私有仓库作为镜像管理.

 

 

Docker下的web开发和Tomcat部署

本章实践的主要目标是开发一个简单的web应用,打包部署到Docker的tomcat容器中去; 并且使用Mavan的自动部署到Docker 完成简单的自动化部署,完成后实现镜像文件打包到hub.docker.com;

  第一步:安装tomcat8 镜像;

查询一下tomcat8镜像文件:

docker search tomcat8

docker pull tomcat:8.0.51

image.png

来快速体验一下镜像的效果,执行命令:

    docker run -it –rm -p 8888:8080 tomcat:8.0.51

     rm参数表示container结束时,Docker会自动清理其所产生的数据。

  可以看到tomcat启动的日志全部打印在终端了,

image.png

    因为我们用-p 8888:8080将容器的8080端口映射到当前电脑的8888端口,所以打开当前电脑的浏览器,输入:localhost:8888,可以看到熟悉的   Tomcat页面:

http://192.168.1.102:8888/

   接下来我们开发一个最简单的spring mvc应用,然后部署到docker的tomcat容器中试试,创建maven工程:

 image.png

      通过mavean 建立简单的WEB项目;

      现在我们把文件部署到tomcat上去,先建一个目录,例如我建了这个目录:/usr/docker/server/tomcat_1,然后把docker-0.0.1-SNAPSHOT.war 文件复制到这个目录下,再在控制台执行以下命令:

image.png

再在控制台执行以下命令:

     docker run –name docker-tomcat -p 8888:8080 -d -v /usr/docker/server/tomcat:/usr/local/tomcat/webapps tomcat:8.0.51

这时候再打开浏览器,输入

      http://192.168.1.102:8888/docker-0.0.1-SNAPSHOT

image.png

       此时,我们今天测试tomcat部署的目的已经达到了,接下来再试试提交镜像,在容器中输入exit 退出容器,再执行”docker stop docker-tomcat”停止容器,然后执行以下命令把容器作为镜像保存在本地:

   docker commit -a "dockertony1" -m "from tomcat 8.0.51,with a demo webapp"  docker-tomcat dockertony1/docker-tomcat:0.0.1

  docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]

OPTIONS说明:

         -a :提交的镜像作者;

       -c :使用Dockerfile指令来创建镜像;

        -m :提交时的说明文字;

  执行完毕后,输入docker images,可以看到新增的镜像:

image.png

       接下来我们试着把本地镜像提交到hub.docker.com去(前提是已经在这个网站上注册过),输入命令docker login,接下来按照提示输入用户名和密码,执行一下命令提交镜像:

image.png

     docker push  dockertony1/docker-tomcat:0.0.1

     有点费时,需要等待:

image.png

在执行过程中, tag的名字斜线前面部分dockertony1 不是本人的用户名,会出现一下问题:

    denied: requested access to the resource is denied : docker

  在上传过程中可能会遇到"net/http: TLS handshake timeout":没办法,因为国外仓库速度较慢,则会报错“net/http: TLS handshake timeout”。

     解决方式如下:

         https://www.cnblogs.com/wozixiaoyao/p/6059780.html

但是我的网络一致不行;一般公司都会搭建自己的私有镜像仓库,后续会补上;

等上传成功后,再去hub.docker.com上看看吧,就可以在自己的仓库下面已经可以看到刚刚提交的镜像了:

使用maven插件自动部署web应用至Docker容器的tomcat

配置maven

在pom.xml加入

<build>
      <plugins>
        <plugin>
           <groupId>org.apache.tomcat.maven</groupId>
           <artifactId>tomcat8-maven-plugin</artifactId>
           <version>2.2</version>
           <configuration>
              <url>http://192.168.1.102:8888/manager/text</url>
              <server>tomcat</server>
              <username>deploy</username>
              <password>deploy</password>
           </configuration>
        </plugin>
      </plugins>
   </build>

在maven的settings.xml加入

 <server>
      <id>tomcat</id>
      <username>deploy</username>
      <password>deploy</password>
  </server>

在tomcat-users.xml加入以下内容

<role rolename="manager-gui"/>
<role rolename="manager-script"/>
<user username="deploy" password="deploy" roles="manager-gui, manager-script"/>

容器的文件是不能修改的,所以我才用虚拟卷的形式来修改tomcat文件;

docker run --name docker-tomcat -p 8888:8080 -d -v /usr/docker/server/tomcat:/usr/local/tomcat/webapps -v /usr/docker/server/tomcat-users.xml:/usr/local/tomcat/conf/tomcat-users.xml tomcat:8.0.51
 将web应用部署至容器里运行的tomcat
mvn tomcat7:deploy 

image.png

可能会出现的问题:
 在maven dploy :

Cannot invoke Tomcat manager: Connection reset by peer:

这是因为tomcat 容器上了manager 项目,把这个加上去就行了

基于Solr空间搜索

概括:

     最近一个项目需要基于LBS查询附近的商铺信息,看了一下网上都是基于Solr和ELS方式来实现, 本来想使用ELS来实现的,但是由于项目以前用的是Solr, 所以就去调研了一下基于Solr来实现地理位置的搜索,并且在实现的时候整理了一下实现的笔记。       

    在开发中如果需要对带经纬度的数据进行检索,比如查找当前所在位置附近1000米的,一种简单的方法就是:获取数据库中的所有酒店数据,按经纬度计算距离,返回距离小于1000米的数据。

   这种方式在数据量小的时候比较有效,但是当数据量大的时候,检索的效率是会十分十分低的,本文介绍使用Solr的Spatial Query进行空间搜索。本文简述基于Solr实现空间范围搜索

基于Mysql 实现地理位置排序:

例如:

公式如下,单位米:

第一点经纬度:lng1 lat1

第二点经纬度:lng2 lat2

      round(6378.138*2*asin(sqrt(pow(sin( (lat1*pi()/180-lat2*pi()/180)/2),2)+cos(lat1*pi()/180)*cos(lat2*pi()/180)* pow(sin( (lng1*pi()/180-lng2*pi()/180)/2),2)))*1000) 

例如:

    SELECT    store_id,lng,lat, ROUND(6378.138*2*ASIN(SQRT(POW(SIN((22.299439*PI()/180-      lat*PI()/180)/2),2)+COS(22.299439*PI()/180)*COS(lat*PI()/180)*POW(SIN((114.173881*PI()/180-lng*PI()/180)/2),2)))*1000) AS juli  FROM store_info

ORDER BY juli DESC
LIMIT 316

基于Solr实现地理位置排序:

没学过的Solr 这里有一份官网手册: 

 http://lucene.apache.org/solr/guide/6_6/spatial-search.html

原理:

   具体的空间搜索原理和算法不多介绍,空间搜索算法都是很成熟的

     https://www.cnblogs.com/hanhuibing/articles/5680616.html

    https://www.cnblogs.com/keleyu/p/4993039.html

基于Solr的空间搜索实战:

   Solr已经提供了3种filedType来进行空间搜索:

  有四种可用于空间搜索的主要字段类型:

LatLonPointSpatialField

LatLonType (现已弃用)及其非大地双胞胎 PointType

SpatialRecursivePrefixTreeFieldType(简称RPT),包括RptWithGeometrySpatialField衍生产品

BBoxField


LatLonPointSpatialField是经纬度点数据最常见使用情况的理想字段类型。它取代了为了向后兼容而仍然存在的LatLonType。RPT提供了更多高级/自定义用例以及诸如多边形和热图等选项的更多功能。

RptWithGeometrySpatialField用于索引和搜索非点数据,尽管它也可以做点。它不能做排序/提升。

BBoxField 用于索引边界框,通过框查询,指定搜索谓词(相交,内部,包含,不相交,等于)以及相关性排序/提升类似overlapRatio或简单区域。

LatLonPointSpatialField  

  <fieldType name="location" class="solr.LatLonPointSpatialField" docValues="true"/>

   Indexing Points使用(x y) 或者(lat,lon) 格式进行保存;不然会报错

image.png

   用于地理空间搜索的空间Solr“查询分析器”:geofilt和bbox

geofilt

    该geofilt过滤器允许您根据给定点的地理空间距离(也称为“大圆距离”)检索结果。另一种看待它的方式是创建一个圆形滤镜。例如,要查找给定纬度/经度点五公里内的所有文件,可以输入。该过滤器返回给定半径的圆周内的所有结果:

   &q=:&fq={!geofilt sfield=store}&pt=45.15,-93.85&d=5

   image.png

bbox

该bbox过滤器与geofilt使用计算的圆的边界框非常相似。请参阅下图中的蓝色框。它采用与geofilt相同的参数。

以下是一个示例查询:

  &q=:&fq={!bbox sfield=store}&pt=45.15,-93.85&d=5


 矩形形状的计算速度更快,所以它有时可以用来替代geofilt返回半径之外的点的可接受范围。但是,如果理想目标是一个圆圈,但您希望它运行得更快,那么请考虑使用RPT字段并尝试一个较大的distErrPct值0.1(如10%半径)。这将返回半径之外的结果,但它将在形状周围有点均匀。

image.png

   当边界框包含一个极点时,边界框最终成为一个“边界盆”(球冠),如果边界框接触北极(或最高纬度的南部,如果有),则该边界包括圆的最低纬度以北的所有值它接触南极)。

通过任意矩形进行过滤

     有时,空间搜索要求要求在矩形区域中查找所有内容,例如用户正在查看的地图所覆盖的区域。对于这种情况,geofilt和bbox不会剪切它。这是一个窍门,但是你可以使用Solr的范围查询语法来提供左下角作为范围的开始和右上角作为范围的结束。

这是一个例子:

   &q=:&fq=store:[45,-94 TO 46,-93]

优化:缓存与否

将空间查询放入“fq”参数中是最常见的过滤器查询。默认情况下,Solr会将查询缓存在过滤器缓存中。

如果您知道过滤器查询(无论是否为空间)是相当独特的,并且不太可能获得缓存命中,则指定cache="false"为本地参数,如以下示例中所示

     &q=…mykeywords…&fq=…someotherfilters…&fq={!geofilt cache=false}&sfield=store&pt=45.15,-93.85&d=5

距离排序或提升(函数查询)

有四个距离函数查询:

geodist,见下文,通常是最合适的;

dist计算多维向量之间的p范数距离;

hsin,计算球体上两点之间的距离;

sqedist,来计算两点之间的平方欧几里德距离。

有关这些功能的查询的详细信息,请参阅该部分功能查询


geodist

    geodist是一个距离函数,需要三个可选参数:(sfield,latitude,longitude)。您可以使用该geodist功能按距离或得分返回结果对结果进行排序。

例如,要按距离递增对结果进行排序,请输入。&q=:&fq={!geofilt}&sfield=store&pt=45.15,-93.85&d=50&sort=geodist() asc

      要将距离作为文档分数返回,请输入&q={!func}geodist()&sfield=store&pt=45.15,-93.85&sort=score+asc。

更多示例

以下是一些您可以在Solr中进行空间搜索的更有用的示例。

1) 按距离排序,距离越近排名越高,加上score=distance,其中distance是索引点到坐标点之间的弧度值,系统根据弧度值排序。

    &fl=*,score&sort=score asc&q={!geofilt score=distance sfield=poi_location_p pt=54.729696,-98.525391 d=10}。

2) 按距离排序,距离越远排名越高,加上score=reciDistance,其中reciDistance 范围是0~1 采用倒数的方式计算,故与distance的排序刚好相反

    &fl=*,score&sort=score asc&q={!geofilt score=reciDistance sfield=poi_location_p pt=54.729696,-98.525391 d=10}

3) 距离仅作排序不做过滤,在条件中设置filter=false,其中d只是确定形状的作用,不起限制作用。

     &fl=*,score&sort=score asc&q={!geofilt score=distance filter=false sfield=poi_location_p pt=54.729696,-98.525391 d=10}

4) 结合关键词查询和距离排序,此时关键字只能作为过滤条件(fq)不能作为查询条件,仅作为过滤域。

     &fl=*,score& fq=store_name:农业&sort=score asc&q={!geofilt score=distance sfield=poi_location_p pt=54.729696,-98.525391 d=10}

5) 当关键字和距离都作为排序条件时,可以用qf参数设置权重

    &fl=*,score& fq=store_name:农业&sort=score asc&q={!geofilt score=distance sfield=poi_location_p pt=54.729696,-98.525391

SpatialRecursivePrefixTreeFieldType:

本文重点介绍使用SpatialRecursivePrefixTreeFieldType,不仅可以用点,也可以用于多边形的查询。

1、配置Solr

    个人使用的solr6.6.2:   

<fieldType name="location_rpt"   class="solr.SpatialRecursivePrefixTreeFieldType"
               spatialContextFactory="org.locationtech.spatial4j.context.jts.JtsSpatialContextFactory"
               autoIndex="true"
               validationRule="repairBuffer0"
               distErrPct="0.025"
               maxDistErr="0.001"
               distanceUnits="kilometers"  />

<field name="station_position" type="location_rpt" indexed="true" stored="true" multiValued="false" />

对solr.SpatialRecursivePrefixTreeFieldType的配置说明:SpatialRecursivePrefixTreeFieldType

用于深度遍历前缀树的FieldType,主要用于获得基于Lucene中的RecursivePrefixTreeStrategy。

geo

默认为true,值为true的情况下坐标基于球面坐标系,采用Geohash的方式;值为false的情况下坐标基于2D平面的坐标系,采用Euclidean/Cartesian的方式。

distErrPct

    定义非Point图形的精度,范围在0-0.5之间。该值决定了非Point的图形索引或查询时的level(如geohash模式时就是geohash编码的长度)。当为0时取maxLevels,即精度最大,精度越大将花费更多的空间和时间去建索引。

maxDistErr/maxLevels:maxDistErr

      定义了索引数据的最高层maxLevels,上述定义为0.000009,根据 GeohashUtils.lookupHashLenForWidthHeight(0.000009, 0.000009)算出编码长度为11位,精度在1米左右,直接决定了Point索引的term数。maxLevels优先级高于maxDistErr, 即有maxLevels的话maxDistErr失效。详见SpatialPrefixTreeFactory.init()方法。不过一般使用 maxDistErr。

distanceUnits 单位是degrees(度) 

worldBounds
       世界坐标值:”minX minY maxX maxY”。 geo=true即geohash模式时,该值默认为”-180 -90 180 90”。geo=false即quad时,该值为Java double类型的正负边界,此时需要指定该值,设置成”-180 -90 180 90”。


实战代码:

public class SolrGEO {
 
       public static void main(String[] args) {
 
              /*SolrQuery solrQuery = new SolrQuery();
 
              solrQuery.set("d", value + ""); // 搜索半径
 
              solrQuery.set("sfield", CommonConst.LOCATION_FIELD);// 存储地理位置的字段名称
 
              solrQuery.set("pt", searchContent.getShopLocation()); // 经纬度参数格式为 纬度,经度
 
              solrQuery.set("fl", "*,distance:geodist()"); // 文档中用distance表示距离字段
 
              solrQuery.addSort("geodist()", ORDER.asc);// 按照从近到远排序
*/
              
              
              
              //addIndex();
              queryIndex();
              
       }
       
       public static void queryIndex() {
              try {
                     HttpSolrClient server = SolrServer.getServer();
                     
                     SolrQuery params=new SolrQuery();   
                  /*params.setParam("q", "{!geofilt score=distance filter=false sfield=station_position pt=23.104487,113.375981 d=1}");
                  params.addSort("score",ORDER.asc);*/
                  
                     params.setParam("q","*:*");
                     params.setParam("start", "0");//记录开始位置    
               params.setParam("rows", "10");//查询的行数    
               params.set("fq", "{!geofilt}");           //距离过滤函数
               params.set("pt", "23.104487,113.375981"); //当前经纬度
               params.set("sfield", "station_position"); //经纬度的字段
               params.set("d", "1"); //就近 d 20km的所有数据
               params.set("score", "distance"); 
               params.addSort("geodist()",ORDER.asc);//按照从近到远排序
             
               /**
                * geofilt :  //距离过滤函数    “大圆距离”)检索结果
                * sfield : 坐标字段
                * d :  空间范围 公里 默认使用单位为千米(1km=0.009度)
                */
             /*  String query=" {!geofilt score=distance sfield=station_position  d=1 }  ";  
               params.addFilterQuery(query);   */  
               
               QueryResponse resp = server.query(params);   
               
               System.out.println(params.toQueryString());
               
               SolrDocumentList docs = resp.getResults();    
               for(int i=0;i<docs.size();i++){    
                   SolrDocument sid=docs.get(i);  
                   
                            String title = (String) sid.getFieldValue("title");
                            System.out.println(title);
                   String station_position= (String) sid.getFieldValue("station_position");
                   String [] positions=station_position.split(",");
                   double lat2=  Double.valueOf(positions[0]);  
                double lng2 = Double.valueOf(positions[1]);
                double range=getDistance(23.104487d,113.375981d,lat2,lng2);
                System.out.println("与当前坐标距离:"+range);
                   System.out.println(sid);    
               }    
              } catch (Exception e) {
                     e.printStackTrace();
              }
              
       }
 
   private static double EARTH_RADIUS = 6378.137;    
    
    private static double rad(double d) {    
        return d * Math.PI / 180.0;    
    }   
    
         /**   
     * 通过经纬度获取距离(单位:米)   
     * @param lat1   
     * @param lng1   
     * @param lat2   
     * @param lng2   
     * @return   
     */    
    public static double getDistance(double lat1, double lng1, double lat2,    
                                     double lng2) {    
        double radLat1 = rad(lat1);    
        double radLat2 = rad(lat2);    
        double a = radLat1 - radLat2;    
        double b = rad(lng1) - rad(lng2);    
        double s = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a / 2), 2)    
                + Math.cos(radLat1) * Math.cos(radLat2)    
                * Math.pow(Math.sin(b / 2), 2)));    
        s = s * EARTH_RADIUS;    
        s = Math.round(s * 10000d) / 10000d;    
        s = s*1000;    
        return s;    
    } 
    
       
 
       /**
        * 添加索引,准备测试数据
        */
       public static void addIndex() {
              HttpSolrClient server = SolrServer.getServer();
              
              /**
               * 删除旧索引
               */
              try {
                     server.deleteByQuery("*:*");
              } catch (SolrServerException e1) {
              } catch (IOException e1) {
                     e1.printStackTrace();
              }
              
              
              
              List<ProductBO> beans = new ArrayList<ProductBO>();
              // 家电类
              ProductBO dianshiBO1 = new ProductBO();
              dianshiBO1.setId("1001");
              dianshiBO1.setTitle("海尔模卡(MOOKA) 65K5 65英寸安卓智能网络窄边框全高清LED液晶电视");
              dianshiBO1.setMajor_s("家用电器");
              dianshiBO1.setSubMajor_s("电视");
              dianshiBO1.setBrand_s("海尔");
              dianshiBO1.setModel_s("65K5");
              dianshiBO1.setPrice_i(5400);
              dianshiBO1.setStation_address("广东省广州市海珠区新港东路1068号");
              //lat,lon
              dianshiBO1.setStation_position("23.104487,113.375981");
              
              beans.add(dianshiBO1);
              
              ProductBO dianshiBO2 = new ProductBO();
              dianshiBO2.setId("1002");
              dianshiBO2.setTitle("三星(SAMSUNG)UA55JU5900JXXZ 55英寸 4K超高清智能 LED液晶电视 黑色");
              dianshiBO2.setMajor_s("家用电器");
              dianshiBO2.setSubMajor_s("电视");
              dianshiBO2.setBrand_s("三星");
              dianshiBO2.setModel_s("UA55JU5900");
              dianshiBO2.setPrice_i(6400);
              dianshiBO2.setStation_address("广东省广州市海珠区凤浦中路741号");
              dianshiBO2.setStation_position("23.103373,113.374265");
              
              beans.add(dianshiBO2);
              
              dianshiBO2 = new ProductBO();
              dianshiBO2.setId("1002");
              dianshiBO2.setTitle("三星(SAMSUNG)UA55JU5900JXXZ 55英寸 4K超高清智能 LED液晶电视 黑色");
              dianshiBO2.setMajor_s("家用电器");
              dianshiBO2.setSubMajor_s("电视");
              dianshiBO2.setBrand_s("三星");
              dianshiBO2.setModel_s("UA55JU5900");
              dianshiBO2.setPrice_i(6400);
              dianshiBO2.setStation_address("广东省广州市海珠区凤浦中路741号");
              dianshiBO2.setStation_position("23.101811,113.376673");
              
              beans.add(dianshiBO2);
 
              ProductBO kongtiaoBO1 = new ProductBO();
              kongtiaoBO1.setId("2001");
              kongtiaoBO1.setTitle("格力(GREE) 大1匹 变频 Q铂 壁挂式冷暖空调 KFR-26GW/(26596)FNAa-A3");
              kongtiaoBO1.setMajor_s("家用电器");
              kongtiaoBO1.setSubMajor_s("空调");
              kongtiaoBO1.setBrand_s("格力");
              kongtiaoBO1.setModel_s("KFR-26GW/(26596)FNAa-A3");
              kongtiaoBO1.setPrice_i(7700);
              kongtiaoBO1.setStation_address("广东省广州市海珠区会展南五路");
              kongtiaoBO1.setStation_position("23.102359,113.377374");
              beans.add(kongtiaoBO1);
              
              ProductBO kongtiaoBO2 = new ProductBO();
              kongtiaoBO2.setId("2002");
              kongtiaoBO2.setTitle("奥克斯(AUX)正1.5匹 冷暖 定速 隐藏式显示屏 壁挂式 空调 KFR-35GW/HFJ+3");
              kongtiaoBO2.setMajor_s("家用电器");
              kongtiaoBO2.setSubMajor_s("空调");
              kongtiaoBO2.setBrand_s("奥克斯");
              kongtiaoBO2.setModel_s("KFR-35GW/HFJ+3");
              kongtiaoBO2.setPrice_i(6600);
              kongtiaoBO2.setStation_address("广东省广州市海珠区新港东路1070号");
              kongtiaoBO2.setStation_position("23.103265,113.376718");
              beans.add(kongtiaoBO2);
              
              
              ProductBO kongtiaoBO3 = new ProductBO();
              kongtiaoBO3.setId("2003");
              kongtiaoBO3.setTitle("海尔(Haier)1.5匹 变频 静音空调 冷暖 壁挂式空调 KFR-35GW/01JDA23A");
              kongtiaoBO3.setMajor_s("家用电器");
              kongtiaoBO3.setSubMajor_s("空调");
              kongtiaoBO3.setBrand_s("海尔");
              kongtiaoBO3.setModel_s("KFR-35GW/01JDA23A");
              kongtiaoBO3.setPrice_i(9600);
              kongtiaoBO3.setStation_address("广东省广州市海珠区AAAAAAA");
              kongtiaoBO3.setStation_position("23.102667,113.376511");
              beans.add(kongtiaoBO3);
              
              
              ProductBO kongtiaoBO4 = new ProductBO();
              kongtiaoBO4.setId("2004");
              kongtiaoBO4.setTitle("广东省广州市天河区车陂,天园,棠下");
              kongtiaoBO4.setMajor_s("家用电器");
              kongtiaoBO4.setSubMajor_s("空调");
              kongtiaoBO4.setBrand_s("海尔");
              kongtiaoBO4.setModel_s("KFR-35GW/01JDA23A");
              kongtiaoBO4.setPrice_i(9600);
              kongtiaoBO4.setStation_address("广东省广州市天河区车陂,天园,棠下");
              kongtiaoBO4.setStation_position("23.125202,113.390858");
              beans.add(kongtiaoBO4);
 
              
              
              ProductBO kongtiaoBO5 = new ProductBO();
              kongtiaoBO5.setId("2005");
              kongtiaoBO5.setTitle("广东省广州市天河区科新路 棠下,天园,天河公园");
              kongtiaoBO5.setMajor_s("家用电器");
              kongtiaoBO5.setSubMajor_s("空调");
              kongtiaoBO5.setBrand_s("海尔");
              kongtiaoBO5.setModel_s("KFR-35GW/01JDA23A");
              kongtiaoBO5.setPrice_i(9600);
              kongtiaoBO5.setStation_address("广东省广州市天河区科新路 棠下,天园,天河公园");
              kongtiaoBO5.setStation_position("23.126299,113.387848");
              beans.add(kongtiaoBO5);
              
              ProductBO kongtiaoBO6 = new ProductBO();
              kongtiaoBO6.setId("2006");
              kongtiaoBO6.setTitle("广东省广州市海珠区阅江中路686");
              kongtiaoBO6.setMajor_s("家用电器");
              kongtiaoBO6.setSubMajor_s("空调");
              kongtiaoBO6.setBrand_s("海尔");
              kongtiaoBO6.setModel_s("KFR-35GW/01JDA23A");
              kongtiaoBO6.setPrice_i(9600);
              kongtiaoBO6.setStation_address("广东省广州市海珠区阅江中路686");
              kongtiaoBO6.setStation_position("23.10929,113.377472");
              beans.add(kongtiaoBO6);
              
           kongtiaoBO6 = new ProductBO();
              kongtiaoBO6.setId("2007");
              kongtiaoBO6.setTitle("广东省广州市海珠区凤浦中路681号");
              kongtiaoBO6.setMajor_s("家用电器");
              kongtiaoBO6.setSubMajor_s("空调");
              kongtiaoBO6.setBrand_s("海尔");
              kongtiaoBO6.setModel_s("KFR-35GW/01JDA23A");
              kongtiaoBO6.setPrice_i(9600);
              kongtiaoBO6.setStation_address("广东省广州市海珠区凤浦中路681号");
              kongtiaoBO6.setStation_position("23.101877,113.370349");
              beans.add(kongtiaoBO6);
              
           kongtiaoBO6 = new ProductBO();
              kongtiaoBO6.setId("2008");
              kongtiaoBO6.setTitle("广东省广州市海珠区会展南三路");
              kongtiaoBO6.setMajor_s("家用电器");
              kongtiaoBO6.setSubMajor_s("空调");
              kongtiaoBO6.setBrand_s("海尔");
              kongtiaoBO6.setModel_s("KFR-35GW/01JDA23A");
              kongtiaoBO6.setPrice_i(9600);
              kongtiaoBO6.setStation_address("广东省广州市海珠区会展南三路");
              kongtiaoBO6.setStation_position("23.10319,113.370088");
              beans.add(kongtiaoBO6);
              
              try {
                     server.addBeans(beans);
                     server.commit();
                     System.out.println("提交完毕!");
              } catch (Exception e) {
                     e.printStackTrace();
              }
       }
 
}
 
 
/**
 * 
 * @author Tony
 *
 */
public class SolrServer {
 
       private static SolrServer solrServer = null;
       private static HttpSolrClient server = null;
 
       private static String url = "http://localhost:8881/solr/test";
 
       public static synchronized SolrServer getInstance() {
              if (solrServer == null) {
                     solrServer = new SolrServer();
              }
              return solrServer;
       }
 
       public static HttpSolrClient getServer() {
              if (server == null) {
                     server = new HttpSolrClient.Builder(url).build();
                     server.setDefaultMaxConnectionsPerHost(1000);
                     server.setMaxTotalConnections(10000);
                     server.setConnectionTimeout(60000);// 设置连接超时时间(单位毫秒) 1000
                     server.setSoTimeout(60000);//// 设置读数据超时时间(单位毫秒) 1000
                     server.setFollowRedirects(false);// 遵循从定向
                     server.setAllowCompression(true);// 允许压缩
              }
              return server;
       }
}

这里使用“经度 纬度”这样的字符串格式将经纬度索引到station_position字段

3、查询

查询语法示例:

q={!geofilt pt=45.15,-93.85 sfield=poi_location_p d=5 score=distance}

q={!bbox pt=45.15,-93.85 sfield=poi_location_p d=5 score=distance}

q=poi_location_p:"Intersects(-74.093 41.042 -69.347 44.558)" //a bounding box (not in WKT)

q=poi_location_p:"Intersects(POLYGON((-10 30, -40 40, -10 -20, 40 20, 0 0, -10 30)))" //a WKT example

涉及到的字段说明:

字段

含义

q

查询条件,如 q=poi_id:134567

fq

过滤条件,如 fq=store_name:农业

fl

返回字段,如fl=poi_id,store_name

pt

坐标点,如pt=54.729696,-98.525391

d

搜索半径,如 d=10表示10km范围内

sfield

指定坐标索引字段,如sfield=geo

defType

指定查询类型可以取 dismax和edismax,edismax支持boost函数相乘作用,dismax是通过累加方式计算最后的score.

qf

指定权重字段:qf=store_name^10+poi_location_p^5

score

排序字段根据qf定义的字段defType定义的方式计算得到score排序输出

其中有几种常见的Solr支持的几何操作:
WITHIN:在内部
CONTAINS:包含关系
DISJOINT:不相交
Intersects:相交(存在交集)

1)点查询

测试代码:查询距离某个点pt距离为d的集合

image.png

从这部分结果集中可以看出,数据是离目标点" 23.104487,113.375981 "最近的

多边形查询:

  JtsSpatialContextFactory

  当有Polygon多边形时会使用jts(需要把jts.jar放到solr webapp服务的lib下)。基本形状使用SpatialContext (spatial4j的类)。

   Jts下载:https://repo1.maven.org/maven2/com/vividsolutions/jts-core/

测试代码:

SolrQuery params = new SolrQuery();  

        //q=geo:"Intersects(POLYGON((-10 30, -40 40, -10 -20, 40 20, 0 0, -10 30)))"

        params.set("q", "station_position:\"Intersects(POLYGON((118 40, 118.5 40, 118.5 38, 118.3 35, 118 38,118 40)))\"");   

        params.set("start", "0");  //记录开始位置

        params.set("rows", "100");  //查询的行数

        params.set("fl", "*");

返回在这个POLYGON内的所有结果集。


管方文档:

    http://lucene.apache.org/solr/guide/6_6/spatial-search.html

 经纬度工具:

   http://www.hhlink.com/%E7%BB%8F%E7%BA%AC%E5%BA%A6

 百度地图API工具:

   http://lbsyun.baidu.com/index.php?title=jspopular







   

深入浅出Docker技术-Dockerfile详解

一、Dockerfile介绍

    Docker通过读取Dockerfile里面的内容可以自动build image,Dockerfile是一个包含了build过程中需要执行的所有命令的文本文件。也可以理解为Dockfile是一种被Docker程序解释的脚本,由一条一条的指令组成,每条指令对应Linux系统下面的一条命令,由Docker程序将这些Dockerfile指令翻译成真正的Linux命令。Dockerfile有自己书写格式和支持的命令,Docker程序解决这些命令间的依赖关系,类似于Makefile。

Docker程序将读取Dockerfile,根据指令生成定制的image。相比image这种黑盒子,Dockerfile这种显而易见的脚本更容易被使用者接受,它明确的表明image是怎么产生的。有了Dockerfile,当我们需要定制自己额外的需求时,只需在Dockerfile上添加或者修改指令,重新生成image即可,省去了敲命令的麻烦。

二、Dockerfile编写规则及指令说明

Dockerfile的指令是忽略大小写的,建议使用大写,使用#作为注释,每一行只支持一条指令,每条指令可以携带多个参数。

Dockerfile的指令根据作用可以分为两种:构建指令和设置指令。构建指令用于构建image,其指定的操作不会在运行image的容器上执行;设置指令用于设置image的属性,其指定的操作将在运行image的容器中执行。

  • FROM(指定基础image)

构建指令,必须指定且需要在Dockerfile其他指令的前面。后续的指令都依赖于该指令指定的image。FROM指令指定的基础image可以是官方远程仓库中的,也可以位于本地仓库。

该指令有两种格式:

1

FROM <image>

指定基础image为该image的最后修改的版本。或者:

1

FROM <image>:<tag>

指定基础image为该image的一个tag版本。

  • MAINTAINER(用来指定镜像创建者信息)

构建指令,用于将image的制作者相关的信息写入到image中。当我们对该image执行docker inspect命令时,输出中有相应的字段记录该信息。格式:

1

MAINTAINER <name>

  • RUN(执行命令)

构建指令,RUN可以运行任何被基础image支持的命令。如基础image选择了centos,那么软件管理部分只能使用centos的命令。该指令有两种格式:

1

2

RUN <command> (the command is run in a shell `/bin/sh c`)

RUN ["executable", "param1", "param2" ] (exec form)

  • CMD(设置容器启动时执行的操作)

设置指令,用于container启动时指定的操作。该操作可以是执行自定义脚本,也可以是执行系统命令。该指令只能在文件中存在一次,如果有多个,则只执行最后一条。该指令有三种格式:

1

2

CMD ["executable","param1","param2"] (like an exec, this is the preferred form)

CMD command param1 param2 (as a shell)

CMD主要用于container时启动指定的服务,当Docker run command的命令匹配到CMD command时,会替换CMD执行的命令。

当Dockerfile指定了ENTRYPOINT,那么使用下面的格式:

1

CMD ["param1","param2"] (as default parameters to ENTRYPOINT)

ENTRYPOINT指定的是一个可执行的脚本或者程序的路径,该指定的脚本或者程序将会以param1和param2作为参数执行。所以如果CMD指令使用上面的形式,那么Dockerfile中必须要有配套的ENTRYPOINT。

  • ENTRYPOINT(设置容器启动时执行的操作)

container启动时执行的命令,但是一个Dockerfile中只能有一条ENTRYPOINT命令,如果多条,则只执行最后一条。ENTRYPOINT没有CMD的可替换特性。两种格式:

1

2

ENTRYPOINT ["executable", "param1", "param2"] (like an exec, the preferred form)

ENTRYPOINT command param1 param2 (as a shell)

该指令的使用分为两种情况,一种是独自使用,另一种和CMD指令配合使用。

当独自使用时,如果你还使用了CMD命令且CMD是一个完整的可执行的命令,那么CMD指令和ENTRYPOINT会互相覆盖只有最后一个CMD或者ENTRYPOINT有效。

1

2

3

# CMD指令将不会被执行,只有ENTRYPOINT指令被执行;

CMD echo "Hello, World!"

ENTRYPOINT ls l

另一种用法和CMD指令配合使用来指定ENTRYPOINT的默认参数,这时CMD指令不是一个完整的可执行命令,仅仅是参数部分;ENTRYPOINT指令只能使用JSON方式指定执行命令,而不能指定参数。

1

2

3

FROM ubuntu

CMD ["-l"]

ENTRYPOINT ["/usr/bin/ls"]

  • USER(设置容器的用户)

设置指令,设置启动容器的用户,默认是root用户。

1

2

3

# 指定memcached的运行用户;

ENTRYPOINT ["memcached"]

USER daemon

1

ENTRYPOINT ["memcached", "-u", "daemon"]

  • EXPOSE(暴露容器端口)

EXPOSE可以用来暴露端口,或者在docker run时指定 expose=1234,这两种方式作用相同。但是, expose可以接受端口范围作为参数,比如 expose=20003000。但是,EXPOSE和 expose都不依赖于宿主机器。默认状态下,这些规则并不会使这些端口可以通过宿主机来访问。

基于EXPOSE指令的上述限制,Dockerfile的作者一般在包含EXPOSE规则时都只将其作为哪个端口提供哪个服务的提示。使用时,还要依赖于容器的操作人员进一步指定网络规则,需要配合 docker run p PORT:EXPORT使用,这样EXPOSE设置的端口号会被指定需要映射到宿主机器的端口,这时要确保宿主机器上的端口号没有被使用。如果直接指定 docker runp EXPORT,这样EXPOSE设置的端口号会被随机映射成宿主机器中的一个端口号。不过通过EXPOSE命令文档化端口的方式十分有用。

本质上说,EXPOSE或者 expose只是为其他命令提供所需信息的元数据(比如容器间link操作就依赖EXPOSE元数据),或者只是告诉容器操作人员有哪些已知选择。

格式:

1

EXPOSE <port> [<port>…]

EXPOSE指令可以一次设置多个端口号,相应的运行容器的时候,可以配套的多次使用-p选项。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

# 暴露一个端口;

EXPOSE port1

 

# 如果想代理EXPOSE端口, 相应的运行容器使用的命令;

docker run p port1 image

 

# 暴露多个端口;

EXPOSE port1 port2 port3

 

# 如果想代理EXPOSE端口, 相应的运行容器使用的命令;

docker run p port1 p port2 p port3 image

 

# 还可以指定需要映射到宿主机器上的某个端口号;

docker run p host_port1:port1 p host_port2:port2 p host_port3:port3 image

注意,EXPOSE仅仅是暴露一个端口,一个标识,在没有定义任何端口映射时,外部是无法访问到容器提供的服务。而端口映射(-p)是docker比较重要的一个功能,原因在于我们每次运行容器的时候容器的IP地址不能指定,而是在桥接网卡的地址范围内随机生成的。宿主机器的IP地址是固定的,我们可以将容器的端口的映射到宿主机器上的一个端口,免去每次访问容器中的某个服务时都要查看容器的IP的地址。对于一个运行的容器,可以使用docker port加上容器ID和EXPOSE暴露的端口来查看该端口号在宿主机器上的映射端口。

1

2

$ docker port redis 6379

0.0.0.0:6380

  • ENV(用于设置环境变量)

构建指令,在image中设置一个环境变量。格式:

1

ENV <key> <value>

设置了后,后续的RUN命令都可以使用,container启动后,可以通过docker inspect查看这个环境变量,也可以通过在docker run –env key=value时设置或修改环境变量。

假如你安装了JAVA程序,需要设置JAVA_HOME,那么可以在Dockerfile中这样写:

1

ENV JAVA_HOME /path/to/java/dirent

  • ADD(从src复制文件到container的dest路径)

构建指令,所有拷贝到container中的文件和文件夹权限为0755,uid和gid为0;如果是一个目录,那么会将该目录下的所有文件添加到container中,不包括目录;如果文件是可识别的压缩格式,则docker会帮忙解压缩(注意压缩格式);如果<src>是文件且<dest>中不使用斜杠结束,则会将<dest>视为文件,<src>的内容会写入<dest>;如果<src>是文件且<dest>中使用斜杠结束,则会<src>文件拷贝到<dest>目录下。
格式:

1

ADD <src> <dest>

<src>:是相对被构建的源目录的相对路径,可以是文件或目录的路径,也可以是一个远程的文件url。

<dest>:是container中的绝对路径。

  • VOLUME(指定挂载点)

设置指令,使容器中的一个目录具有持久化存储数据的功能,该目录可以被容器本身使用,也可以共享给其他容器使用。我们知道容器使用的是AUFS,这种文件系统不能持久化数据,当容器关闭后,所有的更改都会丢失。当容器中的应用有持久化数据的需求时可以在Dockerfile中使用该指令。格式:

1

VOLUME ["&lt;mountpoint&gt;"]

1

2

FROM base

VOLUME ["/tmp/data"]

运行通过该Dockerfile生成image的容器,/tmp/data目录中的数据在容器关闭后,里面的数据还存在。例如另一个容器也有持久化数据的需求,且想使用上面容器共享的/tmp/data目录,那么可以运行下面的命令启动一个容器:

1

$ docker run t i rm volumesfrom container1 image2 bash

container1为第一个容器的ID,image2为第二个容器运行image的名字。

  • WORKDIR(切换目录)

设置指令,可以多次切换(相当于cd命令),对RUN,CMD,ENTRYPOINT生效。格式:

1

WORKDIR /path/to/workdir

1

2

# 在/p1/p2下执行vim a.txt;

WORKDIR /p1 WORKDIR p2 RUN vim a.txt

  • ONBUILD(在子镜像中执行)

1

ONBUILD <Dockerfile关键字>

ONBUILD指定的命令在构建镜像时并不执行,而是在它的子镜像中执行。

最后,网上有哥们提供了一张通俗易懂的构建Dockerfile文件用到的指令先后逻辑顺序及其含义,还挺有意思。

Docker:使用Dockerfile构建Nginx镜像

这篇转载至:http://www.ywnds.com/?p=7611

基于数据库的分布式发号器-viemall-sequence

简述:

     在复杂分布式系统中,往往需要对大量的数据和消息进行唯一标识。公司的各种产品的系统中,数据日渐增长,对数据分库分表后需要有一个唯一ID来标识一条数据或消息,数据库的自增ID显然不能满足需求;特别一点的如订单、用户、优惠券也都需要有唯一ID做标识。此时一个能够生成全局唯一ID的系统是非常必要的。概括下来,那业务系统对ID号的要求有哪些呢?    

    1. 全局唯一性:不能出现重复的ID号,既然是唯一标识,这是最基本的要求。

    2.  趋势递增:在MySQL InnoDB引擎中使用的是聚集索引,由于多数RDBMS使用B-tree的数据结构来存储索引数据,在主键的选择上面我们应该尽量使用有序的主键保证写入性能。

    3. 单调递增:保证下一个ID一定大于上一个ID,例如事务版本号、IM增量消息、排序等特殊需求。

    4. 信息安全:有些业务的ID必须是无规则的ID,比如订单编号。

业界采用了很多的分布式ID生成方式,

     基于数据库的自增字段,自增字段完全依赖于数据库,这在数据库移植,扩容,洗数据,分库分表等操作时带来了很多麻烦。

     比如基于:Zookeeper/redis方式, 基于Snowflake算法的唯一ID生成器;详细的可以看《分布式ID生成解决方式》,但是都有各种的优点和缺点;

本文自身采用常规的解决方案,类似与Hibernate TableGenerator,简单实用的解决思路:

    这种方式比较常用,每一次都请求数据库,通过程序维护数据库的自增ID来获取全局唯一ID,对于小系统来说,这是一个简单有效的方案,这种的生成方式还是比较依赖于数据库,每次获取ID都需要经过一次数据库的调用,性能损耗很大(但可以基于步长方式来补偿,这样会导致Client出现宕机获取其他原因,会丢失这些步长,使至ID不连贯)。还有在数据库的水平扩展上比较麻烦的。

  优点:ID连贯、服务稳定,已经很满足大多数的公司分表分库后的ID业务需求了

  缺点:基于数据库性能有瓶颈。

 

    对于MySQL性能问题,可用如下方案解决:在分布式系统中我们可以多部署几台机器服务,每台机器设置不同的初始值,且步长和机器数相等。比如有两台机器。设置步长step为2,TicketServer1的初始值为1(1,3,5,7,9,11…)、TicketServer2的初始值为2(2,4,6,8,10…)。这是Flickr团队在2010年撰文介绍的一种主键生成策略()。如下所示,为了实现上述方案分别设置两台机器对应的参数,TicketServer1从1开始发号,TicketServer2从2开始发号,两台机器每次发号之后都递增2。

    假设我们要部署N台机器,步长需设置为N,每台的初始值依次为0,1,2…N-1那么整个架构就变成了如下图所示:

 image.png

实现的思路:

  1.采用类似与Hibernate TableGenerator ,设置步长和sequene_name和value值;

  2.通过发号器来维护这个sequene表,jdbc 业务ID新增的时候通过采用sequene服务获取新增的ID值;

  3.基于性能考虑,每个部署的服务都可以设置不同的步长,维护在本地内存上,提高效率,也可以设置不同的增长因子从而满足水平扩展需求;

   第一步:创建一张sequence对应的表。

image.png

     几张逻辑表需要声明几个sequence,也可以采用在项目启动的时候,去自动新建sequence值;

第二步:配置sequenceDao

image.png

sequence生成器核心代码:

public SequenceRange nextRange(String name) throws Exception {
           if (name == null) {
               throw new IllegalArgumentException("序列名称不能为空");
           }
 
           long oldValue;
           long newValue;
 
           Connection conn = null;
           PreparedStatement stmt = null;
           ResultSet rs = null;
 
           for (int i = 0; i < retryTimes + 1; ++i) {
               try {
                   conn = dataSource.getConnection();
                   stmt = conn.prepareStatement(getSelectSql());
                   stmt.setString(1, name);
                   rs = stmt.executeQuery();
                   rs.next();
                   oldValue = rs.getLong(1);
 
                   if (oldValue < 0) {
                       StringBuilder message = new StringBuilder();
                       message.append("Sequence value cannot be less than zero, value = ").append(oldValue);
                       message.append(", please check table ").append(getTableName());
 
                       throw new Exception(message.toString());
                   }
 
                   if (oldValue > Long.MAX_VALUE - DELTA) {
                       StringBuilder message = new StringBuilder();
                       message.append("Sequence value overflow, value = ").append(oldValue);
                       message.append(", please check table ").append(getTableName());
 
                       throw new Exception(message.toString());
                   }
 
                   newValue = oldValue + getStep();
               } catch (Exception e) {
                   throw new Exception(e);
               } finally {
                   closeResultSet(rs);
                   rs = null;
                   closeStatement(stmt);
                   stmt = null;
                   closeConnection(conn);
                   conn = null;
               }
 
               try {
                   conn = dataSource.getConnection();
                   stmt = conn.prepareStatement(getUpdateSql());
                   stmt.setLong(1, newValue);
                   stmt.setTimestamp(2, new Timestamp(System.currentTimeMillis()));
                   stmt.setString(3, name);
                   stmt.setLong(4, oldValue);
                   int affectedRows = stmt.executeUpdate();
                   if (affectedRows == 0) {
                       // retry
                       continue;
                   }
 
                   return new SequenceRange(oldValue + 2, newValue);
               } catch (Exception e) {
                   throw new Exception(e);
               } finally {
                   closeStatement(stmt);
                   stmt = null;
                   closeConnection(conn);
                   conn = null;
               }
           }
 
           throw new Exception("Retried too many times, retryTimes = " + retryTimes);
       }

发号器测试:

image.png

参考资料:

   Leaf——美团点评分布式ID生成系统》

项目源代码:

    https://download.csdn.net/download/tang06211015/10350126