0%

服务调用方式

  • 第一种方式:ribbon+restTemplate
  • 第二种方式:feign(默认集成ribbon)

ribbon是一个负载均衡客户端,可以很好的控制htt和tcp的一些行为。

建立服务提供者

  1. eurekaclient添加一个HelloControl接口类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @RestController
    public class HelloControl {

    @Value("${server.port}")
    String port;

    @RequestMapping("/hi")
    public String home(@RequestParam String name) {
    return "hi "+name+",i am from port:" +port;
    }
    }
  2. 启动两个eurekaclient配置vm启动参数-Dserver.port=8098端口分布为8099和8098。

建立服务消费者

  1. 新建springboot项目勾选如下

    • web->web
    • Could discovery-> eureka server
    • Could routing->ribbon
  2. RibbonrestApplication启动类添加注解@EnableEurekaClient

  3. Application.yml配置文件添加内容:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    server:
    port: 8093
    spring:
    application:
    name: ribbon-client
    eureka:
    client:
    service-url:
    defaultZone: http://127.0.0.1:8091/eureka/ #注意要加eureka,不然找不到
  4. RibbonrestApplication启动类添加负载均衡

    1
    2
    3
    4
    5
    @Bean  //spring ioc bean 依赖注入知识点(待补充)
    @LoadBalanced //负载均衡
    RestTemplate restTemplate(){
    return new RestTemplate();
    }
  5. 新建HelloService类消费服务

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    @Service
    public class HelloService {
    @Autowired
    RestTemplate restTemplate;

    public String hiService(String name){
    //eureka-client为服务提供者的spring.application.name=eureka-client
    return restTemplate.getForObject("http://eureka-client/hi?name="+name,String.class); //该url为服务提供者提供的接口
    }
    }
  6. 调用HelloService的服务,新建一个HelloControler

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @RestController
    public class HelloControler {
    @Autowired
    HelloService helloService;
    @RequestMapping(value = "/resthi") //访问入口
    public String hi(@RequestParam String name){
    return helloService.hiService(name); //调用消费服务
    }
    }
  7. 访问http://127.0.0.1:8093/resthi?name=32这个服务消费者提供的接口,不停刷新可以看到端口的变化,就说明了负载均衡起作用了。

总结图示

1
2
3
4
5
6
Note left of eurekaClient8099/8098: 服务提供者(n个)
Note left of eurekaService8091: 服务注册中心
Note left of ribbonClient8093: 服务消费者
eurekaClient8099/8098-->eurekaService8091: 注册
ribbonClient8093-->eurekaService8091: 注册
ribbonClient8093-->eurekaClient8099/8098: 通过ribbon负载均衡调用服务8099/8098

参考

史上最简单的SpringCloud教程 | 第二篇: 服务消费者(rest+ribbon)

使用Spring Cloud与Docker实战微服务

eureka

eureka是一个服务注册和发现模块

  1. 新建springboot工程,作为eureka服务注册中心,勾选如下选项

    • Cloud Discovery->Eureka Server
  2. Application类上添加注解@EnableEurekaServer声明注册中心

  3. Application.yml配置文件添加内容:

    1
    2
    3
    4
    5
    6
    7
    8
    server:
    port: 8091
    eureka:
    instance:
    hostname: 127.0.0.1 #指定该Eureka实例的主机名,本地默认127.0.0.1,部署docker时再试验
    client:
    register-with-eureka: false #Eureka默认也会作为客户端尝试注册,因此需禁用注册行为
    fetch-registry: false
  4. 访问http://127.0.0.1:8091可以进入管理页面查看注册了那些服务

  5. 重复第一步,作为eureka客户端

  6. Application类上添加注解@EnableEurekaClient声明客户端

  7. Application.yml配置文件添加内容:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    server:
    port: 8092
    spring:
    application:
    name: eureka-client
    eureka:
    client:
    service-url:
    defaultZone: http://127.0.0.1:8091/eureka/ #注意要加eureka,不然找不到
  8. 再进入eureka服务注册中心就可以看到Application名为eureka-client的客户端

