当前位置:   article > 正文

抓娃小兵的Java开发记录_13300003333

13300003333

前言

以下记录的是小兵这几年工作中常用到的一些技术,仅为小兵一家之言。但能保证记录的内容都是小兵实践过的干货,有需要者可放心食用。之后也会持续对本文进行更新,建议收藏!

记录-编写JAR

无依赖时:

  1. #1、写主类,并编译为.class
  2. cat > Hello.java <<EOF
  3. public class Hello {
  4. public static void main(String[] args) {
  5. System.out.println("hello world");
  6. if (args != null) {
  7. for (String arg : args) {
  8. System.out.println("param: " + arg);
  9. }
  10. }
  11. }
  12. }
  13. EOF
  14. javac Hello.java
  15. #2、配置jar
  16. cat > jar.conf <<EOF
  17. Manifest-Version: 1.0
  18. Main-Class: Hello
  19. EOF
  20. #3、打成jar(将需要打包的东西都移动到 jardir)
  21. mkdir jardir && mv jar.conf Hello.class ./jardir
  22. jar -cvfm myJar.jar jardir/jar.conf -C jardir .
  23. #4、运行jar
  24. java -jar myJar.jar
  25. java -jar myJar.jar k1=v1 k2=v2

有依赖时:

  1. #1、写主类,并编译为.class
  2. cat > Demo.java <<EOF
  3. import redis.clients.jedis.Jedis;
  4. public class Demo {
  5. public static void main(String[] args) throws Exception {
  6. String host = System.getProperty("host", "localhost");
  7. String port = System.getProperty("port", "6379");
  8. String cmd = System.getProperty("cmd", "set");
  9. String key = System.getProperty("key", "hello");
  10. String value = System.getProperty("value", "world");
  11. Jedis jedis = new Jedis("localhost");
  12. System.out.println( host + ":" + port + " connect success!");
  13. if (cmd.equals("set")) {
  14. jedis.set(key, value);
  15. System.out.println("set " + key + " = " + value);
  16. } else if (cmd.equals("get")) {
  17. System.out.println("get " + key + " = " + jedis.get(key));
  18. } else {
  19. System.out.println(cmd + ", no this operation!");
  20. }
  21. jedis.close();
  22. }
  23. }
  24. EOF
  25. #编译前需要下载相关依赖
  26. mkdir libs && curl -sO https://static.runoob.com/download/jedis-2.9.0.jar && mv jedis-2.9.0.jar ./libs
  27. javac -classpath libs/*.jar Demo.java
  28. #2、配置jar
  29. cat > jar.conf <<EOF
  30. Manifest-Version: 1.0
  31. Class-Path: libs/`ls libs|xargs|sed 's/ /\n libs\//g'`
  32. Main-Class: Demo
  33. EOF
  34. #3、打成jar
  35. mkdir jardir && mv jar.conf Demo.class ./jardir
  36. jar -cvfm myJar.jar jardir/jar.conf -C jardir .
  37. #4、运行jar(注意,此时依赖仍在libs中,移植时需要把依赖也同时移走,确保myJar.jar与libs处于同一目录,使用eclipse/idea打的运行时jar包是做了一层封装才没体现出依赖)
  38. java -jar myJar.jar
  39. java -Dcmd=get -jar myJar.jar
  40. java -Dkey=testKey -Dvalue=testValue -jar myJar.jar
  41. java -Dcmd=get -Dkey=testKey -jar myJar.jar

指定 jar 运行参数的几种方式:

0)指定堆大小(默认是1G)

java -jar -Xms256m -Xmx256m demo.jar

1)通过main方法中的args获取

java -jar demo.jar k1=v1 k2=v2

2)通过System.getProperty获取

java -Dcmd=get -jar demo.jar

3)通过@value("${server.port}") 或指定默认值 @value("${server.port:8080}")获取

java -jar demo.jar --server.port=9090

记录-Docker 

相关视频教程:

尚硅谷Docker实战教程(docker教程天花板)_哔哩哔哩_bilibili

Kubernetes(K8S) 入门进阶实战完整教程,黑马程序员K8S全套教程(基础+高级)_哔哩哔哩_bilibili

docker安装要求在centos7.0以上(cat centos-release),内核版本在3.10以上(uname -r)

安装docker后,我们可以通过下载对应镜像(images),快速启动相应容器(container)。就好比下载了一个父类之后,快速地new一个实例出来,且这个new出来的实例功能直接就可以对外提供服务(如本文记录的mysql服务、redis服务、es服务、甚至jar/war服务等),一般我们可以通过docker来快速搭建我们的开发测试环境。容器多了之后,就可以使用docker-compose或者k8s来管理了。

安装docker

  1. #1. 删除docker
  2. yum -y remove docker-ce docker-ce-cli containerd.io
  3. rm -rf /var/lib/docker
  4. #2. 安装需要的安装包
  5. yum -y install gcc gcc-c++ yum-utils
  6. #3. 设置镜像的仓库(#默认国外,推荐使用阿里云)
  7. yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo 
  8. #4.更新yum软件包索引
  9. makecache fast
  10. #5. 安装最新版或指定版docker
  11. yum -y install docker-ce-20.10.10 docker-ce-cli-20.10.10 containerd.io
  12. #6. 启动docker、开机自启
  13. systemctl start docker
  14. systemctl enable docker
  15. #7.使用docker version查看是否安装成功
  16. docker --version
  17. #8.测试hello world
  18. docker run hello-world
  19. #9.查看hello world镜像是否正常
  20. docker images

docker常用命令

  1. #查看已有镜像
  2. docker images
  3. #查看当前运行的容器
  4. docker ps
  5. #查看所有的容器
  6. docker ps -a
  7. #进入容器
  8. docker exec -it container-id/name /bin/bash
  9. #删除容器
  10. docker rm -f container-id/name
  11. #运行容器(镜像不存在时会自动下载)
  12. docker run --name container-name images:tag (多数会加 -d -p -v等参数)
  13. #启动容器
  14. docker start container-id/name
  15. #停止容器
  16. docker stop container-id/name
  17. #重启容器
  18. docker restart container-id/name
  19. #查看日志:
  20. docker logs container-id/name
  21. #查看docker情况
  22. docker system df
  23. docker stats
  24. docker top
  25. #更新容器
  26. docker container update --restart=always 容器名字

更多docker内容

记录-MySQL

相关视频教程:

黑马程序员 MySQL数据库入门到精通,从mysql安装到mysql高级、mysql优化全囊括_哔哩哔哩_bilibili

现在mysql最常用的版本应该就是5.6、5.7、8.0了。mysql5.7之后的一个版本就是mysql8.0,之所以版本号跨度这么大是因为这5.7之后的版本区别确实很大,很多地方不一样了。其具体改动本文不涉及,读者可自行查阅,对于大部分开发者来说,5.7版本确实已经足矣。

安装mysql

  1. 一.卸载原有mysql
  2. 1.1检查自己的liunx是否安装过mysql,如果有的话,就删除(XXXX是自己的mysql目录)
  3. rpm -qa | grep mysql
  4. rpm -e --nodeps mysql-xxxx
  5. 1.2查询所有的mysql对应的文件夹
  6. whereis mysql
  7. find / -name mysql
  8. #有的话删除相关目录或者文件
  9. rm -rf /usr/bin/mysql /usr/include/mysql /data/mysql /data/mysql/mysql
  10. #验证一下是否删除干净
  11. whereis mysql
  12. find / -name mysql
  13. 二、下载解压
  14. cd /tmp
  15. wget https://dev.mysql.com/get/Downloads/MySQL-5.7/mysql-5.7.35-linux-glibc2.12-x86_64.tar.gz
  16. tar -zxvf mysql-5.7.35-linux-glibc2.12-x86_64.tar.gz
  17. mv mysql-5.7.35-linux-glibc2.12-x86_64 /usr/local/mysql
  18. 三、修改data,并编译和初始化,新建my.cnf
  19. mkdir /usr/local/mysql/data
  20. cd /usr/local/mysql
  21. ./bin/mysqld --initialize --datadir=/usr/local/mysql/data --basedir=/usr/local/mysql
  22. #以上过程出现的临时密码需要记录下来,后续要用
  23. #新建/my.cnf配置
  24. cat > /usr/local/mysql/my.cnf <<EOF
  25. [mysqld]
  26. datadir=/usr/local/mysql/data
  27. port = 3306
  28. sql_mode=NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES
  29. max_connections=400
  30. character_set_server=utf8mb4
  31. EOF
  32. 四、启动mysql服务
  33. /usr/local/mysql/support-files/mysql.server start
  34. 登录mysql ,并修改你的初始密码!(密码为上面生成的临时密码)
  35. mysql -u root -p
  36. 复制下面的命令修改密码:(如123456)
  37. set password for root@localhost = password('123456');
  38. 授权
  39. use mysql;
  40. update user set user.Host='%' where user.User='root';
  41. flush privileges;

mysql多实例安装脚本(deploy_mysql.sh)参考以下:

  1. #!/bin/bash
  2. #初始化约定:
  3. #安装版本:5.7.35
  4. #使用端口:3306
  5. #安装目录(basedir):/usr/local/mysql5.7.35
  6. #数据目录(datadir):/data/mysql$port,即默认为 /data/mysql3306
  7. #配置文件:存于对应数据目录下conf/my.cnf,即默认为 /data/mysql3306/conf/my.cnf
  8. #
  9. #输入参数:
  10. #$1:端口号:如不输入,则使用3306端口。
  11. #如:sh mysql_deploy.sh 或 sh mysql_deploy.sh 3307
  12. #
  13. #输出:
  14. #success:active (running)
  15. #fail:部署失败原因
  16. #步骤1:根据是否存在basedir来判断是否第一次安装。如果是第一次安装则下载安装mysql$version。
  17. #步骤2:根据输入端口判断对应的datadir是否存在,存在则不允许安装,端口被占用也也不允许安装
  18. #步骤3:生成配置文件,开始安装并通过systemd启动。(root初始化密码123456)
  19. #步骤4:通过systemd管理mysql。即 systemctl start|stop|restart|enable|disable mysql${port}
  20. #卸载mysql
  21. #卸载某实例
  22. #systemctl stop mysql${port}
  23. #rm -rf $mysql_datadir /etc/systemd/system/mysql${port}
  24. #全部卸载
  25. # rm -rf /tmp/install_pkg/mysql* /usr/local/mysql* /data/mysql* /etc/systemd/system/mysql*
  26. ###########################################################################################
  27. default_port=3306
  28. port=$([ -n "$1" ] && echo "$1" || echo "$default_port")
  29. version=5.7.35
  30. init_pwd=123456
  31. download_pkg_url=https://dev.mysql.com/get/Downloads/MySQL-5.7/mysql-${version}-linux-glibc2.12-x86_64.tar.gz
  32. mysql_basedir=/usr/local/mysql$version
  33. mysql_datadir=/data/mysql$port
  34. instance_exist_check(){
  35. #检查该端口对应的实例datadir是否存在,检查该端口是否被占用
  36. if [ -d ${mysql_datadir} ];then
  37. if [ "`ls -A ${mysql_datadir}`" != "" ];then
  38. echo "fail, ${mysql_datadir} is not empty"
  39. exit 1
  40. fi
  41. fi
  42. lsof -i:${port} >/dev/null 2>&1
  43. if [ $? -eq 0 ];then
  44. echo "fail, port ${port} is used"
  45. exit 1
  46. fi
  47. }
  48. download_mysql_server() {
  49. #下载mysql安装包
  50. echo '>>> check whether the mysql-server installation package exists ...'
  51. if [ ! -d "$mysql_basedir" ]; then
  52. mkdir -p /tmp/install_pkg && cd /tmp/install_pkg
  53. if [ ! -f "mysql-$version-linux-glibc2.12-x86_64.tar.gz" ]; then
  54. echo ">>> download installation package from ${download_pkg_url}"
  55. wget $download_pkg_url
  56. fi
  57. if [ ! -f "mysql-$version-linux-glibc2.12-x86_64.tar.gz" ]; then
  58. echo ">>> download installation package fail!"
  59. exit 1
  60. fi
  61. tar -zxf mysql-$version-linux-glibc2.12-x86_64.tar.gz
  62. mv mysql-$version-linux-glibc2.12-x86_64 $mysql_basedir
  63. else
  64. echo "$mysql_basedir have already exists"
  65. fi
  66. }
  67. get_serverid(){
  68. #生成server_id,采用INET_ATON函数算法得到IP的整数值,再加上端口得到server-id
  69. ip=`ifconfig eth0|grep 'inet '|awk '{print $2}'`
  70. A=$(echo $ip | cut -d '.' -f1)
  71. B=$(echo $ip | cut -d '.' -f2)
  72. C=$(echo $ip | cut -d '.' -f3)
  73. D=$(echo $ip | cut -d '.' -f4)
  74. result=$(($A<<24|$B<<16|$C<<8|$D))
  75. echo $(($result + $port))
  76. }
  77. generate_config_file(){
  78. mkdir -p $mysql_datadir/data
  79. mkdir -p $mysql_datadir/conf
  80. mkdir -p $mysql_datadir/log
  81. mkdir -p $mysql_datadir/run
  82. mkdir -p $mysql_datadir/tmp
  83. mkdir -p $mysql_datadir/binlog
  84. mkdir -p $mysql_datadir/relaylog
  85. mkdir -p $mysql_datadir/undo
  86. server_id=`get_serverid`
  87. cat > $mysql_datadir/conf/my.cnf <<EOF
  88. [client]
  89. port = ${port}
  90. socket = $mysql_datadir/run/mysql.sock
  91. default-character-set = utf8mb4
  92. ####################################################
  93. [mysqld]
  94. #####basic settings
  95. port = ${port}
  96. socket = $mysql_datadir/run/mysql.sock
  97. pid_file = $mysql_datadir/run/mysql.pid
  98. lower_case_table_names = 0
  99. back_log = 2048
  100. #
  101. max_connections = 5000
  102. #
  103. max_user_connections=4900
  104. max_connect_errors = 9999
  105. transaction_isolation = READ-COMMITTED
  106. max_allowed_packet = 1024M
  107. open_files_limit=8192
  108. read_only=off
  109. init_connect="set names utf8mb4"
  110. skip-name-resolve
  111. character_set_server = utf8mb4
  112. collation-server = utf8mb4_general_ci
  113. skip-character-set-client-handshake
  114. secure-file-priv= $mysql_datadir/tmp
  115. interactive_timeout = 1800
  116. wait_timeout = 1800
  117. ##### log file setting
  118. #general_log_file = $mysql_datadir/log/mysql.log
  119. log_error = $mysql_datadir/log/mysql-err.log
  120. slow_query_log=1
  121. log_queries_not_using_indexes =0
  122. long_query_time = 1
  123. slow_query_log_file = $mysql_datadir/log/mysql-slow.log
  124. basedir = ${mysql_basedir}
  125. datadir = $mysql_datadir/data
  126. tmpdir= $mysql_datadir/tmp
  127. ##### query cache setting
  128. query_cache_size = 0
  129. query_cache_limit = 2M
  130. query_cache_type=0
  131. ##### binlog setting
  132. log_bin= $mysql_datadir/binlog/binlog
  133. binlog_format=row
  134. binlog_cache_size = 8M
  135. max_binlog_cache_size=4G
  136. max_binlog_size = 1G
  137. expire_logs_days = 7
  138. sync_binlog=1
  139. innodb_flush_log_at_trx_commit = 1
  140. #####memory/performance settings
  141. sort_buffer_size = 2M
  142. join_buffer_size = 2M
  143. key_buffer_size = 128M
  144. read_buffer_size = 2M
  145. read_rnd_buffer_size = 8M
  146. bulk_insert_buffer_size = 16M
  147. myisam_sort_buffer_size = 32M
  148. thread_cache_size = 512
  149. thread_stack = 512K
  150. table_definition_cache = 4096
  151. table_open_cache = 4096
  152. max_length_for_sort_data = 16k
  153. #####innodb engine setting
  154. innodb_undo_directory= $mysql_datadir/undo
  155. innodb_data_home_dir = $mysql_datadir/data
  156. default_storage_engine = INNODB
  157. innodb_file_per_table=1
  158. innodb_buffer_pool_size = 4301M
  159. innodb_buffer_pool_instances = 8
  160. innodb_write_io_threads=16
  161. innodb_read_io_threads=16
  162. innodb_log_buffer_size = 16M
  163. innodb_log_file_size = 256M
  164. innodb_log_group_home_dir = $mysql_datadir/data
  165. innodb_sort_buffer_size=32M
  166. innodb_log_files_in_group = 4
  167. innodb_max_dirty_pages_pct = 75
  168. innodb_stats_on_metadata = 0
  169. innodb_flush_method = O_DIRECT
  170. innodb_lock_wait_timeout = 8
  171. innodb_io_capacity = 2000
  172. innodb_io_capacity_max = 6000
  173. innodb_thread_concurrency = 128
  174. innodb_purge_threads = 4
  175. innodb_checksum_algorithm = crc32
  176. innodb_read_ahead_threshold = 0
  177. innodb_use_native_aio = ON
  178. innodb_adaptive_flushing = ON
  179. innodb_strict_mode = 1
  180. innodb_file_format = Barracuda
  181. innodb_autoinc_lock_mode = 2
  182. innodb_file_format_check = ON
  183. innodb_online_alter_log_max_size = 100G
  184. innodb_change_buffering = inserts
  185. #####replication settings
  186. server_id = ${server_id}
  187. log_slave_updates=on
  188. relay_log_space_limit=10G
  189. skip-slave-start = true
  190. gtid-mode = ON
  191. enforce_gtid_consistency = ON
  192. relay_log_recovery = on
  193. master_info_repository = TABLE
  194. relay_log_info_repository = TABLE
  195. relay_log = $mysql_datadir/relaylog/relay
  196. #relay_log_info_file = $mysql_datadir/relaylog/relay.info
  197. #master_info_file = $mysql_datadir/relaylog/master.info
  198. slave_load_tmpdir = $mysql_datadir/tmp
  199. slave_net_timeout=180
  200. ##### for switchover
  201. replicate-ignore-table=c2c_db.t_node_status
  202. ##### other settings
  203. ft_min_word_len = 4
  204. tmp_table_size = 128M
  205. max_heap_table_size = 2048M
  206. myisam_max_sort_file_size = 10G
  207. myisam_repair_threads = 1
  208. #slave_parallel_workers = 8
  209. #open_files_limit=8192
  210. performance_schema = OFF
  211. log_bin_trust_function_creators = on
  212. sql_mode=STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION
  213. ####################################################
  214. [mysqldump]
  215. quick
  216. max_allowed_packet = 16M
  217. ####################################################
  218. [mysql]
  219. no-auto-rehash
  220. EOF
  221. }
  222. manage_mysql_by_systemd(){
  223. echo ">>> manage mysql by systemd ..."
  224. egrep "^mysql" /etc/group >& /dev/null || groupadd mysql
  225. id mysql &> /dev/null || useradd mysql -g mysql
  226. chown mysql:mysql -R $mysql_datadir
  227. #可另外为mysql用户设置密码,并将mysql用户加到wheel组(即可sudo)
  228. #passwd mysql
  229. #usermod -aG wheel mysql
  230. # 新增systemctl管理mysql
  231. cat > /etc/systemd/system/mysql$port.service << EOF
  232. [Unit]
  233. Description=MySQL Server
  234. After=network.target
  235. [Install]
  236. WantedBy=multi-user.target
  237. [Service]
  238. User=mysql
  239. Group=mysql
  240. Type=forking
  241. TimeoutSec=0
  242. PIDFile=$mysql_datadir/run/mysql.pid
  243. ExecStart=$mysql_basedir/bin/mysqld --defaults-file=$mysql_datadir/conf/my.cnf --user=mysql --daemonize
  244. EOF
  245. # 重新加载systemctl
  246. systemctl daemon-reload
  247. }
  248. install_mysql_server(){
  249. #初始化mysql提示缺失libnuma.so.1时,需要 yum -y install numactl.x86_64
  250. echo ">>> initialize mysql instance in port $port ..."
  251. $mysql_basedir/bin/mysqld --defaults-file=$mysql_datadir/conf/my.cnf --initialize --basedir=$mysql_basedir --datadir=$mysql_datadir/data
  252. }
  253. start_mysql_service(){
  254. #启动mysql服务(通过systemctl start|stop|status|restart mysql$port管理)
  255. manage_mysql_by_systemd
  256. echo ">>> start mysql$port service ..."
  257. systemctl start mysql$port
  258. }
  259. initlize_pwd(){
  260. echo ">>> initlize pwd for instance ..."
  261. #修改密码:临时密码在mysql错误日志中:A temporary password is generated for root@localhost: xxxxxx+x.xxxx
  262. tmp_pwd=`cat $mysql_datadir/log/mysql-err.log|grep 'temporary password is generated'|awk '{print $NF}'`
  263. echo "$mysql_basedir/bin/mysqladmin -uroot -p${tmp_pwd} password ****** -S $mysql_datadir/run/mysql.sock"
  264. $mysql_basedir/bin/mysqladmin -uroot -p${tmp_pwd} password ${init_pwd} -S $mysql_datadir/run/mysql.sock
  265. $mysql_basedir/bin/mysql -uroot -p${init_pwd} -S $mysql_datadir/run/mysql.sock -e "update mysql.user set user.Host='%' where user.User='root'; flush privileges;select user,host from mysql.user where user = 'root';"
  266. systemctl enable mysql$port
  267. systemctl status mysql$port
  268. }
  269. deploy_mysql_instance(){
  270. instance_exist_check
  271. download_mysql_server
  272. generate_config_file
  273. install_mysql_server
  274. start_mysql_service
  275. initlize_pwd
  276. exit 0
  277. }
  278. deploy_mysql_instance

安装mysql(docker)

  1. #生成映射目录及配置文件
  2. mkdir -p /data/docker/mysql23306/conf
  3. mkdir -p /data/docker/mysql23306/data
  4. mkdir -p /data/docker/mysql23306/log
  5. cat > /data/docker/mysql23306/conf/my.cnf <<EOF
  6. [client]
  7. default_character_set = utf8mb4
  8. [mysqld]
  9. collation_server = utf8_general_ci
  10. character_set_server = utf8mb4
  11. EOF
  12. #启动mysql实例(本机23306端口映射容器的3306端口)
  13. docker run -d -p 23306:3306 --privileged=true -v /data/docker/mysql23306/conf:/etc/mysql/conf.d -v /data/docker/mysql23306/data:/var/lib/mysql -v /data/docker/mysql23306/log:/var/log/mysql -e MYSQL_ROOT_PASSWORD=123456 --restart=on-failure --name mysql23306 mysql:5.7
  14. #启动/停止/重启
  15. docker start/stop/restart mysql23306
  16. #进入容器、登录mysql
  17. docker exec -it mysql23306 bash
  18. mysql -u root -p

MySQL主从同步

主从同步的前提是要开启 binlog,即在my.cnf中设置 log-bin 项。并且主从的 server-id 需不一致。

  1. #一、主节点(master)同步设置
  2. #创建同步帐号并授权(节点注意修改)
  3. CREATE USER 'repl'@'%' IDENTIFIED BY 'repl_pwd';
  4. GRANT replication SLAVE, replication client ON *.* TO 'repl'@'%';
  5. FLUSH PRIVILEGES;
  6. #查看主节点同步状态,记录File与Postion的内容
  7. show master status;
  8. #二、从节点(slave)同步设置
  9. #进入从节点,连接master,注意MASTER_LOG_FILE、MASTER_LOG_POS与主库保持一致
  10. change master to master_host='主节点ip',master_user='repl',master_password='repl_pwd',master_port=3306, master_log_file='mysql-bin.000001',master_log_pos=767;
  11. #启动从库slave进程
  12. start slave;
  13. #查看是否配置成功:Slave_IO Running、Slave_SQL Running进程必须是YES状态
  14. show slave status\G;
  15. #在slave节点上执行,由于从库随时会提升成主库,不能写在配置文件里
  16. set global read_only=1;
  17. #三、测试
  18. #进入主节点建库建表插数据,观察从节点是否已经同步
  19. create database db1;
  20. create table db1.tb1 (id int, name varchar(11));
  21. insert into db1.tb1 values(1,'zs');
  22. #连接从库查看:
  23. mysql -uroot -p123456 -e "select * from db1.tb1;\G"

注:上述开启主从同步后,仅是同步MASTER_LOG_POS之后的数据。master上如果已有数据,则需另外同步到从库。方法如下:

  1. 同步主库数据步骤如下:
  2. 1、主库上一定要开启二进制日志,并且选择在主库的某个时间段使用mysqldump备份
  3. 2、将备份的数据导入到新的从库中
  4. 3、查看导出主库时的日志位置。在从库选择从这个位置开始change,并启动
  5. #1、备份主库数据(主库上一定要开启二进制日志)
  6. /usr/local/mysql/bin/mysql -uroot -p123456 -N -e "show databases;"|grep -Ev "information_schema|performance_schema|sys|mysql"|xargs /usr/local/mysql/bin/mysqldump -uroot -p123456 --routines --single_transaction --master-data=2 --databases > /tmp/master_bk.sql
  7. #备份参数说明:
  8. --排除系统库:information_schema|performance_schema|sys|mysql
  9. --routines:导出存储过程和函数
  10. --single_transaction:导出开始时设置事务隔离状态,并使用一致性快照开始事务,然后unlock tables;而lock-tables是锁住一张表不能写操作,直到dump完毕。
  11. --master-data:默认等于1,将dump起始(change master to)binlog点和pos值写到结果中,等于2是将change master to写到结果中并注释。
  12. #2、将备份数据导入到新从库中
  13. scp /tmp/master_bk.sql root@slaveip:/tmp
  14. #3、登录从库载入主节点数据,并开启主从。其中的master_log_file|pos在master_bk.sql中(30行附近)。
  15. /usr/local/mysql/bin/mysql -uroot -p123456
  16. > source /tmp/master_bk.sql;
  17. > change master to master_host='主节点ip',master_user='repl',master_password='repl_pwd',master_port=3306, master_log_file='mysql-bin.000001',master_log_pos=767;
  18. > start slave;
  19. > set global read_only = 1;

 记录-后台开发

相关视频教程:

黑马程序员JVM完整教程,Java虚拟机快速入门,全程干货不拖沓_哔哩哔哩_bilibili
黑马程序员Java进阶教程Tomcat核心原理解析_哔哩哔哩_bilibili
2021版最新SpringBoot2_权威教程_请直接从P112开始学习新版视频--置顶评论有直达链接-_雷丰阳_尚硅谷_哔哩哔哩_bilibili

安装JDK

  1. #1.下载jdk tar.gz格式压缩包
  2. cd /tmp
  3. wget --no-cookies --no-check-certificate --header "Cookie: gpw_e24=http%3A%2F%2Fwww.oracle.com%2F; oraclelicense=accept-securebackup-cookie" "http://download.oracle.com/otn-pub/java/jdk/8u141-b15/336fa29ff2bb4ef291e347e091f7f4a7/jdk-8u141-linux-x64.tar.gz"
  4. #2.解压压缩包
  5. tar -zxvf jdk-8u141-linux-x64.tar.gz -C /usr/local
  6. #3.修改配置文件
  7. vim /etc/profile
  8. export JAVA_HOME=/usr/local/jdk1.8.0_141
  9. export JRE_HOME=$JAVA_HOME/jre
  10. export CLASSPATH=.:$JAVA_HOME/lib:$JRE_HOME/lib
  11. export PATH=$PATH:$JAVA_HOME/bin
  12. #4.刷新配置文件
  13. source /etc/profile
  14. #5.验证jdk1.8是否安装成功
  15. java -version

卸载JDK

  1. #查看java安装的目录:
  2. which java
  3. #删除安装的文件:
  4. rm -rf /usr/local/jdk1.8.0_141
  5. #删除(java相关)环境变量:
  6. vim /etc/profile
  7. #刷新配置文件:
  8. source /etc/profile

安装JDK(yum方式)

  1. #默认安装在 /usr/lib/jvm
  2. #jdk8
  3. yum install -y java-1.8.0-openjdk*
  4. #jdk11
  5. yum -y install java-11-openjdk*
  6. #查看版本
  7. java -version
  8. #删除jdk
  9. yum -y remove java-1.8.0-openjdk*
  10. yum -y remove tzdata-java.noarch
  11. #注意:通过 yum 安装 jdk,是不会自动配置 JAVA_HOME 环境变量的。如果有一些服务依赖这个环境变量就会启动失败。可通过以下方式找到jdk位置并设置JAVA_HOME:
  12. ls -lrt /etc/alternatives/java
  13. #以上命令可找出jre的位置,截取前面位置即得jdk位置
  14. /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.312.b07-1.el7_9.x86_64
  15. 然后同样对vim /etc/profile进行设置

ps: 

vim /etc/profile后,新开终端或电脑重启之后就无效了
解决办法: vim /etc/bashrc 然后在最后加上环境变量内容 source /etc/profile

安装Tomcat

  1. # 1、下载
  2. cd /tmp
  3. wget --no-check-certificate https://dlcdn.apache.org/tomcat/tomcat-8/v8.5.84/bin/apache-tomcat-8.5.84.tar.gz
  4. # 2、解压
  5. tar -zxvf apache-tomcat-8.5.56.tar.gz
  6. mv apache-tomcat-8.5.84 /opt/tomcat8.5.84
  7. # 3、启动
  8. cd /opt/tomcat8.5.84
  9. ./bin/startup.sh
  10. # 4、停止
  11. ./bin/shutdown.sh
  12. #查看日志
  13. tail -200f logs/catalina.out
  14. ## 默认tomcat是使用8080端口,如机器有多个tomcat,需要修改tomact端口:
  15. vim conf/server.xml
  16. 找到以下三个端口(shutdown、http connect、ajp connect):
  17. Server port="8005" Connector port="8080" Connector port="8009"
  18. 分别修改为其它端口如:
  19. Server port="8006" Connector port="8081" Connector port="8010"

tomcat实战-优化

  1. <!-- tomcat优化1:设置JVM参数
  2. #如果没有配置JAVA_OPTS变量,JVM在启动的时候会自动设置Heap size的值,其初始空间(即-Xms)是物理内存的1/64,最大空间(-Xmx)是物理内存的1/4。可以利用JVM提供的-Xmn -Xms -Xmx等选项可进行设置。默认一个线程大小为1M(-Xss控制)。
  3. 所以可以 vim bin/catalina.sh 在配置(/# OS)前加上以下配置: -->
  4. JAVA_OPTS="-Xms1024M -Xmx1024M -Xmn512M -Dspring.profiles.active=test"
  5. <!-- tomcat优化2:优化连接器
  6. tomcat8080端口连接器默认为:
  7. <Connector port="8080" protocol="HTTP/1.1"
  8. connectionTimeout="20000"
  9. redirectPort="8443" />
  10. #连接数相关的参数配置和优化如下:
  11. 1.maxThreads:Tomcat使用线程来处理接收的每个请求。这个值表示Tomcat可创建的最大的线程数。默认值200。可以根据机器的时期性能和内存大小调整,一般可以在400-500。最大可以在1000左右。
  12. 2.acceptCount:指定当所有可以使用的处理请求的线程数都被使用时,可以放到处理队列中的请求数,超过这个数的请求将不予处理。默认值100。
  13. 3.minSpareThreads:Tomcat初始化时创建的线程数。默认值10。
  14. 4.maxSpareThreads:最大备用线程数,一旦创建的线程超过这个值,Tomcat就会关闭不再需要的socket线程。默认值是-1(无限制)。一般不需要指定。
  15. 5.enableLookups:是否反查域名,默认值为true。为了提高处理能力,应设置为false。
  16. 6.connectionTimeout:网络连接超时,默认值20000,单位:毫秒。设置为0表示永不超时,这样设置有隐患的。通常可设置为30000毫秒。
  17. 7.compression:gzip压缩传输,取值on/off/force,默认值off,
  18. 8.compressionMinSize:表示压缩响应的最小值,只有当响应报文大小大于这个值的时候才会对报文进行压缩,如果开启了压缩功能,默认值就是 2048。
  19. 根据以往经验,常用参数配置如下: -->
  20. <Connector port="8080"
  21. protocol="HTTP/1.1"
  22. connectionTimeout="20000"
  23. redirectPort="8443"
  24. minSpareThreads="50"
  25. enableLookups="false"
  26. acceptCount="300"
  27. maxThreads="500"
  28. URIEncoding="UTF-8"
  29. compression="on"
  30. compressionMinSize="2048"
  31. compressableMimeType="application/json,application/javascript,text/html,text/xml,text/css,text/plain,text/json"/>
  32. <!-- tomcat优化3:禁用AJP协议
  33. 一般都是使用Nginx+tomcat的架构,用不着AJP协议,所以可以直接把AJP连接器禁用 -->
  34. <!-- <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />-->

tomcat实战-使用脚本自动发布

  1. cat > autoDeployTomcat.sh <<EOF
  2. #! /bin/sh
  3. #
  4. #自动化部署使用方法:
  5. # 1)在tomcat根路径下建文件夹,如war
  6. # 2)在第一步创建的文件夹下放入此脚本(可以添加权限:chmod +x autoDeploy.sh)
  7. # 3)把需要部署war包放到此文件夹下,执行此脚本即可自动部署并备份。
  8. #
  9. echo '####################开始自动部署####################'
  10. #当前路径
  11. cpath=`pwd`
  12. echo "当前路径: " $cpath
  13. wpath=${cpath##*/}
  14. echo "当前war文件夹:" $wpath
  15. #获取tomcat路径
  16. tpath=$(dirname `pwd`)
  17. echo "TomcatPath:" $tpath
  18. cd $tpath
  19. #获取tomcat名称
  20. tname=${tpath##*/}
  21. echo "TomcatName:" $tname
  22. #查看tomcat是否在运行
  23. PID=$(ps -ef |grep tomcat |grep -w $tname|grep -v 'grep'|awk '{print $2}')
  24. if [ -z "$PID" ];then
  25. echo "No tomcat process is running"
  26. else
  27. #如果在运行,则停止tomcat服务
  28. echo "Tomcat is running, shutdown now! PID:" $PID
  29. ./bin/shutdown.sh
  30. sleep 2
  31. fi
  32. #将原来的war包备份
  33. echo "Backup the old tomcat instance"
  34. mkdir -p ./$wpath/warbak
  35. mv ./webapps/ROOT.war ./$wpath/warbak/ROOT.war.$(date +%Y%m%d%H%M)-bak
  36. bk_num=`ls ./$wpath/warbak|wc -l`
  37. #最大备份数
  38. max_bk_num=100
  39. if [ "$bk_num" -ge "$max_bk_num" ]; then
  40. echo '#########################################################'
  41. echo '##################### 警告 ##############################'
  42. echo '############ 备份数据过多,开始清理旧数据!##############'
  43. echo '############ 备份数据过多,开始清理旧数据!##############'
  44. echo '############ 备份数据过多,开始清理旧数据!##############'
  45. echo '#########################################################'
  46. echo '#########################################################'
  47. for i in `ls -tr ./$wpath/warbak|head -20`
  48. do
  49. rm -rf "./$wpath/warbak/$i"
  50. done;
  51. fi
  52. cp ./$wpath/ROOT.war ./webapps/
  53. sleep 1
  54. if kill -0 "$PID" 2>/dev/null;then
  55. kill -9 $PID
  56. fi
  57. rm -rf ./webapps/ROOT
  58. #重新启动tomcat
  59. echo "Tomcat is restart..."
  60. ./bin/startup.sh
  61. echo '####################部署结束####################'
  62. sleep 5
  63. echo '查看启动日志'
  64. tail -300 ./logs/catalina.out
  65. EOF

CURD(后端)

最基本的后台开发可以认为是由 springboot 2.3+ mysql 实现基本的CURD。这也是我们程序猿大部分时间的工作,其它后续工作基本都是在CURD的基础上完成。

配套代码v1.0.0分支便是记录一个最基础的 curd 项目,代码内容仅包括:代码生成工具 + 提供CURD接口(swagger页面展示 /swagger-ui.html)

代码生成工具

  1. <!-- pom.xml -->
  2. <dependency>
  3. <groupId>com.baomidou</groupId>
  4. <artifactId>mybatis-plus-boot-starter</artifactId>
  5. <version>3.5.2</version>
  6. </dependency>
  7. <dependency>
  8. <groupId>com.baomidou</groupId>
  9. <artifactId>mybatis-plus-generator</artifactId>
  10. <version>3.5.2</version>
  11. </dependency>
  12. <dependency>
  13. <groupId>org.apache.velocity</groupId>
  14. <artifactId>velocity-engine-core</artifactId>
  15. <version>2.0</version>
  16. </dependency>
  1. import com.baomidou.mybatisplus.annotation.FieldFill;
  2. import com.baomidou.mybatisplus.generator.FastAutoGenerator;
  3. import com.baomidou.mybatisplus.generator.config.OutputFile;
  4. import com.baomidou.mybatisplus.generator.config.rules.DateType;
  5. import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
  6. import com.baomidou.mybatisplus.generator.fill.Column;
  7. import java.io.File;
  8. import java.util.ArrayList;
  9. import java.util.Collections;
  10. import java.util.List;
  11. /**
  12. * 代码生成工具类
  13. *
  14. * @author jvxb
  15. * @since 2022-12-10
  16. */
  17. public class CodeGenerateUtil {
  18. // 配置需要生成的表名(多个用逗号隔开)、数据库连接
  19. private static final String TABLES = "sys_user";
  20. private static final String URL = "jdbc:mysql://localhost:3306/demo?useUnicode=true&serverTimezone=GMT%2B8&characterEncoding=utf8&useSSL=false";
  21. private static final String USERNAME = "root";
  22. private static final String PASSWORD = "123456";
  23. private static final String AUTHOR = "jvxb";
  24. // -----------------------------------------------------------------------------
  25. // 是否开启覆盖已生成的 Entity 和 Mapper文件(为防止覆盖原有方法,默认不允许覆盖。字段变化时建议手动处理)
  26. private static final boolean OVERRIDE_ENTITY_MAPPER = false;
  27. // private static final boolean OVERRIDE_ENTITY_MAPPER = true;
  28. // -----------------------------------------------------------------------------
  29. // 当前项目路径,父包名与模块名(projectDir\src\main\java下的模块)
  30. private static final String projectDir = System.getProperty("user.dir");
  31. private static final String PARENT_PACKAGE_NAME = "com.jvxb";
  32. private static final String MODULE_NAME = "demo";
  33. public static void main(String[] args) {
  34. //生成代码
  35. generateCode();
  36. //删除代码
  37. // deleteCode();
  38. }
  39. public static void generateCode() {
  40. FastAutoGenerator.create(URL, USERNAME, PASSWORD)
  41. .globalConfig(builder -> {
  42. builder.author(AUTHOR) // 设置作者
  43. .enableSwagger() // 开启 swagger 模式
  44. .disableOpenDir() //生成代码后不打开目录
  45. .dateType(DateType.ONLY_DATE) //使用 Date
  46. .outputDir(projectDir + "\\src\\main\\java"); // 指定输出目录
  47. })
  48. .packageConfig(builder -> {
  49. builder.parent(PARENT_PACKAGE_NAME) // 设置父包名
  50. .moduleName(MODULE_NAME) // 设置父包模块名
  51. .pathInfo(Collections.singletonMap(OutputFile.xml,
  52. projectDir + "\\src\\main\\resources\\mapper")); // 设置mapperXml生成路径
  53. })
  54. .strategyConfig(builder -> {
  55. builder.addInclude(TABLES);
  56. builder.controllerBuilder()
  57. .enableRestStyle();
  58. builder.serviceBuilder()
  59. .formatServiceFileName("%sService");
  60. builder.entityBuilder()
  61. .addTableFills(new Column("create_time", FieldFill.INSERT))
  62. .addTableFills(new Column("update_time", FieldFill.INSERT_UPDATE))
  63. .enableColumnConstant()
  64. .enableLombok();
  65. builder.mapperBuilder()
  66. .enableBaseResultMap() // 开启BaseResultMap
  67. .enableBaseColumnList(); // 开启BaseColumnList
  68. if (OVERRIDE_ENTITY_MAPPER) {
  69. builder.entityBuilder().fileOverride();
  70. builder.mapperBuilder().fileOverride();
  71. }
  72. })
  73. .execute();
  74. }
  75. private static void deleteCode() {
  76. for (String table : TABLES.split(",")) {
  77. String entityName = NamingStrategy.capitalFirst(NamingStrategy.underlineToCamel(table));
  78. String parentPackageName = PARENT_PACKAGE_NAME.replace(".", "\\");
  79. List<String> pathArr = new ArrayList<>();
  80. pathArr.add(String.format("%s\\src\\main\\java\\%s\\%s\\controller\\%sController.java", projectDir, parentPackageName, MODULE_NAME, entityName));
  81. pathArr.add(String.format("%s\\src\\main\\java\\%s\\%s\\service\\%sService.java", projectDir, parentPackageName, MODULE_NAME, entityName));
  82. pathArr.add(String.format("%s\\src\\main\\java\\%s\\%s\\service\\impl\\%sServiceImpl.java", projectDir, parentPackageName, MODULE_NAME, entityName));
  83. pathArr.add(String.format("%s\\src\\main\\java\\%s\\%s\\entity\\%s.java", projectDir, parentPackageName, MODULE_NAME, entityName));
  84. pathArr.add(String.format("%s\\src\\main\\java\\%s\\%s\\mapper\\%sMapper.java", projectDir, parentPackageName, MODULE_NAME, entityName));
  85. pathArr.add(String.format("%s\\src\\main\\resources\\mapper\\%sMapper.xml", projectDir, entityName));
  86. for (String path : pathArr) {
  87. File file = new File(path);
  88. if (file.exists()) {
  89. System.out.println(String.format("delete file [%s] ...", file.getAbsolutePath()));
  90. file.delete();
  91. } else {
  92. System.out.println(String.format("file [%s] does not exist", file.getAbsolutePath()));
  93. }
  94. }
  95. }
  96. }
  97. }

swagger配置

  1. <! -- pom.xml -->
  2. <dependency>
  3. <groupId>io.springfox</groupId>
  4. <artifactId>springfox-swagger2</artifactId>
  5. <version>2.9.2</version>
  6. <exclusions>
  7. <exclusion>
  8. <groupId>io.swagger</groupId>
  9. <artifactId>swagger-models</artifactId>
  10. </exclusion>
  11. </exclusions>
  12. </dependency>
  13. <dependency>
  14. <groupId>io.swagger</groupId>
  15. <artifactId>swagger-models</artifactId>
  16. <version>1.5.22</version>
  17. </dependency>
  18. <dependency>
  19. <groupId>io.springfox</groupId>
  20. <artifactId>springfox-swagger-ui</artifactId>
  21. <version>2.9.2</version>
  22. </dependency>
  1. @Configuration
  2. @EnableSwagger2
  3. @Profile({"dev", "test"})
  4. public class SwaggerConfig {
  5. @Bean
  6. public Docket Docket() {
  7. return new Docket(DocumentationType.SWAGGER_2)
  8. .apiInfo(ApiInfo())
  9. .select()
  10. .apis(RequestHandlerSelectors.withClassAnnotation(Api.class))
  11. .paths(Predicates.not(PathSelectors.regex("/error.*")))
  12. .build();
  13. }
  14. private ApiInfo ApiInfo() {
  15. return new ApiInfoBuilder().title("标题:抓娃小兵测试项目的接口文档")
  16. .description("这是一段关于接口文档的简介")
  17. .build();
  18. }
  19. }

RestController

  1. @Api(tags = "系统用户")
  2. @RestController
  3. @RequestMapping("/demo/sysUser")
  4. public class SysUserController extends BaseCURDController<SysUser> {
  5. @Autowired
  6. private SysUserService sysUserService;
  7. @ApiOperation("查找列表")
  8. @GetMapping("/getList")
  9. public ResponseDataVo<List<SysUser>> getList(String keyword) {
  10. QueryWrapper wrapper = new QueryWrapper();
  11. wrapper.like(StrUtil.isNotBlank(keyword), SysUser.USER_NAME, keyword);
  12. List<SysUser> list = sysUserService.list(wrapper);
  13. return ResponseDataVo.success(list);
  14. }
  15. }
  16. /**
  17. * <p>================================================
  18. * <p>Title: 基础 CURD 控制器
  19. * <p>Description: 通过继承本类,子类控制器可以直接使用增删改查接口。
  20. * <p>================================================
  21. *
  22. * @author jvxb
  23. * @version 1.0
  24. */
  25. public abstract class BaseCURDController<T> {
  26. protected IService<T> commonService;
  27. @Autowired
  28. public void setCommonService(IService<T> commonService) {
  29. this.commonService = commonService;
  30. }
  31. @ApiOperation("新增")
  32. @PostMapping("/save")
  33. public ResponseDataVo<T> save(@RequestBody T t) {
  34. commonService.save(t);
  35. return ResponseDataVo.success(t);
  36. }
  37. @ApiOperation("修改")
  38. @PostMapping("/update")
  39. public ResponseDataVo<Void> update(@RequestBody T t) {
  40. commonService.updateById(t);
  41. return ResponseDataVo.success();
  42. }
  43. @ApiOperation("删除id")
  44. @PostMapping("/delete/{id}")
  45. public ResponseDataVo<Void> delete(@PathVariable("id") String id) {
  46. commonService.removeById(id);
  47. return ResponseDataVo.success();
  48. }
  49. @ApiOperation("查找id")
  50. @GetMapping("/get/{id}")
  51. public ResponseDataVo<T> get(@PathVariable("id") String id) {
  52. return ResponseDataVo.success(commonService.getOne(new QueryWrapper<T>().eq("id", id)));
  53. }
  54. }

记录-前端开发(vue)

参考地址:vue3官方文档

安装node

  1. #1、下载
  2. cd /tmp
  3. wget https://npm.taobao.org/mirrors/node/v14.17.0/node-v14.17.0-linux-x64.tar.gz
  4. #2、解压
  5. tar -zxvf node-v14.17.0-linux-x64.tar.gz -C /usr/local
  6. #3、配置路径
  7. cd /usr/local/node-v14.17.0-linux-x64
  8. ln -s /usr/local/node-v14.17.0-linux-x64/bin/npm /usr/bin/npm
  9. ln -s /usr/local/node-v14.17.0-linux-x64/bin/node /usr/bin/node
  10. vim /etc/profile
  11. NODE_HOME=/usr/local/node-v14.17.0-linux-x64
  12. PATH=$NODE_HOME/bin:$PATH
  13. source /etc/profile
  14. #4、检测
  15. node -v
  16. npm -v

CURD(前端)

创建项目( 参考 Vue3中文文档

  1. # npm使用淘宝镜像,并验证是否成功
  2. npm config set registry https://registry.npm.taobao.org
  3. npm config get registry
  4. # 通过npm init vite-app <project-name> 即可初始化项目,如:
  5. npm init vite-app demo-web
  6. cd demo-web
  7. npm install
  8. npm run dev
  9. # 按提示访问页面
  10. localhost:3000
  11. #注:如果需要取消淘宝镜像:
  12. npm config set registry https://registry.npmjs.org

CURD页面示例:

  1. #1、安装elementUi依赖
  2. npm install element-plus --save
  3. npm install axios --save
  4. #2、在main.js使用elementUi
  5. import ElementPlus from 'element-plus'
  6. import 'element-plus/dist/index.css'
  7. const app = createApp(App)
  8. app.use(ElementPlus)
  9. app.mount('#app')
  10. #3、将HelloWorld页面改为如下CURD页面:
  11. <template>
  12. <div style="padding: 1rem 1rem;display: flex; justify-content: space-between; align-items: flex-start">
  13. <el-form :inline="true" class="demo-form-inline" @submit.native.prevent>
  14. <el-form-item>
  15. <el-input v-model="searchOpt.keyword" placeholder="用户名称/号码" clearable
  16. @keydown.enter.native="search"></el-input>
  17. </el-form-item>
  18. <el-form-item>
  19. <el-config-provider :locale="locale">
  20. <el-date-picker
  21. v-model="searchOpt.createTime"
  22. type="daterange"
  23. range-separator="至"
  24. start-placeholder="开始日期"
  25. end-placeholder="结束日期"
  26. value-format="YYYY-MM-DD"
  27. />
  28. </el-config-provider>
  29. </el-form-item>
  30. <el-form-item>
  31. <el-button type="primary" @click="search">查询</el-button>
  32. </el-form-item>
  33. </el-form>
  34. <el-form :inline="true" class="demo-form-inline">
  35. <el-button @click="operateUser({}, '新增用户')" type="primary" style="margin: .5rem">添加用户</el-button>
  36. </el-form>
  37. </div>
  38. <div>
  39. <el-table :data="tableData"
  40. element-loading-text="加载中"
  41. border
  42. style="width: 100%">
  43. <el-table-column type="selection" width="55"/>
  44. <el-table-column prop="loginName" label="登录名"/>
  45. <el-table-column prop="userName" label="用户名称"/>
  46. <el-table-column prop="roleName" label="所属角色"/>
  47. <el-table-column prop="phone" label="手机号"/>
  48. <el-table-column label="是否有效">
  49. <template #default="scope">
  50. {{ scope.row.valid === 0 ? '否' : '是' }}
  51. </template>
  52. </el-table-column>
  53. <el-table-column prop="createTime" label="创建时间"/>
  54. <el-table-column fixed="right" label="操作" width="220">
  55. <template #default="scope">
  56. <el-button type="info" size="small" @click="operateUser(scope.row, '查看用户')">查看
  57. </el-button>
  58. <el-button type="primary" size="small" @click="operateUser(scope.row, '编辑用户')">编辑
  59. </el-button>
  60. <el-button type="danger" size="small" @click="del(scope.row.id)">删除</el-button>
  61. </template>
  62. </el-table-column>
  63. </el-table>
  64. </div>
  65. <div>
  66. <el-dialog :title="dialogName"
  67. v-model="dialogVisible"
  68. :before-close="handleClose"
  69. width="50%">
  70. <div style="background: white; padding: 24px">
  71. <el-form label-width="130px" :model="formData" ref="formData" style="width: 70%; margin-left: 10%">
  72. <el-form-item>
  73. <template #label>
  74. <span class="require">*</span>登录名
  75. </template>
  76. <el-input v-model="formData.loginName"/>
  77. </el-form-item>
  78. <el-form-item>
  79. <template #label>
  80. <span class="require">*</span>登录密码
  81. </template>
  82. <el-input type="password" v-model="formData.password"></el-input>
  83. </el-form-item>
  84. <el-form-item>
  85. <template #label>
  86. <span class="require">*</span>用户姓名
  87. </template>
  88. <el-input v-model="formData.userName"></el-input>
  89. </el-form-item>
  90. <el-form-item>
  91. <template #label>
  92. 手机号
  93. </template>
  94. <el-input v-model="formData.phone"></el-input>
  95. </el-form-item>
  96. <el-form-item>
  97. <template #label>
  98. 账号是否有效
  99. </template>
  100. <el-radio v-model="formData.valid" :label="1">是</el-radio>
  101. <el-radio v-model="formData.valid" :label="0">否</el-radio>
  102. </el-form-item>
  103. <el-form-item label="所属角色">
  104. <el-select v-model="formData.roleId" placeholder="请选择用户角色" @change="getRoleKeyValue">
  105. <el-option
  106. v-for="role in roleData"
  107. :key="role.roleName"
  108. :value="role.roleId"
  109. :label="role.roleName"
  110. />
  111. </el-select>
  112. </el-form-item>
  113. <el-form-item v-if="dialogName === '新增用户'">
  114. <el-button type="primary" @click="saveOrUpdate">新增</el-button>
  115. </el-form-item>
  116. <el-form-item v-if="dialogName === '编辑用户'">
  117. <el-button type="primary" @click="saveOrUpdate">保存</el-button>
  118. </el-form-item>
  119. </el-form>
  120. </div>
  121. </el-dialog>
  122. </div>
  123. </template>
  124. <script>
  125. import axios from "axios";
  126. export default {
  127. data() {
  128. return {
  129. searchOpt: {keyword: '', createTime: ''},
  130. dialogName: '',
  131. dialogVisible: false,
  132. formData: {},
  133. roleData: [{'roleName': 'admin', 'roleId': 1}, {'roleName': 'dev', 'roleId': 2}, {
  134. 'roleName': 'test',
  135. 'roleId': 3
  136. }],
  137. tableData: []
  138. }
  139. },
  140. mounted() {
  141. this.loadData();
  142. },
  143. methods: {
  144. loadData: function () {
  145. let _this = this;
  146. axios.get('http://localhost:8080/demo/sysUser/getList' + _this.buildQuery(_this.searchOpt))
  147. .then(resp => {
  148. _this.tableData = resp.data.data;
  149. }).catch(error => {
  150. console.log(error);
  151. });
  152. },
  153. operateUser: function (row, type) {
  154. this.dialogName = type;
  155. this.formData = JSON.parse(JSON.stringify(row));
  156. this.dialogVisible = true;
  157. },
  158. search: function () {
  159. this.loadData();
  160. },
  161. handleClose: function () {
  162. this.dialogName = '';
  163. this.dialogVisible = false;
  164. this.formData = {};
  165. },
  166. getRoleKeyValue: function (kid) {
  167. for (var idx in this.roleData) {
  168. if (this.roleData[idx].roleId === kid) {
  169. this.formData.roleName = this.roleData[idx].roleName;
  170. break;
  171. }
  172. }
  173. },
  174. saveOrUpdate: function () {
  175. let type = this.formData.id ? 'update' : 'save';
  176. let _this = this;
  177. axios.post("http://localhost:8080/demo/sysUser/" + type, this.formData)
  178. .then(resp => {
  179. _this.dialogVisible = false;
  180. _this.loadData();
  181. }).catch(error => {
  182. console.log(error);
  183. });
  184. },
  185. del: function (e) {
  186. let _this = this;
  187. axios.post("http://localhost:8080/demo/sysUser/delete/" + e)
  188. .then(resp => {
  189. console.log(resp);
  190. _this.loadData();
  191. }).catch(error => {
  192. console.log(error);
  193. });
  194. },
  195. buildQuery: function(params) {
  196. let paramsArray = [];
  197. Object.keys(params).forEach(key => paramsArray.push(key + '=' + params[key]));
  198. return '?' + paramsArray.join('&');
  199. }
  200. }
  201. }
  202. </script>
  203. <style scoped>
  204. .require {
  205. color: red;
  206. padding-right: 5px;
  207. }
  208. </style>

 记录-DevOps

相关视频教程: 

黑马程序员Java教程自动化部署Jenkins从环境配置到项目开发_哔哩哔哩_bilibili

DevOps来自Development(开发)和Operations(运维)的缩写,是一组为了能够实现更快、更可靠的的发布更高质量的产品的过程和方法的统称。

用于促进应用开发、应用运维和质量保障(QA)部门之间的沟通、协作与整合。

 CI的英文名称是Continuous Integration(持续集成)。

持续集成(CI)是在源代码变更后自动检测、拉取、构建和(在大多数情况下)进行单元测试(自动化测试)的过程,从而确定新代码和原有代码能否正确地集成在一起。

CD分为Continuous Delivery(持续交付)和Continuous Deployment(持续部署)

CI、CD是实现DevOps的方法。

Jenkins就是一个开源的、提供友好操作界面的持续集成(CI)工具。

安装mvn

  1. #1、下载
  2. cd /tmp
  3. wget --no-check-certificate https://archive.apache.org/dist/maven/maven-3/3.6.1/binaries/apache-maven-3.6.1-bin.tar.gz
  4. #2、解压
  5. tar -zxvf apache-maven-3.6.1-bin.tar.gz -C /usr/local
  6. #3、设置setting(镜像地址、下载地址等)
  7. cd /usr/local/apache-maven-3.6.1
  8. mkdir repo
  9. vim conf/settings.xml (:set paste :set nopaste)
  10. <?xml version="1.0" encoding="UTF-8"?>
  11. <settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
  12. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  13. xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
  14. <localRepository>/opt/apache-maven-3.6.1/repo</localRepository>
  15. <pluginGroups></pluginGroups>
  16. <proxies></proxies>
  17. <servers></servers>
  18. <mirrors>
  19. <mirror>
  20. <id>alimaven</id>
  21. <name>aliyun maven</name>
  22. <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
  23. <mirrorOf>central</mirrorOf>
  24. </mirror>
  25. </mirrors>
  26. <profiles>
  27. <profile>
  28. <id>jdk18</id>
  29. <activation>
  30. <activeByDefault>true</activeByDefault>
  31. <jdk>1.8</jdk>
  32. </activation>
  33. <properties>
  34. <maven.compiler.source>1.8</maven.compiler.source>
  35. <maven.compiler.target>1.8</maven.compiler.target>
  36. <maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion>
  37. </properties>
  38. </profile>
  39. </profiles>
  40. </settings>
  41. #4、配置mvn路径
  42. vim /etc/profile
  43. export MAVEN_HOME=/usr/local/apache-maven-3.6.1
  44. export PATH=$PATH:$MAVEN_HOME/bin
  45. source /etc/profile
  46. #5、检测
  47. mvn -version

安装git

yum方式最简单快速,但是版本不可选。

  1. #安装git
  2. yum -y install git
  3. #查看版本
  4. git --version

可以通过源码方式安装任意版本

  1. #1、下载
  2. cd /tmp
  3. wget --no-check-certificate https://mirrors.edge.kernel.org/pub/software/scm/git/git-2.21.0.tar.gz
  4. #2、安装依赖
  5. yum -y install curl-devel expat-devel gettext-devel openssl-devel zlib-devel gcc perl-ExtUtils-MakeMaker
  6. #3、解压
  7. tar -zxvf git-2.21.0.tar.gz -C /usr/local
  8. #4、编译/安装
  9. cd /usr/local/git-2.21.0
  10. ./configure --prefix=/usr/local/git
  11. make && make install
  12. #5、配置变量
  13. # 删除已有的 git
  14. yum remove git
  15. vim /etc/profile
  16. GIT_HOME=/usr/local/git
  17. export PATH=$PATH:$GIT_HOME/bin
  18. export PATH=$PATH:$GIT_HOME/libexec/git-core/
  19. source /etc/profile
  20. #6、查看版本
  21. git --version
  22. #拉取代码
  23. git clone https://gitee.com/jvxb/demo.git

如需push代码,则需配置git帐号与提交公钥:

  1. #查看git帐号邮箱信息
  2. git config user.name
  3. git config user.email
  4. #修改用户名和邮箱地址
  5. git config --global user.name jvxb
  6. git config --global user.email jvxb@qq.com
  7. %% 查看相关配置信息
  8. git config --list
  9. 配置记住用户验证信息
  10. git config --global credential.helper store
  11. #生成密钥(公钥),三次回车后保留密钥到(/root/.ssh/id_rsa 和 /root/.ssh/id_rsa.pub)
  12. ssh-keygen -t rsa -C "jvxb@qq.com"
  13. #添加公钥:打开gitee/GitHub的setting->ssh setting -> add ssh key
  14. 粘贴到以下内容(不要后面的邮箱):cat /root/.ssh/id_rsa.pub
  15. #测试
  16. ssh -T git@github.com
  17. ssh -T git@gitee.com
  18. #克隆
  19. git clone https://gitee.com/jvxb/demo.git

安装jenkins

安装 jenkins 有docker、war、rmp、yum等方式。其中yum、war包的方式容易受jdk版本影响(较新版本需要JDK11及以上),此处选择使用rpm方式部署(jdk8可支持)。

  1. 卸载 jenkins
  2. rpm -e jenkins
  3. find / -iname jenkins | xargs -n 1000 rm -rf
  4. #1、下载
  5. wget --no-check-certificate https://repo.huaweicloud.com/jenkins/redhat/jenkins-2.305-1.1.noarch.rpm
  6. #2、安装
  7. rpm -ivh jenkins-2.305-1.1.noarch.rpm
  8. 安装完以后重要的目录说明:
  9. /usr/lib/jenkins/jenkins.war WAR包
  10. /etc/sysconfig/jenkins 配置文件
  11. /var/lib/jenkins/ 默认的JENKINS_HOME目录
  12. /var/log/jenkins/jenkins.log Jenkins日志文件
  13. #3.修改配置文件
  14. 修改用户名为root和修改端口为9090,否则容易爆权限不足或端口冲突
  15. sed -i 's/JENKINS_USER="jenkins"/JENKINS_USER="root"/;s/JENKINS_PORT="8080"/JENKINS_PORT="9090"/' /etc/sysconfig/jenkins
  16. 4.配置git/mvn/jdk路径
  17. jendkins后续的拉取、打包、运行代码需要git/maven/java环境,可将它们软连接/usr/bin:
  18. ln -s "$JAVA_HOME/bin/java" /usr/bin/java
  19. ln -s "$MAVEN_HOME/bin/mvn" /usr/bin/mvn
  20. ln -s "$GIT_HOME/bin/git" /usr/bin/git
  21. 5、启动并设置开机自启
  22. systemctl start jenkins
  23. /sbin/chkconfig jenkins on
  24. 6、访问jenkins,
  25. 页面输入 ip:port,第一次须按提示找到密码输入
  26. cat /var/lib/jenkins/secrets/initialAdminPassword
  27. #注意:有防火墙记得开放端口
  28. firewall-cmd --zone=public --list-ports
  29. firewall-cmd --zone=public --add-port=9090/tcp --permanent
  30. firewall-cmd --reload
  31. 7、新手入门:安装插件
  32. 先跳过,后续再安装:选择合适的插件 -> 无 -> 安装。
  33. 手工安装插件前可先更新站点:Manage Jenkins -> Manage Plugins -> Advanced -> Update Site -> 替换为以下地址(后最好重启):
  34. https://mirrors.tuna.tsinghua.edu.cn/jenkins/updates/update-center.json
  35. #安装中文插件
  36. Manage Plugins -> Available:
  37. * 输入 local:找到 Localization: Chinese (Simplified)&Localization Support,安装并重启
  38. * 输入 Maven Integration 安装maven插件
  39. 注:如果不能通过页面安装插件,可自行下载后再在[系统管理→插件管理→高级→上传]中上传phi文件
  40. #关闭安全警告、过期插件、升级通知等,取消勾选以下配置:
  41. 系统管理->系统配置->使用统计
  42. 系统管理->系统配置->管理监控配置->插件/Plugin/更新通知
  43. 系统管理->全局安全配置->隐藏的安全警告(全部取消勾选)
  44. 8、开始使用:新建项目流水线
  45. 新建任务 -> 自由风格 -> 输入项目描述 -> Build -> 构建如下:
  46. git --version
  47. maven -version
  48. java -version

jenkins构建实战:拉取代码并使用 java -jar 启动

  1. BUILD_ID=dontKillMe
  2. project_name=demo
  3. project_branch=master
  4. project_port=8080
  5. cicd_dir=/data/cicd
  6. project_addr="https://gitee.com/jvxb/$project_name.git"
  7. echo "================================"
  8. echo "project_name = $project_name, project_port = $project_port, branch = $project_branch"
  9. echo "================================"
  10. mkdir -p $cicd_dir
  11. cd $cicd_dir
  12. if [ -d "$project_name" ];
  13. then
  14. cd $project_name/
  15. git pull
  16. else
  17. git clone $project_addr
  18. cd $project_name/
  19. fi
  20. git checkout $project_branch
  21. mvn clean package
  22. pid=`lsof -i:$project_port|awk 'NR == 2 {print $2}'`
  23. if [[ -n "$pid" ]];
  24. then
  25. echo "exists pid $pid, execute kill/shutdown and restart"
  26. kill -9 $pid
  27. else
  28. echo "no pid in port $project_port, ready start .."
  29. fi
  30. #通过jar命令启动
  31. cd target/
  32. nohup java -jar ROOT.war --server.port=$project_port > nohup.out 2>&1 &

jenkins构建实战:拉取代码并使用 tomcat 启动

  1. BUILD_ID=dontKillMe
  2. project_name=demo
  3. project_branch=master
  4. cicd_dir=/data/cicd
  5. tomcat_dir=/opt/tomcat8.5.84
  6. project_addr="https://gitee.com/jvxb/$project_name.git"
  7. echo "===================================================="
  8. echo "project_name = $project_name, branch=$project_branch"
  9. echo "===================================================="
  10. mkdir -p $cicd_dir
  11. cd $cicd_dir
  12. if [ -d "$project_name" ];
  13. then
  14. cd $project_name/
  15. git pull
  16. else
  17. git clone $project_addr
  18. cd $project_name/
  19. fi
  20. git checkout $project_branch
  21. mvn clean package
  22. mkdir -p $tomcat_dir/war
  23. mv target/ROOT.war $tomcat_dir/war
  24. cd $tomcat_dir/war
  25. sh autoDeploy.sh

jenkins构建实战:拉取代码并使用npm运行

  1. BUILD_ID=dontKillMe
  2. project_name="simple-web"
  3. project_branch=master
  4. cicd_dir=/data/cicd
  5. dist=/data/meet_u/dist
  6. mkdir -p $cicd_dir
  7. mkdir -p $dist
  8. project_addr="https://gitee.com/jvxb/$project_name.git"
  9. echo "===================================================="
  10. echo "project_name = $project_name, branch=$project_branch"
  11. echo "===================================================="
  12. mkdir -p $cicd_dir
  13. cd $cicd_dir
  14. if [ -d "$project_name" ];
  15. then
  16. cd $project_name/
  17. git pull
  18. else
  19. git clone $project_addr
  20. cd $project_name/
  21. fi
  22. npm install
  23. npm run build-prd
  24. cp -rf ./dist/* $dist

上述构建过程只是手动输入来指定了git的分支并在本机上运行jar/war,一般来说,jenkins需要从某个git源下载代码并打包成jar/war/dist并发送到远端机器去部署。

上述过程需要用到以下插件:Maven IntegrationVersion、GitLab、GitLab API Plugin(由于jenkins版本不同,叫法会略有不同,大体上是一样的),并且需要在Manage Jenkins 中添加主机。


记录-Redis

相关视频教程:

黑马程序员Redis入门到精通,深入剖析Redis缓存技术,Java企业级解决方案redis教程_哔哩哔哩_bilibili

安装Redis

  1. #下载编译安装
  2. wget http://download.redis.io/releases/redis-4.0.12.tar.gz
  3. tar -zxvf redis-5.0.4.tar.gz
  4. mv redis-5.0.4 /usr/local/redis-5.0.4
  5. cd /usr/local/redis-5.0.4
  6. make
  7. cd src && make install
  8. #安装完成,查看版本
  9. /usr/local/bin/redis-server -v
  10. #启动(默认本机6379端口,一般会指定配置文件启动)
  11. /usr/local/bin/redis-server
  12. #指定配置文件启动(解压包中有原redis.conf文件)
  13. /usr/local/bin/redis-server /usr/local/redis-5.0.4/redis.conf
  14. 一般会改动的几个重要配置:
  15. · bind 127.0.0.1:允许访问机器的IP,默认只有本机才能访问,一般设置为 bind 0.0.0.0 来让其他ip也可以访问。
  16. · port 6379:redis 实例启动的端口,默认为 6379
  17. · protected-mode yes:不让外部网络访问,只让本地访问,将其改为no。
  18. · daemonize no:是否以守护进程的方式运行,默认是 no,也就是说你把启动窗口关闭了,将其改为yes
  19. 生产环境时,可以选择禁用一些高位命令:
  20. # 进入 redis.conf 找到 SECURITY 区域;添加如下配置
  21. rename-command FLUSHALL ""
  22. rename-command FLUSHDB ""
  23. rename-command KEYS ""

redis多实例安装脚本(deploy_redis.sh)参考以下:

  1. #!/bin/bash
  2. #初始化约定:
  3. #安装版本:5.0.4
  4. #使用端口:3306
  5. #安装目录(basedir):/usr/local/redis5.0.4
  6. #数据目录(datadir):/data/redis$port,即默认为 /data/redis6379
  7. #配置文件:存于对应数据目录下conf/redis.conf,即默认为 /data/redis6379/conf/redis.conf
  8. #
  9. #输入参数:
  10. #$1:端口号:如不输入,则使用6379端口。
  11. #如:sh redis_deploy.sh 或 sh redis_deploy.sh 6380
  12. #
  13. #输出:
  14. #success:active (running)
  15. #fail:部署失败原因
  16. #步骤1:根据是否存在basedir来判断是否第一次安装。如果是第一次安装则下载安装redis$version。
  17. #步骤2:根据输入端口判断对应的datadir是否存在,存在则不允许安装,端口被占用也也不允许安装
  18. #步骤3:生成配置文件,开始安装并通过systemd启动
  19. #步骤4:通过systemd管理redis。即 systemctl start|stop|restart|enable|disable redis${port}
  20. #卸载redis
  21. #卸载某实例
  22. #systemctl stop redis${port}
  23. #rm -rf $redis_datadir /etc/systemd/system/redis${port}
  24. #全部卸载
  25. # rm -rf /tmp/install_pkg/redis* /usr/local/redis* /data/redis* /etc/systemd/system/redis*
  26. ###########################################################################################
  27. default_port=6379
  28. port=$([ -n "$1" ] && echo "$1" || echo "$default_port")
  29. version=5.0.4
  30. init_pwd=
  31. maxmemory=1024M
  32. memory_policy=allkeys-lru
  33. download_pkg_url=http://download.redis.io/releases/redis-$version.tar.gz
  34. redis_basedir=/usr/local/redis$version
  35. redis_datadir=/data/redis$port
  36. instance_exist_check(){
  37. #检查该端口对应的实例datadir是否存在,检查该端口是否被占用
  38. if [ -d ${redis_datadir} ];then
  39. if [ "`ls -A ${redis_datadir}`" != "" ];then
  40. echo "fail, ${redis_datadir} is not empty"
  41. exit 1
  42. fi
  43. fi
  44. lsof -i:${port} >/dev/null 2>&1
  45. if [ $? -eq 0 ];then
  46. echo "fail, port ${port} is used"
  47. exit 1
  48. fi
  49. }
  50. download_redis_server() {
  51. #下载redis安装包
  52. echo '>>> check whether the redis-server installation package exists ...'
  53. if [ ! -d "$redis_basedir" ]; then
  54. mkdir -p /tmp/install_pkg && cd /tmp/install_pkg
  55. if [ ! -f "redis-$version.tar.gz" ]; then
  56. echo ">>> download installation package from ${download_pkg_url}"
  57. wget $download_pkg_url
  58. fi
  59. if [ ! -f "redis-$version.tar.gz" ]; then
  60. echo ">>> download installation package fail!"
  61. exit 1
  62. fi
  63. tar -zxf redis-$version.tar.gz
  64. mv redis-$version $redis_basedir
  65. else
  66. echo "$redis_basedir have already exists"
  67. fi
  68. }
  69. generate_config_file(){
  70. mkdir -p $redis_datadir/data
  71. mkdir -p $redis_datadir/conf
  72. mkdir -p $redis_datadir/log
  73. mkdir -p $redis_datadir/run
  74. mkdir -p $redis_datadir/tmp
  75. set_maxmemory=$([ -n "$maxmemory" ] && echo "maxmemory $maxmemory" || echo "")
  76. set_maxmemory_policy=$([ -n "$memory_policy" ] && echo "maxmemory-policy $memory_policy" || echo "")
  77. set_requirepass=$([ -n "$init_pwd" ] && echo "requirepass $init_pwd" || echo "")
  78. cat > $redis_datadir/conf/redis.conf <<EOF
  79. bind 0.0.0.0
  80. protected-mode no
  81. port $port
  82. daemonize yes
  83. pidfile /data/redis$port/run/redis.pid
  84. logfile /data/redis$port/log/redis.log
  85. dir /data/redis$port/data
  86. appendonly yes
  87. $set_maxmemory
  88. $set_maxmemory_policy
  89. $set_requirepass
  90. EOF
  91. }
  92. manage_redis_by_systemd(){
  93. echo ">>> manage redis by systemd ..."
  94. egrep "^redis" /etc/group >& /dev/null || groupadd redis
  95. id redis &> /dev/null || useradd redis -g redis
  96. chown redis:redis -R $redis_datadir
  97. # 新增systemctl管理redis
  98. cat > /etc/systemd/system/redis$port.service << EOF
  99. [Unit]
  100. Description=redis Server
  101. After=network.target
  102. [Install]
  103. WantedBy=multi-user.target
  104. [Service]
  105. User=redis
  106. Group=redis
  107. Type=forking
  108. TimeoutSec=0
  109. PIDFile=$redis_datadir/run/redis.pid
  110. ExecStart=/usr/local/redis$version/src/redis-server /data/redis$port/conf/redis.conf
  111. EOF
  112. # 重新加载systemctl
  113. systemctl daemon-reload
  114. }
  115. install_redis_server(){
  116. echo ">>> install redis server in version $version ..."
  117. if [ -f "/usr/local/bin/redis-server" ]; then
  118. current_version=`redis-server -v|awk '{print $3}'`
  119. if [ "$current_version" != "v=$version" ]; then
  120. echo "watch out, exist old version $current_version, not deploy version $version, deploy version would override it!!"
  121. echo "watch out, exist old version $current_version, not deploy version $version, deploy version would override it!!"
  122. echo "watch out, exist old version $current_version, not deploy version $version, deploy version would override it!!"
  123. fi
  124. fi
  125. if [ ! -f "$redis_basedir/src/redis-server" ]; then
  126. cd $redis_basedir
  127. make
  128. cd src && make install
  129. fi
  130. }
  131. start_redis_service(){
  132. #启动redis服务(通过systemctl start|stop|status|restart redis$port管理)
  133. manage_redis_by_systemd
  134. echo ">>> start redis$port service ..."
  135. systemctl start redis$port
  136. systemctl enable redis$port
  137. systemctl status redis$port
  138. }
  139. deploy_redis_instance(){
  140. instance_exist_check
  141. download_redis_server
  142. generate_config_file
  143. install_redis_server
  144. start_redis_service
  145. exit 0
  146. }
  147. deploy_redis_instance

安装Redis(docker)

  1. #下载最新版镜像
  2. docker pull redis
  3. #下载指定版本镜像(推荐)
  4. docker pull redis:5.0.4
  5. #配置redis
  6. mkdir -p /docker/redis/6380/conf
  7. cat > /docker/redis/6380/conf/redis.conf <<EOF
  8. bind 0.0.0.0
  9. protected-mode no
  10. port $port
  11. daemonize yes
  12. pidfile /data/redis6380/run/redis.pid
  13. logfile /data/redis6380/log/redis.log
  14. dir /data/redis$port/data
  15. appendonly yes
  16. EOF
  17. #运行镜像
  18. docker run -p 6380:6379 --name redis6380 --privileged=true -v /docker/redis/6380/conf/redis.conf:/etc/redis/redis.conf -v /docker/redis/6380/data:/data -d redis:5.0.4 redis-server /etc/redis/redis.conf
  19. #查看容器
  20. docker ps
  21. #进入容器
  22. docker exec -it redis6380 /bin/bash
  23. #连接redis
  24. ./redis-cli
  25. set k1 v1

Redis主从同步

redis 可以通过 slaveof master_ip master_port 命令让当前实例成为master实例的从节点。该命令可以在从节点执行,也可以从节点的配置文件中加入。成为从节点后,从节点只提供读操作,不提供写操作。

  1. #方式一:命令行直接执行(实例重启后无法继续同步不推荐)
  2. 127.0.0.1:6380> slaveof 127.0.0.1 6379
  3. #方式二:配置文件配置(服务重启后仍继续同步,推荐)
  4. slaveof 127.0.0.1 6379

成为主从结构后,主节点数据发生改变时会自动同步到从节点。但是主节点挂了之后从节点无法自动变为主节点。如果主节点挂了,可以在从节点执行 slaveof no one 以使自己恢复写入。

为了让从节点能够在主节点异常后自动成为主节点,redis提供了哨兵模式和cluster模式。

Redis Cluster

Redis集群是一个由多个主从节点群组成的分布式服务集群,它具有复制、高可用和分片特性。Redis集群不需要sentinel哨兵也能完成节点移除和故障转移的功能。需要将每个节点设置成集群模式,这种集群模式没有中心节点,可水平扩展,据官方文档称可以线性扩展到上万个节点(官方推荐不超过1000个节点)。redis集群的性能和高可用性均优于之前版本的哨兵模式,且集群配置非常简单。redis集群的运用主要是针对海量数据+高并发+高可用的场景。

搭建三主三从的Redis集群示例:

  1. #修改部署脚本deploy_redis.sh:修改redis.conf配置,添加如下三行(开启集群模式)
  2. cluster-enabled yes
  3. cluster-config-file nodes.conf
  4. cluster-node-timeout 5000
  5. #部署启动6个实例(m1:6381,m2:6382,m3:6383作为master,s1:6384,s2:6385,s3:6386作为从)
  6. echo {6381,6382,6383,6384,6385,6386} |xargs -n 1 sh deploy_redis.sh
  7. #创建新集群命令:命令create,(redis5.0后,才可以使用cluster create命令构建,注意中途有询问需输入yes)
  8. /usr/local/redis5.0.4/src/redis-cli create --replicas 1 127.0.0.1:6381 127.0.0.1:6382 127.0.0.1:6383 127.0.0.1:6384 127.0.0.1:6385 127.0.0.1:6386
  9. #通过集群模式(-c)连接redis,并查看主从关系(cluster nodes)
  10. redis-cli -p 6381 -c
  11. >cluster nodes

redis cluster集群操作(新增/删除节点、分配槽位)

  1. #创建新的实例redis6387,6388,将6387其作为master加入原有的集群(注意配置文件中开启集群模式)
  2. echo {6387,6388} |xargs -n 1 sh deploy_redis.sh
  3. #通过check命令检查集群情况,可以查看到M-S节点和槽位情况
  4. redis-cli --cluster check 127.0.0.1:6381
  5. redis-cli --cluster add-node 127.0.0.1:6387 127.0.0.1:6381
  6. #检查集群情况,发现已经新增加了一个master6387,且上面还没有任何槽位:slots: (0 slots) master
  7. redis-cli --cluster check 127.0.0.1:6381
  8. #加入新master后,重新分配槽位reshard。输入4096(16384/4主)个槽位分给新节点(根据节点id),从all中拿(也可以从某个节点id拿),yes确认分配
  9. redis-cli --cluster reshard 127.0.0.1:6381
  10. #重新分配槽位后,检查槽位情况,发现是1-3号master节点都匀了4096/3个槽位给新节点
  11. redis-cli --cluster check 127.0.0.1:6381
  12. (可以看到6387的槽位是:slots:[0-1364],[5461-6826],[10923-12287] (4096 slots) master)
  13. #为新加入的6387主节点添加一个从节点6388
  14. redis-cli --cluster add-node 127.0.0.1:6388 127.0.0.1:6387 --cluster-slave --cluster-master-id 6387的id
  15. #集群缩容一组主从:1、删从节点 2、重新分配槽位 3、删除主节点
  16. #1.1、找到要删除的从节点id(过程中可以多次check)
  17. redis-cli --cluster check 127.0.0.1:6381
  18. #1.2、删除从节点6388
  19. redis-cli --cluster del-node 127.0.0.1:6388 6388-id
  20. #2、重新分配槽位。可以分三次分配,或者一次分配给一个节点
  21. redis-cli --cluster reshard 127.0.0.1:6381
  22. #3、删除主节点6387
  23. redis-cli --cluster del-node 127.0.0.1:6387 6387-id
  24. #免交互式方式扩缩容示例:
  25. #redis-cli --cluster reshard 127.0.0.1:6381 --cluster-from all --cluster-to $(redis-cli -c -p 6387 cluster nodes|awk '/1:6387/{print $1}') --cluster-slots 4096 --cluster-yes
  26. #redis-cli --cluster del-node 127.0.0.1:6388 $(redis-cli -c -h 127.0.0.1 -p 6388 cluster nodes|awk '/1:6398/{print $1}')

Redis 使用

Redis 集成到 springboot,只需简单几步。

第一步:引入依赖、配置地址

默认使用lettuce,这里使用jedis,并使用redis作为cache(可选)

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-data-redis</artifactId>
  4. <exclusions>
  5. <exclusion>
  6. <groupId>io.lettuce</groupId>
  7. <artifactId>lettuce-core</artifactId>
  8. </exclusion>
  9. </exclusions>
  10. </dependency>
  11. <dependency>
  12. <groupId>redis.clients</groupId>
  13. <artifactId>jedis</artifactId>
  14. </dependency>
  15. <dependency>
  16. <groupId>org.springframework.boot</groupId>
  17. <artifactId>spring-boot-starter-cache</artifactId>
  18. </dependency>
  19. <!-- application.yml中:-->
  20. spring:
  21. redis:
  22. host: jvxb.com
  23. port: 6379
  24. #没有密码则无需配置
  25. password: 123456
  26. #配置jedis客户端
  27. jedis:
  28. pool:
  29. # 连接池最大连接数 默认8 ,负数表示没有限制
  30. max-active: 20
  31. # 连接池中的最大空闲连接 默认8
  32. max-idle: 10
  33. # 连接池中的最小空闲连接 默认0
  34. min-idle: 5
  35. # 连接池最大阻塞等待时间(使用负值表示没有限制) 默认-1
  36. max-wait: -1ms
  37. timeout: 5000ms #连接超时时间(毫秒)

第二步:配置序列化方式、缓存组

不设置序列化方式的话,通过redisTemplate存入redis时格式不是很好辨认,通过序列化,我们可以更方便地查找到具体key和value;再通过缓存组可以少写大量重复的写缓存/查缓存代码。

  1. import org.springframework.cache.CacheManager;
  2. import org.springframework.cache.annotation.EnableCaching;
  3. import org.springframework.context.annotation.Bean;
  4. import org.springframework.context.annotation.Configuration;
  5. import org.springframework.data.redis.cache.RedisCacheConfiguration;
  6. import org.springframework.data.redis.cache.RedisCacheManager;
  7. import org.springframework.data.redis.connection.RedisConnectionFactory;
  8. import org.springframework.data.redis.core.RedisTemplate;
  9. import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
  10. import org.springframework.data.redis.serializer.RedisSerializationContext;
  11. import org.springframework.data.redis.serializer.StringRedisSerializer;
  12. import java.time.Duration;
  13. /**
  14. * 1)redis序列化配置,Object序列化为Json,比自带的jdk序列化方式,具有更快的速度
  15. * 这个主要是根据redis存储的数据类型需求决定,key一般都是String,但是value可能不一样,一般有两种,String和 Object;
  16. * 如果k-v都是String类型,我们可以直接用 StringRedisTemplate,这个是官方建议的,也是最方便的,直接导入即用,无需多余配置!
  17. * 如果k-v是Object类型,则需要自定义 RedisTemplate
  18. *
  19. * 2)配置cache
  20. *
  21. * @author jvxb
  22. * @since 202-12-26
  23. */
  24. @Configuration
  25. @EnableCaching
  26. public class RedisConfig {
  27. /**
  28. * 使用方式:直接引入
  29. * @Autowired
  30. * RedisTemplate<String, Object> redisTemplate
  31. */
  32. @Bean
  33. public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
  34. StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
  35. GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
  36. RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
  37. // key采用String的序列化方式
  38. redisTemplate.setKeySerializer(stringRedisSerializer);
  39. // value序列化方式采用jackson
  40. redisTemplate.setValueSerializer(genericJackson2JsonRedisSerializer);
  41. // hash的key也采用String的序列化方式
  42. redisTemplate.setHashKeySerializer(stringRedisSerializer);
  43. //hash的value序列化方式采用jackson
  44. redisTemplate.setHashValueSerializer(genericJackson2JsonRedisSerializer);
  45. redisTemplate.setConnectionFactory(redisConnectionFactory);
  46. return redisTemplate;
  47. }
  48. /**
  49. * 缓存管理器
  50. * 相关注解:
  51. * @Cacheable:使用缓存。在方法执行前如果有数据,则直接返回缓存数据;没有则调用方法并将方法返回值放进缓存。
  52. * @CachePut:更新缓存,将方法的返回值放到缓存中
  53. * @CacheEvict:清空缓存
  54. * 相关配置:
  55. * key:缓存key,可以不指定,默认为方法参数
  56. * value/cacheNames: 缓存key的前缀,必须指定
  57. * condition:调用前判断,缓存的条件。(condition为false时不进行缓存,默认true)
  58. * unless:执行后判断,不缓存的条件。 (unless为true时不进行缓存,默认false)
  59. * 相应示例:
  60. * @Cacheable(value = "menuById")
  61. * @Cacheable(value = "menuById", key = "#id")
  62. * @Cacheable(value = "menuById", key = "'id-' + #id", condition = "#id != null", unless = "#result == null")
  63. * @CachePut(value = "newMenu", key = "#id")
  64. * @CacheEvict(value = "menuById", key = "#id")
  65. */
  66. @Bean
  67. public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
  68. RedisCacheConfiguration defaultCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
  69. .entryTtl(Duration.ofHours(1))
  70. .serializeKeysWith(
  71. RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
  72. .serializeValuesWith(RedisSerializationContext.SerializationPair
  73. .fromSerializer(new GenericJackson2JsonRedisSerializer()))
  74. .computePrefixWith(name -> name + ":");
  75. RedisCacheManager.RedisCacheManagerBuilder builder = RedisCacheManager.RedisCacheManagerBuilder
  76. .fromConnectionFactory(redisConnectionFactory).cacheDefaults(defaultCacheConfiguration);
  77. return builder.build();
  78. }
  79. }

第三步:使用redisTemplate & 缓存组功能

  1. import org.springframework.beans.factory.annotation.Autowired;
  2. import org.springframework.cache.annotation.CacheEvict;
  3. import org.springframework.cache.annotation.CachePut;
  4. import org.springframework.cache.annotation.Cacheable;
  5. import org.springframework.data.redis.core.RedisTemplate;
  6. import org.springframework.web.bind.annotation.GetMapping;
  7. import org.springframework.web.bind.annotation.RequestMapping;
  8. import org.springframework.web.bind.annotation.RestController;
  9. /**
  10. * Redis控制器
  11. *
  12. * @author jvxb
  13. * @since 2022-12-26
  14. */
  15. @RestController
  16. @RequestMapping("redis")
  17. public class RedisController {
  18. @Autowired
  19. RedisTemplate<String, Object> redisTemplate;
  20. @GetMapping("/set")
  21. public Object set(String key, String value) {
  22. redisTemplate.opsForValue().set(key, value);
  23. return "success";
  24. }
  25. @GetMapping("/get")
  26. public Object get(String key) {
  27. Object value = redisTemplate.opsForValue().get(key);
  28. return value;
  29. }
  30. @GetMapping("/del")
  31. public Object del(String key) {
  32. Object value = redisTemplate.delete(key);
  33. return value;
  34. }
  35. @GetMapping("/hset")
  36. public Object hset(String key, String hashKey, String value) {
  37. redisTemplate.opsForHash().put(key, hashKey, value);
  38. return "success";
  39. }
  40. @GetMapping("/hget")
  41. public Object hget(String key, String hashKey) {
  42. Object value = redisTemplate.opsForHash().get(key, hashKey);
  43. return value;
  44. }
  45. @GetMapping("/cacheable")
  46. @Cacheable(value = "cacheTest", key = "#id")
  47. public String cacheable(String id) {
  48. System.out.println("执行方法-save/find缓存");
  49. if (id == null) return "null id";
  50. return "your id = " + id;
  51. }
  52. @GetMapping("/cachePut")
  53. @CachePut(value = "cacheTest", key = "#id")
  54. public String cachePut(String id) {
  55. System.out.println("执行方法-save缓存");
  56. if (id == null) return "null id";
  57. return "your id = " + id;
  58. }
  59. @GetMapping("cacheEvict")
  60. @CacheEvict(value = "cacheTest", key = "#id")
  61. public String cacheEvict(String id) {
  62. System.out.println("执行方法-evict缓存");
  63. if (id == null) return "null id";
  64. return "your id = " + id;
  65. }
  66. }


记录-Nginx

相关视频教程:

尚硅谷Nginx教程(亿级流量nginx架构设计)_哔哩哔哩_bilibili

安装nginx

  1. #通过yum方式安装nginx
  2. yum install nginx -y
  3. #查看版本
  4. nginx -v
  5. #启动nginx
  6. systemctl start nginx
  7. #访问80端口
  8. curl 127.0.0.1
  9. #yum方式安装的默认地址和配置的默认地址
  10. /etc/nginx/nginx.conf //yum方式安装后默认配置文件的路径
  11. /usr/share/nginx/html //nginx网站默认存放目录
  12. /usr/share/nginx/html/index.html //网站默认主页路径
  13. 拓展:nginx基本操作
  14. nginx -v //查看nginx版本
  15. nginx -t //检查nginx.conf配置是否正常
  16. yum -y instal nginx //安装nginx
  17. systemctl start nginx //启动nginx
  18. systemctl stop nginx //停止nginx
  19. systemctl reload nginx //重载nginx
  20. #找出非法ip
  21. cat /var/log/nginx/access.log | awk -F\" '{A[$(NF-1)]++}END{for(k in A)print A[k],k}' | sort -n |tail
  22. 122 58.144.7.66
  23. 337 106.91.201.75
  24. 2270 122.200.77.170 #显然这个ip不正常,而且这不是nginx所知道的真实ip,而是$http_x_forwarded_for变量

常用配置

  1. #设定首页(例如vue)
  2. server {
  3. listen 80;
  4. server_name localhost;
  5. location / {
  6. root /usr/share/nginx/dist;
  7. index index.html;
  8. }
  9. }
  10. #简单转发
  11. server {
  12. listen 80;
  13. location /mapi {
  14. rewrite ^/mapi/(.*)$ /$1 break;
  15. proxy_pass http://localhost:8080;
  16. }
  17. }
  18. #upstream转发(默认轮询,可设置权重、重试次数、超时时间)
  19. upstream backend_server {
  20. server 127.0.0.1:8080;
  21. server 127.0.0.1:8081;
  22. server 127.0.0.1:8082 max_fails=3 fail_timeout=30s;
  23. }
  24. server {
  25. listen 80;
  26. server_name localhost;
  27. location /mapi {
  28. rewrite ^/mapi/(.*)$ /$1 break;
  29. proxy_pass http://backend_server;
  30. proxy_set_header Host $http_host;
  31. proxy_set_header Cookie $http_cookie;
  32. proxy_set_header X-Real-IP $remote_addr;
  33. proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  34. }
  35. }
  36. #443转发到80端口
  37. server {
  38. listen 443;
  39. server_name localhost;
  40. root /data/front_page/dist;
  41. ssl off;
  42. rewrite ^(.*)$ http://l27.0.0.1/$1 permanent;
  43. }


记录-ES

相关视频教程:

【尚硅谷】ElasticSearch教程入门到精通(基于ELK技术栈elasticsearch 7.x+8.x新特性)_哔哩哔哩_bilibili

推荐安装es 6.2.2 、es 6.8.12 、 es 7.6.2版本以下以springboot2.3配套的7.6.2版本为记录。

在这里插入图片描述

安装es

  1. cd /tmp/install_pkg
  2. wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-7.6.2-linux-x86_64.tar.gz
  3. tar -vxf elasticsearch-7.6.2-linux-x86_64.tar.gz
  4. mv elasticsearch-7.6.2 /usr/local/es7.6.2
  5. #新增es用户并授权
  6. groupadd es
  7. useradd es
  8. chown -R es /usr/local/es7.6.2
  9. #后台启动(不能以root用户,先切到es用户,后台启动 -d)
  10. su es
  11. /usr/local/es7.6.2/bin/elasticsearch -d
  12. #访问测试
  13. curl localhost:9200
  14. #注:默认情况下,es只限本机访问,通过配置放开
  15. echo "http.host: 0.0.0.0" >> /usr/local/es7.6.2/config/elasticsearch.yml
  16. #也可以指定http端口或tcp端口
  17. echo "http.port: 9200" >> /usr/local/es7.6.2/config/elasticsearch.yml
  18. echo "transport.tcp.port: 9300" >> /usr/local/es7.6.2/config/elasticsearch.yml
  19. #启动错误
  20. 1、启动若出现报错:[1]: max file descriptors [65535] for elasticsearch process is too low, increase to at least [65536]
  21. 编辑 /etc/security/limits.conf 新增以下配置并保存退出:
  22. es soft nofile 65536
  23. es hard nofile 65536
  24. ulimit -Hn 命令可以查看当前用户的最大文件数,如果esdmin的最大文件数没改过来,则需要退出当前用户重新登录,重新登录后可发现文件数已经修改)
  25. 2、启动若出现报错:[1]: max virtual memory areas vm.max_map_count [65530] is too low, increase to at least [262144]
  26. 可在/etc/sysctl.conf中添加一行:vm.max_map_count=262144
  27. 并使其执行生效:sysctl -p /etc/sysctl.conf
  28. 3、若本地能访问,外网无法访问,可能要打开防火墙
  29. firewall-cmd --zone=public --list-ports
  30. firewall-cmd --zone=public --add-port=9200/tcp --permanent
  31. firewall-cmd --zone=public --add-port=9300/tcp --permanent
  32. firewall-cmd --reload

