服务网格概念源于 Buoyant 公司的CEO Willain Morgan 的文章 “What’s a service mesh ? And do i need one?”

一、微服务治理的挑战

  • Kubernets 提供了一系列强大的服务管理机制,如多种类型的Pod控制器实现应用部署、升级和弹性扩缩容的服务管理机制,并借助Service CRD等实现了服务注册和负载均衡,但是在随着服务体系不断完善和无法忽略的网络不可靠因素显然也带来了新的挑战,如 :

    • 服务注册和服务发现、负载均衡、健康状态检查、限流、熔断、异常点检测、流量镜像、A/B测试、故障注入、日志、分布式跟踪等…

二、服务网格是什么

  • 服务网格(Service Mesh)是一个专门处理服务通讯的基础设施层。它的职责是在由云原生应用组成服务的复杂拓扑结构下进行可靠的请求传送。在实践中,它是一组和应用服务部署在一起的轻量级的网络代理,并且对应用服务透明。
  • 服务网格从总体架构上来讲比较简单,不过是一堆紧挨着各项服务的用户代理,外加一组任务管理组件组成。
  • control plane 控制平面 : 管理组件被称为控制层主要负责与控制平面中的代理通信,下发策略和配置。
  • data plane 数据平面 : 代理在服务网格中被称为数据层主要负责直接处理入站和出站数据包,转发、路由、健康检查、负载均衡、认证、鉴权、产生监控数据等。
  • 一个典型的服务网格部署网络结构图如下:( 其中绿色方块为应用服务,蓝色方块为 Sidecar Proxy ,应用服务之间通过 Sidecar Proxy 进行通信,整个服务通信形成图中的蓝色网络连线,图中所有蓝色部分就形成了 Service Mesh。)

三、服务网格带来的便捷

  • Service Mesh 解决方案极大降低了业务逻辑于网络功能之间的耦合度,能够快捷、方便地集成到现有的业务环境中,并提供了多语言、多协作,运维和管理成本被大大压缩,且开发人员能够精力集中于业务逻辑本身,而无需关注业务代码以外的其他功能;
  • Service Mesh 服务间通信将遵循以下通信逻辑 :
    • 微服务彼此间不会直接进行通信,而是由各服务前端的称为 Service Mesh的代理程序进行 Envoy Sidecar;
    • Service Mesh 内置支持服务发现、熔断、负载均衡等网络相关的用于控制服务间通信的各种高级功能;
    • Service Mesh于编程语言无关,开发人员可以使用任何编程语言编写微服务的业务逻辑,各服务之间也可以使用不同的编程语言开发;
    • 服务间的通信的局部故障可由 Service Mesh自动处理;
    • Service Mesh中的各服务的代理程序由控制平面(Control Plane)集中管理;各代理程序之间的通信网络也称为数据平面(Data Plane);
    • 部署于容器编排平台,各代理程序会以微服务容器的Sidecar模式运行 ( Kubernetes );

四、开源服务网格的实现方案

  • 在实现上,数据平面的主流解决方案由 Linkerd 、Nginx、Envoy、Haproxy和Traefix等,而控制平面的主要实现有 Istio、SmartStack等几种。

    • Linkerd

      • 由Buoyant 公司于2016年率先创建的开源高性能网络代理程序(数据平面),是业界第一款 Service Mesh产品,引领并促进了相关技术的快速发展。
      • Linkerd使用Namerd提供的控制平面,实现中心化管理和存储路由规则、服务发现配置、支持运行时动态的路由等功能。
    • Envoy
      • 核心功能于数据平面,与2016年由Lyft公司创建并开源,目标是成为通用的数据平面。
      • 云原生应用,既可以作为前端代理,也可以实现Service Mesh中的服务间通信。
      • Envoy 常被用于实现 API Gateway 以及Kubernetes的Ingress Controller ,不过基于Envoy实现的 Service Mesh产品 Istio有着更广泛的用户基础。
    • Istio
      • 相比前两者来说,Istio发布事件稍晚,它与2017年5月面世,但却是目前最火热的Service Mesh解决方案,得到了Google 、IBM、Redhat等公司的大力推广及支持。
      • 目前仅支持部署在Kubernetes之上,其数据平面由Envoy实现。
  • 最新服务网格全景图

五、Istio架构解析

