Docker Registry

Docker Registry

docker run -itd -v /root/data/registry:/var/lib/registry -p 5000:5000 --restart=always --name docker-registry registry:latest 

对于使用 systemd 的系统,请在 /etc/docker/daemon.json 中写入如下内容(如果文件不存
在请新建该文件)

{
    "registry-mirror": [
        "https://registry.docker-cn.com"
    ],
    "insecure-registries": [
        "192.168.199.100:5000"
    ]
}

当前镜像tag

[root@JD ~]# docker tag mysql:5.7 127.0.0.1:5000/registry-mysql:5.7

推送到镜像仓库

[root@JD ~]# docker push 127.0.0.1:5000/registry-mysql

用 curl 查看仓库中的镜像。

[root@JD ~]# curl 127.0.0.1:5000/v2/_catalog
{"repositories":["registry-mysql"]}

Docker 默认不允许非HTTPS方式推送镜像

使用 Docker Compose 搭建一个拥有权限认证、TLS 的私有仓库

harbor

使用脚本搭建

将harbor.sh脚本从 GitHub Gist下载到您的Ubuntu机器或VM。

harbor.sh脚本需要梯子才能下载。我在CSDN上传了一份harbor:v1.10版本的脚本。
使用安装脚本快速部署Harbor的脚本文件-harbor.sh

向当前用户授予运行权限。

chmod u+x

要注意修改脚本中的端口号。

Harbor要求在目标主机上打开以下端口。

港口 协议 描述
443 HTTPS Harbor门户和核心API在此端口上接受HTTPS请求。您可以在配置文件中更改此端口。
4443 HTTPS 与Harbor的Docker内容信任服务的连接。仅在启用公证人的情况下才需要。您可以在配置文件中更改此端口。
80 HTTP Harbor门户和核心API在此端口上接受HTTP请求。您可以在配置文件中更改此端口。

以超级用户身份运行脚本。

sudo ./harbor.sh

选择是使用主机的IP地址还是FQDN部署Harbor。

Would you like to install Harbor based on IP or FQDN?

这是您访问Harbor接口和注册表服务的地址。

要使用IP地址,请输入1。
要使用FQDN,请输入2。

该脚本需要几分钟才能运行。在运行时,该脚本会从Ubuntu下载所需的软件包和依赖项,安装最新的稳定版本的Docker和Docker Compose,并安装最新的稳定版本的Harbor。

当脚本报告时Harbor Installation Complete,登录到新的Harbor实例。

docker login <harbor_ip_or_FQDN>
用户名: admin
密码: VMware12345

在浏览器中输入Harbor地址,以登录Harbor接口

使用harbor.yml搭建

harbor 下载

解压之后修改harbor.yml

配置YML文件

hostname   这里设置本机的ip
harbor_admin_password    web页面的密码,默认的用户名和密码为admin和Harbor12345

如果没有https,则需要先注释:

https:
  # https port for harbor, default is 443
  port: 443
  # The path of cert and key files for nginx
  certificate: /your/certificate/path
  private_key: /your/private/key/path

运行

sh ./install.sh

Creating harbor-log ... done
Creating registry      ... done
Creating harbor-db     ... done
Creating harbor-portal ... done
Creating redis         ... done
Creating registryctl   ... done
Creating harbor-core   ... done
Creating harbor-jobservice ... done
Creating nginx             ... done
✔ ----Harbor has been installed and started successfully.----

访问页面

https://114.67.79.102:8088

建立https访问

生成证书颁发机构证书

生成CA证书私钥。
openssl genrsa -out ca.key 4096
生成CA证书。

调整-subj选项中的值以反映您的组织。如果使用FQDN连接Harbor主机,则必须将其指定为通用名称(CN)属性。

openssl req -x509 -new -nodes -sha512 -days 3650 \
 -subj "/C=CN/ST=Beijing/L=Beijing/O=example/OU=Personal/CN=yourdomain.com" \
 -key ca.key \
 -out ca.crt

生成服务器证书

证书通常包含一个.crt文件和一个.key文件,例如10.19.46.15.crt和10.19.46.15.key。

生成私钥。
openssl genrsa -out 10.19.46.15.key 4096
生成证书签名请求(CSR)。

调整-subj选项中的值以反映您的组织。如果使用FQDN连接Harbor主机,则必须将其指定为公用名(CN)属性,并在密钥和CSR文件名中使用它。

openssl req -sha512 -new \
    -subj "/C=CN/ST=Beijing/L=Beijing/O=example/OU=Personal/CN=yourdomain.com" \
    -key yourdomain.com.key \
    -out yourdomain.com.csr
生成一个x509 v3扩展文件。

无论您使用FQDN还是IP地址连接到Harbor主机,都必须创建此文件,以便可以为您的Harbor主机生成符合主题备用名称(SAN)和x509 v3的证书扩展要求。替换DNS条目以反映您的域。