es多实例安装脚本(deploy_es.sh)参考以下:

  1. #!/bin/bash
  2. #初始化约定:
  3. #安装版本:6.8.12 / 7.6.2(默认)
  4. #安装端口:9200
  5. #安装目录(basedir):/usr/local/es7.6.2
  6. #数据目录(datadir):/data/es$port,即默认为 /data/es9200
  7. #配置文件:存于对应数据目录下conf/es.conf,即默认为 /data/es9200/conf/es.conf
  8. #
  9. #输入参数:
  10. #$1:端口号:如不输入,则使用9200端口。
  11. #如:./es_deploy.sh 或 ./es_deploy.sh 9201
  12. #
  13. #输出:
  14. #success:active (running)
  15. #fail:部署失败原因
  16. #步骤1:根据是否存在basedir来判断是否第一次安装。如果是第一次安装则下载安装es6.8.12。
  17. #步骤2:根据输入端口判断对应的datadir是否存在,存在则不允许安装,端口被占用也也不允许安装
  18. #步骤3:生成配置文件,开始安装并通过systemd启动
  19. #步骤4:通过systemd管理es。即 systemctl start|stop|restart|enable|disable es${port}
  20. #卸载es
  21. #卸载某实例
  22. #systemctl stop es${port}
  23. #rm -rf $es_datadir /etc/systemd/system/es${port}
  24. #全部卸载
  25. # rm -rf /tmp/install_pkg/es* /usr/local/es* /data/es* /etc/systemd/system/es*
  26. ###########################################################################################
  27. default_port=9200
  28. port=$([ -n "$1" ] && echo "$1" || echo "$default_port")
  29. tcp_port=`expr $port + 100`
  30. #version=6.8.12
  31. version=7.6.2
  32. pkg_suffix=$([ ${version:0:1} -ge '7' ] && echo "-linux-x86_64" || echo "")
  33. maxmemory=1024M
  34. download_pkg_url=https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-$version$pkg_suffix.tar.gz
  35. es_basedir=/usr/local/es$version
  36. es_datadir=/data/es$port
  37. instance_exist_check(){
  38. #检查该端口对应的实例datadir是否存在,检查该端口是否被占用
  39. port_datadir=/data/es${port}
  40. if [ -d ${port_datadir} ];then
  41. if [ "`ls -A ${port_datadir}`" != "" ];then
  42. echo "fail, ${port_datadir} is not empty"
  43. exit 1
  44. fi
  45. fi
  46. lsof -i:${port} >/dev/null 2>&1
  47. if [ $? -eq 0 ];then
  48. echo "fail, http port ${port} is used"
  49. exit 1
  50. fi
  51. lsof -i:${tcp_port} >/dev/null 2>&1
  52. if [ $? -eq 0 ];then
  53. echo "fail, tcp port ${tcp_port} is used"
  54. exit 1
  55. fi
  56. }
  57. download_es_server() {
  58. #下载es安装包
  59. echo '>>> check whether the es-server installation package exists ...'
  60. if [ ! -d "$es_basedir" ]; then
  61. mkdir -p /tmp/install_pkg && cd /tmp/install_pkg
  62. if [ ! -f "elasticsearch-$version$pkg_suffix.tar.gz" ]; then
  63. echo ">>> download installation package from ${download_pkg_url}"
  64. wget $download_pkg_url
  65. fi
  66. if [ ! -f "elasticsearch-$version$pkg_suffix.tar.gz" ]; then
  67. echo ">>> download installation package fail!"
  68. exit 1
  69. fi
  70. tar -zxf elasticsearch-$version$pkg_suffix.tar.gz
  71. mv elasticsearch-$version $es_basedir
  72. else
  73. echo "$es_basedir have already exists"
  74. fi
  75. }
  76. generate_config_file(){
  77. #沿用解压后的文件即可
  78. cp -r $es_basedir $es_datadir
  79. #修改1:集群名称、允许外网访问
  80. cat >> $es_datadir/config/elasticsearch.yml <<EOF
  81. discovery.type: single-node
  82. network.host: 0.0.0.0
  83. http.port: $port
  84. transport.tcp.port: $tcp_port
  85. path.data: $es_datadir/data
  86. path.logs: $es_datadir/logs
  87. http.cors.enabled: true
  88. http.cors.allow-origin: "*"
  89. bootstrap.memory_lock: false
  90. bootstrap.system_call_filter: false
  91. EOF
  92. #修改2:es jvm内存(按需分配,默认1g,官方建议是主机总内存的一半,且不超过32g)
  93. sed -i -e "s/-Xms1g/-Xms$maxmemory/" -e "s/-Xmx1g/-Xmx$maxmemory/" $es_datadir/config/jvm.options
  94. }
  95. manage_es_by_systemd(){
  96. echo ">>> manage es by systemd ..."
  97. egrep "^es" /etc/group >& /dev/null || groupadd es
  98. id es &> /dev/null || useradd es -g es
  99. chown es:es -R $es_datadir
  100. # 新增systemctl管理es
  101. cat > /etc/systemd/system/es$port.service << EOF
  102. [Unit]
  103. Description=es Server
  104. After=network.target
  105. [Install]
  106. WantedBy=multi-user.target
  107. [Service]
  108. User=es
  109. Group=es
  110. Type=simple
  111. TimeoutSec=0
  112. LimitNOFILE=100000
  113. LimitNPROC=100000
  114. ExecStart=$es_datadir/bin/elasticsearch
  115. EOF
  116. # 重新加载systemctl
  117. systemctl daemon-reload
  118. }
  119. install_es_server(){
  120. #无需安装,但是需要修改一些参数。
  121. vm_count=`cat /etc/sysctl.conf|grep vm.max_map_count`
  122. if [ ! -n "$vm_count" ];then
  123. echo 'vm.max_map_count=262144' >> /etc/sysctl.conf
  124. /usr/sbin/sysctl -p /etc/sysctl.conf >& /dev/null
  125. fi
  126. }
  127. start_es_service(){
  128. #启动es服务(通过systemctl start|stop|status|restart es$port管理)
  129. manage_es_by_systemd
  130. echo ">>> start es$port service ..."
  131. systemctl start es$port
  132. systemctl enable es$port
  133. systemctl status es$port
  134. }
  135. deploy_es_instance(){
  136. instance_exist_check
  137. download_es_server
  138. generate_config_file
  139. install_es_server
  140. start_es_service
  141. exit 0
  142. }
  143. deploy_es_instance

