容器编排系统k8s之Ingress资源 - Linux-1874 - 博客园

mikel阅读(1098)

来源: 容器编排系统k8s之Ingress资源 – Linux-1874 – 博客园

前文我们了解了k8s上的service资源的相关话题,回顾请参考:https://www.cnblogs.com/qiuhom-1874/p/14161950.html;今天我们来了解下k8s上的Ingress资源的相关话题;

我们知道在k8s上service是用来解决Pod访问问题,它是通过kube-proxy在每个节点上创建iptables规则或ipvs规则,在用户请求某个pod时,用户的请求会被其service规则所捕获,从而实现访问对应pod;对于service来讲,用户请求直接在传输层就被捕获转发,效率很高效,但这同时也引入了一个新问题;比如我们运行的pod对外客户端访问需要https通信,如果使用service这种4层调度,那就意味着每个pod上我们要配置证书,这很显然不是我们想要做的;那有没有什么办法做到在用户访问pod对应的service时使用https,而对应pod里又不用https协议呢?答案是有的;比如我们可以使用nginx来做https会话卸载器;我们只需要在代理上配置证书即可;又比如我们在k8s上运行了各种各样的pod,这些pod的功能每个都不一样,有点是专门处理用户认证的,有点是专门处理站点主页的,有点专门处理支付的等等,而这些pod对外都是提供一个独有的url,那么这些pod需要怎么才能被集群外部访问到呢?我们知道对于一个站点来讲,如果后端有多个server同时提供一种服务,我们可以把这些同功能的server定义成一个组,然后使用nginx代理将不同功能url的访问代理到不同组上即可;这样一来解决了后端多server被负载访问的问题;那么对于k8s上这种同功能的pod怎么归并成一个组呢?用户访问不同url怎么调度到不同的组上呢?很显然要想实现这些功能,在k8s上应该有一个类似nginx一样的代理存在;这个代理就叫做ingress 控制器;ingress 控制器和k8s上的其他控制不一样,ingress控制器并不能直接运行为kube-controller-manager的一部分,它类似k8s集群上的coredns,需要在集群上单独部署,本质上就是一个pod,我们可以使用k8s上的ds或deploy控制器来创建它;ingress controller pod的作用主要是引入集群外部流量,并实时监控着apiserver上ingress资源的变动,并将其ingress中定义的规则转化为对应ingress控制器对应应用程序的专有配置,然后动态的重载或重启对应守护进程来使其配置文件生效;在k8s上ingress是一种标准资源,它本质上就是我们定义的基于dns名称(host)或url路径把请求转发至指定service资源的规则;简单讲ingress就是我们用来定义代理的配置所创建的资源;ingress控制器就是把对应ingress规则转换为对应ingress控制器中应用程序的专有配置,然后重启或重载对应配置文件使其生效的组件;

ingress和ingress controller pod的关系

提示:如上图所示,ingress就是ingress 控制器pod的代理规则;用户请求某个后端pod所提供的服务时,首先会通过ingress controller pod把流量引入到集群内部,然后ingress controller pod根据ingress定义的规则,把对应ingress规则转化为对应ingress controller pod实现的对应应用的配置(ingress controller 可以由任何具有七层反向代理功能的服务实现,比如nginx,haproxy等等)然后再适配用户请求,把对应请求反代到对应service上;而对于pod的选择上,ingress控制器可以基于对应service中的标签选择器,直接同pod直接通信,无须通过service对象api的再次转发,从而省去了用户请求到kube-proxy实现的代理开销(本质上ingress controller 也是运行为一个pod,和其他pod在同一网段中);

ingress controller部署

在k8s上ingress controller的实现有很多,比如基于nginx的,基于haproxy的等等,这里以nginx为例;

下载ingress-nginx包

1
wget https://github.com/kubernetes/ingress-nginx/archive/nginx-0.28.0.tar.gz

解压包,找到对应的部署清单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[root@master01 ~]# ll
total 92144
-rw------- 1 root root 65586688 Dec  8 15:16 flannel-v0.13.1-rc1.tar
drwxr-xr-x 2 root root     4096 Dec 21 21:04 manifests
-rw-r--r-- 1 root root 28760559 Dec 21 21:02 nginx-0.28.0.tar.gz
[root@master01 ~]# tar xf nginx-0.28.0.tar.gz
[root@master01 ~]# ls
flannel-v0.13.1-rc1.tar  ingress-nginx-nginx-0.28.0  manifests  nginx-0.28.0.tar.gz
[root@master01 ~]# cd ingress-nginx-nginx-0.28.0/
[root@master01 ingress-nginx-nginx-0.28.0]# ls
build         code-of-conduct.md  docs    hack      labels.yaml  mkdocs.yml      README.md              SECURITY_CONTACTS  version
Changelog.md  CONTRIBUTING.md     go.mod  images    LICENSE      OWNERS          requirements-docs.txt  test
cmd           deploy              go.sum  internal  Makefile     OWNERS_ALIASES  rootfs                 vendor
[root@master01 ingress-nginx-nginx-0.28.0]# cd deploy/
[root@master01 deploy]# ls
aws        cloud-generic  grafana   prometheus  static                       with-validating-webhook.yaml.tpl
baremetal  cluster-wide   minikube  README.md   validating-webhook.yaml.tpl
[root@master01 deploy]# cd static/
[root@master01 static]# ls
configmap.yaml  mandatory.yaml  namespace.yaml  provider  rbac.yaml  with-rbac.yaml
[root@master01 static]# pwd
/root/ingress-nginx-nginx-0.28.0/deploy/static
[root@master01 static]#

提示:资源配置清单在ingress-nginx-nginx-0.28.0/deploy/static下,名为mandatory.yaml;

资源配置清单内容

复制代码
apiVersion: v1
kind: Namespace
metadata:
  name: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx

---

kind: ConfigMap
apiVersion: v1
metadata:
  name: nginx-configuration
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx

---
kind: ConfigMap
apiVersion: v1
metadata:
  name: tcp-services
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx

---
kind: ConfigMap
apiVersion: v1
metadata:
  name: udp-services
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx

---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: nginx-ingress-serviceaccount
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx

---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
  name: nginx-ingress-clusterrole
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
rules:
  - apiGroups:
      - ""
    resources:
      - configmaps
      - endpoints
      - nodes
      - pods
      - secrets
    verbs:
      - list
      - watch
  - apiGroups:
      - ""
    resources:
      - nodes
    verbs:
      - get
  - apiGroups:
      - ""
    resources:
      - services
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - ""
    resources:
      - events
    verbs:
      - create
      - patch
  - apiGroups:
      - "extensions"
      - "networking.k8s.io"
    resources:
      - ingresses
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - "extensions"
      - "networking.k8s.io"
    resources:
      - ingresses/status
    verbs:
      - update

---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: Role
metadata:
  name: nginx-ingress-role
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
rules:
  - apiGroups:
      - ""
    resources:
      - configmaps
      - pods
      - secrets
      - namespaces
    verbs:
      - get
  - apiGroups:
      - ""
    resources:
      - configmaps
    resourceNames:
      # Defaults to "<election-id>-<ingress-class>"
      # Here: "<ingress-controller-leader>-<nginx>"
      # This has to be adapted if you change either parameter
      # when launching the nginx-ingress-controller.
      - "ingress-controller-leader-nginx"
    verbs:
      - get
      - update
  - apiGroups:
      - ""
    resources:
      - configmaps
    verbs:
      - create
  - apiGroups:
      - ""
    resources:
      - endpoints
    verbs:
      - get

---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: RoleBinding
metadata:
  name: nginx-ingress-role-nisa-binding
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: nginx-ingress-role
subjects:
  - kind: ServiceAccount
    name: nginx-ingress-serviceaccount
    namespace: ingress-nginx

---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: nginx-ingress-clusterrole-nisa-binding
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: nginx-ingress-clusterrole
subjects:
  - kind: ServiceAccount
    name: nginx-ingress-serviceaccount
    namespace: ingress-nginx

---

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-ingress-controller
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      app.kubernetes.io/name: ingress-nginx
      app.kubernetes.io/part-of: ingress-nginx
  template:
    metadata:
      labels:
        app.kubernetes.io/name: ingress-nginx
        app.kubernetes.io/part-of: ingress-nginx
      annotations:
        prometheus.io/port: "10254"
        prometheus.io/scrape: "true"
    spec:
      # wait up to five minutes for the drain of connections
      terminationGracePeriodSeconds: 300
      serviceAccountName: nginx-ingress-serviceaccount
      nodeSelector:
        kubernetes.io/os: linux
      containers:
        - name: nginx-ingress-controller
          image: quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.28.0
          args:
            - /nginx-ingress-controller
            - --configmap=$(POD_NAMESPACE)/nginx-configuration
            - --tcp-services-configmap=$(POD_NAMESPACE)/tcp-services
            - --udp-services-configmap=$(POD_NAMESPACE)/udp-services
            - --publish-service=$(POD_NAMESPACE)/ingress-nginx
            - --annotations-prefix=nginx.ingress.kubernetes.io
          securityContext:
            allowPrivilegeEscalation: true
            capabilities:
              drop:
                - ALL
              add:
                - NET_BIND_SERVICE
            # www-data -> 101
            runAsUser: 101
          env:
            - name: POD_NAME
              valueFrom:
                fieldRef:
                  fieldPath: metadata.name
            - name: POD_NAMESPACE
              valueFrom:
                fieldRef:
                  fieldPath: metadata.namespace
          ports:
            - name: http
              containerPort: 80
              protocol: TCP
            - name: https
              containerPort: 443
              protocol: TCP
          livenessProbe:
            failureThreshold: 3
            httpGet:
              path: /healthz
              port: 10254
              scheme: HTTP
            initialDelaySeconds: 10
            periodSeconds: 10
            successThreshold: 1
            timeoutSeconds: 10
          readinessProbe:
            failureThreshold: 3
            httpGet:
              path: /healthz
              port: 10254
              scheme: HTTP
            periodSeconds: 10
            successThreshold: 1
            timeoutSeconds: 10
          lifecycle:
            preStop:
              exec:
                command:
                  - /wait-shutdown

---

apiVersion: v1
kind: LimitRange
metadata:
  name: ingress-nginx
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
spec:
  limits:
  - default:
    min:
      memory: 90Mi
      cpu: 100m
    type: Container
复制代码

提示:以上清单主要定义了一个名称ingress-nginx的名称空间,在其名称空间下创建了几个configmap,最重要的是用deployment创建了一个ingress-nginx pod;

这里说一下,对于ingress-nginx控制器,它本质还是运行为一个pod,对于pod来说要想接入外部访问流量到集群内部来,有三种方式,一种是使用NodePort类型的service;第二种是使用ds或deploy控制器,在定义pod模板时使用hostPort把pod端口映射到宿主机方式;第三种是定义pod模板时使用hostNetwork,直接共享宿主机网络名称空间;如下所示

使用专有NodePort service来引入外部流量

提示:这种使用deploy控制管理ingress controller pod,如果在pod模板中没有暴露端口,则需要创建一个service资源来暴露ingress controller pod的端口来引入外部流量到集群内部;

使用ds控制器管理ingress controller pod在pod模板中使用hostPort方式暴露端口

提示:使用ds控制器能够保证每个节点上只运行一个ingress controller,所以我们可以把对应ingress controller pod端端口通过端口映射的方式映射到宿主机上的某一固定端口;

使用ds控制器在pod模板中使用hostNetwork方式共享宿主机网络名称空间

提示:共享宿主机网络名称空间,也必须使用ds控制器来确保对应每个节点上只能运行一个ingress controller pod,这样才能确保每个ingress controller pod能够正常把端口暴露出去,以供集群外部客户端访问;

选择上述其中一种方式暴露ingress controller pod的端口即可;如果选择使用ds控制器来暴露端口,我们就需要修改其对应资源配置清单中的pod模板,如下所示

使用ds控制器来管理ingress controller pod在pod模板中使用hostPort方式暴露端口

复制代码
apiVersion: v1
kind: Namespace
metadata:
  name: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx

---

kind: ConfigMap
apiVersion: v1
metadata:
  name: nginx-configuration
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx

---
kind: ConfigMap
apiVersion: v1
metadata:
  name: tcp-services
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx

---
kind: ConfigMap
apiVersion: v1
metadata:
  name: udp-services
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx

---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: nginx-ingress-serviceaccount
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx

