emall商城-面向接口编程实践

1. 面向接口编程

在一个面向对象的系统中,系统的各种功能是由许许多多的不同对象协作完成的。在这种情况下,
各个对象内部是如何实现自己的,对系统设计人员来讲就不那么重要了;而各个对象之间的协作关
系则成为系统设计的关键。小到不同类之间的通信,大到各模块之间的交互,在系统设计之初都
是要着重考虑的,这也是系统设计的主要工作内容。面向接口编程就是指按照这种思想来编程。

上述内容来自百度百科(说实话我没看懂).

2. 需求

emall商城中有用户登录后需要将用户的token信息在服务端保存一份,实现这个功能有两个思路:

  • Redis
  • Guava的LoadingCache

此处就需要将存储token的这一逻辑抽象成接口,两种方式分别实现此接口,从而达到业务逻辑与底层实现分离.

3. 实现

3.1 抽象ILocalCache接口

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
/**
* 本地缓存
* @author lujiahao
* @version 1.0
* @date 2017-10-20 17:24
*/
public interface ILocalCache<T> {
/**
* 设置缓存
* @param key
* @param value
* @return
*/
boolean setCache(String key, T value);

/**
* 删除缓存
* @param key
* @return
*/
boolean cleanCache(String key);

/**
* 获取缓存
* @param key
* @return
*/
Object getCache(String key);
}

3.2 Redis实现方式

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
/**
* Redis实现缓存
* @author lujiahao
* @version 1.0
* @date 2017-10-20 17:33
*/
public class RedisCacheImpl<T> implements ILocalCache<T> {
public static final Logger LOGGER = LoggerFactory.getLogger(RedisCacheImpl.class);

@Autowired
private JedisClientDao jedisClientDao;

@Override
public boolean setCache(String key, T value) {
// 把用户信息写入redis
jedisClientDao.set(Const.CACHE_TOKEN + ":" + key, JsonUtils.objectToJson(value));
// 设置session过期时间
jedisClientDao.expire(Const.CACHE_TOKEN + ":" + key, Const.CACHE_TOKEN_EXPIRE);
return true;
}

@Override
public boolean cleanCache(String key) {
try {
// 根据token从redis中查询用户信息
String json = jedisClientDao.get(Const.CACHE_TOKEN + ":" + key);
if (StringUtils.isNoneBlank(json)) {
// 更新过期时间--清除key
jedisClientDao.expire(Const.CACHE_TOKEN + ":" + key, 0);
}
return true;
} catch (Exception e) {
return false;
}
}

@Override
public Object getCache(String key) {
try {
// 根据token从redis中查询用户信息
String json = jedisClientDao.get(Const.CACHE_TOKEN + ":" + key);
if (StringUtils.isBlank(json)) {
return ServerResponse.build(400, "此Session已经过期,请重新登录");
}
// 更新过期时间
jedisClientDao.expire(Const.CACHE_TOKEN + ":" + key, Const.CACHE_TOKEN_EXPIRE);
// 返回用户信息
EmallUser emallUser = JsonUtils.jsonToPojo(json, EmallUser.class);
return ServerResponse.success(emallUser);
} catch (Exception e) {
return ServerResponse.error("无法获取用户信息");
}
}
}

3.3 Guava的LoadingCache实现方式

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
40
41
42
43
44
45
46
47
48
49
50
/**
* Guava实现缓存
* @author lujiahao
* @version 1.0
* @date 2017-10-20 17:33
*/
public class GuavaCacheImpl<T> implements ILocalCache<T> {
public static final Logger LOGGER = LoggerFactory.getLogger(GuavaCacheImpl.class);

// LRU算法
private static LoadingCache<String, Object> localCache = CacheBuilder.newBuilder().initialCapacity(1000)
.maximumSize(10000).expireAfterAccess(12, TimeUnit.HOURS)
.build(new CacheLoader<String, Object>() {
// 默认的数据加载实现,当调用get取值是,如果没有key,就执行这个方法
@Override
public Object load(String s) throws Exception {
return null;
}
});

@Override
public boolean setCache(String key, T value) {
try {
localCache.put(key, value);
} catch (Exception e) {
return false;
}
return true;
}

@Override
public boolean cleanCache(String key) {
try {
localCache.invalidate(key);
} catch (Exception e) {
return false;
}
return true;
}

@Override
public Object getCache(String key) {
try {
return localCache.get(key);
} catch (Exception e) {
LOGGER.error("========== localCache get error ==========", e);
}
return null;
}
}