安装es(docker)

  1. sudo docker pull elasticsearch:7.6.2
  2. sudo mkdir -p /data/docker/es9200/config
  3. sudo mkdir -p /data/docker/es9200/data
  4. sudo mkdir -p /data/docker/es9200/plugins
  5. echo -e "discovery.type: single-node\nnetwork.host: 0.0.0.0" >> /data/docker/es9200/config/elasticsearch.yml
  6. #授权(生产环境不建议777,须通过其他授权)
  7. chmod -R 777 /data/docker/es9200
  8. sudo docker run --name es9200 \
  9. -p 9200:9200 -p 9300:9300 \
  10. -e ES_JAVA_OPTS="-Xms128m -Xmx128m" \
  11. -v /data/docker/es9200/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml \
  12. -v /data/docker/es9200/data:/usr/share/elasticsearch/data \
  13. -v /data/docker/es9200/plugins:/usr/share/elasticsearch/plugins \
  14. -d elasticsearch:7.6.2

es集群

模拟三实例的集群(默认使用内存为1G,测试时如内存紧张可以修改为256M等)

echo 9201 9202 9203 | xargs -n 1 sh deploy_es.sh

es7的集群模式需要在配置文件加上集群相关的配置:cluster.name、node.name、cluster.initial_master_nodes、discovery.seed_hosts 等。es启动时会根据seed_hosts中的节点地址彼此发现组成集群。并且谁先启动谁就会成为主节点。