---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
  name: nginx-ingress-clusterrole
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
rules:
  - apiGroups:
      - ""
    resources:
      - configmaps
      - endpoints
      - nodes
      - pods
      - secrets
    verbs:
      - list
      - watch
  - apiGroups:
      - ""
    resources:
      - nodes
    verbs:
      - get
  - apiGroups:
      - ""
    resources:
      - services
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - ""
    resources:
      - events
    verbs:
      - create
      - patch
  - apiGroups:
      - "extensions"
      - "networking.k8s.io"
    resources:
      - ingresses
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - "extensions"
      - "networking.k8s.io"
    resources:
      - ingresses/status
    verbs:
      - update

---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: Role
metadata:
  name: nginx-ingress-role
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
rules:
  - apiGroups:
      - ""
    resources:
      - configmaps
      - pods
      - secrets
      - namespaces
    verbs:
      - get
  - apiGroups:
      - ""
    resources:
      - configmaps
    resourceNames:
      # Defaults to "<election-id>-<ingress-class>"
      # Here: "<ingress-controller-leader>-<nginx>"
      # This has to be adapted if you change either parameter
      # when launching the nginx-ingress-controller.
      - "ingress-controller-leader-nginx"
    verbs:
      - get
      - update
  - apiGroups:
      - ""
    resources:
      - configmaps
    verbs:
      - create
  - apiGroups:
      - ""
    resources:
      - endpoints
    verbs:
      - get

---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: RoleBinding
metadata:
  name: nginx-ingress-role-nisa-binding
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: nginx-ingress-role
subjects:
  - kind: ServiceAccount
    name: nginx-ingress-serviceaccount
    namespace: ingress-nginx

---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: nginx-ingress-clusterrole-nisa-binding
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: nginx-ingress-clusterrole
subjects:
  - kind: ServiceAccount
    name: nginx-ingress-serviceaccount
    namespace: ingress-nginx

---

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: nginx-ingress-controller
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: ingress-nginx
      app.kubernetes.io/part-of: ingress-nginx
  template:
    metadata:
      labels:
        app.kubernetes.io/name: ingress-nginx
        app.kubernetes.io/part-of: ingress-nginx
      annotations:
        prometheus.io/port: "10254"
        prometheus.io/scrape: "true"
    spec:
      # wait up to five minutes for the drain of connections
      terminationGracePeriodSeconds: 300
      serviceAccountName: nginx-ingress-serviceaccount
      nodeSelector:
        kubernetes.io/os: linux
      containers:
        - name: nginx-ingress-controller
          image: quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.28.0
          args:
            - /nginx-ingress-controller
            - --configmap=$(POD_NAMESPACE)/nginx-configuration
            - --tcp-services-configmap=$(POD_NAMESPACE)/tcp-services
            - --udp-services-configmap=$(POD_NAMESPACE)/udp-services
            - --publish-service=$(POD_NAMESPACE)/ingress-nginx
            - --annotations-prefix=nginx.ingress.kubernetes.io
          securityContext:
            allowPrivilegeEscalation: true
            capabilities:
              drop:
                - ALL
              add:
                - NET_BIND_SERVICE
            # www-data -> 101
            runAsUser: 101
          env:
            - name: POD_NAME
              valueFrom:
                fieldRef:
                  fieldPath: metadata.name
            - name: POD_NAMESPACE
              valueFrom:
                fieldRef:
                  fieldPath: metadata.namespace
          ports:
            - name: http
              containerPort: 80
              hostPort: 30080
              protocol: TCP
            - name: https
              containerPort: 443
              hostPort: 30443
              protocol: TCP
          livenessProbe:
            failureThreshold: 3
            httpGet:
              path: /healthz
              port: 10254
              scheme: HTTP
            initialDelaySeconds: 10
            periodSeconds: 10
            successThreshold: 1
            timeoutSeconds: 10
          readinessProbe:
            failureThreshold: 3
            httpGet:
              path: /healthz
              port: 10254
              scheme: HTTP
            periodSeconds: 10
            successThreshold: 1
            timeoutSeconds: 10
          lifecycle:
            preStop:
              exec:
                command:
                  - /wait-shutdown

---

apiVersion: v1
kind: LimitRange
metadata:
  name: ingress-nginx
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
spec:
  limits:
  - default:
    min:
      memory: 90Mi
      cpu: 100m
    type: Container
复制代码

提示:只需把对应控制器类型更改为DaemonSet,在pod模板中spec字段下把replicas去掉;在spec.template.spec.containers.ports字段中加上nodePort字段指定要把容器的端口映射到宿主机上某个端口;如果暴露的端口是非标准端口,在对应k8s集群外部我们还需要部署反代,比如使用nginx,haproxy,lvs;

使用ds控制器管理ingress controller pod在ds控制器资源配置中使用hostNetwork

复制代码
apiVersion: v1
kind: Namespace
metadata:
  name: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx

---

kind: ConfigMap
apiVersion: v1
metadata:
  name: nginx-configuration
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx

---
kind: ConfigMap
apiVersion: v1
metadata:
  name: tcp-services
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx

---
kind: ConfigMap
apiVersion: v1
metadata:
  name: udp-services
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx

---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: nginx-ingress-serviceaccount
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx

---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
  name: nginx-ingress-clusterrole
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
rules:
  - apiGroups:
      - ""
    resources:
      - configmaps
      - endpoints
      - nodes
      - pods
      - secrets
    verbs:
      - list
      - watch
  - apiGroups:
      - ""
    resources:
      - nodes
    verbs:
      - get
  - apiGroups:
      - ""
    resources:
      - services
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - ""
    resources:
      - events
    verbs:
      - create
      - patch
  - apiGroups:
      - "extensions"
      - "networking.k8s.io"
    resources:
      - ingresses
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - "extensions"
      - "networking.k8s.io"
    resources:
      - ingresses/status
    verbs:
      - update

---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: Role
metadata:
  name: nginx-ingress-role
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
rules:
  - apiGroups:
      - ""
    resources:
      - configmaps
      - pods
      - secrets
      - namespaces
    verbs:
      - get
  - apiGroups:
      - ""
    resources:
      - configmaps
    resourceNames:
      # Defaults to "<election-id>-<ingress-class>"
      # Here: "<ingress-controller-leader>-<nginx>"
      # This has to be adapted if you change either parameter
      # when launching the nginx-ingress-controller.
      - "ingress-controller-leader-nginx"
    verbs:
      - get
      - update
  - apiGroups:
      - ""
    resources:
      - configmaps
    verbs:
      - create
  - apiGroups:
      - ""
    resources:
      - endpoints
    verbs:
      - get

---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: RoleBinding
metadata:
  name: nginx-ingress-role-nisa-binding
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: nginx-ingress-role
subjects:
  - kind: ServiceAccount
    name: nginx-ingress-serviceaccount
    namespace: ingress-nginx

---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: nginx-ingress-clusterrole-nisa-binding
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: nginx-ingress-clusterrole
subjects:
  - kind: ServiceAccount
    name: nginx-ingress-serviceaccount
    namespace: ingress-nginx

---

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: nginx-ingress-controller
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: ingress-nginx
      app.kubernetes.io/part-of: ingress-nginx
  template:
    metadata:
      labels:
        app.kubernetes.io/name: ingress-nginx
        app.kubernetes.io/part-of: ingress-nginx
      annotations:
        prometheus.io/port: "10254"
        prometheus.io/scrape: "true"
    spec:
      # wait up to five minutes for the drain of connections
      terminationGracePeriodSeconds: 300
      serviceAccountName: nginx-ingress-serviceaccount
      nodeSelector:
        kubernetes.io/os: linux
      hostNetwork: true
      containers:
        - name: nginx-ingress-controller
          image: quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.28.0
          args:
            - /nginx-ingress-controller
            - --configmap=$(POD_NAMESPACE)/nginx-configuration
            - --tcp-services-configmap=$(POD_NAMESPACE)/tcp-services
            - --udp-services-configmap=$(POD_NAMESPACE)/udp-services
            - --publish-service=$(POD_NAMESPACE)/ingress-nginx
            - --annotations-prefix=nginx.ingress.kubernetes.io
          securityContext:
            allowPrivilegeEscalation: true
            capabilities:
              drop:
                - ALL
              add:
                - NET_BIND_SERVICE
            # www-data -> 101
            runAsUser: 101
          env:
            - name: POD_NAME
              valueFrom:
                fieldRef:
                  fieldPath: metadata.name
            - name: POD_NAMESPACE
              valueFrom:
                fieldRef:
                  fieldPath: metadata.namespace
          ports:
            - name: http
              containerPort: 80
              protocol: TCP
            - name: https
              containerPort: 443
              protocol: TCP
          livenessProbe:
            failureThreshold: 3
            httpGet:
              path: /healthz
              port: 10254
              scheme: HTTP
            initialDelaySeconds: 10
            periodSeconds: 10
            successThreshold: 1
            timeoutSeconds: 10
          readinessProbe:
            failureThreshold: 3
            httpGet:
              path: /healthz
              port: 10254
              scheme: HTTP
            periodSeconds: 10
            successThreshold: 1
            timeoutSeconds: 10
          lifecycle:
            preStop:
              exec:
                command:
                  - /wait-shutdown

---

apiVersion: v1
kind: LimitRange
metadata:
  name: ingress-nginx
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
spec:
  limits:
  - default:
    min:
      memory: 90Mi
      cpu: 100m
    type: Container
复制代码

提示:把对应控制器类型更改外DaemonSet,在pod模板中spec字段下的replicas字段去掉;在spec.template.spec字段下加上hostNetwork: true即可;以上两种使用ds控制器管理ingress controller pod也可以使用node选择器,来筛选在某个节点上创建ingress controller pod;

使用deploy控制器管理ingress controller pod,就直接应用mandatory.yaml即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[root@master01 ~]# kubectl apply -f mandatory.yaml
namespace/ingress-nginx created
configmap/nginx-configuration created
configmap/tcp-services created
configmap/udp-services created
serviceaccount/nginx-ingress-serviceaccount created
Warning: rbac.authorization.k8s.io/v1beta1 ClusterRole is deprecated in v1.17+, unavailable in v1.22+; use rbac.authorization.k8s.io/v1 ClusterRole
clusterrole.rbac.authorization.k8s.io/nginx-ingress-clusterrole created
Warning: rbac.authorization.k8s.io/v1beta1 Role is deprecated in v1.17+, unavailable in v1.22+; use rbac.authorization.k8s.io/v1 Role
role.rbac.authorization.k8s.io/nginx-ingress-role created
Warning: rbac.authorization.k8s.io/v1beta1 RoleBinding is deprecated in v1.17+, unavailable in v1.22+; use rbac.authorization.k8s.io/v1 RoleBinding
rolebinding.rbac.authorization.k8s.io/nginx-ingress-role-nisa-binding created
Warning: rbac.authorization.k8s.io/v1beta1 ClusterRoleBinding is deprecated in v1.17+, unavailable in v1.22+; use rbac.authorization.k8s.io/v1 ClusterRoleBinding
clusterrolebinding.rbac.authorization.k8s.io/nginx-ingress-clusterrole-nisa-binding created
deployment.apps/nginx-ingress-controller created
limitrange/ingress-nginx created
[root@master01 ~]#

查看应用资源清单创建的资源对象

1
2
3
4
5
6
7
8
9
10
[root@master01 ~]# kubectl get all -n ingress-nginx
NAME                                            READY   STATUS    RESTARTS   AGE
pod/nginx-ingress-controller-5466cb8999-4lsjc   1/1     Running   0          80s
NAME                                       READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/nginx-ingress-controller   1/1     1            1           80s
NAME                                                  DESIRED   CURRENT   READY   AGE
replicaset.apps/nginx-ingress-controller-5466cb8999   1         1         1       80s
[root@master01 ~]#

提示:可以看到在ingress-nginx名称空间下创建了一个deploy控制器,对应控制器创建了一个nginx-ingress-controller控制器pod;但是此pod现在不能被外部客户端访问到,我们需要创建一个service来引入外部流量到此pod上;

查看pod标签

1
2
3
4
[root@master01 ~]# kubectl get pod -n ingress-nginx --show-labels
NAME                                        READY   STATUS    RESTARTS   AGE     LABELS
nginx-ingress-controller-5466cb8999-4lsjc   1/1     Running   0          4m38s   app.kubernetes.io/name=ingress-nginx,app.kubernetes.io/part-of=ingress-nginx,pod-template-hash=5466cb8999
[root@master01 ~]#

根据上述标签来写一个创建ingress-service资源的配置清单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[root@master01 ~]# cat ingress-nginx-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: ingress-nginx-svc
  namespace: ingress-nginx
