2020java面試題(含答案)有這一篇就夠了

本文摘取了很多其他博客裏的精華內容,同時又融入了一些自己的理解,希望對大家找工作之前的面試準備有所幫助;

一、Spring與SpringMVC

SpringMVC

1.1 簡單介紹你所理解的SpringMVC

Spring MVC是一個基於Java的實現了MVC設計模式的請求驅動類型的輕量級Web框架,通過把Model,View,Controller分離,將web層進行職責解耦,把複雜的web應用分成邏輯清晰的幾部分,簡化開發,減少出錯,方便組內開發人員之間的配合

1.2 SpringMVC具體流程

  1. 用戶發送請求至前端控制器DispatcherServlet;
  2. DispatcherServlet收到請求後,調用HandlerMapping處理器映射器,請求獲取Handle;
  3. 處理器映射器根據請求url找到具體的處理器(Controoller),生成處理器對象及處理器攔截器(如果有則生成)一起返回給DispatcherServlet;
  4. DispatcherServlet 調用 HandlerAdapter處理器適配器,經過適配找到並調用具體處理器(Handler,也叫後端控制器,即Controller裏的具體方法);
  5. Handler執行完成返回ModelAndView視圖;
  6. HandlerAdapter將Handler執行結果ModelAndView返回給DispatcherServlet;
  7. DispatcherServlet將ModelAndView傳給ViewResolver視圖解析器進行解析;
  8. ViewResolver解析後返回具體View;
  9. DispatcherServlet對View進行渲染視圖(即將模型數據填充至視圖中)10. DispatcherServlet響應用戶。

1.3 SpringMVC優點

  1. 可以支持各種視圖技術,而不僅僅侷限於JSP;

  2. 與Spring框架集成(如IoC容器、AOP等);

  3. 清晰的角色分配:前端控制器(dispatcherServlet) , 請求到處理器映射(handlerMapping), 處理器適配器(HandlerAdapter), 視圖解析器(ViewResolver)。

  4. 支持各種請求資源的映射策略。

1.4 主要組件

  1. 前端控制器 DispatcherServlet(不需要程序員開發)

​ 作用:接收請求、響應結果,相當於轉發器,有了DispatcherServlet 就減少了其它組件之間的耦合度。

  1. 處理器映射器HandlerMapping(不需要程序員開發)

​ 作用:根據請求的URL來查找Handler

  1. 處理器適配器HandlerAdapter

注意:在編寫Handler的時候要按照HandlerAdapter要求的規則去編寫,這樣適配器HandlerAdapter纔可以正確的去執行Handler。

  1. 處理器Handler(需要程序員開發)

  2. 視圖解析器 ViewResolver(不需要程序員開發)

作用:進行視圖的解析,根據視圖邏輯名解析成真正的視圖(view)

  1. 視圖View(需要程序員開發jsp)

View是一個接口, 它的實現類支持不同的視圖類型(jsp,freemarker,pdf等等)

1.5 SpringMVC與struts2的區別

  1. springmvc的入口是一個servlet即前端控制器(DispatchServlet),而struts2入口是一個filter過慮器(StrutsPrepareAndExecuteFilter)。

  2. springmvc是基於方法開發(一個url對應一個方法),請求參數傳遞到方法的形參,可以設計爲單例或多例(建議單例),struts2是基於類開發,傳遞參數是通過類的屬性,只能設計爲多例。

  3. Struts採用值棧存儲請求和響應的數據,通過OGNL存取數據,springmvc通過參數解析器是將request請求內容解析,並給方法形參賦值,將數據和視圖封裝成ModelAndView對象,最後又將ModelAndView中的模型數據通過reques域傳輸到頁面。Jsp視圖解析器默認使用jstl。

1.6 怎麼樣設定重定向和轉發的

  1. 轉發:在返回值前面加"forward:",譬如"forward:user.do?name=method4"

  2. 重定向:在返回值前面加"redirect:",譬如"redirect:http://www.baidu.com"

1.7 怎麼和AJAX相互調用

通過Jackson框架就可以把Java裏面的對象直接轉化成Js可以識別的Json對象。具體步驟如下 :

  1. 加入Jackson.jar

  2. 在配置文件中配置json的映射

  3. 在接受Ajax方法裏面可以直接返回Object,List等,但方法前面要加上@ResponseBody註解。

1.8 解決POST與GET請求中文亂碼

1.8.1 post亂碼

在web.xml中配置一個CharacterEncodingFilter過濾器,設置成utf-8;

<filter>

    <filter-name>CharacterEncodingFilter</filter-name>

    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>

    <init-param>

        <param-name>encoding</param-name>

        <param-value>utf-8</param-value>

    </init-param>

</filter>