注:es6时对应配置为discovery.zen.minimum_master_nodes、discovery.zen.ping.unicast.hosts

  1. #删除原有实例的nodes
  2. rm -rf /data/es920{1,2,3}/data/nodes
  3. #添加实例的集群配置
  4. sed -i -e '/discovery.type/d' -e "\$acluster.name: es_cluster\nnode.name: 127.0.0.1:9301\ncluster.initial_master_nodes: [\"127.0.0.1:9301\", \"127.0.0.1:9302\", \"127.0.0.1:9303\"]\ndiscovery.seed_hosts: [\"127.0.0.1:9301\", \"127.0.0.1:9302\", \"127.0.0.1:9303\"]" /data/es9201/config/elasticsearch.yml
  5. sed -i -e '/discovery.type/d' -e "\$acluster.name: es_cluster\nnode.name: 127.0.0.1:9302\ncluster.initial_master_nodes: [\"127.0.0.1:9301\", \"127.0.0.1:9302\", \"127.0.0.1:9303\"]\ndiscovery.seed_hosts: [\"127.0.0.1:9301\", \"127.0.0.1:9302\", \"127.0.0.1:9303\"]" /data/es9202/config/elasticsearch.yml
  6. sed -i -e '/discovery.type/d' -e "\$acluster.name: es_cluster\nnode.name: 127.0.0.1:9303\ncluster.initial_master_nodes: [\"127.0.0.1:9301\", \"127.0.0.1:9302\", \"127.0.0.1:9303\"]\ndiscovery.seed_hosts: [\"127.0.0.1:9301\", \"127.0.0.1:9302\", \"127.0.0.1:9303\"]" /data/es9203/config/elasticsearch.yml
  7. #最终elasticsearch.yml配置如下:
  8. network.host: 0.0.0.0
  9. http.port: 9201
  10. transport.tcp.port: 9301
  11. path.data: /data/es9201/data
  12. path.logs: /data/es9201/logs
  13. http.cors.enabled: true
  14. http.cors.allow-origin: "*"
  15. bootstrap.memory_lock: false
  16. bootstrap.system_call_filter: false
  17. cluster.name: es_cluster
  18. node.name: 127.0.0.1:9301
  19. cluster.initial_master_nodes: ["127.0.0.1:9301", "127.0.0.1:9302", "127.0.0.1:9303"]
  20. discovery.seed_hosts: ["127.0.0.1:9301", "127.0.0.1:9302", "127.0.0.1:9303"]
  21. #重启实例(重启后自动构成集群)
  22. systemctl restart es920{1,2,3}
  23. #查看集群节点
  24. curl http://localhost:9201/_cat/nodes
  25. #回收集群
  26. systemctl stop es920{1,2,3}
  27. systemctl disable es920{1,2,3}
  28. rm -rf /data/es920{1,2,3}
  29. rm -rf /etc/systemd/system/es920{1,2,3}.service