spec:
  type: NodePort
  ports:
    - port: 80
      name: http
      nodePort: 30080
    - port: 443
      name: https
      nodePort: 30443
  selector:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
[root@master01 ~]#

提示:以上配置清单主要把满足对应标签选择器的pod关联起来;并把对应pod的80和443端口分别映射到对应主机上的30080和30443端口;

应用配置清单

1
2
3
4
5
6
[root@master01 ~]# kubectl apply -f ingress-nginx-service.yaml
service/ingress-nginx-svc created
[root@master01 ~]# kubectl get svc -n ingress-nginx
NAME                TYPE       CLUSTER-IP    EXTERNAL-IP   PORT(S)                      AGE
ingress-nginx-svc   NodePort   10.98.4.208   <none>        80:30080/TCP,443:30443/TCP   13s
[root@master01 ~]#

访问集群任意节点ip的30080和30443端口,看看是否访问到对应pod?

提示:30080是能够正常访问的,只是它显示404,是因为我们没有对应的主页;

访问30443端口

提示:30443是一个https端口,所以访问必须用https协议访问,这里提示访问页面有风险是因为浏览器不信任证书引起的,我们可以点击高级,信任证书即可;同样30443端口也是返回404,是因为没有主页的原因;两个端口能够正常访问,说明我们在k8s上部署的ingress-nginx controller就部署好了;

ingress资源的使用

在k8s上创建一个deploy控制器,让其管理2个 ikubernetes/myapp:v1镜像运行的pod,然后再创建一个对应的service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
[root@master01 manifests]# cat myapp-demo.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
  namespace: default
spec:
  replicas: 2
  selector:
    matchLabels:
      app: myapp
      rel: stable
  template:
    metadata:
      namespace: default
      labels:
        app: myapp
        rel: stable
    spec:
      containers:
      - name: myapp
        image: ikubernetes/myapp:v1
---
apiVersion: v1
kind: Service
metadata:
  name: myapp
  namespace: default
spec:
  selector:
    app: myapp
    rel: stable
  ports:
  - name: http
    port: 80
    targetPort: 80
[root@master01 manifests]#

提示:一个清单中定义多个资源,需要用“—”来分割资源;

应用资源清单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
[root@master01 manifests]# kubectl apply -f myapp-demo.yaml
deployment.apps/myapp created
service/myapp created
[root@master01 manifests]# kubectl get pod -o wide
NAME                     READY   STATUS    RESTARTS   AGE   IP            NODE             NOMINATED NODE   READINESS GATES
myapp-6479b786f5-9d4mh   1/1     Running   0          11s   10.244.2.98   node02.k8s.org   <none>           <none>
myapp-6479b786f5-k252c   1/1     Running   0          11s   10.244.4.20   node04.k8s.org   <none>           <none>
[root@master01 manifests]# kubectl get svc
NAME         TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)   AGE
kubernetes   ClusterIP   10.96.0.1        <none>        443/TCP   4h52m
myapp        ClusterIP   10.105.208.218   <none>        80/TCP    21s
[root@master01 manifests]# kubectl describe svc myapp
Name:              myapp
Namespace:         default
Labels:            <none>
Annotations:       <none>
Selector:          app=myapp,rel=stable
Type:              ClusterIP
IP Families:       <none>
IP:                10.105.208.218
IPs:               10.105.208.218
Port:              http  80/TCP
TargetPort:        80/TCP
Endpoints:         10.244.2.98:80,10.244.4.20:80
Session Affinity:  None
Events:            <none>
[root@master01 manifests]#

创建ingress资源来反代以上资源

示例:创建ingress资源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[root@master01 manifests]# cat ingress-myapp.yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: ingress-myapp
  namespace: default
  annotations:
    kubernetes.io/ingress.class: "nginx"
spec:
  rules:
  - host: www.myapp.com
    http:
      paths:
      - path: /
        backend:
          serviceName: myapp
          servicePort: 80
[root@master01 manifests]#

提示:创建ingress资源apiVersion的值要写成extensions/v1beta1,kind为Ingress;对应metadata中的annotations的配置表示把ingress资源通知给那个类别的ingress controller,如果k8s集群上有多个类别的ingress controller时,这一项特别有用;在spec字段主要内嵌了三个字段,rules字段用来定义反代规则列表,其值为一个对象列表;其中rules字段里主要host和http字段;host用来指定虚拟主机的fqdn名称,如果不写表示匹配任意虚拟主机名称;http是用来定义指向后端的http选择器列表;其值为一个对象,里面只有一个paths字段,用于指定把请求映射到后端的某个路径;其值为一个对象列表;对应paths字段中可以定义path,用来指定映射后端的路径;backend用于指定后端pod的service,其值为一个对象;serviceName用于指定对应pod的service名称;servicePort用于指定后端服务的端口;以上配置表示把www.myapp.com这个虚拟主机的访问全部反代至服务名称为myapp端口为80的pod上;

应用配置清单

1
2
3
4
5
6
7
[root@master01 manifests]# kubectl apply -f ingress-myapp.yaml
Warning: extensions/v1beta1 Ingress is deprecated in v1.14+, unavailable in v1.22+; use networking.k8s.io/v1 Ingress
ingress.extensions/ingress-myapp created
[root@master01 manifests]# kubectl get ingress
NAME            CLASS    HOSTS           ADDRESS   PORTS   AGE
ingress-myapp   <none>   www.myapp.com             80      29s
[root@master01 manifests]#

查看ingress资源的详细信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[root@master01 manifests]# kubectl describe ingress ingress-myapp
Name:             ingress-myapp
Namespace:        default
Address:         
Default backend:  default-http-backend:80 (<error: endpoints "default-http-backend" not found>)
Rules:
  Host           Path  Backends
  ----           ----  --------
  www.myapp.com 
                 /   myapp:80 (10.244.2.98:80,10.244.4.20:80)
Annotations:     kubernetes.io/ingress.class: nginx
Events:
  Type    Reason  Age   From                      Message
  ----    ------  ----  ----                      -------
  Normal  CREATE  81s   nginx-ingress-controller  Ingress default/ingress-myapp
[root@master01 manifests]#

提示:可以看到对应满足service名称为myapp并且其端口为80的pod有两个;

进入ingress controller pod里,看看对应配置文件是否有www.myapp.com的配置?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[root@master01 manifests]# kubectl get pods -n ingress-nginx
NAME                                        READY   STATUS    RESTARTS   AGE
nginx-ingress-controller-5466cb8999-4lsjc   1/1     Running   0          78m
[root@master01 manifests]# kubectl exec -it -n ingress-nginx pod/nginx-ingress-controller-5466cb8999-4lsjc -- /bin/sh
/etc/nginx cd /etc/nginx/
/etc/nginx ls
fastcgi.conf            koi-utf                 modsecurity             owasp-modsecurity-crs   uwsgi_params.default
fastcgi.conf.default    koi-win                 modules                 scgi_params             win-utf
fastcgi_params          lua                     nginx.conf              scgi_params.default
fastcgi_params.default  mime.types              nginx.conf.default      template
geoip                   mime.types.default      opentracing.json        uwsgi_params
/etc/nginx grep "www.myapp.com" nginx.conf
        ## start server www.myapp.com
                server_name www.myapp.com ;
        ## end server www.myapp.com
/etc/nginx $

提示:可以看到在对应ingress-nginx 控制器pod中能够搜索到www.myapp.com的配置;说明我们定义的ingress资源已经被ingress-nginx controller 捕获;

用浏览器访问www.myapp.com看看是否能够访问到内容?

提示:使用www.myapp.com访问,需要确保对应域名能够正常解析到k8s集群任意一节点上;可以看到访问www.myapp.com:30080能够访问到对应pod内容;

删除ingress代理规则

1
2
3
4
5
6
[root@master01 manifests]# kubectl delete -f ingress-myapp.yaml
Warning: extensions/v1beta1 Ingress is deprecated in v1.14+, unavailable in v1.22+; use networking.k8s.io/v1 Ingress
ingress.extensions "ingress-myapp" deleted
[root@master01 manifests]# kubectl get ingress
No resources found in default namespace.
[root@master01 manifests]#

示例:配置基于url路径进行流量分发

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[root@master01 manifests]# cat ingress-myapp1.yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: ingress-myapp
  namespace: default
  annotations:
    kubernetes.io/ingress.class: "nginx"
    ingress.kubernetes.io/rewrite-target: /
spec:
  rules:
  - host: www.myapp.com
    http:
      paths:
      - path: /bbs
        backend:
          serviceName: myapp
          servicePort: 80
      - path: /blog
        backend:
          serviceName: myapp
          servicePort: 80
[root@master01 manifests]#

提示:以上配置表示把www.myapp.com/bbs反代到service名称为myapp并且端口为80的pod上;把www.myapp.com/blog反代到ervice名称为myapp并且端口为80的pod上;我这里是因为k8s上只有这一种应用,生成环境中按照对应的业务逻辑来反代对应url到对应pod上即可;

应用配置清单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[root@master01 manifests]# kubectl apply -f ingress-myapp1.yaml
Warning: extensions/v1beta1 Ingress is deprecated in v1.14+, unavailable in v1.22+; use networking.k8s.io/v1 Ingress
ingress.extensions/ingress-myapp created
[root@master01 manifests]# kubectl get ingress
NAME            CLASS    HOSTS           ADDRESS   PORTS   AGE
ingress-myapp   <none>   www.myapp.com             80      5s
[root@master01 manifests]# kubectl describe ingress ingress-myapp
Name:             ingress-myapp
Namespace:        default
Address:         
Default backend:  default-http-backend:80 (<error: endpoints "default-http-backend" not found>)
Rules:
  Host           Path  Backends
  ----           ----  --------
  www.myapp.com 
                 /bbs    myapp:80 (10.244.2.98:80,10.244.4.20:80)
                 /blog   myapp:80 (10.244.2.98:80,10.244.4.20:80)
Annotations:     ingress.kubernetes.io/rewrite-target: /
                 kubernetes.io/ingress.class: nginx
Events:
  Type    Reason  Age   From                      Message
  ----    ------  ----  ----                      -------
  Normal  CREATE  30s   nginx-ingress-controller  Ingress default/ingress-myapp
[root@master01 manifests]#

提示:可以看到对应ingress上就有两个url分别指向后端service名称为myapp端口为80的pod上;

访问对应url,看看是否访问到内容?

提示:这里访问不到内容的原因是对应pod内部并没有对应url的页面;

进入ingress controller pod内部,查看是否有对应配置?

提示:可以看到对应在ingress中定义的配置,都转为对应该ingress controller pod中的配置,说明我们定义基于url分发流量的ingress没有问题;

示例:定义ingress规则基于主机名称的虚拟主机

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
[root@master01 manifests]# cat ingress-myapp2.yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: ingress-myapp
  namespace: default
  annotations:
    kubernetes.io/ingress.class: "nginx"
    ingress.kubernetes.io/rewrite-target: /
spec:
  rules:
  - host: www.myapp.com
    http:
      paths:
      - path:
        backend:
          serviceName: myapp
          servicePort: 80
  - host: blog.myapp.com
    http:
      paths:
      - path:
        backend:
          serviceName: myapp
          servicePort: 80
[root@master01 manifests]#

提示:以上配置表示把www.myapp.com这个虚拟主机名称的访问流量分发至service名称为myapp端口为80的pod上;把blog.myapp.com的流量分发至至service名称为myapp端口为80的pod上;生成环境按照对应的service名称来分发即可;

应用配置清单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
[root@master01 manifests]# kubectl apply -f ingress-myapp2.yaml
Warning: extensions/v1beta1 Ingress is deprecated in v1.14+, unavailable in v1.22+; use networking.k8s.io/v1 Ingress
ingress.extensions/ingress-myapp created
[root@master01 manifests]# kubectl get ingress
NAME            CLASS    HOSTS                          ADDRESS   PORTS   AGE
ingress-myapp   <none>   www.myapp.com,blog.myapp.com             80      16s
[root@master01 manifests]# kubectl describe ingress ingress-myapp
Name:             ingress-myapp
Namespace:        default
Address:         
Default backend:  default-http-backend:80 (<error: endpoints "default-http-backend" not found>)
Rules:
  Host            Path  Backends
  ----            ----  --------
  www.myapp.com  
                     myapp:80 (10.244.2.98:80,10.244.4.20:80)
  blog.myapp.com 
                     myapp:80 (10.244.2.98:80,10.244.4.20:80)
