數據庫相關------一些面試題


1.列舉常見的關系型數據庫和非關系型都有那些、它們的區別?

  關系型數據庫:Oracle、DB2、Microsoft SQL Server、Microsoft Access、MySQL

  非關系型數據庫:NoSql、Cloudant、MongoDB、Redis、HBase

  兩者的區別:

  關系型數據庫 非關系型數據庫
特性

  1、關系型數據庫,是指采用了關系模型來組織數據的數據庫;  
  2、關系型數據庫的最大特點就是事務的一致性;
  3、簡單來說,關系模型指的就是二維表格模型,而一個關系型數據庫就是由二維表及其之間的聯系所組              成的一個數據組織。

  1、使用鍵值對存儲數據;
  2、分布式
  3、一般支持ACID(原子性(Atomicity)、一致性(Consistency)、隔離性(Isolation)和持續性(Durability))特性;
  4、非關系型數據庫嚴格上不是一種數據庫,應該是一種數據結構化存儲方法的集合。
優點   1、容易理解:二維表結構是非常貼近邏輯世界一個概念,關系模型相對網狀、層次等其                                   他模型來說更容易理解;
  2、使用方便:通用的SQL語言使得操作關系型數據庫非常方便;
  3、易於維護:豐富的完整性(實體完整性、參照完整性和用戶定義的完整性)大大減低了                                  數據冗余和數據不一致的概率;
  4、支持SQL,可用於復雜的查詢。
  1、無需經過sql層的解析,讀寫性能很高
  2、基於鍵值對,數據沒有耦合性,容易擴展
  3、存儲數據的格式:nosql的存儲格式是key,value形式、文檔形式、圖片形式等等,文檔形式、圖片形式等等而                   關系型數據庫則只支持基礎類型。
缺點   1、為了維護一致性所付出的巨大代價就是其讀寫性能比較差
  2、固定的表結構;
  3、高並發讀寫需求;
  4、海量數據的高效率讀寫;
   1、不提供sql支持,學習和使用成本較高;
   2、無事務處理,附加功能和報表等支持也不好;

2.MySQL常見數據庫引擎及比較?

MySQL支持數個存儲引擎作為對不同表的類型的處理器。MySQL存儲引擎包括處理事務安全表的引擎和處理非事務安全表的引擎:

MyISAM管理非事務表。它提供高速存儲和檢索,以及全文搜索能力。MyISAM在所有MySQL配置里被支持,它是默認的存儲引擎,除非你配置MySQL默認使用另外一個引擎。

MEMORY存儲引擎提供“內存中”表。MERGE存儲引擎允許集合將被處理同樣的MyISAM表作為一個單獨的表。就像MyISAM一樣,MEMORY和MERGE存儲引擎處理非事務表,這兩個引擎也都被默認包含在MySQL中。

3.簡述數據三大范式?

  數據庫設計對數據的存儲性能,還有開發人員對數據的操作都有莫大的關系。所以建立科學的,規范的的數據庫是需要滿足一些規范的來優化數據數據存儲方式。在關系型數據庫中這些規范就可以稱為范式。

  第一范式:當關系模式R的所有屬性都不能在分解為更基本的數據單位時,稱R是滿足第一范式的,簡記為1NF。滿足第一范式是關系模式規范化的最低要求,否則,將有很多基本操作在這樣的關系模式中實現不了。

  第二范式:如果關系模式R滿足第一范式,並且R得所有非主屬性都完全依賴於R的每一個候選關鍵屬性,稱R滿足第二范式,簡記為2NF。

  第三范式設R是一個滿足第一范式條件的關系模式,X是R的任意屬性集,如果X非傳遞依賴於R的任意一個候選關鍵字,稱R滿足第三范式,簡記為3NF.

簡答題:

  第一范式(確保每列保持原子性)即每列不再需要拆分
  第二范式(確保表中的每列都和主鍵相關)
  第三范式(確保每列都和主鍵列直接相關,而不是間接相關)

4.一條 SQL 語句執行的很慢的原因有哪些?

可以分兩種情況回答。

  (1).大多數情況是正常的,只是偶爾會出現很慢的情況:

    -- 數據庫在刷新臟頁,例如redo log中寫滿了需要同步到磁盤。

      ps:往數據庫插入或更新一條數據,數據庫會在內存中把對應字段更新,但不會馬上同步持久化到磁盤中,而是寫入redo log中,空閑時再將數據同步到磁盤

    -- 執行的時候,遇到了鎖,表鎖或者是行鎖。

  (2).在數據量不變的情況下,這條SQL語句一直以來都執行的很慢:

    -- 沒有用上索引,例如該字段沒有索引,由於對字段進行運算、函數操作導致無法用索引。

    -- 數據庫選錯了索引。

5.講講MYSQL事務,說說ACID是什么?