5.1、控制平面 - istiod

  • Istio的整体架构,从逻辑上,Istio分为数据平面和控制平面两个部分:

    • 数据平面是以 sidecar 方式部署的智能代理,Istio默认集成的是Envoy。数据平面用来控制微服务之间的网络通讯,以及和Mixer模块通信。
    • 控制平面负责管理和配置数据平面,控制数据平面的行为,如代理路由流量,实施策略,收集遥测数据,加密认证等。控制平面分为Pilot、Mixer、Citadel三个组件;
  • 迄今,Istio架构经历了三次重要变革

  • 2018.07,Istio v1.0 ,单体

    • 控制平面主要有三个组件Pilot(流量治理核心组件)

      • Pilot - 流量治理核心组件 : 控制平面核心组件

        • 管理和配置部署在Istio服务网格中的所有Envoy代理实例。
        • 为Envoy Sidecar 提供服务发现、智能路由的流量管理功能 (例如,A/B测试、金丝雀等)和弹性(超时、重试、断路器等)。
      • Citadel - 安全相关核心组件 : 身份和凭据管理等安全相关的功能,实现强大的服务到服务和最终用户身份验证,帮助用户基于服务身份(而非网络控制机制)构建零信任的安全网络环境;
      • Mixer - 遥测相关功能组件 : 遥测(指标、日志、分布式链路)和策略(访问控制、配额、限速等),通过内部插件接口扩展支持第三方组件,插件的修改或更新,需要重新部署Mixer。
  • 2019.03, Istio v1.1 ,完全分布式,新增Galley
    • Galley - 验证用户编写的配置校验

      • 是Istio的配置验证、摄取、处理和分发组件;
      • 负责将其余的Istio组件从底层平台(例如Kubernetes) 获取用户配置的细节隔离开来,从而将Pilot与底层平台进行解耦;
  • 2020.03,Istio v1.5 - istiod ,回归单体
    • 抛弃影响性能的Mixer,遥测功能交由 Envoy自行完成;
    • 将 Pilot、Citadel、Galley 整合为一个单体应用 Istiod ;
    • Istiod
      • Istio 充当控制平面,将配置分发到所有的 Sidecar 代理和网关;
      • 它能够为支持网络的应用实现智能化的负载均衡机制,且相关流量绕过了 kube-proxy ;

5.2、数据平面 - Envoy

  • Istio 的核心控件Envoy

    • Istio 选择 Envoy 作为 Sidecar 代理,Envoy 本质上是一个为面向服务的架构而设计的 7 层代理和通信总线。Envoy 基于 C++ 11 开发而成,性能出色。除了具有强大的网络控制能力外,Envoy 还可以将流量行为和数据提取出来发送给 Mixer 组件,用以进行监控。
    • Envoy 在网络控制方面的主要功能如下。
      • HTTP 7 层路由、支持 gRPC、HTTP/2服务发现和动态配置、健康检查、负载均衡等
  • 我们知道,在Kubernetes环境中,同一个Pod内的不同容器间共享网络栈,这一特性使得Sidecar可以接管进出这些容器的网络流量,这就是Sidecar模式的实现基础。Envoy是目前Istio默认的数据平面,实际上因为Istio灵活的架构,完全可以选择其他兼容的产品作为Sidecar。目前很多服务网格产品都可以作为Istio的数据平面并提供集成。

  • Listeners : 面向客户端一次,监听套接字,用于接收客户端请求组件;
  • Filter : Listeners内部会包含一到多个Filter,组成过滤器链,支持多条过滤串连(支持多个过滤器),依次仅从匹配后向后端代理,也可称为 filter chains;
  • router :用于将Filter过滤的请求分类以后,过滤到不同的后端组件 Cluster
  • Cluster : Cluster在Envoy中可以定义多个,用于实现归类被代理服务器组的组件,它们后端对应的一到多个 Server 组成;