<filter-mapping>

    <filter-name>CharacterEncodingFilter</filter-name>

    <url-pattern>/*</url-pattern>

</filter-mapping>

1.8.2 get亂碼

get請求中文參數出現亂碼解決方方式有兩個

方式一、

修改tomcat配置文件添加編碼與工程編碼一致,如下:

<ConnectorURIEncoding="utf-8" connectionTimeout="20000" port="8080" protocol="HTTP/1.1" redirectPort="8443"/>

方式二、

對參數進行重新編碼:

String userName = new String(request.getParamter("userName").getBytes("ISO8859-1"),"utf-8")

ISO8859-1是tomcat默認編碼,需要將tomcat編碼後的內容按utf-8編碼。

1.9 SpringMVC異常處理

可以將異常拋給Spring框架,由Spring框架來處理;我們只需要配置簡單的異常處理器,在異常處理器中添視圖頁面即可。

1.10 SpringMvc的控制器是不是單例模式,如果是,有什麼問題,怎麼解決

默認情況下是單例模式,所以在多線程訪問的時候有線程安全問題,不要用同步,會影響性能的,解決方案是在控制器裏面不能寫字段(即在控制器裏不寫成員變量)。單例好處:性能好,不用每次請求都創建對象;

1.11 SpringMVC常用的註解有哪些

  1. RequestMapping:用於處理請求 url 映射的註解,可用於類或方法上。用於類上,則表示類中的所有響應請求的方法都是以該地址作爲父路徑。

  2. RequestBody:註解實現接收http請求的json數據,將json轉換爲java對象。

  3. ResponseBody:註解實現將conreoller方法返回對象轉化爲json對象響應給客戶。

  4. @Controller:@Controller 只是定義了一個控制器類,而使用@RequestMapping 註解的方法纔是真正處理請求的處理器.

  5. @Resource與@Autowired:@Resource和@Autowired都是做bean的注入時使用,其實@Resource並不是Spring的註解,它的包是javax.annotation.Resource,需要導入,但是Spring支持該註解的注入。

  6. @PathVariable:用於將請求URL中的模板變量映射到功能處理方法的參數上

    @RequestMapping(value="/product/{productId}",method = RequestMethod.GET)  
         public String getProduct(@PathVariable("productId") String productId){  
               System.out.println("Product Id : " + productId);  
               return "hello"; 
     }  
    
  7. @RequestParam:用於將請求參數區數據映射到功能處理方法的參數上

    @RequestMapping("/testRequestParam")
    public String testRequestParam(@RequestParam("id") int id) {
         System.out.println("testRequestParam  " + id);
         return "success";
    }
    

1.12 控制器的註解一般用哪個,有沒有別的註解可以替代

一般用@Controller註解,也可以使用@RestController,@RestController註解相當於@ResponseBody + @Controller,表示是表現層,除此之外,一般不用別的註解代替

1.13 在攔截請求中,我想攔截get方式提交的方法,怎麼配置

可以在@RequestMapping註解裏面加上method=RequestMethod.GET

1.14 怎樣在方法裏面得到Request,或者Session

直接在方法的形參中聲明request,SpringMvc就自動把request對象傳入

1.15 如果想在攔截的方法裏面得到從前臺傳入的參數,怎麼得到

直接在形參裏面聲明這個參數就可以,但必須名字和傳過來的參數一樣

1.16 如果前臺有很多個參數傳入,並且這些參數都是一個對象的,那麼怎麼樣快速得到這個對象

直接在方法中聲明這個對象,SpringMvc就自動會把屬性賦值到這個對象裏面

1.17 SpringMvc中函數的返回值是什麼

返回值可以有很多類型,有String, ModelAndView。ModelAndView類把視圖和數據都合併的一起的,但一般用String比較好

1.18 SpringMvc用什麼對象從後臺向前臺傳遞數據的

通過ModelMap對象,可以在這個對象裏面調用put方法,把對象加到裏面,前臺就可以通過el表達式拿到

1.19 怎麼樣把ModelMap裏面的數據放入Session裏面

可以在類上面加上@SessionAttributes註解,裏面包含的字符串就是要放入session裏面的key

1.20 SpringMvc裏面攔截器是怎麼寫的

有兩種寫法,一種是實現HandlerInterceptor接口,另外一種是繼承適配器類,接着在接口方法當中,實現處理邏輯;然後在SpringMvc的配置文件中配置攔截器即可:

<!-- 配置SpringMvc的攔截器 -->
 
<mvc:interceptors>
 
    <!-- 配置一個攔截器的Bean就可以了 默認是對所有請求都攔截 -->
 
    <bean id="myInterceptor" class="com.zwp.action.MyHandlerInterceptor"></bean>
 
    <!-- 只針對部分請求攔截 -->
 
    <mvc:interceptor>
 
       <mvc:mapping path="/modelMap.do" />
 
       <bean class="com.zwp.action.MyHandlerInterceptorAdapter" />
 
    </mvc:interceptor>
 
</mvc:interceptors>

1.21 註解原理

註解本質是一個繼承了Annotation的特殊接口,其具體實現類是Java運行時生成的動態代理類。我們通過反射獲取註解時,返回的是Java運行時生成的動態代理對象。通過代理對象調用自定義註解的方法,會最終調用AnnotationInvocationHandler的invoke方法。該方法會從memberValues這個Map中索引出對應的值。而memberValues的來源是Java常量池;

Spring

1.22 SpringBean生命週期

Spring幫我們創建了對象,我們稱之爲Bean,

  1. 調用構造方法實例化Bean對象
  2. 設置Bean屬性(調用Bean的set方法)
  3. 如果實現各種Aware接口聲明瞭依賴關係(即Bean實現了各種Aware接口),則會注入Bean對容器基礎設施層面的依賴。Aware接口具體包括BeanNameAware、BeanFactoryAware、ApplicationContextAware,分別注入Bean ID、Bean Factory或者ApplicationContext
  4. 如果實現了BeanPostProcessor,調用BeanPostProcessor的前置初始化方法postProcessBeforeInitialization
  5. 如果實現了InitialiaingBean接口,則會調用afterPropertiesSet方法
  6. 調用Bean自身的init方法
  7. 調用BeanPostProcessor的後置方法postProcessAfterInitialization。

創建完畢,銷燬:銷燬分兩步,先銷燬接口裏的方法,再銷燬自身的方法

  1. DisposableBean的destory方法和自身的destory方法

SpringBean作用域

我們很少去修改SpringBean作用域,默認作用域是單例的,Spring使用單例的目的,是使用IOC控制反轉解決耦合的情況;也可以設置爲prototype;

如果使用默認的singleton,結果是true,代表是同一個對象
在這裏插入圖片描述
如果使用prototype

在這裏插入圖片描述
結果是false,代表是兩個不同的對象
在這裏插入圖片描述
SpringBean所有作用域

  1. singleton:Spring默認作用域,每個IOC容器創建唯一的一個Beaan
  2. prototype:針對每個getBean請求,容器會單獨創建一個Bean實例
  3. request:針對每個HTTP請求都創建單獨的Bean實例
  4. session:比request範圍大一些,同一個session範圍內使用一個Bean
  5. GlobalSession:用於PortLet容器中,提供了一個全局的HTTP session;

Spring所有作用域

  1. singleton:Spring默認作用域,每個IOC容器創建唯一的一個Beaan
  2. prototype:針對每個getBean請求,容器會單獨創建一個Bean實例
  3. request:針對每個HTTP請求都創建單獨的Bean實例
  4. session:比request範圍大一些,同一個session範圍內使用一個Bean
  5. GlobalSession:用於PortLet容器中,提供了一個全局的HTTP session;

Spring五種隔離級別

在這裏插入圖片描述
概念介紹

  1. 髒讀:一個事物讀取到另一個事物還沒有提交的數據

  2. 不可重複讀:同一個事物裏,兩次去讀取相同的數據,讀取到的結果不一樣,產生的原因是另一條事物對這個事物進行了修改

  3. 幻讀:同一個事物裏,查詢到的結果多了或者少了,像產生幻覺一樣。比如註冊用戶時,用戶名已經有的話給出提示,用戶已存在,不可以註冊。當兩個線程同時去做註冊時(用戶名都是張三),在數據庫裏查的時候都沒有,都去做insert,線程1先insert進去了,線程2去insert時,被告知數據庫裏已經有張三這個用戶了,即對於線程2來說,前邊查的與後邊查的,得到的結果不一樣(前邊查的沒有張三,後邊查時又有了),此時對於線程2來說就是幻讀;

隔離級別

  1. ISOLATIO_DEFAULT:其實不是單獨的隔離級別,指的是使用數據庫默認的隔離級別(mysql是可重複度的級別);
  2. ISOLATIO_READ_UNCOMMITTED:讀未提交,最低級別,允許看到其他事物未提交的事物,會產生髒讀、幻讀、不可重複讀;
  3. ISOLATIO_READ_COMMITTED:讀已提交,只能讀到自己已經提交的數據,可以防止髒讀,會產生幻讀、不可重複讀;
  4. ISOLATIO_REPEATABLE_READ:可重複度,可以防止髒讀、不可重複讀、產生幻讀;
  5. ISOLATIO_SERIALIZABLE:最高級別,事物處理爲串行、阻塞的,能避免所有情況,但是性能下降了;

Spring事物傳播機制

事物傳播機制:指的是在一個方法裏,有多個事物的時候,怎麼去處理;Spring的傳播機制有以下七種:
在這裏插入圖片描述

  1. PROPAGATION_REQUIREC:支持當前事物,如果當前沒有事物,就新建一個事物,這是最常見的選擇
  2. PROPAGATION_SUPPORTS:支持當前事物,如果當前沒有事物,就以非事物方式執行
  3. PROPAGATION_MANDATORY:支持當前事物,如果當前沒有事物,就拋出異常
  4. PROPAGATION_NESTED:支持當前事物,如果當前事物存在,則執行一個嵌套事物,如果當前沒有事物,就新建一個事物(外層事物的回滾會引起內層事物的回滾,反之不成立)
  5. PROPAGATION_REQUIRES_NEW:支持當前事物,如果當前事物存在,則把當前事物掛起(一旦內層事物提交後,外層事物不能對其進行回滾,兩個事物互不影響)
  6. PROPAGATION_NOT_SUPPORTED:以非事物方式執行操作,如果當前事物存在,就把當前事物掛起
  7. PROPAGATION_NEVER:以非事物方式執行操作,如果當前事物存在,則拋出異常

Spring設計模式

  1. 工廠模式:Spring容器就是工廠模式(BeanFactory和ApplicationContext應用了工廠模式)

  2. 單例和原型模式:在Bean創建的時候,Spring提供了單例(singleton)和原型模式(protoType)

  3. 代理模式:AOP用到的是代理模式(在原有方法上進行增強,但沒有改變原代碼)、裝飾器模式、適配器模式

  4. 觀察者模式:事件監聽器

  5. 模版模式:類似JdbcTemplate等模版對象(因爲裏邊已經封裝好了一些方法)

二、Redis

2.1 QPS: 應用系統每秒鐘最大能接受的用戶訪問量

每秒鐘處理完請求的次數,注意這裏是處理完,具體是指發出請求到服務器處理完成功返回結果。可以理解在server中有個counter,每處理一個請求加1,1秒後counter=QPS。

2.2 TPS: 每秒鐘最大能處理的請求數

每秒鐘處理完的事務次數,一個應用系統1s能完成多少事務處理,一個事務在分佈式處理中,可能會對應多個請求,對於衡量單個接口服務的處理能力,用QPS比較合理

2.3 使用Redis的優勢與劣勢

優勢

  1. 速度快,因爲數據存在內存中,類似於HashMap,HashMap的優勢就是查找和操作的時間複雜度都是O(1)

  2. 支持豐富數據類型,支持string,list,set,sorted set,hash

  3. 支持事務,操作都是原子性,所謂的原子性就是對數據的更改要麼全部執行,要麼全部不執行

  4. 豐富的特性:可用於緩存,消息,按key設置過期時間,過期後將會自動刪除

劣勢

Redis的主要缺點是數據庫容量受到物理內存的限制,不能用作海量數據的高性能讀寫,因此Redis適合的場景主要侷限在較小數據量的高性能操作和運算上。

2.4 Redis爲什麼是單線程的

多線程處理會涉及到鎖,而且多線程處理會涉及到線程切換而消耗CPU。因爲CPU不是Redis的瓶頸,Redis的瓶頸最有可能是機器內存或者網絡帶寬。單線程無法發揮多核CPU性能,不過可以通過在單機開多個Redis實例來解決。

2.5 支持多種類型的數據結構

  1. string:最基本的數據類型,二進制安全的字符串,最大512M。

  2. list:按照添加順序保持順序的字符串列表。

  3. set:無序的字符串集合,不存在重複的元素。

  4. sorted set:已排序的字符串集合。

  5. hash:key-value對的一種集合。

每種數據類型的應用場景

  1. String
    最常規的set/get操作,value可以是String也可以是數字。一般做一些複雜的計數功能的緩存。
  2. hash
    value存放的是結構化的對象,可以方便的操作其中的某個字段。比如做單點登錄的時候,就是用這種數據結構存儲用戶信息,以cookieId作爲key,設置20分鐘爲緩存過期時間,能很好的模擬出類似session的效果。
  3. list
    使用List的數據結構,可以做簡單的消息隊列的功能。另外還有一個就是,可以利用lrange命令,做基於redis的分頁功能,性能極佳,用戶體驗好。還可以做簡單的生產者和消費者的場景。LIST可以很好的完成排隊,先進先出的原則。
  4. set
    因爲set堆放的是一堆不重複值的集合,所以可以做全局去重的功能。那問題來了,爲什麼不用JVM自帶的Set進行去重?因爲我們的系統一般都是集羣部署,使用JVM自帶的Set,比較麻煩,難道爲了一個模塊去做一個全局去重,再起一個公共服務,太麻煩了。
    另外,就是利用交集、並集、差集等操作,可以計算共同喜好,全部的喜好,自己獨有的喜好等功能。
  5. sorted set
    sorted set多了一個權重參數score,集合中的元素能夠按score進行排列。可以做排行榜應用,取TOP N操作。

2.6 持久化方式

Redis主要提供了兩種持久化機制:RDB和AOF

RDB

默認開啓,會按照配置的指定時間將內存中的數據快照到磁盤中,創建一個dump.rdb文件,Redis啓動時再恢復到內存中。

Redis會單獨創建fork()一個子進程,將當前父進程的數據庫數據複製到子進程的內存中,然後由子進程寫入到臨時文件中,持久化的過程結束了,再用這個臨時文件替換上次的快照文件,然後子進程退出,內存釋放。

需要注意的是,每次快照持久化都會將主進程的數據庫數據複製一遍,導致內存開銷加倍,若此時內存不足,則會阻塞服務器運行,直到複製結束釋放內存;都會將內存數據完整寫入磁盤一次,所以如果數據量大的話,而且寫操作頻繁,必然會引起大量的磁盤I/O操作,嚴重影響性能,並且最後一次持久化後的數據可能會丟失;

AOF

以日誌的形式記錄每個寫操作(讀操作不記錄),只需追加文件但不可以改寫文件,Redis啓動時會根據日誌從頭到尾全部執行一遍以完成數據的恢復工作。包括flushDB也會執行。

主要有兩種方式觸發:有寫操作就寫、每秒定時寫(也會丟數據)。

因爲AOF採用追加的方式,所以文件會越來越大,針對這個問題,新增了重寫機制,就是當日志文件大到一定程度的時候,會fork出一條新進程來遍歷進程內存中的數據,每條記錄對應一條set語句,寫到臨時文件中,然後再替換到舊的日誌文件(類似rdb的操作方式)。默認觸發是當aof文件大小是上次重寫後大小的一倍且文件大於64M時觸發。

如何選擇持久化

當兩種方式同時開啓時(默認使用RDB),Redis會優先選擇AOF還原數據。一般情況下,只要使用默認開啓的RDB即可,因爲相對於AOF,RDB便於進行數據庫備份,並且恢復數據集的速度也要快很多。

開啓持久化緩存機制,對性能會有一定的影響,特別是當設置的內存滿了的時候,更是下降到幾百reqs/s。所以如果只是用來做緩存的話,可以關掉持久化;

2.7 什麼是緩存穿透?如何避免?

緩存穿透

一般的緩存系統,都是按照key去緩存查詢,如果不存在對應的value,就應該去後端系統查找(比如Oracle、Mysql等)。一些惡意的請求會故意查詢不存在的key,請求量很大,就會對後端系統造成很大的壓力。這就叫做緩存穿透。

如何避免

  1. 對查詢結果爲空的情況也進行緩存,緩存時間設置短一點,或者該key對應的數據insert了之後清理緩存。
  2. 對一定不存在的key進行過濾。可以把所有的可能存在的key放到一個大的Bitmap中,查詢時通過該bitmap過濾。

2.8 什麼是緩存雪崩?何如避免?

緩存雪崩

當緩存服務器重啓或者大量緩存集中在某一個時間段失效,這樣在失效的時候,會給後端系統帶來很大壓力。導致系統崩潰。

何如避免

  1. 在緩存失效後,通過加鎖或者隊列來控制讀數據庫寫緩存的線程數量。比如對某個key只允許一個線程查詢數據和寫緩存,其他線程等待。

  2. 做二級緩存,A1爲原始緩存,A2爲拷貝緩存,A1失效時,可以訪問A2,A1緩存失效時間設置爲短期,A2設置爲長期

  3. 不同的key,設置不同的過期時間,讓緩存失效的時間點儘量均勻。

2.9 Redis集羣方案應該怎麼做?都有哪些方案?

  1. twemproxy,它類似於一個代理方式,使用方法和普通redis無任何區別,設置好它下屬的多個redis實例後,使用時在本需要連接redis的地方改爲連接twemproxy,它會以一個代理的身份接收請求並使用一致性hash算法,將請求轉接到具體redis,將結果再返回twemproxy。使用方式簡便(相對redis只需修改連接端口),是對舊項目擴展的首選。 問題:twemproxy自身單端口實例的壓力,使用一致性hash後,對redis節點數量改變時候的計算值的改變,數據無法自動移動到新的節點。
  2. codis,目前用的最多的集羣方案,基本和twemproxy一致的效果,但它支持在節點數量改變情況下,舊節點數據可恢復到新hash節點。
  3. redis cluster3.0自帶的集羣,特點在於他的分佈式算法不是一致性hash,而是hash槽的概念,以及自身支持節點設置從節點。具體看官方文檔介紹。
  4. 在業務代碼層實現,起幾個毫無關聯的redis實例,在代碼層,對key 進行hash計算,然後去對應的redis實例操作數據。 這種方式對hash層代碼要求比較高,考慮部分包括,節點失效後的替代算法方案,數據震盪後的自動腳本恢復,實例的監控,等等。

2.10 Redis集羣方案什麼情況下會導致整個集羣不可用?

有A,B,C三個節點的集羣,在沒有複製模型的情況下,如果節點B失敗了,那麼整個集羣就會以爲缺少5501-11000這個範圍的槽而不可用。

2.11 MySQL裏有2000w數據,redis中只存20w的數據,如何保證redis中的數據都是熱點數據?

redis內存數據集大小上升到一定大小的時候,就會施行數據淘汰策略。

2.12 Redis有哪幾種數據淘汰策略?

  1. noeviction:返回錯誤當內存限制達到並且客戶端嘗試執行會讓更多內存被使用的命令(大部分的寫入指令,但DEL和幾個例外)
  2. allkeys-lru: 嘗試回收最少使用的鍵(LRU),使得新添加的數據有空間存放。
  3. volatile-lru: 嘗試回收最少使用的鍵(LRU),但僅限於在過期集合的鍵,使得新添加的數據有空間存放。
  4. allkeys-random: 回收隨機的鍵使得新添加的數據有空間存放。
  5. volatile-random: 回收隨機的鍵使得新添加的數據有空間存放,但僅限於在過期集合的鍵。
  6. volatile-ttl: 回收在過期集合的鍵,並且優先回收存活時間(TTL)較短的鍵,使得新添加的數據有空間存放。

2.13 Redis有哪些適合的場景?

  1. 會話緩存(Session Cache):最常用的一種使用Redis的情景是會話緩存(session cache)。用Redis緩存會話比其他存儲(如Memcached)的優勢在於:Redis提供持久化。當維護一個不是嚴格要求一致性的緩存時,如果用戶的購物車信息全部丟失,大部分人都會不高興的,現在,他們還會這樣嗎?幸運的是,隨着 Redis 這些年的改進,很容易找到怎麼恰當的使用Redis來緩存會話的文檔。甚至廣爲人知的商業平臺Magento也提供Redis的插件。
  2. 全頁緩存(FPC):除基本的會話token之外,Redis還提供很簡便的FPC平臺。回到一致性問題,即使重啓了Redis實例,因爲有磁盤的持久化,用戶也不會看到頁面加載速度的下降,這是一個極大改進,類似PHP本地FPC。再次以Magento爲例,Magento提供一個插件來使用Redis作爲全頁緩存後端。此外,對WordPress的用戶來說,Pantheon有一個非常好的插件 wp-redis,這個插件能幫助你以最快速度加載你曾瀏覽過的頁面。
  3. 隊列:Reids在內存存儲引擎領域的一大優點是提供 list 和 set 操作,這使得Redis能作爲一個很好的消息隊列平臺來使用。Redis作爲隊列使用的操作,就類似於本地程序語言(如Python)對 list 的 push/pop 操作。如果你快速的在Google中搜索“Redis queues”,你馬上就能找到大量的開源項目,這些項目的目的就是利用Redis創建非常好的後端工具,以滿足各種隊列需求。例如,Celery有一個後臺就是使用Redis作爲broker,你可以從這裏去查看。
  4. 排行榜/計數器:Redis在內存中對數字進行遞增或遞減的操作實現的非常好。集合(Set)和有序集合(Sorted Set)也使得我們在執行這些操作的時候變的非常簡單,Redis只是正好提供了這兩種數據結構。所以,我們要從排序集合中獲取到排名最靠前的10個用戶–我們稱之爲“user_scores”;當然,這是假定你是根據你用戶的分數做遞增的排序。如果你想返回用戶及用戶的分數,你需要這樣執行:ZRANGE user_scores 0 10 WITHSCORESAgora Games就是一個很好的例子,用Ruby實現的,它的排行榜就是使用Redis來存儲數據的。
  5. 發佈/訂閱:發佈/訂閱的使用場景確實非常多。我已看見人們在社交網絡連接中使用,還可作爲基於發佈/訂閱的腳本觸發器,甚至用Redis的發佈/訂閱功能來建立聊天系統!

2.14 Redis哈希槽的概念

Redis集羣沒有使用一致性hash,而是引入了哈希槽的概念,Redis集羣有16384個哈希槽,每個key通過CRC16校驗後對16384取模來決定放置哪個槽,集羣的每個節點負責一部分hash槽。

2.15 Redis集羣之間是如何複製的?

異步複製

2.16 Redis集羣會有寫操作丟失嗎?爲什麼?

Redis並不能保證數據的強一致性,這意味這在實際中集羣在特定的條件下可能會丟失寫操作。

2.17 Redis集羣最大節點個數是多少?

16384個。

2.18 Redis集羣如何選擇數據庫?

Redis集羣目前無法做數據庫選擇,默認在0數據庫。

2.19 怎麼測試Redis的連通性?

ping

2.20 Redis中的管道有什麼用?

一次請求/響應服務器能實現處理新的請求,即使舊的請求還未被響應。這樣就可以將多個命令發送到服務器,而不用等待回覆,最後在一個步驟中讀取該答覆。這就是管道(pipelining),是一種幾十年來廣泛使用的技術。例如許多POP3協議已經實現支持這個功能,大大加快了從服務器下載新郵件的過程。

2.21 怎麼理解Redis事務?

事務是一個單獨的隔離操作:事務中的所有命令都會序列化、按順序地執行。事務在執行的過程中,不會被其他客戶端發送來的命令請求所打斷。事務是一個原子操作:事務中的命令要麼全部被執行,要麼全部都不執行。

2.22 Redis事務相關的命令有哪幾個?

MULTI、EXEC、DISCARD、WATCH ##28、Redis key的過期時間和永久有效分別怎麼設置? EXPIRE和PERSIST命令。

2.23 Redis如何做內存優化?

儘可能使用散列表(hashes),散列表(是說散列表裏面存儲的數少)使用的內存非常小,所以你應該儘可能的將你的數據模型抽象到一個散列表裏面。比如你的web系統中有一個用戶對象,不要爲這個用戶的名稱,姓氏,郵箱,密碼設置單獨的key,而是應該把這個用戶的所有信息存儲到一張散列表裏面.

2.24 Redis回收進程如何工作的?

一個客戶端運行了新的命令,添加了新的數據。Redi檢查內存使用情況,如果大於maxmemory的限制, 則根據設定好的策略進行回收。一個新的命令被執行,等等。所以我們不斷地穿越內存限制的邊界,通過不斷達到邊界然後不斷地回收回到邊界以下。如果一個命令的結果導致大量內存被使用(例如很大的集合的交集保存到一個新的鍵),不用多久內存限制就會被這個內存使用量超越。

2.25 Redis回收使用的是什麼算法?

LRU算法

2.26 Redis如何做大量數據插入?

Redis2.6開始redis-cli支持一種新的被稱之爲pipe mode的新模式用於執行大量數據插入工作。

2.27 爲什麼要做Redis分區?

分區可以讓Redis管理更大的內存,Redis將可以使用所有機器的內存。如果沒有分區,你最多隻能使用一臺機器的內存。分區使Redis的計算能力通過簡單地增加計算機得到成倍提升,Redis的網絡帶寬也會隨着計算機和網卡的增加而成倍增長。

2.28 你知道有哪些Redis分區實現方案?

客戶端分區就是在客戶端就已經決定數據會被存儲到哪個redis節點或者從哪個redis節點讀取。大多數客戶端已經實現了客戶端分區。代理分區 意味着客戶端將請求發送給代理,然後代理決定去哪個節點寫數據或者讀數據。代理根據分區規則決定請求哪些Redis實例,然後根據Redis的響應結果返回給客戶端。redis和memcached的一種代理實現就是Twemproxy查詢路由(Query routing) 的意思是客戶端隨機地請求任意一個redis實例,然後由Redis將請求轉發給正確的Redis節點。Redis Cluster實現了一種混合形式的查詢路由,但並不是直接將請求從一個redis節點轉發到另一個redis節點,而是在客戶端的幫助下直接redirected到正確的redis節點。

2.29 Redis分區有什麼缺點?

涉及多個key的操作通常不會被支持。例如你不能對兩個集合求交集,因爲他們可能被存儲到不同的Redis實例(實際上這種情況也有辦法,但是不能直接使用交集指令)。同時操作多個key,則不能使用Redis事務.分區使用的粒度是key,不能使用一個非常長的排序key存儲一個數據集(The partitioning granularity is the key, so it is not possible to shard a dataset with a single huge key like a very big sorted set).當使用分區的時候,數據處理會非常複雜,例如爲了備份你必須從不同的Redis實例和主機同時收集RDB / AOF文件。分區時動態擴容或縮容可能非常複雜。Redis集羣在運行時增加或者刪除Redis節點,能做到最大程度對用戶透明地數據再平衡,但其他一些客戶端分區或者代理分區方法則不支持這種特性。然而,有一種預分片的技術也可以較好的解決這個問題。

2.30 Redis持久化數據和緩存怎麼做擴容?

如果Redis被當做緩存使用,使用一致性哈希實現動態擴容縮容。如果Redis被當做一個持久化存儲使用,必須使用固定的keys-to-nodes映射關係,節點的數量一旦確定不能變化。否則的話(即Redis節點需要動態變化的情況),必須使用可以在運行時進行數據再平衡的一套系統,而當前只有Redis集羣可以做到這樣。

2.31 分佈式Redis是前期做還是後期規模上來了再做好?爲什麼?

既然Redis是如此的輕量(單實例只使用1M內存),爲防止以後的擴容,最好的辦法就是一開始就啓動較多實例。即便你只有一臺服務器,你也可以一開始就讓Redis以分佈式的方式運行,使用分區,在同一臺服務器上啓動多個實例。一開始就多設置幾個Redis實例,例如32或者64個實例,對大多數用戶來說這操作起來可能比較麻煩,但是從長久來看做這點犧牲是值得的。這樣的話,當你的數據不斷增長,需要更多的Redis服務器時,你需要做的就是僅僅將Redis實例從一臺服務遷移到另外一臺服務器而已(而不用考慮重新分區的問題)。一旦你添加了另一臺服務器,你需要將你一半的Redis實例從第一臺機器遷移到第二臺機器。

2.32 Twemproxy是什麼?

Twemproxy是Twitter維護的(緩存)代理系統,代理Memcached的ASCII協議和Redis協議。它是單線程程序,使用c語言編寫,運行起來非常快。它是採用Apache 2.0 license的開源軟件。 Twemproxy支持自動分區,如果其代理的其中一個Redis節點不可用時,會自動將該節點排除(這將改變原來的keys-instances的映射關係,所以你應該僅在把Redis當緩存時使用Twemproxy)。 Twemproxy本身不存在單點問題,因爲你可以啓動多個Twemproxy實例,然後讓你的客戶端去連接任意一個Twemproxy實例。 Twemproxy是Redis客戶端和服務器端的一箇中間層,由它來處理分區功能應該不算複雜,並且應該算比較可靠的。

2.33 支持一致性哈希的客戶端有哪些?

Redis-rb、Predis等。

2.34 Redis與其他key-value存儲有什麼不同?

Redis有着更爲複雜的數據結構並且提供對他們的原子性操作,這是一個不同於其他數據庫的進化路徑。Redis的數據類型都是基於基本數據結構的同時對程序員透明,無需進行額外的抽象。Redis運行在內存中但是可以持久化到磁盤,所以在對不同數據集進行高速讀寫時需要權衡內存,應爲數據量不能大於硬件內存。在內存數據庫方面的另一個優點是, 相比在磁盤上相同的複雜的數據結構,在內存中操作起來非常簡單,這樣Redis可以做很多內部複雜性很強的事情。 同時,在磁盤格式方面他們是緊湊的以追加的方式產生的,因爲他們並不需要進行隨機訪問。

2.35 Redis的內存佔用情況怎麼樣?

給你舉個例子: 100萬個鍵值對(鍵是0到999999值是字符串“hello world”)在我的32位的Mac筆記本上 用了100MB。同樣的數據放到一個key裏只需要16MB, 這是因爲鍵值有一個很大的開銷。 在Memcached上執行也是類似的結果,但是相對Redis的開銷要小一點點,因爲Redis會記錄類型信息引用計數等等。當然,大鍵值對時兩者的比例要好很多。64位的系統比32位的需要更多的內存開銷,尤其是鍵值對都較小時,這是因爲64位的系統裏指針佔用了8個字節。 但是,當然,64位系統支持更大的內存,所以爲了運行大型的Redis服務器或多或少的需要使用64位的系統。

2.36 都有哪些辦法可以降低Redis的內存使用情況呢?

如果你使用的是32位的Redis實例,可以好好利用Hash,list,sorted set,set等集合類型數據,因爲通常情況下很多小的Key-Value可以用更緊湊的方式存放到一起。

2.37 查看Redis使用情況及狀態信息用什麼命令?

info

2.38 Redis的內存用完了會發生什麼?

如果達到設置的上限,Redis的寫命令會返回錯誤信息(但是讀命令還可以正常返回。)或者你可以將Redis當緩存來使用配置淘汰機制,當Redis達到內存上限時會沖刷掉舊的內容。

2.39 Redis是單線程的,如何提高多核CPU的利用率?

可以在同一個服務器部署多個Redis的實例,並把他們當作不同的服務器來使用,在某些時候,無論如何一個服務器是不夠的, 所以,如果你想使用多個CPU,你可以考慮一下分片(shard)。

2.40 一個Redis實例最多能存放多少的keys?

List、Set、Sorted Set他們最多能存放多少元素?理論上Redis可以處理多達232的keys,並且在實際中進行了測試,每個實例至少存放了2億5千萬的keys。我們正在測試一些較大的值。任何list、set、和sorted set都可以放232個元素。換句話說,Redis的存儲極限是系統中的可用內存值。

2.41 Redis常見性能問題和解決方案?

(1) Master最好不要做任何持久化工作,如RDB內存快照和AOF日誌文件
(2) 如果數據比較重要,某個Slave開啓AOF備份數據,策略設置爲每秒同步一次
(3) 爲了主從複製的速度和連接的穩定性,Master和Slave最好在同一個局域網內
(4) 儘量避免在壓力很大的主庫上增加從庫
(5) 主從複製不要用圖狀結構,用單向鏈表結構更爲穩定,即:Master <- Slave1 <- Slave2 <- Slave3…這樣的結構方便解決單點故障問題,實現Slave對Master的替換。如果Master掛了,可以立刻啓用Slave1做Master,其他不變。

2.42 Redis提供了哪幾種持久化方式?

RDB持久化方式能夠在指定的時間間隔能對你的數據進行快照存儲.AOF持久化方式記錄每次對服務器寫的操作,當服務器重啓的時候會重新執行這些命令來恢復原始的數據,AOF命令以redis協議追加保存每次寫的操作到文件末尾.Redis還能對AOF文件進行後臺重寫,使得AOF文件的體積不至於過大.如果你只希望你的數據在服務器運行的時候存在,你也可以不使用任何持久化方式.你也可以同時開啓兩種持久化方式, 在這種情況下, 當redis重啓的時候會優先載入AOF文件來恢復原始的數據,因爲在通常情況下AOF文件保存的數據集要比RDB文件保存的數據集要完整.最重要的事情是瞭解RDB和AOF持久化方式的不同,讓我們以RDB持久化方式開始。

2.43 如何選擇合適的持久化方式?

一般來說, 如果想達到足以媲美PostgreSQL的數據安全性, 你應該同時使用兩種持久化功能。如果你非常關心你的數據, 但仍然可以承受數分鐘以內的數據丟失,那麼你可以只使用RDB持久化。有很多用戶都只使用AOF持久化,但並不推薦這種方式:因爲定時生成RDB快照(snapshot)非常便於進行數據庫備份, 並且 RDB 恢復數據集的速度也要比AOF恢復的速度要快,除此之外, 使用RDB還可以避免之前提到的AOF程序的bug。

2.44 修改配置不重啓Redis會實時生效嗎?

針對運行實例,有許多配置選項可以通過 CONFIG SET 命令進行修改,而無需執行任何形式的重啓。 從 Redis 2.2 開始,可以從 AOF 切換到 RDB 的快照持久性或其他方式而不需要重啓 Redis。檢索 ‘CONFIG GET *’ 命令獲取更多信息。但偶爾重新啓動是必須的,如爲升級 Redis 程序到新的版本,或者當你需要修改某些目前 CONFIG 命令還不支持的配置參數的時候。

三、Shiro

3.1 什麼是shiro

Shiro是一個強大易用的java安全框架,提供了認證、授權、加密、會話管理、與web集成、緩存等功能,對於任何一個應用程序,都可以提供全面的安全服務,相比其他安全框架,shiro要簡單的多。

3.2 Shiro的核心概念Subject、SecurityManager、Realm

Subject
主體,代表了當前“用戶”,這個用戶不一定是一個具體的人,與當前應用交互的任何東西都是Subject,如爬蟲、機器人等,即Subject是一個抽象概念;所有Subject都綁定到SecurityManager,與Subject的所有交互都會委託給SecurityManager;可以把Subject認爲是一個門面;SecurityManager纔是實際的執行者。

SecurityManager
安全管理器,即所有與安全有關的操作都會與SecurityManager交互,且它管理着所有Subject;可以看出它是shiro的核心, SecurityManager相當於SpringMVC中的DispatcherServlet前端控制器。

Realm
域,shiro從Realm獲取安全數據(如用戶、角色、權限),Realm是與持久層交互的橋樑,就是說SecurityManager要驗證用戶身份,那麼它需要從Realm獲取相應的用戶進行比較以確定用戶身份是否合法;也需要從Realm得到用戶相應的角色/權限進行驗證用戶是否能進行操作;可以把Realm看成DataSource,即安全數據源。

3.3 Authentication 身份驗證

principals
身份,即主體的標識屬性,可以是任何東西,如用手機號、戶名、郵箱等,唯一即可。

credentials
證明/憑證,即只有主體知道的安全值,如密碼/數字證書等。

身份認證流程

  1. 首先調用Subject.login(token)進行登錄,其會自動委託給SecurityManager,調用之前必須通過SecurityUtils.setSecurityManager()設置;
  2. SecurityManager負責真正的身份驗證邏輯;它會委託給Authenticator進行身份驗證;
  3. Authenticator纔是真正的身份驗證者,是Shiro API中核心的身份認證入口點,此處可以自定義插入自己的實現;
  4. Authenticator可能會委託給相應的AuthenticationStrategy進行多Realm身份驗證,默認ModularRealmAuthenticator會調用AuthenticationStrategy進行多Realm身份驗證;
  5. Authenticator會把相應的token傳入Realm,從Realm獲取身份驗證信息,如果沒有返回/拋出異常表示身份驗證失敗了。此處可以配置多個Realm,將按照相應的順序及策略進行訪問。

Authenticator及AuthenticationStrategy

  1. Authenticator的職責是驗證用戶賬號,是Shiro API中身份驗證核心的入口點。
  2. AuthenticationStrategy 認證策略 ModularRealmAuthenticator默認使用AtLeastOneSuccessfulStrategy策略
    1. FirstSuccessfulStrategy:只要有一個Realm驗證成功即可,只返回第一個Realm身份驗證成功的認證信息,其他的忽略;
    2. AtLeastOneSuccessfulStrategy:只要有一個Realm驗證成功即可,和FirstSuccessfulStrategy不同,返回所有Realm身份驗證成功的認證信息;
    3. AllSuccessfulStrategy:所有Realm驗證成功纔算成功,且返回所有Realm身份驗證成功的認證信息,如果有一個失敗就失敗了。

自定義實現認證時一般繼承AbstractAuthenticationStrategy即可

3.4 Authorization 授權

授權,也叫訪問控制,即在應用中控制誰能訪問哪些資源(如訪問頁面/編輯數據/頁面操作等)。在授權中需瞭解的幾個關鍵對象:主體(Subject)、資源(Resource)、權限(Permission)、角(Role)
授權方式:

  1. 編程式:通過寫if/else授權代碼完成
    Subject subject = SecurityUtils.getSubject();
    If(subject.hasRole(“admin”){
    // 有權限
    }else{
    // 無權限
    }
  2. 註解
    @RequiresRoles(“admin”)
    public void helloWord(){
    // 有權限
    }
  3. Jsp/gsp標籤
    <shiro:hasRole name = “admin”>
    <!—有權限 
    </shiro:hasRole>
    基於資源的訪問控制
    1. 隱式角色:硬編碼的方式(if/else);粗粒度造成的問題:如果有一天不需要了那麼就需要修改相應代碼把所有相關的地方進行刪除;
  4. 顯示角色:規則:資源標識符:操作(user:create,user:update)這種方式叫資源級別的粒度;好處:如果需要修改都是一個資源級別的修改,不會對其他模塊代碼產生影響,粒度小;但實現起來可能稍微複雜點,需要維護“用戶—角色,角色—權限(資源:操作)”之間的關係
    Permission
    字符串通配符權限
    規則:資源標識符 : 操作 : 對象實例ID
    “:”表示資源/操作/實例的分割
    “,”表示操作的分割
    “*”表示任意資源/操作/實例
  5. 單個資源多個權限
    Role=system:user:update,system:user:delete
    等價於role=system:user:update,delete,但是反過來是規則不成立
    代碼判斷
    subject().checkPermissions(“system:user:update,delete”)
  6. 單個資源全部權限:role=sys:user:*/sys:user
  7. 所有資源全部權限:role=*:view;subject.checkPermissions(“user:view”);
  8. 實例級別的權限
    單實多限:role=”user:update,delete:1”;
    subject().checkPermissions(”user:update,delete:1”);
    all實單限:role=”user:auth:”;
    subject().checkPermissions(“user:auth:1”, “user:auth:2”);
    all實all限:role=”user:?”;
    subject().checkPermissions(“user:view:1”, “user:auth:2”);