Annotations:      ingress.kubernetes.io/rewrite-target: /
                  kubernetes.io/ingress.class: nginx
Events:
  Type    Reason  Age   From                      Message
  ----    ------  ----  ----                      -------
  Normal  CREATE  32s   nginx-ingress-controller  Ingress default/ingress-myapp
[root@master01 manifests]#

验证配置信息

访问对应虚拟主机,看看是否能够访问对应pod?

提示:可以看到两个虚拟主机名称都可以正常访问到,对应也做了调度;

示例:创建tls类型的ingress资源

创建证书

1
2
3
4
5
6
7
[root@master01 manifests]# openssl genrsa -out tls.key 2048
Generating RSA private key, 2048 bit long modulus
.........................................+++
........+++
e is 65537 (0x10001)
[root@master01 manifests]# openssl req -x509 -key tls.key -out tls.crt -subj /C=CN/ST=SiChuan/L=GuangYuan/O=Test/CN=www.myapp.com -days 3650 
[root@master01 manifests]#

提示:以上两条命令创建了一个名为tls.key的私钥和一个自签名证书,其名为tls.crt;

创建Secret资源

1
2
3
4
5
6
7
[root@master01 manifests]# kubectl create secret tls www-myapp-com-ingress-secret --cert=./tls.crt --key=./tls.key
secret/www-myapp-com-ingress-secret created
[root@master01 manifests]# kubectl get secret
NAME                           TYPE                                  DATA   AGE
default-token-xvd4c            kubernetes.io/service-account-token   3      13d
www-myapp-com-ingress-secret   kubernetes.io/tls                     2      21s
[root@master01 manifests]#

提示:在ingress控制器上配置https主机时,不能直接使用私钥和证书文件,而是需要使用secret资源对象来传递相关数据;

定义tls类型ingress资源清单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[root@master01 manifests]# cat www-myapp-com-ingress-secret.yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: ingress-myapp-tls
  namespace: default
  annotations:
    kubernetes.io/ingress.class: "nginx"
spec:
  tls:
  - hosts:
    - www.myapp.com
    secretName: www-myapp-com-ingress-secret
  rules:
  - host: www.myapp.com
    http:
      paths:
      - path: /
        backend:
          serviceName: myapp
          servicePort: 80
[root@master01 manifests]#

提示:定义tls类型ingress资源清单,需要在spec字段下用tls字段来指定对应主机名称,以及secret资源对象的名称;

应用资源清单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
[root@master01 manifests]# kubectl apply -f www-myapp-com-ingress-secret.yaml
Warning: extensions/v1beta1 Ingress is deprecated in v1.14+, unavailable in v1.22+; use networking.k8s.io/v1 Ingress
ingress.extensions/ingress-myapp-tls created
[root@master01 manifests]# kubectl get ingress
NAME                CLASS    HOSTS                          ADDRESS   PORTS     AGE
ingress-myapp       <none>   www.myapp.com,blog.myapp.com             80        31m
ingress-myapp-tls   <none>   www.myapp.com                            80, 443   8s
[root@master01 manifests]# kubectl describe ingress ingress-myapp-tls
Name:             ingress-myapp-tls
Namespace:        default
Address:         
Default backend:  default-http-backend:80 (<error: endpoints "default-http-backend" not found>)
TLS:
  www-myapp-com-ingress-secret terminates www.myapp.com
Rules:
  Host           Path  Backends
  ----           ----  --------
  www.myapp.com 
                 /   myapp:80 (10.244.2.98:80,10.244.4.20:80)
Annotations:     kubernetes.io/ingress.class: nginx
Events:
  Type    Reason  Age   From                      Message
  ----    ------  ----  ----                      -------
  Normal  CREATE  26s   nginx-ingress-controller  Ingress default/ingress-myapp-tls
[root@master01 manifests]#

验证:访问对应虚拟主机名称,看看对应的https端口是否能够正常访问到内容?

提示:可以看到使用https协议访问对应的30443端口能够正常访问到对应后端pod提供的内容;

C#使用RabbitMQ(转) - 廖先生 - 博客园

mikel阅读(712)

来源: C#使用RabbitMQ(转) – 廖先生 – 博客园

1. 说明

在企业应用系统领域,会面对不同系统之间的通信、集成与整合,尤其当面临异构系统时,这种分布式的调用与通信变得越发重要。其次,系统中一般会有很多对实时性要求不高的但是执行起来比较较耗时的地方,比如发送短信,邮件提醒,更新文章阅读计数,记录用户操作日志等等,如果实时处理的话,在用户访问量比较大的情况下,对系统压力比较大。

面对这些问题,我们一般会将这些请求,放在消息队列MQ中处理;异构系统之间使用消息进行通讯。

MQ全称为Message Queue, 消息队列(MQ)是一种应用程序对应用程序的通信方法。应用程序通过读写出入队列的消息(针对应用程序的数据)来通信,而无需专用连接来链接它们。消息传递指的是程序之间通过在消息中发送数据进行通信,而不是通过直接调用彼此来通信,直接调用通常是用于诸如远程过程调用的技术。排队指的是应用程序通过 队列来通信。队列的使用除去了接收和发送应用程序同时执行的要求。

MQ是消费-生产者模型的一个典型的代表,一端往消息队列中不断写入消息,而另一端则可以读取或者订阅队列中的消息。

RabbitMQ是一个在AMQP基础上完整的,可复用的企业消息系统。他遵循Mozilla Public License开源协议。

消息传递相较文件传递与远程过程调用(RPC)而言,似乎更胜一筹,因为它具有更好的平台无关性,并能够很好地支持并发与异步调用。所以如果系统中出现了如下情况:

  • 对操作的实时性要求不高,而需要执行的任务极为耗时;
  • 存在异构系统间的整合;

一般的可以考虑引入消息队列。对于第一种情况,常常会选择消息队列来处理执行时间较长的任务。引入的消息队列就成了消息处理的缓冲区。消息队列引入的异步通信机制,使得发送方和接收方都不用等待对方返回成功消息,就可以继续执行下面的代码,从而提高了数据处理的能力。尤其是当访问量和数据流量较大的情况下,就可以结合消息队列与后台任务,通过避开高峰期对大数据进行处理,就可以有效降低数据库处理数据的负荷。

本文简单介绍在RabbitMQ这一消息代理工具,以及在.NET中如何使用RabbitMQ.

2. 搭建环境

2.1 安装Erlang语言运行环境

由于RabbitMQ使用Erlang语言编写,所以先安装Erlang语言运行环境。具体移步博客:windows配置Erlang环境

2.2 安装RabbitMQ服务端

地址 http://www.rabbitmq.com/

下载安装。

使RabbitMQ以Windows Service的方式在后台运行:打开cmd切换到sbin目录下执行

rabbitmq-service install
rabbitmq-service enable
rabbitmq-service start

现在RabbitMQ的服务端已经启动起来了。

要查看和控制RabbitMQ服务端的状态,可以用rabbitmqctl这个脚本。

比如查看状态:

rabbitmqctl status

假如显示node没有连接上,需要到C:\Windows目录下,将.erlang.cookie文件,拷贝到用户目录下 C:\Users\{用户名},这是Erlang的Cookie文件,允许与Erlang进行交互。

使用命令查看用户:

rabbitmqctl list_users

RabbitMQ会为我们创建默认的用户名guest和密码guest,guest默认拥有RabbitMQ的所有权限。

一般的,我们需要新建一个我们自己的用户,设置密码,并授予权限,并将其设置为管理员,可以使用下面的命令来执行这一操作:

rabbitmqctl  add_user  JC JayChou   //创建用户JC密码为JayChou
rabbitmqctl  set_permissions  JC ".*"  ".*"  ".*"    //赋予JC读写所有消息队列的权限
rabbitmqctl  set_user_tags JC administrator    //分配用户组

修改JC密码为123:

rabbitmqctl change_password JC  123

删除用户JC:

rabbitmqctl delete_user  JC

也可以开启rabbitmq_management插件,在web界面查看和管理RabbitMQ服务

rabbitmq-plugins enable rabbitmq_management

 

2.3下载RabbitMQ的Client端dll

下载地址:http://www.rabbitmq.com/releases/rabbitmq-dotnet-client/

本人下载了这个 rabbitmq-dotnet-client-3.6.6-dotnet-4.5.zip

解压,我们需要的是这个文件,以后会引用到vs的项目中:

3.使用

3.1在使用RabitMQ之前,先对几个概念做一下说明

 

RabbitMQ是一个消息代理。他从消息生产者(producers)那里接收消息,然后把消息送给消息消费者(consumer)在发送和接受之间,他能够根据设置的规则进行路由,缓存和持久化。

一般提到RabbitMQ和消息,都用到一些专有名词。

  • 生产(Producing)意思就是发送。发送消息的程序就是一个生产者(producer)。我们一般用”P”来表示:

producer

  • 队列(queue)就是邮箱的名称。消息通过你的应用程序和RabbitMQ进行传输,它们只能存储在队列(queue)中。 队列(queue)容量没有限制,你要存储多少消息都可以——基本上是一个无限的缓冲区。多个生产者(producers)能够把消息发送给同一个队列,同样,多个消费者(consumers)也能从同一个队列(queue)中获取数据。队列可以画成这样(图上是队列的名称):

queue

  • 消费(Consuming)和获取消息是一样的意思。一个消费者(consumer)就是一个等待获取消息的程序。我们把它画作”C”:

consumer

通常,消息生产者,消息消费者和消息代理不在同一台机器上。

3.2 Hello Word

下面来展示简单的RabbitMQ的使用:

rabbitmq hello world

3.2.1 首先创建名为ProjectSend的控制台项目,需要引用RabbitMQ.Client.dll。这个程序作为Producer生产者,用来发送数据:

复制代码
static void Main(string[] args)
    {
        var factory = new ConnectionFactory();
        factory.HostName = "localhost";//RabbitMQ服务在本地运行
        factory.UserName = "guest";//用户名
        factory.Password = "guest";//密码

        using (var connection = factory.CreateConnection())
        {
            using (var channel = connection.CreateModel())
            {
                channel.QueueDeclare("hello", false, false, false, null);//创建一个名称为hello的消息队列
                string message = "Hello World"; //传递的消息内容
                var body = Encoding.UTF8.GetBytes(message);
                channel.BasicPublish("", "hello", null, body); //开始传递
                Console.WriteLine("已发送: {0}", message);
          Console.ReadLine();
            }
        }
    }
复制代码

 

首先,需要创建一个ConnectionFactory,设置目标,由于是在本机,所以设置为localhost,如果RabbitMQ不在本机,只需要设置目标机器的IP地址或者机器名称即可,然后设置前面创建的用户名和密码。

紧接着要创建一个Channel,如果要发送消息,需要创建一个队列,然后将消息发布到这个队列中。在创建队列的时候,只有RabbitMQ上该队列不存在,才会去创建。消息是以二进制数组的形式传输的,所以如果消息是实体对象的话,需要序列化和然后转化为二进制数组。

现在客户端发送代码已经写好了,运行之后,消息会发布到RabbitMQ的消息队列中,现在需要编写服务端的代码连接到RabbitMQ上去获取这些消息。

3.2.2创建名为ProjectReceive的控制台项目,引用RabbitMQ.Client.dll。作为Consumer消费者,用来接收数据:

复制代码
static void Main(string[] args)
        {
            var factory = new ConnectionFactory();
            factory.HostName = "localhost";
            factory.UserName = "guest";
            factory.Password = "guest";

            using (var connection = factory.CreateConnection())
            {
                using (var channel = connection.CreateModel())
                {
                    channel.QueueDeclare("hello", false, false, false, null);

                    var consumer = new EventingBasicConsumer(channel);
                    channel.BasicConsume("hello", false, consumer);
                    consumer.Received += (model, ea) =>
                    {
                        var body = ea.Body;
                        var message = Encoding.UTF8.GetString(body); 
                        Console.WriteLine("已接收: {0}", message);   
                    };
                    Console.ReadLine(); 
                }
            }
        }
复制代码

和发送一样,首先需要定义连接,然后声明消息队列。要接收消息,需要定义一个Consume,然后在接收消息的事件中处理数据。

3.2.3 现在发送和接收的客户端都写好了,让我们编译执行起来

发送消息:

现在,名为hello的消息队列中,发送了一条消息。这条消息存储到了RabbitMQ的服务器上了。使用rabbitmqctl 的list_queues可以查看所有的消息队列,以及里面的消息个数,可以看到,目前Rabbitmq上只有一个消息队列,里面只有一条消息:

也可以在web管理界面查看此queue的相关信息:

 

接收消息:

既然消息已经被接收了,那我们再来看queue的内容:

可见,消息中的内容在接收之后已被删除了。

3.3 工作队列

前面的例子展示了如何在指定的消息队列发送和接收消息。

现在我们创建一个工作队列(work queue)来将一些耗时的任务分发给多个工作者(workers):

rabbitmq-work queue

工作队列(work queues, 又称任务队列Task Queues)的主要思想是为了避免立即执行并等待一些占用大量资源、时间的操作完成。而是把任务(Task)当作消息发送到队列中,稍后处理。一个运行在后台的工作者(worker)进程就会取出任务然后处理。当运行多个工作者(workers)时,任务会在它们之间共享。

这个在网络应用中非常有用,它可以在短暂的HTTP请求中处理一些复杂的任务。在一些实时性要求不太高的地方,我们可以处理完主要操作之后,以消息的方式来处理其他的不紧要的操作,比如写日志等等。

准备

在第一部分,发送了一个包含“Hello World!”的字符串消息。现在发送一些字符串,把这些字符串当作复杂的任务。这里使用time.sleep()函数来模拟耗时的任务。在字符串中加上点号(.)来表示任务的复杂程度,一个点(.)将会耗时1秒钟。比如”Hello…”就会耗时3秒钟。

对之前示例的send.cs做些简单的调整,以便可以发送随意的消息。这个程序会按照计划发送任务到我们的工作队列中。

复制代码
static void Main(string[] args)
{
    var factory = new ConnectionFactory();
    factory.HostName = "localhost";
    factory.UserName = "yy";
    factory.Password = "hello!";

    using (var connection = factory.CreateConnection())
    {
        using (var channel = connection.CreateModel())
        {
            channel.QueueDeclare("hello", false, false, false, null);
            string message = GetMessage(args);
            var properties = channel.CreateBasicProperties();
            properties.DeliveryMode = 2;

            var body = Encoding.UTF8.GetBytes(message);
            channel.BasicPublish("", "hello", properties, body);
            Console.WriteLine(" set {0}", message);
        }
    }

    Console.ReadKey();
}

private static string GetMessage(string[] args)
{
    return ((args.Length > 0) ? string.Join(" ", args) : "Hello World!");
}
复制代码

 

接着我们修改接收端,让他根据消息中的逗点的个数来Sleep对应的秒数:

复制代码
static void Main(string[] args)
{
    var factory = new ConnectionFactory();
    factory.HostName = "localhost";
    factory.UserName = "yy";
    factory.Password = "hello!";

    using (var connection = factory.CreateConnection())
    {
        using (var channel = connection.CreateModel())
        {
            channel.QueueDeclare("hello", false, false, false, null);

            var consumer = new QueueingBasicConsumer(channel);
            channel.BasicConsume("hello", true, consumer);

            while (true)
            {
                var ea = (BasicDeliverEventArgs)consumer.Queue.Dequeue();

                var body = ea.Body;
                var message = Encoding.UTF8.GetString(body);

                int dots = message.Split('.').Length - 1;
                Thread.Sleep(dots * 1000);
                        
                Console.WriteLine("Received {0}", message);
                Console.WriteLine("Done");
            }
        }
    }
}
复制代码

 

轮询分发

使用工作队列的一个好处就是它能够并行的处理队列。如果堆积了很多任务,我们只需要添加更多的工作者(workers)就可以了,扩展很简单。

现在,我们先启动两个接收端,等待接受消息,然后启动一个发送端开始发送消息。

Send message queue

在cmd条件下,发送了5条消息,每条消息后面的逗点表示该消息需要执行的时长,来模拟耗时的操作。

然后可以看到,两个接收端依次接收到了发出的消息:

receive message queue

默认,RabbitMQ会将每个消息按照顺序依次分发给下一个消费者。所以每个消费者接收到的消息个数大致是平均的。 这种消息分发的方式称之为轮询(round-robin)。

3.4 消息响应

当处理一个比较耗时得任务的时候,也许想知道消费者(consumers)是否运行到一半就挂掉。在当前的代码中,当RabbitMQ将消息发送给消费者(consumers)之后,马上就会将该消息从队列中移除。此时,如果把处理这个消息的工作者(worker)停掉,正在处理的这条消息就会丢失。同时,所有发送到这个工作者的还没有处理的消息都会丢失。

我们不想丢失任何任务消息。如果一个工作者(worker)挂掉了,我们希望该消息会重新发送给其他的工作者(worker)。

为了防止消息丢失,RabbitMQ提供了消息响应(acknowledgments)机制。消费者会通过一个ack(响应),告诉RabbitMQ已经收到并处理了某条消息,然后RabbitMQ才会释放并删除这条消息。

如果消费者(consumer)挂掉了,没有发送响应,RabbitMQ就会认为消息没有被完全处理,然后重新发送给其他消费者(consumer)。这样,即使工作者(workers)偶尔的挂掉,也不会丢失消息。

消息是没有超时这个概念的;当工作者与它断开连的时候,RabbitMQ会重新发送消息。这样在处理一个耗时非常长的消息任务的时候就不会出问题了。

消息响应默认是开启的。在之前的例子中使用了no_ack=True标识把它关闭。是时候移除这个标识了,当工作者(worker)完成了任务,就发送一个响应。

复制代码
channel.BasicConsume("hello", false, consumer);

while (true)
{
    var ea = (BasicDeliverEventArgs)consumer.Queue.Dequeue();

    var body = ea.Body;
    var message = Encoding.UTF8.GetString(body);

    int dots = message.Split('.').Length - 1;
    Thread.Sleep(dots * 1000);

    Console.WriteLine("Received {0}", message);
    Console.WriteLine("Done");

    channel.BasicAck(ea.DeliveryTag, false);
}
复制代码

 

现在,可以保证,即使正在处理消息的工作者被停掉,这些消息也不会丢失,所有没有被应答的消息会被重新发送给其他工作者.

一个很常见的错误就是忘掉了BasicAck这个方法,这个错误很常见,但是后果很严重. 当客户端退出时,待处理的消息就会被重新分发,但是RabitMQ会消耗越来越多的内存,因为这些没有被应答的消息不能够被释放。调试这种case,可以使用rabbitmqct打印messages_unacknoledged字段。

rabbitmqctl list_queues name messages_ready messages_unacknowledged
Listing queues ...
hello    0       0
...done.

 

3.5 消息持久化

前面已经搞定了即使消费者down掉,任务也不会丢失,但是,如果RabbitMQ Server停掉了,那么这些消息还是会丢失。

当RabbitMQ Server 关闭或者崩溃,那么里面存储的队列和消息默认是不会保存下来的。如果要让RabbitMQ保存住消息,需要在两个地方同时设置:需要保证队列和消息都是持久化的。

首先,要保证RabbitMQ不会丢失队列,所以要做如下设置:

bool durable = true;
channel.QueueDeclare("hello", durable, false, false, null);

 

虽然在语法上是正确的,但是在目前阶段是不正确的,因为我们之前已经定义了一个非持久化的hello队列。RabbitMQ不允许我们使用不同的参数重新定义一个已经存在的同名队列,如果这样做就会报错。现在,定义另外一个不同名称的队列:

bool durable = true;
channel.queueDeclare("task_queue", durable, false, false, null);

 

queueDeclare 这个改动需要在发送端和接收端同时设置。

现在保证了task_queue这个消息队列即使在RabbitMQ Server重启之后,队列也不会丢失。 然后需要保证消息也是持久化的, 这可以通过设置IBasicProperties.SetPersistent 为true来实现:

var properties = channel.CreateBasicProperties();
properties.SetPersistent(true);

 

需要注意的是,将消息设置为持久化并不能完全保证消息不丢失。虽然他告诉RabbitMQ将消息保存到磁盘上,但是在RabbitMQ接收到消息和将其保存到磁盘上这之间仍然有一个小的时间窗口。 RabbitMQ 可能只是将消息保存到了缓存中,并没有将其写入到磁盘上。持久化是不能够一定保证的,但是对于一个简单任务队列来说已经足够。如果需要消息队列持久化的强保证,可以使用publisher confirms

3.6 公平分发

你可能会注意到,消息的分发可能并没有如我们想要的那样公平分配。比如,对于两个工作者。当奇数个消息的任务比较重,但是偶数个消息任务比较轻时,奇数个工作者始终处理忙碌状态,而偶数个工作者始终处理空闲状态。但是RabbitMQ并不知道这些,他仍然会平均依次的分发消息。

为了改变这一状态,我们可以使用basicQos方法,设置perfetchCount=1 。这样就告诉RabbitMQ 不要在同一时间给一个工作者发送多于1个的消息,或者换句话说。在一个工作者还在处理消息,并且没有响应消息之前,不要给他分发新的消息。相反,将这条新的消息发送给下一个不那么忙碌的工作者。

channel.BasicQos(0, 1, false);

 

3.7 完整实例

现在将所有这些放在一起:

发送端代码如下:

复制代码
static void Main(string[] args)
{
    var factory = new ConnectionFactory();
    factory.HostName = "localhost";
    factory.UserName = "yy";
    factory.Password = "hello!";

    using (var connection = factory.CreateConnection())
    {
        using (var channel = connection.CreateModel())
        {
                   
            bool durable = true;
            channel.QueueDeclare("task_queue", durable, false, false, null);
                    
            string message = GetMessage(args);
            var properties = channel.CreateBasicProperties();
            properties.SetPersistent(true);
                  

            var body = Encoding.UTF8.GetBytes(message);
            channel.BasicPublish("", "task_queue", properties, body);
            Console.WriteLine(" set {0}", message);
        }
    }

    Console.ReadKey();
}

private static string GetMessage(string[] args)
{
    return ((args.Length > 0) ? string.Join(" ", args) : "Hello World!");
}
复制代码

 

接收端代码如下:

复制代码
static void Main(string[] args)
{
    var factory = new ConnectionFactory();
    factory.HostName = "localhost";
    factory.UserName = "yy";
    factory.Password = "hello!";

    using (var connection = factory.CreateConnection())
    {
        using (var channel = connection.CreateModel())
        {
            bool durable = true;
            channel.QueueDeclare("task_queue", durable, false, false, null);
            channel.BasicQos(0, 1, false);

            var consumer = new QueueingBasicConsumer(channel);
            channel.BasicConsume("task_queue", false, consumer);

            while (true)
            {
                var ea = (BasicDeliverEventArgs)consumer.Queue.Dequeue();

                var body = ea.Body;
                var message = Encoding.UTF8.GetString(body);

                int dots = message.Split('.').Length - 1;
                Thread.Sleep(dots * 1000);

                Console.WriteLine("Received {0}", message);
                Console.WriteLine("Done");

                channel.BasicAck(ea.DeliveryTag, false);
            }
        }
    }
}
复制代码

 

4 管理界面

RabbitMQ管理界面,通过该界面可以查看RabbitMQ Server 当前的状态,该界面是以插件形式提供的,并且在安装RabbitMQ的时候已经自带了该插件。需要做的是在RabbitMQ控制台界面中启用该插件,命令如下:

rabbitmq-plugins enable rabbitmq_management

rabbitmq management

现在,在浏览器中输入 http://server-name:15672/ server-name换成机器地址或者域名,如果是本地的,直接用localhost(RabbitMQ 3.0之前版本端口号为55672)在输入之后,弹出登录界面,使用我们之前创建的用户登录。

RabbitMQ Web management .

在该界面上可以看到当前RabbitMQServer的所有状态。

5 总结

本文简单介绍了消息队列的相关概念,并介绍了RabbitMQ消息代理的基本原理以及在Windows 上如何安装RabbitMQ和在.NET中如何使用RabbitMQ。消息队列在构建分布式系统和提高系统的可扩展性和响应性方面有着很重要的作用,希望本文对您了解消息队列以及如何使用RabbitMQ有所帮助。

Redis实现中间件_brycegao321的博客-CSDN博客_redis中间件

mikel阅读(892)

来源: Redis实现中间件_brycegao321的博客-CSDN博客_redis中间件

Redis基本教程: http://www.runoob.com/redis/redis-tutorial.html

其实Redis很简单,就是充当缓存的作用, 不能替代MySQL(及其它)数据库。 做个比喻: 数据库就相当于硬盘,Redis就相当于内存, 在Redis里读写数据更快。 Redis一般作为中间件,服务将数据缓存到Redis里, 但必须要注意跟数据库的同步问题!!!

