MySQL多实例启动

一台服务器安装MySQL,用于测试。同时会用于NextCloud和Wordpress的数据库。NextCloud和Wordpress要经常备份,而且以后可能会迁移。所以隔离不同情景的数据,用多实例启动。便于管理,和提高性能。

首先初始化要用到的数据库:

mysqld --initialize-insecure --datadir=/home/mysql/3307/data --user=mysql;
mysqld --initialize-insecure --datadir=/home/mysql/3308/data --user=mysql;
mysqld --initialize-insecure --datadir=/home/mysql/3309/data --user=mysql;

MySQL自带了mysqld_multi工具运行多个实例。以下是我的配置文件:

[mysqld_multi]
mysqld = /usr/bin/mysqld_safe
mysqladmin = /usr/bin/mysqladmin
user = root
#password = 

# nextcloud
[mysqld3307]
user		= mysql
pid-file	= /home/mysql/3307/mysqld.pid
socket		= /home/mysql/3307/mysqld.sock
port		= 3307
datadir		= /home/mysql/3307/data
lc-messages-dir	= /usr/share/mysql
skip-external-locking
bind-address		= 0.0.0.0
key_buffer_size		= 16M
max_allowed_packet	= 16M
thread_stack		= 192K
thread_cache_size       = 8
myisam-recover-options  = BACKUP
query_cache_limit	= 1M
query_cache_size        = 16M
expire_logs_days	= 10
max_binlog_size   = 100M
innodb_buffer_pool_size = 1G
innodb_io_capacity = 4000

# wordpress
[mysqld3308]
user		= mysql
pid-file	= /home/mysql/3308/mysqld.pid
socket		= /home/mysql/3308/mysqld.sock
port		= 3308
datadir		= /home/mysql/3308/data
lc-messages-dir	= /usr/share/mysql
skip-external-locking
bind-address		= 0.0.0.0
key_buffer_size		= 16M
max_allowed_packet	= 16M
thread_stack		= 192K
thread_cache_size       = 8
myisam-recover-options  = BACKUP
query_cache_limit	= 1M
query_cache_size        = 16M
expire_logs_days	= 10
max_binlog_size   = 100M
innodb_buffer_pool_size = 1G
innodb_io_capacity = 4000
innodb_io_capacity = 4000

[mysqld3309]
user		= mysql
pid-file	= /home/mysql/3309/mysqld.pid
socket		= /home/mysql/3309/mysqld.sock
port		= 3309
datadir		= /home/mysql/3309/data
lc-messages-dir	= /usr/share/mysql
skip-external-locking
bind-address		= 0.0.0.0
key_buffer_size		= 16M
max_allowed_packet	= 16M
thread_stack		= 192K
thread_cache_size       = 8
myisam-recover-options  = BACKUP
query_cache_limit	= 1M
query_cache_size        = 16M
expire_logs_days	= 10
max_binlog_size   = 100M
innodb_buffer_pool_size = 1G
innodb_io_capacity = 4000

配置多实例,每个实例命名为mysqld*,这里我三个实例配置分别对应[mysql3307],[mysql3308],[mysql3309]。

继续阅读“MySQL多实例启动”

Ubuntu Server初始化MySQL的坑

执行MySQL初始化:

mysqld --initialize --datadir=/home/mysql/3307/data --user=mysql

报错,提示不能创建目录:mysqld: Can’t create directory ‘/home/mysql/3307/data/’ (Errcode: 17 – File exists)

建立目录再执行初始化命令,提示没有权限:mysqld: Can’t create directory ‘/home/mysql/3307/data/’ (Errcode: 13 – Permission denied)

该目录已经属于mysql用户,且有读写执行的权限。设置成0777,依然报错。

相同的命令,曾在CentOS上,自己编译的MySQL用过,不应该是命令的问题。该机器是我的笔记本,安装的是Ubuntu Server 18.04,MySQL是用官方的apt源安装的。经过一番查找,发现是一个叫AppArmor的玩意搞鬼。

Ubuntu有个AppArmor,是一个Linux系统安全应用程序,类似于Selinux,AppArmor默认安全策略定义个别应用程序可以访问系统资源和各自的特权,如果不设置服务的执行程序,即使你改了属主属组并0777权限,也是对服务起不到作用。

继续阅读“Ubuntu Server初始化MySQL的坑”

Spring Boot集成Cache和Redis

构建基于Spring Boot 2.X应用,使用Cache,需要引入:

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-cache</artifactId>
</dependency>