授權流程:

  1. 首先調用Subject.isPermitted hasRole接口,其會委託給SecurityManager,而SecurityManager接着會委託給Authorizer;
  2. Authorizer是真正的授權者,如果我們調用如isPermitted(“user:view”), 其首先會通過PermissionResolver把字符串轉換成相應的Permission實例;在進行授權之前,其會調用相應的Realm獲取Subject相應的角色/權限用於匹配傳入的角色/權限;
  3. Authorizer會判斷Realm的角色/權限是否和傳入的匹配,如果有多個Realm,會委託給 ModularRealmAuthorizer進行循環判斷,如果匹配如isPermittedhasRole會返回true, 否則返回false表示授權失敗。

3.5 加密

編碼/解碼
Shiro提供了base64和16進制字符串編碼/解碼的API支持,方便一些編碼解碼操作

//編碼
Base64.encodeToString(str.getBytes())
//解碼
Base64.decodeToString(base64Encoded) 

散列算法
常見散列算法如MD5,SHA等

  1. 首先創建一個DfaultHashService,默認使用SHA-512算法;
  2. 可以通過hashAlgorithmName屬性修改算法;
  3. 可以通過privateSalt設置一個私鹽,其在散列時自動與用戶傳入的公鹽混合產生一個新鹽;
  4. 可以通過generatePublicSalt屬性在用戶沒有傳入公鹽的情況下設置是否生成公鹽;
  5. 可以設置randomNumberGenerator用於生成公鹽;
  6. 可以設置hashIterations屬性來修改默認加密迭代次數;
  7. 需要構建一個HashRequest,傳入算法、數據、公鹽、迭代次數。