cat > v3.ext <<-EOF
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
extendedKeyUsage = serverAuth
subjectAltName = @alt_names

[alt_names]
DNS.1=yourdomain.com
DNS.2=yourdomain
DNS.3=hostname
EOF
使用该v3.ext文件为您的Harbor主机生成证书。

将yourdomain.comCRS和CRT文件名中的替换为Harbor主机名。

openssl x509 -req -sha512 -days 3650 \
    -extfile v3.ext \
    -CA ca.crt -CAkey ca.key -CAcreateserial \
    -in yourdomain.com.csr \
    -out yourdomain.com.crt
提供证书给Harbor和Docker

生成后ca.crt,yourdomain.com.crt和yourdomain.com.key文件,必须将它们提供给港口和码头工人,和重新配置港使用它们。

将服务器证书和密钥复制到Harbor主机上的certficates文件夹中。

cp yourdomain.com.crt /data/cert/
cp yourdomain.com.key /data/cert/

转换yourdomain.com.crt为yourdomain.com.cert,供Docker使用。

Docker守护程序将.crt文件解释为CA证书,并将.cert文件解释为客户端证书。

openssl x509 -inform PEM -in yourdomain.com.crt -out yourdomain.com.cert

将服务器证书,密钥和CA文件复制到Harbor主机上的Docker certificate文件夹中。必须首先创建适当的文件夹。

cp yourdomain.com.cert /etc/docker/certs.d/yourdomain.com/
cp yourdomain.com.key /etc/docker/certs.d/yourdomain.com/
cp ca.crt /etc/docker/certs.d/yourdomain.com/

如果将默认nginx端口443 映射到其他端口,请创建文件夹/etc/docker/certs.d/yourdomain.com:port或/etc/docker/certs.d/harbor_IP:port。

重新启动Docker Engine

systemctl restart docker
添加hosts解析
echo "10.19.46.15   yourdomain.com" >> /etc/hosts   

运行prepare脚本以启用HTTPS

./prepare
停止harbor
docker-compose down -v    
开启harbor
docker-compose up -d
本机登录
[root@JD harbor]# docker login 127.0.0.1:443
Username: admin
Password: 
WARNING! Your password will be stored unencrypted in /root/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store

Login Succeeded   

部署单机版harbor

通过Helm部署具有高可用性的harbor

准备站点证书

如果你拥有一个域名,国内各大云服务商均提供免费的站点证书。你也可以使用 openssl 自
行签发证书。

这里假设我们将要搭建的私有仓库地址为 docker.domain.com ,下面我们介绍使用 openssl
自行签发 docker.domain.com 的站点 SSL 证书。

第一步创建 CA 私钥。

$ openssl genrsa -out "root-ca.key" 4096

第二步利用私钥创建 CA 根证书请求文件。

$ openssl req \
    -new -key "root-ca.key" \
    -out "root-ca.csr" -sha256 \
    -subj '/C=CN/ST=Shanxi/L=Datong/O=Your Company Name/CN=Your Company Name Doc
    ker Registry CA'

以上命令中 -subj 参数里的 /C 表示国家,如 CN ; /ST 表示省; /L 表示城市或者
地区; /O 表示组织名; /CN 通用名称。

第三步配置 CA 根证书,新建 root-ca.cnf 。

[root_ca]
basicConstraints = critical,CA:TRUE,pathlen:1
keyUsage = critical, nonRepudiation, cRLSign, keyCertSign
subjectKeyIdentifier=hash

第四步签发根证书。

$ openssl x509 -req -days 3650 -in "root-ca.csr" \
    -signkey "root-ca.key" -sha256 -out "root-ca.crt" \
    -extfile "root-ca.cnf" -extensions \
    root_ca

第五步生成站点 SSL 私钥。

$ openssl genrsa -out "docker.domain.com.key" 4096

第六步使用私钥生成证书请求文件。

$ openssl req -new -key "docker.domain.com.key" -out "site.csr" -sha256 \
    -subj '/C=CN/ST=Shanxi/L=Datong/O=Your Company Name/CN=docker.domain.com'

第七步配置证书,新建 site.cnf 文件。

[server]
authorityKeyIdentifier=keyid,issuer
basicConstraints = critical,CA:FALSE
extendedKeyUsage=serverAuth
keyUsage = critical, digitalSignature, keyEncipherment
subjectAltName = DNS:docker.domain.com, IP:127.0.0.1
subjectKeyIdentifier=hash

第八步签署站点 SSL 证书。

$ openssl x509 -req -days 750 -in "site.csr" -sha256 \
    -CA "root-ca.crt" -CAkey "root-ca.key" -CAcreateserial \
    -out "docker.domain.com.crt" -extfile "site.cnf" -extensions server

这样已经拥有了 docker.domain.com 的网站 SSL 私钥 docker.domain.com.key 和 SSL 证书
docker.domain.com.crt 。