默认所有节点都有权限成为主节点和读写磁盘。对应权限可以通过 node.master: true/false,node.data: true/false来控制。(条件允许可以设置主节点不为数据节点,提示效率)

es kibana

Kibana是一个针对Elasticsearch的开源分析及可视化平台,用来搜索、查看、处理存储在Elasticsearch索引中的数据。使用Kibana,可以通过各种图表进行高级数据分析及展示。

  1. #0、注意:kibana的安装前提是需要有jdk和node.js
  2. #1、下载安装(最好与es同一版本,否则可能不兼容)
  3. wget https://artifacts.elastic.co/downloads/kibana/kibana-7.6.2-linux-x86_64.tar.gz
  4. tar -zxvf kibana-7.6.2-linux-x86_64.tar.gz
  5. mv kibana-7.6.2-linux-x86_64 /usr/local/kibana7.6.2
  6. #2、修改kibana端口号(默认5601)、host(默认localhost)、连接es(默认localhost:9200)、语言(默认英文)
  7. cat >> /usr/local/kibana7.6.2/config/kibana.yml <<EOF
  8. server.port: 5601
  9. server.host: "改为机器ip(内网),否则远程连接不了"
  10. elasticsearch.hosts: ["http://localhost:9201"]
  11. i18n.locale: "zh-CN"
  12. EOF
  13. #3、启动
  14. cd /usr/local/kibana7.6.2/bin
  15. nohup ./kibana --allow-root &
  16. #4、停止(实际是node服务)
  17. ps -ef|grep node
  18. 找到对应pid后 kill -9
  19. #页面访问,通过开发者工具【Dev-Tools】可以快速增删改查数据
  20. 机器ip(外网):5601