生成隨機數

SecureRandomNumberGenerator randomNumberGenerator = new SecureRandomNumberGenerator();
randomNumberGenerator.setSeed(159.getBytes());
String hex = randomNumberGenerator.nextBytes().toHex();

加密/解密
提供對稱式加密/解密算法的支持,如AES、Blowfish等
PasswordService/CredentialsMatcher用於提供加密密碼及驗證密碼服務
Shiro默認提供了PasswordService實現DefaultPasswordService;CredentialsMatcher實現PasswordMatcher及HashedCredentialsMatcher(更強大)
HashedCredentialsMatcher實現密碼驗證服務
Shiro提供了CredentialsMatcher的散列實現HashedCredentialsMatcher,和PasswordMatcher不同的是,它只是用於密碼驗證,且可以提供自己的鹽,而不是隨機生成鹽,且生成密碼散列值的算法需要自己寫,因爲能提供自己的鹽;

3.5 Realm 域

定義Realm(自定義Realm繼承AuthorizingRealm即可)

  1. UserRealm父類AuthorizingRealm將獲取Subject相關信息分成兩步:獲取身份驗證信息(doGetAuthenticationInfo)及授權信息(doGetAuthorizationInfo)
  2. doGetAuthenticationInfo獲取身份驗證相關信息:首先根據傳入的用戶名獲取User信息;如果user爲空,那麼拋出沒找到賬號異常UnknownAccountExecption;如果user找到但卻被鎖定了拋出鎖定異常LockedAccountException;最後生成AuthenticationInfo信息,交給間接父類AuthenticatingRealm使用CredentialsMatcher進行判斷密碼是否匹配,如果不匹配將拋出密碼錯誤異常信息IncorrectCredentialsException;如果密碼重試次數太多將拋出超出重試次數異常ExcessiveAttemptsException;在組裝SimpleAuthenticationInfo信息時,需要傳入:身份信息(用戶名)、憑據(密文密碼)、鹽(username+salt),CredentialsMatcher使用鹽加密傳入的明文密碼和此處的密文密碼進行匹配。
  3. doGetAuthorizationInfo獲取授權信息:PrincipalCollection是一個身份集合,因爲只用到了一個Realm,所以直接調用getPrimaryPrincipal得到之前傳入的用戶名即可;然後根據用戶名調用UserService接口獲取角色及權限信息。