新建 ssl 文件夹并将 docker.domain.com.key docker.domain.com.crt 这两个文件移入,删
除其他文件。

配置私有仓库

私有仓库默认的配置文件位于 /etc/docker/registry/config.yml ,我们先在本地编辑
config.yml ,之后挂载到容器中。

version: 0.1
log:
    accesslog:
        disabled: true
level: debug
formatter: text
fields:
    service: registry
    environment: staging
storage:
    delete:
        enabled: true
    cache:
        blobdescriptor: inmemory
    filesystem:
        rootdirectory: /var/lib/registry
auth:
    htpasswd:
        realm: basic-realm
        path: /etc/docker/registry/auth/nginx.htpasswd
http:
    addr: :443
    host: https://docker.domain.com
    headers:
        X-Content-Type-Options: [nosniff]
    http2:
        disabled: false
    tls:
        certificate: /etc/docker/registry/ssl/docker.domain.com.crt
        key: /etc/docker/registry/ssl/docker.domain.com.key
health:
    storagedriver:
        enabled: true
        interval: 10s
threshold: 3

生成 http 认证文件

$ mkdir auth
$ docker run --rm \
    --entrypoint htpasswd \
    registry \
    -Bbn username password > auth/nginx.htpasswd

将上面的 username password 替换为你自己的用户名和密码。

编辑 docker-compose.yml

version: '3'

services:
    registry:
    image: registry
    ports:
        - "443:443"
    volumes:
        - ./:/etc/docker/registry
        - registry-data:/var/lib/registry
volumes:
    registry-data:

修改 hosts

编辑 /etc/hosts

docker.domain.com 127.0.0.1

启动

$ docker-compose up -d

这样我们就搭建好了一个具有权限认证、TLS 的私有仓库,接下来我们测试其功能是否正
常。

测试私有仓库功能

登录到私有仓库。

$ docker login docker.domain.com

尝试推送、拉取镜像。

$ docker pull ubuntu:17.10
$ docker tag ubuntu:17.10 docker.domain.com/username/ubuntu:17.10
$ docker push docker.domain.com/username/ubuntu:17.10
$ docker image rm docker.domain.com/username/ubuntu:17.10
$ docker pull docker.domain.com/username/ubuntu:17.10

如果我们退出登录,尝试推送镜像。

$ docker logout docker.domain.com
$ docker push docker.domain.com/username/ubuntu:17.10
no basic auth credentials

发现会提示没有登录,不能将镜像推送到私有仓库中。

参考资料:

《Docker技术入门到实践》

Redis缓存更新怎么保证⼀致性?

删缓存->更新db->再删缓存

更新db->删缓存

第一种方案:

使用阿里的canal将binlog日志采集发送到MQ队列里面,然后通过ACK机制确认处理 这条更新消息,删除缓存,保证数据缓存一致性

(1)读取缓存中是否有相关数据
(2)如果缓存中有相关数据value,则返回
(3)如果缓存中没有相关数据,则从数据库读取相关数据放入缓存中key->value,再返回
(4)如果有更新数据,则先更新数据,再删除缓存
(5)为了保证第四步删除缓存成功,使用binlog异步删除
(6)如果是主从数据库,binglog取自于从库
(7)如果是一主多从,每个从库都要采集binlog,然后消费端收到最后一台binlog数据才删除缓存

高并发环境下,先操作数据库还是先操作缓存

第二种方案:

使用一致性算法: paxos

Redis分布式锁原理?会有什么问题

redis分布式锁的实现主要是基于redis的setnx 命令

第一种情况:业务逻辑执行时间超出锁的超时限制,其他客户端得到锁之后,被超时客户端删除锁

一个客户端拿到了锁,被某个操作阻塞了很长时间,过了超时时间后自动释放了
这个锁,然后这个客户端之后又尝试删除这个其实已经被其他客户端拿到的锁。
所以单纯的用DEL指令有可能造成一个客户端删除了其他客户端的锁,
通过校验这个值保证每个客户端都用一个随机字符串’签名’了,
这样每个锁就只能被获得锁的客户端删除了。

第二种情况:业务逻辑执行时间超出锁的超时限制导致两个客户端同时持有锁的问题

如果在加锁和释放锁之间的逻辑执行得太长,以至于超出了锁的超时限制,
就会出现问题。因为这时候第一个线程持有的锁过期了,临界区的逻辑还没有
执行完,这个时候第二个线程就提前重新持有了这把锁,导致临界区代码不能
得到严格的串行执行

如果在执行计算期间发现锁快要超时了,客户端可以给redis服务实例发送一个
Lua脚本让redis服务端延长锁的时间,只要这个锁的key还存在而且值还等于
客户端设置的那个值    

if  redis.call("get",KEYS[1]) == ARGV[1] then 
        redis.call("set",KEYS[1],ex=3000)
else 
        getDLock();//重新获取锁

第三种: redis的单点故障主从切换带来的两个客户端同时持有锁的问题