3.4 applicationContext.xml中配置

1
2
3
<!--根据具体实现采用缓存实现方式-->
<!--<bean class="com.lujiahao.sso.utils.cache.RedisCacheImpl"/>-->
<bean class="com.lujiahao.sso.utils.cache.GuavaCacheImpl"/>

3.5 代码中使用

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
@Service
public class IUserServiceImpl implements IUserService {
private static final Logger LOGGER = LoggerFactory.getLogger(IUserServiceImpl.class);

@Autowired
private EmallUserMapper emallUserMapper;

@Autowired
private ILocalCache iLocalCache;

@Override
public ServerResponse userLogin(String username, String password) {
try {
String md5Password = DigestUtils.md5DigestAsHex(password.getBytes());
EmallUser emallUser = emallUserMapper.userLogin(username, md5Password);
if (emallUser == null) {
// 不能返回没有此用户名 没用用户名也返回这个信息是因为防止猜测用户名
return ServerResponse.error("用户名或密码错误");
}
// 保存用户信息前先把密码清除,为了安全起见
emallUser.setPassword(StringUtils.EMPTY);
String token = UUID.randomUUID().toString();
// 这里采用接口编程的方式,到底用redis还是用guava
boolean isSaveSuccess = iLocalCache.setCache(token, emallUser);
if (isSaveSuccess) {
return ServerResponse.success(token);
} else {
LOGGER.info("========== 用户信息保存缓存失败 ==========");
return ServerResponse.error("登录失败,请重试!");
}
} catch (Exception e) {
ExceptionUtil.getStackTrace(e);
return ServerResponse.error("服务器异常");
}
}
}

在代码中使用的时候,将接口ILocalCache注入,在applicationContex.xml中根据具体要求配置不同的
bean,由此就可以实现将缓存业务与缓存实现解耦.

4. 总结

我理解的面向接口编程就是将业务中的需求抽取出公共的几种方式或步骤,底层由不同的类来实现这个接口,
由此达到解耦的目的.个人理解,欢迎大家拍砖^_^.

emall商城-SSO单点登录

1. 前期准备

  1. 准备一台Redis服务器
  2. 添加host127.0.0.1 sso.emall.com
  3. 搭建emall-sso工程并整合响应的框架

2. 实现原理

单点登录的场景随处可见,此功能极大的简化了用户在网站间的重复登录,使得用户体验更加良好.本教程单点登录的实现原理:用户根据用户名和密码登录,成功后服务器返回token信息,并将token信息写入Redis和Cookie中,用户再次登录时,首先判断Cookie中是否有token信息,如果有则根据token去后台换取用户信息;否则提示用户重新登录.

3. 实现步骤

3.1 登录接口

/**
 * 用户登录
 */
@Override
public CommonResult userLogin(UserDTO userDTO, HttpServletRequest request, HttpServletResponse response) {
    String username = userDTO.getUsername();
    String password = userDTO.getPassword();

    TbUserExample example = new TbUserExample();
    TbUserExample.Criteria criteria = example.createCriteria();
    criteria.andUsernameEqualTo(username);
    List<TbUser> list = tbUserMapper.selectByExample(example);
    // 如果没有此用户名  没用用户名也返回这个信息是因为防止猜测用户名
    if (list == null || list.size() == 0) {
        return CommonResult.build(400, "用户名或密码错误");
    }
    TbUser tbUser = list.get(0);
    // 对比密码
    if (!DigestUtils.md5DigestAsHex(password.getBytes()).equals(tbUser.getPassword())) {
        return CommonResult.build(400, "用户名或密码错误");
    }
    // 生成token
    String token = UUID.randomUUID().toString();
    // 保存用户信息前先把密码清除,为了安全起见
    tbUser.setPassword(null);

    // 用户信息存入Redis
    saveUserInfoToRedis(tbUser, token);

    // 添加写cookie的逻辑  cookie有效期是关闭浏览器失效
    CookieUtils.setCookie(request, response, COOKIE_TOKEN, token);
    return CommonResult.ok(token);
}