AuthenticationInfo的兩個作用

  1. 如果Realm是AuthenticatingRealm子類,則提供給AuthenticatingRealm內部使用的CredentialsMatcher進行憑據驗證;(如果沒有繼承它需要在自己的Realm中實現驗證);
  2. 提供給SecurityManager來創建Subject(提供身份信息);

3.6 攔截器

基於表單登錄攔截器
onPreHandle主要流程:

  1. 首先判斷是否已經登錄過了,如果已經登錄過了繼續攔截器鏈即可;
  2. 如果沒有登錄,看看是否是登錄請求,如果是get方法的登錄頁面請求,則繼續攔截器鏈(到請求頁面),否則如果是get方法的其他頁面請求則保存當前請求並重定向到登錄頁面;
  3. 如果是post方法的登錄頁面表單提交請求,則收集用戶名/密碼登錄即可,如果失敗了保存錯誤消息到“shiroLoginFailure”並返回到登錄頁面;
  4. 如果登錄成功了,且之前有保存的請求,則重定向到之前的這個請求,否則到默認的成功頁面。

任意角色授權攔截器
流程:

  1. 首先判斷用戶有沒有任意角色,如果沒有返回false,將到onAccessDenied進行處理;
  2. 如果用戶沒有角色,接着判斷用戶有沒有登錄,如果沒有登錄先重定向到登錄;
  3. 如果用戶沒有角色且設置了未授權頁面(unauthorizedUrl),那麼重定向到未授權頁面;否則直接返回401未授權錯誤碼。

