Docker
docker安装CentosUbuntu12345678910111213141516# 卸载旧版本sudo yum remove docker docker-client docker-client-latest docker-common docker-latest docker-latest-logrotate docker-logrotate docker-selinux docker-engine-selinux docker-engine# 安装sudo yum install -y yum-utils device-mapper-persistent-data lvm2# 添加yum源sudo yum-config-manager --add-repo https://mirrors.ustc.edu.cn/docker-ce/linux/centos/docker-ce.repo# 安装yum -y install https://download.docker.com/linux/fedora/30/x86_64/stable/Packages/containerd.io-1.2.6-3.3.fc30.x86_64.rpmyum -y install docker-ce启动systemctl enable dockersystemctl start docker12# 用于系统是干净的,如果重新装,需要吧原来的docker卸载掉sudo apt install docker.io 开启远程访问1234567891011121314# 编辑vim /lib/systemd/system/docker.service# 默认是这样的ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock # 修改成ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock -H tcp://0.0.0.0:2375 -H unix://var/run/docker.sock# 重启systemctl daemon-reloadsystemctl restart docker# 测试curl http://localhost:2375/version 查看远端仓库的标签创建一个dockertags.sh脚本 12345678910111213141516171819202122232425262728293031#!/bin/bashfunction usage() { cat << HELPdockertags -- list all tags for a Docker image on a remote registry.EXAMPLE: - list all tags for ubuntu: dockertags ubuntu - list all php tags containing apache: dockertags php apacheHELP}if [ $# -lt 1 ]; then usage exitfiimage="$1"tags=`wget -q https://registry.hub.docker.com/v1/repositories/${image}/tags -O - | sed -e 's/[][]//g' -e 's/"//g' -e 's/ //g' | tr '}' '\n' | awk -F: '{print $3}'`if [ -n "$2" ]then tags=` echo "${tags}" | grep "$2" `fiecho "${tags}" 12345# 给权限chmod +x dockertags.sh# 使用./dockertags.sh mysql WARNING: IPv4 forwarding is disabled. Networking will not work是没有开启转发,docker网桥配置完后,需要开启转发,不然容器启动后,就会没有网络 修改配置文件: 12345678# vim /etc/sysctl.confnet.ipv4.ip_forward=1 #添加此行配置# 重启network和docker服务systemctl restart network && systemctl restart docker# 查看是否修改成功,如果返回为“net.ipv4.ip_forward = 1”则表示修改成功sysctl net.ipv4.ip_forward 常用命令12345# 查看镜像源,最下面能看到docker info# 查看镜像/容器/数据卷所占的空间docker system df xxx 镜像查看镜像 12# 列出本地主机上的镜像docker images REPOSITORY:表示镜像的仓库源 TAG:镜像的标签 IMAGE ID:镜像ID CREATED:镜像创建时间 SIZE:镜像大小 下载镜像 12345# 查找镜像docker search ubuntu:13.10# 只是直接下载docker pull ubuntu:13.10 删除镜像 12345678# 删除指定镜像docker image rm <id># 删除名字是none的镜像docker rmi $(docker images | grep "none" | awk '{print $3}')# 删除全部镜像docker rmi -f $(docker image -qa) 容器运行容器 1docker run -p 10001:10001 -t springboot/eureka-item 1docker run -it --rm ubuntu:16.04 bash docker run 运行容器的命令 -i 以交互模式运行,通常与-t一起使用 -t 为容器重新分配一个伪输入终端,通常与-i一起使用 bash 进入交互式终端,通常使用/bin/bash -p 指定端口 -P 随机分配端口 -d 后台运行容器并返回容器Id,即启动守护式容器 查看容器 12345678# 查看正在运行的容器docker ps # 查看所有容器docker ps -a# 列出所有的容器 IDdocker ps -aq 启动容器 12#启动已终止容器docker container start 05909cd09bf9 重启容器 1docker restart id 停止容器 1234567891011121314# 停止容器docker stop myredis# 强制停止容器docker kill myredis# 停止所有的容器docker stop $(docker ps -aq)# 删除所有的容器docker rm $(docker ps -aq)# 强制删除,在运行的容器也会删除docker rm -f myredis 容器日志 1docker logs xxx 进入容器12345# 不会启动新的进程,用exit退出,会导致容器的停止docker attach 5cc239848ce5# 打开新的终端,启用新的进程,用exit退出,不会导致容器的停止docker exec -it 5cc239848ce5 /bin/bash 进入容器修改时区,需要重启容器 12ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtimeecho "Asia/Shanghai" > /etc/timezone 拷贝文件12345678# 将主机/www/runoob目录拷贝到容器96f7f14e99ab的/www目录下。docker cp /www/runoob 96f7f14e99ab:/www/# 将主机/www/runoob目录拷贝到容器96f7f14e99ab中,目录重命名为wwwdocker cp /www/runoob 96f7f14e99ab:/www# 将容器96f7f14e99ab的/www目录拷贝到主机的/tmp目录中docker cp 96f7f14e99ab:/www /tmp/ 导出/入容器第一种方式: 1234567# 如果要导出本地某个容器,可以使用 docker export 命令。docker export 1e560fca3906 > ubuntu.tar# docker import 从容器快照文件中再导入为镜像,# 以下实例将快照文件 ubuntu.tar 导入到镜像 test/ubuntu:v1:# 格式: cat 文件名 | docker import - xxx(镜像用户,自定义)/xxx(镜像名,自定义):xxx(版本号自定义)cat docker/ubuntu.tar | docker import - test/ubuntu:v1 第二种方式: 12345# 镜像转文件docker save -o demo.tar(自定义) 镜像名:版本号# 文件转镜像docker load -i demo.tar Commit1docker commit -a "runoob.com" -m "my apache" a404c6c174a2 mymysql:v1 过时:容器不能上网1234vim /etc/sysctl.conf增加:net.ipv4.ip_forward=1重启服务:systemctl restart network查看属性是否修改成功:sysctl net.ipv4.ip_forward 过时:停止和查看容器12345#查看所有的容器(已经存在的容器,已经停止的)docker container ls -a#停止容器docker container stop 05909cd09bf9 过时:Jenkins容器脚本12345678910111213APP_NAME=springboot/lottery-adminecho "当前容器列表"docker ps -a | grep $APP_NAMEecho "star service success!"count = ’docker ps -a |grep $APP_NAME |wc -l‘if [$count -ge 1];thendocker stop $APP_NAMEdocker rm $APP_NAMEfi 123456789101112131415161718192021appName="simons-cloud-eureka"word="1"echo "$word"word=`docker ps -a -q --no-trunc --filter name=^/"$appName"$`echo "$word"if [ -z "$word" ];thenecho "当前不存在该容器,直接进行启动该操作-------------------------------------"elif [ -n "$word" ];thenecho "当前已存在容器,停止并移除该容器-------------------------------------"/usr/bin/docker stop "$word"/usr/bin/docker rm "$word"elif [ "$word" == "1" ];thenecho "查询的信息有误,执行默认操作-------------------------------------"/usr/bin/docker stop "$word"/usr/bin/docker rm "$word"fidocker run -p 8761:8761 -d --name "$appName" "$appName":latest 挂载目录1234567891011121314151617181920docker run -it --privileged=true -v /tmp/host_data:/tmp/docker_data --name=u1 ubuntu# --privileged=true 用来扩容权限用的,最好是加上# -v 宿主目录:容器目录,运行容器的时候,会自动创建目录# 查看挂载情况"Mounts": [ { "Type": "bind", "Source": "/tmp/host_data", "Destination": "/tmp/docker_data", "Mode": "", "RW": true, "Propagation": "rprivate" }]# 继承挂载# 再启动一个容器,起名叫u2,u2的挂载目录,跟u1的一样,就继承u1的挂载目录# 特点是,就算u1停了,也不会影响这个docker run -it --privileged=true --volumes-fron u1 --name=u2 ubuntu DcokerfileExpose暴露端口 1234567891011121314151617# GET_IMAGEFROM 192.168.0.216:5000/centos # MAINTAINER_INFOMAINTAINER hongxue [email protected] RUN yum -y install vimRUN yum -y install net-toolsRUN yum -y install openssh-serverRUN yum -y install wget curl # PORTEXPOSE 8080EXPOSE 22EXPOSE 8009EXPOSE 8005EXPOSE 8443 1docker run -d -it -P --name port_list_container port_list 要使用-P ,绑定的宿主端口,会是随机的 所以Dockerfile的EXPOSE的主要功能,也只是给运维人员看看的 网络123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869# 查看Docker网络,默认的3大网络模式root@anthony:~# docker network lsNETWORK ID NAME DRIVER SCOPEa4154cc357c5 bridge bridge local6625ad672ae9 host host local497caf91f24e none null local# 创建一个网络docker network create xxxx# 删除一个网络docker network rm xxxx# 查看网络docker network inspect xxx root@anthony:~# docker network inspect bridge[ { "Name": "bridge", "Id": "a4154cc357c50d0ac961d8235101d8e199119d5677b911ca84ff49962039a53a", "Created": "2023-10-27T00:40:28.869797225Z", "Scope": "local", "Driver": "bridge", "EnableIPv6": false, "IPAM": { "Driver": "default", "Options": null, "Config": [ { "Subnet": "172.17.0.0/16", "Gateway": "172.17.0.1" } ] }, "Internal": false, "Attachable": false, "Ingress": false, "ConfigFrom": { "Network": "" }, "ConfigOnly": false, "Containers": { "a59c6dd9d8bbdbc4f760389bd85af69786e632e6bd1d346c542d6886f572872b": { "Name": "u1", "EndpointID": "e54ed02028a063b38390abac5306cba4f97ff40135b0cfab2370c950f5ae11dc", "MacAddress": "02:42:ac:11:00:02", "IPv4Address": "172.17.0.2/16", "IPv6Address": "" }, "b5615376be36a2b28485a93fa62d10d9886c53c9f59c5fbcd00de78b0184d6b7": { "Name": "tomcat", "EndpointID": "23688ac53c0f16a8abd20bbe4e64539266645019ce95501253577cf57fc71b7a", "MacAddress": "02:42:ac:11:00:03", "IPv4Address": "172.17.0.3/16", "IPv6Address": "" } }, "Options": { "com.docker.network.bridge.default_bridge": "true", "com.docker.network.bridge.enable_icc": "true", "com.docker.network.bridge.enable_ip_masquerade": "true", "com.docker.network.bridge.host_binding_ipv4": "0.0.0.0", # 网桥名字 "com.docker.network.bridge.name": "docker0", "com.docker.network.driver.mtu": "1500" }, "Labels": {} }] docker-composeDocker Compose 安装1234567891011# 可以修改版本 curl -L "https://github.com/docker/compose/releases/download/v2.23.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose# 给权限sudo chmod +x /usr/local/bin/docker-compose# 超链接sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose# 验证docker-compose --version 命令12345# 启动docker-compose up# 后台启动docker-compose up -d 部署到私有仓库12345678910111213141516171819202122232425//位置gedit /etc/default/docker//添加的命令DOCKER_OPTS="–insecure-registry 172.20.100.211:5000"//重启service docker restart//打tagdocker tag springboot/eureka-item 172.20.100.211:5000/anthonyfirst//推送docker push 172.20.100.211:5000/anthonyfirst//获取私有仓库里的信息curl -XGET http://172.20.100.211:5000/v2/_catalog#客户端配置私有仓库修改/etc/sysconfig/docker(Ubuntu下配置文件地址为:/etc/init/docker.conf),增加启动选项(已有参数的在后面追加),之后重启docker,不添加报错,https证书问题。OPTIONS='--insecure-registry 172.20.100.211:5000' #CentOS 7系统#重启服务systemctl daemon-reloadsystemctl restart docker 报错信息1.缺少FontConfiguration知道是因为alpine中缺少FontConfiguration,那么就考虑安装ttf-dejavu这个软件。 123456789101112131415161718192021222324252627282930313233java.lang.NullPointerException at sun.awt.FontConfiguration.getVersion(FontConfiguration.java:1264) at sun.awt.FontConfiguration.readFontConfigFile(FontConfiguration.java:219) at sun.awt.FontConfiguration.init(FontConfiguration.java:107) at sun.awt.X11FontManager.createFontConfiguration(X11FontManager.java:774) at sun.font.SunFontManager$2.run(SunFontManager.java:431) at java.security.AccessController.doPrivileged(Native Method) at sun.font.SunFontManager.<init>(SunFontManager.java:376) at sun.awt.FcFontManager.<init>(FcFontManager.java:35) at sun.awt.X11FontManager.<init>(X11FontManager.java:57) at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62) at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) at java.lang.reflect.Constructor.newInstance(Constructor.java:423) at java.lang.Class.newInstance(Class.java:442) at sun.font.FontManagerFactory$1.run(FontManagerFactory.java:83) at java.security.AccessController.doPrivileged(Native Method) at sun.font.FontManagerFactory.getInstance(FontManagerFactory.java:74) at java.awt.Font.getFont2D(Font.java:491) at java.awt.Font.access$000(Font.java:224) at java.awt.Font$FontAccessImpl.getFont2D(Font.java:228) at sun.font.FontUtilities.getFont2D(FontUtilities.java:180) at sun.font.StandardGlyphVector.initFontData(StandardGlyphVector.java:1126) at sun.font.StandardGlyphVector.init(StandardGlyphVector.java:1115) at sun.font.StandardGlyphVector.<init>(StandardGlyphVector.java:167) at java.awt.Font.createGlyphVector(Font.java:2545) at nl.captcha.text.renderer.DefaultWordRenderer.render(Unknown Source) at nl.captcha.Captcha$Builder.addText(Unknown Source) at com.liferay.portal.captcha.simplecaptcha.SimpleCaptchaImpl.getSimpleCaptcha(SimpleCaptchaImpl.java:243) at com.liferay.portal.captcha.simplecaptcha.SimpleCaptchaImpl.serveImage(SimpleCaptchaImpl.java:159) at com.liferay.portal.captcha.CaptchaImpl.serveImage(CaptchaImpl.java:100) at com.liferay.portal.kernel.captcha.CaptchaUtil.serveImage(CaptchaUtil.java:78) at com.liferay.portal.captcha.CaptchaPortletAction.serveResource(CaptchaPortletAction.java:42) 原本的dockerfile 1234FROM openjdk:8-jdk-alpineVOLUME /tmpADD agent-0.0.1-SNAPSHOT.jar app.jarENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"] 要改成下面这样RUN apk --update add fontconfig ttf-dejavu 12345FROM openjdk:8-jdk-alpineVOLUME /tmpADD agent-0.0.1-SNAPSHOT.jar app.jarRUN apk --update add fontconfig ttf-dejavuENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"] 2.WARNING: HK2 service reification failed for…12345678910111213141516java.lang.NoClassDefFoundError: javax/activation/DataSource at java.base/java.lang.Class.getDeclaredConstructors0(Native Method) at java.base/java.lang.Class.privateGetDeclaredConstructors(Class.java:3110) at java.base/java.lang.Class.getDeclaredConstructors(Class.java:2314) at org.jvnet.hk2.internal.Utilities$3.run(Utilities.java:1310) at org.jvnet.hk2.internal.Utilities$3.run(Utilities.java:1306) at java.base/java.security.AccessController.doPrivileged(Native Method) at org.jvnet.hk2.internal.Utilities.getAllConstructors(Utilities.java:1306) at org.jvnet.hk2.internal.Utilities.findProducerConstructor(Utilities.java:1249) at org.jvnet.hk2.internal.DefaultClassAnalyzer.getConstructor(DefaultClassAnalyzer.java:83) at org.glassfish.jersey.internal.inject.JerseyClassAnalyzer.getConstructor(JerseyClassAnalyzer.java:144) at org.jvnet.hk2.internal.Utilities.getConstructor(Utilities.java:178) at org.jvnet.hk2.internal.ClazzCreator.initialize(ClazzCreator.java:128) at org.jvnet.hk2.internal.ClazzCreator.initialize(ClazzCreator.java:179) at org.jvnet.hk2.internal.SystemDescriptor.internalReify(SystemDescriptor.java:723) at org.jvnet.hk2.internal.SystemDescriptor.reify(SystemDescriptor.java:678) 在pom.xml里添加 123456789101112<plugin> <groupId>com.spotify</groupId> <artifactId>dockerfile-maven-plugin</artifactId> // ...... <dependencies> <dependency> <groupId>javax.activation</groupId> <artifactId>activation</artifactId> <version>1.1.1</version> </dependency> </dependencies></plugin> 3.不能链接daemon服务没有启动,运行docker,会报这个错 1docker: Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?. 需要启动docker service 1service docker start 安装软件安装Jenkins12345678910111213141516docker run -d \ -u root \ -v /var/run/docker.sock:/var/run/docker.sock \ -v $(which docker):/usr/bin/docker \ -p 8080:8080 \ -p 50000:50000 \ -v /home/jenkins:/var/jenkins_home \ --restart=always \ --name jenkins \ jenkins/jenkins:lts # 查看密码# 容器里的位置cat /var/jenkins_home/secrets/initialAdminPassword# 宿主机的位置cat /root/jenkins/secrets/initialAdminPassword 4.安装插件完之后,安装maven插件,在主机上下载maven,上传到容器中 1234docker cp maven-3.6.0 jenkins:/usr/local/// 上传本机的配置文件docker cp settings.xml jenkins:/home/ 5.进入容器 12345// 普通用户的权限docker exec -it jenkins bash// sudo的用户权限docker exec -it -u 0 jenkins bash 6.从本机拷贝到容器,是不需要用到权限的,但是在容器内,比如从/home下的文件移动到/root 就需要权限,就需要使用 -u 0 在Docker容器的Jenkins,构建SpringBoot 的jar包再执行Shell运行的时候,连接数据库可能有坑,数据库会连不上 安装ElasticSearch12345678910111213docker run -d --name es -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" docker.elastic.co/elasticsearch/elasticsearch:6.3.2docker exec -it es /bin/bashcd configvi elasticsearch.yml# 加入跨域配置http.cors.enabled: truehttp.cors.allow-origin: "*"docker restart es 安装Portainer12345678910111213# 创建数据卷docker volume create portainer_data# 9000才是web访问的端口docker run -d \ -p 8000:8000 \ -p 9000:9000 \ -p 9443:9443 \ --name portainer \ --restart=always \ -v /var/run/docker.sock:/var/run/docker.sock \ -v /docker/portainer:/data \ portainer/portainer-ce:2.21.1 1234567891011121314151617server { listen 80; server_name yourdomain.com; location / { proxy_pass http://localhost:9000; # 将请求转发到 Portainer 后端 proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; # WebSocket 支持 proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; }} 添加docker nodehttps://blog.csdn.net/bj_chengrong/article/details/90300972 安装Redis123456789101112131415# 简单的执行docker run -itd \ --name redis \ --restart=always \ -p 6379:6379 \ redis# 持久化执行docker run -d \ --name redis \ -p 6379:6379 \ -v /docker/redis/config/redis.conf:/usr/local/etc/redis/redis.conf \ -v /docker/redis/data:/data \ --restart=always \ redis redis-server /usr/local/etc/redis/redis.conf 新建redis.conf,位置在:/docker/redis/config/redis.conf 1234567891011#启用 RDB 快照持久化save 900 1save 300 10save 60 10000#启用 AOF 持久化appendonly yesappendfsync everysec#设置持久化目录dir /data 安装Mysql1234567891011# 正式配置# 在对应目录先创建my.cnf文件, 不然系统会自动创建my.cnf文件夹....# 本地对应的目录文件夹会自动创建docker run -itd \ --name mysql \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD=123456 \ -v /Users/anthony/docker/mysql/config/my.cnf:/etc/mysql/conf.d/my.cnf \ -v /Users/anthony/docker/mysql/data:/var/lib/mysql \ --restart=always \ mysql 最简单的my.cnf配置文件 1234567[mysqld]# 时区default-time-zone=+08:00# 字符集character-set-server=utf8mb4# 创建函数/存过的时候,会报安全问题,不用存过/函数,不用管log_bin_trust_function_creators=1; 容器文档:Mysql 安装 Nginx123456789101112131415# 简单的docker run --name nginx-test \ -p 8080:80 \ -d \ nginx# 正式的docker run -d \ -p 443:443 \ -p 80:80 \ --name nginx \ -v /home/nginx/www:/usr/share/nginx/html \ -v /home/nginx/conf/nginx.conf:/etc/nginx/nginx.conf \ -v /home/nginx/logs:/var/log/nginx \ nginx nginx.conf模板 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576# 用户 nginx 使用的工作进程user nginx;# 允许的工作进程数,自动检测 CPU 核心数worker_processes auto;# 错误日志路径和日志级别error_log /var/log/nginx/error.log warn;# PID 文件路径pid /var/run/nginx.pid;events { # 单个工作进程允许的最大连接数 worker_connections 1024;}http { include /etc/nginx/mime.types; default_type application/octet-stream; # 日志格式配置 log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; # 访问日志路径 access_log /var/log/nginx/access.log main; # 启用 Gzip 压缩 gzip on; gzip_disable "msie6"; # 启用发送文件优化 sendfile on; tcp_nopush on; tcp_nodelay on; keepalive_timeout 65; types_hash_max_size 2048; include /etc/nginx/conf.d/*.conf; # 默认服务器配置 server { listen 80 default_server; server_name localhost; # 站点根目录 root /usr/share/nginx/html; # 主页文件 index index.html index.htm; # 处理静态文件请求 location / { try_files $uri $uri/ =404; } # 配置反向代理 # 如果你需要反向代理到一个后端服务,可以启用以下代码: # location /api/ { # proxy_pass http://backend_service; # proxy_set_header Host $host; # proxy_set_header X-Real-IP $remote_addr; # proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # proxy_set_header X-Forwarded-Proto $scheme; # } # 错误页面 error_page 500 502 503 504 /50x.html; location = /50x.html { root /usr/share/nginx/html; } }} 安装nexus 3123456789101112131415# 创建数据文件夹mkdir /docker/nexus3/nexus-data# 如果有权限问题chmod 777 /docker/nexus3/nexus-datadocker run -d \ -p 8081:8081 \ --name nexus \ -v /docker/nexus3/nexus-data:/nexus-data \ --restart=always \ sonatype/nexus3# 查看密码cat /docker/nexus3/nexus-data/admin.password 安装Elastic Search12345678docker run -itd \ -p 9200:9200 \ -p 9300:9300 \ -e "discovery.type=single-node" \ -v /home/anthony/es/config:/usr/share/elasticsearch/config/ \ --name elasticsearch \ --restart=always \ docker.elastic.co/elasticsearch/elasticsearch:7.7.0 安装Zookeeper1docker run -d -p 2181:2181 --name some-zookeeper --restart=always zookeeper 安装可视化软件 mac安装的使用要查看下说明文档,会出现安装包损坏的情况 安装Grafana12# admin / admindocker run -d -p 3000:3000 --name=grafana grafana/grafana 安装WordPressdocker-compose安装WordPress 安装禅道12345678910111213141516# 内置数据库docker run -d -v <你的宿主机目录>/data:/data -p 80:80 -e MYSQL_INTERNAL=true hub.zentao.net/app/zentao # 外接数据库,web页面的安装向导,点击确定之后要等一会docker run -itd \ -v /docker/zentao/data:/data \ -p 8001:80 \ -e MYSQL_INTERNAL=false \ -e ZT_MYSQL_HOST=172.17.0.3 \ -e ZT_MYSQL_PORT=3306 \ -e ZT_MYSQL_USER=root \ -e ZT_MYSQL_PASSWORD=Qwer1234. \ -e ZT_MYSQL_DB=zentao \ --restart=always \ --name zentao \ hub.zentao.net/app/zentao 禅道官方文档 安装Nacosmac docker 安装nacos 12345docker run -d \ -p 8848:8848 \ --env MODE=standalone \ --name nacos \ zhusaidong/nacos-server-m1:2.0.3 访问地址:http://localhost:8848/nacos/ 账号/密码:nacos/nacos Nacos官方文档
Grafana
监控主机 HostName IP master 10.0.0.6 node1 10.0.0.9 服务 端口 Grafana 3000 Prometheus 9090 node-exporter 9100 Cadvisor 8080 部署Prometheus(master机器)123456docker run -d \ --name=prometheus \ --restart=always \ -p 9090:9090 \ -v /root/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml \ prom/prometheus 编辑prometheus.yml配置文件 12345678910111213scrape_configs: - job_name: 'prometheus' static_configs: - targets: ['10.0.0.6:9090'] #添加master及node节点添加 - job_name: 'master' static_configs: - targets: ['10.0.0.6:9100'] - job_name: 'node' static_configs: - targets: ['10.0.0.9:9100'] 这里只是提前编辑好,之后如果要添加新的机器,再后面添加 访问master的9090端口,http://10.0.0.6:9090/ 部署node_exporter(master和node)看需求,master不部署也可以 docker安装源码安装12345678docker run -d \ --name node-exporter \ --restart=always \ -p 9100:9100 \ -v "/proc:/host/proc:ro" \ -v "/sys:/host/sys:ro" \ -v "/:/rootfs:ro" \ prom/node-exporter12345678910111213141516171819202122232425262728293031323334353637# 下载最新的node_exporter版本wget https://github.com/prometheus/node_exporter/releases/download/v1.6.1/node_exporter-1.6.1.linux-amd64.tar.gz# 解压缩下载的文件tar xvfz node_exporter-1.6.1.linux-amd64.tar.gz# 将可执行文件移动到 /usr/local/binsudo mv node_exporter-1.6.1.linux-amd64/node_exporter /usr/local/bin/# 创建 node_exporter 用户useradd -rs /bin/false node_exporter# 创建 systemd 服务vim /etc/systemd/system/node_exporter.service[Unit]Description=Node ExporterWants=network-online.targetAfter=network-online.target[Service]User=node_exporterGroup=node_exporterType=simpleExecStart=/usr/local/bin/node_exporter[Install]WantedBy=multi-user.target# 重新加载 systemd 配置sudo systemctl daemon-reload# 启动 node_exporter 服务sudo systemctl start node_exporter# 设置为开机自动启动sudo systemctl enable node_exporter 验证访问http://<你的服务器IP>:9100/metrics 点击Mertrics,下图为node通过9100端口暴露出的监控指标 再次访问master的9090端口,就会显示上面的 UP 状态 再修改prometheus.yml 1234scrape_configs: - job_name: 'node_exporter' static_configs: - targets: ['<你的服务器IP>:9100'] 部署Grafana12345678910111213# 新建空文件夹grafana,用来存储数据mkdir /root/grafana# 设置权限chmod 777 -R /root/grafana# 运行docker run -d \ -p 3000:3000 \ --name=grafana \ -e "GF_SECURITY_ADMIN_PASSWORD=admin" \ -v /root/grafana:/var/lib/grafana \ grafana/grafana 访问master的3000端口 默认账号密码都为admin 创建数据源 联接Prometheus 点击Save & Test 创建面板 点击Import 输入模板代码9276 点击Load 再下拉框选中Prometheus 点击Import 查看,选中要查看的主机 监控节点主机的容器cAdvisor(Container Advisor)用于采集正在运行的容器资源使用和性能信息。 cAdvisor可以对节点机器上的资源及容器进行实时监控和性能数据采集,包括CPU使用情况内存使用情况 网络吞吐量及文件系统使用情况 部署Cadvisor12345678910docker run -d \ --volume=/:/rootfs:ro \ --volume=/var/run:/var/run:ro \ --volume=/sys:/sys:ro \ --volume=/var/lib/docker/:/var/lib/docker:ro \ --volume=/dev/disk/:/dev/disk:ro \ --publish=8080:8080 \ --detach=true \ --name=cadvisor \ google/cadvisor:latest 访问节点IP+端口 修改prometheus.yml,在末尾添加,修改玩之后要重启Prometheus 123- job_name: 'docker' static_configs: - targets: ['10.0.09:8080'] 访问节点IP+9090 然后在master主机登录Grafana,导入Docker监模板,id:193 在docker中搭建micrometer + grafana + prometheus的jvm监控 - 掘金 监控Mysql123456docker run -d \ --name mysql_exporter \ --restart always \ -p 9104:9104 \ -e DATA_SOURCE_NAME="root:123456@(10.0.0.8:3306)/" \ prom/mysqld-exporter 模板ID:7362
Mysql
常见问题字符集问题修改mysql的数据陆慕下的my.ini的配置文件(Windows系统) 12345[mysql] #大概在63行左右,在其下添加default-character-set=utf8 #默认字符集[mysqld] # 大概在76行左右,在其下添加character-set-server=utf8 collation-server=utf8_general_ci 注意:建议修改配置文件使用notepad++等高级文本编辑器,使用记事本等软件打开修改后可能会导致文件编码修改为“含BOM头”的编码,从而服务重启失败 重启服务,再查看一次编码 修改编码前,已经创建的表和库,修改编码后不会自动更改,需要重新建库表或者手动修改库表的编码 手动修改库表的编码 12345678#修改数据库的字符编码为utf8 alter database test charset utf8; #修改表字符编码为UTF8alter table student charset utf8; #修改字段字符编码为UTF8alter table student modify name varchar(20) charset utf8; 客户端连接的问题旧版本图形界面工具连接MySQL8时出现Authentication plugin caching_sha2_password' cannot be loaded错误。 MySQL8之前的版本中加密规则是mysql_native_password, MySQL8之后的加密规则是caching_sha2_password 解决问题: 修改用户名为“root@localhost”的用户密码规则为“mysql_native_password”,密码值为“123456” 1234567891011121314151617181920212223# 查看所有数据库mysql> show databases;+--------------------+| Database |+--------------------+| information_schema || mysql || performance_schema || ry-vue || sys |+--------------------+5 rows in set (0.01 sec)# 使用mysql数据库,不需要要分号,其余的命令要加分号use mysql;# 修改'root'@'localhost'用户的密码规则和密码ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'abc123';FLUSH PRIVILEGES;# 开启远程访问UPDATE user SET host='%' WHERE user='root';FLUSH PRIVILEGES; 存过报错报错信息: 1[Err] 1418 - This function has none of DETERMINISTIC, NO SQL, or READS SQL DATA in its declaration and binary logging is enabled (you *might* want to use the less safe log_bin_trust_function_creators variable) 我们就必须指定我们的函数是否是: DETERMINISTIC 不确定的 NO SQL 没有SQl语句 READS SQL DATA 只是读取数据 MODIFIES SQL DATA 要修改数据 CONTAINS SQL 包含SQL语句 其中在function/procedure 里面,只有 DETERMINISTIC, NO SQL 和 READS SQL DATA 被支持。 如果我们开启了 bin-log, 我们就必须为我们的function/procedure 指定一个参数。 解决方法: 方法1:临时修改方法2配置文件修改方法3:执行sql的时候添加信息1234# 在mysql数据库中执行以下语句 (临时生效,重启后失效)set global log_bin_trust_function_creators=TRUE;# 或者set global log_bin_trust_function_creators=1;12# 在配置文件/etc/my.cnf的[mysqld]或者my-default.ini文件中配置log_bin_trust_function_creators=112345678CREATE DEFINER=`root`@`localhost` FUNCTION test(param bigint) RETURNS decimal(10,0)# 需要添加这个READS SQL DATAREADS SQL DATABEGIN ....RETURN .....;END 常用SQL例子1234-- select 嵌套子查询select (select count(id) from user_info) as a, (select count(id) from user_info where age > 10) as b; 123-- 用到的函数,查询日期select curdate();select date(now()); 123456/* user_info已经筛选过一次, 再这之后再筛选一次 用的金额 */SELECT SUM(CASE WHEN status = 1 THEN amount ELSE 0 END) AS '冻结的用户的总金额', SUM(CASE WHEN status = 2 THEN amount ELSE 0 END) AS '活跃的用户的总金额'FROM user_info where register between '2020-02-02' and '2023-02-02'; 123-- insert的字段数据,需要查询出来的数据insert into bank_info(bank_code, bank_name)VALUES ('NEW', (select user_no from user_fund_info where id = 46)); 优化的例子优化大表的查询,查询一张流水表,要统计开始时间和时间之间的数据 12345678910111213141516171819202122232425262728293031323334353637-- 业务是:一个用户,流水在这个时间段最多只有一条记录,并且查询所有用户在这个时间段一共有多少条记录-- 流水表目前的数据量有70w-- 这样查询,需要好几秒的时间,explain的行数是70wSELECT count(1)FROM users AS t1 LEFT JOIN liushui AS t2 ON t1.id = t2.user_id and t2.begin_time <= '2023-09-14 21:01:28' AND t2.open_time >= '2023-09-14 21:01:28' ; -- 进行优化-- 增加了一个belong_date的字段,并且添加索引-- begin_time 和 open_time 有可能之间跨天了,所以belong_date要筛选两天的-- explain的行数只有2000多条SELECT count(1)FROM users AS t1 LEFT JOIN liushui AS t2 ON t1.id = t2.user_id AND t2.belong_date IN ('2023-09-14','2023-09-13') and t2.begin_time <= '2023-09-14 21:01:28' AND t2.open_time >= '2023-09-14 21:01:28' ; -- 再进行优化-- 上面的sql和这个sql explain 看着差不多一样,索引出来的流水表的数据量都是一样,可就是上面这条记录查询慢,下面这条记录查询快-- explain的行数只有2000多条-- 查询查询出来的效果是,先用belong_date查一遍,再在子查询的结果集再筛选一遍SELECT COUNT( 1 ) FROM ( SELECT t1.id t2.begin_time, # 这两个时间字段对业务是没有用的,是为了外面这个查询使用到 t2.open_time FROM lottery_open AS t1 LEFT JOIN lottery_period AS t2 ON t1.id = t2.lottery_id AND t2.belong_date IN ( CURDATE(), DATE_SUB( CURDATE(), INTERVAL 1 DAY ) )) AS t3 WHERE t3.begin_time <= '2023-09-14 21:01:28' AND t3.open_time >= '2023-09-14 21:01:28'; 实战mysql使用source导入数据导入的数据,中文乱码,只要先执行uft-8登录之后,再导入数据 1234567891011# 登录mysqlmysql -u root -p --default-character-set=utf8# 显示所有数据库show databases;# 选择数据库use [数据库]# 导入文件mysql> source d:\mysqldb.sql 导出1234567# --default-character-set=utf8 编码# -h 192.168.8.2 数据库地址# -u root 数据库用户名# -p# test 数据库库名# test.sql 导出sql的文件名mysqldump --default-character-set=utf8 --single-transaction --set-gtid-purged=OFF -h 192.168.8.2 -u root -p test > test.sql 语法比较运算符 12345678910111213141516171819202122232425262728# 字符串存在隐式转换,如果转换不成功,就看做0# 两边的值一个是整数,另一个是字符串,则MySQL会将字符串转化为数字进行比较。# 两边的值都是整数,则MySQL会按照整数来比较两个值的大小。mysql> select 1=1,1=2,1!=2,2='2',1='a',0='a';+-----+-----+------+-------+-------+-------+| 1=1 | 1=2 | 1!=2 | 2='2' | 1='a' | 0='a' |+-----+-----+------+-------+-------+-------+| 1 | 0 | 1 | 1 | 0 | 1 |+-----+-----+------+-------+-------+-------+1 row in set, 2 warnings (0.00 sec)# 字符串和字符串比较,就不隐式转换了mysql> select 'a'='a','ab'='ab','a'='b';+---------+-----------+---------+| 'a'='a' | 'ab'='ab' | 'a'='b' |+---------+-----------+---------+| 1 | 1 | 0 |+---------+-----------+---------+1 row in set (0.00 sec)# 两边的值、字符串或表达式中有一个为NULL,则比较结果为NULL。mysql> select 1=null,null=null;+--------+-----------+| 1=null | null=null |+--------+-----------+| NULL | NULL |+--------+-----------+1 row in set (0.01 sec) 安全等于运算符 安全等于运算符<=>与等于运算符=的作用是相似的,唯一的区别:<=>可 以用来对NULL进行判断。 在两个操作数均为NULL时,其返回值为1,而不为NULL;当一个操作数为NULL 时,其返回值为0,而不为NULL。 1234567891011121314151617181920212223242526272829303132333435363738394041mysql> select 1<=>1,1<=>2,2<=>'2',1<=>'a',0<=>'a';+-------+-------+---------+---------+---------+| 1<=>1 | 1<=>2 | 2<=>'2' | 1<=>'a' | 0<=>'a' |+-------+-------+---------+---------+---------+| 1 | 0 | 1 | 0 | 1 |+-------+-------+---------+---------+---------+1 row in set, 2 warnings (0.00 sec)mysql> select 'a'<=>'a','ab'<=>'ab','a'<=>'b';+-----------+-------------+-----------+| 'a'<=>'a' | 'ab'<=>'ab' | 'a'<=>'b' |+-----------+-------------+-----------+| 1 | 1 | 0 |+-----------+-------------+-----------+1 row in set (0.00 sec)mysql> select 1<=>null,null<=>null;+----------+-------------+| 1<=>null | null<=>null |+----------+-------------+| 0 | 1 |+----------+-------------+1 row in set (0.00 sec)#查询commission_pct等于0.40# 有数据SELECT employee_id,commission_pct FROM employees WHERE commission_pct = 0.40;# 没有数据SELECT employee_id,commission_pct FROM employees WHERE commission_pct = null;# 有数据SELECT employee_id,commission_pct FROM employees WHERE commission_pct is null;# 有数据SELECT employee_id,commission_pct FROM employees WHERE commission_pct <=> 0.40; # 只会查出commission_pct = null的SELECT employee_id,commission_pct FROM employees WHERE commission_pct <=> null; 非空运算符 12345678910#查询commission_pct等于NULL。比较如下的四种写法SELECT employee_id,commission_pct FROM employees WHERE commission_pct IS NULL; SELECT employee_id,commission_pct FROM employees WHERE commission_pct <=> NULL;# 有点像调用函数了,也是判断null的SELECT employee_id,commission_pct FROM employees WHERE ISNULL(commission_pct);# 不要这样写SELECT employee_id,commission_pct FROM employees WHERE commission_pct = NULL;# 习惯用就用,不习惯就换一种用法SELECT employee_id,commission_pct FROM employees WHERE NOT commission_pct <=> NULL; between and 12345# 查看年纪在23到230之间select * from table where age between 23 and 230# 查询年纪不再23和230之间的select * from table where age not between 23 and 230 In 和 not in 12345# inselect last_name,salary,department_id from employees where department_id in(10,20,30);# not inselect last_name,salary,department_id from employees where department_id not in(10,20,30); or 1234# 这两个查询出来的结果是不一样的,一定要仔细select last_name,salary,department_id from employees where department_id=10 or department_id=20 or department_id=30select last_name,salary,department_id from employees where department_id=10 or 20 or 30 like 12345678910111213141516171819202122# %代表不确定个数的字符select last_name from employees where last_name like '%a%';# %代表不确定个数的字符select last_name from employees where last_name like 'a%';# 包含字符a 并且 好办字符e的# 第一种写法select last_name from employees where last_name like '%a%' and last_name like '%e%' ;# 第二种写法select last_name from employees where last_name like '%a%e%' or last_name like '%e%a%';# 第三种写法# 强制第二个符号是a的,_表示一个不确定的字符select last_name from employees where last_name like '_a%';# 转义字符# 查询第二个字符是下划线,并且第三个字符串是a的员工select last_name from employees where last_name like '_\_a%';# 或者# 告诉mysql,&是我自定义的转移字符select last_name from employees where last_name like '_&_a%' escape '&'; 正则表达式 1234567mysql> SELECT 'shkstart' REGEXP '^s', 'shkstart' REGEXP 't$', 'shkstart' REGEXP 'hk';+------------------------+------------------------+------------------------+| 'shkstart' REGEXP '^s' | 'shkstart' REGEXP 't$' | 'shkstart' REGEXP 'hk' |+------------------------+------------------------+------------------------+| 1 | 1 | 1 |+------------------------+------------------------+------------------------+1 row in set (0.01 sec) NOT或者! 当给定的值为0 (False)时 NOT FALSE => 1 当给定的值为非0值时返回0; 当给定的值为NULL时,返回NULL。 1234567891011# 把1当做true# NOT1 ==> Not True== False=0# not(2)==>2 是true ==>false=0# NOT !1==> NOT !True ==> NOT False==> True ==>1mysql> SELECT 1=1,NOT 1, NOT 0, NOT(1+1),not(2), NOT !1, NOT NULL;+-----+-------+-------+----------+--------+--------+----------+| 1=1 | NOT 1 | NOT 0 | NOT(1+1) | not(2) | NOT !1 | NOT NULL |+-----+-------+-------+----------+--------+--------+----------+| 1 | 0 | 1 | 0 | 0 | 1 | NULL |+-----+-------+-------+----------+--------+--------+----------+1 row in set, 1 warning (0.00 sec) AND或者 && 当给定的所有值均为非0值,并且都不为NULL时,返回 1; 当给定的一个值或者多个值为0时则返回0; 否则返回NULL。 1234567# 1 理解成True,其它的值就不管用# 1 or -1 ,有一路是通电的,那就是通电的mysql> SELECT 1 OR -1, 1 OR 0, 1 OR NULL, 0 || NULL, NULL || NULL;+---------+--------+-----------+-----------+--------------+| 1 OR -1 | 1 OR 0 | 1 OR NULL | 0 || NULL | NULL || NULL |+---------+--------+-----------+-----------+--------------+| 1| 1| 1|NULL| NULL| +---------+--------+-----------+-----------+--------------+ 1 row in set, 2 warnings (0.00 sec) limit 格式:LIMIT [位置偏移量,] 行数 第一个“位置偏移量”参数指示MySQL从哪一行开始显示,是一个可选参数,如果不指定“位置偏移 量”,将会从表中的第一条记录开始(第一条记录的位置偏移量是0,第二条记录的位置偏移量是 1,以此类推); 第二个参数“行数”指示返回的记录条数。 MySQL 8.0中可以使用“LIMIT 3 OFFSET 4”,意思是获取从第5条记录开始后面的3条记录,和“LIMIT 4,3;”返回的结果相同。 分页显式公式 :(当前页数-1)*每页条数,每页条数 1SELECT * FROM table LIMIT(PageNo - 1)*PageSize,PageSize; 注意:LIMIT 子句必须放在整个SELECT语句的最后! order by 单列排序 12345# 默认升序select salary from employees order by salary# 使用列的别名,进行排序select salary,salary*12 as year_salary from employees order by year_salary 多列排序 123select employee_id, salary, department_idfrom employeesorder by department_id DESC,salary ASC; 可以使用不在SELECT列表中的列排序。 在对多列进行排序的时候,首先排序的第一列必须有相同的列值,才会对第二列进行排序。如果第 一列数据中所有值都是唯一的,将不再对第二列进行排序。 时间字段 date datetime timestamp 插入时间(CURRENT_TIMESTAMP) 不行(now()) 不行(now()) 可以 自动更新(CURRENT_TIMESTAMP) 不行 可以 可以 时区 时间字符串 保存时区 1234567891011121314# 插入的时候,自动插入时间 设置CURRENT_TIMESTAMP--添加CreateTime 设置默认时间 CURRENT_TIMESTAMP ALTER TABLE `test_time` ADD COLUMN `my_time_date` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间' ;--修改CreateTime 设置默认时间 CURRENT_TIMESTAMP ALTER TABLE `test_time` MODIFY COLUMN `my_time_date` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间' ; # 更新的时候自动更新时间 设置CURRENT_TIMESTAMP--添加UpdateTime 设置 默认时间 CURRENT_TIMESTAMP 设置更新时间为 ON UPDATE CURRENT_TIMESTAMP ALTER TABLE `test_time` ADD COLUMN `my_time_date` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '创建时间' ;--修改 UpdateTime 设置 默认时间 CURRENT_TIMESTAMP 设置更新时间为 ON UPDATE CURRENT_TIMESTAMP ALTER TABLE `test_time` MODIFY COLUMN `my_time_date` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '创建时间' ; 参考:MySQL 的 timestamp时区问题 时区123456789101112131415161718192021222324252627282930# 查询时区# 不需要看system_time_zone,只需要修改time_zonemysql> show variables like '%time_zone%';+------------------+--------+| Variable_name | Value |+------------------+--------+| system_time_zone | UTC || time_zone | +08:00 |+------------------+--------+2 rows in set (0.01 sec)# 查询全局时区,和会话时区mysql> select @@GLOBAL.time_zone,@@SESSION.time_zone;+--------------------+---------------------+| @@GLOBAL.time_zone | @@SESSION.time_zone |+--------------------+---------------------+| +08:00 | +08:00 |+--------------------+---------------------+1 row in set (0.00 sec)# 设置会话时区set time_zone='+8:00';# 设置全局时区# 全局会话有效。必须重新连接才生效set global time_zone='+8:00;# 修改 mysql 的配置文件永久设置时区[mysqld]default-time-zone=+08:00 mysql当前在什么时区,看哪个变量 看 time_zone,不看 system_time_zone。如要修改时区,直接修改 time_zone,无视 system_time_zone time_zone 的值如果是 SYSTEM 表示什么? 表示跟 system_time_zone 取值一样。安装MySQL后默认就是SYSTEM system_time_zone 的值是怎么来的? 它的值来自mysql服务启动时读取操作系统时区,读取后即使修改操作系统的时区,它的值也不会再改变了,除非重启mysql 服务变量重新读取 system_time_zone 的值能改变吗? 123# 不能通过命令改变mysql> set system_time_zone='JST';ERROR 1238 (HY000): Variable 'system_time_zone' is a read only variable 参考:关于mysql的时区 索引数据结构二叉树二叉树的缺点:顺序插入,会变成一个链表,层级比较深,检索速度慢,可以使用红黑树解决这个问题 1234567891011121314151617181920212223graph TD subgraph Binary Tree 10[10] 10 --> 5[5] 10 --> 15[15] 5 --> 2[2] 5 --> 7[7] 15 --> 12[12] 15 --> 20[20] 2 --> 1[1] 2 --> 3[3] 7 --> 6[6] end subgraph Slash Linked List direction LR L1[1] -->|/| L2[2] L2[2] -->|/| L3[3] L3[3] -->|/| L4[4] L4[4] -->|/| L5[5] L5[5] -->|/| L6[6] end 红黑树红黑树是一个自平衡的二叉树,大数据情况下,层级较深,检索速度慢 1234567891011121314151617181920212223graph TD style 10 fill:#000,stroke:#fff,stroke-width:2px,color:#fff style 5 fill:#f00,stroke:#000,stroke-width:2px,color:#fff style 15 fill:#f00,stroke:#000,stroke-width:2px,color:#fff style 2 fill:#000,stroke:#fff,stroke-width:2px,color:#fff style 7 fill:#000,stroke:#fff,stroke-width:2px,color:#fff style 12 fill:#000,stroke:#fff,stroke-width:2px,color:#fff style 20 fill:#000,stroke:#fff,stroke-width:2px,color:#fff style 1 fill:#f00,stroke:#000,stroke-width:2px,color:#fff style 3 fill:#f00,stroke:#000,stroke-width:2px,color:#fff style 6 fill:#f00,stroke:#000,stroke-width:2px,color:#fff 10[10] 10 --> 5[5] 10 --> 15[15] 5 --> 2[2] 5 --> 7[7] 15 --> 12[12] 15 --> 20[20] 2 --> 1[1] 2 --> 3[3] 7 --> 6[6] B-Tree和B+树的区别 B树:查找时可能在内部节点就能找到所需的值。 B+树:查找时需要遍历到叶子节点才能找到所需的值,因为所有的值都存储在叶子节点中。 范围查询: B树:范围查询需要在树中进行多次查找,并且由于叶子节点之间没有链接,范围查询可能效率较低。 B+树:由于叶子节点形成了一个链表,范围查询可以从一个叶子节点顺序访问到下一个,效率更高。 插入和删除: B树:插入和删除操作可能会影响到所有节点,因为值存储在所有节点中。 B+树:插入和删除操作主要影响叶子节点,内部节点只需要更新键和指针。这种结构使得B+树的插入和删除操作相对简单。 索引覆盖/覆盖索引 select 后面的字段可以从索引中获取到数据,就是索引覆盖,避免回标操作 比如user表,有id,name和age两个字段,id是主键索引,name是普通索引,age没有索引,select age 就是没有使用索引覆盖 如果不符合最左前缀匹配,虽然是索引覆盖,也是无法用到索引,回扫描索引树 索引下推 是5.6之后才有的,默认开启,可以设置index_condition_pushdown=off关闭掉 用类似官网的例子,user表,有id(主键),name,age三个字段,使用name,age建立一个普通索引 1,anthony,18 2,anthony,19 查询select *from user where name='anthony' and age != 18 如果没有使用下推,存储引擎中会查询到 name=’anthony’的数据行,得到行主键索引,比如有两个name=’anthony’,比如第一行和第四行,那么主键id就是1和2,分别用1和2去聚簇索引中查找匹配的行数据,返回给mysql server层,再过滤age!= 18 ,这就会涉及到两次回表,分别是id=1和id=2 如果使用索引下推,直接在存储引擎中通过where的筛选条件,直接在存储引擎中得到行数据,再回表查询,这样就只是需要回标一次,因为只有一行数据是符合name='anthony' and age != 18 使用了索引下推,执行explain计划的时候,extra的会显示Using index condition 回表 主键索引的B+树的叶子节点存储的是整行数据, 非主键索引的B+树的叶子节点存储的是主键的值 当查询根据非聚簇索引查询的时候,会先通过非聚簇索引查询到主键的值,然后再需要通过主键的值再进行一次查询才能得到要查询的数据,这个过程就是回表 主键索引为什么快 explain Possible_key 理论上用到的key 值有可能有多个,也有可能是null key 实际用到的可以 如果值是primary 就是主键索引 row SQL执行过程中会被扫描的行数,该数值越大,意味着需要扫描的行数,相应的耗时更长 事务ACID 原子性:同时成功,或者同时失败,undo log日志, 比如插入一条记录,undo就会保存一条delete日志 一致性:业务代码正确逻辑保证,比如try catch了异常,导致事务不能回滚 隔离性别: 持久性:一旦提交了事务,它对数据库的改变就是应该永久性的,持久性由redo log日志来保证 隔离级别 隔离级别 脏读 不可重复读 幻读 读未提交(Read Uncommitted) ✅ ✅ ✅ mvcc 读已提交(Read Committed) X ✅ ✅ mvcc 可重复读(Repeatable Read) X X ✅ 锁 串行化(Serializable) X X X 读未提交,查询到别的事务修改但是没有提交的数据 不可重复读和幻读都是同一事务类,读取的结果不一样 区别: 不可重复读:别的事务更新操作导致的 幻读,别的事务插入或者删除导致的 数据库准备SQL: 12345678910CREATE TABLE `mytest` ( `id` int NOT NULL AUTO_INCREMENT, `name` varchar(255) DEFAULT NULL, `amount` decimal(10,2) DEFAULT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB;INSERT INTO `lottery`.`mytest` (`id`, `name`, `amount`) VALUES (1, 'anthony', 100.00);INSERT INTO `lottery`.`mytest` (`id`, `name`, `amount`) VALUES (2, 'nick', 200.00); 命令:设置会话登记 123SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;set SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; 演示过程都是,事务A先操作,然后事务B再操作 读未提交读未提交(read-uncommitted),演示一 事务A 事务B anthony.amount=100 1 BEGIN; BEGIN; 2 UPDATE mytest SET amount = amount + 500 WHERE id = 1; 3 BEGIN; SELECT * FROM mytest WHERE id = 1; anthony.amount=600 4 commit; BEGIN; SELECT * FROM mytest WHERE id = 1; anthony.amount=600 读未提交(read-uncommitted),演示二 事务A 事务B anthony.amount=100 1 BEGIN; BEGIN; 2 UPDATE mytest SET amount = amount + 500 WHERE id = 1; 3 SELECT * FROM mytest WHERE id = 1; anthony.amount=600 4 rollback; SELECT * FROM mytest WHERE id = 1; anthony.amount=100 读已提交会话2,在一个事务你,查询到两次不同的金额 事务A 事务B anthony.amount=100 1 BEGIN; BEGIN; 2 UPDATE mytest SET amount = amount + 500 WHERE id = 1; 3 SELECT * FROM mytest WHERE id = 1; anthony.amount=100 4 commit; SELECT * FROM mytest WHERE id = 1; anthony.amount=600 可重复读会话2,在一个事务里,查到的数据是不会变的 事务A 事务B anthony.amount=100 1 BEGIN; 2 SELECT * FROM mytest WHERE id = 1; anthony.amount=100 3 BEGIN; 4 UPDATE mytest SET amount = amount + 500 WHERE id = 1; SELECT * FROM mytest WHERE id = 1; anthony.amount=100 commit; SELECT * FROM mytest WHERE id = 1; anthony.amount=100 commit; 可串行化 事务A 事务B BEGIN; anthony.amount=100 1 UPDATE mytest SET amount = amount + 500 WHERE id = 1; 2 BEGIN; 3 SELECT * FROM mytest WHERE id = 1; 阻塞中 4 commit; SELECT * FROM mytest WHERE id = 1; anthony.amount=150 事务A读操作 事务B写操作 事务B读操作 不阻塞 阻塞 事务B写操作 阻塞 阻塞 可串行化的实现原理: 在不加锁读的操作,会默认加的 读锁-共享锁-S锁:select * from mytest lock in share mode; 读锁是共享的,多个事务可以同时读取同一个资源,但不允许其他事务修改 事务A 事务B 1 Begin; select * from mytest where id =1 lock in share mode; Begin; Update myset set xx=xx where id =1 这里会阻塞 commit; 然后事务B才会执行 写-排它锁-X锁:select * from mytest for update; 写锁是拍他,会阻塞其他的写锁和读锁,update,delete,insert都会加锁 参考 https://learnku.com/articles/40258 https://blog.51cto.com/shijianfeng/2914313 https://www.zhihu.com/question/392569386 https://juejin.cn/post/7136112451959848991 InnoDB12345678910111213141516171819202122232425-- 查看缓存区的大小,单位是字节(Bytes)SHOW GLOBAL VARIABLES LIKE 'innodb_buffer_pool_size';-- 缓冲池中的总页数:SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_pages_total';-- 已经缓存的页数,需要✖️每一页的大小,通常是16KB每页SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_pages_data';-- 缓冲池中的脏页数:-- 脏页(即已修改但尚未写回磁盘的页)的页数。这也是一个重要的性能指标。-- updat先修改缓存,这时候就成了脏也,然后msyql再开线程更新到磁盘SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_pages_dirty';-- 清理binlog日志show variables like '%log_bin%'; #查看binlog启用状态show binary logs; #查看当前配置下已产生的mysql-binlog日志purge master logs before '2023-09-22 00:00:00'; reset master; # 清理过一次,第二天又恢复了-- 关闭binlog[mysqld]skip-log-bin# 然后重启电脑 参考:https://juejin.cn/post/7234924083842220092
Jenkins
Root用户权限启动Jenkins适用于Ubuntu环境下通过apt-get install -y jenkins命令在线安装的Jenkins 1.修改配置文件 123456789vim /etc/default/jenkins# 将下面两个参数修改为root# 修改前JENKINS_USER=$NAMEJENKINS_GROUP=$NAME# 修改后JENKINS_USER=rootJENKINS_GROUP=root 2.更改目录权限 不知道这步有没有起作用 12345cd /var/lib/chown -R root:root ./jenkins# 重启systemctl restart jenkins.service Jenkins使用教程配置JDKManage Jenkins –> Global Tool Configuration 自动安装 手动安装要注意,JAVA_HOME的输入框下面,不要有警告或者错误信息,否则就是路径不正确,取消勾选自动安装 配置Maven 安装Maven Integration 插件 通常自动安装maven就行了,如果手动安装,MAVEN_HOME 输入框下面,不要有警告或者错误信息,否则就是路径不正确 获取当前项目名称在脚本中输入 $JOB_NAME就能获取到了 卸载jenkins12345678910//服务sudo apt-get remove jenkins//安装包,注意这里如果不是ubuntu那就yumsudo apt-get remove --auto-remove jenkins//配置和数据sudo apt-get purge jenkinssudo apt-get purge --auto-remove jenkins 升级jenkins12# 适用于ubuntu安装的apt-get upgrade jenkins 同时构建任务的个数系统管理–>系统设置–>执行者数量 构建成功才继续发包只是个不大不小的功能,很容易忽略了,在 任务的配置–>Post Steps –> Run only if build succeeds 修改时区 WebHook使用的场景,Github Push代码之后,自动构建 第一步.jenkins的配置 第二步.Github的配置 第三步.返回jenkins设置 第四步.push代码,查看有没有正常构建 Jenkins+Docker部署Maven聚合工程安装Jenkins 1234567891011docker run \ -v /var/run/docker.sock:/var/run/docker.sock \ -v $(which docker):/usr/bin/docker \ -v /root/jenkins:/var/jenkins_home \ --name jenkins \ --user=root \ -p 8080:8080 \ -p 50000:50000 \ -d \ -u 0 \ jenkins/jenkins:jdk11 第2行是将宿主机的/var/run/docker.sock映射到容器中,这样在容器中运行的docker命令,就会在宿主机上来执行。 第3行是将宿主机的docker程序映射进容器中,这样本身没有安装docker的jenkins容器就可以执行docker命令了(事实上容器里是没有运行docker的服务的,我们只是用这个映射进容器的docker来作为客户端发送docker的指令到/var/run/docker.sock而已,而/var/run/docker.sock已经被链接到宿主机了) Dockerfile 1234FROM openjdk:8-jdk-alpineVOLUME /tmpADD admin-server-0.0.1-SNAPSHOT.jar app.jarENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"] Maven的pom.xml配置 12345678910111213141516<plugin> <groupId>com.spotify</groupId> <artifactId>docker-maven-plugin</artifactId> <version>0.4.13</version> <configuration> <imageName>springboot/${project.artifactId}</imageName> <dockerDirectory>src/main/docker</dockerDirectory> <resources> <resource> <targetPath>/</targetPath> <directory>${project.build.directory}</directory> <include>${project.build.finalName}.jar</include> </resource> </resources> </configuration></plugin> imageName打的镜像名称,这里的镜像名称是:springboot/admin-server dockerDirectory指定docker文件夹的位置 Job配置 • -e “SPRING_PROFILES_ACTIVE=prerelease” ,可以看成是启动jar的时候的,java -jar admin-server.jar –spring.profiles.active=prerelease ,指定运行环境 脚本检测容器的运行1234567891011121314# "=" 两边不能有空格count=`docker ps -a | grep sabong-admin-controlfront |awk '{print $1}'`if [ -n "$count" ]; then docker stop $count docker rm $count echo "停止并删除容器"fidocker run \ -itd \ -p 9011:9011 \ -e “SPRING_PROFILES_ACTIVE=prerelease” \ --name admin-server \ springboot/admin-server 推送镜像到harbor12345678910# 打标签# sabong/sabong-controller-manager:latest 已经build成功了的镜像名字# 10.0.0.7/library/sabong/sabong-controller-manager:latest 新的镜像名字docker tag sabong/sabong-controller-manager:latest 10.0.0.7/library/sabong/sabong-controller-manager:latest# 登录私有仓库docker login -u admin -p 123456 10.0.0.7# 推送镜像docker push 10.0.0.7/library/sabong/sabong-controller-manager:latest Vue打包成镜像123456789101112131415npm installnpm run build:prod# build# sabong/sabong-admin-controlfront 随便起的docker build -t sabong/sabong-admin-controlfront --no-cache . # 打标签docker tag sabong/sabong-admin-controlfront:latest 10.0.0.7/library/sabong/sabong-admin-controlfront:latest# 登录私有仓库docker login -u admin -p 123456 10.0.0.7# 推送镜像docker push 10.0.0.7/library/sabong/sabong-admin-controlfront:latest Dockerfile 123From nginxCOPY dist/ /usr/share/nginx/html/ ⛔ 这里用的是nginx容器里默认的nginx配置文件,所以这里没有使用自定义的nginx配置 SpringBoot项目脚本适用于ssh传输文件,启动脚本 1234567891011121314151617181920#!/bin/bash# 具体不知道是干啥的,加上这个,这个看情况加# BUILD_ID=DONTKILLMEAPP_NAME=xxx.jar# 杀进程pid=`ps -ef|grep $APP_NAME|grep -v grep|awk '{print $2}' ` if [ -n "${pid}" ]; then kill -9 $pidfiecho '准备启动jar包'nohup java -jar /root/$APP_NAME --spring.profiles.active=prod &sleep 3echo '启动成功' Maven多模块打包指定打包项目名 1clean package -pl 模块名 -am 配置SSH server 在系统管理–>System—>SSH Server配置 配置SSH Over FTP publish 这里Verbose output in console的作用是把远程的步骤,和远程的shell执行的结果也打印出来 这里Exec in pty 要勾选,不然项目启动完成了,也不会停止SSH链接 前端打包选择已经配置的node版本 构建环境 Build Steps 选择执行shell 12345npm installnpm run build:prodpwd# 删除上一次构建打的包,然后dist文件夹打包成dist.zip文件rm -f dist.zip && zip -r dist.zip dist 各种问题报错1.HTTP ERROR 403 No valid crumb was included in the request 解决办法:网上都是要去关闭CSRF,很明确的是我这里的全局安全配置里面根本就没有选项,我的版本是jenkins 2.293。所以百度出来的都是一堆垃圾,无奈自己解决,尝试一番之后,找到了方法,如 下图所示:还是在全局安全配置里面,勾选上这个参数即可。 2.Jenkins配置中安装插件时提示No such plugin: cloudbees-folder 1sudo systemctl start jenkins 2.Jenkins和Github personal access token 登录到您的 Jenkins 服务器。 选择“Jenkins”>“Manage Jenkins”>“Configure System”。 滚动到“GitHub”部分。 点击“Add GitHub Server”按钮。 在“API URL”字段中输入您的 Github API URL(例如 api.github.com)。 在“Credentials”字段中选择“Add”。 选择“Kind”为“Username with password”。 在“Username”字段中输入您的 Github 用户名。 在“Password”字段中输入您的 Github 个人访问令牌。 点击“Verify credentials”按钮以验证凭据是否有效。 如果验证成功,请点击“Save”按钮保存更改。 3.jenkins映射docker 12345# 如果docker run jenkins 没有指定# 这里就不能打包,就是因为在jenkins里的容器中,没有安装对象,使用了-v # 就是把jenkins里所需要的docker映射到宿主docker中-v /var/run/docker.sock:/var/run/docker.sock \-v $(which docker):/usr/bin/docker 4.安装插件没有成功 需要到插件中心,在可更新和可选插件里,一个一个手动去搜索安装,如果还有问题,就需要更新jenkins 5.npm install需要权限 1Unable to save binary /var/lib/jenkins/workspace/mymanager-/node_modules/node-sass/vendor/linux-x64-83 : Error: EACCES: permission denied, mkdir '/var/lib/jenkins/workspace/lottery-web-24kai-ausk3/node_modules/node-sass/vendor' 修改端口123456789101112131415161718192021222324252627282930# 第一步sudo vim /etc/init.d/jenkins# 8088改成想要的check_tcp_port "http" "$HTTP_PORT" "8088" || return 1# 第二步sudo vim /etc/default/jenkins# 8088改成想要的HTTP_PORT=8088# 第三步vim /lib/systemd/system/jenkins.serviceEnvironment="JENKINS_PORT=80"# 执行sudo systemctl daemon-reloadsudo systemctl start jenkinssudo systemctl status jenkins# 这个时候就启动报错,然后接着修改vim /lib/systemd/system/jenkins.service# 本来是jenkins的,改成rootUser=rootGroup=root# 执行,就可以了,不知道为啥sudo systemctl daemon-reloadsudo systemctl start jenkinssudo systemctl status jenkins jenkins使用root账号登录设置1 设置2 权限管理1.安装插件,Role-based Authorization Strategy 2.点击 管理系统–>安全段落下的–>全局安全配置,选中 3.管理系统–>安全段落下的 就会多一个功能
Nginx
Docker安装 Nginx123456789101112131415# 简单的docker run --name nginx-test \ -p 8080:80 \ -d \ nginx# 正式的docker run -d \ -p 443:443 \ -p 80:80 \ --name nginx \ -v /home/nginx/www:/usr/share/nginx/html \ -v /home/nginx/conf/nginx.conf:/etc/nginx/nginx.conf \ -v /home/nginx/logs:/var/log/nginx \ nginx Ubuntu安装Nginx1234567891011121314151617181920212223242526272829# Ubuntu安装依赖sudo apt-get install libpcre3 libpcre3-dev openssl libssl-dev zlib1g-dev# Ubuntu 如果还缺少依赖apt-get install build-essential# Centos安装依赖yum install -y gcc-c++ pcre pcre-devel zlib zlib-devel openssl openssl-devel# 进入到解压的文件cd ./nginx-1.16./configure --prefix=/url/local/nginx#或者./configure# 如果需要指定模块./configure --add-module=../nginx-rtmp-module#然后目录下就多了一个nginx文件make && make install#启动cd ../nginx/sbin./nginx# 配置文件目录所有的配置文件都在/etc/nginx下,并且每个虚拟主机已经安排在了/etc/nginx/sites-available下程序文件在/usr/sbin/nginx日志放在了/var/log/nginx中并已经在/etc/init.d/下创建了启动脚本nginx默认的虚拟主机的目录设置在了/var/www/nginx-default (有的版本 默认的虚拟主机的目录设置在了/var/www, 请参考/etc/nginx/sites-available里的配置) Nginx 常用命令12345678910111213141516171819202122# 启动nginxstart nginx# 或者nginx# 修改配置后重新加载生效nginx -s reload# 重新打开日志文件nginx -s reopen# 测试nginx配置文件是否正确nginx -t -c /path/to/nginx.conf# 快速停止nginxnginx -s stop# 完整有序的停止nginxnginx -s quit# 指定配置文件启动nginx -c /home/anthony/nginx.conf Nginx 配置模板123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133#user nobody;# 设置 <= cpu核数,一般设置成cpu*核数worker_processes 1;# 指定错误日志文件存放路径,错误日志级别可选项为【debug|info|notice|warn|error|crit】error_log logs/warn.log warn;# 指定pid存放路径 nginx启动后的进程IDpid logs/nginx.pid;# 工作模式及连接数上限#user nobody;# 设置 <= cpu核数worker_processes 1;# 指定错误日志文件存放路径,错误日志级别可选项为【debug|info|notice|warn|error|crit】# error_log logs/error.log error;# 指定pid存放路径 nginx启动后的进程ID# pid logs/nginx.pid;# 工作模式及连接数上限events { # 使用网络I/O模型,Linux系统推荐使用epoll模型,FreeBSD系统推荐使用kqueue;window下不指定 # user epoll; # 允许的最大连接数 worker_connections 1024;}# 设定http服务器,利用他的反向代理功能提供负载均衡支持http { # 设定mime类型 include mime.types; default_type application/octet-stream; # 设定日志格式 log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; client_header_buffer_size 1k; large_client_header_buffers 4 4k; # access_log logs/info.log main; # # 设定access log send_timeout 3m; sendfile on; tcp_nopush on; tcp_nodelay on; # 这个参数表示http连接超时时间,默认是65s。要是上传文件比较大,在规定时间内没有上传完成,就会自动断开连接!所以适当调大这个时间。 keepalive_timeout 0; keepalive_timeout 5000; # 开启gzip模块 gzip on; gzip_min_length 1100; gzip_buffers 4 8k; gzip_types text/plain application/x-javascript text/css application/xml text/javascript application/x-httpd-php application/javascript application/json; output_buffers 1 32k; postpone_output 1460; server_names_hash_bucket_size 128; client_header_timeout 3m; #调大点 client_body_timeout 3m; #调大点 client_max_body_size 200m; #主要是这个参数,限制了上传文件大大小 #client_body_buffer_size 1024; fastcgi_connect_timeout 300; fastcgi_send_timeout 300; fastcgi_read_timeout 300; fastcgi_buffer_size 64k; fastcgi_buffers 4 64k; fastcgi_busy_buffers_size 128k; fastcgi_temp_file_write_size 128k; gzip_http_version 1.1; gzip_comp_level 2; gzip_vary on; #设定虚拟主机 server { listen 80; server_name ap.mmzcg.com; # 设置url编码格式,解决参数中文乱码问题 charset UTF-8; # 设定本虚拟主机的访问日志 # access_log logs/host.access.log main; # 对 "/" 启用负载均衡 location / { # 设定代理访问地址 proxy_pass http://127.0.0.1:30000/; # 配置SSL证书 # ssl_certificate /etc/nginx/conf.d/ssl/STAR.huihuang200.com.crt; # ssl_certificate_key /etc/nginx/conf.d/ssl/STAR.huihuang200.com.key; # ssl_session_timeout 5m; # ssl_session_cache shared:SSL:10m; # ssl_protocols TLSv1 TLSv1.1 TLSv1.2 SSLv2 SSLv3; # ssl_ciphers ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP; # ssl_prefer_server_ciphers on; # ssl_verify_client off; # 解决ajax跨域问题 add_header 'Access-Control-Allow-Origin' '*'; add_header 'Access-Control-Allow-Credentials' 'true'; add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type'; # 保留用户真实信息 proxy_set_header Host $host:$server_port; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # 允许客户端请求的最大单个文件字节数 client_max_body_size 100m; # 缓冲区代理缓冲用户端请求的最大字节数,可以理解为先保存到本地再传给用户 client_body_buffer_size 1280k; # nginx跟后端服务器连接超时时间,发起握手等候响应超时时间(代理连接超时) proxy_connect_timeout 5; # 连接成功后 等待后端服务器响应时间 其实已进入后端的排队之中等候处理 proxy_read_timeout 60; # 代理请求缓存区 这个缓存区间会保存用户的头信息一共Nginx进行规则处理 一般只要能保存下头信息即可 proxy_send_timeout 30; # 同上 告诉Nginx保存单个用的几个Buffer最大用多大空间 proxy_buffer_size 256k; proxy_buffers 4 256k; # 如果系统很忙的时候可以申请国内各大的proxy_buffers 官方推荐 *2 proxy_busy_buffers_size 256k; # proxy 缓存临时文件的大小 proxy_temp_file_write_size 256k; proxy_next_upstream error timeout invalid_header http_500 http_503 http_404; proxy_max_temp_file_size 128m; } }} Nginx日志管理123456789101112131415161718#access_log logs/access.log main;# nginx的默认配置,如果在安装的nginx里没有看到这个,也没有关系#log_format main '$remote_addr - $remote_user [$time_local] "$request" '# '$status $body_bytes_sent "$http_referer" '# '"$http_user_agent" "$http_x_forwarded_for"';server { listen 80; location / { root my; index index.html } #配置日志, main 就是log_format 后面的名字 # 该server的访问日志的文件是logs/access.log,使用的是main格式 access_log logs/access.log main;} 推荐的配置 1234log_format main '$remote_addr - $remote_user [$time_local] "$request" $http_host ' '$status $request_length $body_bytes_sent "$http_referer" ' '"$http_user_agent" $request_time'; remote_addr : 客户端地址 remote_user : 客户端用户名 time_local : 服务器时间 request : 请求内容,包括方法名,地址,和http协议 http_host : 用户请求是使用的http地址 status : 返回的http 状态码 request_length : 请求大小 body_bytes_sent : 返回的大小 http_referer : 来源页 http_user_agent : 客户端名称 request_time : 整体请求延时 日志书写规则 一行要用单引号包起来,一行一个,换行也不需要什么连接的符号 location匹配= 精准匹配不写 一般匹配~ 正则匹配location proxy_pass 后面的url 加与不加/的区别 server_name匹配通配符匹配12345678910111213server { listen 80; server_name *.example.org; ...`}server { listen 80; server_name mail.*; ...} 通配符格式中的*号只能在域名的开头或结尾,并且*号两侧只能是.这些无效: www.*.example.org w*.example.org *号可以匹配多个域名部分,*.example.org可以匹配到: www.example.org www.sub.example.org .example.org是比较特殊的通配符格式, 可以同时匹配 example.org *.example.org。 正则匹配⁉️ 不适用我自己 精确匹配12345server { listen 80; server_name example.org www.example.org; ...} 特殊匹配格式 server_name ""; 匹配Host请求头不存在的情况。 server_name "-"; 无任何意义。 server_name "*"; 它被错误地解释为万能的名称。 它从不用作通用或通配符服务器名称。相反,它提供了server_name_in_redirect指令现在提供的功能。 现在不建议使用特殊名称“ *”,而应使用server_name_in_redirect指令。 匹配顺序 精确的名字 以*号开头的最长通配符名称,例如 *.example.org 以号结尾的最长通配符名称,例如 mail. 第一个匹配的正则表达式(在配置文件中出现的顺序) 优化 尽量使用精确匹配; 当定义大量server_name时或特别长的server_name时,需要在http级别调整server_names_hash_max_size和server_names_hash_bucket_size,否则nginx将无法启动。 Nginx报错1.服务器重启之后,执行 nginx -t 是OK的,然而在执行 nginx -s reload 的时候报错nginx: [error] invalid PID number "" in "/run/nginx.pid" 1234# 解决办法:nginx -c /etc/nginx/nginx.confnginx.conf文件的路径可以从nginx -t的返回中找到。nginx -s reload 2.a duplicate default server for 0.0.0.0:80nginx: [emerg] a duplicate default server for 0.0.0.0:80 in /etc/nginx/sites-enabled/gitlab:10 123删除/etc/nginx/sites-available/default文件,重新启动服务即可nginx -t :检查配置文件是否出错 3.403123打开nginx.conf例子:vim /etc/nginx/nginx.conf把 user 用户名 改为 user root 或 其它有高权限的用户名称即可 Nginx应用Acme.sh的使用123456789101112131415161718192021222324252627282930313233343536# 下载软件curl https://get.acme.sh | sh# 设置别名 alias:alias acme.sh=~/.acme.sh/acme.sh # 切换 Let's Encrypt,默认使用 ZeroSSLacme.sh --set-default-ca --server letsencrypt# 执行,会打印配置域名的TXTacme.sh --issue -d baidu.me --dns --yes-I-know-dns-manual-mode-enough-go-ahead-please# 配置好执行,再执行acme.sh --issue -d baidu.me --dns --yes-I-know-dns-manual-mode-enough-go-ahead-please --renew# --nginx 就是指定域名的配置文件Your cert is in: /root/.acme.sh/baidu.me_ecc/baidu.me.cerYour cert key is in: /root/.acme.sh/baidu.me_ecc/baidu.me.keyThe intermediate CA cert is in: /root/.acme.sh/baidu.me_ecc/ca.cerAnd the full chain certs is there: /root/.acme.sh/baidu.me_ecc/fullchain.cer# 查看申请的证书root@jenkins-jumpserver-nginx:~/.acme.sh# acme.sh --listMain_Domain KeyLength SAN_Domains CA Created Renewbaidu "ec-256" no LetsEncrypt.org 2023-09-17T11:52:22Z 2023-11-15T11:52:22Z# 续期证书# 证书默认是90天,如需强制更新证书,则执行以下命令acme.sh --renew -d abc.xyz --force# acme.sh会自动为你创建 cronjob, 每天 0:00 点自动检测所有的证书, # 如果快过期了, 需要更新, 则会自动更新证书,使用以下命令可查看定时任务crontab -l# 升级 acme.sh 到最新acme.sh --upgrade 12345# CFexport CF_Key="xxxxxxx" export CF_Email="CF账号的邮箱"acme.sh --issue -d "baidu.com" --dns dns_cf 建立软连接1sudo ln -s /etc/nginx/sites-available/domain-one.com /etc/nginx/sites-enabled/ 快速部署静态应用1234567891011121314# 首尾配置暂时忽略server { listen 8080; server_name localhost; location / { # 设置为个人项目的根目录路径 root /usr/local/var/www/my-project; index index.html index.htm; # vue项目404让前端路由处理 try_files $uri $uri/ /index.html; }}# 首尾配置暂时忽略 跨域1234567891011121314# 首尾配置暂时忽略server { listen 8080; server_name localhost; location / { # 跨域代理设置 proxy_pass http://www.proxy.com; # 要实现跨域的域名 add_header Access-Control-Allow-Origin *; add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS'; add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization'; }}# 首尾配置暂时忽略 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748# 第二种server { listen 80 default_server; server_name _ www.*; location / { index index.html; root /home/ubuntu/sabong-server-front; try_files $uri $uri/ /index.html; } location /api/ { if ($request_method = 'OPTIONS') { add_header 'Access-Control-Allow-Origin' *; add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS'; add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,language,If-Modified-Since,Cache-Control,Content-Type'; add_header 'Access-Control-Allow-Credentials' true; add_header 'Access-Control-Max-Age' 86400; return 200; } add_header 'Access-Control-Allow-Origin' *; add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS'; add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,language,If-Modified-Since,Cache-Control,Content-Type'; add_header 'Access-Control-Allow-Credentials' true; add_header 'Access-Control-Max-Age' 86400; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forward-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; proxy_set_header X-Nginx-Proxy true; proxy_pass http://172.31.41.206:9001/; } location /ws { proxy_pass http://172.31.41.206:9006/ws; proxy_http_version 1.1; proxy_read_timeout 360s; proxy_redirect off; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host:$server_port; proxy_set_header X-Real-IP $remote_addr; proxy_set_header REMOTE-HOST $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; }} Gzip压缩1234567http { # 配置gzip压缩 gzip on; gzip_min_length 1000; # 设定压缩的临界点 gzip_comp_level 3; # 压缩级别 gzip_types text/plain application/xml; # 要压缩的文件类别} 转发Vue项目1234567891011121314151617181920server { listen 80 default_server; server_name _; location / { index index.html; root /home/ubuntu/sabong-server-front; # nginx 转发给前端判断是不是404 try_files $uri $uri/ /index.html; } location /api/ { proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; proxy_set_header X-Nginx-Proxy true; proxy_pass http://172.31.41.206:9001/; }} 转发Websocket123456789101112location /ws { proxy_pass http://172.31.41.206:9006/ws; proxy_http_version 1.1; proxy_read_timeout 360s; proxy_redirect off; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host:$server_port; proxy_set_header X-Real-IP $remote_addr; proxy_set_header REMOTE-HOST $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;} 转发Redis只知道这么配置,不知道怎么给redis配置上域名 1234567891011121314151617181920212223242526272829303132user www-data;worker_processes auto;pid /run/nginx.pid;include /etc/nginx/modules-enabled/*.conf;events { worker_connections 768; # multi_accept on;}http{ # ...}# 要跟http统计,旧的版本没有steram模块,需要编译的时候加# 下载最新的版本肯定是有stream模块的stream { upstream redis { # myredis.godjfp.com 要被转发的Redis地址 # 6379:redis自己设置的端口是多少,就是多少 server myredis.godjfp.com:6379 max_fails=3 fail_timeout=30s; } server { # 公网访问的端口 listen 6379; proxy_connect_timeout 1s; proxy_timeout 3s; proxy_pass redis; }} 图片上传post上传文件,出现413错误码 解决方案 1234#允许客户端请求的最大单文件字节数client_max_body_size 10m; #缓冲区代理缓冲用户端请求的最大字节数client_body_buffer_size 128k; HTTPS跳转1234567891011121314151617181920212223242526272829303132333435363738394041424344454647server { listen 80; listen 443 ssl; # 配置SSL证书 server_name abc.xyz; # 如果是http,就跳转https,注意nginx配置文件的不同,可能会出现重定向次数过多 if ($scheme = http) { return 301 https://$host$request_uri; } location / { proxy_pass http://192.168.0.2:8084/; }}# 比如配置强制跳转https,导致重定向次数过多server { listen 80; listen 443 ssl; # 配置SSL证书 server_name abc.xyz; # 如果是http,就跳转https,注意nginx配置文件的不同,可能会出现重定向次数过多 # 比如这个配置 return 301 https://$server_name$request_uri; # 比如这个配置 rewrite ^(.*)$ https://$host$1 permanent; location / { proxy_pass http://192.168.0.2:8084/; }}# 上面两种导致重定向的次数过多,需要修改配置文件,比如下面的配置文件就可以server { listen 80; server_name your_domain.com www.your_domain.com; return 301 https://$host$request_uri;}server { listen 443 ssl; server_name your_domain.com www.your_domain.com; ssl_certificate /path/to/your/certificate.crt; ssl_certificate_key /path/to/your/private_key.key;} 代理JumpServer1234567891011121314151617181920212223server { listen 80 default_server; listen [::]:80 default_server; root /var/www/html; index index.html index.htm index.nginx-debian.html; server_name _; location / { # 比如服务器里的JumpServer的端口已经设置成了8083 proxy_pass http://127.0.0.1:8083/; # 按照官方的配置,需要使用WebSocket,如果不配置的话,访问JumpServer网页会提示WebSocket报错 proxy_http_version 1.1; proxy_buffering off; proxy_request_buffering off; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $remote_addr; }} 官方文档:使用Nginx代理JumpServer 代理Jenkins1234567891011121314151617181920212223242526272829303132333435363738server { listen 80 default_server; listen [::]:80 default_server; server_name _; location / { proxy_pass http://127.0.0.1:8083/; }}# 如果使用这样的配置,会有问题,比如配置的域名是baidu.com# 当访问baidu.com会正常访问,当登录之后,网页地址就变成http://127.0.0.1:8083/# 需要改成一下配置server { listen 80 default_server; listen [::]:80 default_server; server_name _; location / { proxy_pass http://127.0.0.1:8080/; # 下面这几个配置,访问Github会403 proxy_http_version 1.1; http2_push_preload on; # Enable http2 push proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-For $remote_addr; proxy_set_header X-Real-IP $remote_addr; # 这个配置又是正常的,要看看这个请求头是干嘛的了 proxy_set_header X-Rea $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; proxy_set_header X-Nginx-Proxy true; }} 12345678910111213141516171819202122232425262728293031server { listen 80; server_name _; location /api/ { client_max_body_size 100m; client_body_buffer_size 50m; proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_pass http://172.31.2.105:8083/; } # 代理swagger稳定 openapi3的版本,springboot3的版本 location /v3/api-docs { client_max_body_size 100m; client_body_buffer_size 50m; proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_pass http://172.31.2.105:8083/v3/api-docs; } location / { try_files $uri $uri/ /index.html; root /usr/share/nginx/html/manager/dist/; index index.html index.htm; } } SRSDocker方式运行1234docker run -p 1935:1935 -p 1985:1985 -p 8080:8080 ossrs/srs:latest# 测试# 访问:[http://192.168.254.180:8080/](http://192.168.254.180:8080/) SRS安装参考https://cloud.tencent.com/developer/article/1693951 Mac改成只播放系统声音Mac obs推流直播无声音解决方法_OBS教程_OBS Open Broadcaster Software OBS权限监听浏览器
Flutter
安装以Ma为例,安装之后任何地方有问题,要执行flutter自带的命令,检查flutter doctor 选择对应的版本,下载到本地就是一个压缩包,解压,就能看到flutter的源码,包含DartSDK 修改环境变量,记得要source下 1export PATH=安装目录/flutter/bin:$PATH Android Studio 安装Flutter插件,会自动安装Dart插件,新建Flutter项目 选择flutter下载的目录,最后解压出来的文件夹,后缀加上版本号 选择要创建哪些平台,看别人的教程,这里还有ios language的选项,不知道这里为啥没有 点击创建,flutter项目就创建完成了,接下来安装安卓模拟器 用生成出来的Demo运行下,就能看到例子了,到目前使用的是安卓的模拟器,下面是ios的模拟器 下载xcode,创建一个默认的ios的项目,点击运行,会提示下载模拟器,点下载就好了 选择模拟器,并重新再运行 运行成功就会有一个iPhone的界面 安装命令行工具 之后再执行flutter doctor就能大部分的都解决了,没有解决的,有会相应的命令提示 资源 & 媒体添加资源和图片指定资源 Flutter 使用 pubspec.yaml 文件,位于项目根目录,来识别应用程序所需的资源。 在根目录下创建assets文件夹 123456flutter: assets: # 指定单个资源 - assets/my_icon.png # 指定整个目录,需要在目录名称的结尾加上 /: - directory/ 代码使用 1Image.asset("assets/images/star.png", width: 30, height: 30) 加载网络图片1Image.network("https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9Gc") 参考: Flutter官网
Pinia
安装 1npm install pinia
Vue
按照官方文档的教程步骤来,大部分的代码Demo,都是通过脚手架创建的项目的基础上创建的,用的版本的Vue3,Typescript,组合式的写法 基础创建一个应用多个应用实例 没什么用,现在项目都是从头开始都是vue项目,不会用来一步一步替换,没有想到应用场景 1234567891011121314151617181920212223242526272829303132333435363738394041424344<!DOCTYPE html><html><head> <title>Vue Multiple Instances Example</title> <script src="https://cdn.jsdelivr.net/npm/vue@2"></script></head><body> <div id="app1"> <p>{{ message }}</p> <button @click="updateMessage">更新消息</button> </div> <div id="app2"> <p>{{ message }}</p> <button @click="updateMessage">更新消息</button> </div> <script> var app1 = new Vue({ el: '#app1', data: { message: '这是第一个 Vue 实例的消息' }, methods: { updateMessage() { this.message = '第一个实例的消息已更新'; } } }); var app2 = new Vue({ el: '#app2', data: { message: '这是第二个 Vue 实例的消息' }, methods: { updateMessage() { this.message = '第二个实例的消息已更新'; } } }); </script></body></html> 模板语法文本插值 双大括号标签会被替换为msg 属性的值。同时每次 msg 属性更改时它也会同步更新。 12345678<script setup lang="ts">import {ref} from "vue";const text = ref<string>("文本插值语法");</script><template> {{text}}</template> 原始 HTML 有的时候想想渲染源码,有的时候不想渲染源码 v-html就是指令语法 123456789<script setup lang="ts">import {ref} from "vue";const rawHtml = ref<string>("<span style='color: red'>This should be red.</span>");</script><template> <p>Using text interpolation: {{ rawHtml }}</p> <p>Using v-html directive: <span v-html="rawHtml"></span></p></template> Attribute 绑定 如果想给html的属性绑定数据要怎么操作呢,下面以给div的id和class属性绑定做演示 123456789101112131415161718192021222324252627282930313233343536373839404142<script setup lang="ts">import {ref} from "vue";const myId = ref<string>("myId");const showButton = ref<boolean>(false); type objectOfAttrs={ id: string, class:string} const objectOfAttrsObj=ref<objectOfAttrs>({ id: "myId4", class:"demo"}); </script><template> <!-- ❎错误做法 --> <div id={{myId}}></div> <!-- ✅正确做法,如果绑定的值是 null 或者 undefined,那么该 attribute 将会从渲染的元素上移除。 --> <div v-bind:id="myId"></div> <!-- ✅正确做法,简写写法 --> <div :id="myId"></div> <!-- ✅正确做法,同名简写写法,就是属性(Id),和等号对应的值是一样的话 --> <div :id></div> <!-- ✅正确做法,同名简写写法,第二种写法 --> <div v-bind:id></div> <!-- -------------------------------------------------------- --> <!-- 绑定布尔类型 --> <button :disabled="showButton">按钮</button> <!-- 动态绑定多个值 --> <div v-bind="objectOfAttrsObj">动态绑定多个值,只是需要写v-bind</div></template> 响应式基础这个有点底层,不去了解了,大概意思是简单的数据类型就用ref,对象或者复杂嵌套的结构就用reactive 计算属性基础示例 Vue 的计算属性会自动追踪响应式依赖。它会检测到 authorComputed 依赖于 author.books,所以当 author.books 改变时,任何依赖于 authorComputed 的绑定都会同时更新。 计算属性会被缓存,并且只调用一次,方法是调用一次执行一次,在例子中,触发了一次页面渲染(页面的数据有变动),方法就会被执行一次 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061<script setup lang="ts">import {computed, reactive, ref} from "vue";type authorType={ name:string, books:string[]}const author = reactive<authorType>({ name: 'John Doe', books: [ 'Vue 2 - Advanced Guide', 'Vue 3 - Basic Guide', 'Vue 4 - The Mystery' ]})// 计算属性,使用泛型,返回值必须是字符串const authorComputed = computed<string>(()=>{ return author.books.length >0?author.books.length.toString():"没有书"})function add(){ author.books.push("Vue 5 - Advanced Guide")}// 计算属性,使用泛型,返回值必须是字符串const myDateTimeComputed = computed<number>(() => { return getDateTime();});function getDateTime(){ console.log("方法被调用") return Date.now();}function myDateTimeMethod(){ return getDateTime();}// 定义一个用于触发重新渲染的状态const renderCounter = ref(0);function forceRerender() { console.log("12321") renderCounter.value++;}</script><template> <!-- 计算属性 --> <span>通过计算属性计算出来的结果:{{ authorComputed }}</span> <br> <button @click="add()">添加数据</button> <br> <!-- 计算属性和方法的区别 --> <button @click="forceRerender">触发重新渲染</button> 计算属性和方法的区别-计算属性:{{ myDateTimeComputed }} 计算属性和方法的区别-方法:{{renderCounter}}-{{ myDateTimeMethod() }}</template> 可写计算属性 计算属性默认是只读的。当你尝试修改一个计算属性时,你会收到一个运行时警告。只在某些特殊场景中你可能才需要用到“可写”的属性,你可以通过同时提供 getter 和 setter 来创建 1234567891011121314151617181920212223242526272829303132333435363738394041<script setup lang="ts">import {computed, reactive, ref} from "vue";const firstName = ref('Anthony')/** * 错误的写法 */// const fullName = computed(()=>{// return firstName.value + ' ' + lastName.value// })/** * 正确的写法 */const fullName = computed({ // getter get() { return firstName.value }, // setter set(newValue) { // 注意:我们这里使用的是解构赋值语法 firstName.value = newValue }})/** * 这样是不对的 */function showWarn(){ console.log("修改计算属性") fullName.value="DiDiDi"}</script><template> 演示修改计算属性,控制台报错:{{fullName}} <button @click="showWarn">尝试修改计算属性</button></template> Class 与 Style 绑定太麻烦了,不看了 条件渲染v-if 可以单独使用 v-else-if 要跟v-if 一起使用 v-else 要跟v-if 或者 v-else-if 一起使用 12345678910111213<script setup lang="ts">import {computed, reactive, ref} from "vue";const season = ref<number>(6);</script><template> <h1 v-if="season >=1 && season <=3">春季</h1> <h1 v-else-if="season >=4 && season <=6">不是春季</h1> <h1 v-else>数据错误</h1></template> 列表渲染123456789101112131415161718192021222324<script setup lang="ts">import {computed, reactive, ref} from "vue";const items = ref([{ message: 'Foo' }, { message: 'Bar' }])</script><template> for循环 <li v-for="(item,index) in items"> {{index}}-{{ item.message }} </li> 解构函数 <li v-for="{ message } in items"> {{ message }} </li> 解构函数,有索引的时候 <li v-for="({message},index) in items"> {{index}}-{{ message }} </li></template> 事件处理我们可以使用 v-on 指令 (简写为 @) 来监听 DOM 事件,并在事件触发时执行对应的 JavaScript。用法:v-on:click="handler" 或 @click="handler"。 内联事件处理器 12345678910<script setup lang="ts">import {computed, reactive, ref} from "vue";const count = ref(0)</script><template> <button @click="count++">Add 1</button> <p>Count is: {{ count }}</p></template> 方法事件处理器 1234567891011<script setup lang="ts">function greet() { console.log("方法事件处理器")}</script><template> <!-- `greet` 是上面定义过的方法名 --> <button @click="greet">Greet</button></template> 表单输入绑定主要是看框架了,先不学了,太多细节了 生命周期钩子这个API好多个,以后慢慢看 侦听器每次修改被监听的对象,就会打印旧值和新值 基本示例 12345678910111213141516<script setup lang="ts">import {ref, watch} from 'vue'/*被监听的对象*/const question = ref<number>(1);// 可以直接侦听一个 refwatch(question, async (newQuestion, oldQuestion) => { console.log("老值:", oldQuestion) console.log("新值:", newQuestion)})</script><template> <input v-model="question"/></template> 侦听不同的数据源类型 watch 的第一个参数可以是不同形式的“数据源”:它可以是一个 ref (包括计算属性)、一个响应式对象、一个 getter 函数、或多个数据源组成的数组: 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455<script setup lang="ts">import {reactive, ref, watch} from 'vue'const x = ref<number>(0)const y = ref<number>(0)type authorType={ name: string,}const z = reactive<authorType>({ name:"anthony"})// refwatch(x, (newX,oldValue) => { console.log(`监听单个ref,x的旧值是:${oldValue},新值是:${newX}`)})// getter 函数watch( () => x.value + y.value, (newSum, oldSum) => { console.log(`监听getter函数, 原来的和值是:${oldSum},新值是: ${newSum}`) })// 多个来源组成的数组watch([x, () => y.value], ([newX, newY],[oldX, oldY]) => { console.log(`监听多个数据源:X的旧值是:${oldX},X的新值是:${newX},Y的旧值是:${oldY},Y的新值是:${newY}`)})// 监听reactive// 错误,因为 watch() 得到的参数是一个 number// 错误的写法,会提示报错,// MyClick.vue:35 [Vue warn]: Invalid watch source: anthony A watch source can only be a getter/effect function, a ref, a reactive object, or an array of these types.// at <MyClick>// at <App>// watch(z.name, (count) => {// console.log(`name is: ${count}`)// })// 正确的写法watch( () => z.name, (nameNew,oldName) => { console.log(`name is: ${nameNew},${oldName}`) })</script><template> <input v-model="x" type="number"/> <input v-model="y" type="number"/> <input v-model="z.name" /></template> 模板引用进入到页面会自动聚焦到输入框中 123456789101112131415<script setup>import { ref, onMounted } from 'vue'// 声明一个 ref 来存放该元素的引用// 必须和模板里的 ref 同名const input = ref(null)onMounted(() => { input.value.focus()})</script><template> <input ref="input" /></template> 组件基础定义组件 123456789<script setup>import { ref } from 'vue'const count = ref(0)</script><template> <button @click="count++">You clicked me {{ count }} times.</button></template> 使用组件 在父组件中引入 1234567<script setup lang="ts">import MyClick from "@/components/MyClick.vue";</script><template> <MyClick/></template> 传递 props(父传子) 1234567891011121314151617181920212223// 子组件<script setup lang="ts">import {ref} from 'vue'const count = ref<number>(0)defineProps(['title'])</script><template> <button @click="count++">{{title}},You clicked me {{ count }} times.</button></template>// 父组件<script setup lang="ts">import MyClick from "@/components/MyClick.vue";</script><template> <MyClick title="计算器1"/> <MyClick title="计算器2"/> <MyClick title="计算器3"/> <MyClick title="计算器4"/></template> emit(子传父) 父组件 123456789101112131415161718192021222324<script setup lang="ts">import { ref } from 'vue'import MyEmit from "@/components/MyEmit.vue";type Person = { name: string, age: number}const myName = ref<string>('')const myAge = ref<number>('')const getSubmit = (data: Person) => { myName.value = data.name myAge.value = data.age}</script><template> <MyEmit @myEmit="getSubmit" /> 从子组件接收的数据:{{ myName }} {{ myAge }}</template> 子组件 123456789101112131415<script setup lang="ts">type Person = { name: string, age: number}const me = { name: 'anthony', age: 23}const emit = defineEmits<{ (e: 'myEmit', payload: Person): void }>();emit('myEmit', me)</script> $bus 消息总线main.js 123456789101112131415//引入Vueimport Vue from 'vue'//引入Appimport App from './App.vue'//关闭Vue的生产提示Vue.config.productionTip = false//创建vmnew Vue({ el:'#app', render: h => h(App), beforeCreate() { Vue.prototype.$bus = this //安装全局事件总线 },}) App.vue 1234567891011121314151617181920212223242526272829<template> <div class="app"> <h1>{{msg}}</h1> <School/> <Student/> </div></template><script> import Student from './components/Student' import School from './components/School' export default { name:'App', components:{School,Student}, data() { return { msg:'你好啊!', } } }</script><style scoped> .app{ background-color: gray; padding: 5px; }</style> Student.vue 12345678910111213141516171819202122232425262728293031323334<template> <div class="school"> <h2>学校名称:{{name}}</h2> <h2>学校地址:{{address}}</h2> </div></template><script> export default { name:'School', data() { return { name:'尚硅谷', address:'北京', } }, mounted() { // console.log('School',this) this.$bus.$on('hello',(data)=>{ console.log('我是School组件,收到了数据',data) }) }, beforeDestroy() { this.$bus.$off('hello') }, }</script><style scoped> .school{ background-color: skyblue; padding: 5px; }</style> School.vue 1234567891011121314151617181920212223242526272829303132<template> <div class="student"> <h2>学生姓名:{{name}}</h2> <h2>学生性别:{{sex}}</h2> <button @click="sendStudentName">把学生名给School组件</button> </div></template><script> export default { name:'Student', data() { return { name:'张三', sex:'男', } }, methods: { sendStudentName(){ this.$bus.$emit('hello',this.name) } }, }</script><style lang="less" scoped> .student{ background-color: pink; padding: 5px; margin-top: 30px; }</style> $set$nextTick深入组件插槽插槽内容与出口 主文件 1234567891011121314151617181920212223242526272829<script setup lang="ts">import { ref, provide, reactive } from 'vue'import First from "@/components/First.vue";import MySlotWithName from "@/components/MySlotWithName.vue";import MySlotWithOutName from "@/components/MySlotWithOutName.vue";</script><template> <h1>插槽演示</h1> <!-- 匿名插槽 --> <MySlotWithOutName> <a href="https://baidu.com">跳转到baidu</a> </MySlotWithOutName> <br> <MySlotWithName> <!-- 具名插槽 --> <!-- <template v-slot:url> --> <!-- 简写的方式 --> <template #url> <a href="https://google.com">跳转到谷歌</a> </template> </MySlotWithName></template> MySlotWithOutName.vue 1234<template> 匿名插槽 <slot /></template> 1234<template> 具名插槽 <slot name="url" /></template> 作用域插槽 12345678910111213<script setup lang="ts">import MySlotWithName from "@/components/MySlotWithName.vue";</script><template> <MySlotWithName> <template #url="data"> {{ data.title }}- {{ data.age }} <a href="https://google.com">跳转到谷歌</a> </template> </MySlotWithName></template> 1234<template> 具名插槽 <slot name="url" title="anthony" age="12" /></template> 依赖注入给所有的下级组件,包括直接子组件传递数据和方法 这个例子的最上层组件修改数据之后,可以修改所有组件显示的数据 这个例子的下层组件修改数据之后,也是可以修改所有组件显示的数据 最上层组件 12345678910111213141516171819202122232425262728293031323334353637<script setup lang="ts">import { ref, provide, reactive } from 'vue'import First from "@/components/First.vue";type Person = { name: string, age: number}const me: Person = reactive({ name: 'anthony', age: 23})provide("provideDemo", me)const update = () => { me.age = me.age + 1 console.log(me);}const updateTwo = () => { me.age = me.age + 2 console.log(me);}provide("provideUpdateTwo", updateTwo)</script><template> 最顶层组件,接收到的数据:{{ me.name }},{{ me.age }}<button @click="update">修改所有层数据+1</button> <br> <First /></template> 第一层组件 1234567891011121314151617<script setup lang="ts">import { inject } from 'vue'import Second from "./Second.vue";type Person = { name: string, age: number}const me = inject<Person>("provideDemo")</script><template> 这是第一层,接收到的数据:{{ me.name }},{{ me.age }} <br> <Second /></template> 第二层组件 12345678910111213141516<script setup lang="ts">import { inject } from 'vue'import Third from "./Third.vue";type Person = { name: string, age: number}const me = inject<Person>("provideDemo")</script><template> 这是第二层,接收到的数据:{{ me.name }},{{ me.age }} <br> <Third /></template> 第三层组件 123456789101112131415<script setup lang="ts">import { inject } from 'vue'type Person = { name: string, age: number}const me = inject<Person>("provideDemo")const provideUpdateTwo = inject("provideUpdateTwo")</script><template> 这是第三层,接收到的数据:{{ me.name }},{{ me.age }}<button @click="provideUpdateTwo">修改最顶层数据+2</button></template> yarnyarn的安装 1npm install -g yarn yarn常用命令 12345678910111213141516// 初始化yarn init // 添加包yarn add [package]yarn add [package]@[version]yarn add [package]@[tag]// 添加到不同依赖项yarn add [package] --devyarn add [package] --peeryarn add [package] --optional// 升级包yarn upgrade [package]// 移除依赖包yarn remove [package]// 安装所有依赖yarn 或 yarn install 报错1.node新版本引起的报错 1234this[kHandle] = new _Hash(algorithm, xofLen);^Error: error:0308010C:digital envelope routines::unsupported 解决方法1: 123456推荐:修改package.json,在相关构建命令之前加入SET NODE_OPTIONS=--openssl-legacy-provider"scripts": { "serve": "SET NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service serve", "build": "SET NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service build"}, 解决方法2: 降低node版本 前端Vue父组件点击按钮打开子组件弹窗案例父组件 12345678910111213141516171819202122232425<template> <div> # 需要定义ref=child <indexChild ref="child"></indexChild> <el-button @click="open">打开弹窗</el-button> </div></template><script>import indexChild from "../../components/indexChild.vue";export default { components: { indexChild }, data () { return { }; }, methods: { open () { this.$refs.child.open(); // 这样可以直接访问子组件方法,用ref拿子组件方法 } }}</script> 子组件 123456789101112131415161718192021222324252627<template> <div> <el-dialog title="收货地址" :visible.sync="dialogFormVisible"> <span>这是一段信息</span> <div slot="footer" class="dialog-footer"> <el-button @click="dialogFormVisible = false">取 消</el-button> <el-button type="primary" @click="dialogFormVisible = false">确 定</el-button> </div> </el-dialog> </div></template><script>export default { data () { return { dialogFormVisible: false, }; }, methods: { open () { // 在父组件调用打开 this.dialogFormVisible = true } }};</script> 参考:前端Vue父组件点击按钮打开子组件弹窗案例
Redis
安装Redis安装单机RedisWindows安装RedisDocker安装RedisLinux源码安装RedisReleases · tporadowski/redis12345678docker run \ -p 6379:6379 \ --name myredis \ -v $PWD/redis.conf:/etc/redis/redis.conf \ -v $PWD/data:/data \ -d redis:3.2 redis-server /etc/redis/redis.conf \ --restart=always \ --appendonly yes 命令说明: -name myredis : 指定容器名称,这个最好加上,不然在看docker进程的时候会很尴尬。 p 6699:6379 : 端口映射,默认redis启动的是6379,外部端口(6699)。 v $PWD/redis.conf:/etc/redis/redis.conf : 将主机中当前目录下的redis.conf配置文件映射。 v $PWD/data:/data -d redis:latest: 将主机中当前目录下的data挂载到容器的/data -redis-server --appendonly yes :在容器执行redis-server启动命令,并打开redis持久化配置 -restart=always:自动启动 注意事项: 如果不需要指定配置,v $PWD/redis.conf:/etc/redis/redis.conf 可以不用 redis-server 后面的那段 /etc/redis/redis.conf 也可以不用。 $PWD 在window系统下貌似不能用,可以用相对路径/ 客户端连接 1234# 先查询到myredis容器的ip地址。docker inspect myredis | grep IP# 连接到redis容器。然后就进入redis命令行了。docker run -it redis:latest redis-cli -h 192.168.42.321234567891011121314151617# Cetnos安装需要的软件yum -y install gcc gcc-c++ kernel-devel make# Ubunt 安装需要的软件sudo apt install gcc g++ make linux-kernel-headers kernel-package# ubuntu 新内核版本要用到的sudo apt install gcc g++ make linux-libc-dev# 下载rediswget http://download.redis.io/releases/redis-5.0.5.tar.gztar -zxvf redis-5.0.5.tar.gzcd redis-5.0.5# 安装redissudo make && make instal# 注意make的时候可能会报错: #include <jemalloc/jemalloc.h>,使用下面的命令make MALLOC=libc 修改redis.cnf 123456# 设置后台运行daemonize yes# 设置log文件路径logfile /var/log/redis/redis-server.log# 设置持久化文件存放路径dir /var/lib/redis 12345678910# 创建日志存放目录mkdir /var/log/redis/# 创建持久化文件存放目录mkdir /var/log/redis/# 创建存放配置的文件夹mkdir /etc/redis# 拷贝配置文件并改名cp redis.conf /etc/redis/6379.conf# 拷贝自启动脚本文件cp /usr/local/redis-6.0.3/utils/redis_init_script /etc/init.d/redisd 将启动脚本复制到/etc/init.d目录下,本例将启动脚本命名为redisd(通常都以d结尾表示是后台自启动服务) 1234567891011# Centos#设置为开机自启动服务器cd /etc/init.d/chkconfig redisd on# Ubuntu#设置服务脚本有执行权限sudo chmod +x /etc/init.d/redisd#注册服务cd /etc/init.d/sudo update-rc.d redisd defaults 此处直接配置开启自启动 chkconfig redisd on 将报错误: service does not support chkconfig 123#!/bin/sh# chkconfig: 2345 90 10# description: Redis is a persistent key-value database 通用命令 123456#启动Redis服务sudo service redisd start#关闭服务sudo service redisd stop#重启服务:sudo service redisd restart 安装集群RedisDocker安装集群源码安装集群12345678910111213141516171819202122232425262728293031323334353637383940#!/bin/bashecho "start install redis-cluster..."if [ ! -d /opt/docker-redis/7001/ ];then mkdir -p /opt/docker-redis/700{1,2,3,4,5,6}/data/ficd /opt/docker-redis/7001/wget http://download.redis.io/redis-stable/redis.conf -O /opt/docker-redis/7001/redis7001.conf#sed -i '/^#/d;/^$/d' redis7001.conf #取出空行和注释行sed -i 's/bind/#bind/g;s/appendonly no/appendonly yes/g;s/protected-mode yes/protected-mode no/g' redis7001.conf #开启持久化,注释监听ip#echo '#集群配置' >> /opt/docker-redis/7001/redis7001.confecho 'cluster-enabled yes' >>/opt/docker-redis/7001/redis7001.confecho 'cluster-config-file nodes-7001.conf' >>/opt/docker-redis/7001/redis7001.confecho 'cluster-node-timeout 15000' >>/opt/docker-redis/7001/redis7001.conf#echo 'cluster-announce-ip 192.168.92.135' >>/opt/docker-redis/7001/redis7001.confecho 'cluster-announce-port 7001' >>/opt/docker-redis/7001/redis7001.confecho 'cluster-announce-bus-port 17001' >>/opt/docker-redis/7001/redis7001.conf#for port in `seq 7002 7006`;do cp redis7001.conf ../${port}/redis${port}.conf echo "cluster-config-file nodes-${port}.conf" >>/opt/docker-redis/${port}/redis${port}.conf echo "cluster-announce-port ${port}" >>/opt/docker-redis/${port}/redis${port}.conf echo "cluster-announce-bus-port 1${port}" >>/opt/docker-redis/${port}/redis${port}.confdone#for port in `seq 7001 7006`;do# sed -i "s/logfile \"\"/logfile \"\/usr\/local\/docker\/redis-cluster\/log\/redis.log\"/g" redis${port}.conf sed -i "s/port 6379/port ${port}/g" /opt/docker-redis/${port}/redis${port}.conf docker run --restart always --name redis-cluster-${port} --net host --privileged=true -v /opt/docker-redis/${port}/redis${port}.conf:/usr/local/docker/redis-cluster/${port}/redis${port}.conf -v \ /opt/docker-redis/${port}/data/:/usr/local/docker/redis-cluster/data/ \ -d redis redis-server /usr/local/docker/redis-cluster/${port}/redis${port}.confdonedocker pssleep 2sss -tnulp|grep redis#创建集群#redis-cli --cluster create 192.168.92.135:7001 192.168.92.135:7002 192.168.92.135:7003 192.168.92.135:7004 192.168.92.135:7005 192.168.92.135:7006 --cluster-replicas 1下载,解压,编译安装 用一台虚拟机模拟6个节点,创建出3 master、3 salve 环境。 创建文件前,先编译好Redis程序 创建节点创建配置redis.conf 12345678910111213141516# 端口7000 7001 7002 7003 7004 7005port 7000# 自己建议修改为0.0.0.0bind 0.0.0.0# redis后台运行daemonize yes# pidfile文件对应7000 7001 7002 7003 7004 7005pidfile /var/run/redis_7000.pid# 开启集群 把注释#去掉cluster-enabled yes# 集群的配置,配置文件首次启动自动生成7000 7001 7002 7003 7004 7005cluster-config-file nodes_7000.conf# 请求超时 默认15秒,可自行设置cluster-node-timeout 15000# AOF日志开启appendonly yes 创建文件夹 12345678910cd /usr/localmkdir redis_clustercd redis_clustermkdir 7000 7001 7002 7003 7004 7005cp /usr/local/redis-5.0.5/redis.conf /usr/local/redis_cluster/7000/cp /usr/local/redis-5.0.5/redis.conf /usr/local/redis_cluster/7001/cp /usr/local/redis-5.0.5/redis.conf /usr/local/redis_cluster/7002/cp /usr/local/redis-5.0.5/redis.conf /usr/local/redis_cluster/7003/cp /usr/local/redis-5.0.5/redis.conf /usr/local/redis_cluster/7004/cp /usr/local/redis-5.0.5/redis.conf /usr/local/redis_cluster/7005/ 分别修改三个文件夹里的配置文件,修改如下内容 启动节点的redis/usr/local/bin/redis-server 123456/usr/local/bin/redis-server /usr/local/redis_cluster/7000/redis.conf/usr/local/bin/redis-server /usr/local/redis_cluster/7001/redis.conf/usr/local/bin/redis-server /usr/local/redis_cluster/7002/redis.conf/usr/local/bin/redis-server /usr/local/redis_cluster/7003/redis.conf/usr/local/bin/redis-server /usr/local/redis_cluster/7004/redis.conf/usr/local/bin/redis-server /usr/local/redis_cluster/7005/redis.conf 检查 redis 启动情况 1234567ps -ef | grep rediroot 61020 1 0 02:14 ? 00:00:01 redis-server 0.0.0.0:7000 [cluster]root 61024 1 0 02:14 ? 00:00:01 redis-server 0.0.0.0:7001 [cluster]root 61029 1 0 02:14 ? 00:00:01 redis-server 0.0.0.0:7002 [cluster]root 61029 1 0 02:14 ? 00:00:01 redis-server 0.0.0.0:7002 [cluster]root 61029 1 0 02:14 ? 00:00:01 redis-server 0.0.0.0:7002 [cluster]root 61029 1 0 02:14 ? 00:00:01 redis-server 0.0.0.0:7002 [cluster] 启动集群 装的redis是5.x的版本,这里没有应用到redis-trib.rb,所以就不需要装ruby 这里启动的时候可能会有报错,因为默认开启了protected-mode 12345cd /usr/local/binredis-cli --cluster create 192.168.0.100:7003 192.168.0.100:7004 192.168.0.100:7005 192.168.0.179:7000 192.168.0.179:7001 192.168.0.179:7002 --cluster-replicas 1Can I set the above configuration? (type 'yes' to accept): yesyes 校验,等运行完成 1234567891011121314151617181920212223242526272829[root@worker1 src]# redis-cli --cluster check 192.168.0.179:7000192.168.0.179:7000 (27bce53b...) -> 0 keys | 5462 slots | 1 slaves.192.168.0.100:7004 (6b0173d9...) -> 0 keys | 5461 slots | 1 slaves.192.168.0.100:7003 (9f15a932...) -> 0 keys | 5461 slots | 1 slaves.[OK] 0 keys in 3 masters.0.00 keys per slot on average.>>> Performing Cluster Check (using node 192.168.0.179:7000)M: 27bce53bda92341ca4a5c82c2361ab99f24c0b27 192.168.0.179:7000 slots:[5461-10922] (5462 slots) master 1 additional replica(s)S: c7ebcd900fb7d9afb1980797acba45518cb7d877 192.168.0.100:7005 slots: (0 slots) slave replicates 27bce53bda92341ca4a5c82c2361ab99f24c0b27S: ed5256f8db1bf556a8dadbe8f2b07699507e17d9 192.168.0.179:7001 slots: (0 slots) slave replicates 6b0173d925f70807a9081b7bc09bcd37be857342S: 758609eaea88bac25b864f2badbab2171a68089b 192.168.0.179:7002 slots: (0 slots) slave replicates 9f15a9329a9d0ec5c7fcb5abbba817730f0942f9M: 6b0173d925f70807a9081b7bc09bcd37be857342 192.168.0.100:7004 slots:[10923-16383] (5461 slots) master 1 additional replica(s)M: 9f15a9329a9d0ec5c7fcb5abbba817730f0942f9 192.168.0.100:7003 slots:[0-5460] (5461 slots) master 1 additional replica(s)[OK] All nodes agree about slots configuration.>>> Check for open slots...>>> Check slots coverage...[OK] All 16384 slots covered. Cluster配置 1234567891011# 设置加入cluster,成为其中的节点cluster-enabled yes|no# cluster配置文件名,该文件属于自动生成,仅用于快速查找文件并查询文件内容cluster-config-file < filename># 节点服务响应超时时间,用于判定该节点是否下线或切换为从节点cluster-node-timeout < milliseconds># master连接的slave最小数量cluster-migration-barrier < count> Cluster节点操作命令 1234567891011121314151617181920212223242526# 连接到集群,加一个-c就行redis-cli -c# 查看集群节点信息cluster nodes# 进入一个从节点redis,切换其主节点cluster replication <master-id># 发现一个新节点,新增主节点cluster meet ip:port# 忽略一个没有solt的节点cluster forget# 手动故障转移cluster failover# 计算键 key 应该被放置在哪个槽上cluster keyslot <key># 返回槽 slot 目前包含的键值对数量cluster countkeysinslot <slot># 返回 count 个 slot 槽中的键cluster getkeysinslot <slot> <count> 配置12345# requirepass foobared 注释去掉并写入要设置的密码 , 需要重启:systemctl restart redisrequirepass 123456# 将 bind 设置为允许所有 IP 地址的远程访问,需要重启:systemctl restart redisbind 0.0.0.0 Redis的数据类型redis自身是一个Map,其中所有的数据都是采用key:value的形式存储 数据类型指的是存储的数据的类型,也就是value部分的类型,key部分永远都是字符串 string –> String hash –> Hashmap list –> LinkList set –> HashSet sorted_set –> TreeSet String redis所有的操作都是原子性的,采用单线程处理所有业务,命令是一个一个执行的 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586# 设值# 格式 set <key> <value>192.168.245.129:0>set k1 anthony"OK"# 取值# 格式 get <key>192.168.245.129:0>get k1"anthony"# 删除值,成功返回1,不成功返回0192.168.245.129:0>del k1"1"192.168.245.129:0>del k1234"0"# 一次性存入多个值192.168.245.129:0>mset k1 v1 k2 v2 k3 v3"OK"# 一次性取出多个值192.168.245.129:0>mget k1 k2 k3 1) "v1" 2) "v2" 3) "v3"# 打印值的长度192.168.245.129:1>set name anthony"OK"192.168.245.129:1>get name"anthony"192.168.245.129:1>strlen name"7"# 追加# 有数据就追加192.168.245.129:1>append name 666"10"192.168.245.129:1>get name"anthony666"# 没数据就新建192.168.245.129:1>append othername frankie"7"192.168.245.129:1>get othername"frankie"# 递增递减,小数不行192.168.245.129:1>set num 1"OK"192.168.245.129:1>incr num"2"192.168.245.129:1>incr num"3"192.168.245.129:1>decr num"2"192.168.245.129:1>decr num"1"# 递增递减指定值,小数不行192.168.245.129:1>incrby num 4"6"192.168.245.129:1>decrby num 2"4"# 小数不行192.168.245.129:1>incrby num 1.5"ERR value is not an integer or out of range"# 递增指定小数,貌似没有递减192.168.245.129:1>incrbyfloat num 1.5"5.5"# 指定过期时间# EX 用于指定 key 的过期时间。EX(秒),PX(毫秒),KEEPTTL 保持原有的过期时间不变。# PX 毫秒-设置指定的到期时间(以毫秒为单位)。# NX 没有key的时候才能set成功,XX key存在的时候才能set成功SET key value [EX seconds|PX milliseconds|KEEPTTL] [NX|XX] [GET]# 指定过期时间# time,以秒为单位。SETEX key time value# 功能等价NX, SET if Not eXistsredis> SETNX mykey "Hello"(integer) 1redis> SETNX mykey "World"(integer) 0 Hash graph LR; Key; Key-->Field1--> Value1; Key-->Field2--> Value2; Key-->Field3--> Value3; subgraph 相当于Value Field1; Value1; Field2; Value2; Field3; Value3; end; 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748# 设值/修改值 hset key filed1 value192.168.245.129:0>HSET user name zhangsan"1"192.168.245.129:0>hset user age 38"1"# 取一个属性值192.168.245.129:0>hget user age"38"# 取多个属性值192.168.245.129:0>hmget user age name 1) "45" 2) "zhangsan"# 取一个key192.168.245.129:0>hgetall user 1) "name" 2) "zhangsan" 3) "age" 4) "38" # 删除一个属性值192.168.245.129:0>hdel user name"1"192.168.245.129:0>hgetall user 1) "age" 2) "38" # 查看有多少个属性 192.168.245.129:0>hlen user"3"# 获取所有的属性192.168.245.129:0>hkeys user 1) "age" 2) "name" 3) "sex"# 获取所有的属性值192.168.245.129:0>hvals user 1) "45" 2) "zhangsan" 3) "n" # 指定属性增加指定值 192.168.245.129:0>hincrby user age 1"46" List flowchart LR subgraph 顺序表/数组 id0(头指针) id1[华为] id2(苹果) id3(微软) end subgraph 单向链表 direction LR id00[头指针] --> id4[[华为]] id4[[华为]] --> id5[[苹果]] id5[[苹果]] --> id6[[微软]] end subgraph 双向链表 direction LR id000[头指针] <--> id7[[华为]] id7[[华为]] <--> id8[[苹果]] id8[[苹果]] <--> id9[[微软]] end 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758# 先插入huawei192.168.245.129:0>lpush list1 huawei"1"# 再插入apple192.168.245.129:0>lpush list1 apple"2"# 最后插入Microsoft192.168.245.129:0>lpush list1 microsoft"3"# 从左边取192.168.245.129:0>lrange list1 0 2 1) "microsoft" 2) "apple" 3) "huawei" # 一次性插入多条数据192.168.245.129:0>rpush list2 a b c"3"# 从左边取# lrange key start stop# lindex key index 取指定索引的值# llen key 取长度# 没有rrange192.168.245.129:0>lrange list2 0 2 1) "a" 2) "b" 3) "c" # 从左边取的第二钟方法192.168.245.129:0>lrange list2 0 -1 1) "a" 2) "b" 3) "c"# lpop从左边删,rpop从右边删192.168.245.129:0>lpush list3 a b c"3"192.168.245.129:0>lpop list3"c"----------------------------------------------------------------------------------------------------# 阻塞取值,从运行命令开始,如果有数据,取出来,立马返回,如果没有数据,就等指定的时间20s,有就立马返回结束,如果没有,就一直等到时间结束192.168.245.129:0>blpop list4 20 1) "list4" 2) "32"# 删除指定数据# lrem key count value# count是删除多少个value192.168.245.129:0>lpush dianzan a b c d"4"192.168.245.129:0>lrem dianzan 1 c"1"192.168.245.129:0>lrange dianzan 0 -1 1) "d" 2) "b" 3) "a" Set 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263# 添加192.168.245.129:0>sadd users zs"1"192.168.245.129:0>sadd users lisi"1"192.168.245.129:0>sadd users ww"1"# 查列表192.168.245.129:0>smembers users 1) "lisi" 2) "ww" 3) "zs" # 查数量192.168.245.129:0>scard users"3"# 判断是否有指定数据192.168.245.129:0>sismember users ls"0"192.168.245.129:0>sismember users ww"1"192.168.245.129:0>smembers users 1) "lisi" 2) "ww" 3) "zs" # 删除指定数据192.168.245.129:0>srem users ww"1"192.168.245.129:0>smembers users 1) "lisi" 2) "zs" # 随机获取集合中指定数量的数据,获取之后,原来的队列数据不变 srandmember key count # 随机获取集合中的某个数据并讲该数据移除集合 spop key -------------------------------------------------------------------------------------- > sadd u1 a11> sadd u1 a21> sadd u1 a31> sadd u2 a11> sadd u2 a21# 交集> sinter u1 u2a2a1# 并集> sunion u1 u2a1a2a3# 差集 (u1,u2)顺序不一样,结果不一样> sdiff u1 u2a3 redis应用于随机推荐类信息检索,例如热点歌单推荐,热点新闻推荐,热点旅游线路,应用APP推荐,大V推荐等 sort_set12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364# 添加数据> zadd scores 100 zhangsan1> zadd scores 90 lisi1> zadd scores 95 wangwu1# 获取全部数据> zrange scores 0 -1lisiwangwuzhangsan# 获取全部数据> zrange scores 0 -1 withscoreslisi90wangwu95zhangsan100# 反向获取全部数据> zrevrange scores 0 -1 withscoreszhangsan100wangwu95lisi90# 删除数据> zrem scores zhangsan1> zrange scores 0 -1 withscoreslisi90wangwu95# 按条件获取数据,可以用作分页zrangebyscore key min max [WITHSCORES] [LIMIT]zrevrangebyscore key max min [WITHSCORES]# 条件删除zremrangebyrank key start stopzremrangebyscore key min max-----------------------------------------排名-------------------------------------------------------# 添加模拟数据> zadd movies 143 aa 97 bb 201 cc3# 获取数据对应的索引(排名)> zrank movies bb0# 反向获取> zrevrank movies bb2# score 值获取与修改zscore key memberzincrby key increment member Redis 通用命令1234567891011121314151617181920212223# 删除del key# 获取key是否存在exists key# 获取key的类型type key# 设置指定有效期,秒expire key second# 设置指定有效期,毫秒pexpire key milliseconds# 有效期是时间戳expireat key timestamp# 获取key的有效期ttl keypttl key 毫秒# 集群环境下,模糊查询key# 要是查不出来数据,99999要换成一个很大的值SCAN 0 MATCH * count 99999 持久化很多时候我们需要持久化数据也就是将内存中的数据写入到硬盘里面,大部分原因是为了之后重用数据(比如重启机器、机器故障之后恢复数据),或者是为了防止系统故障而将数据备份到一个远程位置。 Redis不同于Memcached的很重一点就是,Redis支持持久化,而且支持两种不同的持久化操作。 Redis的一种持久化方式叫快照(snapshotting,RDB) 另一种方式是只追加文件(append-only file,AOF) 这两种方法各有千秋,下面我会详细这两种持久化方法是什么,怎么用,如何选择适合自己的持久化方法。 RDB(快照)save 会生成rdb文件 save指令相关配置 配置项 描述 dbfilename dump.rdb 指定本地数据库文件名,默认值为 dump.rdb dir ./ 指定本地数据库存放目录 注意:Redis是单线程的,所有命令都会在类似队列中排好队,不建议使用save指令,因为save指令的执行会阻塞当前Redis服务器,直到当前RDB过程完成位置,有可能会造成长时间阻塞,线上环境不建议使用 bgsave指令手动启动后台保存操作,但不是立即执行 执行成功了不会在控制台输出,可以在日志中看到 1234558142:M 07 Aug 2020 07:23:17.355 * Starting BGSAVE for SYNC with target: disk58142:M 07 Aug 2020 07:23:17.355 * Background saving started by pid 5818358183:C 07 Aug 2020 07:23:17.357 * DB saved on disk58183:C 07 Aug 2020 07:23:17.357 * RDB: 0 MB of memory used by copy-on-write58142:M 07 Aug 2020 07:23:17.456 * Background saving terminated with success bgsave命令是针对save阻塞问题做的优化。Redis内部所有涉及到RDB操作都采用bgsave的方式,save命令可以放弃使用 save配置1234567# second:监控时间范围# changes:监控key的变化量save second changessave 900 1 #在900秒(15分钟)之后,如果至少有1个key发生变化,Redis就会自动触发BGSAVE命令创建快照。save 300 10 #在300秒(5分钟)之后,如果至少有10个key发生变化,Redis就会自动触发BGSAVE命令创建快照。save 60 10000 #在60秒(1分钟)之后,如果至少有10000个key发生变化,Redis就会自动触发BGSAVE命令创建快照。 save配置原理 **注意:**save配置要根据实际业务情况进行设置,频度过高或过低都会出现性能问题,结果可能是灾难性的save配置中对second与changes设置通常具有互补对应关系,尽量不要设置成包含性关系save配置启动后执行的是bgsave操作 对比 RDB启动方式 全量复制在主从复制中会提到 服务器运行过程中重启debug reload 关闭服务器时指定保存数据shutdown save RDB 优缺点RDB优点 RDB是一个紧凑压缩的二进制文件,存储效率较高 RDB内部存储的是redis在某个时间点的数据快照,非常适合用于数据备份,全量复制等场景 RDB恢复数据的速度要比AOF快很多 应用:服务器中每X小时执行bgsave备份,并将RDB文件拷贝到远程己气中,用于灾难恢复 RDB缺点 RDB方式无论是执行指令还是利用配置,无法做到实时持久化,具体较大的可能性丢失数据 bgsave指令每次运行要执行fork操作创建子进程,要牺牲掉一些性能 Redis的众多版本中未进行RDB文件格式的版本统一,有可能出现个版本服务之间数据格式无法兼容现象 RDB存储的弊端 存储数据量较大,效率较低——基于快照思想,每次读写都是全部数据,当数据量巨大时,效率非常低 大数据量下的IO性能较低 基于fork创建子进程,内存产生额外消耗 宕机带来的数据丢失风险 解决思路 不写全数据,仅记录部分数据 改记录数据未记录操作过程 对所有操作均进行记录,排除丢失数据的风险 这也就是AOF的引入 AOFAOF写数据过程 AOF写数据的三种策略123appendfsync always #每次有数据修改发生时都会写入AOF文件,这样会严重降低Redis的速度appendfsync everysec #每秒钟同步一次,显示地将多个写命令同步到硬盘 **(默认的)**appendfsync no #让操作系统决定何时进行同步 AOF功能开启和相关配置1234567891011# 是否开启APF持久化功能,默认为不开启appendonly yes|no# AOF写数据策略appendfsync always|everysec|no# AOF持久化文件名,默认文件名为appendonly.aof,建议配置为appendonly-端口号.aofappendfilename filename# AOF持久化文件保存路径,与RDB持久化文件保持一致即可dir 重写随着命令的不断写入AOF,文件会越来越大,为了解决这个问题,Redis引入AOF重写机制压缩文件体积,AOF文件重写是将Redis进程内的数据转换为写命令同步到新AOF文件的过程,简单说就是将同样一个数据的若干个命令执行结果转换为最终结果数据对应的指令进行记录 AOF重写作用 降低磁盘占用量,提高磁盘利用路 提高持久化效率,降低持久化写时间,提高IO性能 降低数据恢复用时,提高数据恢复效率 AOF重写规则 进程内已超时的数据不再写入文件 忽略无效指令,重写时使用进程内数据直接生成,这样新的AOF文件只保留最终数据的写入命令 如del key1,hdel key2,srem key3,set key 222等 对统一数据的多条命令合并为一条命令如 lpush list1 a ,lpush list1 b,lpush list1 c可以转化为lpush list1 a b c为防止数据量过大造成客户端缓冲区溢出,对list,set,hash,set等类型,每条指令最多写入64个元素 重写方式123456# 手动重写,在命令行执行,会覆盖原来的aof文件,但是文件更小bgrewriteaof# 自动重写auto-aof-rewrite-min-size sizeauto-aof-rewrite-percentage percentage 手动重写流程: 自动重写的触发条件: 1234567# 自动重写触发条件设置auto-aof-rewrite-min-sizeauto-aof-rewrite-percentage percent# 自动重写触发对比参数(运行指令info Persistence获取具体信息)aof_current_sizeaof_base_size AOF和RDB的区别 RDB和AOF的选择之感 对数据非常敏感,建议使用默认的AOF持久化方案AOF持久化策略使用erverysecond,每秒钟fsync一次。该策略redis任然可以保持很好的处理性能,当出现问题时,最多丢失0-1秒中的数据。注意:由于AOF文件存储体积较大,且恢复数据较慢 数据呈现阶段有效性,建议使用RDB持久化方案数据可以良好的做到阶段内无丢失(该阶段是开发者或运维人工手工维护的),且恢复速度较快,阶段点数据恢复通常采用RDB方案注意:利用RDB实现紧凑的数据持久化会使Redis降得很低 综合对比 RDB与AOF得选择实际上是在做一种权衡,每种都有利弊 如不能承受数分钟以内得数据丢失,对业务数据非常敏感,选用AOF 如能承受数分钟以内数据丢失,且追求大数据集得恢复速度,选用RDB灾难恢复选用RDB 双保险策略,同时开启RDB和AOF,重启后,Redis优先使用AOF来恢复数据,降低丢失数据的量 Redis 4.0 对于持久化机制的优化Redis 4.0 开始支持 RDB 和 AOF 的混合持久化(默认关闭,可以通过配置项 aof-use-rdb-preamble 开启)。 如果把混合持久化打开,AOF 重写的时候就直接把 RDB 的内容写到 AOF 文件开头。这样做的好处是可以结合 RDB 和 AOF 的优点, 快速加载同时避免丢失过多的数据。当然缺点也是有的, AOF 里面的 RDB 部分是压缩格式不再是 AOF 格式,可读性较差。 事务12345678# 开启事务,设定事务的开启位置,此指令执行后,后续的所有指令均加入到事务中multi# 执行提交事务,加入事务的命令暂时到任务队列中,并没有立即执行,只有执行exec命令才开始执行exec# 取消事务,终止当前事务定义,发生在multi之后,exec之前discard 事务的工作流程 事务的注意事项 语法错误指命令书写格式有误 处理结果如果定义的事务中所包含的命令存在语法错误,整体事务中所有命令均不会被执行。包括那些语法正确的命令 运行错误指命令格式正确,但是无法正常的执行。例如对list进行incr操作 处理结果能够正确运行的命令会执行,运行错误的命令不会执行注意:已经执行完毕的命令对应的数据不会自动回滚,需要程序员自己在代码中实现回滚。 手动进行事务回滚(基本没法用) 记录操作过程中被影响的数据之前的状态单数据:string多数据:hash,list,set,zset 设置指令恢复所有的被修改的项单数据:直接set(注意周边属性,例如时效)多数据:修改对应值或整体克隆复制 锁12345# 对key添加监视锁,在执行exec前如果key发生了变化,终止事务执行watch key1 [key2…]# 取消对所有key的监视unwatch 分布式锁12345678# 加锁setnx lock-key value# 设置超时时间expire lock-key second# 删除锁dek lock-key 利用setnx命令的返回值特征,有值则返回设置失败,无值则返回设置成功 对于返回设置成功的,拥有控制权,进行下一步的具体业务操作 对于返回设置失败的,不具有控制权,排队或等待操作完毕通过del操作释放锁 删除策略过期数据删除策略 Redis是一种内存级数据库,所有数据均存放在内存中,内存中的数据可以通过TTL指令获取其状态 XX : 具有时效性的数据 1 : 永久有效的数据 2 : 已经国企的数据 或 被删除的数据 或 未定义的数据 时效性数据的存储结构 定时删除 创建一个定时器,当key设置过期时间,且过期时间到达时,由定时器任务立即执行对键的删除操作 优点:节约内存,到时就删除,快速释放掉不必要的内存占用 缺点:CPU压力很大,无论CPU此时负载多高,均占用CPU,会影响redis服务器响应时间和指令吞吐量 总结:用处理器性能换取存储空间 惰性删除 数据到达过期时间,不做处理。等下次访问该数据 如果未过期,返回数据 发现已经过期,删除,返回不存在 优点:节约CPU性能,发现必须删除的时候才删除 缺点:内存压力很大,出现长期占用内存的数据 总结:用存储空间换取处理器性能 定期删除 Redis启动服务器初始化时,读取配置server.hz的值,默认为10 每秒钟执行server.hz次serverCron() 周期性轮询redis库中时效性数据,采用随机抽取的策略,利用过期数据占比的方式删除频度 特点1:CPU性能占用设置有峰值,检测频度可自定义设置 特点2:内存压力不是很大,长期占用内存的冷数据会被持续清理 总结:周期性抽查存储空间 逐出算法 Redis使用内存存储数据,在执行每一个命令前,会调用freeMemorylfNeeded()检测内存是否充足。如果内存不满足新加入数据的最低存储要求,redis要临时删除一些数据为当前指令清理存储空间。清理数据的策略称为逐出算法。 注意:逐出数据的过程不是100%能够清理出足够的可使用的内存空间,如果不成功则反复执行。当对所有数据尝试完毕后,如果不能达到内存清理的要求,将出现错误信息 相关配置 12345678# 最大可使用内存maxmemory# 每次选取代删除数据的个数maxmemory-samples# 删除策略maxmemory-policy 属性 说明 volatile-lru 从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰 volatile-ttl 从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰 volatile-random 从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰 volatile-lfu 从已设置过期时间的数据集(server.db[i].expires)中挑选最不经常使用的数据淘汰 allkeys-lru 当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key (这个是最常用的) allkeys-random 从数据集(server.db[i].dict)中任意选择数据淘汰 allkeys-lfu 当内存不足以容纳新写入数据时,在键空间中,移除最不经常使用的key no-eviction 禁止驱逐数据,也就是说当内存不足以容纳新写入数据时,新写入操作会报错。这个应该没人使用吧! 数据逐出策略配置依据 使用INFO命令输出监控信息,查询缓存int和miss的次数,根据业务需求调优Redis配置 配置文件1234567891011121314151617181920212223242526272829# 设置服务器以守护进程的方式运行deamonize yes|no# 绑定主机地址bind 127.0.0.1# 设置服务器端口号port 6379# 设置数据库数量databases 16----------------------------------日志配置--------------------------------------# 设置服务器以指定日志记录级别loglevel debug|verbose|notice|warning# 日志记录文件名logfile 端口号.log# 持久化文件存放目录dir ./----------------------------------日志配置--------------------------------------# 设置同一时间最大客户链接数,默认无限制。当客户端连接到达上线,Redis会关闭新的链接maxclients 0# 客户端限制等待最大时常,达到最大之后关闭连接。如需关闭该功能,设置为0timeout 300 Redis-Cluster结构设计数据存储设计Key -> CRC16(Key) 相当于HashCode值—>%16384 将所有的存储空间计划切割成16384份,每台主机保存一部分,每份代表的使一个存储空间,不是一个key的保存空间 将key按照计算出的结果放到对应的存储空间 当有新的机器加入集群的时候,就会每台机器转移一些数据空间 集群内部通讯设计 集群常用命令参考:https://www.cnblogs.com/kevingrace/p/7910692.html 以下命令是Redis Cluster集群所独有的,执行下面命令需要先登录redis 123# (客户端命令:redis-cli -c -p port -h ip)[root@manage redis]# redis-cli -c -p 6382 -h 192.168.10.12192.168.10.12:6382> 集群cluster info :打印集群的信息cluster nodes :列出集群当前已知的所有节点( node),以及这些节点的相关信息。 节点cluster meet <ip> <port> :将 ip 和 port 所指定的节点添加到集群当中,让它成为集群的一份子。cluster forget <node_id>:从集群中移除 node_id 指定的节点。cluster replicate <master_node_id> :将当前从节点设置为 node_id 指定的master节点的slave节点。只能针对slave节点操作。cluster saveconfig :将节点的配置文件保存到硬盘里面。 slotcluster addslots <slot> [slot ...] :将一个或多个槽( slot)指派( assign)给当前节点。cluster delslots <slot> [slot ...] :移除一个或多个槽对当前节点的指派。cluster flushslots :移除指派给当前节点的所有槽,让当前节点变成一个没有指派任何槽的节点。cluster setslot <slot> node <node_id>:将槽 slot 指派给 node_id 指定的节点,如果槽已经指派给 另一个节点,那么先让另一个节点删除该槽>,然后再进行指派cluster setslot <slot> migrating <node_id>:将本节点的槽 slot 迁移到 node_id 指定的节点中。cluster setslot <slot> importing <node_id> :从 node_id 指定的节点中导入槽 slot 到本节点。cluster setslot <slot> stable:取消对槽 slot 的导入( import)或者迁移( migrate)。 键cluster keyslot <key>:计算键 key 应该被放置在哪个槽上。cluster countkeysinslot <slot>:返回槽 slot 目前包含的键值对数量。cluster getkeysinslot <slot> <count>:返回 count 个 slot 槽中的键 。 解决方案缓存预热在高请求之前,做好一系列措施,保证大量用户数量点击造成灾难。 请求数量较高 主从之间数据吞吐量较大,数据同步操作频度较高 缓存预热解决方案前置准备工作: 日常例行统计数据访问记录,统计访问频度较高的热点数据 利用LRU数据删除策略,构建数据留存队列例如:storm与kafka配合 准备工作: 将统计结果中的数据分类,根据级别,redis优先加载级别较高的热点数据 利用分布式多服务器同时进行数据读取,提速数据加载过程 实施: 使用脚本程序固定出大数据预热过程 如果条件允许,使用CDN(内容分发网络),效果会更好 缓存预热总结:缓存预热就是系统启动前,提前将相关的缓存数据直接加载到缓存系统。避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据! 缓存雪崩 系统平稳运行过程中,忽然数据库连接量激增 应用服务器无法及时请求 大量408,500错误页面出现 客户反复刷新页面获取数据 数据库崩溃 应用服务器崩溃 重启应用服务器无效 Redis服务器崩溃 Redis集群崩溃 重启数据之后再次被瞬间流量放倒 问题排查简介:缓存同一时间大面积的失效,所以,后面的请求都会落到数据库上,造成数据库短时间内承受大量请求而崩掉 在一个较短的时间内,缓存中较多的key集中过期 此周期内请求访问过期的数据,redis未命中,redis向数据库获取数据 数据库同时接受到大量的请求无法即时处理 Redis大量请求被积压,开始出现超时现象 数据库流量激增,数据库崩溃 重启后任然面对缓存中无数据可用 Redis服务器资源被严重占用,Redis服务器崩溃 Redis集群呈现崩塌,集群瓦解 应用服务器无法即时得到数据响应请求,来自客户端的请求数量越来越多,应用服务器崩溃 应用服务器,redis,数据库全部重启,效果不理想 问题分析 短时间范围内 大量key集中过期 解决方案(道) 更多的页面静态化处理 构建多级缓存架构Nginx缓存+redis缓存+ehcache缓存 检测Mysql严重耗时业务进行优化对数据库的瓶颈排查:例如超时查询、耗时较高事务等 灾难预警机制监控redis服务器性能指标1、CPU占用、CPU使用率2、内存容量3、查询平均响应时间4、线程数 限流、降级短时间范围内习生一些客户体验,限制一部分请求访问,降低应用服务器压力,待业务低速运转后再逐渐放开访问 解决方案(术) LRU与LFU切换 数据有效期策略调整根据业务数据有效期进行分类错峰,A类90分钟,B类80分钟,C类70分钟过期时间使用固定形式+随机值的形式,稀释集中到期的key的数量 超热数据使用永久key 定期维护(自动+人工)对即将过期数据做访问量分析,确认是否演示,配合访问量统计,做热点数据的延时 加锁 慎用!!!!!!! 总结缓存雪崩式瞬间过期数量太大,导致对数据库服务器造成压力。如果能有效避免过期时间集中,可以有效解决雪崩现象的出现(约40%)。配合其他策略一起使用,并监控服务器的运行数据,根据运行巨鹿做快速调整 缓存击穿数据库服务器崩溃(2) 系统平稳运行过程中 数据库连接量瞬间激增 Redis服务器无大量key过期 Redis内存平稳,无波动 Redis服务器CPU正常 数据库崩溃 问题排查 Redis中某个key过期,该key访问量巨大 多个数据请求从服务器直接压到Redis后,均为命中 Redis在短时间内发起了大量对数据库中同一个数据的访问 问题分析 单个key高热数据 key过期 解决方案(术) 预先设定以电商为例,每个商家根据店铺等级,指定若干款主打商品,在购物节期间,加大此类信息key的过期时常注意:购物节不仅仅指当天,以及后续若干天,访问峰值呈现逐渐降低趋势 现场调整监控访问量,对自然流量激增的数据延长过期时间或设置为永久性key 后台刷新数据启动定时任务,高峰期来临之前,刷新数据有效期,保存不丢失 二级缓存设置不同的失效时间,保障不会被同时淘汰就行 加锁分布式锁,防止被击穿,但是要注意也是性能瓶颈,慎重!!!!!!!! 总结:缓存击穿就是单个高热数据过期的瞬间,数据访问较大,未命中redis后,发起了大量对同一数据的数据库访问,导致对数据库服务器造成压力。应对策略应该在业务数据分析与预防方面进行,配合运行监控测试与即时调整策略,毕竟单个key的过期监控难度较高,配合雪崩处理策略即可 缓存穿透数据库服务器崩溃(3)简介:一般是黑客故意去请求缓存中不存在的数据,导致所有的请求都落到数据库上,造成数据库短时间内承受大量请求而崩掉。 系统平稳运行过程中 应用服务器流量随时间增量较大 Redis服务器命中率随时间逐步降低 Redis内存平稳,内存无压力 Redis服务器CPU占用激增 数据库服务器压力激增 数据库崩溃 问题排查 redis中大面积出现未命中 出现非正常URL访问 问题分析 获取的数据在数据库中也不存在,数据库查询未得到对应数据 Redis获取到null数据未进行持久化,直接返回 下次此类数据到达重复上述过程 出现黑客攻击服务器 解决方法(术) 缓存null对查询结果为null的数据进行缓存(长期使用,定期清理),设定短时限,例如30-60秒,最高五分钟 白名单策略提前预热各种分类数据id对应的bitmaps,id作为bitmaps的offset,相当于设置了数据白名单。当加载正常数据后放型,加载异常数据时直接拦截(效率偏低)使用布隆过滤器(有关布隆过滤器的命中问题对当前状态可以忽略) 实时监控试试监控redis命中率(业务正常范围时,通常回有一个波动值)与null数据的占比非活动时间波动:通常检测3-5倍,超过5倍纳入重点排查对象活动时间波动:通常检测10-50倍,超过50倍纳入重点排查对象根据背书不同,启动不同的排查流程。然后使用黑名单进行防控(运营) key加密问题出现后,临时启动防灾业务key,对key进行业务层传输加密服务,设定校验程序,过来的key校验例如每天随机分配60个加密串,挑选2-3个,混淆到页面数据id中,发现访问key不满足规则,驳回数据访问 总结缓存穿透是访问了不存在的数据,跳过了合法数据的redis数据缓存阶段,每次访问数据库,导致对数据库服务器造成压力。通常此类数据的出现量是一个较低的值,当出现此类情况以毒攻毒,并即时报警。应对策略应该在临时预案防范方面多做文章无论是黑名单还是白名单,都是对整体系统的压力,警报解除后尽快移除 Redis-Java单机Jedis123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149/** * 192.168.0.5 单机 */public class SingletonDemo { private Jedis jedis; @Before public void before() { //指定Redis服务Host和port jedis = new Jedis("192.168.0.5", 6379); } @After public void after() { //使用完关闭连接 jedis.close(); } /** * 设值 */ @Test public void set() { //访问Redis服务 jedis.set("k1", "v1"); System.out.println(jedis.get("k1")); } /** * 设值,并且设置超时时间 */ @Test public void setTime() { String key = "setTimeKey"; jedis.setex(key, 20000, "setTimeValue"); System.out.println(jedis.get(key)); System.out.println(jedis.ttl(key)); } /** * 分布式锁原理, * key不存在的时候才插入 */ @Test public void setNX() { String key = "setNXKey"; // 1.先插入一个值 jedis.set(key, "setNXValue"); // 2.NX是不存在时才set, XX是存在时才set, EX是秒,PX是毫秒 String set = jedis.set(key, "setNXValue---NX", "NX", "EX", 20000L); System.out.println(set); System.out.println(jedis.get(key)); System.out.println(jedis.ttl(key)); System.out.println("======================================================"); // 3.设置个没有的key String key2 = "setNXKey2"; String set2 = jedis.set(key2, "setNXValue2---NX", "NX", "EX", 20000L); System.out.println(set2); System.out.println(jedis.get(key2)); System.out.println(jedis.ttl(key2)); } /** * key存在时才插入 */ @Test public void setXX() { String key = "setXXKey"; // 1.先插入一个值 jedis.set(key, "setXXValue"); // 2.NN String set = jedis.set(key, "setXXValue---XX", "XX", "EX", 20000L); System.out.println(set); System.out.println(jedis.get(key)); System.out.println(jedis.ttl(key)); System.out.println("======================================================"); // 3.设置个没有的key String key2 = "setXXKey2"; String set2 = jedis.set(key2, "setXXValue2---NX", "XX", "EX", 20000L); System.out.println(set2); System.out.println(jedis.get(key2)); System.out.println(jedis.ttl(key2)); } @Test public void expire() { } /** * redis监控信息 */ @Test public void info() { String info = jedis.info(); Stream.of(info.split("\r\n")).forEach(row -> { String[] split = row.split(":"); if (split.length == 2) { System.out.printf("key:%s ==== value:%s \r\n",split[0],split[1]); } } ); } /** * jedispool */ @Test public void jedisPool() throws InterruptedException { StopWatch watch = new StopWatch(); watch.start(); for (int i = 0; i < 100000; i++) { jedis.set(i + "", i + ""); jedis.close(); } watch.stop(); System.out.println("5-"+watch.getTime()); } @Test public void jedisPool2() { // 替换成你的reids地址和端口 String redisIp = "192.168.0.5"; int reidsPort = 6379; JedisPool jedisPool = new JedisPool(new JedisPoolConfig(), redisIp, reidsPort); Jedis resource = jedisPool.getResource(); StopWatch watch = new StopWatch(); watch.start(); for (int i = 0; i < 100000; i++) { resource.set(i + "", i + ""); } watch.stop(); System.out.println("5-"+watch.getTime()); }} 单机Jedis连接集群节点会报错 12345678redis.clients.jedis.exceptions.JedisMovedDataException: MOVED 12706 192.168.0.8:6379 at redis.clients.jedis.Protocol.processError(Protocol.java:115) at redis.clients.jedis.Protocol.process(Protocol.java:161) at redis.clients.jedis.Protocol.read(Protocol.java:215) at redis.clients.jedis.Connection.readProtocolWithCheckingBroken(Connection.java:340) at redis.clients.jedis.Connection.getStatusCodeReply(Connection.java:239) at redis.clients.jedis.Jedis.set(Jedis.java:121) at reids.ClusterDemo.set(ClusterDemo.java:68) 集群Jedis1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677/** * anthony@base:/var/log/redis$ redis-cli --cluster check 192.168.0.6:6379 * 192.168.0.6:6379 (99af86e5...) -> 0 keys | 5461 slots | 1 slaves. * 192.168.0.8:6379 (982f74c7...) -> 3 keys | 5461 slots | 1 slaves. * 192.168.0.7:6379 (1069a632...) -> 0 keys | 5462 slots | 1 slaves. * [OK] 3 keys in 3 masters. * 0.00 keys per slot on average. * >>> Performing Cluster Check (using node 192.168.0.6:6379) * M: 99af86e59c7f5a4ddf212b29e9e1b12fa5e7a866 192.168.0.6:6379 * slots:[0-5460] (5461 slots) master * 1 additional replica(s) * S: 102517bddae6af2434519e8f348cf062b0152fa7 192.168.0.9:6379 * slots: (0 slots) slave * replicates 982f74c79ed5c5811b7011507c56f81374c70a0b * S: 392c5e4dcd5608c74a76902668ed58fb6ab50aaf 192.168.0.11:6379 * slots: (0 slots) slave * replicates 1069a6320a0489f63e807c970c27f86f13f417cf * S: 4d12dd0de1af9d90bfac7dd141c36c348071d59f 192.168.0.10:6379 * slots: (0 slots) slave * replicates 99af86e59c7f5a4ddf212b29e9e1b12fa5e7a866 * M: 982f74c79ed5c5811b7011507c56f81374c70a0b 192.168.0.8:6379 * slots:[10923-16383] (5461 slots) master * 1 additional replica(s) * M: 1069a6320a0489f63e807c970c27f86f13f417cf 192.168.0.7:6379 * slots:[5461-10922] (5462 slots) master * 1 additional replica(s) * [OK] All nodes agree about slots configuration. * >>> Check for open slots... * >>> Check slots coverage... * [OK] All 16384 slots covered. */public class ClusterDemo { private JedisCluster jedis; @Before public void before() { Set<HostAndPort> nodes = new HashSet<>(); nodes.add(new HostAndPort("192.168.0.6", 6379)); nodes.add(new HostAndPort("192.168.0.7", 6379)); nodes.add(new HostAndPort("192.168.0.8", 6379)); nodes.add(new HostAndPort("192.168.0.9", 6379)); nodes.add(new HostAndPort("192.168.0.10", 6379)); nodes.add(new HostAndPort("192.168.0.11", 6379)); GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig(); jedis = new JedisCluster(nodes,poolConfig); } @After public void after() throws IOException { //使用完关闭连接 jedis.close(); } /** * 设值 */ @Test public void set() { //访问Redis服务 jedis.set("k1", "v1"); System.out.println(jedis.get("k1")); } /** * redis集群的节点信息 * map.key = IP:PORT */ @Test public void info() { Map<String, JedisPool> clusterNodes = jedis.getClusterNodes(); clusterNodes.keySet().forEach(key->{ System.out.printf("%s==%s\r\n",key,clusterNodes.get(key).getResource().info()); }); }} RedisTemplate 123456789101112# 插入myVO.setCode(typeCode);redisTemplate.opsForList().rightPush(key, JSONUtil.toJsonStr(myVO));# 取出List<String> range = redisTemplate.opsForList().range(key, 0, 100);# 删除取出的# trim 是保留的意思if(range.size() >0){ redisTemplate.opsForList().trim(key, range.size() + 1, -1);}