3.2 根据token查询用户信息

/**
 * 根据token查询用户信息
 */
@Override
public CommonResult getUserByToken(String token) {
    // 根据token从redis中查询用户信息
    String json = jedisClientDao.get(REDIS_USER_SESSION_KEY + ":" + token);
    if (StringUtils.isBlank(json)) {
        return CommonResult.build(400, "此Session已经过期,请重新登录");
    }
    // 更新过期时间
    jedisClientDao.expire(REDIS_USER_SESSION_KEY + ":" + token, SSO_SESSION_EXPIRE);
    // 返回用户信息
    return CommonResult.ok(JsonUtils.jsonToPojo(json, TbUser.class));
}

4. 跨域

4.1 JSONP

JSONP的实现与 ajax 没有任何关系,JSONP是通过script的src实现的,最终都是向服务器发送请求数据然后回调,而且方便起见,jquery把 JSONP 封装在了 $.ajax 方法中,调用方式与 ajax 调用方式略有区别。JSONP本质是:动态创建script标签,然后通过他的src属性发送跨域请求.

/**
 * 通过token查询用户信息
 *
 * @param token
 * @return
 */
@RequestMapping(value = "/token/{token}")
@ResponseBody
public Object getUserByToken(@PathVariable String token, String callback) {
    CommonResult result = null;
    try {
        result = userService.getUserByToken(token);
    } catch (Exception e) {
        e.printStackTrace();
        result = CommonResult.build(500, ExceptionUtil.getStackTrace(e));
    }
    // 判断是否为jsonp调用
    if (StringUtils.isBlank(callback)) {
        // 不是jsonp调用
        return result;
    } else {
        MappingJacksonValue mappingJacksonValue = new MappingJacksonValue(result);
        mappingJacksonValue.setJsonpFunction(callback);
        return mappingJacksonValue;
    }
}

4.2 CORS

此种方式后端实现有两种方式:
    让所有的controller类继承自定义的BaseController类,改类中将对返回的头部做些特殊处理;
    通过filter实现所有的请求封装跨域.

4.2.1 继承BaseController

public abstract class BaseController {
      /**
     * description:send the ajax response back to the client side
     * @param responseObj
     * @param response
     */
    protected void writeAjaxJSONResponse(Object responseObj, HttpServletResponse response) {
        response.setCharacterEncoding("UTF-8");
        response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate"); // HTTP 1.1
        response.setHeader("Pragma", "no-cache"); // HTTP 1.0
        /**
         * for ajax-cross-domain request TODO get the ip address from
         * configration(ajax-cross-domain.properties)
         */
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setDateHeader("Expires", 0); // Proxies.
        PrintWriter writer = getWriter(response);
        writeAjaxJSONResponse(responseObj, writer);
    }

      /**
     *
     * @param response
     * @return
     */
    protected PrintWriter getWriter(HttpServletResponse response) {
        if(null == response){
            return null;
        }
        PrintWriter writer = null;
        try {
            writer = response.getWriter();
        } catch (IOException e) {
            logger.error("unknow exception", e);
        }
        return writer;
    }

    /**
     * description:send the ajax response back to the client side.
     *
     * @param responseObj
     * @param writer
     * @param writer
     */
    protected void writeAjaxJSONResponse(Object responseObj, PrintWriter writer) {
        if (writer == null || responseObj == null) {
            return;
        }
        try {         
            writer.write(JSON.toJSONString(responseObj,SerializerFeature.DisableCircularReferenceDetect));
        } finally {
            writer.flush();
            writer.close();
        }
    }
}

4.2.2 Filter实现

/**
 * @author lujiahao
 * @version 1.0
 * @date 2017-10-15 22:20
 */
public class HeadersCORSFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        response.setHeader("Access-Control-Allow-Origin","*");
        filterChain.doFilter(servletRequest, response);
    }

    @Override
    public void destroy() {

    }
}

web.xml中配置:

<!-- Ajax Access-Control-Allow-Origin 跨域 拦截器解决方案 -->
<filter>
    <filter-name>ACAOFilter</filter-name>
    <filter-class>com.lujiahao.sso.filter.HeadersCORSFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>ACAOFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