默認攔截器
身份驗證相關的
authc 基於表單的攔截器,即驗證成功之後才能訪問 /=authc
authcBasic Basic HTTP身份驗證攔截器,主要屬性:applicationName
logout 退出 /logout=logout
user 用戶攔截器 /=user
anon 匿名攔截器,一般用於靜態資源過濾 /static/=anon

授權相關的
roles 角色授權攔截器,主要屬性:loginUrl,unauthorizedUrl /admin/=roles[admin]
perms 權限授權攔截器 /user/**=perms[“user:create”]
port 端口攔截器,主要屬性: port(80) /test=port[80]
rest rest風格攔截器 /users=rest[user],會自動拼接出“user:read,user:create,user:update,user:delete”
ssl ssl攔截器,只有請求協議是https才能通過

3.7 Session Manager 會話管理

Session

所謂session,即用戶訪問應用時保持的連接關係,在多次交互中應用能夠識別出當前訪問的用戶是誰,且可以在多次交互中保存一些數據。

Subject subject = SecurityUtils.getSubject();
Session session = subject.getSession();
session.getId(); // 獲取當前session的唯一標識
session.getHost(); // 獲取當前Subject的主機地址,該地址是通過HostAuthenticationToken.getHost()提供的
session.getTimeOut(); // 獲取超時時間
session.setTimeOut(); // 設置超時時間(不設置默認是全局過期時間)
session.touch(); // 更新最後訪問時間
session.stop(); // 銷燬session,當Subject.logout()時會自動調用stop方法來銷燬會話。如果在web中,調用javax.servlet.http.HttpSession.invalidate()也會自動調用shiro session.top方法進行銷燬shiro的會話
session.setAttribute(“key”,123); // 設置session屬性
session.getAttribute(“key”); // 獲取session屬性
session.removeAttribute(“key”); // 刪除屬性

注:Shiro提供的會話可以用於javaSE/javaEE環境,不依賴於任何底層容器,可以獨立使用,是完整的會話模塊。

Session manager 會話管理器

會話管理器管理着應用中所有Subject的會話的創建、維護、刪除、失效、驗證等工作。是Shiro的核心組件,頂層組件SecurityManager直接繼承了SessionManager,且提供了SessionSecurityManager實現直接把會話管理委託給相應的SessionManager、DefaultSecurityManager及DefaultWebSecurityManager 默認SecurityManager都繼承了SessionSecurityManager。

Shiro提供了三個默認實現

  1. DefaultSessionManager:DefaultSecurityManager使用的默認實現,用於JavaSE環境;
  2. ServletContainerSessionManager: DefaultWebSecurityManager使用的默認實現,用於Web環境,其直接使用Servlet容器的會話;
  3. DefaultWebSessionManager:用於Web環境的實現,可以替代ServletContainerSessionManager,自己維護着會話,直接廢棄了Servlet容器的會話管理。

3.8 Shiro註解

  1. @RequiresAuthentication : 表示當前Subject已經通過login進行了身份驗證;即 Subject.isAuthenticated() 返回 true
  2. @RequiresUser : 表示當前Subject 已經身份驗證或者通過記住我登錄的
  3. @RequiresGuest : 表示當前Subject沒有身份驗證或通過記住我登陸過,即是遊客身份
  4. @RequiresRoles(value = { “admin”, “user” }, logical = Logical.AND) : 表示當前 Subject 需要角色 admin和user
  5. @RequiresPermissions(value = { “user:a”, “user:b” }, logical = Logical.OR) : 表示當前 Subject 需要權限 user:a 或 user:b

3.9 shiro的優點

  1. 簡單的身份驗證,支持多種數據源
  2. 對角色的簡單授權,支持細粒度的授權(方法)
  3. 支持一級緩存,以提升應用程序的性能
  4. 內置基於POJO的企業會話管理,適用於web及非web環境
  5. 非常簡單的API加密
  6. 不跟任何框架綁定,可以獨立運行

四 集合

4.1 ArrayList、LinkedList、Vector

都繼承Collection接口

ArrayList
有序可重複,底層是數組結構,查詢快,增刪慢

LinkedList
有序可重複,底層是鏈表結構,查詢慢,增刪快

Vector
Vector使用了synchronized方法-線程安全,性能上比ArrayList差一點

4.2 Set

和List一樣,繼承Collection接口,不同的是Set集合是不可重複的(不一定是無序的),並且最多隻能允許一個null值。Set常見的實現類有:HashSet、TreeSet和LinkedHashSet。

HashSet
HashSet是一個沒有重複元素的集合。它是由HashMap實現的,不能保證元素的順序,重要的是HashSet允許使用null元素。
HashSet是非同步的。如果多個線程同時訪問一個hashset,而其中至少一個線程修改了該hashset,那麼它必須保持外部同步。

TreeSet
是一個有序的集合,是一個set集合。繼承abstractset,實現了navigableset,cloneable,serializable接口。

LinkedHashSet
是一個有序集合

4.3 Map

HashMap、HashTable都繼承Map接口

HashMap
線程不安全,使用HashMap要注意避免集合的擴容,它會很耗性能,根據元素的數量給它一個初始大小的值(HashMap是數組和鏈表組成的,默認大小爲16,當hashmap中的元素個數超過數組大小*loadFactor(默認值爲0.75)時就會把數組的大小擴展爲原來的兩倍大小,然後重新計算每個元素在數組中的位置)。HashMap的鍵值都可以爲NULL,HashTable不行。
HashMap是數組+鏈表+紅黑樹(JDK1.8增加了紅黑樹部分)實現的,採用哈希表來存儲的;
HashTable
線程安全,key、value不可以爲null;

LinkedHashMap
按照添加順序存儲元素(按自然順序存儲元素則使用TreeMap,按照自定義順序存儲元素也使用TreeMap(Comparetor c))

有沒有有順序的 Map 實現類? 如果有, 他們是怎麼保證有序的?
TreeMap和LinkedHashMap是有序的(TreeMap默認升序,LinkedHashMap則記錄了插入順序)。

五 Oracle數據庫

5.1 數據庫的三大範式

  1. 第一範式:原子件,要求每一列的值不能再拆分了。

  2. 第二範式: 一張表只描述一個實體(若列中有冗餘數據,則不滿足)

  3. 第三範式: 所有列與主鍵值直接相關。

5.2 事務的特性(ACID)

  1. 原子性(Atomic): 事務中的各項操作,要麼全做要麼全不做,任何一項操作的失敗都會導致整個事務的失敗。

  2. 一致性(Consistent): 事務前後數據的完整性必須保持一致。

  3. 隔離性(Isolated):多個用戶併發訪問數據庫時,數據庫爲每一個用戶開啓的事務,不能被其他事務的操作數據所幹擾,多個併發事務之間要相互隔離。

  4. 持久性(Durable):持久性是指一個事務一旦被提交,它對數據庫中數據的改變就是永久性的,接下來即使數據庫發生故障也不應該對其有任何影響。

5.3 oracle中 dml、ddl、dcl

Dml 數據操做語言,如select、update、delete,insert
Ddl 數據定義語言,如create table 、drop table、Truncate 等等
Dcl 數據控制語言, 如 commit、 rollback、grant、 invoke等

5.4 索引

建索引的原則

  1. 索引字段建議建立NOT NULL約束
  2. 經常與其他表進行連接的表,在連接字段上應該建立索引;
  3. 經常出現在Where子句中的字段且過濾性很強的,特別是大表的字段,應該建立索引;
  4. 可選擇性高的關鍵字 ,應該建立索引;
  5. 不要將那些頻繁修改的列作爲索引列;

索引缺點

  1. 創建索引和維護索引要耗費時間,這種時間隨着數據量的增加而增加
  2. 索引需要佔物理空間,除了數據表佔數據空間之外,每一個索引還要佔一定的物理空間
  3. 當對錶中的數據進行增加、刪除和修改的時候,索引也要動態的維護,降低了數據的維護速度
  4. 索引創建在表上,不能創建在視圖上

5.5 oracle中的經常使用到得函數

Length 長度、 lower 小寫、upper 大寫, to_date 轉化日期, to_char轉化字符
Ltrim 去左邊空格、 rtrim去右邊空格,substr取字串,add_month增加或者減掉月份、to_number轉變爲數字

5.6 Oracle是怎樣分頁的

Oracle中使用rownum來進行分頁, 這個是效率最好的分頁方法,hibernate也是使用rownum來進行oralce分頁的

select * from
  ( select rownum r,a from tabName where rownum <= 20 )
where r > 10

5.7 truncate和delete命令的區別

  1. Truncate 和delete都可以將數據實體刪掉,truncate 的操作並不記錄到 rollback日誌,所以操作速度較快,但同時這個數據不能恢復
  2. Delete操作不騰出表空間的空間
  3. Truncate 不能對視圖等進行刪除
  4. Truncate是數據定義語言(DDL),而delete是數據操縱語言(DML)

5.8 什麼是死鎖,如何解決Oracle中的死鎖

死鎖

就是存在加了鎖而沒有解鎖,可能是使用鎖沒有提交或者沒有回滾事務;如果是表級鎖則不能操作表,客戶端處於等在狀態,如果是行級鎖則不能操作鎖定行;

解決死鎖

  1. 查找出被鎖的表
select b.owner,b.object_name,a.session_id,a.locked_mode
from v$locked_object a,dba_objects b
where b.object_id = a.object_id;

select b.username,b.sid,b.serial#,logon_time
from v$locked_object a,v$session b
where a.session_id = b.sid order by b.logon_time;
  1. 殺進程中的會話
alter system kill session "sid,serial#";

5.9 四種隔離級別

  1. 讀未提交(Read uncommitted):
    這種事務隔離級別下,select語句不加鎖。
    此時,可能讀取到不一致的數據,即“讀髒 ”。這是併發最高,一致性最差的隔離級別。

  2. 讀已提交(Read committed):
    可避免 髒讀 的發生。
    在互聯網大數據量,高併發量的場景下,幾乎 不會使用 上述兩種隔離級別。

  3. 可重複讀(Repeatable read):
    MySql默認隔離級別。
    可避免髒讀 、不可避免重複讀的發生。

  4. 串行化(Serializable ):
    可避免 髒讀、不可重複讀、幻讀 的發生。

以上四種隔離級別最高的是 Serializable 級別,最低的是 Read uncommitted 級別,當然級別越高,執行效率就越低。像 Serializable 這樣的級別,就是以 鎖表 的方式(類似於Java多線程中的鎖)使得其他的線程只能在鎖外等待,所以平時選用何種隔離級別應該根據實際情況。在MySQL數據庫中默認的隔離級別爲Repeatable read (可重複讀) 。

在MySQL數據庫中,支持上面四種隔離級別,默認的爲Repeatable read (可重複讀) ;而在 Oracle數據庫 中,只支持Serializable (串行化) 級別和 Read committed (讀已提交) 這兩種級別,其中默認的爲 Read committed(讀已提交) 級別。

六 SpringBoot

6.1 什麼是 SpringBoot

SpringBoot 是 Spring 開源組織下的子項目,是 Spring 組件一站式解決方案,主要是簡化了使用 Spring 的難度,簡省了繁重的配置,提供了各種啓動器,開發者能快速上手。Spring Boot 與傳統項目最大的區別是,傳統項目都是打成 WAR 包部署到服務器上面,需要額外的 Servlet 容器, 而 Spring Boot 則可以直接打成 jar包,並內置集成了 Servlet 容器,通過命令 java -jar xx.jar 則可以直接運行,不需要獨立的 Servlet 容器。

6.2 SpringBoot優點

一、獨立運行

Spring Boot而且內嵌了各種servlet容器,Tomcat、Jetty等,現在不再需要打成war包部署到容器中,Spring Boot只要打成一個可執行的jar包就能獨立運行,所有的依賴包都在一個jar包內。

二、簡化配置

spring-boot-starter-web啓動器自動依賴其他組件,簡少了maven的配置。相對於SpringMVC,SpringBoot無需XML配置文件就能完成所有配置工作

6.3 SpringBoot 的核心配置文件有哪幾個?它們的區別是什麼?

Spring Boot 中有以下兩種配置文件

  • bootstrap (.yml 或者 .properties)
  • application (.yml 或者 .properties)

bootstrap與application 的區別

Spring Cloud 構建於 Spring Boot 之上,在 Spring Boot 中有兩種上下文,一種是 bootstrap,,另外一種是 application,,bootstrap 是應用程序的父上下文,也就是說 bootstrap 加載優先於 applicaton。bootstrap 主要用於從額外的資源來加載配置信息,還可以在本地外部配置文件中解密屬性。這兩個上下文共用一個環境,它是任何Spring應用程序的外部屬性的來源。bootstrap 裏面的屬性會優先加載,它們默認也不能被本地相同配置覆蓋。

所以,對比 application 配置文件,bootstrap 配置文件具有以下幾個特性:

  1. boostrap 由父 ApplicationContext 加載,比 applicaton 優先加載

  2. boostrap 裏面的屬性不能被覆蓋

bootstrap與application 的應用場景

application 配置文件這個容易理解,主要用於 Spring Boot 項目的自動化配置。

bootstrap 配置文件有以下幾個應用場景:

  1. 使用 Spring Cloud Config 配置中心時,這時需要在 bootstrap 配置文件中添加連接到配置中心的配置屬性來加載外部配置中心的配置信息;
  2. 一些固定的不能被覆蓋的屬性
  3. 一些加密/解密的場景。

6.4 Spring Boot 的核心註解是哪個?它主要由哪幾個註解組成的?

啓動類上面的註解是@SpringBootApplication,它也是 SpringBoot 的核心註解,其主要組合包含以下 3 個註解:

  1. @SpringBootConfiguration:組合了 @Configuration 註解,實現配置文件的功能。

  2. @EnableAutoConfiguration:打開自動配置的功能,也可以關閉某個自動配置的選項,如關閉數據源自動配置功能: @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })。

  3. @ComponentScan:Spring組件掃描。

6.5 開啓 Spring Boot 特性有哪幾種方式?

兩種方式
方式一

繼承spring-boot-starter-parent項目

<parent>  
	<groupId>org.springframework.boot</groupId>   
	<artifactId>spring-boot-starter-parent</artifactId>
	<version>1.5.6.RELEASE</version>
</parent>

方式二
導入spring-boot-dependencies項目依賴

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>1.5.6.RELEASE</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    <dependencies>
</dependencyManagement>

6.6 如何在 Spring Boot 啓動的時候運行一些特定的代碼?

如果你想在Spring Boot啓動的時候運行一些特定的代碼,你可以實現接口ApplicationRunner或者CommandLineRunner,這兩個接口實現方式一樣,它們都只提供了一個run方法。

使用方式:

package com.qf.service.impl;

import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

/**
 * @Author: sgw
 * @Date 2020/3/8 16:12
 * @Description: TODO
 **/