什么是事務?

  事務是由一步或幾步數據庫操作序列組成邏輯執行單元,這系列操作要么全部執行要么全部放棄執行。程序和事務是兩個不同的概念。

  一般而言:一段程序中可能包含多個事務。

  事務具有四大特性:原子性(Atomicity)、一致性(Consistency)、隔離性(Isolation)和持續性(Durability)。簡稱ACID。

           (1)原子性:事務是應用中最小的執行單位,就如原子是自然界最小顆粒,具有不可再分的特征一樣。事務是應用中不可再分的最小邏輯執行體。

           (2)一致性:事務執行的結果,必須使數據庫從一個一致性狀態,變到另一個一致性狀態。當數據庫中只包含事務成功提交的結果時,數據庫處於一致性狀態。一致性是通過原子性來保證的。

           (3)隔離性:各個事務的執行互不干擾,任意一個事務的內部操作對其他並發的事務,都是隔離的。也就是說:並發執行的事務之間不能看到對方的中間狀態,並發執行的事務之間不能相互影響。

           (4)持續性:持續性也稱為持久性,指事務一旦提交,對數據所做的任何改變,都要記錄到永久存儲器中,通常是保存進物理數據庫。

MYSQL的事務處理主要有兩種方法:

  1.用begin,rollback,commit來實現
    begin開始一個事務
    rollback事務回滾
       commit 事務確認
  2.直接用set來改變mysql的自動提交模式
          mysql默認是自動提交的,也就是你提交一個query,就直接執行!可以通過
          set autocommit = 0 禁止自動提交
          set autocommit = 1 開啟自動提交

 

6.簡述簡述觸發器、函數、視圖、存儲過程?

1、視圖

  視圖只是一種邏輯對象,是一種虛擬表,它並不是物理對象,因為視圖不占物理存儲空間,在視圖中被查詢的表稱為視圖的基表,大多數的select語句都可以用在創建視圖中(說白了,視圖就是一種虛擬表,就像是一張電子照片)

  優點:集中用戶使用的數據,掩碼數據的復雜性,簡化權限管理以及為向其他應用程序輸出而重新組織數據等

2、觸發器

  (1)觸發器是一個特殊的存儲過程,它是MySQL在insert、update、delete的時候自動執行的代碼塊。

  (2)觸發器必須定義在特定的表上。

  (3)自動執行,不能直接調用,

3、函數

  它跟php或js中的函數幾乎一樣:需要先定義,然后調用。

  只是規定,這個函數,必須要返回數據——要有返回值

4、存儲過程

  存儲過程(procedure),概念類似於函數,就是把一段代碼封裝起來,當要執行這一段代碼的時候,可以通過調用該存儲過程來實現。

  在封裝的語句體里面,可以同if/else ,case,while等控制結構,可以進行sql編程,查看現有的存儲過程。

7.如何基於數據庫實現商城商品計數器?

  見下題解;

8.MySQL索引種類。

單列:

  普通索引:加速查找
  唯一索引:加速查找 + 約束:不能重復(只能有一個空,不然就重復了)
       主鍵(primay key):加速查找 + 約束:不能重復 + 不能為空
多列:
  聯合索引(多個列創建索引)-----> 相當於單列的普通索引
  聯合唯一索引 -----> 相當於單列的唯一索引
  ps:聯合索引的特點:遵循最左前綴的規則
其他:
  合並索引,利用多個單例索引查詢;(例如在數據庫查用戶名和密碼,分別給用戶名和密碼建立索引)
  覆蓋索引,在索引表中就能將想要的數據查詢到;

9.索引在什么情況下遵循最左前綴的規則?

答:聯合索引

索引的最左前綴原理:
  通常我們在建立聯合索引的時候,也就是對多個字段建立索引,相信建立過索引的同學們會發現,無論是oralce還是mysql都會讓我們選擇索引的順序,比如我們想在a,b,c三個字段上建立一個聯合索引,我們可以選擇自己想要的優先級,a、b、c,或者是b、a、c 或者是c、a、b等順序。為什么數據庫會讓我們選擇字段的順序呢?不都是三個字段的聯合索引么?這里就引出了數據庫索引的最左前綴原理。
  比如:索引index1:(a,b,c)有三個字段,我們在使用sql語句來查詢的時候,會發現很多情況下不按照我們想象的來走索引。

10.主鍵和外鍵的區別?

主鍵:

  定義:唯一標識一條記錄,不能有重復的,不允許為空。

  作用:用來保證數據完整性。

  個數:主鍵只能有一個。

ALTER TABLE “表名” ADD PRIMARY KEY (字段名)

外鍵:  

  定義:表的外鍵是另一表的主鍵, 外鍵可以有重復的, 可以是空值。

  作用:用來和其他表建立聯系用的。

  個數:一個表可以有多個外鍵。

ALTER TABLE “表名” ADD FOREIGN KEY (字段名) REFERENCES “另一張表名”( 字段名)

11.MySQL常見的函數?

數學函數
字符串函數
日期和時間函數
條件判斷函數
系統信息函數
加密函數
格式化函數

12.列舉創建索引但是無法命中索引的8種情況。

1.- like '%xx';

select * from tb1 where name like '%cn';

2.- 使用函數;

select * from tb1 where reverse(name) = 'Clint';

3.- or;

select * from tb1 where nid = 1 or email = 'Clint@qq.com';

特別的:當or條件中有未建立索引的列才失效,以下會走索引;