生产中redis一般是主从模式,主节点挂掉时,从节点会取而代之,
客户端上却并没有明显感知。原先第一个客户端在主节点中申请成功了一把锁,
但是这把锁还没有来得及同步到从节点,主节点突然挂掉了。
然后从节点变成了主节点,这个新的节点内部没有这个锁,
所以当另一个客户端过来请求加锁时,立即就批准了。
这样就会导致系统中同样一把锁被两个客户端同时持有

不过这种不安全也仅仅是在主从发生 failover 的情况下才会产生,
而且持续时间极短,业务系统多数情况下可以容忍

RedLock算法

使用N个完全独立、没有主从关系的Redis master节点以保证他们大多数情况下都不会同时宕机,N一般为奇数。一个客户端需要做如下操作来获取锁:

  • 1.获取当前时间(单位是毫秒)。
  • 2.轮流用相同的key和随机值在N个节点上请求锁,在这一步里,客户端在每个master上请求锁时,会有一个和总的锁释放时间相比小的多的超时时间。比如如果锁自动释放时间是10秒钟,那每个节点锁请求的超时时间可能是5-50毫秒的范围,这个可以防止一个客户端在某个宕掉的master节点上阻塞过长时间,如果一个master节点不可用了,我们应该尽快尝试下一个master节点。
  • 3.客户端计算第二步中获取锁所花的时间,只有当客户端在大多数master节点上成功获取了锁((N/2) +1),而且总共消耗的时间不超过锁释放时间,这个锁就认为是获取成功了。
  • 4.如果锁获取成功了,那现在锁自动释放时间就是最初的锁释放时间减去之前获取锁所消耗的时间。
  • 5.如果锁获取失败了,不管是因为获取成功的锁不超过一半(N/2+1)还是因为总消耗时间超过了锁释放时间,客户端都会到每个master节点上释放锁,即便是那些他认为没有获取成功的锁。

使用Redis的分布式锁

Redis集群⽅案?新增节点后如何进⾏数据迁移。

Redis Cluster

添加一个空节点,然后将一些数据移入该节点(如果它是新的主节点),或者告诉它设置为已知节点的副本(如果它是从节点的话)

redis-cli --cluster add-node 127.0.0.1:7006 127.0.0.1:7000
add-node命令将新节点的地址指定为第一个参数,并将集群中随机存在的节点的地址指定为第二个参数.
与其他master节点相比:

由于没有分配的`hash slots`,因此不保存任何数据。(可以使用的重新分片功能为此节点分配`hash slots`)
因为它是没有分配`slots`的主机,所以当从机要成为主机时,它不会参与选举过程。

添加一个新的节点

新增节点后如何进⾏数据迁移没有找到相关资料。。。

Redis持久化机制?RDB、AOF,最新的默认⽅案是什么?

RDB的优点:

  • RDB是Redis数据的非常紧凑的单文件时间点表示。RDB文件非常适合备份。例如,您可能希望在最近的24小时内每小时存档一次RDB文件,并在30天之内每天保存一次RDB快照。这使您可以在发生灾难时轻松还原数据集的不同版本。
  • RDB对于灾难恢复非常有用,它是一个紧凑的文件,可以传输到远程数据中心或Amazon S3(可能已加密)上。
  • RDB最大限度地提高了Redis的性能,因为Redis父进程为了持久化所需要做的唯一工作就是分叉一个孩子,其余的都将做。父实例将永远不会执行磁盘I / O或类似操作。
  • 与AOF相比,RDB允许大型数据集更快地重启。

RDB的缺点:

  • 如果您需要在Redis停止工作(例如断电后)的情况下最大程度地减少数据丢失的机会,则RDB不好。您可以在生成RDB的位置配置不同的保存点(例如,在至少五分钟之后,对数据集进行100次写入,但是您可以有多个保存点)。但是,通常会每隔五分钟或更长时间创建一次RDB快照,因此,如果Redis出于任何原因在没有正确关闭的情况下停止工作,则应该准备丢失最新的数据分钟。
  • RDB需要经常使用fork()才能使用子进程将其持久化在磁盘上。如果数据集很大,Fork()可能很耗时,并且如果数据集很大且CPU性能不佳,则可能导致Redis停止为客户端服务几毫秒甚至一秒钟。AOF还需要fork(),但您可以调整要重写日志的频率,而无需在持久性上进行权衡。