@Component
public class MyBean implements CommandLineRunner {

    @Override
    public void run(String... args) throws Exception {
        System.out.println("啓動項目的時候執行這裏的代碼");
    }
}

啓動順序
如果啓動的時候有多個ApplicationRunner和CommandLineRunner,想控制它們的啓動順序,可以實現 org.springframework.core.Ordered接口或者使用 org.springframework.core.annotation.Order註解。

6.7 讀取配置文件

讀取application文件

在application.yml或者properties文件中添加:

info.address=China

方式一、在程序裏的屬性上加註解讀取

@Value("${info.address}")
private String addr;

方式二、在類上加註解,添加前綴後,將屬性名稱對應上即可

@Component
@ConfigurationProperties(prefix="info")
public class MyTest{
   private String addr;
   //get/set方法......
}

讀取指定文件

資源目錄下建立config/db-config.properties:

db.username=root
db.password=123456

方式一、@PropertySource+@Value註解讀取方式:

package com.qf.service.impl;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;

/**
 * @Author: sgw
 * @Date 2020/3/8 16:12
 * @Description: TODO
 **/
@Component
@PropertySource(value = {"config/db-config.properties"})
public class MyBean {
    @Value("${db.username}")
    private String userName;
    @Value("${db.password}")
    private String passWord;

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getPassWord() {
        return passWord;
    }

    public void setPassWord(String passWord) {
        this.passWord = passWord;
    }
}