select * from tb1 where nid = 1 or name = 'jack';
select * from tb1 where nid = 1 or email = 'rose@163.com' and name = 'rose'

4.- 類型不一致;
  如果列是字符串類型,傳入條件是必須用引號引起來;

select * from tb1 where name = 999;

5.- !=

select * from tb1 where name != 'rose'

特別的:如果是主鍵,則還是會走索引

select * from tb1 where nid != 123

6.- >

select * from tb1 where name > 'alex'

特別的:如果是主鍵或索引是整數類型,則還是會走索引

select * from tb1 where nid > 123
select * from tb1 where num > 123

7.- order by

select email from tb1 order by name desc;

當根據索引排序時候,選擇的映射如果不是索引,則不走索引
特別的:如果對主鍵排序,則還是走索引:

select * from tb1 order by nid desc;

8.- 組合索引最左前綴
如果組合索引為:(name,email)
name and email -- 使用索引
name -- 使用索引
email -- 不使用索引

13.如何開啟慢日志查詢?

為什么要開啟慢查詢日志:

  開啟慢查詢日志,可以讓MySQL記錄下查詢超過指定時間的語句,通過定位分析性能的瓶頸,才能更好的優化數據庫系統的性能。

怎么開啟:

  參數說明:

      slow_query_log 慢查詢開啟狀態
      slow_query_log_file 慢查詢日志存放的位置(這個目錄需要MySQL的運行帳號的可寫權限,一般設置為MySQL的數據存放目錄)
      long_query_time 查詢超過多少秒才記錄

設置步驟:

1.查看慢查詢相關參數

mysql> show variables like 'slow_query%';
+---------------------------+----------------------------------+
| Variable_name             | Value                            |
+---------------------------+----------------------------------+
| slow_query_log            | OFF                              |
| slow_query_log_file       | /mysql/data/localhost-slow.log   |
+---------------------------+----------------------------------+

mysql> show variables like 'long_query_time';
+-----------------+-----------+
| Variable_name   | Value     |
+-----------------+-----------+
| long_query_time | 10.000000 |
+-----------------+-----------+

2.設置方法
方法一:全局變量設置
  將 slow_query_log 全局變量設置為“ON”狀態

mysql> set global slow_query_log='ON'; 

  設置慢查詢日志存放的位置

mysql> set global slow_query_log_file='/usr/local/mysql/data/slow.log';

  查詢超過1秒就記錄

mysql> set global long_query_time=1;

方法二:配置文件設置
  修改配置文件my.cnf,在[mysqld]下的下方加入

[mysqld]
slow_query_log = ON
slow_query_log_file = /usr/local/mysql/data/slow.log
long_query_time = 1

3.重啟MySQL服務

service mysqld restart

4.查看設置后的參數

復制代碼
mysql> show variables like 'slow_query%';
+---------------------+--------------------------------+
| Variable_name       | Value                          |
+---------------------+--------------------------------+
| slow_query_log      | ON                             |
| slow_query_log_file | /usr/local/mysql/data/slow.log |
+---------------------+--------------------------------+

mysql> show variables like 'long_query_time';
+-----------------+----------+
| Variable_name   | Value    |
+-----------------+----------+
| long_query_time | 1.000000 |
+-----------------+----------+
復制代碼

5.測試

  1.執行一條慢查詢SQL語句

mysql> select sleep(2);

  2.查看是否生成慢查詢日志

ls /usr/local/mysql/data/slow.log

如果日志存在,MySQL開啟慢查詢設置成功!

14.數據庫導入導出命令(結構+數據)?

導出數據庫:

mysqldump -u 用戶名 -p 數據庫名 > 導出的文件名,如我輸入的命令行:mysqldump -u root -p news > news.sql (輸入后會讓你輸入進                          入MySQL的密碼),(如果導出單張表的話在數據庫名后面輸入表名即可)

導入數據庫:

1,將要導入的.sql文件移至bin文件下,這樣的路徑比較方便
2,同上面導出的第1步
3,進入MySQL:mysql -u 用戶名 -p ,如我輸入的命令行:mysql -u root -p (輸入同樣后會讓你輸入MySQL的密碼)
4,在MySQL-Front中新建你要建的數據庫,這時是空數據庫,如新建一個名為news的目標數據庫
5,輸入:mysql>use 目標數據庫名,如我輸入的命令行:mysql>use news;
6,導入文件:mysql>source 導入的文件名;如我輸入的命令行:mysql>source news.sql;

15.數據庫優化方案?

1.對查詢進行優化,避免全表掃描

2.避免在where子句中對字段進行null值判斷

16.char和varchar的區別?

1.定長和變長: char長度固定,varchar長度可變

2.存儲容量不同:char最多只能存放字符個數255,和編碼無關;而varchar 最對可以存65532個字符

17.簡述MySQL的執行計划?

* * *

18.在對name做了唯一索引前提下,簡述以下區別:
 
        select * from tb where name = ‘CRM-Clint’ 
 
        select * from tb where name = ‘CRM-Clint’ limit 1

19.1000w條數據,使用limit offset 分頁時,為什么越往后翻越慢?如何解決?