kibana除开发者工具[【Dev Tools】外,它的 发现【Discover】、可视化【Visualize】、仪表盘【Dashboards】、日志【logs】功能,都是及其强大且好用的,此处不做展开。

 es CURD

在es7之前,索引(index)支持多种type,所以索引相当于是一个数据库,type相当于是一张表,type下的document相当于表中的数据。

在es7后取消了type的概念(每个索引只有一个type = _doc)。索引就相当于是一张表,mapping相当于表结构,doucoument相当于是表中的数据。

假设es对应的表结构如下:

  1. CREATE TABLE `user` (
  2. `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键id',
  3. `user_name` varchar(50) NOT NULL COMMENT '用户名',
  4. `age` int(11) DEFAULT NULL COMMENT '年龄',
  5. `phone` varchar(50) DEFAULT NULL COMMENT '手机号码',
  6. `gender` tinyint(1) DEFAULT '3' COMMENT '性别:1男 2女 3未知',
  7. `birthday` date DEFAULT NULL COMMENT '生日',
  8. `remark` text DEFAULT NULL COMMENT '用户标记',
  9. `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  10. `update_time` datetime DEFAULT NULL COMMENT '修改时间',
  11. PRIMARY KEY (`id`)
  12. ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='用户表';

则es通过http接口(默认localhost:9200)进行增删改查操作如下:

快速插入(默认自动生成_id)

  1. POST /user/_doc
  2. {
  3. "id" : 1,
  4. "user_name" : "张三",
  5. "phone" : "13333333333",
  6. "gender" : 1,
  7. "birthday" : "2012-12-12",
  8. "remark": "法外狂徒",
  9. "create_time": "2022-12-12 13:13:13"
  10. }

快速插入/修改(指定_id,存在相同id则更新)

  1. PUT /user/_doc/2
  2. {
  3. "id" : 2,
  4. "user_name" : "李四",
  5. "phone" : "13444444444",
  6. "gender" : 2,
  7. "birthday" : "2012-12-14",
  8. "remark": "良好市民",
  9. "create_time": "2022-12-14 14:14:14"
  10. }

快速查询索引情况(别名aliases、映射mappings、设置setting)

GET /user

 快速查询文档(整个索引)

GET /user/_search

 快速查询文档(根据_id)

GET /user/_doc/2

快速删除(根据_id)

DELETE /user/_doc/2

快速删除(根据索引)

DELETE /user

复杂查询(dsl语法)

  1. POST /user/_search
  2. {
  3. "query": {
  4. "bool": {
  5. "must": [
  6. {
  7. "term": {
  8. "remark.keyword": "法外狂徒"
  9. }
  10. }
  11. ]
  12. }
  13. }
  14. }

常用dsl语法如下:

  1. {
  2. "query": {
  3. "bool": {
  4. "must": [
  5. {
  6. "terms": {
  7. "text_filed_name.keyword": [
  8. "value1",
  9. "value2"
  10. ]
  11. }
  12. },
  13. {
  14. "term": {
  15. "keyword_filed_name1": "value3"
  16. }
  17. },
  18. {
  19. "exists": {
  20. "field": "field_name"
  21. }
  22. }
  23. {
  24. "range": {
  25. "timestamp": {
  26. "from": 1658419200000,
  27. "to": 1668419200000
  28. }
  29. }
  30. }
  31. ],
  32. "must_not": [
  33. {
  34. "term": {
  35. "field_name4": "value4"
  36. }
  37. },
  38. {
  39. "exists": {
  40. "field": "field_name"
  41. }
  42. }
  43. ],
  44. "should": [
  45. "match": {
  46. "filed_name": "将词分割开来,匹配倒排索引,查找包含任意分割的词的字段"
  47. },
  48. "match_phrase": {
  49. "filed_name": "将词分割开来,匹配倒排索引,查找这个短语(必须同时包含)。"
  50. },
  51. {
  52. "wildcard": {
  53. "filed_name": "*value(text时加.keyword)*"
  54. }
  55. }
  56. ]
  57. }
  58. },
  59. "aggs": {
  60. "keywrod_filed_name": {
  61. "terms": {
  62. "field": "keywrod_filed_name"
  63. }
  64. }
  65. },
  66. "from": 0,
  67. "size": 100,
  68. "sort": [ { "field_name": "desc" } ]
  69. }

可见dsl语法还是比较难用且难记的。但是我们可以通过 sql 的形式来进行查询

  1. #方式一(推荐):使用rest风格查询
  2. POST /_xpack/sql
  3. {
  4. "query": "show tables"
  5. }
  6. POST /_xpack/sql?format=json
  7. {
  8. "query": "SELECT * FROM user ORDER BY id DESC LIMIT 5"
  9. }
  10. #将sql转为dsl语法
  11. POST /_xpack/sql/translate
  12. {
  13. "query": "SELECT * FROM user ORDER BY id DESC LIMIT 5"
  14. }
  15. #方式一在7.x后已过时,简化为:
  16. POST _sql
  17. {
  18. "query": "SELECT * FROM user ORDER BY time DESC LIMIT 5"
  19. }
  20. POST _sql/translate
  21. {
  22. "query": "SELECT * FROM user ORDER BY time DESC LIMIT 5"
  23. }
  24. #方式二:使用SQL CLI,启动后可以直接输入sql
  25. /data/es9201/bin/elasticsearch-sql-cli http://localhost:9201

es 中文插件

在中文数据检索场景中,为了提供更好的检索效果,需要在ES中集成中文分词器,因为ES默认是按照英文的分词规则进行分词的,当文本是中文时基本上都是单字分词,对中文分词效果不理想。

  1. #未安装中文分词器前,可以看到分词的结果是按单字来分词,效果不理想
  2. POST /_analyze
  3. {
  4. "text": "这里是一些需要分词的内容"
  5. }

ES常用的中文分词器有hanlp、ansj、结巴、IK,其中IK分词器会比较简单易用一些,也是大多数公司的选择,下面记录在ES中如何集成IK这个中文分词器。

  1. #前往github:
  2. https://github.com/medcl/elasticsearch-analysis-ik/releases/tag
  3. #具体下载地址
  4. wget https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.6.2/elasticsearch-analysis-ik-7.6.2.zip
  5. #上传到服务器,解压(所有实例都需要有)
  6. mkdir -p /data/es920{1,2,3}/plugins/ik
  7. unzip elasticsearch-analysis-ik-7.6.2.zip -d /data/es9201/plugins/ik
  8. unzip elasticsearch-analysis-ik-7.6.2.zip -d /data/es9202/plugins/ik
  9. unzip elasticsearch-analysis-ik-7.6.2.zip -d /data/es9203/plugins/ik
  10. #重启服务
  11. systemctl restart es9201
  12. #安装中文分词器后,可以看到分词的结果比较合理的(IK分词器有两种分词模式:ik_max_word 和 ik_smart 模式。ik_max_word将文本做最细粒度拆分,ik_smart将文本做最粗粒度拆分)。
  13. POST /_analyze
  14. {
  15. "analyzer": "ik_smart",
  16. "text": "这里是一些需要分词的内容"
  17. }

自定义词库:

  1. #自定义词库(一行一个)
  2. vim /data/es9201/plugins/ik/config/my.dic
  3. 抓娃小兵
  4. 只因你太美
  5. #载入自定义词库
  6. vim /data/es9201/plugins/ik/config/IKAnalyzer.cfg.xml
  7. #用户可以在这里配置自己的扩展字典
  8. <entry key="ext_dict">my.dic</entry>

es 索引

索引可以说是es中最重要的内容。索引是一个虚拟的空间,类似于关系型数据库的table。一个索引至少由一个分片组成,索引可以包含一个主分片和多个副本分片。

当我们查看一个索引时,可以看到其 aliases(别名)mappings(映射)settings(设置)信息。

  1. GET /user
  2. {
  3. "user": {
  4. "aliases": {},
  5. "mappings": {
  6. ...
  7. },
  8. "settings": {
  9. "index": {
  10. "creation_date": "1671538197644",
  11. "number_of_shards": "5",
  12. "number_of_replicas": "1",
  13. "uuid": "eaXIsxVhTb2hZitr9vDZ2A",
  14. "version": {
  15. "created": "6082399"
  16. },
  17. "provided_name": "user"
  18. }
  19. }
  20. }
  21. }

索引无需提前创建,第一条数据插入即可创建完成(使用场景:非严格数据模型限制规范的场景,如日志、监控,默认字段类型为long、text)。如果确定了数据模型,最好提前创建好索引(指定别名、映射、分片等)。需要注意的是,索引建立后,分片个数 和 mapping 不可以更改。对于mapping只可以新增字段,而不能修改或删除字段。

索引命名(建议)

  • 业务类型命名:名称 + 数字版本号
  • 日志类型命名:名称 + 时间

分片和副本区别

当分片设置为5,数据量为30G时,es会自动帮我们把数据均衡地分配到5个分片上,即每个分片大概有6G数据,当你查询数据时,es会把查询发送给每个相关的分片,并将结果组合在一起。(es6中默认分片数是5,es7中默认分片数是1)

而副本,就是对分布在5个分片的数据进行复制。因为分片是把数据进行分割而已,数据依然只有一份,这样的目的是保障查询的高效性,副本则是多复制几份分片的数据,这样的目的是保障数据的高可靠性,防止数据丢失。es中默认副本数为1。

索引别名

ES中可以为索引添加别名,一个别名可以指向到多个索引中,同时在添加别名时可以设置筛选条件,指向一个索引的部分数据,实现在关系数据库汇总的视图功能,这就是ES中别名的强大之处。

只要有可能,尽量使用别名,推荐为es的每个索引都使用别名,因为在未来重建索引的时候,别名会赋予你更多的灵活性(修改分片/mapping)。别名的常见应用如下:

  • 实现正在运行的集群上的一个索引到另一个索引之间的无缝切换。试想一下这种场景,由于业务变换,由于业务原因或索引字段修改,我们需要将业务数据从原有索引1变换到新的索引2上,如果没有别名,我们必须修改和中断业务系统,但是有了别名,只需要修改别名,另起指向新的索引2即可,这样的操作可以在用户无任何感知的情况下完成。
  • 使数据检索等操作更加方便。假如有两个月的日志数据,分别存放在index_202208和index_202209两个索引中,没有使用别名进行检索时,我们需要同时写上两个索引名称进行检索,使用索引后,我们可以令别名同时指向这两个索引,检索时只需要使用这个别名就可以同时在两个索引中尽心检索。
  • 为一个索引中的部分数据创建别名,例如,一个索引中存放了一整年的数据,现在新增一个业务场景,更多的是对其中某一个月的数据进行检索,这时,我们可以在创建别名时,通过设置过滤条件filter,可以单独令别名指向一个月的数据,使得检索更加高效。

利用别名,进行索引切换演示:

  1. //1)假设原有索引为 myidx_v1
  2. POST myidx_v1
  3. {
  4. "id": 1,
  5. "name": "张三",
  6. "remark": "法外狂徒",
  7. "phone": "13300003333",
  8. "create_time": "2022-12-13 13:13:13",
  9. "birth": "2022-12-23"
  10. }
  11. //2)为myidx_v1设置别名
  12. POST _aliases
  13. {
  14. "actions": [
  15. {
  16. "add": {
  17. "index": "myidx_v1",
  18. "alias": "myidx"
  19. }
  20. }
  21. ]
  22. }
  23. //3)新增索引 myidx_v2 (修改部分字段类型及分片数)
  24. GET myidx_v1
  25. #拿到原索引信息后,新索引中将部分字段设为keyword、将remark字段设为ik分词、并将分片数量设为3
  26. PUT myidx_v2
  27. {
  28. "aliases": {},
  29. "mappings": {
  30. "_doc": {
  31. "properties": {
  32. "birth": {
  33. "type": "date"
  34. },
  35. "create_time": {
  36. "type": "keyword"
  37. },
  38. "id": {
  39. "type": "long"
  40. },
  41. "name": {
  42. "type": "text",
  43. "analyzer": "ik_max_word"
  44. },
  45. "phone": {
  46. "type": "keyword"
  47. },
  48. "remark": {
  49. "type": "text",
  50. "analyzer": "ik_smart"
  51. }
  52. }
  53. }
  54. },
  55. "settings": {
  56. "index": {
  57. "number_of_shards": "3",
  58. "number_of_replicas": "1"
  59. }
  60. }
  61. }
  62. //4)将myidx_v1的数据同步到新索引myidx_v2
  63. //size 可选,每次批量提交1000个,可以提高效率,建议每次提交5-15M的数据
  64. POST _reindex
  65. {
  66. "source": {
  67. "index": "myidx_v1",
  68. "size":1000
  69. },
  70. "dest": {
  71. "index": "myidx_v2"
  72. }
  73. }
  74. //测试,同样的查询条件,对比索引,可以看到myidx_v2设置了分词器后已经生效。
  75. POST myidx/_search
  76. POST myidx_v1/_search
  77. POST myidx_v2/_search
  78. {
  79. "query": {
  80. "bool": {
  81. "must": [
  82. {
  83. "term": {
  84. "name": {
  85. "value": "张三"
  86. }
  87. }
  88. }
  89. ]
  90. }
  91. }
  92. }
  93. //5)将别名映射到新索引 myidx_v2,此时查询别名,发现已经是使用了新索引 myidx_v2
  94. POST _aliases
  95. {
  96. "actions": [
  97. {
  98. "remove": {
  99. "index": "myidx_v1",
  100. "alias": "myidx"
  101. }
  102. },
  103. {
  104. "add": {
  105. "index": "myidx_v2",
  106. "alias": "myidx"
  107. }
  108. }
  109. ]
  110. }

索引模板

很多时候,为了更好地控制一个索引的大小,我们会把一个大索引分为很多小索引,如一个日志索引 access_log,我们需要把它分为access_log_202201 - access_log_202212这样的12个小索引。这些索引除了名称不同,其它内容完全相同(分片、映射等)。但是我们又不想手动创建这些小索引。这时候我们可以通过索引模板来简化索引的创建,通过索引模板我们可以将配置和映射应用到新创建的索引中。然后通过别名来实现这些索引的查询:

  1. #注意,es7.x后mappings中无需_doc,es6.x的mappings中需要_doc
  2. PUT _template/access_log_template
  3. {
  4. "index_patterns": ["access_log_*"],
  5. "aliases": {
  6. "access_log": {}
  7. },
  8. "settings": {
  9. "number_of_shards": 1,
  10. "index.number_of_replicas": 1
  11. },
  12. "mappings": {
  13. "properties": {
  14. "id": {
  15. "type": "keyword"
  16. },
  17. "sourceIp": {
  18. "type": "keyword"
  19. },
  20. "userId": {
  21. "type": "keyword"
  22. },
  23. "userName": {
  24. "type": "keyword"
  25. },
  26. "requestMethod": {
  27. "type": "keyword"
  28. },
  29. "requestUri": {
  30. "type": "keyword"
  31. },
  32. "requestParams": {
  33. "type": "text"
  34. },
  35. "requestTime": {
  36. "type": "long"
  37. },
  38. "requestTimeStr": {
  39. "type": "keyword"
  40. },
  41. "executeTime": {
  42. "type": "long"
  43. }
  44. }
  45. }
  46. }

es 使用

ES 集成到 springboot,只需简单几步。

第一步:引入依赖、配置地址

Java中最常用的客户端有 spring-data-elasticsearch 和 High Level API,spring-data-elasticsearch用起来更简单方便,High Level API用起来更灵活,可以适配不同es版本。此处用High Level API。

  1. #可以通过version指定具体版本,如到6.8.12。springboot2.3时对应版本为7.6.2
  2. <dependency>
  3. <groupId>org.elasticsearch.client</groupId>
  4. <artifactId>elasticsearch-rest-high-level-client</artifactId>
  5. <version>7.6.2</version>
  6. </dependency>
  7. <!-- application.yml中:-->
  8. elasticsearch:
  9. schema: http
  10. address: jvxb.com:9201,jvxb.com:9202,jvxb.com:9203
  11. connectTimeout: 5000
  12. socketTimeout: 5000
  13. connectionRequestTimeout: 5000
  14. maxConnectNum: 100
  15. maxConnectPerRoute: 100

配置 RestHighLevelClient

  1. package com.jvxb.demo.config;
  2. import org.apache.http.HttpHost;
  3. import org.elasticsearch.client.RestClient;
  4. import org.elasticsearch.client.RestClientBuilder;
  5. import org.elasticsearch.client.RestHighLevelClient;
  6. import org.springframework.beans.factory.annotation.Value;
  7. import org.springframework.context.annotation.Bean;
  8. import org.springframework.context.annotation.Configuration;
  9. import java.util.ArrayList;
  10. import java.util.List;
  11. /**
  12. * @Deacription ElasticSearch 配置
  13. **/
  14. @Configuration
  15. public class EsConfiguration {
  16. /**
  17. * 协议
  18. */
  19. @Value("${elasticsearch.schema:http}")
  20. private String schema;
  21. /**
  22. * 集群地址,如果有多个用“,”隔开
  23. */
  24. @Value("${elasticsearch.address}")
  25. private String address;
  26. /**
  27. * 连接超时时间
  28. */
  29. @Value("${elasticsearch.connectTimeout}")
  30. private int connectTimeout;
  31. /**
  32. * Socket 连接超时时间
  33. */
  34. @Value("${elasticsearch.socketTimeout}")
  35. private int socketTimeout;
  36. /**
  37. * 获取连接的超时时间
  38. */
  39. @Value("${elasticsearch.connectionRequestTimeout}")
  40. private int connectionRequestTimeout;
  41. /**
  42. * 最大连接数
  43. */
  44. @Value("${elasticsearch.maxConnectNum}")
  45. private int maxConnectNum;
  46. /**
  47. * 最大路由连接数
  48. */
  49. @Value("${elasticsearch.maxConnectPerRoute}")
  50. private int maxConnectPerRoute;
  51. @Bean(name = "restHighLevelClient")
  52. public RestHighLevelClient restHighLevelClient() {
  53. // 拆分地址
  54. List<HttpHost> hostLists = new ArrayList<>();
  55. String[] hostList = address.split(",");
  56. for (String addr : hostList) {
  57. String host = addr.split(":")[0];
  58. String port = addr.split(":")[1];
  59. hostLists.add(new HttpHost(host, Integer.parseInt(port), schema));
  60. }
  61. // 转换成 HttpHost 数组
  62. HttpHost[] httpHost = hostLists.toArray(new HttpHost[]{});
  63. // 构建连接对象
  64. RestClientBuilder builder = RestClient.builder(httpHost);
  65. // 异步连接延时配置
  66. builder.setRequestConfigCallback(requestConfigBuilder -> {
  67. requestConfigBuilder.setConnectTimeout(connectTimeout);
  68. requestConfigBuilder.setSocketTimeout(socketTimeout);
  69. requestConfigBuilder.setConnectionRequestTimeout(connectionRequestTimeout);
  70. return requestConfigBuilder;
  71. });
  72. // 异步连接数配置
  73. builder.setHttpClientConfigCallback(httpClientBuilder -> {
  74. httpClientBuilder.setMaxConnTotal(maxConnectNum);
  75. httpClientBuilder.setMaxConnPerRoute(maxConnectPerRoute);
  76. return httpClientBuilder;
  77. });
  78. return new RestHighLevelClient(builder);
  79. }
  80. }

第二步:创建索引映射

新建索引模板 access_log_template 并创建索引映射的实体类。

  1. import lombok.Data;
  2. /**
  3. * 访问来源记录到es的 access_log_yyyyMM 索引,并通过模板映射到access_log索引
  4. *
  5. * @author jvxb
  6. * @since 2022-12-28
  7. */
  8. @Data
  9. public class AccessLog {
  10. private String id;
  11. //来源ip
  12. private String sourceIp;
  13. //请求人id
  14. private String userId;
  15. //请求人姓名
  16. private String userName;
  17. //请求路径
  18. private String requestUri;
  19. //请求方式(get/post)
  20. private String requestMethod;
  21. //请求参数
  22. private String requestParams;
  23. //请求时间
  24. private Long requestTime;
  25. private String requestTimeStr;
  26. //方法执行时间(ms)
  27. private Long executeTime;
  28. }

第三步:使用RestHighLevelClient 

  1. package com.jvxb.demo.controller;
  2. import cn.hutool.core.date.DateUtil;
  3. import cn.hutool.core.util.IdUtil;
  4. import cn.hutool.core.util.StrUtil;
  5. import com.alibaba.fastjson.JSON;
  6. import com.alibaba.fastjson.JSONObject;
  7. import com.jvxb.demo.entity.AccessLog;
  8. import io.swagger.annotations.Api;
  9. import lombok.extern.slf4j.Slf4j;
  10. import org.elasticsearch.action.delete.DeleteRequest;
  11. import org.elasticsearch.action.delete.DeleteResponse;
  12. import org.elasticsearch.action.get.GetRequest;
  13. import org.elasticsearch.action.get.GetResponse;
  14. import org.elasticsearch.action.index.IndexRequest;
  15. import org.elasticsearch.action.index.IndexResponse;
  16. import org.elasticsearch.action.search.SearchRequest;
  17. import org.elasticsearch.action.search.SearchResponse;
  18. import org.elasticsearch.action.update.UpdateRequest;
  19. import org.elasticsearch.action.update.UpdateResponse;
  20. import org.elasticsearch.client.RequestOptions;
  21. import org.elasticsearch.client.RestHighLevelClient;
  22. import org.elasticsearch.common.xcontent.XContentType;
  23. import org.elasticsearch.index.query.IdsQueryBuilder;
  24. import org.elasticsearch.index.query.TermQueryBuilder;
  25. import org.elasticsearch.search.SearchHit;
  26. import org.elasticsearch.search.SearchHits;
  27. import org.elasticsearch.search.builder.SearchSourceBuilder;
  28. import org.springframework.beans.factory.annotation.Autowired;
  29. import org.springframework.web.bind.annotation.*;
  30. import java.util.*;
  31. @Slf4j
  32. @RestController
  33. @RequestMapping("/es")
  34. @Api(tags = "es控制器")
  35. public class EsController {
  36. @Autowired
  37. private RestHighLevelClient restHighLevelClient;
  38. @PostMapping("/save")
  39. public Object save(@RequestBody AccessLog accessLog) {
  40. try {
  41. //使用uuid作为主键
  42. accessLog.setId(IdUtil.simpleUUID());
  43. Date currentDate = new Date();
  44. accessLog.setRequestTime(currentDate.getTime());
  45. accessLog.setRequestTimeStr(DateUtil.formatDateTime(currentDate));
  46. IndexRequest indexRequest = new IndexRequest()
  47. .index("access_log")
  48. .id(accessLog.getId())
  49. .source(JSON.toJSONString(accessLog), XContentType.JSON);
  50. IndexResponse response = restHighLevelClient.index(indexRequest, RequestOptions.DEFAULT);
  51. log.info("创建状态:{}", response.status());
  52. return response;
  53. } catch (Exception e) {
  54. return e.getMessage();
  55. }
  56. }
  57. @GetMapping("/get/{id}")
  58. public Object get(@PathVariable("id") String id) {
  59. AccessLog accessLog = null;
  60. try {
  61. GetRequest getRequest = new GetRequest("access_log", id);
  62. GetResponse getResponse = restHighLevelClient.get(getRequest, RequestOptions.DEFAULT);
  63. // 将 JSON 转换成对象
  64. if (getResponse.isExists()) {
  65. accessLog = JSON.parseObject(getResponse.getSourceAsBytes(), AccessLog.class);
  66. log.info("accessLog:{}", accessLog);
  67. }
  68. } catch (Exception e) {
  69. e.printStackTrace();
  70. }
  71. return accessLog;
  72. }
  73. @PostMapping("/get/ids")
  74. public Object getIds(@RequestBody Map param) {
  75. List<AccessLog> result = new ArrayList<>();
  76. try {
  77. IdsQueryBuilder idsQueryBuilder = new IdsQueryBuilder().addIds(((List<String>) param.get("ids")).toArray(new String[]{}));
  78. SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder().query(idsQueryBuilder);
  79. SearchRequest searchRequest = new SearchRequest().source(searchSourceBuilder);
  80. SearchResponse response = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
  81. //获取到结果
  82. SearchHits hits = response.getHits();
  83. Iterator<SearchHit> iterator = hits.iterator();
  84. while (iterator.hasNext()) {
  85. result.add(JSONObject.parseObject(iterator.next().getSourceAsString(), AccessLog.class));
  86. }
  87. } catch (Exception e) {
  88. e.printStackTrace();
  89. }
  90. return result;
  91. }
  92. @GetMapping("/getList")
  93. public Object getList(String userName) {
  94. List<AccessLog> result = new ArrayList<>();
  95. try {
  96. //构造查询语句
  97. SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
  98. if (StrUtil.isNotBlank(userName)) {
  99. TermQueryBuilder userNameTermQueryBuilder = new TermQueryBuilder("userName", userName);
  100. searchSourceBuilder.query(userNameTermQueryBuilder);
  101. }
  102. log.info("dsl:{}", searchSourceBuilder.toString());
  103. //发起查询
  104. SearchRequest searchRequest = new SearchRequest().indices("access_log").source(searchSourceBuilder);
  105. SearchResponse response = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
  106. //获取到结果
  107. SearchHits hits = response.getHits();
  108. Iterator<SearchHit> iterator = hits.iterator();
  109. while (iterator.hasNext()) {
  110. result.add(JSONObject.parseObject(iterator.next().getSourceAsString(), AccessLog.class));
  111. }
  112. } catch (Exception e) {
  113. e.printStackTrace();
  114. }
  115. return result;
  116. }
  117. @PostMapping("/update")
  118. public Object update(@RequestBody AccessLog accessLog) {
  119. try {
  120. //如果需要更新为null值则使用 JSON.toJSONString(accessLog, SerializerFeature.WriteMapNullValue)
  121. UpdateRequest updateRequest = new UpdateRequest().index("access_log").id(accessLog.getId())
  122. .doc(JSON.toJSONString(accessLog), XContentType.JSON);
  123. UpdateResponse response = restHighLevelClient.update(updateRequest, RequestOptions.DEFAULT);
  124. log.info("更新状态:{}", response.status());
  125. return response;
  126. } catch (Exception e) {
  127. e.printStackTrace();
  128. return e.getMessage();
  129. }
  130. }
  131. @PostMapping("/delete/{id}")
  132. public Object delete(@PathVariable String id) {
  133. try {
  134. DeleteRequest deleteRequest = new DeleteRequest().index("access_log").id(id);
  135. DeleteResponse response = restHighLevelClient.delete(deleteRequest, RequestOptions.DEFAULT);
  136. log.info("删除状态:{}", response.status());
  137. return response;
  138. } catch (Exception e) {
  139. e.printStackTrace();
  140. return e.getMessage();
  141. }
  142. }
  143. }

记录-Kafka

相关视频教程:

【尚硅谷】Kafka3.x教程(从入门到调优,深入全面)_哔哩哔哩_bilibili

kafka3.0之前,需要配合zk来使用,kafka3.0后zk模式依然可以使用,但已经开始去除了对zk的依赖,对于开发者来说少维护一个组件还是美滋滋的,但是官方宣布从kafka3.0开始放弃对JDK8的支持,kafka4.0之后完全弃用JDK8。(会整活)

下面以kafka3.1为示例,记录它的使用。(以下记录还是基于jdk1.8)

kafka安装

 可以前往官网( Apache Kafka)下载kafka3.1版本后再上传到服务器。也可以通过wget下载。

  1. #1、下载kafka
  2. mkdir -p /tmp/install_pkg && cd /tmp/install_pkg
  3. wget https://archive.apache.org/dist/kafka/3.1.0/kafka_2.13-3.1.0.tgz
  4. #2、解压
  5. tar -zxvf kafka_2.13-3.1.0.tgz
  6. mv kafka_2.13-3.1.0 /opt/kafka3.1.0
  7. #3、修改配置(kraft代替了zk,配置含义附在后面)
  8. vim /opt/kafka3.1.0/config/kraft/server.properties
  9. process.roles=broker,controller
  10. node.id=1
  11. controller.quorum.voters=1@本机(内网)ip:9093(集群时格式:node.id1@host1:9093,node.id2@host2:9093,node.id3@host3:9093)
  12. listeners=PLAINTEXT://本机(内网)ip:9092,CONTROLLER://本机(内网)ip:9093
  13. advertised.listeners = PLAINTEXT://本机(外网)ip:9092
  14. log.dirs=/opt/kafka3.1.0/data
  15. #4、格式化存储目录(3.0新增,多实例时每个实例机器都需要执行一遍,使用相同的uuid)
  16. mkdir -p /opt/kafka3.1.0/data
  17. /opt/kafka3.1.0/bin/kafka-storage.sh random-uuid
  18. $得到一个uuid
  19. /opt/kafka3.1.0/bin/kafka-storage.sh format -t $得到的uuid -c /opt/kafka3.1.0/config/kraft/server.properties
  20. #可以查看格式化后的存储结果(在数据目录下多出的原信息meta.properties)
  21. cat /opt/kafka3.1.0/data/meta.properties
  22. #5、启动并测试
  23. #启动kafka
  24. nohup /opt/kafka3.1.0/bin/kafka-server-start.sh /opt/kafka3.1.0/config/kraft/server.properties &
  25. #创建topic(副本数不能大于实例数)
  26. /opt/kafka3.1.0/bin/kafka-topics.sh --create --topic test_topic --partitions 3 --replication-factor 1 --bootstrap-server localhost:9092
  27. #查看topic
  28. /opt/kafka3.1.0/bin/kafka-topics.sh --list --bootstrap-server 本机ip:9092
  29. #生成数据(控制台)
  30. /opt/kafka3.1.0/bin/kafka-console-producer.sh --bootstrap-server 本机ip:9092 --topic test_topic
  31. #消费数据(控制台)
  32. /opt/kafka3.1.0/bin/kafka-console-consumer.sh --bootstrap-server 本机ip:9092 --topic test_topic
  33. #停止kafka
  34. /opt/kafka3.1.0/bin/kafka-server-stop.sh /opt/kafka3.1.0/config/kraft/server.properties
  35. #使用systemd管理kafka(推荐)
  36. cat > /etc/systemd/system/kafka.service <<EOF
  37. [Unit]
  38. Description=kafka Server
  39. After=network.target
  40. [Install]
  41. WantedBy=multi-user.target
  42. [Service]
  43. Type=simple
  44. ExecStart=/opt/kafka3.1.0/bin/kafka-server-start.sh /opt/kafka3.1.0/config/kraft/server.properties
  45. ExecStop=/opt/kafka3.1.0/bin/kafka-server-stop.sh /opt/kafka3.1.0/config/kraft/server.properties
  46. TimeoutSec=0
  47. EOF
  48. systemctl daemon-reload
  49. systemctl start/stop/status kafka
  50. #####配置解释#####
  51. process.roles:一个节点可以充当 broker 或 controller 或同时充当。多个角色用逗号分开。
  52. node.id:作为集群中的节点ID,唯一标识,在不同的服务器上这个值不同。其实就是kafka2.0中的broker.id,只是在3.0版本中kafka实例不再只担任broker角色,也有可能是controller角色,所以改名叫做node节点。
  53. controller.quorum.voters:这个配置用于指定controller主控选举的投票节点,所有process.roles包含controller角色的规划节点都要参与。多个节点时其配置格式为:node.id1@host1:9093,node.id2@host2:9093,node.id3@host3:9093
  54. listeners:默认broker 将使用 9092 端口,而 kraft controller控制器将使用 9093端口。
  55. advertised.listeners:指定kafka暴漏的地址(云服务器外网ip),只在内网使用时可注释。
  56. log.dirs:kafka 将存储数据的日志目录(启动前需建好)

kafka使用

kafka 集成到 springboot,只需简单几步。

第一步:引入依赖、配置地址

  1. #可以通过version指定具体版本,springboot2.3时对应版本为2.5.14
  2. <dependency>
  3. <groupId>org.springframework.kafka</groupId>
  4. <artifactId>spring-kafka</artifactId>
  5. <version>2.5.14.RELEASE</version>
  6. </dependency>
  7. <!-- application.yml中:-->
  8. spring:
  9. kafka:
  10. bootstrap-servers: jvxb.com:7882 #连接kafka的地址,多个地址用逗号分隔
  11. consumer:
  12. group-id: test_group #不同group会同时消费topic的数据,同一group下只会消费一次topic的数据
  13. key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
  14. value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
  15. enable-auto-commit: true #开启自动提交offset
  16. auto-commit-interval: 1000ms #自动提交offset的间隔,默认值是 5000ms
  17. producer:
  18. acks: 1 #默认:表示leader必须应答此请求并写入消息到本地日志则请求被认为成功。(0:不等待,-1:等待且写到所有ISR副本)
  19. retries: 3
  20. key-serializer: org.apache.kafka.common.serialization.StringSerializer
  21. value-serializer: org.apache.kafka.common.serialization.StringSerializer

第二步:发送/消费消息

使用 KafkaTemplate发送消息,使用@KafkaListener监听消息

  1. public interface KafkaService {
  2. void send(String topic, String message);
  3. Set<String> listTopics() throws Exception;
  4. }
  1. @Slf4j
  2. @Service
  3. public class KafkaServiceImpl implements KafkaService {
  4. @Autowired
  5. KafkaTemplate kafkaTemplate;
  6. /**
  7. * 生产者端:指定往主题推送数据
  8. */
  9. @Override
  10. public void send(String topic, String message) {
  11. ListenableFuture listenableFuture = kafkaTemplate.send(topic, message);
  12. listenableFuture.addCallback(new ListenableFutureCallback<SendResult<String, String>>() {
  13. @Override
  14. public void onSuccess(SendResult<String, String> result) {
  15. System.out.println(result);
  16. log.info("发送成功回调:{}", result.getProducerRecord().value());
  17. }
  18. @Override
  19. public void onFailure(Throwable ex) {
  20. log.info("发送失败回调", ex);
  21. }
  22. });
  23. }
  24. @Override
  25. public Set<String> listTopics() throws Exception {
  26. Properties pro = new Properties();
  27. List<String> bootstrapServers = (List<String>) kafkaTemplate.getProducerFactory().getConfigurationProperties().get("bootstrap.servers");
  28. pro.put("bootstrap.servers", bootstrapServers.stream().collect(Collectors.joining(",")));
  29. ListTopicsResult result = KafkaAdminClient.create(pro).listTopics();
  30. KafkaFuture<Set<String>> names = result.names();
  31. return names.get();
  32. }
  33. /**
  34. * 消费者端:消费监听的主题数据
  35. */
  36. @KafkaListener(topics = {"test_topic", "test_topic2"})
  37. public void handlerMsg(ConsumerRecord<String, String> consumerRecord) {
  38. System.out.println(consumerRecord);
  39. System.out.println("主题[test_topic]接收到消息:消息值:" + consumerRecord.value() + ", 消息偏移量:" + consumerRecord.offset());
  40. }
  41. }
  1. @RestController
  2. @RequestMapping("/kafka")
  3. @Api(tags = "kafka控制器")
  4. public class KafkaController {
  5. @Autowired
  6. KafkaService kafkaService;
  7. @PostMapping("/send")
  8. public ResponseDataVo send(@RequestBody Map<String, String> param) {
  9. Assert.notBlank(param.get("topic"), "topic must not be null");
  10. Assert.notBlank(param.get("message"), "message must not be null");
  11. kafkaService.send(param.get("topic"), param.get("message"));
  12. return ResponseDataVo.success();
  13. }
  14. @GetMapping("listTopics")
  15. public ResponseDataVo getTopicList() throws Exception {
  16. Set<String> topics = kafkaService.listTopics();
  17. return ResponseDataVo.success(topics);
  18. }
  19. }

Kafka界面管理

之前常用的kafka界面管理工具有Kafka-manager、kafka-eagle、Kafdrop、Kafka Web Console等(推荐使用Kafka-manager、kafka-eagle 或 Kafdrop

虽然现在kafka3.0可以使用Raft协议来代替zk,但是kafka-manager(后改名为CMAK)已经无法兼容。如果想要对kafka运维监控,并且集成自有告警系统,需要自研。

记录-Rabbitmq

安装rabbitmq

  1. #以下开始安装 rabbitMq 3.9.9(发布于2021.11.11) + erlang 23.3(rabbitmq依赖)
  2. #安装前置依赖
  3. yum -y install gcc glibc-devel make ncurses-devel openssl-devel xmlto perl wget gtk2-devel binutils-devel xz
  4. #安装erlang
  5. cd /tmp
  6. wget http://erlang.org/download/otp_src_23.3.tar.gz
  7. tar -zxvf otp_src_23.3.tar.gz
  8. cd otp_src_23.3/
  9. # 编译安装
  10. # 指定路径
  11. ./configure --prefix=/usr/local/erlang
  12. make install
  13. # 配置环境变量
  14. echo 'export PATH=$PATH:/usr/local/erlang/bin' >> /etc/profile
  15. source /etc/profile
  16. # 测试
  17. erl
  18. #安装rabbitmq
  19. # 下载
  20. cd /tmp
  21. wget https://github.com/rabbitmq/rabbitmq-server/releases/download/v3.9.9/rabbitmq-server-generic-unix-3.9.9.tar.xz
  22. # 解压'*.tar.xz'需要两次
  23. # 第一次解压(*.tar.xz ->*.tar)
  24. xz -d rabbitmq-server-generic-unix-3.9.9.tar.xz
  25. # 第二次解压(*.tar -> *)
  26. tar -xvf rabbitmq-server-generic-unix-3.9.9.tar
  27. mv rabbitmq_server-3.9.9/ rabbitmq3.9.9
  28. mv rabbitmq3.9.9/ /usr/local/
  29. # 启动,并后台运行(默认是5672端口、15672界面管理端口)
  30. cd /usr/local/rabbitmq3.9.9/sbin
  31. ./rabbitmq-server -detached
  32. # 停止
  33. ./rabbitmqctl stop
  34. # 状态
  35. ./rabbitmqctl status
  36. # 启用web插件(可以在web中进行用户管理)
  37. ./rabbitmq-plugins enable rabbitmq_management
  38. # 查看用户列表
  39. ./rabbitmqctl list_users且只能在本机登录,外网登录需要添加用户
  40. #第一步:添加 admin 用户并设置密码
  41. ./rabbitmqctl add_user admin 123456
  42. #第二步:添加 admin 用户为administrator角色
  43. ./rabbitmqctl set_user_tags admin administrator
  44. #第三步:设置 admin 用户的权限,指定允许访问的vhost以及write/read
  45. ./rabbitmqctl set_permissions -p "/" admin ".*" ".*" ".*"

需要修改默认端口(5672、15672)时要改动两个地方: 

  1. vim /usr/local/rabbitmq3.9.9/etc/rabbitmq/rabbitmq.conf
  2. 添加以下:
  3. #默认端口为5672
  4. listeners.tcp.default=修改后的端口
  5. #界面管理端口(默认端口为15672)
  6. management.tcp.port=修改后的端口
  7. vim /usr/local/rabbitmq3.9.9/sbin/rabbitmq-defaults
  8. 在最后添加一行:
  9. CONFIG_FILE=/etc/rabbitmq/rabbitmq.conf

安装rabbitmq(docker)

docker run -d --name rabbit5672 -p 5672:5672 -p 15672:15672 -e RABBITMQ_DEFAULT_USER=admin -e RABBITMQ_DEFAULT_PASS=123456 rabbitmq:management 

Rabbitmq使用

rabbitmq 集成到 springboot,只需简单几步。

第一步:引入依赖、配置地址

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-amqp</artifactId>
  4. </dependency>
  5. spring:
  6. rabbitmq:
  7. host: jvxb.com
  8. port: 7872
  9. username: admin
  10. password: 123456
  11. virtual-host: /
  12. listener:
  13. simple:
  14. acknowledge-mode: manual #消费者手动确认

第二步:绑定 交换机与队列

rabbitmq消息发送时是先发送到交换机,并在发送时通过路由键来发送到具体的队列。所以需要先绑定交换机与队列(通过绑定键)。可以在管理界面中绑定(推荐),也可以在代码中绑定。

  • 如果是广播(fanout)交换机时,消息会发送到与其关联的所有队列。
  • 如果是主题(topic)交换机时,消息会发送到与其路由键匹配的队列。
  • 如果是直连(direct)交换机时,消息会发送到与其路由键相等的队列。

本次以主题交换机为例进行消息的收发,模拟收到订单数据时,同步数据到邮箱队列与短信队列。

  1. import org.springframework.amqp.core.Binding;
  2. import org.springframework.amqp.core.BindingBuilder;
  3. import org.springframework.amqp.core.Queue;
  4. import org.springframework.amqp.core.TopicExchange;
  5. import org.springframework.context.annotation.Bean;
  6. import org.springframework.context.annotation.Configuration;
  7. @Configuration
  8. public class RabbitMqConfig {
  9. public static final String ORDER_TOPICEXCHANGE = "order_topic_exchange";
  10. public static final String SMS_QUEUE = "sms.topic.queue";
  11. public static final String MAIL_QUEUE = "mail.topic.queue";
  12. //只有消息的路由键等于sms.topic.binding,才能路由到邮箱(mail)队列
  13. public static final String MAIL_ORDER_BINDING_KEY = "mail.topic.binding";
  14. //只要消息的路由键以 topic.binding结尾,则路由到短信(sms)队列
  15. public static final String SMS_ORDER_BINDING_KEY = "#.topic.binding";
  16. //声明订单主题交换机
  17. @Bean
  18. TopicExchange orderTopicExchange() {
  19. return new TopicExchange(ORDER_TOPICEXCHANGE);
  20. }
  21. //短信队列
  22. @Bean
  23. public Queue smsTopicQueue() {
  24. return new Queue(SMS_QUEUE);
  25. }
  26. //邮件队列
  27. @Bean
  28. public Queue mailTopicQueue() {
  29. return new Queue(MAIL_QUEUE, true); //true 是否持久
  30. }
  31. //订单交换机与短信队列绑定
  32. @Bean
  33. Binding smsBinding() {
  34. return BindingBuilder.bind(smsTopicQueue()).to(orderTopicExchange()).with(SMS_ORDER_BINDING_KEY);
  35. }
  36. //订单交换机与邮件队列绑定
  37. @Bean
  38. Binding mailBinding() {
  39. return BindingBuilder.bind(mailTopicQueue()).to(orderTopicExchange()).with(MAIL_ORDER_BINDING_KEY);
  40. }
  41. }

第三步:发送/消费消息

使用 RabbitTemplate 发送消息,使用@RabbitListener监听消息

  1. public interface RabbitmqService {
  2. void send(String topic, String routingKey, String message);
  3. }
  1. import com.jvxb.demo.config.RabbitMqConfig;
  2. import com.jvxb.demo.service.RabbitmqService;
  3. import org.springframework.amqp.core.Message;
  4. import org.springframework.amqp.rabbit.annotation.RabbitListener;
  5. import org.springframework.amqp.rabbit.core.RabbitTemplate;
  6. import org.springframework.beans.factory.annotation.Autowired;
  7. import org.springframework.stereotype.Service;
  8. /**
  9. * @author jvxb
  10. * @since 2023-02-04
  11. */
  12. @Service
  13. public class RabbitmqServiceImpl implements RabbitmqService {
  14. @Autowired
  15. RabbitTemplate rabbitTemplate;
  16. @Override
  17. public void send(String exchange, String routingKey, String message) {
  18. rabbitTemplate.convertAndSend(exchange, routingKey, message);
  19. }
  20. @RabbitListener(queues = {RabbitMqConfig.SMS_QUEUE, RabbitMqConfig.MAIL_QUEUE})
  21. public void handleSmsMsg(String msg, Message message) {
  22. String consumerQueue = message.getMessageProperties().getConsumerQueue();
  23. try {
  24. System.out.println(String.format("队列%s接收到消息:%s", consumerQueue, msg));
  25. } catch (Exception e) {
  26. //可以通过另外途径(mysql/redis)记录这条消息,或者投递到死信队列。
  27. System.out.println(String.format("队列%s处理消息%s异常:%s,", consumerQueue, msg, e.getMessage()));
  28. }
  29. }
  30. }
  1. @RestController
  2. @RequestMapping("/rabbitmq")
  3. @Api(tags = "rabbitmq控制器")
  4. public class RabbitmqController {
  5. @Autowired
  6. RabbitmqService rabbitmqService;
  7. @PostMapping("/send")
  8. public ResponseDataVo send(@RequestBody Map<String, String> param) {
  9. Assert.notBlank(param.get("exchange"), "exchange must not be null");
  10. Assert.notBlank(param.get("routingKey"), "routingKey must not be null");
  11. Assert.notBlank(param.get("message"), "message must not be null");
  12. rabbitmqService.send(param.get("exchange"), param.get("routingKey"), param.get("message"));
  13. return ResponseDataVo.success();
  14. }
  15. }

可以测试发现当 routingKey = sms.topic.queue 时两个队列都能收到消息,当 routingKey = mail.topic.queue 时,只有 mail Queue 收到消息。符合使用主题交换机的预期。

记录-日志采集(EFK)

相关视频教程:

尚硅谷大数据Filebeat教程(filebeat日志采集系统)_哔哩哔哩_bilibili

EFK(es,filebeat,kibana)是经典架构ELK(es,logstash,kibana)的变种。

filebeat 是 logstash 的轻量级实现,二者都可以用来收集和转发日志。filebeat虽说功能没logstash那么强大,但是胜在轻量、简单易用,在大部分场景下也足够用了。

日志采集的经典链路是:

filebeat -> es -> kibana

filebeat -> kafka -> es -> kibana

日志采集

  1. cd /tmp
  2. wget https://artifacts.elastic.co/downloads/beats/filebeat/filebeat-7.12.1-linux-x86_64.tar.gz
  3. tar zxvf filebeat-7.12.1-linux-x86_64.tar.gz
  4. mv filebeat-7.12.1-linux-x86_64 /opt/filebeat
  5. #配置需要采集的日志目录及输出地址
  6. cat > /opt/filebeat/mybeat.yml <<EOF
  7. filebeat.inputs:
  8. - type: log
  9. enabled: true
  10. paths:
  11. - /var/log/nginx/*.log
  12. # 添加自定义字段
  13. fields:
  14. #内网ip
  15. intranet_ip: `ifconfig eth0|grep 'inet '|awk '{print $2}'`
  16. # true 为添加到根节点,false为添加到子节点中
  17. fields_under_root: true
  18. output.kafka:
  19. hosts: ["jvxb.com:7882"]
  20. topic: 'nginx_log'
  21. partition.round_robin:
  22. reachable_only: false
  23. compression: gzip
  24. EOF
  25. #通过systemctl控制filebeat
  26. cat > /etc/systemd/system/filebeat.service <<EOF
  27. [Unit]
  28. Description=filebeat Server
  29. After=network.target
  30. [Install]
  31. WantedBy=multi-user.target
  32. [Service]
  33. Type=simple
  34. ExecStart=/opt/filebeat/filebeat -e -c /opt/filebeat/mybeat.yml
  35. EOF
  36. #启动filebeat(并设置开机自启)
  37. systemctl start filebeat
  38. systemctl enable filebeat
  39. #nginx日志中有新消息时,filebeat会将消息直接转发到kafka。

采集到后最终的日志格式如下:

  1. {
  2. "@timestamp": "2023-01-16T15:27:32.818Z",
  3. "@metadata": {
  4. "beat": "filebeat",
  5. "type": "_doc",
  6. "version": "7.12.1"
  7. },
  8. "host": {
  9. "name": "jvxb.com"
  10. },
  11. "agent": {
  12. "type": "filebeat",
  13. "version": "7.12.1",
  14. "hostname": "jvxb.com",
  15. "ephemeral_id": "d4cdcceb-af45-462d-9fa9-3a2192c329d4",
  16. "id": "0dcee92b-9303-4682-ac61-9c43788326c8",
  17. "name": "jvxb.com"
  18. },
  19. "log": {
  20. "file": {
  21. "path": "/var/log/nginx/access.log"
  22. },
  23. "offset": 4998
  24. },
  25. "message": "120.229.8.20 - - [16/Jan/2023:23:27:25 +0800] \"GET / HTTP/1.1\" 304 0 \"-\" \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36\" \"-\"",
  26. "input": {
  27. "type": "log"
  28. },
  29. "intranet_ip": "10.0.10.0",
  30. "ecs": {
  31. "version": "1.8.0"
  32. }
  33. }

日志消费

如nginx 默认的日志格式为:

  1. log_format main '$remote_addr - $remote_user [$time_local] "$request" '
  2. '$status $body_bytes_sent "$http_referer" '
  3. '"$http_user_agent" "$http_x_forwarded_for"';

根据最终上报到kafka时的数据格式,我们需要对其进行解析,主要是需要解析以下内容。

120.229.8.20 - - [17/Jan/2023:00:00:22 +0800] "GET / HTTP/1.1" 304 0 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36" "-"

对应 java 代码如下:

  1. @Data
  2. @ToString
  3. public class NginxAccessLog {
  4. public static final String INDEX_NAME = "nginx_access_log";
  5. String nginx_ip;
  6. String remote_addr;
  7. String remote_user;
  8. String time_local;
  9. Long time;
  10. String request;
  11. String request_url;
  12. Integer status;
  13. Integer body_bytes_sent;
  14. String http_referer;
  15. String http_user_agent;
  16. String http_x_forwarded_for;
  17. }
  1. public interface NginxAccessLogService {
  2. NginxAccessLog resolveAccessLog(String message);
  3. void saveAccessLog(NginxAccessLog nginxAccessLog);
  4. }
  1. @Slf4j
  2. @Service
  3. public class NginxAccessLogServiceImpl implements NginxAccessLogService {
  4. @Autowired
  5. private RestHighLevelClient restHighLevelClient;
  6. @KafkaListener(topics = {"nginx_log"})
  7. public void handlerNginxLog(ConsumerRecord<String, String> consumerRecord) {
  8. System.out.println("消费nginx_log: " + consumerRecord.value());
  9. JSONObject data = JSONObject.parseObject(consumerRecord.value());
  10. String path = data.getJSONObject("log").getJSONObject("file").getString("path");
  11. if (path.endsWith("access.log")) {
  12. String message = data.getString("message");
  13. NginxAccessLog accessLog = resolveAccessLog(message);
  14. accessLog.setNginx_ip(data.getString("intranet_ip"));
  15. saveAccessLog(accessLog);
  16. }
  17. }
  18. @Override
  19. public NginxAccessLog resolveAccessLog(String message) {
  20. String accessLogReg = "(\\S+) - (\\S+) \\[(.+)\\] \"([^\"]+)\" (\\d+) (\\d+) \"([^\"]+)\" \"([^\"]+)\" \"([^\"]+)\"$";
  21. String remoteAddr = message.replaceFirst(accessLogReg, "$1");
  22. if (remoteAddr == null || remoteAddr.length() > 16) {
  23. log.error("解析nginx access_log文本异常,文本内容 {}", message);
  24. return null;
  25. }
  26. NginxAccessLog accessLog = new NginxAccessLog();
  27. accessLog.setRemote_addr(remoteAddr);
  28. accessLog.setRemote_user(message.replaceFirst(accessLogReg, "$2"));
  29. accessLog.setTime_local(message.replaceFirst(accessLogReg, "$3"));
  30. accessLog.setTime(DateUtil.parse(accessLog.getTime_local(), "dd/MMM/yyyy:HH:mm:ss +0800", Locale.ENGLISH).getTime());
  31. accessLog.setTime_local(DateUtil.format(DateUtil.date(accessLog.getTime()), DatePattern.NORM_DATETIME_PATTERN));
  32. accessLog.setRequest(message.replaceFirst(accessLogReg, "$4"));
  33. accessLog.setRequest_url(accessLog.getRequest().split(" ")[1]);
  34. accessLog.setStatus(Integer.valueOf(message.replaceFirst(accessLogReg, "$5")));
  35. accessLog.setBody_bytes_sent(Integer.valueOf(message.replaceFirst(accessLogReg, "$6")));
  36. accessLog.setHttp_referer(message.replaceFirst(accessLogReg, "$7"));
  37. accessLog.setHttp_user_agent(message.replaceFirst(accessLogReg, "$8"));
  38. accessLog.setHttp_x_forwarded_for(message.replaceFirst(accessLogReg, "$9"));
  39. return accessLog;
  40. }
  41. @Override
  42. public void saveAccessLog(NginxAccessLog nginxAccessLog) {
  43. if (nginxAccessLog == null) {
  44. return;
  45. }
  46. try {
  47. IndexRequest indexRequest = new IndexRequest()
  48. .index(NginxAccessLog.INDEX_NAME)
  49. .source(JSON.toJSONString(nginxAccessLog), XContentType.JSON);
  50. IndexResponse response = restHighLevelClient.index(indexRequest, RequestOptions.DEFAULT);
  51. log.info("创建状态:{}", response.status());
  52. } catch (IOException e) {
  53. log.info("创建失败:{}", e.getMessage(), e);
  54. }
  55. }
  56. }

存入es后,通过kibana等进行查看。


记录-SpringCloud

相关视频教程:

尚硅谷SpringCloud框架开发教程(SpringCloudAlibaba微服务分布式架构丨Spring Cloud)

通过springcloud,我们可以快速整合和实现常用功能如:服务发现注册、配置中心、服务网关、负载均衡、断路器、消息总线、数据监控等。

springboot、springcloud、springcloud alibaba版本关系参考:spring-cloud-alibaba/wiki/版本说明

父pom.xml参考:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <modelVersion>4.0.0</modelVersion>
  6. <groupId>com.jvxb</groupId>
  7. <artifactId>cloud_demo</artifactId>
  8. <version>1.0.1-SNAPSHOT</version>
  9. <packaging>pom</packaging>
  10. <description>Demo project for Spring Cloud</description>
  11. <parent>
  12. <groupId>org.springframework.boot</groupId>
  13. <artifactId>spring-boot-starter-parent</artifactId>
  14. <version>2.3.12.RELEASE</version>
  15. <relativePath/> <!-- lookup parent from repository -->
  16. </parent>
  17. <properties>
  18. <springboot.version>2.3.12.RELEASE</springboot.version>
  19. <java.version>1.8</java.version>
  20. <lombok.version>1.18.20</lombok.version>
  21. <hutool.version>5.6.7</hutool.version>
  22. <fastjson.version>1.2.83</fastjson.version>
  23. <mysql.version>5.1.48</mysql.version>
  24. <mybatisplus.version>3.5.2</mybatisplus.version>
  25. <swagger2.version>2.9.2</swagger2.version>
  26. <swaggermodel.version>1.5.22</swaggermodel.version>
  27. <velocity.version>2.0</velocity.version>
  28. <jedis.version>3.3.0</jedis.version>
  29. <elasticsearch.version>7.6.2</elasticsearch.version>
  30. <spring-kafka.version>2.5.14.RELEASE</spring-kafka.version>
  31. <amqp.version>2.3.12.RELEASE</amqp.version>
  32. <spring-cloud.version>Hoxton.SR12</spring-cloud.version>
  33. <spring-cloud-alibaba.version>2.2.9.RELEASE</spring-cloud-alibaba.version>
  34. </properties>
  35. <dependencies>
  36. <dependency>
  37. <groupId>org.projectlombok</groupId>
  38. <artifactId>lombok</artifactId>
  39. <version>${lombok.version}</version>
  40. </dependency>
  41. <dependency>
  42. <groupId>cn.hutool</groupId>
  43. <artifactId>hutool-all</artifactId>
  44. <version>${hutool.version}</version>
  45. </dependency>
  46. <dependency>
  47. <groupId>com.alibaba</groupId>
  48. <artifactId>fastjson</artifactId>
  49. <version>${fastjson.version}</version>
  50. </dependency>
  51. </dependencies>
  52. <!-- dependencyManagement中定义的只是依赖的声明,并不实现引入,子项目直接声明需要用的依赖即可,且无需另外指定版本。 -->
  53. <dependencyManagement>
  54. <dependencies>
  55. <dependency>
  56. <groupId>org.springframework.boot</groupId>
  57. <artifactId>spring-boot-starter-web</artifactId>
  58. <version>${springboot.version}</version>
  59. </dependency>
  60. <dependency>
  61. <groupId>io.springfox</groupId>
  62. <artifactId>springfox-swagger2</artifactId>
  63. <version>${swagger2.version}</version>
  64. <exclusions>
  65. <exclusion>
  66. <groupId>io.swagger</groupId>
  67. <artifactId>swagger-models</artifactId>
  68. </exclusion>
  69. </exclusions>
  70. </dependency>
  71. <dependency>
  72. <groupId>io.swagger</groupId>
  73. <artifactId>swagger-models</artifactId>
  74. <version>${swaggermodel.version}</version>
  75. </dependency>
  76. <dependency>
  77. <groupId>io.springfox</groupId>
  78. <artifactId>springfox-swagger-ui</artifactId>
  79. <version>${swagger2.version}</version>
  80. </dependency>
  81. <dependency>
  82. <groupId>org.springframework.boot</groupId>
  83. <artifactId>spring-boot-starter-jdbc</artifactId>
  84. <version>${springboot.version}</version>
  85. </dependency>
  86. <dependency>
  87. <groupId>mysql</groupId>
  88. <artifactId>mysql-connector-java</artifactId>
  89. <version>${mysql.version}</version>
  90. </dependency>
  91. <dependency>
  92. <groupId>com.baomidou</groupId>
  93. <artifactId>mybatis-plus-boot-starter</artifactId>
  94. <version>${mybatisplus.version}</version>
  95. </dependency>
  96. <dependency>
  97. <groupId>com.baomidou</groupId>
  98. <artifactId>mybatis-plus-generator</artifactId>
  99. <version>${mybatisplus.version}</version>
  100. </dependency>
  101. <dependency>
  102. <groupId>org.apache.velocity</groupId>
  103. <artifactId>velocity-engine-core</artifactId>
  104. <version>${velocity.version}</version>
  105. </dependency>
  106. <dependency>
  107. <groupId>org.springframework.boot</groupId>
  108. <artifactId>spring-boot-starter-data-redis</artifactId>
  109. <version>${springboot.version}</version>
  110. <exclusions>
  111. <exclusion>
  112. <groupId>io.lettuce</groupId>
  113. <artifactId>lettuce-core</artifactId>
  114. </exclusion>
  115. </exclusions>
  116. </dependency>
  117. <dependency>
  118. <groupId>redis.clients</groupId>
  119. <artifactId>jedis</artifactId>
  120. <version>${jedis.version}</version>
  121. </dependency>
  122. <dependency>
  123. <groupId>org.elasticsearch.client</groupId>
  124. <artifactId>elasticsearch-rest-high-level-client</artifactId>
  125. <version>${elasticsearch.version}</version>
  126. </dependency>
  127. <dependency>
  128. <groupId>org.springframework.kafka</groupId>
  129. <artifactId>spring-kafka</artifactId>
  130. <version>${spring-kafka.version}</version>
  131. </dependency>
  132. <dependency>
  133. <groupId>org.springframework.boot</groupId>
  134. <artifactId>spring-boot-starter-amqp</artifactId>
  135. <version>${amqp.version}</version>
  136. </dependency>
  137. <dependency>
  138. <groupId>org.springframework.cloud</groupId>
  139. <artifactId>spring-cloud-dependencies</artifactId>
  140. <version>${spring-cloud.version}</version>
  141. <type>pom</type>
  142. <scope>import</scope>
  143. </dependency>
  144. <dependency>
  145. <groupId>com.alibaba.cloud</groupId>
  146. <artifactId>spring-cloud-alibaba-dependencies</artifactId>
  147. <version>${spring-cloud-alibaba.version}</version>
  148. <type>pom</type>
  149. <scope>import</scope>
  150. </dependency>
  151. </dependencies>
  152. </dependencyManagement>
  153. </project>

Nacos

Nacos 官方文档

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service的首字母简称,一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。

Nacos = 注册中心 + 配置中心。(等于早期的 eureka + config + bus)

Nacos 安装

可前往github下载稳定版本,如 Tags · alibaba/nacos · GitHub

或直接下载安装启动

  1. wget https://github.com/alibaba/nacos/releases/download/2.1.1/nacos-server-2.1.1.tar.gz
  2. tar -xvf nacos-server-2.1.1.tar.gz
  3. cd nacos\bin
  4. #启动命令(standalone代表着单机模式运行,非集群模式)
  5. sh startup.sh -m standalone

默认端口为 8848,访问 ip:8848/nacos 即可进入管理页面,默认帐密都是 nacos。

#注意:有防火墙记得开放端口
firewall-cmd --zone=public --list-ports
firewall-cmd --zone=public --add-port=8848/tcp --permanent
firewall-cmd --reload

Nacos集成

服务发现

  1. 1、在服务的pom.xml中引入nacos服务发现的依赖
  2. <dependency>
  3. <groupId>com.alibaba.cloud</groupId>
  4. <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
  5. </dependency>
  6. 2、在服务的bootstrap.yml中配置nacos地址(注意不是application.yml)
  7. spring:
  8. cloud:
  9. nacos:
  10. discovery:
  11. server-addr: 127.0.0.1:8848
  12. 3、在服务主启动类上开启服务注册发现功能:@EnableDiscoveryClient

进行服务注册和发现后,可以使用 RestTemplate 进行服务间方法调用。

  1. @LoadBalanced
  2. @Bean
  3. public RestTemplate restTemplate() {
  4. return new RestTemplate();
  5. }
  6. @GetMapping("/createOrder/{id}")
  7. public ResponseDataVo createOrder(@PathVariable("id") Integer id) {
  8. ResponseDataVo forObject = restTemplate.getForObject("http://order/order/" + id, ResponseDataVo.class);
  9. return ResponseDataVo.success(new User(id, "用户" + id).toString() + "创建订单" + forObject.getData());
  10. }

配置管理

  1. 1、在服务的pom.xml中引入nacos服务发现的依赖
  2. <dependency>
  3. <groupId>com.alibaba.cloud</groupId>
  4. <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
  5. </dependency>
  6. 2、在服务的bootstrap.yml中配置nacos地址(注意不是application.yml)
  7. spring:
  8. cloud:
  9. nacos:
  10. config:
  11. server-addr: 127.0.0.1:8848
  12. 3、通过 Spring Cloud 原生注解 @RefreshScope 实现配置自动更新
  13. @RestController
  14. @RequestMapping("/user")
  15. @RefreshScope
  16. public class UserController {
  17. @Value("${testConfig:false}")
  18. private String testConfig;
  19. @RequestMapping("/getConfig")
  20. public ResponseDataVo getConfig() {
  21. return ResponseDataVo.success(testConfig);
  22. }
  23. }
  24. 4、在nacos -> 配置管理 -> 配置列表 中,通过 dataId 对配置进行管理。
  25. ${prefix}-${spring.profiles.active}.${file-extension}
  26. dataId示例:user、user-dev (就是服务名 或者 服务名-环境)

Nacos集群

生产环境上,微服务只有单节点是不允许的,所以需要搭建Nacos集群。

并且nacos默认的数据源是使用的内置数据源,是存在内存中的,重启就会失效。启动nacos集群的时候内存中的数据肯定就不能共享,需要另行配置数据源(多为mysql)。

Nacos集群搭建参考:Nacos2.1.1在Linux中的集群部署详解

  1. #第一步:建立nacos数据库为数据源
  2. #创建nacos数据库
  3. CREATE DATABASE `nacos` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
  4. #执行nacos/conf/nacos-mysql.sql
  5. #第二步:为节点添加数据源与节点配置(以单台机器部署集群为例,此时需要修改启动端口)
  6. cp -r nacos nacos1
  7. #2.1 配置数据源
  8. cat >> nacos1/conf/application.properties <<EOF
  9. spring.datasource.platform=mysql
  10. db.num=1
  11. db.url.0=jdbc:mysql://127.0.0.1:3306/nacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC
  12. db.user.0=root
  13. db.password.0=123456
  14. EOF
  15. #2.2 配置集群节点
  16. cp nacos1/conf/cluster.conf.example nacos1/conf/cluster.conf
  17. vim nacos1/conf/cluster.conf 将example改为具体节点
  18. 127.0.0.1:8841
  19. 127.0.0.1:8843
  20. 127.0.0.1:8845
  21. #注:以集群的方式启动,Nacos默认占用的内存是2G。可以根据实际情况修改:
  22. vim nacos1/bin/startup.sh
  23. 将 -Xms2g -Xmx2g -Xmn1g 改为 -Xms256m -Xmx256m -Xmn125m
  24. #2.3 将一个节点,复制成3个节点,并将3个节点的启动端口分别修改为8841 8843 8845。
  25. cp -r nacos1 nacos2
  26. cp -r nacos1 nacos3
  27. vim nacos1/conf/application.properties 将 server.port 改为 8841
  28. vim nacos2/conf/application.properties 将 server.port 改为 8843
  29. vim nacos3/conf/application.properties 将 server.port 改为 8845
  30. #2.4 启动节点:
  31. sh nacos1/bin/startup.sh
  32. sh nacos2/bin/startup.sh
  33. sh nacos3/bin/startup.sh
  34. #第三步:配置负载均衡
  35. upstream nacos-cluster {
  36. server localhost:8841;
  37. server localhost:8843;
  38. server localhost:8845;
  39. }
  40. server {
  41. listen 8999;
  42. server_name localhost;
  43. location /nacos { #监听的请求路径为/nacos
  44. proxy_pass http://nacos-cluster; #反向代理配置
  45. }
  46. }
  47. #此时项目中配置地址为:
  48. spring:
  49. cloud:
  50. nacos:
  51. discovery:
  52. server-addr: ip:8999
  53. config:
  54. server-addr: ip:8999

OpenFeign

通过 restTemplate + ribbon 我们可以实现通过服务名来调用其他微服务。

  1. @LoadBalanced
  2. @Bean
  3. public RestTemplate restTemplate() {
  4. return new RestTemplate();
  5. }
  6. ResponseDataVo forObject = restTemplate.getForObject("http://order/order/" + id, ResponseDataVo.class);

但还是不够优雅。通过 OpenFeign 我们可以像调普通方法一样进行微服务间的方法调用。下面记录OpenFeign的集成:

OpenFeign集成

第一步:引入依赖

  1. #1、引入依赖
  2. <dependency>
  3. <groupId>org.springframework.cloud</groupId>
  4. <artifactId>spring-cloud-starter-openfeign</artifactId>
  5. </dependency>

第二步:使用 openFeign

若服务端有接口如下:

  1. @RestController
  2. @RequestMapping("/order")
  3. public class OrderController {
  4. @GetMapping("/{id}")
  5. public ResponseDataVo<Order> getById(@PathVariable("id") Integer id) {
  6. return ResponseDataVo.success(new Order(id, "订单" + id));
  7. }
  8. @GetMapping("/timeout")
  9. public ResponseDataVo<Order> timeout(Integer id) {
  10. try {
  11. Thread.sleep(id);
  12. } catch (InterruptedException e) {
  13. e.printStackTrace();
  14. }
  15. return ResponseDataVo.success(new Order(id, "订单" + id));
  16. }
  17. }

则客户端中: 

  1. 先在主启动类上添加启用feign:@EnableFeignClients
  2. //然后写服务类,通过@FeignClient表明该方法由对应服务实现。
  3. @Service
  4. @FeignClient("order")
  5. public interface OrderService {
  6. @GetMapping("/order/{id}")
  7. ResponseDataVo<Order> getById(@PathVariable("id") Integer id);
  8. @GetMapping("/order/timeout")
  9. ResponseDataVo<Order> timeout(@RequestParam("id") Integer id);
  10. }
  11. //3、与普通服务类一样调用即可
  12. @Autowired
  13. OrderService orderService;
  14. ResponseDataVo<Order> forObject = orderService.getById(id);

注意,openFeign底层用的也是ribbon,默认超时时间为1s,一般来说这个时间是过短的。所以需要配置它的超时时间(在bootstrap.yml 或 application.yml 中配置都行)。

  1. ribbon:
  2. ConnnectTimeout: 5000 #tcp建立连接的时间
  3. ReadTimeout: 5000 # 设置读取时间

另外 openFeign 可以开启日志打印。

  1. import feign.Logger;
  2. import org.springframework.context.annotation.Bean;
  3. import org.springframework.context.annotation.Configuration;
  4. @Configuration
  5. public class FeignConfig {
  6. /**
  7. * feign 日志记录级别
  8. * NONE:无日志记录(默认)
  9. * BASIC:只记录请求方法和 url 以及响应状态代码和执行时间。
  10. * HEADERS:记录请求和响应头的基本信息。
  11. * FULL:记录请求和响应的头、正文和元数据。
  12. *
  13. * @return Logger.Level
  14. */
  15. @Bean
  16. public Logger.Level feignLoggerLevel() {
  17. return Logger.Level.FULL;
  18. }
  19. }
  1. #为需要的服务类打印openFeign的日志
  2. logging:
  3. level:
  4. com.jvxb.user.service.OrderService: debug

Hystrix

通过openFeign,我们可以像调用本地方法一样进行微服务间的方法调用。但是仍有一个问题,调用时很可能会超时或者出现异常。虽然说我们可以手动通过 try-catch 来使程序继续运行,但是在高负载的情况下,如果不做任何处理的话,此类问题可能会导致服务消费者的资源耗竭甚至整个系统的奔溃。我们把这种因提供者的不可用从而导致消费者不可用,并将不可用逐渐放大的现象称为雪崩效应。要想防止雪崩效应,必须有一个强大的容错机制。该机制需实现以下两点:(1)为网路请求设置超时;(2)使用断路器模式。Hystrix就是一个实现超时机制与断路器模式的工具类库。

Hystrix主要负责解决雪崩效应,进行服务熔断,服务降级和资源隔离。。下面记录Hystrix断路器的使用。

Hystrix集成

第一步:引入依赖,开启断路保护

  1. <!-- Feign中已经包含Hystrix功能,如果已经使用Feign无需继续引入 -->
  2. <dependency>
  3. <groupId>org.springframework.cloud</groupId>
  4. <artifactId>spring-cloud-starter-openfeign</artifactId>
  5. </dependency>

在application.yml中开启hystrix,且关闭或修改hystrix的默认超时时间(默认1s)。如果hystrix开启时其会代替 ribbon 的超时时间。

  1. #开启hystrix
  2. feign:
  3. hystrix:
  4. enabled: true
  5. #关闭hystrix超时时间,默认为true
  6. hystrix:
  7. command:
  8. default:
  9. execution:
  10. timeout:
  11. enabled: false
  12. #修改hystrix超时时间,默认为1000
  13. hystrix:
  14. command:
  15. default:
  16. execution:
  17. isolation:
  18. thread:
  19. timeoutInMilliseconds: 3000

第二步:进行容错处理

(1)服务调用方接口容错处理

定义容错处理类,并通过Feign的 fallback 属性指定

  1. @Service
  2. @FeignClient(value = "order", fallback = OrderMockServiceImpl.class)
  3. public interface OrderService {
  4. @GetMapping("/order/{id}")
  5. ResponseDataVo getById(@PathVariable("id") Integer id);
  6. @GetMapping("/order/timeout")
  7. ResponseDataVo<Order> timeout(@RequestParam("id") Integer id);
  8. }

异常容错类通过继承Feign接口类,在其实现中进行容错处理。

  1. @Service
  2. public class OrderMockServiceImpl implements OrderService {
  3. @Override
  4. public ResponseDataVo getById(Integer id) {
  5. return ResponseDataVo.error("服务调用异常,请稍后重试!");
  6. }
  7. @Override
  8. public ResponseDataVo<Order> timeout(Integer id) {
  9. return ResponseDataVo.error("服务调用异常,请稍后重试!");
  10. }
  11. }

(2)服务提供方接口容错处理

服务提供方可以单独引入hystrix依赖。

  1. <dependency>
  2. <groupId>org.springframework.cloud</groupId>
  3. <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
  4. </dependency>

服务提供方改造feign接口方法实现,添加@HystrixCommand注解

  1. 先在主启动类上添加启用hystrix:@EnableCircuitBreaker
  2. @GetMapping("/order/timeout")
  3. @HystrixCommand(fallbackMethod = "timeOutErrorHandler", commandProperties = {
  4. @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "2000") })
  5. public ResponseDataVo<Order> timeout(@RequestParam("id") Integer id)
  6. try {
  7. //方法休眠超过2秒,一定会发生错误,也就会调用下边的fallbakcMethod方法
  8. TimeUnit.SECONDS.sleep(id);
  9. } catch (InterruptedException e) {
  10. e.printStackTrace();
  11. }
  12. return "服务正常调用";
  13. }
  14. /**
  15. * 这个就是当上边方法异常时的“兜底”方法
  16. */
  17. public String timeOutErrorHandler() {
  18. return "对不起,系统处理超时";
  19. }