AOF的优点:

  • 使用AOF Redis更加持久:您可以有不同的fsync策略:完全没有fsync,每秒fsync,每个查询fsync。使用默认策略fsync时,每秒的写入性能仍然很好(fsync是使用后台线程执行的,并且在没有进行fsync的情况下,主线程将尽力执行写入操作。)但是您只能损失一秒钟的写入时间。
  • AOF日志仅是一个追加日志,因此,如果断电,也不会出现寻道或损坏问题。即使由于某种原因(磁盘已满或其他原因)以半写命令结束日志,redis-check-aof工具也可以轻松修复它。
  • Redis太大时,Redis可以在后台自动重写AOF。重写是完全安全的,因为Redis继续追加到旧文件时,会生成一个全新的文件,其中包含创建当前数据集所需的最少操作集,一旦准备好第二个文件,Redis会切换这两个文件并开始追加到新的那一个。
  • AOF以易于理解和解析的格式包含所有操作的日志。您甚至可以轻松导出AOF文件。例如,即使您使用FLUSHALL命令刷新了所有错误文件,如果在此期间未执行任何日志重写操作,您仍然可以保存数据集,只是停止服务器,删除最新命令并重新启动Redis。

AOF的缺点:

  • 对于相同的数据集,AOF文件通常大于等效的RDB文件。
  • 根据确切的fsync策略,AOF可能比RDB慢。通常,在将fsync设置为每秒的情况下,性能仍然很高,并且在禁用fsync的情况下,即使在高负载下,它也应与RDB一样快。即使在巨大的写负载情况下,RDB仍然能够提供有关最大延迟的更多保证。
  • 过去,我们在特定命令中遇到过罕见的错误(例如,其中一个涉及阻止命令,例如BRPOPLPUSH),导致生成的AOF在重载时无法重现完全相同的数据集。这些错误很少见,我们在测试套件中进行了测试,自动创建了随机的复杂数据集,然后重新加载它们以检查一切是否正常。但是,RDB持久性几乎是不可能的。为了更清楚地说明这一点:Redis AOF通过像MySQL或MongoDB那样增量更新现有状态来工作,而RDB快照一次又一次地创建所有内容,从概念上讲,它更健壮。但是-1)请注意,每次Redis重写AOF时,都会从数据集中包含的实际数据开始重新创建AOF,与始终附加AOF文件(或重写为读取旧AOF而不是读取内存中的数据)相比,提高了对错误的抵抗力。2)我们从未收到过有关真实环境中检测到的AOF损坏的用户报告。
    Redis持久化机制

redis持久化深入

Redis缓存过期与淘汰机制是什么?

  • volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
  • volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
  • volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
  • allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰
  • allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
  • no-enviction(驱逐):禁止驱逐数据

Redis的缓存穿透、缓存击穿是否了解?业界会使⽤什么⽅案?如果缓存⼤规模失效,怎么办?

缓存穿透,即黑客故意去请求缓存中不存在的数据,导致所有的请求都怼到数据库上,从而数据库连接异常。

解决方案:
(一)利用互斥锁,缓存失效的时候,先去获得锁,得到锁了,再去请求数据库。没得到锁,则休眠一段时间重试
(二)采用异步更新策略,无论key是否取到值,都直接返回。value值中维护一个缓存失效时间,缓存如果过期,异步起一个线程去读数据库,更新缓存。需要做缓存预热(项目启动前,先加载缓存)操作。
(三)提供一个能迅速判断请求是否有效的拦截机制,比如,利用布隆过滤器,内部维护一系列合法有效的key。迅速判断出,请求所携带的Key是否合法有效。如果不合法,则直接返回。

redis bloom

使⽤Redis的场景

签到,报名,投票: 位图

位图是一个byte数组,可以使用get/set直接设置跟读取整个位图的值,
也可以使用位图操作getbit/setbit等将byte数组当做位数组来处理。
Redis的位数组是自动扩展的,如果设置了阈值且当前超出了阈值会自动将数组进行扩充,
还可以使用bitcount实现快速统计,使用位图查找指令bitpos查找某范围的位图值,
两个指令也可以组合使用对某个范围的位图值进行统计。比如查找一个用户一年中每周末签到数总和。 

地理位置GEO模块:附近人功能

ZSet底层是如何实现的

ZSet底层是如何实现的

HashSet是如何实现的?

HashSet不能保证元素的顺序,TreeSet中的元素可以按照某个顺序排列。他们的元素都不能重复

HashSet是如何实现的

redis和zookeeper做分布式锁各有什么优缺点?

Redis 分布式锁,其实需要自己不断去尝试获取锁,比较消耗性能。   

Zookeeper分布式锁,获取不到锁,注册个监听器即可,不需要不断主动尝试获取锁,性能开销较小。   

Redis有什么缺点?

(一)缓存和数据库双写一致性问题
(二)缓存雪崩问题
(三)缓存击穿问题
(四)缓存的并发竞争问题

redis_cluster 不同的key的 set集合数据是在⼀个node还是多个node

为什么说redis能够快速执行

绝大部分请求是纯粹的内存操作(非常快速)
采用单线程,避免了不必要的上下文切换和竞争条件
非阻塞IO - IO多路复用