注意:@PropertySource不支持yml文件讀取。

方式二、@PropertySource+@ConfigurationProperties註解讀取方式:

在這裏插入圖片描述

6.8 SpringBoot 支持哪些日誌框架?推薦和默認的日誌框架是哪個?

Spring Boot支持Java Util Logging,Log4j2,Lockback作爲日誌框架,如果你使用starters啓動器,Spring Boot將使用Logback作爲默認日誌框架。無論使用哪種日誌框架,Spring Boot都支持配置將日誌輸出到控制檯或者文件中。

spring-boot-starter啓動器包含spring-boot-starter-logging啓動器並集成了slf4j日誌抽象及Logback日誌框架。

自定義日誌文件

根據不同的日誌框架,默認加載的日誌配置文件的文件名,放在資源根目錄下,其他的目錄及文件名不能被加載
在這裏插入圖片描述
既然默認自帶了Logback框架,Logback也是最優秀的日誌框架,往資源目錄下創建一個logback-spring.xml即可。
強烈推薦使用logback-spring.xml作爲文件名,因爲logback.xml加載太早。

6.9 SpringBoot實現熱部署

添加依賴

<dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-devtools</artifactId>
     <optional>true</optional>
</dependency>

在配置文件裏配置熱部署
111
IDEA需要設置:
在這裏插入圖片描述
ctrl+Alt+shift+/
在這裏插入圖片描述
在這裏插入圖片描述

七 java基礎

7.1 String, Stringbuffer, StringBuilder 的區別

StringBuffer:線程安全,效率低;
StringBuilder:線程不安全,效率高;

字符串比較

String x = "string";
String y = "string";
String z = new String("string");
System.out.println(x==y); // true,引用相同
System.out.println(x==z); // false,==:string比較引用,開闢了新的堆內存空間,所以false
System.out.println(x.equals(y)); // true,equals:string:比較值,相同
System.out.println(x.equals(z)); // true,equals:string比較值,相同

字符串反轉

StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append("abcdefg");
System.out.println(stringBuffer.reverse()); // gfedcba

字符串常用方法

indexOf():返回指定字符的索引。
charAt():返回指定索引處的字符。
replace():字符串替換。
trim():去除字符串兩端空白。
split():分割字符串,返回一個分割後的字符串數組。
getBytes():返回字符串的 byte 類型數組。
length():返回字符串長度。
toLowerCase():將字符串轉成小寫字母。
toUpperCase():將字符串轉成大寫字符。
substring():截取字符串。
equals():字符串比較。

7.2、類加載順序

  1. 父類靜態代變量
  2. 父類靜態代碼塊
  3. 子類靜態變量
  4. 子類靜態代碼塊
  5. 父類非靜態變量(父類實例成員變量)
  6. 父類構造函數
  7. 子類非靜態變量(子類實例成員變量)
  8. 子類構造函數

7.3 IO流

IO流種類

按功能來分:輸入流(input)、輸出流(output)。
按類型來分:字節流和字符流。

字節流和字符流的區別是:字節流按 8 位傳輸以字節爲單位輸入輸出數據,字符流按 16 位傳輸以字符爲單位輸入輸出數據。

Files的常用方法都有哪些

Files.exists():檢測文件路徑是否存在。
Files.createFile():創建文件。
Files.createDirectory():創建文件夾。
Files.delete():刪除一個文件或目錄。
Files.copy():複製文件。
Files.move():移動文件。
Files.size():查看文件個數。
Files.read():讀取文件。
Files.write():寫入文件。

7.4 get與post區別

  • GET把參數包含在URL中,POST通過request body傳遞參數
  • GET比POST更不安全,因爲參數直接暴露在URL上,所以不能用來傳遞敏感信息。
  • GET在瀏覽器回退時是無害的,而POST會再次提交請求。
  • GET產生的URL地址可以被Bookmark,而POST不可以。
  • GET請求會被瀏覽器主動cache,而POST不會,除非手動設置。
  • GET請求只能進行url編碼,而POST支持多種編碼方式。
  • GET請求參數會被完整保留在瀏覽器歷史記錄裏,而POST中的參數不會被保留。
  • GET請求在URL中傳送的參數是有長度限制的,而POST麼有。
  • 對參數的數據類型,GET只接受ASCII字符,而POST沒有限制。
  • 傳文件、form表單等必須是post

7.5 異常

1、Exception和Error的區別

Exception和Error都繼承自Throwable,在Java中只有Throwable類型的實例纔可以被拋出或捕獲。
Error指正常情況下不太可能出現的情況,絕大部分的Error或導致程序崩潰,處於非正常的不可恢復的狀態,如OutOfMemoryError、StackOverflowError。是程序中不應該試圖捕獲的嚴重問題。
Exception是程序正常運行中可以預料的意外情況,可以捕獲並處理。

2.運行時異常和一般異常的區別
受檢查異常(一般異常):在編譯時被強制檢查的異常。在方法的聲明中聲明的異常。
(舉例:ClassNotFoundException、IOException)
不受檢查異常(運行時異常,都繼承自RuntimeException):不受檢查異常通常是在編碼中可以避免的邏輯錯誤,根據需求來判斷如何處理,不需要再編譯期強制要求。

3.寫出幾種常見的運行時異常(考察編程經驗)
運行時異常RuntimeException是所有不受檢查異常的基類。
NullPointerException、ClassCastException、NumberFormatException、IndexOutOfBoundsException。

7.6 servlet生命週期

自定義servlet,繼承HttpServlet即可,重寫init、doGet、doPost、destory方法。servlet是單例的,也就是說在整個容器裏只有一個servlet對象;

在web.xml裏將servlet與請求地址對應起來:
在這裏插入圖片描述
自定義Servlet:
在這裏插入圖片描述

servlet生命週期

  1. Web容器加載Servlet類並實例化(默認延遲,只加載一次)
  2. 運行init方法進行初始化(只加載一次)
  3. 用戶請求該servlet,請求到達服務器時,運行其service方法(每請求一次就運行一次,service是父類裏的方法,不需要我們自己寫)
  4. service運行與請求對應的doGet/doPost/delete/put方法(每請求一次就運行一次)
  5. 銷燬實例時調用destory方法(只執行一次)

上邊的步驟:1-2是初始化階段,3-4是使用階段,5是銷燬階段,其中第一步延遲加載,指的是在第一次訪問的時候才加載,可以在web.xml裏配置爲啓動容器時就加載,如下
在這裏插入圖片描述

7.7 轉發(forward)與重定向(redirect)

  1. 轉發是容器內部控制的跳轉,服務器直接訪問目標地址,把目標地址響應的數據讀取出來,直接發送給瀏覽器,瀏覽器是不知道請求從哪裏來的,瀏覽器地址不變
  2. 重定向是服務器接收請求後,返回一個狀態碼給瀏覽器,瀏覽器去請求新的地址,地址欄會變化
  3. 轉發效率高,儘量使用轉發,但是轉發不能跳轉到其他服務器上,重定向可以跳轉到其他服務器;

7.8 數據庫連接池工作機制

連接池的作用

  1. 打開關閉數據庫連接非常耗時,頻繁開關連接的話就會消耗系統資源,連接池可以控制併發數量

  2. 可以控制併發數量(比如應用在隊列裏排隊,拿到數據庫連接後纔可以使用)

連接池工作機制

  1. 服務器在啓動的時候會建立一定數量的連接池,並一直維持不少於此數目的連接池;

  2. 客戶端程序連接時,池驅動程序會返回一個爲使用的池連接並將其標記爲忙;

  3. 如果當前沒有空閒連接,則池驅動程序會新建一定數量的連接,新建連接具體的數量由配置決定,但是不會超過最大連接數;

  4. 當使用的池連接調用完之後,池驅動程序就會將此鏈接標記爲空閒,其他調用者就可以使用這個連接了;

7.9 TCP三次握手

先介紹幾個簡單概念

主機A向主機B發送數據的同時,主機A也可以接收主機B發過來的數據;

在TCP報文包裏,有六個標誌位 ,這裏了介紹其中的兩個:SYN包與ACK包;

SYN包:請求建立連接的數據包,SYN=1,則表示要建立連接;

ACK包:迴應數據包(用來做迴應的),表示接收到了對方的某個數據包,僅當ACK=1時,確認號字段纔有效;

seq序列號:用來標記數據包的順序;

ack確認號:表示序號爲確認號減去1的數據包及其以前的所有數據包已經正確接收,也就是說他相當於下一個準備接收的字節的序號 (如果ack確認號是101,則表示前100個都已經收到了);

當我們發一個SYN=1的包時,會得到一個ACK=1的包;

三次握手

第一次握手:建立連接時,客戶端發送數據包,標誌位SYN=1,隨機seq=x到服務器(x代表隨機生成的數)

第二次握手:服務器收到SYN=1的包,知道客戶端要建立連接,返回SYN=1和ACK=1,ack=x+1,和隨機seq=y (y代表隨機生成的數)

第三次握手:客戶端厚道服務器的SYN+ACK包,向服務器發送確認包ACK=1,ack=y+1

7.10 session與cookie

  1. session存在服務器裏,客戶端不知道其中的信息;cookie存在客戶端,服務器能夠知道其中的信息
  2. session中保存的是對象,cookie裏保存的是字符串
  3. session不能區分路徑,同一個用戶在訪問同一個網站期間,所有的session在任何一個地方都可以讀取到;而cookie如果設置了路徑參數,那麼同一個網站中不同路徑下的cookie是互相訪問不到的;
  4. session需要藉助cookie才能正常生效,如果客戶端禁止cookie的話,session將失效;
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章