以前开发Spring用EhCache来做缓存。在做集群或分布式时,还是Redis比较好用。引入Spring Data Redis如下:

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

注意这里会有jedis依赖。

在applicatin.yml配置Redis和Cache:

spring:
  cache:
    redis:
      cache-names:
      cache-null-values: false
      key-prefix: spring:cache
      time-to-live: 0
      use-key-prefix: true
    type: # Cache type. By default, auto-detected according to the environment.
  redis:
    database: 5
    url: # Connection URL. Overrides host, port, and password. User is ignored. Example: redis://user:password@example.com:6379
    host: 127.0.0.1
    port: 6379
    password:
    timeout: 3000
    jedis:
      pool:
        max-active: 200
        max-idle: 10
        max-wait: -1
        min-idle: 0

更多配置属性可参考Spring Boot官方说明:

新建RedisConfig类,增加如下方法:

/**
 * 定义缓存数据 key 生成策略的bean
 * 包名+类名+方法名+所有参数
 * @return
 */
@Bean
public KeyGenerator wiselyKeyGenerator(){
	return new KeyGenerator() {
		@Override
		public Object generate(Object target, Method method, Object... params) {
			StringBuilder buff = new StringBuilder();
			buff.append(target.getClass().getName());
			buff.append(method.getName());
			for (Object obj : params) {
				buff.append(obj.toString());
			}
			return buff.toString();
		}
	};
}
继续阅读“Spring Boot集成Cache和Redis”

VI转换文件编码

在Windows平台写Shell脚本,放到Linux下执行失败。控制台输出:bin/sh^M: bad interpreter: No such file or directory

这是因为不同系统的编码格式引起的。

用vi打开文件,利用如下命令查看文件格式
:set ff 或 :set fileformat

可以看到如下信息
fileformat=dos 或 fileformat=unix

利用如下命令修改文件格式
:set ff=unix 或 :set fileformat=unix

:wq (存盘退出)

再次执行,成功。

柴米油盐酱醋茶

“如果我不曾见过太阳,我本可以忍受黑暗”

我们在一起生活有段时间了,时间未曾磨去那份炙热,却开始慢慢展现出自己的另一面。或许最终两个面合起来才是完整的自己。

片段一

晚上我们一起买菜做饭。因为是两个人的饭,她只拿出了三根葱放在桌上,然后拿着菜去厨房炒。我自然是不能偷懒,把桌上的果皮啥的扔垃圾桶。我把那不起眼的三根葱也一起扔了。她回来从垃圾桶里翻出来的,然后质问我,刚刚切好准备种花盆的发芽的姜哪里去了。我说没看到(是真的没看到),她不信,说我肯定丢垃圾桶里了,然后就要翻出来。

垃圾桶里的东西湿答答黏糊糊多恶心呐,翻出来的垃圾还不是我收拾。我就说,“算我扔了,别找了”。“我还冤枉你了?”最后翻出姜,又是一顿河东狮吼。

“我去做饭,你把姜种下去”。我看着发芽的姜,是一个7子型的,心想“姜是长土里的,切口应该朝下,不然就长出土了”。她做完饭一看,姜的芽是横放在土上的,又是一顿教育。我都不敢还嘴,还去百度查了种姜的图片。

片段二

下雨了,外面打雷,她要我去收衣服。其实我在家里是什么都没做过,可是我是一个会思考,而且考虑很周全的一个人。我站在阳台上看着天空闪电,想起我的家。我家是五层有装避雷针,但是这里我住在10层,不知道楼顶有避雷针没。以前就有下雨打雷,雷落在山上,通过山路的铁护栏导电击死山下护栏旁的人,还有树下避雨,雨中打电话被雷击中的。我现在要是拿个铁收衣杆举着,不是引雷吗?

就这么想了几十秒,一场突然的暴雨把衣服都打湿了。然后……嗯,连解释都是我的错。

片段三

她有点咳嗽,我买了999感冒灵冲剂。要不是她要求,我是不会买冲剂的,太难喝了。我烧好水,用多年冲牛奶咖啡的经验,满满地冲了一大杯。结果都懂的,又涨知识了。

最近总是各种被嫌弃,还不允许我解释。解释都被当作借口。我想起多年前我妈的预言,“看你这样以后是个天天被堂客撅的”。不过即使如此,她还是会给我准备晚餐,有时也会准备早餐。而我,也在努力学会照顾人。

Spring Boot打包分离依赖JAR和配置文件