六、Istio流量治理入门

  • Istio的所有流量规则和控制策略都基于 Kubernetes CRD 实现,这包括网络功能相关的 VirtualServiceDestinationRuleGatewayServiceEntryEnvoyFiler 等;
  • Istio通过 Ingress Gateway 为网格引入外部流量;
    • Gateway 中运行的主程序亦为Envoy,它同样从控制平面接收配置,并负责完成相关的流量传输;
    • 换而言之,Gateway资源对象用于将外部访问映射到内部服务,它自身只负责通信子网的相关功能,例如套接字,而七层路由功能则由 VirtualService 实现;
  • Istio 基于 ServiceEntry 资源对象将外部服务注册到网格内,从而像将外部服务以类同内部服务一样的方式进行治理;
    • 对于外部服务,网格内 Sidecar方式运行的Envoy即能执行治理;
    • 若需要将外出流量约束于特定几个节点时则需要使用专门的 Egress Gateway 完成,并基于此 Egress Gateway执行相应的流量治理;
  • Virtual Services 和 Destination Rules 是Istio流量路由功能的核心组件;
    • Virtual Services 用于将分类流量并将其路由到指定的目的地 (Destination),而 Destination Rules 则用于配置哪个指定 Destination 如何处理流量;

      • Virtual Service

        • 用于在 Istio及其底层平台 (例如 Kubernetes) 的基础上配置如何将请求路由到网格中的各 Service之上;
        • 通常由一组路由规则 (routing rules) 组成,这些路由规则按顺序进行评估,从而使 Istio能够将那些对 Virtual Service 的每个给定请求匹配到网格内特定的目标之上;
        • 事实上,其定义的是分发给网格内各 Envoy 的VirtualHost 和 Route的相关配置;
      • Destination Rules
        • 定义流量在 “目标” 内部的各端点之间的分发机制,例如将各端点进行分组,分组内端点间的流量均衡机制,异常探测等;
        • 事实上,其定义的是分发给网格内各 Envoy和 Cluster的相关配置;
  • 下图为入栈流量请求路线 :Ingress Gateway -> Gateway -> VirtualService -> DestinationRule -> Service -> Pod

6.1、istio中是如何开放服务给外部访问 ?

如果不考虑网格的话,Kubernetes中是如何让内部应用给外部访问的呢 ?
方式一 :Pod hostNetwork 网络模型,通过Pod所运行的Node 节点访问
方式二 :Service 类型为 NodePort 通过任何 Node节点的地址访问
方式三 :Service 类型为 LoadBalancer 集群外部的负载均衡器访问
方式四 :Ingress (NodePort) -> Service (ClusterIP) -> Pod 通过Ingress Service类型为NodePort 再通过任何 Node节点的地址访问

以 Grafana 为例 :ingress gateway(接入集群外部流量 80) -> virtualservice -> destinationrule

  • 1、 Gateway CRD - ingress gateway 配置grafana 侦听器和路由,将外部grafana.test.om域名的对于80端口和路由的请求引入到内部grafana