5. 代码详见emall-sso

6. 实现效果

7. 说明

emall商城系列是整合[淘淘商城]和[慕课网Java从零到企业级电商项目实战]的系列,这两部教程来自互联网.

MySQL笔记-SQL语句分类

SQL语句可以分为以下四类 : 数据操作语言(DML) , 数据定义语言(DDL) , 数据控制语言(DCL) , 事务控制语言(TCL).

数据定义语言(DDL : Data Definition Language)

用于定义SQL模式,基本表,视图和索引的创建和撤消操作.

主要包含CREATE , ALTER , DROP , TRUNCATE , COMMENT , REPLACE(RENAME)等语句,一般不需要commit等事务操作.

数据操作语言(DML : Data Manipulation Language)

由数据库管理系统(DBMS) 提供,用于让用户或程序员使用,实现对数据库中数据的操作.

主要包含SELECT , INSERT , UPDATE , DELETE , MERGE , CALL , EXPLAIN PLAN , LOCK TABLE等语句.

数据控制语言(DCL:Data Control Language)

授权用户或用户组操作和访问数据的权限.

主要包含GRANT , REVOKE等语句.

事务控制语言(TCL:Transaction Control Language)

用于数据库的事务管理,确保被DML语句影响的表的所有行及时得以更新.

主要包含SAVEPOINT , SET TRANSACTION , BEGIN TRANSACTION , COMMIT , ROLLBACK等语句。

非官方分法

##数据查询语言(DQL : Data Queries Language)
用以从表中获得数据.

主要包含SELECT , WHERE , GROUP BY , HAVINGORDER BY.

Docker教程(八)---构建Kafka镜像

Kafka

该容器的前提依赖是使用我的JDK的镜像的依赖,后面我会把镜像同步到线上.

构建镜像

下载好Dockerfile文件执行

docker build -t="lujiahao/kafkatest" .

启动容器

docker run --name test_kafka -it -p 2181:2181 -p 9092:9092 lujiahao/kafkatest /bin/bash

额外配置

容器中使用kafka还需要将容器的内部IP配置到config/server.properties

docker inspcet test_kafka查看容器的内部IP

然后更改config目录中的server.properties中的三个配置

#容器的ip地址
host.name=172.17.0.1
#容器的ip地址
listeners=PLAINTEXT://172.17.0.1:9092
#宿主机的IP地址,用于外部Producer和Consumer使用(公司服务器你懂得)
advertised.listeners=PLAINTEXT://xx.xx.xx.xx:9092

启动Kafka

./kafkastart.sh

错误总结

1.无法分配内存

Java HotSpot(TM) 64-Bit Server VM warning: INFO: os::commit_memory(0x00000000c0000000, 1073741824, 0) failed;
error='Cannot allocate memory' (errno=12)

解决方案

vi zookeeper-server-start.sh 
    将 export KAFKA_HEAP_OPTS="-Xmx512M -Xms512M"
    改成 export KAFKA_HEAP_OPTS="-Xmx512M -Xms128M"
vi kafka-server-start.sh 
    将 export KAFKA_HEAP_OPTS="-Xmx1G -Xms1G"
    改成 export KAFKA_HEAP_OPTS="-Xmx512M -Xms128M"
把这两个值改成相同的就行了

参考资料

2.无法发送消息

Error when sending message to topic TESTTOPIC with key: null, value: 9 bytes with error: 
Failed to update metadata after 60000 ms. (org.apache.kafka.clients.producer.internals.ErrorLoggingCallback)

解决方案

在 server.properties 中添加 advertised.listeners=PLAINTEXT://xx.xx.xx.xx:9092 ,对应的IP为宿主机的IP

参考资料

Kafka的基本操作

  1. 创建topic

    kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic test
  2. 查看topic

    kafka-topics.sh --list --zookeeper localhost:2181
  3. 生产者

    kafka-console-producer.sh --broker-list localhost:9092 --topic test
  4. 消费者

    kafka-console-consumer.sh --zookeeper localhost:2181 --topic test --from-beginning

Docker教程(七)---构建Tomcat镜像

这次镜像构建是基于上篇的JDK镜像构建的.