以上示例理论上是要返回“服务调用正常”,如果造成了超时错误,所以返回兜底的 fallback 中的“对不起,系统处理超时”,而且这个返回是会在2秒后。上述方法存在问题,代码膨胀,耦合度高,业务逻辑混乱

上述方法存在问题,代码膨胀(每个方法都要写fallbackMethod 的话),耦合度高,业务逻辑混乱,解决方法:

@DefaultProperties(defaultFallback = "global_fallback", commandProperties = { .. 可选 }),即定义一个统一的默认处理,然后方法上直接使用@HystrixCommand修饰即可。

第三步:服务熔断

当启用服务降级时,会默认启用服务熔断机制,我们只需要对一些参数进行配置就可以了,就是在上边的 @HystrixCommand 中的一些属性,比如:

  1. @HystrixCommand(fallbackMethod = "timeOutErrorHandler", commandProperties = {
  2. @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "2000"),
  3. @HystrixProperty(name = "circuitBreaker.enabled", value = "true"),//开启断路器
  4. @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "20"),//请求次数的峰值
  5. @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "10000"),//检测错误次数的时间范围
  6. @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "60")//请求失败率达到多少比例后会打开断路器})
  7. })

请求失败数量超过一定比例(默认50%),断路器会切换到开路状态(Open)。 这时所有请求会直接失败而不会发送到服务提供方。断路器保持在开路状态一段时间后(默认5秒),自动切换到半开路状态(HALF-OPEN)。

这时会判断下一次请求的返回情况,如果请求成功,断路器切回闭路状态(CLOSED), 否则重新切换到开路状态(OPEN)。 Hystrix的断路器就像我们家庭电路中的保险丝, 一旦后端服务不可用, 断路器会直接切断请求链, 避免发送大量无效请求影响系统吞吐量, 并且断路器有自我检测并恢复的能力。

值得注意的是:执行回退逻辑并不代表断路器已经打开,请求失败、超时、被拒绝以及断路器打开时都会执行回退逻辑。只有当请求的失败率达到阈值(默认是5秒内20次失败),断路器才会打开。

Sentinel

Hystrix断路器,主要负责解决雪崩效应,进行服务熔断,服务降级和资源隔离。

Sentinel,主要负责熔断降级,系统负载保护,多样化的流量控制,实时监控和控制台。

总的来说,Hystrix与Sentinel的功能基本一致,但Sentinel 的功能更强大,也更值得推荐。(且Hystrix目前已停更)

Sentinel集成

第一步:下载并启动Sentinel

  1. #下载sentinel
  2. wget https://github.com/alibaba/Sentinel/releases/download/1.8.6/sentinel-dashboard-
  3. 1.8.6.jar
  4. #启动sentinel,默认是8080端口。 可以通过 --server.port = 8081 修改
  5. java -jar sentinel-dashboard-1.8.6.jar
  6. #浏览器访问(初始帐密:sentinel)
  7. http://机器ip:8080

第二步:微服务引入sentinel

  1. <dependency>
  2. <groupId>com.alibaba.cloud</groupId>
  3. <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
  4. </dependency>
  5. <dependency>
  6. <groupId>com.alibaba.cloud</groupId>
  7. <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
  8. </dependency>
  9. <!-- 后续做持久化用 -->
  10. <dependency>
  11. <groupId>com.alibaba.csp</groupId>
  12. <artifactId>sentinel-datasource-nacos</artifactId>
  13. </dependency>

