1. 还原项目
1.1 修改端口号
1.2 修改图片路径
1.3 修改nginx配置文件
- 反向代理到8091服务器
- 修改hosts文件
1.4 效果展现
2. 数据库优化
2.1 实现数据库读写分离
2.2 Mycat
2.2.1 Mycat介绍
活跃的、性能好的开源数据库中间件! 我们致力于开发高性能的开源中间件而努力!
2.2.2 Mycat特性
支持前端作为MySQL通用代理
后端JDBC方式支持Oracle,DB2,SQL Server,mongodb,巨杉
基于心跳的自动故障切换,支持读写分离
支持MySQL Cluster,Galera,Percona,cluster集群
支持数据的多片自动路由与聚合
支持sum,count,max等常用的聚合函数,支持跨库分页
支持库内分表,支持单库内部任意join全局表,支持跨库2表join
基于caltlet的多表join
支持通过全局表,ER关系的分片策略,实现了高效的多表join查询
2.2.3 上传Mycat安装包
- 上传安装包
- 解压Mycat安装包
tar -xvf Mycat-server-1.7.0-DEV-20170416134921-linux.tar.gz
2.2.4 编辑server.xml配置文件
说明: server.xml代表的是用户与mycat之间的配置关系.
端口配置:
<property name="serverPort">8066</property>
用户配置:
<!--用户标签-->
<user name="root">
<property name="password">root</property>
<!--与schema.xml中的配置相同 注意数据库的大小写-->
<!--<property name="schemas">jtdb,db1,db2,db3</property>-->
<property name="schemas">jtdb</property>
</user>
<user name="user">
<property name="password">user</property>
<property name="schemas">jtdb</property>
<!-- 只读的用户 -->
<property name="readOnly">true</property>
</user>
2.2.5 编辑schema.xml配置文件
<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">
<!--name属性是自定义的 dataNode表示数据库的节点信息 jtdb表示逻辑库-->
<schema name="jtdb" checkSQLschema="false" sqlMaxLimit="100" dataNode="jtdb"/>
<!--定义节点名称/节点主机/真实数据名称-->
<dataNode name="jtdb" dataHost="localhost1" database="jtdb" />
<!--参数介绍 UTF-8 中文报错 使用单行注释 -->
<!--balance 0表示所有的读操作都会发往writeHost主机 -->
<!--1表示所有的读操作发往readHost和闲置的主节点中-->
<!--writeType=0 所有的写操作都发往第一个writeHost主机-->
<!--writeType=1 所有的写操作随机发往writeHost中-->
<!--dbType 表示数据库类型 mysql/oracle-->
<!--dbDriver="native" 固定参数 不变-->
<!--switchType=-1 表示不自动切换, 主机宕机后不会自动切换从节点-->
<!--switchType=1 表示会自动切换(默认值)如果第一个主节点宕机后,Mycat会进行3次心跳检测,如果3次都没有响应,则会自动切换到第二个主节点-->
<!--并且会更新/conf/dnindex.properties文件的主节点信息 localhost1=0 表示第一个节点.该文件不要随意修改否则会出现大问题-->
<dataHost name="localhost1" maxCon="1000" minCon="10" balance="1"
writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100">
<!--数据库高可用有关-->
<heartbeat>select 1</heartbeat>
<!--配置第一台主机主要进行写库操作,在默认的条件下Mycat主要操作第一台主机在第一台主机中已经实现了读写分离.因为默认写操作会发往137的数据库.读的操作默认发往141.如果从节点比较忙,则主节点分担部分压力.
-->
<writeHost host="hostM1" url="192.168.126.129:3306" user="root" password="root">
<!--读数据库1 负载均衡-->
<readHost host="hostS1" url="192.168.126.130:3306" user="root" password="root" />
<!--读数据库2-->
<readHost host="hostS2" url="192.168.126.129:3306" user="root" password="root" />
</writeHost>
<!--定义第二台主机 由于数据库内部已经实现了双机热备.-->
<!--Mycat实现高可用.当第一个主机137宕机后.mycat会自动发出心跳检测.检测3次.-->
<!--如果主机137没有给Mycat响应则判断主机死亡.则回启东第二台主机继续为用户提供服务.-->
<!--如果137主机恢复之后则处于等待状态.如果141宕机则137再次持续为用户提供服务.-->
<!--前提:实现双机热备.-->
<!--<writeHost host="hostM2" url="192.168.126.130:3306" user="root" password="root">
<readHost host="hostS1" url="192.168.126.130:3306" user="root" password="root" />
<readHost host="hostS2" url="192.168.126.129:3306" user="root" password="root" />
</writeHost>-->
</dataHost>
</mycat:schema>
2.2.6 上传配置文件
2.2.7 Mycat发布
- 校验JDK
- 进入bin目录
- 启动mycat
- 状态检查
- 日志检查 根据报错提示 检查问题
cat wrapper.log
cat mycat.log
2.2.8 Mycat负载均衡测试
说明: 修改数据库从库的数据 之后启动服务器检查是否有负载均衡的效果
注意事项: 最后将数据保证一致即可.
2.3 实现数据库高可用
2.3.1 业务说明
当数据库主库发生宕机的现象,则直接影响用户的使用. 最好的解决的策略 实现数据库高可用.
前提条件: 必须实现数据库主从同步
2.3.2 数据库同步策略
以前:
1.主机: 192.168.126.129
2.从机: 192.168.126.130
现在:
1.主机: 192.168.126.130
2.从机: 192.168.126.129
2.3.3 双击热备
说明:2台数据库同时互为主从结构,称之为双机热备的形式,(2台数据库同时都是主机/从机)
配置步骤:
- 检查主库状态
- 开始进行主从挂载
2.4 Mycat实现数据库高可用
2.4.1 编辑schema.xml
<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">
<!--name属性是自定义的 dataNode表示数据库的节点信息 jtdb表示逻辑库-->
<schema name="jtdb" checkSQLschema="false" sqlMaxLimit="100" dataNode="jtdb"/>
<!--定义节点名称/节点主机/真实数据名称-->
<dataNode name="jtdb" dataHost="localhost1" database="jtdb" />
<!--参数介绍 UTF-8 中文报错 使用单行注释 -->
<!--balance 0表示所有的读操作都会发往writeHost主机 -->
<!--1表示所有的读操作发往readHost和闲置的主节点中-->
<!--writeType=0 所有的写操作都发往第一个writeHost主机-->
<!--writeType=1 所有的写操作随机发往writeHost中-->
<!--dbType 表示数据库类型 mysql/oracle-->
<!--dbDriver="native" 固定参数 不变-->
<!--switchType=-1 表示不自动切换, 主机宕机后不会自动切换从节点-->
<!--switchType=1 表示会自动切换(默认值)如果第一个主节点宕机后,Mycat会进行3次心跳检测,如果3次都没有响应,则会自动切换到第二个主节点-->
<!--并且会更新/conf/dnindex.properties文件的主节点信息 localhost1=0 表示第一个节点.该文件不要随意修改否则会出现大问题-->
<dataHost name="localhost1" maxCon="1000" minCon="10" balance="1"
writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100">
<!--数据库高可用有关-->
<heartbeat>select 1</heartbeat>
<!--配置第一台主机主要进行写库操作,在默认的条件下Mycat主要操作第一台主机在第一台主机中已经实现了读写分离.因为默认写操作会发往137的数据库.读的操作默认发往141.如果从节点比较忙,则主节点分担部分压力.
-->
<writeHost host="hostM1" url="192.168.126.129:3306" user="root" password="root">
<!--读数据库1 负载均衡-->
<readHost host="hostS1" url="192.168.126.130:3306" user="root" password="root" />
<!--读数据库2-->
<readHost host="hostS2" url="192.168.126.129:3306" user="root" password="root" />
</writeHost>
<!--定义第二台主机 由于数据库内部已经实现了双机热备.-->
<!--Mycat实现高可用.当第一个主机137宕机后.mycat会自动发出心跳检测.检测3次.-->
<!--如果主机137没有给Mycat响应则判断主机死亡.则回启东第二台主机继续为用户提供服务.-->
<!--如果137主机恢复之后则处于等待状态.如果141宕机则137再次持续为用户提供服务.-->
<!--前提:实现双机热备.-->
<writeHost host="hostM2" url="192.168.126.130:3306" user="root" password="root">
<readHost host="hostS1" url="192.168.126.130:3306" user="root" password="root" />
<readHost host="hostS2" url="192.168.126.129:3306" user="root" password="root" />
</writeHost>
</dataHost>
</mycat:schema>
2.4.2 上传配置文件
说明: 将文件上传到服务器中,重启mycat服务器.检查结果
2.4.3 数据库高可用测试
1).关闭129的主数据库.
2).测试用户链接是否正常.
3).重启主数据,检查数据是否同步.
3.Redis缓存服务器
3.1 业务说明
3.2 关于缓存机制说明
说明: 缓存中的数据都是数据库中的记录. 引入缓存机制之后,主要的目的为了降低用户访问物理设备的频次.提高服务器响应效率.
维度说明:
1).开发语言: C语言开发
2).数据结构问题: K-V结构 map/properties key不能重复
3).内存数据 断电及擦除: 定期将内存数据持久化操作
4).内存资源有效: 如何有效的维护内存大小? 定期清理内存. LRU算法/LFU算法/TLL算法
3.3 Redis介绍
Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构,如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 与范围查询, bitmaps, hyperloglogs 和 地理空间(geospatial) 索引半径查询。 Redis 内置了 复制(replication),LUA脚本(Lua scripting), LRU驱动事件(LRU eviction),事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)。
速度: 读: 11.2万次/秒 写: 8.6万次/秒 平均10万次/秒
3.4 Redis安装
3.4.1 上传Redis安装包
3.4.2 解压文件
tar -xvf redis-5.0.4.tar.gz
3.4.2 安装redis
步骤: 进入redis根目录中
命令1: make
命令2: make install
3.4.3修改redis配置文件
命令: vim redis.conf
展现行号:
- 关闭IP绑定
- 关闭保护模式
- 开启后台运行
3.4.4 Redis启动命令
- 启动redis命令
redis-server redis.conf
2. 进入redis客户端
redis-cli -p 6379
-
关闭redis
redis-cli -p 6379 shutdown -
Redis入门案例
4.1 导入jar包
<!--spring整合redis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
</dependency>
4.2 入门案例
package com.jt.redis;
import org.junit.jupiter.api.Test;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;
import redis.clients.jedis.params.Params;
import redis.clients.jedis.params.SetParams;
public class TestRedis {
/**
* 1.redis配置文件3处修改
* 2.Linux系统防火墙
* 3.检查redis启动方式 redis-server redis.conf
* */
@Test
public void testSet() throws InterruptedException {
String host = "192.168.126.129";
int port = 6379;
Jedis jedis = new Jedis(host,port);
//命令怎么敲,代码怎么写
jedis.set("2011", "redis入门案例");
System.out.println(jedis.get("2011"));
jedis.flushAll();
//测试是否存在
if(jedis.exists("2011")){
jedis.del("2011");
}else{
jedis.set("num", "100");
//自增1
jedis.incr("num");
jedis.expire("num", 20);
Thread.sleep(2000);
//检查超时时间
System.out.println(jedis.ttl("num"));
//取消超时时间
jedis.persist("num");
}
}
//有时我们可能会为数据添加超时时间
//原子性问题: 有时业务要求要么同时完成,要么同时失败
@Test
public void testSetEx(){
Jedis jedis = new Jedis("192.168.126.129", 6379);
//jedis.set("a","aa");
//服务器异常
//jedis.expire("a",60);
jedis.setex("a", 20, "100");
System.out.println(jedis.get("a"));
}
/**
* 需求1:如果该数据不存在时,才会赋值.
* 需求2: 如果数据存在时,进行修改, 并且为他设定超时时间 满足原子性要求.
*
*/
@Test
public void testSetNX(){
Jedis jedis = new Jedis("192.168.126.129", 6379);
jedis.set("a", "111");
jedis.setnx("a", "123");
System.out.println(jedis.get("a"));
SetParams params = new SetParams();
params.xx().ex(20);
jedis.set("b", "bb", params);
}
//默认hash是无序的
@Test
public void testHash(){
Jedis jedis = new Jedis("192.168.126.129", 6379);
jedis.hset("user", "id", "100");
jedis.hset("user", "name", "101");
jedis.hset("user", "age", "18");
System.out.println(jedis.hkeys("user"));
}
//队列:先进先出 方向相反
//栈: 先进后出 方向相同 注意可变参数类型
@Test
public void testList(){
Jedis jedis = new Jedis("192.168.126.129", 6379);
jedis.lpush("list", "1","2","3","4");
System.out.println(jedis.rpop("list"));
}
@Test
public void testTx(){
Jedis jedis = new Jedis("192.168.126.129", 6379);
//开启事务
Transaction transaction = jedis.multi();
try {
transaction.set("a", "a");
transaction.set("b", "b");
transaction.set("b", "b");
//提交事务
transaction.exec();
}catch (Exception e){
transaction.discard();
}
}
}
5.SpringBoot整合Redis
5.1 编辑pro配置文件
5.2 编辑配置类
package com.jt.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import redis.clients.jedis.Jedis;
@Configuration //标识为一个配置类, 一般整合第三方
@PropertySource("classpath:/properties/redis.properties")
public class RedisConfig {
@Value("${redis.host}")
private String host;
@Value("${redis.port}")
private Integer port;
@Bean //将该方法的返回值,交给Spring容器管理
public Jedis jedis(){
return new Jedis(host,port);
}
}
6 JSON格式转化
6.1 业务说明
由于程序需要与JSON串进行数据的交互,所以通过API进行转化
6.2 代码如下
package com.jt.json;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.jt.pojo.ItemDesc;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
public class TestJSON {
/**
* json格式:
* 1.object格式 {key:value,key2:value2}
* 2.数组格式 [value,value2,value3]
* 3.嵌套格式 value可以进行嵌套.
*
* 任务: 实现对象与JSON串之间的转化.
* 面试题: 常量是否有线程安全性问题??? 没有
*/
private static final ObjectMapper MAPPER = new ObjectMapper();
@Test
public void objectToJSON() throws JsonProcessingException {
ItemDesc itemDesc = new ItemDesc();
itemDesc.setItemId(100L).setItemDesc("json测试")
.setCreated(new Date()).setUpdated(itemDesc.getCreated());
//将对象转化为JSON 调用对象的get方法 形成属性/属性值
String json = MAPPER.writeValueAsString(itemDesc);
System.out.println(json);
//将JSON串,转化为对象 反射机制 实例化对象 调用对象的set方法为属性赋值
ItemDesc itemDesc2 = MAPPER.readValue(json,ItemDesc.class);
System.out.println(itemDesc2.toString()+""+itemDesc2.getCreated());
}
public List<ItemDesc> getList(){
List<ItemDesc> list = new ArrayList<>();
ItemDesc itemDesc = new ItemDesc();
itemDesc.setItemId(100L).setItemDesc("bbb")
.setCreated(new Date()).setUpdated(new Date());
ItemDesc itemDesc2 = new ItemDesc();
itemDesc2.setItemId(200L).setItemDesc("bbb")
.setCreated(new Date()).setUpdated(new Date());
list.add(itemDesc);
list.add(itemDesc2);
return list;
}
@Test
public void ListToJSON() throws JsonProcessingException {
List<ItemDesc> list = getList();
//将对象转化为JSON
String json = MAPPER.writeValueAsString(list);
System.out.println(json);
//将JSON还原回对象
List<ItemDesc> list2 = MAPPER.readValue(json, List.class);
System.out.println(list2);
}
}
6.3 JSON转化工具API
说明: 在jt-common中添加工具API
package com.jt.util;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.jt.pojo.ItemDesc;
import org.springframework.stereotype.Component;
public class ObjectMapperUtil {
private static final ObjectMapper MAPPER = new ObjectMapper();
//1.对象转化为JSON
public static String toJSON(Object obj){
try {
return MAPPER.writeValueAsString(obj);
} catch (JsonProcessingException e) {
//将检查异常,转化为运行时异常
e.printStackTrace();
throw new RuntimeException(e);
}
}
//2.JSON转化为对象 传递什么样的类型,返回什么样的对象???
public static <T> T toObject(String json, Class<T> target){
try {
return MAPPER.readValue(json, target);
} catch (JsonProcessingException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
}
7 商品分离缓存实现
7.1 编辑工具ItemCatServiceImpl
/**
*
* 1.将商品分类信息添加到缓存中
* 2.数据结构: k-v结构 ITEM_CAT_PARENT::0 - JSON
* 3.业务流程:
* 1.先查缓存 true/false
* 2.false 用户第一次查询数据库, 将结果保存到redis缓存中
* 3.true 查询缓存
* AOP机制
*/
@Override
public List<EasyUITree> findItemCatCache(long parentId) {
long startTime = System.currentTimeMillis();
List<EasyUITree> treeList = new ArrayList<>();
//1.定义key
String key = "ITEM_CAT_PARENT::" + parentId;
//2.判断数据是否存在
if(jedis.exists(key)){
String json = jedis.get(key);
treeList =
ObjectMapperUtil.toObject(json, treeList.getClass());
long endTime = System.currentTimeMillis();
System.out.println("redis查询! 耗时:"+(endTime-startTime));
}else{
//数据不存在,查询数据库
treeList = findItemCatList(parentId);
//将数据库记录,转化为JSON
String json = ObjectMapperUtil.toJSON(treeList);
// 将数据保存到redis中
jedis.set(key, json);
long endTime = System.currentTimeMillis();
System.out.println("查询数据库!! 耗时:"+(endTime-startTime));
}
return treeList;
}
7.2 Redis效果展现
8 Spring中AOP复习
8.1 什么是AOP
名称解释: 面向切面编程
作用: 在不修改源码的条件下,对方法进行了额外了扩展.
意义: 降低了代码的耦合性.
公式: AOP = 切入点表达式 + 通知方法
8.2 切入点表达式
1.bean(“包名.类名”) 具体某个类 按类匹配 粗粒度
2.within(“包名.类名”) 可以使用通配符 几个类一起匹配 粗粒度
3.execution(返回值类型 包名.类名.方法名(参数列表)) 细粒度
4.注解方式 @annotation(注解的类型) 细粒度
8.3 通知方法
1.before 目标方法执行之前执行.
2.afterReturning 目标方法执行之后执行
3.afterThrowing 目标方法抛出异常时执行
4.after 不管上述操作如何运行,最终都要执行.
上述的4大通知,都不能控制目标方法是否执行,一般只会做程序的监控.
5.around 在目标方法执行前后 都要执行. 可以控制目标方法的执行轨迹.
8.4 AOP入门案例
package com.jt.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Component //将对象交给Spring容器管理
@Aspect //标识AOP切面
public class RedisAOP {
/**
* 1.定义切入点表达式
* bean: 被spring容器管理的对象称之为bean
* 1.1 bean(bean的ID) 按类匹配 1个
* bean(itemCatServiceImpl)
* 1.2 within(包名.类名) 按类匹配 一堆
* within(com.jt.service.*)
* 1.3 execution(返回值类型 包名.类名.方法名(参数列表))
* execution(* com.jt.service..*.*(..))
* 解释: 返回值为任意类型 com.jt.service包所有的子孙包的类
* 类中的任意方法,任意参数
* execution(Integer com.jt.service..*.add*(int))
* execution(int com.jt.service..*.add*(int))
*/
@Pointcut("execution(* com.jt.service..*.*(..))")
public void pointCut(){
}
//如何理解什么是连接点? 被切入点拦截的方法
//ProceedingJoinPoint is only supported for around advice
//只有环绕通知可以控制目标方法
@Before("pointCut()")
public void before(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
String className = joinPoint.getSignature().getDeclaringTypeName();
Object[] args = joinPoint.getArgs();
Object target = joinPoint.getTarget();
System.out.println(methodName);
System.out.println(className);
System.out.println(args);
System.out.println(target);
System.out.println("我是一个前置通知");
}
}