JasonsGong.github.io/posts/46317.html
2024-03-28 11:28:52 +08:00

903 lines
102 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html><html lang="zh-CN" data-theme="light"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0,viewport-fit=cover"><title>面试专题 | The Blog</title><meta name="author" content="Jason"><meta name="copyright" content="Jason"><meta name="format-detection" content="telephone=no"><meta name="theme-color" content="#ffffff"><meta name="description" content="50W字的面试文档来源于:咕泡教育 个人使用 不外传 在线预览链接https:&#x2F;&#x2F;www.aliyundrive.com&#x2F;s&#x2F;F2wn9fxYhFs 黑马程序员Java面试视频教程黑马程序员新版Java面试专题视频教程java八股文面试全套真题+深度详解(含大厂高频面试真题) https:&#x2F;&#x2F;www.bilibili.com&#x2F;video&#x2F;BV1yT411H7YK&#x2F;?share_source">
<meta property="og:type" content="article">
<meta property="og:title" content="面试专题">
<meta property="og:url" content="https://jasonsgong.gitee.io/posts/46317.html">
<meta property="og:site_name" content="The Blog">
<meta property="og:description" content="50W字的面试文档来源于:咕泡教育 个人使用 不外传 在线预览链接https:&#x2F;&#x2F;www.aliyundrive.com&#x2F;s&#x2F;F2wn9fxYhFs 黑马程序员Java面试视频教程黑马程序员新版Java面试专题视频教程java八股文面试全套真题+深度详解(含大厂高频面试真题) https:&#x2F;&#x2F;www.bilibili.com&#x2F;video&#x2F;BV1yT411H7YK&#x2F;?share_source">
<meta property="og:locale" content="zh_CN">
<meta property="og:image" content="https://jasonsgong.gitee.io/img/8.png">
<meta property="article:published_time" content="2023-05-08T02:31:58.000Z">
<meta property="article:modified_time" content="2023-09-11T23:36:48.000Z">
<meta property="article:author" content="Jason">
<meta property="article:tag" content="面试">
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="https://jasonsgong.gitee.io/img/8.png"><link rel="shortcut icon" href="/img/%E5%9B%BE%E6%A0%87.png"><link rel="canonical" href="https://jasonsgong.gitee.io/posts/46317.html"><link rel="preconnect" href="//fastly.jsdelivr.net"/><link rel="preconnect" href="//hm.baidu.com"/><link rel="preconnect" href="//busuanzi.ibruce.info"/><link rel="stylesheet" href="/css/index.css"><link rel="stylesheet" href="/cdn/icon/fontawesome-free/css/all.min.css" media="print" onload="this.media='all'"><link rel="stylesheet" href="/cdn/css/snackbar.min.css" media="print" onload="this.media='all'"><link rel="stylesheet" href="/cdn/css/fancybox.min.css" media="print" onload="this.media='all'"><script>var _hmt = _hmt || [];
(function() {
var hm = document.createElement("script");
hm.src = "https://hm.baidu.com/hm.js?aad83e413776c1cd5d11ed7eeedd98f7";
var s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(hm, s);
})();
</script><script>const GLOBAL_CONFIG = {
root: '/',
algolia: undefined,
localSearch: {"path":"/search.xml","preload":true,"top_n_per_article":1,"unescape":false,"languages":{"hits_empty":"找不到您查询的内容:${query}","hits_stats":"共找到 ${hits} 篇文章"}},
translate: undefined,
noticeOutdate: undefined,
highlight: {"plugin":"highlighjs","highlightCopy":true,"highlightLang":true,"highlightHeightLimit":400},
copy: {
success: '复制成功',
error: '复制错误',
noSupport: '浏览器不支持'
},
relativeDate: {
homepage: true,
post: true
},
runtime: '天',
dateSuffix: {
just: '刚刚',
min: '分钟前',
hour: '小时前',
day: '天前',
month: '个月前'
},
copyright: undefined,
lightbox: 'mediumZoom',
Snackbar: {"chs_to_cht":"你已切换为繁体","cht_to_chs":"你已切换为简体","day_to_night":"你已切换为深色模式","night_to_day":"你已切换为浅色模式","bgLight":"#006650","bgDark":"#006650","position":"top-center"},
source: {
justifiedGallery: {
js: 'https://fastly.jsdelivr.net/npm/flickr-justified-gallery/dist/fjGallery.min.js',
css: 'https://fastly.jsdelivr.net/npm/flickr-justified-gallery/dist/fjGallery.min.css'
}
},
isPhotoFigcaption: false,
islazyload: false,
isAnchor: true,
percent: {
toc: true,
rightside: false,
},
autoDarkmode: true
}</script><script id="config-diff">var GLOBAL_CONFIG_SITE = {
title: '面试专题',
isPost: true,
isHome: false,
isHighlightShrink: false,
isToc: true,
postUpdate: '2023-09-12 07:36:48'
}</script><noscript><style type="text/css">
#nav {
opacity: 1
}
.justified-gallery img {
opacity: 1
}
#recent-posts time,
#post-meta time {
display: inline !important
}
</style></noscript><script>(win=>{
win.saveToLocal = {
set: function setWithExpiry(key, value, ttl) {
if (ttl === 0) return
const now = new Date()
const expiryDay = ttl * 86400000
const item = {
value: value,
expiry: now.getTime() + expiryDay,
}
localStorage.setItem(key, JSON.stringify(item))
},
get: function getWithExpiry(key) {
const itemStr = localStorage.getItem(key)
if (!itemStr) {
return undefined
}
const item = JSON.parse(itemStr)
const now = new Date()
if (now.getTime() > item.expiry) {
localStorage.removeItem(key)
return undefined
}
return item.value
}
}
win.getScript = url => new Promise((resolve, reject) => {
const script = document.createElement('script')
script.src = url
script.async = true
script.onerror = reject
script.onload = script.onreadystatechange = function() {
const loadState = this.readyState
if (loadState && loadState !== 'loaded' && loadState !== 'complete') return
script.onload = script.onreadystatechange = null
resolve()
}
document.head.appendChild(script)
})
win.getCSS = (url,id = false) => new Promise((resolve, reject) => {
const link = document.createElement('link')
link.rel = 'stylesheet'
link.href = url
if (id) link.id = id
link.onerror = reject
link.onload = link.onreadystatechange = function() {
const loadState = this.readyState
if (loadState && loadState !== 'loaded' && loadState !== 'complete') return
link.onload = link.onreadystatechange = null
resolve()
}
document.head.appendChild(link)
})
win.activateDarkMode = function () {
document.documentElement.setAttribute('data-theme', 'dark')
if (document.querySelector('meta[name="theme-color"]') !== null) {
document.querySelector('meta[name="theme-color"]').setAttribute('content', '#0d0d0d')
}
}
win.activateLightMode = function () {
document.documentElement.setAttribute('data-theme', 'light')
if (document.querySelector('meta[name="theme-color"]') !== null) {
document.querySelector('meta[name="theme-color"]').setAttribute('content', '#ffffff')
}
}
const t = saveToLocal.get('theme')
const isDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches
const isLightMode = window.matchMedia('(prefers-color-scheme: light)').matches
const isNotSpecified = window.matchMedia('(prefers-color-scheme: no-preference)').matches
const hasNoSupport = !isDarkMode && !isLightMode && !isNotSpecified
if (t === undefined) {
if (isLightMode) activateLightMode()
else if (isDarkMode) activateDarkMode()
else if (isNotSpecified || hasNoSupport) {
const now = new Date()
const hour = now.getHours()
const isNight = hour <= 8 || hour >= 22
isNight ? activateDarkMode() : activateLightMode()
}
window.matchMedia('(prefers-color-scheme: dark)').addListener(function (e) {
if (saveToLocal.get('theme') === undefined) {
e.matches ? activateDarkMode() : activateLightMode()
}
})
} else if (t === 'light') activateLightMode()
else activateDarkMode()
const asideStatus = saveToLocal.get('aside-status')
if (asideStatus !== undefined) {
if (asideStatus === 'hide') {
document.documentElement.classList.add('hide-aside')
} else {
document.documentElement.classList.remove('hide-aside')
}
}
const detectApple = () => {
if(/iPad|iPhone|iPod|Macintosh/.test(navigator.userAgent)){
document.documentElement.classList.add('apple')
}
}
detectApple()
})(window)</script><script src="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script><script type="text/javascript" src ="/js/welcome.js" ></script><script src="/js/sweetalert.js"></script><link rel="stylesheet" href="/css/sweetalert.css"><!-- hexo injector head_end start --><link rel="stylesheet" href="https://npm.elemecdn.com/hexo-butterfly-swiper/lib/swiper.min.css" media="print" onload="this.media='all'"><link rel="stylesheet" href="https://npm.elemecdn.com/hexo-butterfly-swiper/lib/swiperstyle.css" media="print" onload="this.media='all'"><!-- hexo injector head_end end --><meta name="generator" content="Hexo 6.3.0"></head><body><div id="sidebar"><div id="menu-mask"></div><div id="sidebar-menus"><div class="avatar-img is-center"><img src="/img/avatar.jpg" onerror="onerror=null;src='/img/loading.gif'" alt="avatar"/></div><div class="sidebar-site-data site-data is-center"><a href="/archives/"><div class="headline">文章</div><div class="length-num">77</div></a><a href="/tags/"><div class="headline">标签</div><div class="length-num">47</div></a><a href="/categories/"><div class="headline">分类</div><div class="length-num">10</div></a></div><br/><div class="menus_items"><div class="menus_item"><a class="site-page" target="_blank" rel="noopener" href="https://gitee.com/JasonsGong/jasonsgong/pages"><i class="fa-fw fas fa-sync-alt"></i><span> 更新</span></a></div><div class="menus_item"><a class="site-page" target="_blank" rel="noopener" href="https://www.tutorialspoint.com/compile_java8_online.php"><i class="fa-fw fas fa-code"></i><span> 代码</span></a></div><div class="menus_item"><a class="site-page" href="/notice/"><i class="fa-fw fas fa-stream"></i><span> 公告</span></a></div><div class="menus_item"><a class="site-page" href="/website/"><i class="fa-fw fas fa-list"></i><span> 网址</span></a></div><div class="menus_item"><a class="site-page" href="/record/"><i class="fa-fw fas fa-bars"></i><span> 记录</span></a></div><div class="menus_item"><a class="site-page" target="_blank" rel="noopener" href="https://tongji.baidu.com/main/overview/10000573913/overview/index?siteId=20315864"><i class="fa-fw fas fa-chart-line"></i><span> 统计</span></a></div><div class="menus_item"><a class="site-page" target="_blank" rel="noopener" href="https://jasonsgong.github.io"><i class="fa-fw fas fa-wave-square"></i><span> 镜像</span></a></div><div class="menus_item"><a class="site-page" href="/"><i class="fa-fw fas fa-home"></i><span> 主页</span></a></div></div></div></div><div class="post" id="body-wrap"><header class="not-top-img" id="page-header"><nav id="nav"><span id="blog-info"><a href="/" title="The Blog"><img class="site-icon" src="/img/logo.png"/><span class="site-name">The Blog</span></a></span><div id="menus"><div id="search-button"><a class="site-page social-icon search" href="javascript:void(0);"><i class="fas fa-search fa-fw"></i><span> 搜索</span></a></div><div class="menus_items"><div class="menus_item"><a class="site-page" target="_blank" rel="noopener" href="https://gitee.com/JasonsGong/jasonsgong/pages"><i class="fa-fw fas fa-sync-alt"></i><span> 更新</span></a></div><div class="menus_item"><a class="site-page" target="_blank" rel="noopener" href="https://www.tutorialspoint.com/compile_java8_online.php"><i class="fa-fw fas fa-code"></i><span> 代码</span></a></div><div class="menus_item"><a class="site-page" href="/notice/"><i class="fa-fw fas fa-stream"></i><span> 公告</span></a></div><div class="menus_item"><a class="site-page" href="/website/"><i class="fa-fw fas fa-list"></i><span> 网址</span></a></div><div class="menus_item"><a class="site-page" href="/record/"><i class="fa-fw fas fa-bars"></i><span> 记录</span></a></div><div class="menus_item"><a class="site-page" target="_blank" rel="noopener" href="https://tongji.baidu.com/main/overview/10000573913/overview/index?siteId=20315864"><i class="fa-fw fas fa-chart-line"></i><span> 统计</span></a></div><div class="menus_item"><a class="site-page" target="_blank" rel="noopener" href="https://jasonsgong.github.io"><i class="fa-fw fas fa-wave-square"></i><span> 镜像</span></a></div><div class="menus_item"><a class="site-page" href="/"><i class="fa-fw fas fa-home"></i><span> 主页</span></a></div></div><div id="toggle-menu"><a class="site-page" href="javascript:void(0);"><i class="fas fa-bars fa-fw"></i></a></div></div></nav></header><main class="layout" id="content-inner"><div id="post"><div id="post-info"><h1 class="post-title">面试专题</h1><div id="post-meta"><div class="meta-firstline"><span class="post-meta-date"><i class="far fa-calendar-alt fa-fw post-meta-icon"></i><span class="post-meta-label">发表于</span><time class="post-meta-date-created" datetime="2023-05-08T02:31:58.000Z" title="发表于 2023-05-08 10:31:58">2023-05-08</time><span class="post-meta-separator">|</span><i class="fas fa-history fa-fw post-meta-icon"></i><span class="post-meta-label">更新于</span><time class="post-meta-date-updated" datetime="2023-09-11T23:36:48.000Z" title="更新于 2023-09-12 07:36:48">2023-09-12</time></span><span class="post-meta-categories"><span class="post-meta-separator">|</span><i class="fas fa-inbox fa-fw post-meta-icon"></i><a class="post-meta-categories" href="/categories/%E9%9D%A2%E8%AF%95/">面试</a></span></div><div class="meta-secondline"><span class="post-meta-separator">|</span><span class="post-meta-wordcount"><i class="far fa-file-word fa-fw post-meta-icon"></i><span class="post-meta-label">字数总计:</span><span class="word-count">18.9k</span><span class="post-meta-separator">|</span><i class="far fa-clock fa-fw post-meta-icon"></i><span class="post-meta-label">阅读时长:</span><span>56分钟</span></span><span class="post-meta-separator">|</span><span class="post-meta-pv-cv" id="" data-flag-title="面试专题"><i class="far fa-eye fa-fw post-meta-icon"></i><span class="post-meta-label">阅读量:</span><span id="busuanzi_value_page_pv"><i class="fa-solid fa-spinner fa-spin"></i></span></span></div></div></div><article class="post-content" id="article-container"><h2 id="50W字的面试文档"><a href="#50W字的面试文档" class="headerlink" title="50W字的面试文档"></a>50W字的面试文档</h2><p>来源于:咕泡教育 个人使用 不外传</p>
<p><strong>在线预览链接:<a target="_blank" rel="noopener" href="https://www.aliyundrive.com/s/F2wn9fxYhFs">https://www.aliyundrive.com/s/F2wn9fxYhFs</a></strong></p>
<p><img src="/pictures/image-20230513134435330.png" alt="image-20230513134435330"></p>
<h2 id="黑马程序员Java面试视频教程"><a href="#黑马程序员Java面试视频教程" class="headerlink" title="黑马程序员Java面试视频教程"></a>黑马程序员Java面试视频教程</h2><p>黑马程序员新版Java面试专题视频教程java八股文面试全套真题+深度详解(含大厂高频面试真题) <a target="_blank" rel="noopener" href="https://www.bilibili.com/video/BV1yT411H7YK/?share_source=copy_web&vd_source=aee5e475191b69e6c781059ab6662584">https://www.bilibili.com/video/BV1yT411H7YK/?share_source=copy_web&amp;vd_source=aee5e475191b69e6c781059ab6662584</a></p>
<h2 id="1-Redis篇"><a href="#1-Redis篇" class="headerlink" title="1.Redis篇"></a>1.Redis篇</h2><blockquote>
<p><strong>面试官</strong>:什么是缓存穿透 ? 怎么解决 ?</p>
<p><strong>候选人</strong></p>
<p>嗯~~,我想一下</p>
<p>缓存穿透是指查询一个一定<strong>不存在</strong>的数据,如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到 DB 去查询,可能导致 DB 挂掉。这种情况大概率是遭到了攻击。</p>
<p>解决方案的话,我们通常都会用布隆过滤器来解决它</p>
<p><strong>面试官</strong>:好的,你能介绍一下布隆过滤器吗?</p>
<p><strong>候选人</strong></p>
<p>嗯,是这样~</p>
<p>布隆过滤器主要是用于检索一个元素是否在一个集合中。我们当时使用的是redisson实现的布隆过滤器。</p>
<p>它的底层主要是先去初始化一个比较大数组里面存放的二进制0或1。在一开始都是0当一个key来了之后经过3次hash计算模于数组长度找到数据的下标然后把数组中原来的0改为1这样的话三个数组的位置就能标明一个key的存在。查找的过程也是一样的。</p>
<p>当然是有缺点的布隆过滤器有可能会产生一定的误判我们一般可以设置这个误判率大概不会超过5%其实这个误判是必然存在的要不就得增加数组的长度其实已经算是很划分了5%以内的误判率一般的项目也能接受,不至于高并发下压倒数据库。</p>
<p><strong>面试官</strong>:什么是缓存击穿 ? 怎么解决 ?</p>
<p><strong>候选人</strong></p>
<p>嗯!!</p>
<p>缓存击穿的意思是对于设置了过期时间的key缓存在某个时间点过期的时候恰好这时间点对这个Key有大量的并发请求过来这些请求发现缓存过期一般都会从后端 DB 加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把 DB 压垮。</p>
<p>解决方案有两种方式:</p>
<p>第一可以使用互斥锁当缓存失效时不立即去load db先使用如 Redis 的 setnx 去设置一个互斥锁,当操作成功返回时再进行 load db的操作并回设缓存否则重试get缓存的方法</p>
<p>第二种方案可以设置当前key逻辑过期大概是思路如下</p>
<p>在设置key的时候设置一个过期时间字段一块存入缓存中不给当前key设置过期时间</p>
<p>当查询的时候从redis取出数据后判断时间是否过期</p>
<p>③:如果过期则开通另外一个线程进行数据同步,当前线程正常返回数据,这个数据不是最新</p>
<p>当然两种方案各有利弊:&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;</p>
<p>如果选择数据的强一致性,建议使用分布式锁的方案,性能上可能没那么高,锁需要等,也有可能产生死锁的问题</p>
<p>如果选择key的逻辑删除则优先考虑的高可用性性能比较高但是数据同步这块做不到强一致。</p>
<p><strong>面试官</strong>:什么是缓存雪崩 ? 怎么解决 ?</p>
<p><strong>候选人</strong></p>
<p>嗯!!</p>
<p>缓存雪崩意思是设置缓存时采用了相同的过期时间导致缓存在某一时刻同时失效请求全部转发到DBDB 瞬时压力过重雪崩。与缓存击穿的区别雪崩是很多key击穿是某一个key缓存。</p>
<p>解决方案主要是可以将缓存失效时间分散开比如可以在原有的失效时间基础上增加一个随机值比如1-5分钟随机这样每一个缓存的过期时间的重复率就会降低就很难引发集体失效的事件。</p>
<p><strong>面试官</strong>redis做为缓存mysql的数据如何与redis进行同步呢双写一致性</p>
<p><strong>候选人</strong>就说我最近做的这个项目里面有xxxx<strong>根据自己的简历上写</strong>的功能需要让数据库与redis高度保持一致因为要求时效性比较高我们当时采用的读写锁保证的强一致性。</p>
<p>我们采用的是redisson实现的读写锁在读的时候添加共享锁可以保证读读不互斥读写互斥。当我们更新数据的时候添加排他锁它是读写读读都互斥这样就能保证在写数据的同时是不会让其他线程读数据的避免了脏数据。这里面需要注意的是读方法和写方法上需要使用同一把锁才行。</p>
<p><strong>面试官</strong>:那这个排他锁是如何保证读写、读读互斥的呢?</p>
<p><strong>候选人</strong>其实排他锁底层使用也是setnx保证了同时只能有一个线程操作锁住的方法</p>
<p><strong>面试官</strong>:你听说过延时双删吗?为什么不用它呢?</p>
<p><strong>候选人</strong>:延迟双删,如果是写操作,我们先把缓存中的数据删除,然后更新数据库,最后再延时删除缓存中的数据,其中这个延时多久不太好确定,在延时的过程中可能会出现脏数据,并不能保证强一致性,所以没有采用它。</p>
<p><strong>面试官</strong>redis做为缓存mysql的数据如何与redis进行同步呢双写一致性</p>
<p><strong>候选人</strong>就说我最近做的这个项目里面有xxxx<strong>根据自己的简历上写</strong>)的功能,数据同步可以有一定的延时(符合大部分业务)</p>
<p>我们当时采用的阿里的canal组件实现数据同步不需要更改业务代码部署一个canal服务。canal服务把自己伪装成mysql的一个从节点当mysql数据更新以后canal会读取binlog数据然后在通过canal的客户端获取到数据更新缓存即可。</p>
<p><strong>面试官</strong>redis做为缓存数据的持久化是怎么做的</p>
<p><strong>候选人</strong>在Redis中提供了两种数据持久化的方式1、RDB 2、AOF</p>
<p><strong>面试官</strong>:这两种持久化方式有什么区别呢?</p>
<p><strong>候选人</strong>RDB是一个快照文件它是把redis内存存储的数据写到磁盘上当redis实例宕机恢复数据的时候方便从RDB的快照文件中恢复数据。</p>
<p>AOF的含义是追加文件当redis操作写命令的时候都会存储这个文件中当redis实例宕机恢复数据的时候会从这个文件中再次执行一遍命令来恢复数据</p>
<p><strong>面试官</strong>:这两种方式,哪种恢复的比较快呢?</p>
<p><strong>候选人</strong>RDB因为是二进制文件在保存的时候体积也是比较小的它恢复的比较快但是它有可能会丢数据我们通常在项目中也会使用AOF来恢复数据虽然AOF恢复的速度慢一些但是它丢数据的风险要小很多在AOF文件中可以设置刷盘策略我们当时设置的就是每秒批量写入一次命令</p>
<p><strong>面试官</strong>Redis的数据过期策略有哪些 ?</p>
<p><strong>候选人</strong></p>
<p>嗯~在redis中提供了两种数据过期删除策略</p>
<p>第一种是惰性删除在设置该key过期时间后我们不去管它当需要该key时我们在检查其是否过期如果过期我们就删掉它反之返回该key。</p>
<p>第二种是 定期删除就是说每隔一段时间我们就对一些key进行检查删除里面过期的key</p>
<p>定期清理的两种模式:</p>
<ul>
<li>SLOW模式是定时任务执行频率默认为10hz每次不超过25ms以通过修改配置文件redis.conf 的 <strong>hz</strong> 选项来调整这个次数</li>
<li>FAST模式执行频率不固定每次事件循环会尝试执行但两次间隔不低于2ms每次耗时不超过1ms</li>
</ul>
<p>Redis的过期删除策略<strong>惰性删除 + 定期删除</strong>两种策略进行配合使用。</p>
<p><strong>面试官</strong>Redis的数据淘汰策略有哪些 ?</p>
<p><strong>候选人</strong></p>
<p>这个在redis中提供了很多种默认是noeviction不删除任何数据内部不足直接报错</p>
<p>是可以在redis的配置文件中进行设置的里面有两个非常重要的概念一个是LRU另外一个是LFU</p>
<p>LRU的意思就是最少最近使用用当前时间减去最后一次访问时间这个值越大则淘汰优先级越高。</p>
<p>LFU的意思是最少频率使用。会统计每个key的访问频率值越小淘汰优先级越高</p>
<p>我们在项目设置的allkeys-lru挑选最近最少使用的数据淘汰把一些经常访问的key留在redis中</p>
<p><strong>面试官</strong>数据库有1000万数据 ,Redis只能缓存20w数据, 如何保证Redis中的数据都是热点数据 ?</p>
<p><strong>候选人</strong></p>
<p>嗯,我想一下~~</p>
<p>可以使用 allkeys-lru (挑选最近最少使用的数据淘汰)淘汰策略,那留下来的都是经常访问的热点数据</p>
<p><strong>面试官</strong>Redis的内存用完了会发生什么</p>
<p><strong>候选人</strong></p>
<p>嗯~这个要看redis的数据淘汰策略是什么如果是默认的配置redis内存用完以后则直接报错。我们当时设置的 allkeys-lru 策略。把最近最常访问的数据留在缓存中。</p>
<p><strong>面试官</strong>Redis分布式锁如何实现 ?</p>
<p><strong>候选人</strong>在redis中提供了一个命令setnx(SET if not exists)</p>
<p>由于redis的单线程的用了命令之后只能有一个客户端对某一个key设置值在没有过期或删除key的时候是其他客户端是不能设置这个key的</p>
<p><strong>面试官</strong>好的那你如何控制Redis实现分布式锁有效时长呢</p>
<p><strong>候选人</strong>的确redis的setnx指令不好控制这个问题我们当时采用的redis的一个框架redisson实现的。</p>
<p>在redisson中需要手动加锁并且可以控制锁的失效时间和等待时间当锁住的一个业务还没有执行完成的时候在redisson中引入了一个看门狗机制就是说每隔一段时间就检查当前业务是否还持有锁如果持有就增加加锁的持有时间当业务执行完成之后需要使用释放锁就可以了</p>
<p>还有一个好处就是在高并发下一个业务有可能会执行很快先客户1持有锁的时候客户2来了以后并不会马上拒绝它会自旋不断尝试获取锁如果客户1释放之后客户2就可以马上持有锁性能也得到了提升。</p>
<p><strong>面试官</strong>好的redisson实现的分布式锁是可重入的吗</p>
<p><strong>候选人</strong>是可以重入的。这样做是为了避免死锁的产生。这个重入其实在内部就是判断是否是当前线程持有的锁如果是当前线程持有的锁就会计数如果释放锁就会在计算上减一。在存储数据的时候采用的hash结构大key可以按照自己的业务进行定制其中小key是当前线程的唯一标识value是当前线程重入的次数</p>
<p><strong>面试官</strong>redisson实现的分布式锁能解决主从一致性的问题吗</p>
<p><strong>候选人</strong>这个是不能的比如当线程1加锁成功后master节点数据会异步复制到slave节点此时当前持有Redis锁的master节点宕机slave节点被提升为新的master节点假如现在来了一个线程2再次加锁会在新的master节点上加锁成功这个时候就会出现两个节点同时持有一把锁的问题。</p>
<p>我们可以利用redisson提供的红锁来解决这个问题它的主要作用是不能只在一个redis实例上创建锁应该是在多个redis实例上创建锁并且要求在大多数redis节点上都成功创建锁红锁中要求是redis的节点数量要过半。这样就能避免线程1加锁成功后master节点宕机导致线程2成功加锁到新的master节点上的问题了。</p>
<p>但是,如果使用了红锁,因为需要同时在多个节点上都添加锁,性能就变的很低了,并且运维维护成本也非常高,所以,我们一般在项目中也不会直接使用红锁,并且官方也暂时废弃了这个红锁</p>
<p><strong>面试官</strong>:好的,如果业务非要保证数据的强一致性,这个该怎么解决呢?</p>
<p><strong>候选人:</strong>嗯~redis本身就是支持高可用的做到强一致性就非常影响性能所以如果有强一致性要求高的业务建议使用zookeeper实现的分布式锁它是可以保证强一致性的。</p>
<p><strong>面试官</strong>Redis集群有哪些方案, 知道嘛 ?</p>
<p><strong>候选人</strong>:嗯~~在Redis中提供的集群方案总共有三种主从复制、哨兵模式、Redis分片集群</p>
<p><strong>面试官</strong>:那你来介绍一下主从同步</p>
<p><strong>候选人</strong>是这样的单节点Redis的并发能力是有上限的要进一步提高Redis的并发能力可以搭建主从集群实现读写分离。一般都是一主多从主节点负责写数据从节点负责读数据主节点写入数据之后需要把数据同步到从节点中</p>
<p><strong>面试官</strong>:能说一下,主从同步数据的流程</p>
<p><strong>候选人</strong>:嗯~~,好!主从同步分为了两个阶段,一个是全量同步,一个是增量同步</p>
<p>全量同步是指从节点第一次与主节点建立连接的时候使用全量同步,流程是这样的:</p>
<p>第一从节点请求主节点同步数据其中从节点会携带自己的replication id和offset偏移量。</p>
<p>第二主节点判断是否是第一次请求主要判断的依据就是主节点与从节点是否是同一个replication id如果不是就说明是第一次同步那主节点就会把自己的replication id和offset发送给从节点让从节点与主节点的信息保持一致。</p>
<p>第三在同时主节点会执行bgsave生成rdb文件后发送给从节点去执行从节点先把自己的数据清空然后执行主节点发送过来的rdb文件这样就保持了一致</p>
<p>当然如果在rdb生成执行期间依然有请求到了主节点而主节点会以命令的方式记录到缓冲区缓冲区是一个日志文件最后把这个日志文件发送给从节点这样就能保证主节点与从节点完全一致了后期再同步数据的时候都是依赖于这个日志文件这个就是全量同步</p>
<p>增量同步指的是当从节点服务重启之后数据就不一致了所以这个时候从节点会请求主节点同步数据主节点还是判断不是第一次请求不是第一次就获取从节点的offset值然后主节点从命令日志中获取offset值之后的数据发送给从节点进行数据同步</p>
<p><strong>面试官</strong>怎么保证Redis的高并发高可用</p>
<p><strong>候选人</strong>首先可以搭建主从集群再加上使用redis中的哨兵模式哨兵模式可以实现主从集群的自动故障恢复里面就包含了对主从服务的监控、自动故障恢复、通知如果master故障Sentinel会将一个slave提升为master。当故障实例恢复后也以新的master为主同时Sentinel也充当Redis客户端的服务发现来源当集群发生故障转移时会将最新信息推送给Redis的客户端所以一般项目都会采用哨兵的模式来保证redis的高并发高可用</p>
<p><strong>面试官</strong>你们使用redis是单点还是集群哪种集群</p>
<p><strong>候选人</strong>我们当时使用的是主从1主1从加哨兵。一般单节点不超过10G内存如果Redis内存不足则可以给不同服务分配独立的Redis主从节点。尽量不做分片集群。因为集群维护起来比较麻烦并且集群之间的心跳检测和数据通信会消耗大量的网络带宽也没有办法使用lua脚本和事务</p>
<p><strong>面试官</strong>redis集群脑裂该怎么解决呢</p>
<p><strong>候选人</strong>:嗯! 这个在项目很少见不过脑裂的问题是这样的我们现在用的是redis的哨兵模式集群的</p>
<p>有的时候由于网络等原因可能会出现脑裂的情况就是说由于redis master节点和redis salve节点和sentinel处于不同的网络分区使得sentinel没有能够心跳感知到master所以通过选举的方式提升了一个salve为master这样就存在了两个master就像大脑分裂了一样这样会导致客户端还在old master那里写入数据新节点无法同步数据当网络恢复后sentinel会将old master降为salve这时再从新master同步数据这会导致old master中的大量数据丢失。</p>
<p>关于解决的话我记得在redis的配置中可以设置第一可以设置最少的salve节点个数比如设置至少要有一个从节点才能同步数据第二个可以设置主从数据复制和同步的延迟时间达不到要求就拒绝请求就可以避免大量的数据丢失</p>
<p><strong>面试官</strong>redis的分片集群有什么作用</p>
<p><strong>候选人</strong>分片集群主要解决的是海量数据存储的问题集群中有多个master每个master保存不同数据并且还可以给每个master设置多个slave节点就可以继续增大集群的高并发能力。同时每个master之间通过ping监测彼此健康状态就类似于哨兵模式了。当客户端请求可以访问集群任意节点最终都会被转发到正确节点</p>
<p><strong>面试官</strong>Redis分片集群中数据是怎么存储和读取的</p>
<p><strong>候选人</strong></p>
<p>嗯~在redis集群中是这样的</p>
<p>Redis 集群引入了哈希槽的概念,有 16384 个哈希槽,集群中每个主节点绑定了一定范围的哈希槽范围, key通过 CRC16 校验后对 16384 取模来决定放置哪个槽,通过槽找到对应的节点进行存储。</p>
<p>取值的逻辑是一样的</p>
<p><strong>面试官</strong>Redis是单线程的但是为什么还那么快</p>
<p><strong>候选人</strong></p>
<p>嗯,这个有几个原因吧~~~</p>
<p>1、完全基于内存的C语言编写</p>
<p>2、采用单线程避免不必要的上下文切换可竞争条件</p>
<p>3、使用多路I&#x2F;O复用模型非阻塞IO</p>
<p>例如bgsave 和 bgrewriteaof 都是在<strong>后台</strong>执行操作,不影响主线程的正常使用,不会产生阻塞</p>
<p><strong>面试官</strong>能解释一下I&#x2F;O多路复用模型</p>
<p><strong>候选人</strong>:嗯~~I&#x2F;O多路复用是指利用单个线程来同时监听多个Socket 并在某个Socket可读、可写时得到通知从而避免无效的等待充分利用CPU资源。目前的I&#x2F;O多路复用都是采用的epoll模式实现它会在通知用户进程Socket就绪的同时把已就绪的Socket写入用户空间不需要挨个遍历Socket来判断是否就绪提升了性能。</p>
<p>其中Redis的网络模型就是使用I&#x2F;O多路复用结合事件的处理器来应对多个Socket请求比如提供了连接应答处理器、命令回复处理器命令请求处理器</p>
<p>在Redis6.0之后,为了提升更好的性能,在命令回复处理器使用了多线程来处理回复事件,在命令请求处理器中,将命令的转换使用了多线程,增加命令转换速度,在命令执行的时候,依然是单线程</p>
</blockquote>
<p>pdf</p>
<div class="row">
<embed src="/pdf/Redis面试题-参考回答.pdf" width="100%" height="550" type="application/pdf">
</div>
<h2 id="2-数据库篇"><a href="#2-数据库篇" class="headerlink" title="2.数据库篇"></a>2.数据库篇</h2><blockquote>
<p><strong>面试官:</strong>MySQL中如何定位慢查询?</p>
<p><strong>候选人:</strong></p>
<p>嗯~我们当时做压测的时候有的接口非常的慢接口的响应时间超过了2秒以上因为我们当时的系统部署了运维的监控系统Skywalking 在展示的报表中可以看到是哪一个接口比较慢并且可以分析这个接口哪部分比较慢这里可以看到SQL的具体的执行时间所以可以定位是哪个sql出了问题</p>
<p>如果项目中没有这种运维的监控系统其实在MySQL中也提供了慢日志查询的功能可以在MySQL的系统配置文件中开启这个慢日志的功能并且也可以设置SQL执行超过多少时间来记录到一个日志文件中我记得上一个项目配置的是2秒只要SQL执行的时间超过了2秒就会记录到日志文件中我们就可以在日志文件找到执行比较慢的SQL了。</p>
<p><strong>面试官:</strong>那这个SQL语句执行很慢, 如何分析呢?</p>
<p><strong>候选人:</strong>如果一条sql执行很慢的话我们通常会使用mysql自动的执行计划explain来去查看这条sql的执行情况比如在这里面可以通过key和key_len检查是否命中了索引如果本身已经添加了索引也可以判断索引是否有失效的情况第二个可以通过type字段查看sql是否有进一步的优化空间是否存在全索引扫描或全盘扫描第三个可以通过extra建议来判断是否出现了回表的情况如果出现了可以尝试添加索引或修改返回字段来修复</p>
<p><strong>面试官:</strong>了解过索引吗?(什么是索引)</p>
<p><strong>候选人:</strong>索引在项目中还是比较常见的它是帮助MySQL高效获取数据的数据结构主要是用来提高数据检索的效率降低数据库的IO成本同时通过索引列对数据进行排序降低数据排序的成本也能降低了CPU的消耗</p>
<p><strong>面试官:</strong>索引的底层数据结构了解过嘛 ? </p>
<p><strong>候选人:</strong>MySQL的默认的存储引擎InnoDB采用的B+树的数据结构来存储索引选择B+树的主要的原因是第一阶数更多路径更短第二个磁盘读写代价B+树更低非叶子节点只存储指针叶子阶段存储数据第三是B+树便于扫库和区间查询,叶子节点是一个双向链表</p>
<p><strong>面试官:</strong>B树和B+树的区别是什么呢?</p>
<p><strong>候选人</strong>第一在B树中非叶子节点和叶子节点都会存放数据而B+树的所有的数据都会出现在叶子节点在查询的时候B+树查找效率更加稳定</p>
<p>第二在进行范围查询的时候B+树效率更高因为B+树都在叶子节点存储,并且叶子节点是一个双向链表</p>
<p><strong>面试官:</strong>什么是聚簇索引什么是非聚簇索引 ?</p>
<p><strong>候选人:</strong></p>
<p>好的~聚簇索引主要是指数据与索引放到一块B+树的叶子节点保存了整行数据,有且只有一个,一般情况下主键在作为聚簇索引的</p>
<p>非聚簇索引值的是数据与索引分开存储B+树的叶子节点保存对应的主键,可以有多个,一般我们自己定义的索引都是非聚簇索引</p>
<p><strong>面试官:</strong>知道什么是回表查询嘛 ?</p>
<p><strong>候选人:</strong>嗯,其实跟刚才介绍的聚簇索引和非聚簇索引是有关系的,回表的意思就是通过二级索引找到对应的主键值,然后再通过主键值找到聚集索引中所对应的整行数据,这个过程就是回表</p>
<p><strong>备注</strong>:如果面试官直接问回表,则需要先介绍聚簇索引和非聚簇索引】</p>
<p><strong>面试官:</strong>知道什么叫覆盖索引嘛 ? </p>
<p><strong>候选人:</strong>嗯~,清楚的</p>
<p>覆盖索引是指select查询语句使用了索引在返回的列必须在索引中全部能够找到如果我们使用id查询它会直接走聚集索引查询一次索引扫描直接返回数据性能高。</p>
<p>如果按照二级索引查询数据的时候返回的列中没有创建索引有可能会触发回表查询尽量避免使用select *,尽量在返回的列中都包含添加索引的字段</p>
<p><strong>面试官:</strong>MYSQL超大分页怎么处理 ?</p>
<p><strong>候选人:</strong>超大分页一般都是在数据量比较大时我们使用了limit分页查询并且需要对数据进行排序这个时候效率就很低我们可以采用覆盖索引和子查询来解决</p>
<p>先分页查询数据的id字段确定了id之后再用子查询来过滤只查询这个id列表中的数据就可以了</p>
<p>因为查询id的时候走的覆盖索引所以效率可以提升很多</p>
<p><strong>面试官:</strong>索引创建原则有哪些?</p>
<p><strong>候选人:</strong>这个情况有很多不过都有一个大前提就是表中的数据要超过10万以上我们才会创建索引并且添加索引的字段是查询比较频繁的字段一般也是像作为查询条件排序字段或分组的字段这些。</p>
<p>还有就是我们通常创建索引的时候都是使用复合索引来创建一条sql的返回值尽量使用覆盖索引如果字段的区分度不高的话我们也会把它放在组合索引后面的字段。</p>
<p>如果某一个字段的内容较长,我们会考虑使用前缀索引来使用,当然并不是所有的字段都要添加索引,这个索引的数量也要控制,因为添加索引也会导致新增改的速度变慢。</p>
<p><strong>面试官:</strong>什么情况下索引会失效 ?</p>
<p><strong>候选人:</strong>嗯,这个情况比较多,我说一些自己的经验,以前遇到过的</p>
<p>比如,索引在使用的时候没有遵循最左匹配法则,第二个是,模糊查询,如果%号在前面也会导致索引失效。如果在添加索引的字段上进行了运算操作或者类型转换也都会导致索引失效。</p>
<p>我们之前还遇到过一个就是,如果使用了复合索引,中间使用了范围查询,右边的条件索引也会失效</p>
<p>查询的时候发生了类型转换,在查询的时候做了运算的操作和模糊查询也会导致索引失效</p>
<p>所以通常情况下想要判断出这条sql是否有索引失效的情况可以使用explain执行计划来分析</p>
<p><strong>面试官:</strong>sql的优化的经验</p>
<p><strong>候选人:</strong>这个在项目还是挺常见的当然如果直说sql优化的话我们会从这几方面考虑比如</p>
<p>建表的时候、使用索引、sql语句的编写、主从复制读写分离还有一个是如果量比较大的话可以考虑分库分表</p>
<p>直接经验:</p>
<p>①SELECT语句务必指明字段名称避免直接使用select * </p>
<p>②SQL语句要避免造成索引失效的写法</p>
<p>③尽量用union all代替union union会多一次过滤效率低</p>
<p>④避免在where子句中对字段进行表达式操作</p>
<p>⑤Join优化 能用innerjoin 就不用left join right join如必须使用 一定要以小表为驱动内连接会对两个表进行优化优先把小表放到外边把大表放到里边。left join 或 right join不会重新调整顺序</p>
<p><strong>面试官:</strong>创建表的时候,你们是如何优化的呢?</p>
<p><strong>候选人:</strong>这个我们主要参考的阿里出的那个开发手册《嵩山版》就比如在定义字段的时候需要结合字段的内容来选择合适的类型如果是数值的话像tinyint、int 、bigint这些类型要根据实际情况选择。如果是字符串类型也是结合存储的内容来选择char和varchar或者text类型</p>
<p><strong>面试官:</strong>那在使用索引的时候,是如何优化呢?</p>
<p><strong>候选人:</strong>【参考索引创建原则 进行描述】</p>
<p>1). 针对于数据量较大,且查询比较频繁的表建立索引。</p>
<p>2). 针对于常作为查询条件where、排序order by、分组group by操作的字段建立索引。</p>
<p>3). 尽量选择区分度高的列作为索引,尽量建立唯一索引,区分度越高,使用索引的效率越高。</p>
<p>4). 如果是字符串类型的字段,字段的长度较长,可以针对于字段的特点,建立前缀索引。</p>
<p>5). 尽量使用联合索引,减少单列索引,查询时,联合索引很多时候可以覆盖索引,节省存储空间,避免回表,提高查询效率。</p>
<p>6). 要控制索引的数量,索引并不是多多益善,索引越多,维护索引结构的代价也就越大,会影响增删改的效率。</p>
<p>7). 如果索引列不能存储NULL值请在创建表时使用NOT NULL约束它。当优化器知道每列是否包含NULL值时它可以更好地确定哪个索引最有效地用于查询。</p>
<p><strong>面试官:</strong>你平时对sql语句做了哪些优化呢</p>
<p><strong>候选人:</strong>这个也有很多比如SELECT语句务必指明字段名称不要直接使用select * 还有就是要注意SQL语句避免造成索引失效的写法如果是聚合查询尽量用union all代替union union会多一次过滤效率比较低如果是表关联的话尽量使用innerjoin 不要使用用left join right join如必须使用 一定要以小表为驱动</p>
<p><strong>面试官:</strong>事务的特性是什么?可以详细说一下吗?</p>
<p><strong>候选人:</strong>这个比较清楚ACID分别指的是原子性、一致性、隔离性、持久性我举个例子</p>
<p>A向B转账500转账成功A扣除500元B增加500元原子操作体现在要么都成功要么都失败</p>
<p>在转账的过程中数据要一致A扣除了500B必须增加500</p>
<p>在转账的过程中隔离性体现在A像B转账不能受其他事务干扰</p>
<p>在转账的过程中,持久性体现在事务提交后,要把数据持久化(可以说是落盘操作)</p>
<p>ACID介绍:</p>
<p>原子性(<strong>A</strong>tomicity事务是不可分割的最小操作单元要么全部成功要么全部失败。</p>
<p>一致性(<strong>C</strong>onsistency事务完成时必须使所有的数据都保持一致状态。</p>
<p>隔离性(<strong>I</strong>solation数据库系统提供的隔离机制保证事务在不受外部并发操作影响的独立环境下运行。</p>
<p>持久性(<strong>D</strong>urability事务一旦提交或回滚它对数据库中的数据的改变就是永久的。</p>
<p><strong>面试官</strong>:并发事务带来哪些问题?</p>
<p><strong>候选人</strong></p>
<p>我们在项目开发中,多个事务并发进行是经常发生的,并发也是必然的,有可能导致一些问题</p>
<p>第一是脏读, 当一个事务正在访问数据并且对数据进行了修改,而这种修改还没有提交到数据库中,这时另外一个事务也访问了这个数据,因为这个数据是还没有提交的数据,那么另外一个事务读到的这个数据是“脏数据”,依据“脏数据”所做的操作可能是不正确的。</p>
<p>第二是不可重复读:比如在一个事务内多次读同一数据。在这个事务还没有结束时,另一个事务也访问该数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改导致第一个事务两次读取的数据可能不太一样。这就发生了在一个事务内两次读到的数据是不一样的情况,因此称为不可重复读。</p>
<p>第三是幻读Phantom read幻读与不可重复读类似。它发生在一个事务T1读取了几行数据接着另一个并发事务T2插入了一些数据时。在随后的查询中第一个事务T1就会发现多了一些原本不存在的记录就好像发生了幻觉一样所以称为幻读。</p>
<p><img src="/pictures/image-20230511172226172.png" alt="image-20230511172226172"></p>
<p><strong>面试官</strong>怎么解决这些问题呢MySQL的默认隔离级别是</p>
<p><strong>候选人</strong>:解决方案是对事务进行隔离</p>
<p>MySQL支持四种隔离级别分别有</p>
<p>第一个是未提交读read uncommitted它解决不了刚才提出的所有问题一般项目中也不用这个。第二个是读已提交read committed它能解决脏读的问题的但是解决不了不可重复读和幻读。第三个是可重复读repeatable read它能解决脏读和不可重复读但是解决不了幻读这个也是mysql默认的隔离级别。第四个是串行化serializable它可以解决刚才提出来的所有问题但是由于让是事务串行执行的性能比较低。所以我们一般使用的都是mysql默认的隔离级别:可重复读</p>
<p><img src="/pictures/image-20230511172259204.png" alt="image-20230511172259204"></p>
<p><strong>面试官</strong>undo log和redo log的区别</p>
<p><strong>候选人</strong>好的其中redo log日志记录的是数据页的物理变化服务宕机可用来同步数据而undo log 不同它主要记录的是逻辑日志当事务回滚时通过逆操作恢复原来的数据比如我们删除一条数据的时候就会在undo log日志文件中新增一条delete语句如果发生回滚就执行逆操作</p>
<p>redo log保证了事务的持久性undo log保证了事务的原子性和一致性</p>
<p><strong>面试官</strong>:事务中的隔离性是如何保证的呢?(你解释一下MVCC)</p>
<p><strong>候选人</strong>事务的隔离性是由锁和mvcc实现的。</p>
<p>其中mvcc的意思是多版本并发控制。指维护一个数据的多个版本使得读写操作没有冲突它的底层实现主要是分为了三个部分第一个是隐藏字段第二个是undo log日志第三个是readView读视图</p>
<p>隐藏字段是指在mysql中给每个表都设置了隐藏字段有一个是trx_id(事务id)记录每一次操作的事务id是自增的另一个字段是roll_pointer(回滚指针),指向上一个版本的事务版本记录地址</p>
<p>undo log主要的作用是记录回滚日志存储老版本数据在内部会形成一个版本链在多个事务并行操作某一行记录记录不同事务修改数据的版本通过roll_pointer指针形成一个链表</p>
<p>readView解决的是一个事务查询选择版本的问题在内部定义了一些匹配规则和当前的一些事务id判断该访问那个版本的数据不同的隔离级别快照读是不一样的最终的访问的结果不一样。如果是rc隔离级别每一次执行快照读时生成ReadView如果是rr隔离级别仅在事务中第一次执行快照读时生成ReadView后续复用</p>
<p><strong>面试官</strong>MySQL主从同步原理 </p>
<p><strong>候选人</strong>MySQL主从复制的核心就是二进制日志(DDL数据定义语言语句和 DML数据操纵语言语句),它的步骤是这样的:</p>
<p>第一:主库在事务提交时,会把数据变更记录在二进制日志文件 Binlog 中。</p>
<p>第二:从库读取主库的二进制日志文件 Binlog ,写入到从库的中继日志 Relay Log 。</p>
<p>第三:从库重做中继日志中的事件,将改变反映它自己的数据</p>
<p><strong>面试官</strong>你们项目用过MySQL的分库分表吗</p>
<p><strong>候选人</strong></p>
<p>嗯,因为我们都是微服务开发,每个微服务对应了一个数据库,是根据业务进行拆分的,这个其实就是垂直拆分。</p>
<p><strong>面试官</strong>:那你之前使用过水平分库吗?</p>
<p><strong>候选人</strong></p>
<p>嗯,这个是使用过的,我们当时的业务是(xxx),一开始,我们也是单库,后来这个业务逐渐发展,业务量上来的很迅速,其中(xx)表已经存放了超过1000万的数据我们做了很多优化也不好使性能依然很慢所以当时就使用了水平分库。</p>
<p>我们一开始先做了3台服务器对应了3个数据库由于库多了需要分片我们当时采用的mycat来作为数据库的中间件。数据都是按照id自增取模的方式来存取的。</p>
<p>当然一开始的时候那些旧数据我们做了一些清洗的工作我们也是按照id取模规则分别存储到了各个数据库中好处就是可以让各个数据库分摊存储和读取的压力解决了我们当时性能的问题</p>
</blockquote>
<p>pdf</p>
<div class="row">
<embed src="/pdf/MySQL面试题-参考回答.pdf" width="100%" height="550" type="application/pdf">
</div>
<h2 id="3-框架篇"><a href="#3-框架篇" class="headerlink" title="3.框架篇"></a>3.框架篇</h2><blockquote>
<p><strong>面试官</strong>Spring框架中的单例bean是线程安全的吗</p>
<p><strong>候选人</strong></p>
<p>嗯!</p>
<p>不是线程安全的,是这样的</p>
<p>当多用户同时请求一个服务时,容器会给每一个请求分配一个线程,这是多个线程会并发执行该请求对应的业务逻辑(成员方法),如果该处理逻辑中有对该单列状态的修改(体现为该单例的成员属性),则必须考虑线程同步问题。</p>
<p>Spring框架并没有对单例bean进行任何多线程的封装处理。关于单例bean的线程安全和并发问题需要开发者自行去搞定。</p>
<p>比如我们通常在项目中使用的Spring bean都是不可可变的状态(比如Service类和DAO类)所以在某种程度上说Spring的单例bean是线程安全的。</p>
<p>如果你的bean有多种状态的话比如 View Model对象就需要自行保证线程安全。最浅显的解决办法就是将多态bean的作用由“<strong>singleton</strong>”变更为“<strong>prototype</strong>”。</p>
<p><strong>面试官</strong>什么是AOP</p>
<p><strong>候选人</strong></p>
<p>aop是面向切面编程在spring中用于将那些与业务无关但却对多个对象产生影响的公共行为和逻辑抽取公共模块复用降低耦合一般比如可以做为公共日志保存事务处理等</p>
<p><strong>面试官</strong>你们项目中有没有使用到AOP</p>
<p><strong>候选人</strong></p>
<p>我们当时在后台管理系统中就是使用aop来记录了系统的操作日志</p>
<p>主要思路是这样的使用aop中的环绕通知+切点表达式,这个表达式就是要找到要记录日志的方法,然后通过环绕通知的参数获取请求方法的参数,比如类信息、方法信息、注解、请求方式等,获取到这些参数以后,保存到数据库</p>
<p><strong>面试官</strong>Spring中的事务是如何实现的</p>
<p><strong>候选人</strong></p>
<p>spring实现的事务本质就是aop完成对方法前后进行拦截在执行方法之前开启事务在执行完目标方法之后根据执行情况提交或者回滚事务。</p>
<p><strong>面试官</strong>Spring中事务失效的场景有哪些</p>
<p><strong>候选人</strong></p>
<p>嗯!这个在项目中之前遇到过,我想想啊</p>
<p>第一个,如果方法上异常捕获处理,自己处理了异常,没有抛出,就会导致事务失效,所以一般处理了异常以后,别忘了跑出去就行了</p>
<p>第二个如果方法抛出检查异常如果报错也会导致事务失效最后在spring事务的注解上就是@Transactional上配置rollbackFor属性为Exception这样别管是什么异常都会回滚事务</p>
<p>第三我之前还遇到过一个如果方法上不是public修饰的也会导致事务失效</p>
<p>嗯,就能想起来那么多</p>
<p><strong>面试官</strong>Spring的bean的生命周期</p>
<p><strong>候选人</strong></p>
<p>嗯!,这个步骤还是挺多的,我之前看过一些源码,它大概流程是这样的</p>
<p>首先会通过一个非常重要的类叫做BeanDefinition获取bean的定义信息这里面就封装了bean的所有信息比如类的全路径是否是延迟加载是否是单例等等这些信息</p>
<p>在创建bean的时候第一步是调用构造函数实例化bean</p>
<p>第二步是bean的依赖注入比如一些set方法注入像平时开发用的@Autowire都是这一步完成</p>
<p>第三步是处理Aware接口如果某一个bean实现了Aware接口就会重写方法执行</p>
<p>第四步是bean的后置处理器BeanPostProcessor这个是前置处理器</p>
<p>第五步是初始化方法比如实现了接口InitializingBean或者自定义了方法init-method标签或@PostContruct</p>
<p>第六步是执行了bean的后置处理器BeanPostProcessor主要是对bean进行增强有可能在这里产生代理对象</p>
<p>最后一步是销毁bean</p>
<p><strong>面试官</strong>Spring中的循环引用</p>
<p><strong>候选人</strong></p>
<p>嗯,好的,我来解释一下</p>
<p>循环依赖:循环依赖其实就是循环引用,也就是两个或两个以上的bean互相持有对方,最终形成闭环。比如A依赖于B,B依赖于A</p>
<p>循环依赖在spring中是允许存在spring框架依据三级缓存已经解决了大部分的循环依赖</p>
<p>①一级缓存单例池缓存已经经历了完整的生命周期已经初始化完成的bean对象</p>
<p>②二级缓存缓存早期的bean对象生命周期还没走完</p>
<p>③三级缓存缓存的是ObjectFactory表示对象工厂用来创建某个对象的</p>
<p><strong>面试官</strong>:那具体解决流程清楚吗?</p>
<p><strong>候选人</strong></p>
<p>第一先实例A对象同时会创建ObjectFactory对象存入三级缓存singletonFactories</p>
<p>第二A在初始化的时候需要B对象这个走B的创建的逻辑</p>
<p>第三B实例化完成也会创建ObjectFactory对象存入三级缓存singletonFactories</p>
<p>第四B需要注入A通过三级缓存中获取ObjectFactory来生成一个A的对象同时存入二级缓存这个是有两种情况一个是可能是A的普通对象另外一个是A的代理对象都可以让ObjectFactory来生产对应的对象这也是三级缓存的关键</p>
<p>第五B通过从通过二级缓存earlySingletonObjects 获得到A的对象后可以正常注入B创建成功存入一级缓存singletonObjects</p>
<p>第六回到A对象初始化因为B对象已经创建完成则可以直接注入BA创建成功存入一次缓存singletonObjects</p>
<p>第七二级缓存中的临时对象A清除</p>
<p><strong>面试官</strong>:构造方法出现了循环依赖怎么解决?</p>
<p><strong>候选人</strong></p>
<p>由于bean的生命周期中构造函数是第一个执行的spring框架并不能解决构造函数的的依赖注入可以使用@Lazy懒加载什么时候需要对象再进行bean对象的创建</p>
<p><strong>面试官</strong>SpringMVC的执行流程知道嘛</p>
<p><strong>候选人</strong></p>
<p>嗯,这个知道的,它分了好多步骤</p>
<p>1、用户发送出请求到前端控制器DispatcherServlet这是一个调度中心</p>
<p>2、DispatcherServlet收到请求调用HandlerMapping处理器映射器</p>
<p>3、HandlerMapping找到具体的处理器(可查找xml配置或注解配置),生成处理器对象及处理器拦截器(如果有)再一起返回给DispatcherServlet。</p>
<p>4、DispatcherServlet调用HandlerAdapter处理器适配器</p>
<p>5、HandlerAdapter经过适配调用具体的处理器Handler&#x2F;Controller</p>
<p>6、Controller执行完成返回ModelAndView对象。</p>
<p>7、HandlerAdapter将Controller执行结果ModelAndView返回给DispatcherServlet。</p>
<p>8、DispatcherServlet将ModelAndView传给ViewReslover视图解析器</p>
<p>9、ViewReslover解析后返回具体View视图</p>
<p>10、DispatcherServlet根据View进行渲染视图即将模型数据填充至视图中</p>
<p>11、DispatcherServlet响应用户。</p>
<p>当然现在的开发基本都是前后端分离的开发的并没有视图这些一般都是handler中使用Response直接结果返回</p>
<p><strong>面试官</strong>Springboot自动配置原理</p>
<p><strong>候选人</strong></p>
<p>嗯,好的,它是这样的。</p>
<p>在Spring Boot项目中的引导类上有一个注解@SpringBootApplication这个注解是对三个注解进行了封装分别是</p>
<ul>
<li>@SpringBootConfiguration</li>
<li>@EnableAutoConfiguration</li>
<li>@ComponentScan</li>
</ul>
<p>其中<code>@EnableAutoConfiguration</code>是实现自动化配置的核心注解。</p>
<p>该注解通过<code>@Import</code>注解导入对应的配置选择器。关键的是内部就是读取了该项目和该项目引用的Jar包的的classpath路径下<strong>META-INF&#x2F;spring.factories</strong>文件中的所配置的类的全类名。</p>
<p>在这些配置类中所定义的Bean会根据条件注解所<strong>指定的条件来决定</strong>是否需要将其导入到Spring容器中。</p>
<p>一般条件判断会有像<code>@ConditionalOnClass</code>这样的注解判断是否有对应的class文件如果有则加载该类把这个配置类的所有的Bean放入spring容器中使用。</p>
<p><strong>面试官</strong>Spring 的常见注解有哪些?</p>
<p><strong>候选人</strong></p>
<p>嗯,这个就很多了</p>
<p>第一类是声明bean有@Component、@Service、@Repository、@Controller</p>
<p>第二类是:依赖注入相关的,有@Autowired、@Qualifier、@Resourse</p>
<p>第三类是:设置作用域 @Scope</p>
<p>第四类是spring配置相关的比如@Configuration@ComponentScan 和 @Bean</p>
<p>第五类是跟aop相关做增强的注解 @Aspect@Before@After@Around@Pointcut</p>
<p><strong>面试官</strong>SpringMVC常见的注解有哪些</p>
<p><strong>候选人</strong></p>
<p>嗯,这个也很多的</p>
<p>有@RequestMapping用于映射请求路径</p>
<p>@RequestBody注解实现接收http请求的json数据将json转换为java对象</p>
<p>@RequestParam指定请求参数的名称</p>
<p>@PathViriable从请求路径下中获取请求参数(&#x2F;user&#x2F;{id}),传递给方法的形式参数;@ResponseBody注解实现将controller方法返回对象转化为json对象响应给客户端。@RequestHeader获取指定的请求头数据还有像@PostMapping、@GetMapping这些。</p>
<p><strong>面试官</strong>Springboot常见注解有哪些</p>
<p><strong>候选人</strong></p>
<p>嗯~~</p>
<p>Spring Boot的核心注解是@SpringBootApplication , 他由几个注解组成 :</p>
<ul>
<li>@SpringBootConfiguration 组合了- @Configuration注解实现配置文件的功能</li>
<li>@EnableAutoConfiguration打开自动配置的功能也可以关闭某个自动配置的选项</li>
<li>@ComponentScanSpring组件扫描</li>
</ul>
<p><strong>面试官</strong>MyBatis执行流程</p>
<p><strong>候选人</strong></p>
<p>好,这个知道的,不过步骤也很多</p>
<p>①读取MyBatis配置文件mybatis-config.xml加载运行环境和映射文件</p>
<p>②构造会话工厂SqlSessionFactory一个项目只需要一个单例的一般由spring进行管理</p>
<p>③会话工厂创建SqlSession对象这里面就含了执行SQL语句的所有方法</p>
<p>④操作数据库的接口Executor执行器同时负责查询缓存的维护</p>
<p>⑤Executor接口的执行方法中有一个MappedStatement类型的参数封装了映射信息</p>
<p>⑥输入参数映射</p>
<p>⑦输出结果映射</p>
<p><strong>面试官</strong>Mybatis是否支持延迟加载</p>
<p><strong>候选人</strong></p>
<p>是支持的~</p>
<p>延迟加载的意思是:就是在需要用到数据时才进行加载,不需要用到数据时就不加载数据。</p>
<p>Mybatis支持一对一关联对象和一对多关联集合对象的延迟加载</p>
<p>在Mybatis配置文件中可以配置是否启用延迟加载lazyLoadingEnabled&#x3D;true|false默认是关闭的</p>
<p><strong>面试官</strong>:延迟加载的底层原理知道吗?</p>
<p><strong>候选人</strong></p>
<p>嗯,我想想啊</p>
<p>延迟加载在底层主要使用的CGLIB动态代理完成的</p>
<p>第一是使用CGLIB创建目标对象的代理对象这里的目标对象就是开启了延迟加载的mapper</p>
<p>第二个是当调用目标方法时进入拦截器invoke方法发现目标方法是null值再执行sql查询</p>
<p>第三个是获取数据以后调用set方法设置属性值再继续查询目标方法就有值了</p>
<p><strong>面试官</strong>Mybatis的一级、二级缓存用过吗</p>
<p><strong>候选人</strong></p>
<p>嗯~~,用过的~</p>
<p>mybatis的一级缓存: 基于 PerpetualCache 的 HashMap 本地缓存,其存储作用域为 Session当Session进行flush或close之后该Session中的所有Cache就将清空默认打开一级缓存</p>
<p>关于二级缓存需要单独开启</p>
<p>二级缓存是基于namespace和mapper的作用域起作用的不是依赖于SQL session默认也是采用 PerpetualCacheHashMap 存储。</p>
<p>如果想要开启二级缓存需要在全局配置文件和映射文件中开启配置才行。</p>
<p><strong>面试官</strong>Mybatis的二级缓存什么时候会清理缓存中的数据</p>
<p><strong>候选人</strong></p>
<p>嗯!!</p>
<p>当某一个作用域(一级缓存 Session&#x2F;二级缓存Namespaces)的进行了新增、修改、删除操作后,默认该作用域下所有 select 中的缓存将被 clear。</p>
</blockquote>
<p>pdf</p>
<div class="row">
<embed src="/pdf/框架篇面试题-参考回答.pdf" width="100%" height="550" type="application/pdf">
</div>
<h2 id="4-微服务篇"><a href="#4-微服务篇" class="headerlink" title="4.微服务篇"></a>4.微服务篇</h2><blockquote>
<p><strong>面试官:</strong>Spring Cloud 5大组件有哪些</p>
<p><strong>候选人:</strong></p>
<p>早期我们一般认为的Spring Cloud五大组件是</p>
<ul>
<li>Eureka : 注册中心</li>
<li>Ribbon : 负载均衡</li>
<li>Feign : 远程调用</li>
<li>Hystrix : 服务熔断</li>
<li>Zuul&#x2F;Gateway : 网关</li>
</ul>
<p>随着SpringCloudAlibba在国内兴起 , 我们项目中使用了一些阿里巴巴的组件</p>
<ul>
<li>注册中心&#x2F;配置中心 Nacos</li>
<li>负载均衡 Ribbon</li>
<li>服务调用 Feign</li>
<li>服务保护 sentinel</li>
<li>服务网关 Gateway</li>
</ul>
<p><strong>面试官:</strong>服务注册和发现是什么意思Spring Cloud 如何实现服务注册发现?</p>
<p><strong>候选人:</strong></p>
<p>我理解的是主要三块大功能,分别是服务注册 、服务发现、服务状态监控</p>
<p>我们当时项目采用的eureka作为注册中心这个也是spring cloud体系中的一个核心组件</p>
<p><strong>服务注册</strong>服务提供者需要把自己的信息注册到eureka由eureka来保存这些信息比如服务名称、ip、端口等等</p>
<p><strong>服务发现</strong>消费者向eureka拉取服务列表信息如果服务提供者有集群则消费者会利用负载均衡算法选择一个发起调用</p>
<p><strong>服务监控</strong>服务提供者会每隔30秒向eureka发送心跳报告健康状态如果eureka服务90秒没接收到心跳从eureka中剔除</p>
<p><strong>面试官:</strong>我看你之前也用过nacos、你能说下nacos与eureka的区别</p>
<p><strong>候选人:</strong></p>
<p>我们当时xx项目就是采用的nacos作为注册中心选择nacos还要一个重要原因就是它支持配置中心不过nacos作为注册中心也比eureka要方便好用一些主要相同不同点在于几点</p>
<ul>
<li>共同点</li>
</ul>
<p>Nacos与eureka都支持服务注册和服务拉取都支持服务提供者心跳方式做健康检测</p>
<ul>
<li>Nacos与Eureka的区别</li>
</ul>
<p>①Nacos支持服务端主动检测提供者状态临时实例采用心跳模式非临时实例采用主动检测模式</p>
<p>②临时实例心跳不正常会被剔除,非临时实例则不会被剔除</p>
<p>③Nacos支持服务列表变更的消息推送模式服务列表更新更及时</p>
<p>④Nacos集群默认采用AP方式当集群中存在非临时实例时采用CP模式Eureka采用AP方式</p>
<p><strong>面试官:</strong>你们项目负载均衡如何实现的 ?</p>
<p><strong>候选人:</strong></p>
<p>是这样~~</p>
<p>在服务调用过程中的负载均衡一般使用SpringCloud的Ribbon 组件实现 , Feign的底层已经自动集成了Ribbon , 使用起来非常简单</p>
<p>当发起远程调用时ribbon先从注册中心拉取服务地址列表然后按照一定的路由策略选择一个发起远程调用一般的调用策略是轮询</p>
<p><strong>面试官:</strong>Ribbon负载均衡策略有哪些 ?</p>
<p><strong>候选人:</strong></p>
<p>我想想啊,有很多种,我记得几个:</p>
<ul>
<li>RoundRobinRule简单轮询服务列表来选择服务器</li>
<li>WeightedResponseTimeRule按照权重来选择服务器响应时间越长权重越小</li>
<li>RandomRule随机选择一个可用的服务器</li>
<li>ZoneAvoidanceRule区域敏感策略以区域可用的服务器为基础进行服务器的选择。使用Zone对服务器进行分类这个Zone可以理解为一个机房、一个机架等。而后再对Zone内的多个服务做轮询(默认)</li>
</ul>
<p><strong>面试官:</strong>如果想自定义负载均衡策略如何实现 ?</p>
<p><strong>候选人:</strong></p>
<p>提供了两种方式:</p>
<p>1创建类实现IRule接口可以指定负载均衡策略这个是全局的对所有的远程调用都起作用</p>
<p>2在客户端的配置文件中可以配置某一个服务调用的负载均衡策略只是对配置的这个服务生效远程调用</p>
<p><strong>面试官:</strong>什么是服务雪崩,怎么解决这个问题?</p>
<p><strong>候选人:</strong></p>
<p>服务雪崩是指一个服务失败,导致整条链路的服务都失败的情形,一般我们在项目解决的话就是两种方案,第一个是服务降级,第二个是服务熔断,如果流量太大的话,可以考虑限流</p>
<p>服务降级服务自我保护的一种方式或者保护下游服务的一种方式用于确保服务不会受请求突增影响变得不可用确保服务不会崩溃一般在实际开发中与feign接口整合编写降级逻辑</p>
<p>服务熔断:默认关闭,需要手动打开,如果检测到 10 秒内请求的失败率超过 50%,就触发熔断机制。之后每隔 5 秒重新尝试请求微服务,如果微服务不能响应,继续走熔断机制。如果微服务可达,则关闭熔断机制,恢复正常请求</p>
<p><strong>面试官:</strong>你们的微服务是怎么监控的?</p>
<p><strong>候选人:</strong></p>
<p>我们项目中采用的skywalking进行监控的</p>
<p>1skywalking主要可以监控接口、服务、物理实例的一些状态。特别是在压测的时候可以看到众多服务中哪些服务和接口比较慢我们可以针对性的分析和优化。</p>
<p>2我们还在skywalking设置了告警规则特别是在项目上线以后如果报错我们分别设置了可以给相关负责人发短信和发邮件第一时间知道项目的bug情况第一时间修复</p>
<p><strong>面试官:</strong>你们项目中有没有做过限流 ? 怎么做的 ?</p>
<p><strong>候选人:</strong></p>
<p>我当时做的xx项目采用就是微服务的架构因为xx因为应该会有突发流量最大QPS可以达到2000但是服务支撑不住我们项目都通过压测最多可以支撑1200QPS。因为我们平时的QPS也就不到100为了解决这些突发流量所以采用了限流。</p>
<p>【版本1】</p>
<p>我们当时采用的nginx限流操作nginx使用的漏桶算法来实现过滤让请求以固定的速率处理请求可以应对突发流量我们控制的速率是按照ip进行限流限制的流量是每秒20</p>
<p>【版本2】</p>
<p>我们当时采用的是spring cloud gateway中支持局部过滤器RequestRateLimiter来做限流使用的是令牌桶算法可以根据ip或路径进行限流可以设置每秒填充平均速率和令牌桶总容量</p>
<p><strong>面试官:</strong>限流常见的算法有哪些呢?</p>
<p><strong>候选人:</strong></p>
<p>比较常见的限流算法有漏桶算法和令牌桶算法</p>
<p>漏桶算法是把请求存入到桶中,以固定速率从桶中流出,可以让我们的服务做到绝对的平均,起到很好的限流效果</p>
<p>令牌桶算法在桶中存储的是令牌,按照一定的速率生成令牌,每个请求都要先申请令牌,申请到令牌以后才能正常请求,也可以起到很好的限流作用</p>
<p>它们的区别是漏桶和令牌桶都可以处理突发流量其中漏桶可以做到绝对的平滑令牌桶有可能会产生突发大量请求的情况一般nginx限流采用的漏桶spring cloud gateway中可以支持令牌桶算法</p>
<p><strong>面试官</strong>什么是CAP理论</p>
<p><strong>候选人</strong></p>
<p>CAP主要是在分布式项目下的一个理论。包含了三项一致性、可用性、分区容错性</p>
<ul>
<li>一致性(Consistency)是指更新操作成功并返回客户端完成后,所有节点在同一时间的数据完全一致(强一致性),不能存在中间状态。</li>
<li>可用性(Availability) 是指系统提供的服务必须一直处于可用的状态,对于用户的每一个操作请求总是能够在有限的时间内返回结果。</li>
<li>分区容错性(Partition tolerance) 是指分布式系统在遇到任何网络分区故障时,仍然需要能够保证对外提供满足一致性和可用性的服务,除非是整个网络环境都发生了故障。</li>
</ul>
<p><strong>面试官</strong>:为什么分布式系统中无法同时保证一致性和可用性?</p>
<p><strong>候选人</strong></p>
<p>嗯,是这样的~~</p>
<p>首先一个前提对于分布式系统而言分区容错性是一个最基本的要求因此基本上我们在设计分布式系统的时候只能从一致性C和可用性A之间进行取舍。</p>
<p>如果保证了一致性C对于节点N1和N2当往N1里写数据时N2上的操作必须被暂停只有当N1同步数据到N2时才能对N2进行读写请求在N2被暂停操作期间客户端提交的请求会收到失败或超时。显然这与可用性是相悖的。</p>
<p>如果保证了可用性A那就不能暂停N2的读写操作但同时N1在写数据的话这就违背了一致性的要求。</p>
<p><strong>面试官</strong>什么是BASE理论</p>
<p><strong>候选人</strong></p>
<p>这个也是CAP分布式系统设计理论</p>
<p>BASE是CAP理论中AP方案的延伸核心思想是即使无法做到强一致性StrongConsistencyCAP的一致性就是强一致性但应用可以采用适合的方式达到最终一致性Eventual Consitency。它的思想包含三方面</p>
<p>1、Basically Available基本可用基本可用是指分布式系统在出现不可预知的故障的时候允许损失部分可用性但不等于系统不可用。</p>
<p>2、Soft state软状态即是指允许系统中的数据存在中间状态并认为该中间状态的存在不会影响系统的整体可用性即允许系统在不同节点的数据副本之间进行数据同步的过程存在延时。</p>
<p>3、Eventually consistent最终一致性强调系统中所有的数据副本在经过一段时间的同步后最终能够达到一个一致的状态。其本质是需要系统保证最终数据能够达到一致而不需要实时保证系统数据的强一致性。</p>
<p><strong>面试官:</strong>你们采用哪种分布式事务解决方案?</p>
<p><strong>候选人:</strong></p>
<p>我们当时是xx项目主要使用到的seata的at模式解决的分布式事务</p>
<p>seata的AT模型分为两个阶段</p>
<p>1、阶段一RM的工作① 注册分支事务 ② 记录undo-log数据快照③ 执行业务sql并提交 ④报告事务状态</p>
<p>2、阶段二提交时RM的工作删除undo-log即可</p>
<p>3、阶段二回滚时RM的工作根据undo-log恢复数据到更新前</p>
<p>at模式牺牲了一致性保证了可用性不过它保证的是最终一致性</p>
<p><strong>面试官:</strong>分布式服务的接口幂等性如何设计?</p>
<p><strong>候选人:</strong></p>
<p>我们当时有一个xx项目的下单操作采用的token+redis实现的流程是这样的</p>
<p>第一次请求也就是用户打开了商品详情页面我们会发起一个请求在后台生成一个唯一token存入rediskey就是用户的idvalue就是这个token同时把这个token返回前端</p>
<p>第二次请求当用户点击了下单操作会后会携带之前的token后台先到redis进行验证如果存在token可以执行业务同时删除token如果不存在则直接返回不处理业务就保证了同一个token只处理一次业务就保证了幂等性</p>
<p><strong>面试官:</strong>xxl-job路由策略有哪些</p>
<p><strong>候选人:</strong></p>
<p>xxl-job提供了很多的路由策略我们平时用的较多就是轮询、故障转移、分片广播…</p>
<p><strong>面试官:</strong>xxl-job任务执行失败怎么解决</p>
<p><strong>候选人:</strong></p>
<p>有这么几个操作</p>
<p>第一:路由策略选择故障转移,优先使用健康的实例来执行任务</p>
<p>第二,如果还有失败的,我们在创建任务时,可以设置重试次数</p>
<p>第三,如果还有失败的,就可以查看日志或者配置邮件告警来通知相关负责人解决</p>
<p><strong>面试官:</strong>如果有大数据量的任务同时都需要执行,怎么解决?</p>
<p><strong>候选人:</strong></p>
<p>我们会让部署多个实例,共同去执行这些批量的任务,其中任务的路由策略是分片广播</p>
<p>在任务执行的代码中可以获取分片总数和当前分片,按照取模的方式分摊到各个实例执行就可以了</p>
</blockquote>
<p>pdf</p>
<div class="row">
<embed src="/pdf/微服务面试题-参考回答.pdf" width="100%" height="550" type="application/pdf">
</div>
<h2 id="5-消息中间件篇"><a href="#5-消息中间件篇" class="headerlink" title="5.消息中间件篇"></a>5.消息中间件篇</h2><blockquote>
<p><strong>面试官</strong>RabbitMQ-如何保证消息不丢失</p>
<p><strong>候选人</strong></p>
<p>我们当时MYSQL和Redis的数据双写一致性就是采用RabbitMQ实现同步的这里面就要求了消息的高可用性我们要保证消息的不丢失。主要从三个层面考虑</p>
<p>第一个是开启生产者确认机制,确保生产者的消息能到达队列,如果报错可以先记录到日志中,再去修复数据</p>
<p>第二个是开启持久化功能,确保消息未消费前在队列中不会丢失,其中的交换机、队列、和消息都要做持久化</p>
<p>第三个是开启消费者确认机制为auto由spring确认消息处理成功后完成ack当然也需要设置一定的重试次数我们当时设置了3次如果重试3次还没有收到消息就将失败后的消息投递到异常交换机交由人工处理</p>
<p><strong>面试官</strong>RabbitMQ消息的重复消费问题如何解决的</p>
<p><strong>候选人</strong></p>
<p>这个我们还真遇到过是这样的我们当时消费者是设置了自动确认机制当服务还没来得及给MQ确认的时候服务宕机了导致服务重启之后又消费了一次消息。这样就重复消费了</p>
<p>因为我们当时处理的支付(订单|业务唯一标识),它有一个业务的唯一标识,我们再处理消息时,先到数据库查询一下,这个数据是否存在,如果不存在,说明没有处理过,这个时候就可以正常处理这个消息了。如果已经存在这个数据了,就说明消息重复消费了,我们就不需要再消费了</p>
<p><strong>面试官</strong>:那你还知道其他的解决方案吗?</p>
<p><strong>候选人</strong></p>
<p>嗯,我想想~</p>
<p>其实这个就是典型的幂等的问题比如redis分布式锁、数据库的锁都是可以的</p>
<p><strong>面试官</strong>RabbitMQ中死信交换机 ? RabbitMQ延迟队列有了解过嘛</p>
<p><strong>候选人</strong></p>
<p>嗯!了解过!</p>
<p>我们当时的xx项目有一个xx业务需要用到延迟队列其中就是使用RabbitMQ来实现的。</p>
<p>延迟队列就是用到了死信交换机和TTL消息存活时间实现的。</p>
<p>如果消息超时未消费就会变成死信在RabbitMQ中如果消息成为死信队列可以绑定一个死信交换机在死信交换机上可以绑定其他队列在我们发消息的时候可以按照需求指定TTL的时间这样就实现了延迟队列的功能了。</p>
<p>我记得RabbitMQ还有一种方式可以实现延迟队列在RabbitMQ中安装一个死信插件这样更方便一些我们只需要在声明交互机的时候指定这个就是死信交换机然后在发送消息的时候直接指定超时时间就行了相对于死信交换机+TTL要省略了一些步骤</p>
<p><strong>面试官</strong>如果有100万消息堆积在MQ , 如何解决 ?</p>
<p><strong>候选人</strong></p>
<p>我在实际的开发中,没遇到过这种情况,不过,如果发生了堆积的问题,解决方案也所有很多的</p>
<p>第一:提高消费者的消费能力 ,可以使用多线程消费任务</p>
<p>第二:增加更多消费者,提高消费速度</p>
<p> 使用工作队列模式, 设置多个消费者消费消费同一个队列中的消息</p>
<p>第三:扩大队列容积,提高堆积上限</p>
<p>可以使用RabbitMQ惰性队列惰性队列的好处主要是</p>
<p>①接收到消息后直接存入磁盘而非内存</p>
<p>②消费者要消费消息时才会从磁盘中读取并加载到内存</p>
<p>③支持数百万条的消息存储</p>
<p><strong>面试官</strong>RabbitMQ的高可用机制有了解过嘛</p>
<p><strong>候选人</strong></p>
<p>嗯,熟悉的~</p>
<p>我们当时项目在生产环境下使用的集群当时搭建是镜像模式集群使用了3台机器。</p>
<p>镜像队列结构是一主多从,所有操作都是主节点完成,然后同步给镜像节点,如果主节点宕机后,镜像节点会替代成新的主节点,不过在主从同步完成前,主节点就已经宕机,可能出现数据丢失</p>
<p><strong>面试官</strong>:那出现丢数据怎么解决呢?</p>
<p><strong>候选人</strong></p>
<p>我们可以采用仲裁队列与镜像队列一样都是主从模式支持主从数据同步主从同步基于Raft协议强一致。</p>
<p>并且使用起来也非常简单,不需要额外的配置,在声明队列的时候只要指定这个是仲裁队列即可</p>
<p><strong>面试官</strong>Kafka是如何保证消息不丢失</p>
<p><strong>候选人</strong></p>
<p>嗯,这个保证机制很多,在发送消息到消费者接收消息,在每个阶段都有可能会丢失消息,所以我们解决的话也是从多个方面考虑</p>
<p>第一个是生产者发送消息的时候,可以使用异步回调发送,如果消息发送失败,我们可以通过回调获取失败后的消息信息,可以考虑重试或记录日志,后边再做补偿都是可以的。同时在生产者这边还可以设置消息重试,有的时候是由于网络抖动的原因导致发送不成功,就可以使用重试机制来解决</p>
<p>第二个在broker中消息有可能会丢失我们可以通过kafka的复制机制来确保消息不丢失在生产者发送消息的时候可以设置一个acks就是确认机制。我们可以设置参数为all这样的话当生产者发送消息到了分区之后不仅仅只在leader分区保存确认在follwer分区也会保存确认只有当所有的副本都保存确认以后才算是成功发送了消息所以这样设置就很大程度了保证了消息不会在broker丢失</p>
<p>第三个有可能是在消费者端丢失消息kafka消费消息都是按照offset进行标记消费的消费者默认是自动按期提交已经消费的偏移量默认是每隔5s提交一次如果出现重平衡的情况可能会重复消费或丢失数据。我们一般都会禁用掉自动提价偏移量改为手动提交当消费成功以后再报告给broker消费的位置这样就可以避免消息丢失和重复消费了</p>
<p><strong>面试官</strong>Kafka中消息的重复消费问题如何解决的</p>
<p><strong>候选人</strong></p>
<p>kafka消费消息都是按照offset进行标记消费的消费者默认是自动按期提交已经消费的偏移量默认是每隔5s提交一次如果出现重平衡的情况可能会重复消费或丢失数据。我们一般都会禁用掉自动提价偏移量改为手动提交当消费成功以后再报告给broker消费的位置这样就可以避免消息丢失和重复消费了</p>
<p>为了消息的幂等我们也可以设置唯一主键来进行区分或者是加锁数据库的锁或者是redis分布式锁都能解决幂等的问题</p>
<p><strong>面试官</strong>Kafka是如何保证消费的顺序性</p>
<p><strong>候选人</strong></p>
<p>kafka默认存储和消费消息是不能保证顺序性的因为一个topic数据可能存储在不同的分区中每个分区都有一个按照顺序的存储的偏移量如果消费者关联了多个分区不能保证顺序性</p>
<p>如果有这样的需求的话我们是可以解决的把消息都存储同一个分区下就行了有两种方式都可以进行设置第一个是发送消息时指定分区号第二个是发送消息时按照相同的业务设置相同的key因为默认情况下分区也是通过key的hashcode值来选择分区的hash值如果一样的话分区肯定也是一样的</p>
<p><strong>面试官</strong>Kafka的高可用机制有了解过嘛</p>
<p><strong>候选人</strong></p>
<p>嗯,主要是有两个层面,第一个是集群,第二个是提供了复制机制</p>
<p>kafka集群指的是由多个broker实例组成即使某一台宕机也不耽误其他broker继续对外提供服务</p>
<p>复制机制是可以保证kafka的高可用的一个topic有多个分区每个分区有多个副本有一个leader其余的是follower副本存储在不同的broker中所有的分区副本的内容是都是相同的如果leader发生故障时会自动将其中一个follower提升为leader保证了系统的容错性、高可用性</p>
<p><strong>面试官</strong>解释一下复制机制中的ISR</p>
<p><strong>候选人</strong></p>
<p>ISR的意思是in-sync replica就是需要同步复制保存的follower</p>
<p>其中分区副本有很多的follower分为了两类一个是ISR与leader副本同步保存数据另外一个普通的副本是异步同步数据当leader挂掉之后会优先从ISR副本列表中选取一个作为leader因为ISR是同步保存数据数据更加的完整一些所以优先选择ISR副本列表</p>
<p><strong>面试官</strong>Kafka数据清理机制了解过嘛</p>
<p><strong>候选人</strong></p>
<p>嗯,了解过~~</p>
<p>Kafka中topic的数据存储在分区上分区如果文件过大会分段存储segment</p>
<p>每个分段都在磁盘上以索引(xxxx.index)和日志文件(xxxx.log)的形式存储这样分段的好处是第一能够减少单个文件内容的大小查找数据方便第二方便kafka进行日志清理。</p>
<p>在kafka中提供了两个日志的清理策略</p>
<p>第一根据消息的保留时间当消息保存的时间超过了指定的时间就会触发清理默认是168小时 7天</p>
<p>第二是根据topic存储的数据大小当topic所占的日志文件大小大于一定的阈值则开始删除最久的消息。这个默认是关闭的</p>
<p>这两个策略都可以通过kafka的broker中的配置文件进行设置</p>
<p><strong>面试官</strong>Kafka中实现高性能的设计有了解过嘛</p>
<p><strong>候选人</strong></p>
<p>Kafka 高性能是多方面协同的结果包括宏观架构、分布式存储、ISR 数据同步、以及高效的利用磁盘、操作系统特性等。主要体现有这么几点:</p>
<p>消息分区:不受单台服务器的限制,可以不受限的处理更多的数据</p>
<p>顺序读写:磁盘顺序读写,提升读写效率</p>
<p>页缓存:把磁盘中的数据缓存到内存中,把对磁盘的访问变为对内存的访问</p>
<p>零拷贝:减少上下文切换及数据拷贝</p>
<p>消息压缩减少磁盘IO和网络IO</p>
<p>分批发送:将消息打包批量发送,减少网络开销</p>
</blockquote>
<p>pdf</p>
<div class="row">
<embed src="/pdf/消息中间件面试题-参考回答.pdf" width="100%" height="550" type="application/pdf">
</div>
</article><div class="tag_share"><div class="post-meta__tag-list"><a class="post-meta__tags" href="/tags/%E9%9D%A2%E8%AF%95/">面试</a></div><div class="post_share"><div class="social-share" data-image="/img/8.png" data-sites="wechat,weibo,qq"></div><link rel="stylesheet" href="/cdn/css/share.min.css" media="print" onload="this.media='all'"><script src="/cdn/js/social-share.min.js" defer></script></div></div><div class="post-reward"><div class="reward-button"><i class="fas fa-qrcode"></i> 打赏</div><div class="reward-main"><ul class="reward-all"><li class="reward-item"><a href="/img/wechat.jpg" target="_blank"><img class="post-qr-code-img" src="/img/wechat.jpg" alt="微信"/></a><div class="post-qr-code-desc">微信</div></li><li class="reward-item"><a href="/img/alipay.jpg" target="_blank"><img class="post-qr-code-img" src="/img/alipay.jpg" alt="支付宝"/></a><div class="post-qr-code-desc">支付宝</div></li></ul></div></div><br/><div id="post-comment"><div class="comment-head"><div class="comment-headline"><i class="far fa-comment-alt fa-fw"></i><span> 评论</span></div></div><div class="comment-wrap"><div><div id="gitalk-container"></div></div></div></div></div><div class="aside-content" id="aside-content"><div class="card-widget card-info"><div class="is-center"><div class="avatar-img"><img src="/img/avatar.jpg" onerror="this.onerror=null;this.src='/img/loading.gif'" alt="avatar"/></div><div class="author-info__name">Jason</div><div class="author-info__description">Debug the World</div></div><div class="card-info-data site-data is-center"><a href="/archives/"><div class="headline">文章</div><div class="length-num">77</div></a><a href="/tags/"><div class="headline">标签</div><div class="length-num">47</div></a><a href="/categories/"><div class="headline">分类</div><div class="length-num">10</div></a></div><a id="card-info-btn"><i class="fab fa-microsoft"></i><span>Ctrl + D 收藏</span></a><div class="card-info-social-icons is-center"><a class="social-icon" href="https://github.com/JasonsGong" target="_blank" title="Github"><i class="fab fa-github"></i></a><a class="social-icon" href="tencent://AddContact/?fromId=45&amp;fromSubId=1&amp;subcmd=all&amp;uin=2602183349&amp;website=www.oicqzone.com" target="_blank" title="QQ"><i class="fab fa-qq"></i></a><a class="social-icon" href="mailto:2602183349@qq.com" target="_blank" title="Email"><i class="fas fa-envelope-open-text"></i></a><a class="social-icon" href="https://github.com/JasonsGong?tab=repositories" target="_blank" title="代码仓库"><i class="fas fa-database"></i></a></div></div><div class="card-widget card-announcement"><div class="item-headline"><i class="fas fa-bullhorn fa-shake"></i><span>公告</span></div><div class="announcement_content">本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站</div></div><div class="sticky_layout"><div class="card-widget" id="card-toc"><div class="item-headline"><i class="fas fa-stream"></i><span>目录</span><span class="toc-percentage"></span></div><div class="toc-content is-expand"><ol class="toc"><li class="toc-item toc-level-2"><a class="toc-link" href="#50W%E5%AD%97%E7%9A%84%E9%9D%A2%E8%AF%95%E6%96%87%E6%A1%A3"><span class="toc-text">50W字的面试文档</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E9%BB%91%E9%A9%AC%E7%A8%8B%E5%BA%8F%E5%91%98Java%E9%9D%A2%E8%AF%95%E8%A7%86%E9%A2%91%E6%95%99%E7%A8%8B"><span class="toc-text">黑马程序员Java面试视频教程</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#1-Redis%E7%AF%87"><span class="toc-text">1.Redis篇</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#2-%E6%95%B0%E6%8D%AE%E5%BA%93%E7%AF%87"><span class="toc-text">2.数据库篇</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#3-%E6%A1%86%E6%9E%B6%E7%AF%87"><span class="toc-text">3.框架篇</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#4-%E5%BE%AE%E6%9C%8D%E5%8A%A1%E7%AF%87"><span class="toc-text">4.微服务篇</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#5-%E6%B6%88%E6%81%AF%E4%B8%AD%E9%97%B4%E4%BB%B6%E7%AF%87"><span class="toc-text">5.消息中间件篇</span></a></li></ol></div></div><div class="card-widget card-recent-post"><div class="item-headline"><i class="fas fa-history"></i><span>最近更新</span></div><div class="aside-list"><div class="aside-list-item"><a class="thumbnail" href="/posts/24183.html" title="任务进度"><img src="/img/4.png" onerror="this.onerror=null;this.src='/img/404.jpg'" alt="任务进度"/></a><div class="content"><a class="title" href="/posts/24183.html" title="任务进度">任务进度</a><time datetime="2024-03-28T03:28:02.368Z" title="更新于 2024-03-28 11:28:02">2024-03-28</time></div></div><div class="aside-list-item"><a class="thumbnail" href="/posts/32696.html" title="Blog"><img src="/img/3.png" onerror="this.onerror=null;this.src='/img/404.jpg'" alt="Blog"/></a><div class="content"><a class="title" href="/posts/32696.html" title="Blog">Blog</a><time datetime="2024-03-25T03:14:05.606Z" title="更新于 2024-03-25 11:14:05">2024-03-25</time></div></div><div class="aside-list-item"><a class="thumbnail" href="/posts/5293.html" title="开源项目-若依框架"><img src="/img/2.png" onerror="this.onerror=null;this.src='/img/404.jpg'" alt="开源项目-若依框架"/></a><div class="content"><a class="title" href="/posts/5293.html" title="开源项目-若依框架">开源项目-若依框架</a><time datetime="2024-02-03T14:30:54.022Z" title="更新于 2024-02-03 22:30:54">2024-02-03</time></div></div><div class="aside-list-item"><a class="thumbnail" href="/posts/200.html" title="常用bat和shell脚本"><img src="/img/4.png" onerror="this.onerror=null;this.src='/img/404.jpg'" alt="常用bat和shell脚本"/></a><div class="content"><a class="title" href="/posts/200.html" title="常用bat和shell脚本">常用bat和shell脚本</a><time datetime="2024-01-19T11:49:36.772Z" title="更新于 2024-01-19 19:49:36">2024-01-19</time></div></div><div class="aside-list-item"><a class="thumbnail" href="/posts/26357.html" title="WebSocket使用案例"><img src="/img/5.png" onerror="this.onerror=null;this.src='/img/404.jpg'" alt="WebSocket使用案例"/></a><div class="content"><a class="title" href="/posts/26357.html" title="WebSocket使用案例">WebSocket使用案例</a><time datetime="2024-01-17T13:39:10.166Z" title="更新于 2024-01-17 21:39:10">2024-01-17</time></div></div></div></div></div></div></main><footer id="footer"><div id="footer-wrap"></div></footer></div><div id="rightside"><div id="rightside-config-hide"><button id="readmode" type="button" title="阅读模式"><i class="fas fa-book-open"></i></button><button id="darkmode" type="button" title="浅色和深色模式转换"><i class="fas fa-adjust"></i></button><button id="hide-aside-btn" type="button" title="单栏和双栏切换"><i class="fas fa-arrows-alt-h"></i></button></div><div id="rightside-config-show"><button id="rightside_config" type="button" title="设置"><i class="fas fa-cog fa-spin"></i></button><button class="close" id="mobile-toc-button" type="button" title="目录"><i class="fas fa-list-ul"></i></button><a id="to_comment" href="#post-comment" title="直达评论"><i class="fas fa-comment-alt"></i></a><button id="go-up" type="button" title="回到顶部"><span class="scroll-percent"></span><i class="fas fa-arrow-up"></i></button></div></div><div><script src="/js/utils.js"></script><script src="/js/main.js"></script><script src="/cdn/js/medium-zoom.min.js"></script><script src="/cdn/js/instantpage.min.js" type="module"></script><script src="/cdn/js/snackbar.min.js"></script><div class="js-pjax"><script>function loadGitalk () {
function initGitalk () {
var gitalk = new Gitalk(Object.assign({
clientID: '00fb27b1e484536359c2',
clientSecret: 'be41a12281c68b6e228d1a27e8d08aeb91541145',
repo: 'BlogComment',
owner: 'JasonsGong',
admin: ['JasonsGong'],
id: '442d293f31d9ea901217773d7b539337',
updateCountCallback: commentCount
},null))
gitalk.render('gitalk-container')
}
if (typeof Gitalk === 'function') initGitalk()
else {
getCSS('/cdn/css/gitalk.min.css')
getScript('/cdn/js/gitalk.min.js').then(initGitalk)
}
}
function commentCount(n){
let isCommentCount = document.querySelector('#post-meta .gitalk-comment-count')
if (isCommentCount) {
isCommentCount.textContent= n
}
}
if ('Gitalk' === 'Gitalk' || !true) {
if (true) btf.loadComment(document.getElementById('gitalk-container'), loadGitalk)
else loadGitalk()
} else {
function loadOtherComment () {
loadGitalk()
}
}</script></div><script async data-pjax src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script><div id="local-search"><div class="search-dialog"><nav class="search-nav"><span class="search-dialog-title">搜索</span><span id="loading-status"></span><button class="search-close-button"><i class="fas fa-times"></i></button></nav><div class="is-center" id="loading-database"><i class="fas fa-spinner fa-pulse"></i><span> 数据库加载中</span></div><div class="search-wrap"><div id="local-search-input"><div class="local-search-box"><input class="local-search-box--input" placeholder="搜索文章" type="text"/></div></div><br/><div class="no-result" id="local-search-results"></div><div id="local-search-stats-wrap"></div></div></div><div id="search-mask"></div><script src="/js/search/local-search.js"></script></div></div><!-- hexo injector body_end start --><script data-pjax>
function butterfly_swiper_injector_config(){
var parent_div_git = document.getElementById('recent-posts');
var item_html = '<div class="recent-post-item" style="height: auto;width: 100%"><div class="blog-slider swiper-container-fade swiper-container-horizontal" id="swiper_container"><div class="blog-slider__wrp swiper-wrapper" style="transition-duration: 0ms;"><div class="blog-slider__item swiper-slide" style="width: 750px; opacity: 1; transform: translate3d(0px, 0px, 0px); transition-duration: 0ms;"><a class="blog-slider__img" href="posts/19306.html" alt=""><img width="48" height="48" src="/img/1.png" alt="" onerror="this.src=https://unpkg.zhimg.com/akilar-candyassets/image/loading.gif; this.onerror = null;"/></a><div class="blog-slider__content"><span class="blog-slider__code">2023-04-21</span><a class="blog-slider__title" href="posts/19306.html" alt="">Docker容器化技术</a><div class="blog-slider__text">Docker</div><a class="blog-slider__button" href="posts/19306.html" alt="">详情 </a></div></div><div class="blog-slider__item swiper-slide" style="width: 750px; opacity: 1; transform: translate3d(0px, 0px, 0px); transition-duration: 0ms;"><a class="blog-slider__img" href="posts/47003.html" alt=""><img width="48" height="48" src="/img/10.png" alt="" onerror="this.src=https://unpkg.zhimg.com/akilar-candyassets/image/loading.gif; this.onerror = null;"/></a><div class="blog-slider__content"><span class="blog-slider__code">2023-03-10</span><a class="blog-slider__title" href="posts/47003.html" alt="">常用正则表达式大全</a><div class="blog-slider__text">正则表达式</div><a class="blog-slider__button" href="posts/47003.html" alt="">详情 </a></div></div><div class="blog-slider__item swiper-slide" style="width: 750px; opacity: 1; transform: translate3d(0px, 0px, 0px); transition-duration: 0ms;"><a class="blog-slider__img" href="posts/20683.html" alt=""><img width="48" height="48" src="/img/10.png" alt="" onerror="this.src=https://unpkg.zhimg.com/akilar-candyassets/image/loading.gif; this.onerror = null;"/></a><div class="blog-slider__content"><span class="blog-slider__code">2023-06-05</span><a class="blog-slider__title" href="posts/20683.html" alt="">Linux中开发环境的搭建</a><div class="blog-slider__text">环境搭建</div><a class="blog-slider__button" href="posts/20683.html" alt="">详情 </a></div></div><div class="blog-slider__item swiper-slide" style="width: 750px; opacity: 1; transform: translate3d(0px, 0px, 0px); transition-duration: 0ms;"><a class="blog-slider__img" href="posts/63333.html" alt=""><img width="48" height="48" src="/img/7.png" alt="" onerror="this.src=https://unpkg.zhimg.com/akilar-candyassets/image/loading.gif; this.onerror = null;"/></a><div class="blog-slider__content"><span class="blog-slider__code">2023-06-03</span><a class="blog-slider__title" href="posts/63333.html" alt="">开发环境的搭建</a><div class="blog-slider__text">环境搭建</div><a class="blog-slider__button" href="posts/63333.html" alt="">详情 </a></div></div></div><div class="blog-slider__pagination swiper-pagination-clickable swiper-pagination-bullets"></div></div></div>';
if (parent_div_git !== null && typeof parent_div_git !== 'undefined') {
parent_div_git.insertAdjacentHTML("afterbegin",item_html)
}
}
var elist = 'undefined'.split(',');
var cpage = location.pathname;
var epage = 'all';
var flag = 0;
for (var i=0;i<elist.length;i++){
if (cpage.includes(elist[i])){
flag++;
}
}
if ((epage ==='all')&&(flag == 0)){
butterfly_swiper_injector_config();
}
else if (epage === cpage){
butterfly_swiper_injector_config();
}
</script><script defer src="https://npm.elemecdn.com/hexo-butterfly-swiper/lib/swiper.min.js"></script><script defer data-pjax src="https://npm.elemecdn.com/hexo-butterfly-swiper/lib/swiper_init.js"></script><!-- hexo injector body_end end --></body></html>