application.yml中配置sentinel地址

  1. spring:
  2. cloud:
  3. sentinel:
  4. #取消懒加载,默认须服务接口发生调用才加载到sentinel
  5. eager: true
  6. transport:
  7. #配置sentinel dashboard地址
  8. dashboard: localhost:7880
  9. #sentinel数据传输端口,默认从8719开始一直往下找
  10. port: 8719

启动服务,刷新sentinel控制台,可以看到服务已经载入sentinel。此时可以通过控制台对服务接口进行各种控制。

sentinel异常处理

当违反规则时,出来的异常信息页面不够友好和统一,我们可以通过设置统一的异常处理类,针对不同规则显示不同异常信息。

  1. @Component
  2. public class SentinelBlockExceptionHandler implements BlockExceptionHandler {
  3. @Override
  4. public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws Exception {
  5. Map rs = new HashMap();
  6. rs.put("code", 500);
  7. rs.put("data", null);
  8. if (e instanceof FlowException) {
  9. rs.put("msg", "接口限流了");
  10. } else if (e instanceof DegradeException) {
  11. rs.put("msg", "服务降级了");
  12. } else if (e instanceof ParamFlowException) {
  13. rs.put("msg", "参数限流了");
  14. } else if (e instanceof AuthorityException) {
  15. rs.put("msg", "权限规则不过");
  16. } else if (e instanceof SystemBlockException) {
  17. rs.put("msg", "系统保护");
  18. }
  19. httpServletResponse.setContentType("application/json;charset=utf-8");
  20. httpServletResponse.getWriter().write(JSON.toJSONString(rs, SerializerFeature.WriteMapNullValue));
  21. }
  22. }

也可以通过 @SentinelResource 的方式进行异常处理,@SentinelResource资源的异常处理有两种方式:

  • blockHandler:sentinel定义的失败调用或限制调用,若本次访问被限流或服务降级,则调用blockHandler指定的接口
  • fallback:失败调用,若本接口出现未知异常,则调用fallback指定的接口。

当两个都配置时,也就是当出现sentinel定义的异常时,调用blockHandler,出现其它异常时,调用fallback。

Sentinel监控

不需任何配置,sentinel中可以实时观察到当前服务接口的访问情况。

Sentinel流控

在Sentinel的流控规则中,

  • 阈值类型有:QPS、并发线程数
  • 流控模式有:直接、关联、链路
  • 流控效果有:直接失败、warm up、排队等待

阈值类型释义:

  • QPS:每秒钟查询的次数达到阈值时进行限流。
  • 并发线程数:当某个资源的线程数并发阈值时进行限流。

流控模式释义:

  • 直接:根据调用来源进行限流,默认为default,即针对所有的来源(我不是针对谁)。
  • 关联:指资源同时被两个接口访问时,如果其中一个接口超过qps阈值时,可以对另一个接口进行限流(用于某接口提高优先级)。
  • 链路:指资源同时被两个接口访问时,如果超过阈值时,可以只对其中一个接口进行限流(我只是针对你)。

流控效果释义:

  • 快速失败:当QPS超过任意规则的阈值后,新的请求就会被立即拒绝,拒绝方式为抛出FlowException。这种方式适用于对系统处理能力确切已知的情况下,比如通过压测确定了系统的准确水位时。
  • Warm Up:预热/冷启动方式,当系统长期处于低水位的情况下,当流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮。通过"冷启动",让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮。
  • 排队等待:排队等待即为匀速排队,该方式会严格控制请求通过的间隔时间,也即是让请求以均匀的速度通过,对应的是漏桶算法。


Sentinel熔断

Sentinel的熔断策略有:慢调用比例、异常比例、异常数

  • 慢调用比例:在统计时长内,达到最小请求数,且慢请求数(超过最大RT)与总请求数的比例超过阈值时,进行一定时长的熔断。
  • 异常比例:在统计时长内,达到最小请求数,且异常请求数与总请求数的比例超过阈值时,进行一定时长的熔断。
  • 异常数:在统计时长内,达到最小请求数,且异常请求数超过阈值时,进行一定时长的熔断。

* Sentinel1.8后熔断才有半开状态,默认的熔断行为是抛出DegradeException

Sentinel热点

热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。(需要额外引入sentinel-parameter-flow-control依赖)


系统规则

系统保护规则是从应用级别的入口流量进行控制,从单台机器的总体 Load、RT、入口 QPS 、CPU 使用率和线程数五个维度监控应用数据,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。 系统保护规则是应用整体维度的,而不是资源维度的,并且仅对入口流量 (进入应用的流量) 生效。

  • Load(仅对 Linux/Unix-like 机器生效):当系统 load1 超过阈值,且系统当前的并发线程数超过 系统容量时才会触发系统保护。系统容量由系统的 maxQps * minRt 计算得出。设定参考值一般 是 CPU cores * 2.5。
  • RT:当单台机器上所有入口流量的平均 RT 达到阈值即触发系统保护,单位是毫秒。
  • 线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。
  • 入口 QPS:当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护。
  • CPU使用率:当单台机器上所有入口流量的 CPU使用率达到阈值即触发系统保护。


授权规则

授权规则可以对请求方来源做判断和控制,有白名单和黑名单两种方式。

  • 白名单:来源(origin)在白名单内的调用者允许访问
  • 黑名单:来源(origin)在黑名单内的调用者不允许访问

Sentinel是通过RequestOriginParser这个接口的parseOrigin来获取请求的来源的(如给请求头设置origin=gateway)。
注意,如果配置了黑名单,且请求来源存在黑名单中,则拒绝请求,如果配置了白名单,且请求来源存在白名单中则放行。Sentinel 不支持一个黑白名单规则同时配置黑名单和白名单,因此不存优先级的问题。如果请求中没有Origin,则授权规则限流无效。

集群流控

集群流控可以精确地控制整个集群的调用总量,结合单机限流兜底,可以更好地发挥流量控制的效果。(需要额外开发)

持久化

大部分sentinel的规则持久化可以使用nacos实现,但是如果本身不想使用nacos,就为了sentinel的持久化而引入nacos,所以可以改为使用mysql进行持久化,持久化的规则有:授权规则、降级规则、流控规则、热点规则、系统规则。

Gateway

Gateway概念

简单来说,网关 = 路由 + 过滤。

常见的网关有Zuul、Gateway。Zuul是比较早期的一代网关,目前已经停止维护了,所以现在更多使用的是Gateway网关。简单说下二者区别:

Zuul:使用的是阻塞式的 API,不支持长连接,比如 websockets。底层是servlet;Zuul处理的是http请求;没有提供异步支持,流控等均由hystrix支持

Gateway:底层依然是servlet,但使用了webflux,多嵌套了一层框架;提供了异步支持,提供了负载均衡、流量控制

gateway有三个核心概念:

  • Route(路由):路由是构建网关的基本模块,它由ID,目标URI,一系列的断言和过滤器组成,如果断言为true则匹配该路由
  • Predicate(断言):开发人员可以匹配HTTP请求中的所有内容(例如请求头或请求参数),如果请求与断言相匹配则进行路由
  • Filter(过滤):指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由前或者之后对请求进行修改

使用网关后,一般的请求流程即为  web -> nginx -> gateway网关 -> service

下面记录Gateway网关的使用。

Gateway路由

第一步:引入依赖

  1. <dependency>
  2. <groupId>com.alibaba.cloud</groupId>
  3. <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
  4. </dependency>
  5. <dependency>
  6. <groupId>com.alibaba.cloud</groupId>
  7. <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
  8. </dependency>
  9. <dependency>
  10. <groupId>org.springframework.cloud</groupId>
  11. <artifactId>spring-cloud-starter-gateway</artifactId>
  12. </dependency>

第二步:配置注册中心

在bootstrap.yml中配置注册中心地址,参考nacos

  1. spring:
  2. cloud:
  3. nacos:
  4. discovery:
  5. server-addr: localhost:8848
  6. config:
  7. server-addr: localhost:8848

第三步:配置路由

#方式一:动态路由

  1. spring:
  2. cloud:
  3. gateway:
  4. discovery:
  5. locator:
  6. enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由

开启从注册中心动态创建路由的功能,gateway即可利用微服务名进行路由。如直接通过gateway端口 + 微服务名 + 微服务接口地址 就能直接访问到该微服务上的接口。

#方式二:静态路由

  1. spring:
  2. cloud:
  3. gateway:
  4. routes:
  5. - id: user_service #路由的ID,没有固定规则但要求唯一,建议配合服务名
  6. uri: http://localhost:8080 #匹配后提供服务的路由地址,http不能少
  7. predicates:
  8. - Path=/user/** # 断言,路径相匹配的进行路由,多个匹配时使用第一个
  9. - id: order service
  10. uri: http://localhost:8081
  11. predicates:
  12. - Path=/order/**

通过gateway端口 + 微服务接口地址,只要满足 断言条件,也能访问到具体微服务上的接口。不过这样的硬编码对路由更新不太友好,需要重启服务才能更新路由。

最常用的断言 predicates 有 Path、Host、After、Before、Cookie、Header、Method等,能满足绝大部分的情况。

#方式三:代码配置

  1. @Configuration
  2. public class GateWayConfig {
  3. /**
  4. * 配置了一个id为your_route_name的路由规则:
  5. * 当访问地址 gateway服务端口/guonei时,会转发到地址:http://news.baidu.com/guonei
  6. */
  7. @Bean
  8. public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
  9. RouteLocatorBuilder.Builder routes = builder.routes();
  10. routes.route("your_route_name",
  11. r -> r.path("/guonei").uri("http://news.baidu.com/guonei")
  12. ).build();
  13. return routes.build();
  14. }
  15. }

代码配置的方法与写在配置文件中的静态路由方式基本一致,实现效果也一样。

Gateway过滤器

使用过滤器,可以在请求被路由前或者之后对请求进行修改。

只有两种种类的过滤器:GatewayFilter(单一)、GlobalFilter(全局),默认的内置过滤器加起来已经有40多种。下面记录自定义一个全局过滤器 GlobalFilter 来使用。通过该全局过滤器实现访问日志的打印。

  1. @Component
  2. @Slf4j
  3. public class LogGlobalFilter implements GlobalFilter, Ordered {
  4. @Override
  5. public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
  6. String userName = exchange.getRequest().getQueryParams().getFirst("userName");
  7. log.info("userName " + userName + " 访问 " + exchange.getRequest().getURI());
  8. if (userName == null) {
  9. //非法用户,结束访问。
  10. log.info("非法用户访问。来源ip:" + exchange.getRequest().getHeaders().getFirst("从nginx中传来的来源ip"));
  11. ServerHttpResponse response = exchange.getResponse();
  12. DataBuffer buffer = response.bufferFactory().wrap("非法登录".getBytes(StandardCharsets.UTF_8));
  13. response.setStatusCode(HttpStatus.UNAUTHORIZED);
  14. response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
  15. return response.writeWith(Mono.just(buffer));
  16. }
  17. return chain.filter(exchange);
  18. }
  19. //拦截器的顺序,越小越排前面
  20. @Override
  21. public int getOrder() {
  22. return 0;
  23. }
  24. }

Sleuth

一个分布式系统往往有很多个服务单元。由于服务单元数量众多,业务的复杂性,如果出现了错误和异常,很难去定位。主要体现在,一个请求可能需要调用很多个服务,而内部服务的调用复杂性,决定了问题难以定位。所以微服务架构中,必须实现分布式链路追踪,去跟进一个请求到底有哪些服务参与,参与的顺序又是怎样的,从而达到每个请求的步骤清晰可见,出了问题,很快定位。

目前,链路追踪组件有Google的Dapper,Twitter 的Zipkin,以及阿里的Eagleeye (鹰眼)等,它们都是非常优秀的链路追踪开源组件。

下面记录如何在Spring Cloud Sleuth中集成Zipkin。在Spring Cloud Sleuth中集成Zipkin非常的简单,只需要引入相应的依赖和做相关的配置即可。

Seata

使用微服务后,单体应用被拆分成微服务应用,原来的三个模块被拆分成三个独立的应用,分别使用三个独立的数据源,业务操作需要调用三个服务来完成。此时每个服务内部的数据一致性由本地事务来保证, 但是全局的数据一致性问题没法保证。

一次业务操作需要跨多个数据源或需要跨多个系统进行远程调用,就会产生分布式事务问题

Seata是一款开源的的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。

记录-数据监控(Prometheus + Grafna)

Prometheus 是一套开源的监控&报警&时间序列数据库的组合。

Grafana 是一个开源的监控数据分析和可视化套件。

通过 Prometheus + Grafana,我们就可以很方便的对我们的数据进行采集和监控。

Prometheus的工作流程

  • 数据来源:prometheus server 定期从配置好的 jobs 或者 exporters 中拉 metrics。或者接受来自pushgateway发过的 metrics,或者从其他的 prometheus server 中拉取 metrics。
  • 图形显示:在图形界面中,可视化采集数据,可以使用别人写好的grafana模板。类似于通过kibana将 es 中的数据可视化。
  • 告警情况:prometheus server 在本地存储收集到的metrics,并运行已经定义好的arlt.rules,记录新的时间序列或者向alertmanager推送报警,Alertmanager根据配置文件,对接受的警报进行处理,发出告警
     

部署Prometheus

  1. #下载:https://prometheus.io/download/#prometheus
  2. wget https://github.com/prometheus/prometheus/releases/download/v2.37.5/prometheus-2.37.5.linux-amd64.tar.gz
  3. #解压:
  4. tar -zxvf prometheus-2.37.5.linux-amd64.tar.gz
  5. mv prometheus-2.37.5.linux-amd64 /opt/prometheus
  6. #启动:
  7. cd /opt/prometheus
  8. ./prometheus &
  9. #访问:(默认端口9090,修改时可在启动命令后添加 --web.listen-address=:9091)
  10. ip:9090
  11. #使用systemd管理prometheus(推荐)
  12. cat > /etc/systemd/system/prometheus.service <<EOF
  13. [Unit]
  14. Description=prometheus Server
  15. After=network.target
  16. [Install]
  17. WantedBy=multi-user.target
  18. [Service]
  19. Type=simple
  20. ExecStart=/opt/prometheus/prometheus --config.file=/opt/prometheus/prometheus.yml --storage.tsdb.path=/data/prometheus --web.enable-admin-api --web.enable-lifecycle
  21. EOF
  22. systemctl daemon-reload
  23. systemctl start/stop/status prometheus

--web.listen-address=:9091  该参数为指定启动端口

--storage.tsdb.path=/data/prometheus  指定promethus数据的存储路径

--web.enable-admin-api   该参数为开启API服务,开启后可通过http请求管理监控数据

--web.enable-lifecycle   该参数为开启动态加载配置文件(修改配置后需要使用curl -X POST http://ip:port/-/reload 热更新)

在promethus页面,有

  • Alerts:查看告警情况,
  • Graph:查看数据情况,
  • Status:查看监控的配置、资源情况,

部署Grafna

  1. #下载:
  2. wget https://dl.grafana.com/enterprise/release/grafana-enterprise-9.0.4.linux-amd64.tar.gz
  3. #解压:
  4. tar -zxvf grafana-enterprise-9.0.4.linux-amd64.tar.gz
  5. mv grafana-9.0.4 /opt/grafana
  6. #启动
  7. cd /opt/grafana
  8. ./bin/grafana-server &
  9. #浏览器访问:(默认端口3000,可修改conf/defaults.ini#http_port,初始的账号密码为:admin。 第一次登录完成后需要修改密码。)
  10. ip:3000
  11. #使用systemd管理grafna(推荐)
  12. cat > /etc/systemd/system/grafana.service <<EOF
  13. [Unit]
  14. Description=grafana server
  15. [Install]
  16. WantedBy=multi-user.target
  17. [Service]
  18. ExecStart=/opt/grafana/bin/grafana-server -homepath=/opt/grafana
  19. ExecReload=/bin/kill -HUP $MAINPID
  20. KillMode=process
  21. EOF
  22. systemctl daemon-reload
  23. systemctl start/stop/status grafana

在 grafna 页面,主要是要配置自己需要的 Dashboard,以便快速查看需要的数据。(自定义创建或者使用模板导入)

Prometheus监控

prometheus server 定期从配置好的 jobs 或者 exporters 中拉 metrics。或者接受来自pushgateway发过的 metrics,或者从其他的 prometheus server 中拉取 metrics。prometheus 官网上已经提供了各种各样的exporter。如 MySQL Exporter(监控mysql), Redis Exporter(监控redis), Kafka Exporter(监控kakfa),  RabbitMQ Exporter(监控rabbitmq), Nginx Exporter(监控nginx),Node Exporter(监控Linux主机)等。

所有可以向Prometheus提供监控样本数据的程序都可以被称为一个Exporter

监控Prometheus状态

vim prometheus.yml 可看到如下内容:默认就是只配置了promethus自身状态的监控。在 prometheus 页面的 Status - Targets 可以查看到 job = promethus 的状态 state 为UP。

  1. global:    #全局配置
  2.   scrape_interval: 15s #设置每15s采集数据一次,默认1分钟
  3.   evaluation_interval: 15s # 每15秒计算一次规则。默认1分钟
  4. alerting:    #告警配置
  5.   alertmanagers:
  6.     - static_configs:
  7.         - targets:
  8. rule_files:    #告警规则
  9. scrape_configs:    #配置数据源,称为target,每个target用job_name命名
  10.   - job_name: "prometheus"    #默认job即为promethus本身(可以将127.0.0.1改为自身ip)
  11.     static_configs:
  12.       - targets: ["127.0.0.1:9090"]

监控Linux主机

第一步:部署 node_exporter 

主机数据通过node_exporter来采集。

  1. #下载:https://prometheus.io/download/#node_exporter
  2. wget https://github.com/prometheus/node_exporter/releases/download/v1.3.1/node_exporter-1.3.1.linux-amd64.tar.gz
  3. #解压:并移动到指定目录
  4. tar xzvf node_exporter-1.3.1.linux-amd64.tar.gz
  5. mv node_exporter-1.3.1.linux-amd64 /opt/node_exporter
  6. #添加systemd管理
  7. cat > /etc/systemd/system/node_exporter.service <<EOF
  8. [Unit]
  9. Description=node_exportier
  10. [Install]
  11. WantedBy=multi-user.target
  12. [Service]
  13. ExecStart=/opt/node_exporter/node_exporter
  14. EOF
  15. #启动node_exporter
  16. systemctl start/stop/status node_exporter

第二步:prometheus配置采集node_exporter

vim prometheus.yml 后添加对node_exporter的监控 job(其中127.0.0.1需换成真实ip)

  1. - job_name: "nodes" #监控本机基础数据(磁盘/内存/CPU/网络..)
  2. static_configs:
  3. - targets: ["127.0.0.1:9100"]

也可使用以下语句一建添加配置:

  1. cat >> /opt/prometheus/prometheus.yml <<EOF
  2. - job_name: "node_exporter" #监控本机基础数据(磁盘/内存/CPU/网络..)
  3. static_configs:
  4. - targets: ["`ifconfig eth0|grep 'inet '|awk '{print $2}'`:9100"]
  5. EOF

修改后检查和更新prometheus.yml

  1. /opt/prometheus/promtool check config prometheus.yml
  2. curl -X POST http://127.0.0.1:9090/-/reload

更新后可以在 prometheus 页面的 Status - Targets 查看到最新的 jobs。

第三步:grafna展示数据

3.1 添加数据源

找到Configuration - Add data source,选择对应的数据源,如Prometheus,输入其地址即可。

3.2 添加仪表板

可以选择从官网中导入仪表板模板 选择Dashboards – import - 9276 ,并选择数据源为Prometheus即可查看 node_exporter 采集的主机基础监控(cpu/内存/磁盘/网络)数据。

可以选择从官网中导入仪表板模板 选择Dashboards – import - 1860 ,并选择数据源为Prometheus即可查看 node_exporter 采集的监控数据。

监控MySql服务

  1. #0)mysql库创建相应用户并赋权:
  2. GRANT REPLICATION CLIENT, PROCESS ON *.* TO 'mysqld_exporter'@'127.0.0.1' identified by '123456';
  3. GRANT SELECT ON performance_schema.* TO 'mysqld_exporter'@'127.0.0.1';
  4. flush privileges;
  5. #1)下载解压:https://prometheus.io/download/#mysqld_exporter
  6. wget https://github.com/prometheus/mysqld_exporter/releases/download/v0.14.0/mysqld_exporter-0.14.0.linux-amd64.tar.gz
  7. tar -zxvf mysqld_exporter-0.14.0.linux-amd64.tar.gz
  8. mv mysqld_exporter-0.14.0.linux-amd64 /opt/mysqld_exporter
  9. #2)配置.my.cnf。即为mysql_exporter添加获取mysql监控数据的账号,如果不写端口,默认为3306(根据自己安装的mysql实际情况填写),.my.cnf默认放置在启动用户的家目录,启动时无需指定;也可以随意放置在任意目录,在启动时通过 --config.my-cnf={conf_dir}/.my.cnf指定配置文件。mysql_exporter默认启动端口为9104,可以通过 web.listen-address指定。
  10. cat > /opt/mysqld_exporter/.my.cnf <<EOF
  11. [client]
  12. user=mysqld_exporter
  13. password=123456
  14. port=13306
  15. EOF
  16. #3)添加systemd管理
  17. cat > /etc/systemd/system/mysqld_exporter.service <<EOF
  18. [Unit]
  19. Description=mysqld_exporter
  20. [Install]
  21. WantedBy=multi-user.target
  22. [Service]
  23. ExecStart=/opt/mysqld_exporter/mysqld_exporter --config.my-cnf=/opt/mysqld_exporter/.my.cnf
  24. EOF
  25. #4)启动mysqld_exporter
  26. systemctl daemon-reload
  27. systemctl start/stop/status mysqld_exporter
  28. #访问测试
  29. curl localhost:9104/metrics
  30. #5)配置prometheus.yml对mysql_exporter的采集(ip修改为真实ip):
  31. cat >> /opt/prometheus/prometheus.yml <<EOF
  32. - job_name: "mysql_export" # 监控mysql数据
  33. static_configs:
  34. - targets: ["127.0.0.1:9104"]
  35. EOF
  36. #6)重载配置
  37. /opt/prometheus/promtool check config prometheus.yml
  38. curl -X POST http://127.0.0.1:9090/-/reload
  39. #7)配置仪表盘
  40. Grafana - Dashboard - Import - 763

监控Redis服务

  1. #1)下载解压:https://prometheus.io/download/
  2. wget https://github.com/oliver006/redis_exporter/releases/download/v1.45.0/redis_exporter-v1.45.0.linux-amd64.tar.gz
  3. tar -zxvf redis_exporter-v1.45.0.linux-amd64.tar.gz
  4. mv redis_exporter-v1.45.0.linux-amd64 /opt/redis_exporter
  5. #2)添加systemd管理
  6. cat > /etc/systemd/system/node_exporter.service <<EOF
  7. redis_exporter-v1.45.0.linux-amd64/
  8. [Unit]
  9. Description=redis_exporter
  10. [Install]
  11. WantedBy=multi-user.target
  12. [Service]
  13. ExecStart=/opt/redis_exporter/redis_exporter -redis.addr 127.0.0.1:6379 -web.listen-address 0.0.0.0:9122
  14. EOF
  15. #3)启动node_exporter
  16. systemctl daemon-reload
  17. systemctl start/stop/status redis_exporter
  18. #4)配置prometheus.yml对redis_exporter的采集(ip修改为真实ip):
  19. cat >> /opt/prometheus/prometheus.yml <<EOF
  20. - job_name: "redis_export" # 监控redis数据
  21. static_configs:
  22. - targets: ["127.0.0.1:9122"]
  23. EOF
  24. #5)重载配置
  25. /opt/prometheus/promtool check config prometheus.yml
  26. curl -X POST http://127.0.0.1:9090/-/reload
  27. #6)配置仪表盘
  28. Grafana - Dashboard - Import - 763

记录-Netty

Netty 是由 JBOSS 提供的一个 Java 开源框架。Netty 提供异步的、基于事件驱动的网络应用程序框架,用以快速开发高性能、高可靠性的网络 IO 程序,是目前最流行的 NIO 框架,Netty 在互联网领域、大数据分布式计算领域、游戏行业、通信行业等获得了广泛的应用,知名的 Elasticsearch 、Dubbo 框架内部都采用了 Netty。

记录-分布式调度(XxlJob)

常见的分布式任务调度框架有:Elastic-job、lts、Quartzxxl-job、TBSchedule等。

下面记录 xxl-job 的使用。

记录-流程引擎(Activity)

工作流(Workflow),就是通过计算机对业务流程自动化执行管理。它主要解决的是“使在多个参与者之间按照某种预定义的规则自动进行传递文档、信息或任务的过程,从而实现某个预期的业务目标,或者促使此目标的实现”。

Activiti是一个工作流引擎, activiti可以将业务系统中复杂的业务流程抽取出来,使用专门的建模语言BPMN2.0进行定义,业务流程按照预先定义的流程进行执行,实现了系统的流程由activiti进行管理,减少业务系统由于流程变更进行系统升级改造的工作量,从而提高系统的健壮性,同时也减少了系统开发维护成本。


记录-服务编排(Conductor)

每个微服务都提供了n多个接口,当有新功能需要开发时,传统做法是根据需求来进行定制化开发。如新写一个业务类,在业务类里面进行具体调用(服务A->服务B->服务C),但是这种定制化开发的方式不够灵活,第二天可能需求又变为 服务A->服务C->服务B->服务D了,这时我们就得又重新修改业务类重新发版上线。

对此情况,服务编排(Conductor)就出现了。它要求每个服务提供最细粒度的接口,然后可以基于这些原子粒度的接口进行一系列的编排(以json方式),并且提供了超时、重试、异常处理等机制,使服务调用更加灵活稳健。无论你的需求怎么变,我只需要改一下编排过程即可!

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/码创造者/article/detail/814245
推荐阅读
相关标签
  

闽ICP备14008679号