jvm
jvm运行时内存区域
对象创建过程(加载、解析、初始化)
对象内存布局【对象头(gc分代年龄信息,hashcode,锁状态标志,类型指针),实例数据,对齐填充】
垃圾回收器与内存分配策略
对象是否已死(引用计数(循环引用),可达性分析)
对象引用(强,弱,软,虚)
垃圾回收算法
标记清除。产生内存碎片
复制算法。空间利用率低
标记整理。
分代回收。新生代(8-1-1),老年代,永久代
垃圾回收器
serial。单线程收集器
parnew。多线程收集器
parallel。并行收集器
concurrent。并发收集器
parallel scavenge。并行的多线程收集器 标记整理
cms。最短回收停顿时间 标记清除
g1 面向服务端的收集器
类加载
类生命周期: 加载 连接 初始化 使用 卸载
类加载器
启动类加载器
扩展类加载器
引用程序类加载器
双亲委派模型
如果一个类加载器收到一个类加载的请求,它首先不会自己加载,而是把这个请求委派给父类加载器。只有父类无法完成时子类才会尝试加载。
java基础
封装 继承 多态 反射
volatile
保证线程可见性和有序性,不保证线程安全
transient
只作用于变量,不会被序列化,存在于内存中
object方法
getclass
tostring
equals
hashcode
clone
finalize
设计模式
单例模式
工厂模式
代理模式
装饰器
观察者
模版方法
策略模式
数据结构和算法
数据结构
数组
链表
链表逆序
树
遍历
二叉树左右子树转化
算法
排序算法
选择
冒泡
递归
递归开始,递归内容,递归结束
滑动窗口
双指针的一种思想,两个指针指向的元素之间形成一个窗口。
动态规划
如果一个问题,可以把所有可能的答案穷举出来,并且穷举出来后,发现存在重叠子问题,就可以考虑使用动态规划。
如最长递增子序列、最小编辑距离、背包问题、凑零钱问题
深度遍历
广度遍历
贪心算法
局部最优解:找零
集合
//List
List<String> list = new ArrayList<>();
list.add("a");//底层是数组 扩容 新数组复制
List<String> linkedlist = new LinkedList<>();
linkedlist.add("e");//底层是双向列表 不扩容
List<String> vector = new Vector<>();//底层是数组 扩容 synchronized 线程安全
vector.add("e");
//set
Set<String> hashSet = new HashSet<>();
hashSet.add("a");//底层是hashmap 扩容
LinkedHashSet<String> linkedSet = new LinkedHashSet<>();
linkedSet.add("b");//底层是linkedHashmap 扩容
TreeSet<String> treeSet = new TreeSet<>();
treeSet.add("b");//底层是treemap 红黑树 不扩容
//map
Map<String,String> map = new HashMap<>();
map.put("a","b");//底层数组+链表/红黑树
LinkedHashMap<String,String> linkedMap = new LinkedHashMap<>();
linkedMap.put("a","b");//底层是hashmap,维护了一个双向链表,linkedhashmap.entry,继承自hashmap.node
Map<String,String> treeMap = new TreeMap<>();
treeMap.put("a","b");//底层是红黑树
Hashtable<String,String> hashtable = new Hashtable<>();
hashtable.put("a","b");//底层是数组+链表 synchronized 线程安全
ConcurrentHashMap<String,String> cHashMap = new ConcurrentHashMap<>();
cHashMap.put("a","b");//底层是hashmap 线程安全 1。bucket无值,compareAndSwapObject 2。bucket有值,synchronized 锁bucket桶列表
io流
IO和NIO
IO 面向流,阻塞io
NIO 面向缓冲,非阻塞io,也是多路复用的基础
每连接每线程模型 。 消耗cpu资源
NIO主要有三大核心部分:Channel(通道),Buffer(缓冲区), Selector。
NIO的主要事件有几个:读就绪、写就绪、有新连接到来。
1.最简单的reactor模式
注册所有感兴趣的事件处理器,单线程轮询选择就绪事件,执行事件处理器。
仔细分析一下我们需要的线程,其实主要包括以下几种:
- 事件分发器,单线程选择就绪的事件。
- I/O处理器,包括connect、read、write等,这种纯CPU操作,一般开启CPU核心个线程就可以。
- 业务线程,在处理完I/O后,业务一般还会有自己的业务逻辑,有的还会有其他的阻塞I/O,如DB操作,RPC等。只要有阻塞,就需要单独的线程。
对于Redis来说,由于服务端是全局串行的,能够保证同一连接的所有请求与返回顺序一致。这样可以使用单线程+队列,把请求数据缓冲。然后pipeline发送,返回future,然后channel可读时,直接在队列中把future取回来,done()就可以了。
涉及到事件分发器的两种模式称为:Reactor和Proactor
标准/典型的Reactor:
- 步骤1:等待事件到来(Reactor负责)。
- 步骤2:将读就绪事件分发给用户定义的处理器(Reactor负责)。
- 步骤3:读数据(用户处理器负责)。
- 步骤4:处理数据(用户处理器负责)。
改进实现的模拟Proactor:
- 步骤1:等待事件到来(Proactor负责)。
- 步骤2:得到读就绪事件,执行读数据(现在由Proactor负责)。(异步)
- 步骤3:将读完成事件分发给用户处理器(Proactor负责)。(异步)
步骤4:处理数据(用户处理器负责)(异步)
在Proactor模式中,事件处理者(或者代由事件分发器发起)直接发起一个异步读写操作(相当于请求),封装了读写请求的处理逻辑,而实际的工作是由操作系统来完成的。
nio好处
- 事件驱动模型
- 避免多线程
- 单线程处理多任务
- 非阻塞I/O,I/O读写不再阻塞,而是返回0
- 基于block的传输,通常比基于流的传输更高效
- 更高级的IO函数,zero-copy
- IO多路复用大大提高了Java网络应用的可伸缩性和实用性
select、poll、epoll
- select、poll 基于轮询机制
- epoll基于os支持的I/O通知机制。epoll支持水平触发和边沿触发两种模式。
多线程
多线程的创建和使用
//1.继承thread new Thread(){ @Override public void run() { System.out.println("abd"); } }.start(); //2.实现runnable new Thread(() -> { System.out.println("abd"); }).start(); //3.实现collable接口 有返回值 Object call = new CallableImpl().call(); //4.线程池 ThreadPoolExecutor threadPool = new ThreadPoolExecutor( 10, //核心线程数 10, //最大线程数 20, //空闲线程存活时间 TimeUnit.MINUTES, //单位 new ArrayBlockingQueue<Runnable>(20), //阻塞对列 Executors.defaultThreadFactory(), //线程创建工厂 new ThreadPoolExecutor.AbortPolicy()); //拒绝策略 //AbortPolicy:中止策略。默认的饱和策略,抛出未检查的RejectedExecutionException。 //DiscardPolicy:抛弃策略。 //DiscardOldestPolicy:抛弃最旧的策略 //CallerRunsPolicy:调用者运行策略。 //提交任务 threadPool.submit(()->{});//可提交runnable 和 callable任务 threadPool.execute(()->{});//可提交runnable //阻塞队列 lock锁 ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue(20);//列表形式的工作队列 LinkedBlockingQueue linkedBlockingQueue = new LinkedBlockingQueue(20);//链表形式的工作队列 SynchronousQueue synchronousQueue = new SynchronousQueue();//SynchronousQueue不是一个真正的队列,而是一种在线程之间移交的机制。 PriorityBlockingQueue priorityBlockingQueue = new PriorityBlockingQueue(10);//优先级队 DelayQueue delayQueue = new DelayQueue();//延迟队列 //放 // offer 添加元素 满返回false // add 添加元素 满抛异常 // put 添加元素 阻塞 //取 // poll 移出并返回队列头元素 无null // remove 移出并返回头元素 无抛异常 // take 移出并返回头元素 阻塞
线程状态
thread方法
join 方法表示调用此方法的线程被阻塞,仅当该方法完成以后,才能继续运行
setPriority 提高线程的优先级
setdomain 守护线程,主线程结束守护线程也结束
线程同步
//锁synchronized 与 Lock的异同? // 同:都是用于解决线程安全问题 //异:①、Lock是显示锁,需要我们自己手动开启和关闭;synchronized是隐式锁,出了作用域就会自动释放锁。 // ②、Lock只有代码块锁,synchronized有代码块锁和方法锁。 // ③、Lock锁性能好,拓展性好。线程通信
//1。notify和wait //wait():当前线程阻塞,并释放锁 //notify():当前线程唤醒被wait的其他一个线程,谁的优先级高就唤醒谁 //notifyAll():当前线程唤醒all被wait的线程 //2。lock的condition通信//3。阻塞队列实现通信
lock锁底层原理
public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); } //tryAcquire final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); //volatile int c = getState(); if (c == 0) { if (compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; } //acquireQueued 循环获取锁 final boolean acquireQueued(final Node node, int arg) { boolean failed = true; try { boolean interrupted = false; for (;;) { final Node p = node.predecessor(); if (p == head && tryAcquire(arg)) { setHead(node); p.next = null; // help GC failed = false; return interrupted; } if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; } } finally { if (failed) cancelAcquire(node); } } //加入到阻塞队列 private Node addWaiter(Node mode) { Node node = new Node(Thread.currentThread(), mode); // Try the fast path of enq; backup to full enq on failure Node pred = tail; if (pred != null) { node.prev = pred; if (compareAndSetTail(pred, node)) { pred.next = node; return node; } } enq(node); return node; } //Unsafe 直接操作内存 compareAndSwap
threadlocal
线程本地变量
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) { table = new Entry[INITIAL_CAPACITY]; int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); table[i] = new Entry(firstKey, firstValue); size = 1; setThreshold(INITIAL_CAPACITY); } //其实是一个entry数组
spring框架
ioc和aop
aop相关注解
@aspect,@pointcut
aop五大通知类型(前置通知,后置通知,环绕通知,最终通知,异常通知)
spring事务失效场景
1.访问修饰符必须是public
2.类未被spring管理
3.方法内部调用
4.异常回滚类型与方法抛出异常不一致
5.吞了异常
6.错误的传播特性
springboot框架
微服务springcloud
//更改负载均和策略 @Bean public IRule ribbonRule() { // 负载均衡规则,改为随机 return new RandomRule(); }
1.gateway
网关路由
loadbanlancefactory,ribbon的阻塞式拦截器
reactiveloadbanlancefactory
网关过滤器
globalfilter 全局过滤器
gatewayfilter 自定义网关路由过滤器
全局异常拦截器
服务返回异常拦截
routefunction
自定义一个api
@Configuration public class RouteConfiguration { @Bean public RouterFunction routerFunction() { return RouterFunctions.route( RequestPredicates.GET("/code").and(RequestPredicates.accept(MediaType.TEXT_PLAIN)), new HandlerFunction<ServerResponse>() { @Override public Mono<ServerResponse> handle(ServerRequest serverRequest) { return ServerResponse.status(HttpStatus.OK).body(BodyInserters.fromObject("success")); } }); } }
2.熔断器
3.feign调用
4.注册中心
nacos和eureka
5.服务负载均衡
缓存redis
redis数据结构
string,hash,list,set,zset
redis 本身有持久化,为什么还要写进 mysql 呢?
RDB :快照形式是直接把内存中的数据保存到⼀个 dump ⽂件中,定时保存,保存策略。AOF :把所有的对 Redis 的服务器进⾏修改的命令都存到⼀个⽂件⾥,命令的集合。RDB 会丢数据, AOP 性能不⾏有改动先插⼊数据库,再插缓存,⽐较靠谱但性能⼀般;有改动先插缓存,批量更新到数据库,靠谱度略差,但性能好。redis的数据结构和各种应⽤场景?
a. 更多的数据结构;b. 可持久化;c. 计数器;d. 发布 - 订阅功能;e. 事务功能;f. 过期回调功能;g. 队列功能;h. 排序、聚合查询功能redis缓存问题
① 缓存穿透:大量请求根本不存在的key(下文详解)
② 缓存雪崩:redis中大量key集体过期(下文详解)
③ 缓存击穿:redis中一个热点key过期(大量用户访问该热点key,但是热点key过期)
穿透解决方案:
对空值进行缓存
设置白名单
使用布隆过滤器
网警
雪崩解决方案:进行预先的热门词汇的设置,进行key时长的调整
实时调整,监控哪些数据是热门数据,实时的调整key的过期时长
使用锁机制
击穿解决方案:进行预先的热门词汇的设置,进行key时长的调整
实时调整,监控哪些数据是热门数据,实时的调整key的过期时长
使用锁机制setnx命令
将 key 的值设为 value,当且仅当 key 不存在。
redis分布式锁
private void lock(int couponId,String uuid,String lockKey){ //lua脚本(可固定写法) String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end"; Boolean nativeLock = redisTemplate.opsForValue().setIfAbsent(lockKey,uuid,Duration.ofSeconds(30)); System.out.println(uuid+"加锁状态:"+nativeLock); if(nativeLock){ //加锁成功 try{ //TODO 做相关业务逻辑(自定义) TimeUnit.SECONDS.sleep(10L); } catch (InterruptedException e) { } finally { //解锁 Long result = redisTemplate.execute( new DefaultRedisScript<>(script,Long.class),Arrays.asList(lockKey),uuid); System.out.println("解锁状态:"+result); } }else { //自旋操作 try { System.out.println("加锁失败,睡眠5秒 进行自旋"); TimeUnit.MILLISECONDS.sleep(5000); } catch (InterruptedException e) { } //睡眠一会再尝试获取锁 lock(couponId,uuid,lockKey); } }
redis主从同步
Redis全量同步是通过RDB内存快照文件实现,增量同步则是将主机收到的写命令传播给从机。
Redis哨兵(Sentinel)模式
用文字描述一下故障切换(failover)的过程。假设主服务器宕机,哨兵1先检测到这个结果,系统并不会马上进行failover过程,仅仅是哨兵1主观的认为主服务器不可用,这个现象成为主观下线。当后面的哨兵也检测到主服务器不可用,并且数量达到一定值时,那么哨兵之间就会进行一次投票,投票的结果由一个哨兵发起,进行failover操作。切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机,这个过程称为客观下线。这样对于客户端而言,一切都是透明的。
redis底层通信模型
- Redis 服务器启动,开启主线程事件循环(Event Loop),注册
acceptTcpHandler
连接应答处理器到用户配置的监听端口对应的文件描述符,等待新连接到来;- 客户端和服务端建立网络连接;
acceptTcpHandler
被调用,主线程使用 AE 的 API 将readQueryFromClient
命令读取处理器绑定到新连接对应的文件描述符上,并初始化一个client
绑定这个客户端连接;- 客户端发送请求命令,触发读就绪事件,服务端主线程不会通过 socket 去读取客户端的请求命令,而是先将
client
放入一个 LIFO 队列clients_pending_read
;- 在事件循环(Event Loop)中,主线程执行
beforeSleep
-->handleClientsWithPendingReadsUsingThreads
,利用 Round-Robin 轮询负载均衡策略,把clients_pending_read
队列中的连接均匀地分配给 I/O 线程各自的本地 FIFO 任务队列io_threads_list[id]
和主线程自己,I/O 线程通过 socket 读取客户端的请求命令,存入client->querybuf
并解析第一个命令,但不执行命令,主线程忙轮询,等待所有 I/O 线程完成读取任务;- 主线程和所有 I/O 线程都完成了读取任务,主线程结束忙轮询,遍历
clients_pending_read
队列,执行所有客户端连接的请求命令,先调用processCommandAndResetClient
执行第一条已经解析好的命令,然后调用processInputBuffer
解析并执行客户端连接的所有命令,在其中使用processInlineBuffer
或者processMultibulkBuffer
根据 Redis 协议解析命令,最后调用processCommand
执行命令;- 根据请求命令的类型(SET, GET, DEL, EXEC 等),分配相应的命令执行器去执行,最后调用
addReply
函数族的一系列函数将响应数据写入到对应client
的写出缓冲区:client->buf
或者client->reply
,client->buf
是首选的写出缓冲区,固定大小 16KB,一般来说可以缓冲足够多的响应数据,但是如果客户端在时间窗口内需要响应的数据非常大,那么则会自动切换到client->reply
链表上去,使用链表理论上能够保存无限大的数据(受限于机器的物理内存),最后把client
添加进一个 LIFO 队列clients_pending_write
;- 在事件循环(Event Loop)中,主线程执行
beforeSleep
-->handleClientsWithPendingWritesUsingThreads
,利用 Round-Robin 轮询负载均衡策略,把clients_pending_write
队列中的连接均匀地分配给 I/O 线程各自的本地 FIFO 任务队列io_threads_list[id]
和主线程自己,I/O 线程通过调用writeToClient
把client
的写出缓冲区里的数据回写到客户端,主线程忙轮询,等待所有 I/O 线程完成写出任务;- 主线程和所有 I/O 线程都完成了写出任务, 主线程结束忙轮询,遍历
clients_pending_write
队列,如果client
的写出缓冲区还有数据遗留,则注册sendReplyToClient
到该连接的写就绪事件,等待客户端可写时在事件循环中再继续回写残余的响应数据。这里大部分逻辑和之前的单线程模型是一致的,变动的地方仅仅是把读取客户端请求命令和回写响应数据的逻辑异步化了,交给 I/O 线程去完成,这里需要特别注意的一点是:I/O 线程仅仅是读取和解析客户端命令而不会真正去执行命令,客户端命令的执行最终还是要在主线程上完成。
数据库mysql
mysql执行过程
- MySQL客户端通过协议将SQL语句发送给MySQL服务器。
- 服务器会先检查查询缓存中是否有执行过这条SQL,如果命中缓存,则将结果返回,否则进入下一个环节(查询缓存默认不开启)。
- 服务器端进行SQL解析,预处理,然后由查询优化器生成对应的执行计划。
- 服务器根据查询优化器给出的执行计划,再调用存储引擎的API执行查询。
- 将结果返回给客户端,如果开启查询缓存,则会备份一份到查询缓存中。
sql执行顺序
mysql事务
1. MySQL InnoDB 、 Mysaim 的特点?a. InnoDB :1. ⽀持事务处理2. ⽀持外键3. ⽀持⾏锁4. 不⽀持 FULLTEXT 类型的索引(在 Mysql5.6 已引⼊)5. 不保存表的具体⾏数,扫描表来计算有多少⾏6. 对于 AUTO_INCREMENT 类型的字段,必须包含只有该字段的索引7. DELETE 表时,是⼀⾏⼀⾏的删除8. InnoDB 把数据和索引存放在表空间⾥⾯9. 跨平台可直接拷⻉使⽤10. 表格很难被压缩
表空间 - > 段 256m ->区 1m -> 页 16k
独立表空间
段包括数据段,索引段,回滚段
b. MyISAM :1. 不⽀持事务,回滚将造成不完全回滚,不具有原⼦性2. 不⽀持外键3. ⽀持全⽂搜索4. 保存表的具体⾏数,不带 where 时,直接返回保存的⾏数5. DELETE 表时,先 drop 表,然后重建表6. MyISAM 表被存放在三个⽂件 。 frm ⽂件存放表格定义。 数据⽂件是 MYD ( MYData) 。 索引⽂件是MYI(MYIndex )引伸7. 跨平台很难直接拷⻉8. AUTO_INCREMENT 类型字段可以和其他字段⼀起建⽴联合索引9. 表格可以被压缩c. 选择:因为 MyISAM 相对简单所以在效率上要优于 InnoDB. 如果系统读多,写少。对原⼦性要求低。那么 MyISAM 最好的选择。且 MyISAM 恢复速度快。可直接⽤备份覆盖恢复。如果系统读少,写多的时候,尤其是并发写⼊⾼的时候。InnoDB 就是⾸选了。两种类型都有⾃⼰优缺点,选择那个完全要看⾃⼰的实际类弄。myisam查询为什么比innodb快1)数据块,InnoDB要缓存,MyISAM只缓存索引块, 这中间还有换进换出的减少;
2)InnoDB寻址要映射到块,再到行,MyISAM记录的直接是文件的OFFSET,定位比InnoDB要快
3)InnoDB还需要维护MVCC一致; 虽然你的场景没有,但他还是需要去检查和维护
2. MySQL事务四大特性 ?原子性,隔离性,一致性,持久性3数据库隔离级别是什么?有什么作⽤?读未提交读已提交可重复读串行化mysql事务底层mvccmysql索引
索引类型
Mysql目前主要有以下几种索引类型:FULLTEXT,HASH,BTREE,RTREE。
索引种类
普通索引
主键索引
唯一索引 可以有null
联合索引
全文索引
索引失效场景
索引一定不是越多越好,越全越好,一定是建合适的。
匹配列前缀可用到索引 like 9999%,like %9999%、like %9999用不到索引;
Where 条件中 not in 和 <>操作无法使用索引;
匹配范围值,order by 也可用到索引;
多用指定列查询,只返回自己想到的数据列,少用select *;
联合索引中如果不是按照索引最左列开始查找,无法使用索引;
隐式转换和函数查询数据据量多,导致全表扫描
mysql锁
a. ⾏锁:数据库表中某⼀⾏被锁住。b. 表锁:整个数据库表被锁住。c. 乐观锁:顾名思义,就是很乐观,每次去拿数据的时候都认为别⼈不会修改,具体实现是给表增加⼀个版本号的字段,在执⾏ update 操作时⽐较该版本号是否与当前数据库中版本号⼀致,如⼀致,更新数据,反之拒绝。d. 悲观锁:顾名思义,就是很悲观,每次去拿数据的时候都认为别⼈会修改。读数据的时候会上锁,直到 update 完成才释放锁,使⽤悲观锁要注意不要锁住整个表。mvcc机制索引优化
- 查看表结构 desc table_name; - 查看生成表的SQL show create table table_name; - 查看索引 show index from table_name; - 查看执行时间 set profiling = 1; SQL... show profiles; - explain查询计划 id:选择标识符 select_type:表示查询的类型。 simple,union,primary,subquery,dependent subquery table:输出结果集的表 partitions:匹配的分区 type:表示表的连接类型 ref,const,eq_ref,system,range possible_keys:表示查询时,可能使用的索引 key:表示实际使用的索引 key_len:索引字段的长度 ref:列与索引的比较 rows:扫描出的行数(估算的行数) filtered:按表条件过滤的行百分比 Extra:执行情况的描述和说明 - 慢查询日志 --查询配置命令 show variables like '%query%'; --当前配置参数 binlog_rows_query_log_events OFF ft_query_expansion_limit 20 have_query_cache YES --时间限制,超过此时间,则记录 long_query_time 10.000000 query_alloc_block_size 8192 query_cache_limit 1048576 query_cache_min_res_unit 4096 query_cache_size 1048576 query_cache_type OFF query_cache_wlock_invalidate OFF query_prealloc_size 8192 --是否开启慢日志记录 slow_query_log OFF --日志文件 slow_query_log_file D:\mysql-5.7.18-winx64\data\Jack-slow.log --看慢查询sql mysqldumpslow -s c -t 10 /var/run/mysqld/mysqld-slow.log # 取出使用最多的10条慢查询
消息队列kafka
Kafka 概述:深入理解架构 - 知乎 (zhihu.com)https://zhuanlan.zhihu.com/p/103249714
运维相关
nginx配置 反向代理和负载均衡
location /backstage { alias /home/dispatch/ui; try_files $uri $uri/ /backstage/index.html; index index.html index.htm; } location /article.html { root /home/dispatch; index article.html; try_files $uri $uri/ /article.html; } location /stage-api/ { proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header REMOTE-HOST $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; rewrite ^/stage-api/code$ /code break; proxy_pass http://172.21.105.252:8080/; }
skywalking
https://www.yuque.com/yulal/noaqkm/mfycaknfd2moetss
链路追踪
服务拓扑
cpu资源
服务监控
日志查看
服务告警