Redis常见性能问题和解决方案?

  • (1) Master最好不要做任何持久化工作,如RDB内存快照和AOF日志文件
  • (2) 如果数据比较重要,某个Slave开启AOF备份数据,策略设置为每秒同步一次
  • (3) 为了主从复制的速度和连接的稳定性,Master和Slave最好在同一个局域网内
  • (4) 尽量避免在压力很大的主库上增加从库
  • (5) 主从复制不要用图状结构,用单向链表结构更为稳定,即:Master <- Slave1 <- Slave2 <- Slave3…这样的结构方便解决单点故障问题,实现Slave对Master的替换。如果Master挂了,可以立刻启用Slave1做Master,其他不变。

为什么Redis不支持回滚

Redis命令在事务期间可能会失败,但Redis仍将执行事务的其余部分而不是回滚

  • 仅当使用错误的语法(并且在命令队列期间无法检测到该问题)或针对持有错误数据类型的键调用Redis命令时,该命令才能失败:这实际上意味着失败的命令是编程错误的结果,还有一种很可能在开发过程中而不是生产过程中发现的错误。

  • Redis在内部得到了简化和更快,因为它不需要回滚的能力。

  • 为什么Redis不支持回滚

    Redis使用epoll异步非阻塞模型 ,Redis自身实现了事件处理模型将epoll中的链接、读写、关闭都转换为事件,不在网络IO上浪费过多的事

    redis使用epoll

    升级redis集群节点

    升级从节点:

    停止节点并使用更新版本的Redis重新启动它即可。
    如果存在使用从属节点扩展读取的客户端,则在给定的从属节点不可用时,
    它们应该能够重新连接到其他从属节点

升级master节点:

  • 使用CLUSTER FAILOVER触发主服务器到其从服务器之一的手动故障转移。
  • 等待主机变成从机。
  • 最后,像对从属服务器一样升级节点。
  • 如果要将主节点作为刚刚升级的节点,请触发新的手动故障转移,以将升级后的节点转换回主节点。

升级redis集群节点

##
参考资料:

redis常用命令解析

redis_commands_list

redis-FAQ

docker小结

容器和虚拟机

容器在Linux上本地运行,并与其他容器共享主机的内核。它运行一个离散进程,不占用任何其他可执行文件更多的内存,从而使其轻巧。

相比之下,虚拟机(VM)运行具有“ 虚拟机管理程序”对主机资源的虚拟访问权的成熟guest操作系统。通常,VM会产生大量开销,超出了应用程序逻辑所消耗的开销。

docker和虚拟机

安装docker

windows下安装docker

在Windows上安装Docker Desktop

下载Docker Desktop

需要注意的是:

运行Docker Desktop需要Microsoft Hyper-V。必要时,Docker桌面Windows安装程序会启用Hyper-V,然后重新启动计算机。启用Hyper-V后,VirtualBox不再起作用。但是,将保留所有现有的VirtualBox VM映像。

用docker-machine(包括default通常在Toolbox安装过程中创建的虚拟机)创建的VirtualBox VM 不再启动。这些VM不能与Docker Desktop并排使用。但是,您仍然可以 docker-machine用来管理远程VM。

安装Docker后VMware和VirtualBox无法启动 VT-x is not available

linux下安装docker

用yum源安装

查看是否已安装docker列表

yum list installed | grep docker

安装docker

yum -y install docker

启动docker

systemctl start docker

查看docker服务状态

systemctl status docker

配置 加速器

对于使用 upstart 的系统而言,编辑 /etc/default/docker 文件,在其中的 DOCKER_OPTS 中
添加获得的加速器配置:

DOCKER_OPTS="--registry-mirror=https://registry.docker-cn.com"

重新启动服务。

$ sudo service docker restart

对于使用 systemd 的系统,请在 /etc/docker/daemon.json 中写入如下内容(如果文件不存
在请新建该文件)

{
    "registry-mirrors": [
        "https://registry.docker-cn.com"
    ]
}

注意,一定要保证该文件符合 json 规范,否则 Docker 将不能启动。
之后重新启动服务。

$ sudo systemctl daemon-reload
$ sudo systemctl restart docker    

创建镜像

基于dockerfile创建镜像

编写docker file

dockerfile命令参考

# Use the official image as a parent image
FROM node:current-slim

# Set the working directory
WORKDIR /usr/src/app

# Copy the file from your host to your current location
COPY package.json .

# Run the command inside your image filesystem
RUN npm install

# Inform Docker that the container is listening on the specified port at runtime.
EXPOSE 8080

# Run the specified command within the container.
CMD [ "npm", "start" ]

# Copy the rest of your app's source code from your host to your image filesystem.
COPY . .    

dockerfile 命令

MAINTAINER

格式
MAINTAINER <name>
指定该dockerfilr文件的维护者信息。

RUN

格式:
RUN <command>                             (shell模式)
RUN["executable", "param1","param2"]      (exec模式)

表示当前镜像构建时候运行的命令,如果有确认输入的话,一定要在命令中添加 -v

如果命令较长,那么可以在命令结尾使用 \ 换行

