首页
关于
留言
接口
搜索
首页
登录
登录
搜索
KAKA 梦很美
累计撰写
47
篇文章
累计收到
0
条评论
首页
栏目
首页
登录
页面
首页
关于
留言
接口
自定义幻灯片
2022-12-30
Dockerfile 与 Docker-Compose 的区别
先简单理解 Docker 的使用过程,它分为镜像构建与容器启动。 镜像构建 即创建一个镜像,它包含安装运行所需的环境、程序代码等。这个创建过程就是使用 Dockerfile 来完成的。 容器启动 容器最终运行起来是通过拉取构建好的镜像,通过一系列运行指令(如端口映射、外部数据挂载、环境变量等)来启动服务的。针对单个容器,这可以通过 docker run 来运行。 而如果涉及多个容器的运行(如服务编排)就可以通过 docker-compose 来实现,它可以轻松的将多个容器作为 service 来运行(当然也可仅运行其中的某个),并且提供了 scale (服务扩容) 的功能。 dockerfile - 构建镜像 docker run - 启动容器 docker-compose - 启动服务 我们从头梳理下。 假如你不用 docker, 搭建个 wordpress 项目应该怎么弄? 大概是这样的: 首先应该是找台服务器, 假设其 OS 是 CentOS, 然后按照文档一步步敲命令, 写配置。 用 docker 呢?大概是这样的: 找台服务器,不管什么操作系统,只要支持 docker 就行, 执行命令 docker run centos, docker 会从官方源里拉取最新的 CentOS 镜像,可以认为你开了个 CentOS 虚拟机,然后一步步安装,跟上面一样。 但是你发现没有? 这样安装有个显著的缺点,一旦 container 被删,你做的工作就都没了。当然可以用 docker commit 来保存成镜像,这样就可以复用了。 但是镜像一般比较大,而且只分享镜像的话,别人也不知道你这镜像到底包含什么,这些问题都不利于分享和复用。 一个直观的解决方案就是,写个脚本把安装过程全部记录下来,这样再次安装的时候,执行脚本就行了。 Dockerfile 就是这样的脚本,它记录了一个镜像的制作过程。 有了 Dockerfile, 只要执行 docker build . 就能制作镜像,而且 Dockerfile 就是文本文件,修改也很方便。 现在有了 wordpress 的镜像,只需要 docker run 就把 wordpress 启动起来了。 如果仅仅是 wordpress, 这也就够了。但是很多时候,需要多个镜像合作才能启动一个服务,比如前端要有 Nginx ,数据库 Mysql, 邮件服务等,当然你可以把所有这些都弄到一个镜像里去,但这样做就无法复用了。 更常见的是, nginx, mysql, smtp 都分别是个镜像,然后这些镜像合作,共同服务一个项目。 docker-compose 就是解决这个问题的。你的项目需要哪些镜像,每个镜像怎么配置,要挂载哪些 volume 等信息都包含在 docker-compose.yml 里。 要启动服务,只需要 docker-compose up 就行,停止也只需要 docker-compose stop/down 简而言之, Dockerfile 记录单个镜像的构建过程。 docker-compose.yml 记录一个项目(project, 一般是多个镜像)的构建过程。 有些教程用了 dockerfile + docker-compose, 是因为 docker-compose.yml 本身没有镜像构建的信息,如果镜像是从 docker registry 拉取下来的,那么 Dockerfile 就不需要了; 如果镜像是需要 build 的,那就需要提供 Dockerfile。 docker-compose 是 编排容器 的。例如, 你有一个php镜像,一个mysql镜像,一个nginx镜像。如果没有docker-compose,那么每次启动的时候,你需要敲各个容器的启动参数,环境变量,容器命名,指定不同容器的链接参数等一系列的操作,相当繁琐。而用了docker-compose之后,你就可以把这些命令一次性写在docker-composer.yml文件中,以后每次启动这一整个环境(含3个容器)的时候,你只要敲一个docker-composer up命令就ok了。 Dockerfile 的作用是 从无到有的构建镜像。Dockerfile - 为 docker build 命令准备的,用于建立一个独立的 image 。在 docker-compose 里也可以用来实时 build docker-compose.yml - 为 docker-compose 准备的脚本,可以同时管理多个 container, 包括他们之间的关系、用官方 image 还是自己 build、各种网络端口定义、储存空间定义等。
2022年-12月-30日
158 阅读
0 评论
Docker
2022-12-28
Docker 启动 Mysql 一直报错 lower_case_table_names
操作系统 MacOS 在安装 Docker 后无法启动 Mysql8.0, 无形中遇到一个大坑, Docker 日志一直报错 Different lower_case_table_names settings for server ('2') and data dictionary ('0') , 不管怎么修改 docker-compose.yml 文件还是 mysql 配置都无济于事, 就是无法启动, 网上很多教程都是没用的。 经过发现, 主要导致无法启动 Mysql 的原因是 Docker 应用设置/首选项里(默认)选中 使用 gRPC 进行文件共享 (Use gRPC FUSE for file sharing) [gRPC Fuse 设置导致此问题, 它与数据字典0不兼容]。 解决方法 取消选中 使用 gRPC 进行文件共享 (Use gRPC FUSE for file sharing), 然后重启容器服务就可以了。 这主要还是 Docker 需要解决的应用程序错误, 而不是 Mysql 本身。 在后续的版本中, Docker 20.10.10 应该是已经修复了该问题, 因为我发现即使开了该选项也能正常启动了。
2022年-12月-28日
168 阅读
0 评论
Docker
置顶
项目内存增大解决方案之日志优化
本文主要实践在 PHP (Swoole 环境) 和 Golang 项目中的 K8S 生产环境解决方案。 内存现象 在部署到 K8S 生产环境 后用 grafana 监控工具发现项目内存很高, 明明业务服务量不算大, 却占用了大量内存。于是排查容器内存情况发现, Buffer Cache 无法释放, 一直在增长。查看代码层也并没有静态变量等现象, 于是我想到每次请求进来都会打印日志, 会不会是不间断地写文件导致文件没法关闭, 内存一直在缓冲区越来越大。为了验证我的猜测, 我尝试把请求日志暂时性关闭, 观察了一天时间发现我的猜测是正确的!内存对比也相对明显(频繁写日志会也明显), 并且容器的总内存也通过自动伸缩机制 降低了2-3个倍数 (因为内存占比是根据总内存大小计算的, 所以内存占比仅仅是反映当前总内存的占用率)!!! 下面是关闭请求日志前后的内存对比: 解决方案 知道了问题就好办了, 解决方案就是: 将日志直接输出到控制台, 运维采集控制台日志到 ELK 。 下面是最终的内存对比: 还在担心服务内存太高 连业务代码都要写的非常小心么? 还在担心写多几行复杂业务 系统炸掉么? 还在担心不敢多打印几个日志? 从此不需要再有此顾虑了!!!
2022年-12月-16日
153 阅读
0 评论
服务架构
2022-11-13
Docker 宝典|常用命令
先感受下图, 几乎所有命令分布都在里面。 登录退出 - 镜像仓库 登陆到一个Docker镜像仓库,如果未指定镜像仓库地址,默认为官方仓库 Docker Hub docker login [OPTIONS] [SERVER] # OPTIONS 说明 # -u: 登录的用户名 # -p: 登录的密码 # # docker login -u 用户名 -p 密码 登出一个Docker镜像仓库,如果未指定镜像仓库地址,默认为官方仓库 Docker Hub docker logout 镜像相关操作 从Docker Hub查找镜像 docker search [OPTIONS] TERM # OPTIONS 说明 # --automated: 只列出 automated build类型的镜像 # --no-trunc: 显示完整的镜像描述 # -f <过滤条件>: 列出收藏数不小于指定值的镜像 # # 从 Docker Hub 查找所有镜像名包含 php, 并且收藏数大于 10 的镜像 # docker search -f stars=10 php 参数说明: NAME: 镜像仓库源的名称 DESCRIPTION: 镜像的描述 OFFICIAL: 是否 docker 官方发布 STARS: 类似 Github 里面的 star,表示点赞、喜欢的意思 AUTOMATED: 自动构建 从镜像仓库中拉取或者更新指定镜像 docker pull [OPTIONS] NAME[:TAG|@DIGEST] # OPTIONS 说明 # -a: 拉取所有 tagged 镜像 # --disable-content-trust: 忽略镜像的校验, 默认开启 # # docker pull raylin666/golang:1.17 列出本地镜像 docker images [OPTIONS] [REPOSITORY[:TAG]] # OPTIONS 说明 # -a: 列出本地所有的镜像(含中间映像层,默认情况下,过滤掉中间映像层) # --digests: 显示镜像的摘要信息 # -f: 显示满足条件的镜像 # --format: 指定返回值的模板文件 # --no-trunc: 显示完整的镜像信息 # -q: 只显示镜像ID 删除本地一个或多个镜像 docker rmi [OPTIONS] IMAGE [IMAGE...] # OPTIONS 说明 # -f: 强制删除 # --no-prune: 不移除该镜像的过程镜像,默认移除 # # 强制删除本地镜像 # docker rmi -f raylin666/php:v7.3 标记本地镜像,将其归入某一仓库 docker tag [OPTIONS] IMAGE[:TAG] [REGISTRYHOST/][USERNAME/]NAME[:TAG] # 将镜像 openresty:latest 标记为 raylin666/openresty:1.19.9.1 镜像 # docker tag openresty:latest raylin666/openresty:1.19.9.1 使用 Dockerfile 创建镜像 docker build [OPTIONS] PATH | URL | - # OPTIONS 说明 # --build-arg=[]: 设置镜像创建时的变量 # --cpu-shares: 设置 cpu 使用权重 # --cpu-period: 限制 CPU CFS周期 # --cpu-quota: 限制 CPU CFS配额 # --cpuset-cpus: 指定使用的CPU ID # --cpuset-mems: 指定使用的内存 ID # --disable-content-trust: 忽略校验,默认开启 # -f: 指定要使用的 Dockerfile 路径 # --force-rm: 设置镜像过程中删除中间容器 # --isolation: 使用容器隔离技术 # --label=[]: 设置镜像使用的元数据 # -m: 设置内存最大值 # --memory-swap: 设置Swap的最大值为内存+swap,"-1"表示不限swap # --no-cache: 创建镜像的过程不使用缓存 # --pull: 尝试去更新镜像的新版本 # --quiet, -q: 安静模式,成功后只输出镜像 ID # --rm: 设置镜像成功后删除中间容器 # --shm-size: 设置 /dev/shm 的大小,默认值是 64M # --ulimit: Ulimit 配置 # --squash: 将 Dockerfile 中所有的操作压缩为一层 # --tag, -t: 镜像的名字及标签,通常 name:tag 或者 name 格式;可以在一次构建中为一个镜像设置多个标签 # --network: 默认 default。在构建期间设置 RUN 指令的网络模式 # # 1. 使用当前目录的 Dockerfile 创建镜像,标签为 raylin666/php:7.4 # docker build -t raylin666/php:7.4 . # # 2. 使用URL github.com/creack/docker-firefox 的 Dockerfile 创建镜像 # docker build github.com/creack/docker-firefox # # 3. 也可以通过 -f Dockerfile 文件的位置 # docker build -f /path/to/php/Dockerfile . 将本地的镜像上传到镜像仓库,要先登陆到镜像仓库。可以通过 docker build 构建自己的镜像再上传, 也可以通过 docker tag 标记本地镜像到某个仓库后再上传。 docker push [OPTIONS] NAME[:TAG] # OPTIONS 说明 # --disable-content-trust: 忽略镜像的校验, 默认开启 # # docker push raylin666/php:v7.4.26 查看指定镜像的创建历史 docker history [OPTIONS] IMAGE # OPTIONS 说明 # -H: 以可读的格式打印镜像大小和日期,默认为TRUE # --no-trunc: 显示完整的提交记录 # -q: 仅列出提交记录ID # # 查看本地镜像 raylin666/mysql:8.0.27 的创建历史 # docker history raylin666/mysql:8.0.27 将指定镜像保存成 tar 归档文件 docker save [OPTIONS] IMAGE [IMAGE...] # OPTIONS 说明 # -o: 输出到的文件 # # 将镜像 raylin666/php:7.4 生成 my_php74.tar 文档 # docker save -o my_php74.tar raylin666/php:7.4 导入使用 docker save 命令导出的镜像 docker load [OPTIONS] # OPTIONS 说明 # --input, -i: 指定导入的文件,代替 STDIN # --quiet, -q: 精简输出信息 # # docker load < busybox.tar.gz # docker load --input fedora.tar 将文件系统作为一个 tar 归档文件导出到 STDOUT docker export [OPTIONS] CONTAINER # OPTIONS 说明 # -o: 将输入内容写到文件 # # 将ID为 a404c6c174a2 的容器按日期保存为tar文件 # docker export -o mysql-`date +%Y%m%d`.tar a404c6c174a2 从归档文件中创建镜像 docker import [OPTIONS] file|URL|- [REPOSITORY[:TAG]] # OPTIONS 说明 # -c: 应用 docker 指令创建镜像 # -m: 提交时的说明文字 # # 从镜像归档文件 my_php74.tar 创建镜像,命名为 raylin666/php:7.4 # docker import my_php74.tar raylin666/php:7.4 容器相关操作 创建一个新的容器并运行一个命令 docker run [OPTIONS] IMAGE [COMMAND] [ARG...] # OPTIONS 说明 # -a stdin: 指定标准输入输出内容类型,可选 STDIN/STDOUT/STDERR 三项 # -d: 后台运行容器,并返回容器ID # -i: 以交互模式运行容器,通常与 -t 同时使用 # -P: 随机端口映射,容器内部端口随机映射到主机的端口 # -p: 指定端口映射,格式为: 主机(宿主)端口:容器端口, 例如: 8080:80 # -t: 为容器重新分配一个伪输入终端,通常与 -i 同时使用 # --name="nginx": 为容器指定一个名称 # --dns 8.8.8.8: 指定容器使用的DNS服务器,默认和宿主一致 # --dns-search example.com: 指定容器DNS搜索域名,默认和宿主一致 # -h "bars": 指定容器的 hostname # -e username="raylin666": 设置环境变量 # --env-file=[]: 从指定文件读入环境变量 # --cpuset="0-2" or --cpuset="0,1,2": 绑定容器到指定CPU运行 # -m: 设置容器使用内存最大值 # --net="bridge": 指定容器的网络连接类型,支持 bridge/host/none/container: 四种类型 # --link=[]: 添加链接到另一个容器 # --expose=[]: 开放一个端口或一组端口 # --volume, -v: 绑定一个卷 # # 1. 使用 docker 镜像 nginx:latest 以后台模式启动一个容器, 并将容器命名为 mynginx # docker run --name mynginx -d nginx:latest # # 2. 使用镜像 nginx:latest 以后台模式启动一个容器, 并将容器的 80 端口映射到主机随机端口 # docker run -P -d nginx:latest # # 3. 使用镜像 nginx:latest,以后台模式启动一个容器, 将容器的 80 端口映射到主机的 80 端口, 主机的目录 /www/data 映射到容器的 /data # docker run -p 80:80 -v /www/data:/data -d nginx:latest # # 4. 绑定容器的 8080 端口,并将其映射到本地主机 127.0.0.1 的 80 端口上 # docker run -p 127.0.0.1:80:8080/tcp ubuntu bash # # 5. 使用镜像 nginx:latest 以交互模式启动一个容器, 在容器内执行 /bin/bash 命令 # docker run -it nginx:latest /bin/bash 启动一个或多个已经被停止的容器 docker start [OPTIONS] CONTAINER [CONTAINER...] 停止一个运行中的容器 docker stop [OPTIONS] CONTAINER [CONTAINER...] 重启容器 docker restart [OPTIONS] CONTAINER [CONTAINER...] 杀掉一个运行中的容器 docker kill [OPTIONS] CONTAINER [CONTAINER...] # OPTIONS 说明 # -s: 向容器发送一个信号 # # 杀掉运行中的容器 mynginx # docker kill -s KILL mynginx 删除一个或多个容器 docker rm [OPTIONS] CONTAINER [CONTAINER...] # OPTIONS 说明 # -f: 通过 SIGKILL 信号强制删除一个运行中的容器 # -l: 移除容器间的网络连接,而非容器本身 # -v: 删除与容器关联的卷 # # 1. 强制删除容器 db01、db02 # docker rm -f db01 db02 # # 2. 移除容器 nginx01 对容器 db01 的连接,连接名 db # docker rm -l db # # 3. 删除容器 nginx01, 并删除容器挂载的数据卷 # docker rm -v nginx01 # # 4. 删除所有已经停止的容器 # docker rm $(docker ps -a -q) 暂停容器中所有的进程 (暂停容器提供服务) docker pause CONTAINER [CONTAINER...] 恢复容器中所有的进程 (恢复容器提供服务) docker unpause CONTAINER [CONTAINER...] 创建一个新的容器但不启动它 [用法同 docker run 一样] docker create [OPTIONS] IMAGE [COMMAND] [ARG...] 在运行的容器中执行命令 docker exec [OPTIONS] CONTAINER COMMAND [ARG...] # OPTIONS 说明 # -d: 分离模式 - 在后台运行 # -i: 即使没有附加也保持 STDIN 打开 # -t: 分配一个伪终端 # # 1. 在容器 mynginx 中以交互模式执行容器内 /root/runoob.sh 脚本 # docker exec -it mynginx /bin/sh /root/runoob.sh # # 2. 在容器 mynginx 中开启一个交互模式的终端 # docker exec -i -t [容器名称或容器ID] /bin/bash 列出容器列表 docker ps [OPTIONS] # OPTIONS 说明 # -a: 显示所有的容器,包括未运行的 # -f: 根据条件过滤显示的内容 # --format: 指定返回值的模板文件 # -l: 显示最近创建的容器 # -n: 列出最近创建的n个容器, 比如 docker ps -n 3 # --no-trunc: 不截断输出 # -q: 静默模式,只显示容器编号 # -s: 显示总的文件大小 # # docker ps 输出详情介绍: CONTAINER ID: 容器 ID IMAGE: 使用的镜像 COMMAND: 启动容器时运行的命令 CREATED: 容器的创建时间 STATUS: 容器状态 - 状态有7种 已创建: created 重启中: restarting 运行中: running 迁移中: removing 暂停中: paused 已停止: exited 已死亡: dead PORTS: 容器的端口信息和使用的连接类型(tcp\udp) NAMES: 自动分配的容器名称 获取容器/镜像的元数据 docker inspect [OPTIONS] NAME|ID [NAME|ID...] # OPTIONS 说明 # -f: 指定返回值的模板文件 # -s: 显示总的文件大小 # --type: 为指定类型返回JSON # # 1. 获取镜像 raylin666/openresty:1.19.9.1 的元信息 # docker inspect raylin666/openresty:1.19.9.1 # # 2. 获取正在运行的容器 openresty 的 IP # docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' openresty 查看容器中运行的进程信息,支持 ps 命令参数 docker top [OPTIONS] CONTAINER [ps OPTIONS] 容器运行时不一定有 /bin/bash 终端来交互执行 top 命令,而且容器还不一定有top命令,可以使用docker top来实现查看container中正在运行的进程。 查看容器 php 的进程信息 查看所有运行容器的进程信息 连接到正在运行中的容器 docker attach [OPTIONS] CONTAINER 要attach上去的容器必须正在运行,可以同时连接上同一个container来共享屏幕(与screen命令的attach类似)。 官方文档中说attach后可以通过CTRL-C来 detach,但实际上经过我的测试,如果 container 当前在运行 bash,CTRL-C 自然是当前行的输入,没有退出; 如果 container 当前正在前台运行进程,如输出 nginx 的 access.log 日志,CTRL-C 不仅会导致退出容器,而且还 stop 了。这不是我们想要的,detach 的意思按理应该是脱离容器终端,但容器依然运行。好在 attach 是可以带上--sig-proxy=false来确保CTRL-D或CTRL-C不会关闭容器。 从服务器获取实时事件 docker events [OPTIONS] # OPTIONS 说明 # -f: 根据条件过滤事件 # --since: 从指定的时间戳后显示所有事件 # --until: 流水时间显示到指定的时间为止 # # 1. 显示 docker 2021年11月28日后的所有事件 # docker events --since="1638091082" # # 2. 显示 docker 镜像为 mysql:8.0 2021年11月28日后的相关事件 # docker events -f "image"="mysql:8.0" --since="1638091082" # # 如果指定的时间是到秒级的,需要将时间转成时间戳。如果时间为日期的话,可以直接使用,如--since="2021-11-28"。 获取容器的日志 docker logs [OPTIONS] CONTAINER # OPTIONS 说明 # -f: 跟踪日志输出 # --since: 显示某个开始时间的所有日志 # -t: 显示时间戳 # --tail: 仅列出最新N条容器日志 # # 1. 跟踪查看容器 mysql 的日志输出 # docker logs -f mysql # # 2. 查看容器 php 从2021年11月27日后的最新10条日志 # docker logs --since="2021-11-27" --tail=10 php 阻塞运行直到容器停止,然后打印出它的退出代码 docker wait [OPTIONS] CONTAINER [CONTAINER...] 列出指定的容器的端口映射,或者查找将 PRIVATE_PORT NAT到面向公众的端口 docker port [OPTIONS] CONTAINER [PRIVATE_PORT[/PROTO]] ➜ test docker port openresty 80/tcp -> 0.0.0.0:19100 从容器创建一个新的镜像 docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]] # OPTIONS 说明 # -a: 提交的镜像作者 # -c: 使用 Dockerfile 指令来创建镜像 # -m: 提交时的说明文字 # -p: 在 commit 时,将容器暂停 # # 将容器 a404c6c174a2 保存为新的镜像, 并添加提交人信息和说明信息 # docker commit -a "raylin666" -m "my openresty" a404c6c174a2 raylin666/openresty:1.19.9.1 用于容器与主机之间的数据拷贝 docker cp [OPTIONS] CONTAINER:SRC_PATH DEST_PATH|- docker cp [OPTIONS] SRC_PATH|- CONTAINER:DEST_PATH # OPTIONS 说明 # -L: 保持源目标中的链接 # # 1. 将主机 /www/html 目录拷贝到容器 96f7f14e99ab 的 /www 目录下 # docker cp /www/html 96f7f14e99ab:/www/ # # 2. 将主机 /www/html 目录拷贝到容器 96f7f14e99ab 中,目录重命名为 www # docker cp /www/html 96f7f14e99ab:/www # # 3. 将容器 96f7f14e99ab 的 /www 目录拷贝到主机的 /tmp 目录中 # docker cp 96f7f14e99ab:/www /tmp/ 检查容器里文件结构的更改 docker diff [OPTIONS] CONTAINER ➜ test docker diff openresty C /run C /run/openresty A /run/openresty/nginx-uwsgi A /run/openresty/nginx-client-body A /run/openresty/nginx-fastcgi A /run/openresty/nginx-proxy A /run/openresty/nginx-scgi
2022年-11月-13日
157 阅读
0 评论
Docker
2022-10-12
开发PHP扩展
PHP 的底层是由 c/c++ 实现的,所以 PHP 本身就是一层皮,而我们也可以通过开发一些扩展来增强 PHP 的能力。可以通过 php -m 或 phpinfo() 来查看环境中已安装的扩展。这里使用的PHP 版本是 7.4, 为了方便演示 PHP源码文件在 /Users/linshan/Container/C/php-7.4.24, 编译安装的目录为 /Users/linshan/Container/C/php-7.4.24/php74。 生成扩展 进入源码目录下的 ext 目录, 可以看到下面有个 ext_skel.php 文件,该文件是用来生成扩展工程的脚手架命令。 如上命令, 生成名称为 goext 的 PHP 扩展, 在 ext 目录下会生成 goext 文件夹, 该目录下将存放相关扩展的内容。对于 PHP7 是不需要对 php_goext.h 和 goext.c 文件做任何其他变更,可以直接添加自己需要的函数。 打开 goext.c 找一个有利的位置添加以下代码: PHP_FUNCTION(go_version) { php_printf("v1.17"); } 然后找到 static const zend_function_entry goext_functions[] 这行, 将定义的函数添加进来: 到此, PHP 最简单的扩展就已经完成了, go_version 函数的定义在这里的寓意为: 假设用来获取 golang 的版本号。 编译安装 写完了扩展总得检查看看能不能编译吧。接下来我演示下编译安装 goext 扩展。 首先, 进入到 goext 扩展目录。 cd /Users/linshan/Container/C/php-7.4.24/ext/goext 执行 phpize 命令来生成相应的 configure 文件, 已便后续的编译扩展。 /Users/linshan/Container/C/php-7.4.24/php74/bin/phpize 配置安装扩展参数 ./configure --with-php-config=/Users/linshan/Container/C/php-7.4.24/php74/bin/php-config 接下来就是编译安装。 make && make install 如无意外, 您将看到类似下面的界面, 表示编译成功了, PHP 会将 so 文件移动到 PHP 扩展的默认文件路径下。 ini 添加扩展 既然扩展编译安装完成, 那我们就看看能不能加入到 PHP 扩展里并使用吧。 查看下 php.ini 所在的位置。 /Users/linshan/Container/C/php-7.4.24/php74/bin/php -i |grep php.ini 打开 php.ini 文件并加入 goext 扩展。 vi /Users/linshan/Container/C/php-7.4.24/php74/lib/php.ini 检查下 goext 扩展是否安装成功。 /Users/linshan/Container/C/php-7.4.24/php74/bin/php -m // 当然, 您也可以使用命令: /Users/linshan/Container/C/php-7.4.24/php74/bin/php --ri goext 代码调试 简单检查看看能不能调用里面定义的 go_version 函数。 /Users/linshan/Container/C/php-7.4.24/php74/bin/php -r "echo go_version();" 扩展运行成功, 完美输出内容!!!
2022年-10月-12日
179 阅读
0 评论
PHP
置顶
Lua5.3 脚本语言入门
简介 Lua 是一种强大、高效、轻量级、可嵌入的 脚本语言。它支持 过程式编程、面向对象编程、函数式编程、数据驱动编程和数据描述。Lua 将简单的过程语法与基于关联数组和可扩展语义的强大数据描述结构相结合。Lua 是 动态类型 的,通过使用基于寄存器的虚拟机解释字节码来运行(即: 需要借助虚拟机的解释才能被执行, 所以又称之为 Lua 虚拟机),并具有 自动内存管理 和 增量垃圾收集,使其成为配置、脚本和快速原型设计的理想选择。 Lua 被实现为一个库, 用干净的 C 编写, 标准 C 和 C++ 的公共子集。 作为一种扩展语言,Lua 没有“主”程序的概念: 它嵌入在主机客户端中,称为嵌入程序或简称为主机。(通常 这个宿主是独立lua程序)宿主程序可以调用函数来执行一段Lua代码,可以写和读Lua变量,可以注册C函数供Lua代码调用。通过使用 C 函数,可以增强 Lua 以应对广泛的不同领域,从而创建共享语法框架的定制编程语言。 数据类型 (八种) number: 数字类型, 内部以 double 表示, 比如 year = 2022 nil: nil 类型, 表示没有任何有效值, 只要是没有声明的值, 它就是 nil string: 字符串类型, 存储文本数据, 比如 str = 'hello world' boolean: 布尔类型, 只有两个可选值: true (真) 和 false (假) 【 Lua 把 false 和 nil 看作是 false,其他的都为 true(包括 0 这个值,也是相当于 true)】 function: 表示用C语言或Lua编写的方法 table: 异构的 Hash 表, 表内可以包含任意类型的数据 userdata: 用户(非脚本用户)定义的 C 数据结构。脚本用户只能使用它,不能定义 thread: 表示独立的执行线程,它用于实现协同程序。 Lua 虚拟机 虚拟机相对于物理机,借助于操作系统对物理机器(CPU等硬件)的一种模拟、抽象,主要扮演CPU和内存的作用。 执行字节码中的指令,管理全局状态(global_state)、数据栈(StackValue)和函数调用链状态(CallInfo) 通过调用 c api 创建 luaL_newstate(lauxlib.c),lua_State结构体代表一个 Lua虚拟机,可同时创建多个,内部为单线程多实例实现,意味着各自创建的虚拟机栈相互隔离。 如下为 Lua 虚拟机体系结构图 (根据v5.4.3源码整理): Lua源码文件通过语法词法分析(llex.c/lparser.c)生成Lua的字节码文件(指令集), 再通过Lua虚拟机解析字节码,并执行其中的指令集最后输出结果。 如下为 Lua 的解析过程: 如下为 Lua 的执行流程: 基本语法 全局变量与局部变量 -- 全局变量(不会自动销毁释放内存资源) 在代码运行周期从头到尾,都不会被销毁,而且随处都可调用。当我们代码量增加,很多时候大量新建全局变量会导致内存激增。 str = "hello world" print(str) -- 输出: hello world -- 获取全局变量 _G str1 = "global" print(_G) -- 输出: table: 0x3 [显然, _G 是个 table 类型] print(_G['str']) -- 输出: hello world -- 局部变量 通过 `local` 关键字标志来新建局部(临时)变量, 局部变量只在被声明的那个代码块内有效。 str2 = 'abc' function connect() local str2 = 1 end connect() print(str2) -- 输出: abc -- 类型函数, `type` 函数能准确的确定变量的类型 print(type("hello world")) -- 输出: string 变量赋值 -- 使用等号对左边的变量进行赋值, 可以对多个变量同时赋值,变量用逗号分开,赋值语句右边的值会依次赋给左边的变量。 --[[ 当左右值的数量不一致时,Lua会进行下面的设定: 变量个数 > 值的个数:按变量个数补足nil 变量个数 < 值的个数:多余的值会被忽略 ]] a, b, c = 0, 1 print(a, b, c) -- 输出: 0, 1, nil a, b = a+1, b+1, b+2 print(a, b) -- 输出: 1, 2 a, b, c = 0 print(a, b, c) -- 输出: 0, nil, nil 交换变量 a = 34 b = 12 -- 在这里进行交换变量的操作, print(a, b) 需要输出 12, 34 a, b = b, a print(a, b) -- 输出: 12, 34 算术运算符 --[[ + 加法 - 减法 * 乘法 / 除法 % 取余,求出除法的余数 ^ 乘幂,计算次方 - 负号,取负值 ]] a = 21 b = 10 c = a + b print(c) -- 输出: 31 c = a - b print(c) -- 输出: 11 c = a * b print(c) -- 输出: 210 c = a / b print(c) -- 输出: 2.1 c = a % b print(c) -- 输出: 1 c = a^b print(c) -- 输出: 16679880978201.0 c = -a print(c) -- 输出: -21 c = a * (b - a) print(c) -- 输出: -231 字符串相关 字符串三种表示方式 单引号间的一串字符 双引号间的一串字符 双中括号的一串字符 str = '单引号间的一串字符' str1 = "双引号间的一串字符" str2 = [[ hello world 你好啊朋友 双中括号的一串字符 ]] 转义字符, 以 \ 开头的都是转义字符,下面时常用的转义字符格式 转义字符 含义 \n 换行(LF),将当前位置移到下一行开头 \r 回车(CR),将当前位置移到本行开头 \|反斜杠字符\ ' 单引号 " 双引号 \0 空字符(NULL) \ddd 1到3位八进制数所代表的任意字符 \xhh 1到2位十六进制所代表的任意字符 例如,如果我们想给str赋值一个单引号,一个双引号(’”),那么我们可以这样写: str = "\'\"" -- 输出: '" 例如,新建一个变量str, 需要输出为 ab\cd"ef'g\h]] str = "ab\\cd\"ef'g\\h]]" print(str) -- 输出: ab\cd"ef'g\h]] 字符串拼接 s1,s2,s3 = "aaa","bbb","ccc" all = s1 .. s2 .. s3 print(all) -- 输出: aaabbbccc number 转 string n = 123 s = 'm/s' result = tostring(n)..s print(result) -- 输出: 123m/s [某些情况下, Lua 会自动将number类型转换成string类型] string 转 number n = 123 s = '456' result = tonumber(n) + tonumber(s) print(result) -- 输出: 579 -- 相加的值都是数字值时候其实不用转换也是可以相加操作的, 但是会得到一个带小数的值 result = n + s print(result) -- 输出: 579.0 关系运算符 符号 含义 == 等于,检测两个值是否相等,相等返回 true,否则返回 false ~= 不等于,检测两个值是否相等,相等返回 false,否则返回 true > 大于,如果左边的值大于右边的值,返回 true,否则返回 false < 小于,如果左边的值大于右边的值,返回 false,否则返回 true >= 大于等于,如果左边的值大于等于右边的值,返回 true,否则返回 false <= 小于等于, 如果左边的值小于等于右边的值,返回 true,否则返回 false a = 21 b = 10 print(a==b) -- 输出: false print(a~=b) -- 输出: true print(a>b) -- 输出: true print(a<b) -- 输出: false print(a>=b) -- 输出: true print(a<=b) -- 输出: false a = 1 b = '1' c = a d = 2 print(a == b) -- 输出: false [说明不同类型间的对比即使值相同也是不相等的] print(c == a) -- 输出: true print(a ~= b) -- 输出: true print(d <= c) -- 输出: false 逻辑运算符 逻辑运算符基于布尔型的值来进行计算, 并给出结果。 符号 含义 and 逻辑与操作符。 若 A 为 false,则返回 A,否则返回 B or 逻辑或操作符。 若 A 为 true,则返回 A,否则返回 B not 逻辑非操作符。与逻辑运算结果相反,如果条件为 true,逻辑非为 false print(true and false) -- 输出: false print(true or false) -- 输出: true print(true and true) -- 输出: true print(false or false) -- 输出: false print(not false) -- 输出: true print(123 and 345) -- 输出: 345 print(nil and true) -- 输出: nil a = 1 b = '1' c = 0 print(a and b) -- 输出: 1 print(c or a) -- 输出: 0 print(not b) -- 输出: false print(d and c) -- 输出: nil print(1 < 2 and 3 > 2) -- 输出: true 条件判断 --[[ 单条件判断语法结构: if 条件 then 符合条件的代码 end ]] n = 5 if n < 10 then print('n 小于 10') end --[[ 多条件判断语法结构: if 条件1 then 满足条件1 elseif 条件2 then 不满足条件1,但是满足条件2 else 前面条件全都不满足 end ]] n = 2 if n >= 0 and n < 5 then print('太小') elseif n >= 5 and n < 10 then print('适中') elseif n >= 10 then print('太大') end -- 输出: 太小 循环语句 --[[ while 循环语法结构: while 继续循环判断依据 do 执行的代码 end ]] -- 计算从1加到100 result = 0 num = 1 while num <= 100 do result = result + num num = num + 1 end print(result) -- 输出: 5050 --[[ for 循环语法结构: for 临时变量名=开始值,结束值,步长 do 循环的代码 end 步长可以省略,默认为1 ]] result = 0 for i = 0, 100 do result = result + i end print(result) -- 输出: 5050 --[[ 中断循环语句: 比如循环运行到一半, 不想继续运行了(跳出本次循环), 可以使用 break 关键字 ]] result = 0 for i = 1, 100 do result = result + i if i == 100 then result = result - i end end print(result) -- 输出: 4950 迭代器泛型 迭代器 ipairs 是一个构造,可以用来遍历集合或容器的元素。 在Lua中,这些集合通常引用表,这些表用于创建各种数据结构,如数组。 迭代器泛型提供集合中每个元素的键值对。 for key, value in ipairs({"hello", "world"}) do print(key, value) end --[[ 输出: 1 hello 2 world ]] 函数使用 --[[ 语法结构: function 函数名(参数1(可选),参数2(可选),...) 函数代码内容 end ]] function hello() return "hello world" end print(hello()) -- 输出: hello world Table 表 很多语言中都有 数组 这个概念,在 Lua 中,我们可以使用 table(表)来实现这个功能 (table 就是当成 Lua 中的数组使用)。table 是一个一系列元素的集合(里面元素可以是任意类型数据, 包括 function),使用大括号进行表示,其中的元素之间以逗号分隔,类似下面的代码: t = {1,3,8,5,4} 我们可以直接使用元素的下标,来访问、或者对该元素进行赋值操作。 注意 元素的开始下标是从 1 开始的, 这个和其他很多语言从 0 开始的下标不一样。 一般语言中的数组基本都为 不可变长度,这里的 table 为 可变长度 。 t = {1,3,8,5,4} print(t[0]) -- 输出: nil print(t[1]) -- 输出: 1 -- 自定义下标 t = { ["apple"] = 10, banana = 12, pear = 6, } print(t.apple) -- 输出: 10 print(t["pear"]) -- 输出: 6 -- 使用 `["下标"] = 值` 和 `下标 = 值` 都是正确写法。当第二种方式有歧义时,应该用第一种方式 t1 = {3, 2, 4} --[[ 上面 t1 的代码等价于: t1 = { [1] = 3, [2] = 2, [3] = 4, } 甚至你可以跳过某些下标, 如 `t2 = {[2] = 5}` ]] 将元素是string或者number类型的table,每个元素连接起来变成字符串并返回。 --[[ 基本语法: table.concat (table [, sep [, i [, j ] ] ]) 可选参数sep,表示连接间隔符,默认为空。 i 和 j 表示元素起始和结束的下标。 ]] local a = {1, 3, 5, "hello" } print(table.concat(a)) -- 输出: 135hello print(table.concat(a, "|")) -- 输出: 1|3|5|hello 也可以对 table 元素进行插入、删减和移动 --[[ 插入基本语法: table.insert (table, [pos ,] value) 在(数组型)表 table 的 pos 索引位置插入 value,其它元素向后移动到空的地方。pos 的默认值是表的长度加一,即默认是插在表的最后。 ]] local a = {1, 8} -- a[1] = 1, a[2] = 8 table.insert(a, 1, 3) -- 在表索引为1处插入3 print(a[1], a[2], a[3]) -- 输出: 3 1 8 table.insert(a, 10) -- 在表的最后插入10 print(a[1], a[2], a[3], a[4]) -- 输出: 3 1 8 10 --[[ 删除基本语法: table.remove (table [, pos]) 在表 table 中删除索引为 pos(pos 只能是 number 型)的元素,并返回这个被删除的元素,它后面所有元素的索引值都会减一。pos 的默认值是表的长度,即默认是删除表的最后一个元素。 ]] local a = {1, 2, 3, 4} print(table.remove(a, 1)) -- 删除索引为1的元素, 返回被删除的元素值为1 print(a[1], a[2], a[3], a[4]) -- 输出: 2 3 4 nil print(table.remove(a)) -- 删除最后一个元素, 返回被删除的元素值为4 print(a[1], a[2], a[3], a[4]) -- 输出: 2 3 nil nil --[[ 移动基本语法: table.move (a1, f, e, t [,a2]) 将元素从 table 移动a1到 table a2,执行与以下多重赋值等效的操作: a2[t],··· = a1[f],···,a1[e]. 的默认a2值为a1。目标范围可以与源范围重叠。要移动的元素数量必须适合 Lua 整数。 返回目标表a2。 ]] t = {1, 2, 4, 5, 6, 7, 8} b = {} table.move(t, 3, 5, 1, b) for k,v in ipairs(b) do print(k) end --[[ 输出: 1 2 3 ]] 还可以对 table 根据可选的比较器参数对表进行排序 --[[ 排序基本语法: table.sort (table [, comp]) 对一个长度为 length=n 的数组 table 排序,也就是对 tab_table[1] 到tab_table[n] 排序,如果参数 comp 不省略,则它必须是一个函数,可以接收表tab_table 的两个元素,并且在第一个元素小于第二个元素时返回 true,其他情况返回 false,如果省略参数 comp,则 Lua 彼岸准运算符 operator < 将会被使用。 ]] fruits = {"banana", "red", "orange", "blue", "apple", "grapes"} table.sort(fruits) for k,v in ipairs(fruits) do print(k,v) end --[[ 输出: 1 apple 2 banana 3 blue 4 grapes 5 orange 6 red ]] 最后还能获取 table 的大小 --[[ 基本语法: #table 在旧版本的 Lua5.1 前, 使用的是 table.getn() 方法 (个人认为 getn 方法更加人性化、更好理解)。 ]] t = {1, 2, 4, 5} print(#t) -- 输出: 4
2022年-9月-28日
170 阅读
0 评论
Lua
2022-9-22
Redis 批量删除未设置过期时间的KEYS
有时候可能因为历史原因或者不规范操作导致产生了很多Redis Keys没有设置过期时间,为了保证Redis空间的干净,清理掉脏数据,可以通过如下shell命令实现批量删除未设置过期时间的KEYS redis-cli -h 地址 -p 端口 -a 密码 -n 数据库 --scan --pattern "被执行的内容(如: user:order:*)" | while read LINE ; do TTL=`redis-cli -h 地址 -p 端口 -a 密码 -n 数据库 ttl "$LINE"`; if [ $TTL -eq -1 ]; then RES=`redis-cli -h 地址 -p 端口 -a 密码 -n 数据库 del $LINE`; fi; done;
2022年-9月-22日
333 阅读
0 评论
Redis
2022-9-12
CentOS7.4 干净环境配置及搭建LNMP (PHP7+)
当我们刚买好 CentOS 后,应该如何做到快速搭建 Web 应用环境呢? 在此, 我特地写下这篇实战文章, 希望大家少踩坑!我采用的是新装干净的 CentOS 7.4 操作系统, 安装 LNMP 开发环境. CentOS 7.4 Openresty Nginx 1.15 [ 高性能 Web 平台 ] PHP 7.3 [ 安装 Swoole4 扩展 ] Mysql 5.7 更新系统源 由于我们的系统源太旧、国外等原因,建议先更新源镜像。 第一步 备份源 mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup 第二步 下载 CentOS-Base.repo 到 /etc/yum.repos.d/ cd /etc/yum.repos.d/ wget http://mirrors.163.com/.help/CentOS7-Base-163.repo 第三步 生成缓存 yum clean all yum makecache 第四步 更新系统 (更新系统时间可能会比较久取决于服务器网速) yum -y update 防火墙配置 源更新好后,我们关闭系统默认的 firewall 防火墙,因为作为 Web 应用服务器 推荐 使用 iptables 第一步 停止/禁用 firewall systemctl stop firewalld.service // 停止 systemctl disable firewalld.service // 禁止开机启动 第二步 设置 iptables service yum -y install iptables-services 第三步 打开 vi /etc/sysconfig/iptables 文件修改防火墙配置,增加防火墙端口 3306 / 80 / 443 -A INPUT -m state --state NEW -m tcp -p tcp --dport 80 -j ACCEPT -A INPUT -m state --state NEW -m tcp -p tcp --dport 443 -j ACCEPT -A INPUT -m state --state NEW -m tcp -p tcp --dport 3306 -j ACCEPT 第四步 重启/设置开机启动 systemctl restart iptables.service // 重启防火墙使配置生效 systemctl enable iptables.service // 设置防火墙开机启动 !!! 注意: 必须重启系统使设置生效即可。 安装 OpenResty 做完以上的配置准备后,终于来到了 LNMP 环境搭建。首先我们安装 Nginx,在此我使用的是 OpenResty,因为它整合了我们的加强版 Nginx 的核心,我们的加强版[LuaJIT],许多浓墨重彩 Lua 库,大量的高品质第三方的 Nginx 模块,并且大部分外部依赖的。它旨在帮助开发人员轻松构建可伸缩的 Web 应用程序,Web 服务和动态 Web 网关。 当然,你也可以只安装 [Nginx], 需要到[官方下载包]。这里强烈推荐 [OpenResty]。 废话不多说,我们开始吧。 第一步 在 CentOS 系统中添加 openresty 仓库, 这样就可以便于未来安装或更新软件包(通过 yum update 命令)。 运行下面的命令就可以添加仓库: sudo yum install yum-utils sudo yum-config-manager --add-repo https://openresty.org/package/centos/openresty.repo 第二步 安装依赖库 yum install readline-devel pcre-devel openssl-devel perl 第三步 安装软件包 sudo yum install openresty 安装完成后软件目录默认在 /usr/local/openresty,运行 nginx 命令为 /usr/local/openresty/nginx/sbin/nginx 查看版本 /usr/local/openresty/nginx/sbin/nginx -v 操作方式 # 启动服务 /usr/local/openresty/nginx/sbin/nginx # 停止服务 /usr/local/openresty/nginx/sbin/nginx -s stop # 重启服务 /usr/local/openresty/nginx/sbin/nginx -s reload # 检验nginx配置是否正确 /usr/local/openresty/nginx/sbin/nginx -t 配置环境变量 将 nginx 目录添加到 PATH 中。打开文件 /etc/profile, 在文件末尾加入 export PATH=$PATH:/usr/local/openresty/nginx/sbin,若你的安装目录不一样,则做相应修改。 注意:这一步操作需要重新加载环境变量才会生效,可通过命令 source /etc/profile 或者重启服务器等方式实现。 配置项 (建议网站根目录配置到 openresty 外,比如 root /data/www) # 配置目录 /usr/local/openresty/nginx/conf # 配置文件 /usr/local/openresty/nginx/conf/nginx.conf 检查是否运行成功 ps aux|grep nginx 访问 Web 应用 curl localhost 安装 PHP 安装 PHP 7 前需要安装源,在这里我们安装的版本是 PHP7.3 第一步 下载安装源 yum install epel-release wget http://rpms.remirepo.net/enterprise/remi-release-7.rpm && rpm -Uvh remi-release-7.rpm 第二步 安装PHP yum --enablerepo=remi-safe -y install php73 第三步 安装扩展 (加入了 swoole4 扩展) yum install php73-php-gd php73-php-openssl php73-php-mcrypt php73-php-mcrypt php73-php-json php73-php-mbstring php73-php-xml php73-php-soap php73-php-intl php73-php-xmlrpc php73-php-simplexml php73-php-curl php73-php-mysqlnd php73-php-pecl-redis4 php73-php-pecl-swoole4 yum install zip unzip php7.3-zip 第四步 查看 PHP 版本 php73 -v 由于每次使用 PHP 命令都需要带上 php73,如果当前服务器不需要多个 PHP 版本情况下,可以执行 mv /usr/bin/php73 /usr/bin/php mv /usr/bin/php73-cgi /usr/bin/php-cgi mv /usr/bin/php73-phar /usr/bin/php-phar 这样就可以 使用 php -v 命令了。 接下来我们需要安装 PHP-FPM 工具来管理我们的 PHP yum --enablerepo=remi-safe -y install php73-php-fpm 操作方式 # 系统开机启动 php-fpm 服务 systemctl enable php73-php-fpm # 启动服务 systemctl start php73-php-fpm # 停止服务 systemctl stop php73-php-fpm # 重启服务 systemctl restart php73-php-fpm # 查看服务状态 systemctl status php73-php-fpm.service PHP-FPM 配置 # 配置目录 /etc/opt/remi/php73/ # 配置文件 /etc/opt/remi/php73/php-fpm.d/www.conf 安装 Mysql 同样的,安装 Mysql5.7 前需要下载源 第一步 下载源 wget http://dev.mysql.com/get/mysql57-community-release-el7-8.noarch.rpm 第二步 安装源 yum localinstall mysql57-community-release-el7-8.noarch.rpm 第三步 检查源是否安装成功 yum repolist enabled | grep "mysql.*-community.*" 可以修改 vim /etc/yum.repos.d/mysql-community.repo 源,改变默认安装的 Mysql 版本。 比如要安装 5.6 版本,将 5.7 源的 enabled=1 改成 enabled=0。然后再将 5.6 源的 enabled=0 改成 enabled=1 即可。 第四步 安装命令 (包相对大,大概在 170M 左右,下载速度取决于您服务器的带宽) yum install mysql-community-server 操作方式 # 系统开机自启动 systemctl enable mysqld systemctl daemon-reload # 启动服务 systemctl start mysqld # 停止服务 systemctl stop mysqld # 重启服务 systemctl restart mysqld # 查看服务状态 systemctl status mysqld 修改 root本地登录密码 Mysql 安装完成之后,在 /var/log/mysqld.log 文件中给 root 生成了一个默认密码。通过下面的方式找到 root 默认密码,然后登录Mysql 进行修改: grep 'temporary password' /var/log/mysqld.log 登录 Mysql mysql -uroot -p 设置登录密码 ALTER USER 'root'@'localhost' IDENTIFIED BY 'Au87uejJD!'; # 或者 set password for 'root'@'localhost'=password('Au87uejJD!'); 注意:Mysql5.7 默认安装了密码安全检查插件(validate_password),默认密码检查策略要求密码必须包含:大小写字母、数字和特殊符号,并且长度不能少于8位。否则会提示 ERROR 1819 (HY000): Your password does not satisfy the current policy requirements 错误。 通过 Msyql 环境变量可以查看密码策略的相关信息 show variables like '%password%'; validate_password_policy:密码策略,默认为MEDIUM策略 validate_password_dictionary_file:密码策略文件,策略为STRONG才需要 validate_password_length:密码最少长度 validate_password_mixed_case_count:大小写字符长度,至少1个 validate_password_number_count :数字至少1个 validate_password_special_char_count:特殊字符至少1个 上述参数是默认策略 MEDIUM 的密码检查规则。 修改密码策略 在 /etc/my.cnf 文件添加 validate_password_policy 配置,指定密码策略 选择 0(LOW),1(MEDIUM),2(STRONG) 其中一种,选择2需要提供密码字典文件 validate_password_policy=0 如果不需要密码策略,添加 my.cnf 文件中添加如下配置禁用即可: validate_password = off 重新启动 Mysql 服务使配置生效: systemctl restart mysqld 添加远程登录用户 默认只允许 root 帐户在本地登录,如果要在其它机器上连接 mysql,必须修改 root 允许远程连接,或者添加一个允许远程连接的帐户,为了安全起见,我添加一个新的帐户: # 创建账号及生成密码 GRANT ALL PRIVILEGES ON *.* TO 'shugachara'@'%' IDENTIFIED BY 'Abc0331#' WITH GRANT OPTION; # 立即生效 flush privileges; 配置默认编码为 UTF8 修改 /etc/my.cnf 配置文件,在 [mysqld] 下添加编码配置,如下所示: [mysqld] character_set_server=utf8 init_connect='SET NAMES utf8' 重新启动 Mysql 服务,查看数据库默认编码如下所示: show variables like '%character%'; 默认配置文件路径 配置文件:/etc/my.cnf 日志文件:/var/log//var/log/mysqld.log 服务启动脚本:/usr/lib/systemd/system/mysqld.service socket文件:/var/run/mysqld/mysqld.pid 到此, 整个环境已安装完成!感谢您的阅读~ 🙏
2022年-9月-12日
162 阅读
0 评论
操作系统
2022-8-11
简单实现Go+PHP gRPC通信
背景 工作中难免会遇到单语言无法解决的问题(谨慎点描述就是 单语言实现起来比较麻烦、吃力、复杂), 导致产品需求的实现没这么完美。这时候,我们可以给它添上一双翅膀,大家调侃最多的应该就是Golang赋予PHP能量了吧。 题外话, 其实我认为: 语言之争没有意义,语言只是工具, 它只是为了帮助我们更好地解决问题。当单语言无法满足需求的时候,可以根据业务和成本来决定是否利用其他语言来实现。 多一门技术就多一条门路,只局限于一种语言,您可能就无法前行。 举个例子: 公司3个业务部门都使用PHP语言, 另外一个中台服务部门使用Java, Golang, Python。此时公司想要打通这3个业务部门的用户体系,将各业务部门的用户关联起来形成互通。这个串联工作肯定需要中台服务部门来开发,一切的规范由中台定义。由于跨语言,在中台招聘PHP开发也不太现实,业务部门抽离人员去支持中台也不可能(那要中台干嘛?)。这个时候有两个选择: HTTP 协议传输 gRPC 远程调用 如果您的业务调用服务频率较高,采用 HTTP 可能不太行,这个时候我们需要 gRPC 通信。 至于什么是gRPC, 可以 点击查阅官方文档 说了这么多, 如果您了解 gRPC, 上面的内容可以忽略, 当然 您可能已经读完了, 嘿嘿。废话说完, 我们开始实现 Golang+PHP 吧, 这里只是实现简单案例: 通过 Go 获取系统Cpu、内存、磁盘信息。 实现 如果您本地没有安装 protoc, 请先安装。 我使用的是 Mac 环境 brew install protobuf 验证是否安装成功 protoc --version 如果提示没有 protoc 命令, 你可能需要将 protoc 加入到环境变量(查找 protoc 执行文件 find / -name protoc), 加入环境变量后执行 source ~/.bash_profile 如果您的 PHP 没有安装gRPC 扩展, 请先安装。 pecl install grpc 我们先编写 Golang 程序, 在这里我默认您会安装 Go, 如果不会安装并且您是 Mac 操作系统 可以参考Mac-brew-安装-Golang 新建项目 mkdir go-grpc 确保环境变量已开启 Module, 可项目根目录执行 export GO111MODULE=on 创建 go.mod (包管理,类似 composer) go mod init go-grpc 安装 grpc 和 protobuf 包 go get google.golang.org/grpc go get google.golang.org/protobuf/reflect/protoreflect@v1.25.0 创建 proto 文件 创建 system/system.proto 文件, 写入内容 syntax = "proto3"; // 版本声明,使用Protocol Buffers v3版本 package system; // 表示生成的go文件的存放地址,会自动生成目录。 option go_package = "./system"; // 定义服务 service System { // 获取系统信息接口 rpc GetSystemInfo (GetSystemInfoRequest) returns (GetSystemInfoResponse) {} } // 获取系统信息接口请求参数 message GetSystemInfoRequest {} // 获取系统信息接口返回值 message GetSystemInfoResponse { double cpuPercent = 1; // CPU使用率 double memPercent = 2; // 内存使用率 double diskPercent = 3; // 磁盘使用率 string cpuGHz = 4; // CPU主频 int32 cpuCounts = 5; // CPU核数 string memTotal = 6; // 总内存 string memUsed = 7; // 剩余内存 string diskTotal = 8; // 磁盘总大小 string diskUsed = 9; // 磁盘剩余大小 } 编写 shell 文件 创建system/system_rpc.sh 文件, 写入内容 #! /bin/sh # 系统服务 - 该文件目录下执行生成GO RPC文件命令 protoc -I $(pwd)/ $(pwd)/system.proto --go_out=plugins=grpc:./rpc 给文件赋予可执行权限 chmod -R 755 system/system_rpc.sh 生成 GO 代码 进入 cd system 目录 新建 rpc 目录 mkdir rpc 执行 ./system_rpc.sh 此时, 在 system/rpc 目录已生成 system/system.pb.go 文件 安装 gopsutil 包获取系统信息 go get github.com/shirou/gopsutil go get github.com/tklauser/go-sysconf 编写获取系统信息代码 新建 internal/utils/system.go 文件, 写入内容 package utils import ( "fmt" "github.com/shirou/gopsutil/cpu" "github.com/shirou/gopsutil/disk" "github.com/shirou/gopsutil/mem" "time" ) // 获取 cpu 使用率 func GetCpuPercent() float64 { percent, _ := cpu.Percent(time.Second, false) return percent[0] } // 获取 内存 使用率 func GetMemPercent() float64 { memInfo, _ := mem.VirtualMemory() return memInfo.UsedPercent } // 获取 磁盘 使用率 func GetDiskPercent() float64 { diskInfo, _ := disk.Usage("/") return diskInfo.UsedPercent } // 字节的单位转换 保留两位小数 func FormatFileSize(fileSize int64) (size string) { if fileSize < 1024 { // return strconv.FormatInt(fileSize, 10) + "B" return fmt.Sprintf("%.2fB", float64(fileSize)/float64(1)) } else if fileSize < (1024 * 1024) { return fmt.Sprintf("%.2fKB", float64(fileSize)/float64(1024)) } else if fileSize < (1024 * 1024 * 1024) { return fmt.Sprintf("%.2fMB", float64(fileSize)/float64(1024*1024)) } else if fileSize < (1024 * 1024 * 1024 * 1024) { return fmt.Sprintf("%.2fGB", float64(fileSize)/float64(1024*1024*1024)) } else if fileSize < (1024 * 1024 * 1024 * 1024 * 1024) { return fmt.Sprintf("%.2fTB", float64(fileSize)/float64(1024*1024*1024*1024)) } else { // if fileSize < (1024 * 1024 * 1024 * 1024 * 1024 * 1024) return fmt.Sprintf("%.2fEB", float64(fileSize)/float64(1024*1024*1024*1024*1024)) } } 新建 rpc/logic/systeminfo.go 文件, 写入内容 package logic import ( "context" "fmt" "github.com/shirou/gopsutil/cpu" "github.com/shirou/gopsutil/disk" "github.com/shirou/gopsutil/mem" "go-grpc/system/internal/utils" "go-grpc/system/rpc/system" ) type SystemInfoLogic struct { ctx context.Context } func NewSystemInfoLogic(ctx context.Context) *SystemInfoLogic { return &SystemInfoLogic{ ctx: ctx, } } // 获取系统信息 func (l *SystemInfoLogic) GetSystemInfo(request *system.GetSystemInfoRequest) (*system.GetSystemInfoResponse, error) { var ( cpuGHz = "0GHz" ) c, _ := cpu.Info() for _, v := range c { if v.Mhz > 0 { cpuGHz = fmt.Sprintf("%v0GHz", v.Mhz/1000) } } cpuCounts, _ := cpu.Counts(true) m, _ := mem.VirtualMemory() w, _ := disk.Usage("/") return &system.GetSystemInfoResponse{ CpuPercent: utils.GetCpuPercent(), MemPercent: utils.GetMemPercent(), DiskPercent: utils.GetDiskPercent(), CpuGHz: cpuGHz, CpuCounts: int32(cpuCounts), MemTotal: utils.FormatFileSize(int64(m.Total)), MemUsed: utils.FormatFileSize(int64(m.Used)), DiskTotal: utils.FormatFileSize(int64(w.Total)), DiskUsed: utils.FormatFileSize(int64(w.Total) - int64(w.Free)), }, nil } 新建服务端文件 进入 cd rpc 目录 新建 server 目录 mkdir server 进入 server 目录, 新建 system.go 文件, 写入内容 package server import ( "context" "go-grpc/system/rpc/logic" "go-grpc/system/rpc/system" ) // 系统信息服务 type System struct {} // 获取系统信息 func (server *System) GetSystemInfo(ctx context.Context, request *system.GetSystemInfoRequest) (*system.GetSystemInfoResponse, error) { l := logic.NewSystemInfoLogic(ctx) return l.GetSystemInfo(request) } 编写服务启动文件 system 目录下新建 server.go 文件, 写入内容 package main import ( "fmt" "google.golang.org/grpc" "google.golang.org/grpc/reflection" "net" "go-grpc/system/rpc/server" "go-grpc/system/rpc/system" ) func main() { // 监听本地的 10000 端口 lis, err := net.Listen("tcp", ":10000") if err != nil { fmt.Printf("failed to listen: %v", err) return } s := grpc.NewServer() // 创建 GRPC 服务器 system.RegisterSystemServer(s, &server.System{}) // 在 GRPC 服务端注册服务 reflection.Register(s) // 在给定的 GRPC 服务器上注册服务器反射服务 // Serve 方法在 lis 上接受传入连接,为每个连接创建一个 ServerTransport 和 server 的 goroutine。 // 该 goroutine 读取 GRPC 请求,然后调用已注册的处理程序来响应它们。 err = s.Serve(lis) if err != nil { fmt.Printf("failed to serve: %v", err) return } } 项目目录结构 . ├── go.mod ├── go.sum └── system ├── internal │ └── utils │ └── system.go ├── rpc │ ├── logic │ │ └── systeminfo.go │ ├── server │ │ └── system.go │ └── system │ └── system.pb.go ├── server.go ├── system.proto └── system_rpc.sh 启动服务 go run server.go 此时, Golang 服务已经编写完成!!! 接下来, 我们编写 PHP 代码, 在这里我默认您用了composer 包管理。 导入 grpc 和 protobuf 包 composer require grpc/grpc composer require google/protobuf 新建 gRPC 目录 为了方便演示, 我们在项目根目录下新建 mkdir -p grpc/system 文件夹, 然后进入到该目录cd grpc/system 创建 touch system.proto 文件, 写入内容 syntax = "proto3"; // 版本声明,使用Protocol Buffers v3版本 package System; // 系统服务 service System { // 获取系统信息接口 rpc GetSystemInfo (GetSystemInfoRequest) returns (GetSystemInfoResponse) {} } // 获取系统信息接口请求参数 message GetSystemInfoRequest {} // 获取系统信息接口返回值 message GetSystemInfoResponse { double cpuPercent = 1; // CPU使用率 double memPercent = 2; // 内存使用率 double diskPercent = 3; // 磁盘使用率 string cpuGHz = 4; // CPU主频 int32 cpuCounts = 5; // CPU核数 string memTotal = 6; // 总内存 string memUsed = 7; // 剩余内存 string diskTotal = 8; // 磁盘总大小 string diskUsed = 9; // 磁盘剩余大小 } 编写 shell 文件 创建 system.sh 文件, 写入内容 #! /bin/sh # 系统服务 - 该目录文件下执行生成 PHP 文件命令 protoc -I $(pwd)/ $(pwd)/system.proto --php_out=../ 给文件赋予可执行权限chmod -R 755 system.sh 添加自动加载命名空间 在 composer.json 文件的 autoload 配置增加如下 "autoload":{ "psr-4":{ "GPBMetadata\\":"grpc/GPBMetadata/", "System\\":"grpc/system/" } }, 然后执行 composer dump-autoload 生成 PHP 代码 在 grpc/system目录下执行 ./system.sh 生成 PHP 代码文件, 此时我们 Tree 看看目录结构 . ├── composer.json ├── composer.lock ├── vendor └── grpc ├── GPBMetadata │ └── System.php ├── system │ ├── GetSystemInfoRequest.php │ ├── GetSystemInfoRequest.php │ └── system.proto │ └── system.sh 编写客户端文件 我们进入到 grpc/system 目录新建客户端文件 SystemClient.php 调用系统服务接口, 写入内容 <?php namespace System; class SystemClient extends \Grpc\BaseStub { public function __construct($hostname, $opts, $channel = null) { parent::__construct($hostname, $opts, $channel); } public function getSystemInfo(\System\GetSystemInfoRequest $argument, $metadata = [], $options = []) { return $this->_simpleRequest( '/system.System/GetSystemInfo', $argument, [\System\GetSystemInfoResponse::class, 'decode'], $metadata, $options ); } } 编写调用服务文件 在根目录新建 index.php 文件, 写入内容 <?php require 'vendor/autoload.php'; // 创建客户端实例 $client = new \System\SystemClient('127.0.0.1:10000', [ 'credentials' => \Grpc\ChannelCredentials::createInsecure() ]); $request = new \System\GetSystemInfoRequest(); $reponse = $client->getSystemInfo($request)->wait(); list($reply, $status) = $reponse; $data['system'] = [ 'cpuPercent' => '0%', 'memPercent' => '0%', 'diskPercent' => '0%', 'cpuGHz' => '0GHz', 'cpuCounts' => 0, 'memTotal' => '0GB', 'memUsed' => '0GB', 'diskTotal' => '0GB', 'diskUsed' => '0GB', ]; if ($status->code === 0) { $data['system']['cpuPercent'] = floatval(sprintf("%.2f", $reply->getCpuPercent())) . '%'; $data['system']['memPercent'] = floatval(sprintf("%.2f", $reply->getMemPercent())) . '%'; $data['system']['diskPercent'] = floatval(sprintf("%.2f", $reply->getDiskPercent())) . '%'; $data['system']['cpuGHz'] = $reply->getCpuGHz(); $data['system']['cpuCounts'] = $reply->getCpuCounts(); $data['system']['memTotal'] = $reply->getMemTotal(); $data['system']['memUsed'] = $reply->getMemUsed(); $data['system']['diskTotal'] = $reply->getDiskTotal(); $data['system']['diskUsed'] = $reply->getDiskUsed(); } var_dump($data); 运行调用 根目录执行 php index.php 输出内容如下: array(1) { ["system"]=> array(9) { ["cpuPercent"]=> string(6) "10.97%" ["memPercent"]=> string(6) "69.17%" ["diskPercent"]=> string(6) "26.89%" ["cpuGHz"]=> string(7) "2.80GHz" ["cpuCounts"]=> int(8) ["memTotal"]=> string(7) "16.00GB" ["memUsed"]=> string(7) "11.13GB" ["diskTotal"]=> string(8) "233.47GB" ["diskUsed"]=> string(8) "175.94GB" } } 到这里整个功能完成, 贴上 PHP 端最终目录结构 . ├── index.php ├── composer.json ├── composer.lock ├── vendor └── grpc ├── GPBMetadata │ └── System.php ├── system │ ├── GetSystemInfoRequest.php │ ├── GetSystemInfoRequest.php │ └── system.proto │ └── system.sh │ └── SystemClient.php 以上使用的是原生态 PHP, 在此推荐使用 Hyperf 实现 gRPC 客户端。
2022年-8月-11日
202 阅读
0 评论
服务架构
2022-8-9
手把手教你源码编译安装PHP及相关扩展
说明 我这边用的是 Mac 操作系统 来演示, 下载的PHP版本 - 7.4.24。 下载、解压 如果你本地有 wget 命令, 可以直接 wget https://www.php.net/distributions/php-7.4.24.tar.gz 当然, 你也可以 点击PHP官网 下载对应的PHP版本源码 下载完之后解压 tar -zxvf php-7.4.24.tar.gz, 为了方便之后需要调试 PHP 的 C 源码, 所以我把 php-7.4.24 文件夹复制放到 /Users/linshan/Container/C 目录下。 配置 PHP 初始的配置和安装过程被 configure 脚本中一系列命令行选项控制。可以通过 ./configure --help 命令了解 PHP 所有可用的编译选项及简短解释。 Linux 下安装软件的步骤: ./configure 执行配置选项(例如 --prefix 可以指定安装位置),判断硬件及操作系统平台,生成 Makefile 文件 make 编译 make install 安装 常用的配置选项 1. PHP 选项 –-prefix[=PREFIX] 安装路径的前缀,可以自定义,例如 /mysoft/php。默认安装在 /usr/local 。 –-with-config-file-path=PATH 设置 php.ini 的搜索路径。默认安装在 PREFIX/lib。 –-disable-short-tags 默认禁用短形式的开始标签 <? 。 --enable-debug 使用调试符号编译。 2. PHP 扩展 –-enable-mbstring 开启 mbstring 多字节扩展 –-with-gd[=DIR] 激活 GD 支持,可以指定扩展位置。编译 GD 库需要libpng 和 libjpeg。 –-with-pear 安装 PEAR 扩展。 –-with-zip[=DIR] 提供 zip 支持,[DIR] 是 ZZIPlib 库安装路径。建议通过 通过 PECL 安装。 --enable-posix 开启 posix 扩展支持。 3. 进程控制扩展 –-enable-pcntl 开启 pcntl 进程控制扩展,只能编译安装。 4. 网络相关扩展 –-with-openssl[=DIR] 开启 OpenSSL 扩展,可以指定扩展位置 –-enable-ftp 开启 FTP 扩展 –-with-curl 支持 cURL –-enable-sockets 开启 socket 扩展 –-enable-soap 支持 SOAP –-enable-fpm 激活 FPM 支持 5. 数据库扩展 –-with-mysql=mysqlnd –-with-mysqli=mysqlnd –-with-pdo-mysql=mysqlnd 运行下配置, 用最简单的配置参数 ./configure --prefix=/Users/linshan/Container/C/php-7.4.24/php74 --enable-debug --enable-mysqlnd --enable-posix --with-iconv=/usr/local/opt/libiconv 在此过程中可能会出现一些报错提示, 根据提示查看相关文档就能解决掉, 这里就不一一说明了。如果一切顺利, 大概会展示下图信息 编译、安装 运行如下命令 make && make install 不出意外, 你会看到如下输出: 此时, 编译安装完成!我们通过 PHP 命令验证下是否安装成功: /Users/linshan/Container/C/php-7.4.24/php74/bin/php -v 源码安装 swoole 扩展 首先下载 swoole 源码, 你也可以到 https://github.com/swoole/swoole-src git 下载 git clone git@github.com:swoole/swoole-src.git 进入 swoole-src 目录 cd swoole-src 通过执行 phpize 命令生成 swoole 安装的 configure 文件 /Users/linshan/Container/C/php-7.4.24/php74/bin/phpize 一切顺利的话, 会生成configure执行文件, 这样就可以配置swoole选项了 接下来配置 swoole ./configure --with-php-config=/Users/linshan/Container/C/php-7.4.24/php74/bin/php-config 编译安装 make && make install 将扩展加入到 php.ini 配置文件, 由于编译安装后并没有设置 php.ini 相关信息, 所以理论上应该是没有该文件的。可以先看看安装在哪个目录 /Users/linshan/Container/C/php-7.4.24/php74/bin/php -i |grep php.ini 然后我们将 /Users/linshan/Container/C/php-7.4.24 目录下的 php.ini-development 或 php.ini-production 配置文件拷贝到 /Users/linshan/Container/C/php-7.4.24/php74/lib 目录下并重命名为 php.ini。 打开 php.ini 文件, 将 swoole 扩展加入到配置中 => extension=swoole.so 。 我们看下是否有安装成功 swoole 扩展 /Users/linshan/Container/C/php-7.4.24/php74/bin/php -m 可以看到是安装成功的, 然后我们可以看看 swoole 相关信息 /Users/linshan/Container/C/php-7.4.24/php74/bin/php --ri swoole 到此, 整个源码编译安装就完成了, 相关的扩展也是类似方式, 安装过程中遇到的环境或软件扩展报错基本都不复杂, 相对容易解决。
2022年-8月-9日
157 阅读
0 评论
PHP
1
2
3
4