原则:先在Redis里查,如果查不到再去数据库查, 并保持结果到Redis里。

因为Redis使用关键字读写键值的, 如果多个团队使用同一个Redis服务, 那么很可能出现关键字冲突的问题。 解决办法有2个:

1、 为每个团队分配不同的关键字前缀, 例如 jkcom_user***,jkcom_sell***等。

2、 每个团队使用不同的database, 同一个database要保证key不同,不同database的key可以相同。 (PS

: 每个database都是独立的存储空间,  在redis.conf配置,默认有16个,即SELECT 0~SELECT 15。)

 

  1. # Set the number of databases. The default database is DB 0, you can select
  2. # a different one on a per-connection basis using SELECT <dbid> where
  3. # dbid is a number between 0 and ‘databases’-1
  4. databases 16

 

 

在redis目录里执行./src/redis-server启动redis服务, 默认使用redis.conf配置文件。

执行./src/redis-cli连接到客户端。

API说明:http://docs.spring.io/spring-data/redis/docs/current/api/

Maven包最新代码地址:

spring-data-redis:  https://mvnrepository.com/artifact/org.springframework.data/spring-data-redis
jedis:  https://mvnrepository.com/artifact/redis.clients/jedis

在SpringMVC的配置文件里添加redis的代码包, 版本号去文章上面仓库去查:
     创建redis的配置文件, Java代码会读取这个文件。 PS:在实际开发中, 要写多个配置文件分别对应开发、测试、预上线、线上环境等(通过配置springmvc的profiles标签切换)。
     这里有个坑: 如果要设置database, 必须先设置密码!!!
     下面代码可以拿到Jedis实例引用, 然后就可以调用它的增删改查方法了。
  1. public class RedisProvider {
  2. protected static JedisPool jedispool;
  3. static{
  4. ResourceBundle bundle = ResourceBundle.getBundle(“redis”); //读取redis.properties文件
  5. if (bundle == null) {
  6. throw new IllegalArgumentException(
  7. “[redis.properties] is not found!”);
  8. }
  9. try {
  10. JedisPoolConfig jedisconfig = new JedisPoolConfig();
  11. jedisconfig.setMaxIdle(Integer.valueOf(bundle
  12. .getString(“redis.pool.maxIdle”)));
  13. jedisconfig.setTestOnBorrow(Boolean.valueOf(bundle
  14. .getString(“redis.pool.testOnBorrow”)));
  15. jedisconfig.setTestOnReturn(Boolean.valueOf(bundle
  16. .getString(“redis.pool.testOnReturn”)));
  17. jedispool = new JedisPool(jedisconfig, bundle.getString(“redis.ip”),
  18. Integer.valueOf(bundle.getString(“redis.port”)),
  19. Integer.valueOf(bundle.getString(“redis.timeout”)),
  20. bundle.getString(“redis.password”),
  21. Integer.valueOf(bundle.getString(“redis.database”)));
  22. } catch (Exception ex) {
  23. ex.printStackTrace();
  24. }
  25. }
  26. public static Jedis getJedis() {
  27. Jedis jedis = null;
  28. try {
  29. jedis = jedispool.getResource();
  30. } catch (JedisConnectionException jce) {
  31. jce.printStackTrace();
  32. }
  33. return jedis;
  34. }
  35. }

编写最简单的读写字符串示例:

          OK, 成功!
          Redis还支持list,set,zset,hash等数据结构。
        实际生产环境至少部署主从redis服务器, 对于大数据量的服务要部署redis分布式集群(Codis,阿里redis集群或redis sentinel)。

Redis 与 MQ 的区别 - 逆水行舟,平原走马 - 博客园

mikel阅读(690)

来源: Redis 与 MQ 的区别 – 逆水行舟,平原走马 – 博客园

Redis是一个高性能的key-value数据库,它的出现很大程度补偿了memcached这类key-value存储的不足。虽然它是一个数据库系统,但本身支持MQ功能,完全可以当做一个轻量级的队列服务器使用。

不过,Redis只是提供一个高性能的、原子操作内存键值队,具有高速访问能力,虽可用做消息队列的存储,但是不具备消息队列的任何功能和逻辑,要作做为消息队列来实现的话,功能和逻辑要通过上层应用自己实现。

 

  Redis从2.0版本开始支持发布/订阅指令,发布者调用redis的publish方法往特定的channel发送消息,订阅者在初始化的时候要订阅到该channel,一旦有消息就会立即接收。
  Redis 消息推送(基于分布式 pub/sub)多用于实时性较高的消息推送,并不保证可靠。redis-pub/sub断电就清空,而使用redis-list作为消息推送虽然有持久化,但是也并非完全可靠不会丢。其他的mq和kafka保证可靠但有一些延迟(非实时系统没有保证延迟)。
  另外一点,redis 发布订阅除了表示不同的 topic 外,并不支持分组。但kafka是支持分组的,比如kafka中发布一个东西,多个订阅者可以分组,同一个组里只有一个订阅者会收到该消息,而这个可以用作负载均衡。

Redis发布订阅与rabbitmq的区别

1. 可靠性

redis :没有相应的机制保证消息的可靠消费,如果发布者发布一条消息,而没有对应的订阅者的话,这条消息将丢失,不会存在内存中;

rabbitmq:具有消息消费确认机制,如果发布一条消息,还没有消费者消费该队列,那么这条消息将一直存放在队列中,直到有消费者消费了该条消息,以此可以保证消息的可靠消费。

2. 实时性

redis:实时性高,redis作为高效的缓存服务器,所有数据都存在内存中,所以它具有更高的实时性

3. 消费者负载均衡:

rabbitmq队列可以被多个消费者同时监控消费,但是每一条消息只能被消费一次,由于rabbitmq的消费确认机制,因此它能够根据消费者的消费能力而调整它的负载;

redis发布订阅模式,一个队列可以被多个消费者同时订阅,当有消息到达时,会将该消息依次发送给每个订阅者,她是一种消息的广播形式,redis本身不做消费者的负载均衡,因此消费效率存在瓶颈;

4. 持久性

redis:redis的持久化是针对于整个redis缓存的内容,它有RDB和AOF两种持久化方式(redis持久化方式,后续更新),可以将整个redis实例持久化到磁盘,以此来做数据备份,防止异常情况下导致数据丢失。

rabbitmq:队列,每条消息都可以选择性持久化,持久化粒度更小,更灵活;

5. 队列监控

rabbitmq实现了后台监控平台,可以在该平台上看到所有创建的队列的详细情况,良好的后台管理平台可以方面我们更好的使用;

redis没有所谓的监控平台。

6. 性能

性能上,对于RabbitMQ和Redis的入队和出队操作,各执行100万次,每10万次记录一次执行时间。测试数据分为128Bytes、512Bytes、1K和10K四个不同大小的数据。实验表明:入队时,当数据比较小时Redis的性能要高于RabbitMQ,而如果数据大小超过了10K,Redis则慢的无法忍受;出队时,无论数据大小,Redis都表现出非常好的性能,而RabbitMQ的出队性能则远低于Redis。

总结

redis: 轻量级,低延迟,高并发,低可靠性;

rabbitmq:重量级,高可靠,异步,不保证实时;

rabbitmq是一个专门的AMQP协议队列,他的优势就在于提供可靠的队列服务,并且可做到异步,而redis主要是用于缓存的,redis的发布订阅模块,可用于实现及时性,且可靠性低的功能。

 

 

Redis发布订阅与ActiveMQ的比较

  1. ActiveMQ支持多种消息协议,包括AMQP,MQTT,Stomp等,并且支持JMS规范,但Redis没有提供对这些协议的支持;

  2. ActiveMQ提供持久化功能,但Redis无法对消息持久化存储,一旦消息被发送,如果没有订阅者接收,那么消息就会丢失;

  3. ActiveMQ提供了消息传输保障,当客户端连接超时或事务回滚等情况发生时,消息会被重新发送给客户端,Redis没有提供消息传输保障。

总之,ActiveMQ所提供的功能远比Redis发布订阅要复杂,毕竟Redis不是专门做发布订阅的。但是如果系统中已经有了Redis,并且只需要基本的发布订阅功能,可以考虑使用Redis的发布订阅机制以满足需求。

使用Redis实现MQ_沈林楠的专栏-CSDN博客

mikel阅读(1165)

来源: 使用Redis实现MQ_沈林楠的专栏-CSDN博客

要说明如何实现MQ之前,需要先说明一下MQ的分类,总共分为两类:

publish-subscribe

发布订阅模式有点类似于我们日常生活中订阅报纸。每年到年尾的时候,邮局就会发一本报纸集合让我们来选择订阅哪一个。在这个表里头列了所有出版发行的报纸,那么对于我们每一个订阅者来说,我们可以选择一份或者多份报纸。比如北京日报、潇湘晨报等。那么这些个我们订阅的报纸,就相当于发布订阅模式里的topic。有很多个人订阅报纸,也有人可能和我订阅了相同的报纸。那么,在这里,相当于我们在同一个topic里注册了。对于一份报纸发行方来说,它和所有的订阅者就构成了一个1对多的关系。这种关系如下图所示:
这里写图片描述

Producer-Consumer

Producer-Consumer的过程则理解起来更加简单。它好比是两个人打电话,这两个人是独享这一条通信链路的。一方发送消息,另外一方接收,就这么简单。在实际应用中因为有多个用户对使用p2p的链路,它的通信场景如下图所示:
这里写图片描述

Redis中的publish-subscribe

redis中已经实现了publish-subscribe,订阅者(Subscriber)可以订阅自己感兴趣的频道(Channel),发布者(Publisher)可以将消息发往指定的频道(Channel),正式通过这种方式,可以将消息的发送者和接收者解耦。另外,由于可以动态的Subscribe和Unsubscribe,也可以提高系统的灵活性和可扩展性。
打开redis客户端,使用SUBSCRIBE命令就可以订阅消息了,如:

SUBSCRIBE china hongkong

发布命令如下:

PUBLISH china "hahahaha"

这样在消息订阅的一方就可以接收到消息了,如下:

1) "message"
2) "china"
3) "hahahaha"

 

要想取消订阅可以使用:

UNSUBSCRIBE china hongkong

上面是如何使用redis客户端进行消息的订阅和发布,下面介绍一下如何使用代码实现,我们目前使用Spring Boot的工程框架,所以很多东西不需要手工去配置了,默认Spring Boot会帮我们实现RedisTemplate的bean,所以我们直接注入使用即可。

@Bean
    RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory,
                                            MessageListenerAdapter listenerAdapter) {
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);
        container.addMessageListener(listenerAdapter, new PatternTopic("chat"));
        return container;
    }

@Bean
MessageListenerAdapter listenerAdapter(Receiver receiver) {
    return new MessageListenerAdapter(receiver, "receiveMessage");
}

这里的代码的意思是将消息接收的处理方法和我们的redis订阅端进行一个连接。

return new MessageListenerAdapter(receiver, "receiveMessage");

这里就是接收消息的对象和方法,以后要扩展的话,可以做一个接口,可能通过不同的tag或者是其他的标志,来使用不同的对象处理消息。

container.addMessageListener(listenerAdapter, new PatternTopic("chat"));

代码这里也可以做成接收多个消息的topic,也是需要重构代码的。
使用RedisTemplate的convertAndSend方法就可以发送消息了,如下:

redisTemplate.convertAndSend("chat", "Hello from Redis!");

至此,redis的消息发布订阅就介绍完了

Redis中的Producer-Consumer

对于如何实现Producer-Consumer,redis并没有比较直接的方案,但是在list中提供了一个方法RPOPLPUSH,其中官方的资料是这样介绍的:

模式:安全的队列
Redis通常都被用做一个处理各种后台工作或消息任务的消息服务器。 一个简单的队列模式就是:生产者把消息放入一个列表中,等待消息的消费者用 RPOP 命令(用轮询方式), 或者用 BRPOP 命令(如果客户端使用阻塞操作会更好)来得到这个消息。
然而,因为消息有可能会丢失,所以这种队列并是不安全的。例如,当接收到消息后,出现了网络问题或者消费者端崩溃了, 那么这个消息就丢失了。
RPOPLPUSH (或者其阻塞版本的 BRPOPLPUSH) 提供了一种方法来避免这个问题:消费者端取到消息的同时把该消息放入一个正在处理中的列表。 当消息被处理了之后,该命令会使用 LREM 命令来移除正在处理中列表中的对应消息。
另外,可以添加一个客户端来监控这个正在处理中列表,如果有某些消息已经在这个列表中存在很长时间了(即超过一定的处理时限), 那么这个客户端会把这些超时消息重新加入到队列中。