shell模式:类似于 /bin/bash -c command

实例:RUN echo hello

exec模式:类似于 RUN["/bin/bash", "-c", "command"]

实例: RUN["echo", "hello"]

EXPOSE

格式:EXPOSE <port> [<port>...]

设置docker容器对外暴露的端口号,docker 为了安全,不会自动对外打开端口,如果需要外部提供访问,还需要启动容器时增加-p或者-P参数对容器的端口进行分配。

基于容器创建镜像

不要使用 docker commit 定制镜像,定制镜像应该使用 Dockerfile 来完成

docker commit :从容器创建一个新的镜像。

语法

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

OPTIONS说明:

  • a :提交的镜像作者;

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

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

  • p :在commit时,将容器暂停。

示例:

[root@JD ~]# docker ps -a |grep mysql
e718ed5b4e32        mysql:5.7                           "docker-entrypoint.s…"   3 weeks ago         Up 3 weeks                 0.0.0.0:3306->3306/tcp, 33060/tcp                                mysql
[root@JD ~]# docker commit -a "mujia_prince" -m "test docker commit mysql" e718ed5b4e32 my-test-mysql:v1.0
sha256:bb9c07f10ff04f0fa0f486afc07221110029e13ab3dd61bc495b7bd5ad93978f

[root@JD ~]# docker images -a
REPOSITORY                     TAG                 IMAGE ID            CREATED             SIZE
my-test-mysql                  v1.0                bb9c07f10ff0        41 seconds ago      437MB
mysql                          5.7                 db39680b63ac        3 months ago        437MB

搭建镜像仓库

搭建docker镜像仓库

docker volumes

docker volumes

-v:把宿主机的/root/data/registry目录绑定到容器/var/lib/registry目录(这个目录是registry容器中存放镜像文件的目录),来实现数据的持久化;

示例:

docker run -itd -v /root/data/registry:/var/lib/registry -p 5000:5000 --restart=always --name docker-registry registry:latest 

在镜像仓库中推送镜像registry-mysql之后,在/root/data/registry下能找到:

[root@JD repositories]# pwd
/root/data/registry/docker/registry/v2/repositories
[root@JD repositories]# ll
total 0
drwxr-xr-x 5 root root 55 Apr  1 23:32 registry-mysql

使用 -v 参数时如果本地目录不存在 Docker 会自动为创建一个文件夹,现在使用 –mount 参数时如果本地目录不存在,Docker 会报错

挂载一个本地主机文件作为数据卷

–mount 标记也可以从主机挂载单个文件到容器中

docker run --rm -it \
# -v $HOME/.bash_history:/root/.bash_history \
--mount type=bind,source=$HOME/.bash_history,target=/root/.bash_history \
ubuntu:17.10 \
bash

--rm :在Docker容器退出时,默认容器内部的文件系统仍然被保留,以方便调试并保留用户数据。

但是,对于foreground容器,由于其只是在开发调试过程中短期运行,其用户数据并无保留的必要,因而可以在容器启动时设置–rm选项,这样在容器退出时就能够自动清理容器内部的文件系统

docker volume container

数据备份

docker compose

docker compose install

1
2
3
4
5
6
$ sudo curl -L "https://github.com/docker/compose/releases/download/1.24.1/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose

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

$ docker-compose --version
docker-compose version 1.24.1, build 1110ad01

常用命令

删除已经停止的容器

$ docker rm [OPTIONS] CONTAINER [CONTAINER...]
docker rm 2d85b99b03b2

查看镜像、容器、数据卷所占用的空间

docker system df   

查看当前宿主机开放了哪些端口

netstat -tnulp

查看docker 信息

docker inspect [image id]

go 函数小结

函数

defer

go语言中函数的return不是原子操作,在底层分为两层操作

第一步:返回值赋值

defer

第二步:真正的ret返回

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
func f1() int {
x := 5
defer func() {
x++
}()
return x
}

func f2() (x int) {
defer func() {
x++
}()
return 5
}

func f3() (y int) {
x := 5
defer func() {
x++
}()
return x
}
func f4() (x int) {
defer func(x int) {
x++
}(x)
return 5
}
func f5()(x int){
defer func(x int) int{
x++
return x
}(x)
return 5
}

func f6(x int){
defer func(x *int){
(*x)++
}(*x)
return 5
}
func main() {
fmt.Println(f1()) //5
fmt.Println(f2()) //6
fmt.Println(f3()) //5
fmt.Println(f4()) //5
fmt.Println(f5()) //5
fmt.Println(f6()) //6
}

defer 会将值先赋值到函数中

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
func main(){
a := 1
b := 2
defer calc("1", a, calc("10", a, b))
a = 0
defer calc("2", a, calc("20", a, b))
b = 1
}


func calc(index string, a, b int) int{
ret := a +b
fmt.Println(index, a, b, ret)
// 20 0 1
// 2 0 1
// 10 0 1
// 1 0 1
return ret
}