Dockerfile

FROM lujiahao/jdk1.7
MAINTAINER lujiahao
ADD apache-tomcat-7.0.75.tar.gz /usr/local/
RUN mv /usr/local/apache-tomcat-7.0.75 /usr/local/tomcat7
WORKDIR /usr/local/tomcat7/bin
EXPOSE 8080

构建命令

docker build -t="lujiahao/tomcat7" .

启动容器测试

docker run --name tomcat7 -p 8080:8080 -d lujiahao/tomcat7 ./catalina.sh run

注意:在运行装有tomcat的容器的时候使用catalina.sh run (调试模式,在前台运行)来启动,如果使用startup.sh 的话会在后台运行,容器会认为进程down掉,容器也会自动停止。

然后在浏览器中查看能否访问tomcat主页

Docker教程(六)---构建JDK镜像

1.创建文件夹jdk

因为会把当前构建目录中的内容添加到镜像里面  所以要单独创建一个文件夹

2.编写Dockerfile文件

FROM centos
MAINTAINER lujiahao
ADD jdk-7u80-linux-x64.tar.gz /usr/local
RUN mv /usr/local/jdk1.7.0_80 /usr/local/jdk1.7
ENV JAVA_HOME /usr/local/jdk1.7
ENV JRE_HOME /usr/local/jdk1.7/jre
ENV CLASSPATH .:$JAVA_HOME/jre/lib/rt.jar  :$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
ENV PATH $JAVA_HOME/bin:$PATH

3.构建镜像

docker build -t="lujiahao/jdk1.7" .

4.构建完成之后启动一个临时性容器来测试

docker run --rm -it lujiahao/jdk1.7 /bin/bash

在运行java和javac看看,java -version看版本

相关资源

可以到 DockerfileCollection 中寻找对应的 Dockerfile 文件

Docker教程(五)---Azure虚拟安装Docker

老大把Azure的权限给我了,哈哈,可惜我已经准备离职了,但是工作还是要继续的.

一切进行的非常顺利,登录Azure云平台,创建虚机,然后切换root权限安装docker,退出root用户,准备docker pull centos…
无法运行,卧槽!

运行docker命令弹出如下提示:

Cannot connect to the Docker daemon. Is the docker daemon running on this host?

