在微服務(wù)架構(gòu)下,業(yè)務(wù)系統(tǒng)的功能是由大量的微服務(wù)組成,例如:一個(gè)電商購(gòu)物平臺(tái)下單環(huán)節(jié)就需要訂單服務(wù)、庫(kù)存服務(wù)、短信服務(wù)和物流服務(wù)等逐級(jí)調(diào)用。但是每個(gè)服務(wù)是由不同的研發(fā)團(tuán)隊(duì)負(fù)責(zé)的,部署在成百上千臺(tái)服務(wù)器上。
示例
在這么復(fù)雜的數(shù)據(jù)傳遞過(guò)程中最怕發(fā)生系統(tǒng)故障,因?yàn)楹茈y去快速定位到問(wèn)題。所以就要引入服務(wù)間的鏈路追蹤。
在技術(shù)調(diào)研過(guò)程中大致有以下幾種方案:
1、基于 Sleuth + Zipkin 方案
2、通過(guò) SkyWalking 實(shí)施鏈路追蹤
3、自建鏈路追蹤
這里選擇了自建鏈路追蹤的方式,至于為啥不用1、2 就不多講了,這次的坑是在自建過(guò)程中采用的是通過(guò) OpenFeign 來(lái)作為服務(wù)間遠(yuǎn)程數(shù)據(jù)交換,通過(guò)在HTTP請(qǐng)求的Header 總增加TraceId 參數(shù)來(lái)作為服務(wù)間的鏈路追蹤。
import feign.RequestInterceptor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Enumeration;
@Configuration
@Slf4j
public class FeignClientConfig {
@Bean
public RequestInterceptor headerInterceptor() {
return template -> {
ServletRequestAttributes attributes=(ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (null !=attributes) {
HttpServletRequest request=attributes.getRequest();
Enumeration<String> headerNames=request.getHeaderNames();
if (headerNames !=null) {
while (headerNames.hasMoreElements()) {
String name=headerNames.nextElement();
String values=request.getHeader(name);
template.header(name, values);
}
}
}
};
}
}
上面說(shuō)到有些服務(wù)是其他團(tuán)隊(duì)負(fù)責(zé)研發(fā)的,有自己的網(wǎng)關(guān)、鑒權(quán)等。那么在通過(guò)OpenFeign調(diào)用的時(shí)候出現(xiàn)了返回亂碼,具體如下:
{
"code": "200",
"message": "請(qǐng)求成功",
"result": "\u0015\u0013\u0001\u0000??Y[???3Y????\u000E??H?GHВ?????Y\u0018H??%?@?@?m??\u001BL%???\u0019u\u000Ev-+??8&t???\t<?\u001EW0002?\t?eZ?\u0004?Xk,??lt5\u0010\u0004V\u0000*???D?*?0??\u0000[???\u0005????y???\u001B\b?,r?C\u0012\u000G?3?m??<?v\"?]?m??`??p1^?J??h\u00M#?$\u001F?<?\r?Hf[?\u0002"
}
比較奇怪的是只有個(gè)別服務(wù)是有問(wèn)題的,也沒(méi)有辦法去看對(duì)方的代碼。
最終定位到的原因是Request 的Header 中設(shè)置Accept-Encoding:gzip, deflate, br;的問(wèn)題,如果加上就會(huì)返回亂碼。
最開(kāi)始的時(shí)候以為是編碼的問(wèn)題,所以小編嘗試了各種編碼,但是始終無(wú)法解決問(wèn)題。
后來(lái)想到 Accept-Encoding 不參與數(shù)據(jù)傳遞就OK啦!
增加代碼:
之所以添加:"Accept-Encoding"="gzip, deflate, br"
是因?yàn)椋瑸g覽器對(duì)于從服務(wù)器中返回的對(duì)應(yīng)的gzip壓縮的網(wǎng)頁(yè),會(huì)自動(dòng)解壓縮,所以Request的時(shí)候,添加對(duì)應(yīng)的Header信息,表示接收壓縮后的數(shù)據(jù)。
而此代碼中,如果也添加此頭信息,結(jié)果就是,返回的壓縮后的數(shù)據(jù),由于沒(méi)有解碼,客戶(hù)端將壓縮后的數(shù)據(jù)當(dāng)做普通的html文本來(lái)處理,當(dāng)前顯示出來(lái)的內(nèi)容,是亂碼了。
小編選擇的是排除掉 Accept-Encoding ,當(dāng)然也可以只去掉 gzip參數(shù)。
文轉(zhuǎn)自與博客園一杯涼茶的博客.
Servlet是一種古老的Java Web技術(shù),在開(kāi)發(fā)中除了祖?zhèn)鞯捻?xiàng)目,已經(jīng)很少見(jiàn)到它的身影,但是作為Java Web的重要組成部分,Servlet還是值得深入學(xué)習(xí)的,形形色色框架再好用,底層終歸還是Servlet,Service ,JDBC這些基礎(chǔ)的東西,學(xué)好Servlet有助于大家審理理解框架的底層源碼深入學(xué)習(xí),也為大家在面試過(guò)程中取得一線(xiàn)生機(jī),但是在servlet經(jīng)常會(huì)遇到一些亂碼問(wèn)題,今天我們來(lái)聊聊解決Servlet中的亂碼問(wèn)題。
get請(qǐng)求:
get請(qǐng)求的參數(shù)是在url后面提交過(guò)來(lái)的,也就是在請(qǐng)求行中,
MyServlet是一個(gè)普通的Servlet,瀏覽器訪問(wèn)它時(shí),使用get請(qǐng)求方式提交了一個(gè)name=小明的參數(shù)值,在doGet中獲取該參數(shù)值,并且打印到控制臺(tái),發(fā)現(xiàn)出現(xiàn)亂碼
出現(xiàn)亂碼的原因:
前提知識(shí):需要了解碼表,編碼,解碼這三個(gè)名詞的意思。我簡(jiǎn)單說(shuō)一下常規(guī)的,
碼表:是一種規(guī)則,用來(lái)讓我們看得懂的語(yǔ)言轉(zhuǎn)換為電腦能夠認(rèn)識(shí)的語(yǔ)言的一種規(guī)則,有很多中碼表,IS0-8859-1,GBK,UTF-8,UTF-16等一系列碼表,比如GBK,UTF-8,UTF-16都可以標(biāo)識(shí)一個(gè)漢字,而如果要標(biāo)識(shí)英文,就可以用IS0-8859-1等別的碼表。
編碼:將我們看得懂的語(yǔ)言轉(zhuǎn)換為電腦能夠認(rèn)識(shí)的語(yǔ)言。這個(gè)過(guò)程就是編碼的作用
解碼:將電腦認(rèn)識(shí)的語(yǔ)言轉(zhuǎn)換為我們能看得懂得語(yǔ)言。這個(gè)過(guò)程就是解碼的作用
詳細(xì)請(qǐng)參考這篇博文。
這里只能夠代表經(jīng)過(guò)一次編碼例子,有些程序中,會(huì)將一個(gè)漢字或者一個(gè)字母用不同的碼表連續(xù)編碼幾次,那么第一次編碼還是上面所說(shuō)的作用,第二次編碼的話(huà),就是將電腦能夠認(rèn)識(shí)的語(yǔ)言轉(zhuǎn)換為電腦能夠認(rèn)識(shí)的語(yǔ)言(轉(zhuǎn)換規(guī)則不同),那么該解碼過(guò)程,就必須要經(jīng)過(guò)兩次解碼,也就是編碼的逆過(guò)程,下面這個(gè)例子就很好的說(shuō)明了這個(gè)問(wèn)題。
瀏覽器使用的是UTF-8碼表,通過(guò)http協(xié)議傳輸,http協(xié)議只支持IS0-8859-1,到了服務(wù)器,默認(rèn)也是使用的是IS0-8859-1的碼表,看圖
也就是三個(gè)過(guò)程,經(jīng)歷了兩次編碼,所以就需要進(jìn)行兩次解碼,
1、瀏覽器將"小明"使用UTF-8碼表進(jìn)行編碼(因?yàn)樾∶鬟@個(gè)是漢字,所以使用能標(biāo)識(shí)中文的碼表,這也是我們可以在瀏覽器上可以手動(dòng)設(shè)置的,如果使用了不能標(biāo)識(shí)中文的碼表,那么就將會(huì)出現(xiàn)亂碼,因?yàn)榇a表中找不到中文對(duì)應(yīng)的計(jì)算機(jī)符號(hào),就可能會(huì)用??等其他符號(hào)表示),編碼后得到的為 1234 ,將其通過(guò)http協(xié)議傳輸。
2、在http協(xié)議傳輸,只能用ISO-8859-1碼表中所代表的符號(hào),所以會(huì)將我們?cè)鹊?234再次進(jìn)行一次編碼,這次使用的是ISO-8859-1,得到的為 ???? ,然后傳輸?shù)椒?wù)器
3、服務(wù)器獲取到該數(shù)據(jù)是經(jīng)過(guò)了兩次編碼后得到的數(shù)據(jù),所以必須跟原先編碼的過(guò)程逆過(guò)來(lái)解碼,先是UTF-8編碼,然后在ISO-8859-1編碼,那么解碼的過(guò)程,就必須是先ISO-8859-1解碼,然后在用UTF-8解碼,這樣就能夠得到正確的數(shù)據(jù)。????.getBytes("ISO-8859-1");//第一次解碼,轉(zhuǎn)換為電腦能夠識(shí)別的語(yǔ)言, new String(1234,"UTF-8");//第二次解碼,轉(zhuǎn)換為我們認(rèn)識(shí)的語(yǔ)言
解決代碼
Post請(qǐng)求:
post請(qǐng)求方式的參數(shù)是在請(qǐng)求體中,相對(duì)于get請(qǐng)求簡(jiǎn)單很多,沒(méi)有經(jīng)過(guò)http協(xié)議這一步的編碼過(guò)程,所以只需要在服務(wù)器端,設(shè)置服務(wù)器解碼的碼表跟瀏覽器編碼的碼表是一樣的就行了,在這里瀏覽器使用的是UTF-8碼表編碼,那么服務(wù)器端就設(shè)置解碼所用碼表也為UTF-8就OK了
設(shè)置服務(wù)器端使用UTF-8碼表解碼
request.setCharacterEncoding("UTF-8"); //命令Tomcat使用UTF-8碼表解碼,而不用默認(rèn)的ISO-8859-1了。
所以在很多時(shí)候,在doPost方法的第一句,就是這句代碼,防止獲取請(qǐng)求參數(shù)時(shí)亂碼。
總結(jié)請(qǐng)求參數(shù)亂碼問(wèn)題
get請(qǐng)求和post請(qǐng)求方式的中文亂碼問(wèn)題處理方式不同
get:請(qǐng)求參數(shù)在請(qǐng)求行中,涉及了http協(xié)議,手動(dòng)解決亂碼問(wèn)題,知道出現(xiàn)亂碼的根本原因,對(duì)癥下藥,其原理就是進(jìn)行兩次編碼,兩次解碼的過(guò)程
new String(xxx.getBytes("ISO-8859-1"),"UTF-8");
post:請(qǐng)求參數(shù)在請(qǐng)求體中,使用servlet API解決亂碼問(wèn)題,其原理就是一次編碼一次解碼,命令tomcat使用特定的碼表解碼。
request.setCharaterEncoding("UTF-8");
首先介紹一下,response對(duì)象是如何向?yàn)g覽器發(fā)送數(shù)據(jù)的。兩種方法,一種getOutputStream,一種getWrite。
ServletOutputStream getOutputStream(); //獲取輸出字節(jié)流。提供write() 和 print() 兩個(gè)輸出方法
PrintWriter getWrite(); //獲取輸出字符流 提供write() 和 print()兩個(gè)輸出方法
print()方法底層都是使用write()方法的,相當(dāng)于print()方法就是將write()方法進(jìn)行了封裝,使開(kāi)發(fā)者更方便快捷的使用,想輸出什么,就直接選擇合適的print()方法,而不用考慮如何轉(zhuǎn)換字節(jié)。
1、ServeltOutputStream getOutputStream();
不能直接輸出中文,直接輸出中文會(huì)報(bào)異常,
報(bào)異常的源代碼
解決:
resp.getoutputStream().write("哈哈哈,我要輸出到瀏覽器".getBytes("UTF-8"));
將要輸出的漢字先用UTF-8進(jìn)行編碼,而不用讓tomcat來(lái)進(jìn)行編碼,這樣如果瀏覽器用的是UTF-8碼表進(jìn)行解碼的話(huà),那么就會(huì)正確輸出,如果瀏覽器用的不是UTF-8,那么還是會(huì)出現(xiàn)亂碼,所以說(shuō)這個(gè)關(guān)鍵要看瀏覽器用的什么碼表,這個(gè)就不太好,這里還要注意一點(diǎn),就是使用的是write(byte)方法,因?yàn)閜rint()方法沒(méi)有輸出byte類(lèi)型的方法。
2、PrintWriter getWrite();
直接輸出中文,不會(huì)報(bào)異常,但是肯定會(huì)報(bào)異常,因?yàn)橛肐SO-8859-1的碼表不能標(biāo)識(shí)中文,一開(kāi)始就是錯(cuò)的,怎么解碼編碼讀沒(méi)用了
有三種方法來(lái)讓其正確輸出中文
1、使用Servlet API response.setCharacterEncoding()
response.setCharacterEncoding("UTF-8"); //讓tomcat將我們要響應(yīng)到瀏覽器的中文用UTF-8進(jìn)行編碼,而不使用默認(rèn)的ISO-8859-1了,這個(gè)還是要取決于瀏覽器是不是用的UTF-8的碼表,跟上面的一樣有缺陷
2、通知tomcat和瀏覽器都使用同一張碼表
response.setHeader("content-type","text/html;charset=uft-8"); //手動(dòng)設(shè)置響應(yīng)內(nèi)容,通知tomcat和瀏覽器使用utf-8來(lái)進(jìn)行編碼和解碼。
charset=uft-8就相當(dāng)于response.setCharacterEncoding("UTF-8");//通知tomcat使用utf-8進(jìn)行編碼
response.setHeader("content-type","text/html;charset=uft-8");//合起來(lái),就是既通知tomcat用utf-8編碼,又通知瀏覽器用UTF-8進(jìn)行解碼。
response.setContentType("text/html;charset=uft-8"); //使用Servlet API 來(lái)通知tomcaat和強(qiáng)制瀏覽器使用UTF-8來(lái)進(jìn)行編碼解碼,這個(gè)的底層代碼就是上一行的代碼,進(jìn)行了簡(jiǎn)單的封裝而已。
3、通知tomcat,在使用html<meta>通知瀏覽器 (html源碼),注意:<meta>建議瀏覽器應(yīng)該使用編碼,不能強(qiáng)制要求
進(jìn)行兩步
所以response在響應(yīng)時(shí),只要通知tomcat和瀏覽器使用同一張碼表,一般使用第二種方法,那么就可以解決響應(yīng)的亂碼問(wèn)題了
在上面講解的時(shí)候總是看起來(lái)很繁瑣,其實(shí)知道了其中的原理,很簡(jiǎn)單,現(xiàn)在來(lái)總結(jié)一下,
請(qǐng)求亂碼
get請(qǐng)求:
經(jīng)過(guò)了兩次編碼,所以就要兩次解碼
第一次解碼:xxx.getBytes("ISO-8859-1");得到y(tǒng)yy
第二次解碼:new String(yyy,"utf-8");
連續(xù)寫(xiě):new String(xxx.getBytes("ISO-8859-1"),"UTF-8");
post請(qǐng)求:
只經(jīng)過(guò)一次編碼,所以也就只要一次解碼,使用Servlet API request.setCharacterEncoding();
request.setCharacterEncoding("UTF-8"); //不一定解決,取決于瀏覽器是用什么碼表來(lái)編碼,瀏覽器用UTF-8,那么這里就寫(xiě)UTF-8。
響應(yīng)亂碼
getOutputStream();
使用該字節(jié)輸出流,不能直接輸出中文,會(huì)出異常,要想輸出中文,解決方法如下
解決:getOutputStream().write(xxx.getBytes("UTF-8")); //手動(dòng)將中文用UTF-8碼表編碼,變成字節(jié)傳輸,變成字節(jié)后,就不會(huì)報(bào)異常,并且tomcat也不會(huì)在編碼,因?yàn)橐呀?jīng)編碼過(guò)了,所以到瀏覽器后,如果瀏覽器使用的是UTF-8碼表解碼,那么就不會(huì)出現(xiàn)中文亂碼,反之則出現(xiàn)中文亂碼,所以這個(gè)方法,不能完全保證中文不亂碼
getWrite();
使用字符輸出流,能直接輸出中文,不會(huì)出異常,但是會(huì)出現(xiàn)亂碼。能用三種方法解決,一直使用第二種方法
解決:通知tomcat和瀏覽器使用同一張碼表。
response.setContentType("text/html;charset=utf-8"); //通知瀏覽器使用UTF-8解碼
通知tomcat和瀏覽器使用UTF-8編碼和解碼。這個(gè)方法的底層原理是這句話(huà):response.setHeader("contentType","text/html;charset=utf-8");
注意:getOutputStream()和getWrite() 這兩個(gè)方法不能夠同時(shí)使用,一次只能使用一個(gè),否則報(bào)異常。
文將介紹一種Java Web/Api 開(kāi)發(fā)常見(jiàn)的亂碼問(wèn)題。
前提摘要:在學(xué)習(xí)Java Web的過(guò)程中,亂碼問(wèn)題是經(jīng)常遇到的,無(wú)論是頁(yè)面亂碼還是數(shù)據(jù)庫(kù)亂碼。本文將羅列筆者在開(kāi)發(fā)過(guò)程中常常遇到幾種亂碼場(chǎng)景。
一、JSP頁(yè)面亂碼
因?yàn)榫W(wǎng)頁(yè)默認(rèn)的編碼格式為ISO-8859-1
,該編碼格式不支持漢字,所以在網(wǎng)頁(yè)上顯示為亂碼???.
解決方案
在文件頂部添加header上添加
<pre>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
</pre>
二、ajax請(qǐng)求時(shí) request.getParams亂碼
在你的tomcat server.xml中配置
<Connector port="8080" protocol="HTTP/1.1"
</pre>
即添加
如果你想學(xué)習(xí)java可以來(lái)這個(gè)群,五三二,二五九,九五二,里面有大量的學(xué)習(xí)資料可以下載。 URIEconding="UTF-8" useBodyEncodingForURI="true"
三、ajax請(qǐng)求時(shí) response出去為亂碼
在使用Spring MVC時(shí) 須在web.xml 配置
<filter>
mvc注釋 RequestMapping 聲明 增加utf-8聲明如
@RequestMapping(method=RequestMethod.POST, produces="application/json; charset=utf-8")
如果是普通的servlet時(shí),則須代碼編碼之后輸出
response.setContentType("text/html;charset=UTF-8");
三、文件亂碼
經(jīng)常在拿到別人的工程文件時(shí),由于他不同的編碼例如GBK之類(lèi)的編碼,需要在IDE中將文件編碼統(tǒng)一成UTF-8 即可.
window平臺(tái)下,可以實(shí)用notepad++進(jìn)行編轉(zhuǎn)化替換即可。
四、終端亂碼
如果是 System.out.println 打印顯示亂碼 則 VM options 里 添加 -Dfile.encoding=UTF-8
參數(shù)
五、數(shù)據(jù)庫(kù)亂碼
jdbc鏈接utf-8聲明
jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf-8
新建數(shù)據(jù)庫(kù)時(shí),選擇utf-8/utf-8mb4編碼
六、emoji亂碼支持
新建數(shù)據(jù)庫(kù)選擇utf-8mb4
數(shù)據(jù)庫(kù)的字符集配置 參考mysql/Java服務(wù)端對(duì)emoji的支持
mysql-connector-java 版本升級(jí)至 5.1.22,此版本筆者親測(cè) Ok!
如果你想學(xué)習(xí)java可以來(lái)這個(gè)群 532259952
*請(qǐng)認(rèn)真填寫(xiě)需求信息,我們會(huì)在24小時(shí)內(nèi)與您取得聯(lián)系。