1. 还原项目
1.1 修改端口号
1.2 修改图片路径
1.3 修改nginx配置文件
1).反向代理到8091服务器
2).修改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安装包
1).上传安装包
2).解压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发布
1).校验JDK
2).进入bin目录
3).启动mycat
4).状态检查
5).日志检查 根据报错提示 检查问题
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台数据库同时都是主机/从机)
配置步骤:
1).检查主库状态
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 解压文件
[root@localhost src]# 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
展现行号:
1).关闭IP绑定
2).关闭保护模式
3).开启后台运行
3.4.4 Redis启动命令
1).启动redis命令
redis-server redis.conf
2).进入redis客户端
redis-cli -p 6379
3).关闭redis
redis-cli -p 6379 shutdown
4.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("我是一个前置通知");
}
}
9 自定义注解实现缓存
如何设计 自己定!!!