docker出于安全的考虑才会这样做(http://dockone.io/article/589).

解决方案

  1. 切换root权限

    visudo -f /etc/sudoers
  2. /etc/sudoers 中添加如下内容

    azureuser  ALL=(ALL)       NOPASSWD: /usr/bin/docker
  3. 退出root用户

  4. 执行 vim ~/.bashrc 添加

    alias docker='sudo /usr/bin/docker'
  5. 保存后执行

    source ~/.bashrc

此时就可以自由的使用docker命令了.

Docker教程(四)---Dockerfile命令详解

深入docker镜像

拉取镜像文件到本地

docker pull centos

查看本地镜像:

docker images 

查找镜像

docker search [镜像名称]

构建镜像

1. 使用docker commit 命令      用户名:lujiahao
    docker commit 容器名称/ID lujiahao/centos-tomcat
    docker commit -m="describe" --author="lujiahao" 容器名称/ID  lujiahao/centos-tomcat:test
    docker inspect lujiahao/centos-tomcat:test
2. 使用docker build命令和Dockerfile文件(推荐)
    docker build  -t=“用户名/镜像名称" .
    docker build  -t="crxy/centos" .
    点表示会在当前目录中找Dockerfile文件
    建议创建镜像的时候单独一个目录,然后在写Dockerfile,这个目录叫构建目录.如果目录下面有其他文件,会把目录下所有文件发送给docker的守护进程

查看构建的步骤和层级

docker history 用户ID/镜像名

执行流程

  • dockerfile中的指令会按照从上到下执行
  • docker首先从基础镜像运行一个容器
  • 执行一条指令,对容器进行修改
  • 执行类似docker commit的操作,提交一个新的镜像层
  • docker再基于刚提交的镜像运行一个新容器
  • 然后执行dockerfile中的下一条指令,直到所有指令都执行完毕

Dockerfile指令详解

  • 所有指令都必须为大写字母
  • 以#开头的都认为是注释

FROM

第一条指令必须是`FROM`(指定使用的基础镜像)

MAINTAINER

指定镜像的作者信息

RUN

指定镜像被构建时要运行的命令,默认会使用/bin/bash -c 执行RUN 后面指定的命令
也可以使用RUN  ["mkdir","a.txt"]

EXPOSE

告诉docker该容器需要对外开放的端口

CMD

指定一个容器运行时要执行的命令 
docker run -i -t lujiahao/cenos-tomcat /bin/bash
等于在Dockerfile中添加 CMD ["/bin/ps"]
注意:使用 docker run 命令可以覆盖CMD指令

ENTRYPOINT

与CMD类似,但是ENTRYPOINT指定的命令不会被docker run指定的命令覆盖,
它会把docker run命令行中指定的参数传递给ENTRYPOINT指令中指定的命令当作参数。
ENTRYPOINT ["/bin/ps"]
docker run -t -i lujiahao/centos  -l
ENTRYPOINT 还可以和CMD组合一块来用,可以实现,当用户不指定参数的时候使用默认的参数执行,如果用户指定的话就使用用户提供的参数.
ENTRYPOINT ["/bin/ps"]
CMD ["-h"]
注意:如果确实要覆盖默认的参数的话,可以在docker run中指定--entrypoint进行覆盖

WORKDIR

WORKDIR /etc
RUN touch a.txt
WORKDIR /usr/local
RUN touch b.txt
注意:可以在docker run命令中使用-w覆盖工作目录,但是不会影响之前在工作目录中执行的命令

ENV

设置环境变量,在这设置的环境变量可以在后面的指令中使用,使用$引用
并且这个环境变量也会被持久保存到从我们的镜像创建的任何容器中。可以使用env命令查看
也可以使用docker run 命令行的-e参数来传递环境变量,这些变量只在运行时有效 -e JAVA_HOME=/usr/local

USER(了解)

可以指定以什么用户去运行这个容器
USER ftp(用户信息:查看/etc/group)
还可以使用其他组合方式
USER 用户名/UID:组/GID(用户名/UID和组/GID可以单独指定或者组合)
不指定的话默认就是root用户

ADD

ADD指令用来将构建目录下的指定文件复制到镜像中(注意只能是构建目录下面的文件)
ADD a.txt /etc/a.txt
ADD 在添加压缩文件的时候会把压缩文件自动解压。(gzip bzip2 xz),如果目的位置已经存在了和压缩文件同名的文件或者目录,那么这些文件将会被覆盖。

COPY

COPY和ADD类似,但是COPY命令不会对压缩文件进行解压,只负责复制。
如果目的位置不存在,docker会自动创建所有需要的目录,就像使用make -p一样
注意:使用COPY和ADD的命令的时候,复制的源文件必须在当前构建环境之内,也就是和Dockerfile同一目录,否则会找不到。

ONBUILD

这个指令能为镜像添加触发器,当一个镜像被用作其他镜像的基础镜像时,
该镜像中的触发器将会执行。这个触发器所指定的命令会在FROM指令之后执行。
ONBUILD ADD . /usr/local
先在一个镜像中添加这个触发器,再使用这个镜像作为基础镜像创建一个镜像,
会发现在FROM指令之后就开始执行基础镜像中设置的触发器中的指令了。启动一个容器可以查看效果。
注意:触发器只能被继承一次。
有一些命令是不能在ONBUILD中指定的。包括FROM,MAINTAINER,ONBUILD,这样是为了防止在构建过程中产生递归调用的问题。

.dockerignore

在执行build的时候忽略掉不需要的文件。
在Dockerfile同目录下创建.dockerignore,将要忽略的文件填到里面即可.

VOLUME

可以理解为设置(共享目录,磁盘挂载),添加到容器中,并没有真正提交到镜像文件中
这个指令用来向基于镜像创建的容器添加卷,一个卷可以存在于一个或者多个容器内的特定的目录。
卷的特点
    1:卷可以在容器间共享和重用
    2:对卷的修改是立刻生效的
    3:对卷的修改不会对更新镜像产生影响
    4:卷会一直存在直到没有任何容器再使用它。
卷功能可以让我们将一些数据添加到镜像中而不是将将这个内容提交到镜像中。并且允许我们在多个容器间共享这些内容。
    格式:VOLUME  ["/usr/projrct"]
    还可以一次指定多个VOLUME ["/usr/project","/data"]
创建数据卷
    VOLUME  ["/usr/projrct"]
    或者在启动容器的时候创建docker run -it -v /webapp centos /bin/bash
    注意:默认情况下,如果只是声明数据卷而没有映射到宿主机上的具体目录,docker会在/var/lib/docker/vfs/dir/
        目录下分配一个具有唯一名字的目录给该数据卷。通过docker inspect 命令可以查看具体路径
指定宿主主机目录作为数据卷
                        宿主机目录:容器目录
    docker run -it -v /usr/local/webapp:/webapp centos /bin/bash
    注意:如果容器内部已经存在webapp目录,那么目录中的文件将会被覆盖。
默认情况下,数据卷是具有读写权限,rw权限,可以改为只读权限,ro
    docker run -it -v /usr/local/webapp:/webapp:ro centos /bin/bash
使用场景:
    容器里面的tomcat/webapps/目录和本地宿主机的目录对应上,本地修改代码后,
    直接重启容器即可(如果是html连重启都不用了),相当于一个动态部署.

镜像操作

docker的构建缓存

由于每一步的构建过程都会将结果提交为镜像,所以docker的构建镜像过程就显得非常聪明,它会将之前的镜像层看作缓存。再进行重新构建时会从第一条发生了变化的指令开始,前面的都使用缓存

不使用构建缓存的两种方式:

  • 可以使用 --no-cache 参数来指定不使用缓存功能
  • 一般会在dockerfile中使用ENV指令设置一个时间(ENV CREATE_TIME 2015-01-01),写在FROM下面,这样只要一改了这个就不会使用构建缓存了

容器端口映射

默认情况下docker不会自动打开这些端口,必须在运行容器的时候指定-P(大写)参数,这样就可以打开expose指定的所有端口。 会随机映射到宿主机的一个端口上

EXPOSE指令可以同时指定一个或多个需要对外开放的端口

expose 80 8080 6379

对外开放端口的两种方式

-P(大写) 这样的话会将dockerfile文件中EXPOSE 指令指定的所有端口一并公开
-p(小写)可以手工指定需要对外公开的端口

如果没有使用expose指定需要对外开放的端口,还可以在启动容器的时候动态指定,使用-p(小写)参数

-p 80(docker会在宿主机上随机选择一个位于49000~49900内未被使用的端口来映射到容器中的80端口上)
-p 8080:8080(把宿主机上的8080端口映射到容器的8080端口) 前面的表示宿主机
-p 80:80 -p 8080:8080 同时指定多个

使用docker ps 可以查看容器的端口分配情况 或者使用docker port 容器ID/名称查看

推送镜像到Docker Hub(需要先登录:docker login)

docker push 用户ID/镜像名(docker 1.6以下版本这样使用)
docker1.6需要使用下面命令才能推送
docker tag image_id docker.io/login_name/image_name
docker push docker.io/login_name/image_name
推送可能会报错,cdn-registry-1.docker.io: no such host 是因为网络问题、重试几次。

删除镜像

docker rmi 用户ID/镜像名
或者docker rmi `docker images -a -q`
删除所有未打标签的镜像
 docker rmi -f $(docker images --filter 'dangling=true')

Docker教程(三)---容器命令

Docker 容器命令

交互式操作流程

运行一个容器(交互式)

docker run -i -t centos /bin/bash
-i 表示stdin标准输入
-t表示tty终端

第一次执行docker run命令会先从远程仓库中拉取centos的镜像
内部流程如下图

run命令内部流程.jpg

退出容器

exit

重命名容器

docker rename 旧名称 新名称
另一种方法,启动时重命名
docker run --name crxy -t -i centos /bin/bash
合法的容器名称正则[a-zA-Z0-9_.-]

查看容器列表

docker ps [-a]  [-l]  [-n num]

重新启动已经停止的容器

docker start 容器名称/容器ID

附着到一个容器上

docker attach 容器名称/容器ID
    使用`exit`退出容器后,重新由`docker start`命令启动了容器
    此时可以使用这个命令重新进入到容器里面
    可以理解为从外部进入到这个容器内部
    仅仅适用于使用-it启动的容器,像下面的后台启动的是无法使用这个命令的

后台长期运行

创建长期运行的容器

docker run --name lujiahao -d centos /bin/bash -c "while true;do echo hello world;sleep 1;done"

会返回该容器的长id : 55ba56913bb5666ecb352a031503b5191cc8f186558e2f22380c2adadecc7c14

我们通过docker ps获得的是这个长id的前十二位

docker run后面追加-d=true或者-d,那么容器将会运行在后台模式.

获取容器日志

docker logs [--tail num] [-t] [-f]  容器名称/容器ID
    旧版本的日志位置/var/lib/docker/containers/ID/ID-json.log
    但是新版本的实验并没有发现这个log存在位置
    -t 是来添加时间戳的
    -f 持续输出

查看容器内的进程

docker top 容器名称/容器ID

操作一个正在运行的容器

在容器内部运行进程(docker1.3之后才支持)   
docker exec -d lujiahao touch /etc/lujiahao.txt
在后台运行的容器里面创建一个文件
docker exec -it lujiahao /bin/bash
连到一个在后台运行的容器,此时执行exit退出,后台容器还在运行

停止容器

docker stop 容器名称/容器ID  
    向容器发送停止指令,容器自己停止,建议使用
docker kill 容器名称/容器ID   
    直接停掉,不建议使用

获取容器详细信息

docker inspect  [--format | -f] 容器名称/容器ID
    例子:docker inspect --format='{{.State.Running}}' lujiahao
    容器存储位置:/var/lib/docker/containers

删除容器

docker rm 容器名称/容器ID

正在运行中的容器是无法删除的,会出现下面的提示

Error response from daemon: You cannot remove a running container 55ba56913bb5666ecb352a031503b5191cc8f186558e2f22380c2adadecc7c14. 
Stop the container before attempting removal or use -f

可以先停止容器,或者使用 -f 参数

批量删除正在运行中的容器

docker rm `docker ps -a -q`
    docker ps -a -q : 这个命令用来获取所有容器的id

容器的导入和导出

导出:docker export 容器ID/名称 > my_container.tar
导入:cat mycontainer.tar | docker import - 镜像名称:标签

镜像的保存和加载(暂时还没讲到)

保存:docker save 镜像ID > my_image.tar 
加载:docker load< my_image.tar

Docker教程(二)---安装Docker

Docker 核心组件

镜像(Image)

镜像是构建docker世界的基石,也是docker生命周期中的构建阶段.

仓库(Registry)

存储用户构建的镜像以及官方的镜像,分为公有和私有.
Docker公司运营的公有仓库叫做 Docker Hub

容器(Container)

容器是基于镜像启动的,容器中可以运行一个或多个进程。
容器是docker生命周期中的启动或执行阶段。

仓库里面存储着镜像,基于镜像可以启动容器

container = image + docker run

Docker工作流

dockerworkflow.jpg

安装Docker

宿主机Centos7 64位,因为 Docker 是基于64位系统构建的.

  1. 验证linux内核版本uname -a,官方建议使用3.8版本以上

  2. 检查Device Mapper(Docker的存储驱动)

    grep device-mapper /proc/devices
    如果不存在则安装yum install -y device-mapper
    加载dm_mod内核模块 modprobe dm_mod
  3. 安装

    yum -y install docker(默认安装最新版)
  4. 安装指定版本 Docker

    查看可安装的版本
        yum makecache fast(相当于更新本地缓存)
        yum list docker --showduplicates
    安装指定版本
        yum install 2:1.12.5-14.el7.centos
  5. 验证

    启动docker
        systemctl start docker(centos7)
    添加开机启动项
        systemctl enable docker(centos7)
    验证docker是否正常
        systemctl status  docker
        docker info

    出现下面图片表示安装成功了
    dockerinfo.jpg

总结

至此,Docker已经顺利安装了,下面进入到基础命令学习阶段吧!


欢迎大家关注 : LF工作室

简书 : https://www.jianshu.com/u/e61935d18b09

掘金 : https://juejin.im/user/59239002570c350069c5f0bb

微信公众号 :

头条号 :