开发Spring Boot应用,用Spring Boot的Maven插件打包部署,会得到一个很大的JAR文件。该JAR文件包含了所有依赖JAR和配置文件。有时升级只需更新某个模块或修改某项配置,所以需要把它们分离出来。

在pom.xml添加如下插件,配置。

<plugins>
	<plugin>
		<groupId>org.apache.maven.plugins</groupId>
		<artifactId>maven-compiler-plugin</artifactId>
		<configuration>
			<source>1.8</source>
			<target>1.8</target>
			<encoding>UTF-8</encoding>
		</configuration>
	</plugin>
	<plugin>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-maven-plugin</artifactId>
		<configuration>
			<includes>
				<include>
					<groupId>null</groupId>
					<artifactId>null</artifactId>
				</include>
			</includes>
			<layout>JAR</layout>
			<addResources>true</addResources>
		</configuration>
		<executions>
			<execution>
				<goals>
					<goal>repackage</goal>
				</goals>
				<configuration>
					<!--配置jar包特殊标识 配置后,保留原文件,生成新文件 *-run.jar -->
					<!--配置jar包特殊标识 不配置,原文件命名为 *.jar.original,生成新文件 *.jar -->
					<!--<classifier>run</classifier>-->
				</configuration>
			</execution>
		</executions>
	</plugin>

	<!--打包jar-->
	<plugin>
		<groupId>org.apache.maven.plugins</groupId>
		<artifactId>maven-jar-plugin</artifactId>
		<configuration>
			<!--不打包资源文件-->
			<excludes>
				<exclude>*.**</exclude>
				<exclude>*/*.xml</exclude>
			</excludes>
			<archive>
				<manifest>
					<addClasspath>true</addClasspath>
					<!--MANIFEST.MF 中 Class-Path 加入前缀-->
					<classpathPrefix>lib/</classpathPrefix>
					<!--jar包不包含唯一版本标识-->
					<useUniqueVersions>false</useUniqueVersions>
					<!--指定入口类-->
					<mainClass>cn.payadd.admin.AdminApplication</mainClass>
				</manifest>
				<manifestEntries>
					<!--MANIFEST.MF 中 Class-Path 加入资源文件目录-->
					<Class-Path>./resources/</Class-Path>
				</manifestEntries>
			</archive>
			<outputDirectory>${project.build.directory}</outputDirectory>
		</configuration>
	</plugin>

	<!--拷贝依赖 copy-dependencies-->
	<plugin>
		<groupId>org.apache.maven.plugins</groupId>
		<artifactId>maven-dependency-plugin</artifactId>
		<executions>
			<execution>
				<id>copy-dependencies</id>
				<phase>package</phase>
				<goals>
					<goal>copy-dependencies</goal>
				</goals>
				<configuration>
					<outputDirectory>
						${project.build.directory}/lib/
					</outputDirectory>
				</configuration>
			</execution>
		</executions>
	</plugin>

	<!--拷贝资源文件 copy-resources-->
	<plugin>
		<artifactId>maven-resources-plugin</artifactId>
		<executions>
			<execution>
				<id>copy-resources</id>
				<phase>package</phase>
				<goals>
					<goal>copy-resources</goal>
				</goals>
				<configuration>
					<resources>
						<resource>
							<directory>src/main/resources</directory>
						</resource>
					</resources>
					<outputDirectory>${project.build.directory}/resources</outputDirectory>
				</configuration>
			</execution>
		</executions>
	</plugin>
</plugins>

执行maven,编译结果如图:

lib目录是依赖jar,resources是配置文件目录。

参考:
https://my.oschina.net/u/2329318/blog/1933393

一个PhpMyAdmin的https错误

使用docker安装phpmyadmin,启动成功登录,出现提示:服务器和客户端上指示的 HTTPS 之间不匹配。这可能导致 phpMyAdmin 无法正常工作或存在安全风险。请修复您的服务器配置以正确指示 HTTPS。

以前phpmyadmin是直接安装在nginx里的,没有这个问题。现在用nginx做为代理。外部访问使用https,内部用http。

解决方法是修改参数:

$cfg[‘PmaAbsoluteUri’] = ‘https://pma.xxx.com’;

在docker中启动,可以在启动时指定环境参数(该参数在官方文档里没有但是有效):

-e PMA_ABSOLUTE_URI=https://pma.xxx.com

Java各种对象的区分

PO:持久对象 (persistent object),po(persistent object)就是在Object/Relation Mapping框架中的Entity,po的每个属性基本上都对应数据库表里面的某个字段。完全是一个符合Java Bean规范的纯Java对象,没有增加别的属性和方法。持久对象是由insert数据库创建,由数据库delete删除的。基本上持久对象生命周期和数据库密切相关。

VO:值对象(Value Object),通常用于业务层之间的数据传递,和PO一样也是仅仅包含数据而已。但应是抽象出的业务对象,可以和表对应,也可以不,这根据业务的需要。

表现层对象(View Object),主要对应展示界面显示的数据对象,用一个VO对象来封装整个界面展示所需要的对象数据。

BO:业务对象层的缩写(Business Object),封装业务逻辑的java对象,通过调用DAO方法,结合PO,VO进行业务操作。具体可以看网上的一个例子:

比如一个简历,有教育经历、工作经历、社会关系等等。
我们可以把教育经历对应一个PO,工作经历对应一个PO,社会关系对应一个PO。
建立一个对应简历的BO对象处理简历,每个BO包含这些PO。
这样处理业务逻辑时,我们就可以针对BO去处理。

DTO:数据传输对象(Data Transfer Object),是一种设计模式之间传输数据的软件应用系统。数据传输目标往往是数据访问对象从数据库中检索数据。数据传输对象与数据交互对象或数据访问对象之间的差异是一个以不具有任何行为除了存储和检索的数据(访问和存取器)。简单来说,当我们需要一个对象10个字段的内容,但这个对象总共有20个字段,我们不需要把整个PO对象全部字段传输到客户端,而是可以用DTO重新封装,传递到客户端。此时,如果这个对象用来对应界面的展现,就叫VO。

JavaBean:一种可重用组件,即“一次性编写,任何地方执行,任何地方重用”。满足三个条件①类必须是具体的和公共的②具有无参构造器③提供一致性设计模式的公共方法将内部域暴露成员属性。

有两种:一种是有用户界面(UI,User Interface)的JavaBean;还有一种是没有用户界面,主要负责处理事务(如数据运算,操纵数据库)的JavaBean。JSP通常访问的是后一种JavaBean。

继续阅读“Java各种对象的区分”

说出来本身就是一种解决问题的方法

以前遇到过一个同事,性格温和,入职时大家都很喜欢她。但渐渐地,总觉得哪里不对劲。一次执行一个大项目,分解任务到她时,她闷闷地说一句「哦」,急性子的同事忍不住追问,「行还是不行?」她说「行吧。」

搭档当时觉得有点儿不对劲,但也没多想,觉得都是职场人,既然应允了下来,应该会各自前行,为结果负责,而且每天坐在同一个办公室,遇到状况肯定会说的。

当时人少事多,大家也就各忙各的了。

一周之后,大家阶段性碰头,这才发现她工作倒是都做了,但完全不符合要求。

「我又不是没做,前几天还熬了通宵。」她很委屈。

比她更崩溃的,是那个搭档。「到这时候了,我们哪里来得及翻身调头!」

她一脸迷茫,「那怎么办,我又没经验,我也不知道怎么办啊。」

搭档忽然就意识到「怪怪的」感觉是什么。她从来不跟大家反馈,你觉得有问题去问她,她觉得「还好啊」。再多说几句,「那就改呗,我也没太清楚。」

她态度总是很好。可是我们都知道,有些东西不对。

「我们既然已经是同事,每天早上从一个城市的四面八方赶到同一个地点,选择走在一条路上。如果她能相信我们多一点,她就可以把她的顾虑和困扰说出来,那些东西本来就是我们整个团队一起扛的,她要相信我们可以接得住。」

说出「我不喜欢,我不行了,这一切不是我想要的」,本身就是一种解决方法。

破解WinRAR

最近WinRAR爆出一个存在十多年的漏洞,影响很大。全新的5.7版本已修复。

几年前WinRAR开始免费,但免费版会弹出广告。我在官方下载了最新WinRAR 5.7,然后导入注册文件。再打开WinRAR显示已注册。可WinRAR依然弹出广告。

源于强迫症,不肯屈服广告。但网上没有最新的正式破解版(有5.7 beta2版本),无奈只有自己动手了。

下载 Resource Hacker ,找到WinRAR安装目录,打开WinRAR.exe文件编辑。

找到字符串表格(String Table)展开,找到第80行。然后找到1277行,把内容删除。

点击编译,然后保存文个。用保存下来的文件替换安装目录下的WinRAR。

经测试,如果没有注册文件,广告弹窗依然会有,只是没有内容。这里我提供一份网上找来的注册文件:点击下载

把rarreg.key复制到和WinRAR.exe同一目录即可。

这下没有弹窗,终于清爽了。