暂时未有相关云产品技术能力~
1 简单介绍技术RFC:https://www.rfc-editor.org/rfc/rfc6749.html,本文部分内容会参考该文档部分内容。OAuth是Open Authorization,即“开放授权”的简写。OAUTH协议为用户资源的授权提供了一个安全的、开放而又简易的标准。OAuth2.0是OAuth协议的延续版本,但不向前兼容OAuth 1.0。OAuth 2.0授权框架支持第三方应用程序获取对HTTP服务的有限访问,通过编排审批交互来代表资源所有者资源所有者和HTTP服务之间,或通过允许第三方应用程序以自己的名义获取访问。现在百度开放平台,腾讯开放平台等大部分的开放平台都是使用的OAuth 2.0协议作为支撑。OAuth2.0解决的问题:需要第三方应用存储资源所有者的凭证以备将来使用,通常是密码以明文,服务器仍须支持密码验证密码固有的安全弱点。第三方应用程序获得对资源的过度广泛访问所有者的受保护资源,使资源所有者没有任何的有限子集限制持续时间或访问的能力资源。资源所有者不能撤销对单个第三方的访问权限不撤销对所有第三方的访问,并且必须这样做修改第三方用户密码。任何第三方应用程序的妥协将导致妥协终端用户的密码以及所有受该密码保护的数据密码。2 流程梳理OAuth2.0总体流程:我们来用现实的事件来举个例子——使用QQ登录极客时间(够现实了吧)(1)首先我们了解状况:QQ的服务器和极客时间的服务器肯定不是同一个服务器,而且用户数据的存储方式也可能也不同(2)其次我们在选择用QQ登录极客时间时会重定向出一个网页,这个网页是QQ的网页然后我们画一个图:其中,实线部分是我们用户真正操作的流程,而虚线部分则是服务内部的流程。由此,我们知道,QQ服务器,作为我们将要登录的网站的第三方认证服务,必须事先保存我们用户的信息,以便认证时使用。3 四种角色resource owner(资源拥有者):用户,能够授予对受保护资源的访问权的实体。resource server(资源服务器):将要访问的服务,托管受保护资源的服务器,能够接受以及使用访问令牌响应受保护的资源请求。client(客户端):即Web浏览器,请求受保护资源的应用程序资源所有者及其授权。authorization server(认证服务器):三方授权服务器,服务提供商专门用来处理认证授权的服务器,认证成功后向客户端发出访问令牌资源所有者身份验证,获取授权。4 四种实现方式在OAuth2.0中最常用的当属授权码模式,也就是我们上文讲述的实现,除此之外还有简化模式、密码模式、客户端模式等,模式的不同当然带来的就是流程和访问方式及请求参数的不同,由于其他三种模式并不常用,因此只讲述基本流程,重点还是在授权码模式中,下面我们开始分析:4.1 授权码模式Authorization Code(最常用)(A) 用户访问客户端,客户端将用户重定向到认证服务器;(B) 用户选择是否授权;(C) 如果用户同意授权,认证服务器重定向到客户端事先指定的地址,而且带上授权码(code);(D) 客户端收到授权码,带着前面的重定向地址,向认证服务器申请访问令牌;(E) 认证服务器核对授权码与重定向地址,确认后向客户端发送访问令牌和更新令牌(可选)。参数名称参数含义是否必须response_type授权类型,一般为code必须client_id客户端ID,客户端到资源服务器注册的ID必须redirect_uri重定向URI可选scope申请的权限范围,多个逗号隔开可选state客户端的当前状态,可以指定任意值,认证服务器会原封不动的返回这个值推荐4.2 简化模式Implicit(A) 客户端将用户导向认证服务器, 携带客户端ID及重定向URI;(B) 用户是否授权;(C) 用户同意授权后,认证服务器重定向到A中指定的URI,并且在URI的Fragment中包含了访问令牌;(D) 浏览器向资源服务器发出请求,该请求中不包含C中的Fragment值;(E) 资源服务器返回一个网页,其中包含了可以提取C中Fragment里面访问令牌的脚本;(F) 浏览器执行E中获得的脚本,提取令牌;(G) 浏览器将令牌发送给客户端。4.3 密码模式Resource Owner Password Credentials(A) 资源所有者提供用户名密码给客户端;(B) 客户端拿着用户名密码去认证服务器请求令牌;(C) 认证服务器确认后,返回令牌;4.4 客户端模式Client Credentials(A) 客户端发起身份认证,请求访问令牌;(B) 认证服务器确认无误,返回访问令牌。5 动手实现一个OAuth2.0鉴权服务具体代码见GitHub:https://github.com/ibarryyan/oauth25.1 整体流程5.2 代码5.2.1 Clientpackage main import ( "golang.org/x/oauth2" "log" "net/http" ) const ( authServerURL = "http://localhost:9096" ) var ( config = oauth2.Config{ ClientID: "222222", ClientSecret: "22222222", Scopes: []string{"all"}, RedirectURL: "http://localhost:9094/oauth2", Endpoint: oauth2.Endpoint{ AuthURL: authServerURL + "/oauth/authorize", TokenURL: authServerURL + "/oauth/token", }, } globalToken *oauth2.Token // Non-concurrent security ) func main() { //授权码模式Authorization Code //访问第三方授权页 http.HandleFunc("/", index) //由三方鉴权服务重定向返回,拿到code,并请求和验证token http.HandleFunc("/oauth2", oAuth2) //刷新验证码 http.HandleFunc("/refresh", refresh) http.HandleFunc("/try", try) //密码模式Resource Owner Password Credentials http.HandleFunc("/pwd", pwd) //客户端模式Client Credentials http.HandleFunc("/client", client) log.Println("Client is running at 9094 port.Please open http://localhost:9094") log.Fatal(http.ListenAndServe(":9094", nil)) }handlerpackage main import ( "context" "crypto/sha256" "encoding/base64" "encoding/json" "fmt" "golang.org/x/oauth2" "golang.org/x/oauth2/clientcredentials" "io" "net/http" "time" ) //index 重定向到三方授权服务器 func index(w http.ResponseWriter, r *http.Request) { u := config.AuthCodeURL("xyz", oauth2.SetAuthURLParam("code_challenge", genCodeChallengeS256("s256example")), oauth2.SetAuthURLParam("code_challenge_method", "S256")) http.Redirect(w, r, u, http.StatusFound) } //oAuth2 由三方鉴权服务返回,拿到code,并请求和验证token func oAuth2(w http.ResponseWriter, r *http.Request) { r.ParseForm() state := r.Form.Get("state") if state != "xyz" { http.Error(w, "State invalid", http.StatusBadRequest) return } code := r.Form.Get("code") if code == "" { http.Error(w, "Code not found", http.StatusBadRequest) return } // 获取token token, err := config.Exchange(context.Background(), code, oauth2.SetAuthURLParam("code_verifier", "s256example")) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } globalToken = token e := json.NewEncoder(w) e.SetIndent("", " ") e.Encode(token) } func refresh(w http.ResponseWriter, r *http.Request) { if globalToken == nil { http.Redirect(w, r, "/", http.StatusFound) return } globalToken.Expiry = time.Now() token, err := config.TokenSource(context.Background(), globalToken).Token() if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } globalToken = token e := json.NewEncoder(w) e.SetIndent("", " ") e.Encode(token) } func try(w http.ResponseWriter, r *http.Request) { if globalToken == nil { http.Redirect(w, r, "/", http.StatusFound) return } resp, err := http.Get(fmt.Sprintf("%s/test?access_token=%s", authServerURL, globalToken.AccessToken)) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } defer resp.Body.Close() io.Copy(w, resp.Body) } func pwd(w http.ResponseWriter, r *http.Request) { token, err := config.PasswordCredentialsToken(context.Background(), "test", "test") if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } globalToken = token e := json.NewEncoder(w) e.SetIndent("", " ") e.Encode(token) } func client(w http.ResponseWriter, r *http.Request) { cfg := clientcredentials.Config{ ClientID: config.ClientID, ClientSecret: config.ClientSecret, TokenURL: config.Endpoint.TokenURL, } token, err := cfg.Token(context.Background()) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } e := json.NewEncoder(w) e.SetIndent("", " ") e.Encode(token) } func genCodeChallengeS256(s string) string { s256 := sha256.Sum256([]byte(s)) return base64.URLEncoding.EncodeToString(s256[:]) }5.2.2 Serverpackage main import ( "context" "flag" "fmt" "github.com/go-oauth2/oauth2/v4/generates" "github.com/go-oauth2/oauth2/v4/models" "log" "net/http" "github.com/go-oauth2/oauth2/v4/errors" "github.com/go-oauth2/oauth2/v4/manage" "github.com/go-oauth2/oauth2/v4/server" "github.com/go-oauth2/oauth2/v4/store" ) var ( dumpvar bool idvar string secretvar string domainvar string portvar int ) var srv *server.Server var manager *manage.Manager var clientStore *store.ClientStore func init() { flag.BoolVar(&dumpvar, "d", true, "Dump requests and responses") flag.StringVar(&idvar, "i", "222222", "The client id being passed in") flag.StringVar(&secretvar, "s", "22222222", "The client secret being passed in") flag.StringVar(&domainvar, "r", "http://localhost:9094", "The domain of the redirect url") flag.IntVar(&portvar, "p", 9096, "the base port for the server") } func InitManager() { clientStore = store.NewClientStore() clientStore.Set(idvar, &models.Client{ ID: idvar, Secret: secretvar, Domain: domainvar, }) manager = manage.NewDefaultManager() manager.SetAuthorizeCodeTokenCfg(manage.DefaultAuthorizeCodeTokenCfg) manager.MustTokenStorage(store.NewMemoryTokenStore()) // generate jwt access token // manager.MapAccessGenerate(generates.NewJWTAccessGenerate("", []byte("00000000"), jwt.SigningMethodHS512)) manager.MapAccessGenerate(generates.NewAccessGenerate()) manager.MapClientStorage(clientStore) } func InitServer() { srv = server.NewServer(server.NewConfig(), manager) //密码登录 srv.SetPasswordAuthorizationHandler(func(ctx context.Context, clientID, username, password string) (userID string, err error) { if username == "test" && password == "test" { userID = "test" } return }) // srv.SetUserAuthorizationHandler(userAuthorizeHandler) srv.SetInternalErrorHandler(func(err error) (re *errors.Response) { log.Println("Internal Error:", err.Error()) return }) srv.SetResponseErrorHandler(func(re *errors.Response) { log.Println("Response Error:", re.Error.Error()) }) } func main() { flag.Parse() if dumpvar { log.Println("Dumping requests") } InitManager() InitServer() //登录页 http.HandleFunc("/login", loginHandler) //授权页 http.HandleFunc("/auth", authHandler) //重定向回去 http.HandleFunc("/oauth/authorize", authorize) //验证token http.HandleFunc("/oauth/token", token) http.HandleFunc("/test", test) log.Printf("Server is running at %d port.\n", portvar) log.Printf("Point your OAuth client Auth endpoint to %s:%d%s", "http://localhost", portvar, "/oauth/authorize") log.Printf("Point your OAuth client Token endpoint to %s:%d%s", "http://localhost", portvar, "/oauth/token") log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", portvar), nil)) }handlerpackage main import ( "encoding/json" "github.com/go-session/session" "io" "net/http" "net/http/httputil" "net/url" "os" "time" ) var ( loginName = "ymx" passWord = "123" ) //authorize 三方授权服务点击确认授权 func authorize(w http.ResponseWriter, r *http.Request) { if dumpvar { dumpRequest(os.Stdout, "authorize", r) } store, err := session.Start(r.Context(), w, r) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } var form url.Values if v, ok := store.Get("ReturnUri"); ok { form = v.(url.Values) } r.Form = form store.Delete("ReturnUri") store.Save() //重定向 err = srv.HandleAuthorizeRequest(w, r) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) } } func token(w http.ResponseWriter, r *http.Request) { if dumpvar { _ = dumpRequest(os.Stdout, "token", r) // Ignore the error } err := srv.HandleTokenRequest(w, r) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } } func dumpRequest(writer io.Writer, header string, r *http.Request) error { data, err := httputil.DumpRequest(r, true) if err != nil { return err } writer.Write([]byte("\n" + header + ": \n")) writer.Write(data) return nil } func userAuthorizeHandler(w http.ResponseWriter, r *http.Request) (userID string, err error) { if dumpvar { _ = dumpRequest(os.Stdout, "userAuthorizeHandler", r) // Ignore the error } store, err := session.Start(r.Context(), w, r) if err != nil { return } uid, ok := store.Get("LoggedInUserID") if !ok { if r.Form == nil { r.ParseForm() } store.Set("ReturnUri", r.Form) store.Save() w.Header().Set("Location", "/login") w.WriteHeader(http.StatusFound) return } userID = uid.(string) store.Delete("LoggedInUserID") store.Save() return } //loginHandler 三方授权登录 func loginHandler(w http.ResponseWriter, r *http.Request) { if dumpvar { _ = dumpRequest(os.Stdout, "login", r) // Ignore the error } store, err := session.Start(r.Context(), w, r) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } if r.Method == "POST" { if r.Form == nil { if err := r.ParseForm(); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } } if !checkPwd(r.Form.Get("username"), r.Form.Get("password")) { outputHTML(w, r, "static/login.html") } store.Set("LoggedInUserID", r.Form.Get("username")) store.Save() w.Header().Set("Location", "/auth") w.WriteHeader(http.StatusFound) return } outputHTML(w, r, "static/login.html") } //authHandler func authHandler(w http.ResponseWriter, r *http.Request) { if dumpvar { _ = dumpRequest(os.Stdout, "auth", r) // Ignore the error } store, err := session.Start(nil, w, r) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } if _, ok := store.Get("LoggedInUserID"); !ok { w.Header().Set("Location", "/login") w.WriteHeader(http.StatusFound) return } outputHTML(w, r, "static/auth.html") } func outputHTML(w http.ResponseWriter, req *http.Request, filename string) { file, err := os.Open(filename) if err != nil { http.Error(w, err.Error(), 500) return } defer file.Close() fi, _ := file.Stat() http.ServeContent(w, req, file.Name(), fi.ModTime(), file) } func test(w http.ResponseWriter, r *http.Request) { if dumpvar { _ = dumpRequest(os.Stdout, "test", r) // Ignore the error } token, err := srv.ValidationBearerToken(r) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } data := map[string]interface{}{ "expires_in": int64(token.GetAccessCreateAt().Add(token.GetAccessExpiresIn()).Sub(time.Now()).Seconds()), "client_id": token.GetClientID(), "user_id": token.GetUserID(), } e := json.NewEncoder(w) e.SetIndent("", " ") e.Encode(data) } //密码验证 func checkPwd(name, pwd string) bool { return loginName == name && pwd == passWord }login.html<!DOCTYPE html> <html lang="zh"> <head> <meta charset="UTF-8"> <title>Login</title> <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css"> <script src="//code.jquery.com/jquery-2.2.4.min.js"></script> <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script> </head> <body> <div class="container"> <h1>Login In</h1> <form action="/login" method="POST"> <div class="form-group"> <label for="username">User Name</label> <input type="text" class="form-control" name="username" required placeholder="Please enter your user name"> </div> <div class="form-group"> <label for="password">Password</label> <input type="password" class="form-control" name="password" placeholder="Please enter your password"> </div> <button type="submit" class="btn btn-success">Login</button> </form> </div> </body> </html>auth.html<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>Auth</title> <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" /> <script src="//code.jquery.com/jquery-2.2.4.min.js"></script> <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script> </head> <body> <div class="container"> <div class="jumbotron"> <form action="/oauth/authorize" method="POST"> <h1>Authorize</h1> <p>The client would like to perform actions on your behalf.</p> <p> <button type="submit" class="btn btn-primary btn-lg" style="width:200px;" > Allow </button> </p> </form> </div> </div> </body> </html>6 小总结OK,到这里OAuth2.0的讲解就快要结束了,当然由于时间关系,文章中有些内容讲解的可能不够详细,希望读者朋友能够给予指出。文中的代码案例主要采用Go语音进行实现,除此之外Spring社区中也有相关的实现,语言并不是局限。在实际的项目中可能会更加的复杂,但是思想都是一致的,在业务上可能或多或少有所补充,这就需要我们一起在工作中不断学习了。最后,有一个小思考想分享一下,为什么用户在第三方认证完成后使用返回的Code换取Token,而不是直接使用Code进行后续的步骤呢?在这里我先给出我的思考和一位前辈的指点:首先当然是安全,一般Code只能兑换一次token,如果你获取Code后,无法授权,则系统自然会发现被黑客攻击了,会重新授权,那么之前的token就无效了。其次还是为了安全,Code是服务端生成的,防止Code被拿到后多次请求被认为是恶意请求,而token每次请求后都会变化,且有过期时间。(接下来的原因还请读者朋友们积极讨论)参考:https://razeen.me/posts/oauth2-protocol-detailshttps://www.rfc-editor.org/rfc/rfc6749.htmlhttps://zhuanlan.zhihu.com/p/509212673https://www.zhihu.com/question/275041157/answer/1342887745
1 概述如题,本次玩转MongoDB我们从搭建集群开始,话说MongoDB一共有三种搭建集群的方式,但是由于版本更新,据说在4.0版本之后第一种方式,也就是主从复制的方式被遗弃掉了,大概是因为这种方式的效率不高吧,因为目前我们使用的是5.x版本,因此就不花时间讲解第一种方式了,在其他的文章上摘录了一下,可供大家参考。重点还是要放在后两种。2 方式一:主从复制(1)准备三个机器一个主机两个备机mongod --port 27007 --dbpath /data/master/ --bind_ip 0.0.0.0 --master --oplogSize 50mongod --port 27008 --dbpath /data/slave1 --bind_ip 0.0.0.0 --slave --source 127.0.0.1:27007 --only ymxmongod --dbpath /data/slave2 --port 27009 --bind_ip 0.0.0.0 --slave --source 127.0.0.1:27007 --only ymx --slavedelay 30(2)主从复制的选项--master :主节点--slave :从节点--source [server:port] :为从节点时从哪个主节点复制<>--only [库名] : 为从节点时只复制主节点的那个库--slavedelay [秒数]:从节点延迟多长时间复制主节点 秒--autoresync :从机数据不是最新是自动重新同步数据--oplogSize :主节点的操作日志单位是M(3)查看集群状态rs.help();rs.slaveOk(); //开启从机查询3 方式二:Replica Set(副本集)" title="">(图片来自:https://www.mongodb.com/docs/manual/replication/)Replica Set概念翻译过来就是副本集 ,也是相当于主从复制的意思,主节点负责读写数据,从节点负责备份数据,当主节点宕机后,剩余从节点会自动的选取出主节点进行数据的读写。(图片来自:https://www.mongodb.com/docs/manual/replication/)两个角色:Primary:主节点,负责数据的交互Secondary:从节点,负责数据的备份3.1 总体流程环境:一台Linux虚拟机,三个MongoDB节点(一主二从)1. 创建对应目录 2. 根据目录和端口以及其他集群参数启动各个节点 3. 在主节点进行集群配置 4. 进入各个从节点观察 5. 配置成功3.2 具体流程3.2.1 创建对应目录ymx@ymxdedeepin:/data$ sudo mkdir db1 ymx@ymxdedeepin:/data$ sudo mkdir db2 ymx@ymxdedeepin:/data$ sudo mkdir db3 ymx@ymxdedeepin:/data$ ls -al drwxrwxrwx 6 root root 0 6月 26 16:28 . drwxr-xr-x 19 root root 0 6月 26 07:37 .. drwxrwxrwx 4 root root 0 6月 26 21:52 db1 drwxrwxrwx 4 root root 0 6月 26 21:52 db2 drwxrwxrwx 4 root root 0 6月 26 21:52 db3 ymx@ymxdedeepin:/data$ cd .. ymx@ymxdedeepin:/$ sudo chmod 777 -R data/3.2.2 启动各个节点mongod --port 27011 --dbpath /data/db1 --bind_ip 0.0.0.0 --replSet ymx/localhost:27012 mongod --port 27012 --dbpath /data/db2 --bind_ip 0.0.0.0 --replSet ymx/localhost:27013 mongod --port 27013 --dbpath /data/db3 --bind_ip 0.0.0.0 --replSet ymx/localhost:27011语法:[命令] --port [端口] --dbpath [目录] --bind_ip [绑定IP] --replSet [副本集名称]/[相邻接点主机名:端口号] mongod --port 27011 --dbpath /data/db1 --bind_ip 0.0.0.0 --replSet ymx/localhost:27012概念:3.2.3 进入配置语法:var config = { _id:[副本集名称], members:[ {_id:0,host:[节点主机名:端口号]}, {_id:1,host:[节点主机名:端口号]}, ...... } rs.initiate(config);实例:ymx@ymxdedeepin:~/Desktop/mongodb/bin$ ./mongo 127.0.0.1:27011 ...... > use admin switched to db admin > > var config = { ... _id:"ymx", ... members:[ ... {_id:0,host:"localhost:27011"}, ... {_id:1,host:"localhost:27012"}, ... {_id:2,host:"localhost:27013"}] ... } > rs.initiate(config); { "ok" : 1, "$clusterTime" : { "clusterTime" : Timestamp(1656250256, 1), "signature" : { "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="), "keyId" : NumberLong(0) } }, "operationTime" : Timestamp(1656250256, 1) } ymx:SECONDARY> ymx:PRIMARY> ymx:PRIMARY> show databases admin 0.000GB config 0.000GB local 0.000GB3.2.4 进入其他节点观察ymx@ymxdedeepin:~/Desktop/mongodb/bin$ ./mongo 127.0.0.1:27012 ...... ymx:SECONDARY> ymx@ymxdedeepin:~/Desktop/mongodb/bin$ ./mongo 127.0.0.1:27013 ...... ymx:SECONDARY> 3.2.5 配置成功3.3 注意点3.3.1 Driver的URLmongodb://127.0.0.1:27017,127.0.0.1:27018,127.0.0.1:27019/ems(库名)?replcaSet=spock(副本集名称)3.3.2 从节点访问数据从节点是不能直接访问数据的,如果需要访问,则可以使用rs.slaveOk();4 方式三:Sharding(分片)(图片来自:https://www.mongodb.com/docs/manual/sharding/)三个角色:Router:路由,客户端由此接入,且让整个集群看上去像单一数据库。Config Servers:mongod实例,存储了整个 ClusterMetadata,即元数据。Shard:用于存储实际的数据块,实际生产环境中一个shard server角色可由几台机器组个一个replica set承担,防止主机单点故障4.1 总体流程环境:一台Linux虚拟机,一个Router、两个Shard、两个Config Servers1. 创建数据目录 2. 根据端口和目录启动Shard节点 3. 根据端口和目录启动Config Servers节点 4. 配置Config Servers节点副本集 5. 启动Router节点 6. 利用mongos进入路由配置分片节点 7. 利用mongos配置分片数据库和集合以及片键信息4.2 具体流程4.2.1 创建数据目录ymx@ymxdedeepin:/$ cd data/ ymx@ymxdedeepin:/data$ sudo mkdir shard_test 请输入密码: 验证成功 ymx@ymxdedeepin:/data$ cd shard_test/ ymx@ymxdedeepin:/data/shard_test$ ls ymx@ymxdedeepin:/data/shard_test$ sudo mkdir db1 db2 config1 config2 ymx@ymxdedeepin:/data/shard_test$ ls config1 config2 db1 db2 ymx@ymxdedeepin:/data/shard_test$ cd .. ymx@ymxdedeepin:/data$ sudo chmod 777 -R shard_test/4.2.2 启动Shard节点mongod --port 27021 --dbpath /data/shard_test/db1 --bind_ip 0.0.0.0 --shardsvr --replSet "shards"/localhost:27022mongod --port 27022 --dbpath /data/shard_test/db2 --bind_ip 0.0.0.0 --shardsvr --replSet "shards"/localhost:27021进入Shard节点进行配置:ymx@ymxdedeepin:~/Desktop/mongodb/bin$ ./mongo 127.0.0.1:27011 ...... > use admin switched to db admin > > var config = { ... _id:"shards", ... members:[ ... {_id:0,host:"localhost:27022"}, ... {_id:1,host:"localhost:27021"},] ... } > rs.initiate(config); { "ok" : 1, "$clusterTime" : { "clusterTime" : Timestamp(1656250256, 1), "signature" : { "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="), "keyId" : NumberLong(0) } }, "operationTime" : Timestamp(1656250256, 1) }4.2.3 启动Config Servers节点mongod --port 27100 --dbpath /data/shard_test/config1 --bind_ip 0.0.0.0 --replSet "configs"/localhost:27101 --configsvrmongod --port 27101 --dbpath /data/shard_test/config2 --bind_ip 0.0.0.0 --replSet "configs"/localhost:27100 --configsvr4.2.4 配置Config Servers节点副本集use admin var config = { _id:"configs", configsvr: true, members:[ {_id:0,host:"localhost:27100"}, {_id:1,host:"localhost:27101"}] } rs.initiate(config);实例:ymx@ymxdedeepin:~/Desktop/mongodb/bin$ ./mongo -port 27999 ...... mongos> use admin switched to db admin mongos> var config = { _id:"configs", configsvr: true, members:[ {_id:0,host:"localhost:27100"}, {_id:1,host:"localhost:27101"} ] } mongos> rs.initiate(config);4.2.5 启动Router节点./mongos --port 27999 --configdb "configs"/localhost:27100,localhost:27101 --bind_ip 0.0.0.0注意: config为上面的副本集名称4.2.6 利用mongos进入路由配置分片节点./mongo -port 27999 use admin db.runCommand({ addshard:"localhost:27021","allowLocal":true }); db.runCommand({ addshard:"localhost:27022","allowLocal":true });4.2.7 利用mongos配置分片数据库和集合以及片键信息设置分片的库:db.runCommand({ enablesharding:"[库名]" });设置库中集合和片键信息:db.runCommand({ shardcollection: "[库名].users", key: { _id:1}}) //按照id分片db.runCommand({ shardcollection: "[库名].users", key: { _id: "hashed"}}) //按照hash分片5 总结总结下三种类型的优缺点吧:主从复制:优点:配置简单缺点:被不推荐使用了Replica Set:优点:高可用,能够自动故障转移缺点:占用存储空间过多,数据冗余Sharding:优点:存储空间的利用率较高缺点:节点类型复杂see you~
1 都有哪些语言有MongoDB的Driver诺,如图:你以为只有这些吗?太小看我们的工程师群体了,我们还有研究并开源的社区版本的Drivers,如Dart、Erlang、Kotlin等等附上链接:https://www.mongodb.com/docs/drivers/community-supported-drivers/2 Go连接MongoDBDoc.:https://www.mongodb.com/docs/drivers/go/current/2.1 添加依赖go get go.mongodb.org/mongo-driver/mongo2.2 代码main函数package main import ( "context" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" ) var Coll *mongo.Collection func main() { //URL规则: //mongodb://[username:password@]host1[:port1][,...hostN[:portN]][/[defaultauthdb][?options]] client, err := mongo.Connect(context.TODO(), options.Client().ApplyURI("mongodb://127.0.0.1:27017")) if err != nil { panic(err) } defer func() { if err := client.Disconnect(context.TODO()); err != nil { panic(err) } }() Coll = client.Database("test").Collection("books") //InsertOne() //InsertMany() //FindOne() //FindAny() //UpdateOne() //UpdateAny() DeleteOne() }具体代码:package main import ( "context" "encoding/json" "fmt" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo/options" ) //InsertOne 添加一个 func InsertOne() { result, err := Coll.InsertOne( context.TODO(), bson.D{ {"type", "计算机"}, {"name", "《MongoDB教程》"}, {"price", 9.9}, {"vendor", bson.A{"JD", "TB"}}, }, ) if err != nil { panic(err) } fmt.Println(result) } //InsertMany 添加一批 func InsertMany() { docs := []interface{}{ bson.D{{"type", "计算机"}, {"name", "《Go教程》"}, {"price", 19.9}, {"vendor", bson.A{"JD", "TB"}}}, bson.D{{"type", "人文"}, {"name", "《人间词话》"}, {"price", 9.9}, {"vendor", bson.A{"PDD", "TB"}}}, bson.D{{"type", "计算机"}, {"name", "《Java教程》"}, {"price", 29.9}, {"vendor", bson.A{"JD", "TB", "PDD"}}}, bson.D{{"type", "计算机"}, {"name", "《Redis教程》"}, {"price", 19.9}, {"vendor", bson.A{"JD", "TB"}}}, } result, err := Coll.InsertMany(context.TODO(), docs) if err != nil { panic(err) } fmt.Println(result) } //FindOne 查找一个,多个符合条件的也返回一个 func FindOne() { var result bson.M err := Coll.FindOne(context.TODO(), bson.D{{"type", "计算机"}}).Decode(&result) if err != nil { panic(err) } jsonData, err := json.MarshalIndent(result, "", "") if err != nil { panic(err) } fmt.Printf("%s\n", jsonData) } //FindAny 查看多个 func FindAny() { var result bson.M //取前两条 cursor, err := Coll.Find(context.TODO(), bson.D{}, options.Find().SetLimit(2)) if err != nil { panic(err) } for cursor.Next(context.TODO()) { cursor.Decode(&result) jsonData, err := json.MarshalIndent(result, "", "") if err != nil { panic(err) } fmt.Printf("%s\n", jsonData) } } //UpdateOne 修改一个 func UpdateOne() { result, err := Coll.UpdateOne( context.TODO(), //修改条件 bson.D{{"price", 9.9}}, //修改结果 bson.D{{"$set", bson.D{{"price", 39.9}}}}, ) if err != nil { panic(err) } //修改文档的数量 fmt.Println(result.ModifiedCount) } //UpdateAny 修改符合条件的多个 func UpdateAny() { result, err := Coll.UpdateMany( context.TODO(), //修改条件 bson.D{{"price", 39.9}}, //修改结果 bson.D{{"$set", bson.D{{"price", 9.99}}}}, ) if err != nil { panic(err) } //修改文档的数量 fmt.Println(result.ModifiedCount) } //DeleteOne 删除一个 func DeleteOne() { result, err := Coll.DeleteOne( context.TODO(), bson.D{{"name", "《Redis教程》"}}, ) if err != nil { panic(err) } //删除文档的数量 fmt.Println(result.DeletedCount) }3 Java连接MongoDBDoc.:https://www.mongodb.com/docs/drivers/java-drivers/3.1 依赖<dependency> <groupId>org.mongodb</groupId> <artifactId>mongodb-driver-sync</artifactId> <version>4.6.1</version> <exclusions> <exclusion> <groupId>org.mongodb</groupId> <artifactId>mongodb-driver-core</artifactId> </exclusion> <exclusion> <groupId>org.mongodb</groupId> <artifactId>bson</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.mongodb</groupId> <artifactId>mongodb-driver-core</artifactId> <version>4.6.1</version> <exclusions> <exclusion> <groupId>org.mongodb</groupId> <artifactId>bson</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.mongodb</groupId> <artifactId>bson</artifactId> <version>4.6.1</version> </dependency>3.2 代码package org.ymx.sb_mongodb.cache; import com.mongodb.client.*; import com.mongodb.client.model.Filters; import com.mongodb.client.model.Updates; import com.mongodb.client.result.DeleteResult; import com.mongodb.client.result.InsertManyResult; import com.mongodb.client.result.InsertOneResult; import com.mongodb.client.result.UpdateResult; import org.bson.Document; import org.bson.conversions.Bson; import java.util.ArrayList; import java.util.Arrays; import static com.mongodb.client.model.Filters.eq; public class MongoDBUtil { private String url = "mongodb://localhost:27017"; private MongoClient client; private MongoCollection<Document> collection; public void init() { // 注意,这里我们配置mongos服务信息即可 client = MongoClients.create(url); //获取数据库 MongoDatabase database = client.getDatabase("test"); // 获取集合 collection = database.getCollection("books"); } public void insertOne() { Document doc = new Document("type", "计算机") .append("name", "《JavaScript教程》") .append("price", 59.9) .append("vendor", Arrays.asList("JD", "TB")); InsertOneResult result = collection.insertOne(doc); System.out.println(result.getInsertedId()); } public void findOne() { Bson eq = eq("type", "计算机"); FindIterable<Document> find = collection.find(eq); Document first = find.first(); System.out.println(first); } public void insertMany() { ArrayList<Document> documents = new ArrayList<>(); Document doc1 = new Document("type", "历史") .append("name", "《人类简史》") .append("price", 69.9) .append("vendor", Arrays.asList("JD", "TB")); Document doc2 = new Document("type", "历史") .append("name", "《近代史》") .append("price", 29.9) .append("vendor", Arrays.asList("JD", "TB")); Document doc3 = new Document("type", "计算机") .append("name", "《Python教程》") .append("price", 59.9) .append("vendor", Arrays.asList("JD", "TB")); documents.add(doc1); documents.add(doc2); documents.add(doc3); InsertManyResult result = collection.insertMany(documents); System.out.println(result.getInsertedIds()); } public void updateOne() { Bson bson = Filters.eq("type", "计算机"); Bson bson1 = Updates.set("type", "科学"); UpdateResult result = collection.updateOne(bson, bson1); System.out.println(result.getModifiedCount()); } public void findMany() { FindIterable<Document> documents = collection.find(); documents.forEach(document -> { System.out.println(document); }); } public void deleteOne() { DeleteResult result = collection.deleteOne(eq("name", "《MongoDB教程》")); System.out.println(result.getDeletedCount()); } public static void main(String[] args) { MongoDBUtil util = new MongoDBUtil(); util.init(); //util.insertOne(); //util.findOne(); //util.insertMany(); //util.updateOne(); //util.findMany(); //util.deleteOne(); } }4 总结本篇文章仅展示了不同的编程语言连接MongoDB中最常用的操作,当然MongoDB的可用操作不止这些,大家还可以利用官方文档进行深入的研究。参考:https://www.zhangbj.com/p/670.html
1 基本介绍官网:https://www.mongodb.com/Doc.:https://www.mongodb.com/docs/GitHub:https://github.com/mongodb/mongo1.1 MongoDB是什么MongoDB是一个非关系型文档数据库,旨在简化开发和扩展。 什么是文档数据库?首先,文档数据库并不是存放文档文件的数据库,在应用程序层中,数据经常表示为 JSON 文档,并将其数据模型视为文档,因此存放该数据模型的数据库就被称为文档数据库。文档数据库也是非关系型数据库(NoSQL)的一种,一个文档相当于关系数据库中的一条记录。1.2 重要概念MongoDB中的一条记录是一个文档,它是由字段和值对组成的数据结构。 MongoDB文档类似于JSON对象。 字段的值可以包括其他文档、数组和文档数组。MongoDB中有三层概念:Database、Collection、Document,他们的关系是Database>Collection>DocumentDatabase:数据库,也类似于关系型数据库中的数据库Collection:集合,类似于关系型数据库中的表Document:文档,类似于关系型数据库中的一条数据1.3 关键特性高性能查询API高可用性水平伸缩性支持多种存储引擎1.4 BSON数据类型BSON是MongoDB中用于存储文档和进行远程过程调用的二进制序列化格式。 JSON和BSON的区别:与JSON相比,BSON着眼于提高存储和扫描效率。BSON文档中的大型元素以长度字段为前缀以便于扫描。BSON使用的空间会多于JSON。另外,除了支持 JSON 中的数据类型外,BSON 还支持日期(Date)和二进制(BinData)等类型。JSONBSONJSON 是 javascript 对象表示法BSON 是二进制 JSON是一种轻量级的、基于文本的、开放的数据交换格式是一种二进制序列化文档格式JSON 包含一些基本数据类型,如字符串、数字、布尔值、空值除了支持 JSON 中的类型外,BSON 还包含一些额外的数据类型,例如日期(Date)、二进制(BinData)等AnyDB、redis 等数据库将数据存储为 JSON 格式MongoDB 中将数据存储为 BSON 格式主要用于传输数据主要用于存储数据没有响应的编码和解码技术有专用的编码和解码技术如果想从 JSON 文件中读取指定信息,需要遍历整个数据在 BSON 中,可以使用索引跳过到指定内容JSON 格式不需要解析,因为它是人类可读的BSON 需要解析,因为它是二进制的JSON 是对象和数组的组合,其中对象是键值对的集合,而数组是元素的有序列表BSON 是二进制数据,在其中可以存储一些附加信息,例如字符串长度、对象类型等2 安装使用各版本下载地址:https://www.mongodb.com/docs/manual/installation/2.1 Windows下安装双击安装包:默认,直接下一步:直接下一步:2.2 工具安装下载地址:https://www.mongodb.com/try/download/tools操作MongoDB最常用的工具有两个,如图,一个是命令行操作,一个是类似于Web页面的操作。2.2.1 MongoDB ShellMongoDB Shell是连接和使用MongoDB最快的方法。通过这个现代的、可扩展的命令行界面,可以轻松地查询数据、配置设置和执行其他操作,具体的功能有语法高亮显示、智能自动完成、上下文帮助和错误消息。2.2.2 MongoDB CompassMongoDB Compass是 MongoDB的GUI,也就是可视化界面,直观而灵活,提供了详细的模式可视化、实时性能指标、复杂的查询功能等等。MongoDB Compass有三个版本:一个拥有所有特性的完整版本,一个没有写或删除功能的只读版本,以及一个单独的版本,它的唯一网络连接到MongoDB实例。3 初步使用3.1 使用MongoDB Shell进行CRUDhttps://www.mongodb.com/docs/mongodb-shell/crud/3.1.1 连接到MongoDB运行mongosh,不带任何命令行选项,连接到运行在你的localhost上的MongoDB实例,默认端口27017:mongosh带参数的连接命令:# 使用URL连接 mongosh "mongodb://localhost:27017" # 指定端口号连接 mongosh --port 28015 # 自定义URL连接 mongosh "mongodb://mongodb0.example.com:28015" # 自定义host和port连接 mongosh --host mongodb0.example.com --port 28015 # 使用用户名和密码连接 mongosh "mongodb://mongodb0.example.com:28015" --username alice --authenticationDatabase admin实例:3.1.2 增insertOne() 返回一个包含新插入文档的' _id '字段值的文档。insertMany()返回一个包含新插入文档' ' _id '字段值的文档。插入一个:# 选择数据库 use test # 插入数据 db.movies.insertOne( { title: "The Favourite", genres: [ "Drama", "History" ], runtime: 121, rated: "R", year: 2018, directors: [ "Yorgos Lanthimos" ], cast: [ "Olivia Colman", "Emma Stone", "Rachel Weisz" ], type: "movie" } )查询:db.movies.find( { title: "The Favourite" } )同时插入多个:use test db.movies.insertMany([ { title: "Jurassic World: Fallen Kingdom", genres: [ "Action", "Sci-Fi" ], runtime: 130, rated: "PG-13", year: 2018, directors: [ "J. A. Bayona" ], cast: [ "Chris Pratt", "Bryce Dallas Howard", "Rafe Spall" ], type: "movie" }, { title: "Tag", genres: [ "Comedy", "Action" ], runtime: 105, rated: "R", year: 2018, directors: [ "Jeff Tomsic" ], cast: [ "Annabelle Wallis", "Jeremy Renner", "Jon Hamm" ], type: "movie" } ])3.1.3 删deleteMany():该方法返回一个包含操作状态的文档deleteOne():删除一个文档,该方法返回一个包含操作状态的文档删除全部:db.movies.deleteMany({})根据条件删除多个:db.movies.deleteMany( { title: "Titanic" } )根据条件删除一个:use sample_mflix db.movies.deleteOne( { cast: "Brad Pitt" } )3.1.4 改updateOne():修改一个updateMany():同时修改多个replaceOne():替换一个根据添加修改:use test db.movies.updateOne( { title: "Tag" }, { $set: { plot: "One month every year, five highly competitive friends hit the ground running for a no-holds-barred game of tag" } { $currentDate: { lastUpdated: true } } })修改多个:db.movies.updateMany( { security_deposit: { $lt: 100 } }, # 修改security_deposit小于100的 { $set: { security_deposit: 100, minimum_nights: 1 } } )替换一个:db.movies.replaceOne( { account_id: 371138 }, # 替换account_id为371138的 { account_id: 893421, limit: 5000, products: [ "Investment", "Brokerage" ] } )3.1.5 查find():查找全部或根据条件返回查找全部:use test db.movies.find()根据条件查找:db.movies.find( { "title": "Titanic" } )3.2 MongoDB Compass简单使用3.2.1 连接MongoDB Server3.2.2 查看数据库3.2.3 查看数据3.2.3 添加和修改数据选择添加一个文档:会自动生成ID,我们不需要改,只填写主体内容即可:点击这个按钮可以修改:4 小总结今天的分享到这里就结束了,如果有讲的不清楚的地方请在下发留言哈,在下一节我们将会使用MongoDB官方的Drivers来进行不同的编程语言对MongoDB的操作,敬请期待~ :bamboo:参考文章:http://c.biancheng.net/json/json-bson.htmlhttps://www.mongodb.com/docs/mongodb-shell/crud/
经常接触云服务或云厂商软件的UU们一定回听说过这三个概念:IaaSPaaSSaaS当然很多小伙伴乍一看肯定不知道是什么意思,下面我就用比较通俗的例子解释下他们三个分别是什么意思,代表着行业中的哪些概念。就拿我上次分享的这个项目说起:【基于数据可视化的毕业生管理系统】假设真的有高校想要用我的项目进行对他们学校的毕业生信息进行统计管理,那么我肯定会直接奉献出去(嘻嘻嘻),这个时候对方想要使用该软件服务时必须要有一个服务方,就是提供服务的那一端,而且提供服务的形式可能有许多种,今天要说的IaaS、PaaS、SaaS这三个概念就是属于当下行业中最流行的三种不同形式,接下来我们分别做介绍。1 IaaS-基础设施即服务IaaS是基础设施即服务。英文全称是Infrastructure as a server。概念:需要帮助客户搭建好运行服务的基础设施,就是在线下筹备机房或机器,组成环境运行能够提供服务的项目并部署该项目提供服务。举例:举例说就是我要在提供服务之前给客户那边搭建好线下的运行环境,比如在高校的校园中选择一个房间作为机房,在准备电脑进行组网,将项目运行在搭建好的机器上面就可以进行服务的访问。优缺点分析:优势:自主性,灵活性,安全性较高劣势:不易扩展成本较高2 PaaS-平台即服务PaaS是平台即服务,英文全称是Platform as a Server。概念:利用云端搭建好操作系统或软件层面的如数据库、中间件等供用户使用,使得用户无需关注底层的基础设施和运行环境,只需要利用这些环境运行自己的应用和数据。举例:就好比学校要使用我的就业管理系统进行管理,但是不想自己搭建机房和环境,所以就直接本地用少量的机器搭建客户端,将数据库、中间件或部分接口放在云端,请求时直接请求云端的接口和数据。优缺点分析:优势:节省部分资源较容易资源扩展劣势:自主性,灵活性一般3 SaaS-软件即服务SaaS是软件即服务。英文全称是Software as a Server。概念:即云端已经将操作系统到运行环境到软件的客户端都已经搭建好了,使用方不需要安装任何环境或软件,只需要访问客户端就能直接使用、举例:类似于在我的毕业生管理系统上,我先自己利用云端搭建好平台,然后如果有学校想要使用的话直接分配给他们一些账号和密码,他们就可以直接访问我搭建好的系统进行操作了。优缺点分析:优势:节省资源和运维成本劣势:安全性一般4 小总结随着云计算的快速发展,已经有这么多种服务的提供方式,当然每种方式都有各自的优缺点,我们在选择服务方式的同时也要根据自己的需求来进行合理的选择。今天的分享就到这里咯~参考:https://worktile.com/blog/know-92/https://www.bilibili.com/video/BV1RR4y1c7qDhttps://blog.csdn.net/m0_37631322/article/details/121959887
应一位粉丝的要求发此文章~1 什么是JMeter官网:https://jmeter.apache.orgApache JMeter是由Apache开源的、100% 纯 Java 的应用程序,旨在对程序性能进行测试 。它最初是为测试 Web 应用程序而设计的,但后来扩展到其他测试功能。使用JMeter能够测试哪些功能:Web接口(包括HTTP、HTTPS)SOAP / REST Web 服务FTP通过 JDBC 的数据库LDAP通过 JMS 的面向消息的中间件 (MOM)邮件 - SMTP(S)、POP3(S) 和 IMAP(S)本机命令或 shell 脚本TCPJava 对象2 为什么需要JMeter每个测试工程师,必须掌握的测试工具,熟练使用Jmeter能大大提高工作效率。熟练使用Jmeter后, 能用Jmeter搞定的事情,你就不会使用LoadRunner了。Jmeter 是一款使用Java开发的,主要用来做功能测试和性能测试(压力测试/负载测试). 而且用Jmeter 来测试 Restful API, 非常好用。3 下载JMeter下载地址:https://jmeter.apache.org/download_jmeter.cgi运行时需要环境:JDK8+下载后的目录:主要使用到的目录及其作用:bin:启动文件目录docs:相关文档4 初步使用总体使用流程:(1)新建测试计划(2)启动被测试服务(3)启动JMeter进行测试(4)获取测试结果4.1 使用JMeter的Hello World4.1.1 编写被测试程序package main import ( "fmt" "net/http" ) // i 请求次数 var i int64 // RunHttp1 最简单的HTTP服务 func RunHttp1() { http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) { i += 1 fmt.Println("请求次数:",i) w.Write([]byte("Hello Http!")) }) http.ListenAndServe(":8080", nil) } func main() { RunHttp1() }4.1.2 新建测试计划(1)Windows下启动JMeterbin>jmeter.bat(2)新建测试计划(3)在测试计划下新建测试线程组(4)在线程组下新建HTTP请求请求HTTP接口时的配置设置线程数和循环次数4.1.3 启动JMeter进行测试4.1.4 观察控制台输出启动JMeter测试后我们观察控制台输出就能够清晰的发现JMeter的请求啦4.2 使用测试报告当然总看程序的控制台是很不专业的,因为我们这样只能用过控制台来观察,在实际的生产环境中是很难实现的,因此我们最好是有一个类似于测试报告的东西,当然JMeter也为我们准备了,他就是Listener,用于测试的监听和结果的显示,我们下面来实际演示下:(1)新建Listener(2)设置Lisenter名称(3)启动测试,观察变化4.3 性能和错误测试(1)程序package main import ( "fmt" "net/http" "time" ) var i int64 // RunHttp1 最简单的HTTP服务 func RunHttp1() { http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) { i += 1 fmt.Println("请求次数:", i) w.Write([]byte("Hello Http!")) }) http.HandleFunc("/hi", func(w http.ResponseWriter, r *http.Request) { time.Sleep(time.Second * 1) i += 1 fmt.Println("请求次数:", i) w.Write([]byte("Hi Http!")) }) http.HandleFunc("/err", func(w http.ResponseWriter, r *http.Request) { i += 1 if i%10 == 0 { http.Error(w, "err", 500) } else { w.Write([]byte("Err Http!")) } fmt.Println("请求次数:", i) }) http.ListenAndServe(":8080", nil) } func main() { RunHttp1() }(2)测试结果:4.4 切换中文如果英文版的用起来不是那么的顺手,可以切换成中文哈5 小总结本文仅讲述了最常用也是最简单的JMeter测试HTTP接口的功能,我们只需要根据需求填写我们需要测试的HTTP接口信息即可,并且通过不同种类的Listener可以实时的获取测试报告,能够帮助我们更好的分析出我们的接口标准。最后给大家一个小提示哈:无论是测试自己的开发机器还是服务器,尽量谨慎测试,不要一上来就1W个线程直接跑,可能会给机器带来不太好的影响,请优雅一点。今天的分享就到这里咯~
浅谈云原生技术组件—etcd技术官网:https://etcd.io/GitHub:https://github.com/etcd-io/etcd1 什么是etcd?etcd是一种强一致性的分布式键值存储组件,使用Raft算法,利用Go语言编写,它提供了一种可靠的方式来存储需要由分布式系统或机器集群访问的数据。它在网络分区期间优雅地处理领导者选举,并且可以容忍机器故障,即使在领导者节点中也是如此。特点:操作简单整洁,使用HTTP协议和JSON文件格式进行存取键值对键值对存储,类似于Zookeeper更够根据值的变化快速相应可以利用TLS增强安全性2 动手运行etcd2.1 安装要求支持平台:https://etcd.io/docs/v3.5/op-guide/hardware/硬件要求:https://etcd.io/docs/v3.5/op-guide/supported-platform/本次实验将使用Ubuntu虚拟机,64bit、4GB内存、4核CPU2.2 下载并测试下载页面:https://github.com/etcd-io/etcd/releases/我们选择的版本是v3.5.4,下面演示下:root@ymx-ubuntu:/usr/local# wget https://github.com/etcd-io/etcd/releases/download/v3.5.4/etcd-v3.5.4-linux-amd64.tar.gz ...... 2022-05-15 14:20:34 (1.45 MB/s) - 已保存 “etcd-v3.5.4-linux-amd64.tar.gz” [19432359/19432359]) root@ymx-ubuntu:/usr/local# ls etcd-v3.5.4-linux-amd64.tar.gz ...... root@ymx-ubuntu:/usr/local# tar -zxvf etcd-v3.5.4-linux-amd64.tar.gz ...... root@ymx-ubuntu:/usr/local# cd etcd-v3.5.4-linux-amd64/ root@ymx-ubuntu:/usr/local/etcd-v3.5.4-linux-amd64# ls -al 总用量 56300 drwxr-xr-x 3 528287 89939 4096 4月 24 18:45 . drwxr-xr-x 14 root root 4096 5月 15 14:20 .. drwxr-xr-x 3 528287 89939 4096 4月 24 18:45 Documentation -rwxr-xr-x 1 528287 89939 23564288 4月 24 18:45 etcd -rwxr-xr-x 1 528287 89939 17960960 4月 24 18:45 etcdctl -rwxr-xr-x 1 528287 89939 16039936 4月 24 18:45 etcdutl -rw-r--r-- 1 528287 89939 42066 4月 24 18:45 README-etcdctl.md -rw-r--r-- 1 528287 89939 7359 4月 24 18:45 README-etcdutl.md -rw-r--r-- 1 528287 89939 9394 4月 24 18:45 README.md -rw-r--r-- 1 528287 89939 7896 4月 24 18:45 READMEv2-etcdctl.md root@ymx-ubuntu:/usr/local/etcd-v3.5.4-linux-amd64# ./etcd -version etcd Version: 3.5.4 Git SHA: 0xxxxxx Go Version: go1.16.15 Go OS/Arch: linux/amd64etcd:etcd服务etcdctl :etcd的命令行客户端etcdutl :etcd的命令行管理工具2.3 运行并初步使用etcd2.3.1 设置环境变量root@ymx-ubuntu:/usr/local/etcd-v3.5.4-linux-amd64# vim /etc/profile #### 在文件末尾加入以下内容 export ETCD_HOME=/usr/local/etcd-v3.5.4-linux-amd64 export PATH=$PATH:$ETCD_HOME2.3.1 启动etcd(单节点)TOKEN=my_etcd CLUSTER_STATE=new NAME_1=my_etcd_name HOST_1=127.0.0.1 CLUSTER=${NAME_1}=http://${HOST_1}:2380 THIS_NAME=${NAME_1} THIS_IP=${HOST_1} etcd --data-dir=data.etcd --name ${THIS_NAME} \ --initial-advertise-peer-urls http://${THIS_IP}:2380 --listen-peer-urls http://${THIS_IP}:2380 \ --advertise-client-urls http://${THIS_IP}:2379 --listen-client-urls http://${THIS_IP}:2379 \ --initial-cluster ${CLUSTER} \ --initial-cluster-state ${CLUSTER_STATE} --initial-cluster-token ${TOKEN}2.3.2 使用 etcdctl 连接到 etcdetcdctl --endpoints=127.0.0.1:2379 member list演示:root@ymx-ubuntu:/usr/local/etcd-v3.5.4-linux-amd64# ./etcdctl --endpoints=127.0.0.1:2379 member list xxxxxxxx, started, my_etcd_name, http://127.0.0.1:2380, http://127.0.0.1:2379, false2.3.3 使用etcd的Hello WorldENDPOINTS=127.0.0.1:2379 etcdctl --endpoints=$ENDPOINTS put foo "Hello World!" etcdctl --endpoints=$ENDPOINTS get foo etcdctl --endpoints=$ENDPOINTS --write-out="json" get foo演示:root@ymx-ubuntu:/usr/local/etcd-v3.5.4-linux-amd64# ./etcdctl --endpoints=$ENDPOINTS put foo "Hello World!" OK root@ymx-ubuntu:/usr/local/etcd-v3.5.4-linux-amd64# ./etcdctl --endpoints=$ENDPOINTS get foo foo Hello World! root@ymx-ubuntu:/usr/local/etcd-v3.5.4-linux-amd64# ./etcdctl --endpoints=$ENDPOINTS --write-out="json" get foo {"header":{"cluster_id":87772999870000089,"member_id":10756500090258089453,"revision":3,"raft_term":2},"kvs":[{"key":"Zm9v","create_revision":2,"mod_revision":3,"version":2,"value":"SGV00000ybGQh"}],"count":1}2.3.4 将etcd数据进行持久化ENDPOINTS=127.0.0.1:2379 etcdctl --endpoints=$ENDPOINTS snapshot save my.db etcdctl --write-out=table --endpoints=$ENDPOINTS snapshot status my.db演示:root@ymx-ubuntu:/usr/local/etcd-v3.5.4-linux-amd64# ./etcdctl --endpoints=$ENDPOINTS snapshot save my.db ...... Snapshot saved at my.db root@ymx-ubuntu:/usr/local/etcd-v3.5.4-linux-amd64# ./etcdctl --write-out=table --endpoints=$ENDPOINTS snapshot status my.db Deprecated: Use `etcdutl snapshot status` instead. +----------+----------+------------+------------+ | HASH | REVISION | TOTAL KEYS | TOTAL SIZE | +----------+----------+------------+------------+ | ebfb411d | 3 | 8 | 20 kB | +----------+----------+------------+------------+3 使用客户端连接etcdetcd支持以下客户端:GoJavaScalaPerlPythonNodeRubyCC++......因为作者本人目前只能够熟练的使用Java和Go,因此只那这两个做演示哈~3.1 使用Go操作etcd3.1.1 Go官方推荐的etcd连接工具(3.x版本以上)https://github.com/etcd-io/etcd/tree/main/client/v3下载:go get go.etcd.io/etcd/client/v33.1.2 代码func main() { cli, err := clientv3.New(clientv3.Config{ Endpoints: []string{"127.0.0.1:2379"}, DialTimeout: 5 * time.Second, }) if err != nil { panic(err) } resp, err := cli.Put(context.TODO(), "sample_key", "sample_value") fmt.Println(resp) response, err := cli.Get(context.TODO(), "sample_key") fmt.Println(response) defer cli.Close() }3.2 使用Java连接etcd3.2.1 使用jetcd进行连接https://github.com/etcd-io/jetcdmaven依赖:<dependency> <groupId>io.etcd</groupId> <artifactId>jetcd-core</artifactId> <version>0.5.0</version> </dependency>3.2.2 代码@Test void TestEtcd() throws Exception { //集群模式下可以使用逗号分隔 Client client = Client.builder().endpoints("http://127.0.0.1:2379").build(); KV kvClient = client.getKVClient(); ByteSequence key = ByteSequence.from("test_key".getBytes()); ByteSequence value = ByteSequence.from("test_value".getBytes()); // put值 kvClient.put(key, value).get(); // get值 CompletableFuture<GetResponse> getFuture = kvClient.get(key); GetResponse response = getFuture.get(); System.out.println(response); // 删除值 kvClient.delete(key).get(); }4 小总结下etcd作为Kubernetes集群默认的服务配置中心,可以说是最贴近云原生的,相比Zookeeper,虽说有几分相似,但是他们所使用的分布式一致性算法颇有些不同,etcd使用Raft协议, Zookeeper使用ZAB协议,而且etcd支持使用http和https进行访问,使用上更加方便。参考:https://github.com/etcd-io/jetcdhttps://github.com/etcd-io/etcd/tree/main/client/v3https://github.com/etcd-io/etcdhttps://etcd.io/docs/v3.5/integrations/
下载依赖:go get -u github.com/olivere/elastic/v7Init.gopackage main import ( "context" "fmt" "github.com/olivere/elastic/v7" "log" ) var ctx = context.Background() var Url = "http://127.0.0.1:9200" var esClient *elastic.Client var index = "student" //结构体 type Student struct { Id int `json:"id"` Name string `json:"name"` Age int `json:"age"` Address string `json:"address"` } // 定义一些变量,mapping为定制的index字段类型 // number_of_replicas备份数 , number_of_shards分片数 const mapping = ` { "settings":{ "number_of_shards": 1, "number_of_replicas": 0 }, "mappings":{ "properties":{ "name":{ "type":"keyword" }, "address":{ "type":"text" }, "age":{ "type":"long" }, "id":{ "type":"long" } } } }` // 初始化es连接 func init() { client, err := elastic.NewClient( elastic.SetURL(Url), ) if err != nil { log.Fatal("es 连接失败:", err) } // ping通服务端,并获得服务端的es版本,本实例的es版本为version 7.16.2 info, code, err := client.Ping(Url).Do(ctx) if err != nil { panic(err) } fmt.Println("Elasticsearch call code:", code, " version:", info.Version.Number) esClient = client // fmt.Println("es连接成功") }Add.gopackage main import ( "fmt" "log" ) //添加数据 func AddDoc(index string, data interface{}) (bool, error) { // 添加索引 _, err := addIndex(index) if err != nil { log.Fatal("创建索引失败", err) } // 添加doc 先获取index再为index添加index res, err := esClient.Index(). Index(index). BodyJson(data). Do(ctx) if err != nil { return false, err } fmt.Println("添加数据成功:", res) return true, nil } //添加数据 指定id func AddDocById(id string, index string, data interface{}) (bool, error) { // 添加索引 _, err := addIndex(index) if err != nil { log.Fatal("创建索引失败", err) } // 添加doc 先获取index再为index添加index res, err := esClient.Index(). Index(index). BodyJson(data). Id(id). Do(ctx) if err != nil { return false, err } fmt.Println("添加数据成功:", res) return true, nil } // 添加索引 // 并在7.x后强制要求只能有一个类型,就是 _doc ---> /{index}/_doc/{id} func addIndex(index string) (bool, error) { // 创建index前,先查看es引擎中是否存在自己想要创建的索引index exists, err := esClient.IndexExists(index).Do(ctx) if err != nil { fmt.Println("存在索引:", err) return true, nil } if !exists { // 如果不存在,就创建 BodyString将索引的配置指定为字符串。 createIndex, err := esClient.CreateIndex(index).BodyString(mapping).Do(ctx) if err != nil { return false, err } if !createIndex.Acknowledged { return false, err } } return true, nil }select.gopackage main import ( "encoding/json" "fmt" "github.com/olivere/elastic/v7" "strings" ) // 查询数据 func query(index string, field string, filter elastic.Query, sort string, page int, limit int) (*elastic.SearchResult, error) { // 分页数据处理 isAsc := true if sort != "" { sortSlice := strings.Split(sort, " ") sort = sortSlice[0] if sortSlice[1] == "desc" { isAsc = false } } // 查询位置处理 if page <= 1 { page = 1 } fsc := elastic.NewFetchSourceContext(true) // 返回字段处理 if field != "" { fieldSlice := strings.Split(field, ",") if len(fieldSlice) > 0 { for _, v := range fieldSlice { fsc.Include(v) } } } // 开始查询位置 fromStart := (page - 1) * limit res, err := esClient.Search(). Index(index). FetchSourceContext(fsc). Query(filter). Sort(sort, isAsc). From(fromStart). Size(limit). Pretty(true). Do(ctx) if err != nil { return nil, err } return res, nil } //index:"index" 索引 //field:"name,age" 要查询的字段 //filter:*TermQuery 查询规则 //sort:"age asc" 排序规则 //page:0 页数 //limit:10 条目数量 func QueryDoc(index string, field string, filter elastic.Query, sort string, page int, limit int) (interface{}, error) { res, err := query(index, field, filter, sort, page, limit) strD, _ := json.Marshal(res) if err != nil { fmt.Println("失败:", err) return nil, nil } fmt.Println("执行完成") return string(strD), nil }Update.gopackage main import ( "errors" "fmt" "github.com/olivere/elastic/v7" ) // 条件更新文档 func UpdateDoc(index string, filter elastic.Query, data map[string]interface{}) (bool, error) { // 修改数据组装 if len(data) < 0 { return false, errors.New("修改参数不正确") } scriptStr := "" for k := range data { scriptStr += "ctx._source." + k + " = params." + k + ";" } script := elastic.NewScript(scriptStr).Params(data) res, err := esClient.UpdateByQuery(index). Query(filter). Script(script). Do(ctx) if err != nil { return false, err } fmt.Println("添加数据成功:", res) return true, nil }delete.gopackage main import ( "fmt" "github.com/olivere/elastic/v7" ) // 删除文档 func DeleteDoc(index string, filter elastic.Query) (bool, error) { res, err := esClient.DeleteByQuery(). Query(filter). Index(index). Do(ctx) if err != nil { return false, err } fmt.Println("删除信息:", res) return true, nil }Main.gopackage main import ( "fmt" "github.com/olivere/elastic/v7" ) func main() { //deleteDoc() //add() //update() } func add() { stu := Student{ Id: 2, Address: "北京", Name: "小红", Age: 10, } //res, err := AddDoc(index, stu) res, err := AddDocById("2", index, stu) fmt.Println(res) fmt.Println(err) } func selectDoc() { index_ := index sort := "age asc" page := 0 limit := 10 field := "id,name,age,address" filter := elastic.NewTermQuery("name", "名称") res, err := QueryDoc(index_, field, filter, sort, page, limit) fmt.Println(res) fmt.Println(err) } func deleteDoc() { query := elastic.NewTermQuery("name", "小明") DeleteDoc(index, query) } func update() { index_ := index filter := elastic.NewTermQuery("name", "小明") data := make(map[string]interface{}) data["address"] = "上海" data["age"] = 120 res, err := UpdateDoc(index_, filter, data) if err != nil { fmt.Println("失败:", err) } else { fmt.Println("成功:", res) } }
5.1 pom依赖新建spring boot项目:<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- 依赖web starter--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-elasticsearch</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency>5.2 配置配置文件:es: address: 127.0.0.1 port: 9200配置类:@Configuration public class ElasticsearchConfig extends AbstractElasticsearchConfiguration { @Value("${es.address}") private String address; @Value("${es.port}") private Integer port; @Override @Bean public RestHighLevelClient elasticsearchClient() { final String ES_URL = address + ":" + port; final ClientConfiguration clientConfiguration = ClientConfiguration.builder() .connectedTo(ES_URL) .build(); return RestClients.create(clientConfiguration).rest(); } }5.3 编码测试@SpringBootTest class SpEsApplicationTests { @Autowired private ElasticsearchOperations elasticsearchOperations; @Test void save() { Student student = new Student(3L, "ww", 13, "上海"); IndexQuery indexQuery = new IndexQueryBuilder() .withId(student.getId().toString()) .withObject(student) .build(); String str = elasticsearchOperations.index(indexQuery, IndexCoordinates.of("student")); System.out.println(str); } @Test void findById() { Student student = elasticsearchOperations.get("1", Student.class, IndexCoordinates.of("student")); System.out.println(student); } @Test void findAll() { SearchHits<Student> search = elasticsearchOperations.search(Query.findAll(), Student.class, IndexCoordinates.of("student")); for (SearchHit<Student> hit : search) { System.out.println(hit.getContent()); } } @Test void findBySome(){ Criteria criteria = new Criteria("name").is("zs"); Query query = new CriteriaQuery(criteria); SearchHits<Student> search = elasticsearchOperations.search(query, Student.class, IndexCoordinates.of("student")); for (SearchHit<Student> hit : search) { System.out.println(hit.getContent()); } } @Test void findBySome2(){ Criteria contains = new Criteria("name").contains("s"); Query query = new CriteriaQuery(contains); SearchHits<Student> search = elasticsearchOperations.search(query, Student.class, IndexCoordinates.of("student")); for (SearchHit<Student> hit : search) { System.out.println(hit.getContent()); } } }
1 过程一览DNS解析(如果IP直接访问则此过程省略)客户端与服务端进行TCP三次握手连接客户端发送HTTP请求服务器处理请求并返回HTTP报文浏览器解析渲染页面连接结束2 细节剖析2.1 DNS解析域名系统(英文全称:Domain Name System,简称DNS)是互联网的一项服务。它作为将域名和IP地址相互映射的一个分布式数据库,能够使人更方便地访问互联网。DNS使用UDP端口53。当前,对于每一级域名长度的限制是63个字符,域名总长度则不能超过253个字符。域名系统是Internet上解决网上机器命名的一种系统。就像拜访朋友要先知道别人家怎么走一样,Internet上当一台主机要访问另外一台主机时,必须首先获知其地址,TCP/IP中的IP地址是由四段以“.”分开的数字组成(此处以IPv4的地址为例,IPv6的地址同理),记起来总是不如名字那么方便,所以,就采用了域名系统来管理名字和IP的对应关系。2.1.1 解析类型虽然DNS的功能可以概括为将域名和IP地址相互映射,但是实际上并不是只有简单的映射,而是有多重类型的不同映射关系,就好比我们开发Java系统时的ORM对象关系映射,也可能会在期间进行一些联表查询和验证策略,具体DNS有哪些规则,如下:SOA:起始授权记录;一个区域解析库有且仅能有一个SOA记录,必须位于解析库的第一条记录SOA,是起始授权机构记录,说明了在众多 NS 记录里哪一台才是主要的服务器。A:域名解析成IP地址AAAA(FQDN):域名解析成IPv6地址PTR:反向解析,IP地址解析成域名NS:专用于标明当前区域的DNS服务器,服务器类型为域名服务器CNAME :别名记录MX:邮件交换器TXT:对域名进行标识和说明的一种方式,一般做验证记录时会使用此项,如:反垃圾邮件记录、https验证等2.1.1 A类型解析过程(图片来源:https://s3.51cto.com/wyfs02/M01/8F/4E/wKiom1jaWlKQ67OVAAkppI9dMxc765.jpg)2.1.3 域名访问和IP访问有什么不同虽然域名访问和IP+端口访问都属于浏览器的URL访问,但是也是有很不同的区别的,下面就来介绍下:域名访问容易记住,IP地址不好记域名访问更安全域名访问可以进行DNS负载均衡,而IP访问则很困难域名访问便于服务器IP的更换......反正总结下就是,尽量使用域名作为URL咯~2.2 客户端与服务端进行TCP三次握手连接这个可能是TCP/IP网络协议簇中最重要的一个概念了,直接上图:2.3 客户端发送HTTP请求到了应用层的HTTP这里就是比较好理解的了,首先HTTP是默认使用的TCP80端口,因此HTTP的请求是一定基于TCP连接的,只不过HTTP的报文更加的复杂,占用的报文头更多,通过Whireshark抓取的报文如下:在网上找一个更清楚一点的(来自:https://www.cnblogs.com/zzzwqh/p/12877863.html):2.4 服务器处理请求并返回HTTP报文响应报文与请求报文类似,但是最独特的一点就是存在响应码,具体的就不多讲了,感兴趣的朋友请移步这篇文章《一文带你搞懂HTTP和HTTPS》2.5 浏览器解析渲染页面这里就不用解释了吧,你目前看到的文章就是渲染的结果:satisfied:2.6 连接结束非长连接情况下会进行TCP四次挥手3 总结下以上就是从输入一个URL到页面展示的过程,其实本文的描述也只是基于操作系统层面以上的,什么意思呢?就是在第一步访问DNS的时候,在这之前可能会有一系列复杂的操作,比如网络的交换和路由转发等等,而基于HTTP以上的呢?又涉及到浏览器的页面解析,Javascript的解释编译等等,所以说技术的探索是无限的,加油~
1 前言Pulsar官方支持的客户端库:C++PythonWebSocketGo clientNode.jsC#JavaGitHub中三方的客户端库:GoHaskellScalaRust.NETNode.js具体可参看:https://pulsar.apache.org/docs/zh-CN/next/client-libraries/本次仅演示Go和Java的客户端操作。2 单机模式运行Pulsar[root@iZ2ze4m2 bin]# pwd /root/apache-pulsar-2.10.0/bin [root@iZ2ze4m2 bin]# ./pulsar standalonePS:针对单机启动报错问题,如下面的:可以尝试使用该命令进行启动:./pulsar standalone -nss3 Go客户端操作Pulsar(1)添加依赖 go get -u "github.com/apache/pulsar-client-go/pulsar"(2)生产者package main import ( "context" "fmt" "github.com/apache/pulsar-client-go/pulsar" "log" "time" ) func main() { client, err := pulsar.NewClient(pulsar.ClientOptions{ URL: "pulsar://192.168.71.143:6650", //支持:"pulsar://localhost:6650,localhost:6651,localhost:6652" OperationTimeout: 60 * time.Second, ConnectionTimeout: 60 * time.Second, }) defer client.Close() if err != nil { log.Fatalf("Could not instantiate Pulsar client: %v", err) } producer, err := client.CreateProducer(pulsar.ProducerOptions{ Topic: "my-topic", }) if err != nil { log.Fatal(err) } _, err = producer.Send(context.Background(), &pulsar.ProducerMessage{ Payload: []byte("hello"), }) defer producer.Close() if err != nil { fmt.Println("Failed to publish message", err) } fmt.Println("Published message") }(3)消费者package main import ( "context" "fmt" "github.com/apache/pulsar-client-go/pulsar" "log" "time" ) func main() { client, err := pulsar.NewClient(pulsar.ClientOptions{ URL: "pulsar://192.168.71.143:6650", //支持:"pulsar://localhost:6650,localhost:6651,localhost:6652" OperationTimeout: 60 * time.Second, ConnectionTimeout: 60 * time.Second, }) defer client.Close() if err != nil { log.Fatalf("Could not instantiate Pulsar client: %v", err) } consumer, err := client.Subscribe(pulsar.ConsumerOptions{ Topic: "my-topic", SubscriptionName: "my-sub", Type: pulsar.Shared, }) if err != nil { log.Fatal(err) } defer consumer.Close() for i := 0; i < 10; i++ { msg, err := consumer.Receive(context.Background()) if err != nil { log.Fatal(err) } fmt.Printf("Received message msgId: %#v -- content: '%s'\n", msg.ID(), string(msg.Payload())) consumer.Ack(msg) } if err := consumer.Unsubscribe(); err != nil { log.Fatal(err) } }4 Java&Spring客户端操作Pulsar4.1 Java客户端(1)pom依赖<properties> <pulsar.version>2.9.1</pulsar.version> </properties> <dependencies> <dependency> <groupId>org.apache.pulsar</groupId> <artifactId>pulsar-client</artifactId> <version>${pulsar.version}</version> </dependency> </dependencies>(2)生产者和消费者class SbPursarApplicationTests { private PulsarClient client; private void init() throws PulsarClientException { client = PulsarClient.builder() .serviceUrl("pulsar://192.168.71.147:6650") .build(); } @Test void producer() throws Exception { init(); Producer<byte[]> producer = client.newProducer() .topic("my-topic") .create(); // 然后你就可以发送消息到指定的broker 和topic上: producer.send("My message".getBytes()); client.close(); } @Test void consumer() throws PulsarClientException { init(); Consumer consumer = client.newConsumer() .topic("my-topic") .subscriptionName("my-subscription") .subscribe(); while (true) { Message msg = consumer.receive(); try { System.out.println("Message received: " + new String(msg.getData())); //消息确认 consumer.acknowledge(msg); } catch (Exception e) { consumer.negativeAcknowledge(msg); } } } }4.2 Spring客户端(1)依赖<properties> <java.version>1.8</java.version> <!-- in your <properties> block --> <pulsar.version>2.9.1</pulsar.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- in your <dependencies> block --> <dependency> <groupId>org.apache.pulsar</groupId> <artifactId>pulsar-client</artifactId> <version>${pulsar.version}</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>(2)项目结构(3)配置类主要用于将自定义Bean放入Spring中@Configuration public class PulsarConfig { @Bean public Producer pulsarProducer() throws PulsarClientException { PulsarClient client = PulsarClient.builder() .serviceUrl("pulsar://192.168.71.147:6650") .build(); Producer<byte[]> producer = client .newProducer() .topic("my-topic") .create(); return producer; } @Bean public Consumer pulsarConsumer() throws PulsarClientException { PulsarClient client = PulsarClient.builder() .serviceUrl("pulsar://192.168.71.147:6650") .build(); Consumer consumer = client.newConsumer() .topic("my-topic") .subscriptionName("my-subscription") .subscribe(); return consumer; } }(4)控制器类:生产者@RestController public class HelloPulsarController { @Autowired private Producer pulsarProducer; @RequestMapping("/hello/{msg}") public String hello(@PathVariable("msg") String msg) { try { pulsarProducer.send(msg.getBytes()); } catch (PulsarClientException e) { return "发送失败"; } return "发送成功"; } }(3)消费者直接使用自定义Bean,并在Spring Boot启动后自动调用该方法@Service public class PulsarConsumerService implements ApplicationRunner { @Autowired private Consumer pulsarConsumer; public void consumer() throws PulsarClientException { while (true) { Message msg = pulsarConsumer.receive(); try { System.out.println("Message received: " + new String(msg.getData())); pulsarConsumer.acknowledge(msg); } catch (Exception e) { pulsarConsumer.negativeAcknowledge(msg); } } } @Override public void run(ApplicationArguments args) throws Exception { consumer(); } }5 通过pulsar-manager搭建可视化管理界面5.1 下载链接https://github.com/apache/pulsar-manager#access-pulsar-manager或https://pulsar.apache.org/en/download/5.2 启动并配置启动$ wget https://dist.apache.org/repos/dist/release/pulsar/pulsar-manager/pulsar-manager-0.2.0/apache-pulsar-manager-0.2.0-bin.tar.gz $ tar -zxvf apache-pulsar-manager-0.2.0-bin.tar.gz $ cd pulsar-manager $ tar -xvf pulsar-manager.tar $ cd pulsar-manager $ cp -r ../dist ui $ ./bin/pulsar-manager配置账号密码$ CSRF_TOKEN=$(curl http://127.0.0.1:7750/pulsar-manager/csrf-token) $ curl \ -H "X-XSRF-TOKEN: $CSRF_TOKEN" \ -H "Cookie: XSRF-TOKEN=$CSRF_TOKEN;" \ -H 'Content-Type: application/json' \ -X PUT http://127.0.0.1:7750/pulsar-manager/users/superuser \ -d '{"name": "admin", "password": "apachepulsar", "description": "test", "email": "username@test.org"}'5.3 使用访问http://localhost:9527就可以打开pulsar-manager界面:参考:https://pulsar.apache.org/docs/zh-CN/client-libraries-go/https://pulsar.apache.org/docs/zh-CN/client-libraries-java/https://blog.csdn.net/ycf921244819/article/details/120907372https://github.com/apache/pulsar-manager#access-pulsar-manager
1 什么是Docker Compose前面我们使用 Docker 的时候,定义 Dockerfile 文件,然后使用 docker build、docker run 等命令操作容器。然而微服务架构的应用系统一般包含若干个微服务,每个微服务一般都会部署多个实例,如果每个微服务都要手动启停,那么效率之低,维护量之大可想而知。使用 Docker Compose 可以轻松、高效的管理容器,它是一个用于定义和运行多容器 Docker 的应用程序工具2 安装Docker Compose安装命令:[root@iZ2ze4m2ri7irkf6h6n8zoZ ~]# curl -L https://github.com/docker/compose/releases/download/1.21.2/docker-compose-$(uname -s)-$(uname -m) -o /usr/local/bin/docker-compose [root@iZ2ze4m2ri7irkf6h6n8zoZ ~]# chmod +x /usr/local/bin/docker-compose检查是否安装成功:[root@iZ2ze4m2ri7irkf6h6n8zoZ ~]# docker-compose -v3 Docker Compose文件格式的简单介绍Docker Compose文件一般命名为docker-compose.yml,并且执行Docker-compose命令时在该文件所在目录下执行。Docker Compose 分为三层,分别是工程(project)、服务(service)/引用标签、容器(container)例如:docker-compose.yml # 一个文件代表一个project serveices: # 服务 container-name: # 容器 build: - xxx:xxx network: # 引用标签 xxx:下面是一个标准的docker-compose.yml文件version: "3" # 指定版本 services: # services proxy: # 自定义容器名称 build: ./proxy # Dockerfile所在目录,用于构建容器 networks: # 自定义容器网络 - frontend app: build: ./app networks: - frontend - backend db: image: postgres networks: - backend networks: frontend: driver: custom-driver-1 backend: driver: custom-driver-2 driver_opts: foo: "1" bar: "2"4 Docker Compose常用命令ps:列出所有运行容器docker-compose pslogs:查看服务日志输出docker-compose logsport:打印绑定的公共端口,下面命令可以输出 eureka 服务 8761 端口所绑定的公共端口docker-compose port eureka 8761build:构建或者重新构建服务docker-compose buildstart:启动指定服务已存在的容器docker-compose start eurekastop:停止已运行的服务的容器docker-compose stop eurekarm:删除指定服务的容器docker-compose rm eurekaup:构建、启动容器docker-compose upkill:通过发送 SIGKILL 信号来停止指定服务的容器docker-compose kill eurekapull:下载服务镜像docker-compose pull eurekascale:设置指定服务运气容器的个数,以 service=num 形式指定docker-compose scale user=3 movie=3run:在一个服务上执行一个命令docker-compose run web bash5 使用Docker Compose一键部署Spring Boot+Redis实战5.1 构建应用5.1.1 Spring Boot项目依赖:<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--redis--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>配置文件:spring: redis: #host: 127.0.0.1 host: ymx.redis port: 6379 password: jedis: pool: max-active: 8 max-wait: -1 max-idle: 500 min-idle: 0 lettuce: shutdown-timeout: 0controller代码:@RestController public class HelloController { @Autowired private RedisTemplate<String, String> redisTemplate; @RequestMapping("/hello/{id}") public String hello(@PathVariable("id") Integer id) { return redisTemplate.opsForValue().get(String.valueOf(id)); } @RequestMapping("/save/{id}/{name}") public String save(@PathVariable("id") Integer id, @PathVariable("name") String name) { try { redisTemplate.opsForValue().set(String.valueOf(id), "Hello " + name + "!"); } catch (Exception e) { return "false"; } return "success"; } }5.1.2 Redis配置文件只是将redis自带的redis.conf做了一点修改#注释掉bind 127.0.0.1 # bind 127.0.0.1 -::1 #修改protected-mode yes->no protected-mode no5.2 打包应用并构建目录5.2.1 打包Spring Boot项目5.2.2 上传redis.conf配置文件5.2.3 目录结构- mycompose - docker-compose.yml - rd - Dockerfile - redis.conf - sp - Dockerfile - sp_redis-0.0.1-SNAPSHOT.jar5.3 编写Dockerfile5.3.1 Spring Boot容器的DockerfileFROM java:8 MAINTAINER YMX "1712229564@qq.com" COPY sp_redis-0.0.1-SNAPSHOT.jar /root/sp_redis-0.0.1-SNAPSHOT.jar EXPOSE 8080 ENTRYPOINT ["java", "-jar","/root/sp_redis-0.0.1-SNAPSHOT.jar"]5.3.2 redis容器的DockerfileFROM redis MAINTAINER ymx 1712229564@qq.com COPY redis.conf /usr/local/etc/redis/redis.conf EXPOSE 6379 CMD ["redis-server","/usr/local/etc/redis/redis.conf" ]5.4 编写docker-compose.ymlversion: "2.8" # 表示该 Docker-Compose 文件使用的是 Version 2 file services: sp-demo: # 指定服务名称 build: ./sp # 指定 Dockerfile 所在路径 ports: # 指定端口映射 - "9001:8080" links: - re-demo:ymx.redis # 进行容器链接 re-demo: build: ./rd5.5 运行并测试部署结果运行:[root@iZ2ze4m2ri7i mycompose]# docker-compose up Creating network "mycompose_default" with the default driver Building re-demo Sending build context to Docker daemon 96.77kB Step 1/5 : FROM redis latest: Pulling from library/redis ......测试:[root@iZ2ze4m2ri7i mycompose]# curl http://localhost:9001/save/2/Ymx success [root@iZ2ze4m2ri7i mycompose]# curl http://localhost:9001/hello/2 Hello Ymx!6 小总结在Spring Boot配置文件中,redis的host没有使用localhost或者127.0.0.1,而是使用了域名ymx.redis,这一域名在docker-compose.yml文件中进行了映射,进而Spring Boot的容器能够链接到redis容器,但是这一情况依赖于一个默认条件,就是docker的网络默认是桥接模式,两个容器都在同一子网中,因此才能够互相访问。因此,links并不是唯一的容器网络解决方案,在容器较多时,需要使用networks进行网络的管理。参考文章:https://www.jianshu.com/p/658911a8cff3https://www.jianshu.com/p/3004fbce4d37https://blog.csdn.net/luo15242208310/article/details/88642187https://blog.csdn.net/qq_36781505/article/details/86612988
1 基本概念LRU是一个老生常谈的问题,即最近最少使用,LRU是Least Recently Used的缩写,是一种操作系统中常用的页面置换算法,选择最近最久未使用的页面予以淘汰。该算法赋予每个页面一个访问字段,用来记录一个页面自上次被访问以来所经历的时间 t,当须淘汰一个页面时,选择现有页面中其 t 值最大的,即最近最少使用的页面予以淘汰。实现LRU基本的数据结构:Map+LinkedList一般规则:添加数据时,将新增数据节点放在头指针,尾结点部分大于最大长度时删除。删除数据时,先按照Map的规则进行查找,再根据链表规则进行删除。查找数据时,按照Map进行查找,没有则返回空,有则返回该数据的值并移动到头节点。2 代码实现package main import "fmt" var head *Node var end *Node type Node struct { Key string Value string pre *Node next *Node } func (n *Node) Init(key string, value string) { n.Key = key n.Value = value } type LRUCache struct { Capacity int //页面初始化大小 Size int //页面实际大小 Map map[string]*Node //具体的cache } func GetLRUCache(capacity int) *LRUCache { lruCache := LRUCache{Capacity: capacity} lruCache.Map = make(map[string]*Node, capacity) return &lruCache } func (l *LRUCache) get(key string) string { if v, ok := l.Map[key]; ok { l.refreshNode(v) return v.Value } else { return "null" } } func (l *LRUCache) put(key, value string) { if v, ok := l.Map[key]; !ok { if len(l.Map) >= l.Capacity { oldKey := l.removeNode(head) delete(l.Map, oldKey) } node := Node{Key: key, Value: value} l.addNode(&node) l.Map[key] = &node } else { v.Value = value l.refreshNode(v) } } func (l *LRUCache) refreshNode(node *Node) { if node == end { return } l.removeNode(node) l.addNode(node) } func (l *LRUCache) removeNode(node *Node) string { if node == end { end = end.pre } else if node == head { head = head.next } else { node.pre.next = node.next node.next.pre = node.pre } return node.Key } func (l *LRUCache) addNode(node *Node) { if end != nil { end.next = node node.pre = end node.next = nil } end = node if head == nil { head = node } }3 测试使用func main() { lruCache := GetLRUCache(3) lruCache.put("001", "1") lruCache.put("002", "2") lruCache.put("003", "3") lruCache.put("004", "4") lruCache.put("005", "5") lruCache.get("002") fmt.Println(lruCache.get("001")) fmt.Println(lruCache.get("002")) fmt.Print(lruCache.Map) }参考文章:https://blog.csdn.net/u010647109/article/details/83746784
1 什么是RPC协议1.1 简介这是老生常谈的协议了,RPC即远程过程调用(Remote Procedure Call),RPC协议是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。1.2 原理及模型RPC协议广泛的应用于分布式系统中,主要用于不同计算机(即服务节点)间的通信,RPC主要是基于Socket,而Socket又基于TCP,因此我们可以理解为RPC基于TCP协议(部分RPC框架基于HTTP协议),在TCP的基础上增加了编程语言的机制,比如反射、编码与解码、以及动态代理,因此我们可以认为RPC只是一个概念,而实现这一概念有不同方式,典型的RPC框架如gRPC、Thrift、Netty、Dubbo等都是基于这一思想。2 什么是HTTP协议2.1 简介这是老生常谈的协议了 too,HTTP即超文本传输协议(Hyper Text Transfer Protocol)HTTP是互联网上应用最为广泛的一种网络协议,是一个客户端和服务器端请求和应答的标准(TCP),用于从WWW服务器传输超文本到本地浏览器的传输协议,它可以使浏览器更加高效,使网络传输减少。2.2 原理及模型HTTP协议是处于OSI网络模型的最上层应用层的协议,基于TCP协议,是通信协议中相对简单的协议之一,为什么简单呢?我认为原因有二:一是封装了大量的协议和报文头,使用简单,二是明文传输,处理简单,也是基于此,HTTP协议成为了互联网中最广泛的协议之一。3 为什么要拿HTTP和RPC协议进行比较?为什么要拿HTTP和RPC进行比较呢,我觉得大抵是因为HTTP和RPC是互联网应用系统中使用最广泛的两种网络通信协议了吧(模仿下鲁迅),并且在使用上似乎谁都能替代谁(当然抛开使用效率层面),一个是简单易用,一个是复杂高效,因此二者间的比较会很丰富。4 两者的相同点都是基于TCP协议点对点通信都可以在不同编程语言(应用系统)间进行通信5 两者的不同点所属网络七层模型中不同的网络层级(部分RPC框架属于应用层)数据编码格式不同一般情况下,RPC是长连接,HTTP则是短连接一般情况下,RPC的传输效率高于HTTP6 总结在系统开发中,一般需要对外提供接口时,因为普适性,HTTP是首选,而在同一个组织或公司内部进行不同系统间服务的提供时,面向服务封装的RPC更具有竞争力,可以针对服务进行可用性和效率的优化,因此HTTP和RPC不同的网络通信协议各自具有更擅长的领域。
因为本人也是初次探索云原生,对云原生这个概念也不是很清楚,因此是属于边学习边输出的状态中,但其实云原生这个概念就在我们身边,比如我们学习和使用的Go语言,是云原生技术的高频编程语言,使用的Docker容器技术,是云原生的基础技术之一,广为人知的Kubernetes则是云原生的核心主流技术之一,平时使用的阿里云、腾讯云和百度云等等,也都是针对云计算和云原生的不同厂商构建的基础设施,因此我们需要的就是花一些时间去学习和了解他。该篇文章主要是对一些优秀概念的摘取和自身了解后的一些理解,如果哪里有不对的地方还请读者和前辈指出。在说云原生之前我们要有一个前提的概念,那就是云计算,因为云原生是由云计算技术衍生出的可以说是比较新星的技术,因此我们对云计算的理解是要有的,那我们下面先讲解云计算:1 云计算1.1 云计算概念云计算(英语:Cloud Computing),也被意译为网络计算,是一种基于互联网的计算方式,通过这种方式,共享的软硬件资源和信息可以按需求提供给计算机各种终端和其他设备,使用服务商提供的电脑基建作计算和资源。为什么要使用云计算?互联网上汇聚的计算资源、存储资源、数据资源和应用资源正随着互联网规模的扩大而不断增加,互联网正在从传统意义的通信平台转化为泛在、智能的计算平台。与计算机系统这样的传统计算平台比较,互联网上还没有形成类似计算机操作系统的服务环境,以支持互联网资源的有效管理和综合利用。在传统计算机中已成熟的操作系统技术,已不再能适用于互联网环境,其根本原因在于:互联网资源的自主控制、自治对等、异构多尺度等基本特性,与传统计算机系统的资源特性存在本质上的不同。为了适应互联网资源的基本特性,形成承接互联网资源和互联网应用的一体化服务环境,面向互联网计算的虚拟计算环境(Internet-based Virtual Computing Environment,iVCE)的研究工作,使用户能够方便、有效地共享和利用开放网络上的资源。(以图片上由Sam Johnston - 本W3C状态不明的矢量图使用Inkscape创作 .CC BY-SA 3.0,https://commons.wikimedia.org/w/index.php?curid=6080417)1.2 云计算的特征互联网上的云计算服务特征和自然界的云、水循环具有一定的相似性,因此,云是一个相当贴切的比喻。根据美国国家标准和技术研究院的定义,云计算服务应该具备以下几条特征:随需应变自助服务。随时随地用任何网络设备访问。多人共享资源池。快速重新部署灵活度。可被监控与量测的服务。一般认为还有如下特征:基于虚拟化技术快速部署资源或获得服务。减少用户终端的处理负担。降低了用户对于IT专业知识的依赖。1.3 云计算的部署模型(1)公用云公用云(Public Cloud)服务可透过网络及第三方服务供应者,开放给客户使用,“公用”一词并不一定代表“免费”,但也可能代表免费或相当廉价,公用云并不表示用户资料可供任何人查看,公用云供应者通常会对用户实施使用访问控制机制,公用云作为解决方案,既有弹性,又具备成本效益。(2)私有云私有云(Private Cloud)具备许多公用云环境的优点,例如弹性、适合提供服务,两者差别在于私有云服务中,资料与程序皆在组织内管理,且与公用云服务不同,不会受到网络带宽、安全疑虑、法规限制影响;此外,私有云服务让供应者及用户更能掌控云基础架构、改善安全与弹性,因为用户与网络都受到特殊限制。(3)社群云社群云(Community Cloud)由众多利益相仿的组织掌控及使用,例如特定安全要求、共同宗旨等。社群成员共同使用云资料及应用程序。(4)混合云混合云(Hybrid Cloud)结合公用云及私有云,这个模式中,用户通常将非企业关键信息外包,并在公用云上处理,但同时掌控企业关键服务及资料。2 云原生2.1 概念引入先引用下一个Apache大佬的演讲:## 引用: https://www.slideshare.net/bibryam/designing-cloud-native-applications-with-kubernetes ## 作者: https://leanpub.com/u/bibryam ## 原文: Applications adopting the principles of `Microservices` packaged as `Containers` orchestracted by `Platforms` running on top of `Cloud infrastructure`,developed using practices such as `Continous Delivery` and `DevOps`. ## 翻译: 基于`微服务`原理而开发应用,以`容器`的方式打包。在运行时`容器`由运行于`云基础设施`之上的平台进 行调度。应用开发采用储`持续交付`和`DevOps`实践。综上,云原生应用也就是面向“云”而设计的应用,在使用云原生技术后,开发者无需考虑底层的技术实现,可以充分发挥云平台的弹性和分布式优势,实现快速部署、按需伸缩、不停机交付等。直白的讲,我认为的云原生就是把云当做原生开发环境来对待:不使用物理机进行项目的发布和维护,而是采用公有云使用容器进行项目的运行项目变更采用CI/CD和DevOps的方式2.2 目前业界云原生的主要技术领域(1)容器容器(container)这一概念最早是在Linux中出现的,主要是通过Linux的Cgroups的资源管理能力和Namespace的资源隔离能力结合在一起实现进程级别的隔离。(2)Kubernetes由Google 基于 Borg 开源的容器编排调度系统,是一种基于容器技术的分布式架构领先方案。在Docker技术的基础上,为容器化的应用提供部署运行、资源调度、服务发现和动态伸缩等功能,用户不需要再过度的关注资源的管理问题,降低操作的复杂度,提高了大规模容器集群管理的便捷性。(3)微服务(Microservices)微服务则是一种用于构建应用的架构方案,微服务架构有别于为传统的单体应用,它是将应用拆分成多个核心功能,每个功能都被称为一个独立的服务,可以单独构建和部署,其中某个服务出现故障也不会影响其他的功能模块,这句体现了其针对特定服务发布,影响小,风险小等特点。(4)服务网格(Service Mesh)服务网格指的是用于微服务应用的可配置基础架构层。在使用服务网格时通常会提供一个sidecar代理实例,主要处理 service 间的通信、监控、以及一些安全相关的考量,每个serivce里面都会有一个sidecar,同样也提供了服务发现、负载均衡、授权等功能。(5)无服务(Serverless)根据 CNCF 的定义,Serverless 是指构建和运行不需要服务器管理的应用程序的概念。即开发人员无需关注底层的基础设施,只需要关注应用程序的业务本身就行,且该服务是可以自动扩展。(6)DevOps早期的项目使用的是‘瀑布模型’进行软件交付,即一个阶段所有的工作完成之后再往下一个阶段,但这样的模式无法满足业务快速开发交付及变更需求的情况,于是后面就出现了敏捷开发这一概念,即一种快速应对需求变化软件开发能力,而DevOps就是基于敏捷开发将软件开发/测试人员/IT运维关联在一起,通过工具、组织等方式使开发、测试、发布流程自动化,软件发布频繁,高效。(7)云(Cloud)常常听到的‘公有云’,‘私有云’,‘混合云’都是基于这个生态衍生出来的各种场景,不同的云搭建环境,所需资源亦有所不同,比如公有云是在互联网上发布的云计算服务,而私有云则是在公司内网发布的云计算服务,目前没有一种云计算类型可以解决所有场景出现的问题,怎么选择适合自己的场景则需要根据技术需求决定。3 总结学习了云原生,我们不得不再回头看下在出现云原生之前我们如何开发。首先是拿来一台或多台堡垒机或一些电脑相互连接,并组成网络作为服务器集群并放在实地的机房中。首先每台单独的计算机必须是符合冯诺依曼体系的,其次网络也是符合IPv4或IPv6标准的,而且为了考虑安全,我们还会限制网络的入口规则、出口规则以及增加防火墙等等,在业务扩展时我们还需要扩展内容、硬盘或直接扩展计算机。而云原生技术的出现,让我们之前可以省略上述的很多地方,无需考虑使用的计算机是否符合冯诺依曼体系,无需手动去增加硬盘或内存,无需配置复杂的网络等等,甚至还无需考虑地域带来的业务延迟问题,因为云服务器可能再世界的任何一个地方。综上,就是我认为的云原生为什么越来越主流。参考:https://zhuanlan.zhihu.com/p/441747471https://zh.wikipedia.org/wiki/%E9%9B%B2%E7%AB%AF%E9%81%8B%E7%AE%97https://baike.baidu.com/item/%E4%BA%91%E5%8E%9F%E7%94%9F/53770166?fr=aladdin
1 简介以下系统环境可以使用:FreeBSD i386/amd64/armLinux i386/amd64/arm(raspberry pi)Windows i386/amd64/arm/arm64Darwin i386/amd64OpenBSD amd64 (Thank you @mpfz0r!)Solaris amd64 (developed and tested on SmartOS/Illumos, Thank you @jen20!)这些都有部分支持:CPU on DragonFly BSDhost on Linux RISC-V安装方式:go get github.com/shirou/gopsutil2 常用的API具体使用2.1 CPU信息package main import ( "fmt" "github.com/shirou/gopsutil/cpu" "time" ) //获取CPU信息 func getCpuInfo() { //1 CPU全部信息 cpuInfos, err := cpu.Info() if err != nil { fmt.Printf("获取CPU信息出错 , err:\n %v", err) } for _, ci := range cpuInfos { fmt.Println("CPU基本信息 : \n", ci) } // 实时加载CPU使用率 for { //每秒加载一次 percent, _ := cpu.Percent(time.Second, false) fmt.Printf("CPU负载信息 : %v\n", percent) } } func main() { getCpuInfo() }2.2 磁盘信息package main import ( "fmt" "github.com/shirou/gopsutil/disk" ) // 磁盘信息 func getDiskInfo() { parts, err := disk.Partitions(true) if err != nil { fmt.Printf("获取磁盘信息失败 , err:%v\n", err) return } for _, part := range parts { fmt.Printf("磁盘分区 :%v\n", part.String()) diskInfo, _ := disk.Usage(part.Mountpoint) fmt.Printf("该磁盘使用信息:used:%v free:%v\n", diskInfo.UsedPercent, diskInfo.Free) } ioStat, _ := disk.IOCounters() for k, v := range ioStat { fmt.Printf("%v:%v\n", k, v) } } func main() { getDiskInfo() }2.3 内存信息package main import ( "fmt" "github.com/shirou/gopsutil/mem" ) //内存信息 func getMemInfo() { //获取内存信息 memInfo, _ := mem.VirtualMemory() fmt.Printf("内存信息 :\n %v", memInfo) } func main() { getMemInfo() }2.4 主机信息package main import ( "fmt" "github.com/shirou/gopsutil/host" ) //主机信息 func getHostInfo() { hInfo, _ := host.Info() fmt.Printf("主机信息 :\n %v", hInfo) } func main() { getHostInfo() }2.5 IP地址信息package main import ( "fmt" "net" ) func GetLocalIP() (ip string, err error) { addrs, err := net.InterfaceAddrs() if err != nil { return } for _, addr := range addrs { ipAddr, ok := addr.(*net.IPNet) if !ok { continue } if ipAddr.IP.IsLoopback() { continue } if !ipAddr.IP.IsGlobalUnicast() { continue } return ipAddr.IP.String(), nil } return } func main() { ip, err := GetLocalIP() if err!=nil { fmt.Print(err) } fmt.Println("IP地址 : "+ip) }~
1 gorm简介官网:https://gorm.io/zh_CN/Github:https://github.com/go-gorm/gorm中文文档:https://gorm.io/zh_CN/docs/特性(摘自官网):全功能 ORM关联 (Has One,Has Many,Belongs To,Many To Many,多态,单表继承)Create,Save,Update,Delete,Find 中钩子方法支持 Preload、Joins 的预加载事务,嵌套事务,Save Point,Rollback To Saved PointContext、预编译模式、DryRun 模式批量插入,FindInBatches,Find/Create with Map,使用 SQL 表达式、Context Valuer 进行 CRUDSQL 构建器,Upsert,数据库锁,Optimizer/Index/Comment Hint,命名参数,子查询复合主键,索引,约束Auto Migration自定义 Logger灵活的可扩展插件 API:Database Resolver(多数据库,读写分离)、Prometheus…每个特性都经过了测试的重重考验开发者友好2 为什么要引入ORM框架2.1 概念ORM一般指对象关系映射。 对象关系映射(英语:Object Relational Mapping,简称ORM,或O/RM,或O/R mapping),是一种程序设计技术,用于实现面向对象编程语言里不同类型系统的数据之间的转换。2.2 引入orm之前的CRUDvar mysqlCon *sql.DB func InitMySQL(cnf *model.MySQLConfig) { var err error source := cnf.GetUserName() + ":" + cnf.PassWord + "@tcp(" + cnf.Host + ":" + cnf.GetPort() + ")/" + cnf.GetDBName() + "?charset=utf8" mysqlCon, err = sql.Open("mysql", source) if err != nil { panic("连接失败") } log.Info("MySQL Server is connected...") } func (dao LogMessageDaoImpl) SaveLogMessage(ctx context.Context, ms model.LogMessage) bool { tx, err := mysqlCon.Begin() stmt, err := tx.Prepare("INSERT INTO tb_log_message(id,create_time,time_stamp,log_user_id,log_user_role,topic_id,topic_name,operation,state,user_token,ip_address,os,browser) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?)") if err != nil { fmt.Println("save err") } stmt.Exec(ms.Id, ms.CreateTime, ms.TimeStamp, ms.LogUserId, ms.LogUserRole, ms.TopicId, ms.TopicName, ms.Operation, ms.State, ms.UserToken, ms.IpAddress, ms.OS, ms.Browser) tx.Commit() return false }2.3 使用orm框架进行CRUDfunc main() { // 参考 https://github.com/go-sql-driver/mysql#dsn-data-source-name 获取详情 dsn := "root:12345678@tcp(127.0.0.1:3306)/test?charset=utf8mb4&parseTime=True&loc=Local" db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{}) if err != nil { panic(err) } //ORM操作 GormCreate(db) } func GormCreate(db *gorm.DB) { //create student := Student{Name: "sss", Age: 10, Address: "Guangzhou"} res := db.Create(&student) fmt.Println(student.Id) fmt.Println(res.Error) }2.4 原因操作对象,就相当于直接操作数据库表,不需要占位符代码量少不易出错3 gorm简单使用package main import ( "fmt" "gorm.io/driver/mysql" "gorm.io/gorm" "time" ) type Student struct { Id int Name string Age int Address string } // TableName 实现接口就可以更改表名 func (stu Student) TableName() string { return "student" } func main() { // 参考 https://github.com/go-sql-driver/mysql#dsn-data-source-name 获取详情 dsn := "root:12345678@tcp(127.0.0.1:3306)/test?charset=utf8mb4&parseTime=True&loc=Local" db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{}) if err != nil { panic(err) } //ORM操作 //GormCreate(db) GormUpdate(db) } func GormCreate(db *gorm.DB) { //create student := Student{Name: "sss", Age: 10, Address: "Guangzhou"} res := db.Create(&student) fmt.Println(student.Id) fmt.Println(res.Error) } func GormRead(db *gorm.DB) { //Select var stu Student db.First(&stu, 1) fmt.Println(stu) } func GormUpdate(db *gorm.DB) { stu := Student{Id: 1} // Update - 将 product 的 price 更新为 200 db.Model(&stu).Update("age", 200) // Update - 更新多个字段 db.Model(&stu).Updates(Student{Name: "ZhangSan", Address: "F42"}) // 仅更新非零值字段 //db.Model(&stu).Updates(map[string]interface{}{"Price": 200, "Code": "F42"}) } func GormDelete(db *gorm.DB) { stu := Student{} // Delete - 删除 product db.Delete(&stu, 1) }~
1 搭建Spring Boot+MySQL项目1.1 项目依赖<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.2.1</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.6</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency>1.2 配置文件server.port=8081 spring.datasource.username=root spring.datasource.password=12345678 spring.datasource.driver-class-name=com.mysql.jdbc.Driver spring.datasource.url=jdbc:mysql://localhost:3306/test spring.datasource.type=com.alibaba.druid.pool.DruidDataSource1.3 具体代码HelloDao.java@Mapper @Repository public interface HelloDao { @Select("SELECT * FROM student WHERE id=#{id}") Map<String, Object> findObjectById(@Param("id") Integer id); }HelloController.java@RestController public class HelloController { @Autowired private HelloDao helloDao; @GetMapping("/hello") public String hello() { return "Hello Docker!"; } @GetMapping("/find/{id}") public String find(@PathVariable("id") Integer id){ return helloDao.findObjectById(id).toString(); } }2 编写Dockerfile# 该镜像需要依赖的基础镜像 FROM java:8 # 指定维护者的名字 MAINTAINER YMX "1712229564@qq.com" # 将指定目录下的jar包复制到docker容器的/export/Apps/springboot-admin目录下 COPY sp-docker-0.0.1-SNAPSHOT.jar /usr/local/sp_demo/sp-docker-0.0.1-SNAPSHOT.jar # 声明服务运行在8080端口 EXPOSE 8081 # 指定docker容器启动时运行jar包 ENTRYPOINT ["java", "-jar","/usr/local/sp_demo/sp-docker-0.0.1-SNAPSHOT.jar"]3 导出数据库在MySQL安装目录的/bin目录下执行以下命令: ./mysqldump -uroot -p12345678 test > ~/test.sql 数据库信息:CREATE TABLE `student` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(255) DEFAULT NULL, `age` int(2) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4; LOCK TABLES `student` WRITE; INSERT INTO `student` VALUES (1,'zs',32),(2,'ls',24),(3,'zsss',21); UNLOCK TABLES;4 复制SQL数据在宿主机中执行以下命令:docker cp test.sql mysql:/tmp docker run -p 3307:3306 --name mysql -e MYSQL_ROOT_PASSWORD=12345678 -d mysql docker ps docker exec -it mysql /bin/sh在已安装MySQL 的docker容器中执行以下命令:$ cd /tmp $ ls test.sql $ mysql -uroot -p12345678 ###进入MySQL### mysql> use test Database changed mysql> source /tmp/test.sql Query OK, 0 rows affected, 2 warnings (0.03 sec) mysql> select * from student; +----+------+------+ | id | name | age | +----+------+------+ | 1 | zs | 32 | | 2 | ls | 24 | | 3 | zsss | 21 | +----+------+------+ 3 rows in set (0.00 sec)5 将Spring Boot容器和MySQL容器进行联通使用Docker命令查看IP地址root ~ % docker network ls NETWORK ID NAME DRIVER SCOPE 5b9134swqe954 bridge bridge local root ~ % docker network inspect 5b9134swqe954 [ { ...... "IPAM": { "Driver": "default", "Options": null, "Config": [ { "Subnet": "172.17.0.0/16" } ] }, ......, "Containers": { "d3938789hhuinj1499f680e": { "Name": "mysql", ...... "IPv4Address": "172.17.0.2/16", "IPv6Address": "" } }, ...... } ]经过查看我们发现,Docker的网络默认是桥接网络,即每个容器都属于同一网段,图示:(图片来自https://www.cnblogs.com/ygria/p/13217793.html)因此,我们把Spring Boot项目的配置文件修改下:server.port=8081 spring.datasource.username=root spring.datasource.password=12345678 spring.datasource.driver-class-name=com.mysql.jdbc.Driver spring.datasource.url=jdbc:mysql://172.17.0.2:3306/test spring.datasource.type=com.alibaba.druid.pool.DruidDataSource如果条件允许可直接使用域名。然后再重启Spring Boot则可以进行MySQL的访问和使用扩展:Docker网络Docker 的网络子系统是可选的,使用驱动程序。默认情况下存在几个驱动程序,并提供核心网络功能:bridge:默认网络驱动程序。如果您未指定驱动程序,则这是您正在创建的网络类型。当您的应用程序在需要通信的独立容器中运行时,通常会使用桥接网络。host:对于独立容器,去掉容器与 Docker 主机之间的网络隔离,直接使用主机的网络。overlay: Overlay 网络将多个 Docker 守护进程连接在一起,使 swarm 服务能够相互通信。您还可以使用覆盖网络来促进 swarm 服务和独立容器之间的通信,或者不同 Docker 守护程序上的两个独立容器之间的通信。这种策略消除了在这些容器之间进行操作系统级路由的需要。ipvlan:IPvlan 网络使用户可以完全控制 IPv4 和 IPv6 寻址。VLAN 驱动程序建立在此之上,为对底层网络集成感兴趣的用户提供了对第 2 层 VLAN 标记甚至 IPvlan L3 路由的完全控制。macvlan:Macvlan 网络允许您将 MAC 地址分配给容器,使其在您的网络上显示为物理设备。Docker 守护进程通过它们的 MAC 地址将流量路由到容器。macvlan 在处理期望直接连接到物理网络而不是通过 Docker 主机的网络堆栈路由的遗留应用程序时,使用驱动程序有时是最佳选择。none:对于这个容器,禁用所有网络。通常与自定义网络驱动程序一起使用。none不适用于 swarm 服务。
1 基本流程(1)编写Go工程代码(2)可用性测试(3)编写Dockerfile(4)使用Dockerfile构建Docker镜像(5)查看构建结果并运行(6)测试新建的Docker镜像2 操作实现2.1 编写Go工程代码go-docker #项目根目录 - core #代码目录 - main.go #具体代码文件 - go.mod #mod文件 - Dockerfile #Dockerfile文件 - main #go build ./core/main.go 命令编译后的二进制文件2.2 具体代码package main import ( "log" "net/http" ) func main() { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Hello Docker")) }) log.Print("go docker project is running...") http.ListenAndServe(":8888", nil) }2.3 可用性测试运行:go run ./core/main.go测试(在宿主机上):curl http://127.0.0.1:88882.4 编写Dockerfile#依赖镜像(母镜像),可以先使用docker search命令搜索 FROM golang:latest #作者信息 MAINTAINER YMX "1712229564@qq.com" #Docker工作目录 WORKDIR $GOPATH/src/go-docker #将当前目录添加到Docker ADD . $GOPATH/src/go-docker #在Docker工作目录下执行命令 RUN go build ./core/main.go #暴露端口 EXPOSE 8888 #编译后在根目录下生成而非./core目录下,最终运行Docker的初始命令 ENTRYPOINT ["./main"]2.5 使用Dockerfile构建Docker镜像基本语法:docker build -t [镜像名] [Dockerfile所在目录]具体演示:docker build -t go-docker-latest .2.6 查看构建结果并运行root ~ % docker images REPOSITORY TAG IMAGE ID CREATED SIZE go-docker-latest latest 453fb231s245 9 minutes ago 953MB root ~ % docker run -d -p 8888:8888 go-docker-latest a4a1f3b753913325b908bedf3d74ad1d0056223d84125242.7 测试新建的Docker镜像root ~ % docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES a4a1f3b75391 go-docker-latest "./main" 42 seconds ago Up 42 seconds 0.0.0.0:8888->8888/tcp jovial_z root ~ % curl http://127.0.0.1:8888 Hello Docker% end~
0 什么是负载均衡?负载均衡(Load Balance),其含义就是指将负载(工作任务)进行平衡、分摊到多个操作单元上进行运行,从而协同完成工作任务。负载均衡都分为哪些种类?软件和硬件负载均衡软件负载均衡硬件负载均衡本地和全局负载均衡本地负载均衡全局负载均衡本篇文章的负载均衡算法是属于软件层面的负载均衡。1 轮询顾名思义,将子任务在子节点中一个接一个有序的询问请求。var list = make([]string, 0) var servers = make(map[string]string) func init() { servers = map[string]string{ "stringA": "10.0.0.1", "stringB": "10.0.0.2", "stringC": "10.0.0.3", } for s := range servers { list = append(list, s) } } //轮询 var i = 0 func RoundRobin() string { if i >= len(list) { i = 0 } str := servers[list[i]] i += 1 return str }2 随机在子节点中随机的进行请求。var list = make([]string, 0) var servers = make(map[string]string) func init() { servers = map[string]string{ "stringA": "10.0.0.1", "stringB": "10.0.0.2", "stringC": "10.0.0.3", } for s := range servers { list = append(list, s) } } //随机 func Random() string { i := rand.Intn(len(list)) return servers[list[i]] }3 加权轮询与轮询不同的是,可以增加权重,就是说权重最大的节点会有更多次数(比例)的请求。var list = make([]string, 0) var servers = make(map[string]string) func init() { servers = map[string]string{ "stringA": "10.0.0.1", "stringB": "10.0.0.2", "stringC": "10.0.0.3", } for s := range servers { list = append(list, s) } //加权轮询 var weight_map = map[string]int{ "stringA": 1, "stringB": 2, "stringC": 3, } for s := range weight_map { for i := 0; i < weight_map[s]-1; i++ { list = append(list, s) } } } //加权轮询 func WeightRoundRobin() string { if i >= len(list) { i = 0 } str := servers[list[i]] i += 1 return str }4 加权随机与随机不同的是,增加某个节点被随机访问的概率。var list = make([]string, 0) var servers = make(map[string]string) func init() { servers = map[string]string{ "stringA": "10.0.0.1", "stringB": "10.0.0.2", "stringC": "10.0.0.3", } for s := range servers { list = append(list, s) } //加权轮询 var weight_map = map[string]int{ "stringA": 1, "stringB": 2, "stringC": 3, } for s := range weight_map { for i := 0; i < weight_map[s]-1; i++ { list = append(list, s) } } } //加权随机 func WeightRandom() string { i := rand.Intn(len(list)) return servers[list[i]] }5 源地址哈希该方法是将请求的源地址进行哈希,并将哈希的结果进行取余,将取余后的结果进行节点的匹配最后进行请求。//Source Hash func Hash() string { //对客户端(源)地址做哈希 使用md5哈希算法 has, err := md5.New().Write([]byte("127.0.0.1")) if err != nil { panic(err) } i := has % len(list) return servers[list[i]] }6 最小连接数最小连接数法是根据服务器当前的连接情况进行负载均衡的,当请求到来时,会选取当前连接数最少的一台服务器来处理请求。由此也可以延伸出,根据服务器 CPU 占用最少,根据单位时间内处理请求的效率高低等进行服务器选择。最小连接数法只是动态分配服务器的一种算法,通过各种维度的参数计算,可以找到适合不同场景的更均衡的动态分配服务器的方案。7 全部代码package main import ( "crypto/md5" "math/rand" "net/http" ) var list = make([]string, 0) var servers = make(map[string]string) func init() { servers = map[string]string{ "stringA": "10.0.0.1", "stringB": "10.0.0.2", "stringC": "10.0.0.3", } for s := range servers { list = append(list, s) } //加权轮询 var weight_map = map[string]int{ "stringA": 1, "stringB": 2, "stringC": 3, } for s := range weight_map { for i := 0; i < weight_map[s]-1; i++ { list = append(list, s) } } } //轮询 var i = 0 func RoundRobin() string { if i >= len(list) { i = 0 } str := servers[list[i]] i += 1 return str } //随机 func Random() string { i := rand.Intn(len(list)) return servers[list[i]] } //Source Hash func Hash() string { //对客户端(源)地址做哈希 使用md5哈希算法 has, err := md5.New().Write([]byte("127.0.0.1")) if err != nil { panic(err) } i := has % len(list) return servers[list[i]] } //加权轮询 func WeightRoundRobin() string { if i >= len(list) { i = 0 } str := servers[list[i]] i += 1 return str } //加权随机 func WeightRandom() string { i := rand.Intn(len(list)) return servers[list[i]] } //----------Web测试---------------// func main() { //httpServer(WeightRandom) } func httpServer(fun func() string) { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Request Node is " + fun())) }) http.ListenAndServe(":8888", nil) }参考文章:https://blog.csdn.net/claram/article/details/90290397
Go语言操作Redis的客户端工具有很多,今天我们就选择比较常见的一个来进行演示,这个就是go-redisGitHub链接:https://github.com/go-redis/redis1 简介开箱即用的工作与Redis服务器,Redis集群,Redis哨兵。类型安全的 go-redis为大多数Redis命令提供了类型。功能丰富的 支持管道、事务、发布/订阅、Lua脚本、模拟、分布式锁等等。2 使用在使用之前我们一定要先将自己的项目安装好go-redis包,然后再进行初始化操作,具体步骤如下:安装go-redisgo mod init github.com/my/repo go get github.com/go-redis/redis/v8新建go文件,初始化redis.Client,以下的每一个函数都在该文件内//上下文变量 var ctx = context.Background() //redis操作指针 var rdb *redis.Client /** 初始化*redis.Client */ func init() { rdb = redis.NewClient(&redis.Options{ Addr: "localhost:6379", Password: "", DB: 0, }) }2.1 操作String类型/** 操作String类型 */ func ExampleString() { err := rdb.Set(ctx, "key", "value", 0).Err() if err != nil { fmt.Println(err) } val, err := rdb.Get(ctx, "key").Result() if err != nil { fmt.Println(err) } fmt.Println("key", val) }2.2 操作List类型/** 操作List类型 */ func ExampleList() { list := []interface{}{2, 3, 4, 5, 6} //添加数组类型元素 err := rdb.LPush(ctx, "list", list).Err() if err != nil { fmt.Println(err) } //获取长度 rLen, err := rdb.LLen(ctx, "list").Result() //获取根据范围获取数据 lists, err := rdb.LRange(ctx, "list", 0, rLen-1).Result() fmt.Println(lists) //遍历全部元素 for item := range lists { fmt.Println(lists[item]) } }2.3 操作Set类型/** Set类型 */ func ExampleSet() { set := []interface{}{"s", "3", "5", 4, "sw", "s"} //添加zSet类型 result, err := rdb.SAdd(ctx, "set", set).Result() if err != nil { fmt.Println(result) fmt.Println("err") } //获取和变量zSet类型变量 members, err := rdb.SMembers(ctx, "set").Result() for i := range members { fmt.Println(members[i]) } }2.4 操作ZSet类型/** ZSet类型 */ func ExampleZSet() { //zSet类型需要使用特定的类型值*redis.Z,以便作为排序使用 zs := redis.Z{1, "zs"} ls := redis.Z{2, "ls"} ww := redis.Z{3, "ww"} member := []*redis.Z{&zs, &ls, &ww} //插入ZSet类型 result, err := rdb.ZAdd(ctx, "zset", member...).Result() if err != nil { fmt.Println(result) fmt.Println("err") } //将ZSet中的某一个元素顺序值增加 newScore, err := rdb.ZIncrBy(ctx, "zset", 5.0, "zs").Result() fmt.Println("zs加5分后的最新分数", newScore) //根据分数排名取出元素 zsetList2, _ := rdb.ZRevRangeWithScores(ctx, "zset", 0, 1).Result() fmt.Println("zset前2名热度的", zsetList2) }2.5 操作Map类型/** Map类型 */ func ExampleMap() { my_map := map[string]interface{}{ "id": 1, "name": "zs", "age": 23, } //插入map类型变量 result, err := rdb.HMSet(ctx, "map", my_map).Result() if err != nil { fmt.Println(result) fmt.Println("err") } //获取map类型变量的全部值 get_map_all, err := rdb.HGetAll(ctx, "map").Result() fmt.Println(get_map_all) //获取map类型变量的某一个字段值 get_map, err := rdb.HMGet(ctx, "map", "name").Result() fmt.Println(get_map) }2.6 操作结构体类型type Student struct { Id int64 Name string Age int } func (stu Student) MarshalBinary() ([]byte, error) { return json.Marshal(stu) } /** Struct类型 */ func ExampleStruct() { student := Student{1, "zs", 23} //将结构体转化为json类型 bytes, err2 := student.MarshalBinary() if err2 != nil { fmt.Println("err", err2) } //插入结构体类型变量 result, err := rdb.Set(ctx, "student", bytes, 0).Result() if err != nil { fmt.Println(result) fmt.Println("err", err) } stu, err := rdb.Get(ctx, "student").Result() //将获取的json反序列化成结构体 var studen Student err = json.Unmarshal([]byte(stu), &studen) fmt.Println(studen) }~
说到Linux的用户系统,不得不让人联想到:“超人”没错就是他,Linux中最厉害的用户,没有之一,维护世界和平,维护系统治安可以对全部用户、全部目录、全部文件等等为所欲为的超级用户,英文名称root。1 Linux用户层级以及组的关系超级管理员:用户名root,对Linux系统具有最高级别权限,可以对系统的任何文件和目录进行操作。用户组:具有唯一GID的多个用户的集合。一般用户:Linux的一般使用用户,具有唯一的UID。2 GID和UIDGroup Id,即组ID,用来标识用户组的唯一标识符User Id,即用户ID,用来标识每个用户的唯一标示符3 组管理3.1 添加组(1)命令:groupadd [组名](2)示例:[root@iZ1608aqb7ntn9Z ~]# groupadd ymx [root@iZ1608aqb7ntn9Z ~]# groupadd ymx groupadd:“ymx”组已存在(3)常见参数:group -g [组ID] [组名][root@iZ1608aqb7ntn9Z ~]# groupadd -g 1111 g1 [root@iZ1608aqb7ntn9Z ~]# cat /etc/group ...... g1:x:1111:3.2 删除组(1)命令:groupdel [组名](2)示例:[root@iZ1608aqb7ntn9Z ~]# groupdel g13.3 查看全部用户组(1)命令:cat /etc/group 或使用管道符 cat /etc/group | grep [组名](2)示例:[root@iZ1608aqb7ntn9Z ~]# cat /etc/group # 查看全部组 root:x:0: bin:x:1: daemon:x:2: sys:x:3: adm:x:4: tty:x:5: disk:x:6: lp:x:7: mem:x:8: kmem:x:9: wheel:x:10: cdrom:x:11: mail:x:12: man:x:15: dialout:x:18: floppy:x:19: games:x:20: tape:x:33: video:x:39: ftp:x:50: lock:x:54: audio:x:63: users:x:100: nobody:x:65534: dbus:x:81: utmp:x:22: utempter:x:35: input:x:999: kvm:x:36: render:x:998: systemd-journal:x:190: systemd-coredump:x:997: systemd-resolve:x:193: tss:x:59: polkitd:x:996: libstoragemgmt:x:995: printadmin:x:994: unbound:x:993: ssh_keys:x:992: setroubleshoot:x:991: cockpit-ws:x:990: cockpit-wsinstance:x:989: sssd:x:988: sshd:x:74: chrony:x:987: slocate:x:21: rngd:x:986: tcpdump:x:72: nscd:x:28: admin:x:1000: mysql:x:1001: docker:x:985: cgred:x:984: super:x:1002: ymx:x:1003: [root@iZ1608aqb7ntn9Z ~]# cat /etc/group | grep ymx # 根据管道符查看组 ymx:x:1003:格式说明: [用户组名称]:[密码]:[GID],例如ymx:x:1003: 的组名称为ymx,密码在其他配置文件中,GID为1003大家根据命令的使用结果可以大致看出来几个规律,Linux系统中有很多系统默认的组,并且他们的GID都在1000以下,我们自己新加的组GID一般都在1000以上其次是管道符(后边会专门文章讲解),规律是|后边的值是|前边的查询条件4 用户管理4.1 添加用户(1)命令:useradd [用户名](2)示例:[root@iZ1608aqb7ntn9Z article]# useradd ymx1(3)常见参数:useradd -D : 查看系统创建用户时的默认值[root@iZ1608aqb7ntn9Z article]# useradd -D GROUP=100 # 表示默认的GID为100 HOME=/home # 表示默认用户目录在/home目录下 INACTIVE=-1 # 表示新用户的密码过期后不会被禁用 EXPIRE= # 密码的过期日期 SHELL=/bin/bash # 默认的shell环境 SKEL=/etc/skel # 将/etc/skel目录下的内容复制到用户的HOME目录下 CREATE_MAIL_SPOOL=yes # 在mail目录下创建一个用于接收邮件的文件useradd [用户名] -g [组名] 或者 useradd -g [组名] [用户名] : 创建用户时指定组[root@iZ1608aqb7ntn9Z article]# useradd ymx2 -g ymx [root@iZ1608aqb7ntn9Z article]# useradd -g ymx ymx2 useradd:用户“ymx2”已存在useradd -d [目录名称] [用户名] : 创建用户时指定用户的home目录[root@iZ1608aqb7ntn9Z article]# useradd -d /home/yyy3 ymx3 [root@iZ1608aqb7ntn9Z article]# cd /home/ [root@iZ1608aqb7ntn9Z home]# ls admin article mysql ymx1 ymx2 yyy3useradd [用户名] -u [UID] :为新用户指定UIDuseradd [用户名] -e [过期时间] :为新用户指定过期时间[root@iZ1608aqb7ntn9Z home]# useradd ymx5 -e 2021-09-01 [root@iZ1608aqb7ntn9Z home]# ls admin article mysql ymx1 ymx4 ymx5 yyy3passwd : 为用户设置密码[root@iZ1608aqb7ntn9Z home]# useradd ymx4 [root@iZ1608aqb7ntn9Z home]# passwd ymx4 更改用户 ymx4 的密码 。 新的 密码: # 输入密码时是隐藏的(安全) 重新输入新的 密码: passwd:所有的身份验证令牌已经成功更新。 [root@iZ1608aqb7ntn9Z home]# 4.2 删除用户(1)命令:userdel [用户名](2)示例:[root@iZ1608aqb7ntn9Z home]# ls admin article mysql ymx1 ymx2 ymx4 yyy3 [root@iZ1608aqb7ntn9Z home]# userdel ymx3 [root@iZ1608aqb7ntn9Z home]# ls admin article mysql ymx1 ymx2 ymx4 yyy3(3)常见参数:userdel -r [用户名]: 删除user的信息和所有的目录[root@iZ1608aqb7ntn9Z home]# ls admin article mysql ymx1 ymx2 ymx4 yyy3 [root@iZ1608aqb7ntn9Z home]# userdel -r ymx2 [root@iZ1608aqb7ntn9Z home]# ls admin article mysql ymx1 ymx4 yyy34.3 查看全部用户(1)命令:四种方式:getent passwdcat /etc/shadowcat /etc/passwdcompgen -u(2)示例:[root@iZ1608aqb7ntn9Z home]# getent passwd ...... admin:x:1000:1000::/home/admin:/bin/bash mysql:x:1001:1001::/home/mysql:/bin/bash ymx1:x:1002:1004::/home/ymx1:/bin/bash ymx4:x:1005:1006::/home/ymx4:/bin/bash ymx5:x:1006:1007::/home/ymx5:/bin/bash [root@iZ1608aqb7ntn9Z home]# cat /etc/shadow ...... admin:!!:18704:0:99999:7::: mysql:!!:18704:0:99999:7::: ymx1:!!:18862:0:99999:7::: ymx4:$6$F6bYMV/44jm63XXX$9FLXnCqXrE5BbXILRJghmaLF.LUBlnV.YBdFS0R8Wq5mzFIh3g9LlpwCtsfWChZaMw0P91P4deNJVHway7Gom1:18862:0:99999:7::: ymx5:!!:18862:0:99999:7::18871: [root@iZ1608aqb7ntn9Z home]# cat /etc/passwd ...... admin:x:1000:1000::/home/admin:/bin/bash mysql:x:1001:1001::/home/mysql:/bin/bash ymx1:x:1002:1004::/home/ymx1:/bin/bash ymx4:x:1005:1006::/home/ymx4:/bin/bash ymx5:x:1006:1007::/home/ymx5:/bin/bash [root@iZ1608aqb7ntn9Z home]# compgen -u ...... admin mysql ymx1 ymx4 ymx54.4 切换用户(1)命令:su [用户名] 切换到root 直接使用 su(2)示例:[root@iZ1608aqb7ntn9Z home]# su ymx1 [ymx1@iZ1608aqb7ntn9Z home]$ who root pts/0 2021-08-23 10:03 (222.128.60.244) root pts/1 2021-08-23 10:03 (222.128.60.244 [ymx1@iZ1608aqb7ntn9Z home]$ su 密码: [root@iZ1608aqb7ntn9Z home]# 注意:区分是root用户还是其他用户,可以用 # 和 $ 来区分,例如[root@iZ1608aqb7ntn9Z home]#代表root用户下的终端,[ymx1@iZ1608aqb7ntn9Z home]$ 则代表普通模式下的终端(3)常见参数:su -l [用户名] : 表示重新登录该用户[root@iZ1608aqb7ntn9Z home]# su -l ymx1 上一次登录:一 8月 23 10:43:12 CST 2021pts/0 上5 两个重要的配置文件5.1 /etc/shadow(1)先看下内容:[root@iZ1608aqb7ntn9Z ymx1]# cat /etc/shadow ...... ymx1:!!:18862:0:99999:7::: ymx4:$6$F6bYMV/44jm63XXX$9FLXnCqXrE5BbXILRJghmaLF.LUBlnV.YBdFS0R8Wq5mzFIh3g9LlpwCtsfWChZaMw0P91P4deNJVHway7Gom1:18862:0:99999:7::: ymx5:!!:18862:0:99999:7::18871:(2)作用:用于存储 Linux 系统中用户的密码信息,又称为“影子文件”(3)每一行的含义:第一字段:用户名第二字段:密码第三字段:上次修改口令的时间戳第四字段:两次修改口令间隔最少的天数;如果这个字段的值为空,帐号永久可用;第五字段:两次修改口令间隔最多的天数;如果这个字段的值为空,帐号永久可用;第六字段:提前多少天警告用户口令将过期;如果这个字段的值为空,帐号永久可用;第七字段:在口令过期之后多少天禁用此用户;如果这个字段的值为空,帐号永久可用;第八字段:用户过期日期;此字段指定了用户作废的天数(从1970年的1月1日开始的天数),如果这个字段的值为空,帐号永久可用;第九字段:保留字段,目前为空,以备将来发展之用;5.2 /etc/password先看下内容:[root@iZ1608aqb7ntn9Z ymx1]# cat /etc/passwd ...... ymx1:x:1002:1004::/home/ymx1:/bin/bash ymx4:x:1005:1006::/home/ymx4:/bin/bash ymx5:x:1006:1007::/home/ymx5:/bin/bash(2)作用:用于存储 Linux 系统中用户信息(3)每一行的含义:第一段:用户名第二段:用户密码,x表示放在 /etc/shadow 中去了第三段:UID,0代表了root第四段:GID第五段:用户的描述介绍,可使用usermod -c 修改第六段:用户的家目录,在创建用户时自动生成在/home/用户名中,可以自定义这个目录。第七段:用户登陆的时候需要启动一个进程
1 概念目录就是部分文件和目录的集合,在Linux中可以理解为树结构中的节点绝对路径和相对路径:绝对路径:可以理解为,相对于任何位置的路径写法:一定是由根目录“/”写起,例如:“/usr/local”相对路径:相对于当前目录的路径写法:不是由“/"写起,例如:“/usr/local/java”在“/usr/local”中就可以写成“java”或“/java”(当java是文件时可写成“./java”,“.”代表当前目录)举例:问题:文件File02的绝对路径和在Dir02以及Dir01中的相对路径分别是多少?答案:File02的绝对路径为“/Dir01/Dir02/File02”,在Dir02中的相对路径为“File02”或“./File02”,在Dir01中的相对路径为“/Diro2/File02”2 Linux中常见的目录及作用请参看这篇文章:https://blog.csdn.net/Mr_YanMingXin/article/details/1198359143 常见的对目录的操作3.0 切换目录(1)命令:cd [目录名称/路径加名称](2)示例:[root@iZ1608aqb7ntn9Z article]# cd /home/ [root@iZ1608aqb7ntn9Z home]# pwd /home [root@iZ1608aqb7ntn9Z home]# ls admin article mysql [root@iZ1608aqb7ntn9Z home]# cd article/ [root@iZ1608aqb7ntn9Z article]# pwd /home/article小扩展:在操作系统的每个目录中,都有“.”,“..”这两个目录,一个代表当前目录,一个代表上级目录(父目录),我们可以使用ls -a命令来查看:[root@iZ1608aqb7ntn9Z TestDir]# ls -a . .. dir01 dir02因此,cd ..命令就表示返回上级目录,举个例子:[root@iZ1608aqb7ntn9Z TestDir]# pwd /home/article/TestDir [root@iZ1608aqb7ntn9Z TestDir]# cd .. [root@iZ1608aqb7ntn9Z article]# pwd /home/article3.1 创建目录(1)命令:mkdir [目录名称/路径加名称](2)示例:[root@iZ1608aqb7ntn9Z TestDir]# mkdir dir01 [root@iZ1608aqb7ntn9Z TestDir]# mkdir dir01/dir001 [root@iZ1608aqb7ntn9Z TestDir]# ls dir01 [root@iZ1608aqb7ntn9Z TestDir]# cd dir01/ [root@iZ1608aqb7ntn9Z dir01]# ls dir001(3)常见参数:mkdir -p [路径加目录名称] :当路径中父目录不存在时,进行递归创建举例:[root@iZ1608aqb7ntn9Z TestDir]# mkdir dir02/dir002 mkdir: 无法创建目录 “dir02/dir002”: 没有那个文件或目录 # 不加-p就会报错 [root@iZ1608aqb7ntn9Z TestDir]# mkdir -p dir02/dir002 # 加上-p可以将路径上的父目录一并创建 [root@iZ1608aqb7ntn9Z TestDir]# ls dir01 dir02 [root@iZ1608aqb7ntn9Z TestDir]# cd dir02/ [root@iZ1608aqb7ntn9Z dir02]# ls dir0023.2 查看目录/路径(1)命令:pwd(2)示例:进入任意目录:[root@iZ1608aqb7ntn9Z dir02]# pwd /home/article/TestDir/dir02 [root@iZ1608aqb7ntn9Z dir02]# cd .. [root@iZ1608aqb7ntn9Z TestDir]# pwd /home/article/TestDir3.3 复制目录(1)命令:cp -r [源目录] [目标目录](2)示例:[root@iZ1608aqb7ntn9Z TestDir]# ls dir01 dir02 [root@iZ1608aqb7ntn9Z TestDir]# cp dir01 dir03 cp: 未指定 -r;略过目录'dir01' [root@iZ1608aqb7ntn9Z TestDir]# cp -r dir01 dir03 [root@iZ1608aqb7ntn9Z TestDir]# ls dir01 dir02 dir03 [root@iZ1608aqb7ntn9Z TestDir]# 3.4 修改目录名称/位置(1)命令:mv [源目录] [目标路径/目录名称](2)示例:修改位置:[root@iZ1608aqb7ntn9Z TestDir]# mv dir03 dir01/ [root@iZ1608aqb7ntn9Z TestDir]# ls dir01 dir02 [root@iZ1608aqb7ntn9Z TestDir]# cd dir01 [root@iZ1608aqb7ntn9Z dir01]# ls dir001 dir03修改名称:[root@iZ1608aqb7ntn9Z dir01]# ls dir001 dir03 [root@iZ1608aqb7ntn9Z dir01]# mv dir03 dir002 [root@iZ1608aqb7ntn9Z dir01]# ls dir001 dir0023.5 删除目录(1)命令:rm -r [目录名称](2)示例:[root@iZ1608aqb7ntn9Z dir01]# ls dir001 dir002 [root@iZ1608aqb7ntn9Z dir01]# rm -r dir001 rm:是否删除目录 'dir001'?y [root@iZ1608aqb7ntn9Z dir01]# ls dir002(3)常见参数:rm -rf [目录名称] : 强制删除[root@iZ1608aqb7ntn9Z dir01]# ls dir002 [root@iZ1608aqb7ntn9Z dir01]# rm -rf dir002/ [root@iZ1608aqb7ntn9Z dir01]# ls [root@iZ1608aqb7ntn9Z dir01]# 4 注意点因为Linux是属于多用户的操作系统,因此在不同用户间都是相互隔离的,所以在操作目录和文件时需要验证当前用户是否对将要操作的文件和目录有无权限,例如用户A的文件FileA,用户B一般情况下可以看见,但是大概率没有修改和删除的权限(超级管理员root用户除外),因此如 rm、mv等命令一定无法执行,这也是充分体现了Linux操作系统的安全性,赞!文章到此结束 ~
一、首先来说下MarkDown语言Markdown是一种轻量级标记语言,创始人为约翰·格鲁伯(英语:John Gruber)。 它允许人们使用易读易写的纯文本格式编写文档,然后转换成有效的XHTML(或者HTML)文档。这种语言吸收了很多在电子邮件中已有的纯文本标记的特性。由于Markdown的轻量化、易读易写特性,并且对于图片,图表、数学式都有支持,目前许多网站都广泛使用Markdown来撰写帮助文档或是用于论坛上发表消息。 如GitHub、Reddit、Diaspora、Stack Exchange、OpenStreetMap 、SourceForge、简书等,甚至还能被使用来撰写电子书。二、下面来说下Typora文本编辑器Typora是markdown的一种编辑器,也是最富盛名的一款了。它解决了传统markdown最让人诟病的一点:不够直观。传统的markdown是两页式的,左面为编辑界面,右侧为效果图,但在typoa上采用了实时渲染的技术,真正做到了所见即所得.三、MarkDown基本语法1 标题语法//标题一共有6个级别,书写方式如下: # 一级标题 ## 二级标题 ### 三级标题 #### 四级标题 ##### 五级标题 ###### 六级标题测试一级标题二级标题三级标题四级标题五级标题六级标题2 字体样式语法//粗体 **你好呀,叶先森** //斜体 *我喜欢你* //删除线 ~~此内容为废弃内容~~ //高亮 `文字内容`测试//粗体你好呀,叶先森//斜体我喜欢你//删除线此内容为废弃内容//高亮文字内容3 引用语法//引用书写语法如下: >此教程来自叶十三出品,版权所有,违版必究 >>此教程来自小小叶儿出品,版权所有,违版必究 >>>此教程来自小小叶儿出品,版权所有,违版必究测试此教程来自叶十三出品,版权所有,违版必究此教程来自小小叶儿出品,版权所有,违版必究此教程来自小小叶儿出品,版权所有,违版必究4 分割线语法//分隔线(长度:根据内容而定) --- //分割线(占全屏) ***测试// 分隔线(长度:根据内容而定)//分割线(占全屏)5 图片语法//图片插入 测试6 超链接语法//超链接 [点击跳转到我的CSDN主页](https://blog.csdn.net/Mr_YanMingXin)测试//超链接点击跳转到我的CSDN主页7 列表语法//有序列表:序号+点+空格,生成列表 1. 首页 2. 分类 3. 标题 //无需列表:- + 空格 - 测试//有序列表:序号+点+空格,生成列表首页分类标题无序列表- + 空格无序列表8 表格语法测试9 代码语法//代码块> 测试 //代码块public static void main(String[] args)## 10 快捷键 > 语法 查看源代码快捷键:Ctrl+/ 回退:Ctrl+Z
1 为什么要主从复制主从复制、读写分离一般是一起使用的。目的很简单,就是为了提高数据库的并发性能。你想,假设是单机,读写都在一台MySQL上面完成,性能肯定不高。如果有三台MySQL,一台mater只负责写操作,两台salve只负责读操作,性能就能大大提高了。所以主从复制、读写分离就是为了数据库能支持更大的并发、提高数据库的可用性。2 主从复制步骤2.1 环境准备MySQL主机:IP地址:10.0.0.1端口号:3306版本:5.5MySQL从机:IP地址:10.0.0.2端口号:3306版本:5.52.2 修改配置文件MySQL主机:[mysqld] log-bin=/home/mysql/binlog server-id=1MySQL从机:[mysqld] server-id=2 log-bin=/home/mysql/binlog relay-log=/home/mysql/relaylog2.3 在MySQL中进行操作首先重启两个MySQLMySQL主机:[root@iZ2ze4m2ri7irkf6h6n8zoZ mysql]# mysql -uroot -p Enter password: Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 1 Server version: 5.5.62-log MySQL Community Server (GPL) Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved. Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. mysql> CREATE USER ymx IDENTIFIED BY '123456'; Query OK, 0 rows affected (0.00 sec) mysql> grant replication slave on *.* to 'ymx'@'10.0.0.2' identified by '123456'; Query OK, 0 rows affected (0.00 sec) mysql> flush privileges; Query OK, 0 rows affected (0.01 sec) mysql> show master status; +---------------+----------+--------------+------------------+ | File | Position | Binlog_Do_DB | Binlog_Ignore_DB | +---------------+----------+--------------+------------------+ | binlog.000003 | 594 | | | +---------------+----------+--------------+------------------+ 1 row in set (0.00 sec)MySQL从机:[root@iZ1608aqb7ntn9Z ~]# mysql -uroot -p Enter password: Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 1 Server version: 5.5.62-log MySQL Community Server (GPL) Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved. Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. mysql> show databases; +--------------------+ | Database | +--------------------+ | information_schema | | college_work | | db_chat | | db_stu_work_mg | | mysql | | performance_schema | | sysbooking | | test | | yn_db | +--------------------+ 9 rows in set (0.00 sec) mysql> change master to master_host='10.0.0.1',master_user='ymx',master_password='123456',master_port=3306,master_log_file='binlog.000003',master_log_pos=594; Query OK, 0 rows affected (0.01 sec)file='binlog.000003',master_log_pos=594; mysql> start slave; Query OK, 0 rows affected (0.00 sec) mysql> show slave status\G *************************** 1. row *************************** Slave_IO_State: Waiting for master to send event Master_Host: 10.0.0.1 Master_User: ymx Master_Port: 3306 Connect_Retry: 60 Master_Log_File: binlog.000003 Read_Master_Log_Pos: 594 Relay_Log_File: relaylog.000002 Relay_Log_Pos: 250 Relay_Master_Log_File: binlog.000003 Slave_IO_Running: Yes Slave_SQL_Running: Yes Replicate_Do_DB: Replicate_Ignore_DB: Replicate_Do_Table: Replicate_Ignore_Table: Replicate_Wild_Do_Table: Replicate_Wild_Ignore_Table: Last_Errno: 0 Last_Error: Skip_Counter: 0 Exec_Master_Log_Pos: 594 Relay_Log_Space: 399 Until_Condition: None Until_Log_File: Until_Log_Pos: 0 Master_SSL_Allowed: No Master_SSL_CA_File: Master_SSL_CA_Path: Master_SSL_Cert: Master_SSL_Cipher: Master_SSL_Key: Seconds_Behind_Master: 0 Master_SSL_Verify_Server_Cert: No Last_IO_Errno: 0 Last_IO_Error: Last_SQL_Errno: 0 Last_SQL_Error: Replicate_Ignore_Server_Ids: Master_Server_Id: 1 1 row in set (0.00 sec)2.4 测试MySQL主机:mysql> create database slave_db; Query OK, 1 row affected (0.00 sec) mysql> use slave_db; Database changed mysql> show tables; Empty set (0.00 sec) mysql> create table user( id int(20),name varchar(200)); Query OK, 0 rows affected (0.01 sec) mysql> show tables; +--------------------+ | Tables_in_slave_db | +--------------------+ | user | +--------------------+ 1 row in set (0.00 sec)MySQL从机:mysql> show databases; +--------------------+ | Database | +--------------------+ | information_schema | | college_work | | db_chat | | db_stu_work_mg | | mysql | | performance_schema | | slave_db | | sysbooking | | test | | yn_db | +--------------------+ 10 rows in set (0.00 sec) mysql> use slave_db; Database changed mysql> show tables; Empty set (0.00 sec) mysql> show tables; +--------------------+ | Tables_in_slave_db | +--------------------+ | user | +--------------------+ 1 row in set (0.00 sec)3 MySQL主从复制原理
首先引用一句名言:Don’t communicate by sharing memory; share memory by communicating. (不要通过共享内存来通信,而应该通过通信来共享内存。)-Rob Pike我是这样理解的:1 简介通道(chan)类似于一个队列,特性就是先进先出,多用于goruntine之间的通信声明方式:ch := make(chan int)放入元素:ch <- 0取出元素:elem1 := <-ch遍历元素:for data := range ch { ... }2 最基本使用func chanPlay01() { //声明一个chan,设置长度为3 ch1 := make(chan int, 3) //进channel ch1 <- 2 ch1 <- 1 ch1 <- 3 //出channel elem1 := <-ch1 elem2 := <-ch1 elem3 := <-ch1 //打印通道的值 fmt.Printf("The first element received from channel ch1: %v\n", elem1) fmt.Printf("The first element received from channel ch1: %v\n", elem2) fmt.Printf("The first element received from channel ch1: %v\n", elem3) //关闭通道 close(ch1) }3 引入panic()方法func chanPlay03() { //声明一个chan,设置长度为3 ch1 := make(chan int, 2) //进channel ch1 <- 2 ch1 <- 1 ch1 <- 3 //出channel elem1 := <-ch1 elem2 := <-ch1 elem3 := <-ch1 //打印通道的值 fmt.Printf("The first element received from channel ch1: %v\n", elem1) fmt.Printf("The first element received from channel ch1: %v\n", elem2) //panic内置函数停止当前线程的正常执行goroutine panic(ch1) fmt.Printf("The first element received from channel ch1: %v\n", elem3) //关闭通道 close(ch1) }4 不同协程间通信func main() { // 构建一个通道 ch := make(chan int) // 开启一个并发匿名函数 go func() { // 从3循环到0 for i := 3; i >= 0; i-- { // 发送3到0之间的数值 ch <- i // 每次发送完时等待 time.Sleep(time.Second) } }() // 遍历接收通道数据 for data := range ch { // 打印通道数据 fmt.Println(data) // 当遇到数据0时, 退出接收循环 if data == 0 { break } } }
相信你在使用Linux的过程中,一定会用到过诸如“?”、“*”、“%”、“|”等等的符号(一般都是在网上直接复制)这些符号在Linux的命令中是一种具有特殊功能的符号,被称为通配符或管道符。1 简介1.1 通配符通配符是一种特殊语句,用来模糊搜索文件。当查找文件夹时,可以使用它来代替一个或多个真正字符;当不知道真正字符或者懒得输入完整名字时,常常使用通配符代替一个或多个真正的字符。常用的通配符:* :表示匹配一个或多个字符 ? :表示匹配一个字符(不能是0个字符) [] :类似于正则表达式(只能是匹配一个字符)1.2 管道符管道命令符的作用是把前一个命令原本要输出到屏幕的标注正常数据当做是后一个命令的标准输入。一条命令中可以有多个管道符,只要前面的命令有输出结果,管道符后面的命令即可再执行。常用的管道符:| :是把前一个命令原本要输出到屏幕的标注正常数据当做是后一个命令的标准输入2 通配符为了实验方便,我们先新建几个文件[root@iZ1608aqb7ntn9Z Test0901]# touch file01 file02 file03 [root@iZ1608aqb7ntn9Z Test0901]# ls file01 file02 file032.1 * 的使用[root@iZ1608aqb7ntn9Z Test0901]# ls file* #查看以file开头的文件 file01 file02 file03 [root@iZ1608aqb7ntn9Z Test0901]# ls file01 file02 file03 [root@iZ1608aqb7ntn9Z Test0901]# rm -rf * #删除当前目录下全部文件 [root@iZ1608aqb7ntn9Z Test0901]# ls [root@iZ1608aqb7ntn9Z Test0901]# 2.2 ?的使用[root@iZ1608aqb7ntn9Z Test0901]# touch file01 file02 file03 [root@iZ1608aqb7ntn9Z Test0901]# ls file01 file02 file03 [root@iZ1608aqb7ntn9Z Test0901]# ls file?1 file01 [root@iZ1608aqb7ntn9Z Test0901]# rm -rf file?2 [root@iZ1608aqb7ntn9Z Test0901]# ls file01 file03 [root@iZ1608aqb7ntn9Z Test0901]# rm -rf file0? [root@iZ1608aqb7ntn9Z Test0901]# ls [root@iZ1608aqb7ntn9Z Test0901]# 2.3 [] 的使用[root@iZ1608aqb7ntn9Z Test0901]# ls file01 file02 file03 [root@iZ1608aqb7ntn9Z Test0901]# ls file0[1] file01 [root@iZ1608aqb7ntn9Z Test0901]# ls file0[12] file01 file02 [root@iZ1608aqb7ntn9Z Test0901]# ls file0[1-2] file01 file02 [root@iZ1608aqb7ntn9Z Test0901]# ls file0[0-1] file013 管道符3.1 基本语法bash命令 | 管道命令3.2 何为bash命令和管道命令?首先解释下bash命令,就是Linux中的一切具有能够输入的命令,例如ps输出进程信息,ls输出文件列表等等管道命令,就是能够接收标准输出,例如grep,less,head,tail等命令3.3 管道符基本使用3.3.1 grep截取出ps -ef命令里带有docker字符的所有命令[root@iZ1608aqb7ntn9Z Test0901]# ps -ef|grep docker root 369560 1 0 8月03 ? 00:05:24 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock root 777276 710637 0 14:37 pts/0 00:00:00 grep --color=auto docker截取带有java进程的命令也是如此:[root@iZ1608aqb7ntn9Z Test0901]# ps -ef|grep java root 46858 1 0 5月02 ? 02:03:47 /usr/local/jdk1.8.0_141/bin/java -Djava.util.logging.config.file=/opt/20210406/conf/logging.properties -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -D...... root 779698 710637 0 14:38 pts/0 00:00:00 grep --color=auto java root 3369864 1 0 8月24 ? 00:07:09 java -jar stumg-0.0.1-SNAPSHOT.jar 使用ls命令也可以哦:[root@iZ1608aqb7ntn9Z Test0901]# ls file01 file02 file03 [root@iZ1608aqb7ntn9Z Test0901]# ls |grep 02 file023.3.2 wc查看前一条命令输入的个数:[root@iZ1608aqb7ntn9Z Test0901]# ls -a | wc -l 5 [root@iZ1608aqb7ntn9Z Test0901]# ls | wc -l 3
开源地址:https://github.com/go-sql-driver/mysql1 简介正如官方所说,Go-MySQL-Driver是一个Go的sql或数据库操作包。2 优势纯粹的Go语言实现,轻量级和快速z支持TCP/IPv4, TCP/IPv6, Unix域套接字或自定义协议自动处理断开的连接,自动连接池(通过数据库/sql包)支持大于16MB的查询完整的sql体系,智能长数据处理3 上手操作3.1 建表CREATE TABLE `student` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(255) DEFAULT NULL, `age` int(2) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;3.2 新建Go Modoules并下载Go-MySQL-Driver依赖在项目根目录下执行:go get -u github.com/go-sql-driver/mysql3.3 代码package main import ( "database/sql" "fmt" _ "github.com/go-sql-driver/mysql" "time" ) var my_db *sql.DB //数据库 var my_tb = "student" //表名 /** 实体类 */ type Student struct { Id int Name string Age int } /** 数据库连接初始化 */ func init() { var err error my_db, err = sql.Open("mysql", "root:12345678@tcp(127.0.0.1:3306)/test") my_db.SetConnMaxLifetime(100 * time.Second) // 超时时间 my_db.SetMaxOpenConns(0) // 设置最大打开的连接数,默认值为0表示不限制。 my_db.SetMaxIdleConns(16) // 设置闲置的连接数 if err != nil { fmt.Println("连接失败") } fmt.Println("连接成功") // 检查是否连接成功数据库 } /** 查看全部 */ func FindAllStudent() []Student { var students []Student var stu Student rows, err := my_db.Query("SELECT * FROM " + my_tb) if err != nil { return nil } for rows.Next() { err := rows.Scan(&stu.Id, &stu.Name, &stu.Age) if err != nil { return nil } students = append(students, stu) } return students } /** 添加一个 */ func SaveAdminPwd(stu Student) bool { tx, err := my_db.Begin() if err != nil { return false } stmt, err := tx.Prepare("INSERT INTO " + my_tb + "(id,name,age) VALUES (?,?,?)") if err != nil { return false } result, err := stmt.Exec(stu.Id, stu.Name, stu.Age) tx.Commit() if err != nil { return false } println(result) return true } /** 删除一个根据id */ func DeleteStudentById(id int) bool { tx, err := my_db.Begin() if err != nil { return false } stmt, err := tx.Prepare("DELETE FROM " + my_tb + " WHERE id=?") if err != nil { return false } result, err := stmt.Exec(id) if err != nil { return false } tx.Commit() //别忘 println(result) return true } /** 查看一个是否存在 */ func GetStudentById(id int) Student { var stu Student stmt, err := my_db.Prepare("SELECT * FROM " + my_tb + " WHERE id=?") rows, err := stmt.Query(id) if err != nil { return Student{} } if rows.Next() { err := rows.Scan(&stu.Id, &stu.Name,&stu.Age) if err != nil { return Student{} } } return stu } /** 修改一个 */ func UpdateStudentById(stu Student) bool { prepare, err := my_db.Prepare("UPDATE " + my_tb + " SET name=?,age=? WHERE id=?") if err != nil { return false } exec, err := prepare.Exec(stu.Name, stu.Age, stu.Id) if err != nil { return false } print(exec) return true } /** 使用main函数测试 */ func main() { stu := Student{3, "ww", 32} SaveAdminPwd(stu) //DeleteStudentById(2) studentById := GetStudentById(3) fmt.Println(studentById) UpdateStudentById(Student{3,"zsss",21}) s := GetStudentById(3) fmt.Println(s) students := FindAllStudent() for _, stu := range students { fmt.Println(stu) } }~
一、简介1.1 IO(BIO)---阻塞式IO起源于JDK1.0java.io 包几乎包含了所有操作输入、输出需要的类。所有这些流类代表了输入源和输出目标。java.io 包中的流支持很多种格式,比如:基本类型、对象、本地化字符集等等。一个流可以理解为一个数据的序列。输入流表示从一个源读取数据,输出流表示向一个目标写数据。Java 为 I/O 提供了强大的而灵活的支持,使其更广泛地应用到文件传输和网络编程中。1.2 NIO---非阻塞式IO起源于JDK1.4Java NIO可以让你非阻塞的使用IO,例如:当线程从通道读取数据到缓冲区时,线程还是可以进行其他事情。当数据被写入到缓冲区时,线程可以继续处理它。从缓冲区写入通道也类似。1.3 BIO和NIO对比标准的IO基于字节流和字符流进行操作的,而NIO是基于通道(Channel)和缓冲区(Buffer)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。二、使用2.1 BIO使用2.1.1 读取文件内容到控制台 /** * IO读取文件到控制台 */ public static void readFile() { File file = new File("D:\\desktop\\io.txt"); try { FileReader fr = new FileReader(file); BufferedReader bufferedReader = new BufferedReader(fr); String str = bufferedReader.readLine(); while (str != null) { System.out.println(str); str = bufferedReader.readLine();//将reader置为空 } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } }2.1.2 写入文本内容到文件中 /** * IO写入(生成)文件 */ public static void writeFile() { File file = new File("D:\\desktop\\io2.txt"); FileOutputStream outputStream = null; try { outputStream = new FileOutputStream(file); String str = "Hello Write File"; outputStream.write(str.getBytes()); outputStream.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { try { if (outputStream != null) outputStream.close(); } catch (IOException e) { e.printStackTrace(); } } }2.1.3 复制文件 /** * IO复制文件 */ public static void copyFile() { InputStream inputStream = null; OutputStream outputStream = null; try { inputStream = new FileInputStream("D:\\desktop\\Linux软件安装包.zip"); //文件大小387M outputStream = new FileOutputStream("D:\\desktop\\Linux软件安装包_cp.zip"); byte[] buf = new byte[1024]; int len = -1; while ((len = inputStream.read(buf)) != -1) { outputStream.write(buf, 0, len); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { if (outputStream != null) { try { outputStream.close(); inputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } }2.2 NIO使用2.2.1 NIO方式读取文件内容 /** * NIO读取文件 */ public static void read() { RandomAccessFile access = null; try { access = new RandomAccessFile(new File("D:\\desktop\\io.txt"), "r"); } catch (FileNotFoundException e) { e.printStackTrace(); } FileChannel channel = access.getChannel(); ByteBuffer byteBuffer = ByteBuffer.allocate(1024); CharBuffer charBuffer = CharBuffer.allocate(1024); Charset charset = Charset.forName("utf-8"); CharsetDecoder decoder = charset.newDecoder(); int length = 0; try { length = channel.read(byteBuffer); while (length != -1) { byteBuffer.flip(); decoder.decode(byteBuffer, charBuffer, true); charBuffer.flip(); System.out.println(charBuffer.toString()); if (byteBuffer != null) byteBuffer.clear(); if (charBuffer != null) charBuffer.clear(); length = channel.read(byteBuffer); // 再次读取文本内容 } } catch (IOException e) { e.printStackTrace(); } finally { try { if (channel != null) channel.close(); if (access != null) access.close(); } catch (IOException e) { e.printStackTrace(); } } } 2.2.2 NIO方式写入文件/** * NIO写文件 * * @param context * @throws IOException */ public static void write(String context) { FileOutputStream outputStream = null; FileChannel channel = null; try { outputStream = new FileOutputStream(new File("D:\\desktop\\nio.txt"), true);//允许文件内容追加而不是覆盖 channel = outputStream.getChannel(); ByteBuffer byteBuffer = ByteBuffer.allocate(1024); byteBuffer.put(context.getBytes("utf-8")); byteBuffer.flip();//读取模式转换为写入模式 channel.write(byteBuffer); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { try { if (channel != null) channel.close(); if (outputStream != null) outputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } 2.2.3 NIO方式复制文件 /** * NIO复制文件 * * @param source * @param target */ public static void nioCopy(String source, String target) { ByteBuffer byteBuffer = ByteBuffer.allocate(1024); FileInputStream inputStream = null; FileChannel inChannel = null; FileOutputStream outputStream = null; FileChannel outChannel = null; try { inputStream = new FileInputStream(source); inChannel = inputStream.getChannel(); outputStream = new FileOutputStream(target); outChannel = outputStream.getChannel(); int length = 0; length = inChannel.read(byteBuffer); while (length != -1) { byteBuffer.flip();//读取模式转换写入模式 outChannel.write(byteBuffer); byteBuffer.clear(); //清空缓存,等待下次写入 length = inChannel.read(byteBuffer); // 再次读取文本内容 } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { try { outputStream.close(); outChannel.close(); inputStream.close(); inChannel.close(); } catch (IOException e) { e.printStackTrace(); } } }
1 概述代理模式是在二十三种设计模式中比较接近现实生活和容易理解的一种设计模式,简单的来说就是A通过B的代理,与C进行通信,如图:当然这样看来并没有增加A与C通信的效率,反而多了一层代理,但是,当A多了以后呢?显然,这样可以只让B与C进行通信,类似于多加了一层的代理层,专门用来处理请求,再来一个现实点的例子:如果一个人有事想找你你可以直接与他对话,而如果一群人想找你你一定不能同时与他们进行对话,就需要一个一个的回复,比如我们平常的移动联系方式微信,此时他就可以代理你进行消息的接收和回复,如图 :因此,我们总结下代理模式的概念和优点:定义:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。优点:(1)职责清晰 (2)高扩展性(3)智能化2 代理模式(静态代理)静态代理简单的说就是接口代理实现类,当一个或多个类实现一个接口后,需要将接口的方法进行实现,当类进行实例化时使用接口进行代理方法的实现,直接调用接口的方法而不是实现类的方法。/** * 代理模式(静态代理) * * @author 17122 */ public class StaticProxy { public static void main(String[] args) { People people = new Programmer(); people.sayHello(); } } interface People { /** * sayHello方法 */ void sayHello(); } class Student implements People { @Override public void sayHello() { System.out.println("Hello I am a Student"); } public void study() { System.out.println("Study"); } } class Programmer implements People { @Override public void sayHello() { System.out.println("Hello I am a Programmer"); } public void playCode() { System.out.println("Play code"); } }3 动态代理动态代理的实现较为复杂,因为要实现动态代理就需要依托Java的反射机制,就是JDK官方原生的代理,继承InvocationHandler接口,实现invoke方法,就可以自定义代理工具类,根据不同的类进行动态的代理。/** * 动态代理 * * @author 17122 */ public class DynamicProxy { public static void main(String[] args) { Boos boos = new Boos(); Worker worker = new Worker(); getDynamicProxy(worker, Worker.class); getDynamicProxy(boos, Boos.class); } /** * 动态代理实现方法 * * @param obj * @param clazz */ public static void getDynamicProxy(Object obj, Class clazz) { MyInvocationHandler myInvocationHandler = new MyInvocationHandler(obj); Courier instance = (Courier) Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), myInvocationHandler); instance.takeSomething(); } } /** * 快递员 */ interface Courier { /** * 取东西 */ void takeSomething(); } class Worker implements Courier { @Override public void takeSomething() { System.out.println("Worker take Something"); } } class Boos implements Courier { @Override public void takeSomething() { System.out.println("Boos take Something"); } } class MyInvocationHandler implements InvocationHandler { /** * 被代理的目标类 */ private Object target; /** * 构造方法 放入目标类 * * @param target */ public MyInvocationHandler(Object target) { this.target = target; } /** * Returns an instance of a proxy class for the specified interfaces that dispatches method invocations to the specified invocation handler. * 返回指定接口的代理类实例,该接口将方法调用分派给指定的调用处理程序。 * * @param proxy the class loader to define the proxy class 定义代理类的类装入器 * @param method the list of interfaces for the proxy class to implement 代理类要实现的接口列表 * @param args the invocation handler to dispatch method invocations to 将方法调用分派到的调用处理程序 * @return * @throws Throwable */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object result = method.invoke(target, args); return result; } }4 静态代理和动态代理有什么不同(1)最主要的不同就是动态代理依靠反射机制,可以在程序运行的过程中进行被代理对象的确定,而静态代理在程序编译时期就已经确定。(2)静态代理需要自己写代码进行完成,而动态代理就是通过Proxy.newInstance()生成。
1 @ConfigurationProperties说明在编写项目代码时,我们要求更灵活的配置,更好的模块化整合。在 Spring Boot 项目中,为满足以上要求,我们将大量的参数配置在 application.properties 或 application.yml 文件中,通过 @ConfigurationProperties 注解,我们可以方便的获取这些参数值2 使用@ConfigurationPropertiesConfigurationPropertiesTest.java/** * @desc: @ConfigurationProperties * @author: YanMingXin * @create: 2021/7/27-17:36 **/ @ConfigurationProperties(prefix = "student") @Component public class ConfigurationPropertiesTest { private String name; private String age; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getAge() { return age; } public void setAge(String age) { this.age = age; } }SpringUtil.java/** * @desc: SpringUtil * @author: YanMingXin * @create: 2021/7/27-17:55 **/ @Component public class SpringUtil implements ApplicationContextAware { private static ApplicationContext applicationContext; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { if (SpringUtil.applicationContext == null) { SpringUtil.applicationContext = applicationContext; } } /** * 获取applicationContext * * @return */ public static ApplicationContext getApplicationContext() { return applicationContext; } /** * 通过name获取 Bean * * @param name * @return */ public static Object getBean(String name) { return getApplicationContext().getBean(name); } /** * 通过class获取Bean * * @param clazz * @param <T> * @return */ public static <T> T getBean(Class<T> clazz) { return getApplicationContext().getBean(clazz); } /** * 通过name,以及Clazz返回指定的Bean * * @param name * @param clazz * @param <T> * @return */ public static <T> T getBean(String name, Class<T> clazz) { return getApplicationContext().getBean(name, clazz); } }application.propertiesstudent.name=zs student.age=23测试:@Test void contextLoads() { ConfigurationPropertiesTest bean = (ConfigurationPropertiesTest) SpringUtil.getBean("configurationPropertiesTest"); System.out.println(bean.getAge()); }输出:23
1 基本介绍Shell 是一个用 C 语言编写的程序,它是用户使用 Linux 的桥梁。Shell 既是一种命令语言,又是一种程序设计语言。2 Hello World2.1 文件命名和头部标识、执行方式文件命名:一般以xx.sh为shell脚本文件的名称头部标识:一般在文件内容的第一行加入#!/bin/bash执行方式:一般使用命令./xx.sh执行shell脚本文件2.2 编写HelloWorldymx@ymx-PC:~/Desktop/files/shellTest$ sudo touch hello.sh ymx@ymx-PC:~/Desktop/files/shellTest$ ls hello.sh ymx@ymx-PC:~/Desktop/files/shellTest$ sudo vim hello.sh ###########文件内容############## #!/bin/bash echo Hello World! ###########文件内容结束############## ymx@ymx-PC:~/Desktop/files/shellTest$ sudo chmod 777 hello.sh ymx@ymx-PC:~/Desktop/files/shellTest$ ./hello.sh Hello World!3 引入变量3.1 定义变量变量名 = 变量值 例如:str = "Hello Shell"注意:等号两边不能加空格3.2 使用变量操作命令 $变量名例如:echo $str3.3 示例ymx@ymx-PC:~/Desktop/files/shellTest$ sudo touch args1.sh ymx@ymx-PC:~/Desktop/files/shellTest$ sudo chmod 777 args1.sh ymx@ymx-PC:~/Desktop/files/shellTest$ vim args1.sh ##########写入内容########## #!/bin/bash str="Hello Shell" echo $str ##########写入内容结束########## ymx@ymx-PC:~/Desktop/files/shellTest$ ./args1.sh Hello Shell4 参数使用4.1 参数接收$0表示所执行的文件名$1表示接收的第一个参数$2表示接收的第二个参数......4.2 基本使用ymx@ymx-PC:~/Desktop/files/shellTest$ sudo touch args2.sh ymx@ymx-PC:~/Desktop/files/shellTest$ sudo chmod 777 args2.sh ymx@ymx-PC:~/Desktop/files/shellTest$ vim args2.sh ##########开始写入######### #!/bin/bash echo "执行文件名为$0" echo "第一个输入参数为$1" echo "第二个输入参数为$2" ##########写入结束######### ymx@ymx-PC:~/Desktop/files/shellTest$ ./args2.sh 1 2 执行文件名为./args2.sh 第一个输入参数为1 第二个输入参数为24.3 几个特殊参数参数处理说明$#传递到脚本的参数个数$*以一个单字符串显示所有向脚本传递的参数$$脚本运行的当前进程ID号$!后台运行的最后一个进程的ID号$@与$*相同,但是使用时加引号,并在引号中返回每个参数。$-显示Shell使用的当前选项,与set命令功能相同。$?显示最后命令的退出状态。0表示没有错误,其他任何值表明有错误。5 Shell数组5.1 定义数组方式一:array_1=(1 2 3 4 5)方式二:array_2[0]=21 array_2[1]=22 array_2[2]=23 array_2[3]=24 array_2[4]=25注:shell只有一维数组5.2 使用数组(1)使用数组数组名称[index]:获取第index个元素,从0开始数组名称[*]:获取全部元素(2)案例[root@iZ1608aqb7ntn9Z shellTest]# touch array.sh [root@iZ1608aqb7ntn9Z shellTest]# vim array.sh #########写入内容######### #!/bin/bash array_1=(1 2 3 4 5) array_2[0]=21 array_2[1]=22 array_2[2]=23 array_2[3]=24 array_2[4]=25 echo array_1 first element is ${array_1[0]} and all elements are [${array_1[*]}] echo array_2 first element is ${array_2[0]} and all elements are [${array_2[*]}] #########写入结束######### [root@iZ1608aqb7ntn9Z shellTest]# ./array.sh array_1 first element is 1 and all elements are [1 2 3 4 5] array_2 first element is 21 and all elements are [21 22 23 24 25]5.3 数组长度echo array_1 length is ${#array_1[*]} echo array_1 length is ${#array_1[@]}6 流程控制6.1 判断语句6.1.1 if语句(1)基本语法if [ 表达式 ] then 语句1 elif [ 表达式 ] 语句2 else 语句3 fi(2)案例[root@iZ1608aqb7ntn9Z shellTest]# vim if.sh #####################写入内容########################## #!/bin/bash a=$1 b=$2 if [ $a == $b ] then echo "a 等于 b" elif [ $a -gt $b ] then echo "a 大于 b" elif [ $a -lt $b ] then echo "a 小于 b" else echo "没有符合的条件" fi #####################写入内容结束########################## [root@iZ1608aqb7ntn9Z shellTest]# ./if.sh 2 4 a 小于 b [root@iZ1608aqb7ntn9Z shellTest]# ./if.sh 8 4 a 大于 b [root@iZ1608aqb7ntn9Z shellTest]# ./if.sh 4 4 a 等于 b6.1.2 case语句(1)基本语法case 变量 in 值1) 语句1 ;; 值2) 语句2 ;; 值3) 语句3 ;; esac(2)案例[root@iZ1608aqb7ntn9Z shellTest]# vim case.sh #####################写入内容############################## #!/bin/bash i=$1 case $i in 1) echo "print one" ;; 2) echo "print two" ;; 3) echo "print three" ;; esac #####################写入内容结束########################## [root@iZ1608aqb7ntn9Z shellTest]# ./case.sh 1 print one [root@iZ1608aqb7ntn9Z shellTest]# ./case.sh 2 print two [root@iZ1608aqb7ntn9Z shellTest]# ./case.sh 3 print three6.2 循环语句6.2.1 for循环(1)基本语法for 变量 in 变量1 变量2 ...... do 语句 done(2)案例[root@iZ1608aqb7ntn9Z shellTest]# vim for.sh #####################写入内容############################## #/bin/bash for val in 1 2 3 4 5 do echo $val done #####################写入内容结束########################## [root@iZ1608aqb7ntn9Z shellTest]# ./for.sh 1 2 3 4 56.2.2 while循环(1)基本语法while(( 表达式 )) do 语句 done 或者 while true do 语句 done(2)案例[root@iZ1608aqb7ntn9Z shellTest]# vim while.sh #!/bin/bash i=1 while(( $i<=3 )) do echo $i let "i++" done [root@iZ1608aqb7ntn9Z shellTest]# ./while.sh 1 2 3
下载安装:go get github.com/robfig/cronv3版本安装(适用于Go 1.11版本及之后的):go get github.com/robfig/cron/v3@v3.0.0代码:package main import ( "fmt" "github.com/robfig/cron/v3" "time" ) func main() { methodB() } func methodA() { c := cron.New(cron.WithSeconds()) //精确到秒级,V3版本之后提供的 //定时任务 spec := "*/1 * * * * ?" //cron表达式,每秒一次 c.AddFunc(spec, func() { fmt.Println("methodA 每秒一次...") }) c.Start() select {} //阻塞主线程停止 } func methodB() { c := cron.New() //定时任务 spec := "*/1 * * * * ?" //cron表达式,每秒一次 c.AddFunc(spec, func() { fmt.Println("methodA 每秒一次...") time.Sleep(time.Second*5) c.Stop()//停止任务 }) c.Start() select { } } func methodC() { fmt.Println("methodC 定时任务C") } func methodE() { fmt.Println("methodC 定时任务C") } func methodD() { c := cron.New() //定时任务 spec := "*/1 * * * * ?" //cron表达式,每秒一次 c.AddFunc(spec, methodE) c.AddFunc(spec, methodC) c.Start() select {} //阻塞主线程停止 } 常用的cron字符串:https://blog.csdn.net/ysq222/article/details/88965936
第一个http程序:Hello Worldpackage main import "net/http" //最简单的HTTP服务 func RunHttp1() { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Hello Http!")) }) http.ListenAndServe(":8080", nil) } func main() { RunHttp1() }PostMan测试:一般请求一般参数请求:package main import ( "fmt" "net/http" ) func GetMethod1() { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { //获取参数 str := r.URL.Query().Get("str") fmt.Println("Get Method Str is " + str) w.Write([]byte("Hello Http Get!")) }) http.ListenAndServe(":8080", nil) } func main() { GetMethod1() }测试:表单请求package main import ( "fmt" "net/http" "strconv" ) type Student struct { Name string Age int } func GetMethod2() { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { //获取参数 name := r.URL.Query().Get("name") age := r.URL.Query().Get("age") //将string类型转为int类型 ageStr, err := strconv.Atoi(age) if err != nil { fmt.Println("err...") } stu := Student{Name: name, Age: ageStr} fmt.Println("Get Method Str is ", stu) w.Write([]byte("Hello Http Get Form!")) }) http.ListenAndServe(":8080", nil) } func main() { GetMethod2() } JSON格式的HTTP报文package main import ( "encoding/json" "fmt" "io/ioutil" "net/http" ) //自定义结构体 对应JSON结构体 type Student struct { Id int64 `json:"id"` Name string `json:"name"` Age int `json:"age"` } func TakeJSON() { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { //获取请求方法 fmt.Println("req method : ", r.Method) //获取请求题 body, err := ioutil.ReadAll(r.Body) if err != nil { fmt.Printf("获取请求体错误 , %v\n", err) return } fmt.Println("请求体 :", string(body)) //将请求的JSON转化为结构体 var stu Student if err = json.Unmarshal(body, &stu); err != nil { fmt.Printf("反序列化失败 , %v\n", err) return } fmt.Printf("反序列化成功,JSON解析结果 %+v", stu) w.Write([]byte("Hello Http Get Form!")) }) http.ListenAndServe(":8080", nil) } func main() { TakeJSON() }
1 什么是Spring Starter摘自官网Starters are a set of convenient dependency descriptors that you can include in your application. You get a one-stop shop for all the Spring and related technologies that you need without having to hunt through sample code and copy-paste loads of dependency descriptors. For example, if you want to get started using Spring and JPA for database access, include the spring-boot-starter-data-jpa dependency in your project.翻译下:启动器是一组方便的依赖项描述符,您可以在应用程序中包含它们。您可以获得所需的所有Spring和相关技术的一站式服务,而无需查找示例代码和复制-粘贴大量依赖描述符。例如,如果您想开始使用Spring和JPA进行数据库访问,请在您的项目中包含Spring -boot-starter-data- JPA依赖项。2 自定义Starter的Hello World2.1 自定义Spring Starter的一般流程(1)新建Spring Boot项目(2)添加依赖(3)调整项目结构(4)自定义Configuration配置类(5)增加spring.factories文件,指定自动配置类(6)maven install安装到本地仓库(7)其他项目引入使用下面我们就来一一的实现下:2.2 具体流程2.2.1 新建Spring Boot项目<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.4.RELEASE</version> <relativePath/> </parent> <groupId>org.ymx</groupId> <artifactId>go-kafka-log-spring-starter</artifactId> <version>0.0.1-RELEASE</version>2.2.2 添加依赖<!--引入web依赖,下文自定义拦截器使用--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--自定义starter需要的依赖 autoconfigure--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-autoconfigure</artifactId> </dependency> <!--自定义starter需要的依赖 configuration-processor--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency> <!-- lombok--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency>2.2.3 调整项目结构一般就是:删除主启动类删除test包删除主配置文件2.2.4 自定义Configuration配置类LogAutoConfig.java@Configuration @EnableConfigurationProperties({ConfigProperties.class}) @ConditionalOnProperty(prefix = "go.kafka.log", name = "enable", havingValue = "true") public class LogAutoConfig { @Autowired private ConfigProperties configProperties; @Bean(name = "logService") public LogService getLogService() { LogService logService = new LogService(); logService.setConfigProperties(configProperties); return logService; } }ConfigProperties.java@Component @ConfigurationProperties("go.kafka.log") public class ConfigProperties { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } }LogService.javapublic class LogService { private ConfigProperties configProperties; public String getName() { return configProperties.getName(); } public void setConfigProperties(ConfigProperties configProperties) { this.configProperties = configProperties; } public String hello() { return "Hello " + getName() + "!"; } }2.2.5 增加spring.factories文件,指定自动配置类spring.factoriesorg.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.ymx.log.config.LogAutoConfig2.2.6 maven install安装到本地仓库2.2.7 测试新建Spring Boot项目<dependency> <groupId>org.ymx</groupId> <artifactId>go-kafka-log-spring-starter</artifactId> <version>0.0.1-RELEASE</version> </dependency>application.propertiesserver.port=8888 go.kafka.log.name=Spring Starter go.kafka.log.enable=trueHelloController.java@RestController public class HelloController { @Resource private LogService logService; @GetMapping("/hello") public String hello() { return logService.hello(); } }测试:3 自定义Starter实现拦截器3.1 编写自定义拦截器@Component public class LogInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("----------------------"+request.getMethod()+"----------------------"); return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("----------------------"+request.getMethod()+"----------------------"); HandlerInterceptor.super.postHandle(request, response, handler, modelAndView); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { HandlerInterceptor.super.afterCompletion(request, response, handler, ex); } }3.2 starter增加配置类@Configuration @ConditionalOnProperty(prefix = "go.kafka.log.interception", name = "enable", havingValue = "true") public class LogInterceptorConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LogInterceptor()) .addPathPatterns("/**") ; } }3.3 调用方增加配置文件内容,主启动类增加注解配置文件:go.kafka.log.enable=true主启动类:@SpringBootApplication @ComponentScans(value = {@ComponentScan("org.ymx.log"),@ComponentScan("com.example.demo")}) public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }3.4 测试访问任意url4 自定义Starter实现全局异常处理4.1 编写starter全局异常处理类@RestControllerAdvice public class LogAdviceHandler { @ExceptionHandler(value = NullPointerException.class) public String exceptionHandlerJwt(NullPointerException e) { e.printStackTrace(); return "error----NullPointerException"; } @ExceptionHandler(value = Exception.class) public String exceptionHandler(Exception e) { e.printStackTrace(); return "error----Exception"; } }4.2 调用方制造异常测试@GetMapping("/ex") public String ex() { String str = null; str.equals("2"); return "22"; }测试结果:5 总结几个重要的注解@ConditionalOnProperty(prefix = "go.kafka.log", name = "enable", havingValue = "true")prefix为配置文件中的前缀,name为配置的名字havingValue是与配置的值对比值,当两个值相同返回true,配置类生效.@EnableConfigurationProperties({ConfigProperties.class})使@ConfigurationProperties()注解生效@ConfigurationProperties("go.kafka.log")见文章:https://blog.csdn.net/Mr_YanMingXin/article/details/119249740@ComponentScans(value = {@ComponentScan("org.ymx.log"),@ComponentScan("com.example.demo")})扫描的bean的路径,这里是扫描starter的路径加上本项目的路径
1 代码演示下代码演示:package main import "fmt" func main() { testMap() fmt.Println("--------") testSlice() fmt.Println("--------") testChannel() } func testMap() { mmap := make(map[string]int64) nmap := new(map[string]int64) fmt.Println("&mmap = ", &mmap, "------ mmap = ", mmap) fmt.Println("&nmap = ", &nmap, "------ nmap = ", nmap) mmap = map[string]int64{"key": 100} nmap = &map[string]int64{"key": 100} fmt.Println("&mmap = ", &mmap, "------ mmap = ", mmap) fmt.Println("&nmap = ", &nmap, "------ nmap = ", nmap) } func testSlice() { mslice := make([]int, 0, 10) mslice2 := make([]int, 0) nslice := new([]int) fmt.Println("&mslice = ", &mslice, "------ mslice = ", mslice) fmt.Println("&mslice2 = ", &mslice2, "------ mslice2 = ", mslice2) fmt.Println("&nslice = ", &nslice, "------ nmap = ", nslice) mslice = append(mslice, 111) mslice2 = append(mslice2, 222) *nslice = append(*nslice, 333) fmt.Println("&mslice = ", &mslice, "------ mslice = ", mslice) fmt.Println("&mslice2 = ", &mslice2, "------ mslice2 = ", mslice2) fmt.Println("&nslice = ", &nslice, "------ nmap = ", nslice) } func testChannel() { mchan := make(chan int, 1) mchan2 := make(chan int, 10) nchan := new(chan int) fmt.Println("&mchan = ", &mchan, "------ mchan = ", mchan) fmt.Println("&mchan2 = ", &mchan2, "------ mchan2 = ", mchan2) fmt.Println("&nchan = ", &nchan, "------ nchan = ", nchan) mchan <- 10 mchan2 <- 20 *nchan = make(chan int, 1) *nchan <- 30 fmt.Println("&mchan = ", &mchan, "------ mchan = ", <-mchan) fmt.Println("&mchan2 = ", &mchan2, "------ mchan2 = ", <-mchan2) fmt.Println("&nchan = ", &nchan, "------ nchan = ", <-*nchan) }运行结果:&mmap = &map[] ------ mmap = map[] &nmap = 0xc1200ac020 ------ nmap = &map[] &mmap = &map[key:100] ------ mmap = map[key:100] &nmap = 0xc1200ac020 ------ nmap = &map[key:100] -------- &mslice = &[] ------ mslice = [] &mslice2 = &[] ------ mslice2 = [] &nslice = 0xc1200ac040 ------ nmap = &[] &mslice = &[111] ------ mslice = [111] &mslice2 = &[222] ------ mslice2 = [222] &nslice = 0xc1200ac040 ------ nmap = &[333] -------- &mchan = 0xc1200ac048 ------ mchan = 0xc1200c6000 &mchan2 = 0xc1200ac050 ------ mchan2 = 0xc1200c8000 &nchan = 0xc1200ac058 ------ nchan = 0xc1200ac060 &mchan = 0xc1200ac048 ------ mchan = 10 &mchan2 = 0xc1200ac050 ------ mchan2 = 20 &nchan = 0xc1200ac058 ------ nchan = 302 翻源码深入了解下源码:// The make built-in function allocates and initializes an object of type // slice, map, or chan (only). Like new, the first argument is a type, not a // value. Unlike new, make's return type is the same as the type of its // argument, not a pointer to it. The specification of the result depends on // the type: // Slice: The size specifies the length. The capacity of the slice is // equal to its length. A second integer argument may be provided to // specify a different capacity; it must be no smaller than the // length. For example, make([]int, 0, 10) allocates an underlying array // of size 10 and returns a slice of length 0 and capacity 10 that is // backed by this underlying array. // Map: An empty map is allocated with enough space to hold the // specified number of elements. The size may be omitted, in which case // a small starting size is allocated. // Channel: The channel's buffer is initialized with the specified // buffer capacity. If zero, or the size is omitted, the channel is // unbuffered. func make(t Type, size ...IntegerType) Type // The new built-in function allocates memory. The first argument is a type, // not a value, and the value returned is a pointer to a newly // allocated zero value of that type. func new(Type) *Type简单翻译下://make内置函数分配并初始化一个类型对象切片、映射或chan(仅这三个)。像new一样,第一个参数是一个类型,而不是value。与new不同,make的返回类型与他的参数类型相同,而不是指向它的指针。结果的规格取决于类型: //- Slice:大小指定长度。切片的容量为等于它的长度。可以提供第二个整数参数指定不同的容量;它必须不小于长度。例如,make([]int, 0,10)分配一个底层数组的大小为10,返回长度为0,容量为10的切片由此基础数组支持。 //- Map:为空映射分配足够的空间来容纳指定的元素数。在这种情况下,可以省略字号分配一个小的起始大小。 //- Channel:通道的缓冲区用指定的参数初始化缓冲能力。如果为零,或者省略了大小,则通道为无缓冲的。 func make(t Type, size ...IntegerType) Type //new的内置函数分配内存。第一个参数是一个类型,不是一个value,返回的值是一个指向new的指针分配该类型的零值。 func new(Type) *Type3 总结下Go语言中的 new 和 make 主要区别如下:make 只能用来分配及初始化类型为 slice、map、chan 的数据。new 可以分配任意类型的数据;new 分配返回的是指针,即类型 *Type。make 返回引用,即 Type;new 分配的空间被清零。make 分配空间后,会进行初始化;最后,简单总结一下Go语言中 make 和 new 关键字的实现原理,make 关键字的主要作用是创建 slice、map 和 Channel 等内置的数据结构,而 new 的主要作用是为类型申请一片内存空间,并返回指向这片内存的指针。
1 场景再现今天上午刚想用云服务器传输下文件,当打开finalshell连接服务器时突然发现服务器的系统指标很异常,而且在终端输入命令的时候都非常的卡,图示:很明显我们可以看出服务器的异常状态:CPU高负载内存高利用率网络高出口带宽2 尝试解决2.1 尝试使用netstat命令检查网络连接状态首先使用最基本的netstat命令查看网络的连接状态#### 命令 netstat #### 输出字段含义 Proto # Recv-Q # Send-Q # Local # Address # Foreign Address # State #2.2 回顾TCP三次握手由TCP三次握手的流程和各自的状态我们可以看出,当客户端向服务器发送玩syn=1,seq=n时,客户端会处于SYN_SEND状态,并等待服务端ACK。由上图得知我们的服务器TCP连接大量的处于SYN_SEND状态,因此可以判断是被黑客当成了肉机对其他服务器进行拒绝服务攻击,我们自己服务器攻击的类似则属于SYN泛洪攻击。2.3 netstat命令搭配参数查看更多网络状态信息接下来我们使用更复杂一点的netstat参数来查看详细一点的网络信息,首先是#### 命令 netstst -p #### 参数作用 -p 显示建立相关链接的程序名 ### 其他参数 -a (all)显示所有选项,默认不显示LISTEN相关 -t (tcp)仅显示tcp相关选项 -u (udp)仅显示udp相关选项 -n 拒绝显示别名,能显示数字的全部转化成数字。 -l 仅列出有在 Listen (监听) 的服務状态 -p 显示建立相关链接的程序名 -r 显示路由信息,路由表 -e 显示扩展信息,例如uid等 -s 按各个协议进行统计 -c 每隔一个固定时间,执行该netstat命令。 提示:LISTEN和LISTENING的状态只有用-a或者-l才能看到 #### -p输出字段含义 Proto # Recv-Q # Send-Q # Local # Address # Foreign Address # State # PID/Program name #我们使用netstat -p命令进行对网络连接所处程序的查询,发现无法正确的查看,因此我们可以断定攻击者的水平还算是可以的,并且我们通过这些简单的命令很难查出背后的原因了。发现TCP的连接数如此之多,而且状态多为正在打开并未连接的TCP报文2.4 发现异常TCP连接下一步排查当前服务器的TCP已连接情况:22端口的都是我在本机使用三方工具进行对服务器的远程连接,22端口和我本机的公网IP,因此这一部分TCP连接不需要质疑,但是唯独有几个不知名端口号和不知名IP的连接,因此我们可以断定这些连接多半为黑客攻击时的异常连接,并且隐藏了连接的进程。因为只有一个异常连接能够看到进程名,于是我们顺藤摸瓜找到该程序的位置该程序的位置在/tmp目录下,但是这个服务器只有我一个人使用,并未下载和添加该程序,因此该程序来源不明,对于该程序的介绍(来自GitHub):https://github.com/tmate-io/tmatetmate 的意思是 teammates,它是 tmux 的一个分支,并且使用相同的配置信息(例如快捷键配置,配色方案等)。它是一个终端多路复用器,同时具有即时分享终端的能力。它允许在单个屏幕中创建并操控多个终端,同时这些终端还能与其他同事分享。你可以分离会话,让作业在后台运行,然后在想要查看状态时重新连接会话。tmate 提供了一个即时配对的方案,让你可以与一个或多个队友共享一个终端。在屏幕的底部有一个状态栏,显示了当前会话的一些诸如 ssh 命令之类的共享信息。因此我们大致可以断定黑客就是通过tmate来对我们的服务器进行控制,让服务器出去SYN泛红状态并启动了挖矿程序进行挖矿。3 偶遇大神指点由于服务器的用途有限,并且针对服务器异常情况的应对经验也有限,因此先在微信的一个技术群中进行询问,以下是处理过程。3.1 ps命令查找恶意进程并终止什么都没说,一位很有经验的老前辈上来就让我执行这几部操作ps -ef|egrep "mass|scan" 停止相关进程找到对应的文件,删除虽然不知道为什么,但是还是照做了,当停止进程的时候,发现网络和内存的情况一下子就好了3.2 删除异常用户和文件按照前辈说的,再将黑客创建的用户和用户目录都删除掉还有异常的脚本也都删除掉3.3 缘由探究在那位老前辈的指引下,服务器的网络和内存情况已经得到修复,但是CPU占用情况仍然没有缓解,而且多半是被挖矿程序占用,因此很不容易被发现。在操作完成后,向老前辈请教本次异常出现的原因,老前辈直接就说肯定是端口开太多了,我回头一想,当初确实是为了方便做实验,开放了很多端口,因此被黑客利用,对服务器发起了攻击。4 回顾总结对TCP连接过程比较熟悉的同学应该都知道,TCP的SYN泛洪攻击以及DOS、DDOS攻击等都是基于TCP三次握手来进行的,因此很难完全避免,我们要做的就是在公网服务器上尽量少放开不常用的端口,以免遭受黑客攻击。还有就是加深自己的计算机网络知识,遇到问题要知道是什么原因,并且尝试着去解决。当然因为有挖矿程序很难清理,最后还是备份数据,重装了服务器的操作系统来解决的,但是内存和网络的异常得以被修复就是很值得学习和记录的,继续加油~
1 概念官网:https://gin-gonic.com/zh-cn/What is Gin?Gin is a web framework written in Golang.It features a martini-like API with much better performance, up to 40 times faster.If you need performance and good productivity, you will love Gin.翻译:Gin是一个用Golang写的网络框架。它有一个马提尼式的API,性能更好,速度快40倍。如果您需要性能和良好的生产力,您会喜欢Gin。特点:快速:基于基树路由,内存占用小。没有反射。可预测的API的性能。中间件支持:传入的HTTP请求可以由中间件链和最终操作处理。例如:Logger, Authorization, GZIP,最后在DB中发布消息。轻松捕获故障:Gin可以捕捉到HTTP请求期间发生的恐慌并恢复它。 这样,您的服务器将始终可用。 也可以将这种恐慌报告给哨兵!JSON验证:Gin可以解析和验证请求的JSON,例如检查需要的值是否存在。路线分组:更好地组织你的路线。 授权需要和不需要,不同的API版本。 此外,组可以无限制地嵌套而不会降低性能。错误管理:Gin提供了一种方便的方法来收集HTTP请求期间发生的所有错误。最终,中间件可以将它们写入日志文件、数据库并通过网络发送。内置渲染:Gin为JSON、XML和HTML渲染提供了一个易于使用的API。可扩展的:创建新的中间件非常简单,只需查看示例代码即可。2 安装go get -u github.com/gin-gonic/gin3 初步使用3.1 HelloWorldpackage main import "github.com/gin-gonic/gin" func main() { r := gin.Default() r.GET("/Hello", func(c *gin.Context) { c.JSON(200, gin.H{ "message": "HelloWorld", }) }) r.Run("127.0.0.1:8899") // 监听并在 0.0.0.0:8899 上启动服务 }测试:curl http://127.0.0.1:8899/hello {"message": "HelloWorld"}3.2 获取请求参数(1)http://127.0.0.1:8899?name=zs型参数r.GET("/name", func(c *gin.Context) { query := c.Query("name") //等价于:c.Request.URL.Query().Get("name") c.JSON(200, "Hello "+query) })测试:curl http://127.0.0.1:8899/name?name=zs "Hello zs"(2)http://127.0.0.1:8899/name/{zs}型参数r.GET("/name/:name", func(c *gin.Context) { name := c.Param("name") c.JSON(200, "Hello "+name) })测试:curl http://127.0.0.1:8899/name/zs "Hello zs"3.3 获取请求体(1)一般获取请求体结构体:type Student struct { Name string `json:"name"` Age int `json:"age"` }方法:r.POST("/stu", func(c *gin.Context) { body := c.Request.Body bytes, err := ioutil.ReadAll(body) stu := Student{} json.Unmarshal(bytes, &stu) if err != nil { panic(err) } c.JSON(200, stu) })测试:(2)获取请求体与结构体绑定r.POST("/stu", func(c *gin.Context) { stu := Student{} // c.ShouldBindJSON 使用了 c.Request.Body,不可重用。 c.ShouldBindJSON(&stu) c.JSON(200, stu) })测试:3.4 路由组路由组就好比一层配置,增加了一个层级,以便于区分不同分组的URL,例如下面的代码中,如果要请求FindAppInfoById方法,路径就是/appInfo/save,完整路径就是http://127.0.0.1:8899/appInfo/savefunc main() { r := gin.Default() //路由组 appInfoGroup := r.Group("/appInfo") { appInfoGroup.POST("/save", controller.SaveAppInfo) appInfoGroup.GET("/find/:id", controller.FindAppInfoById) appInfoGroup.GET("/findAll", controller.FindAllAppInfo) } r.Run("127.0.0.1:" + utils.IntToString(port)) } // FindAppInfoById 自定义HTTP方法 func FindAppInfoById(c *gin.Context) { id := c.Param("id") ...... c.JSON(200, id) } func SaveAppInfo(c *gin.Context) { ...... } func FindAllAppInfo(c *gin.Context) { ...... }4 Gin实现拦截器func main() { r := gin.Default() //增加拦截器 r.Use(interceptor.HttpInterceptor()) ...... r.Run("127.0.0.1:" + utils.IntToString(port)) }拦截器实现:// HttpInterceptor 自定义中间件 func HttpInterceptor() gin.HandlerFunc { return func(c *gin.Context) { t := time.Now() // 设置 example 变量 c.Set("example", "12345") // 请求前 log.Print("--------------拦截器-------------") //定义错误,终止并返回该JSON //c.AbortWithStatusJSON(500, "error") requestURI := c.Request.RequestURI fmt.Println(requestURI) //通过请求 c.Next() // 请求后 latency := time.Since(t) log.Print(latency) // 获取发送的 status status := c.Writer.Status() log.Println(status) } }~
1.string(字符串) Go中的字符串是一个字节的切片。可以通过将其内容封装在“”中来创建字符串。Go中的字符串是Unicode兼容的,并且是UTF-8编码的。1.1 基本使用func StringTest() { str1 := "Hello" //声明方式1 str2 := string("World") //声明方式2 fmt.Println(str1) fmt.Println(str2) }运行:1.2 strings包中操作string的函数func StringTest() { str1 := "Hello" str2 := string("World") /** strings的常见函数 */ index := strings.Index(str1, "e") //查找“e”在str1中的位置 contains := strings.Compare(str1, str2) //Compare返回一个按字典顺序比较两个字符串的整数。 如果a==b,则结果为0,如果a < b则为-1,如果a > b则为+1。 fold := strings.EqualFold(str1, str2) //类似于Java里的equals() contain := strings.Contains(str1, "llo") //查找“llo”是否在str1中 fmt.Println(index) fmt.Println(contains) fmt.Println(fold) fmt.Println(contain) }运行结果:1.3 strconv包中与string相关函数访问strconv包,可以实现string和其他数值类型之间的转换。func StrconvTest() { /** 参数1 数字的字符串形式 参数2 数字字符串的进制 比如二进制 八进制 十进制 十六进制 参数3 返回结果的bit大小 也就是int8 int16 int32 int64 */ parseInt, err := strconv.ParseInt("456", 16, 32) if err == nil { fmt.Println(parseInt) } /** 参数1 数字的字符串形式 (相当于调用了ParseInt(s, 10, 0)) */ atoi, err := strconv.Atoi("123") if err == nil { fmt.Println(atoi) } /** 参数1 放入true或false */ parseBool, err := strconv.ParseBool("false") if err == nil { fmt.Println(parseBool) } }运行结果:2.slice(切片) Go 语言切片是对数组的抽象。 Go 数组的长度不可改变,在特定场景中这样的集合就不太适用,Go 中提供了一种灵活,功能强悍的内置类型切片("动态数组"),与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。 切片是一种方便、灵活且强大的包装器。切片本身没有任何数据。它们只是对现有数组的引用。 切片与数组相比,不需要设定长度,在[]中不用设定值,相对来说比较自由从概念上面来说slice像一个结构体,这个结构体包含了三个元素: 指针,指向数组中slice指定的开始位置长度,即slice的长度最大长度,也就是slice开始位置到数组的最后位置的长度2.1 定义//声明方式一 var ints1 []int //声明方式二 ints2 := make([]int, 0)2.2 常见操作func SliceTest() { //声明方式一 var ints1 []int //声明方式二 ints2 := make([]int, 0) //添加元素 ints1 = append(ints1, 2, 2, 3, 4, 5, 8) ints2 = append(ints2, 9, 4, 2, 3, 5, 6) //根据下标查找元素 i := ints1[2] fmt.Println(i) //遍历 for i := 0; i < len(ints2); i++ { fmt.Print(ints2[i], " ") } fmt.Println() //删除元素 fmt.Println(ints1) ints1 = append(ints1[:1], ints1[2:]...) fmt.Println(ints1) }运行结果:2.3 常见函数len() :获取长度cap() :测量切片最长可以达到多少append() :向slice里面追加一个或者多个元素,然后返回一个和slice一样类型的slicecopy():copy从源slice的src中复制元素到目标dst,并且返回复制的元素的个数3.map(字典)map是Go中的内置类型,它将一个值与一个键关联起来。可以使用相应的键检索值3.1 定义/* 声明变量,默认 map 是 nil */ var map_variable(Map名称) map[key_data_type(键类型)]value_data_type(值类型) /* 使用 make 函数创建 */ map_variable = make(map[key_data_type]value_data_type) rating := map[string]float32 {"C":5, "Go":4.5, "Python":4.5, "C++":2 } //如果不初始化 map,那么就会创建一个 nil map。nil map 不能用来存放键值对3.2 常见操作func MapTest() { var m1 map[string]string /*创建集合 */ m1 = make(map[string]string) /* map插入key - value对,各个国家对应的首都 */ m1["France"] = "巴黎" m1["Italy"] = "罗马" m1["Japan"] = "东京" m1["India "] = "新德里" /*使用键输出地图值 */ for country := range m1 { fmt.Println(country, "首都是", m1[country]) } /*查看元素在集合中是否存在 */ capital, ok := m1["American"] /*如果确定是真实的,则存在,否则不存在 */ fmt.Println(capital) fmt.Println(ok) if ok { fmt.Println("American 的首都是", capital) } else { fmt.Println("American 的首都不存在") } }运行结果:3.3 常见函数delete(map, key) 函数:用于删除集合的元素, 参数为 map 和其对应的 key。删除函数不返回任何值先到这里就ok啦,因为博主也是go语言初学者,高深的东西以后再分享吧!
整体架构:下载链接:Doubbo:https://github.com/apache/dubboZookeeper:https://downloads.apache.org/zookeeper/Doubbo-admin:https://github.com/apache/dubbo-admin1 首先安装Zookpeer[root@bogon tmp]# ls apache-zookeeper-3.7.0-bin.tar.gz jdk1.8.0_141 jdk8.tar.gz [root@bogon tmp]# tar -zxvf apache-zookeeper-3.7.0-bin.tar.gz [root@bogon tmp]# tar -zxvf jdk8.tar.gz [root@bogon tmp]# mv jdk1.8.0_141/ /usr/local/ [root@bogon tmp]# mv apache-zookeeper-3.7.0-bin/ /usr/local/zookpeer [root@bogon tmp]# cd /usr/local/ [root@bogon local]# ls bin etc games include jdk1.8.0_141 lib lib64 libexec sbin share src zookeeper [root@bogon local]# vim /etc/profile # --------写入文件------------ export JAVA_HOME=/usr/local/jdk1.8.0_141 export JER_HOME=${JAVA_HOME}/jre export CLASSPATH=.:${JAVA_HOME}/lib:${JRE_HOME}/lib:$CLASSPATH export JAVA_PATH=${JAVA_HOME}/bin:${JRE_HOME}/bin export PATH=$PATH:${JAVA_PATH} # --------写入文件完成------------ [root@bogon local]# source /etc/profile [root@bogon local]# java -version java version "1.8.0_141" Java(TM) SE Runtime Environment (build 1.8.0_141-b15) Java HotSpot(TM) 64-Bit Server VM (build 25.141-b15, mixed mode) [root@bogon local]# cd zookeeper/ [root@bogon conf]# cp zoo_sample.cfg zoo.cfg [root@bogon conf]# cd ../bin [root@bogon bin]# ./zkServer.sh start [root@bogon bin]# ./zkServer.sh start ZooKeeper JMX enabled by default Using config: /usr/local/zookeeper/bin/../conf/zoo.cfg Starting zookeeper ... STARTED [root@bogon bin]# ./zkCli.sh Connecting to localhost:2181 ...... [zk: localhost:2181(CONNECTED) 1] ls / [zookeeper]zookpeer的启动和重启命令:[root@bogon bin]# ./zkServer.sh start //启动 [root@bogon bin]# ./zkServer.sh stop //停止 [root@bogon bin]# ./zkServer.sh status //查看当前状态 [root@bogon bin]# ./zkServer.sh restart //重启 [root@bogon bin]# ./zkServer.sh version //查看当前版本2 创建Spring Boot项目2.1 服务提供方dubbo_providerpom.xml依赖:<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.alibaba.boot</groupId> <artifactId>dubbo-spring-boot-starter</artifactId> <version>0.2.0</version> </dependency> <dependency> <groupId>org.ymx</groupId> <artifactId>doubbo_interface</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>application.properties配置文件:server.port=8081 dubbo.application.name=my_provider dubbo.registry.protocol=zookeeper dubbo.registry.address=192.168.190.129:2181 dubbo.protocol.name=dubboUserServiceImpl接口实现类:import com.alibaba.dubbo.config.annotation.Service; import org.ymx.doubbo_interface.service.UserService; /** * @desc: 接口实现类 * @author: YanMingXin * @create: 2021/8/7-9:32 **/ @Service public class UserServiceImpl implements UserService { @Override public String sayHello(String name) { return "Hello " + name; } }主启动类:@SpringBootApplication @EnableDubbo(scanBasePackages = "org.ymx.*") public class DoubboProviderApplication { public static void main(String[] args) { SpringApplication.run(DoubboProviderApplication.class, args); } }2.2 服务消费方dubbo_consumerpom.xml依赖<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.alibaba.boot</groupId> <artifactId>dubbo-spring-boot-starter</artifactId> <version>0.2.0</version> </dependency> <dependency> <groupId>org.ymx</groupId> <artifactId>doubbo_interface</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>application.properties配置文件dubbo.application.name=my_consumer dubbo.registry.protocol=zookeeper dubbo.registry.address=192.168.190.129:2181 dubbo.protocol.name=dubboUserController控制器类/** * @desc: Controller控制类 * @author: YanMingXin * @create: 2021/8/7-10:08 **/ @RestController public class UserController { @Reference private UserService userService; @RequestMapping("/hello/{name}") public String hello(@PathVariable("name") String name) { return userService.sayHello(name); } }主启动类:@SpringBootApplication @EnableDubbo(scanBasePackages = "org.ymx.*") public class DoubboConsumerApplication { public static void main(String[] args) { SpringApplication.run(DoubboConsumerApplication.class, args); } }2.3 公共接口dubbo_interfacepom.xml依赖<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>application.properties配置文件空UserService接口类/** * @desc: 公共接口 * @author: YanMingXin * @create: 2021/8/7-10:07 **/ public interface UserService { String sayHello(String name); }主启动类:@SpringBootApplication public class DoubboInterfaceApplication { public static void main(String[] args) { SpringApplication.run(DoubboInterfaceApplication.class, args); } }@SpringBootApplication public class DoubboInterfaceApplication { public static void main(String[] args) { SpringApplication.run(DoubboInterfaceApplication.class, args); } }2.4 启动后观察Zookpeer并测试doubbo_interface不需要启动,只需要install就可以了[zk: localhost:2181(CONNECTED) 14] ls / [dubbo, zookeeper] [zk: localhost:2181(CONNECTED) 15] ls /dubbo [org.ymx.doubbo_consumer.service.UserService, org.ymx.doubbo_provider.service.UserService]测试:3 dubbo管理界面放到tomcat中修改配置文件,在修改内容dubbo.registry.address=zookeeper://192.168.190.129:2181 # 注册中心地址 dubbo.admin.root.password=root # root账户的密码 dubbo.admin.guest.password=guest # guest账户的密码启动访问:http://localhost:8080/dubbo-admin/
1 使用@Scheduled注解举例:/** * @desc: 基于注解的Spring定时任务 * @author: YanMingXin * @create: 2021/9/28-16:25 **/ @Configuration @EnableScheduling public class SpringScheduleTask { /** * cron方式 */ @Scheduled(cron = "*/5 * * * * ?") private void configureTasks1() { System.err.println("定时任务1......"); } /** * fixedRate方式 */ @Scheduled(fixedRate = 1) private void configureTasks2() { System.out.println("定时任务2......"); } }注意:fixedRate和cron不可以同时使用1.1 cron方式字段含义:*:代表全部可能的值-:指定范围例如1-4,:表示或 例如在分钟里,"5,15"表示5分钟和20分钟触发W:只能用在月份中,表示最接近指定天的工作日:用在星期中表示这个月的第几个周几,例如5#3表示这个月的第3个周五/:表示增量 例如在分钟里,"3/15"表示从3分钟开始,没隔15分钟执行一次?:表示没有具体的值L:表示last,在星期中表示周日,月份中表示最后一天,6L表示这个月倒数第6天,FRIL表示这个月的最后一个星期五示例:/5 * ? 每隔5秒执行一次0 /1 ? 每隔1分钟执行一次0 0 5-15 ? 每天5-15点整点触发0 0/3 * ? 每三分钟触发一次0 ? 每1分钟触发一次0 0 * ? 每天每1小时触发一次0 0 10 ? 每天10点触发一次0 14 * ? 在每天下午2点到下午2:59期间的每1分钟触发1.2 fixedRate方式配合timeUnit参数,例如@Scheduled(fixedRate = 1,timeUnit = TimeUnit.SECONDS) private void configureTasks2() { System.out.println("定时任务2......"); }TimeUnit的枚举类型主要有DAYS :天HOURS :小时MINUTES :分钟SECONDS :秒钟MILLISECONDS :毫秒MICROSECONDS :微秒NANOSECONDS:纳秒1.3 @Scheduled注解源码@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Repeatable(Schedules.class) public @interface Scheduled { String CRON_DISABLED = ScheduledTaskRegistrar.CRON_DISABLED; String cron() default ""; //配置cron参数 String zone() default ""; //配置时区 long fixedDelay() default -1; String fixedDelayString() default ""; long fixedRate() default -1; String fixedRateString() default ""; long initialDelay() default -1; String initialDelayString() default ""; // TimeUnit timeUnit() default TimeUnit.MILLISECONDS; //配置时间单位,默认毫秒 }2 实现SchedulingConfigurer接口2.1 代码实现/** * @desc: 实现SchedulingConfigurer接口来实现定时任务 * @author: YanMingXin * @create: 2021/9/28-17:12 **/ @Configuration @EnableScheduling public class SpringScheduleTaskImpl implements SchedulingConfigurer { /** * 实现自定义任务 * * @param taskRegistrar */ @SuppressWarnings("all") @Override public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { taskRegistrar.addTriggerTask(() -> { System.out.println("定时任务3......"); }, triggerContext -> { return new CronTrigger("0/1 * * * * ?").nextExecutionTime(triggerContext); }); taskRegistrar.addFixedRateTask(() -> { System.out.println("定时任务4......"); }, 1000 ); taskRegistrar.addCronTask(() -> { System.out.println("定时任务5......"); }, "0/1 * * * * ?" ); } }2.2 配置文件方式配置文件:server.port=0 task.cron=0/2 * * * * ?代码:/** * @desc: 实现SchedulingConfigurer接口来实现定时任务 * @author: YanMingXin * @create: 2021/9/28-17:12 **/ @Configuration @EnableScheduling public class SpringScheduleTaskImplByFile implements SchedulingConfigurer { @Value("${task.cron}") private String cron; /** * 实现自定义任务 * * @param taskRegistrar */ @SuppressWarnings("all") @Override public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { taskRegistrar.addTriggerTask(() -> { System.out.println("定时任务7......"); }, triggerContext -> { return new CronTrigger(cron).nextExecutionTime(triggerContext); }); } }2.3 数据库方式/** * @desc: * @author: YanMingXin * @create: 2021/9/28-17:44 **/ @Configuration @EnableScheduling public class SpringScheduleTaskImplByDB implements SchedulingConfigurer { @TableName("tb_task") @Data class MyTask { @TableId(type = IdType.AUTO,value = "id") private Integer id; @TableField(value = "cron") private String cron; } @Mapper interface TaskMapper extends BaseMapper<MyTask> { } @Autowired private TaskMapper taskMapper; /** * 实现自定义任务 * * @param taskRegistrar */ @SuppressWarnings("all") @Override public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { taskRegistrar.addTriggerTask(() -> { System.out.println("定时任务10......"); }, triggerContext -> { MyTask myTask = taskMapper.selectById(1); return new CronTrigger(myTask.getCron()).nextExecutionTime(triggerContext); }); } }3 对比基于注解形式的一般都是静态的定时任务,就是注解中的内容是固定的并且只有一个定时任务,而实现接口的可以是动态的,可以根据配置文件和数据库进行切换。
1 概述为什么要进行连接查询?因为不同表之间的数据具有不同的用途和字段,连接查询可以将我们需要用到的两个表的不同字段进行关联,从而找到我们有用的信息。连接操作给用户带来很大的灵活性,他们可以在任何时候增加新的数据类型。为不同实体创建新的表,然后通过连接进行查询。2 连接类型3 各种连接详解和示例首先我们新建两张表,并设置好相应的字段和数据建表学生表(student)CREATE TABLE `student` ( `id` int(11) NOT NULL, `no` varchar(45) DEFAULT NULL, `name` varchar(45) DEFAULT NULL, `age` varchar(45) DEFAULT NULL, `school` varchar(45) DEFAULT NULL, `address` varchar(45) DEFAULT NULL, `school_address` varchar(45) DEFAULT NULL, `sex` varchar(45) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;用户表(user)CREATE TABLE `user` ( `id` varchar(99) NOT NULL DEFAULT '', `name` varchar(45) DEFAULT NULL, `age` varchar(45) DEFAULT NULL, `address` varchar(45) DEFAULT NULL, `sex` varchar(45) DEFAULT NULL, `login_name` varchar(45) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;数据LOCK TABLES `student` WRITE; INSERT INTO `student` VALUES (1,'20210101','zs','67','ZhengDa','Beijing','ZhengZhou','man'),(2,'20210102','ls','12','ZhengDa','TianJing','ZhengZhou','man'),(3,'20210103','ww','12','BeiDa','TianJing','Beijing','woman'),(4,'20210104','ww','12','BeiDa','TianJing','Beijing','woman'),(5,'20210105','xm','15','TianDa','TianJing','TianJing','woman'),(6,'20210106','fs','16','HeDa','TianJing','Shijiazhuang','man'),(7,'20210107','wv','12','YanDa','TianJing','Qinhuangdao','woman'),(8,'20210108','yd','42','BeiDa','TianJing','Beijing','man'),(9,'20210109','xd','32','BeiDa','TianJing','Beijing','woman'),(10,'20210110','hh','15','BeiDa','TianJing','Beijing','woman'); UNLOCK TABLES; LOCK TABLES `user` WRITE; INSERT INTO `user` VALUES ('1','ww','12','TianJing','woman','ww123'), ('2','fs','16','TianJing','man','fs123'), ('3','yd','42','TianJing','woman','yd123'), ('4','xd','32','TianJing','woman','xd123'), ('5','hh','15','TianJing','woman','hh123'); UNLOCK TABLES;3.1 交叉连接(1)图示即笛卡尔积:所有情况的组合,不推荐使用(2)SQL语句和关键字SQL:select * from user,student;关键字:无(3)示例3.2 内连接(1)图示多张表通过相同字段进行匹配,只显示匹配成功的数据(2)SQL语句和关键字SQL:select * from user,student where user.name=student.name; 或 select * from user inner join student on user.name=student.name关键字:inner join(3)示例3.3 外连接3.3.1 左外连接(1)图示左外连接:以左表为基准(左表数据全部显示),去匹配右表数据,如果匹配成功 则全部显示;匹配不成功,显示部分(无数据部分 用NULL填充)(2)SQL语句和关键字SQL:不包含交集: select * from user left outer join student on user.name=student.name; 包含交集: select * from user left outer join student on user.name=student.name union select * from user,student where user.name=student.name;关键字:left outer、union(3)示例3.3.2 右外连接(1)图示右外连接:以右表为基准(右表数据全部显示),去匹配左表数据,如果匹配成功 则全部显示;匹配不成功,显示部分(无数据部分 用NULL填充)(2)SQL语句和关键字SQL:不包含交集: select * from user right outer join student on user.name=student.name; 包含交集: select * from user,student where user.name=student.name union select * from user right outer join student on user.name=student.name;关键字:right outer join、union(3)示例3.3.3 全外连接(1)图示全外连接 = 左外连接+右外连接+去重(2)SQL语句和关键字SQL:select * from student,user full outer join student on student.name=user.name;注意:MySQL是不支持全外的连接的,这里给出的写法适合Oracle和DB2。但是可以通过左外和右外求合集来获取全外连接的查询结果。左外和右外的合集:select * from user right outer join student on user.name=student.name union select * from user left outer join student on user.name=student.name;关键字:right outer join、left outer join、union(3)示例3.4 自连接(1)图示将一张表 通过别名 “视为”不同的表(2)SQL语句和关键字SQL:select * from student stu,student sch where stu.address=sch.school_address;关键字:无(3)示例4 小总结在各种连接中还可以被分为等值连接和不等值连接,但是一般情况下只使用等值连接select语句尽量不要使用select * ...,以上的演示只是为了方便
1 概述1.1 子网划分当我们对一个网络进行子网划分时,基本上就是将它分成小的网络。比如,当一组IP地址指定给一个公司时,公司可能将该网络“分割成”小的网络,每个部门一个。这样,技术部门和管理部门都可以有属于它们的小网络。通过划分子网,我们可以按照我们的需要将网络分割成小网络。这样也有助于降低流量和隐藏网络的复杂性子网划分优点:1.减少网络流量 2.提高网络性能 3.简化管理 4.易于扩大地理范围1.2 为什么要划分子网节约IP地址,避免浪费。限定广播的传播。保证网络的安全。有助于覆盖大型地理区域。1.3 超网汇聚超网(supernetting)是与子网类似的概念–IP地址根据子网掩码被分为独立的网络地址和主机地址。但是,与子网把大网络分成若干小网络相反,它是把一些小网络组合成一个大网络–超网。1.4 为什么要超网汇聚可以使用这个聚合起来的IP地址的共同地址前缀作为其网络号。超网创建用来解决路由列表超出现有软件和管理人力的问题以及提供B类网络地址空间耗尽的解决办法。解决路由表的内容冗余问题,使用路由聚合能够缩小路由表的规模2 子网划分及例题分析3 超网汇聚及例题分析4 小总结子网划分主要是利用网络位的增加,将原来的网络划分成许多小的网络,数量一般是2的n次方个超网汇聚则是将许多小网络汇聚成一个大网络,主要是利用寻找多个小网络直接的公共网络位
1 缓存的作用和一般使用流程作用:减少服务器压力,增加请求承载量,快速响应请求等等。一般流程:2 缓存穿透概念:指缓存和数据库中都没有用户想要查询到的数据,并且不断进行请求,造成数据库承载部分压力。解决方法:接口校验:防止非法请求将缓存中key-value的value设置为null3 缓存击穿概念:指同一时间内大量缓存同时失效,导致请求全部转向数据库。解决方法:设置热点数据永不过期加锁,设置数据库为延时访问4 缓存雪崩概念:指同一时间内大量不同请求的缓存同时失效,导致请求全部转向数据库,引起数据库宕机。解决方法:缓存数据的过期时间设置随机将热点数据均匀分布在不同缓存数据库中设置热点数据永远不过期
1 概述Redis为什么能支持每秒钟十万级的高并发?基于内存的存取方式高效的数据结构单线程,使用多路I/O复用模型,非阻塞IO......其中一个重要的原因,就是Redis中高效的数据结构,因此我们就专门的来研究下Redis的核心数据结构,Go!2 五大基本数据结构分别是String、List、Set、ZSet、MapString类型:一个String类型的value最大可以存储512MList类型:list的元素个数最多为2^32-1个,也就是4294967295个。Set类型:元素个数最多为2^32-1个,也就是4294967295个。Hash类型:键值对个数最多为2^32-1个,也就是4294967295个。Sorted set类型:跟Sets类型相似,但是有序。2.1 String(1)使用127.0.0.1:6379> set str hello OK 127.0.0.1:6379> get str "hello"(2)原理(3)源码typedef char *sds; /* Sdshdr5从未被使用过,我们只是直接访问标记字节,在这里是为了记录类型5 SDS字符串的布局。*/ struct __attribute__ ((__packed__)) sdshdr5 { unsigned char flags; /* 3LSB的类型,5MSB的字符串长度 */ char buf[]; }; struct __attribute__ ((__packed__)) sdshdr8 { uint8_t len; uint8_t alloc; /* 排除头和空结束符 */ unsigned char flags; char buf[]; }; struct __attribute__ ((__packed__)) sdshdr16 { uint16_t len; uint16_t alloc; unsigned char flags; char buf[]; }; struct __attribute__ ((__packed__)) sdshdr32 { uint32_t len; uint32_t alloc; unsigned char flags; char buf[]; }; struct __attribute__ ((__packed__)) sdshdr64 { uint64_t len; uint64_t alloc; unsigned char flags; char buf[]; };2.2 List(1)使用127.0.0.1:6379> lpush items a b c (integer) 3 127.0.0.1:6379> lrange items 1 10 1) "b" 2) "a" 127.0.0.1:6379> lrange items 0 10 1) "c" 2) "b" 3) "a" 127.0.0.1:6379> lpush items d (integer) 4 127.0.0.1:6379> lrange items 0 10 1) "d" 2) "c" 3) "b" 4) "a" 127.0.0.1:6379> lpop items "d" 127.0.0.1:6379> lrange items 0 10 1) "c" 2) "b" 3) "a"(2)原理(3)源码typedef struct listNode { struct listNode *prev; //头指针 struct listNode *next; //尾指针 void *value; //节点的值 } listNode; typedef struct listIter { listNode *next; int direction; } listIter; typedef struct list { listNode *head; listNode *tail; void *(*dup)(void *ptr); void (*free)(void *ptr); int (*match)(void *ptr, void *key); unsigned long len; } list;2.3 Set(1)使用127.0.0.1:6379> sadd set_items a b c c d d (integer) 4 127.0.0.1:6379> smembers set_items 1) "c" 2) "b" 3) "d" 4) "a" 127.0.0.1:6379> sadd set_items e (integer) 1 127.0.0.1:6379> smembers set_items 1) "d" 2) "a" 3) "e" 4) "b" 5) "c" 127.0.0.1:6379>(2)原理(3)源码typedef struct intset { uint32_t encoding; uint32_t length; int8_t contents[]; } intset; 2.4 ZSet(1)使用127.0.0.1:6379> zadd zset_items 1 a 2 c 3 b (integer) 3 127.0.0.1:6379> zrange zset_items 0 10 1) "a" 2) "c" 3) "b" 127.0.0.1:6379> add 2 d (error) ERR unknown command 'add' 127.0.0.1:6379> zadd zset_items 2 d (integer) 1 127.0.0.1:6379> zrange zset_items 0 10 1) "a" 2) "c" 3) "d" 4) "b"(2)原理(3)源码/* ZSET使用特殊版本的跳表 */ typedef struct zskiplistNode { sds ele; double score; struct zskiplistNode *backward; struct zskiplistLevel { // 跳表的层数 struct zskiplistNode *forward; unsigned long span; } level[]; } zskiplistNode; typedef struct zskiplist { struct zskiplistNode *header, *tail; //头指针和尾指针 unsigned long length; int level; } zskiplist; typedef struct zset { dict *dict; //哈希表 zskiplist *zsl; //跳表 } zset;2.5 Map(1)使用127.0.0.1:6379> hmset map name zs age 12 OK 127.0.0.1:6379> hgetall map 1) "name" 2) "zs" 3) "age" 4) "12" 127.0.0.1:6379> hget map name "zs" 127.0.0.1:6379> hmset map school zz OK 127.0.0.1:6379> hgetall map 1) "name" 2) "zs" 3) "age" 4) "12" 5) "school" 6) "zz" 127.0.0.1:6379> hdel map age (integer) 1 127.0.0.1:6379> hgetall map 1) "name" 2) "zs" 3) "school" 4) "zz" 127.0.0.1:6379>(2)原理(3)源码typedef struct dictEntry { void *key; union { void *val; uint64_t u64; int64_t s64; double d; } v; struct dictEntry *next; } dictEntry; typedef struct dictType { uint64_t (*hashFunction)(const void *key); void *(*keyDup)(void *privdata, const void *key); void *(*valDup)(void *privdata, const void *obj); int (*keyCompare)(void *privdata, const void *key1, const void *key2); void (*keyDestructor)(void *privdata, void *key); void (*valDestructor)(void *privdata, void *obj); } dictType; /* 这是哈希表结构。 当我们实现增量重哈希时,每个字典都有两个这样的表,从旧表到新表。 */ typedef struct dictht { dictEntry **table; unsigned long size; unsigned long sizemask; unsigned long used; } dictht; typedef struct dict { dictType *type; void *privdata; dictht ht[2]; long rehashidx; unsigned long iterators; /* 当前运行的迭代器数量 */ } dict;3 重点讲下跳表(SkipList)这个数据结构3.1 图解3.2 查找过程
1 RestTemplate简介在java代码里想要进行restful web client服务,一般使用Apache的HttpClient。不过此种方法使用起来太过繁琐。Spring Boot提供了一种简单便捷的内置模板类来进行操作,这就是RestTemplate。2 RestTemplate基本使用2.1 依赖:Spring Boot的web starter已经内置了RestTemplate的Bean,我们主需要将它引入到我们的Spring Context中,再进行下简单的配置就可以直接使用了。<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.76</version> </dependency>2.2 Web服务端代码:这部分代码作为Http请求的服务端:/** * @desc: HTTP服务端 * @author: YanMingXin * @create: 2022/1/15-18:08 **/ @RestController public class RestControllerDemo { /** * 普通Get * * @param name * @return */ @GetMapping("/get") private String getMethod(@RequestParam("name") String name) { System.out.println("getMethod : name=" + name); return name; } /** * Restful Get * * @param name * @return */ @GetMapping("/getName/{name}") private String getRestName(@PathVariable("name") String name) { System.out.println("getRestName : name=" + name); return name; } /** * post * * @param name * @return */ @PostMapping("/post") private String postMethod(@RequestParam("name") String name) { System.out.println("postMethod : name=" + name); return name; } /** * post json * * @param stu * @return */ @PostMapping("/postBody") public String postBodyMethod(@RequestBody String stu) { Student student = JSONObject.parseObject(stu, Student.class); System.out.println("postBodyMethod : student=" + student); return student.toString(); } /** * delete * * @param name * @return */ @DeleteMapping("/delete") public String deleteMethod(@RequestParam("name") String name) { System.out.println("deleteMethod : name=" + name); return name; } /** * put * * @param name * @return */ @PutMapping("/put") public String putMethod(@RequestParam("name") String name) { System.out.println("putMethod : name=" + name); return name; } }2.3 RestTemplate代码:配置:/** * @desc: RestTemplate配置 * @author: YanMingXin * @create: 2022/1/15-17:34 **/ @Configuration public class RestTemplateConfig { @Bean public RestTemplate restTemplate(ClientHttpRequestFactory factory) { return new RestTemplate(factory); } @Bean public ClientHttpRequestFactory simpleClientHttpRequestFactory() { SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory(); factory.setReadTimeout(5000); factory.setConnectTimeout(15000); return factory; } }使用测试:@SpringBootTest class DemoApplicationTests { @Resource private RestTemplate restTemplate; @Test void getTest() { String str = restTemplate.getForObject("http://localhost:8888/get?name=zs", String.class); System.out.println(str); } @Test void getRestTest() { String name = "ls"; String str = restTemplate.getForObject("http://localhost:8888/getName/" + name, String.class); System.out.println(str); } @Test void postTest() { LinkedMultiValueMap<String, String> map = new LinkedMultiValueMap<>(); map.set("name", "zs"); String str = restTemplate.postForObject("http://localhost:8888/post", map, String.class); System.out.println(str); } @Test void postBodyTest() { HttpHeaders headers = new HttpHeaders(); MediaType type = MediaType.parseMediaType("application/json; charset=UTF-8"); headers.setContentType(type); headers.add("Accept", MediaType.APPLICATION_JSON.toString()); HashMap<String, Object> map = new HashMap<>(); map.put("name", "zs"); map.put("age", 23); String stu = JSON.toJSONString(map); HttpEntity<String> formEntity = new HttpEntity<String>(stu, headers); String str = restTemplate.postForObject("http://localhost:8888/postBody", formEntity, String.class); System.out.println(str); } @Test void putTest() { restTemplate.put("http://localhost:8888/put?name=zs", null); } @Test void deleteTest() { restTemplate.delete("http://localhost:8888/delete?name=zs"); } }3 其他API使用exchange():在URL上执行特定的HTTP方法,返回包含对象的ResponseEntity,这个对象是从响应体中映射得到的execute(): 在URL上执行特定的HTTP方法,返回一个从响应体映射得到的对象getForEntity(): 发送一个GET请求,返回的ResponseEntity包含了响应体所映射成的对象getForObject() :发送一个GET请求,返回的请求体将映射为一个对象postForEntity():POST 数据到一个URL,返回包含一个对象的ResponseEntity,这个对象是从响应体中映射得到的postForObject() :POST 数据到一个URL,返回根据响应体匹配形成的对象4 注意点RestTemplate需要手动的注入到我们自己的Spring Context中才能进行使用,不可以直接在一个业务类中注入使用。使用POST形式的JSON格式进行请求时,需要配置http报文的header请求头中的报文格式。
1 事务的四大特性ACIDACID,是指数据库管理系统(DBMS)在写入或更新的过程中,为保证事务(transaction)是正确可靠的,所必须具备的四个特性:原子性(atomicity)、一致性(consistency)、隔离性(isolation)、持久性(durability)。Atomicity(原子性):一个事务中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被恢复到事务开始前的状态,就像这个事务从来没有执行过一样。Consistency(一致性):在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作。Isolation(隔离性):数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交(Read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(Serializable)。Durability(持久性):事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。2 事务的四种隔离级别查看数据库的隔离级别show variables like '%isolation%'设置数据库的隔离级别set session transaction isolation level [隔离级别];隔离级别可选项:read uncommittedread committedrepeatable readserializable2.1 读未提交(read-uncommitted)在该隔离级别,所有的事务都可以看到其他事务没有提交的执行结果。(生产中不使用)2.2 读提交(read-committed)该隔离级别,一个事务只能看到其他的已经提交的事务所做的改变。这种隔离级别也支持不可重复读,即同一个 select 可能得到不同的结果。2.3 可重复读(repeatable-read)这是 MySQL 默认的隔离级别,它确保同一个事务在并发读取数据时,会看到同样的数据行。2.4 串行化(serializable)这是事务的最高隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决了幻读的问题。它在每个读的数据行上面加上共享锁,。但是可能会导致超时和锁竞争。(生产环境基本不使用)3 事务的并发问题(脏读、幻读、不可重复读)3.1 脏读A事务读取B事务尚未提交的数据,此时如果B事务发生错误并执行回滚操作,那么A事务读取到的数据就是脏数据。一则故事描述让你理解什么是脏读:3.2 幻读相同的条件查询一些数据,然后其他事务【新增】或者是【删除】了该条件的数据,然后导致读取的结果不一样多。例如事务A第一次查询时student数量为2,此时事务B新增一个student,事务A再次查询发现莫名其妙多了一个。理解什么是幻读:3.3 不可重复读事务 A 多次读取同一数据,事务 B 在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果不一致。理解什么是不可重复读:4 隔离级别与并发问题的关系事务隔离级别脏读不可重复读幻读读未提交(read-uncommitted)是是是读提交(read-committed)否是是可重复读(repeatable-read)否否是串行化(serializable)否否否
为什么需要拦截器?在我们开发的Web系统中,资源可分为大致三类:公开资源、个人资源和隐私资源,比如公开资源有任何人都能看新闻、视频、文章等等,个人资源就是指系统用户的个人信息等等,隐私资源可以表示系统的后台管理、用户管理等等。因此我们需要进行系统用户访问资源的认证规则,而Spring自带的拦截器处理器就可以很好地完成我们的需求,下面开始今天的表演!1 场景需求分析我们假设有三类资源,分别为公开资源、个人资源和隐私资源,请求路径分别对应为:公开资源:/user/getUserPublicInfo个人资源:/user/getUserInfo隐私资源:/user/getUserPrivateInfo此外我们还需要一个别拦截后的跳转路径,防止用户在自己被拦截后还浑然不知未授权下跳转路径:/user/noAuth下面我们罗列下访问这些资源都有什么要求资源名称资源路径要求公开资源/user/getUserPublicInfo无个人资源/user/getUserInfo带上请求参数token隐私资源/user/getUserPrivateInfo带上请求参数token加请求头ymx-name2 项目架构2.1 依赖和配置文件我们新建Spring Boot项目,引入依赖,修改配置文件<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency>配置文件:server.port=90002.2 项目结构2.3 类的分析3 具体代码实现3.1 项目代码实现UserController.java /** * @desc: UserController * @author: YanMingXin * @create: 2021/9/27-17:12 **/ @RestController @RequestMapping("/user") public class UserController { @Data @Accessors(chain = true) class User { private String name; private Integer age; } /** * 个人资源 * * @return */ @RequestMapping("/getUserInfo") public User getUserInfo() { return new User().setName("User:zs").setAge(12); } /** * 隐私资源 * * @return */ @RequestMapping("/getUserPrivateInfo") public User getUserPrivateInfo() { return new User().setName("PrivateUser:ls").setAge(2); } /** * 公开资源 * * @return */ @RequestMapping("/getUserPublicInfo") public User getUserPublicInfo() { return new User().setName("PublicUser:ww").setAge(15); } /** * 未授权跳转页面 * * @return */ @RequestMapping("/noAuth") public String noAuth() { return "You were intercepted~"; } }WebInterceptorHandler.java/** * @desc: 自定义拦截器 * @author: YanMingXin * @create: 2021/9/27-17:10 **/ @Component public class WebInterceptorHandler implements HandlerInterceptor { private final String USER_TOKEN_NAME = "token"; private final String USER_TOKEN_VALUE = "ymx"; private final String AUTH_HEADER = "ymx-name"; private final String PRIVATE_URL = "/user/getUserPrivateInfo"; private final String REDIRECT_URL = "/user/noAuth"; /** * 预处理回调方法 * * @param request * @param response * @param handler * @return 返回true为继续处理,false为中断处理 * @throws Exception */ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token = request.getParameter(USER_TOKEN_NAME); String headerValue = request.getHeader(AUTH_HEADER); String requestURI = request.getRequestURI(); //用户个人隐私 if (PRIVATE_URL.equals(requestURI)) { if (USER_TOKEN_VALUE.equals(headerValue) && USER_TOKEN_VALUE.equals(token)) { return true; } else { //拦截后跳转路径 response.sendRedirect(REDIRECT_URL); return false; } } //一般隐私 if (USER_TOKEN_VALUE.equals(token)) { return true; } //拦截后跳转路径 response.sendRedirect(REDIRECT_URL); return false; } /** * 后处理回调方法,在渲染视图之前实现处理器的后处理 * * @param request * @param response * @param handler * @param modelAndView * @throws Exception */ @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { } /** * 整个请求处理完毕回调方法,即在视图渲染完毕时回调,可用于记录日志 * * @param request * @param response * @param handler * @param ex * @throws Exception */ @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { } }WebInterceptorConfig.java/** * @desc: 拦截器配置 * @author: YanMingXin * @create: 2021/9/27-17:01 **/ @Configuration public class WebInterceptorConfig extends WebMvcConfigurationSupport { /** * 注入自定义拦截器 */ @Resource private WebInterceptorHandler webInterceptorHandler; /** * 配置拦截器和拦截、放行路径 * * @param registry */ @Override protected void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(webInterceptorHandler) .excludePathPatterns("/user/getUserPublicInfo") .excludePathPatterns("/user/noAuth") .addPathPatterns("/**"); } }3.2 总体分析实现自定义拦截器:在Spring5之前@Component public class WebInterceptorHandler extends WebMvcConfigurerAdapter {}Spring5及以后@Component public class WebInterceptorHandler implements HandlerInterceptor {}或@Component public class WebInterceptorHandler extends WebMvcConfigurationSupport {}拦截器配置:@Configuration public class WebInterceptorConfig extends WebMvcConfigurationSupport{}4 测试启动项目,打开postman4.1 访问公开路径:http://127.0.0.1:9000/user/getUserPublicInfo4.2 访问个人路径:http://127.0.0.1:9000/user/getUserInfo带上需要的参数再次访问:4.3 访问隐私路径:带上参数和请求头再次访问:测试成功!今天的表演到此结束咯~
1 搭建Linux服务器1.1 购买阿里云服务器或安装虚拟机这里建议是CentOS 7.X或CentOS 8.X,当然其他的Linux如deepin、Ubuntu也可以,只是软件环境的安装包和安装方式不同,跑项目都是差不多的1.2 安装JDK1.8(CentOS 8为例)下载JDK安装包:https://www.oracle.com/java/technologies/javase/javase-jdk8-downloads.html选择:jdk-8u291-linux-x64.tar.gz安装:[root@xxx local]# tar -zxvf jdk-8u291-linux-x64.tar.gz [root@xxx local]# vim /etc/profile修改环境变量:export JAVA_HOME=/usr/local/jdk1.8.0_291 export JER_HOME=${JAVA_HOME}/jre export CLASSPATH=.:${JAVA_HOME}/lib:${JRE_HOME}/lib:$CLASSPATH export JAVA_PATH=${JAVA_HOME}/bin:${JRE_HOME}/bin export PATH=$PATH:${JAVA_PATH}刷新配置文件并查看安装结果:[root@xxx local]# source /etc/profile [root@xxx local]# java -version java version "1.8.0_291" Java(TM) SE Runtime Environment (build 1.8.0_291-b15) Java HotSpot(TM) 64-Bit Server VM (build 25.291-b15, mixed mode)1.3 安装MySQL参考这篇文章:https://blog.csdn.net/Mr_YanMingXin/article/details/1108583481.4 安装Maven(非必要)为什么说安装Maven不是必要的呢?因为Java具有跨平台的特性,可以在Windows环境下打包成Jar文件或者war文件,在Linux上一样可以直接运行!Linux安装Maven的作用:进行项目的编译和打包,当然也可以直接运行安装步骤:下载Maven安装包:https://mirrors.tuna.tsinghua.edu.cn/apache/maven/maven-3/3.8.1/binaries/apache-maven-3.8.1-bin.tar.gz放到Linux上解压并修改配置文件:[root@xxx local]# tar -zxvf apache-maven-3.8.1-bin.tar.gz [root@xxx local]# vim /etc/profile修改环境变量:export MAVEN_HOME=/opt/apache-maven-3.8.1 export PATH=$MAVEN_HOME/bin:$PATH刷新配置文件并验证:[root@xxx local]# source /etc/profile [root@xxx local]# mvn -v Apache Maven 3.8.1 (05c21c65bdfed0f71a2f2ada8b84da59348c4c5d) Maven home: /opt/apache-maven-3.8.1 Java version: 1.8.0_141, vendor: Oracle Corporation, runtime: /usr/local/jdk1.8.0_141/jre Default locale: en_US, platform encoding: UTF-8 OS name: "linux", version: "4.18.0-193.14.2.el8_2.x86_64", arch: "amd64", family: "unix"别忘了!Maven配置阿里云镜像:[root@xxx apache-maven-3.8.1]# pwd /opt/apache-maven-3.8.1 [root@xxx apache-maven-3.8.1]# vim conf/settings.xml大约是在第140多行左右: <mirrors> <!-- 阿里云仓库 --> <mirror> <id>alimaven</id> <mirrorOf>central</mirrorOf> <name>aliyun maven</name> <url>https://maven.aliyun.com/repository/public</url> </mirror> <mirror> <id>alimaven</id> <mirrorOf>central</mirrorOf> <name>aliyun maven</name> <url>http://maven.aliyun.com/nexus/content/repositories/central/</url> </mirror> <!-- 中央仓库1 --> <mirror> <id>repo1</id> <mirrorOf>central</mirrorOf> <name>Human Readable Name for this Mirror.</name> <url>http://repo1.maven.org/maven2/</url> </mirror> <!-- 中央仓库2 --> <mirror> <id>repo2</id> <mirrorOf>central</mirrorOf> <name>Human Readable Name for this Mirror.</name> <url>http://repo2.maven.org/maven2/</url> </mirror> </mirrors>2 项目部署运行2.1 项目编译打包(Windows环境下打包成jar文件)项目的编译和打包一般有两种方式(1)直接在IDEA中进行项目的打包(2)使用命令行方式在含有pom.xml文件的目录下使用cmd执行mvn clean package命令这样就代表成功啦,然后就会发现多了一个target目录和一个jar 文件以上两个过程在Linux下也是一样的!(前提是Linux也安装了Maven)2.2 部署运行(1)方式一:可以是未编译成Jar文件情况下cd到需要运行的项目主目录下,pom.xml文件的目录[root@xxx placard_demo]# ll -rw-r--r-- 1 root root 10519 May 27 08:17 placard_demo.iml -rw-r--r-- 1 root root 5989 May 27 08:17 pom.xml -rw-r--r-- 1 root root 1366 Apr 1 22:36 README.md drwxr-xr-x 4 root root 30 Apr 1 10:21 src drwxr-xr-x 5 root root 61 May 27 14:24 target使用mvn spring-boot:run命令运行如果长时间运行请使用nohup mvn spring-boot:run(2)方式二:已经有Jar文件情况下cd到需要运行的项目主目录的target目录下,jar文件的目录[root@xxx target]# ll total 56372 drwxr-xr-x 6 root root 92 May 27 14:23 classes drwxr-xr-x 2 root root 28 Jun 27 15:06 maven-archiver drwxr-xr-x 3 root root 35 May 27 14:23 maven-status -rw-r--r-- 1 root root 42572685 Jun 27 15:07 placard_demo-0.0.1-SNAPSHOT.jar -rw-r--r-- 1 root root 15147851 Jun 27 15:07 placard_demo-0.0.1-SNAPSHOT.jar.original drwxr-xr-x 3 root root 17 May 27 14:24 test-classes使用java -jar xxx.jar命令运行如果长时间运行请使用nohup java -jar xxx.jar2.3 注意事项使用nohup运行时会产生一个日志文件nohup.out,包含项目运行时的全部控制台日志输入,所以报错信息就可以根据它来查看。2.4 常见问题(1)jar文件无法运行Linux的文件权限分为可读、可写、可执行,分别用r、w、x表示所以无法运行可能是权限的问题,使用ll或ls -a命令查看jar文件的权限改变权限:chmod 777 xx.jar 或 chmod ugo+x xx.jar当然这是一种不太安全的行为,因为把所有用户都赋予了对该文件的全部权限,相对安全的做法请自字百度下chmod 的用法。(2)查看后台进程ps -ef|grep java如果后台有进程,大家就可以利用客户端测试啦!欢迎留言点赞!
2022年08月
2022年07月
2022年05月