apiVersion: networking.istio.io/v1beta1
kind: Gateway
metadata:name: grafana-gatewaynamespace: istio-system
spec:# 匹配 ingress-gaetway 标签Podselector:app: istio-ingressgatewayservers:- port:# 端口, 通过 istio-ingressgateway 标准80端口接入number: 80name: httpprotocol: HTTPhosts:- "grafana.test.com"
  • 2、 virtualservice ,使其和Gateway进行联动
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:name: grafana-virtualservicenamespace: istio-system
spec:hosts:- "grafana.test.com"gateways:- grafana-gatewayhttp:# 定义路由匹配条件- match:- uri:prefix: /   # 用户请求路由,表示由 grafana.test.com主机由 / 开头, 都路由此目标主机;route:- destination:host: grafana  # 目标port:number: 3000 # 目标端口# kubectl get vs -n istio-system
NAME                     GATEWAYS              HOSTS                  AGE
grafana-virtualservice   ["grafana-gateway"]   ["grafana.test.com"]   12s# 查看 Ingress Gateway 中定义的路由
# istioctl proxy-config routes $InGW -n istio-system
NAME           DOMAINS              MATCH                  VIRTUAL SERVICE
http.8080      grafana.test.com     /*                     grafana-virtualservice.istio-system*                    /healthz/ready**                    /stats/prometheus*# 此时cluster其实是没有人为去定义的, 虽然grafana的cluster并没有和destinationrule所关联,但是cluster是存在的(因为Service是事先存在的)
istioctl proxy-config clusters $InGW -n istio-system --port 3000
SERVICE FQDN                               PORT     SUBSET     DIRECTION     TYPE     DESTINATION RULE
grafana.istio-system.svc.cluster.local     3000     -          outbound(出栈概念是站在sidecar proxy角度)      EDS
  • 3、 destinationrule 额外定义路由策略
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:name: grafananamespace: istio-system
spec:host: grafanatrafficPolicy:tls:mode: DISABLE

6.2、istio中网格内部服务间如何通讯 ?

frontend访问demoapp -> frontend访问多版本demoapp

6.2.1、frontend访问demoapp

  • 两个应用

    • frontend(proxy) : 前端应用,会请求后端的 demoapp

      • service : proxy
    • demoapp : 后端应用
      • service : demoappv10

  • 1、 部署demoapp v10 - 标签 demoapp: v1.0
apiVersion: apps/v1
kind: Deployment
metadata:labels:app: demoappname: demoappv10
spec:replicas: 3selector:matchLabels:app: demoappversion: v1.0template:metadata:creationTimestamp: nulllabels:app: demoappversion: v1.0spec:containers:- image: ikubernetes/demoapp:v1.0name: demoappenv:- name: PORTvalue: "8080"
  • 2、 为demoapp v.1.0创建service - demoappv10 (istio发现并作为服务作为网格内部使用,需要创建service)
apiVersion: v1
kind: Service
metadata:labels:app: demoappname: demoappv10
spec:ports:- name: http-8080port: 8080protocol: TCPtargetPort: 8080selector:app: demoappversion: v1.0type: ClusterIP
# kubectl get pods
NAME                          READY   STATUS    RESTARTS   AGE
demoappv10-65cdf575c8-cqx5b   2/2     Running   0          25s  # demoapp + sidecar
demoappv10-65cdf575c8-fc7cv   2/2     Running   0          25s
demoappv10-65cdf575c8-nwftc   2/2     Running   0          25s# DEMOAPP=$(kubectl get pods -l app=demoapp -o jsonpath={.items[0].metadata.name})
# istioctl proxy-status
NAME                                                   CDS        LDS        EDS        RDS          ISTIOD                      VERSION
demoappv10-5c497c6f7c-24dk4.default                    SYNCED     SYNCED     SYNCED     SYNCED       istiod-76d66d9876-lqgph     1.12.1
demoappv10-5c497c6f7c-fdwf4.default                    SYNCED     SYNCED     SYNCED     SYNCED       istiod-76d66d9876-lqgph     1.12.1
demoappv10-5c497c6f7c-ks5hk.default                    SYNCED     SYNCED     SYNCED     SYNCED       istiod-76d66d9876-lqgph     1.12.1# 查看侦听器
# istioctl proxy-config listeners $DEMOAPP --port=8080
ADDRESS PORT MATCH                        DESTINATION
0.0.0.0 8080 Trans: raw_buffer; App: HTTP Route: 8080
0.0.0.0 8080 ALL                          PassthroughCluster# 查看路由信息
# istioctl proxy-config routes $DEMOAPP | grep "demoappv10"
8080                                                                      demoappv10, demoappv10.default + 1 more...           /*# 查看集群信息
# istioctl proxy-config clusters $DEMOAPP | grep "demoappv10"
demoappv10.default.svc.cluster.local                                 8080      -          outbound      EDS# 查看后端端点信息
# istioctl proxy-config endpoints $DEMOAPP | grep "demoappv10"
10.220.104.135:8080              HEALTHY     OK                outbound|8080||demoappv10.default.svc.cluster.local
10.220.104.139:8080              HEALTHY     OK                outbound|8080||demoappv10.default.svc.cluster.local
10.220.104.140:8080              HEALTHY     OK                outbound|8080||demoappv10.default.svc.cluster.local
  • 3、 部署frontend proxy 前端代理应用
apiVersion: apps/v1
kind: Deployment
metadata:name: proxy
spec:progressDeadlineSeconds: 600replicas: 1selector:matchLabels:app: proxytemplate:metadata:labels:app: proxyspec:containers:- env:- name: PROXYURLvalue: http://demoappv10:8080   # 请求demoappv10image: ikubernetes/proxy:v0.1.1imagePullPolicy: IfNotPresentname: proxyports:- containerPort: 8080name: webprotocol: TCPresources:limits:cpu: 50m
---
apiVersion: v1
kind: Service
metadata:name: proxy
spec:ports:- name: http-80port: 80protocol: TCPtargetPort: 8080selector:app: proxy
  • 4、 client 访问 frontend proxy (补充 :真正发挥网格流量调度的是 egress listener)

流量走向 :client pod -> Sidecar Envoy(Egress Listener proxy:80) -> (Ingress Listener) poroxy pod -> (Egress Listener)demoappv10:8080 -> (Ingress Listener)demoappv10 pod

# kubectl run client --image=ikubernetes/admin-box -it --rm --restart=Never --command -- /bin/sh
If you don t see a command prompt, try pressing enter.
root@client # curl proxy
# 后端demoappv10服务网络的内容
Proxying value: iKubernetes demoapp v1.0 !! ClientIP: 127.0.0.6, ServerName: demoappv10-5c497c6f7c-24dk4, ServerIP: 10.220.104.143!- Took 314 milliseconds.
  • 5、流量走向视图

6.2.2、frontend访问多版本demoapp,并期望流量在两版本中进行按需分配

如proxy访问demoapp 时 http://demoapp/canary 转发至 v11版本,http://demoapp/ 转发至 v10版本,并且要求访问 v11版本权重为 90% ,v10版本为10%;

  • 6、 部署demoapp v11 - 标签 demoapp: v1.1
apiVersion: apps/v1
kind: Deployment
metadata:labels:app: demoappv11version: v1.1name: demoappv11
spec:progressDeadlineSeconds: 600replicas: 2selector:matchLabels:app: demoappversion: v1.1template:metadata:labels:app: demoappversion: v1.1spec:containers:- image: ikubernetes/demoapp:v1.1imagePullPolicy: IfNotPresentname: demoappenv:- name: "PORT"value: "8080"ports:- containerPort: 8080name: webprotocol: TCPresources:limits:cpu: 50m

7、 为demoapp v.11 创建service - demoappv11 (istio发现并作为服务作为网格内部使用,需要创建service)

apiVersion: v1
kind: Service
metadata:name: demoappv11
spec:ports:- name: http-8080port: 8080protocol: TCPtargetPort: 8080selector:app: demoappversion: v1.1type: ClusterIP
# kubectl get service
NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
demoappv10   ClusterIP   10.100.67.168   <none>        8080/TCP   18h
demoappv11   ClusterIP   10.100.72.0     <none>        8080/TCP   17s
  • 8、 为demoapp v1.0 & 1.1 创建service - demoapp 将demoappv10和demoappv11两个版本绑定到一组Cluster
apiVersion: v1
kind: Service
metadata:name: demoapp
spec:ports:- name: httpport: 8080protocol: TCPtargetPort: 8080selector: # 选择pod标签为 demoappv10 和 demoappv11 共同存在的标签app: demoapptype: ClusterIP
  • 9、 定义DestinationRule : DestinationRule 的主要作用就是将定义的demoapp 的service后端适配到的Pod分为两组,v10和v11两个组称为两个子集;
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:# DS名称,标注集群名name: demoapp
spec:# 主机 :对demoapp service 服务的访问host: demoapp# 集群子集划分策略, 这里使用标签选择器对后端POD做逻辑组划分subsets:# 逻辑组名称- name: v10# 在原本的筛选条件上,额外增加使用以下标签选择器对后端端点归类为 v10 子集labels:version: v1.0- name: v11# 在原本的筛选条件上,额外增加使用以下标签选择器对后端端点归类为 v11 子集labels:version: v1.1
  • 10、 调配 VirtualService :对 frontend proxy 访问的 demoapp的route进行定义
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:name: demoapp
spec:hosts:- demoapphttp:- name: canary# 匹配条件match:- uri:prefix: /canaryrewrite:uri: /# 路由目标route:- destination:# 调度给demoapp的clusters的v11子集host: demoapp# 子集subset: v11weight: 90   # 承载权重 90% 流量- name: defaultroute:- destination:# 调度给demoapp的clusters的v10子集host: demoapp# 子集subset: v10weight: 10        # 承载权重 10% 流量
  • 11、流量走向视图

微服务治理 - 初探Istio相关推荐

  1. 微服务治理框架的选择:对比Spring Cloud和Istio

    导读:目前主流的微服务治理框架主要是Spring Cloud.而Istio作为新一代微服务框架,越来越受到关注.在本文中,我们分享如何选择这两种微服务框架. 作者:魏新宇 宋志麒 杨金锋 来源:大数据 ...

  2. 详解4种微服务框架接入Istio方案

    本文分享自华为云社区<传统微服务框架接入Istio方案详解>,作者:香菜聊游戏 . 微服务的概念和原理 微服务带来的问题 微服务带来的好处: 解耦了业务,解耦了代码和架构,业务更紧凑,逻辑 ...

  3. 微服务治理之分布式链路追踪--3.zipkin实战

    微服务治理之分布式链路追踪–3.zipkin实战 本节是基于zipkin分布式追踪系统搭建,因为对 scala 和 play framework 2 框架不熟悉,所以,没有采用opentelemetr ...

  4. 企业微服务治理的解决思路

    背景 随着业务需求的日渐复杂以及产品迭代节奏的不断加快,业务开发部门面临着前所未有的压力.为了抢占先机,用最快的速度准确把握用户需求的变化,优化开发出来的业务产品,微服务(MicroServices) ...

  5. Mendix结合腾讯TSF实现微服务治理

    联合作者: 彭杉(原Mendix产品总监) 肖雨浓(腾讯云中间件总经理) 姜彪(Mendix技术经理) 韩欣(腾讯微服务平台技术经理) 刘阎(腾讯云中间件产品经理) 01. 概述 本文档介绍了西门子企 ...

  6. 不改一行代码,轻松拥有企业级微服务治理|MSE微服务治理专业版重磅发布

    简介:随着业务的发展,微服务拆分越来越复杂,微服务的治理也成了一个比较令人头疼的问题.有没有更加简单且高效的方法来解决微服务治理的难题? 作者:十眠 随着业务的发展,微服务拆分越来越复杂,微服务的治理 ...

  7. 微服务技术初探:基于IDEA使用Maven构建SpringCloud项目

    Spring Cloud是一系列框架的有序集合.它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册.配置中心.消息总线.负载均衡.断路器.数据监控等,都可以用 ...

  8. 干货 | 基于开源体系的云原生微服务治理实践与探索

    作者简介 CH3CHO,携程高级研发经理,负责微服务.网关等中间件产品的研发工作,关注云原生.微服务等技术领域. 一.携程微服务产品的发展历程 携程微服务产品起步于2013年.最初,公司基于开源项目S ...

  9. 基于开源体系的云原生微服务治理实践与探索

    作者:董艺荃|携程服务框架负责人 携程微服务产品的发展历程 携程微服务产品起步于 2013 年.最初,公司基于开源项目 ServiceStack 进行二次开发,推出 .Net 平台下的微服务框架 CS ...

最新文章

  1. ZOJ 3735 dp
  2. VC:隐藏CTabCtrl标签按钮
  3. ASP.NET 程序中常用的三十三种代码(1)
  4. python elasticsearch bulk_Elasticsearch —— bulk批量导入数据
  5. html怎么定位到不同的页面,html页面定位到指定位置的4种实现方式
  6. UnityShader6:最简单的顶点/片元着色器
  7. 阿里13篇论文入选数据库顶会!PolarDB技术被认为引领数据库发展方向
  8. html背景怎么变成透明的,怎样把图片背景变成透明
  9. 【UV打印机】PrintExp打印软件教程(七)-高级模式(其它)
  10. CCPC-Wannafly Winter Camp Day1 (Div2, onsite) A 机器人 分类讨论
  11. TARA-威胁建模方案2
  12. hadoop如何解除safemode-安全模式
  13. IT的道德和伦理-个人隐私
  14. 网易云音乐推出异乡人年度企划:不同圈层音乐人演绎他乡故事
  15. 摩拜app显示未能连接到服务器,摩拜单车又现大面积故障?回应称未接到反馈
  16. How does “mov (%ebx,%eax,4),%eax” work?
  17. java.util.regex.PatternSyntaxException: Unclosed counted closure near index 14 [0-9a-zA-Z]{1, 20}报错
  18. 爬虫实战5:豆瓣读书爬取
  19. 图像去模糊(维纳滤波)
  20. 微信小程序项目实例——食堂吃哪个

热门文章

  1. 《小狗钱钱》--读书笔记
  2. Ubuntu桌面生存指南 (2) --- Ubuntu桌面体验简介
  3. Android平台OCR软件,Android平台OCR工具TessTwo工程
  4. Windows记事本UTF-8编码异常
  5. 半导体二极管(笔记)
  6. python内置函数reversed_Python内置函数(36)——reversed
  7. python实现闭合导线平差与坐标计算(改进)
  8. 用模拟精灵写的自动投票系统
  9. M-ADDA: Unsupervised Domain Adaptation with Deep Metric Learning
  10. 标书打印装订好了,改正错误,换页的方法