----------------
10 1 2 3
20 0 2 2
2 0 2 2
1 1 3 4

闭包

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

func f11(f func()){
fmt.Println("this is f1")
f()
}

func f22(x, y int) {
fmt.Println("this is f2")
fmt.Println(x + y)
}

func f33(f func(int, int), x, y int) func(){
tmp := func(){
f(x, y)
}
return tmp
}
//实现f1(f2)
func main(){
ret := f33(f22, 100, 200)
f11(ret)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func adder() func(int) int {
var x int
return func(y int) int {
x += y
return x
}
}
func main() {
var f = adder()
fmt.Println(f(10)) //10
fmt.Println(f(20)) //30
fmt.Println(f(30)) //60

f1 := adder()
fmt.Println(f1(40)) //40
fmt.Println(f1(50)) //90
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func calc(base int) (func(int) int, func(int) int) {
add := func(i int) int {
base += i
return base
}

sub := func(i int) int {
base -= i
return base
}
return add, sub
}

func main() {
f1, f2 := calc(10)
fmt.Println(f1(1), f2(2)) //11 9
fmt.Println(f1(3), f2(4)) //12 8
fmt.Println(f1(5), f2(6)) //13 7
}

1、

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func calc(index string, a, b int) int {
ret := a + b
fmt.Println(index, a, b, ret)
return ret
}

func main() {
x := 1
y := 2
defer calc("AA", x, calc("A", x, y))
x = 10
defer calc("BB", x, calc("B", x, y))
y = 20
}

上面代码的输出结果是?

go指针小结

指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func main() {
var a int= 20 /* 声明实际变量 */
var ip *int /* 声明指针变量 */

ip = &a /* 指针变量的存储地址 */

fmt.Printf("a 变量的地址是: %x\n", &a )

/* 指针变量的存储地址 */
fmt.Printf("ip 变量储存的指针地址: %x\n", ip )

/* 使用指针访问值 */
fmt.Printf("*ip 变量的值: %d\n", *ip )
}

空指针

当一个指针被定义后没有分配到任何变量时,它的值为 nil。

nil 指针也称为空指针。

nil在概念上和其它语言的null、None、nil、NULL一样,都指代零值或空值。

1
2
3
4
5
6
7
8
9
10
11
12
    var a *int
*a = 100
fmt.Println(*a)

运行时异常:
panic: runtime error: invalid memory address or nil pointer dereference

var a = new(int)
fmt.Println(a) //<nil>
fmt.Println(*a) // 0
*a = 100
fmt.Println(*a) //100

make

###make和new区别

1、都是用来申请内存的。

2、 make只适用于 slice,map和 channle的内存创建,而且他返回的类型就是他们三个类型本身,而不是他们的指针类型,因为他们是引用类型,所以就没有必要返回他们的指针了。

3、 new很少用,一般用来给基本数据类型申请内存, stringint;返回的都是对应的类型的指针(*string, *int)

map

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var m1  map[string]int
m1 = make(map[string]int, 10)
m1["m1"] = 1
m1["m2"] = 2

fmt.Println(m1)
//若key不存在,则返回0
fmt.Println(m1["m3"]) // 0

for k := range m1{
fmt.Println(k)
}
for k, v := range m1{
fmt.Println(k, v)
}
for _, v := range m1{
fmt.Println(v)
}

map 排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

rand.Seed(time.Now().UnixNano())

var sortMap = make(map[string]int, 100)
var keys = make([]string, 0, 200)
for i := 1; i<100; i++ {
key := fmt.Sprintf("student_%02d", i)
value := rand.Intn(100)
sortMap[key] = value
keys = append(keys, key)
}

sort.Strings(keys)

for _, k := range keys {
fmt.Println(k, sortMap[k])
}

###值为map的切片

1
2
3
4
5
6
7

var s1 = make([]map[string]int, 10, 10)
s1[0] = make(map[string]int, 10)
s1[0]["北京"] = 100
fmt.Println(s1)

//[map[北京:100] map[] map[] map[] map[] map[] map[] map[] map[] map[]]

值为切片的map

1
2
3
4
5
6
7

var m1 = make(map[string][]int, 10)
m1["北京"] = make([]int, 10, 10)
m1["北京"][0] = 1
fmt.Println(m1)

//map[北京:[1 0 0 0 0 0 0 0 0 0]]

统计一个字符串中每个单词出现的次数,比如”how do you do “ 中 how 1次,do 2次,you 1次

var map1 = make(map[string]int, 10)
var str1 = "how do you do how do you do how do you do"
var s1 = make([]string, 10, 10)
s1 = strings.Split(str1, " ")
fmt.Println(s1)
for _, item := range s1 {
    value, ok := map1[item]
    fmt.Println(item)
    if ok{
        value ++
        map1[item] = value
    }else {
        map1[item] = 1
    }
}
fmt.Println(map1)