首先说明了,为什么会有这个命令,就是因为在使用RPOP或者BRPOP命令的时候,会出现丢失的问题,所以需要在从一个队列弹出的时候立马将这个对象放到工作队列中,等完成之后再进行删除操作。

在实际的使用中,我们使用的是RPOPLPUSH的阻塞版,也就是说,在没有获取到消息的时候,这个获取的任务会一直阻塞在线程中,直到从队列中取出消息为止。

到目前为止,已经将理论介绍完毕了,下面就说说代码是如何实现的。

String recieveQueueMessage = redisTemplate.opsForList().rightPopAndLeftPush(waitQueue, workQueue, 0, TimeUnit.MILLISECONDS);

这是最核心的代码部分,使用的是RedisTemplate中用来操作list的接口rightPopAndLeftPush,他是将waitQueue列表最底部的信息弹出,推送到workQueue顶部,等待执行,如果执行都没有问题,再使用

redisTemplate.opsForList().remove(workQueue, REMOVE_COUNT, messageQueueEntity);

代码进行删除工作队列的操作,如果没有弹出信息,则继续进行等待,第一个参数是要移出的队列,第二个参数是移出的数目,第三个参数是要移出的内容。

那整体是如何进行工作的呢,下面贴一下整体的代码,然后再详细的进行说明:

@PostConstruct
public void init() {
    executorService = Executors.newFixedThreadPool(threadCount);
    LOGGER.info("INIT|RECIEVE|MESSAGE|START...");
    for(int i = 0; i < threadCount; i++){
        executorService.execute(() -> {
                    String threadName = Thread.currentThread().getName();
                    while(true) {
                        MessageQueueEntity message = channelAdapter.getMessage();
                        LOGGER.info("RECIEVE|MESSAGE|SUCCESS|{}|{}|", threadName, message);
                        LOGGER.info("START|HANDLE|MESSAGE|{}", message.getId());
                        try{
                            smsSendService.sendSms(message);
                        } catch(SmsSendErrorException e) {
                            LOGGER.error("SENDSMS|ERROR|{}|{}", message.getId(), e);
                        } catch(Exception e) {
                            e.printStackTrace();
                            LOGGER.error("SENDSMS|UNKNOW|ERROR|{}|{}", message.getId(), e);
                        }
                        LOGGER.info("FINISH|HANDLE|MESSAGE|{}", message.getId());
                    }
                }

        );
    }
}

@PreDestroy
public void destroy() {
    executorService.shutdown();
    LOGGER.info("SHUTDOWN|RECIEVE|MESSAGE|SUCCESS|");
}
  1. 可以看到使用了spring注解@PostConstruct和@PreDestroy,@PostConstruct注解是要在bean注入的时候去初始化的方法上的,所以当bean进行spring的注入之后,里面的内容就会自动的执行,因为我们要接收信息的时机必须是在启动服务器之后自动就执行,所以使用了这两个注解。
  2. 使用了Executors.newFixedThreadPool(threadCount)多线程,这里是固定产生threadCount个线程的线程池,无论是否使用,线程都会等待在那里,threadCount是根据配置来生成了,为了以后能够进行很好的扩展。
  3. for(int i = 0; i < threadCount; i++)这里的循环是有几个线程就要执行几次。
  4. 后面是比较核心的部分,while(true)可以保证在服务器启动到结束这之间,这几个线程一直在运行,并接收着信息。
  5. 接收之后就是之前讲过的使用redis的方式来进行队列的操作
  6. 这里值得一提的是,无论多少个线程,多少个消息,他们都是轮询的。

解决:SqlDateTime 溢出。必须介于 1/1/1753 12:00:00 AM 和 12/31/9999 11:59:59 PM 之间提示问题 - JiYF - 博客园

mikel阅读(695)

来源: 解决:SqlDateTime 溢出。必须介于 1/1/1753 12:00:00 AM 和 12/31/9999 11:59:59 PM 之间提示问题 – JiYF – 博客园

提示信息如下

“/”应用程序中的服务器错误。


SQLDateTime 溢出。必须介于 1/1/1753 12:00:00 AM 和 12/31/9999 11:59:59 PM 之间。

问题现象:

 

问题原因:

出现个问题的原因是:在更新或者添加数据,出现的错误,再给parameters传递值没有对DateTime类型字段没有传递值,默认为null

但是在SQLServer里面对DateTime类型取值范围是:介于 1/1/1753 12:00:00 AM 和 12/31/9999 11:59:59 PM 

 但是在而.NET Framework中,DateTime类型,最小值是1/1/0001 0:00:00  12/31/9999 11:59:59 PM

当在传递数据时候,没有给DateTime类型赋值,默认为null即:1/1/0001 0:00:00 显然不在SQLServer中Datetime类型的范围之中,就产生溢出,导致此错误信息

代码查找:

字段

 

数据添加

数据库中的datetime类型字段:

 

解决办法:

办法1.再给datetime类型得变量赋值:不让其等于null而且在区间于 1/1/1753 12:00:00 AM 和 12/31/9999 11:59:59 PM这个之间

办法2:使用System.Data.SQLTypes.SQLDateTime.MinValue替代System.DateTime类型,这样SqlDateTime的MinValue和Sql中DateTime的范围吻合,就不会再出现以上的错误了。

layui 框架 table插件 实现键盘快捷键 切换单元格编辑 - zakary_zhen - 博客园

mikel阅读(1070)

来源: layui 框架 table插件 实现键盘快捷键 切换单元格编辑 – zakary_zhen – 博客园

最近使用layui的框架时,发现table插件不支持键盘快捷键切换单元格,花了点时间实现此功能。

分享给有需要的朋友们~~~

 

效果图

 

代码:

 

1.支持 enter,上,下,右键 切换单元格,支持隐藏列跳过切换。

注:单元格必须开启了 edit:text 模式,才支持键盘切换。

使用方法:
1.在需要启用此功能的页面中table done回调函数中插入。
2.修改源代码,在源代码中直接插入,此方法就不需要在每一个页面的table done回调函数中 插入。

layui table 可编辑单元格 JS实现通过键盘上下左右键 光标焦点移动到旁边的编辑行/列_CaiXinXing的CSDN博客-CSDN博客

mikel阅读(2172)

来源: layui table 可编辑单元格 JS实现通过键盘上下左右键 光标焦点移动到旁边的编辑行/列_CaiXinXing的CSDN博客-CSDN博客

layui table 可编辑单元格 JS实现通过键盘上下左右键 光标焦点移动到旁边的编辑行/列

类似效果

上下左右键实现的代码