1:先查主鍵,在分頁;

select * from tb where id in (
select id from tb where limit 10 offset 30
)

2:按照也無需求是否可以設置只讓用戶看200頁;

3:記錄當前頁 數據ID最大值和最小值,在翻頁時,根據條件先進行篩選;篩選完畢之后,再根據limit offset 查詢;

select * from (select * from tb where id > 22222222) as B limit 10 offset 0

如果用戶自己修改頁碼,也可能導致慢;此時對url種的頁碼進行加密(rest framework );

20.什么是索引合並?

說明: 

1、索引合並是把幾個索引的范圍掃描合並成一個索引。
2、索引合並的時候,會對索引進行並集,交集或者先交集再並集操作,以便合並成一個索引。
3、這些需要合並的索引只能是一個表的。不能對多表進行索引合並。

怎么確定?

在使用explain對sql語句進行操作時,如果使用了索引合並,那么在輸出內容的type列會顯示 index_merge,key列會顯示出所有使用的索引。

21.什么是覆蓋索引?

定義:索引是高效找到行的一個方法,當能通過檢索索引就可以讀取想要的數據,那就不需要再到數據表中讀取行了。如果一個索引包含了(或覆蓋了)滿足查詢語句中字段與條件的數據就叫做覆蓋索引

查看覆蓋索引:只需要在select關鍵字之前添加explain這個命令查看。當發起一個被索引覆蓋的查詢時,在explain的Extra列可以看到 Using index的標識。

22.簡述數據庫讀寫分離?

對於數據存儲層高並發問題,最先想到的可能就是讀寫分離,在網站訪問量大並且讀寫不平均的情況下,將存儲分為master,slave兩台,所有的寫都路由到master上,所有的讀都路由到slave上,然后master和slave同步。如果一台salve不夠,可以加多台,比如一台master,3台slave。對於什么是讀寫分離,以及讀寫分離有什么好處,這里不再敘述,有興趣的可以參考 這里 。 
在設計讀寫分離的時候,有幾種解決方案:
    1. 將讀寫分離放在dao層,在dao層, 所有的insert/update/delete都訪問master庫,所有的select 都訪問salve庫,這樣對於業務層是透明的。 
    2. 將讀寫分離放在ORM層,比如mybatis可以通過mybatis plus攔截sql語句,所有的insert/update/delete都訪問master庫,所有的select 都訪問salve庫,這樣對於dao層都是透明。 
    3. 放在代理層,比如MySQL-Proxy,這樣針對整個應用程序都是透明的。 
對於絕大多數情景,讀寫分離都適用,但是讀寫分離有一個問題是master slave同步,這個同步是會有一定延遲。

23.簡述數據庫分庫分表?(水平、垂直)

數據庫瓶頸:

  IO瓶頸:

      磁盤讀IO瓶頸:熱點數據太多,數據庫緩存放不下,每次查詢時會產生大量的IO,降低查詢速度    ---->  分表

      網絡IO瓶頸:請求的數據太多,網絡帶寬不夠                                                                                   ----->  分庫

  CPU瓶頸:

      單表數據量太大,查詢時掃描的行太多,SQL效率低,CPU率先出現瓶頸          --->  水平分表

 

  水平分庫:以字段為依據,按照一定的策略(hash、range等),將一個中的數據拆分到多個

  水平分表:同理,...,將一個中的數據拆分到多個

 

  垂直分庫:為依據,按照業務歸屬不同,將不同的拆分到不同的

  垂直分表:字段為依據,按照字段的活躍性,將表中的字段拆到不同的表(主表和擴展表)中

分庫分表工具:

  1. sharding-sphere:jar,前身是sharding-jdbc;
  2. TDDL:jar,Taobao Distribute Data Layer;

  3. Mycat:中間件。

24.redis和memcached還有MongoDB比較?

1.數據庫類型方面  

  memcache數據結構單一,Redis不僅僅支持簡單的k/v類型的數據,同時還提供list,set,hash等數據結構的存儲;
  Redis和Memcache都是將數據存放在內存中,都是內存數據庫。不過memcache還可用於緩存其他,例如圖片、視頻等;

 

2、操作的便利性
  redis豐富一些,數據操作方面,redis更好一些,較少的網絡IO次數;
  mongodb支持豐富的數據表達,索引,最類似關系型數據庫,支持的查詢語言非常豐富;

3、內存空間的大小和數據量的大小
  redis在2.0版本后增加了自己的VM特性,突破物理內存的限制;可以對key value設置過期時間(類似memcache);
  memcache可以修改最大可用內存,采用LRU算法
  mongoDB適合大數據量的存儲,依賴操作系統VM做內存管理,吃內存也比較厲害,服務不要和別的服務在一起;

4、可用性(單點問題)
  redis,依賴客戶端來實現分布式讀寫;主從復制時,每次從節點重新連接主節點都要依賴整個快照,無增量復制,因性能和效率問題,所以單點問題比較復雜;不支持自動sharding,需要依賴程序設定一致hash 機制;
  Memcache本身沒有數據冗余機制,也沒必要;對於故障預防,采用依賴成熟的hash或者環狀的算法,解決單點故障引起的抖動問題;
  mongoDB支持master-slave,replicaset(內部采用paxos選舉算法,自動故障恢復),auto sharding機制,對客戶端屏蔽了故障轉移和切分機制;