eureka高可用(未实践)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#多节点固定模式,文件名application-peer1.yml
spring:
application:
name: ${APPLICATION_NAME:eureka-center}
server:
port: ${EUREKA_PORT:14031}
eureka:
instance:
hostname: ${EUREKA_HOST:eureka-center-peer1} #指定该Eureka实例的主机名,需要host映射
prefer-ip-address: false
client:
serviceUrl: #高可用
defaultZone: ${EUREKA_CENTER_REG:http://eureka-center-peer2:14032/eureka/}
###----------------第二个eureka注册中心互相注册即可-----------------------------------------
#多节点固定模式,文件名application-peer2.yml
spring:
application:
name: ${APPLICATION_NAME:eureka-center}
server:
port: ${EUREKA_PORT:14032}
eureka:
instance:
hostname: ${EUREKA_HOST:eureka-center-peer2} #指定该Eureka实例的主机名,需要host映射
prefer-ip-address: false
client:
serviceUrl: #高可用
defaultZone: ${EUREKA_CENTER_REG:http://eureka-center-peer1:14031/eureka/}

启动:

java -jar app.jar --spring.profiles.active=peer1

java -jar app.jar --spring.profiles.active=peer2

注意:

  1. 高可用启动,application.name一定要一致
  2. 一定要指定端口号
  3. 不能禁用自我注册,注意配置文件加载顺序及覆盖顺序

健康检查

1
2
3
  compile('org.springframework.cloud:spring-cloud-starter-netflix-eureka-server')
//上面的包包含下面的依赖,因此springcould不需要添加该依赖
compile("org.springframework.boot:spring-boot-starter-actuator")

常用链接

1
2
3
4
$ curl localhost:8080/actuator/health
{"status":"UP"}
$ curl localhost:8080/actuator
{"_links":{"self":{"href":"http://127.0.0.1:14031/actuator","templated":false},"health":{"href":"http://127.0.0.1:14031/actuator/health","templated":false},"info":{"href":"http://127.0.0.1:14031/actuator/info","templated":false}}}

dockerfile

1
2
3
4
5
6
7
8
HEALTHCHECK  --interval=5m --timeout=3s \
CMD wget --quiet --tries=1 --spider http://127.0.0.1:14031/actuator/health || exit 1
# --quiet 安静模式
# --tries=1 重试次数
# --spider 不下载任何资料

wget --quiet --tries=1 --spider http://127.0.0.1:14031/actuator/health

Wget命令参数及使用

问题:

1
EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY'RE NOT. RENEWALS ARE LESSER THAN THRESHOLD AND HENCE THE INSTANCES ARE NOT BEING EXPIRED JUST TO BE SAFE.

原因据说是

注册中心不是高可用的原因

出处:https://www.cnblogs.com/xiaojf/p/7919088.html

额外

1. 修改eureka client的注册ip,在本地有多个网卡时,默认注册的那个ip可能无法访问,解决指定本地服务ip

1
2
3
4
5
eureka:
instance:
prefer-ip-address: true
ip-address: 10.88.123.151
instance-id: 10.88.123.151:9301

RabbitMQ安装

docker-hub/rabbitmq

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
version: '3'
services:
#-------------------------不带管理界面---------------------------------------------
rabbitmq:
image: rabbitmq
restart: always
hostname: xuanps #节点名字
environment:
RABBITMQ_DEFAULT_USER: root #设置用户名
RABBITMQ_DEFAULT_PASS: ******* #设置密码
ports:
- 14002:5672
volumes:
- "/dockerdata/v-rabbitmq:/var/lib/rabbitmq"
deploy:
replicas: 1
restart_policy:
condition: on-failure
placement:
constraints: [node.hostname == xuanps]
#--------------带管理界面(从带管理界面升级要清空挂载目录)---------------------------------
rabbitmq:
image: rabbitmq:3.7-management-alpine
restart: always
hostname: me #节点名字
environment:
RABBITMQ_DEFAULT_USER: test #设置用户名
RABBITMQ_DEFAULT_PASS: test #设置密码
ports:
- 14012:5672
- 14013:15672
volumes:
- "/home/dockerdata/v-nantian-dev/rabbitmq:/var/lib/rabbitmq"
deploy:
replicas: 1
restart_policy:
condition: on-failure
placement:
constraints: [node.hostname == me]

springboot 连接mq

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

mq连接配置application.yml

1
2
3
4
5
6
spring:
rabbitmq:
host: 112.74.51.136
port: 14002
username: root
password: ********

mq发送数据(测试类)

1
2
3
4
5
6
7
8
@Autowired
private AmqpTemplate rabbitTemplate;
@Test
public void send() {
String context="hello"+new Date();
System.out.print("send context:"+context);
rabbitTemplate.convertAndSend("hello",context);
}

mq配置类MqConfig.java

1
2
3
4
5
6
7
8
import org.springframework.amqp.core.Queue; //注意不要导错包
@Configuration
public class MqConfig {
@Bean
public Queue helloQueue(){
return new Queue("hello");
}
}

mq接受数据类MqReceiver.java

1
2
3
4
5
6
7
8
@Component  //注解必须加
@RabbitListener(queues = "hello")
public class MqReceiver {
@RabbitHandler
public void process(String hello){
System.out.print("receiver:"+hello);
}
}

测试效果:先启动主程序,再点击测试类发送,主程序就可以接收到消息了。(先发送后启动主程序是接受不到的)

centos7 install RabbitMQ

  1. 首先下载安装包:erlang、socat、rabbitmq以此用rpm -ivh <>安装这三个

  2. 新建修改配置文件vi /etc/rabbitmq/rabbitmq.config

    1
    2
    3
    [
    {rabbit, [{tcp_listeners, [5672]}, {loopback_users, ["admin"]}]}
    ].
  3. 启动rabbitmq服务

    1
    2
    3
    4
    #启动服务
    systemctl start rabbitmq-server
    #查看状态
    systemctl status rabbitmq-server
  4. 配置远程管理web界面

    1
    2
    3
    rabbitmq-plugins enable rabbitmq_management
    rabbitmq-plugins enable rabbitmq_stomp
    rabbitmq-plugins enable rabbitmq_web_stomp
  5. 配置用户远程访问

    1
    2
    3
    4
    #新建用户test密码test
    rabbitmqctl add_user test test
    rabbitmqctl set_user_tags test administrator
    rabbitmqctl set_permissions -p / test '.*' '.*' '.*'
  6. 重启rabbitmq服务执行systemctl restart rabbitmq-server

  7. 添加防火墙访问端口

    1
    2
    3
    4
    5
    6
    #添加15672端口
    firewall-cmd --zone=public --add-port=15672/tcp --permanent
    #加载配置
    firewall-cmd --reload
    #查看配置是否生效
    firewall-cmd --list-all
  8. 测试,浏览器访问http://<服务器ip>:15672/使用test,test用户密码登陆

常见问题

问题1:.erlang.cookie must be accessible by owner only 不断重启

解决:删除整个挂载目录,包括rabbitmq,然后重建

多线程

关键词概念

  1. 线程饥饿:某些线程优先级过低,导致永远无法得倒运行

  2. 守护线程与用户线程:用户线程会阻止jvm的正常停止,守护线程重要性不高,一般用来做监控,是否守护线程父线程决定。

  3. 工作线程(后台线程):用户执行特定任务。

  4. 多线程编程的优缺点

    • 优点:提高系统吞吐率、响应性、多核处理资源、最小化对系统资源的使用、简化程序结构
    • 缺点(风险):线程安全、线程活性(死锁与活锁)、上下文切换(额外资源消耗)、可靠性
  5. 串发(顺序执行)、并发(宏观同时运行,微观轮流运行)、并行(严格同时进行)

  6. 竞态(Race Condition):计算结果的正确性依赖于相对时间顺序或者线程的交错(多个线程对共享变量进行修改)。

  7. 原子性:一个线程对共享变量的更新,从另一个线程的角度来看,它要么完成,要么尚未发生。

  8. 可见性:一个线程对共享变量的更新对于另一个线程而言是否可见

  9. 重排序:1.分配对象的内存空间,2.初始化对象instance,3.设置instance指向刚分配的内存地址.2和3可能发生重排序,可能出现线程安全问题。

  10. 上下文切换:一个线程被暂停,即被剥夺处理器的使用权,另外一个线程被选中开始或者继续运行的过程

  11. 非公平调度策略(吞吐量大/申请资源时间长容易导致饥饿现象)与公平调度策略(相反)

  12. 临界区:锁的持有线程在其获得锁之后和释放锁之前的这段时间内所执行的代码被称为临界区。

  13. 内部锁(synchronized):内部锁表现为整体并发中的局部串行

  14. 显示锁(Lock接口):多读单写

  15. 并发集合

    1. ArrayList—-CopyOnWriteArrayList 快照实现遍历
    2. HashSet—CopyOnWriteArraySet 快照实现遍历
    3. LinkedList—ConcurrentLinkedQueue 准实时
    4. HashMap—ConcurrentHashMap 准实时
    5. TreeMap—ConcurrentSkipListMap 准实时
    6. TreeSet—ConcurrentSkipListSet 准实时
  16. 捕获线程异常不用try catch,用UncaughtExceptionHandler

参考

Java多线程编程实战指南(核心篇)读书笔记(五)

面试百问

  1. Http2.0与http1.0

    • 性能大幅提升
    • 多路复用
    • header压缩
  2. 重写equals为何要重写hashcode

    hashcode:是jdk根据对象的地址或者字符串或者数字算出来的int类型的数值。

    实战:向HashSet添加对象,自动去除重复的对象

    原理:HashSetadd 方法判断两个对象是否“相同”

       graph LR
    q[hashset.add对象]-->a{调用对象内hashCode判断}
    a-->|相等|b{调用对象内equlas判断}
    a-->|不相等|c[添加到集合hashset]
    b-->|相等|d[不添加对象到hashset]
    b-->|不相等|c

    举例测试:

    1
    2
    3
    4
    5
    6
    HashSet<Xy> xys=new HashSet<>();
    xys.add(new Xy(1,2));
    xys.add(new Xy(2,2));
    xys.add(new Xy(2,2));
    System.out.print(xys.toString()); //输出[{2,2}, {1,2}]
    System.out.print(new Xy(1,2).equals(new Xy(1,2))); //输出true

    对象类的实现必须覆写equalshashCode

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    public class Xy {
    private int x;
    private int y;
    Xy(int x, int y){
    this.x=x;
    this.y=y;
    }
    @Override
    public int hashCode() {
    return Objects.hash(x,y); //生成hashcode,只要保证不同的值生成不同的hashcode即可
    }
    @Override
    public boolean equals(Object obj) {
    System.out.print("2");
    if (!(obj instanceof Xy)){
    return false;
    }
    Xy test= (Xy) obj;
    return (test.x == x) && (y == test.y);
    }
    @Override
    public String toString() {
    return "{"+x+","+y+"}";
    }
    }
  3. java对象的生命周期

    1. 创建阶段(created) 分配存储空间->构造对象->static初始化(超类到子类)->变量构造方法初始化(超到子类)
    2. 应用阶段(InUse)至少被一个强引用持有(Object object=new Object();)
    3. 不可见阶段(Invisible)不在被任何强引用持有(但是可能被jvm持有),超出对象作用域(方法内定义了变量,方法外该变量就是不可见,编译报错)
    4. 不可达阶段(Unreachable)不在被任何强引用持有
    5. 收集阶段(collected)已经被垃圾回收器发现,会调用finazlie方法,所以一般不要重写,会影响垃圾回收
    6. 终结阶段(Finalized)对象执行完finazlie仍处于不可达,则进入该阶段等待垃圾回收
    7. 对象空间重写分配阶段(Deallocated)垃圾已经回收,空间处于再分配状态
  4. java创建对象的几种方式

    1
    2
    3
    4
    5
    6
    7
    User user = new User(); //new方式
    User user = User.class.newInstance(); //反射方式1.newInstance,无参的构造对象
    Constructor<User> constructor = User.class.getConstructor();
    User user = constructor.newInstance(); //反射方式2.Constructor 有参构造对象
    //public class CloneTest implements Cloneable{} //clone需要实现该接口
    CloneTest copyClone = (CloneTest) cloneTest.clone(); //clone浅克隆,深克隆(含内部自定义对象)
    public class Test implements Serializable//反序列化,文件反序列化为对象
  5. 反射invoke

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    //正常的调用
    Xy xy = new Xy();
    xy.setX(5);
    System.out.println("xy x:" + xy.getX());
    //使用反射调用
    try {
    Class xy2 = Class.forName("com.example.demo.Xy"); //找到class
    Method setxMethod = xy2.getMethod("setX", int.class); //获得set方法
    Object xyObj = xy2.newInstance();//必须有不带参的构造(不然运行InstantiationException)
    setxMethod.invoke(xyObj, 5); //调用set方法
    Method getXMethod = xy2.getMethod("getX"); //获得get方法
    System.out.println("xy x:" + getXMethod.invoke(xyObj)); //调用get方法
    } catch (Exception e) {
    e.printStackTrace();
    }
  6. 集合Collections

    • List 有序可重复对象,Arraylist存读效率高(数组存储方式),LinkedList大数据插入删除效率高(双向循环链表),Vector同步(现场安全),Arraylist非同步
  • Map 键值对,key不可重复,HashMap非线程安全,Hashtable线程安全(ConcurrentHashMap升级版),HashSet元素唯一

    1
    2
    3
    4
    5
    6
    7
    List<Integer> list = new ArrayList<>();
    list.add(1);list.add(2);list.add(3);
    List<Integer> list2 = new ArrayList<>();
    list2.add(2);list2.add(3);list2.add(4);
    list.addAll(list2); //并集(123234)
    list.retainAll(list2); //交集(23)
    list.removeAll(list2); //差集(1)
  • Set 不允许重复

Q1:list找出重复元素,java8(Stream),hashmap,去重(set),然后转换list,差集得出重复元素

Q2:list排序重写Comparator定制排序

  1. 多线程

  2. 线程安全

mysql存储过程

变量

系统变量两个@@
1
2
3
show variables; #查看系统内置变量
select @@变量名; #查看系统变量的值,如select @@version
set 变量名 = 值; #修改变量(局部修改)命令 如set autocommit = 3;
自定义变量一个@
1
2
set @变量名 = 值; #自定义变量语法,如`set @name = 'saboran';
select @name; #查看变量的值

函数

1
2
3
4
5
create function 函数名(参数列表) returns 数据类型
begin
// 函数体
// 返回值
end

常用命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#连接mysql
mysql -u root -p
show status like 'Threads%';
+-------------------+-------+
| Variable_name | Value |
+-------------------+-------+
| Threads_cached | 8 | #连接缓存数
| Threads_connected | 81 | #打开的连接数,如果该连接数大于max_connections当前最大连接数会报错
| Threads_created | 181 | #创建过的线程数
| Threads_running | 1 | #正在运行的连接数
+-------------------+-------+
show variables like '%max_connections%';
+-----------------+-------+
| Variable_name | Value |
+-----------------+-------+
| max_connections | 151 | #当前设置的最大连接数
+-----------------+-------+
show processlist; #显示前100条的连接,如果显示所有show full processlist;
+------+------+------------------+------+---------+------+----------+------------------+
| Id | User | Host | db | Command | Time | State | Info |
+------+------+------------------+------+---------+------+----------+------------------+
| 1100 | root | localhost | NULL | Query | 0 | starting | show processlist |
| 1147 | root | 10.255.0.2:52580 | xhzg | Sleep | 7226 | | NULL |
+------+------+------------------+------+---------+------+----------+------------------+

mysql配置my.cnf,添加挂在卷 - /dockerdata/manager/mysqldata/config:/etc/mysql/conf.d

然后在挂在卷创建配置文件,添加配置my.cnf 文件名字随便

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[mysql]
# 设置mysql客户端默认字符集
default-character-set=utf8
[mysqld]
skip-name-resolve
#设置3306端口
port = 3306
# 设置mysql的安装目录
basedir=/usr/local/mysql
# 设置mysql数据库的数据的存放目录
datadir=/usr/local/mysql/data
# 允许最大连接数
max_connections=200
# 服务端使用的字符集默认为8比特编码的latin1字符集
character-set-server=utf8
# 创建新表时将使用的默认存储引擎
default-storage-engine=INNODB
lower_case_table_names=1
max_allowed_packet=16M
sql_mode=ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION

备份与恢复

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#备份数据库 manage geoserver两个
mysqldump -uroot -plfadmin --databases manage geoserver > ~/manageandgeoserver20180829.sql
# 恢复
mysql -uroot -plfadmin <manageandgeoserver20180829.sql
# 检查校验,进入mysql命令行
mysql -u root -p
# 显示所有数据库
mysql> show databases;
# 使用 manage数据库
mysql> use manage;
# 显示所有用表
mysql> show tables;
#远程连接
mysql> mysql -h172.16.16.8 -P14036 -uroot -p

参考 MySql数据库备份与恢复——使用mysqldump 导入与导出方法总结

主从库

  1. 修改主从配置库的配置文件

主库配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
[mysqld]
init_connect='SET NAMES utf8'
character-set-server=utf8
#无效屏蔽
# 设置mysql数据库的数据的存放目录
#datadir=/data/app/mysqldata/master/
#socket=/data/app/mysqldata/master/mysql.sock
#user=mysql
#port=3306

#Disabling symbolic-links is recommended to prevent assorted security risks
symbolic-links=0

log-bin=mysql-bin
server-id=1
binlog-ignore-db=information_schema
binlog-ignore-db=mysql
binlog-do-db=shenqics
#无效屏蔽
#[mysqld_safe]
#log-error=/data/app/mysqldata/master/mysqld.log
#pid-file=/data/app/mysqldata/master/mysqld.pid

[client]
default-character-set=utf8

[mysql]
default-character-set=utf8

从数据库配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
[mysqld]
init_connect='SET NAMES utf8'
character-set-server=utf8
#无效屏蔽
#datadir=/data/app/mysqldata/slave/
#socket=/data/app/mysqldata/slave/mysql.sock
#user=mysql
#port=3307

#Disabling symbolic-links is recommended to prevent assorted security risks
symbolic-links=0

log-bin=mysql-bin
server-id=2
binlog-ignore-db=information_schema
binlog-ignore-db=mysql
#只会复制shenqics该数据库,其他不会
replicate-do-db=shenqics
replicate-ignore-db=mysql
log-slave-updates
slave-skip-errors=all
slave-net-timeout=60

#无效屏蔽
#[mysqld_safe]
#log-error=/data/app/mysqldata/slave/mysqld.log
#pid-file=/data/app/mysqldata/slave/mysqld.pid

[client]
default-character-set=utf8

[mysql]
default-character-set=utf8
  1. 启动服务

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    mysql-master:
    restart: always
    image: mysql:5.7.18
    environment:
    MYSQL_ROOT_PASSWORD: admin
    volumes:
    - /data/v-yinfu/mysql/master/data:/var/lib/mysql
    - /data/v-yinfu/mysql/master/config:/etc/mysql/conf.d
    ports:
    - target: 3306
    published: 14036
    protocol: tcp
    mode: host
    deploy:
    replicas: 1
    restart_policy:
    condition: on-failure
    placement:
    constraints: [node.hostname == VM_16_8_centos]
    mysql-slave:
    restart: always
    image: mysql:5.7.18
    environment:
    MYSQL_ROOT_PASSWORD: admin
    volumes:
    - /data/v-yinfu/mysql/slave/data:/var/lib/mysql
    - /data/v-yinfu/mysql/slave/config:/etc/mysql/conf.d
    ports:
    - target: 3306
    published: 14037
    protocol: tcp
    mode: host
    deploy:
    replicas: 1
    restart_policy:
    condition: on-failure
    placement:
    constraints: [node.hostname == VM_16_13_centos]
  2. 授权

    进入主库容器执行mysql -uroot -p

    1
    2
    3
    4
    5
    #ip为从库ip,设置为只有从库可以访问
    GRANT REPLICATION SLAVE ON *.* TO 'app_sync'@'172.16.16.13' IDENTIFIED BY 'password';
    GRANT ALL ON *.* TO 'app_root'@'%' IDENTIFIED BY 'password';
    GRANT SELECT ON *.* TO 'app_read'@'%' IDENTIFIED BY 'password';
    FLUSH PRIVILEGES;
  3. 然后执行show master status;

    1
    2
    3
    4
    5
    6
    +------------------+----------+--------------+--------------------------+-------------------+
    | File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
    +------------------+----------+--------------+--------------------------+-------------------+
    | mysql-bin.000003 | 1164 | shenqics | information_schema,mysql | |
    +------------------+----------+--------------+--------------------------+-------------------+
    1 row in set (0.00 sec)
  4. 进入从库容器执行mysql -uroot -p

    1
    2
    3
    4
    5
    6
    7
    stop slave;
    change master to master_host='172.16.16.8',master_port=14036,master_user='app_sync',master_password='password',master_log_file='mysql-bin.000003', master_log_pos=1164;
    start slave;
    show slave status;
    #授权
    GRANT SELECT ON *.* TO 'app_read'@'%' IDENTIFIED BY 'password';
    FLUSH PRIVILEGES;
  5. 执行show slave status\G;检查是否这两个为yes

    1
    2
    Slave_IO_Running: Connecting
    Slave_SQL_Running: Yes
  6. 重新注册

    1
    2
    3
    4
    5
    6
    stop slave;
    change master to master_host='172.16.16.8',master_port=14036,master_user='app_sync',master_password='admin',master_log_file='mysql-bin.000005', master_log_pos=361;
    start slave;
    #清除log,执行start slave报错时
    reset slave;
    start slave;
  7. 再次执行第6步检查

mysql创建用户命令详解

创建用户

GRANT 权限 ON 数据库.表名 TO '用户'@'主机' IDENTIFIED BY '密码'

  • 权限:all,select,等
  • 主机:指定ip地址访问、localhost或127.0.0.1(本地访问)、%(任意主机均可访问)
  • 密码:为空时则不需要密码

eg:

1
2
GRANT REPLICATION SLAVE ON *.* TO 'app_sync'@'172.16.16.13' IDENTIFIED BY 'admin';
#意思是创建一个专门的用户(app_sync)进行从库复制,复制任何库和任何表,密码是admin,可以访问的ip只有来源172.16.16.13(从库ip)

删除用户drop user test@'172.16.16.13';

HikariCP连接池优化

HikariCP作为springboot连接池,在性能压测时,连接时间成指数递增。

1
2
3
4
5
6
7
8
9
10
11
#该属性用于控制连接在池中的最大生存时间,超过该时间强制逐出,当前正在使用的连接不会强制逐出。
#缺省:1800000, 即30min
spring.datasource.hikari.max-lefetime = 0
#配置允许连接池达到的最大连接数,cpu核心的两倍
spring.datasource.hikari.maximum-pool-size=50
spring.datasource.hikari.connection-test-query=select 1
spring.datasource.hikari.idle-timeout=60000
#建议比数据库的connect_timeout大一些
spring.datasource.hikari.connection-timeout=2000
spring.datasource.hikari.validation-timeout=1500
spring.datasource.hikari.minimum-idel=5

数据库配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
> show variables like '%timeout%';
connect_timeout 60
delayed_insert_timeout 300
have_statement_timeout YES
innodb_flush_log_at_timeout 1
innodb_lock_wait_timeout 50
innodb_rollback_on_timeout OFF
interactive_timeout 28800
lock_wait_timeout 31536000
mysqlx_connect_timeout 30
mysqlx_idle_worker_thread_timeout 60
mysqlx_port_open_timeout 0
net_read_timeout 30
net_write_timeout 60
rpl_stop_slave_timeout 31536000
slave_net_timeout 10
wait_timeout 28800

常见错误

  1. Too many connections症状,不断重启运行springboot并访问,出现如下错误

    1
    2
    3
    4
    5
    6
    2018-04-05 21:48:56.824 ERROR 6838 --- [           main] com.xhzg.xhzg.XhzgApplicationTests       : nested exception is org.apache.ibatis.exceptions.PersistenceException: 
    ### Error querying database. Cause: org.springframework.jdbc.CannotGetJdbcConnectionException: Failed to obtain JDBC Connection; nested exception is com.mysql.jdbc.exceptions.jdbc4.MySQLNonTransientConnectionException: Data source rejected establishment of connection, message from server: "Too many connections"
    ### The error may exist in com/xhzg/xhzg/mapper/UserMapper.java (best guess)
    ### The error may involve com.xhzg.xhzg.mapper.UserMapper.loadUserByUsername
    ### The error occurred while executing a query
    ### Cause: org.springframework.jdbc.CannotGetJdbcConnectionException: Failed to obtain JDBC Connection; nested exception is com.mysql.jdbc.exceptions.jdbc4.MySQLNonTransientConnectionException: Data source rejected establishment of connection, message from server: "Too many connections"

    解决:快速解决重启mysql释放Threads_connected连接数,或者等待一会儿,也会慢慢释放连接数,另一种更改max_connections最大连接数启动mysql添加参数--ulimit nofile=65536:65536

    参考:

    Docker容器中MySQL最大连接数被限制为214的解决方案

    [Increasing mysql max_connections to 1024 in a docker container]

  2. 错误

    1
    [Err] 1055 - Expression #1 of ORDER BY clause is not in GROUP BY clause and contains nonaggregated column 'information_schema.PROFILING.SEQ' which is not functionally dependent on columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by

    解决:

    删除配置文件sql_mode=ONLY_FULL_GROUP_BY这个属性值

    1
    2
    mysql>SELECT @@sql_mode;
    mysql>SELECT @@GLOBAL.sql_mode;

    参考:https://www.zhihu.com/question/37942423

  3. navicat客户端,连接mysql 8.0以上报错,提示授权啥的错误

  4. 设置主从模式时,使用用户'app_sync'@'172.16.16.13'连接时提示,以及一直Slave_IO_Running: Connecting

    1
    ERROR 1045 (28000): Access denied for user 'app_sync'@'10.255.0.2' (using password: YES)

    解决:部署时设置host模式

    原因:非host模式连接时,访问客户端ip是内部ip不是host的ip

  5. 错误

    1
    java.sql.SQLException: Incorrect string value: '\xF0\x9F\x90\xB6' for column 'UserNickname' at row 1

    解决:

    1. 在mysql配置文件添加后,重启

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      [client]
      default-character-set = utf8mb4

      [mysql]
      default-character-set = utf8mb4

      [mysqld]
      character-set-client-handshake = FALSE
      character-set-server = utf8mb4
      collation-server = utf8mb4_unicode_ci
    2. 修改数据表的编码

      ALTER TABLE TABLE_NAME CONVERT TO CHARACTER SET utf8mb4;

    3. 修改数据库连接

      jdbc:mysql://localhost:3306/"+DATABASENAME+"?useunicode=true&characterEncoding=utf8

      方式一:去掉参数&characterEncoding=utf8useunicode=true

      方式二(建议):添加autoReconnect=true

java 日期操作

1
2
3
4
5
6
7
8
//java8
LocalDateTime today_start = LocalDateTime.of(LocalDate.now(), LocalTime.MIN);//当天零点
LocalDateTime today_start = LocalDateTime.of(LocalDate.now(), LocalTime.MAX);//当天23:59:59
today_start.format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")); //时间格式化
//LocalDateTime 转 Date
Date out = Date.from(today_start.atZone(ZoneId.systemDefault()).toInstant());
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
sdf.format(out); //时间格式化

redis springboot常用命令

1
2
3
4
5
6
7
8
9
10
11
@Autowired
StringRedisTemplate redisTemplate;
//----------------------------------------------------------------------
//给key设置value
redisTemplate.opsForValue().set("key","value"); //SET key "value"
//取key的value
redisTemplate.opsForValue().get("key");//GET key
//设置key在new Date()过期
redisTemplate.expireAt("key",new Date()); //EXPIREAT key timestamp
redisTemplate.getExpire("testttl"); //返回过期时间

reids设置过期key监听事件

  1. 在redis命令行执行config set notify-keyspace-events Ex

  2. 在java代码添加监听事件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    import org.springframework.data.redis.connection.Message;
    import org.springframework.data.redis.listener.KeyExpirationEventMessageListener;
    import org.springframework.data.redis.listener.RedisMessageListenerContainer;
    import org.springframework.stereotype.Component;

    @Component
    public class RedisKeyExpirationListener extends KeyExpirationEventMessageListener {
    public RedisKeyExpirationListener(RedisMessageListenerContainer listenerContainer) {
    super(listenerContainer);
    }
    @Override
    public void onMessage(Message message, byte[] pattern) {
    // 处理接收到的消息
    String channel = new String(message.getChannel());
    String body = new String(message.getBody());
    System.out.println("接收到来自通道 " + channel + " 的消息:" + body);
    }
    }
  3. 等待过期,就会打印如下内容

    1
    2
    3
    接收到来自通道 __keyevent@1__:expired 的消息:GATE:DEVICE:HEART:1725361717475983361
    接收到来自通道 __keyevent@1__:expired 的消息:GATE:DEVICE:HEART:1728969231125295105
    接收到来自通道 __keyevent@1__:expired 的消息:GATE:DEVICE:HEART:1726505401086349313

从redis里面获取二进制图片

redis的key是camera:frames:1838502535022682114,类型是SORTED SET,里面的数据格式是:

Member Score
\xff\xd8\xff\xe0\x00\x10JFIF\x…..\t\x8e\xc7\xff\xd9 173312719710
\xff\xd8\xff\xe0\x00\x10JFIF\x…..\t\x8e\xc7\xff\xd9 173312719711
\xff\xd8\xff\xe0\x00\x10JFIF\x…..\t\x8e\xc7\xff\xd9 173312719712

对应的java代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Resource
private RedisTemplate<String, byte[]> redisTemplate;

@GetMapping("/getVideoImg")
public String getVideoImg(@RequestParam(value = "deviceId") String deviceId) {
// 动态设置序列化器为 ByteArrayRedisSerializer
redisTemplate.setKeySerializer(new StringRedisSerializer()); // 键:String 类型,指的是key=camera:frames:1838502535022682114是string
//设置自定义序列号类型
redisTemplate.setValueSerializer(new ByteArrayRedisSerializer()); // 值:byte[] 类型
// 获取 Sorted Set 中分数最高的成员,成员为 byte[]
Set<byte[]> members = redisTemplate.opsForZSet().range("camera:frames:" + deviceId, 0, 0); //取第0个到第0个数据,也就是只拿一个数据
if (members != null && !members.isEmpty()) {
byte[] latestFrameKey = members.iterator().next();//获取一个元素的本身
// 将图片转换为 Base64 编码
return Base64.getEncoder().encodeToString(latestFrameKey);
}
return "not found";
}

对应的序列号类:ByteArrayRedisSerializer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import org.springframework.data.redis.serializer.RedisSerializer;

public class ByteArrayRedisSerializer implements RedisSerializer<byte[]> {

@Override
public byte[] serialize(byte[] bytes) {
return bytes; // 不需要额外处理,直接返回
}

@Override
public byte[] deserialize(byte[] bytes) {
return bytes; // 不需要额外处理,直接返回
}
}

访问测试:apifox掉[http://127.0.0.1:8080/test/getVideoImg?deviceId=1838502535022682114]接口,就可以得倒base64的图片了,在apifox的后置脚本添加如下自定义脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 获取图片数据
let svgContent = pm.response.text();

// 将图片声明为Base64编码
let imgBase64 = `data:image/jpeg;base64,${svgContent}`;

// html 模板
const template = `<html>
<img src="{{imgTemplate}}" />
</html>`;

// 设置 visualizer 数据。传模板、解析对象。
pm.visualizer.set(template, {
imgTemplate: imgBase64,
})

然后可以直接在结果切到Visualize视图就可以看到图片了。

python里面设置可以java序列号的json字符串

最重要的是要在json外层添加"@class": "java.lang.Object",为什么是java.lang.Object,因为这个支持嵌套,其他hash map不支持多层嵌套,除非python在每层都是在@class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import redis
import json

# 创建 Redis 客户端
r = redis.StrictRedis(host='172.16.80.117', port=6479, db=1, password='123456')

# 示例 JSON 对象
data = {
"@class": "java.lang.Object", # 手动添加类型标识
"name": "Alice",
"age": 30,
"city": "New York"
}

data2={
"@class": "java.lang.Object",
"algo_name": "OBJECT_SEGMENTATION",
"algo_show_name": "OBJECT_SEGMENTATION",
"algo_icon": "&#xe671",
"algo_args": {},
"bus_args": {"ROI": [[[0, 0], [1919, 0], [1919, 1079], [0, 1079]]], "ThreshIOU": 0.0, "ObjectType": ["person"]}
}


# 将 JSON 对象转换为字符串
data_string = json.dumps(data)
data_string2 = json.dumps(data2)

try:
# 使用 hset 存储 JSON 字符串
r.hset("user:1000", "sdfsdfsdfsdf", data_string)
r.hset("user:1000", "www", data_string2)
r.hset("user:1000", "www2", data_string2)
print("数据已成功存储到 Redis")
except Exception as e:
print(f"存储时发生错误: {e}")

注意

reids重新设值会覆盖expireAt过期时间的设置

参考

记一次Redis_Key值无效监听失效的失败历程

docker swarm 安装 redis

  1. 创建redis挂载目录/dockerdata/v-redis

  2. 并在该目录vim redis.conf新建配置文件,配置文件添加如下内容

    1
    requirepass <登陆密码,最好64位以上>
  3. 编辑vim stack-redis.yml,内容如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    version: '3'
    services:
    redis:
    image: redis
    restart: always
    ports:
    - 14007:6379
    command: "redis-server /data/redis.conf"
    volumes:
    - "/dockerdata/v-redis:/data"
    deploy:
    replicas: 1
    restart_policy:
    condition: on-failure
    placement:
    constraints: [node.hostname == xuanps]
  4. 运行启动docker stack deploy -c stack-redis.yml redis

  5. 测试命令

    1
    2
    3
    4
    5
    6
    #本地直接redis-cli不需要任何参数
    redis-cli -h host -p port -a password
    #进入redis,用改名了进行密码登陆
    AUTH "password"
    #查询所有key
    keys *

springboot 连接redis

  1. 添加pom.xml依赖

    1
    2
    3
    4
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
  2. 编辑application.yml添加redis连接信息

    1
    2
    3
    4
    5
    6
    spring:
    redis:
    database: 0
    host: 112.74.51.136
    port: 14007
    password: <你的密码>
  3. 编写测试类

    1
    2
    3
    4
    5
    6
    7
    8
    @Autowired
    StringRedisTemplate stringRedisTemplate;
    @Test
    public void testredis(){
    stringRedisTemplate.opsForValue().set("testconnect","hello world");
    String test= stringRedisTemplate.opsForValue().get("testconnect");
    log.info(test);
    }

idea redis插件iedis