layui.config({
base: ‘/static/layuiadmin/’ //静态资源所在路径
}).extend({
index: ‘lib/index’ //主入口模块
}).use([‘index’,’table’,’form’],function(){

var $ = layui.JQuery;
var table = layui.table,
var form = layui.form;

//按键监听事件
$(document).on(‘keydown’, ‘.layui-input’,
function(event) {
var td = $(this).parent(‘td’);
var index = td.index();
var tr = td.parent(‘tr’);
switch (event.key) {
case “ArrowUp”://上键
tr[‘prev’]().children(‘td’).eq(index).click();
break;
case “ArrowDown”://下键
tr[‘next’]().children(‘td’).eq(index).click();
break;
case “ArrowLeft”://左键
td[‘prevAll’](‘[data-edit=”text”]:first’).click();
break;
case “ArrowRight”://右键
td[‘nextAll’](‘[data-edit=”text”]:first’).click();
break;
}
});

}

layui form表单 input输入框获取焦点后 阻止Enter回车自动提交 - &执念 - 博客园

mikel阅读(2446)

来源: layui form表单 input输入框获取焦点后 阻止Enter回车自动提交 – &执念 – 博客园

最简单的解决办法,不影响其他操作,给提交按钮增加 type=”button” 属性 完美解决

<button type="button" class="layui-btn" lay-submit lay-filter="*">立即提交</button>

layui表格编辑状态下回车进入下一单元格

$(document).keyup(function (event) {
if (event.keyCode == “13”) {
if ($($(“.layui-table-edit”).parent().next()).length > 0) {
$($(“.layui-table-edit”).parent().next()).click();
} else {
if ($(“.layui-table-edit”).parent().parent().next().find(“td[data-field=’OldMeterNo’]”).length > 0) {
$($(“.layui-table-edit”).parent().parent().next().find(“td[data-field=’OldMeterNo’]”)).click();
}
}
}
});

 

SQLServer之创建索引视图_小子pk了的博客-CSDN博客_索引视图

mikel阅读(678)

来源: SQLServer之创建索引视图_小子pk了的博客-CSDN博客_索引视图

索引视图创建注意事项

对视图创建的第一个索引必须是唯一聚集索引。 创建唯一聚集索引后,可以创建更多非聚集索引。 为视图创建唯一聚集索引可以提高查询性能,因为视图在数据库中的存储方式与具有聚集索引的表的存储方式相同。 查询优化器可使用索引视图加快执行查询的速度。 要使优化器考虑将该视图作为替换,并不需要在查询中引用该视图。

索引视图中列的 large_value_types_out_of_row 选项的设置继承的是基表中相应列的设置。 此值是使用 sp_tableoption设置的。从表达式组成的列的默认设置为 0。 这意味着大值类型存储在行内。

可以对已分区表创建索引视图,并可以由其自行分区。

若要防止 数据库引擎 使用索引视图,请在查询中包含 OPTION (EXPAND VIEWS) 提示。 此外,任何所列选项设置不正确均会阻止优化器使用视图上的索引。 有关 OPTION (EXPAND VIEWS) 提示的详细信息,请参阅 SELECT (Transact-SQL)。

若删除视图,该视图的所有索引也将被删除。 若删除聚集索引,视图的所有非聚集索引和自动创建的统计信息也将被删除。 视图中用户创建的统计信息受到维护。 非聚集索引可以分别删除。 删除视图的聚集索引将删除存储的结果集,并且优化器将重新像处理标准视图那样处理视图。

可以禁用表和视图的索引。 禁用表的聚集索引时,与该表关联的视图的索引也将被禁用。

索引视图创建要求

创建索引视图需要执行下列步骤并且这些步骤对于成功实现索引视图而言非常重要:

  1. 验证是否视图中将引用的所有现有表的 SET 选项都正确。
  2. 在创建任意表和视图之前,验证会话的 SET 选项设置是否正确。
  3. 验证视图定义是否为确定性的。
  4. 使用 WITH SCHEMABINDING 选项创建视图。
  5. 为视图创建唯一的聚集索引。

索引视图所需的 SET 选项

如果执行查询时启用不同的 SET 选项,则在 数据库引擎 中对同一表达式求值会产生不同结果。 例如,将 SET 选项 CONCAT_NULL_YIELDS_NULL 设置为 ON 后,表达式 ‘ abc ‘ + NULL 会返回值 NULL。 但将 CONCAT_NULL_YIEDS_NULL 设置为 OFF 后,同一表达式会生成 ‘ abc ‘。

为了确保能够正确维护视图并返回一致结果,索引视图需要多个 SET 选项具有固定值。 下表中的 SET 选项必须设置中显示的值为RequiredValue列出现以下情况时:

  • 创建视图和视图上的后续索引。
  • 在创建表时,在视图中引用的基表。
  • 对构成该索引视图的任何表执行了任何插入、更新或删除操作。 此要求包括大容量复制、复制和分布式查询等操作。
  • 查询优化器使用该索引视图生成查询计划。
    SET 选项 必需的值 默认服务器值 ,则“默认”

    OLE DB 和 ODBC 值

    ,则“默认”

    DB-Library 值

    ANSI_NULLS ON ON ON OFF
    ANSI_PADDING ON ON ON OFF
    ANSI_WARNINGS* ON ON ON OFF
    ARITHABORT ON ON OFF OFF
    CONCAT_NULL_YIELDS_NULL ON ON ON OFF
    NUMERIC_ROUNDABORT OFF OFF OFF OFF
    QUOTED_IDENTIFIER ON ON ON OFF

    *将 ANSI_WARNINGS 设置为 ON 隐式将 ARITHABORT 设置为 ON。

    如果使用的是 OLE DB 或 ODBC 服务器连接,则唯一必须要修改的值是 ARITHABORT 设置。 必须使用 sp_configure 在服务器级别或使用 SET 命令从应用程序中正确设置所有 DB-Library 值。极力建议在服务器的任一数据库中创建计算列的第一个索引视图或索引后,尽早在服务器范围内将 ARITHABORT 用户选项设置为 ON。

确定性视图

索引视图的定义必须是确定性的。 如果选择列表中的所有表达式、WHERE 和 GROUP BY 子句都具有确定性,则视图也具有确定性。 在使用特定的输入值集对确定性表达式求值时,它们始终返回相同的结果。 只有确定性函数可以加入确定性表达式。 例如,DATEADD 函数是确定性函数,因为对于其三个参数的任何给定参数值集它总是返回相同的结果。 GETDATE 不是确定性函数,因为总是使用相同的参数调用它,而它在每次执行时返回结果都不同。

要确定视图列是否为确定性列,请使用 COLUMNPROPERTY 函数的 IsDeterministic 属性。 使用 COLUMNPROPERTY 函数的 IsPrecise 属性确定具有架构绑定的视图中的确定性列是否为精确列。 如果为 TRUE,则 COLUMNPROPERTY 返回 1;如果为 FALSE,则返回 0;如果输入无效,则返回 NULL。 这意味着该列不是确定性列,也不是精确列。

即使是确定性表达式,如果其中包含浮点表达式,则准确结果也会取决于处理器体系结构或微代码的版本。 为了确保数据完整性,此类表达式只能作为索引视图的非键列加入。 不包含浮点表达式的确定性表达式称为精确表达式。 只有精确的确定性表达式才能加入键列,并包含在索引视图的 WHERE 或 GROUP BY 子句中。

其他要求

除对 SET 选项和确定性函数的要求外,还必须满足下列要求:

    • 执行 CREATE INDEX 的用户必须是视图所有者。
    • 创建索引时,IGNORE_DUP_KEY 选项必须设置为 OFF(默认设置)。
    • 在视图定义中,表必须由两部分组成的名称(即 schema.tablename**)引用。
    • 必须已使用 WITH SCHEMABINDING 选项创建了在视图中引用的用户定义函数。
    • 视图中引用的任何用户定义函数都必须由两部分组成的名称(即 schema.function**)引用。
    • 用户定义函数的数据访问属性必须为 NO SQL,外部访问属性必须是 NO。
    • 公共语言运行时 (CLR) 功能可以出现在视图的选择列表中,但不能作为聚集索引键定义的一部分。 CLR 函数不能出现在视图的 WHERE 子句中或视图中的 JOIN 运算的 ON 子句中。
    • 在视图定义中使用的 CLR 函数和 CLR 用户定义类型方法必须具有下表所示的属性设置。
      “属性” 注意
      DETERMINISTIC = TRUE 必须显式声明为 Microsoft .NET Framework 方法的属性。
      PRECISE = TRUE 必须显式声明为 .NET Framework 方法的属性。
      DATA ACCESS = NO SQL 通过将 DataAccess 属性设置为 DataAccessKind.None 并将 SystemDataAccess 属性设置为 SystemDataAccessKind.None 来确定。
      EXTERNAL ACCESS = NO 对于 CLR 例程,该属性的默认设置为 NO。
    • 必须使用 WITH SCHEMABINDING 选项创建视图。
    • 视图必须仅引用与视图位于同一数据库中的基表。 视图无法引用其他视图。
    • 视图定义中的 SELECT 语句不能包含下列 Transact-SQL 元素:
      COUNT ROWSET 函数(OPENDATASOURCE、OPENQUERY、OPENROWSET 和 OPENXML) OUTER 联接(LEFT、RIGHT 或 FULL)
      派生表(通过在 FROM 子句中指定 SELECT 语句来定义) 自联接 通过使用 SELECT * 或 SELECT table_name来指定列。*
      DISTINCT STDEV、STDEVP、VAR、VARP 或 AVG 公用表表达式 (CTE)
      float*text, ntext, image, XML,或filestream 子查询 包括排名或聚合开窗函数的 OVER 子句
      全文谓词(CONTAIN、FREETEXT) 引用可为 Null 的表达式的 SUM 函数 ORDER BY
      CLR 用户定义聚合函数 返回页首 CUBE、ROLLUP 或 GROUPING SETS 运算符
      MIN、MAX UNION、EXCEPT 或 INTERSECT 运算符 TABLESAMPLE
      表变量 OUTER APPLY 或 CROSS APPLY PIVOT、UNPIVOT
      稀疏列集 内联或多语句表值函数 OFFSET
      CHECKSUM_AGG

      *索引的视图可以包含float列; 但是,不能在聚集的索引键中包含此类列。

    • 如果存在 GROUP BY,则 VIEW 定义必须包含 COUNT_BIG(*),并且不得包含 HAVING。 这些 GROUP BY 限制仅适用于索引视图定义。 即使一个索引视图不满足这些 GROUP BY 限制,查询也可以在其执行计划中使用该视图。
    • 如果视图定义包含 GROUP BY 子句,则唯一聚集索引的键只能引用 GROUP BY 子句中指定的列。

使用SSMS数据库管理工具创建索引视图

1、连接数据库,选择数据库,展开数据库-》右键视图-》选择新建视图。

2、在添加表弹出框-》选择要创建视图的表、视图、函数、或者同义词等-》点击添加-》添加完成后选择关闭。

3、在关系图窗格中-》选择表与表之间关联的数据列-》选择列的其他排序或筛选条件。

4、右键点击空白处-》选择属性。

5、在视图属性窗格-》绑定到架构选择是-》非重复值选择是。

6、点击保存或者ctrl+s-》查看新创建的视图。

7、在对象资源管理器窗口-》展开视图-》选择视图-》右键点击索引-》选择新建索引-》选择聚集索引。

8、在新建索引弹出框-》选择索引数据列-》索引创建步骤可以参考本博主的创建索引博文-》点击确定(创建唯一聚集索引之后才能创建非聚集索引)。

9、在对象资源管理器中查看视图中的索引。

10、刷新视图-》可以创建非聚集索引,步骤同创建聚集索引(此处省略创建非聚集索引)。

11、点击保存或者ctrl+s-》刷新视图-》查看结果。

12、使用视图。

使用T-SQL脚本创建索引视图

语法:

–声明数据库引用
use 数据库;
go

–判断视图是否存在,如果存在则删除
if exists(select * from sys.views where name=视图名称)
drop view 视图名称;
go

–创建视图
create
view

–视图所属架构的名称。
–[schema_name][.]

–视图名称。 视图名称必须符合有关标识符的规则。 可以选择是否指定视图所有者名称。
[dbo][.]视图名称

–视图中的列使用的名称。 仅在下列情况下需要列名:列是从算术表达式、函数或常量派生的;两个或更多的列可能会具有相同的名称(通常是由于联接的原因);视图中的某个列的指定名称不同于其派生来源列的名称。 还可以在 SELECT 语句中分配列名。
–如果未指定 column,则视图列将获得与 SELECT 语句中的列相同的名称。
–column

with

–适用范围: SQL Server 2008 到 SQL Server 2017 和 Azure SQL Database。
–对 sys.syscomments 表中包含 CREATE VIEW 语句文本的项进行加密。 使用 WITH ENCRYPTION 可防止在 SQL Server 复制过程中发布视图。
–encryption,

–将视图绑定到基础表的架构。 如果指定了 SCHEMABINDING,则不能按照将影响视图定义的方式修改基表或表。 必须首先修改或删除视图定义本身,才能删除将要修改的表的依赖关系。
–使用 SCHEMABINDING 时,select_statement 必须包含所引用的表、视图或用户定义函数的两部分名称 (schema.object)。 所有被引用对象都必须在同一个数据库内。
–不能删除参与了使用 SCHEMABINDING 子句创建的视图的视图或表,除非该视图已被删除或更改而不再具有架构绑定。 否则, 数据库引擎将引发错误。
–另外,如果对参与具有架构绑定的视图的表执行 ALTER TABLE 语句,而这些语句又会影响视图定义,则这些语句将会失败。
schemabinding

–指定为引用视图的查询请求浏览模式的元数据时, SQL Server 实例将向 DB-Library、ODBC 和 OLE DB API 返回有关视图的元数据信息,而不返回基表的元数据信息。
–浏览模式元数据是 SQL Server 实例向这些客户端 API 返回的附加元数据。 如果使用此元数据,客户端 API 将可以实现可更新客户端游标。 浏览模式的元数据包含结果集中的列所属的基表的相关信息。
–对于使用 VIEW_METADATA 创建的视图,浏览模式的元数据在描述结果集内视图中的列时,将返回视图名,而不返回基表名。
–当使用 WITH VIEW_METADATA 创建视图时,如果该视图具有 INSTEAD OF INSERT 或 INSTEAD OF UPDATE 触发器,则视图的所有列(timestamp 列除外)都可更新。 有关可更新视图的详细信息,请参阅“备注”。
–view_metadata

–指定视图要执行的操作。
as
select_statement
go

–创建索引详情请参考索引博客
if not exists (select * from sys.indexes where name=索引名称)
–设置索引
create
unique
clustered
index
索引名称
on
dbo.视图名
(列名 [ asc | desc],列名  [ asc | desc],……);
go

示例:本示例演示在视图上创建一个唯一聚集索引。

–声明数据库引用
use testss;
go

–判断视图是否存在,如果存在则删除
if exists(select * from sys.views where name=’indexview1′)
drop view indexview1;
go

–创建视图
create
view

–视图所属架构的名称。
–[schema_name][.]

–视图名称。 视图名称必须符合有关标识符的规则。 可以选择是否指定视图所有者名称。
dbo.indexview1

–视图中的列使用的名称。 仅在下列情况下需要列名:列是从算术表达式、函数或常量派生的;两个或更多的列可能会具有相同的名称(通常是由于联接的原因);视图中的某个列的指定名称不同于其派生来源列的名称。 还可以在 SELECT 语句中分配列名。
–如果未指定 column,则视图列将获得与 SELECT 语句中的列相同的名称。
–column

with

–适用范围: SQL Server 2008 到 SQL Server 2017 和 Azure SQL Database。
–对 sys.syscomments 表中包含 CREATE VIEW 语句文本的项进行加密。 使用 WITH ENCRYPTION 可防止在 SQL Server 复制过程中发布视图。
–encryption,

–将视图绑定到基础表的架构。 如果指定了 SCHEMABINDING,则不能按照将影响视图定义的方式修改基表或表。 必须首先修改或删除视图定义本身,才能删除将要修改的表的依赖关系。
–使用 SCHEMABINDING 时,select_statement 必须包含所引用的表、视图或用户定义函数的两部分名称 (schema.object)。 所有被引用对象都必须在同一个数据库内。
–不能删除参与了使用 SCHEMABINDING 子句创建的视图的视图或表,除非该视图已被删除或更改而不再具有架构绑定。 否则, 数据库引擎将引发错误。
–另外,如果对参与具有架构绑定的视图的表执行 ALTER TABLE 语句,而这些语句又会影响视图定义,则这些语句将会失败。
schemabinding

–指定为引用视图的查询请求浏览模式的元数据时, SQL Server 实例将向 DB-Library、ODBC 和 OLE DB API 返回有关视图的元数据信息,而不返回基表的元数据信息。
–浏览模式元数据是 SQL Server 实例向这些客户端 API 返回的附加元数据。 如果使用此元数据,客户端 API 将可以实现可更新客户端游标。 浏览模式的元数据包含结果集中的列所属的基表的相关信息。
–对于使用 VIEW_METADATA 创建的视图,浏览模式的元数据在描述结果集内视图中的列时,将返回视图名,而不返回基表名。
–当使用 WITH VIEW_METADATA 创建视图时,如果该视图具有 INSTEAD OF INSERT 或 INSTEAD OF UPDATE 触发器,则视图的所有列(timestamp 列除外)都可更新。 有关可更新视图的详细信息,请参阅“备注”。
–view_metadata

–指定视图要执行的操作。
as
select a.id,a.age,a.height,a.name,b.id as classid from dbo.test1 as a
inner join dbo.test3 as b on a.classid=b.id
–要求对该视图执行的所有数据修改语句都必须符合 select_statement 中所设置的条件。 通过视图修改行时,WITH CHECK OPTION 可确保提交修改后,仍可通过视图看到数据。
–with check option;
go

if not exists (select * from sys.indexes where name=’umiqueindexview1′)
–设置索引
create
unique
clustered
index
umiqueindexview1
on
dbo.indexview1
(name asc);
go

示例结果:因为数据量太小,查询时间和效果不是很明显。