6、數據一致性(事務支持
  Memcache 在並發場景下,用cas保證一致性;
  redis事務支持比較弱,只能保證事務中的每個操作連續執行;
  mongoDB不支持事務;

7、數據分析
  mongoDB內置了數據分析的功能(mapreduce),其他不支持;

8、應用場景
  redis:數據量較小的、更小性能操作和運算上;
  memcache:用於在動態系統中減少數據庫負載,提升性能;做緩存,提高性能(適合讀多寫少,對於數據量比較大,可以采用sharding);
  MongoDB:主要解決海量數據的訪問效率問題;   

25.redis中數據庫默認是多少個db 及作用?

redis下,數據庫是由一個整數索引標識,而不是由一個數據庫名稱。默認情況下,一個客戶端連接到數據庫0。redis配置文件中下面的參數來控制數據庫總數:
/etc/redis/redis.conf;該文件中,有個配置項 databases = 16 //默認有16個數據庫

26.python操作redis的模塊?

- 連接
- 直接連接:
    import redis 
    r = redis.Redis(host='10.211.55.4', port=6379)
    r.set('foo', 'Bar')
    print r.get('foo')
- 連接池:
    import redis
    pool = redis.ConnectionPool(host='10.211.55.4', port=6379)
     
    r = redis.Redis(connection_pool=pool)
    r.set('foo', 'Bar')
    print r.get('foo')

27.如果redis中的某個列表中的數據量非常大,如果實現循環顯示每一個值?

- 如果一個列表在redis中保存了10w個值,我需要將所有值全部循環並顯示,請問如何實現?
一個一個取值,列表沒有iter方法,但能自定義

def list_scan_iter(name,count=3):
    start = 0
    while True:
        result = conn.lrange(name, start, start+count-1)
        start += count
        if not result:
            break
        for item in result:
            yield item

for val in list_scan_iter('num_list'):
    print(val)
場景:投票系統,script-redis                            

28.redis如何實現主從復制?以及數據同步機制?

和Mysql主從復制的原因一樣,Redis雖然讀取寫入的速度都特別快,但是也會產生讀壓力特別大的情況。為了分擔讀壓力,Redis支持主從復制,Redis的主從結構可以采用一主多從或者級聯結構,Redis主從復制可以根據是否是全量分為全量同步增量同步

29.redis中的sentinel的作用?

Redis-Sentinel是Redis官方推薦的高可用性(HA)解決方案,當用Redis做Master-slave的高可用方案時,假如master宕機了,Redis本身(包括它的很多客戶端)都沒有實現自動進行主備切換,而Redis-sentinel本身也是一個獨立運行的進程,它能監控多個master-slave集群,發現master宕機后能進行自動切換。

主要作用: 

  不時地監控redis是否按照預期良好地運行;

  如果發現某個redis節點運行出現狀況,能夠通知另外一個進程(例如它的客戶端);

  能夠進行自動切換。當一個master節點不可用時,能夠選舉出master的多個slave(如果有超過一個slave的話)中的一個來作為新的master,其它的slave節點會將它所追隨的master的地址改為被提升為master的slave的新地址;

30.如何實現redis集群?

redis集群、分片、分布式redis
redis-py-cluster
集群方案:
- redis cluster 官方提供的集群方案。
- codis,豌豆莢技術團隊。
- tweproxy,Twiter技術團隊。
redis cluster的原理?
- 基於分片來完成。
- redis將所有能放置數據的地方創建了 16384 個哈希槽。
- 如果設置集群的話,就可以為每個實例分配哈希槽:
- 192.168.1.20【0-5000】
- 192.168.1.21【5001-10000】
- 192.168.1.22【10001-16384】
- 以后想要在redis中寫值時,
set k1 123
將k1通過crc16的算法,將k1轉換成一個數字。然后再將該數字和16384求余,如果得到的余數 3000,那么就將該值寫入到 192.168.1.20 實例中。

31.redis中默認有多少個哈希槽?

Redis 集群中內置了 16384 個哈希槽,當需要在 Redis 集群中放置一個 key-value時,redis 先對 key 使用 crc16 算法算出一個結果,然后把結果對 16384 求余數,這樣每個 key 都會對應一個編號在 0-16383 之間的哈希槽,redis 會根據節點數量大致均等的將哈希槽映射到不同的節點.

Redis 集群沒有使用一致性hash, 而是引入了哈希槽的概念。

Redis 集群有16384個哈希槽,每個key通過CRC16校驗后對16384取模來決定放置哪個槽.集群的每個節點負責一部分hash槽。這種結構很容易添加或者刪除節點,並且無論是添加刪除或者修改某一個節點,都不會造成集群不可用的狀態。

使用哈希槽的好處就在於可以方便的添加或移除節點。

當需要增加節點時,只需要把其他節點的某些哈希槽挪到新節點就可以了;

當需要移除節點時,只需要把移除節點上的哈希槽挪到其他節點就行了;

在這一點上,我們以后新增或移除節點的時候不用先停掉所有的 redis 服務。

**"用了哈希槽的概念,而沒有用一致性哈希算法,不都是哈希么?這樣做的原因是為什么呢?"
Redis Cluster是自己做的crc16的簡單hash算法,沒有用一致性hash。Redis的作者認為它的crc16(key) mod 16384的效果已經不錯了,雖然沒有一致性hash靈活,但實現很簡單,節點增刪時處理起來也很方便。

**"為了動態增刪節點的時候,不至於丟失數據么?"
節點增刪時不丟失數據和hash算法沒什么關系,不丟失數據要求的是一份數據有多個副本。

**“還有集群總共有2的14次方,16384個哈希槽,那么每一個哈希槽中存的key 和 value是什么?”
當你往Redis Cluster中加入一個Key時,會根據crc16(key) mod 16384計算這個key應該分布到哪個hash slot中,一個hash slot中會有很多key和value。你可以理解成表的分區,使用單節點時的redis時只有一個表,所有的key都放在這個表里;改用Redis Cluster以后會自動為你生成16384個分區表,你insert數據時會根據上面的簡單算法來決定你的key應該存在哪個分區,每個分區里有很多key。

32.簡述redis的有哪幾種持久化策略及比較?

RDB:每隔一段時間對redis進行一次持久化。
- 缺點:數據不完整
- 優點:速度快
AOF:把所有命令保存起來,如果想到重新生成到redis,那么就要把命令重新執行一次。
- 缺點:速度慢,文件比較大
- 優點:數據完整

33.列舉redis支持的過期策略。

voltile-lru:    從已設置過期時間的數據集(server.db[i].expires)中挑選最近頻率最少數據淘汰
  volatile-ttl:   從已設置過期時間的數據集(server.db[i].expires)中挑選將要過期的數據淘汰
  volatile-random:從已設置過期時間的數據集(server.db[i].expires)中任意選擇數據淘汰

  
  allkeys-lru:       從數據集(server.db[i].dict)中挑選最近最少使用的數據淘汰
  allkeys-random:    從數據集(server.db[i].dict)中任意選擇數據淘汰
  no-enviction(驅逐):禁止驅逐數據

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

相關知識:redis 內存數據集大小上升到一定大小的時候,就會施行數據淘汰策略(回收策略)。redis 提供 6種數據淘汰策略:

  volatile-lru:從已設置過期時間的數據集(server.db[i].expires)中挑選最近最少使用的數據淘汰
  volatile-ttl:從已設置過期時間的數據集(server.db[i].expires)中挑選將要過期的數據淘汰
  volatile-random:從已設置過期時間的數據集(server.db[i].expires)中任意選擇數據淘汰
  allkeys-lru:從數據集(server.db[i].dict)中挑選最近最少使用的數據淘汰
  allkeys-random:從數據集(server.db[i].dict)中任意選擇數據淘汰
  no-enviction(驅逐):禁止驅逐數據

35.寫代碼,基於redis的列表實現 先進先出、后進先出隊列、優先級隊列。

參看script—redis源碼
from scrapy.utils.reqser import request_to_dict, request_from_dict

  from . import picklecompat


  class Base(object):
      """Per-spider base queue class"""

      def __init__(self, server, spider, key, serializer=None):
          """Initialize per-spider redis queue.

          Parameters
          ----------
          server : StrictRedis
              Redis client instance.
          spider : Spider
              Scrapy spider instance.
          key: str
              Redis key where to put and get messages.
          serializer : object
              Serializer object with ``loads`` and ``dumps`` methods.

          """
          if serializer is None:
              # Backward compatibility.
              # TODO: deprecate pickle.
              serializer = picklecompat
          if not hasattr(serializer, 'loads'):
              raise TypeError("serializer does not implement 'loads' function: %r"
                              % serializer)
          if not hasattr(serializer, 'dumps'):
              raise TypeError("serializer '%s' does not implement 'dumps' function: %r"
                              % serializer)

          self.server = server
          self.spider = spider
          self.key = key % {'spider': spider.name}
          self.serializer = serializer

      def _encode_request(self, request):
          """Encode a request object"""
          obj = request_to_dict(request, self.spider)
          return self.serializer.dumps(obj)

      def _decode_request(self, encoded_request):
          """Decode an request previously encoded"""
          obj = self.serializer.loads(encoded_request)
          return request_from_dict(obj, self.spider)

      def __len__(self):
          """Return the length of the queue"""
          raise NotImplementedError

      def push(self, request):
          """Push a request"""
          raise NotImplementedError

      def pop(self, timeout=0):
          """Pop a request"""
          raise NotImplementedError

      def clear(self):
          """Clear queue/stack"""
          self.server.delete(self.key)


  class FifoQueue(Base):
      """Per-spider FIFO queue"""

      def __len__(self):
          """Return the length of the queue"""
          return self.server.llen(self.key)

      def push(self, request):
          """Push a request"""
          self.server.lpush(self.key, self._encode_request(request))

      def pop(self, timeout=0):
          """Pop a request"""
          if timeout > 0:
              data = self.server.brpop(self.key, timeout)
              if isinstance(data, tuple):
                  data = data[1]
          else:
              data = self.server.rpop(self.key)
          if data:
              return self._decode_request(data)


  class PriorityQueue(Base):
      """Per-spider priority queue abstraction using redis' sorted set"""

      def __len__(self):
          """Return the length of the queue"""
          return self.server.zcard(self.key)

      def push(self, request):
          """Push a request"""
          data = self._encode_request(request)
          score = -request.priority
          # We don't use zadd method as the order of arguments change depending on
          # whether the class is Redis or StrictRedis, and the option of using
          # kwargs only accepts strings, not bytes.
          self.server.execute_command('ZADD', self.key, score, data)

      def pop(self, timeout=0):
          """
          Pop a request
          timeout not support in this queue class
          """
          # use atomic range/remove using multi/exec
          pipe = self.server.pipeline()
          pipe.multi()
          pipe.zrange(self.key, 0, 0).zremrangebyrank(self.key, 0, 0)
          results, count = pipe.execute()
          if results:
              return self._decode_request(results[0])


  class LifoQueue(Base):
      """Per-spider LIFO queue."""

      def __len__(self):
          """Return the length of the stack"""
          return self.server.llen(self.key)

      def push(self, request):
          """Push a request"""
          self.server.lpush(self.key, self._encode_request(request))

      def pop(self, timeout=0):
          """Pop a request"""
          if timeout > 0:
              data = self.server.blpop(self.key, timeout)
              if isinstance(data, tuple):
                  data = data[1]
          else:
              data = self.server.lpop(self.key)

          if data:
              return self._decode_request(data)


  # TODO: Deprecate the use of these names.
  SpiderQueue = FifoQueue
  SpiderStack = LifoQueue
  SpiderPriorityQueue = PriorityQueue
View Code

36.如何基於redis實現消息隊列?

# 通過發布訂閱模式的PUB、SUB實現消息隊列
# 發布者發布消息到頻道了,頻道就是一個消息隊列。
# 發布者:
import redis
conn = redis.Redis(host='127.0.0.1',port=6379)
conn.publish('104.9MH', "hahahahahaha")
# 訂閱者:
import redis
conn = redis.Redis(host='127.0.0.1',port=6379)
pub = conn.pubsub()
pub.subscribe('104.9MH')
while True:
    msg= pub.parse_response()
    print(msg)
對了,redis 做消息隊列不合適
業務上避免過度復用一個redis,用它做緩存、做計算,還做任務隊列,壓力太大,不好。

37.如何基於redis實現發布和訂閱?以及發布訂閱和消息隊列的區別?

發布和訂閱,只要有任務就給所有訂閱者沒人一份
  發布者:
      import redis

      conn = redis.Redis(host='127.0.0.1',port=6379)
      conn.publish('104.9MH', "hahaha")
  訂閱者:
      import redis

      conn = redis.Redis(host='127.0.0.1',port=6379)
      pub = conn.pubsub()
      pub.subscribe('104.9MH')

      while True:
          msg= pub.parse_response()
          print(msg)

38.什么是codis及作用?

Codis 是一個分布式 Redis 解決方案, 對於上層的應用來說, 連接到 Codis Proxy 和連接原生的 Redis Server 沒有明顯的區別
(不支持的命令列表), 上層應用可以像使用單機的 Redis 一樣使用, Codis 底層會處理請求的轉發, 不停機的數據遷移等工作,
所有后邊的一切事情, 對於前面的客戶端來說是透明的, 可以簡單的認為后邊連接的是一個內存無限大的 Redis 服務.

39.什么是twemproxy及作用?

概念:

  Twemproxy是由Twitter開源的Redis代理,其基本原理是:Redis客戶端把請求發送到Twemproxy,Twemproxy根據路由規則發送到正確的Redis實例,最后Twemproxy把結果匯集返回給客戶端;
  Twemproxy通過引入一個代理層,將多個Redis實例進行統一管理,使Redis客戶端只需要在Twemproxy上進行操作,而不需要關心后面有多少個Redis實例;

作用:

  實現Redis集群;

40.寫代碼實現redis事務操作。

41.redis中的watch的命令的作用?

watch 用於在進行事務操作的最后一步也就是在執行exec 之前對某個key進行監視;
如果這個被監視的key被改動,那么事務就被取消,否則事務正常執行;
一般在MULTI 命令前就用watch命令對某個key進行監控.如果想讓key取消被監控,可以用unwatch命令; 

在Redis的事務中,WATCH命令可用於提供CAS(check-and-set)功能。
假設我們通過WATCH命令在事務執行之前監控了多個Keys,倘若在WATCH之后有任何Key的值發生了變化,
EXEC命令執行的事務都將被放棄,同時返回Null multi-bulk應答以通知調用者事務執行失敗。

面試題:你如何控制剩余的數量不會出問題?
方式一:- 通過redis的watch實現

import redis
conn = redis.Redis(host='127.0.0.1',port=6379)

# conn.set('count',1000)
val = conn.get('count')
print(val)

with conn.pipeline(transaction=True) as pipe:

# 先監視,自己的值沒有被修改過
conn.watch('count')

# 事務開始
pipe.multi()
old_count = conn.get('count')
count = int(old_count)
print('現在剩余的商品有:%s',count)
input("問媳婦讓不讓買?")
pipe.set('count', count - 1)

# 執行,把所有命令一次性推送過去
pipe.execute()

方式二 - 數據庫的鎖

42.基於redis如何實現商城商品數量計數器?

import redis

conn = redis.Redis(host='192.168.1.41',port=6379)

conn.set('count',1000)

with conn.pipeline() as pipe:

    # 先監視,自己的值沒有被修改過
    conn.watch('count')

    # 事務開始
    pipe.multi()
    old_count = conn.get('count')
    count = int(old_count)
    if count > 0:  # 有庫存
        pipe.set('count', count - 1)

    # 執行,把所有命令一次性推送過去
    pipe.execute()

43.簡述redis分布式鎖和redlock的實現機制。

在不同進程需要互斥地訪問共享資源時,分布式鎖是一種非常有用的技術手段。
有很多三方庫和文章描述如何用Redis實現一個分布式鎖管理器,但是這些庫實現的方式差別很大
,而且很多簡單的實現其實只需采用稍微增加一點復雜的設計就可以獲得更好的可靠性。
用Redis實現分布式鎖管理器的算法,我們把這個算法稱為RedLock。

實現
- 寫值並設置超時時間
- 超過一半的redis實例設置成功,就表示加鎖完成。
- 使用:安裝redlock-py

from redlock import Redlock
dlm = Redlock(
[
{"host": "localhost", "port": 6379, "db": 0},
{"host": "localhost", "port": 6379, "db": 0},
{"host": "localhost", "port": 6379, "db": 0},
]
)
# 加鎖,acquire
my_lock = dlm.lock("my_resource_name",10000)
if my_lock:
# J進行操作
# 解鎖,release
dlm.unlock(my_lock)
else:
print('獲取鎖失敗')

redis分布式鎖?

# 不是單機操作,又多了一/多台機器
# redis內部是單進程、單線程,是數據安全的(只有自己的線程在操作數據)
----------------------------------------------------------------
\A、B、C,三個實例(主)
1、來了一個'隔壁老王'要操作,且不想讓別人操作,so,加鎖;
加鎖:'隔壁老王'自己生成一個隨機字符串,設置到A、B、C里(xxx=666)
2、來了一個'鄰居老李'要操作A、B、C,一讀發現里面有字符串,擦,被加鎖了,不能操作了,等着吧~
3、'隔壁老王'解決完問題,不用鎖了,把A、B、C里的key:'xxx'刪掉;完成解鎖
4、'鄰居老李'現在可以訪問,可以加鎖了
# 問題:
1、如果'隔壁老王'加鎖后突然掛了,就沒人解鎖,就死鎖了,其他人干看着沒法用咋辦?
2、如果'隔壁老王'去給A、B、C加鎖的過程中,剛加到A,'鄰居老李'就去操作C了,加鎖成功or失敗?
3、如果'隔壁老王'去給A、B、C加鎖時,C突然掛了,這次加鎖是成功還是失敗?
4、如果'隔壁老王'去給A、B、C加鎖時,超時時間為5秒,加一個鎖耗時3秒,此次加鎖能成功嗎?
# 解決
1、安全起見,讓'隔壁老王'加鎖時設置超時時間,超時的話就會自動解鎖(刪除key:'xxx')
2、加鎖程度達到(1/2)+1個就表示加鎖成功,即使沒有給全部實例加鎖;
3、加鎖程度達到(1/2)+1個就表示加鎖成功,即使沒有給全部實例加鎖;
4、不能成功,鎖還沒加完就過期,沒有意義了,應該合理設置過期時間

44.什么是一致性哈希?Python中是否有相應模塊?

一致性哈希 一致性hash算法(DHT)可以通過減少影響范圍的方式,解決增減服務器導致的數據散列問題,從而解決了分布式環境下負載均衡問題; 如果存在熱點數據,可以通過增添節點的方式,對熱點區間進行划分,將壓力分配至其他服務器,重新達到負載均衡的狀態。

Python模塊--hash_ring,即Python中的一致性hash

45.如何高效的找到redis中所有以clint開頭的key?

redis 有一個keys命令。
# 語法:KEYS pattern
# 說明:返回與指定模式相匹配的所用的keys。
該命令所支持的匹配模式如下:
1、?:用於匹配單個字符。例如,h?llo可以匹配hello、hallo和hxllo等;
2、*:用於匹配零個或者多個字符。例如,h*llo可以匹配hllo和heeeello等;
2、[]:可以用來指定模式的選擇區間。例如h[ae]llo可以匹配hello和hallo,但是不能匹配hillo。同時,可以使用“/”符號來轉義特殊的字符
# 注意
KEYS 的速度非常快,但如果數據太大,內存可能會崩掉,
如果需要從一個數據集中查找特定的key,最好還是用Redis的集合結構(set)來代替。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM